From f14db21745e1e149e03ad029ce9a3e96208d979d Mon Sep 17 00:00:00 2001 From: Icecream95 Date: Sun, 17 May 2020 19:52:23 +1200 Subject: [PATCH 0001/2859] Make disableShadowsForStateSet a no-op when shadows are disabled Otherwise the GPU has to do useless shadow comparisons when shadows are disabled. --- components/sceneutil/shadow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index a1cd1d660b..8c564f2249 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -69,6 +69,9 @@ namespace SceneUtil void ShadowManager::disableShadowsForStateSet(osg::ref_ptr stateset) { + if (!Settings::Manager::getBool("enable shadows", "Shadows")) + return; + int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); numberOfShadowMapsPerLight = std::max(1, std::min(numberOfShadowMapsPerLight, 8)); From dee91d12c2ee9633a20e37c4beb1a54982bad95d Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 13 Nov 2020 16:26:08 +0100 Subject: [PATCH 0002/2859] Update .travis.yml --- .travis.yml | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f457798a7..fb029fcf39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,7 @@ language: cpp branches: only: - master - - coverity_scan - /openmw-.*$/ -env: - global: - # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created - # via the "travis encrypt" command using the project repo's public key - - secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE=" cache: ccache addons: apt: @@ -27,28 +21,17 @@ addons: # The other ones from OpenMW ppa libbullet-dev, libopenscenegraph-dev, libmygui-dev ] - coverity_scan: # TODO: currently takes too long, disabled openmw/openmw-cs for now. - project: - name: "OpenMW/openmw" - description: "" - branch_pattern: coverity_scan - notification_email: 1122069+psi29a@users.noreply.github.com - build_command_prepend: "cov-configure --comptype gcc --compiler gcc-5 --template; cmake . -DBUILD_OPENMW=FALSE -DBUILD_OPENCS=FALSE" - build_command: "make VERBOSE=1 -j3" matrix: include: - name: OpenMW (all) on MacOS 10.15 with Xcode 11.6 os: osx osx_image: xcode11.6 - if: branch != coverity_scan - name: OpenMW (all) on Ubuntu Focal with GCC os: linux dist: focal - if: branch != coverity_scan - name: OpenMW (tests only) on Ubuntu Focal with GCC os: linux dist: focal - if: branch != coverity_scan env: - BUILD_TESTS_ONLY: 1 - name: OpenMW (openmw) on Ubuntu Focal with Clang's Static Analysis @@ -57,16 +40,7 @@ matrix: env: - MATRIX_EVAL="CC=clang && CXX=clang++" - ANALYZE="scan-build --force-analyze-debug-code --use-cc clang --use-c++ clang++" - if: branch != coverity_scan compiler: clang - - name: OpenMW Components Coverity Scan - os: linux - dist: focal - if: branch = coverity_scan -# allow_failures: -# - name: OpenMW (openmw) on Ubuntu Focal with GCC-10 -# env: -# - MATRIX_EVAL="CC=gcc-10 && CXX=g++-10" before_install: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi From df2ae6e86651935c4c3c95bec84bedcb9d011230 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 13 Nov 2020 16:29:11 +0100 Subject: [PATCH 0003/2859] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fb029fcf39..8ec35a6713 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: cpp +dist: focal branches: only: - master @@ -8,7 +9,6 @@ addons: apt: sources: - sourceline: 'ppa:openmw/openmw' - # - ubuntu-toolchain-r-test # for GCC-10 packages: [ # Dev build-essential, cmake, clang-tools, ccache, From e15716eb0c09dcaa834a99982c169042feaa9bc7 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 13 Nov 2020 16:38:53 +0100 Subject: [PATCH 0004/2859] Update .travis.yml --- .travis.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8ec35a6713..956997f25d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: cpp +os: [ linux, osx ] dist: focal branches: only: @@ -17,7 +18,7 @@ addons: # FFmpeg libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev, # Audio, Video and Misc. deps - libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev + libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev, # The other ones from OpenMW ppa libbullet-dev, libopenscenegraph-dev, libmygui-dev ] @@ -44,17 +45,17 @@ matrix: before_install: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi - - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ./CI/before_install.${TRAVIS_OS_NAME}.sh; fi + - ./CI/before_install.${TRAVIS_OS_NAME}.sh before_script: - ccache -z - - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ./CI/before_script.${TRAVIS_OS_NAME}.sh; fi + - ./CI/before_script.${TRAVIS_OS_NAME}.sh script: - cd ./build - - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi - - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi - - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi - - if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi + - ${ANALYZE} make -j3; + - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi + - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi + - if [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - cd "${TRAVIS_BUILD_DIR}" - ccache -s deploy: From 8b0475037d30453dad616ca69be3b090331c0bd7 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 13 Nov 2020 16:42:10 +0100 Subject: [PATCH 0005/2859] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 956997f25d..54ef006981 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,7 @@ before_install: - ./CI/before_install.${TRAVIS_OS_NAME}.sh before_script: - ccache -z - - ./CI/before_script.${TRAVIS_OS_NAME}.sh + - ./CI/before_script.${TRAVIS_OS_NAME}.sh script: - cd ./build - ${ANALYZE} make -j3; From 68836aa0fd35a8c3938177a16d197ca9d5d4224d Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 13 Nov 2020 16:44:06 +0100 Subject: [PATCH 0006/2859] Update .travis.yml --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 54ef006981..e84a9ad712 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,4 @@ language: cpp -os: [ linux, osx ] -dist: focal branches: only: - master From 8a6d3d1b4f2836e09d663354a4074d8bdce0e46d Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 13 Nov 2020 22:53:12 +0300 Subject: [PATCH 0007/2859] Minor fixes Fix extra semicolon Disable collision avoidance if AI is disabled --- apps/openmw/mwmechanics/actors.cpp | 3 +++ components/nifosg/controller.hpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 8f3675ef7e..047741baca 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1731,6 +1731,9 @@ namespace MWMechanics void Actors::predictAndAvoidCollisions() { + if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) + return; + const float minGap = 10.f; const float maxDistForPartialAvoiding = 200.f; const float maxDistForStrictAvoiding = 100.f; diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index a29fabefd0..996e4ef979 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -83,7 +83,7 @@ namespace NifOsg mLastLowKey = mKeys->mKeys.end(); mLastHighKey = mKeys->mKeys.end(); } - }; + } ValueInterpolator(std::shared_ptr keys, ValueT defaultVal = ValueT()) : mKeys(keys) From 117697ea227933cf8c3a6c4b784bc8d659991559 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 14 Nov 2020 01:12:32 +0300 Subject: [PATCH 0008/2859] Fix NiStringPalette loading --- components/nif/data.cpp | 16 ++-------------- components/nif/data.hpp | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 1eb5c40fe6..235825e4a6 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -460,21 +460,9 @@ void NiPalette::read(NIFStream *nif) void NiStringPalette::read(NIFStream *nif) { - unsigned int size = nif->getUInt(); - if (!size) - return; - std::vector source; - nif->getChars(source, size); - if (nif->getUInt() != size) + palette = nif->getString(); + if (nif->getUInt() != palette.size()) nif->file->warn("Failed size check in NiStringPalette"); - if (source[source.size()-1] != '\0') - source.emplace_back('\0'); - const char* buffer = source.data(); - while (static_cast(buffer - source.data()) < source.size()) - { - palette.emplace_back(buffer); - buffer += palette.back().size() + 1; - } } void NiBoolData::read(NIFStream *nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 4d13afb9d0..35f329573e 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -262,7 +262,7 @@ public: struct NiStringPalette : public Record { - std::vector palette; + std::string palette; void read(NIFStream *nif) override; }; From 89d73c5fc74ecc1a2f8c4b807f06967d5e83a960 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 14 Nov 2020 02:04:46 +0000 Subject: [PATCH 0009/2859] Only reroute stdout etc. to new console if not already redirected This should fix the issue where Windows Release builds (compiled as /SUBSYSTEM:WINDOWS instead of /SUBSYSTEM:CONSOLE) can't have their output redirected. Basically, a console application creates a console if not given one, so you get a console window behind OpenMW while it's running. It was decided that this was ugly, so we set Release builds to be windows applications, which don't get an automatic console and don't automatically connect to a console if given one anyway. Of course, we still wanted to actually be able to print to a console if given one, so we manually attach to the parent process' console if it exists, then reopen the standard streams connected to CON, the Windows pseudo-file representing the current console. This is a little like connecting a second wire into a dumb terminal in that you're pumping characters into the display rather than onto a pipeline, so output can't be redirected. It turns out, though, that if a /SUBSYSTEM:WINDOWS application has its standard streams redirected by the calling process, it still gets its handles as normal, so everything starts off connected just how we want it and we were clobbering this good setup with the straight-to-console fix. All we need to do to fix that is check if we've got valid standard handles and that they go somewhere useful, and if so, avoid reopening them once the console is attached. Simples. --- components/debug/debugging.cpp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index dfed077e37..c4f3af3072 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -11,11 +11,22 @@ namespace Debug { #ifdef _WIN32 + bool isRedirected(DWORD nStdHandle) + { + DWORD fileType = GetFileType(GetStdHandle(nStdHandle)); + + return (fileType == FILE_TYPE_DISK) || (fileType == FILE_TYPE_PIPE); + } + bool attachParentConsole() { if (GetConsoleWindow() != nullptr) return true; + bool inRedirected = isRedirected(STD_INPUT_HANDLE); + bool outRedirected = isRedirected(STD_OUTPUT_HANDLE); + bool errRedirected = isRedirected(STD_ERROR_HANDLE); + if (AttachConsole(ATTACH_PARENT_PROCESS)) { fflush(stdout); @@ -24,12 +35,21 @@ namespace Debug std::cerr.flush(); // this looks dubious but is really the right way - _wfreopen(L"CON", L"w", stdout); - _wfreopen(L"CON", L"w", stderr); - _wfreopen(L"CON", L"r", stdin); - freopen("CON", "w", stdout); - freopen("CON", "w", stderr); - freopen("CON", "r", stdin); + if (!inRedirected) + { + _wfreopen(L"CON", L"r", stdin); + freopen("CON", "r", stdin); + } + if (!outRedirected) + { + _wfreopen(L"CON", L"w", stdout); + freopen("CON", "w", stdout); + } + if (!errRedirected) + { + _wfreopen(L"CON", L"w", stderr); + freopen("CON", "w", stderr); + } return true; } From df9667e92348a9fadb4615c911ef54ef266ee05a Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 14 Nov 2020 03:42:15 +0300 Subject: [PATCH 0010/2859] Read NIF bounding volume data correctly --- .../nifloader/testbulletnifloader.cpp | 66 ++++++---- components/nif/node.hpp | 124 ++++++++++++++++-- components/nifbullet/bulletnifloader.cpp | 27 +++- components/nifbullet/bulletnifloader.hpp | 4 +- 4 files changed, 178 insertions(+), 43 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 72dcd30664..7da8f0fd5b 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -373,6 +373,7 @@ namespace TEST_F(TestBulletNifLoader, for_zero_num_roots_should_return_default) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; @@ -422,11 +423,13 @@ namespace { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; @@ -444,12 +447,14 @@ namespace { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; @@ -467,16 +472,19 @@ namespace { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNiNode.hasBounds = true; - mNiNode.boundXYZ = osg::Vec3f(4, 5, 6); - mNiNode.boundPos = osg::Vec3f(-4, -5, -6); + mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.bounds.box.extents = osg::Vec3f(4, 5, 6); + mNiNode.bounds.box.center = osg::Vec3f(-4, -5, -6); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; @@ -494,20 +502,24 @@ namespace { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNode2.hasBounds = true; - mNode2.boundXYZ = osg::Vec3f(4, 5, 6); - mNode2.boundPos = osg::Vec3f(-4, -5, -6); + mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); + mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); mNiNode.hasBounds = true; - mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); - mNiNode.boundPos = osg::Vec3f(-7, -8, -9); + mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9); + mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; @@ -524,21 +536,25 @@ namespace TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) { mNode.hasBounds = true; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNode2.hasBounds = true; mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode2.boundXYZ = osg::Vec3f(4, 5, 6); - mNode2.boundPos = osg::Vec3f(-4, -5, -6); + mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); + mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); mNiNode.hasBounds = true; - mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); - mNiNode.boundPos = osg::Vec3f(-7, -8, -9); + mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9); + mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; @@ -555,8 +571,9 @@ namespace TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) { mNode.hasBounds = true; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); @@ -588,8 +605,9 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_shape_with_bounds_but_with_null_collision_shape) { mNiTriShape.hasBounds = true; - mNiTriShape.boundXYZ = osg::Vec3f(1, 2, 3); - mNiTriShape.boundPos = osg::Vec3f(-1, -2, -3); + mNiTriShape.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiTriShape.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNiTriShape.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 72adfe06cd..0b958d2c25 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -16,6 +16,117 @@ namespace Nif struct NiNode; +struct NiBoundingVolume +{ + enum Type + { + SPHERE_BV = 0, + BOX_BV = 1, + CAPSULE_BV = 2, + LOZENGE_BV = 3, + UNION_BV = 4, + HALFSPACE_BV = 5 + }; + + struct NiSphereBV + { + osg::Vec3f center; + float radius{0.f}; + }; + + struct NiBoxBV + { + osg::Vec3f center; + Matrix3 axis; + osg::Vec3f extents; + }; + + struct NiCapsuleBV + { + osg::Vec3f center, axis; + float extent{0.f}, radius{0.f}; + }; + + struct NiLozengeBV + { + float radius{0.f}, extent0{0.f}, extent1{0.f}; + osg::Vec3f center, axis0, axis1; + }; + + struct NiHalfSpaceBV + { + osg::Vec3f center, normal; + }; + + unsigned int type; + NiSphereBV sphere; + NiBoxBV box; + NiCapsuleBV capsule; + NiLozengeBV lozenge; + std::vector children; + NiHalfSpaceBV plane; + void read(NIFStream* nif) + { + type = nif->getUInt(); + switch (type) + { + case SPHERE_BV: + { + sphere.center = nif->getVector3(); + sphere.radius = nif->getFloat(); + break; + } + case BOX_BV: + { + box.center = nif->getVector3(); + box.axis = nif->getMatrix3(); + box.extents = nif->getVector3(); + break; + } + case CAPSULE_BV: + { + capsule.center = nif->getVector3(); + capsule.axis = nif->getVector3(); + capsule.extent = nif->getFloat(); + capsule.radius = nif->getFloat(); + break; + } + case LOZENGE_BV: + { + lozenge.radius = nif->getFloat(); + lozenge.extent0 = nif->getFloat(); + lozenge.extent1 = nif->getFloat(); + lozenge.center = nif->getVector3(); + lozenge.axis0 = nif->getVector3(); + lozenge.axis1 = nif->getVector3(); + break; + } + case UNION_BV: + { + unsigned int numChildren = nif->getUInt(); + if (numChildren == 0) + break; + children.resize(numChildren); + for (NiBoundingVolume& child : children) + child.read(nif); + break; + } + case HALFSPACE_BV: + { + plane.center = nif->getVector3(); + plane.normal = nif->getVector3(); + break; + } + default: + { + std::stringstream error; + error << "Unhandled NiBoundingVolume type: " << type; + nif->file->fail(error.str()); + } + } + } +}; + /** A Node is an object that's part of the main NIF tree. It has parent node (unless it's the root), and transformation (location and rotation) relative to it's parent. @@ -31,9 +142,7 @@ public: // Bounding box info bool hasBounds{false}; - osg::Vec3f boundPos; - Matrix3 boundRot; - osg::Vec3f boundXYZ; // Box size + NiBoundingVolume bounds; void read(NIFStream *nif) override { @@ -48,13 +157,8 @@ public: if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) hasBounds = nif->getBoolean(); - if(hasBounds) - { - nif->getInt(); // always 1 - boundPos = nif->getVector3(); - boundRot = nif->getMatrix3(); - boundXYZ = nif->getVector3(); - } + if (hasBounds) + bounds.read(nif); // Reference to the collision object in Gamebryo files. if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) nif->skip(4); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 5b531121ee..b1461e5367 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -132,13 +132,14 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) if ((node = dynamic_cast(r))) break; } + const std::string filename = nif.getFilename(); if (!node) { - warn("Found no root nodes in NIF."); + warn("Found no root nodes in NIF file " + filename); return mShape; } - if (findBoundingBox(node)) + if (findBoundingBox(node, filename)) { const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); @@ -158,7 +159,6 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). // assume all nodes in the file will be animated - const auto filename = nif.getFilename(); const bool isAnimated = pathFileNameStartsWithX(filename); handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated); @@ -194,12 +194,25 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // Find a boundingBox in the node hierarchy. // Return: use bounding box for collision? -bool BulletNifLoader::findBoundingBox(const Nif::Node* node) +bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& filename) { if (node->hasBounds) { - mShape->mCollisionBoxHalfExtents = node->boundXYZ; - mShape->mCollisionBoxTranslate = node->boundPos; + unsigned int type = node->bounds.type; + switch (type) + { + case Nif::NiBoundingVolume::Type::BOX_BV: + mShape->mCollisionBoxHalfExtents = node->bounds.box.extents; + mShape->mCollisionBoxTranslate = node->bounds.box.center; + break; + default: + { + std::stringstream warning; + warning << "Unsupported NiBoundingVolume type " << type << " in node " << node->recIndex; + warning << " in file " << filename; + warn(warning.str()); + } + } if (node->flags & Nif::NiNode::Flag_BBoxCollision) { @@ -215,7 +228,7 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node) { if(!list[i].empty()) { - bool found = findBoundingBox (list[i].getPtr()); + bool found = findBoundingBox (list[i].getPtr(), filename); if (found) return true; } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index e423e51496..054b33fedb 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -40,7 +40,7 @@ class BulletNifLoader public: void warn(const std::string &msg) { - Log(Debug::Warning) << "NIFLoader: Warn:" << msg; + Log(Debug::Warning) << "NIFLoader: Warn: " << msg; } void fail(const std::string &msg) @@ -52,7 +52,7 @@ public: osg::ref_ptr load(const Nif::File& file); private: - bool findBoundingBox(const Nif::Node* node); + bool findBoundingBox(const Nif::Node* node, const std::string& filename); void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false); From e5fa457fe7bcd4d346b8d10c33e4ee6601bac37b Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 1 Nov 2020 17:14:59 +0100 Subject: [PATCH 0011/2859] Properly account for interleaved move of actors. Before this change, if an actor position was changed while the physics simulation was running, the simulation result would be discarded. It is fine in case of one off event such as teleport, but in the case of scripts making use of this functionality to make lifts or conveyor (such as Sotha Sil Expanded mod) it broke actor movement. To alleviate this issue, at the end of the simulation, the position of the Actor in the world is compared to the position it had at the beginning of the simulation. A difference indicate a force move occured. In this case, the Actor mPosition and mPreviousPosition are translated by the difference of position. Since the Actor position will be really set while the next simulation runs, we save it in the mNextPosition field. --- apps/openmw/mwphysics/actor.cpp | 90 ++++++++----------- apps/openmw/mwphysics/actor.hpp | 19 ++-- apps/openmw/mwphysics/mtphysics.cpp | 53 +++++------ apps/openmw/mwphysics/mtphysics.hpp | 1 - apps/openmw/mwphysics/physicssystem.cpp | 7 +- apps/openmw/mwphysics/physicssystem.hpp | 3 +- .../mwscript/transformationextensions.cpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 6 +- 8 files changed, 91 insertions(+), 94 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 5caaba5c9b..760e21cce1 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -75,9 +75,10 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateRotation(); updateScale(); updatePosition(); + setPosition(mWorldPosition, true); addCollisionMask(getCollisionMask()); - commitPositionChange(); + updateCollisionObjectPosition(); } Actor::~Actor() @@ -122,88 +123,77 @@ int Actor::getCollisionMask() const void Actor::updatePosition() { - std::unique_lock lock(mPositionMutex); - osg::Vec3f position = mPtr.getRefData().getPosition().asVec3(); + std::scoped_lock lock(mPositionMutex); + mWorldPosition = mPtr.getRefData().getPosition().asVec3(); +} - mPosition = position; - mPreviousPosition = position; +osg::Vec3f Actor::getWorldPosition() const +{ + std::scoped_lock lock(mPositionMutex); + return mWorldPosition; +} - mTransformUpdatePending = true; - updateCollisionObjectPosition(); +void Actor::setNextPosition(const osg::Vec3f& position) +{ + mNextPosition = position; +} + +osg::Vec3f Actor::getNextPosition() const +{ + return mNextPosition; } void Actor::updateCollisionObjectPosition() { + std::scoped_lock lock(mPositionMutex); + mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); osg::Vec3f newPosition = scaledTranslation + mPosition; mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition)); mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation)); - -} - -void Actor::commitPositionChange() -{ - std::unique_lock lock(mPositionMutex); - if (mScaleUpdatePending) - { - mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); - mScaleUpdatePending = false; - } - if (mTransformUpdatePending) - { - mCollisionObject->setWorldTransform(mLocalTransform); - mTransformUpdatePending = false; - } + mCollisionObject->setWorldTransform(mLocalTransform); } osg::Vec3f Actor::getCollisionObjectPosition() const { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); return Misc::Convert::toOsg(mLocalTransform.getOrigin()); } -void Actor::setPosition(const osg::Vec3f &position, bool updateCollisionObject) +void Actor::setPosition(const osg::Vec3f& position, bool reset) { - std::unique_lock lock(mPositionMutex); - if (mTransformUpdatePending) + if (reset) { - mCollisionObject->setWorldTransform(mLocalTransform); - mTransformUpdatePending = false; + mPreviousPosition = position; + mNextPosition = position; } else - { mPreviousPosition = mPosition; + mPosition = position; +} - mPosition = position; - if (updateCollisionObject) - { - updateCollisionObjectPosition(); - mCollisionObject->setWorldTransform(mLocalTransform); - } - } +void Actor::adjustPosition(const osg::Vec3f& offset) +{ + mPosition += offset; + mPreviousPosition += offset; } osg::Vec3f Actor::getPosition() const { - std::unique_lock lock(mPositionMutex); return mPosition; } osg::Vec3f Actor::getPreviousPosition() const { - std::unique_lock lock(mPositionMutex); return mPreviousPosition; } void Actor::updateRotation () { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude()) return; mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); - - mTransformUpdatePending = true; - updateCollisionObjectPosition(); } bool Actor::isRotationallyInvariant() const @@ -213,37 +203,33 @@ bool Actor::isRotationallyInvariant() const void Actor::updateScale() { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); float scale = mPtr.getCellRef().getScale(); osg::Vec3f scaleVec(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, false); mScale = scaleVec; - mScaleUpdatePending = true; scaleVec = osg::Vec3f(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, true); mRenderingScale = scaleVec; - - mTransformUpdatePending = true; - updateCollisionObjectPosition(); } osg::Vec3f Actor::getHalfExtents() const { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); return osg::componentMultiply(mHalfExtents, mScale); } osg::Vec3f Actor::getOriginalHalfExtents() const { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); return mHalfExtents; } osg::Vec3f Actor::getRenderingHalfExtents() const { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); return osg::componentMultiply(mHalfExtents, mRenderingScale); } @@ -274,7 +260,7 @@ void Actor::setWalkingOnWater(bool walkingOnWater) void Actor::setCanWaterWalk(bool waterWalk) { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); if (waterWalk != mCanWaterWalk) { mCanWaterWalk = waterWalk; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index ef7b368b99..00ba162afd 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -57,13 +57,20 @@ namespace MWPhysics bool isRotationallyInvariant() const; /** - * Set mPosition and mPreviousPosition to the position in the Ptr's RefData. This should be used + * Set mWorldPosition to the position in the Ptr's RefData. This is used by the physics simulation to account for * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation. */ void updatePosition(); + osg::Vec3f getWorldPosition() const; + + /** + * Used by the physics simulation to store the simulation result. Used in conjunction with mWorldPosition + * to account for e.g. scripted movements + */ + void setNextPosition(const osg::Vec3f& position); + osg::Vec3f getNextPosition() const; void updateCollisionObjectPosition(); - void commitPositionChange(); /** * Returns the half extents of the collision body (scaled according to collision scale) @@ -83,9 +90,9 @@ namespace MWPhysics /** * Store the current position into mPreviousPosition, then move to this position. - * Optionally, inform the physics engine about the change of position. */ - void setPosition(const osg::Vec3f& position, bool updateCollisionObject=true); + void setPosition(const osg::Vec3f& position, bool reset=false); + void adjustPosition(const osg::Vec3f& offset); osg::Vec3f getPosition() const; @@ -159,11 +166,11 @@ namespace MWPhysics osg::Vec3f mScale; osg::Vec3f mRenderingScale; + osg::Vec3f mWorldPosition; + osg::Vec3f mNextPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; btTransform mLocalTransform; - bool mScaleUpdatePending; - bool mTransformUpdatePending; mutable std::mutex mPositionMutex; osg::Vec3f mForce; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index f105efce5e..1b99b4c3f6 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -102,9 +102,18 @@ namespace stats.addToFallHeight(-actorData.mFallHeight); } - osg::Vec3f interpolateMovements(const MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) + osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = timeAccum / physicsDt; + + // account for force change of actor's position in the main thread + const auto correction = actorData.mActorRaw->getWorldPosition() - actorData.mOrigin; + if (correction.length() != 0) + { + actorData.mActorRaw->adjustPosition(correction); + actorData.mPosition = actorData.mActorRaw->getPosition(); + } + return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } @@ -182,7 +191,6 @@ namespace MWPhysics mPostSimBarrier = std::make_unique(mNumThreads, [&]() { - udpateActorsAabbs(); mNewFrame = false; if (mLOSCacheExpiry >= 0) { @@ -229,6 +237,9 @@ namespace MWPhysics updateMechanics(data); if (mAdvanceSimulation) updateStandingCollision(data, standingCollisions); + + if (mMovementResults.find(data.mPtr) != mMovementResults.end()) + data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); } } @@ -245,10 +256,6 @@ namespace MWPhysics if (mAdvanceSimulation) mWorldFrameData = std::make_unique(); - // update each actor position based on latest data - for (auto& data : mActorsFrameData) - data.updatePosition(); - // we are asked to skip the simulation (load a savegame for instance) // just return the actors' reference position without applying the movements if (skipSimulation) @@ -256,7 +263,10 @@ namespace MWPhysics standingCollisions.clear(); mMovementResults.clear(); for (const auto& m : mActorsFrameData) - mMovementResults[m.mPtr] = m.mPosition; + { + m.mActorRaw->setPosition(m.mActorRaw->getWorldPosition(), true); + mMovementResults[m.mPtr] = m.mActorRaw->getWorldPosition(); + } return mMovementResults; } @@ -271,6 +281,11 @@ namespace MWPhysics for (auto& data : mActorsFrameData) updateStandingCollision(data, standingCollisions); } + for (auto& data : mActorsFrameData) + { + if (mMovementResults.find(data.mPtr) != mMovementResults.end()) + data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); + } return mMovementResults; } @@ -427,7 +442,7 @@ namespace MWPhysics { if (const auto actor = std::dynamic_pointer_cast(p)) { - actor->commitPositionChange(); + actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } else if (const auto object = std::dynamic_pointer_cast(p)) @@ -485,28 +500,17 @@ namespace MWPhysics { if(const auto actor = actorData.mActor.lock()) { - if (actorData.mPosition == actor->getPosition()) - actor->setPosition(actorData.mPosition, false); // update previous position to make sure interpolation is correct - else + bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition(); + actorData.mActorRaw->setPosition(actorData.mPosition); + if (positionChanged) { - actorData.mPositionChanged = true; - actor->setPosition(actorData.mPosition); + actor->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } } } } - void PhysicsTaskScheduler::udpateActorsAabbs() - { - std::unique_lock lock(mCollisionWorldMutex); - for (const auto& actorData : mActorsFrameData) - if (actorData.mPositionChanged) - { - if(const auto actor = actorData.mActor.lock()) - mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); - } - } - bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) { btVector3 pos1 = Misc::Convert::toBullet(actor1->getCollisionObjectPosition() + osg::Vec3f(0,0,actor1->getHalfExtents().z() * 0.9)); // eye level @@ -538,6 +542,5 @@ namespace MWPhysics mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt); updateMechanics(actorData); } - udpateActorsAabbs(); } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 100e71a907..84ea93c088 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -49,7 +49,6 @@ namespace MWPhysics void syncComputation(); void worker(); void updateActorsPositions(); - void udpateActorsAabbs(); bool hasLineOfSight(const Actor* actor1, const Actor* actor2); void refreshLOSCache(); void updateAabbs(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9a777bd454..6b94ef43b6 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -883,7 +883,7 @@ namespace MWPhysics ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mPositionChanged(false), mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), + mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() { const MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -893,10 +893,9 @@ namespace MWPhysics mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0; mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead(); mWasOnGround = actor->getOnGround(); - } - void ActorFrameData::updatePosition() - { + mActorRaw->updatePosition(); + mOrigin = mActorRaw->getNextPosition(); mPosition = mActorRaw->getPosition(); if (mMoveToWaterSurface) { diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 36ef762d3e..4844b5e8ea 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,14 +78,12 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); - void updatePosition(); std::weak_ptr mActor; Actor* mActorRaw; MWWorld::Ptr mPtr; MWWorld::Ptr mStandingOn; bool mFlying; bool mSwimming; - bool mPositionChanged; bool mWasOnGround; bool mWantJump; bool mDidJump; @@ -97,6 +95,7 @@ namespace MWPhysics float mOldHeight; float mFallHeight; osg::Vec3f mMovement; + osg::Vec3f mOrigin; osg::Vec3f mPosition; ESM::Position mRefpos; }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 41df1870c5..ce45729b3b 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,7 +32,11 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - MWBase::Environment::get().getWorld()->queueMovement(actor, diff); + { + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + actorPos += diff; + MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z()); + } } template diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index be32765ad4..ad6c337909 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1504,11 +1504,11 @@ namespace MWWorld const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements); mDiscardMovements = false; - for(const auto& result : results) + for(const auto& [actor, position]: results) { // Handle player last, in case a cell transition occurs - if(result.first != getPlayerPtr()) - moveObjectImp(result.first, result.second.x(), result.second.y(), result.second.z(), false); + if(actor != getPlayerPtr()) + moveObjectImp(actor, position.x(), position.y(), position.z(), false); } const auto player = results.find(getPlayerPtr()); From d64ed6cf53f9d35edfd9dddc5521939db89cb4f5 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 7 Nov 2020 18:30:52 +0100 Subject: [PATCH 0012/2859] Get rid of the StandingActorsMap. Just embed the necessary info into Actor class. --- apps/openmw/mwphysics/actor.cpp | 14 ++++++- apps/openmw/mwphysics/actor.hpp | 4 ++ apps/openmw/mwphysics/mtphysics.cpp | 25 +++--------- apps/openmw/mwphysics/mtphysics.hpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 51 +++++++++---------------- apps/openmw/mwphysics/physicssystem.hpp | 8 ---- 6 files changed, 40 insertions(+), 64 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 760e21cce1..0f3d69d217 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -19,7 +19,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) - : mCanWaterWalk(false), mWalkingOnWater(false) + : mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents) , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) @@ -268,4 +268,16 @@ void Actor::setCanWaterWalk(bool waterWalk) } } +MWWorld::Ptr Actor::getStandingOnPtr() const +{ + std::scoped_lock lock(mPositionMutex); + return mStandingOnPtr; +} + +void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr) +{ + std::scoped_lock lock(mPositionMutex); + mStandingOnPtr = ptr; +} + } diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 00ba162afd..6b23b31d3d 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -144,7 +144,11 @@ namespace MWPhysics void setWalkingOnWater(bool walkingOnWater); bool isWalkingOnWater() const; + MWWorld::Ptr getStandingOnPtr() const; + void setStandingOnPtr(const MWWorld::Ptr& ptr); + private: + MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world void updateCollisionMask(); void addCollisionMask(int collisionMask); diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 1b99b4c3f6..ff676413bd 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -82,14 +82,6 @@ namespace ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; } - void updateStandingCollision(MWPhysics::ActorFrameData& actorData, MWPhysics::CollisionMap& standingCollisions) - { - if (!actorData.mStandingOn.isEmpty()) - standingCollisions[actorData.mPtr] = actorData.mStandingOn; - else - standingCollisions.erase(actorData.mPtr); - } - void updateMechanics(MWPhysics::ActorFrameData& actorData) { if (actorData.mDidJump) @@ -215,7 +207,7 @@ namespace MWPhysics thread.join(); } - const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, CollisionMap& standingCollisions, bool skipSimulation) + const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skipSimulation) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. @@ -225,9 +217,6 @@ namespace MWPhysics // start by finishing previous background computation if (mNumThreads != 0) { - if (mAdvanceSimulation) - standingCollisions.clear(); - for (auto& data : mActorsFrameData) { // Ignore actors that were deleted while the background thread was running @@ -236,7 +225,7 @@ namespace MWPhysics updateMechanics(data); if (mAdvanceSimulation) - updateStandingCollision(data, standingCollisions); + data.mActorRaw->setStandingOnPtr(data.mStandingOn); if (mMovementResults.find(data.mPtr) != mMovementResults.end()) data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); @@ -260,10 +249,10 @@ namespace MWPhysics // just return the actors' reference position without applying the movements if (skipSimulation) { - standingCollisions.clear(); mMovementResults.clear(); for (const auto& m : mActorsFrameData) { + m.mActorRaw->setStandingOnPtr(nullptr); m.mActorRaw->setPosition(m.mActorRaw->getWorldPosition(), true); mMovementResults[m.mPtr] = m.mActorRaw->getWorldPosition(); } @@ -275,14 +264,10 @@ namespace MWPhysics mMovementResults.clear(); syncComputation(); - if (mAdvanceSimulation) - { - standingCollisions.clear(); - for (auto& data : mActorsFrameData) - updateStandingCollision(data, standingCollisions); - } for (auto& data : mActorsFrameData) { + if (mAdvanceSimulation) + data.mActorRaw->setStandingOnPtr(data.mStandingOn); if (mMovementResults.find(data.mPtr) != mMovementResults.end()) data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 84ea93c088..ef1c90b873 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -30,7 +30,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, CollisionMap& standingCollisions, bool skip); + const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skip); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 6b94ef43b6..5de26fdfcb 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -148,11 +148,11 @@ namespace MWPhysics if (!physactor || !physactor->getOnGround()) return false; - CollisionMap::const_iterator found = mStandingCollisions.find(actor); - if (found == mStandingCollisions.end()) + const auto obj = physactor->getStandingOnPtr(); + if (obj.isEmpty()) return true; // assume standing on terrain (which is a non-object, so not collision tracked) - ObjectMap::const_iterator foundObj = mObjects.find(found->second); + ObjectMap::const_iterator foundObj = mObjects.find(obj); if (foundObj == mObjects.end()) return false; @@ -501,22 +501,6 @@ namespace MWPhysics } } - void PhysicsSystem::updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated) - { - CollisionMap::iterator found = map.find(old); - if (found != map.end()) - { - map[updated] = found->second; - map.erase(found); - } - - for (auto& collision : map) - { - if (collision.second == old) - collision.second = updated; - } - } - void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { ObjectMap::iterator found = mObjects.find(old); @@ -537,7 +521,11 @@ namespace MWPhysics mActors.emplace(updated, std::move(actor)); } - updateCollisionMapPtr(mStandingCollisions, old, updated); + for (auto& [_, actor] : mActors) + { + if (actor->getStandingOnPtr() == old) + actor->setStandingOnPtr(updated); + } } Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) @@ -675,7 +663,6 @@ namespace MWPhysics void PhysicsSystem::clearQueuedMovement() { mMovementQueue.clear(); - mStandingCollisions.clear(); } const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation) @@ -688,7 +675,7 @@ namespace MWPhysics mTimeAccum -= numSteps * mPhysicsDt; - return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), mStandingCollisions, skipSimulation); + return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), skipSimulation); } std::vector PhysicsSystem::prepareFrameData(int numSteps) @@ -700,10 +687,8 @@ namespace MWPhysics { const auto foundActor = mActors.find(character); if (foundActor == mActors.end()) // actor was already removed from the scene - { - mStandingCollisions.erase(character); continue; - } + auto physicActor = foundActor->second; float waterlevel = -std::numeric_limits::max(); @@ -734,7 +719,7 @@ namespace MWPhysics // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. MWWorld::Ptr standingOn; if (numSteps == 0) - standingOn = mStandingCollisions[character]; + standingOn = physicActor->getStandingOnPtr(); actorsFrameData.emplace_back(std::move(physicActor), character, standingOn, moveToWaterSurface, movement, slowFall, waterlevel); } @@ -774,20 +759,18 @@ namespace MWPhysics bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const { - for (const auto& standingActor : mStandingCollisions) - { - if (standingActor.first == actor && standingActor.second == object) - return true; - } + const auto physActor = mActors.find(actor); + if (physActor != mActors.end()) + return physActor->second->getStandingOnPtr() == object; return false; } void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector &out) const { - for (const auto& standingActor : mStandingCollisions) + for (const auto& [_, actor] : mActors) { - if (standingActor.second == object) - out.push_back(standingActor.first); + if (actor->getStandingOnPtr() == object) + out.emplace_back(actor->getPtr()); } } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4844b5e8ea..ccfca5422a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -50,7 +50,6 @@ class btVector3; namespace MWPhysics { using PtrPositionList = std::map; - using CollisionMap = std::map; class HeightField; class Object; @@ -272,13 +271,6 @@ namespace MWPhysics bool mDebugDrawEnabled; - // Tracks standing collisions happening during a single frame. - // This will detect standing on an object, but won't detect running e.g. against a wall. - CollisionMap mStandingCollisions; - - // replaces all occurrences of 'old' in the map by 'updated', no matter if it's a key or value - void updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated); - using PtrVelocityList = std::vector>; PtrVelocityList mMovementQueue; From bb5213670c41634c666dd705e196832c74b8aa52 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Mon, 16 Nov 2020 11:09:08 +0100 Subject: [PATCH 0013/2859] Use bigger hammer to set Actor's position after teleporting. Otherwise traceDown() would use old collision object transform and gives incorrect results, making the Actor "fall" in the new position. --- apps/openmw/mwphysics/actor.cpp | 24 ++++++++++++------------ apps/openmw/mwphysics/actor.hpp | 3 ++- apps/openmw/mwphysics/mtphysics.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 0f3d69d217..430bd4deef 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -74,11 +74,8 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateRotation(); updateScale(); - updatePosition(); - setPosition(mWorldPosition, true); - + resetPosition(); addCollisionMask(getCollisionMask()); - updateCollisionObjectPosition(); } Actor::~Actor() @@ -160,15 +157,9 @@ osg::Vec3f Actor::getCollisionObjectPosition() const return Misc::Convert::toOsg(mLocalTransform.getOrigin()); } -void Actor::setPosition(const osg::Vec3f& position, bool reset) +void Actor::setPosition(const osg::Vec3f& position) { - if (reset) - { - mPreviousPosition = position; - mNextPosition = position; - } - else - mPreviousPosition = mPosition; + mPreviousPosition = mPosition; mPosition = position; } @@ -178,6 +169,15 @@ void Actor::adjustPosition(const osg::Vec3f& offset) mPreviousPosition += offset; } +void Actor::resetPosition() +{ + updatePosition(); + mPreviousPosition = mWorldPosition; + mPosition = mWorldPosition; + mNextPosition = mWorldPosition; + updateCollisionObjectPosition(); +} + osg::Vec3f Actor::getPosition() const { return mPosition; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 6b23b31d3d..3d6f93ae05 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -91,7 +91,8 @@ namespace MWPhysics /** * Store the current position into mPreviousPosition, then move to this position. */ - void setPosition(const osg::Vec3f& position, bool reset=false); + void setPosition(const osg::Vec3f& position); + void resetPosition(); void adjustPosition(const osg::Vec3f& offset); osg::Vec3f getPosition() const; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index ff676413bd..dbb714fac5 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -253,7 +253,7 @@ namespace MWPhysics for (const auto& m : mActorsFrameData) { m.mActorRaw->setStandingOnPtr(nullptr); - m.mActorRaw->setPosition(m.mActorRaw->getWorldPosition(), true); + m.mActorRaw->resetPosition(); mMovementResults[m.mPtr] = m.mActorRaw->getWorldPosition(); } return mMovementResults; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5de26fdfcb..42ead3606e 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -433,8 +433,8 @@ namespace MWPhysics ActorMap::iterator found = mActors.find(ptr); if (found == mActors.end()) return ptr.getRefData().getPosition().asVec3(); - else - return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); + found->second->resetPosition(); + return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); } void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) From 7768556ce64c240bdcf361910e6520b2e32b224f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 16 Nov 2020 21:01:20 +0000 Subject: [PATCH 0014/2859] Set dummy state when disabling shadows indoors As we don't reconfigure all shaders without shadows when we disable them indoors (as it'd probably add a hitch to transitioning in and out) we need to set up dummy state so the shaders don't do anything illegal. This hadn't had symptoms for most objects as when indoors, nearly everything would be drawn first in one of the water RTTs, which had dummy state to disable shadows already. This wasn't true of the water plane itself, though, yet somehow it took until just now for anyone to report that. This resolves vtastek's issue where the water would be invisible indoors --- components/sceneutil/mwshadowtechnique.cpp | 26 +++++++++++++++++++++- components/sceneutil/mwshadowtechnique.hpp | 3 ++- components/sceneutil/shadow.cpp | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 0411dbc431..c49a147776 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -820,9 +820,10 @@ void MWShadowTechnique::enableShadows() _enableShadows = true; } -void MWShadowTechnique::disableShadows() +void MWShadowTechnique::disableShadows(bool setDummyState) { _enableShadows = false; + mSetDummyStateWhenDisabled = setDummyState; } void SceneUtil::MWShadowTechnique::enableDebugHUD() @@ -914,7 +915,28 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) { if (!_enableShadows) { + if (mSetDummyStateWhenDisabled) + { + osg::ref_ptr dummyState = new osg::StateSet(); + + ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + int baseUnit = settings->getBaseShadowTextureUnit(); + int endUnit = baseUnit + settings->getNumShadowMapsPerLight(); + for (int i = baseUnit; i < endUnit; ++i) + { + dummyState->setTextureAttributeAndModes(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); + } + _shadowedScene->osg::Group::traverse(cv); + + if (mSetDummyStateWhenDisabled) + cv.popStateSet(); + return; } @@ -1577,6 +1599,8 @@ void MWShadowTechnique::createShaders() _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); _fallbackShadowMapTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); _fallbackShadowMapTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); + _fallbackShadowMapTexture->setShadowComparison(true); + _fallbackShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); } diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index a7208cfa6d..5125247dda 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -67,7 +67,7 @@ namespace SceneUtil { virtual void enableShadows(); - virtual void disableShadows(); + virtual void disableShadows(bool setDummyState = false); virtual void enableDebugHUD(); @@ -252,6 +252,7 @@ namespace SceneUtil { osg::ref_ptr _program; bool _enableShadows; + bool mSetDummyStateWhenDisabled; double _splitPointUniformLogRatio = 0.5; double _splitPointDeltaBias = 0.0; diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 1e14fbbb13..35646b834e 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -168,7 +168,7 @@ namespace SceneUtil if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask); else - mShadowTechnique->disableShadows(); + mShadowTechnique->disableShadows(true); } void ShadowManager::enableOutdoorMode() From 06ae2a0536274b45fc04d0b1a1a3585fda25ac3a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 16 Nov 2020 21:07:30 +0000 Subject: [PATCH 0015/2859] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f19783ef5..51babd907b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Bug #5644: Summon effects running on the player during game initialization cause crashes Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval + Bug #5688: Water shader broken indoors with enable indoor shadows = false Feature #390: 3rd person look "over the shoulder" Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container From 211894a178481ff4ae5434840e4da1bbacf32247 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 17 Nov 2020 16:14:05 +0000 Subject: [PATCH 0016/2859] Fix extraction with 7z 9.10 This is still used in the wild as lots of people install 7zip and never update it because it works. We can't check the version and abort if it's too old as the changelog doesn't make it clear which version fixed the behaviour. --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 2a0db9c91d..0ef67f47ef 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -913,7 +913,7 @@ printf "LZ4 1.9.2... " printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf LZ4_1.9.2 - eval 7z x -y lz4_win${BITS}_v1_9_2.7z -o./LZ4_1.9.2 $STRIP + eval 7z x -y lz4_win${BITS}_v1_9_2.7z -o$(real_pwd)/LZ4_1.9.2 $STRIP fi export LZ4DIR="$(real_pwd)/LZ4_1.9.2" add_cmake_opts -DLZ4_INCLUDE_DIR="${LZ4DIR}/include" \ From 48ea9960b92c2e31c6888e04775aae8aaab4981a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 17 Nov 2020 16:45:13 +0000 Subject: [PATCH 0017/2859] Fix Debian GCC timeout on forks --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 25a04d536e..810e23d38e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,6 +37,8 @@ Debian_GCC: CC: gcc CXX: g++ CCACHE_SIZE: 3G + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + timeout: 2h Debian_GCC_tests: extends: .Debian From 06d1e70aacba6b18a9751e99db5625dc4a7e39ff Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 18 Nov 2020 15:34:21 +0000 Subject: [PATCH 0018/2859] Make Bullet DebugDrawer's default state match the physics system --- apps/openmw/mwphysics/physicssystem.cpp | 2 +- apps/openmw/mwrender/bulletdebugdraw.cpp | 8 +++----- apps/openmw/mwrender/bulletdebugdraw.hpp | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 42ead3606e..8a58919ca5 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -93,7 +93,7 @@ namespace MWPhysics } mTaskScheduler = std::make_unique(mPhysicsDt, mCollisionWorld); - mDebugDrawer = std::make_unique(mParentNode, mCollisionWorld.get()); + mDebugDrawer = std::make_unique(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled); } PhysicsSystem::~PhysicsSystem() diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index 61570be452..00529ef80d 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -14,13 +14,11 @@ namespace MWRender { -DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world) +DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world, int debugMode) : mParentNode(parentNode), - mWorld(world), - mDebugOn(true) + mWorld(world) { - - createGeometry(); + setDebugMode(debugMode); } void DebugDrawer::createGeometry() diff --git a/apps/openmw/mwrender/bulletdebugdraw.hpp b/apps/openmw/mwrender/bulletdebugdraw.hpp index f07ce2e2ed..ec421bd742 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.hpp +++ b/apps/openmw/mwrender/bulletdebugdraw.hpp @@ -48,7 +48,7 @@ protected: public: - DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world); + DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world, int debugMode = 1); ~DebugDrawer(); void step(); From c126d8801f938c6b188d8ab4bd708a9f4e48ba82 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 18 Nov 2020 17:28:09 +0100 Subject: [PATCH 0019/2859] Fix #5689 --- apps/openmw/mwstate/statemanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index e409e5b3b9..72e4b1ae02 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -48,8 +48,8 @@ void MWState::StateManager::cleanup (bool force) MWBase::Environment::get().getDialogueManager()->clear(); MWBase::Environment::get().getJournal()->clear(); MWBase::Environment::get().getScriptManager()->clear(); - MWBase::Environment::get().getWorld()->clear(); MWBase::Environment::get().getWindowManager()->clear(); + MWBase::Environment::get().getWorld()->clear(); MWBase::Environment::get().getInputManager()->clear(); MWBase::Environment::get().getMechanicsManager()->clear(); From 9363bc2d4825705fcf8c60f42b150828b3ba332b Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Nov 2020 18:03:29 +0100 Subject: [PATCH 0020/2859] Update recastnavigation to 6624e7aef5e15df11cb2f5673574df8e4c96af6a --- extern/recastnavigation/.id | 2 +- extern/recastnavigation/CMakeLists.txt | 6 ++++++ .../DebugUtils/CMakeLists.txt | 19 ++++++++++--------- extern/recastnavigation/Detour/CMakeLists.txt | 19 ++++++++++--------- .../DetourCrowd/CMakeLists.txt | 19 ++++++++++--------- .../DetourCrowd/Source/DetourCrowd.cpp | 8 +++++--- .../DetourTileCache/CMakeLists.txt | 19 ++++++++++--------- extern/recastnavigation/Recast/CMakeLists.txt | 19 ++++++++++--------- 8 files changed, 62 insertions(+), 49 deletions(-) diff --git a/extern/recastnavigation/.id b/extern/recastnavigation/.id index 81e564671a..b53727263d 100644 --- a/extern/recastnavigation/.id +++ b/extern/recastnavigation/.id @@ -1 +1 @@ -57610fa6ef31b39020231906f8c5d40eaa8294ae +6624e7aef5e15df11cb2f5673574df8e4c96af6a diff --git a/extern/recastnavigation/CMakeLists.txt b/extern/recastnavigation/CMakeLists.txt index 0d31c2e367..cf35af1e87 100644 --- a/extern/recastnavigation/CMakeLists.txt +++ b/extern/recastnavigation/CMakeLists.txt @@ -13,6 +13,12 @@ SET(VERSION 1.0.0) option(RECASTNAVIGATION_STATIC "Build static libraries" ON) +if(MSVC AND BUILD_SHARED_LIBS) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +include(GNUInstallDirs) + add_subdirectory(DebugUtils) add_subdirectory(Detour) add_subdirectory(DetourCrowd) diff --git a/extern/recastnavigation/DebugUtils/CMakeLists.txt b/extern/recastnavigation/DebugUtils/CMakeLists.txt index 8b6a3fcf62..21d8f8f9d9 100644 --- a/extern/recastnavigation/DebugUtils/CMakeLists.txt +++ b/extern/recastnavigation/DebugUtils/CMakeLists.txt @@ -1,12 +1,8 @@ file(GLOB SOURCES Source/*.cpp) - -if (RECASTNAVIGATION_STATIC) - add_library(DebugUtils STATIC ${SOURCES}) -else() - add_library(DebugUtils SHARED ${SOURCES}) -endif() +add_library(DebugUtils ${SOURCES}) add_library(RecastNavigation::DebugUtils ALIAS DebugUtils) +set_target_properties(DebugUtils PROPERTIES DEBUG_POSTFIX -d) set(DebugUtils_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") @@ -23,13 +19,18 @@ target_link_libraries(DebugUtils set_target_properties(DebugUtils PROPERTIES SOVERSION ${SOVERSION} VERSION ${VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "DebugUtils-d" ) install(TARGETS DebugUtils - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library ) file(GLOB INCLUDES Include/*.h) -install(FILES ${INCLUDES} DESTINATION include) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +install(FILES "$/DebugUtils-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") diff --git a/extern/recastnavigation/Detour/CMakeLists.txt b/extern/recastnavigation/Detour/CMakeLists.txt index de88111d56..5cb47ec0e2 100644 --- a/extern/recastnavigation/Detour/CMakeLists.txt +++ b/extern/recastnavigation/Detour/CMakeLists.txt @@ -1,12 +1,8 @@ file(GLOB SOURCES Source/*.cpp) - -if(RECASTNAVIGATION_STATIC) - add_library(Detour STATIC ${SOURCES}) -else() - add_library(Detour SHARED ${SOURCES}) -endif() +add_library(Detour ${SOURCES}) add_library(RecastNavigation::Detour ALIAS Detour) +set_target_properties(Detour PROPERTIES DEBUG_POSTFIX -d) set(Detour_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") @@ -17,13 +13,18 @@ target_include_directories(Detour PUBLIC set_target_properties(Detour PROPERTIES SOVERSION ${SOVERSION} VERSION ${VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "Detour-d" ) install(TARGETS Detour - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library ) file(GLOB INCLUDES Include/*.h) -install(FILES ${INCLUDES} DESTINATION include) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +install(FILES "$/Detour-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") diff --git a/extern/recastnavigation/DetourCrowd/CMakeLists.txt b/extern/recastnavigation/DetourCrowd/CMakeLists.txt index 73cdf7ce84..d0e186be07 100644 --- a/extern/recastnavigation/DetourCrowd/CMakeLists.txt +++ b/extern/recastnavigation/DetourCrowd/CMakeLists.txt @@ -1,12 +1,8 @@ file(GLOB SOURCES Source/*.cpp) - -if (RECASTNAVIGATION_STATIC) - add_library(DetourCrowd STATIC ${SOURCES}) -else () - add_library(DetourCrowd SHARED ${SOURCES}) -endif () +add_library(DetourCrowd ${SOURCES}) add_library(RecastNavigation::DetourCrowd ALIAS DetourCrowd) +set_target_properties(DetourCrowd PROPERTIES DEBUG_POSTFIX -d) set(DetourCrowd_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") @@ -21,13 +17,18 @@ target_link_libraries(DetourCrowd set_target_properties(DetourCrowd PROPERTIES SOVERSION ${SOVERSION} VERSION ${VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "DetourCrowd-d" ) install(TARGETS DetourCrowd - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library ) file(GLOB INCLUDES Include/*.h) -install(FILES ${INCLUDES} DESTINATION include) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +install(FILES "$/DetourCrowd-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") diff --git a/extern/recastnavigation/DetourCrowd/Source/DetourCrowd.cpp b/extern/recastnavigation/DetourCrowd/Source/DetourCrowd.cpp index 1e76e40ce2..3f0311f7ff 100644 --- a/extern/recastnavigation/DetourCrowd/Source/DetourCrowd.cpp +++ b/extern/recastnavigation/DetourCrowd/Source/DetourCrowd.cpp @@ -1409,12 +1409,14 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) } // Update agents using off-mesh connection. - for (int i = 0; i < m_maxAgents; ++i) + for (int i = 0; i < nagents; ++i) { - dtCrowdAgentAnimation* anim = &m_agentAnims[i]; + dtCrowdAgent* ag = agents[i]; + const int idx = (int)(ag - m_agents); + dtCrowdAgentAnimation* anim = &m_agentAnims[idx]; if (!anim->active) continue; - dtCrowdAgent* ag = agents[i]; + anim->t += dt; if (anim->t > anim->tmax) diff --git a/extern/recastnavigation/DetourTileCache/CMakeLists.txt b/extern/recastnavigation/DetourTileCache/CMakeLists.txt index 121b8edcc7..3703ebb929 100644 --- a/extern/recastnavigation/DetourTileCache/CMakeLists.txt +++ b/extern/recastnavigation/DetourTileCache/CMakeLists.txt @@ -1,12 +1,8 @@ file(GLOB SOURCES Source/*.cpp) - -if (RECASTNAVIGATION_STATIC) - add_library(DetourTileCache STATIC ${SOURCES}) -else () - add_library(DetourTileCache SHARED ${SOURCES}) -endif () +add_library(DetourTileCache ${SOURCES}) add_library(RecastNavigation::DetourTileCache ALIAS DetourTileCache) +set_target_properties(DetourTileCache PROPERTIES DEBUG_POSTFIX -d) set(DetourTileCache_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") @@ -21,14 +17,19 @@ target_link_libraries(DetourTileCache set_target_properties(DetourTileCache PROPERTIES SOVERSION ${SOVERSION} VERSION ${VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "DetourTileCache-d" ) install(TARGETS DetourTileCache - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library ) file(GLOB INCLUDES Include/*.h) -install(FILES ${INCLUDES} DESTINATION include) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +install(FILES "$/DetourTileCache-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") diff --git a/extern/recastnavigation/Recast/CMakeLists.txt b/extern/recastnavigation/Recast/CMakeLists.txt index 5e843762e5..3606544642 100644 --- a/extern/recastnavigation/Recast/CMakeLists.txt +++ b/extern/recastnavigation/Recast/CMakeLists.txt @@ -1,12 +1,8 @@ file(GLOB SOURCES Source/*.cpp) - -if (RECASTNAVIGATION_STATIC) - add_library(Recast STATIC ${SOURCES}) -else () - add_library(Recast SHARED ${SOURCES}) -endif () +add_library(Recast ${SOURCES}) add_library(RecastNavigation::Recast ALIAS Recast) +set_target_properties(Recast PROPERTIES DEBUG_POSTFIX -d) set(Recast_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") @@ -17,13 +13,18 @@ target_include_directories(Recast PUBLIC set_target_properties(Recast PROPERTIES SOVERSION ${SOVERSION} VERSION ${VERSION} + COMPILE_PDB_OUTPUT_DIRECTORY . + COMPILE_PDB_NAME "Recast-d" ) install(TARGETS Recast - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library ) file(GLOB INCLUDES Include/*.h) -install(FILES ${INCLUDES} DESTINATION include) +install(FILES ${INCLUDES} DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) +install(FILES "$/Recast-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") From 9b11b8a27b42782ea528b1b2f5e2f884352bdb13 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Nov 2020 18:52:00 +0100 Subject: [PATCH 0021/2859] Fix boundary check --- components/detournavigator/chunkytrimesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/detournavigator/chunkytrimesh.cpp b/components/detournavigator/chunkytrimesh.cpp index 3a8fc34802..ffd39d0a97 100644 --- a/components/detournavigator/chunkytrimesh.cpp +++ b/components/detournavigator/chunkytrimesh.cpp @@ -51,7 +51,7 @@ namespace DetourNavigator const auto inum = imax - imin; const auto icur = curNode; - if (curNode > nodes.size()) + if (curNode >= nodes.size()) return; ChunkyTriMeshNode& node = nodes[curNode++]; From f78a5d795c0395c48d441989ddaeaffb6eae02d4 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 18 Nov 2020 22:48:47 +0200 Subject: [PATCH 0022/2859] Separate keyframes logic to provide basis for osgAnimation integration. --- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/character.hpp | 2 +- apps/openmw/mwrender/animation.cpp | 43 ++++++------ apps/openmw/mwrender/animation.hpp | 20 +++--- apps/openmw/mwrender/npcanimation.cpp | 5 +- components/nifosg/controller.cpp | 3 +- components/nifosg/controller.hpp | 6 +- components/nifosg/nifloader.cpp | 18 ++--- components/nifosg/nifloader.hpp | 38 +---------- components/resource/keyframemanager.cpp | 53 +++++++++++++-- components/resource/keyframemanager.hpp | 10 ++- components/resource/resourcesystem.cpp | 2 +- components/sceneutil/keyframe.hpp | 68 +++++++++++++++++++ .../{nifosg => sceneutil}/textkeymap.hpp | 6 +- 14 files changed, 174 insertions(+), 102 deletions(-) create mode 100644 components/sceneutil/keyframe.hpp rename components/{nifosg => sceneutil}/textkeymap.hpp (94%) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 657f2e2eca..9b3c8576ea 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -936,7 +936,7 @@ void split(const std::string &s, char delim, std::vector &elems) { } } -void CharacterController::handleTextKey(const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, const NifOsg::TextKeyMap& map) +void CharacterController::handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) { const std::string &evt = key->second; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 949affcfde..2308ba971d 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -241,7 +241,7 @@ public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); - void handleTextKey(const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, const NifOsg::TextKeyMap& map) override; + void handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) override; // Be careful when to call this, see comment in Actors void updateContinuousVfx(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 10a6b2be42..f8ff3780d3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -20,8 +20,7 @@ #include #include -#include // KeyframeHolder -#include +#include #include @@ -148,7 +147,7 @@ namespace } }; - float calcAnimVelocity(const NifOsg::TextKeyMap& keys, NifOsg::KeyframeController *nonaccumctrl, + float calcAnimVelocity(const SceneUtil::TextKeyMap& keys, SceneUtil::KeyframeController *nonaccumctrl, const osg::Vec3f& accum, const std::string &groupname) { const std::string start = groupname+": start"; @@ -530,13 +529,13 @@ namespace MWRender struct Animation::AnimSource { - osg::ref_ptr mKeyframes; + osg::ref_ptr mKeyframes; - typedef std::map > ControllerMap; + typedef std::map > ControllerMap; ControllerMap mControllerMap[Animation::sNumBlendMasks]; - const NifOsg::TextKeyMap& getTextKeys() const; + const SceneUtil::TextKeyMap& getTextKeys() const; }; void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) @@ -688,7 +687,7 @@ namespace MWRender return 0; } - const NifOsg::TextKeyMap &Animation::AnimSource::getTextKeys() const + const SceneUtil::TextKeyMap &Animation::AnimSource::getTextKeys() const { return mKeyframes->mTextKeys; } @@ -729,8 +728,6 @@ namespace MWRender if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) kfname.replace(kfname.size()-4, 4, ".kf"); - else - return; addSingleAnimSource(kfname, baseModel); @@ -753,7 +750,7 @@ namespace MWRender const NodeMap& nodeMap = getNodeMap(); - for (NifOsg::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); + for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); it != animsrc->mKeyframes->mKeyframeControllers.end(); ++it) { std::string bonename = Misc::StringUtils::lowerCase(it->first); @@ -769,7 +766,7 @@ namespace MWRender size_t blendMask = detectBlendMask(node); // clone the controller, because each Animation needs its own ControllerSource - osg::ref_ptr cloned = new NifOsg::KeyframeController(*it->second, osg::CopyOp::SHALLOW_COPY); + osg::ref_ptr cloned = osg::clone(it->second.get(), osg::CopyOp::SHALLOW_COPY); cloned->setSource(mAnimationTimePtr[blendMask]); animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned)); @@ -810,7 +807,7 @@ namespace MWRender AnimSourceList::const_iterator iter(mAnimSources.begin()); for(;iter != mAnimSources.end();++iter) { - const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); + const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); if (keys.hasGroupStart(anim)) return true; } @@ -822,7 +819,7 @@ namespace MWRender { for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { - const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); + const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); const auto found = keys.findGroupStart(groupname); if(found != keys.end()) @@ -835,7 +832,7 @@ namespace MWRender { for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) { - const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); + const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys(); for(auto iterKey = keys.begin(); iterKey != keys.end(); ++iterKey) { @@ -847,8 +844,8 @@ namespace MWRender return -1.f; } - void Animation::handleTextKey(AnimState &state, const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, - const NifOsg::TextKeyMap& map) + void Animation::handleTextKey(AnimState &state, const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, + const SceneUtil::TextKeyMap& map) { const std::string &evt = key->second; @@ -911,7 +908,7 @@ namespace MWRender AnimSourceList::reverse_iterator iter(mAnimSources.rbegin()); for(;iter != mAnimSources.rend();++iter) { - const NifOsg::TextKeyMap &textkeys = (*iter)->getTextKeys(); + const SceneUtil::TextKeyMap &textkeys = (*iter)->getTextKeys(); if(reset(state, textkeys, groupname, start, stop, startpoint, loopfallback)) { state.mSource = *iter; @@ -956,7 +953,7 @@ namespace MWRender resetActiveGroups(); } - bool Animation::reset(AnimState &state, const NifOsg::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback) + bool Animation::reset(AnimState &state, const SceneUtil::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback) { // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two // separate walkforward keys, and the last one is supposed to be used. @@ -1186,7 +1183,7 @@ namespace MWRender AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin()); for(;animsrc != mAnimSources.rend();++animsrc) { - const NifOsg::TextKeyMap &keys = (*animsrc)->getTextKeys(); + const SceneUtil::TextKeyMap &keys = (*animsrc)->getTextKeys(); if (keys.hasGroupStart(groupname)) break; } @@ -1194,7 +1191,7 @@ namespace MWRender return 0.0f; float velocity = 0.0f; - const NifOsg::TextKeyMap &keys = (*animsrc)->getTextKeys(); + const SceneUtil::TextKeyMap &keys = (*animsrc)->getTextKeys(); const AnimSource::ControllerMap& ctrls = (*animsrc)->mControllerMap[0]; for (AnimSource::ControllerMap::const_iterator it = ctrls.begin(); it != ctrls.end(); ++it) @@ -1215,7 +1212,7 @@ namespace MWRender while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend()) { - const NifOsg::TextKeyMap &keys2 = (*animiter)->getTextKeys(); + const SceneUtil::TextKeyMap &keys2 = (*animiter)->getTextKeys(); const AnimSource::ControllerMap& ctrls2 = (*animiter)->mControllerMap[0]; for (AnimSource::ControllerMap::const_iterator it = ctrls2.begin(); it != ctrls2.end(); ++it) @@ -1265,7 +1262,7 @@ namespace MWRender continue; } - const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys(); + const SceneUtil::TextKeyMap &textkeys = state.mSource->getTextKeys(); auto textkey = textkeys.upperBound(state.getTime()); float timepassed = duration * state.mSpeedMult; @@ -1839,7 +1836,7 @@ namespace MWRender osg::Callback* cb = node->getUpdateCallback(); while (cb) { - if (dynamic_cast(cb)) + if (dynamic_cast(cb)) { foundKeyframeCtrl = true; break; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 9d03831be5..ebfe8a2e59 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -4,8 +4,8 @@ #include "../mwworld/ptr.hpp" #include +#include #include -#include #include @@ -20,14 +20,10 @@ namespace Resource class ResourceSystem; } -namespace NifOsg +namespace SceneUtil { class KeyframeHolder; class KeyframeController; -} - -namespace SceneUtil -{ class LightSource; class LightListCallback; class Skeleton; @@ -150,8 +146,8 @@ public: class TextKeyListener { public: - virtual void handleTextKey(const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, - const NifOsg::TextKeyMap& map) = 0; + virtual void handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, + const SceneUtil::TextKeyMap& map) = 0; virtual ~TextKeyListener() = default; }; @@ -242,7 +238,7 @@ protected: osg::ref_ptr mAccumRoot; // The controller animating that node. - osg::ref_ptr mAccumCtrl; + osg::ref_ptr mAccumCtrl; // Used to reset the position of the accumulation root every frame - the movement should be applied to the physics system osg::ref_ptr mResetAccumRootCallback; @@ -306,12 +302,12 @@ protected: * the marker is not found, or if the markers are the same, it returns * false. */ - bool reset(AnimState &state, const NifOsg::TextKeyMap &keys, + bool reset(AnimState &state, const SceneUtil::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback); - void handleTextKey(AnimState &state, const std::string &groupname, NifOsg::TextKeyMap::ConstIterator key, - const NifOsg::TextKeyMap& map); + void handleTextKey(AnimState &state, const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, + const SceneUtil::TextKeyMap& map); /** Sets the root model of the object. * diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 36213fc96d..d97e57115a 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -19,11 +19,10 @@ #include #include #include +#include #include -#include // TextKeyMapHolder - #include #include "../mwworld/esmstore.hpp" @@ -864,7 +863,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g for (unsigned int i=0; igetUserDataContainer()->getNumUserObjects(); ++i) { osg::Object* obj = node->getUserDataContainer()->getUserObject(i); - if (NifOsg::TextKeyMapHolder* keys = dynamic_cast(obj)) + if (SceneUtil::TextKeyMapHolder* keys = dynamic_cast(obj)) { for (const auto &key : keys->mTextKeys) { diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 64e9f7de6c..31fd92b43e 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -71,8 +71,7 @@ KeyframeController::KeyframeController() } KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) - , Controller(copy) + : SceneUtil::KeyframeController(copy, copyop) , mRotations(copy.mRotations) , mXRotations(copy.mXRotations) , mYRotations(copy.mYRotations) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 996e4ef979..0063b2ec0f 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -226,7 +226,7 @@ namespace NifOsg std::vector mKeyFrames; }; - class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller + class KeyframeController : public SceneUtil::KeyframeController { public: // This is used if there's no interpolator but there is data (Morrowind meshes). @@ -242,7 +242,7 @@ namespace NifOsg META_Object(NifOsg, KeyframeController) - virtual osg::Vec3f getTranslation(float time) const; + osg::Vec3f getTranslation(float time) const override; void operator() (osg::Node*, osg::NodeVisitor*) override; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a5a61b3176..751bdb51fe 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -139,7 +139,7 @@ namespace } }; - void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys) + void extractTextKeys(const Nif::NiTextKeyExtraData *tk, SceneUtil::TextKeyMap &textkeys) { for(size_t i = 0;i < tk->list.size();i++) { @@ -234,7 +234,7 @@ namespace NifOsg // This is used to queue emitters that weren't attached to their node yet. std::vector>> mEmitterQueue; - static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target) + static void loadKf(Nif::NIFFilePtr nif, SceneUtil::KeyframeHolder& target) { const Nif::NiSequenceStreamHelper *seq = nullptr; const size_t numRoots = nif->numRoots(); @@ -284,7 +284,7 @@ namespace NifOsg if (key->data.empty() && key->interpolator.empty()) continue; - osg::ref_ptr callback(handleKeyframeController(key)); + osg::ref_ptr callback(handleKeyframeController(key)); callback->setFunction(std::shared_ptr(new NifOsg::ControllerFunction(key))); if (!target.mKeyframeControllers.emplace(strdata->string, callback).second) @@ -305,7 +305,7 @@ namespace NifOsg if (!nifNode) nif->fail("Found no root nodes"); - osg::ref_ptr textkeys (new TextKeyMapHolder); + osg::ref_ptr textkeys (new SceneUtil::TextKeyMapHolder); osg::ref_ptr created = handleNode(nifNode, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); @@ -353,10 +353,10 @@ namespace NifOsg else if (props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) { if (props[i].getPtr()->recIndex == mFirstRootTextureIndex) - applyTo->setUserValue("overrideFx", 1); + applyTo->setUserValue("overrideFx", 1); } handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags); - } + } } } @@ -514,7 +514,7 @@ namespace NifOsg } osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, TextKeyMap* textKeys, osg::Node* rootNode=nullptr) + std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, osg::Node* rootNode=nullptr) { if (rootNode != nullptr && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) return nullptr; @@ -1197,7 +1197,7 @@ namespace NifOsg for (const auto& strip : data->strips) { if (strip.size() >= 3) - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), (unsigned short*)strip.data())); } } @@ -1929,7 +1929,7 @@ namespace NifOsg return impl.load(file, imageManager); } - void Loader::loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target) + void Loader::loadKf(Nif::NIFFilePtr kf, SceneUtil::KeyframeHolder& target) { LoaderImpl impl(kf->getFilename(), kf->getVersion(), kf->getUserVersion(), kf->getBethVersion()); impl.loadKf(kf, target); diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 49a78ad5f6..8ee6b41674 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -2,12 +2,13 @@ #define OPENMW_COMPONENTS_NIFOSG_LOADER #include +#include +#include #include #include #include "controller.hpp" -#include "textkeymap.hpp" namespace osg { @@ -21,39 +22,6 @@ namespace Resource namespace NifOsg { - struct TextKeyMapHolder : public osg::Object - { - public: - TextKeyMapHolder() {} - TextKeyMapHolder(const TextKeyMapHolder& copy, const osg::CopyOp& copyop) - : osg::Object(copy, copyop) - , mTextKeys(copy.mTextKeys) - {} - - TextKeyMap mTextKeys; - - META_Object(NifOsg, TextKeyMapHolder) - - }; - - class KeyframeHolder : public osg::Object - { - public: - KeyframeHolder() {} - KeyframeHolder(const KeyframeHolder& copy, const osg::CopyOp& copyop) - : mTextKeys(copy.mTextKeys) - , mKeyframeControllers(copy.mKeyframeControllers) - { - } - - TextKeyMap mTextKeys; - - META_Object(OpenMW, KeyframeHolder) - - typedef std::map > KeyframeControllerMap; - KeyframeControllerMap mKeyframeControllers; - }; - /// The main class responsible for loading NIF files into an OSG-Scenegraph. /// @par This scene graph is self-contained and can be cloned using osg::clone if desired. Particle emitters /// and programs hold a pointer to their ParticleSystem, which would need to be manually updated when cloning. @@ -64,7 +32,7 @@ namespace NifOsg static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager); /// Load keyframe controllers from the given kf file. - static void loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target); + static void loadKf(Nif::NIFFilePtr kf, SceneUtil::KeyframeHolder& target); /// Set whether or not nodes marked as "MRK" should be shown. /// These should be hidden ingame, but visible in the editor. diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 8c5c50adca..ef6339adb5 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -2,13 +2,45 @@ #include +#include + #include "objectcache.hpp" +#include "scenemanager.hpp" + +namespace +{ + class RetrieveAnimationsVisitor : public osg::NodeVisitor + { + public: + RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target) {} + + + virtual void apply(osg::Node& node) + { + if (node.libraryName() == std::string("osgAnimation")) + std::cout << "found an " << node.className() << std::endl; + traverse(node); + } + + private: + SceneUtil::KeyframeHolder& mTarget; + }; + + std::string getFileExtension(const std::string& file) + { + size_t extPos = file.find_last_of('.'); + if (extPos != std::string::npos && extPos+1 < file.size()) + return file.substr(extPos+1); + return std::string(); + } +} namespace Resource { - KeyframeManager::KeyframeManager(const VFS::Manager* vfs) + KeyframeManager::KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager) : ResourceManager(vfs) + , mSceneManager(sceneManager) { } @@ -16,19 +48,28 @@ namespace Resource { } - osg::ref_ptr KeyframeManager::get(const std::string &name) + osg::ref_ptr KeyframeManager::get(const std::string &name) { std::string normalized = name; mVFS->normalizeFilename(normalized); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) - return osg::ref_ptr(static_cast(obj.get())); + return osg::ref_ptr(static_cast(obj.get())); else { - osg::ref_ptr loaded (new NifOsg::KeyframeHolder); - NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); - + osg::ref_ptr loaded (new SceneUtil::KeyframeHolder); + std::string ext = getFileExtension(normalized); + if (ext == "kf") + { + NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); + } + else + { + osg::ref_ptr scene = mSceneManager->getTemplate(normalized); + RetrieveAnimationsVisitor rav(*loaded.get()); + const_cast(scene.get())->accept(rav); // const_cast required because there is no const version of osg::NodeVisitor + } mCache->addEntryToObjectCache(normalized, loaded); return loaded; } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index fe1c4014e0..e186e2783b 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -4,26 +4,30 @@ #include #include -#include +#include #include "resourcemanager.hpp" namespace Resource { + class SceneManager; + /// @brief Managing of keyframe resources /// @note May be used from any thread. class KeyframeManager : public ResourceManager { public: - KeyframeManager(const VFS::Manager* vfs); + KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager); ~KeyframeManager(); /// Retrieve a read-only keyframe resource by name (case-insensitive). /// @note Throws an exception if the resource is not found. - osg::ref_ptr get(const std::string& name); + osg::ref_ptr get(const std::string& name); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + private: + SceneManager* mSceneManager; }; } diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 2015ba874d..ab9d0aba2c 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -14,9 +14,9 @@ namespace Resource : mVFS(vfs) { mNifFileManager.reset(new NifFileManager(vfs)); - mKeyframeManager.reset(new KeyframeManager(vfs)); mImageManager.reset(new ImageManager(vfs)); mSceneManager.reset(new SceneManager(vfs, mImageManager.get(), mNifFileManager.get())); + mKeyframeManager.reset(new KeyframeManager(vfs, mSceneManager.get())); addResourceManager(mNifFileManager.get()); addResourceManager(mKeyframeManager.get()); diff --git a/components/sceneutil/keyframe.hpp b/components/sceneutil/keyframe.hpp new file mode 100644 index 0000000000..c993c7b7c7 --- /dev/null +++ b/components/sceneutil/keyframe.hpp @@ -0,0 +1,68 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_KEYFRAME_HPP +#define OPENMW_COMPONENTS_SCENEUTIL_KEYFRAME_HPP + +#include + +#include + +#include +#include + +namespace SceneUtil +{ + + class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller + { + public: + KeyframeController() {} + + KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop) + : osg::NodeCallback(copy, copyop) + , SceneUtil::Controller(copy) + {} + META_Object(SceneUtil, KeyframeController) + + virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } + + virtual void operator() (osg::Node* node, osg::NodeVisitor* nodeVisitor) { traverse(node, nodeVisitor); } + }; + + /// Wrapper object containing an animation track as a ref-countable osg::Object. + struct TextKeyMapHolder : public osg::Object + { + public: + TextKeyMapHolder() {} + TextKeyMapHolder(const TextKeyMapHolder& copy, const osg::CopyOp& copyop) + : osg::Object(copy, copyop) + , mTextKeys(copy.mTextKeys) + {} + + TextKeyMap mTextKeys; + + META_Object(SceneUtil, TextKeyMapHolder) + + }; + + /// Wrapper object containing the animation track and its KeyframeControllers. + class KeyframeHolder : public osg::Object + { + public: + KeyframeHolder() {} + KeyframeHolder(const KeyframeHolder& copy, const osg::CopyOp& copyop) + : mTextKeys(copy.mTextKeys) + , mKeyframeControllers(copy.mKeyframeControllers) + { + } + + TextKeyMap mTextKeys; + + META_Object(SceneUtil, KeyframeHolder) + + /// Controllers mapped to node name. + typedef std::map > KeyframeControllerMap; + KeyframeControllerMap mKeyframeControllers; + }; + +} + +#endif diff --git a/components/nifosg/textkeymap.hpp b/components/sceneutil/textkeymap.hpp similarity index 94% rename from components/nifosg/textkeymap.hpp rename to components/sceneutil/textkeymap.hpp index 49e1e461e4..ee58bc72a7 100644 --- a/components/nifosg/textkeymap.hpp +++ b/components/sceneutil/textkeymap.hpp @@ -1,12 +1,12 @@ -#ifndef OPENMW_COMPONENTS_NIFOSG_TEXTKEYMAP -#define OPENMW_COMPONENTS_NIFOSG_TEXTKEYMAP +#ifndef OPENMW_COMPONENTS_SCENEUTIL_TEXTKEYMAP +#define OPENMW_COMPONENTS_SCENEUTIL_TEXTKEYMAP #include #include #include #include -namespace NifOsg +namespace SceneUtil { class TextKeyMap { From 6e77ad1f6a77cd31bd7fbf8ed7007015899013e9 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Thu, 19 Nov 2020 01:11:56 +0200 Subject: [PATCH 0023/2859] OSG-Collada animation support --- CMakeLists.txt | 2 +- apps/openmw/mwrender/animation.cpp | 2 + components/CMakeLists.txt | 5 +- components/resource/animation.cpp | 40 +++++ components/resource/animation.hpp | 39 +++++ components/resource/keyframemanager.cpp | 94 +++++++++--- components/resource/keyframemanager.hpp | 20 +++ components/resource/scenemanager.cpp | 2 +- components/sceneutil/clone.cpp | 12 +- components/sceneutil/keyframe.hpp | 13 ++ components/sceneutil/osgacontroller.cpp | 195 ++++++++++++++++++++++++ components/sceneutil/osgacontroller.hpp | 70 +++++++++ 12 files changed, 471 insertions(+), 23 deletions(-) create mode 100644 components/resource/animation.cpp create mode 100644 components/resource/animation.hpp create mode 100644 components/sceneutil/osgacontroller.cpp create mode 100644 components/sceneutil/osgacontroller.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 37696f44c4..76764fbd93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,7 +259,7 @@ if(NOT HAVE_STDINT_H) endif() -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow osgAnimation) include_directories(SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS}) set(USED_OSG_PLUGINS diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f8ff3780d3..8c9f5f4939 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -782,6 +782,8 @@ namespace MWRender NodeMap::const_iterator found = nodeMap.find("bip01"); if (found == nodeMap.end()) found = nodeMap.find("root bone"); + if (found == nodeMap.end()) + found = nodeMap.find("root"); if (found != nodeMap.end()) mAccumRoot = found->second; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 08f183f5e8..3c18990375 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,7 +41,8 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager stats + scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem + resourcemanager stats animation ) add_component_dir (shader @@ -51,7 +52,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer - actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin + actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller ) add_component_dir (nif diff --git a/components/resource/animation.cpp b/components/resource/animation.cpp new file mode 100644 index 0000000000..34ae162ee7 --- /dev/null +++ b/components/resource/animation.cpp @@ -0,0 +1,40 @@ +#include + +#include +#include + +namespace Resource +{ + Animation::Animation(const Animation& anim, const osg::CopyOp& copyop): osg::Object(anim, copyop), + mDuration(0.0f), + mStartTime(0.0f) + { + const osgAnimation::ChannelList& channels = anim.getChannels(); + for (const osg::ref_ptr channel: channels) + addChannel(channel.get()->clone()); + } + + void Animation::addChannel(osg::ref_ptr pChannel) + { + mChannels.push_back(pChannel); + } + + std::vector>& Animation::getChannels() + { + return mChannels; + } + + const std::vector>& Animation::getChannels() const + { + return mChannels; + } + + bool Animation::update (double time) + { + for (const osg::ref_ptr channel: mChannels) + { + channel->update(time, 1.0f, 0); + } + return true; + } +} diff --git a/components/resource/animation.hpp b/components/resource/animation.hpp new file mode 100644 index 0000000000..885394747e --- /dev/null +++ b/components/resource/animation.hpp @@ -0,0 +1,39 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_ANIMATION_HPP +#define OPENMW_COMPONENTS_RESOURCE_ANIMATION_HPP + +#include + +#include +#include +#include + +namespace Resource +{ + /// Stripped down class of osgAnimation::Animation, only needed for OSG's plugin formats like dae + class Animation : public osg::Object + { + public: + META_Object(Resource, Animation) + + Animation() : + mDuration(0.0), mStartTime(0) {} + + Animation(const Animation&, const osg::CopyOp&); + ~Animation() {} + + void addChannel (osg::ref_ptr pChannel); + + std::vector>& getChannels(); + + const std::vector>& getChannels() const; + + bool update (double time); + + protected: + double mDuration; + double mStartTime; + std::vector> mChannels; + }; +} + +#endif diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index ef6339adb5..6cda9728cd 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -2,29 +2,86 @@ #include +#include +#include +#include + #include +#include +#include +#include "animation.hpp" #include "objectcache.hpp" #include "scenemanager.hpp" -namespace +namespace OsgAOpenMW { - class RetrieveAnimationsVisitor : public osg::NodeVisitor - { - public: - RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target) {} + RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager) {} - virtual void apply(osg::Node& node) - { - if (node.libraryName() == std::string("osgAnimation")) - std::cout << "found an " << node.className() << std::endl; - traverse(node); - } + void RetrieveAnimationsVisitor::apply(osg::Node& node) + { + if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("root")) + { + if (!mAnimationManager) + { + traverse(node); + return; + } + + osg::ref_ptr callback = new OsgaController::KeyframeController(); + + std::vector emulatedAnimations; + + for (auto animation : mAnimationManager->getAnimationList()) + { + if (animation) + { + if (animation->getName() == "Default") //"Default" is osg dae plugin's default naming scheme for unnamed animations + { + animation->setName(std::string("idle")); // animation naming scheme "idle: start" and "idle: stop" is the default idle animation that OpenMW seems to want to play + } + + osg::ref_ptr mergedAnimationTrack = new Resource::Animation; + std::string animationName = animation->getName(); + std::string start = animationName + std::string(": start"); + std::string stop = animationName + std::string(": stop"); + std::string loopstart = animationName + std::string(": loop start"); + std::string loopstop = animationName + std::string(": loop stop"); + + const osgAnimation::ChannelList& channels = animation->getChannels(); + for (const osg::ref_ptr channel: channels) + { + mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? + } + mergedAnimationTrack->setName(animation->getName()); + callback->addMergedAnimationTrack(mergedAnimationTrack); + + float startTime = animation->getStartTime(); + float stopTime = startTime + animation->getDuration(); + + // mTextKeys is a nif-thing, used by OpenMW's animation system + // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" + // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played + // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" + mTarget.mTextKeys.emplace(startTime, std::move(start)); + mTarget.mTextKeys.emplace(stopTime, std::move(stop)); + mTarget.mTextKeys.emplace(startTime, std::move(loopstart)); + mTarget.mTextKeys.emplace(stopTime, std::move(loopstop)); + + SceneUtil::EmulatedAnimation emulatedAnimation; + emulatedAnimation.mStartTime = startTime; + emulatedAnimation.mStopTime = stopTime; + emulatedAnimation.mName = animationName; + emulatedAnimations.emplace_back(emulatedAnimation); + } + } + callback->setEmulatedAnimations(emulatedAnimations); + mTarget.mKeyframeControllers.emplace(node.getName(), callback); + } - private: - SceneUtil::KeyframeHolder& mTarget; - }; + traverse(node); + } std::string getFileExtension(const std::string& file) { @@ -59,16 +116,17 @@ namespace Resource else { osg::ref_ptr loaded (new SceneUtil::KeyframeHolder); - std::string ext = getFileExtension(normalized); + std::string ext = OsgAOpenMW::getFileExtension(normalized); if (ext == "kf") { NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); } else { - osg::ref_ptr scene = mSceneManager->getTemplate(normalized); - RetrieveAnimationsVisitor rav(*loaded.get()); - const_cast(scene.get())->accept(rav); // const_cast required because there is no const version of osg::NodeVisitor + osg::ref_ptr scene = const_cast ( mSceneManager->getTemplate(normalized).get() ); + osg::ref_ptr bam = dynamic_cast (scene->getUpdateCallback()); + OsgAOpenMW::RetrieveAnimationsVisitor rav(*loaded.get(), bam); + scene->accept(rav); } mCache->addEntryToObjectCache(normalized, loaded); return loaded; diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index e186e2783b..4f83196f34 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -2,12 +2,32 @@ #define OPENMW_COMPONENTS_KEYFRAMEMANAGER_H #include +#include #include #include #include "resourcemanager.hpp" +namespace OsgAOpenMW +{ + /// @brief extract animations to OpenMW's animation system + class RetrieveAnimationsVisitor : public osg::NodeVisitor + { + public: + RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager); + + virtual void apply(osg::Node& node); + + private: + SceneUtil::KeyframeHolder& mTarget; + osg::ref_ptr mAnimationManager; + + }; + + std::string getFileExtension(const std::string& file); +} + namespace Resource { diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 44ba7e6878..e99003b749 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -492,7 +492,7 @@ namespace Resource } catch (std::exception& e) { - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2" }; + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }; for (unsigned int i=0; i +#include +#include +#include +#include + #include #include #include @@ -30,6 +35,11 @@ namespace SceneUtil mUpdaterToOldPs[cloned] = updater->getParticleSystem(0); return cloned; } + + if (dynamic_cast(node) || dynamic_cast(node)) + { + return osg::clone(node, *this); + } return osg::CopyOp::operator()(node); } @@ -38,7 +48,7 @@ namespace SceneUtil if (const osgParticle::ParticleSystem* partsys = dynamic_cast(drawable)) return operator()(partsys); - if (dynamic_cast(drawable) || dynamic_cast(drawable)) + if (dynamic_cast(drawable) || dynamic_cast(drawable) || dynamic_cast(drawable) || dynamic_cast(drawable)) { return static_cast(drawable->clone(*this)); } diff --git a/components/sceneutil/keyframe.hpp b/components/sceneutil/keyframe.hpp index c993c7b7c7..5a0435469c 100644 --- a/components/sceneutil/keyframe.hpp +++ b/components/sceneutil/keyframe.hpp @@ -7,9 +7,16 @@ #include #include +#include namespace SceneUtil { + struct EmulatedAnimation + { + float mStartTime; + float mStopTime; + std::string mName; + }; class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller { @@ -19,12 +26,18 @@ namespace SceneUtil KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) , SceneUtil::Controller(copy) + , mMergedAnimationTracks(copy.mMergedAnimationTracks) + , mEmulatedAnimations(copy.mEmulatedAnimations) {} META_Object(SceneUtil, KeyframeController) virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } virtual void operator() (osg::Node* node, osg::NodeVisitor* nodeVisitor) { traverse(node, nodeVisitor); } + + protected: + std::vector> mMergedAnimationTracks; // Used only by osgAnimation-based formats (e.g. dae) + std::vector mEmulatedAnimations; }; /// Wrapper object containing an animation track as a ref-countable osg::Object. diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp new file mode 100644 index 0000000000..6b635d4ea1 --- /dev/null +++ b/components/sceneutil/osgacontroller.cpp @@ -0,0 +1,195 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace OsgaController +{ + LinkVisitor::LinkVisitor() : osg::NodeVisitor( TRAVERSE_ALL_CHILDREN ) + { + mAnimation = nullptr; + } + + void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) + { + const osgAnimation::ChannelList& channels = mAnimation->getChannels(); + for (const osg::ref_ptr channel: channels) + { + const std::string& channelName = channel->getName(); + const std::string& channelTargetName = channel->getTargetName(); + + if (channelTargetName != umt->getName()) continue; + + // check if we can link a StackedTransformElement to the current Channel + for (auto stackedTransform : umt->getStackedTransforms()) + { + osgAnimation::StackedTransformElement* element = stackedTransform.get(); + if (element && !element->getName().empty() && channelName == element->getName()) + { + osgAnimation::Target* target = element->getOrCreateTarget(); + if (target) + { + channel->setTarget(target); + } + } + } + } + } + + void LinkVisitor::handle_stateset(osg::StateSet* stateset) + { + if (!stateset) + return; + const osg::StateSet::AttributeList& attributeList = stateset->getAttributeList(); + for (auto attribute : attributeList) + { + osg::StateAttribute* sattr = attribute.second.first.get(); + osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(sattr->getUpdateCallback()); //Can this even be in sa? + if (umt) link(umt); + } + } + + void LinkVisitor::setAnimation(Resource::Animation* animation) + { + mAnimation = animation; + } + + void LinkVisitor::apply(osg::Node& node) + { + osg::StateSet* st = node.getStateSet(); + if (st) + handle_stateset(st); + + osg::Callback* cb = node.getUpdateCallback(); + while (cb) + { + osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(cb); + if (umt) + if (node.getName() != "root") link(umt); + cb = cb->getNestedCallback(); + } + + traverse( node ); + } + + void LinkVisitor::apply(osg::Geode& node) + { + for (unsigned int i = 0; i < node.getNumDrawables(); i++) + { + osg::Drawable* drawable = node.getDrawable(i); + if (drawable && drawable->getStateSet()) + handle_stateset(drawable->getStateSet()); + } + apply(static_cast(node)); + } + + KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) + { + mLinker = nullptr; + } + + osg::Vec3f KeyframeController::getTranslation(float time) const + { + osg::Vec3f translationValue; + std::string animationName; + float newTime = time; + + //Find the correct animation based on time + for (auto emulatedAnimation : mEmulatedAnimations) + { + if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) + { + newTime = time - emulatedAnimation.mStartTime; + animationName = emulatedAnimation.mName; + } + } + + //Find the root transform track in animation + for (auto mergedAnimationTrack : mMergedAnimationTracks) + { + if (mergedAnimationTrack->getName() != animationName) continue; + + const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); + + for (const osg::ref_ptr channel: channels) + { + if (channel->getTargetName() != "root" || channel->getName() != "transform") continue; + + if ( osgAnimation::MatrixLinearSampler* templateSampler = dynamic_cast (channel->getSampler()) ) + { + osg::Matrixf matrix; + templateSampler->getValueAt(newTime, matrix); + translationValue = matrix.getTrans(); + return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]); + } + } + } + + return osg::Vec3f(); + } + + void KeyframeController::update(float time, std::string animationName) + { + for (auto mergedAnimationTrack : mMergedAnimationTracks) + { + if (mergedAnimationTrack->getName() == animationName) mergedAnimationTrack->update(time); + } + } + + void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) + { + if (hasInput()) + { + if (mNeedToLink) + { + for (auto mergedAnimationTrack : mMergedAnimationTracks) + { + if (!mLinker.valid()) mLinker = new LinkVisitor(); + mLinker->setAnimation(mergedAnimationTrack); + node->accept(*mLinker); + } + mNeedToLink = false; + } + + float time = getInputValue(nv); + + for (auto emulatedAnimation : mEmulatedAnimations) + { + if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) + { + update(time - emulatedAnimation.mStartTime, emulatedAnimation.mName); + } + } + } + + traverse(node, nv); + } + + void KeyframeController::setEmulatedAnimations(std::vector emulatedAnimations) + { + mEmulatedAnimations = emulatedAnimations; + } + + void KeyframeController::addMergedAnimationTrack(osg::ref_ptr animationTrack) + { + mMergedAnimationTracks.emplace_back(animationTrack); + } +} diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp new file mode 100644 index 0000000000..4ae1221992 --- /dev/null +++ b/components/sceneutil/osgacontroller.hpp @@ -0,0 +1,70 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_OSGACONTROLLER_HPP +#define OPENMW_COMPONENTS_SCENEUTIL_OSGACONTROLLER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace OsgaController +{ + class LinkVisitor : public osg::NodeVisitor + { + public: + LinkVisitor(); + + virtual void link(osgAnimation::UpdateMatrixTransform* umt); + + virtual void handle_stateset(osg::StateSet* stateset); + + virtual void setAnimation(Resource::Animation* animation); + + virtual void apply(osg::Node& node); + + virtual void apply(osg::Geode& node); + + protected: + Resource::Animation* mAnimation; + }; + + class KeyframeController : public SceneUtil::KeyframeController + { + public: + /// @brief Handles the animation for osgAnimation formats + KeyframeController() {}; + + KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop); + + META_Object(OsgaController, KeyframeController) + + /// @brief Handles the location of the instance + osg::Vec3f getTranslation(float time) const override; + + /// @brief Calls animation track update() + void update(float time, std::string animationName); + + /// @brief Called every frame for osgAnimation + void operator() (osg::Node*, osg::NodeVisitor*) override; + + /// @brief Sets details of the animations + void setEmulatedAnimations(std::vector emulatedAnimations); + + /// @brief Adds an animation track to a model + void addMergedAnimationTrack(osg::ref_ptr animationTrack); + private: + bool mNeedToLink = true; + osg::ref_ptr mLinker; + }; +} + +#endif From 2413de38b52d6c6184f5b393f800d20086345b37 Mon Sep 17 00:00:00 2001 From: jefetienne Date: Tue, 17 Nov 2020 19:47:56 -0500 Subject: [PATCH 0024/2859] Extend spell/item search to search by magic effect name --- apps/openmw/mwgui/spellmodel.cpp | 46 +++++++++++++++++++++++++++++--- apps/openmw/mwgui/spellmodel.hpp | 3 +++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 1dedfa10b1..2ae92e33fe 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -42,6 +42,44 @@ namespace MWGui { } + bool SpellModel::matchingEffectExists(std::string filter, const ESM::EffectList &effects) + { + auto wm = MWBase::Environment::get().getWindowManager(); + const MWWorld::ESMStore &store = + MWBase::Environment::get().getWorld()->getStore(); + + for (unsigned int i = 0; i < effects.mList.size(); ++i) + { + short effectId = effects.mList[i].mEffectID; + const ESM::MagicEffect *magicEffect = + store.get().search(effectId); + + if (effectId != -1) + { + std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId); + std::string fullEffectName = wm->getGameSettingString(effectIDStr, ""); + + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effects.mList[i].mSkill != -1) + { + fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effects.mList[i].mSkill], ""); + } + + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effects.mList[i].mAttribute != -1) + { + fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effects.mList[i].mAttribute], ""); + } + + std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName); + if (convert.find(filter) != std::string::npos) + { + return true; + } + } + } + + return false; + } + void SpellModel::update() { mSpells.clear(); @@ -61,8 +99,9 @@ namespace MWGui continue; std::string name = Misc::StringUtils::lowerCaseUtf8(spell->mName); - - if (name.find(filter) == std::string::npos) + + if (name.find(filter) == std::string::npos + && !matchingEffectExists(filter, spell->mEffects)) continue; Spell newSpell; @@ -103,7 +142,8 @@ namespace MWGui std::string name = Misc::StringUtils::lowerCaseUtf8(item.getClass().getName(item)); - if (name.find(filter) == std::string::npos) + if (name.find(filter) == std::string::npos + && !matchingEffectExists(filter, enchant->mEffects)) continue; Spell newSpell; diff --git a/apps/openmw/mwgui/spellmodel.hpp b/apps/openmw/mwgui/spellmodel.hpp index d191cba0e0..2404610bf1 100644 --- a/apps/openmw/mwgui/spellmodel.hpp +++ b/apps/openmw/mwgui/spellmodel.hpp @@ -2,6 +2,7 @@ #define OPENMW_GUI_SPELLMODEL_H #include "../mwworld/ptr.hpp" +#include namespace MWGui { @@ -57,6 +58,8 @@ namespace MWGui std::vector mSpells; std::string mFilter; + + bool matchingEffectExists(std::string filter, const ESM::EffectList &effects); }; } From bc6f46465f9d3bf09aebbd44689898673998efe4 Mon Sep 17 00:00:00 2001 From: jefetienne Date: Wed, 18 Nov 2020 14:30:41 -0500 Subject: [PATCH 0025/2859] Add to changelog, authors. Move variable declaration inside block --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/openmw/mwgui/spellmodel.cpp | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 25004078e0..09ab784125 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -96,6 +96,7 @@ Programmers Jan Borsodi (am0s) Jason Hooks (jhooks) jeaye + jefetienne Jeffrey Haines (Jyby) Jengerer Jiří Kuneš (kunesj) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51babd907b..8f909a19fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Feature #5642: Ability to attach arrows to actor skeleton instead of bow mesh Feature #5649: Skyrim SE compressed BSA format support Feature #5672: Make stretch menu background configuration more accessible + Feature #5692: Improve spell/magic item search to factor in magic effect names Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 2ae92e33fe..136547c4c1 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -51,11 +51,11 @@ namespace MWGui for (unsigned int i = 0; i < effects.mList.size(); ++i) { short effectId = effects.mList[i].mEffectID; - const ESM::MagicEffect *magicEffect = - store.get().search(effectId); if (effectId != -1) { + const ESM::MagicEffect *magicEffect = + store.get().search(effectId); std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId); std::string fullEffectName = wm->getGameSettingString(effectIDStr, ""); From 32d4344803702efb99d435b0732a4c883c4c360a Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 20 Nov 2020 19:38:29 +0200 Subject: [PATCH 0026/2859] Don't copy osga-data in base class keyframecontroller, fix warnings. --- components/resource/keyframemanager.cpp | 4 ++-- components/resource/keyframemanager.hpp | 2 +- components/sceneutil/keyframe.hpp | 15 +-------------- components/sceneutil/osgacontroller.cpp | 4 +++- components/sceneutil/osgacontroller.hpp | 16 +++++++++++++--- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 6cda9728cd..d084d6a08e 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -31,7 +31,7 @@ namespace OsgAOpenMW osg::ref_ptr callback = new OsgaController::KeyframeController(); - std::vector emulatedAnimations; + std::vector emulatedAnimations; for (auto animation : mAnimationManager->getAnimationList()) { @@ -69,7 +69,7 @@ namespace OsgAOpenMW mTarget.mTextKeys.emplace(startTime, std::move(loopstart)); mTarget.mTextKeys.emplace(stopTime, std::move(loopstop)); - SceneUtil::EmulatedAnimation emulatedAnimation; + OsgaController::EmulatedAnimation emulatedAnimation; emulatedAnimation.mStartTime = startTime; emulatedAnimation.mStopTime = stopTime; emulatedAnimation.mName = animationName; diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 4f83196f34..778b4bc9a9 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -17,7 +17,7 @@ namespace OsgAOpenMW public: RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager); - virtual void apply(osg::Node& node); + virtual void apply(osg::Node& node) override; private: SceneUtil::KeyframeHolder& mTarget; diff --git a/components/sceneutil/keyframe.hpp b/components/sceneutil/keyframe.hpp index 5a0435469c..e09541cb9a 100644 --- a/components/sceneutil/keyframe.hpp +++ b/components/sceneutil/keyframe.hpp @@ -11,13 +11,6 @@ namespace SceneUtil { - struct EmulatedAnimation - { - float mStartTime; - float mStopTime; - std::string mName; - }; - class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller { public: @@ -26,18 +19,12 @@ namespace SceneUtil KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) , SceneUtil::Controller(copy) - , mMergedAnimationTracks(copy.mMergedAnimationTracks) - , mEmulatedAnimations(copy.mEmulatedAnimations) {} META_Object(SceneUtil, KeyframeController) virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } - virtual void operator() (osg::Node* node, osg::NodeVisitor* nodeVisitor) { traverse(node, nodeVisitor); } - - protected: - std::vector> mMergedAnimationTracks; // Used only by osgAnimation-based formats (e.g. dae) - std::vector mEmulatedAnimations; + virtual void operator() (osg::Node* node, osg::NodeVisitor* nodeVisitor) override { traverse(node, nodeVisitor); } }; /// Wrapper object containing an animation track as a ref-countable osg::Object. diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 6b635d4ea1..e20ecb5b89 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -102,6 +102,8 @@ namespace OsgaController } KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) + , mMergedAnimationTracks(copy.mMergedAnimationTracks) + , mEmulatedAnimations(copy.mEmulatedAnimations) { mLinker = nullptr; } @@ -183,7 +185,7 @@ namespace OsgaController traverse(node, nv); } - void KeyframeController::setEmulatedAnimations(std::vector emulatedAnimations) + void KeyframeController::setEmulatedAnimations(std::vector emulatedAnimations) { mEmulatedAnimations = emulatedAnimations; } diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 4ae1221992..a3072088aa 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -18,6 +18,13 @@ namespace OsgaController { + struct EmulatedAnimation + { + float mStartTime; + float mStopTime; + std::string mName; + }; + class LinkVisitor : public osg::NodeVisitor { public: @@ -29,9 +36,9 @@ namespace OsgaController virtual void setAnimation(Resource::Animation* animation); - virtual void apply(osg::Node& node); + virtual void apply(osg::Node& node) override; - virtual void apply(osg::Geode& node); + virtual void apply(osg::Geode& node) override; protected: Resource::Animation* mAnimation; @@ -57,13 +64,16 @@ namespace OsgaController void operator() (osg::Node*, osg::NodeVisitor*) override; /// @brief Sets details of the animations - void setEmulatedAnimations(std::vector emulatedAnimations); + void setEmulatedAnimations(std::vector emulatedAnimations); /// @brief Adds an animation track to a model void addMergedAnimationTrack(osg::ref_ptr animationTrack); + private: bool mNeedToLink = true; osg::ref_ptr mLinker; + std::vector> mMergedAnimationTracks; // Used only by osgAnimation-based formats (e.g. dae) + std::vector mEmulatedAnimations; }; } From 3232faa703bfe5963895c35a69534def1c283e21 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 20 Nov 2020 19:41:01 +0200 Subject: [PATCH 0027/2859] Use const ref instead of value --- components/sceneutil/osgacontroller.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index e20ecb5b89..503d9cf022 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -115,7 +115,7 @@ namespace OsgaController float newTime = time; //Find the correct animation based on time - for (auto emulatedAnimation : mEmulatedAnimations) + for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) { if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) { @@ -125,7 +125,7 @@ namespace OsgaController } //Find the root transform track in animation - for (auto mergedAnimationTrack : mMergedAnimationTracks) + for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() != animationName) continue; @@ -150,7 +150,7 @@ namespace OsgaController void KeyframeController::update(float time, std::string animationName) { - for (auto mergedAnimationTrack : mMergedAnimationTracks) + for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() == animationName) mergedAnimationTrack->update(time); } @@ -162,7 +162,7 @@ namespace OsgaController { if (mNeedToLink) { - for (auto mergedAnimationTrack : mMergedAnimationTracks) + for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) { if (!mLinker.valid()) mLinker = new LinkVisitor(); mLinker->setAnimation(mergedAnimationTrack); @@ -173,7 +173,7 @@ namespace OsgaController float time = getInputValue(nv); - for (auto emulatedAnimation : mEmulatedAnimations) + for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) { if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) { From 08dcbe30b37cb266bebf3c8171e9130e5bea9d5d Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 20 Nov 2020 19:46:08 +0200 Subject: [PATCH 0028/2859] Earlier nullptr check --- components/resource/keyframemanager.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index d084d6a08e..421b0ddf7b 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -23,12 +23,6 @@ namespace OsgAOpenMW { if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("root")) { - if (!mAnimationManager) - { - traverse(node); - return; - } - osg::ref_ptr callback = new OsgaController::KeyframeController(); std::vector emulatedAnimations; @@ -125,8 +119,11 @@ namespace Resource { osg::ref_ptr scene = const_cast ( mSceneManager->getTemplate(normalized).get() ); osg::ref_ptr bam = dynamic_cast (scene->getUpdateCallback()); - OsgAOpenMW::RetrieveAnimationsVisitor rav(*loaded.get(), bam); - scene->accept(rav); + if (bam) + { + OsgAOpenMW::RetrieveAnimationsVisitor rav(*loaded.get(), bam); + scene->accept(rav); + } } mCache->addEntryToObjectCache(normalized, loaded); return loaded; From 9aba55a21a0d7423b91a40ae9234144e3b823ec2 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Fri, 20 Nov 2020 13:11:53 +0100 Subject: [PATCH 0029/2859] Add the async physics worker to the profiler overlay. --- apps/openmw/engine.cpp | 10 +++++++++- apps/openmw/mwbase/world.hpp | 4 +++- apps/openmw/mwphysics/mtphysics.cpp | 17 ++++++++++++++++- apps/openmw/mwphysics/mtphysics.hpp | 10 +++++++++- apps/openmw/mwphysics/physicssystem.cpp | 5 +++-- apps/openmw/mwphysics/physicssystem.hpp | 3 ++- apps/openmw/mwworld/worldimp.cpp | 9 +++++---- apps/openmw/mwworld/worldimp.hpp | 4 ++-- 8 files changed, 49 insertions(+), 13 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 49a4b40590..0bfd67dd77 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -94,6 +94,7 @@ namespace Script, Mechanics, Physics, + PhysicsWorker, World, Gui, @@ -124,6 +125,9 @@ namespace template <> const UserStats UserStatsValue::sValue {"Phys", "physics"}; + template <> + const UserStats UserStatsValue::sValue {" -Async", "physicsworker"}; + template <> const UserStats UserStatsValue::sValue {"World", "world"}; @@ -203,6 +207,10 @@ namespace profiler.addUserStatsLine(v.mLabel, textColor, barColor, v.mTaken, multiplier, average, averageInInverseSpace, v.mBegin, v.mEnd, maxValue); }); + // the forEachUserStatsValue loop is "run" at compile time, hence the settings manager is not available. + // Unconditionnally add the async physics stats, and then remove it at runtime if necessary + if (Settings::Manager::getInt("async num threads", "Physics") == 0) + profiler.removeUserStatsLine(" -Async"); } } @@ -318,7 +326,7 @@ bool OMW::Engine::frame(float frametime) if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { - mEnvironment.getWorld()->updatePhysics(frametime, guiActive); + mEnvironment.getWorld()->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); } } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f9cbc89723..49bc76a761 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -10,6 +10,8 @@ #include +#include + #include "../mwworld/ptr.hpp" #include "../mwworld/doorstate.hpp" @@ -391,7 +393,7 @@ namespace MWBase /// \return pointer to created record virtual void update (float duration, bool paused) = 0; - virtual void updatePhysics (float duration, bool paused) = 0; + virtual void updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) = 0; virtual void updateWindowManager () = 0; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index dbb714fac5..8c06e01403 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -1,6 +1,8 @@ #include #include +#include + #include "components/debug/debuglog.hpp" #include #include "components/misc/convert.hpp" @@ -154,6 +156,8 @@ namespace MWPhysics , mQuit(false) , mNextJob(0) , mNextLOS(0) + , mFrameNumber(0) + , mTimer(osg::Timer::instance()) { mNumThreads = Config::computeNumThreads(mThreadSafeBullet); @@ -192,6 +196,7 @@ namespace MWPhysics [](const LOSRequest& req) { return req.mStale; }), mLOSCache.end()); } + mTimeEnd = mTimer->tick(); }); } @@ -207,7 +212,7 @@ namespace MWPhysics thread.join(); } - const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skipSimulation) + const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. @@ -230,6 +235,16 @@ namespace MWPhysics if (mMovementResults.find(data.mPtr) != mMovementResults.end()) data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); } + + if (mFrameNumber == frameNumber - 1) + { + stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin)); + stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd)); + stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd)); + } + mFrameStart = frameStart; + mTimeBegin = mTimer->tick(); + mFrameNumber = frameNumber; } // init diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index ef1c90b873..c061fe01da 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -9,6 +9,8 @@ #include +#include + #include "physicssystem.hpp" #include "ptrholder.hpp" @@ -30,7 +32,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skip); + const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skip, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; @@ -87,6 +89,12 @@ namespace MWPhysics mutable std::shared_mutex mLOSCacheMutex; mutable std::mutex mUpdateAabbMutex; std::condition_variable_any mHasJob; + + unsigned int mFrameNumber; + const osg::Timer* mTimer; + osg::Timer_t mTimeBegin; + osg::Timer_t mTimeEnd; + osg::Timer_t mFrameStart; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8a58919ca5..1ceba2f315 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -665,7 +666,7 @@ namespace MWPhysics mMovementQueue.clear(); } - const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation) + const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { mTimeAccum += dt; @@ -675,7 +676,7 @@ namespace MWPhysics mTimeAccum -= numSteps * mPhysicsDt; - return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), skipSimulation); + return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), skipSimulation, frameStart, frameNumber, stats); } std::vector PhysicsSystem::prepareFrameData(int numSteps) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ccfca5422a..379aea1dd4 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "../mwworld/ptr.hpp" @@ -200,7 +201,7 @@ namespace MWPhysics void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); /// Apply all queued movements, then clear the list. - const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation); + const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ad6c337909..54b94833e3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -1494,14 +1495,14 @@ namespace MWWorld mPhysics->updateAnimatedCollisionShape(ptr); } - void World::doPhysics(float duration) + void World::doPhysics(float duration, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { mPhysics->stepSimulation(); processDoors(duration); mProjectileManager->update(duration); - const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements); + const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); mDiscardMovements = false; for(const auto& [actor, position]: results) @@ -1830,11 +1831,11 @@ namespace MWWorld } } - void World::updatePhysics (float duration, bool paused) + void World::updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { if (!paused) { - doPhysics (duration); + doPhysics (duration, frameStart, frameNumber, stats); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 909ac1d412..41ab9cf2ca 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -157,7 +157,7 @@ namespace MWWorld void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. - void doPhysics(float duration); + void doPhysics(float duration, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); ///< Run physics simulation and modify \a world accordingly. void updateNavigator(); @@ -493,7 +493,7 @@ namespace MWWorld /// \return pointer to created record void update (float duration, bool paused) override; - void updatePhysics (float duration, bool paused) override; + void updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) override; void updateWindowManager () override; From 96e22bd44e58e69487456759ffc0a11125ecc7b7 Mon Sep 17 00:00:00 2001 From: psi29a Date: Mon, 23 Nov 2020 06:05:45 +0000 Subject: [PATCH 0030/2859] Merge branch 'fastforwardpos' into 'master' Discard physics simulation results after fast forward See merge request OpenMW/openmw!423 (cherry picked from commit ff2d7695698341ef059c75707aa092cef48deea4) 03a37433 In case of time fast forward (resting, jail), force reset of positions --- apps/openmw/mwworld/worldimp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 54b94833e3..20ee7625b4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -890,6 +890,7 @@ namespace MWWorld { mRendering->notifyWorldSpaceChanged(); mProjectileManager->clear(); + mDiscardMovements = true; } } From 4acd910b3768f0f0d3e941d318dad8480d15e0f0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 13 Nov 2020 10:36:26 +0400 Subject: [PATCH 0031/2859] Rework file error messages handling --- components/files/lowlevelfile.cpp | 100 +++++++++++++++++++----------- 1 file changed, 65 insertions(+), 35 deletions(-) diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 424527b9d9..07915abba0 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -4,6 +4,11 @@ #include #include +#if FILE_API == FILE_API_STDIO +#include +#include +#endif + #if FILE_API == FILE_API_POSIX #include #include @@ -39,7 +44,7 @@ void LowLevelFile::open (char const * filename) if (mHandle == nullptr) { std::ostringstream os; - os << "Failed to open '" << filename << "' for reading."; + os << "Failed to open '" << filename << "' for reading: " << strerror(errno); throw std::runtime_error (os.str ()); } } @@ -58,42 +63,63 @@ size_t LowLevelFile::size () assert (mHandle != nullptr); long oldPosition = ftell (mHandle); - if (oldPosition == -1) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An ftell() call failed: " << strerror(errno); + throw std::runtime_error (os.str ()); + } if (fseek (mHandle, 0, SEEK_END) != 0) - throw std::runtime_error ("A query operation on a file failed."); - - long Size = ftell (mHandle); + { + std::ostringstream os; + os << "An fseek() call failed: " << strerror(errno); + throw std::runtime_error (os.str ()); + } - if (Size == -1) - throw std::runtime_error ("A query operation on a file failed."); + long size = ftell (mHandle); + if (size == -1) + { + std::ostringstream os; + os << "An ftell() call failed: " << strerror(errno); + throw std::runtime_error (os.str ()); + } if (fseek (mHandle, oldPosition, SEEK_SET) != 0) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An fseek() call failed: " << strerror(errno); + throw std::runtime_error (os.str ()); + } - return size_t (Size); + return size_t (size); } -void LowLevelFile::seek (size_t Position) +void LowLevelFile::seek (size_t position) { assert (mHandle != nullptr); - if (fseek (mHandle, Position, SEEK_SET) != 0) - throw std::runtime_error ("A seek operation on a file failed."); + if (fseek (mHandle, position, SEEK_SET) != 0) + { + std::ostringstream os; + os << "An fseek() call failed: " << strerror(errno); + throw std::runtime_error (os.str ()); + } } size_t LowLevelFile::tell () { assert (mHandle != nullptr); - long Position = ftell (mHandle); - - if (Position == -1) - throw std::runtime_error ("A query operation on a file failed."); + long position = ftell (mHandle); + if (position == -1) + { + std::ostringstream os; + os << "An ftell() call failed: " << strerror(errno); + throw std::runtime_error (os.str ()); + } - return size_t (Position); + return size_t (position); } size_t LowLevelFile::read (void * data, size_t size) @@ -103,7 +129,11 @@ size_t LowLevelFile::read (void * data, size_t size) int amount = fread (data, 1, size, mHandle); if (amount == 0 && ferror (mHandle)) - throw std::runtime_error ("A read operation on a file failed."); + { + std::ostringstream os; + os << "An attempt to read " << size << " bytes failed: " << strerror(errno); + throw std::runtime_error (os.str ()); + } return amount; } @@ -164,37 +194,37 @@ size_t LowLevelFile::size () if (oldPosition == size_t (-1)) { std::ostringstream os; - os << "An lseek() call failed:" << strerror(errno); + os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } - size_t Size = ::lseek (mHandle, 0, SEEK_END); + size_t size = ::lseek (mHandle, 0, SEEK_END); - if (Size == size_t (-1)) + if (size == size_t (-1)) { std::ostringstream os; - os << "An lseek() call failed:" << strerror(errno); + os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } if (lseek (mHandle, oldPosition, SEEK_SET) == -1) { std::ostringstream os; - os << "An lseek() call failed:" << strerror(errno); + os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } - return Size; + return size; } -void LowLevelFile::seek (size_t Position) +void LowLevelFile::seek (size_t position) { assert (mHandle != -1); - if (::lseek (mHandle, Position, SEEK_SET) == -1) + if (::lseek (mHandle, position, SEEK_SET) == -1) { std::ostringstream os; - os << "An lseek() call failed:" << strerror(errno); + os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } } @@ -203,16 +233,16 @@ size_t LowLevelFile::tell () { assert (mHandle != -1); - size_t Position = ::lseek (mHandle, 0, SEEK_CUR); + size_t position = ::lseek (mHandle, 0, SEEK_CUR); - if (Position == size_t (-1)) + if (position == size_t (-1)) { std::ostringstream os; - os << "An lseek() call failed:" << strerror(errno); + os << "An lseek() call failed: " << strerror(errno); throw std::runtime_error (os.str ()); } - return Position; + return position; } size_t LowLevelFile::read (void * data, size_t size) @@ -224,7 +254,7 @@ size_t LowLevelFile::read (void * data, size_t size) if (amount == -1) { std::ostringstream os; - os << "An attempt to read " << size << "bytes failed:" << strerror(errno); + os << "An attempt to read " << size << " bytes failed: " << strerror(errno); throw std::runtime_error (os.str ()); } @@ -292,11 +322,11 @@ size_t LowLevelFile::size () return info.nFileSizeLow; } -void LowLevelFile::seek (size_t Position) +void LowLevelFile::seek (size_t position) { assert (mHandle != INVALID_HANDLE_VALUE); - if (SetFilePointer (mHandle, Position, nullptr, SEEK_SET) == INVALID_SET_FILE_POINTER) + if (SetFilePointer (mHandle, position, nullptr, SEEK_SET) == INVALID_SET_FILE_POINTER) if (GetLastError () != NO_ERROR) throw std::runtime_error ("A seek operation on a file failed."); } From 5b6377b061b1814b25e81d5927285acce8f70825 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 28 Nov 2020 13:12:30 +0300 Subject: [PATCH 0032/2859] Handle multiple root nodes (bug #5604) --- CHANGELOG.md | 1 + components/nifbullet/bulletnifloader.cpp | 95 +++++++++++++----------- components/nifosg/nifloader.cpp | 53 +++++++------ 3 files changed, 81 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f909a19fd..7257e23386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Bug #5557: Diagonal movement is noticeably slower with analogue stick Bug #5588: Randomly clicking on the journal's right-side page when it's empty shows random topics Bug #5603: Setting constant effect cast style doesn't correct effects view + Bug #5604: Only one valid NIF root node is loaded from a single file Bug #5611: Usable items with "0 Uses" should be used only once Bug #5622: Can't properly interact with the console when in pause menu Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index b1461e5367..db9d5ae437 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -123,73 +123,79 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) mStaticMesh.reset(); mAvoidStaticMesh.reset(); - Nif::Node* node = nullptr; const size_t numRoots = nif.numRoots(); + std::vector roots; for (size_t i = 0; i < numRoots; ++i) { Nif::Record* r = nif.getRoot(i); assert(r != nullptr); + Nif::Node* node = nullptr; if ((node = dynamic_cast(r))) - break; + roots.emplace_back(node); } const std::string filename = nif.getFilename(); - if (!node) + if (roots.empty()) { warn("Found no root nodes in NIF file " + filename); return mShape; } - if (findBoundingBox(node, filename)) + // Try to find a valid bounding box first. If one's found for any root node, use that. + for (const Nif::Node* node : roots) { - const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); - const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); - std::unique_ptr compound (new btCompoundShape); - std::unique_ptr boxShape(new btBoxShape(halfExtents)); - btTransform transform = btTransform::getIdentity(); - transform.setOrigin(origin); - compound->addChildShape(transform, boxShape.get()); - boxShape.release(); - - mShape->mCollisionShape = compound.release(); - return mShape; + if (findBoundingBox(node, filename)) + { + const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); + const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); + std::unique_ptr compound (new btCompoundShape); + std::unique_ptr boxShape(new btBoxShape(halfExtents)); + btTransform transform = btTransform::getIdentity(); + transform.setOrigin(origin); + compound->addChildShape(transform, boxShape.get()); + boxShape.release(); + + mShape->mCollisionShape = compound.release(); + return mShape; + } } - else + // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). + // assume all nodes in the file will be animated + const bool isAnimated = pathFileNameStartsWithX(filename); + + // If there's no bounding box, we'll have to generate a Bullet collision shape + // from the collision data present in every root node. + for (const Nif::Node* node : roots) { bool autogenerated = hasAutoGeneratedCollision(node); - - // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). - // assume all nodes in the file will be animated - const bool isAnimated = pathFileNameStartsWithX(filename); - handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated); + } - if (mCompoundShape) - { - if (mStaticMesh) - { - btTransform trans; - trans.setIdentity(); - std::unique_ptr child(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); - mCompoundShape->addChildShape(trans, child.get()); - child.release(); - mStaticMesh.release(); - } - mShape->mCollisionShape = mCompoundShape.release(); - } - else if (mStaticMesh) + if (mCompoundShape) + { + if (mStaticMesh) { - mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh.get(), true); + btTransform trans; + trans.setIdentity(); + std::unique_ptr child(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); + mCompoundShape->addChildShape(trans, child.get()); + child.release(); mStaticMesh.release(); } + mShape->mCollisionShape = mCompoundShape.release(); + } + else if (mStaticMesh) + { + mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh.get(), true); + mStaticMesh.release(); + } - if (mAvoidStaticMesh) - { - mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false); - mAvoidStaticMesh.release(); - } - - return mShape; + if (mAvoidStaticMesh) + { + mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false); + mAvoidStaticMesh.release(); } + + return mShape; } // Find a boundingBox in the node hierarchy. @@ -228,8 +234,7 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& { if(!list[i].empty()) { - bool found = findBoundingBox (list[i].getPtr(), filename); - if (found) + if (findBoundingBox(list[i].getPtr(), filename)) return true; } } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a5a61b3176..815f1aef20 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -231,6 +231,9 @@ namespace NifOsg size_t mFirstRootTextureIndex = -1; bool mFoundFirstRootTexturingProperty = false; + bool mHasNightDayLabel = false; + bool mHasHerbalismLabel = false; + // This is used to queue emitters that weren't attached to their node yet. std::vector>> mEmitterQueue; @@ -294,20 +297,31 @@ namespace NifOsg osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::ImageManager* imageManager) { - const Nif::Node* nifNode = nullptr; const size_t numRoots = nif->numRoots(); + std::vector roots; for (size_t i = 0; i < numRoots; ++i) { const Nif::Record* r = nif->getRoot(i); + const Nif::Node* nifNode = nullptr; if ((nifNode = dynamic_cast(r))) - break; + roots.emplace_back(nifNode); } - if (!nifNode) + if (roots.empty()) nif->fail("Found no root nodes"); osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + osg::ref_ptr created(new osg::Group); + created->setDataVariance(osg::Object::STATIC); + for (const Nif::Node* root : roots) + { + auto node = handleNode(root, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + created->addChild(node); + } + if (mHasNightDayLabel) + created->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); + if (mHasHerbalismLabel) + created->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); // Attach particle emitters to their nodes which should all be loaded by now. handleQueuedParticleEmitters(created, nif); @@ -315,18 +329,11 @@ namespace NifOsg if (nif->getUseSkinning()) { osg::ref_ptr skel = new SceneUtil::Skeleton; - - osg::Group* root = created->asGroup(); - if (root && root->getDataVariance() == osg::Object::STATIC && !root->asTransform()) - { - skel->setStateSet(root->getStateSet()); - skel->setName(root->getName()); - for (unsigned int i=0; igetNumChildren(); ++i) - skel->addChild(root->getChild(i)); - root->removeChildren(0, root->getNumChildren()); - } - else - skel->addChild(created); + skel->setStateSet(created->getStateSet()); + skel->setName(created->getName()); + for (unsigned int i=0; i < created->getNumChildren(); ++i) + skel->addChild(created->getChild(i)); + created->removeChildren(0, created->getNumChildren()); created = skel; } @@ -632,7 +639,7 @@ namespace NifOsg } if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) - handleParticleSystem(nifNode, node, composite, animflags, rootNode); + handleParticleSystem(nifNode, node, composite, animflags); if (composite->getNumControllers() > 0) { @@ -662,10 +669,10 @@ namespace NifOsg const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); osg::ref_ptr switchNode = handleSwitchNode(niSwitchNode); node->addChild(switchNode); - if (niSwitchNode->name == Constants::NightDayLabel && !SceneUtil::hasUserDescription(rootNode, Constants::NightDayLabel)) - rootNode->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); - else if (niSwitchNode->name == Constants::HerbalismLabel && !SceneUtil::hasUserDescription(rootNode, Constants::HerbalismLabel)) - rootNode->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); + if (niSwitchNode->name == Constants::NightDayLabel) + mHasNightDayLabel = true; + else if (niSwitchNode->name == Constants::HerbalismLabel) + mHasHerbalismLabel = true; currentNode = switchNode; } @@ -1023,7 +1030,7 @@ namespace NifOsg return emitter; } - void handleQueuedParticleEmitters(osg::Node* rootNode, Nif::NIFFilePtr nif) + void handleQueuedParticleEmitters(osg::Group* rootNode, Nif::NIFFilePtr nif) { for (const auto& emitterPair : mEmitterQueue) { @@ -1044,7 +1051,7 @@ namespace NifOsg mEmitterQueue.clear(); } - void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags, osg::Node* rootNode) + void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); From 55dcc6582a7b5024994368fbdf07a998b1394cbd Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 28 Nov 2020 15:03:10 +0200 Subject: [PATCH 0033/2859] Don't duplicate getFileExtension, use OpenMW's namespaces --- components/resource/keyframemanager.cpp | 20 ++++++-------------- components/resource/keyframemanager.hpp | 4 +--- components/resource/scenemanager.cpp | 17 ++++++++--------- components/resource/scenemanager.hpp | 1 + components/sceneutil/osgacontroller.cpp | 14 +++++++------- components/sceneutil/osgacontroller.hpp | 10 +++++----- 6 files changed, 28 insertions(+), 38 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 421b0ddf7b..af0f365ee5 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -14,7 +14,7 @@ #include "objectcache.hpp" #include "scenemanager.hpp" -namespace OsgAOpenMW +namespace Resource { RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager) {} @@ -23,9 +23,9 @@ namespace OsgAOpenMW { if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("root")) { - osg::ref_ptr callback = new OsgaController::KeyframeController(); + osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); - std::vector emulatedAnimations; + std::vector emulatedAnimations; for (auto animation : mAnimationManager->getAnimationList()) { @@ -63,7 +63,7 @@ namespace OsgAOpenMW mTarget.mTextKeys.emplace(startTime, std::move(loopstart)); mTarget.mTextKeys.emplace(stopTime, std::move(loopstop)); - OsgaController::EmulatedAnimation emulatedAnimation; + SceneUtil::EmulatedAnimation emulatedAnimation; emulatedAnimation.mStartTime = startTime; emulatedAnimation.mStopTime = stopTime; emulatedAnimation.mName = animationName; @@ -76,14 +76,6 @@ namespace OsgAOpenMW traverse(node); } - - std::string getFileExtension(const std::string& file) - { - size_t extPos = file.find_last_of('.'); - if (extPos != std::string::npos && extPos+1 < file.size()) - return file.substr(extPos+1); - return std::string(); - } } namespace Resource @@ -110,7 +102,7 @@ namespace Resource else { osg::ref_ptr loaded (new SceneUtil::KeyframeHolder); - std::string ext = OsgAOpenMW::getFileExtension(normalized); + std::string ext = Resource::getFileExtension(normalized); if (ext == "kf") { NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); @@ -121,7 +113,7 @@ namespace Resource osg::ref_ptr bam = dynamic_cast (scene->getUpdateCallback()); if (bam) { - OsgAOpenMW::RetrieveAnimationsVisitor rav(*loaded.get(), bam); + Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam); scene->accept(rav); } } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 778b4bc9a9..3e992ac5e9 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -9,7 +9,7 @@ #include "resourcemanager.hpp" -namespace OsgAOpenMW +namespace Resource { /// @brief extract animations to OpenMW's animation system class RetrieveAnimationsVisitor : public osg::NodeVisitor @@ -24,8 +24,6 @@ namespace OsgAOpenMW osg::ref_ptr mAnimationManager; }; - - std::string getFileExtension(const std::string& file); } namespace Resource diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e99003b749..2630cd4536 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -338,17 +338,9 @@ namespace Resource Resource::ImageManager* mImageManager; }; - std::string getFileExtension(const std::string& file) - { - size_t extPos = file.find_last_of('.'); - if (extPos != std::string::npos && extPos+1 < file.size()) - return file.substr(extPos+1); - return std::string(); - } - osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { - std::string ext = getFileExtension(normalizedFilename); + std::string ext = Resource::getFileExtension(normalizedFilename); if (ext == "nif") return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); else @@ -780,4 +772,11 @@ namespace Resource return shaderVisitor; } + std::string getFileExtension(const std::string& file) + { + size_t extPos = file.find_last_of('.'); + if (extPos != std::string::npos && extPos+1 < file.size()) + return file.substr(extPos+1); + return std::string(); + } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 8df556158e..fd75070a18 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -181,6 +181,7 @@ namespace Resource void operator = (const SceneManager&); }; + std::string getFileExtension(const std::string& file); } #endif diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 503d9cf022..87e6f02fee 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -21,7 +21,7 @@ #include #include -namespace OsgaController +namespace SceneUtil { LinkVisitor::LinkVisitor() : osg::NodeVisitor( TRAVERSE_ALL_CHILDREN ) { @@ -101,14 +101,14 @@ namespace OsgaController apply(static_cast(node)); } - KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) + OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) , mMergedAnimationTracks(copy.mMergedAnimationTracks) , mEmulatedAnimations(copy.mEmulatedAnimations) { mLinker = nullptr; } - osg::Vec3f KeyframeController::getTranslation(float time) const + osg::Vec3f OsgAnimationController::getTranslation(float time) const { osg::Vec3f translationValue; std::string animationName; @@ -148,7 +148,7 @@ namespace OsgaController return osg::Vec3f(); } - void KeyframeController::update(float time, std::string animationName) + void OsgAnimationController::update(float time, std::string animationName) { for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) { @@ -156,7 +156,7 @@ namespace OsgaController } } - void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) + void OsgAnimationController::operator() (osg::Node* node, osg::NodeVisitor* nv) { if (hasInput()) { @@ -185,12 +185,12 @@ namespace OsgaController traverse(node, nv); } - void KeyframeController::setEmulatedAnimations(std::vector emulatedAnimations) + void OsgAnimationController::setEmulatedAnimations(std::vector emulatedAnimations) { mEmulatedAnimations = emulatedAnimations; } - void KeyframeController::addMergedAnimationTrack(osg::ref_ptr animationTrack) + void OsgAnimationController::addMergedAnimationTrack(osg::ref_ptr animationTrack) { mMergedAnimationTracks.emplace_back(animationTrack); } diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index a3072088aa..f1c1584c2a 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -16,7 +16,7 @@ #include #include -namespace OsgaController +namespace SceneUtil { struct EmulatedAnimation { @@ -44,15 +44,15 @@ namespace OsgaController Resource::Animation* mAnimation; }; - class KeyframeController : public SceneUtil::KeyframeController + class OsgAnimationController : public SceneUtil::KeyframeController { public: /// @brief Handles the animation for osgAnimation formats - KeyframeController() {}; + OsgAnimationController() {}; - KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop); + OsgAnimationController(const OsgAnimationController& copy, const osg::CopyOp& copyop); - META_Object(OsgaController, KeyframeController) + META_Object(SceneUtil, OsgAnimationController) /// @brief Handles the location of the instance osg::Vec3f getTranslation(float time) const override; From ea2ba27084c38485ee3ad5fb2fa27e370e9e0e82 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 28 Nov 2020 20:45:23 +0100 Subject: [PATCH 0034/2859] Move the moment when the actor origin is saved before simulation so to be sure the simulation is over. Otherwise, if the simulation is too slow the position is wrong, and the actors would jump back and forth between old and new position instead of actually moving. --- apps/openmw/mwphysics/actor.cpp | 33 +++++++++++++++++-------- apps/openmw/mwphysics/actor.hpp | 8 +++--- apps/openmw/mwphysics/mtphysics.cpp | 7 ++++-- apps/openmw/mwphysics/physicssystem.cpp | 5 +++- apps/openmw/mwphysics/physicssystem.hpp | 1 + 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 430bd4deef..0e557378df 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -118,10 +118,15 @@ int Actor::getCollisionMask() const return collisionMask; } +void Actor::updatePositionUnsafe() +{ + mWorldPosition = mPtr.getRefData().getPosition().asVec3(); +} + void Actor::updatePosition() { std::scoped_lock lock(mPositionMutex); - mWorldPosition = mPtr.getRefData().getPosition().asVec3(); + updatePositionUnsafe(); } osg::Vec3f Actor::getWorldPosition() const @@ -130,19 +135,18 @@ osg::Vec3f Actor::getWorldPosition() const return mWorldPosition; } -void Actor::setNextPosition(const osg::Vec3f& position) +void Actor::setSimulationPosition(const osg::Vec3f& position) { - mNextPosition = position; + mSimulationPosition = position; } -osg::Vec3f Actor::getNextPosition() const +osg::Vec3f Actor::getSimulationPosition() const { - return mNextPosition; + return mSimulationPosition; } -void Actor::updateCollisionObjectPosition() +void Actor::updateCollisionObjectPositionUnsafe() { - std::scoped_lock lock(mPositionMutex); mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); osg::Vec3f newPosition = scaledTranslation + mPosition; @@ -151,6 +155,12 @@ void Actor::updateCollisionObjectPosition() mCollisionObject->setWorldTransform(mLocalTransform); } +void Actor::updateCollisionObjectPosition() +{ + std::scoped_lock lock(mPositionMutex); + updateCollisionObjectPositionUnsafe(); +} + osg::Vec3f Actor::getCollisionObjectPosition() const { std::scoped_lock lock(mPositionMutex); @@ -159,23 +169,26 @@ osg::Vec3f Actor::getCollisionObjectPosition() const void Actor::setPosition(const osg::Vec3f& position) { + std::scoped_lock lock(mPositionMutex); mPreviousPosition = mPosition; mPosition = position; } void Actor::adjustPosition(const osg::Vec3f& offset) { + std::scoped_lock lock(mPositionMutex); mPosition += offset; mPreviousPosition += offset; } void Actor::resetPosition() { - updatePosition(); + std::scoped_lock lock(mPositionMutex); + updatePositionUnsafe(); mPreviousPosition = mWorldPosition; mPosition = mWorldPosition; - mNextPosition = mWorldPosition; - updateCollisionObjectPosition(); + mSimulationPosition = mWorldPosition; + updateCollisionObjectPositionUnsafe(); } osg::Vec3f Actor::getPosition() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 3d6f93ae05..07a9bebd28 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -67,8 +67,8 @@ namespace MWPhysics * Used by the physics simulation to store the simulation result. Used in conjunction with mWorldPosition * to account for e.g. scripted movements */ - void setNextPosition(const osg::Vec3f& position); - osg::Vec3f getNextPosition() const; + void setSimulationPosition(const osg::Vec3f& position); + osg::Vec3f getSimulationPosition() const; void updateCollisionObjectPosition(); @@ -154,6 +154,8 @@ namespace MWPhysics void updateCollisionMask(); void addCollisionMask(int collisionMask); int getCollisionMask() const; + void updateCollisionObjectPositionUnsafe(); + void updatePositionUnsafe(); bool mCanWaterWalk; std::atomic mWalkingOnWater; @@ -172,7 +174,7 @@ namespace MWPhysics osg::Vec3f mScale; osg::Vec3f mRenderingScale; osg::Vec3f mWorldPosition; - osg::Vec3f mNextPosition; + osg::Vec3f mSimulationPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; btTransform mLocalTransform; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 8c06e01403..2d4009e7d8 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -219,6 +219,9 @@ namespace MWPhysics std::unique_lock lock(mSimulationMutex); + for (auto& data : actorsData) + data.updatePosition(); + // start by finishing previous background computation if (mNumThreads != 0) { @@ -233,7 +236,7 @@ namespace MWPhysics data.mActorRaw->setStandingOnPtr(data.mStandingOn); if (mMovementResults.find(data.mPtr) != mMovementResults.end()) - data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); + data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]); } if (mFrameNumber == frameNumber - 1) @@ -284,7 +287,7 @@ namespace MWPhysics if (mAdvanceSimulation) data.mActorRaw->setStandingOnPtr(data.mStandingOn); if (mMovementResults.find(data.mPtr) != mMovementResults.end()) - data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); + data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]); } return mMovementResults; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 1ceba2f315..7aa1f59141 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -877,9 +877,12 @@ namespace MWPhysics mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0; mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead(); mWasOnGround = actor->getOnGround(); + } + void ActorFrameData::updatePosition() + { mActorRaw->updatePosition(); - mOrigin = mActorRaw->getNextPosition(); + mOrigin = mActorRaw->getSimulationPosition(); mPosition = mActorRaw->getPosition(); if (mMoveToWaterSurface) { diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 379aea1dd4..03ae789934 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,6 +78,7 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); + void updatePosition(); std::weak_ptr mActor; Actor* mActorRaw; MWWorld::Ptr mPtr; From 8084a336b52f0d0a1938f1d22732f91baf21a3e3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 13 Nov 2020 11:39:47 +0400 Subject: [PATCH 0035/2859] Replace zeroes and nulls by nullptrs --- apps/launcher/advancedpage.hpp | 2 +- apps/launcher/datafilespage.hpp | 2 +- apps/launcher/graphicspage.hpp | 2 +- apps/launcher/maindialog.hpp | 2 +- apps/launcher/playpage.hpp | 2 +- apps/launcher/settingspage.hpp | 2 +- apps/launcher/utils/lineedit.hpp | 2 +- apps/launcher/utils/profilescombobox.cpp | 2 +- apps/launcher/utils/profilescombobox.hpp | 4 +- apps/launcher/utils/textinputdialog.cpp | 2 +- apps/launcher/utils/textinputdialog.hpp | 2 +- apps/opencs/model/doc/operationholder.hpp | 2 +- apps/opencs/model/doc/runner.cpp | 8 +- apps/opencs/model/prefs/boolsetting.cpp | 4 +- apps/opencs/model/prefs/coloursetting.cpp | 2 +- apps/opencs/model/prefs/doublesetting.cpp | 2 +- apps/opencs/model/prefs/enumsetting.cpp | 2 +- apps/opencs/model/prefs/intsetting.cpp | 2 +- apps/opencs/model/prefs/modifiersetting.cpp | 2 +- apps/opencs/model/prefs/shortcut.cpp | 8 +- apps/opencs/model/prefs/shortcutmanager.cpp | 2 +- apps/opencs/model/prefs/shortcutsetting.cpp | 2 +- apps/opencs/model/prefs/state.cpp | 4 +- apps/opencs/model/tools/mergestages.cpp | 2 +- apps/opencs/model/tools/mergestages.hpp | 2 +- apps/opencs/model/tools/scriptcheck.cpp | 6 +- apps/opencs/model/tools/searchstage.cpp | 2 +- apps/opencs/model/tools/tools.cpp | 6 +- apps/opencs/model/world/columnimp.hpp | 8 +- apps/opencs/model/world/commanddispatcher.hpp | 2 +- apps/opencs/model/world/commands.cpp | 4 +- apps/opencs/model/world/commands.hpp | 18 ++--- apps/opencs/model/world/data.cpp | 18 ++--- apps/opencs/model/world/idtableproxymodel.hpp | 2 +- .../model/world/infotableproxymodel.hpp | 2 +- apps/opencs/model/world/record.hpp | 2 +- apps/opencs/model/world/refidcollection.cpp | 2 +- apps/opencs/model/world/resources.hpp | 4 +- apps/opencs/view/doc/adjusterwidget.hpp | 2 +- apps/opencs/view/doc/filedialog.cpp | 2 +- apps/opencs/view/doc/filedialog.hpp | 2 +- apps/opencs/view/doc/filewidget.hpp | 2 +- .../view/doc/globaldebugprofilemenu.cpp | 4 +- .../view/doc/globaldebugprofilemenu.hpp | 2 +- apps/opencs/view/doc/loader.cpp | 2 +- apps/opencs/view/doc/sizehint.hpp | 2 +- apps/opencs/view/doc/view.cpp | 2 +- apps/opencs/view/doc/viewmanager.hpp | 2 +- apps/opencs/view/filter/editwidget.hpp | 2 +- apps/opencs/view/filter/filterbox.hpp | 2 +- apps/opencs/view/filter/recordfilterbox.hpp | 2 +- apps/opencs/view/prefs/keybindingpage.cpp | 6 +- apps/opencs/view/render/cell.cpp | 4 +- apps/opencs/view/render/cellwater.cpp | 8 +- apps/opencs/view/render/editmode.hpp | 2 +- apps/opencs/view/render/instancemode.cpp | 6 +- apps/opencs/view/render/instancemode.hpp | 2 +- apps/opencs/view/render/instancemovemode.hpp | 2 +- apps/opencs/view/render/lighting.hpp | 2 +- apps/opencs/view/render/object.cpp | 2 +- apps/opencs/view/render/orbitcameramode.cpp | 4 +- .../view/render/pagedworldspacewidget.cpp | 2 +- apps/opencs/view/render/pathgrid.cpp | 12 +-- apps/opencs/view/render/pathgridmode.cpp | 6 +- apps/opencs/view/render/pathgridmode.hpp | 2 +- apps/opencs/view/render/previewwidget.hpp | 2 +- apps/opencs/view/render/scenewidget.cpp | 4 +- apps/opencs/view/render/terrainshapemode.cpp | 16 ++-- .../opencs/view/render/terraintexturemode.cpp | 18 ++--- apps/opencs/view/render/worldspacewidget.cpp | 10 +-- apps/opencs/view/render/worldspacewidget.hpp | 2 +- apps/opencs/view/tools/merge.cpp | 4 +- apps/opencs/view/tools/merge.hpp | 2 +- apps/opencs/view/tools/reporttable.cpp | 6 +- apps/opencs/view/tools/reporttable.hpp | 2 +- apps/opencs/view/tools/searchbox.hpp | 2 +- apps/opencs/view/tools/searchsubview.cpp | 2 +- apps/opencs/view/widget/coloreditor.hpp | 6 +- apps/opencs/view/widget/completerpopup.hpp | 2 +- apps/opencs/view/widget/droplineedit.hpp | 2 +- apps/opencs/view/widget/modebutton.hpp | 2 +- apps/opencs/view/widget/pushbutton.hpp | 4 +- apps/opencs/view/widget/scenetoolbar.hpp | 4 +- apps/opencs/view/widget/scenetoolmode.cpp | 4 +- .../view/widget/scenetoolshapebrush.hpp | 2 +- .../view/widget/scenetooltexturebrush.hpp | 2 +- apps/opencs/view/widget/scenetooltoggle.cpp | 2 +- apps/opencs/view/widget/scenetooltoggle2.cpp | 2 +- apps/opencs/view/world/creator.cpp | 2 +- apps/opencs/view/world/dialoguespinbox.hpp | 4 +- apps/opencs/view/world/dialoguesubview.cpp | 12 +-- apps/opencs/view/world/dialoguesubview.hpp | 4 +- apps/opencs/view/world/enumdelegate.cpp | 2 +- .../world/extendedcommandconfigurator.hpp | 2 +- apps/opencs/view/world/genericcreator.cpp | 8 +- .../view/world/idcompletiondelegate.cpp | 2 +- apps/opencs/view/world/idvalidator.hpp | 2 +- apps/opencs/view/world/recordbuttonbar.hpp | 4 +- .../view/world/recordstatusdelegate.hpp | 2 +- apps/opencs/view/world/regionmap.hpp | 2 +- apps/opencs/view/world/scriptedit.cpp | 4 +- apps/opencs/view/world/scripterrortable.hpp | 2 +- apps/opencs/view/world/scriptsubview.cpp | 4 +- apps/opencs/view/world/tablebottombox.hpp | 2 +- apps/opencs/view/world/tableeditidaction.hpp | 2 +- apps/opencs/view/world/util.cpp | 8 +- apps/openmw/engine.cpp | 2 +- apps/openmw/mwbase/environment.cpp | 26 +++---- apps/openmw/mwbase/statemanager.hpp | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwgui/bookpage.cpp | 6 +- apps/openmw/mwgui/charactercreation.cpp | 54 +++++++------- apps/openmw/mwgui/class.cpp | 16 ++-- apps/openmw/mwgui/console.cpp | 2 +- apps/openmw/mwgui/draganddrop.cpp | 2 +- apps/openmw/mwgui/mainmenu.cpp | 2 +- apps/openmw/mwgui/mapwindow.cpp | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 6 +- apps/openmw/mwgui/review.cpp | 2 +- apps/openmw/mwgui/spellcreationdialog.cpp | 8 +- apps/openmw/mwgui/tooltips.cpp | 4 +- apps/openmw/mwgui/widgets.cpp | 4 +- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- apps/openmw/mwinput/mousemanager.cpp | 2 +- apps/openmw/mwmechanics/alchemy.cpp | 2 +- .../mwscript/transformationextensions.cpp | 4 +- apps/openmw/mwsound/ffmpeg_decoder.cpp | 2 +- apps/openmw/mwsound/openal_output.cpp | 24 +++--- apps/openmw/mwsound/sound_buffer.hpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 4 +- apps/openmw/mwstate/character.cpp | 4 +- apps/openmw/mwstate/charactermanager.cpp | 2 +- apps/openmw/mwstate/statemanagerimp.hpp | 2 +- apps/openmw/mwworld/cells.cpp | 6 +- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 28 +++---- apps/openmw/mwworld/esmstore.cpp | 4 +- apps/openmw/mwworld/esmstore.hpp | 6 +- apps/openmw/mwworld/manualref.cpp | 2 +- apps/openmw/mwworld/player.cpp | 8 +- apps/openmw/mwworld/ptr.cpp | 4 +- apps/openmw/mwworld/ptr.hpp | 24 +++--- apps/openmw/mwworld/refdata.cpp | 18 ++--- apps/openmw/mwworld/scene.cpp | 4 +- apps/openmw/mwworld/store.cpp | 34 ++++----- apps/openmw/mwworld/store.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 12 +-- apps/openmw/mwworld/worldimp.hpp | 2 +- apps/wizard/mainwizard.hpp | 2 +- apps/wizard/unshield/unshieldworker.hpp | 2 +- apps/wizard/utils/componentlistwidget.hpp | 2 +- components/compiler/context.hpp | 4 +- .../contentselector/model/contentmodel.cpp | 6 +- components/contentselector/model/esmfile.hpp | 2 +- .../contentselector/model/modelitem.hpp | 2 +- components/contentselector/view/combobox.cpp | 2 +- components/contentselector/view/combobox.hpp | 2 +- .../contentselector/view/contentselector.hpp | 2 +- components/crashcatcher/crashcatcher.cpp | 2 +- components/detournavigator/findsmoothpath.cpp | 4 +- components/detournavigator/findsmoothpath.hpp | 4 +- components/esm/loadcell.hpp | 2 +- components/esm/variant.cpp | 14 ++-- components/esm/variantimp.hpp | 6 +- components/esmterrain/storage.cpp | 14 ++-- components/files/constrainedfilestream.cpp | 6 +- components/interpreter/runtime.cpp | 6 +- .../myguiplatform/myguirendermanager.cpp | 2 +- components/nifosg/particle.cpp | 2 +- components/resource/imagemanager.cpp | 2 +- components/resource/objectcache.hpp | 2 +- components/sceneutil/lightmanager.cpp | 4 +- components/sdlutil/sdlcursormanager.cpp | 4 +- components/sdlutil/sdlgraphicswindow.cpp | 6 +- components/sdlutil/sdlgraphicswindow.hpp | 2 +- components/terrain/quadtreenode.cpp | 2 +- components/widgets/box.cpp | 8 +- components/widgets/list.cpp | 8 +- .../osg-ffmpeg-videoplayer/audiodecoder.cpp | 8 +- extern/osg-ffmpeg-videoplayer/videoplayer.cpp | 6 +- extern/osg-ffmpeg-videoplayer/videostate.cpp | 74 +++++++++---------- extern/osg-ffmpeg-videoplayer/videostate.hpp | 2 +- 183 files changed, 483 insertions(+), 483 deletions(-) diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index bdf5af0c85..ef2f4740f3 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -20,7 +20,7 @@ namespace Launcher public: AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, - Settings::Manager &engineSettings, QWidget *parent = 0); + Settings::Manager &engineSettings, QWidget *parent = nullptr); bool loadSettings(); void saveSettings(); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index af54fe75e4..92984847e2 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -32,7 +32,7 @@ namespace Launcher public: explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, - Config::LauncherSettings &launcherSettings, QWidget *parent = 0); + Config::LauncherSettings &launcherSettings, QWidget *parent = nullptr); QAbstractItemModel* profilesModel() const; diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 3b03a72bdd..55178e0d75 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -20,7 +20,7 @@ namespace Launcher Q_OBJECT public: - GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0); + GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = nullptr); void saveSettings(); bool loadSettings(); diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 214d31dd4a..de74233305 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -48,7 +48,7 @@ namespace Launcher Q_OBJECT public: - explicit MainDialog(QWidget *parent = 0); + explicit MainDialog(QWidget *parent = nullptr); ~MainDialog(); FirstRunDialogResult showFirstRunDialog(); diff --git a/apps/launcher/playpage.hpp b/apps/launcher/playpage.hpp index 1dc5bb0fe0..b0bd3ee277 100644 --- a/apps/launcher/playpage.hpp +++ b/apps/launcher/playpage.hpp @@ -16,7 +16,7 @@ namespace Launcher Q_OBJECT public: - PlayPage(QWidget *parent = 0); + PlayPage(QWidget *parent = nullptr); void setProfilesModel(QAbstractItemModel *model); signals: diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index ccc2061dd7..97b099dd62 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -24,7 +24,7 @@ namespace Launcher public: SettingsPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, - Config::LauncherSettings &launcherSettings, MainDialog *parent = 0); + Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr); ~SettingsPage(); void saveSettings(); diff --git a/apps/launcher/utils/lineedit.hpp b/apps/launcher/utils/lineedit.hpp index 50da730459..da28e858ce 100644 --- a/apps/launcher/utils/lineedit.hpp +++ b/apps/launcher/utils/lineedit.hpp @@ -24,7 +24,7 @@ class LineEdit : public QLineEdit QString mPlaceholderText; public: - LineEdit(QWidget *parent = 0); + LineEdit(QWidget *parent = nullptr); protected: void resizeEvent(QResizeEvent *) override; diff --git a/apps/launcher/utils/profilescombobox.cpp b/apps/launcher/utils/profilescombobox.cpp index 7df89098e2..462c2ebc27 100644 --- a/apps/launcher/utils/profilescombobox.cpp +++ b/apps/launcher/utils/profilescombobox.cpp @@ -33,7 +33,7 @@ void ProfilesComboBox::setEditEnabled(bool editable) ComboBoxLineEdit *edit = new ComboBoxLineEdit(this); setLineEdit(edit); - setCompleter(0); + setCompleter(nullptr); connect(lineEdit(), SIGNAL(editingFinished()), this, SLOT(slotEditingFinished())); diff --git a/apps/launcher/utils/profilescombobox.hpp b/apps/launcher/utils/profilescombobox.hpp index 7b83c41b2f..065682f3d1 100644 --- a/apps/launcher/utils/profilescombobox.hpp +++ b/apps/launcher/utils/profilescombobox.hpp @@ -16,12 +16,12 @@ public: class ComboBoxLineEdit : public LineEdit { public: - explicit ComboBoxLineEdit (QWidget *parent = 0); + explicit ComboBoxLineEdit (QWidget *parent = nullptr); }; public: - explicit ProfilesComboBox(QWidget *parent = 0); + explicit ProfilesComboBox(QWidget *parent = nullptr); void setEditEnabled(bool editable); void setCurrentProfile(int index) { diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 385d086fd0..70b827596e 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -23,7 +23,7 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString & QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore mLineEdit = new LineEdit(this); mLineEdit->setValidator(validator); - mLineEdit->setCompleter(0); + mLineEdit->setCompleter(nullptr); QVBoxLayout *dialogLayout = new QVBoxLayout(this); dialogLayout->addWidget(label); diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp index a9353956a0..de06599639 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -15,7 +15,7 @@ namespace Launcher public: - explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); + explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = nullptr); ~TextInputDialog (); inline LineEdit *lineEdit() { return mLineEdit; } diff --git a/apps/opencs/model/doc/operationholder.hpp b/apps/opencs/model/doc/operationholder.hpp index b73d61dab1..69af6ed66c 100644 --- a/apps/opencs/model/doc/operationholder.hpp +++ b/apps/opencs/model/doc/operationholder.hpp @@ -25,7 +25,7 @@ namespace CSMDoc public: - OperationHolder (Operation *operation = 0); + OperationHolder (Operation *operation = nullptr); void setOperation (Operation *operation); diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 84bc61a9a0..ccdff1444f 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -8,7 +8,7 @@ #include "operationholder.hpp" CSMDoc::Runner::Runner (const boost::filesystem::path& projectPath) -: mRunning (false), mStartup (0), mProjectPath (projectPath) +: mRunning (false), mStartup (nullptr), mProjectPath (projectPath) { connect (&mProcess, SIGNAL (finished (int, QProcess::ExitStatus)), this, SLOT (finished (int, QProcess::ExitStatus))); @@ -25,7 +25,7 @@ CSMDoc::Runner::~Runner() { if (mRunning) { - disconnect (&mProcess, 0, this, 0); + disconnect (&mProcess, nullptr, this, nullptr); mProcess.kill(); mProcess.waitForFinished(); } @@ -36,7 +36,7 @@ void CSMDoc::Runner::start (bool delayed) if (mStartup) { delete mStartup; - mStartup = 0; + mStartup = nullptr; } if (!delayed) @@ -102,7 +102,7 @@ void CSMDoc::Runner::start (bool delayed) void CSMDoc::Runner::stop() { delete mStartup; - mStartup = 0; + mStartup = nullptr; if (mProcess.state()==QProcess::NotRunning) { diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index 6431dc6af3..0058753490 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -11,7 +11,7 @@ CSMPrefs::BoolSetting::BoolSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, bool default_) -: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(0) +: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(nullptr) {} CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip (const std::string& tooltip) @@ -33,7 +33,7 @@ std::pair CSMPrefs::BoolSetting::makeWidgets (QWidget *par connect (mWidget, SIGNAL (stateChanged (int)), this, SLOT (valueChanged (int))); - return std::make_pair (static_cast (0), mWidget); + return std::make_pair (static_cast (nullptr), mWidget); } void CSMPrefs::BoolSetting::updateWidget() diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index 1a41621da2..e676ad91c3 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -14,7 +14,7 @@ CSMPrefs::ColourSetting::ColourSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, QColor default_) -: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(0) +: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(nullptr) {} CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip (const std::string& tooltip) diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 8ae6f4818c..757b67389a 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -16,7 +16,7 @@ CSMPrefs::DoubleSetting::DoubleSetting (Category *parent, Settings::Manager *val QMutex *mutex, const std::string& key, const std::string& label, double default_) : Setting (parent, values, mutex, key, label), mPrecision(2), mMin (0), mMax (std::numeric_limits::max()), - mDefault (default_), mWidget(0) + mDefault (default_), mWidget(nullptr) {} CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setPrecision(int precision) diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index 62cac062a7..ec3fca328f 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -42,7 +42,7 @@ CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const std::string& value, const CSMPrefs::EnumSetting::EnumSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, const EnumValue& default_) -: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(0) +: Setting (parent, values, mutex, key, label), mDefault (default_), mWidget(nullptr) {} CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip (const std::string& tooltip) diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 25dbf78c29..407ed11f05 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -15,7 +15,7 @@ CSMPrefs::IntSetting::IntSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, int default_) : Setting (parent, values, mutex, key, label), mMin (0), mMax (std::numeric_limits::max()), - mDefault (default_), mWidget(0) + mDefault (default_), mWidget(nullptr) {} CSMPrefs::IntSetting& CSMPrefs::IntSetting::setRange (int min, int max) diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index da6b2ccddf..288926d00b 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -15,7 +15,7 @@ namespace CSMPrefs ModifierSetting::ModifierSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, const std::string& label) : Setting(parent, values, mutex, key, label) - , mButton(0) + , mButton(nullptr) , mEditorActive(false) { } diff --git a/apps/opencs/model/prefs/shortcut.cpp b/apps/opencs/model/prefs/shortcut.cpp index 924b9535e2..ff7b949a4a 100644 --- a/apps/opencs/model/prefs/shortcut.cpp +++ b/apps/opencs/model/prefs/shortcut.cpp @@ -23,7 +23,7 @@ namespace CSMPrefs , mLastPos(0) , mActivationStatus(AS_Inactive) , mModifierStatus(false) - , mAction(0) + , mAction(nullptr) { assert (parent); @@ -42,7 +42,7 @@ namespace CSMPrefs , mLastPos(0) , mActivationStatus(AS_Inactive) , mModifierStatus(false) - , mAction(0) + , mAction(nullptr) { assert (parent); @@ -62,7 +62,7 @@ namespace CSMPrefs , mLastPos(0) , mActivationStatus(AS_Inactive) , mModifierStatus(false) - , mAction(0) + , mAction(nullptr) { assert (parent); @@ -218,6 +218,6 @@ namespace CSMPrefs void Shortcut::actionDeleted() { - mAction = 0; + mAction = nullptr; } } diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index f39492c6c7..781ad4de31 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -781,7 +781,7 @@ namespace CSMPrefs std::make_pair((int)Qt::Key_LastNumberRedial , "LastNumberRedial"), std::make_pair((int)Qt::Key_Camera , "Camera"), std::make_pair((int)Qt::Key_CameraFocus , "CameraFocus"), - std::make_pair(0 , (const char*) 0) + std::make_pair(0 , (const char*) nullptr) }; } diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index de495b9fc9..1c065f7da7 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -18,7 +18,7 @@ namespace CSMPrefs ShortcutSetting::ShortcutSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, const std::string& label) : Setting(parent, values, mutex, key, label) - , mButton(0) + , mButton(nullptr) , mEditorActive(false) , mEditorPos(0) { diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index abd1ddfc8d..39aae48bda 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -12,7 +12,7 @@ #include "shortcutsetting.hpp" #include "modifiersetting.hpp" -CSMPrefs::State *CSMPrefs::State::sThis = 0; +CSMPrefs::State *CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::load() { @@ -599,7 +599,7 @@ CSMPrefs::State::State (const Files::ConfigurationManager& configurationManager) CSMPrefs::State::~State() { - sThis = 0; + sThis = nullptr; } void CSMPrefs::State::save() diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 897c3329c5..016e2da392 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -104,7 +104,7 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa ref.mNew = false; CSMWorld::Record newRecord ( - CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref); + CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &ref); mState.mTarget->getData().getReferences().appendRecord (newRecord); } diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp index bcb3fe2ad2..a6b6de58f8 100644 --- a/apps/opencs/model/tools/mergestages.hpp +++ b/apps/opencs/model/tools/mergestages.hpp @@ -82,7 +82,7 @@ namespace CSMTools const CSMWorld::Record& record = source.getRecord (stage); if (!record.isDeleted()) - target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get())); + target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get())); } class MergeRefIdsStage : public CSMDoc::Stage diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index 952127edf2..46a74362b0 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -50,7 +50,7 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) } CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document) -: mDocument (document), mContext (document.getData()), mMessages (0), mWarningMode (Mode_Ignore) +: mDocument (document), mContext (document.getData()), mMessages (nullptr), mWarningMode (Mode_Ignore) { /// \todo add an option to configure warning mode setWarningsMode (0); @@ -73,7 +73,7 @@ int CSMTools::ScriptCheckStage::setup() mWarningMode = Mode_Strict; mContext.clear(); - mMessages = 0; + mMessages = nullptr; mId.clear(); Compiler::ErrorHandler::reset(); @@ -130,5 +130,5 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) messages.add (id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); } - mMessages = 0; + mMessages = nullptr; } diff --git a/apps/opencs/model/tools/searchstage.cpp b/apps/opencs/model/tools/searchstage.cpp index 3db10b0c37..7cd3f49243 100644 --- a/apps/opencs/model/tools/searchstage.cpp +++ b/apps/opencs/model/tools/searchstage.cpp @@ -5,7 +5,7 @@ #include "searchoperation.hpp" CSMTools::SearchStage::SearchStage (const CSMWorld::IdTableBase *model) -: mModel (model), mOperation (0) +: mModel (model), mOperation (nullptr) {} int CSMTools::SearchStage::setup() diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 07a721e8e2..a3d7db497f 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -43,7 +43,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::get (int type) case CSMDoc::State_Merging: return &mMerge; } - return 0; + return nullptr; } const CSMDoc::OperationHolder *CSMTools::Tools::get (int type) const @@ -138,8 +138,8 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() } CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding) -: mDocument (document), mData (document.getData()), mVerifierOperation (0), - mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0), mEncoding (encoding) +: mDocument (document), mData (document.getData()), mVerifierOperation (nullptr), + mSearchOperation (nullptr), mMergeOperation (nullptr), mNextReportNumber (0), mEncoding (encoding) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index c69ca1d842..17518937c2 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -2254,7 +2254,7 @@ namespace CSMWorld QVariant get (const Record& record) const override { - const std::string *string = 0; + const std::string *string = nullptr; switch (this->mColumnId) { @@ -2272,7 +2272,7 @@ namespace CSMWorld void set (Record& record, const QVariant& data) override { - std::string *string = 0; + std::string *string = nullptr; ESXRecordT record2 = record.get(); @@ -2312,7 +2312,7 @@ namespace CSMWorld QVariant get (const Record& record) const override { - const std::string *string = 0; + const std::string *string = nullptr; switch (this->mColumnId) { @@ -2330,7 +2330,7 @@ namespace CSMWorld void set (Record& record, const QVariant& data) override { - std::string *string = 0; + std::string *string = nullptr; ESXRecordT record2 = record.get(); diff --git a/apps/opencs/model/world/commanddispatcher.hpp b/apps/opencs/model/world/commanddispatcher.hpp index 1d29e48c19..538fd7f188 100644 --- a/apps/opencs/model/world/commanddispatcher.hpp +++ b/apps/opencs/model/world/commanddispatcher.hpp @@ -34,7 +34,7 @@ namespace CSMWorld public: CommandDispatcher (CSMDoc::Document& document, const CSMWorld::UniversalId& id, - QObject *parent = 0); + QObject *parent = nullptr); ///< \param id ID of the table the commands should operate on primarily. void setEditLock (bool locked); diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index e9682d7c9a..e33be11397 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -290,7 +290,7 @@ void CSMWorld::CreateCommand::undo() } CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id), mOld (0) +: QUndoCommand (parent), mModel (model), mId (id), mOld (nullptr) { setText (("Revert record " + id).c_str()); @@ -326,7 +326,7 @@ void CSMWorld::RevertCommand::undo() CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id), mOld (0), mType(type) +: QUndoCommand (parent), mModel (model), mId (id), mOld (nullptr), mType(type) { setText (("Delete record " + id).c_str()); diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 88af32636b..5776cae363 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -140,7 +140,7 @@ namespace CSMWorld public: ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, - QUndoCommand *parent = 0); + QUndoCommand *parent = nullptr); void redo() override; @@ -167,7 +167,7 @@ namespace CSMWorld public: - CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0); + CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); void setType (UniversalId::Type type); @@ -189,7 +189,7 @@ namespace CSMWorld CloneCommand (IdTable& model, const std::string& idOrigin, const std::string& IdDestination, const UniversalId::Type type, - QUndoCommand* parent = 0); + QUndoCommand* parent = nullptr); void redo() override; @@ -208,7 +208,7 @@ namespace CSMWorld public: - RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0); + RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); virtual ~RevertCommand(); @@ -231,7 +231,7 @@ namespace CSMWorld public: DeleteCommand (IdTable& model, const std::string& id, - UniversalId::Type type = UniversalId::Type_None, QUndoCommand *parent = 0); + UniversalId::Type type = UniversalId::Type_None, QUndoCommand *parent = nullptr); virtual ~DeleteCommand(); @@ -259,7 +259,7 @@ namespace CSMWorld { public: - CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent = 0); + CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent = nullptr); void redo() override; }; @@ -279,7 +279,7 @@ namespace CSMWorld public: - UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent = 0); + UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent = nullptr); void redo() override; @@ -316,7 +316,7 @@ namespace CSMWorld public: - DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); + DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = nullptr); void redo() override; @@ -338,7 +338,7 @@ namespace CSMWorld public: - AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); + AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = nullptr); void redo() override; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 23720a99a6..70c496e3f2 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -68,7 +68,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::PathContainer& dataPaths, const std::vector& archives, const boost::filesystem::path& resDir) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mReader (0), mDialogue (0), mReaderIndex(1), + mReader (nullptr), mDialogue (nullptr), mReaderIndex(1), mFsStrict(fsStrict), mDataPaths(dataPaths), mArchives(archives) { mVFS.reset(new VFS::Manager(mFsStrict)); @@ -916,7 +916,7 @@ const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const void CSMWorld::Data::setMetaData (const MetaData& metaData) { - Record record (RecordBase::State_ModifiedOnly, 0, &metaData); + Record record (RecordBase::State_ModifiedOnly, nullptr, &metaData); mMetaData.setRecord (0, record); } @@ -932,7 +932,7 @@ QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& // construction of the ESX data where no update signals are available. if (id.getType()==UniversalId::Type_RegionMap) { - RegionMap *table = 0; + RegionMap *table = nullptr; addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap, false); return table; } @@ -962,9 +962,9 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading std::shared_ptr ptr(mReader); mReaders.push_back(ptr); - mReader = 0; + mReader = nullptr; - mDialogue = 0; + mDialogue = nullptr; mReader = new ESM::ESMReader; mReader->setEncoder (&mEncoder); @@ -982,7 +982,7 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base metaData.mId = "sys::meta"; metaData.load (*mReader); - mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); + mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, nullptr, &metaData)); } // Fix uninitialized master data index @@ -1064,9 +1064,9 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) else delete mReader; - mReader = 0; + mReader = nullptr; - mDialogue = 0; + mDialogue = nullptr; loadFallbackEntries(); @@ -1151,7 +1151,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) if (isDeleted) { // record vector can be shuffled around which would make pointer to record invalid - mDialogue = 0; + mDialogue = nullptr; if (mJournals.tryDelete (record.mId)) { diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 14d7057924..7e0563834a 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -35,7 +35,7 @@ namespace CSMWorld public: - IdTableProxyModel (QObject *parent = 0); + IdTableProxyModel (QObject *parent = nullptr); virtual QModelIndex getModelIndex (const std::string& id, int column) const; diff --git a/apps/opencs/model/world/infotableproxymodel.hpp b/apps/opencs/model/world/infotableproxymodel.hpp index 6a8e66b4f8..92afdabdc5 100644 --- a/apps/opencs/model/world/infotableproxymodel.hpp +++ b/apps/opencs/model/world/infotableproxymodel.hpp @@ -28,7 +28,7 @@ namespace CSMWorld ///< \a currentRow is a row of the source model. public: - InfoTableProxyModel(UniversalId::Type type, QObject *parent = 0); + InfoTableProxyModel(UniversalId::Type type, QObject *parent = nullptr); void setSourceModel(QAbstractItemModel *sourceModel) override; diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index 82f2abe770..5f67a93b12 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -87,7 +87,7 @@ namespace CSMWorld template RecordBase *Record::modifiedCopy() const { - return new Record (State_ModifiedOnly, 0, &(this->get())); + return new Record (State_ModifiedOnly, nullptr, &(this->get())); } template diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 535a31dddb..cd2dd89dff 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -350,7 +350,7 @@ CSMWorld::RefIdCollection::RefIdCollection() }; // for re-use in NPC records - const RefIdColumn *essential = 0; + const RefIdColumn *essential = nullptr; for (int i=0; sCreatureFlagTable[i].mName!=-1; ++i) { diff --git a/apps/opencs/model/world/resources.hpp b/apps/opencs/model/world/resources.hpp index 5e9872ea84..c217b793d3 100644 --- a/apps/opencs/model/world/resources.hpp +++ b/apps/opencs/model/world/resources.hpp @@ -25,9 +25,9 @@ namespace CSMWorld /// \param type Type of resources in this table. Resources (const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, - const char * const *extensions = 0); + const char * const *extensions = nullptr); - void recreate(const VFS::Manager* vfs, const char * const *extensions = 0); + void recreate(const VFS::Manager* vfs, const char * const *extensions = nullptr); int getSize() const; diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp index 91e308236f..cec9ca2291 100644 --- a/apps/opencs/view/doc/adjusterwidget.hpp +++ b/apps/opencs/view/doc/adjusterwidget.hpp @@ -32,7 +32,7 @@ namespace CSVDoc public: - AdjusterWidget (QWidget *parent = 0); + AdjusterWidget (QWidget *parent = nullptr); void setLocalData (const boost::filesystem::path& localData); void setAction (ContentAction action); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 7a3fe398f4..69490edca2 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -18,7 +18,7 @@ #include "adjusterwidget.hpp" CSVDoc::FileDialog::FileDialog(QWidget *parent) : - QDialog(parent), mSelector (0), mAction(ContentAction_Undefined), mFileWidget (0), mAdjusterWidget (0), mDialogBuilt(false) + QDialog(parent), mSelector (nullptr), mAction(ContentAction_Undefined), mFileWidget (nullptr), mAdjusterWidget (nullptr), mDialogBuilt(false) { ui.setupUi (this); resize(400, 400); diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index bec2c68695..6c48fa78b9 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -42,7 +42,7 @@ namespace CSVDoc public: - explicit FileDialog(QWidget *parent = 0); + explicit FileDialog(QWidget *parent = nullptr); void showDialog (ContentAction action); void addFiles (const QString &path); diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp index ab06f37f18..626b8d77d8 100644 --- a/apps/opencs/view/doc/filewidget.hpp +++ b/apps/opencs/view/doc/filewidget.hpp @@ -23,7 +23,7 @@ namespace CSVDoc public: - FileWidget (QWidget *parent = 0); + FileWidget (QWidget *parent = nullptr); void setType (bool addon); diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.cpp b/apps/opencs/view/doc/globaldebugprofilemenu.cpp index f0d9655dd0..c898b819c2 100644 --- a/apps/opencs/view/doc/globaldebugprofilemenu.cpp +++ b/apps/opencs/view/doc/globaldebugprofilemenu.cpp @@ -13,7 +13,7 @@ void CSVDoc::GlobalDebugProfileMenu::rebuild() clear(); delete mActions; - mActions = 0; + mActions = nullptr; int idColumn = mDebugProfiles->findColumnIndex (CSMWorld::Columns::ColumnId_Id); int stateColumn = mDebugProfiles->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); @@ -48,7 +48,7 @@ void CSVDoc::GlobalDebugProfileMenu::rebuild() CSVDoc::GlobalDebugProfileMenu::GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, QWidget *parent) -: QMenu (parent), mDebugProfiles (debugProfiles), mActions (0) +: QMenu (parent), mDebugProfiles (debugProfiles), mActions (nullptr) { rebuild(); diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.hpp b/apps/opencs/view/doc/globaldebugprofilemenu.hpp index 0d7906ccef..e12ee306a1 100644 --- a/apps/opencs/view/doc/globaldebugprofilemenu.hpp +++ b/apps/opencs/view/doc/globaldebugprofilemenu.hpp @@ -26,7 +26,7 @@ namespace CSVDoc public: - GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, QWidget *parent = 0); + GlobalDebugProfileMenu (CSMWorld::IdTable *debugProfiles, QWidget *parent = nullptr); void updateActions (bool running); diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 49a53e1794..1cdfc01733 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -17,7 +17,7 @@ void CSVDoc::LoadingDocument::closeEvent (QCloseEvent *event) } CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) -: mDocument (document), mAborted (false), mMessages (0), mTotalRecords (0) +: mDocument (document), mAborted (false), mMessages (nullptr), mTotalRecords (0) { setWindowTitle (QString::fromUtf8((std::string("Opening ") + document->getSavePath().filename().string()).c_str())); diff --git a/apps/opencs/view/doc/sizehint.hpp b/apps/opencs/view/doc/sizehint.hpp index 1b3c52eb8a..14ec7b1865 100644 --- a/apps/opencs/view/doc/sizehint.hpp +++ b/apps/opencs/view/doc/sizehint.hpp @@ -11,7 +11,7 @@ namespace CSVDoc QSize mSize; public: - SizeHintWidget(QWidget *parent = 0); + SizeHintWidget(QWidget *parent = nullptr); ~SizeHintWidget(); QSize sizeHint() const override; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index ac7c8ebf97..be3fe51423 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -733,7 +733,7 @@ void CSVDoc::View::infoAbout() #endif // Get current year - time_t now = time(NULL); + time_t now = time(nullptr); struct tm tstruct; char copyrightInfo[40]; tstruct = *localtime(&now); diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index 70431107f6..8424be6f89 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -43,7 +43,7 @@ namespace CSVDoc ViewManager& operator= (const ViewManager&); void updateIndices(); - bool notifySaveOnClose (View *view = 0); + bool notifySaveOnClose (View *view = nullptr); bool showModifiedDocumentMessageBox (View *view); bool showSaveInProgressMessageBox (View *view); bool removeDocument(View *view); diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index b47a884a38..f933ec92eb 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -30,7 +30,7 @@ namespace CSVFilter public: - EditWidget (CSMWorld::Data& data, QWidget *parent = 0); + EditWidget (CSMWorld::Data& data, QWidget *parent = nullptr); void createFilterRequest(std::vector > >& filterSource, Qt::DropAction action); diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index 94aa80b844..94b5fced30 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -25,7 +25,7 @@ namespace CSVFilter RecordFilterBox *mRecordFilterBox; public: - FilterBox (CSMWorld::Data& data, QWidget *parent = 0); + FilterBox (CSMWorld::Data& data, QWidget *parent = nullptr); void setRecordFilter (const std::string& filter); diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index 77a07c92bc..3bcd7c57b4 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -25,7 +25,7 @@ namespace CSVFilter public: - RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); + RecordFilterBox (CSMWorld::Data& data, QWidget *parent = nullptr); void setFilter (const std::string& filter); diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index eed5c0eb8e..39c9f78ec1 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -16,9 +16,9 @@ namespace CSVPrefs { KeyBindingPage::KeyBindingPage(CSMPrefs::Category& category, QWidget* parent) : PageBase(category, parent) - , mStackedLayout(0) - , mPageLayout(0) - , mPageSelector(0) + , mStackedLayout(nullptr) + , mPageLayout(nullptr) + , mPageSelector(nullptr) { // Need one widget for scroll area QWidget* topWidget = new QWidget(); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 23b5aa91e4..75d83cf639 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -504,12 +504,12 @@ void CSVRender::Cell::setCellArrows (int mask) bool enable = mask & direction; - if (enable!=(mCellArrows[i].get()!=0)) + if (enable!=(mCellArrows[i].get()!=nullptr)) { if (enable) mCellArrows[i].reset (new CellArrow (mCellNode, direction, mCoordinates)); else - mCellArrows[i].reset (0); + mCellArrows[i].reset (nullptr); } } } diff --git a/apps/opencs/view/render/cellwater.cpp b/apps/opencs/view/render/cellwater.cpp index 4351788609..f8857c3afc 100644 --- a/apps/opencs/view/render/cellwater.cpp +++ b/apps/opencs/view/render/cellwater.cpp @@ -27,9 +27,9 @@ namespace CSVRender : mData(data) , mId(id) , mParentNode(cellNode) - , mWaterTransform(0) - , mWaterNode(0) - , mWaterGeometry(0) + , mWaterTransform(nullptr) + , mWaterNode(nullptr) + , mWaterGeometry(nullptr) , mDeleted(false) , mExterior(false) , mHasWater(false) @@ -137,7 +137,7 @@ namespace CSVRender if (mWaterGeometry) { mWaterNode->removeDrawable(mWaterGeometry); - mWaterGeometry = 0; + mWaterGeometry = nullptr; } if (mDeleted || !mHasWater) diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp index c0482c81a6..52c35811d2 100644 --- a/apps/opencs/view/render/editmode.hpp +++ b/apps/opencs/view/render/editmode.hpp @@ -30,7 +30,7 @@ namespace CSVRender public: EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, unsigned int mask, - const QString& tooltip = "", QWidget *parent = 0); + const QString& tooltip = "", QWidget *parent = nullptr); unsigned int getInteractionMask() const; diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 987dea437b..19018fae6c 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -98,7 +98,7 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", - parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None), + parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None), mDragAxis (-1), mLocked (false), mUnitScaleDist(1), mParentNode (parentNode) { connect(this, SIGNAL(requestFocus(const std::string&)), @@ -169,14 +169,14 @@ void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) { toolbar->removeTool (mSelectionMode); delete mSelectionMode; - mSelectionMode = 0; + mSelectionMode = nullptr; } if (mSubMode) { toolbar->removeTool (mSubMode); delete mSubMode; - mSubMode = 0; + mSubMode = nullptr; } EditMode::deactivate (toolbar); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 29955feef6..32dd4ac67d 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -62,7 +62,7 @@ namespace CSVRender public: - InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent = 0); + InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent = nullptr); void activate (CSVWidget::SceneToolbar *toolbar) override; diff --git a/apps/opencs/view/render/instancemovemode.hpp b/apps/opencs/view/render/instancemovemode.hpp index bd0e28dac8..62e6b6a1f8 100644 --- a/apps/opencs/view/render/instancemovemode.hpp +++ b/apps/opencs/view/render/instancemovemode.hpp @@ -11,7 +11,7 @@ namespace CSVRender public: - InstanceMoveMode (QWidget *parent = 0); + InstanceMoveMode (QWidget *parent = nullptr); }; } diff --git a/apps/opencs/view/render/lighting.hpp b/apps/opencs/view/render/lighting.hpp index 66b0eec000..d9d90767f0 100644 --- a/apps/opencs/view/render/lighting.hpp +++ b/apps/opencs/view/render/lighting.hpp @@ -16,7 +16,7 @@ namespace CSVRender { public: - Lighting() : mRootNode(0) {} + Lighting() : mRootNode(nullptr) {} virtual ~Lighting(); virtual void activate (osg::Group* rootNode, bool isExterior) = 0; diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index f9d2c8872e..2bb537d74a 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -413,7 +413,7 @@ osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) -: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), +: mData (data), mBaseNode(nullptr), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f) { mRootNode = new osg::PositionAttitudeTransform; diff --git a/apps/opencs/view/render/orbitcameramode.cpp b/apps/opencs/view/render/orbitcameramode.cpp index ba25beaba9..79ac0167ab 100644 --- a/apps/opencs/view/render/orbitcameramode.cpp +++ b/apps/opencs/view/render/orbitcameramode.cpp @@ -13,7 +13,7 @@ namespace CSVRender QWidget* parent) : ModeButton(icon, tooltip, parent) , mWorldspaceWidget(worldspaceWidget) - , mCenterOnSelection(0) + , mCenterOnSelection(nullptr) { mCenterShortcut = new CSMPrefs::Shortcut("orbit-center-selection", worldspaceWidget); mCenterShortcut->enable(false); @@ -35,7 +35,7 @@ namespace CSVRender void OrbitCameraMode::deactivate(CSVWidget::SceneToolbar* toolbar) { - mCenterShortcut->associateAction(0); + mCenterShortcut->associateAction(nullptr); mCenterShortcut->enable(false); } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index b5d9234e42..dca5549af4 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -787,7 +787,7 @@ CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& poi if (searchResult != mCells.end()) return searchResult->second; else - return 0; + return nullptr; } CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const diff --git a/apps/opencs/view/render/pathgrid.cpp b/apps/opencs/view/render/pathgrid.cpp index 7f0454d8fe..470a3d0928 100644 --- a/apps/opencs/view/render/pathgrid.cpp +++ b/apps/opencs/view/render/pathgrid.cpp @@ -60,8 +60,8 @@ namespace CSVRender , mRemoveGeometry(false) , mUseOffset(true) , mParent(parent) - , mPathgridGeometry(0) - , mDragGeometry(0) + , mPathgridGeometry(nullptr) + , mDragGeometry(nullptr) , mTag(new PathgridTag(this)) { const float CoordScalar = ESM::Land::REAL_SIZE; @@ -219,7 +219,7 @@ namespace CSVRender mMoveOffset.set(0, 0, 0); mPathgridGeode->removeDrawable(mDragGeometry); - mDragGeometry = 0; + mDragGeometry = nullptr; } void Pathgrid::applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos) @@ -557,7 +557,7 @@ namespace CSVRender if (mPathgridGeometry) { mPathgridGeode->removeDrawable(mPathgridGeometry); - mPathgridGeometry = 0; + mPathgridGeometry = nullptr; } } @@ -566,7 +566,7 @@ namespace CSVRender if (mSelectedGeometry) { mPathgridGeode->removeDrawable(mSelectedGeometry); - mSelectedGeometry = 0; + mSelectedGeometry = nullptr; } } @@ -612,7 +612,7 @@ namespace CSVRender return &mPathgridCollection.getRecord(index).get(); } - return 0; + return nullptr; } int Pathgrid::edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2) diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 8863ad235c..7a3fc8ecf7 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -27,7 +27,7 @@ namespace CSVRender getTooltip(), parent) , mDragMode(DragMode_None) , mFromNode(0) - , mSelectionMode(0) + , mSelectionMode(nullptr) { } @@ -59,7 +59,7 @@ namespace CSVRender { toolbar->removeTool (mSelectionMode); delete mSelectionMode; - mSelectionMode = 0; + mSelectionMode = nullptr; } } @@ -214,7 +214,7 @@ namespace CSVRender Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); if (cell && cell->getPathgrid()) { - PathgridTag* tag = 0; + PathgridTag* tag = nullptr; if (hit.tag && (tag = dynamic_cast(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) { unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); diff --git a/apps/opencs/view/render/pathgridmode.hpp b/apps/opencs/view/render/pathgridmode.hpp index 6d8f96e8c3..cc61dfe9b0 100644 --- a/apps/opencs/view/render/pathgridmode.hpp +++ b/apps/opencs/view/render/pathgridmode.hpp @@ -15,7 +15,7 @@ namespace CSVRender public: - PathgridMode(WorldspaceWidget* worldspace, QWidget* parent=0); + PathgridMode(WorldspaceWidget* worldspace, QWidget* parent=nullptr); void activate(CSVWidget::SceneToolbar* toolbar) override; diff --git a/apps/opencs/view/render/previewwidget.hpp b/apps/opencs/view/render/previewwidget.hpp index 630ccf293d..a8d73729a4 100644 --- a/apps/opencs/view/render/previewwidget.hpp +++ b/apps/opencs/view/render/previewwidget.hpp @@ -29,7 +29,7 @@ namespace CSVRender public: PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, - QWidget *parent = 0); + QWidget *parent = nullptr); signals: diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index f3186e76a9..3abc01d2e6 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -36,7 +36,7 @@ namespace CSVRender RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) - , mRootNode(0) + , mRootNode(nullptr) { osgViewer::CompositeViewer& viewer = CompositeViewer::get(); @@ -257,7 +257,7 @@ void SceneWidget::setLighting(Lighting *lighting) mLighting = lighting; mLighting->activate (mRootNode, mIsExterior); - osg::Vec4f ambient = mLighting->getAmbientColour(mHasDefaultAmbient ? &mDefaultAmbient : 0); + osg::Vec4f ambient = mLighting->getAmbientColour(mHasDefaultAmbient ? &mDefaultAmbient : nullptr); setAmbient(ambient); flagAsModified(); diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 5664378ca9..e495d23555 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -99,7 +99,7 @@ void CSVRender::TerrainShapeMode::primaryOpenPressed (const WorldspaceHitResult& void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& hit) { - if (hit.hit && hit.tag == 0) + if (hit.hit && hit.tag == nullptr) { if (mShapeEditTool == ShapeEditTool_Flatten) setFlattenToolTargetHeight(hit); @@ -124,7 +124,7 @@ void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit) { - if(hit.hit && hit.tag == 0) + if(hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, false); } @@ -132,7 +132,7 @@ void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit) { - if(hit.hit && hit.tag == 0) + if(hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, false); } @@ -144,7 +144,7 @@ bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) mDragMode = InteractionType_PrimaryEdit; - if (hit.hit && hit.tag == 0) + if (hit.hit && hit.tag == nullptr) { mEditingPos = hit.worldPos; mIsEditing = true; @@ -164,7 +164,7 @@ bool CSVRender::TerrainShapeMode::primarySelectStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimarySelect; - if (!hit.hit || hit.tag != 0) + if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; @@ -177,7 +177,7 @@ bool CSVRender::TerrainShapeMode::secondarySelectStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_SecondarySelect; - if (!hit.hit || hit.tag != 0) + if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; @@ -202,13 +202,13 @@ void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, if (mDragMode == InteractionType_PrimarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == 0) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == 0) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); } } diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index ae57118813..c8d63f32e8 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -119,7 +119,7 @@ void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == 0) + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { undoStack.beginMacro ("Edit texture records"); if(allowLandTextureEditing(mCellId)) @@ -133,7 +133,7 @@ void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResult& hit) { - if(hit.hit && hit.tag == 0) + if(hit.hit && hit.tag == nullptr) { selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, false); } @@ -141,7 +141,7 @@ void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResu void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit) { - if(hit.hit && hit.tag == 0) + if(hit.hit && hit.tag == nullptr) { selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, false); } @@ -166,7 +166,7 @@ bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos) CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == 0) + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { undoStack.beginMacro ("Edit texture records"); mIsEditing = true; @@ -189,7 +189,7 @@ bool CSVRender::TerrainTextureMode::primarySelectStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimarySelect; - if (!hit.hit || hit.tag != 0) + if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; @@ -202,7 +202,7 @@ bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_SecondarySelect; - if (!hit.hit || hit.tag != 0) + if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; @@ -222,7 +222,7 @@ void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diff CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == 0) + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == nullptr) { editTerrainTextureGrid(hit); } @@ -231,13 +231,13 @@ void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diff if (mDragMode == InteractionType_PrimarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == 0) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, true); + if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, true); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == 0) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); + if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); } } diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 4535b5e8af..82a1459f2d 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -34,11 +34,11 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (document.getData().getResourceSystem(), parent, Qt::WindowFlags(), false) - , mSceneElements(0) - , mRun(0) + , mSceneElements(nullptr) + , mRun(nullptr) , mDocument(document) , mInteractionMask (0) - , mEditMode (0) + , mEditMode (nullptr) , mLocked (false) , mDragMode(InteractionType_None) , mDragging (false) @@ -435,7 +435,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo } // Something untagged, probably terrain - WorldspaceHitResult hit = { true, 0, 0, 0, 0, intersection.getWorldIntersectPoint() }; + WorldspaceHitResult hit = { true, nullptr, 0, 0, 0, intersection.getWorldIntersectPoint() }; if (intersection.indexList.size() >= 3) { hit.index0 = intersection.indexList[0]; @@ -449,7 +449,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo direction.normalize(); direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt(); - WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction }; + WorldspaceHitResult hit = { false, nullptr, 0, 0, 0, start + direction }; return hit; } diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 5ed3d01b35..3b8cf70c2a 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -96,7 +96,7 @@ namespace CSVRender InteractionType_None }; - WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = 0); + WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = nullptr); ~WorldspaceWidget (); CSVWidget::SceneToolMode *makeNavigationSelector (CSVWidget::SceneToolbar *parent); diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp index c49044ccd4..f50a85f2fc 100644 --- a/apps/opencs/view/tools/merge.cpp +++ b/apps/opencs/view/tools/merge.cpp @@ -27,7 +27,7 @@ void CSVTools::Merge::keyPressEvent (QKeyEvent *event) } CSVTools::Merge::Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent) -: QWidget (parent), mDocument (0), mDocumentManager (documentManager) +: QWidget (parent), mDocument (nullptr), mDocumentManager (documentManager) { setWindowTitle ("Merge Content Files into a new Game File"); @@ -117,7 +117,7 @@ CSMDoc::Document *CSVTools::Merge::getDocument() const void CSVTools::Merge::cancel() { - mDocument = 0; + mDocument = nullptr; hide(); } diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp index c82feba14d..d394a431ed 100644 --- a/apps/opencs/view/tools/merge.hpp +++ b/apps/opencs/view/tools/merge.hpp @@ -39,7 +39,7 @@ namespace CSVTools public: - Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = 0); + Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = nullptr); /// Configure dialogue for a new merge void configure (CSMDoc::Document *document); diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index 7b28f2b0fd..c1297d475b 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -25,7 +25,7 @@ namespace CSVTools { public: - RichTextDelegate (QObject *parent = 0); + RichTextDelegate (QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; @@ -142,7 +142,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, bool richTextDescription, int refreshState, QWidget *parent) : CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)), - mRefreshAction (0), mRefreshState (refreshState) + mRefreshAction (nullptr), mRefreshState (refreshState) { horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); horizontalHeader()->setStretchLastSection (true); @@ -159,7 +159,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, setModel (mProxyModel); setColumnHidden (2, true); - mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate (0, + mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate (nullptr, mDocument, this); setItemDelegateForColumn (0, mIdTypeDelegate); diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index 4c169a9862..f39dd6f857 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -62,7 +62,7 @@ namespace CSVTools /// 0 no refresh function exists. If the document current has the specified state /// the refresh function is disabled. ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, - bool richTextDescription, int refreshState = 0, QWidget *parent = 0); + bool richTextDescription, int refreshState = 0, QWidget *parent = nullptr); std::vector getDraggedRecords() const override; diff --git a/apps/opencs/view/tools/searchbox.hpp b/apps/opencs/view/tools/searchbox.hpp index eff5296b4a..cbeb150d8b 100644 --- a/apps/opencs/view/tools/searchbox.hpp +++ b/apps/opencs/view/tools/searchbox.hpp @@ -41,7 +41,7 @@ namespace CSVTools public: - SearchBox (QWidget *parent = 0); + SearchBox (QWidget *parent = nullptr); void setSearchMode (bool enabled); diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 9bada22af3..07ba7907e7 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -30,7 +30,7 @@ void CSVTools::SearchSubView::replace (bool selection) bool autoDelete = CSMPrefs::get()["Search & Replace"]["auto-delete"].isTrue(); CSMTools::Search search (mSearch); - CSMWorld::IdTableBase *currentTable = 0; + CSMWorld::IdTableBase *currentTable = nullptr; // We are running through the indices in reverse order to avoid messing up multiple results // in a single string. diff --git a/apps/opencs/view/widget/coloreditor.hpp b/apps/opencs/view/widget/coloreditor.hpp index d4a802ca2f..aa746da682 100644 --- a/apps/opencs/view/widget/coloreditor.hpp +++ b/apps/opencs/view/widget/coloreditor.hpp @@ -22,8 +22,8 @@ namespace CSVWidget QPoint calculatePopupPosition(); public: - ColorEditor(const QColor &color, QWidget *parent = 0, const bool popupOnStart = false); - ColorEditor(const int colorInt, QWidget *parent = 0, const bool popupOnStart = false); + ColorEditor(const QColor &color, QWidget *parent = nullptr, const bool popupOnStart = false); + ColorEditor(const int colorInt, QWidget *parent = nullptr, const bool popupOnStart = false); QColor color() const; @@ -41,7 +41,7 @@ namespace CSVWidget void showEvent(QShowEvent *event) override; private: - ColorEditor(QWidget *parent = 0, const bool popupOnStart = false); + ColorEditor(QWidget *parent = nullptr, const bool popupOnStart = false); private slots: void showPicker(); diff --git a/apps/opencs/view/widget/completerpopup.hpp b/apps/opencs/view/widget/completerpopup.hpp index 62fdf5388f..96675f56f4 100644 --- a/apps/opencs/view/widget/completerpopup.hpp +++ b/apps/opencs/view/widget/completerpopup.hpp @@ -8,7 +8,7 @@ namespace CSVWidget class CompleterPopup : public QListView { public: - CompleterPopup(QWidget *parent = 0); + CompleterPopup(QWidget *parent = nullptr); int sizeHintForRow(int row) const override; }; diff --git a/apps/opencs/view/widget/droplineedit.hpp b/apps/opencs/view/widget/droplineedit.hpp index ed991af0dd..9110518736 100644 --- a/apps/opencs/view/widget/droplineedit.hpp +++ b/apps/opencs/view/widget/droplineedit.hpp @@ -26,7 +26,7 @@ namespace CSVWidget ///< The accepted Display type for this LineEdit. public: - DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent = 0); + DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent = nullptr); protected: void dragEnterEvent(QDragEnterEvent *event) override; diff --git a/apps/opencs/view/widget/modebutton.hpp b/apps/opencs/view/widget/modebutton.hpp index 1615ff298a..f595969231 100644 --- a/apps/opencs/view/widget/modebutton.hpp +++ b/apps/opencs/view/widget/modebutton.hpp @@ -17,7 +17,7 @@ namespace CSVWidget public: ModeButton (const QIcon& icon, const QString& tooltip = "", - QWidget *parent = 0); + QWidget *parent = nullptr); /// Default-Implementation: do nothing virtual void activate (SceneToolbar *toolbar); diff --git a/apps/opencs/view/widget/pushbutton.hpp b/apps/opencs/view/widget/pushbutton.hpp index 5522cd74f6..b3aaaebef2 100644 --- a/apps/opencs/view/widget/pushbutton.hpp +++ b/apps/opencs/view/widget/pushbutton.hpp @@ -48,11 +48,11 @@ namespace CSVWidget /// \param push Do not maintain a toggle state PushButton (const QIcon& icon, Type type, const QString& tooltip = "", - QWidget *parent = 0); + QWidget *parent = nullptr); /// \param push Do not maintain a toggle state PushButton (Type type, const QString& tooltip = "", - QWidget *parent = 0); + QWidget *parent = nullptr); bool hasKeepOpen() const; diff --git a/apps/opencs/view/widget/scenetoolbar.hpp b/apps/opencs/view/widget/scenetoolbar.hpp index d9998eefc0..70f5807652 100644 --- a/apps/opencs/view/widget/scenetoolbar.hpp +++ b/apps/opencs/view/widget/scenetoolbar.hpp @@ -23,11 +23,11 @@ namespace CSVWidget public: - SceneToolbar (int buttonSize, QWidget *parent = 0); + SceneToolbar (int buttonSize, QWidget *parent = nullptr); /// If insertPoint==0, insert \a tool at the end of the scrollbar. Otherwise /// insert tool after \a insertPoint. - void addTool (SceneTool *tool, SceneTool *insertPoint = 0); + void addTool (SceneTool *tool, SceneTool *insertPoint = nullptr); void removeTool (SceneTool *tool); diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 7b2ff64db4..3aec44f1ba 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -33,7 +33,7 @@ void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) toolTip += "

(left click to change mode)"; - if (createContextMenu (0)) + if (createContextMenu (nullptr)) toolTip += "
(right click to access context menu)"; setToolTip (toolTip); @@ -62,7 +62,7 @@ void CSVWidget::SceneToolMode::setButton (std::map::i CSVWidget::SceneToolMode::SceneToolMode (SceneToolbar *parent, const QString& toolTip) : SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), - mToolTip (toolTip), mFirst (0), mCurrent (0), mToolbar (parent) + mToolTip (toolTip), mFirst (nullptr), mCurrent (nullptr), mToolbar (parent) { mPanel = new QFrame (this, Qt::Popup); diff --git a/apps/opencs/view/widget/scenetoolshapebrush.hpp b/apps/opencs/view/widget/scenetoolshapebrush.hpp index 76c0dfa04e..3afd7f8b3b 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.hpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -54,7 +54,7 @@ namespace CSVWidget public: - ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = 0); + ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = nullptr); void configureButtonInitialSettings(QPushButton *button); const QString toolTipPoint = "Paint single point"; diff --git a/apps/opencs/view/widget/scenetooltexturebrush.hpp b/apps/opencs/view/widget/scenetooltexturebrush.hpp index c6f0b5e527..5f42800cb3 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.hpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.hpp @@ -57,7 +57,7 @@ namespace CSVWidget Q_OBJECT public: - TextureBrushWindow(CSMDoc::Document& document, QWidget *parent = 0); + TextureBrushWindow(CSMDoc::Document& document, QWidget *parent = nullptr); void configureButtonInitialSettings(QPushButton *button); const QString toolTipPoint = "Paint single point"; diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index 5919a280af..fa0be31555 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -115,7 +115,7 @@ QRect CSVWidget::SceneToolToggle::getIconBox (int index) const CSVWidget::SceneToolToggle::SceneToolToggle (SceneToolbar *parent, const QString& toolTip, const std::string& emptyIcon) : SceneTool (parent), mEmptyIcon (emptyIcon), mButtonSize (parent->getButtonSize()), - mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (0) + mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (nullptr) { mPanel = new QFrame (this, Qt::Popup); diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 720da6a964..cf9bfe349b 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -57,7 +57,7 @@ CSVWidget::SceneToolToggle2::SceneToolToggle2 (SceneToolbar *parent, const QStri const std::string& compositeIcon, const std::string& singleIcon) : SceneTool (parent), mCompositeIcon (compositeIcon), mSingleIcon (singleIcon), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), mToolTip (toolTip), - mFirst (0) + mFirst (nullptr) { mPanel = new QFrame (this, Qt::Popup); diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp index 7a93339c5b..53664c186a 100644 --- a/apps/opencs/view/world/creator.cpp +++ b/apps/opencs/view/world/creator.cpp @@ -17,5 +17,5 @@ CSVWorld::CreatorFactoryBase::~CreatorFactoryBase() {} CSVWorld::Creator *CSVWorld::NullCreatorFactory::makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - return 0; + return nullptr; } diff --git a/apps/opencs/view/world/dialoguespinbox.hpp b/apps/opencs/view/world/dialoguespinbox.hpp index b7c4889a50..90fe8d20cd 100644 --- a/apps/opencs/view/world/dialoguespinbox.hpp +++ b/apps/opencs/view/world/dialoguespinbox.hpp @@ -12,7 +12,7 @@ namespace CSVWorld public: - DialogueSpinBox (QWidget *parent = 0); + DialogueSpinBox (QWidget *parent = nullptr); protected: @@ -27,7 +27,7 @@ namespace CSVWorld public: - DialogueDoubleSpinBox (QWidget *parent = 0); + DialogueDoubleSpinBox (QWidget *parent = nullptr); protected: diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index e29fcb779a..3d3b3cdbe1 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -498,7 +498,7 @@ void CSVWorld::EditWidget::remake(int row) if (mDispatcher) delete mDispatcher; - mDispatcher = new DialogueDelegateDispatcher(0/*this*/, mTable, mCommandDispatcher, mDocument); + mDispatcher = new DialogueDelegateDispatcher(nullptr/*this*/, mTable, mCommandDispatcher, mDocument); if (mNestedTableDispatcher) delete mNestedTableDispatcher; @@ -648,7 +648,7 @@ void CSVWorld::EditWidget::remake(int row) mNestedTableMapper->setModel(tree); // FIXME: lack MIME support? mNestedTableDispatcher = - new DialogueDelegateDispatcher (0/*this*/, mTable, mCommandDispatcher, mDocument, tree); + new DialogueDelegateDispatcher (nullptr/*this*/, mTable, mCommandDispatcher, mDocument, tree); mNestedTableMapper->setRootIndex (tree->index(row, i)); mNestedTableMapper->setItemDelegate(mNestedTableDispatcher); @@ -732,7 +732,7 @@ bool CSVWorld::SimpleDialogueSubView::isLocked() const CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), - mEditWidget(0), + mEditWidget(nullptr), mMainLayout(nullptr), mTable(dynamic_cast(document.getData().getTableModel(id))), mLocked(false), @@ -834,7 +834,7 @@ void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &pa if(mEditWidget) { delete mEditWidget; - mEditWidget = 0; + mEditWidget = nullptr; } emit closeRequest(this); } @@ -869,7 +869,7 @@ void CSVWorld::DialogueSubView::addButtonBar() CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) -: SimpleDialogueSubView (id, document), mButtons (0) +: SimpleDialogueSubView (id, document), mButtons (nullptr) { // bottom box mBottom = new TableBottomBox (creatorFactory, document, id, this); @@ -905,7 +905,7 @@ void CSVWorld::DialogueSubView::settingChanged (const CSMPrefs::Setting *setting { getMainLayout().removeWidget (mButtons); delete mButtons; - mButtons = 0; + mButtons = nullptr; } } } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index eb14efa8e5..2cf05f711e 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -50,7 +50,7 @@ namespace CSVWorld const CSMWorld::IdTable* mTable; public: NotEditableSubDelegate(const CSMWorld::IdTable* table, - QObject * parent = 0); + QObject * parent = nullptr); void setEditorData (QWidget* editor, const QModelIndex& index) const override; @@ -126,7 +126,7 @@ namespace CSVWorld CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, - QAbstractItemModel* model = 0); + QAbstractItemModel* model = nullptr); ~DialogueDelegateDispatcher(); diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 3140adc485..65ded46c7f 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -71,7 +71,7 @@ QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptio const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { if (!index.data(Qt::EditRole).isValid() && !index.data(Qt::DisplayRole).isValid()) - return 0; + return nullptr; QComboBox *comboBox = new QComboBox (parent); diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp index 42573924a8..85862ac49e 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.hpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -57,7 +57,7 @@ namespace CSVWorld public: ExtendedCommandConfigurator(CSMDoc::Document &document, const CSMWorld::UniversalId &id, - QWidget *parent = 0); + QWidget *parent = nullptr); void configure(Mode mode, const std::vector &selectedIds); void setEditLock(bool locked); diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index 5e2118e9b2..23813f8066 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -148,8 +148,8 @@ void CSVWorld::GenericCreator::addScope (const QString& name, CSMWorld::Scope sc CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, bool relaxedIdRules) : mData (data), mUndoStack (undoStack), mListId (id), mLocked (false), - mClonedType (CSMWorld::UniversalId::Type_None), mScopes (CSMWorld::Scope_Content), mScope (0), - mScopeLabel (0), mCloneMode (false) + mClonedType (CSMWorld::UniversalId::Type_None), mScopes (CSMWorld::Scope_Content), mScope (nullptr), + mScopeLabel (nullptr), mCloneMode (false) { // If the collection ID has a parent type, use it instead. // It will change IDs with Record/SubRecord class (used for creators in Dialogue subviews) @@ -322,10 +322,10 @@ void CSVWorld::GenericCreator::setScope (unsigned int scope) else { delete mScope; - mScope = 0; + mScope = nullptr; delete mScopeLabel; - mScopeLabel = 0; + mScopeLabel = nullptr; } updateNamespace(); diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 4ff850b9f4..447bcc25d9 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -74,7 +74,7 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, { return new CSVWidget::DropLineEdit(display, parent); } - default: return 0; // The rest of them can't be edited anyway + default: return nullptr; // The rest of them can't be edited anyway } } diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp index 17624a243b..278335a65b 100644 --- a/apps/opencs/view/world/idvalidator.hpp +++ b/apps/opencs/view/world/idvalidator.hpp @@ -19,7 +19,7 @@ namespace CSVWorld public: - IdValidator (bool relaxed = false, QObject *parent = 0); + IdValidator (bool relaxed = false, QObject *parent = nullptr); ///< \param relaxed Relaxed rules for IDs that also functino as user visible text State validate (QString& input, int& pos) const override; diff --git a/apps/opencs/view/world/recordbuttonbar.hpp b/apps/opencs/view/world/recordbuttonbar.hpp index fbee066cea..aca3211f8a 100644 --- a/apps/opencs/view/world/recordbuttonbar.hpp +++ b/apps/opencs/view/world/recordbuttonbar.hpp @@ -58,8 +58,8 @@ namespace CSVWorld public: RecordButtonBar (const CSMWorld::UniversalId& id, - CSMWorld::IdTable& table, TableBottomBox *bottomBox = 0, - CSMWorld::CommandDispatcher *commandDispatcher = 0, QWidget *parent = 0); + CSMWorld::IdTable& table, TableBottomBox *bottomBox = nullptr, + CSMWorld::CommandDispatcher *commandDispatcher = nullptr, QWidget *parent = nullptr); void setEditLock (bool locked); diff --git a/apps/opencs/view/world/recordstatusdelegate.hpp b/apps/opencs/view/world/recordstatusdelegate.hpp index 6ec8c37bd8..38f066862b 100644 --- a/apps/opencs/view/world/recordstatusdelegate.hpp +++ b/apps/opencs/view/world/recordstatusdelegate.hpp @@ -19,7 +19,7 @@ namespace CSVWorld RecordStatusDelegate (const ValueList& values, const IconList& icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, - QObject *parent = 0); + QObject *parent = nullptr); }; class RecordStatusDelegateFactory : public DataDisplayDelegateFactory diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index b1f7cdc674..443de9ce3d 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -61,7 +61,7 @@ namespace CSVWorld public: RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, - QWidget *parent = 0); + QWidget *parent = nullptr); std::vector getDraggedRecords() const override; diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 9083359d27..743df9c765 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -47,7 +47,7 @@ CSVWorld::ScriptEdit::ScriptEdit( ) : QPlainTextEdit(parent), mChangeLocked(0), mShowLineNum(false), - mLineNumberArea(0), + mLineNumberArea(nullptr), mDefaultFont(font()), mMonoFont(QFont("Monospace")), mTabCharCount(4), @@ -314,7 +314,7 @@ void CSVWorld::ScriptEdit::markOccurrences() // prevent infinite recursion with cursor.select(), // which ends up calling this function again // could be fixed with blockSignals, but mDocument is const - disconnect(this, SIGNAL(cursorPositionChanged()), this, 0); + disconnect(this, SIGNAL(cursorPositionChanged()), this, nullptr); cursor.select(QTextCursor::WordUnderCursor); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(markOccurrences())); diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index ad287707dd..7165d0fc6a 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -41,7 +41,7 @@ namespace CSVWorld public: - ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = 0); + ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = nullptr); void update (const std::string& source); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 58ad094519..096fc8a9e7 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -88,7 +88,7 @@ void CSVWorld::ScriptSubView::adjustSplitter() } CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), +: SubView (id), mDocument (document), mColumn (-1), mBottom(nullptr), mButtons (nullptr), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())), mErrorHeight (CSMPrefs::get()["Scripts"]["error-height"].toInt()) { @@ -177,7 +177,7 @@ void CSVWorld::ScriptSubView::settingChanged (const CSMPrefs::Setting *setting) { mLayout.removeWidget (mButtons); delete mButtons; - mButtons = 0; + mButtons = nullptr; } } else if (*setting=="Scripts/compile-delay") diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 50d61150f3..6ad2dbd821 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -59,7 +59,7 @@ namespace CSVWorld TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMDoc::Document& document, const CSMWorld::UniversalId& id, - QWidget *parent = 0); + QWidget *parent = nullptr); virtual ~TableBottomBox(); diff --git a/apps/opencs/view/world/tableeditidaction.hpp b/apps/opencs/view/world/tableeditidaction.hpp index f2cf0b7bd0..9fe41b0de2 100644 --- a/apps/opencs/view/world/tableeditidaction.hpp +++ b/apps/opencs/view/world/tableeditidaction.hpp @@ -19,7 +19,7 @@ namespace CSVWorld CellData getCellData(int row, int column) const; public: - TableEditIdAction(const QTableView &table, QWidget *parent = 0); + TableEditIdAction(const QTableView &table, QWidget *parent = nullptr); void setCell(int row, int column); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 5a45033622..ba9f408474 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -57,7 +57,7 @@ QVariant CSVWorld::NastyTableModelHack::getData() const CSVWorld::CommandDelegateFactory::~CommandDelegateFactory() {} -CSVWorld::CommandDelegateFactoryCollection *CSVWorld::CommandDelegateFactoryCollection::sThis = 0; +CSVWorld::CommandDelegateFactoryCollection *CSVWorld::CommandDelegateFactoryCollection::sThis = nullptr; CSVWorld::CommandDelegateFactoryCollection::CommandDelegateFactoryCollection() { @@ -69,7 +69,7 @@ CSVWorld::CommandDelegateFactoryCollection::CommandDelegateFactoryCollection() CSVWorld::CommandDelegateFactoryCollection::~CommandDelegateFactoryCollection() { - sThis = 0; + sThis = nullptr; for (std::map::iterator iter ( mFactories.begin()); @@ -193,7 +193,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO variant = index.data(Qt::DisplayRole); if (!variant.isValid()) { - return 0; + return nullptr; } } @@ -362,7 +362,7 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde if (!n.isEmpty()) { if (!variant.isValid()) - variant = QVariant(editor->property(n).userType(), (const void *)0); + variant = QVariant(editor->property(n).userType(), (const void *)nullptr); editor->setProperty(n, variant); } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 0bfd67dd77..8092bb770b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -380,7 +380,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mGrab(true) , mExportFonts(false) , mRandomSeed(0) - , mScriptContext (0) + , mScriptContext (nullptr) , mFSStrict (false) , mScriptBlacklistUse (true) , mNewGame (false) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 764a07ec96..aca2685e06 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -14,11 +14,11 @@ #include "windowmanager.hpp" #include "statemanager.hpp" -MWBase::Environment *MWBase::Environment::sThis = 0; +MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0), - mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mStateManager (0), +: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), + mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { assert (!sThis); @@ -28,7 +28,7 @@ MWBase::Environment::Environment() MWBase::Environment::~Environment() { cleanup(); - sThis = 0; + sThis = nullptr; } void MWBase::Environment::setWorld (World *world) @@ -166,31 +166,31 @@ float MWBase::Environment::getFrameDuration() const void MWBase::Environment::cleanup() { delete mMechanicsManager; - mMechanicsManager = 0; + mMechanicsManager = nullptr; delete mDialogueManager; - mDialogueManager = 0; + mDialogueManager = nullptr; delete mJournal; - mJournal = 0; + mJournal = nullptr; delete mScriptManager; - mScriptManager = 0; + mScriptManager = nullptr; delete mWindowManager; - mWindowManager = 0; + mWindowManager = nullptr; delete mWorld; - mWorld = 0; + mWorld = nullptr; delete mSoundManager; - mSoundManager = 0; + mSoundManager = nullptr; delete mInputManager; - mInputManager = 0; + mInputManager = nullptr; delete mStateManager; - mStateManager = 0; + mStateManager = nullptr; } const MWBase::Environment& MWBase::Environment::get() diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index 643695c37c..157833a0ee 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -59,7 +59,7 @@ namespace MWBase virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0; - virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0; + virtual void saveGame (const std::string& description, const MWState::Slot *slot = nullptr) = 0; ///< Write a saved game to \a slot or create a new slot if \a slot == 0. /// /// \note Slot must belong to the current character. diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 49bc76a761..d4f1d2f8ab 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -180,7 +180,7 @@ namespace MWBase virtual char getGlobalVariableType (const std::string& name) const = 0; ///< Return ' ', if there is no global variable with this name. - virtual std::string getCellName (const MWWorld::CellStore *cell = 0) const = 0; + virtual std::string getCellName (const MWWorld::CellStore *cell = nullptr) const = 0; ///< Return name of the cell. /// /// \note If cell==0, the cell the player is currently in will be used instead to diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index c51eab513f..31341db730 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -69,7 +69,7 @@ namespace MWClass return *this; } - CreatureCustomData() : mContainerStore(0) {} + CreatureCustomData() : mContainerStore(nullptr) {} virtual ~CreatureCustomData() { delete mContainerStore; } }; diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 8a6ec998da..c70783f39c 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -925,7 +925,7 @@ public: void dirtyFocusItem () { - if (mFocusItem != 0) + if (mFocusItem != nullptr) { MyGUI::IFont* Font = mBook->affectedFont (mFocusItem); @@ -946,7 +946,7 @@ public: dirtyFocusItem (); - mFocusItem = 0; + mFocusItem = nullptr; mItemActive = false; } @@ -976,7 +976,7 @@ public: } } else - if (mFocusItem != 0) + if (mFocusItem != nullptr) { bool newItemActive = hit == mFocusItem; diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 31fe3afb04..827f87c7d6 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -87,15 +87,15 @@ namespace MWGui CharacterCreation::CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem) : mParent(parent) , mResourceSystem(resourceSystem) - , mNameDialog(0) - , mRaceDialog(0) - , mClassChoiceDialog(0) - , mGenerateClassQuestionDialog(0) - , mGenerateClassResultDialog(0) - , mPickClassDialog(0) - , mCreateClassDialog(0) - , mBirthSignDialog(0) - , mReviewDialog(0) + , mNameDialog(nullptr) + , mRaceDialog(nullptr) + , mClassChoiceDialog(nullptr) + , mGenerateClassQuestionDialog(nullptr) + , mGenerateClassResultDialog(nullptr) + , mPickClassDialog(nullptr) + , mCreateClassDialog(nullptr) + , mBirthSignDialog(nullptr) + , mReviewDialog(nullptr) , mGenerateClassStep(0) { mCreationStage = CSE_NotStarted; @@ -184,7 +184,7 @@ namespace MWGui { case GM_Name: MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); - mNameDialog = 0; + mNameDialog = nullptr; mNameDialog = new TextInputDialog(); mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); mNameDialog->setTextInput(mPlayerName); @@ -195,7 +195,7 @@ namespace MWGui case GM_Race: MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); - mRaceDialog = 0; + mRaceDialog = nullptr; mRaceDialog = new RaceDialog(mParent, mResourceSystem); mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen); mRaceDialog->setRaceId(mPlayerRaceId); @@ -208,7 +208,7 @@ namespace MWGui case GM_Class: MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); - mClassChoiceDialog = 0; + mClassChoiceDialog = nullptr; mClassChoiceDialog = new ClassChoiceDialog(); mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); mClassChoiceDialog->setVisible(true); @@ -218,7 +218,7 @@ namespace MWGui case GM_ClassPick: MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); - mPickClassDialog = 0; + mPickClassDialog = nullptr; mPickClassDialog = new PickClassDialog(); mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); mPickClassDialog->setClassId(mPlayerClass.mId); @@ -231,7 +231,7 @@ namespace MWGui case GM_Birth: MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); - mBirthSignDialog = 0; + mBirthSignDialog = nullptr; mBirthSignDialog = new BirthDialog(); mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); mBirthSignDialog->setBirthId(mPlayerBirthSignId); @@ -266,7 +266,7 @@ namespace MWGui break; case GM_Review: MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = 0; + mReviewDialog = nullptr; mReviewDialog = new ReviewDialog(); MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -316,7 +316,7 @@ namespace MWGui void CharacterCreation::onReviewDialogDone(WindowBase* parWindow) { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = 0; + mReviewDialog = nullptr; MWBase::Environment::get().getWindowManager()->popGuiMode(); } @@ -324,7 +324,7 @@ namespace MWGui void CharacterCreation::onReviewDialogBack() { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = 0; + mReviewDialog = nullptr; mCreationStage = CSE_ReviewBack; MWBase::Environment::get().getWindowManager()->popGuiMode(); @@ -334,7 +334,7 @@ namespace MWGui void CharacterCreation::onReviewActivateDialog(int parDialog) { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = 0; + mReviewDialog = nullptr; mCreationStage = CSE_ReviewNext; MWBase::Environment::get().getWindowManager()->popGuiMode(); @@ -370,7 +370,7 @@ namespace MWGui mPlayerClass = *klass; } MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); - mPickClassDialog = 0; + mPickClassDialog = nullptr; } updatePlayerHealth(); @@ -394,7 +394,7 @@ namespace MWGui void CharacterCreation::onClassChoice(int _index) { MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); - mClassChoiceDialog = 0; + mClassChoiceDialog = nullptr; MWBase::Environment::get().getWindowManager()->popGuiMode(); @@ -423,7 +423,7 @@ namespace MWGui mPlayerName = mNameDialog->getTextInput(); MWBase::Environment::get().getMechanicsManager()->setPlayerName(mPlayerName); MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); - mNameDialog = 0; + mNameDialog = nullptr; } handleDialogDone(CSE_NameChosen, GM_Race); @@ -446,7 +446,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->getInventoryWindow()->rebuildAvatar(); MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); - mRaceDialog = 0; + mRaceDialog = nullptr; } updatePlayerHealth(); @@ -475,7 +475,7 @@ namespace MWGui if (!mPlayerBirthSignId.empty()) MWBase::Environment::get().getMechanicsManager()->setPlayerBirthsign(mPlayerBirthSignId); MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); - mBirthSignDialog = 0; + mBirthSignDialog = nullptr; } updatePlayerHealth(); @@ -551,7 +551,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->stopSay(); MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassQuestionDialog); - mGenerateClassQuestionDialog = 0; + mGenerateClassQuestionDialog = nullptr; if (_index < 0 || _index >= 3) { @@ -669,7 +669,7 @@ namespace MWGui } MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); - mGenerateClassResultDialog = 0; + mGenerateClassResultDialog = nullptr; mGenerateClassResultDialog = new GenerateClassResultDialog(); mGenerateClassResultDialog->setClassId(mGenerateClass); @@ -687,7 +687,7 @@ namespace MWGui } MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassQuestionDialog); - mGenerateClassQuestionDialog = 0; + mGenerateClassQuestionDialog = nullptr; mGenerateClassQuestionDialog = new InfoBoxDialog(); @@ -711,7 +711,7 @@ namespace MWGui void CharacterCreation::selectGeneratedClass() { MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); - mGenerateClassResultDialog = 0; + mGenerateClassResultDialog = nullptr; MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index cfbf5e5fb5..ee5fe59399 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -550,16 +550,16 @@ namespace MWGui void CreateClassDialog::onDialogCancel() { MWBase::Environment::get().getWindowManager()->removeDialog(mSpecDialog); - mSpecDialog = 0; + mSpecDialog = nullptr; MWBase::Environment::get().getWindowManager()->removeDialog(mAttribDialog); - mAttribDialog = 0; + mAttribDialog = nullptr; MWBase::Environment::get().getWindowManager()->removeDialog(mSkillDialog); - mSkillDialog = 0; + mSkillDialog = nullptr; MWBase::Environment::get().getWindowManager()->removeDialog(mDescDialog); - mDescDialog = 0; + mDescDialog = nullptr; } void CreateClassDialog::onSpecializationClicked(MyGUI::Widget* _sender) @@ -577,7 +577,7 @@ namespace MWGui setSpecialization(mSpecializationId); MWBase::Environment::get().getWindowManager()->removeDialog(mSpecDialog); - mSpecDialog = 0; + mSpecDialog = nullptr; } void CreateClassDialog::setSpecialization(int id) @@ -618,7 +618,7 @@ namespace MWGui } mAffectedAttribute->setAttributeId(id); MWBase::Environment::get().getWindowManager()->removeDialog(mAttribDialog); - mAttribDialog = 0; + mAttribDialog = nullptr; update(); } @@ -651,7 +651,7 @@ namespace MWGui mAffectedSkill->setSkillId(mSkillDialog->getSkillId()); MWBase::Environment::get().getWindowManager()->removeDialog(mSkillDialog); - mSkillDialog = 0; + mSkillDialog = nullptr; update(); } @@ -667,7 +667,7 @@ namespace MWGui { mDescription = mDescDialog->getTextInput(); MWBase::Environment::get().getWindowManager()->removeDialog(mDescDialog); - mDescDialog = 0; + mDescDialog = nullptr; } void CreateClassDialog::onOkClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index e56cd170c8..89ba5d388d 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -36,7 +36,7 @@ namespace MWGui ConsoleInterpreterContext::ConsoleInterpreterContext (Console& console, MWWorld::Ptr reference) : MWScript::InterpreterContext ( - reference.isEmpty() ? 0 : &reference.getRefData().getLocals(), reference), + reference.isEmpty() ? nullptr : &reference.getRefData().getLocals(), reference), mConsole (console) {} diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp index daf9f66367..cfe60db5dd 100644 --- a/apps/openmw/mwgui/draganddrop.cpp +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -135,7 +135,7 @@ void DragAndDrop::finish() MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); - mDraggedWidget = 0; + mDraggedWidget = nullptr; MWBase::Environment::get().getWindowManager()->setDragDrop(false); } diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index a5d8f7344d..4e695710ff 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -24,7 +24,7 @@ namespace MWGui MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription) : WindowBase("openmw_mainmenu.layout") , mWidth (w), mHeight (h) - , mVFS(vfs), mButtonBox(0) + , mVFS(vfs), mButtonBox(nullptr) , mBackground(nullptr) , mVideoBackground(nullptr) , mVideo(nullptr) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index acf1319269..6e8804a43f 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -644,7 +644,7 @@ namespace MWGui : WindowPinnableBase("openmw_map_window.layout") , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) - , mGlobalMap(0) + , mGlobalMap(nullptr) , mGlobalMapImage(nullptr) , mGlobalMapOverlay(nullptr) , mGlobal(Settings::Manager::getBool("global", "Map")) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 214e529428..a6bfac2a45 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -37,9 +37,9 @@ namespace MWGui , mKey(std::vector(10)) , mSelected(nullptr) , mActivated(nullptr) - , mAssignDialog(0) - , mItemSelectionDialog(0) - , mMagicSelectionDialog(0) + , mAssignDialog(nullptr) + , mItemSelectionDialog(nullptr) + , mMagicSelectionDialog(nullptr) { getWidget(mOkButton, "OKButton"); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index e76cbe7706..101b6956d0 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -86,7 +86,7 @@ namespace MWGui for (int i = 0; i < ESM::Skill::Length; ++i) { mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); - mSkillWidgetMap.insert(std::make_pair(i, static_cast (0))); + mSkillWidgetMap.insert(std::make_pair(i, static_cast (nullptr))); } MyGUI::Button* backButton; diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 1f086507fd..f9de469e20 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -587,7 +587,7 @@ namespace MWGui mAddEffectDialog.newEffect(effect); mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId()); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); - mSelectAttributeDialog = 0; + mSelectAttributeDialog = nullptr; } void EffectEditorBase::onSelectSkill () @@ -598,7 +598,7 @@ namespace MWGui mAddEffectDialog.newEffect(effect); mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId()); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); - mSelectSkillDialog = 0; + mSelectSkillDialog = nullptr; } void EffectEditorBase::onAttributeOrSkillCancel () @@ -608,8 +608,8 @@ namespace MWGui if (mSelectAttributeDialog) MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); - mSelectSkillDialog = 0; - mSelectAttributeDialog = 0; + mSelectSkillDialog = nullptr; + mSelectAttributeDialog = nullptr; } void EffectEditorBase::onAvailableEffectClicked (MyGUI::Widget* sender) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index c0db57b1b1..e0a8e4d3e0 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -153,7 +153,7 @@ namespace MWGui return; MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget(); - if (focus == 0) + if (focus == nullptr) return; MyGUI::IntSize tooltipSize; @@ -410,7 +410,7 @@ namespace MWGui if (text.size() > 0 && text[0] == '\n') text.erase(0, 1); - const ESM::Enchantment* enchant = 0; + const ESM::Enchantment* enchant = nullptr; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); if (info.enchant != "") { diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 74076641a9..fb8521f067 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -101,7 +101,7 @@ namespace MWGui button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWSkill::onClicked); } - button = 0; + button = nullptr; assignWidget(button, "StatValueButton"); if (button) { @@ -192,7 +192,7 @@ namespace MWGui button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWAttribute::onClicked); } - button = 0; + button = nullptr; assignWidget(button, "StatValueButton"); if (button) { diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 840f0f9cfa..bfce908103 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1957,7 +1957,7 @@ namespace MWGui { if (_type != "Text") return; - char* text=0; + char* text=nullptr; text = SDL_GetClipboardText(); if (text) _data = MyGUI::TextIterator::toTagsString(text); diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index ac30d4487b..4816470ffb 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -154,7 +154,7 @@ namespace MWInput { guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; - if (MyGUI::InputManager::getInstance().getMouseFocusWidget () != 0) + if (MyGUI::InputManager::getInstance().getMouseFocusWidget () != nullptr) { MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); if (b && b->getEnabled() && id == SDL_BUTTON_LEFT) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 116937fcdb..6d06724f00 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -255,7 +255,7 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co return &(*iter); } - return 0; + return nullptr; } void MWMechanics::Alchemy::removeIngredients() diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index ce45729b3b..a908940ed9 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -368,7 +368,7 @@ namespace MWScript std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::CellStore* store = 0; + MWWorld::CellStore* store = nullptr; try { store = MWBase::Environment::get().getWorld()->getInterior(cellID); @@ -481,7 +481,7 @@ namespace MWScript Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; runtime.pop(); - MWWorld::CellStore* store = 0; + MWWorld::CellStore* store = nullptr; try { store = MWBase::Environment::get().getWorld()->getInterior(cellID); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 6c334978c0..95ed9eeed8 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -437,7 +437,7 @@ FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) , mFrameSize(0) , mFramePos(0) , mNextPts(0.0) - , mSwr(0) + , mSwr(nullptr) , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) , mOutputChannelLayout(0) , mDataBuf(nullptr) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index a7be5a743f..67b52309d5 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -624,7 +624,7 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname attrs.reserve(15); if(ALC.SOFT_HRTF) { - LPALCGETSTRINGISOFT alcGetStringiSOFT = 0; + LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); attrs.push_back(ALC_HRTF_SOFT); @@ -850,13 +850,13 @@ void OpenAL_Output::deinit() alDeleteFilters(1, &mWaterFilter); mWaterFilter = 0; - alcMakeContextCurrent(0); + alcMakeContextCurrent(nullptr); if(mContext) alcDestroyContext(mContext); - mContext = 0; + mContext = nullptr; if(mDevice) alcCloseDevice(mDevice); - mDevice = 0; + mDevice = nullptr; mInitialized = false; } @@ -869,7 +869,7 @@ std::vector OpenAL_Output::enumerateHrtf() if(!mDevice || !ALC.SOFT_HRTF) return ret; - LPALCGETSTRINGISOFT alcGetStringiSOFT = 0; + LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); ALCint num_hrtf; @@ -892,10 +892,10 @@ void OpenAL_Output::setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) return; } - LPALCGETSTRINGISOFT alcGetStringiSOFT = 0; + LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); - LPALCRESETDEVICESOFT alcResetDeviceSOFT = 0; + LPALCRESETDEVICESOFT alcResetDeviceSOFT = nullptr; getALCFunc(alcResetDeviceSOFT, mDevice, "alcResetDeviceSOFT"); std::vector attrs; @@ -1213,7 +1213,7 @@ void OpenAL_Output::finishSound(Sound *sound) { if(!sound->mHandle) return; ALuint source = GET_PTRID(sound->mHandle); - sound->mHandle = 0; + sound->mHandle = nullptr; // Rewind the stream to put the source back into an AL_INITIAL state, for // the next time it's used. @@ -1316,7 +1316,7 @@ void OpenAL_Output::finishStream(Stream *sound) OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); ALuint source = stream->mSource; - sound->mHandle = 0; + sound->mHandle = nullptr; mStreamThread->remove(stream); // Rewind the stream to put the source back into an AL_INITIAL state, for @@ -1462,7 +1462,7 @@ void OpenAL_Output::pauseActiveDevice() if(alcIsExtensionPresent(mDevice, "ALC_SOFT_PAUSE_DEVICE")) { - LPALCDEVICEPAUSESOFT alcDevicePauseSOFT = 0; + LPALCDEVICEPAUSESOFT alcDevicePauseSOFT = nullptr; getALCFunc(alcDevicePauseSOFT, mDevice, "alcDevicePauseSOFT"); alcDevicePauseSOFT(mDevice); getALCError(mDevice); @@ -1478,7 +1478,7 @@ void OpenAL_Output::resumeActiveDevice() if(alcIsExtensionPresent(mDevice, "ALC_SOFT_PAUSE_DEVICE")) { - LPALCDEVICERESUMESOFT alcDeviceResumeSOFT = 0; + LPALCDEVICERESUMESOFT alcDeviceResumeSOFT = nullptr; getALCFunc(alcDeviceResumeSOFT, mDevice, "alcDeviceResumeSOFT"); alcDeviceResumeSOFT(mDevice); getALCError(mDevice); @@ -1513,7 +1513,7 @@ void OpenAL_Output::resumeSounds(int types) OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr) - , mDevice(0), mContext(0) + , mDevice(nullptr), mContext(nullptr) , mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal) , mWaterFilter(0), mWaterEffect(0), mDefaultEffect(0), mEffectSlot(0) , mStreamThread(new StreamThread) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 5ca3a45da7..83b08d6be8 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -20,7 +20,7 @@ namespace MWSound size_t mUses; Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) - : mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(0), mUses(0) + : mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(nullptr), mUses(0) { } }; } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 1a90dceedf..37ba80820d 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -120,7 +120,7 @@ namespace MWSound { if(sfx.mHandle) mOutput->unloadSound(sfx.mHandle); - sfx.mHandle = 0; + sfx.mHandle = nullptr; } mUnusedBuffers.clear(); mOutput.reset(); @@ -229,7 +229,7 @@ namespace MWSound size = mOutput->unloadSound(unused->mHandle); mBufferCacheSize -= size; - unused->mHandle = 0; + unused->mHandle = nullptr; mUnusedBuffers.pop_back(); } while(mBufferCacheSize > mBufferCacheMin); diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 3c5c4f8b2a..df1ab1bdfb 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -64,7 +64,7 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) } slot.mProfile = profile; - slot.mTimeStamp = std::time (0); + slot.mTimeStamp = std::time (nullptr); mSlots.push_back (slot); } @@ -143,7 +143,7 @@ const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM Slot newSlot = *slot; newSlot.mProfile = profile; - newSlot.mTimeStamp = std::time (0); + newSlot.mTimeStamp = std::time (nullptr); mSlots.erase (mSlots.begin()+index); diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index b5868c3e5a..a324dfe0f7 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -7,7 +7,7 @@ MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, const std::string& game) -: mPath (saves), mCurrent (0), mGame (game) +: mPath (saves), mCurrent (nullptr), mGame (game) { if (!boost::filesystem::is_directory (mPath)) { diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 11984b7f50..3534dabf2b 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -53,7 +53,7 @@ namespace MWState void deleteGame (const MWState::Character *character, const MWState::Slot *slot) override; ///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted too. - void saveGame (const std::string& description, const Slot *slot = 0) override; + void saveGame (const std::string& description, const Slot *slot = nullptr) override; ///< Write a saved game to \a slot or create a new slot if \a slot == 0. /// /// \note Slot must belong to the current character. diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 94c78b4332..15c1b46bab 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -94,7 +94,7 @@ void MWWorld::Cells::clear() { mInteriors.clear(); mExteriors.clear(); - std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::CellStore*)0)); + std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::CellStore*)nullptr)); mIdCacheIndex = 0; } @@ -132,7 +132,7 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector& reader) : mStore (store), mReader (reader), - mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair ("", (CellStore*)0)), + mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair ("", (CellStore*)nullptr)), mIdCacheIndex (0) {} @@ -449,7 +449,7 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, ESM::CellState state; state.mId.load (reader); - CellStore *cellStore = 0; + CellStore *cellStore = nullptr; try { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 14b96b27c6..b48fe74a68 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -37,7 +37,7 @@ namespace for (typename MWWorld::CellRefList::List::iterator iter (containerList.mList.begin()); iter!=containerList.mList.end(); ++iter) { - MWWorld::Ptr container (&*iter, 0); + MWWorld::Ptr container (&*iter, nullptr); if (container.getRefData().getCustomData() == nullptr) continue; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 6fad429263..17c35489f5 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -67,7 +67,7 @@ namespace { if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount()) { - MWWorld::Ptr ptr (&*iter, 0); + MWWorld::Ptr ptr (&*iter, nullptr); ptr.setContainerStore (store); return ptr; } @@ -326,7 +326,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr if (actorPtr == player) { // Items in player's inventory have cell set to 0, so their scripts will never be removed - item.mCell = 0; + item.mCell = nullptr; } else { @@ -1138,18 +1138,18 @@ PtrType MWWorld::ContainerStoreIteratorBase::operator*() const switch (mType) { - case ContainerStore::Type_Potion: ptr = PtrType (&*mPotion, 0); break; - case ContainerStore::Type_Apparatus: ptr = PtrType (&*mApparatus, 0); break; - case ContainerStore::Type_Armor: ptr = PtrType (&*mArmor, 0); break; - case ContainerStore::Type_Book: ptr = PtrType (&*mBook, 0); break; - case ContainerStore::Type_Clothing: ptr = PtrType (&*mClothing, 0); break; - case ContainerStore::Type_Ingredient: ptr = PtrType (&*mIngredient, 0); break; - case ContainerStore::Type_Light: ptr = PtrType (&*mLight, 0); break; - case ContainerStore::Type_Lockpick: ptr = PtrType (&*mLockpick, 0); break; - case ContainerStore::Type_Miscellaneous: ptr = PtrType (&*mMiscellaneous, 0); break; - case ContainerStore::Type_Probe: ptr = PtrType (&*mProbe, 0); break; - case ContainerStore::Type_Repair: ptr = PtrType (&*mRepair, 0); break; - case ContainerStore::Type_Weapon: ptr = PtrType (&*mWeapon, 0); break; + case ContainerStore::Type_Potion: ptr = PtrType (&*mPotion, nullptr); break; + case ContainerStore::Type_Apparatus: ptr = PtrType (&*mApparatus, nullptr); break; + case ContainerStore::Type_Armor: ptr = PtrType (&*mArmor, nullptr); break; + case ContainerStore::Type_Book: ptr = PtrType (&*mBook, nullptr); break; + case ContainerStore::Type_Clothing: ptr = PtrType (&*mClothing, nullptr); break; + case ContainerStore::Type_Ingredient: ptr = PtrType (&*mIngredient, nullptr); break; + case ContainerStore::Type_Light: ptr = PtrType (&*mLight, nullptr); break; + case ContainerStore::Type_Lockpick: ptr = PtrType (&*mLockpick, nullptr); break; + case ContainerStore::Type_Miscellaneous: ptr = PtrType (&*mMiscellaneous, nullptr); break; + case ContainerStore::Type_Probe: ptr = PtrType (&*mProbe, nullptr); break; + case ContainerStore::Type_Repair: ptr = PtrType (&*mRepair, nullptr); break; + case ContainerStore::Type_Weapon: ptr = PtrType (&*mWeapon, nullptr); break; } if (ptr.isEmpty()) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 942d5feeba..90bc80b484 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -71,7 +71,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) { listener->setProgressRange(1000); - ESM::Dialogue *dialogue = 0; + ESM::Dialogue *dialogue = nullptr; // Land texture loading needs to use a separate internal store for each plugin. // We set the number of plugins here to avoid continual resizes during loading, @@ -153,7 +153,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) if (n.intval==ESM::REC_DIAL) { dialogue = const_cast(mDialogs.find(id.mId)); } else { - dialogue = 0; + dialogue = nullptr; } } listener->setProgress(static_cast(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 99db96ac10..d69c56d8c0 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -196,7 +196,7 @@ namespace MWWorld const std::string id = "$dynamic" + std::to_string(mDynamicCount++); Store &store = const_cast &>(get()); - if (store.search(id) != 0) + if (store.search(id) != nullptr) { const std::string msg = "Try to override existing record '" + id + "'"; throw std::runtime_error(msg); @@ -234,7 +234,7 @@ namespace MWWorld const std::string id = "$dynamic" + std::to_string(mDynamicCount++); Store &store = const_cast &>(get()); - if (store.search(id) != 0) + if (store.search(id) != nullptr) { const std::string msg = "Try to override existing record '" + id + "'"; throw std::runtime_error(msg); @@ -286,7 +286,7 @@ namespace MWWorld { return mNpcs.insert(npc); } - else if (mNpcs.search(id) != 0) + else if (mNpcs.search(id) != nullptr) { const std::string msg = "Try to override existing record '" + id + "'"; throw std::runtime_error(msg); diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp index 48270d224d..7f7fd60335 100644 --- a/apps/openmw/mwworld/manualref.cpp +++ b/apps/openmw/mwworld/manualref.cpp @@ -26,7 +26,7 @@ namespace MWWorld::LiveCellRef ref(cellRef, base); refValue = ref; - ptrValue = MWWorld::Ptr(&boost::any_cast&>(refValue), 0); + ptrValue = MWWorld::Ptr(&boost::any_cast&>(refValue), nullptr); } } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 66ae4e3194..d8e2fb2f0d 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -29,7 +29,7 @@ namespace MWWorld { Player::Player (const ESM::NPC *player) - : mCellStore(0), + : mCellStore(nullptr), mLastKnownExteriorPosition(0,0,0), mMarkedPosition(ESM::Position()), mMarkedCell(nullptr), @@ -299,9 +299,9 @@ namespace MWWorld void Player::clear() { - mCellStore = 0; + mCellStore = nullptr; mSign.clear(); - mMarkedCell = 0; + mMarkedCell = nullptr; mAutoMove = false; mForwardBackward = 0; mTeleported = false; @@ -448,7 +448,7 @@ namespace MWWorld } else { - mMarkedCell = 0; + mMarkedCell = nullptr; } mForwardBackward = 0; diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 12c44b0b31..e16a196297 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -8,7 +8,7 @@ const std::string& MWWorld::Ptr::getTypeName() const { - if(mRef != 0) + if(mRef != nullptr) return mRef->mClass->getTypeName(); throw std::runtime_error("Can't get type name from an empty object."); } @@ -57,7 +57,7 @@ MWWorld::Ptr::operator const void *() const std::string &MWWorld::ConstPtr::getTypeName() const { - if(mRef != 0) + if(mRef != nullptr) return mRef->mClass->getTypeName(); throw std::runtime_error("Can't get type name from an empty object."); } diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index d7c170e456..9ab18d7f48 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -25,21 +25,21 @@ namespace MWWorld ContainerStore *mContainerStore; public: - Ptr(MWWorld::LiveCellRefBase *liveCellRef=0, CellStore *cell=0) - : mRef(liveCellRef), mCell(cell), mContainerStore(0) + Ptr(MWWorld::LiveCellRefBase *liveCellRef=nullptr, CellStore *cell=nullptr) + : mRef(liveCellRef), mCell(cell), mContainerStore(nullptr) { } bool isEmpty() const { - return mRef == 0; + return mRef == nullptr; } const std::string& getTypeName() const; const Class& getClass() const { - if(mRef != 0) + if(mRef != nullptr) return *(mRef->mClass); throw std::runtime_error("Cannot get class of an empty object"); } @@ -52,7 +52,7 @@ namespace MWWorld std::stringstream str; str<< "Bad LiveCellRef cast to "<mClass); throw std::runtime_error("Cannot get class of an empty object"); } @@ -128,7 +128,7 @@ namespace MWWorld std::stringstream str; str<< "Bad LiveCellRef cast to "<clone() : 0; + mCustomData = refData.mCustomData ? refData.mCustomData->clone() : nullptr; } void RefData::cleanup() { - mBaseNode = 0; + mBaseNode = nullptr; delete mCustomData; - mCustomData = 0; + mCustomData = nullptr; } RefData::RefData() - : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false), mFlags(0) + : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (nullptr), mChanged(false), mFlags(0) { for (int i=0; i<3; ++i) { @@ -56,20 +56,20 @@ namespace MWWorld } RefData::RefData (const ESM::CellRef& cellRef) - : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), + : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), - mCustomData (0), + mCustomData (nullptr), mChanged(false), mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } RefData::RefData (const ESM::ObjectState& objectState, bool deletedByContentFile) - : mBaseNode(0), mDeletedByContentFile(deletedByContentFile), + : mBaseNode(nullptr), mDeletedByContentFile(deletedByContentFile), mEnabled (objectState.mEnabled != 0), mCount (objectState.mCount), mPosition (objectState.mPosition), mAnimationState(objectState.mAnimationState), - mCustomData (0), + mCustomData (nullptr), mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed { // "Note that the ActivationFlag_UseEnabled is saved to the reference, @@ -79,7 +79,7 @@ namespace MWWorld } RefData::RefData (const RefData& refData) - : mBaseNode(0), mCustomData (0) + : mBaseNode(nullptr), mCustomData (nullptr) { try { diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index d01cc741cb..47b62862f7 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -397,7 +397,7 @@ namespace MWWorld if (!test && cell->getCell()->isExterior()) { osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; if (data) { mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); @@ -755,7 +755,7 @@ namespace MWWorld Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, DetourNavigator::Navigator& navigator) - : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator) + : mCurrentCell (nullptr), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator) , mCellLoadingThreshold(1024.f) , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index f8ec8c7c27..69ca1f8c27 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -101,7 +101,7 @@ namespace MWWorld const T *IndexedStore::find(int index) const { const T *ptr = search(index); - if (ptr == 0) + if (ptr == nullptr) { const std::string msg = T::getRecordType() + " with index " + std::to_string(index) + " not found"; throw std::runtime_error(msg); @@ -146,7 +146,7 @@ namespace MWWorld if (it != mStatic.end()) return &(it->second); - return 0; + return nullptr; } template const T *Store::searchStatic(const std::string &id) const @@ -156,7 +156,7 @@ namespace MWWorld if (it != mStatic.end()) return &(it->second); - return 0; + return nullptr; } template @@ -178,7 +178,7 @@ namespace MWWorld const T *Store::find(const std::string &id) const { const T *ptr = search(id); - if (ptr == 0) + if (ptr == nullptr) { const std::string msg = T::getRecordType() + " '" + id + "' not found"; throw std::runtime_error(msg); @@ -189,7 +189,7 @@ namespace MWWorld const T *Store::findRandom(const std::string &id) const { const T *ptr = searchRandom(id); - if(ptr == 0) + if(ptr == nullptr) { const std::string msg = T::getRecordType() + " starting with '" + id + "' not found"; throw std::runtime_error(msg); @@ -370,7 +370,7 @@ namespace MWWorld const ESM::LandTexture *Store::find(size_t index, size_t plugin) const { const ESM::LandTexture *ptr = search(index, plugin); - if (ptr == 0) + if (ptr == nullptr) { const std::string msg = "Land texture with index " + std::to_string(index) + " not found"; throw std::runtime_error(msg); @@ -471,12 +471,12 @@ namespace MWWorld if (it != mStatic.end() && (*it)->mX == x && (*it)->mY == y) { return *it; } - return 0; + return nullptr; } const ESM::Land *Store::find(int x, int y) const { const ESM::Land *ptr = search(x, y); - if (ptr == 0) + if (ptr == nullptr) { const std::string msg = "Land at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found"; throw std::runtime_error(msg); @@ -570,7 +570,7 @@ namespace MWWorld return &dit->second; } - return 0; + return nullptr; } const ESM::Cell *Store::search(int x, int y) const { @@ -588,7 +588,7 @@ namespace MWWorld return &dit->second; } - return 0; + return nullptr; } const ESM::Cell *Store::searchStatic(int x, int y) const { @@ -600,7 +600,7 @@ namespace MWWorld if (it != mExt.end()) { return &(it->second); } - return 0; + return nullptr; } const ESM::Cell *Store::searchOrCreate(int x, int y) { @@ -628,7 +628,7 @@ namespace MWWorld const ESM::Cell *Store::find(const std::string &id) const { const ESM::Cell *ptr = search(id); - if (ptr == 0) + if (ptr == nullptr) { const std::string msg = "Cell '" + id + "' not found"; throw std::runtime_error(msg); @@ -638,7 +638,7 @@ namespace MWWorld const ESM::Cell *Store::find(int x, int y) const { const ESM::Cell *ptr = search(x, y); - if (ptr == 0) + if (ptr == nullptr) { const std::string msg = "Exterior at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found"; throw std::runtime_error(msg); @@ -781,7 +781,7 @@ namespace MWWorld { if (Misc::StringUtils::ciEqual(sharedCell->mName, id)) { - if (cell == 0 || + if (cell == nullptr || (sharedCell->mData.mX > cell->mData.mX) || (sharedCell->mData.mX == cell->mData.mX && sharedCell->mData.mY > cell->mData.mY)) { @@ -831,7 +831,7 @@ namespace MWWorld } ESM::Cell *Store::insert(const ESM::Cell &cell) { - if (search(cell) != 0) + if (search(cell) != nullptr) { const std::string cellType = (cell.isExterior()) ? "exterior" : "interior"; throw std::runtime_error("Failed to create " + cellType + " cell"); @@ -1032,7 +1032,7 @@ namespace MWWorld const ESM::Attribute *Store::search(size_t index) const { if (index >= mStatic.size()) { - return 0; + return nullptr; } return &mStatic.at(index); } @@ -1040,7 +1040,7 @@ namespace MWWorld const ESM::Attribute *Store::find(size_t index) const { const ESM::Attribute *ptr = search(index); - if (ptr == 0) + if (ptr == nullptr) { const std::string msg = "Attribute with index " + std::to_string(index) + " not found"; throw std::runtime_error(msg); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index d2406b602b..cb9f4f1e02 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -412,7 +412,7 @@ namespace MWWorld const ESM::WeaponType *search(const int id) const; const ESM::WeaponType *find(const int id) const; - RecordId load(ESM::ESMReader &esm) override { return RecordId(0, false); } + RecordId load(ESM::ESMReader &esm) override { return RecordId(nullptr, false); } ESM::WeaponType* insert(const ESM::WeaponType &weaponType); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 20ee7625b4..224925dd55 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -302,7 +302,7 @@ namespace MWWorld if (mPlayer) { mPlayer->clear(); - mPlayer->setCell(0); + mPlayer->setCell(nullptr); mPlayer->getPlayer().getRefData() = RefData(); mPlayer->set(mStore.get().find ("player")); } @@ -1010,7 +1010,7 @@ namespace MWWorld if (!facedObject.isEmpty() && !facedObject.getClass().allowTelekinesis(facedObject) && mDistanceToFacedObject > getMaxActivationDistance() && !MWBase::Environment::get().getWindowManager()->isGuiMode()) - return 0; + return nullptr; } return facedObject; } @@ -1180,7 +1180,7 @@ namespace MWWorld haveToMove = false; newPtr = currCell->moveTo(ptr, newCell); - newPtr.getRefData().setBaseNode(0); + newPtr.getRefData().setBaseNode(nullptr); } else if (!currCellActive && !newCellActive) newPtr = currCell->moveTo(ptr, newCell); @@ -1263,7 +1263,7 @@ namespace MWWorld mWorldScene->removeFromPagedRefs(ptr); } - if(ptr.getRefData().getBaseNode() != 0) + if(ptr.getRefData().getBaseNode() != nullptr) mWorldScene->updateObjectScale(ptr); if (mPhysics->getActor(ptr)) @@ -1311,7 +1311,7 @@ namespace MWWorld mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); mWorldScene->removeFromPagedRefs(ptr); - if(ptr.getRefData().getBaseNode() != 0) + if(ptr.getRefData().getBaseNode() != nullptr) { const auto order = flags & MWBase::RotationFlag_inverseOrder ? RotationOrder::inverse : RotationOrder::direct; @@ -1396,7 +1396,7 @@ namespace MWWorld void World::rotateWorldObject (const Ptr& ptr, osg::Quat rotate) { - if(ptr.getRefData().getBaseNode() != 0) + if(ptr.getRefData().getBaseNode() != nullptr) { mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); mWorldScene->removeFromPagedRefs(ptr); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 41ab9cf2ca..1fc5ebc20d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -275,7 +275,7 @@ namespace MWWorld char getGlobalVariableType (const std::string& name) const override; ///< Return ' ', if there is no global variable with this name. - std::string getCellName (const MWWorld::CellStore *cell = 0) const override; + std::string getCellName (const MWWorld::CellStore *cell = nullptr) const override; ///< Return name of the cell. /// /// \note If cell==0, the cell the player is currently in will be used instead to diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 8d9623baa1..4c221635ef 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -41,7 +41,7 @@ namespace Wizard Page_Conclusion }; - MainWizard(QWidget *parent = 0); + MainWizard(QWidget *parent = nullptr); ~MainWizard(); bool findFiles(const QString &name, const QString &path); diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 3f922ad78a..2553d1bef8 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -26,7 +26,7 @@ namespace Wizard Q_OBJECT public: - UnshieldWorker(QObject *parent = 0); + UnshieldWorker(QObject *parent = nullptr); ~UnshieldWorker(); void stopWorker(); diff --git a/apps/wizard/utils/componentlistwidget.hpp b/apps/wizard/utils/componentlistwidget.hpp index 23965f8a6b..be15ea49f7 100644 --- a/apps/wizard/utils/componentlistwidget.hpp +++ b/apps/wizard/utils/componentlistwidget.hpp @@ -10,7 +10,7 @@ class ComponentListWidget : public QListWidget Q_PROPERTY(QStringList mCheckedItems READ checkedItems) public: - ComponentListWidget(QWidget *parent = 0); + ComponentListWidget(QWidget *parent = nullptr); QStringList mCheckedItems; QStringList checkedItems(); diff --git a/components/compiler/context.hpp b/components/compiler/context.hpp index 2d6af0e451..399e8125bb 100644 --- a/components/compiler/context.hpp +++ b/components/compiler/context.hpp @@ -13,14 +13,14 @@ namespace Compiler public: - Context() : mExtensions (0) {} + Context() : mExtensions (nullptr) {} virtual ~Context() = default; virtual bool canDeclareLocals() const = 0; ///< Is the compiler allowed to declare local variables? - void setExtensions (const Extensions *extensions = 0) + void setExtensions (const Extensions *extensions = nullptr) { mExtensions = extensions; } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 86208d7af6..e51c1809a9 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -53,7 +53,7 @@ const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(in if (row >= 0 && row < mFiles.size()) return mFiles.at(row); - return 0; + return nullptr; } ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) @@ -61,7 +61,7 @@ ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) if (row >= 0 && row < mFiles.count()) return mFiles.at(row); - return 0; + return nullptr; } const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(const QString &name) const { @@ -75,7 +75,7 @@ const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(co if (name.compare(file->fileProperty (fp).toString(), Qt::CaseInsensitive) == 0) return file; } - return 0; + return nullptr; } QModelIndex ContentSelectorModel::ContentModel::indexFromItem(const EsmFile *item) const diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 614eee2987..66863d7c61 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -28,7 +28,7 @@ namespace ContentSelectorModel FileProperty_GameFile = 6 }; - EsmFile(QString fileName = QString(), ModelItem *parent = 0); + EsmFile(QString fileName = QString(), ModelItem *parent = nullptr); // EsmFile(const EsmFile &); ~EsmFile() diff --git a/components/contentselector/model/modelitem.hpp b/components/contentselector/model/modelitem.hpp index e4ea7acc6a..a860b1891a 100644 --- a/components/contentselector/model/modelitem.hpp +++ b/components/contentselector/model/modelitem.hpp @@ -11,7 +11,7 @@ namespace ContentSelectorModel Q_OBJECT public: - ModelItem(ModelItem *parent = 0); + ModelItem(ModelItem *parent = nullptr); //ModelItem(const ModelItem *parent = 0); ~ModelItem(); diff --git a/components/contentselector/view/combobox.cpp b/components/contentselector/view/combobox.cpp index 1ef9f9bd75..742e236b30 100644 --- a/components/contentselector/view/combobox.cpp +++ b/components/contentselector/view/combobox.cpp @@ -9,7 +9,7 @@ ContentSelectorView::ComboBox::ComboBox(QWidget *parent) : mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore setValidator(mValidator); setEditable(true); - setCompleter(0); + setCompleter(nullptr); setEnabled (true); setInsertPolicy(QComboBox::NoInsert); diff --git a/components/contentselector/view/combobox.hpp b/components/contentselector/view/combobox.hpp index c57a5f1169..9af3c83af8 100644 --- a/components/contentselector/view/combobox.hpp +++ b/components/contentselector/view/combobox.hpp @@ -14,7 +14,7 @@ namespace ContentSelectorView Q_OBJECT public: - explicit ComboBox (QWidget *parent = 0); + explicit ComboBox (QWidget *parent = nullptr); void setPlaceholderText(const QString &text); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index f1058d510f..1b50f1e5e8 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -23,7 +23,7 @@ namespace ContentSelectorView public: - explicit ContentSelector(QWidget *parent = 0); + explicit ContentSelector(QWidget *parent = nullptr); QString currentFile() const; diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 307c08d95f..4ad8565483 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -451,7 +451,7 @@ static void getExecPath(char **argv) if(argv[0][0] == '/') snprintf(argv0, sizeof(argv0), "%s", argv[0]); - else if (getcwd(argv0, sizeof(argv0)) != NULL) + else if (getcwd(argv0, sizeof(argv0)) != nullptr) { cwdlen = strlen(argv0); snprintf(argv0+cwdlen, sizeof(argv0)-cwdlen, "/%s", argv[0]); diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp index 8e443def98..a13b83abd8 100644 --- a/components/detournavigator/findsmoothpath.cpp +++ b/components/detournavigator/findsmoothpath.cpp @@ -58,8 +58,8 @@ namespace DetourNavigator return path; // Get connected polygons - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; + const dtMeshTile* tile = nullptr; + const dtPoly* poly = nullptr; if (dtStatusFailed(navQuery.getAttachedNavMesh()->getTileAndPolyByRef(path[0], &tile, &poly))) return path; diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index dcdddce8a6..a351f8279c 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -158,10 +158,10 @@ namespace DetourNavigator { // Iterate over the path to find smooth path on the detail mesh surface. osg::Vec3f iterPos; - navMeshQuery.closestPointOnPoly(polygonPath.front(), start.ptr(), iterPos.ptr(), 0); + navMeshQuery.closestPointOnPoly(polygonPath.front(), start.ptr(), iterPos.ptr(), nullptr); osg::Vec3f targetPos; - navMeshQuery.closestPointOnPoly(polygonPath.back(), end.ptr(), targetPos.ptr(), 0); + navMeshQuery.closestPointOnPoly(polygonPath.back(), end.ptr(), targetPos.ptr(), nullptr); const float SLOP = 0.01f; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 132c869ad5..d06b509c5a 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -185,7 +185,7 @@ struct Cell CellRef &ref, bool &isDeleted, bool ignoreMoves = false, - MovedCellRef *mref = 0); + MovedCellRef *mref = nullptr); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. diff --git a/components/esm/variant.cpp b/components/esm/variant.cpp index c65eed5e09..6ff31dc442 100644 --- a/components/esm/variant.cpp +++ b/components/esm/variant.cpp @@ -16,11 +16,11 @@ namespace const uint32_t STTV = ESM::FourCC<'S','T','T','V'>::value; } -ESM::Variant::Variant() : mType (VT_None), mData (0) {} +ESM::Variant::Variant() : mType (VT_None), mData (nullptr) {} ESM::Variant::Variant(const std::string &value) { - mData = 0; + mData = nullptr; mType = VT_None; setType(VT_String); setString(value); @@ -28,7 +28,7 @@ ESM::Variant::Variant(const std::string &value) ESM::Variant::Variant(int value) { - mData = 0; + mData = nullptr; mType = VT_None; setType(VT_Long); setInteger(value); @@ -36,7 +36,7 @@ ESM::Variant::Variant(int value) ESM::Variant::Variant(float value) { - mData = 0; + mData = nullptr; mType = VT_None; setType(VT_Float); setFloat(value); @@ -51,7 +51,7 @@ ESM::Variant& ESM::Variant::operator= (const Variant& variant) { if (&variant!=this) { - VariantDataBase *newData = variant.mData ? variant.mData->clone() : 0; + VariantDataBase *newData = variant.mData ? variant.mData->clone() : nullptr; delete mData; @@ -63,7 +63,7 @@ ESM::Variant& ESM::Variant::operator= (const Variant& variant) } ESM::Variant::Variant (const Variant& variant) -: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : 0) +: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : nullptr) {} ESM::VarType ESM::Variant::getType() const @@ -254,7 +254,7 @@ void ESM::Variant::setType (VarType type) { if (type!=mType) { - VariantDataBase *newData = 0; + VariantDataBase *newData = nullptr; switch (type) { diff --git a/components/esm/variantimp.hpp b/components/esm/variantimp.hpp index e7ac722b1e..c1203ddf86 100644 --- a/components/esm/variantimp.hpp +++ b/components/esm/variantimp.hpp @@ -71,7 +71,7 @@ namespace ESM public: - VariantStringData (const VariantDataBase *data = 0); + VariantStringData (const VariantDataBase *data = nullptr); ///< Calling the constructor with an incompatible data type will result in a silent /// default initialisation. @@ -103,7 +103,7 @@ namespace ESM public: - VariantIntegerData (const VariantDataBase *data = 0); + VariantIntegerData (const VariantDataBase *data = nullptr); ///< Calling the constructor with an incompatible data type will result in a silent /// default initialisation. @@ -142,7 +142,7 @@ namespace ESM public: - VariantFloatData (const VariantDataBase *data = 0); + VariantFloatData (const VariantDataBase *data = nullptr); ///< Calling the constructor with an incompatible data type will result in a silent /// default initialisation. diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index cf27b7128d..4d3f3c3059 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -71,7 +71,7 @@ namespace ESMTerrain int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1; osg::ref_ptr land = getLand (cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; if (data) { min = std::numeric_limits::max(); @@ -119,7 +119,7 @@ namespace ESMTerrain } const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0; + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : nullptr; if (data) { normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; @@ -156,7 +156,7 @@ namespace ESMTerrain } const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : nullptr; if (data) { color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; @@ -207,9 +207,9 @@ namespace ESMTerrain for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData *heightData = 0; - const ESM::Land::LandData *normalData = 0; - const ESM::Land::LandData *colourData = 0; + const ESM::Land::LandData *heightData = nullptr; + const ESM::Land::LandData *normalData = nullptr; + const ESM::Land::LandData *colourData = nullptr; if (land) { heightData = land->getData(ESM::Land::DATA_VHGT); @@ -341,7 +341,7 @@ namespace ESMTerrain const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData *data = land ? land->getData(ESM::Land::DATA_VTEX) : 0; + const ESM::Land::LandData *data = land ? land->getData(ESM::Land::DATA_VTEX) : nullptr; if (data) { int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index d2802218f0..baab1b081f 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -32,7 +32,7 @@ namespace Files if (start != 0) mFile.seek(start); - setg(0,0,0); + setg(nullptr,nullptr,nullptr); mOrigin = start; } @@ -81,7 +81,7 @@ namespace Files mFile.seek(mOrigin+newPos); // Clear read pointers so underflow() gets called on the next read attempt. - setg(0, 0, 0); + setg(nullptr, nullptr, nullptr); return newPos; } @@ -97,7 +97,7 @@ namespace Files mFile.seek(mOrigin + pos); // Clear read pointers so underflow() gets called on the next read attempt. - setg(0, 0, 0); + setg(nullptr, nullptr, nullptr); return pos; } diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp index a90bda94bc..35ad93a8a6 100644 --- a/components/interpreter/runtime.cpp +++ b/components/interpreter/runtime.cpp @@ -6,7 +6,7 @@ namespace Interpreter { - Runtime::Runtime() : mContext (0), mCode (0), mCodeSize(0), mPC (0) {} + Runtime::Runtime() : mContext (nullptr), mCode (nullptr), mCodeSize(0), mPC (0) {} int Runtime::getPC() const { @@ -65,8 +65,8 @@ namespace Interpreter void Runtime::clear() { - mContext = 0; - mCode = 0; + mContext = nullptr; + mCode = nullptr; mCodeSize = 0; mStack.clear(); } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index bba7df702b..dc771f11f2 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -127,7 +127,7 @@ public: if(texture) state->applyTextureAttribute(0, texture); - osg::GLBufferObject* bufferobject = state->isVertexBufferObjectSupported() ? vbo->getOrCreateGLBufferObject(state->getContextID()) : 0; + osg::GLBufferObject* bufferobject = state->isVertexBufferObjectSupported() ? vbo->getOrCreateGLBufferObject(state->getContextID()) : nullptr; if (bufferobject) { state->bindVertexBufferObject(bufferobject); diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 2cb0ffc629..e2e1f92cf3 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -344,7 +344,7 @@ void Emitter::emitParticles(double dt) for (int i=0; icreateParticle(0); + osgParticle::Particle* P = getParticleSystem()->createParticle(nullptr); if (P) { mPlacer->place(P); diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index ff6fb04a65..dd34ed1a1f 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -151,7 +151,7 @@ namespace Resource image->setFileName(normalized); if (!checkSupported(image, filename)) { - static bool uncompress = (getenv("OPENMW_DECOMPRESS_TEXTURES") != 0); + static bool uncompress = (getenv("OPENMW_DECOMPRESS_TEXTURES") != nullptr); if (!uncompress) { Log(Debug::Error) << "Error loading " << filename << ": no S3TC texture compression support installed"; diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 6e309a7f77..5c4b511f0a 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -119,7 +119,7 @@ class GenericObjectCache : public osg::Referenced typename ObjectCacheMap::iterator itr = _objectCache.find(key); if (itr!=_objectCache.end()) return itr->second.first; - else return 0; + else return nullptr; } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 673e01f33c..2ebce241d5 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -111,11 +111,11 @@ namespace SceneUtil { public: CollectLightCallback() - : mLightManager(0) { } + : mLightManager(nullptr) { } CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) - , mLightManager(0) { } + , mLightManager(nullptr) { } META_Object(SceneUtil, SceneUtil::CollectLightCallback) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 82854cb2f8..56225868e3 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -50,7 +50,7 @@ namespace CursorDecompression traits->alpha = 8; traits->windowDecoration = false; traits->doubleBuffer = false; - traits->sharedContext = 0; + traits->sharedContext = nullptr; traits->pbuffer = true; osg::GraphicsContext::ScreenIdentifier si; @@ -267,7 +267,7 @@ namespace SDLUtil if (mCursorMap.find(name) != mCursorMap.end()) return; - static bool forceSoftwareDecompression = (getenv("OPENMW_DECOMPRESS_TEXTURES") != 0); + static bool forceSoftwareDecompression = (getenv("OPENMW_DECOMPRESS_TEXTURES") != nullptr); SurfaceUniquePtr (*decompressionFunction)(osg::ref_ptr, float); if (forceSoftwareDecompression || CursorDecompression::DXTCSupported) { diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index 0a19517001..ad7ecd9ae3 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -11,8 +11,8 @@ GraphicsWindowSDL2::~GraphicsWindowSDL2() } GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits *traits) - : mWindow(0) - , mContext(0) + : mWindow(nullptr) + , mContext(nullptr) , mValid(false) , mRealized(false) , mOwnsWindow(false) @@ -79,7 +79,7 @@ void GraphicsWindowSDL2::init() WindowData *inheritedWindowData = dynamic_cast(_traits->inheritedWindowData.get()); mWindow = inheritedWindowData ? inheritedWindowData->mWindow : nullptr; - mOwnsWindow = (mWindow == 0); + mOwnsWindow = (mWindow == nullptr); if(mOwnsWindow) { OSG_FATAL<<"Error: No SDL window provided."<(object)!=0; } + bool isSameKindAs(const Object* object) const override { return dynamic_cast(object)!=nullptr; } const char* libraryName() const override { return "osgViewer"; } const char* className() const override { return "GraphicsWindowSDL2"; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 7baea45c82..c113a629c5 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -63,7 +63,7 @@ QuadTreeNode::QuadTreeNode(QuadTreeNode* parent, ChildDirection direction, float , mCenter(center) { for (unsigned int i=0; i<4; ++i) - mNeighbours[i] = 0; + mNeighbours[i] = nullptr; } QuadTreeNode::~QuadTreeNode() diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index 1db1933202..23a108ee92 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -8,7 +8,7 @@ namespace Gui void AutoSizedWidget::notifySizeChange (MyGUI::Widget* w) { MyGUI::Widget * parent = w->getParent(); - if (parent != 0) + if (parent != nullptr) { if (mExpandDirection.isLeft()) { @@ -17,7 +17,7 @@ namespace Gui } w->setSize(getRequestedSize ()); - while (parent != 0) + while (parent != nullptr) { Box * b = dynamic_cast(parent); if (b) @@ -280,7 +280,7 @@ namespace Gui void HBox::initialiseOverride() { Base::initialiseOverride(); - MyGUI::Widget* client = 0; + MyGUI::Widget* client = nullptr; assignWidget(client, "Client"); setWidgetClient(client); } @@ -435,7 +435,7 @@ namespace Gui void VBox::initialiseOverride() { Base::initialiseOverride(); - MyGUI::Widget* client = 0; + MyGUI::Widget* client = nullptr; assignWidget(client, "Client"); setWidgetClient(client); } diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index bf24acf7e1..73ca94929f 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -7,9 +7,9 @@ namespace Gui { - MWList::MWList() : - mScrollView(0) - ,mClient(0) + MWList::MWList() + : mScrollView(nullptr) + , mClient(nullptr) , mItemHeight(0) { } @@ -19,7 +19,7 @@ namespace Gui Base::initialiseOverride(); assignWidget(mClient, "Client"); - if (mClient == 0) + if (mClient == nullptr) mClient = this; mScrollView = mClient->createWidgetReal( diff --git a/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp index 0c314ec7da..3373826793 100644 --- a/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp +++ b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp @@ -30,7 +30,7 @@ namespace Video struct AudioResampler { AudioResampler() - : mSwr(NULL) + : mSwr(nullptr) { } @@ -51,8 +51,8 @@ MovieAudioDecoder::MovieAudioDecoder(VideoState* videoState) , mFramePos(0) , mFrameSize(0) , mAudioClock(0.0) - , mDataBuf(NULL) - , mFrameData(NULL) + , mDataBuf(nullptr) + , mFrameData(nullptr) , mDataBufLen(0) , mFrame(av_frame_alloc()) , mGetNextPacket(true) @@ -125,7 +125,7 @@ void MovieAudioDecoder::setupFormat() inputSampleFormat, inputSampleRate, 0, // logging level offset - NULL); // log context + nullptr); // log context if(!mAudioResampler->mSwr) fail(std::string("Couldn't allocate SwrContext")); if(swr_init(mAudioResampler->mSwr) < 0) diff --git a/extern/osg-ffmpeg-videoplayer/videoplayer.cpp b/extern/osg-ffmpeg-videoplayer/videoplayer.cpp index c6544de9c6..28ac3b36c2 100644 --- a/extern/osg-ffmpeg-videoplayer/videoplayer.cpp +++ b/extern/osg-ffmpeg-videoplayer/videoplayer.cpp @@ -11,7 +11,7 @@ namespace Video { VideoPlayer::VideoPlayer() - : mState(NULL) + : mState(nullptr) { } @@ -87,13 +87,13 @@ void VideoPlayer::close() mState->deinit(); delete mState; - mState = NULL; + mState = nullptr; } } bool VideoPlayer::hasAudioStream() { - return mState && mState->audio_st != NULL; + return mState && mState->audio_st != nullptr; } void VideoPlayer::play() diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index c176fe0f29..5858d985a8 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -42,14 +42,14 @@ namespace Video { VideoState::VideoState() - : mAudioFactory(NULL) - , format_ctx(NULL) - , video_ctx(NULL) - , audio_ctx(NULL) + : mAudioFactory(nullptr) + , format_ctx(nullptr) + , video_ctx(nullptr) + , audio_ctx(nullptr) , av_sync_type(AV_SYNC_DEFAULT) - , audio_st(NULL) - , video_st(NULL), frame_last_pts(0.0) - , video_clock(0.0), sws_context(NULL), rgbaFrame(NULL), pictq_size(0) + , audio_st(nullptr) + , video_st(nullptr), frame_last_pts(0.0) + , video_clock(0.0), sws_context(nullptr), rgbaFrame(nullptr), pictq_size(0) , pictq_rindex(0), pictq_windex(0) , mSeekRequested(false) , mSeekPos(0) @@ -86,7 +86,7 @@ void PacketQueue::put(AVPacket *pkt) throw std::runtime_error("Failed to duplicate packet"); pkt1->pkt = *pkt; - pkt1->next = NULL; + pkt1->next = nullptr; this->mutex.lock (); @@ -112,7 +112,7 @@ int PacketQueue::get(AVPacket *pkt, VideoState *is) { this->first_pkt = pkt1->next; if(!this->first_pkt) - this->last_pkt = NULL; + this->last_pkt = nullptr; this->nb_packets--; this->size -= pkt1->pkt.size; @@ -141,15 +141,15 @@ void PacketQueue::clear() AVPacketList *pkt, *pkt1; this->mutex.lock(); - for(pkt = this->first_pkt; pkt != NULL; pkt = pkt1) + for(pkt = this->first_pkt; pkt != nullptr; pkt = pkt1) { pkt1 = pkt->next; if (pkt->pkt.data != flush_pkt.data) av_packet_unref(&pkt->pkt); av_freep(&pkt); } - this->last_pkt = NULL; - this->first_pkt = NULL; + this->last_pkt = nullptr; + this->first_pkt = nullptr; this->nb_packets = 0; this->size = 0; this->mutex.unlock (); @@ -296,14 +296,14 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) // Convert the image into RGBA format // TODO: we could do this in a pixel shader instead, if the source format // matches a commonly used format (ie YUV420P) - if(this->sws_context == NULL) + if(this->sws_context == nullptr) { int w = this->video_ctx->width; int h = this->video_ctx->height; this->sws_context = sws_getContext(w, h, this->video_ctx->pix_fmt, w, h, AV_PIX_FMT_RGBA, SWS_BICUBIC, - NULL, NULL, NULL); - if(this->sws_context == NULL) + nullptr, nullptr, nullptr); + if(this->sws_context == nullptr) throw std::runtime_error("Cannot initialize the conversion context!\n"); } @@ -587,7 +587,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) av_codec_set_pkt_timebase(this->audio_ctx, pFormatCtx->streams[stream_index]->time_base); #endif - if (avcodec_open2(this->audio_ctx, codec, NULL) < 0) + if (avcodec_open2(this->audio_ctx, codec, nullptr) < 0) { fprintf(stderr, "Unsupported codec!\n"); return -1; @@ -597,7 +597,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) { std::cerr << "No audio factory registered, can not play audio stream" << std::endl; avcodec_free_context(&this->audio_ctx); - this->audio_st = NULL; + this->audio_st = nullptr; return -1; } @@ -606,7 +606,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) { std::cerr << "Failed to create audio decoder, can not play audio stream" << std::endl; avcodec_free_context(&this->audio_ctx); - this->audio_st = NULL; + this->audio_st = nullptr; return -1; } mAudioDecoder->setupFormat(); @@ -624,7 +624,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) av_codec_set_pkt_timebase(this->video_ctx, pFormatCtx->streams[stream_index]->time_base); #endif - if (avcodec_open2(this->video_ctx, codec, NULL) < 0) + if (avcodec_open2(this->video_ctx, codec, nullptr) < 0) { fprintf(stderr, "Unsupported codec!\n"); return -1; @@ -653,7 +653,7 @@ void VideoState::init(std::shared_ptr inputstream, const std::stri if(!this->stream.get()) throw std::runtime_error("Failed to open video resource"); - AVIOContext *ioCtx = avio_alloc_context(NULL, 0, 0, this, istream_read, istream_write, istream_seek); + AVIOContext *ioCtx = avio_alloc_context(nullptr, 0, 0, this, istream_read, istream_write, istream_seek); if(!ioCtx) throw std::runtime_error("Failed to allocate AVIOContext"); this->format_ctx = avformat_alloc_context(); @@ -667,27 +667,27 @@ void VideoState::init(std::shared_ptr inputstream, const std::stri /// /// https://trac.ffmpeg.org/ticket/1357 /// - if(!this->format_ctx || avformat_open_input(&this->format_ctx, name.c_str(), NULL, NULL)) + if(!this->format_ctx || avformat_open_input(&this->format_ctx, name.c_str(), nullptr, nullptr)) { - if (this->format_ctx != NULL) + if (this->format_ctx != nullptr) { - if (this->format_ctx->pb != NULL) + if (this->format_ctx->pb != nullptr) { av_free(this->format_ctx->pb->buffer); - this->format_ctx->pb->buffer = NULL; + this->format_ctx->pb->buffer = nullptr; av_free(this->format_ctx->pb); - this->format_ctx->pb = NULL; + this->format_ctx->pb = nullptr; } } // "Note that a user-supplied AVFormatContext will be freed on failure." - this->format_ctx = NULL; + this->format_ctx = nullptr; av_free(ioCtx); throw std::runtime_error("Failed to open video input"); } // Retrieve stream information - if(avformat_find_stream_info(this->format_ctx, NULL) < 0) + if(avformat_find_stream_info(this->format_ctx, nullptr) < 0) throw std::runtime_error("Failed to retrieve stream information"); // Dump information about file onto standard error @@ -735,16 +735,16 @@ void VideoState::deinit() if(this->audio_ctx) avcodec_free_context(&this->audio_ctx); - this->audio_st = NULL; - this->audio_ctx = NULL; + this->audio_st = nullptr; + this->audio_ctx = nullptr; if(this->video_ctx) avcodec_free_context(&this->video_ctx); - this->video_st = NULL; - this->video_ctx = NULL; + this->video_st = nullptr; + this->video_ctx = nullptr; if(this->sws_context) sws_freeContext(this->sws_context); - this->sws_context = NULL; + this->sws_context = nullptr; if(this->format_ctx) { @@ -754,13 +754,13 @@ void VideoState::deinit() /// /// https://trac.ffmpeg.org/ticket/1357 /// - if (this->format_ctx->pb != NULL) + if (this->format_ctx->pb != nullptr) { av_free(this->format_ctx->pb->buffer); - this->format_ctx->pb->buffer = NULL; + this->format_ctx->pb->buffer = nullptr; av_free(this->format_ctx->pb); - this->format_ctx->pb = NULL; + this->format_ctx->pb = nullptr; } avformat_close_input(&this->format_ctx); } @@ -768,8 +768,8 @@ void VideoState::deinit() if (mTexture) { // reset Image separately, it's pointing to *this and there might still be outside references to mTexture - mTexture->setImage(NULL); - mTexture = NULL; + mTexture->setImage(nullptr); + mTexture = nullptr; } } diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index 984050cd6f..641fce04be 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -71,7 +71,7 @@ struct ExternalClock struct PacketQueue { PacketQueue() - : first_pkt(NULL), last_pkt(NULL), flushing(false), nb_packets(0), size(0) + : first_pkt(nullptr), last_pkt(nullptr), flushing(false), nb_packets(0), size(0) { } ~PacketQueue() { clear(); } From b0e3bd6ff931dd0c68e9de622b86d59333ce3744 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 29 Nov 2020 23:25:34 +0200 Subject: [PATCH 0036/2859] Fix crash caused by QStatusBar --- apps/opencs/view/world/tablebottombox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index f6b060a8f3..1b065da49e 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -95,7 +95,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto mStatus = new QLabel; - mStatusBar = new QStatusBar; + mStatusBar = new QStatusBar(this); mStatusBar->addWidget (mStatus); From db9c174ca848b7d7297150ca58498019f3f5d7c0 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 26 Nov 2020 21:04:56 +0300 Subject: [PATCH 0037/2859] Make NIF particle node handling more generic --- components/nif/data.cpp | 4 ++-- components/nif/data.hpp | 4 ++-- components/nif/niffile.cpp | 10 ++++++---- components/nif/node.hpp | 27 +++++---------------------- components/nif/record.hpp | 7 +++---- components/nif/recordptr.hpp | 6 ++---- components/nifosg/nifloader.cpp | 12 +++++------- 7 files changed, 25 insertions(+), 45 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 235825e4a6..415a013b3a 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -184,7 +184,7 @@ void NiLinesData::read(NIFStream *nif) } } -void NiAutoNormalParticlesData::read(NIFStream *nif) +void NiParticlesData::read(NIFStream *nif) { NiGeometryData::read(nif); @@ -216,7 +216,7 @@ void NiAutoNormalParticlesData::read(NIFStream *nif) void NiRotatingParticlesData::read(NIFStream *nif) { - NiAutoNormalParticlesData::read(nif); + NiParticlesData::read(nif); if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0) && nif->getBoolean()) nif->getQuaternions(rotations, vertices.size()); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 35f329573e..908313f504 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -70,7 +70,7 @@ struct NiLinesData : public NiGeometryData void read(NIFStream *nif) override; }; -class NiAutoNormalParticlesData : public NiGeometryData +class NiParticlesData : public NiGeometryData { public: int numParticles{0}; @@ -84,7 +84,7 @@ public: void read(NIFStream *nif) override; }; -class NiRotatingParticlesData : public NiAutoNormalParticlesData +class NiRotatingParticlesData : public NiParticlesData { public: void read(NIFStream *nif) override; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 7915908d1b..67c864f473 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -47,8 +47,9 @@ static std::map makeFactory() factory["NiTriShape"] = {&construct , RC_NiTriShape }; factory["NiTriStrips"] = {&construct , RC_NiTriStrips }; factory["NiLines"] = {&construct , RC_NiLines }; - factory["NiRotatingParticles"] = {&construct , RC_NiRotatingParticles }; - factory["NiAutoNormalParticles"] = {&construct , RC_NiAutoNormalParticles }; + factory["NiParticles"] = {&construct , RC_NiParticles }; + factory["NiRotatingParticles"] = {&construct , RC_NiParticles }; + factory["NiAutoNormalParticles"] = {&construct , RC_NiParticles }; factory["NiCamera"] = {&construct , RC_NiCamera }; factory["RootCollisionNode"] = {&construct , RC_RootCollisionNode }; factory["NiTexturingProperty"] = {&construct , RC_NiTexturingProperty }; @@ -99,8 +100,9 @@ static std::map makeFactory() factory["NiSkinData"] = {&construct , RC_NiSkinData }; factory["NiUVData"] = {&construct , RC_NiUVData }; factory["NiPosData"] = {&construct , RC_NiPosData }; - factory["NiRotatingParticlesData"] = {&construct , RC_NiRotatingParticlesData }; - factory["NiAutoNormalParticlesData"] = {&construct , RC_NiAutoNormalParticlesData }; + factory["NiParticlesData"] = {&construct , RC_NiParticlesData }; + factory["NiRotatingParticlesData"] = {&construct , RC_NiParticlesData }; + factory["NiAutoNormalParticlesData"] = {&construct , RC_NiParticlesData }; factory["NiSequenceStreamHelper"] = {&construct , RC_NiSequenceStreamHelper }; factory["NiSourceTexture"] = {&construct , RC_NiSourceTexture }; factory["NiSkinInstance"] = {&construct , RC_NiSkinInstance }; diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 0b958d2c25..58397bdca4 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -401,39 +401,22 @@ struct NiCamera : Node } }; -struct NiAutoNormalParticles : Node +struct NiParticles : NiGeometry { - NiAutoNormalParticlesDataPtr data; - + NiParticlesDataPtr data; void read(NIFStream *nif) override { Node::read(nif); data.read(nif); - nif->getInt(); // -1 - } - - void post(NIFFile *nif) override - { - Node::post(nif); - data.post(nif); - } -}; - -struct NiRotatingParticles : Node -{ - NiRotatingParticlesDataPtr data; - - void read(NIFStream *nif) override - { - Node::read(nif); - data.read(nif); - nif->getInt(); // -1 + skin.read(nif); + materialData.read(nif); } void post(NIFFile *nif) override { Node::post(nif); data.post(nif); + skin.post(nif); } }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index c5773643a0..2217c588ee 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -44,8 +44,7 @@ enum RecordType RC_NiTriShape, RC_NiTriStrips, RC_NiLines, - RC_NiRotatingParticles, - RC_NiAutoNormalParticles, + RC_NiParticles, RC_NiBSParticleNode, RC_NiCamera, RC_NiTexturingProperty, @@ -94,7 +93,7 @@ enum RecordType RC_NiUVData, RC_NiPosData, RC_NiRotatingParticlesData, - RC_NiAutoNormalParticlesData, + RC_NiParticlesData, RC_NiSequenceStreamHelper, RC_NiSourceTexture, RC_NiSkinInstance, @@ -119,7 +118,7 @@ enum RecordType RC_NiFloatInterpolator, RC_NiPoint3Interpolator, RC_NiBoolInterpolator, - RC_NiTransformInterpolator, + RC_NiTransformInterpolator }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index b40a175838..ed8f7ef6b9 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -138,8 +138,7 @@ class NiTriShapeData; class NiTriStripsData; class NiSkinInstance; class NiSourceTexture; -class NiRotatingParticlesData; -class NiAutoNormalParticlesData; +class NiParticlesData; class NiPalette; struct NiParticleModifier; struct NiLinesData; @@ -167,8 +166,7 @@ using NiTriStripsDataPtr = RecordPtrT; using NiLinesDataPtr = RecordPtrT; using NiSkinInstancePtr = RecordPtrT; using NiSourceTexturePtr = RecordPtrT; -using NiRotatingParticlesDataPtr = RecordPtrT; -using NiAutoNormalParticlesDataPtr = RecordPtrT; +using NiParticlesDataPtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 4bc48637ce..0db15237ec 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -638,7 +638,7 @@ namespace NifOsg } } - if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) + if (nifNode->recType == Nif::RC_NiParticles) handleParticleSystem(nifNode, node, composite, animflags); if (composite->getNumControllers() > 0) @@ -946,14 +946,12 @@ namespace NifOsg // Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors. void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { - const Nif::NiAutoNormalParticlesData *particledata = nullptr; - if(nifNode->recType == Nif::RC_NiAutoNormalParticles) - particledata = static_cast(nifNode)->data.getPtr(); - else if(nifNode->recType == Nif::RC_NiRotatingParticles) - particledata = static_cast(nifNode)->data.getPtr(); - else + const auto particleNode = static_cast(nifNode); + if (particleNode->data.empty()) return; + const Nif::NiParticlesData* particledata = particleNode->data.getPtr(); + osg::BoundingBox box; int i=0; From b416c52978131166259f73c0794ad80605660b22 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 2 Dec 2020 16:01:07 +0200 Subject: [PATCH 0038/2859] Changelog additions --- CHANGELOG.md | 2 ++ CHANGELOG_PR.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7257e23386..f0c6dd5ee9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5688: Water shader broken indoors with enable indoor shadows = false + Bug #5703: OpenMW-CS menu system crashing on XFCE Feature #390: 3rd person look "over the shoulder" Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container @@ -75,6 +76,7 @@ Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher Feature #5362: Show the soul gems' trapped soul in count dialog Feature #5445: Handle NiLines + Feature #5456: Basic collada animation support Feature #5457: Realistic diagonal movement Feature #5486: Fixes trainers to choose their training skills based on their base skill points Feature #5519: Code Patch tab in launcher diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 0a039ec3fa..6a16b76585 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -18,6 +18,7 @@ Known Issues: New Features: - Dialogue to split item stacks now displays the name of the trapped soul for stacks of soul gems (#5362) +- Basics of Collada animations are now supported via osgAnimation plugin (#5456) New Editor Features: - ? @@ -34,6 +35,7 @@ Bug Fixes: Editor Bug Fixes: - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) +- Flicker and crashing on XFCE4 fixed (#5703) Miscellaneous: - Prevent save-game bloating by using an appropriate fog texture format (#5108) From 6f0b90e6066bb2cbbba865958eda02803a726d75 Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 2 Dec 2020 23:03:10 +0000 Subject: [PATCH 0039/2859] documented that currently underwater shadows are mutually exclusive to refraction scale; to be fixed in follow up issue #5709; documentation fixes --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/water.cpp | 6 ++++-- docs/source/reference/modding/settings/water.rst | 5 ++++- files/shaders/water_fragment.glsl | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 63d3743a97..217f0b73ca 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -305,7 +305,7 @@ namespace MWRender mTerrain->setWorkQueue(mWorkQueue.get()); // water goes after terrain for correct waterculling order - mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); + mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); mCamera.reset(new Camera(mViewer->getCamera())); if (Settings::Manager::getBool("view over shoulder", "Camera")) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b569f1bfab..b9018e0a27 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -235,13 +235,14 @@ public: Refraction() { unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); - setRenderOrder(osg::Camera::PRE_RENDER); + setRenderOrder(osg::Camera::PRE_RENDER, 1); setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("RefractionCamera"); setCullCallback(new InheritViewPointCallback); + setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setNodeMask(Mask_RenderToTexture); @@ -284,7 +285,8 @@ public: attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); - SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); + if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709 + SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setScene(osg::Node* scene) diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index 3581efb991..b79daacb7d 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -59,7 +59,7 @@ 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. reflection detail --------------- +----------------- :Type: integer :Range: 0, 1, 2, 3, 4 @@ -111,3 +111,6 @@ This setting only applies if water shader is on and refractions are enabled. Not setting if off, there will still be small refractions caused by the water waves, which however do not cause such significant distortion. +.. warning:: + The `refraction scale` is currently mutually exclusive to underwater shadows. Setting this to any value except 1.0 + will cause underwater shadows to be disabled. This will be addressed in issue https://gitlab.com/OpenMW/openmw/-/issues/5709 diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 93ba156beb..d9b9463ad9 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -246,7 +246,7 @@ void main(void) float sunHeight = lVec.z; vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); vec3 lR = reflect(lVec, lNormal); - float lightScatter = shadow * clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); + float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * gl_LightSource[0].specular.xyz + vec3(rainRipple.w) * 0.2; gl_FragData[0].w = 1.0; #else From 5a4872393a77c0dc35f1f633a15143bf13e87dba Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 3 Dec 2020 12:57:57 +0100 Subject: [PATCH 0040/2859] Rework actor position reset. While solving the issue with invalid position being used under heavy load, I introduced a regression that prevented the position to be updated in case of teleport. Move the logic in its own function and decide in PhysicsSystem whether a reset is needed. --- apps/openmw/mwphysics/mtphysics.cpp | 25 ++++++++++--------------- apps/openmw/mwphysics/mtphysics.hpp | 4 +++- apps/openmw/mwphysics/physicssystem.cpp | 12 +++++++++++- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2d4009e7d8..bdffcef44f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -212,7 +212,7 @@ namespace MWPhysics thread.join(); } - const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. @@ -263,20 +263,6 @@ namespace MWPhysics if (mAdvanceSimulation) mWorldFrameData = std::make_unique(); - // we are asked to skip the simulation (load a savegame for instance) - // just return the actors' reference position without applying the movements - if (skipSimulation) - { - mMovementResults.clear(); - for (const auto& m : mActorsFrameData) - { - m.mActorRaw->setStandingOnPtr(nullptr); - m.mActorRaw->resetPosition(); - mMovementResults[m.mPtr] = m.mActorRaw->getWorldPosition(); - } - return mMovementResults; - } - if (mNumThreads == 0) { mMovementResults.clear(); @@ -311,6 +297,15 @@ namespace MWPhysics return mPreviousMovementResults; } + const PtrPositionList& PhysicsTaskScheduler::resetSimulation() + { + std::unique_lock lock(mSimulationMutex); + mMovementResults.clear(); + mPreviousMovementResults.clear(); + mActorsFrameData.clear(); + return mMovementResults; + } + void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const { MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index c061fe01da..f7a131ca26 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -32,7 +32,9 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skip, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + + const PtrPositionList& resetSimulation(); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 7aa1f59141..cdbad2cddd 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -676,7 +676,17 @@ namespace MWPhysics mTimeAccum -= numSteps * mPhysicsDt; - return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), skipSimulation, frameStart, frameNumber, stats); + if (skipSimulation) + { + for (auto& [_, actor] : mActors) + { + actor->resetPosition(); + actor->setStandingOnPtr(nullptr); + } + return mTaskScheduler->resetSimulation(); + } + + return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats); } std::vector PhysicsSystem::prepareFrameData(int numSteps) From 5ad297e6ff6f9fae76b3ae2c00d4bc312ed0a0e6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 3 Dec 2020 18:41:32 +0000 Subject: [PATCH 0041/2859] Guarantee glow updater regenerates shaders on completion Previously, it would edit the odd numbered stateset, then regenerate shaders for the even-numbered one, then edit the even numbered one, and regenerate shaders for the odd numbered one (or vice versa if it finished during an even numbered frame). This would leave one of the shader programs still trying to use the state that had been removed. --- components/sceneutil/util.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index a23f3f1090..2c0d8efa0c 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -112,6 +112,8 @@ void GlowUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) removeTexture(stateset); this->reset(); mDone = true; + // normally done in StateSetUpdater::operator(), but needs doing here so the shader visitor sees the right StateSet + mNode->setStateSet(stateset); mResourceSystem->getSceneManager()->recreateShaders(mNode); } if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow From 48f397f168ba32965e209c498643413a1533f468 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 3 Dec 2020 21:06:02 +0000 Subject: [PATCH 0042/2859] Remove existing shader when no longer required --- components/shader/shadervisitor.cpp | 20 ++++++++++++++++++++ components/shader/shadervisitor.hpp | 1 + 2 files changed, 21 insertions(+) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 10e9e606e5..e908b6aaa8 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -294,7 +294,10 @@ namespace Shader void ShaderVisitor::createProgram(const ShaderRequirements &reqs) { if (!reqs.mShaderRequired && !mForceShaders) + { + ensureFFP(*reqs.mNode); return; + } osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = nullptr; @@ -333,6 +336,19 @@ namespace Shader } } + void ShaderVisitor::ensureFFP(osg::Node& node) + { + if (!node.getStateSet() || !node.getStateSet()->getAttribute(osg::StateAttribute::PROGRAM)) + return; + osg::StateSet* writableStateSet = nullptr; + if (mAllowedToModifyStateSets) + writableStateSet = node.getStateSet(); + else + writableStateSet = getWritableStateSet(node); + + writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM); + } + bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) { bool useShader = reqs.mShaderRequired || mForceShaders; @@ -380,6 +396,8 @@ namespace Shader createProgram(reqs); } + else + ensureFFP(geometry); if (needPop) popRequirements(); @@ -414,6 +432,8 @@ namespace Shader morph->setSourceGeometry(sourceGeometry); } } + else + ensureFFP(drawable); if (needPop) popRequirements(); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 6b2353b662..6031dbfe6c 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -94,6 +94,7 @@ namespace Shader std::string mDefaultFsTemplate; void createProgram(const ShaderRequirements& reqs); + void ensureFFP(osg::Node& node); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); }; From 84e1a29700169afd0440ccce9f9061e22a69f711 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 27 Nov 2020 00:34:07 +0300 Subject: [PATCH 0043/2859] Make AI cast self-targeted spells at the ground (bug #5695) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aicast.cpp | 32 ++++++++++++++++-------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c6dd5ee9..2f38ce17f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5688: Water shader broken indoors with enable indoor shadows = false + Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Feature #390: 3rd person look "over the shoulder" Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 9ad7b4c56d..af3aac340e 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -46,26 +46,28 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac { return false; } + } - osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); - if (target.getClass().isActor()) - { - osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); - targetPos.z() += halfExtents.z() * 2 * 0.75f; - } + osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); + // If the target of an on-target spell is an actor that is not the caster + // the target position must be adjusted so that it's not casted at the actor's feet. + if (target != actor && target.getClass().isActor()) + { + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); + targetPos.z() += halfExtents.z() * 2 * 0.75f; + } - osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); - osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - actorPos.z() += halfExtents.z() * 2 * 0.75f; + osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + actorPos.z() += halfExtents.z() * 2 * 0.75f; - osg::Vec3f dir = targetPos - actorPos; + osg::Vec3f dir = targetPos - actorPos; - bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f)); - turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f)); + bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f)); + turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f)); - if (!turned) - return false; - } + if (!turned) + return false; // Check if the actor is already casting another spell bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor); From 078de86e608b8fe9c1dca53d83fed7bc53a852cf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 4 Dec 2020 18:34:51 +0100 Subject: [PATCH 0044/2859] Use range based for loops and auto --- apps/openmw/mwgui/spellmodel.cpp | 12 ++--- apps/openmw/mwworld/containerstore.cpp | 64 +++++++++++--------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 136547c4c1..78b9171e59 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -48,9 +48,9 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - for (unsigned int i = 0; i < effects.mList.size(); ++i) + for (const auto& effect : effects.mList) { - short effectId = effects.mList[i].mEffectID; + short effectId = effect.mEffectID; if (effectId != -1) { @@ -59,14 +59,14 @@ namespace MWGui std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId); std::string fullEffectName = wm->getGameSettingString(effectIDStr, ""); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effects.mList[i].mSkill != -1) + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effect.mSkill != -1) { - fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effects.mList[i].mSkill], ""); + fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effect.mSkill], ""); } - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effects.mList[i].mAttribute != -1) + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effect.mAttribute != -1) { - fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effects.mList[i].mAttribute], ""); + fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], ""); } std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName); diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 17c35489f5..635485dde8 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -26,7 +26,7 @@ namespace void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell) { auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts(); - for(const MWWorld::Ptr& ptr : store) + for(const auto&& ptr : store) { const std::string& script = ptr.getClass().getScript(ptr); if(!script.empty()) @@ -43,13 +43,10 @@ namespace { float sum = 0; - for (typename MWWorld::CellRefList::List::const_iterator iter ( - cellRefList.mList.begin()); - iter!=cellRefList.mList.end(); - ++iter) + for (const auto& iter : cellRefList.mList) { - if (iter->mData.getCount()>0) - sum += iter->mData.getCount()*iter->mBase->mData.mWeight; + if (iter.mData.getCount()>0) + sum += iter.mData.getCount()*iter.mBase->mData.mWeight; } return sum; @@ -62,12 +59,11 @@ namespace store->resolve(); std::string id2 = Misc::StringUtils::lowerCase (id); - for (typename MWWorld::CellRefList::List::iterator iter (list.mList.begin()); - iter!=list.mList.end(); ++iter) + for (auto& iter : list.mList) { - if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount()) + if (Misc::StringUtils::ciEqual(iter.mBase->mId, id2) && iter.mData.getCount()) { - MWWorld::Ptr ptr (&*iter, nullptr); + MWWorld::Ptr ptr (&iter, nullptr); ptr.setContainerStore (store); return ptr; } @@ -81,7 +77,7 @@ MWWorld::ResolutionListener::~ResolutionListener() { if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty()) { - for(const MWWorld::Ptr& ptr : mStore) + for(const auto&& ptr : mStore) ptr.getRefData().setCount(0); mStore.fillNonRandom(mStore.mPtr.get()->mBase->mInventory, "", mStore.mSeed); addScripts(mStore, mStore.mPtr.mCell); @@ -127,15 +123,14 @@ template void MWWorld::ContainerStore::storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const { - for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); - iter!=collection.mList.end(); ++iter) + for (const auto& iter : collection.mList) { - if (iter->mData.getCount() == 0) + if (iter.mData.getCount() == 0) continue; ESM::ObjectState state; - storeState (*iter, state); + storeState (iter, state); if (equipable) - storeEquipmentState(*iter, index, inventory); + storeEquipmentState(iter, index, inventory); inventory.mItems.push_back (state); ++index; } @@ -188,7 +183,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() int MWWorld::ContainerStore::count(const std::string &id) const { int total=0; - for (const auto& iter : *this) + for (const auto&& iter : *this) if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) total += iter.getRefData().getCount(); return total; @@ -430,14 +425,14 @@ void MWWorld::ContainerStore::rechargeItems(float duration) updateRechargingItems(); mRechargingItemsUpToDate = true; } - for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it) + for (auto& it : mRechargingItems) { - if (!MWMechanics::rechargeItem(*it->first, it->second, duration)) + if (!MWMechanics::rechargeItem(*it.first, it.second, duration)) continue; // attempt to restack when fully recharged - if (it->first->getCellRef().getEnchantmentCharge() == it->second) - it->first = restack(*it->first); + if (it.first->getCellRef().getEnchantmentCharge() == it.second) + it.first = restack(*it.first); } } @@ -481,9 +476,9 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const bool MWWorld::ContainerStore::hasVisibleItems() const { - for (auto iter(begin()); iter != end(); ++iter) + for (const auto&& iter : *this) { - if (iter->getClass().showsInInventory(*iter)) + if (iter.getClass().showsInInventory(iter)) return true; } @@ -601,8 +596,8 @@ void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const s void MWWorld::ContainerStore::clear() { - for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) - iter->getRefData().setCount (0); + for (auto&& iter : *this) + iter.getRefData().setCount (0); flagAsModified(); mModified = true; @@ -623,7 +618,7 @@ void MWWorld::ContainerStore::resolve() { if(!mResolved && !mPtr.isEmpty()) { - for(const MWWorld::Ptr& ptr : *this) + for(const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Seed seed{mSeed}; fill(mPtr.get()->mBase->mInventory, "", seed); @@ -644,7 +639,7 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() } if(!mResolved && !mPtr.isEmpty()) { - for(const MWWorld::Ptr& ptr : *this) + for(const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Seed seed{mSeed}; fill(mPtr.get()->mBase->mInventory, "", seed); @@ -727,10 +722,10 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) { MWWorld::Ptr item; int itemHealth = 1; - for (MWWorld::ContainerStoreIterator iter = begin(); iter != end(); ++iter) + for (auto&& iter : *this) { - int iterHealth = iter->getClass().hasItemHealth(*iter) ? iter->getClass().getItemHealth(*iter) : 1; - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) + int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1; + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) { // Prefer the stack with the lowest remaining uses // Try to get item with zero durability only if there are no other items found @@ -738,7 +733,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) (iterHealth > 0 && iterHealth < itemHealth) || (itemHealth <= 0 && iterHealth > 0)) { - item = *iter; + item = iter; itemHealth = iterHealth; } } @@ -867,11 +862,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) mResolved = true; int index = 0; - for (std::vector::const_iterator - iter (inventory.mItems.begin()); iter!=inventory.mItems.end(); ++iter) + for (const ESM::ObjectState& state : inventory.mItems) { - const ESM::ObjectState& state = *iter; - int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID); int thisIndex = index++; From 7843dad35d772a1c0835dd8eb7dba4040ac3d7ff Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 5 Dec 2020 01:09:43 +0100 Subject: [PATCH 0045/2859] Don't let the actor "nowhere" after a teleport but move them in their place. This solve the problem where after loading, an empty frame was rendered because the player is "nowhere". --- apps/openmw/mwphysics/mtphysics.cpp | 9 ++++++++- apps/openmw/mwphysics/mtphysics.hpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 9 +-------- apps/openmw/mwphysics/physicssystem.hpp | 3 ++- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index bdffcef44f..50cfd808ff 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -297,12 +297,19 @@ namespace MWPhysics return mPreviousMovementResults; } - const PtrPositionList& PhysicsTaskScheduler::resetSimulation() + const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { std::unique_lock lock(mSimulationMutex); mMovementResults.clear(); mPreviousMovementResults.clear(); mActorsFrameData.clear(); + + for (const auto& [_, actor] : actors) + { + actor->resetPosition(); + actor->setStandingOnPtr(nullptr); + mMovementResults[actor->getPtr()] = actor->getWorldPosition(); + } return mMovementResults; } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index f7a131ca26..3a761829b5 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -34,7 +34,7 @@ namespace MWPhysics /// @return new position of each actor const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - const PtrPositionList& resetSimulation(); + const PtrPositionList& resetSimulation(const ActorMap& actors); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index cdbad2cddd..b750970d9c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -677,14 +677,7 @@ namespace MWPhysics mTimeAccum -= numSteps * mPhysicsDt; if (skipSimulation) - { - for (auto& [_, actor] : mActors) - { - actor->resetPosition(); - actor->setStandingOnPtr(nullptr); - } - return mTaskScheduler->resetSimulation(); - } + return mTaskScheduler->resetSimulation(mActors); return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 03ae789934..4137b1e098 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -57,6 +57,8 @@ namespace MWPhysics class Actor; class PhysicsTaskScheduler; + using ActorMap = std::map>; + struct ContactPoint { MWWorld::Ptr mObject; @@ -265,7 +267,6 @@ namespace MWPhysics std::set mAnimatedObjects; // stores pointers to elements in mObjects - using ActorMap = std::map>; ActorMap mActors; using HeightFieldMap = std::map, HeightField *>; From 5734551ff3dfcc7d157a4eebfa724f793ac3478e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 4 Dec 2020 11:17:49 +0100 Subject: [PATCH 0046/2859] Add time to logs. Redirect OSG log to OpenMW log. --- apps/openmw/main.cpp | 37 ++++++++++++++++++++++++++++++++++ components/debug/debugging.cpp | 34 ++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index d3d9849010..3a6dc526a8 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -223,6 +223,42 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return true; } +namespace +{ + class OSGLogHandler : public osg::NotifyHandler + { + void notify(osg::NotifySeverity severity, const char* msg) override + { + // Copy, because osg logging is not thread safe. + std::string msgCopy(msg); + if (msgCopy.empty()) + return; + + Debug::Level level; + switch (severity) + { + case osg::ALWAYS: + case osg::FATAL: + level = Debug::Error; + break; + case osg::WARN: + case osg::NOTICE: + level = Debug::Warning; + break; + case osg::INFO: + level = Debug::Info; + break; + case osg::DEBUG_INFO: + case osg::DEBUG_FP: + default: + level = Debug::Debug; + } + std::string_view s(msgCopy); + Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s); + } + }; +} + int runApplication(int argc, char *argv[]) { #ifdef __APPLE__ @@ -231,6 +267,7 @@ int runApplication(int argc, char *argv[]) setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); #endif + osg::setNotifyHandler(new OSGLogHandler()); Files::ConfigurationManager cfgMgr; std::unique_ptr engine; engine.reset(new OMW::Engine(cfgMgr)); diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index c4f3af3072..88219dcbe1 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -1,5 +1,7 @@ #include "debugging.hpp" +#include + #include #ifdef _WIN32 @@ -60,15 +62,40 @@ namespace Debug std::streamsize DebugOutputBase::write(const char *str, std::streamsize size) { + if (size <= 0) + return size; + std::string_view msg{str, size_t(size)}; + // Skip debug level marker Level level = getLevelMarker(str); if (level != NoLevel) + msg = msg.substr(1); + + char prefix[32]; + int prefixSize; { - writeImpl(str+1, size-1, level); - return size; + prefix[0] = '['; + uint64_t ms = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + std::time_t t = ms / 1000; + prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", std::localtime(&t)) + 1; + char levelLetter = " EWIVD*"[int(level)]; + prefixSize += snprintf(prefix + prefixSize, sizeof(prefix) - prefixSize, + ".%03u %c] ", static_cast(ms % 1000), levelLetter); + } + + while (!msg.empty()) + { + if (msg[0] == 0) + break; + size_t lineSize = 1; + while (lineSize < msg.size() && msg[lineSize - 1] != '\n') + lineSize++; + writeImpl(prefix, prefixSize, level); + writeImpl(msg.data(), lineSize, level); + msg = msg.substr(lineSize); } - writeImpl(str, size, NoLevel); return size; } @@ -172,6 +199,7 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c // Restore cout and cerr std::cout.rdbuf(cout_rdbuf); std::cerr.rdbuf(cerr_rdbuf); + Debug::CurrentDebugLevel = Debug::NoLevel; return ret; } From b79f6ac808e71f7ee1fed5cbe46ce718491fc51c Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 6 Dec 2020 13:20:37 +0100 Subject: [PATCH 0047/2859] Force reset position of actor after snapping to the ground. Otherwise the interpolation calculation would kick in and make the actor goes upward if the spawn point is higher than summoner or downward if lower. The actor would then either jump or fall through terrain. --- apps/openmw/mwworld/worldimp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 224925dd55..05051f3003 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1351,6 +1351,8 @@ namespace MWWorld } moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); + if (force) // force physics to use the new position + mPhysics->getActor(ptr)->resetPosition(); } void World::fixPosition() From 08e73a09ec41fc791166473acdf91a3c0b39c0bb Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 6 Dec 2020 13:24:42 +0100 Subject: [PATCH 0048/2859] Make the code more compact by mean of std::min / max and ternary operator. --- apps/openmw/mwworld/worldimp.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 05051f3003..265e45d37f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1332,22 +1332,15 @@ namespace MWWorld return; } - float terrainHeight = -std::numeric_limits::max(); - if (ptr.getCell()->isExterior()) - terrainHeight = getTerrainHeightAt(pos); - - if (pos.z() < terrainHeight) - pos.z() = terrainHeight; - - pos.z() += 20; // place slightly above. will snap down to ground with code below + const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits::max(); + pos.z() = std::max(pos.z(), terrainHeight + 20); // place slightly above terrain. will snap down to ground with code below // We still should trace down dead persistent actors - they do not use the "swimdeath" animation. bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()); if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr))) { osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits); - if (traced.z() < pos.z()) - pos.z() = traced.z(); + pos.z() = std::min(pos.z(), traced.z()); } moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); From c6c02a6f16384533d1c09a32bb81e5f0ad554cb4 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 6 Dec 2020 13:26:43 +0100 Subject: [PATCH 0049/2859] Remove useless code. ipos is already initialized with the correct values. --- apps/openmw/mwworld/worldimp.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 265e45d37f..49e6a729a8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1444,17 +1444,11 @@ namespace MWWorld ipos.pos[1] = spawnPoint.y(); ipos.pos[2] = spawnPoint.z(); - if (!referenceObject.getClass().isActor()) - { - ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0]; - ipos.rot[1] = referenceObject.getRefData().getPosition().rot[1]; - } - else + if (referenceObject.getClass().isActor()) { ipos.rot[0] = 0; ipos.rot[1] = 0; } - ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2]; MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); adjustPosition(placed, true); // snap to ground From 6bfdf0e57fd67142fcaad37388b002ad57b6770a Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Fri, 4 Dec 2020 22:21:23 +0100 Subject: [PATCH 0050/2859] Add more information on mouse over level --- apps/openmw/mwgui/statswindow.cpp | 9 +++++++++ apps/openmw/mwmechanics/npcstats.cpp | 5 +++++ apps/openmw/mwmechanics/npcstats.hpp | 2 ++ files/mygui/openmw_tooltips.layout | 6 ++++++ 4 files changed, 22 insertions(+) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 2a3e2cd85c..e02b2f45f1 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -335,6 +335,15 @@ namespace MWGui { int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); + + std::string detail; + for (int i = 0; i < ESM::Attribute::Length; ++i) + { + if (auto increase = PCstats.getLevelUpAttributeIncrease(i)) + detail += (detail.empty() ? "" : "\n") + ESM::Attribute::sAttributeNames[i] + " x" + MyGUI::utility::toString(increase); + } + if (!detail.empty()) + levelWidget->setUserString("Caption_LevelDetailText", detail); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 5d19368bf6..71453cd07c 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -322,6 +322,11 @@ void MWMechanics::NpcStats::updateHealth() setHealth(floor(0.5f * (strength + endurance))); } +int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const +{ + return mSkillIncreases[attribute]; +} + int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const { int num = mSkillIncreases[attribute]; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 9bd8e20ad7..cab52cb281 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -87,6 +87,8 @@ namespace MWMechanics int getLevelProgress() const; + int getLevelUpAttributeIncrease(int attribute) const; + int getLevelupAttributeMultiplier(int attribute) const; int getSkillIncreasesForSpecialization(int spec) const; diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index 70246fa4e9..59050f932b 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -267,6 +267,12 @@ + + + + + + From 2d3d22025a5f278ffa350fd6d69dd8bb0dab59eb Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Fri, 4 Dec 2020 22:28:36 +0100 Subject: [PATCH 0051/2859] Avoid height for empty message in AutoSizedTextBox --- components/widgets/box.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index 23a108ee92..3e8f62b4ba 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -32,7 +32,7 @@ namespace Gui MyGUI::IntSize AutoSizedTextBox::getRequestedSize() { - return getTextSize(); + return getCaption().empty() ? MyGUI::IntSize{0, 0} : getTextSize(); } void AutoSizedTextBox::setCaption(const MyGUI::UString& _value) From 86c31ded533a737d681297e63f5917060993a9f3 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Mon, 7 Dec 2020 14:56:29 +0100 Subject: [PATCH 0052/2859] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c6dd5ee9..e1e71256a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5703: OpenMW-CS menu system crashing on XFCE Feature #390: 3rd person look "over the shoulder" + Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container Feature #4894: Consider actors as obstacles for pathfinding From 807367ca3f771a99b4ab270c31f6ae9cbfdb3360 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 7 Dec 2020 19:09:58 +0400 Subject: [PATCH 0053/2859] Mark mock methods as overrides (requires GTest 1.10) --- apps/openmw_test_suite/CMakeLists.txt | 4 ++-- .../nifloader/testbulletnifloader.cpp | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index cd2d2e80a6..5658a5eefc 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -1,5 +1,5 @@ -find_package(GTest REQUIRED) -find_package(GMock REQUIRED) +find_package(GTest 1.10 REQUIRED) +find_package(GMock 1.10 REQUIRED) if (GTEST_FOUND AND GMOCK_FOUND) include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 7da8f0fd5b..355bfdb1c0 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -293,22 +293,22 @@ namespace struct NifFileMock : Nif::File { - MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t)); - MOCK_CONST_METHOD0(numRecords, std::size_t ()); - MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t)); - MOCK_CONST_METHOD0(numRoots, std::size_t ()); - MOCK_CONST_METHOD1(getString, std::string (uint32_t)); - MOCK_METHOD1(setUseSkinning, void (bool)); - MOCK_CONST_METHOD0(getUseSkinning, bool ()); - MOCK_CONST_METHOD0(getFilename, std::string ()); - MOCK_CONST_METHOD0(getVersion, unsigned int ()); - MOCK_CONST_METHOD0(getUserVersion, unsigned int ()); - MOCK_CONST_METHOD0(getBethVersion, unsigned int ()); + MOCK_METHOD(Nif::Record*, getRecord, (std::size_t), (const, override)); + MOCK_METHOD(std::size_t, numRecords, (), (const, override)); + MOCK_METHOD(Nif::Record*, getRoot, (std::size_t), (const, override)); + MOCK_METHOD(std::size_t, numRoots, (), (const, override)); + MOCK_METHOD(std::string, getString, (uint32_t), (const, override)); + MOCK_METHOD(void, setUseSkinning, (bool), (override)); + MOCK_METHOD(bool, getUseSkinning, (), (const, override)); + MOCK_METHOD(std::string, getFilename, (), (const, override)); + MOCK_METHOD(unsigned int, getVersion, (), (const, override)); + MOCK_METHOD(unsigned int, getUserVersion, (), (const, override)); + MOCK_METHOD(unsigned int, getBethVersion, (), (const, override)); }; struct RecordMock : Nif::Record { - MOCK_METHOD1(read, void (Nif::NIFStream *nif)); + MOCK_METHOD(void, read, (Nif::NIFStream *nif), (override)); }; struct TestBulletNifLoader : Test From e62fff5f2eaae841a4378e6bf18fb6a9b50b3d16 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 7 Dec 2020 19:04:32 +0100 Subject: [PATCH 0054/2859] Add a setting to disable graphical herbalism --- apps/openmw/engine.cpp | 4 ++-- apps/openmw/mwclass/container.cpp | 10 +++++++++- apps/openmw/mwclass/container.hpp | 5 +++++ files/settings-default.cfg | 3 +++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8092bb770b..8f23f710dd 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -386,8 +386,6 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mNewGame (false) , mCfgMgr(configurationManager) { - MWClass::registerClasses(); - SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR; @@ -838,6 +836,8 @@ void OMW::Engine::go() std::string settingspath; settingspath = loadSettings (settings); + MWClass::registerClasses(); + // Create encoder mEncoder = new ToUTF8::Utf8Encoder(mEncoding); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index a27e3debdd..5ae22cf05f 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -59,6 +60,11 @@ namespace MWClass return *this; } + Container::Container() + { + mHarvestEnabled = Settings::Manager::getBool("enable graphic herbalism", "Game"); + } + void Container::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) @@ -72,8 +78,10 @@ namespace MWClass } } - bool canBeHarvested(const MWWorld::ConstPtr& ptr) + bool Container::canBeHarvested(const MWWorld::ConstPtr& ptr) const { + if (!mHarvestEnabled) + return false; const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (animation == nullptr) return false; diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 57dbf0c76c..2dc0c06cae 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -30,11 +30,16 @@ namespace MWClass class Container : public MWWorld::Class { + bool mHarvestEnabled; + void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + bool canBeHarvested(const MWWorld::ConstPtr& ptr) const; + public: + Container(); void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 0b307ba09d..fbf646b38e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -361,6 +361,9 @@ trainers training skills based on base skill = false # Make stealing items from NPCs that were knocked down possible during combat. always allow stealing from knocked out actors = false +# Enables visually harvesting plants for models that support it. +enable graphic herbalism = true + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). From 275b9aea4d6d245b69eab91401ba11b8d764a756 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 7 Dec 2020 21:56:41 +0100 Subject: [PATCH 0055/2859] rename setting --- apps/openmw/mwclass/container.cpp | 2 +- files/settings-default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 5ae22cf05f..28305c394e 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -62,7 +62,7 @@ namespace MWClass Container::Container() { - mHarvestEnabled = Settings::Manager::getBool("enable graphic herbalism", "Game"); + mHarvestEnabled = Settings::Manager::getBool("graphic herbalism", "Game"); } void Container::ensureCustomData (const MWWorld::Ptr& ptr) const diff --git a/files/settings-default.cfg b/files/settings-default.cfg index fbf646b38e..c8805faa39 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -362,7 +362,7 @@ trainers training skills based on base skill = false always allow stealing from knocked out actors = false # Enables visually harvesting plants for models that support it. -enable graphic herbalism = true +graphic herbalism = true [General] From 242dd8d496b8ec819edb8ace9ff0c107e142de5c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 8 Dec 2020 11:06:30 +0400 Subject: [PATCH 0056/2859] Use a logging system instead of cout for a couple of missing messages --- apps/openmw/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3a6dc526a8..aca0505920 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -153,13 +153,13 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.mergeComposingVariables(variables, composingVariables, desc); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); - std::cout << v.describe() << std::endl; + Log(Debug::Info) << v.describe(); engine.setGrabMouse(!variables["no-grab"].as()); // Font encoding settings std::string encoding(variables["encoding"].as().toStdString()); - std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; + Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); engine.setEncoding(ToUTF8::calculateEncoding(encoding)); // directory settings From dc7b48e92ed2ddfaed796df236811bfc5f7005d8 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Feb 2019 11:30:16 +0400 Subject: [PATCH 0057/2859] Generate physics collisions for projectiles (bug #3372) Remove redundant now mHit field --- CHANGELOG.md | 1 + apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwphysics/actor.cpp | 2 +- .../closestnotmeconvexresultcallback.cpp | 22 +++- .../closestnotmerayresultcallback.cpp | 14 +- .../closestnotmerayresultcallback.hpp | 3 +- apps/openmw/mwphysics/object.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 54 +++++++- apps/openmw/mwphysics/physicssystem.hpp | 22 +++- apps/openmw/mwphysics/projectile.cpp | 64 +++++++++ apps/openmw/mwphysics/projectile.hpp | 68 ++++++++++ apps/openmw/mwphysics/trace.cpp | 3 + apps/openmw/mwworld/projectilemanager.cpp | 123 ++++++++++++++++-- apps/openmw/mwworld/projectilemanager.hpp | 7 + apps/openmw/mwworld/worldimp.cpp | 5 + apps/openmw/mwworld/worldimp.hpp | 2 + 17 files changed, 372 insertions(+), 26 deletions(-) create mode 100644 apps/openmw/mwphysics/projectile.cpp create mode 100644 apps/openmw/mwphysics/projectile.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f38ce17f7..e545156760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug #2473: Unable to overstock merchants Bug #2798: Mutable ESM records Bug #2976 [reopened]: Issues combining settings from the command line and both config files + Bug #3372: Projectiles and magic bolts go through moving targets Bug #3676: NiParticleColorModifier isn't applied properly Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #3789: Crash in visitEffectSources while in battle diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d943c7836b..6b6134dfb9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -66,13 +66,13 @@ add_openmw_dir (mwworld cells localscripts customdata inventorystore ptr actionopen actionread actionharvest actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager + contentloader esmloader actiontrap cellreflist cellref weather projectilemanager cellpreloader datetimemanager ) add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback - contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver + contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile closestnotmeconvexresultcallback raycasting mtphysics ) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index d4f1d2f8ab..c4b3771af0 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -610,6 +610,8 @@ namespace MWBase virtual bool isPlayerInJail() const = 0; + virtual void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) = 0; + virtual void rest(double hours) = 0; virtual void rechargeItems(double duration, bool activeOnly) = 0; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 0e557378df..77b07cde53 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -70,7 +70,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); - mCollisionObject->setUserPointer(static_cast(this)); + mCollisionObject->setUserPointer(this); updateRotation(); updateScale(); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index ddfdb8a42f..3b97a3a341 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -2,6 +2,14 @@ #include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "collisiontype.hpp" +#include "projectile.hpp" + namespace MWPhysics { ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot) @@ -10,11 +18,23 @@ namespace MWPhysics { } - btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); + if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) + { + Projectile* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); + int projectileId = projectileHolder->getProjectileId(); + PtrHolder* targetHolder = static_cast(mMe->getUserPointer()); + const MWWorld::Ptr target = targetHolder->getPtr(); + + osg::Vec3f pos = Misc::Convert::makeOsgVec3f(convexResult.m_hitPointLocal); + MWBase::Environment::get().getWorld()->manualProjectileHit(projectileId, target, pos); + return btScalar(1); + } + btVector3 hitNormalWorld; if (normalInWorldSpace) hitNormalWorld = convexResult.m_hitNormalLocal; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 86763a7933..43e1d5c628 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -6,13 +6,14 @@ #include "../mwworld/class.hpp" +#include "projectile.hpp" #include "ptrholder.hpp" namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to, int projId) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me), mTargets(targets) + , mMe(me), mTargets(targets), mProjectileId(projId) { } @@ -29,6 +30,15 @@ namespace MWPhysics return 1.f; } } + + if (mProjectileId >= 0) + { + PtrHolder* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); + Projectile* projectile = dynamic_cast(holder); + if (projectile && projectile->getProjectileId() == mProjectileId) + return 1.f; + } + return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index 23d52998ca..d27d64c535 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -12,12 +12,13 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to); + ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to, int projId=-1); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: const btCollisionObject* mMe; const std::vector mTargets; + const int mProjectileId; }; } diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index c822bbcbe0..910f7bf15c 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -24,7 +24,7 @@ namespace MWPhysics mCollisionObject.reset(new btCollisionObject); mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); - mCollisionObject->setUserPointer(static_cast(this)); + mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index b750970d9c..8933c9d025 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -46,6 +46,8 @@ #include "collisiontype.hpp" #include "actor.hpp" + +#include "projectile.hpp" #include "trace.h" #include "object.hpp" #include "heightfield.hpp" @@ -64,6 +66,7 @@ namespace MWPhysics , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) + , mProjectileId(0) , mWaterHeight(0) , mWaterEnabled(false) , mParentNode(parentNode) @@ -113,6 +116,10 @@ namespace MWPhysics mObjects.clear(); mActors.clear(); + for (ProjectileMap::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + { + delete it->second; + } } void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) @@ -248,7 +255,7 @@ namespace MWPhysics return 0.f; } - RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const + PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const { if (from == to) { @@ -285,7 +292,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); + ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, projId); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; @@ -298,7 +305,17 @@ namespace MWPhysics result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld); if (PtrHolder* ptrHolder = static_cast(resultCallback.m_collisionObject->getUserPointer())) + { result.mHitObject = ptrHolder->getPtr(); + + if (group == CollisionType_Projectile) + { + Projectile* projectile = static_cast(ptrHolder); + result.mProjectileId = projectile->getProjectileId(); + } + else + result.mProjectileId = -1; + } } return result; } @@ -502,6 +519,19 @@ namespace MWPhysics } } + void PhysicsSystem::removeProjectile(const int projectileId) + { + ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); + if (foundProjectile != mProjectiles.end()) + { + delete foundProjectile->second; + mProjectiles.erase(foundProjectile); + + if (projectileId == mProjectileId) + mProjectileId--; + } + } + void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { ObjectMap::iterator found = mObjects.find(old); @@ -572,6 +602,17 @@ namespace MWPhysics } } + void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) + { + ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); + if (foundProjectile != mProjectiles.end()) + { + foundProjectile->second->setPosition(position); + mCollisionWorld->updateSingleAabb(foundProjectile->second->getCollisionObject()); + return; + } + } + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); @@ -632,6 +673,15 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } + int PhysicsSystem::addProjectile (const osg::Vec3f& position) + { + mProjectileId++; + Projectile* projectile = new Projectile(mProjectileId, position, mCollisionWorld); + mProjectiles.insert(std::make_pair(mProjectileId, projectile)); + + return mProjectileId; + } + bool PhysicsSystem::toggleCollisionMode() { ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4137b1e098..4b2aa0384d 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -56,6 +56,7 @@ namespace MWPhysics class Object; class Actor; class PhysicsTaskScheduler; + class Projectile; using ActorMap = std::map>; @@ -127,6 +128,10 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); + int addProjectile(const osg::Vec3f& position); + void updateProjectile(const int projectileId, const osg::Vec3f &position); + void removeProjectile(const int projectileId); + void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); Actor* getActor(const MWWorld::Ptr& ptr); @@ -141,7 +146,6 @@ namespace MWPhysics void updateRotation (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr); - void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); void removeHeightField (int x, int y); @@ -169,10 +173,19 @@ namespace MWPhysics /// \note Only Actor targets are supported at the moment. float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; + struct RayResult + { + bool mHit; + osg::Vec3f mHitPos; + osg::Vec3f mHitNormal; + MWWorld::Ptr mHitObject; + int mProjectileId; + }; + /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; @@ -269,6 +282,9 @@ namespace MWPhysics ActorMap mActors; + using ProjectileMap = std::map; + ProjectileMap mProjectiles; + using HeightFieldMap = std::map, HeightField *>; HeightFieldMap mHeightFields; @@ -279,6 +295,8 @@ namespace MWPhysics float mTimeAccum; + int mProjectileId; + float mWaterHeight; bool mWaterEnabled; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp new file mode 100644 index 0000000000..f129b8f78c --- /dev/null +++ b/apps/openmw/mwphysics/projectile.cpp @@ -0,0 +1,64 @@ +#include "projectile.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "../mwworld/class.hpp" + +#include "collisiontype.hpp" + +namespace MWPhysics +{ +Projectile::Projectile(int projectileId, const osg::Vec3f& position, btCollisionWorld* world) + : mCollisionWorld(world) +{ + mProjectileId = projectileId; + + mShape.reset(new btSphereShape(1.f)); + mConvexShape = static_cast(mShape.get()); + + mCollisionObject.reset(new btCollisionObject); + mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); + mCollisionObject->setActivationState(DISABLE_DEACTIVATION); + mCollisionObject->setCollisionShape(mShape.get()); + mCollisionObject->setUserPointer(this); + + setPosition(position); + + const int collisionMask = CollisionType_World | CollisionType_HeightMap | + CollisionType_Actor | CollisionType_Door | CollisionType_Water; + mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); +} + +Projectile::~Projectile() +{ + if (mCollisionObject.get()) + mCollisionWorld->removeCollisionObject(mCollisionObject.get()); +} + +void Projectile::updateCollisionObjectPosition() +{ + btTransform tr = mCollisionObject->getWorldTransform(); + // osg::Vec3f scaledTranslation = mRotation * mMeshTranslation; + // osg::Vec3f newPosition = scaledTranslation + mPosition; + tr.setOrigin(Misc::Convert::toBullet(mPosition)); + mCollisionObject->setWorldTransform(tr); +} + +void Projectile::setPosition(const osg::Vec3f &position) +{ + mPosition = position; + updateCollisionObjectPosition(); +} + +osg::Vec3f Projectile::getPosition() const +{ + return mPosition; +} + +} diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp new file mode 100644 index 0000000000..180802555b --- /dev/null +++ b/apps/openmw/mwphysics/projectile.hpp @@ -0,0 +1,68 @@ +#ifndef OPENMW_MWPHYSICS_PROJECTILE_H +#define OPENMW_MWPHYSICS_PROJECTILE_H + +#include + +#include "ptrholder.hpp" + +#include +#include +#include + +class btCollisionWorld; +class btCollisionShape; +class btCollisionObject; +class btConvexShape; + +namespace Resource +{ + class BulletShape; +} + +namespace MWPhysics +{ + class Projectile : public PtrHolder + { + public: + Projectile(const int projectileId, const osg::Vec3f& position, btCollisionWorld* world); + ~Projectile(); + + btConvexShape* getConvexShape() const { return mConvexShape; } + + void updateCollisionObjectPosition(); + + void setPosition(const osg::Vec3f& position); + + osg::Vec3f getPosition() const; + + btCollisionObject* getCollisionObject() const + { + return mCollisionObject.get(); + } + + int getProjectileId() const + { + return mProjectileId; + } + + private: + + std::unique_ptr mShape; + btConvexShape* mConvexShape; + + std::unique_ptr mCollisionObject; + + osg::Vec3f mPosition; + + btCollisionWorld* mCollisionWorld; + + Projectile(const Projectile&); + Projectile& operator=(const Projectile&); + + int mProjectileId; + }; + +} + + +#endif diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 58082f4db2..4d2fccdb7b 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -5,6 +5,9 @@ #include #include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + #include "collisiontype.hpp" #include "actor.hpp" #include "closestnotmeconvexresultcallback.hpp" diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 39cdc3e722..3b61e0522a 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -302,6 +302,7 @@ namespace MWWorld MWWorld::Ptr ptr = ref.getPtr(); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -312,7 +313,8 @@ namespace MWWorld if (sound) state.mSounds.push_back(sound); } - + + state.mProjectileId = mPhysics->addProjectile(pos); mMagicBolts.push_back(state); } @@ -325,7 +327,6 @@ namespace MWWorld state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; - int type = projectile.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown; @@ -336,6 +337,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); + state.mProjectileId = mPhysics->addProjectile(pos); mProjectiles.push_back(state); } @@ -416,6 +418,8 @@ namespace MWWorld it->mNode->setPosition(newPos); + mPhysics->updateProjectile(it->mProjectileId, newPos); + update(*it, duration); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. @@ -425,7 +429,7 @@ namespace MWWorld // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); bool hit = false; if (result.mHit) @@ -433,7 +437,7 @@ namespace MWWorld hit = true; if (result.mHitObject.isEmpty()) { - // terrain + // terrain or projectile } else { @@ -456,11 +460,7 @@ namespace MWWorld MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, ESM::RT_Target, it->mSpellId, it->mSourceName); - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) - sndMgr->stopSound(it->mSounds.at(soundIter)); - - mParent->removeChild(it->mNode); + cleanupMagicBolt(*it); it = mMagicBolts.erase(it); continue; @@ -491,6 +491,8 @@ namespace MWWorld it->mNode->setPosition(newPos); + mPhysics->updateProjectile(it->mProjectileId, newPos); + update(*it, duration); MWWorld::Ptr caster = it->getCaster(); @@ -502,15 +504,14 @@ namespace MWWorld // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); if (result.mHit || underwater) { - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); - // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); MWWorld::Ptr bow = projectileRef.getPtr(); if (!caster.isEmpty() && it->mIdArrow != it->mBowId) { @@ -529,8 +530,9 @@ namespace MWWorld if (underwater) mRendering->emitWaterRipple(newPos); - mParent->removeChild(it->mNode); + cleanupProjectile(*it); it = mProjectiles.erase(it); + continue; } @@ -538,14 +540,105 @@ namespace MWWorld } } + void ProjectileManager::manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) + { + for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + { + if (it->mProjectileId == projectileId) + { + MWWorld::Ptr caster = it->getCaster(); + if (caster == target) + return; + + if (!isValidTarget(caster, target)) + return; + + if (caster.isEmpty()) + caster = target; + + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty() && it->mIdArrow != it->mBowId) + { + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + bow = *invIt; + } + + it->mHitPosition = pos; + cleanupProjectile(*it); + MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, it->mAttackStrength); + mProjectiles.erase(it); + return; + } + } + for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) + { + if (it->mProjectileId == projectileId) + { + MWWorld::Ptr caster = it->getCaster(); + if (caster == target) + return; + + if (!isValidTarget(caster, target)) + return; + + it->mHitPosition = pos; + cleanupMagicBolt(*it); + + MWMechanics::CastSpell cast(caster, target); + cast.mHitPosition = pos; + cast.mId = it->mSpellId; + cast.mSourceName = it->mSourceName; + cast.mStack = false; + cast.inflict(target, caster, it->mEffects, ESM::RT_Target, false, true); + + MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, target, ESM::RT_Target, it->mSpellId, it->mSourceName); + mMagicBolts.erase(it); + + return; + } + } + } + + bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + { + // 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 (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) + { + caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); + if (!targetActors.empty()) + { + bool validTarget = false; + for (MWWorld::Ptr& targetActor : targetActors) + { + if (targetActor == target) + { + validTarget = true; + break; + } + } + + return validTarget; + } + } + + return true; + } + void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) { mParent->removeChild(state.mNode); + mPhysics->removeProjectile(state.mProjectileId); } void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) { mParent->removeChild(state.mNode); + mPhysics->removeProjectile(state.mProjectileId); for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) { MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); @@ -626,9 +719,10 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; + + state.mProjectileId = mPhysics->addProjectile(osg::Vec3f(esm.mPosition)); } catch(...) { @@ -672,6 +766,7 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); + state.mProjectileId = mPhysics->addProjectile(osg::Vec3f(esm.mPosition)); } catch(...) { diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index c7b1056b72..58e0ecbf8f 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -56,6 +56,8 @@ namespace MWWorld void update(float dt); + void manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); + /// Removes all current projectiles. Should be called when switching to a new worldspace. void clear(); @@ -76,6 +78,9 @@ namespace MWWorld std::shared_ptr mEffectAnimationTime; int mActorId; + int mProjectileId; + + osg::Vec3f mHitPosition; // TODO: this will break when the game is saved and reloaded, since there is currently // no way to write identifiers for non-actors to a savegame. @@ -125,6 +130,8 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); + bool isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 49e6a729a8..931791fbc9 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -843,6 +843,11 @@ namespace MWWorld } } + void World::manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) + { + mProjectileManager->manualHit(projectileId, target, pos); + } + void World::disable (const Ptr& reference) { if (!reference.getRefData().isEnabled()) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1fc5ebc20d..a913108ad2 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -584,6 +584,8 @@ namespace MWWorld RestPermitted canRest() const override; ///< check if the player is allowed to rest + void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); + void rest(double hours) override; void rechargeItems(double duration, bool activeOnly) override; From 66fe3b0d38469f280a1f6ec4747b5ed8d889d9bd Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Oct 2020 20:27:07 +0200 Subject: [PATCH 0058/2859] Modify projectile collision to work with async physics --- apps/openmw/mwbase/world.hpp | 2 - .../closestnotmeconvexresultcallback.cpp | 5 +- apps/openmw/mwphysics/mtphysics.cpp | 6 + apps/openmw/mwphysics/physicssystem.cpp | 38 +-- apps/openmw/mwphysics/physicssystem.hpp | 15 +- apps/openmw/mwphysics/projectile.cpp | 58 ++-- apps/openmw/mwphysics/projectile.hpp | 59 ++++- apps/openmw/mwphysics/raycasting.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 249 +++++++++--------- apps/openmw/mwworld/projectilemanager.hpp | 4 +- apps/openmw/mwworld/worldimp.cpp | 6 +- apps/openmw/mwworld/worldimp.hpp | 2 - 12 files changed, 241 insertions(+), 205 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c4b3771af0..d4f1d2f8ab 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -610,8 +610,6 @@ namespace MWBase virtual bool isPlayerInJail() const = 0; - virtual void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) = 0; - virtual void rest(double hours) = 0; virtual void rechargeItems(double duration, bool activeOnly) = 0; diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 3b97a3a341..b709811f41 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -26,12 +26,13 @@ namespace MWPhysics if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) { Projectile* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); - int projectileId = projectileHolder->getProjectileId(); + if (!projectileHolder->isActive()) + return btScalar(1); PtrHolder* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); osg::Vec3f pos = Misc::Convert::makeOsgVec3f(convexResult.m_hitPointLocal); - MWBase::Environment::get().getWorld()->manualProjectileHit(projectileId, target, pos); + projectileHolder->hit(target, pos); return btScalar(1); } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 50cfd808ff..a78a307883 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -17,6 +17,7 @@ #include "mtphysics.hpp" #include "object.hpp" #include "physicssystem.hpp" +#include "projectile.hpp" namespace { @@ -455,6 +456,11 @@ namespace MWPhysics object->commitPositionChange(); mCollisionWorld->updateSingleAabb(object->getCollisionObject()); } + else if (const auto projectile = std::dynamic_pointer_cast(p)) + { + projectile->commitPositionChange(); + mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); + } }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8933c9d025..78010fb0ee 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -115,11 +115,7 @@ namespace MWPhysics mObjects.clear(); mActors.clear(); - - for (ProjectileMap::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) - { - delete it->second; - } + mProjectiles.clear(); } void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) @@ -255,7 +251,7 @@ namespace MWPhysics return 0.f; } - PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const + RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const { if (from == to) { @@ -305,17 +301,7 @@ namespace MWPhysics result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld); if (PtrHolder* ptrHolder = static_cast(resultCallback.m_collisionObject->getUserPointer())) - { result.mHitObject = ptrHolder->getPtr(); - - if (group == CollisionType_Projectile) - { - Projectile* projectile = static_cast(ptrHolder); - result.mProjectileId = projectile->getProjectileId(); - } - else - result.mProjectileId = -1; - } } return result; } @@ -523,13 +509,7 @@ namespace MWPhysics { ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); if (foundProjectile != mProjectiles.end()) - { - delete foundProjectile->second; mProjectiles.erase(foundProjectile); - - if (projectileId == mProjectileId) - mProjectileId--; - } } void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) @@ -583,6 +563,14 @@ namespace MWPhysics return nullptr; } + Projectile* PhysicsSystem::getProjectile(int projectileId) const + { + ProjectileMap::const_iterator found = mProjectiles.find(projectileId); + if (found != mProjectiles.end()) + return found->second.get(); + return nullptr; + } + void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); @@ -608,7 +596,7 @@ namespace MWPhysics if (foundProjectile != mProjectiles.end()) { foundProjectile->second->setPosition(position); - mCollisionWorld->updateSingleAabb(foundProjectile->second->getCollisionObject()); + mTaskScheduler->updateSingleAabb(foundProjectile->second); return; } } @@ -676,8 +664,8 @@ namespace MWPhysics int PhysicsSystem::addProjectile (const osg::Vec3f& position) { mProjectileId++; - Projectile* projectile = new Projectile(mProjectileId, position, mCollisionWorld); - mProjectiles.insert(std::make_pair(mProjectileId, projectile)); + auto projectile = std::make_shared(mProjectileId, position, mTaskScheduler.get()); + mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4b2aa0384d..6721a2d918 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -139,6 +139,8 @@ namespace MWPhysics const Object* getObject(const MWWorld::ConstPtr& ptr) const; + Projectile* getProjectile(int projectileId) const; + // Object or Actor void remove (const MWWorld::Ptr& ptr); @@ -173,15 +175,6 @@ namespace MWPhysics /// \note Only Actor targets are supported at the moment. float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; - struct RayResult - { - bool mHit; - osg::Vec3f mHitPos; - osg::Vec3f mHitNormal; - MWWorld::Ptr mHitObject; - int mProjectileId; - }; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), @@ -282,7 +275,7 @@ namespace MWPhysics ActorMap mActors; - using ProjectileMap = std::map; + using ProjectileMap = std::map>; ProjectileMap mProjectiles; using HeightFieldMap = std::map, HeightField *>; @@ -295,7 +288,7 @@ namespace MWPhysics float mTimeAccum; - int mProjectileId; + unsigned int mProjectileId; float mWaterHeight; bool mWaterEnabled; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index f129b8f78c..8abf238cf2 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -1,24 +1,28 @@ -#include "projectile.hpp" +#include #include #include -#include -#include +#include + #include #include +#include +#include #include "../mwworld/class.hpp" #include "collisiontype.hpp" +#include "mtphysics.hpp" +#include "projectile.hpp" namespace MWPhysics { -Projectile::Projectile(int projectileId, const osg::Vec3f& position, btCollisionWorld* world) - : mCollisionWorld(world) +Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler) + : mActive(true) + , mTaskScheduler(scheduler) + , mProjectileId(projectileId) { - mProjectileId = projectileId; - mShape.reset(new btSphereShape(1.f)); mConvexShape = static_cast(mShape.get()); @@ -32,33 +36,47 @@ Projectile::Projectile(int projectileId, const osg::Vec3f& position, btCollision const int collisionMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor | CollisionType_Door | CollisionType_Water; - mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); + mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); + + commitPositionChange(); } Projectile::~Projectile() { - if (mCollisionObject.get()) - mCollisionWorld->removeCollisionObject(mCollisionObject.get()); + if (mCollisionObject) + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } -void Projectile::updateCollisionObjectPosition() +void Projectile::commitPositionChange() { - btTransform tr = mCollisionObject->getWorldTransform(); - // osg::Vec3f scaledTranslation = mRotation * mMeshTranslation; - // osg::Vec3f newPosition = scaledTranslation + mPosition; - tr.setOrigin(Misc::Convert::toBullet(mPosition)); - mCollisionObject->setWorldTransform(tr); + std::unique_lock lock(mPositionMutex); + if (mTransformUpdatePending) + { + mCollisionObject->setWorldTransform(mLocalTransform); + mTransformUpdatePending = false; + } } void Projectile::setPosition(const osg::Vec3f &position) { - mPosition = position; - updateCollisionObjectPosition(); + std::unique_lock lock(mPositionMutex); + mLocalTransform.setOrigin(Misc::Convert::toBullet(position)); + mTransformUpdatePending = true; } -osg::Vec3f Projectile::getPosition() const +void Projectile::hit(MWWorld::Ptr target, osg::Vec3f pos) { - return mPosition; + if (!mActive.load(std::memory_order_acquire)) + return; + std::unique_lock lock(mPositionMutex); + mHitTarget = target; + mHitPosition = pos; + mActive.store(false, std::memory_order_release); } +void Projectile::activate() +{ + assert(!mActive); + mActive.store(true, std::memory_order_release); +} } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 180802555b..7ad0a31246 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -1,18 +1,23 @@ #ifndef OPENMW_MWPHYSICS_PROJECTILE_H #define OPENMW_MWPHYSICS_PROJECTILE_H +#include #include +#include -#include "ptrholder.hpp" +#include -#include -#include -#include +#include "ptrholder.hpp" -class btCollisionWorld; -class btCollisionShape; class btCollisionObject; +class btCollisionShape; class btConvexShape; +class btVector3; + +namespace osg +{ + class Vec3f; +} namespace Resource { @@ -21,20 +26,21 @@ namespace Resource namespace MWPhysics { - class Projectile : public PtrHolder + class PhysicsTaskScheduler; + class PhysicsSystem; + + class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const osg::Vec3f& position, btCollisionWorld* world); - ~Projectile(); + Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler); + ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } - void updateCollisionObjectPosition(); + void commitPositionChange(); void setPosition(const osg::Vec3f& position); - osg::Vec3f getPosition() const; - btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); @@ -45,16 +51,43 @@ namespace MWPhysics return mProjectileId; } + bool isActive() const + { + return mActive.load(std::memory_order_acquire); + } + + MWWorld::Ptr getTarget() const + { + assert(!mActive); + return mHitTarget; + } + + osg::Vec3f getHitPos() const + { + assert(!mActive); + return Misc::Convert::toOsg(mHitPosition); + } + + void hit(MWWorld::Ptr target, osg::Vec3f pos); + void activate(); + private: std::unique_ptr mShape; btConvexShape* mConvexShape; std::unique_ptr mCollisionObject; + btTransform mLocalTransform; + bool mTransformUpdatePending; + std::atomic mActive; + MWWorld::Ptr mHitTarget; + osg::Vec3f mHitPosition; + + mutable std::mutex mPositionMutex; osg::Vec3f mPosition; - btCollisionWorld* mCollisionWorld; + PhysicsTaskScheduler *mTaskScheduler; Projectile(const Projectile&); Projectile& operator=(const Projectile&); diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 7afbe93214..8ca6965d8e 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -29,7 +29,7 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3b61e0522a..626c57ac32 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -43,6 +44,7 @@ #include "../mwsound/sound.hpp" #include "../mwphysics/physicssystem.hpp" +#include "../mwphysics/projectile.hpp" namespace { @@ -284,7 +286,7 @@ namespace MWWorld else state.mActorId = -1; - std::string texture = ""; + std::string texture; state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); @@ -315,6 +317,7 @@ namespace MWWorld } state.mProjectileId = mPhysics->addProjectile(pos); + state.mToDelete = false; mMagicBolts.push_back(state); } @@ -338,6 +341,7 @@ namespace MWWorld SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); state.mProjectileId = mPhysics->addProjectile(pos); + state.mToDelete = false; mProjectiles.push_back(state); } @@ -362,65 +366,58 @@ namespace MWWorld return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold; }; - for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + for (auto& projectileState : mProjectiles) { - if (isCleanable(*it)) - { - cleanupProjectile(*it); - it = mProjectiles.erase(it); - } - else - ++it; + if (isCleanable(projectileState)) + cleanupProjectile(projectileState); } - for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) + for (auto& magicBoltState : mMagicBolts) { - if (isCleanable(*it)) - { - cleanupMagicBolt(*it); - it = mMagicBolts.erase(it); - } - else - ++it; + if (isCleanable(magicBoltState)) + cleanupMagicBolt(magicBoltState); } } } void ProjectileManager::moveMagicBolts(float duration) { - for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) + for (auto& magicBoltState : mMagicBolts) { + if (magicBoltState.mToDelete) + continue; + + const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + if (!projectile->isActive()) + continue; // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. - MWWorld::Ptr caster = it->getCaster(); + MWWorld::Ptr caster = magicBoltState.getCaster(); if (!caster.isEmpty() && caster.getClass().isActor()) { if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) { - cleanupMagicBolt(*it); - it = mMagicBolts.erase(it); + cleanupMagicBolt(magicBoltState); continue; } } - osg::Quat orient = it->mNode->getAttitude(); + osg::Quat orient = magicBoltState.mNode->getAttitude(); static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() .find("fTargetSpellMaxSpeed")->mValue.getFloat(); - float speed = fTargetSpellMaxSpeed * it->mSpeed; + float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); - osg::Vec3f pos(it->mNode->getPosition()); + osg::Vec3f pos(magicBoltState.mNode->getPosition()); osg::Vec3f newPos = pos + direction * duration * speed; - for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) - { - it->mSounds.at(soundIter)->setPosition(newPos); - } + for (const auto& sound : magicBoltState.mSounds) + sound->setPosition(newPos); - it->mNode->setPosition(newPos); + magicBoltState.mNode->setPosition(newPos); - mPhysics->updateProjectile(it->mProjectileId, newPos); + mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); - update(*it, duration); + update(magicBoltState, duration); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; @@ -429,7 +426,7 @@ namespace MWWorld // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); + const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId); bool hit = false; if (result.mHit) @@ -443,10 +440,10 @@ namespace MWWorld { MWMechanics::CastSpell cast(caster, result.mHitObject); cast.mHitPosition = pos; - cast.mId = it->mSpellId; - cast.mSourceName = it->mSourceName; + cast.mId = magicBoltState.mSpellId; + cast.mSourceName = magicBoltState.mSourceName; cast.mStack = false; - cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true); + cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); } } @@ -457,45 +454,46 @@ namespace MWWorld if (hit) { - MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, - ESM::RT_Target, it->mSpellId, it->mSourceName); - - cleanupMagicBolt(*it); + MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject, + ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); - it = mMagicBolts.erase(it); - continue; + cleanupMagicBolt(magicBoltState); } - else - ++it; } } void ProjectileManager::moveProjectiles(float duration) { - for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + for (auto& projectileState : mProjectiles) { + if (projectileState.mToDelete) + continue; + + const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + if (!projectile->isActive()) + continue; // gravity constant - must be way lower than the gravity affecting actors, since we're not // simulating aerodynamics at all - it->mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; + projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - osg::Vec3f pos(it->mNode->getPosition()); - osg::Vec3f newPos = pos + it->mVelocity * duration; + osg::Vec3f pos(projectileState.mNode->getPosition()); + osg::Vec3f newPos = pos + projectileState.mVelocity * duration; // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. - if (!it->mThrown) + if (!projectileState.mThrown) { osg::Quat orient; - orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); - it->mNode->setAttitude(orient); + orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity); + projectileState.mNode->setAttitude(orient); } - it->mNode->setPosition(newPos); + projectileState.mNode->setPosition(newPos); - mPhysics->updateProjectile(it->mProjectileId, newPos); + mPhysics->updateProjectile(projectileState.mProjectileId, newPos); - update(*it, duration); + update(projectileState, duration); - MWWorld::Ptr caster = it->getCaster(); + MWWorld::Ptr caster = projectileState.getCaster(); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; @@ -504,103 +502,107 @@ namespace MWWorld // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); + const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId); bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); if (result.mHit || underwater) { // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty() && it->mIdArrow != it->mBowId) + if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) { MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) bow = *invIt; } if (caster.isEmpty()) caster = result.mHitObject; - MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, it->mAttackStrength); + MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength); mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); if (underwater) mRendering->emitWaterRipple(newPos); - cleanupProjectile(*it); - it = mProjectiles.erase(it); - - continue; + cleanupProjectile(projectileState); } - - ++it; } } - void ProjectileManager::manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) + void ProjectileManager::processHits() { - for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + for (auto& projectileState : mProjectiles) { - if (it->mProjectileId == projectileId) - { - MWWorld::Ptr caster = it->getCaster(); - if (caster == target) - return; - - if (!isValidTarget(caster, target)) - return; + if (projectileState.mToDelete) + continue; - if (caster.isEmpty()) - caster = target; + auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + if (projectile->isActive()) + continue; + const auto target = projectile->getTarget(); + const auto pos = projectile->getHitPos(); + MWWorld::Ptr caster = projectileState.getCaster(); + if (caster == target || !isValidTarget(caster, target)) + { + projectile->activate(); + continue; + } - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty() && it->mIdArrow != it->mBowId) - { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) - bow = *invIt; - } + if (caster.isEmpty()) + caster = target; - it->mHitPosition = pos; - cleanupProjectile(*it); - MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, it->mAttackStrength); - mProjectiles.erase(it); - return; + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) + { + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) + bow = *invIt; } + + projectileState.mHitPosition = pos; + cleanupProjectile(projectileState); + MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); } - for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) + for (auto& magicBoltState : mMagicBolts) { - if (it->mProjectileId == projectileId) - { - MWWorld::Ptr caster = it->getCaster(); - if (caster == target) - return; - - if (!isValidTarget(caster, target)) - return; + if (magicBoltState.mToDelete) + continue; - it->mHitPosition = pos; - cleanupMagicBolt(*it); + auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + if (projectile->isActive()) + continue; + const auto target = projectile->getTarget(); + const auto pos = projectile->getHitPos(); + MWWorld::Ptr caster = magicBoltState.getCaster(); + if (caster == target || !isValidTarget(caster, target)) + { + projectile->activate(); + continue; + } - MWMechanics::CastSpell cast(caster, target); - cast.mHitPosition = pos; - cast.mId = it->mSpellId; - cast.mSourceName = it->mSourceName; - cast.mStack = false; - cast.inflict(target, caster, it->mEffects, ESM::RT_Target, false, true); + magicBoltState.mHitPosition = pos; + cleanupMagicBolt(magicBoltState); - MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, target, ESM::RT_Target, it->mSpellId, it->mSourceName); - mMagicBolts.erase(it); + MWMechanics::CastSpell cast(caster, target); + cast.mHitPosition = pos; + cast.mId = magicBoltState.mSpellId; + cast.mSourceName = magicBoltState.mSourceName; + cast.mStack = false; + cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); - return; - } + MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); } + mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), + mProjectiles.end()); + mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }), + mMagicBolts.end()); } bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) @@ -633,12 +635,14 @@ namespace MWWorld { mParent->removeChild(state.mNode); mPhysics->removeProjectile(state.mProjectileId); + state.mToDelete = true; } void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) { mParent->removeChild(state.mNode); mPhysics->removeProjectile(state.mProjectileId); + state.mToDelete = true; for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) { MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); @@ -647,15 +651,12 @@ namespace MWWorld void ProjectileManager::clear() { - for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) - { - cleanupProjectile(*it); - } + for (auto& mProjectile : mProjectiles) + cleanupProjectile(mProjectile); mProjectiles.clear(); - for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) - { - cleanupMagicBolt(*it); - } + + for (auto& mMagicBolt : mMagicBolts) + cleanupMagicBolt(mMagicBolt); mMagicBolts.clear(); } @@ -712,6 +713,7 @@ namespace MWWorld state.mVelocity = esm.mVelocity; state.mIdArrow = esm.mId; state.mAttackStrength = esm.mAttackStrength; + state.mToDelete = false; std::string model; try @@ -734,7 +736,7 @@ namespace MWWorld mProjectiles.push_back(state); return true; } - else if (type == ESM::REC_MPRJ) + if (type == ESM::REC_MPRJ) { ESM::MagicBoltState esm; esm.load(reader); @@ -743,7 +745,8 @@ namespace MWWorld state.mIdMagic.push_back(esm.mId); state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; - std::string texture = ""; + state.mToDelete = false; + std::string texture; try { diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 58e0ecbf8f..d589e985e5 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -56,7 +56,7 @@ namespace MWWorld void update(float dt); - void manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); + void processHits(); /// Removes all current projectiles. Should be called when switching to a new worldspace. void clear(); @@ -93,6 +93,8 @@ namespace MWWorld // MW-id of an arrow projectile std::string mIdArrow; + + bool mToDelete; }; struct MagicBoltState : public State diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 931791fbc9..9a2dd11819 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -843,11 +843,6 @@ namespace MWWorld } } - void World::manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) - { - mProjectileManager->manualHit(projectileId, target, pos); - } - void World::disable (const Ptr& reference) { if (!reference.getRefData().isEnabled()) @@ -1498,6 +1493,7 @@ namespace MWWorld mProjectileManager->update(duration); const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); + mProjectileManager->processHits(); mDiscardMovements = false; for(const auto& [actor, position]: results) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index a913108ad2..1fc5ebc20d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -584,8 +584,6 @@ namespace MWWorld RestPermitted canRest() const override; ///< check if the player is allowed to rest - void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); - void rest(double hours) override; void rechargeItems(double duration, bool activeOnly) override; From 7e85235220dfefca9f44f75098c271709e70e001 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 31 Oct 2020 14:01:14 +0100 Subject: [PATCH 0059/2859] Projectile to projectile collision --- .../closestnotmeconvexresultcallback.cpp | 4 +-- .../closestnotmerayresultcallback.cpp | 28 +++++++++++++------ .../closestnotmerayresultcallback.hpp | 6 ++-- apps/openmw/mwphysics/physicssystem.cpp | 4 +-- apps/openmw/mwphysics/projectile.cpp | 12 ++++++-- apps/openmw/mwphysics/projectile.hpp | 10 ++++--- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index b709811f41..bbffe16a8e 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -30,9 +30,7 @@ namespace MWPhysics return btScalar(1); PtrHolder* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); - - osg::Vec3f pos = Misc::Convert::makeOsgVec3f(convexResult.m_hitPointLocal); - projectileHolder->hit(target, pos); + projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); return btScalar(1); } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 43e1d5c628..422ca78bd5 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -1,19 +1,22 @@ #include "closestnotmerayresultcallback.hpp" #include +#include #include #include "../mwworld/class.hpp" +#include "actor.hpp" +#include "collisiontype.hpp" #include "projectile.hpp" #include "ptrholder.hpp" namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to, int projId) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me), mTargets(targets), mProjectileId(projId) + , mMe(me), mTargets(std::move(targets)), mProjectile(proj) { } @@ -25,20 +28,27 @@ namespace MWPhysics { if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) { - PtrHolder* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); + auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) return 1.f; } } - if (mProjectileId >= 0) + btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); + if (mProjectile) { - PtrHolder* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); - Projectile* projectile = dynamic_cast(holder); - if (projectile && projectile->getProjectileId() == mProjectileId) - return 1.f; + auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); + if (auto* target = dynamic_cast(holder)) + { + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + } + else if (auto* target = dynamic_cast(holder)) + { + target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + } } - return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); + return rayResult.m_hitFraction; } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index d27d64c535..b86427165a 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -9,16 +9,18 @@ class btCollisionObject; namespace MWPhysics { + class Projectile; + class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to, int projId=-1); + ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: const btCollisionObject* mMe; const std::vector mTargets; - const int mProjectileId; + Projectile* mProjectile; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 78010fb0ee..7da2a69643 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -288,7 +288,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, projId); + ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId)); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; @@ -664,7 +664,7 @@ namespace MWPhysics int PhysicsSystem::addProjectile (const osg::Vec3f& position) { mProjectileId++; - auto projectile = std::make_shared(mProjectileId, position, mTaskScheduler.get()); + auto projectile = std::make_shared(mProjectileId, position, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 8abf238cf2..cbc5b19814 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -18,8 +18,9 @@ namespace MWPhysics { -Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler) +Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) : mActive(true) + , mPhysics(physicssystem) , mTaskScheduler(scheduler) , mProjectileId(projectileId) { @@ -35,7 +36,7 @@ Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTask setPosition(position); const int collisionMask = CollisionType_World | CollisionType_HeightMap | - CollisionType_Actor | CollisionType_Door | CollisionType_Water; + CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); commitPositionChange(); @@ -44,7 +45,11 @@ Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTask Projectile::~Projectile() { if (mCollisionObject) + { + if (!mActive) + mPhysics->reportCollision(mHitPosition, mHitNormal); mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + } } void Projectile::commitPositionChange() @@ -64,13 +69,14 @@ void Projectile::setPosition(const osg::Vec3f &position) mTransformUpdatePending = true; } -void Projectile::hit(MWWorld::Ptr target, osg::Vec3f pos) +void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) return; std::unique_lock lock(mPositionMutex); mHitTarget = target; mHitPosition = pos; + mHitNormal = normal; mActive.store(false, std::memory_order_release); } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 7ad0a31246..6e8aba60b8 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include "components/misc/convert.hpp" #include "ptrholder.hpp" @@ -32,7 +32,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler); + Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -68,7 +68,7 @@ namespace MWPhysics return Misc::Convert::toOsg(mHitPosition); } - void hit(MWWorld::Ptr target, osg::Vec3f pos); + void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void activate(); private: @@ -81,12 +81,14 @@ namespace MWPhysics bool mTransformUpdatePending; std::atomic mActive; MWWorld::Ptr mHitTarget; - osg::Vec3f mHitPosition; + btVector3 mHitPosition; + btVector3 mHitNormal; mutable std::mutex mPositionMutex; osg::Vec3f mPosition; + PhysicsSystem *mPhysics; PhysicsTaskScheduler *mTaskScheduler; Projectile(const Projectile&); From 4fbe1ed12cbe2e2adaa4b95eefee42a24af8f5a4 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 21 Nov 2020 16:26:45 +0100 Subject: [PATCH 0060/2859] Ignore caster collision shape. Sometimes the magic bolt get launched inside too near its caster. --- .../mwphysics/closestnotmeconvexresultcallback.cpp | 12 ++++++++---- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/projectile.cpp | 3 ++- apps/openmw/mwphysics/projectile.hpp | 5 ++++- apps/openmw/mwworld/projectilemanager.cpp | 14 ++++++++------ 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index bbffe16a8e..8f72422765 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -25,13 +25,17 @@ namespace MWPhysics if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) { - Projectile* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); + auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) return btScalar(1); - PtrHolder* targetHolder = static_cast(mMe->getUserPointer()); + auto* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); - projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); - return btScalar(1); + // do nothing if we hit the caster. Sometimes the launching origin is inside of caster collision shape + if (projectileHolder->getCaster() != target) + { + projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); + return btScalar(1); + } } btVector3 hitNormalWorld; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 7da2a69643..324b1db2d7 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -661,10 +661,10 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const osg::Vec3f& position) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position) { mProjectileId++; - auto projectile = std::make_shared(mProjectileId, position, mTaskScheduler.get(), this); + auto projectile = std::make_shared(mProjectileId, caster, position, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6721a2d918..b33d829a46 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -128,7 +128,7 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const osg::Vec3f& position); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position); void updateProjectile(const int projectileId, const osg::Vec3f &position); void removeProjectile(const int projectileId); diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index cbc5b19814..5f5bc778ba 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -18,8 +18,9 @@ namespace MWPhysics { -Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) +Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) : mActive(true) + , mCaster(caster) , mPhysics(physicssystem) , mTaskScheduler(scheduler) , mProjectileId(projectileId) diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 6e8aba60b8..ed0fdce150 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -32,7 +32,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -62,6 +62,8 @@ namespace MWPhysics return mHitTarget; } + MWWorld::Ptr getCaster() const { return mCaster; } + osg::Vec3f getHitPos() const { assert(!mActive); @@ -80,6 +82,7 @@ namespace MWPhysics btTransform mLocalTransform; bool mTransformUpdatePending; std::atomic mActive; + MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; btVector3 mHitPosition; btVector3 mHitNormal; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 626c57ac32..dba4289a23 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -316,7 +316,7 @@ namespace MWWorld state.mSounds.push_back(sound); } - state.mProjectileId = mPhysics->addProjectile(pos); + state.mProjectileId = mPhysics->addProjectile(caster, pos); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -340,7 +340,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(pos); + state.mProjectileId = mPhysics->addProjectile(actor, pos); state.mToDelete = false; mProjectiles.push_back(state); } @@ -546,7 +546,8 @@ namespace MWWorld const auto target = projectile->getTarget(); const auto pos = projectile->getHitPos(); MWWorld::Ptr caster = projectileState.getCaster(); - if (caster == target || !isValidTarget(caster, target)) + assert(target != caster); + if (!isValidTarget(caster, target)) { projectile->activate(); continue; @@ -581,7 +582,8 @@ namespace MWWorld const auto target = projectile->getTarget(); const auto pos = projectile->getHitPos(); MWWorld::Ptr caster = magicBoltState.getCaster(); - if (caster == target || !isValidTarget(caster, target)) + assert(target != caster); + if (!isValidTarget(caster, target)) { projectile->activate(); continue; @@ -724,7 +726,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(osg::Vec3f(esm.mPosition)); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); } catch(...) { @@ -769,7 +771,7 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - state.mProjectileId = mPhysics->addProjectile(osg::Vec3f(esm.mPosition)); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); } catch(...) { From 39ac0cbb4ab37a373e4c67b8c2c18be74e45cde8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 8 Dec 2020 17:47:25 +0100 Subject: [PATCH 0061/2859] Don't crash the game when placing a non-actor --- apps/openmw/mwworld/worldimp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9a2dd11819..d00fabb66d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1345,7 +1345,11 @@ namespace MWWorld moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); if (force) // force physics to use the new position - mPhysics->getActor(ptr)->resetPosition(); + { + auto actor = mPhysics->getActor(ptr); + if(actor) + actor->resetPosition(); + } } void World::fixPosition() From 61a4a0807b4ed2398b8169b477132b12a1a3a106 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 9 Dec 2020 00:10:58 +0200 Subject: [PATCH 0062/2859] Load master index in refId to mContentFile, keep master index also in mIndex --- apps/opencs/model/world/refcollection.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index d8f6b391b7..126a0ea789 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -64,10 +64,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // ignore content file number std::map::iterator iter = cache.begin(); - ref.mRefNum.mIndex = ref.mRefNum.mIndex & 0x00ffffff; + unsigned int thisIndex = ref.mRefNum.mIndex & 0x00ffffff; + if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; + for (; iter != cache.end(); ++iter) { - if (ref.mRefNum.mIndex == iter->first.mIndex) + if (thisIndex == iter->first.mIndex) break; } From 49c6e50c31082b00ce54c76d6ff7a7fe0c1e4d7d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 8 Dec 2020 22:23:11 +0100 Subject: [PATCH 0063/2859] Print '--version' and '--help' messages without timestamps --- CHANGELOG.md | 1 + apps/openmw/main.cpp | 4 ++-- components/debug/debugging.cpp | 9 +++++++++ components/debug/debugging.hpp | 3 +++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e545156760..b09c60abb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ Feature #390: 3rd person look "over the shoulder" Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container + Feature #2686: Timestamps in openmw.log Feature #4894: Consider actors as obstacles for pathfinding Feature #5043: Head Bobbing Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index aca0505920..89aa2b9fd5 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -135,7 +135,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (variables.count ("help")) { - std::cout << desc << std::endl; + getRawStdout() << desc << std::endl; return false; } @@ -144,7 +144,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc, true); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); - std::cout << v.describe() << std::endl; + getRawStdout() << v.describe() << std::endl; return false; } diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 88219dcbe1..987a3db7e1 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -1,6 +1,7 @@ #include "debugging.hpp" #include +#include #include @@ -133,11 +134,19 @@ namespace Debug } } +static std::unique_ptr rawStdout = nullptr; + +std::ostream& getRawStdout() +{ + return rawStdout ? *rawStdout : std::cout; +} + int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName) { #if defined _WIN32 (void)Debug::attachParentConsole(); #endif + rawStdout = std::make_unique(std::cout.rdbuf()); // Some objects used to redirect cout and cerr // Scope must be here, so this still works inside the catch block for logging exceptions diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp index 39390446ff..d8849cd896 100644 --- a/components/debug/debugging.hpp +++ b/components/debug/debugging.hpp @@ -135,6 +135,9 @@ namespace Debug #endif } +// Can be used to print messages without timestamps +std::ostream& getRawStdout(); + int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName); #endif From fd4a62ce35d5d2dc65e4b6353c38192a6f381554 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 9 Dec 2020 00:32:01 +0000 Subject: [PATCH 0064/2859] Use correct variable types when loading config for tests --- apps/openmw_test_suite/mwworld/test_store.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 77aaccfdd0..f3b2bb3dcb 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -58,10 +59,10 @@ struct ContentFileTest : public ::testing::Test boost::program_options::options_description desc("Allowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) - ("content", boost::program_options::value >()->default_value(std::vector(), "") - ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") - ("data-local", boost::program_options::value()->default_value("")); + ("data", boost::program_options::value()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing()) + ("content", boost::program_options::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + ("data-local", boost::program_options::value()->default_value(Files::EscapePath(), "")); boost::program_options::notify(variables); @@ -69,12 +70,12 @@ struct ContentFileTest : public ::testing::Test Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = Files::PathContainer(variables["data"].as()); + dataDirs = Files::EscapePath::toPathContainer(variables["data"].as()); } - std::string local = variables["data-local"].as(); + Files::PathContainer::value_type local(variables["data-local"].as().mPath); if (!local.empty()) { - dataLocal.push_back(Files::PathContainer::value_type(local)); + dataLocal.push_back(local); } mConfigurationManager.processPaths (dataDirs); @@ -85,7 +86,7 @@ struct ContentFileTest : public ::testing::Test Files::Collections collections (dataDirs, true); - std::vector contentFiles = variables["content"].as >(); + std::vector contentFiles = variables["content"].as().toStdStringVector(); for (auto & contentFile : contentFiles) mContentFiles.push_back(collections.getPath(contentFile)); } From 525292b18418989c6ae37351bffb17cd30f42b4e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Dec 2020 19:02:38 +0100 Subject: [PATCH 0065/2859] Add graphic herbalism to the launcher --- CHANGELOG.md | 1 + apps/launcher/advancedpage.cpp | 2 ++ docs/source/reference/modding/settings/game.rst | 11 +++++++++++ files/ui/advancedpage.ui | 10 ++++++++++ 4 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b09c60abb5..b16480b02d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Feature #5649: Skyrim SE compressed BSA format support Feature #5672: Make stretch menu background configuration more accessible Feature #5692: Improve spell/magic item search to factor in magic effect names + Feature #5730: Add graphic herbalism option to the launcher and documents Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index b35e786395..ed04072351 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -153,6 +153,7 @@ bool Launcher::AdvancedPage::loadSettings() if (showOwnedIndex >= 0 && showOwnedIndex <= 3) showOwnedComboBox->setCurrentIndex(showOwnedIndex); loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); + loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); } // Bug fixes @@ -279,6 +280,7 @@ void Launcher::AdvancedPage::saveSettings() if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); + saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); } // Bug fixes diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index fd93eba61b..4e1fe13183 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -428,3 +428,14 @@ even if the fighting NPC is knocked out. This setting allows the player to steal items from fighting NPCs that were knocked out if enabled. This setting can be controlled in Advanced tab of the launcher. + +graphic herbalism +----------------- + +:Type: boolean +:Range: True/False +:Default: True + +Some mods add harvestable container models. When this setting is enabled, activating a container using a harvestable model will visually harvest from it instead of opening the menu. + +When this setting is turned off or when activating a regular container, the menu will open as usual. diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 3f53180daf..a3601ce94d 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -724,6 +724,16 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C + + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + + + Enable graphic herbalism + + + From 7156b11dbc4e924a3d97654ed657f1d5b3dae88f Mon Sep 17 00:00:00 2001 From: psi29a Date: Thu, 10 Dec 2020 21:30:05 +0000 Subject: [PATCH 0066/2859] be explicit about narrowing to resolve: error: type 'btScalar *' (aka 'float *') cannot be narrowed to 'bool' in initializer list [-Wc++11-narrowing] --- apps/openmw_test_suite/detournavigator/recastmeshobject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index a3606f8273..621db51a89 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -15,7 +15,7 @@ namespace struct DetourNavigatorRecastMeshObjectTest : Test { btBoxShape mBoxShape {btVector3(1, 2, 3)}; - btCompoundShape mCompoundShape {btVector3(1, 2, 3)}; + btCompoundShape mCompoundShape {true}; btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; DetourNavigatorRecastMeshObjectTest() From 842ea9d6ed06cbfc3044387f96582fda2b6316c2 Mon Sep 17 00:00:00 2001 From: Coleman Smith Date: Thu, 10 Dec 2020 21:36:46 +0000 Subject: [PATCH 0067/2859] simplifying a bit - attaching gradient camera directly to the main camera - using Vec4ub --- apps/opencs/model/prefs/state.cpp | 13 ++ apps/opencs/view/render/cellarrow.cpp | 4 +- apps/opencs/view/render/scenewidget.cpp | 154 ++++++++++++++++++++++-- apps/opencs/view/render/scenewidget.hpp | 7 +- 4 files changed, 168 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 39aae48bda..588be9ccb3 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -210,6 +210,19 @@ void CSMPrefs::State::declare() setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world."). setRange(10, 10000); declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1); + declareBool("scene-use-gradient", "Use Gradient Background", true); + declareColour ("scene-day-background-colour", "Day Background Colour", QColor (110, 120, 128, 255)); + declareColour ("scene-day-gradient-colour", "Day Gradient Colour", QColor (47, 51, 51, 255)). + setTooltip("Sets the gradient color to use in conjunction with the day background color. Ignored if " + "the gradient option is disabled."); + declareColour ("scene-bright-background-colour", "Scene Bright Background Colour", QColor (79, 87, 92, 255)); + declareColour ("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor (47, 51, 51, 255)). + setTooltip("Sets the gradient color to use in conjunction with the bright background color. Ignored if " + "the gradient option is disabled."); + declareColour ("scene-night-background-colour", "Scene Night Background Colour", QColor (64, 77, 79, 255)); + declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)). + setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if " + "the gradient option is disabled."); declareCategory ("Tooltips"); declareBool ("scene", "Show Tooltips in 3D scenes", true); diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index b6fee15459..ac260fe83a 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -151,9 +151,9 @@ void CSVRender::CellArrow::buildShape() osg::Vec4Array *colours = new osg::Vec4Array; for (int i=0; i<6; ++i) - colours->push_back (osg::Vec4f (1.0f, 0.0f, 0.0f, 1.0f)); + colours->push_back (osg::Vec4f (0.11, 0.6f, 0.95f, 1.0f)); for (int i=0; i<6; ++i) - colours->push_back (osg::Vec4f (0.8f, (i==2 || i==5) ? 0.6f : 0.4f, 0.0f, 1.0f)); + colours->push_back (osg::Vec4f (0.08f, 0.44f, 0.7f, 1.0f)); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 3abc01d2e6..b2648da675 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -69,7 +69,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) setLayout(layout); mView->getCamera()->setGraphicsContext(window); - mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) ); mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; @@ -212,6 +211,25 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() ); + // set up gradient view or configured clear color + QColor bgColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); + + if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) { + QColor gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); + mGradientCamera = createGradientCamera(bgColour, gradientColour); + + mView->getCamera()->setClearMask(0); + mView->getCamera()->addChild(mGradientCamera.get()); + } + else { + mView->getCamera()->setClearColor(osg::Vec4( + bgColour.redF(), + bgColour.greenF(), + bgColour.blueF(), + 1.0f + )); + } + // we handle lighting manually mView->setLightingMode(osgViewer::View::NO_LIGHT); @@ -249,6 +267,81 @@ SceneWidget::~SceneWidget() mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); } + +osg::ref_ptr SceneWidget::createGradientRectangle(QColor bgColour, QColor gradientColour) +{ + osg::ref_ptr geometry = new osg::Geometry; + + osg::ref_ptr vertices = new osg::Vec3Array; + + vertices->push_back(osg::Vec3(0.0f, 0.0f, -1.0f)); + vertices->push_back(osg::Vec3(1.0f, 0.0f, -1.0f)); + vertices->push_back(osg::Vec3(0.0f, 1.0f, -1.0f)); + vertices->push_back(osg::Vec3(1.0f, 1.0f, -1.0f)); + + geometry->setVertexArray(vertices); + + osg::ref_ptr primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // triangle 1 + primitives->push_back (0); + primitives->push_back (1); + primitives->push_back (2); + + // triangle 2 + primitives->push_back (2); + primitives->push_back (1); + primitives->push_back (3); + + geometry->addPrimitiveSet(primitives); + + osg::ref_ptr colours = new osg::Vec4ubArray; + colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); + + osg::Vec4ub bgVec = osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f); + + geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + return geometry; +} + + +osg::ref_ptr SceneWidget::createGradientCamera(QColor bgColour, QColor gradientColour) +{ + osg::ref_ptr camera = new osg::Camera(); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1.0f, 0, 1.0f)); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setViewMatrix(osg::Matrix::identity()); + + camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + camera->setAllowEventFocus(false); + + // draw subgraph before main camera view. + camera->setRenderOrder(osg::Camera::PRE_RENDER); + + camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + osg::ref_ptr gradientQuad = createGradientRectangle(bgColour, gradientColour); + + camera->addChild(gradientQuad); + return camera; +} + + +void SceneWidget::updateGradientCamera(QColor bgColour, QColor gradientColour) +{ + osg::ref_ptr gradientRect = createGradientRectangle(bgColour, gradientColour); + // Replaces previous rectangle + mGradientCamera->setChild(0, gradientRect.get()); +} + void SceneWidget::setLighting(Lighting *lighting) { if (mLighting) @@ -276,12 +369,59 @@ void SceneWidget::setAmbient(const osg::Vec4f& ambient) void SceneWidget::selectLightingMode (const std::string& mode) { - if (mode=="day") - setLighting (&mLightingDay); - else if (mode=="night") - setLighting (&mLightingNight); - else if (mode=="bright") - setLighting (&mLightingBright); + QColor backgroundColour; + QColor gradientColour; + if (mode == "day") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); + setLighting(&mLightingDay); + } + else if (mode == "night") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor(); + setLighting(&mLightingNight); + } + else if (mode == "bright") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor(); + setLighting(&mLightingBright); + } + if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) { + if (mGradientCamera.get() != nullptr) { + // we can go ahead and update since this camera still exists + updateGradientCamera(backgroundColour, gradientColour); + + if (!mView->getCamera()->containsNode(mGradientCamera.get())) + { + // need to re-attach the gradient camera + mView->getCamera()->setClearMask(0); + mView->getCamera()->addChild(mGradientCamera.get()); + } + } + else { + // need to create the gradient camera + mGradientCamera = createGradientCamera(backgroundColour, gradientColour); + mView->getCamera()->setClearMask(0); + mView->getCamera()->addChild(mGradientCamera.get()); + } + } + else { + // Fall back to using the clear color for the camera + mView->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + mView->getCamera()->setClearColor(osg::Vec4( + backgroundColour.redF(), + backgroundColour.greenF(), + backgroundColour.blueF(), + 1.0f + )); + if (mGradientCamera.get() != nullptr && mView->getCamera()->containsNode(mGradientCamera.get())) { + // Remove the child to prevent the gradient from rendering + mView->getCamera()->removeChild(mGradientCamera.get()); + } + } } CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 6a94254b99..d7d9dba0c5 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -100,10 +100,15 @@ namespace CSVRender void mouseMoveEvent (QMouseEvent *event) override; void wheelEvent (QWheelEvent *event) override; + osg::ref_ptr createGradientRectangle(QColor bgColour, QColor gradientColour); + osg::ref_ptr createGradientCamera(QColor bgColour, QColor gradientColour); + void updateGradientCamera(QColor bgColour, QColor gradientColour); + std::shared_ptr mResourceSystem; Lighting* mLighting; - + + osg::ref_ptr mGradientCamera; osg::Vec4f mDefaultAmbient; bool mHasDefaultAmbient; bool mIsExterior; From d35715e5dcc7023cbac7949415c10ba94bc56c34 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 10 Dec 2020 22:39:54 +0100 Subject: [PATCH 0068/2859] add olcoal to authors and feature 5199 to changelog --- AUTHORS.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 09ab784125..e8134a681e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -49,6 +49,7 @@ Programmers Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) + Coleman Smith (olcoal) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) crussell187 diff --git a/CHANGELOG.md b/CHANGELOG.md index b16480b02d..219234a388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ Feature #2686: Timestamps in openmw.log Feature #4894: Consider actors as obstacles for pathfinding Feature #5043: Head Bobbing + Feature #5199: Improve Scene Colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher Feature #5362: Show the soul gems' trapped soul in count dialog Feature #5445: Handle NiLines From 3b9db413463dfc30d97836bf164066ead187c305 Mon Sep 17 00:00:00 2001 From: Coleman Smith Date: Thu, 10 Dec 2020 17:03:10 -0800 Subject: [PATCH 0069/2859] removing unneeded variable --- apps/opencs/view/render/scenewidget.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index b2648da675..4d73cde153 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -301,8 +301,6 @@ osg::ref_ptr SceneWidget::createGradientRectangle(QColor bgColour colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); - osg::Vec4ub bgVec = osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f); - geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); From 32601e0ae48da869347b9509d4d5e05049c0f6c3 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 11 Dec 2020 08:56:28 +0300 Subject: [PATCH 0070/2859] Properly reserve body parts for skirts (bug #5731) --- CHANGELOG.md | 1 + apps/opencs/model/world/actoradapter.cpp | 47 ++++++------------------ 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 219234a388..d399bcb30e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE + Bug #5731: Editor: skirts are invisible on characters Feature #390: 3rd person look "over the shoulder" Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 5ed80a1e4e..8558aa9bc9 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -593,56 +593,33 @@ namespace CSMWorld } else if (type == UniversalId::Type_Clothing) { - int priority = 0; - // TODO: reserve bodyparts for robes and skirts auto& clothing = dynamic_cast&>(record).get(); + std::vector parts; if (clothing.mData.mType == ESM::Clothing::Robe) { - auto reservedList = std::vector(); - - ESM::PartReference pr; - pr.mMale = ""; - pr.mFemale = ""; - - ESM::PartReferenceType parts[] = { + parts = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, - ESM::PRT_RForearm, ESM::PRT_LForearm + ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass }; - size_t parts_size = sizeof(parts)/sizeof(parts[0]); - for(size_t p = 0;p < parts_size;++p) - { - pr.mPart = parts[p]; - reservedList.push_back(pr); - } - - priority = parts_size; - addParts(reservedList, priority); } else if (clothing.mData.mType == ESM::Clothing::Skirt) { - auto reservedList = std::vector(); + parts = {ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg}; + } + std::vector reservedList; + for (const auto& p : parts) + { ESM::PartReference pr; - pr.mMale = ""; - pr.mFemale = ""; - - ESM::PartReferenceType parts[] = { - ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg - }; - size_t parts_size = sizeof(parts)/sizeof(parts[0]); - for(size_t p = 0;p < parts_size;++p) - { - pr.mPart = parts[p]; - reservedList.push_back(pr); - } - - priority = parts_size; - addParts(reservedList, priority); + pr.mPart = p; + reservedList.emplace_back(pr); } + int priority = parts.size(); addParts(clothing.mParts.mParts, priority); + addParts(reservedList, priority); // Changing parts could affect what is picked for rendering data->addOtherDependency(itemId); From 899b8422faccc8d77ab88e5d2a38035e25c97d3f Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Thu, 10 Dec 2020 22:00:33 +0100 Subject: [PATCH 0071/2859] explicitely use a reference, auto can't infer it and make a copy --- apps/openmw/mwworld/worldimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d00fabb66d..c24597b842 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1496,7 +1496,7 @@ namespace MWWorld mProjectileManager->update(duration); - const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); + const auto& results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); mProjectileManager->processHits(); mDiscardMovements = false; From 460e5abb55d6233814c043de7f6475368573ccd1 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Thu, 10 Dec 2020 21:54:47 +0100 Subject: [PATCH 0072/2859] Update physics object position after spawning. --- apps/openmw/mwworld/scene.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 47b62862f7..631253bd43 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -253,7 +253,7 @@ namespace bool operator() (const MWWorld::Ptr& ptr) { if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) - ptr.getClass().adjustPosition (ptr, false); + ptr.getClass().adjustPosition (ptr, true); return true; } }; From 15291f15d3e5ce7e5eb777e235a308c3eb34cc02 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 16 Nov 2020 19:37:30 +0300 Subject: [PATCH 0073/2859] Make actor collision box components a struct --- apps/openmw/mwphysics/actor.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 2 +- .../nifloader/testbulletnifloader.cpp | 35 ++++++++++--------- components/nifbullet/bulletnifloader.cpp | 12 +++---- components/resource/bulletshape.cpp | 6 ++-- components/resource/bulletshape.hpp | 8 +++-- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 77b07cde53..b1f219e338 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -20,7 +20,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) : mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) - , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents) + , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 324b1db2d7..8106a78ea1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -645,7 +645,7 @@ namespace MWPhysics osg::ref_ptr shape = mShapeManager->getShape(mesh); // Try to get shape from basic model as fallback for creatures - if (!ptr.getClass().isNpc() && shape && shape->mCollisionBoxHalfExtents.length2() == 0) + if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.extents.length2() == 0) { const std::string fallbackModel = ptr.getClass().getModel(ptr); if (fallbackModel != mesh) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 7da8f0fd5b..3a854bf5c9 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -162,8 +162,8 @@ namespace Resource { return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) && compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape) - && lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents - && lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate + && lhs.mCollisionBox.extents == rhs.mCollisionBox.extents + && lhs.mCollisionBox.center == rhs.mCollisionBox.center && lhs.mAnimatedShapes == rhs.mAnimatedShapes; } @@ -172,7 +172,8 @@ namespace Resource return stream << "Resource::BulletShape {" << value.mCollisionShape << ", " << value.mAvoidCollisionShape << ", " - << "osg::Vec3f {" << value.mCollisionBoxHalfExtents << "}" << ", " + << "osg::Vec3f {" << value.mCollisionBox.extents << "}" << ", " + << "osg::Vec3f {" << value.mCollisionBox.center << "}" << ", " << value.mAnimatedShapes << "}"; } @@ -433,8 +434,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -458,8 +459,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -488,8 +489,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -523,8 +524,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -558,8 +559,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(4, 5, 6); - expected.mCollisionBoxTranslate = osg::Vec3f(-4, -5, -6); + expected.mCollisionBox.extents = osg::Vec3f(4, 5, 6); + expected.mCollisionBox.center = osg::Vec3f(-4, -5, -6); std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); @@ -581,8 +582,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } @@ -615,8 +616,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index db9d5ae437..b24d08fd8c 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -145,12 +145,12 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { if (findBoundingBox(node, filename)) { - const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); - const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); + const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.extents); + const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.center); std::unique_ptr compound (new btCompoundShape); - std::unique_ptr boxShape(new btBoxShape(halfExtents)); + std::unique_ptr boxShape(new btBoxShape(extents)); btTransform transform = btTransform::getIdentity(); - transform.setOrigin(origin); + transform.setOrigin(center); compound->addChildShape(transform, boxShape.get()); boxShape.release(); @@ -208,8 +208,8 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& switch (type) { case Nif::NiBoundingVolume::Type::BOX_BV: - mShape->mCollisionBoxHalfExtents = node->bounds.box.extents; - mShape->mCollisionBoxTranslate = node->bounds.box.center; + mShape->mCollisionBox.extents = node->bounds.box.extents; + mShape->mCollisionBox.center = node->bounds.box.center; break; default: { diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 7dd0964e8b..fc68c55458 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -20,8 +20,7 @@ BulletShape::BulletShape() BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape)) , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape)) - , mCollisionBoxHalfExtents(copy.mCollisionBoxHalfExtents) - , mCollisionBoxTranslate(copy.mCollisionBoxTranslate) + , mCollisionBox(copy.mCollisionBox) , mAnimatedShapes(copy.mAnimatedShapes) { } @@ -106,8 +105,7 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) : BulletShape() , mSource(source) { - mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents; - mCollisionBoxTranslate = source->mCollisionBoxTranslate; + mCollisionBox = source->mCollisionBox; mAnimatedShapes = source->mAnimatedShapes; diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index e77c96327f..7d9577ba02 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -27,10 +27,14 @@ namespace Resource btCollisionShape* mCollisionShape; btCollisionShape* mAvoidCollisionShape; + struct CollisionBox + { + osg::Vec3f extents; + osg::Vec3f center; + }; // Used for actors. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures. // For now, use one file <-> one resource for simplicity. - osg::Vec3f mCollisionBoxHalfExtents; - osg::Vec3f mCollisionBoxTranslate; + CollisionBox mCollisionBox; // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape // will be a btCompoundShape (which consists of one or more child shapes). From a314f196eb792e3f1bcb90f8ec40a287c9192d19 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 12 Dec 2020 18:02:46 +0100 Subject: [PATCH 0074/2859] Unconditionally call actor->resetPosition in adjustPosition. Revert broken change that would force adjust all actors in cell upon loading. That break floating corpses (and probably others things). --- apps/openmw/mwworld/scene.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 631253bd43..47b62862f7 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -253,7 +253,7 @@ namespace bool operator() (const MWWorld::Ptr& ptr) { if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) - ptr.getClass().adjustPosition (ptr, true); + ptr.getClass().adjustPosition (ptr, false); return true; } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c24597b842..f59c92c6a6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1344,12 +1344,8 @@ namespace MWWorld } moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); - if (force) // force physics to use the new position - { - auto actor = mPhysics->getActor(ptr); - if(actor) - actor->resetPosition(); - } + if (ptr.getClass().isActor()) + mPhysics->getActor(ptr)->resetPosition(); } void World::fixPosition() From 28c79227df36a3b27cce106837650842dbcec034 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 12 Dec 2020 21:04:59 +0200 Subject: [PATCH 0075/2859] Editor changelog additions --- CHANGELOG.md | 2 ++ CHANGELOG_PR.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac8168ebc..d896ca55cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.47.0 ------ + Bug #832: OpenMW-CS: Handle deleted references Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path Bug #1952: Incorrect particle lighting Bug #2069: Fireflies in Fireflies invade Morrowind look wrong @@ -67,6 +68,7 @@ Bug #5644: Summon effects running on the player during game initialization cause crashes Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval + Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 6a16b76585..8a7948bb70 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -34,7 +34,9 @@ Bug Fixes: - Morrowind legacy madness: Using a key on a trapped door/container now only disarms the trap if the door/container is locked (#5370) Editor Bug Fixes: +- Deleted and moved objects within a cell are now saved properly (#832) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) +- Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) Miscellaneous: From 73afc55462ba1fc11ebebf83cbeb9cfcb2738bd1 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Sat, 12 Dec 2020 17:29:29 +0100 Subject: [PATCH 0076/2859] Fork the current process to monitor exe, generate minidump on crash --- components/CMakeLists.txt | 8 +- .../crashcatcher/windows_crashcatcher.cpp | 200 ++++++++++++++++++ .../crashcatcher/windows_crashcatcher.hpp | 79 +++++++ .../crashcatcher/windows_crashmonitor.cpp | 185 ++++++++++++++++ .../crashcatcher/windows_crashmonitor.hpp | 49 +++++ components/crashcatcher/windows_crashshm.hpp | 44 ++++ components/debug/debugging.cpp | 9 +- 7 files changed, 571 insertions(+), 3 deletions(-) create mode 100644 components/crashcatcher/windows_crashcatcher.cpp create mode 100644 components/crashcatcher/windows_crashcatcher.hpp create mode 100644 components/crashcatcher/windows_crashmonitor.cpp create mode 100644 components/crashcatcher/windows_crashmonitor.hpp create mode 100644 components/crashcatcher/windows_crashshm.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3c18990375..10a5d22fbe 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -151,7 +151,13 @@ add_component_dir (fallback fallback validate ) -if(NOT WIN32 AND NOT ANDROID) +if(WIN32) + add_component_dir (crashcatcher + windows_crashcatcher + windows_crashmonitor + windows_crashshm + ) +elseif(NOT ANDROID) add_component_dir (crashcatcher crashcatcher ) diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp new file mode 100644 index 0000000000..3291631c10 --- /dev/null +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include + +#include "windows_crashcatcher.hpp" +#include "windows_crashmonitor.hpp" +#include "windows_crashshm.hpp" +#include + +namespace Crash +{ + + HANDLE duplicateHandle(HANDLE handle) + { + HANDLE duplicate; + if (!DuplicateHandle(GetCurrentProcess(), handle, + GetCurrentProcess(), &duplicate, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + throw std::runtime_error("Crash monitor could not duplicate handle"); + } + return duplicate; + } + + CrashCatcher* CrashCatcher::sInstance = nullptr; + + CrashCatcher::CrashCatcher(int argc, char **argv, const std::string& crashLogPath) + { + assert(sInstance == nullptr); // don't allow two instances + + sInstance = this; + + HANDLE shmHandle = nullptr; + for (int i=0; i= argc - 1) + throw std::runtime_error("Crash monitor is missing the SHM handle argument"); + + sscanf(argv[i + 1], "%p", &shmHandle); + break; + } + + if (!shmHandle) + { + setupIpc(); + startMonitorProcess(crashLogPath); + installHandler(); + } + else + { + CrashMonitor(shmHandle).run(); + exit(0); + } + } + + CrashCatcher::~CrashCatcher() + { + sInstance = nullptr; + + if (mShm && mSignalMonitorEvent) + { + shmLock(); + mShm->mEvent = CrashSHM::Event::Shutdown; + shmUnlock(); + + SetEvent(mSignalMonitorEvent); + } + + if (mShmHandle) + CloseHandle(mShmHandle); + } + + void CrashCatcher::setupIpc() + { + SECURITY_ATTRIBUTES attributes; + ZeroMemory(&attributes, sizeof(attributes)); + attributes.bInheritHandle = TRUE; + + mSignalAppEvent = CreateEventW(&attributes, FALSE, FALSE, NULL); + mSignalMonitorEvent = CreateEventW(&attributes, FALSE, FALSE, NULL); + + mShmHandle = CreateFileMappingW(INVALID_HANDLE_VALUE, &attributes, PAGE_READWRITE, HIWORD(sizeof(CrashSHM)), LOWORD(sizeof(CrashSHM)), NULL); + if (mShmHandle == nullptr) + throw std::runtime_error("Failed to allocate crash catcher shared memory"); + + mShm = reinterpret_cast(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM))); + if (mShm == nullptr) + throw std::runtime_error("Failed to map crash catcher shared memory"); + + mShmMutex = CreateMutexW(&attributes, FALSE, NULL); + if (mShmMutex == nullptr) + throw std::runtime_error("Failed to create crash catcher shared memory mutex"); + } + + void CrashCatcher::shmLock() + { + if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0) + throw std::runtime_error("SHM lock timed out"); + } + + void CrashCatcher::shmUnlock() + { + ReleaseMutex(mShmMutex); + } + + void CrashCatcher::waitMonitor() + { + if (WaitForSingleObject(mSignalAppEvent, CrashCatcherTimeout) != WAIT_OBJECT_0) + throw std::runtime_error("Waiting for monitor failed"); + } + + void CrashCatcher::signalMonitor() + { + SetEvent(mSignalMonitorEvent); + } + + void CrashCatcher::installHandler() + { + SetUnhandledExceptionFilter(vectoredExceptionHandler); + } + + void CrashCatcher::startMonitorProcess(const std::string& crashLogPath) + { + WCHAR executablePath[MAX_PATH + 1]; + GetModuleFileNameW(NULL, executablePath, MAX_PATH + 1); + + memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath)); + int length = crashLogPath.length(); + if (length > MAX_PATH) length = MAX_PATH; + strncpy(mShm->mStartup.mLogFilePath, crashLogPath.c_str(), length); + mShm->mStartup.mLogFilePath[length] = '\0'; + + // note that we don't need to lock the SHM here, the other process has not started yet + mShm->mEvent = CrashSHM::Event::Startup; + mShm->mStartup.mShmMutex = duplicateHandle(mShmMutex); + mShm->mStartup.mAppProcessHandle = duplicateHandle(GetCurrentProcess()); + mShm->mStartup.mSignalApp = duplicateHandle(mSignalAppEvent); + mShm->mStartup.mSignalMonitor = duplicateHandle(mSignalMonitorEvent); + + std::wstringstream ss; + ss << "--crash-monitor " << std::hex << duplicateHandle(mShmHandle); + std::wstring argumetns(ss.str()); + + STARTUPINFOW si; + ZeroMemory(&si, sizeof(si)); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + if (!CreateProcessW(executablePath, &argumetns[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + throw std::runtime_error("Could not start crash monitor process"); + + waitMonitor(); + } + + LONG CrashCatcher::vectoredExceptionHandler(PEXCEPTION_POINTERS info) + { + switch (info->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_SINGLE_STEP: + case EXCEPTION_BREAKPOINT: + case DBG_PRINTEXCEPTION_C: + return EXCEPTION_EXECUTE_HANDLER; + } + if (!sInstance) + return EXCEPTION_EXECUTE_HANDLER; + + sInstance->handleVectoredException(info); + + _Exit(1); + + return EXCEPTION_CONTINUE_SEARCH; + } + + void CrashCatcher::handleVectoredException(PEXCEPTION_POINTERS info) + { + shmLock(); + + mShm->mEvent = CrashSHM::Event::Crashed; + mShm->mCrashed.mThreadId = GetCurrentThreadId(); + mShm->mCrashed.mContext = *info->ContextRecord; + mShm->mCrashed.mExceptionRecord = *info->ExceptionRecord; + + shmUnlock(); + + signalMonitor(); + + // must remain until monitor has finished + waitMonitor(); + + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !"; + SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); + } + +} // namespace Crash diff --git a/components/crashcatcher/windows_crashcatcher.hpp b/components/crashcatcher/windows_crashcatcher.hpp new file mode 100644 index 0000000000..e1857271e9 --- /dev/null +++ b/components/crashcatcher/windows_crashcatcher.hpp @@ -0,0 +1,79 @@ +#ifndef WINDOWS_CRASHCATCHER_HPP +#define WINDOWS_CRASHCATCHER_HPP + +#include + +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include + +#include + +namespace Crash +{ + + // The implementation spawns the current executable as a monitor process which waits + // for a global synchronization event which is sent when the parent process crashes. + // The monitor process then extracts crash information from the parent process while + // the parent process waits for the monitor process to finish. The crashed process + // quits and the monitor writes the crash information to a file. + // + // To detect unexpected shutdowns of the application which are not handled by the + // crash handler, the monitor periodically checks the exit code of the parent + // process and exits if it does not return STILL_ACTIVE. You can test this by closing + // the main openmw process in task manager. + + static constexpr const int CrashCatcherTimeout = 2500; + + struct CrashSHM; + + class CrashCatcher final + { + public: + + CrashCatcher(int argc, char **argv, const std::string& crashLogPath); + ~CrashCatcher(); + + private: + + static CrashCatcher* sInstance; + + // mapped SHM area + CrashSHM* mShm = nullptr; + // the handle is allocated by the catcher and passed to the monitor + // process via the command line which maps the SHM and sends / receives + // events through it + HANDLE mShmHandle = nullptr; + // mutex which guards SHM area + HANDLE mShmMutex = nullptr; + + // triggered when the monitor signals the application + HANDLE mSignalAppEvent = INVALID_HANDLE_VALUE; + + // triggered when the application wants to wake the monitor process + HANDLE mSignalMonitorEvent = INVALID_HANDLE_VALUE; + + void setupIpc(); + + void shmLock(); + + void shmUnlock(); + + void startMonitorProcess(const std::string& crashLogPath); + + void waitMonitor(); + + void signalMonitor(); + + void installHandler(); + + void handleVectoredException(PEXCEPTION_POINTERS info); + + public: + + static LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS info); + }; + +} // namespace Crash + +#endif // WINDOWS_CRASHCATCHER_HPP diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp new file mode 100644 index 0000000000..56f8bbdd4c --- /dev/null +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -0,0 +1,185 @@ +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include + +#include +#include +#include + +#include "windows_crashcatcher.hpp" +#include "windows_crashmonitor.hpp" +#include "windows_crashshm.hpp" +#include + +namespace Crash +{ + + CrashMonitor::CrashMonitor(HANDLE shmHandle) + : mShmHandle(shmHandle) + { + mShm = reinterpret_cast(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM))); + if (mShm == nullptr) + throw std::runtime_error("Failed to map crash monitor shared memory"); + + // accessing SHM without lock is OK here, the parent waits for a signal before continuing + + mShmMutex = mShm->mStartup.mShmMutex; + mAppProcessHandle = mShm->mStartup.mAppProcessHandle; + mSignalAppEvent = mShm->mStartup.mSignalApp; + mSignalMonitorEvent = mShm->mStartup.mSignalMonitor; + } + + CrashMonitor::~CrashMonitor() + { + if (mShm) + UnmapViewOfFile(mShm); + + // the handles received from the app are duplicates, we must close them + + if (mShmHandle) + CloseHandle(mShmHandle); + + if (mShmMutex) + CloseHandle(mShmMutex); + + if (mSignalAppEvent) + CloseHandle(mSignalAppEvent); + + if (mSignalMonitorEvent) + CloseHandle(mSignalMonitorEvent); + } + + void CrashMonitor::shmLock() + { + if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0) + throw std::runtime_error("SHM monitor lock timed out"); + } + + void CrashMonitor::shmUnlock() + { + ReleaseMutex(mShmMutex); + } + + void CrashMonitor::signalApp() const + { + SetEvent(mSignalAppEvent); + } + + bool CrashMonitor::waitApp() const + { + return WaitForSingleObject(mSignalMonitorEvent, CrashCatcherTimeout) == WAIT_OBJECT_0; + } + + bool CrashMonitor::isAppAlive() const + { + DWORD code = 0; + GetExitCodeProcess(mAppProcessHandle, &code); + return code == STILL_ACTIVE; + } + + void CrashMonitor::run() + { + try + { + // app waits for monitor start up, let it continue + signalApp(); + + bool running = true; + while (isAppAlive() && running) + { + if (waitApp()) + { + shmLock(); + + switch (mShm->mEvent) + { + case CrashSHM::Event::None: + break; + case CrashSHM::Event::Crashed: + handleCrash(); + running = false; + break; + case CrashSHM::Event::Shutdown: + running = false; + break; + case CrashSHM::Event::Startup: + break; + } + + shmUnlock(); + } + } + + } + catch (...) + { + Log(Debug::Error) << "Exception in crash monitor, exiting"; + } + signalApp(); + } + + std::wstring utf8ToUtf16(const std::string& utf8) + { + const int nLenWide = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), nullptr, 0); + + std::wstring utf16; + utf16.resize(nLenWide); + if (MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), utf16.data(), nLenWide) != nLenWide) + return {}; + + return utf16; + } + + void CrashMonitor::handleCrash() + { + DWORD processId = GetProcessId(mAppProcessHandle); + + try + { + HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); + if (dbghelp == NULL) + return; + + using MiniDumpWirteDumpFn = BOOL (WINAPI*)( + HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + + MiniDumpWirteDumpFn miniDumpWriteDump = (MiniDumpWirteDumpFn)GetProcAddress(dbghelp, "MiniDumpWriteDump"); + if (miniDumpWriteDump == NULL) + return; + + const std::wstring utf16Path = utf8ToUtf16(mShm->mStartup.mLogFilePath); + if (utf16Path.empty()) + return; + + HANDLE hCrashLog = CreateFileW(utf16Path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hCrashLog == NULL || hCrashLog == INVALID_HANDLE_VALUE) + return; + if (auto err = GetLastError(); err != ERROR_ALREADY_EXISTS && err != 0) + return; + + EXCEPTION_POINTERS exp; + exp.ContextRecord = &mShm->mCrashed.mContext; + exp.ExceptionRecord = &mShm->mCrashed.mExceptionRecord; + MINIDUMP_EXCEPTION_INFORMATION infos = {}; + infos.ThreadId = mShm->mCrashed.mThreadId; + infos.ExceptionPointers = &exp; + infos.ClientPointers = FALSE; + MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithHandleData); + miniDumpWriteDump(mAppProcessHandle, processId, hCrashLog, type, &infos, 0, 0); + } + catch (const std::exception&e) + { + Log(Debug::Error) << "CrashMonitor: " << e.what(); + } + catch (...) + { + Log(Debug::Error) << "CrashMonitor: unknown exception"; + } + } + +} // namespace Crash diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp new file mode 100644 index 0000000000..678d38435c --- /dev/null +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -0,0 +1,49 @@ +#ifndef WINDOWS_CRASHMONITOR_HPP +#define WINDOWS_CRASHMONITOR_HPP + +#include + +namespace Crash +{ + +struct CrashSHM; + +class CrashMonitor final +{ +public: + + CrashMonitor(HANDLE shmHandle); + + ~CrashMonitor(); + + void run(); + +private: + + HANDLE mAppProcessHandle = nullptr; + + // triggered when the monitor process wants to wake the parent process (received via SHM) + HANDLE mSignalAppEvent = nullptr; + // triggered when the application wants to wake the monitor process (received via SHM) + HANDLE mSignalMonitorEvent = nullptr; + + CrashSHM* mShm = nullptr; + HANDLE mShmHandle = nullptr; + HANDLE mShmMutex = nullptr; + + void signalApp() const; + + bool waitApp() const; + + bool isAppAlive() const; + + void shmLock(); + + void shmUnlock(); + + void handleCrash(); +}; + +} // namespace Crash + +#endif // WINDOWS_CRASHMONITOR_HPP diff --git a/components/crashcatcher/windows_crashshm.hpp b/components/crashcatcher/windows_crashshm.hpp new file mode 100644 index 0000000000..80e47f07b7 --- /dev/null +++ b/components/crashcatcher/windows_crashshm.hpp @@ -0,0 +1,44 @@ +#ifndef WINDOWS_CRASHSHM_HPP +#define WINDOWS_CRASHSHM_HPP + +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include + +namespace Crash +{ + + // Used to communicate between the app and the monitor, fields are is overwritten with each event. + + struct CrashSHM + { + enum class Event + { + None, + Startup, + Crashed, + Shutdown + }; + + Event mEvent; + + struct Startup + { + HANDLE mAppProcessHandle; + HANDLE mSignalApp; + HANDLE mSignalMonitor; + HANDLE mShmMutex; + char mLogFilePath[MAX_PATH + 1]; + } mStartup; + + struct Crashed + { + DWORD mThreadId; + CONTEXT mContext; + EXCEPTION_RECORD mExceptionRecord; + } mCrashed; + }; + +} // namespace Crash + +#endif // WINDOWS_CRASHSHM_HPP diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 987a3db7e1..657c04d0f6 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -2,10 +2,12 @@ #include #include +#include #include #ifdef _WIN32 +# include # undef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # include @@ -187,13 +189,16 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c std::cerr.rdbuf (&cerrsb); #endif +#if defined(_WIN32) + Crash::CrashCatcher crashy(argc, argv, (cfgMgr.getLogPath() / crashLogName).make_preferred().string()); +#else // install the crash handler as soon as possible. note that the log path // does not depend on config being read. crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string()); - +#endif ret = innerApplication(argc, argv); } - catch (std::exception& e) + catch (const std::exception& e) { #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) if (!isatty(fileno(stdin))) From 817ac4cfbdc96a79d751cb5bb9b8d8e8ca78a865 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 12 Dec 2020 22:43:06 +0300 Subject: [PATCH 0077/2859] Don't regenerate the topics list unconditionally --- apps/openmw/mwgui/dialogue.cpp | 15 ++++++++++----- apps/openmw/mwgui/dialogue.hpp | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index fde029d77f..ace051a025 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -451,6 +451,7 @@ namespace MWGui setTitle(mPtr.getClass().getName(mPtr)); updateTopics(); + updateTopicsPane(); // force update for new services updateDisposition(); restock(); @@ -487,12 +488,14 @@ namespace MWGui mHistoryContents.clear(); } - void DialogueWindow::setKeywords(std::list keyWords) + bool DialogueWindow::setKeywords(std::list keyWords) { if (mKeywords == keyWords && isCompanion() == mIsCompanion) - return; + return false; mIsCompanion = isCompanion(); mKeywords = keyWords; + updateTopicsPane(); + return true; } void DialogueWindow::updateTopicsPane() @@ -556,6 +559,8 @@ namespace MWGui mTopicsList->adjustSize(); updateHistory(); + // The topics list has been regenerated so topic formatting needs to be updated + updateTopicFormat(); } void DialogueWindow::updateHistory(bool scrollbar) @@ -758,9 +763,9 @@ namespace MWGui void DialogueWindow::updateTopics() { - setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()); - updateTopicsPane(); - updateTopicFormat(); + // Topic formatting needs to be updated regardless of whether the topic list has changed + if (!setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics())) + updateTopicFormat(); } bool DialogueWindow::isCompanion() diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index ed4c39afed..02401f2e14 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -122,7 +122,8 @@ namespace MWGui void setPtr(const MWWorld::Ptr& actor) override; - void setKeywords(std::list keyWord); + /// @return true if stale keywords were updated successfully + bool setKeywords(std::list keyWord); void addResponse (const std::string& title, const std::string& text, bool needMargin = true); From 256aa5e71d59b817753dbe440ac3ee06e186dc7a Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 12 Dec 2020 22:23:20 +0200 Subject: [PATCH 0078/2859] Use const auto& --- components/resource/animation.cpp | 4 ++-- components/resource/keyframemanager.cpp | 2 +- components/sceneutil/osgacontroller.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/resource/animation.cpp b/components/resource/animation.cpp index 34ae162ee7..d2d7d08d55 100644 --- a/components/resource/animation.cpp +++ b/components/resource/animation.cpp @@ -10,7 +10,7 @@ namespace Resource mStartTime(0.0f) { const osgAnimation::ChannelList& channels = anim.getChannels(); - for (const osg::ref_ptr channel: channels) + for (const auto& channel: channels) addChannel(channel.get()->clone()); } @@ -31,7 +31,7 @@ namespace Resource bool Animation::update (double time) { - for (const osg::ref_ptr channel: mChannels) + for (const auto& channel: mChannels) { channel->update(time, 1.0f, 0); } diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index af0f365ee5..41b10bd65d 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -44,7 +44,7 @@ namespace Resource std::string loopstop = animationName + std::string(": loop stop"); const osgAnimation::ChannelList& channels = animation->getChannels(); - for (const osg::ref_ptr channel: channels) + for (const auto& channel: channels) { mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? } diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 87e6f02fee..d77b8d666c 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -31,7 +31,7 @@ namespace SceneUtil void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) { const osgAnimation::ChannelList& channels = mAnimation->getChannels(); - for (const osg::ref_ptr channel: channels) + for (const auto& channel: channels) { const std::string& channelName = channel->getName(); const std::string& channelTargetName = channel->getTargetName(); @@ -125,13 +125,13 @@ namespace SceneUtil } //Find the root transform track in animation - for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() != animationName) continue; const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); - for (const osg::ref_ptr channel: channels) + for (const auto& channel: channels) { if (channel->getTargetName() != "root" || channel->getName() != "transform") continue; @@ -150,7 +150,7 @@ namespace SceneUtil void OsgAnimationController::update(float time, std::string animationName) { - for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() == animationName) mergedAnimationTrack->update(time); } @@ -162,7 +162,7 @@ namespace SceneUtil { if (mNeedToLink) { - for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (!mLinker.valid()) mLinker = new LinkVisitor(); mLinker->setAnimation(mergedAnimationTrack); From e2041de9690f4257c2b2b01a4cf9704a6da23068 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Sat, 12 Dec 2020 21:47:50 +0100 Subject: [PATCH 0079/2859] Use the incremental approach to handle long path --- components/crashcatcher/windows_crashcatcher.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp index 3291631c10..129907fc3b 100644 --- a/components/crashcatcher/windows_crashcatcher.cpp +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -126,8 +126,13 @@ namespace Crash void CrashCatcher::startMonitorProcess(const std::string& crashLogPath) { - WCHAR executablePath[MAX_PATH + 1]; - GetModuleFileNameW(NULL, executablePath, MAX_PATH + 1); + std::wstring executablePath; + DWORD copied = 0; + do { + executablePath.resize(executablePath.size() + MAX_PATH); + copied = GetModuleFileNameW(nullptr, &executablePath[0], executablePath.size()); + } while (copied >= executablePath.size()); + executablePath.resize(copied); memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath)); int length = crashLogPath.length(); @@ -152,7 +157,7 @@ namespace Crash PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); - if (!CreateProcessW(executablePath, &argumetns[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + if (!CreateProcessW(&executablePath[0], &argumetns[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) throw std::runtime_error("Could not start crash monitor process"); waitMonitor(); From 3eb2b321239b294c4922e38c0af88c344ab136a7 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Sun, 13 Dec 2020 14:09:14 +0100 Subject: [PATCH 0080/2859] Fix typpo issue on arguments --- components/crashcatcher/windows_crashcatcher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp index 129907fc3b..8495e84f8d 100644 --- a/components/crashcatcher/windows_crashcatcher.cpp +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -149,7 +149,7 @@ namespace Crash std::wstringstream ss; ss << "--crash-monitor " << std::hex << duplicateHandle(mShmHandle); - std::wstring argumetns(ss.str()); + std::wstring arguments(ss.str()); STARTUPINFOW si; ZeroMemory(&si, sizeof(si)); @@ -157,7 +157,7 @@ namespace Crash PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); - if (!CreateProcessW(&executablePath[0], &argumetns[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + if (!CreateProcessW(&executablePath[0], &arguments[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) throw std::runtime_error("Could not start crash monitor process"); waitMonitor(); From f400116bcd139c44cb8622670a0403f1f3222580 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Sun, 13 Dec 2020 14:09:44 +0100 Subject: [PATCH 0081/2859] Use 32767 characters for log path --- components/crashcatcher/windows_crashcatcher.cpp | 2 +- components/crashcatcher/windows_crashshm.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp index 8495e84f8d..dc10f4dcb5 100644 --- a/components/crashcatcher/windows_crashcatcher.cpp +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -136,7 +136,7 @@ namespace Crash memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath)); int length = crashLogPath.length(); - if (length > MAX_PATH) length = MAX_PATH; + if (length > MAX_LONG_PATH) length = MAX_LONG_PATH; strncpy(mShm->mStartup.mLogFilePath, crashLogPath.c_str(), length); mShm->mStartup.mLogFilePath[length] = '\0'; diff --git a/components/crashcatcher/windows_crashshm.hpp b/components/crashcatcher/windows_crashshm.hpp index 80e47f07b7..47929a45fe 100644 --- a/components/crashcatcher/windows_crashshm.hpp +++ b/components/crashcatcher/windows_crashshm.hpp @@ -9,6 +9,7 @@ namespace Crash { // Used to communicate between the app and the monitor, fields are is overwritten with each event. + static constexpr const int MAX_LONG_PATH = 0x7fff; struct CrashSHM { @@ -28,7 +29,7 @@ namespace Crash HANDLE mSignalApp; HANDLE mSignalMonitor; HANDLE mShmMutex; - char mLogFilePath[MAX_PATH + 1]; + char mLogFilePath[MAX_LONG_PATH]; } mStartup; struct Crashed From adeb4fe02f92637395c4b1742f2d60405105c75c Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Sun, 13 Dec 2020 14:10:44 +0100 Subject: [PATCH 0082/2859] Handle case where the log path has more that MAX_PATH characters --- components/crashcatcher/windows_crashmonitor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 56f8bbdd4c..8976deb2ea 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -152,10 +152,13 @@ namespace Crash if (miniDumpWriteDump == NULL) return; - const std::wstring utf16Path = utf8ToUtf16(mShm->mStartup.mLogFilePath); + std::wstring utf16Path = utf8ToUtf16(mShm->mStartup.mLogFilePath); if (utf16Path.empty()) return; + if (utf16Path.length() > MAX_PATH) + utf16Path = LR"(\\?\)" + utf16Path; + HANDLE hCrashLog = CreateFileW(utf16Path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hCrashLog == NULL || hCrashLog == INVALID_HANDLE_VALUE) return; From 112437cf287b7873ec04b54dbf01f64822740b69 Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Sun, 13 Dec 2020 14:13:07 +0100 Subject: [PATCH 0083/2859] Change crash file to dmp on window to avoid renaming it --- components/debug/debugging.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 657c04d0f6..e9bcf8ad75 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -165,7 +165,6 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c #endif const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; - const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log"; boost::filesystem::ofstream logfile; int ret = 0; @@ -190,8 +189,10 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c #endif #if defined(_WIN32) + const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp"; Crash::CrashCatcher crashy(argc, argv, (cfgMgr.getLogPath() / crashLogName).make_preferred().string()); #else + const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log"; // install the crash handler as soon as possible. note that the log path // does not depend on config being read. crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string()); From cc5c6fe3ad642394bdf64983ee1e3367c896cd4c Mon Sep 17 00:00:00 2001 From: CedricMocquillon Date: Sun, 13 Dec 2020 19:03:04 +0100 Subject: [PATCH 0084/2859] Use data() method --- components/crashcatcher/windows_crashcatcher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp index dc10f4dcb5..4c3dfa8f63 100644 --- a/components/crashcatcher/windows_crashcatcher.cpp +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -130,7 +130,7 @@ namespace Crash DWORD copied = 0; do { executablePath.resize(executablePath.size() + MAX_PATH); - copied = GetModuleFileNameW(nullptr, &executablePath[0], executablePath.size()); + copied = GetModuleFileNameW(nullptr, executablePath.data(), executablePath.size()); } while (copied >= executablePath.size()); executablePath.resize(copied); @@ -157,7 +157,7 @@ namespace Crash PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); - if (!CreateProcessW(&executablePath[0], &arguments[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + if (!CreateProcessW(executablePath.data(), arguments.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) throw std::runtime_error("Could not start crash monitor process"); waitMonitor(); From 201999c4a99866a64795aef6852f066663e79625 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 14 Dec 2020 03:01:26 +0300 Subject: [PATCH 0085/2859] Make sure adjustPosition() is safe to call for any actor --- apps/openmw/mwworld/worldimp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f59c92c6a6..3756ea04b8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1345,7 +1345,11 @@ namespace MWWorld moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); if (ptr.getClass().isActor()) - mPhysics->getActor(ptr)->resetPosition(); + { + MWPhysics::Actor* actor = mPhysics->getActor(ptr); + if (actor) + actor->resetPosition(); + } } void World::fixPosition() From ab789e37e1136465c8bfdb1ccb1b3aee6a9f52cf Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 14 Dec 2020 03:45:03 +0300 Subject: [PATCH 0086/2859] Wizard: fix "file is already opened" warning --- apps/wizard/mainwizard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 160c6f7217..d92f5b029c 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -157,6 +157,8 @@ void Wizard::MainWizard::setupGameSettings() mGameSettings.readUserFile(stream); } + file.close(); + // Now the rest QStringList paths; paths.append(userPath + QLatin1String("openmw.cfg")); From 1c83e4936d30d4873b785b62d43e3718547fbd0e Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 13 Dec 2020 02:35:57 +0300 Subject: [PATCH 0087/2859] Read BSShaderTextureSet and NiColorInterpolator Accept boolean-based and 4D vector-based NiInterpolators in ValueInterpolator constructor --- components/nif/controlled.cpp | 5 +++++ components/nif/controlled.hpp | 18 ++++++++++++++++++ components/nif/controller.cpp | 11 +++++++++++ components/nif/controller.hpp | 8 ++++++++ components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 4 +++- components/nif/recordptr.hpp | 2 ++ components/nifosg/controller.hpp | 4 +++- 8 files changed, 52 insertions(+), 2 deletions(-) diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index ab2b8dc173..c8116ce74e 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -47,6 +47,11 @@ namespace Nif data.post(nif); } + void BSShaderTextureSet::read(NIFStream *nif) + { + nif->getSizedStrings(textures, nif->getUInt()); + } + void NiParticleModifier::read(NIFStream *nif) { next.read(nif); diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 57d538f839..ac48e50498 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -66,6 +66,24 @@ public: void post(NIFFile *nif) override; }; +struct BSShaderTextureSet : public Record +{ + enum TextureType + { + TextureType_Base = 0, + TextureType_Normal = 1, + TextureType_Glow = 2, + TextureType_Parallax = 3, + TextureType_Env = 4, + TextureType_EnvMask = 5, + TextureType_Subsurface = 6, + TextureType_BackLighting = 7 + }; + std::vector textures; + + void read(NIFStream *nif) override; +}; + struct NiParticleModifier : public Record { NiParticleModifierPtr next; diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 1e909120e4..704c4928e6 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -325,4 +325,15 @@ namespace Nif data.post(nif); } + void NiColorInterpolator::read(NIFStream *nif) + { + defaultVal = nif->getVector4(); + data.read(nif); + } + + void NiColorInterpolator::post(NIFFile *nif) + { + data.post(nif); + } + } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 6b84d3749b..71afdef264 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -230,5 +230,13 @@ struct NiTransformInterpolator : public Interpolator void post(NIFFile *nif) override; }; +struct NiColorInterpolator : public Interpolator +{ + osg::Vec4f defaultVal; + NiColorDataPtr data; + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + } // Namespace #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 67c864f473..c96bf30d63 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -129,6 +129,8 @@ static std::map makeFactory() factory["NiPoint3Interpolator"] = {&construct , RC_NiPoint3Interpolator }; factory["NiTransformController"] = {&construct , RC_NiKeyframeController }; factory["NiTransformInterpolator"] = {&construct , RC_NiTransformInterpolator }; + factory["NiColorInterpolator"] = {&construct , RC_NiColorInterpolator }; + factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; return factory; } diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 2217c588ee..041122731a 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -118,7 +118,9 @@ enum RecordType RC_NiFloatInterpolator, RC_NiPoint3Interpolator, RC_NiBoolInterpolator, - RC_NiTransformInterpolator + RC_NiTransformInterpolator, + RC_NiColorInterpolator, + RC_BSShaderTextureSet }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index ed8f7ef6b9..0f5615a687 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -147,6 +147,7 @@ struct NiSkinPartition; struct NiFloatInterpolator; struct NiPoint3Interpolator; struct NiTransformInterpolator; +struct BSShaderTextureSet; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -174,6 +175,7 @@ using NiSkinPartitionPtr = RecordPtrT; using NiFloatInterpolatorPtr = RecordPtrT; using NiPoint3InterpolatorPtr = RecordPtrT; using NiTransformInterpolatorPtr = RecordPtrT; +using BSShaderTextureSetPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 0063b2ec0f..96beafcbb8 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -66,7 +66,9 @@ namespace NifOsg std::conjunction_v< std::disjunction< std::is_same, - std::is_same + std::is_same, + std::is_same, + std::is_same >, std::is_same >, From 5310dd6807a25564399fbff4cda9442d88bac800 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 13 Dec 2020 02:46:33 +0300 Subject: [PATCH 0088/2859] Clean up particle vertex handling --- components/nif/controller.hpp | 2 +- components/nifosg/nifloader.cpp | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 71afdef264..4016188ce0 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -37,7 +37,7 @@ public: float lifetime; float lifespan; float timestamp; - int vertex; + unsigned short vertex; }; float velocity; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0db15237ec..b172e386e7 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -963,6 +963,9 @@ namespace NifOsg if (particle.lifespan <= 0) continue; + if (particle.vertex >= particledata->vertices.size()) + continue; + ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime)); osgParticle::Particle* created = partsys->createParticle(&particletemplate); @@ -971,16 +974,16 @@ namespace NifOsg // Note this position and velocity is not correct for a particle system with absolute reference frame, // which can not be done in this loader since we are not attached to the scene yet. Will be fixed up post-load in the SceneManager. created->setVelocity(particle.velocity); - const osg::Vec3f& position = particledata->vertices.at(particle.vertex); + const osg::Vec3f& position = particledata->vertices[particle.vertex]; created->setPosition(position); osg::Vec4f partcolor (1.f,1.f,1.f,1.f); - if (particle.vertex < int(particledata->colors.size())) - partcolor = particledata->colors.at(particle.vertex); + if (particle.vertex < particledata->colors.size()) + partcolor = particledata->colors[particle.vertex]; float size = partctrl->size; - if (particle.vertex < int(particledata->sizes.size())) - size *= particledata->sizes.at(particle.vertex); + if (particle.vertex < particledata->sizes.size()) + size *= particledata->sizes[particle.vertex]; created->setSizeRange(osgParticle::rangef(size, size)); box.expandBy(osg::BoundingSphere(position, size)); From 8ca324af0ad83cfc6d863322420ae8542cb33971 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 13 Dec 2020 02:49:12 +0300 Subject: [PATCH 0089/2859] Handle emissive TexEnv creation in one place --- components/nifosg/nifloader.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index b172e386e7..3120b0d748 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -466,19 +466,12 @@ namespace NifOsg texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); - osg::ref_ptr texEnv = new osg::TexEnvCombine; - texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - int texUnit = 3; // FIXME osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); } @@ -1484,6 +1477,17 @@ namespace NifOsg return image; } + osg::ref_ptr createEmissiveTexEnv() + { + osg::ref_ptr texEnv(new osg::TexEnvCombine); + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + return texEnv; + } + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { if (!boundTextures.empty()) @@ -1570,14 +1574,7 @@ namespace NifOsg if (i == Nif::NiTexturingProperty::GlowTexture) { - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); } else if (i == Nif::NiTexturingProperty::DarkTexture) { From 8fd45d85ec0ae1c209c51dab942bcfba7d8be14e Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 13 Dec 2020 03:16:05 +0300 Subject: [PATCH 0090/2859] Unify NiGeometry/NiGeometryData handling --- .../nifloader/testbulletnifloader.cpp | 22 +++- components/nif/node.hpp | 121 +++++------------- components/nif/recordptr.hpp | 10 +- components/nifbullet/bulletnifloader.cpp | 51 ++++---- components/nifosg/nifloader.cpp | 81 ++++++------ 5 files changed, 115 insertions(+), 170 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 3a854bf5c9..5769b1d0a8 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -259,14 +259,19 @@ namespace value.isBone = false; } - void init(Nif::NiTriShape& value) + void init(Nif::NiGeometry& value) { init(static_cast(value)); - value.recType = Nif::RC_NiTriShape; - value.data = Nif::NiTriShapeDataPtr(nullptr); + value.data = Nif::NiGeometryDataPtr(nullptr); value.skin = Nif::NiSkinInstancePtr(nullptr); } + void init(Nif::NiTriShape& value) + { + init(static_cast(value)); + value.recType = Nif::RC_NiTriShape; + } + void init(Nif::NiSkinInstance& value) { value.data = Nif::NiSkinDataPtr(nullptr); @@ -361,13 +366,15 @@ namespace init(mNiStringExtraData2); init(mController); + mNiTriShapeData.recType = Nif::RC_NiTriShapeData; mNiTriShapeData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)}; mNiTriShapeData.triangles = {0, 1, 2}; - mNiTriShape.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData); + mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + mNiTriShapeData2.recType = Nif::RC_NiTriShapeData; mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)}; mNiTriShapeData2.triangles = {0, 1, 2}; - mNiTriShape2.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData2); + mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); } }; @@ -873,7 +880,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { - mNiTriShape.data = Nif::NiTriShapeDataPtr(nullptr); + mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -888,7 +895,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) { - mNiTriShape.data->triangles.clear(); + auto data = static_cast(mNiTriShape.data.getPtr()); + data->triangles.clear(); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 58397bdca4..3106e368bb 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -241,80 +241,52 @@ struct NiNode : Node struct NiGeometry : Node { + /* Possible flags: + 0x40 - mesh has no vertex normals ? + + Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have + been observed so far. + */ + struct MaterialData { - std::vector materialNames; - std::vector materialExtraData; - unsigned int activeMaterial{0}; - bool materialNeedsUpdate{false}; + std::vector names; + std::vector extra; + unsigned int active{0}; + bool needsUpdate{false}; void read(NIFStream *nif) { if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) return; - unsigned int numMaterials = 0; + unsigned int num = 0; if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3)) - numMaterials = nif->getBoolean(); // Has Shader + num = nif->getBoolean(); // Has Shader else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) - numMaterials = nif->getUInt(); - if (numMaterials) + num = nif->getUInt(); + if (num) { - nif->getStrings(materialNames, numMaterials); - nif->getInts(materialExtraData, numMaterials); + nif->getStrings(names, num); + nif->getInts(extra, num); } if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) - activeMaterial = nif->getUInt(); + active = nif->getUInt(); if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) - { - materialNeedsUpdate = nif->getBoolean(); - if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) - nif->skip(8); - } + needsUpdate = nif->getBoolean(); } }; + NiGeometryDataPtr data; NiSkinInstancePtr skin; - MaterialData materialData; -}; - -struct NiTriShape : NiGeometry -{ - /* Possible flags: - 0x40 - mesh has no vertex normals ? - - Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have - been observed so far. - */ - - NiTriShapeDataPtr data; - - void read(NIFStream *nif) override - { - Node::read(nif); - data.read(nif); - skin.read(nif); - materialData.read(nif); - } - - void post(NIFFile *nif) override - { - Node::post(nif); - data.post(nif); - skin.post(nif); - if (!skin.empty()) - nif->setUseSkinning(true); - } -}; - -struct NiTriStrips : NiGeometry -{ - NiTriStripsDataPtr data; + MaterialData material; void read(NIFStream *nif) override { Node::read(nif); data.read(nif); skin.read(nif); - materialData.read(nif); + material.read(nif); + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + nif->skip(8); } void post(NIFFile *nif) override @@ -322,31 +294,15 @@ struct NiTriStrips : NiGeometry Node::post(nif); data.post(nif); skin.post(nif); - if (!skin.empty()) + if (recType != RC_NiParticles && !skin.empty()) nif->setUseSkinning(true); } }; -struct NiLines : NiGeometry -{ - NiLinesDataPtr data; - - void read(NIFStream *nif) override - { - Node::read(nif); - data.read(nif); - skin.read(nif); - } - - void post(NIFFile *nif) override - { - Node::post(nif); - data.post(nif); - skin.post(nif); - if (!skin.empty()) - nif->setUseSkinning(true); - } -}; +struct NiTriShape : NiGeometry {}; +struct NiTriStrips : NiGeometry {}; +struct NiLines : NiGeometry {}; +struct NiParticles : NiGeometry { }; struct NiCamera : Node { @@ -401,25 +357,6 @@ struct NiCamera : Node } }; -struct NiParticles : NiGeometry -{ - NiParticlesDataPtr data; - void read(NIFStream *nif) override - { - Node::read(nif); - data.read(nif); - skin.read(nif); - materialData.read(nif); - } - - void post(NIFFile *nif) override - { - Node::post(nif); - data.post(nif); - skin.post(nif); - } -}; - // A node used as the base to switch between child nodes, such as for LOD levels. struct NiSwitchNode : public NiNode { diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 0f5615a687..dd6e357084 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -134,20 +134,18 @@ struct NiMorphData; class NiPixelData; class NiColorData; struct NiKeyframeData; -class NiTriShapeData; class NiTriStripsData; class NiSkinInstance; class NiSourceTexture; -class NiParticlesData; class NiPalette; struct NiParticleModifier; -struct NiLinesData; struct NiBoolData; struct NiSkinPartition; struct NiFloatInterpolator; struct NiPoint3Interpolator; struct NiTransformInterpolator; struct BSShaderTextureSet; +struct NiGeometryData; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -162,12 +160,8 @@ using NiPixelDataPtr = RecordPtrT; using NiFloatDataPtr = RecordPtrT; using NiColorDataPtr = RecordPtrT; using NiKeyframeDataPtr = RecordPtrT; -using NiTriShapeDataPtr = RecordPtrT; -using NiTriStripsDataPtr = RecordPtrT; -using NiLinesDataPtr = RecordPtrT; using NiSkinInstancePtr = RecordPtrT; using NiSourceTexturePtr = RecordPtrT; -using NiParticlesDataPtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; @@ -176,12 +170,14 @@ using NiFloatInterpolatorPtr = RecordPtrT; using NiPoint3InterpolatorPtr = RecordPtrT; using NiTransformInterpolatorPtr = RecordPtrT; using BSShaderTextureSetPtr = RecordPtrT; +using NiGeometryDataPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; using ExtraList = RecordListT; using NiSourceTextureList = RecordListT; using NiFloatInterpolatorList = RecordListT; +using NiTriStripsDataList = RecordListT; } // Namespace #endif diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index b24d08fd8c..a14672636a 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -34,11 +34,10 @@ bool pathFileNameStartsWithX(const std::string& path) void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform) { - mesh.preallocateVertices(static_cast(data.vertices.size())); - mesh.preallocateIndices(static_cast(data.triangles.size())); - const std::vector &vertices = data.vertices; const std::vector &triangles = data.triangles; + mesh.preallocateVertices(static_cast(vertices.size())); + mesh.preallocateIndices(static_cast(triangles.size())); for (std::size_t i = 0; i < triangles.size(); i += 3) { @@ -54,8 +53,6 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co { const std::vector &vertices = data.vertices; const std::vector> &strips = data.strips; - if (vertices.empty() || strips.empty()) - return; mesh.preallocateVertices(static_cast(vertices.size())); int numTriangles = 0; for (const std::vector& strip : strips) @@ -102,12 +99,12 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co } } -void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform = osg::Matrixf()) +void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry* geometry, const osg::Matrixf &transform = osg::Matrixf()) { - if (nifNode->recType == Nif::RC_NiTriShape) - fillTriangleMesh(mesh, static_cast(nifNode)->data.get(), transform); - else if (nifNode->recType == Nif::RC_NiTriStrips) - fillTriangleMesh(mesh, static_cast(nifNode)->data.get(), transform); + if (geometry->recType == Nif::RC_NiTriShape) + fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); + else if (geometry->recType == Nif::RC_NiTriStrips) + fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); } } @@ -341,23 +338,31 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons if ((flags & 0x800)) return; - if (nifNode->recType == Nif::RC_NiTriShape) + auto niGeometry = static_cast(nifNode); + if (niGeometry->data.empty() || niGeometry->data->vertices.empty()) + return; + + if (niGeometry->recType == Nif::RC_NiTriShape) { - const Nif::NiTriShape* shape = static_cast(nifNode); - if (!shape->skin.empty()) - isAnimated = false; - if (shape->data.empty() || shape->data->triangles.empty()) + if (niGeometry->data->recType != Nif::RC_NiTriShapeData) + return; + + auto data = static_cast(niGeometry->data.getPtr()); + if (data->triangles.empty()) return; } - else + else if (niGeometry->recType == Nif::RC_NiTriStrips) { - const Nif::NiTriStrips* shape = static_cast(nifNode); - if (!shape->skin.empty()) - isAnimated = false; - if (shape->data.empty() || shape->data->strips.empty()) + if (niGeometry->data->recType != Nif::RC_NiTriStripsData) + return; + + auto data = static_cast(niGeometry->data.getPtr()); + if (data->strips.empty()) return; } + if (!niGeometry->skin.empty()) + isAnimated = false; if (isAnimated) { @@ -366,7 +371,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons std::unique_ptr childMesh(new btTriangleMesh); - fillTriangleMesh(*childMesh, nifNode); + fillTriangleMesh(*childMesh, niGeometry); std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); @@ -394,7 +399,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons if (!mAvoidStaticMesh) mAvoidStaticMesh.reset(new btTriangleMesh(false)); - fillTriangleMesh(*mAvoidStaticMesh, nifNode, transform); + fillTriangleMesh(*mAvoidStaticMesh, niGeometry, transform); } else { @@ -402,7 +407,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons mStaticMesh.reset(new btTriangleMesh(false)); // Static shape, just transform all vertices into position - fillTriangleMesh(*mStaticMesh, nifNode, transform); + fillTriangleMesh(*mStaticMesh, niGeometry, transform); } } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3120b0d748..7ebb9bf8d4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -939,11 +939,11 @@ namespace NifOsg // Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors. void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { - const auto particleNode = static_cast(nifNode); - if (particleNode->data.empty()) + auto particleNode = static_cast(nifNode); + if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) return; - const Nif::NiParticlesData* particledata = particleNode->data.getPtr(); + auto particledata = static_cast(particleNode->data.getPtr()); osg::BoundingBox box; @@ -1173,51 +1173,50 @@ namespace NifOsg void handleNiGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - const Nif::NiGeometryData* niGeometryData = nullptr; - if (nifNode->recType == Nif::RC_NiTriShape) + const Nif::NiGeometry* niGeometry = static_cast(nifNode); + if (niGeometry->data.empty()) + return; + const Nif::NiGeometryData* niGeometryData = niGeometry->data.getPtr(); + + if (niGeometry->recType == Nif::RC_NiTriShape) { - const Nif::NiTriShape* triShape = static_cast(nifNode); - if (!triShape->data.empty()) - { - const Nif::NiTriShapeData* data = triShape->data.getPtr(); - niGeometryData = static_cast(data); - if (!data->triangles.empty()) - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(), - (unsigned short*)data->triangles.data())); - } + if (niGeometryData->recType != Nif::RC_NiTriShapeData) + return; + auto triangles = static_cast(niGeometryData)->triangles; + if (triangles.empty()) + return; + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, triangles.size(), + (unsigned short*)triangles.data())); } - else if (nifNode->recType == Nif::RC_NiTriStrips) + else if (niGeometry->recType == Nif::RC_NiTriStrips) { - const Nif::NiTriStrips* triStrips = static_cast(nifNode); - if (!triStrips->data.empty()) + if (niGeometryData->recType != Nif::RC_NiTriStripsData) + return; + auto data = static_cast(niGeometryData); + bool hasGeometry = false; + for (const auto& strip : data->strips) { - const Nif::NiTriStripsData* data = triStrips->data.getPtr(); - niGeometryData = static_cast(data); - if (!data->strips.empty()) - { - for (const auto& strip : data->strips) - { - if (strip.size() >= 3) - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), - (unsigned short*)strip.data())); - } - } + if (strip.size() < 3) + continue; + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), + (unsigned short*)strip.data())); + hasGeometry = true; } + if (!hasGeometry) + return; } - else if (nifNode->recType == Nif::RC_NiLines) + else if (niGeometry->recType == Nif::RC_NiLines) { - const Nif::NiLines* lines = static_cast(nifNode); - if (!lines->data.empty()) - { - const Nif::NiLinesData* data = lines->data.getPtr(); - niGeometryData = static_cast(data); - const auto& line = data->lines; - if (!line.empty()) - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), (unsigned short*)line.data())); - } + if (niGeometryData->recType != Nif::RC_NiLinesData) + return; + auto data = static_cast(niGeometryData); + const auto& line = data->lines; + if (line.empty()) + return; + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), + (unsigned short*)line.data())); } - if (niGeometryData) - handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name); + handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name); // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. @@ -1225,7 +1224,7 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, niGeometryData && !niGeometryData->colors.empty(), animflags); + applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->colors.empty(), animflags); } void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) From 42226533d8d2a3bde55e88c4f934b0a915013f68 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 13 Dec 2020 03:45:29 +0300 Subject: [PATCH 0091/2859] Handle BSLODTriShape Its levels of detail are currently not handled --- components/nif/niffile.cpp | 1 + components/nif/node.hpp | 11 +++++++++++ components/nif/record.hpp | 3 ++- components/nifbullet/bulletnifloader.cpp | 8 +++++--- components/nifosg/nifloader.cpp | 3 ++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index c96bf30d63..8aa0b60350 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -131,6 +131,7 @@ static std::map makeFactory() factory["NiTransformInterpolator"] = {&construct , RC_NiTransformInterpolator }; factory["NiColorInterpolator"] = {&construct , RC_NiColorInterpolator }; factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; + factory["BSLODTriShape"] = {&construct , RC_BSLODTriShape }; return factory; } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 3106e368bb..75041e187d 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -300,6 +300,17 @@ struct NiGeometry : Node }; struct NiTriShape : NiGeometry {}; +struct BSLODTriShape : NiTriShape +{ + unsigned int lod0, lod1, lod2; + void read(NIFStream *nif) override + { + NiTriShape::read(nif); + lod0 = nif->getUInt(); + lod1 = nif->getUInt(); + lod2 = nif->getUInt(); + } +}; struct NiTriStrips : NiGeometry {}; struct NiLines : NiGeometry {}; struct NiParticles : NiGeometry { }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 041122731a..6834c554ee 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -120,7 +120,8 @@ enum RecordType RC_NiBoolInterpolator, RC_NiTransformInterpolator, RC_NiColorInterpolator, - RC_BSShaderTextureSet + RC_BSShaderTextureSet, + RC_BSLODTriShape }; /// Base class for all records diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index a14672636a..033cfcab68 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -101,7 +101,7 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry* geometry, const osg::Matrixf &transform = osg::Matrixf()) { - if (geometry->recType == Nif::RC_NiTriShape) + if (geometry->recType == Nif::RC_NiTriShape || geometry->recType == Nif::RC_BSLODTriShape) fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); else if (geometry->recType == Nif::RC_NiTriStrips) fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); @@ -309,7 +309,9 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape || node->recType == Nif::RC_NiTriStrips)) + if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape + || node->recType == Nif::RC_NiTriStrips + || node->recType == Nif::RC_BSLODTriShape)) { handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid); } @@ -342,7 +344,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons if (niGeometry->data.empty() || niGeometry->data->vertices.empty()) return; - if (niGeometry->recType == Nif::RC_NiTriShape) + if (niGeometry->recType == Nif::RC_NiTriShape || niGeometry->recType == Nif::RC_BSLODTriShape) { if (niGeometry->data->recType != Nif::RC_NiTriShapeData) return; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7ebb9bf8d4..c3f4e50cf8 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -67,6 +67,7 @@ namespace case Nif::RC_NiTriShape: case Nif::RC_NiTriStrips: case Nif::RC_NiLines: + case Nif::RC_BSLODTriShape: return true; } return false; @@ -1178,7 +1179,7 @@ namespace NifOsg return; const Nif::NiGeometryData* niGeometryData = niGeometry->data.getPtr(); - if (niGeometry->recType == Nif::RC_NiTriShape) + if (niGeometry->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_BSLODTriShape) { if (niGeometryData->recType != Nif::RC_NiTriShapeData) return; From c0b9823372e3da7e91d57c8c2029a957efa5a481 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 13 Dec 2020 04:04:14 +0300 Subject: [PATCH 0092/2859] Read BSShaderProperty and handle NiGeometry properties --- components/nif/niffile.cpp | 1 + components/nif/node.hpp | 9 ++++++++- components/nif/property.cpp | 12 ++++++++++++ components/nif/property.hpp | 7 +++++++ components/nif/record.hpp | 3 ++- components/nif/recordptr.hpp | 4 ++++ components/nifosg/nifloader.cpp | 14 ++++++++++++++ 7 files changed, 48 insertions(+), 2 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 8aa0b60350..665533c91b 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -132,6 +132,7 @@ static std::map makeFactory() factory["NiColorInterpolator"] = {&construct , RC_NiColorInterpolator }; factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; factory["BSLODTriShape"] = {&construct , RC_BSLODTriShape }; + factory["BSShaderProperty"] = {&construct , RC_BSShaderProperty }; return factory; } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 75041e187d..08f066e361 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -278,6 +278,8 @@ struct NiGeometry : Node NiGeometryDataPtr data; NiSkinInstancePtr skin; MaterialData material; + BSShaderPropertyPtr shaderprop; + NiAlphaPropertyPtr alphaprop; void read(NIFStream *nif) override { @@ -286,7 +288,10 @@ struct NiGeometry : Node skin.read(nif); material.read(nif); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) - nif->skip(8); + { + shaderprop.read(nif); + alphaprop.read(nif); + } } void post(NIFFile *nif) override @@ -294,6 +299,8 @@ struct NiGeometry : Node Node::post(nif); data.post(nif); skin.post(nif); + shaderprop.post(nif); + alphaprop.post(nif); if (recType != RC_NiParticles && !skin.empty()) nif->setUseSkinning(true); } diff --git a/components/nif/property.cpp b/components/nif/property.cpp index e6ae71c241..55113d7c88 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -99,6 +99,18 @@ void NiTexturingProperty::post(NIFFile *nif) shaderTextures[i].post(nif); } +void BSShaderProperty::read(NIFStream *nif) +{ + NiShadeProperty::read(nif); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + { + type = nif->getUInt(); + flags1 = nif->getUInt(); + flags2 = nif->getUInt(); + envMapIntensity = nif->getFloat(); + } +} + void NiFogProperty::read(NIFStream *nif) { Property::read(nif); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index c821b7c37e..5bc0c52dba 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -118,6 +118,13 @@ struct NiShadeProperty : public Property } }; +struct BSShaderProperty : public NiShadeProperty +{ + unsigned int type{0u}, flags1{0u}, flags2{0u}; + float envMapIntensity{0.f}; + void read(NIFStream *nif) override; +}; + struct NiDitherProperty : public Property { unsigned short flags; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 6834c554ee..efacd82462 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -121,7 +121,8 @@ enum RecordType RC_NiTransformInterpolator, RC_NiColorInterpolator, RC_BSShaderTextureSet, - RC_BSLODTriShape + RC_BSLODTriShape, + RC_BSShaderProperty }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index dd6e357084..4792895e2d 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -146,6 +146,8 @@ struct NiPoint3Interpolator; struct NiTransformInterpolator; struct BSShaderTextureSet; struct NiGeometryData; +struct BSShaderProperty; +class NiAlphaProperty; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -171,6 +173,8 @@ using NiPoint3InterpolatorPtr = RecordPtrT; using NiTransformInterpolatorPtr = RecordPtrT; using BSShaderTextureSetPtr = RecordPtrT; using NiGeometryDataPtr = RecordPtrT; +using BSShaderPropertyPtr = RecordPtrT; +using NiAlphaPropertyPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index c3f4e50cf8..5f68b12291 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -96,6 +96,15 @@ namespace } } } + + auto geometry = dynamic_cast(nifNode); + if (geometry) + { + if (!geometry->shaderprop.empty()) + out.emplace_back(geometry->shaderprop.getPtr()); + if (!geometry->alphaprop.empty()) + out.emplace_back(geometry->alphaprop.getPtr()); + } } // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale @@ -366,6 +375,11 @@ namespace NifOsg handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags); } } + + auto geometry = dynamic_cast(nifNode); + // NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property + if (geometry && !geometry->shaderprop.empty()) + handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags); } void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags) From 085ea44af52d2780a25fee2ecd1d278c1170c9ec Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 13 Dec 2020 04:19:14 +0300 Subject: [PATCH 0093/2859] Add BSShaderLightingProperty abstraction --- components/nif/property.cpp | 7 +++++++ components/nif/property.hpp | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 55113d7c88..d5357e1230 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -111,6 +111,13 @@ void BSShaderProperty::read(NIFStream *nif) } } +void BSShaderLightingProperty::read(NIFStream *nif) +{ + BSShaderProperty::read(nif); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + clamp = nif->getUInt(); +} + void NiFogProperty::read(NIFStream *nif) { Property::read(nif); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 5bc0c52dba..813a1888d8 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -125,6 +125,12 @@ struct BSShaderProperty : public NiShadeProperty void read(NIFStream *nif) override; }; +struct BSShaderLightingProperty : public BSShaderProperty +{ + unsigned int clamp{0u}; + void read(NIFStream *nif) override; +}; + struct NiDitherProperty : public Property { unsigned short flags; From 53e1e57eef3954b5b61dd44c98aeb3c26932d679 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 13 Dec 2020 16:51:20 +0300 Subject: [PATCH 0094/2859] Formatting --- components/nifbullet/bulletnifloader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 033cfcab68..d72cef1940 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -310,8 +310,8 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape - || node->recType == Nif::RC_NiTriStrips - || node->recType == Nif::RC_BSLODTriShape)) + || node->recType == Nif::RC_NiTriStrips + || node->recType == Nif::RC_BSLODTriShape)) { handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid); } From b39437dfb63156fa32a93515d0404e01efdf70e2 Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 14 Dec 2020 22:23:01 +0100 Subject: [PATCH 0095/2859] Don't allow projectiles to stand still when they hit an ally. When an NPC fire a projectile, it should affect only its targeted actor. To this end, after a hit is detected the target is checked against the list of AI targets and reactivated if necessary. Problem occurs when the hit occurs as a result of a friendly actor going into the projectile (detected in ClosestNotMeConvexResultCallback): while the projectile is inside the friend's collision box, it is deactivated, just to be immediately reactivated. Effectively, the projectile does nothing until the actor moves out. Add a check inside the ClosestNotMeConvexResultCallback before declaring a hit. Since the necessary data is not safely accessible from the async thread, maintain a copy inside the Projectile class. --- .../closestnotmeconvexresultcallback.cpp | 7 +-- apps/openmw/mwphysics/physicssystem.cpp | 7 +++ apps/openmw/mwphysics/projectile.cpp | 48 +++++++++++++++++-- apps/openmw/mwphysics/projectile.hpp | 10 +++- apps/openmw/mwworld/projectilemanager.cpp | 36 +++----------- apps/openmw/mwworld/projectilemanager.hpp | 2 - 6 files changed, 68 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 8f72422765..3ec09d00d2 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -30,12 +30,9 @@ namespace MWPhysics return btScalar(1); auto* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); - // do nothing if we hit the caster. Sometimes the launching origin is inside of caster collision shape - if (projectileHolder->getCaster() != target) - { + if (projectileHolder->isValidTarget(target)) projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); - return btScalar(1); - } + return btScalar(1); } btVector3 hitNormalWorld; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8106a78ea1..151801e67d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -537,6 +537,13 @@ namespace MWPhysics if (actor->getStandingOnPtr() == old) actor->setStandingOnPtr(updated); } + + for (auto& [_, projectile] : mProjectiles) + { + if (projectile->getCaster() == old) + projectile->setCaster(updated); + } + } Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 5f5bc778ba..a8aaeb72a4 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -55,7 +55,7 @@ Projectile::~Projectile() void Projectile::commitPositionChange() { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mMutex); if (mTransformUpdatePending) { mCollisionObject->setWorldTransform(mLocalTransform); @@ -65,7 +65,7 @@ void Projectile::commitPositionChange() void Projectile::setPosition(const osg::Vec3f &position) { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mMutex); mLocalTransform.setOrigin(Misc::Convert::toBullet(position)); mTransformUpdatePending = true; } @@ -74,7 +74,7 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) return; - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mMutex); mHitTarget = target; mHitPosition = pos; mHitNormal = normal; @@ -86,4 +86,46 @@ void Projectile::activate() assert(!mActive); mActive.store(true, std::memory_order_release); } + +MWWorld::Ptr Projectile::getCaster() const +{ + std::scoped_lock lock(mMutex); + return mCaster; +} + +void Projectile::setCaster(MWWorld::Ptr caster) +{ + std::scoped_lock lock(mMutex); + mCaster = caster; +} + +void Projectile::setValidTargets(const std::vector& targets) +{ + std::scoped_lock lock(mMutex); + mValidTargets = targets; +} + +bool Projectile::isValidTarget(const MWWorld::Ptr& target) const +{ + std::scoped_lock lock(mMutex); + if (mCaster == target) + return false; + + if (!mValidTargets.empty()) + { + bool validTarget = false; + for (const auto& targetActor : mValidTargets) + { + if (targetActor == target) + { + validTarget = true; + break; + } + } + + return validTarget; + } + return true; +} + } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index ed0fdce150..5ae963b9a8 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -62,7 +62,8 @@ namespace MWPhysics return mHitTarget; } - MWWorld::Ptr getCaster() const { return mCaster; } + MWWorld::Ptr getCaster() const; + void setCaster(MWWorld::Ptr caster); osg::Vec3f getHitPos() const { @@ -73,6 +74,9 @@ namespace MWPhysics void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void activate(); + void setValidTargets(const std::vector& targets); + bool isValidTarget(const MWWorld::Ptr& target) const; + private: std::unique_ptr mShape; @@ -87,7 +91,9 @@ namespace MWPhysics btVector3 mHitPosition; btVector3 mHitNormal; - mutable std::mutex mPositionMutex; + std::vector mValidTargets; + + mutable std::mutex mMutex; osg::Vec3f mPosition; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index dba4289a23..08c9e46627 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -387,7 +387,7 @@ namespace MWWorld if (magicBoltState.mToDelete) continue; - const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); if (!projectile->isActive()) continue; // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. @@ -423,6 +423,7 @@ namespace MWWorld std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); + projectile->setValidTargets(targetActors); // Check for impact // TODO: use a proper btRigidBody / btGhostObject? @@ -469,7 +470,7 @@ namespace MWWorld if (projectileState.mToDelete) continue; - const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; // gravity constant - must be way lower than the gravity affecting actors, since we're not @@ -499,6 +500,7 @@ namespace MWWorld std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); + projectile->setValidTargets(targetActors); // Check for impact // TODO: use a proper btRigidBody / btGhostObject? @@ -547,7 +549,7 @@ namespace MWWorld const auto pos = projectile->getHitPos(); MWWorld::Ptr caster = projectileState.getCaster(); assert(target != caster); - if (!isValidTarget(caster, target)) + if (!projectile->isValidTarget(target)) { projectile->activate(); continue; @@ -583,7 +585,7 @@ namespace MWWorld const auto pos = projectile->getHitPos(); MWWorld::Ptr caster = magicBoltState.getCaster(); assert(target != caster); - if (!isValidTarget(caster, target)) + if (!projectile->isValidTarget(target)) { projectile->activate(); continue; @@ -607,32 +609,6 @@ namespace MWWorld mMagicBolts.end()); } - bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) - { - // 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 (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) - { - caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); - if (!targetActors.empty()) - { - bool validTarget = false; - for (MWWorld::Ptr& targetActor : targetActors) - { - if (targetActor == target) - { - validTarget = true; - break; - } - } - - return validTarget; - } - } - - return true; - } - void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) { mParent->removeChild(state.mNode); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index d589e985e5..2cd570847b 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -132,8 +132,6 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - bool isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); - void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); From 3195716a2c7cae7d34c5da5c360cf4c068aa9ba1 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 15 Dec 2020 13:49:25 +0200 Subject: [PATCH 0096/2859] Don't force loop textkey --- components/resource/keyframemanager.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 41b10bd65d..0ffe597122 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -40,8 +40,6 @@ namespace Resource std::string animationName = animation->getName(); std::string start = animationName + std::string(": start"); std::string stop = animationName + std::string(": stop"); - std::string loopstart = animationName + std::string(": loop start"); - std::string loopstop = animationName + std::string(": loop stop"); const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel: channels) @@ -60,8 +58,6 @@ namespace Resource // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" mTarget.mTextKeys.emplace(startTime, std::move(start)); mTarget.mTextKeys.emplace(stopTime, std::move(stop)); - mTarget.mTextKeys.emplace(startTime, std::move(loopstart)); - mTarget.mTextKeys.emplace(stopTime, std::move(loopstop)); SceneUtil::EmulatedAnimation emulatedAnimation; emulatedAnimation.mStartTime = startTime; From 6c1f6169c0e191dd2a6c6414f484e031470a0e96 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 15 Dec 2020 13:50:19 +0200 Subject: [PATCH 0097/2859] Fix root movement glitch --- components/sceneutil/osgacontroller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index d77b8d666c..f0eb7da6f9 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -117,7 +117,7 @@ namespace SceneUtil //Find the correct animation based on time for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) { - if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) + if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) { newTime = time - emulatedAnimation.mStartTime; animationName = emulatedAnimation.mName; From 8b2bf12e8fa55327ac9896c741ef366993a87570 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 15 Dec 2020 13:51:49 +0200 Subject: [PATCH 0098/2859] Use bip01 for root bone name --- apps/openmw/mwrender/animation.cpp | 2 -- components/resource/keyframemanager.cpp | 2 +- components/sceneutil/osgacontroller.cpp | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 8c9f5f4939..f8ff3780d3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -782,8 +782,6 @@ namespace MWRender NodeMap::const_iterator found = nodeMap.find("bip01"); if (found == nodeMap.end()) found = nodeMap.find("root bone"); - if (found == nodeMap.end()) - found = nodeMap.find("root"); if (found != nodeMap.end()) mAccumRoot = found->second; diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 0ffe597122..d739392e8b 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -21,7 +21,7 @@ namespace Resource void RetrieveAnimationsVisitor::apply(osg::Node& node) { - if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("root")) + if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("bip01")) { osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index f0eb7da6f9..37aa525af1 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -83,7 +83,7 @@ namespace SceneUtil { osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(cb); if (umt) - if (node.getName() != "root") link(umt); + if (node.getName() != "bip01") link(umt); cb = cb->getNestedCallback(); } @@ -133,7 +133,7 @@ namespace SceneUtil for (const auto& channel: channels) { - if (channel->getTargetName() != "root" || channel->getName() != "transform") continue; + if (channel->getTargetName() != "bip01" || channel->getName() != "transform") continue; if ( osgAnimation::MatrixLinearSampler* templateSampler = dynamic_cast (channel->getSampler()) ) { From 259d52280088c50f6399f55549f7db8b91d98277 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 15 Dec 2020 21:34:58 +0100 Subject: [PATCH 0099/2859] When doing a ray cast to the next projectile position, ignore collisions that occurs with the projectile own collision object. Otherwise a magic bolt can explode "spontaneously". --- apps/openmw/mwphysics/closestnotmerayresultcallback.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 422ca78bd5..f9dc3bdd50 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -24,6 +24,10 @@ namespace MWPhysics { if (rayResult.m_collisionObject == mMe) return 1.f; + + if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject()) + return 1.f; + if (!mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) From 1e6156e04ae35dbe04dca932ff61a3dc61c9c9a4 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 16 Dec 2020 01:06:05 +0300 Subject: [PATCH 0100/2859] Turn all NIF records into structs --- components/nif/base.hpp | 9 +++----- components/nif/controlled.hpp | 21 ++++++----------- components/nif/controller.hpp | 31 +++++++++---------------- components/nif/data.hpp | 42 ++++++++++++---------------------- components/nif/extra.hpp | 9 +++----- components/nif/node.hpp | 3 +-- components/nif/property.hpp | 12 ++++------ components/nif/recordptr.hpp | 34 +++++++++++++-------------- components/nifosg/particle.hpp | 8 +++---- 9 files changed, 65 insertions(+), 104 deletions(-) diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 711272abb0..022e5224a0 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -11,9 +11,8 @@ namespace Nif { // An extra data record. All the extra data connected to an object form a linked list. -class Extra : public Record +struct Extra : public Record { -public: std::string name; ExtraPtr next; // Next extra data record in the list @@ -31,9 +30,8 @@ public: void post(NIFFile *nif) override { next.post(nif); } }; -class Controller : public Record +struct Controller : public Record { -public: ControllerPtr next; int flags; float frequency, phase; @@ -45,9 +43,8 @@ public: }; /// Has name, extra-data and controller -class Named : public Record +struct Named : public Record { -public: std::string name; ExtraPtr extra; ExtraList extralist; diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index ac48e50498..0d93381eab 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -29,9 +29,8 @@ namespace Nif { -class NiSourceTexture : public Named +struct NiSourceTexture : public Named { -public: // Is this an external (references a separate texture file) or // internal (data is inside the nif itself) texture? bool external; @@ -93,27 +92,24 @@ struct NiParticleModifier : public Record void post(NIFFile *nif) override; }; -class NiParticleGrowFade : public NiParticleModifier +struct NiParticleGrowFade : public NiParticleModifier { -public: float growTime; float fadeTime; void read(NIFStream *nif) override; }; -class NiParticleColorModifier : public NiParticleModifier +struct NiParticleColorModifier : public NiParticleModifier { -public: NiColorDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; -class NiGravity : public NiParticleModifier +struct NiGravity : public NiParticleModifier { -public: float mForce; /* 0 - Wind (fixed direction) * 1 - Point (fixed origin) @@ -133,27 +129,24 @@ struct NiParticleCollider : public NiParticleModifier }; // NiPinaColada -class NiPlanarCollider : public NiParticleCollider +struct NiPlanarCollider : public NiParticleCollider { -public: void read(NIFStream *nif) override; osg::Vec3f mPlaneNormal; float mPlaneDistance; }; -class NiSphericalCollider : public NiParticleCollider +struct NiSphericalCollider : public NiParticleCollider { -public: float mRadius; osg::Vec3f mCenter; void read(NIFStream *nif) override; }; -class NiParticleRotation : public NiParticleModifier +struct NiParticleRotation : public NiParticleModifier { -public: void read(NIFStream *nif) override; }; diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 4016188ce0..503710fe90 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -29,9 +29,8 @@ namespace Nif { -class NiParticleSystemController : public Controller +struct NiParticleSystemController : public Controller { -public: struct Particle { osg::Vec3f velocity; float lifetime; @@ -80,9 +79,8 @@ public: }; using NiBSPArrayController = NiParticleSystemController; -class NiMaterialColorController : public Controller +struct NiMaterialColorController : public Controller { -public: NiPoint3InterpolatorPtr interpolator; NiPosDataPtr data; unsigned int targetColor; @@ -91,9 +89,8 @@ public: void post(NIFFile *nif) override; }; -class NiPathController : public Controller +struct NiPathController : public Controller { -public: NiPosDataPtr posData; NiFloatDataPtr floatData; @@ -115,9 +112,8 @@ public: void post(NIFFile *nif) override; }; -class NiLookAtController : public Controller +struct NiLookAtController : public Controller { -public: NodePtr target; unsigned short lookAtFlags{0}; @@ -125,9 +121,8 @@ public: void post(NIFFile *nif) override; }; -class NiUVController : public Controller +struct NiUVController : public Controller { -public: NiUVDataPtr data; unsigned int uvSet; @@ -135,9 +130,8 @@ public: void post(NIFFile *nif) override; }; -class NiKeyframeController : public Controller +struct NiKeyframeController : public Controller { -public: NiKeyframeDataPtr data; NiTransformInterpolatorPtr interpolator; @@ -154,12 +148,11 @@ struct NiFloatInterpController : public Controller void post(NIFFile *nif) override; }; -class NiAlphaController : public NiFloatInterpController { }; -class NiRollController : public NiFloatInterpController { }; +struct NiAlphaController : public NiFloatInterpController { }; +struct NiRollController : public NiFloatInterpController { }; -class NiGeomMorpherController : public Controller +struct NiGeomMorpherController : public Controller { -public: NiMorphDataPtr data; NiFloatInterpolatorList interpolators; @@ -167,18 +160,16 @@ public: void post(NIFFile *nif) override; }; -class NiVisController : public Controller +struct NiVisController : public Controller { -public: NiVisDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; -class NiFlipController : public Controller +struct NiFlipController : public Controller { -public: NiFloatInterpolatorPtr mInterpolator; int mTexSlot; // NiTexturingProperty::TextureType float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 908313f504..66a391afc3 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -32,9 +32,8 @@ namespace Nif { // Common ancestor for several data classes -class NiGeometryData : public Record +struct NiGeometryData : public Record { -public: std::vector vertices, normals, tangents, bitangents; std::vector colors; std::vector< std::vector > uvlist; @@ -44,18 +43,16 @@ public: void read(NIFStream *nif) override; }; -class NiTriShapeData : public NiGeometryData +struct NiTriShapeData : public NiGeometryData { -public: // Triangles, three vertex indices per triangle std::vector triangles; void read(NIFStream *nif) override; }; -class NiTriStripsData : public NiGeometryData +struct NiTriStripsData : public NiGeometryData { -public: // Triangle strips, series of vertex indices. std::vector> strips; @@ -70,9 +67,8 @@ struct NiLinesData : public NiGeometryData void read(NIFStream *nif) override; }; -class NiParticlesData : public NiGeometryData +struct NiParticlesData : public NiGeometryData { -public: int numParticles{0}; int activeCount; @@ -84,39 +80,34 @@ public: void read(NIFStream *nif) override; }; -class NiRotatingParticlesData : public NiParticlesData +struct NiRotatingParticlesData : public NiParticlesData { -public: void read(NIFStream *nif) override; }; -class NiPosData : public Record +struct NiPosData : public Record { -public: Vector3KeyMapPtr mKeyList; void read(NIFStream *nif) override; }; -class NiUVData : public Record +struct NiUVData : public Record { -public: FloatKeyMapPtr mKeyList[4]; void read(NIFStream *nif) override; }; -class NiFloatData : public Record +struct NiFloatData : public Record { -public: FloatKeyMapPtr mKeyList; void read(NIFStream *nif) override; }; -class NiPixelData : public Record +struct NiPixelData : public Record { -public: enum Format { NIPXFMT_RGB8, @@ -150,17 +141,15 @@ public: void post(NIFFile *nif) override; }; -class NiColorData : public Record +struct NiColorData : public Record { -public: Vector4KeyMapPtr mKeyMap; void read(NIFStream *nif) override; }; -class NiVisData : public Record +struct NiVisData : public Record { -public: struct VisData { float time; bool isSet; @@ -170,9 +159,8 @@ public: void read(NIFStream *nif) override; }; -class NiSkinInstance : public Record +struct NiSkinInstance : public Record { -public: NiSkinDataPtr data; NiSkinPartitionPtr partitions; NodePtr root; @@ -182,9 +170,8 @@ public: void post(NIFFile *nif) override; }; -class NiSkinData : public Record +struct NiSkinData : public Record { -public: struct VertWeight { unsigned short vertex; @@ -251,9 +238,8 @@ struct NiKeyframeData : public Record void read(NIFStream *nif) override; }; -class NiPalette : public Record +struct NiPalette : public Record { -public: // 32-bit RGBA colors that correspond to 8-bit indices std::vector colors; diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 5d7aa0c3b2..6d345a18ea 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -29,15 +29,13 @@ namespace Nif { -class NiVertWeightsExtraData : public Extra +struct NiVertWeightsExtraData : public Extra { -public: void read(NIFStream *nif) override; }; -class NiTextKeyExtraData : public Extra +struct NiTextKeyExtraData : public Extra { -public: struct TextKey { float time; @@ -48,9 +46,8 @@ public: void read(NIFStream *nif) override; }; -class NiStringExtraData : public Extra +struct NiStringExtraData : public Extra { -public: /* Two known meanings: "MRK" - marker, only visible in the editor, not rendered in-game "NCO" - no collision diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 08f066e361..1d082b8f9b 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -131,9 +131,8 @@ struct NiBoundingVolume parent node (unless it's the root), and transformation (location and rotation) relative to it's parent. */ -class Node : public Named +struct Node : public Named { -public: // Node flags. Interpretation depends somewhat on the type of node. unsigned int flags; Transformation trafo; diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 813a1888d8..eccb442f7e 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -29,11 +29,10 @@ namespace Nif { -class Property : public Named { }; +struct Property : public Named { }; -class NiTexturingProperty : public Property +struct NiTexturingProperty : public Property { -public: unsigned short flags{0u}; // A sub-texture @@ -96,9 +95,8 @@ public: void post(NIFFile *nif) override; }; -class NiFogProperty : public Property +struct NiFogProperty : public Property { -public: unsigned short mFlags; float mFogDepth; osg::Vec3f mColour; @@ -307,8 +305,8 @@ struct S_StencilProperty void read(NIFStream *nif); }; -class NiAlphaProperty : public StructPropT { }; -class NiVertexColorProperty : public StructPropT { }; +struct NiAlphaProperty : public StructPropT { }; +struct NiVertexColorProperty : public StructPropT { }; struct NiStencilProperty : public Property { S_StencilProperty data; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 4792895e2d..b30d99fbe4 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -120,24 +120,24 @@ public: }; -class Node; -class Extra; -class Property; -class NiUVData; -class NiPosData; -class NiVisData; -class Controller; -class Named; -class NiSkinData; -class NiFloatData; +struct Node; +struct Extra; +struct Property; +struct NiUVData; +struct NiPosData; +struct NiVisData; +struct Controller; +struct Named; +struct NiSkinData; +struct NiFloatData; struct NiMorphData; -class NiPixelData; -class NiColorData; +struct NiPixelData; +struct NiColorData; struct NiKeyframeData; -class NiTriStripsData; -class NiSkinInstance; -class NiSourceTexture; -class NiPalette; +struct NiTriStripsData; +struct NiSkinInstance; +struct NiSourceTexture; +struct NiPalette; struct NiParticleModifier; struct NiBoolData; struct NiSkinPartition; @@ -147,7 +147,7 @@ struct NiTransformInterpolator; struct BSShaderTextureSet; struct NiGeometryData; struct BSShaderProperty; -class NiAlphaProperty; +struct NiAlphaProperty; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index dd89f4501d..b0a46f0ca5 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -14,10 +14,10 @@ namespace Nif { - class NiGravity; - class NiPlanarCollider; - class NiSphericalCollider; - class NiColorData; + struct NiGravity; + struct NiPlanarCollider; + struct NiSphericalCollider; + struct NiColorData; } namespace NifOsg From 9f81dcbd1afbd1904254e3c7a8f27a6bc22be8a8 Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 16 Dec 2020 13:21:00 +0000 Subject: [PATCH 0101/2859] Per request via PM: "I would like to request that my name is removed from the teams page as my contributions has all been replaced by better solutions years ago." by vorenon, Manuel Edelmann --- AUTHORS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index e8134a681e..e6ff67293a 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -123,7 +123,6 @@ Programmers Lordrea Łukasz Gołębiewski (lukago) Lukasz Gromanowski (lgro) - Manuel Edelmann (vorenon) Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) From 640420eaaa61b38307b16aa6e5b14180f26bc4b8 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 16 Dec 2020 21:46:52 +0100 Subject: [PATCH 0102/2859] No longer delete and recreate CopyFramebufferToTextureCallback, remove oneshot functionality. Instead just remove the callback after the traversals. Use addInitialDrawCallback instead of setInitialDrawCallback to avoid messing with existing callbacks. --- apps/openmw/mwgui/loadingscreen.cpp | 17 +++++++++-------- apps/openmw/mwgui/loadingscreen.hpp | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index cd0384bb02..2e2de578e3 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -137,15 +137,11 @@ namespace MWGui public: CopyFramebufferToTextureCallback(osg::Texture2D* texture) : mTexture(texture) - , oneshot(true) { } void operator () (osg::RenderInfo& renderInfo) const override { - if (!oneshot) - return; - oneshot = false; int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); @@ -153,7 +149,6 @@ namespace MWGui private: osg::ref_ptr mTexture; - mutable bool oneshot; }; class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback @@ -322,9 +317,12 @@ namespace MWGui mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture)); } - // Notice that the next time this is called, the current CopyFramebufferToTextureCallback will be deleted - // so there's no memory leak as at most one object of type CopyFramebufferToTextureCallback is allocated at a time. - mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture)); + if (!mCopyFramebufferToTextureCallback) + { + mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture); + } + + mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback); mBackgroundImage->setBackgroundImage(""); mBackgroundImage->setVisible(false); @@ -367,6 +365,9 @@ namespace MWGui mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + if(mCopyFramebufferToTextureCallback) + mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); + mLastRenderTime = mTimer.time_m(); } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 2577827aaa..ac911ab60b 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -28,6 +28,7 @@ namespace Resource namespace MWGui { class BackgroundImage; + class CopyFramebufferToTextureCallback; class LoadingScreen : public WindowBase, public Loading::Listener { @@ -84,6 +85,7 @@ namespace MWGui std::vector mSplashScreens; osg::ref_ptr mTexture; + osg::ref_ptr mCopyFramebufferToTextureCallback; std::unique_ptr mGuiTexture; void changeWallpaper(); From bc961a13f54842aa1dd75a459fabfe98ccce2cad Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 16 Dec 2020 22:03:16 +0100 Subject: [PATCH 0103/2859] avoid redundant calls to removeInitialDrawCallback --- apps/openmw/mwgui/loadingscreen.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 2e2de578e3..20586ef4af 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -365,8 +365,11 @@ namespace MWGui mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - if(mCopyFramebufferToTextureCallback) + if (mCopyFramebufferToTextureCallback) + { mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); + mCopyFramebufferToTextureCallback = nullptr; + } mLastRenderTime = mTimer.time_m(); } From ce2bcba5d41631a63ae24d0491578f0ddf187cd2 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 16 Dec 2020 23:44:15 +0000 Subject: [PATCH 0104/2859] Replace deprecated alpha test in shader visitor --- components/CMakeLists.txt | 2 +- components/sceneutil/mwshadowtechnique.cpp | 19 ++- components/sceneutil/mwshadowtechnique.hpp | 4 +- components/sceneutil/shadowsbin.cpp | 57 ++++++- components/sceneutil/shadowsbin.hpp | 27 ++-- components/shader/removedalphafunc.cpp | 27 ++++ components/shader/removedalphafunc.hpp | 40 +++++ components/shader/shadervisitor.cpp | 172 +++++++++++++++++---- components/shader/shadervisitor.hpp | 4 + files/shaders/CMakeLists.txt | 1 + files/shaders/alpha.glsl | 38 +++++ files/shaders/objects_fragment.glsl | 4 + files/shaders/shadowcasting_fragment.glsl | 7 +- 13 files changed, 342 insertions(+), 60 deletions(-) create mode 100644 components/shader/removedalphafunc.cpp create mode 100644 components/shader/removedalphafunc.hpp create mode 100644 files/shaders/alpha.glsl diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3c18990375..45864a6e83 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -46,7 +46,7 @@ add_component_dir (resource ) add_component_dir (shader - shadermanager shadervisitor + shadermanager shadervisitor removedalphafunc ) add_component_dir (sceneutil diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index c49a147776..31267f75ce 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -278,7 +278,7 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) static osg::ref_ptr ss; if (!ss) { - ShadowsBinAdder adder("ShadowsBin"); + ShadowsBinAdder adder("ShadowsBin", _vdsm->getCastingPrograms()); ss = new osg::StateSet; ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); } @@ -882,11 +882,15 @@ void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) { // This can't be part of the constructor as OSG mandates that there be a trivial constructor available - - _castingProgram = new osg::Program(); - _castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); - _castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); + osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX); + for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) + { + auto& program = _castingPrograms[alphaFunc - GL_NEVER]; + program = new osg::Program(); + program->addShader(castingVertexShader); + program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)} }, osg::Shader::FRAGMENT)); + } } MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) @@ -1604,10 +1608,11 @@ void MWShadowTechnique::createShaders() } - if (!_castingProgram) + if (!_castingPrograms[GL_ALWAYS - GL_NEVER]) OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; - _shadowCastingStateSet->setAttributeAndModes(_castingProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + // Always use the GL_ALWAYS shader as the shadows bin will change it if necessary + _shadowCastingStateSet->setAttributeAndModes(_castingPrograms[GL_ALWAYS - GL_NEVER], osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 5125247dda..96b1c7d0ed 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -215,6 +215,8 @@ namespace SceneUtil { virtual void createShaders(); + virtual std::array, GL_ALWAYS - GL_NEVER> getCastingPrograms() const { return _castingPrograms; } + virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); @@ -288,7 +290,7 @@ namespace SceneUtil { }; osg::ref_ptr _debugHud; - osg::ref_ptr _castingProgram; + std::array, GL_ALWAYS - GL_NEVER> _castingPrograms; }; } diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 520ad0362f..9e6d461ae7 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -1,7 +1,9 @@ #include "shadowsbin.hpp" #include #include +#include #include +#include #include using namespace osgUtil; @@ -40,6 +42,10 @@ namespace namespace SceneUtil { +std::array, GL_ALWAYS - GL_NEVER> ShadowsBin::sCastingPrograms = { + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr +}; + ShadowsBin::ShadowsBin() { mNoTestStateSet = new osg::StateSet; @@ -49,6 +55,12 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + + for (size_t i = 0; i < sCastingPrograms.size(); ++i) + { + mAlphaFuncShaders[i] = new osg::StateSet; + mAlphaFuncShaders[i]->setAttribute(sCastingPrograms[i], osg::StateAttribute::ON | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); + } } StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache) @@ -71,7 +83,6 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un continue; accumulateModeState(ss, state.mAlphaBlend, state.mAlphaBlendOverride, GL_BLEND); - accumulateModeState(ss, state.mAlphaTest, state.mAlphaTestOverride, GL_ALPHA_TEST); const osg::StateSet::AttributeList& attributes = ss->getAttributeList(); osg::StateSet::AttributeList::const_iterator found = attributes.find(std::make_pair(osg::StateAttribute::MATERIAL, 0)); @@ -83,6 +94,14 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un state.mMaterial = nullptr; } + found = attributes.find(std::make_pair(osg::StateAttribute::ALPHAFUNC, 0)); + if (found != attributes.end()) + { + // As force shaders is on, we know this is really a RemovedAlphaFunc + const osg::StateSet::RefAttributePair& rap = found->second; + accumulateState(state.mAlphaFunc, static_cast(rap.first.get()), state.mAlphaFuncOverride, rap.second); + } + // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it. found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); if (found != attributes.end()) @@ -113,16 +132,44 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un leaf->_parent = sg_new; sg_new->_leaves.push_back(leaf); } - return sg_new; + sg = sg_new; + } + + // GL_ALWAYS is set by default by mwshadowtechnique + if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS) + { + sg_new = sg->find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]); + for (RenderLeaf* leaf : sg->_leaves) + { + leaf->_parent = sg_new; + sg_new->_leaves.push_back(leaf); + } + sg = sg_new; } + return sg; } +void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms) +{ + sCastingPrograms = castingPrograms; + osg::ref_ptr bin(new ShadowsBin); + osgUtil::RenderBin::addRenderBinPrototype(name, bin); +} + +inline bool ShadowsBin::State::needTexture() const +{ + return mAlphaBlend || (mAlphaFunc && mAlphaFunc->getFunction() != GL_ALWAYS); +} + bool ShadowsBin::State::needShadows() const { - if (!mMaterial) - return true; - return materialNeedShadows(mMaterial); + if (mAlphaFunc && mAlphaFunc->getFunction() == GL_NEVER) + return false; + // other alpha func + material combinations might be skippable + if (mAlphaBlend && mMaterial) + return materialNeedShadows(mMaterial); + return true; } void ShadowsBin::sortImplementation() diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index cc6fd3525c..7247a9af4e 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -1,11 +1,13 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H #define OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H +#include #include #include namespace osg { class Material; + class AlphaFunc; } namespace SceneUtil @@ -15,8 +17,12 @@ namespace SceneUtil class ShadowsBin : public osgUtil::RenderBin { private: + static std::array, GL_ALWAYS - GL_NEVER> sCastingPrograms; + osg::ref_ptr mNoTestStateSet; osg::ref_ptr mShaderAlphaTestStateSet; + + std::array, GL_ALWAYS - GL_NEVER> mAlphaFuncShaders; public: META_Object(SceneUtil, ShadowsBin) ShadowsBin(); @@ -24,6 +30,7 @@ namespace SceneUtil : osgUtil::RenderBin(rhs, copyop) , mNoTestStateSet(rhs.mNoTestStateSet) , mShaderAlphaTestStateSet(rhs.mShaderAlphaTestStateSet) + , mAlphaFuncShaders(rhs.mAlphaFuncShaders) {} void sortImplementation() override; @@ -33,8 +40,8 @@ namespace SceneUtil State() : mAlphaBlend(false) , mAlphaBlendOverride(false) - , mAlphaTest(false) - , mAlphaTestOverride(false) + , mAlphaFunc(nullptr) + , mAlphaFuncOverride(false) , mMaterial(nullptr) , mMaterialOverride(false) , mImportantState(false) @@ -42,33 +49,29 @@ namespace SceneUtil bool mAlphaBlend; bool mAlphaBlendOverride; - bool mAlphaTest; - bool mAlphaTestOverride; + osg::AlphaFunc* mAlphaFunc; + bool mAlphaFuncOverride; osg::Material* mMaterial; bool mMaterialOverride; bool mImportantState; - bool needTexture() const { return mAlphaBlend || mAlphaTest; } + bool needTexture() const; bool needShadows() const; // A state is interesting if there's anything about it that might affect whether we can optimise child state bool interesting() const { - return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaTestOverride || mMaterialOverride || mImportantState; + return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaFuncOverride || mMaterialOverride || mImportantState; } }; osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting); - static void addPrototype(const std::string& name) - { - osg::ref_ptr bin (new ShadowsBin); - osgUtil::RenderBin::addRenderBinPrototype(name, bin); - } + static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms); }; class ShadowsBinAdder { public: - ShadowsBinAdder(const std::string& name){ ShadowsBin::addPrototype(name); } + ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } }; } diff --git a/components/shader/removedalphafunc.cpp b/components/shader/removedalphafunc.cpp new file mode 100644 index 0000000000..15d0fb0f19 --- /dev/null +++ b/components/shader/removedalphafunc.cpp @@ -0,0 +1,27 @@ +#include "removedalphafunc.hpp" + +#include + +#include + +namespace Shader +{ + std::array, GL_ALWAYS - GL_NEVER> RemovedAlphaFunc::sInstances{ + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr + }; + + osg::ref_ptr RemovedAlphaFunc::getInstance(GLenum func) + { + assert(func >= GL_NEVER && func <= GL_ALWAYS); + if (!sInstances[func - GL_NEVER]) + sInstances[func - GL_NEVER] = new RemovedAlphaFunc(static_cast(func), 1.0); + return sInstances[func - GL_NEVER]; + } + + void RemovedAlphaFunc::apply(osg::State & state) const + { + // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it + if (!state.getGlobalDefaultAttribute(ALPHAFUNC)->getType() != getType()) + state.setGlobalDefaultAttribute(static_cast(cloneType())); + } +} diff --git a/components/shader/removedalphafunc.hpp b/components/shader/removedalphafunc.hpp new file mode 100644 index 0000000000..a8165b0dc7 --- /dev/null +++ b/components/shader/removedalphafunc.hpp @@ -0,0 +1,40 @@ +#ifndef OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H +#define OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H + +#include + +#include + +namespace Shader +{ + // State attribute used when shader visitor replaces the deprecated alpha function with a shader + // Prevents redundant glAlphaFunc calls and lets the shadowsbin know the stateset had alpha testing + class RemovedAlphaFunc : public osg::AlphaFunc + { + public: + // Get a singleton-like instance with the right func (but a default threshold) + static osg::ref_ptr getInstance(GLenum func); + + RemovedAlphaFunc() + : osg::AlphaFunc() + {} + + RemovedAlphaFunc(ComparisonFunction func, float ref) + : osg::AlphaFunc(func, ref) + {} + + RemovedAlphaFunc(const RemovedAlphaFunc& raf, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) + : osg::AlphaFunc(raf, copyop) + {} + + META_StateAttribute(Shader, RemovedAlphaFunc, ALPHAFUNC); + + void apply(osg::State& state) const override; + + protected: + virtual ~RemovedAlphaFunc() = default; + + static std::array, GL_ALWAYS - GL_NEVER> sInstances; + }; +} +#endif //OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index e908b6aaa8..aeec391c67 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,5 +1,6 @@ #include "shadervisitor.hpp" +#include #include #include #include @@ -13,6 +14,7 @@ #include #include +#include "removedalphafunc.hpp" #include "shadermanager.hpp" namespace Shader @@ -22,6 +24,9 @@ namespace Shader : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) + , mAlphaTestOverridden(false) + , mAlphaFunc(GL_ALWAYS) + , mAlphaRef(1.0) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) @@ -76,6 +81,34 @@ namespace Shader return newStateSet.get(); } + osg::UserDataContainer* getWritableUserDataContainer(osg::Object& object) + { + if (!object.getUserDataContainer()) + return object.getOrCreateUserDataContainer(); + + osg::ref_ptr newUserData = static_cast(object.getUserDataContainer()->clone(osg::CopyOp::SHALLOW_COPY)); + object.setUserDataContainer(newUserData); + return newUserData.get(); + } + + osg::StateSet* getRemovedState(osg::StateSet& stateSet) + { + if (!stateSet.getUserDataContainer()) + return nullptr; + + return static_cast(stateSet.getUserDataContainer()->getUserObject("removedState")); + } + + void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* stateSet) + { + unsigned int index = userData.getUserObjectIndex("removedState"); + if (index < userData.getNumUserObjects()) + userData.setUserObject(index, stateSet); + else + userData.addUserObject(stateSet); + stateSet->setName("removedState"); + } + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; bool isTextureNameRecognized(const std::string& name) { @@ -234,49 +267,67 @@ namespace Shader } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); - for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) + osg::StateSet::AttributeList removedAttributes; + osg::ref_ptr removedState; + if (removedState = getRemovedState(*stateset)) + removedAttributes = removedState->getAttributeList(); + for (const auto& attributeMap : { attributes, removedAttributes }) { - if (it->first.first == osg::StateAttribute::MATERIAL) + for (osg::StateSet::AttributeList::const_iterator it = attributeMap.begin(); it != attributeMap.end(); ++it) { - // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define - if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) + if (it->first.first == osg::StateAttribute::MATERIAL) { - if (it->second.second & osg::StateAttribute::OVERRIDE) - mRequirements.back().mMaterialOverridden = true; + // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define + if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mMaterialOverridden = true; - const osg::Material* mat = static_cast(it->second.first.get()); + const osg::Material* mat = static_cast(it->second.first.get()); - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); - int colorMode; - switch (mat->getColorMode()) - { - case osg::Material::OFF: - colorMode = 0; - break; - case osg::Material::EMISSION: - colorMode = 1; - break; - default: - case osg::Material::AMBIENT_AND_DIFFUSE: - colorMode = 2; - break; - case osg::Material::AMBIENT: - colorMode = 3; - break; - case osg::Material::DIFFUSE: - colorMode = 4; - break; - case osg::Material::SPECULAR: - colorMode = 5; - break; + int colorMode; + switch (mat->getColorMode()) + { + case osg::Material::OFF: + colorMode = 0; + break; + case osg::Material::EMISSION: + colorMode = 1; + break; + default: + case osg::Material::AMBIENT_AND_DIFFUSE: + colorMode = 2; + break; + case osg::Material::AMBIENT: + colorMode = 3; + break; + case osg::Material::DIFFUSE: + colorMode = 4; + break; + case osg::Material::SPECULAR: + colorMode = 5; + break; + } + + mRequirements.back().mColorMode = colorMode; } + } + else if (it->first.first == osg::StateAttribute::ALPHAFUNC) + { + if (!mRequirements.back().mAlphaTestOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mAlphaTestOverridden = true; - mRequirements.back().mColorMode = colorMode; + const osg::AlphaFunc* alpha = static_cast(it->second.first.get()); + mRequirements.back().mAlphaFunc = alpha->getFunction(); + mRequirements.back().mAlphaRef = alpha->getReferenceValue(); + } } } - // Eventually, move alpha testing to discard in shader adn remove deprecated state here } } @@ -322,6 +373,42 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); + if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) + { + writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); + + // back up removed state in case recreateShaders gets rid of the shader later + osg::ref_ptr removedState; + if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) + removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); + if (!removedState) + removedState = new osg::StateSet(); + + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) + removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); + // This disables the deprecated fixed-function alpha test + writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); + + const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); + if (alphaFunc) + removedState->setAttribute(alphaFunc->first, alphaFunc->second); + // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test + writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getOrCreateUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + updateRemovedState(*writableUserData, removedState); + } + } + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); @@ -347,6 +434,25 @@ namespace Shader writableStateSet = getWritableStateSet(node); writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM); + + osg::ref_ptr removedState; + if (removedState = getRemovedState(*writableStateSet)) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + unsigned int index = writableUserData->getUserObjectIndex("removedState"); + writableUserData->removeUserObject(index); + + for (const auto& [mode, value] : removedState->getModeList()) + writableStateSet->setMode(mode, value); + + for (const auto& attribute : removedState->getAttributeList()) + writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + } } bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 6031dbfe6c..2ba18107b5 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -79,6 +79,10 @@ namespace Shader int mColorMode; bool mMaterialOverridden; + bool mAlphaTestOverridden; + + GLenum mAlphaFunc; + float mAlphaRef; bool mNormalHeight; // true if normal map has height info in alpha channel diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 8012c2bc10..16aedd16db 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -10,6 +10,7 @@ set(SHADER_FILES water_vertex.glsl water_fragment.glsl water_nm.png + alpha.glsl objects_vertex.glsl objects_fragment.glsl terrain_vertex.glsl diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl new file mode 100644 index 0000000000..9dee7bd314 --- /dev/null +++ b/files/shaders/alpha.glsl @@ -0,0 +1,38 @@ + +#define FUNC_NEVER 0x0200 +#define FUNC_LESS 0x0201 +#define FUNC_EQUAL 0x0202 +#define FUNC_LEQUAL 0x0203 +#define FUNC_GREATER 0x0204 +#define FUNC_NOTEQUAL 0x0205 +#define FUNC_GEQUAL 0x0206 +#define FUNC_ALWAYS 0x0207 + +#if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER +uniform float alphaRef; +#endif + +void alphaTest() +{ + #if @alphaFunc == FUNC_NEVER + discard; + #elif @alphaFunc == FUNC_LESS + if (gl_FragData[0].a > alphaRef) + discard; + #elif @alphaFunc == FUNC_EQUAL + if (gl_FragData[0].a != alphaRef) + discard; + #elif @alphaFunc == FUNC_LEQUAL + if (gl_FragData[0].a >= alphaRef) + discard; + #elif @alphaFunc == FUNC_GREATER + if (gl_FragData[0].a < alphaRef) + discard; + #elif @alphaFunc == FUNC_NOTEQUAL + if (gl_FragData[0].a == alphaRef) + discard; + #elif @alphaFunc == FUNC_GEQUAL + if (gl_FragData[0].a <= alphaRef) + discard; + #endif +} \ No newline at end of file diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index d5716c378b..ac631308cb 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -67,6 +67,7 @@ varying vec3 passNormal; #include "shadows_fragment.glsl" #include "lighting.glsl" #include "parallax.glsl" +#include "alpha.glsl" void main() { @@ -108,6 +109,7 @@ void main() #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); + alphaTest(); #else gl_FragData[0] = vec4(1.0); #endif @@ -164,6 +166,8 @@ void main() gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor, shadowing); #endif + alphaTest(); + #if @envMap && !@preLightEnv gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; #endif diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index a5410d0089..8c53c542bd 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -8,6 +8,8 @@ varying float alphaPassthrough; uniform bool useDiffuseMapForShadowAlpha; uniform bool alphaTestShadows; +#include "alpha.glsl" + void main() { gl_FragData[0].rgb = vec3(1.0); @@ -16,7 +18,10 @@ void main() else gl_FragData[0].a = alphaPassthrough; - // Prevent translucent things casting shadow (including the player using an invisibility effect). For now, rely on the deprecated FF test for non-blended stuff. + alphaTest(); + + // Prevent translucent things casting shadow (including the player using an invisibility effect). + // This replaces alpha blending, which obviously doesn't work with depth buffers if (alphaTestShadows && gl_FragData[0].a <= 0.5) discard; } From 46ec40fa9291c8cf763f21906c6197cd2cf11a69 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 18 Dec 2020 00:20:40 +0300 Subject: [PATCH 0105/2859] Make sure NIFLoader avoids working further with empty geometry --- components/nifosg/nifloader.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5f68b12291..902f15fb35 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1245,9 +1245,12 @@ namespace NifOsg void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { assert(isTypeGeometry(nifNode->recType)); - osg::ref_ptr drawable; osg::ref_ptr geom (new osg::Geometry); handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); + // If the record had no valid geometry data in it, early-out + if (geom->empty()) + return; + osg::ref_ptr drawable; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) @@ -1292,6 +1295,8 @@ namespace NifOsg assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geometry (new osg::Geometry); handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); + if (geometry->empty()) + return; osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); rig->setName(nifNode->name); From 96a87b582c41f2c83a373a019a776a5ec16f1b6e Mon Sep 17 00:00:00 2001 From: psi29a Date: Thu, 17 Dec 2020 22:51:04 +0000 Subject: [PATCH 0106/2859] Merge branch 'loadingScreen_initialdrawcallback' into 'master' Fix for !472 for older versions of OSG See merge request OpenMW/openmw!474 (cherry picked from commit 35e25d79b9a6c76807084be5ca2584c4fd9b9c35) 4447dd41 osg versions 06f4d63b Preserve callback in older osg version --- apps/openmw/mwgui/loadingscreen.cpp | 14 ++++++++++++++ apps/openmw/mwgui/loadingscreen.hpp | 2 ++ 2 files changed, 16 insertions(+) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 20586ef4af..9ab5e32a3f 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ namespace MWGui , mNestedLoadingCount(0) , mProgress(0) , mShowWallpaper(true) + , mOldCallback(nullptr) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); @@ -322,7 +324,13 @@ namespace MWGui mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture); } +#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback); +#else + // TODO: Remove once we officially end support for OSG versions pre 3.5.10 + mOldCallback = mViewer->getCamera()->getInitialDrawCallback(); + mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback); +#endif mBackgroundImage->setBackgroundImage(""); mBackgroundImage->setVisible(false); @@ -367,7 +375,13 @@ namespace MWGui if (mCopyFramebufferToTextureCallback) { + +#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); +#else + // TODO: Remove once we officially end support for OSG versions pre 3.5.10 + mViewer->getCamera()->setInitialDrawCallback(mOldCallback); +#endif mCopyFramebufferToTextureCallback = nullptr; } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index ac911ab60b..d58899eae4 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -3,6 +3,7 @@ #include +#include #include #include @@ -86,6 +87,7 @@ namespace MWGui osg::ref_ptr mTexture; osg::ref_ptr mCopyFramebufferToTextureCallback; + osg::ref_ptr mOldCallback; std::unique_ptr mGuiTexture; void changeWallpaper(); From a0800715887135576ff0f863df00c897481b28b8 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 00:02:51 +0000 Subject: [PATCH 0107/2859] Set default state sensibly --- apps/openmw/mwrender/renderingmanager.cpp | 5 +++++ components/shader/removedalphafunc.cpp | 7 ------- components/shader/removedalphafunc.hpp | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 217f0b73ca..9cb5146dc3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -32,6 +32,8 @@ #include #include #include + +#include #include #include @@ -376,6 +378,9 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); + // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it + mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_GREATER)); + mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); updateProjectionMatrix(); diff --git a/components/shader/removedalphafunc.cpp b/components/shader/removedalphafunc.cpp index 15d0fb0f19..0da36ab48f 100644 --- a/components/shader/removedalphafunc.cpp +++ b/components/shader/removedalphafunc.cpp @@ -17,11 +17,4 @@ namespace Shader sInstances[func - GL_NEVER] = new RemovedAlphaFunc(static_cast(func), 1.0); return sInstances[func - GL_NEVER]; } - - void RemovedAlphaFunc::apply(osg::State & state) const - { - // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it - if (!state.getGlobalDefaultAttribute(ALPHAFUNC)->getType() != getType()) - state.setGlobalDefaultAttribute(static_cast(cloneType())); - } } diff --git a/components/shader/removedalphafunc.hpp b/components/shader/removedalphafunc.hpp index a8165b0dc7..1aa9978cfe 100644 --- a/components/shader/removedalphafunc.hpp +++ b/components/shader/removedalphafunc.hpp @@ -29,7 +29,7 @@ namespace Shader META_StateAttribute(Shader, RemovedAlphaFunc, ALPHAFUNC); - void apply(osg::State& state) const override; + void apply(osg::State& state) const override {} protected: virtual ~RemovedAlphaFunc() = default; From a4f32a469efbc7744146b3ab7d2c75d6e2fa91e9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 01:36:20 +0000 Subject: [PATCH 0108/2859] Work around Nvidia driver bug https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.1.20.pdf Section 4.1.3 says that hexadecimal integer literals are supported, but Nvidia have never read a specification since their founding, so their engineers didn't know that hexadecimal integer literals are requires to be supported to advertise support OpenGL versions with GLSL support. --- files/shaders/alpha.glsl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 9dee7bd314..0c69df0ab2 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -1,12 +1,12 @@ -#define FUNC_NEVER 0x0200 -#define FUNC_LESS 0x0201 -#define FUNC_EQUAL 0x0202 -#define FUNC_LEQUAL 0x0203 -#define FUNC_GREATER 0x0204 -#define FUNC_NOTEQUAL 0x0205 -#define FUNC_GEQUAL 0x0206 -#define FUNC_ALWAYS 0x0207 +#define FUNC_NEVER 512 // 0x0200 +#define FUNC_LESS 513 // 0x0201 +#define FUNC_EQUAL 514 // 0x0202 +#define FUNC_LEQUAL 515 // 0x0203 +#define FUNC_GREATER 516 // 0x0204 +#define FUNC_NOTEQUAL 517 // 0x0205 +#define FUNC_GEQUAL 518 // 0x0206 +#define FUNC_ALWAYS 519 // 0x0207 #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER uniform float alphaRef; From 05ad44d0b1527339f8da788898cf9fa1ab92ba84 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 01:44:46 +0000 Subject: [PATCH 0109/2859] Set correct array size --- components/sceneutil/mwshadowtechnique.hpp | 4 ++-- components/sceneutil/shadowsbin.cpp | 6 +++--- components/sceneutil/shadowsbin.hpp | 8 ++++---- components/shader/removedalphafunc.cpp | 4 ++-- components/shader/removedalphafunc.hpp | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 96b1c7d0ed..7ffe687b47 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -215,7 +215,7 @@ namespace SceneUtil { virtual void createShaders(); - virtual std::array, GL_ALWAYS - GL_NEVER> getCastingPrograms() const { return _castingPrograms; } + virtual std::array, GL_ALWAYS - GL_NEVER + 1> getCastingPrograms() const { return _castingPrograms; } virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; @@ -290,7 +290,7 @@ namespace SceneUtil { }; osg::ref_ptr _debugHud; - std::array, GL_ALWAYS - GL_NEVER> _castingPrograms; + std::array, GL_ALWAYS - GL_NEVER + 1> _castingPrograms; }; } diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 9e6d461ae7..de778f14b5 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -42,8 +42,8 @@ namespace namespace SceneUtil { -std::array, GL_ALWAYS - GL_NEVER> ShadowsBin::sCastingPrograms = { - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr +std::array, GL_ALWAYS - GL_NEVER + 1> ShadowsBin::sCastingPrograms = { + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; ShadowsBin::ShadowsBin() @@ -150,7 +150,7 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un return sg; } -void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms) +void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms) { sCastingPrograms = castingPrograms; osg::ref_ptr bin(new ShadowsBin); diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index 7247a9af4e..d8bdd86071 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -17,12 +17,12 @@ namespace SceneUtil class ShadowsBin : public osgUtil::RenderBin { private: - static std::array, GL_ALWAYS - GL_NEVER> sCastingPrograms; + static std::array, GL_ALWAYS - GL_NEVER + 1> sCastingPrograms; osg::ref_ptr mNoTestStateSet; osg::ref_ptr mShaderAlphaTestStateSet; - std::array, GL_ALWAYS - GL_NEVER> mAlphaFuncShaders; + std::array, GL_ALWAYS - GL_NEVER + 1> mAlphaFuncShaders; public: META_Object(SceneUtil, ShadowsBin) ShadowsBin(); @@ -65,13 +65,13 @@ namespace SceneUtil osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting); - static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms); + static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms); }; class ShadowsBinAdder { public: - ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } + ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } }; } diff --git a/components/shader/removedalphafunc.cpp b/components/shader/removedalphafunc.cpp index 0da36ab48f..6b701b8900 100644 --- a/components/shader/removedalphafunc.cpp +++ b/components/shader/removedalphafunc.cpp @@ -6,8 +6,8 @@ namespace Shader { - std::array, GL_ALWAYS - GL_NEVER> RemovedAlphaFunc::sInstances{ - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr + std::array, GL_ALWAYS - GL_NEVER + 1> RemovedAlphaFunc::sInstances{ + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; osg::ref_ptr RemovedAlphaFunc::getInstance(GLenum func) diff --git a/components/shader/removedalphafunc.hpp b/components/shader/removedalphafunc.hpp index 1aa9978cfe..c744391e56 100644 --- a/components/shader/removedalphafunc.hpp +++ b/components/shader/removedalphafunc.hpp @@ -34,7 +34,7 @@ namespace Shader protected: virtual ~RemovedAlphaFunc() = default; - static std::array, GL_ALWAYS - GL_NEVER> sInstances; + static std::array, GL_ALWAYS - GL_NEVER + 1> sInstances; }; } #endif //OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H From 0b5d5eab4c997789b1a3eab5b9da8406f6fc1552 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 02:11:51 +0000 Subject: [PATCH 0110/2859] Move is faster --- components/sceneutil/shadowsbin.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index de778f14b5..312d4226b5 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -127,11 +127,9 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un if (state.mAlphaBlend) { sg_new = sg->find_or_insert(mShaderAlphaTestStateSet); - for (RenderLeaf* leaf : sg->_leaves) - { + sg_new->_leaves = std::move(sg->_leaves); + for (RenderLeaf* leaf : sg_new->_leaves) leaf->_parent = sg_new; - sg_new->_leaves.push_back(leaf); - } sg = sg_new; } @@ -139,11 +137,9 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS) { sg_new = sg->find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]); - for (RenderLeaf* leaf : sg->_leaves) - { + sg_new->_leaves = std::move(sg->_leaves); + for (RenderLeaf* leaf : sg_new->_leaves) leaf->_parent = sg_new; - sg_new->_leaves.push_back(leaf); - } sg = sg_new; } From 7bae6691b63878e76777ec497a1fe14bb00eb48c Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 18 Dec 2020 08:18:07 +0100 Subject: [PATCH 0111/2859] Introduce World::moveObjectBy() function to translate an object relatively to its current position. Use it in relevant MWScripts opcode (move and moveworld). Remove the fragile detection of scripted translation from PhysicsTaskScheduler. No user visible change, just a more robust mechanism. --- apps/openmw/mwbase/world.hpp | 3 +++ apps/openmw/mwphysics/actor.cpp | 17 ++++++++++++----- apps/openmw/mwphysics/actor.hpp | 5 ++++- apps/openmw/mwphysics/mtphysics.cpp | 13 +------------ .../mwscript/transformationextensions.cpp | 19 ++++++------------- apps/openmw/mwworld/worldimp.cpp | 12 ++++++++++++ apps/openmw/mwworld/worldimp.hpp | 3 +++ 7 files changed, 41 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index d4f1d2f8ab..619bbbdf5e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -286,6 +286,9 @@ namespace MWBase virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0; + ///< @return an updated Ptr + virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z, diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index b1f219e338..e7fe93b35d 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -120,6 +120,8 @@ int Actor::getCollisionMask() const void Actor::updatePositionUnsafe() { + if (!mWorldPositionChanged && mWorldPosition != mPtr.getRefData().getPosition().asVec3()) + mWorldPositionChanged = true; mWorldPosition = mPtr.getRefData().getPosition().asVec3(); } @@ -153,6 +155,7 @@ void Actor::updateCollisionObjectPositionUnsafe() mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition)); mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation)); mCollisionObject->setWorldTransform(mLocalTransform); + mWorldPositionChanged = false; } void Actor::updateCollisionObjectPosition() @@ -167,18 +170,20 @@ osg::Vec3f Actor::getCollisionObjectPosition() const return Misc::Convert::toOsg(mLocalTransform.getOrigin()); } -void Actor::setPosition(const osg::Vec3f& position) +bool Actor::setPosition(const osg::Vec3f& position) { std::scoped_lock lock(mPositionMutex); - mPreviousPosition = mPosition; - mPosition = position; + bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; + mPreviousPosition = mPosition + mPositionOffset; + mPosition = position + mPositionOffset; + mPositionOffset = osg::Vec3f(); + return hasChanged; } void Actor::adjustPosition(const osg::Vec3f& offset) { std::scoped_lock lock(mPositionMutex); - mPosition += offset; - mPreviousPosition += offset; + mPositionOffset += offset; } void Actor::resetPosition() @@ -189,6 +194,8 @@ void Actor::resetPosition() mPosition = mWorldPosition; mSimulationPosition = mWorldPosition; updateCollisionObjectPositionUnsafe(); + mStandingOnPtr = nullptr; + mWorldPositionChanged = false; } osg::Vec3f Actor::getPosition() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 07a9bebd28..7c8ea0b734 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -90,8 +90,9 @@ namespace MWPhysics /** * Store the current position into mPreviousPosition, then move to this position. + * Returns true if the new position is different. */ - void setPosition(const osg::Vec3f& position); + bool setPosition(const osg::Vec3f& position); void resetPosition(); void adjustPosition(const osg::Vec3f& offset); @@ -177,6 +178,8 @@ namespace MWPhysics osg::Vec3f mSimulationPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; + osg::Vec3f mPositionOffset; + bool mWorldPositionChanged; btTransform mLocalTransform; mutable std::mutex mPositionMutex; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index a78a307883..95c61edc7f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -100,15 +100,6 @@ namespace osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = timeAccum / physicsDt; - - // account for force change of actor's position in the main thread - const auto correction = actorData.mActorRaw->getWorldPosition() - actorData.mOrigin; - if (correction.length() != 0) - { - actorData.mActorRaw->adjustPosition(correction); - actorData.mPosition = actorData.mActorRaw->getPosition(); - } - return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } @@ -511,9 +502,7 @@ namespace MWPhysics { if(const auto actor = actorData.mActor.lock()) { - bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition(); - actorData.mActorRaw->setPosition(actorData.mPosition); - if (positionChanged) + if (actor->setPosition(actorData.mPosition)) { actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index a908940ed9..103c6629d2 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,11 +32,7 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - { - osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); - actorPos += diff; - MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z()); - } + MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff); } template @@ -727,14 +723,12 @@ namespace MWScript return; osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; - osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3()); - worldPos += diff; // We should move actors, standing on moving object, too. // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z())); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); } }; @@ -755,15 +749,14 @@ namespace MWScript Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - const float *objPos = ptr.getRefData().getPosition().pos; osg::Vec3f diff; if (axis == "x") - diff.x() += movement; + diff.x() = movement; else if (axis == "y") - diff.y() += movement; + diff.y() = movement; else if (axis == "z") - diff.z() += movement; + diff.z() = movement; else return; @@ -771,7 +764,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z())); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3756ea04b8..3086fed833 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1251,6 +1251,18 @@ namespace MWWorld return moveObjectImp(ptr, x, y, z, true, moveToActive); } + MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec) + { + auto* actor = mPhysics->getActor(ptr); + if (actor) + { + actor->adjustPosition(vec); + return ptr; + } + osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; + return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); + } + void World::scaleObject (const Ptr& ptr, float scale) { if (mPhysics->getActor(ptr)) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1fc5ebc20d..75717b16b2 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -380,6 +380,9 @@ namespace MWWorld MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; ///< @return an updated Ptr + MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override; + ///< @return an updated Ptr + void scaleObject (const Ptr& ptr, float scale) override; /// World rotates object, uses radians From ea8f98b33960f1d8b3e05900362f9294b93b1686 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 18 Dec 2020 08:48:39 +0000 Subject: [PATCH 0112/2859] Wait for initialDrawCallback to finish before removing it --- apps/openmw/mwgui/loadingscreen.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 9ab5e32a3f..5f0e9f33a3 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,6 +1,7 @@ #include "loadingscreen.hpp" #include +#include #include @@ -139,6 +140,7 @@ namespace MWGui public: CopyFramebufferToTextureCallback(osg::Texture2D* texture) : mTexture(texture) + , mOneshot(true) { } @@ -147,9 +149,25 @@ namespace MWGui int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); + + { + std::unique_lock lock(mMutex); + mOneshot = false; + } + mSignal.notify_all(); + } + + void wait() + { + std::unique_lock lock(mMutex); + while (mOneshot) + mSignal.wait(lock); } private: + mutable bool mOneshot; + mutable std::mutex mMutex; + mutable std::condition_variable mSignal; osg::ref_ptr mTexture; }; @@ -375,7 +393,7 @@ namespace MWGui if (mCopyFramebufferToTextureCallback) { - + mCopyFramebufferToTextureCallback->wait(); #if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); #else From 4e7c9b66960258bfb7e51d71ea79130ca3758581 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 18 Dec 2020 09:22:02 +0100 Subject: [PATCH 0113/2859] Embed physics simulation results inside of actor class. This gives finer control over reseting positions (switch off tcl is no longer glitchy) and solve most of the erroneous usage of stale World::Ptr indicated by: "Error in frame: moveTo: object is not in this cell" --- apps/openmw/mwphysics/actor.cpp | 7 ++- apps/openmw/mwphysics/actor.hpp | 1 + apps/openmw/mwphysics/mtphysics.cpp | 84 ++++++++++--------------- apps/openmw/mwphysics/mtphysics.hpp | 9 ++- apps/openmw/mwphysics/physicssystem.cpp | 18 +++++- apps/openmw/mwphysics/physicssystem.hpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 23 ++++--- 7 files changed, 73 insertions(+), 75 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index e7fe93b35d..10fd463fb0 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -76,6 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); resetPosition(); addCollisionMask(getCollisionMask()); + updateCollisionObjectPosition(); } Actor::~Actor() @@ -139,7 +140,9 @@ osg::Vec3f Actor::getWorldPosition() const void Actor::setSimulationPosition(const osg::Vec3f& position) { - mSimulationPosition = position; + if (!mResetSimulation) + mSimulationPosition = position; + mResetSimulation = false; } osg::Vec3f Actor::getSimulationPosition() const @@ -193,9 +196,9 @@ void Actor::resetPosition() mPreviousPosition = mWorldPosition; mPosition = mWorldPosition; mSimulationPosition = mWorldPosition; - updateCollisionObjectPositionUnsafe(); mStandingOnPtr = nullptr; mWorldPositionChanged = false; + mResetSimulation = true; } osg::Vec3f Actor::getPosition() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 7c8ea0b734..b26b52d144 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -180,6 +180,7 @@ namespace MWPhysics osg::Vec3f mPreviousPosition; osg::Vec3f mPositionOffset; bool mWorldPositionChanged; + bool mResetSimulation; btTransform mLocalTransform; mutable std::mutex mPositionMutex; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 95c61edc7f..60a7259f80 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -204,31 +204,31 @@ namespace MWPhysics thread.join(); } - const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + const std::vector& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. std::unique_lock lock(mSimulationMutex); - for (auto& data : actorsData) - data.updatePosition(); + mMovedActors.clear(); // start by finishing previous background computation if (mNumThreads != 0) { for (auto& data : mActorsFrameData) { - // Ignore actors that were deleted while the background thread was running - if (!data.mActor.lock()) - continue; - - updateMechanics(data); - if (mAdvanceSimulation) - data.mActorRaw->setStandingOnPtr(data.mStandingOn); + // Only return actors that are still part of the scene + if (std::any_of(actorsData.begin(), actorsData.end(), [&data](const auto& newFrameData) { return data.mActorRaw->getPtr() == newFrameData.mActorRaw->getPtr(); })) + { + updateMechanics(data); - if (mMovementResults.find(data.mPtr) != mMovementResults.end()) - data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]); + // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values + if (mAdvanceSimulation) + data.mActorRaw->setStandingOnPtr(data.mStandingOn); + data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); + mMovedActors.emplace_back(data.mActorRaw->getPtr()); + } } if (mFrameNumber == frameNumber - 1) @@ -243,6 +243,8 @@ namespace MWPhysics } // init + for (auto& data : actorsData) + data.updatePosition(); mRemainingSteps = numSteps; mTimeAccum = timeAccum; mActorsFrameData = std::move(actorsData); @@ -257,52 +259,28 @@ namespace MWPhysics if (mNumThreads == 0) { - mMovementResults.clear(); syncComputation(); - - for (auto& data : mActorsFrameData) - { - if (mAdvanceSimulation) - data.mActorRaw->setStandingOnPtr(data.mStandingOn); - if (mMovementResults.find(data.mPtr) != mMovementResults.end()) - data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]); - } - return mMovementResults; - } - - // Remove actors that were deleted while the background thread was running - for (auto& data : mActorsFrameData) - { - if (!data.mActor.lock()) - mMovementResults.erase(data.mPtr); + return mMovedActors; } - std::swap(mMovementResults, mPreviousMovementResults); - - // mMovementResults is shared between all workers instance - // pre-allocate all nodes so that we don't need synchronization - mMovementResults.clear(); - for (const auto& m : mActorsFrameData) - mMovementResults[m.mPtr] = m.mPosition; lock.unlock(); mHasJob.notify_all(); - return mPreviousMovementResults; + return mMovedActors; } - const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) + const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { std::unique_lock lock(mSimulationMutex); - mMovementResults.clear(); - mPreviousMovementResults.clear(); + mMovedActors.clear(); mActorsFrameData.clear(); - for (const auto& [_, actor] : actors) { actor->resetPosition(); - actor->setStandingOnPtr(nullptr); - mMovementResults[actor->getPtr()] = actor->getWorldPosition(); + actor->setSimulationPosition(actor->getWorldPosition()); // resetPosition skip next simulation, now we need to "consume" it + actor->updateCollisionObjectPosition(); + mMovedActors.emplace_back(actor->getPtr()); } - return mMovementResults; + return mMovedActors; } void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const @@ -370,17 +348,17 @@ namespace MWPhysics mCollisionWorld->removeCollisionObject(collisionObject); } - void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr) + void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr, bool immediate) { - if (mDeferAabbUpdate) + if (!mDeferAabbUpdate || immediate) { - std::unique_lock lock(mUpdateAabbMutex); - mUpdateAabb.insert(std::move(ptr)); + std::unique_lock lock(mCollisionWorldMutex); + updatePtrAabb(ptr); } else { - std::unique_lock lock(mCollisionWorldMutex); - updatePtrAabb(ptr); + std::unique_lock lock(mUpdateAabbMutex); + mUpdateAabb.insert(std::move(ptr)); } } @@ -484,7 +462,6 @@ namespace MWPhysics { auto& actorData = mActorsFrameData[job]; handleFall(actorData, mAdvanceSimulation); - mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt); } } @@ -539,8 +516,11 @@ namespace MWPhysics for (auto& actorData : mActorsFrameData) { handleFall(actorData, mAdvanceSimulation); - mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt); + actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); updateMechanics(actorData); + mMovedActors.emplace_back(actorData.mActorRaw->getPtr()); + if (mAdvanceSimulation) + actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); } } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 3a761829b5..90e1007b87 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -32,9 +32,9 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + const std::vector& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - const PtrPositionList& resetSimulation(const ActorMap& actors); + const std::vector& resetSimulation(const ActorMap& actors); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; @@ -46,7 +46,7 @@ namespace MWPhysics void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask); void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); void removeCollisionObject(btCollisionObject* collisionObject); - void updateSingleAabb(std::weak_ptr ptr); + void updateSingleAabb(std::weak_ptr ptr, bool immediate=false); bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); private: @@ -60,8 +60,7 @@ namespace MWPhysics std::unique_ptr mWorldFrameData; std::vector mActorsFrameData; - PtrPositionList mMovementResults; - PtrPositionList mPreviousMovementResults; + std::vector mMovedActors; const float mPhysicsDt; float mTimeAccum; std::shared_ptr mCollisionWorld; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 151801e67d..8b9cb0a79f 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -437,7 +437,7 @@ namespace MWPhysics ActorMap::iterator found = mActors.find(ptr); if (found == mActors.end()) return ptr.getRefData().getPosition().asVec3(); - found->second->resetPosition(); + resetPosition(ptr); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); } @@ -647,6 +647,17 @@ namespace MWPhysics } } + void PhysicsSystem::resetPosition(const MWWorld::ConstPtr &ptr) + { + ActorMap::iterator foundActor = mActors.find(ptr); + if (foundActor != mActors.end()) + { + foundActor->second->resetPosition(); + mTaskScheduler->updateSingleAabb(foundActor->second, true); + return; + } + } + void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { osg::ref_ptr shape = mShapeManager->getShape(mesh); @@ -683,6 +694,8 @@ namespace MWPhysics if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); + if (cmode) + resetPosition(found->first); cmode = !cmode; found->second->enableCollisionMode(cmode); // NB: Collision body isn't disabled for vanilla TCL compatibility @@ -711,7 +724,7 @@ namespace MWPhysics mMovementQueue.clear(); } - const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + const std::vector& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { mTimeAccum += dt; @@ -930,7 +943,6 @@ namespace MWPhysics void ActorFrameData::updatePosition() { mActorRaw->updatePosition(); - mOrigin = mActorRaw->getSimulationPosition(); mPosition = mActorRaw->getPosition(); if (mMoveToWaterSurface) { diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b33d829a46..8c76749913 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -50,8 +50,6 @@ class btVector3; namespace MWPhysics { - using PtrPositionList = std::map; - class HeightField; class Object; class Actor; @@ -99,7 +97,6 @@ namespace MWPhysics float mOldHeight; float mFallHeight; osg::Vec3f mMovement; - osg::Vec3f mOrigin; osg::Vec3f mPosition; ESM::Position mRefpos; }; @@ -147,6 +144,7 @@ namespace MWPhysics void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr); + void resetPosition(const MWWorld::ConstPtr &ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); @@ -210,7 +208,7 @@ namespace MWPhysics void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); /// Apply all queued movements, then clear the list. - const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + const std::vector& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3086fed833..c2ad6e22f2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1356,12 +1356,7 @@ namespace MWWorld } moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); - if (ptr.getClass().isActor()) - { - MWPhysics::Actor* actor = mPhysics->getActor(ptr); - if (actor) - actor->resetPosition(); - } + mPhysics->resetPosition(ptr); } void World::fixPosition() @@ -1512,16 +1507,26 @@ namespace MWWorld mProjectileManager->processHits(); mDiscardMovements = false; - for(const auto& [actor, position]: results) + for(const auto& actor : results) { // Handle player last, in case a cell transition occurs if(actor != getPlayerPtr()) + { + auto* physactor = mPhysics->getActor(actor); + assert(physactor); + const auto position = physactor->getSimulationPosition(); moveObjectImp(actor, position.x(), position.y(), position.z(), false); + } } - const auto player = results.find(getPlayerPtr()); + const auto player = std::find(results.begin(), results.end(), getPlayerPtr()); if (player != results.end()) - moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); + { + auto* physactor = mPhysics->getActor(*player); + assert(physactor); + const auto position = physactor->getSimulationPosition(); + moveObjectImp(*player, position.x(), position.y(), position.z(), false); + } } void World::updateNavigator() From 2ecd00b6db190476f4f8d713044fd3c42dd5f220 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 16:31:21 +0000 Subject: [PATCH 0114/2859] Don't accidentally enable alpha testing for all shadow casting --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9cb5146dc3..736e4f2ee1 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -379,7 +379,7 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it - mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_GREATER)); + mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); From eec2ba3623f0ad74e3f6747c9dcb959d28321edd Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 18 Dec 2020 18:15:14 +0000 Subject: [PATCH 0115/2859] Loading screen initialdrawcallback 4th time's the charm --- apps/openmw/mwgui/loadingscreen.cpp | 34 ++++++++++++++++++++++++----- apps/openmw/mwgui/loadingscreen.hpp | 1 + 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 5f0e9f33a3..7ddc8c5507 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -46,6 +46,7 @@ namespace MWGui , mProgress(0) , mShowWallpaper(true) , mOldCallback(nullptr) + , mHasCallback(false) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); @@ -139,13 +140,19 @@ namespace MWGui { public: CopyFramebufferToTextureCallback(osg::Texture2D* texture) - : mTexture(texture) - , mOneshot(true) + : mOneshot(true) + , mTexture(texture) { } void operator () (osg::RenderInfo& renderInfo) const override { + { + std::unique_lock lock(mMutex); + mOneshot = false; + } + mSignal.notify_all(); + int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); @@ -164,6 +171,18 @@ namespace MWGui mSignal.wait(lock); } + void waitUntilInvoked() + { + std::unique_lock lock(mMutex); + while (mOneshot) + mSignal.wait(lock); + } + + void reset() + { + mOneshot = true; + } + private: mutable bool mOneshot; mutable std::mutex mMutex; @@ -349,6 +368,8 @@ namespace MWGui mOldCallback = mViewer->getCamera()->getInitialDrawCallback(); mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback); #endif + mCopyFramebufferToTextureCallback->reset(); + mHasCallback = true; mBackgroundImage->setBackgroundImage(""); mBackgroundImage->setVisible(false); @@ -391,16 +412,19 @@ namespace MWGui mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - if (mCopyFramebufferToTextureCallback) + if (mHasCallback) { - mCopyFramebufferToTextureCallback->wait(); + mCopyFramebufferToTextureCallback->waitUntilInvoked(); + + // Note that we are removing the callback before the draw thread has returned from it. + // This is OK as we are retaining the ref_ptr. #if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); #else // TODO: Remove once we officially end support for OSG versions pre 3.5.10 mViewer->getCamera()->setInitialDrawCallback(mOldCallback); #endif - mCopyFramebufferToTextureCallback = nullptr; + mHasCallback = false; } mLastRenderTime = mTimer.time_m(); diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index d58899eae4..5d86ed3896 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -88,6 +88,7 @@ namespace MWGui osg::ref_ptr mTexture; osg::ref_ptr mCopyFramebufferToTextureCallback; osg::ref_ptr mOldCallback; + bool mHasCallback; std::unique_ptr mGuiTexture; void changeWallpaper(); From a2a462f416b7c3c8b1f0c126833e2f101b9d5049 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 18 Dec 2020 20:04:24 +0000 Subject: [PATCH 0116/2859] Update CMakeLists.txt to disable MSVC warning 4866 --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76764fbd93..f0f7a3fc5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -557,6 +557,7 @@ if (WIN32) # caused by MyGUI 4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception' 4297 # function assumed not to throw an exception but does + 4866 # compiler may not enforce left-to-right evaluation order for call # OpenMW specific warnings 4099 # Type mismatch, declared class or struct is defined with other type From 24a8b8c66ad09f12850cf5ef2ce5334adeb5b611 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 18 Dec 2020 21:16:48 +0100 Subject: [PATCH 0117/2859] Remove redundant call to resetPosition(). adjustPosition() takes care of it, --- apps/openmw/mwphysics/physicssystem.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8b9cb0a79f..3ea48a1f29 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -694,8 +694,6 @@ namespace MWPhysics if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); - if (cmode) - resetPosition(found->first); cmode = !cmode; found->second->enableCollisionMode(cmode); // NB: Collision body isn't disabled for vanilla TCL compatibility From 58297ffbf46abf178f02f4bb73d424817e4627ed Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 18 Dec 2020 21:18:04 +0100 Subject: [PATCH 0118/2859] Move stats update into their own function. --- apps/openmw/mwphysics/mtphysics.cpp | 24 ++++++++++++++---------- apps/openmw/mwphysics/mtphysics.hpp | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 60a7259f80..4838cab505 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -230,16 +230,7 @@ namespace MWPhysics mMovedActors.emplace_back(data.mActorRaw->getPtr()); } } - - if (mFrameNumber == frameNumber - 1) - { - stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin)); - stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd)); - stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd)); - } - mFrameStart = frameStart; - mTimeBegin = mTimer->tick(); - mFrameNumber = frameNumber; + updateStats(frameStart, frameNumber, stats); } // init @@ -523,4 +514,17 @@ namespace MWPhysics actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); } } + + void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + { + if (mFrameNumber == frameNumber - 1) + { + stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin)); + stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd)); + stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd)); + } + mFrameStart = frameStart; + mTimeBegin = mTimer->tick(); + mFrameNumber = frameNumber; + } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 90e1007b87..b35ebd5ee9 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -57,6 +57,7 @@ namespace MWPhysics void refreshLOSCache(); void updateAabbs(); void updatePtrAabb(const std::weak_ptr& ptr); + void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; std::vector mActorsFrameData; From 8e084dea2e44e0d48513261c1365ce477651e87a Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 18 Dec 2020 20:40:27 +0100 Subject: [PATCH 0119/2859] Don't cache Ptr, it can be updated while the simulation is running. --- apps/openmw/mwphysics/movementsolver.cpp | 9 ++++++--- apps/openmw/mwphysics/mtphysics.cpp | 14 ++++++++++---- apps/openmw/mwphysics/physicssystem.cpp | 16 ++++++++-------- apps/openmw/mwphysics/physicssystem.hpp | 3 +-- apps/openmw/mwphysics/ptrholder.hpp | 8 ++++++++ 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 3c78104dc7..e55ec9166c 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -78,12 +78,14 @@ namespace MWPhysics WorldFrameData& worldData) { auto* physicActor = actor.mActorRaw; - auto ptr = actor.mPtr; const ESM::Position& refpos = actor.mRefpos; // Early-out for totally static creatures // (Not sure if gravity should still apply?) - if (!ptr.getClass().isMobile(ptr)) - return; + { + const auto ptr = physicActor->getPtr(); + if (!ptr.getClass().isMobile(ptr)) + return; + } // Reset per-frame data physicActor->setWalkingOnWater(false); @@ -211,6 +213,7 @@ namespace MWPhysics if (result) { // don't let pure water creatures move out of water after stepMove + const auto ptr = physicActor->getPtr(); if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel) newPosition = oldPosition; } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 60a7259f80..bedcaba6ac 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -87,12 +87,13 @@ namespace void updateMechanics(MWPhysics::ActorFrameData& actorData) { + auto ptr = actorData.mActorRaw->getPtr(); if (actorData.mDidJump) - handleJump(actorData.mPtr); + handleJump(ptr); - MWMechanics::CreatureStats& stats = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (actorData.mNeedLand) - stats.land(actorData.mPtr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming)); + stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming)); else if (actorData.mFallHeight < 0) stats.addToFallHeight(-actorData.mFallHeight); } @@ -218,8 +219,13 @@ namespace MWPhysics { for (auto& data : mActorsFrameData) { + const auto actorActive = [&data](const auto& newFrameData) -> bool + { + const auto actor = data.mActor.lock(); + return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr(); + }; // Only return actors that are still part of the scene - if (std::any_of(actorsData.begin(), actorsData.end(), [&data](const auto& newFrameData) { return data.mActorRaw->getPtr() == newFrameData.mActorRaw->getPtr(); })) + if (std::any_of(actorsData.begin(), actorsData.end(), actorActive)) { updateMechanics(data); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8b9cb0a79f..9c239200bf 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -783,7 +783,7 @@ namespace MWPhysics if (numSteps == 0) standingOn = physicActor->getStandingOnPtr(); - actorsFrameData.emplace_back(std::move(physicActor), character, standingOn, moveToWaterSurface, movement, slowFall, waterlevel); + actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); } mMovementQueue.clear(); return actorsFrameData; @@ -925,18 +925,18 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, + ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() { const MWBase::World *world = MWBase::Environment::get().getWorld(); - mPtr = actor->getPtr(); - mFlying = world->isFlying(character); - mSwimming = world->isSwimming(character); - mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0; - mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead(); + const auto ptr = actor->getPtr(); + mFlying = world->isFlying(ptr); + mSwimming = world->isSwimming(ptr); + mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; + mIsDead = ptr.getClass().getCreatureStats(ptr).isDead(); mWasOnGround = actor->getOnGround(); } @@ -950,7 +950,7 @@ namespace MWPhysics mActorRaw->setPosition(mPosition); } mOldHeight = mPosition.z(); - mRefpos = mPtr.getRefData().getPosition(); + mRefpos = mActorRaw->getPtr().getRefData().getPosition(); } WorldFrameData::WorldFrameData() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 8c76749913..39bf9dd8be 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,11 +78,10 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); + ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); void updatePosition(); std::weak_ptr mActor; Actor* mActorRaw; - MWWorld::Ptr mPtr; MWWorld::Ptr mStandingOn; bool mFlying; bool mSwimming; diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index f8188b43ed..152a5d64fc 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H +#include + #include "../mwworld/ptr.hpp" namespace MWPhysics @@ -12,21 +14,27 @@ namespace MWPhysics void updatePtr(const MWWorld::Ptr& updated) { + std::scoped_lock lock(mMutex); mPtr = updated; } MWWorld::Ptr getPtr() { + std::scoped_lock lock(mMutex); return mPtr; } MWWorld::ConstPtr getPtr() const { + std::scoped_lock lock(mMutex); return mPtr; } protected: MWWorld::Ptr mPtr; + + private: + mutable std::mutex mMutex; }; } From 93a12fe388236f28b7451cdedffc339f8c23c304 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 18 Dec 2020 23:47:01 +0100 Subject: [PATCH 0120/2859] Avoid dynamic_cast when possible. --- .../closestnotmerayresultcallback.cpp | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index f9dc3bdd50..c3104f8603 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -41,15 +41,21 @@ namespace MWPhysics btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); if (mProjectile) { - auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); - if (auto* target = dynamic_cast(holder)) + switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup) { - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - } - else if (auto* target = dynamic_cast(holder)) - { - target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + case CollisionType_Actor: + { + auto* target = static_cast(rayResult.m_collisionObject->getUserPointer()); + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + case CollisionType_Projectile: + { + auto* target = static_cast(rayResult.m_collisionObject->getUserPointer()); + target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + break; + } } } From 637c76f438774eb691b1980ad1636f1918e889f5 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 19 Dec 2020 08:34:36 +0000 Subject: [PATCH 0121/2859] Update CMakeLists.txt --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0f7a3fc5d..be7bc79e3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -557,7 +557,6 @@ if (WIN32) # caused by MyGUI 4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception' 4297 # function assumed not to throw an exception but does - 4866 # compiler may not enforce left-to-right evaluation order for call # OpenMW specific warnings 4099 # Type mismatch, declared class or struct is defined with other type @@ -585,6 +584,12 @@ if (WIN32) 5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp) ) endif() + + if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" ) + set(WARNINGS_DISABLE ${WARNINGS_DISABLE} + 4866 # compiler may not enforce left-to-right evaluation order for call + ) + endif() foreach(d ${WARNINGS_DISABLE}) set(WARNINGS "${WARNINGS} /wd${d}") From e5f0c7f117c12d4b229833355a7ad9b9c3c46a15 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 19 Dec 2020 16:54:50 +0100 Subject: [PATCH 0122/2859] Fix a typo introduced in 08e73a09ec41fc791166473acdf91a3c0b39c0bb It closes #5752 --- apps/openmw/mwworld/worldimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c2ad6e22f2..6a7e396447 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1345,7 +1345,7 @@ namespace MWWorld } const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits::max(); - pos.z() = std::max(pos.z(), terrainHeight + 20); // place slightly above terrain. will snap down to ground with code below + pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below // We still should trace down dead persistent actors - they do not use the "swimdeath" animation. bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()); From cc2ce9fa3e49b86fe1501eb2fe3b26e80918c995 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 19 Dec 2020 21:57:42 +0000 Subject: [PATCH 0123/2859] Explicitly default-construct array The docs seem to imply this is automatic when the array contains a class-type, which osg::ref_ptr is, but I got a crash log that doesn't make sense if that's true. --- components/sceneutil/mwshadowtechnique.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 31267f75ce..39e47b0ea3 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -782,13 +782,15 @@ void MWShadowTechnique::ViewDependentData::releaseGLObjects(osg::State* state) c MWShadowTechnique::MWShadowTechnique(): ShadowTechnique(), _enableShadows(false), - _debugHud(nullptr) + _debugHud(nullptr), + _castingPrograms{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } { _shadowRecievingPlaceholderStateSet = new osg::StateSet; } MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): ShadowTechnique(vdsm,copyop) + , _castingPrograms(vdsm._castingPrograms) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; _enableShadows = vdsm._enableShadows; From 0e4e8eb0f3af47ea236dac8c7372da97eb39dbdb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 20 Dec 2020 01:22:14 +0000 Subject: [PATCH 0124/2859] Add glDebugGroup support --- components/debug/gldebug.cpp | 306 +++++++++++++++++++++++------------ components/debug/gldebug.hpp | 60 +++++++ 2 files changed, 267 insertions(+), 99 deletions(-) diff --git a/components/debug/gldebug.cpp b/components/debug/gldebug.cpp index 3c5ec728ac..22d7bbe682 100644 --- a/components/debug/gldebug.cpp +++ b/components/debug/gldebug.cpp @@ -38,127 +38,235 @@ either expressed or implied, of the FreeBSD Project. // OpenGL constants not provided by OSG: #include -void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +namespace Debug { -#ifdef GL_DEBUG_OUTPUT - std::string srcStr; - switch (source) + + void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { - case GL_DEBUG_SOURCE_API: - srcStr = "API"; - break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: - srcStr = "WINDOW_SYSTEM"; - break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: - srcStr = "SHADER_COMPILER"; - break; - case GL_DEBUG_SOURCE_THIRD_PARTY: - srcStr = "THIRD_PARTY"; - break; - case GL_DEBUG_SOURCE_APPLICATION: - srcStr = "APPLICATION"; - break; - case GL_DEBUG_SOURCE_OTHER: - srcStr = "OTHER"; - break; - default: - srcStr = "UNDEFINED"; - break; +#ifdef GL_DEBUG_OUTPUT + std::string srcStr; + switch (source) + { + case GL_DEBUG_SOURCE_API: + srcStr = "API"; + break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + srcStr = "WINDOW_SYSTEM"; + break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + srcStr = "SHADER_COMPILER"; + break; + case GL_DEBUG_SOURCE_THIRD_PARTY: + srcStr = "THIRD_PARTY"; + break; + case GL_DEBUG_SOURCE_APPLICATION: + srcStr = "APPLICATION"; + break; + case GL_DEBUG_SOURCE_OTHER: + srcStr = "OTHER"; + break; + default: + srcStr = "UNDEFINED"; + break; + } + + std::string typeStr; + + Level logSeverity = Warning; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: + typeStr = "ERROR"; + logSeverity = Error; + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + typeStr = "DEPRECATED_BEHAVIOR"; + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + typeStr = "UNDEFINED_BEHAVIOR"; + break; + case GL_DEBUG_TYPE_PORTABILITY: + typeStr = "PORTABILITY"; + break; + case GL_DEBUG_TYPE_PERFORMANCE: + typeStr = "PERFORMANCE"; + break; + case GL_DEBUG_TYPE_OTHER: + typeStr = "OTHER"; + break; + default: + typeStr = "UNDEFINED"; + break; + } + + Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message; +#endif } - std::string typeStr; + class PushDebugGroup + { + public: + static std::unique_ptr sInstance; + + void (GL_APIENTRY * glPushDebugGroup) (GLenum source, GLuint id, GLsizei length, const GLchar * message); + + void (GL_APIENTRY * glPopDebugGroup) (void); - Debug::Level logSeverity = Debug::Warning; - switch (type) + bool valid() + { + return glPushDebugGroup && glPopDebugGroup; + } + }; + + std::unique_ptr PushDebugGroup::sInstance{ std::make_unique() }; + + EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false) { - case GL_DEBUG_TYPE_ERROR: - typeStr = "ERROR"; - logSeverity = Debug::Error; - break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: - typeStr = "DEPRECATED_BEHAVIOR"; - break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: - typeStr = "UNDEFINED_BEHAVIOR"; - break; - case GL_DEBUG_TYPE_PORTABILITY: - typeStr = "PORTABILITY"; - break; - case GL_DEBUG_TYPE_PERFORMANCE: - typeStr = "PERFORMANCE"; - break; - case GL_DEBUG_TYPE_OTHER: - typeStr = "OTHER"; - break; - default: - typeStr = "UNDEFINED"; - break; } - Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message; + void EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext) + { +#ifdef GL_DEBUG_OUTPUT + OpenThreads::ScopedLock lock(mMutex); + + unsigned int contextID = graphicsContext->getState()->getContextID(); + + typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); + typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); + typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam); + + GLDebugMessageControlFunction glDebugMessageControl = nullptr; + GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr; + + if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl"); + osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPushDebugGroup, "glPushDebugGroup"); + osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPopDebugGroup, "glPopDebugGroup"); + } + else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB"); + } + else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD"); + } + + if (glDebugMessageCallback && glDebugMessageControl) + { + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true); + glDebugMessageCallback(debugCallback, nullptr); + + Log(Info) << "OpenGL debug callback attached."; + } + else #endif -} + Log(Error) << "Unable to attach OpenGL debug callback."; + } -void enableGLDebugExtension(unsigned int contextID) -{ -#ifdef GL_DEBUG_OUTPUT - typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); - typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); - typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam); - - GLDebugMessageControlFunction glDebugMessageControl = nullptr; - GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr; - - if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug")) + bool shouldDebugOpenGL() + { + const char* env = std::getenv("OPENMW_DEBUG_OPENGL"); + if (!env) + return false; + std::string str(env); + if (str.length() == 0) + return true; + + return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos; + } + + DebugGroup::DebugGroup(const std::string & message, GLuint id) + : mSource(GL_DEBUG_SOURCE_APPLICATION) + , mId(id) + , mMessage(message) { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl"); } - else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output")) + + DebugGroup::DebugGroup(const DebugGroup & debugGroup, const osg::CopyOp & copyop) + : osg::StateAttribute(debugGroup, copyop) + , mSource(debugGroup.mSource) + , mId(debugGroup.mId) + , mMessage(debugGroup.mMessage) { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB"); } - else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output")) + + void DebugGroup::apply(osg::State & state) const { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD"); + if (!PushDebugGroup::sInstance->valid()) + { + Log(Error) << "OpenGL debug groups not supported on this system, or OPENMW_DEBUG_OPENGL environment variable not set."; + return; + } + + auto& attributeVec = state.getAttributeVec(this); + auto& lastAppliedStack = sLastAppliedStack[state.getContextID()]; + + size_t firstNonMatch = 0; + while (firstNonMatch < lastAppliedStack.size() + && ((firstNonMatch < attributeVec.size() && lastAppliedStack[firstNonMatch] == attributeVec[firstNonMatch].first) + || lastAppliedStack[firstNonMatch] == this)) + firstNonMatch++; + + for (size_t i = lastAppliedStack.size(); i > firstNonMatch; --i) + lastAppliedStack[i - 1]->pop(state); + lastAppliedStack.resize(firstNonMatch); + + lastAppliedStack.reserve(attributeVec.size()); + for (size_t i = firstNonMatch; i < attributeVec.size(); ++i) + { + const DebugGroup* group = static_cast(attributeVec[i].first); + group->push(state); + lastAppliedStack.push_back(group); + } + if (!(lastAppliedStack.back() == this)) + { + push(state); + lastAppliedStack.push_back(this); + } } - if (glDebugMessageCallback && glDebugMessageControl) + int DebugGroup::compare(const StateAttribute & sa) const { - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true); - glDebugMessageCallback(debugCallback, nullptr); + COMPARE_StateAttribute_Types(DebugGroup, sa); + + COMPARE_StateAttribute_Parameter(mSource); + COMPARE_StateAttribute_Parameter(mId); + COMPARE_StateAttribute_Parameter(mMessage); - Log(Debug::Info) << "OpenGL debug callback attached."; + return 0; } - else -#endif - Log(Debug::Error) << "Unable to attach OpenGL debug callback."; -} -Debug::EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false) -{ -} + void DebugGroup::releaseGLObjects(osg::State * state) const + { + if (state) + sLastAppliedStack.erase(state->getContextID()); + else + sLastAppliedStack.clear(); + } -void Debug::EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext) -{ - OpenThreads::ScopedLock lock(mMutex); + bool DebugGroup::isValid() const + { + return mSource || mId || mMessage.length(); + } - unsigned int contextID = graphicsContext->getState()->getContextID(); - enableGLDebugExtension(contextID); -} + void DebugGroup::push(osg::State & state) const + { + if (isValid()) + PushDebugGroup::sInstance->glPushDebugGroup(mSource, mId, mMessage.size(), mMessage.c_str()); + } + + void DebugGroup::pop(osg::State & state) const + { + if (isValid()) + PushDebugGroup::sInstance->glPopDebugGroup(); + } + + std::map> DebugGroup::sLastAppliedStack{}; -bool Debug::shouldDebugOpenGL() -{ - const char* env = std::getenv("OPENMW_DEBUG_OPENGL"); - if (!env) - return false; - std::string str(env); - if (str.length() == 0) - return true; - - return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos; } diff --git a/components/debug/gldebug.hpp b/components/debug/gldebug.hpp index 8be747afe8..b6f32c9cff 100644 --- a/components/debug/gldebug.hpp +++ b/components/debug/gldebug.hpp @@ -17,5 +17,65 @@ namespace Debug }; bool shouldDebugOpenGL(); + + + /* + Debug groups allow rendering to be annotated, making debugging via APITrace/CodeXL/NSight etc. much clearer. + + Because I've not thought of a quick and clean way of doing it without incurring a small performance cost, + there are no uses of this class checked in. For now, add annotations locally when you need them. + + To use this class, add it to a StateSet just like any other StateAttribute. Prefer the string-only constructor. + You'll need OPENMW_DEBUG_OPENGL set to true, or shouldDebugOpenGL() redefined to just return true as otherwise + the extension function pointers won't get set up. That can maybe be cleaned up in the future. + + Beware that consecutive identical debug groups (i.e. pointers match) won't always get applied due to OSG thinking + it's already applied them. Either avoid nesting the same object, add dummy groups so they're not consecutive, or + ensure the leaf group isn't identical to its parent. + */ + class DebugGroup : public osg::StateAttribute + { + public: + DebugGroup() + : mSource(0) + , mId(0) + , mMessage("") + {} + + DebugGroup(GLenum source, GLuint id, const std::string& message) + : mSource(source) + , mId(id) + , mMessage(message) + {} + + DebugGroup(const std::string& message, GLuint id = 0); + + DebugGroup(const DebugGroup& debugGroup, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_StateAttribute(Debug, DebugGroup, osg::StateAttribute::Type(101)); + + void apply(osg::State& state) const override; + + int compare(const StateAttribute& sa) const override; + + void releaseGLObjects(osg::State* state = nullptr) const override; + + virtual bool isValid() const; + + protected: + virtual ~DebugGroup() = default; + + virtual void push(osg::State& state) const; + + virtual void pop(osg::State& state) const; + + GLenum mSource; + GLuint mId; + std::string mMessage; + + static std::map> sLastAppliedStack; + + friend EnableGLDebugOperation; + }; } #endif From 657da50d996d304cca4ee27884c46a290bb05223 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 20 Dec 2020 01:36:34 +0000 Subject: [PATCH 0125/2859] Ensure GL_BLEND is disabled when drawing shadow maps --- components/sceneutil/shadowsbin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 312d4226b5..8b5c788dd0 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -54,7 +54,7 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); - mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); for (size_t i = 0; i < sCastingPrograms.size(); ++i) { From 7e045cff7560a16015ab60b984d1713fb549c42e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 20 Dec 2020 01:51:45 +0000 Subject: [PATCH 0126/2859] #include --- components/debug/gldebug.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/debug/gldebug.cpp b/components/debug/gldebug.cpp index 22d7bbe682..ee7dd608e4 100644 --- a/components/debug/gldebug.cpp +++ b/components/debug/gldebug.cpp @@ -32,6 +32,7 @@ either expressed or implied, of the FreeBSD Project. #include "gldebug.hpp" #include +#include #include From 33eb09f5345387f47eb6193385103a6c24405d8d Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 20 Dec 2020 18:28:21 +0100 Subject: [PATCH 0127/2859] Remove unused headers --- apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp | 5 ----- apps/openmw/mwphysics/trace.cpp | 3 --- 2 files changed, 8 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 3ec09d00d2..18c196e444 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -2,11 +2,6 @@ #include -#include - -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - #include "collisiontype.hpp" #include "projectile.hpp" diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 4d2fccdb7b..58082f4db2 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -5,9 +5,6 @@ #include #include -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - #include "collisiontype.hpp" #include "actor.hpp" #include "closestnotmeconvexresultcallback.hpp" From 5bd921fa3aa494af76972abf6299bcf44ec74b7f Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 19 Dec 2020 20:25:46 +0100 Subject: [PATCH 0128/2859] Restore pre-async handling of absolute actors positionning One of the issue since the introduction of async physics is the quirky handling of scripted moves. Previous attempt to account for them was based on detecting changes in actor position while the physics thread is running. To this end, semantics of Actor::updatePosition() (which is responsible for set the absolute position of an actor in the world) was toned down to merely store the desired position, with the physics system actually responsible for moving the actor. For the cases were complete override of the physics simulation was needed, I introduced Actor::resetPosition(), which actually have same semantics as original updatePosition(). This in turn introduced a loads of new bugs when the weakened semantics broke key assumptions inside the engine (spawning, summoning, teleport, etc). Instead of tracking them down, count on the newly introduced support for object relative movements in the engine (World::moveObjectBy) to register relative movements and restore original handling of absolute positionning. Changes are relatively small: - move resetPosition() content into updatePosition() - call updatePosition() everywhere it was called before - remove all added calls to the now non-existing resetPosition() tldr; ditch last month worth of bug introduction and eradication and redo it properly --- apps/openmw/mwphysics/actor.cpp | 51 +++++++++---------------- apps/openmw/mwphysics/actor.hpp | 8 ++-- apps/openmw/mwphysics/mtphysics.cpp | 4 +- apps/openmw/mwphysics/physicssystem.cpp | 16 +------- apps/openmw/mwphysics/physicssystem.hpp | 1 - apps/openmw/mwworld/worldimp.cpp | 1 - 6 files changed, 23 insertions(+), 58 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 10fd463fb0..015424db58 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -74,7 +74,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateRotation(); updateScale(); - resetPosition(); + updatePosition(); addCollisionMask(getCollisionMask()); updateCollisionObjectPosition(); } @@ -119,30 +119,34 @@ int Actor::getCollisionMask() const return collisionMask; } -void Actor::updatePositionUnsafe() +void Actor::updatePosition() { - if (!mWorldPositionChanged && mWorldPosition != mPtr.getRefData().getPosition().asVec3()) - mWorldPositionChanged = true; - mWorldPosition = mPtr.getRefData().getPosition().asVec3(); + std::scoped_lock lock(mPositionMutex); + updateWorldPosition(); + mPreviousPosition = mWorldPosition; + mPosition = mWorldPosition; + mSimulationPosition = mWorldPosition; + mStandingOnPtr = nullptr; + mSkipSimulation = true; } -void Actor::updatePosition() +void Actor::updateWorldPosition() { - std::scoped_lock lock(mPositionMutex); - updatePositionUnsafe(); + if (mWorldPosition != mPtr.getRefData().getPosition().asVec3()) + mWorldPositionChanged = true; + mWorldPosition = mPtr.getRefData().getPosition().asVec3(); } osg::Vec3f Actor::getWorldPosition() const { - std::scoped_lock lock(mPositionMutex); return mWorldPosition; } void Actor::setSimulationPosition(const osg::Vec3f& position) { - if (!mResetSimulation) + if (!mSkipSimulation) mSimulationPosition = position; - mResetSimulation = false; + mSkipSimulation = false; } osg::Vec3f Actor::getSimulationPosition() const @@ -150,8 +154,9 @@ osg::Vec3f Actor::getSimulationPosition() const return mSimulationPosition; } -void Actor::updateCollisionObjectPositionUnsafe() +void Actor::updateCollisionObjectPosition() { + std::scoped_lock lock(mPositionMutex); mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); osg::Vec3f newPosition = scaledTranslation + mPosition; @@ -161,12 +166,6 @@ void Actor::updateCollisionObjectPositionUnsafe() mWorldPositionChanged = false; } -void Actor::updateCollisionObjectPosition() -{ - std::scoped_lock lock(mPositionMutex); - updateCollisionObjectPositionUnsafe(); -} - osg::Vec3f Actor::getCollisionObjectPosition() const { std::scoped_lock lock(mPositionMutex); @@ -189,18 +188,6 @@ void Actor::adjustPosition(const osg::Vec3f& offset) mPositionOffset += offset; } -void Actor::resetPosition() -{ - std::scoped_lock lock(mPositionMutex); - updatePositionUnsafe(); - mPreviousPosition = mWorldPosition; - mPosition = mWorldPosition; - mSimulationPosition = mWorldPosition; - mStandingOnPtr = nullptr; - mWorldPositionChanged = false; - mResetSimulation = true; -} - osg::Vec3f Actor::getPosition() const { return mPosition; @@ -214,8 +201,6 @@ osg::Vec3f Actor::getPreviousPosition() const void Actor::updateRotation () { std::scoped_lock lock(mPositionMutex); - if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude()) - return; mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); } @@ -246,7 +231,6 @@ osg::Vec3f Actor::getHalfExtents() const osg::Vec3f Actor::getOriginalHalfExtents() const { - std::scoped_lock lock(mPositionMutex); return mHalfExtents; } @@ -283,7 +267,6 @@ void Actor::setWalkingOnWater(bool walkingOnWater) void Actor::setCanWaterWalk(bool waterWalk) { - std::scoped_lock lock(mPositionMutex); if (waterWalk != mCanWaterWalk) { mCanWaterWalk = waterWalk; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index b26b52d144..18419c2c80 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -60,7 +60,7 @@ namespace MWPhysics * Set mWorldPosition to the position in the Ptr's RefData. This is used by the physics simulation to account for * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation. */ - void updatePosition(); + void updateWorldPosition(); osg::Vec3f getWorldPosition() const; /** @@ -93,7 +93,7 @@ namespace MWPhysics * Returns true if the new position is different. */ bool setPosition(const osg::Vec3f& position); - void resetPosition(); + void updatePosition(); void adjustPosition(const osg::Vec3f& offset); osg::Vec3f getPosition() const; @@ -155,8 +155,6 @@ namespace MWPhysics void updateCollisionMask(); void addCollisionMask(int collisionMask); int getCollisionMask() const; - void updateCollisionObjectPositionUnsafe(); - void updatePositionUnsafe(); bool mCanWaterWalk; std::atomic mWalkingOnWater; @@ -180,7 +178,7 @@ namespace MWPhysics osg::Vec3f mPreviousPosition; osg::Vec3f mPositionOffset; bool mWorldPositionChanged; - bool mResetSimulation; + bool mSkipSimulation; btTransform mLocalTransform; mutable std::mutex mPositionMutex; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index bedcaba6ac..3774ee7347 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -281,8 +281,8 @@ namespace MWPhysics mActorsFrameData.clear(); for (const auto& [_, actor] : actors) { - actor->resetPosition(); - actor->setSimulationPosition(actor->getWorldPosition()); // resetPosition skip next simulation, now we need to "consume" it + actor->updatePosition(); + actor->setSimulationPosition(actor->getWorldPosition()); // updatePosition skip next simulation, now we need to "consume" it actor->updateCollisionObjectPosition(); mMovedActors.emplace_back(actor->getPtr()); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9c239200bf..0b3cdbff50 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -437,7 +437,6 @@ namespace MWPhysics ActorMap::iterator found = mActors.find(ptr); if (found == mActors.end()) return ptr.getRefData().getPosition().asVec3(); - resetPosition(ptr); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); } @@ -642,17 +641,6 @@ namespace MWPhysics if (foundActor != mActors.end()) { foundActor->second->updatePosition(); - mTaskScheduler->updateSingleAabb(foundActor->second); - return; - } - } - - void PhysicsSystem::resetPosition(const MWWorld::ConstPtr &ptr) - { - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) - { - foundActor->second->resetPosition(); mTaskScheduler->updateSingleAabb(foundActor->second, true); return; } @@ -694,8 +682,6 @@ namespace MWPhysics if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); - if (cmode) - resetPosition(found->first); cmode = !cmode; found->second->enableCollisionMode(cmode); // NB: Collision body isn't disabled for vanilla TCL compatibility @@ -942,7 +928,7 @@ namespace MWPhysics void ActorFrameData::updatePosition() { - mActorRaw->updatePosition(); + mActorRaw->updateWorldPosition(); mPosition = mActorRaw->getPosition(); if (mMoveToWaterSurface) { diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 39bf9dd8be..9ebd587f51 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -143,7 +143,6 @@ namespace MWPhysics void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr); - void resetPosition(const MWWorld::ConstPtr &ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6a7e396447..b3a9dbb022 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1356,7 +1356,6 @@ namespace MWWorld } moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); - mPhysics->resetPosition(ptr); } void World::fixPosition() From 09759c66208d61d2bad0e85d7a0438fe3767a93a Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 19 Dec 2020 20:17:42 +0300 Subject: [PATCH 0129/2859] Clean up shader lighting --- files/shaders/CMakeLists.txt | 1 + files/shaders/lighting.glsl | 83 +++++++---------------------- files/shaders/objects_fragment.glsl | 29 +++++----- files/shaders/objects_vertex.glsl | 14 +++-- files/shaders/terrain_fragment.glsl | 29 +++++----- files/shaders/terrain_vertex.glsl | 14 +++-- files/shaders/vertexcolors.glsl | 38 +++++++++++++ 7 files changed, 107 insertions(+), 101 deletions(-) create mode 100644 files/shaders/vertexcolors.glsl diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 8012c2bc10..47670e7a03 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -22,6 +22,7 @@ set(SHADER_FILES shadows_fragment.glsl shadowcasting_vertex.glsl shadowcasting_fragment.glsl + vertexcolors.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 1ed162eeac..930f4de264 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -1,85 +1,38 @@ #define MAX_LIGHTS 8 -uniform int colorMode; - -const int ColorMode_None = 0; -const int ColorMode_Emission = 1; -const int ColorMode_AmbientAndDiffuse = 2; -const int ColorMode_Ambient = 3; -const int ColorMode_Diffuse = 4; -const int ColorMode_Specular = 5; - -void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal, vec4 diffuse, vec3 ambient) +void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) { - vec3 lightDir; - float lightDistance; - - lightDir = gl_LightSource[lightIndex].position.xyz - (viewPos.xyz * gl_LightSource[lightIndex].position.w); - lightDistance = length(lightDir); + vec3 lightDir = gl_LightSource[lightIndex].position.xyz - viewPos * gl_LightSource[lightIndex].position.w; + float lightDistance = length(lightDir); lightDir = normalize(lightDir); float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); - ambientOut = ambient * gl_LightSource[lightIndex].ambient.xyz * illumination; - diffuseOut = diffuse.xyz * gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal.xyz, lightDir), 0.0) * illumination; + ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination; + diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal, lightDir), 0.0) * illumination; } #if PER_PIXEL_LIGHTING -vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, float shadowing) +void doLighting(vec3 viewPos, vec3 viewNormal, float shadowing, out vec3 diffuseLight, out vec3 ambientLight) #else -vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, out vec3 shadowDiffuse) +void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight, out vec3 shadowDiffuse) #endif { - vec4 diffuse; - vec3 ambient; - if (colorMode == ColorMode_AmbientAndDiffuse) - { - diffuse = vertexColor; - ambient = vertexColor.xyz; - } - else if (colorMode == ColorMode_Diffuse) - { - diffuse = vertexColor; - ambient = gl_FrontMaterial.ambient.xyz; - } - else if (colorMode == ColorMode_Ambient) - { - diffuse = gl_FrontMaterial.diffuse; - ambient = vertexColor.xyz; - } - else - { - diffuse = gl_FrontMaterial.diffuse; - ambient = gl_FrontMaterial.ambient.xyz; - } - vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); - - vec3 diffuseLight, ambientLight; - perLight(ambientLight, diffuseLight, 0, viewPos, viewNormal, diffuse, ambient); + vec3 ambientOut, diffuseOut; + // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. + perLight(ambientOut, diffuseOut, 0, viewPos, viewNormal); #if PER_PIXEL_LIGHTING - lightResult.xyz += diffuseLight * shadowing - diffuseLight; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. + diffuseLight = diffuseOut * shadowing - diffuseOut; #else - shadowDiffuse = diffuseLight; - lightResult.xyz -= shadowDiffuse; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. + shadowDiffuse = diffuseOut; + diffuseLight = -diffuseOut; #endif + ambientLight = gl_LightModel.ambient.xyz; for (int i=0; i Date: Tue, 22 Dec 2020 05:56:59 +0300 Subject: [PATCH 0130/2859] Fix AI sequence looping code (bug #5706) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aisequence.cpp | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d896ca55cb..fc6eb2d282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE + Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5731: Editor: skirts are invisible on characters Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 57d32898cc..575a03434f 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -403,7 +403,7 @@ const AiPackage& MWMechanics::AiSequence::getActivePackage() void AiSequence::fill(const ESM::AIPackageList &list) { // If there is more than one package in the list, enable repeating - if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1)) + if (list.mList.size() >= 2) mRepeat = true; for (const auto& esmPackage : list.mList) @@ -459,8 +459,15 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) int count = 0; for (auto& container : sequence.mPackages) { - if (isActualAiPackage(static_cast(container.mType))) - count++; + switch (container.mType) + { + case ESM::AiSequence::Ai_Wander: + case ESM::AiSequence::Ai_Travel: + case ESM::AiSequence::Ai_Escort: + case ESM::AiSequence::Ai_Follow: + case ESM::AiSequence::Ai_Activate: + ++count; + } } if (count > 1) From 218597b13d5c45df2d5928dec91dc78a8c4b958b Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 22 Dec 2020 06:13:35 +0300 Subject: [PATCH 0131/2859] Fix paralyze for levitating actors --- apps/openmw/mwworld/worldimp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b3a9dbb022..2ac6cca796 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2302,8 +2302,12 @@ namespace MWWorld if (stats.isDead()) return false; + const bool isPlayer = ptr == getPlayerConstPtr(); + if (!(isPlayer && mGodMode) && stats.isParalyzed()) + return false; + if (ptr.getClass().canFly(ptr)) - return !stats.isParalyzed(); + return true; if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && isLevitationEnabled()) From 22476281da55ea908ae60d1072c6e6e0fd50d9f4 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 22 Dec 2020 06:19:18 +0300 Subject: [PATCH 0132/2859] Fix paralyze for swimming actors --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 10 ++++++++-- apps/openmw/mwphysics/movementsolver.cpp | 5 +++-- apps/openmw/mwphysics/physicssystem.cpp | 4 +++- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 619bbbdf5e..958fcfb0ea 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -519,7 +519,7 @@ namespace MWBase /// Returns true if levitation spell effect is allowed. virtual bool isLevitationEnabled() const = 0; - virtual bool getGodModeState() = 0; + virtual bool getGodModeState() const = 0; virtual bool toggleGodMode() = 0; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9b3c8576ea..1784869651 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2439,8 +2439,14 @@ void CharacterController::update(float duration, bool animationOnly) } } - if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) - moved.z() = 1.0; + if (mFloatToSurface && cls.isActor() && cls.canSwim(mPtr)) + { + if (cls.getCreatureStats(mPtr).isDead() + || (!godmode && cls.getCreatureStats(mPtr).isParalyzed())) + { + moved.z() = 1.0; + } + } // Update movement if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor()) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index e55ec9166c..1feaf3c94e 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -130,8 +130,9 @@ namespace MWPhysics velocity = velocity + inertia; } - // dead actors underwater will float to the surface, if the CharacterController tells us to do so - if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel) + // Dead and paralyzed actors underwater will float to the surface, + // if the CharacterController tells us to do so + if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; if (actor.mWantJump) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 0b3cdbff50..cc4d6bba8c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -922,7 +922,9 @@ namespace MWPhysics mFlying = world->isFlying(ptr); mSwimming = world->isSwimming(ptr); mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; - mIsDead = ptr.getClass().getCreatureStats(ptr).isDead(); + auto& stats = ptr.getClass().getCreatureStats(ptr); + const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); + mFloatToSurface = stats.isDead() || (!godmode && stats.isParalyzed()); mWasOnGround = actor->getOnGround(); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 9ebd587f51..2de8d153b9 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -88,7 +88,7 @@ namespace MWPhysics bool mWasOnGround; bool mWantJump; bool mDidJump; - bool mIsDead; + bool mFloatToSurface; bool mNeedLand; bool mMoveToWaterSurface; float mWaterlevel; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2ac6cca796..8b95ee1221 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2917,7 +2917,7 @@ namespace MWWorld mRendering->rebuildPtr(getPlayerPtr()); } - bool World::getGodModeState() + bool World::getGodModeState() const { return mGodMode; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 75717b16b2..9f05014133 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -619,7 +619,7 @@ namespace MWWorld /// Returns true if levitation spell effect is allowed. bool isLevitationEnabled() const override; - bool getGodModeState() override; + bool getGodModeState() const override; bool toggleGodMode() override; From eaa65ec60a39029b816b893d8e367e67fdfbce11 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 22 Dec 2020 06:20:26 +0300 Subject: [PATCH 0133/2859] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d896ca55cb..a132a85bea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5731: Editor: skirts are invisible on characters + Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging From f6ab2cd4a4df76cc6e58c0362a5f053175ced372 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 22 Dec 2020 14:34:09 +0400 Subject: [PATCH 0134/2859] Update info about font-related settings --- docs/source/reference/modding/font.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/font.rst b/docs/source/reference/modding/font.rst index fa89b33ea6..3778ac5ed2 100644 --- a/docs/source/reference/modding/font.rst +++ b/docs/source/reference/modding/font.rst @@ -26,7 +26,7 @@ Now Fonts folder should include ``openmw_font.xml`` file and three ``.ttf`` file If desired, you can now delete the ``Data Files/Fonts`` directory. -It is also possible to adjust the font size and resolution:: +It is also possible to adjust the font size and resolution via ``settings.cfg`` file:: [GUI] font size = 16 From 32cc14981eb4fafcb1396edf62d83aa6da0aaf02 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 22 Dec 2020 20:40:52 +0300 Subject: [PATCH 0135/2859] Don't move characters if their animation can't move them --- 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 1784869651..1eb58f4d30 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2423,7 +2423,7 @@ void CharacterController::update(float duration, bool animationOnly) moved.y() *= scale; // Ensure we're moving in generally the right direction... - if(speed > 0.f) + if (speed > 0.f && moved != osg::Vec3f()) { float l = moved.length(); if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 || From a36ed5f129ff4cb008c29413d1683da4fac59e9a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 23 Dec 2020 00:23:49 +0000 Subject: [PATCH 0136/2859] Optimise out redundant call We already had the results --- components/sceneutil/shadowsbin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 8b5c788dd0..a2424fe363 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -27,9 +27,9 @@ namespace osg::StateSet::ModeList::const_iterator mf = l.find(mode); if (mf == l.end()) return; - int flags = mf->second; + unsigned int flags = mf->second; bool newValue = flags & osg::StateAttribute::ON; - accumulateState(currentValue, newValue, isOverride, ss->getMode(mode)); + accumulateState(currentValue, newValue, isOverride, flags); } inline bool materialNeedShadows(osg::Material* m) From fd77ef6b0113a30997cc594d1ef3440ad4daad91 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 23 Dec 2020 03:33:34 +0300 Subject: [PATCH 0137/2859] Only ignore plain text after the last EOL tag (#5627) --- CHANGELOG.md | 1 + apps/openmw/mwgui/formatting.cpp | 23 ++++++++++++++--------- apps/openmw/mwgui/formatting.hpp | 2 ++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b75a179b1d..e557c7bcb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Bug #5604: Only one valid NIF root node is loaded from a single file Bug #5611: Usable items with "0 Uses" should be used only once Bug #5622: Can't properly interact with the console when in pause menu + Bug #5627: Bookart not shown if it isn't followed by
statement Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them Bug #5639: Tooltips cover Messageboxes Bug #5644: Summon effects running on the player during game initialization cause crashes diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 75bf8a3426..156dc5b0d6 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -30,14 +30,18 @@ namespace MWGui // vanilla game does not show any text after the last EOL tag. const std::string lowerText = Misc::StringUtils::lowerCase(mText); - int brIndex = lowerText.rfind("
"); - int pIndex = lowerText.rfind("

"); - if (brIndex == pIndex) - mText = ""; - else if (brIndex > pIndex) - mText = mText.substr(0, brIndex+4); - else - mText = mText.substr(0, pIndex+3); + size_t brIndex = lowerText.rfind("
"); + size_t pIndex = lowerText.rfind("

"); + mPlainTextEnd = 0; + if (brIndex != pIndex) + { + if (brIndex != std::string::npos && pIndex != std::string::npos) + mPlainTextEnd = std::max(brIndex, pIndex); + else if (brIndex != std::string::npos) + mPlainTextEnd = brIndex; + else + mPlainTextEnd = pIndex; + } registerTag("br", Event_BrTag); registerTag("p", Event_PTag); @@ -103,7 +107,8 @@ namespace MWGui { if (!mIgnoreLineEndings || ch != '\n') { - mBuffer.push_back(ch); + if (mIndex < mPlainTextEnd) + mBuffer.push_back(ch); mIgnoreLineEndings = false; mIgnoreNewlineTags = false; } diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index d563514148..12a3d46caa 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -73,6 +73,8 @@ namespace MWGui bool mClosingTag; std::map mTagTypes; std::string mBuffer; + + size_t mPlainTextEnd; }; class Paginator From 11b4af49ce2166057c68994bf8258a253e504137 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 23 Dec 2020 01:24:15 +0000 Subject: [PATCH 0138/2859] Allow shadowsbin to optimise clockwise-wound meshes when face culling is off --- components/sceneutil/mwshadowtechnique.cpp | 6 +++++ components/sceneutil/shadowsbin.cpp | 31 +++++++++++++++++----- components/sceneutil/shadowsbin.hpp | 2 +- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 39e47b0ea3..cfc94ba863 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -870,7 +870,10 @@ void SceneUtil::MWShadowTechnique::enableFrontFaceCulling() _useFrontFaceCulling = true; if (_shadowCastingStateSet) + { _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + } } void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() @@ -878,7 +881,10 @@ void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() _useFrontFaceCulling = false; if (_shadowCastingStateSet) + { + _shadowCastingStateSet->removeAttribute(osg::StateAttribute::CULLFACE); _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + } } void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index a2424fe363..26cbd58c31 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -63,7 +63,7 @@ ShadowsBin::ShadowsBin() } } -StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache) +StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache, bool cullFaceOverridden) { std::vector return_path; State state; @@ -102,10 +102,13 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un accumulateState(state.mAlphaFunc, static_cast(rap.first.get()), state.mAlphaFuncOverride, rap.second); } - // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it. - found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); - if (found != attributes.end()) - state.mImportantState = true; + if (!cullFaceOverridden) + { + // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it unless GL_CULL_FACE is off or we flip face culling. + found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); + if (found != attributes.end()) + state.mImportantState = true; + } if ((*itr) != sg && !state.interesting()) uninterestingCache.insert(*itr); @@ -188,7 +191,21 @@ void ShadowsBin::sortImplementation() return; } StateGraph* noTestRoot = root->find_or_insert(mNoTestStateSet.get()); - // root is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state + // noTestRoot is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state + + bool cullFaceOverridden = false; + while (root = root->_parent) + { + if (!root->getStateSet()) + continue; + unsigned int cullFaceFlags = root->getStateSet()->getMode(GL_CULL_FACE); + if (cullFaceFlags & osg::StateAttribute::OVERRIDE && !(cullFaceFlags & osg::StateAttribute::ON)) + { + cullFaceOverridden = true; + break; + } + } + noTestRoot->_leaves.reserve(_stateGraphList.size()); StateGraphList newList; std::unordered_set uninterestingCache; @@ -197,7 +214,7 @@ void ShadowsBin::sortImplementation() // Render leaves which shouldn't use the diffuse map for shadow alpha but do cast shadows become children of root, so graph is now empty. Don't add to newList. // Graphs containing just render leaves which don't cast shadows are discarded. Don't add to newList. // Graphs containing other leaves need to be in newList. - StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache); + StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache, cullFaceOverridden); if (graphToAdd) newList.push_back(graphToAdd); } diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index d8bdd86071..1c63caf4bb 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -63,7 +63,7 @@ namespace SceneUtil } }; - osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting); + osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting, bool cullFaceOverridden); static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms); }; From 8c3a786e5408cc8d2e857afc02ff1590c0ca8ef9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 24 Dec 2020 00:32:15 +0000 Subject: [PATCH 0139/2859] Unconditionally disable alpha testing when shaders are used --- components/shader/shadervisitor.cpp | 45 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index aeec391c67..7db9933169 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -374,39 +374,40 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); + + // back up removed state in case recreateShaders gets rid of the shader later + osg::ref_ptr removedState; + if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) + removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); + if (!removedState) + removedState = new osg::StateSet(); + if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); - // back up removed state in case recreateShaders gets rid of the shader later - osg::ref_ptr removedState; - if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) - removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); - if (!removedState) - removedState = new osg::StateSet(); - - if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) - removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); - // This disables the deprecated fixed-function alpha test - writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); - const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); if (alphaFunc) removedState->setAttribute(alphaFunc->first, alphaFunc->second); // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } - if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) - { - // user data is normally shallow copied so shared with the original stateset - osg::ref_ptr writableUserData; - if (mAllowedToModifyStateSets) - writableUserData = writableStateSet->getOrCreateUserDataContainer(); - else - writableUserData = getWritableUserDataContainer(*writableStateSet); + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) + removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); + // This disables the deprecated fixed-function alpha test + writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); - updateRemovedState(*writableUserData, removedState); - } + if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getOrCreateUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + updateRemovedState(*writableUserData, removedState); } osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); From 51d63a4152509b2fb0924cbb2185952f71d8eed1 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 24 Dec 2020 03:39:59 +0100 Subject: [PATCH 0140/2859] Fix minor bug: mSmoothedSpeed is not updating in first person mode, that leads to an incorrect value after switching to first person mode and back. Additional refactoring: move `setSideMovementAngle` from camera.cpp to character.cpp as this logic shouldn't belong to Camera. --- apps/openmw/mwmechanics/character.cpp | 9 +++++++-- apps/openmw/mwrender/camera.cpp | 3 --- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1784869651..8d27d78ef6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1965,7 +1965,7 @@ void CharacterController::update(float duration, bool animationOnly) movementSettings.mSpeedFactor *= 2.f; static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); - if (smoothMovement && !isFirstPersonPlayer) + if (smoothMovement) { static const float playerTurningCoef = 1.0 / std::max(0.01f, Settings::Manager::getFloat("smooth movement player turning delay", "Game")); float angle = mPtr.getRefData().getPosition().rot[2]; @@ -1975,7 +1975,9 @@ void CharacterController::update(float duration, bool animationOnly) float deltaLen = delta.length(); float maxDelta; - if (std::abs(speedDelta) < deltaLen / 2) + if (isFirstPersonPlayer) + maxDelta = 1; + else if (std::abs(speedDelta) < deltaLen / 2) // Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point). maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f); else if (isPlayer && speedDelta < -deltaLen / 2) @@ -2013,7 +2015,10 @@ void CharacterController::update(float duration, bool animationOnly) bool canMove = cls.getMaxSpeed(mPtr) > 0; static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game"); if (!turnToMovementDirection || isFirstPersonPlayer) + { movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2; + stats.setSideMovementAngle(0); + } else if (canMove) { float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y()); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index b964eacffb..12e6ef3ce9 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -356,9 +356,6 @@ namespace MWRender else mViewModeToggleQueued = false; - if (mTrackingPtr.getClass().isActor()) - mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0); - mFirstPersonView = !mFirstPersonView; updateStandingPreviewMode(); instantTransition(); From 8b3088a9e5e33acf3e6ae123e91e9d91269b4dae Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 24 Dec 2020 03:00:09 +0100 Subject: [PATCH 0141/2859] Fix #5743 --- apps/openmw/mwmechanics/character.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1784869651..483492034d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2265,18 +2265,19 @@ void CharacterController::update(float duration, bool animationOnly) sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); } - if (turnToMovementDirection) + if (turnToMovementDirection && !isFirstPersonPlayer && + (movestate == CharState_SwimRunForward || movestate == CharState_SwimWalkForward || + movestate == CharState_SwimRunBack || movestate == CharState_SwimWalkBack)) { - float targetSwimmingPitch; - if (inwater && vec.y() != 0 && !isFirstPersonPlayer && !movementSettings.mIsStrafing) - targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0]; - else - targetSwimmingPitch = 0; - float maxSwimPitchDelta = 3.0f * duration; float swimmingPitch = mAnimation->getBodyPitchRadians(); + float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0]; + float maxSwimPitchDelta = 3.0f * duration; swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta); mAnimation->setBodyPitchRadians(swimmingPitch); } + else + mAnimation->setBodyPitchRadians(0); + static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game"); if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection) { From bdcbb412a5b8bda4e3fbf59e84b19459c60b5333 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 25 Dec 2020 13:03:09 +0100 Subject: [PATCH 0142/2859] Don't restore focus to hidden/unrelated buttons --- apps/openmw/mwgui/keyboardnavigation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index 6dd66029b8..b65cd98081 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -67,7 +67,7 @@ void KeyboardNavigation::saveFocus(int mode) { mKeyFocus[mode] = focus; } - else + else if(shouldAcceptKeyFocus(mCurrentFocus)) { mKeyFocus[mode] = mCurrentFocus; } From 1f1755ae48537b2b3b5f2bde712bba3163421fbc Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 25 Dec 2020 13:05:41 +0100 Subject: [PATCH 0143/2859] Add version checks --- apps/openmw/mwgui/keyboardnavigation.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index b65cd98081..b718b712c0 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -93,6 +93,7 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget) mCurrentFocus = nullptr; } +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) void styleFocusedButton(MyGUI::Widget* w) { if (w) @@ -103,6 +104,7 @@ void styleFocusedButton(MyGUI::Widget* w) } } } +#endif bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) { @@ -126,7 +128,9 @@ void KeyboardNavigation::onFrame() if (focus == mCurrentFocus) { +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) styleFocusedButton(mCurrentFocus); +#endif return; } @@ -137,19 +141,21 @@ void KeyboardNavigation::onFrame() focus = mCurrentFocus; } - // style highlighted button (won't be needed for MyGUI 3.2.3) if (focus != mCurrentFocus) { +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) if (mCurrentFocus) { if (MyGUI::Button* b = mCurrentFocus->castType(false)) b->_setWidgetState("normal"); } - +#endif mCurrentFocus = focus; } +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) styleFocusedButton(mCurrentFocus); +#endif } void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus) From d2a28d915a31bec236abaf6207b8199bb8d3ca88 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 25 Dec 2020 15:58:11 +0100 Subject: [PATCH 0144/2859] Don't consider a path completed unless we're close --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 4bffd28baa..8880820dd0 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -158,7 +158,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& zTurn(actor, getZAngleToPoint(position, dest)); smoothTurn(actor, getXAngleToPoint(position, dest), 0); world->removeActorPath(actor); - return true; + return isDestReached; } world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); From 7cac7fa87052e9e14a2e2ec3b5fb489a4820cdbb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 25 Dec 2020 23:57:01 +0100 Subject: [PATCH 0145/2859] Make followers keep a distance dependant on the fattest party member --- apps/openmw/mwbase/mechanicsmanager.hpp | 1 + apps/openmw/mwmechanics/actors.cpp | 97 +++++++++++-------- apps/openmw/mwmechanics/actors.hpp | 1 + apps/openmw/mwmechanics/aifollow.cpp | 27 +++--- .../mwmechanics/mechanicsmanagerimp.cpp | 5 + .../mwmechanics/mechanicsmanagerimp.hpp | 1 + 6 files changed, 78 insertions(+), 54 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index b6ce4d0619..967504552d 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -199,6 +199,7 @@ namespace MWBase virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; + virtual std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0; ///Returns a list of actors who are fighting the given actor within the fAlarmDistance /** ie AiCombat is active and the target is the actor **/ diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 047741baca..88c402b14c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -142,6 +142,29 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); } +template +void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWWorld::Ptr& actor, const MWWorld::Ptr& player, T&& func) +{ + for(auto& iter : actors) + { + const MWWorld::Ptr &iteratedActor = iter.first; + if (iteratedActor == player || iteratedActor == actor) + continue; + + const MWMechanics::CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); + if (stats.isDead()) + continue; + + // An actor counts as following if AiFollow is the current AiPackage, + // or there are only Combat and Wander packages before the AiFollow package + for (const auto& package : stats.getAiSequence()) + { + if(!func(iter, package)) + break; + } + } +} + } namespace MWMechanics @@ -2512,26 +2535,14 @@ namespace MWMechanics std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) { std::list list; - for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) - { - const MWWorld::Ptr &iteratedActor = iter->first; - if (iteratedActor == getPlayer() || iteratedActor == actor) - continue; - - const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); - if (stats.isDead()) - continue; - - // An actor counts as following if AiFollow is the current AiPackage, - // or there are only Combat and Wander packages before the AiFollow package - for (const auto& package : stats.getAiSequence()) - { - if (package->followTargetThroughDoors() && package->getTarget() == actor) - list.push_back(iteratedActor); - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) - break; - } - } + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) + { + if (package->followTargetThroughDoors() && package->getTarget() == actor) + list.push_back(iter.first); + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); return list; } @@ -2575,32 +2586,38 @@ namespace MWMechanics std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) { std::list list; - for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) { - const MWWorld::Ptr &iteratedActor = iter->first; - if (iteratedActor == getPlayer() || iteratedActor == actor) - continue; - - const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); - if (stats.isDead()) - continue; - - // An actor counts as following if AiFollow is the current AiPackage, - // or there are only Combat and Wander packages before the AiFollow package - for (const auto& package : stats.getAiSequence()) + if (package->followTargetThroughDoors() && package->getTarget() == actor) { - if (package->followTargetThroughDoors() && package->getTarget() == actor) - { - list.push_back(static_cast(package.get())->getFollowIndex()); - break; - } - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) - break; + list.push_back(static_cast(package.get())->getFollowIndex()); + return false; } - } + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); return list; } + std::map Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor) + { + std::map map; + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) + { + if (package->followTargetThroughDoors() && package->getTarget() == actor) + { + int index = static_cast(package.get())->getFollowIndex(); + map[index] = iter.first; + return false; + } + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); + return map; + } + std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { std::list list; std::vector neighbors; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 4535400016..59814bcc22 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -181,6 +181,7 @@ namespace MWMechanics /// Get the list of AiFollow::mFollowIndex for all actors following this target std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); + std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor); ///Returns the list of actors which are fighting the given actor /**ie AiCombat is active and the target is the actor **/ diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index b3c308d75f..1e4fb206e3 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -113,24 +113,23 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte if (!mActive) return false; - // The distances below are approximations based on observations of the original engine. - // If only one actor is following the target, it uses 186. - // If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target. - - short followDistance = 186; - std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target); - if (followers.size() >= 2) + // In the original engine the first follower stays closer to the player than any subsequent followers. + // Followers beyond the first usually attempt to stand inside each other. + osg::Vec3f::value_type floatingDistance = 0; + auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target); + if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex) { - followDistance = 313; - short i = 0; - followers.sort(); - for (int followIndex : followers) + osg::Vec3f::value_type maxSize = 0; + for(auto& follower : followers) { - if (followIndex == mFollowIndex) - followDistance += 130 * i; - ++i; + auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y(); + if(halfExtent > floatingDistance) + floatingDistance = halfExtent; } } + floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y(); + floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2; + short followDistance = static_cast(floatingDistance); if (!mAlwaysFollow) //Update if you only follow for a bit { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b1db2562b2..fb67218532 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1654,6 +1654,11 @@ namespace MWMechanics return mActors.getActorsFollowingIndices(actor); } + std::map MechanicsManager::getActorsFollowingByIndex(const MWWorld::Ptr& actor) + { + return mActors.getActorsFollowingByIndex(actor); + } + std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 28f62b7774..3f2c3f5e98 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -150,6 +150,7 @@ namespace MWMechanics std::list getActorsSidingWith(const MWWorld::Ptr& actor) override; std::list getActorsFollowing(const MWWorld::Ptr& actor) override; std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) override; + std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) override; std::list getActorsFighting(const MWWorld::Ptr& actor) override; std::list getEnemiesNearby(const MWWorld::Ptr& actor) override; From f981dd99167eaed067ead892b409c1093ca46c8c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 25 Dec 2020 23:58:23 +0100 Subject: [PATCH 0146/2859] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b75a179b1d..c9f3d18161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work Bug #5416: Junk non-node records before the root node are not handled gracefully Bug #5422: The player loses all spells when resurrected + Bug #5423: Guar follows actors too closely Bug #5424: Creatures do not headtrack player Bug #5425: Poison effect only appears for one frame Bug #5427: GetDistance unknown ID error is misleading From 8f4b856b449f4ad15e002dbb2afa88cd10ace588 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 26 Dec 2020 22:45:53 +0000 Subject: [PATCH 0147/2859] Initial A2C implementation --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/resource/scenemanager.cpp | 6 ++ components/resource/scenemanager.hpp | 3 + components/sceneutil/mwshadowtechnique.cpp | 2 +- components/shader/shadervisitor.cpp | 13 ++++ components/shader/shadervisitor.hpp | 4 ++ .../reference/modding/settings/shaders.rst | 11 ++++ files/settings-default.cfg | 5 ++ files/shaders/alpha.glsl | 60 +++++++++++++------ 9 files changed, 86 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 736e4f2ee1..a60077a169 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -219,6 +219,7 @@ namespace MWRender resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); + resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 2630cd4536..5f7116722e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -290,6 +290,11 @@ namespace Resource mApplyLightingToEnvMaps = apply; } + void SceneManager::setConvertAlphaTestToAlphaToCoverage(bool convert) + { + mConvertAlphaTestToAlphaToCoverage = convert; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -769,6 +774,7 @@ namespace Resource shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); + shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); return shaderVisitor; } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index fd75070a18..aae7d143f2 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -75,6 +75,8 @@ namespace Resource void setApplyLightingToEnvMaps(bool apply); + void setConvertAlphaTestToAlphaToCoverage(bool convert); + void setShaderPath(const std::string& path); /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded @@ -159,6 +161,7 @@ namespace Resource bool mAutoUseSpecularMaps; std::string mSpecularMapPattern; bool mApplyLightingToEnvMaps; + bool mConvertAlphaTestToAlphaToCoverage; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index cfc94ba863..c781318faf 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -897,7 +897,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh auto& program = _castingPrograms[alphaFunc - GL_NEVER]; program = new osg::Program(); program->addShader(castingVertexShader); - program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)} }, osg::Shader::FRAGMENT)); + program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"} }, osg::Shader::FRAGMENT)); } } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 7db9933169..4762549a7a 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -382,6 +383,7 @@ namespace Shader if (!removedState) removedState = new osg::StateSet(); + defineMap["alphaToCoverage"] = "0"; if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); @@ -391,6 +393,12 @@ namespace Shader removedState->setAttribute(alphaFunc->first, alphaFunc->second); // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + if (mConvertAlphaTestToAlphaToCoverage) + { + writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); + defineMap["alphaToCoverage"] = "1"; + } } if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) @@ -581,4 +589,9 @@ namespace Shader mApplyLightingToEnvMaps = apply; } + void ShaderVisitor::setConvertAlphaTestToAlphaToCoverage(bool convert) + { + mConvertAlphaTestToAlphaToCoverage = convert; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 2ba18107b5..606e06df97 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -40,6 +40,8 @@ namespace Shader void setApplyLightingToEnvMaps(bool apply); + void setConvertAlphaTestToAlphaToCoverage(bool convert); + void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -63,6 +65,8 @@ namespace Shader bool mApplyLightingToEnvMaps; + bool mConvertAlphaTestToAlphaToCoverage; + ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index e23cc3d548..49ada897e4 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -146,3 +146,14 @@ radial fog By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. + +convert alpha test to alpha-to-coverage +--------------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. +This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. +When MSAA is off, this setting will have no visible effect, but might have a performance cost. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c8805faa39..6629cb503e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -442,6 +442,11 @@ apply lighting to environment maps = false # This makes fogging independent from the viewing angle. Shaders will be used to render all objects. radial fog = false +# Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. +# This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. +# When MSAA is off, this setting will have no visible effect, but might have a performance cost. +convert alpha test to alpha-to-coverage = false + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 0c69df0ab2..b1c7ed149e 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -14,25 +14,49 @@ uniform float alphaRef; void alphaTest() { - #if @alphaFunc == FUNC_NEVER - discard; - #elif @alphaFunc == FUNC_LESS - if (gl_FragData[0].a > alphaRef) - discard; - #elif @alphaFunc == FUNC_EQUAL - if (gl_FragData[0].a != alphaRef) - discard; - #elif @alphaFunc == FUNC_LEQUAL - if (gl_FragData[0].a >= alphaRef) - discard; - #elif @alphaFunc == FUNC_GREATER - if (gl_FragData[0].a < alphaRef) - discard; - #elif @alphaFunc == FUNC_NOTEQUAL - if (gl_FragData[0].a == alphaRef) + #if @alphaToCoverage + float coverageAlpha = (gl_FragData[0].a - alphaRef) / max(fwidth(gl_FragData[0].a), 0.0001) + 0.5; + + // Some functions don't make sense with A2C or are a pain to think about and no meshes use them anyway + // Use regular alpha testing in such cases until someone complains. + #if @alphaFunc == FUNC_NEVER discard; - #elif @alphaFunc == FUNC_GEQUAL - if (gl_FragData[0].a <= alphaRef) + #elif @alphaFunc == FUNC_LESS + gl_FragData[0].a = 1.0 - coverageAlpha; + #elif @alphaFunc == FUNC_EQUAL + if (gl_FragData[0].a != alphaRef) + discard; + #elif @alphaFunc == FUNC_LEQUAL + gl_FragData[0].a = 1.0 - coverageAlpha; + #elif @alphaFunc == FUNC_GREATER + gl_FragData[0].a = coverageAlpha; + #elif @alphaFunc == FUNC_NOTEQUAL + if (gl_FragData[0].a == alphaRef) + discard; + #elif @alphaFunc == FUNC_GEQUAL + gl_FragData[0].a = coverageAlpha; + #endif + #else + #if @alphaFunc == FUNC_NEVER discard; + #elif @alphaFunc == FUNC_LESS + if (gl_FragData[0].a > alphaRef) + discard; + #elif @alphaFunc == FUNC_EQUAL + if (gl_FragData[0].a != alphaRef) + discard; + #elif @alphaFunc == FUNC_LEQUAL + if (gl_FragData[0].a >= alphaRef) + discard; + #elif @alphaFunc == FUNC_GREATER + if (gl_FragData[0].a < alphaRef) + discard; + #elif @alphaFunc == FUNC_NOTEQUAL + if (gl_FragData[0].a == alphaRef) + discard; + #elif @alphaFunc == FUNC_GEQUAL + if (gl_FragData[0].a <= alphaRef) + discard; + #endif #endif } \ No newline at end of file From 54d465e3dcb5e5f98cbe12b8f85e6abc394968ea Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 27 Dec 2020 03:07:04 +0000 Subject: [PATCH 0148/2859] Do all alpha testing early and only once --- files/shaders/objects_fragment.glsl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index c79ad5e196..995c2adaf3 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -109,11 +109,14 @@ void main() #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); - alphaTest(); #else gl_FragData[0] = vec4(1.0); #endif + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; + alphaTest(); + #if @detailMap gl_FragData[0].xyz *= texture2D(detailMap, detailMapUV).xyz * 2.0; #endif @@ -152,10 +155,6 @@ void main() #endif - vec4 diffuseColor = getDiffuseColor(); - gl_FragData[0].a *= diffuseColor.a; - alphaTest(); - float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; #if !PER_PIXEL_LIGHTING From ebb564ad226aaba712e69c2207d7baff69473f26 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 27 Dec 2020 17:31:55 +0100 Subject: [PATCH 0149/2859] call moveObject() after applying waterwalking This unbreak abot's boat mods: they're continually teleporting the boats (who is an actor with waterwalking effect). As such, the physics simulation was never run and the boat never went to sea level. --- apps/openmw/mwphysics/physicssystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index cc4d6bba8c..b50366ade9 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -935,7 +935,7 @@ namespace MWPhysics if (mMoveToWaterSurface) { mPosition.z() = mWaterlevel; - mActorRaw->setPosition(mPosition); + MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z()); } mOldHeight = mPosition.z(); mRefpos = mActorRaw->getPtr().getRefData().getPosition(); From 18ef32ca82bd9c3cfcdc9f420131e4697ee054c9 Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 27 Dec 2020 22:16:11 +0000 Subject: [PATCH 0150/2859] values for this higher than sGroundOffset cause jittering on some surface; use safe-seeming value slightly less than sGroundOffset --- CHANGELOG.md | 10 + CHANGELOG_PR.md | 2 +- CI/before_install.android.sh | 2 +- CI/before_script.msvc.sh | 2 +- CMakeLists.txt | 5 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwphysics/actor.cpp | 26 +- apps/openmw/mwphysics/actor.hpp | 3 + .../closestnotmeconvexresultcallback.cpp | 61 +++- .../closestnotmeconvexresultcallback.hpp | 3 +- apps/openmw/mwphysics/constants.hpp | 12 +- apps/openmw/mwphysics/contacttestwrapper.cpp | 21 ++ apps/openmw/mwphysics/contacttestwrapper.h | 14 + apps/openmw/mwphysics/movementsolver.cpp | 331 ++++++++++++++---- apps/openmw/mwphysics/movementsolver.hpp | 37 +- apps/openmw/mwphysics/mtphysics.cpp | 16 +- apps/openmw/mwphysics/raycasting.hpp | 2 +- apps/openmw/mwphysics/stepper.cpp | 239 +++++++------ apps/openmw/mwphysics/stepper.hpp | 3 +- apps/openmw/mwphysics/trace.cpp | 4 +- appveyor.yml | 2 +- cmake/OSIdentity.cmake | 67 ++++ components/CMakeLists.txt | 7 +- 23 files changed, 658 insertions(+), 213 deletions(-) create mode 100644 apps/openmw/mwphysics/contacttestwrapper.cpp create mode 100644 apps/openmw/mwphysics/contacttestwrapper.h create mode 100644 cmake/OSIdentity.cmake diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b139253f8..661a058d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,14 @@ Bug #832: OpenMW-CS: Handle deleted references Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path + Bug #1901: Actors colliding behaviour is different from vanilla Bug #1952: Incorrect particle lighting Bug #2069: Fireflies in Fireflies invade Morrowind look wrong Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #2473: Unable to overstock merchants Bug #2798: Mutable ESM records Bug #2976 [reopened]: Issues combining settings from the command line and both config files + Bug #3137: Walking into a wall prevents jumping Bug #3372: Projectiles and magic bolts go through moving targets Bug #3676: NiParticleColorModifier isn't applied properly Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects @@ -18,6 +20,11 @@ Bug #4021: Attributes and skills are not stored as floats Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors + Bug #4247: Cannot walk up stairs in Ebonheart docks + Bug #4447: Actor collision capsule shape allows looking through some walls + Bug #4465: Collision shape overlapping causes twitching + Bug #4476: Abot Gondoliers: player hangs in air during scenic travel + Bug #4568: Too many actors in one spot can push other actors out of bounds Bug #4623: Corprus implementation is incorrect Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level Bug #4764: Data race in osg ParticleSystem @@ -71,12 +78,15 @@ Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx + Bug #5681: Player character can clip or pass through bridges instead of colliding against them + Bug #5683: Player character can get stuck with MR's balmora's wooden gate Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5731: Editor: skirts are invisible on characters Bug #5758: Paralyzed actors behavior is inconsistent with vanilla + Bug #5762: Movement solver is insufficiently robust Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 8a7948bb70..69ad0cc1b9 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -41,4 +41,4 @@ Editor Bug Fixes: Miscellaneous: - Prevent save-game bloating by using an appropriate fog texture format (#5108) -- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363) \ No newline at end of file +- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363) diff --git a/CI/before_install.android.sh b/CI/before_install.android.sh index 791377dd96..0243a96092 100755 --- a/CI/before_install.android.sh +++ b/CI/before_install.android.sh @@ -1,4 +1,4 @@ #!/bin/sh -ex -curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201018.zip -o ~/openmw-android-deps.zip +curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201129.zip -o ~/openmw-android-deps.zip unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 0ef67f47ef..0c26801cc4 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -73,7 +73,7 @@ CONFIGURATIONS=() TEST_FRAMEWORK="" GOOGLE_INSTALL_ROOT="" INSTALL_PREFIX="." -BULLET_DOUBLE="" +BULLET_DOUBLE=true BULLET_DBL="" BULLET_DBL_DISPLAY="Single precision" diff --git a/CMakeLists.txt b/CMakeLists.txt index be7bc79e3f..4a615e8432 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.1.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# Detect OS +include(cmake/OSIdentity.cmake) + # for link time optimization, remove if cmake version is >= 3.9 if(POLICY CMP0069) cmake_policy(SET CMP0069 NEW) @@ -21,7 +24,7 @@ option(BUILD_NIFTEST "Build nif file tester" ON) option(BUILD_DOCS "Build documentation." OFF ) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) -option(BULLET_USE_DOUBLES "Use double precision for Bullet" OFF) +option(BULLET_USE_DOUBLES "Use double precision for Bullet" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6b6134dfb9..a4c3b91361 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -73,7 +73,7 @@ add_openmw_dir (mwworld add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile - closestnotmeconvexresultcallback raycasting mtphysics + closestnotmeconvexresultcallback raycasting mtphysics contacttestwrapper ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 015424db58..3b7b0d0616 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -1,6 +1,5 @@ #include "actor.hpp" -#include #include #include @@ -14,6 +13,8 @@ #include "collisiontype.hpp" #include "mtphysics.hpp" +#include + namespace MWPhysics { @@ -52,17 +53,8 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; } - // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) - if (std::abs(mHalfExtents.x()-mHalfExtents.y())= mHalfExtents.x()) - { - mShape.reset(new btCapsuleShapeZ(mHalfExtents.x(), 2*mHalfExtents.z() - 2*mHalfExtents.x())); - mRotationallyInvariant = true; - } - else - { - mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); - mRotationallyInvariant = false; - } + mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); + mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2; mConvexShape = static_cast(mShape.get()); @@ -72,8 +64,11 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setUserPointer(this); - updateRotation(); updateScale(); + + if(!mRotationallyInvariant) + updateRotation(); + updatePosition(); addCollisionMask(getCollisionMask()); updateCollisionObjectPosition(); @@ -154,6 +149,11 @@ osg::Vec3f Actor::getSimulationPosition() const return mSimulationPosition; } +osg::Vec3f Actor::getScaledMeshTranslation() const +{ + return mRotation * osg::componentMultiply(mMeshTranslation, mScale); +} + void Actor::updateCollisionObjectPosition() { std::scoped_lock lock(mPositionMutex); diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 18419c2c80..9d129f2ba6 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -82,6 +82,9 @@ namespace MWPhysics */ osg::Vec3f getOriginalHalfExtents() const; + /// Returns the mesh translation, scaled and rotated as necessary + osg::Vec3f getScaledMeshTranslation() const; + /** * Returns the position of the collision body * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 18c196e444..8dd5a624fc 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -1,15 +1,39 @@ +#include + #include "closestnotmeconvexresultcallback.hpp" +#include "collisiontype.hpp" +#include "contacttestwrapper.h" #include +#include #include "collisiontype.hpp" #include "projectile.hpp" namespace MWPhysics { - ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot) + class ActorOverlapTester : public btCollisionWorld::ContactResultCallback + { + public: + bool overlapping = false; + + btScalar addSingleResult(btManifoldPoint& cp, + const btCollisionObjectWrapper* colObj0Wrap, + int partId0, + int index0, + const btCollisionObjectWrapper* colObj1Wrap, + int partId1, + int index1) + { + if(cp.getDistance() <= 0.0f) + overlapping = true; + return btScalar(1); + } + }; + + ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), - mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot) + mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world) { } @@ -18,6 +42,39 @@ namespace MWPhysics if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); + // override data for actor-actor collisions + // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter of the distance between them + // For some reason this doesn't work as well as it should when using capsules, but it still helps a lot. + if(convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) + { + ActorOverlapTester isOverlapping; + // FIXME: This is absolutely terrible and bullet should feel terrible for not making contactPairTest const-correct. + ContactTestWrapper::contactPairTest(const_cast(mWorld), const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), isOverlapping); + + if(isOverlapping.overlapping) + { + auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin()); + auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin()); + osg::Vec3f motion = Misc::Convert::toOsg(mMotion); + osg::Vec3f normal = (originA-originB); + normal.z() = 0; + normal.normalize(); + // only collide if horizontally moving towards the hit actor (note: the motion vector appears to be inverted) + // FIXME: This kinda screws with standing on actors that walk up slopes for some reason. Makes you fall through them. + // It happens in vanilla Morrowind too, but much less often. + // I tried hunting down why but couldn't figure it out. Possibly a stair stepping or ground ejection bug. + if(normal * motion > 0.0f) + { + convexResult.m_hitFraction = 0.0f; + convexResult.m_hitNormalLocal = Misc::Convert::toBullet(normal); + return ClosestConvexResultCallback::addSingleResult(convexResult, true); + } + else + { + return btScalar(1); + } + } + } if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) { auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp index 97aaa64a1c..538721ad80 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp @@ -10,7 +10,7 @@ namespace MWPhysics class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot); + ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override; @@ -18,6 +18,7 @@ namespace MWPhysics const btCollisionObject *mMe; const btVector3 mMotion; const btScalar mMinCollisionDot; + const btCollisionWorld * mWorld; }; } diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index 46367ab343..c6f2f3b530 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -5,12 +5,22 @@ namespace MWPhysics { static const float sStepSizeUp = 34.0f; static const float sStepSizeDown = 62.0f; - static const float sMinStep = 10.f; + + static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes + static const float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes + // whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance + static const bool sDoExtraStairHacks = true; + static const float sGroundOffset = 1.0f; static const float sMaxSlope = 49.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static const int sMaxIterations = 8; + // Allows for more precise movement solving without getting stuck or snagging too easily. + static const float sCollisionMargin = 0.1; + // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily + // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues + static const float sAllowedPenetration = 0.0; } #endif diff --git a/apps/openmw/mwphysics/contacttestwrapper.cpp b/apps/openmw/mwphysics/contacttestwrapper.cpp new file mode 100644 index 0000000000..c11a7e2926 --- /dev/null +++ b/apps/openmw/mwphysics/contacttestwrapper.cpp @@ -0,0 +1,21 @@ +#include + +#include "contacttestwrapper.h" + +namespace MWPhysics +{ + // Concurrent calls to contactPairTest (and by extension contactTest) are forbidden. + static std::mutex contactMutex; + void ContactTestWrapper::contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) + { + std::unique_lock lock(contactMutex); + collisionWorld->contactTest(colObj, resultCallback); + } + + void ContactTestWrapper::contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback) + { + std::unique_lock lock(contactMutex); + collisionWorld->contactPairTest(colObjA, colObjB, resultCallback); + } + +} diff --git a/apps/openmw/mwphysics/contacttestwrapper.h b/apps/openmw/mwphysics/contacttestwrapper.h new file mode 100644 index 0000000000..b3b6edc59a --- /dev/null +++ b/apps/openmw/mwphysics/contacttestwrapper.h @@ -0,0 +1,14 @@ +#ifndef OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H +#define OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H + +#include + +namespace MWPhysics +{ + struct ContactTestWrapper + { + static void contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback); + static void contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback); + }; +} +#endif diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 1feaf3c94e..69bf042369 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -17,10 +17,13 @@ #include "actor.hpp" #include "collisiontype.hpp" #include "constants.hpp" +#include "contacttestwrapper.h" #include "physicssystem.hpp" #include "stepper.hpp" #include "trace.h" +#include + namespace MWPhysics { static bool isActor(const btCollisionObject *obj) @@ -29,12 +32,50 @@ namespace MWPhysics return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; } - template - static bool isWalkableSlope(const Vec3 &normal) + class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback { - static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); - return (normal.z() > sMaxSlopeCos); - } + public: + ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me) + { + m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; + m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask; + mVelocity = Misc::Convert::toBullet(velocity); + } + virtual btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) + { + if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) + return 0.0; + // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, that would break detection when not moving) + if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) + return 0.0; + auto delta = contact.m_normalWorldOnB * -contact.m_distance1; + mContactSum += delta; + mMaxX = std::max(std::abs(delta.x()), mMaxX); + mMaxY = std::max(std::abs(delta.y()), mMaxY); + mMaxZ = std::max(std::abs(delta.z()), mMaxZ); + if (contact.m_distance1 < mDistance) + { + mDistance = contact.m_distance1; + mNormal = contact.m_normalWorldOnB; + mDelta = delta; + return mDistance; + } + else + { + return 0.0; + } + } + btScalar mMaxX = 0.0; + btScalar mMaxY = 0.0; + btScalar mMaxZ = 0.0; + btVector3 mContactSum{0.0, 0.0, 0.0}; + btVector3 mNormal{0.0, 0.0, 0.0}; // points towards "me" + btVector3 mDelta{0.0, 0.0, 0.0}; // points towards "me" + btScalar mDistance = 0.0; // negative or zero + protected: + btVector3 mVelocity; + const btCollisionObject * mMe; + }; osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) { @@ -99,13 +140,13 @@ namespace MWPhysics } const btCollisionObject *colobj = physicActor->getCollisionObject(); - osg::Vec3f halfExtents = physicActor->getHalfExtents(); - // NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos). - // That means the collision shape used for moving this actor is in a different spot than the collision shape - // other actors are using to collide against this actor. - // While this is strictly speaking wrong, it's needed for MW compatibility. - actor.mPosition.z() += halfExtents.z(); + // Adjust for collision mesh offset relative to actor's "location" + // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own) + // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation + // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() + osg::Vec3f halfExtents = physicActor->getHalfExtents(); + actor.mPosition.z() += halfExtents.z(); // vanilla-accurate static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); @@ -156,6 +197,13 @@ namespace MWPhysics * The initial velocity was set earlier (see above). */ float remainingTime = time; + bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying; + + int numTimesSlid = 0; + osg::Vec3f lastSlideNormal(0,0,1); + osg::Vec3f lastSlideNormalFallback(0,0,1); + bool forceGroundTest = false; + for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) { osg::Vec3f nextpos = newPosition + velocity * remainingTime; @@ -164,7 +212,7 @@ namespace MWPhysics if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel) { const osg::Vec3f down(0,0,-1); - velocity = slide(velocity, down); + velocity = reject(velocity, down); // NOTE: remainingTime is unchanged before the loop continues continue; // velocity updated, calculate nextpos again } @@ -193,92 +241,158 @@ namespace MWPhysics break; } - // We are touching something. - if (tracer.mFraction < 1E-9f) - { - // Try to separate by backing off slighly to unstuck the solver - osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f; - newPosition += backOff; - } + if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel) + seenGround = true; // We hit something. Check if we can step up. float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); osg::Vec3f oldPosition = newPosition; - bool result = false; + bool usedStepLogic = false; if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject)) { // Try to step up onto it. - // NOTE: stepMove does not allow stepping over, modifies newPosition if successful - result = stepper.step(newPosition, velocity*remainingTime, remainingTime); + // NOTE: this modifies newPosition and velocity on its own if successful + usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0); } - if (result) + if (usedStepLogic) { // don't let pure water creatures move out of water after stepMove const auto ptr = physicActor->getPtr(); if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel) newPosition = oldPosition; + else if(!actor.mFlying && actor.mPosition.z() >= swimlevel) + forceGroundTest = true; } else { - // Can't move this way, try to find another spot along the plane - osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal); + // Can't step up, so slide against what we ran into + remainingTime *= (1.0f-tracer.mFraction); + + auto planeNormal = tracer.mPlaneNormal; + + // If we touched the ground this frame, and whatever we ran into is a wall of some sort, + // pretend that its collision normal is pointing horizontally + // (fixes snagging on slightly downward-facing walls, and crawling up the bases of very steep walls because of the collision margin) + if (seenGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) + { + planeNormal.z() = 0; + planeNormal.normalize(); + } + + // Move up to what we ran into (with a bit of a collision margin) + if ((newPosition-tracer.mEndPos).length2() > sCollisionMargin*sCollisionMargin) + { + auto direction = velocity; + direction.normalize(); + newPosition = tracer.mEndPos; + newPosition -= direction*sCollisionMargin; + } + + osg::Vec3f newVelocity = (velocity * planeNormal <= 0.0) ? reject(velocity, planeNormal) : velocity; + bool usedSeamLogic = false; + + // check for the current and previous collision planes forming an acute angle; slide along the seam if they do + if(numTimesSlid > 0) + { + auto dotA = lastSlideNormal * planeNormal; + auto dotB = lastSlideNormalFallback * planeNormal; + if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide + dotB = 1.0; + if(dotA <= 0.0 || dotB <= 0.0) + { + osg::Vec3f bestNormal = lastSlideNormal; + // use previous-to-previous collision plane if it's acute with current plane but actual previous plane isn't + if(dotB < dotA) + { + bestNormal = lastSlideNormalFallback; + lastSlideNormal = lastSlideNormalFallback; + } + + auto constraintVector = bestNormal ^ planeNormal; // cross product + if(constraintVector.length2() > 0) // only if it's not zero length + { + constraintVector.normalize(); + newVelocity = project(velocity, constraintVector); + + // version of surface rejection for acute crevices/seams + auto averageNormal = bestNormal + planeNormal; + averageNormal.normalize(); + tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); + newPosition = (newPosition + tracer.mEndPos)/2.0; + + usedSeamLogic = true; + } + } + } + // otherwise just keep the normal vector rejection + + // if this isn't the first iteration, or if the first iteration is also the last iteration, + // move away from the collision plane slightly, if possible + // this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings + // this is different from the normal collision margin, because the normal collision margin is along the movement path, + // but this is along the collision normal + if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f)) + { + tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); + newPosition = (newPosition + tracer.mEndPos)/2.0; + } - // Do not allow sliding upward if there is gravity. - // Stepping will have taken care of that. - if(!(newPosition.z() < swimlevel || actor.mFlying)) - newVelocity.z() = std::min(newVelocity.z(), 0.0f); + // Do not allow sliding up steep slopes if there is gravity. + if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal)) + newVelocity.z() = std::min(newVelocity.z(), velocity.z()); - if ((newVelocity-velocity).length2() < 0.01) + if (newVelocity * origVelocity <= 0.0f) break; - if ((newVelocity * origVelocity) <= 0.f) - break; // ^ dot product + numTimesSlid += 1; + lastSlideNormalFallback = lastSlideNormal; + lastSlideNormal = planeNormal; velocity = newVelocity; } } bool isOnGround = false; bool isOnSlope = false; - if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel)) + if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; - osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown + 2*sGroundOffset) : osg::Vec3f(0,0,2*sGroundOffset)); + auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0); + osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); tracer.doTrace(colobj, from, to, collisionWorld); - if(tracer.mFraction < 1.0f && !isActor(tracer.mHitObject)) + if(tracer.mFraction < 1.0f) { - const btCollisionObject* standingOn = tracer.mHitObject; - PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); - if (ptrHolder) - actor.mStandingOn = ptrHolder->getPtr(); - - if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) - physicActor->setWalkingOnWater(true); - if (!actor.mFlying) - newPosition.z() = tracer.mEndPos.z() + sGroundOffset; + if (!isActor(tracer.mHitObject)) + { + isOnGround = true; + isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); - isOnGround = true; + const btCollisionObject* standingOn = tracer.mHitObject; + PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); + if (ptrHolder) + actor.mStandingOn = ptrHolder->getPtr(); - isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); - } - else - { - // standing on actors is not allowed (see above). - // in addition to that, apply a sliding effect away from the center of the actor, - // so that we do not stay suspended in air indefinitely. - if (tracer.mFraction < 1.0f && isActor(tracer.mHitObject)) - { - if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f) + if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) + physicActor->setWalkingOnWater(true); + if (!actor.mFlying && !isOnSlope) { - btVector3 aabbMin, aabbMax; - tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax); - btVector3 center = (aabbMin + aabbMax) / 2.f; - inertia = osg::Vec3f(actor.mPosition.x() - center.x(), actor.mPosition.y() - center.y(), 0); - inertia.normalize(); - inertia *= 100; + if (tracer.mFraction*dropDistance > sGroundOffset) + newPosition.z() = tracer.mEndPos.z() + sGroundOffset; + else + { + newPosition.z() = tracer.mEndPos.z(); + tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld); + newPosition = (newPosition+tracer.mEndPos)/2.0; + } } } + else + { + // Vanilla allows actors to float on top of other actors. Do not push them off. + if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) && tracer.mEndPos.z()+sGroundOffset <= newPosition.z()) + newPosition.z() = tracer.mEndPos.z() + sGroundOffset; - isOnGround = false; + isOnGround = false; + } } } @@ -298,7 +412,98 @@ namespace MWPhysics physicActor->setOnGround(isOnGround); physicActor->setOnSlope(isOnSlope); - newPosition.z() -= halfExtents.z(); // remove what was added at the beginning actor.mPosition = newPosition; + // remove what was added earlier in compensating for doTrace not taking interior transformation into account + actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate + } + + btVector3 addMarginToDelta(btVector3 delta) + { + if(delta.length2() == 0.0) + return delta; + return delta + delta.normalized() * sCollisionMargin; + } + + void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) + { + const auto& ptr = actor.mActorRaw->getPtr(); + if (!ptr.getClass().isMobile(ptr)) + return; + + auto* physicActor = actor.mActorRaw; + if(!physicActor->getCollisionMode()) // noclipping/tcl + return; + + auto* collisionObject = physicActor->getCollisionObject(); + auto tempPosition = actor.mPosition; + + // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) + // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() + const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); + + // use a 3d approximation of the movement vector to better judge player intent + const ESM::Position& refpos = ptr.getRefData().getPosition(); + auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + // try to pop outside of the world before doing anything else if we're inside of it + if (!physicActor->getOnGround() || physicActor->getOnSlope()) + velocity += physicActor->getInertialForce(); + + // because of the internal collision box offset hack, and the fact that we're moving the collision box manually, + // we need to replicate part of the collision box's transform process from scratch + osg::Vec3f refPosition = tempPosition + verticalHalfExtent; + osg::Vec3f goodPosition = refPosition; + const btTransform oldTransform = collisionObject->getWorldTransform(); + btTransform newTransform = oldTransform; + + auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback + { + goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset)); + newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); + collisionObject->setWorldTransform(newTransform); + + ContactCollectionCallback callback{collisionObject, velocity}; + ContactTestWrapper::contactTest(const_cast(collisionWorld), collisionObject, callback); + return callback; + }; + + // check whether we're inside the world with our collision box with manually-derived offset + auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); + if(contactCallback.mDistance < -sAllowedPenetration) + { + // we are; try moving it out of the world + auto positionDelta = contactCallback.mContactSum; + // limit rejection delta to the largest known individual rejections + if(std::abs(positionDelta.x()) > contactCallback.mMaxX) + positionDelta *= contactCallback.mMaxX / std::abs(positionDelta.x()); + if(std::abs(positionDelta.y()) > contactCallback.mMaxY) + positionDelta *= contactCallback.mMaxY / std::abs(positionDelta.y()); + if(std::abs(positionDelta.z()) > contactCallback.mMaxZ) + positionDelta *= contactCallback.mMaxZ / std::abs(positionDelta.z()); + + auto contactCallback2 = gatherContacts(positionDelta); + // successfully moved further out from contact (does not have to be in open space, just less inside of things) + if(contactCallback2.mDistance > contactCallback.mDistance) + tempPosition = goodPosition - verticalHalfExtent; + // try again but only upwards (fixes some bad coc floors) + else + { + // upwards-only offset + auto contactCallback3 = gatherContacts({0.0, 0.0, std::abs(positionDelta.z())}); + // success + if(contactCallback3.mDistance > contactCallback.mDistance) + tempPosition = goodPosition - verticalHalfExtent; + else + // try again but fixed distance up + { + auto contactCallback4 = gatherContacts({0.0, 0.0, 10.0}); + // success + if(contactCallback4.mDistance > contactCallback.mDistance) + tempPosition = goodPosition - verticalHalfExtent; + } + } + } + + collisionObject->setWorldTransform(oldTransform); + actor.mPosition = tempPosition; } } diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 75fba1cf0e..76141ec0e9 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -5,6 +5,9 @@ #include +#include "constants.hpp" +#include "../mwworld/ptr.hpp" + class btCollisionWorld; namespace MWWorld @@ -14,29 +17,35 @@ namespace MWWorld namespace MWPhysics { + /// Vector projection + static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) + { + return v * (u * v); + } + + /// Vector rejection + static inline osg::Vec3f reject(const osg::Vec3f& direction, const osg::Vec3f &planeNormal) + { + return direction - project(direction, planeNormal); + } + + template + static bool isWalkableSlope(const Vec3 &normal) + { + static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); + return (normal.z() > sMaxSlopeCos); + } + class Actor; struct ActorFrameData; struct WorldFrameData; class MovementSolver { - private: - ///Project a vector u on another vector v - static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) - { - return v * (u * v); - // ^ dot product - } - - ///Helper for computing the character sliding - static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal) - { - return direction - project(direction, planeNormal); - } - public: static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight); static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData); + static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld); }; } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index f610ceaea3..0d1e5962a0 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -13,6 +13,7 @@ #include "../mwworld/player.hpp" #include "actor.hpp" +#include "contacttestwrapper.h" #include "movementsolver.hpp" #include "mtphysics.hpp" #include "object.hpp" @@ -167,7 +168,14 @@ namespace MWPhysics mPreStepBarrier = std::make_unique(mNumThreads, [&]() { + if (mDeferAabbUpdate) updateAabbs(); + for (auto& data : mActorsFrameData) + if (data.mActor.lock()) + { + std::unique_lock lock(mCollisionWorldMutex); + MovementSolver::unstuck(data, mCollisionWorld.get()); + } }); mPostStepBarrier = std::make_unique(mNumThreads, [&]() @@ -295,7 +303,7 @@ namespace MWPhysics void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { std::shared_lock lock(mCollisionWorldMutex); - mCollisionWorld->contactTest(colObj, resultCallback); + ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) @@ -438,8 +446,7 @@ namespace MWPhysics if (!mNewFrame) mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; }); - if (mDeferAabbUpdate) - mPreStepBarrier->wait(); + mPreStepBarrier->wait(); int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) @@ -505,7 +512,10 @@ namespace MWPhysics while (mRemainingSteps--) { for (auto& actorData : mActorsFrameData) + { + MovementSolver::unstuck(actorData, mCollisionWorld.get()); MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + } updateActorsPositions(); } diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 8ca6965d8e..b176e83304 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -16,7 +16,7 @@ namespace MWPhysics osg::Vec3f mHitNormal; MWWorld::Ptr mHitObject; }; - + class RayCastingInterface { public: diff --git a/apps/openmw/mwphysics/stepper.cpp b/apps/openmw/mwphysics/stepper.cpp index 0ab383dd1e..2a28381bee 100644 --- a/apps/openmw/mwphysics/stepper.cpp +++ b/apps/openmw/mwphysics/stepper.cpp @@ -7,6 +7,7 @@ #include "collisiontype.hpp" #include "constants.hpp" +#include "movementsolver.hpp" namespace MWPhysics { @@ -24,125 +25,155 @@ namespace MWPhysics Stepper::Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj) : mColWorld(colWorld) , mColObj(colObj) - , mHaveMoved(true) { } - bool Stepper::step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime) + bool Stepper::step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration) { - /* - * Slide up an incline or set of stairs. Should be called only after a - * collision detection otherwise unnecessary tracing will be performed. - * - * NOTE: with a small change this method can be used to step over an obstacle - * of height sStepSize. - * - * If successful return 'true' and update 'position' to the new possible - * location and adjust 'remainingTime'. - * - * If not successful return 'false'. May fail for these reasons: - * - can't move directly up from current position - * - having moved up by between epsilon() and sStepSize, can't move forward - * - having moved forward by between epsilon() and toMove, - * = moved down between 0 and just under sStepSize but slope was too steep, or - * = moved the full sStepSize down (FIXME: this could be a bug) - * - * Starting position. Obstacle or stairs with height upto sStepSize in front. - * - * +--+ +--+ |XX - * | | -------> toMove | | +--+XX - * | | | | |XXXXX - * | | +--+ | | +--+XXXXX - * | | |XX| | | |XXXXXXXX - * +--+ +--+ +--+ +-------- - * ============================================== - */ - - /* - * Try moving up sStepSize using stepper. - * FIXME: does not work in case there is no front obstacle but there is one above - * - * +--+ +--+ - * | | | | - * | | | | |XX - * | | | | +--+XX - * | | | | |XXXXX - * +--+ +--+ +--+ +--+XXXXX - * |XX| |XXXXXXXX - * +--+ +-------- - * ============================================== - */ - if (mHaveMoved) - { - mHaveMoved = false; + if(velocity.x() == 0.0 && velocity.y() == 0.0) + return false; + + // Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the ground. + // This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and just prevent stepping on insane geometry. - mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld); - if (mUpStepper.mFraction < std::numeric_limits::epsilon()) - return false; // didn't even move the smallest representable amount - // (TODO: shouldn't this be larger? Why bother with such a small amount?) + mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld); + + float upDistance = 0; + if(!mUpStepper.mHitObject) + upDistance = sStepSizeUp; + else if(mUpStepper.mFraction*sStepSizeUp > sCollisionMargin) + upDistance = mUpStepper.mFraction*sStepSizeUp - sCollisionMargin; + else + { + return false; } - /* - * Try moving from the elevated position using tracer. - * - * +--+ +--+ - * | | |YY| FIXME: collision with object YY - * | | +--+ - * | | - * <------------------->| | - * +--+ +--+ - * |XX| the moved amount is toMove*tracer.mFraction - * +--+ - * ============================================== - */ - osg::Vec3f tracerPos = mUpStepper.mEndPos; - mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld); - if (mTracer.mFraction < std::numeric_limits::epsilon()) - return false; // didn't even move the smallest representable amount - - /* - * Try moving back down sStepSizeDown using stepper. - * NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up". - * Below diagram is the case where we "stepped over" an obstacle in front. - * - * +--+ - * |YY| - * +--+ +--+ - * | | - * | | - * +--+ | | - * |XX| | | - * +--+ +--+ - * ============================================== - */ - mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld); - if (!canStepDown(mDownStepper)) + auto toMove = velocity * remainingTime; + + osg::Vec3f tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); + + osg::Vec3f tracerDest; + auto normalMove = toMove; + auto moveDistance = normalMove.normalize(); + // attempt 1: normal movement + // attempt 2: fixed distance movement, only happens on the first movement solver iteration/bounce each frame to avoid a glitch + // attempt 3: further, less tall fixed distance movement, same as above + // If you're making a full conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems with vanilla Morrowind assets. + int attempt = 0; + float downStepSize; + while(attempt < 3) { - // Try again with increased step length - if (mTracer.mFraction < 1.0f || toMove.length2() > sMinStep*sMinStep) - return false; + attempt++; - osg::Vec3f direction = toMove; - direction.normalize(); - mTracer.doTrace(mColObj, tracerPos, tracerPos + direction*sMinStep, mColWorld); - if (mTracer.mFraction < 0.001f) + if(attempt == 1) + tracerDest = tracerPos + toMove; + else if (!firstIteration || !sDoExtraStairHacks) // first attempt failed and not on first movement solver iteration, can't retry -- or we have extra hacks disabled + { return false; + } + else if(attempt == 2) + { + moveDistance = sMinStep; + tracerDest = tracerPos + normalMove*sMinStep; + } + else if(attempt == 3) + { + if(upDistance > sStepSizeUp) + { + upDistance = sStepSizeUp; + tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); + } + moveDistance = sMinStep2; + tracerDest = tracerPos + normalMove*sMinStep2; + } + + mTracer.doTrace(mColObj, tracerPos, tracerDest, mColWorld); + if(mTracer.mHitObject) + { + // map against what we hit, minus the safety margin + moveDistance *= mTracer.mFraction; + if(moveDistance <= sCollisionMargin) // didn't move enough to accomplish anything + { + return false; + } + + moveDistance -= sCollisionMargin; + tracerDest = tracerPos + normalMove*moveDistance; + + // safely eject from what we hit by the safety margin + auto tempDest = tracerDest + mTracer.mPlaneNormal*sCollisionMargin*2; + + ActorTracer tempTracer; + tempTracer.doTrace(mColObj, tracerDest, tempDest, mColWorld); + + if(tempTracer.mFraction > 0.5f) // distance to any object is greater than sCollisionMargin (we checked sCollisionMargin*2 distance) + { + auto effectiveFraction = tempTracer.mFraction*2.0f - 1.0f; + tracerDest += mTracer.mPlaneNormal*sCollisionMargin*effectiveFraction; + } + } + + if(attempt > 2) // do not allow stepping down below original height for attempt 3 + downStepSize = upDistance; + else + downStepSize = moveDistance + upDistance + sStepSizeDown; + mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld); + + // can't step down onto air, non-walkable-slopes, or actors + // NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were intended to be valid at the bottoms of stairs + // (like the bottoms of the staircases in aldruhn's guild of mages) + // The old code worked around this by trying to do mTracer again with a fixed distance of sMinStep (10.0) but it caused all sorts of other problems. + // Switched back to cylinders to avoid that and similer problems. + if(canStepDown(mDownStepper)) + { + break; + } + else + { + // do not try attempt 3 if we just tried attempt 2 and the horizontal distance was rather large + // (forces actor to get snug against the defective ledge for attempt 3 to be tried) + if(attempt == 2 && moveDistance > upDistance-(mDownStepper.mFraction*downStepSize)) + { + return false; + } + // do next attempt if first iteration of movement solver and not out of attempts + if(firstIteration && attempt < 3) + { + continue; + } - mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld); - if (!canStepDown(mDownStepper)) return false; + } } - if (mDownStepper.mFraction < 1.0f) + // note: can't downstep onto actors so no need to pick safety margin + float downDistance = 0; + if(mDownStepper.mFraction*downStepSize > sCollisionMargin) + downDistance = mDownStepper.mFraction*downStepSize - sCollisionMargin; + + if(downDistance-sCollisionMargin-sGroundOffset > upDistance && !onGround) + return false; + + auto newpos = tracerDest + osg::Vec3f(0.0f, 0.0f, -downDistance); + + if((position-newpos).length2() < sCollisionMargin*sCollisionMargin) + return false; + + if(mTracer.mHitObject) { - // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. - // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing - // NOTE: caller's variables 'position' & 'remainingTime' are modified here - position = mDownStepper.mEndPos; - remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance - mHaveMoved = true; - return true; + auto planeNormal = mTracer.mPlaneNormal; + if (onGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0) + { + planeNormal.z() = 0; + planeNormal.normalize(); + } + velocity = reject(velocity, planeNormal); } - return false; + velocity = reject(velocity, mDownStepper.mPlaneNormal); + + position = newpos; + + remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance + return true; } } diff --git a/apps/openmw/mwphysics/stepper.hpp b/apps/openmw/mwphysics/stepper.hpp index 27e6294b05..512493c524 100644 --- a/apps/openmw/mwphysics/stepper.hpp +++ b/apps/openmw/mwphysics/stepper.hpp @@ -20,12 +20,11 @@ namespace MWPhysics const btCollisionObject *mColObj; ActorTracer mTracer, mUpStepper, mDownStepper; - bool mHaveMoved; public: Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj); - bool step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime); + bool step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration); }; } diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 58082f4db2..f50b6100a6 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -24,7 +24,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star to.setOrigin(btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0)); + ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; @@ -62,7 +62,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const btTransform to(trans.getBasis(), btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0)); + ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; diff --git a/appveyor.yml b/appveyor.yml index e2c13ed948..ed6f727be5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -50,7 +50,7 @@ install: before_build: - cmd: git submodule update --init --recursive - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install -D build_script: - cmd: if %PLATFORM%==Win32 set build=MSVC%msvc%_32 diff --git a/cmake/OSIdentity.cmake b/cmake/OSIdentity.cmake new file mode 100644 index 0000000000..40b7b20895 --- /dev/null +++ b/cmake/OSIdentity.cmake @@ -0,0 +1,67 @@ +if (UNIX) + + if (APPLE) + + set(CMAKE_OS_NAME "OSX" CACHE STRING "Operating system name" FORCE) + + else (APPLE) + + ## Check for Debian GNU/Linux ________________ + + find_file(DEBIAN_FOUND debian_version debconf.conf + PATHS /etc + ) + if (DEBIAN_FOUND) + set(CMAKE_OS_NAME "Debian" CACHE STRING "Operating system name" FORCE) + endif (DEBIAN_FOUND) + + ## Check for Fedora _________________________ + + find_file(FEDORA_FOUND fedora-release + PATHS /etc + ) + if (FEDORA_FOUND) + set(CMAKE_OS_NAME "Fedora" CACHE STRING "Operating system name" FORCE) + endif (FEDORA_FOUND) + + ## Check for RedHat _________________________ + + find_file(REDHAT_FOUND redhat-release inittab.RH + PATHS /etc + ) + if (REDHAT_FOUND) + set(CMAKE_OS_NAME "RedHat" CACHE STRING "Operating system name" FORCE) + endif (REDHAT_FOUND) + + ## Extra check for Ubuntu ____________________ + + if (DEBIAN_FOUND) + + ## At its core Ubuntu is a Debian system, with + ## a slightly altered configuration; hence from + ## a first superficial inspection a system will + ## be considered as Debian, which signifies an + ## extra check is required. + + find_file(UBUNTU_EXTRA legal issue + PATHS /etc + ) + + if (UBUNTU_EXTRA) + ## Scan contents of file + file(STRINGS ${UBUNTU_EXTRA} UBUNTU_FOUND + REGEX Ubuntu + ) + ## Check result of string search + if (UBUNTU_FOUND) + set(CMAKE_OS_NAME "Ubuntu" CACHE STRING "Operating system name" FORCE) + set(DEBIAN_FOUND FALSE) + endif (UBUNTU_FOUND) + + endif (UBUNTU_EXTRA) + + endif (DEBIAN_FOUND) + + endif (APPLE) + +endif (UNIX) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 10a5d22fbe..832fc611f6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -236,7 +236,6 @@ target_link_libraries(components ${OSGGA_LIBRARIES} ${OSGSHADOW_LIBRARIES} ${OSGANIMATION_LIBRARIES} - ${BULLET_LIBRARIES} ${SDL2_LIBRARIES} ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} @@ -246,6 +245,12 @@ target_link_libraries(components RecastNavigation::Recast ) +if (BULLET_USE_DOUBLES AND (UBUNTU_FOUND OR DEBIAN_FOUND)) + target_link_libraries(components BulletCollision-float64 LinearMath-float64) +else() + target_link_libraries(components ${BULLET_LIBRARIES}) +endif() + if (WIN32) target_link_libraries(components ${Boost_LOCALE_LIBRARY} From 6a12c2b58b80ca69c93575820be8666d493f60b1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Dec 2020 12:06:41 +0400 Subject: [PATCH 0151/2859] Fix GCC build warnings --- apps/openmw/mwmechanics/aifollow.cpp | 1 - apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp | 2 +- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 1e4fb206e3..f31be9c10a 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -119,7 +119,6 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target); if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex) { - osg::Vec3f::value_type maxSize = 0; for(auto& follower : followers) { auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y(); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 8dd5a624fc..27e7a390c4 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -23,7 +23,7 @@ namespace MWPhysics int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, - int index1) + int index1) override { if(cp.getDistance() <= 0.0f) overlapping = true; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 69bf042369..9d29574098 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -41,7 +41,7 @@ namespace MWPhysics m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask; mVelocity = Misc::Convert::toBullet(velocity); } - virtual btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) + btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override { if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) return 0.0; From 66fee6dccbc539770a3adc3ead060c0dbd14e27b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Dec 2020 11:45:17 +0400 Subject: [PATCH 0152/2859] Enhance level-up tooltip --- apps/openmw/mwgui/statswindow.cpp | 11 +++++++---- files/mygui/openmw_tooltips.layout | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index e02b2f45f1..66a1ea1eff 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -336,14 +337,16 @@ namespace MWGui int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); - std::string detail; + std::stringstream detail; for (int i = 0; i < ESM::Attribute::Length; ++i) { if (auto increase = PCstats.getLevelUpAttributeIncrease(i)) - detail += (detail.empty() ? "" : "\n") + ESM::Attribute::sAttributeNames[i] + " x" + MyGUI::utility::toString(increase); + detail << (detail.str().empty() ? "" : "\n") << "#{" + << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[i]) + << "} x" << MyGUI::utility::toString(increase); } - if (!detail.empty()) - levelWidget->setUserString("Caption_LevelDetailText", detail); + if (!detail.str().empty()) + levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index 59050f932b..bb505d2539 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -272,6 +272,7 @@ + From ee2416017ecd15baed2dd213d1cca8184d916dfc Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Dec 2020 12:19:22 +0400 Subject: [PATCH 0153/2859] Fix tag mismatch --- components/nifbullet/bulletnifloader.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 054b33fedb..17a0d3e8b9 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -23,7 +23,7 @@ class btCollisionShape; namespace Nif { - class Node; + struct Node; struct Transformation; struct NiTriShape; struct NiTriStrips; From 694b9c15059f7fd3d3cc67acb94339d15e30b918 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 28 Dec 2020 18:11:51 +0100 Subject: [PATCH 0154/2859] Fixed by 7cac7fa87052e9e14a2e2ec3b5fb489a4820cdbb --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 661a058d4c..15bb77e739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Bug #3862: Random container contents behave differently than vanilla Bug #3929: Leveled list merchant containers respawn on barter Bug #4021: Attributes and skills are not stored as floats + Bug #4039: Multiple followers should have the same following distance Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks From e80d34268ca8d1c5c37ec51d5bb7dec8e67d13cf Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Dec 2020 21:11:58 +0400 Subject: [PATCH 0155/2859] Fix defines names --- apps/openmw/mwrender/landmanager.hpp | 4 ++-- apps/openmw/mwrender/objectpaging.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index ea73f11c2b..1694bd243b 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H -#define OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H +#ifndef OPENMW_MWRENDER_LANDMANAGER_H +#define OPENMW_MWRENDER_LANDMANAGER_H #include diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index ff32dadd4d..65f53d530c 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H -#define OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H +#ifndef OPENMW_MWRENDER_OBJECTPAGING_H +#define OPENMW_MWRENDER_OBJECTPAGING_H #include #include From e4e19bab1b3b7bbd1f0eb45c58209f731d6d55f2 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 28 Dec 2020 21:42:04 +0300 Subject: [PATCH 0156/2859] Make min and max view distance accurate to vanilla --- docs/source/reference/modding/settings/camera.rst | 2 +- files/mygui/openmw_settings_window.layout | 10 +++++----- files/settings-default.cfg | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index 5701947dc4..c30dcb3f3b 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -46,7 +46,7 @@ viewing distance :Type: floating point :Range: > 0 -:Default: 6656.0 +:Default: 7168.0 This value controls the maximum visible distance (also called the far clipping plane). Larger values significantly improve rendering in exterior spaces, diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index f61af5a100..14ab7c9de5 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -377,25 +377,25 @@ - + - - + + - + - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c8805faa39..d0793fc819 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -23,7 +23,7 @@ small feature culling pixel size = 2.0 # Maximum visible distance. Caution: this setting # can dramatically affect performance, see documentation for details. -viewing distance = 6656.0 +viewing distance = 7168.0 # Camera field of view in degrees (e.g. 30.0 to 110.0). # Does not affect the player's hands in the first person camera. From 54853380cdbdeb76195244a5f85ac75a956dd005 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 28 Dec 2020 22:39:09 +0000 Subject: [PATCH 0157/2859] Enable multisampling in RTTs to support A2C --- apps/openmw/mwrender/characterpreview.cpp | 3 ++- apps/openmw/mwrender/localmap.cpp | 12 +++++++++++- apps/openmw/mwrender/water.cpp | 24 +++++++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 89db3e5f46..061f4d5094 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -135,7 +136,7 @@ namespace MWRender mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed mCamera->setViewport(0, 0, sizeX, sizeY); mCamera->setRenderOrder(osg::Camera::PRE_RENDER); - mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); + mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); mCamera->setName("CharacterPreview"); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); mCamera->setCullMask(~(Mask_UpdateVisitor)); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 401e21ae46..90c85f39fa 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -233,7 +233,17 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - camera->attach(osg::Camera::COLOR_BUFFER, texture); + unsigned int samples = 0; + unsigned int colourSamples = 0; + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + { + // Alpha-to-coverage requires a multisampled framebuffer. + // OSG will set that up automatically and resolve it to the specified single-sample texture for us. + // For some reason, two samples are needed, at least with some drivers. + samples = 2; + colourSamples = 1; + } + camera->attach(osg::Camera::COLOR_BUFFER, texture, 0, 0, false, samples, colourSamples); camera->addChild(mSceneRoot); mRoot->addChild(camera); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b9018e0a27..3d3ec4ef6e 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -271,7 +271,17 @@ public: mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); + unsigned int samples = 0; + unsigned int colourSamples = 0; + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + { + // Alpha-to-coverage requires a multisampled framebuffer. + // OSG will set that up automatically and resolve it to the specified single-sample texture for us. + // For some reason, two samples are needed, at least with some drivers. + samples = 2; + colourSamples = 1; + } + attach(osg::Camera::COLOR_BUFFER, mRefractionTexture, 0, 0, false, samples, colourSamples); mRefractionDepthTexture = new osg::Texture2D; mRefractionDepthTexture->setTextureSize(rttSize, rttSize); @@ -356,7 +366,17 @@ public: mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); + unsigned int samples = 0; + unsigned int colourSamples = 0; + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + { + // Alpha-to-coverage requires a multisampled framebuffer. + // OSG will set that up automatically and resolve it to the specified single-sample texture for us. + // For some reason, two samples are needed, at least with some drivers. + samples = 2; + colourSamples = 1; + } + attach(osg::Camera::COLOR_BUFFER, mReflectionTexture, 0, 0, false, samples, colourSamples); // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. osg::ref_ptr frontFace (new osg::FrontFace); From e46472442a01ca792371b6da20e42f74033bda26 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 29 Dec 2020 01:40:30 +0000 Subject: [PATCH 0158/2859] Switch torches to shields for hostile NPCs --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/actors.cpp | 7 +++++++ apps/openmw/mwworld/inventorystore.cpp | 8 ++++++++ apps/openmw/mwworld/inventorystore.hpp | 2 ++ 4 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15bb77e739..ce0cce9522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #5101: Hostile followers travel with the player Bug #5108: Savegame bloating due to inefficient fog textures format Bug #5165: Active spells should use real time intead of timestamps + Bug #5300: NPCs don't switch from torch to shield when starting combat Bug #5358: ForceGreeting always resets the dialogue window completely Bug #5363: Enchantment autocalc not always 0/1 Bug #5364: Script fails/stops if trying to startscript an unknown script diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 88c402b14c..c9fcf82804 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1430,6 +1430,13 @@ namespace MWMechanics if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) inventoryStore.unequipItem(*heldIter, ptr); } + else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name()) + { + // For hostile NPCs, see if they have anything better to equip first + auto shield = inventoryStore.getPreferredShield(ptr); + if(shield != inventoryStore.end()) + inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr); + } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 386bbb30a4..38007c1cbb 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -556,6 +556,14 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) } } +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield(const MWWorld::Ptr& actor) +{ + TSlots slots; + initSlots (slots); + autoEquipArmor(actor, slots); + return slots[Slot_CarriedLeft]; +} + const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const { return mMagicEffects; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index e70c214809..df69a57090 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -153,6 +153,8 @@ namespace MWWorld ContainerStoreIterator getSlot (int slot); ConstContainerStoreIterator getSlot(int slot) const; + ContainerStoreIterator getPreferredShield(const MWWorld::Ptr& actor); + void unequipAll(const MWWorld::Ptr& actor); ///< Unequip all currently equipped items. From 396667f801927b6ea8912e5fded9c5445a4bdc15 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 29 Dec 2020 14:49:22 +0300 Subject: [PATCH 0159/2859] Fix weather particle fading Update rain particle system setup --- apps/openmw/mwrender/sky.cpp | 147 ++++++++++++++++------------------- apps/openmw/mwrender/sky.hpp | 4 +- 2 files changed, 70 insertions(+), 81 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2920e07dde..3c63c58c66 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -45,6 +45,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -1136,7 +1138,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) - , mWeatherAlpha(0.f) + , mEffectFade(0.f) { osg::ref_ptr skyroot (new CameraRelativeTransform); skyroot->setName("Sky Root"); @@ -1277,16 +1279,10 @@ private: class AlphaFader : public SceneUtil::StateSetUpdater { public: - /// @param alphaUpdate variable which to update with alpha value - AlphaFader(float *alphaUpdate) - : mAlpha(1.f) - { - mAlphaUpdate = alphaUpdate; - } - - void setAlpha(float alpha) + /// @param alpha the variable alpha value is recovered from + AlphaFader(float& alpha) + : mAlpha(alpha) { - mAlpha = alpha; } void setDefaults(osg::StateSet* stateset) override @@ -1300,19 +1296,16 @@ public: { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mAlpha)); - - if (mAlphaUpdate) - *mAlphaUpdate = mAlpha; } // Helper for adding AlphaFaders to a subgraph class SetupVisitor : public osg::NodeVisitor { public: - SetupVisitor(float *alphaUpdate) + SetupVisitor(float &alpha) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mAlpha(alpha) { - mAlphaUpdate = alphaUpdate; } void apply(osg::Node &node) override @@ -1332,56 +1325,24 @@ public: callback = callback->getNestedCallback(); } - osg::ref_ptr alphaFader (new AlphaFader(mAlphaUpdate)); + osg::ref_ptr alphaFader (new AlphaFader(mAlpha)); if (composite) composite->addController(alphaFader); else node.addUpdateCallback(alphaFader); - - mAlphaFaders.push_back(alphaFader); } } traverse(node); } - std::vector > getAlphaFaders() - { - return mAlphaFaders; - } - private: - std::vector > mAlphaFaders; - float *mAlphaUpdate; + float &mAlpha; }; protected: - float mAlpha; - float *mAlphaUpdate; -}; - -class RainFader : public AlphaFader -{ -public: - RainFader(float *alphaUpdate): AlphaFader(alphaUpdate) - { - } - - void setDefaults(osg::StateSet* stateset) override - { - osg::ref_ptr mat (new osg::Material); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); - mat->setColorMode(osg::Material::OFF); - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - } - - void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override - { - AlphaFader::apply(stateset,nv); - *mAlphaUpdate = mAlpha * 2.0; // mAlpha is limited to 0.6 so multiply by 2 to reach full intensity - } + float &mAlpha; }; void SkyManager::setCamera(osg::Camera *camera) @@ -1465,6 +1426,37 @@ protected: } }; +class WeatherAlphaOperator : public osgParticle::Operator +{ +public: + WeatherAlphaOperator(float& alpha, bool rain) + : mAlpha(alpha) + , mIsRain(rain) + { + } + + osg::Object *cloneType() const override + { + return nullptr; + } + + osg::Object *clone(const osg::CopyOp &op) const override + { + return nullptr; + } + + void operate(osgParticle::Particle *particle, double dt) override + { + constexpr float rainThreshold = 0.6f; // Rain_Threshold? + const float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; + particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); + } + +private: + float &mAlpha; + bool mIsRain; +}; + void SkyManager::createRain() { if (mRainNode) @@ -1472,7 +1464,7 @@ void SkyManager::createRain() mRainNode = new osg::Group; - mRainParticleSystem = new osgParticle::ParticleSystem; + mRainParticleSystem = new NifOsg::ParticleSystem; osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); @@ -1491,6 +1483,12 @@ void SkyManager::createRain() stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + osg::ref_ptr mat (new osg::Material); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); @@ -1523,6 +1521,7 @@ void SkyManager::createRain() osg::ref_ptr program (new osgParticle::ModularProgram); program->addOperator(new WrapAroundOperator(mCamera,rainRange)); + program->addOperator(new WeatherAlphaOperator(mEffectFade, true)); program->setParticleSystem(mRainParticleSystem); mRainNode->addChild(program); @@ -1530,8 +1529,7 @@ void SkyManager::createRain() mRainNode->addChild(mRainParticleSystem); mRainNode->addChild(updater); - mRainFader = new RainFader(&mWeatherAlpha); - mRainNode->addUpdateCallback(mRainFader); + // Note: if we ever switch to regular geometry rain, it'll need to use an AlphaFader. mRainNode->addCullCallback(mUnderwaterSwitch); mRainNode->setNodeMask(Mask_WeatherParticles); @@ -1549,7 +1547,6 @@ void SkyManager::destroyRain() mCounter = nullptr; mRainParticleSystem = nullptr; mRainShooter = nullptr; - mRainFader = nullptr; } SkyManager::~SkyManager() @@ -1588,17 +1585,18 @@ void SkyManager::update(float duration) if (!mEnabled) { if (mRainIntensityUniform) - mRainIntensityUniform->set((float) 0.0); + mRainIntensityUniform->set(0.f); return; } if (mRainIntensityUniform) { - if (mIsStorm || (!hasRain() && !mParticleNode)) - mRainIntensityUniform->set((float) 0.0); - else - mRainIntensityUniform->set((float) mWeatherAlpha); + float rainIntensity = 0.f; + if (!mIsStorm && (hasRain() || mParticleNode)) + rainIntensity = mEffectFade; + + mRainIntensityUniform->set(rainIntensity); } switchUnderwaterRain(); @@ -1712,7 +1710,6 @@ void SkyManager::setWeather(const WeatherResult& weather) { mParticleNode->removeChild(mParticleEffect); mParticleEffect = nullptr; - mParticleFaders.clear(); } if (mCurrentParticleEffect.empty()) @@ -1738,28 +1735,26 @@ void SkyManager::setWeather(const WeatherResult& weather) SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(new SceneUtil::FrameTimeSource)); mParticleEffect->accept(assignVisitor); - AlphaFader::SetupVisitor alphaFaderSetupVisitor(&mWeatherAlpha); + AlphaFader::SetupVisitor alphaFaderSetupVisitor(mEffectFade); mParticleEffect->accept(alphaFaderSetupVisitor); - mParticleFaders = alphaFaderSetupVisitor.getAlphaFaders(); SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; mParticleEffect->accept(disableFreezeOnCullVisitor); - if (!weather.mIsStorm) - { - SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); - mParticleEffect->accept(findPSVisitor); + SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); + mParticleEffect->accept(findPSVisitor); - for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) - { - osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); + for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) + { + osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); - osg::ref_ptr program (new osgParticle::ModularProgram); + osg::ref_ptr program (new osgParticle::ModularProgram); + if (!mIsStorm) program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); - program->setParticleSystem(ps); - mParticleNode->addChild(program); - } + program->addOperator(new WeatherAlphaOperator(mEffectFade, false)); + program->setParticleSystem(ps); + mParticleNode->addChild(program); } } } @@ -1846,11 +1841,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0); - if (mRainFader) - mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold? - - for (AlphaFader* fader : mParticleFaders) - fader->setAlpha(weather.mEffectFade); + mEffectFade = weather.mEffectFade; } void SkyManager::sunEnable() diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index cf697bd44f..c4e0b57738 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -200,7 +200,6 @@ namespace MWRender osg::ref_ptr mParticleNode; osg::ref_ptr mParticleEffect; - std::vector > mParticleFaders; osg::ref_ptr mUnderwaterSwitch; osg::ref_ptr mCloudNode; @@ -227,7 +226,6 @@ namespace MWRender osg::ref_ptr mPlacer; osg::ref_ptr mCounter; osg::ref_ptr mRainShooter; - osg::ref_ptr mRainFader; bool mCreated; @@ -269,7 +267,7 @@ namespace MWRender bool mEnabled; bool mSunEnabled; - float mWeatherAlpha; + float mEffectFade; osg::Vec4f mMoonScriptColor; }; From 3bf641d3ce975fd0d39c22036822e800508146d0 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 29 Dec 2020 21:45:59 +0100 Subject: [PATCH 0160/2859] Show mesh origin --- CHANGELOG.md | 1 + apps/openmw/engine.cpp | 1 + apps/openmw/mwbase/environment.cpp | 12 ++++++++++++ apps/openmw/mwbase/environment.hpp | 10 ++++++++++ apps/openmw/mwscript/miscextensions.cpp | 15 ++++++++++++++- components/bsa/bsa_file.hpp | 5 +++++ components/vfs/archive.hpp | 5 +++++ components/vfs/bsaarchive.cpp | 17 +++++++++++++++++ components/vfs/bsaarchive.hpp | 2 ++ components/vfs/filesystemarchive.cpp | 15 +++++++++++++++ components/vfs/filesystemarchive.hpp | 3 +++ components/vfs/manager.cpp | 11 +++++++++++ components/vfs/manager.hpp | 1 + 13 files changed, 97 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0cce9522..141edcad28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,7 @@ Feature #5672: Make stretch menu background configuration more accessible Feature #5692: Improve spell/magic item search to factor in magic effect names Feature #5730: Add graphic herbalism option to the launcher and documents + Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8f23f710dd..81a97b0f2d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -749,6 +749,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage)); + mEnvironment.setResourceSystem(mResourceSystem.get()); // scripts if (mCompileAll) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index aca2685e06..edb10d9456 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "world.hpp" #include "scriptmanager.hpp" #include "dialoguemanager.hpp" @@ -76,6 +78,11 @@ void MWBase::Environment::setStateManager (StateManager *stateManager) mStateManager = stateManager; } +void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem) +{ + mResourceSystem = resourceSystem; +} + void MWBase::Environment::setFrameDuration (float duration) { mFrameDuration = duration; @@ -158,6 +165,11 @@ MWBase::StateManager *MWBase::Environment::getStateManager() const return mStateManager; } +Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const +{ + return mResourceSystem; +} + float MWBase::Environment::getFrameDuration() const { return mFrameDuration; diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 80e6a6243c..7871153cc5 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -6,6 +6,11 @@ namespace osg class Stats; } +namespace Resource +{ + class ResourceSystem; +} + namespace MWBase { class World; @@ -37,6 +42,7 @@ namespace MWBase Journal *mJournal; InputManager *mInputManager; StateManager *mStateManager; + Resource::ResourceSystem *mResourceSystem; float mFrameDuration; float mFrameRateLimit; @@ -70,6 +76,8 @@ namespace MWBase void setStateManager (StateManager *stateManager); + void setResourceSystem (Resource::ResourceSystem *resourceSystem); + void setFrameDuration (float duration); ///< Set length of current frame in seconds. @@ -95,6 +103,8 @@ namespace MWBase StateManager *getStateManager() const; + Resource::ResourceSystem *getResourceSystem() const; + float getFrameDuration() const; void cleanup(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index a288d66730..d78337a627 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -13,10 +13,15 @@ #include #include +#include + +#include #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" @@ -1377,7 +1382,15 @@ namespace MWScript msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() << std::endl; osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); msg << "Coordinates: " << pos.x() << " " << pos.y() << " " << pos.z() << std::endl; - msg << "Model: " << ptr.getClass().getModel(ptr) << std::endl; + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + std::string model = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), vfs); + msg << "Model: " << model << std::endl; + if(!model.empty()) + { + const std::string archive = vfs->getArchive(model); + if(!archive.empty()) + msg << "(" << archive << ")" << std::endl; + } if (!ptr.getClass().getScript(ptr).empty()) msg << "Script: " << ptr.getClass().getScript(ptr) << std::endl; } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 0378027397..3e7538401d 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -135,6 +135,11 @@ public: /// @note Thread safe. const FileList &getList() const { return mFiles; } + + const std::string& getFilename() const + { + return mFilename; + } }; } diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index b36c7117b0..971ac15b39 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -23,6 +23,11 @@ namespace VFS /// List all resources contained in this archive, and run the resource names through the given normalize function. virtual void listResources(std::map& out, char (*normalize_function) (char)) = 0; + + /// True if this archive contains the provided normalized file. + virtual bool contains(const std::string& file, char (*normalize_function) (char)) const = 0; + + virtual std::string getDescription() const = 0; }; } diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index ac65c58a1c..e6d779aabc 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -39,6 +39,23 @@ void BsaArchive::listResources(std::map &out, char (*normal } } +bool BsaArchive::contains(const std::string& file, char (*normalize_function)(char)) const +{ + for (const auto& it : mResources) + { + std::string ent = it.mInfo->name; + std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); + if(file == ent) + return true; + } + return false; +} + +std::string BsaArchive::getDescription() const +{ + return std::string{"BSA: "} + mFile->getFilename(); +} + // ------------------------------------------------------------------------------ BsaArchiveFile::BsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::BSAFile* bsa) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 65a9db16c1..c979b5ce7e 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -24,6 +24,8 @@ namespace VFS BsaArchive(const std::string& filename); virtual ~BsaArchive(); void listResources(std::map& out, char (*normalize_function) (char)) override; + bool contains(const std::string& file, char (*normalize_function) (char)) const override; + std::string getDescription() const override; private: std::unique_ptr mFile; diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index ce4ff020ef..17f3891ec6 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -53,6 +53,21 @@ namespace VFS } } + bool FileSystemArchive::contains(const std::string& file, char (*normalize_function)(char)) const + { + for (const auto& it : mIndex) + { + if(it.first == file) + return true; + } + return false; + } + + std::string FileSystemArchive::getDescription() const + { + return std::string{"DIR: "} + mPath; + } + // ---------------------------------------------------------------------------------- FileSystemArchiveFile::FileSystemArchiveFile(const std::string &path) diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index d228ba87c5..70463d32f2 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -25,6 +25,9 @@ namespace VFS void listResources(std::map& out, char (*normalize_function) (char)) override; + bool contains(const std::string& file, char (*normalize_function) (char)) const override; + + std::string getDescription() const override; private: typedef std::map index; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index c198823811..c7abc2483a 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -96,4 +96,15 @@ namespace VFS normalize_path(name, mStrict); } + std::string Manager::getArchive(const std::string& name) const + { + std::string normalized = name; + normalize_path(normalized, mStrict); + for(const auto archive : mArchives) + { + if(archive->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) + return archive->getDescription(); + } + return {}; + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index c5f0a8fec3..5a09a995eb 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -58,6 +58,7 @@ namespace VFS /// @note May be called from any thread once the index has been built. Files::IStreamPtr getNormalized(const std::string& normalizedName) const; + std::string getArchive(const std::string& name) const; private: bool mStrict; From 630ec36d1fcaa68805731c4ac6bc4123b173a8f0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 30 Dec 2020 10:35:51 +0100 Subject: [PATCH 0161/2859] iterate in reverse order --- components/vfs/manager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index c7abc2483a..045fe3cf5c 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -100,10 +100,10 @@ namespace VFS { std::string normalized = name; normalize_path(normalized, mStrict); - for(const auto archive : mArchives) + for(auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) { - if(archive->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) - return archive->getDescription(); + if((*it)->contains(normalized, mStrict ? &strict_normalize_char : &nonstrict_normalize_char)) + return (*it)->getDescription(); } return {}; } From 57c92673bc804653fd9502fa2b90c597f3620562 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 30 Dec 2020 16:09:12 +0100 Subject: [PATCH 0162/2859] Consider a path completed if it was non-empty --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 4 ++++ apps/openmw/mwmechanics/pathfinding.hpp | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8880820dd0..8af2c71ed5 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -158,7 +158,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& zTurn(actor, getZAngleToPoint(position, dest)); smoothTurn(actor, getXAngleToPoint(position, dest), 0); world->removeActorPath(actor); - return isDestReached; + return isDestReached || mPathFinder.pathWasPossible(); } world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index a82dcf7173..77ab78fd79 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -318,6 +318,7 @@ namespace MWMechanics mPath.clear(); mPath.push_back(endPoint); mConstructed = true; + mPossible = true; } void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -329,6 +330,7 @@ namespace MWMechanics buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); mConstructed = true; + mPossible = !mPath.empty(); } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, @@ -342,6 +344,7 @@ namespace MWMechanics mPath.push_back(endPoint); mConstructed = true; + mPossible = !mPath.empty(); } void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -367,6 +370,7 @@ namespace MWMechanics mPath.push_back(endPoint); mConstructed = true; + mPossible = !mPath.empty(); } bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 5af822fee7..ed786dc846 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -74,6 +74,7 @@ namespace MWMechanics public: PathFinder() : mConstructed(false) + , mPossible(false) , mCell(nullptr) { } @@ -81,6 +82,7 @@ namespace MWMechanics void clearPath() { mConstructed = false; + mPossible = false; mPath.clear(); mCell = nullptr; } @@ -109,6 +111,11 @@ namespace MWMechanics return mConstructed && mPath.empty(); } + bool pathWasPossible() const + { + return mPossible; + } + /// In radians float getZAngleToNext(float x, float y) const; @@ -137,6 +144,7 @@ namespace MWMechanics void addPointToPath(const osg::Vec3f& point) { mConstructed = true; + mPossible = true; mPath.push_back(point); } @@ -196,6 +204,7 @@ namespace MWMechanics private: bool mConstructed; + bool mPossible; std::deque mPath; const MWWorld::CellStore* mCell; From dbdd397716b366baf492f21af3a71ed12e885e0d Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 1 Jan 2021 16:54:45 +0100 Subject: [PATCH 0163/2859] Remove deadcode. --- apps/launcher/advancedpage.cpp | 4 +--- apps/launcher/advancedpage.hpp | 4 +--- apps/launcher/datafilespage.hpp | 4 ---- apps/launcher/graphicspage.cpp | 3 +-- apps/launcher/graphicspage.hpp | 3 +-- apps/launcher/maindialog.cpp | 4 ++-- apps/launcher/utils/cellnameloader.cpp | 3 ++- components/config/gamesettings.cpp | 2 -- components/config/gamesettings.hpp | 5 ++--- components/config/launchersettings.cpp | 4 ---- components/config/launchersettings.hpp | 3 --- 11 files changed, 10 insertions(+), 29 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index ed04072351..6f59ade7ab 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -10,11 +10,9 @@ #include -Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, - Config::GameSettings &gameSettings, +Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) - , mCfgMgr(cfg) , mGameSettings(gameSettings) , mEngineSettings(engineSettings) { diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index ef2f4740f3..a373fae430 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -9,7 +9,6 @@ #include -namespace Files { struct ConfigurationManager; } namespace Config { class GameSettings; } namespace Launcher @@ -19,7 +18,7 @@ namespace Launcher Q_OBJECT public: - AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + AdvancedPage(Config::GameSettings &gameSettings, Settings::Manager &engineSettings, QWidget *parent = nullptr); bool loadSettings(); @@ -35,7 +34,6 @@ namespace Launcher void slotViewOverShoulderToggled(bool checked); private: - Files::ConfigurationManager &mCfgMgr; Config::GameSettings &mGameSettings; Settings::Manager &mEngineSettings; QCompleter mCellNameCompleter; diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 92984847e2..3d200e6d2d 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -88,11 +88,7 @@ namespace Launcher QStringList previousSelectedFiles; QString mDataLocal; - void setPluginsCheckstates(Qt::CheckState state); - void buildView(); - void setupConfig(); - void readConfig(); void setProfile (int index, bool savePrevious); void setProfile (const QString &previous, const QString ¤t, bool savePrevious); void removeProfile (const QString &profile); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index d7e7eabc50..d1cf3aa6ff 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -29,9 +29,8 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) - , mCfgMgr(cfg) , mEngineSettings(engineSettings) { setObjectName ("GraphicsPage"); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 55178e0d75..35f7115003 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -20,7 +20,7 @@ namespace Launcher Q_OBJECT public: - GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = nullptr); + GraphicsPage(Settings::Manager &engineSettings, QWidget *parent = nullptr); void saveSettings(); bool loadSettings(); @@ -35,7 +35,6 @@ namespace Launcher void slotShadowDistLimitToggled(bool checked); private: - Files::ConfigurationManager &mCfgMgr; Settings::Manager &mEngineSettings; QVector mResolutionsPerScreen; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index df2ba68910..215258bcd2 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -126,9 +126,9 @@ void Launcher::MainDialog::createPages() mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); + mGraphicsPage = new GraphicsPage(mEngineSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this); + mAdvancedPage = new AdvancedPage(mGameSettings, mEngineSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp index 6d1ed2f494..e7f6e83e74 100644 --- a/apps/launcher/utils/cellnameloader.cpp +++ b/apps/launcher/utils/cellnameloader.cpp @@ -45,4 +45,5 @@ QString CellNameLoader::getCellName(ESM::ESMReader &esmReader) cell.loadNameAndData(esmReader, isDeleted); return QString::fromStdString(cell.mName); -} \ No newline at end of file +} + diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 85c090a3ff..d7fe7da948 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -14,8 +14,6 @@ Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg) { } -Config::GameSettings::~GameSettings() = default; - void Config::GameSettings::validatePaths() { QStringList paths = mSettings.values(QString("data")); diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index ccb1d5fd25..263a151c93 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -21,7 +21,6 @@ namespace Config { public: GameSettings(Files::ConfigurationManager &cfg); - ~GameSettings(); inline QString value(const QString &key, const QString &defaultValue = QString()) { @@ -54,11 +53,11 @@ namespace Config mUserSettings.remove(key); } - inline QStringList getDataDirs() { return mDataDirs; } + inline QStringList getDataDirs() const { return mDataDirs; } inline void removeDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.removeAll(dir); } inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } - inline QString getDataLocal() {return mDataLocal; } + inline QString getDataLocal() const {return mDataLocal; } bool hasMaster(); diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index ff2c38438f..025bc43827 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -12,10 +12,6 @@ const char Config::LauncherSettings::sLauncherConfigFileName[] = "launcher.cfg"; const char Config::LauncherSettings::sContentListsSectionPrefix[] = "Profiles/"; const char Config::LauncherSettings::sContentListSuffix[] = "/content"; -Config::LauncherSettings::LauncherSettings() = default; - -Config::LauncherSettings::~LauncherSettings() = default; - QStringList Config::LauncherSettings::subKeys(const QString &key) { QMultiMap settings = SettingsBase::getSettings(); diff --git a/components/config/launchersettings.hpp b/components/config/launchersettings.hpp index 1483052bbc..da492c85ce 100644 --- a/components/config/launchersettings.hpp +++ b/components/config/launchersettings.hpp @@ -9,9 +9,6 @@ namespace Config class LauncherSettings : public SettingsBase > { public: - LauncherSettings(); - ~LauncherSettings(); - bool writeFile(QTextStream &stream); /// \return names of all Content Lists in the launcher's .cfg file. From 9b99c76032bb96002fae6a5d67a41423a935119f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 1 Jan 2021 16:01:33 +0000 Subject: [PATCH 0164/2859] Clamp alpha reference to avoid degenerate case --- files/shaders/alpha.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index b1c7ed149e..cb38831c5b 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -15,7 +15,7 @@ uniform float alphaRef; void alphaTest() { #if @alphaToCoverage - float coverageAlpha = (gl_FragData[0].a - alphaRef) / max(fwidth(gl_FragData[0].a), 0.0001) + 0.5; + float coverageAlpha = (gl_FragData[0].a - clamp(alphaRef, 0.0001, 0.9999)) / max(fwidth(gl_FragData[0].a), 0.0001) + 0.5; // Some functions don't make sense with A2C or are a pain to think about and no meshes use them anyway // Use regular alpha testing in such cases until someone complains. From 5215ffd964aaa13297f8fd15d96a2f1a9e79eb9e Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 22 Dec 2020 14:51:16 +0100 Subject: [PATCH 0165/2859] The debug drawer rely on Bullet to determines the vertices of collision shapes. It doesn't work in the case of spheres (used by projectiles): resulting shape is malformed. It can also leads to this error which makes the debug drawer non-working till game restart: CullVisitor::apply(Geode&) detected NaN, depth=nan, center=(nan nan nan), matrix={ -0.265814 -0.0639702 0.9619 0 0.964024 -0.0176387 0.265228 0 -4.29769e-09 0.997796 0.0663574 0 18178.6 -3550.91 42154.4 1 } Avoid this issue by using osg::Sphere While here, remove an unused function. We don't use Bullet's solver so we never have any contact points (in Bullet parlance). --- apps/openmw/mwrender/bulletdebugdraw.cpp | 22 +++++++++++++++++----- apps/openmw/mwrender/bulletdebugdraw.hpp | 4 +++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index 00529ef80d..207b0ee504 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -7,6 +7,9 @@ #include #include +#include +#include +#include #include "bulletdebugdraw.hpp" #include "vismask.hpp" @@ -41,6 +44,14 @@ void DebugDrawer::createGeometry() mGeometry->addPrimitiveSet(mDrawArrays); mParentNode->addChild(mGeometry); + + auto* stateSet = new osg::StateSet; + stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); + mShapesRoot = new osg::Group; + mShapesRoot->setStateSet(stateSet); + mShapesRoot->setDataVariance(osg::Object::DYNAMIC); + mShapesRoot->setNodeMask(Mask_Debug); + mParentNode->addChild(mShapesRoot); } } @@ -49,6 +60,7 @@ void DebugDrawer::destroyGeometry() if (mGeometry) { mParentNode->removeChild(mGeometry); + mParentNode->removeChild(mShapesRoot); mGeometry = nullptr; mVertices = nullptr; mDrawArrays = nullptr; @@ -66,6 +78,7 @@ void DebugDrawer::step() { mVertices->clear(); mColors->clear(); + mShapesRoot->removeChildren(0, mShapesRoot->getNumChildren()); mWorld->debugDrawWorld(); showCollisions(); mDrawArrays->setCount(mVertices->size()); @@ -106,12 +119,11 @@ void DebugDrawer::showCollisions() mCollisionViews.end()); } -void DebugDrawer::drawContactPoint(const btVector3 &PointOnB, const btVector3 &normalOnB, btScalar distance, int lifeTime, const btVector3 &color) +void DebugDrawer::drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) { - mVertices->push_back(Misc::Convert::toOsg(PointOnB)); - mVertices->push_back(Misc::Convert::toOsg(PointOnB) + (Misc::Convert::toOsg(normalOnB) * distance * 20)); - mColors->push_back({1,1,1,1}); - mColors->push_back({1,1,1,1}); + auto* geom = new osg::ShapeDrawable(new osg::Sphere(Misc::Convert::toOsg(transform.getOrigin()), radius)); + geom->setColor(osg::Vec4(1, 1, 1, 1)); + mShapesRoot->addChild(geom); } void DebugDrawer::reportErrorWarning(const char *warningString) diff --git a/apps/openmw/mwrender/bulletdebugdraw.hpp b/apps/openmw/mwrender/bulletdebugdraw.hpp index ec421bd742..b24640427d 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.hpp +++ b/apps/openmw/mwrender/bulletdebugdraw.hpp @@ -32,6 +32,7 @@ private: CollisionView(btVector3 orig, btVector3 normal) : mOrig(orig), mEnd(orig + normal * 20), mCreated(std::chrono::steady_clock::now()) {}; }; std::vector mCollisionViews; + osg::ref_ptr mShapesRoot; protected: osg::ref_ptr mParentNode; @@ -59,7 +60,8 @@ public: void showCollisions(); - void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) override; + void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) override {}; + void drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) override; void reportErrorWarning(const char* warningString) override; From c75d7ceada68c67ba62cb6111a850d9f34aa2f19 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 02:50:19 +0000 Subject: [PATCH 0166/2859] Ensure alpha test is off by default --- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a60077a169..567cd88ac6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -381,6 +381,8 @@ namespace MWRender // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); + // The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and breaks things. + mRootNode->getOrCreateStateSet()->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); From 69386df03602b6c1f1b4acc0e72d31f84dcb538d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 18:27:25 +0000 Subject: [PATCH 0167/2859] Correct alpha testing functions --- files/shaders/alpha.glsl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index cb38831c5b..2551a02052 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -40,22 +40,22 @@ void alphaTest() #if @alphaFunc == FUNC_NEVER discard; #elif @alphaFunc == FUNC_LESS - if (gl_FragData[0].a > alphaRef) + if (gl_FragData[0].a >= alphaRef) discard; #elif @alphaFunc == FUNC_EQUAL if (gl_FragData[0].a != alphaRef) discard; #elif @alphaFunc == FUNC_LEQUAL - if (gl_FragData[0].a >= alphaRef) + if (gl_FragData[0].a > alphaRef) discard; #elif @alphaFunc == FUNC_GREATER - if (gl_FragData[0].a < alphaRef) + if (gl_FragData[0].a <= alphaRef) discard; #elif @alphaFunc == FUNC_NOTEQUAL if (gl_FragData[0].a == alphaRef) discard; #elif @alphaFunc == FUNC_GEQUAL - if (gl_FragData[0].a <= alphaRef) + if (gl_FragData[0].a < alphaRef) discard; #endif #endif From f2eed5594a1ebd46909312eb6bf76742214853ab Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 18:27:46 +0000 Subject: [PATCH 0168/2859] Don't use A2C when MSAA is off --- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/water.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 90c85f39fa..28b7c816bc 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -235,7 +235,7 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 567cd88ac6..17a6527fc9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -219,7 +219,7 @@ namespace MWRender resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); - resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")); + resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 3d3ec4ef6e..8955248810 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -273,7 +273,7 @@ public: unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. @@ -368,7 +368,7 @@ public: unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. From e3fd5efcfefc22d9323463c7f274252b895c1b01 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 19:09:06 +0000 Subject: [PATCH 0169/2859] Disable A2C for alpha-blended drawables --- components/shader/shadervisitor.cpp | 14 +++++++++++++- components/shader/shadervisitor.hpp | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 4762549a7a..165be6745d 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -26,8 +26,10 @@ namespace Shader , mColorMode(0) , mMaterialOverridden(false) , mAlphaTestOverridden(false) + , mAlphaBlendOverridden(false) , mAlphaFunc(GL_ALWAYS) , mAlphaRef(1.0) + , mAlphaBlend(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) @@ -330,6 +332,15 @@ namespace Shader } } } + + unsigned int alphaBlend = stateset->getMode(GL_BLEND); + if (alphaBlend != osg::StateAttribute::INHERIT && (!mRequirements.back().mAlphaBlendOverridden || alphaBlend & osg::StateAttribute::PROTECTED)) + { + if (alphaBlend & osg::StateAttribute::OVERRIDE) + mRequirements.back().mAlphaBlendOverridden = true; + + mRequirements.back().mAlphaBlend = alphaBlend & osg::StateAttribute::ON; + } } void ShaderVisitor::pushRequirements(osg::Node& node) @@ -394,7 +405,8 @@ namespace Shader // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - if (mConvertAlphaTestToAlphaToCoverage) + // Blending won't work with A2C as we use the alpha channel for coverage. gl_SampleCoverage from ARB_sample_shading would save the day, but requires GLSL 130 + if (mConvertAlphaTestToAlphaToCoverage && !reqs.mAlphaBlend) { writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); defineMap["alphaToCoverage"] = "1"; diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 606e06df97..9daeab29ba 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -84,9 +84,11 @@ namespace Shader bool mMaterialOverridden; bool mAlphaTestOverridden; + bool mAlphaBlendOverridden; GLenum mAlphaFunc; float mAlphaRef; + bool mAlphaBlend; bool mNormalHeight; // true if normal map has height info in alpha channel From d061ae809601ae2b258fe6627d456c236f93ffc9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 23:27:28 +0000 Subject: [PATCH 0170/2859] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f3d18161..214159db16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log Feature #4894: Consider actors as obstacles for pathfinding + Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #5043: Head Bobbing Feature #5199: Improve Scene Colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher From 701a5662b02b4d92c884774ddf75905b3f7b1909 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 3 Jan 2021 09:33:03 +0000 Subject: [PATCH 0171/2859] Remove #5683 changelog entry --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0cce9522..fa35722820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,7 +81,6 @@ Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx Bug #5681: Player character can clip or pass through bridges instead of colliding against them - Bug #5683: Player character can get stuck with MR's balmora's wooden gate Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE From d13459ecf98c4dfe0949d10ad4b5dec73817ab62 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 5 Jan 2021 22:21:54 +0000 Subject: [PATCH 0172/2859] Scale mipmap alpha to preserve coverage --- files/shaders/alpha.glsl | 23 +++++++++++++++++++++++ files/shaders/objects_fragment.glsl | 3 +++ files/shaders/shadowcasting_fragment.glsl | 2 ++ 3 files changed, 28 insertions(+) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 2551a02052..6ead9e6ca2 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -12,6 +12,29 @@ uniform float alphaRef; #endif +float mipmapLevel(vec2 scaleduv) +{ + vec2 dUVdx = dFdx(scaleduv); + vec2 dUVdy = dFdy(scaleduv); + float maxDUVSquared = max(dot(dUVdx, dUVdx), dot(dUVdy, dUVdy)); + return max(0.0, 0.5 * log2(maxDUVSquared)); +} + +float coveragePreservingAlphaScale(sampler2D diffuseMap, vec2 uv) +{ + #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER + vec2 textureSize; + #ifdef GL_EXT_gpu_shader4 + textureSize = textureSize2D(diffuseMap, 0); + #else + textureSize = 256.0; + #endif + return 1.0 + mipmapLevel(uv * textureSize) * 0.25; + #else + return 1.0; + #endif +} + void alphaTest() { #if @alphaToCoverage diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 995c2adaf3..c5f397789a 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,5 +1,7 @@ #version 120 +#extension EXT_gpu_shader4: enable + #if @diffuseMap uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; @@ -109,6 +111,7 @@ void main() #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); #else gl_FragData[0] = vec4(1.0); #endif diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 8c53c542bd..ea8a63313e 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -1,5 +1,7 @@ #version 120 +#extension EXT_gpu_shader4: enable + uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; From 4ed32520018dff5debf8d66ff7e1d7aa367a6c3b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 7 Jan 2021 18:13:51 +0000 Subject: [PATCH 0173/2859] Check for EXT_gpu_shader4 CPU-side Mesa lies and always defines GL_EXT_gpu_shader4 even when the extension isn't present. --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/sceneutil/mwshadowtechnique.cpp | 7 ++++++- components/shader/shadervisitor.cpp | 7 +++++++ files/shaders/alpha.glsl | 4 ++-- files/shaders/objects_fragment.glsl | 4 +++- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 17a6527fc9..95e98f55b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -252,6 +252,7 @@ namespace MWRender globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + globalDefines["useGPUShader4"] = "0"; // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index c781318faf..41e64e1247 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -892,12 +892,17 @@ 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_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr exts = osg::GLExtensions::Get(0, false); + std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) { auto& program = _castingPrograms[alphaFunc - GL_NEVER]; program = new osg::Program(); program->addShader(castingVertexShader); - program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"} }, osg::Shader::FRAGMENT)); + program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, + {"alphaToCoverage", "0"}, + {"useGPUShader4", useGPUShader4} + }, osg::Shader::FRAGMENT)); } } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 165be6745d..d719daec28 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -411,6 +412,12 @@ namespace Shader writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); defineMap["alphaToCoverage"] = "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) + defineMap["useGPUShader4"] = "1"; + // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 6ead9e6ca2..05be801e93 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -24,10 +24,10 @@ float coveragePreservingAlphaScale(sampler2D diffuseMap, vec2 uv) { #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER vec2 textureSize; - #ifdef GL_EXT_gpu_shader4 + #if @useGPUShader4 textureSize = textureSize2D(diffuseMap, 0); #else - textureSize = 256.0; + textureSize = vec2(256.0); #endif return 1.0 + mipmapLevel(uv * textureSize) * 0.25; #else diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index c5f397789a..37cca273fc 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,6 +1,8 @@ #version 120 -#extension EXT_gpu_shader4: enable +#if @useGPUShader4 + #extension EXT_gpu_shader4: require +#endif #if @diffuseMap uniform sampler2D diffuseMap; From 2a583e2337f8003a8ba47ac08fd8ab65d03d149b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 8 Jan 2021 17:24:13 +0100 Subject: [PATCH 0174/2859] consider empty paths as not constructed --- apps/openmw/mwmechanics/aipackage.cpp | 4 +++- apps/openmw/mwmechanics/pathfinding.cpp | 10 +++------- apps/openmw/mwmechanics/pathfinding.hpp | 9 --------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8af2c71ed5..ce5673909f 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -158,8 +158,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& zTurn(actor, getZAngleToPoint(position, dest)); smoothTurn(actor, getXAngleToPoint(position, dest), 0); world->removeActorPath(actor); - return isDestReached || mPathFinder.pathWasPossible(); + return true; } + else if (mPathFinder.getPath().empty()) + return false; world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 77ab78fd79..276321b81a 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -318,7 +318,6 @@ namespace MWMechanics mPath.clear(); mPath.push_back(endPoint); mConstructed = true; - mPossible = true; } void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -329,8 +328,7 @@ namespace MWMechanics buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, @@ -343,8 +341,7 @@ namespace MWMechanics if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath))) mPath.push_back(endPoint); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, @@ -369,8 +366,7 @@ namespace MWMechanics if (!hasNavMesh && mPath.empty()) mPath.push_back(endPoint); - mConstructed = true; - mPossible = !mPath.empty(); + mConstructed = !mPath.empty(); } bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ed786dc846..5af822fee7 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -74,7 +74,6 @@ namespace MWMechanics public: PathFinder() : mConstructed(false) - , mPossible(false) , mCell(nullptr) { } @@ -82,7 +81,6 @@ namespace MWMechanics void clearPath() { mConstructed = false; - mPossible = false; mPath.clear(); mCell = nullptr; } @@ -111,11 +109,6 @@ namespace MWMechanics return mConstructed && mPath.empty(); } - bool pathWasPossible() const - { - return mPossible; - } - /// In radians float getZAngleToNext(float x, float y) const; @@ -144,7 +137,6 @@ namespace MWMechanics void addPointToPath(const osg::Vec3f& point) { mConstructed = true; - mPossible = true; mPath.push_back(point); } @@ -204,7 +196,6 @@ namespace MWMechanics private: bool mConstructed; - bool mPossible; std::deque mPath; const MWWorld::CellStore* mCell; From 0068c7bb252428571c1f6ae4ca57220873d0c701 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 8 Jan 2021 17:32:15 +0000 Subject: [PATCH 0175/2859] Spec says GL_ --- files/shaders/objects_fragment.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 37cca273fc..693c045847 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,7 +1,7 @@ #version 120 #if @useGPUShader4 - #extension EXT_gpu_shader4: require + #extension GL_EXT_gpu_shader4: require #endif #if @diffuseMap From 7d551b0cfde472da8acb7e8a8f1a0b1899694ad9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 8 Jan 2021 23:21:39 +0100 Subject: [PATCH 0176/2859] Split long osg log messages into lines. --- apps/openmw/main.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 89aa2b9fd5..8eaac36e81 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -254,7 +254,19 @@ namespace level = Debug::Debug; } std::string_view s(msgCopy); - Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s); + if (s.size() < 1024) + Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s); + else + { + while (!s.empty()) + { + size_t lineSize = 1; + while (lineSize < s.size() && s[lineSize - 1] != '\n') + lineSize++; + Log(level) << s.substr(0, s[lineSize - 1] == '\n' ? lineSize - 1 : lineSize); + s = s.substr(lineSize); + } + } } }; } From 944033db4e4e3d7d0205480a8763f4d714652d59 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Jul 2020 23:56:14 +0200 Subject: [PATCH 0177/2859] Separate sound buffer pool from sound manager --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwsound/sound.hpp | 7 + apps/openmw/mwsound/sound_buffer.cpp | 154 +++++++++++++++++++++ apps/openmw/mwsound/sound_buffer.hpp | 91 +++++++++++-- apps/openmw/mwsound/sound_output.hpp | 9 +- apps/openmw/mwsound/soundmanagerimp.cpp | 169 +++--------------------- apps/openmw/mwsound/soundmanagerimp.hpp | 29 +--- 7 files changed, 276 insertions(+), 184 deletions(-) create mode 100644 apps/openmw/mwsound/sound_buffer.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a4c3b91361..f32240a915 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,6 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings + sound_buffer ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 9d264e1b69..d2e65c9895 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,6 +7,13 @@ namespace MWSound { + // Extra play flags, not intended for caller use + enum PlayModeEx + { + Play_2D = 0, + Play_3D = 1 << 31, + }; + // For testing individual PlayMode flags inline int operator&(int a, PlayMode b) { return a & static_cast(b); } inline int operator&(PlayMode a, PlayMode b) { return static_cast(a) & static_cast(b); } diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp new file mode 100644 index 0000000000..0e25ff6015 --- /dev/null +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -0,0 +1,154 @@ +#include "sound_buffer.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" + +#include +#include +#include + +#include +#include + +namespace MWSound +{ + namespace + { + struct AudioParams + { + float mAudioDefaultMinDistance; + float mAudioDefaultMaxDistance; + float mAudioMinDistanceMult; + float mAudioMaxDistanceMult; + }; + + AudioParams makeAudioParams(const MWBase::World& world) + { + const auto& settings = world.getStore().get(); + AudioParams params; + params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat(); + params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat(); + params.mAudioMinDistanceMult = settings.find("fAudioMinDistanceMult")->mValue.getFloat(); + params.mAudioMaxDistanceMult = settings.find("fAudioMaxDistanceMult")->mValue.getFloat(); + return params; + } + } + + SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) : + mVfs(&vfs), + mOutput(&output), + mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024), + mBufferCacheMin(std::min(static_cast(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax)) + { + } + + SoundBufferPool::~SoundBufferPool() + { + clear(); + } + + Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const + { + const auto it = mBufferNameMap.find(soundId); + if (it != mBufferNameMap.end()) + { + Sound_Buffer* sfx = it->second; + if (sfx->getHandle() != nullptr) + return sfx; + } + return nullptr; + } + + Sound_Buffer* SoundBufferPool::load(const std::string& soundId) + { + if (mBufferNameMap.empty()) + { + for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get()) + insertSound(Misc::StringUtils::lowerCase(sound.mId), sound); + } + + Sound_Buffer* sfx; + const auto it = mBufferNameMap.find(soundId); + if (it != mBufferNameMap.end()) + sfx = it->second; + else + { + const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get().search(soundId); + if (sound == nullptr) + return {}; + sfx = insertSound(soundId, *sound); + } + + if (sfx->getHandle() == nullptr) + { + Sound_Handle handle; + size_t size; + std::tie(handle, size) = mOutput->loadSound(sfx->getResourceName()); + if (handle == nullptr) + return {}; + + sfx->mHandle = handle; + + mBufferCacheSize += size; + if (mBufferCacheSize > mBufferCacheMax) + { + unloadUnused(); + if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax) + Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; + } + mUnusedBuffers.push_front(sfx); + } + + return sfx; + } + + void SoundBufferPool::clear() + { + for (auto &sfx : mSoundBuffers) + { + if(sfx.mHandle) + mOutput->unloadSound(sfx.mHandle); + sfx.mHandle = nullptr; + } + mUnusedBuffers.clear(); + } + + Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound) + { + static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld()); + + float volume = static_cast(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0)); + float min = sound.mData.mMinRange; + float max = sound.mData.mMaxRange; + if (min == 0 && max == 0) + { + min = audioParams.mAudioDefaultMinDistance; + max = audioParams.mAudioDefaultMaxDistance; + } + + min *= audioParams.mAudioMinDistanceMult; + max *= audioParams.mAudioMaxDistanceMult; + min = std::max(min, 1.0f); + max = std::max(min, max); + + Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); + mVfs->normalizeFilename(sfx.mResourceName); + + mBufferNameMap.emplace(soundId, &sfx); + return &sfx; + } + + void SoundBufferPool::unloadUnused() + { + while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin) + { + Sound_Buffer* const unused = mUnusedBuffers.back(); + + mBufferCacheSize -= mOutput->unloadSound(unused->getHandle()); + unused->mHandle = nullptr; + + mUnusedBuffers.pop_back(); + } + } +} diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 83b08d6be8..e623923003 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -1,27 +1,100 @@ #ifndef GAME_SOUND_SOUND_BUFFER_H #define GAME_SOUND_SOUND_BUFFER_H +#include #include +#include +#include #include "sound_output.hpp" +namespace ESM +{ + struct Sound; +} + +namespace VFS +{ + class Manager; +} + namespace MWSound { + class SoundBufferPool; + class Sound_Buffer { - public: - std::string mResourceName; + public: + Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) + : mResourceName(std::move(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) + {} + + const std::string& getResourceName() const noexcept { return mResourceName; } + + Sound_Handle getHandle() const noexcept { return mHandle; } + + float getVolume() const noexcept { return mVolume; } + + float getMinDist() const noexcept { return mMinDist; } + + float getMaxDist() const noexcept { return mMaxDist; } + + private: + std::string mResourceName; + float mVolume = 0; + float mMinDist = 0; + float mMaxDist = 0; + Sound_Handle mHandle = nullptr; + std::size_t mUses = 0; + + friend class SoundBufferPool; + }; + + class SoundBufferPool + { + public: + SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output); + + SoundBufferPool(const SoundBufferPool&) = delete; + + ~SoundBufferPool(); + + Sound_Buffer* lookup(const std::string& soundId) const; + + Sound_Buffer* load(const std::string& soundId); + + void use(Sound_Buffer& sfx) + { + if (sfx.mUses++ == 0) + { + const auto it = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), &sfx); + if (it != mUnusedBuffers.end()) + mUnusedBuffers.erase(it); + } + } + + void release(Sound_Buffer& sfx) + { + if (--sfx.mUses == 0) + mUnusedBuffers.push_front(&sfx); + } - float mVolume; - float mMinDist, mMaxDist; + void clear(); - Sound_Handle mHandle; + private: + const VFS::Manager* const mVfs; + Sound_Output* mOutput; + std::deque mSoundBuffers; + std::unordered_map mBufferNameMap; + std::size_t mBufferCacheMax; + std::size_t mBufferCacheMin; + std::size_t mBufferCacheSize = 0; + // NOTE: unused buffers are stored in front-newest order. + std::deque mUnusedBuffers; - size_t mUses; + inline Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound& sound); - Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) - : mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(nullptr), mUses(0) - { } + inline void unloadUnused(); }; } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 4075e36ccd..9ec8b17dc9 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -5,7 +5,7 @@ #include #include -#include "soundmanagerimp.hpp" +#include "../mwbase/soundmanager.hpp" namespace MWSound { @@ -25,6 +25,12 @@ namespace MWSound Auto }; + enum Environment + { + Env_Normal, + Env_Underwater + }; + class Sound_Output { SoundManager &mManager; @@ -81,6 +87,7 @@ namespace MWSound friend class OpenAL_Output; friend class SoundManager; + friend class SoundBufferPool; }; } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 37ba80820d..03ad58088a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -56,8 +56,7 @@ namespace MWSound : mVFS(vfs) , mOutput(new DEFAULT_OUTPUT(*this)) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) - , mSoundBuffers(new SoundBufferList::element_type()) - , mBufferCacheSize(0) + , mSoundBuffers(*vfs, *mOutput) , mListenerUnderwater(false) , mListenerPos(0,0,0) , mListenerDir(1,0,0) @@ -69,11 +68,6 @@ namespace MWSound , mLastCell(nullptr) , mCurrentRegionSound(nullptr) { - mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1); - mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1); - mBufferCacheMax *= 1024*1024; - mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax); - if(!useSound) { Log(Debug::Info) << "Sound disabled."; @@ -116,13 +110,7 @@ namespace MWSound SoundManager::~SoundManager() { clear(); - for(Sound_Buffer &sfx : *mSoundBuffers) - { - if(sfx.mHandle) - mOutput->unloadSound(sfx.mHandle); - sfx.mHandle = nullptr; - } - mUnusedBuffers.clear(); + mSoundBuffers.clear(); mOutput.reset(); } @@ -132,112 +120,18 @@ namespace MWSound return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - Sound_Buffer *SoundManager::insertSound(const std::string &soundId, const ESM::Sound *sound) - { - MWBase::World* world = MWBase::Environment::get().getWorld(); - static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->mValue.getFloat(); - static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->mValue.getFloat(); - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); - float volume, min, max; - - volume = static_cast(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); - min = sound->mData.mMinRange; - max = sound->mData.mMaxRange; - if (min == 0 && max == 0) - { - min = fAudioDefaultMinDistance; - max = fAudioDefaultMaxDistance; - } - - min *= fAudioMinDistanceMult; - max *= fAudioMaxDistanceMult; - min = std::max(min, 1.0f); - max = std::max(min, max); - - Sound_Buffer *sfx = &*mSoundBuffers->insert(mSoundBuffers->end(), - Sound_Buffer("Sound/"+sound->mSound, volume, min, max) - ); - mVFS->normalizeFilename(sfx->mResourceName); - - mBufferNameMap.insert(std::make_pair(soundId, sfx)); - - return sfx; - } - // Lookup a soundId for its sound data (resource name, local volume, // minRange, and maxRange) Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const { - NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(snd != mBufferNameMap.end()) - { - Sound_Buffer *sfx = snd->second; - if(sfx->mHandle) return sfx; - } - return nullptr; + return mSoundBuffers.lookup(soundId); } // Lookup a soundId for its sound data (resource name, local volume, // minRange, and maxRange), and ensure it's ready for use. Sound_Buffer *SoundManager::loadSound(const std::string &soundId) { -#ifdef __GNUC__ -#define LIKELY(x) __builtin_expect((bool)(x), true) -#define UNLIKELY(x) __builtin_expect((bool)(x), false) -#else -#define LIKELY(x) (bool)(x) -#define UNLIKELY(x) (bool)(x) -#endif - if(UNLIKELY(mBufferNameMap.empty())) - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - for(const ESM::Sound &sound : world->getStore().get()) - insertSound(Misc::StringUtils::lowerCase(sound.mId), &sound); - } - - Sound_Buffer *sfx; - NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(LIKELY(snd != mBufferNameMap.end())) - sfx = snd->second; - else - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const ESM::Sound *sound = world->getStore().get().search(soundId); - if(!sound) return nullptr; - sfx = insertSound(soundId, sound); - } -#undef LIKELY -#undef UNLIKELY - - if(!sfx->mHandle) - { - size_t size; - std::tie(sfx->mHandle, size) = mOutput->loadSound(sfx->mResourceName); - if(!sfx->mHandle) return nullptr; - - mBufferCacheSize += size; - if(mBufferCacheSize > mBufferCacheMax) - { - do { - if(mUnusedBuffers.empty()) - { - Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; - break; - } - Sound_Buffer *unused = mUnusedBuffers.back(); - - size = mOutput->unloadSound(unused->mHandle); - mBufferCacheSize -= size; - unused->mHandle = nullptr; - - mUnusedBuffers.pop_back(); - } while(mBufferCacheSize > mBufferCacheMin); - } - mUnusedBuffers.push_front(sfx); - } - - return sfx; + return mSoundBuffers.load(soundId); } DecoderPtr SoundManager::loadVoice(const std::string &voicefile) @@ -624,23 +518,18 @@ namespace MWSound SoundPtr sound = getSoundRef(); sound->init([&] { SoundParams params; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); - if(!mOutput->playSound(sound.get(), sfx->mHandle, offset)) + if(!mOutput->playSound(sound.get(), sfx->getHandle(), offset)) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -668,40 +557,35 @@ namespace MWSound { sound->init([&] { SoundParams params; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); - played = mOutput->playSound(sound.get(), sfx->mHandle, offset); + played = mOutput->playSound(sound.get(), sfx->getHandle(), offset); } else { sound->init([&] { SoundParams params; params.mPos = objpos; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; - params.mMinDistance = sfx->mMinDist; - params.mMaxDistance = sfx->mMaxDist; + params.mMinDistance = sfx->getMinDist(); + params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); - played = mOutput->playSound3D(sound.get(), sfx->mHandle, offset); + played = mOutput->playSound3D(sound.get(), sfx->getHandle(), offset); } if(!played) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[ptr].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -720,25 +604,20 @@ namespace MWSound sound->init([&] { SoundParams params; params.mPos = initialPos; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; - params.mMinDistance = sfx->mMinDist; - params.mMaxDistance = sfx->mMaxDist; + params.mMinDistance = sfx->getMinDist(); + params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); - if(!mOutput->playSound3D(sound.get(), sfx->mHandle, offset)) + if(!mOutput->playSound3D(sound.get(), sfx->getHandle(), offset)) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -921,7 +800,7 @@ namespace MWSound case WaterSoundAction::DoNothing: break; case WaterSoundAction::SetVolume: - mNearWaterSound->setVolume(update.mVolume * sfx->mVolume); + mNearWaterSound->setVolume(update.mVolume * sfx->getVolume()); break; case WaterSoundAction::FinishSound: mOutput->finishSound(mNearWaterSound); @@ -1028,7 +907,6 @@ namespace MWSound while(sndidx != snditer->second.end()) { Sound *sound = sndidx->first.get(); - Sound_Buffer *sfx = sndidx->second; if(!ptr.isEmpty() && sound->getIs3D()) { @@ -1050,8 +928,7 @@ namespace MWSound mUnderwaterSound = nullptr; if (sound == mNearWaterSound) mNearWaterSound = nullptr; - if(sfx->mUses-- == 1) - mUnusedBuffers.push_front(sfx); + mSoundBuffers.release(*sndidx->second); sndidx = snditer->second.erase(sndidx); } else @@ -1313,9 +1190,7 @@ namespace MWSound for(SoundBufferRefPair &sndbuf : snd.second) { mOutput->finishSound(sndbuf.first.get()); - Sound_Buffer *sfx = sndbuf.second; - if(sfx->mUses-- == 1) - mUnusedBuffers.push_front(sfx); + mSoundBuffers.release(*sndbuf.second); } } mActiveSounds.clear(); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index f69171a09e..21004dc94a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -18,6 +17,7 @@ #include "watersoundupdater.hpp" #include "type.hpp" #include "volumesettings.hpp" +#include "sound_buffer.hpp" namespace VFS { @@ -36,17 +36,6 @@ namespace MWSound struct Sound_Decoder; class Sound; class Stream; - class Sound_Buffer; - - enum Environment { - Env_Normal, - Env_Underwater - }; - // Extra play flags, not intended for caller use - enum PlayModeEx { - Play_2D = 0, - Play_3D = 1<<31 - }; using SoundPtr = Misc::ObjectPtr; using StreamPtr = Misc::ObjectPtr; @@ -66,21 +55,7 @@ namespace MWSound WaterSoundUpdater mWaterSoundUpdater; - typedef std::unique_ptr > SoundBufferList; - // List of sound buffers, grown as needed. New enties are added to the - // back, allowing existing Sound_Buffer references/pointers to remain - // valid. - SoundBufferList mSoundBuffers; - size_t mBufferCacheMin; - size_t mBufferCacheMax; - size_t mBufferCacheSize; - - typedef std::unordered_map NameBufferMap; - NameBufferMap mBufferNameMap; - - // NOTE: unused buffers are stored in front-newest order. - typedef std::deque SoundList; - SoundList mUnusedBuffers; + SoundBufferPool mSoundBuffers; Misc::ObjectPool mSounds; From 799bd3379c36d14635bf200f28f8fe7938bb7e1a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 8 Jan 2021 19:33:51 +0400 Subject: [PATCH 0178/2859] Move screenshots handling to the separate class --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/actionmanager.cpp | 10 +- apps/openmw/mwrender/renderingmanager.cpp | 290 +----------------- apps/openmw/mwrender/renderingmanager.hpp | 9 +- apps/openmw/mwrender/screenshotmanager.cpp | 324 +++++++++++++++++++++ apps/openmw/mwrender/screenshotmanager.hpp | 40 +++ apps/openmw/mwworld/worldimp.cpp | 6 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 9 files changed, 386 insertions(+), 299 deletions(-) create mode 100644 apps/openmw/mwrender/screenshotmanager.cpp create mode 100644 apps/openmw/mwrender/screenshotmanager.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a4c3b91361..f96ddb27f8 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -19,7 +19,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask - creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation + creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging ) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 958fcfb0ea..a3b035a8dd 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -497,7 +497,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual bool screenshot360 (osg::Image* image, std::string settingStr) = 0; + virtual bool screenshot360 (osg::Image* image) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index b29aa58a29..e0fcc5ccfc 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -333,12 +333,8 @@ namespace MWInput void ActionManager::screenshot() { - bool regularScreenshot = true; - - std::string settingStr; - - settingStr = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; + const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); + bool regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; if (regularScreenshot) { @@ -349,7 +345,7 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); - if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), settingStr)) + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) { (*mScreenCaptureOperation) (*(screenshot.get()), 0); // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 217f0b73ca..6ce431d2e2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -2,8 +2,6 @@ #include #include -#include -#include #include #include @@ -13,25 +11,20 @@ #include #include #include -#include -#include #include -#include - #include #include #include -#include - #include #include #include #include + #include #include @@ -74,7 +67,7 @@ #include "recastmesh.hpp" #include "fogmanager.hpp" #include "objectpaging.hpp" - +#include "screenshotmanager.hpp" namespace MWRender { @@ -311,6 +304,8 @@ namespace MWRender if (Settings::Manager::getBool("view over shoulder", "Camera")) mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get())); + mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); + mViewer->setLightingMode(osgViewer::View::NO_LIGHT); osg::ref_ptr source = new osg::LightSource; @@ -695,298 +690,31 @@ namespace MWRender mSky->setWaterHeight(height); } - class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback + void RenderingManager::screenshot(osg::Image* image, int w, int h) { - public: - NotifyDrawCompletedCallback(unsigned int frame) - : mDone(false), mFrame(frame) - { - } - - void operator () (osg::RenderInfo& renderInfo) const override - { - std::lock_guard lock(mMutex); - if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame) - { - mDone = true; - mCondition.notify_one(); - } - } - - void waitTillDone() - { - std::unique_lock lock(mMutex); - if (mDone) - return; - mCondition.wait(lock); - } - - mutable std::condition_variable mCondition; - mutable std::mutex mMutex; - mutable bool mDone; - unsigned int mFrame; - }; + mScreenshotManager->screenshot(image, w, h); + } - bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) + bool RenderingManager::screenshot360(osg::Image* image) { - int screenshotW = mViewer->getCamera()->getViewport()->width(); - int screenshotH = mViewer->getCamera()->getViewport()->height(); - int screenshotMapping = 0; - - std::vector settingArgs; - Misc::StringUtils::split(settingStr, settingArgs); - - if (settingArgs.size() > 0) - { - std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; - bool found = false; - - for (int i = 0; i < 4; ++i) - if (settingArgs[0].compare(typeStrings[i]) == 0) - { - screenshotMapping = i; - found = true; - break; - } - - if (!found) - { - Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; - return false; - } - } - - // planet mapping needs higher resolution - int cubeSize = screenshotMapping == 2 ? screenshotW : screenshotW / 2; - - if (settingArgs.size() > 1) - screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); - - if (settingArgs.size() > 2) - screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str())); - - if (settingArgs.size() > 3) - cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); - if (mCamera->isVanityOrPreviewModeEnabled()) { Log(Debug::Warning) << "Spherical screenshots are not allowed in preview mode."; return false; } - bool rawCubemap = screenshotMapping == 3; - - if (rawCubemap) - screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row - else if (screenshotMapping == 2) - screenshotH = screenshotW; // use square resolution for planet mapping - - std::vector> images; - - for (int i = 0; i < 6; ++i) - images.push_back(new osg::Image); - - osg::Vec3 directions[6] = { - rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), - osg::Vec3(0,0,-1), - osg::Vec3(-1,0,0), - rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), - osg::Vec3(0,1,0), - osg::Vec3(0,-1,0)}; - - double rotations[] = { - -osg::PI / 2.0, - osg::PI / 2.0, - osg::PI, - 0, - osg::PI / 2.0, - osg::PI / 2.0}; - - double fovBackup = mFieldOfView; - mFieldOfView = 90.0; // each cubemap side sees 90 degrees - int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); - for (int i = 0; i < 6; ++i) // for each cubemap side - { - osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]); - - if (!rawCubemap) - transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); - - osg::Image *sideImage = images[i].get(); - screenshot(sideImage,cubeSize,cubeSize,transform); - - if (!rawCubemap) - sideImage->flipHorizontal(); - } + mScreenshotManager->screenshot360(image); mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); - mFieldOfView = fovBackup; - - if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images - { - image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); - - for (int i = 0; i < 6; ++i) - osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); - - return true; - } - - // run on GPU now: - - osg::ref_ptr cubeTexture (new osg::TextureCubeMap); - cubeTexture->setResizeNonPowerOfTwoHint(false); - - cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); - cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); - - cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - for (int i = 0; i < 6; ++i) - cubeTexture->setImage(i,images[i].get()); - - osg::ref_ptr screenshotCamera (new osg::Camera); - osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); - - std::map defineMap; - - Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); - osg::ref_ptr fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT)); - osg::ref_ptr vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); - osg::ref_ptr stateset = new osg::StateSet; - - osg::ref_ptr program (new osg::Program); - program->addShader(fragmentShader); - program->addShader(vertexShader); - stateset->setAttributeAndModes(program, osg::StateAttribute::ON); - - stateset->addUniform(new osg::Uniform("cubeMap",0)); - stateset->addUniform(new osg::Uniform("mapping",screenshotMapping)); - stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); - - quad->setStateSet(stateset); - quad->setUpdateCallback(nullptr); - - screenshotCamera->addChild(quad); - - renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); return true; } - void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) - { - camera->setNodeMask(Mask_RenderToTexture); - camera->attach(osg::Camera::COLOR_BUFFER, image); - camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); - - camera->setViewport(0, 0, w, h); - - osg::ref_ptr texture (new osg::Texture2D); - texture->setInternalFormat(GL_RGB); - texture->setTextureSize(w,h); - texture->setResizeNonPowerOfTwoHint(false); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::COLOR_BUFFER,texture); - - image->setDataType(GL_UNSIGNED_BYTE); - image->setPixelFormat(texture->getInternalFormat()); - - mRootNode->addChild(camera); - - // The draw needs to complete before we can copy back our image. - osg::ref_ptr callback (new NotifyDrawCompletedCallback(0)); - camera->setFinalDrawCallback(callback); - - MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); - - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); - callback->waitTillDone(); - - MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); - - // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - - camera->removeChildren(0, camera->getNumChildren()); - mRootNode->removeChild(camera); - } - - class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback - { - public: - ReadImageFromFramebufferCallback(osg::Image* image, int width, int height) - : mWidth(width), mHeight(height), mImage(image) - { - } - void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override - { - int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); - int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); - double imageaspect = (double)mWidth/(double)mHeight; - int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); - int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); - int width = screenW - leftPadding*2; - int height = screenH - topPadding*2; - mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); - mImage->scaleImage(mWidth, mHeight, 1); - } - private: - int mWidth; - int mHeight; - osg::ref_ptr mImage; - }; - - void RenderingManager::screenshotFramebuffer(osg::Image* image, int w, int h) - { - osg::Camera* camera = mViewer->getCamera(); - osg::ref_ptr tempDrw = new osg::Drawable; - tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); - tempDrw->setCullingActive(false); - tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera - camera->addChild(tempDrw); - osg::ref_ptr callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber())); - camera->setFinalDrawCallback(callback); - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); - callback->waitTillDone(); - // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - camera->removeChild(tempDrw); - camera->setFinalDrawCallback(nullptr); - } - - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) - { - osg::ref_ptr rttCamera (new osg::Camera); - rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); - - rttCamera->setUpdateCallback(new NoTraverseCallback); - rttCamera->addChild(mSceneRoot); - - rttCamera->addChild(mWater->getReflectionCamera()); - rttCamera->addChild(mWater->getRefractionCamera()); - - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - - rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - renderCameraToImage(rttCamera.get(),image,w,h); - } - osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb) { if (!worldbb.valid()) return osg::Vec4f(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index ef28cf544e..39d1a0194e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -74,6 +74,7 @@ namespace MWRender class StateUpdater; class EffectManager; + class ScreenshotManager; class FogManager; class SkyManager; class NpcAnimation; @@ -148,9 +149,8 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); // make a new render at given size - void screenshotFramebuffer(osg::Image* image, int w, int h); // copy directly from framebuffer and scale to given size - bool screenshot360(osg::Image* image, std::string settingStr); + void screenshot(osg::Image* image, int w, int h); + bool screenshot360(osg::Image* image); struct RayResult { @@ -248,8 +248,6 @@ namespace MWRender void reportStats() const; - void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); - void updateNavMesh(); void updateRecastMesh(); @@ -281,6 +279,7 @@ namespace MWRender std::unique_ptr mObjectPaging; std::unique_ptr mSky; std::unique_ptr mFog; + std::unique_ptr mScreenshotManager; std::unique_ptr mEffectManager; std::unique_ptr mShadowManager; osg::ref_ptr mPlayerAnimation; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp new file mode 100644 index 0000000000..89b225da42 --- /dev/null +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -0,0 +1,324 @@ +#include "screenshotmanager.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "../mwgui/loadingscreen.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "util.hpp" +#include "vismask.hpp" +#include "water.hpp" + +namespace MWRender +{ + enum Screenshot360Type + { + Spherical, + Cylindrical, + Planet, + RawCubemap + }; + + class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback + { + public: + NotifyDrawCompletedCallback(unsigned int frame) + : mDone(false), mFrame(frame) + { + } + + void operator () (osg::RenderInfo& renderInfo) const override + { + std::lock_guard lock(mMutex); + if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame) + { + mDone = true; + mCondition.notify_one(); + } + } + + void waitTillDone() + { + std::unique_lock lock(mMutex); + if (mDone) + return; + mCondition.wait(lock); + } + + mutable std::condition_variable mCondition; + mutable std::mutex mMutex; + mutable bool mDone; + unsigned int mFrame; + }; + + class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback + { + public: + ReadImageFromFramebufferCallback(osg::Image* image, int width, int height) + : mWidth(width), mHeight(height), mImage(image) + { + } + void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override + { + int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); + int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); + double imageaspect = (double)mWidth/(double)mHeight; + int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); + int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); + int width = screenW - leftPadding*2; + int height = screenH - topPadding*2; + mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); + mImage->scaleImage(mWidth, mHeight, 1); + } + private: + int mWidth; + int mHeight; + osg::ref_ptr mImage; + }; + + ScreenshotManager::ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water) + : mViewer(viewer) + , mRootNode(rootNode) + , mSceneRoot(sceneRoot) + , mResourceSystem(resourceSystem) + , mWater(water) + { + } + + void ScreenshotManager::screenshot(osg::Image* image, int w, int h) + { + osg::Camera* camera = mViewer->getCamera(); + osg::ref_ptr tempDrw = new osg::Drawable; + tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); + tempDrw->setCullingActive(false); + tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera + camera->addChild(tempDrw); + osg::ref_ptr callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber())); + camera->setFinalDrawCallback(callback); + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + callback->waitTillDone(); + // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + camera->removeChild(tempDrw); + camera->setFinalDrawCallback(nullptr); + } + + bool ScreenshotManager::screenshot360(osg::Image* image) + { + int screenshotW = mViewer->getCamera()->getViewport()->width(); + int screenshotH = mViewer->getCamera()->getViewport()->height(); + Screenshot360Type screenshotMapping = Spherical; + + const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); + std::vector settingArgs; + Misc::StringUtils::split(settingStr, settingArgs); + + if (settingArgs.size() > 0) + { + std::string typeStrings[4] = {"spherical", "cylindrical", "planet", "cubemap"}; + bool found = false; + + for (int i = 0; i < 4; ++i) + { + if (settingArgs[0].compare(typeStrings[i]) == 0) + { + screenshotMapping = static_cast(i); + found = true; + break; + } + } + + if (!found) + { + Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; + return false; + } + } + + // planet mapping needs higher resolution + int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; + + if (settingArgs.size() > 1) + screenshotW = std::min(10000, std::atoi(settingArgs[1].c_str())); + + if (settingArgs.size() > 2) + screenshotH = std::min(10000, std::atoi(settingArgs[2].c_str())); + + if (settingArgs.size() > 3) + cubeSize = std::min(5000, std::atoi(settingArgs[3].c_str())); + + bool rawCubemap = screenshotMapping == RawCubemap; + + if (rawCubemap) + screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row + else if (screenshotMapping == Planet) + screenshotH = screenshotW; // use square resolution for planet mapping + + std::vector> images; + + for (int i = 0; i < 6; ++i) + images.push_back(new osg::Image); + + osg::Vec3 directions[6] = { + rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), + osg::Vec3(0,1,0), + osg::Vec3(0,-1,0)}; + + double rotations[] = { + -osg::PI / 2.0, + osg::PI / 2.0, + osg::PI, + 0, + osg::PI / 2.0, + osg::PI / 2.0 }; + + for (int i = 0; i < 6; ++i) // for each cubemap side + { + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1), directions[i]); + + if (!rawCubemap) + transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); + + osg::Image *sideImage = images[i].get(); + makeCubemapScreenshot(sideImage, cubeSize, cubeSize, transform); + + if (!rawCubemap) + sideImage->flipHorizontal(); + } + + if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images + { + image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); + + for (int i = 0; i < 6; ++i) + osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); + + return true; + } + + // run on GPU now: + osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + cubeTexture->setResizeNonPowerOfTwoHint(false); + + cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); + cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); + + cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i, images[i].get()); + + osg::ref_ptr screenshotCamera(new osg::Camera); + osg::ref_ptr quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0))); + + std::map defineMap; + + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); + osg::ref_ptr fragmentShader(shaderMgr.getShader("s360_fragment.glsl", defineMap,osg::Shader::FRAGMENT)); + osg::ref_ptr vertexShader(shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr program(new osg::Program); + program->addShader(fragmentShader); + program->addShader(vertexShader); + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("cubeMap", 0)); + stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); + stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); + + quad->setStateSet(stateset); + quad->setUpdateCallback(nullptr); + + screenshotCamera->addChild(quad); + + renderCameraToImage(screenshotCamera, image, screenshotW, screenshotH); + + return true; + } + + void ScreenshotManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) + { + camera->setNodeMask(Mask_RenderToTexture); + camera->attach(osg::Camera::COLOR_BUFFER, image); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); + + camera->setViewport(0, 0, w, h); + + osg::ref_ptr texture (new osg::Texture2D); + texture->setInternalFormat(GL_RGB); + texture->setTextureSize(w,h); + texture->setResizeNonPowerOfTwoHint(false); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + camera->attach(osg::Camera::COLOR_BUFFER,texture); + + image->setDataType(GL_UNSIGNED_BYTE); + image->setPixelFormat(texture->getInternalFormat()); + + mRootNode->addChild(camera); + + // The draw needs to complete before we can copy back our image. + osg::ref_ptr callback (new NotifyDrawCompletedCallback(0)); + camera->setFinalDrawCallback(callback); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + callback->waitTillDone(); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); + + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + + camera->removeChildren(0, camera->getNumChildren()); + mRootNode->removeChild(camera); + } + + void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) + { + osg::ref_ptr rttCamera (new osg::Camera); + float nearClip = Settings::Manager::getFloat("near clip", "Camera"); + float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); + // each cubemap side sees 90 degrees + rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); + + rttCamera->setUpdateCallback(new NoTraverseCallback); + rttCamera->addChild(mSceneRoot); + + rttCamera->addChild(mWater->getReflectionCamera()); + rttCamera->addChild(mWater->getRefractionCamera()); + + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); + + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderCameraToImage(rttCamera.get(),image,w,h); + } +} diff --git a/apps/openmw/mwrender/screenshotmanager.hpp b/apps/openmw/mwrender/screenshotmanager.hpp new file mode 100644 index 0000000000..2ac50bdf0c --- /dev/null +++ b/apps/openmw/mwrender/screenshotmanager.hpp @@ -0,0 +1,40 @@ +#ifndef MWRENDER_SCREENSHOTMANAGER_H +#define MWRENDER_SCREENSHOTMANAGER_H + +#include + +#include +#include + +#include + +namespace Resource +{ + class ResourceSystem; +} + +namespace MWRender +{ + class Water; + + class ScreenshotManager + { + public: + ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water); + + void screenshot(osg::Image* image, int w, int h); + bool screenshot360(osg::Image* image); + + private: + osg::ref_ptr mViewer; + osg::ref_ptr mRootNode; + osg::ref_ptr mSceneRoot; + Resource::ResourceSystem* mResourceSystem; + Water* mWater; + + void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); + void makeCubemapScreenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); + }; +} + +#endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8b95ee1221..bf5b6db738 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2536,12 +2536,12 @@ namespace MWWorld void World::screenshot(osg::Image* image, int w, int h) { - mRendering->screenshotFramebuffer(image, w, h); + mRendering->screenshot(image, w, h); } - bool World::screenshot360(osg::Image* image, std::string settingStr) + bool World::screenshot360(osg::Image* image) { - return mRendering->screenshot360(image,settingStr); + return mRendering->screenshot360(image); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 9f05014133..79c8a4980e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -597,7 +597,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - bool screenshot360 (osg::Image* image, std::string settingStr) override; + bool screenshot360 (osg::Image* image) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From c5a36ad440a55ebf999a2e9d452e5437b62180ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:19:41 +0400 Subject: [PATCH 0179/2859] Do not cast enums to booleans --- apps/launcher/advancedpage.cpp | 2 +- apps/launcher/graphicspage.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 6f59ade7ab..54fbbdfe01 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -108,7 +108,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); - if (animSourcesCheckBox->checkState()) + if (animSourcesCheckBox->checkState() != Qt::Unchecked) { loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game"); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index d1cf3aa6ff..01205043ec 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -206,7 +206,7 @@ void Launcher::GraphicsPage::saveSettings() if (cScreen != mEngineSettings.getInt("screen", "Video")) mEngineSettings.setInt("screen", "Video", cScreen); - if (framerateLimitCheckBox->checkState()) + if (framerateLimitCheckBox->checkState() != Qt::Unchecked) { float cFpsLimit = framerateLimitSpinBox->value(); if (cFpsLimit != mEngineSettings.getFloat("framerate limit", "Video")) @@ -217,7 +217,7 @@ void Launcher::GraphicsPage::saveSettings() mEngineSettings.setFloat("framerate limit", "Video", 0); } - int cShadowDist = shadowDistanceCheckBox->checkState() ? shadowDistanceSpinBox->value() : 0; + int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; if (mEngineSettings.getInt("maximum shadow map distance", "Shadows") != cShadowDist) mEngineSettings.setInt("maximum shadow map distance", "Shadows", cShadowDist); float cFadeStart = fadeStartSpinBox->value(); From 801e2d6ad0f66ce9646c06ff6b73793c54349ce4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:36:40 +0400 Subject: [PATCH 0180/2859] Avoid to use uninitialized variables --- apps/opencs/model/world/refidadapterimp.cpp | 2 +- apps/opencs/model/world/refidadapterimp.hpp | 2 +- components/files/constrainedfilestream.cpp | 2 +- components/nif/data.hpp | 10 +++++----- components/sceneutil/mwshadowtechnique.cpp | 2 ++ 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index d85fcc068f..d944adc23e 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -10,7 +10,7 @@ #include "nestedtablewrapper.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) -: InventoryColumns (columns) {} +: InventoryColumns (columns), mEffects(nullptr) {} CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const PotionColumns& columns, const RefIdColumn *autoCalc) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 7695e9acec..a73f76af9d 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -115,7 +115,7 @@ namespace CSMWorld { const RefIdColumn *mModel; - ModelColumns (const BaseColumns& base) : BaseColumns (base) {} + ModelColumns (const BaseColumns& base) : BaseColumns (base), mModel(nullptr) {} }; /// \brief Adapter for IDs with models (all but levelled lists) diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index baab1b081f..b9968038d1 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -21,7 +21,7 @@ namespace Files LowLevelFile mFile; - char mBuffer[sBufferSize]; + char mBuffer[sBufferSize]{0}; public: ConstrainedFileStreamBuf(const std::string &fname, size_t start, size_t length) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 66a391afc3..efbe138c53 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -71,7 +71,7 @@ struct NiParticlesData : public NiGeometryData { int numParticles{0}; - int activeCount; + int activeCount{0}; std::vector particleRadii, sizes, rotationAngles; std::vector rotations; @@ -119,14 +119,14 @@ struct NiPixelData : public Record NIPXFMT_DXT5, NIPXFMT_DXT5_ALT }; - Format fmt; + Format fmt{NIPXFMT_RGB8}; - unsigned int colorMask[4]; - unsigned int bpp, pixelTiling{0}; + unsigned int colorMask[4]{0}; + unsigned int bpp{0}, pixelTiling{0}; bool sRGB{false}; NiPalettePtr palette; - unsigned int numberOfMipmaps; + unsigned int numberOfMipmaps{0}; struct Mipmap { diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index c49a147776..294780cfd7 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -785,6 +785,7 @@ MWShadowTechnique::MWShadowTechnique(): _debugHud(nullptr) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; + mSetDummyStateWhenDisabled = false; } MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): @@ -792,6 +793,7 @@ MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::C { _shadowRecievingPlaceholderStateSet = new osg::StateSet; _enableShadows = vdsm._enableShadows; + mSetDummyStateWhenDisabled = vdsm.mSetDummyStateWhenDisabled; } MWShadowTechnique::~MWShadowTechnique() From 7fc4c9f3f69d81be16d2294a7c390859ae63e278 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 13:52:01 +0400 Subject: [PATCH 0181/2859] Avoid dead code --- apps/opencs/model/filter/parser.cpp | 6 ------ apps/opencs/view/render/terraintexturemode.cpp | 8 ++------ apps/openmw/mwgui/journalwindow.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 4 ++-- components/compiler/scanner.hpp | 11 ++++------- extern/oics/ICSInputControlSystem_joystick.cpp | 2 +- 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index d2a4f2a356..d363b4849d 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -325,12 +325,6 @@ std::shared_ptr CSMFilter::Parser::parseNAry (const Token& keyw break; } - if (nodes.empty()) - { - error(); - return std::shared_ptr(); - } - switch (keyword.mType) { case Token::Type_Keyword_And: return std::shared_ptr (new AndNode (nodes)); diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index c8d63f32e8..09d6b135a1 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -429,12 +429,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe { int distanceX(0); int distanceY(0); - if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i; - if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j; - if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize* abs(i_cell-cellX) + i; - if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j; - if (i_cell == cellX) distanceX = abs(i-xHitInCell); - if (j_cell == cellY) distanceY = abs(j-yHitInCell); + distanceX = abs(i-xHitInCell); + distanceY = abs(j-yHitInCell); float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 1474becf08..96c42549a5 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -561,7 +561,7 @@ namespace if (mAllQuests) { SetNamesInactive setInactive(list); - mModel->visitQuestNames(!mAllQuests, setInactive); + mModel->visitQuestNames(false, setInactive); } MWBase::Environment::get().getWindowManager()->playSound("book page"); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0f..b9ef50bf7f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -446,9 +446,9 @@ std::string CharacterController::fallbackShortWeaponGroup(const std::string& bas const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones - if (isRealWeapon && weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) + if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) groupName += twoHandFallback; - else if (isRealWeapon) + else groupName += oneHandFallback; // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 2139f04b26..9c7bd656ed 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -140,15 +140,12 @@ namespace Compiler for (int i = 0; i <= length; i++) { - if (length >= i) - { - in.get (ch); + in.get (ch); - if (!in.good()) - return false; + if (!in.good()) + return false; - mData[i] = ch; - } + mData[i] = ch; } mLength = length; diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp index 38199436ba..697d0ed3d3 100644 --- a/extern/oics/ICSInputControlSystem_joystick.cpp +++ b/extern/oics/ICSInputControlSystem_joystick.cpp @@ -286,7 +286,7 @@ namespace ICS //ControlAxisBinderItem joystickBinderItem = mControlsJoystickAxisBinderMap[ evt.which ][ axis ]; // joystic axis start at 0 index //Control* ctrl = joystickBinderItem.control; //if(ctrl && ctrl->isAxisBindable()) - if(mDetectingBindingControl && mDetectingBindingControl->isAxisBindable()) + if(mDetectingBindingControl->isAxisBindable()) { if( abs( evt.value ) > ICS_JOYSTICK_AXIS_BINDING_MARGIN) { From 8283ec6cadef7bcafdc4a830f2d60d00b26b6c9b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:03:48 +0400 Subject: [PATCH 0182/2859] Do not use & for boolean arguments --- apps/opencs/view/doc/view.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index be3fe51423..6fe01dc27d 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -438,8 +438,8 @@ void CSVDoc::View::updateActions() for (std::vector::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter) (*iter)->setEnabled (editing); - mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo()); - mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo()); + mUndo->setEnabled (editing && mDocument->getUndoStack().canUndo()); + mRedo->setEnabled (editing && mDocument->getUndoStack().canRedo()); mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running); mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying)); From 56666c60d44fa7809f8e9ab811827c4c908ce6d9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:17:59 +0400 Subject: [PATCH 0183/2859] Remove dead code --- apps/openmw/mwmechanics/weapontype.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp index 07345557f0..2f8e45f7ff 100644 --- a/apps/openmw/mwmechanics/weapontype.cpp +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -4,8 +4,6 @@ namespace MWMechanics { - static const ESM::WeaponType *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; - MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype) { MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); From c1512b8b6cc47a8615c8400d91ed913e0ae35a82 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:18:38 +0400 Subject: [PATCH 0184/2859] Convert loop to condition --- apps/openmw/mwworld/localscripts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 42914d4ac0..1661d6b9f7 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -76,7 +76,7 @@ void MWWorld::LocalScripts::startIteration() bool MWWorld::LocalScripts::getNext(std::pair& script) { - while (mIter!=mScripts.end()) + if (mIter!=mScripts.end()) { std::list >::iterator iter = mIter++; script = *iter; From 33648313a68efad40eebf4d218261b5d00845ea1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:21:57 +0400 Subject: [PATCH 0185/2859] Initialize variables --- apps/openmw/mwphysics/mtphysics.cpp | 3 +++ components/nif/node.hpp | 4 ++-- components/nif/property.hpp | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 0d1e5962a0..3e4ed9b478 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -152,6 +152,9 @@ namespace MWPhysics , mNextLOS(0) , mFrameNumber(0) , mTimer(osg::Timer::instance()) + , mTimeBegin(0) + , mTimeEnd(0) + , mFrameStart(0) { mNumThreads = Config::computeNumThreads(mThreadSafeBullet); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 1d082b8f9b..406a4d4549 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -177,7 +177,7 @@ struct Node : public Named // NiNodes (or types derived from NiNodes) can be parents. NiNode *parent; - bool isBone; + bool isBone{false}; void setBone() { @@ -378,7 +378,7 @@ struct NiCamera : Node struct NiSwitchNode : public NiNode { unsigned int switchFlags{0}; - unsigned int initialIndex; + unsigned int initialIndex{0}; void read(NIFStream *nif) override { diff --git a/components/nif/property.hpp b/components/nif/property.hpp index eccb442f7e..008e84515c 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -61,7 +61,7 @@ struct NiTexturingProperty : public Property 3 - hilight // These two are for PS2 only? 4 - hilight2 */ - unsigned int apply; + unsigned int apply{0}; /* * The textures in this list are as follows: @@ -193,7 +193,7 @@ struct S_MaterialProperty // The vector components are R,G,B osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f}; osg::Vec3f specular, emissive; - float glossiness, alpha; + float glossiness{0.f}, alpha{0.f}; void read(NIFStream *nif); }; From c9b885ffd4e3f01943d08488722abd7fbb539e61 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:24:04 +0400 Subject: [PATCH 0186/2859] Avoid possible null dereferencing --- apps/opencs/view/world/dragdroputils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp index 789d4f33dc..808125a601 100644 --- a/apps/opencs/view/world/dragdroputils.cpp +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -20,7 +20,8 @@ CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent { if (canAcceptData(event, type)) { - return getTableMimeData(event)->returnMatching(type); + if (const CSMWorld::TableMimeData *data = getTableMimeData(event)) + return data->returnMatching(type); } return CSMWorld::UniversalId::Type_None; } From 8e5f26c109da6e1b2b5d7126e3e66b6a61ce073d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:41:10 +0400 Subject: [PATCH 0187/2859] Code cleanup --- apps/opencs/view/world/dragrecordtable.cpp | 12 ++++-------- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwrender/globalmap.cpp | 2 +- components/terrain/terraindrawable.cpp | 2 +- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index d795bd5de1..f84bf639da 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -19,14 +19,10 @@ void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTa } CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument); - - if (mime) - { - QDrag* drag = new QDrag (this); - drag->setMimeData (mime); - drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); - drag->exec (Qt::CopyAction); - } + QDrag* drag = new QDrag (this); + drag->setMimeData (mime); + drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); + drag->exec (Qt::CopyAction); } CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) : diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index a4ab20fd68..45defe9a56 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -587,7 +587,7 @@ namespace MWGui // effect box can have variable width -> variable left coordinate int effectsDx = 0; if (!mMinimapBox->getVisible ()) - effectsDx = (viewSize.width - mMinimapBoxBaseRight) - (viewSize.width - mEffectBoxBaseRight); + effectsDx = mEffectBoxBaseRight - mMinimapBoxBaseRight; mMapVisible = mMinimapBox->getVisible (); if (!mMapVisible) diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index ba300accbc..366da6439c 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -583,7 +583,7 @@ namespace MWRender } mOverlayImage->copySubImage(imageDest.mX, imageDest.mY, 0, imageDest.mImage); - it = mPendingImageDest.erase(it); + mPendingImageDest.erase(it); return true; } } diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index 0d82be4fff..746534abb4 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -78,7 +78,7 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) osg::RefMatrix& matrix = *cv->getModelViewMatrix(); - if (cv->getComputeNearFarMode() && bb.valid()) + if (cv->getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR && bb.valid()) { if (!cv->updateCalculatedNearFar(matrix, *this, false)) return; From a80ee7a76a6ad94bc17fbda46b53affeb96968a6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 14:43:00 +0400 Subject: [PATCH 0188/2859] Avoid possible memory leak in the mInterMessageBoxe field --- apps/openmw/mwgui/messagebox.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index d64ec9c37a..5bd8ceb5ae 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -28,10 +28,7 @@ namespace MWGui MessageBoxManager::~MessageBoxManager () { - for (MessageBox* messageBox : mMessageBoxes) - { - delete messageBox; - } + MessageBoxManager::clear(); } int MessageBoxManager::getMessagesCount() From 24f8a2db27acf5ecb8c38b8022f4ba4006aee4d8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:07:30 +0100 Subject: [PATCH 0189/2859] Use perfect forwarding in Sound_Buffer ctor --- apps/openmw/mwsound/sound_buffer.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index e623923003..02e3c499b8 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -25,8 +25,9 @@ namespace MWSound class Sound_Buffer { public: - Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) - : mResourceName(std::move(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) + template + Sound_Buffer(T&& resname, float volume, float mindist, float maxdist) + : mResourceName(std::forward(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) {} const std::string& getResourceName() const noexcept { return mResourceName; } From 90c8e77e2ce75860ed7d674e3e068e164cec7a39 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:08:54 +0100 Subject: [PATCH 0190/2859] Remove redundant default Sound_Buffer fields initialization --- apps/openmw/mwsound/sound_buffer.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 02e3c499b8..61d569e131 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -42,9 +42,9 @@ namespace MWSound private: std::string mResourceName; - float mVolume = 0; - float mMinDist = 0; - float mMaxDist = 0; + float mVolume; + float mMinDist; + float mMaxDist; Sound_Handle mHandle = nullptr; std::size_t mUses = 0; From e4cce88142f383b8ef9dd086c23a545db57ce121 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:09:27 +0100 Subject: [PATCH 0191/2859] Replace std::tie by structured binding --- apps/openmw/mwsound/sound_buffer.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index 0e25ff6015..cb71cb56d2 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -82,9 +82,7 @@ namespace MWSound if (sfx->getHandle() == nullptr) { - Sound_Handle handle; - size_t size; - std::tie(handle, size) = mOutput->loadSound(sfx->getResourceName()); + auto [handle, size] = mOutput->loadSound(sfx->getResourceName()); if (handle == nullptr) return {}; From a6aba83741218241db323590cb2042232b2e7f9d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 14:11:49 +0100 Subject: [PATCH 0192/2859] Remove redundant SoundManager::lookupSound and loadSound --- apps/openmw/mwsound/sound_buffer.hpp | 4 ++++ apps/openmw/mwsound/soundmanagerimp.cpp | 28 +++++++------------------ apps/openmw/mwsound/soundmanagerimp.hpp | 3 --- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 61d569e131..5c45ac08aa 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -60,8 +60,12 @@ namespace MWSound ~SoundBufferPool(); + /// Lookup a soundId for its sound data (resource name, local volume, + /// minRange, and maxRange) Sound_Buffer* lookup(const std::string& soundId) const; + /// Lookup a soundId for its sound data (resource name, local volume, + /// minRange, and maxRange), and ensure it's ready for use. Sound_Buffer* load(const std::string& soundId); void use(Sound_Buffer& sfx) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 03ad58088a..44465e5a76 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -120,20 +120,6 @@ namespace MWSound return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - // Lookup a soundId for its sound data (resource name, local volume, - // minRange, and maxRange) - Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const - { - return mSoundBuffers.lookup(soundId); - } - - // Lookup a soundId for its sound data (resource name, local volume, - // minRange, and maxRange), and ensure it's ready for use. - Sound_Buffer *SoundManager::loadSound(const std::string &soundId) - { - return mSoundBuffers.load(soundId); - } - DecoderPtr SoundManager::loadVoice(const std::string &voicefile) { try @@ -509,7 +495,7 @@ namespace MWSound if(!mOutput->isInitialized()) return nullptr; - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; // Only one copy of given sound can be played at time, so stop previous copy @@ -545,7 +531,7 @@ namespace MWSound return nullptr; // Look up the sound in the ESM data - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; // Only one copy of given sound can be played at time on ptr, so stop previous copy @@ -597,7 +583,7 @@ namespace MWSound return nullptr; // Look up the sound in the ESM data - Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; SoundPtr sound = getSoundRef(); @@ -645,7 +631,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (!sfx) return; stopSound(sfx, ptr); @@ -697,7 +683,7 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); if (sfx == nullptr) return; for(SoundBufferRefPair &sndbuf : snditer->second) @@ -713,7 +699,7 @@ namespace MWSound SoundMap::const_iterator snditer = mActiveSounds.find(ptr); if(snditer != mActiveSounds.end()) { - Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + Sound_Buffer *sfx = mSoundBuffers.lookup(Misc::StringUtils::lowerCase(soundId)); return std::find_if(snditer->second.cbegin(), snditer->second.cend(), [this,sfx](const SoundBufferRefPair &snd) -> bool { return snd.second == sfx && mOutput->isSoundPlaying(snd.first.get()); } @@ -826,7 +812,7 @@ namespace MWSound bool soundIdChanged = false; - Sound_Buffer* sfx = lookupSound(update.mId); + Sound_Buffer* sfx = mSoundBuffers.lookup(update.mId); if (mLastCell != cell) { const auto snditer = mActiveSounds.find(MWWorld::ConstPtr()); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 21004dc94a..6b9de800f0 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -99,9 +99,6 @@ namespace MWSound Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); - Sound_Buffer *lookupSound(const std::string &soundId) const; - Sound_Buffer *loadSound(const std::string &soundId); - // returns a decoder to start streaming, or nullptr if the sound was not found DecoderPtr loadVoice(const std::string &voicefile); From 33da0af1d1760f5cd6b79a853aa0a6fe3fe848ff Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 18:25:48 +0400 Subject: [PATCH 0193/2859] Use explicit calls for virtual methods in constructors --- apps/openmw/mwrender/water.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b9018e0a27..e786ce9372 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -135,7 +135,7 @@ public: mClipNodeTransform = new osg::Group; mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); - addChild(mClipNodeTransform); + osg::Group::addChild(mClipNodeTransform); mClipNode = new osg::ClipNode; @@ -240,7 +240,7 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); - setName("RefractionCamera"); + osg::Camera::setName("RefractionCamera"); setCullCallback(new InheritViewPointCallback); setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); @@ -261,7 +261,7 @@ public: getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); mClipCullNode = new ClipCullNode; - addChild(mClipCullNode); + osg::Camera::addChild(mClipCullNode); mRefractionTexture = new osg::Texture2D; mRefractionTexture->setTextureSize(rttSize, rttSize); @@ -335,7 +335,7 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); - setName("ReflectionCamera"); + osg::Camera::setName("ReflectionCamera"); setCullCallback(new InheritViewPointCallback); setInterior(isInterior); @@ -364,7 +364,7 @@ public: getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); mClipCullNode = new ClipCullNode; - addChild(mClipCullNode); + osg::Camera::addChild(mClipCullNode); SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } From 80ee1b55ea636184ec38718fa2d7dd928e1c5306 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 18:28:26 +0400 Subject: [PATCH 0194/2859] Protect assignment operator from this == &src case --- apps/openmw/mwworld/inventorystore.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 38007c1cbb..fd05957034 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -120,6 +120,9 @@ MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) { + if (this == &store) + return *this; + mListener = store.mListener; mInventoryListener = store.mInventoryListener; mMagicEffects = store.mMagicEffects; From a2d8a0b61a18da0c85a55930d480fbec8fb202dd Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Jan 2021 14:44:15 +0000 Subject: [PATCH 0195/2859] engine.cpp typos --- apps/openmw/engine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8f23f710dd..6f501e5ad1 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -589,8 +589,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel."; if (traits->blue < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel."; - if (traits->depth < 8) - Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->red << " bits of depth precision."; + if (traits->depth < 24) + Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->depth << " bits of depth precision."; traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel } From 874348fb4681d4608d403e984ed9363972a19f4f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 19:19:38 +0400 Subject: [PATCH 0196/2859] Remove redundant code --- apps/openmw/mwmechanics/aiwander.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 68bcddf228..8e718061e2 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -99,7 +99,6 @@ namespace MWMechanics { AiPackage::Options options; options.mUseVariableSpeed = true; - options.mRepeat = false; return options; } From 50e4600b160174024654a8d4fe8f82b0bb1cc99b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 20:00:51 +0400 Subject: [PATCH 0197/2859] Reduce code duplication --- apps/opencs/view/world/util.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index ba9f408474..58d3d49e44 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -261,16 +261,10 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return dsb; } + /// \todo implement size limit. QPlainTextEdit does not support a size limit. case CSMWorld::ColumnBase::Display_LongString: - { - QPlainTextEdit *edit = new QPlainTextEdit(parent); - edit->setUndoRedoEnabled (false); - return edit; - } - case CSMWorld::ColumnBase::Display_LongString256: { - /// \todo implement size limit. QPlainTextEdit does not support a size limit. QPlainTextEdit *edit = new QPlainTextEdit(parent); edit->setUndoRedoEnabled (false); return edit; From 1930f8f37d45c246186d996fc9edc30c74d5fab3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 20:03:12 +0400 Subject: [PATCH 0198/2859] Fix copy-paste error --- apps/opencs/model/world/refidadapterimp.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index a73f76af9d..0a29afcad7 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1858,18 +1858,18 @@ namespace CSMWorld break; // always save case 16: if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); + content.mTravel.mX = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); + content.mTarget.mX = value.toFloat(); else return; // return without saving break; // always save case 17: if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); + content.mTravel.mY = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); + content.mTarget.mY = value.toFloat(); else return; // return without saving From 564a0d7d559622d4d8ff7baebdbf9e4c950eb160 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 9 Jan 2021 18:25:46 +0100 Subject: [PATCH 0199/2859] Don't nuke fog when bounds have changed --- CHANGELOG.md | 1 + apps/openmw/mwrender/localmap.cpp | 126 +++++++++++++++++------------- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa35722820..3dce266d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Bug #5441: Enemies can't push a player character when in critical strike stance Bug #5451: Magic projectiles don't disappear with the caster Bug #5452: Autowalk is being included in savegames + Bug #5469: Local map is reset when re-entering certain cells Bug #5472: Mistify mod causes CTD in 0.46 on Mac Bug #5479: NPCs who should be walking around town are standing around without walking Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 401e21ae46..5fa1a0e299 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -65,6 +65,15 @@ namespace return val*val; } + std::pair divideIntoSegments(const osg::BoundingBox& bounds, float mapSize) + { + osg::Vec2f min(bounds.xMin(), bounds.yMin()); + osg::Vec2f max(bounds.xMax(), bounds.yMax()); + osg::Vec2f length = max - min; + const int segsX = static_cast(std::ceil(length.x() / mapSize)); + const int segsY = static_cast(std::ceil(length.y() / mapSize)); + return {segsX, segsY}; + } } namespace MWRender @@ -127,12 +136,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) } else { - // FIXME: segmenting code duplicated from requestMap - osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - osg::Vec2f max(mBounds.xMax(), mBounds.yMax()); - osg::Vec2f length = max-min; - const int segsX = static_cast(std::ceil(length.x() / mMapWorldSize)); - const int segsY = static_cast(std::ceil(length.y() / mMapWorldSize)); + auto segments = divideIntoSegments(mBounds, mMapWorldSize); std::unique_ptr fog (new ESM::FogState()); @@ -142,11 +146,11 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) fog->mBounds.mMaxY = mBounds.yMax(); fog->mNorthMarkerAngle = mAngle; - fog->mFogTextures.reserve(segsX*segsY); + fog->mFogTextures.reserve(segments.first * segments.second); - for (int x=0; x> segmentMappings; if (cell->getFog()) { ESM::FogState* fog = cell->getFog(); - osg::Vec3f newMin (fog->mBounds.mMinX, fog->mBounds.mMinY, zMin); - osg::Vec3f newMax (fog->mBounds.mMaxX, fog->mBounds.mMaxY, zMax); - - osg::Vec3f minDiff = newMin - mBounds._min; - osg::Vec3f maxDiff = newMax - mBounds._max; - - if (std::abs(minDiff.x()) > padding || std::abs(minDiff.y()) > padding - || std::abs(maxDiff.x()) > padding || std::abs(maxDiff.y()) > padding - || std::abs(mAngle - fog->mNorthMarkerAngle) > osg::DegreesToRadians(5.f)) + if (std::abs(mAngle - fog->mNorthMarkerAngle) < osg::DegreesToRadians(5.f)) { - // Nuke it - cellHasValidFog = false; - } - else - { - // Looks sane, use it - mBounds = osg::BoundingBox(newMin, newMax); + // Expand mBounds so the saved textures fit the same grid + int xOffset = 0; + int yOffset = 0; + if(fog->mBounds.mMinX < mBounds.xMin()) + { + mBounds.xMin() = fog->mBounds.mMinX; + } + else if(fog->mBounds.mMinX > mBounds.xMin()) + { + float diff = fog->mBounds.mMinX - mBounds.xMin(); + xOffset += diff / mMapWorldSize; + xOffset++; + mBounds.xMin() = fog->mBounds.mMinX - xOffset * mMapWorldSize; + } + if(fog->mBounds.mMinY < mBounds.yMin()) + { + mBounds.yMin() = fog->mBounds.mMinY; + } + else if(fog->mBounds.mMinY > mBounds.yMin()) + { + float diff = fog->mBounds.mMinY - mBounds.yMin(); + yOffset += diff / mMapWorldSize; + yOffset++; + mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize; + } + mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX); + mBounds.yMax() = std::max(mBounds.yMax(), fog->mBounds.mMaxY); + + if(xOffset != 0 || yOffset != 0) + Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset; + + const auto& textures = fog->mFogTextures; + segmentMappings.reserve(textures.size()); + osg::BoundingBox savedBounds{ + fog->mBounds.mMinX, fog->mBounds.mMinY, 0, + fog->mBounds.mMaxX, fog->mBounds.mMaxY, 0 + }; + auto segments = divideIntoSegments(savedBounds, mMapWorldSize); + for (int x = 0; x < segments.first; ++x) + for (int y = 0; y < segments.second; ++y) + segmentMappings.emplace_back(std::make_pair(x + xOffset, y + yOffset)); + mAngle = fog->mNorthMarkerAngle; - cellHasValidFog = true; } } osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - osg::Vec2f max(mBounds.xMax(), mBounds.yMax()); - - osg::Vec2f length = max-min; - osg::Vec2f center(bounds.center().x(), bounds.center().y()); + osg::Vec2f center(mBounds.center().x(), mBounds.center().y()); + osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1)); - // divide into segments - const int segsX = static_cast(std::ceil(length.x() / mMapWorldSize)); - const int segsY = static_cast(std::ceil(length.y() / mMapWorldSize)); - - int i = 0; - for (int x=0; xgetFog(); - - // We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same. - if (i >= int(fog->mFogTextures.size())) + if(segmentMappings[index] == coords) { - Log(Debug::Warning) << "Warning: fog texture count mismatch"; + ESM::FogState* fog = cell->getFog(); + segment.loadFogOfWar(fog->mFogTextures[index]); + loaded = true; break; } - - segment.loadFogOfWar(fog->mFogTextures[i]); } + if(!loaded) + segment.initFogOfWar(); } - ++i; } } } From ad101de733b383c4f708079c31abe42fedc840ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 22:58:54 +0400 Subject: [PATCH 0200/2859] Merge declaration and initialization --- apps/opencs/view/render/terraintexturemode.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 09d6b135a1..f4a3f461c9 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -427,10 +427,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe { if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) { - int distanceX(0); - int distanceY(0); - distanceX = abs(i-xHitInCell); - distanceY = abs(j-yHitInCell); + int distanceX = abs(i-xHitInCell); + int distanceY = abs(j-yHitInCell); float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); float rf = static_cast(mBrushSize) / 2; if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; From 9bc687e20992cf5b7941343d7bce379f2c66b252 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 9 Jan 2021 20:56:20 +0100 Subject: [PATCH 0201/2859] Avoid a rare but possible deadlock around mCollisionWorldMutex. What happened is that the last handle to an Actor shared_ptr was a promoted weak_ptr. When the shared_ptr goes out of scope, the Actor dtor is invoked. That involves removing the Actor collision object after exclusively locking mCollisionWorldMutex. In this case, the lock was already held in the outter scope of the promoted weak_ptr. Reduce the scope of the mCollisionWorldMutex to never encompass the lifetime of a promoted weak_ptr. --- apps/openmw/mwphysics/mtphysics.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 0d1e5962a0..c77bc6acd5 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -357,7 +357,6 @@ namespace MWPhysics { if (!mDeferAabbUpdate || immediate) { - std::unique_lock lock(mCollisionWorldMutex); updatePtrAabb(ptr); } else @@ -410,7 +409,7 @@ namespace MWPhysics void PhysicsTaskScheduler::updateAabbs() { - std::scoped_lock lock(mCollisionWorldMutex, mUpdateAabbMutex); + std::scoped_lock lock(mUpdateAabbMutex); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), [this](const std::weak_ptr& ptr) { updatePtrAabb(ptr); }); mUpdateAabb.clear(); @@ -420,6 +419,7 @@ namespace MWPhysics { if (const auto p = ptr.lock()) { + std::scoped_lock lock(mCollisionWorldMutex); if (const auto actor = std::dynamic_pointer_cast(p)) { actor->updateCollisionObjectPosition(); @@ -451,9 +451,11 @@ namespace MWPhysics int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); if(const auto actor = mActorsFrameData[job].mActor.lock()) + { + MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData); + } } mPostStepBarrier->wait(); @@ -478,13 +480,13 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActorsPositions() { - std::unique_lock lock(mCollisionWorldMutex); for (auto& actorData : mActorsFrameData) { if(const auto actor = actorData.mActor.lock()) { if (actor->setPosition(actorData.mPosition)) { + std::scoped_lock lock(mCollisionWorldMutex); actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } From 60f66f5e29a43f8ef566d3855b5abc699e24527a Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 7 Jan 2021 11:00:57 +0100 Subject: [PATCH 0202/2859] Remove never used parameter from CharacterController:update() --- apps/openmw/mwmechanics/character.cpp | 13 ++++++------- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0f..5c50c547c6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1898,7 +1898,7 @@ void CharacterController::updateAnimQueue() mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } -void CharacterController::update(float duration, bool animationOnly) +void CharacterController::update(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Class &cls = mPtr.getClass(); @@ -2386,10 +2386,10 @@ void CharacterController::update(float duration, bool animationOnly) world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); } - if (!animationOnly && !mMovementAnimationControlled) + if (!mMovementAnimationControlled) world->queueMovement(mPtr, vec); } - else if (!animationOnly) + else // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); @@ -2414,8 +2414,7 @@ void CharacterController::update(float duration, bool animationOnly) playDeath(1.f, mDeathState); } // We must always queue movement, even if there is none, to apply gravity. - if (!animationOnly) - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } bool isPersist = isPersistentAnimPlaying(); @@ -2452,10 +2451,10 @@ void CharacterController::update(float duration, bool animationOnly) { moved.z() = 1.0; } - } + } // Update movement - if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor()) + if(mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); mSkipAnim = false; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 2308ba971d..0821b32250 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -248,7 +248,7 @@ public: void updatePtr(const MWWorld::Ptr &ptr); - void update(float duration, bool animationOnly=false); + void update(float duration); bool onOpen(); void onClose(); From 313e8959129c05145a16194c0893282cb27523ad Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 9 Jan 2021 21:35:07 +0000 Subject: [PATCH 0203/2859] [OpenMW-CS] Cube and sphere instance selection --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 2 +- apps/opencs/model/prefs/state.cpp | 15 + apps/opencs/view/render/cell.cpp | 47 +++ apps/opencs/view/render/cell.hpp | 7 + apps/opencs/view/render/instancedragmodes.hpp | 18 + apps/opencs/view/render/instancemode.cpp | 106 +++++- apps/opencs/view/render/instancemode.hpp | 16 +- .../view/render/instanceselectionmode.cpp | 347 +++++++++++++++++- .../view/render/instanceselectionmode.hpp | 32 +- .../view/render/pagedworldspacewidget.cpp | 16 + .../view/render/pagedworldspacewidget.hpp | 5 + apps/opencs/view/render/selectionmode.cpp | 21 +- .../view/render/unpagedworldspacewidget.cpp | 10 + .../view/render/unpagedworldspacewidget.hpp | 4 + apps/opencs/view/render/worldspacewidget.hpp | 5 + 16 files changed, 627 insertions(+), 25 deletions(-) create mode 100644 apps/opencs/view/render/instancedragmodes.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0cce9522..fa33b6647e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log + Feature #3171: OpenMW-CS: Instance drag selection Feature #4894: Consider actors as obstacles for pathfinding Feature #5043: Head Bobbing Feature #5199: Improve Scene Colors diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 69ad0cc1b9..fdf27dc932 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -21,7 +21,7 @@ New Features: - Basics of Collada animations are now supported via osgAnimation plugin (#5456) New Editor Features: -- ? +- Instance selection modes are now implemented (centred cube, corner-dragged cube, sphere) with four user-configurable actions (select only, add to selection, remove from selection, invert selection) (#3171) Bug Fixes: - NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 588be9ccb3..32b1ee33f8 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -247,6 +247,15 @@ void CSMPrefs::State::declare() EnumValues landeditOutsideVisibleCell; landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit); + EnumValue SelectOnly ("Select only"); + EnumValue SelectAdd ("Add to selection"); + EnumValue SelectRemove ("Remove from selection"); + EnumValue selectInvert ("Invert selection"); + EnumValues primarySelectAction; + primarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); + EnumValues secondarySelectAction; + secondarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); + declareCategory ("3D Scene Editing"); declareInt ("distance", "Drop Distance", 50). setTooltip ("If an instance drop can not be placed against another object at the " @@ -276,6 +285,12 @@ void CSMPrefs::State::declare() declareBool ("open-list-view", "Open displays list view", false). setTooltip ("When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); + declareEnum ("primary-select-action", "Action for primary select", SelectOnly). + setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). + addValues (primarySelectAction); + declareEnum ("secondary-select-action", "Action for secondary select", SelectAdd). + setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection."). + addValues (secondarySelectAction); declareCategory ("Key Bindings"); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 75d83cf639..2502dc1fd0 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,5 +1,7 @@ #include "cell.hpp" +#include + #include #include #include @@ -25,6 +27,7 @@ #include "pathgrid.hpp" #include "terrainstorage.hpp" #include "object.hpp" +#include "instancedragmodes.hpp" namespace CSVRender { @@ -496,6 +499,50 @@ void CSVRender::Cell::selectAllWithSameParentId (int elementMask) } } +void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode) +{ + if (dragMode == DragMode_Select_Only || dragMode == DragMode_Select_Add) + object->setSelected(true); + + else if (dragMode == DragMode_Select_Remove) + object->setSelected(false); + + else if (dragMode == DragMode_Select_Invert) + object->setSelected (!object->getSelected()); +} + +void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + for (auto& object : mObjects) + { + if (dragMode == DragMode_Select_Only) object.second->setSelected (false); + + if ( ( object.second->getPosition().pos[0] > pointA[0] && object.second->getPosition().pos[0] < pointB[0] ) || + ( object.second->getPosition().pos[0] > pointB[0] && object.second->getPosition().pos[0] < pointA[0] )) + { + if ( ( object.second->getPosition().pos[1] > pointA[1] && object.second->getPosition().pos[1] < pointB[1] ) || + ( object.second->getPosition().pos[1] > pointB[1] && object.second->getPosition().pos[1] < pointA[1] )) + { + if ( ( object.second->getPosition().pos[2] > pointA[2] && object.second->getPosition().pos[2] < pointB[2] ) || + ( object.second->getPosition().pos[2] > pointB[2] && object.second->getPosition().pos[2] < pointA[2] )) + handleSelectDrag(object.second, dragMode); + } + + } + } +} + +void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + for (auto& object : mObjects) + { + if (dragMode == DragMode_Select_Only) object.second->setSelected (false); + + float distanceFromObject = (point - object.second->getPosition().asVec3()).length(); + if (distanceFromObject < distance) handleSelectDrag(object.second, dragMode); + } +} + void CSVRender::Cell::setCellArrows (int mask) { for (int i=0; i<4; ++i) diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 281ac67356..5998a4ee62 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -10,6 +10,7 @@ #include "../../model/world/cellcoordinates.hpp" #include "terrainstorage.hpp" +#include "instancedragmodes.hpp" class QModelIndex; @@ -152,6 +153,12 @@ namespace CSVRender // already selected void selectAllWithSameParentId (int elementMask); + void handleSelectDrag(Object* object, DragMode dragMode); + + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); + + void selectWithinDistance(const osg::Vec3d& pointA, float distance, DragMode dragMode); + void setCellArrows (int mask); /// \brief Set marker for this cell. diff --git a/apps/opencs/view/render/instancedragmodes.hpp b/apps/opencs/view/render/instancedragmodes.hpp new file mode 100644 index 0000000000..01547545ae --- /dev/null +++ b/apps/opencs/view/render/instancedragmodes.hpp @@ -0,0 +1,18 @@ +#ifndef CSV_WIDGET_INSTANCEDRAGMODES_H +#define CSV_WIDGET_INSTANCEDRAGMODES_H + +namespace CSVRender +{ + enum DragMode + { + DragMode_None, + DragMode_Move, + DragMode_Rotate, + DragMode_Scale, + DragMode_Select_Only, + DragMode_Select_Add, + DragMode_Select_Remove, + DragMode_Select_Invert + }; +} +#endif diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 19018fae6c..4f6759cdb8 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -96,6 +96,33 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) return pos * combined; } +osg::Vec3f CSVRender::InstanceMode::getProjectionSpaceCoords(const osg::Vec3f& pos) +{ + osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix(); + osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix(); + osg::Matrix combined = viewMatrix * projMatrix; + + return pos * combined; +} + +osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart) +{ + osg::Matrix viewMatrix; + viewMatrix.invert(getWorldspaceWidget().getCamera()->getViewMatrix()); + osg::Matrix projMatrix; + projMatrix.invert(getWorldspaceWidget().getCamera()->getProjectionMatrix()); + osg::Matrix combined = projMatrix * viewMatrix; + + /* calculate viewport normalized coordinates + note: is there a reason to use getCamera()->getViewport()->computeWindowMatrix() instead? */ + float x = (point.x() * 2) / getWorldspaceWidget().getCamera()->getViewport()->width() - 1.0f; + float y = 1.0f - (point.y() * 2) / getWorldspaceWidget().getCamera()->getViewport()->height(); + + osg::Vec3f mousePlanePoint = osg::Vec3f(x, y, dragStart.z()) * combined; + + return mousePlanePoint; +} + CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing", parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None), @@ -146,7 +173,7 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) } if (!mSelectionMode) - mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget()); + mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget(), mParentNode); mDragMode = DragMode_None; @@ -322,6 +349,42 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) return false; } +bool CSVRender::InstanceMode::primarySelectStartDrag (const QPoint& pos) +{ + if (mDragMode!=DragMode_None || mLocked) + return false; + + std::string primarySelectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); + + if ( primarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; + else if ( primarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; + else if ( primarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; + else if ( primarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mSelectionMode->setDragStart(hit.worldPos); + + return true; +} + +bool CSVRender::InstanceMode::secondarySelectStartDrag (const QPoint& pos) +{ + if (mDragMode!=DragMode_None || mLocked) + return false; + + std::string secondarySelectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); + + if ( secondarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only; + else if ( secondarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add; + else if ( secondarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove; + else if ( secondarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mSelectionMode->setDragStart(hit.worldPos); + + return true; +} + void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) { osg::Vec3f offset; @@ -432,6 +495,24 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou // Only uniform scaling is currently supported offset = osg::Vec3f(scale, scale, scale); } + else if (mSelectionMode->getCurrentId() == "cube-centre") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionCubeCentre (mousePlanePoint); + return; + } + else if (mSelectionMode->getCurrentId() == "cube-corner") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionCubeCorner (mousePlanePoint); + return; + } + else if (mSelectionMode->getCurrentId() == "sphere") + { + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->drawSelectionSphere (mousePlanePoint); + return; + } // Apply for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) @@ -495,6 +576,22 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) case DragMode_Move: description = "Move Instances"; break; case DragMode_Rotate: description = "Rotate Instances"; break; case DragMode_Scale: description = "Scale Instances"; break; + case DragMode_Select_Only : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Add : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Remove : + handleSelectDrag(pos); + return; + break; + case DragMode_Select_Invert : + handleSelectDrag(pos); + return; + break; case DragMode_None: break; } @@ -680,6 +777,13 @@ void CSVRender::InstanceMode::subModeChanged (const std::string& id) getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference); } +void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos) +{ + osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart())); + mSelectionMode->dragEnded (mousePlanePoint, mDragMode); + mDragMode = DragMode_None; +} + void CSVRender::InstanceMode::deleteSelectedInstances(bool active) { std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 32dd4ac67d..0a4f2e4788 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -9,6 +9,7 @@ #include #include "editmode.hpp" +#include "instancedragmodes.hpp" namespace CSVWidget { @@ -25,14 +26,6 @@ namespace CSVRender { Q_OBJECT - enum DragMode - { - DragMode_None, - DragMode_Move, - DragMode_Rotate, - DragMode_Scale - }; - enum DropMode { Collision, @@ -57,6 +50,9 @@ namespace CSVRender osg::Vec3f getSelectionCenter(const std::vector >& selection) const; osg::Vec3f getScreenCoords(const osg::Vec3f& pos); + osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); + osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); + void handleSelectDrag(const QPoint& pos); void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight); float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); @@ -84,6 +80,10 @@ namespace CSVRender bool secondaryEditStartDrag (const QPoint& pos) override; + bool primarySelectStartDrag(const QPoint& pos) override; + + bool secondarySelectStartDrag(const QPoint& pos) override; + void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override; void dragCompleted(const QPoint& pos) override; diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index bf8ede0eb4..9b5fb759cc 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -2,17 +2,24 @@ #include #include +#include + +#include +#include +#include +#include #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" +#include "instancedragmodes.hpp" #include "worldspacewidget.hpp" #include "object.hpp" namespace CSVRender { - InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) - : SelectionMode(parent, worldspaceWidget, Mask_Reference) + InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode) + : SelectionMode(parent, worldspaceWidget, Mask_Reference), mParentNode(cellNode) { mSelectSame = new QAction("Extend selection to instances with same object ID", this); mDeleteSelection = new QAction("Delete selected instances", this); @@ -21,6 +28,342 @@ namespace CSVRender connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection())); } + InstanceSelectionMode::~InstanceSelectionMode() + { + mParentNode->removeChild(mBaseNode); + } + + void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart) + { + mDragStart = dragStart; + } + + const osg::Vec3d& InstanceSelectionMode::getDragStart() + { + return mDragStart; + } + + void InstanceSelectionMode::dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode) + { + float dragDistance = (mDragStart - dragEndPoint).length(); + if (mBaseNode) mParentNode->removeChild (mBaseNode); + if (getCurrentId() == "cube-centre") + { + osg::Vec3d pointA(mDragStart[0] - dragDistance, mDragStart[1] - dragDistance, mDragStart[2] - dragDistance); + osg::Vec3d pointB(mDragStart[0] + dragDistance, mDragStart[1] + dragDistance, mDragStart[2] + dragDistance); + getWorldspaceWidget().selectInsideCube(pointA, pointB, dragMode); + } + else if (getCurrentId() == "cube-corner") + { + getWorldspaceWidget().selectInsideCube(mDragStart, dragEndPoint, dragMode); + } + else if (getCurrentId() == "sphere") + { + getWorldspaceWidget().selectWithinDistance(mDragStart, dragDistance, dragMode); + } + } + + void InstanceSelectionMode::drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint) + { + float dragDistance = (mDragStart - mousePlanePoint).length(); + drawSelectionCube(mDragStart, dragDistance); + } + + void InstanceSelectionMode::drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint) + { + drawSelectionBox(mDragStart, mousePlanePoint); + } + + void InstanceSelectionMode::drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(pointA); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + vertices->push_back (osg::Vec3f (0.0f, 0.0f, 0.0f)); + vertices->push_back (osg::Vec3f (0.0f, 0.0f, pointB[2] - pointA[2])); + vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], 0.0f)); + vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], pointB[2] - pointA[2])); + + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, 0.0f)); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, pointB[2] - pointA[2])); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], 0.0f)); + vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2])); + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // top + primitives->push_back (2); + primitives->push_back (1); + primitives->push_back (0); + + primitives->push_back (3); + primitives->push_back (1); + primitives->push_back (2); + + // bottom + primitives->push_back (4); + primitives->push_back (5); + primitives->push_back (6); + + primitives->push_back (6); + primitives->push_back (5); + primitives->push_back (7); + + // sides + primitives->push_back (1); + primitives->push_back (4); + primitives->push_back (0); + + primitives->push_back (4); + primitives->push_back (1); + primitives->push_back (5); + + primitives->push_back (4); + primitives->push_back (2); + primitives->push_back (0); + + primitives->push_back (6); + primitives->push_back (2); + primitives->push_back (4); + + primitives->push_back (6); + primitives->push_back (3); + primitives->push_back (2); + + primitives->push_back (7); + primitives->push_back (3); + primitives->push_back (6); + + primitives->push_back (1); + primitives->push_back (3); + primitives->push_back (5); + + primitives->push_back (5); + primitives->push_back (3); + primitives->push_back (7); + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + + void InstanceSelectionMode::drawSelectionCube(const osg::Vec3d& point, float radius) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(point); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + for (int i = 0; i < 2; ++i) + { + float height = i ? -radius : radius; + vertices->push_back (osg::Vec3f (height, -radius, -radius)); + vertices->push_back (osg::Vec3f (height, -radius, radius)); + vertices->push_back (osg::Vec3f (height, radius, -radius)); + vertices->push_back (osg::Vec3f (height, radius, radius)); + } + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // top + primitives->push_back (2); + primitives->push_back (1); + primitives->push_back (0); + + primitives->push_back (3); + primitives->push_back (1); + primitives->push_back (2); + + // bottom + primitives->push_back (4); + primitives->push_back (5); + primitives->push_back (6); + + primitives->push_back (6); + primitives->push_back (5); + primitives->push_back (7); + + // sides + primitives->push_back (1); + primitives->push_back (4); + primitives->push_back (0); + + primitives->push_back (4); + primitives->push_back (1); + primitives->push_back (5); + + primitives->push_back (4); + primitives->push_back (2); + primitives->push_back (0); + + primitives->push_back (6); + primitives->push_back (2); + primitives->push_back (4); + + primitives->push_back (6); + primitives->push_back (3); + primitives->push_back (2); + + primitives->push_back (7); + primitives->push_back (3); + primitives->push_back (6); + + primitives->push_back (1); + primitives->push_back (3); + primitives->push_back (5); + + primitives->push_back (5); + primitives->push_back (3); + primitives->push_back (7); + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f)); + colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + + void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3f& mousePlanePoint) + { + float dragDistance = (mDragStart - mousePlanePoint).length(); + drawSelectionSphere(mDragStart, dragDistance); + } + + void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3d& point, float radius) + { + if (mBaseNode) mParentNode->removeChild (mBaseNode); + mBaseNode = new osg::PositionAttitudeTransform; + mBaseNode->setPosition(point); + + osg::ref_ptr geometry (new osg::Geometry); + + osg::Vec3Array *vertices = new osg::Vec3Array; + int resolution = 32; + float radiusPerResolution = radius / resolution; + float reciprocalResolution = 1.0f / resolution; + float doubleReciprocalRes = reciprocalResolution * 2; + + osg::Vec4Array *colours = new osg::Vec4Array; + + for (float i = 0.0; i <= resolution; i += 2) + { + float iShifted = (static_cast(i) - resolution / 2.0f); // i - 16 = -16 ... 16 + float xPercentile = iShifted * doubleReciprocalRes; + float x = xPercentile * radius; + float thisRadius = sqrt (radius * radius - x * x); + + //the next row + float iShifted2 = (static_cast(i + 1) - resolution / 2.0f); + float xPercentile2 = iShifted2 * doubleReciprocalRes; + float x2 = xPercentile2 * radius; + float thisRadius2 = sqrt (radius * radius - x2 * x2); + + for (int j = 0; j < resolution; ++j) + { + float vertexX = thisRadius * sin(j * reciprocalResolution * osg::PI * 2); + float vertexY = i * radiusPerResolution * 2 - radius; + float vertexZ = thisRadius * cos(j * reciprocalResolution * osg::PI * 2); + float heightPercentage = (vertexZ + radius) / (radius * 2); + vertices->push_back (osg::Vec3f (vertexX, vertexY, vertexZ)); + colours->push_back (osg::Vec4f (heightPercentage, heightPercentage, heightPercentage, 0.3f)); + + float vertexNextRowX = thisRadius2 * sin(j * reciprocalResolution * osg::PI * 2); + float vertexNextRowY = (i + 1) * radiusPerResolution * 2 - radius; + float vertexNextRowZ = thisRadius2 * cos(j * reciprocalResolution * osg::PI * 2); + float heightPercentageNextRow = (vertexZ + radius) / (radius * 2); + vertices->push_back (osg::Vec3f (vertexNextRowX, vertexNextRowY, vertexNextRowZ)); + colours->push_back (osg::Vec4f (heightPercentageNextRow, heightPercentageNextRow, heightPercentageNextRow, 0.3f)); + } + } + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLE_STRIP, 0); + + for (int i = 0; i < resolution; ++i) + { + //Even + for (int j = 0; j < resolution * 2; ++j) + { + if (i * resolution * 2 + j > static_cast(vertices->size()) - 1) continue; + primitives->push_back (i * resolution * 2 + j); + } + if (i * resolution * 2 > static_cast(vertices->size()) - 1) continue; + primitives->push_back (i * resolution * 2); + primitives->push_back (i * resolution * 2 + 1); + + //Odd + for (int j = 1; j < resolution * 2 - 2; j += 2) + { + if ((i + 1) * resolution * 2 + j - 1 > static_cast(vertices->size()) - 1) continue; + primitives->push_back ((i + 1) * resolution * 2 + j - 1); + primitives->push_back (i * resolution * 2 + j + 2); + } + if ((i + 2) * resolution * 2 - 2 > static_cast(vertices->size()) - 1) continue; + primitives->push_back ((i + 2) * resolution * 2 - 2); + primitives->push_back (i * resolution * 2 + 1); + primitives->push_back ((i + 1) * resolution * 2); + } + + geometry->addPrimitiveSet (primitives); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON); + geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + mBaseNode->addChild (geometry); + mParentNode->addChild(mBaseNode); + } + bool InstanceSelectionMode::createContextMenu(QMenu* menu) { if (menu) diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index a238116710..81795d5d3e 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -1,7 +1,13 @@ #ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H #define CSV_RENDER_INSTANCE_SELECTION_MODE_H +#include + +#include +#include + #include "selectionmode.hpp" +#include "instancedragmodes.hpp" namespace CSVRender { @@ -11,8 +17,25 @@ namespace CSVRender public: - InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode); + + ~InstanceSelectionMode(); + + /// Store the worldspace-coordinate when drag begins + void setDragStart(const osg::Vec3d& dragStart); + /// Store the worldspace-coordinate when drag begins + const osg::Vec3d& getDragStart(); + + /// Store the screen-coordinate when drag begins + void setScreenDragStart(const QPoint& dragStartPoint); + + /// Apply instance selection changes + void dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode); + + void drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint ); + void drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint ); + void drawSelectionSphere(const osg::Vec3f& mousePlanePoint ); protected: /// Add context menu items to \a menu. @@ -25,8 +48,15 @@ namespace CSVRender private: + void drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB); + void drawSelectionCube(const osg::Vec3d& point, float radius); + void drawSelectionSphere(const osg::Vec3d& point, float radius); + QAction* mDeleteSelection; QAction* mSelectSame; + osg::Vec3d mDragStart; + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; private slots: diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index dca5549af4..ed35584226 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -768,6 +768,22 @@ void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMas flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + for (auto& cell : mCells) + { + cell.second->selectInsideCube (pointA, pointB, dragMode); + } +} + +void CSVRender::PagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + for (auto& cell : mCells) + { + cell.second->selectWithinDistance (point, distance, dragMode); + } +} + std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { CSMWorld::CellCoordinates cellCoordinates ( diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index d17670cfa7..beab0c575b 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -7,6 +7,7 @@ #include "worldspacewidget.hpp" #include "cell.hpp" +#include "instancedragmodes.hpp" namespace CSVWidget { @@ -120,6 +121,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation void selectAllWithSameParentId (int elementMask) override; + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; + + void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; + std::string getCellId (const osg::Vec3f& point) const override; Cell* getCell(const osg::Vec3d& point) const override; diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp index b5ccda5ad4..e7e7d47b5a 100644 --- a/apps/opencs/view/render/selectionmode.cpp +++ b/apps/opencs/view/render/selectionmode.cpp @@ -15,30 +15,27 @@ namespace CSVRender { addButton(":scenetoolbar/selection-mode-cube", "cube-centre", "Centred cube" - "

  • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " - "(invert selection state) from the centre of the selection cube outwards
  • " + "
    • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " + "from the centre of the selection cube outwards.
    • " "
    • The selection cube is aligned to the word space axis
    • " "
    • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
    • " - "
    " - "Not implemented yet"); + "
"); addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner", "Cube corner to corner" - "
  • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " - "(invert selection state) from one corner of the selection cube to the opposite corner
  • " + "
    • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " + "from one corner of the selection cube to the opposite corner
    • " "
    • The selection cube is aligned to the word space axis
    • " "
    • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
    • " - "
    " - "Not implemented yet"); + "
"); addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere", "Centred sphere" - "
  • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " - "(invert selection state) from the centre of the selection sphere outwards
  • " + "
    • Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select " + "from the centre of the selection sphere outwards
    • " "
    • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " "starting on an instance will have the same effect
    • " - "
    " - "Not implemented yet"); + "
"); mSelectAll = new QAction("Select all", this); mDeselectAll = new QAction("Clear selection", this); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index b1088aa60e..c4fef45dd7 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -140,6 +140,16 @@ void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementM flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) +{ + mCell->selectInsideCube (pointA, pointB, dragMode); +} + +void CSVRender::UnpagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) +{ + mCell->selectWithinDistance (point, distance, dragMode); +} + std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { return mCellId; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index eec1b01f34..83233c3270 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -60,6 +60,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation void selectAllWithSameParentId (int elementMask) override; + void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override; + + void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override; + std::string getCellId (const osg::Vec3f& point) const override; Cell* getCell(const osg::Vec3d& point) const override; diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 3b8cf70c2a..5e224b3803 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -7,6 +7,7 @@ #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" +#include "instancedragmodes.hpp" #include "scenewidget.hpp" #include "mask.hpp" @@ -160,6 +161,10 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation virtual void selectAllWithSameParentId (int elementMask) = 0; + virtual void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) = 0; + + virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0; + /// Return the next intersection with scene elements matched by /// \a interactionMask based on \a localPos and the camera vector. /// If there is no such intersection, instead a point "in front" of \a localPos will be From 7b54415c40a184b2cd4c41e726f63d61ad3ab41a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 21:38:56 +0100 Subject: [PATCH 0204/2859] Fix reference binding to null /usr/include/c++/10.2.0/bits/stl_vector.h:1046:34: runtime error: reference binding to null pointer of type 'value_type' #0 0x55e37f50008a in std::vector >::operator[](unsigned long) /usr/include/c++/10.2.0/bits/stl_vector.h:1046 #1 0x55e37f50008a in ESM::SavedGame::load(ESM::ESMReader&) /home/elsid/dev/openmw/components/esm/savedgame.cpp:28 #2 0x55e37e726139 in MWState::Character::addSlot(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/character.cpp:31 #3 0x55e37e742b39 in MWState::Character::Character(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/character.cpp:88 #4 0x55e37e7006e1 in MWState::CharacterManager::CharacterManager(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/charactermanager.cpp:25 #5 0x55e37e6d4140 in MWState::StateManager::StateManager(boost::filesystem::path const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:90 #6 0x55e37e82595a in OMW::Engine::prepareEngine(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:641 #7 0x55e37e8439fd in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:867 #8 0x55e37e782760 in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:289 #9 0x55e37f6483c3 in wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:200 #10 0x55e37ba8e3fe in main /home/elsid/dev/openmw/apps/openmw/main.cpp:301 #11 0x7f013e845151 in __libc_start_main (/usr/lib/libc.so.6+0x28151) #12 0x55e37baa0e3d in _start (/home/elsid/dev/openmw/build/gcc/ubsan/openmw+0x6c11e3d) --- components/esm/savedgame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 9edcb1a671..7cb30f2dd2 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -25,7 +25,7 @@ void ESM::SavedGame::load (ESMReader &esm) esm.getSubNameIs("SCRN"); esm.getSubHeader(); mScreenshot.resize(esm.getSubSize()); - esm.getExact(&mScreenshot[0], mScreenshot.size()); + esm.getExact(mScreenshot.data(), mScreenshot.size()); } void ESM::SavedGame::save (ESMWriter &esm) const From d2d8a7a940b47660f7bd51cc065fbf13119c0179 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Jan 2021 21:58:27 +0100 Subject: [PATCH 0205/2859] Fix passing null to memcpy /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:36:24: runtime error: null pointer passed as argument 2, which is declared to never be null #0 0x55e37ba4cda5 in makeNavMeshKey /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:36 #1 0x55e37ba4cda5 in DetourNavigator::NavMeshTilesCache::set(osg::Vec3f const&, osg::Vec2i const&, DetourNavigator::RecastMesh const&, std::vector > const&, DetourNavigator::NavMeshData&&) /home/elsid/dev/openmw/components/detournavigator/navmeshtilescache.cpp:81 #2 0x55e37fe3c861 in DetourNavigator::updateNavMesh(osg::Vec3f const&, DetourNavigator::RecastMesh const*, osg::Vec2i const&, osg::Vec2i const&, std::vector > const&, DetourNavigator::Settings const&, std::shared_ptr > const&, DetourNavigator::NavMeshTilesCache&) /home/elsid/dev/openmw/components/detournavigator/makenavmesh.cpp:582 #3 0x55e37fb796ce in DetourNavigator::AsyncNavMeshUpdater::processJob(DetourNavigator::AsyncNavMeshUpdater::Job const&) /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:178 #4 0x55e37fb9a125 in DetourNavigator::AsyncNavMeshUpdater::process() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:144 #5 0x7f013f585c23 in execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80 #6 0x7f013f8c63e8 in start_thread (/usr/lib/libpthread.so.0+0x93e8) #7 0x7f013e91d292 in __GI___clone (/usr/lib/libc.so.6+0x100292) --- .../detournavigator/navmeshtilescache.cpp | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index f554cd4143..cff93ac0e5 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -21,19 +21,32 @@ namespace DetourNavigator std::vector result(indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize); unsigned char* dst = result.data(); - std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); - dst += indicesSize; + if (indicesSize > 0) + { + std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); + dst += indicesSize; + } - std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); - dst += verticesSize; + if (verticesSize > 0) + { + std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); + dst += verticesSize; + } - std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); - dst += areaTypesSize; + if (areaTypesSize > 0) + { + std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); + dst += areaTypesSize; + } - std::memcpy(dst, recastMesh.getWater().data(), waterSize); - dst += waterSize; + if (waterSize > 0) + { + std::memcpy(dst, recastMesh.getWater().data(), waterSize); + dst += waterSize; + } - std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); + if (offMeshConnectionsSize > 0) + std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); return result; } From 5f1d3e0e2fb2c209d7ad23c6c53d69d4258e6788 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 9 Jan 2021 22:20:57 +0200 Subject: [PATCH 0206/2859] Use the Topic ID of the cloned target from topicinfos --- apps/opencs/model/world/collection.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 451ef9d0e1..f657c67a1c 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -15,6 +15,7 @@ #include "columnbase.hpp" #include "collectionbase.hpp" +#include "info.hpp" #include "land.hpp" #include "landtexture.hpp" #include "ref.hpp" @@ -264,6 +265,13 @@ namespace CSMWorld CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified; ptr->mRefNum.mIndex = 0; } + if (type == UniversalId::Type_TopicInfo || type == UniversalId::Type_JournalInfo) + { + CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; + std::vector splitStringContainer; + Misc::StringUtils::split(destination, splitStringContainer, "#"); + ptr->mTopicId = splitStringContainer[0]; + } int index = getAppendIndex(destination, type); insertRecord(copy, getAppendIndex(destination, type)); From 29416269b27434f0e1bf7854f736fb2ada47c9eb Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 01:07:03 +0200 Subject: [PATCH 0207/2859] Update changelog --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee84ae6d70..123936d42e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks + Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4465: Collision shape overlapping causes twitching Bug #4476: Abot Gondoliers: player hangs in air during scenic travel diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index fdf27dc932..f70fbad575 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -35,6 +35,7 @@ Bug Fixes: Editor Bug Fixes: - Deleted and moved objects within a cell are now saved properly (#832) +- Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) From 3045d20a970afd0da6a63081186e545ca940bfe6 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 01:25:40 +0200 Subject: [PATCH 0208/2859] Make sure that vector isn't empty, just in case --- apps/opencs/model/world/collection.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index f657c67a1c..16ebb3a616 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -270,7 +270,7 @@ namespace CSMWorld CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; std::vector splitStringContainer; Misc::StringUtils::split(destination, splitStringContainer, "#"); - ptr->mTopicId = splitStringContainer[0]; + if (!splitStringContainer.empty()) ptr->mTopicId = splitStringContainer[0]; } int index = getAppendIndex(destination, type); From a257567b8077a0c272eb987569947e4cdac3ad71 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 10 Jan 2021 08:04:06 +0000 Subject: [PATCH 0209/2859] Don't update magic effects when unequipping items to equip something else --- CHANGELOG.md | 1 + apps/openmw/mwworld/inventorystore.cpp | 9 +++++---- apps/openmw/mwworld/inventorystore.hpp | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa35722820..db1e4042c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,7 @@ Bug #5661: Region sounds don't play at the right interval Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx Bug #5681: Player character can clip or pass through bridges instead of colliding against them + Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 38007c1cbb..c9b1d8bc1f 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -762,7 +762,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor return retCount; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool fireEvent) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool applyUpdates) { if (slot<0 || slot>=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); @@ -794,10 +794,11 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } } - if (fireEvent) + if (applyUpdates) + { fireEquipmentChangedEvent(actor); - - updateMagicEffects(actor); + updateMagicEffects(actor); + } return retval; } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index df69a57090..bfe0a99922 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -173,7 +173,7 @@ namespace MWWorld /// /// @return the number of items actually removed - ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool fireEvent=true); + ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool applyUpdates = true); ///< Unequip \a slot. /// /// @return an iterator to the item that was previously in the slot From d015f17a6ce290a0a338db51745dc9a98329cba6 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 25 Dec 2020 18:20:42 +0100 Subject: [PATCH 0210/2859] Make all physics object manage their own resources Use smart pointer for heightfields and their members. Move collision object addition inside of Object's ctor, as for Actors and HeightFields --- apps/openmw/mwphysics/actor.cpp | 5 ++--- apps/openmw/mwphysics/heightfield.cpp | 25 +++++++++++++------------ apps/openmw/mwphysics/heightfield.hpp | 11 ++++++++--- apps/openmw/mwphysics/object.cpp | 12 ++++++------ apps/openmw/mwphysics/object.hpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 24 ++++-------------------- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/projectile.cpp | 11 ++++------- 8 files changed, 39 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 3b7b0d0616..3b52ee934f 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -58,7 +58,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mConvexShape = static_cast(mShape.get()); - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); @@ -76,8 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic Actor::~Actor() { - if (mCollisionObject) - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Actor::enableCollisionMode(bool collision) diff --git a/apps/openmw/mwphysics/heightfield.cpp b/apps/openmw/mwphysics/heightfield.cpp index e1448116bf..436cdfe8fa 100644 --- a/apps/openmw/mwphysics/heightfield.cpp +++ b/apps/openmw/mwphysics/heightfield.cpp @@ -1,4 +1,5 @@ #include "heightfield.hpp" +#include "mtphysics.hpp" #include @@ -42,10 +43,12 @@ namespace namespace MWPhysics { - HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) - : mHeights(makeHeights(heights, sqrtVerts)) + HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) + : mHoldObject(holdObject) + , mHeights(makeHeights(heights, sqrtVerts)) + , mTaskScheduler(scheduler) { - mShape = new btHeightfieldTerrainShape( + mShape = std::make_unique( sqrtVerts, sqrtVerts, getHeights(heights, mHeights), 1, @@ -60,31 +63,29 @@ namespace MWPhysics (y+0.5f) * triSize * (sqrtVerts-1), (maxH+minH)*0.5f)); - mCollisionObject = new btCollisionObject; - mCollisionObject->setCollisionShape(mShape); + mCollisionObject = std::make_unique(); + mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setWorldTransform(transform); - - mHoldObject = holdObject; + mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_HeightMap, CollisionType_Actor|CollisionType_Projectile); } HeightField::~HeightField() { - delete mCollisionObject; - delete mShape; + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } btCollisionObject* HeightField::getCollisionObject() { - return mCollisionObject; + return mCollisionObject.get(); } const btCollisionObject* HeightField::getCollisionObject() const { - return mCollisionObject; + return mCollisionObject.get(); } const btHeightfieldTerrainShape* HeightField::getShape() const { - return mShape; + return mShape.get(); } } diff --git a/apps/openmw/mwphysics/heightfield.hpp b/apps/openmw/mwphysics/heightfield.hpp index 2ba58afff8..c76f8b9437 100644 --- a/apps/openmw/mwphysics/heightfield.hpp +++ b/apps/openmw/mwphysics/heightfield.hpp @@ -5,6 +5,7 @@ #include +#include #include class btCollisionObject; @@ -17,10 +18,12 @@ namespace osg namespace MWPhysics { + class PhysicsTaskScheduler; + class HeightField { public: - HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); + HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); ~HeightField(); btCollisionObject* getCollisionObject(); @@ -28,11 +31,13 @@ namespace MWPhysics const btHeightfieldTerrainShape* getShape() const; private: - btHeightfieldTerrainShape* mShape; - btCollisionObject* mCollisionObject; + std::unique_ptr mShape; + std::unique_ptr mCollisionObject; osg::ref_ptr mHoldObject; std::vector mHeights; + PhysicsTaskScheduler* mTaskScheduler; + void operator=(const HeightField&); HeightField(const HeightField&); }; diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 910f7bf15c..e3615989dd 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,29 +14,29 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) { mPtr = ptr; - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); - const float* pos = ptr.getRefData().getPosition().pos; - setOrigin(btVector3(pos[0], pos[1], pos[2])); + setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); + + mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } Object::~Object() { - if (mCollisionObject) - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } const Resource::BulletShapeInstance* Object::getShapeInstance() const diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index 876e35651f..cae8771809 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,7 +26,7 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index b50366ade9..95d1ec70aa 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -107,12 +107,7 @@ namespace MWPhysics if (mWaterCollisionObject) mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); - for (auto& heightField : mHeightFields) - { - mTaskScheduler->removeCollisionObject(heightField.second->getCollisionObject()); - delete heightField.second; - } - + mHeightFields.clear(); mObjects.clear(); mActors.clear(); mProjectiles.clear(); @@ -442,22 +437,14 @@ namespace MWPhysics void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) { - HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject); - mHeightFields[std::make_pair(x,y)] = heightfield; - - mTaskScheduler->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap, - CollisionType_Actor|CollisionType_Projectile); + mHeightFields[std::make_pair(x,y)] = std::make_unique(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject, mTaskScheduler.get()); } void PhysicsSystem::removeHeightField (int x, int y) { HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y)); if(heightfield != mHeightFields.end()) - { - mTaskScheduler->removeCollisionObject(heightfield->second->getCollisionObject()); - delete heightfield->second; mHeightFields.erase(heightfield); - } } const HeightField* PhysicsSystem::getHeightField(int x, int y) const @@ -465,7 +452,7 @@ namespace MWPhysics const auto heightField = mHeightFields.find(std::make_pair(x, y)); if (heightField == mHeightFields.end()) return nullptr; - return heightField->second; + return heightField->second.get(); } void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) @@ -474,14 +461,11 @@ namespace MWPhysics if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj.get()); - - mTaskScheduler->addCollisionObject(obj->getCollisionObject(), collisionType, - CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } void PhysicsSystem::remove(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 2de8d153b9..4492099743 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -274,7 +274,7 @@ namespace MWPhysics using ProjectileMap = std::map>; ProjectileMap mProjectiles; - using HeightFieldMap = std::map, HeightField *>; + using HeightFieldMap = std::map, std::unique_ptr>; HeightFieldMap mHeightFields; bool mDebugDrawEnabled; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index a8aaeb72a4..1b9beca5ff 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -28,7 +28,7 @@ Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg:: mShape.reset(new btSphereShape(1.f)); mConvexShape = static_cast(mShape.get()); - mCollisionObject.reset(new btCollisionObject); + mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); @@ -45,12 +45,9 @@ Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg:: Projectile::~Projectile() { - if (mCollisionObject) - { - if (!mActive) - mPhysics->reportCollision(mHitPosition, mHitNormal); - mTaskScheduler->removeCollisionObject(mCollisionObject.get()); - } + if (!mActive) + mPhysics->reportCollision(mHitPosition, mHitNormal); + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } void Projectile::commitPositionChange() From 1ab4683dcecb4947b81dcf6606407b6c46a1eacb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 28 Dec 2020 19:19:07 +0100 Subject: [PATCH 0211/2859] Tweak follow distance to be more like the original --- apps/openmw/mwmechanics/aifollow.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f31be9c10a..ec23679977 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -14,6 +14,16 @@ #include "movement.hpp" #include "steering.hpp" +namespace +{ +osg::Vec3f::value_type getHalfExtents(const MWWorld::ConstPtr& actor) +{ + if(actor.getClass().isNpc()) + return 64; + return MWBase::Environment::get().getWorld()->getHalfExtents(actor).y(); +} +} + namespace MWMechanics { int AiFollow::mFollowIndexCounter = 0; @@ -121,13 +131,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte { for(auto& follower : followers) { - auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y(); + auto halfExtent = getHalfExtents(follower.second); if(halfExtent > floatingDistance) floatingDistance = halfExtent; } + floatingDistance += 128; } - floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y(); - floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2; + floatingDistance += getHalfExtents(target) + 64; + floatingDistance += getHalfExtents(actor) * 2; short followDistance = static_cast(floatingDistance); if (!mAlwaysFollow) //Update if you only follow for a bit From 14dd11372f3e76a7289d929387ef38c06f0fbc8d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 5 Jan 2021 22:20:09 +0100 Subject: [PATCH 0212/2859] Utility functions for little-endian <-> big-endian conversion. --- components/misc/endianness.hpp | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 components/misc/endianness.hpp diff --git a/components/misc/endianness.hpp b/components/misc/endianness.hpp new file mode 100644 index 0000000000..1b43e584e5 --- /dev/null +++ b/components/misc/endianness.hpp @@ -0,0 +1,82 @@ +#ifndef COMPONENTS_MISC_ENDIANNESS_H +#define COMPONENTS_MISC_ENDIANNESS_H + +#include + +namespace Misc +{ + + // Two-way conversion little-endian <-> big-endian + template + void swapEndiannessInplace(T& v) + { + static_assert(std::is_arithmetic_v); + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); + + if constexpr (sizeof(T) == 2) + { + uint16_t& v16 = *reinterpret_cast(&v); + v16 = (v16 >> 8) | (v16 << 8); + } + if constexpr (sizeof(T) == 4) + { + uint32_t& v32 = *reinterpret_cast(&v); + v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) || v32 << 24; + } + if constexpr (sizeof(T) == 8) + { + uint64_t& v64 = *reinterpret_cast(&v); + v64 = (v64 >> 56) | ((v64 & 0x00ff'0000'0000'0000) >> 40) | ((v64 & 0x0000'ff00'0000'0000) >> 24) + | ((v64 & 0x0000'00ff'0000'0000) >> 8) | ((v64 & 0x0000'0000'ff00'0000) << 8) + | ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56); + } + } + + #ifdef _WIN32 + constexpr bool IS_LITTLE_ENDIAN = true; + constexpr bool IS_BIG_ENDIAN = false; + #else + constexpr bool IS_LITTLE_ENDIAN = __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__; + constexpr bool IS_BIG_ENDIAN = __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; + #endif + + // Usage: swapEndiannessInplaceIf(v) - native to little-endian or back + // swapEndiannessInplaceIf(v) - native to big-endian or back + template + void swapEndiannessInplaceIf(T& v) + { + static_assert(std::is_arithmetic_v); + static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8); + if constexpr (C) + swapEndiannessInplace(v); + } + + template + T toLittleEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + template + T fromLittleEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + + template + T toBigEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + template + T fromBigEndian(T v) + { + swapEndiannessInplaceIf(v); + return v; + } + +} + +#endif // COMPONENTS_MISC_ENDIANNESS_H From eaaa2f4a1c4daff1e035208eda662476449a4a1f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 8 Jan 2021 23:02:19 +0100 Subject: [PATCH 0213/2859] Use misc/endianness.hpp in components/nif/nifstream --- components/nif/nifstream.cpp | 2 +- components/nif/nifstream.hpp | 98 +++++++++++++----------------------- 2 files changed, 35 insertions(+), 65 deletions(-) diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 69f1a905b0..07c9c917ce 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -7,7 +7,7 @@ namespace Nif osg::Quat NIFStream::getQuaternion() { float f[4]; - readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&f); + readLittleEndianBufferOfType<4, float>(inp, (float*)&f); osg::Quat quat; quat.w() = f[0]; quat.x() = f[1]; diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 97075c288c..4d221b8676 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -22,61 +23,30 @@ namespace Nif class NIFFile; /* - readLittleEndianBufferOfType: This template should only be used with non POD data types + readLittleEndianBufferOfType: This template should only be used with arithmetic types */ -template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) +template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) { -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) pIStream->read((char*)dest, numInstances * sizeof(T)); -#else - uint8_t* destByteBuffer = (uint8_t*)dest; - pIStream->read((char*)dest, numInstances * sizeof(T)); - /* - Due to the loop iterations being known at compile time, - this nested loop will most likely be unrolled - For example, for 2 instances of a 4 byte data type, you should get the below result - */ - union { - IntegerT i; - T t; - } u; - for (uint32_t i = 0; i < numInstances; i++) - { - u = { 0 }; - for (uint32_t byte = 0; byte < sizeof(T); byte++) - u.i |= (((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8)); - dest[i] = u.t; - } -#endif + if constexpr (Misc::IS_BIG_ENDIAN) + for (uint32_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); } /* - readLittleEndianDynamicBufferOfType: This template should only be used with non POD data types + readLittleEndianDynamicBufferOfType: This template should only be used with arithmetic types */ -template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances) +template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances) { -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) pIStream->read((char*)dest, numInstances * sizeof(T)); -#else - uint8_t* destByteBuffer = (uint8_t*)dest; - pIStream->read((char*)dest, numInstances * sizeof(T)); - union { - IntegerT i; - T t; - } u; - for (uint32_t i = 0; i < numInstances; i++) - { - u.i = 0; - for (uint32_t byte = 0; byte < sizeof(T); byte++) - u.i |= ((IntegerT)destByteBuffer[i * sizeof(T) + byte]) << (byte * 8); - dest[i] = u.t; - } -#endif + if constexpr (Misc::IS_BIG_ENDIAN) + for (uint32_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); } -template type inline readLittleEndianType(Files::IStreamPtr &pIStream) +template type inline readLittleEndianType(Files::IStreamPtr &pIStream) { type val; - readLittleEndianBufferOfType<1,type,IntegerT>(pIStream, (type*)&val); + readLittleEndianBufferOfType<1, type>(pIStream, (type*)&val); return val; } @@ -95,59 +65,59 @@ public: char getChar() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } short getShort() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } unsigned short getUShort() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } int getInt() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } unsigned int getUInt() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } float getFloat() { - return readLittleEndianType(inp); + return readLittleEndianType(inp); } osg::Vec2f getVector2() { osg::Vec2f vec; - readLittleEndianBufferOfType<2,float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<2,float>(inp, (float*)&vec._v[0]); return vec; } osg::Vec3f getVector3() { osg::Vec3f vec; - readLittleEndianBufferOfType<3, float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<3, float>(inp, (float*)&vec._v[0]); return vec; } osg::Vec4f getVector4() { osg::Vec4f vec; - readLittleEndianBufferOfType<4, float,uint32_t>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<4, float>(inp, (float*)&vec._v[0]); return vec; } Matrix3 getMatrix3() { Matrix3 mat; - readLittleEndianBufferOfType<9, float,uint32_t>(inp, (float*)&mat.mValues); + readLittleEndianBufferOfType<9, float>(inp, (float*)&mat.mValues); return mat; } @@ -181,14 +151,14 @@ public: ///Read in a string of the length specified in the file std::string getSizedString() { - size_t size = readLittleEndianType(inp); + size_t size = readLittleEndianType(inp); return getSizedString(size); } ///Specific to Bethesda headers, uses a byte for length std::string getExportString() { - size_t size = static_cast(readLittleEndianType(inp)); + size_t size = static_cast(readLittleEndianType(inp)); return getSizedString(size); } @@ -203,58 +173,58 @@ public: void getChars(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUChars(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUShorts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getFloats(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getInts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getUInts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, vec.data(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getVector2s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec2f is 2 floats exactly */ - readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); + readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); } void getVector3s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec3f is 3 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); } void getVector4s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec4f is 4 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); } void getQuaternions(std::vector &quat, size_t size) From 7196ad74557d8c608a9401273a51220fd786f8db Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 10 Jan 2021 21:23:52 +0200 Subject: [PATCH 0214/2859] Implement an override-value when cloning, use when cloning info records --- apps/opencs/model/world/collection.hpp | 8 -------- apps/opencs/model/world/commands.cpp | 9 +++++++++ apps/opencs/model/world/commands.hpp | 3 +++ apps/opencs/view/world/infocreator.cpp | 4 ++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 16ebb3a616..451ef9d0e1 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -15,7 +15,6 @@ #include "columnbase.hpp" #include "collectionbase.hpp" -#include "info.hpp" #include "land.hpp" #include "landtexture.hpp" #include "ref.hpp" @@ -265,13 +264,6 @@ namespace CSMWorld CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) ©.mModified; ptr->mRefNum.mIndex = 0; } - if (type == UniversalId::Type_TopicInfo || type == UniversalId::Type_JournalInfo) - { - CSMWorld::Info* ptr = (CSMWorld::Info*) ©.mModified; - std::vector splitStringContainer; - Misc::StringUtils::split(destination, splitStringContainer, "#"); - if (!splitStringContainer.empty()) ptr->mTopicId = splitStringContainer[0]; - } int index = getAppendIndex(destination, type); insertRecord(copy, getAppendIndex(destination, type)); diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index e33be11397..34485a46d7 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -397,6 +397,10 @@ void CSMWorld::CloneCommand::redo() { mModel.cloneRecord (mIdOrigin, mId, mType); applyModifications(); + for (auto& value : mOverrideValues) + { + mModel.setData(mModel.getModelIndex (mId, value.first), value.second); + } } void CSMWorld::CloneCommand::undo() @@ -404,6 +408,11 @@ void CSMWorld::CloneCommand::undo() mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } +void CSMWorld::CloneCommand::setOverrideValue(int column, QVariant value) +{ + mOverrideValues.emplace_back(std::make_pair(column, value)); +} + CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent) : CreateCommand(model, id, parent) { diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 5776cae363..33608304f4 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -183,6 +183,7 @@ namespace CSMWorld class CloneCommand : public CreateCommand { std::string mIdOrigin; + std::vector> mOverrideValues; public: @@ -194,6 +195,8 @@ namespace CSMWorld void redo() override; void undo() override; + + void setOverrideValue(int column, QVariant value); }; class RevertCommand : public QUndoCommand diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 2f1615c876..505de753c6 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -40,10 +40,14 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); + if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); } else { command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); + if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } } From 7be7af13d753d7cdbf7a8fa3b6ef9331a79b063b Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 11 Jan 2021 06:53:23 +0300 Subject: [PATCH 0215/2859] Downgrade FOV-dependent view distance factor to a recommendation --- apps/launcher/advancedpage.cpp | 4 +-- apps/openmw/mwrender/fogmanager.cpp | 4 +-- .../reference/modding/settings/camera.rst | 33 +++++++------------ 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 54fbbdfe01..d82dd1be2e 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -62,12 +62,12 @@ namespace double convertToCells(double unitRadius) { - return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits); + return std::round((unitRadius + 1024) / CellSizeInUnits); } double convertToUnits(double CellGridRadius) { - return (CellSizeInUnits * CellGridRadius - 1024) * 0.93; + return CellSizeInUnits * CellGridRadius - 1024; } } diff --git a/apps/openmw/mwrender/fogmanager.cpp b/apps/openmw/mwrender/fogmanager.cpp index 837e6ad041..b151882922 100644 --- a/apps/openmw/mwrender/fogmanager.cpp +++ b/apps/openmw/mwrender/fogmanager.cpp @@ -76,8 +76,8 @@ namespace MWRender mLandFogStart = viewDistance * (1 - fogDepth); mLandFogEnd = viewDistance; } - mUnderwaterFogStart = std::min(viewDistance, 6666.f) * (1 - underwaterFog); - mUnderwaterFogEnd = std::min(viewDistance, 6666.f); + mUnderwaterFogStart = std::min(viewDistance, 7168.f) * (1 - underwaterFog); + mUnderwaterFogEnd = std::min(viewDistance, 7168.f); } mFogColor = color; } diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index c30dcb3f3b..e15b06e749 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -51,39 +51,28 @@ viewing distance This value controls the maximum visible distance (also called the far clipping plane). Larger values significantly improve rendering in exterior spaces, but also increase the amount of rendered geometry and significantly reduce the frame rate. -Note that when cells are visible before loading (when not using a Distant Land), the geometry will "pop-in" suddenly, -creating a jarring visual effect. To prevent this effect, this value must be less than:: +Note that when cells are visible before loading, the geometry will "pop-in" suddenly, +creating a jarring visual effect. To prevent this effect, this value should not be greater than: - (CellSizeInUnits * CellGridRadius - 1024) * 0.93 + CellSizeInUnits * CellGridRadius - 1024 The CellSizeInUnits is the size of a game cell in units (8192 by default), CellGridRadius determines how many neighboring cells to current one to load (1 by default - 3x3 grid), and 1024 is the threshold distance for loading a new cell. -Additionally, the field of view setting also interacts with this setting because the view frustum end is a plane, +The field of view setting also interacts with this setting because the view frustum end is a plane, so you can see further at the edges of the screen than you should be able to. This can be observed in game by looking at distant objects and rotating the camera so the objects are near the edge of the screen. -As a result, this setting should further be reduced by a factor that depends on the field of view setting. -In the default configuration this reduction is 7%, hence the factor of 0.93 above. -Using this factor, approximate values recommended for other CellGridRadius values are: - -======= ======== -Cells Viewing - Distance -======= ======== -2 14285 -3 21903 -4 29522 -5 35924 -======= ======== - -Reductions of up to 25% or more can be required to completely eliminate pop-in for wide fields of view -and long viewing distances near the edges of the screen. +As a result, this distance is recommended to further be reduced to avoid pop-in for wide fields of view +and long viewing distances near the edges of the screen if distant terrain and object paging are not used. + +Reductions of up to 25% or more can be required to completely eliminate this pop-in. Such situations are unusual and probably not worth the performance penalty introduced by loading geometry obscured by fog in the center of the screen. See RenderingManager::configureFog for the relevant source code. -This setting can be adjusted in game from the ridiculously low value of 2048.0 to a maximum of 81920.0 -using the View Distance slider in the Detail tab of the Video panel of the Options menu. +This setting can be adjusted in game from the ridiculously low value of 2500 units to a maximum of 7168 units +using the View Distance slider in the Detail tab of the Video panel of the Options menu, unless distant terrain is enabled, +in which case the maximum is increased to 81920 units. field of view ------------- From 93b1b444f2a3abd13d5105b0bb35c41fc5498cd3 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 11 Jan 2021 12:53:34 +0200 Subject: [PATCH 0216/2859] Optimize CreateCommand and CloneCommand configuration --- apps/opencs/view/world/infocreator.cpp | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 505de753c6..cf1b48a195 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -34,20 +34,29 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com { CSMWorld::IdTable& table = dynamic_cast (*getData().getTableModel (getCollectionId())); + CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos) { - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); - CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); - if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + if (!cloneCommand) + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + } + else + { + cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + } } else { - command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); - CSMWorld::CloneCommand* cloneCommand = dynamic_cast (&command); - if (cloneCommand) cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + if (!cloneCommand) + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + } + else + cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); } } From 3087ce9c7039bb072c7587b723d0a58623e81d75 Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 11 Jan 2021 17:56:34 +0100 Subject: [PATCH 0217/2859] Use saved fallheight to determine a character's jump state. The jump state initial state is "none", and it is set after physics simulation. If a save is done just above the ground, the character may land before the first run of the simulation, effectively cancelling the effect of falling. --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa35722820..5b2e1b260b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5731: Editor: skirts are invisible on characters + Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Feature #390: 3rd person look "over the shoulder" diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9bfe6bc0f..53c8e4a8b7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -883,7 +883,11 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim } if(!cls.getCreatureStats(mPtr).isDead()) + { mIdleState = CharState_Idle; + if (cls.getCreatureStats(mPtr).getFallHeight() > 0) + mJumpState = JumpState_InAir; + } else { const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); From 70087e16feaeda3bdd8789b209d0f7cdbca75044 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 11 Jan 2021 13:43:44 +0200 Subject: [PATCH 0218/2859] Disable dialogue info table sorting --- apps/opencs/view/world/subviews.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 3e72f9a9e6..169bc2e94e 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -75,10 +75,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_TopicInfos, - new CSVDoc::SubViewFactoryWithCreator); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_JournalInfos, - new CSVDoc::SubViewFactoryWithCreator); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Pathgrids, new CSVDoc::SubViewFactoryWithCreator); From 390314215225099ef09ef6c10b7365b587d3767b Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 12 Jan 2021 12:05:17 +0000 Subject: [PATCH 0219/2859] Update apps/openmw/mwbase/environment.cpp --- apps/openmw/mwbase/environment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index edb10d9456..d639dd4c69 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -19,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), +: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), mResourceSystem (nullptr), mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { From 4638fc36b46c8a0961f84ae5c3a026c59e72a7e5 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 12 Jan 2021 15:46:19 +0300 Subject: [PATCH 0220/2859] Allow all creatures to float to the water surface --- 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 abff30bb90..172ddb25cf 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2448,7 +2448,7 @@ void CharacterController::update(float duration) } } - if (mFloatToSurface && cls.isActor() && cls.canSwim(mPtr)) + if (mFloatToSurface && cls.isActor()) { if (cls.getCreatureStats(mPtr).isDead() || (!godmode && cls.getCreatureStats(mPtr).isParalyzed())) From 73740013a30cece6cb99f0ea9312bb95dd96d94f Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jan 2021 19:58:46 +0100 Subject: [PATCH 0221/2859] mResourceSystem initialization reorder --- apps/openmw/mwbase/environment.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index d639dd4c69..9014fafff9 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -19,9 +19,9 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), mResourceSystem (nullptr), - mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr), - mFrameDuration (0), mFrameRateLimit(0.f) +: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), + mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), + mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { assert (!sThis); sThis = this; From 823e7bea382ef028643b114dfb85d49a147b2e6e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 13 Jan 2021 02:48:54 +0000 Subject: [PATCH 0222/2859] Fix MyGUI detection `libfind_pkg_detect` used `pkg_check_modules`, which requires all the given modules to be found. This means it always failed for MyGUI, which passes `MyGUI${MYGUI_STATIC_SUFFIX} MYGUI${MYGUI_STATIC_SUFFIX}` to it. Replaces `pkg_check_modules` with `pkg_search_module`, which finds the first match instead. --- cmake/LibFindMacros.cmake | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/LibFindMacros.cmake b/cmake/LibFindMacros.cmake index 2be27c5fcb..3044601f6b 100644 --- a/cmake/LibFindMacros.cmake +++ b/cmake/LibFindMacros.cmake @@ -19,11 +19,11 @@ macro (libfind_package PREFIX PKG) endmacro() # A simple wrapper to make pkg-config searches a bit easier. -# Works the same as CMake's internal pkg_check_modules but is always quiet. -macro (libfind_pkg_check_modules) +# Works the same as CMake's internal pkg_search_module but is always quiet. +macro (libfind_pkg_search_module) find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) - pkg_check_modules(${ARGN} QUIET) + pkg_search_module(${ARGN} QUIET) endif() endmacro() @@ -47,7 +47,7 @@ function (libfind_pkg_detect PREFIX) message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") endif() # Find library - libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) + libfind_pkg_search_module(${PREFIX}_PKGCONF ${pkgargs}) if (pathargs) find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) endif() From f175beb3047d97dffabc3caa9ffc57746760060a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:33:46 +0400 Subject: [PATCH 0223/2859] Define template ref classes in components --- apps/openmw/mwrender/objectpaging.cpp | 13 ++----------- components/resource/scenemanager.cpp | 18 ++++-------------- components/resource/scenemanager.hpp | 25 +++++++++++++++++++++++++ components/sceneutil/serialize.cpp | 1 + 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index a5015f377b..d8e856e769 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -249,15 +249,6 @@ namespace MWRender } }; - class TemplateRef : public osg::Object - { - public: - TemplateRef() {} - TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} - META_Object(MWRender, TemplateRef) - std::vector> mObjects; - }; - class RefnumSet : public osg::Object { public: @@ -530,7 +521,7 @@ namespace MWRender osg::ref_ptr group = new osg::Group; osg::ref_ptr mergeGroup = new osg::Group; - osg::ref_ptr templateRefs = new TemplateRef; + osg::ref_ptr templateRefs = new Resource::TemplateMultiRef; osgUtil::StateToCompile stateToCompile(0, nullptr); CopyOp copyop; for (const auto& pair : nodes) @@ -596,7 +587,7 @@ namespace MWRender if (numinstances > 0) { // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache - templateRefs->mObjects.emplace_back(cnode); + templateRefs->addRef(cnode); if (pair.second.mNeedCompile) { diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 2630cd4536..e75fa4f74e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -110,6 +110,10 @@ namespace namespace Resource { + void TemplateMultiRef::addRef(const osg::Node* node) + { + mObjects.emplace_back(node); + } class SharedStateManager : public osgDB::SharedStateManager { @@ -554,20 +558,6 @@ namespace Resource return node; } - class TemplateRef : public osg::Object - { - public: - TemplateRef(const Object* object) - : mObject(object) {} - TemplateRef() {} - TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObject(copy.mObject) {} - - META_Object(Resource, TemplateRef) - - private: - osg::ref_ptr mObject; - }; - osg::ref_ptr SceneManager::createInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index fd75070a18..e897566a8b 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -37,6 +37,31 @@ namespace Shader namespace Resource { + class TemplateRef : public osg::Object + { + public: + TemplateRef(const Object* object) : mObject(object) {} + TemplateRef() {} + TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObject(copy.mObject) {} + + META_Object(Resource, TemplateRef) + + private: + osg::ref_ptr mObject; + }; + + class TemplateMultiRef : public osg::Object + { + public: + TemplateMultiRef() {} + TemplateMultiRef(const TemplateMultiRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {} + void addRef(const osg::Node* node); + + META_Object(Resource, TemplateMultiRef) + + private: + std::vector> mObjects; + }; class MultiObjectCache; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index d03612fc1d..7e176be3dd 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -121,6 +121,7 @@ void registerSerializers() const char* ignore[] = { "MWRender::PtrHolder", "Resource::TemplateRef", + "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", From 0418e8e7a6eccef17a6995470d4f9ce2e95d1106 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:41:02 +0400 Subject: [PATCH 0224/2859] Add an API to get base wind speed (which is from openmw.cfg) --- apps/openmw/mwrender/sky.cpp | 9 +++++++++ apps/openmw/mwrender/sky.hpp | 4 ++++ apps/openmw/mwworld/weather.cpp | 2 ++ 3 files changed, 15 insertions(+) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2920e07dde..93061022c3 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1134,6 +1134,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mRainEntranceSpeed(1) , mRainMaxRaindrops(0) , mWindSpeed(0.f) + , mBaseWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) , mWeatherAlpha(0.f) @@ -1685,6 +1686,7 @@ void SkyManager::setWeather(const WeatherResult& weather) mRainMaxHeight = weather.mRainMaxHeight; mRainSpeed = weather.mRainSpeed; mWindSpeed = weather.mWindSpeed; + mBaseWindSpeed = weather.mBaseWindSpeed; if (mRainEffect != weather.mRainEffect) { @@ -1853,6 +1855,13 @@ void SkyManager::setWeather(const WeatherResult& weather) fader->setAlpha(weather.mEffectFade); } +float SkyManager::getBaseWindSpeed() const +{ + if (!mCreated) return 0.f; + + return mBaseWindSpeed; +} + void SkyManager::sunEnable() { if (!mCreated) return; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index cf697bd44f..2ec134d09d 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -70,6 +70,7 @@ namespace MWRender float mDLFogOffset; float mWindSpeed; + float mBaseWindSpeed; float mCurrentWindSpeed; float mNextWindSpeed; @@ -181,6 +182,8 @@ namespace MWRender void setRainIntensityUniform(osg::Uniform *uniform); + float getBaseWindSpeed() const; + private: void create(); ///< no need to call this, automatically done on first enable() @@ -265,6 +268,7 @@ namespace MWRender float mRainEntranceSpeed; int mRainMaxRaindrops; float mWindSpeed; + float mBaseWindSpeed; bool mEnabled; bool mSunEnabled; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6a4a227a49..415e69d20b 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1123,6 +1123,7 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mCloudBlendFactor = 0; mResult.mNextWindSpeed = 0; mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed); + mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed; mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; @@ -1214,6 +1215,7 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed); mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed); + mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor); mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, factor); mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); From 89f3f860ed99ce1b19c4ee3ec07373ad2f4cdcf1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 13:57:05 +0400 Subject: [PATCH 0225/2859] Allow to get a rotation vector from ESM::Position --- components/esm/defs.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 0f9cefab12..9bf9b01f3d 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -54,6 +54,11 @@ struct Position { return osg::Vec3f(pos[0], pos[1], pos[2]); } + + osg::Vec3f asRotationVec3() const + { + return osg::Vec3f(rot[0], rot[1], rot[2]); + } }; #pragma pack(pop) From 16e03c151a783fc9902bce6cde73026c58cb6ddc Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 15:38:29 +0200 Subject: [PATCH 0226/2859] Implement basic move algorithm, connect it to drag&drop --- apps/opencs/view/world/dragdroputils.cpp | 10 +++- apps/opencs/view/world/dragdroputils.hpp | 3 + apps/opencs/view/world/dragrecordtable.cpp | 7 ++- apps/opencs/view/world/dragrecordtable.hpp | 5 ++ apps/opencs/view/world/table.cpp | 70 ++++++++++++++++++++++ apps/opencs/view/world/table.hpp | 2 + 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp index 808125a601..bb4d972737 100644 --- a/apps/opencs/view/world/dragdroputils.cpp +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -15,7 +15,15 @@ bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::C return data != nullptr && data->holdsType(type); } -CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, +bool CSVWorld::DragDropUtils::isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type) +{ + const CSMWorld::TableMimeData *data = getTableMimeData(event); + return data != nullptr && ( + data->holdsType(CSMWorld::UniversalId::Type_TopicInfo) || + data->holdsType(CSMWorld::UniversalId::Type_JournalInfo) ); +} + +CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) { if (canAcceptData(event, type)) diff --git a/apps/opencs/view/world/dragdroputils.hpp b/apps/opencs/view/world/dragdroputils.hpp index d1d780708e..2181e7606b 100644 --- a/apps/opencs/view/world/dragdroputils.hpp +++ b/apps/opencs/view/world/dragdroputils.hpp @@ -20,6 +20,9 @@ namespace CSVWorld bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type + bool isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + ///< Info types can be dragged to sort the info table + CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); ///< Gets the accepted data from the \a event using the \a type ///< \return Type_None if the \a event data doesn't holds the \a type diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index f84bf639da..58041af9fc 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -46,7 +46,8 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event) void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) { QModelIndex index = indexAt(event->pos()); - if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index))) + if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index)) || + CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index)) ) { if (index.flags() & Qt::ItemIsEditable) { @@ -75,6 +76,10 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) } } } + else if (CSVWorld::DragDropUtils::isInfo(*event, display) && event->source() == this) + { + emit moveRecordsFromSameTable(event); + } } CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const diff --git a/apps/opencs/view/world/dragrecordtable.hpp b/apps/opencs/view/world/dragrecordtable.hpp index a6b6756aa0..f6c3fa8909 100644 --- a/apps/opencs/view/world/dragrecordtable.hpp +++ b/apps/opencs/view/world/dragrecordtable.hpp @@ -23,6 +23,8 @@ namespace CSVWorld { class DragRecordTable : public QTableView { + Q_OBJECT + protected: CSMDoc::Document& mDocument; bool mEditLock; @@ -45,6 +47,9 @@ namespace CSVWorld private: CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const; + + signals: + void moveRecordsFromSameTable(QDropEvent *event); }; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index e5f4e36c5b..03d7dbbfab 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -248,6 +249,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, if (isInfoTable) { mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this); + connect (this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords); } else if (isLtexTable) { @@ -563,6 +565,74 @@ void CSVWorld::Table::moveDownRecord() } } +void CSVWorld::Table::moveRecords(QDropEvent *event) +{ + if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) + return; + + QModelIndex targedIndex = indexAt(event->pos()); + + QModelIndexList selectedRows = selectionModel()->selectedRows(); + int targetRow = targedIndex.row(); + int baseRow = targedIndex.row() - 1; + int highestDifference = 0; + + for (const auto& thisRowData : selectedRows) + { + if (std::abs(targetRow - thisRowData.row()) > highestDifference) highestDifference = std::abs(targetRow - thisRowData.row()); + if (thisRowData.row() - 1 < baseRow) baseRow = thisRowData.row() - 1; + } + + std::vector newOrder (highestDifference + 1); + + for (long unsigned int i = 0; i < newOrder.size(); ++i) + { + newOrder[i] = i; + } + + if (selectedRows.size() > 1) + { + Log(Debug::Warning) << "Move operation failed: Moving multiple selections isn't implemented."; + return; + } + + for (const auto& thisRowData : selectedRows) + { + /* + Moving algorithm description + a) Remove the (ORIGIN + 1)th list member. + b) Add (ORIGIN+1)th list member with value TARGET + c) If ORIGIN > TARGET,d_INC; ELSE d_DEC + d_INC) increase all members after (and including) the TARGET by one, stop before hitting ORIGINth address + d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET + */ + + int originRow = thisRowData.row(); + //int sourceMappedOriginRow = mProxyModel->mapToSource (mProxyModel->index (originRow, 0)).row(); + + newOrder.erase(newOrder.begin() + originRow - baseRow - 1); + newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1); + + if (originRow > targetRow) + { + for (int i = targetRow - baseRow - 1; i < originRow - baseRow - 1; ++i) + { + ++newOrder[i]; + } + } + else + { + for (int i = originRow - baseRow; i <= targetRow - baseRow - 1; ++i) + { + --newOrder[i]; + } + } + + } + mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand ( + dynamic_cast (*mModel), baseRow + 1, newOrder)); +} + void CSVWorld::Table::editCell() { emit editRequest(mEditIdAction->getCurrentId(), ""); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 61dd57c061..9c4b9b5e5a 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -141,6 +141,8 @@ namespace CSVWorld void moveDownRecord(); + void moveRecords(QDropEvent *event); + void viewRecord(); void previewRecord(); From f2fc02cdff69cabafa00a33de95bd942511d2eb7 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 15:55:16 +0200 Subject: [PATCH 0227/2859] Support filtered tables (mapToSource for indexes) --- apps/opencs/view/world/table.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 03d7dbbfab..c676a5ecc0 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -573,14 +573,17 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) QModelIndex targedIndex = indexAt(event->pos()); QModelIndexList selectedRows = selectionModel()->selectedRows(); - int targetRow = targedIndex.row(); - int baseRow = targedIndex.row() - 1; + int targetRowRaw = targedIndex.row(); + int targetRow = mProxyModel->mapToSource (mProxyModel->index (targetRowRaw, 0)).row(); + int baseRowRaw = targedIndex.row() - 1; + int baseRow = mProxyModel->mapToSource (mProxyModel->index (baseRowRaw, 0)).row(); int highestDifference = 0; for (const auto& thisRowData : selectedRows) { - if (std::abs(targetRow - thisRowData.row()) > highestDifference) highestDifference = std::abs(targetRow - thisRowData.row()); - if (thisRowData.row() - 1 < baseRow) baseRow = thisRowData.row() - 1; + int thisRow = mProxyModel->mapToSource (mProxyModel->index (thisRowData.row(), 0)).row(); + if (std::abs(targetRow - thisRow) > highestDifference) highestDifference = std::abs(targetRow - thisRow); + if (thisRow - 1 < baseRow) baseRow = thisRow - 1; } std::vector newOrder (highestDifference + 1); @@ -607,8 +610,8 @@ void CSVWorld::Table::moveRecords(QDropEvent *event) d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET */ - int originRow = thisRowData.row(); - //int sourceMappedOriginRow = mProxyModel->mapToSource (mProxyModel->index (originRow, 0)).row(); + int originRowRaw = thisRowData.row(); + int originRow = mProxyModel->mapToSource (mProxyModel->index (originRowRaw, 0)).row(); newOrder.erase(newOrder.begin() + originRow - baseRow - 1); newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1); From 5740258d3b006977a5a8fa78f71b4cbb5d11f837 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 13 Jan 2021 16:23:59 +0200 Subject: [PATCH 0228/2859] Add changelog entry --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 943dcbe898..b628a847a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors Bug #4247: Cannot walk up stairs in Ebonheart docks + Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4465: Collision shape overlapping causes twitching diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index f70fbad575..c7abb6fec2 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -35,6 +35,7 @@ Bug Fixes: Editor Bug Fixes: - Deleted and moved objects within a cell are now saved properly (#832) +- Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357) - Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) From d24a5f7b89a775baa09febcfd960138ae36eaf3e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 13 Jan 2021 20:45:34 +0000 Subject: [PATCH 0229/2859] Fix OSG USE_GRAPHICSWINDOW check When OSG is built with `-DOSG_WINDOWING_SYSTEM=None`, this macro does not exist. Replaces the Android-specific check with a general one. --- components/sdlutil/sdlcursormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 56225868e3..964762ec76 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -20,7 +20,7 @@ #include "imagetosurface.hpp" -#if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) +#if defined(OSG_LIBRARY_STATIC) && defined(USE_GRAPHICSWINDOW) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() From 24d8412c0cab3b1eb146389168ec601acf222537 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Thu, 14 Jan 2021 02:52:59 +0000 Subject: [PATCH 0230/2859] cmake: Fix missing OPENGL_INCLUDE_DIR OPENGL_INCLUDE_DIR wasn't being included. It is usually just /usr/include but if it was custom the build failed. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a615e8432..226511b527 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,7 @@ include_directories("." ${Boost_INCLUDE_DIR} ${MyGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} + ${OPENGL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} ) From 11dfb9daff116ae9e1ccda971ebb8852831bfc6e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 15 Jan 2021 15:11:50 +0000 Subject: [PATCH 0231/2859] cmake/FindOSGPlugins: Support lib-prefixed plugins With a regular OSG build, the plugin names are prefixed with `lib`, e.g. `libosgdb_jpeg.a` for a static build. However, on Debian on Ubuntu they are not. With this commit we now try both options. Fixes #5972 Signed-off-by: Gleb Mazovetskiy --- cmake/FindOSGPlugins.cmake | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/FindOSGPlugins.cmake b/cmake/FindOSGPlugins.cmake index c210466c08..457abf665c 100644 --- a/cmake/FindOSGPlugins.cmake +++ b/cmake/FindOSGPlugins.cmake @@ -27,9 +27,12 @@ foreach(_library ${OSGPlugins_FIND_COMPONENTS}) string(TOUPPER ${_library} _library_uc) set(_component OSGPlugins_${_library}) - set(${_library_uc}_DIR ${OSGPlugins_LIB_DIR}) # to help function osg_find_library + # On some systems, notably Debian and Ubuntu, the OSG plugins do not have + # the usual "lib" prefix. We temporarily add the empty string to the list + # of prefixes CMake searches for (via osg_find_library) to support these systems. set(_saved_lib_prefix ${CMAKE_FIND_LIBRARY_PREFIXES}) # save CMAKE_FIND_LIBRARY_PREFIXES - set(CMAKE_FIND_LIBRARY_PREFIXES "") # search libraries with no prefix + list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "") # search libraries with no prefix + set(${_library_uc}_DIR ${OSGPlugins_LIB_DIR}) # to help function osg_find_library osg_find_library(${_library_uc} ${_library}) # find it into ${_library_uc}_LIBRARIES set(CMAKE_FIND_LIBRARY_PREFIXES ${_saved_lib_prefix}) # restore prefix From 4974b64cbf4f54d446386a0303962d08cd518026 Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 17 Jan 2021 15:58:43 +0000 Subject: [PATCH 0232/2859] Update CI/before_install.osx.sh --- CI/before_install.osx.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 85434fa06d..ba0ff00ca7 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,5 +1,9 @@ #!/bin/sh -e +# workaround python issue on travis +HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true +HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true + # Some of these tools can come from places other than brew, so check before installing command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake From e37e5d4d16fd3240bd57355df40abc889e3ce8db Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 18 Jan 2021 17:42:03 +0100 Subject: [PATCH 0233/2859] Don't run unstuck if there is no simulation running in async case. In this case, the actor mPreviousPosition is not updated, so the actor position is interpolated between an old (stucked) position and the new (unstucked) position. The new position is most likely "stucked", so the unstuck code strikes again, making the actor "vibrates". That's exactly what the sync code path does, and it doesn't exhibit this behavior. --- apps/openmw/mwphysics/mtphysics.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2781a5b1bc..6c7c573a49 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -173,6 +173,8 @@ namespace MWPhysics { if (mDeferAabbUpdate) updateAabbs(); + if (!mRemainingSteps) + return; for (auto& data : mActorsFrameData) if (data.mActor.lock()) { From 54ea8eb5c79d4a6bb88159cd6a67ce0dd4e17235 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:21:02 +0200 Subject: [PATCH 0234/2859] Fix string corruption with Qt on linux-systems --- apps/opencs/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 5287c8b191..c7d57a256e 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -63,6 +63,9 @@ int runApplication(int argc, char *argv[]) application.setWindowIcon (QIcon (":./openmw-cs.png")); CS::Editor editor(argc, argv); +#ifdef __linux__ + setlocale(LC_NUMERIC,"C"); +#endif if(!editor.makeIPCServer()) { From 9f0f3eaeb2224a725fee002b986ebd56eb02b57c Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:22:01 +0200 Subject: [PATCH 0235/2859] Add collada to supported formats --- apps/opencs/model/world/resourcesmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 1af9c5e9b1..378ba7c6bb 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -17,7 +17,7 @@ void CSMWorld::ResourcesManager::addResources (const Resources& resources) const char * const * CSMWorld::ResourcesManager::getMeshExtensions() { // maybe we could go over the osgDB::Registry to list all supported node formats - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae", 0 }; return sMeshTypes; } From 0acae08e526846838f8e5f5f0673e3af7cca4d83 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 18 Jan 2021 19:24:38 +0200 Subject: [PATCH 0236/2859] Add changelog entry --- CHANGELOG.md | 1 + CHANGELOG_PR.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3c..47cfd00310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5706: AI sequences stop looping after the saved game is reloaded + Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view Bug #5731: Editor: skirts are invisible on characters Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index f70fbad575..944b260442 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -39,6 +39,7 @@ Editor Bug Fixes: - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) - Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) +- Collada models render properly in the Editor (#5713) Miscellaneous: - Prevent save-game bloating by using an appropriate fog texture format (#5108) From 8af8ad38406c35182dcb23a3c511be007a87dd9c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 01:17:16 +0000 Subject: [PATCH 0237/2859] Always write opaque fragments instead of relying on blending being off for translucent RTT --- apps/openmw/mwrender/characterpreview.cpp | 26 +++++++++++++++++++++-- apps/openmw/mwrender/renderingmanager.cpp | 1 + files/shaders/objects_fragment.glsl | 4 ++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 89db3e5f46..f21e667eb0 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -85,7 +86,7 @@ namespace MWRender class SetUpBlendVisitor : public osg::NodeVisitor { public: - SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mNoAlphaUniform(new osg::Uniform("noAlpha", false)) { } @@ -102,10 +103,17 @@ namespace MWRender newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); node.setStateSet(newStateSet); } - + if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON) + { + // Disable noBlendAlphaEnv + stateset->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); + stateset->addUniform(mNoAlphaUniform); + } } traverse(node); } + private: + osg::ref_ptr mNoAlphaUniform; }; CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, @@ -164,6 +172,20 @@ namespace MWRender fog->setEnd(10000000); stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // Opaque stuff must have 1 as its fragment alpha as the FBO is translucent, so having blending off isn't enough + osg::ref_ptr noBlendAlphaEnv = new osg::TexEnvCombine(); + noBlendAlphaEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + noBlendAlphaEnv->setSource0_Alpha(osg::TexEnvCombine::CONSTANT); + noBlendAlphaEnv->setConstantColor(osg::Vec4(0.0, 0.0, 0.0, 1.0)); + noBlendAlphaEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); + noBlendAlphaEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + osg::ref_ptr dummyTexture = new osg::Texture2D(); + dummyTexture->setInternalFormat(GL_RED); + dummyTexture->setTextureSize(1, 1); + stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); + stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("noAlpha", true)); + osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0)); stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ce431d2e2..e1f6f0dd96 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -370,6 +370,7 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); + mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("noAlpha", false)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 78660685f6..bd2bd5909f 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -50,6 +50,7 @@ uniform mat2 bumpMapMatrix; #endif uniform bool simpleWater; +uniform bool noAlpha; varying float euclideanDepth; varying float linearDepth; @@ -208,5 +209,8 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + if (noAlpha) + gl_FragData[0].a = 1.0; + applyShadowDebugOverlay(); } From 35fab974783e6a3f3b384ec09ce31f22432c89eb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 01:24:05 +0000 Subject: [PATCH 0238/2859] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3c..ce1fb07e8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Bug #5370: Opening an unlocked but trapped door uses the key Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly + Bug #5391: Races Redone 1.2 bodies don't show on the inventory Bug #5397: NPC greeting does not reset if you leave + reenter area Bug #5400: Editor: Verifier checks race of non-skin bodyparts Bug #5403: Enchantment effect doesn't show on an enemy during death animation From b6e92c9c6deedfb9c4a4acfd106d57eb3e1628e8 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Jan 2021 23:37:19 +0000 Subject: [PATCH 0239/2859] Use ShaderVisitor to skip translucent framebuffer specific stuff --- apps/openmw/mwrender/characterpreview.cpp | 3 +++ apps/openmw/mwrender/renderingmanager.cpp | 1 - components/resource/scenemanager.cpp | 7 ++++--- components/resource/scenemanager.hpp | 4 ++-- components/shader/shadervisitor.cpp | 10 +++++++++- components/shader/shadervisitor.hpp | 4 ++++ files/shaders/objects_fragment.glsl | 3 +++ 7 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index f21e667eb0..14735050ce 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include @@ -249,6 +251,7 @@ namespace MWRender void CharacterPreview::setBlendMode() { + mResourceSystem->getSceneManager()->recreateShaders(mNode, "objects", true); SetUpBlendVisitor visitor; mNode->accept(visitor); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e1f6f0dd96..6ce431d2e2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -370,7 +370,6 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("noAlpha", false)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e75fa4f74e..d937b992b1 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -247,9 +247,9 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer) { - osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix)); + osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); node->accept(*shaderVisitor); } @@ -749,7 +749,7 @@ namespace Resource stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); } - Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix) + Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) { Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix+"_vertex.glsl", shaderPrefix+"_fragment.glsl"); shaderVisitor->setForceShaders(mForceShaders); @@ -759,6 +759,7 @@ namespace Resource shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); + shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index e897566a8b..a815a324f2 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects"); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); @@ -173,7 +173,7 @@ namespace Resource private: - Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); + Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); std::unique_ptr mShaderManager; bool mForceShaders; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index e908b6aaa8..9dec5522ca 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -40,6 +40,7 @@ namespace Shader , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultVsTemplate(defaultVsTemplate) @@ -146,7 +147,7 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } - else + else if (!mTranslucentFramebuffer) Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } @@ -322,6 +323,8 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); @@ -474,4 +477,9 @@ namespace Shader mApplyLightingToEnvMaps = apply; } + void ShaderVisitor::setTranslucentFramebuffer(bool translucent) + { + mTranslucentFramebuffer = translucent; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 6031dbfe6c..11b37c9230 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -40,6 +40,8 @@ namespace Shader void setApplyLightingToEnvMaps(bool apply); + void setTranslucentFramebuffer(bool translucent); + void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -63,6 +65,8 @@ namespace Shader bool mApplyLightingToEnvMaps; + bool mTranslucentFramebuffer; + ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index bd2bd5909f..50bb77145a 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -209,8 +209,11 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +#if @translucentFramebuffer + // having testing & blending isn't enough - we need to write an opaque pixel to be opaque if (noAlpha) gl_FragData[0].a = 1.0; +#endif applyShadowDebugOverlay(); } From cc24f13b391b3c3db25d8a1d77cb95b65fcfafee Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Jan 2021 13:08:50 +0100 Subject: [PATCH 0240/2859] Remove duplicated sound_buffer entry --- apps/openmw/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 56875a8c3f..f96ddb27f8 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,6 @@ add_openmw_dir (mwscript add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings - sound_buffer ) add_openmw_dir (mwworld From 1f4c85520f33a1f340765597e0b1dbf837670b64 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 7 Jan 2021 11:02:53 +0100 Subject: [PATCH 0241/2859] Use convexSweepTest for projectile movement to solve any imprecision issue with projectile collision detection. Simplify the mechanics: manage hits in one spot. Give magic projectiles a collision shape similar in size to their visible model. Rename the 2 convex result callback to clearly state their purpose. --- CHANGELOG.md | 1 + apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 35 ++--- ...ltcallback.cpp => actorconvexcallback.cpp} | 6 +- ...ltcallback.hpp => actorconvexcallback.hpp} | 8 +- .../closestnotmerayresultcallback.cpp | 33 +--- .../closestnotmerayresultcallback.hpp | 4 +- apps/openmw/mwphysics/physicssystem.cpp | 54 +++++-- apps/openmw/mwphysics/physicssystem.hpp | 6 +- apps/openmw/mwphysics/projectile.cpp | 61 +++++--- apps/openmw/mwphysics/projectile.hpp | 26 ++-- .../mwphysics/projectileconvexcallback.cpp | 67 +++++++++ .../mwphysics/projectileconvexcallback.hpp | 27 ++++ apps/openmw/mwphysics/raycasting.hpp | 2 +- apps/openmw/mwphysics/trace.cpp | 6 +- apps/openmw/mwworld/projectilemanager.cpp | 142 ++++-------------- apps/openmw/mwworld/projectilemanager.hpp | 4 +- 17 files changed, 259 insertions(+), 225 deletions(-) rename apps/openmw/mwphysics/{closestnotmeconvexresultcallback.cpp => actorconvexcallback.cpp} (92%) rename apps/openmw/mwphysics/{closestnotmeconvexresultcallback.hpp => actorconvexcallback.hpp} (54%) create mode 100644 apps/openmw/mwphysics/projectileconvexcallback.cpp create mode 100644 apps/openmw/mwphysics/projectileconvexcallback.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fd4cbe3c..a2eaa32596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Bug #4039: Multiple followers should have the same following distance Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors + Bug #4201: Projectile-projectile collision Bug #4247: Cannot walk up stairs in Ebonheart docks Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4447: Actor collision capsule shape allows looking through some walls diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 56875a8c3f..412041f74f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -74,7 +74,7 @@ add_openmw_dir (mwworld add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile - closestnotmeconvexresultcallback raycasting mtphysics contacttestwrapper + actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 81b3a353df..142914c358 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -60,7 +60,10 @@ namespace MWMechanics void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { - if (!target.isEmpty() && target.getClass().isActor()) + if (target.isEmpty()) + return; + + if (target.getClass().isActor()) { // Early-out for characters that have departed. const auto& stats = target.getClass().getCreatureStats(target); @@ -82,7 +85,7 @@ namespace MWMechanics return; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); - if (spell && !target.isEmpty() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) + if (spell && target.getClass().isActor() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) { int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? ESM::MagicEffect::ResistCommonDisease @@ -105,13 +108,13 @@ namespace MWMechanics // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. // Otherwise, they'd only apply after the whole spell was added. MagicEffects targetEffects; - if (!target.isEmpty() && target.getClass().isActor()) + if (target.getClass().isActor()) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); ActiveSpells targetSpells; - if (!target.isEmpty() && target.getClass().isActor()) + if (target.getClass().isActor()) targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false @@ -123,7 +126,7 @@ namespace MWMechanics int currentEffectIndex = 0; for (std::vector::const_iterator effectIt (effects.mList.begin()); - !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) + effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) { if (effectIt->mRange != range) continue; @@ -267,7 +270,7 @@ namespace MWMechanics } // Re-casting a summon effect will remove the creature from previous castings of that effect. - if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor()) + if (isSummoningEffect(effectIt->mEffectID) && target.getClass().isActor()) { CreatureStats& targetStats = target.getClass().getCreatureStats(target); ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex); @@ -310,18 +313,16 @@ namespace MWMechanics if (!exploded) MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); - if (!target.isEmpty()) { - if (!reflectedEffects.mList.empty()) - inflict(caster, target, reflectedEffects, range, true, exploded); + if (!reflectedEffects.mList.empty()) + inflict(caster, target, reflectedEffects, range, true, exploded); - if (!appliedLastingEffects.empty()) - { - int casterActorId = -1; - if (!caster.isEmpty() && caster.getClass().isActor()) - casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, casterActorId); - } + if (!appliedLastingEffects.empty()) + { + int casterActorId = -1; + if (!caster.isEmpty() && caster.getClass().isActor()) + casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, + mSourceName, casterActorId); } } diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp similarity index 92% rename from apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp rename to apps/openmw/mwphysics/actorconvexcallback.cpp index 27e7a390c4..f99b706d74 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -1,6 +1,6 @@ #include -#include "closestnotmeconvexresultcallback.hpp" +#include "actorconvexcallback.hpp" #include "collisiontype.hpp" #include "contacttestwrapper.h" @@ -31,13 +31,13 @@ namespace MWPhysics } }; - ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) + ActorConvexCallback::ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world) { } - btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) + btScalar ActorConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp b/apps/openmw/mwphysics/actorconvexcallback.hpp similarity index 54% rename from apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp rename to apps/openmw/mwphysics/actorconvexcallback.hpp index 538721ad80..1c28ee6cc4 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp +++ b/apps/openmw/mwphysics/actorconvexcallback.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H -#define OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H +#ifndef OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H +#define OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H #include @@ -7,10 +7,10 @@ class btCollisionObject; namespace MWPhysics { - class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback + class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); + ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index c3104f8603..32d97d6c75 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -7,16 +7,13 @@ #include "../mwworld/class.hpp" -#include "actor.hpp" -#include "collisiontype.hpp" -#include "projectile.hpp" #include "ptrholder.hpp" namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me), mTargets(std::move(targets)), mProjectile(proj) + , mMe(me), mTargets(std::move(targets)) { } @@ -25,9 +22,6 @@ namespace MWPhysics if (rayResult.m_collisionObject == mMe) return 1.f; - if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject()) - return 1.f; - if (!mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) @@ -38,27 +32,6 @@ namespace MWPhysics } } - btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); - if (mProjectile) - { - switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup) - { - case CollisionType_Actor: - { - auto* target = static_cast(rayResult.m_collisionObject->getUserPointer()); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - case CollisionType_Projectile: - { - auto* target = static_cast(rayResult.m_collisionObject->getUserPointer()); - target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - } - } - - return rayResult.m_hitFraction; + return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index b86427165a..1fa32ef686 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -14,13 +14,13 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr); + ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; + private: const btCollisionObject* mMe; const std::vector mTargets; - Projectile* mProjectile; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 95d1ec70aa..ed0e0c915d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -55,6 +55,7 @@ #include "deepestnotmecontacttestresultcallback.hpp" #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" +#include "projectileconvexcallback.hpp" #include "constants.hpp" #include "movementsolver.hpp" #include "mtphysics.hpp" @@ -246,7 +247,7 @@ namespace MWPhysics return 0.f; } - RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const + RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const { if (from == to) { @@ -283,7 +284,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId)); + ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; @@ -580,15 +581,44 @@ namespace MWPhysics } } - void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) + void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const { - ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); - if (foundProjectile != mProjectiles.end()) - { - foundProjectile->second->setPosition(position); - mTaskScheduler->updateSingleAabb(foundProjectile->second); + const auto foundProjectile = mProjectiles.find(projectileId); + assert(foundProjectile != mProjectiles.end()); + auto* projectile = foundProjectile->second.get(); + + btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition()); + btVector3 btTo = Misc::Convert::toBullet(position); + + if (btFrom == btTo) return; - } + + const auto casterPtr = projectile->getCaster(); + const auto* caster = [this,&casterPtr]() -> const btCollisionObject* + { + const Actor* actor = getActor(casterPtr); + if (actor) + return actor->getCollisionObject(); + const Object* object = getObject(casterPtr); + if (object) + return object->getCollisionObject(); + return nullptr; + }(); + assert(caster); + + ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile); + resultCallback.m_collisionFilterMask = 0xff; + resultCallback.m_collisionFilterGroup = CollisionType_Projectile; + + const btQuaternion btrot = btQuaternion::getIdentity(); + btTransform from_ (btrot, btFrom); + btTransform to_ (btrot, btTo); + + mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); + + const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld); + projectile->setPosition(newpos); + mTaskScheduler->updateSingleAabb(foundProjectile->second); } void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) @@ -651,10 +681,10 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater) { mProjectileId++; - auto projectile = std::make_shared(mProjectileId, caster, position, mTaskScheduler.get(), this); + auto projectile = std::make_shared(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; @@ -863,7 +893,7 @@ namespace MWPhysics mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, - CollisionType_Actor); + CollisionType_Actor|CollisionType_Projectile); } bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4492099743..6f901067a2 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -124,8 +124,8 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position); - void updateProjectile(const int projectileId, const osg::Vec3f &position); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); + void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); @@ -174,7 +174,7 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 1b9beca5ff..a931219975 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -13,19 +13,22 @@ #include "../mwworld/class.hpp" #include "collisiontype.hpp" +#include "memory" #include "mtphysics.hpp" #include "projectile.hpp" namespace MWPhysics { -Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) - : mActive(true) +Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) + : mCanCrossWaterSurface(canCrossWaterSurface) + , mCrossedWaterSurface(false) + , mActive(true) , mCaster(caster) + , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) - , mProjectileId(projectileId) { - mShape.reset(new btSphereShape(1.f)); + mShape = std::make_unique(radius); mConvexShape = static_cast(mShape.get()); mCollisionObject = std::make_unique(); @@ -67,6 +70,17 @@ void Projectile::setPosition(const osg::Vec3f &position) mTransformUpdatePending = true; } +osg::Vec3f Projectile::getPosition() const +{ + std::scoped_lock lock(mMutex); + return Misc::Convert::toOsg(mLocalTransform.getOrigin()); +} + +bool Projectile::canTraverseWater() const +{ + return mCanCrossWaterSurface; +} + void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) @@ -78,12 +92,6 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) mActive.store(false, std::memory_order_release); } -void Projectile::activate() -{ - assert(!mActive); - mActive.store(true, std::memory_order_release); -} - MWWorld::Ptr Projectile::getCaster() const { std::scoped_lock lock(mMutex); @@ -108,21 +116,32 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const if (mCaster == target) return false; - if (!mValidTargets.empty()) + if (target.isEmpty() || mValidTargets.empty()) + return true; + + bool validTarget = false; + for (const auto& targetActor : mValidTargets) { - bool validTarget = false; - for (const auto& targetActor : mValidTargets) + if (targetActor == target) { - if (targetActor == target) - { - validTarget = true; - break; - } + validTarget = true; + break; } - - return validTarget; } - return true; + return validTarget; +} + +std::optional Projectile::getWaterHitPosition() +{ + return std::exchange(mWaterHitPosition, std::nullopt); +} + +void Projectile::setWaterHitPosition(btVector3 pos) +{ + if (mCrossedWaterSurface) + return; + mCrossedWaterSurface = true; + mWaterHitPosition = pos; } } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 5ae963b9a8..fb50eebde8 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "components/misc/convert.hpp" @@ -32,7 +33,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -40,17 +41,13 @@ namespace MWPhysics void commitPositionChange(); void setPosition(const osg::Vec3f& position); + osg::Vec3f getPosition() const; btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - int getProjectileId() const - { - return mProjectileId; - } - bool isActive() const { return mActive.load(std::memory_order_acquire); @@ -65,18 +62,16 @@ namespace MWPhysics MWWorld::Ptr getCaster() const; void setCaster(MWWorld::Ptr caster); - osg::Vec3f getHitPos() const - { - assert(!mActive); - return Misc::Convert::toOsg(mHitPosition); - } + bool canTraverseWater() const; void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); - void activate(); void setValidTargets(const std::vector& targets); bool isValidTarget(const MWWorld::Ptr& target) const; + std::optional getWaterHitPosition(); + void setWaterHitPosition(btVector3 pos); + private: std::unique_ptr mShape; @@ -85,9 +80,12 @@ namespace MWPhysics std::unique_ptr mCollisionObject; btTransform mLocalTransform; bool mTransformUpdatePending; + bool mCanCrossWaterSurface; + bool mCrossedWaterSurface; std::atomic mActive; MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; + std::optional mWaterHitPosition; btVector3 mHitPosition; btVector3 mHitNormal; @@ -95,15 +93,11 @@ namespace MWPhysics mutable std::mutex mMutex; - osg::Vec3f mPosition; - PhysicsSystem *mPhysics; PhysicsTaskScheduler *mTaskScheduler; Projectile(const Projectile&); Projectile& operator=(const Projectile&); - - int mProjectileId; }; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp new file mode 100644 index 0000000000..0d0ac87202 --- /dev/null +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -0,0 +1,67 @@ +#include "../mwworld/class.hpp" + +#include "actor.hpp" +#include "collisiontype.hpp" +#include "projectile.hpp" +#include "projectileconvexcallback.hpp" +#include "ptrholder.hpp" + +namespace MWPhysics +{ + ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj) + : btCollisionWorld::ClosestConvexResultCallback(from, to) + , mMe(me), mProjectile(proj) + { + assert(mProjectile); + } + + btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) + { + // don't hit the caster + if (result.m_hitCollisionObject == mMe) + return 1.f; + + // don't hit the projectile + if (result.m_hitCollisionObject == mProjectile->getCollisionObject()) + return 1.f; + + btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); + switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup) + { + case CollisionType_Actor: + { + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getPtr())) + return 1.f; + mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal); + break; + } + case CollisionType_Projectile: + { + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getCaster())) + return 1.f; + target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + case CollisionType_Water: + { + mProjectile->setWaterHitPosition(m_hitPointWorld); + if (mProjectile->canTraverseWater()) + return 1.f; + mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + default: + { + mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + break; + } + } + + return result.m_hitFraction; + } + +} + diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp new file mode 100644 index 0000000000..c687de36c0 --- /dev/null +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H +#define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H + +#include + +#include + +class btCollisionObject; + +namespace MWPhysics +{ + class Projectile; + + class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback + { + public: + ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj); + + btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; + + private: + const btCollisionObject* mMe; + Projectile* mProjectile; + }; +} + +#endif diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index b176e83304..7c8375cb5a 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -29,7 +29,7 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index f50b6100a6..049d026e8e 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -7,7 +7,7 @@ #include "collisiontype.hpp" #include "actor.hpp" -#include "closestnotmeconvexresultcallback.hpp" +#include "actorconvexcallback.hpp" namespace MWPhysics { @@ -24,7 +24,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star to.setOrigin(btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0), world); + ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; @@ -62,7 +62,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const btTransform to(trans.getBasis(), btend); const btVector3 motion = btstart-btend; - ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); + ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 08c9e46627..c87f625053 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ #include "../mwsound/sound.hpp" +#include "../mwphysics/collisiontype.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/projectile.hpp" @@ -187,7 +189,7 @@ namespace MWWorld }; - void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, + float ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) { state.mNode = new osg::PositionAttitudeTransform; @@ -251,6 +253,7 @@ namespace MWWorld state.mNode->accept(assignVisitor); MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); + return projectile->getBound().radius(); } void ProjectileManager::update(State& state, float duration) @@ -305,7 +308,7 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); + const auto radius = createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) @@ -316,7 +319,7 @@ namespace MWWorld state.mSounds.push_back(sound); } - state.mProjectileId = mPhysics->addProjectile(caster, pos); + state.mProjectileId = mPhysics->addProjectile(caster, pos, radius, false); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -340,7 +343,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(actor, pos); + state.mProjectileId = mPhysics->addProjectile(actor, pos, 1.f, true); state.mToDelete = false; mProjectiles.push_back(state); } @@ -407,15 +410,7 @@ namespace MWWorld float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); - osg::Vec3f pos(magicBoltState.mNode->getPosition()); - osg::Vec3f newPos = pos + direction * duration * speed; - - for (const auto& sound : magicBoltState.mSounds) - sound->setPosition(newPos); - - magicBoltState.mNode->setPosition(newPos); - - mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); + osg::Vec3f newPos = projectile->getPosition() + direction * duration * speed; update(magicBoltState, duration); @@ -425,41 +420,7 @@ namespace MWWorld caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - // Check for impact - // TODO: use a proper btRigidBody / btGhostObject? - const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId); - - bool hit = false; - if (result.mHit) - { - hit = true; - if (result.mHitObject.isEmpty()) - { - // terrain or projectile - } - else - { - MWMechanics::CastSpell cast(caster, result.mHitObject); - cast.mHitPosition = pos; - cast.mId = magicBoltState.mSpellId; - cast.mSourceName = magicBoltState.mSourceName; - cast.mStack = false; - cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); - mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); - } - } - - // Explodes when hitting water - if (MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos)) - hit = true; - - if (hit) - { - MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject, - ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); - - cleanupMagicBolt(magicBoltState); - } + mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); } } @@ -467,9 +428,6 @@ namespace MWWorld { for (auto& projectileState : mProjectiles) { - if (projectileState.mToDelete) - continue; - auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; @@ -477,8 +435,7 @@ namespace MWWorld // simulating aerodynamics at all projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - osg::Vec3f pos(projectileState.mNode->getPosition()); - osg::Vec3f newPos = pos + projectileState.mVelocity * duration; + osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration; // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. if (!projectileState.mThrown) @@ -488,10 +445,6 @@ namespace MWWorld projectileState.mNode->setAttitude(orient); } - projectileState.mNode->setPosition(newPos); - - mPhysics->updateProjectile(projectileState.mProjectileId, newPos); - update(projectileState, duration); MWWorld::Ptr caster = projectileState.getCaster(); @@ -502,36 +455,7 @@ namespace MWWorld caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - // Check for impact - // TODO: use a proper btRigidBody / btGhostObject? - const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId); - - bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); - - if (result.mHit || underwater) - { - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) - { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) - bow = *invIt; - } - - if (caster.isEmpty()) - caster = result.mHitObject; - - MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength); - mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); - - if (underwater) - mRendering->emitWaterRipple(newPos); - - cleanupProjectile(projectileState); - } + mPhysics->updateProjectile(projectileState.mProjectileId, newPos); } } @@ -543,17 +467,19 @@ namespace MWWorld continue; auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + + if (const auto hitWaterPos = projectile->getWaterHitPosition()) + mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos)); + + const auto pos = projectile->getPosition(); + projectileState.mNode->setPosition(pos); + if (projectile->isActive()) continue; + const auto target = projectile->getTarget(); - const auto pos = projectile->getHitPos(); - MWWorld::Ptr caster = projectileState.getCaster(); + auto caster = projectileState.getCaster(); assert(target != caster); - if (!projectile->isValidTarget(target)) - { - projectile->activate(); - continue; - } if (caster.isEmpty()) caster = target; @@ -569,9 +495,8 @@ namespace MWWorld bow = *invIt; } - projectileState.mHitPosition = pos; - cleanupProjectile(projectileState); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); + cleanupProjectile(projectileState); } for (auto& magicBoltState : mMagicBolts) { @@ -579,20 +504,18 @@ namespace MWWorld continue; auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + + const auto pos = projectile->getPosition(); + magicBoltState.mNode->setPosition(pos); + for (const auto& sound : magicBoltState.mSounds) + sound->setPosition(pos); + if (projectile->isActive()) continue; + const auto target = projectile->getTarget(); - const auto pos = projectile->getHitPos(); - MWWorld::Ptr caster = magicBoltState.getCaster(); + const auto caster = magicBoltState.getCaster(); assert(target != caster); - if (!projectile->isValidTarget(target)) - { - projectile->activate(); - continue; - } - - magicBoltState.mHitPosition = pos; - cleanupMagicBolt(magicBoltState); MWMechanics::CastSpell cast(caster, target); cast.mHitPosition = pos; @@ -602,6 +525,7 @@ namespace MWWorld cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); + cleanupMagicBolt(magicBoltState); } mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), mProjectiles.end()); @@ -702,7 +626,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), 1.f, true); } catch(...) { @@ -747,7 +671,6 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); } catch(...) { @@ -755,7 +678,8 @@ namespace MWWorld } osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); + const auto radius = createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), radius, false); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 2cd570847b..e088dd7011 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -80,8 +80,6 @@ namespace MWWorld int mActorId; int mProjectileId; - osg::Vec3f mHitPosition; - // TODO: this will break when the game is saved and reloaded, since there is currently // no way to write identifiers for non-actors to a savegame. MWWorld::Ptr mCasterHandle; @@ -132,7 +130,7 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, + float createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); From cd5e31dc4ba2cdbbc80de93bf59ccc83c3bbda70 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 22 Jan 2021 19:04:07 +0000 Subject: [PATCH 0242/2859] Revert "Merge branch 'windowing-system' into 'master'" This reverts merge request !541 --- components/sdlutil/sdlcursormanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 964762ec76..56225868e3 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -20,7 +20,7 @@ #include "imagetosurface.hpp" -#if defined(OSG_LIBRARY_STATIC) && defined(USE_GRAPHICSWINDOW) +#if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() From eb80c997b8255e86b86096bc4ac9218929eaeddd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 22 Jan 2021 19:44:22 +0000 Subject: [PATCH 0243/2859] Avoid OSG setting array binding from multiple threads --- components/terrain/buffercache.cpp | 2 +- components/terrain/chunkmanager.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 5871b96bc0..f9eb7ae635 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -186,7 +186,7 @@ namespace Terrain int vertexCount = numVerts * numVerts; - osg::ref_ptr uvs (new osg::Vec2Array); + osg::ref_ptr uvs (new osg::Vec2Array(osg::Array::BIND_PER_VERTEX)); uvs->reserve(vertexCount); for (unsigned int col = 0; col < numVerts; ++col) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 041414a87a..50724628dd 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -197,8 +197,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve bool useCompositeMap = chunkSize >= mCompositeMapLevel; unsigned int numUvSets = useCompositeMap ? 1 : 2; - for (unsigned int i=0; isetTexCoordArray(i, mBufferCache.getUVBuffer(numVerts)); + geometry->setTexCoordArrayList(osg::Geometry::ArrayList(numUvSets, mBufferCache.getUVBuffer(numVerts))); geometry->createClusterCullingCallback(); From a401c517bfa000347443c2837a0b55718000a53a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 23 Jan 2021 00:56:46 +0100 Subject: [PATCH 0244/2859] Always unload height fields loadCell always adds a height field, but unloadCell only removed it for cells with height data. Reloading a cell overwrote the height field added earlier (leading to its destruction) while the navigator retained a reference to the now deleted collision shape, leading to a crash. --- apps/openmw/mwworld/scene.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 47b62862f7..427ee3aa8b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -340,17 +340,9 @@ namespace MWWorld if ((*iter)->getCell()->isExterior()) { - const ESM::Land* land = - MWBase::Environment::get().getWorld()->getStore().get().search( - (*iter)->getCell()->getGridX(), - (*iter)->getCell()->getGridY() - ); - if (land && land->mDataTypes&ESM::Land::DATA_VHGT) - { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->removeObject(DetourNavigator::ObjectId(heightField)); - mPhysics->removeHeightField(cellX, cellY); - } + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->removeObject(DetourNavigator::ObjectId(heightField)); + mPhysics->removeHeightField(cellX, cellY); } if ((*iter)->getCell()->hasWater()) From bd7d5a8f92640ceadaa70b22e650a5644f1b030f Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 23 Jan 2021 04:08:39 +0000 Subject: [PATCH 0245/2859] Fix memory leak in FontLoader::loadFontFromXml Tried building with -DCMAKE_CXX_FLAGS='-fsanitize=address -fsanitize-recover=address' and this was one of the reported leaks. --- components/fontloader/fontloader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 605d552435..09bf0b190b 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -630,6 +630,7 @@ namespace Gui } MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); + delete copy; } } From 3300e26c86ecd4c84cc6b099e2abe918a6a5ef3c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 23 Jan 2021 15:25:05 +0000 Subject: [PATCH 0246/2859] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3a3438a1..cede01f631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Bug #4623: Corprus implementation is incorrect Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level Bug #4764: Data race in osg ParticleSystem + Bug #4765: Data race in ChunkManager -> Array::setBinding Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #5101: Hostile followers travel with the player Bug #5108: Savegame bloating due to inefficient fog textures format From 1e113710efb68183300c228ffa05136072789659 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 23 Jan 2021 17:14:56 +0000 Subject: [PATCH 0247/2859] Clean-up FontLoader::loadFontFromXml fix Follow-up to !559 --- components/fontloader/fontloader.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 09bf0b190b..2bed079e17 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -593,7 +593,7 @@ namespace Gui if (createCopy) { - MyGUI::xml::ElementPtr copy = _node->createCopy(); + std::unique_ptr copy{_node->createCopy()}; MyGUI::xml::ElementEnumerator copyFont = copy->getElementEnumerator(); while (copyFont.next("Resource")) @@ -629,8 +629,7 @@ namespace Gui } } - MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); - delete copy; + MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy.get(), _file, _version); } } From 165c7314928dc281a364fa1a0143c45fd6d2adfd Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 23 Jan 2021 10:43:32 +0100 Subject: [PATCH 0248/2859] Remove physics dependency on basenode Necessary to be able to load physics objects from inactive cells. --- apps/openmw/mwclass/activator.cpp | 4 +-- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/actor.cpp | 6 +---- apps/openmw/mwclass/actor.hpp | 10 +++---- apps/openmw/mwclass/apparatus.cpp | 5 ---- apps/openmw/mwclass/apparatus.hpp | 2 -- apps/openmw/mwclass/armor.cpp | 5 ---- apps/openmw/mwclass/armor.hpp | 2 -- apps/openmw/mwclass/bodypart.cpp | 4 --- apps/openmw/mwclass/bodypart.hpp | 2 -- apps/openmw/mwclass/book.cpp | 5 ---- apps/openmw/mwclass/book.hpp | 2 -- apps/openmw/mwclass/clothing.cpp | 5 ---- apps/openmw/mwclass/clothing.hpp | 2 -- apps/openmw/mwclass/container.cpp | 4 +-- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/door.cpp | 4 +-- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 5 ---- apps/openmw/mwclass/ingredient.hpp | 2 -- apps/openmw/mwclass/light.cpp | 4 +-- apps/openmw/mwclass/light.hpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 5 ---- apps/openmw/mwclass/lockpick.hpp | 2 -- apps/openmw/mwclass/misc.cpp | 5 ---- apps/openmw/mwclass/misc.hpp | 2 -- apps/openmw/mwclass/potion.cpp | 5 ---- apps/openmw/mwclass/potion.hpp | 2 -- apps/openmw/mwclass/probe.cpp | 5 ---- apps/openmw/mwclass/probe.hpp | 2 -- apps/openmw/mwclass/repair.cpp | 5 ---- apps/openmw/mwclass/repair.hpp | 2 -- apps/openmw/mwclass/static.cpp | 4 +-- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwclass/weapon.cpp | 5 ---- apps/openmw/mwclass/weapon.hpp | 2 -- apps/openmw/mwphysics/actor.cpp | 6 ++--- apps/openmw/mwphysics/actor.hpp | 2 +- apps/openmw/mwphysics/object.cpp | 11 +++++--- apps/openmw/mwphysics/object.hpp | 4 +-- apps/openmw/mwphysics/physicssystem.cpp | 10 +++---- apps/openmw/mwphysics/physicssystem.hpp | 4 +-- apps/openmw/mwworld/class.cpp | 6 +---- apps/openmw/mwworld/class.hpp | 13 +++++---- apps/openmw/mwworld/scene.cpp | 36 +++++++++++++------------ apps/openmw/mwworld/worldimp.cpp | 2 +- 46 files changed, 68 insertions(+), 155 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index c54b1c3691..716e548e1d 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,10 +38,10 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 10ace6f74d..c7b65ef679 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,7 +17,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 33aeb26bb0..1789f1b19a 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -17,16 +17,12 @@ namespace MWClass { - Actor::Actor() {} - - Actor::~Actor() {} - void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 3d509b2768..98998b4eb2 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -15,16 +15,16 @@ namespace MWClass { protected: - Actor(); + Actor() = default; public: - virtual ~Actor(); + ~Actor() override = default; void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; @@ -46,8 +46,8 @@ namespace MWClass float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; // not implemented - Actor(const Actor&); - Actor& operator= (const Actor&); + Actor(const Actor&) = delete; + Actor& operator= (const Actor&) = delete; }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 518695fabf..e09e4804ce 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -26,11 +26,6 @@ namespace MWClass } } - void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 8087c57ba3..828abef25e 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -17,8 +17,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 3f9bfb859f..817adbc1f5 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -34,11 +34,6 @@ namespace MWClass } } - void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 4f04e0824b..f64f138a29 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -16,8 +16,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 0315d3ddb0..7fe798e27d 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -22,10 +22,6 @@ namespace MWClass } } - void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const - { - } - std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 13d9141386..0e372b884a 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -15,8 +15,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 4ea71e3ac2..51b9e39d7a 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -31,11 +31,6 @@ namespace MWClass } } - void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index c58e68ad87..f3d34c5168 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 6d7960aac2..400cd97e41 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -29,11 +29,6 @@ namespace MWClass } } - void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index a87e0cbe00..3d5c162aa4 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 28305c394e..3023466f08 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,10 +111,10 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 2dc0c06cae..74d9ac0da2 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -44,7 +44,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ba51d9c2bc..983953c20d 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -62,10 +62,10 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, MWPhysics::CollisionType_Door); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 6c2fa26b80..6d40a840b1 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,7 +18,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index a007ad115f..20f9576dff 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 5219cf39ce..2aa831f868 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 3bdf10f475..d4d196bc4b 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,7 +33,7 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -41,7 +41,7 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index e37dddc250..1b1794d4a7 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 9b8abc8f23..985b087711 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index fabae33435..d4b265e397 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 8d3cda6fe5..facab9d51c 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -37,11 +37,6 @@ namespace MWClass } } - void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 9bff85ca56..18788c7ed8 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 4af97e6345..56d9dff279 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -30,11 +30,6 @@ namespace MWClass } } - void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75d923f0ba..75b962164b 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index dba4e8c063..51273337a6 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -28,11 +28,6 @@ namespace MWClass } } - void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index a0a41dcfb6..ef9273a379 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 8907c8212e..f1b88e422b 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -25,11 +25,6 @@ namespace MWClass } } - void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index b9791e9cf4..c403449e18 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -14,8 +14,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 5551b3d731..108c4eaa20 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,10 +23,10 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, rotation); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 6bc783dad0..f856e9fd94 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 0d6a27cf60..6246c8fb09 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -34,11 +34,6 @@ namespace MWClass } } - void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const - { - // TODO: add option somewhere to enable collision for placeable objects - } - std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index f1824b7d14..db17e6b70f 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -15,8 +15,6 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; - std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 3b52ee934f..06600fd6ac 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -67,7 +67,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); if(!mRotationallyInvariant) - updateRotation(); + setRotation(mPtr.getRefData().getBaseNode()->getAttitude()); updatePosition(); addCollisionMask(getCollisionMask()); @@ -197,10 +197,10 @@ osg::Vec3f Actor::getPreviousPosition() const return mPreviousPosition; } -void Actor::updateRotation () +void Actor::setRotation(osg::Quat quat) { std::scoped_lock lock(mPositionMutex); - mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); + mRotation = quat; } bool Actor::isRotationallyInvariant() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 9d129f2ba6..4039f44811 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -49,7 +49,7 @@ namespace MWPhysics void enableCollisionBody(bool collision); void updateScale(); - void updateRotation(); + void setRotation(osg::Quat quat); /** * Return true if the collision shape looks the same no matter how its Z rotated. diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index e3615989dd..0a7b9540c5 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,7 +14,7 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) @@ -27,7 +27,7 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + setRotation(rotation); setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); @@ -51,10 +51,10 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(const btQuaternion& quat) + void Object::setRotation(osg::Quat quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(quat); + mLocalTransform.setRotation(Misc::Convert::toBullet(quat)); mTransformUpdatePending = true; } @@ -116,6 +116,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; + if (mPtr.getRefData().getBaseNode() == nullptr) + return true; + assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index cae8771809..c2273831e5 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,12 +26,12 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(const btQuaternion& quat); + void setRotation(osg::Quat quat); void setOrigin(const btVector3& vec); void commitPositionChange(); btCollisionObject* getCollisionObject(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ed0e0c915d..68cec48bcb 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -456,13 +456,13 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) @@ -621,12 +621,12 @@ namespace MWPhysics mTaskScheduler->updateSingleAabb(foundProjectile->second); } - void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + found->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -635,7 +635,7 @@ namespace MWPhysics { if (!foundActor->second->isRotationallyInvariant()) { - foundActor->second->updateRotation(); + foundActor->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(foundActor->second); } return; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6f901067a2..c61b368f8e 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -121,7 +121,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); @@ -141,7 +141,7 @@ namespace MWPhysics void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); - void updateRotation (const MWWorld::Ptr& ptr); + void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 950c8a6d49..becf912eaa 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -25,16 +25,12 @@ namespace MWWorld { std::map > Class::sClasses; - Class::Class() {} - - Class::~Class() {} - void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const { } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 1b3d4029e4..39fb6fe4c5 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "ptr.hpp" @@ -57,13 +58,9 @@ namespace MWWorld std::string mTypeName; - // not implemented - Class (const Class&); - Class& operator= (const Class&); - protected: - Class(); + Class() = default; std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items @@ -72,14 +69,16 @@ namespace MWWorld public: - virtual ~Class(); + virtual ~Class() = default; + Class (const Class&) = delete; + Class& operator= (const Class&) = delete; const std::string& getTypeName() const { return mTypeName; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual std::string getName (const ConstPtr& ptr) const = 0; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 427ee3aa8b..313e9a1527 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -75,18 +75,20 @@ namespace * osg::Quat(xr, osg::Vec3(-1, 0, 0)); } - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order) + osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) { - if (!ptr.getRefData().getBaseNode()) - return; + const auto pos = ptr.getRefData().getPosition(); + + const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) + : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos)); + + return rot; + } - rendering.rotateObject(ptr, - ptr.getClass().isActor() - ? makeActorOsgQuat(ptr.getRefData().getPosition()) - : (order == RotationOrder::inverse - ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) - : makeObjectOsgQuat(ptr.getRefData().getPosition())) - ); + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation) + { + if (ptr.getRefData().getBaseNode()) + rendering.rotateObject(ptr, rotation); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) @@ -117,11 +119,10 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); - else - ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode - setNodeRotation(ptr, rendering, RotationOrder::direct); - ptr.getClass().insertObject (ptr, model, physics); + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + setNodeRotation(ptr, rendering, rotation); + ptr.getClass().insertObject (ptr, model, rotation, physics); if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); @@ -276,7 +277,7 @@ namespace MWWorld { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); - setNodeRotation(ptr, mRendering, RotationOrder::direct); + setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); reloadTerrain(); } } @@ -292,8 +293,9 @@ namespace MWWorld void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - setNodeRotation(ptr, mRendering, order); - mPhysics->updateRotation(ptr); + const auto rot = makeNodeRotation(ptr, order); + setNodeRotation(ptr, mRendering, rot); + mPhysics->updateRotation(ptr, rot); } void Scene::updateObjectScale(const Ptr &ptr) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bf5b6db738..af9a5e0bb3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1407,7 +1407,7 @@ namespace MWWorld mWorldScene->removeFromPagedRefs(ptr); mRendering->rotateObject(ptr, rotate); - mPhysics->updateRotation(ptr); + mPhysics->updateRotation(ptr, rotate); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); From f031a191b847443c848637b17d0936a43b5070b5 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 23 Jan 2021 20:59:24 +0100 Subject: [PATCH 0249/2859] Some actors are supposed to spawn on a static object that belong to an adjacent cell. Since actors can be active in 3x3 grid around the player, we need to first load all statics in a 5x5 grid around the player. Split load and unloading in 2 phases. Add an mInactiveCells set into the scene, which contains all cells inside the aforementioned 5x5 grid. These cells contains only heightfields and physics objects of static class. --- apps/openmw/mwclass/static.cpp | 5 + apps/openmw/mwclass/static.hpp | 2 + apps/openmw/mwphysics/physicssystem.cpp | 5 +- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 13 +- apps/openmw/mwworld/class.hpp | 4 + apps/openmw/mwworld/scene.cpp | 405 ++++++++++++++---------- apps/openmw/mwworld/scene.hpp | 15 +- 8 files changed, 269 insertions(+), 182 deletions(-) diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 108c4eaa20..28156c97f6 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -63,4 +63,9 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } + + bool Static::isStatic() const + { + return true; + } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index f856e9fd94..d0f4913f0f 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -25,6 +25,8 @@ namespace MWClass static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; + + bool isStatic() const override; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 68cec48bcb..5a9a0be832 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -469,7 +469,7 @@ namespace MWPhysics mAnimatedObjects.insert(obj.get()); } - void PhysicsSystem::remove(const MWWorld::Ptr &ptr) + void PhysicsSystem::remove(const MWWorld::Ptr &ptr, bool keepObject) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) @@ -479,7 +479,8 @@ namespace MWPhysics mAnimatedObjects.erase(found->second.get()); - mObjects.erase(found); + if (!keepObject) + mObjects.erase(found); } ActorMap::iterator foundActor = mActors.find(ptr); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index c61b368f8e..715a6cd1a2 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -138,7 +138,7 @@ namespace MWPhysics Projectile* getProjectile(int projectileId) const; // Object or Actor - void remove (const MWWorld::Ptr& ptr); + void remove (const MWWorld::Ptr& ptr, bool keepObject = false); void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index e68b383b77..5985d06fb6 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -18,12 +18,23 @@ namespace MWWorld if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); - mObjects.push_back (ptr); } + mObjects.push_back (ptr); return true; } }; + + struct ListObjectsVisitor + { + std::vector mObjects; + + bool operator() (MWWorld::Ptr ptr) + { + mObjects.push_back (ptr); + return true; + } + }; } #endif diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 39fb6fe4c5..592552d47f 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -318,6 +318,10 @@ namespace MWWorld return false; } + virtual bool isStatic() const { + return false; + } + virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 313e9a1527..fcf2c4b387 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -105,7 +105,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs) + MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -113,25 +113,29 @@ namespace return; } - bool useAnim = ptr.getClass().useAnim(); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + if (!onlyPhysics) + { + bool useAnim = ptr.getClass().useAnim(); - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) - ptr.getClass().insertObjectRendering(ptr, model, rendering); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - setNodeRotation(ptr, rendering, rotation); - ptr.getClass().insertObject (ptr, model, rotation, physics); + setNodeRotation(ptr, rendering, rotation); - if (useAnim) - MWBase::Environment::get().getMechanicsManager()->add(ptr); + if (useAnim) + MWBase::Environment::get().getMechanicsManager()->add(ptr); - if (ptr.getClass().isActor()) - rendering.addWaterRippleEmitter(ptr); + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); - // Restore effect particles - MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + } + if (!physics.getObject(ptr)) + ptr.getClass().insertObject (ptr, model, rotation, physics); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) @@ -202,11 +206,12 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; + bool mOnlyStatics; bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test); bool operator() (const MWWorld::Ptr& ptr); @@ -214,8 +219,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) - : mCell (cell), mLoadingListener (loadingListener), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test) + : mCell (cell), mLoadingListener (loadingListener), mOnlyStatics(onlyStatics), mTest(test) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -231,7 +236,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && ((mOnlyStatics && ptr.getClass().isStatic()) || !mOnlyStatics)) { try { @@ -264,6 +269,16 @@ namespace return std::abs(cellPosition.first) + std::abs(cellPosition.second); } + bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection) + { + for (auto *cell : collection) + { + assert(cell->getCell()->isExterior()); + if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY()) + return true; + } + return false; + } } @@ -315,15 +330,41 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) + void Scene::unloadInactiveCell (CellStore* cell, bool test) + { + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) != mInactiveCells.end()); + if (!test) + Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); + + ListObjectsVisitor visitor; + + cell->forEach(visitor); + for (const auto& ptr : visitor.mObjects) + mPhysics->remove(ptr); + + if (cell->getCell()->isExterior()) + { + const auto cellX = cell->getCell()->getGridX(); + const auto cellY = cell->getCell()->getGridY(); + mPhysics->removeHeightField(cellX, cellY); + } + + mInactiveCells.erase(cell); + } + + void Scene::deactivateCell(CellStore* cell, bool test) { + assert(mInactiveCells.find(cell) != mInactiveCells.end()); + if (mActiveCells.find(cell) == mActiveCells.end()) + return; if (!test) - Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); + Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; - (*iter)->forEach(visitor); + cell->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { @@ -334,140 +375,157 @@ namespace MWWorld navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } - mPhysics->remove(ptr); + mPhysics->remove(ptr, ptr.getClass().isStatic()); } - const auto cellX = (*iter)->getCell()->getGridX(); - const auto cellY = (*iter)->getCell()->getGridY(); + const auto cellX = cell->getCell()->getGridX(); + const auto cellY = cell->getCell()->getGridY(); - if ((*iter)->getCell()->isExterior()) + if (cell->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(DetourNavigator::ObjectId(heightField)); - mPhysics->removeHeightField(cellX, cellY); } - if ((*iter)->getCell()->hasWater()) + if (cell->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); - if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) navigator->removePathgrid(*pathgrid); const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getMechanicsManager()->drop (*iter); + MWBase::Environment::get().getMechanicsManager()->drop (cell); - mRendering.removeCell(*iter); - MWBase::Environment::get().getWindowManager()->removeCell(*iter); + mRendering.removeCell(cell); + MWBase::Environment::get().getWindowManager()->removeCell(cell); - MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); + MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell); - MWBase::Environment::get().getSoundManager()->stopSound (*iter); - mActiveCells.erase(*iter); + MWBase::Environment::get().getSoundManager()->stopSound (cell); + mActiveCells.erase(cell); } - void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { - std::pair result = mActiveCells.insert(cell); + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) != mInactiveCells.end()); + mActiveCells.insert(cell); + + if (test) + Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + + const auto world = MWBase::Environment::get().getWorld(); + const auto navigator = world->getNavigator(); + + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); - if(result.second) + if (!test && cell->getCell()->isExterior()) { - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), + heightField->getCollisionObject()->getWorldTransform()); + } - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + navigator->addPathgrid(*cell->getCell(), *pathgrid); - const auto world = MWBase::Environment::get().getWorld(); - const auto navigator = world->getNavigator(); + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); + if (respawn) + cell->respawn(); + + insertCell (*cell, loadingListener, false, test); - // Load terrain physics first... - if (!test && cell->getCell()->isExterior()) + mRendering.addCell(cell); + if (!test) + { + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) { - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); + + if (cell->getCell()->isExterior()) { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); } else { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + cell->getWaterLevel(), btTransform::getIdentity()); } - - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), - heightField->getCollisionObject()->getWorldTransform()); } + else + mPhysics->disableWater(); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) - navigator->addPathgrid(*cell->getCell(), *pathgrid); - - // register local scripts - // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); - if (respawn) - cell->respawn(); + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + mRendering.configureAmbient(cell->getCell()); + } - // ... then references. This is important for adjustPosition to work correctly. - insertCell (*cell, loadingListener, test); + mPreloader->notifyLoaded(cell); + } - mRendering.addCell(cell); - if (!test) - { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) - { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); + void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) + { + assert(mActiveCells.find(cell) == mActiveCells.end()); + assert(mInactiveCells.find(cell) == mInactiveCells.end()); + mInactiveCells.insert(cell); - if (cell->getCell()->isExterior()) - { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); - } - else - { - navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - cell->getWaterLevel(), btTransform::getIdentity()); - } - } - else - mPhysics->disableWater(); + if (test) + Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); + if (!test && cell->getCell()->isExterior()) + { + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - { + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); - mRendering.configureAmbient(cell->getCell()); - } + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + if (data) + { + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + } + else + { + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } } - mPreloader->notifyLoaded(cell); + insertCell (*cell, loadingListener, true, test); } void Scene::clear() { - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) - unloadCell (active++); + for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + { + auto* cell = *iter++; + deactivateCell(cell); + unloadInactiveCell (cell); + } assert(mActiveCells.empty()); + assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -510,20 +568,24 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) + for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) { - if ((*active)->getCell()->isExterior()) + auto* cell = *iter++; + if (cell->getCell()->isExterior()) { - if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && - std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) - { - // keep cells within the new grid - ++active; - continue; - } + const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); + const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); + if (dx > mHalfGridSize || dy > mHalfGridSize) + deactivateCell(cell); + + if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) + unloadInactiveCell(cell); + } + else + { + deactivateCell(cell); + unloadInactiveCell(cell); } - unloadCell (active++); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -535,32 +597,24 @@ namespace MWWorld mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; - std::vector> cellsPositionsToLoad; - // get the number of refs to load - for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) + const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector> { - for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) + std::vector> cellsPositionsToLoad; + for (int x = playerCellX - range; x <= playerCellX + range; ++x) { - CellStoreCollection::iterator iter = mActiveCells.begin(); - - while (iter!=mActiveCells.end()) + for (int y = playerCellY - range; y <= playerCellY + range; ++y) { - assert ((*iter)->getCell()->isExterior()); - - if (x==(*iter)->getCell()->getGridX() && - y==(*iter)->getCell()->getGridY()) - break; - - ++iter; - } - - if (iter==mActiveCells.end()) - { - refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); - cellsPositionsToLoad.emplace_back(x, y); + if (!isCellInCollection(x, y, collection)) + { + refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + cellsPositionsToLoad.emplace_back(x, y); + } } } - } + return cellsPositionsToLoad; + }; + auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); + auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -584,30 +638,26 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); + std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), + [&] (const std::pair& lhs, const std::pair& rhs) { + return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); + }); + // Load cells - for (const auto& cellPosition : cellsPositionsToLoad) + for (const auto& [x,y] : cellsPositionsToLoadInactive) { - const auto x = cellPosition.first; - const auto y = cellPosition.second; - - CellStoreCollection::iterator iter = mActiveCells.begin(); - - while (iter != mActiveCells.end()) + if (!isCellInCollection(x, y, mInactiveCells)) { - assert ((*iter)->getCell()->isExterior()); - - if (x == (*iter)->getCell()->getGridX() && - y == (*iter)->getCell()->getGridY()) - break; - - ++iter; + CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); + loadInactiveCell (cell, loadingListener); } - - if (iter == mActiveCells.end()) + } + for (const auto& [x,y] : cellsPositionsToLoad) + { + if (!isCellInCollection(x, y, mActiveCells)) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - - loadCell (cell, loadingListener, changeEvent); + activateCell (cell, loadingListener, changeEvent); } } @@ -640,7 +690,8 @@ namespace MWWorld CellStoreCollection::iterator iter = mActiveCells.begin(); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadCell (cell, loadingListener, false, true); + loadInactiveCell (cell, loadingListener, true); + activateCell (cell, loadingListener, false, true); iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -648,7 +699,8 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - unloadCell(iter, true); + deactivateCell(*iter, true); + unloadInactiveCell (*iter, true); break; } @@ -686,7 +738,8 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadCell (cell, loadingListener, false, true); + loadInactiveCell (cell, loadingListener, true); + activateCell (cell, loadingListener, false, true); CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -695,7 +748,8 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - unloadCell(iter, true); + deactivateCell(*iter, true); + unloadInactiveCell (*iter, true); break; } @@ -818,15 +872,21 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - CellStoreCollection::iterator active = mActiveCells.begin(); - while (active!=mActiveCells.end()) - unloadCell (active++); + for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + { + auto* cell = *iter++; + deactivateCell(cell); + unloadInactiveCell(cell); + } + assert(mActiveCells.empty()); + assert(mInactiveCells.empty()); loadingListener->setProgressRange(cell->count()); // Load cell. mPagedRefs.clear(); - loadCell (cell, loadingListener, changeEvent); + loadInactiveCell (cell, loadingListener); + activateCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -874,23 +934,26 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test) { - InsertVisitor insertVisitor (cell, *loadingListener, test); + InsertVisitor insertVisitor (cell, *loadingListener, onlyStatics, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyStatics); }); + if (!onlyStatics) + { + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + PositionVisitor posVisitor; + cell.forEach (posVisitor); + } } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs); + addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index a70d3ccdd5..33c7b78d09 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -65,13 +65,13 @@ namespace MWWorld class Scene { public: - - typedef std::set CellStoreCollection; + using CellStoreCollection = std::set; private: CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; + CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -92,7 +92,7 @@ namespace MWWorld std::set mPagedRefs; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test = false); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -108,6 +108,11 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; + void unloadInactiveCell (CellStore* cell, bool test = false); + void deactivateCell (CellStore* cell, bool test = false); + void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); + void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); + public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, @@ -119,10 +124,6 @@ namespace MWWorld void preloadTerrain(const osg::Vec3f& pos, bool sync=false); void reloadTerrain(); - void unloadCell (CellStoreCollection::iterator iter, bool test = false); - - void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void playerMoved (const osg::Vec3f& pos); void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); From f219c5992bc6efe688ab128915f2d36c39da47a0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 24 Jan 2021 11:28:26 +0100 Subject: [PATCH 0250/2859] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cede01f631..64528e5c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key + Bug #5379: Wandering NPCs falling through cantons Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory From a2171875a069c2704ee035f1a407eaacee2a3979 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 24 Jan 2021 15:15:51 +0100 Subject: [PATCH 0251/2859] Prevent nullptr access --- apps/openmw/mwworld/projectilemanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index c87f625053..092ce270e9 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -428,6 +428,9 @@ namespace MWWorld { for (auto& projectileState : mProjectiles) { + if (projectileState.mToDelete) + continue; + auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; From fe815d3d8dcfe5007b229082370113865c5ba930 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 24 Jan 2021 15:22:27 +0000 Subject: [PATCH 0252/2859] Fix memory leak in MWInput mListener wasn't being cleaned up --- apps/openmw/mwinput/bindingsmanager.cpp | 13 ++++++------- apps/openmw/mwinput/bindingsmanager.hpp | 5 +++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 18fac6ae29..851e33a87c 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -171,16 +171,16 @@ namespace MWInput , mDragDrop(false) { std::string file = userFileExists ? userFile : ""; - mInputBinder = new InputControlSystem(file); - mListener = new BindingsListener(mInputBinder, this); - mInputBinder->setDetectingBindingListener(mListener); + mInputBinder = std::make_unique(file); + mListener = std::make_unique(mInputBinder.get(), this); + mInputBinder->setDetectingBindingListener(mListener.get()); loadKeyDefaults(); loadControllerDefaults(); for (int i = 0; i < A_Last; ++i) { - mInputBinder->getChannel(i)->addListener(mListener); + mInputBinder->getChannel(i)->addListener(mListener.get()); } } @@ -192,7 +192,6 @@ namespace MWInput BindingsManager::~BindingsManager() { mInputBinder->save(mUserFile); - delete mInputBinder; } void BindingsManager::update(float dt) @@ -315,7 +314,7 @@ namespace MWInput && mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS && mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED)) { - clearAllKeyBindings(mInputBinder, control); + clearAllKeyBindings(mInputBinder.get(), control); if (defaultKeyBindings.find(i) != defaultKeyBindings.end() && (force || !mInputBinder->isKeyBound(defaultKeyBindings[i]))) @@ -402,7 +401,7 @@ namespace MWInput if (!controlExists || force || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS::InputControlSystem::UNASSIGNED && mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS)) { - clearAllControllerBindings(mInputBinder, control); + clearAllControllerBindings(mInputBinder.get(), control); if (defaultButtonBindings.find(i) != defaultButtonBindings.end() && (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i]))) diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index 7a44a1a335..74416d3c7f 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -1,6 +1,7 @@ #ifndef MWINPUT_MWBINDINGSMANAGER_H #define MWINPUT_MWBINDINGSMANAGER_H +#include #include #include @@ -64,8 +65,8 @@ namespace MWInput private: void setupSDLKeyMappings(); - InputControlSystem* mInputBinder; - BindingsListener* mListener; + std::unique_ptr mInputBinder; + std::unique_ptr mListener; std::string mUserFile; From 3bb551a6f12e1c90f884a82b7806832a38bf2284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 25 Jan 2021 10:01:39 +0000 Subject: [PATCH 0253/2859] Show level multipliers in levelup tooltip --- apps/openmw/mwgui/statswindow.cpp | 21 +++++++++++---------- apps/openmw/mwmechanics/npcstats.cpp | 5 ----- apps/openmw/mwmechanics/npcstats.hpp | 2 -- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 66a1ea1eff..4c4450ee00 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -337,21 +337,22 @@ namespace MWGui int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); - std::stringstream detail; - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - if (auto increase = PCstats.getLevelUpAttributeIncrease(i)) - detail << (detail.str().empty() ? "" : "\n") << "#{" - << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[i]) - << "} x" << MyGUI::utility::toString(increase); - } - if (!detail.str().empty()) - levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" + MyGUI::utility::toString(max)); } + std::stringstream detail; + for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) + { + float mult = PCstats.getLevelupAttributeMultiplier(attribute); + mult = std::min(mult, 100 - PCstats.getAttribute(attribute).getBase()); + if (mult > 1) + detail << (detail.str().empty() ? "" : "\n") << "#{" + << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[attribute]) + << "} x" << MyGUI::utility::toString(mult); + } + levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str())); setFactions(PCstats.getFactionRanks()); setExpelled(PCstats.getExpelled ()); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 71453cd07c..5d19368bf6 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -322,11 +322,6 @@ void MWMechanics::NpcStats::updateHealth() setHealth(floor(0.5f * (strength + endurance))); } -int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const -{ - return mSkillIncreases[attribute]; -} - int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const { int num = mSkillIncreases[attribute]; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index cab52cb281..9bd8e20ad7 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -87,8 +87,6 @@ namespace MWMechanics int getLevelProgress() const; - int getLevelUpAttributeIncrease(int attribute) const; - int getLevelupAttributeMultiplier(int attribute) const; int getSkillIncreasesForSpecialization(int spec) const; From bc2cec86e9ca16abdab140aff40a1739e35c066a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 24 Dec 2020 04:18:39 +0100 Subject: [PATCH 0254/2859] Fix bug: NPCs doesn't move if the target is exactly above or exactly below. --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 276321b81a..595f9d629c 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -309,7 +309,7 @@ namespace MWMechanics if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) mPath.pop_front(); - if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) + if (mPath.size() == 1 && (mPath.front() - position).length2() < destinationTolerance * destinationTolerance) mPath.pop_front(); } From 663e0a10551fbd394d27aa4aa31321cda88b382b Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Tue, 26 Jan 2021 19:28:09 +0100 Subject: [PATCH 0255/2859] Fix a race condition in loading screen. --- apps/openmw/mwgui/loadingscreen.cpp | 49 ++--------------------------- apps/openmw/mwgui/loadingscreen.hpp | 2 -- 2 files changed, 2 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 7ddc8c5507..aeaacaa638 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -45,8 +45,6 @@ namespace MWGui , mNestedLoadingCount(0) , mProgress(0) , mShowWallpaper(true) - , mOldCallback(nullptr) - , mHasCallback(false) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); @@ -147,35 +145,11 @@ namespace MWGui void operator () (osg::RenderInfo& renderInfo) const override { - { - std::unique_lock lock(mMutex); - mOneshot = false; - } - mSignal.notify_all(); - int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); - { - std::unique_lock lock(mMutex); - mOneshot = false; - } - mSignal.notify_all(); - } - - void wait() - { - std::unique_lock lock(mMutex); - while (mOneshot) - mSignal.wait(lock); - } - - void waitUntilInvoked() - { - std::unique_lock lock(mMutex); - while (mOneshot) - mSignal.wait(lock); + mOneshot = false; } void reset() @@ -185,8 +159,6 @@ namespace MWGui private: mutable bool mOneshot; - mutable std::mutex mMutex; - mutable std::condition_variable mSignal; osg::ref_ptr mTexture; }; @@ -362,14 +334,12 @@ namespace MWGui } #if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) + mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback); #else - // TODO: Remove once we officially end support for OSG versions pre 3.5.10 - mOldCallback = mViewer->getCamera()->getInitialDrawCallback(); mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback); #endif mCopyFramebufferToTextureCallback->reset(); - mHasCallback = true; mBackgroundImage->setBackgroundImage(""); mBackgroundImage->setVisible(false); @@ -412,21 +382,6 @@ namespace MWGui mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - if (mHasCallback) - { - mCopyFramebufferToTextureCallback->waitUntilInvoked(); - - // Note that we are removing the callback before the draw thread has returned from it. - // This is OK as we are retaining the ref_ptr. -#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) - mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); -#else - // TODO: Remove once we officially end support for OSG versions pre 3.5.10 - mViewer->getCamera()->setInitialDrawCallback(mOldCallback); -#endif - mHasCallback = false; - } - mLastRenderTime = mTimer.time_m(); } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 5d86ed3896..e1c652386d 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -87,8 +87,6 @@ namespace MWGui osg::ref_ptr mTexture; osg::ref_ptr mCopyFramebufferToTextureCallback; - osg::ref_ptr mOldCallback; - bool mHasCallback; std::unique_ptr mGuiTexture; void changeWallpaper(); From 14cf0ce1dc67d561a04a3e5b13fa633af54ca939 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 12 Jan 2020 11:42:47 +0400 Subject: [PATCH 0256/2859] Implement instanced groundcover --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/engine.cpp | 9 +- apps/openmw/engine.hpp | 2 + apps/openmw/main.cpp | 15 +- apps/openmw/mwgui/settingswindow.cpp | 4 +- apps/openmw/mwrender/groundcover.cpp | 264 ++++++++++++++++++ apps/openmw/mwrender/groundcover.hpp | 69 +++++ apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 77 ++++- apps/openmw/mwrender/renderingmanager.hpp | 9 +- apps/openmw/mwrender/vismask.hpp | 4 +- apps/openmw/mwrender/water.cpp | 5 +- apps/openmw/mwworld/cellpreloader.cpp | 7 + apps/openmw/mwworld/cellreflist.hpp | 2 + apps/openmw/mwworld/cellstore.cpp | 13 + apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 18 +- apps/openmw/mwworld/esmloader.hpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 9 + apps/openmw/mwworld/esmstore.hpp | 17 ++ apps/openmw/mwworld/worldimp.cpp | 29 +- apps/openmw/mwworld/worldimp.hpp | 3 +- components/config/gamesettings.cpp | 2 + components/esm/esmreader.cpp | 9 +- components/esm/esmreader.hpp | 6 +- components/esm/loadstat.cpp | 2 + components/esm/loadstat.hpp | 2 + components/resource/scenemanager.cpp | 6 +- components/resource/scenemanager.hpp | 2 +- components/resource/stats.cpp | 4 +- components/shader/shadermanager.cpp | 2 + components/terrain/chunkmanager.cpp | 2 +- components/terrain/chunkmanager.hpp | 2 +- components/terrain/quadtreeworld.cpp | 29 +- components/terrain/quadtreeworld.hpp | 5 +- components/terrain/terraingrid.cpp | 8 + components/terrain/terraingrid.hpp | 1 + components/terrain/world.cpp | 41 ++- components/terrain/world.hpp | 1 + docs/source/reference/modding/extended.rst | 56 +++- .../modding/settings/groundcover.rst | 68 +++++ .../reference/modding/settings/index.rst | 1 + .../reference/modding/settings/shaders.rst | 1 + .../reference/modding/settings/water.rst | 3 +- files/mygui/openmw_settings_window.layout | 1 + files/settings-default.cfg | 19 ++ files/shaders/CMakeLists.txt | 2 + files/shaders/groundcover_fragment.glsl | 93 ++++++ files/shaders/groundcover_vertex.glsl | 143 ++++++++++ files/shaders/lighting.glsl | 15 +- 50 files changed, 1015 insertions(+), 74 deletions(-) create mode 100644 apps/openmw/mwrender/groundcover.cpp create mode 100644 apps/openmw/mwrender/groundcover.hpp create mode 100644 docs/source/reference/modding/settings/groundcover.rst create mode 100644 files/shaders/groundcover_fragment.glsl create mode 100644 files/shaders/groundcover_vertex.glsl diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c01cbe60c7..fdc47e8d7c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover ) add_openmw_dir (mwinput diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ead2726cd3..103d06f31d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -460,6 +460,11 @@ void OMW::Engine::addContentFile(const std::string& file) mContentFiles.push_back(file); } +void OMW::Engine::addGroundcoverFile(const std::string& file) +{ + mGroundcoverFiles.emplace_back(file); +} + void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; @@ -720,8 +725,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // Create the world - mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), - mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName, + mEnvironment.setWorld(new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3dd1a69b27..ff362f4b69 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -85,6 +85,7 @@ namespace OMW osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; + std::vector mGroundcoverFiles; bool mSkipMenu; bool mUseSound; bool mCompileAll; @@ -155,6 +156,7 @@ namespace OMW * @param file - filename (extension is required) */ void addContentFile(const std::string& file); + void addGroundcoverFile(const std::string& file); /// Disable or enable all sounds void setSoundUsage(bool soundUsage); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 8eaac36e81..709ffda2cb 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -62,6 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") + ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") @@ -190,11 +193,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - StringsVector::const_iterator it(content.begin()); - StringsVector::const_iterator end(content.end()); - for (; it != end; ++it) + for (auto& file : content) + { + engine.addContentFile(file); + } + + StringsVector groundcover = variables["groundcover"].as().toStdStringVector(); + for (auto& file : groundcover) { - engine.addContentFile(*it); + engine.addGroundcoverFile(file); } // startup-settings diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 68dac4a95c..538b3db5ed 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -269,7 +269,7 @@ namespace MWGui mWaterTextureSize->setIndexSelected(2); int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - waterReflectionDetail = std::min(4, std::max(0, waterReflectionDetail)); + waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); @@ -353,7 +353,7 @@ namespace MWGui void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min((unsigned int)4, (unsigned int)pos); + unsigned int level = std::min((unsigned int)5, (unsigned int)pos); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp new file mode 100644 index 0000000000..b9fdc2e280 --- /dev/null +++ b/apps/openmw/mwrender/groundcover.cpp @@ -0,0 +1,264 @@ +#include "groundcover.hpp" + +#include +#include + +#include + +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" + +#include "vismask.hpp" + +namespace MWRender +{ + void GroundcoverUpdater::setWindSpeed(float windSpeed) + { + mWindSpeed = windSpeed; + } + + void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos) + { + mPlayerPos = playerPos; + } + + void GroundcoverUpdater::setDefaults(osg::StateSet *stateset) + { + osg::ref_ptr windUniform = new osg::Uniform("windSpeed", 0.0f); + stateset->addUniform(windUniform.get()); + + osg::ref_ptr playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)); + stateset->addUniform(playerPosUniform.get()); + } + + void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + osg::ref_ptr windUniform = stateset->getUniform("windSpeed"); + if (windUniform != nullptr) + windUniform->set(mWindSpeed); + + osg::ref_ptr playerPosUniform = stateset->getUniform("playerPos"); + if (playerPosUniform != nullptr) + playerPosUniform->set(mPlayerPos); + } + + class InstancingVisitor : public osg::NodeVisitor + { + public: + InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mInstances(instances) + , mChunkPosition(chunkPosition) + { + } + + void apply(osg::Node& node) override + { + osg::ref_ptr ss = node.getStateSet(); + if (ss != nullptr) + { + removeAlpha(ss); + } + + traverse(node); + } + + void apply(osg::Geometry& geom) override + { + for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) + { + geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); + } + + osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); + osg::BoundingBox box; + float radius = geom.getBoundingBox().radius(); + for (unsigned int i = 0; i < transforms->getNumElements(); i++) + { + osg::Vec3f pos(mInstances[i].mPos.asVec3()); + osg::Vec3f relativePos = pos - mChunkPosition; + (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); + + // Use an additional margin due to groundcover animation + float instanceRadius = radius * mInstances[i].mScale * 1.1f; + osg::BoundingSphere instanceBounds(relativePos, instanceRadius); + box.expandBy(instanceBounds); + } + + geom.setInitialBound(box); + + osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); + for (unsigned int i = 0; i < rotations->getNumElements(); i++) + { + (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); + } + + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); + geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); + + osg::ref_ptr ss = geom.getOrCreateStateSet(); + ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); + ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + + removeAlpha(ss); + + traverse(geom); + } + private: + std::vector mInstances; + osg::Vec3f mChunkPosition; + + void removeAlpha(osg::StateSet* stateset) + { + // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + stateset->setRenderBinToInherit(); + } + }; + + class DensityCalculator + { + public: + DensityCalculator(float density) + : mDensity(density) + { + } + + bool isInstanceEnabled() + { + if (mDensity >= 1.f) return true; + + mCurrentGroundcover += mDensity; + if (mCurrentGroundcover < 1.f) return false; + + mCurrentGroundcover -= 1.f; + + return true; + } + void reset() { mCurrentGroundcover = 0.f; } + + private: + float mCurrentGroundcover = 0.f; + float mDensity = 0.f; + }; + + inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) + { + osg::Vec2f size = maxBound - minBound; + if (size.x() >=1 && size.y() >=1) return true; + + osg::Vec3f pos = ref.mPos.asVec3(); + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + return false; + + return true; + } + + osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + ChunkId id = std::make_tuple(center, size, activeGrid); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(id); + if (obj) + return obj->asNode(); + else + { + InstanceMap instances; + collectInstances(instances, size, center); + osg::ref_ptr node = createChunk(instances, center); + mCache->addEntryToObjectCache(id, node.get()); + return node; + } + } + + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + , mDensity(density) + { + } + + void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); + osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); + DensityCalculator calculator(mDensity); + std::vector esm; + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) + { + for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) + { + const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); + if (!cell) continue; + + calculator.reset(); + for (size_t i=0; imContextList.size(); ++i) + { + unsigned int index = cell->mContextList.at(i).index; + if (esm.size() <= index) + esm.resize(index+1); + cell->restore(esm[index], i); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + bool deleted = false; + while(cell->getNextRef(esm[index], ref, deleted)) + { + if (deleted) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + std::string model; + if (!store.isGroundcover(ref.mRefID, model)) continue; + if (model.empty()) continue; + + if (!calculator.isInstanceEnabled()) continue; + if (!isInChunkBorders(ref, minBound, maxBound)) continue; + + model = "meshes/" + model; + instances[model].emplace_back(ref, model); + } + } + } + } + } + + osg::ref_ptr Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center) + { + osg::ref_ptr group = new osg::Group; + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + for (auto& pair : instances) + { + const osg::Node* temp = mSceneManager->getTemplate(pair.first); + osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); + + // Keep link to original mesh to keep it in cache + group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); + + InstancingVisitor visitor(pair.second, worldCenter); + node->accept(visitor); + group->addChild(node); + } + + group->getBound(); + group->setNodeMask(Mask_Groundcover); + mSceneManager->recreateShaders(group, "groundcover", false, true); + + return group; + } + + unsigned int Groundcover::getNodeMask() + { + return Mask_Groundcover; + } + + void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const + { + stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); + } +} diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp new file mode 100644 index 0000000000..cd80978bef --- /dev/null +++ b/apps/openmw/mwrender/groundcover.hpp @@ -0,0 +1,69 @@ +#ifndef OPENMW_MWRENDER_GROUNDCOVER_H +#define OPENMW_MWRENDER_GROUNDCOVER_H + +#include +#include +#include +#include + +namespace MWRender +{ + class GroundcoverUpdater : public SceneUtil::StateSetUpdater + { + public: + GroundcoverUpdater() + : mWindSpeed(0.f) + , mPlayerPos(osg::Vec3f()) + { + } + + void setWindSpeed(float windSpeed); + void setPlayerPos(osg::Vec3f playerPos); + + protected: + void setDefaults(osg::StateSet *stateset) override; + void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; + + private: + float mWindSpeed; + osg::Vec3f mPlayerPos; + }; + + typedef std::tuple ChunkId; // Center, Size, ActiveGrid + class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager + { + public: + Groundcover(Resource::SceneManager* sceneManager, float density); + ~Groundcover() = default; + + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + + unsigned int getNodeMask() override; + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + + struct GroundcoverEntry + { + ESM::Position mPos; + float mScale; + std::string mModel; + + GroundcoverEntry(const ESM::CellRef& ref, const std::string& model) + { + mPos = ref.mPos; + mScale = ref.mScale; + mModel = model; + } + }; + + private: + Resource::SceneManager* mSceneManager; + float mDensity; + + typedef std::map> InstanceMap; + osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); + void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center); + }; +} + +#endif diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index d8e856e769..478fde0f86 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -398,6 +398,7 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } + if (store.isGroundcover(ref.mRefID)) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ce431d2e2..c755f46f83 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -68,10 +69,10 @@ #include "fogmanager.hpp" #include "objectpaging.hpp" #include "screenshotmanager.hpp" +#include "groundcover.hpp" namespace MWRender { - class StateUpdater : public SceneUtil::StateSetUpdater { public: @@ -243,6 +244,10 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + float groundcoverDistance = (Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover") - 1024) * 0.93; + globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); + globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); @@ -269,7 +274,8 @@ namespace MWRender const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); - mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); + mTerrainStorage.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps)); + const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); if (Settings::Manager::getBool("distant terrain", "Terrain")) { @@ -277,12 +283,11 @@ namespace MWRender int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); compMapPower = std::max(-3, compMapPower); float compMapLevel = pow(2, compMapPower); - const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); mTerrain.reset(new Terrain::QuadTreeWorld( - sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, + sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); if (Settings::Manager::getBool("object paging", "Terrain")) { @@ -292,11 +297,43 @@ namespace MWRender } } else - mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); + if (Settings::Manager::getBool("enabled", "Groundcover")) + { + osg::ref_ptr groundcoverRoot = new osg::Group; + groundcoverRoot->setNodeMask(Mask_Groundcover); + groundcoverRoot->setName("Groundcover Root"); + sceneRoot->addChild(groundcoverRoot); + + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f/255.f); + groundcoverRoot->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); + + mGroundcoverUpdater = new GroundcoverUpdater; + groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); + + float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover"); + if (chunkSize >= 1.0f) + chunkSize = 1.0f; + else if (chunkSize >= 0.5f) + chunkSize = 0.5f; + else if (chunkSize >= 0.25f) + chunkSize = 0.25f; + else if (chunkSize != 0.125f) + chunkSize = 0.125f; + + float density = Settings::Manager::getFloat("density", "Groundcover"); + density = std::clamp(density, 0.f, 1.f); + + mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize)); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); + static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); + mResourceSystem->addResourceManager(mGroundcover.get()); + } // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -508,7 +545,11 @@ namespace MWRender mWater->changeCell(store); if (store->getCell()->isExterior()) + { mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + if (mGroundcoverWorld) + mGroundcoverWorld->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } } void RenderingManager::removeCell(const MWWorld::CellStore *store) { @@ -517,7 +558,11 @@ namespace MWRender mObjects->removeCell(store); if (store->getCell()->isExterior()) + { mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + if (mGroundcoverWorld) + mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } mWater->removeCell(store); } @@ -527,6 +572,8 @@ namespace MWRender if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); + if (mGroundcoverWorld) + mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) @@ -612,6 +659,16 @@ namespace MWRender mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); + + if (mGroundcoverUpdater) + { + const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + + float windSpeed = mSky->getBaseWindSpeed(); + mGroundcoverUpdater->setWindSpeed(windSpeed); + mGroundcoverUpdater->setPlayerPos(playerPos); + } } updateNavMesh(); @@ -805,7 +862,7 @@ namespace MWRender mIntersectionVisitor->setIntersector(intersector); int mask = ~0; - mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater); + mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater|Mask_Groundcover); if (ignorePlayer) mask &= ~(Mask_Player); if (ignoreActors) @@ -964,6 +1021,12 @@ namespace MWRender fov = std::min(mFieldOfView, 140.f); float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + + if (mGroundcoverWorld) + { + int groundcoverDistance = Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover"); + mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + } } void RenderingManager::updateTextureFiltering() @@ -1158,6 +1221,8 @@ namespace MWRender void RenderingManager::setActiveGrid(const osg::Vec4i &grid) { mTerrain->setActiveGrid(grid); + if (mGroundcoverWorld) + mGroundcoverWorld->setActiveGrid(grid); } bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 39d1a0194e..a7afa2fa0d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -70,7 +70,7 @@ namespace DetourNavigator namespace MWRender { - + class GroundcoverUpdater; class StateUpdater; class EffectManager; @@ -88,6 +88,7 @@ namespace MWRender class ActorsPaths; class RecastMesh; class ObjectPaging; + class Groundcover; class RenderingManager : public MWRender::RenderingInterface { @@ -261,6 +262,8 @@ namespace MWRender osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mGroundcoverUpdater; + osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; @@ -275,8 +278,10 @@ namespace MWRender std::unique_ptr mObjects; std::unique_ptr mWater; std::unique_ptr mTerrain; - TerrainStorage* mTerrainStorage; + std::unique_ptr mGroundcoverWorld; + std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; + std::unique_ptr mGroundcover; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index f9f9dc74ca..bc3d3f1920 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -53,7 +53,9 @@ namespace MWRender Mask_PreCompile = (1<<18), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<19) + Mask_Lighting = (1<<19), + + Mask_Groundcover = (1<<20), }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index e786ce9372..1cc5a3cb7c 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -244,7 +244,7 @@ public: setCullCallback(new InheritViewPointCallback); setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); - setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting|Mask_Groundcover); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -372,12 +372,13 @@ public: void setInterior(bool isInterior) { int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); + reflectionDetail = std::min(5, std::max(isInterior ? 2 : 0, reflectionDetail)); unsigned int extraMask = 0; if(reflectionDetail >= 1) extraMask |= Mask_Terrain; if(reflectionDetail >= 2) extraMask |= Mask_Static; if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object; if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor; + if(reflectionDetail >= 5) extraMask |= Mask_Groundcover; setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 31af5b24bd..937491f622 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -36,6 +36,13 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { + if (ptr.getTypeName()==typeid (ESM::Static).name()) + { + const MWWorld::LiveCellRef *ref = ptr.get(); + if (ref->mBase->mIsGroundcover) + return true; + } + ptr.getClass().getModelsToPreload(ptr, mOut); return true; diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 30be4a6610..69161c8400 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,6 +24,8 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); + inline bool ignoreInstance (const X* ptr); + LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b48fe74a68..d8e2eb65fe 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,6 +169,17 @@ namespace namespace MWWorld { + template + bool CellRefList::ignoreInstance (const X* ptr) + { + return false; + } + + template <> + bool CellRefList::ignoreInstance (const ESM::Static* ptr) + { + return ptr->mIsGroundcover; + } template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) @@ -177,6 +188,8 @@ namespace MWWorld if (const X *ptr = store.search (ref.mRefID)) { + if (ignoreInstance(ptr)) return; + typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b529ae9db8..b559df0832 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index) + virtual void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index b12d646e70..c966182152 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -15,17 +15,17 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) { - ContentLoader::load(filepath.filename(), index); + ContentLoader::load(filepath.filename(), index, isGroundcover); - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string(), isGroundcover); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 506105bebb..cc4c15a15e 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -25,7 +25,7 @@ struct EsmLoader : public ContentLoader EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index) override; + void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override; private: std::vector& mEsm; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 90bc80b484..2731d7eb17 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,6 +190,15 @@ void ESMStore::setUp(bool validateRecords) { validate(); countRecords(); + + if (mGroundcovers.empty()) + { + for (const ESM::Static& record : mStatics) + { + if (record.mIsGroundcover) + mGroundcovers[record.mId] = record.mModel; + } + } } } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d69c56d8c0..18bb95580d 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,6 +75,7 @@ namespace MWWorld // maps the id name to the record type. std::map mIds; std::map mStaticIds; + std::map mGroundcovers; std::map mRefCount; @@ -121,6 +122,22 @@ namespace MWWorld return it->second; } + bool isGroundcover(const std::string &id, std::string &model) const + { + std::map::const_iterator it = mGroundcovers.find(id); + if (it == mGroundcovers.end()) { + return false; + } + model = it->second; + return true; + } + + bool isGroundcover(const std::string &id) const + { + std::map::const_iterator it = mGroundcovers.find(id); + return (it != mGroundcovers.end()); + } + ESMStore() : mDynamicCount(0) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index af9a5e0bb3..93d1a799c0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -103,12 +103,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index) override + void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index); + it->second->load(filepath, index, isGroundcover); } else { @@ -140,6 +140,7 @@ namespace MWWorld Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath) @@ -152,7 +153,7 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { - mEsm.resize(contentFiles.size()); + mEsm.resize(contentFiles.size() + groundcoverFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); @@ -165,7 +166,7 @@ namespace MWWorld gameContentLoader.addLoader(".omwaddon", &esmLoader); gameContentLoader.addLoader(".project", &esmLoader); - loadContentFiles(fileCollections, contentFiles, gameContentLoader); + loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader); listener->loadingOff(); @@ -2941,7 +2942,7 @@ namespace MWWorld } void World::loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, ContentLoader& contentLoader) + const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader) { int idx = 0; for (const std::string &file : content) @@ -2950,7 +2951,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx); + contentLoader.load(col.getPath(file), idx, false); } else { @@ -2959,6 +2960,22 @@ namespace MWWorld } idx++; } + + for (const std::string &file : groundcover) + { + boost::filesystem::path filename(file); + const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); + if (col.doesExist(file)) + { + contentLoader.load(col.getPath(file), idx, true); + } + else + { + std::string message = "Failed loading " + file + ": the groundcover file does not exist"; + throw std::runtime_error(message); + } + idx++; + } } bool World::startSpellCast(const Ptr &actor) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 79c8a4980e..29d29a1604 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -177,7 +177,7 @@ namespace MWWorld * @param contentLoader - */ void loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, ContentLoader& contentLoader); + const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader); float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); @@ -196,6 +196,7 @@ namespace MWWorld Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index d7fe7da948..8717a6839b 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -100,6 +100,7 @@ bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap node, const std::string& shaderPrefix, bool translucentFramebuffer) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); + if (forceShadersForNode) + shaderVisitor->setForceShaders(true); node->accept(*shaderVisitor); } @@ -512,7 +514,7 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - osg::ref_ptr shaderVisitor (createShaderVisitor()); + osg::ref_ptr shaderVisitor (createShaderVisitor("objects")); loaded->accept(*shaderVisitor); // share state diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index a815a324f2..9da6bc500d 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 942bd92d80..05f97b3ed9 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -281,6 +281,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "FrameNumber", "", "Compiling", + "UnrefQueue", "WorkQueue", "WorkThread", "", @@ -294,14 +295,13 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Nif", "Keyframe", "", + "Groundcover Chunk", "Object Chunk", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", - "UnrefQueue", - "", "NavMesh UpdateJobs", "NavMesh CacheSize", "NavMesh UsedTiles", diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 788a8720bc..4f887e659b 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -342,6 +342,8 @@ namespace Shader osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); + program->addBindAttribLocation("aOffset", 6); + program->addBindAttribLocation("aRotation", 7); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 50724628dd..a744471de5 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -40,7 +40,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } -osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) +osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 118df698f1..9b7dbf3ee1 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -35,7 +35,7 @@ namespace Terrain public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) override; + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 57c09000c1..7f184c70eb 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -90,8 +90,6 @@ private: osg::Vec4i mActiveGrid; }; -const float MIN_SIZE = 1/8.f; - class RootNode : public QuadTreeNode { public: @@ -250,6 +248,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) + , mMinSize(1/8.f) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -257,6 +256,17 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManagers.push_back(mChunkManager.get()); } +QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize) + : TerrainGrid(parent, storage, nodeMask) + , mViewDataMap(new ViewDataMap) + , mQuadTreeBuilt(false) + , mLodFactor(lodFactor) + , mVertexLodMod(0) + , mViewDistance(std::numeric_limits::max()) + , mMinSize(chunkSize) +{ +} + QuadTreeWorld::~QuadTreeWorld() { } @@ -425,7 +435,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) if (needsUpdate) { vd->reset(); - DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, mActiveGrid); + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); } @@ -438,7 +448,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) entry.mRenderingNode->accept(nv); } - if (isCullVisitor) + if (mHeightCullCallback && isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); vd->markUnchanged(); @@ -457,7 +467,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt() if (mQuadTreeBuilt) return; - QuadTreeBuilder builder(mStorage, MIN_SIZE); + QuadTreeBuilder builder(mStorage, mMinSize); builder.build(); mRootNode = builder.getRootNode(); @@ -491,7 +501,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); - DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, grid); + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); if (!progressTotal) @@ -515,14 +525,15 @@ bool QuadTreeWorld::storeView(const View* view, double referenceTime) void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) { - stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); + if (mCompositeMapRenderer) + stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); } void QuadTreeWorld::loadCell(int x, int y) { // fallback behavior only for undefined cells (every other is already handled in quadtree) float dummy; - if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) + if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::loadCell(x,y); else World::loadCell(x,y); @@ -532,7 +543,7 @@ void QuadTreeWorld::unloadCell(int x, int y) { // fallback behavior only for undefined cells (every other is already handled in quadtree) float dummy; - if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) + if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::unloadCell(x,y); else World::unloadCell(x,y); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 4c05efe646..aba2dccf3b 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -22,6 +22,8 @@ namespace Terrain public: QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); + QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize); + ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); @@ -47,7 +49,7 @@ namespace Terrain { public: virtual ~ChunkManager(){} - virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) = 0; + virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) = 0; virtual unsigned int getNodeMask() { return 0; } }; void addChunkManager(ChunkManager*); @@ -66,6 +68,7 @@ namespace Terrain float mLodFactor; int mVertexLodMod; float mViewDistance; + float mMinSize; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 679597971e..cf8debc696 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -26,6 +26,12 @@ TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource:: { } +TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask) + : Terrain::World(parent, storage, nodeMask) + , mNumSplits(4) +{ +} + TerrainGrid::~TerrainGrid() { while (!mGrid.empty()) @@ -107,6 +113,8 @@ void TerrainGrid::unloadCell(int x, int y) void TerrainGrid::updateWaterCulling() { + if (!mHeightCullCallback) return; + osg::ComputeBoundsVisitor computeBoundsVisitor; mTerrainRoot->accept(computeBoundsVisitor); float lowZ = computeBoundsVisitor.getBoundingBox()._min.z(); diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index f8b0fb2590..dc9203466d 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -15,6 +15,7 @@ namespace Terrain { public: TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0, int borderMask=0); + TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask=~0); ~TerrainGrid(); void cacheCell(View* view, int x, int y) override; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 5b4807b387..15ec72973d 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -49,17 +49,38 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mResourceSystem->addResourceManager(mTextureManager.get()); } +World::World(osg::Group* parent, Storage* storage, int nodeMask) + : mStorage(storage) + , mParent(parent) + , mCompositeMapCamera(nullptr) + , mCompositeMapRenderer(nullptr) + , mResourceSystem(nullptr) + , mTextureManager(nullptr) + , mChunkManager(nullptr) + , mCellBorder(nullptr) + , mBorderVisible(false) + , mHeightCullCallback(nullptr) +{ + mTerrainRoot = new osg::Group; + mTerrainRoot->setNodeMask(nodeMask); + + mParent->addChild(mTerrainRoot); +} + World::~World() { - mResourceSystem->removeResourceManager(mChunkManager.get()); - mResourceSystem->removeResourceManager(mTextureManager.get()); + if (mResourceSystem && mChunkManager) + mResourceSystem->removeResourceManager(mChunkManager.get()); + if (mResourceSystem && mTextureManager) + mResourceSystem->removeResourceManager(mTextureManager.get()); mParent->removeChild(mTerrainRoot); - mCompositeMapCamera->removeChild(mCompositeMapRenderer); - mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); - - delete mStorage; + if (mCompositeMapCamera && mCompositeMapRenderer) + { + mCompositeMapCamera->removeChild(mCompositeMapRenderer); + mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); + } } void World::setWorkQueue(SceneUtil::WorkQueue* workQueue) @@ -108,16 +129,20 @@ float World::getHeightAt(const osg::Vec3f &worldPos) void World::updateTextureFiltering() { - mTextureManager->updateTextureFiltering(); + if (mTextureManager) + mTextureManager->updateTextureFiltering(); } void World::clearAssociatedCaches() { - mChunkManager->clearCache(); + if (mChunkManager) + mChunkManager->clearCache(); } osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask) { + if (!mHeightCullCallback) return nullptr; + mHeightCullCallback->setHighZ(highz); mHeightCullCallback->setCullMask(mask); return mHeightCullCallback; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index d94125100e..a4be57e8ef 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -106,6 +106,7 @@ namespace Terrain /// @param nodeMask mask for the terrain root /// @param preCompileMask mask for pre compiling textures World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask); + World(osg::Group* parent, Storage* storage, int nodeMask); virtual ~World(); /// Set a WorkQueue to delete objects in the background thread. diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index 9e8db49fd4..98b3e7f001 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -223,10 +223,10 @@ For example, to attach a custom weapon bone, you'll need to follow this NIF reco :: -NiNode "root" - NiNode "Bip01 L Hand" - NiNode "Weapon Bone Left" - NiStringExtraData "BONE" + NiNode "root" + NiNode "Bip01 L Hand" + NiNode "Weapon Bone Left" + NiStringExtraData "BONE" OpenMW will detect ``Weapon Bone Left`` node and attach it to ``Bip01 L Hand`` bone of the target skeleton. @@ -276,6 +276,54 @@ Also it is possible to add a "Bip01 Arrow" bone to actor skeletons. In this case Such approach allows to implement better shooting animations (for example, beast races have tail, so quivers should be attached under different angle and default arrow fetching animation does not look good). +Groundcover support +------------------- + +Groundcover objects is a special kind of objects (e.g. grass), which can be used to improve visual fidelity. +They use these assumptions: + +1. Each object is independent, so part of objects can be removed from scene without causing graphical artifacts. + +2. Groundover should not have collisions. + +3. They are not important for some parts of game scene (e.g. local map). + +4. They can not be moved or disabled on the fly. + +5. They can not be interacted with. + +As result, such objects can be treated in the separate way: + +1. It is possible to tweak groundcover objects density. + +2. It is possible to safely merge such objects even near player. + +3. Such objects can be animated (to simulate wind, for example). + +4. Some parts of processing can be skipped. + +For example, we do not need to have collision or animation objects for groundcover, +do not need to render groundcover on the map, do not need to render it for the whole visible area (which can be very large with Distant Terrain). It allows to increase performance a lot. + +General advices to create assets for this feature: +1. Alpha properties from Nif files are not used, a unified alpha settings are used (alpha testing, "greater of equal" function, 128/255 threshold). +2. Use a single NiTriShape in groundocver mesh, or at least use same properties (texture, alpha, material, etc), so OpenMW can merge them on the fly. Otherwise animations may not work properly. +3. Smooth fading does not work for meshes, which have textures without alpha (e.g. rock). + +Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones: + +:: + + groundcover=my_grass_mod.esp + +Every static from such mod is treated as a groundcover object. +Also groundcover detection should be enabled via settings.cfg: + +:: + + [Groundcover] + enabled = true + .. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 .. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 .. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886 diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst new file mode 100644 index 0000000000..9b00d85a1a --- /dev/null +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -0,0 +1,68 @@ +Groundcover Settings +#################### + +enabled +------- + +:Type: boolean +:Range: True/False +:Default: False + +Allows the engine to use groundcover. +Groundcover objects are static objects which come from ESP files, registered via +"groundcover" entries from openmw.cfg rather than "content" ones. +We assume that groundcover objects have no collisions, can not be moved or interacted with, +so we can merge them to pages and animate them indifferently from distance from player. + +This setting can only be configured by editing the settings configuration file. + +fade start +---------- + +:Type: floating point +:Range: 0.0 to 1.0 +:Default: 0.85 + +Determines on which distance from player groundcover fading starts. +Does not work for meshes which textures do not have transparency (e.g. rocks). + +This setting can only be configured by editing the settings configuration file. + +density +------- + +:Type: floating point +:Range: 0.0 (0%) to 1.0 (100%) +:Default: 1.0 + +Determines how many groundcover instances from content files +are used in the game. Can affect performance a lot. + +This setting can only be configured by editing the settings configuration file. + +distance +-------- + +:Type: integer +:Range: > 0 +:Default: 1 + +Determines on which distance in cells grass pages are rendered. +Default 1 value means 3x3 cells area (active grid). +May affect performance a lot. + +This setting can only be configured by editing the settings configuration file. + +min chunk size +-------------- + +:Type: floating point +:Range: 0.125, 0.25, 0.5, 1.0 +:Default: 0.5 + +Determines a minimum size of groundcover chunks in cells. For example, with 0.5 value +chunks near player will have size 4096x4096 game units. Larger chunks reduce CPU usage +(Draw and Cull bars), but can increase GPU usage (GPU bar) since culling becomes less efficient. +Smaller values do an opposite. + +This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 586a99dbb7..220ee88c4b 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -42,6 +42,7 @@ The ranges included with each setting are the physically possible ranges, not re camera cells fog + groundcover map GUI HUD diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index e23cc3d548..ed43b19a2a 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -26,6 +26,7 @@ Has no effect if the 'force shaders' option is false. Enabling per-pixel lighting results in visual differences to the original MW engine. It is not recommended to enable this option when using vanilla Morrowind files, because certain lights in Morrowind rely on vertex lighting to look as intended. +Note that groundcover shaders ignore this setting. clamp lighting -------------- diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index b79daacb7d..b3a6f8c1f2 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -62,7 +62,7 @@ reflection detail ----------------- :Type: integer -:Range: 0, 1, 2, 3, 4 +:Range: 0, 1, 2, 3, 4, 5 :Default: 2 Controls what kinds of things are rendered in water reflections. @@ -72,6 +72,7 @@ Controls what kinds of things are rendered in water reflections. 2: statics, activators, and doors are also reflected 3: items, containers, and particles are also reflected 4: actors are also reflected +5: groundcover objects are also reflected In interiors the lowest level is 2. This setting can be changed ingame with the "Reflection shader detail" dropdown under the Water tab of the Video panel in the Options menu. diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 14ab7c9de5..b57d362ed0 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -449,6 +449,7 @@ + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index d0793fc819..f6bfff7b10 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -950,6 +950,25 @@ lineofsight keep inactive cache = 0 defer aabb update = true [Models] + # Attempt to load any valid NIF file regardless of its version and track the progress. # Loading arbitrary meshes is not advised and may cause instability. load unsupported nif files = false + +[Groundcover] + +# enable separate groundcover handling +enabled = false + +# configure groundcover fade out threshold +fade start = 0.85 + +# A groundcover density (0.0 <= value <= 1.0) +# 1.0 means 100% density +density = 1.0 + +# A maximum distance in cells on which groundcover is rendered. +distance = 1 + +# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) +min chunk size = 0.5 diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 47670e7a03..a4e898e4b0 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -7,6 +7,8 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIRRELATIVE resources/shaders) set(SHADER_FILES + groundcover_vertex.glsl + groundcover_fragment.glsl water_vertex.glsl water_fragment.glsl water_nm.png diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl new file mode 100644 index 0000000000..9c680853af --- /dev/null +++ b/files/shaders/groundcover_fragment.glsl @@ -0,0 +1,93 @@ +#version 120 + +#define GROUNDCOVER + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +#if @normalMap +uniform sampler2D normalMap; +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. +// They may do not look as intended with per-pixel lighting, so ignore this setting for now. +#define PER_PIXEL_LIGHTING @normalMap + +varying float euclideanDepth; +varying float linearDepth; + +#if PER_PIXEL_LIGHTING +varying vec3 passViewPos; +varying vec3 passNormal; +#else +centroid varying vec3 passLighting; +centroid varying vec3 shadowDiffuseLighting; +#endif + +#include "shadows_fragment.glsl" +#include "lighting.glsl" + +float calc_coverage(float a, float alpha_ref, float falloff_rate) +{ + return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0); +} + +void main() +{ +#if @normalMap + vec4 normalTex = texture2D(normalMap, normalMapUV); + + vec3 normalizedNormal = normalize(passNormal); + vec3 normalizedTangent = normalize(passTangent.xyz); + vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; + mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); + + vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); +#endif + +#if (!@normalMap && @forcePPL && false) + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); +#endif + +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); +#else + gl_FragData[0] = vec4(1.0); +#endif + + gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0); + + float shadowing = unshadowedLightRatio(linearDepth); + if (euclideanDepth > @groundcoverFadeStart) + gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth); + + vec3 lighting; +#if !PER_PIXEL_LIGHTING + lighting = passLighting + shadowDiffuseLighting * shadowing; +#else + vec3 diffuseLight, ambientLight; + doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); + lighting = diffuseLight + ambientLight; +#endif + +#if @clamp + lighting = clamp(lighting, vec3(0.0), vec3(1.0)); +#else + lighting = max(lighting, 0.0); +#endif + + gl_FragData[0].xyz *= lighting; + +#if @radialFog + float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + + applyShadowDebugOverlay(); +} diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl new file mode 100644 index 0000000000..4f3303b037 --- /dev/null +++ b/files/shaders/groundcover_vertex.glsl @@ -0,0 +1,143 @@ +#version 120 + +#define GROUNDCOVER + +attribute vec4 aOffset; +attribute vec3 aRotation; + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @normalMap +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. +// They may do not look as intended with per-pixel lighting, so ignore this setting for now. +#define PER_PIXEL_LIGHTING @normalMap + +varying float euclideanDepth; +varying float linearDepth; + +#if PER_PIXEL_LIGHTING +varying vec3 passViewPos; +varying vec3 passNormal; +#else +centroid varying vec3 passLighting; +centroid varying vec3 shadowDiffuseLighting; +#endif + +#include "shadows_vertex.glsl" +#include "lighting.glsl" + +uniform float osg_SimulationTime; +uniform mat4 osg_ViewMatrixInverse; +uniform mat4 osg_ViewMatrix; +uniform float windSpeed; +uniform vec3 playerPos; + +vec2 groundcoverDisplacement(in vec3 worldpos, float h) +{ + vec2 windDirection = vec2(1.0); + vec3 footPos = playerPos; + vec3 windVec = vec3(windSpeed * windDirection, 1.0); + + float v = length(windVec); + vec2 displace = vec2(2.0 * windVec + 0.1); + vec2 harmonics = vec2(0.0); + + harmonics += vec2((1.0 - 0.10*v) * sin(1.0*osg_SimulationTime + worldpos.xy / 1100.0)); + harmonics += vec2((1.0 - 0.04*v) * cos(2.0*osg_SimulationTime + worldpos.xy / 750.0)); + harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0)); + harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0)); + + // FIXME: stomping function does not work well in MGE: + // 1. It does not take in account Z coordinate, so it works even when player levitates. + // 2. It works more-or-less well only for grass meshes, but not for other types of plants. + // So disable this function for now, until we find a better one. + vec2 stomp = vec2(0.0); + //float d = length(worldpos.xy - footPos.xy); + //if (d < 150.0 && d > 0.0) + //{ + // stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy); + //} + + return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp); +} + +mat4 rotation(in vec3 angle) +{ + float sin_x = sin(angle.x); + float cos_x = cos(angle.x); + float sin_y = sin(angle.y); + float cos_y = cos(angle.y); + float sin_z = sin(angle.z); + float cos_z = cos(angle.z); + + return mat4( + cos_z*cos_y+sin_x*sin_y*sin_z, -sin_z*cos_x, cos_z*sin_y+sin_z*sin_x*cos_y, 0.0, + sin_z*cos_y+cos_z*sin_x*sin_y, cos_z*cos_x, sin_z*sin_y-cos_z*sin_x*cos_y, 0.0, + -sin_y*cos_x, sin_x, cos_x*cos_y, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +mat3 rotation3(in mat4 rot4) +{ + return mat3( + rot4[0].xyz, + rot4[1].xyz, + rot4[2].xyz); +} + +void main(void) +{ + vec3 position = aOffset.xyz; + float scale = aOffset.w; + + mat4 rotation = rotation(aRotation); + vec4 displacedVertex = rotation * scale * gl_Vertex; + + displacedVertex = vec4(displacedVertex.xyz + position, 1.0); + + vec4 worldPos = osg_ViewMatrixInverse * gl_ModelViewMatrix * displacedVertex; + worldPos.xy += groundcoverDisplacement(worldPos.xyz, gl_Vertex.z); + vec4 viewPos = osg_ViewMatrix * worldPos; + + gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + + if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd) + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + else + gl_Position = gl_ProjectionMatrix * viewPos; + + linearDepth = gl_Position.z; + +#if (!PER_PIXEL_LIGHTING || @shadows_enabled) + vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); +#endif + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + +#if @normalMap + normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; + passTangent = gl_MultiTexCoord7.xyzw * rotation; +#endif + +#if PER_PIXEL_LIGHTING + passViewPos = viewPos.xyz; + passNormal = rotation3(rotation) * gl_Normal.xyz; +#else + vec3 diffuseLight, ambientLight; + doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); + passLighting = diffuseLight + ambientLight; +#endif + +#if (@shadows_enabled) + setupShadowCoords(viewPos, viewNormal); +#endif +} diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 930f4de264..1b3ff288a0 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -8,7 +8,20 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination; - diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal, lightDir), 0.0) * illumination; + + float lambert = dot(viewNormal.xyz, lightDir) * illumination; +#ifndef GROUNDCOVER + lambert = max(lambert, 0.0); +#else + { + // might need to be < 0 depending on direction of viewPos + if (dot(viewPos, viewNormal.xyz) > 0) + lambert = -lambert; + if (lambert < 0) + lambert *= -0.3; + } +#endif + diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; } #if PER_PIXEL_LIGHTING From 5124e81348ecdc915dd7248c8b088353f2037ea2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 22 Nov 2020 10:17:20 +0400 Subject: [PATCH 0257/2859] Use linear interpolation instead of abrupt transitions for groundcover lighting --- files/shaders/lighting.glsl | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 1b3ff288a0..5eae890291 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -14,11 +14,20 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie lambert = max(lambert, 0.0); #else { - // might need to be < 0 depending on direction of viewPos - if (dot(viewPos, viewNormal.xyz) > 0) - lambert = -lambert; - if (lambert < 0) - lambert *= -0.3; + float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz)); + if (lambert >= 0.0) + cosine = -cosine; + + float mult = 1.0; + float divisor = 8.0; + + if (cosine < 0.0 && cosine >= -1.0/divisor) + mult = mix(1.0, 0.3, -cosine*divisor); + else if (cosine < -1.0/divisor) + mult = 0.3; + + lambert *= mult; + lambert = abs(lambert); } #endif diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; From 36fc573375dcea73cc412d9452e6684f0de9f9ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 17 Dec 2020 13:55:20 +0400 Subject: [PATCH 0258/2859] Take in account Z direction for stomping --- files/shaders/groundcover_vertex.glsl | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 4f3303b037..407599effd 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -53,18 +53,14 @@ vec2 groundcoverDisplacement(in vec3 worldpos, float h) harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0)); harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0)); - // FIXME: stomping function does not work well in MGE: - // 1. It does not take in account Z coordinate, so it works even when player levitates. - // 2. It works more-or-less well only for grass meshes, but not for other types of plants. - // So disable this function for now, until we find a better one. - vec2 stomp = vec2(0.0); - //float d = length(worldpos.xy - footPos.xy); - //if (d < 150.0 && d > 0.0) - //{ - // stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy); - //} - - return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp); + float d = length(worldpos - footPos.xyz); + vec3 stomp = vec3(0.0); + if (d < 150.0 && d > 0.0) + { + stomp = (60.0 / d - 0.4) * (worldpos - footPos.xyz); + } + + return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp.xy); } mat4 rotation(in vec3 angle) From a09f03c85062180d5ae82796a92b5eab4ce27543 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 18 Dec 2020 14:52:30 +0400 Subject: [PATCH 0259/2859] Drop groundcover materials - they are not used --- 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 b9fdc2e280..1a5ccc29f6 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -58,6 +58,7 @@ namespace MWRender osg::ref_ptr ss = node.getStateSet(); if (ss != nullptr) { + ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); } @@ -101,6 +102,7 @@ namespace MWRender ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); traverse(geom); From 859cd0fd0c34ef6256b0c70cd4be0bef9797a5a0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 22:47:19 +0400 Subject: [PATCH 0260/2859] Do not use display lists for instanced meshes --- apps/openmw/mwrender/groundcover.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 1a5ccc29f6..11a155f9ff 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -95,6 +95,9 @@ namespace MWRender (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); } + // Display lists do not support instancing in OSG 3.4 + geom.setUseDisplayList(false); + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); From 1a6c06f7b59c05c60b31693f46f9a25940881332 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 10 Jan 2021 16:36:25 +0400 Subject: [PATCH 0261/2859] Do not allow to set distance to non-positive values --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c755f46f83..1b4b0cf270 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -244,7 +244,7 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; - float groundcoverDistance = (Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover") - 1024) * 0.93; + float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); @@ -1024,7 +1024,7 @@ namespace MWRender if (mGroundcoverWorld) { - int groundcoverDistance = Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover"); + int groundcoverDistance = Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")); mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } } From 8874e88ff1e0339afdf8365ed793a43bf4a750ea Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jan 2021 09:59:57 +0400 Subject: [PATCH 0262/2859] Drop fading setting --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- .../reference/modding/settings/groundcover.rst | 12 ------------ files/settings-default.cfg | 3 --- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 1b4b0cf270..68576cf44f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -245,7 +245,7 @@ namespace MWRender globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; - globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); + globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); // It is unnecessary to stop/start the viewer as no frames are being rendered yet. diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst index 9b00d85a1a..f0c37b7388 100644 --- a/docs/source/reference/modding/settings/groundcover.rst +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -16,18 +16,6 @@ so we can merge them to pages and animate them indifferently from distance from This setting can only be configured by editing the settings configuration file. -fade start ----------- - -:Type: floating point -:Range: 0.0 to 1.0 -:Default: 0.85 - -Determines on which distance from player groundcover fading starts. -Does not work for meshes which textures do not have transparency (e.g. rocks). - -This setting can only be configured by editing the settings configuration file. - density ------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f6bfff7b10..3660f56f0a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -960,9 +960,6 @@ load unsupported nif files = false # enable separate groundcover handling enabled = false -# configure groundcover fade out threshold -fade start = 0.85 - # A groundcover density (0.0 <= value <= 1.0) # 1.0 means 100% density density = 1.0 From d12a0fdcb3dbbe056ae4734f8e931fd3e060274f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jan 2021 10:02:55 +0400 Subject: [PATCH 0263/2859] Mark only instances from groundcover files as groundcover objects --- apps/openmw/mwrender/groundcover.cpp | 21 +++++++++++++++++---- apps/openmw/mwrender/objectpaging.cpp | 3 +-- apps/openmw/mwworld/cellpreloader.cpp | 8 ++------ apps/openmw/mwworld/cellstore.cpp | 20 +++++--------------- apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 6 +++--- apps/openmw/mwworld/esmloader.hpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 9 --------- apps/openmw/mwworld/esmstore.hpp | 17 ----------------- apps/openmw/mwworld/worldimp.cpp | 10 ++++++---- components/esm/cellref.cpp | 5 +++++ components/esm/cellref.hpp | 5 +++++ components/esm/esmreader.cpp | 9 +++------ components/esm/esmreader.hpp | 7 ++----- components/esm/loadstat.cpp | 2 -- components/esm/loadstat.hpp | 2 -- 16 files changed, 51 insertions(+), 77 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 11a155f9ff..049118c904 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -13,6 +13,17 @@ namespace MWRender { + std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store) + { + switch (type) + { + case ESM::REC_STAT: + return store.get().searchStatic(id)->mModel; + default: + return std::string(); + } + } + void GroundcoverUpdater::setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; @@ -217,15 +228,17 @@ namespace MWRender while(cell->getNextRef(esm[index], ref, deleted)) { if (deleted) continue; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - std::string model; - if (!store.isGroundcover(ref.mRefID, model)) continue; - if (model.empty()) continue; + if (!ref.mRefNum.fromGroundcoverFile()) continue; if (!calculator.isInstanceEnabled()) continue; if (!isInChunkBorders(ref, minBound, maxBound)) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + int type = store.findStatic(ref.mRefID); + std::string model = getGroundcoverModel(type, ref.mRefID, store); + if (model.empty()) continue; model = "meshes/" + model; + instances[model].emplace_back(ref, model); } } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 478fde0f86..b85358c208 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -373,7 +373,6 @@ namespace MWRender std::map refs; std::vector esm; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) @@ -398,7 +397,7 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } - if (store.isGroundcover(ref.mRefID)) continue; + if (ref.mRefNum.fromGroundcoverFile()) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 937491f622..421de4a7d6 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,5 +1,6 @@ #include "cellpreloader.hpp" +#include #include #include @@ -36,12 +37,7 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getTypeName()==typeid (ESM::Static).name()) - { - const MWWorld::LiveCellRef *ref = ptr.get(); - if (ref->mBase->mIsGroundcover) - return true; - } + if (ptr.getCellRef().getRefNum().fromGroundcoverFile()) return true; ptr.getClass().getModelsToPreload(ptr, mOut); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d8e2eb65fe..7ca35a1dfa 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,18 +169,6 @@ namespace namespace MWWorld { - template - bool CellRefList::ignoreInstance (const X* ptr) - { - return false; - } - - template <> - bool CellRefList::ignoreInstance (const ESM::Static* ptr) - { - return ptr->mIsGroundcover; - } - template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { @@ -188,8 +176,6 @@ namespace MWWorld if (const X *ptr = store.search (ref.mRefID)) { - if (ignoreInstance(ptr)) return; - typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); @@ -700,7 +686,11 @@ namespace MWWorld case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; - case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; + case ESM::REC_STAT: + { + if (ref.mRefNum.fromGroundcoverFile()) return; + mStatics.load(ref, deleted, store); break; + } case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b559df0832..b529ae9db8 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) + virtual void load(const boost::filesystem::path& filepath, int& index) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index c966182152..46b806582b 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -15,15 +15,15 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { - ContentLoader::load(filepath.filename(), index, isGroundcover); + ContentLoader::load(filepath.filename(), index); ESM::ESMReader lEsm; lEsm.setEncoder(mEncoder); lEsm.setIndex(index); lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string(), isGroundcover); + lEsm.open(filepath.string()); mEsm[index] = lEsm; mStore.load(mEsm[index], &mListener); } diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index cc4c15a15e..506105bebb 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -25,7 +25,7 @@ struct EsmLoader : public ContentLoader EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override; + void load(const boost::filesystem::path& filepath, int& index) override; private: std::vector& mEsm; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 2731d7eb17..90bc80b484 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,15 +190,6 @@ void ESMStore::setUp(bool validateRecords) { validate(); countRecords(); - - if (mGroundcovers.empty()) - { - for (const ESM::Static& record : mStatics) - { - if (record.mIsGroundcover) - mGroundcovers[record.mId] = record.mModel; - } - } } } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 18bb95580d..d69c56d8c0 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,7 +75,6 @@ namespace MWWorld // maps the id name to the record type. std::map mIds; std::map mStaticIds; - std::map mGroundcovers; std::map mRefCount; @@ -122,22 +121,6 @@ namespace MWWorld return it->second; } - bool isGroundcover(const std::string &id, std::string &model) const - { - std::map::const_iterator it = mGroundcovers.find(id); - if (it == mGroundcovers.end()) { - return false; - } - model = it->second; - return true; - } - - bool isGroundcover(const std::string &id) const - { - std::map::const_iterator it = mGroundcovers.find(id); - return (it != mGroundcovers.end()); - } - ESMStore() : mDynamicCount(0) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 93d1a799c0..98af121a5e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -103,12 +103,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override + void load(const boost::filesystem::path& filepath, int& index) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index, isGroundcover); + it->second->load(filepath, index); } else { @@ -2951,7 +2951,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx, false); + contentLoader.load(col.getPath(file), idx); } else { @@ -2961,13 +2961,15 @@ namespace MWWorld idx++; } + ESM::GroundcoverIndex = idx; + for (const std::string &file : groundcover) { boost::filesystem::path filename(file); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx, true); + contentLoader.load(col.getPath(file), idx); } else { diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 4b9852d656..b4d6ac7a72 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -5,6 +5,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +namespace ESM +{ + int GroundcoverIndex = std::numeric_limits::max(); +} + void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 5bb7fbc531..c2f7ff6de5 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -12,6 +12,7 @@ namespace ESM class ESMReader; const int UnbreakableLock = std::numeric_limits::max(); + extern int GroundcoverIndex; struct RefNum { @@ -25,6 +26,10 @@ namespace ESM enum { RefNum_NoContentFile = -1 }; inline bool hasContentFile() const { return mContentFile != RefNum_NoContentFile; } inline void unset() { mIndex = 0; mContentFile = RefNum_NoContentFile; } + + // Note: this method should not be used for objects with invalid RefNum + // (for example, for objects from disabled plugins in savegames). + inline bool fromGroundcoverFile() const { return mContentFile >= GroundcoverIndex; } }; /* Cell reference. This represents ONE object (of many) inside the diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 63d2f4d4f7..1b6eca7346 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -25,7 +25,6 @@ ESMReader::ESMReader() , mGlobalReaderList(nullptr) , mEncoder(nullptr) , mFileSize(0) - , mIsGroundcoverFile(false) { clearCtx(); } @@ -81,10 +80,8 @@ void ESMReader::openRaw(const std::string& filename) openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); } -void ESMReader::open(Files::IStreamPtr _esm, const std::string &name, bool isGroundcover) +void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) { - mIsGroundcoverFile = isGroundcover; - openRaw(_esm, name); if (getRecName() != "TES3") @@ -95,9 +92,9 @@ void ESMReader::open(Files::IStreamPtr _esm, const std::string &name, bool isGro mHeader.load (*this); } -void ESMReader::open(const std::string &file, bool isGroundcover) +void ESMReader::open(const std::string &file) { - open (Files::openConstrainedFileStream (file.c_str ()), file, isGroundcover); + open (Files::openConstrainedFileStream (file.c_str ()), file); } int64_t ESMReader::getHNLong(const char *name) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 600cd497b0..c660b0ddab 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -31,7 +31,6 @@ public: int getVer() const { return mHeader.mData.version; } int getRecordCount() const { return mHeader.mData.records; } - bool isGroundcoverFile() const { return mIsGroundcoverFile; } float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } const std::string getAuthor() const { return mHeader.mData.author; } const std::string getDesc() const { return mHeader.mData.desc; } @@ -67,9 +66,9 @@ public: /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. - void open(Files::IStreamPtr _esm, const std::string &name, bool isGroundcover = false); + void open(Files::IStreamPtr _esm, const std::string &name); - void open(const std::string &file, bool isGroundcover = false); + void open(const std::string &file); void openRaw(const std::string &filename); @@ -290,8 +289,6 @@ private: ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; - - bool mIsGroundcoverFile; }; } #endif diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 85e366f1c9..6c9de22bd1 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -37,8 +37,6 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - - mIsGroundcover = esm.isGroundcoverFile(); } void Static::save(ESMWriter &esm, bool isDeleted) const { diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index da94c4b8d5..3d91440402 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,8 +28,6 @@ struct Static std::string mId, mModel; - bool mIsGroundcover = false; - void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; From b975f16e6b9ef5ee8f11dbed504d067a2087b9b1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jan 2021 11:23:36 +0400 Subject: [PATCH 0264/2859] Remove redundant check - groundcover is not present in the CellStore --- apps/openmw/mwworld/cellpreloader.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 421de4a7d6..31af5b24bd 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,6 +1,5 @@ #include "cellpreloader.hpp" -#include #include #include @@ -37,8 +36,6 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getCellRef().getRefNum().fromGroundcoverFile()) return true; - ptr.getClass().getModelsToPreload(ptr, mOut); return true; From f40e22768673eb9a7d8ffe2126f853659111d599 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jan 2021 12:39:19 +0400 Subject: [PATCH 0265/2859] Remove redundant formatting changes --- apps/openmw/engine.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwworld/cellreflist.hpp | 2 -- apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/esmloader.cpp | 16 ++++++++-------- components/esm/esmreader.hpp | 1 + 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 103d06f31d..dfaf09c21a 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -725,7 +725,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // Create the world - mEnvironment.setWorld(new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index b85358c208..7386c00690 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -373,6 +373,7 @@ namespace MWRender std::map refs; std::vector esm; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 68576cf44f..6ba4baec54 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -73,6 +73,7 @@ namespace MWRender { + class StateUpdater : public SceneUtil::StateSetUpdater { public: diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 69161c8400..30be4a6610 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,8 +24,6 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); - inline bool ignoreInstance (const X* ptr); - LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 7ca35a1dfa..3f98684ae7 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,6 +169,7 @@ namespace namespace MWWorld { + template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 46b806582b..b12d646e70 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -17,15 +17,15 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { - ContentLoader::load(filepath.filename(), index); + ContentLoader::load(filepath.filename(), index); - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string()); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index c660b0ddab..761756e8fb 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -289,6 +289,7 @@ private: ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; + }; } #endif From 24e1dfcddc50bd51a019d062cc8ad7f4ea2b1515 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 14:30:51 +0400 Subject: [PATCH 0266/2859] Use default argument --- components/resource/scenemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 0a4faed014..71f11e382e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -514,7 +514,7 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - osg::ref_ptr shaderVisitor (createShaderVisitor("objects")); + osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); // share state From e3490c8606c3c83a981421ddfc5bdd74e25c88ae Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 23 Jan 2021 09:30:57 +0400 Subject: [PATCH 0267/2859] Remove dead code --- files/shaders/groundcover_fragment.glsl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 9c680853af..77fd32e58b 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -49,10 +49,6 @@ void main() vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); #endif -#if (!@normalMap && @forcePPL && false) - vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); -#endif - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); #else From 5225ec9e5045142f7191b6d81a19350f694682b3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 26 Jan 2021 22:32:06 +0400 Subject: [PATCH 0268/2859] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f0460f11..c6c7b5757a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,7 @@ Feature #5692: Improve spell/magic item search to factor in magic effect names Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. + Feature #5813: Instanced groundcover support Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation From 99ba45a308c09f01976da483160dba93007413d1 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 02:25:46 +0000 Subject: [PATCH 0269/2859] Optional static builds of OSG, MyGUI, Bullet --- .gitignore | 2 +- .gitlab-ci.yml | 49 +++++-- CI/before_script.android.sh | 6 +- CI/before_script.linux.sh | 77 +++++----- CI/install_debian_deps.sh | 64 +++++++++ CMakeLists.txt | 90 ++++++------ apps/opencs/CMakeLists.txt | 14 ++ apps/openmw/CMakeLists.txt | 35 ++--- components/CMakeLists.txt | 2 +- extern/CMakeLists.txt | 139 +++++++++++++++++++ extern/osg-ffmpeg-videoplayer/CMakeLists.txt | 1 + 11 files changed, 361 insertions(+), 118 deletions(-) create mode 100755 CI/install_debian_deps.sh create mode 100644 extern/CMakeLists.txt diff --git a/.gitignore b/.gitignore index 1a164592a1..8112f683ad 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ CMakeCache.txt cmake_install.cmake Makefile makefile -build* +build*/ prebuilt ##windows build process diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 810e23d38e..7ad26bc550 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,19 +1,20 @@ +# Note: We set `needs` on each job to control the job DAG. +# See https://docs.gitlab.com/ee/ci/yaml/#needs stages: - build -.Debian: +.Debian_Image: tags: - docker - linux image: debian:bullseye + +.Debian: + extends: .Debian_Image cache: paths: - apt-cache/ - ccache/ - before_script: - - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - - apt-get update -yq - - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake build-essential libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libopenscenegraph-dev libunshield-dev libtinyxml-dev libmygui-dev libbullet-dev liblz4-dev ccache git clang stage: build script: - export CCACHE_BASEDIR="`pwd`" @@ -33,6 +34,8 @@ Debian_GCC: extends: .Debian cache: key: Debian_GCC.v2 + before_script: + - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic variables: CC: gcc CXX: g++ @@ -41,31 +44,52 @@ Debian_GCC: timeout: 2h Debian_GCC_tests: - extends: .Debian + extends: Debian_GCC cache: key: Debian_GCC_tests.v2 variables: - CC: gcc - CXX: g++ + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + +Debian_GCC_Static_Deps: + extends: Debian_GCC + cache: + key: Debian_GCC_Static_Deps + paths: + - apt-cache/ + - ccache/ + - build/extern/fetched/ + before_script: + - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static + variables: + CI_OPENMW_USE_STATIC_DEPS: 1 + +Debian_GCC_Static_Deps_tests: + extends: Debian_GCC_Static_Deps + cache: + key: Debian_GCC_Static_Deps_tests + variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 Debian_Clang: extends: .Debian + before_script: + - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic cache: key: Debian_Clang.v2 variables: CC: clang CXX: clang++ CCACHE_SIZE: 2G + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + timeout: 2h Debian_Clang_tests: - extends: .Debian + extends: Debian_Clang cache: key: Debian_Clang_tests.v2 variables: - CC: clang - CXX: clang++ CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 @@ -286,12 +310,13 @@ Debian_AndroidNDK_arm64-v8a: paths: - apt-cache/ - ccache/ + - build/extern/fetched/ before_script: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - echo "deb http://deb.debian.org/debian unstable main contrib" > /etc/apt/sources.list - echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections - apt-get update -yq - - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer + - apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer stage: build script: - export CCACHE_BASEDIR="`pwd`" diff --git a/CI/before_script.android.sh b/CI/before_script.android.sh index 8f0ec77da0..3ea429f1bb 100755 --- a/CI/before_script.android.sh +++ b/CI/before_script.android.sh @@ -3,7 +3,7 @@ # hack to work around: FFmpeg version is too old, 3.2 is required sed -i s/"NOT FFVER_OK"/"FALSE"/ CMakeLists.txt -mkdir build +mkdir -p build cd build cmake \ @@ -21,5 +21,7 @@ cmake \ -DBUILD_ESSIMPORTER=0 \ -DBUILD_OPENCS=0 \ -DBUILD_WIZARD=0 \ --DMyGUI_LIBRARY="/usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/libMyGUIEngineStatic.a" \ +-DOPENMW_USE_SYSTEM_MYGUI=OFF \ +-DOPENMW_USE_SYSTEM_OSG=OFF \ +-DOPENMW_USE_SYSTEM_BULLET=OFF \ .. diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 6df3dc32e0..ef617e0d51 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -1,44 +1,55 @@ -#!/bin/bash -ex +#!/bin/bash + +set -xeo pipefail free -m if [[ "${BUILD_TESTS_ONLY}" ]]; then - export GOOGLETEST_DIR="$(pwd)/googletest/build/install" - env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh + export GOOGLETEST_DIR="${PWD}/googletest/build/install" + env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh +fi + +declare -a CMAKE_CONF_OPTS=( + -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" + -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" + -DCMAKE_C_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + -DCMAKE_INSTALL_PREFIX=install + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DBUILD_SHARED_LIBS=OFF + -DUSE_SYSTEM_TINYXML=ON + -DCMAKE_INSTALL_PREFIX=install +) +declare -a CMAKE_CONF_ENV=() + +if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then + CMAKE_CONF_OPTS+=( + -DOPENMW_USE_SYSTEM_MYGUI=OFF + -DOPENMW_USE_SYSTEM_OSG=OFF + -DOPENMW_USE_SYSTEM_BULLET=OFF + ) fi -mkdir build +mkdir -p build cd build if [[ "${BUILD_TESTS_ONLY}" ]]; then - ${ANALYZE} cmake \ - -D CMAKE_C_COMPILER="${CC}" \ - -D CMAKE_CXX_COMPILER="${CXX}" \ - -D CMAKE_C_COMPILER_LAUNCHER=ccache \ - -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -D CMAKE_INSTALL_PREFIX=install \ - -D CMAKE_BUILD_TYPE=RelWithDebInfo \ - -D USE_SYSTEM_TINYXML=TRUE \ - -D BUILD_OPENMW=OFF \ - -D BUILD_BSATOOL=OFF \ - -D BUILD_ESMTOOL=OFF \ - -D BUILD_LAUNCHER=OFF \ - -D BUILD_MWINIIMPORTER=OFF \ - -D BUILD_ESSIMPORTER=OFF \ - -D BUILD_OPENCS=OFF \ - -D BUILD_WIZARD=OFF \ - -D BUILD_UNITTESTS=ON \ - -D GTEST_ROOT="${GOOGLETEST_DIR}" \ - -D GMOCK_ROOT="${GOOGLETEST_DIR}" \ - .. + env "${CMAKE_CONF_ENV[@]}" ${ANALYZE} cmake \ + "${CMAKE_CONF_OPTS[@]}" \ + -DBUILD_OPENMW=OFF \ + -DBUILD_BSATOOL=OFF \ + -DBUILD_ESMTOOL=OFF \ + -DBUILD_LAUNCHER=OFF \ + -DBUILD_MWINIIMPORTER=OFF \ + -DBUILD_ESSIMPORTER=OFF \ + -DBUILD_OPENCS=OFF \ + -DBUILD_WIZARD=OFF \ + -DBUILD_UNITTESTS=ON \ + -DGTEST_ROOT="${GOOGLETEST_DIR}" \ + -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ + .. else - ${ANALYZE} cmake \ - -D CMAKE_C_COMPILER="${CC}" \ - -D CMAKE_CXX_COMPILER="${CXX}" \ - -D CMAKE_C_COMPILER_LAUNCHER=ccache \ - -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -D USE_SYSTEM_TINYXML=TRUE \ - -D CMAKE_INSTALL_PREFIX=install \ - -D CMAKE_BUILD_TYPE=Debug \ - .. + env "${CMAKE_CONF_ENV[@]}" ${ANALYZE} cmake \ + "${CMAKE_CONF_OPTS[@]}" \ + .. fi diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh new file mode 100755 index 0000000000..82d2ff6819 --- /dev/null +++ b/CI/install_debian_deps.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -euo pipefail + +print_help() { + echo "usage: $0 [group]..." + echo + echo " available groups: "${!GROUPED_DEPS[@]}"" +} + +declare -rA GROUPED_DEPS=( + [gcc]="binutils gcc g++ libc-dev" + [clang]="binutils clang" + + # Common dependencies for building OpenMW. + [openmw-deps]=" + make cmake ccache git pkg-config + + libboost-filesystem-dev libboost-program-options-dev + libboost-system-dev libboost-iostreams-dev + + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev + libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev + libbullet-dev liblz4-dev libpng-dev libjpeg-dev + " + + # These dependencies can alternatively be built and linked statically. + [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" + + # Pre-requisites for building MyGUI and OSG for static linking. + # + # * MyGUI and OSG: libsdl2-dev liblz4-dev libfreetype6-dev + # * OSG: libgl-dev + # + # Plugins: + # * DAE: libcollada-dom-dev libboost-system-dev libboost-filesystem-dev + # * JPEG: libjpeg-dev + # * PNG: libpng-dev + [openmw-deps-static]=" + make cmake + ccache curl unzip libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev + libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev + " +) + +if [[ $# -eq 0 ]]; then + >&2 print_help + exit 1 +fi + +deps=() +for group in "$@"; do + if [[ ! -v GROUPED_DEPS[$group] ]]; then + >&2 echo "error: unknown group ${group}" + exit 1 + fi + deps+=(${GROUPED_DEPS[$group]}) +done + +export APT_CACHE_DIR="${PWD}/apt-cache" +set -x +mkdir -pv "$APT_CACHE_DIR" +apt-get update -yq +apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y "${deps[@]}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 226511b527..be1f4dae4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,11 @@ project(OpenMW) -cmake_minimum_required(VERSION 3.1.0) +cmake_minimum_required(VERSION 3.11) # CMP0083 NEW set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Detect OS include(cmake/OSIdentity.cmake) -# for link time optimization, remove if cmake version is >= 3.9 -if(POLICY CMP0069) - cmake_policy(SET CMP0069 NEW) -endif() - # Apps and tools option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_LAUNCHER "Build Launcher" ON) @@ -52,7 +47,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) if (ANDROID) set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") - set (OSG_PLUGINS_DIR CACHE STRING "") endif() # Version @@ -100,12 +94,18 @@ include(OpenMWMacros) configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_DIR}/docs/mainpage.hpp") -option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) -option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" FALSE) option(QT_STATIC "Link static build of QT into the binaries" FALSE) +option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON) +option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" OFF) + +option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) + +option(OPENMW_USE_SYSTEM_MYGUI "Use system provided mygui library" ON) +option(MYGUI_STATIC "Link static build of Mygui into the binaries" OFF) + option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) @@ -168,6 +168,8 @@ if (USE_QT) #set(CMAKE_AUTOMOC ON) endif() +add_subdirectory(extern) + # Sound setup # Require at least ffmpeg 3.2 for now @@ -243,6 +245,15 @@ if (WIN32) add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() +if(OPENMW_USE_SYSTEM_BULLET) + set(REQUIRED_BULLET_VERSION 286) # Bullet 286 required due to runtime bugfixes for btCapsuleShape + if (DEFINED ENV{TRAVIS_BRANCH} OR DEFINED ENV{APPVEYOR}) + set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine + endif() + + find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath) +endif() + if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer find_package(LIBUNSHIELD REQUIRED) # required only for non win32 when building openmw-wizard set(OPENMW_USE_UNSHIELD TRUE) @@ -261,35 +272,25 @@ if(NOT HAVE_STDINT_H) message(FATAL_ERROR "stdint.h was not found" ) endif() +if(OPENMW_USE_SYSTEM_OSG) + find_package(OpenSceneGraph 3.3.4 REQUIRED ${USED_OSG_COMPONENTS}) + if(OSG_STATIC) + unset(OSGPlugins_LIB_DIR) + foreach(OSGDB_LIB ${OSGDB_LIBRARY}) + # Skip library type names + if(EXISTS ${OSGDB_LIB} AND NOT IS_DIRECTORY ${OSGDB_LIB}) + get_filename_component(OSG_LIB_DIR ${OSGDB_LIB} DIRECTORY) + list(APPEND OSGPlugins_LIB_DIR "${OSG_LIB_DIR}/osgPlugins-${OPENSCENEGRAPH_VERSION}") + endif() + endforeach(OSGDB_LIB) + find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS}) + endif() +endif() -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow osgAnimation) -include_directories(SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS}) - -set(USED_OSG_PLUGINS - osgdb_bmp - osgdb_dds - osgdb_freetype - osgdb_jpeg - osgdb_osg - osgdb_png - osgdb_serializers_osg - osgdb_tga - ) - -set(OSGPlugins_LIB_DIR "") -foreach(OSGDB_LIB ${OSGDB_LIBRARY}) - # Skip library type names - if(EXISTS ${OSGDB_LIB} AND NOT IS_DIRECTORY ${OSGDB_LIB}) - get_filename_component(OSG_LIB_DIR ${OSGDB_LIB} DIRECTORY) - list(APPEND OSGPlugins_LIB_DIR "${OSG_LIB_DIR}/osgPlugins-${OPENSCENEGRAPH_VERSION}") - endif() -endforeach(OSGDB_LIB) +include_directories(BEFORE SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS}) if(OSG_STATIC) add_definitions(-DOSG_LIBRARY_STATIC) - - find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS}) - list(APPEND OPENSCENEGRAPH_LIBRARIES ${OSGPlugins_LIBRARIES}) endif() set(BOOST_COMPONENTS system filesystem program_options iostreams) @@ -304,21 +305,18 @@ IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() -set(REQUIRED_BULLET_VERSION 286) # Bullet 286 required due to runtime bugfixes for btCapsuleShape -if (DEFINED ENV{TRAVIS_BRANCH} OR DEFINED ENV{APPVEYOR}) - set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine -endif() - set(Boost_NO_BOOST_CMAKE ON) find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -find_package(MyGUI 3.2.2 REQUIRED) +if(OPENMW_USE_SYSTEM_MYGUI) + find_package(MyGUI 3.2.2 REQUIRED) +endif() find_package(SDL2 2.0.9 REQUIRED) find_package(OpenAL REQUIRED) -find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath) -include_directories("." - SYSTEM +include_directories( + BEFORE SYSTEM + "." ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${MyGUI_INCLUDE_DIRS} @@ -848,11 +846,7 @@ elseif(NOT APPLE) # Install binaries IF(BUILD_OPENMW) - IF(ANDROID) - INSTALL(PROGRAMS "${INSTALL_SOURCE}/libopenmw.so" DESTINATION "${BINDIR}" ) - ELSE(ANDROID) - INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw" DESTINATION "${BINDIR}" ) - ENDIF(ANDROID) + INSTALL(PROGRAMS "$" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENMW) IF(BUILD_LAUNCHER) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-launcher" DESTINATION "${BINDIR}" ) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index b20920904c..e6df909194 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -228,6 +228,20 @@ target_link_libraries(openmw-cs components ) +if(OSG_STATIC) + unset(_osg_plugins_static_files) + add_library(openmw_cs_osg_plugins INTERFACE) + foreach(_plugin ${USED_OSG_PLUGINS}) + string(TOUPPER ${_plugin} _plugin_uc) + list(APPEND _osg_plugins_static_files $) + target_link_libraries(openmw_cs_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY}) + endforeach() + # We use --whole-archive because OSG plugins use registration. + target_link_options(openmw_cs_osg_plugins INTERFACE + -Wl,--whole-archive ${_osg_plugins_static_files} -Wl,--no-whole-archive) + target_link_libraries(openmw-cs openmw_cs_osg_plugins) +endif(OSG_STATIC) + target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) if (WIN32) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c01cbe60c7..c87a96b3a3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -143,29 +143,22 @@ target_link_libraries(openmw components ) -if (ANDROID) - set (OSG_PLUGINS - -Wl,--whole-archive - ) - foreach(PLUGIN_NAME ${USED_OSG_PLUGINS}) - set(OSG_PLUGINS ${OSG_PLUGINS} ${OSG_PLUGINS_DIR}/lib${PLUGIN_NAME}.a) +if(OSG_STATIC) + unset(_osg_plugins_static_files) + add_library(openmw_osg_plugins INTERFACE) + foreach(_plugin ${USED_OSG_PLUGINS}) + string(TOUPPER ${_plugin} _plugin_uc) + list(APPEND _osg_plugins_static_files $) + target_link_libraries(openmw_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY}) endforeach() + # We use --whole-archive because OSG plugins use registration. + target_link_options(openmw_osg_plugins INTERFACE + -Wl,--whole-archive ${_osg_plugins_static_files} -Wl,--no-whole-archive) + target_link_libraries(openmw openmw_osg_plugins) +endif(OSG_STATIC) - set (OSG_PLUGINS - ${OSG_PLUGINS} -Wl,--no-whole-archive - ) - - target_link_libraries(openmw - EGL - android - log - dl - z - ${OPENSCENEGRAPH_LIBRARIES} - freetype - jpeg - png - ) +if (ANDROID) + target_link_libraries(openmw EGL android log z) endif (ANDROID) if (USE_SYSTEM_TINYXML) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 832fc611f6..09a2edb050 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -245,7 +245,7 @@ target_link_libraries(components RecastNavigation::Recast ) -if (BULLET_USE_DOUBLES AND (UBUNTU_FOUND OR DEBIAN_FOUND)) +if (BULLET_USE_DOUBLES AND (UBUNTU_FOUND OR DEBIAN_FOUND) AND OPENMW_USE_SYSTEM_BULLET) target_link_libraries(components BulletCollision-float64 LinearMath-float64) else() target_link_libraries(components ${BULLET_LIBRARIES}) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt new file mode 100644 index 0000000000..f5743307ae --- /dev/null +++ b/extern/CMakeLists.txt @@ -0,0 +1,139 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# Like `FetchContent_MakeAvailable` but passes EXCLUDE_FROM_ALL to `add_subdirectory`. +macro(FetchContent_MakeAvailableExcludeFromAll) + foreach(contentName IN ITEMS ${ARGV}) + string(TOLOWER ${contentName} contentNameLower) + FetchContent_GetProperties(${contentName}) + if(NOT ${contentNameLower}_POPULATED) + FetchContent_Populate(${contentName}) + if(EXISTS ${${contentNameLower}_SOURCE_DIR}/CMakeLists.txt) + add_subdirectory(${${contentNameLower}_SOURCE_DIR} + ${${contentNameLower}_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() + endif() + endforeach() +endmacro() + +if(NOT OPENMW_USE_SYSTEM_BULLET) + set(BUILD_BULLET3 OFF CACHE BOOL "") + set(BUILD_EXTRAS OFF CACHE BOOL "") + set(BUILD_OPENGL3_DEMOS OFF CACHE BOOL "") + set(BUILD_UNIT_TESTS OFF CACHE BOOL "") + set(BUILD_BULLET2_DEMOS OFF CACHE BOOL "") + set(BUILD_CLSOCKET OFF CACHE BOOL "") + set(BUILD_ENET OFF CACHE BOOL "") + set(BUILD_CPU_DEMOS OFF CACHE BOOL "") + set(BUILD_EGL OFF CACHE BOOL "") + + set(USE_DOUBLE_PRECISION ${BULLET_USE_DOUBLES} CACHE BOOL "") + set(BULLET2_MULTITHREADING ON CACHE BOOL "") + + # Version 3.08 with the following changes: + # 1. Fixes the linking of Threads: + # https://github.com/bulletphysics/bullet3/pull/3237 + # 2. Removes ~300 MiB of files not used here: + # rm -rf build3 data docs examples test Doxyfile + include(FetchContent) + FetchContent_Declare(bullet + URL https://github.com/glebm/bullet3/archive/ed5256454f4f84bd2c1728c88ddb0405d614e7d2.zip + URL_HASH MD5=e3c94fac35a7be885ad8843f828a0f96 + SOURCE_DIR fetched/bullet + ) + FetchContent_MakeAvailableExcludeFromAll(bullet) + + set(BULLET_INCLUDE_DIRS ${bullet_SOURCE_DIR}/src PARENT_SCOPE) + + # The order here is important to work around a bug in Bullet: + # https://github.com/bulletphysics/bullet3/issues/3233 + set(BULLET_LIBRARIES BulletCollision LinearMath PARENT_SCOPE) +endif() + +if(NOT OPENMW_USE_SYSTEM_MYGUI) + set(MYGUI_STATIC ON CACHE BOOL "") + + set(MYGUI_RENDERSYSTEM 4 CACHE STRING "") + set(MYGUI_DISABLE_PLUGINS TRUE CACHE BOOL "") + set(MYGUI_BUILD_DEMOS OFF CACHE BOOL "") + set(MYGUI_BUILD_PLUGINS OFF CACHE BOOL "") + set(MYGUI_BUILD_TOOLS OFF CACHE BOOL "") + + include(FetchContent) + FetchContent_Declare(mygui + URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.0.zip + URL_HASH MD5=9e990a4240430cbf567bfe73488a274e + SOURCE_DIR fetched/mygui + ) + FetchContent_MakeAvailableExcludeFromAll(mygui) + + set(MyGUI_INCLUDE_DIRS ${mygui_SOURCE_DIR}/MyGUIEngine/include PARENT_SCOPE) + set(MyGUI_LIBRARIES MyGUIEngine PARENT_SCOPE) +endif() + +set(USED_OSG_COMPONENTS + osgDB + osgViewer + osgText + osgGA + osgParticle + osgUtil + osgFX + osgShadow + osgAnimation) +set(USED_OSG_COMPONENTS ${USED_OSG_COMPONENTS} PARENT_SCOPE) +set(USED_OSG_PLUGINS + osgdb_bmp + osgdb_dds + osgdb_freetype + osgdb_jpeg + osgdb_osg + osgdb_png + osgdb_serializers_osg + osgdb_tga) +set(USED_OSG_PLUGINS ${USED_OSG_PLUGINS} PARENT_SCOPE) +if(NOT OPENMW_USE_SYSTEM_OSG) + set(OSG_STATIC ON CACHE BOOL "") + + set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "") + set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "") + set(BUILD_OSG_APPLICATIONS OFF CACHE BOOL "") + set(BUILD_OSG_DEPRECATED_SERIALIZERS OFF CACHE BOOL "") + + set(BUILD_OSG_PLUGINS_BY_DEFAULT OFF CACHE BOOL "") + set(BUILD_OSG_PLUGIN_BMP ON CACHE BOOL "") + set(BUILD_OSG_PLUGIN_DDS ON CACHE BOOL "") + set(BUILD_OSG_PLUGIN_FREETYPE ON CACHE BOOL "") + set(BUILD_OSG_PLUGIN_JPEG ON CACHE BOOL "") + set(BUILD_OSG_PLUGIN_OSG ON CACHE BOOL "") + set(BUILD_OSG_PLUGIN_PNG ON CACHE BOOL "") + set(BUILD_OSG_PLUGIN_TGA ON CACHE BOOL "") + set(BUILD_OSG_PLUGIN_KTX ON CACHE BOOL "") + + set(OSG_USE_FLOAT_MATRIX ON CACHE BOOL "") + set(OSG_USE_FLOAT_PLANE ON CACHE BOOL "") + set(OSG_USE_FLOAT_QUAT ON CACHE BOOL "") + + set(OPENGL_PROFILE "GL2" CACHE STRING "") + + # branch OpenSceneGraph-3.6 on 18 Jan 2021. + # + https://github.com/openscenegraph/OpenSceneGraph/pull/1032 + # + https://github.com/openscenegraph/OpenSceneGraph/pull/1033 + include(FetchContent) + FetchContent_Declare(osg + URL https://github.com/glebm/OpenSceneGraph/archive/041090d84d9d4b72f1202457fceae0ec6f79b663.zip + URL_HASH MD5=2cbf8126b27a45a2a44efe9fa39090f3 + SOURCE_DIR fetched/osg + ) + FetchContent_MakeAvailableExcludeFromAll(osg) + + set(OPENSCENEGRAPH_INCLUDE_DIRS ${osg_SOURCE_DIR}/include ${osg_BINARY_DIR}/include PARENT_SCOPE) + set(OSG_LIBRARIES OpenThreads osg PARENT_SCOPE) + foreach(_name ${USED_OSG_COMPONENTS}) + string(TOUPPER ${_name} _name_uc) + set(${_name_uc}_LIBRARIES ${_name} PARENT_SCOPE) + endforeach() + foreach(_name ${USED_OSG_PLUGINS}) + string(TOUPPER ${_name} _name_uc) + set(${_name_uc}_LIBRARY ${_name} PARENT_SCOPE) + endforeach() +endif() diff --git a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt index 5289cd3af9..8ab51196a8 100644 --- a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt @@ -13,5 +13,6 @@ set(OSG_FFMPEG_VIDEOPLAYER_SOURCE_FILES include_directories(${FFmpeg_INCLUDE_DIRS}) add_library(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} STATIC ${OSG_FFMPEG_VIDEOPLAYER_SOURCE_FILES}) target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} ${FFmpeg_LIBRARIES} ${Boost_THREAD_LIBRARY}) +target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} ${OSG_LIBRARIES}) link_directories(${CMAKE_CURRENT_BINARY_DIR}) From 93fe84aea8921a87f7aaf7fc949b425324a3c276 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 19:03:54 +0000 Subject: [PATCH 0270/2859] cmake: Move USED_OSG_(COMPONENTS|PLUGINS) from extern to top-level --- CMakeLists.txt | 19 +++++++++++++++++++ extern/CMakeLists.txt | 21 --------------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index be1f4dae4f..f883aecfc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,6 +272,25 @@ if(NOT HAVE_STDINT_H) message(FATAL_ERROR "stdint.h was not found" ) endif() +set(USED_OSG_COMPONENTS + osgDB + osgViewer + osgText + osgGA + osgParticle + osgUtil + osgFX + osgShadow + osgAnimation) +set(USED_OSG_PLUGINS + osgdb_bmp + osgdb_dds + osgdb_freetype + osgdb_jpeg + osgdb_osg + osgdb_png + osgdb_serializers_osg + osgdb_tga) if(OPENMW_USE_SYSTEM_OSG) find_package(OpenSceneGraph 3.3.4 REQUIRED ${USED_OSG_COMPONENTS}) if(OSG_STATIC) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index f5743307ae..b426ac4bff 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -70,27 +70,6 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) set(MyGUI_LIBRARIES MyGUIEngine PARENT_SCOPE) endif() -set(USED_OSG_COMPONENTS - osgDB - osgViewer - osgText - osgGA - osgParticle - osgUtil - osgFX - osgShadow - osgAnimation) -set(USED_OSG_COMPONENTS ${USED_OSG_COMPONENTS} PARENT_SCOPE) -set(USED_OSG_PLUGINS - osgdb_bmp - osgdb_dds - osgdb_freetype - osgdb_jpeg - osgdb_osg - osgdb_png - osgdb_serializers_osg - osgdb_tga) -set(USED_OSG_PLUGINS ${USED_OSG_PLUGINS} PARENT_SCOPE) if(NOT OPENMW_USE_SYSTEM_OSG) set(OSG_STATIC ON CACHE BOOL "") From 8737453498d7dc4b62458e53f5318c76bc40865d Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 19:19:36 +0000 Subject: [PATCH 0271/2859] cmake: Compiler-specific whole-archive macro --- CMakeLists.txt | 1 + apps/opencs/CMakeLists.txt | 4 ++-- apps/openmw/CMakeLists.txt | 4 ++-- cmake/WholeArchive.cmake | 10 ++++++++++ 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 cmake/WholeArchive.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f883aecfc7..658a961f35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) # Macros include(OpenMWMacros) +include(WholeArchive) # doxygen main page diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index e6df909194..17b3375f59 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -237,8 +237,8 @@ if(OSG_STATIC) target_link_libraries(openmw_cs_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY}) endforeach() # We use --whole-archive because OSG plugins use registration. - target_link_options(openmw_cs_osg_plugins INTERFACE - -Wl,--whole-archive ${_osg_plugins_static_files} -Wl,--no-whole-archive) + get_whole_archive_options(_opts ${_osg_plugins_static_files}) + target_link_options(openmw_cs_osg_plugins INTERFACE ${_opts}) target_link_libraries(openmw-cs openmw_cs_osg_plugins) endif(OSG_STATIC) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c87a96b3a3..308b8bd5e5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -152,8 +152,8 @@ if(OSG_STATIC) target_link_libraries(openmw_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY}) endforeach() # We use --whole-archive because OSG plugins use registration. - target_link_options(openmw_osg_plugins INTERFACE - -Wl,--whole-archive ${_osg_plugins_static_files} -Wl,--no-whole-archive) + get_whole_archive_options(_opts ${_osg_plugins_static_files}) + target_link_options(openmw_osg_plugins INTERFACE ${_opts}) target_link_libraries(openmw openmw_osg_plugins) endif(OSG_STATIC) diff --git a/cmake/WholeArchive.cmake b/cmake/WholeArchive.cmake new file mode 100644 index 0000000000..a834e830e0 --- /dev/null +++ b/cmake/WholeArchive.cmake @@ -0,0 +1,10 @@ +macro (get_whole_archive_options OUT_VAR) + # We use --whole-archive because OSG plugins use registration. + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(${OUT_VAR} -Wl,--whole-archive ${ARGN} -Wl,--no-whole-archive) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + set(${OUT_VAR} -Wl,-all_load ${ARGN} -Wl,-noall_load) + else () + message(FATAL_ERROR "get_whole_archive_options not implemented for CMAKE_CXX_COMPILER_ID ${CMAKE_CXX_COMPILER_ID}") + endif() +endmacro (whole_archive_options) From a06f598442e99e9ae7869a9db106e51ab5b9cfe8 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 19:59:34 +0000 Subject: [PATCH 0272/2859] cmake: Move USED_OSG_(COMPONENTS|PLUGINS) before add_subdirectory(extern) --- CMakeLists.txt | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 658a961f35..5e7e59713a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,26 @@ if (USE_QT) #set(CMAKE_AUTOMOC ON) endif() +set(USED_OSG_COMPONENTS + osgDB + osgViewer + osgText + osgGA + osgParticle + osgUtil + osgFX + osgShadow + osgAnimation) +set(USED_OSG_PLUGINS + osgdb_bmp + osgdb_dds + osgdb_freetype + osgdb_jpeg + osgdb_osg + osgdb_png + osgdb_serializers_osg + osgdb_tga) + add_subdirectory(extern) # Sound setup @@ -273,25 +293,6 @@ if(NOT HAVE_STDINT_H) message(FATAL_ERROR "stdint.h was not found" ) endif() -set(USED_OSG_COMPONENTS - osgDB - osgViewer - osgText - osgGA - osgParticle - osgUtil - osgFX - osgShadow - osgAnimation) -set(USED_OSG_PLUGINS - osgdb_bmp - osgdb_dds - osgdb_freetype - osgdb_jpeg - osgdb_osg - osgdb_png - osgdb_serializers_osg - osgdb_tga) if(OPENMW_USE_SYSTEM_OSG) find_package(OpenSceneGraph 3.3.4 REQUIRED ${USED_OSG_COMPONENTS}) if(OSG_STATIC) From 90766dcc82309f806b0243ab272585b8a47bab7d Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 20:01:32 +0000 Subject: [PATCH 0273/2859] cmake: get_whole_archive_options macro -> function --- cmake/WholeArchive.cmake | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/WholeArchive.cmake b/cmake/WholeArchive.cmake index a834e830e0..2def95e8ad 100644 --- a/cmake/WholeArchive.cmake +++ b/cmake/WholeArchive.cmake @@ -1,10 +1,10 @@ -macro (get_whole_archive_options OUT_VAR) +function (get_whole_archive_options OUT_VAR) # We use --whole-archive because OSG plugins use registration. if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(${OUT_VAR} -Wl,--whole-archive ${ARGN} -Wl,--no-whole-archive) + set(${OUT_VAR} -Wl,--whole-archive ${ARGN} -Wl,--no-whole-archive PARENT_SCOPE) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - set(${OUT_VAR} -Wl,-all_load ${ARGN} -Wl,-noall_load) + set(${OUT_VAR} -Wl,-all_load ${ARGN} -Wl,-noall_load PARENT_SCOPE) else () message(FATAL_ERROR "get_whole_archive_options not implemented for CMAKE_CXX_COMPILER_ID ${CMAKE_CXX_COMPILER_ID}") endif() -endmacro (whole_archive_options) +endfunction (whole_archive_options) From 98564b0aae7df4b9bf9cf52b4a06c44c4b6d1fff Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 20:14:12 +0000 Subject: [PATCH 0274/2859] cmake: move cmake_minimum_required bump to extern/CMakeLists.txt --- CMakeLists.txt | 2 +- extern/CMakeLists.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e7e59713a..5a497733c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ project(OpenMW) -cmake_minimum_required(VERSION 3.11) # CMP0083 NEW +cmake_minimum_required(VERSION 3.1.0) # CMP0083 NEW set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index b426ac4bff..646558897e 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -16,6 +16,8 @@ macro(FetchContent_MakeAvailableExcludeFromAll) endmacro() if(NOT OPENMW_USE_SYSTEM_BULLET) + cmake_minimum_required(VERSION 3.11) # for FetchContent + set(BUILD_BULLET3 OFF CACHE BOOL "") set(BUILD_EXTRAS OFF CACHE BOOL "") set(BUILD_OPENGL3_DEMOS OFF CACHE BOOL "") @@ -50,6 +52,8 @@ if(NOT OPENMW_USE_SYSTEM_BULLET) endif() if(NOT OPENMW_USE_SYSTEM_MYGUI) + cmake_minimum_required(VERSION 3.11) # for FetchContent + set(MYGUI_STATIC ON CACHE BOOL "") set(MYGUI_RENDERSYSTEM 4 CACHE STRING "") @@ -71,6 +75,8 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) endif() if(NOT OPENMW_USE_SYSTEM_OSG) + cmake_minimum_required(VERSION 3.11) # for FetchContent + set(OSG_STATIC ON CACHE BOOL "") set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "") From 4dc0fd299f45006e95b813dfdfd714ff0dd94def Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 22:19:10 +0000 Subject: [PATCH 0275/2859] cmake/WholeArchive.cmake: fix typo --- cmake/WholeArchive.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/WholeArchive.cmake b/cmake/WholeArchive.cmake index 2def95e8ad..be88da8181 100644 --- a/cmake/WholeArchive.cmake +++ b/cmake/WholeArchive.cmake @@ -7,4 +7,4 @@ function (get_whole_archive_options OUT_VAR) else () message(FATAL_ERROR "get_whole_archive_options not implemented for CMAKE_CXX_COMPILER_ID ${CMAKE_CXX_COMPILER_ID}") endif() -endfunction (whole_archive_options) +endfunction (get_whole_archive_options) From 26814b238626f2c204a8756257bd52ff2445a6b7 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 22:32:40 +0000 Subject: [PATCH 0276/2859] CMakeLists.txt: Restore policies as we unbumped cmake version --- CMakeLists.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a497733c4..336e8cb524 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,18 @@ project(OpenMW) -cmake_minimum_required(VERSION 3.1.0) # CMP0083 NEW +cmake_minimum_required(VERSION 3.1.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# for link time optimization, remove if cmake version is >= 3.9 +if(POLICY CMP0069) # LTO + cmake_policy(SET CMP0069 NEW) +endif() + +# for position-independent executable, remove if cmake version is >= 3.14 +if(POLICY CMP0083) + cmake_policy(SET CMP0083 NEW) +endif() + # Detect OS include(cmake/OSIdentity.cmake) From 402e43678c7219aef4afbf0abec57b7b213c69d8 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 20 Jan 2021 22:44:46 +0000 Subject: [PATCH 0277/2859] extern/CMakeLists.txt: Bump OSG --- extern/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 646558897e..f3ebfa00f1 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -105,8 +105,8 @@ if(NOT OPENMW_USE_SYSTEM_OSG) # + https://github.com/openscenegraph/OpenSceneGraph/pull/1033 include(FetchContent) FetchContent_Declare(osg - URL https://github.com/glebm/OpenSceneGraph/archive/041090d84d9d4b72f1202457fceae0ec6f79b663.zip - URL_HASH MD5=2cbf8126b27a45a2a44efe9fa39090f3 + URL https://github.com/glebm/OpenSceneGraph/archive/7fd71578dfe9283f15b999e82e802ccfda87d936.zip + URL_HASH MD5=c1bad7cc5e263ac650ae1269f212b752 SOURCE_DIR fetched/osg ) FetchContent_MakeAvailableExcludeFromAll(osg) From f0febe095ca16235dd1b505163df08cfc0776521 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Thu, 21 Jan 2021 13:52:52 +0000 Subject: [PATCH 0278/2859] extern/CMakeLists.txt: Set OSG/MYGUI_STATIC on PARENT_SCOPE --- extern/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index f3ebfa00f1..4ba3d8af19 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -54,7 +54,7 @@ endif() if(NOT OPENMW_USE_SYSTEM_MYGUI) cmake_minimum_required(VERSION 3.11) # for FetchContent - set(MYGUI_STATIC ON CACHE BOOL "") + set(MYGUI_STATIC ON CACHE BOOL "" PARENT_SCOPE) set(MYGUI_RENDERSYSTEM 4 CACHE STRING "") set(MYGUI_DISABLE_PLUGINS TRUE CACHE BOOL "") @@ -77,7 +77,7 @@ endif() if(NOT OPENMW_USE_SYSTEM_OSG) cmake_minimum_required(VERSION 3.11) # for FetchContent - set(OSG_STATIC ON CACHE BOOL "") + set(OSG_STATIC ON CACHE BOOL "" PARENT_SCOPE) set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "") set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "") From eba151884e57eb92883c989ac5ce22563ff74261 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Thu, 21 Jan 2021 16:26:41 +0000 Subject: [PATCH 0279/2859] Fix Android build https://github.com/openscenegraph/OpenSceneGraph/pull/1037 --- extern/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 4ba3d8af19..6d3d46fb0e 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -83,6 +83,7 @@ if(NOT OPENMW_USE_SYSTEM_OSG) set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "") set(BUILD_OSG_APPLICATIONS OFF CACHE BOOL "") set(BUILD_OSG_DEPRECATED_SERIALIZERS OFF CACHE BOOL "") + set(OSG_FIND_3RD_PARTY_DEPS OFF CACHE BOOL "") set(BUILD_OSG_PLUGINS_BY_DEFAULT OFF CACHE BOOL "") set(BUILD_OSG_PLUGIN_BMP ON CACHE BOOL "") @@ -103,10 +104,11 @@ if(NOT OPENMW_USE_SYSTEM_OSG) # branch OpenSceneGraph-3.6 on 18 Jan 2021. # + https://github.com/openscenegraph/OpenSceneGraph/pull/1032 # + https://github.com/openscenegraph/OpenSceneGraph/pull/1033 + # + https://github.com/openscenegraph/OpenSceneGraph/pull/1037 include(FetchContent) FetchContent_Declare(osg - URL https://github.com/glebm/OpenSceneGraph/archive/7fd71578dfe9283f15b999e82e802ccfda87d936.zip - URL_HASH MD5=c1bad7cc5e263ac650ae1269f212b752 + URL https://github.com/glebm/OpenSceneGraph/archive/7684224d8a9a8f60447d6561faf32a7b58fb5204.zip + URL_HASH MD5=d67088aeb976486287343c1287b56ba3 SOURCE_DIR fetched/osg ) FetchContent_MakeAvailableExcludeFromAll(osg) From 377bd27aa75d5c807b4c87a4531a1d17fb9b948d Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 22 Jan 2021 16:01:11 +0000 Subject: [PATCH 0280/2859] set(BUILD_SHARED_LIBS ${OSG/MYGUI_STATIC}) --- extern/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 6d3d46fb0e..4fd4536015 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -68,6 +68,7 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) URL_HASH MD5=9e990a4240430cbf567bfe73488a274e SOURCE_DIR fetched/mygui ) + set(BUILD_SHARED_LIBS ${MYGUI_STATIC}) FetchContent_MakeAvailableExcludeFromAll(mygui) set(MyGUI_INCLUDE_DIRS ${mygui_SOURCE_DIR}/MyGUIEngine/include PARENT_SCOPE) @@ -111,6 +112,7 @@ if(NOT OPENMW_USE_SYSTEM_OSG) URL_HASH MD5=d67088aeb976486287343c1287b56ba3 SOURCE_DIR fetched/osg ) + set(BUILD_SHARED_LIBS ${OSG_STATIC}) FetchContent_MakeAvailableExcludeFromAll(osg) set(OPENSCENEGRAPH_INCLUDE_DIRS ${osg_SOURCE_DIR}/include ${osg_BINARY_DIR}/include PARENT_SCOPE) From daf080ff192ad664f2fb714ccfe64a056be52040 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 22 Jan 2021 19:37:07 +0000 Subject: [PATCH 0281/2859] cmake: Move MYGUI/OSG_STATIC default to top-level Makes it clear that the USE_SYSTEM variables affect the defaults of STATIC variables. --- CMakeLists.txt | 6 ++++++ extern/CMakeLists.txt | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 336e8cb524..fc0d1b32c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,11 +111,17 @@ option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON) option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" OFF) +if(NOT OPENMW_USE_SYSTEM_OSG) + set(OSG_STATIC ON CACHE BOOL "") +endif() option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) option(OPENMW_USE_SYSTEM_MYGUI "Use system provided mygui library" ON) option(MYGUI_STATIC "Link static build of Mygui into the binaries" OFF) +if(NOT OPENMW_USE_SYSTEM_MYGUI) + set(MYGUI_STATIC ON CACHE BOOL "") +endif() option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 4fd4536015..de2f80ddfc 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -54,8 +54,6 @@ endif() if(NOT OPENMW_USE_SYSTEM_MYGUI) cmake_minimum_required(VERSION 3.11) # for FetchContent - set(MYGUI_STATIC ON CACHE BOOL "" PARENT_SCOPE) - set(MYGUI_RENDERSYSTEM 4 CACHE STRING "") set(MYGUI_DISABLE_PLUGINS TRUE CACHE BOOL "") set(MYGUI_BUILD_DEMOS OFF CACHE BOOL "") @@ -78,8 +76,6 @@ endif() if(NOT OPENMW_USE_SYSTEM_OSG) cmake_minimum_required(VERSION 3.11) # for FetchContent - set(OSG_STATIC ON CACHE BOOL "" PARENT_SCOPE) - set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "") set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "") set(BUILD_OSG_APPLICATIONS OFF CACHE BOOL "") From 99061345cc652d827e4a1dea9af49958ef207f6e Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 22 Jan 2021 20:44:21 +0000 Subject: [PATCH 0282/2859] WholeArchive.cmake: Fix mismatched args warning --- cmake/WholeArchive.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/WholeArchive.cmake b/cmake/WholeArchive.cmake index be88da8181..0e4a09c796 100644 --- a/cmake/WholeArchive.cmake +++ b/cmake/WholeArchive.cmake @@ -7,4 +7,4 @@ function (get_whole_archive_options OUT_VAR) else () message(FATAL_ERROR "get_whole_archive_options not implemented for CMAKE_CXX_COMPILER_ID ${CMAKE_CXX_COMPILER_ID}") endif() -endfunction (get_whole_archive_options) +endfunction () From 3d334dae754fdc67851812cc2ef5c2be8c8909fd Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 22 Jan 2021 21:33:07 +0000 Subject: [PATCH 0283/2859] Fix MYGUI/OSG_STATIC and BUILD_SHARED_LIBS --- CMakeLists.txt | 20 ++++++++++++-------- extern/CMakeLists.txt | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc0d1b32c0..5ba48c01d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,19 +109,23 @@ option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) option(QT_STATIC "Link static build of QT into the binaries" FALSE) +option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) + option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON) -option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" OFF) -if(NOT OPENMW_USE_SYSTEM_OSG) - set(OSG_STATIC ON CACHE BOOL "") +if(OPENMW_USE_SYSTEM_OSG) + set(_osg_static_default OFF) +else() + set(_osg_static_default ON) endif() - -option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) +option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" ${_osg_static_default}) option(OPENMW_USE_SYSTEM_MYGUI "Use system provided mygui library" ON) -option(MYGUI_STATIC "Link static build of Mygui into the binaries" OFF) -if(NOT OPENMW_USE_SYSTEM_MYGUI) - set(MYGUI_STATIC ON CACHE BOOL "") +if(OPENMW_USE_SYSTEM_MYGUI) + set(_mygui_static_default OFF) +else() + set(_mygui_static_default ON) endif() +option(MYGUI_STATIC "Link static build of Mygui into the binaries" ${_mygui_static_default}) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index de2f80ddfc..a8e741bd99 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -60,13 +60,18 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) set(MYGUI_BUILD_PLUGINS OFF CACHE BOOL "") set(MYGUI_BUILD_TOOLS OFF CACHE BOOL "") + if(MYGUI_STATIC) + set(BUILD_SHARED_LIBS OFF) + else() + set(BUILD_SHARED_LIBS ON) + endif() + include(FetchContent) FetchContent_Declare(mygui URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.0.zip URL_HASH MD5=9e990a4240430cbf567bfe73488a274e SOURCE_DIR fetched/mygui ) - set(BUILD_SHARED_LIBS ${MYGUI_STATIC}) FetchContent_MakeAvailableExcludeFromAll(mygui) set(MyGUI_INCLUDE_DIRS ${mygui_SOURCE_DIR}/MyGUIEngine/include PARENT_SCOPE) @@ -98,6 +103,12 @@ if(NOT OPENMW_USE_SYSTEM_OSG) set(OPENGL_PROFILE "GL2" CACHE STRING "") + if(OSG_STATIC) + set(BUILD_SHARED_LIBS OFF) + else() + set(BUILD_SHARED_LIBS ON) + endif() + # branch OpenSceneGraph-3.6 on 18 Jan 2021. # + https://github.com/openscenegraph/OpenSceneGraph/pull/1032 # + https://github.com/openscenegraph/OpenSceneGraph/pull/1033 @@ -108,7 +119,6 @@ if(NOT OPENMW_USE_SYSTEM_OSG) URL_HASH MD5=d67088aeb976486287343c1287b56ba3 SOURCE_DIR fetched/osg ) - set(BUILD_SHARED_LIBS ${OSG_STATIC}) FetchContent_MakeAvailableExcludeFromAll(osg) set(OPENSCENEGRAPH_INCLUDE_DIRS ${osg_SOURCE_DIR}/include ${osg_BINARY_DIR}/include PARENT_SCOPE) From 4098b455f59f6e3f5b8e8d11a5a212db7a65a06b Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Fri, 22 Jan 2021 22:37:10 +0000 Subject: [PATCH 0284/2859] extern/CMakeLists.txt: Bump OSG All the necessary fixes have been upstreamed --- extern/CMakeLists.txt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index a8e741bd99..d82f57b209 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -109,14 +109,11 @@ if(NOT OPENMW_USE_SYSTEM_OSG) set(BUILD_SHARED_LIBS ON) endif() - # branch OpenSceneGraph-3.6 on 18 Jan 2021. - # + https://github.com/openscenegraph/OpenSceneGraph/pull/1032 - # + https://github.com/openscenegraph/OpenSceneGraph/pull/1033 - # + https://github.com/openscenegraph/OpenSceneGraph/pull/1037 + # branch OpenSceneGraph-3.6 on 23 Jan 2021. include(FetchContent) FetchContent_Declare(osg - URL https://github.com/glebm/OpenSceneGraph/archive/7684224d8a9a8f60447d6561faf32a7b58fb5204.zip - URL_HASH MD5=d67088aeb976486287343c1287b56ba3 + URL https://github.com/openscenegraph/OpenSceneGraph/archive/b8862d04203c7d5576fc8189fe4df83c4e855b9b.zip + URL_HASH MD5=d80a42eb9c983c08b40ecd103bae03f4 SOURCE_DIR fetched/osg ) FetchContent_MakeAvailableExcludeFromAll(osg) From 3308c717f877423aa4c3640374ea462b21a3ce82 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 24 Jan 2021 01:20:23 +0000 Subject: [PATCH 0285/2859] extern/CMakeLists.txt: Switch to openmw's OSG --- extern/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index d82f57b209..fd21b70722 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -112,8 +112,8 @@ if(NOT OPENMW_USE_SYSTEM_OSG) # branch OpenSceneGraph-3.6 on 23 Jan 2021. include(FetchContent) FetchContent_Declare(osg - URL https://github.com/openscenegraph/OpenSceneGraph/archive/b8862d04203c7d5576fc8189fe4df83c4e855b9b.zip - URL_HASH MD5=d80a42eb9c983c08b40ecd103bae03f4 + URL https://github.com/OpenMW/osg/archive/e65f47c4ab3a0b53cc19f517961671e5f840a08d.zip + URL_HASH MD5=0c967fe48d80744f6956f6b0b67ef7c6 SOURCE_DIR fetched/osg ) FetchContent_MakeAvailableExcludeFromAll(osg) From 2798db541844fffe4b1f9b572087e0ee7f20deed Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 24 Jan 2021 18:44:18 +0000 Subject: [PATCH 0286/2859] CI/before_script.linux.sh: -> 4 spaces and remove unused ENV --- CI/before_script.linux.sh | 67 +++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index ef617e0d51..5f74b47147 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -5,51 +5,50 @@ set -xeo pipefail free -m if [[ "${BUILD_TESTS_ONLY}" ]]; then - export GOOGLETEST_DIR="${PWD}/googletest/build/install" - env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh + export GOOGLETEST_DIR="${PWD}/googletest/build/install" + env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh fi declare -a CMAKE_CONF_OPTS=( - -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" - -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" - -DCMAKE_C_COMPILER_LAUNCHER=ccache - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - -DCMAKE_INSTALL_PREFIX=install - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DBUILD_SHARED_LIBS=OFF - -DUSE_SYSTEM_TINYXML=ON - -DCMAKE_INSTALL_PREFIX=install + -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" + -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" + -DCMAKE_C_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + -DCMAKE_INSTALL_PREFIX=install + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DBUILD_SHARED_LIBS=OFF + -DUSE_SYSTEM_TINYXML=ON + -DCMAKE_INSTALL_PREFIX=install ) -declare -a CMAKE_CONF_ENV=() if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then - CMAKE_CONF_OPTS+=( - -DOPENMW_USE_SYSTEM_MYGUI=OFF - -DOPENMW_USE_SYSTEM_OSG=OFF - -DOPENMW_USE_SYSTEM_BULLET=OFF - ) + CMAKE_CONF_OPTS+=( + -DOPENMW_USE_SYSTEM_MYGUI=OFF + -DOPENMW_USE_SYSTEM_OSG=OFF + -DOPENMW_USE_SYSTEM_BULLET=OFF + ) fi mkdir -p build cd build if [[ "${BUILD_TESTS_ONLY}" ]]; then - env "${CMAKE_CONF_ENV[@]}" ${ANALYZE} cmake \ - "${CMAKE_CONF_OPTS[@]}" \ - -DBUILD_OPENMW=OFF \ - -DBUILD_BSATOOL=OFF \ - -DBUILD_ESMTOOL=OFF \ - -DBUILD_LAUNCHER=OFF \ - -DBUILD_MWINIIMPORTER=OFF \ - -DBUILD_ESSIMPORTER=OFF \ - -DBUILD_OPENCS=OFF \ - -DBUILD_WIZARD=OFF \ - -DBUILD_UNITTESTS=ON \ - -DGTEST_ROOT="${GOOGLETEST_DIR}" \ - -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ - .. + ${ANALYZE} cmake \ + "${CMAKE_CONF_OPTS[@]}" \ + -DBUILD_OPENMW=OFF \ + -DBUILD_BSATOOL=OFF \ + -DBUILD_ESMTOOL=OFF \ + -DBUILD_LAUNCHER=OFF \ + -DBUILD_MWINIIMPORTER=OFF \ + -DBUILD_ESSIMPORTER=OFF \ + -DBUILD_OPENCS=OFF \ + -DBUILD_WIZARD=OFF \ + -DBUILD_UNITTESTS=ON \ + -DGTEST_ROOT="${GOOGLETEST_DIR}" \ + -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ + .. else - env "${CMAKE_CONF_ENV[@]}" ${ANALYZE} cmake \ - "${CMAKE_CONF_OPTS[@]}" \ - .. + ${ANALYZE} cmake \ + "${CMAKE_CONF_OPTS[@]}" \ + .. fi From 93bc5848af328457a29406a25460343b01c897f7 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Mon, 25 Jan 2021 21:02:29 +0000 Subject: [PATCH 0287/2859] .gitlab-ci.yml: Bump Android cache key to v3 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7ad26bc550..42c70c9806 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -306,7 +306,7 @@ Debian_AndroidNDK_arm64-v8a: variables: CCACHE_SIZE: 3G cache: - key: Debian_AndroidNDK_arm64-v8a.v2 + key: Debian_AndroidNDK_arm64-v8a.v3 paths: - apt-cache/ - ccache/ From c8db4b9b3407ddedea2b3e665abe8b9c8ed61716 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Tue, 26 Jan 2021 19:01:52 +0000 Subject: [PATCH 0288/2859] .gitlab-ci.yml: Increase Android timeout (1h -> 1h30m) --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 42c70c9806..e63a8b9d92 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -331,3 +331,5 @@ Debian_AndroidNDK_arm64-v8a: artifacts: paths: - build/install/ + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + timeout: 1h30m From 64475ebedb803a853c62062eb199df2183076005 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 27 Jan 2021 07:15:09 +0100 Subject: [PATCH 0289/2859] Remove a brainfart from precise projectile handling: all non-actor non-projectile objects were treated as ground. --- apps/openmw/mwphysics/projectileconvexcallback.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index 0d0ac87202..b803c4400b 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -55,7 +55,9 @@ namespace MWPhysics } default: { - mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + auto ptr = target ? target->getPtr() : MWWorld::Ptr(); + mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld); break; } } From 7cd7fa2f08eaba7de05532a2a1998f2f751959f7 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Wed, 27 Jan 2021 08:04:33 +0000 Subject: [PATCH 0290/2859] Collect all available stats if OPENMW_OSG_STATS_FILE is set and point to a valid file. --- apps/openmw/engine.cpp | 27 ++++--- apps/openmw/mwphysics/mtphysics.cpp | 2 + components/resource/stats.cpp | 110 +++++++++++++++++++++++++++- components/resource/stats.hpp | 11 ++- 4 files changed, 135 insertions(+), 15 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ead2726cd3..a81a373375 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -177,6 +177,8 @@ namespace ~ScopedProfile() { + if (!mStats.collectStats("engine")) + return; const osg::Timer_t end = mTimer.tick(); const UserStats& stats = UserStatsValue::sValue; @@ -863,16 +865,29 @@ void OMW::Engine::go() prepareEngine (settings); + std::ofstream stats; + if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) + { + stats.open(path, std::ios_base::out); + if (stats.is_open()) + Log(Debug::Info) << "Stats will be written to: " << path; + else + Log(Debug::Warning) << "Failed to open file for stats: " << path; + } + // Setup profiler - osg::ref_ptr statshandler = new Resource::Profiler; + osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); - osg::ref_ptr resourceshandler = new Resource::StatsHandler; + osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open()); mViewer->addEventHandler(resourceshandler); + if (stats.is_open()) + Resource::CollectStatistics(mViewer); + // Start the game if (!mSaveGameFile.empty()) { @@ -897,14 +912,6 @@ void OMW::Engine::go() mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } - std::ofstream stats; - if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) - { - stats.open(path, std::ios_base::out); - if (!stats) - Log(Debug::Warning) << "Failed to open file for stats: " << path; - } - // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 6c7c573a49..754bb60afe 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -540,6 +540,8 @@ namespace MWPhysics void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { + if (!stats.collectStats("engine")) + return; if (mFrameNumber == frameNumber - 1) { stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin)); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 942bd92d80..28b90c31c2 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -18,10 +18,72 @@ namespace Resource { -StatsHandler::StatsHandler(): +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 void setupStatCollection() +{ + const char* envList = getenv("OPENMW_OSG_STATS_LIST"); + if (envList == nullptr) + return; + + std::string_view kwList(envList); + + auto kwBegin = kwList.begin(); + + while (kwBegin != kwList.end()) + { + auto kwEnd = std::find(kwBegin, kwList.end(), ';'); + + const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); + + if (kw.compare("gpu") == 0) + collectStatGPU = true; + else if (kw.compare("event") == 0) + collectStatEvent = true; + else if (kw.compare("frame_rate") == 0) + collectStatFrameRate = true; + else if (kw.compare("update") == 0) + collectStatUpdate = true; + else if (kw.compare("engine") == 0) + collectStatEngine = true; + else if (kw.compare("rendering") == 0) + collectStatRendering = true; + else if (kw.compare("cameraobjects") == 0) + collectStatCameraObjects = true; + else if (kw.compare("viewerobjects") == 0) + collectStatViewerObjects = true; + else if (kw.compare("resource") == 0) + collectStatResource = true; + else if (kw.compare("times") == 0) + { + collectStatGPU = true; + collectStatEvent = true; + collectStatFrameRate = true; + collectStatUpdate = true; + collectStatEngine = true; + collectStatRendering = true; + } + + if (kwEnd == kwList.end()) + break; + + kwBegin = std::next(kwEnd); + } +} + +StatsHandler::StatsHandler(bool offlineCollect): _key(osgGA::GUIEventAdapter::KEY_F4), _initialized(false), _statsType(false), + _offlineCollect(offlineCollect), _statsWidth(1280.0f), _statsHeight(1024.0f), _font(""), @@ -38,7 +100,8 @@ StatsHandler::StatsHandler(): _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); } -Profiler::Profiler() +Profiler::Profiler(bool offlineCollect): + _offlineCollect(offlineCollect) { if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); @@ -48,6 +111,28 @@ Profiler::Profiler() _characterSize = 18; setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); + setupStatCollection(); +} + +bool Profiler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) +{ + osgViewer::ViewerBase* viewer = nullptr; + + bool handled = StatsHandler::handle(ea, aa); + + auto* view = dynamic_cast(&aa); + if (view) + viewer = view->getViewerBase(); + + if (viewer) + { + // Add/remove openmw stats to the osd as necessary + viewer->getViewerStats()->collectStats("engine", _statsType == StatsHandler::StatsType::VIEWER_STATS); + + if (_offlineCollect) + CollectStatistics(viewer); + } + return handled; } bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) @@ -67,6 +152,9 @@ bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdap toggle(viewer); + if (_offlineCollect) + CollectStatistics(viewer); + aa.requestRedraw(); return true; } @@ -370,6 +458,22 @@ void StatsHandler::getUsage(osg::ApplicationUsage &usage) const usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); } - +void CollectStatistics(osgViewer::ViewerBase* viewer) +{ + osgViewer::Viewer::Cameras cameras; + viewer->getCameras(cameras); + for (auto* camera : cameras) + { + if (collectStatGPU) camera->getStats()->collectStats("gpu", true); + if (collectStatRendering) camera->getStats()->collectStats("rendering", true); + if (collectStatCameraObjects) camera->getStats()->collectStats("scene", true); + } + if (collectStatEvent) viewer->getViewerStats()->collectStats("event", true); + if (collectStatFrameRate) viewer->getViewerStats()->collectStats("frame_rate", true); + if (collectStatUpdate) viewer->getViewerStats()->collectStats("update", true); + if (collectStatResource) viewer->getViewerStats()->collectStats("resource", true); + if (collectStatViewerObjects) viewer->getViewerStats()->collectStats("scene", true); + if (collectStatEngine) viewer->getViewerStats()->collectStats("engine", true); +} } diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index 9fa583cca4..560275d701 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -18,13 +18,17 @@ namespace Resource class Profiler : public osgViewer::StatsHandler { public: - Profiler(); + Profiler(bool offlineCollect); + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; + + private: + bool _offlineCollect; }; class StatsHandler : public osgGA::GUIEventHandler { public: - StatsHandler(); + StatsHandler(bool offlineCollect); void setKey(int key) { _key = key; } int getKey() const { return _key; } @@ -47,6 +51,7 @@ namespace Resource osg::ref_ptr _camera; bool _initialized; bool _statsType; + bool _offlineCollect; float _statsWidth; float _statsHeight; @@ -58,6 +63,8 @@ namespace Resource }; + void CollectStatistics(osgViewer::ViewerBase* viewer); + } #endif From 3194520dcd5eecb04540af0fc936bd303b38c566 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 30 Dec 2020 22:11:32 +0200 Subject: [PATCH 0291/2859] Move base_anim settings to settings-default.cfg --- apps/openmw/mwclass/npc.cpp | 12 ++++++------ apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/creatureanimation.cpp | 6 +++--- apps/openmw/mwrender/npcanimation.cpp | 4 ++-- apps/openmw/mwrender/renderingmanager.cpp | 15 +++++++++------ components/resource/scenemanager.cpp | 2 ++ components/sceneutil/actorutil.cpp | 18 ++++++++++-------- files/settings-default.cfg | 16 ++++++++++++++++ 8 files changed, 49 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 27c98bbce5..1f7e77daf0 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -418,10 +418,10 @@ namespace MWClass { const MWWorld::LiveCellRef *ref = ptr.get(); - std::string model = "meshes\\base_anim.nif"; + std::string model = Settings::Manager::getString("xbaseanim", "Models"); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if(race->mData.mFlags & ESM::Race::Beast) - model = "meshes\\base_animkna.nif"; + model = Settings::Manager::getString("baseanimkna", "Models"); return model; } @@ -431,12 +431,12 @@ namespace MWClass const MWWorld::LiveCellRef *npc = ptr.get(); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mRace); if(race && race->mData.mFlags & ESM::Race::Beast) - models.emplace_back("meshes\\base_animkna.nif"); + models.emplace_back(Settings::Manager::getString("baseanimkna", "Models")); // keep these always loaded just in case - models.emplace_back("meshes/xargonian_swimkna.nif"); - models.emplace_back("meshes/xbase_anim_female.nif"); - models.emplace_back("meshes/xbase_anim.nif"); + models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models")); + models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models")); + models.emplace_back(Settings::Manager::getString("xbaseanim", "Models")); if (!npc->mBase->mModel.empty()) models.push_back("meshes/"+npc->mBase->mModel); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f8ff3780d3..d8c7599358 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1500,7 +1500,7 @@ namespace MWRender MWWorld::LiveCellRef *ref = mPtr.get(); if(ref->mBase->mFlags & ESM::Creature::Bipedal) { - defaultSkeleton = "meshes\\xbase_anim.nif"; + defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models"); inject = true; } } diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index f1df6c90fc..2987111621 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -10,7 +10,7 @@ #include #include #include - +#include #include #include "../mwbase/environment.hpp" @@ -35,7 +35,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, setObjectRoot(model, false, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) - addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); addAnimSource(model, model); } } @@ -54,7 +54,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const if((ref->mBase->mFlags&ESM::Creature::Bipedal)) { - addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); } addAnimSource(model, model); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d97e57115a..b538f0b7b6 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -524,7 +524,7 @@ void NpcAnimation::updateNpcBase() if(!is1stPerson) { - const std::string base = "meshes\\xbase_anim.nif"; + const std::string base = Settings::Manager::getString("xbaseanim", "Models"); if (smodel != base && !isWerewolf) addAnimSource(base, smodel); @@ -538,7 +538,7 @@ void NpcAnimation::updateNpcBase() } else { - const std::string base = "meshes\\xbase_anim.1st.nif"; + const std::string base = Settings::Manager::getString("xbaseanim1st", "Models"); if (smodel != base && !isWerewolf) addAnimSource(base, smodel); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ba4baec54..a3aee5c0fa 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -456,12 +456,15 @@ namespace MWRender mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); mWater->listAssetsToPreload(workItem->mTextures); - const char* basemodels[] = {"xbase_anim", "xbase_anim.1st", "xbase_anim_female", "xbase_animkna"}; - for (size_t i=0; imModels.push_back(std::string("meshes/") + basemodels[i] + ".nif"); - workItem->mKeyframes.push_back(std::string("meshes/") + basemodels[i] + ".kf"); - } + workItem->mModels.push_back(Settings::Manager::getString("xbaseanim", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xbaseanim1st", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xbaseanimfemale", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xargonianswimkna", "Models")); + + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimkf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanim1stkf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimfemalekf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xargonianswimknakf", "Models")); workItem->mTextures.emplace_back("textures/_land_default.dds"); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 71f11e382e..92de2e0dd5 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -26,6 +26,8 @@ #include #include +#include + #include #include diff --git a/components/sceneutil/actorutil.cpp b/components/sceneutil/actorutil.cpp index 988a61f60e..a0785e413d 100644 --- a/components/sceneutil/actorutil.cpp +++ b/components/sceneutil/actorutil.cpp @@ -1,5 +1,7 @@ #include "actorutil.hpp" +#include + namespace SceneUtil { std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf) @@ -7,24 +9,24 @@ namespace SceneUtil if (!firstPerson) { if (isWerewolf) - return "meshes\\wolf\\skin.nif"; + return Settings::Manager::getString("wolfskin", "Models"); else if (isBeast) - return "meshes\\base_animkna.nif"; + return Settings::Manager::getString("baseanimkna", "Models"); else if (isFemale) - return "meshes\\base_anim_female.nif"; + return Settings::Manager::getString("baseanimfemale", "Models"); else - return "meshes\\base_anim.nif"; + return Settings::Manager::getString("baseanim", "Models"); } else { if (isWerewolf) - return "meshes\\wolf\\skin.1st.nif"; + return Settings::Manager::getString("wolfskin1st", "Models"); else if (isBeast) - return "meshes\\base_animkna.1st.nif"; + return Settings::Manager::getString("baseanimkna1st", "Models"); else if (isFemale) - return "meshes\\base_anim_female.1st.nif"; + return Settings::Manager::getString("baseanimfemale1st", "Models"); else - return "meshes\\base_anim.1st.nif"; + return Settings::Manager::getString("xbaseanim1st", "Models"); } } } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 3660f56f0a..db947f8ed0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -969,3 +969,19 @@ distance = 1 # A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) min chunk size = 0.5 + +xbaseanim = meshes\xbase_anim.nif +baseanim = meshes\base_anim.nif +xbaseanim1st = meshes\xbase_anim.1st.nif +baseanimkna = meshes\base_animkna.nif +baseanimkna1st = meshes\base_animkna.1st.nif +xbaseanimfemale = meshes/xbase_anim_female.nif +baseanimfemale = meshes/base_anim_female.nif +baseanimfemale1st = meshes\base_anim_female.1st.nif +wolfskin = meshes\wolf\skin.nif +wolfskin1st = meshes\wolf\skin.1st.nif +xargonianswimkna = meshes/xargonian_swimkna.nif +xbaseanimkf = meshes\xbase_anim.kf +xbaseanim1stkf = meshes\xbase_anim.1st.kf +xbaseanimfemalekf = meshes/xbase_anim_female.kf +xargonianswimknakf = meshes/xargonian_swimkna.kf From 83ee1cc582e355a78e836d674bb228ec47cd1c9e Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 1 Jan 2021 22:51:23 +0200 Subject: [PATCH 0292/2859] fix xbase to baseanim --- apps/openmw/mwclass/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 1f7e77daf0..5de4c197e6 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -418,7 +418,7 @@ namespace MWClass { const MWWorld::LiveCellRef *ref = ptr.get(); - std::string model = Settings::Manager::getString("xbaseanim", "Models"); + std::string model = Settings::Manager::getString("baseanim", "Models"); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if(race->mData.mFlags & ESM::Race::Beast) model = Settings::Manager::getString("baseanimkna", "Models"); From 23137d0c54f555b30323359953e3db81ded3f361 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 27 Jan 2021 16:24:11 +0100 Subject: [PATCH 0293/2859] Revert a wrong change introduced in MR 546 A prerequisite to create physics objects for statics was to remove the dependency on base node (since it doesn't yet exists) for object position. It is still necessary for animation though. Restore the basenode (and the associated FIXME) so that animated objects works properly. --- apps/openmw/mwphysics/object.cpp | 3 --- apps/openmw/mwworld/scene.cpp | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 0a7b9540c5..6d3954088a 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -116,9 +116,6 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - if (mPtr.getRefData().getBaseNode() == nullptr) - return true; - assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b387..2abedcd788 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -122,6 +122,8 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, rotation); From a3ab8dfbb44aef52e895e8746e525d82519799b5 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 28 Jan 2021 12:48:19 +0000 Subject: [PATCH 0294/2859] Revert "Merge branch 'movement_fix2' into 'master'" This reverts merge request !496 --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 595f9d629c..276321b81a 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -309,7 +309,7 @@ namespace MWMechanics if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) mPath.pop_front(); - if (mPath.size() == 1 && (mPath.front() - position).length2() < destinationTolerance * destinationTolerance) + if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) mPath.pop_front(); } From 3b9f8b5fa2318e35bae594ad94a759e2d6d4a998 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 28 Jan 2021 18:37:47 +0400 Subject: [PATCH 0295/2859] Avoid null dereference for objects without cells --- apps/openmw/mwworld/worldimp.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 98af121a5e..ef00315cbb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1337,6 +1337,12 @@ namespace MWWorld void World::adjustPosition(const Ptr &ptr, bool force) { + if (ptr.isEmpty()) + { + Log(Debug::Warning) << "Unable to adjust position for empty object"; + return; + } + osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); if(!ptr.getRefData().getBaseNode()) @@ -1345,6 +1351,12 @@ namespace MWWorld return; } + if (!ptr.isInCell()) + { + Log(Debug::Warning) << "Unable to adjust position for object '" << ptr.getCellRef().getRefId() << "' - it has no cell"; + return; + } + const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits::max(); pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below From 36cd81815504cef195fe853b0b93046ac04c3c19 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:10:33 +0100 Subject: [PATCH 0296/2859] Fix separate drop, refactor for code reuse --- apps/opencs/view/render/instancemode.cpp | 116 ++++++++--------------- apps/opencs/view/render/instancemode.hpp | 21 ++-- 2 files changed, 54 insertions(+), 83 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4f6759cdb8..2489f4f570 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -802,39 +802,15 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active) getWorldspaceWidget().clearSelection (Mask_Reference); } -void CSVRender::InstanceMode::dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight) +void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) { - osg::Vec3d point = object->getPosition().asVec3(); - - osg::Vec3d start = point; - start.z() += objectHeight; - osg::Vec3d end = point; - end.z() = std::numeric_limits::lowest(); - - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( - osgUtil::Intersector::MODEL, start, end) ); - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor(intersector); - - if (dropMode == TerrainSep) - visitor.setTraversalMask(Mask_Terrain); - if (dropMode == CollisionSep) - visitor.setTraversalMask(Mask_Terrain | Mask_Reference); - - mParentNode->accept(visitor); - - osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); - if (it != intersector->getIntersections().end()) - { - osgUtil::LineSegmentIntersector::Intersection intersection = *it; - ESM::Position position = object->getPosition(); - object->setEdited (Object::Override_Position); - position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight; - object->setPosition(position.pos); - } + object->setEdited(Object::Override_Position); + ESM::Position position = object->getPosition(); + position.pos[2] -= dropHeight; + object->setPosition(position.pos); } -float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) +float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) { osg::Vec3d point = object->getPosition().asVec3(); @@ -848,9 +824,9 @@ float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Objec intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); - if (dropMode == Terrain) + if (dropMode & Terrain) visitor.setTraversalMask(Mask_Terrain); - if (dropMode == Collision) + if (dropMode & Collision) visitor.setTraversalMask(Mask_Terrain | Mask_Reference); mParentNode->accept(visitor); @@ -897,52 +873,44 @@ void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString comman CSMWorld::CommandMacro macro (undoStack, commandMsg); - DropObjectDataHandler dropObjectDataHandler(&getWorldspaceWidget()); + DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget()); - switch (dropMode) + if(dropMode & Separate) { - case Terrain: - case Collision: - { - float smallestDropHeight = std::numeric_limits::max(); - int counter = 0; - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); - if (thisDrop < smallestDropHeight) - smallestDropHeight = thisDrop; - counter++; - } - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - objectTag->mObject->setEdited (Object::Override_Position); - ESM::Position position = objectTag->mObject->getPosition(); - position.pos[2] -= smallestDropHeight; - objectTag->mObject->setPosition(position.pos); - objectTag->mObject->apply (macro); - } - } - break; - - case TerrainSep: - case CollisionSep: - { - int counter = 0; - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); - objectTag->mObject->apply (macro); - counter++; - } - } - break; + int counter = 0; + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; + float dropHeight = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); + dropInstance(objectTag->mObject, dropHeight); + objectTag->mObject->apply(macro); + counter++; + } + } + else + { + float smallestDropHeight = std::numeric_limits::max(); + int counter = 0; + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; + float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); + if (thisDrop < smallestDropHeight) + smallestDropHeight = thisDrop; + counter++; + } + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + dropInstance(objectTag->mObject, smallestDropHeight); + objectTag->mObject->apply(macro); + } } } -CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget) +CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget) : mWorldspaceWidget(worldspacewidget) { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); @@ -969,7 +937,7 @@ CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worlds } } -CSVRender::DropObjectDataHandler::~DropObjectDataHandler() +CSVRender::DropObjectHeightHandler::~DropObjectHeightHandler() { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); int counter = 0; diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 0a4f2e4788..d0a263ed84 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -28,10 +28,13 @@ namespace CSVRender enum DropMode { - Collision, - Terrain, - CollisionSep, - TerrainSep + Separate = 0x1, + + Collision = 0b10, + Terrain = 0b100, + + CollisionSep = Collision | Separate, + TerrainSep = Terrain | Separate, }; CSVWidget::SceneToolMode *mSubMode; @@ -53,8 +56,8 @@ namespace CSVRender osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); void handleSelectDrag(const QPoint& pos); - void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight); - float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); + void dropInstance(CSVRender::Object* object, float objectHeight); + float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: @@ -116,11 +119,11 @@ namespace CSVRender }; /// \brief Helper class to handle object mask data in safe way - class DropObjectDataHandler + class DropObjectHeightHandler { public: - DropObjectDataHandler(WorldspaceWidget* worldspacewidget); - ~DropObjectDataHandler(); + DropObjectHeightHandler(WorldspaceWidget* worldspacewidget); + ~DropObjectHeightHandler(); std::vector mObjectHeights; private: From edc6d5c3e725a2da0c53d82dc025f718008e185d Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:22:48 +0100 Subject: [PATCH 0297/2859] Fix a typo in separate drop binds --- apps/opencs/view/render/instancemode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 2489f4f570..99ddce7f7d 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -854,12 +854,12 @@ void CSVRender::InstanceMode::dropSelectedInstancesToTerrain() void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately() { - handleDropMethod(TerrainSep, "Drop instances to next collision level separately"); + handleDropMethod(CollisionSep, "Drop instances to next collision level separately"); } void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately() { - handleDropMethod(CollisionSep, "Drop instances to terrain level separately"); + handleDropMethod(TerrainSep, "Drop instances to terrain level separately"); } void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg) From eca0d8b7ea9a9e63779a8dff55623c12a151f870 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 22:40:44 +0100 Subject: [PATCH 0298/2859] Fix typo in DropMode enum --- apps/opencs/view/render/instancemode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index d0a263ed84..c81c153d44 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -28,7 +28,7 @@ namespace CSVRender enum DropMode { - Separate = 0x1, + Separate = 0b1, Collision = 0b10, Terrain = 0b100, From ee2f0e7eb33d1226f4e9b961f8a355224bff079f Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Jan 2021 23:47:38 +0100 Subject: [PATCH 0299/2859] Fix inconsistent argument name --- apps/opencs/view/render/instancemode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index c81c153d44..73b7fff12a 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -56,7 +56,7 @@ namespace CSVRender osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); void handleSelectDrag(const QPoint& pos); - void dropInstance(CSVRender::Object* object, float objectHeight); + void dropInstance(CSVRender::Object* object, float dropHeight); float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: From f8e8496d36b2a232c15c421b1d55c3c8a9cf49f4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:50:39 +0400 Subject: [PATCH 0300/2859] Revert "Revert a wrong change introduced in MR 546" This reverts commit 23137d0c54f555b30323359953e3db81ded3f361. --- apps/openmw/mwphysics/object.cpp | 3 +++ apps/openmw/mwworld/scene.cpp | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 6d3954088a..0a7b9540c5 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -116,6 +116,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; + if (mPtr.getRefData().getBaseNode() == nullptr) + return true; + assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2abedcd788..fcf2c4b387 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -122,8 +122,6 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); - else - ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode setNodeRotation(ptr, rendering, rotation); From 8019fd594defac7d18e88c690f1bde910811a85d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:50:56 +0400 Subject: [PATCH 0301/2859] Revert "Add changelog" This reverts commit f219c5992bc6efe688ab128915f2d36c39da47a0. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c7b5757a..f347fb46eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,6 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key - Bug #5379: Wandering NPCs falling through cantons Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory From 165af1c365628a4e2ae1bec6a54cc39b335db83a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:51:05 +0400 Subject: [PATCH 0302/2859] Revert "Some actors are supposed to spawn on a static object that belong to an adjacent cell." This reverts commit f031a191b847443c848637b17d0936a43b5070b5. --- apps/openmw/mwclass/static.cpp | 5 - apps/openmw/mwclass/static.hpp | 2 - apps/openmw/mwphysics/physicssystem.cpp | 5 +- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 13 +- apps/openmw/mwworld/class.hpp | 4 - apps/openmw/mwworld/scene.cpp | 405 ++++++++++-------------- apps/openmw/mwworld/scene.hpp | 15 +- 8 files changed, 182 insertions(+), 269 deletions(-) diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 28156c97f6..108c4eaa20 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -63,9 +63,4 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } - - bool Static::isStatic() const - { - return true; - } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index d0f4913f0f..f856e9fd94 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -25,8 +25,6 @@ namespace MWClass static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; - - bool isStatic() const override; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5a9a0be832..68cec48bcb 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -469,7 +469,7 @@ namespace MWPhysics mAnimatedObjects.insert(obj.get()); } - void PhysicsSystem::remove(const MWWorld::Ptr &ptr, bool keepObject) + void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) @@ -479,8 +479,7 @@ namespace MWPhysics mAnimatedObjects.erase(found->second.get()); - if (!keepObject) - mObjects.erase(found); + mObjects.erase(found); } ActorMap::iterator foundActor = mActors.find(ptr); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 715a6cd1a2..c61b368f8e 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -138,7 +138,7 @@ namespace MWPhysics Projectile* getProjectile(int projectileId) const; // Object or Actor - void remove (const MWWorld::Ptr& ptr, bool keepObject = false); + void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index 5985d06fb6..e68b383b77 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -18,23 +18,12 @@ namespace MWWorld if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); + mObjects.push_back (ptr); } - mObjects.push_back (ptr); return true; } }; - - struct ListObjectsVisitor - { - std::vector mObjects; - - bool operator() (MWWorld::Ptr ptr) - { - mObjects.push_back (ptr); - return true; - } - }; } #endif diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 592552d47f..39fb6fe4c5 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -318,10 +318,6 @@ namespace MWWorld return false; } - virtual bool isStatic() const { - return false; - } - virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b387..313e9a1527 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -105,7 +105,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) + MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -113,29 +113,25 @@ namespace return; } + bool useAnim = ptr.getClass().useAnim(); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - if (!onlyPhysics) - { - bool useAnim = ptr.getClass().useAnim(); - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) - ptr.getClass().insertObjectRendering(ptr, model, rendering); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); - setNodeRotation(ptr, rendering, rotation); + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + setNodeRotation(ptr, rendering, rotation); + ptr.getClass().insertObject (ptr, model, rotation, physics); - if (useAnim) - MWBase::Environment::get().getMechanicsManager()->add(ptr); + if (useAnim) + MWBase::Environment::get().getMechanicsManager()->add(ptr); - if (ptr.getClass().isActor()) - rendering.addWaterRippleEmitter(ptr); + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); - // Restore effect particles - MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); - } - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) @@ -206,12 +202,11 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; - bool mOnlyStatics; bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); bool operator() (const MWWorld::Ptr& ptr); @@ -219,8 +214,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test) - : mCell (cell), mLoadingListener (loadingListener), mOnlyStatics(onlyStatics), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) + : mCell (cell), mLoadingListener (loadingListener), mTest(test) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -236,7 +231,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && ((mOnlyStatics && ptr.getClass().isStatic()) || !mOnlyStatics)) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) { try { @@ -269,16 +264,6 @@ namespace return std::abs(cellPosition.first) + std::abs(cellPosition.second); } - bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection) - { - for (auto *cell : collection) - { - assert(cell->getCell()->isExterior()); - if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY()) - return true; - } - return false; - } } @@ -330,41 +315,15 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadInactiveCell (CellStore* cell, bool test) - { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - if (!test) - Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); - - ListObjectsVisitor visitor; - - cell->forEach(visitor); - for (const auto& ptr : visitor.mObjects) - mPhysics->remove(ptr); - - if (cell->getCell()->isExterior()) - { - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); - mPhysics->removeHeightField(cellX, cellY); - } - - mInactiveCells.erase(cell); - } - - void Scene::deactivateCell(CellStore* cell, bool test) + void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) { - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - if (mActiveCells.find(cell) == mActiveCells.end()) - return; if (!test) - Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; - cell->forEach(visitor); + (*iter)->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { @@ -375,157 +334,140 @@ namespace MWWorld navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } - mPhysics->remove(ptr, ptr.getClass().isStatic()); + mPhysics->remove(ptr); } - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); + const auto cellX = (*iter)->getCell()->getGridX(); + const auto cellY = (*iter)->getCell()->getGridY(); - if (cell->getCell()->isExterior()) + if ((*iter)->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(DetourNavigator::ObjectId(heightField)); + mPhysics->removeHeightField(cellX, cellY); } - if (cell->getCell()->hasWater()) + if ((*iter)->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) navigator->removePathgrid(*pathgrid); const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getMechanicsManager()->drop (cell); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); - mRendering.removeCell(cell); - MWBase::Environment::get().getWindowManager()->removeCell(cell); + mRendering.removeCell(*iter); + MWBase::Environment::get().getWindowManager()->removeCell(*iter); - MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell); + MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); - MWBase::Environment::get().getSoundManager()->stopSound (cell); - mActiveCells.erase(cell); + MWBase::Environment::get().getSoundManager()->stopSound (*iter); + mActiveCells.erase(*iter); } - void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - mActiveCells.insert(cell); - - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - - const auto world = MWBase::Environment::get().getWorld(); - const auto navigator = world->getNavigator(); - - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); + std::pair result = mActiveCells.insert(cell); - if (!test && cell->getCell()->isExterior()) + if(result.second) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), - heightField->getCollisionObject()->getWorldTransform()); - } - - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) - navigator->addPathgrid(*cell->getCell(), *pathgrid); + if (test) + Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - // register local scripts - // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; - if (respawn) - cell->respawn(); + const auto world = MWBase::Environment::get().getWorld(); + const auto navigator = world->getNavigator(); - insertCell (*cell, loadingListener, false, test); + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); - mRendering.addCell(cell); - if (!test) - { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) + // Load terrain physics first... + if (!test && cell->getCell()->isExterior()) { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); - - if (cell->getCell()->isExterior()) + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + if (data) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { - navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - cell->getWaterLevel(), btTransform::getIdentity()); + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } + + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), + heightField->getCollisionObject()->getWorldTransform()); } - else - mPhysics->disableWater(); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + navigator->addPathgrid(*cell->getCell(), *pathgrid); - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - mRendering.configureAmbient(cell->getCell()); - } + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); - mPreloader->notifyLoaded(cell); - } + if (respawn) + cell->respawn(); - void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) - { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) == mInactiveCells.end()); - mInactiveCells.insert(cell); + // ... then references. This is important for adjustPosition to work correctly. + insertCell (*cell, loadingListener, test); - if (test) - Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); + mRendering.addCell(cell); + if (!test) + { + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) + { + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); - if (!test && cell->getCell()->isExterior()) - { - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; + if (cell->getCell()->isExterior()) + { + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + } + else + { + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + cell->getWaterLevel(), btTransform::getIdentity()); + } + } + else + mPhysics->disableWater(); - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) - { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); - } - else - { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + { + + mRendering.configureAmbient(cell->getCell()); + } } } - insertCell (*cell, loadingListener, true, test); + mPreloader->notifyLoaded(cell); } void Scene::clear() { - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) - { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell (cell); - } + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) + unloadCell (active++); assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -568,24 +510,20 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) { - auto* cell = *iter++; - if (cell->getCell()->isExterior()) - { - const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); - const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); - if (dx > mHalfGridSize || dy > mHalfGridSize) - deactivateCell(cell); - - if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) - unloadInactiveCell(cell); - } - else + if ((*active)->getCell()->isExterior()) { - deactivateCell(cell); - unloadInactiveCell(cell); + if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && + std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) + { + // keep cells within the new grid + ++active; + continue; + } } + unloadCell (active++); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -597,24 +535,32 @@ namespace MWWorld mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; - const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector> + std::vector> cellsPositionsToLoad; + // get the number of refs to load + for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) { - std::vector> cellsPositionsToLoad; - for (int x = playerCellX - range; x <= playerCellX + range; ++x) + for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) { - for (int y = playerCellY - range; y <= playerCellY + range; ++y) + CellStoreCollection::iterator iter = mActiveCells.begin(); + + while (iter!=mActiveCells.end()) { - if (!isCellInCollection(x, y, collection)) - { - refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); - cellsPositionsToLoad.emplace_back(x, y); - } + assert ((*iter)->getCell()->isExterior()); + + if (x==(*iter)->getCell()->getGridX() && + y==(*iter)->getCell()->getGridY()) + break; + + ++iter; + } + + if (iter==mActiveCells.end()) + { + refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + cellsPositionsToLoad.emplace_back(x, y); } } - return cellsPositionsToLoad; - }; - auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); - auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); + } Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -638,26 +584,30 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); - std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), - [&] (const std::pair& lhs, const std::pair& rhs) { - return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); - }); - // Load cells - for (const auto& [x,y] : cellsPositionsToLoadInactive) + for (const auto& cellPosition : cellsPositionsToLoad) { - if (!isCellInCollection(x, y, mInactiveCells)) + const auto x = cellPosition.first; + const auto y = cellPosition.second; + + CellStoreCollection::iterator iter = mActiveCells.begin(); + + while (iter != mActiveCells.end()) { - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadInactiveCell (cell, loadingListener); + assert ((*iter)->getCell()->isExterior()); + + if (x == (*iter)->getCell()->getGridX() && + y == (*iter)->getCell()->getGridY()) + break; + + ++iter; } - } - for (const auto& [x,y] : cellsPositionsToLoad) - { - if (!isCellInCollection(x, y, mActiveCells)) + + if (iter == mActiveCells.end()) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - activateCell (cell, loadingListener, changeEvent); + + loadCell (cell, loadingListener, changeEvent); } } @@ -690,8 +640,7 @@ namespace MWWorld CellStoreCollection::iterator iter = mActiveCells.begin(); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadCell (cell, loadingListener, false, true); iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -699,8 +648,7 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + unloadCell(iter, true); break; } @@ -738,8 +686,7 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadCell (cell, loadingListener, false, true); CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -748,8 +695,7 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + unloadCell(iter, true); break; } @@ -872,21 +818,15 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) - { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell(cell); - } - assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) + unloadCell (active++); loadingListener->setProgressRange(cell->count()); // Load cell. mPagedRefs.clear(); - loadInactiveCell (cell, loadingListener); - activateCell (cell, loadingListener, changeEvent); + loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -934,26 +874,23 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) { - InsertVisitor insertVisitor (cell, *loadingListener, onlyStatics, test); + InsertVisitor insertVisitor (cell, *loadingListener, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyStatics); }); - if (!onlyStatics) - { - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); - } + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + PositionVisitor posVisitor; + cell.forEach (posVisitor); } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); + addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 33c7b78d09..a70d3ccdd5 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -65,13 +65,13 @@ namespace MWWorld class Scene { public: - using CellStoreCollection = std::set; + + typedef std::set CellStoreCollection; private: CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; - CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -92,7 +92,7 @@ namespace MWWorld std::set mPagedRefs; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test = false); + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -108,11 +108,6 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; - void unloadInactiveCell (CellStore* cell, bool test = false); - void deactivateCell (CellStore* cell, bool test = false); - void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); - public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, @@ -124,6 +119,10 @@ namespace MWWorld void preloadTerrain(const osg::Vec3f& pos, bool sync=false); void reloadTerrain(); + void unloadCell (CellStoreCollection::iterator iter, bool test = false); + + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); + void playerMoved (const osg::Vec3f& pos); void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); From 7b727e4d70df35560fe08da8a0ba839e8d6790de Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Jan 2021 16:51:13 +0400 Subject: [PATCH 0303/2859] Revert "Remove physics dependency on basenode" This reverts commit 165c7314928dc281a364fa1a0143c45fd6d2adfd. --- apps/openmw/mwclass/activator.cpp | 4 +-- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/actor.cpp | 6 ++++- apps/openmw/mwclass/actor.hpp | 10 +++---- apps/openmw/mwclass/apparatus.cpp | 5 ++++ apps/openmw/mwclass/apparatus.hpp | 2 ++ apps/openmw/mwclass/armor.cpp | 5 ++++ apps/openmw/mwclass/armor.hpp | 2 ++ apps/openmw/mwclass/bodypart.cpp | 4 +++ apps/openmw/mwclass/bodypart.hpp | 2 ++ apps/openmw/mwclass/book.cpp | 5 ++++ apps/openmw/mwclass/book.hpp | 2 ++ apps/openmw/mwclass/clothing.cpp | 5 ++++ apps/openmw/mwclass/clothing.hpp | 2 ++ apps/openmw/mwclass/container.cpp | 4 +-- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/door.cpp | 4 +-- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 5 ++++ apps/openmw/mwclass/ingredient.hpp | 2 ++ apps/openmw/mwclass/light.cpp | 4 +-- apps/openmw/mwclass/light.hpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 5 ++++ apps/openmw/mwclass/lockpick.hpp | 2 ++ apps/openmw/mwclass/misc.cpp | 5 ++++ apps/openmw/mwclass/misc.hpp | 2 ++ apps/openmw/mwclass/potion.cpp | 5 ++++ apps/openmw/mwclass/potion.hpp | 2 ++ apps/openmw/mwclass/probe.cpp | 5 ++++ apps/openmw/mwclass/probe.hpp | 2 ++ apps/openmw/mwclass/repair.cpp | 5 ++++ apps/openmw/mwclass/repair.hpp | 2 ++ apps/openmw/mwclass/static.cpp | 4 +-- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwclass/weapon.cpp | 5 ++++ apps/openmw/mwclass/weapon.hpp | 2 ++ apps/openmw/mwphysics/actor.cpp | 6 ++--- apps/openmw/mwphysics/actor.hpp | 2 +- apps/openmw/mwphysics/object.cpp | 11 +++----- apps/openmw/mwphysics/object.hpp | 4 +-- apps/openmw/mwphysics/physicssystem.cpp | 10 +++---- apps/openmw/mwphysics/physicssystem.hpp | 4 +-- apps/openmw/mwworld/class.cpp | 6 ++++- apps/openmw/mwworld/class.hpp | 13 ++++----- apps/openmw/mwworld/scene.cpp | 36 ++++++++++++------------- apps/openmw/mwworld/worldimp.cpp | 2 +- 46 files changed, 155 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 716e548e1d..c54b1c3691 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,10 +38,10 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index c7b65ef679..10ace6f74d 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,7 +17,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 1789f1b19a..33aeb26bb0 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -17,12 +17,16 @@ namespace MWClass { + Actor::Actor() {} + + Actor::~Actor() {} + void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 98998b4eb2..3d509b2768 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -15,16 +15,16 @@ namespace MWClass { protected: - Actor() = default; + Actor(); public: - ~Actor() override = default; + virtual ~Actor(); void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; @@ -46,8 +46,8 @@ namespace MWClass float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; // not implemented - Actor(const Actor&) = delete; - Actor& operator= (const Actor&) = delete; + Actor(const Actor&); + Actor& operator= (const Actor&); }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e09e4804ce..518695fabf 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -26,6 +26,11 @@ namespace MWClass } } + void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 828abef25e..8087c57ba3 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -17,6 +17,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 817adbc1f5..3f9bfb859f 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -34,6 +34,11 @@ namespace MWClass } } + void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index f64f138a29..4f04e0824b 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -16,6 +16,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 7fe798e27d..0315d3ddb0 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -22,6 +22,10 @@ namespace MWClass } } + void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const + { + } + std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 0e372b884a..13d9141386 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -15,6 +15,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 51b9e39d7a..4ea71e3ac2 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -31,6 +31,11 @@ namespace MWClass } } + void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index f3d34c5168..c58e68ad87 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 400cd97e41..6d7960aac2 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -29,6 +29,11 @@ namespace MWClass } } + void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 3d5c162aa4..a87e0cbe00 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 3023466f08..28305c394e 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,10 +111,10 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 74d9ac0da2..2dc0c06cae 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -44,7 +44,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 983953c20d..ba51d9c2bc 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -62,10 +62,10 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); + physics.addObject(ptr, model, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 6d40a840b1..6c2fa26b80 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,7 +18,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 20f9576dff..a007ad115f 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 2aa831f868..5219cf39ce 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index d4d196bc4b..3bdf10f475 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,7 +33,7 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -41,7 +41,7 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 1b1794d4a7..e37dddc250 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 985b087711..9b8abc8f23 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index d4b265e397..fabae33435 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index facab9d51c..8d3cda6fe5 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -37,6 +37,11 @@ namespace MWClass } } + void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 18788c7ed8..9bff85ca56 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 56d9dff279..4af97e6345 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -30,6 +30,11 @@ namespace MWClass } } + void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75b962164b..75d923f0ba 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 51273337a6..dba4e8c063 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index ef9273a379..a0a41dcfb6 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index f1b88e422b..8907c8212e 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -25,6 +25,11 @@ namespace MWClass } } + void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index c403449e18..b9791e9cf4 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 108c4eaa20..5551b3d731 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,10 +23,10 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index f856e9fd94..6bc783dad0 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 6246c8fb09..0d6a27cf60 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -34,6 +34,11 @@ namespace MWClass } } + void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index db17e6b70f..f1824b7d14 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -15,6 +15,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 06600fd6ac..3b52ee934f 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -67,7 +67,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); if(!mRotationallyInvariant) - setRotation(mPtr.getRefData().getBaseNode()->getAttitude()); + updateRotation(); updatePosition(); addCollisionMask(getCollisionMask()); @@ -197,10 +197,10 @@ osg::Vec3f Actor::getPreviousPosition() const return mPreviousPosition; } -void Actor::setRotation(osg::Quat quat) +void Actor::updateRotation () { std::scoped_lock lock(mPositionMutex); - mRotation = quat; + mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); } bool Actor::isRotationallyInvariant() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 4039f44811..9d129f2ba6 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -49,7 +49,7 @@ namespace MWPhysics void enableCollisionBody(bool collision); void updateScale(); - void setRotation(osg::Quat quat); + void updateRotation(); /** * Return true if the collision shape looks the same no matter how its Z rotated. diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 0a7b9540c5..e3615989dd 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,7 +14,7 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) @@ -27,7 +27,7 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(rotation); + setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); @@ -51,10 +51,10 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(osg::Quat quat) + void Object::setRotation(const btQuaternion& quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(Misc::Convert::toBullet(quat)); + mLocalTransform.setRotation(quat); mTransformUpdatePending = true; } @@ -116,9 +116,6 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - if (mPtr.getRefData().getBaseNode() == nullptr) - return true; - assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index c2273831e5..cae8771809 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,12 +26,12 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(osg::Quat quat); + void setRotation(const btQuaternion& quat); void setOrigin(const btVector3& vec); void commitPositionChange(); btCollisionObject* getCollisionObject(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 68cec48bcb..ed0e0c915d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -456,13 +456,13 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) @@ -621,12 +621,12 @@ namespace MWPhysics mTaskScheduler->updateSingleAabb(foundProjectile->second); } - void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(rotate); + found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -635,7 +635,7 @@ namespace MWPhysics { if (!foundActor->second->isRotationallyInvariant()) { - foundActor->second->setRotation(rotate); + foundActor->second->updateRotation(); mTaskScheduler->updateSingleAabb(foundActor->second); } return; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index c61b368f8e..6f901067a2 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -121,7 +121,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); @@ -141,7 +141,7 @@ namespace MWPhysics void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); - void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); + void updateRotation (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index becf912eaa..950c8a6d49 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -25,12 +25,16 @@ namespace MWWorld { std::map > Class::sClasses; + Class::Class() {} + + Class::~Class() {} + void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const { } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 39fb6fe4c5..1b3d4029e4 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,7 +6,6 @@ #include #include -#include #include #include "ptr.hpp" @@ -58,9 +57,13 @@ namespace MWWorld std::string mTypeName; + // not implemented + Class (const Class&); + Class& operator= (const Class&); + protected: - Class() = default; + Class(); std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items @@ -69,16 +72,14 @@ namespace MWWorld public: - virtual ~Class() = default; - Class (const Class&) = delete; - Class& operator= (const Class&) = delete; + virtual ~Class(); const std::string& getTypeName() const { return mTypeName; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual std::string getName (const ConstPtr& ptr) const = 0; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 313e9a1527..427ee3aa8b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -75,20 +75,18 @@ namespace * osg::Quat(xr, osg::Vec3(-1, 0, 0)); } - osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order) { - const auto pos = ptr.getRefData().getPosition(); - - const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) - : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos)); - - return rot; - } + if (!ptr.getRefData().getBaseNode()) + return; - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation) - { - if (ptr.getRefData().getBaseNode()) - rendering.rotateObject(ptr, rotation); + rendering.rotateObject(ptr, + ptr.getClass().isActor() + ? makeActorOsgQuat(ptr.getRefData().getPosition()) + : (order == RotationOrder::inverse + ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) + : makeObjectOsgQuat(ptr.getRefData().getPosition())) + ); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) @@ -119,10 +117,11 @@ namespace const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode + setNodeRotation(ptr, rendering, RotationOrder::direct); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - setNodeRotation(ptr, rendering, rotation); - ptr.getClass().insertObject (ptr, model, rotation, physics); + ptr.getClass().insertObject (ptr, model, physics); if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); @@ -277,7 +276,7 @@ namespace MWWorld { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); - setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); + setNodeRotation(ptr, mRendering, RotationOrder::direct); reloadTerrain(); } } @@ -293,9 +292,8 @@ namespace MWWorld void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - const auto rot = makeNodeRotation(ptr, order); - setNodeRotation(ptr, mRendering, rot); - mPhysics->updateRotation(ptr, rot); + setNodeRotation(ptr, mRendering, order); + mPhysics->updateRotation(ptr); } void Scene::updateObjectScale(const Ptr &ptr) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ef00315cbb..442672d2bf 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1420,7 +1420,7 @@ namespace MWWorld mWorldScene->removeFromPagedRefs(ptr); mRendering->rotateObject(ptr, rotate); - mPhysics->updateRotation(ptr, rotate); + mPhysics->updateRotation(ptr); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); From 642ca02e3501f0d6691642b6d9476af0745d424e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 21:07:24 +0100 Subject: [PATCH 0304/2859] Shorten almost straight paths only if smooth movement is enabled; reduce angle limit for the shortening. --- apps/openmw/mwmechanics/aipackage.cpp | 4 ++-- apps/openmw/mwmechanics/pathfinding.cpp | 21 ++++++++++++--------- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index ce5673909f..da1766f4d9 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -150,7 +150,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& + 1.2 * std::max(halfExtents.x(), halfExtents.y()); const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); - mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE); + static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); + mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { @@ -180,7 +181,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front(); mObstacleCheck.update(actor, destination, duration); - static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); if (smoothMovement) { const float smoothTurnReservedDist = 150; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 276321b81a..cc28b7995e 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -97,12 +97,12 @@ namespace float dotProduct = v1.x() * v3.x() + v1.y() * v3.y(); float crossProduct = v1.x() * v3.y() - v1.y() * v3.x(); - // Check that the angle between v1 and v3 is less or equal than 10 degrees. - static const float cos170 = std::cos(osg::PI / 180 * 170); - bool checkAngle = dotProduct <= cos170 * v1.length() * v3.length(); + // Check that the angle between v1 and v3 is less or equal than 5 degrees. + static const float cos175 = std::cos(osg::PI * (175.0 / 180)); + bool checkAngle = dotProduct <= cos175 * v1.length() * v3.length(); // Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`. - bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length() * 2; + bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length(); return checkAngle && checkDist; } @@ -296,7 +296,7 @@ namespace MWMechanics return getXAngleToDir(dir); } - void PathFinder::update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance) + void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight) { if (mPath.empty()) return; @@ -304,10 +304,13 @@ namespace MWMechanics while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance) mPath.pop_front(); - while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance)) - mPath.erase(mPath.begin() + 1); - if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) - mPath.pop_front(); + if (shortenIfAlmostStraight) + { + while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance)) + mPath.erase(mPath.begin() + 1); + if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) + mPath.pop_front(); + } if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) mPath.pop_front(); diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 5af822fee7..bd81bbfe1f 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -102,7 +102,7 @@ namespace MWMechanics const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); /// Remove front point if exist and within tolerance - void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance); + void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight); bool checkPathCompleted() const { From 9590377f224c49b62eb86ac6a8505ef023b50c9c Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 21:27:22 +0100 Subject: [PATCH 0305/2859] Don't ignore Z in path finding if actor can move by Z. --- apps/openmw/mwmechanics/aipackage.cpp | 5 +++-- apps/openmw/mwmechanics/pathfinding.cpp | 15 ++++++++++++--- apps/openmw/mwmechanics/pathfinding.hpp | 3 ++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index da1766f4d9..99132b711a 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -96,6 +96,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const float distToTarget = distance(position, dest); const bool isDestReached = (distToTarget <= destTolerance); + const bool actorCanMoveByZ = canActorMoveByZAxis(actor); if (!isDestReached && mTimer > AI_REACTION_TIME) { @@ -104,7 +105,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const bool wasShortcutting = mIsShortcutting; bool destInLOS = false; - const bool actorCanMoveByZ = canActorMoveByZAxis(actor); // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. mIsShortcutting = actorCanMoveByZ @@ -151,7 +151,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); - mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, /*shortenIfAlmostStraight=*/smoothMovement); + mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, + /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index cc28b7995e..93ae90547c 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -296,7 +296,8 @@ namespace MWMechanics return getXAngleToDir(dir); } - void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight) + void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, + bool shortenIfAlmostStraight, bool canMoveByZ) { if (mPath.empty()) return; @@ -312,8 +313,16 @@ namespace MWMechanics mPath.pop_front(); } - if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) - mPath.pop_front(); + if (mPath.size() == 1) + { + float distSqr; + if (canMoveByZ) + distSqr = (mPath.front() - position).length2(); + else + distSqr = sqrDistanceIgnoreZ(mPath.front(), position); + if (distSqr < destinationTolerance * destinationTolerance) + mPath.pop_front(); + } } void PathFinder::buildStraightPath(const osg::Vec3f& endPoint) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index bd81bbfe1f..b5c376b8ca 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -102,7 +102,8 @@ namespace MWMechanics const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); /// Remove front point if exist and within tolerance - void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, bool shortenIfAlmostStraight); + void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, + bool shortenIfAlmostStraight, bool canMoveByZ); bool checkPathCompleted() const { From 157b14cdaa07395a8e2cf17c0242d57d908d3ae1 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 22:53:02 +0100 Subject: [PATCH 0306/2859] Fix #5821: NPCs from mods getting removed if mod order was changed --- CHANGELOG.md | 1 + apps/openmw/mwworld/cellstore.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f347fb46eb..aa74164d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust + Bug #5821: NPCs from mods getting removed if mod order was changed Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3f98684ae7..cc614133aa 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -921,6 +921,13 @@ namespace MWWorld refnum.load(reader, true, "MVRF"); movedTo.load(reader); + if (refnum.hasContentFile()) + { + auto iter = contentFileMap.find(refnum.mContentFile); + if (iter != contentFileMap.end()) + refnum.mContentFile = iter->second; + } + // Search for the reference. It might no longer exist if its content file was removed. Ptr movedRef = searchViaRefNum(refnum); if (movedRef.isEmpty()) From f87c45c92a6cfba68d431398a1ceab57976b74ff Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 30 Jan 2021 16:03:02 +0200 Subject: [PATCH 0307/2859] Get collision box extents and center from btBvhTriangleMeshShape --- components/resource/bulletshapemanager.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index bcadf51c4e..d1da9090dd 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -86,7 +86,17 @@ public: return osg::ref_ptr(); osg::ref_ptr shape (new BulletShape); - shape->mCollisionShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btBvhTriangleMeshShape* triangleMeshShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); + btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); + shape->mCollisionBox.extents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; + shape->mCollisionBox.extents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; + shape->mCollisionBox.extents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; + shape->mCollisionBox.center = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, + (aabbMax[1] + aabbMin[1]) / 2.0f, + (aabbMax[2] + aabbMin[2]) / 2.0f ); + shape->mCollisionShape = triangleMeshShape; + return shape; } From 7edaa50195baad8ac126ca116b12049d0fe29e5d Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Sun, 31 Jan 2021 18:02:05 +0100 Subject: [PATCH 0308/2859] another approach --- components/sceneutil/mwshadowtechnique.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 294780cfd7..7476bc2190 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -997,9 +997,9 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) } // 1. Traverse main scene graph - cv.pushStateSet( _shadowRecievingPlaceholderStateSet.get() ); - - osg::ref_ptr decoratorStateGraph = cv.getCurrentStateGraph(); + auto* shadowReceiverStateSet = vdd->getStateSet(cv.getTraversalNumber()); + shadowReceiverStateSet->clear(); + cv.pushStateSet(shadowReceiverStateSet); cullShadowReceivingScene(&cv); @@ -1426,7 +1426,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) if (numValidShadows>0) { - decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber())); + selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); } // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()< Date: Mon, 1 Feb 2021 18:34:10 +0000 Subject: [PATCH 0309/2859] Fix the regression involving Cure --- apps/openmw/mwmechanics/actors.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c9fcf82804..cb3570d21f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -958,22 +958,22 @@ namespace MWMechanics if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze); } - else if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) { creatureStats.getSpells().purgeCommonDisease(); } - else if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) { creatureStats.getSpells().purgeBlightDisease(); } - else if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) { creatureStats.getActiveSpells().purgeCorprusDisease(); creatureStats.getSpells().purgeCorprusDisease(); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true); } - else if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) + if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) { creatureStats.getSpells().purgeCurses(); } From 7d3f2bc113aed491a004abab576e7871788eb400 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 17:33:40 +0200 Subject: [PATCH 0310/2859] Convert underscores in bone names to whitespaces --- components/sceneutil/visitor.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 00d18ffcb8..169e07adae 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -60,7 +60,19 @@ namespace SceneUtil void NodeMapVisitor::apply(osg::MatrixTransform& trans) { // Take transformation for first found node in file - const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName()); + std::string originalNodeName = Misc::StringUtils::lowerCase(trans.getName()); + + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) + std::string underscore = "_"; + std::size_t foundUnderscore = originalNodeName.find(underscore); + + if (foundUnderscore != std::string::npos) + { + std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); + //originalNodeName.replace(foundUnderscore.begin(), foundPageNumberEnd - foundPageNumber - 6, " ") ); + } + const std::string nodeName = originalNodeName; + mMap.emplace(nodeName, &trans); traverse(trans); From 2162b97fefd91678154d9a8b1c13723e64c266ab Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 17:34:02 +0200 Subject: [PATCH 0311/2859] Handle case in osgAnimation bone names --- components/resource/keyframemanager.cpp | 3 ++- components/sceneutil/osgacontroller.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index d739392e8b..b6417597d5 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "animation.hpp" #include "objectcache.hpp" @@ -21,7 +22,7 @@ namespace Resource void RetrieveAnimationsVisitor::apply(osg::Node& node) { - if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("bip01")) + if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && Misc::StringUtils::lowerCase(node.getName()) == std::string("bip01")) { osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 37aa525af1..8f447621f6 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -83,7 +84,7 @@ namespace SceneUtil { osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(cb); if (umt) - if (node.getName() != "bip01") link(umt); + if (Misc::StringUtils::lowerCase(node.getName()) != "bip01") link(umt); cb = cb->getNestedCallback(); } From 5b88d16a50bc3c5def4aa2bcb22501e7a0d2b0a0 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 21:09:50 +0200 Subject: [PATCH 0312/2859] Clean-up --- components/resource/scenemanager.cpp | 2 -- components/sceneutil/visitor.cpp | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 92de2e0dd5..71f11e382e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -26,8 +26,6 @@ #include #include -#include - #include #include diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 169e07adae..e632e7ce91 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -67,10 +67,8 @@ namespace SceneUtil std::size_t foundUnderscore = originalNodeName.find(underscore); if (foundUnderscore != std::string::npos) - { std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); - //originalNodeName.replace(foundUnderscore.begin(), foundPageNumberEnd - foundPageNumber - 6, " ") ); - } + const std::string nodeName = originalNodeName; mMap.emplace(nodeName, &trans); From 2c67f34c6d8ca8ba794e3b7bd810e7fdc07e8773 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 21:18:14 +0200 Subject: [PATCH 0313/2859] Update base_anim file settings --- files/settings-default.cfg | 59 ++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index db947f8ed0..8e365690d4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -955,33 +955,62 @@ defer aabb update = true # Loading arbitrary meshes is not advised and may cause instability. load unsupported nif files = false -[Groundcover] - -# enable separate groundcover handling -enabled = false - -# A groundcover density (0.0 <= value <= 1.0) -# 1.0 means 100% density -density = 1.0 - -# A maximum distance in cells on which groundcover is rendered. -distance = 1 - -# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) -min chunk size = 0.5 - +# 3rd person base animation model that looks also for the corresponding kf-file xbaseanim = meshes\xbase_anim.nif + +# 3rd person base model with textkeys-data baseanim = meshes\base_anim.nif + +# 1st person base animation model that looks also for corresponding kf-file xbaseanim1st = meshes\xbase_anim.1st.nif + +# 3rd person beast race base model with textkeys-data baseanimkna = meshes\base_animkna.nif + +# 1st person beast race base animation model baseanimkna1st = meshes\base_animkna.1st.nif + +# 3rd person female base animation model xbaseanimfemale = meshes/xbase_anim_female.nif + +# 3rd person female base model with textkeys-data baseanimfemale = meshes/base_anim_female.nif + +# 1st person female base model with textkeys-data baseanimfemale1st = meshes\base_anim_female.1st.nif + +# 3rd person werewolf skin wolfskin = meshes\wolf\skin.nif + +# 1st person werewolf skin wolfskin1st = meshes\wolf\skin.1st.nif + +# Argonian smimkna xargonianswimkna = meshes/xargonian_swimkna.nif + +# File to load xbaseanim 3rd person animations xbaseanimkf = meshes\xbase_anim.kf + +# File to load xbaseanim 3rd person animations xbaseanim1stkf = meshes\xbase_anim.1st.kf + +# File to load xbaseanim animations from xbaseanimfemalekf = meshes/xbase_anim_female.kf + +# File to load xargonianswimkna animations from xargonianswimknakf = meshes/xargonian_swimkna.kf + +[Groundcover] + +# enable separate groundcover handling +enabled = false + +# A groundcover density (0.0 <= value <= 1.0) +# 1.0 means 100% density +density = 1.0 + +# A maximum distance in cells on which groundcover is rendered. +distance = 1 + +# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) +min chunk size = 0.5 From 1221889cf7c099ae0b00e7752b24448a1deac9e0 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 21:41:17 +0200 Subject: [PATCH 0314/2859] Limit conversion of underscores to nodes origating from osgAnimation library --- components/sceneutil/visitor.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index e632e7ce91..60f99b2fef 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -62,12 +62,15 @@ namespace SceneUtil // Take transformation for first found node in file std::string originalNodeName = Misc::StringUtils::lowerCase(trans.getName()); - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) - std::string underscore = "_"; - std::size_t foundUnderscore = originalNodeName.find(underscore); + if (trans.libraryName() == std::string("osgAnimation")) + { + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) + std::string underscore = "_"; + std::size_t foundUnderscore = originalNodeName.find(underscore); - if (foundUnderscore != std::string::npos) - std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); + if (foundUnderscore != std::string::npos) + std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); + } const std::string nodeName = originalNodeName; From 382324173b3587fcf46ca00ce47cf2b71e75d9ac Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 21:42:25 +0200 Subject: [PATCH 0315/2859] \ to / --- files/settings-default.cfg | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 8e365690d4..67d944d195 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -956,19 +956,19 @@ defer aabb update = true load unsupported nif files = false # 3rd person base animation model that looks also for the corresponding kf-file -xbaseanim = meshes\xbase_anim.nif +xbaseanim = meshes/xbase_anim.nif # 3rd person base model with textkeys-data -baseanim = meshes\base_anim.nif +baseanim = meshes/base_anim.nif # 1st person base animation model that looks also for corresponding kf-file -xbaseanim1st = meshes\xbase_anim.1st.nif +xbaseanim1st = meshes/xbase_anim.1st.nif # 3rd person beast race base model with textkeys-data -baseanimkna = meshes\base_animkna.nif +baseanimkna = meshes/base_animkna.nif # 1st person beast race base animation model -baseanimkna1st = meshes\base_animkna.1st.nif +baseanimkna1st = meshes/base_animkna.1st.nif # 3rd person female base animation model xbaseanimfemale = meshes/xbase_anim_female.nif @@ -977,22 +977,22 @@ xbaseanimfemale = meshes/xbase_anim_female.nif baseanimfemale = meshes/base_anim_female.nif # 1st person female base model with textkeys-data -baseanimfemale1st = meshes\base_anim_female.1st.nif +baseanimfemale1st = meshes/base_anim_female.1st.nif # 3rd person werewolf skin -wolfskin = meshes\wolf\skin.nif +wolfskin = meshes/wolf/skin.nif # 1st person werewolf skin -wolfskin1st = meshes\wolf\skin.1st.nif +wolfskin1st = meshes/wolf/skin.1st.nif # Argonian smimkna xargonianswimkna = meshes/xargonian_swimkna.nif # File to load xbaseanim 3rd person animations -xbaseanimkf = meshes\xbase_anim.kf +xbaseanimkf = meshes/xbase_anim.kf # File to load xbaseanim 3rd person animations -xbaseanim1stkf = meshes\xbase_anim.1st.kf +xbaseanim1stkf = meshes/xbase_anim.1st.kf # File to load xbaseanim animations from xbaseanimfemalekf = meshes/xbase_anim_female.kf From 6c0c28c2eb8d41ee26dd562f332ce72c6a2c76c3 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 30 Jan 2021 16:03:02 +0200 Subject: [PATCH 0316/2859] Get collision box extents and center from btBvhTriangleMeshShape --- components/resource/bulletshapemanager.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index bcadf51c4e..d1da9090dd 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -86,7 +86,17 @@ public: return osg::ref_ptr(); osg::ref_ptr shape (new BulletShape); - shape->mCollisionShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btBvhTriangleMeshShape* triangleMeshShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); + btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); + shape->mCollisionBox.extents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; + shape->mCollisionBox.extents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; + shape->mCollisionBox.extents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; + shape->mCollisionBox.center = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, + (aabbMax[1] + aabbMin[1]) / 2.0f, + (aabbMax[2] + aabbMin[2]) / 2.0f ); + shape->mCollisionShape = triangleMeshShape; + return shape; } From b28d8251aaa7fbf0e5ccab4fede1c34e1e62ad00 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 30 Jan 2021 15:27:47 +0200 Subject: [PATCH 0317/2859] Clone animation tracks --- components/sceneutil/osgacontroller.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 8f447621f6..766d0cb79f 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -103,10 +103,14 @@ namespace SceneUtil } OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) - , mMergedAnimationTracks(copy.mMergedAnimationTracks) , mEmulatedAnimations(copy.mEmulatedAnimations) { mLinker = nullptr; + for (const auto& mergedAnimationTrack : copy.mMergedAnimationTracks) + { + Resource::Animation* copiedAnimationTrack = dynamic_cast(mergedAnimationTrack.get()->clone(copyop)); + mMergedAnimationTracks.emplace_back(copiedAnimationTrack); + } } osg::Vec3f OsgAnimationController::getTranslation(float time) const From bae27e81993813d7de75fe9eb466fa7c50a9a6ad Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 2 Feb 2021 18:00:14 +0200 Subject: [PATCH 0318/2859] dynamic_cast to static_cast --- components/sceneutil/osgacontroller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 766d0cb79f..b2d819117f 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -108,7 +108,7 @@ namespace SceneUtil mLinker = nullptr; for (const auto& mergedAnimationTrack : copy.mMergedAnimationTracks) { - Resource::Animation* copiedAnimationTrack = dynamic_cast(mergedAnimationTrack.get()->clone(copyop)); + Resource::Animation* copiedAnimationTrack = static_cast(mergedAnimationTrack.get()->clone(copyop)); mMergedAnimationTracks.emplace_back(copiedAnimationTrack); } } From 384112746c3e7c7999853afb77076c23be4e2de1 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 3 Feb 2021 14:25:09 +0200 Subject: [PATCH 0319/2859] Add option for custom collision node with non-nif files --- apps/openmw/mwworld/scene.cpp | 4 +-- components/resource/bulletshapemanager.cpp | 32 +++++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b387..57da414ddb 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -115,6 +115,8 @@ namespace std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + if (!physics.getObject(ptr)) + ptr.getClass().insertObject (ptr, model, rotation, physics); if (!onlyPhysics) { bool useAnim = ptr.getClass().useAnim(); @@ -134,8 +136,6 @@ namespace // Restore effect particles MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index d1da9090dd..aceb9bb35c 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -145,11 +146,34 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & osg::ref_ptr constNode (mSceneManager->getTemplate(normalized)); osg::ref_ptr node (const_cast(constNode.get())); // const-trickery required because there is no const version of NodeVisitor - NodeToShapeVisitor visitor; - node->accept(visitor); - shape = visitor.getShape(); + + // Check first if there's a custom collision node + SceneUtil::FindByNameVisitor nameFinder("Collision"); + node->accept(nameFinder); + if (nameFinder.mFoundNode) + { + NodeToShapeVisitor visitor; + nameFinder.mFoundNode->accept(visitor); + shape = visitor.getShape(); + for (unsigned int i = 0; i < nameFinder.mFoundNode->getNumParents(); ++i) + { + nameFinder.mFoundNode->getParent(i)->removeChild(nameFinder.mFoundNode); + } + + /* // CleanObjectRootVisitor is an alternative method + SceneUtil::CleanObjectRootVisitor cleanerVisitor; + nameFinder.mFoundNode->accept(cleanerVisitor);*/ + } + + // Generate a collision shape from the mesh if (!shape) - return osg::ref_ptr(); + { + NodeToShapeVisitor visitor; + node->accept(visitor); + shape = visitor.getShape(); + if (!shape) + return osg::ref_ptr(); + } } mCache->addEntryToObjectCache(normalized, shape); From 5c32460153497359ef3815c6b79e4437c3667480 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 3 Feb 2021 14:25:50 +0200 Subject: [PATCH 0320/2859] Add underscore-separated node-names to reserved-list --- components/resource/scenemanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 71f11e382e..70320760f5 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -390,7 +390,8 @@ namespace Resource { const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", - "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"}; + "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera", "Collision", "Right_Wrist", "Left_Wrist", + "Shield_Bone", "Right_Forearm", "Left_Forearm", "Right_Upper_Arm", "Left_Clavicle", "Weapon_Bone", "Root_Bone"}; reservedNames = std::vector(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); From 0639f8b7c6d5536f8a0ab03a058ab16bfb54c8bb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 3 Feb 2021 18:45:22 +0000 Subject: [PATCH 0321/2859] Make the dummy texture for the character preview shadow-friendly --- apps/openmw/mwrender/characterpreview.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 14735050ce..262b03229f 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -184,6 +184,9 @@ namespace MWRender osg::ref_ptr dummyTexture = new osg::Texture2D(); dummyTexture->setInternalFormat(GL_RED); dummyTexture->setTextureSize(1, 1); + // This might clash with a shadow map, so make sure it doesn't cast shadows + dummyTexture->setShadowComparison(true); + dummyTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("noAlpha", true)); From e91d1a2b42aa3b28e5f86acc83201702c08cc2b7 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 3 Feb 2021 21:16:26 +0200 Subject: [PATCH 0322/2859] Fix earlier broken commit --- apps/openmw/mwworld/scene.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 57da414ddb..fcf2c4b387 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -115,8 +115,6 @@ namespace std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); if (!onlyPhysics) { bool useAnim = ptr.getClass().useAnim(); @@ -136,6 +134,8 @@ namespace // Restore effect particles MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } + if (!physics.getObject(ptr)) + ptr.getClass().insertObject (ptr, model, rotation, physics); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) From 45fde84f4ff005d476a05b628a27329ce01d976a Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 3 Feb 2021 21:16:54 +0200 Subject: [PATCH 0323/2859] Use nodemasks and visitors for detecting custom collision shapes --- components/resource/bulletshapemanager.cpp | 13 +++++-------- components/resource/scenemanager.cpp | 9 +++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index aceb9bb35c..ad37eda0dd 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -148,21 +148,18 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & osg::ref_ptr node (const_cast(constNode.get())); // const-trickery required because there is no const version of NodeVisitor // Check first if there's a custom collision node + unsigned int visitAllNodesMask = 0xffffffff; SceneUtil::FindByNameVisitor nameFinder("Collision"); + nameFinder.setTraversalMask(visitAllNodesMask); + nameFinder.setNodeMaskOverride(visitAllNodesMask); node->accept(nameFinder); if (nameFinder.mFoundNode) { NodeToShapeVisitor visitor; + visitor.setTraversalMask(visitAllNodesMask); + visitor.setNodeMaskOverride(visitAllNodesMask); nameFinder.mFoundNode->accept(visitor); shape = visitor.getShape(); - for (unsigned int i = 0; i < nameFinder.mFoundNode->getNumParents(); ++i) - { - nameFinder.mFoundNode->getParent(i)->removeChild(nameFinder.mFoundNode); - } - - /* // CleanObjectRootVisitor is an alternative method - SceneUtil::CleanObjectRootVisitor cleanerVisitor; - nameFinder.mFoundNode->accept(cleanerVisitor);*/ } // Generate a collision shape from the mesh diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 70320760f5..19cc96433b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -373,6 +374,14 @@ namespace Resource errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl; throw std::runtime_error(errormsg.str()); } + + // Recognize and hide collision node + unsigned int hiddenNodeMask = 0; + SceneUtil::FindByNameVisitor nameFinder("Collision"); + result.getNode()->accept(nameFinder); + if (nameFinder.mFoundNode) + nameFinder.mFoundNode->setNodeMask(hiddenNodeMask); + return result.getNode(); } } From 88ca4a1db6e10eeeecd0d9f97fbe61dd5b8a9411 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:18:25 +0100 Subject: [PATCH 0324/2859] Count navmesh cache hit rate --- components/detournavigator/navmeshtilescache.cpp | 12 +++++++++++- components/detournavigator/navmeshtilescache.hpp | 2 ++ components/resource/stats.cpp | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index cff93ac0e5..1b90ea89eb 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -53,13 +53,16 @@ namespace DetourNavigator } NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) - : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0) {} + : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), + mHitCount(0), mGetCount(0){} NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections) { const std::lock_guard lock(mMutex); + ++mGetCount; + const auto agentValues = mValues.find(agentHalfExtents); if (agentValues == mValues.end()) return Value(); @@ -74,6 +77,8 @@ namespace DetourNavigator acquireItemUnsafe(tile->second); + ++mHitCount; + return Value(*this, tile->second); } @@ -123,17 +128,22 @@ namespace DetourNavigator std::size_t navMeshCacheSize = 0; std::size_t usedNavMeshTiles = 0; std::size_t cachedNavMeshTiles = 0; + std::size_t hitCount = 0; + std::size_t getCount = 0; { const std::lock_guard lock(mMutex); navMeshCacheSize = mUsedNavMeshDataSize; usedNavMeshTiles = mBusyItems.size(); cachedNavMeshTiles = mFreeItems.size(); + hitCount = mHitCount; + getCount = mGetCount; } stats.setAttribute(frameNumber, "NavMesh CacheSize", navMeshCacheSize); stats.setAttribute(frameNumber, "NavMesh UsedTiles", usedNavMeshTiles); stats.setAttribute(frameNumber, "NavMesh CachedTiles", cachedNavMeshTiles); + stats.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(hitCount) / getCount * 100.0); } void NavMeshTilesCache::removeLeastRecentlyUsed() diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 064d9e185b..a6dd1ed9a7 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -194,6 +194,8 @@ namespace DetourNavigator std::size_t mMaxNavMeshDataSize; std::size_t mUsedNavMeshDataSize; std::size_t mFreeNavMeshDataSize; + std::size_t mHitCount; + std::size_t mGetCount; std::list mBusyItems; std::list mFreeItems; std::map> mValues; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 3649af0896..690814f91d 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -394,6 +394,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", + "NavMesh CacheHitRate", "", "Mechanics Actors", "Mechanics Objects", From 489107c5eed4de2ce0cef3a88d1cedf0cb811311 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:44:15 +0100 Subject: [PATCH 0325/2859] Count navmesh cache key once in item size Key is stored only in NavMeshTilesCache::Item, TileMap uses KeyView with a pointer to a vector. --- .../detournavigator/navmeshtilescache.cpp | 22 +++++++++---------- .../detournavigator/navmeshtilescache.cpp | 2 +- .../detournavigator/navmeshtilescache.hpp | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index e8e7820d91..5bc7af6467 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -68,7 +68,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, @@ -81,7 +81,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -97,7 +97,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); @@ -142,7 +142,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -163,7 +163,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -182,7 +182,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -214,7 +214,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -258,7 +258,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); + const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize); NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -277,7 +277,7 @@ namespace const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize1 = cRecastMeshKeySize; const std::size_t navMeshKeySize2 = cRecastMeshWithWaterKeySize; - const std::size_t maxSize = 2 * navMeshDataSize + 2 * navMeshKeySize1 + 2 * navMeshKeySize2; + const std::size_t maxSize = 2 * navMeshDataSize + navMeshKeySize1 + navMeshKeySize2; NavMeshTilesCache cache(maxSize); const std::vector anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -306,7 +306,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; @@ -330,7 +330,7 @@ namespace { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; - const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; + const std::size_t maxSize = navMeshDataSize + navMeshKeySize; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 1b90ea89eb..d9879fcbb6 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -97,7 +97,7 @@ namespace DetourNavigator return Value(); auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); - const auto itemSize = navMeshSize + 2 * navMeshKey.size(); + const auto itemSize = navMeshSize + navMeshKey.size(); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index a6dd1ed9a7..338ead3aad 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -208,7 +208,7 @@ namespace DetourNavigator static std::size_t getSize(const Item& item) { - return static_cast(item.mNavMeshData.mSize) + 2 * item.mNavMeshKey.size(); + return static_cast(item.mNavMeshData.mSize) + item.mNavMeshKey.size(); } }; } From ad1f8c1e8445acdb2f40f7fcee767b587e5df11c Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:14:29 +0100 Subject: [PATCH 0326/2859] Sort water and off mesh connections for recast mesh Inconsisten order of these objects in navmesh cache key leads to cache misses due to key inequality. --- components/bullethelpers/operators.hpp | 15 +++++++++++++++ components/detournavigator/offmeshconnection.hpp | 7 +++++++ .../detournavigator/offmeshconnectionsmanager.hpp | 2 ++ components/detournavigator/recastmesh.hpp | 8 ++++++++ components/detournavigator/recastmeshbuilder.cpp | 1 + 5 files changed, 33 insertions(+) diff --git a/components/bullethelpers/operators.hpp b/components/bullethelpers/operators.hpp index ea88deddf0..dd2ec8017c 100644 --- a/components/bullethelpers/operators.hpp +++ b/components/bullethelpers/operators.hpp @@ -68,4 +68,19 @@ inline std::ostream& operator <<(std::ostream& stream, BroadphaseNativeTypes val } } +inline bool operator <(const btVector3& lhs, const btVector3& rhs) +{ + return std::tie(lhs.x(), lhs.y(), lhs.z()) < std::tie(rhs.x(), rhs.y(), rhs.z()); +} + +inline bool operator <(const btMatrix3x3& lhs, const btMatrix3x3& rhs) +{ + return std::tie(lhs[0], lhs[1], lhs[2]) < std::tie(rhs[0], rhs[1], rhs[2]); +} + +inline bool operator <(const btTransform& lhs, const btTransform& rhs) +{ + return std::tie(lhs.getBasis(), lhs.getOrigin()) < std::tie(rhs.getBasis(), rhs.getOrigin()); +} + #endif diff --git a/components/detournavigator/offmeshconnection.hpp b/components/detournavigator/offmeshconnection.hpp index ca999dbdb9..01bae02732 100644 --- a/components/detournavigator/offmeshconnection.hpp +++ b/components/detournavigator/offmeshconnection.hpp @@ -5,6 +5,8 @@ #include +#include + namespace DetourNavigator { struct OffMeshConnection @@ -13,6 +15,11 @@ namespace DetourNavigator osg::Vec3f mEnd; AreaType mAreaType; }; + + inline bool operator<(const OffMeshConnection& lhs, const OffMeshConnection& rhs) + { + return std::tie(lhs.mStart, lhs.mEnd, lhs.mAreaType) < std::tie(rhs.mStart, rhs.mEnd, rhs.mAreaType); + } } #endif diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index de707f3a86..1ad96e3b97 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -85,6 +85,8 @@ namespace DetourNavigator std::for_each(byId.first, byId.second, [&] (const auto& v) { result.push_back(v.second); }); }); + std::sort(result.begin(), result.end()); + return result; } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index f3259903f3..29f37822ec 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -5,9 +5,12 @@ #include "chunkytrimesh.hpp" #include "bounds.hpp" +#include + #include #include #include +#include #include @@ -87,6 +90,11 @@ namespace DetourNavigator ChunkyTriMesh mChunkyTriMesh; Bounds mBounds; }; + + inline bool operator<(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs) + { + return std::tie(lhs.mCellSize, lhs.mTransform) < std::tie(rhs.mCellSize, rhs.mTransform); + } } #endif diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index ee014b9328..f8456acf0b 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -155,6 +155,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) { optimizeRecastMesh(mIndices, mVertices); + std::sort(mWater.begin(), mWater.end()); return std::make_shared(generation, revision, mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk); } From 3a2cea52714b26c69dfd89672dd1ad6a9886acac Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 00:15:54 +0100 Subject: [PATCH 0327/2859] Use raw recast mesh data and off mesh connections for navmesh key Serialization into a vector of chars produces inconsistent results that leads to reduced cache hit rate. Using a structured object is a more clear solution and allows to remove serialization and nontrivial key compare logic with more straigt forward structured object comparison. --- .../detournavigator/navmeshtilescache.cpp | 123 ++----------- .../detournavigator/navmeshtilescache.hpp | 168 ++++++++++-------- components/detournavigator/recastmesh.hpp | 6 + 3 files changed, 110 insertions(+), 187 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index d9879fcbb6..84c658653a 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -9,52 +9,21 @@ namespace DetourNavigator { namespace { - inline std::vector makeNavMeshKey(const RecastMesh& recastMesh, - const std::vector& offMeshConnections) + inline std::size_t getSize(const RecastMesh& recastMesh, + const std::vector& offMeshConnections) { const std::size_t indicesSize = recastMesh.getIndices().size() * sizeof(int); const std::size_t verticesSize = recastMesh.getVertices().size() * sizeof(float); const std::size_t areaTypesSize = recastMesh.getAreaTypes().size() * sizeof(AreaType); const std::size_t waterSize = recastMesh.getWater().size() * sizeof(RecastMesh::Water); const std::size_t offMeshConnectionsSize = offMeshConnections.size() * sizeof(OffMeshConnection); - - std::vector result(indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize); - unsigned char* dst = result.data(); - - if (indicesSize > 0) - { - std::memcpy(dst, recastMesh.getIndices().data(), indicesSize); - dst += indicesSize; - } - - if (verticesSize > 0) - { - std::memcpy(dst, recastMesh.getVertices().data(), verticesSize); - dst += verticesSize; - } - - if (areaTypesSize > 0) - { - std::memcpy(dst, recastMesh.getAreaTypes().data(), areaTypesSize); - dst += areaTypesSize; - } - - if (waterSize > 0) - { - std::memcpy(dst, recastMesh.getWater().data(), waterSize); - dst += waterSize; - } - - if (offMeshConnectionsSize > 0) - std::memcpy(dst, offMeshConnections.data(), offMeshConnectionsSize); - - return result; + return indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize; } } NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), - mHitCount(0), mGetCount(0){} + mHitCount(0), mGetCount(0) {} NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections) @@ -71,7 +40,7 @@ namespace DetourNavigator if (tileValues == agentValues->second.end()) return Value(); - const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections)); + const auto tile = tileValues->second.mMap.find(NavMeshKeyView(recastMesh, offMeshConnections)); if (tile == tileValues->second.mMap.end()) return Value(); @@ -96,8 +65,11 @@ namespace DetourNavigator if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); - auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); - const auto itemSize = navMeshSize + navMeshKey.size(); + NavMeshKey navMeshKey { + RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()}, + offMeshConnections + }; + const auto itemSize = navMeshSize + getSize(recastMesh, offMeshConnections); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); @@ -105,7 +77,7 @@ namespace DetourNavigator while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) removeLeastRecentlyUsed(); - const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey)); + const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator); if (!emplaced.second) @@ -162,8 +134,8 @@ namespace DetourNavigator if (value == tileValues->second.mMap.end()) return; - mUsedNavMeshDataSize -= getSize(item); - mFreeNavMeshDataSize -= getSize(item); + mUsedNavMeshDataSize -= item.mSize; + mFreeNavMeshDataSize -= item.mSize; tileValues->second.mMap.erase(value); mFreeItems.pop_back(); @@ -184,7 +156,7 @@ namespace DetourNavigator return; mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); - mFreeNavMeshDataSize -= getSize(*iterator); + mFreeNavMeshDataSize -= iterator->mSize; } void NavMeshTilesCache::releaseItem(ItemIterator iterator) @@ -195,71 +167,6 @@ namespace DetourNavigator const std::lock_guard lock(mMutex); mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator); - mFreeNavMeshDataSize += getSize(*iterator); - } - - namespace - { - struct CompareBytes - { - const unsigned char* mRhsIt; - const unsigned char* const mRhsEnd; - - template - int operator ()(const std::vector& lhs) - { - const auto lhsBegin = reinterpret_cast(lhs.data()); - const auto lhsEnd = reinterpret_cast(lhs.data() + lhs.size()); - const auto lhsSize = static_cast(lhsEnd - lhsBegin); - const auto rhsSize = static_cast(mRhsEnd - mRhsIt); - - if (lhsBegin == nullptr || mRhsIt == nullptr) - { - if (lhsSize < rhsSize) - return -1; - else if (lhsSize > rhsSize) - return 1; - else - return 0; - } - - const auto size = std::min(lhsSize, rhsSize); - - if (const auto result = std::memcmp(lhsBegin, mRhsIt, size)) - return result; - - if (lhsSize > rhsSize) - return 1; - - mRhsIt += size; - - return 0; - } - }; - } - - int NavMeshTilesCache::RecastMeshKeyView::compare(const std::vector& other) const - { - CompareBytes compareBytes {other.data(), other.data() + other.size()}; - - if (const auto result = compareBytes(mRecastMesh.get().getIndices())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getVertices())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getAreaTypes())) - return result; - - if (const auto result = compareBytes(mRecastMesh.get().getWater())) - return result; - - if (const auto result = compareBytes(mOffMeshConnections.get())) - return result; - - if (compareBytes.mRhsIt < compareBytes.mRhsEnd) - return -1; - - return 0; + mFreeNavMeshDataSize += iterator->mSize; } } diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 338ead3aad..25f4dc1878 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -27,6 +27,89 @@ namespace DetourNavigator int mSize; }; + struct RecastMeshData + { + std::vector mIndices; + std::vector mVertices; + std::vector mAreaTypes; + std::vector mWater; + }; + + inline bool operator <(const RecastMeshData& lhs, const RecastMeshData& rhs) + { + return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes, lhs.mWater) + < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); + } + + inline bool operator <(const RecastMeshData& lhs, const RecastMesh& rhs) + { + return std::tie(lhs.mIndices, lhs.mVertices, lhs.mAreaTypes, lhs.mWater) + < std::tie(rhs.getIndices(), rhs.getVertices(), rhs.getAreaTypes(), rhs.getWater()); + } + + inline bool operator <(const RecastMesh& lhs, const RecastMeshData& rhs) + { + return std::tie(lhs.getIndices(), lhs.getVertices(), lhs.getAreaTypes(), lhs.getWater()) + < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); + } + + struct NavMeshKey + { + RecastMeshData mRecastMesh; + std::vector mOffMeshConnections; + }; + + inline bool operator <(const NavMeshKey& lhs, const NavMeshKey& rhs) + { + return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections) + < std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections); + } + + struct NavMeshKeyRef + { + std::reference_wrapper mRef; + + explicit NavMeshKeyRef(const NavMeshKey& ref) : mRef(ref) {} + }; + + inline bool operator <(const NavMeshKeyRef& lhs, const NavMeshKeyRef& rhs) + { + return lhs.mRef.get() < rhs.mRef.get(); + } + + struct NavMeshKeyView + { + std::reference_wrapper mRecastMesh; + std::reference_wrapper> mOffMeshConnections; + + NavMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) + : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} + }; + + inline bool operator <(const NavMeshKeyView& lhs, const NavMeshKey& rhs) + { + return std::tie(lhs.mRecastMesh.get(), lhs.mOffMeshConnections.get()) + < std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections); + } + + inline bool operator <(const NavMeshKey& lhs, const NavMeshKeyView& rhs) + { + return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections) + < std::tie(rhs.mRecastMesh.get(), rhs.mOffMeshConnections.get()); + } + + template + inline bool operator <(const NavMeshKeyRef& lhs, const R& rhs) + { + return lhs.mRef.get() < rhs; + } + + template + inline bool operator <(const L& lhs, const NavMeshKeyRef& rhs) + { + return lhs < rhs.mRef.get(); + } + class NavMeshTilesCache { public: @@ -35,14 +118,16 @@ namespace DetourNavigator std::atomic mUseCount; osg::Vec3f mAgentHalfExtents; TilePosition mChangedTile; - std::vector mNavMeshKey; + NavMeshKey mNavMeshKey; NavMeshData mNavMeshData; + std::size_t mSize; - Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::vector&& navMeshKey) + Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, NavMeshKey&& navMeshKey, std::size_t size) : mUseCount(0) , mAgentHalfExtents(agentHalfExtents) , mChangedTile(changedTile) - , mNavMeshKey(std::move(navMeshKey)) + , mNavMeshKey(navMeshKey) + , mSize(size) {} }; @@ -115,79 +200,9 @@ namespace DetourNavigator void reportStats(unsigned int frameNumber, osg::Stats& stats) const; private: - class KeyView - { - public: - KeyView() = default; - - virtual ~KeyView() = default; - - KeyView(const std::vector& value) - : mValue(&value) {} - - const std::vector& getValue() const - { - assert(mValue); - return *mValue; - } - - virtual int compare(const std::vector& other) const - { - assert(mValue); - - const auto valueSize = mValue->size(); - const auto otherSize = other.size(); - - if (const auto result = std::memcmp(mValue->data(), other.data(), std::min(valueSize, otherSize))) - return result; - - if (valueSize < otherSize) - return -1; - - if (valueSize > otherSize) - return 1; - - return 0; - } - - virtual bool isLess(const KeyView& other) const - { - assert(mValue); - return other.compare(*mValue) > 0; - } - - friend bool operator <(const KeyView& lhs, const KeyView& rhs) - { - return lhs.isLess(rhs); - } - - private: - const std::vector* mValue = nullptr; - }; - - class RecastMeshKeyView : public KeyView - { - public: - RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) - : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} - - int compare(const std::vector& other) const override; - - bool isLess(const KeyView& other) const override - { - return compare(other.getValue()) < 0; - } - - virtual ~RecastMeshKeyView() = default; - - private: - std::reference_wrapper mRecastMesh; - std::reference_wrapper> mOffMeshConnections; - }; - struct TileMap { - std::map mMap; + std::map> mMap; }; mutable std::mutex mMutex; @@ -205,11 +220,6 @@ namespace DetourNavigator void acquireItemUnsafe(ItemIterator iterator); void releaseItem(ItemIterator iterator); - - static std::size_t getSize(const Item& item) - { - return static_cast(item.mNavMeshData.mSize) + item.mNavMeshKey.size(); - } }; } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 29f37822ec..746422ac82 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -95,6 +95,12 @@ namespace DetourNavigator { return std::tie(lhs.mCellSize, lhs.mTransform) < std::tie(rhs.mCellSize, rhs.mTransform); } + + inline bool operator <(const RecastMesh& lhs, const RecastMesh& rhs) + { + return std::tie(lhs.getIndices(), lhs.getVertices(), lhs.getAreaTypes(), lhs.getWater()) + < std::tie(rhs.getIndices(), rhs.getVertices(), rhs.getAreaTypes(), rhs.getWater()); + } } #endif From 68fe6b91145350c12487332cc26483f032f2ec64 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Feb 2021 01:12:52 +0100 Subject: [PATCH 0328/2859] Use only item size to check whether item fits cache Item size has to be counted anyway and there is no reason to check only navmesh data first. --- components/detournavigator/navmeshtilescache.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 84c658653a..b6048da589 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -55,27 +55,20 @@ namespace DetourNavigator const RecastMesh& recastMesh, const std::vector& offMeshConnections, NavMeshData&& value) { - const auto navMeshSize = static_cast(value.mSize); + const auto itemSize = static_cast(value.mSize) + getSize(recastMesh, offMeshConnections); const std::lock_guard lock(mMutex); - if (navMeshSize > mMaxNavMeshDataSize) + if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); - if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) - return Value(); + while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) + removeLeastRecentlyUsed(); NavMeshKey navMeshKey { RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()}, offMeshConnections }; - const auto itemSize = navMeshSize + getSize(recastMesh, offMeshConnections); - - if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) - return Value(); - - while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) - removeLeastRecentlyUsed(); const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator); From 61e014a7657092bf8ea1e426665ea3934169c560 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 4 Feb 2021 21:25:38 +0100 Subject: [PATCH 0329/2859] Allow negative values for ai stats --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/stat.cpp | 4 +++- apps/openmw/mwmechanics/stat.hpp | 2 +- apps/openmw/mwscript/aiextensions.cpp | 6 ++---- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa74164d95..b5b66ee01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Bug #5821: NPCs from mods getting removed if mod order was changed + Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 7f71cf9b18..c87de2ccbb 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -18,8 +18,10 @@ namespace MWMechanics } template - T Stat::getModified() const + T Stat::getModified(bool capped) const { + if(!capped) + return mModified; return std::max(static_cast(0), mModified); } diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 5f49da48e8..fb9dca9221 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -28,7 +28,7 @@ namespace MWMechanics const T& getBase() const; - T getModified() const; + T getModified(bool capped = true) const; T getCurrentModified() const; T getModifier() const; T getCurrentModifier() const; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 499c2f672d..223ae3a152 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -241,7 +241,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified()); + runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified(false)); } }; template @@ -276,9 +276,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex); - stat.setModified(value, 0); - ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, stat); + ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, value); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value); } }; From d5844b098295389c3b757a5a3c54f0627780d59b Mon Sep 17 00:00:00 2001 From: unelsson Date: Thu, 4 Feb 2021 23:14:21 +0200 Subject: [PATCH 0330/2859] Use accompanying txt file for textkeys in osgAnimation formats --- components/resource/keyframemanager.cpp | 68 ++++++++++++++++++++----- components/resource/keyframemanager.hpp | 10 +++- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index b6417597d5..923ce43265 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -18,7 +18,9 @@ namespace Resource { - RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager) {} + RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, + const std::string& normalized, const VFS::Manager* vfs) : + osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager), mNormalized(normalized), mVFS(vfs) {} void RetrieveAnimationsVisitor::apply(osg::Node& node) { @@ -39,27 +41,19 @@ namespace Resource osg::ref_ptr mergedAnimationTrack = new Resource::Animation; std::string animationName = animation->getName(); - std::string start = animationName + std::string(": start"); - std::string stop = animationName + std::string(": stop"); + mergedAnimationTrack->setName(animationName); const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel: channels) { mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? } - mergedAnimationTrack->setName(animation->getName()); + callback->addMergedAnimationTrack(mergedAnimationTrack); float startTime = animation->getStartTime(); float stopTime = startTime + animation->getDuration(); - // mTextKeys is a nif-thing, used by OpenMW's animation system - // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" - // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played - // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" - mTarget.mTextKeys.emplace(startTime, std::move(start)); - mTarget.mTextKeys.emplace(stopTime, std::move(stop)); - SceneUtil::EmulatedAnimation emulatedAnimation; emulatedAnimation.mStartTime = startTime; emulatedAnimation.mStopTime = stopTime; @@ -67,12 +61,62 @@ namespace Resource emulatedAnimations.emplace_back(emulatedAnimation); } } + + // mTextKeys is a nif-thing, used by OpenMW's animation system + // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" + // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played + // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" + // osgAnimation formats should have a .txt file with the same name, each line holding a textkey and whitespace separated time value + // e.g. idle: start 0.0333 + try + { + Files::IStreamPtr textKeysFile = mVFS->get(changeFileExtension(mNormalized, "txt")); + std::string line; + while ( getline (*textKeysFile, line) ) + { + Log(Debug::Warning) << "add textkey ***" << parseTextKey(line) << "***" << parseTimeSignature(line) << "***"; + mTarget.mTextKeys.emplace(parseTimeSignature(line), std::move(parseTextKey(line))); + } + } + catch (std::exception& e) + { + Log(Debug::Warning) << "No textkey file found for " << mNormalized; + } + callback->setEmulatedAnimations(emulatedAnimations); mTarget.mKeyframeControllers.emplace(node.getName(), callback); } traverse(node); } + + std::string RetrieveAnimationsVisitor::parseTextKey(const std::string& line) + { + size_t spacePos = line.find_last_of(' '); + if (spacePos != std::string::npos) + return line.substr(0, spacePos); + return ""; + } + + double RetrieveAnimationsVisitor::parseTimeSignature(const std::string& line) + { + size_t spacePos = line.find_last_of(' '); + double time = 0.0; + if (spacePos != std::string::npos && spacePos + 1 < line.size()) + time = std::stod(line.substr(spacePos + 1)); + return time; + } + + std::string RetrieveAnimationsVisitor::changeFileExtension(const std::string file, const std::string ext) + { + size_t extPos = file.find_last_of('.'); + if (extPos != std::string::npos && extPos+1 < file.size()) + { + return file.substr(0, extPos + 1) + ext; + } + return file; + } + } namespace Resource @@ -110,7 +154,7 @@ namespace Resource osg::ref_ptr bam = dynamic_cast (scene->getUpdateCallback()); if (bam) { - Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam); + Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS); scene->accept(rav); } } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 3e992ac5e9..87a20b97a5 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -15,13 +15,21 @@ namespace Resource class RetrieveAnimationsVisitor : public osg::NodeVisitor { public: - RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager); + RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, + const std::string& normalized, const VFS::Manager* vfs); virtual void apply(osg::Node& node) override; private: + + std::string changeFileExtension(const std::string file, const std::string ext); + std::string parseTextKey(const std::string& line); + double parseTimeSignature(const std::string& line); + SceneUtil::KeyframeHolder& mTarget; osg::ref_ptr mAnimationManager; + std::string mNormalized; + const VFS::Manager* mVFS; }; } From 303f1912a6af88e49f96fc8260621a8e7519e48a Mon Sep 17 00:00:00 2001 From: unelsson Date: Thu, 4 Feb 2021 23:14:52 +0200 Subject: [PATCH 0331/2859] less debug spam --- components/resource/keyframemanager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 923ce43265..0c33428e01 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -74,7 +74,6 @@ namespace Resource std::string line; while ( getline (*textKeysFile, line) ) { - Log(Debug::Warning) << "add textkey ***" << parseTextKey(line) << "***" << parseTimeSignature(line) << "***"; mTarget.mTextKeys.emplace(parseTimeSignature(line), std::move(parseTextKey(line))); } } From e42b67ee50b8d5950e183db51d750198d87db7f2 Mon Sep 17 00:00:00 2001 From: Noah Gooder Date: Fri, 5 Feb 2021 17:59:36 +0000 Subject: [PATCH 0332/2859] Modified actors.cpp and Authors.md --- AUTHORS.md | 1 + apps/openmw/mwmechanics/actors.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index e6ff67293a..53efdd286f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -215,6 +215,7 @@ Programmers Yohaulticetl Yuri Krupenin zelurker + Noah Gooder Documentation ------------- diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index cb3570d21f..c0a1371585 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -951,7 +951,7 @@ namespace MWMechanics if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison); } - else if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) { creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze); From 3007cb14a9fa10ef2d17f53a2519030135d737db Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 5 Feb 2021 19:00:35 +0100 Subject: [PATCH 0333/2859] Also allow negative AI values in dialogue --- CHANGELOG.md | 1 + apps/openmw/mwdialogue/filter.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5b66ee01b..d25984a83d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ Bug #5762: Movement solver is insufficiently robust Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee + Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index a3c326ab8f..334a9db39f 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -316,7 +316,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_AiSetting: return mActor.getClass().getCreatureStats (mActor).getAiSetting ( - (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(); + (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(false); case SelectWrapper::Function_PcAttribute: From 6e969ca3fae992a54aea6452a81caf32e8418135 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 5 Feb 2021 12:12:34 +0100 Subject: [PATCH 0334/2859] Use mesh collision box instead of node bounding sphere for projectile size. The bounding sphere is much bigger than the mesh. --- apps/openmw/mwphysics/physicssystem.cpp | 7 ++++++- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 22 +++++++++++++--------- apps/openmw/mwworld/projectilemanager.hpp | 2 +- components/resource/bulletshape.hpp | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5a9a0be832..b434ff3406 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -682,9 +682,14 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater) { + osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); + assert(shapeInstance); + float radius = computeRadius ? shapeInstance->mCollisionBox.extents.length() / 2.f : 1.f; + mProjectileId++; + auto projectile = std::make_shared(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 715a6cd1a2..03d3020b9c 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -124,7 +124,7 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater); void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 092ce270e9..3ffc7bb953 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -189,7 +189,7 @@ namespace MWWorld }; - float ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, + void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) { state.mNode = new osg::PositionAttitudeTransform; @@ -253,7 +253,6 @@ namespace MWWorld state.mNode->accept(assignVisitor); MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); - return projectile->getBound().radius(); } void ProjectileManager::update(State& state, float duration) @@ -308,7 +307,8 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - const auto radius = createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); + auto model = ptr.getClass().getModel(ptr); + createModel(state, model, pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) @@ -319,7 +319,10 @@ namespace MWWorld state.mSounds.push_back(sound); } - state.mProjectileId = mPhysics->addProjectile(caster, pos, radius, false); + // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape + if (state.mIdMagic.size() > 1) + model = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get().find(state.mIdMagic.at(1))->mModel; + state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true, false); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -339,11 +342,12 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); - createModel(state, ptr.getClass().getModel(ptr), pos, orient, false, false, osg::Vec4(0,0,0,0)); + const auto model = ptr.getClass().getModel(ptr); + createModel(state, model, pos, orient, false, false, osg::Vec4(0,0,0,0)); if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(actor, pos, 1.f, true); + state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false, true); state.mToDelete = false; mProjectiles.push_back(state); } @@ -629,7 +633,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), 1.f, true); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false, true); } catch(...) { @@ -681,8 +685,8 @@ namespace MWWorld } osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - const auto radius = createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), radius, false); + createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true, false); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index e088dd7011..c047d90ddf 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -130,7 +130,7 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - float createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, + void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 7d9577ba02..8ad13fae14 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -32,7 +32,7 @@ namespace Resource osg::Vec3f extents; osg::Vec3f center; }; - // Used for actors. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures. + // Used for actors and projectiles. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures. // For now, use one file <-> one resource for simplicity. CollisionBox mCollisionBox; From 3e273a759acc8e238de5ce2ceb03d877405a6f0f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 6 Feb 2021 16:41:46 +0000 Subject: [PATCH 0335/2859] Clarify method name now we're using it differently --- components/sceneutil/mwshadowtechnique.cpp | 6 +++--- components/sceneutil/mwshadowtechnique.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 7476bc2190..ba903f6db3 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -1426,7 +1426,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) if (numValidShadows>0) { - selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); + prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); } // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()< stateset = vdd.getStateSet(traversalNumber); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 5125247dda..7b934b7984 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -231,7 +231,7 @@ namespace SceneUtil { virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; - virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const; + virtual osg::StateSet* prepareStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const; protected: virtual ~MWShadowTechnique(); From 6aa75c287aa86e7e1c44cd7cd15ae89cef0894c8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Feb 2021 00:15:01 +0100 Subject: [PATCH 0336/2859] Don't check magicka when casting free spells --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/spellutil.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa74164d95..3e29a292f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Bug #5821: NPCs from mods getting removed if mod order was changed + Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0 Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 8b2f5c46c8..0c667e680a 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -123,7 +123,7 @@ namespace MWMechanics if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; - if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost) + if (checkMagicka && spell->mData.mCost > 0 && stats.getMagicka().getCurrent() < spell->mData.mCost) return 0; if (spell->mData.mFlags & ESM::Spell::F_Always) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 442672d2bf..d21c17a512 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3009,7 +3009,7 @@ namespace MWWorld // Check mana bool godmode = (isPlayer && mGodMode); MWMechanics::DynamicStat magicka = stats.getMagicka(); - if (magicka.getCurrent() < spell->mData.mCost && !godmode) + if (spell->mData.mCost > 0 && magicka.getCurrent() < spell->mData.mCost && !godmode) { message = "#{sMagicInsufficientSP}"; fail = true; From 31b5150e0db3f2094d4ac4a67990a82529c69a83 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 7 Feb 2021 09:12:38 +0100 Subject: [PATCH 0337/2859] Fix implementation of Misc::swapEndiannessInplace --- components/misc/endianness.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/components/misc/endianness.hpp b/components/misc/endianness.hpp index 1b43e584e5..8019d33ed8 100644 --- a/components/misc/endianness.hpp +++ b/components/misc/endianness.hpp @@ -2,6 +2,8 @@ #define COMPONENTS_MISC_ENDIANNESS_H #include +#include +#include namespace Misc { @@ -15,20 +17,26 @@ namespace Misc if constexpr (sizeof(T) == 2) { - uint16_t& v16 = *reinterpret_cast(&v); + uint16_t v16; + std::memcpy(&v16, &v, sizeof(T)); v16 = (v16 >> 8) | (v16 << 8); + std::memcpy(&v, &v16, sizeof(T)); } if constexpr (sizeof(T) == 4) { - uint32_t& v32 = *reinterpret_cast(&v); - v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) || v32 << 24; + uint32_t v32; + std::memcpy(&v32, &v, sizeof(T)); + v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) | v32 << 24; + std::memcpy(&v, &v32, sizeof(T)); } if constexpr (sizeof(T) == 8) { - uint64_t& v64 = *reinterpret_cast(&v); + uint64_t v64; + std::memcpy(&v64, &v, sizeof(T)); v64 = (v64 >> 56) | ((v64 & 0x00ff'0000'0000'0000) >> 40) | ((v64 & 0x0000'ff00'0000'0000) >> 24) | ((v64 & 0x0000'00ff'0000'0000) >> 8) | ((v64 & 0x0000'0000'ff00'0000) << 8) | ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56); + std::memcpy(&v, &v64, sizeof(T)); } } From f1caeea4445cbe8c0b235203867a86512cd5f120 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Feb 2021 11:58:23 +0100 Subject: [PATCH 0338/2859] Don't return negative values from GetMagicka --- apps/openmw/mwscript/statsextensions.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 58a943e1a9..ab3287c9a4 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -187,6 +187,9 @@ namespace MWScript .getCreatureStats(ptr) .getDynamic(mIndex) .getCurrent(); + // GetMagicka shouldn't return negative values + if(mIndex == 1 && value < 0) + value = 0; } runtime.push (value); } From 39c2c19daee5d4cff5379a077bdee4d75ab07d7a Mon Sep 17 00:00:00 2001 From: Jonas Tobias Hopusch Date: Sun, 7 Feb 2021 12:53:06 +0100 Subject: [PATCH 0339/2859] Update 'toggle sneak' documentation The docs now correctly say that this setting can be changed in the launcher, instead of insisting it's a config-file-only setting. Closes #5844 Signed-off-by: Jonas Tobias Hopusch --- CHANGELOG.md | 1 + docs/source/reference/modding/settings/input.rst | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d25984a83d..20932c89f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,7 @@ Feature #5813: Instanced groundcover support Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation + Task #5844: Update 'toggle sneak' documentation 0.46.0 ------ diff --git a/docs/source/reference/modding/settings/input.rst b/docs/source/reference/modding/settings/input.rst index 8a95686cfc..d04c267a76 100644 --- a/docs/source/reference/modding/settings/input.rst +++ b/docs/source/reference/modding/settings/input.rst @@ -38,7 +38,7 @@ This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in the launcher under "Advanced" -> "Game Mechanics" -> "Toggle sneak". always run ---------- From af0f94f03e373f79823a86fa40e59de9fbfa0788 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Feb 2021 12:55:10 +0100 Subject: [PATCH 0340/2859] Play damage sound when hurt by elemental shields --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/combat.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d25984a83d..6c4c241b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee + Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior ) Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 183845b8c1..fd3f318117 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -345,6 +345,8 @@ namespace MWMechanics MWMechanics::DynamicStat health = attackerStats.getHealth(); health.setCurrent(health.getCurrent() - x); attackerStats.setHealth(health); + + MWBase::Environment::get().getSoundManager()->playSound3D(attacker, "Health Damage", 1.0f, 1.0f); } } From b78820de5563c92184d31c1d44af122b4b830364 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 7 Feb 2021 18:32:03 +0100 Subject: [PATCH 0341/2859] Ignore projectiles inside of MovementSolver::unstuck. It is normal for actors to be inside of a projectile collision shape. A side effect of moving actors outside of projectile collision shape is that if both the actor and the projectile are near a wall, the actor could get moved outside of the world. --- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 9d29574098..5f0322a1f2 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -38,7 +38,7 @@ namespace MWPhysics ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me) { m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; - m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask; + m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; mVelocity = Misc::Convert::toBullet(velocity); } btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override From da6223fc4b7f5bb98143b6a5bd1585ce93fd9a92 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Feb 2021 23:27:45 +0000 Subject: [PATCH 0342/2859] Use default icon.tga when inventory icon is missing --- apps/openmw/mwgui/itemwidget.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 940e95a7e9..7c7fa88287 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -6,6 +6,9 @@ #include // correctIconPath +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -106,7 +109,10 @@ namespace MWGui std::string invIcon = ptr.getClass().getInventoryIcon(ptr); if (invIcon.empty()) invIcon = "default icon.tga"; - setIcon(MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon)); + invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon); + if (!MWBase::Environment::get().getResourceSystem()->getVFS()->exists(invIcon)) + invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath("default icon.tga"); + setIcon(invIcon); } From 034a7a9dbca2094e3e76bcd075b06ea30ca03ddd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Feb 2021 23:45:53 +0000 Subject: [PATCH 0343/2859] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b88e9ee81..83ad5725e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ Feature #2686: Timestamps in openmw.log Feature #3171: OpenMW-CS: Instance drag selection Feature #4894: Consider actors as obstacles for pathfinding + Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #5043: Head Bobbing Feature #5199: Improve Scene Colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher From f3271cb66b1222cfbce3a588858c5b0fe004dcf7 Mon Sep 17 00:00:00 2001 From: Sergey Fukanchik Date: Tue, 9 Feb 2021 13:09:36 -0500 Subject: [PATCH 0344/2859] Add unit test for swapEndiannessInplace(). Part of Bug #5837 --- apps/openmw_test_suite/CMakeLists.txt | 1 + .../misc/test_endianness.cpp | 122 ++++++++++++++++++ components/misc/endianness.hpp | 2 +- 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 apps/openmw_test_suite/misc/test_endianness.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 5658a5eefc..a3bb0c6f87 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -15,6 +15,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) esm/test_fixed_string.cpp misc/test_stringops.cpp + misc/test_endianness.cpp nifloader/testbulletnifloader.cpp diff --git a/apps/openmw_test_suite/misc/test_endianness.cpp b/apps/openmw_test_suite/misc/test_endianness.cpp new file mode 100644 index 0000000000..240e4a49ac --- /dev/null +++ b/apps/openmw_test_suite/misc/test_endianness.cpp @@ -0,0 +1,122 @@ +#include +#include "components/misc/endianness.hpp" + +struct EndiannessTest : public ::testing::Test {}; + +TEST_F(EndiannessTest, test_swap_endianness_inplace1) +{ + uint8_t zero=0x00; + uint8_t ff=0xFF; + uint8_t fortytwo=0x42; + uint8_t half=128; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00); + + Misc::swapEndiannessInplace(ff); + EXPECT_EQ(ff, 0xFF); + + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x42); + + Misc::swapEndiannessInplace(half); + EXPECT_EQ(half, 128); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace2) +{ + uint16_t zero = 0x0000; + uint16_t ffff = 0xFFFF; + uint16_t n12 = 0x0102; + uint16_t fortytwo = 0x0042; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF); + + Misc::swapEndiannessInplace(n12); + EXPECT_EQ(n12, 0x0201); + Misc::swapEndiannessInplace(n12); + EXPECT_EQ(n12, 0x0102); + + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x4200); + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x0042); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace4) +{ + uint32_t zero = 0x00000000; + uint32_t n1234 = 0x01020304; + uint32_t ffff = 0xFFFFFFFF; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00000000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00000000); + + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x04030201); + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x01020304); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFFFFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFFFFFF); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace8) +{ + uint64_t zero = 0x0000'0000'0000'0000; + uint64_t n1234 = 0x0102'0304'0506'0708; + uint64_t ffff = 0xFFFF'FFFF'FFFF'FFFF; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000'0000'0000'0000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000'0000'0000'0000); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFF); + + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x0807'0605'0403'0201); + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x0102'0304'0506'0708); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace_float) +{ + const uint32_t original = 0x4023d70a; + const uint32_t expected = 0x0ad72340; + + float number; + memcpy(&number, &original, sizeof(original)); + + Misc::swapEndiannessInplace(number); + + EXPECT_TRUE(!memcmp(&number, &expected, sizeof(expected))); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace_double) +{ + const uint64_t original = 0x040047ae147ae147ul; + const uint64_t expected = 0x47e17a14ae470004ul; + + double number; + memcpy(&number, &original, sizeof(original)); + + Misc::swapEndiannessInplace(number); + + EXPECT_TRUE(!memcmp(&number, &expected, sizeof(expected)) ); +} diff --git a/components/misc/endianness.hpp b/components/misc/endianness.hpp index 8019d33ed8..ad8aba18c7 100644 --- a/components/misc/endianness.hpp +++ b/components/misc/endianness.hpp @@ -26,7 +26,7 @@ namespace Misc { uint32_t v32; std::memcpy(&v32, &v, sizeof(T)); - v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) | v32 << 24; + v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) | (v32 << 24); std::memcpy(&v, &v32, sizeof(T)); } if constexpr (sizeof(T) == 8) From f8f6d63d4c34d0284123e664dfcdcde6db7b1c15 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 9 Feb 2021 21:56:21 +0000 Subject: [PATCH 0345/2859] Optimise groundcover lighting --- files/shaders/lighting.glsl | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 5eae890291..a2dcc758a8 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -13,22 +13,13 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie #ifndef GROUNDCOVER lambert = max(lambert, 0.0); #else + float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); + if (lambert < 0.0) { - float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz)); - if (lambert >= 0.0) - cosine = -cosine; - - float mult = 1.0; - float divisor = 8.0; - - if (cosine < 0.0 && cosine >= -1.0/divisor) - mult = mix(1.0, 0.3, -cosine*divisor); - else if (cosine < -1.0/divisor) - mult = 0.3; - - lambert *= mult; - lambert = abs(lambert); + lambert = -lambert; + eyeCosine = -eyeCosine; } + lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; } From 2e73d2c1451f080d854b51de89ce77893ebbf67d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 10 Feb 2021 22:13:04 +0100 Subject: [PATCH 0346/2859] Fallback to default cell name for door destination --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwclass/door.cpp | 27 ++++++--------------------- apps/openmw/mwworld/worldimp.cpp | 16 +++++++++++----- apps/openmw/mwworld/worldimp.hpp | 1 + 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ad5725e6..acfdc9e50b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee + Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod. Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior ) Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0 Feature #390: 3rd person look "over the shoulder" diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index a3b035a8dd..194e8f6956 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -185,6 +185,7 @@ namespace MWBase /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. + virtual std::string getCellName(const ESM::Cell* cell) const = 0; virtual void removeRefScript (MWWorld::RefData *ref) = 0; //< Remove the script attached to ref from mLocalScripts diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ba51d9c2bc..25f7fc4568 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -302,30 +302,15 @@ namespace MWClass std::string Door::getDestination (const MWWorld::LiveCellRef& door) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - - std::string dest; - if (door.mRef.getDestCell() != "") - { - // door leads to an interior, use interior name as tooltip - dest = door.mRef.getDestCell(); - } - else + std::string dest = door.mRef.getDestCell(); + if (dest.empty()) { // door leads to exterior, use cell name (if any), otherwise translated region name int x,y; - MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); - const ESM::Cell* cell = store.get().find(x,y); - if (cell->mName != "") - dest = cell->mName; - else - { - const ESM::Region* region = - store.get().find(cell->mRegion); - - //name as is, not a token - return MyGUI::TextIterator::toTagsString(region->mName); - } + auto world = MWBase::Environment::get().getWorld(); + world->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); + const ESM::Cell* cell = world->getStore().get().search(x,y); + dest = world->getCellName(cell); } return "#{sCell=" + dest + "}"; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d21c17a512..651d93ec58 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -656,13 +656,19 @@ namespace MWWorld { if (!cell) cell = mWorldScene->getCurrentCell(); + return getCellName(cell->getCell()); + } - if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty()) - return cell->getCell()->mName; - - if (const ESM::Region* region = mStore.get().search (cell->getCell()->mRegion)) - return region->mName; + std::string World::getCellName(const ESM::Cell* cell) const + { + if (cell) + { + if (!cell->isExterior() || !cell->mName.empty()) + return cell->mName; + if (const ESM::Region* region = mStore.get().search (cell->mRegion)) + return region->mName; + } return mStore.get().find ("sDefaultCellname")->mValue.getString(); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 29d29a1604..bc2baafd8a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -281,6 +281,7 @@ namespace MWWorld /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. + std::string getCellName(const ESM::Cell* cell) const override; void removeRefScript (MWWorld::RefData *ref) override; //< Remove the script attached to ref from mLocalScripts From c97980a0f679e2f89955c4ab1ab6900e4ac271a8 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 11 Feb 2021 01:35:32 +0000 Subject: [PATCH 0347/2859] Don't force linker to work with doubly-defined symbols --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 226511b527..fe88618158 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -440,8 +440,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 5.0) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") endif() -elseif (MSVC) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE") endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) # Extern From 8ab5fd9b406408fdab50a52a37871aeb69fb326a Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 11 Feb 2021 18:19:55 +0100 Subject: [PATCH 0348/2859] Fix frame rate limit Measure time at the computation end but before sleep. This allows to adjust sleep interval for the next frame in case sleep is not precise due to syscall overhead or too low timer resolution. Remove old frame limiting mechanism. --- apps/openmw/engine.cpp | 14 ++++--- apps/openmw/mwbase/environment.cpp | 15 ------- apps/openmw/mwbase/environment.hpp | 1 - apps/openmw/mwgui/windowmanagerimp.cpp | 15 ++++--- components/misc/frameratelimiter.hpp | 56 ++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 components/misc/frameratelimiter.hpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 0e4cf762ac..d7c315323d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -38,6 +38,8 @@ #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -918,13 +920,15 @@ void OMW::Engine::go() } // Start the main rendering loop - osg::Timer frameTimer; double simulationTime = 0.0; + Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); + const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) { - double dt = frameTimer.time_s(); - frameTimer.setStartTick(); - dt = std::min(dt, 0.2); + const double dt = std::chrono::duration_cast>(std::min( + frameRateLimiter.getLastFrameDuration(), + maxSimulationInterval + )).count(); mViewer->advance(simulationTime); @@ -960,7 +964,7 @@ void OMW::Engine::go() } } - mEnvironment.limitFrameRate(frameTimer.time_s()); + frameRateLimiter.limit(); } // Save user settings diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 9014fafff9..b7235edd4b 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -1,8 +1,6 @@ #include "environment.hpp" #include -#include -#include #include @@ -98,19 +96,6 @@ float MWBase::Environment::getFrameRateLimit() const return mFrameRateLimit; } -void MWBase::Environment::limitFrameRate(double dt) const -{ - if (mFrameRateLimit > 0.f) - { - double thisFrameTime = dt; - double minFrameTime = 1.0 / static_cast(mFrameRateLimit); - if (thisFrameTime < minFrameTime) - { - std::this_thread::sleep_for(std::chrono::duration(minFrameTime - thisFrameTime)); - } - } -} - MWBase::World *MWBase::Environment::getWorld() const { assert (mWorld); diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 7871153cc5..3b57e4e7c1 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -83,7 +83,6 @@ namespace MWBase void setFrameRateLimit(float frameRateLimit); float getFrameRateLimit() const; - void limitFrameRate(double dt) const; World *getWorld() const; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index bfce908103..7da8cb7bd8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -50,6 +50,7 @@ #include #include +#include #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" @@ -710,12 +711,11 @@ namespace MWGui if (block) { - osg::Timer frameTimer; + Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); while (mMessageBoxManager->readPressedButton(false) == -1 && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - double dt = frameTimer.time_s(); - frameTimer.setStartTick(); + const double dt = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()).count(); mKeyboardNavigation->onFrame(); mMessageBoxManager->onFrame(dt); @@ -734,7 +734,7 @@ namespace MWGui // refer to the advance() and frame() order in Engine::go() mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - MWBase::Environment::get().limitFrameRate(frameTimer.time_s()); + frameRateLimiter.limit(); } } } @@ -1750,11 +1750,10 @@ namespace MWGui ~MWSound::Type::Movie & MWSound::Type::Mask ); - osg::Timer frameTimer; + Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - double dt = frameTimer.time_s(); - frameTimer.setStartTick(); + const double dt = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()).count(); MWBase::Environment::get().getInputManager()->update(dt, true, false); @@ -1777,7 +1776,7 @@ namespace MWGui // refer to the advance() and frame() order in Engine::go() mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - MWBase::Environment::get().limitFrameRate(frameTimer.time_s()); + frameRateLimiter.limit(); } mVideoWidget->stop(); diff --git a/components/misc/frameratelimiter.hpp b/components/misc/frameratelimiter.hpp new file mode 100644 index 0000000000..b8e2101651 --- /dev/null +++ b/components/misc/frameratelimiter.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_COMPONENTS_MISC_FRAMERATELIMITER_H +#define OPENMW_COMPONENTS_MISC_FRAMERATELIMITER_H + +#include +#include + +namespace Misc +{ + class FrameRateLimiter + { + public: + template + explicit FrameRateLimiter(std::chrono::duration maxFrameDuration, + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) + : mMaxFrameDuration(std::chrono::duration_cast(maxFrameDuration)) + , mLastMeasurement(now) + {} + + std::chrono::steady_clock::duration getLastFrameDuration() const + { + return mLastFrameDuration; + } + + void limit(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) + { + const auto passed = now - mLastMeasurement; + const auto left = mMaxFrameDuration - passed; + if (left > left.zero()) + { + std::this_thread::sleep_for(left); + mLastMeasurement = now + left; + mLastFrameDuration = mMaxFrameDuration; + } + else + { + mLastMeasurement = now; + mLastFrameDuration = passed; + } + } + + private: + std::chrono::steady_clock::duration mMaxFrameDuration; + std::chrono::steady_clock::time_point mLastMeasurement; + std::chrono::steady_clock::duration mLastFrameDuration; + }; + + inline Misc::FrameRateLimiter makeFrameRateLimiter(float frameRateLimit) + { + if (frameRateLimit > 0.0f) + return Misc::FrameRateLimiter(std::chrono::duration(1.0f / frameRateLimit)); + else + return Misc::FrameRateLimiter(std::chrono::steady_clock::duration::zero()); + } +} + +#endif From bc4047d81552d825ea9f960cdaffe6fdc2ef3a6a Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 12 Feb 2021 19:03:02 +0100 Subject: [PATCH 0349/2859] Update engine stats for all levels above first, not only at the second. --- components/resource/stats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 690814f91d..4d07889d0c 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -127,7 +127,7 @@ bool Profiler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter if (viewer) { // Add/remove openmw stats to the osd as necessary - viewer->getViewerStats()->collectStats("engine", _statsType == StatsHandler::StatsType::VIEWER_STATS); + viewer->getViewerStats()->collectStats("engine", _statsType >= StatsHandler::StatsType::VIEWER_STATS); if (_offlineCollect) CollectStatistics(viewer); From 9d90e250cf51993d817874209936dd6dd59d3fb1 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 12 Feb 2021 19:33:08 +0100 Subject: [PATCH 0350/2859] Physics is not running while paused, so zero the stats for the async thread instead of keeping whatever value was before the pause. --- apps/openmw/mwworld/worldimp.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d21c17a512..1f3a2428c5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1861,6 +1861,13 @@ namespace MWWorld { doPhysics (duration, frameStart, frameNumber, stats); } + else + { + // zero the async stats if we are paused + stats.setAttribute(frameNumber, "physicsworker_time_begin", 0); + stats.setAttribute(frameNumber, "physicsworker_time_taken", 0); + stats.setAttribute(frameNumber, "physicsworker_time_end", 0); + } } void World::updatePlayer() From c4e909c29e4f1bbb4c463bc015daf42610ad6ec5 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 12 Feb 2021 19:36:03 +0100 Subject: [PATCH 0351/2859] Silence a clang warning: warning: moving a temporary object prevents copy elision [-Wpessimizing-move] --- components/resource/keyframemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 0c33428e01..f4ab79519e 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -74,7 +74,7 @@ namespace Resource std::string line; while ( getline (*textKeysFile, line) ) { - mTarget.mTextKeys.emplace(parseTimeSignature(line), std::move(parseTextKey(line))); + mTarget.mTextKeys.emplace(parseTimeSignature(line), parseTextKey(line)); } } catch (std::exception& e) From 0736fec148fa68680a54a928c7fdc186bca9bab5 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 12 Feb 2021 19:21:07 +0000 Subject: [PATCH 0352/2859] Give binaries static filename --- .gitlab-ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 810e23d38e..2c45e19e66 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -116,12 +116,13 @@ variables: &cs-targets - .\ActivateMSVC.ps1 - cmake --build . --config $config --target ($targets.Split(',')) - 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 - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' + 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: @@ -206,12 +207,13 @@ Windows_Ninja_CS_RelWithDebInfo: - cd MSVC2019_64 - cmake --build . --config $config --target ($targets.Split(',')) - 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 - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' + 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: From becccf3b5e30cff03a358295cf6dd1f547542022 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 10 Feb 2021 22:34:15 +0100 Subject: [PATCH 0353/2859] Make actor flee from a combat when cannot reach a target --- apps/openmw/mwmechanics/aicombat.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index b98d5ef491..4b01281191 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -262,6 +262,25 @@ namespace MWMechanics // start new attack storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); } + else if (!isRangedCombat && !mPathFinder.isPathConstructed() && storage.mCurrentAction->isAttackingOrSpell()) + { + const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); + const osg::Vec3f destination = target.getRefData().getPosition().asVec3(); + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + mPathFinder.buildPath(actor, position, destination, actor.getCell(), getPathGridGraph(actor.getCell()), + halfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); + + if (!mPathFinder.isPathConstructed()) + { + storage.stopAttack(); + characterController.setAttackingOrSpell(false); + currentAction.reset(new ActionFlee()); + actionCooldown = currentAction->getActionCooldown(); + storage.startFleeing(); + MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); + } + } + return false; } From 5e5c0a1d8982fad48dbed3784da59201ba3954aa Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Feb 2021 00:28:18 +0000 Subject: [PATCH 0354/2859] Add branch name back to filename The URL needs to contain the branch name anyway, so there's no point removing it. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2c45e19e66..6bfe0bf23a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -122,7 +122,7 @@ variables: &cs-targets 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: @@ -213,7 +213,7 @@ Windows_Ninja_CS_RelWithDebInfo: 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: From 8dba61f7aed8072edac0755454e0df7edd269531 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Feb 2021 04:07:08 +0100 Subject: [PATCH 0355/2859] Use navmesh raycast to find reachable position around target --- apps/openmw/mwmechanics/aicombat.cpp | 80 ++++++++++++++----- apps/openmw/mwmechanics/aicombat.hpp | 7 +- .../detournavigator/navigator.cpp | 22 +++++ components/CMakeLists.txt | 1 + components/detournavigator/navigator.cpp | 16 ++++ components/detournavigator/navigator.hpp | 11 +++ components/detournavigator/raycast.cpp | 44 ++++++++++ components/detournavigator/raycast.hpp | 19 +++++ 8 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 components/detournavigator/raycast.cpp create mode 100644 components/detournavigator/raycast.hpp diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4b01281191..58a9086720 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "../mwphysics/collisiontype.hpp" @@ -127,10 +128,11 @@ namespace MWMechanics { //Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame. updateLOS(actor, target, duration, storage); - float targetReachedTolerance = 0.0f; - if (storage.mLOS) - targetReachedTolerance = storage.mAttackRange; - const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance); + const float targetReachedTolerance = storage.mLOS && !storage.mUseCustomDestination + ? storage.mAttackRange : 0.0f; + const osg::Vec3f destination = storage.mUseCustomDestination + ? storage.mCustomDestination : target.getRefData().getPosition().asVec3(); + const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance); if (is_target_reached) storage.mReadyToAttack = true; } @@ -232,8 +234,8 @@ namespace MWMechanics const ESM::Weapon* weapon = currentAction->getWeapon(); ESM::Position pos = actor.getRefData().getPosition(); - osg::Vec3f vActorPos(pos.asVec3()); - osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); + const osg::Vec3f vActorPos(pos.asVec3()); + const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); @@ -243,9 +245,7 @@ namespace MWMechanics if (isRangedCombat) { // rotate actor taking into account target movement direction and projectile speed - osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); - lastTargetPos = vTargetPos; + vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); @@ -256,28 +256,66 @@ namespace MWMechanics storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } + storage.mLastTargetPos = vTargetPos; + if (storage.mReadyToAttack) { storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); } - else if (!isRangedCombat && !mPathFinder.isPathConstructed() && storage.mCurrentAction->isAttackingOrSpell()) + + // If actor uses custom destination it has to try to rebuild path because environment can change + // (door is opened between actor and target) or target position has changed and current custom destination + // is not good enough to attack target. + if (storage.mCurrentAction->isAttackingOrSpell() + && ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed()) + || (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack))) { - const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); - const osg::Vec3f destination = target.getRefData().getPosition().asVec3(); - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); - mPathFinder.buildPath(actor, position, destination, actor.getCell(), getPathGridGraph(actor.getCell()), - halfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); + // Try to build path to the target. + const auto halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + const auto navigatorFlags = getNavigatorFlags(actor); + const auto areaCosts = getAreaCosts(actor); + const auto pathGridGraph = getPathGridGraph(actor.getCell()); + mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts); if (!mPathFinder.isPathConstructed()) { - storage.stopAttack(); - characterController.setAttackingOrSpell(false); - currentAction.reset(new ActionFlee()); - actionCooldown = currentAction->getActionCooldown(); - storage.startFleeing(); - MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); + // If there is no path, try to find a point on a line from the actor position to target projected + // on navmesh to attack the target from there. + const MWBase::World* world = MWBase::Environment::get().getWorld(); + const auto halfExtents = world->getPathfindingHalfExtents(actor); + const auto navigator = world->getNavigator(); + const auto navigatorFlags = getNavigatorFlags(actor); + const auto areaCosts = getAreaCosts(actor); + const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags); + + if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack) + { + // If the point is close enough, try to find a path to that point. + mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts); + if (mPathFinder.isPathConstructed()) + { + // If path to that point is found use it as custom destination. + storage.mCustomDestination = *hit; + storage.mUseCustomDestination = true; + } + } + + if (!mPathFinder.isPathConstructed()) + { + storage.mUseCustomDestination = false; + storage.stopAttack(); + characterController.setAttackingOrSpell(false); + currentAction.reset(new ActionFlee()); + actionCooldown = currentAction->getActionCooldown(); + storage.startFleeing(); + MWBase::Environment::get().getDialogueManager()->say(actor, "flee"); + } + } + else + { + storage.mUseCustomDestination = false; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 64645ca941..3a77aa8e8e 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -55,6 +55,9 @@ namespace MWMechanics float mFleeBlindRunTimer; ESM::Pathgrid::Point mFleeDest; + bool mUseCustomDestination; + osg::Vec3f mCustomDestination; + AiCombatStorage(): mAttackCooldown(0.0f), mTimerReact(AI_REACTION_TIME), @@ -74,7 +77,9 @@ namespace MWMechanics mFleeState(FleeState_None), mLOS(false), mUpdateLOSTimer(0.0f), - mFleeBlindRunTimer(0.0f) + mFleeBlindRunTimer(0.0f), + mUseCustomDestination(false), + mCustomDestination() {} void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index ae345d187f..c6d093d5f7 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -802,4 +802,26 @@ namespace EXPECT_GT(duration, mSettings.mMinUpdateInterval) << std::chrono::duration_cast>(duration).count() << " ms"; } + + TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + const auto result = mNavigator->raycast(mAgentHalfExtents, mStart, mEnd, Flag_walk); + + ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.87719))); + } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 832fc611f6..44813180e5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -181,6 +181,7 @@ add_component_dir(detournavigator settings navigator findrandompointaroundcircle + raycast ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 658e539ad9..700217c52f 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -1,5 +1,6 @@ #include "findrandompointaroundcircle.hpp" #include "navigator.hpp" +#include "raycast.hpp" namespace DetourNavigator { @@ -17,4 +18,19 @@ namespace DetourNavigator return std::optional(); return std::optional(fromNavMeshCoordinates(settings, *result)); } + + std::optional Navigator::raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, + const osg::Vec3f& end, const Flags includeFlags) const + { + const auto navMesh = getNavMesh(agentHalfExtents); + if (navMesh == nullptr) + return {}; + const auto settings = getSettings(); + const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), + toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), + toNavMeshCoordinates(settings, end), includeFlags, settings); + if (!result) + return {}; + return fromNavMeshCoordinates(settings, *result); + } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index a79aa59d42..ef61f78c68 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -223,6 +223,17 @@ namespace DetourNavigator std::optional findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const; + /** + * @brief raycast finds farest navmesh point from start on a line from start to end that has path from start. + * @param agentHalfExtents allows to find navmesh for given actor. + * @param start of the line + * @param end of the line + * @param includeFlags setup allowed surfaces for actor to walk. + * @return not empty optional with position if point is found and empty optional if point is not found. + */ + std::optional raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, + const osg::Vec3f& end, const Flags includeFlags) const; + virtual RecastMeshTiles getRecastMeshTiles() = 0; }; } diff --git a/components/detournavigator/raycast.cpp b/components/detournavigator/raycast.cpp new file mode 100644 index 0000000000..86fabe9c1f --- /dev/null +++ b/components/detournavigator/raycast.cpp @@ -0,0 +1,44 @@ +#include "raycast.hpp" +#include "settings.hpp" +#include "findsmoothpath.hpp" + +#include +#include +#include + +#include + +namespace DetourNavigator +{ + std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings) + { + dtNavMeshQuery navMeshQuery; + if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) + return {}; + + dtQueryFilter queryFilter; + queryFilter.setIncludeFlags(includeFlags); + + dtPolyRef ref = 0; + if (dtStatus status = navMeshQuery.findNearestPoly(start.ptr(), halfExtents.ptr(), &queryFilter, &ref, nullptr); + dtStatusFailed(status) || ref == 0) + return {}; + + const unsigned options = 0; + std::array path; + dtRaycastHit hit; + hit.path = path.data(); + hit.maxPath = path.size(); + if (dtStatus status = navMeshQuery.raycast(ref, start.ptr(), end.ptr(), &queryFilter, options, &hit); + dtStatusFailed(status) || hit.pathCount == 0) + return {}; + + osg::Vec3f hitPosition; + if (dtStatus status = navMeshQuery.closestPointOnPoly(path[hit.pathCount - 1], end.ptr(), hitPosition.ptr(), nullptr); + dtStatusFailed(status)) + return {}; + + return hitPosition; + } +} diff --git a/components/detournavigator/raycast.hpp b/components/detournavigator/raycast.hpp new file mode 100644 index 0000000000..ddf61b49f4 --- /dev/null +++ b/components/detournavigator/raycast.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H + +#include "flags.hpp" + +#include +#include + +class dtNavMesh; + +namespace DetourNavigator +{ + struct Settings; + + std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings); +} + +#endif From e7afbc74d82351e59e671343f2a9f40a7603c4e5 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 27 Jan 2021 20:57:11 +0100 Subject: [PATCH 0356/2859] Wizard: Only allow to select Morrowind.esm master files, display an error if Morrowind.bsa is missing --- apps/wizard/existinginstallationpage.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 2434f42cfe..886fcd95db 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -95,9 +95,9 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() { QString selectedFile = QFileDialog::getOpenFileName( this, - tr("Select master file"), + tr("Select Morrowind.esm (located in Data Files)"), QDir::currentPath(), - QString(tr("Morrowind master file (*.esm)")), + QString(tr("Morrowind master file (Morrowind.esm)")), nullptr, QFileDialog::DontResolveSymlinks); @@ -110,7 +110,18 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() return; if (!mWizard->findFiles(QLatin1String("Morrowind"), info.absolutePath())) - return; // No valid Morrowind installation found + { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error detecting Morrowind files")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr( + "Morrowind.bsa is missing!
\ + Make sure your Morrowind installation is complete." + )); + msgBox.exec(); + return; + } QString path(QDir::toNativeSeparators(info.absolutePath())); QList items = installationsList->findItems(path, Qt::MatchExactly); From 70a059d4bfaefc128e9d0844a4370df7c6e402e7 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 Feb 2021 16:31:16 +0000 Subject: [PATCH 0357/2859] Use different filenames for Engine and CS packages --- .gitlab-ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6bfe0bf23a..823a92cc2f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -89,9 +89,11 @@ MacOS: variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" + package: "Engine" variables: &cs-targets targets: "openmw-cs,bsatool,esmtool,niftest" + package: "CS" .Windows_Ninja_Base: tags: @@ -119,10 +121,10 @@ variables: &cs-targets - 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 - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt + 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: @@ -210,10 +212,10 @@ Windows_Ninja_CS_RelWithDebInfo: - 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 - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt + 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: From 6291b9304b98945b571cd9dcf87f129921df8648 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 Feb 2021 16:56:21 +0000 Subject: [PATCH 0358/2859] Log when icon is missing and fallback is used --- apps/openmw/mwgui/itemwidget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 7c7fa88287..d2dfa827b4 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -5,6 +5,7 @@ #include #include +#include // correctIconPath #include #include @@ -111,7 +112,10 @@ namespace MWGui invIcon = "default icon.tga"; invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon); if (!MWBase::Environment::get().getResourceSystem()->getVFS()->exists(invIcon)) + { + Log(Debug::Error) << "Failed to open image: '" << invIcon << "' not found, falling back to 'default-icon.tga'"; invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath("default icon.tga"); + } setIcon(invIcon); } From d3ab6c972fe2876e889ddabba81d38884e3526ad Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Feb 2021 01:00:43 +0100 Subject: [PATCH 0359/2859] Avoid set unused position from dtNavMeshQuery::findNearestPoly result --- components/detournavigator/findrandompointaroundcircle.cpp | 3 +-- components/detournavigator/findsmoothpath.hpp | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index f2e815c918..444050ed7c 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -21,11 +21,10 @@ namespace DetourNavigator queryFilter.setIncludeFlags(includeFlags); dtPolyRef startRef = 0; - osg::Vec3f startPolygonPosition; for (int i = 0; i < 3; ++i) { const auto status = navMeshQuery.findNearestPoly(start.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter, - &startRef, startPolygonPosition.ptr()); + &startRef, nullptr); if (!dtStatusFailed(status) && startRef != 0) break; } diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index a351f8279c..f757a511f4 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -283,11 +283,10 @@ namespace DetourNavigator queryFilter.setAreaCost(AreaType_ground, areaCosts.mGround); dtPolyRef startRef = 0; - osg::Vec3f startPolygonPosition; for (int i = 0; i < 3; ++i) { const auto status = navMeshQuery.findNearestPoly(start.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter, - &startRef, startPolygonPosition.ptr()); + &startRef, nullptr); if (!dtStatusFailed(status) && startRef != 0) break; } @@ -296,11 +295,10 @@ namespace DetourNavigator return Status::StartPolygonNotFound; dtPolyRef endRef = 0; - osg::Vec3f endPolygonPosition; for (int i = 0; i < 3; ++i) { const auto status = navMeshQuery.findNearestPoly(end.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter, - &endRef, endPolygonPosition.ptr()); + &endRef, nullptr); if (!dtStatusFailed(status) && endRef != 0) break; } From a7fe6c7ba16ea16775d9324fa00cc346f07c4959 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Feb 2021 01:08:14 +0100 Subject: [PATCH 0360/2859] Move duplicated usage patter of dtNavMeshQuery::findNearestPoly into a separate function --- .../findrandompointaroundcircle.cpp | 10 +------- components/detournavigator/findsmoothpath.cpp | 13 +++++++++++ components/detournavigator/findsmoothpath.hpp | 23 ++++--------------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index 444050ed7c..29d4e9da44 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -20,15 +20,7 @@ namespace DetourNavigator dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); - dtPolyRef startRef = 0; - for (int i = 0; i < 3; ++i) - { - const auto status = navMeshQuery.findNearestPoly(start.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter, - &startRef, nullptr); - if (!dtStatusFailed(status) && startRef != 0) - break; - } - + dtPolyRef startRef = findNearestPolyExpanding(navMeshQuery, queryFilter, start, halfExtents); if (startRef == 0) return std::optional(); diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp index a13b83abd8..84ccf34a44 100644 --- a/components/detournavigator/findsmoothpath.cpp +++ b/components/detournavigator/findsmoothpath.cpp @@ -140,4 +140,17 @@ namespace DetourNavigator return result; } + + dtPolyRef findNearestPolyExpanding(const dtNavMeshQuery& query, const dtQueryFilter& filter, + const osg::Vec3f& center, const osg::Vec3f& halfExtents) + { + dtPolyRef ref = 0; + for (int i = 0; i < 3; ++i) + { + const dtStatus status = query.findNearestPoly(center.ptr(), (halfExtents * (1 << i)).ptr(), &filter, &ref, nullptr); + if (!dtStatusFailed(status) && ref != 0) + break; + } + return ref; + } } diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index f757a511f4..3dc4999740 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -103,6 +103,9 @@ namespace DetourNavigator return dtStatusSucceed(status); } + dtPolyRef findNearestPolyExpanding(const dtNavMeshQuery& query, const dtQueryFilter& filter, + const osg::Vec3f& center, const osg::Vec3f& halfExtents); + struct MoveAlongSurfaceResult { osg::Vec3f mResultPos; @@ -282,27 +285,11 @@ namespace DetourNavigator queryFilter.setAreaCost(AreaType_pathgrid, areaCosts.mPathgrid); queryFilter.setAreaCost(AreaType_ground, areaCosts.mGround); - dtPolyRef startRef = 0; - for (int i = 0; i < 3; ++i) - { - const auto status = navMeshQuery.findNearestPoly(start.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter, - &startRef, nullptr); - if (!dtStatusFailed(status) && startRef != 0) - break; - } - + dtPolyRef startRef = findNearestPolyExpanding(navMeshQuery, queryFilter, start, halfExtents); if (startRef == 0) return Status::StartPolygonNotFound; - dtPolyRef endRef = 0; - for (int i = 0; i < 3; ++i) - { - const auto status = navMeshQuery.findNearestPoly(end.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter, - &endRef, nullptr); - if (!dtStatusFailed(status) && endRef != 0) - break; - } - + dtPolyRef endRef = findNearestPolyExpanding(navMeshQuery, queryFilter, end, halfExtents); if (endRef == 0) return Status::EndPolygonNotFound; From 3a9b1ce63a365d8e759f51b4e156303728c79cd5 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Feb 2021 23:11:57 +0100 Subject: [PATCH 0361/2859] Use camel case for local constant --- components/detournavigator/findsmoothpath.cpp | 10 +++++----- components/detournavigator/findsmoothpath.hpp | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp index 84ccf34a44..8974be5321 100644 --- a/components/detournavigator/findsmoothpath.cpp +++ b/components/detournavigator/findsmoothpath.cpp @@ -108,13 +108,13 @@ namespace DetourNavigator { // Find steer target. SteerTarget result; - const int MAX_STEER_POINTS = 3; - std::array steerPath; - std::array steerPathFlags; - std::array steerPathPolys; + constexpr int maxSteerPoints = 3; + std::array steerPath; + std::array steerPathFlags; + std::array steerPathPolys; int nsteerPath = 0; navQuery.findStraightPath(startPos.ptr(), endPos.ptr(), path.data(), int(path.size()), steerPath.data(), - steerPathFlags.data(), steerPathPolys.data(), &nsteerPath, MAX_STEER_POINTS); + steerPathFlags.data(), steerPathPolys.data(), &nsteerPath, maxSteerPoints); assert(nsteerPath >= 0); if (!nsteerPath) return std::nullopt; diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 3dc4999740..29a3ce8050 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -166,7 +166,7 @@ namespace DetourNavigator osg::Vec3f targetPos; navMeshQuery.closestPointOnPoly(polygonPath.back(), end.ptr(), targetPos.ptr(), nullptr); - const float SLOP = 0.01f; + constexpr float slop = 0.01f; *out++ = iterPos; @@ -177,7 +177,7 @@ namespace DetourNavigator while (!polygonPath.empty() && smoothPathSize < maxSmoothPathSize) { // Find location to steer towards. - const auto steerTarget = getSteerTarget(navMeshQuery, iterPos, targetPos, SLOP, polygonPath); + const auto steerTarget = getSteerTarget(navMeshQuery, iterPos, targetPos, slop, polygonPath); if (!steerTarget) break; @@ -209,7 +209,7 @@ namespace DetourNavigator iterPos.y() = h; // Handle end of path and off-mesh links when close enough. - if (endOfPath && inRange(iterPos, steerTarget->steerPos, SLOP, 1.0f)) + if (endOfPath && inRange(iterPos, steerTarget->steerPos, slop, 1.0f)) { // Reached end of path. iterPos = targetPos; @@ -217,7 +217,7 @@ namespace DetourNavigator ++smoothPathSize; break; } - else if (offMeshConnection && inRange(iterPos, steerTarget->steerPos, SLOP, 1.0f)) + else if (offMeshConnection && inRange(iterPos, steerTarget->steerPos, slop, 1.0f)) { // Advance the path up to and over the off-mesh connection. dtPolyRef prevRef = 0; From bb0c4789540c4da23da1975473df288bb3d2d7a1 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Feb 2021 00:01:34 +0100 Subject: [PATCH 0362/2859] Add missing include and use std malloc and free --- components/detournavigator/recastglobalallocator.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/recastglobalallocator.hpp b/components/detournavigator/recastglobalallocator.hpp index 7c4b2c5343..313d311009 100644 --- a/components/detournavigator/recastglobalallocator.hpp +++ b/components/detournavigator/recastglobalallocator.hpp @@ -3,6 +3,8 @@ #include "recasttempallocator.hpp" +#include + namespace DetourNavigator { class RecastGlobalAllocator @@ -32,7 +34,7 @@ namespace DetourNavigator else { assert(BufferType_perm == getDataPtrBufferType(ptr)); - ::free(getPermDataPtrHeapPtr(ptr)); + std::free(getPermDataPtrHeapPtr(ptr)); } } @@ -56,7 +58,7 @@ namespace DetourNavigator static void* allocPerm(size_t size) { - const auto ptr = ::malloc(size + sizeof(std::size_t)); + const auto ptr = std::malloc(size + sizeof(std::size_t)); if (rcUnlikely(!ptr)) return ptr; setPermPtrBufferType(ptr, BufferType_perm); From 4983684fda3266ca3308a13b5e7668fd63f24f0f Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Feb 2021 00:07:20 +0100 Subject: [PATCH 0363/2859] Fix implicit int to float conversion warning --- components/detournavigator/settingsutils.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index a22205b2aa..39ffc03d19 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -52,7 +52,7 @@ namespace DetourNavigator inline float getTileSize(const Settings& settings) { - return settings.mTileSize * settings.mCellSize; + return static_cast(settings.mTileSize) * settings.mCellSize; } inline TilePosition getTilePosition(const Settings& settings, const osg::Vec3f& position) @@ -73,7 +73,7 @@ namespace DetourNavigator inline float getBorderSize(const Settings& settings) { - return settings.mBorderSize * settings.mCellSize; + return static_cast(settings.mBorderSize) * settings.mCellSize; } inline float getSwimLevel(const Settings& settings, const float agentHalfExtentsZ) From bc67669a970df2e58e3571f2b9fa4d8050434090 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Feb 2021 00:22:48 +0100 Subject: [PATCH 0364/2859] Comment unused argument --- components/detournavigator/navigatorstub.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 8b81bde197..f1f9e06ef3 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -66,7 +66,7 @@ namespace DetourNavigator void update(const osg::Vec3f& /*playerPosition*/) override {} - void setUpdatesEnabled(bool enabled) override {} + void setUpdatesEnabled(bool /*enabled*/) override {} void wait() override {} From b8ee32e3173c9226d92cf0d9f60a07fe1c75a479 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 Feb 2021 23:32:04 +0000 Subject: [PATCH 0365/2859] Support A2C for groundcover --- files/shaders/groundcover_fragment.glsl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 77fd32e58b..c3339ba763 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -30,11 +30,7 @@ centroid varying vec3 shadowDiffuseLighting; #include "shadows_fragment.glsl" #include "lighting.glsl" - -float calc_coverage(float a, float alpha_ref, float falloff_rate) -{ - return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0); -} +#include "alpha.glsl" void main() { @@ -55,12 +51,13 @@ void main() gl_FragData[0] = vec4(1.0); #endif - gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0); - - float shadowing = unshadowedLightRatio(linearDepth); if (euclideanDepth > @groundcoverFadeStart) gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth); + alphaTest(); + + float shadowing = unshadowedLightRatio(linearDepth); + vec3 lighting; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; From 1c9245bd58a07f6e3709b4136c934f441c1272f7 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 24 Jan 2021 01:43:43 +0000 Subject: [PATCH 0366/2859] Move recastnavigation to FetchContent --- CMakeLists.txt | 10 +- extern/CMakeLists.txt | 21 + extern/recastnavigation/.editorconfig | 12 - extern/recastnavigation/.gitignore | 49 - extern/recastnavigation/.id | 1 - extern/recastnavigation/.url | 1 - extern/recastnavigation/CMakeLists.txt | 26 - .../DebugUtils/CMakeLists.txt | 36 - .../DebugUtils/Include/DebugDraw.h | 223 - .../DebugUtils/Include/DetourDebugDraw.h | 48 - .../DebugUtils/Include/RecastDebugDraw.h | 42 - .../DebugUtils/Include/RecastDump.h | 43 - .../DebugUtils/Source/DebugDraw.cpp | 612 --- .../DebugUtils/Source/DetourDebugDraw.cpp | 864 ---- .../DebugUtils/Source/RecastDebugDraw.cpp | 1064 ----- .../DebugUtils/Source/RecastDump.cpp | 451 -- extern/recastnavigation/Detour/CMakeLists.txt | 30 - .../Detour/Include/DetourAlloc.h | 61 - .../Detour/Include/DetourAssert.h | 56 - .../Detour/Include/DetourCommon.h | 572 --- .../Detour/Include/DetourMath.h | 24 - .../Detour/Include/DetourNavMesh.h | 785 ---- .../Detour/Include/DetourNavMeshBuilder.h | 149 - .../Detour/Include/DetourNavMeshQuery.h | 573 --- .../Detour/Include/DetourNode.h | 168 - .../Detour/Include/DetourStatus.h | 65 - .../Detour/Source/DetourAlloc.cpp | 50 - .../Detour/Source/DetourAssert.cpp | 35 - .../Detour/Source/DetourCommon.cpp | 387 -- .../Detour/Source/DetourNavMesh.cpp | 1584 ------- .../Detour/Source/DetourNavMeshBuilder.cpp | 802 ---- .../Detour/Source/DetourNavMeshQuery.cpp | 3663 ----------------- .../Detour/Source/DetourNode.cpp | 200 - .../DetourCrowd/CMakeLists.txt | 34 - .../DetourCrowd/Include/DetourCrowd.h | 460 --- .../DetourCrowd/Include/DetourLocalBoundary.h | 66 - .../Include/DetourObstacleAvoidance.h | 159 - .../DetourCrowd/Include/DetourPathCorridor.h | 151 - .../DetourCrowd/Include/DetourPathQueue.h | 79 - .../DetourCrowd/Include/DetourProximityGrid.h | 74 - .../DetourCrowd/Source/DetourCrowd.cpp | 1450 ------- .../Source/DetourLocalBoundary.cpp | 137 - .../Source/DetourObstacleAvoidance.cpp | 619 --- .../DetourCrowd/Source/DetourPathCorridor.cpp | 597 --- .../DetourCrowd/Source/DetourPathQueue.cpp | 200 - .../Source/DetourProximityGrid.cpp | 194 - .../DetourTileCache/CMakeLists.txt | 35 - .../DetourTileCache/Include/DetourTileCache.h | 262 -- .../Include/DetourTileCacheBuilder.h | 156 - .../Source/DetourTileCache.cpp | 820 ---- .../Source/DetourTileCacheBuilder.cpp | 2250 ---------- extern/recastnavigation/License.txt | 18 - extern/recastnavigation/Recast/CMakeLists.txt | 30 - .../recastnavigation/Recast/Include/Recast.h | 1208 ------ .../Recast/Include/RecastAlloc.h | 342 -- .../Recast/Include/RecastAssert.h | 56 - .../recastnavigation/Recast/Source/Recast.cpp | 575 --- .../Recast/Source/RecastAlloc.cpp | 60 - .../Recast/Source/RecastArea.cpp | 591 --- .../Recast/Source/RecastAssert.cpp | 35 - .../Recast/Source/RecastContour.cpp | 1105 ----- .../Recast/Source/RecastFilter.cpp | 202 - .../Recast/Source/RecastLayers.cpp | 644 --- .../Recast/Source/RecastMesh.cpp | 1552 ------- .../Recast/Source/RecastMeshDetail.cpp | 1464 ------- .../Recast/Source/RecastRasterization.cpp | 454 -- .../Recast/Source/RecastRegion.cpp | 1812 -------- 67 files changed, 29 insertions(+), 30569 deletions(-) delete mode 100644 extern/recastnavigation/.editorconfig delete mode 100644 extern/recastnavigation/.gitignore delete mode 100644 extern/recastnavigation/.id delete mode 100644 extern/recastnavigation/.url delete mode 100644 extern/recastnavigation/CMakeLists.txt delete mode 100644 extern/recastnavigation/DebugUtils/CMakeLists.txt delete mode 100644 extern/recastnavigation/DebugUtils/Include/DebugDraw.h delete mode 100755 extern/recastnavigation/DebugUtils/Include/DetourDebugDraw.h delete mode 100644 extern/recastnavigation/DebugUtils/Include/RecastDebugDraw.h delete mode 100644 extern/recastnavigation/DebugUtils/Include/RecastDump.h delete mode 100644 extern/recastnavigation/DebugUtils/Source/DebugDraw.cpp delete mode 100644 extern/recastnavigation/DebugUtils/Source/DetourDebugDraw.cpp delete mode 100644 extern/recastnavigation/DebugUtils/Source/RecastDebugDraw.cpp delete mode 100644 extern/recastnavigation/DebugUtils/Source/RecastDump.cpp delete mode 100644 extern/recastnavigation/Detour/CMakeLists.txt delete mode 100644 extern/recastnavigation/Detour/Include/DetourAlloc.h delete mode 100644 extern/recastnavigation/Detour/Include/DetourAssert.h delete mode 100644 extern/recastnavigation/Detour/Include/DetourCommon.h delete mode 100644 extern/recastnavigation/Detour/Include/DetourMath.h delete mode 100644 extern/recastnavigation/Detour/Include/DetourNavMesh.h delete mode 100644 extern/recastnavigation/Detour/Include/DetourNavMeshBuilder.h delete mode 100644 extern/recastnavigation/Detour/Include/DetourNavMeshQuery.h delete mode 100644 extern/recastnavigation/Detour/Include/DetourNode.h delete mode 100644 extern/recastnavigation/Detour/Include/DetourStatus.h delete mode 100644 extern/recastnavigation/Detour/Source/DetourAlloc.cpp delete mode 100644 extern/recastnavigation/Detour/Source/DetourAssert.cpp delete mode 100644 extern/recastnavigation/Detour/Source/DetourCommon.cpp delete mode 100644 extern/recastnavigation/Detour/Source/DetourNavMesh.cpp delete mode 100644 extern/recastnavigation/Detour/Source/DetourNavMeshBuilder.cpp delete mode 100644 extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp delete mode 100644 extern/recastnavigation/Detour/Source/DetourNode.cpp delete mode 100644 extern/recastnavigation/DetourCrowd/CMakeLists.txt delete mode 100644 extern/recastnavigation/DetourCrowd/Include/DetourCrowd.h delete mode 100644 extern/recastnavigation/DetourCrowd/Include/DetourLocalBoundary.h delete mode 100644 extern/recastnavigation/DetourCrowd/Include/DetourObstacleAvoidance.h delete mode 100644 extern/recastnavigation/DetourCrowd/Include/DetourPathCorridor.h delete mode 100644 extern/recastnavigation/DetourCrowd/Include/DetourPathQueue.h delete mode 100644 extern/recastnavigation/DetourCrowd/Include/DetourProximityGrid.h delete mode 100644 extern/recastnavigation/DetourCrowd/Source/DetourCrowd.cpp delete mode 100644 extern/recastnavigation/DetourCrowd/Source/DetourLocalBoundary.cpp delete mode 100644 extern/recastnavigation/DetourCrowd/Source/DetourObstacleAvoidance.cpp delete mode 100644 extern/recastnavigation/DetourCrowd/Source/DetourPathCorridor.cpp delete mode 100644 extern/recastnavigation/DetourCrowd/Source/DetourPathQueue.cpp delete mode 100644 extern/recastnavigation/DetourCrowd/Source/DetourProximityGrid.cpp delete mode 100644 extern/recastnavigation/DetourTileCache/CMakeLists.txt delete mode 100644 extern/recastnavigation/DetourTileCache/Include/DetourTileCache.h delete mode 100644 extern/recastnavigation/DetourTileCache/Include/DetourTileCacheBuilder.h delete mode 100644 extern/recastnavigation/DetourTileCache/Source/DetourTileCache.cpp delete mode 100644 extern/recastnavigation/DetourTileCache/Source/DetourTileCacheBuilder.cpp delete mode 100644 extern/recastnavigation/License.txt delete mode 100644 extern/recastnavigation/Recast/CMakeLists.txt delete mode 100644 extern/recastnavigation/Recast/Include/Recast.h delete mode 100644 extern/recastnavigation/Recast/Include/RecastAlloc.h delete mode 100644 extern/recastnavigation/Recast/Include/RecastAssert.h delete mode 100644 extern/recastnavigation/Recast/Source/Recast.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastAlloc.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastArea.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastAssert.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastContour.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastFilter.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastLayers.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastMesh.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastMeshDetail.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastRasterization.cpp delete mode 100644 extern/recastnavigation/Recast/Source/RecastRegion.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b02ec055d2..1a7412253b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,14 @@ else() endif() option(MYGUI_STATIC "Link static build of Mygui into the binaries" ${_mygui_static_default}) +option(OPENMW_USE_SYSTEM_RECASTNAVIGATION "Use system provided recastnavigation library" OFF) +if(OPENMW_USE_SYSTEM_RECASTNAVIGATION) + set(_recastnavigation_static_default OFF) +else() + set(_recastnavigation_static_default ON) +endif() +option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_recastnavigation_static_default}) + option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) @@ -482,9 +490,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) # Extern -set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries") -add_subdirectory (extern/recastnavigation EXCLUDE_FROM_ALL) add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) if (BUILD_OPENCS) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index fd21b70722..b4182a1053 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -129,3 +129,24 @@ if(NOT OPENMW_USE_SYSTEM_OSG) set(${_name_uc}_LIBRARY ${_name} PARENT_SCOPE) endforeach() endif() + +if(NOT OPENMW_USE_SYSTEM_RECASTNAVIGATION) + if(RECASTNAVIGATION_STATIC) + set(BUILD_SHARED_LIBS OFF) + else() + set(BUILD_SHARED_LIBS ON) + endif() + + set(RECASTNAVIGATION_DEMO OFF CACHE BOOL "") + set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "") + set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "") + + # master on 15 Feb 2021 + include(FetchContent) + FetchContent_Declare(recastnavigation + URL https://github.com/recastnavigation/recastnavigation/archive/e75adf86f91eb3082220085e42dda62679f9a3ea.zip + URL_HASH MD5=af905d121ef9d1cdfa979b0495cba059 + SOURCE_DIR fetched/recastnavigation + ) + FetchContent_MakeAvailableExcludeFromAll(recastnavigation) +endif() diff --git a/extern/recastnavigation/.editorconfig b/extern/recastnavigation/.editorconfig deleted file mode 100644 index 08f28f4417..0000000000 --- a/extern/recastnavigation/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -# editorconfig.org - -# top-most EditorConfig file -root = true - -[*] -indent_size = 4 -indent_style = tab - -[*.yml] -indent_size = 2 -indent_style = space diff --git a/extern/recastnavigation/.gitignore b/extern/recastnavigation/.gitignore deleted file mode 100644 index 98f17e4b73..0000000000 --- a/extern/recastnavigation/.gitignore +++ /dev/null @@ -1,49 +0,0 @@ -## Compiled source # -*.com -*.class -*.dll -*.exe -*.ilk -*.o -*.pdb -*.so -*.idb - -## Linux exes have no extension -RecastDemo/Bin/RecastDemo -RecastDemo/Bin/Tests - -# Build directory -RecastDemo/Build - -# Ignore meshes -RecastDemo/Bin/Meshes/* - -## Logs and databases # -*.log -*.sql -*.sqlite - -## OS generated files # -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db -*.swp -*.swo - -## xcode specific -*xcuserdata* - -## SDL contrib -RecastDemo/Contrib/SDL/* - -## Generated doc files -Docs/html - -## IDE files -.idea/ -cmake-build-*/ diff --git a/extern/recastnavigation/.id b/extern/recastnavigation/.id deleted file mode 100644 index b53727263d..0000000000 --- a/extern/recastnavigation/.id +++ /dev/null @@ -1 +0,0 @@ -6624e7aef5e15df11cb2f5673574df8e4c96af6a diff --git a/extern/recastnavigation/.url b/extern/recastnavigation/.url deleted file mode 100644 index b38b1645f3..0000000000 --- a/extern/recastnavigation/.url +++ /dev/null @@ -1 +0,0 @@ -https://github.com/recastnavigation/recastnavigation.git diff --git a/extern/recastnavigation/CMakeLists.txt b/extern/recastnavigation/CMakeLists.txt deleted file mode 100644 index cf35af1e87..0000000000 --- a/extern/recastnavigation/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -# for link time optimization, remove if cmake version is >= 3.9 -if(POLICY CMP0069) - cmake_policy(SET CMP0069 NEW) -endif() - -project(RecastNavigation) - -# lib versions -SET(SOVERSION 1) -SET(VERSION 1.0.0) - -option(RECASTNAVIGATION_STATIC "Build static libraries" ON) - -if(MSVC AND BUILD_SHARED_LIBS) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) -endif() - -include(GNUInstallDirs) - -add_subdirectory(DebugUtils) -add_subdirectory(Detour) -add_subdirectory(DetourCrowd) -add_subdirectory(DetourTileCache) -add_subdirectory(Recast) diff --git a/extern/recastnavigation/DebugUtils/CMakeLists.txt b/extern/recastnavigation/DebugUtils/CMakeLists.txt deleted file mode 100644 index 21d8f8f9d9..0000000000 --- a/extern/recastnavigation/DebugUtils/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -file(GLOB SOURCES Source/*.cpp) -add_library(DebugUtils ${SOURCES}) - -add_library(RecastNavigation::DebugUtils ALIAS DebugUtils) -set_target_properties(DebugUtils PROPERTIES DEBUG_POSTFIX -d) - -set(DebugUtils_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") - -target_include_directories(DebugUtils PUBLIC - "$" -) - -target_link_libraries(DebugUtils - Recast - Detour - DetourTileCache -) - -set_target_properties(DebugUtils PROPERTIES - SOVERSION ${SOVERSION} - VERSION ${VERSION} - COMPILE_PDB_OUTPUT_DIRECTORY . - COMPILE_PDB_NAME "DebugUtils-d" - ) - -install(TARGETS DebugUtils - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT library - ) - -file(GLOB INCLUDES Include/*.h) -install(FILES ${INCLUDES} DESTINATION - ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) -install(FILES "$/DebugUtils-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") diff --git a/extern/recastnavigation/DebugUtils/Include/DebugDraw.h b/extern/recastnavigation/DebugUtils/Include/DebugDraw.h deleted file mode 100644 index f47df0b7bf..0000000000 --- a/extern/recastnavigation/DebugUtils/Include/DebugDraw.h +++ /dev/null @@ -1,223 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DEBUGDRAW_H -#define DEBUGDRAW_H - -// Some math headers don't have PI defined. -static const float DU_PI = 3.14159265f; - -enum duDebugDrawPrimitives -{ - DU_DRAW_POINTS, - DU_DRAW_LINES, - DU_DRAW_TRIS, - DU_DRAW_QUADS, -}; - -/// Abstract debug draw interface. -struct duDebugDraw -{ - virtual ~duDebugDraw() = 0; - - virtual void depthMask(bool state) = 0; - - virtual void texture(bool state) = 0; - - /// Begin drawing primitives. - /// @param prim [in] primitive type to draw, one of rcDebugDrawPrimitives. - /// @param size [in] size of a primitive, applies to point size and line width only. - virtual void begin(duDebugDrawPrimitives prim, float size = 1.0f) = 0; - - /// Submit a vertex - /// @param pos [in] position of the verts. - /// @param color [in] color of the verts. - virtual void vertex(const float* pos, unsigned int color) = 0; - - /// Submit a vertex - /// @param x,y,z [in] position of the verts. - /// @param color [in] color of the verts. - virtual void vertex(const float x, const float y, const float z, unsigned int color) = 0; - - /// Submit a vertex - /// @param pos [in] position of the verts. - /// @param color [in] color of the verts. - virtual void vertex(const float* pos, unsigned int color, const float* uv) = 0; - - /// Submit a vertex - /// @param x,y,z [in] position of the verts. - /// @param color [in] color of the verts. - virtual void vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v) = 0; - - /// End drawing primitives. - virtual void end() = 0; - - /// Compute a color for given area. - virtual unsigned int areaToCol(unsigned int area); -}; - -inline unsigned int duRGBA(int r, int g, int b, int a) -{ - return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24); -} - -inline unsigned int duRGBAf(float fr, float fg, float fb, float fa) -{ - unsigned char r = (unsigned char)(fr*255.0f); - unsigned char g = (unsigned char)(fg*255.0f); - unsigned char b = (unsigned char)(fb*255.0f); - unsigned char a = (unsigned char)(fa*255.0f); - return duRGBA(r,g,b,a); -} - -unsigned int duIntToCol(int i, int a); -void duIntToCol(int i, float* col); - -inline unsigned int duMultCol(const unsigned int col, const unsigned int d) -{ - const unsigned int r = col & 0xff; - const unsigned int g = (col >> 8) & 0xff; - const unsigned int b = (col >> 16) & 0xff; - const unsigned int a = (col >> 24) & 0xff; - return duRGBA((r*d) >> 8, (g*d) >> 8, (b*d) >> 8, a); -} - -inline unsigned int duDarkenCol(unsigned int col) -{ - return ((col >> 1) & 0x007f7f7f) | (col & 0xff000000); -} - -inline unsigned int duLerpCol(unsigned int ca, unsigned int cb, unsigned int u) -{ - const unsigned int ra = ca & 0xff; - const unsigned int ga = (ca >> 8) & 0xff; - const unsigned int ba = (ca >> 16) & 0xff; - const unsigned int aa = (ca >> 24) & 0xff; - const unsigned int rb = cb & 0xff; - const unsigned int gb = (cb >> 8) & 0xff; - const unsigned int bb = (cb >> 16) & 0xff; - const unsigned int ab = (cb >> 24) & 0xff; - - unsigned int r = (ra*(255-u) + rb*u)/255; - unsigned int g = (ga*(255-u) + gb*u)/255; - unsigned int b = (ba*(255-u) + bb*u)/255; - unsigned int a = (aa*(255-u) + ab*u)/255; - return duRGBA(r,g,b,a); -} - -inline unsigned int duTransCol(unsigned int c, unsigned int a) -{ - return (a<<24) | (c & 0x00ffffff); -} - - -void duCalcBoxColors(unsigned int* colors, unsigned int colTop, unsigned int colSide); - -void duDebugDrawCylinderWire(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col, const float lineWidth); - -void duDebugDrawBoxWire(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col, const float lineWidth); - -void duDebugDrawArc(struct duDebugDraw* dd, const float x0, const float y0, const float z0, - const float x1, const float y1, const float z1, const float h, - const float as0, const float as1, unsigned int col, const float lineWidth); - -void duDebugDrawArrow(struct duDebugDraw* dd, const float x0, const float y0, const float z0, - const float x1, const float y1, const float z1, - const float as0, const float as1, unsigned int col, const float lineWidth); - -void duDebugDrawCircle(struct duDebugDraw* dd, const float x, const float y, const float z, - const float r, unsigned int col, const float lineWidth); - -void duDebugDrawCross(struct duDebugDraw* dd, const float x, const float y, const float z, - const float size, unsigned int col, const float lineWidth); - -void duDebugDrawBox(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, const unsigned int* fcol); - -void duDebugDrawCylinder(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col); - -void duDebugDrawGridXZ(struct duDebugDraw* dd, const float ox, const float oy, const float oz, - const int w, const int h, const float size, - const unsigned int col, const float lineWidth); - - -// Versions without begin/end, can be used to draw multiple primitives. -void duAppendCylinderWire(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col); - -void duAppendBoxWire(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col); - -void duAppendBoxPoints(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col); - -void duAppendArc(struct duDebugDraw* dd, const float x0, const float y0, const float z0, - const float x1, const float y1, const float z1, const float h, - const float as0, const float as1, unsigned int col); - -void duAppendArrow(struct duDebugDraw* dd, const float x0, const float y0, const float z0, - const float x1, const float y1, const float z1, - const float as0, const float as1, unsigned int col); - -void duAppendCircle(struct duDebugDraw* dd, const float x, const float y, const float z, - const float r, unsigned int col); - -void duAppendCross(struct duDebugDraw* dd, const float x, const float y, const float z, - const float size, unsigned int col); - -void duAppendBox(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, const unsigned int* fcol); - -void duAppendCylinder(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col); - - -class duDisplayList : public duDebugDraw -{ - float* m_pos; - unsigned int* m_color; - int m_size; - int m_cap; - - bool m_depthMask; - duDebugDrawPrimitives m_prim; - float m_primSize; - - void resize(int cap); - -public: - duDisplayList(int cap = 512); - ~duDisplayList(); - void depthMask(bool state) override; - void begin(duDebugDrawPrimitives prim, float size = 1.0f) override; - void vertex(const float x, const float y, const float z, unsigned int color) override; - void vertex(const float* pos, unsigned int color) override; - void end() override; - void clear(); - void draw(struct duDebugDraw* dd); -private: - // Explicitly disabled copy constructor and copy assignment operator. - duDisplayList(const duDisplayList&); - duDisplayList& operator=(const duDisplayList&); -}; - - -#endif // DEBUGDRAW_H diff --git a/extern/recastnavigation/DebugUtils/Include/DetourDebugDraw.h b/extern/recastnavigation/DebugUtils/Include/DetourDebugDraw.h deleted file mode 100755 index ff2ca2f9d1..0000000000 --- a/extern/recastnavigation/DebugUtils/Include/DetourDebugDraw.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURDEBUGDRAW_H -#define DETOURDEBUGDRAW_H - -#include "DetourNavMesh.h" -#include "DetourNavMeshQuery.h" -#include "DetourTileCacheBuilder.h" - -enum DrawNavMeshFlags -{ - DU_DRAWNAVMESH_OFFMESHCONS = 0x01, - DU_DRAWNAVMESH_CLOSEDLIST = 0x02, - DU_DRAWNAVMESH_COLOR_TILES = 0x04, -}; - -void duDebugDrawNavMesh(struct duDebugDraw* dd, const dtNavMesh& mesh, unsigned char flags); -void duDebugDrawNavMeshWithClosedList(struct duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery& query, unsigned char flags); -void duDebugDrawNavMeshNodes(struct duDebugDraw* dd, const dtNavMeshQuery& query); -void duDebugDrawNavMeshBVTree(struct duDebugDraw* dd, const dtNavMesh& mesh); -void duDebugDrawNavMeshPortals(struct duDebugDraw* dd, const dtNavMesh& mesh); -void duDebugDrawNavMeshPolysWithFlags(struct duDebugDraw* dd, const dtNavMesh& mesh, const unsigned short polyFlags, const unsigned int col); -void duDebugDrawNavMeshPoly(struct duDebugDraw* dd, const dtNavMesh& mesh, dtPolyRef ref, const unsigned int col); - -void duDebugDrawTileCacheLayerAreas(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch); -void duDebugDrawTileCacheLayerRegions(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch); -void duDebugDrawTileCacheContours(duDebugDraw* dd, const struct dtTileCacheContourSet& lcset, - const float* orig, const float cs, const float ch); -void duDebugDrawTileCachePolyMesh(duDebugDraw* dd, const struct dtTileCachePolyMesh& lmesh, - const float* orig, const float cs, const float ch); - -#endif // DETOURDEBUGDRAW_H diff --git a/extern/recastnavigation/DebugUtils/Include/RecastDebugDraw.h b/extern/recastnavigation/DebugUtils/Include/RecastDebugDraw.h deleted file mode 100644 index 6a55fa6472..0000000000 --- a/extern/recastnavigation/DebugUtils/Include/RecastDebugDraw.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef RECAST_DEBUGDRAW_H -#define RECAST_DEBUGDRAW_H - -void duDebugDrawTriMesh(struct duDebugDraw* dd, const float* verts, int nverts, const int* tris, const float* normals, int ntris, const unsigned char* flags, const float texScale); -void duDebugDrawTriMeshSlope(struct duDebugDraw* dd, const float* verts, int nverts, const int* tris, const float* normals, int ntris, const float walkableSlopeAngle, const float texScale); - -void duDebugDrawHeightfieldSolid(struct duDebugDraw* dd, const struct rcHeightfield& hf); -void duDebugDrawHeightfieldWalkable(struct duDebugDraw* dd, const struct rcHeightfield& hf); - -void duDebugDrawCompactHeightfieldSolid(struct duDebugDraw* dd, const struct rcCompactHeightfield& chf); -void duDebugDrawCompactHeightfieldRegions(struct duDebugDraw* dd, const struct rcCompactHeightfield& chf); -void duDebugDrawCompactHeightfieldDistance(struct duDebugDraw* dd, const struct rcCompactHeightfield& chf); - -void duDebugDrawHeightfieldLayer(duDebugDraw* dd, const struct rcHeightfieldLayer& layer, const int idx); -void duDebugDrawHeightfieldLayers(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset); -void duDebugDrawHeightfieldLayersRegions(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset); - -void duDebugDrawRegionConnections(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f); -void duDebugDrawRawContours(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f); -void duDebugDrawContours(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f); -void duDebugDrawPolyMesh(struct duDebugDraw* dd, const struct rcPolyMesh& mesh); -void duDebugDrawPolyMeshDetail(struct duDebugDraw* dd, const struct rcPolyMeshDetail& dmesh); - -#endif // RECAST_DEBUGDRAW_H diff --git a/extern/recastnavigation/DebugUtils/Include/RecastDump.h b/extern/recastnavigation/DebugUtils/Include/RecastDump.h deleted file mode 100644 index 6a722fdaea..0000000000 --- a/extern/recastnavigation/DebugUtils/Include/RecastDump.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef RECAST_DUMP_H -#define RECAST_DUMP_H - -struct duFileIO -{ - virtual ~duFileIO() = 0; - virtual bool isWriting() const = 0; - virtual bool isReading() const = 0; - virtual bool write(const void* ptr, const size_t size) = 0; - virtual bool read(void* ptr, const size_t size) = 0; -}; - -bool duDumpPolyMeshToObj(struct rcPolyMesh& pmesh, duFileIO* io); -bool duDumpPolyMeshDetailToObj(struct rcPolyMeshDetail& dmesh, duFileIO* io); - -bool duDumpContourSet(struct rcContourSet& cset, duFileIO* io); -bool duReadContourSet(struct rcContourSet& cset, duFileIO* io); - -bool duDumpCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io); -bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io); - -void duLogBuildTimes(rcContext& ctx, const int totalTileUsec); - - -#endif // RECAST_DUMP_H diff --git a/extern/recastnavigation/DebugUtils/Source/DebugDraw.cpp b/extern/recastnavigation/DebugUtils/Source/DebugDraw.cpp deleted file mode 100644 index d0179bca23..0000000000 --- a/extern/recastnavigation/DebugUtils/Source/DebugDraw.cpp +++ /dev/null @@ -1,612 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#define _USE_MATH_DEFINES -#include -#include "DebugDraw.h" -#include "DetourMath.h" -#include "DetourNavMesh.h" - - -duDebugDraw::~duDebugDraw() -{ - // Empty -} - -unsigned int duDebugDraw::areaToCol(unsigned int area) -{ - if (area == 0) - { - // Treat zero area type as default. - return duRGBA(0, 192, 255, 255); - } - else - { - return duIntToCol(area, 255); - } -} - -inline int bit(int a, int b) -{ - return (a & (1 << b)) >> b; -} - -unsigned int duIntToCol(int i, int a) -{ - int r = bit(i, 1) + bit(i, 3) * 2 + 1; - int g = bit(i, 2) + bit(i, 4) * 2 + 1; - int b = bit(i, 0) + bit(i, 5) * 2 + 1; - return duRGBA(r*63,g*63,b*63,a); -} - -void duIntToCol(int i, float* col) -{ - int r = bit(i, 0) + bit(i, 3) * 2 + 1; - int g = bit(i, 1) + bit(i, 4) * 2 + 1; - int b = bit(i, 2) + bit(i, 5) * 2 + 1; - col[0] = 1 - r*63.0f/255.0f; - col[1] = 1 - g*63.0f/255.0f; - col[2] = 1 - b*63.0f/255.0f; -} - -void duCalcBoxColors(unsigned int* colors, unsigned int colTop, unsigned int colSide) -{ - if (!colors) return; - - colors[0] = duMultCol(colTop, 250); - colors[1] = duMultCol(colSide, 140); - colors[2] = duMultCol(colSide, 165); - colors[3] = duMultCol(colSide, 217); - colors[4] = duMultCol(colSide, 165); - colors[5] = duMultCol(colSide, 217); -} - -void duDebugDrawCylinderWire(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col, const float lineWidth) -{ - if (!dd) return; - - dd->begin(DU_DRAW_LINES, lineWidth); - duAppendCylinderWire(dd, minx,miny,minz, maxx,maxy,maxz, col); - dd->end(); -} - -void duDebugDrawBoxWire(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col, const float lineWidth) -{ - if (!dd) return; - - dd->begin(DU_DRAW_LINES, lineWidth); - duAppendBoxWire(dd, minx,miny,minz, maxx,maxy,maxz, col); - dd->end(); -} - -void duDebugDrawArc(struct duDebugDraw* dd, const float x0, const float y0, const float z0, - const float x1, const float y1, const float z1, const float h, - const float as0, const float as1, unsigned int col, const float lineWidth) -{ - if (!dd) return; - - dd->begin(DU_DRAW_LINES, lineWidth); - duAppendArc(dd, x0,y0,z0, x1,y1,z1, h, as0, as1, col); - dd->end(); -} - -void duDebugDrawArrow(struct duDebugDraw* dd, const float x0, const float y0, const float z0, - const float x1, const float y1, const float z1, - const float as0, const float as1, unsigned int col, const float lineWidth) -{ - if (!dd) return; - - dd->begin(DU_DRAW_LINES, lineWidth); - duAppendArrow(dd, x0,y0,z0, x1,y1,z1, as0, as1, col); - dd->end(); -} - -void duDebugDrawCircle(struct duDebugDraw* dd, const float x, const float y, const float z, - const float r, unsigned int col, const float lineWidth) -{ - if (!dd) return; - - dd->begin(DU_DRAW_LINES, lineWidth); - duAppendCircle(dd, x,y,z, r, col); - dd->end(); -} - -void duDebugDrawCross(struct duDebugDraw* dd, const float x, const float y, const float z, - const float size, unsigned int col, const float lineWidth) -{ - if (!dd) return; - - dd->begin(DU_DRAW_LINES, lineWidth); - duAppendCross(dd, x,y,z, size, col); - dd->end(); -} - -void duDebugDrawBox(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, const unsigned int* fcol) -{ - if (!dd) return; - - dd->begin(DU_DRAW_QUADS); - duAppendBox(dd, minx,miny,minz, maxx,maxy,maxz, fcol); - dd->end(); -} - -void duDebugDrawCylinder(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col) -{ - if (!dd) return; - - dd->begin(DU_DRAW_TRIS); - duAppendCylinder(dd, minx,miny,minz, maxx,maxy,maxz, col); - dd->end(); -} - -void duDebugDrawGridXZ(struct duDebugDraw* dd, const float ox, const float oy, const float oz, - const int w, const int h, const float size, - const unsigned int col, const float lineWidth) -{ - if (!dd) return; - - dd->begin(DU_DRAW_LINES, lineWidth); - for (int i = 0; i <= h; ++i) - { - dd->vertex(ox,oy,oz+i*size, col); - dd->vertex(ox+w*size,oy,oz+i*size, col); - } - for (int i = 0; i <= w; ++i) - { - dd->vertex(ox+i*size,oy,oz, col); - dd->vertex(ox+i*size,oy,oz+h*size, col); - } - dd->end(); -} - - -void duAppendCylinderWire(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col) -{ - if (!dd) return; - - static const int NUM_SEG = 16; - static float dir[NUM_SEG*2]; - static bool init = false; - if (!init) - { - init = true; - for (int i = 0; i < NUM_SEG; ++i) - { - const float a = (float)i/(float)NUM_SEG*DU_PI*2; - dir[i*2] = dtMathCosf(a); - dir[i*2+1] = dtMathSinf(a); - } - } - - const float cx = (maxx + minx)/2; - const float cz = (maxz + minz)/2; - const float rx = (maxx - minx)/2; - const float rz = (maxz - minz)/2; - - for (int i = 0, j = NUM_SEG-1; i < NUM_SEG; j = i++) - { - dd->vertex(cx+dir[j*2+0]*rx, miny, cz+dir[j*2+1]*rz, col); - dd->vertex(cx+dir[i*2+0]*rx, miny, cz+dir[i*2+1]*rz, col); - dd->vertex(cx+dir[j*2+0]*rx, maxy, cz+dir[j*2+1]*rz, col); - dd->vertex(cx+dir[i*2+0]*rx, maxy, cz+dir[i*2+1]*rz, col); - } - for (int i = 0; i < NUM_SEG; i += NUM_SEG/4) - { - dd->vertex(cx+dir[i*2+0]*rx, miny, cz+dir[i*2+1]*rz, col); - dd->vertex(cx+dir[i*2+0]*rx, maxy, cz+dir[i*2+1]*rz, col); - } -} - -void duAppendBoxWire(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col) -{ - if (!dd) return; - // Top - dd->vertex(minx, miny, minz, col); - dd->vertex(maxx, miny, minz, col); - dd->vertex(maxx, miny, minz, col); - dd->vertex(maxx, miny, maxz, col); - dd->vertex(maxx, miny, maxz, col); - dd->vertex(minx, miny, maxz, col); - dd->vertex(minx, miny, maxz, col); - dd->vertex(minx, miny, minz, col); - - // bottom - dd->vertex(minx, maxy, minz, col); - dd->vertex(maxx, maxy, minz, col); - dd->vertex(maxx, maxy, minz, col); - dd->vertex(maxx, maxy, maxz, col); - dd->vertex(maxx, maxy, maxz, col); - dd->vertex(minx, maxy, maxz, col); - dd->vertex(minx, maxy, maxz, col); - dd->vertex(minx, maxy, minz, col); - - // Sides - dd->vertex(minx, miny, minz, col); - dd->vertex(minx, maxy, minz, col); - dd->vertex(maxx, miny, minz, col); - dd->vertex(maxx, maxy, minz, col); - dd->vertex(maxx, miny, maxz, col); - dd->vertex(maxx, maxy, maxz, col); - dd->vertex(minx, miny, maxz, col); - dd->vertex(minx, maxy, maxz, col); -} - -void duAppendBoxPoints(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col) -{ - if (!dd) return; - // Top - dd->vertex(minx, miny, minz, col); - dd->vertex(maxx, miny, minz, col); - dd->vertex(maxx, miny, minz, col); - dd->vertex(maxx, miny, maxz, col); - dd->vertex(maxx, miny, maxz, col); - dd->vertex(minx, miny, maxz, col); - dd->vertex(minx, miny, maxz, col); - dd->vertex(minx, miny, minz, col); - - // bottom - dd->vertex(minx, maxy, minz, col); - dd->vertex(maxx, maxy, minz, col); - dd->vertex(maxx, maxy, minz, col); - dd->vertex(maxx, maxy, maxz, col); - dd->vertex(maxx, maxy, maxz, col); - dd->vertex(minx, maxy, maxz, col); - dd->vertex(minx, maxy, maxz, col); - dd->vertex(minx, maxy, minz, col); -} - -void duAppendBox(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, const unsigned int* fcol) -{ - if (!dd) return; - const float verts[8*3] = - { - minx, miny, minz, - maxx, miny, minz, - maxx, miny, maxz, - minx, miny, maxz, - minx, maxy, minz, - maxx, maxy, minz, - maxx, maxy, maxz, - minx, maxy, maxz, - }; - static const unsigned char inds[6*4] = - { - 7, 6, 5, 4, - 0, 1, 2, 3, - 1, 5, 6, 2, - 3, 7, 4, 0, - 2, 6, 7, 3, - 0, 4, 5, 1, - }; - - const unsigned char* in = inds; - for (int i = 0; i < 6; ++i) - { - dd->vertex(&verts[*in*3], fcol[i]); in++; - dd->vertex(&verts[*in*3], fcol[i]); in++; - dd->vertex(&verts[*in*3], fcol[i]); in++; - dd->vertex(&verts[*in*3], fcol[i]); in++; - } -} - -void duAppendCylinder(struct duDebugDraw* dd, float minx, float miny, float minz, - float maxx, float maxy, float maxz, unsigned int col) -{ - if (!dd) return; - - static const int NUM_SEG = 16; - static float dir[NUM_SEG*2]; - static bool init = false; - if (!init) - { - init = true; - for (int i = 0; i < NUM_SEG; ++i) - { - const float a = (float)i/(float)NUM_SEG*DU_PI*2; - dir[i*2] = cosf(a); - dir[i*2+1] = sinf(a); - } - } - - unsigned int col2 = duMultCol(col, 160); - - const float cx = (maxx + minx)/2; - const float cz = (maxz + minz)/2; - const float rx = (maxx - minx)/2; - const float rz = (maxz - minz)/2; - - for (int i = 2; i < NUM_SEG; ++i) - { - const int a = 0, b = i-1, c = i; - dd->vertex(cx+dir[a*2+0]*rx, miny, cz+dir[a*2+1]*rz, col2); - dd->vertex(cx+dir[b*2+0]*rx, miny, cz+dir[b*2+1]*rz, col2); - dd->vertex(cx+dir[c*2+0]*rx, miny, cz+dir[c*2+1]*rz, col2); - } - for (int i = 2; i < NUM_SEG; ++i) - { - const int a = 0, b = i, c = i-1; - dd->vertex(cx+dir[a*2+0]*rx, maxy, cz+dir[a*2+1]*rz, col); - dd->vertex(cx+dir[b*2+0]*rx, maxy, cz+dir[b*2+1]*rz, col); - dd->vertex(cx+dir[c*2+0]*rx, maxy, cz+dir[c*2+1]*rz, col); - } - for (int i = 0, j = NUM_SEG-1; i < NUM_SEG; j = i++) - { - dd->vertex(cx+dir[i*2+0]*rx, miny, cz+dir[i*2+1]*rz, col2); - dd->vertex(cx+dir[j*2+0]*rx, miny, cz+dir[j*2+1]*rz, col2); - dd->vertex(cx+dir[j*2+0]*rx, maxy, cz+dir[j*2+1]*rz, col); - - dd->vertex(cx+dir[i*2+0]*rx, miny, cz+dir[i*2+1]*rz, col2); - dd->vertex(cx+dir[j*2+0]*rx, maxy, cz+dir[j*2+1]*rz, col); - dd->vertex(cx+dir[i*2+0]*rx, maxy, cz+dir[i*2+1]*rz, col); - } -} - - -inline void evalArc(const float x0, const float y0, const float z0, - const float dx, const float dy, const float dz, - const float h, const float u, float* res) -{ - res[0] = x0 + dx * u; - res[1] = y0 + dy * u + h * (1-(u*2-1)*(u*2-1)); - res[2] = z0 + dz * u; -} - - -inline void vcross(float* dest, const float* v1, const float* v2) -{ - dest[0] = v1[1]*v2[2] - v1[2]*v2[1]; - dest[1] = v1[2]*v2[0] - v1[0]*v2[2]; - dest[2] = v1[0]*v2[1] - v1[1]*v2[0]; -} - -inline void vnormalize(float* v) -{ - float d = 1.0f / sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); - v[0] *= d; - v[1] *= d; - v[2] *= d; -} - -inline void vsub(float* dest, const float* v1, const float* v2) -{ - dest[0] = v1[0]-v2[0]; - dest[1] = v1[1]-v2[1]; - dest[2] = v1[2]-v2[2]; -} - -inline float vdistSqr(const float* v1, const float* v2) -{ - const float x = v1[0]-v2[0]; - const float y = v1[1]-v2[1]; - const float z = v1[2]-v2[2]; - return x*x + y*y + z*z; -} - - -void appendArrowHead(struct duDebugDraw* dd, const float* p, const float* q, - const float s, unsigned int col) -{ - const float eps = 0.001f; - if (!dd) return; - if (vdistSqr(p,q) < eps*eps) return; - float ax[3], ay[3] = {0,1,0}, az[3]; - vsub(az, q, p); - vnormalize(az); - vcross(ax, ay, az); - vcross(ay, az, ax); - vnormalize(ay); - - dd->vertex(p, col); -// dd->vertex(p[0]+az[0]*s+ay[0]*s/2, p[1]+az[1]*s+ay[1]*s/2, p[2]+az[2]*s+ay[2]*s/2, col); - dd->vertex(p[0]+az[0]*s+ax[0]*s/3, p[1]+az[1]*s+ax[1]*s/3, p[2]+az[2]*s+ax[2]*s/3, col); - - dd->vertex(p, col); -// dd->vertex(p[0]+az[0]*s-ay[0]*s/2, p[1]+az[1]*s-ay[1]*s/2, p[2]+az[2]*s-ay[2]*s/2, col); - dd->vertex(p[0]+az[0]*s-ax[0]*s/3, p[1]+az[1]*s-ax[1]*s/3, p[2]+az[2]*s-ax[2]*s/3, col); - -} - -void duAppendArc(struct duDebugDraw* dd, const float x0, const float y0, const float z0, - const float x1, const float y1, const float z1, const float h, - const float as0, const float as1, unsigned int col) -{ - if (!dd) return; - static const int NUM_ARC_PTS = 8; - static const float PAD = 0.05f; - static const float ARC_PTS_SCALE = (1.0f-PAD*2) / (float)NUM_ARC_PTS; - const float dx = x1 - x0; - const float dy = y1 - y0; - const float dz = z1 - z0; - const float len = sqrtf(dx*dx + dy*dy + dz*dz); - float prev[3]; - evalArc(x0,y0,z0, dx,dy,dz, len*h, PAD, prev); - for (int i = 1; i <= NUM_ARC_PTS; ++i) - { - const float u = PAD + i * ARC_PTS_SCALE; - float pt[3]; - evalArc(x0,y0,z0, dx,dy,dz, len*h, u, pt); - dd->vertex(prev[0],prev[1],prev[2], col); - dd->vertex(pt[0],pt[1],pt[2], col); - prev[0] = pt[0]; prev[1] = pt[1]; prev[2] = pt[2]; - } - - // End arrows - if (as0 > 0.001f) - { - float p[3], q[3]; - evalArc(x0,y0,z0, dx,dy,dz, len*h, PAD, p); - evalArc(x0,y0,z0, dx,dy,dz, len*h, PAD+0.05f, q); - appendArrowHead(dd, p, q, as0, col); - } - - if (as1 > 0.001f) - { - float p[3], q[3]; - evalArc(x0,y0,z0, dx,dy,dz, len*h, 1-PAD, p); - evalArc(x0,y0,z0, dx,dy,dz, len*h, 1-(PAD+0.05f), q); - appendArrowHead(dd, p, q, as1, col); - } -} - -void duAppendArrow(struct duDebugDraw* dd, const float x0, const float y0, const float z0, - const float x1, const float y1, const float z1, - const float as0, const float as1, unsigned int col) -{ - if (!dd) return; - - dd->vertex(x0,y0,z0, col); - dd->vertex(x1,y1,z1, col); - - // End arrows - const float p[3] = {x0,y0,z0}, q[3] = {x1,y1,z1}; - if (as0 > 0.001f) - appendArrowHead(dd, p, q, as0, col); - if (as1 > 0.001f) - appendArrowHead(dd, q, p, as1, col); -} - -void duAppendCircle(struct duDebugDraw* dd, const float x, const float y, const float z, - const float r, unsigned int col) -{ - if (!dd) return; - static const int NUM_SEG = 40; - static float dir[40*2]; - static bool init = false; - if (!init) - { - init = true; - for (int i = 0; i < NUM_SEG; ++i) - { - const float a = (float)i/(float)NUM_SEG*DU_PI*2; - dir[i*2] = cosf(a); - dir[i*2+1] = sinf(a); - } - } - - for (int i = 0, j = NUM_SEG-1; i < NUM_SEG; j = i++) - { - dd->vertex(x+dir[j*2+0]*r, y, z+dir[j*2+1]*r, col); - dd->vertex(x+dir[i*2+0]*r, y, z+dir[i*2+1]*r, col); - } -} - -void duAppendCross(struct duDebugDraw* dd, const float x, const float y, const float z, - const float s, unsigned int col) -{ - if (!dd) return; - dd->vertex(x-s,y,z, col); - dd->vertex(x+s,y,z, col); - dd->vertex(x,y-s,z, col); - dd->vertex(x,y+s,z, col); - dd->vertex(x,y,z-s, col); - dd->vertex(x,y,z+s, col); -} - -duDisplayList::duDisplayList(int cap) : - m_pos(0), - m_color(0), - m_size(0), - m_cap(0), - m_depthMask(true), - m_prim(DU_DRAW_LINES), - m_primSize(1.0f) -{ - if (cap < 8) - cap = 8; - resize(cap); -} - -duDisplayList::~duDisplayList() -{ - delete [] m_pos; - delete [] m_color; -} - -void duDisplayList::resize(int cap) -{ - float* newPos = new float[cap*3]; - if (m_size) - memcpy(newPos, m_pos, sizeof(float)*3*m_size); - delete [] m_pos; - m_pos = newPos; - - unsigned int* newColor = new unsigned int[cap]; - if (m_size) - memcpy(newColor, m_color, sizeof(unsigned int)*m_size); - delete [] m_color; - m_color = newColor; - - m_cap = cap; -} - -void duDisplayList::clear() -{ - m_size = 0; -} - -void duDisplayList::depthMask(bool state) -{ - m_depthMask = state; -} - -void duDisplayList::begin(duDebugDrawPrimitives prim, float size) -{ - clear(); - m_prim = prim; - m_primSize = size; -} - -void duDisplayList::vertex(const float x, const float y, const float z, unsigned int color) -{ - if (m_size+1 >= m_cap) - resize(m_cap*2); - float* p = &m_pos[m_size*3]; - p[0] = x; - p[1] = y; - p[2] = z; - m_color[m_size] = color; - m_size++; -} - -void duDisplayList::vertex(const float* pos, unsigned int color) -{ - vertex(pos[0],pos[1],pos[2],color); -} - -void duDisplayList::end() -{ -} - -void duDisplayList::draw(struct duDebugDraw* dd) -{ - if (!dd) return; - if (!m_size) return; - dd->depthMask(m_depthMask); - dd->begin(m_prim, m_primSize); - for (int i = 0; i < m_size; ++i) - dd->vertex(&m_pos[i*3], m_color[i]); - dd->end(); -} diff --git a/extern/recastnavigation/DebugUtils/Source/DetourDebugDraw.cpp b/extern/recastnavigation/DebugUtils/Source/DetourDebugDraw.cpp deleted file mode 100644 index 4ca0581c77..0000000000 --- a/extern/recastnavigation/DebugUtils/Source/DetourDebugDraw.cpp +++ /dev/null @@ -1,864 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include "DebugDraw.h" -#include "DetourDebugDraw.h" -#include "DetourNavMesh.h" -#include "DetourCommon.h" -#include "DetourNode.h" - - -static float distancePtLine2d(const float* pt, const float* p, const float* q) -{ - float pqx = q[0] - p[0]; - float pqz = q[2] - p[2]; - float dx = pt[0] - p[0]; - float dz = pt[2] - p[2]; - float d = pqx*pqx + pqz*pqz; - float t = pqx*dx + pqz*dz; - if (d != 0) t /= d; - dx = p[0] + t*pqx - pt[0]; - dz = p[2] + t*pqz - pt[2]; - return dx*dx + dz*dz; -} - -static void drawPolyBoundaries(duDebugDraw* dd, const dtMeshTile* tile, - const unsigned int col, const float linew, - bool inner) -{ - static const float thr = 0.01f*0.01f; - - dd->begin(DU_DRAW_LINES, linew); - - for (int i = 0; i < tile->header->polyCount; ++i) - { - const dtPoly* p = &tile->polys[i]; - - if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) continue; - - const dtPolyDetail* pd = &tile->detailMeshes[i]; - - for (int j = 0, nj = (int)p->vertCount; j < nj; ++j) - { - unsigned int c = col; - if (inner) - { - if (p->neis[j] == 0) continue; - if (p->neis[j] & DT_EXT_LINK) - { - bool con = false; - for (unsigned int k = p->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) - { - if (tile->links[k].edge == j) - { - con = true; - break; - } - } - if (con) - c = duRGBA(255,255,255,48); - else - c = duRGBA(0,0,0,48); - } - else - c = duRGBA(0,48,64,32); - } - else - { - if (p->neis[j] != 0) continue; - } - - const float* v0 = &tile->verts[p->verts[j]*3]; - const float* v1 = &tile->verts[p->verts[(j+1) % nj]*3]; - - // Draw detail mesh edges which align with the actual poly edge. - // This is really slow. - for (int k = 0; k < pd->triCount; ++k) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+k)*4]; - const float* tv[3]; - for (int m = 0; m < 3; ++m) - { - if (t[m] < p->vertCount) - tv[m] = &tile->verts[p->verts[t[m]]*3]; - else - tv[m] = &tile->detailVerts[(pd->vertBase+(t[m]-p->vertCount))*3]; - } - for (int m = 0, n = 2; m < 3; n=m++) - { - if ((dtGetDetailTriEdgeFlags(t[3], n) & DT_DETAIL_EDGE_BOUNDARY) == 0) - continue; - - if (distancePtLine2d(tv[n],v0,v1) < thr && - distancePtLine2d(tv[m],v0,v1) < thr) - { - dd->vertex(tv[n], c); - dd->vertex(tv[m], c); - } - } - } - } - } - dd->end(); -} - -static void drawMeshTile(duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery* query, - const dtMeshTile* tile, unsigned char flags) -{ - dtPolyRef base = mesh.getPolyRefBase(tile); - - int tileNum = mesh.decodePolyIdTile(base); - const unsigned int tileColor = duIntToCol(tileNum, 128); - - dd->depthMask(false); - - dd->begin(DU_DRAW_TRIS); - for (int i = 0; i < tile->header->polyCount; ++i) - { - const dtPoly* p = &tile->polys[i]; - if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) // Skip off-mesh links. - continue; - - const dtPolyDetail* pd = &tile->detailMeshes[i]; - - unsigned int col; - if (query && query->isInClosedList(base | (dtPolyRef)i)) - col = duRGBA(255,196,0,64); - else - { - if (flags & DU_DRAWNAVMESH_COLOR_TILES) - col = tileColor; - else - col = duTransCol(dd->areaToCol(p->getArea()), 64); - } - - for (int j = 0; j < pd->triCount; ++j) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; - for (int k = 0; k < 3; ++k) - { - if (t[k] < p->vertCount) - dd->vertex(&tile->verts[p->verts[t[k]]*3], col); - else - dd->vertex(&tile->detailVerts[(pd->vertBase+t[k]-p->vertCount)*3], col); - } - } - } - dd->end(); - - // Draw inter poly boundaries - drawPolyBoundaries(dd, tile, duRGBA(0,48,64,32), 1.5f, true); - - // Draw outer poly boundaries - drawPolyBoundaries(dd, tile, duRGBA(0,48,64,220), 2.5f, false); - - if (flags & DU_DRAWNAVMESH_OFFMESHCONS) - { - dd->begin(DU_DRAW_LINES, 2.0f); - for (int i = 0; i < tile->header->polyCount; ++i) - { - const dtPoly* p = &tile->polys[i]; - if (p->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) // Skip regular polys. - continue; - - unsigned int col, col2; - if (query && query->isInClosedList(base | (dtPolyRef)i)) - col = duRGBA(255,196,0,220); - else - col = duDarkenCol(duTransCol(dd->areaToCol(p->getArea()), 220)); - - const dtOffMeshConnection* con = &tile->offMeshCons[i - tile->header->offMeshBase]; - const float* va = &tile->verts[p->verts[0]*3]; - const float* vb = &tile->verts[p->verts[1]*3]; - - // Check to see if start and end end-points have links. - bool startSet = false; - bool endSet = false; - for (unsigned int k = p->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) - { - if (tile->links[k].edge == 0) - startSet = true; - if (tile->links[k].edge == 1) - endSet = true; - } - - // End points and their on-mesh locations. - dd->vertex(va[0],va[1],va[2], col); - dd->vertex(con->pos[0],con->pos[1],con->pos[2], col); - col2 = startSet ? col : duRGBA(220,32,16,196); - duAppendCircle(dd, con->pos[0],con->pos[1]+0.1f,con->pos[2], con->rad, col2); - - dd->vertex(vb[0],vb[1],vb[2], col); - dd->vertex(con->pos[3],con->pos[4],con->pos[5], col); - col2 = endSet ? col : duRGBA(220,32,16,196); - duAppendCircle(dd, con->pos[3],con->pos[4]+0.1f,con->pos[5], con->rad, col2); - - // End point vertices. - dd->vertex(con->pos[0],con->pos[1],con->pos[2], duRGBA(0,48,64,196)); - dd->vertex(con->pos[0],con->pos[1]+0.2f,con->pos[2], duRGBA(0,48,64,196)); - - dd->vertex(con->pos[3],con->pos[4],con->pos[5], duRGBA(0,48,64,196)); - dd->vertex(con->pos[3],con->pos[4]+0.2f,con->pos[5], duRGBA(0,48,64,196)); - - // Connection arc. - duAppendArc(dd, con->pos[0],con->pos[1],con->pos[2], con->pos[3],con->pos[4],con->pos[5], 0.25f, - (con->flags & 1) ? 0.6f : 0, 0.6f, col); - } - dd->end(); - } - - const unsigned int vcol = duRGBA(0,0,0,196); - dd->begin(DU_DRAW_POINTS, 3.0f); - for (int i = 0; i < tile->header->vertCount; ++i) - { - const float* v = &tile->verts[i*3]; - dd->vertex(v[0], v[1], v[2], vcol); - } - dd->end(); - - dd->depthMask(true); -} - -void duDebugDrawNavMesh(duDebugDraw* dd, const dtNavMesh& mesh, unsigned char flags) -{ - if (!dd) return; - - for (int i = 0; i < mesh.getMaxTiles(); ++i) - { - const dtMeshTile* tile = mesh.getTile(i); - if (!tile->header) continue; - drawMeshTile(dd, mesh, 0, tile, flags); - } -} - -void duDebugDrawNavMeshWithClosedList(struct duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery& query, unsigned char flags) -{ - if (!dd) return; - - const dtNavMeshQuery* q = (flags & DU_DRAWNAVMESH_CLOSEDLIST) ? &query : 0; - - for (int i = 0; i < mesh.getMaxTiles(); ++i) - { - const dtMeshTile* tile = mesh.getTile(i); - if (!tile->header) continue; - drawMeshTile(dd, mesh, q, tile, flags); - } -} - -void duDebugDrawNavMeshNodes(struct duDebugDraw* dd, const dtNavMeshQuery& query) -{ - if (!dd) return; - - const dtNodePool* pool = query.getNodePool(); - if (pool) - { - const float off = 0.5f; - dd->begin(DU_DRAW_POINTS, 4.0f); - for (int i = 0; i < pool->getHashSize(); ++i) - { - for (dtNodeIndex j = pool->getFirst(i); j != DT_NULL_IDX; j = pool->getNext(j)) - { - const dtNode* node = pool->getNodeAtIdx(j+1); - if (!node) continue; - dd->vertex(node->pos[0],node->pos[1]+off,node->pos[2], duRGBA(255,192,0,255)); - } - } - dd->end(); - - dd->begin(DU_DRAW_LINES, 2.0f); - for (int i = 0; i < pool->getHashSize(); ++i) - { - for (dtNodeIndex j = pool->getFirst(i); j != DT_NULL_IDX; j = pool->getNext(j)) - { - const dtNode* node = pool->getNodeAtIdx(j+1); - if (!node) continue; - if (!node->pidx) continue; - const dtNode* parent = pool->getNodeAtIdx(node->pidx); - if (!parent) continue; - dd->vertex(node->pos[0],node->pos[1]+off,node->pos[2], duRGBA(255,192,0,128)); - dd->vertex(parent->pos[0],parent->pos[1]+off,parent->pos[2], duRGBA(255,192,0,128)); - } - } - dd->end(); - } -} - - -static void drawMeshTileBVTree(duDebugDraw* dd, const dtMeshTile* tile) -{ - // Draw BV nodes. - const float cs = 1.0f / tile->header->bvQuantFactor; - dd->begin(DU_DRAW_LINES, 1.0f); - for (int i = 0; i < tile->header->bvNodeCount; ++i) - { - const dtBVNode* n = &tile->bvTree[i]; - if (n->i < 0) // Leaf indices are positive. - continue; - duAppendBoxWire(dd, tile->header->bmin[0] + n->bmin[0]*cs, - tile->header->bmin[1] + n->bmin[1]*cs, - tile->header->bmin[2] + n->bmin[2]*cs, - tile->header->bmin[0] + n->bmax[0]*cs, - tile->header->bmin[1] + n->bmax[1]*cs, - tile->header->bmin[2] + n->bmax[2]*cs, - duRGBA(255,255,255,128)); - } - dd->end(); -} - -void duDebugDrawNavMeshBVTree(duDebugDraw* dd, const dtNavMesh& mesh) -{ - if (!dd) return; - - for (int i = 0; i < mesh.getMaxTiles(); ++i) - { - const dtMeshTile* tile = mesh.getTile(i); - if (!tile->header) continue; - drawMeshTileBVTree(dd, tile); - } -} - -static void drawMeshTilePortal(duDebugDraw* dd, const dtMeshTile* tile) -{ - // Draw portals - const float padx = 0.04f; - const float pady = tile->header->walkableClimb; - - dd->begin(DU_DRAW_LINES, 2.0f); - - for (int side = 0; side < 8; ++side) - { - unsigned short m = DT_EXT_LINK | (unsigned short)side; - - for (int i = 0; i < tile->header->polyCount; ++i) - { - dtPoly* poly = &tile->polys[i]; - - // Create new links. - const int nv = poly->vertCount; - for (int j = 0; j < nv; ++j) - { - // Skip edges which do not point to the right side. - if (poly->neis[j] != m) - continue; - - // Create new links - const float* va = &tile->verts[poly->verts[j]*3]; - const float* vb = &tile->verts[poly->verts[(j+1) % nv]*3]; - - if (side == 0 || side == 4) - { - unsigned int col = side == 0 ? duRGBA(128,0,0,128) : duRGBA(128,0,128,128); - - const float x = va[0] + ((side == 0) ? -padx : padx); - - dd->vertex(x,va[1]-pady,va[2], col); - dd->vertex(x,va[1]+pady,va[2], col); - - dd->vertex(x,va[1]+pady,va[2], col); - dd->vertex(x,vb[1]+pady,vb[2], col); - - dd->vertex(x,vb[1]+pady,vb[2], col); - dd->vertex(x,vb[1]-pady,vb[2], col); - - dd->vertex(x,vb[1]-pady,vb[2], col); - dd->vertex(x,va[1]-pady,va[2], col); - } - else if (side == 2 || side == 6) - { - unsigned int col = side == 2 ? duRGBA(0,128,0,128) : duRGBA(0,128,128,128); - - const float z = va[2] + ((side == 2) ? -padx : padx); - - dd->vertex(va[0],va[1]-pady,z, col); - dd->vertex(va[0],va[1]+pady,z, col); - - dd->vertex(va[0],va[1]+pady,z, col); - dd->vertex(vb[0],vb[1]+pady,z, col); - - dd->vertex(vb[0],vb[1]+pady,z, col); - dd->vertex(vb[0],vb[1]-pady,z, col); - - dd->vertex(vb[0],vb[1]-pady,z, col); - dd->vertex(va[0],va[1]-pady,z, col); - } - - } - } - } - - dd->end(); -} - -void duDebugDrawNavMeshPortals(duDebugDraw* dd, const dtNavMesh& mesh) -{ - if (!dd) return; - - for (int i = 0; i < mesh.getMaxTiles(); ++i) - { - const dtMeshTile* tile = mesh.getTile(i); - if (!tile->header) continue; - drawMeshTilePortal(dd, tile); - } -} - -void duDebugDrawNavMeshPolysWithFlags(struct duDebugDraw* dd, const dtNavMesh& mesh, - const unsigned short polyFlags, const unsigned int col) -{ - if (!dd) return; - - for (int i = 0; i < mesh.getMaxTiles(); ++i) - { - const dtMeshTile* tile = mesh.getTile(i); - if (!tile->header) continue; - dtPolyRef base = mesh.getPolyRefBase(tile); - - for (int j = 0; j < tile->header->polyCount; ++j) - { - const dtPoly* p = &tile->polys[j]; - if ((p->flags & polyFlags) == 0) continue; - duDebugDrawNavMeshPoly(dd, mesh, base|(dtPolyRef)j, col); - } - } -} - -void duDebugDrawNavMeshPoly(duDebugDraw* dd, const dtNavMesh& mesh, dtPolyRef ref, const unsigned int col) -{ - if (!dd) return; - - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - if (dtStatusFailed(mesh.getTileAndPolyByRef(ref, &tile, &poly))) - return; - - dd->depthMask(false); - - const unsigned int c = duTransCol(col, 64); - const unsigned int ip = (unsigned int)(poly - tile->polys); - - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - { - dtOffMeshConnection* con = &tile->offMeshCons[ip - tile->header->offMeshBase]; - - dd->begin(DU_DRAW_LINES, 2.0f); - - // Connection arc. - duAppendArc(dd, con->pos[0],con->pos[1],con->pos[2], con->pos[3],con->pos[4],con->pos[5], 0.25f, - (con->flags & 1) ? 0.6f : 0.0f, 0.6f, c); - - dd->end(); - } - else - { - const dtPolyDetail* pd = &tile->detailMeshes[ip]; - - dd->begin(DU_DRAW_TRIS); - for (int i = 0; i < pd->triCount; ++i) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+i)*4]; - for (int j = 0; j < 3; ++j) - { - if (t[j] < poly->vertCount) - dd->vertex(&tile->verts[poly->verts[t[j]]*3], c); - else - dd->vertex(&tile->detailVerts[(pd->vertBase+t[j]-poly->vertCount)*3], c); - } - } - dd->end(); - } - - dd->depthMask(true); - -} - -static void debugDrawTileCachePortals(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch) -{ - const int w = (int)layer.header->width; - const int h = (int)layer.header->height; - const float* bmin = layer.header->bmin; - - // Portals - unsigned int pcol = duRGBA(255,255,255,255); - - const int segs[4*4] = {0,0,0,1, 0,1,1,1, 1,1,1,0, 1,0,0,0}; - - // Layer portals - dd->begin(DU_DRAW_LINES, 2.0f); - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int idx = x+y*w; - const int lh = (int)layer.heights[idx]; - if (lh == 0xff) continue; - - for (int dir = 0; dir < 4; ++dir) - { - if (layer.cons[idx] & (1<<(dir+4))) - { - const int* seg = &segs[dir*4]; - const float ax = bmin[0] + (x+seg[0])*cs; - const float ay = bmin[1] + (lh+2)*ch; - const float az = bmin[2] + (y+seg[1])*cs; - const float bx = bmin[0] + (x+seg[2])*cs; - const float by = bmin[1] + (lh+2)*ch; - const float bz = bmin[2] + (y+seg[3])*cs; - dd->vertex(ax, ay, az, pcol); - dd->vertex(bx, by, bz, pcol); - } - } - } - } - dd->end(); -} - -void duDebugDrawTileCacheLayerAreas(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch) -{ - const int w = (int)layer.header->width; - const int h = (int)layer.header->height; - const float* bmin = layer.header->bmin; - const float* bmax = layer.header->bmax; - const int idx = layer.header->tlayer; - - unsigned int color = duIntToCol(idx+1, 255); - - // Layer bounds - float lbmin[3], lbmax[3]; - lbmin[0] = bmin[0] + layer.header->minx*cs; - lbmin[1] = bmin[1]; - lbmin[2] = bmin[2] + layer.header->miny*cs; - lbmax[0] = bmin[0] + (layer.header->maxx+1)*cs; - lbmax[1] = bmax[1]; - lbmax[2] = bmin[2] + (layer.header->maxy+1)*cs; - duDebugDrawBoxWire(dd, lbmin[0],lbmin[1],lbmin[2], lbmax[0],lbmax[1],lbmax[2], duTransCol(color,128), 2.0f); - - // Layer height - dd->begin(DU_DRAW_QUADS); - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int lidx = x+y*w; - const int lh = (int)layer.heights[lidx]; - if (lh == 0xff) continue; - - const unsigned char area = layer.areas[lidx]; - unsigned int col; - if (area == 63) - col = duLerpCol(color, duRGBA(0,192,255,64), 32); - else if (area == 0) - col = duLerpCol(color, duRGBA(0,0,0,64), 32); - else - col = duLerpCol(color, dd->areaToCol(area), 32); - - const float fx = bmin[0] + x*cs; - const float fy = bmin[1] + (lh+1)*ch; - const float fz = bmin[2] + y*cs; - - dd->vertex(fx, fy, fz, col); - dd->vertex(fx, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz, col); - } - } - dd->end(); - - debugDrawTileCachePortals(dd, layer, cs, ch); -} - -void duDebugDrawTileCacheLayerRegions(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch) -{ - const int w = (int)layer.header->width; - const int h = (int)layer.header->height; - const float* bmin = layer.header->bmin; - const float* bmax = layer.header->bmax; - const int idx = layer.header->tlayer; - - unsigned int color = duIntToCol(idx+1, 255); - - // Layer bounds - float lbmin[3], lbmax[3]; - lbmin[0] = bmin[0] + layer.header->minx*cs; - lbmin[1] = bmin[1]; - lbmin[2] = bmin[2] + layer.header->miny*cs; - lbmax[0] = bmin[0] + (layer.header->maxx+1)*cs; - lbmax[1] = bmax[1]; - lbmax[2] = bmin[2] + (layer.header->maxy+1)*cs; - duDebugDrawBoxWire(dd, lbmin[0],lbmin[1],lbmin[2], lbmax[0],lbmax[1],lbmax[2], duTransCol(color,128), 2.0f); - - // Layer height - dd->begin(DU_DRAW_QUADS); - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int lidx = x+y*w; - const int lh = (int)layer.heights[lidx]; - if (lh == 0xff) continue; - const unsigned char reg = layer.regs[lidx]; - - unsigned int col = duLerpCol(color, duIntToCol(reg, 255), 192); - - const float fx = bmin[0] + x*cs; - const float fy = bmin[1] + (lh+1)*ch; - const float fz = bmin[2] + y*cs; - - dd->vertex(fx, fy, fz, col); - dd->vertex(fx, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz, col); - } - } - dd->end(); - - debugDrawTileCachePortals(dd, layer, cs, ch); -} - - - - -/*struct dtTileCacheContour -{ - int nverts; - unsigned char* verts; - unsigned char reg; - unsigned char area; -}; - -struct dtTileCacheContourSet -{ - int nconts; - dtTileCacheContour* conts; -};*/ - -void duDebugDrawTileCacheContours(duDebugDraw* dd, const struct dtTileCacheContourSet& lcset, - const float* orig, const float cs, const float ch) -{ - if (!dd) return; - - const unsigned char a = 255;// (unsigned char)(alpha*255.0f); - - const int offs[2*4] = {-1,0, 0,1, 1,0, 0,-1}; - - dd->begin(DU_DRAW_LINES, 2.0f); - - for (int i = 0; i < lcset.nconts; ++i) - { - const dtTileCacheContour& c = lcset.conts[i]; - unsigned int color = 0; - - color = duIntToCol(i, a); - - for (int j = 0; j < c.nverts; ++j) - { - const int k = (j+1) % c.nverts; - const unsigned char* va = &c.verts[j*4]; - const unsigned char* vb = &c.verts[k*4]; - const float ax = orig[0] + va[0]*cs; - const float ay = orig[1] + (va[1]+1+(i&1))*ch; - const float az = orig[2] + va[2]*cs; - const float bx = orig[0] + vb[0]*cs; - const float by = orig[1] + (vb[1]+1+(i&1))*ch; - const float bz = orig[2] + vb[2]*cs; - unsigned int col = color; - if ((va[3] & 0xf) != 0xf) - { - // Portal segment - col = duRGBA(255,255,255,128); - int d = va[3] & 0xf; - - const float cx = (ax+bx)*0.5f; - const float cy = (ay+by)*0.5f; - const float cz = (az+bz)*0.5f; - - const float dx = cx + offs[d*2+0]*2*cs; - const float dy = cy; - const float dz = cz + offs[d*2+1]*2*cs; - - dd->vertex(cx,cy,cz,duRGBA(255,0,0,255)); - dd->vertex(dx,dy,dz,duRGBA(255,0,0,255)); - } - - duAppendArrow(dd, ax,ay,az, bx,by,bz, 0.0f, cs*0.5f, col); - } - } - dd->end(); - - dd->begin(DU_DRAW_POINTS, 4.0f); - - for (int i = 0; i < lcset.nconts; ++i) - { - const dtTileCacheContour& c = lcset.conts[i]; - unsigned int color = 0; - - for (int j = 0; j < c.nverts; ++j) - { - const unsigned char* va = &c.verts[j*4]; - - color = duDarkenCol(duIntToCol(i, a)); - if (va[3] & 0x80) - { - // Border vertex - color = duRGBA(255,0,0,255); - } - - float fx = orig[0] + va[0]*cs; - float fy = orig[1] + (va[1]+1+(i&1))*ch; - float fz = orig[2] + va[2]*cs; - dd->vertex(fx,fy,fz, color); - } - } - dd->end(); -} - -void duDebugDrawTileCachePolyMesh(duDebugDraw* dd, const struct dtTileCachePolyMesh& lmesh, - const float* orig, const float cs, const float ch) -{ - if (!dd) return; - - const int nvp = lmesh.nvp; - - const int offs[2*4] = {-1,0, 0,1, 1,0, 0,-1}; - - dd->begin(DU_DRAW_TRIS); - - for (int i = 0; i < lmesh.npolys; ++i) - { - const unsigned short* p = &lmesh.polys[i*nvp*2]; - const unsigned char area = lmesh.areas[i]; - - unsigned int color; - if (area == DT_TILECACHE_WALKABLE_AREA) - color = duRGBA(0,192,255,64); - else if (area == DT_TILECACHE_NULL_AREA) - color = duRGBA(0,0,0,64); - else - color = dd->areaToCol(area); - - unsigned short vi[3]; - for (int j = 2; j < nvp; ++j) - { - if (p[j] == DT_TILECACHE_NULL_IDX) break; - vi[0] = p[0]; - vi[1] = p[j-1]; - vi[2] = p[j]; - for (int k = 0; k < 3; ++k) - { - const unsigned short* v = &lmesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch; - const float z = orig[2] + v[2]*cs; - dd->vertex(x,y,z, color); - } - } - } - dd->end(); - - // Draw neighbours edges - const unsigned int coln = duRGBA(0,48,64,32); - dd->begin(DU_DRAW_LINES, 1.5f); - for (int i = 0; i < lmesh.npolys; ++i) - { - const unsigned short* p = &lmesh.polys[i*nvp*2]; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == DT_TILECACHE_NULL_IDX) break; - if (p[nvp+j] & 0x8000) continue; - const int nj = (j+1 >= nvp || p[j+1] == DT_TILECACHE_NULL_IDX) ? 0 : j+1; - int vi[2] = {p[j], p[nj]}; - - for (int k = 0; k < 2; ++k) - { - const unsigned short* v = &lmesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x, y, z, coln); - } - } - } - dd->end(); - - // Draw boundary edges - const unsigned int colb = duRGBA(0,48,64,220); - dd->begin(DU_DRAW_LINES, 2.5f); - for (int i = 0; i < lmesh.npolys; ++i) - { - const unsigned short* p = &lmesh.polys[i*nvp*2]; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == DT_TILECACHE_NULL_IDX) break; - if ((p[nvp+j] & 0x8000) == 0) continue; - const int nj = (j+1 >= nvp || p[j+1] == DT_TILECACHE_NULL_IDX) ? 0 : j+1; - int vi[2] = {p[j], p[nj]}; - - unsigned int col = colb; - if ((p[nvp+j] & 0xf) != 0xf) - { - const unsigned short* va = &lmesh.verts[vi[0]*3]; - const unsigned short* vb = &lmesh.verts[vi[1]*3]; - - const float ax = orig[0] + va[0]*cs; - const float ay = orig[1] + (va[1]+1+(i&1))*ch; - const float az = orig[2] + va[2]*cs; - const float bx = orig[0] + vb[0]*cs; - const float by = orig[1] + (vb[1]+1+(i&1))*ch; - const float bz = orig[2] + vb[2]*cs; - - const float cx = (ax+bx)*0.5f; - const float cy = (ay+by)*0.5f; - const float cz = (az+bz)*0.5f; - - int d = p[nvp+j] & 0xf; - - const float dx = cx + offs[d*2+0]*2*cs; - const float dy = cy; - const float dz = cz + offs[d*2+1]*2*cs; - - dd->vertex(cx,cy,cz,duRGBA(255,0,0,255)); - dd->vertex(dx,dy,dz,duRGBA(255,0,0,255)); - - col = duRGBA(255,255,255,128); - } - - for (int k = 0; k < 2; ++k) - { - const unsigned short* v = &lmesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x, y, z, col); - } - } - } - dd->end(); - - dd->begin(DU_DRAW_POINTS, 3.0f); - const unsigned int colv = duRGBA(0,0,0,220); - for (int i = 0; i < lmesh.nverts; ++i) - { - const unsigned short* v = &lmesh.verts[i*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x,y,z, colv); - } - dd->end(); -} - - - diff --git a/extern/recastnavigation/DebugUtils/Source/RecastDebugDraw.cpp b/extern/recastnavigation/DebugUtils/Source/RecastDebugDraw.cpp deleted file mode 100644 index c1a73a168e..0000000000 --- a/extern/recastnavigation/DebugUtils/Source/RecastDebugDraw.cpp +++ /dev/null @@ -1,1064 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#define _USE_MATH_DEFINES -#include -#include "DebugDraw.h" -#include "RecastDebugDraw.h" -#include "Recast.h" - -void duDebugDrawTriMesh(duDebugDraw* dd, const float* verts, int /*nverts*/, - const int* tris, const float* normals, int ntris, - const unsigned char* flags, const float texScale) -{ - if (!dd) return; - if (!verts) return; - if (!tris) return; - if (!normals) return; - - float uva[2]; - float uvb[2]; - float uvc[2]; - - const unsigned int unwalkable = duRGBA(192,128,0,255); - - dd->texture(true); - - dd->begin(DU_DRAW_TRIS); - for (int i = 0; i < ntris*3; i += 3) - { - const float* norm = &normals[i]; - unsigned int color; - unsigned char a = (unsigned char)(220*(2+norm[0]+norm[1])/4); - if (flags && !flags[i/3]) - color = duLerpCol(duRGBA(a,a,a,255), unwalkable, 64); - else - color = duRGBA(a,a,a,255); - - const float* va = &verts[tris[i+0]*3]; - const float* vb = &verts[tris[i+1]*3]; - const float* vc = &verts[tris[i+2]*3]; - - int ax = 0, ay = 0; - if (rcAbs(norm[1]) > rcAbs(norm[ax])) - ax = 1; - if (rcAbs(norm[2]) > rcAbs(norm[ax])) - ax = 2; - ax = (1<vertex(va, color, uva); - dd->vertex(vb, color, uvb); - dd->vertex(vc, color, uvc); - } - dd->end(); - dd->texture(false); -} - -void duDebugDrawTriMeshSlope(duDebugDraw* dd, const float* verts, int /*nverts*/, - const int* tris, const float* normals, int ntris, - const float walkableSlopeAngle, const float texScale) -{ - if (!dd) return; - if (!verts) return; - if (!tris) return; - if (!normals) return; - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*DU_PI); - - float uva[2]; - float uvb[2]; - float uvc[2]; - - dd->texture(true); - - const unsigned int unwalkable = duRGBA(192,128,0,255); - - dd->begin(DU_DRAW_TRIS); - for (int i = 0; i < ntris*3; i += 3) - { - const float* norm = &normals[i]; - unsigned int color; - unsigned char a = (unsigned char)(220*(2+norm[0]+norm[1])/4); - if (norm[1] < walkableThr) - color = duLerpCol(duRGBA(a,a,a,255), unwalkable, 64); - else - color = duRGBA(a,a,a,255); - - const float* va = &verts[tris[i+0]*3]; - const float* vb = &verts[tris[i+1]*3]; - const float* vc = &verts[tris[i+2]*3]; - - int ax = 0, ay = 0; - if (rcAbs(norm[1]) > rcAbs(norm[ax])) - ax = 1; - if (rcAbs(norm[2]) > rcAbs(norm[ax])) - ax = 2; - ax = (1<vertex(va, color, uva); - dd->vertex(vb, color, uvb); - dd->vertex(vc, color, uvc); - } - dd->end(); - - dd->texture(false); -} - -void duDebugDrawHeightfieldSolid(duDebugDraw* dd, const rcHeightfield& hf) -{ - if (!dd) return; - - const float* orig = hf.bmin; - const float cs = hf.cs; - const float ch = hf.ch; - - const int w = hf.width; - const int h = hf.height; - - unsigned int fcol[6]; - duCalcBoxColors(fcol, duRGBA(255,255,255,255), duRGBA(255,255,255,255)); - - dd->begin(DU_DRAW_QUADS); - - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - float fx = orig[0] + x*cs; - float fz = orig[2] + y*cs; - const rcSpan* s = hf.spans[x + y*w]; - while (s) - { - duAppendBox(dd, fx, orig[1]+s->smin*ch, fz, fx+cs, orig[1] + s->smax*ch, fz+cs, fcol); - s = s->next; - } - } - } - dd->end(); -} - -void duDebugDrawHeightfieldWalkable(duDebugDraw* dd, const rcHeightfield& hf) -{ - if (!dd) return; - - const float* orig = hf.bmin; - const float cs = hf.cs; - const float ch = hf.ch; - - const int w = hf.width; - const int h = hf.height; - - unsigned int fcol[6]; - duCalcBoxColors(fcol, duRGBA(255,255,255,255), duRGBA(217,217,217,255)); - - dd->begin(DU_DRAW_QUADS); - - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - float fx = orig[0] + x*cs; - float fz = orig[2] + y*cs; - const rcSpan* s = hf.spans[x + y*w]; - while (s) - { - if (s->area == RC_WALKABLE_AREA) - fcol[0] = duRGBA(64,128,160,255); - else if (s->area == RC_NULL_AREA) - fcol[0] = duRGBA(64,64,64,255); - else - fcol[0] = duMultCol(dd->areaToCol(s->area), 200); - - duAppendBox(dd, fx, orig[1]+s->smin*ch, fz, fx+cs, orig[1] + s->smax*ch, fz+cs, fcol); - s = s->next; - } - } - } - - dd->end(); -} - -void duDebugDrawCompactHeightfieldSolid(duDebugDraw* dd, const rcCompactHeightfield& chf) -{ - if (!dd) return; - - const float cs = chf.cs; - const float ch = chf.ch; - - dd->begin(DU_DRAW_QUADS); - - for (int y = 0; y < chf.height; ++y) - { - for (int x = 0; x < chf.width; ++x) - { - const float fx = chf.bmin[0] + x*cs; - const float fz = chf.bmin[2] + y*cs; - const rcCompactCell& c = chf.cells[x+y*chf.width]; - - for (unsigned i = c.index, ni = c.index+c.count; i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - - const unsigned char area = chf.areas[i]; - unsigned int color; - if (area == RC_WALKABLE_AREA) - color = duRGBA(0,192,255,64); - else if (area == RC_NULL_AREA) - color = duRGBA(0,0,0,64); - else - color = dd->areaToCol(area); - - const float fy = chf.bmin[1] + (s.y+1)*ch; - dd->vertex(fx, fy, fz, color); - dd->vertex(fx, fy, fz+cs, color); - dd->vertex(fx+cs, fy, fz+cs, color); - dd->vertex(fx+cs, fy, fz, color); - } - } - } - dd->end(); -} - -void duDebugDrawCompactHeightfieldRegions(duDebugDraw* dd, const rcCompactHeightfield& chf) -{ - if (!dd) return; - - const float cs = chf.cs; - const float ch = chf.ch; - - dd->begin(DU_DRAW_QUADS); - - for (int y = 0; y < chf.height; ++y) - { - for (int x = 0; x < chf.width; ++x) - { - const float fx = chf.bmin[0] + x*cs; - const float fz = chf.bmin[2] + y*cs; - const rcCompactCell& c = chf.cells[x+y*chf.width]; - - for (unsigned i = c.index, ni = c.index+c.count; i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - const float fy = chf.bmin[1] + (s.y)*ch; - unsigned int color; - if (s.reg) - color = duIntToCol(s.reg, 192); - else - color = duRGBA(0,0,0,64); - - dd->vertex(fx, fy, fz, color); - dd->vertex(fx, fy, fz+cs, color); - dd->vertex(fx+cs, fy, fz+cs, color); - dd->vertex(fx+cs, fy, fz, color); - } - } - } - - dd->end(); -} - - -void duDebugDrawCompactHeightfieldDistance(duDebugDraw* dd, const rcCompactHeightfield& chf) -{ - if (!dd) return; - if (!chf.dist) return; - - const float cs = chf.cs; - const float ch = chf.ch; - - float maxd = chf.maxDistance; - if (maxd < 1.0f) maxd = 1; - const float dscale = 255.0f / maxd; - - dd->begin(DU_DRAW_QUADS); - - for (int y = 0; y < chf.height; ++y) - { - for (int x = 0; x < chf.width; ++x) - { - const float fx = chf.bmin[0] + x*cs; - const float fz = chf.bmin[2] + y*cs; - const rcCompactCell& c = chf.cells[x+y*chf.width]; - - for (unsigned i = c.index, ni = c.index+c.count; i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - const float fy = chf.bmin[1] + (s.y+1)*ch; - const unsigned char cd = (unsigned char)(chf.dist[i] * dscale); - const unsigned int color = duRGBA(cd,cd,cd,255); - dd->vertex(fx, fy, fz, color); - dd->vertex(fx, fy, fz+cs, color); - dd->vertex(fx+cs, fy, fz+cs, color); - dd->vertex(fx+cs, fy, fz, color); - } - } - } - dd->end(); -} - -static void drawLayerPortals(duDebugDraw* dd, const rcHeightfieldLayer* layer) -{ - const float cs = layer->cs; - const float ch = layer->ch; - const int w = layer->width; - const int h = layer->height; - - unsigned int pcol = duRGBA(255,255,255,255); - - const int segs[4*4] = {0,0,0,1, 0,1,1,1, 1,1,1,0, 1,0,0,0}; - - // Layer portals - dd->begin(DU_DRAW_LINES, 2.0f); - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int idx = x+y*w; - const int lh = (int)layer->heights[idx]; - if (lh == 255) continue; - - for (int dir = 0; dir < 4; ++dir) - { - if (layer->cons[idx] & (1<<(dir+4))) - { - const int* seg = &segs[dir*4]; - const float ax = layer->bmin[0] + (x+seg[0])*cs; - const float ay = layer->bmin[1] + (lh+2)*ch; - const float az = layer->bmin[2] + (y+seg[1])*cs; - const float bx = layer->bmin[0] + (x+seg[2])*cs; - const float by = layer->bmin[1] + (lh+2)*ch; - const float bz = layer->bmin[2] + (y+seg[3])*cs; - dd->vertex(ax, ay, az, pcol); - dd->vertex(bx, by, bz, pcol); - } - } - } - } - dd->end(); -} - -void duDebugDrawHeightfieldLayer(duDebugDraw* dd, const struct rcHeightfieldLayer& layer, const int idx) -{ - const float cs = layer.cs; - const float ch = layer.ch; - const int w = layer.width; - const int h = layer.height; - - unsigned int color = duIntToCol(idx+1, 255); - - // Layer bounds - float bmin[3], bmax[3]; - bmin[0] = layer.bmin[0] + layer.minx*cs; - bmin[1] = layer.bmin[1]; - bmin[2] = layer.bmin[2] + layer.miny*cs; - bmax[0] = layer.bmin[0] + (layer.maxx+1)*cs; - bmax[1] = layer.bmax[1]; - bmax[2] = layer.bmin[2] + (layer.maxy+1)*cs; - duDebugDrawBoxWire(dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duTransCol(color,128), 2.0f); - - // Layer height - dd->begin(DU_DRAW_QUADS); - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int lidx = x+y*w; - const int lh = (int)layer.heights[lidx]; - if (h == 0xff) continue; - const unsigned char area = layer.areas[lidx]; - - unsigned int col; - if (area == RC_WALKABLE_AREA) - col = duLerpCol(color, duRGBA(0,192,255,64), 32); - else if (area == RC_NULL_AREA) - col = duLerpCol(color, duRGBA(0,0,0,64), 32); - else - col = duLerpCol(color, dd->areaToCol(area), 32); - - const float fx = layer.bmin[0] + x*cs; - const float fy = layer.bmin[1] + (lh+1)*ch; - const float fz = layer.bmin[2] + y*cs; - - dd->vertex(fx, fy, fz, col); - dd->vertex(fx, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz, col); - } - } - dd->end(); - - // Portals - drawLayerPortals(dd, &layer); -} - -void duDebugDrawHeightfieldLayers(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset) -{ - if (!dd) return; - for (int i = 0; i < lset.nlayers; ++i) - duDebugDrawHeightfieldLayer(dd, lset.layers[i], i); -} - -/* -void duDebugDrawLayerContours(duDebugDraw* dd, const struct rcLayerContourSet& lcset) -{ - if (!dd) return; - - const float* orig = lcset.bmin; - const float cs = lcset.cs; - const float ch = lcset.ch; - - const unsigned char a = 255;// (unsigned char)(alpha*255.0f); - - const int offs[2*4] = {-1,0, 0,1, 1,0, 0,-1}; - - dd->begin(DU_DRAW_LINES, 2.0f); - - for (int i = 0; i < lcset.nconts; ++i) - { - const rcLayerContour& c = lcset.conts[i]; - unsigned int color = 0; - - color = duIntToCol(i, a); - - for (int j = 0; j < c.nverts; ++j) - { - const int k = (j+1) % c.nverts; - const unsigned char* va = &c.verts[j*4]; - const unsigned char* vb = &c.verts[k*4]; - const float ax = orig[0] + va[0]*cs; - const float ay = orig[1] + (va[1]+1+(i&1))*ch; - const float az = orig[2] + va[2]*cs; - const float bx = orig[0] + vb[0]*cs; - const float by = orig[1] + (vb[1]+1+(i&1))*ch; - const float bz = orig[2] + vb[2]*cs; - unsigned int col = color; - if ((va[3] & 0xf) != 0xf) - { - col = duRGBA(255,255,255,128); - int d = va[3] & 0xf; - - const float cx = (ax+bx)*0.5f; - const float cy = (ay+by)*0.5f; - const float cz = (az+bz)*0.5f; - - const float dx = cx + offs[d*2+0]*2*cs; - const float dy = cy; - const float dz = cz + offs[d*2+1]*2*cs; - - dd->vertex(cx,cy,cz,duRGBA(255,0,0,255)); - dd->vertex(dx,dy,dz,duRGBA(255,0,0,255)); - } - - duAppendArrow(dd, ax,ay,az, bx,by,bz, 0.0f, cs*0.5f, col); - } - } - dd->end(); - - dd->begin(DU_DRAW_POINTS, 4.0f); - - for (int i = 0; i < lcset.nconts; ++i) - { - const rcLayerContour& c = lcset.conts[i]; - unsigned int color = 0; - - for (int j = 0; j < c.nverts; ++j) - { - const unsigned char* va = &c.verts[j*4]; - - color = duDarkenCol(duIntToCol(i, a)); - if (va[3] & 0x80) - color = duRGBA(255,0,0,255); - - float fx = orig[0] + va[0]*cs; - float fy = orig[1] + (va[1]+1+(i&1))*ch; - float fz = orig[2] + va[2]*cs; - dd->vertex(fx,fy,fz, color); - } - } - dd->end(); -} - -void duDebugDrawLayerPolyMesh(duDebugDraw* dd, const struct rcLayerPolyMesh& lmesh) -{ - if (!dd) return; - - const int nvp = lmesh.nvp; - const float cs = lmesh.cs; - const float ch = lmesh.ch; - const float* orig = lmesh.bmin; - - const int offs[2*4] = {-1,0, 0,1, 1,0, 0,-1}; - - dd->begin(DU_DRAW_TRIS); - - for (int i = 0; i < lmesh.npolys; ++i) - { - const unsigned short* p = &lmesh.polys[i*nvp*2]; - - unsigned int color; - if (lmesh.areas[i] == RC_WALKABLE_AREA) - color = duRGBA(0,192,255,64); - else if (lmesh.areas[i] == RC_NULL_AREA) - color = duRGBA(0,0,0,64); - else - color = duIntToCol(lmesh.areas[i], 255); - - unsigned short vi[3]; - for (int j = 2; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) break; - vi[0] = p[0]; - vi[1] = p[j-1]; - vi[2] = p[j]; - for (int k = 0; k < 3; ++k) - { - const unsigned short* v = &lmesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch; - const float z = orig[2] + v[2]*cs; - dd->vertex(x,y,z, color); - } - } - } - dd->end(); - - // Draw neighbours edges - const unsigned int coln = duRGBA(0,48,64,32); - dd->begin(DU_DRAW_LINES, 1.5f); - for (int i = 0; i < lmesh.npolys; ++i) - { - const unsigned short* p = &lmesh.polys[i*nvp*2]; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) break; - if (p[nvp+j] & 0x8000) continue; - const int nj = (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) ? 0 : j+1; - int vi[2] = {p[j], p[nj]}; - - for (int k = 0; k < 2; ++k) - { - const unsigned short* v = &lmesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x, y, z, coln); - } - } - } - dd->end(); - - // Draw boundary edges - const unsigned int colb = duRGBA(0,48,64,220); - dd->begin(DU_DRAW_LINES, 2.5f); - for (int i = 0; i < lmesh.npolys; ++i) - { - const unsigned short* p = &lmesh.polys[i*nvp*2]; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) break; - if ((p[nvp+j] & 0x8000) == 0) continue; - const int nj = (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) ? 0 : j+1; - int vi[2] = {p[j], p[nj]}; - - unsigned int col = colb; - if ((p[nvp+j] & 0xf) != 0xf) - { - const unsigned short* va = &lmesh.verts[vi[0]*3]; - const unsigned short* vb = &lmesh.verts[vi[1]*3]; - - const float ax = orig[0] + va[0]*cs; - const float ay = orig[1] + (va[1]+1+(i&1))*ch; - const float az = orig[2] + va[2]*cs; - const float bx = orig[0] + vb[0]*cs; - const float by = orig[1] + (vb[1]+1+(i&1))*ch; - const float bz = orig[2] + vb[2]*cs; - - const float cx = (ax+bx)*0.5f; - const float cy = (ay+by)*0.5f; - const float cz = (az+bz)*0.5f; - - int d = p[nvp+j] & 0xf; - - const float dx = cx + offs[d*2+0]*2*cs; - const float dy = cy; - const float dz = cz + offs[d*2+1]*2*cs; - - dd->vertex(cx,cy,cz,duRGBA(255,0,0,255)); - dd->vertex(dx,dy,dz,duRGBA(255,0,0,255)); - - col = duRGBA(255,255,255,128); - } - - for (int k = 0; k < 2; ++k) - { - const unsigned short* v = &lmesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x, y, z, col); - } - } - } - dd->end(); - - dd->begin(DU_DRAW_POINTS, 3.0f); - const unsigned int colv = duRGBA(0,0,0,220); - for (int i = 0; i < lmesh.nverts; ++i) - { - const unsigned short* v = &lmesh.verts[i*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x,y,z, colv); - } - dd->end(); -} -*/ - -static void getContourCenter(const rcContour* cont, const float* orig, float cs, float ch, float* center) -{ - center[0] = 0; - center[1] = 0; - center[2] = 0; - if (!cont->nverts) - return; - for (int i = 0; i < cont->nverts; ++i) - { - const int* v = &cont->verts[i*4]; - center[0] += (float)v[0]; - center[1] += (float)v[1]; - center[2] += (float)v[2]; - } - const float s = 1.0f / cont->nverts; - center[0] *= s * cs; - center[1] *= s * ch; - center[2] *= s * cs; - center[0] += orig[0]; - center[1] += orig[1] + 4*ch; - center[2] += orig[2]; -} - -static const rcContour* findContourFromSet(const rcContourSet& cset, unsigned short reg) -{ - for (int i = 0; i < cset.nconts; ++i) - { - if (cset.conts[i].reg == reg) - return &cset.conts[i]; - } - return 0; -} - -void duDebugDrawRegionConnections(duDebugDraw* dd, const rcContourSet& cset, const float alpha) -{ - if (!dd) return; - - const float* orig = cset.bmin; - const float cs = cset.cs; - const float ch = cset.ch; - - // Draw centers - float pos[3], pos2[3]; - - unsigned int color = duRGBA(0,0,0,196); - - dd->begin(DU_DRAW_LINES, 2.0f); - - for (int i = 0; i < cset.nconts; ++i) - { - const rcContour* cont = &cset.conts[i]; - getContourCenter(cont, orig, cs, ch, pos); - for (int j = 0; j < cont->nverts; ++j) - { - const int* v = &cont->verts[j*4]; - if (v[3] == 0 || (unsigned short)v[3] < cont->reg) continue; - const rcContour* cont2 = findContourFromSet(cset, (unsigned short)v[3]); - if (cont2) - { - getContourCenter(cont2, orig, cs, ch, pos2); - duAppendArc(dd, pos[0],pos[1],pos[2], pos2[0],pos2[1],pos2[2], 0.25f, 0.6f, 0.6f, color); - } - } - } - - dd->end(); - - unsigned char a = (unsigned char)(alpha * 255.0f); - - dd->begin(DU_DRAW_POINTS, 7.0f); - - for (int i = 0; i < cset.nconts; ++i) - { - const rcContour* cont = &cset.conts[i]; - unsigned int col = duDarkenCol(duIntToCol(cont->reg,a)); - getContourCenter(cont, orig, cs, ch, pos); - dd->vertex(pos, col); - } - dd->end(); -} - -void duDebugDrawRawContours(duDebugDraw* dd, const rcContourSet& cset, const float alpha) -{ - if (!dd) return; - - const float* orig = cset.bmin; - const float cs = cset.cs; - const float ch = cset.ch; - - const unsigned char a = (unsigned char)(alpha*255.0f); - - dd->begin(DU_DRAW_LINES, 2.0f); - - for (int i = 0; i < cset.nconts; ++i) - { - const rcContour& c = cset.conts[i]; - unsigned int color = duIntToCol(c.reg, a); - - for (int j = 0; j < c.nrverts; ++j) - { - const int* v = &c.rverts[j*4]; - float fx = orig[0] + v[0]*cs; - float fy = orig[1] + (v[1]+1+(i&1))*ch; - float fz = orig[2] + v[2]*cs; - dd->vertex(fx,fy,fz,color); - if (j > 0) - dd->vertex(fx,fy,fz,color); - } - // Loop last segment. - const int* v = &c.rverts[0]; - float fx = orig[0] + v[0]*cs; - float fy = orig[1] + (v[1]+1+(i&1))*ch; - float fz = orig[2] + v[2]*cs; - dd->vertex(fx,fy,fz,color); - } - dd->end(); - - dd->begin(DU_DRAW_POINTS, 2.0f); - - for (int i = 0; i < cset.nconts; ++i) - { - const rcContour& c = cset.conts[i]; - unsigned int color = duDarkenCol(duIntToCol(c.reg, a)); - - for (int j = 0; j < c.nrverts; ++j) - { - const int* v = &c.rverts[j*4]; - float off = 0; - unsigned int colv = color; - if (v[3] & RC_BORDER_VERTEX) - { - colv = duRGBA(255,255,255,a); - off = ch*2; - } - - float fx = orig[0] + v[0]*cs; - float fy = orig[1] + (v[1]+1+(i&1))*ch + off; - float fz = orig[2] + v[2]*cs; - dd->vertex(fx,fy,fz, colv); - } - } - dd->end(); -} - -void duDebugDrawContours(duDebugDraw* dd, const rcContourSet& cset, const float alpha) -{ - if (!dd) return; - - const float* orig = cset.bmin; - const float cs = cset.cs; - const float ch = cset.ch; - - const unsigned char a = (unsigned char)(alpha*255.0f); - - dd->begin(DU_DRAW_LINES, 2.5f); - - for (int i = 0; i < cset.nconts; ++i) - { - const rcContour& c = cset.conts[i]; - if (!c.nverts) - continue; - const unsigned int color = duIntToCol(c.reg, a); - const unsigned int bcolor = duLerpCol(color,duRGBA(255,255,255,a),128); - for (int j = 0, k = c.nverts-1; j < c.nverts; k=j++) - { - const int* va = &c.verts[k*4]; - const int* vb = &c.verts[j*4]; - unsigned int col = (va[3] & RC_AREA_BORDER) ? bcolor : color; - float fx,fy,fz; - fx = orig[0] + va[0]*cs; - fy = orig[1] + (va[1]+1+(i&1))*ch; - fz = orig[2] + va[2]*cs; - dd->vertex(fx,fy,fz, col); - fx = orig[0] + vb[0]*cs; - fy = orig[1] + (vb[1]+1+(i&1))*ch; - fz = orig[2] + vb[2]*cs; - dd->vertex(fx,fy,fz, col); - } - } - dd->end(); - - dd->begin(DU_DRAW_POINTS, 3.0f); - - for (int i = 0; i < cset.nconts; ++i) - { - const rcContour& c = cset.conts[i]; - unsigned int color = duDarkenCol(duIntToCol(c.reg, a)); - for (int j = 0; j < c.nverts; ++j) - { - const int* v = &c.verts[j*4]; - float off = 0; - unsigned int colv = color; - if (v[3] & RC_BORDER_VERTEX) - { - colv = duRGBA(255,255,255,a); - off = ch*2; - } - - float fx = orig[0] + v[0]*cs; - float fy = orig[1] + (v[1]+1+(i&1))*ch + off; - float fz = orig[2] + v[2]*cs; - dd->vertex(fx,fy,fz, colv); - } - } - dd->end(); -} - -void duDebugDrawPolyMesh(duDebugDraw* dd, const struct rcPolyMesh& mesh) -{ - if (!dd) return; - - const int nvp = mesh.nvp; - const float cs = mesh.cs; - const float ch = mesh.ch; - const float* orig = mesh.bmin; - - dd->begin(DU_DRAW_TRIS); - - for (int i = 0; i < mesh.npolys; ++i) - { - const unsigned short* p = &mesh.polys[i*nvp*2]; - const unsigned char area = mesh.areas[i]; - - unsigned int color; - if (area == RC_WALKABLE_AREA) - color = duRGBA(0,192,255,64); - else if (area == RC_NULL_AREA) - color = duRGBA(0,0,0,64); - else - color = dd->areaToCol(area); - - unsigned short vi[3]; - for (int j = 2; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) break; - vi[0] = p[0]; - vi[1] = p[j-1]; - vi[2] = p[j]; - for (int k = 0; k < 3; ++k) - { - const unsigned short* v = &mesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch; - const float z = orig[2] + v[2]*cs; - dd->vertex(x,y,z, color); - } - } - } - dd->end(); - - // Draw neighbours edges - const unsigned int coln = duRGBA(0,48,64,32); - dd->begin(DU_DRAW_LINES, 1.5f); - for (int i = 0; i < mesh.npolys; ++i) - { - const unsigned short* p = &mesh.polys[i*nvp*2]; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) break; - if (p[nvp+j] & 0x8000) continue; - const int nj = (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) ? 0 : j+1; - const int vi[2] = {p[j], p[nj]}; - - for (int k = 0; k < 2; ++k) - { - const unsigned short* v = &mesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x, y, z, coln); - } - } - } - dd->end(); - - // Draw boundary edges - const unsigned int colb = duRGBA(0,48,64,220); - dd->begin(DU_DRAW_LINES, 2.5f); - for (int i = 0; i < mesh.npolys; ++i) - { - const unsigned short* p = &mesh.polys[i*nvp*2]; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) break; - if ((p[nvp+j] & 0x8000) == 0) continue; - const int nj = (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) ? 0 : j+1; - const int vi[2] = {p[j], p[nj]}; - - unsigned int col = colb; - if ((p[nvp+j] & 0xf) != 0xf) - col = duRGBA(255,255,255,128); - for (int k = 0; k < 2; ++k) - { - const unsigned short* v = &mesh.verts[vi[k]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x, y, z, col); - } - } - } - dd->end(); - - dd->begin(DU_DRAW_POINTS, 3.0f); - const unsigned int colv = duRGBA(0,0,0,220); - for (int i = 0; i < mesh.nverts; ++i) - { - const unsigned short* v = &mesh.verts[i*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - dd->vertex(x,y,z, colv); - } - dd->end(); -} - -void duDebugDrawPolyMeshDetail(duDebugDraw* dd, const struct rcPolyMeshDetail& dmesh) -{ - if (!dd) return; - - dd->begin(DU_DRAW_TRIS); - - for (int i = 0; i < dmesh.nmeshes; ++i) - { - const unsigned int* m = &dmesh.meshes[i*4]; - const unsigned int bverts = m[0]; - const unsigned int btris = m[2]; - const int ntris = (int)m[3]; - const float* verts = &dmesh.verts[bverts*3]; - const unsigned char* tris = &dmesh.tris[btris*4]; - - unsigned int color = duIntToCol(i, 192); - - for (int j = 0; j < ntris; ++j) - { - dd->vertex(&verts[tris[j*4+0]*3], color); - dd->vertex(&verts[tris[j*4+1]*3], color); - dd->vertex(&verts[tris[j*4+2]*3], color); - } - } - dd->end(); - - // Internal edges. - dd->begin(DU_DRAW_LINES, 1.0f); - const unsigned int coli = duRGBA(0,0,0,64); - for (int i = 0; i < dmesh.nmeshes; ++i) - { - const unsigned int* m = &dmesh.meshes[i*4]; - const unsigned int bverts = m[0]; - const unsigned int btris = m[2]; - const int ntris = (int)m[3]; - const float* verts = &dmesh.verts[bverts*3]; - const unsigned char* tris = &dmesh.tris[btris*4]; - - for (int j = 0; j < ntris; ++j) - { - const unsigned char* t = &tris[j*4]; - for (int k = 0, kp = 2; k < 3; kp=k++) - { - unsigned char ef = (t[3] >> (kp*2)) & 0x3; - if (ef == 0) - { - // Internal edge - if (t[kp] < t[k]) - { - dd->vertex(&verts[t[kp]*3], coli); - dd->vertex(&verts[t[k]*3], coli); - } - } - } - } - } - dd->end(); - - // External edges. - dd->begin(DU_DRAW_LINES, 2.0f); - const unsigned int cole = duRGBA(0,0,0,64); - for (int i = 0; i < dmesh.nmeshes; ++i) - { - const unsigned int* m = &dmesh.meshes[i*4]; - const unsigned int bverts = m[0]; - const unsigned int btris = m[2]; - const int ntris = (int)m[3]; - const float* verts = &dmesh.verts[bverts*3]; - const unsigned char* tris = &dmesh.tris[btris*4]; - - for (int j = 0; j < ntris; ++j) - { - const unsigned char* t = &tris[j*4]; - for (int k = 0, kp = 2; k < 3; kp=k++) - { - unsigned char ef = (t[3] >> (kp*2)) & 0x3; - if (ef != 0) - { - // Ext edge - dd->vertex(&verts[t[kp]*3], cole); - dd->vertex(&verts[t[k]*3], cole); - } - } - } - } - dd->end(); - - dd->begin(DU_DRAW_POINTS, 3.0f); - const unsigned int colv = duRGBA(0,0,0,64); - for (int i = 0; i < dmesh.nmeshes; ++i) - { - const unsigned int* m = &dmesh.meshes[i*4]; - const unsigned int bverts = m[0]; - const int nverts = (int)m[1]; - const float* verts = &dmesh.verts[bverts*3]; - for (int j = 0; j < nverts; ++j) - dd->vertex(&verts[j*3], colv); - } - dd->end(); -} diff --git a/extern/recastnavigation/DebugUtils/Source/RecastDump.cpp b/extern/recastnavigation/DebugUtils/Source/RecastDump.cpp deleted file mode 100644 index 2093825157..0000000000 --- a/extern/recastnavigation/DebugUtils/Source/RecastDump.cpp +++ /dev/null @@ -1,451 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastDump.h" - - -duFileIO::~duFileIO() -{ - // Empty -} - -static void ioprintf(duFileIO* io, const char* format, ...) -{ - char line[256]; - va_list ap; - va_start(ap, format); - const int n = vsnprintf(line, sizeof(line), format, ap); - va_end(ap); - if (n > 0) - io->write(line, sizeof(char)*n); -} - -bool duDumpPolyMeshToObj(rcPolyMesh& pmesh, duFileIO* io) -{ - if (!io) - { - printf("duDumpPolyMeshToObj: input IO is null.\n"); - return false; - } - if (!io->isWriting()) - { - printf("duDumpPolyMeshToObj: input IO not writing.\n"); - return false; - } - - const int nvp = pmesh.nvp; - const float cs = pmesh.cs; - const float ch = pmesh.ch; - const float* orig = pmesh.bmin; - - ioprintf(io, "# Recast Navmesh\n"); - ioprintf(io, "o NavMesh\n"); - - ioprintf(io, "\n"); - - for (int i = 0; i < pmesh.nverts; ++i) - { - const unsigned short* v = &pmesh.verts[i*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + (v[1]+1)*ch + 0.1f; - const float z = orig[2] + v[2]*cs; - ioprintf(io, "v %f %f %f\n", x,y,z); - } - - ioprintf(io, "\n"); - - for (int i = 0; i < pmesh.npolys; ++i) - { - const unsigned short* p = &pmesh.polys[i*nvp*2]; - for (int j = 2; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) break; - ioprintf(io, "f %d %d %d\n", p[0]+1, p[j-1]+1, p[j]+1); - } - } - - return true; -} - -bool duDumpPolyMeshDetailToObj(rcPolyMeshDetail& dmesh, duFileIO* io) -{ - if (!io) - { - printf("duDumpPolyMeshDetailToObj: input IO is null.\n"); - return false; - } - if (!io->isWriting()) - { - printf("duDumpPolyMeshDetailToObj: input IO not writing.\n"); - return false; - } - - ioprintf(io, "# Recast Navmesh\n"); - ioprintf(io, "o NavMesh\n"); - - ioprintf(io, "\n"); - - for (int i = 0; i < dmesh.nverts; ++i) - { - const float* v = &dmesh.verts[i*3]; - ioprintf(io, "v %f %f %f\n", v[0],v[1],v[2]); - } - - ioprintf(io, "\n"); - - for (int i = 0; i < dmesh.nmeshes; ++i) - { - const unsigned int* m = &dmesh.meshes[i*4]; - const unsigned int bverts = m[0]; - const unsigned int btris = m[2]; - const unsigned int ntris = m[3]; - const unsigned char* tris = &dmesh.tris[btris*4]; - for (unsigned int j = 0; j < ntris; ++j) - { - ioprintf(io, "f %d %d %d\n", - (int)(bverts+tris[j*4+0])+1, - (int)(bverts+tris[j*4+1])+1, - (int)(bverts+tris[j*4+2])+1); - } - } - - return true; -} - -static const int CSET_MAGIC = ('c' << 24) | ('s' << 16) | ('e' << 8) | 't'; -static const int CSET_VERSION = 2; - -bool duDumpContourSet(struct rcContourSet& cset, duFileIO* io) -{ - if (!io) - { - printf("duDumpContourSet: input IO is null.\n"); - return false; - } - if (!io->isWriting()) - { - printf("duDumpContourSet: input IO not writing.\n"); - return false; - } - - io->write(&CSET_MAGIC, sizeof(CSET_MAGIC)); - io->write(&CSET_VERSION, sizeof(CSET_VERSION)); - - io->write(&cset.nconts, sizeof(cset.nconts)); - - io->write(cset.bmin, sizeof(cset.bmin)); - io->write(cset.bmax, sizeof(cset.bmax)); - - io->write(&cset.cs, sizeof(cset.cs)); - io->write(&cset.ch, sizeof(cset.ch)); - - io->write(&cset.width, sizeof(cset.width)); - io->write(&cset.height, sizeof(cset.height)); - io->write(&cset.borderSize, sizeof(cset.borderSize)); - - for (int i = 0; i < cset.nconts; ++i) - { - const rcContour& cont = cset.conts[i]; - io->write(&cont.nverts, sizeof(cont.nverts)); - io->write(&cont.nrverts, sizeof(cont.nrverts)); - io->write(&cont.reg, sizeof(cont.reg)); - io->write(&cont.area, sizeof(cont.area)); - io->write(cont.verts, sizeof(int)*4*cont.nverts); - io->write(cont.rverts, sizeof(int)*4*cont.nrverts); - } - - return true; -} - -bool duReadContourSet(struct rcContourSet& cset, duFileIO* io) -{ - if (!io) - { - printf("duReadContourSet: input IO is null.\n"); - return false; - } - if (!io->isReading()) - { - printf("duReadContourSet: input IO not reading.\n"); - return false; - } - - int magic = 0; - int version = 0; - - io->read(&magic, sizeof(magic)); - io->read(&version, sizeof(version)); - - if (magic != CSET_MAGIC) - { - printf("duReadContourSet: Bad voodoo.\n"); - return false; - } - if (version != CSET_VERSION) - { - printf("duReadContourSet: Bad version.\n"); - return false; - } - - io->read(&cset.nconts, sizeof(cset.nconts)); - - cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*cset.nconts, RC_ALLOC_PERM); - if (!cset.conts) - { - printf("duReadContourSet: Could not alloc contours (%d)\n", cset.nconts); - return false; - } - memset(cset.conts, 0, sizeof(rcContour)*cset.nconts); - - io->read(cset.bmin, sizeof(cset.bmin)); - io->read(cset.bmax, sizeof(cset.bmax)); - - io->read(&cset.cs, sizeof(cset.cs)); - io->read(&cset.ch, sizeof(cset.ch)); - - io->read(&cset.width, sizeof(cset.width)); - io->read(&cset.height, sizeof(cset.height)); - io->read(&cset.borderSize, sizeof(cset.borderSize)); - - for (int i = 0; i < cset.nconts; ++i) - { - rcContour& cont = cset.conts[i]; - io->read(&cont.nverts, sizeof(cont.nverts)); - io->read(&cont.nrverts, sizeof(cont.nrverts)); - io->read(&cont.reg, sizeof(cont.reg)); - io->read(&cont.area, sizeof(cont.area)); - - cont.verts = (int*)rcAlloc(sizeof(int)*4*cont.nverts, RC_ALLOC_PERM); - if (!cont.verts) - { - printf("duReadContourSet: Could not alloc contour verts (%d)\n", cont.nverts); - return false; - } - cont.rverts = (int*)rcAlloc(sizeof(int)*4*cont.nrverts, RC_ALLOC_PERM); - if (!cont.rverts) - { - printf("duReadContourSet: Could not alloc contour rverts (%d)\n", cont.nrverts); - return false; - } - - io->read(cont.verts, sizeof(int)*4*cont.nverts); - io->read(cont.rverts, sizeof(int)*4*cont.nrverts); - } - - return true; -} - - -static const int CHF_MAGIC = ('r' << 24) | ('c' << 16) | ('h' << 8) | 'f'; -static const int CHF_VERSION = 3; - -bool duDumpCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io) -{ - if (!io) - { - printf("duDumpCompactHeightfield: input IO is null.\n"); - return false; - } - if (!io->isWriting()) - { - printf("duDumpCompactHeightfield: input IO not writing.\n"); - return false; - } - - io->write(&CHF_MAGIC, sizeof(CHF_MAGIC)); - io->write(&CHF_VERSION, sizeof(CHF_VERSION)); - - io->write(&chf.width, sizeof(chf.width)); - io->write(&chf.height, sizeof(chf.height)); - io->write(&chf.spanCount, sizeof(chf.spanCount)); - - io->write(&chf.walkableHeight, sizeof(chf.walkableHeight)); - io->write(&chf.walkableClimb, sizeof(chf.walkableClimb)); - io->write(&chf.borderSize, sizeof(chf.borderSize)); - - io->write(&chf.maxDistance, sizeof(chf.maxDistance)); - io->write(&chf.maxRegions, sizeof(chf.maxRegions)); - - io->write(chf.bmin, sizeof(chf.bmin)); - io->write(chf.bmax, sizeof(chf.bmax)); - - io->write(&chf.cs, sizeof(chf.cs)); - io->write(&chf.ch, sizeof(chf.ch)); - - int tmp = 0; - if (chf.cells) tmp |= 1; - if (chf.spans) tmp |= 2; - if (chf.dist) tmp |= 4; - if (chf.areas) tmp |= 8; - - io->write(&tmp, sizeof(tmp)); - - if (chf.cells) - io->write(chf.cells, sizeof(rcCompactCell)*chf.width*chf.height); - if (chf.spans) - io->write(chf.spans, sizeof(rcCompactSpan)*chf.spanCount); - if (chf.dist) - io->write(chf.dist, sizeof(unsigned short)*chf.spanCount); - if (chf.areas) - io->write(chf.areas, sizeof(unsigned char)*chf.spanCount); - - return true; -} - -bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io) -{ - if (!io) - { - printf("duReadCompactHeightfield: input IO is null.\n"); - return false; - } - if (!io->isReading()) - { - printf("duReadCompactHeightfield: input IO not reading.\n"); - return false; - } - - int magic = 0; - int version = 0; - - io->read(&magic, sizeof(magic)); - io->read(&version, sizeof(version)); - - if (magic != CHF_MAGIC) - { - printf("duReadCompactHeightfield: Bad voodoo.\n"); - return false; - } - if (version != CHF_VERSION) - { - printf("duReadCompactHeightfield: Bad version.\n"); - return false; - } - - io->read(&chf.width, sizeof(chf.width)); - io->read(&chf.height, sizeof(chf.height)); - io->read(&chf.spanCount, sizeof(chf.spanCount)); - - io->read(&chf.walkableHeight, sizeof(chf.walkableHeight)); - io->read(&chf.walkableClimb, sizeof(chf.walkableClimb)); - io->read(&chf.borderSize, sizeof(chf.borderSize)); - - io->read(&chf.maxDistance, sizeof(chf.maxDistance)); - io->read(&chf.maxRegions, sizeof(chf.maxRegions)); - - io->read(chf.bmin, sizeof(chf.bmin)); - io->read(chf.bmax, sizeof(chf.bmax)); - - io->read(&chf.cs, sizeof(chf.cs)); - io->read(&chf.ch, sizeof(chf.ch)); - - int tmp = 0; - io->read(&tmp, sizeof(tmp)); - - if (tmp & 1) - { - chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*chf.width*chf.height, RC_ALLOC_PERM); - if (!chf.cells) - { - printf("duReadCompactHeightfield: Could not alloc cells (%d)\n", chf.width*chf.height); - return false; - } - io->read(chf.cells, sizeof(rcCompactCell)*chf.width*chf.height); - } - if (tmp & 2) - { - chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*chf.spanCount, RC_ALLOC_PERM); - if (!chf.spans) - { - printf("duReadCompactHeightfield: Could not alloc spans (%d)\n", chf.spanCount); - return false; - } - io->read(chf.spans, sizeof(rcCompactSpan)*chf.spanCount); - } - if (tmp & 4) - { - chf.dist = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_PERM); - if (!chf.dist) - { - printf("duReadCompactHeightfield: Could not alloc dist (%d)\n", chf.spanCount); - return false; - } - io->read(chf.dist, sizeof(unsigned short)*chf.spanCount); - } - if (tmp & 8) - { - chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_PERM); - if (!chf.areas) - { - printf("duReadCompactHeightfield: Could not alloc areas (%d)\n", chf.spanCount); - return false; - } - io->read(chf.areas, sizeof(unsigned char)*chf.spanCount); - } - - return true; -} - - -static void logLine(rcContext& ctx, rcTimerLabel label, const char* name, const float pc) -{ - const int t = ctx.getAccumulatedTime(label); - if (t < 0) return; - ctx.log(RC_LOG_PROGRESS, "%s:\t%.2fms\t(%.1f%%)", name, t/1000.0f, t*pc); -} - -void duLogBuildTimes(rcContext& ctx, const int totalTimeUsec) -{ - const float pc = 100.0f / totalTimeUsec; - - ctx.log(RC_LOG_PROGRESS, "Build Times"); - logLine(ctx, RC_TIMER_RASTERIZE_TRIANGLES, "- Rasterize", pc); - logLine(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD, "- Build Compact", pc); - logLine(ctx, RC_TIMER_FILTER_BORDER, "- Filter Border", pc); - logLine(ctx, RC_TIMER_FILTER_WALKABLE, "- Filter Walkable", pc); - logLine(ctx, RC_TIMER_ERODE_AREA, "- Erode Area", pc); - logLine(ctx, RC_TIMER_MEDIAN_AREA, "- Median Area", pc); - logLine(ctx, RC_TIMER_MARK_BOX_AREA, "- Mark Box Area", pc); - logLine(ctx, RC_TIMER_MARK_CONVEXPOLY_AREA, "- Mark Convex Area", pc); - logLine(ctx, RC_TIMER_MARK_CYLINDER_AREA, "- Mark Cylinder Area", pc); - logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD, "- Build Distance Field", pc); - logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD_DIST, " - Distance", pc); - logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD_BLUR, " - Blur", pc); - logLine(ctx, RC_TIMER_BUILD_REGIONS, "- Build Regions", pc); - logLine(ctx, RC_TIMER_BUILD_REGIONS_WATERSHED, " - Watershed", pc); - logLine(ctx, RC_TIMER_BUILD_REGIONS_EXPAND, " - Expand", pc); - logLine(ctx, RC_TIMER_BUILD_REGIONS_FLOOD, " - Find Basins", pc); - logLine(ctx, RC_TIMER_BUILD_REGIONS_FILTER, " - Filter", pc); - logLine(ctx, RC_TIMER_BUILD_LAYERS, "- Build Layers", pc); - logLine(ctx, RC_TIMER_BUILD_CONTOURS, "- Build Contours", pc); - logLine(ctx, RC_TIMER_BUILD_CONTOURS_TRACE, " - Trace", pc); - logLine(ctx, RC_TIMER_BUILD_CONTOURS_SIMPLIFY, " - Simplify", pc); - logLine(ctx, RC_TIMER_BUILD_POLYMESH, "- Build Polymesh", pc); - logLine(ctx, RC_TIMER_BUILD_POLYMESHDETAIL, "- Build Polymesh Detail", pc); - logLine(ctx, RC_TIMER_MERGE_POLYMESH, "- Merge Polymeshes", pc); - logLine(ctx, RC_TIMER_MERGE_POLYMESHDETAIL, "- Merge Polymesh Details", pc); - ctx.log(RC_LOG_PROGRESS, "=== TOTAL:\t%.2fms", totalTimeUsec/1000.0f); -} - diff --git a/extern/recastnavigation/Detour/CMakeLists.txt b/extern/recastnavigation/Detour/CMakeLists.txt deleted file mode 100644 index 5cb47ec0e2..0000000000 --- a/extern/recastnavigation/Detour/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -file(GLOB SOURCES Source/*.cpp) -add_library(Detour ${SOURCES}) - -add_library(RecastNavigation::Detour ALIAS Detour) -set_target_properties(Detour PROPERTIES DEBUG_POSTFIX -d) - -set(Detour_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") - -target_include_directories(Detour PUBLIC - "$" -) - -set_target_properties(Detour PROPERTIES - SOVERSION ${SOVERSION} - VERSION ${VERSION} - COMPILE_PDB_OUTPUT_DIRECTORY . - COMPILE_PDB_NAME "Detour-d" - ) - -install(TARGETS Detour - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT library - ) - -file(GLOB INCLUDES Include/*.h) -install(FILES ${INCLUDES} DESTINATION - ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) -install(FILES "$/Detour-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") diff --git a/extern/recastnavigation/Detour/Include/DetourAlloc.h b/extern/recastnavigation/Detour/Include/DetourAlloc.h deleted file mode 100644 index f87b454acb..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourAlloc.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURALLOCATOR_H -#define DETOURALLOCATOR_H - -#include - -/// Provides hint values to the memory allocator on how long the -/// memory is expected to be used. -enum dtAllocHint -{ - DT_ALLOC_PERM, ///< Memory persist after a function call. - DT_ALLOC_TEMP ///< Memory used temporarily within a function. -}; - -/// A memory allocation function. -// @param[in] size The size, in bytes of memory, to allocate. -// @param[in] rcAllocHint A hint to the allocator on how long the memory is expected to be in use. -// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. -/// @see dtAllocSetCustom -typedef void* (dtAllocFunc)(size_t size, dtAllocHint hint); - -/// A memory deallocation function. -/// @param[in] ptr A pointer to a memory block previously allocated using #dtAllocFunc. -/// @see dtAllocSetCustom -typedef void (dtFreeFunc)(void* ptr); - -/// Sets the base custom allocation functions to be used by Detour. -/// @param[in] allocFunc The memory allocation function to be used by #dtAlloc -/// @param[in] freeFunc The memory de-allocation function to be used by #dtFree -void dtAllocSetCustom(dtAllocFunc *allocFunc, dtFreeFunc *freeFunc); - -/// Allocates a memory block. -/// @param[in] size The size, in bytes of memory, to allocate. -/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. -/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. -/// @see dtFree -void* dtAlloc(size_t size, dtAllocHint hint); - -/// Deallocates a memory block. -/// @param[in] ptr A pointer to a memory block previously allocated using #dtAlloc. -/// @see dtAlloc -void dtFree(void* ptr); - -#endif diff --git a/extern/recastnavigation/Detour/Include/DetourAssert.h b/extern/recastnavigation/Detour/Include/DetourAssert.h deleted file mode 100644 index e05fd66fa5..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourAssert.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURASSERT_H -#define DETOURASSERT_H - -// Note: This header file's only purpose is to include define assert. -// Feel free to change the file and include your own implementation instead. - -#ifdef NDEBUG - -// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ -# define dtAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false) - -#else - -/// An assertion failure function. -// @param[in] expression asserted expression. -// @param[in] file Filename of the failed assertion. -// @param[in] line Line number of the failed assertion. -/// @see dtAssertFailSetCustom -typedef void (dtAssertFailFunc)(const char* expression, const char* file, int line); - -/// Sets the base custom assertion failure function to be used by Detour. -/// @param[in] assertFailFunc The function to be invoked in case of failure of #dtAssert -void dtAssertFailSetCustom(dtAssertFailFunc *assertFailFunc); - -/// Gets the base custom assertion failure function to be used by Detour. -dtAssertFailFunc* dtAssertFailGetCustom(); - -# include -# define dtAssert(expression) \ - { \ - dtAssertFailFunc* failFunc = dtAssertFailGetCustom(); \ - if(failFunc == NULL) { assert(expression); } \ - else if(!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \ - } - -#endif - -#endif // DETOURASSERT_H diff --git a/extern/recastnavigation/Detour/Include/DetourCommon.h b/extern/recastnavigation/Detour/Include/DetourCommon.h deleted file mode 100644 index 113e8c3361..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourCommon.h +++ /dev/null @@ -1,572 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURCOMMON_H -#define DETOURCOMMON_H - -#include "DetourMath.h" -#include - -/** -@defgroup detour Detour - -Members in this module are used to create, manipulate, and query navigation -meshes. - -@note This is a summary list of members. Use the index or search -feature to find minor members. -*/ - -/// @name General helper functions -/// @{ - -/// Used to ignore a function parameter. VS complains about unused parameters -/// and this silences the warning. -/// @param [in] _ Unused parameter -template void dtIgnoreUnused(const T&) { } - -/// Swaps the values of the two parameters. -/// @param[in,out] a Value A -/// @param[in,out] b Value B -template inline void dtSwap(T& a, T& b) { T t = a; a = b; b = t; } - -/// Returns the minimum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The minimum of the two values. -template inline T dtMin(T a, T b) { return a < b ? a : b; } - -/// Returns the maximum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The maximum of the two values. -template inline T dtMax(T a, T b) { return a > b ? a : b; } - -/// Returns the absolute value. -/// @param[in] a The value. -/// @return The absolute value of the specified value. -template inline T dtAbs(T a) { return a < 0 ? -a : a; } - -/// Returns the square of the value. -/// @param[in] a The value. -/// @return The square of the value. -template inline T dtSqr(T a) { return a*a; } - -/// Clamps the value to the specified range. -/// @param[in] v The value to clamp. -/// @param[in] mn The minimum permitted return value. -/// @param[in] mx The maximum permitted return value. -/// @return The value, clamped to the specified range. -template inline T dtClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); } - -/// @} -/// @name Vector helper functions. -/// @{ - -/// Derives the cross product of two vectors. (@p v1 x @p v2) -/// @param[out] dest The cross product. [(x, y, z)] -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] -inline void dtVcross(float* dest, const float* v1, const float* v2) -{ - dest[0] = v1[1]*v2[2] - v1[2]*v2[1]; - dest[1] = v1[2]*v2[0] - v1[0]*v2[2]; - dest[2] = v1[0]*v2[1] - v1[1]*v2[0]; -} - -/// Derives the dot product of two vectors. (@p v1 . @p v2) -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] -/// @return The dot product. -inline float dtVdot(const float* v1, const float* v2) -{ - return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; -} - -/// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s)) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] -/// @param[in] s The amount to scale @p v2 by before adding to @p v1. -inline void dtVmad(float* dest, const float* v1, const float* v2, const float s) -{ - dest[0] = v1[0]+v2[0]*s; - dest[1] = v1[1]+v2[1]*s; - dest[2] = v1[2]+v2[2]*s; -} - -/// Performs a linear interpolation between two vectors. (@p v1 toward @p v2) -/// @param[out] dest The result vector. [(x, y, x)] -/// @param[in] v1 The starting vector. -/// @param[in] v2 The destination vector. -/// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0] -inline void dtVlerp(float* dest, const float* v1, const float* v2, const float t) -{ - dest[0] = v1[0]+(v2[0]-v1[0])*t; - dest[1] = v1[1]+(v2[1]-v1[1])*t; - dest[2] = v1[2]+(v2[2]-v1[2])*t; -} - -/// Performs a vector addition. (@p v1 + @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] -inline void dtVadd(float* dest, const float* v1, const float* v2) -{ - dest[0] = v1[0]+v2[0]; - dest[1] = v1[1]+v2[1]; - dest[2] = v1[2]+v2[2]; -} - -/// Performs a vector subtraction. (@p v1 - @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] -inline void dtVsub(float* dest, const float* v1, const float* v2) -{ - dest[0] = v1[0]-v2[0]; - dest[1] = v1[1]-v2[1]; - dest[2] = v1[2]-v2[2]; -} - -/// Scales the vector by the specified value. (@p v * @p t) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v The vector to scale. [(x, y, z)] -/// @param[in] t The scaling factor. -inline void dtVscale(float* dest, const float* v, const float t) -{ - dest[0] = v[0]*t; - dest[1] = v[1]*t; - dest[2] = v[2]*t; -} - -/// Selects the minimum value of each element from the specified vectors. -/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] -inline void dtVmin(float* mn, const float* v) -{ - mn[0] = dtMin(mn[0], v[0]); - mn[1] = dtMin(mn[1], v[1]); - mn[2] = dtMin(mn[2], v[2]); -} - -/// Selects the maximum value of each element from the specified vectors. -/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] -inline void dtVmax(float* mx, const float* v) -{ - mx[0] = dtMax(mx[0], v[0]); - mx[1] = dtMax(mx[1], v[1]); - mx[2] = dtMax(mx[2], v[2]); -} - -/// Sets the vector elements to the specified values. -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] x The x-value of the vector. -/// @param[in] y The y-value of the vector. -/// @param[in] z The z-value of the vector. -inline void dtVset(float* dest, const float x, const float y, const float z) -{ - dest[0] = x; dest[1] = y; dest[2] = z; -} - -/// Performs a vector copy. -/// @param[out] dest The result. [(x, y, z)] -/// @param[in] a The vector to copy. [(x, y, z)] -inline void dtVcopy(float* dest, const float* a) -{ - dest[0] = a[0]; - dest[1] = a[1]; - dest[2] = a[2]; -} - -/// Derives the scalar length of the vector. -/// @param[in] v The vector. [(x, y, z)] -/// @return The scalar length of the vector. -inline float dtVlen(const float* v) -{ - return dtMathSqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); -} - -/// Derives the square of the scalar length of the vector. (len * len) -/// @param[in] v The vector. [(x, y, z)] -/// @return The square of the scalar length of the vector. -inline float dtVlenSqr(const float* v) -{ - return v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; -} - -/// Returns the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] -/// @return The distance between the two points. -inline float dtVdist(const float* v1, const float* v2) -{ - const float dx = v2[0] - v1[0]; - const float dy = v2[1] - v1[1]; - const float dz = v2[2] - v1[2]; - return dtMathSqrtf(dx*dx + dy*dy + dz*dz); -} - -/// Returns the square of the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] -/// @return The square of the distance between the two points. -inline float dtVdistSqr(const float* v1, const float* v2) -{ - const float dx = v2[0] - v1[0]; - const float dy = v2[1] - v1[1]; - const float dz = v2[2] - v1[2]; - return dx*dx + dy*dy + dz*dz; -} - -/// Derives the distance between the specified points on the xz-plane. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] -/// @return The distance between the point on the xz-plane. -/// -/// The vectors are projected onto the xz-plane, so the y-values are ignored. -inline float dtVdist2D(const float* v1, const float* v2) -{ - const float dx = v2[0] - v1[0]; - const float dz = v2[2] - v1[2]; - return dtMathSqrtf(dx*dx + dz*dz); -} - -/// Derives the square of the distance between the specified points on the xz-plane. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] -/// @return The square of the distance between the point on the xz-plane. -inline float dtVdist2DSqr(const float* v1, const float* v2) -{ - const float dx = v2[0] - v1[0]; - const float dz = v2[2] - v1[2]; - return dx*dx + dz*dz; -} - -/// Normalizes the vector. -/// @param[in,out] v The vector to normalize. [(x, y, z)] -inline void dtVnormalize(float* v) -{ - float d = 1.0f / dtMathSqrtf(dtSqr(v[0]) + dtSqr(v[1]) + dtSqr(v[2])); - v[0] *= d; - v[1] *= d; - v[2] *= d; -} - -/// Performs a 'sloppy' colocation check of the specified points. -/// @param[in] p0 A point. [(x, y, z)] -/// @param[in] p1 A point. [(x, y, z)] -/// @return True if the points are considered to be at the same location. -/// -/// Basically, this function will return true if the specified points are -/// close enough to eachother to be considered colocated. -inline bool dtVequal(const float* p0, const float* p1) -{ - static const float thr = dtSqr(1.0f/16384.0f); - const float d = dtVdistSqr(p0, p1); - return d < thr; -} - -/// Checks that the specified vector's components are all finite. -/// @param[in] v A point. [(x, y, z)] -/// @return True if all of the point's components are finite, i.e. not NaN -/// or any of the infinities. -inline bool dtVisfinite(const float* v) -{ - bool result = - dtMathIsfinite(v[0]) && - dtMathIsfinite(v[1]) && - dtMathIsfinite(v[2]); - - return result; -} - -/// Checks that the specified vector's 2D components are finite. -/// @param[in] v A point. [(x, y, z)] -inline bool dtVisfinite2D(const float* v) -{ - bool result = dtMathIsfinite(v[0]) && dtMathIsfinite(v[2]); - return result; -} - -/// Derives the dot product of two vectors on the xz-plane. (@p u . @p v) -/// @param[in] u A vector [(x, y, z)] -/// @param[in] v A vector [(x, y, z)] -/// @return The dot product on the xz-plane. -/// -/// The vectors are projected onto the xz-plane, so the y-values are ignored. -inline float dtVdot2D(const float* u, const float* v) -{ - return u[0]*v[0] + u[2]*v[2]; -} - -/// Derives the xz-plane 2D perp product of the two vectors. (uz*vx - ux*vz) -/// @param[in] u The LHV vector [(x, y, z)] -/// @param[in] v The RHV vector [(x, y, z)] -/// @return The dot product on the xz-plane. -/// -/// The vectors are projected onto the xz-plane, so the y-values are ignored. -inline float dtVperp2D(const float* u, const float* v) -{ - return u[2]*v[0] - u[0]*v[2]; -} - -/// @} -/// @name Computational geometry helper functions. -/// @{ - -/// Derives the signed xz-plane area of the triangle ABC, or the relationship of line AB to point C. -/// @param[in] a Vertex A. [(x, y, z)] -/// @param[in] b Vertex B. [(x, y, z)] -/// @param[in] c Vertex C. [(x, y, z)] -/// @return The signed xz-plane area of the triangle. -inline float dtTriArea2D(const float* a, const float* b, const float* c) -{ - const float abx = b[0] - a[0]; - const float abz = b[2] - a[2]; - const float acx = c[0] - a[0]; - const float acz = c[2] - a[2]; - return acx*abz - abx*acz; -} - -/// Determines if two axis-aligned bounding boxes overlap. -/// @param[in] amin Minimum bounds of box A. [(x, y, z)] -/// @param[in] amax Maximum bounds of box A. [(x, y, z)] -/// @param[in] bmin Minimum bounds of box B. [(x, y, z)] -/// @param[in] bmax Maximum bounds of box B. [(x, y, z)] -/// @return True if the two AABB's overlap. -/// @see dtOverlapBounds -inline bool dtOverlapQuantBounds(const unsigned short amin[3], const unsigned short amax[3], - const unsigned short bmin[3], const unsigned short bmax[3]) -{ - bool overlap = true; - overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; - overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; - overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; - return overlap; -} - -/// Determines if two axis-aligned bounding boxes overlap. -/// @param[in] amin Minimum bounds of box A. [(x, y, z)] -/// @param[in] amax Maximum bounds of box A. [(x, y, z)] -/// @param[in] bmin Minimum bounds of box B. [(x, y, z)] -/// @param[in] bmax Maximum bounds of box B. [(x, y, z)] -/// @return True if the two AABB's overlap. -/// @see dtOverlapQuantBounds -inline bool dtOverlapBounds(const float* amin, const float* amax, - const float* bmin, const float* bmax) -{ - bool overlap = true; - overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; - overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; - overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; - return overlap; -} - -/// Derives the closest point on a triangle from the specified reference point. -/// @param[out] closest The closest point on the triangle. -/// @param[in] p The reference point from which to test. [(x, y, z)] -/// @param[in] a Vertex A of triangle ABC. [(x, y, z)] -/// @param[in] b Vertex B of triangle ABC. [(x, y, z)] -/// @param[in] c Vertex C of triangle ABC. [(x, y, z)] -void dtClosestPtPointTriangle(float* closest, const float* p, - const float* a, const float* b, const float* c); - -/// Derives the y-axis height of the closest point on the triangle from the specified reference point. -/// @param[in] p The reference point from which to test. [(x, y, z)] -/// @param[in] a Vertex A of triangle ABC. [(x, y, z)] -/// @param[in] b Vertex B of triangle ABC. [(x, y, z)] -/// @param[in] c Vertex C of triangle ABC. [(x, y, z)] -/// @param[out] h The resulting height. -bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h); - -bool dtIntersectSegmentPoly2D(const float* p0, const float* p1, - const float* verts, int nverts, - float& tmin, float& tmax, - int& segMin, int& segMax); - -bool dtIntersectSegSeg2D(const float* ap, const float* aq, - const float* bp, const float* bq, - float& s, float& t); - -/// Determines if the specified point is inside the convex polygon on the xz-plane. -/// @param[in] pt The point to check. [(x, y, z)] -/// @param[in] verts The polygon vertices. [(x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices. [Limit: >= 3] -/// @return True if the point is inside the polygon. -bool dtPointInPolygon(const float* pt, const float* verts, const int nverts); - -bool dtDistancePtPolyEdgesSqr(const float* pt, const float* verts, const int nverts, - float* ed, float* et); - -float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, float& t); - -/// Derives the centroid of a convex polygon. -/// @param[out] tc The centroid of the polgyon. [(x, y, z)] -/// @param[in] idx The polygon indices. [(vertIndex) * @p nidx] -/// @param[in] nidx The number of indices in the polygon. [Limit: >= 3] -/// @param[in] verts The polygon vertices. [(x, y, z) * vertCount] -void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const float* verts); - -/// Determines if the two convex polygons overlap on the xz-plane. -/// @param[in] polya Polygon A vertices. [(x, y, z) * @p npolya] -/// @param[in] npolya The number of vertices in polygon A. -/// @param[in] polyb Polygon B vertices. [(x, y, z) * @p npolyb] -/// @param[in] npolyb The number of vertices in polygon B. -/// @return True if the two polygons overlap. -bool dtOverlapPolyPoly2D(const float* polya, const int npolya, - const float* polyb, const int npolyb); - -/// @} -/// @name Miscellanious functions. -/// @{ - -inline unsigned int dtNextPow2(unsigned int v) -{ - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v++; - return v; -} - -inline unsigned int dtIlog2(unsigned int v) -{ - unsigned int r; - unsigned int shift; - r = (v > 0xffff) << 4; v >>= r; - shift = (v > 0xff) << 3; v >>= shift; r |= shift; - shift = (v > 0xf) << 2; v >>= shift; r |= shift; - shift = (v > 0x3) << 1; v >>= shift; r |= shift; - r |= (v >> 1); - return r; -} - -inline int dtAlign4(int x) { return (x+3) & ~3; } - -inline int dtOppositeTile(int side) { return (side+4) & 0x7; } - -inline void dtSwapByte(unsigned char* a, unsigned char* b) -{ - unsigned char tmp = *a; - *a = *b; - *b = tmp; -} - -inline void dtSwapEndian(unsigned short* v) -{ - unsigned char* x = (unsigned char*)v; - dtSwapByte(x+0, x+1); -} - -inline void dtSwapEndian(short* v) -{ - unsigned char* x = (unsigned char*)v; - dtSwapByte(x+0, x+1); -} - -inline void dtSwapEndian(unsigned int* v) -{ - unsigned char* x = (unsigned char*)v; - dtSwapByte(x+0, x+3); dtSwapByte(x+1, x+2); -} - -inline void dtSwapEndian(int* v) -{ - unsigned char* x = (unsigned char*)v; - dtSwapByte(x+0, x+3); dtSwapByte(x+1, x+2); -} - -inline void dtSwapEndian(float* v) -{ - unsigned char* x = (unsigned char*)v; - dtSwapByte(x+0, x+3); dtSwapByte(x+1, x+2); -} - -void dtRandomPointInConvexPoly(const float* pts, const int npts, float* areas, - const float s, const float t, float* out); - -template -TypeToRetrieveAs* dtGetThenAdvanceBufferPointer(const unsigned char*& buffer, const size_t distanceToAdvance) -{ - TypeToRetrieveAs* returnPointer = reinterpret_cast(buffer); - buffer += distanceToAdvance; - return returnPointer; -} - -template -TypeToRetrieveAs* dtGetThenAdvanceBufferPointer(unsigned char*& buffer, const size_t distanceToAdvance) -{ - TypeToRetrieveAs* returnPointer = reinterpret_cast(buffer); - buffer += distanceToAdvance; - return returnPointer; -} - - -/// @} - -#endif // DETOURCOMMON_H - -/////////////////////////////////////////////////////////////////////////// - -// This section contains detailed documentation for members that don't have -// a source file. It reduces clutter in the main section of the header. - -/** - -@fn float dtTriArea2D(const float* a, const float* b, const float* c) -@par - -The vertices are projected onto the xz-plane, so the y-values are ignored. - -This is a low cost function than can be used for various purposes. Its main purpose -is for point/line relationship testing. - -In all cases: A value of zero indicates that all vertices are collinear or represent the same point. -(On the xz-plane.) - -When used for point/line relationship tests, AB usually represents a line against which -the C point is to be tested. In this case: - -A positive value indicates that point C is to the left of line AB, looking from A toward B.
-A negative value indicates that point C is to the right of lineAB, looking from A toward B. - -When used for evaluating a triangle: - -The absolute value of the return value is two times the area of the triangle when it is -projected onto the xz-plane. - -A positive return value indicates: - -
    -
  • The vertices are wrapped in the normal Detour wrap direction.
  • -
  • The triangle's 3D face normal is in the general up direction.
  • -
- -A negative return value indicates: - -
    -
  • The vertices are reverse wrapped. (Wrapped opposite the normal Detour wrap direction.)
  • -
  • The triangle's 3D face normal is in the general down direction.
  • -
- -*/ diff --git a/extern/recastnavigation/Detour/Include/DetourMath.h b/extern/recastnavigation/Detour/Include/DetourMath.h deleted file mode 100644 index 54af8af095..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourMath.h +++ /dev/null @@ -1,24 +0,0 @@ -/** -@defgroup detour Detour - -Members in this module are wrappers around the standard math library -*/ - -#ifndef DETOURMATH_H -#define DETOURMATH_H - -#include -// This include is required because libstdc++ has problems with isfinite -// if cmath is included before math.h. -#include - -inline float dtMathFabsf(float x) { return fabsf(x); } -inline float dtMathSqrtf(float x) { return sqrtf(x); } -inline float dtMathFloorf(float x) { return floorf(x); } -inline float dtMathCeilf(float x) { return ceilf(x); } -inline float dtMathCosf(float x) { return cosf(x); } -inline float dtMathSinf(float x) { return sinf(x); } -inline float dtMathAtan2f(float y, float x) { return atan2f(y, x); } -inline bool dtMathIsfinite(float x) { return std::isfinite(x); } - -#endif diff --git a/extern/recastnavigation/Detour/Include/DetourNavMesh.h b/extern/recastnavigation/Detour/Include/DetourNavMesh.h deleted file mode 100644 index caf77eb1b3..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourNavMesh.h +++ /dev/null @@ -1,785 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURNAVMESH_H -#define DETOURNAVMESH_H - -#include "DetourAlloc.h" -#include "DetourStatus.h" - -// Undefine (or define in a build cofnig) the following line to use 64bit polyref. -// Generally not needed, useful for very large worlds. -// Note: tiles build using 32bit refs are not compatible with 64bit refs! -//#define DT_POLYREF64 1 - -#ifdef DT_POLYREF64 -// TODO: figure out a multiplatform version of uint64_t -// - maybe: https://code.google.com/p/msinttypes/ -// - or: http://www.azillionmonkeys.com/qed/pstdint.h -#include -#endif - -// Note: If you want to use 64-bit refs, change the types of both dtPolyRef & dtTileRef. -// It is also recommended that you change dtHashRef() to a proper 64-bit hash. - -/// A handle to a polygon within a navigation mesh tile. -/// @ingroup detour -#ifdef DT_POLYREF64 -static const unsigned int DT_SALT_BITS = 16; -static const unsigned int DT_TILE_BITS = 28; -static const unsigned int DT_POLY_BITS = 20; -typedef uint64_t dtPolyRef; -#else -typedef unsigned int dtPolyRef; -#endif - -/// A handle to a tile within a navigation mesh. -/// @ingroup detour -#ifdef DT_POLYREF64 -typedef uint64_t dtTileRef; -#else -typedef unsigned int dtTileRef; -#endif - -/// The maximum number of vertices per navigation polygon. -/// @ingroup detour -static const int DT_VERTS_PER_POLYGON = 6; - -/// @{ -/// @name Tile Serialization Constants -/// These constants are used to detect whether a navigation tile's data -/// and state format is compatible with the current build. -/// - -/// A magic number used to detect compatibility of navigation tile data. -static const int DT_NAVMESH_MAGIC = 'D'<<24 | 'N'<<16 | 'A'<<8 | 'V'; - -/// A version number used to detect compatibility of navigation tile data. -static const int DT_NAVMESH_VERSION = 7; - -/// A magic number used to detect the compatibility of navigation tile states. -static const int DT_NAVMESH_STATE_MAGIC = 'D'<<24 | 'N'<<16 | 'M'<<8 | 'S'; - -/// A version number used to detect compatibility of navigation tile states. -static const int DT_NAVMESH_STATE_VERSION = 1; - -/// @} - -/// A flag that indicates that an entity links to an external entity. -/// (E.g. A polygon edge is a portal that links to another polygon.) -static const unsigned short DT_EXT_LINK = 0x8000; - -/// A value that indicates the entity does not link to anything. -static const unsigned int DT_NULL_LINK = 0xffffffff; - -/// A flag that indicates that an off-mesh connection can be traversed in both directions. (Is bidirectional.) -static const unsigned int DT_OFFMESH_CON_BIDIR = 1; - -/// The maximum number of user defined area ids. -/// @ingroup detour -static const int DT_MAX_AREAS = 64; - -/// Tile flags used for various functions and fields. -/// For an example, see dtNavMesh::addTile(). -enum dtTileFlags -{ - /// The navigation mesh owns the tile memory and is responsible for freeing it. - DT_TILE_FREE_DATA = 0x01, -}; - -/// Vertex flags returned by dtNavMeshQuery::findStraightPath. -enum dtStraightPathFlags -{ - DT_STRAIGHTPATH_START = 0x01, ///< The vertex is the start position in the path. - DT_STRAIGHTPATH_END = 0x02, ///< The vertex is the end position in the path. - DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04, ///< The vertex is the start of an off-mesh connection. -}; - -/// Options for dtNavMeshQuery::findStraightPath. -enum dtStraightPathOptions -{ - DT_STRAIGHTPATH_AREA_CROSSINGS = 0x01, ///< Add a vertex at every polygon edge crossing where area changes. - DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02, ///< Add a vertex at every polygon edge crossing. -}; - - -/// Options for dtNavMeshQuery::initSlicedFindPath and updateSlicedFindPath -enum dtFindPathOptions -{ - DT_FINDPATH_ANY_ANGLE = 0x02, ///< use raycasts during pathfind to "shortcut" (raycast still consider costs) -}; - -/// Options for dtNavMeshQuery::raycast -enum dtRaycastOptions -{ - DT_RAYCAST_USE_COSTS = 0x01, ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost -}; - -enum dtDetailTriEdgeFlags -{ - DT_DETAIL_EDGE_BOUNDARY = 0x01, ///< Detail triangle edge is part of the poly boundary -}; - - -/// Limit raycasting during any angle pahfinding -/// The limit is given as a multiple of the character radius -static const float DT_RAY_CAST_LIMIT_PROPORTIONS = 50.0f; - -/// Flags representing the type of a navigation mesh polygon. -enum dtPolyTypes -{ - /// The polygon is a standard convex polygon that is part of the surface of the mesh. - DT_POLYTYPE_GROUND = 0, - /// The polygon is an off-mesh connection consisting of two vertices. - DT_POLYTYPE_OFFMESH_CONNECTION = 1, -}; - - -/// Defines a polygon within a dtMeshTile object. -/// @ingroup detour -struct dtPoly -{ - /// Index to first link in linked list. (Or #DT_NULL_LINK if there is no link.) - unsigned int firstLink; - - /// The indices of the polygon's vertices. - /// The actual vertices are located in dtMeshTile::verts. - unsigned short verts[DT_VERTS_PER_POLYGON]; - - /// Packed data representing neighbor polygons references and flags for each edge. - unsigned short neis[DT_VERTS_PER_POLYGON]; - - /// The user defined polygon flags. - unsigned short flags; - - /// The number of vertices in the polygon. - unsigned char vertCount; - - /// The bit packed area id and polygon type. - /// @note Use the structure's set and get methods to acess this value. - unsigned char areaAndtype; - - /// Sets the user defined area id. [Limit: < #DT_MAX_AREAS] - inline void setArea(unsigned char a) { areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f); } - - /// Sets the polygon type. (See: #dtPolyTypes.) - inline void setType(unsigned char t) { areaAndtype = (areaAndtype & 0x3f) | (t << 6); } - - /// Gets the user defined area id. - inline unsigned char getArea() const { return areaAndtype & 0x3f; } - - /// Gets the polygon type. (See: #dtPolyTypes) - inline unsigned char getType() const { return areaAndtype >> 6; } -}; - -/// Defines the location of detail sub-mesh data within a dtMeshTile. -struct dtPolyDetail -{ - unsigned int vertBase; ///< The offset of the vertices in the dtMeshTile::detailVerts array. - unsigned int triBase; ///< The offset of the triangles in the dtMeshTile::detailTris array. - unsigned char vertCount; ///< The number of vertices in the sub-mesh. - unsigned char triCount; ///< The number of triangles in the sub-mesh. -}; - -/// Defines a link between polygons. -/// @note This structure is rarely if ever used by the end user. -/// @see dtMeshTile -struct dtLink -{ - dtPolyRef ref; ///< Neighbour reference. (The neighbor that is linked to.) - unsigned int next; ///< Index of the next link. - unsigned char edge; ///< Index of the polygon edge that owns this link. - unsigned char side; ///< If a boundary link, defines on which side the link is. - unsigned char bmin; ///< If a boundary link, defines the minimum sub-edge area. - unsigned char bmax; ///< If a boundary link, defines the maximum sub-edge area. -}; - -/// Bounding volume node. -/// @note This structure is rarely if ever used by the end user. -/// @see dtMeshTile -struct dtBVNode -{ - unsigned short bmin[3]; ///< Minimum bounds of the node's AABB. [(x, y, z)] - unsigned short bmax[3]; ///< Maximum bounds of the node's AABB. [(x, y, z)] - int i; ///< The node's index. (Negative for escape sequence.) -}; - -/// Defines an navigation mesh off-mesh connection within a dtMeshTile object. -/// An off-mesh connection is a user defined traversable connection made up to two vertices. -struct dtOffMeshConnection -{ - /// The endpoints of the connection. [(ax, ay, az, bx, by, bz)] - float pos[6]; - - /// The radius of the endpoints. [Limit: >= 0] - float rad; - - /// The polygon reference of the connection within the tile. - unsigned short poly; - - /// Link flags. - /// @note These are not the connection's user defined flags. Those are assigned via the - /// connection's dtPoly definition. These are link flags used for internal purposes. - unsigned char flags; - - /// End point side. - unsigned char side; - - /// The id of the offmesh connection. (User assigned when the navigation mesh is built.) - unsigned int userId; -}; - -/// Provides high level information related to a dtMeshTile object. -/// @ingroup detour -struct dtMeshHeader -{ - int magic; ///< Tile magic number. (Used to identify the data format.) - int version; ///< Tile data format version number. - int x; ///< The x-position of the tile within the dtNavMesh tile grid. (x, y, layer) - int y; ///< The y-position of the tile within the dtNavMesh tile grid. (x, y, layer) - int layer; ///< The layer of the tile within the dtNavMesh tile grid. (x, y, layer) - unsigned int userId; ///< The user defined id of the tile. - int polyCount; ///< The number of polygons in the tile. - int vertCount; ///< The number of vertices in the tile. - int maxLinkCount; ///< The number of allocated links. - int detailMeshCount; ///< The number of sub-meshes in the detail mesh. - - /// The number of unique vertices in the detail mesh. (In addition to the polygon vertices.) - int detailVertCount; - - int detailTriCount; ///< The number of triangles in the detail mesh. - int bvNodeCount; ///< The number of bounding volume nodes. (Zero if bounding volumes are disabled.) - int offMeshConCount; ///< The number of off-mesh connections. - int offMeshBase; ///< The index of the first polygon which is an off-mesh connection. - float walkableHeight; ///< The height of the agents using the tile. - float walkableRadius; ///< The radius of the agents using the tile. - float walkableClimb; ///< The maximum climb height of the agents using the tile. - float bmin[3]; ///< The minimum bounds of the tile's AABB. [(x, y, z)] - float bmax[3]; ///< The maximum bounds of the tile's AABB. [(x, y, z)] - - /// The bounding volume quantization factor. - float bvQuantFactor; -}; - -/// Defines a navigation mesh tile. -/// @ingroup detour -struct dtMeshTile -{ - unsigned int salt; ///< Counter describing modifications to the tile. - - unsigned int linksFreeList; ///< Index to the next free link. - dtMeshHeader* header; ///< The tile header. - dtPoly* polys; ///< The tile polygons. [Size: dtMeshHeader::polyCount] - float* verts; ///< The tile vertices. [Size: dtMeshHeader::vertCount] - dtLink* links; ///< The tile links. [Size: dtMeshHeader::maxLinkCount] - dtPolyDetail* detailMeshes; ///< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] - - /// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount] - float* detailVerts; - - /// The detail mesh's triangles. [(vertA, vertB, vertC, triFlags) * dtMeshHeader::detailTriCount]. - /// See dtDetailTriEdgeFlags and dtGetDetailTriEdgeFlags. - unsigned char* detailTris; - - /// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount] - /// (Will be null if bounding volumes are disabled.) - dtBVNode* bvTree; - - dtOffMeshConnection* offMeshCons; ///< The tile off-mesh connections. [Size: dtMeshHeader::offMeshConCount] - - unsigned char* data; ///< The tile data. (Not directly accessed under normal situations.) - int dataSize; ///< Size of the tile data. - int flags; ///< Tile flags. (See: #dtTileFlags) - dtMeshTile* next; ///< The next free tile, or the next tile in the spatial grid. -// OpenMW code - make dtMeshTile POD since R&D init it by memset -//private: -// dtMeshTile(const dtMeshTile&); -// dtMeshTile& operator=(const dtMeshTile&); -}; - -/// Get flags for edge in detail triangle. -/// @param triFlags[in] The flags for the triangle (last component of detail vertices above). -/// @param edgeIndex[in] The index of the first vertex of the edge. For instance, if 0, -/// returns flags for edge AB. -inline int dtGetDetailTriEdgeFlags(unsigned char triFlags, int edgeIndex) -{ - return (triFlags >> (edgeIndex * 2)) & 0x3; -} - -/// Configuration parameters used to define multi-tile navigation meshes. -/// The values are used to allocate space during the initialization of a navigation mesh. -/// @see dtNavMesh::init() -/// @ingroup detour -struct dtNavMeshParams -{ - float orig[3]; ///< The world space origin of the navigation mesh's tile space. [(x, y, z)] - float tileWidth; ///< The width of each tile. (Along the x-axis.) - float tileHeight; ///< The height of each tile. (Along the z-axis.) - int maxTiles; ///< The maximum number of tiles the navigation mesh can contain. - int maxPolys; ///< The maximum number of polygons each tile can contain. -}; - -/// A navigation mesh based on tiles of convex polygons. -/// @ingroup detour -class dtNavMesh -{ -public: - dtNavMesh(); - ~dtNavMesh(); - - /// @{ - /// @name Initialization and Tile Management - - /// Initializes the navigation mesh for tiled use. - /// @param[in] params Initialization parameters. - /// @return The status flags for the operation. - dtStatus init(const dtNavMeshParams* params); - - /// Initializes the navigation mesh for single tile use. - /// @param[in] data Data of the new tile. (See: #dtCreateNavMeshData) - /// @param[in] dataSize The data size of the new tile. - /// @param[in] flags The tile flags. (See: #dtTileFlags) - /// @return The status flags for the operation. - /// @see dtCreateNavMeshData - dtStatus init(unsigned char* data, const int dataSize, const int flags); - - /// The navigation mesh initialization params. - const dtNavMeshParams* getParams() const; - - /// Adds a tile to the navigation mesh. - /// @param[in] data Data for the new tile mesh. (See: #dtCreateNavMeshData) - /// @param[in] dataSize Data size of the new tile mesh. - /// @param[in] flags Tile flags. (See: #dtTileFlags) - /// @param[in] lastRef The desired reference for the tile. (When reloading a tile.) [opt] [Default: 0] - /// @param[out] result The tile reference. (If the tile was succesfully added.) [opt] - /// @return The status flags for the operation. - dtStatus addTile(unsigned char* data, int dataSize, int flags, dtTileRef lastRef, dtTileRef* result); - - /// Removes the specified tile from the navigation mesh. - /// @param[in] ref The reference of the tile to remove. - /// @param[out] data Data associated with deleted tile. - /// @param[out] dataSize Size of the data associated with deleted tile. - /// @return The status flags for the operation. - dtStatus removeTile(dtTileRef ref, unsigned char** data, int* dataSize); - - /// @} - - /// @{ - /// @name Query Functions - - /// Calculates the tile grid location for the specified world position. - /// @param[in] pos The world position for the query. [(x, y, z)] - /// @param[out] tx The tile's x-location. (x, y) - /// @param[out] ty The tile's y-location. (x, y) - void calcTileLoc(const float* pos, int* tx, int* ty) const; - - /// Gets the tile at the specified grid location. - /// @param[in] x The tile's x-location. (x, y, layer) - /// @param[in] y The tile's y-location. (x, y, layer) - /// @param[in] layer The tile's layer. (x, y, layer) - /// @return The tile, or null if the tile does not exist. - const dtMeshTile* getTileAt(const int x, const int y, const int layer) const; - - /// Gets all tiles at the specified grid location. (All layers.) - /// @param[in] x The tile's x-location. (x, y) - /// @param[in] y The tile's y-location. (x, y) - /// @param[out] tiles A pointer to an array of tiles that will hold the result. - /// @param[in] maxTiles The maximum tiles the tiles parameter can hold. - /// @return The number of tiles returned in the tiles array. - int getTilesAt(const int x, const int y, - dtMeshTile const** tiles, const int maxTiles) const; - - /// Gets the tile reference for the tile at specified grid location. - /// @param[in] x The tile's x-location. (x, y, layer) - /// @param[in] y The tile's y-location. (x, y, layer) - /// @param[in] layer The tile's layer. (x, y, layer) - /// @return The tile reference of the tile, or 0 if there is none. - dtTileRef getTileRefAt(int x, int y, int layer) const; - - /// Gets the tile reference for the specified tile. - /// @param[in] tile The tile. - /// @return The tile reference of the tile. - dtTileRef getTileRef(const dtMeshTile* tile) const; - - /// Gets the tile for the specified tile reference. - /// @param[in] ref The tile reference of the tile to retrieve. - /// @return The tile for the specified reference, or null if the - /// reference is invalid. - const dtMeshTile* getTileByRef(dtTileRef ref) const; - - /// The maximum number of tiles supported by the navigation mesh. - /// @return The maximum number of tiles supported by the navigation mesh. - int getMaxTiles() const; - - /// Gets the tile at the specified index. - /// @param[in] i The tile index. [Limit: 0 >= index < #getMaxTiles()] - /// @return The tile at the specified index. - const dtMeshTile* getTile(int i) const; - - /// Gets the tile and polygon for the specified polygon reference. - /// @param[in] ref The reference for the a polygon. - /// @param[out] tile The tile containing the polygon. - /// @param[out] poly The polygon. - /// @return The status flags for the operation. - dtStatus getTileAndPolyByRef(const dtPolyRef ref, const dtMeshTile** tile, const dtPoly** poly) const; - - /// Returns the tile and polygon for the specified polygon reference. - /// @param[in] ref A known valid reference for a polygon. - /// @param[out] tile The tile containing the polygon. - /// @param[out] poly The polygon. - void getTileAndPolyByRefUnsafe(const dtPolyRef ref, const dtMeshTile** tile, const dtPoly** poly) const; - - /// Checks the validity of a polygon reference. - /// @param[in] ref The polygon reference to check. - /// @return True if polygon reference is valid for the navigation mesh. - bool isValidPolyRef(dtPolyRef ref) const; - - /// Gets the polygon reference for the tile's base polygon. - /// @param[in] tile The tile. - /// @return The polygon reference for the base polygon in the specified tile. - dtPolyRef getPolyRefBase(const dtMeshTile* tile) const; - - /// Gets the endpoints for an off-mesh connection, ordered by "direction of travel". - /// @param[in] prevRef The reference of the polygon before the connection. - /// @param[in] polyRef The reference of the off-mesh connection polygon. - /// @param[out] startPos The start position of the off-mesh connection. [(x, y, z)] - /// @param[out] endPos The end position of the off-mesh connection. [(x, y, z)] - /// @return The status flags for the operation. - dtStatus getOffMeshConnectionPolyEndPoints(dtPolyRef prevRef, dtPolyRef polyRef, float* startPos, float* endPos) const; - - /// Gets the specified off-mesh connection. - /// @param[in] ref The polygon reference of the off-mesh connection. - /// @return The specified off-mesh connection, or null if the polygon reference is not valid. - const dtOffMeshConnection* getOffMeshConnectionByRef(dtPolyRef ref) const; - - /// @} - - /// @{ - /// @name State Management - /// These functions do not effect #dtTileRef or #dtPolyRef's. - - /// Sets the user defined flags for the specified polygon. - /// @param[in] ref The polygon reference. - /// @param[in] flags The new flags for the polygon. - /// @return The status flags for the operation. - dtStatus setPolyFlags(dtPolyRef ref, unsigned short flags); - - /// Gets the user defined flags for the specified polygon. - /// @param[in] ref The polygon reference. - /// @param[out] resultFlags The polygon flags. - /// @return The status flags for the operation. - dtStatus getPolyFlags(dtPolyRef ref, unsigned short* resultFlags) const; - - /// Sets the user defined area for the specified polygon. - /// @param[in] ref The polygon reference. - /// @param[in] area The new area id for the polygon. [Limit: < #DT_MAX_AREAS] - /// @return The status flags for the operation. - dtStatus setPolyArea(dtPolyRef ref, unsigned char area); - - /// Gets the user defined area for the specified polygon. - /// @param[in] ref The polygon reference. - /// @param[out] resultArea The area id for the polygon. - /// @return The status flags for the operation. - dtStatus getPolyArea(dtPolyRef ref, unsigned char* resultArea) const; - - /// Gets the size of the buffer required by #storeTileState to store the specified tile's state. - /// @param[in] tile The tile. - /// @return The size of the buffer required to store the state. - int getTileStateSize(const dtMeshTile* tile) const; - - /// Stores the non-structural state of the tile in the specified buffer. (Flags, area ids, etc.) - /// @param[in] tile The tile. - /// @param[out] data The buffer to store the tile's state in. - /// @param[in] maxDataSize The size of the data buffer. [Limit: >= #getTileStateSize] - /// @return The status flags for the operation. - dtStatus storeTileState(const dtMeshTile* tile, unsigned char* data, const int maxDataSize) const; - - /// Restores the state of the tile. - /// @param[in] tile The tile. - /// @param[in] data The new state. (Obtained from #storeTileState.) - /// @param[in] maxDataSize The size of the state within the data buffer. - /// @return The status flags for the operation. - dtStatus restoreTileState(dtMeshTile* tile, const unsigned char* data, const int maxDataSize); - - /// @} - - /// @{ - /// @name Encoding and Decoding - /// These functions are generally meant for internal use only. - - /// Derives a standard polygon reference. - /// @note This function is generally meant for internal use only. - /// @param[in] salt The tile's salt value. - /// @param[in] it The index of the tile. - /// @param[in] ip The index of the polygon within the tile. - inline dtPolyRef encodePolyId(unsigned int salt, unsigned int it, unsigned int ip) const - { -#ifdef DT_POLYREF64 - return ((dtPolyRef)salt << (DT_POLY_BITS+DT_TILE_BITS)) | ((dtPolyRef)it << DT_POLY_BITS) | (dtPolyRef)ip; -#else - return ((dtPolyRef)salt << (m_polyBits+m_tileBits)) | ((dtPolyRef)it << m_polyBits) | (dtPolyRef)ip; -#endif - } - - /// Decodes a standard polygon reference. - /// @note This function is generally meant for internal use only. - /// @param[in] ref The polygon reference to decode. - /// @param[out] salt The tile's salt value. - /// @param[out] it The index of the tile. - /// @param[out] ip The index of the polygon within the tile. - /// @see #encodePolyId - inline void decodePolyId(dtPolyRef ref, unsigned int& salt, unsigned int& it, unsigned int& ip) const - { -#ifdef DT_POLYREF64 - const dtPolyRef saltMask = ((dtPolyRef)1<> (DT_POLY_BITS+DT_TILE_BITS)) & saltMask); - it = (unsigned int)((ref >> DT_POLY_BITS) & tileMask); - ip = (unsigned int)(ref & polyMask); -#else - const dtPolyRef saltMask = ((dtPolyRef)1<> (m_polyBits+m_tileBits)) & saltMask); - it = (unsigned int)((ref >> m_polyBits) & tileMask); - ip = (unsigned int)(ref & polyMask); -#endif - } - - /// Extracts a tile's salt value from the specified polygon reference. - /// @note This function is generally meant for internal use only. - /// @param[in] ref The polygon reference. - /// @see #encodePolyId - inline unsigned int decodePolyIdSalt(dtPolyRef ref) const - { -#ifdef DT_POLYREF64 - const dtPolyRef saltMask = ((dtPolyRef)1<> (DT_POLY_BITS+DT_TILE_BITS)) & saltMask); -#else - const dtPolyRef saltMask = ((dtPolyRef)1<> (m_polyBits+m_tileBits)) & saltMask); -#endif - } - - /// Extracts the tile's index from the specified polygon reference. - /// @note This function is generally meant for internal use only. - /// @param[in] ref The polygon reference. - /// @see #encodePolyId - inline unsigned int decodePolyIdTile(dtPolyRef ref) const - { -#ifdef DT_POLYREF64 - const dtPolyRef tileMask = ((dtPolyRef)1<> DT_POLY_BITS) & tileMask); -#else - const dtPolyRef tileMask = ((dtPolyRef)1<> m_polyBits) & tileMask); -#endif - } - - /// Extracts the polygon's index (within its tile) from the specified polygon reference. - /// @note This function is generally meant for internal use only. - /// @param[in] ref The polygon reference. - /// @see #encodePolyId - inline unsigned int decodePolyIdPoly(dtPolyRef ref) const - { -#ifdef DT_POLYREF64 - const dtPolyRef polyMask = ((dtPolyRef)1<header->bvQuantFactor; -const dtBVNode* n = &tile->bvTree[i]; -if (n->i >= 0) -{ - // This is a leaf node. - float worldMinX = tile->header->bmin[0] + n->bmin[0]*cs; - float worldMinY = tile->header->bmin[0] + n->bmin[1]*cs; - // Etc... -} -@endcode - -@struct dtMeshTile -@par - -Tiles generally only exist within the context of a dtNavMesh object. - -Some tile content is optional. For example, a tile may not contain any -off-mesh connections. In this case the associated pointer will be null. - -If a detail mesh exists it will share vertices with the base polygon mesh. -Only the vertices unique to the detail mesh will be stored in #detailVerts. - -@warning Tiles returned by a dtNavMesh object are not guarenteed to be populated. -For example: The tile at a location might not have been loaded yet, or may have been removed. -In this case, pointers will be null. So if in doubt, check the polygon count in the -tile's header to determine if a tile has polygons defined. - -@var float dtOffMeshConnection::pos[6] -@par - -For a properly built navigation mesh, vertex A will always be within the bounds of the mesh. -Vertex B is not required to be within the bounds of the mesh. - -*/ diff --git a/extern/recastnavigation/Detour/Include/DetourNavMeshBuilder.h b/extern/recastnavigation/Detour/Include/DetourNavMeshBuilder.h deleted file mode 100644 index 9425a7a789..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourNavMeshBuilder.h +++ /dev/null @@ -1,149 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURNAVMESHBUILDER_H -#define DETOURNAVMESHBUILDER_H - -#include "DetourAlloc.h" - -/// Represents the source data used to build an navigation mesh tile. -/// @ingroup detour -struct dtNavMeshCreateParams -{ - - /// @name Polygon Mesh Attributes - /// Used to create the base navigation graph. - /// See #rcPolyMesh for details related to these attributes. - /// @{ - - const unsigned short* verts; ///< The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx] - int vertCount; ///< The number vertices in the polygon mesh. [Limit: >= 3] - const unsigned short* polys; ///< The polygon data. [Size: #polyCount * 2 * #nvp] - const unsigned short* polyFlags; ///< The user defined flags assigned to each polygon. [Size: #polyCount] - const unsigned char* polyAreas; ///< The user defined area ids assigned to each polygon. [Size: #polyCount] - int polyCount; ///< Number of polygons in the mesh. [Limit: >= 1] - int nvp; ///< Number maximum number of vertices per polygon. [Limit: >= 3] - - /// @} - /// @name Height Detail Attributes (Optional) - /// See #rcPolyMeshDetail for details related to these attributes. - /// @{ - - const unsigned int* detailMeshes; ///< The height detail sub-mesh data. [Size: 4 * #polyCount] - const float* detailVerts; ///< The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu] - int detailVertsCount; ///< The number of vertices in the detail mesh. - const unsigned char* detailTris; ///< The detail mesh triangles. [Size: 4 * #detailTriCount] - int detailTriCount; ///< The number of triangles in the detail mesh. - - /// @} - /// @name Off-Mesh Connections Attributes (Optional) - /// Used to define a custom point-to-point edge within the navigation graph, an - /// off-mesh connection is a user defined traversable connection made up to two vertices, - /// at least one of which resides within a navigation mesh polygon. - /// @{ - - /// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu] - const float* offMeshConVerts; - /// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu] - const float* offMeshConRad; - /// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount] - const unsigned short* offMeshConFlags; - /// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount] - const unsigned char* offMeshConAreas; - /// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount] - /// - /// 0 = Travel only from endpoint A to endpoint B.
- /// #DT_OFFMESH_CON_BIDIR = Bidirectional travel. - const unsigned char* offMeshConDir; - /// The user defined ids of the off-mesh connection. [Size: #offMeshConCount] - const unsigned int* offMeshConUserID; - /// The number of off-mesh connections. [Limit: >= 0] - int offMeshConCount; - - /// @} - /// @name Tile Attributes - /// @note The tile grid/layer data can be left at zero if the destination is a single tile mesh. - /// @{ - - unsigned int userId; ///< The user defined id of the tile. - int tileX; ///< The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.) - int tileY; ///< The tile's y-grid location within the multi-tile desitation mesh. (Along the z-axis.) - int tileLayer; ///< The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.) - float bmin[3]; ///< The minimum bounds of the tile. [(x, y, z)] [Unit: wu] - float bmax[3]; ///< The maximum bounds of the tile. [(x, y, z)] [Unit: wu] - - /// @} - /// @name General Configuration Attributes - /// @{ - - float walkableHeight; ///< The agent height. [Unit: wu] - float walkableRadius; ///< The agent radius. [Unit: wu] - float walkableClimb; ///< The agent maximum traversable ledge. (Up/Down) [Unit: wu] - float cs; ///< The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu] - float ch; ///< The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu] - - /// True if a bounding volume tree should be built for the tile. - /// @note The BVTree is not normally needed for layered navigation meshes. - bool buildBvTree; - - /// @} -}; - -/// Builds navigation mesh tile data from the provided tile creation data. -/// @ingroup detour -/// @param[in] params Tile creation data. -/// @param[out] outData The resulting tile data. -/// @param[out] outDataSize The size of the tile data array. -/// @return True if the tile data was successfully created. -bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, int* outDataSize); - -/// Swaps the endianess of the tile data's header (#dtMeshHeader). -/// @param[in,out] data The tile data array. -/// @param[in] dataSize The size of the data array. -bool dtNavMeshHeaderSwapEndian(unsigned char* data, const int dataSize); - -/// Swaps endianess of the tile data. -/// @param[in,out] data The tile data array. -/// @param[in] dataSize The size of the data array. -bool dtNavMeshDataSwapEndian(unsigned char* data, const int dataSize); - -#endif // DETOURNAVMESHBUILDER_H - -// This section contains detailed documentation for members that don't have -// a source file. It reduces clutter in the main section of the header. - -/** - -@struct dtNavMeshCreateParams -@par - -This structure is used to marshal data between the Recast mesh generation pipeline and Detour navigation components. - -See the rcPolyMesh and rcPolyMeshDetail documentation for detailed information related to mesh structure. - -Units are usually in voxels (vx) or world units (wu). The units for voxels, grid size, and cell size -are all based on the values of #cs and #ch. - -The standard navigation mesh build process is to create tile data using dtCreateNavMeshData, then add the tile -to a navigation mesh using either the dtNavMesh single tile init() function or the dtNavMesh::addTile() -function. - -@see dtCreateNavMeshData - -*/ - diff --git a/extern/recastnavigation/Detour/Include/DetourNavMeshQuery.h b/extern/recastnavigation/Detour/Include/DetourNavMeshQuery.h deleted file mode 100644 index 0b40371beb..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourNavMeshQuery.h +++ /dev/null @@ -1,573 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURNAVMESHQUERY_H -#define DETOURNAVMESHQUERY_H - -#include "DetourNavMesh.h" -#include "DetourStatus.h" - - -// Define DT_VIRTUAL_QUERYFILTER if you wish to derive a custom filter from dtQueryFilter. -// On certain platforms indirect or virtual function call is expensive. The default -// setting is to use non-virtual functions, the actual implementations of the functions -// are declared as inline for maximum speed. - -//#define DT_VIRTUAL_QUERYFILTER 1 - -/// Defines polygon filtering and traversal costs for navigation mesh query operations. -/// @ingroup detour -class dtQueryFilter -{ - float m_areaCost[DT_MAX_AREAS]; ///< Cost per area type. (Used by default implementation.) - unsigned short m_includeFlags; ///< Flags for polygons that can be visited. (Used by default implementation.) - unsigned short m_excludeFlags; ///< Flags for polygons that should not be visted. (Used by default implementation.) - -public: - dtQueryFilter(); - -#ifdef DT_VIRTUAL_QUERYFILTER - virtual ~dtQueryFilter() { } -#endif - - /// Returns true if the polygon can be visited. (I.e. Is traversable.) - /// @param[in] ref The reference id of the polygon test. - /// @param[in] tile The tile containing the polygon. - /// @param[in] poly The polygon to test. -#ifdef DT_VIRTUAL_QUERYFILTER - virtual bool passFilter(const dtPolyRef ref, - const dtMeshTile* tile, - const dtPoly* poly) const; -#else - bool passFilter(const dtPolyRef ref, - const dtMeshTile* tile, - const dtPoly* poly) const; -#endif - - /// Returns cost to move from the beginning to the end of a line segment - /// that is fully contained within a polygon. - /// @param[in] pa The start position on the edge of the previous and current polygon. [(x, y, z)] - /// @param[in] pb The end position on the edge of the current and next polygon. [(x, y, z)] - /// @param[in] prevRef The reference id of the previous polygon. [opt] - /// @param[in] prevTile The tile containing the previous polygon. [opt] - /// @param[in] prevPoly The previous polygon. [opt] - /// @param[in] curRef The reference id of the current polygon. - /// @param[in] curTile The tile containing the current polygon. - /// @param[in] curPoly The current polygon. - /// @param[in] nextRef The refernece id of the next polygon. [opt] - /// @param[in] nextTile The tile containing the next polygon. [opt] - /// @param[in] nextPoly The next polygon. [opt] -#ifdef DT_VIRTUAL_QUERYFILTER - virtual float getCost(const float* pa, const float* pb, - const dtPolyRef prevRef, const dtMeshTile* prevTile, const dtPoly* prevPoly, - const dtPolyRef curRef, const dtMeshTile* curTile, const dtPoly* curPoly, - const dtPolyRef nextRef, const dtMeshTile* nextTile, const dtPoly* nextPoly) const; -#else - float getCost(const float* pa, const float* pb, - const dtPolyRef prevRef, const dtMeshTile* prevTile, const dtPoly* prevPoly, - const dtPolyRef curRef, const dtMeshTile* curTile, const dtPoly* curPoly, - const dtPolyRef nextRef, const dtMeshTile* nextTile, const dtPoly* nextPoly) const; -#endif - - /// @name Getters and setters for the default implementation data. - ///@{ - - /// Returns the traversal cost of the area. - /// @param[in] i The id of the area. - /// @returns The traversal cost of the area. - inline float getAreaCost(const int i) const { return m_areaCost[i]; } - - /// Sets the traversal cost of the area. - /// @param[in] i The id of the area. - /// @param[in] cost The new cost of traversing the area. - inline void setAreaCost(const int i, const float cost) { m_areaCost[i] = cost; } - - /// Returns the include flags for the filter. - /// Any polygons that include one or more of these flags will be - /// included in the operation. - inline unsigned short getIncludeFlags() const { return m_includeFlags; } - - /// Sets the include flags for the filter. - /// @param[in] flags The new flags. - inline void setIncludeFlags(const unsigned short flags) { m_includeFlags = flags; } - - /// Returns the exclude flags for the filter. - /// Any polygons that include one ore more of these flags will be - /// excluded from the operation. - inline unsigned short getExcludeFlags() const { return m_excludeFlags; } - - /// Sets the exclude flags for the filter. - /// @param[in] flags The new flags. - inline void setExcludeFlags(const unsigned short flags) { m_excludeFlags = flags; } - - ///@} - -}; - -/// Provides information about raycast hit -/// filled by dtNavMeshQuery::raycast -/// @ingroup detour -struct dtRaycastHit -{ - /// The hit parameter. (FLT_MAX if no wall hit.) - float t; - - /// hitNormal The normal of the nearest wall hit. [(x, y, z)] - float hitNormal[3]; - - /// The index of the edge on the final polygon where the wall was hit. - int hitEdgeIndex; - - /// Pointer to an array of reference ids of the visited polygons. [opt] - dtPolyRef* path; - - /// The number of visited polygons. [opt] - int pathCount; - - /// The maximum number of polygons the @p path array can hold. - int maxPath; - - /// The cost of the path until hit. - float pathCost; -}; - -/// Provides custom polygon query behavior. -/// Used by dtNavMeshQuery::queryPolygons. -/// @ingroup detour -class dtPolyQuery -{ -public: - virtual ~dtPolyQuery() { } - - /// Called for each batch of unique polygons touched by the search area in dtNavMeshQuery::queryPolygons. - /// This can be called multiple times for a single query. - virtual void process(const dtMeshTile* tile, dtPoly** polys, dtPolyRef* refs, int count) = 0; -}; - -/// Provides the ability to perform pathfinding related queries against -/// a navigation mesh. -/// @ingroup detour -class dtNavMeshQuery -{ -public: - dtNavMeshQuery(); - ~dtNavMeshQuery(); - - /// Initializes the query object. - /// @param[in] nav Pointer to the dtNavMesh object to use for all queries. - /// @param[in] maxNodes Maximum number of search nodes. [Limits: 0 < value <= 65535] - /// @returns The status flags for the query. - dtStatus init(const dtNavMesh* nav, const int maxNodes); - - /// @name Standard Pathfinding Functions - // /@{ - - /// Finds a path from the start polygon to the end polygon. - /// @param[in] startRef The refrence id of the start polygon. - /// @param[in] endRef The reference id of the end polygon. - /// @param[in] startPos A position within the start polygon. [(x, y, z)] - /// @param[in] endPos A position within the end polygon. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) - /// [(polyRef) * @p pathCount] - /// @param[out] pathCount The number of polygons returned in the @p path array. - /// @param[in] maxPath The maximum number of polygons the @p path array can hold. [Limit: >= 1] - dtStatus findPath(dtPolyRef startRef, dtPolyRef endRef, - const float* startPos, const float* endPos, - const dtQueryFilter* filter, - dtPolyRef* path, int* pathCount, const int maxPath) const; - - /// Finds the straight path from the start to the end position within the polygon corridor. - /// @param[in] startPos Path start position. [(x, y, z)] - /// @param[in] endPos Path end position. [(x, y, z)] - /// @param[in] path An array of polygon references that represent the path corridor. - /// @param[in] pathSize The number of polygons in the @p path array. - /// @param[out] straightPath Points describing the straight path. [(x, y, z) * @p straightPathCount]. - /// @param[out] straightPathFlags Flags describing each point. (See: #dtStraightPathFlags) [opt] - /// @param[out] straightPathRefs The reference id of the polygon that is being entered at each point. [opt] - /// @param[out] straightPathCount The number of points in the straight path. - /// @param[in] maxStraightPath The maximum number of points the straight path arrays can hold. [Limit: > 0] - /// @param[in] options Query options. (see: #dtStraightPathOptions) - /// @returns The status flags for the query. - dtStatus findStraightPath(const float* startPos, const float* endPos, - const dtPolyRef* path, const int pathSize, - float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, - int* straightPathCount, const int maxStraightPath, const int options = 0) const; - - ///@} - /// @name Sliced Pathfinding Functions - /// Common use case: - /// -# Call initSlicedFindPath() to initialize the sliced path query. - /// -# Call updateSlicedFindPath() until it returns complete. - /// -# Call finalizeSlicedFindPath() to get the path. - ///@{ - - /// Intializes a sliced path query. - /// @param[in] startRef The refrence id of the start polygon. - /// @param[in] endRef The reference id of the end polygon. - /// @param[in] startPos A position within the start polygon. [(x, y, z)] - /// @param[in] endPos A position within the end polygon. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[in] options query options (see: #dtFindPathOptions) - /// @returns The status flags for the query. - dtStatus initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef, - const float* startPos, const float* endPos, - const dtQueryFilter* filter, const unsigned int options = 0); - - /// Updates an in-progress sliced path query. - /// @param[in] maxIter The maximum number of iterations to perform. - /// @param[out] doneIters The actual number of iterations completed. [opt] - /// @returns The status flags for the query. - dtStatus updateSlicedFindPath(const int maxIter, int* doneIters); - - /// Finalizes and returns the results of a sliced path query. - /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) - /// [(polyRef) * @p pathCount] - /// @param[out] pathCount The number of polygons returned in the @p path array. - /// @param[in] maxPath The max number of polygons the path array can hold. [Limit: >= 1] - /// @returns The status flags for the query. - dtStatus finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, const int maxPath); - - /// Finalizes and returns the results of an incomplete sliced path query, returning the path to the furthest - /// polygon on the existing path that was visited during the search. - /// @param[in] existing An array of polygon references for the existing path. - /// @param[in] existingSize The number of polygon in the @p existing array. - /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) - /// [(polyRef) * @p pathCount] - /// @param[out] pathCount The number of polygons returned in the @p path array. - /// @param[in] maxPath The max number of polygons the @p path array can hold. [Limit: >= 1] - /// @returns The status flags for the query. - dtStatus finalizeSlicedFindPathPartial(const dtPolyRef* existing, const int existingSize, - dtPolyRef* path, int* pathCount, const int maxPath); - - ///@} - /// @name Dijkstra Search Functions - /// @{ - - /// Finds the polygons along the navigation graph that touch the specified circle. - /// @param[in] startRef The reference id of the polygon where the search starts. - /// @param[in] centerPos The center of the search circle. [(x, y, z)] - /// @param[in] radius The radius of the search circle. - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] resultRef The reference ids of the polygons touched by the circle. [opt] - /// @param[out] resultParent The reference ids of the parent polygons for each result. - /// Zero if a result polygon has no parent. [opt] - /// @param[out] resultCost The search cost from @p centerPos to the polygon. [opt] - /// @param[out] resultCount The number of polygons found. [opt] - /// @param[in] maxResult The maximum number of polygons the result arrays can hold. - /// @returns The status flags for the query. - dtStatus findPolysAroundCircle(dtPolyRef startRef, const float* centerPos, const float radius, - const dtQueryFilter* filter, - dtPolyRef* resultRef, dtPolyRef* resultParent, float* resultCost, - int* resultCount, const int maxResult) const; - - /// Finds the polygons along the naviation graph that touch the specified convex polygon. - /// @param[in] startRef The reference id of the polygon where the search starts. - /// @param[in] verts The vertices describing the convex polygon. (CCW) - /// [(x, y, z) * @p nverts] - /// @param[in] nverts The number of vertices in the polygon. - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] resultRef The reference ids of the polygons touched by the search polygon. [opt] - /// @param[out] resultParent The reference ids of the parent polygons for each result. Zero if a - /// result polygon has no parent. [opt] - /// @param[out] resultCost The search cost from the centroid point to the polygon. [opt] - /// @param[out] resultCount The number of polygons found. - /// @param[in] maxResult The maximum number of polygons the result arrays can hold. - /// @returns The status flags for the query. - dtStatus findPolysAroundShape(dtPolyRef startRef, const float* verts, const int nverts, - const dtQueryFilter* filter, - dtPolyRef* resultRef, dtPolyRef* resultParent, float* resultCost, - int* resultCount, const int maxResult) const; - - /// Gets a path from the explored nodes in the previous search. - /// @param[in] endRef The reference id of the end polygon. - /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) - /// [(polyRef) * @p pathCount] - /// @param[out] pathCount The number of polygons returned in the @p path array. - /// @param[in] maxPath The maximum number of polygons the @p path array can hold. [Limit: >= 0] - /// @returns The status flags. Returns DT_FAILURE | DT_INVALID_PARAM if any parameter is wrong, or if - /// @p endRef was not explored in the previous search. Returns DT_SUCCESS | DT_BUFFER_TOO_SMALL - /// if @p path cannot contain the entire path. In this case it is filled to capacity with a partial path. - /// Otherwise returns DT_SUCCESS. - /// @remarks The result of this function depends on the state of the query object. For that reason it should only - /// be used immediately after one of the two Dijkstra searches, findPolysAroundCircle or findPolysAroundShape. - dtStatus getPathFromDijkstraSearch(dtPolyRef endRef, dtPolyRef* path, int* pathCount, int maxPath) const; - - /// @} - /// @name Local Query Functions - ///@{ - - /// Finds the polygon nearest to the specified center point. - /// @param[in] center The center of the search box. [(x, y, z)] - /// @param[in] halfExtents The search distance along each axis. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] nearestRef The reference id of the nearest polygon. - /// @param[out] nearestPt The nearest point on the polygon. [opt] [(x, y, z)] - /// @returns The status flags for the query. - dtStatus findNearestPoly(const float* center, const float* halfExtents, - const dtQueryFilter* filter, - dtPolyRef* nearestRef, float* nearestPt) const; - - /// Finds polygons that overlap the search box. - /// @param[in] center The center of the search box. [(x, y, z)] - /// @param[in] halfExtents The search distance along each axis. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] polys The reference ids of the polygons that overlap the query box. - /// @param[out] polyCount The number of polygons in the search result. - /// @param[in] maxPolys The maximum number of polygons the search result can hold. - /// @returns The status flags for the query. - dtStatus queryPolygons(const float* center, const float* halfExtents, - const dtQueryFilter* filter, - dtPolyRef* polys, int* polyCount, const int maxPolys) const; - - /// Finds polygons that overlap the search box. - /// @param[in] center The center of the search box. [(x, y, z)] - /// @param[in] halfExtents The search distance along each axis. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[in] query The query. Polygons found will be batched together and passed to this query. - dtStatus queryPolygons(const float* center, const float* halfExtents, - const dtQueryFilter* filter, dtPolyQuery* query) const; - - /// Finds the non-overlapping navigation polygons in the local neighbourhood around the center position. - /// @param[in] startRef The reference id of the polygon where the search starts. - /// @param[in] centerPos The center of the query circle. [(x, y, z)] - /// @param[in] radius The radius of the query circle. - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] resultRef The reference ids of the polygons touched by the circle. - /// @param[out] resultParent The reference ids of the parent polygons for each result. - /// Zero if a result polygon has no parent. [opt] - /// @param[out] resultCount The number of polygons found. - /// @param[in] maxResult The maximum number of polygons the result arrays can hold. - /// @returns The status flags for the query. - dtStatus findLocalNeighbourhood(dtPolyRef startRef, const float* centerPos, const float radius, - const dtQueryFilter* filter, - dtPolyRef* resultRef, dtPolyRef* resultParent, - int* resultCount, const int maxResult) const; - - /// Moves from the start to the end position constrained to the navigation mesh. - /// @param[in] startRef The reference id of the start polygon. - /// @param[in] startPos A position of the mover within the start polygon. [(x, y, x)] - /// @param[in] endPos The desired end position of the mover. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] resultPos The result position of the mover. [(x, y, z)] - /// @param[out] visited The reference ids of the polygons visited during the move. - /// @param[out] visitedCount The number of polygons visited during the move. - /// @param[in] maxVisitedSize The maximum number of polygons the @p visited array can hold. - /// @returns The status flags for the query. - dtStatus moveAlongSurface(dtPolyRef startRef, const float* startPos, const float* endPos, - const dtQueryFilter* filter, - float* resultPos, dtPolyRef* visited, int* visitedCount, const int maxVisitedSize) const; - - /// Casts a 'walkability' ray along the surface of the navigation mesh from - /// the start position toward the end position. - /// @note A wrapper around raycast(..., RaycastHit*). Retained for backward compatibility. - /// @param[in] startRef The reference id of the start polygon. - /// @param[in] startPos A position within the start polygon representing - /// the start of the ray. [(x, y, z)] - /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] - /// @param[out] t The hit parameter. (FLT_MAX if no wall hit.) - /// @param[out] hitNormal The normal of the nearest wall hit. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] path The reference ids of the visited polygons. [opt] - /// @param[out] pathCount The number of visited polygons. [opt] - /// @param[in] maxPath The maximum number of polygons the @p path array can hold. - /// @returns The status flags for the query. - dtStatus raycast(dtPolyRef startRef, const float* startPos, const float* endPos, - const dtQueryFilter* filter, - float* t, float* hitNormal, dtPolyRef* path, int* pathCount, const int maxPath) const; - - /// Casts a 'walkability' ray along the surface of the navigation mesh from - /// the start position toward the end position. - /// @param[in] startRef The reference id of the start polygon. - /// @param[in] startPos A position within the start polygon representing - /// the start of the ray. [(x, y, z)] - /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[in] flags govern how the raycast behaves. See dtRaycastOptions - /// @param[out] hit Pointer to a raycast hit structure which will be filled by the results. - /// @param[in] prevRef parent of start ref. Used during for cost calculation [opt] - /// @returns The status flags for the query. - dtStatus raycast(dtPolyRef startRef, const float* startPos, const float* endPos, - const dtQueryFilter* filter, const unsigned int options, - dtRaycastHit* hit, dtPolyRef prevRef = 0) const; - - - /// Finds the distance from the specified position to the nearest polygon wall. - /// @param[in] startRef The reference id of the polygon containing @p centerPos. - /// @param[in] centerPos The center of the search circle. [(x, y, z)] - /// @param[in] maxRadius The radius of the search circle. - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] hitDist The distance to the nearest wall from @p centerPos. - /// @param[out] hitPos The nearest position on the wall that was hit. [(x, y, z)] - /// @param[out] hitNormal The normalized ray formed from the wall point to the - /// source point. [(x, y, z)] - /// @returns The status flags for the query. - dtStatus findDistanceToWall(dtPolyRef startRef, const float* centerPos, const float maxRadius, - const dtQueryFilter* filter, - float* hitDist, float* hitPos, float* hitNormal) const; - - /// Returns the segments for the specified polygon, optionally including portals. - /// @param[in] ref The reference id of the polygon. - /// @param[in] filter The polygon filter to apply to the query. - /// @param[out] segmentVerts The segments. [(ax, ay, az, bx, by, bz) * segmentCount] - /// @param[out] segmentRefs The reference ids of each segment's neighbor polygon. - /// Or zero if the segment is a wall. [opt] [(parentRef) * @p segmentCount] - /// @param[out] segmentCount The number of segments returned. - /// @param[in] maxSegments The maximum number of segments the result arrays can hold. - /// @returns The status flags for the query. - dtStatus getPolyWallSegments(dtPolyRef ref, const dtQueryFilter* filter, - float* segmentVerts, dtPolyRef* segmentRefs, int* segmentCount, - const int maxSegments) const; - - /// Returns random location on navmesh. - /// Polygons are chosen weighted by area. The search runs in linear related to number of polygon. - /// @param[in] filter The polygon filter to apply to the query. - /// @param[in] frand Function returning a random number [0..1). - /// @param[out] randomRef The reference id of the random location. - /// @param[out] randomPt The random location. - /// @returns The status flags for the query. - dtStatus findRandomPoint(const dtQueryFilter* filter, float (*frand)(), - dtPolyRef* randomRef, float* randomPt) const; - - /// Returns random location on navmesh within the reach of specified location. - /// Polygons are chosen weighted by area. The search runs in linear related to number of polygon. - /// The location is not exactly constrained by the circle, but it limits the visited polygons. - /// @param[in] startRef The reference id of the polygon where the search starts. - /// @param[in] centerPos The center of the search circle. [(x, y, z)] - /// @param[in] filter The polygon filter to apply to the query. - /// @param[in] frand Function returning a random number [0..1). - /// @param[out] randomRef The reference id of the random location. - /// @param[out] randomPt The random location. [(x, y, z)] - /// @returns The status flags for the query. - dtStatus findRandomPointAroundCircle(dtPolyRef startRef, const float* centerPos, const float maxRadius, - const dtQueryFilter* filter, float (*frand)(), - dtPolyRef* randomRef, float* randomPt) const; - - /// Finds the closest point on the specified polygon. - /// @param[in] ref The reference id of the polygon. - /// @param[in] pos The position to check. [(x, y, z)] - /// @param[out] closest The closest point on the polygon. [(x, y, z)] - /// @param[out] posOverPoly True of the position is over the polygon. - /// @returns The status flags for the query. - dtStatus closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const; - - /// Returns a point on the boundary closest to the source point if the source point is outside the - /// polygon's xz-bounds. - /// @param[in] ref The reference id to the polygon. - /// @param[in] pos The position to check. [(x, y, z)] - /// @param[out] closest The closest point. [(x, y, z)] - /// @returns The status flags for the query. - dtStatus closestPointOnPolyBoundary(dtPolyRef ref, const float* pos, float* closest) const; - - /// Gets the height of the polygon at the provided position using the height detail. (Most accurate.) - /// @param[in] ref The reference id of the polygon. - /// @param[in] pos A position within the xz-bounds of the polygon. [(x, y, z)] - /// @param[out] height The height at the surface of the polygon. - /// @returns The status flags for the query. - dtStatus getPolyHeight(dtPolyRef ref, const float* pos, float* height) const; - - /// @} - /// @name Miscellaneous Functions - /// @{ - - /// Returns true if the polygon reference is valid and passes the filter restrictions. - /// @param[in] ref The polygon reference to check. - /// @param[in] filter The filter to apply. - bool isValidPolyRef(dtPolyRef ref, const dtQueryFilter* filter) const; - - /// Returns true if the polygon reference is in the closed list. - /// @param[in] ref The reference id of the polygon to check. - /// @returns True if the polygon is in closed list. - bool isInClosedList(dtPolyRef ref) const; - - /// Gets the node pool. - /// @returns The node pool. - class dtNodePool* getNodePool() const { return m_nodePool; } - - /// Gets the navigation mesh the query object is using. - /// @return The navigation mesh the query object is using. - const dtNavMesh* getAttachedNavMesh() const { return m_nav; } - - /// @} - -private: - // Explicitly disabled copy constructor and copy assignment operator - dtNavMeshQuery(const dtNavMeshQuery&); - dtNavMeshQuery& operator=(const dtNavMeshQuery&); - - /// Queries polygons within a tile. - void queryPolygonsInTile(const dtMeshTile* tile, const float* qmin, const float* qmax, - const dtQueryFilter* filter, dtPolyQuery* query) const; - - /// Returns portal points between two polygons. - dtStatus getPortalPoints(dtPolyRef from, dtPolyRef to, float* left, float* right, - unsigned char& fromType, unsigned char& toType) const; - dtStatus getPortalPoints(dtPolyRef from, const dtPoly* fromPoly, const dtMeshTile* fromTile, - dtPolyRef to, const dtPoly* toPoly, const dtMeshTile* toTile, - float* left, float* right) const; - - /// Returns edge mid point between two polygons. - dtStatus getEdgeMidPoint(dtPolyRef from, dtPolyRef to, float* mid) const; - dtStatus getEdgeMidPoint(dtPolyRef from, const dtPoly* fromPoly, const dtMeshTile* fromTile, - dtPolyRef to, const dtPoly* toPoly, const dtMeshTile* toTile, - float* mid) const; - - // Appends vertex to a straight path - dtStatus appendVertex(const float* pos, const unsigned char flags, const dtPolyRef ref, - float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, - int* straightPathCount, const int maxStraightPath) const; - - // Appends intermediate portal points to a straight path. - dtStatus appendPortals(const int startIdx, const int endIdx, const float* endPos, const dtPolyRef* path, - float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, - int* straightPathCount, const int maxStraightPath, const int options) const; - - // Gets the path leading to the specified end node. - dtStatus getPathToNode(struct dtNode* endNode, dtPolyRef* path, int* pathCount, int maxPath) const; - - const dtNavMesh* m_nav; ///< Pointer to navmesh data. - - struct dtQueryData - { - dtStatus status; - struct dtNode* lastBestNode; - float lastBestNodeCost; - dtPolyRef startRef, endRef; - float startPos[3], endPos[3]; - const dtQueryFilter* filter; - unsigned int options; - float raycastLimitSqr; - }; - dtQueryData m_query; ///< Sliced query state. - - class dtNodePool* m_tinyNodePool; ///< Pointer to small node pool. - class dtNodePool* m_nodePool; ///< Pointer to node pool. - class dtNodeQueue* m_openList; ///< Pointer to open list queue. -}; - -/// Allocates a query object using the Detour allocator. -/// @return An allocated query object, or null on failure. -/// @ingroup detour -dtNavMeshQuery* dtAllocNavMeshQuery(); - -/// Frees the specified query object using the Detour allocator. -/// @param[in] query A query object allocated using #dtAllocNavMeshQuery -/// @ingroup detour -void dtFreeNavMeshQuery(dtNavMeshQuery* query); - -#endif // DETOURNAVMESHQUERY_H diff --git a/extern/recastnavigation/Detour/Include/DetourNode.h b/extern/recastnavigation/Detour/Include/DetourNode.h deleted file mode 100644 index db09747080..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourNode.h +++ /dev/null @@ -1,168 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURNODE_H -#define DETOURNODE_H - -#include "DetourNavMesh.h" - -enum dtNodeFlags -{ - DT_NODE_OPEN = 0x01, - DT_NODE_CLOSED = 0x02, - DT_NODE_PARENT_DETACHED = 0x04, // parent of the node is not adjacent. Found using raycast. -}; - -typedef unsigned short dtNodeIndex; -static const dtNodeIndex DT_NULL_IDX = (dtNodeIndex)~0; - -static const int DT_NODE_PARENT_BITS = 24; -static const int DT_NODE_STATE_BITS = 2; -struct dtNode -{ - float pos[3]; ///< Position of the node. - float cost; ///< Cost from previous node to current node. - float total; ///< Cost up to the node. - unsigned int pidx : DT_NODE_PARENT_BITS; ///< Index to parent node. - unsigned int state : DT_NODE_STATE_BITS; ///< extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE - unsigned int flags : 3; ///< Node flags. A combination of dtNodeFlags. - dtPolyRef id; ///< Polygon ref the node corresponds to. -}; - -static const int DT_MAX_STATES_PER_NODE = 1 << DT_NODE_STATE_BITS; // number of extra states per node. See dtNode::state - -class dtNodePool -{ -public: - dtNodePool(int maxNodes, int hashSize); - ~dtNodePool(); - void clear(); - - // Get a dtNode by ref and extra state information. If there is none then - allocate - // There can be more than one node for the same polyRef but with different extra state information - dtNode* getNode(dtPolyRef id, unsigned char state=0); - dtNode* findNode(dtPolyRef id, unsigned char state); - unsigned int findNodes(dtPolyRef id, dtNode** nodes, const int maxNodes); - - inline unsigned int getNodeIdx(const dtNode* node) const - { - if (!node) return 0; - return (unsigned int)(node - m_nodes) + 1; - } - - inline dtNode* getNodeAtIdx(unsigned int idx) - { - if (!idx) return 0; - return &m_nodes[idx - 1]; - } - - inline const dtNode* getNodeAtIdx(unsigned int idx) const - { - if (!idx) return 0; - return &m_nodes[idx - 1]; - } - - inline int getMemUsed() const - { - return sizeof(*this) + - sizeof(dtNode)*m_maxNodes + - sizeof(dtNodeIndex)*m_maxNodes + - sizeof(dtNodeIndex)*m_hashSize; - } - - inline int getMaxNodes() const { return m_maxNodes; } - - inline int getHashSize() const { return m_hashSize; } - inline dtNodeIndex getFirst(int bucket) const { return m_first[bucket]; } - inline dtNodeIndex getNext(int i) const { return m_next[i]; } - inline int getNodeCount() const { return m_nodeCount; } - -private: - // Explicitly disabled copy constructor and copy assignment operator. - dtNodePool(const dtNodePool&); - dtNodePool& operator=(const dtNodePool&); - - dtNode* m_nodes; - dtNodeIndex* m_first; - dtNodeIndex* m_next; - const int m_maxNodes; - const int m_hashSize; - int m_nodeCount; -}; - -class dtNodeQueue -{ -public: - dtNodeQueue(int n); - ~dtNodeQueue(); - - inline void clear() { m_size = 0; } - - inline dtNode* top() { return m_heap[0]; } - - inline dtNode* pop() - { - dtNode* result = m_heap[0]; - m_size--; - trickleDown(0, m_heap[m_size]); - return result; - } - - inline void push(dtNode* node) - { - m_size++; - bubbleUp(m_size-1, node); - } - - inline void modify(dtNode* node) - { - for (int i = 0; i < m_size; ++i) - { - if (m_heap[i] == node) - { - bubbleUp(i, node); - return; - } - } - } - - inline bool empty() const { return m_size == 0; } - - inline int getMemUsed() const - { - return sizeof(*this) + - sizeof(dtNode*) * (m_capacity + 1); - } - - inline int getCapacity() const { return m_capacity; } - -private: - // Explicitly disabled copy constructor and copy assignment operator. - dtNodeQueue(const dtNodeQueue&); - dtNodeQueue& operator=(const dtNodeQueue&); - - void bubbleUp(int i, dtNode* node); - void trickleDown(int i, dtNode* node); - - dtNode** m_heap; - const int m_capacity; - int m_size; -}; - - -#endif // DETOURNODE_H diff --git a/extern/recastnavigation/Detour/Include/DetourStatus.h b/extern/recastnavigation/Detour/Include/DetourStatus.h deleted file mode 100644 index 8e1bb44b9d..0000000000 --- a/extern/recastnavigation/Detour/Include/DetourStatus.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURSTATUS_H -#define DETOURSTATUS_H - -typedef unsigned int dtStatus; - -// High level status. -static const unsigned int DT_FAILURE = 1u << 31; // Operation failed. -static const unsigned int DT_SUCCESS = 1u << 30; // Operation succeed. -static const unsigned int DT_IN_PROGRESS = 1u << 29; // Operation still in progress. - -// Detail information for status. -static const unsigned int DT_STATUS_DETAIL_MASK = 0x0ffffff; -static const unsigned int DT_WRONG_MAGIC = 1 << 0; // Input data is not recognized. -static const unsigned int DT_WRONG_VERSION = 1 << 1; // Input data is in wrong version. -static const unsigned int DT_OUT_OF_MEMORY = 1 << 2; // Operation ran out of memory. -static const unsigned int DT_INVALID_PARAM = 1 << 3; // An input parameter was invalid. -static const unsigned int DT_BUFFER_TOO_SMALL = 1 << 4; // Result buffer for the query was too small to store all results. -static const unsigned int DT_OUT_OF_NODES = 1 << 5; // Query ran out of nodes during search. -static const unsigned int DT_PARTIAL_RESULT = 1 << 6; // Query did not reach the end location, returning best guess. -static const unsigned int DT_ALREADY_OCCUPIED = 1 << 7; // A tile has already been assigned to the given x,y coordinate - - -// Returns true of status is success. -inline bool dtStatusSucceed(dtStatus status) -{ - return (status & DT_SUCCESS) != 0; -} - -// Returns true of status is failure. -inline bool dtStatusFailed(dtStatus status) -{ - return (status & DT_FAILURE) != 0; -} - -// Returns true of status is in progress. -inline bool dtStatusInProgress(dtStatus status) -{ - return (status & DT_IN_PROGRESS) != 0; -} - -// Returns true if specific detail is set. -inline bool dtStatusDetail(dtStatus status, unsigned int detail) -{ - return (status & detail) != 0; -} - -#endif // DETOURSTATUS_H diff --git a/extern/recastnavigation/Detour/Source/DetourAlloc.cpp b/extern/recastnavigation/Detour/Source/DetourAlloc.cpp deleted file mode 100644 index d9ad1fc013..0000000000 --- a/extern/recastnavigation/Detour/Source/DetourAlloc.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#include "DetourAlloc.h" - -static void *dtAllocDefault(size_t size, dtAllocHint) -{ - return malloc(size); -} - -static void dtFreeDefault(void *ptr) -{ - free(ptr); -} - -static dtAllocFunc* sAllocFunc = dtAllocDefault; -static dtFreeFunc* sFreeFunc = dtFreeDefault; - -void dtAllocSetCustom(dtAllocFunc *allocFunc, dtFreeFunc *freeFunc) -{ - sAllocFunc = allocFunc ? allocFunc : dtAllocDefault; - sFreeFunc = freeFunc ? freeFunc : dtFreeDefault; -} - -void* dtAlloc(size_t size, dtAllocHint hint) -{ - return sAllocFunc(size, hint); -} - -void dtFree(void* ptr) -{ - if (ptr) - sFreeFunc(ptr); -} diff --git a/extern/recastnavigation/Detour/Source/DetourAssert.cpp b/extern/recastnavigation/Detour/Source/DetourAssert.cpp deleted file mode 100644 index 5e019e0cfc..0000000000 --- a/extern/recastnavigation/Detour/Source/DetourAssert.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include "DetourAssert.h" - -#ifndef NDEBUG - -static dtAssertFailFunc* sAssertFailFunc = 0; - -void dtAssertFailSetCustom(dtAssertFailFunc *assertFailFunc) -{ - sAssertFailFunc = assertFailFunc; -} - -dtAssertFailFunc* dtAssertFailGetCustom() -{ - return sAssertFailFunc; -} - -#endif diff --git a/extern/recastnavigation/Detour/Source/DetourCommon.cpp b/extern/recastnavigation/Detour/Source/DetourCommon.cpp deleted file mode 100644 index b89d7512c4..0000000000 --- a/extern/recastnavigation/Detour/Source/DetourCommon.cpp +++ /dev/null @@ -1,387 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include "DetourCommon.h" -#include "DetourMath.h" - -////////////////////////////////////////////////////////////////////////////////////////// - -void dtClosestPtPointTriangle(float* closest, const float* p, - const float* a, const float* b, const float* c) -{ - // Check if P in vertex region outside A - float ab[3], ac[3], ap[3]; - dtVsub(ab, b, a); - dtVsub(ac, c, a); - dtVsub(ap, p, a); - float d1 = dtVdot(ab, ap); - float d2 = dtVdot(ac, ap); - if (d1 <= 0.0f && d2 <= 0.0f) - { - // barycentric coordinates (1,0,0) - dtVcopy(closest, a); - return; - } - - // Check if P in vertex region outside B - float bp[3]; - dtVsub(bp, p, b); - float d3 = dtVdot(ab, bp); - float d4 = dtVdot(ac, bp); - if (d3 >= 0.0f && d4 <= d3) - { - // barycentric coordinates (0,1,0) - dtVcopy(closest, b); - return; - } - - // Check if P in edge region of AB, if so return projection of P onto AB - float vc = d1*d4 - d3*d2; - if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) - { - // barycentric coordinates (1-v,v,0) - float v = d1 / (d1 - d3); - closest[0] = a[0] + v * ab[0]; - closest[1] = a[1] + v * ab[1]; - closest[2] = a[2] + v * ab[2]; - return; - } - - // Check if P in vertex region outside C - float cp[3]; - dtVsub(cp, p, c); - float d5 = dtVdot(ab, cp); - float d6 = dtVdot(ac, cp); - if (d6 >= 0.0f && d5 <= d6) - { - // barycentric coordinates (0,0,1) - dtVcopy(closest, c); - return; - } - - // Check if P in edge region of AC, if so return projection of P onto AC - float vb = d5*d2 - d1*d6; - if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) - { - // barycentric coordinates (1-w,0,w) - float w = d2 / (d2 - d6); - closest[0] = a[0] + w * ac[0]; - closest[1] = a[1] + w * ac[1]; - closest[2] = a[2] + w * ac[2]; - return; - } - - // Check if P in edge region of BC, if so return projection of P onto BC - float va = d3*d6 - d5*d4; - if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) - { - // barycentric coordinates (0,1-w,w) - float w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); - closest[0] = b[0] + w * (c[0] - b[0]); - closest[1] = b[1] + w * (c[1] - b[1]); - closest[2] = b[2] + w * (c[2] - b[2]); - return; - } - - // P inside face region. Compute Q through its barycentric coordinates (u,v,w) - float denom = 1.0f / (va + vb + vc); - float v = vb * denom; - float w = vc * denom; - closest[0] = a[0] + ab[0] * v + ac[0] * w; - closest[1] = a[1] + ab[1] * v + ac[1] * w; - closest[2] = a[2] + ab[2] * v + ac[2] * w; -} - -bool dtIntersectSegmentPoly2D(const float* p0, const float* p1, - const float* verts, int nverts, - float& tmin, float& tmax, - int& segMin, int& segMax) -{ - static const float EPS = 0.00000001f; - - tmin = 0; - tmax = 1; - segMin = -1; - segMax = -1; - - float dir[3]; - dtVsub(dir, p1, p0); - - for (int i = 0, j = nverts-1; i < nverts; j=i++) - { - float edge[3], diff[3]; - dtVsub(edge, &verts[i*3], &verts[j*3]); - dtVsub(diff, p0, &verts[j*3]); - const float n = dtVperp2D(edge, diff); - const float d = dtVperp2D(dir, edge); - if (fabsf(d) < EPS) - { - // S is nearly parallel to this edge - if (n < 0) - return false; - else - continue; - } - const float t = n / d; - if (d < 0) - { - // segment S is entering across this edge - if (t > tmin) - { - tmin = t; - segMin = j; - // S enters after leaving polygon - if (tmin > tmax) - return false; - } - } - else - { - // segment S is leaving across this edge - if (t < tmax) - { - tmax = t; - segMax = j; - // S leaves before entering polygon - if (tmax < tmin) - return false; - } - } - } - - return true; -} - -float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, float& t) -{ - float pqx = q[0] - p[0]; - float pqz = q[2] - p[2]; - float dx = pt[0] - p[0]; - float dz = pt[2] - p[2]; - float d = pqx*pqx + pqz*pqz; - t = pqx*dx + pqz*dz; - if (d > 0) t /= d; - if (t < 0) t = 0; - else if (t > 1) t = 1; - dx = p[0] + t*pqx - pt[0]; - dz = p[2] + t*pqz - pt[2]; - return dx*dx + dz*dz; -} - -void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const float* verts) -{ - tc[0] = 0.0f; - tc[1] = 0.0f; - tc[2] = 0.0f; - for (int j = 0; j < nidx; ++j) - { - const float* v = &verts[idx[j]*3]; - tc[0] += v[0]; - tc[1] += v[1]; - tc[2] += v[2]; - } - const float s = 1.0f / nidx; - tc[0] *= s; - tc[1] *= s; - tc[2] *= s; -} - -bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h) -{ - const float EPS = 1e-6f; - float v0[3], v1[3], v2[3]; - - dtVsub(v0, c, a); - dtVsub(v1, b, a); - dtVsub(v2, p, a); - - // Compute scaled barycentric coordinates - float denom = v0[0] * v1[2] - v0[2] * v1[0]; - if (fabsf(denom) < EPS) - return false; - - float u = v1[2] * v2[0] - v1[0] * v2[2]; - float v = v0[0] * v2[2] - v0[2] * v2[0]; - - if (denom < 0) { - denom = -denom; - u = -u; - v = -v; - } - - // If point lies inside the triangle, return interpolated ycoord. - if (u >= 0.0f && v >= 0.0f && (u + v) <= denom) { - h = a[1] + (v0[1] * u + v1[1] * v) / denom; - return true; - } - return false; -} - -/// @par -/// -/// All points are projected onto the xz-plane, so the y-values are ignored. -bool dtPointInPolygon(const float* pt, const float* verts, const int nverts) -{ - // TODO: Replace pnpoly with triArea2D tests? - int i, j; - bool c = false; - for (i = 0, j = nverts-1; i < nverts; j = i++) - { - const float* vi = &verts[i*3]; - const float* vj = &verts[j*3]; - if (((vi[2] > pt[2]) != (vj[2] > pt[2])) && - (pt[0] < (vj[0]-vi[0]) * (pt[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) ) - c = !c; - } - return c; -} - -bool dtDistancePtPolyEdgesSqr(const float* pt, const float* verts, const int nverts, - float* ed, float* et) -{ - // TODO: Replace pnpoly with triArea2D tests? - int i, j; - bool c = false; - for (i = 0, j = nverts-1; i < nverts; j = i++) - { - const float* vi = &verts[i*3]; - const float* vj = &verts[j*3]; - if (((vi[2] > pt[2]) != (vj[2] > pt[2])) && - (pt[0] < (vj[0]-vi[0]) * (pt[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) ) - c = !c; - ed[j] = dtDistancePtSegSqr2D(pt, vj, vi, et[j]); - } - return c; -} - -static void projectPoly(const float* axis, const float* poly, const int npoly, - float& rmin, float& rmax) -{ - rmin = rmax = dtVdot2D(axis, &poly[0]); - for (int i = 1; i < npoly; ++i) - { - const float d = dtVdot2D(axis, &poly[i*3]); - rmin = dtMin(rmin, d); - rmax = dtMax(rmax, d); - } -} - -inline bool overlapRange(const float amin, const float amax, - const float bmin, const float bmax, - const float eps) -{ - return ((amin+eps) > bmax || (amax-eps) < bmin) ? false : true; -} - -/// @par -/// -/// All vertices are projected onto the xz-plane, so the y-values are ignored. -bool dtOverlapPolyPoly2D(const float* polya, const int npolya, - const float* polyb, const int npolyb) -{ - const float eps = 1e-4f; - - for (int i = 0, j = npolya-1; i < npolya; j=i++) - { - const float* va = &polya[j*3]; - const float* vb = &polya[i*3]; - const float n[3] = { vb[2]-va[2], 0, -(vb[0]-va[0]) }; - float amin,amax,bmin,bmax; - projectPoly(n, polya, npolya, amin,amax); - projectPoly(n, polyb, npolyb, bmin,bmax); - if (!overlapRange(amin,amax, bmin,bmax, eps)) - { - // Found separating axis - return false; - } - } - for (int i = 0, j = npolyb-1; i < npolyb; j=i++) - { - const float* va = &polyb[j*3]; - const float* vb = &polyb[i*3]; - const float n[3] = { vb[2]-va[2], 0, -(vb[0]-va[0]) }; - float amin,amax,bmin,bmax; - projectPoly(n, polya, npolya, amin,amax); - projectPoly(n, polyb, npolyb, bmin,bmax); - if (!overlapRange(amin,amax, bmin,bmax, eps)) - { - // Found separating axis - return false; - } - } - return true; -} - -// Returns a random point in a convex polygon. -// Adapted from Graphics Gems article. -void dtRandomPointInConvexPoly(const float* pts, const int npts, float* areas, - const float s, const float t, float* out) -{ - // Calc triangle araes - float areasum = 0.0f; - for (int i = 2; i < npts; i++) { - areas[i] = dtTriArea2D(&pts[0], &pts[(i-1)*3], &pts[i*3]); - areasum += dtMax(0.001f, areas[i]); - } - // Find sub triangle weighted by area. - const float thr = s*areasum; - float acc = 0.0f; - float u = 1.0f; - int tri = npts - 1; - for (int i = 2; i < npts; i++) { - const float dacc = areas[i]; - if (thr >= acc && thr < (acc+dacc)) - { - u = (thr - acc) / dacc; - tri = i; - break; - } - acc += dacc; - } - - float v = dtMathSqrtf(t); - - const float a = 1 - v; - const float b = (1 - u) * v; - const float c = u * v; - const float* pa = &pts[0]; - const float* pb = &pts[(tri-1)*3]; - const float* pc = &pts[tri*3]; - - out[0] = a*pa[0] + b*pb[0] + c*pc[0]; - out[1] = a*pa[1] + b*pb[1] + c*pc[1]; - out[2] = a*pa[2] + b*pb[2] + c*pc[2]; -} - -inline float vperpXZ(const float* a, const float* b) { return a[0]*b[2] - a[2]*b[0]; } - -bool dtIntersectSegSeg2D(const float* ap, const float* aq, - const float* bp, const float* bq, - float& s, float& t) -{ - float u[3], v[3], w[3]; - dtVsub(u,aq,ap); - dtVsub(v,bq,bp); - dtVsub(w,ap,bp); - float d = vperpXZ(u,v); - if (fabsf(d) < 1e-6f) return false; - s = vperpXZ(v,w) / d; - t = vperpXZ(u,w) / d; - return true; -} - diff --git a/extern/recastnavigation/Detour/Source/DetourNavMesh.cpp b/extern/recastnavigation/Detour/Source/DetourNavMesh.cpp deleted file mode 100644 index a655d1d89a..0000000000 --- a/extern/recastnavigation/Detour/Source/DetourNavMesh.cpp +++ /dev/null @@ -1,1584 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#include -#include -#include "DetourNavMesh.h" -#include "DetourNode.h" -#include "DetourCommon.h" -#include "DetourMath.h" -#include "DetourAlloc.h" -#include "DetourAssert.h" -#include - - -inline bool overlapSlabs(const float* amin, const float* amax, - const float* bmin, const float* bmax, - const float px, const float py) -{ - // Check for horizontal overlap. - // The segment is shrunken a little so that slabs which touch - // at end points are not connected. - const float minx = dtMax(amin[0]+px,bmin[0]+px); - const float maxx = dtMin(amax[0]-px,bmax[0]-px); - if (minx > maxx) - return false; - - // Check vertical overlap. - const float ad = (amax[1]-amin[1]) / (amax[0]-amin[0]); - const float ak = amin[1] - ad*amin[0]; - const float bd = (bmax[1]-bmin[1]) / (bmax[0]-bmin[0]); - const float bk = bmin[1] - bd*bmin[0]; - const float aminy = ad*minx + ak; - const float amaxy = ad*maxx + ak; - const float bminy = bd*minx + bk; - const float bmaxy = bd*maxx + bk; - const float dmin = bminy - aminy; - const float dmax = bmaxy - amaxy; - - // Crossing segments always overlap. - if (dmin*dmax < 0) - return true; - - // Check for overlap at endpoints. - const float thr = dtSqr(py*2); - if (dmin*dmin <= thr || dmax*dmax <= thr) - return true; - - return false; -} - -static float getSlabCoord(const float* va, const int side) -{ - if (side == 0 || side == 4) - return va[0]; - else if (side == 2 || side == 6) - return va[2]; - return 0; -} - -static void calcSlabEndPoints(const float* va, const float* vb, float* bmin, float* bmax, const int side) -{ - if (side == 0 || side == 4) - { - if (va[2] < vb[2]) - { - bmin[0] = va[2]; - bmin[1] = va[1]; - bmax[0] = vb[2]; - bmax[1] = vb[1]; - } - else - { - bmin[0] = vb[2]; - bmin[1] = vb[1]; - bmax[0] = va[2]; - bmax[1] = va[1]; - } - } - else if (side == 2 || side == 6) - { - if (va[0] < vb[0]) - { - bmin[0] = va[0]; - bmin[1] = va[1]; - bmax[0] = vb[0]; - bmax[1] = vb[1]; - } - else - { - bmin[0] = vb[0]; - bmin[1] = vb[1]; - bmax[0] = va[0]; - bmax[1] = va[1]; - } - } -} - -inline int computeTileHash(int x, int y, const int mask) -{ - const unsigned int h1 = 0x8da6b343; // Large multiplicative constants; - const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes - unsigned int n = h1 * x + h2 * y; - return (int)(n & mask); -} - -inline unsigned int allocLink(dtMeshTile* tile) -{ - if (tile->linksFreeList == DT_NULL_LINK) - return DT_NULL_LINK; - unsigned int link = tile->linksFreeList; - tile->linksFreeList = tile->links[link].next; - return link; -} - -inline void freeLink(dtMeshTile* tile, unsigned int link) -{ - tile->links[link].next = tile->linksFreeList; - tile->linksFreeList = link; -} - - -dtNavMesh* dtAllocNavMesh() -{ - void* mem = dtAlloc(sizeof(dtNavMesh), DT_ALLOC_PERM); - if (!mem) return 0; - return new(mem) dtNavMesh; -} - -/// @par -/// -/// This function will only free the memory for tiles with the #DT_TILE_FREE_DATA -/// flag set. -void dtFreeNavMesh(dtNavMesh* navmesh) -{ - if (!navmesh) return; - navmesh->~dtNavMesh(); - dtFree(navmesh); -} - -////////////////////////////////////////////////////////////////////////////////////////// - -/** -@class dtNavMesh - -The navigation mesh consists of one or more tiles defining three primary types of structural data: - -A polygon mesh which defines most of the navigation graph. (See rcPolyMesh for its structure.) -A detail mesh used for determining surface height on the polygon mesh. (See rcPolyMeshDetail for its structure.) -Off-mesh connections, which define custom point-to-point edges within the navigation graph. - -The general build process is as follows: - --# Create rcPolyMesh and rcPolyMeshDetail data using the Recast build pipeline. --# Optionally, create off-mesh connection data. --# Combine the source data into a dtNavMeshCreateParams structure. --# Create a tile data array using dtCreateNavMeshData(). --# Allocate at dtNavMesh object and initialize it. (For single tile navigation meshes, - the tile data is loaded during this step.) --# For multi-tile navigation meshes, load the tile data using dtNavMesh::addTile(). - -Notes: - -- This class is usually used in conjunction with the dtNavMeshQuery class for pathfinding. -- Technically, all navigation meshes are tiled. A 'solo' mesh is simply a navigation mesh initialized - to have only a single tile. -- This class does not implement any asynchronous methods. So the ::dtStatus result of all methods will - always contain either a success or failure flag. - -@see dtNavMeshQuery, dtCreateNavMeshData, dtNavMeshCreateParams, #dtAllocNavMesh, #dtFreeNavMesh -*/ - -dtNavMesh::dtNavMesh() : - m_tileWidth(0), - m_tileHeight(0), - m_maxTiles(0), - m_tileLutSize(0), - m_tileLutMask(0), - m_posLookup(0), - m_nextFree(0), - m_tiles(0) -{ -#ifndef DT_POLYREF64 - m_saltBits = 0; - m_tileBits = 0; - m_polyBits = 0; -#endif - memset(&m_params, 0, sizeof(dtNavMeshParams)); - m_orig[0] = 0; - m_orig[1] = 0; - m_orig[2] = 0; -} - -dtNavMesh::~dtNavMesh() -{ - for (int i = 0; i < m_maxTiles; ++i) - { - if (m_tiles[i].flags & DT_TILE_FREE_DATA) - { - dtFree(m_tiles[i].data); - m_tiles[i].data = 0; - m_tiles[i].dataSize = 0; - } - } - dtFree(m_posLookup); - dtFree(m_tiles); -} - -dtStatus dtNavMesh::init(const dtNavMeshParams* params) -{ - memcpy(&m_params, params, sizeof(dtNavMeshParams)); - dtVcopy(m_orig, params->orig); - m_tileWidth = params->tileWidth; - m_tileHeight = params->tileHeight; - - // Init tiles - m_maxTiles = params->maxTiles; - m_tileLutSize = dtNextPow2(params->maxTiles/4); - if (!m_tileLutSize) m_tileLutSize = 1; - m_tileLutMask = m_tileLutSize-1; - - m_tiles = (dtMeshTile*)dtAlloc(sizeof(dtMeshTile)*m_maxTiles, DT_ALLOC_PERM); - if (!m_tiles) - return DT_FAILURE | DT_OUT_OF_MEMORY; - m_posLookup = (dtMeshTile**)dtAlloc(sizeof(dtMeshTile*)*m_tileLutSize, DT_ALLOC_PERM); - if (!m_posLookup) - return DT_FAILURE | DT_OUT_OF_MEMORY; - memset(m_tiles, 0, sizeof(dtMeshTile)*m_maxTiles); - memset(m_posLookup, 0, sizeof(dtMeshTile*)*m_tileLutSize); - m_nextFree = 0; - for (int i = m_maxTiles-1; i >= 0; --i) - { - m_tiles[i].salt = 1; - m_tiles[i].next = m_nextFree; - m_nextFree = &m_tiles[i]; - } - - // Init ID generator values. -#ifndef DT_POLYREF64 - m_tileBits = dtIlog2(dtNextPow2((unsigned int)params->maxTiles)); - m_polyBits = dtIlog2(dtNextPow2((unsigned int)params->maxPolys)); - // Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow. - m_saltBits = dtMin((unsigned int)31, 32 - m_tileBits - m_polyBits); - - if (m_saltBits < 10) - return DT_FAILURE | DT_INVALID_PARAM; -#endif - - return DT_SUCCESS; -} - -dtStatus dtNavMesh::init(unsigned char* data, const int dataSize, const int flags) -{ - // Make sure the data is in right format. - dtMeshHeader* header = (dtMeshHeader*)data; - if (header->magic != DT_NAVMESH_MAGIC) - return DT_FAILURE | DT_WRONG_MAGIC; - if (header->version != DT_NAVMESH_VERSION) - return DT_FAILURE | DT_WRONG_VERSION; - - dtNavMeshParams params; - dtVcopy(params.orig, header->bmin); - params.tileWidth = header->bmax[0] - header->bmin[0]; - params.tileHeight = header->bmax[2] - header->bmin[2]; - params.maxTiles = 1; - params.maxPolys = header->polyCount; - - dtStatus status = init(¶ms); - if (dtStatusFailed(status)) - return status; - - return addTile(data, dataSize, flags, 0, 0); -} - -/// @par -/// -/// @note The parameters are created automatically when the single tile -/// initialization is performed. -const dtNavMeshParams* dtNavMesh::getParams() const -{ - return &m_params; -} - -////////////////////////////////////////////////////////////////////////////////////////// -int dtNavMesh::findConnectingPolys(const float* va, const float* vb, - const dtMeshTile* tile, int side, - dtPolyRef* con, float* conarea, int maxcon) const -{ - if (!tile) return 0; - - float amin[2], amax[2]; - calcSlabEndPoints(va, vb, amin, amax, side); - const float apos = getSlabCoord(va, side); - - // Remove links pointing to 'side' and compact the links array. - float bmin[2], bmax[2]; - unsigned short m = DT_EXT_LINK | (unsigned short)side; - int n = 0; - - dtPolyRef base = getPolyRefBase(tile); - - for (int i = 0; i < tile->header->polyCount; ++i) - { - dtPoly* poly = &tile->polys[i]; - const int nv = poly->vertCount; - for (int j = 0; j < nv; ++j) - { - // Skip edges which do not point to the right side. - if (poly->neis[j] != m) continue; - - const float* vc = &tile->verts[poly->verts[j]*3]; - const float* vd = &tile->verts[poly->verts[(j+1) % nv]*3]; - const float bpos = getSlabCoord(vc, side); - - // Segments are not close enough. - if (dtAbs(apos-bpos) > 0.01f) - continue; - - // Check if the segments touch. - calcSlabEndPoints(vc,vd, bmin,bmax, side); - - if (!overlapSlabs(amin,amax, bmin,bmax, 0.01f, tile->header->walkableClimb)) continue; - - // Add return value. - if (n < maxcon) - { - conarea[n*2+0] = dtMax(amin[0], bmin[0]); - conarea[n*2+1] = dtMin(amax[0], bmax[0]); - con[n] = base | (dtPolyRef)i; - n++; - } - break; - } - } - return n; -} - -void dtNavMesh::unconnectLinks(dtMeshTile* tile, dtMeshTile* target) -{ - if (!tile || !target) return; - - const unsigned int targetNum = decodePolyIdTile(getTileRef(target)); - - for (int i = 0; i < tile->header->polyCount; ++i) - { - dtPoly* poly = &tile->polys[i]; - unsigned int j = poly->firstLink; - unsigned int pj = DT_NULL_LINK; - while (j != DT_NULL_LINK) - { - if (decodePolyIdTile(tile->links[j].ref) == targetNum) - { - // Remove link. - unsigned int nj = tile->links[j].next; - if (pj == DT_NULL_LINK) - poly->firstLink = nj; - else - tile->links[pj].next = nj; - freeLink(tile, j); - j = nj; - } - else - { - // Advance - pj = j; - j = tile->links[j].next; - } - } - } -} - -void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) -{ - if (!tile) return; - - // Connect border links. - for (int i = 0; i < tile->header->polyCount; ++i) - { - dtPoly* poly = &tile->polys[i]; - - // Create new links. -// unsigned short m = DT_EXT_LINK | (unsigned short)side; - - const int nv = poly->vertCount; - for (int j = 0; j < nv; ++j) - { - // Skip non-portal edges. - if ((poly->neis[j] & DT_EXT_LINK) == 0) - continue; - - const int dir = (int)(poly->neis[j] & 0xff); - if (side != -1 && dir != side) - continue; - - // Create new links - const float* va = &tile->verts[poly->verts[j]*3]; - const float* vb = &tile->verts[poly->verts[(j+1) % nv]*3]; - dtPolyRef nei[4]; - float neia[4*2]; - int nnei = findConnectingPolys(va,vb, target, dtOppositeTile(dir), nei,neia,4); - for (int k = 0; k < nnei; ++k) - { - unsigned int idx = allocLink(tile); - if (idx != DT_NULL_LINK) - { - dtLink* link = &tile->links[idx]; - link->ref = nei[k]; - link->edge = (unsigned char)j; - link->side = (unsigned char)dir; - - link->next = poly->firstLink; - poly->firstLink = idx; - - // Compress portal limits to a byte value. - if (dir == 0 || dir == 4) - { - float tmin = (neia[k*2+0]-va[2]) / (vb[2]-va[2]); - float tmax = (neia[k*2+1]-va[2]) / (vb[2]-va[2]); - if (tmin > tmax) - dtSwap(tmin,tmax); - link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); - link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); - } - else if (dir == 2 || dir == 6) - { - float tmin = (neia[k*2+0]-va[0]) / (vb[0]-va[0]); - float tmax = (neia[k*2+1]-va[0]) / (vb[0]-va[0]); - if (tmin > tmax) - dtSwap(tmin,tmax); - link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); - link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); - } - } - } - } - } -} - -void dtNavMesh::connectExtOffMeshLinks(dtMeshTile* tile, dtMeshTile* target, int side) -{ - if (!tile) return; - - // Connect off-mesh links. - // We are interested on links which land from target tile to this tile. - const unsigned char oppositeSide = (side == -1) ? 0xff : (unsigned char)dtOppositeTile(side); - - for (int i = 0; i < target->header->offMeshConCount; ++i) - { - dtOffMeshConnection* targetCon = &target->offMeshCons[i]; - if (targetCon->side != oppositeSide) - continue; - - dtPoly* targetPoly = &target->polys[targetCon->poly]; - // Skip off-mesh connections which start location could not be connected at all. - if (targetPoly->firstLink == DT_NULL_LINK) - continue; - - const float halfExtents[3] = { targetCon->rad, target->header->walkableClimb, targetCon->rad }; - - // Find polygon to connect to. - const float* p = &targetCon->pos[3]; - float nearestPt[3]; - dtPolyRef ref = findNearestPolyInTile(tile, p, halfExtents, nearestPt); - if (!ref) - continue; - // findNearestPoly may return too optimistic results, further check to make sure. - if (dtSqr(nearestPt[0]-p[0])+dtSqr(nearestPt[2]-p[2]) > dtSqr(targetCon->rad)) - continue; - // Make sure the location is on current mesh. - float* v = &target->verts[targetPoly->verts[1]*3]; - dtVcopy(v, nearestPt); - - // Link off-mesh connection to target poly. - unsigned int idx = allocLink(target); - if (idx != DT_NULL_LINK) - { - dtLink* link = &target->links[idx]; - link->ref = ref; - link->edge = (unsigned char)1; - link->side = oppositeSide; - link->bmin = link->bmax = 0; - // Add to linked list. - link->next = targetPoly->firstLink; - targetPoly->firstLink = idx; - } - - // Link target poly to off-mesh connection. - if (targetCon->flags & DT_OFFMESH_CON_BIDIR) - { - unsigned int tidx = allocLink(tile); - if (tidx != DT_NULL_LINK) - { - const unsigned short landPolyIdx = (unsigned short)decodePolyIdPoly(ref); - dtPoly* landPoly = &tile->polys[landPolyIdx]; - dtLink* link = &tile->links[tidx]; - link->ref = getPolyRefBase(target) | (dtPolyRef)(targetCon->poly); - link->edge = 0xff; - link->side = (unsigned char)(side == -1 ? 0xff : side); - link->bmin = link->bmax = 0; - // Add to linked list. - link->next = landPoly->firstLink; - landPoly->firstLink = tidx; - } - } - } - -} - -void dtNavMesh::connectIntLinks(dtMeshTile* tile) -{ - if (!tile) return; - - dtPolyRef base = getPolyRefBase(tile); - - for (int i = 0; i < tile->header->polyCount; ++i) - { - dtPoly* poly = &tile->polys[i]; - poly->firstLink = DT_NULL_LINK; - - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - continue; - - // Build edge links backwards so that the links will be - // in the linked list from lowest index to highest. - for (int j = poly->vertCount-1; j >= 0; --j) - { - // Skip hard and non-internal edges. - if (poly->neis[j] == 0 || (poly->neis[j] & DT_EXT_LINK)) continue; - - unsigned int idx = allocLink(tile); - if (idx != DT_NULL_LINK) - { - dtLink* link = &tile->links[idx]; - link->ref = base | (dtPolyRef)(poly->neis[j]-1); - link->edge = (unsigned char)j; - link->side = 0xff; - link->bmin = link->bmax = 0; - // Add to linked list. - link->next = poly->firstLink; - poly->firstLink = idx; - } - } - } -} - -void dtNavMesh::baseOffMeshLinks(dtMeshTile* tile) -{ - if (!tile) return; - - dtPolyRef base = getPolyRefBase(tile); - - // Base off-mesh connection start points. - for (int i = 0; i < tile->header->offMeshConCount; ++i) - { - dtOffMeshConnection* con = &tile->offMeshCons[i]; - dtPoly* poly = &tile->polys[con->poly]; - - const float halfExtents[3] = { con->rad, tile->header->walkableClimb, con->rad }; - - // Find polygon to connect to. - const float* p = &con->pos[0]; // First vertex - float nearestPt[3]; - dtPolyRef ref = findNearestPolyInTile(tile, p, halfExtents, nearestPt); - if (!ref) continue; - // findNearestPoly may return too optimistic results, further check to make sure. - if (dtSqr(nearestPt[0]-p[0])+dtSqr(nearestPt[2]-p[2]) > dtSqr(con->rad)) - continue; - // Make sure the location is on current mesh. - float* v = &tile->verts[poly->verts[0]*3]; - dtVcopy(v, nearestPt); - - // Link off-mesh connection to target poly. - unsigned int idx = allocLink(tile); - if (idx != DT_NULL_LINK) - { - dtLink* link = &tile->links[idx]; - link->ref = ref; - link->edge = (unsigned char)0; - link->side = 0xff; - link->bmin = link->bmax = 0; - // Add to linked list. - link->next = poly->firstLink; - poly->firstLink = idx; - } - - // Start end-point is always connect back to off-mesh connection. - unsigned int tidx = allocLink(tile); - if (tidx != DT_NULL_LINK) - { - const unsigned short landPolyIdx = (unsigned short)decodePolyIdPoly(ref); - dtPoly* landPoly = &tile->polys[landPolyIdx]; - dtLink* link = &tile->links[tidx]; - link->ref = base | (dtPolyRef)(con->poly); - link->edge = 0xff; - link->side = 0xff; - link->bmin = link->bmax = 0; - // Add to linked list. - link->next = landPoly->firstLink; - landPoly->firstLink = tidx; - } - } -} - -namespace -{ - template - void closestPointOnDetailEdges(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* closest) - { - const unsigned int ip = (unsigned int)(poly - tile->polys); - const dtPolyDetail* pd = &tile->detailMeshes[ip]; - - float dmin = FLT_MAX; - float tmin = 0; - const float* pmin = 0; - const float* pmax = 0; - - for (int i = 0; i < pd->triCount; i++) - { - const unsigned char* tris = &tile->detailTris[(pd->triBase + i) * 4]; - const int ANY_BOUNDARY_EDGE = - (DT_DETAIL_EDGE_BOUNDARY << 0) | - (DT_DETAIL_EDGE_BOUNDARY << 2) | - (DT_DETAIL_EDGE_BOUNDARY << 4); - if (onlyBoundary && (tris[3] & ANY_BOUNDARY_EDGE) == 0) - continue; - - const float* v[3]; - for (int j = 0; j < 3; ++j) - { - if (tris[j] < poly->vertCount) - v[j] = &tile->verts[poly->verts[tris[j]] * 3]; - else - v[j] = &tile->detailVerts[(pd->vertBase + (tris[j] - poly->vertCount)) * 3]; - } - - for (int k = 0, j = 2; k < 3; j = k++) - { - if ((dtGetDetailTriEdgeFlags(tris[3], j) & DT_DETAIL_EDGE_BOUNDARY) == 0 && - (onlyBoundary || tris[j] < tris[k])) - { - // Only looking at boundary edges and this is internal, or - // this is an inner edge that we will see again or have already seen. - continue; - } - - float t; - float d = dtDistancePtSegSqr2D(pos, v[j], v[k], t); - if (d < dmin) - { - dmin = d; - tmin = t; - pmin = v[j]; - pmax = v[k]; - } - } - } - - dtVlerp(closest, pmin, pmax, tmin); - } -} - -bool dtNavMesh::getPolyHeight(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* height) const -{ - // Off-mesh connections do not have detail polys and getting height - // over them does not make sense. - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - return false; - - const unsigned int ip = (unsigned int)(poly - tile->polys); - const dtPolyDetail* pd = &tile->detailMeshes[ip]; - - float verts[DT_VERTS_PER_POLYGON*3]; - const int nv = poly->vertCount; - for (int i = 0; i < nv; ++i) - dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]); - - if (!dtPointInPolygon(pos, verts, nv)) - return false; - - if (!height) - return true; - - // Find height at the location. - for (int j = 0; j < pd->triCount; ++j) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; - const float* v[3]; - for (int k = 0; k < 3; ++k) - { - if (t[k] < poly->vertCount) - v[k] = &tile->verts[poly->verts[t[k]]*3]; - else - v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; - } - float h; - if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h)) - { - *height = h; - return true; - } - } - - // If all triangle checks failed above (can happen with degenerate triangles - // or larger floating point values) the point is on an edge, so just select - // closest. This should almost never happen so the extra iteration here is - // ok. - float closest[3]; - closestPointOnDetailEdges(tile, poly, pos, closest); - *height = closest[1]; - return true; -} - -void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const -{ - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - getTileAndPolyByRefUnsafe(ref, &tile, &poly); - - dtVcopy(closest, pos); - if (getPolyHeight(tile, poly, pos, &closest[1])) - { - if (posOverPoly) - *posOverPoly = true; - return; - } - - if (posOverPoly) - *posOverPoly = false; - - // Off-mesh connections don't have detail polygons. - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - { - const float* v0 = &tile->verts[poly->verts[0]*3]; - const float* v1 = &tile->verts[poly->verts[1]*3]; - float t; - dtDistancePtSegSqr2D(pos, v0, v1, t); - dtVlerp(closest, v0, v1, t); - return; - } - - // Outside poly that is not an offmesh connection. - closestPointOnDetailEdges(tile, poly, pos, closest); -} - -dtPolyRef dtNavMesh::findNearestPolyInTile(const dtMeshTile* tile, - const float* center, const float* halfExtents, - float* nearestPt) const -{ - float bmin[3], bmax[3]; - dtVsub(bmin, center, halfExtents); - dtVadd(bmax, center, halfExtents); - - // Get nearby polygons from proximity grid. - dtPolyRef polys[128]; - int polyCount = queryPolygonsInTile(tile, bmin, bmax, polys, 128); - - // Find nearest polygon amongst the nearby polygons. - dtPolyRef nearest = 0; - float nearestDistanceSqr = FLT_MAX; - for (int i = 0; i < polyCount; ++i) - { - dtPolyRef ref = polys[i]; - float closestPtPoly[3]; - float diff[3]; - bool posOverPoly = false; - float d; - closestPointOnPoly(ref, center, closestPtPoly, &posOverPoly); - - // If a point is directly over a polygon and closer than - // climb height, favor that instead of straight line nearest point. - dtVsub(diff, center, closestPtPoly); - if (posOverPoly) - { - d = dtAbs(diff[1]) - tile->header->walkableClimb; - d = d > 0 ? d*d : 0; - } - else - { - d = dtVlenSqr(diff); - } - - if (d < nearestDistanceSqr) - { - dtVcopy(nearestPt, closestPtPoly); - nearestDistanceSqr = d; - nearest = ref; - } - } - - return nearest; -} - -int dtNavMesh::queryPolygonsInTile(const dtMeshTile* tile, const float* qmin, const float* qmax, - dtPolyRef* polys, const int maxPolys) const -{ - if (tile->bvTree) - { - const dtBVNode* node = &tile->bvTree[0]; - const dtBVNode* end = &tile->bvTree[tile->header->bvNodeCount]; - const float* tbmin = tile->header->bmin; - const float* tbmax = tile->header->bmax; - const float qfac = tile->header->bvQuantFactor; - - // Calculate quantized box - unsigned short bmin[3], bmax[3]; - // dtClamp query box to world box. - float minx = dtClamp(qmin[0], tbmin[0], tbmax[0]) - tbmin[0]; - float miny = dtClamp(qmin[1], tbmin[1], tbmax[1]) - tbmin[1]; - float minz = dtClamp(qmin[2], tbmin[2], tbmax[2]) - tbmin[2]; - float maxx = dtClamp(qmax[0], tbmin[0], tbmax[0]) - tbmin[0]; - float maxy = dtClamp(qmax[1], tbmin[1], tbmax[1]) - tbmin[1]; - float maxz = dtClamp(qmax[2], tbmin[2], tbmax[2]) - tbmin[2]; - // Quantize - bmin[0] = (unsigned short)(qfac * minx) & 0xfffe; - bmin[1] = (unsigned short)(qfac * miny) & 0xfffe; - bmin[2] = (unsigned short)(qfac * minz) & 0xfffe; - bmax[0] = (unsigned short)(qfac * maxx + 1) | 1; - bmax[1] = (unsigned short)(qfac * maxy + 1) | 1; - bmax[2] = (unsigned short)(qfac * maxz + 1) | 1; - - // Traverse tree - dtPolyRef base = getPolyRefBase(tile); - int n = 0; - while (node < end) - { - const bool overlap = dtOverlapQuantBounds(bmin, bmax, node->bmin, node->bmax); - const bool isLeafNode = node->i >= 0; - - if (isLeafNode && overlap) - { - if (n < maxPolys) - polys[n++] = base | (dtPolyRef)node->i; - } - - if (overlap || isLeafNode) - node++; - else - { - const int escapeIndex = -node->i; - node += escapeIndex; - } - } - - return n; - } - else - { - float bmin[3], bmax[3]; - int n = 0; - dtPolyRef base = getPolyRefBase(tile); - for (int i = 0; i < tile->header->polyCount; ++i) - { - dtPoly* p = &tile->polys[i]; - // Do not return off-mesh connection polygons. - if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - continue; - // Calc polygon bounds. - const float* v = &tile->verts[p->verts[0]*3]; - dtVcopy(bmin, v); - dtVcopy(bmax, v); - for (int j = 1; j < p->vertCount; ++j) - { - v = &tile->verts[p->verts[j]*3]; - dtVmin(bmin, v); - dtVmax(bmax, v); - } - if (dtOverlapBounds(qmin,qmax, bmin,bmax)) - { - if (n < maxPolys) - polys[n++] = base | (dtPolyRef)i; - } - } - return n; - } -} - -/// @par -/// -/// The add operation will fail if the data is in the wrong format, the allocated tile -/// space is full, or there is a tile already at the specified reference. -/// -/// The lastRef parameter is used to restore a tile with the same tile -/// reference it had previously used. In this case the #dtPolyRef's for the -/// tile will be restored to the same values they were before the tile was -/// removed. -/// -/// The nav mesh assumes exclusive access to the data passed and will make -/// changes to the dynamic portion of the data. For that reason the data -/// should not be reused in other nav meshes until the tile has been successfully -/// removed from this nav mesh. -/// -/// @see dtCreateNavMeshData, #removeTile -dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, - dtTileRef lastRef, dtTileRef* result) -{ - // Make sure the data is in right format. - dtMeshHeader* header = (dtMeshHeader*)data; - if (header->magic != DT_NAVMESH_MAGIC) - return DT_FAILURE | DT_WRONG_MAGIC; - if (header->version != DT_NAVMESH_VERSION) - return DT_FAILURE | DT_WRONG_VERSION; - - // Make sure the location is free. - if (getTileAt(header->x, header->y, header->layer)) - return DT_FAILURE | DT_ALREADY_OCCUPIED; - - // Allocate a tile. - dtMeshTile* tile = 0; - if (!lastRef) - { - if (m_nextFree) - { - tile = m_nextFree; - m_nextFree = tile->next; - tile->next = 0; - } - } - else - { - // Try to relocate the tile to specific index with same salt. - int tileIndex = (int)decodePolyIdTile((dtPolyRef)lastRef); - if (tileIndex >= m_maxTiles) - return DT_FAILURE | DT_OUT_OF_MEMORY; - // Try to find the specific tile id from the free list. - dtMeshTile* target = &m_tiles[tileIndex]; - dtMeshTile* prev = 0; - tile = m_nextFree; - while (tile && tile != target) - { - prev = tile; - tile = tile->next; - } - // Could not find the correct location. - if (tile != target) - return DT_FAILURE | DT_OUT_OF_MEMORY; - // Remove from freelist - if (!prev) - m_nextFree = tile->next; - else - prev->next = tile->next; - - // Restore salt. - tile->salt = decodePolyIdSalt((dtPolyRef)lastRef); - } - - // Make sure we could allocate a tile. - if (!tile) - return DT_FAILURE | DT_OUT_OF_MEMORY; - - // Insert tile into the position lut. - int h = computeTileHash(header->x, header->y, m_tileLutMask); - tile->next = m_posLookup[h]; - m_posLookup[h] = tile; - - // Patch header pointers. - const int headerSize = dtAlign4(sizeof(dtMeshHeader)); - const int vertsSize = dtAlign4(sizeof(float)*3*header->vertCount); - const int polysSize = dtAlign4(sizeof(dtPoly)*header->polyCount); - const int linksSize = dtAlign4(sizeof(dtLink)*(header->maxLinkCount)); - const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*header->detailMeshCount); - const int detailVertsSize = dtAlign4(sizeof(float)*3*header->detailVertCount); - const int detailTrisSize = dtAlign4(sizeof(unsigned char)*4*header->detailTriCount); - const int bvtreeSize = dtAlign4(sizeof(dtBVNode)*header->bvNodeCount); - const int offMeshLinksSize = dtAlign4(sizeof(dtOffMeshConnection)*header->offMeshConCount); - - unsigned char* d = data + headerSize; - tile->verts = dtGetThenAdvanceBufferPointer(d, vertsSize); - tile->polys = dtGetThenAdvanceBufferPointer(d, polysSize); - tile->links = dtGetThenAdvanceBufferPointer(d, linksSize); - tile->detailMeshes = dtGetThenAdvanceBufferPointer(d, detailMeshesSize); - tile->detailVerts = dtGetThenAdvanceBufferPointer(d, detailVertsSize); - tile->detailTris = dtGetThenAdvanceBufferPointer(d, detailTrisSize); - tile->bvTree = dtGetThenAdvanceBufferPointer(d, bvtreeSize); - tile->offMeshCons = dtGetThenAdvanceBufferPointer(d, offMeshLinksSize); - - // If there are no items in the bvtree, reset the tree pointer. - if (!bvtreeSize) - tile->bvTree = 0; - - // Build links freelist - tile->linksFreeList = 0; - tile->links[header->maxLinkCount-1].next = DT_NULL_LINK; - for (int i = 0; i < header->maxLinkCount-1; ++i) - tile->links[i].next = i+1; - - // Init tile. - tile->header = header; - tile->data = data; - tile->dataSize = dataSize; - tile->flags = flags; - - connectIntLinks(tile); - - // Base off-mesh connections to their starting polygons and connect connections inside the tile. - baseOffMeshLinks(tile); - connectExtOffMeshLinks(tile, tile, -1); - - // Create connections with neighbour tiles. - static const int MAX_NEIS = 32; - dtMeshTile* neis[MAX_NEIS]; - int nneis; - - // Connect with layers in current tile. - nneis = getTilesAt(header->x, header->y, neis, MAX_NEIS); - for (int j = 0; j < nneis; ++j) - { - if (neis[j] == tile) - continue; - - connectExtLinks(tile, neis[j], -1); - connectExtLinks(neis[j], tile, -1); - connectExtOffMeshLinks(tile, neis[j], -1); - connectExtOffMeshLinks(neis[j], tile, -1); - } - - // Connect with neighbour tiles. - for (int i = 0; i < 8; ++i) - { - nneis = getNeighbourTilesAt(header->x, header->y, i, neis, MAX_NEIS); - for (int j = 0; j < nneis; ++j) - { - connectExtLinks(tile, neis[j], i); - connectExtLinks(neis[j], tile, dtOppositeTile(i)); - connectExtOffMeshLinks(tile, neis[j], i); - connectExtOffMeshLinks(neis[j], tile, dtOppositeTile(i)); - } - } - - if (result) - *result = getTileRef(tile); - - return DT_SUCCESS; -} - -const dtMeshTile* dtNavMesh::getTileAt(const int x, const int y, const int layer) const -{ - // Find tile based on hash. - int h = computeTileHash(x,y,m_tileLutMask); - dtMeshTile* tile = m_posLookup[h]; - while (tile) - { - if (tile->header && - tile->header->x == x && - tile->header->y == y && - tile->header->layer == layer) - { - return tile; - } - tile = tile->next; - } - return 0; -} - -int dtNavMesh::getNeighbourTilesAt(const int x, const int y, const int side, dtMeshTile** tiles, const int maxTiles) const -{ - int nx = x, ny = y; - switch (side) - { - case 0: nx++; break; - case 1: nx++; ny++; break; - case 2: ny++; break; - case 3: nx--; ny++; break; - case 4: nx--; break; - case 5: nx--; ny--; break; - case 6: ny--; break; - case 7: nx++; ny--; break; - }; - - return getTilesAt(nx, ny, tiles, maxTiles); -} - -int dtNavMesh::getTilesAt(const int x, const int y, dtMeshTile** tiles, const int maxTiles) const -{ - int n = 0; - - // Find tile based on hash. - int h = computeTileHash(x,y,m_tileLutMask); - dtMeshTile* tile = m_posLookup[h]; - while (tile) - { - if (tile->header && - tile->header->x == x && - tile->header->y == y) - { - if (n < maxTiles) - tiles[n++] = tile; - } - tile = tile->next; - } - - return n; -} - -/// @par -/// -/// This function will not fail if the tiles array is too small to hold the -/// entire result set. It will simply fill the array to capacity. -int dtNavMesh::getTilesAt(const int x, const int y, dtMeshTile const** tiles, const int maxTiles) const -{ - int n = 0; - - // Find tile based on hash. - int h = computeTileHash(x,y,m_tileLutMask); - dtMeshTile* tile = m_posLookup[h]; - while (tile) - { - if (tile->header && - tile->header->x == x && - tile->header->y == y) - { - if (n < maxTiles) - tiles[n++] = tile; - } - tile = tile->next; - } - - return n; -} - - -dtTileRef dtNavMesh::getTileRefAt(const int x, const int y, const int layer) const -{ - // Find tile based on hash. - int h = computeTileHash(x,y,m_tileLutMask); - dtMeshTile* tile = m_posLookup[h]; - while (tile) - { - if (tile->header && - tile->header->x == x && - tile->header->y == y && - tile->header->layer == layer) - { - return getTileRef(tile); - } - tile = tile->next; - } - return 0; -} - -const dtMeshTile* dtNavMesh::getTileByRef(dtTileRef ref) const -{ - if (!ref) - return 0; - unsigned int tileIndex = decodePolyIdTile((dtPolyRef)ref); - unsigned int tileSalt = decodePolyIdSalt((dtPolyRef)ref); - if ((int)tileIndex >= m_maxTiles) - return 0; - const dtMeshTile* tile = &m_tiles[tileIndex]; - if (tile->salt != tileSalt) - return 0; - return tile; -} - -int dtNavMesh::getMaxTiles() const -{ - return m_maxTiles; -} - -dtMeshTile* dtNavMesh::getTile(int i) -{ - return &m_tiles[i]; -} - -const dtMeshTile* dtNavMesh::getTile(int i) const -{ - return &m_tiles[i]; -} - -void dtNavMesh::calcTileLoc(const float* pos, int* tx, int* ty) const -{ - *tx = (int)floorf((pos[0]-m_orig[0]) / m_tileWidth); - *ty = (int)floorf((pos[2]-m_orig[2]) / m_tileHeight); -} - -dtStatus dtNavMesh::getTileAndPolyByRef(const dtPolyRef ref, const dtMeshTile** tile, const dtPoly** poly) const -{ - if (!ref) return DT_FAILURE; - unsigned int salt, it, ip; - decodePolyId(ref, salt, it, ip); - if (it >= (unsigned int)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; - if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return DT_FAILURE | DT_INVALID_PARAM; - if (ip >= (unsigned int)m_tiles[it].header->polyCount) return DT_FAILURE | DT_INVALID_PARAM; - *tile = &m_tiles[it]; - *poly = &m_tiles[it].polys[ip]; - return DT_SUCCESS; -} - -/// @par -/// -/// @warning Only use this function if it is known that the provided polygon -/// reference is valid. This function is faster than #getTileAndPolyByRef, but -/// it does not validate the reference. -void dtNavMesh::getTileAndPolyByRefUnsafe(const dtPolyRef ref, const dtMeshTile** tile, const dtPoly** poly) const -{ - unsigned int salt, it, ip; - decodePolyId(ref, salt, it, ip); - *tile = &m_tiles[it]; - *poly = &m_tiles[it].polys[ip]; -} - -bool dtNavMesh::isValidPolyRef(dtPolyRef ref) const -{ - if (!ref) return false; - unsigned int salt, it, ip; - decodePolyId(ref, salt, it, ip); - if (it >= (unsigned int)m_maxTiles) return false; - if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return false; - if (ip >= (unsigned int)m_tiles[it].header->polyCount) return false; - return true; -} - -/// @par -/// -/// This function returns the data for the tile so that, if desired, -/// it can be added back to the navigation mesh at a later point. -/// -/// @see #addTile -dtStatus dtNavMesh::removeTile(dtTileRef ref, unsigned char** data, int* dataSize) -{ - if (!ref) - return DT_FAILURE | DT_INVALID_PARAM; - unsigned int tileIndex = decodePolyIdTile((dtPolyRef)ref); - unsigned int tileSalt = decodePolyIdSalt((dtPolyRef)ref); - if ((int)tileIndex >= m_maxTiles) - return DT_FAILURE | DT_INVALID_PARAM; - dtMeshTile* tile = &m_tiles[tileIndex]; - if (tile->salt != tileSalt) - return DT_FAILURE | DT_INVALID_PARAM; - - // Remove tile from hash lookup. - int h = computeTileHash(tile->header->x,tile->header->y,m_tileLutMask); - dtMeshTile* prev = 0; - dtMeshTile* cur = m_posLookup[h]; - while (cur) - { - if (cur == tile) - { - if (prev) - prev->next = cur->next; - else - m_posLookup[h] = cur->next; - break; - } - prev = cur; - cur = cur->next; - } - - // Remove connections to neighbour tiles. - static const int MAX_NEIS = 32; - dtMeshTile* neis[MAX_NEIS]; - int nneis; - - // Disconnect from other layers in current tile. - nneis = getTilesAt(tile->header->x, tile->header->y, neis, MAX_NEIS); - for (int j = 0; j < nneis; ++j) - { - if (neis[j] == tile) continue; - unconnectLinks(neis[j], tile); - } - - // Disconnect from neighbour tiles. - for (int i = 0; i < 8; ++i) - { - nneis = getNeighbourTilesAt(tile->header->x, tile->header->y, i, neis, MAX_NEIS); - for (int j = 0; j < nneis; ++j) - unconnectLinks(neis[j], tile); - } - - // Reset tile. - if (tile->flags & DT_TILE_FREE_DATA) - { - // Owns data - dtFree(tile->data); - tile->data = 0; - tile->dataSize = 0; - if (data) *data = 0; - if (dataSize) *dataSize = 0; - } - else - { - if (data) *data = tile->data; - if (dataSize) *dataSize = tile->dataSize; - } - - tile->header = 0; - tile->flags = 0; - tile->linksFreeList = 0; - tile->polys = 0; - tile->verts = 0; - tile->links = 0; - tile->detailMeshes = 0; - tile->detailVerts = 0; - tile->detailTris = 0; - tile->bvTree = 0; - tile->offMeshCons = 0; - - // Update salt, salt should never be zero. -#ifdef DT_POLYREF64 - tile->salt = (tile->salt+1) & ((1<salt = (tile->salt+1) & ((1<salt == 0) - tile->salt++; - - // Add to free list. - tile->next = m_nextFree; - m_nextFree = tile; - - return DT_SUCCESS; -} - -dtTileRef dtNavMesh::getTileRef(const dtMeshTile* tile) const -{ - if (!tile) return 0; - const unsigned int it = (unsigned int)(tile - m_tiles); - return (dtTileRef)encodePolyId(tile->salt, it, 0); -} - -/// @par -/// -/// Example use case: -/// @code -/// -/// const dtPolyRef base = navmesh->getPolyRefBase(tile); -/// for (int i = 0; i < tile->header->polyCount; ++i) -/// { -/// const dtPoly* p = &tile->polys[i]; -/// const dtPolyRef ref = base | (dtPolyRef)i; -/// -/// // Use the reference to access the polygon data. -/// } -/// @endcode -dtPolyRef dtNavMesh::getPolyRefBase(const dtMeshTile* tile) const -{ - if (!tile) return 0; - const unsigned int it = (unsigned int)(tile - m_tiles); - return encodePolyId(tile->salt, it, 0); -} - -struct dtTileState -{ - int magic; // Magic number, used to identify the data. - int version; // Data version number. - dtTileRef ref; // Tile ref at the time of storing the data. -}; - -struct dtPolyState -{ - unsigned short flags; // Flags (see dtPolyFlags). - unsigned char area; // Area ID of the polygon. -}; - -/// @see #storeTileState -int dtNavMesh::getTileStateSize(const dtMeshTile* tile) const -{ - if (!tile) return 0; - const int headerSize = dtAlign4(sizeof(dtTileState)); - const int polyStateSize = dtAlign4(sizeof(dtPolyState) * tile->header->polyCount); - return headerSize + polyStateSize; -} - -/// @par -/// -/// Tile state includes non-structural data such as polygon flags, area ids, etc. -/// @note The state data is only valid until the tile reference changes. -/// @see #getTileStateSize, #restoreTileState -dtStatus dtNavMesh::storeTileState(const dtMeshTile* tile, unsigned char* data, const int maxDataSize) const -{ - // Make sure there is enough space to store the state. - const int sizeReq = getTileStateSize(tile); - if (maxDataSize < sizeReq) - return DT_FAILURE | DT_BUFFER_TOO_SMALL; - - dtTileState* tileState = dtGetThenAdvanceBufferPointer(data, dtAlign4(sizeof(dtTileState))); - dtPolyState* polyStates = dtGetThenAdvanceBufferPointer(data, dtAlign4(sizeof(dtPolyState) * tile->header->polyCount)); - - // Store tile state. - tileState->magic = DT_NAVMESH_STATE_MAGIC; - tileState->version = DT_NAVMESH_STATE_VERSION; - tileState->ref = getTileRef(tile); - - // Store per poly state. - for (int i = 0; i < tile->header->polyCount; ++i) - { - const dtPoly* p = &tile->polys[i]; - dtPolyState* s = &polyStates[i]; - s->flags = p->flags; - s->area = p->getArea(); - } - - return DT_SUCCESS; -} - -/// @par -/// -/// Tile state includes non-structural data such as polygon flags, area ids, etc. -/// @note This function does not impact the tile's #dtTileRef and #dtPolyRef's. -/// @see #storeTileState -dtStatus dtNavMesh::restoreTileState(dtMeshTile* tile, const unsigned char* data, const int maxDataSize) -{ - // Make sure there is enough space to store the state. - const int sizeReq = getTileStateSize(tile); - if (maxDataSize < sizeReq) - return DT_FAILURE | DT_INVALID_PARAM; - - const dtTileState* tileState = dtGetThenAdvanceBufferPointer(data, dtAlign4(sizeof(dtTileState))); - const dtPolyState* polyStates = dtGetThenAdvanceBufferPointer(data, dtAlign4(sizeof(dtPolyState) * tile->header->polyCount)); - - // Check that the restore is possible. - if (tileState->magic != DT_NAVMESH_STATE_MAGIC) - return DT_FAILURE | DT_WRONG_MAGIC; - if (tileState->version != DT_NAVMESH_STATE_VERSION) - return DT_FAILURE | DT_WRONG_VERSION; - if (tileState->ref != getTileRef(tile)) - return DT_FAILURE | DT_INVALID_PARAM; - - // Restore per poly state. - for (int i = 0; i < tile->header->polyCount; ++i) - { - dtPoly* p = &tile->polys[i]; - const dtPolyState* s = &polyStates[i]; - p->flags = s->flags; - p->setArea(s->area); - } - - return DT_SUCCESS; -} - -/// @par -/// -/// Off-mesh connections are stored in the navigation mesh as special 2-vertex -/// polygons with a single edge. At least one of the vertices is expected to be -/// inside a normal polygon. So an off-mesh connection is "entered" from a -/// normal polygon at one of its endpoints. This is the polygon identified by -/// the prevRef parameter. -dtStatus dtNavMesh::getOffMeshConnectionPolyEndPoints(dtPolyRef prevRef, dtPolyRef polyRef, float* startPos, float* endPos) const -{ - unsigned int salt, it, ip; - - if (!polyRef) - return DT_FAILURE; - - // Get current polygon - decodePolyId(polyRef, salt, it, ip); - if (it >= (unsigned int)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; - if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return DT_FAILURE | DT_INVALID_PARAM; - const dtMeshTile* tile = &m_tiles[it]; - if (ip >= (unsigned int)tile->header->polyCount) return DT_FAILURE | DT_INVALID_PARAM; - const dtPoly* poly = &tile->polys[ip]; - - // Make sure that the current poly is indeed off-mesh link. - if (poly->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) - return DT_FAILURE; - - // Figure out which way to hand out the vertices. - int idx0 = 0, idx1 = 1; - - // Find link that points to first vertex. - for (unsigned int i = poly->firstLink; i != DT_NULL_LINK; i = tile->links[i].next) - { - if (tile->links[i].edge == 0) - { - if (tile->links[i].ref != prevRef) - { - idx0 = 1; - idx1 = 0; - } - break; - } - } - - dtVcopy(startPos, &tile->verts[poly->verts[idx0]*3]); - dtVcopy(endPos, &tile->verts[poly->verts[idx1]*3]); - - return DT_SUCCESS; -} - - -const dtOffMeshConnection* dtNavMesh::getOffMeshConnectionByRef(dtPolyRef ref) const -{ - unsigned int salt, it, ip; - - if (!ref) - return 0; - - // Get current polygon - decodePolyId(ref, salt, it, ip); - if (it >= (unsigned int)m_maxTiles) return 0; - if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return 0; - const dtMeshTile* tile = &m_tiles[it]; - if (ip >= (unsigned int)tile->header->polyCount) return 0; - const dtPoly* poly = &tile->polys[ip]; - - // Make sure that the current poly is indeed off-mesh link. - if (poly->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) - return 0; - - const unsigned int idx = ip - tile->header->offMeshBase; - dtAssert(idx < (unsigned int)tile->header->offMeshConCount); - return &tile->offMeshCons[idx]; -} - - -dtStatus dtNavMesh::setPolyFlags(dtPolyRef ref, unsigned short flags) -{ - if (!ref) return DT_FAILURE; - unsigned int salt, it, ip; - decodePolyId(ref, salt, it, ip); - if (it >= (unsigned int)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; - if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return DT_FAILURE | DT_INVALID_PARAM; - dtMeshTile* tile = &m_tiles[it]; - if (ip >= (unsigned int)tile->header->polyCount) return DT_FAILURE | DT_INVALID_PARAM; - dtPoly* poly = &tile->polys[ip]; - - // Change flags. - poly->flags = flags; - - return DT_SUCCESS; -} - -dtStatus dtNavMesh::getPolyFlags(dtPolyRef ref, unsigned short* resultFlags) const -{ - if (!ref) return DT_FAILURE; - unsigned int salt, it, ip; - decodePolyId(ref, salt, it, ip); - if (it >= (unsigned int)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; - if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return DT_FAILURE | DT_INVALID_PARAM; - const dtMeshTile* tile = &m_tiles[it]; - if (ip >= (unsigned int)tile->header->polyCount) return DT_FAILURE | DT_INVALID_PARAM; - const dtPoly* poly = &tile->polys[ip]; - - *resultFlags = poly->flags; - - return DT_SUCCESS; -} - -dtStatus dtNavMesh::setPolyArea(dtPolyRef ref, unsigned char area) -{ - if (!ref) return DT_FAILURE; - unsigned int salt, it, ip; - decodePolyId(ref, salt, it, ip); - if (it >= (unsigned int)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; - if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return DT_FAILURE | DT_INVALID_PARAM; - dtMeshTile* tile = &m_tiles[it]; - if (ip >= (unsigned int)tile->header->polyCount) return DT_FAILURE | DT_INVALID_PARAM; - dtPoly* poly = &tile->polys[ip]; - - poly->setArea(area); - - return DT_SUCCESS; -} - -dtStatus dtNavMesh::getPolyArea(dtPolyRef ref, unsigned char* resultArea) const -{ - if (!ref) return DT_FAILURE; - unsigned int salt, it, ip; - decodePolyId(ref, salt, it, ip); - if (it >= (unsigned int)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM; - if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return DT_FAILURE | DT_INVALID_PARAM; - const dtMeshTile* tile = &m_tiles[it]; - if (ip >= (unsigned int)tile->header->polyCount) return DT_FAILURE | DT_INVALID_PARAM; - const dtPoly* poly = &tile->polys[ip]; - - *resultArea = poly->getArea(); - - return DT_SUCCESS; -} - diff --git a/extern/recastnavigation/Detour/Source/DetourNavMeshBuilder.cpp b/extern/recastnavigation/Detour/Source/DetourNavMeshBuilder.cpp deleted file mode 100644 index e93a97629b..0000000000 --- a/extern/recastnavigation/Detour/Source/DetourNavMeshBuilder.cpp +++ /dev/null @@ -1,802 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#include -#include -#include -#include "DetourNavMesh.h" -#include "DetourCommon.h" -#include "DetourMath.h" -#include "DetourNavMeshBuilder.h" -#include "DetourAlloc.h" -#include "DetourAssert.h" - -static unsigned short MESH_NULL_IDX = 0xffff; - - -struct BVItem -{ - unsigned short bmin[3]; - unsigned short bmax[3]; - int i; -}; - -static int compareItemX(const void* va, const void* vb) -{ - const BVItem* a = (const BVItem*)va; - const BVItem* b = (const BVItem*)vb; - if (a->bmin[0] < b->bmin[0]) - return -1; - if (a->bmin[0] > b->bmin[0]) - return 1; - return 0; -} - -static int compareItemY(const void* va, const void* vb) -{ - const BVItem* a = (const BVItem*)va; - const BVItem* b = (const BVItem*)vb; - if (a->bmin[1] < b->bmin[1]) - return -1; - if (a->bmin[1] > b->bmin[1]) - return 1; - return 0; -} - -static int compareItemZ(const void* va, const void* vb) -{ - const BVItem* a = (const BVItem*)va; - const BVItem* b = (const BVItem*)vb; - if (a->bmin[2] < b->bmin[2]) - return -1; - if (a->bmin[2] > b->bmin[2]) - return 1; - return 0; -} - -static void calcExtends(BVItem* items, const int /*nitems*/, const int imin, const int imax, - unsigned short* bmin, unsigned short* bmax) -{ - bmin[0] = items[imin].bmin[0]; - bmin[1] = items[imin].bmin[1]; - bmin[2] = items[imin].bmin[2]; - - bmax[0] = items[imin].bmax[0]; - bmax[1] = items[imin].bmax[1]; - bmax[2] = items[imin].bmax[2]; - - for (int i = imin+1; i < imax; ++i) - { - const BVItem& it = items[i]; - if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0]; - if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1]; - if (it.bmin[2] < bmin[2]) bmin[2] = it.bmin[2]; - - if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0]; - if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1]; - if (it.bmax[2] > bmax[2]) bmax[2] = it.bmax[2]; - } -} - -inline int longestAxis(unsigned short x, unsigned short y, unsigned short z) -{ - int axis = 0; - unsigned short maxVal = x; - if (y > maxVal) - { - axis = 1; - maxVal = y; - } - if (z > maxVal) - { - axis = 2; - } - return axis; -} - -static void subdivide(BVItem* items, int nitems, int imin, int imax, int& curNode, dtBVNode* nodes) -{ - int inum = imax - imin; - int icur = curNode; - - dtBVNode& node = nodes[curNode++]; - - if (inum == 1) - { - // Leaf - node.bmin[0] = items[imin].bmin[0]; - node.bmin[1] = items[imin].bmin[1]; - node.bmin[2] = items[imin].bmin[2]; - - node.bmax[0] = items[imin].bmax[0]; - node.bmax[1] = items[imin].bmax[1]; - node.bmax[2] = items[imin].bmax[2]; - - node.i = items[imin].i; - } - else - { - // Split - calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); - - int axis = longestAxis(node.bmax[0] - node.bmin[0], - node.bmax[1] - node.bmin[1], - node.bmax[2] - node.bmin[2]); - - if (axis == 0) - { - // Sort along x-axis - qsort(items+imin, inum, sizeof(BVItem), compareItemX); - } - else if (axis == 1) - { - // Sort along y-axis - qsort(items+imin, inum, sizeof(BVItem), compareItemY); - } - else - { - // Sort along z-axis - qsort(items+imin, inum, sizeof(BVItem), compareItemZ); - } - - int isplit = imin+inum/2; - - // Left - subdivide(items, nitems, imin, isplit, curNode, nodes); - // Right - subdivide(items, nitems, isplit, imax, curNode, nodes); - - int iescape = curNode - icur; - // Negative index means escape. - node.i = -iescape; - } -} - -static int createBVTree(dtNavMeshCreateParams* params, dtBVNode* nodes, int /*nnodes*/) -{ - // Build tree - float quantFactor = 1 / params->cs; - BVItem* items = (BVItem*)dtAlloc(sizeof(BVItem)*params->polyCount, DT_ALLOC_TEMP); - for (int i = 0; i < params->polyCount; i++) - { - BVItem& it = items[i]; - it.i = i; - // Calc polygon bounds. Use detail meshes if available. - if (params->detailMeshes) - { - int vb = (int)params->detailMeshes[i*4+0]; - int ndv = (int)params->detailMeshes[i*4+1]; - float bmin[3]; - float bmax[3]; - - const float* dv = ¶ms->detailVerts[vb*3]; - dtVcopy(bmin, dv); - dtVcopy(bmax, dv); - - for (int j = 1; j < ndv; j++) - { - dtVmin(bmin, &dv[j * 3]); - dtVmax(bmax, &dv[j * 3]); - } - - // BV-tree uses cs for all dimensions - it.bmin[0] = (unsigned short)dtClamp((int)((bmin[0] - params->bmin[0])*quantFactor), 0, 0xffff); - it.bmin[1] = (unsigned short)dtClamp((int)((bmin[1] - params->bmin[1])*quantFactor), 0, 0xffff); - it.bmin[2] = (unsigned short)dtClamp((int)((bmin[2] - params->bmin[2])*quantFactor), 0, 0xffff); - - it.bmax[0] = (unsigned short)dtClamp((int)((bmax[0] - params->bmin[0])*quantFactor), 0, 0xffff); - it.bmax[1] = (unsigned short)dtClamp((int)((bmax[1] - params->bmin[1])*quantFactor), 0, 0xffff); - it.bmax[2] = (unsigned short)dtClamp((int)((bmax[2] - params->bmin[2])*quantFactor), 0, 0xffff); - } - else - { - const unsigned short* p = ¶ms->polys[i*params->nvp * 2]; - it.bmin[0] = it.bmax[0] = params->verts[p[0] * 3 + 0]; - it.bmin[1] = it.bmax[1] = params->verts[p[0] * 3 + 1]; - it.bmin[2] = it.bmax[2] = params->verts[p[0] * 3 + 2]; - - for (int j = 1; j < params->nvp; ++j) - { - if (p[j] == MESH_NULL_IDX) break; - unsigned short x = params->verts[p[j] * 3 + 0]; - unsigned short y = params->verts[p[j] * 3 + 1]; - unsigned short z = params->verts[p[j] * 3 + 2]; - - if (x < it.bmin[0]) it.bmin[0] = x; - if (y < it.bmin[1]) it.bmin[1] = y; - if (z < it.bmin[2]) it.bmin[2] = z; - - if (x > it.bmax[0]) it.bmax[0] = x; - if (y > it.bmax[1]) it.bmax[1] = y; - if (z > it.bmax[2]) it.bmax[2] = z; - } - // Remap y - it.bmin[1] = (unsigned short)dtMathFloorf((float)it.bmin[1] * params->ch / params->cs); - it.bmax[1] = (unsigned short)dtMathCeilf((float)it.bmax[1] * params->ch / params->cs); - } - } - - int curNode = 0; - subdivide(items, params->polyCount, 0, params->polyCount, curNode, nodes); - - dtFree(items); - - return curNode; -} - -static unsigned char classifyOffMeshPoint(const float* pt, const float* bmin, const float* bmax) -{ - static const unsigned char XP = 1<<0; - static const unsigned char ZP = 1<<1; - static const unsigned char XM = 1<<2; - static const unsigned char ZM = 1<<3; - - unsigned char outcode = 0; - outcode |= (pt[0] >= bmax[0]) ? XP : 0; - outcode |= (pt[2] >= bmax[2]) ? ZP : 0; - outcode |= (pt[0] < bmin[0]) ? XM : 0; - outcode |= (pt[2] < bmin[2]) ? ZM : 0; - - switch (outcode) - { - case XP: return 0; - case XP|ZP: return 1; - case ZP: return 2; - case XM|ZP: return 3; - case XM: return 4; - case XM|ZM: return 5; - case ZM: return 6; - case XP|ZM: return 7; - }; - - return 0xff; -} - -// TODO: Better error handling. - -/// @par -/// -/// The output data array is allocated using the detour allocator (dtAlloc()). The method -/// used to free the memory will be determined by how the tile is added to the navigation -/// mesh. -/// -/// @see dtNavMesh, dtNavMesh::addTile() -bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, int* outDataSize) -{ - if (params->nvp > DT_VERTS_PER_POLYGON) - return false; - if (params->vertCount >= 0xffff) - return false; - if (!params->vertCount || !params->verts) - return false; - if (!params->polyCount || !params->polys) - return false; - - const int nvp = params->nvp; - - // Classify off-mesh connection points. We store only the connections - // whose start point is inside the tile. - unsigned char* offMeshConClass = 0; - int storedOffMeshConCount = 0; - int offMeshConLinkCount = 0; - - if (params->offMeshConCount > 0) - { - offMeshConClass = (unsigned char*)dtAlloc(sizeof(unsigned char)*params->offMeshConCount*2, DT_ALLOC_TEMP); - if (!offMeshConClass) - return false; - - // Find tight heigh bounds, used for culling out off-mesh start locations. - float hmin = FLT_MAX; - float hmax = -FLT_MAX; - - if (params->detailVerts && params->detailVertsCount) - { - for (int i = 0; i < params->detailVertsCount; ++i) - { - const float h = params->detailVerts[i*3+1]; - hmin = dtMin(hmin,h); - hmax = dtMax(hmax,h); - } - } - else - { - for (int i = 0; i < params->vertCount; ++i) - { - const unsigned short* iv = ¶ms->verts[i*3]; - const float h = params->bmin[1] + iv[1] * params->ch; - hmin = dtMin(hmin,h); - hmax = dtMax(hmax,h); - } - } - hmin -= params->walkableClimb; - hmax += params->walkableClimb; - float bmin[3], bmax[3]; - dtVcopy(bmin, params->bmin); - dtVcopy(bmax, params->bmax); - bmin[1] = hmin; - bmax[1] = hmax; - - for (int i = 0; i < params->offMeshConCount; ++i) - { - const float* p0 = ¶ms->offMeshConVerts[(i*2+0)*3]; - const float* p1 = ¶ms->offMeshConVerts[(i*2+1)*3]; - offMeshConClass[i*2+0] = classifyOffMeshPoint(p0, bmin, bmax); - offMeshConClass[i*2+1] = classifyOffMeshPoint(p1, bmin, bmax); - - // Zero out off-mesh start positions which are not even potentially touching the mesh. - if (offMeshConClass[i*2+0] == 0xff) - { - if (p0[1] < bmin[1] || p0[1] > bmax[1]) - offMeshConClass[i*2+0] = 0; - } - - // Cound how many links should be allocated for off-mesh connections. - if (offMeshConClass[i*2+0] == 0xff) - offMeshConLinkCount++; - if (offMeshConClass[i*2+1] == 0xff) - offMeshConLinkCount++; - - if (offMeshConClass[i*2+0] == 0xff) - storedOffMeshConCount++; - } - } - - // Off-mesh connectionss are stored as polygons, adjust values. - const int totPolyCount = params->polyCount + storedOffMeshConCount; - const int totVertCount = params->vertCount + storedOffMeshConCount*2; - - // Find portal edges which are at tile borders. - int edgeCount = 0; - int portalCount = 0; - for (int i = 0; i < params->polyCount; ++i) - { - const unsigned short* p = ¶ms->polys[i*2*nvp]; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == MESH_NULL_IDX) break; - edgeCount++; - - if (p[nvp+j] & 0x8000) - { - unsigned short dir = p[nvp+j] & 0xf; - if (dir != 0xf) - portalCount++; - } - } - } - - const int maxLinkCount = edgeCount + portalCount*2 + offMeshConLinkCount*2; - - // Find unique detail vertices. - int uniqueDetailVertCount = 0; - int detailTriCount = 0; - if (params->detailMeshes) - { - // Has detail mesh, count unique detail vertex count and use input detail tri count. - detailTriCount = params->detailTriCount; - for (int i = 0; i < params->polyCount; ++i) - { - const unsigned short* p = ¶ms->polys[i*nvp*2]; - int ndv = params->detailMeshes[i*4+1]; - int nv = 0; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == MESH_NULL_IDX) break; - nv++; - } - ndv -= nv; - uniqueDetailVertCount += ndv; - } - } - else - { - // No input detail mesh, build detail mesh from nav polys. - uniqueDetailVertCount = 0; // No extra detail verts. - detailTriCount = 0; - for (int i = 0; i < params->polyCount; ++i) - { - const unsigned short* p = ¶ms->polys[i*nvp*2]; - int nv = 0; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == MESH_NULL_IDX) break; - nv++; - } - detailTriCount += nv-2; - } - } - - // Calculate data size - const int headerSize = dtAlign4(sizeof(dtMeshHeader)); - const int vertsSize = dtAlign4(sizeof(float)*3*totVertCount); - const int polysSize = dtAlign4(sizeof(dtPoly)*totPolyCount); - const int linksSize = dtAlign4(sizeof(dtLink)*maxLinkCount); - const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*params->polyCount); - const int detailVertsSize = dtAlign4(sizeof(float)*3*uniqueDetailVertCount); - const int detailTrisSize = dtAlign4(sizeof(unsigned char)*4*detailTriCount); - const int bvTreeSize = params->buildBvTree ? dtAlign4(sizeof(dtBVNode)*params->polyCount*2) : 0; - const int offMeshConsSize = dtAlign4(sizeof(dtOffMeshConnection)*storedOffMeshConCount); - - const int dataSize = headerSize + vertsSize + polysSize + linksSize + - detailMeshesSize + detailVertsSize + detailTrisSize + - bvTreeSize + offMeshConsSize; - - unsigned char* data = (unsigned char*)dtAlloc(sizeof(unsigned char)*dataSize, DT_ALLOC_PERM); - if (!data) - { - dtFree(offMeshConClass); - return false; - } - memset(data, 0, dataSize); - - unsigned char* d = data; - - dtMeshHeader* header = dtGetThenAdvanceBufferPointer(d, headerSize); - float* navVerts = dtGetThenAdvanceBufferPointer(d, vertsSize); - dtPoly* navPolys = dtGetThenAdvanceBufferPointer(d, polysSize); - d += linksSize; // Ignore links; just leave enough space for them. They'll be created on load. - dtPolyDetail* navDMeshes = dtGetThenAdvanceBufferPointer(d, detailMeshesSize); - float* navDVerts = dtGetThenAdvanceBufferPointer(d, detailVertsSize); - unsigned char* navDTris = dtGetThenAdvanceBufferPointer(d, detailTrisSize); - dtBVNode* navBvtree = dtGetThenAdvanceBufferPointer(d, bvTreeSize); - dtOffMeshConnection* offMeshCons = dtGetThenAdvanceBufferPointer(d, offMeshConsSize); - - - // Store header - header->magic = DT_NAVMESH_MAGIC; - header->version = DT_NAVMESH_VERSION; - header->x = params->tileX; - header->y = params->tileY; - header->layer = params->tileLayer; - header->userId = params->userId; - header->polyCount = totPolyCount; - header->vertCount = totVertCount; - header->maxLinkCount = maxLinkCount; - dtVcopy(header->bmin, params->bmin); - dtVcopy(header->bmax, params->bmax); - header->detailMeshCount = params->polyCount; - header->detailVertCount = uniqueDetailVertCount; - header->detailTriCount = detailTriCount; - header->bvQuantFactor = 1.0f / params->cs; - header->offMeshBase = params->polyCount; - header->walkableHeight = params->walkableHeight; - header->walkableRadius = params->walkableRadius; - header->walkableClimb = params->walkableClimb; - header->offMeshConCount = storedOffMeshConCount; - header->bvNodeCount = params->buildBvTree ? params->polyCount*2 : 0; - - const int offMeshVertsBase = params->vertCount; - const int offMeshPolyBase = params->polyCount; - - // Store vertices - // Mesh vertices - for (int i = 0; i < params->vertCount; ++i) - { - const unsigned short* iv = ¶ms->verts[i*3]; - float* v = &navVerts[i*3]; - v[0] = params->bmin[0] + iv[0] * params->cs; - v[1] = params->bmin[1] + iv[1] * params->ch; - v[2] = params->bmin[2] + iv[2] * params->cs; - } - // Off-mesh link vertices. - int n = 0; - for (int i = 0; i < params->offMeshConCount; ++i) - { - // Only store connections which start from this tile. - if (offMeshConClass[i*2+0] == 0xff) - { - const float* linkv = ¶ms->offMeshConVerts[i*2*3]; - float* v = &navVerts[(offMeshVertsBase + n*2)*3]; - dtVcopy(&v[0], &linkv[0]); - dtVcopy(&v[3], &linkv[3]); - n++; - } - } - - // Store polygons - // Mesh polys - const unsigned short* src = params->polys; - for (int i = 0; i < params->polyCount; ++i) - { - dtPoly* p = &navPolys[i]; - p->vertCount = 0; - p->flags = params->polyFlags[i]; - p->setArea(params->polyAreas[i]); - p->setType(DT_POLYTYPE_GROUND); - for (int j = 0; j < nvp; ++j) - { - if (src[j] == MESH_NULL_IDX) break; - p->verts[j] = src[j]; - if (src[nvp+j] & 0x8000) - { - // Border or portal edge. - unsigned short dir = src[nvp+j] & 0xf; - if (dir == 0xf) // Border - p->neis[j] = 0; - else if (dir == 0) // Portal x- - p->neis[j] = DT_EXT_LINK | 4; - else if (dir == 1) // Portal z+ - p->neis[j] = DT_EXT_LINK | 2; - else if (dir == 2) // Portal x+ - p->neis[j] = DT_EXT_LINK | 0; - else if (dir == 3) // Portal z- - p->neis[j] = DT_EXT_LINK | 6; - } - else - { - // Normal connection - p->neis[j] = src[nvp+j]+1; - } - - p->vertCount++; - } - src += nvp*2; - } - // Off-mesh connection vertices. - n = 0; - for (int i = 0; i < params->offMeshConCount; ++i) - { - // Only store connections which start from this tile. - if (offMeshConClass[i*2+0] == 0xff) - { - dtPoly* p = &navPolys[offMeshPolyBase+n]; - p->vertCount = 2; - p->verts[0] = (unsigned short)(offMeshVertsBase + n*2+0); - p->verts[1] = (unsigned short)(offMeshVertsBase + n*2+1); - p->flags = params->offMeshConFlags[i]; - p->setArea(params->offMeshConAreas[i]); - p->setType(DT_POLYTYPE_OFFMESH_CONNECTION); - n++; - } - } - - // Store detail meshes and vertices. - // The nav polygon vertices are stored as the first vertices on each mesh. - // We compress the mesh data by skipping them and using the navmesh coordinates. - if (params->detailMeshes) - { - unsigned short vbase = 0; - for (int i = 0; i < params->polyCount; ++i) - { - dtPolyDetail& dtl = navDMeshes[i]; - const int vb = (int)params->detailMeshes[i*4+0]; - const int ndv = (int)params->detailMeshes[i*4+1]; - const int nv = navPolys[i].vertCount; - dtl.vertBase = (unsigned int)vbase; - dtl.vertCount = (unsigned char)(ndv-nv); - dtl.triBase = (unsigned int)params->detailMeshes[i*4+2]; - dtl.triCount = (unsigned char)params->detailMeshes[i*4+3]; - // Copy vertices except the first 'nv' verts which are equal to nav poly verts. - if (ndv-nv) - { - memcpy(&navDVerts[vbase*3], ¶ms->detailVerts[(vb+nv)*3], sizeof(float)*3*(ndv-nv)); - vbase += (unsigned short)(ndv-nv); - } - } - // Store triangles. - memcpy(navDTris, params->detailTris, sizeof(unsigned char)*4*params->detailTriCount); - } - else - { - // Create dummy detail mesh by triangulating polys. - int tbase = 0; - for (int i = 0; i < params->polyCount; ++i) - { - dtPolyDetail& dtl = navDMeshes[i]; - const int nv = navPolys[i].vertCount; - dtl.vertBase = 0; - dtl.vertCount = 0; - dtl.triBase = (unsigned int)tbase; - dtl.triCount = (unsigned char)(nv-2); - // Triangulate polygon (local indices). - for (int j = 2; j < nv; ++j) - { - unsigned char* t = &navDTris[tbase*4]; - t[0] = 0; - t[1] = (unsigned char)(j-1); - t[2] = (unsigned char)j; - // Bit for each edge that belongs to poly boundary. - t[3] = (1<<2); - if (j == 2) t[3] |= (1<<0); - if (j == nv-1) t[3] |= (1<<4); - tbase++; - } - } - } - - // Store and create BVtree. - if (params->buildBvTree) - { - createBVTree(params, navBvtree, 2*params->polyCount); - } - - // Store Off-Mesh connections. - n = 0; - for (int i = 0; i < params->offMeshConCount; ++i) - { - // Only store connections which start from this tile. - if (offMeshConClass[i*2+0] == 0xff) - { - dtOffMeshConnection* con = &offMeshCons[n]; - con->poly = (unsigned short)(offMeshPolyBase + n); - // Copy connection end-points. - const float* endPts = ¶ms->offMeshConVerts[i*2*3]; - dtVcopy(&con->pos[0], &endPts[0]); - dtVcopy(&con->pos[3], &endPts[3]); - con->rad = params->offMeshConRad[i]; - con->flags = params->offMeshConDir[i] ? DT_OFFMESH_CON_BIDIR : 0; - con->side = offMeshConClass[i*2+1]; - if (params->offMeshConUserID) - con->userId = params->offMeshConUserID[i]; - n++; - } - } - - dtFree(offMeshConClass); - - *outData = data; - *outDataSize = dataSize; - - return true; -} - -bool dtNavMeshHeaderSwapEndian(unsigned char* data, const int /*dataSize*/) -{ - dtMeshHeader* header = (dtMeshHeader*)data; - - int swappedMagic = DT_NAVMESH_MAGIC; - int swappedVersion = DT_NAVMESH_VERSION; - dtSwapEndian(&swappedMagic); - dtSwapEndian(&swappedVersion); - - if ((header->magic != DT_NAVMESH_MAGIC || header->version != DT_NAVMESH_VERSION) && - (header->magic != swappedMagic || header->version != swappedVersion)) - { - return false; - } - - dtSwapEndian(&header->magic); - dtSwapEndian(&header->version); - dtSwapEndian(&header->x); - dtSwapEndian(&header->y); - dtSwapEndian(&header->layer); - dtSwapEndian(&header->userId); - dtSwapEndian(&header->polyCount); - dtSwapEndian(&header->vertCount); - dtSwapEndian(&header->maxLinkCount); - dtSwapEndian(&header->detailMeshCount); - dtSwapEndian(&header->detailVertCount); - dtSwapEndian(&header->detailTriCount); - dtSwapEndian(&header->bvNodeCount); - dtSwapEndian(&header->offMeshConCount); - dtSwapEndian(&header->offMeshBase); - dtSwapEndian(&header->walkableHeight); - dtSwapEndian(&header->walkableRadius); - dtSwapEndian(&header->walkableClimb); - dtSwapEndian(&header->bmin[0]); - dtSwapEndian(&header->bmin[1]); - dtSwapEndian(&header->bmin[2]); - dtSwapEndian(&header->bmax[0]); - dtSwapEndian(&header->bmax[1]); - dtSwapEndian(&header->bmax[2]); - dtSwapEndian(&header->bvQuantFactor); - - // Freelist index and pointers are updated when tile is added, no need to swap. - - return true; -} - -/// @par -/// -/// @warning This function assumes that the header is in the correct endianess already. -/// Call #dtNavMeshHeaderSwapEndian() first on the data if the data is expected to be in wrong endianess -/// to start with. Call #dtNavMeshHeaderSwapEndian() after the data has been swapped if converting from -/// native to foreign endianess. -bool dtNavMeshDataSwapEndian(unsigned char* data, const int /*dataSize*/) -{ - // Make sure the data is in right format. - dtMeshHeader* header = (dtMeshHeader*)data; - if (header->magic != DT_NAVMESH_MAGIC) - return false; - if (header->version != DT_NAVMESH_VERSION) - return false; - - // Patch header pointers. - const int headerSize = dtAlign4(sizeof(dtMeshHeader)); - const int vertsSize = dtAlign4(sizeof(float)*3*header->vertCount); - const int polysSize = dtAlign4(sizeof(dtPoly)*header->polyCount); - const int linksSize = dtAlign4(sizeof(dtLink)*(header->maxLinkCount)); - const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*header->detailMeshCount); - const int detailVertsSize = dtAlign4(sizeof(float)*3*header->detailVertCount); - const int detailTrisSize = dtAlign4(sizeof(unsigned char)*4*header->detailTriCount); - const int bvtreeSize = dtAlign4(sizeof(dtBVNode)*header->bvNodeCount); - const int offMeshLinksSize = dtAlign4(sizeof(dtOffMeshConnection)*header->offMeshConCount); - - unsigned char* d = data + headerSize; - float* verts = dtGetThenAdvanceBufferPointer(d, vertsSize); - dtPoly* polys = dtGetThenAdvanceBufferPointer(d, polysSize); - d += linksSize; // Ignore links; they technically should be endian-swapped but all their data is overwritten on load anyway. - //dtLink* links = dtGetThenAdvanceBufferPointer(d, linksSize); - dtPolyDetail* detailMeshes = dtGetThenAdvanceBufferPointer(d, detailMeshesSize); - float* detailVerts = dtGetThenAdvanceBufferPointer(d, detailVertsSize); - d += detailTrisSize; // Ignore detail tris; single bytes can't be endian-swapped. - //unsigned char* detailTris = dtGetThenAdvanceBufferPointer(d, detailTrisSize); - dtBVNode* bvTree = dtGetThenAdvanceBufferPointer(d, bvtreeSize); - dtOffMeshConnection* offMeshCons = dtGetThenAdvanceBufferPointer(d, offMeshLinksSize); - - // Vertices - for (int i = 0; i < header->vertCount*3; ++i) - { - dtSwapEndian(&verts[i]); - } - - // Polys - for (int i = 0; i < header->polyCount; ++i) - { - dtPoly* p = &polys[i]; - // poly->firstLink is update when tile is added, no need to swap. - for (int j = 0; j < DT_VERTS_PER_POLYGON; ++j) - { - dtSwapEndian(&p->verts[j]); - dtSwapEndian(&p->neis[j]); - } - dtSwapEndian(&p->flags); - } - - // Links are rebuild when tile is added, no need to swap. - - // Detail meshes - for (int i = 0; i < header->detailMeshCount; ++i) - { - dtPolyDetail* pd = &detailMeshes[i]; - dtSwapEndian(&pd->vertBase); - dtSwapEndian(&pd->triBase); - } - - // Detail verts - for (int i = 0; i < header->detailVertCount*3; ++i) - { - dtSwapEndian(&detailVerts[i]); - } - - // BV-tree - for (int i = 0; i < header->bvNodeCount; ++i) - { - dtBVNode* node = &bvTree[i]; - for (int j = 0; j < 3; ++j) - { - dtSwapEndian(&node->bmin[j]); - dtSwapEndian(&node->bmax[j]); - } - dtSwapEndian(&node->i); - } - - // Off-mesh Connections. - for (int i = 0; i < header->offMeshConCount; ++i) - { - dtOffMeshConnection* con = &offMeshCons[i]; - for (int j = 0; j < 6; ++j) - dtSwapEndian(&con->pos[j]); - dtSwapEndian(&con->rad); - dtSwapEndian(&con->poly); - } - - return true; -} diff --git a/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp b/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp deleted file mode 100644 index 265b10b34a..0000000000 --- a/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp +++ /dev/null @@ -1,3663 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#include -#include "DetourNavMeshQuery.h" -#include "DetourNavMesh.h" -#include "DetourNode.h" -#include "DetourCommon.h" -#include "DetourMath.h" -#include "DetourAlloc.h" -#include "DetourAssert.h" -#include - -/// @class dtQueryFilter -/// -/// The Default Implementation -/// -/// At construction: All area costs default to 1.0. All flags are included -/// and none are excluded. -/// -/// If a polygon has both an include and an exclude flag, it will be excluded. -/// -/// The way filtering works, a navigation mesh polygon must have at least one flag -/// set to ever be considered by a query. So a polygon with no flags will never -/// be considered. -/// -/// Setting the include flags to 0 will result in all polygons being excluded. -/// -/// Custom Implementations -/// -/// DT_VIRTUAL_QUERYFILTER must be defined in order to extend this class. -/// -/// Implement a custom query filter by overriding the virtual passFilter() -/// and getCost() functions. If this is done, both functions should be as -/// fast as possible. Use cached local copies of data rather than accessing -/// your own objects where possible. -/// -/// Custom implementations do not need to adhere to the flags or cost logic -/// used by the default implementation. -/// -/// In order for A* searches to work properly, the cost should be proportional to -/// the travel distance. Implementing a cost modifier less than 1.0 is likely -/// to lead to problems during pathfinding. -/// -/// @see dtNavMeshQuery - -dtQueryFilter::dtQueryFilter() : - m_includeFlags(0xffff), - m_excludeFlags(0) -{ - for (int i = 0; i < DT_MAX_AREAS; ++i) - m_areaCost[i] = 1.0f; -} - -#ifdef DT_VIRTUAL_QUERYFILTER -bool dtQueryFilter::passFilter(const dtPolyRef /*ref*/, - const dtMeshTile* /*tile*/, - const dtPoly* poly) const -{ - return (poly->flags & m_includeFlags) != 0 && (poly->flags & m_excludeFlags) == 0; -} - -float dtQueryFilter::getCost(const float* pa, const float* pb, - const dtPolyRef /*prevRef*/, const dtMeshTile* /*prevTile*/, const dtPoly* /*prevPoly*/, - const dtPolyRef /*curRef*/, const dtMeshTile* /*curTile*/, const dtPoly* curPoly, - const dtPolyRef /*nextRef*/, const dtMeshTile* /*nextTile*/, const dtPoly* /*nextPoly*/) const -{ - return dtVdist(pa, pb) * m_areaCost[curPoly->getArea()]; -} -#else -inline bool dtQueryFilter::passFilter(const dtPolyRef /*ref*/, - const dtMeshTile* /*tile*/, - const dtPoly* poly) const -{ - return (poly->flags & m_includeFlags) != 0 && (poly->flags & m_excludeFlags) == 0; -} - -inline float dtQueryFilter::getCost(const float* pa, const float* pb, - const dtPolyRef /*prevRef*/, const dtMeshTile* /*prevTile*/, const dtPoly* /*prevPoly*/, - const dtPolyRef /*curRef*/, const dtMeshTile* /*curTile*/, const dtPoly* curPoly, - const dtPolyRef /*nextRef*/, const dtMeshTile* /*nextTile*/, const dtPoly* /*nextPoly*/) const -{ - return dtVdist(pa, pb) * m_areaCost[curPoly->getArea()]; -} -#endif - -static const float H_SCALE = 0.999f; // Search heuristic scale. - - -dtNavMeshQuery* dtAllocNavMeshQuery() -{ - void* mem = dtAlloc(sizeof(dtNavMeshQuery), DT_ALLOC_PERM); - if (!mem) return 0; - return new(mem) dtNavMeshQuery; -} - -void dtFreeNavMeshQuery(dtNavMeshQuery* navmesh) -{ - if (!navmesh) return; - navmesh->~dtNavMeshQuery(); - dtFree(navmesh); -} - -////////////////////////////////////////////////////////////////////////////////////////// - -/// @class dtNavMeshQuery -/// -/// For methods that support undersized buffers, if the buffer is too small -/// to hold the entire result set the return status of the method will include -/// the #DT_BUFFER_TOO_SMALL flag. -/// -/// Constant member functions can be used by multiple clients without side -/// effects. (E.g. No change to the closed list. No impact on an in-progress -/// sliced path query. Etc.) -/// -/// Walls and portals: A @e wall is a polygon segment that is -/// considered impassable. A @e portal is a passable segment between polygons. -/// A portal may be treated as a wall based on the dtQueryFilter used for a query. -/// -/// @see dtNavMesh, dtQueryFilter, #dtAllocNavMeshQuery(), #dtAllocNavMeshQuery() - -dtNavMeshQuery::dtNavMeshQuery() : - m_nav(0), - m_tinyNodePool(0), - m_nodePool(0), - m_openList(0) -{ - memset(&m_query, 0, sizeof(dtQueryData)); -} - -dtNavMeshQuery::~dtNavMeshQuery() -{ - if (m_tinyNodePool) - m_tinyNodePool->~dtNodePool(); - if (m_nodePool) - m_nodePool->~dtNodePool(); - if (m_openList) - m_openList->~dtNodeQueue(); - dtFree(m_tinyNodePool); - dtFree(m_nodePool); - dtFree(m_openList); -} - -/// @par -/// -/// Must be the first function called after construction, before other -/// functions are used. -/// -/// This function can be used multiple times. -dtStatus dtNavMeshQuery::init(const dtNavMesh* nav, const int maxNodes) -{ - if (maxNodes > DT_NULL_IDX || maxNodes > (1 << DT_NODE_PARENT_BITS) - 1) - return DT_FAILURE | DT_INVALID_PARAM; - - m_nav = nav; - - if (!m_nodePool || m_nodePool->getMaxNodes() < maxNodes) - { - if (m_nodePool) - { - m_nodePool->~dtNodePool(); - dtFree(m_nodePool); - m_nodePool = 0; - } - m_nodePool = new (dtAlloc(sizeof(dtNodePool), DT_ALLOC_PERM)) dtNodePool(maxNodes, dtNextPow2(maxNodes/4)); - if (!m_nodePool) - return DT_FAILURE | DT_OUT_OF_MEMORY; - } - else - { - m_nodePool->clear(); - } - - if (!m_tinyNodePool) - { - m_tinyNodePool = new (dtAlloc(sizeof(dtNodePool), DT_ALLOC_PERM)) dtNodePool(64, 32); - if (!m_tinyNodePool) - return DT_FAILURE | DT_OUT_OF_MEMORY; - } - else - { - m_tinyNodePool->clear(); - } - - if (!m_openList || m_openList->getCapacity() < maxNodes) - { - if (m_openList) - { - m_openList->~dtNodeQueue(); - dtFree(m_openList); - m_openList = 0; - } - m_openList = new (dtAlloc(sizeof(dtNodeQueue), DT_ALLOC_PERM)) dtNodeQueue(maxNodes); - if (!m_openList) - return DT_FAILURE | DT_OUT_OF_MEMORY; - } - else - { - m_openList->clear(); - } - - return DT_SUCCESS; -} - -dtStatus dtNavMeshQuery::findRandomPoint(const dtQueryFilter* filter, float (*frand)(), - dtPolyRef* randomRef, float* randomPt) const -{ - dtAssert(m_nav); - - if (!filter || !frand || !randomRef || !randomPt) - return DT_FAILURE | DT_INVALID_PARAM; - - // Randomly pick one tile. Assume that all tiles cover roughly the same area. - const dtMeshTile* tile = 0; - float tsum = 0.0f; - for (int i = 0; i < m_nav->getMaxTiles(); i++) - { - const dtMeshTile* t = m_nav->getTile(i); - if (!t || !t->header) continue; - - // Choose random tile using reservoi sampling. - const float area = 1.0f; // Could be tile area too. - tsum += area; - const float u = frand(); - if (u*tsum <= area) - tile = t; - } - if (!tile) - return DT_FAILURE; - - // Randomly pick one polygon weighted by polygon area. - const dtPoly* poly = 0; - dtPolyRef polyRef = 0; - const dtPolyRef base = m_nav->getPolyRefBase(tile); - - float areaSum = 0.0f; - for (int i = 0; i < tile->header->polyCount; ++i) - { - const dtPoly* p = &tile->polys[i]; - // Do not return off-mesh connection polygons. - if (p->getType() != DT_POLYTYPE_GROUND) - continue; - // Must pass filter - const dtPolyRef ref = base | (dtPolyRef)i; - if (!filter->passFilter(ref, tile, p)) - continue; - - // Calc area of the polygon. - float polyArea = 0.0f; - for (int j = 2; j < p->vertCount; ++j) - { - const float* va = &tile->verts[p->verts[0]*3]; - const float* vb = &tile->verts[p->verts[j-1]*3]; - const float* vc = &tile->verts[p->verts[j]*3]; - polyArea += dtTriArea2D(va,vb,vc); - } - - // Choose random polygon weighted by area, using reservoi sampling. - areaSum += polyArea; - const float u = frand(); - if (u*areaSum <= polyArea) - { - poly = p; - polyRef = ref; - } - } - - if (!poly) - return DT_FAILURE; - - // Randomly pick point on polygon. - const float* v = &tile->verts[poly->verts[0]*3]; - float verts[3*DT_VERTS_PER_POLYGON]; - float areas[DT_VERTS_PER_POLYGON]; - dtVcopy(&verts[0*3],v); - for (int j = 1; j < poly->vertCount; ++j) - { - v = &tile->verts[poly->verts[j]*3]; - dtVcopy(&verts[j*3],v); - } - - const float s = frand(); - const float t = frand(); - - float pt[3]; - dtRandomPointInConvexPoly(verts, poly->vertCount, areas, s, t, pt); - - float h = 0.0f; - dtStatus status = getPolyHeight(polyRef, pt, &h); - if (dtStatusFailed(status)) - return status; - pt[1] = h; - - dtVcopy(randomPt, pt); - *randomRef = polyRef; - - return DT_SUCCESS; -} - -dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const float* centerPos, const float maxRadius, - const dtQueryFilter* filter, float (*frand)(), - dtPolyRef* randomRef, float* randomPt) const -{ - dtAssert(m_nav); - dtAssert(m_nodePool); - dtAssert(m_openList); - - // Validate input - if (!m_nav->isValidPolyRef(startRef) || - !centerPos || !dtVisfinite(centerPos) || - maxRadius < 0 || !dtMathIsfinite(maxRadius) || - !filter || !frand || !randomRef || !randomPt) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - const dtMeshTile* startTile = 0; - const dtPoly* startPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(startRef, &startTile, &startPoly); - if (!filter->passFilter(startRef, startTile, startPoly)) - return DT_FAILURE | DT_INVALID_PARAM; - - m_nodePool->clear(); - m_openList->clear(); - - dtNode* startNode = m_nodePool->getNode(startRef); - dtVcopy(startNode->pos, centerPos); - startNode->pidx = 0; - startNode->cost = 0; - startNode->total = 0; - startNode->id = startRef; - startNode->flags = DT_NODE_OPEN; - m_openList->push(startNode); - - dtStatus status = DT_SUCCESS; - - const float radiusSqr = dtSqr(maxRadius); - float areaSum = 0.0f; - - const dtMeshTile* randomTile = 0; - const dtPoly* randomPoly = 0; - dtPolyRef randomPolyRef = 0; - - while (!m_openList->empty()) - { - dtNode* bestNode = m_openList->pop(); - bestNode->flags &= ~DT_NODE_OPEN; - bestNode->flags |= DT_NODE_CLOSED; - - // Get poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtPolyRef bestRef = bestNode->id; - const dtMeshTile* bestTile = 0; - const dtPoly* bestPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(bestRef, &bestTile, &bestPoly); - - // Place random locations on on ground. - if (bestPoly->getType() == DT_POLYTYPE_GROUND) - { - // Calc area of the polygon. - float polyArea = 0.0f; - for (int j = 2; j < bestPoly->vertCount; ++j) - { - const float* va = &bestTile->verts[bestPoly->verts[0]*3]; - const float* vb = &bestTile->verts[bestPoly->verts[j-1]*3]; - const float* vc = &bestTile->verts[bestPoly->verts[j]*3]; - polyArea += dtTriArea2D(va,vb,vc); - } - // Choose random polygon weighted by area, using reservoi sampling. - areaSum += polyArea; - const float u = frand(); - if (u*areaSum <= polyArea) - { - randomTile = bestTile; - randomPoly = bestPoly; - randomPolyRef = bestRef; - } - } - - - // Get parent poly and tile. - dtPolyRef parentRef = 0; - const dtMeshTile* parentTile = 0; - const dtPoly* parentPoly = 0; - if (bestNode->pidx) - parentRef = m_nodePool->getNodeAtIdx(bestNode->pidx)->id; - if (parentRef) - m_nav->getTileAndPolyByRefUnsafe(parentRef, &parentTile, &parentPoly); - - for (unsigned int i = bestPoly->firstLink; i != DT_NULL_LINK; i = bestTile->links[i].next) - { - const dtLink* link = &bestTile->links[i]; - dtPolyRef neighbourRef = link->ref; - // Skip invalid neighbours and do not follow back to parent. - if (!neighbourRef || neighbourRef == parentRef) - continue; - - // Expand to neighbour - const dtMeshTile* neighbourTile = 0; - const dtPoly* neighbourPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(neighbourRef, &neighbourTile, &neighbourPoly); - - // Do not advance if the polygon is excluded by the filter. - if (!filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) - continue; - - // Find edge and calc distance to the edge. - float va[3], vb[3]; - if (!getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb)) - continue; - - // If the circle is not touching the next polygon, skip it. - float tseg; - float distSqr = dtDistancePtSegSqr2D(centerPos, va, vb, tseg); - if (distSqr > radiusSqr) - continue; - - dtNode* neighbourNode = m_nodePool->getNode(neighbourRef); - if (!neighbourNode) - { - status |= DT_OUT_OF_NODES; - continue; - } - - if (neighbourNode->flags & DT_NODE_CLOSED) - continue; - - // Cost - if (neighbourNode->flags == 0) - dtVlerp(neighbourNode->pos, va, vb, 0.5f); - - const float total = bestNode->total + dtVdist(bestNode->pos, neighbourNode->pos); - - // The node is already in open list and the new result is worse, skip. - if ((neighbourNode->flags & DT_NODE_OPEN) && total >= neighbourNode->total) - continue; - - neighbourNode->id = neighbourRef; - neighbourNode->flags = (neighbourNode->flags & ~DT_NODE_CLOSED); - neighbourNode->pidx = m_nodePool->getNodeIdx(bestNode); - neighbourNode->total = total; - - if (neighbourNode->flags & DT_NODE_OPEN) - { - m_openList->modify(neighbourNode); - } - else - { - neighbourNode->flags = DT_NODE_OPEN; - m_openList->push(neighbourNode); - } - } - } - - if (!randomPoly) - return DT_FAILURE; - - // Randomly pick point on polygon. - const float* v = &randomTile->verts[randomPoly->verts[0]*3]; - float verts[3*DT_VERTS_PER_POLYGON]; - float areas[DT_VERTS_PER_POLYGON]; - dtVcopy(&verts[0*3],v); - for (int j = 1; j < randomPoly->vertCount; ++j) - { - v = &randomTile->verts[randomPoly->verts[j]*3]; - dtVcopy(&verts[j*3],v); - } - - const float s = frand(); - const float t = frand(); - - float pt[3]; - dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); - - float h = 0.0f; - dtStatus stat = getPolyHeight(randomPolyRef, pt, &h); - if (dtStatusFailed(status)) - return stat; - pt[1] = h; - - dtVcopy(randomPt, pt); - *randomRef = randomPolyRef; - - return DT_SUCCESS; -} - - -////////////////////////////////////////////////////////////////////////////////////////// - -/// @par -/// -/// Uses the detail polygons to find the surface height. (Most accurate.) -/// -/// @p pos does not have to be within the bounds of the polygon or navigation mesh. -/// -/// See closestPointOnPolyBoundary() for a limited but faster option. -/// -dtStatus dtNavMeshQuery::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const -{ - dtAssert(m_nav); - if (!m_nav->isValidPolyRef(ref) || - !pos || !dtVisfinite(pos) || - !closest) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - m_nav->closestPointOnPoly(ref, pos, closest, posOverPoly); - return DT_SUCCESS; -} - -/// @par -/// -/// Much faster than closestPointOnPoly(). -/// -/// If the provided position lies within the polygon's xz-bounds (above or below), -/// then @p pos and @p closest will be equal. -/// -/// The height of @p closest will be the polygon boundary. The height detail is not used. -/// -/// @p pos does not have to be within the bounds of the polybon or the navigation mesh. -/// -dtStatus dtNavMeshQuery::closestPointOnPolyBoundary(dtPolyRef ref, const float* pos, float* closest) const -{ - dtAssert(m_nav); - - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) - return DT_FAILURE | DT_INVALID_PARAM; - - if (!pos || !dtVisfinite(pos) || !closest) - return DT_FAILURE | DT_INVALID_PARAM; - - // Collect vertices. - float verts[DT_VERTS_PER_POLYGON*3]; - float edged[DT_VERTS_PER_POLYGON]; - float edget[DT_VERTS_PER_POLYGON]; - int nv = 0; - for (int i = 0; i < (int)poly->vertCount; ++i) - { - dtVcopy(&verts[nv*3], &tile->verts[poly->verts[i]*3]); - nv++; - } - - bool inside = dtDistancePtPolyEdgesSqr(pos, verts, nv, edged, edget); - if (inside) - { - // Point is inside the polygon, return the point. - dtVcopy(closest, pos); - } - else - { - // Point is outside the polygon, dtClamp to nearest edge. - float dmin = edged[0]; - int imin = 0; - for (int i = 1; i < nv; ++i) - { - if (edged[i] < dmin) - { - dmin = edged[i]; - imin = i; - } - } - const float* va = &verts[imin*3]; - const float* vb = &verts[((imin+1)%nv)*3]; - dtVlerp(closest, va, vb, edget[imin]); - } - - return DT_SUCCESS; -} - -/// @par -/// -/// Will return #DT_FAILURE | DT_INVALID_PARAM if the provided position is outside the xz-bounds -/// of the polygon. -/// -dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* height) const -{ - dtAssert(m_nav); - - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) - return DT_FAILURE | DT_INVALID_PARAM; - - if (!pos || !dtVisfinite2D(pos)) - return DT_FAILURE | DT_INVALID_PARAM; - - // We used to return success for offmesh connections, but the - // getPolyHeight in DetourNavMesh does not do this, so special - // case it here. - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - { - const float* v0 = &tile->verts[poly->verts[0]*3]; - const float* v1 = &tile->verts[poly->verts[1]*3]; - float t; - dtDistancePtSegSqr2D(pos, v0, v1, t); - if (height) - *height = v0[1] + (v1[1] - v0[1])*t; - - return DT_SUCCESS; - } - - return m_nav->getPolyHeight(tile, poly, pos, height) - ? DT_SUCCESS - : DT_FAILURE | DT_INVALID_PARAM; -} - -class dtFindNearestPolyQuery : public dtPolyQuery -{ - const dtNavMeshQuery* m_query; - const float* m_center; - float m_nearestDistanceSqr; - dtPolyRef m_nearestRef; - float m_nearestPoint[3]; - -public: - dtFindNearestPolyQuery(const dtNavMeshQuery* query, const float* center) - : m_query(query), m_center(center), m_nearestDistanceSqr(FLT_MAX), m_nearestRef(0), m_nearestPoint() - { - } - - dtPolyRef nearestRef() const { return m_nearestRef; } - const float* nearestPoint() const { return m_nearestPoint; } - - void process(const dtMeshTile* tile, dtPoly** polys, dtPolyRef* refs, int count) override - { - dtIgnoreUnused(polys); - - for (int i = 0; i < count; ++i) - { - dtPolyRef ref = refs[i]; - float closestPtPoly[3]; - float diff[3]; - bool posOverPoly = false; - float d; - m_query->closestPointOnPoly(ref, m_center, closestPtPoly, &posOverPoly); - - // If a point is directly over a polygon and closer than - // climb height, favor that instead of straight line nearest point. - dtVsub(diff, m_center, closestPtPoly); - if (posOverPoly) - { - d = dtAbs(diff[1]) - tile->header->walkableClimb; - d = d > 0 ? d*d : 0; - } - else - { - d = dtVlenSqr(diff); - } - - if (d < m_nearestDistanceSqr) - { - dtVcopy(m_nearestPoint, closestPtPoly); - - m_nearestDistanceSqr = d; - m_nearestRef = ref; - } - } - } -}; - -/// @par -/// -/// @note If the search box does not intersect any polygons the search will -/// return #DT_SUCCESS, but @p nearestRef will be zero. So if in doubt, check -/// @p nearestRef before using @p nearestPt. -/// -dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* halfExtents, - const dtQueryFilter* filter, - dtPolyRef* nearestRef, float* nearestPt) const -{ - dtAssert(m_nav); - - if (!nearestRef) - return DT_FAILURE | DT_INVALID_PARAM; - - // queryPolygons below will check rest of params - - dtFindNearestPolyQuery query(this, center); - - dtStatus status = queryPolygons(center, halfExtents, filter, &query); - if (dtStatusFailed(status)) - return status; - - *nearestRef = query.nearestRef(); - // Only override nearestPt if we actually found a poly so the nearest point - // is valid. - if (nearestPt && *nearestRef) - dtVcopy(nearestPt, query.nearestPoint()); - - return DT_SUCCESS; -} - -void dtNavMeshQuery::queryPolygonsInTile(const dtMeshTile* tile, const float* qmin, const float* qmax, - const dtQueryFilter* filter, dtPolyQuery* query) const -{ - dtAssert(m_nav); - static const int batchSize = 32; - dtPolyRef polyRefs[batchSize]; - dtPoly* polys[batchSize]; - int n = 0; - - if (tile->bvTree) - { - const dtBVNode* node = &tile->bvTree[0]; - const dtBVNode* end = &tile->bvTree[tile->header->bvNodeCount]; - const float* tbmin = tile->header->bmin; - const float* tbmax = tile->header->bmax; - const float qfac = tile->header->bvQuantFactor; - - // Calculate quantized box - unsigned short bmin[3], bmax[3]; - // dtClamp query box to world box. - float minx = dtClamp(qmin[0], tbmin[0], tbmax[0]) - tbmin[0]; - float miny = dtClamp(qmin[1], tbmin[1], tbmax[1]) - tbmin[1]; - float minz = dtClamp(qmin[2], tbmin[2], tbmax[2]) - tbmin[2]; - float maxx = dtClamp(qmax[0], tbmin[0], tbmax[0]) - tbmin[0]; - float maxy = dtClamp(qmax[1], tbmin[1], tbmax[1]) - tbmin[1]; - float maxz = dtClamp(qmax[2], tbmin[2], tbmax[2]) - tbmin[2]; - // Quantize - bmin[0] = (unsigned short)(qfac * minx) & 0xfffe; - bmin[1] = (unsigned short)(qfac * miny) & 0xfffe; - bmin[2] = (unsigned short)(qfac * minz) & 0xfffe; - bmax[0] = (unsigned short)(qfac * maxx + 1) | 1; - bmax[1] = (unsigned short)(qfac * maxy + 1) | 1; - bmax[2] = (unsigned short)(qfac * maxz + 1) | 1; - - // Traverse tree - const dtPolyRef base = m_nav->getPolyRefBase(tile); - while (node < end) - { - const bool overlap = dtOverlapQuantBounds(bmin, bmax, node->bmin, node->bmax); - const bool isLeafNode = node->i >= 0; - - if (isLeafNode && overlap) - { - dtPolyRef ref = base | (dtPolyRef)node->i; - if (filter->passFilter(ref, tile, &tile->polys[node->i])) - { - polyRefs[n] = ref; - polys[n] = &tile->polys[node->i]; - - if (n == batchSize - 1) - { - query->process(tile, polys, polyRefs, batchSize); - n = 0; - } - else - { - n++; - } - } - } - - if (overlap || isLeafNode) - node++; - else - { - const int escapeIndex = -node->i; - node += escapeIndex; - } - } - } - else - { - float bmin[3], bmax[3]; - const dtPolyRef base = m_nav->getPolyRefBase(tile); - for (int i = 0; i < tile->header->polyCount; ++i) - { - dtPoly* p = &tile->polys[i]; - // Do not return off-mesh connection polygons. - if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - continue; - // Must pass filter - const dtPolyRef ref = base | (dtPolyRef)i; - if (!filter->passFilter(ref, tile, p)) - continue; - // Calc polygon bounds. - const float* v = &tile->verts[p->verts[0]*3]; - dtVcopy(bmin, v); - dtVcopy(bmax, v); - for (int j = 1; j < p->vertCount; ++j) - { - v = &tile->verts[p->verts[j]*3]; - dtVmin(bmin, v); - dtVmax(bmax, v); - } - if (dtOverlapBounds(qmin, qmax, bmin, bmax)) - { - polyRefs[n] = ref; - polys[n] = p; - - if (n == batchSize - 1) - { - query->process(tile, polys, polyRefs, batchSize); - n = 0; - } - else - { - n++; - } - } - } - } - - // Process the last polygons that didn't make a full batch. - if (n > 0) - query->process(tile, polys, polyRefs, n); -} - -class dtCollectPolysQuery : public dtPolyQuery -{ - dtPolyRef* m_polys; - const int m_maxPolys; - int m_numCollected; - bool m_overflow; - -public: - dtCollectPolysQuery(dtPolyRef* polys, const int maxPolys) - : m_polys(polys), m_maxPolys(maxPolys), m_numCollected(0), m_overflow(false) - { - } - - int numCollected() const { return m_numCollected; } - bool overflowed() const { return m_overflow; } - - void process(const dtMeshTile* tile, dtPoly** polys, dtPolyRef* refs, int count) override - { - dtIgnoreUnused(tile); - dtIgnoreUnused(polys); - - int numLeft = m_maxPolys - m_numCollected; - int toCopy = count; - if (toCopy > numLeft) - { - m_overflow = true; - toCopy = numLeft; - } - - memcpy(m_polys + m_numCollected, refs, (size_t)toCopy * sizeof(dtPolyRef)); - m_numCollected += toCopy; - } -}; - -/// @par -/// -/// If no polygons are found, the function will return #DT_SUCCESS with a -/// @p polyCount of zero. -/// -/// If @p polys is too small to hold the entire result set, then the array will -/// be filled to capacity. The method of choosing which polygons from the -/// full set are included in the partial result set is undefined. -/// -dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* halfExtents, - const dtQueryFilter* filter, - dtPolyRef* polys, int* polyCount, const int maxPolys) const -{ - if (!polys || !polyCount || maxPolys < 0) - return DT_FAILURE | DT_INVALID_PARAM; - - dtCollectPolysQuery collector(polys, maxPolys); - - dtStatus status = queryPolygons(center, halfExtents, filter, &collector); - if (dtStatusFailed(status)) - return status; - - *polyCount = collector.numCollected(); - return collector.overflowed() ? DT_SUCCESS | DT_BUFFER_TOO_SMALL : DT_SUCCESS; -} - -/// @par -/// -/// The query will be invoked with batches of polygons. Polygons passed -/// to the query have bounding boxes that overlap with the center and halfExtents -/// passed to this function. The dtPolyQuery::process function is invoked multiple -/// times until all overlapping polygons have been processed. -/// -dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* halfExtents, - const dtQueryFilter* filter, dtPolyQuery* query) const -{ - dtAssert(m_nav); - - if (!center || !dtVisfinite(center) || - !halfExtents || !dtVisfinite(halfExtents) || - !filter || !query) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - float bmin[3], bmax[3]; - dtVsub(bmin, center, halfExtents); - dtVadd(bmax, center, halfExtents); - - // Find tiles the query touches. - int minx, miny, maxx, maxy; - m_nav->calcTileLoc(bmin, &minx, &miny); - m_nav->calcTileLoc(bmax, &maxx, &maxy); - - static const int MAX_NEIS = 32; - const dtMeshTile* neis[MAX_NEIS]; - - for (int y = miny; y <= maxy; ++y) - { - for (int x = minx; x <= maxx; ++x) - { - const int nneis = m_nav->getTilesAt(x,y,neis,MAX_NEIS); - for (int j = 0; j < nneis; ++j) - { - queryPolygonsInTile(neis[j], bmin, bmax, filter, query); - } - } - } - - return DT_SUCCESS; -} - -/// @par -/// -/// If the end polygon cannot be reached through the navigation graph, -/// the last polygon in the path will be the nearest the end polygon. -/// -/// If the path array is to small to hold the full result, it will be filled as -/// far as possible from the start polygon toward the end polygon. -/// -/// The start and end positions are used to calculate traversal costs. -/// (The y-values impact the result.) -/// -dtStatus dtNavMeshQuery::findPath(dtPolyRef startRef, dtPolyRef endRef, - const float* startPos, const float* endPos, - const dtQueryFilter* filter, - dtPolyRef* path, int* pathCount, const int maxPath) const -{ - dtAssert(m_nav); - dtAssert(m_nodePool); - dtAssert(m_openList); - - if (!pathCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *pathCount = 0; - - // Validate input - if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef) || - !startPos || !dtVisfinite(startPos) || - !endPos || !dtVisfinite(endPos) || - !filter || !path || maxPath <= 0) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - if (startRef == endRef) - { - path[0] = startRef; - *pathCount = 1; - return DT_SUCCESS; - } - - m_nodePool->clear(); - m_openList->clear(); - - dtNode* startNode = m_nodePool->getNode(startRef); - dtVcopy(startNode->pos, startPos); - startNode->pidx = 0; - startNode->cost = 0; - startNode->total = dtVdist(startPos, endPos) * H_SCALE; - startNode->id = startRef; - startNode->flags = DT_NODE_OPEN; - m_openList->push(startNode); - - dtNode* lastBestNode = startNode; - float lastBestNodeCost = startNode->total; - - bool outOfNodes = false; - - while (!m_openList->empty()) - { - // Remove node from open list and put it in closed list. - dtNode* bestNode = m_openList->pop(); - bestNode->flags &= ~DT_NODE_OPEN; - bestNode->flags |= DT_NODE_CLOSED; - - // Reached the goal, stop searching. - if (bestNode->id == endRef) - { - lastBestNode = bestNode; - break; - } - - // Get current poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtPolyRef bestRef = bestNode->id; - const dtMeshTile* bestTile = 0; - const dtPoly* bestPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(bestRef, &bestTile, &bestPoly); - - // Get parent poly and tile. - dtPolyRef parentRef = 0; - const dtMeshTile* parentTile = 0; - const dtPoly* parentPoly = 0; - if (bestNode->pidx) - parentRef = m_nodePool->getNodeAtIdx(bestNode->pidx)->id; - if (parentRef) - m_nav->getTileAndPolyByRefUnsafe(parentRef, &parentTile, &parentPoly); - - for (unsigned int i = bestPoly->firstLink; i != DT_NULL_LINK; i = bestTile->links[i].next) - { - dtPolyRef neighbourRef = bestTile->links[i].ref; - - // Skip invalid ids and do not expand back to where we came from. - if (!neighbourRef || neighbourRef == parentRef) - continue; - - // Get neighbour poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtMeshTile* neighbourTile = 0; - const dtPoly* neighbourPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(neighbourRef, &neighbourTile, &neighbourPoly); - - if (!filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) - continue; - - // deal explicitly with crossing tile boundaries - unsigned char crossSide = 0; - if (bestTile->links[i].side != 0xff) - crossSide = bestTile->links[i].side >> 1; - - // get the node - dtNode* neighbourNode = m_nodePool->getNode(neighbourRef, crossSide); - if (!neighbourNode) - { - outOfNodes = true; - continue; - } - - // If the node is visited the first time, calculate node position. - if (neighbourNode->flags == 0) - { - getEdgeMidPoint(bestRef, bestPoly, bestTile, - neighbourRef, neighbourPoly, neighbourTile, - neighbourNode->pos); - } - - // Calculate cost and heuristic. - float cost = 0; - float heuristic = 0; - - // Special case for last node. - if (neighbourRef == endRef) - { - // Cost - const float curCost = filter->getCost(bestNode->pos, neighbourNode->pos, - parentRef, parentTile, parentPoly, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly); - const float endCost = filter->getCost(neighbourNode->pos, endPos, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly, - 0, 0, 0); - - cost = bestNode->cost + curCost + endCost; - heuristic = 0; - } - else - { - // Cost - const float curCost = filter->getCost(bestNode->pos, neighbourNode->pos, - parentRef, parentTile, parentPoly, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly); - cost = bestNode->cost + curCost; - heuristic = dtVdist(neighbourNode->pos, endPos)*H_SCALE; - } - - const float total = cost + heuristic; - - // The node is already in open list and the new result is worse, skip. - if ((neighbourNode->flags & DT_NODE_OPEN) && total >= neighbourNode->total) - continue; - // The node is already visited and process, and the new result is worse, skip. - if ((neighbourNode->flags & DT_NODE_CLOSED) && total >= neighbourNode->total) - continue; - - // Add or update the node. - neighbourNode->pidx = m_nodePool->getNodeIdx(bestNode); - neighbourNode->id = neighbourRef; - neighbourNode->flags = (neighbourNode->flags & ~DT_NODE_CLOSED); - neighbourNode->cost = cost; - neighbourNode->total = total; - - if (neighbourNode->flags & DT_NODE_OPEN) - { - // Already in open, update node location. - m_openList->modify(neighbourNode); - } - else - { - // Put the node in open list. - neighbourNode->flags |= DT_NODE_OPEN; - m_openList->push(neighbourNode); - } - - // Update nearest node to target so far. - if (heuristic < lastBestNodeCost) - { - lastBestNodeCost = heuristic; - lastBestNode = neighbourNode; - } - } - } - - dtStatus status = getPathToNode(lastBestNode, path, pathCount, maxPath); - - if (lastBestNode->id != endRef) - status |= DT_PARTIAL_RESULT; - - if (outOfNodes) - status |= DT_OUT_OF_NODES; - - return status; -} - -dtStatus dtNavMeshQuery::getPathToNode(dtNode* endNode, dtPolyRef* path, int* pathCount, int maxPath) const -{ - // Find the length of the entire path. - dtNode* curNode = endNode; - int length = 0; - do - { - length++; - curNode = m_nodePool->getNodeAtIdx(curNode->pidx); - } while (curNode); - - // If the path cannot be fully stored then advance to the last node we will be able to store. - curNode = endNode; - int writeCount; - for (writeCount = length; writeCount > maxPath; writeCount--) - { - dtAssert(curNode); - - curNode = m_nodePool->getNodeAtIdx(curNode->pidx); - } - - // Write path - for (int i = writeCount - 1; i >= 0; i--) - { - dtAssert(curNode); - - path[i] = curNode->id; - curNode = m_nodePool->getNodeAtIdx(curNode->pidx); - } - - dtAssert(!curNode); - - *pathCount = dtMin(length, maxPath); - - if (length > maxPath) - return DT_SUCCESS | DT_BUFFER_TOO_SMALL; - - return DT_SUCCESS; -} - - -/// @par -/// -/// @warning Calling any non-slice methods before calling finalizeSlicedFindPath() -/// or finalizeSlicedFindPathPartial() may result in corrupted data! -/// -/// The @p filter pointer is stored and used for the duration of the sliced -/// path query. -/// -dtStatus dtNavMeshQuery::initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef, - const float* startPos, const float* endPos, - const dtQueryFilter* filter, const unsigned int options) -{ - dtAssert(m_nav); - dtAssert(m_nodePool); - dtAssert(m_openList); - - // Init path state. - memset(&m_query, 0, sizeof(dtQueryData)); - m_query.status = DT_FAILURE; - m_query.startRef = startRef; - m_query.endRef = endRef; - if (startPos) - dtVcopy(m_query.startPos, startPos); - if (endPos) - dtVcopy(m_query.endPos, endPos); - m_query.filter = filter; - m_query.options = options; - m_query.raycastLimitSqr = FLT_MAX; - - // Validate input - if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef) || - !startPos || !dtVisfinite(startPos) || - !endPos || !dtVisfinite(endPos) || !filter) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - // trade quality with performance? - if (options & DT_FINDPATH_ANY_ANGLE) - { - // limiting to several times the character radius yields nice results. It is not sensitive - // so it is enough to compute it from the first tile. - const dtMeshTile* tile = m_nav->getTileByRef(startRef); - float agentRadius = tile->header->walkableRadius; - m_query.raycastLimitSqr = dtSqr(agentRadius * DT_RAY_CAST_LIMIT_PROPORTIONS); - } - - if (startRef == endRef) - { - m_query.status = DT_SUCCESS; - return DT_SUCCESS; - } - - m_nodePool->clear(); - m_openList->clear(); - - dtNode* startNode = m_nodePool->getNode(startRef); - dtVcopy(startNode->pos, startPos); - startNode->pidx = 0; - startNode->cost = 0; - startNode->total = dtVdist(startPos, endPos) * H_SCALE; - startNode->id = startRef; - startNode->flags = DT_NODE_OPEN; - m_openList->push(startNode); - - m_query.status = DT_IN_PROGRESS; - m_query.lastBestNode = startNode; - m_query.lastBestNodeCost = startNode->total; - - return m_query.status; -} - -dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) -{ - if (!dtStatusInProgress(m_query.status)) - return m_query.status; - - // Make sure the request is still valid. - if (!m_nav->isValidPolyRef(m_query.startRef) || !m_nav->isValidPolyRef(m_query.endRef)) - { - m_query.status = DT_FAILURE; - return DT_FAILURE; - } - - dtRaycastHit rayHit; - rayHit.maxPath = 0; - - int iter = 0; - while (iter < maxIter && !m_openList->empty()) - { - iter++; - - // Remove node from open list and put it in closed list. - dtNode* bestNode = m_openList->pop(); - bestNode->flags &= ~DT_NODE_OPEN; - bestNode->flags |= DT_NODE_CLOSED; - - // Reached the goal, stop searching. - if (bestNode->id == m_query.endRef) - { - m_query.lastBestNode = bestNode; - const dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; - m_query.status = DT_SUCCESS | details; - if (doneIters) - *doneIters = iter; - return m_query.status; - } - - // Get current poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtPolyRef bestRef = bestNode->id; - const dtMeshTile* bestTile = 0; - const dtPoly* bestPoly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(bestRef, &bestTile, &bestPoly))) - { - // The polygon has disappeared during the sliced query, fail. - m_query.status = DT_FAILURE; - if (doneIters) - *doneIters = iter; - return m_query.status; - } - - // Get parent and grand parent poly and tile. - dtPolyRef parentRef = 0, grandpaRef = 0; - const dtMeshTile* parentTile = 0; - const dtPoly* parentPoly = 0; - dtNode* parentNode = 0; - if (bestNode->pidx) - { - parentNode = m_nodePool->getNodeAtIdx(bestNode->pidx); - parentRef = parentNode->id; - if (parentNode->pidx) - grandpaRef = m_nodePool->getNodeAtIdx(parentNode->pidx)->id; - } - if (parentRef) - { - bool invalidParent = dtStatusFailed(m_nav->getTileAndPolyByRef(parentRef, &parentTile, &parentPoly)); - if (invalidParent || (grandpaRef && !m_nav->isValidPolyRef(grandpaRef)) ) - { - // The polygon has disappeared during the sliced query, fail. - m_query.status = DT_FAILURE; - if (doneIters) - *doneIters = iter; - return m_query.status; - } - } - - // decide whether to test raycast to previous nodes - bool tryLOS = false; - if (m_query.options & DT_FINDPATH_ANY_ANGLE) - { - if ((parentRef != 0) && (dtVdistSqr(parentNode->pos, bestNode->pos) < m_query.raycastLimitSqr)) - tryLOS = true; - } - - for (unsigned int i = bestPoly->firstLink; i != DT_NULL_LINK; i = bestTile->links[i].next) - { - dtPolyRef neighbourRef = bestTile->links[i].ref; - - // Skip invalid ids and do not expand back to where we came from. - if (!neighbourRef || neighbourRef == parentRef) - continue; - - // Get neighbour poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtMeshTile* neighbourTile = 0; - const dtPoly* neighbourPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(neighbourRef, &neighbourTile, &neighbourPoly); - - if (!m_query.filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) - continue; - - // get the neighbor node - dtNode* neighbourNode = m_nodePool->getNode(neighbourRef, 0); - if (!neighbourNode) - { - m_query.status |= DT_OUT_OF_NODES; - continue; - } - - // do not expand to nodes that were already visited from the same parent - if (neighbourNode->pidx != 0 && neighbourNode->pidx == bestNode->pidx) - continue; - - // If the node is visited the first time, calculate node position. - if (neighbourNode->flags == 0) - { - getEdgeMidPoint(bestRef, bestPoly, bestTile, - neighbourRef, neighbourPoly, neighbourTile, - neighbourNode->pos); - } - - // Calculate cost and heuristic. - float cost = 0; - float heuristic = 0; - - // raycast parent - bool foundShortCut = false; - rayHit.pathCost = rayHit.t = 0; - if (tryLOS) - { - raycast(parentRef, parentNode->pos, neighbourNode->pos, m_query.filter, DT_RAYCAST_USE_COSTS, &rayHit, grandpaRef); - foundShortCut = rayHit.t >= 1.0f; - } - - // update move cost - if (foundShortCut) - { - // shortcut found using raycast. Using shorter cost instead - cost = parentNode->cost + rayHit.pathCost; - } - else - { - // No shortcut found. - const float curCost = m_query.filter->getCost(bestNode->pos, neighbourNode->pos, - parentRef, parentTile, parentPoly, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly); - cost = bestNode->cost + curCost; - } - - // Special case for last node. - if (neighbourRef == m_query.endRef) - { - const float endCost = m_query.filter->getCost(neighbourNode->pos, m_query.endPos, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly, - 0, 0, 0); - - cost = cost + endCost; - heuristic = 0; - } - else - { - heuristic = dtVdist(neighbourNode->pos, m_query.endPos)*H_SCALE; - } - - const float total = cost + heuristic; - - // The node is already in open list and the new result is worse, skip. - if ((neighbourNode->flags & DT_NODE_OPEN) && total >= neighbourNode->total) - continue; - // The node is already visited and process, and the new result is worse, skip. - if ((neighbourNode->flags & DT_NODE_CLOSED) && total >= neighbourNode->total) - continue; - - // Add or update the node. - neighbourNode->pidx = foundShortCut ? bestNode->pidx : m_nodePool->getNodeIdx(bestNode); - neighbourNode->id = neighbourRef; - neighbourNode->flags = (neighbourNode->flags & ~(DT_NODE_CLOSED | DT_NODE_PARENT_DETACHED)); - neighbourNode->cost = cost; - neighbourNode->total = total; - if (foundShortCut) - neighbourNode->flags = (neighbourNode->flags | DT_NODE_PARENT_DETACHED); - - if (neighbourNode->flags & DT_NODE_OPEN) - { - // Already in open, update node location. - m_openList->modify(neighbourNode); - } - else - { - // Put the node in open list. - neighbourNode->flags |= DT_NODE_OPEN; - m_openList->push(neighbourNode); - } - - // Update nearest node to target so far. - if (heuristic < m_query.lastBestNodeCost) - { - m_query.lastBestNodeCost = heuristic; - m_query.lastBestNode = neighbourNode; - } - } - } - - // Exhausted all nodes, but could not find path. - if (m_openList->empty()) - { - const dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; - m_query.status = DT_SUCCESS | details; - } - - if (doneIters) - *doneIters = iter; - - return m_query.status; -} - -dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, const int maxPath) -{ - if (!pathCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *pathCount = 0; - - if (!path || maxPath <= 0) - return DT_FAILURE | DT_INVALID_PARAM; - - if (dtStatusFailed(m_query.status)) - { - // Reset query. - memset(&m_query, 0, sizeof(dtQueryData)); - return DT_FAILURE; - } - - int n = 0; - - if (m_query.startRef == m_query.endRef) - { - // Special case: the search starts and ends at same poly. - path[n++] = m_query.startRef; - } - else - { - // Reverse the path. - dtAssert(m_query.lastBestNode); - - if (m_query.lastBestNode->id != m_query.endRef) - m_query.status |= DT_PARTIAL_RESULT; - - dtNode* prev = 0; - dtNode* node = m_query.lastBestNode; - int prevRay = 0; - do - { - dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); - node->pidx = m_nodePool->getNodeIdx(prev); - prev = node; - int nextRay = node->flags & DT_NODE_PARENT_DETACHED; // keep track of whether parent is not adjacent (i.e. due to raycast shortcut) - node->flags = (node->flags & ~DT_NODE_PARENT_DETACHED) | prevRay; // and store it in the reversed path's node - prevRay = nextRay; - node = next; - } - while (node); - - // Store path - node = prev; - do - { - dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); - dtStatus status = 0; - if (node->flags & DT_NODE_PARENT_DETACHED) - { - float t, normal[3]; - int m; - status = raycast(node->id, node->pos, next->pos, m_query.filter, &t, normal, path+n, &m, maxPath-n); - n += m; - // raycast ends on poly boundary and the path might include the next poly boundary. - if (path[n-1] == next->id) - n--; // remove to avoid duplicates - } - else - { - path[n++] = node->id; - if (n >= maxPath) - status = DT_BUFFER_TOO_SMALL; - } - - if (status & DT_STATUS_DETAIL_MASK) - { - m_query.status |= status & DT_STATUS_DETAIL_MASK; - break; - } - node = next; - } - while (node); - } - - const dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; - - // Reset query. - memset(&m_query, 0, sizeof(dtQueryData)); - - *pathCount = n; - - return DT_SUCCESS | details; -} - -dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing, const int existingSize, - dtPolyRef* path, int* pathCount, const int maxPath) -{ - if (!pathCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *pathCount = 0; - - if (!existing || existingSize <= 0 || !path || !pathCount || maxPath <= 0) - return DT_FAILURE | DT_INVALID_PARAM; - - if (dtStatusFailed(m_query.status)) - { - // Reset query. - memset(&m_query, 0, sizeof(dtQueryData)); - return DT_FAILURE; - } - - int n = 0; - - if (m_query.startRef == m_query.endRef) - { - // Special case: the search starts and ends at same poly. - path[n++] = m_query.startRef; - } - else - { - // Find furthest existing node that was visited. - dtNode* prev = 0; - dtNode* node = 0; - for (int i = existingSize-1; i >= 0; --i) - { - m_nodePool->findNodes(existing[i], &node, 1); - if (node) - break; - } - - if (!node) - { - m_query.status |= DT_PARTIAL_RESULT; - dtAssert(m_query.lastBestNode); - node = m_query.lastBestNode; - } - - // Reverse the path. - int prevRay = 0; - do - { - dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); - node->pidx = m_nodePool->getNodeIdx(prev); - prev = node; - int nextRay = node->flags & DT_NODE_PARENT_DETACHED; // keep track of whether parent is not adjacent (i.e. due to raycast shortcut) - node->flags = (node->flags & ~DT_NODE_PARENT_DETACHED) | prevRay; // and store it in the reversed path's node - prevRay = nextRay; - node = next; - } - while (node); - - // Store path - node = prev; - do - { - dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); - dtStatus status = 0; - if (node->flags & DT_NODE_PARENT_DETACHED) - { - float t, normal[3]; - int m; - status = raycast(node->id, node->pos, next->pos, m_query.filter, &t, normal, path+n, &m, maxPath-n); - n += m; - // raycast ends on poly boundary and the path might include the next poly boundary. - if (path[n-1] == next->id) - n--; // remove to avoid duplicates - } - else - { - path[n++] = node->id; - if (n >= maxPath) - status = DT_BUFFER_TOO_SMALL; - } - - if (status & DT_STATUS_DETAIL_MASK) - { - m_query.status |= status & DT_STATUS_DETAIL_MASK; - break; - } - node = next; - } - while (node); - } - - const dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; - - // Reset query. - memset(&m_query, 0, sizeof(dtQueryData)); - - *pathCount = n; - - return DT_SUCCESS | details; -} - - -dtStatus dtNavMeshQuery::appendVertex(const float* pos, const unsigned char flags, const dtPolyRef ref, - float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, - int* straightPathCount, const int maxStraightPath) const -{ - if ((*straightPathCount) > 0 && dtVequal(&straightPath[((*straightPathCount)-1)*3], pos)) - { - // The vertices are equal, update flags and poly. - if (straightPathFlags) - straightPathFlags[(*straightPathCount)-1] = flags; - if (straightPathRefs) - straightPathRefs[(*straightPathCount)-1] = ref; - } - else - { - // Append new vertex. - dtVcopy(&straightPath[(*straightPathCount)*3], pos); - if (straightPathFlags) - straightPathFlags[(*straightPathCount)] = flags; - if (straightPathRefs) - straightPathRefs[(*straightPathCount)] = ref; - (*straightPathCount)++; - - // If there is no space to append more vertices, return. - if ((*straightPathCount) >= maxStraightPath) - { - return DT_SUCCESS | DT_BUFFER_TOO_SMALL; - } - - // If reached end of path, return. - if (flags == DT_STRAIGHTPATH_END) - { - return DT_SUCCESS; - } - } - return DT_IN_PROGRESS; -} - -dtStatus dtNavMeshQuery::appendPortals(const int startIdx, const int endIdx, const float* endPos, const dtPolyRef* path, - float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, - int* straightPathCount, const int maxStraightPath, const int options) const -{ - const float* startPos = &straightPath[(*straightPathCount-1)*3]; - // Append or update last vertex - dtStatus stat = 0; - for (int i = startIdx; i < endIdx; i++) - { - // Calculate portal - const dtPolyRef from = path[i]; - const dtMeshTile* fromTile = 0; - const dtPoly* fromPoly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(from, &fromTile, &fromPoly))) - return DT_FAILURE | DT_INVALID_PARAM; - - const dtPolyRef to = path[i+1]; - const dtMeshTile* toTile = 0; - const dtPoly* toPoly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(to, &toTile, &toPoly))) - return DT_FAILURE | DT_INVALID_PARAM; - - float left[3], right[3]; - if (dtStatusFailed(getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right))) - break; - - if (options & DT_STRAIGHTPATH_AREA_CROSSINGS) - { - // Skip intersection if only area crossings are requested. - if (fromPoly->getArea() == toPoly->getArea()) - continue; - } - - // Append intersection - float s,t; - if (dtIntersectSegSeg2D(startPos, endPos, left, right, s, t)) - { - float pt[3]; - dtVlerp(pt, left,right, t); - - stat = appendVertex(pt, 0, path[i+1], - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath); - if (stat != DT_IN_PROGRESS) - return stat; - } - } - return DT_IN_PROGRESS; -} - -/// @par -/// -/// This method peforms what is often called 'string pulling'. -/// -/// The start position is clamped to the first polygon in the path, and the -/// end position is clamped to the last. So the start and end positions should -/// normally be within or very near the first and last polygons respectively. -/// -/// The returned polygon references represent the reference id of the polygon -/// that is entered at the associated path position. The reference id associated -/// with the end point will always be zero. This allows, for example, matching -/// off-mesh link points to their representative polygons. -/// -/// If the provided result buffers are too small for the entire result set, -/// they will be filled as far as possible from the start toward the end -/// position. -/// -dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* endPos, - const dtPolyRef* path, const int pathSize, - float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, - int* straightPathCount, const int maxStraightPath, const int options) const -{ - dtAssert(m_nav); - - if (!straightPathCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *straightPathCount = 0; - - if (!startPos || !dtVisfinite(startPos) || - !endPos || !dtVisfinite(endPos) || - !path || pathSize <= 0 || !path[0] || - maxStraightPath <= 0) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - dtStatus stat = 0; - - // TODO: Should this be callers responsibility? - float closestStartPos[3]; - if (dtStatusFailed(closestPointOnPolyBoundary(path[0], startPos, closestStartPos))) - return DT_FAILURE | DT_INVALID_PARAM; - - float closestEndPos[3]; - if (dtStatusFailed(closestPointOnPolyBoundary(path[pathSize-1], endPos, closestEndPos))) - return DT_FAILURE | DT_INVALID_PARAM; - - // Add start point. - stat = appendVertex(closestStartPos, DT_STRAIGHTPATH_START, path[0], - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath); - if (stat != DT_IN_PROGRESS) - return stat; - - if (pathSize > 1) - { - float portalApex[3], portalLeft[3], portalRight[3]; - dtVcopy(portalApex, closestStartPos); - dtVcopy(portalLeft, portalApex); - dtVcopy(portalRight, portalApex); - int apexIndex = 0; - int leftIndex = 0; - int rightIndex = 0; - - unsigned char leftPolyType = 0; - unsigned char rightPolyType = 0; - - dtPolyRef leftPolyRef = path[0]; - dtPolyRef rightPolyRef = path[0]; - - for (int i = 0; i < pathSize; ++i) - { - float left[3], right[3]; - unsigned char toType; - - if (i+1 < pathSize) - { - unsigned char fromType; // fromType is ignored. - - // Next portal. - if (dtStatusFailed(getPortalPoints(path[i], path[i+1], left, right, fromType, toType))) - { - // Failed to get portal points, in practice this means that path[i+1] is invalid polygon. - // Clamp the end point to path[i], and return the path so far. - - if (dtStatusFailed(closestPointOnPolyBoundary(path[i], endPos, closestEndPos))) - { - // This should only happen when the first polygon is invalid. - return DT_FAILURE | DT_INVALID_PARAM; - } - - // Apeend portals along the current straight path segment. - if (options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) - { - // Ignore status return value as we're just about to return anyway. - appendPortals(apexIndex, i, closestEndPos, path, - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath, options); - } - - // Ignore status return value as we're just about to return anyway. - appendVertex(closestEndPos, 0, path[i], - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath); - - return DT_SUCCESS | DT_PARTIAL_RESULT | ((*straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); - } - - // If starting really close the portal, advance. - if (i == 0) - { - float t; - if (dtDistancePtSegSqr2D(portalApex, left, right, t) < dtSqr(0.001f)) - continue; - } - } - else - { - // End of the path. - dtVcopy(left, closestEndPos); - dtVcopy(right, closestEndPos); - - toType = DT_POLYTYPE_GROUND; - } - - // Right vertex. - if (dtTriArea2D(portalApex, portalRight, right) <= 0.0f) - { - if (dtVequal(portalApex, portalRight) || dtTriArea2D(portalApex, portalLeft, right) > 0.0f) - { - dtVcopy(portalRight, right); - rightPolyRef = (i+1 < pathSize) ? path[i+1] : 0; - rightPolyType = toType; - rightIndex = i; - } - else - { - // Append portals along the current straight path segment. - if (options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) - { - stat = appendPortals(apexIndex, leftIndex, portalLeft, path, - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath, options); - if (stat != DT_IN_PROGRESS) - return stat; - } - - dtVcopy(portalApex, portalLeft); - apexIndex = leftIndex; - - unsigned char flags = 0; - if (!leftPolyRef) - flags = DT_STRAIGHTPATH_END; - else if (leftPolyType == DT_POLYTYPE_OFFMESH_CONNECTION) - flags = DT_STRAIGHTPATH_OFFMESH_CONNECTION; - dtPolyRef ref = leftPolyRef; - - // Append or update vertex - stat = appendVertex(portalApex, flags, ref, - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath); - if (stat != DT_IN_PROGRESS) - return stat; - - dtVcopy(portalLeft, portalApex); - dtVcopy(portalRight, portalApex); - leftIndex = apexIndex; - rightIndex = apexIndex; - - // Restart - i = apexIndex; - - continue; - } - } - - // Left vertex. - if (dtTriArea2D(portalApex, portalLeft, left) >= 0.0f) - { - if (dtVequal(portalApex, portalLeft) || dtTriArea2D(portalApex, portalRight, left) < 0.0f) - { - dtVcopy(portalLeft, left); - leftPolyRef = (i+1 < pathSize) ? path[i+1] : 0; - leftPolyType = toType; - leftIndex = i; - } - else - { - // Append portals along the current straight path segment. - if (options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) - { - stat = appendPortals(apexIndex, rightIndex, portalRight, path, - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath, options); - if (stat != DT_IN_PROGRESS) - return stat; - } - - dtVcopy(portalApex, portalRight); - apexIndex = rightIndex; - - unsigned char flags = 0; - if (!rightPolyRef) - flags = DT_STRAIGHTPATH_END; - else if (rightPolyType == DT_POLYTYPE_OFFMESH_CONNECTION) - flags = DT_STRAIGHTPATH_OFFMESH_CONNECTION; - dtPolyRef ref = rightPolyRef; - - // Append or update vertex - stat = appendVertex(portalApex, flags, ref, - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath); - if (stat != DT_IN_PROGRESS) - return stat; - - dtVcopy(portalLeft, portalApex); - dtVcopy(portalRight, portalApex); - leftIndex = apexIndex; - rightIndex = apexIndex; - - // Restart - i = apexIndex; - - continue; - } - } - } - - // Append portals along the current straight path segment. - if (options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) - { - stat = appendPortals(apexIndex, pathSize-1, closestEndPos, path, - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath, options); - if (stat != DT_IN_PROGRESS) - return stat; - } - } - - // Ignore status return value as we're just about to return anyway. - appendVertex(closestEndPos, DT_STRAIGHTPATH_END, 0, - straightPath, straightPathFlags, straightPathRefs, - straightPathCount, maxStraightPath); - - return DT_SUCCESS | ((*straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); -} - -/// @par -/// -/// This method is optimized for small delta movement and a small number of -/// polygons. If used for too great a distance, the result set will form an -/// incomplete path. -/// -/// @p resultPos will equal the @p endPos if the end is reached. -/// Otherwise the closest reachable position will be returned. -/// -/// @p resultPos is not projected onto the surface of the navigation -/// mesh. Use #getPolyHeight if this is needed. -/// -/// This method treats the end position in the same manner as -/// the #raycast method. (As a 2D point.) See that method's documentation -/// for details. -/// -/// If the @p visited array is too small to hold the entire result set, it will -/// be filled as far as possible from the start position toward the end -/// position. -/// -dtStatus dtNavMeshQuery::moveAlongSurface(dtPolyRef startRef, const float* startPos, const float* endPos, - const dtQueryFilter* filter, - float* resultPos, dtPolyRef* visited, int* visitedCount, const int maxVisitedSize) const -{ - dtAssert(m_nav); - dtAssert(m_tinyNodePool); - - if (!visitedCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *visitedCount = 0; - - if (!m_nav->isValidPolyRef(startRef) || - !startPos || !dtVisfinite(startPos) || - !endPos || !dtVisfinite(endPos) || - !filter || !resultPos || !visited || - maxVisitedSize <= 0) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - dtStatus status = DT_SUCCESS; - - static const int MAX_STACK = 48; - dtNode* stack[MAX_STACK]; - int nstack = 0; - - m_tinyNodePool->clear(); - - dtNode* startNode = m_tinyNodePool->getNode(startRef); - startNode->pidx = 0; - startNode->cost = 0; - startNode->total = 0; - startNode->id = startRef; - startNode->flags = DT_NODE_CLOSED; - stack[nstack++] = startNode; - - float bestPos[3]; - float bestDist = FLT_MAX; - dtNode* bestNode = 0; - dtVcopy(bestPos, startPos); - - // Search constraints - float searchPos[3], searchRadSqr; - dtVlerp(searchPos, startPos, endPos, 0.5f); - searchRadSqr = dtSqr(dtVdist(startPos, endPos)/2.0f + 0.001f); - - float verts[DT_VERTS_PER_POLYGON*3]; - - while (nstack) - { - // Pop front. - dtNode* curNode = stack[0]; - for (int i = 0; i < nstack-1; ++i) - stack[i] = stack[i+1]; - nstack--; - - // Get poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtPolyRef curRef = curNode->id; - const dtMeshTile* curTile = 0; - const dtPoly* curPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(curRef, &curTile, &curPoly); - - // Collect vertices. - const int nverts = curPoly->vertCount; - for (int i = 0; i < nverts; ++i) - dtVcopy(&verts[i*3], &curTile->verts[curPoly->verts[i]*3]); - - // If target is inside the poly, stop search. - if (dtPointInPolygon(endPos, verts, nverts)) - { - bestNode = curNode; - dtVcopy(bestPos, endPos); - break; - } - - // Find wall edges and find nearest point inside the walls. - for (int i = 0, j = (int)curPoly->vertCount-1; i < (int)curPoly->vertCount; j = i++) - { - // Find links to neighbours. - static const int MAX_NEIS = 8; - int nneis = 0; - dtPolyRef neis[MAX_NEIS]; - - if (curPoly->neis[j] & DT_EXT_LINK) - { - // Tile border. - for (unsigned int k = curPoly->firstLink; k != DT_NULL_LINK; k = curTile->links[k].next) - { - const dtLink* link = &curTile->links[k]; - if (link->edge == j) - { - if (link->ref != 0) - { - const dtMeshTile* neiTile = 0; - const dtPoly* neiPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(link->ref, &neiTile, &neiPoly); - if (filter->passFilter(link->ref, neiTile, neiPoly)) - { - if (nneis < MAX_NEIS) - neis[nneis++] = link->ref; - } - } - } - } - } - else if (curPoly->neis[j]) - { - const unsigned int idx = (unsigned int)(curPoly->neis[j]-1); - const dtPolyRef ref = m_nav->getPolyRefBase(curTile) | idx; - if (filter->passFilter(ref, curTile, &curTile->polys[idx])) - { - // Internal edge, encode id. - neis[nneis++] = ref; - } - } - - if (!nneis) - { - // Wall edge, calc distance. - const float* vj = &verts[j*3]; - const float* vi = &verts[i*3]; - float tseg; - const float distSqr = dtDistancePtSegSqr2D(endPos, vj, vi, tseg); - if (distSqr < bestDist) - { - // Update nearest distance. - dtVlerp(bestPos, vj,vi, tseg); - bestDist = distSqr; - bestNode = curNode; - } - } - else - { - for (int k = 0; k < nneis; ++k) - { - // Skip if no node can be allocated. - dtNode* neighbourNode = m_tinyNodePool->getNode(neis[k]); - if (!neighbourNode) - continue; - // Skip if already visited. - if (neighbourNode->flags & DT_NODE_CLOSED) - continue; - - // Skip the link if it is too far from search constraint. - // TODO: Maybe should use getPortalPoints(), but this one is way faster. - const float* vj = &verts[j*3]; - const float* vi = &verts[i*3]; - float tseg; - float distSqr = dtDistancePtSegSqr2D(searchPos, vj, vi, tseg); - if (distSqr > searchRadSqr) - continue; - - // Mark as the node as visited and push to queue. - if (nstack < MAX_STACK) - { - neighbourNode->pidx = m_tinyNodePool->getNodeIdx(curNode); - neighbourNode->flags |= DT_NODE_CLOSED; - stack[nstack++] = neighbourNode; - } - } - } - } - } - - int n = 0; - if (bestNode) - { - // Reverse the path. - dtNode* prev = 0; - dtNode* node = bestNode; - do - { - dtNode* next = m_tinyNodePool->getNodeAtIdx(node->pidx); - node->pidx = m_tinyNodePool->getNodeIdx(prev); - prev = node; - node = next; - } - while (node); - - // Store result - node = prev; - do - { - visited[n++] = node->id; - if (n >= maxVisitedSize) - { - status |= DT_BUFFER_TOO_SMALL; - break; - } - node = m_tinyNodePool->getNodeAtIdx(node->pidx); - } - while (node); - } - - dtVcopy(resultPos, bestPos); - - *visitedCount = n; - - return status; -} - - -dtStatus dtNavMeshQuery::getPortalPoints(dtPolyRef from, dtPolyRef to, float* left, float* right, - unsigned char& fromType, unsigned char& toType) const -{ - dtAssert(m_nav); - - const dtMeshTile* fromTile = 0; - const dtPoly* fromPoly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(from, &fromTile, &fromPoly))) - return DT_FAILURE | DT_INVALID_PARAM; - fromType = fromPoly->getType(); - - const dtMeshTile* toTile = 0; - const dtPoly* toPoly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(to, &toTile, &toPoly))) - return DT_FAILURE | DT_INVALID_PARAM; - toType = toPoly->getType(); - - return getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right); -} - -// Returns portal points between two polygons. -dtStatus dtNavMeshQuery::getPortalPoints(dtPolyRef from, const dtPoly* fromPoly, const dtMeshTile* fromTile, - dtPolyRef to, const dtPoly* toPoly, const dtMeshTile* toTile, - float* left, float* right) const -{ - // Find the link that points to the 'to' polygon. - const dtLink* link = 0; - for (unsigned int i = fromPoly->firstLink; i != DT_NULL_LINK; i = fromTile->links[i].next) - { - if (fromTile->links[i].ref == to) - { - link = &fromTile->links[i]; - break; - } - } - if (!link) - return DT_FAILURE | DT_INVALID_PARAM; - - // Handle off-mesh connections. - if (fromPoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - { - // Find link that points to first vertex. - for (unsigned int i = fromPoly->firstLink; i != DT_NULL_LINK; i = fromTile->links[i].next) - { - if (fromTile->links[i].ref == to) - { - const int v = fromTile->links[i].edge; - dtVcopy(left, &fromTile->verts[fromPoly->verts[v]*3]); - dtVcopy(right, &fromTile->verts[fromPoly->verts[v]*3]); - return DT_SUCCESS; - } - } - return DT_FAILURE | DT_INVALID_PARAM; - } - - if (toPoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - { - for (unsigned int i = toPoly->firstLink; i != DT_NULL_LINK; i = toTile->links[i].next) - { - if (toTile->links[i].ref == from) - { - const int v = toTile->links[i].edge; - dtVcopy(left, &toTile->verts[toPoly->verts[v]*3]); - dtVcopy(right, &toTile->verts[toPoly->verts[v]*3]); - return DT_SUCCESS; - } - } - return DT_FAILURE | DT_INVALID_PARAM; - } - - // Find portal vertices. - const int v0 = fromPoly->verts[link->edge]; - const int v1 = fromPoly->verts[(link->edge+1) % (int)fromPoly->vertCount]; - dtVcopy(left, &fromTile->verts[v0*3]); - dtVcopy(right, &fromTile->verts[v1*3]); - - // If the link is at tile boundary, dtClamp the vertices to - // the link width. - if (link->side != 0xff) - { - // Unpack portal limits. - if (link->bmin != 0 || link->bmax != 255) - { - const float s = 1.0f/255.0f; - const float tmin = link->bmin*s; - const float tmax = link->bmax*s; - dtVlerp(left, &fromTile->verts[v0*3], &fromTile->verts[v1*3], tmin); - dtVlerp(right, &fromTile->verts[v0*3], &fromTile->verts[v1*3], tmax); - } - } - - return DT_SUCCESS; -} - -// Returns edge mid point between two polygons. -dtStatus dtNavMeshQuery::getEdgeMidPoint(dtPolyRef from, dtPolyRef to, float* mid) const -{ - float left[3], right[3]; - unsigned char fromType, toType; - if (dtStatusFailed(getPortalPoints(from, to, left,right, fromType, toType))) - return DT_FAILURE | DT_INVALID_PARAM; - mid[0] = (left[0]+right[0])*0.5f; - mid[1] = (left[1]+right[1])*0.5f; - mid[2] = (left[2]+right[2])*0.5f; - return DT_SUCCESS; -} - -dtStatus dtNavMeshQuery::getEdgeMidPoint(dtPolyRef from, const dtPoly* fromPoly, const dtMeshTile* fromTile, - dtPolyRef to, const dtPoly* toPoly, const dtMeshTile* toTile, - float* mid) const -{ - float left[3], right[3]; - if (dtStatusFailed(getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right))) - return DT_FAILURE | DT_INVALID_PARAM; - mid[0] = (left[0]+right[0])*0.5f; - mid[1] = (left[1]+right[1])*0.5f; - mid[2] = (left[2]+right[2])*0.5f; - return DT_SUCCESS; -} - - - -/// @par -/// -/// This method is meant to be used for quick, short distance checks. -/// -/// If the path array is too small to hold the result, it will be filled as -/// far as possible from the start postion toward the end position. -/// -/// Using the Hit Parameter (t) -/// -/// If the hit parameter is a very high value (FLT_MAX), then the ray has hit -/// the end position. In this case the path represents a valid corridor to the -/// end position and the value of @p hitNormal is undefined. -/// -/// If the hit parameter is zero, then the start position is on the wall that -/// was hit and the value of @p hitNormal is undefined. -/// -/// If 0 < t < 1.0 then the following applies: -/// -/// @code -/// distanceToHitBorder = distanceToEndPosition * t -/// hitPoint = startPos + (endPos - startPos) * t -/// @endcode -/// -/// Use Case Restriction -/// -/// The raycast ignores the y-value of the end position. (2D check.) This -/// places significant limits on how it can be used. For example: -/// -/// Consider a scene where there is a main floor with a second floor balcony -/// that hangs over the main floor. So the first floor mesh extends below the -/// balcony mesh. The start position is somewhere on the first floor. The end -/// position is on the balcony. -/// -/// The raycast will search toward the end position along the first floor mesh. -/// If it reaches the end position's xz-coordinates it will indicate FLT_MAX -/// (no wall hit), meaning it reached the end position. This is one example of why -/// this method is meant for short distance checks. -/// -dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, const float* endPos, - const dtQueryFilter* filter, - float* t, float* hitNormal, dtPolyRef* path, int* pathCount, const int maxPath) const -{ - dtRaycastHit hit; - hit.path = path; - hit.maxPath = maxPath; - - dtStatus status = raycast(startRef, startPos, endPos, filter, 0, &hit); - - *t = hit.t; - if (hitNormal) - dtVcopy(hitNormal, hit.hitNormal); - if (pathCount) - *pathCount = hit.pathCount; - - return status; -} - - -/// @par -/// -/// This method is meant to be used for quick, short distance checks. -/// -/// If the path array is too small to hold the result, it will be filled as -/// far as possible from the start postion toward the end position. -/// -/// Using the Hit Parameter t of RaycastHit -/// -/// If the hit parameter is a very high value (FLT_MAX), then the ray has hit -/// the end position. In this case the path represents a valid corridor to the -/// end position and the value of @p hitNormal is undefined. -/// -/// If the hit parameter is zero, then the start position is on the wall that -/// was hit and the value of @p hitNormal is undefined. -/// -/// If 0 < t < 1.0 then the following applies: -/// -/// @code -/// distanceToHitBorder = distanceToEndPosition * t -/// hitPoint = startPos + (endPos - startPos) * t -/// @endcode -/// -/// Use Case Restriction -/// -/// The raycast ignores the y-value of the end position. (2D check.) This -/// places significant limits on how it can be used. For example: -/// -/// Consider a scene where there is a main floor with a second floor balcony -/// that hangs over the main floor. So the first floor mesh extends below the -/// balcony mesh. The start position is somewhere on the first floor. The end -/// position is on the balcony. -/// -/// The raycast will search toward the end position along the first floor mesh. -/// If it reaches the end position's xz-coordinates it will indicate FLT_MAX -/// (no wall hit), meaning it reached the end position. This is one example of why -/// this method is meant for short distance checks. -/// -dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, const float* endPos, - const dtQueryFilter* filter, const unsigned int options, - dtRaycastHit* hit, dtPolyRef prevRef) const -{ - dtAssert(m_nav); - - if (!hit) - return DT_FAILURE | DT_INVALID_PARAM; - - hit->t = 0; - hit->pathCount = 0; - hit->pathCost = 0; - - // Validate input - if (!m_nav->isValidPolyRef(startRef) || - !startPos || !dtVisfinite(startPos) || - !endPos || !dtVisfinite(endPos) || - !filter || - (prevRef && !m_nav->isValidPolyRef(prevRef))) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - float dir[3], curPos[3], lastPos[3]; - float verts[DT_VERTS_PER_POLYGON*3+3]; - int n = 0; - - dtVcopy(curPos, startPos); - dtVsub(dir, endPos, startPos); - dtVset(hit->hitNormal, 0, 0, 0); - - dtStatus status = DT_SUCCESS; - - const dtMeshTile* prevTile, *tile, *nextTile; - const dtPoly* prevPoly, *poly, *nextPoly; - dtPolyRef curRef; - - // The API input has been checked already, skip checking internal data. - curRef = startRef; - tile = 0; - poly = 0; - m_nav->getTileAndPolyByRefUnsafe(curRef, &tile, &poly); - nextTile = prevTile = tile; - nextPoly = prevPoly = poly; - if (prevRef) - m_nav->getTileAndPolyByRefUnsafe(prevRef, &prevTile, &prevPoly); - - while (curRef) - { - // Cast ray against current polygon. - - // Collect vertices. - int nv = 0; - for (int i = 0; i < (int)poly->vertCount; ++i) - { - dtVcopy(&verts[nv*3], &tile->verts[poly->verts[i]*3]); - nv++; - } - - float tmin, tmax; - int segMin, segMax; - if (!dtIntersectSegmentPoly2D(startPos, endPos, verts, nv, tmin, tmax, segMin, segMax)) - { - // Could not hit the polygon, keep the old t and report hit. - hit->pathCount = n; - return status; - } - - hit->hitEdgeIndex = segMax; - - // Keep track of furthest t so far. - if (tmax > hit->t) - hit->t = tmax; - - // Store visited polygons. - if (n < hit->maxPath) - hit->path[n++] = curRef; - else - status |= DT_BUFFER_TOO_SMALL; - - // Ray end is completely inside the polygon. - if (segMax == -1) - { - hit->t = FLT_MAX; - hit->pathCount = n; - - // add the cost - if (options & DT_RAYCAST_USE_COSTS) - hit->pathCost += filter->getCost(curPos, endPos, prevRef, prevTile, prevPoly, curRef, tile, poly, curRef, tile, poly); - return status; - } - - // Follow neighbours. - dtPolyRef nextRef = 0; - - for (unsigned int i = poly->firstLink; i != DT_NULL_LINK; i = tile->links[i].next) - { - const dtLink* link = &tile->links[i]; - - // Find link which contains this edge. - if ((int)link->edge != segMax) - continue; - - // Get pointer to the next polygon. - nextTile = 0; - nextPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(link->ref, &nextTile, &nextPoly); - - // Skip off-mesh connections. - if (nextPoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - continue; - - // Skip links based on filter. - if (!filter->passFilter(link->ref, nextTile, nextPoly)) - continue; - - // If the link is internal, just return the ref. - if (link->side == 0xff) - { - nextRef = link->ref; - break; - } - - // If the link is at tile boundary, - - // Check if the link spans the whole edge, and accept. - if (link->bmin == 0 && link->bmax == 255) - { - nextRef = link->ref; - break; - } - - // Check for partial edge links. - const int v0 = poly->verts[link->edge]; - const int v1 = poly->verts[(link->edge+1) % poly->vertCount]; - const float* left = &tile->verts[v0*3]; - const float* right = &tile->verts[v1*3]; - - // Check that the intersection lies inside the link portal. - if (link->side == 0 || link->side == 4) - { - // Calculate link size. - const float s = 1.0f/255.0f; - float lmin = left[2] + (right[2] - left[2])*(link->bmin*s); - float lmax = left[2] + (right[2] - left[2])*(link->bmax*s); - if (lmin > lmax) dtSwap(lmin, lmax); - - // Find Z intersection. - float z = startPos[2] + (endPos[2]-startPos[2])*tmax; - if (z >= lmin && z <= lmax) - { - nextRef = link->ref; - break; - } - } - else if (link->side == 2 || link->side == 6) - { - // Calculate link size. - const float s = 1.0f/255.0f; - float lmin = left[0] + (right[0] - left[0])*(link->bmin*s); - float lmax = left[0] + (right[0] - left[0])*(link->bmax*s); - if (lmin > lmax) dtSwap(lmin, lmax); - - // Find X intersection. - float x = startPos[0] + (endPos[0]-startPos[0])*tmax; - if (x >= lmin && x <= lmax) - { - nextRef = link->ref; - break; - } - } - } - - // add the cost - if (options & DT_RAYCAST_USE_COSTS) - { - // compute the intersection point at the furthest end of the polygon - // and correct the height (since the raycast moves in 2d) - dtVcopy(lastPos, curPos); - dtVmad(curPos, startPos, dir, hit->t); - float* e1 = &verts[segMax*3]; - float* e2 = &verts[((segMax+1)%nv)*3]; - float eDir[3], diff[3]; - dtVsub(eDir, e2, e1); - dtVsub(diff, curPos, e1); - float s = dtSqr(eDir[0]) > dtSqr(eDir[2]) ? diff[0] / eDir[0] : diff[2] / eDir[2]; - curPos[1] = e1[1] + eDir[1] * s; - - hit->pathCost += filter->getCost(lastPos, curPos, prevRef, prevTile, prevPoly, curRef, tile, poly, nextRef, nextTile, nextPoly); - } - - if (!nextRef) - { - // No neighbour, we hit a wall. - - // Calculate hit normal. - const int a = segMax; - const int b = segMax+1 < nv ? segMax+1 : 0; - const float* va = &verts[a*3]; - const float* vb = &verts[b*3]; - const float dx = vb[0] - va[0]; - const float dz = vb[2] - va[2]; - hit->hitNormal[0] = dz; - hit->hitNormal[1] = 0; - hit->hitNormal[2] = -dx; - dtVnormalize(hit->hitNormal); - - hit->pathCount = n; - return status; - } - - // No hit, advance to neighbour polygon. - prevRef = curRef; - curRef = nextRef; - prevTile = tile; - tile = nextTile; - prevPoly = poly; - poly = nextPoly; - } - - hit->pathCount = n; - - return status; -} - -/// @par -/// -/// At least one result array must be provided. -/// -/// The order of the result set is from least to highest cost to reach the polygon. -/// -/// A common use case for this method is to perform Dijkstra searches. -/// Candidate polygons are found by searching the graph beginning at the start polygon. -/// -/// If a polygon is not found via the graph search, even if it intersects the -/// search circle, it will not be included in the result set. For example: -/// -/// polyA is the start polygon. -/// polyB shares an edge with polyA. (Is adjacent.) -/// polyC shares an edge with polyB, but not with polyA -/// Even if the search circle overlaps polyC, it will not be included in the -/// result set unless polyB is also in the set. -/// -/// The value of the center point is used as the start position for cost -/// calculations. It is not projected onto the surface of the mesh, so its -/// y-value will effect the costs. -/// -/// Intersection tests occur in 2D. All polygons and the search circle are -/// projected onto the xz-plane. So the y-value of the center point does not -/// effect intersection tests. -/// -/// If the result arrays are to small to hold the entire result set, they will be -/// filled to capacity. -/// -dtStatus dtNavMeshQuery::findPolysAroundCircle(dtPolyRef startRef, const float* centerPos, const float radius, - const dtQueryFilter* filter, - dtPolyRef* resultRef, dtPolyRef* resultParent, float* resultCost, - int* resultCount, const int maxResult) const -{ - dtAssert(m_nav); - dtAssert(m_nodePool); - dtAssert(m_openList); - - if (!resultCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *resultCount = 0; - - if (!m_nav->isValidPolyRef(startRef) || - !centerPos || !dtVisfinite(centerPos) || - radius < 0 || !dtMathIsfinite(radius) || - !filter || maxResult < 0) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - m_nodePool->clear(); - m_openList->clear(); - - dtNode* startNode = m_nodePool->getNode(startRef); - dtVcopy(startNode->pos, centerPos); - startNode->pidx = 0; - startNode->cost = 0; - startNode->total = 0; - startNode->id = startRef; - startNode->flags = DT_NODE_OPEN; - m_openList->push(startNode); - - dtStatus status = DT_SUCCESS; - - int n = 0; - - const float radiusSqr = dtSqr(radius); - - while (!m_openList->empty()) - { - dtNode* bestNode = m_openList->pop(); - bestNode->flags &= ~DT_NODE_OPEN; - bestNode->flags |= DT_NODE_CLOSED; - - // Get poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtPolyRef bestRef = bestNode->id; - const dtMeshTile* bestTile = 0; - const dtPoly* bestPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(bestRef, &bestTile, &bestPoly); - - // Get parent poly and tile. - dtPolyRef parentRef = 0; - const dtMeshTile* parentTile = 0; - const dtPoly* parentPoly = 0; - if (bestNode->pidx) - parentRef = m_nodePool->getNodeAtIdx(bestNode->pidx)->id; - if (parentRef) - m_nav->getTileAndPolyByRefUnsafe(parentRef, &parentTile, &parentPoly); - - if (n < maxResult) - { - if (resultRef) - resultRef[n] = bestRef; - if (resultParent) - resultParent[n] = parentRef; - if (resultCost) - resultCost[n] = bestNode->total; - ++n; - } - else - { - status |= DT_BUFFER_TOO_SMALL; - } - - for (unsigned int i = bestPoly->firstLink; i != DT_NULL_LINK; i = bestTile->links[i].next) - { - const dtLink* link = &bestTile->links[i]; - dtPolyRef neighbourRef = link->ref; - // Skip invalid neighbours and do not follow back to parent. - if (!neighbourRef || neighbourRef == parentRef) - continue; - - // Expand to neighbour - const dtMeshTile* neighbourTile = 0; - const dtPoly* neighbourPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(neighbourRef, &neighbourTile, &neighbourPoly); - - // Do not advance if the polygon is excluded by the filter. - if (!filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) - continue; - - // Find edge and calc distance to the edge. - float va[3], vb[3]; - if (!getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb)) - continue; - - // If the circle is not touching the next polygon, skip it. - float tseg; - float distSqr = dtDistancePtSegSqr2D(centerPos, va, vb, tseg); - if (distSqr > radiusSqr) - continue; - - dtNode* neighbourNode = m_nodePool->getNode(neighbourRef); - if (!neighbourNode) - { - status |= DT_OUT_OF_NODES; - continue; - } - - if (neighbourNode->flags & DT_NODE_CLOSED) - continue; - - // Cost - if (neighbourNode->flags == 0) - dtVlerp(neighbourNode->pos, va, vb, 0.5f); - - float cost = filter->getCost( - bestNode->pos, neighbourNode->pos, - parentRef, parentTile, parentPoly, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly); - - const float total = bestNode->total + cost; - - // The node is already in open list and the new result is worse, skip. - if ((neighbourNode->flags & DT_NODE_OPEN) && total >= neighbourNode->total) - continue; - - neighbourNode->id = neighbourRef; - neighbourNode->pidx = m_nodePool->getNodeIdx(bestNode); - neighbourNode->total = total; - - if (neighbourNode->flags & DT_NODE_OPEN) - { - m_openList->modify(neighbourNode); - } - else - { - neighbourNode->flags = DT_NODE_OPEN; - m_openList->push(neighbourNode); - } - } - } - - *resultCount = n; - - return status; -} - -/// @par -/// -/// The order of the result set is from least to highest cost. -/// -/// At least one result array must be provided. -/// -/// A common use case for this method is to perform Dijkstra searches. -/// Candidate polygons are found by searching the graph beginning at the start -/// polygon. -/// -/// The same intersection test restrictions that apply to findPolysAroundCircle() -/// method apply to this method. -/// -/// The 3D centroid of the search polygon is used as the start position for cost -/// calculations. -/// -/// Intersection tests occur in 2D. All polygons are projected onto the -/// xz-plane. So the y-values of the vertices do not effect intersection tests. -/// -/// If the result arrays are is too small to hold the entire result set, they will -/// be filled to capacity. -/// -dtStatus dtNavMeshQuery::findPolysAroundShape(dtPolyRef startRef, const float* verts, const int nverts, - const dtQueryFilter* filter, - dtPolyRef* resultRef, dtPolyRef* resultParent, float* resultCost, - int* resultCount, const int maxResult) const -{ - dtAssert(m_nav); - dtAssert(m_nodePool); - dtAssert(m_openList); - - if (!resultCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *resultCount = 0; - - if (!m_nav->isValidPolyRef(startRef) || - !verts || nverts < 3 || - !filter || maxResult < 0) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) - return DT_FAILURE | DT_INVALID_PARAM; - - m_nodePool->clear(); - m_openList->clear(); - - float centerPos[3] = {0,0,0}; - for (int i = 0; i < nverts; ++i) - dtVadd(centerPos,centerPos,&verts[i*3]); - dtVscale(centerPos,centerPos,1.0f/nverts); - - dtNode* startNode = m_nodePool->getNode(startRef); - dtVcopy(startNode->pos, centerPos); - startNode->pidx = 0; - startNode->cost = 0; - startNode->total = 0; - startNode->id = startRef; - startNode->flags = DT_NODE_OPEN; - m_openList->push(startNode); - - dtStatus status = DT_SUCCESS; - - int n = 0; - - while (!m_openList->empty()) - { - dtNode* bestNode = m_openList->pop(); - bestNode->flags &= ~DT_NODE_OPEN; - bestNode->flags |= DT_NODE_CLOSED; - - // Get poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtPolyRef bestRef = bestNode->id; - const dtMeshTile* bestTile = 0; - const dtPoly* bestPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(bestRef, &bestTile, &bestPoly); - - // Get parent poly and tile. - dtPolyRef parentRef = 0; - const dtMeshTile* parentTile = 0; - const dtPoly* parentPoly = 0; - if (bestNode->pidx) - parentRef = m_nodePool->getNodeAtIdx(bestNode->pidx)->id; - if (parentRef) - m_nav->getTileAndPolyByRefUnsafe(parentRef, &parentTile, &parentPoly); - - if (n < maxResult) - { - if (resultRef) - resultRef[n] = bestRef; - if (resultParent) - resultParent[n] = parentRef; - if (resultCost) - resultCost[n] = bestNode->total; - - ++n; - } - else - { - status |= DT_BUFFER_TOO_SMALL; - } - - for (unsigned int i = bestPoly->firstLink; i != DT_NULL_LINK; i = bestTile->links[i].next) - { - const dtLink* link = &bestTile->links[i]; - dtPolyRef neighbourRef = link->ref; - // Skip invalid neighbours and do not follow back to parent. - if (!neighbourRef || neighbourRef == parentRef) - continue; - - // Expand to neighbour - const dtMeshTile* neighbourTile = 0; - const dtPoly* neighbourPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(neighbourRef, &neighbourTile, &neighbourPoly); - - // Do not advance if the polygon is excluded by the filter. - if (!filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) - continue; - - // Find edge and calc distance to the edge. - float va[3], vb[3]; - if (!getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb)) - continue; - - // If the poly is not touching the edge to the next polygon, skip the connection it. - float tmin, tmax; - int segMin, segMax; - if (!dtIntersectSegmentPoly2D(va, vb, verts, nverts, tmin, tmax, segMin, segMax)) - continue; - if (tmin > 1.0f || tmax < 0.0f) - continue; - - dtNode* neighbourNode = m_nodePool->getNode(neighbourRef); - if (!neighbourNode) - { - status |= DT_OUT_OF_NODES; - continue; - } - - if (neighbourNode->flags & DT_NODE_CLOSED) - continue; - - // Cost - if (neighbourNode->flags == 0) - dtVlerp(neighbourNode->pos, va, vb, 0.5f); - - float cost = filter->getCost( - bestNode->pos, neighbourNode->pos, - parentRef, parentTile, parentPoly, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly); - - const float total = bestNode->total + cost; - - // The node is already in open list and the new result is worse, skip. - if ((neighbourNode->flags & DT_NODE_OPEN) && total >= neighbourNode->total) - continue; - - neighbourNode->id = neighbourRef; - neighbourNode->pidx = m_nodePool->getNodeIdx(bestNode); - neighbourNode->total = total; - - if (neighbourNode->flags & DT_NODE_OPEN) - { - m_openList->modify(neighbourNode); - } - else - { - neighbourNode->flags = DT_NODE_OPEN; - m_openList->push(neighbourNode); - } - } - } - - *resultCount = n; - - return status; -} - -dtStatus dtNavMeshQuery::getPathFromDijkstraSearch(dtPolyRef endRef, dtPolyRef* path, int* pathCount, int maxPath) const -{ - if (!m_nav->isValidPolyRef(endRef) || !path || !pathCount || maxPath < 0) - return DT_FAILURE | DT_INVALID_PARAM; - - *pathCount = 0; - - dtNode* endNode; - if (m_nodePool->findNodes(endRef, &endNode, 1) != 1 || - (endNode->flags & DT_NODE_CLOSED) == 0) - return DT_FAILURE | DT_INVALID_PARAM; - - return getPathToNode(endNode, path, pathCount, maxPath); -} - -/// @par -/// -/// This method is optimized for a small search radius and small number of result -/// polygons. -/// -/// Candidate polygons are found by searching the navigation graph beginning at -/// the start polygon. -/// -/// The same intersection test restrictions that apply to the findPolysAroundCircle -/// mehtod applies to this method. -/// -/// The value of the center point is used as the start point for cost calculations. -/// It is not projected onto the surface of the mesh, so its y-value will effect -/// the costs. -/// -/// Intersection tests occur in 2D. All polygons and the search circle are -/// projected onto the xz-plane. So the y-value of the center point does not -/// effect intersection tests. -/// -/// If the result arrays are is too small to hold the entire result set, they will -/// be filled to capacity. -/// -dtStatus dtNavMeshQuery::findLocalNeighbourhood(dtPolyRef startRef, const float* centerPos, const float radius, - const dtQueryFilter* filter, - dtPolyRef* resultRef, dtPolyRef* resultParent, - int* resultCount, const int maxResult) const -{ - dtAssert(m_nav); - dtAssert(m_tinyNodePool); - - if (!resultCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *resultCount = 0; - - if (!m_nav->isValidPolyRef(startRef) || - !centerPos || !dtVisfinite(centerPos) || - radius < 0 || !dtMathIsfinite(radius) || - !filter || maxResult < 0) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - static const int MAX_STACK = 48; - dtNode* stack[MAX_STACK]; - int nstack = 0; - - m_tinyNodePool->clear(); - - dtNode* startNode = m_tinyNodePool->getNode(startRef); - startNode->pidx = 0; - startNode->id = startRef; - startNode->flags = DT_NODE_CLOSED; - stack[nstack++] = startNode; - - const float radiusSqr = dtSqr(radius); - - float pa[DT_VERTS_PER_POLYGON*3]; - float pb[DT_VERTS_PER_POLYGON*3]; - - dtStatus status = DT_SUCCESS; - - int n = 0; - if (n < maxResult) - { - resultRef[n] = startNode->id; - if (resultParent) - resultParent[n] = 0; - ++n; - } - else - { - status |= DT_BUFFER_TOO_SMALL; - } - - while (nstack) - { - // Pop front. - dtNode* curNode = stack[0]; - for (int i = 0; i < nstack-1; ++i) - stack[i] = stack[i+1]; - nstack--; - - // Get poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtPolyRef curRef = curNode->id; - const dtMeshTile* curTile = 0; - const dtPoly* curPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(curRef, &curTile, &curPoly); - - for (unsigned int i = curPoly->firstLink; i != DT_NULL_LINK; i = curTile->links[i].next) - { - const dtLink* link = &curTile->links[i]; - dtPolyRef neighbourRef = link->ref; - // Skip invalid neighbours. - if (!neighbourRef) - continue; - - // Skip if cannot alloca more nodes. - dtNode* neighbourNode = m_tinyNodePool->getNode(neighbourRef); - if (!neighbourNode) - continue; - // Skip visited. - if (neighbourNode->flags & DT_NODE_CLOSED) - continue; - - // Expand to neighbour - const dtMeshTile* neighbourTile = 0; - const dtPoly* neighbourPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(neighbourRef, &neighbourTile, &neighbourPoly); - - // Skip off-mesh connections. - if (neighbourPoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - continue; - - // Do not advance if the polygon is excluded by the filter. - if (!filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) - continue; - - // Find edge and calc distance to the edge. - float va[3], vb[3]; - if (!getPortalPoints(curRef, curPoly, curTile, neighbourRef, neighbourPoly, neighbourTile, va, vb)) - continue; - - // If the circle is not touching the next polygon, skip it. - float tseg; - float distSqr = dtDistancePtSegSqr2D(centerPos, va, vb, tseg); - if (distSqr > radiusSqr) - continue; - - // Mark node visited, this is done before the overlap test so that - // we will not visit the poly again if the test fails. - neighbourNode->flags |= DT_NODE_CLOSED; - neighbourNode->pidx = m_tinyNodePool->getNodeIdx(curNode); - - // Check that the polygon does not collide with existing polygons. - - // Collect vertices of the neighbour poly. - const int npa = neighbourPoly->vertCount; - for (int k = 0; k < npa; ++k) - dtVcopy(&pa[k*3], &neighbourTile->verts[neighbourPoly->verts[k]*3]); - - bool overlap = false; - for (int j = 0; j < n; ++j) - { - dtPolyRef pastRef = resultRef[j]; - - // Connected polys do not overlap. - bool connected = false; - for (unsigned int k = curPoly->firstLink; k != DT_NULL_LINK; k = curTile->links[k].next) - { - if (curTile->links[k].ref == pastRef) - { - connected = true; - break; - } - } - if (connected) - continue; - - // Potentially overlapping. - const dtMeshTile* pastTile = 0; - const dtPoly* pastPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(pastRef, &pastTile, &pastPoly); - - // Get vertices and test overlap - const int npb = pastPoly->vertCount; - for (int k = 0; k < npb; ++k) - dtVcopy(&pb[k*3], &pastTile->verts[pastPoly->verts[k]*3]); - - if (dtOverlapPolyPoly2D(pa,npa, pb,npb)) - { - overlap = true; - break; - } - } - if (overlap) - continue; - - // This poly is fine, store and advance to the poly. - if (n < maxResult) - { - resultRef[n] = neighbourRef; - if (resultParent) - resultParent[n] = curRef; - ++n; - } - else - { - status |= DT_BUFFER_TOO_SMALL; - } - - if (nstack < MAX_STACK) - { - stack[nstack++] = neighbourNode; - } - } - } - - *resultCount = n; - - return status; -} - - -struct dtSegInterval -{ - dtPolyRef ref; - short tmin, tmax; -}; - -static void insertInterval(dtSegInterval* ints, int& nints, const int maxInts, - const short tmin, const short tmax, const dtPolyRef ref) -{ - if (nints+1 > maxInts) return; - // Find insertion point. - int idx = 0; - while (idx < nints) - { - if (tmax <= ints[idx].tmin) - break; - idx++; - } - // Move current results. - if (nints-idx) - memmove(ints+idx+1, ints+idx, sizeof(dtSegInterval)*(nints-idx)); - // Store - ints[idx].ref = ref; - ints[idx].tmin = tmin; - ints[idx].tmax = tmax; - nints++; -} - -/// @par -/// -/// If the @p segmentRefs parameter is provided, then all polygon segments will be returned. -/// Otherwise only the wall segments are returned. -/// -/// A segment that is normally a portal will be included in the result set as a -/// wall if the @p filter results in the neighbor polygon becoomming impassable. -/// -/// The @p segmentVerts and @p segmentRefs buffers should normally be sized for the -/// maximum segments per polygon of the source navigation mesh. -/// -dtStatus dtNavMeshQuery::getPolyWallSegments(dtPolyRef ref, const dtQueryFilter* filter, - float* segmentVerts, dtPolyRef* segmentRefs, int* segmentCount, - const int maxSegments) const -{ - dtAssert(m_nav); - - if (!segmentCount) - return DT_FAILURE | DT_INVALID_PARAM; - - *segmentCount = 0; - - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) - return DT_FAILURE | DT_INVALID_PARAM; - - if (!filter || !segmentVerts || maxSegments < 0) - return DT_FAILURE | DT_INVALID_PARAM; - - int n = 0; - static const int MAX_INTERVAL = 16; - dtSegInterval ints[MAX_INTERVAL]; - int nints; - - const bool storePortals = segmentRefs != 0; - - dtStatus status = DT_SUCCESS; - - for (int i = 0, j = (int)poly->vertCount-1; i < (int)poly->vertCount; j = i++) - { - // Skip non-solid edges. - nints = 0; - if (poly->neis[j] & DT_EXT_LINK) - { - // Tile border. - for (unsigned int k = poly->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) - { - const dtLink* link = &tile->links[k]; - if (link->edge == j) - { - if (link->ref != 0) - { - const dtMeshTile* neiTile = 0; - const dtPoly* neiPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(link->ref, &neiTile, &neiPoly); - if (filter->passFilter(link->ref, neiTile, neiPoly)) - { - insertInterval(ints, nints, MAX_INTERVAL, link->bmin, link->bmax, link->ref); - } - } - } - } - } - else - { - // Internal edge - dtPolyRef neiRef = 0; - if (poly->neis[j]) - { - const unsigned int idx = (unsigned int)(poly->neis[j]-1); - neiRef = m_nav->getPolyRefBase(tile) | idx; - if (!filter->passFilter(neiRef, tile, &tile->polys[idx])) - neiRef = 0; - } - - // If the edge leads to another polygon and portals are not stored, skip. - if (neiRef != 0 && !storePortals) - continue; - - if (n < maxSegments) - { - const float* vj = &tile->verts[poly->verts[j]*3]; - const float* vi = &tile->verts[poly->verts[i]*3]; - float* seg = &segmentVerts[n*6]; - dtVcopy(seg+0, vj); - dtVcopy(seg+3, vi); - if (segmentRefs) - segmentRefs[n] = neiRef; - n++; - } - else - { - status |= DT_BUFFER_TOO_SMALL; - } - - continue; - } - - // Add sentinels - insertInterval(ints, nints, MAX_INTERVAL, -1, 0, 0); - insertInterval(ints, nints, MAX_INTERVAL, 255, 256, 0); - - // Store segments. - const float* vj = &tile->verts[poly->verts[j]*3]; - const float* vi = &tile->verts[poly->verts[i]*3]; - for (int k = 1; k < nints; ++k) - { - // Portal segment. - if (storePortals && ints[k].ref) - { - const float tmin = ints[k].tmin/255.0f; - const float tmax = ints[k].tmax/255.0f; - if (n < maxSegments) - { - float* seg = &segmentVerts[n*6]; - dtVlerp(seg+0, vj,vi, tmin); - dtVlerp(seg+3, vj,vi, tmax); - if (segmentRefs) - segmentRefs[n] = ints[k].ref; - n++; - } - else - { - status |= DT_BUFFER_TOO_SMALL; - } - } - - // Wall segment. - const int imin = ints[k-1].tmax; - const int imax = ints[k].tmin; - if (imin != imax) - { - const float tmin = imin/255.0f; - const float tmax = imax/255.0f; - if (n < maxSegments) - { - float* seg = &segmentVerts[n*6]; - dtVlerp(seg+0, vj,vi, tmin); - dtVlerp(seg+3, vj,vi, tmax); - if (segmentRefs) - segmentRefs[n] = 0; - n++; - } - else - { - status |= DT_BUFFER_TOO_SMALL; - } - } - } - } - - *segmentCount = n; - - return status; -} - -/// @par -/// -/// @p hitPos is not adjusted using the height detail data. -/// -/// @p hitDist will equal the search radius if there is no wall within the -/// radius. In this case the values of @p hitPos and @p hitNormal are -/// undefined. -/// -/// The normal will become unpredicable if @p hitDist is a very small number. -/// -dtStatus dtNavMeshQuery::findDistanceToWall(dtPolyRef startRef, const float* centerPos, const float maxRadius, - const dtQueryFilter* filter, - float* hitDist, float* hitPos, float* hitNormal) const -{ - dtAssert(m_nav); - dtAssert(m_nodePool); - dtAssert(m_openList); - - // Validate input - if (!m_nav->isValidPolyRef(startRef) || - !centerPos || !dtVisfinite(centerPos) || - maxRadius < 0 || !dtMathIsfinite(maxRadius) || - !filter || !hitDist || !hitPos || !hitNormal) - { - return DT_FAILURE | DT_INVALID_PARAM; - } - - m_nodePool->clear(); - m_openList->clear(); - - dtNode* startNode = m_nodePool->getNode(startRef); - dtVcopy(startNode->pos, centerPos); - startNode->pidx = 0; - startNode->cost = 0; - startNode->total = 0; - startNode->id = startRef; - startNode->flags = DT_NODE_OPEN; - m_openList->push(startNode); - - float radiusSqr = dtSqr(maxRadius); - - dtStatus status = DT_SUCCESS; - - while (!m_openList->empty()) - { - dtNode* bestNode = m_openList->pop(); - bestNode->flags &= ~DT_NODE_OPEN; - bestNode->flags |= DT_NODE_CLOSED; - - // Get poly and tile. - // The API input has been cheked already, skip checking internal data. - const dtPolyRef bestRef = bestNode->id; - const dtMeshTile* bestTile = 0; - const dtPoly* bestPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(bestRef, &bestTile, &bestPoly); - - // Get parent poly and tile. - dtPolyRef parentRef = 0; - const dtMeshTile* parentTile = 0; - const dtPoly* parentPoly = 0; - if (bestNode->pidx) - parentRef = m_nodePool->getNodeAtIdx(bestNode->pidx)->id; - if (parentRef) - m_nav->getTileAndPolyByRefUnsafe(parentRef, &parentTile, &parentPoly); - - // Hit test walls. - for (int i = 0, j = (int)bestPoly->vertCount-1; i < (int)bestPoly->vertCount; j = i++) - { - // Skip non-solid edges. - if (bestPoly->neis[j] & DT_EXT_LINK) - { - // Tile border. - bool solid = true; - for (unsigned int k = bestPoly->firstLink; k != DT_NULL_LINK; k = bestTile->links[k].next) - { - const dtLink* link = &bestTile->links[k]; - if (link->edge == j) - { - if (link->ref != 0) - { - const dtMeshTile* neiTile = 0; - const dtPoly* neiPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(link->ref, &neiTile, &neiPoly); - if (filter->passFilter(link->ref, neiTile, neiPoly)) - solid = false; - } - break; - } - } - if (!solid) continue; - } - else if (bestPoly->neis[j]) - { - // Internal edge - const unsigned int idx = (unsigned int)(bestPoly->neis[j]-1); - const dtPolyRef ref = m_nav->getPolyRefBase(bestTile) | idx; - if (filter->passFilter(ref, bestTile, &bestTile->polys[idx])) - continue; - } - - // Calc distance to the edge. - const float* vj = &bestTile->verts[bestPoly->verts[j]*3]; - const float* vi = &bestTile->verts[bestPoly->verts[i]*3]; - float tseg; - float distSqr = dtDistancePtSegSqr2D(centerPos, vj, vi, tseg); - - // Edge is too far, skip. - if (distSqr > radiusSqr) - continue; - - // Hit wall, update radius. - radiusSqr = distSqr; - // Calculate hit pos. - hitPos[0] = vj[0] + (vi[0] - vj[0])*tseg; - hitPos[1] = vj[1] + (vi[1] - vj[1])*tseg; - hitPos[2] = vj[2] + (vi[2] - vj[2])*tseg; - } - - for (unsigned int i = bestPoly->firstLink; i != DT_NULL_LINK; i = bestTile->links[i].next) - { - const dtLink* link = &bestTile->links[i]; - dtPolyRef neighbourRef = link->ref; - // Skip invalid neighbours and do not follow back to parent. - if (!neighbourRef || neighbourRef == parentRef) - continue; - - // Expand to neighbour. - const dtMeshTile* neighbourTile = 0; - const dtPoly* neighbourPoly = 0; - m_nav->getTileAndPolyByRefUnsafe(neighbourRef, &neighbourTile, &neighbourPoly); - - // Skip off-mesh connections. - if (neighbourPoly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - continue; - - // Calc distance to the edge. - const float* va = &bestTile->verts[bestPoly->verts[link->edge]*3]; - const float* vb = &bestTile->verts[bestPoly->verts[(link->edge+1) % bestPoly->vertCount]*3]; - float tseg; - float distSqr = dtDistancePtSegSqr2D(centerPos, va, vb, tseg); - - // If the circle is not touching the next polygon, skip it. - if (distSqr > radiusSqr) - continue; - - if (!filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) - continue; - - dtNode* neighbourNode = m_nodePool->getNode(neighbourRef); - if (!neighbourNode) - { - status |= DT_OUT_OF_NODES; - continue; - } - - if (neighbourNode->flags & DT_NODE_CLOSED) - continue; - - // Cost - if (neighbourNode->flags == 0) - { - getEdgeMidPoint(bestRef, bestPoly, bestTile, - neighbourRef, neighbourPoly, neighbourTile, neighbourNode->pos); - } - - const float total = bestNode->total + dtVdist(bestNode->pos, neighbourNode->pos); - - // The node is already in open list and the new result is worse, skip. - if ((neighbourNode->flags & DT_NODE_OPEN) && total >= neighbourNode->total) - continue; - - neighbourNode->id = neighbourRef; - neighbourNode->flags = (neighbourNode->flags & ~DT_NODE_CLOSED); - neighbourNode->pidx = m_nodePool->getNodeIdx(bestNode); - neighbourNode->total = total; - - if (neighbourNode->flags & DT_NODE_OPEN) - { - m_openList->modify(neighbourNode); - } - else - { - neighbourNode->flags |= DT_NODE_OPEN; - m_openList->push(neighbourNode); - } - } - } - - // Calc hit normal. - dtVsub(hitNormal, centerPos, hitPos); - dtVnormalize(hitNormal); - - *hitDist = dtMathSqrtf(radiusSqr); - - return status; -} - -bool dtNavMeshQuery::isValidPolyRef(dtPolyRef ref, const dtQueryFilter* filter) const -{ - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - dtStatus status = m_nav->getTileAndPolyByRef(ref, &tile, &poly); - // If cannot get polygon, assume it does not exists and boundary is invalid. - if (dtStatusFailed(status)) - return false; - // If cannot pass filter, assume flags has changed and boundary is invalid. - if (!filter->passFilter(ref, tile, poly)) - return false; - return true; -} - -/// @par -/// -/// The closed list is the list of polygons that were fully evaluated during -/// the last navigation graph search. (A* or Dijkstra) -/// -bool dtNavMeshQuery::isInClosedList(dtPolyRef ref) const -{ - if (!m_nodePool) return false; - - dtNode* nodes[DT_MAX_STATES_PER_NODE]; - int n= m_nodePool->findNodes(ref, nodes, DT_MAX_STATES_PER_NODE); - - for (int i=0; iflags & DT_NODE_CLOSED) - return true; - } - - return false; -} diff --git a/extern/recastnavigation/Detour/Source/DetourNode.cpp b/extern/recastnavigation/Detour/Source/DetourNode.cpp deleted file mode 100644 index 48abbba6b5..0000000000 --- a/extern/recastnavigation/Detour/Source/DetourNode.cpp +++ /dev/null @@ -1,200 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include "DetourNode.h" -#include "DetourAlloc.h" -#include "DetourAssert.h" -#include "DetourCommon.h" -#include - -#ifdef DT_POLYREF64 -// From Thomas Wang, https://gist.github.com/badboy/6267743 -inline unsigned int dtHashRef(dtPolyRef a) -{ - a = (~a) + (a << 18); // a = (a << 18) - a - 1; - a = a ^ (a >> 31); - a = a * 21; // a = (a + (a << 2)) + (a << 4); - a = a ^ (a >> 11); - a = a + (a << 6); - a = a ^ (a >> 22); - return (unsigned int)a; -} -#else -inline unsigned int dtHashRef(dtPolyRef a) -{ - a += ~(a<<15); - a ^= (a>>10); - a += (a<<3); - a ^= (a>>6); - a += ~(a<<11); - a ^= (a>>16); - return (unsigned int)a; -} -#endif - -////////////////////////////////////////////////////////////////////////////////////////// -dtNodePool::dtNodePool(int maxNodes, int hashSize) : - m_nodes(0), - m_first(0), - m_next(0), - m_maxNodes(maxNodes), - m_hashSize(hashSize), - m_nodeCount(0) -{ - dtAssert(dtNextPow2(m_hashSize) == (unsigned int)m_hashSize); - // pidx is special as 0 means "none" and 1 is the first node. For that reason - // we have 1 fewer nodes available than the number of values it can contain. - dtAssert(m_maxNodes > 0 && m_maxNodes <= DT_NULL_IDX && m_maxNodes <= (1 << DT_NODE_PARENT_BITS) - 1); - - m_nodes = (dtNode*)dtAlloc(sizeof(dtNode)*m_maxNodes, DT_ALLOC_PERM); - m_next = (dtNodeIndex*)dtAlloc(sizeof(dtNodeIndex)*m_maxNodes, DT_ALLOC_PERM); - m_first = (dtNodeIndex*)dtAlloc(sizeof(dtNodeIndex)*hashSize, DT_ALLOC_PERM); - - dtAssert(m_nodes); - dtAssert(m_next); - dtAssert(m_first); - - memset(m_first, 0xff, sizeof(dtNodeIndex)*m_hashSize); - memset(m_next, 0xff, sizeof(dtNodeIndex)*m_maxNodes); -} - -dtNodePool::~dtNodePool() -{ - dtFree(m_nodes); - dtFree(m_next); - dtFree(m_first); -} - -void dtNodePool::clear() -{ - memset(m_first, 0xff, sizeof(dtNodeIndex)*m_hashSize); - m_nodeCount = 0; -} - -unsigned int dtNodePool::findNodes(dtPolyRef id, dtNode** nodes, const int maxNodes) -{ - int n = 0; - unsigned int bucket = dtHashRef(id) & (m_hashSize-1); - dtNodeIndex i = m_first[bucket]; - while (i != DT_NULL_IDX) - { - if (m_nodes[i].id == id) - { - if (n >= maxNodes) - return n; - nodes[n++] = &m_nodes[i]; - } - i = m_next[i]; - } - - return n; -} - -dtNode* dtNodePool::findNode(dtPolyRef id, unsigned char state) -{ - unsigned int bucket = dtHashRef(id) & (m_hashSize-1); - dtNodeIndex i = m_first[bucket]; - while (i != DT_NULL_IDX) - { - if (m_nodes[i].id == id && m_nodes[i].state == state) - return &m_nodes[i]; - i = m_next[i]; - } - return 0; -} - -dtNode* dtNodePool::getNode(dtPolyRef id, unsigned char state) -{ - unsigned int bucket = dtHashRef(id) & (m_hashSize-1); - dtNodeIndex i = m_first[bucket]; - dtNode* node = 0; - while (i != DT_NULL_IDX) - { - if (m_nodes[i].id == id && m_nodes[i].state == state) - return &m_nodes[i]; - i = m_next[i]; - } - - if (m_nodeCount >= m_maxNodes) - return 0; - - i = (dtNodeIndex)m_nodeCount; - m_nodeCount++; - - // Init node - node = &m_nodes[i]; - node->pidx = 0; - node->cost = 0; - node->total = 0; - node->id = id; - node->state = state; - node->flags = 0; - - m_next[i] = m_first[bucket]; - m_first[bucket] = i; - - return node; -} - - -////////////////////////////////////////////////////////////////////////////////////////// -dtNodeQueue::dtNodeQueue(int n) : - m_heap(0), - m_capacity(n), - m_size(0) -{ - dtAssert(m_capacity > 0); - - m_heap = (dtNode**)dtAlloc(sizeof(dtNode*)*(m_capacity+1), DT_ALLOC_PERM); - dtAssert(m_heap); -} - -dtNodeQueue::~dtNodeQueue() -{ - dtFree(m_heap); -} - -void dtNodeQueue::bubbleUp(int i, dtNode* node) -{ - int parent = (i-1)/2; - // note: (index > 0) means there is a parent - while ((i > 0) && (m_heap[parent]->total > node->total)) - { - m_heap[i] = m_heap[parent]; - i = parent; - parent = (i-1)/2; - } - m_heap[i] = node; -} - -void dtNodeQueue::trickleDown(int i, dtNode* node) -{ - int child = (i*2)+1; - while (child < m_size) - { - if (((child+1) < m_size) && - (m_heap[child]->total > m_heap[child+1]->total)) - { - child++; - } - m_heap[i] = m_heap[child]; - i = child; - child = (i*2)+1; - } - bubbleUp(i, node); -} diff --git a/extern/recastnavigation/DetourCrowd/CMakeLists.txt b/extern/recastnavigation/DetourCrowd/CMakeLists.txt deleted file mode 100644 index d0e186be07..0000000000 --- a/extern/recastnavigation/DetourCrowd/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -file(GLOB SOURCES Source/*.cpp) -add_library(DetourCrowd ${SOURCES}) - -add_library(RecastNavigation::DetourCrowd ALIAS DetourCrowd) -set_target_properties(DetourCrowd PROPERTIES DEBUG_POSTFIX -d) - -set(DetourCrowd_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") - -target_include_directories(DetourCrowd PUBLIC - "$" -) - -target_link_libraries(DetourCrowd - Detour -) - -set_target_properties(DetourCrowd PROPERTIES - SOVERSION ${SOVERSION} - VERSION ${VERSION} - COMPILE_PDB_OUTPUT_DIRECTORY . - COMPILE_PDB_NAME "DetourCrowd-d" - ) - -install(TARGETS DetourCrowd - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT library - ) - -file(GLOB INCLUDES Include/*.h) -install(FILES ${INCLUDES} DESTINATION - ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation) -install(FILES "$/DetourCrowd-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib") diff --git a/extern/recastnavigation/DetourCrowd/Include/DetourCrowd.h b/extern/recastnavigation/DetourCrowd/Include/DetourCrowd.h deleted file mode 100644 index 952050878a..0000000000 --- a/extern/recastnavigation/DetourCrowd/Include/DetourCrowd.h +++ /dev/null @@ -1,460 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef DETOURCROWD_H -#define DETOURCROWD_H - -#include "DetourNavMeshQuery.h" -#include "DetourObstacleAvoidance.h" -#include "DetourLocalBoundary.h" -#include "DetourPathCorridor.h" -#include "DetourProximityGrid.h" -#include "DetourPathQueue.h" - -/// The maximum number of neighbors that a crowd agent can take into account -/// for steering decisions. -/// @ingroup crowd -static const int DT_CROWDAGENT_MAX_NEIGHBOURS = 6; - -/// The maximum number of corners a crowd agent will look ahead in the path. -/// This value is used for sizing the crowd agent corner buffers. -/// Due to the behavior of the crowd manager, the actual number of useful -/// corners will be one less than this number. -/// @ingroup crowd -static const int DT_CROWDAGENT_MAX_CORNERS = 4; - -/// The maximum number of crowd avoidance configurations supported by the -/// crowd manager. -/// @ingroup crowd -/// @see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(), dtCrowd::getObstacleAvoidanceParams(), -/// dtCrowdAgentParams::obstacleAvoidanceType -static const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8; - -/// The maximum number of query filter types supported by the crowd manager. -/// @ingroup crowd -/// @see dtQueryFilter, dtCrowd::getFilter() dtCrowd::getEditableFilter(), -/// dtCrowdAgentParams::queryFilterType -static const int DT_CROWD_MAX_QUERY_FILTER_TYPE = 16; - -/// Provides neighbor data for agents managed by the crowd. -/// @ingroup crowd -/// @see dtCrowdAgent::neis, dtCrowd -struct dtCrowdNeighbour -{ - int idx; ///< The index of the neighbor in the crowd. - float dist; ///< The distance between the current agent and the neighbor. -}; - -/// The type of navigation mesh polygon the agent is currently traversing. -/// @ingroup crowd -enum CrowdAgentState -{ - DT_CROWDAGENT_STATE_INVALID, ///< The agent is not in a valid state. - DT_CROWDAGENT_STATE_WALKING, ///< The agent is traversing a normal navigation mesh polygon. - DT_CROWDAGENT_STATE_OFFMESH, ///< The agent is traversing an off-mesh connection. -}; - -/// Configuration parameters for a crowd agent. -/// @ingroup crowd -struct dtCrowdAgentParams -{ - float radius; ///< Agent radius. [Limit: >= 0] - float height; ///< Agent height. [Limit: > 0] - float maxAcceleration; ///< Maximum allowed acceleration. [Limit: >= 0] - float maxSpeed; ///< Maximum allowed speed. [Limit: >= 0] - - /// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0] - float collisionQueryRange; - - float pathOptimizationRange; ///< The path visibility optimization range. [Limit: > 0] - - /// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0] - float separationWeight; - - /// Flags that impact steering behavior. (See: #UpdateFlags) - unsigned char updateFlags; - - /// The index of the avoidance configuration to use for the agent. - /// [Limits: 0 <= value <= #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS] - unsigned char obstacleAvoidanceType; - - /// The index of the query filter used by this agent. - unsigned char queryFilterType; - - /// User defined data attached to the agent. - void* userData; -}; - -enum MoveRequestState -{ - DT_CROWDAGENT_TARGET_NONE = 0, - DT_CROWDAGENT_TARGET_FAILED, - DT_CROWDAGENT_TARGET_VALID, - DT_CROWDAGENT_TARGET_REQUESTING, - DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE, - DT_CROWDAGENT_TARGET_WAITING_FOR_PATH, - DT_CROWDAGENT_TARGET_VELOCITY, -}; - -/// Represents an agent managed by a #dtCrowd object. -/// @ingroup crowd -struct dtCrowdAgent -{ - /// True if the agent is active, false if the agent is in an unused slot in the agent pool. - bool active; - - /// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState) - unsigned char state; - - /// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the requested position, else false. - bool partial; - - /// The path corridor the agent is using. - dtPathCorridor corridor; - - /// The local boundary data for the agent. - dtLocalBoundary boundary; - - /// Time since the agent's path corridor was optimized. - float topologyOptTime; - - /// The known neighbors of the agent. - dtCrowdNeighbour neis[DT_CROWDAGENT_MAX_NEIGHBOURS]; - - /// The number of neighbors. - int nneis; - - /// The desired speed. - float desiredSpeed; - - float npos[3]; ///< The current agent position. [(x, y, z)] - float disp[3]; ///< A temporary value used to accumulate agent displacement during iterative collision resolution. [(x, y, z)] - float dvel[3]; ///< The desired velocity of the agent. Based on the current path, calculated from scratch each frame. [(x, y, z)] - float nvel[3]; ///< The desired velocity adjusted by obstacle avoidance, calculated from scratch each frame. [(x, y, z)] - float vel[3]; ///< The actual velocity of the agent. The change from nvel -> vel is constrained by max acceleration. [(x, y, z)] - - /// The agent's configuration parameters. - dtCrowdAgentParams params; - - /// The local path corridor corners for the agent. (Staight path.) [(x, y, z) * #ncorners] - float cornerVerts[DT_CROWDAGENT_MAX_CORNERS*3]; - - /// The local path corridor corner flags. (See: #dtStraightPathFlags) [(flags) * #ncorners] - unsigned char cornerFlags[DT_CROWDAGENT_MAX_CORNERS]; - - /// The reference id of the polygon being entered at the corner. [(polyRef) * #ncorners] - dtPolyRef cornerPolys[DT_CROWDAGENT_MAX_CORNERS]; - - /// The number of corners. - int ncorners; - - unsigned char targetState; ///< State of the movement request. - dtPolyRef targetRef; ///< Target polyref of the movement request. - float targetPos[3]; ///< Target position of the movement request (or velocity in case of DT_CROWDAGENT_TARGET_VELOCITY). - dtPathQueueRef targetPathqRef; ///< Path finder ref. - bool targetReplan; ///< Flag indicating that the current path is being replanned. - float targetReplanTime; ///
+ + + Audio + + + + + + + + Audio Device + + + Select your preferred audio device. + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 0 + + + + Default + + + + + + + + + + + + HRTF + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 0 + + + + Automatic + + + + + Off + + + + + On + + + + + + + + + + + + HRTF Profile + + + Select your preferred HRTF profile. + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 0 + + + + Default + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + Camera From 6e1c67a9aea864df8dff68ecb0fa3eb5c73c8ed0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 9 Apr 2021 19:11:26 +0200 Subject: [PATCH 0534/2859] Account for waterwalking when updating position. Otherwise we might trace down the actor at waterlevel at the wrong coordinate. Triggered by multimark mod with waterwalking effect. --- apps/openmw/mwphysics/mtphysics.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 47 +++++++++++++------------ apps/openmw/mwphysics/physicssystem.hpp | 4 +-- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 4be8b2396f..4957ef422a 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -317,7 +317,7 @@ namespace MWPhysics // init for (auto& data : actorsData) - data.updatePosition(); + data.updatePosition(mCollisionWorld); mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ac8dd92f86..4087ba7e16 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -60,6 +60,22 @@ #include "movementsolver.hpp" #include "mtphysics.hpp" +namespace +{ + bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world) + { + if (!physicActor) + return false; + const float halfZ = physicActor->getHalfExtents().z(); + const osg::Vec3f actorPosition = physicActor->getPosition(); + const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); + const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); + MWPhysics::ActorTracer tracer; + tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); + return (tracer.mFraction >= 1.0f); + } +} + namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) @@ -347,16 +363,7 @@ namespace MWPhysics bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { - const Actor* physicActor = getActor(actor); - if (!physicActor) - return false; - const float halfZ = physicActor->getHalfExtents().z(); - const osg::Vec3f actorPosition = physicActor->getPosition(); - const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); - const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); - ActorTracer tracer; - tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get()); - return (tracer.mFraction >= 1.0f); + return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get()); } osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const @@ -772,16 +779,10 @@ namespace MWPhysics const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects(); bool waterCollision = false; - bool moveToWaterSurface = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) - waterCollision = true; - else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel)) - { - moveToWaterSurface = true; + if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) waterCollision = true; - } } physicActor->setCanWaterWalk(waterCollision); @@ -794,7 +795,7 @@ namespace MWPhysics if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); - actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); + actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel); } mMovementQueue.clear(); return actorsFrameData; @@ -937,9 +938,9 @@ namespace MWPhysics } ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, - bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) + bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel) : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), + mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() { const MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -953,7 +954,7 @@ namespace MWPhysics mWasOnGround = actor->getOnGround(); } - void ActorFrameData::updatePosition() + void ActorFrameData::updatePosition(btCollisionWorld* world) { mActorRaw->updateWorldPosition(); // If physics runs "fast enough", position are interpolated without simulation @@ -961,10 +962,10 @@ namespace MWPhysics // regardless of simulation speed. mActorRaw->applyOffsetChange(); mPosition = mActorRaw->getPosition(); - if (mMoveToWaterSurface) + if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) { mPosition.z() = mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z()); + MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false); } mOldHeight = mPosition.z(); mRefpos = mActorRaw->getPtr().getRefData().getPosition(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 5ccbf1a380..ce10b4246c 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -79,7 +79,7 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); - void updatePosition(); + void updatePosition(btCollisionWorld* world); std::weak_ptr mActor; Actor* mActorRaw; MWWorld::Ptr mStandingOn; @@ -90,7 +90,7 @@ namespace MWPhysics bool mDidJump; bool mFloatToSurface; bool mNeedLand; - bool mMoveToWaterSurface; + bool mWaterCollision; float mWaterlevel; float mSlowFall; float mOldHeight; From 8874a5be22710c06b26f25c83051dddf373d6f67 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 9 Apr 2021 23:06:36 +0200 Subject: [PATCH 0535/2859] Change (again) the way SetPos behave. Instead of registering the desired change of position and rely on physics simulation to apply it to the world, immediately change the position in the world without reset the simulation. --- apps/openmw/mwbase/world.hpp | 4 +-- apps/openmw/mwphysics/actor.cpp | 12 +------ .../mwscript/transformationextensions.cpp | 21 +++++++++--- apps/openmw/mwworld/worldimp.cpp | 32 ++++--------------- apps/openmw/mwworld/worldimp.hpp | 7 ++-- 5 files changed, 27 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 3d55ad987b..6cee36b402 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -281,13 +281,13 @@ namespace MWBase virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; - virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0; + virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0; ///< @return an updated Ptr in case the Ptr's cell changes virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr - virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0; + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 776212ede9..f9adc9bc69 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include #include #include @@ -181,6 +179,7 @@ bool Actor::setPosition(const osg::Vec3f& position) if (mSkipSimulation) return false; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; + updateWorldPosition(); applyOffsetChange(); mPreviousPosition = mPosition; mPosition = position; @@ -197,15 +196,6 @@ void Actor::applyOffsetChange() { if (mPositionOffset.length() == 0) return; - if (mPositionOffset.z() != 0) - { - // Often, offset are set in sequence x, y, z - // We don't want actors to be moved under the ground - // Check terrain height at new coordinate and update z offset if necessary - const auto pos = mWorldPosition + mPositionOffset; - const auto terrainHeight = mPtr.getCell()->isExterior() ? MWBase::Environment::get().getWorld()->getTerrainHeightAt(pos) : -std::numeric_limits::max(); - mPositionOffset.z() = std::max(pos.z(), terrainHeight) - mWorldPosition.z(); - } mWorldPosition += mPositionOffset; mPosition += mPositionOffset; mPreviousPosition += mPositionOffset; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 6b92378c70..ba211503e1 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,7 +32,7 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff); + MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false); } template @@ -284,6 +284,17 @@ namespace MWScript } else if(axis == "z") { + // We should not place actors under ground + if (ptr.getClass().isActor()) + { + float terrainHeight = -std::numeric_limits::max(); + if (ptr.getCell()->isExterior()) + terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos); + + if (pos < terrainHeight) + pos = terrainHeight; + } + newPos[2] = pos; } else @@ -292,7 +303,7 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true)); } }; @@ -428,7 +439,7 @@ namespace MWScript } else { - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true); } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); @@ -715,7 +726,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); } }; @@ -751,7 +762,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3b50dd6aac..5b9afa4fe2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -862,19 +862,6 @@ namespace MWWorld if (reference == getPlayerPtr()) throw std::runtime_error("can not disable player object"); - // A common pattern to teleport NPC in scripts is a sequence of SetPos/Disable/Enable - // Disable/Enable create a new physics actor, and so the SetPos call is lost - // Call moveObject so that the newly created physics actor will have up-to-date position - if (reference.getClass().isActor()) - { - auto* physactor = mPhysics->getActor(reference); - if (physactor) - { - physactor->applyOffsetChange(); - const auto position = physactor->getSimulationPosition(); - moveObject(reference, position.x(), position.y(), position.z(), true); - } - } reference.getRefData().disable(); if (reference.getCellRef().getRefNum().hasContentFile()) @@ -1251,7 +1238,7 @@ namespace MWWorld return newPtr; } - MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) + MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) { int cellX, cellY; positionToIndex(x, y, cellX, cellY); @@ -1266,21 +1253,14 @@ namespace MWWorld return moveObject(ptr, cell, x, y, z, movePhysics); } - MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive) - { - return moveObjectImp(ptr, x, y, z, true, moveToActive); - } - - MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec) + MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) { auto* actor = mPhysics->getActor(ptr); if (actor) - { actor->adjustPosition(vec); - return ptr; - } + osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; - return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); + return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr()); } void World::scaleObject (const Ptr& ptr, float scale) @@ -1546,7 +1526,7 @@ namespace MWWorld auto* physactor = mPhysics->getActor(actor); assert(physactor); const auto position = physactor->getSimulationPosition(); - moveObjectImp(actor, position.x(), position.y(), position.z(), false); + moveObject(actor, position.x(), position.y(), position.z(), false, false); } } @@ -1556,7 +1536,7 @@ namespace MWWorld auto* physactor = mPhysics->getActor(*player); assert(physactor); const auto position = physactor->getSimulationPosition(); - moveObjectImp(*player, position.x(), position.y(), position.z(), false); + moveObject(*player, position.x(), position.y(), position.z(), false, false); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 929e035e9e..33b23e0653 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -138,9 +138,6 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags); - Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false); - ///< @return an updated Ptr in case the Ptr's cell changes - Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); @@ -376,13 +373,13 @@ namespace MWWorld void undeleteObject (const Ptr& ptr) override; - MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive=false) override; + MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) override; ///< @return an updated Ptr in case the Ptr's cell changes MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; ///< @return an updated Ptr - MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override; + MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) override; ///< @return an updated Ptr void scaleObject (const Ptr& ptr, float scale) override; From 8ff4f731fb6bcba2fa85ef0c742a4d2ad2bd74c1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 9 Apr 2021 23:08:51 +0000 Subject: [PATCH 0536/2859] Make Coverity happy about A2C Initialise member variable --- components/shader/shadervisitor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 612c9011d2..b0013538f0 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -50,6 +50,7 @@ namespace Shader , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mConvertAlphaTestToAlphaToCoverage(false) , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) From 93954a961c7511946e4b25b46c597eaeaa18cdc7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 09:30:58 +0400 Subject: [PATCH 0537/2859] Unlock mutex on return to avoid hang --- extern/osg-ffmpeg-videoplayer/videostate.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 7c4bddb01e..a35c845dbd 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -349,7 +349,10 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) vp->pts = pts; if (vp->set_dimensions(w, h) < 0) + { + this->pictq_mutex.unlock(); return -1; + } sws_scale(this->sws_context, pFrame->data, pFrame->linesize, 0, this->video_ctx->height, vp->rgbaFrame->data, vp->rgbaFrame->linesize); From 41c78a889a54ff0e13efba637f151fdf3c992857 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 09:35:31 +0400 Subject: [PATCH 0538/2859] Check for decompression error code --- components/bsa/compressedbsafile.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index aaeb5bffa3..0f3a26e159 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -381,8 +381,10 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) LZ4F_decompressionContext_t context = nullptr; LZ4F_createDecompressionContext(&context, LZ4F_VERSION); LZ4F_decompressOptions_t options = {}; - LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.get(), &size, &options); - LZ4F_errorCode_t errorCode = LZ4F_freeDecompressionContext(context); + LZ4F_errorCode_t errorCode = LZ4F_decompress(context, memoryStreamPtr->getRawData(), &uncompressedSize, buffer.get(), &size, &options); + if (LZ4F_isError(errorCode)) + fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode)); + errorCode = LZ4F_freeDecompressionContext(context); if (LZ4F_isError(errorCode)) fail("LZ4 decompression error (file " + mFilename + "): " + LZ4F_getErrorName(errorCode)); } From b96929f3fc94ad0a0ae54438cd4cf1541e606967 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 09:52:46 +0400 Subject: [PATCH 0539/2859] Avoid division by zero --- apps/launcher/graphicspage.cpp | 5 ++++- apps/openmw/mwgui/settingswindow.cpp | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 01205043ec..c6e74573c4 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -20,6 +20,9 @@ QString getAspect(int x, int y) { int gcd = std::gcd (x, y); + if (gcd == 0) + return QString(); + int xaspect = x / gcd; int yaspect = y / gcd; // special case: 8 : 5 is usually referred to as 16:10 @@ -298,9 +301,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) return result; } - QString aspect = getAspect(mode.w, mode.h); QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h); + QString aspect = getAspect(mode.w, mode.h); if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { resolution.append(tr("\t(Wide ") + aspect + ")"); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 538b3db5ed..6342433c47 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -61,6 +61,9 @@ namespace std::string getAspect (int x, int y) { int gcd = std::gcd (x, y); + if (gcd == 0) + return std::string(); + int xaspect = x / gcd; int yaspect = y / gcd; // special case: 8 : 5 is usually referred to as 16:10 @@ -249,8 +252,10 @@ namespace MWGui std::sort(resolutions.begin(), resolutions.end(), sortResolutions); for (std::pair& resolution : resolutions) { - std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second) - + " (" + getAspect(resolution.first, resolution.second) + ")"; + std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second); + std::string aspect = getAspect(resolution.first, resolution.second); + if (!aspect.empty()) + str = str + " (" + aspect + ")"; if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); From 124a33d8a314924c6df5095622ca1a597dfdf889 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 10:58:00 +0400 Subject: [PATCH 0540/2859] Fix uninitialized variables --- apps/opencs/model/world/refidadapterimp.cpp | 22 ++++++++-- apps/opencs/model/world/refidadapterimp.hpp | 43 +++++++++++++++++--- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/esm/loadtes3.cpp | 1 + components/misc/frameratelimiter.hpp | 1 + components/resource/scenemanager.cpp | 1 + components/sdlutil/sdlinputwrapper.cpp | 2 + components/shader/shadervisitor.cpp | 1 + extern/osg-ffmpeg-videoplayer/videostate.cpp | 5 ++- 9 files changed, 66 insertions(+), 11 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index d944adc23e..644092f164 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) -: InventoryColumns (columns) {} +: InventoryColumns (columns) +, mEffects(nullptr) +{} CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) : InventoryRefIdAdapter (UniversalId::Type_Ingredient, columns), @@ -585,7 +587,13 @@ void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& } CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns) -: InventoryColumns (columns) {} +: InventoryColumns (columns) +, mTime(nullptr) +, mRadius(nullptr) +, mColor(nullptr) +, mSound(nullptr) +, mEmitterType(nullptr) +{} CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns) : InventoryRefIdAdapter (UniversalId::Type_Light, columns), mColumns (columns) @@ -1454,7 +1462,15 @@ int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *co } CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) -: EnchantableColumns (columns) {} +: EnchantableColumns (columns) +, mType(nullptr) +, mHealth(nullptr) +, mSpeed(nullptr) +, mReach(nullptr) +, mChop{nullptr} +, mSlash{nullptr} +, mThrust{nullptr} +{} CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns) : EnchantableRefIdAdapter (UniversalId::Type_Weapon, columns), mColumns (columns) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 0a29afcad7..95d1a09a26 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -178,7 +178,11 @@ namespace CSMWorld const RefIdColumn *mName; const RefIdColumn *mScript; - NameColumns (const ModelColumns& base) : ModelColumns (base) {} + NameColumns (const ModelColumns& base) + : ModelColumns (base) + , mName(nullptr) + , mScript(nullptr) + {} }; /// \brief Adapter for IDs with names (all but levelled lists and statics) @@ -247,7 +251,12 @@ namespace CSMWorld const RefIdColumn *mWeight; const RefIdColumn *mValue; - InventoryColumns (const NameColumns& base) : NameColumns (base) {} + InventoryColumns (const NameColumns& base) + : NameColumns (base) + , mIcon(nullptr) + , mWeight(nullptr) + , mValue(nullptr) + {} }; /// \brief Adapter for IDs that can go into an inventory @@ -405,7 +414,11 @@ namespace CSMWorld const RefIdColumn *mEnchantment; const RefIdColumn *mEnchantmentPoints; - EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {} + EnchantableColumns (const InventoryColumns& base) + : InventoryColumns (base) + , mEnchantment(nullptr) + , mEnchantmentPoints(nullptr) + {} }; /// \brief Adapter for enchantable IDs @@ -474,7 +487,11 @@ namespace CSMWorld const RefIdColumn *mQuality; const RefIdColumn *mUses; - ToolColumns (const InventoryColumns& base) : InventoryColumns (base) {} + ToolColumns (const InventoryColumns& base) + : InventoryColumns (base) + , mQuality(nullptr) + , mUses(nullptr) + {} }; /// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes) @@ -549,7 +566,17 @@ namespace CSMWorld const RefIdColumn *mAiPackages; std::map mServices; - ActorColumns (const NameColumns& base) : NameColumns (base) {} + ActorColumns (const NameColumns& base) + : NameColumns (base) + , mHello(nullptr) + , mFlee(nullptr) + , mFight(nullptr) + , mAlarm(nullptr) + , mInventory(nullptr) + , mSpells(nullptr) + , mDestinations(nullptr) + , mAiPackages(nullptr) + {} }; /// \brief Adapter for actor IDs (handles common AI functionality) @@ -2054,7 +2081,11 @@ namespace CSMWorld const RefIdColumn *mLevList; const RefIdColumn *mNestedListLevList; - LevListColumns (const BaseColumns& base) : BaseColumns (base) {} + LevListColumns (const BaseColumns& base) + : BaseColumns (base) + , mLevList(nullptr) + , mNestedListLevList(nullptr) + {} }; template diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 331927155b..b6fe05d0f8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -815,6 +815,7 @@ namespace MWRender RenderingManager::RayResult result; result.mHit = false; result.mHitRefnum.mContentFile = -1; + result.mHitRefnum.mIndex = -1; result.mRatio = 0; if (intersector->containsIntersections()) { diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index d953f1dc23..84a31b3bd0 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -42,6 +42,7 @@ void ESM::Header::load (ESMReader &esm) MasterData m; m.name = esm.getHString(); m.size = esm.getHNLong ("DATA"); + m.index = -1; mMaster.push_back (m); } diff --git a/components/misc/frameratelimiter.hpp b/components/misc/frameratelimiter.hpp index b8e2101651..b727074d26 100644 --- a/components/misc/frameratelimiter.hpp +++ b/components/misc/frameratelimiter.hpp @@ -13,6 +13,7 @@ namespace Misc explicit FrameRateLimiter(std::chrono::duration maxFrameDuration, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) : mMaxFrameDuration(std::chrono::duration_cast(maxFrameDuration)) + , mLastFrameDuration(0) , mLastMeasurement(now) {} diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 66d48f9715..287365a830 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -226,6 +226,7 @@ namespace Resource , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mConvertAlphaTestToAlphaToCoverage(false) , mInstanceCache(new MultiObjectCache) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 8d6a124e2c..ca223ae3b8 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -375,6 +375,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v pack_evt.y = mMouseY = evt.motion.y; pack_evt.xrel = evt.motion.xrel; pack_evt.yrel = evt.motion.yrel; + pack_evt.type = SDL_MOUSEMOTION; if (mFirstMouseMove) { // first event should be treated as non-relative, since there's no point of reference @@ -387,6 +388,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v { mMouseZ += pack_evt.zrel = (evt.wheel.y * 120); pack_evt.z = mMouseZ; + pack_evt.type = SDL_MOUSEWHEEL; } else { diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 612c9011d2..b0013538f0 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -50,6 +50,7 @@ namespace Shader , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mConvertAlphaTestToAlphaToCoverage(false) , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index a35c845dbd..c153aa14c7 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -50,8 +50,9 @@ VideoState::VideoState() , av_sync_type(AV_SYNC_DEFAULT) , audio_st(nullptr) , video_st(nullptr), frame_last_pts(0.0) - , video_clock(0.0), sws_context(nullptr), pictq_size(0) - , pictq_rindex(0), pictq_windex(0) + , video_clock(0.0), sws_context(nullptr) + , sws_context_w(0), sws_context_h(0) + , pictq_size(0), pictq_rindex(0), pictq_windex(0) , mSeekRequested(false) , mSeekPos(0) , mVideoEnded(false) From c989fac67b6de8e6df9676fbf34a6f06d1b053e6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 11:20:12 +0400 Subject: [PATCH 0541/2859] Add bound for pointers cache size, as it specified in docs --- apps/openmw/mwworld/cells.cpp | 9 +++++---- apps/openmw/mwworld/cells.hpp | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 15c1b46bab..40ad62a403 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -132,9 +132,11 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector& reader) : mStore (store), mReader (reader), - mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair ("", (CellStore*)nullptr)), mIdCacheIndex (0) -{} +{ + int cacheSize = std::max(Settings::Manager::getInt("pointers cache size", "Cells"), 0); + mIdCache = IdCache(cacheSize, std::pair ("", (CellStore*)nullptr)); +} MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) { @@ -259,8 +261,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) { // First check the cache - for (std::vector >::iterator iter (mIdCache.begin()); - iter!=mIdCache.end(); ++iter) + for (IdCache::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter) if (iter->first==name && iter->second) { Ptr ptr = getPtr (name, *iter->second); diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 90ede409b2..654d9a14b5 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -28,11 +28,12 @@ namespace MWWorld /// \brief Cell container class Cells { + typedef std::vector > IdCache; const MWWorld::ESMStore& mStore; std::vector& mReader; mutable std::map mInteriors; mutable std::map, CellStore> mExteriors; - std::vector > mIdCache; + IdCache mIdCache; std::size_t mIdCacheIndex; Cells (const Cells&); From 903b89a0ffafed6f92bfc0c6265d37aeedcdc70e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 11:21:53 +0400 Subject: [PATCH 0542/2859] Add bound for UI scale factor, as it specified in docs --- apps/openmw/mwgui/inventorywindow.cpp | 2 +- apps/openmw/mwinput/controllermanager.cpp | 2 +- apps/openmw/mwinput/mousemanager.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 2 +- components/fontloader/fontloader.cpp | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index b0749d4bdf..6152efaa98 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -71,7 +71,7 @@ namespace MWGui , mUpdateTimer(0.f) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - if (uiScale > 1.0) + if (uiScale > 0.f) mScaleFactor = uiScale; mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 48091541ca..d17a4bd95d 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -70,7 +70,7 @@ namespace MWInput } float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - if (uiScale != 0.f) + if (uiScale > 0.f) mInvUiScalingFactor = 1.f / uiScale; float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input"); diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 4816470ffb..8df116baa6 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -37,7 +37,7 @@ namespace MWInput , mGuiCursorEnabled(true) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - if (uiScale != 0.f) + if (uiScale > 0.f) mInvUiScalingFactor = 1.f / uiScale; int w,h; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 64931aa880..25d859e547 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -90,7 +90,7 @@ LocalMap::LocalMap(osg::Group* root) { // Increase map resolution, if use UI scaling float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - if (uiScale > 1.0) + if (uiScale > 0.f) mMapResolution *= uiScale; SceneUtil::FindByNameVisitor find("Scene Root"); diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 2bed079e17..98fce32d2a 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -569,7 +569,8 @@ namespace Gui resolution = std::min(960, std::max(48, resolution)); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); - resolution *= uiScale; + if (uiScale > 0.f) + resolution *= uiScale; MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property"); resolutionNode->addAttribute("key", "Resolution"); From 1db369f4184a083ed6c57be8f3d4341ebc3da1d6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 11:26:54 +0400 Subject: [PATCH 0543/2859] Do not use unchecked value in calculations --- apps/openmw/mwgui/spellcreationdialog.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index f9de469e20..5a5dec60f7 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -393,7 +393,8 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold) + int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); + if (price > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; @@ -401,8 +402,6 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // add gold to NPC trading gold pool From f984e96b347730469da4d49f22a489bfef316522 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 12:23:03 +0400 Subject: [PATCH 0544/2859] Use conventional names for atan2 arguments --- apps/openmw/mwmechanics/aiavoiddoor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 73a6385638..6a59ae2bfb 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -45,13 +45,13 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont return true; //Door is no longer opening ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door - float x = pos.pos[0] - tPos.pos[0]; - float y = pos.pos[1] - tPos.pos[1]; + float x = pos.pos[1] - tPos.pos[1]; + float y = pos.pos[0] - tPos.pos[0]; actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); // Turn away from the door and move when turn completed - if (zTurn(actor, std::atan2(x,y) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) + if (zTurn(actor, std::atan2(y,x) + getAdjustedAngle(), osg::DegreesToRadians(5.f))) actor.getClass().getMovementSettings(actor).mPosition[1] = 1; else actor.getClass().getMovementSettings(actor).mPosition[1] = 0; From 45b1c68af437ea26b193fd823ffea84f79eb2864 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 10 Apr 2021 12:32:12 +0400 Subject: [PATCH 0545/2859] Remove annotation which does not work --- components/crashcatcher/crashcatcher.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 4ad8565483..b4b2a4a0c6 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -146,11 +146,10 @@ static void gdb_info(pid_t pid) /* * Create a temp file to put gdb commands into. * Note: POSIX.1-2008 declares that the file should be already created with mode 0600 by default. - * Modern systems implement it and and suggest to do not touch masks in multithreaded applications. + * Modern systems implement it and suggest to do not touch masks in multithreaded applications. * So CoverityScan warning is valid only for ancient versions of stdlib. */ strcpy(respfile, "/tmp/gdb-respfile-XXXXXX"); - // coverity[secure_temp] if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != nullptr) { fprintf(f, "attach %d\n" From 400cae58e56a17df3ec8d4a35e5615618ba8d064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Sat, 10 Apr 2021 12:52:42 +0000 Subject: [PATCH 0546/2859] Collada user documentation --- .../reference/modding/custom-models/index.rst | 19 +++++ .../pipeline-blender-collada.rst | 84 +++++++++++++++++++ .../custom-models/pipeline-blender-nif.rst | 17 ++++ .../pipeline-blender-osgnative.rst} | 27 ++---- docs/source/reference/modding/index.rst | 1 + .../modding/texture-modding/index.rst | 1 - 6 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 docs/source/reference/modding/custom-models/index.rst create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada.rst create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-nif.rst rename docs/source/reference/modding/{texture-modding/native-mesh-format.rst => custom-models/pipeline-blender-osgnative.rst} (77%) diff --git a/docs/source/reference/modding/custom-models/index.rst b/docs/source/reference/modding/custom-models/index.rst new file mode 100644 index 0000000000..8f575bf3c8 --- /dev/null +++ b/docs/source/reference/modding/custom-models/index.rst @@ -0,0 +1,19 @@ +############# +Custom Models +############# + +Custom models can be imported into OpenMW using a variety of formats. Below is a quick overview of supported formats, followed by separate articles with further look at the pipelines. + +* **COLLADA** has no license restrictions and is suitable for modding as well as standalone games based on the OpenMW engine. It supports static and animated models. While it doesn't yet work in all parts of the engine, work is being done to resolve the remaining limitations. + +* **OSG native** has no license restrictions, but currently supports only static, non-animated models. + +* **NIF** is the proprietary format used in the original Morrowind game. It supports static and animated models and everything else the format included in the original game. + +.. toctree:: + :caption: Table of Contents + :maxdepth: 1 + + pipeline-blender-collada + pipeline-blender-osgnative + pipeline-blender-nif diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst new file mode 100644 index 0000000000..48a19cb7c7 --- /dev/null +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst @@ -0,0 +1,84 @@ +############################## +Blender to OpenMW with Collada +############################## + +First, let's take a look at the pipeline requirements and how the fundamental properties of a scene translate from Blender to OpenMW. + +Requirements +------------ +* `OpenMW 0.47 `_ or later +* `Blender 2.81 `_ or later. Latest confirmed, working version is Blender 2.91 +* `Better COLLADA Exporter `_ tuned for OpenMW +* A model you would like to export + +In addition, OpenMW needs to be configured to read COLLADA (dae) files instead of the default format (nif). In settings.cfg under [Models] section... TODO + +Location +-------- + +Objects keep their visual location and origin they had in the original scene. + + +Rotation +-------- + +* Blender’s +Z axis is up axis in OpenMW +* Blender’s +Y axis is front axis in OpenMW +* Blender’s X axis is left-right axis in OpenMW + + +Scale +----- + +Scale ratio between Blender and OpenMW is 70 to 1. This means 70 blender units translate to 1 m in OpenMW. + +However, a scale factor like this is impractical to work with. A better approach is to work with a scale of 1 Blender unit = 1m and apply the 70 scale factor in the Better COLLADA Exporter. The exporter will automatically scale all object, mesh, armature and animation data. + + +Exporter settings - static models +--------------------------------- + +Better COLLADA Exporter offers various options which are rather straightforward for static models. The important one is last in the list, to apply a scaling factor of 70 to the whole scene, so 1 blender unit equals 1 m in OpenMW. The following settings should be good for general use. +It's also very important to have "export selected" box checked, as otherwise the exporter may just fail with an error message. It's also important to have the correct window open, and the models selected before exporting. + + +Animated models to OpenMW +------------------------- + +Animated models are those where a hierarchy of bones, known as armature, deforms the mesh and makes things move. Besides the topics covered above, the following requirements apply. + +Armature +-------- + +* For animated models, a single armature per COLLADA file is advised to avoid any potential problems. +* There needs to be a single top-most bone in the armature’s hierarchy, where both the deformation and control bones fall under it. +* Not all bones need to be exported. By disabing the bone’s “Deform” property and using the corresponding option in the exporter, it is possible to export only the bones needed for animation. + + +Animations +---------- + +Every action in Blender is exported as its own animation clip in COLLADA. Actions you don't wish to export need to have "-noexp" added to their name, with the corresponding option enabled in the exporter. + +Due to current limitations of the format / exporter, the keyframes of any action must not overlap the keyframes of any other action. Thus in practice, the keyframes for each action need to be manually offset to their unique range on the timeline. + +An animated .dae file needs a corresponding animation definition file, or textkeys, for OpenMW to understand. Textkeys are set in .txt file with the same name as the model. E.g. OpenMWDude.dae -> OpenMWDude.txt , each line having a textkey and a double number for timesignature. E.g. idle: start 0.03333333333333333. + +Root Motion +----------- + +OpenMW can read the movement of the root (top-most) bone and use it to move objects in the game world. For this to work, the root bone must be animated to move through space. The root bone must, in its default pose, be alligned with the world. + + +Exporter Settings +----------------- + +For animated models, use the following exporter settings. Before export, select all objects you wish to include in the exported file. TODO + + + + + + + + diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-nif.rst b/docs/source/reference/modding/custom-models/pipeline-blender-nif.rst new file mode 100644 index 0000000000..a1b7b0f705 --- /dev/null +++ b/docs/source/reference/modding/custom-models/pipeline-blender-nif.rst @@ -0,0 +1,17 @@ +########################## +Blender to OpenMW with NIF +########################## + +There is a lot of information available around the Internet on how to work with NIF files. We recommend you refer to https://www.niftools.org/ for more information. + +For Blender specifically, you will need the following requirements. + +Requirements +------------ +* `OpenMW `_ +* `Blender 2.8+ `_ +* Either `Niftools addon `_ +* Or `Morrowind Blender Plugin `_ +* A model you would like to export + + diff --git a/docs/source/reference/modding/texture-modding/native-mesh-format.rst b/docs/source/reference/modding/custom-models/pipeline-blender-osgnative.rst similarity index 77% rename from docs/source/reference/modding/texture-modding/native-mesh-format.rst rename to docs/source/reference/modding/custom-models/pipeline-blender-osgnative.rst index c2ddd26ba7..95bc7e5acb 100644 --- a/docs/source/reference/modding/texture-modding/native-mesh-format.rst +++ b/docs/source/reference/modding/custom-models/pipeline-blender-osgnative.rst @@ -1,17 +1,9 @@ -################## -Native Mesh Format -################## +################################# +Blender to OpenMW with OSG native +################################# -This article explains how to export a model from Blender to OpenMW using the OSG model format. -Starting with OpenMW version 0.38 we can utilize the OSG native model format. -The OSG model format doesn't yet support all the features that NIF's support, -but works for basic models. For more details on the format, refer to -`this forum post `_. - -Previously, NIF files were the only way to get models into the game. -Unfortunately, the NIF format is proprietary, bloated, -and the available exporters are not in great shape. -For example, the Blender NIF exporter currently only works with the very old Blender 2.49. +This article explains how to export a model from Blender to OpenMW using the OSG model format. It supports only basic, static models. +For more details on the format, refer to `this forum post `_. Prerequisites ############# @@ -103,12 +95,5 @@ Using shaders/normal maps ######################### See :ref:`OSG Native Files` + -Conclusion -########## - -These are the basics of getting a textured, static model from Blender into the game. -In the future, we will want a way to add texture animations, -skeletal animations, separate collision shapes, -and some other features that are currently only available via NIF files. -We will likely add these features to the native OSG format after OpenMW 1.0. \ No newline at end of file diff --git a/docs/source/reference/modding/index.rst b/docs/source/reference/modding/index.rst index 69ec0a56a0..df98137d85 100644 --- a/docs/source/reference/modding/index.rst +++ b/docs/source/reference/modding/index.rst @@ -22,6 +22,7 @@ about creating new content for OpenMW, please refer to mod-install settings/index texture-modding/index + custom-models/index font extended paths diff --git a/docs/source/reference/modding/texture-modding/index.rst b/docs/source/reference/modding/texture-modding/index.rst index 3e0b359ee2..aa2802af5d 100644 --- a/docs/source/reference/modding/texture-modding/index.rst +++ b/docs/source/reference/modding/texture-modding/index.rst @@ -13,4 +13,3 @@ to texture modding in OpenMW. texture-basics convert-bump-mapped-mods - native-mesh-format From 010f290fd525f4b71eacb63a9d2a590b9f34dc11 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 11 Apr 2021 14:07:12 +0200 Subject: [PATCH 0547/2859] Update OSX deployment target to 10.14 To support std::variant --- CI/before_script.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 36fa792991..f9191eb890 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -16,7 +16,7 @@ cmake \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ --D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \ +-D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \ -D CMAKE_BUILD_TYPE=RELEASE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ -D BUILD_OPENMW=TRUE \ From b91be1e8035f36fdfd5cee44b706bafd82a2a047 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 11 Apr 2021 14:12:31 +0200 Subject: [PATCH 0548/2859] Catch exceptions in ResolutionListener --- apps/openmw/mwworld/containerstore.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 635485dde8..86c5ec331d 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -77,10 +77,24 @@ MWWorld::ResolutionListener::~ResolutionListener() { if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty()) { - for(const auto&& ptr : mStore) - ptr.getRefData().setCount(0); + try + { + for(const auto&& ptr : mStore) + ptr.getRefData().setCount(0); + } + catch(const std::exception& e) + { + Log(Debug::Warning) << "Failed to clear temporary container contents of " << mStore.mPtr.get()->mBase->mId << ": " << e.what(); + } mStore.fillNonRandom(mStore.mPtr.get()->mBase->mInventory, "", mStore.mSeed); - addScripts(mStore, mStore.mPtr.mCell); + try + { + addScripts(mStore, mStore.mPtr.mCell); + } + catch(const std::exception& e) + { + Log(Debug::Warning) << "Failed to restart item scripts inside " << mStore.mPtr.get()->mBase->mId << ": " << e.what(); + } mStore.mResolved = false; } } From fda639eb57f3180a4a87b8d2879ffbb381f7eb32 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 14 Mar 2021 16:21:33 +0100 Subject: [PATCH 0549/2859] Remove unused forward declarations --- apps/openmw/engine.hpp | 20 -------------------- apps/openmw/mwbase/windowmanager.hpp | 1 - apps/openmw/mwgui/container.hpp | 6 ------ apps/openmw/mwgui/dialogue.hpp | 5 ----- apps/openmw/mwgui/race.hpp | 5 ----- apps/openmw/mwgui/review.hpp | 5 ----- apps/openmw/mwgui/settingswindow.hpp | 5 ----- apps/openmw/mwgui/spellbuyingwindow.hpp | 5 ----- apps/openmw/mwgui/statswindow.hpp | 2 -- apps/openmw/mwgui/textinput.hpp | 5 ----- apps/openmw/mwgui/travelwindow.hpp | 6 ------ apps/openmw/mwgui/windowbase.hpp | 6 ------ apps/openmw/mwgui/windowmanagerimp.hpp | 1 - apps/openmw/mwgui/windowpinnablebase.hpp | 2 -- apps/openmw/mwscript/interpretercontext.hpp | 10 ---------- apps/openmw/mwworld/scene.hpp | 1 - apps/openmw/mwworld/worldimp.hpp | 2 -- components/nifosg/controller.hpp | 5 ----- 18 files changed, 92 deletions(-) diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index ff362f4b69..1aef62df53 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -33,26 +33,6 @@ namespace Compiler class Context; } -namespace MWScript -{ - class ScriptManager; -} - -namespace MWSound -{ - class SoundManager; -} - -namespace MWWorld -{ - class World; -} - -namespace MWGui -{ - class WindowManager; -} - namespace Files { struct ConfigurationManager; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 29d404777c..9bbea9c519 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -32,7 +32,6 @@ namespace MyGUI namespace ESM { - struct Class; class ESMReader; class ESMWriter; struct CellId; diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index feda123fb7..85c0dddc67 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -6,11 +6,6 @@ #include "itemmodel.hpp" -namespace MWWorld -{ - class Environment; -} - namespace MyGUI { class Gui; @@ -19,7 +14,6 @@ namespace MyGUI namespace MWGui { - class WindowManager; class ContainerWindow; class ItemView; class SortFilterItemModel; diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 02401f2e14..ac6303e20a 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -15,11 +15,6 @@ namespace Gui class MWList; } -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class ResponseCallback; diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 0299c2a1ac..170c1dbce3 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -7,11 +7,6 @@ #include -namespace MWGui -{ - class WindowManager; -} - namespace MWRender { class RaceSelectionPreview; diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index bd17c7afb3..cb847536d3 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -11,11 +11,6 @@ namespace ESM struct Spell; } -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class ReviewDialog : public WindowModal diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 6f25dd1143..c268514dc6 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -3,11 +3,6 @@ #include "windowbase.hpp" -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class SettingsWindow : public WindowBase diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index 622548c959..f46c437963 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -15,11 +15,6 @@ namespace MyGUI class Widget; } -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class SpellBuyingWindow : public ReferenceInterface, public WindowBase diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index 24f302580e..bf78cde34a 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -6,8 +6,6 @@ namespace MWGui { - class WindowManager; - class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener { public: diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp index 84d9d032da..4d365eb44c 100644 --- a/apps/openmw/mwgui/textinput.hpp +++ b/apps/openmw/mwgui/textinput.hpp @@ -3,11 +3,6 @@ #include "windowbase.hpp" -namespace MWGui -{ - class WindowManager; -} - namespace MWGui { class TextInputDialog : public WindowModal diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 962d17161d..00b7db7305 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -11,12 +11,6 @@ namespace MyGUI class Widget; } -namespace MWGui -{ - class WindowManager; -} - - namespace MWGui { class TravelWindow : public ReferenceInterface, public WindowBase diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 8afb2321e8..90ef2118de 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -3,11 +3,6 @@ #include "layout.hpp" -namespace MWBase -{ - class WindowManager; -} - namespace MWWorld { class Ptr; @@ -15,7 +10,6 @@ namespace MWWorld namespace MWGui { - class WindowManager; class DragAndDrop; class WindowBase: public Layout diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index cc1a1b6944..3fd8b132f6 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -114,7 +114,6 @@ namespace MWGui class TrainingWindow; class SpellIcons; class MerchantRepair; - class Repair; class SoulgemDialog; class Recharge; class CompanionWindow; diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index a942128195..c91f0a1489 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -5,8 +5,6 @@ namespace MWGui { - class WindowManager; - class WindowPinnableBase: public WindowBase { public: diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index c1481d6d0a..298454bcd2 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -10,16 +10,6 @@ #include "../mwworld/ptr.hpp" -namespace MWSound -{ - class SoundManager; -} - -namespace MWInput -{ - struct MWInputManager; -} - namespace MWScript { class Locals; diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index a70d3ccdd5..f87a0ca733 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -36,7 +36,6 @@ namespace Loading namespace DetourNavigator { struct Navigator; - class Water; } namespace MWRender diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 33b23e0653..5447e20c33 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -60,8 +60,6 @@ namespace ToUTF8 class Utf8Encoder; } -struct ContentLoader; - namespace MWPhysics { class Object; diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 96beafcbb8..b459166931 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -24,11 +24,6 @@ namespace osg class Material; } -namespace osgParticle -{ - class Emitter; -} - namespace NifOsg { From efb241f1de280987a79526c1d817c1f9ebe5feaa Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 29 Mar 2021 19:44:10 +0200 Subject: [PATCH 0550/2859] Use override instead of virtual --- apps/openmw/mwsound/soundmanagerimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 6b9de800f0..934402cd4c 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -141,7 +141,7 @@ namespace MWSound public: SoundManager(const VFS::Manager* vfs, bool useSound); - virtual ~SoundManager(); + ~SoundManager() override; void processChangedSettings(const Settings::CategorySettingVector& settings) override; From 32981bcd88d411090d324e410e0084297b8c3f97 Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 29 Mar 2021 19:44:23 +0200 Subject: [PATCH 0551/2859] Constify a few things --- apps/openmw/mwgui/timeadvancer.cpp | 4 ++-- apps/openmw/mwgui/timeadvancer.hpp | 4 ++-- apps/openmw/mwrender/objectpaging.hpp | 2 +- apps/openmw/mwstate/quicksavemanager.cpp | 4 ++-- apps/openmw/mwstate/quicksavemanager.hpp | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwgui/timeadvancer.cpp b/apps/openmw/mwgui/timeadvancer.cpp index a07da16825..c38094ae45 100644 --- a/apps/openmw/mwgui/timeadvancer.cpp +++ b/apps/openmw/mwgui/timeadvancer.cpp @@ -58,12 +58,12 @@ namespace MWGui } } - int TimeAdvancer::getHours() + int TimeAdvancer::getHours() const { return mHours; } - bool TimeAdvancer::isRunning() + bool TimeAdvancer::isRunning() const { return mRunning; } diff --git a/apps/openmw/mwgui/timeadvancer.hpp b/apps/openmw/mwgui/timeadvancer.hpp index 8367b5a8b2..b8456f3761 100644 --- a/apps/openmw/mwgui/timeadvancer.hpp +++ b/apps/openmw/mwgui/timeadvancer.hpp @@ -14,8 +14,8 @@ namespace MWGui void stop(); void onFrame(float dt); - int getHours(); - bool isRunning(); + int getHours() const; + bool isRunning() const; // signals typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index 65f53d530c..c24cdf4f8d 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -63,7 +63,7 @@ namespace MWRender { std::set mDisabled; std::set mBlacklist; - bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } + bool operator==(const RefTracker&other) const { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; } }; RefTracker mRefTracker; RefTracker mRefTrackerNew; diff --git a/apps/openmw/mwstate/quicksavemanager.cpp b/apps/openmw/mwstate/quicksavemanager.cpp index df078e026c..bf17815207 100644 --- a/apps/openmw/mwstate/quicksavemanager.cpp +++ b/apps/openmw/mwstate/quicksavemanager.cpp @@ -18,14 +18,14 @@ void MWState::QuickSaveManager::visitSave(const Slot *saveSlot) } } -bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) +bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) const { if(mOldestSlotVisited == nullptr) return true; return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp); } -bool MWState::QuickSaveManager::shouldCreateNewSlot() +bool MWState::QuickSaveManager::shouldCreateNewSlot() const { return (mSlotsVisited < mMaxSaves); } diff --git a/apps/openmw/mwstate/quicksavemanager.hpp b/apps/openmw/mwstate/quicksavemanager.hpp index a5237d7c30..cdeff42c26 100644 --- a/apps/openmw/mwstate/quicksavemanager.hpp +++ b/apps/openmw/mwstate/quicksavemanager.hpp @@ -13,8 +13,8 @@ namespace MWState{ unsigned int mSlotsVisited; const Slot *mOldestSlotVisited; private: - bool shouldCreateNewSlot(); - bool isOldestSave(const Slot *compare); + bool shouldCreateNewSlot() const; + bool isOldestSave(const Slot *compare) const; public: QuickSaveManager(std::string &saveName, unsigned int maxSaves); ///< A utility class to manage multiple quicksave slots From 95042a2a688e674fae259df8a23616d97780cdee Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 11 Apr 2021 20:31:24 +0200 Subject: [PATCH 0552/2859] Use the number of logical cores on the CI on OSX --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 713cc26019..c26235d0a4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -105,7 +105,7 @@ MacOS: - rm -fr build/* # remove anything in the build directory - CI/before_install.osx.sh - CI/before_script.osx.sh - - cd build; make -j2 package + - cd build; make -j $(sysctl -n hw.logicalcpu) package - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done artifacts: paths: From 634556be9de94456d4019aeb331df97d4184bb83 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 11 Apr 2021 17:57:36 +0200 Subject: [PATCH 0553/2859] Add setting to allow following creatures to find path over water surface --- apps/launcher/advancedpage.cpp | 1 + apps/openmw/mwmechanics/aipackage.cpp | 7 ++++++- docs/source/reference/modding/settings/game.rst | 16 ++++++++++++++++ files/settings-default.cfg | 4 ++++ files/ui/advancedpage.ui | 10 ++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index b7bf1a2494..683d441193 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -108,6 +108,7 @@ bool Launcher::AdvancedPage::loadSettings() int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics"); if (numPhysicsThreads >= 0) physicsThreadsSpinBox->setValue(numPhysicsThreads); + loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game"); } // Visuals diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 214aad320b..87ee562c67 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -411,10 +411,15 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const { + static const bool allowToFollowOverWaterSurface = Settings::Manager::getBool("allow actors to follow over water surface", "Game"); + const MWWorld::Class& actorClass = actor.getClass(); DetourNavigator::Flags result = DetourNavigator::Flag_none; - if (actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && actorClass.canSwim(actor))) + if (actorClass.isPureWaterCreature(actor) + || (getTypeId() != AiPackageTypeId::Wander + && ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow) + || actorClass.canSwim(actor)))) result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor)) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4e1fe13183..878485b3b3 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -439,3 +439,19 @@ graphic herbalism Some mods add harvestable container models. When this setting is enabled, activating a container using a harvestable model will visually harvest from it instead of opening the menu. When this setting is turned off or when activating a regular container, the menu will open as usual. + +allow actors to follow over water surface +--------------------- + +:Type: boolean +:Range: True/False +:Default: True + +If enabled actors will always find path over the water surface when following other actors. This makes OpenMW behaviour closer to the vanilla engine. + +If disabled actors without the ability to swim will not follow other actors to the water. + +.. note:: + Has effect only when Navigator is enabled. + +This setting can be controlled in Advanced tab of the launcher. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 78487b1735..68c27abe5a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -364,6 +364,10 @@ always allow stealing from knocked out actors = false # Enables visually harvesting plants for models that support it. graphic herbalism = true +# Give actors an ability to swim over water surface when they follow other actor independently from their ability to swim +# (true, false) +allow actors to follow over water surface = true + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index a990e9172f..594372aab7 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -153,6 +153,16 @@
+ + + + Give NPC an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + + + Always allow NPC to follow over water surface + + + From 28fc21792e7e4078029139b8f06828354ffde78d Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 11 Apr 2021 19:17:23 +0200 Subject: [PATCH 0554/2859] Allow water walking actors to find path over water surface --- apps/openmw/mwmechanics/actorutil.cpp | 6 ++++++ apps/openmw/mwmechanics/actorutil.hpp | 1 + apps/openmw/mwmechanics/aipackage.cpp | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index e27c9de495..04cbb8e9f5 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -23,4 +23,10 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); } + + bool hasWaterWalking(const MWWorld::Ptr& actor) + { + const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); + return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; + } } diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index 1e993f5606..a226fc9cb6 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -31,6 +31,7 @@ namespace MWMechanics MWWorld::Ptr getPlayer(); bool isPlayerInCombat(); bool canActorMoveByZAxis(const MWWorld::Ptr& actor); + bool hasWaterWalking(const MWWorld::Ptr& actor); template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 87ee562c67..8dcf37355c 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -419,7 +419,8 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: if (actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow) - || actorClass.canSwim(actor)))) + || actorClass.canSwim(actor) + || hasWaterWalking(actor)))) result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor)) From 56ede535b5e2f8bf818c4ade2c9456fb399190cf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 12 Apr 2021 08:31:45 +0200 Subject: [PATCH 0555/2859] Don't perform a hit test outside the page's bounds --- CHANGELOG.md | 1 + apps/openmw/mwgui/bookpage.cpp | 76 +++++++++++++++------------------- 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c78345660..a506079e8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,7 @@ Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs Bug #5912: ImprovedBound mod doesn't work Bug #5914: BM: The Swimmer can't reach destination + Bug #5923: Clicking on empty spaces between journal entries might show random topics Bug #5934: AddItem command doesn't accept negative values Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index c70783f39c..fba136f88f 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1,5 +1,7 @@ #include "bookpage.hpp" +#include + #include "MyGUI_RenderItem.h" #include "MyGUI_RenderManager.h" #include "MyGUI_TextureUtility.h" @@ -894,6 +896,27 @@ protected: return mIsPageReset || (mPage != page); } + std::optional getAdjustedPos(int left, int top, bool move = false) + { + if (!mBook) + return {}; + + if (mPage >= mBook->mPages.size()) + return {}; + + MyGUI::IntPoint pos (left, top); +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) + // work around inconsistency in MyGUI where the mouse press coordinates aren't + // transformed by the current Layer (even though mouse *move* events are). + if(!move) + pos = mNode->getLayer()->getPosition(left, top); +#endif + pos.left -= mCroppedParent->getAbsoluteLeft (); + pos.top -= mCroppedParent->getAbsoluteTop (); + pos.top += mViewTop; + return pos; + } + public: typedef TypesetBookImpl::StyleImpl Style; @@ -952,16 +975,10 @@ public: void onMouseMove (int left, int top) { - if (!mBook) - return; - - if (mPage >= mBook->mPages.size()) - return; - - left -= mCroppedParent->getAbsoluteLeft (); - top -= mCroppedParent->getAbsoluteTop (); - - Style * hit = mBook->hitTestWithMargin (left, mViewTop + top); + Style * hit = nullptr; + if(auto pos = getAdjustedPos(left, top, true)) + if(pos->top <= mViewBottom) + hit = mBook->hitTestWithMargin (pos->left, pos->top); if (mLastDown == MyGUI::MouseButton::None) { @@ -991,24 +1008,11 @@ public: void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id) { - if (!mBook) - return; + auto pos = getAdjustedPos(left, top); - if (mPage >= mBook->mPages.size()) - return; - - // work around inconsistency in MyGUI where the mouse press coordinates aren't - // transformed by the current Layer (even though mouse *move* events are). - MyGUI::IntPoint pos (left, top); -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - pos = mNode->getLayer()->getPosition(left, top); -#endif - pos.left -= mCroppedParent->getAbsoluteLeft (); - pos.top -= mCroppedParent->getAbsoluteTop (); - - if (mLastDown == MyGUI::MouseButton::None) + if (pos && mLastDown == MyGUI::MouseButton::None) { - mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); + mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr; mItemActive = true; dirtyFocusItem (); @@ -1019,25 +1023,11 @@ public: void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id) { - if (!mBook) - return; - - if (mPage >= mBook->mPages.size()) - return; - - // work around inconsistency in MyGUI where the mouse release coordinates aren't - // transformed by the current Layer (even though mouse *move* events are). - MyGUI::IntPoint pos (left, top); -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - pos = mNode->getLayer()->getPosition(left, top); -#endif - - pos.left -= mCroppedParent->getAbsoluteLeft (); - pos.top -= mCroppedParent->getAbsoluteTop (); + auto pos = getAdjustedPos(left, top); - if (mLastDown == id) + if (pos && mLastDown == id) { - Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); + Style * item = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr; bool clicked = mFocusItem == item; From 9a87940fc6319fc855af5c6f4281425e370b5af8 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 11:12:15 +0200 Subject: [PATCH 0556/2859] Use `rule` instead of `only` only/except will likely be deprecated: https://docs.gitlab.com/ee/ci/yaml/README.html#onlyexcept-basic --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2faeb820a3..da712a2dc5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,8 +32,8 @@ stages: Coverity: extends: .Debian - only: - - schedules + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN From dc10ab7bad235184872c9b74564ca63f23d66a4f Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 11:26:33 +0200 Subject: [PATCH 0557/2859] Install curl in the coverity job --- .gitlab-ci.yml | 2 +- CI/install_debian_deps.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2faeb820a3..6ec4a327e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,7 +35,7 @@ Coverity: only: - schedules before_script: - - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic + - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz script: diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 3e7ab7fca3..490fad0dae 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -27,6 +27,8 @@ declare -rA GROUPED_DEPS=( # These dependencies can alternatively be built and linked statically. [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" + + [coverity]="curl" # Pre-requisites for building MyGUI and OSG for static linking. # From 54adb9cbedb3a5e2c117c1cfc34059ae103f81eb Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 11 Apr 2021 18:57:47 +0200 Subject: [PATCH 0558/2859] Fix an off-by-one in loadscpt --- components/esm/loadscpt.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index a7f348cb17..53b6aedd38 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -30,7 +30,7 @@ namespace ESM // The tmp buffer is a null-byte separated string list, we // just have to pick out one string at a time. char* str = tmp.data(); - if (!str) + if (tmp.empty()) { if (mVarNames.size() > 0) Log(Debug::Warning) << "SCVR with no variable names"; @@ -51,6 +51,7 @@ namespace ESM ss << "\n Subrecord: " << "SCVR"; ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); Log(Debug::Verbose) << ss.str(); + str = tmp.data(); } for (size_t i = 0; i < mVarNames.size(); i++) From 1e955fb2e7df23fc193ef5b0a146e2965b9bc215 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 14:01:22 +0200 Subject: [PATCH 0559/2859] Fix the path for the coverity build --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2faeb820a3..c7b8898334 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,8 +40,7 @@ Coverity: - tar xfz /tmp/cov-analysis-linux64.tgz script: - CI/before_script.linux.sh - - cd build - - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build . -- -j $(nproc) + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME \ From 539b46aaf0e3dfb34b86ff7933d40d0035b12dea Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 12 Apr 2021 16:01:09 +0200 Subject: [PATCH 0560/2859] Use different cache keys for different macOS builds --- .gitlab-ci.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 715a888741..f8cd181066 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -115,7 +115,7 @@ Debian_Clang_tests: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 -MacOS: +.MacOS: image: macos-11-xcode-12 tags: - macos @@ -123,8 +123,14 @@ MacOS: only: variables: - $CI_PROJECT_ID == "7107382" + cache: + paths: + - ccache/ script: - rm -fr build/* # remove anything in the build directory + - export CCACHE_BASEDIR="$(pwd)" + - export CCACHE_DIR="$(pwd)/ccache" + - mkdir -pv "${CCACHE_DIR}" - CI/before_install.osx.sh - CI/before_script.osx.sh - cd build; make -j $(sysctl -n hw.logicalcpu) package @@ -134,9 +140,17 @@ MacOS: - build/OpenMW-*.dmg - "build/**/*.log" +macOS11_Xcode12: + extends: .MacOS + image: macos-11-xcode-12 + cache: + key: macOS11_Xcode12.v1 + macOS10.15_Xcode11: - extends: MacOS + extends: .MacOS image: macos-10.15-xcode-11 + cache: + key: macOS10.15_Xcode11.v1 variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" From 663ad5e19207c83ee75dc151a80a83ed5695fcbe Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 12 Apr 2021 16:02:57 +0200 Subject: [PATCH 0561/2859] Print ccache stats for macOS builds --- .gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f8cd181066..10d443dd15 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -131,10 +131,12 @@ Debian_Clang_tests: - export CCACHE_BASEDIR="$(pwd)" - export CCACHE_DIR="$(pwd)/ccache" - mkdir -pv "${CCACHE_DIR}" + - ccache -z -M "${CCACHE_SIZE}" - CI/before_install.osx.sh - CI/before_script.osx.sh - cd build; make -j $(sysctl -n hw.logicalcpu) package - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done + - ccache -s artifacts: paths: - build/OpenMW-*.dmg @@ -145,12 +147,16 @@ macOS11_Xcode12: image: macos-11-xcode-12 cache: key: macOS11_Xcode12.v1 + variables: + CCACHE_SIZE: 3G macOS10.15_Xcode11: extends: .MacOS image: macos-10.15-xcode-11 cache: key: macOS10.15_Xcode11.v1 + variables: + CCACHE_SIZE: 3G variables: &engine-targets targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" From c10273675effb8e7c00ba5bfcd1d4e4f7a096a6f Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 17:13:38 +0200 Subject: [PATCH 0562/2859] Massively increase coverity's timeout --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 10d443dd15..6ccf02d1e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,7 +50,7 @@ Coverity: variables: CC: gcc CXX: g++ - timeout: 2h + timeout: 8h Debian_GCC: extends: .Debian From ccb62ad8b0fdddd2b108df5666f35465bc877adc Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 12 Apr 2021 20:23:23 +0200 Subject: [PATCH 0563/2859] Only build openmw in coverity --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ccf02d1e8..a04b6a1cd2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,7 +40,8 @@ Coverity: - tar xfz /tmp/cov-analysis-linux64.tgz script: - CI/before_script.linux.sh - - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) + # Add more than just `openmw` once we can build everything under 3h + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME \ From dda735c54a0e4676c4d0c99b4b0fe14ebeab79d8 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 21 Feb 2021 10:38:15 -0800 Subject: [PATCH 0564/2859] initial commit --- apps/opencs/view/render/scenewidget.cpp | 1 - apps/openmw/engine.cpp | 1 + apps/openmw/mwrender/characterpreview.cpp | 3 +- apps/openmw/mwrender/localmap.cpp | 11 + apps/openmw/mwrender/renderingmanager.cpp | 13 +- apps/openmw/mwrender/water.cpp | 3 + components/resource/scenemanager.cpp | 13 +- components/resource/scenemanager.hpp | 4 + components/sceneutil/lightmanager.cpp | 573 +++++++++++++++++----- components/sceneutil/lightmanager.hpp | 88 +++- components/shader/shadermanager.cpp | 13 + components/shader/shadermanager.hpp | 10 + files/settings-default.cfg | 5 + files/shaders/CMakeLists.txt | 1 + files/shaders/groundcover_fragment.glsl | 1 + files/shaders/groundcover_vertex.glsl | 2 + files/shaders/lighting.glsl | 83 +++- files/shaders/objects_fragment.glsl | 4 +- files/shaders/objects_vertex.glsl | 3 +- files/shaders/sun.glsl | 14 + files/shaders/terrain_fragment.glsl | 4 +- files/shaders/terrain_vertex.glsl | 3 +- files/shaders/water_fragment.glsl | 11 +- 23 files changed, 698 insertions(+), 166 deletions(-) create mode 100644 files/shaders/sun.glsl diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 4d73cde153..61707967b9 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -72,7 +72,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; - lightMgr->setStartLight(1); lightMgr->setLightingMask(Mask_Lighting); mRootNode = lightMgr; diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d7c315323d..019e9f67a9 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -31,6 +31,7 @@ #include #include +#include #include diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 262b03229f..a44b51db83 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -152,7 +152,7 @@ namespace MWRender mCamera->setNodeMask(Mask_RenderToTexture); - osg::ref_ptr lightManager = new SceneUtil::LightManager; + osg::ref_ptr lightManager = new SceneUtil::LightManager(mResourceSystem->getSceneManager()->getFFPLighting()); lightManager->setStartLight(1); osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); @@ -215,6 +215,7 @@ namespace MWRender light->setConstantAttenuation(1.f); light->setLinearAttenuation(0.f); light->setQuadraticAttenuation(0.f); + lightManager->setSunlight(light); osg::ref_ptr lightSource = new osg::LightSource; lightSource->setLight(light); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 5fa1a0e299..8b1dda841a 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -18,7 +18,10 @@ #include #include #include +#include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -219,6 +222,14 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + // override sun for local map + if (!MWBase::Environment::get().getResourceSystem()->getSceneManager()->getFFPLighting()) + { + osg::ref_ptr sun = new SceneUtil::SunlightStateAttribute; + sun->setFromLight(light); + sun->setStateSet(stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + camera->addChild(lightSource); camera->setStateSet(stateset); camera->setViewport(0, 0, mMapResolution, mMapResolution); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a3aee5c0fa..733e1d287a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -203,17 +203,19 @@ namespace MWRender resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); // Shadows and radial fog have problems with fixed-function mode bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows"); + bool clampLighting = Settings::Manager::getBool("clamp lighting", "Shaders"); resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped - resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); + resourceSystem->getSceneManager()->setClampLighting(clampLighting); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::Manager::getString("normal height map pattern", "Shaders")); resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); + resourceSystem->getSceneManager()->setFFPLighting(clampLighting || !forceShaders || !SceneUtil::LightManager::queryNonFFPLightingSupport()); - osg::ref_ptr sceneRoot = new SceneUtil::LightManager; + osg::ref_ptr sceneRoot = new SceneUtil::LightManager(mResourceSystem->getSceneManager()->getFFPLighting()); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); @@ -235,8 +237,12 @@ namespace MWRender mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); + Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); + for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) + globalDefines[itr->first] = itr->second; + for (auto itr = shadowDefines.begin(); itr != shadowDefines.end(); itr++) globalDefines[itr->first] = itr->second; @@ -248,7 +254,7 @@ namespace MWRender float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); - + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); @@ -354,6 +360,7 @@ namespace MWRender mSunLight->setAmbient(osg::Vec4f(0,0,0,1)); mSunLight->setSpecular(osg::Vec4f(0,0,0,0)); mSunLight->setConstantAttenuation(1.f); + sceneRoot->setSunlight(mSunLight); sceneRoot->addChild(source); sceneRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 1cc5a3cb7c..4eac2fe4cd 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -27,6 +27,7 @@ #include #include +#include #include @@ -644,6 +645,8 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); + if (!mResourceSystem->getSceneManager()->getFFPLighting()) + program->addBindUniformBlock("SunlightBuffer", 9); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); node->setStateSet(shaderStateset); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 19cc96433b..6755db2557 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -220,12 +220,13 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) - , mShaderManager(new Shader::ShaderManager) + , mShaderManager(new Shader::ShaderManager(this)) , mForceShaders(false) , mClampLighting(true) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mFFPLighting(true) , mInstanceCache(new MultiObjectCache) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) @@ -297,6 +298,16 @@ namespace Resource mApplyLightingToEnvMaps = apply; } + void SceneManager::setFFPLighting(bool apply) + { + mFFPLighting = apply; + } + + bool SceneManager::getFFPLighting() const + { + return mFFPLighting; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 9da6bc500d..92800df9fb 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -100,6 +100,9 @@ namespace Resource void setApplyLightingToEnvMaps(bool apply); + void setFFPLighting(bool apply); + bool getFFPLighting() const; + void setShaderPath(const std::string& path); /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded @@ -184,6 +187,7 @@ namespace Resource bool mAutoUseSpecularMaps; std::string mSpecularMapPattern; bool mApplyLightingToEnvMaps; + bool mFFPLighting; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 2ebce241d5..fa9dc4fda8 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1,11 +1,145 @@ #include "lightmanager.hpp" +#include + #include #include +#include + +#include + +#include + +namespace +{ + /* similar to the boost::hash_combine */ + template + inline void hash_combine(std::size_t& seed, const T& v) + { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + + bool sortLights(const SceneUtil::LightManager::LightSourceViewBound* left, const SceneUtil::LightManager::LightSourceViewBound* right) + { + return left->mViewBound.center().length2() - left->mViewBound.radius2()*81 < right->mViewBound.center().length2() - right->mViewBound.radius2()*81; + } +} + namespace SceneUtil { + static int sLightId = 0; + + class LightBuffer : public osg::Referenced + { + public: + LightBuffer(int elements, int count=1) + : mNumElements(elements) + , mData(new osg::Vec4Array(elements * count)) + , mDirty(false) + {} + + virtual ~LightBuffer() {} + + osg::ref_ptr getData() + { + return mData; + } + + bool isDirty() const + { + return mDirty; + } + + void dirty() + { + mData->dirty(); + mDirty = false; + } + + int blockSize() const + { + return mData->getNumElements() * sizeof(GL_FLOAT) * osg::Vec4::num_components; + } + + protected: + size_t mNumElements; + osg::ref_ptr mData; + bool mDirty; + }; + + /* + * struct: + * vec4 diffuse + * vec4 ambient + * vec4 specular + * vec4 direction + */ + class SunlightBuffer : public LightBuffer + { + public: + + enum Type {Diffuse, Ambient, Specular, Direction}; + + SunlightBuffer() + : LightBuffer(4) + {} + + void setValue(Type type, const osg::Vec4& value) + { + if (getValue(type) == value) + return; + + (*mData)[type] = value; + + mDirty = true; + } + + osg::Vec4 getValue(Type type) + { + return (*mData)[type]; + } + }; + + /* + * struct: + * vec4 diffuse + * vec4 ambient + * vec4 position + * vec4 illumination (constant, linear, quadratic) + */ + class PointLightBuffer : public LightBuffer + { + public: + + enum Type {Diffuse, Ambient, Position, Attenuation}; + + PointLightBuffer(int count) + : LightBuffer(4, count) + {} + + void setValue(int index, Type type, const osg::Vec4& value) + { + if (getValue(index, type) == value) + return; + + (*mData)[mNumElements * index + type] = value; + + mDirty = true; + } + + osg::Vec4 getValue(int index, Type type) + { + return (*mData)[mNumElements * index + type]; + } + + static constexpr int queryBlockSize(int sz) + { + return 4 * osg::Vec4::num_components * sizeof(GL_FLOAT) * sz; + } + }; class LightStateCache { @@ -13,7 +147,7 @@ namespace SceneUtil osg::Light* lastAppliedLight[8]; }; - LightStateCache* getLightStateCache(unsigned int contextid) + LightStateCache* getLightStateCache(size_t contextid) { static std::vector cacheVector; if (cacheVector.size() < contextid+1) @@ -21,14 +155,143 @@ namespace SceneUtil return &cacheVector[contextid]; } - // Resets the modelview matrix to just the view matrix before applying lights. + SunlightStateAttribute::SunlightStateAttribute() + : mBuffer(new SunlightBuffer) + { + osg::ref_ptr ubo = new osg::UniformBufferObject; + mBuffer->getData()->setBufferObject(ubo); + mUbb = new osg::UniformBufferBinding(9 , mBuffer->getData().get(), 0, mBuffer->blockSize()); + } + + SunlightStateAttribute::SunlightStateAttribute(const SunlightStateAttribute ©, const osg::CopyOp ©op) + : osg::StateAttribute(copy, copyop), mBuffer(copy.mBuffer) + {} + + int SunlightStateAttribute::compare(const StateAttribute &sa) const + { + throw std::runtime_error("LightStateAttribute::compare: unimplemented"); + } + + void SunlightStateAttribute::setFromLight(const osg::Light* light) + { + mBuffer->setValue(SunlightBuffer::Diffuse, light->getDiffuse()); + mBuffer->setValue(SunlightBuffer::Ambient, light->getAmbient()); + mBuffer->setValue(SunlightBuffer::Specular, light->getSpecular()); + mBuffer->setValue(SunlightBuffer::Direction, light->getPosition()); + } + + void SunlightStateAttribute::setStateSet(osg::StateSet* stateset, int mode) + { + stateset->setAttributeAndModes(mUbb, mode); + stateset->setAssociatedModes(this, mode); + mBuffer->dirty(); + } + + class DisableLight : public osg::StateAttribute + { + public: + DisableLight() : mIndex(0) {} + DisableLight(int index) : mIndex(index) {} + + DisableLight(const DisableLight& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex) {} + + osg::Object* cloneType() const override { return new DisableLight(mIndex); } + osg::Object* clone(const osg::CopyOp& copyop) const override { return new DisableLight(*this,copyop); } + bool isSameKindAs(const osg::Object* obj) const override { return dynamic_cast(obj)!=nullptr; } + const char* libraryName() const override { return "SceneUtil"; } + const char* className() const override { return "DisableLight"; } + Type getType() const override { return LIGHT; } + + unsigned int getMember() const override + { + return mIndex; + } + + bool getModeUsage(ModeUsage & usage) const override + { + usage.usesMode(GL_LIGHT0 + mIndex); + return true; + } + + int compare(const StateAttribute &sa) const override + { + throw std::runtime_error("DisableLight::compare: unimplemented"); + } + + void apply(osg::State& state) const override + { + int lightNum = GL_LIGHT0 + mIndex; + glLightfv( lightNum, GL_AMBIENT, mnullptr.ptr() ); + glLightfv( lightNum, GL_DIFFUSE, mnullptr.ptr() ); + glLightfv( lightNum, GL_SPECULAR, mnullptr.ptr() ); + + LightStateCache* cache = getLightStateCache(state.getContextID()); + cache->lastAppliedLight[mIndex] = nullptr; + } + + private: + size_t mIndex; + osg::Vec4f mnullptr; + }; + class LightStateAttribute : public osg::StateAttribute { public: - LightStateAttribute() : mIndex(0) {} - LightStateAttribute(unsigned int index, const std::vector >& lights) : mIndex(index), mLights(lights) {} + LightStateAttribute() : mBuffer(nullptr) {} + LightStateAttribute(const std::vector >& lights, const osg::ref_ptr& buffer) : mLights(lights), mBuffer(buffer) {} LightStateAttribute(const LightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mLights(copy.mLights), mBuffer(copy.mBuffer) {} + + int compare(const StateAttribute &sa) const override + { + throw std::runtime_error("LightStateAttribute::compare: unimplemented"); + } + + META_StateAttribute(NifOsg, LightStateAttribute, osg::StateAttribute::LIGHT) + + void apply(osg::State& state) const override + { + if (mLights.empty()) + return; + osg::Matrix modelViewMatrix = state.getModelViewMatrix(); + + state.applyModelViewMatrix(state.getInitialViewMatrix()); + + if (mBuffer) + { + for (size_t i = 0; i < mLights.size(); ++i) + { + + mBuffer->setValue(i, PointLightBuffer::Diffuse, mLights[i]->getDiffuse()); + mBuffer->setValue(i, PointLightBuffer::Ambient, mLights[i]->getAmbient()); + mBuffer->setValue(i, PointLightBuffer::Position, mLights[i]->getPosition() * state.getModelViewMatrix()); + mBuffer->setValue(i, PointLightBuffer::Attenuation, + osg::Vec4(mLights[i]->getConstantAttenuation(), + mLights[i]->getLinearAttenuation(), + mLights[i]->getQuadraticAttenuation(), 0.0)); + } + + if (mBuffer && mBuffer->isDirty()) + mBuffer->dirty(); + } + + state.applyModelViewMatrix(modelViewMatrix); + } + + private: + std::vector> mLights; + osg::ref_ptr mBuffer; + }; + + class FFPLightStateAttribute : public osg::StateAttribute + { + public: + FFPLightStateAttribute() : mIndex(0) {} + FFPLightStateAttribute(size_t index, const std::vector >& lights) : mIndex(index), mLights(lights) {} + + FFPLightStateAttribute(const FFPLightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex), mLights(copy.mLights) {} unsigned int getMember() const override @@ -38,17 +301,17 @@ namespace SceneUtil bool getModeUsage(ModeUsage & usage) const override { - for (unsigned int i=0; ilastAppliedLight[i+mIndex]; if (current != mLights[i].get()) @@ -90,14 +353,14 @@ namespace SceneUtil } private: - unsigned int mIndex; + size_t mIndex; - std::vector > mLights; + std::vector> mLights; }; LightManager* findLightManager(const osg::NodePath& path) { - for (unsigned int i=0;i(path[i])) return lightManager; @@ -160,33 +423,161 @@ namespace SceneUtil } }; - LightManager::LightManager() + class SunlightCallback : public osg::NodeCallback + { + public: + SunlightCallback(LightManager* lightManager) : mLightManager(lightManager) {} + + void operator()(osg::Node* node, osg::NodeVisitor* nv) override + { + osgUtil::CullVisitor* cv = static_cast(nv); + + if (mLastFrameNumber != cv->getTraversalNumber()) + { + mLastFrameNumber = cv->getTraversalNumber(); + + auto sun = mLightManager->getSunlight(); + + if (!sun) + return; + + auto buf = mLightManager->getSunBuffer(); + + buf->setValue(SunlightBuffer::Diffuse, sun->getDiffuse()); + buf->setValue(SunlightBuffer::Ambient, sun->getAmbient()); + buf->setValue(SunlightBuffer::Specular, sun->getSpecular()); + buf->setValue(SunlightBuffer::Direction, sun->getPosition() * *cv->getCurrentRenderStage()->getInitialViewMatrix()); + + if (buf->isDirty()) + buf->dirty(); + } + + traverse(node, nv); + } + + private: + LightManager* mLightManager; + size_t mLastFrameNumber; + }; + + LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) + , mSun(nullptr) + , mSunBuffer(nullptr) + , mFFP(ffp) { setUpdateCallback(new LightManagerUpdateCallback); - for (unsigned int i=0; i<8; ++i) - mDummies.push_back(new LightStateAttribute(i, std::vector >())); + + if (usingFFP()) + { + for (int i=0; i >())); + return; + } + + auto* stateset = getOrCreateStateSet(); + + mSunBuffer = new SunlightBuffer(); + osg::ref_ptr ubo = new osg::UniformBufferObject; + mSunBuffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(9 , mSunBuffer->getData().get(), 0, mSunBuffer->blockSize()); + stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("PointLightCount", 0)); + + addCullCallback(new SunlightCallback(this)); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) , mStartLight(copy.mStartLight) , mLightingMask(copy.mLightingMask) + , mSun(copy.mSun) + , mSunBuffer(copy.mSunBuffer) + , mFFP(copy.mFFP) + { + } + + bool LightManager::usingFFP() const + { + return mFFP; + } + + int LightManager::getMaxLights() const + { + if (usingFFP()) return LightManager::mFFPMaxLights; + return std::clamp(Settings::Manager::getInt("max lights", "Shaders"), 0, getMaxLightsInScene()); + } + + int LightManager::getMaxLightsInScene() const { + static constexpr int max = 16384 / PointLightBuffer::queryBlockSize(1); + return max; + } + + Shader::ShaderManager::DefineMap LightManager::getLightDefines() const + { + Shader::ShaderManager::DefineMap defines; + + bool ffp = usingFFP(); + + defines["ffpLighting"] = ffp ? "1" : "0"; + defines["sunDirection"] = ffp ? "gl_LightSource[0].position" : "Sun.direction"; + defines["sunAmbient"] = ffp ? "gl_LightSource[0].ambient" : "Sun.ambient"; + defines["sunDiffuse"] = ffp ? "gl_LightSource[0].diffuse" : "Sun.diffuse"; + defines["sunSpecular"] = ffp ? "gl_LightSource[0].specular" : "Sun.specular"; + defines["maxLights"] = std::to_string(getMaxLights()); + defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene()); + + return defines; + } + bool LightManager::queryNonFFPLightingSupport() + { + osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); + if (!exts || !exts->isUniformBufferObjectSupported) + { + auto ffpWarning = Misc::StringUtils::format("GL_ARB_uniform_buffer_object not supported: Falling back to FFP %zu light limit. Can not set lights to %i." + , LightManager::mFFPMaxLights + , Settings::Manager::getInt("max lights", "Shaders")); + Log(Debug::Warning) << ffpWarning; + return false; + } + return true; } - void LightManager::setLightingMask(unsigned int mask) + void LightManager::setLightingMask(size_t mask) { mLightingMask = mask; } - unsigned int LightManager::getLightingMask() const + size_t LightManager::getLightingMask() const { return mLightingMask; } + void LightManager::setStartLight(int start) + { + if (!usingFFP()) return; + + mStartLight = start; + + // Set default light state to zero + // This is necessary because shaders don't respect glDisable(GL_LIGHTX) so in addition to disabling + // we'll have to set a light state that has no visible effect + for (int i=start; i defaultLight (new DisableLight(i)); + getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); + } + } + + int LightManager::getStartLight() const + { + return mStartLight; + } + void LightManager::update() { mLights.clear(); @@ -200,7 +591,7 @@ namespace SceneUtil } } - void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum) + void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum) { LightSourceTransform l; l.mLightSource = lightSource; @@ -211,19 +602,28 @@ namespace SceneUtil mLights.push_back(l); } - /* similar to the boost::hash_combine */ - template - inline void hash_combine(std::size_t& seed, const T& v) + void LightManager::setSunlight(osg::ref_ptr sun) { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + if (usingFFP()) return; + + mSun = sun; + } + + osg::ref_ptr LightManager::getSunlight() + { + return mSun; + } + + osg::ref_ptr LightManager::getSunBuffer() + { + return mSunBuffer; } - osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, unsigned int frameNum) + osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, size_t frameNum) { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; - for (unsigned int i=0; imLightSource->getId()); LightStateSetMap& stateSetCache = mStateSetCache[frameNum%2]; @@ -236,21 +636,35 @@ namespace SceneUtil osg::ref_ptr stateset = new osg::StateSet; std::vector > lights; lights.reserve(lightList.size()); - for (unsigned int i=0; imLightSource->getLight(frameNum)); - // the first light state attribute handles the actual state setting for all lights - // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary - // don't use setAttributeAndModes, that does not support light indices! - stateset->setAttribute(new LightStateAttribute(mStartLight, std::move(lights)), osg::StateAttribute::ON); - - for (unsigned int i=0; isetMode(GL_LIGHT0 + mStartLight + i, osg::StateAttribute::ON); - - // need to push some dummy attributes to ensure proper state tracking - // lights need to reset to their default when the StateSet is popped - for (unsigned int i=1; isetAttribute(mDummies[i+mStartLight].get(), osg::StateAttribute::ON); + if (usingFFP()) + { + // the first light state attribute handles the actual state setting for all lights + // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary + // don't use setAttributeAndModes, that does not support light indices! + stateset->setAttribute(new FFPLightStateAttribute(mStartLight, std::move(lights)), osg::StateAttribute::ON); + + for (size_t i=0; isetMode(GL_LIGHT0 + mStartLight + i, osg::StateAttribute::ON); + + // need to push some dummy attributes to ensure proper state tracking + // lights need to reset to their default when the StateSet is popped + for (size_t i=1; isetAttribute(mDummies[i+mStartLight].get(), osg::StateAttribute::ON); + } + else + { + osg::ref_ptr buffer = new PointLightBuffer(lights.size()); + osg::ref_ptr ubo = new osg::UniformBufferObject; + buffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(8 ,buffer->getData().get(), 0, buffer->blockSize()); + stateset->addUniform(new osg::Uniform("PointLightCount", (int)lights.size())); + stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + + stateset->setAttribute(new LightStateAttribute(std::move(lights), buffer), osg::StateAttribute::ON); + } stateSetCache.emplace(hash, stateset); return stateset; @@ -262,7 +676,7 @@ namespace SceneUtil return mLights; } - const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix) + const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { osg::observer_ptr camPtr (camera); std::map, LightSourceViewBoundCollection>::iterator it = mLightsInViewSpace.find(camPtr); @@ -286,75 +700,6 @@ namespace SceneUtil return it->second; } - class DisableLight : public osg::StateAttribute - { - public: - DisableLight() : mIndex(0) {} - DisableLight(int index) : mIndex(index) {} - - DisableLight(const DisableLight& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex) {} - - osg::Object* cloneType() const override { return new DisableLight(mIndex); } - osg::Object* clone(const osg::CopyOp& copyop) const override { return new DisableLight(*this,copyop); } - bool isSameKindAs(const osg::Object* obj) const override { return dynamic_cast(obj)!=nullptr; } - const char* libraryName() const override { return "SceneUtil"; } - const char* className() const override { return "DisableLight"; } - Type getType() const override { return LIGHT; } - - unsigned int getMember() const override - { - return mIndex; - } - - bool getModeUsage(ModeUsage & usage) const override - { - usage.usesMode(GL_LIGHT0 + mIndex); - return true; - } - - int compare(const StateAttribute &sa) const override - { - throw std::runtime_error("DisableLight::compare: unimplemented"); - } - - void apply(osg::State& state) const override - { - int lightNum = GL_LIGHT0 + mIndex; - glLightfv( lightNum, GL_AMBIENT, mnullptr.ptr() ); - glLightfv( lightNum, GL_DIFFUSE, mnullptr.ptr() ); - glLightfv( lightNum, GL_SPECULAR, mnullptr.ptr() ); - - LightStateCache* cache = getLightStateCache(state.getContextID()); - cache->lastAppliedLight[mIndex] = nullptr; - } - - private: - unsigned int mIndex; - osg::Vec4f mnullptr; - }; - - void LightManager::setStartLight(int start) - { - mStartLight = start; - - // Set default light state to zero - // This is necessary because shaders don't respect glDisable(GL_LIGHTX) so in addition to disabling - // we'll have to set a light state that has no visible effect - for (int i=start; i<8; ++i) - { - osg::ref_ptr defaultLight (new DisableLight(i)); - getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); - } - } - - int LightManager::getStartLight() const - { - return mStartLight; - } - - static int sLightId = 0; - LightSource::LightSource() : mRadius(0.f) { @@ -372,12 +717,6 @@ namespace SceneUtil mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } - - bool sortLights (const LightManager::LightSourceViewBound* left, const LightManager::LightSourceViewBound* right) - { - return left->mViewBound.center().length2() - left->mViewBound.radius2()*81 < right->mViewBound.center().length2() - right->mViewBound.radius2()*81; - } - void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) { osgUtil::CullVisitor* cv = static_cast(nv); @@ -413,7 +752,7 @@ namespace SceneUtil // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); - const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix); + const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); // get the node bounds in view space // NB do not node->getBound() * modelView, that would apply the node's transformation twice @@ -421,7 +760,7 @@ namespace SceneUtil osg::Transform* transform = node->asTransform(); if (transform) { - for (unsigned int i=0; igetNumChildren(); ++i) + for (size_t i=0; igetNumChildren(); ++i) nodeBound.expandBy(transform->getChild(i)->getBound()); } else @@ -430,7 +769,7 @@ namespace SceneUtil transformBoundingSphere(mat, nodeBound); mLightList.clear(); - for (unsigned int i=0; i (8 - mLightManager->getStartLight()); + size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight(); osg::StateSet* stateset = nullptr; diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index c370f1b7f0..7ceb725aef 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -8,6 +8,9 @@ #include #include #include +#include + +#include namespace osgUtil { @@ -16,6 +19,27 @@ namespace osgUtil namespace SceneUtil { + class SunlightBuffer; + + // Used to override sun. Rarely useful but necassary for local map. + class SunlightStateAttribute : public osg::StateAttribute + { + public: + SunlightStateAttribute(); + SunlightStateAttribute(const SunlightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + int compare(const StateAttribute &sa) const override; + + META_StateAttribute(NifOsg, SunlightStateAttribute, osg::StateAttribute::LIGHT) + + void setFromLight(const osg::Light* light); + + void setStateSet(osg::StateSet* stateset, int mode=osg::StateAttribute::ON); + + private: + osg::ref_ptr mBuffer; + osg::ref_ptr mUbb; + }; /// LightSource managed by a LightManager. /// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole scene @@ -57,7 +81,7 @@ namespace SceneUtil /// Get the osg::Light safe for modification in the given frame. /// @par May be used externally to animate the light's color/attenuation properties, /// and is used internally to synchronize the light's position with the position of the LightSource. - osg::Light* getLight(unsigned int frame) + osg::Light* getLight(size_t frame) { return mLight[frame % 2]; } @@ -83,10 +107,25 @@ namespace SceneUtil class LightManager : public osg::Group { public: + struct LightSourceTransform + { + LightSource* mLightSource; + osg::Matrixf mWorldMatrix; + }; + + struct LightSourceViewBound + { + LightSource* mLightSource; + osg::BoundingSphere mViewBound; + }; + + typedef std::vector LightList; + + static bool queryNonFFPLightingSupport(); META_Node(SceneUtil, LightManager) - LightManager(); + LightManager(bool ffp = true); LightManager(const LightManager& copy, const osg::CopyOp& copyop); @@ -94,40 +133,36 @@ namespace SceneUtil /// By default, it's ~0u i.e. always on. /// If you have some views that do not require lighting, then set the Camera's cull mask to not include /// the lightingMask for a much faster cull and rendering. - void setLightingMask (unsigned int mask); - - unsigned int getLightingMask() const; + void setLightingMask (size_t mask); + size_t getLightingMask() const; /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. void setStartLight(int start); - int getStartLight() const; /// Internal use only, called automatically by the LightManager's UpdateCallback void update(); /// Internal use only, called automatically by the LightSource's UpdateCallback - void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum); - - struct LightSourceTransform - { - LightSource* mLightSource; - osg::Matrixf mWorldMatrix; - }; + void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); const std::vector& getLights() const; - struct LightSourceViewBound - { - LightSource* mLightSource; - osg::BoundingSphere mViewBound; - }; + const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum); - const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix); + osg::ref_ptr getLightListStateSet(const LightList& lightList, size_t frameNum); - typedef std::vector LightList; + void setSunlight(osg::ref_ptr sun); + osg::ref_ptr getSunlight(); + + osg::ref_ptr getSunBuffer(); - osg::ref_ptr getLightListStateSet(const LightList& lightList, unsigned int frameNum); + bool usingFFP() const; + + int getMaxLights() const; + int getMaxLightsInScene() const; + + Shader::ShaderManager::DefineMap getLightDefines() const; private: // Lights collected from the scene graph. Only valid during the cull traversal. @@ -144,7 +179,14 @@ namespace SceneUtil int mStartLight; - unsigned int mLightingMask; + size_t mLightingMask; + + osg::ref_ptr mSun; + osg::ref_ptr mSunBuffer; + + bool mFFP; + + static constexpr int mFFPMaxLights = 8; }; /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via @@ -180,7 +222,7 @@ namespace SceneUtil private: LightManager* mLightManager; - unsigned int mLastFrameNumber; + size_t mLastFrameNumber; LightManager::LightList mLightList; std::set mIgnoredLightSources; }; diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 4f887e659b..96671bf8b4 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -9,12 +9,20 @@ #include #include +#include +#include #include #include namespace Shader { + ShaderManager::ShaderManager(Resource::SceneManager* sceneManager) + : mSceneManager(sceneManager) + { + + } + void ShaderManager::setShaderPath(const std::string &path) { mPath = path; @@ -344,6 +352,11 @@ namespace Shader program->addShader(fragmentShader); program->addBindAttribLocation("aOffset", 6); program->addBindAttribLocation("aRotation", 7); + if (!mSceneManager->getFFPLighting()) + { + program->addBindUniformBlock("PointLightBuffer", 8); + program->addBindUniformBlock("SunlightBuffer", 9); + } found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 13db30b019..b9bb005b11 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -11,6 +11,11 @@ #include +namespace Resource +{ + class SceneManager; +} + namespace Shader { @@ -19,6 +24,9 @@ namespace Shader class ShaderManager { public: + + ShaderManager(Resource::SceneManager* sceneManager); + void setShaderPath(const std::string& path); typedef std::map DefineMap; @@ -59,6 +67,8 @@ namespace Shader typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; + Resource::SceneManager* mSceneManager; + std::mutex mMutex; }; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 67d944d195..368d74f6bd 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -442,6 +442,11 @@ apply lighting to environment maps = false # This makes fogging independent from the viewing angle. Shaders will be used to render all objects. radial fog = false +# Set maximum number of lights per object, does not include the sun. +# This feature may not work on your device, in which case it will fall back to the previous OpenGL limit of 8 lights. +# "[Shaders]/clamp lighting" must be set to 'false' and "[Shaders]/force shaders" must be set to 'true' for this to take effect. +max lights = 16 + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index a4e898e4b0..1a0c5f1a54 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -25,6 +25,7 @@ set(SHADER_FILES shadowcasting_vertex.glsl shadowcasting_fragment.glsl vertexcolors.glsl + sun.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 77fd32e58b..392419d92b 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -1,4 +1,5 @@ #version 120 +#extension GL_ARB_uniform_buffer_object : enable #define GROUNDCOVER diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 407599effd..3238bc864f 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -1,5 +1,7 @@ #version 120 +#extension GL_ARB_uniform_buffer_object : enable + #define GROUNDCOVER attribute vec4 aOffset; diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index a2dcc758a8..b9f426fc9d 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -1,13 +1,62 @@ -#define MAX_LIGHTS 8 +#if !@ffpLighting -void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) +#include "sun.glsl" + +#define getLight PointLights + +struct PointLight +{ + vec4 diffuse; + vec4 ambient; + vec4 position; + vec4 attenuation; +}; + +uniform mat4 osg_ViewMatrix; +uniform int PointLightCount; +uniform int PointLightIndex[@maxLights]; + +layout(std140) uniform PointLightBuffer +{ + PointLight PointLights[@maxLights]; +}; + +#else +#define getLight gl_LightSource +#endif + +void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal) +{ + vec3 lightDir = @sunDirection.xyz; + lightDir = normalize(lightDir); + + ambientOut = @sunAmbient.xyz; + + float lambert = dot(viewNormal.xyz, lightDir); +#ifndef GROUNDCOVER + lambert = max(lambert, 0.0); +#else + float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); + if (lambert < 0.0) + { + lambert = -lambert; + eyeCosine = -eyeCosine; + } + lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); +#endif + diffuseOut = @sunDiffuse.xyz * lambert; +} + +void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) { - vec3 lightDir = gl_LightSource[lightIndex].position.xyz - viewPos * gl_LightSource[lightIndex].position.w; + vec3 lightDir = getLight[lightIndex].position.xyz - viewPos; + //vec3 lightDir = (osg_ViewMatrix * vec4(getLight[lightIndex].position, 1.0)).xyz - viewPos; + float lightDistance = length(lightDir); lightDir = normalize(lightDir); - float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); - ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination; + float illumination = clamp(1.0 / (getLight[lightIndex].attenuation.x + getLight[lightIndex].attenuation.y * lightDistance + getLight[lightIndex].attenuation.z * lightDistance * lightDistance), 0.0, 1.0); + ambientOut = getLight[lightIndex].ambient.xyz * illumination; float lambert = dot(viewNormal.xyz, lightDir) * illumination; #ifndef GROUNDCOVER @@ -21,7 +70,7 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie } lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif - diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; + diffuseOut = getLight[lightIndex].diffuse.xyz * lambert; } #if PER_PIXEL_LIGHTING @@ -32,7 +81,8 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a { vec3 ambientOut, diffuseOut; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. - perLight(ambientOut, diffuseOut, 0, viewPos, viewNormal); + perLightSun(ambientOut, diffuseOut, viewPos, viewNormal); + #if PER_PIXEL_LIGHTING diffuseLight = diffuseOut * shadowing - diffuseOut; #else @@ -40,22 +90,31 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a diffuseLight = -diffuseOut; #endif ambientLight = gl_LightModel.ambient.xyz; - for (int i=0; i Date: Mon, 1 Mar 2021 23:30:54 -0800 Subject: [PATCH 0565/2859] Add shared UBO --- apps/opencs/model/world/data.cpp | 1 + apps/opencs/view/render/scenewidget.cpp | 1 + apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 8 +- apps/openmw/mwrender/water.cpp | 2 +- components/resource/scenemanager.cpp | 2 +- components/sceneutil/lightcontroller.cpp | 9 +- components/sceneutil/lightcontroller.hpp | 3 +- components/sceneutil/lightmanager.cpp | 233 +++++++++++++--------- components/sceneutil/lightmanager.hpp | 47 ++++- components/sceneutil/lightutil.cpp | 12 +- components/sceneutil/lightutil.hpp | 4 +- components/shader/shadermanager.cpp | 16 +- components/shader/shadermanager.hpp | 6 +- files/shaders/lighting.glsl | 34 +++- 15 files changed, 253 insertions(+), 127 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 70c496e3f2..25ac051f41 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -83,6 +83,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat defines["clamp"] = "1"; // Clamp lighting defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["radialFog"] = "0"; + defines["ffpLighting"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 61707967b9..4d73cde153 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -72,6 +72,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; + lightMgr->setStartLight(1); lightMgr->setLightingMask(Mask_Lighting); mRootNode = lightMgr; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index d8c7599358..7ccf653ea1 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1613,7 +1613,7 @@ namespace MWRender { bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); - SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior); + SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior, mResourceSystem->getSceneManager()->getFFPLighting()); } void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 733e1d287a..bbd7d06925 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -213,9 +213,11 @@ namespace MWRender resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); - resourceSystem->getSceneManager()->setFFPLighting(clampLighting || !forceShaders || !SceneUtil::LightManager::queryNonFFPLightingSupport()); - - osg::ref_ptr sceneRoot = new SceneUtil::LightManager(mResourceSystem->getSceneManager()->getFFPLighting()); + bool useFFP = clampLighting || !forceShaders || !SceneUtil::LightManager::queryNonFFPLightingSupport(); + resourceSystem->getSceneManager()->setFFPLighting(useFFP); + resourceSystem->getSceneManager()->getShaderManager().setFFPLighting(useFFP); + + osg::ref_ptr sceneRoot = new SceneUtil::LightManager(useFFP); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 4eac2fe4cd..51792d1baa 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -646,7 +646,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R program->addShader(vertexShader); program->addShader(fragmentShader); if (!mResourceSystem->getSceneManager()->getFFPLighting()) - program->addBindUniformBlock("SunlightBuffer", 9); + program->addBindUniformBlock("SunlightBuffer", 0); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); node->setStateSet(shaderStateset); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 6755db2557..ef53bea0d4 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -220,7 +220,7 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) - , mShaderManager(new Shader::ShaderManager(this)) + , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) , mClampLighting(true) , mAutoUseNormalMaps(false) diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index c759fabc79..cc95fe2e4f 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -11,13 +11,14 @@ namespace SceneUtil { - LightController::LightController() + LightController::LightController(bool useFFPLighting) : mType(LT_Normal) , mPhase(0.25f + Misc::Rng::rollClosedProbability() * 0.75f) , mBrightness(0.675f) , mStartTime(0.0) , mLastTime(0.0) , mTicksToAdvance(0.f) + , mFFPLighting(useFFPLighting) { } @@ -62,7 +63,11 @@ namespace SceneUtil mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } - static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness); + auto* lightSource = static_cast(node); + if (mFFPLighting) + lightSource->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness); + else + lightSource->setBrightness(nv->getTraversalNumber(), mBrightness); traverse(node, nv); } diff --git a/components/sceneutil/lightcontroller.hpp b/components/sceneutil/lightcontroller.hpp index 36b2e868e5..f416be7240 100644 --- a/components/sceneutil/lightcontroller.hpp +++ b/components/sceneutil/lightcontroller.hpp @@ -20,7 +20,7 @@ namespace SceneUtil LT_PulseSlow }; - LightController(); + LightController(bool useFFPLighting=true); void setType(LightType type); @@ -36,6 +36,7 @@ namespace SceneUtil double mStartTime; double mLastTime; float mTicksToAdvance; + bool mFFPLighting; }; } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index fa9dc4fda8..987a95e20e 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1,6 +1,7 @@ #include "lightmanager.hpp" #include +#include #include @@ -12,6 +13,8 @@ #include +#include "apps/openmw/mwrender/vismask.hpp" + namespace { /* similar to the boost::hash_combine */ @@ -32,6 +35,10 @@ namespace SceneUtil { static int sLightId = 0; +//////////////////////////////////////////////////////////////////////////////// +// Internal Data Structures +//////////////////////////////////////////////////////////////////////////////// + class LightBuffer : public osg::Referenced { public: @@ -59,11 +66,6 @@ namespace SceneUtil mDirty = false; } - int blockSize() const - { - return mData->getNumElements() * sizeof(GL_FLOAT) * osg::Vec4::num_components; - } - protected: size_t mNumElements; osg::ref_ptr mData; @@ -114,7 +116,7 @@ namespace SceneUtil { public: - enum Type {Diffuse, Ambient, Position, Attenuation}; + enum Type {Position, Diffuse, Ambient, Attenuation}; PointLightBuffer(int count) : LightBuffer(4, count) @@ -124,9 +126,10 @@ namespace SceneUtil { if (getValue(index, type) == value) return; - - (*mData)[mNumElements * index + type] = value; - + + int indexOffset = mNumElements * index + type; + (*mData)[indexOffset] = value; + mDirty = true; } @@ -155,12 +158,16 @@ namespace SceneUtil return &cacheVector[contextid]; } +//////////////////////////////////////////////////////////////////////////////// +// State Attributes +//////////////////////////////////////////////////////////////////////////////// + SunlightStateAttribute::SunlightStateAttribute() : mBuffer(new SunlightBuffer) { osg::ref_ptr ubo = new osg::UniformBufferObject; mBuffer->getData()->setBufferObject(ubo); - mUbb = new osg::UniformBufferBinding(9 , mBuffer->getData().get(), 0, mBuffer->blockSize()); + mUbb = new osg::UniformBufferBinding(0, mBuffer->getData().get(), 0, mBuffer->getData()->getTotalDataSize()); } SunlightStateAttribute::SunlightStateAttribute(const SunlightStateAttribute ©, const osg::CopyOp ©op) @@ -235,56 +242,6 @@ namespace SceneUtil osg::Vec4f mnullptr; }; - class LightStateAttribute : public osg::StateAttribute - { - public: - LightStateAttribute() : mBuffer(nullptr) {} - LightStateAttribute(const std::vector >& lights, const osg::ref_ptr& buffer) : mLights(lights), mBuffer(buffer) {} - - LightStateAttribute(const LightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop), mLights(copy.mLights), mBuffer(copy.mBuffer) {} - - int compare(const StateAttribute &sa) const override - { - throw std::runtime_error("LightStateAttribute::compare: unimplemented"); - } - - META_StateAttribute(NifOsg, LightStateAttribute, osg::StateAttribute::LIGHT) - - void apply(osg::State& state) const override - { - if (mLights.empty()) - return; - osg::Matrix modelViewMatrix = state.getModelViewMatrix(); - - state.applyModelViewMatrix(state.getInitialViewMatrix()); - - if (mBuffer) - { - for (size_t i = 0; i < mLights.size(); ++i) - { - - mBuffer->setValue(i, PointLightBuffer::Diffuse, mLights[i]->getDiffuse()); - mBuffer->setValue(i, PointLightBuffer::Ambient, mLights[i]->getAmbient()); - mBuffer->setValue(i, PointLightBuffer::Position, mLights[i]->getPosition() * state.getModelViewMatrix()); - mBuffer->setValue(i, PointLightBuffer::Attenuation, - osg::Vec4(mLights[i]->getConstantAttenuation(), - mLights[i]->getLinearAttenuation(), - mLights[i]->getQuadraticAttenuation(), 0.0)); - } - - if (mBuffer && mBuffer->isDirty()) - mBuffer->dirty(); - } - - state.applyModelViewMatrix(modelViewMatrix); - } - - private: - std::vector> mLights; - osg::ref_ptr mBuffer; - }; - class FFPLightStateAttribute : public osg::StateAttribute { public: @@ -368,6 +325,10 @@ namespace SceneUtil return nullptr; } +//////////////////////////////////////////////////////////////////////////////// +// Node Callbacks +//////////////////////////////////////////////////////////////////////////////// + // Set on a LightSource. Adds the light source to its light manager for the current frame. // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager. class CollectLightCallback : public osg::NodeCallback @@ -460,6 +421,42 @@ namespace SceneUtil size_t mLastFrameNumber; }; + class LightManagerStateAttribute : public osg::StateAttribute + { + public: + LightManagerStateAttribute() : mLightManager(nullptr) {} + LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} + + LightManagerStateAttribute(const LightManagerStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop),mLightManager(copy.mLightManager) {} + + int compare(const StateAttribute &sa) const override + { + throw std::runtime_error("LightManagerStateAttribute::compare: unimplemented"); + } + + META_StateAttribute(NifOsg, LightManagerStateAttribute, osg::StateAttribute::LIGHT) + + void apply(osg::State& state) const override + { + osg::Matrix modelViewMatrix = state.getModelViewMatrix(); + + state.applyModelViewMatrix(state.getInitialViewMatrix()); + + for (size_t i = 0; i < mLightManager->mPointLightProxyData.size(); i++) + { + auto& data = mLightManager->mPointLightProxyData[i]; + auto pos = data.mPosition * state.getInitialViewMatrix(); + pos[3] = data.mBrightness; + mLightManager->mPointBuffer->setValue(i, PointLightBuffer::Position, pos); + } + state.applyModelViewMatrix(modelViewMatrix); + mLightManager->mPointBuffer->dirty(); + } + + LightManager* mLightManager; + }; + LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) @@ -467,28 +464,44 @@ namespace SceneUtil , mSunBuffer(nullptr) , mFFP(ffp) { - setUpdateCallback(new LightManagerUpdateCallback); - if (usingFFP()) { for (int i=0; i >())); + setUpdateCallback(new LightManagerUpdateCallback); return; } auto* stateset = getOrCreateStateSet(); - mSunBuffer = new SunlightBuffer(); - osg::ref_ptr ubo = new osg::UniformBufferObject; - mSunBuffer->getData()->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(9 , mSunBuffer->getData().get(), 0, mSunBuffer->blockSize()); - stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + { // sunlight UBO data + mSunBuffer = new SunlightBuffer(); + osg::ref_ptr ubo = new osg::UniformBufferObject; + mSunBuffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(0, mSunBuffer->getData().get(), 0, mSunBuffer->getData()->getTotalDataSize()); + stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + } + + { // point lights UBO data + mPointBuffer = new PointLightBuffer(getMaxLightsInScene()); + osg::ref_ptr ubo = new osg::UniformBufferObject; + mPointBuffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(1, mPointBuffer->getData().get(), 0, mPointBuffer->getData()->getTotalDataSize()); + stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + } + + mPointLightProxyData.resize(getMaxLightsInScene()); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + stateset->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("PointLightCount", 0)); + setUpdateCallback(new LightManagerUpdateCallback); addCullCallback(new SunlightCallback(this)); } +//////////////////////////////////////////////////////////////////////////////// + LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) , mStartLight(copy.mStartLight) @@ -507,7 +520,7 @@ namespace SceneUtil int LightManager::getMaxLights() const { if (usingFFP()) return LightManager::mFFPMaxLights; - return std::clamp(Settings::Manager::getInt("max lights", "Shaders"), 0, getMaxLightsInScene()); + return std::clamp(Settings::Manager::getInt("max lights", "Shaders"),LightManager::mFFPMaxLights , getMaxLightsInScene()); } int LightManager::getMaxLightsInScene() const @@ -579,16 +592,18 @@ namespace SceneUtil } void LightManager::update() - { + { mLights.clear(); mLightsInViewSpace.clear(); - // do an occasional cleanup for orphaned lights + // Do an occasional cleanup for orphaned lights. for (int i=0; i<2; ++i) { - if (mStateSetCache[i].size() > 5000) + if (mStateSetCache[i].size() > 5000 || mIndexNeedsRecompiling) mStateSetCache[i].clear(); } + + mIndexNeedsRecompiling = false; } void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum) @@ -599,7 +614,8 @@ namespace SceneUtil lightSource->getLight(frameNum)->setPosition(osg::Vec4f(worldMat.getTrans().x(), worldMat.getTrans().y(), worldMat.getTrans().z(), 1.f)); - mLights.push_back(l); + if (mLights.size() < static_cast(getMaxLightsInScene())) + mLights.emplace_back(l); } void LightManager::setSunlight(osg::ref_ptr sun) @@ -627,20 +643,21 @@ namespace SceneUtil hash_combine(hash, lightList[i]->mLightSource->getId()); LightStateSetMap& stateSetCache = mStateSetCache[frameNum%2]; - + LightStateSetMap::iterator found = stateSetCache.find(hash); if (found != stateSetCache.end()) return found->second; else { osg::ref_ptr stateset = new osg::StateSet; - std::vector > lights; - lights.reserve(lightList.size()); - for (size_t i=0; imLightSource->getLight(frameNum)); if (usingFFP()) { + std::vector > lights; + lights.reserve(lightList.size()); + for (size_t i=0; imLightSource->getLight(frameNum)); + // the first light state attribute handles the actual state setting for all lights // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary // don't use setAttributeAndModes, that does not support light indices! @@ -656,14 +673,18 @@ namespace SceneUtil } else { - osg::ref_ptr buffer = new PointLightBuffer(lights.size()); - osg::ref_ptr ubo = new osg::UniformBufferObject; - buffer->getData()->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(8 ,buffer->getData().get(), 0, buffer->blockSize()); - stateset->addUniform(new osg::Uniform("PointLightCount", (int)lights.size())); - stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); - - stateset->setAttribute(new LightStateAttribute(std::move(lights), buffer), osg::StateAttribute::ON); + osg::ref_ptr indices = new osg::IntArray(getMaxLights()); + osg::ref_ptr indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", indices->size()); + int validCount = 0; + for (size_t i = 0; i < lightList.size(); ++i) + { + auto it = mLightData.find(lightList[i]->mLightSource->getId()); + if (it != mLightData.end()) + indices->at(validCount++) = it->second; + } + indicesUni->setArray(indices); + stateset->addUniform(indicesUni); + stateset->addUniform(new osg::Uniform("PointLightCount", validCount)); } stateSetCache.emplace(hash, stateset); @@ -671,11 +692,6 @@ namespace SceneUtil } } - const std::vector& LightManager::getLights() const - { - return mLights; - } - const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { osg::observer_ptr camPtr (camera); @@ -695,13 +711,47 @@ namespace SceneUtil l.mLightSource = lightIt->mLightSource; l.mViewBound = viewBound; it->second.push_back(l); + + if (usingFFP()) continue; + + auto* light = l.mLightSource->getLight(frameNum); + + auto dataIt = mLightData.find(l.mLightSource->getId()); + if (dataIt != mLightData.end()) + { + mPointLightProxyData[dataIt->second].mPosition = light->getPosition(); + mPointLightProxyData[dataIt->second].mBrightness = l.mLightSource->getBrightness(frameNum); + mPointBuffer->setValue(dataIt->second, PointLightBuffer::Diffuse, light->getDiffuse()); + continue; + } + + if (mLightData.size() >= static_cast(getMaxLightsInScene())) + { + mIndexNeedsRecompiling = true; + mLightData.clear(); + } + + int index = mLightData.size(); + updateGPUPointLight(index, l.mLightSource, frameNum); + mLightData.emplace(l.mLightSource->getId(), index); } } return it->second; } + void LightManager::updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum) + { + auto* light = lightSource->getLight(frameNum); + mPointLightProxyData[index].mPosition = light->getPosition(); + mPointLightProxyData[index].mBrightness = lightSource->getBrightness(frameNum); + mPointBuffer->setValue(index, PointLightBuffer::Diffuse, light->getDiffuse()); + mPointBuffer->setValue(index, PointLightBuffer::Ambient, light->getAmbient()); + mPointBuffer->setValue(index, PointLightBuffer::Attenuation, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); + } + LightSource::LightSource() : mRadius(0.f) + , mBrightness{1.0,1.0} { setUpdateCallback(new CollectLightCallback); mId = sLightId++; @@ -753,7 +803,7 @@ namespace SceneUtil // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); - + // get the node bounds in view space // NB do not node->getBound() * modelView, that would apply the node's transformation twice osg::BoundingSphere nodeBound; @@ -818,7 +868,6 @@ namespace SceneUtil else stateset = mLightManager->getLightListStateSet(mLightList, cv->getTraversalNumber()); - cv->pushStateSet(stateset); return true; } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 7ceb725aef..9c478b294b 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -2,13 +2,15 @@ #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H #include +#include +#include +#include #include #include #include #include -#include #include @@ -17,9 +19,16 @@ namespace osgUtil class CullVisitor; } +namespace osg +{ + class UniformBufferBinding; + class UniformBufferObject; +} + namespace SceneUtil { class SunlightBuffer; + class PointLightBuffer; // Used to override sun. Rarely useful but necassary for local map. class SunlightStateAttribute : public osg::StateAttribute @@ -59,6 +68,8 @@ namespace SceneUtil int mId; + float mBrightness[2]; + public: META_Node(SceneUtil, LightSource) @@ -78,6 +89,16 @@ namespace SceneUtil mRadius = radius; } + float getBrightness(size_t frame) + { + return mBrightness[frame % 2]; + } + + void setBrightness(size_t frame, float brightness) + { + mBrightness[frame % 2] = brightness; + } + /// Get the osg::Light safe for modification in the given frame. /// @par May be used externally to animate the light's color/attenuation properties, /// and is used internally to synchronize the light's position with the position of the LightSource. @@ -119,7 +140,7 @@ namespace SceneUtil osg::BoundingSphere mViewBound; }; - typedef std::vector LightList; + using LightList = std::vector; static bool queryNonFFPLightingSupport(); @@ -146,8 +167,6 @@ namespace SceneUtil /// Internal use only, called automatically by the LightSource's UpdateCallback void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); - const std::vector& getLights() const; - const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum); osg::ref_ptr getLightListStateSet(const LightList& lightList, size_t frameNum); @@ -165,6 +184,11 @@ namespace SceneUtil Shader::ShaderManager::DefineMap getLightDefines() const; private: + + friend class LightManagerStateAttribute; + + void updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum); + // Lights collected from the scene graph. Only valid during the cull traversal. std::vector mLights; @@ -184,6 +208,21 @@ namespace SceneUtil osg::ref_ptr mSun; osg::ref_ptr mSunBuffer; + struct PointLightProxyData + { + osg::Vec4 mPosition; + float mBrightness; + }; + + std::vector mPointLightProxyData; + osg::ref_ptr mPointBuffer; + + // < Light ID , Buffer Index > + using LightDataMap = std::unordered_map; + LightDataMap mLightData; + + bool mIndexNeedsRecompiling; + bool mFFP; static constexpr int mFFPMaxLights = 8; diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index e9be05908e..6c17117d01 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -58,7 +58,7 @@ namespace SceneUtil light->setQuadraticAttenuation(quadraticAttenuation); } - void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior) + void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior, bool useFFPLighting) { SceneUtil::FindByNameVisitor visitor("AttachLight"); node->accept(visitor); @@ -85,17 +85,21 @@ namespace SceneUtil attachTo = trans; } - osg::ref_ptr lightSource = createLightSource(esmLight, lightMask, isExterior); + osg::ref_ptr lightSource = createLightSource(esmLight, lightMask, isExterior, osg::Vec4f(0,0,0,1), useFFPLighting); attachTo->addChild(lightSource); } - osg::ref_ptr createLightSource(const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient) + osg::ref_ptr createLightSource(const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient, bool useFFPLighting) { osg::ref_ptr lightSource (new SceneUtil::LightSource); osg::ref_ptr light (new osg::Light); lightSource->setNodeMask(lightMask); float radius = esmLight->mData.mRadius; + // arbitrary multipler to reduce light popping, this is hard to avoid with per-object lighting + // we offset this multipler in shaders + if (!useFFPLighting) + radius *= 2.0; lightSource->setRadius(radius); configureLight(light, radius, isExterior); @@ -112,7 +116,7 @@ namespace SceneUtil lightSource->setLight(light); - osg::ref_ptr ctrl (new SceneUtil::LightController); + osg::ref_ptr ctrl (new SceneUtil::LightController(useFFPLighting)); ctrl->setDiffuse(light->getDiffuse()); if (esmLight->mData.mFlags & ESM::Light::Flicker) ctrl->setType(SceneUtil::LightController::LT_Flicker); diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp index 7096c38b20..bb28276e9b 100644 --- a/components/sceneutil/lightutil.hpp +++ b/components/sceneutil/lightutil.hpp @@ -32,14 +32,14 @@ namespace SceneUtil /// @param partsysMask Node mask to ignore when computing the sub graph's bounding box. /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. - void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior); + void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior, bool useFFPLighting=true); /// @brief Convert an ESM::Light to a SceneUtil::LightSource, and return it. /// @param esmLight The light definition coming from the game files containing radius, color, flicker, etc. /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. /// @param ambient Ambient component of the light. - osg::ref_ptr createLightSource (const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient=osg::Vec4f(0,0,0,1)); + osg::ref_ptr createLightSource (const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient=osg::Vec4f(0,0,0,1), bool useFFPLighting=true); } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 96671bf8b4..b1dc145f78 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -17,10 +17,9 @@ namespace Shader { - ShaderManager::ShaderManager(Resource::SceneManager* sceneManager) - : mSceneManager(sceneManager) + ShaderManager::ShaderManager() + : mFFPLighting(false) { - } void ShaderManager::setShaderPath(const std::string &path) @@ -28,6 +27,11 @@ namespace Shader mPath = path; } + void ShaderManager::setFFPLighting(bool useFFP) + { + mFFPLighting = useFFP; + } + bool addLineDirectivesAfterConditionalBlocks(std::string& source) { for (size_t position = 0; position < source.length(); ) @@ -352,10 +356,10 @@ namespace Shader program->addShader(fragmentShader); program->addBindAttribLocation("aOffset", 6); program->addBindAttribLocation("aRotation", 7); - if (!mSceneManager->getFFPLighting()) + if (!mFFPLighting) { - program->addBindUniformBlock("PointLightBuffer", 8); - program->addBindUniformBlock("SunlightBuffer", 9); + program->addBindUniformBlock("SunlightBuffer", 0); + program->addBindUniformBlock("PointLightBuffer", 1); } found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index b9bb005b11..c70085485d 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -25,10 +25,12 @@ namespace Shader { public: - ShaderManager(Resource::SceneManager* sceneManager); + ShaderManager(); void setShaderPath(const std::string& path); + void setFFPLighting(bool useFFP); + typedef std::map DefineMap; /// Create or retrieve a shader instance. @@ -67,7 +69,7 @@ namespace Shader typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; - Resource::SceneManager* mSceneManager; + bool mFFPLighting; std::mutex mMutex; }; diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index b9f426fc9d..b243a90c4e 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -6,19 +6,21 @@ struct PointLight { + vec4 position; vec4 diffuse; vec4 ambient; - vec4 position; - vec4 attenuation; + float constantAttenuation; + float linearAttenuation; + float quadraticAttenuation; + float radius; }; -uniform mat4 osg_ViewMatrix; uniform int PointLightCount; uniform int PointLightIndex[@maxLights]; layout(std140) uniform PointLightBuffer { - PointLight PointLights[@maxLights]; + PointLight PointLights[@maxLightsInScene]; }; #else @@ -47,15 +49,26 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi diffuseOut = @sunDiffuse.xyz * lambert; } + +uniform float osg_SimulationTime; void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) { - vec3 lightDir = getLight[lightIndex].position.xyz - viewPos; - //vec3 lightDir = (osg_ViewMatrix * vec4(getLight[lightIndex].position, 1.0)).xyz - viewPos; + vec4 pos = getLight[lightIndex].position; + vec3 lightDir = pos.xyz - viewPos; float lightDistance = length(lightDir); lightDir = normalize(lightDir); - float illumination = clamp(1.0 / (getLight[lightIndex].attenuation.x + getLight[lightIndex].attenuation.y * lightDistance + getLight[lightIndex].attenuation.z * lightDistance * lightDistance), 0.0, 1.0); + float illumination = clamp(1.0 / (getLight[lightIndex].constantAttenuation + getLight[lightIndex].linearAttenuation * lightDistance + getLight[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); + +// Add an artificial cutoff, otherwise effected objects will be brightly lit and adjacent objects not effected by this light will be dark by contrast +// This causes nasty artifacts, especially with active grid so it is necassary for now. +#if !@ffpLighting + float cutoff = getLight[lightIndex].radius * 0.5; + illumination *= 1.0 - smoothstep(0.0, 1.0, ((lightDistance / cutoff) - 1.0) * 0.887); + illumination = max(0.0, illumination); +#endif + ambientOut = getLight[lightIndex].ambient.xyz * illumination; float lambert = dot(viewNormal.xyz, lightDir) * illumination; @@ -70,7 +83,12 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec } lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif + +#if @ffpLighting diffuseOut = getLight[lightIndex].diffuse.xyz * lambert; +#else + diffuseOut = (getLight[lightIndex].diffuse.xyz * pos.w) * lambert; +#endif } #if PER_PIXEL_LIGHTING @@ -97,7 +115,7 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a diffuseLight += diffuseOut; for (int i=0; i Date: Sat, 27 Mar 2021 12:47:41 -0700 Subject: [PATCH 0566/2859] Brighter point lights and light fade --- components/sceneutil/lightmanager.cpp | 35 ++++++++++++++++++++++++--- components/sceneutil/lightmanager.hpp | 2 ++ files/settings-default.cfg | 8 +++++- files/shaders/lighting.glsl | 29 +++++++++++----------- 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 7215459202..d4275e5d28 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -56,7 +56,15 @@ namespace SceneUtil void setDiffuse(int index, const osg::Vec4& value) { - *(unsigned int*)(&(*mData)[3*index][0]) = asRGBA(value); + auto signedValue = value; + float signBit = 1.0; + if (value[0] < 0) + { + signedValue *= -1.0; + signBit = -1.0; + } + *(unsigned int*)(&(*mData)[3*index][0]) = asRGBA(signedValue); + *(int*)(&(*mData)[3*index][3]) = signBit; } void setAmbient(int index, const osg::Vec4& value) @@ -587,8 +595,16 @@ namespace SceneUtil : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) - , mPointLightRadiusMultiplier(std::max(0.0f, Settings::Manager::getFloat("light bounds multiplier", "Shaders"))) + , mPointLightRadiusMultiplier(std::max(0.f, Settings::Manager::getFloat("light bounds multiplier", "Shaders"))) + , mPointLightFadeStart(0.f) { + mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); + if (mPointLightFadeEnd > 0) + { + mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); + mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; + } + auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); bool validLightingModel = isValidLightingModelString(lightingModelString); if (!validLightingModel) @@ -869,6 +885,19 @@ namespace SceneUtil osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius * mPointLightRadiusMultiplier); transformBoundingSphere(worldViewMat, viewBound); + static const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; + + if (mPointLightFadeEnd != 0.f) + { + float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); + + if (fade == 0.f) + continue; + + auto* light = transform.mLightSource->getLight(frameNum); + light->setDiffuse(light->getDiffuse() * fade); + } + LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; @@ -896,7 +925,7 @@ namespace SceneUtil auto* light = lightSource->getLight(frameNum); auto& buf = getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); - buf->setAmbient(index, light->getSpecular()); + buf->setAmbient(index, light->getAmbient()); buf->setAttenuation(index, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation()); buf->setRadius(index, lightSource->getRadius()); buf->setPosition(index, light->getPosition() * (*viewMatrix)); diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index b430153b27..214ffa1385 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -215,6 +215,8 @@ namespace SceneUtil LightingMethod mLightingMethod; float mPointLightRadiusMultiplier; + float mPointLightFadeEnd; + float mPointLightFadeStart; int mMaxLights; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index cb23f7df73..b74d3ce02d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -448,7 +448,13 @@ lighting method = experimental # Sets the bounding sphere multiplier of light sources, which are used to determine if an object should # receive lighting. Higher values will allow for smoother transitions of light sources, but may have a performance cost and # requires a higher number of 'max lights' set. It is recommended to keep this at 1.0 with 'legacy' lighting enabled. -light bounds multiplier = 1.9 +light bounds multiplier = 2.0 + +# The distance from the camera at which lights fade away completely. Set to 0 to disable fading. +maximum light distance = 8192 + +# Fraction of the maximum distance at which lights begin to gradually fade away. +light fade start = 0.85 # Set maximum number of lights per object. # Only used when 'lighting method' is not set to 'legacy' diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 36be081efc..257c0513af 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -16,22 +16,21 @@ float quickstep(float x) #if @useUBO const uint mask = uint(0xff); +const uvec4 shift = uvec4(uint(0), uint(8), uint(16), uint(24)); -vec3 unpackRGB(float data) +vec3 unpackRGB(uint data) { - uint colors = uint(data); - return vec3( (((colors >> 0) & mask) / 255.0) - ,(((colors >> 8) & mask) / 255.0) - ,(((colors >> 16) & mask) / 255.0)); + return vec3( (float(((data >> shift.x) & mask)) / 255.0) + ,(float(((data >> shift.y) & mask)) / 255.0) + ,(float(((data >> shift.z) & mask)) / 255.0)); } -vec4 unpackRGBA(float data) +vec4 unpackRGBA(uint data) { - uint colors = uint(data); - return vec4( (((colors >> 0) & mask) / 255.0) - ,(((colors >> 8) & mask) / 255.0) - ,(((colors >> 16) & mask) / 255.0) - ,(((colors >> 24) & mask) / 255.0)); + return vec4( (float(((data >> shift.x) & mask)) / 255.0) + ,(float(((data >> shift.y) & mask)) / 255.0) + ,(float(((data >> shift.z) & mask)) / 255.0) + ,(float(((data >> shift.w) & mask)) / 255.0)); } struct LightData @@ -74,7 +73,7 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi vec3 lightDir = normalize(getLight[0].position.xyz); #if @lightingModel == LIGHTING_MODEL_SINGLE_UBO - vec4 data = getLight[0].packedColors; + uvec4 data = getLight[0].packedColors; ambientOut = unpackRGB(data.y); vec3 sunDiffuse = unpackRGB(data.x); #else @@ -108,11 +107,11 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec float illumination = clamp(1.0 / (getLight[lightIndex].constantAttenuation + getLight[lightIndex].linearAttenuation * lightDistance + getLight[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); #else float illumination = clamp(1.0 / (getLight[lightIndex].attenuation.x + getLight[lightIndex].attenuation.y * lightDistance + getLight[lightIndex].attenuation.z * lightDistance * lightDistance), 0.0, 1.0); - illumination *= 1.0 - quickstep((lightDistance * 0.887 / getLight[lightIndex].attenuation.w) - 0.887); + illumination *= 1.0 - quickstep((lightDistance / (getLight[lightIndex].attenuation.w)) - 1.0); #endif #if @useUBO - vec4 data = getLight[lightIndex].packedColors; + uvec4 data = getLight[lightIndex].packedColors; ambientOut = unpackRGB(data.y) * illumination; #else ambientOut = getLight[lightIndex].ambient.xyz * illumination; @@ -133,7 +132,7 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec #endif #if @useUBO - diffuseOut = unpackRGB(data.x) * lambert; + diffuseOut = unpackRGB(data.x) * lambert * float(int(data.w)); #else diffuseOut = getLight[lightIndex].diffuse.xyz * lambert; #endif From ec27e60284511a11ce9f775f2b4d27f5548cfd7e Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 27 Mar 2021 23:41:22 -0700 Subject: [PATCH 0567/2859] Cutoff conditional in light shader --- files/settings-default.cfg | 2 +- files/shaders/lighting.glsl | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index b74d3ce02d..102c1aa66c 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -448,7 +448,7 @@ lighting method = experimental # Sets the bounding sphere multiplier of light sources, which are used to determine if an object should # receive lighting. Higher values will allow for smoother transitions of light sources, but may have a performance cost and # requires a higher number of 'max lights' set. It is recommended to keep this at 1.0 with 'legacy' lighting enabled. -light bounds multiplier = 2.0 +light bounds multiplier = 1.0 # The distance from the camera at which lights fade away completely. Set to 0 to disable fading. maximum light distance = 8192 diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 257c0513af..4b7e233146 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -101,6 +101,17 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec vec3 lightDir = getLight[lightIndex].position.xyz - viewPos; float lightDistance = length(lightDir); + +#if !@ffpLighting + // This has a *considerable* performance uplift where GPU is a bottleneck + if (lightDistance > getLight[lightIndex].attenuation.w * 2.0) + { + ambientOut = vec3(0.0); + diffuseOut = vec3(0.0); + return; + } +#endif + lightDir = normalize(lightDir); #if @ffpLighting From 24454a1698d8331f8b822b6f0f004f432f7964bb Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 27 Mar 2021 23:52:05 -0700 Subject: [PATCH 0568/2859] Switch to integer, uint not reliable in GLSL 120 --- files/shaders/lighting.glsl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 4b7e233146..c6c03f2232 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -15,17 +15,17 @@ float quickstep(float x) #if @useUBO -const uint mask = uint(0xff); -const uvec4 shift = uvec4(uint(0), uint(8), uint(16), uint(24)); +const int mask = int(0xff); +const ivec4 shift = ivec4(int(0), int(8), int(16), int(24)); -vec3 unpackRGB(uint data) +vec3 unpackRGB(int data) { return vec3( (float(((data >> shift.x) & mask)) / 255.0) ,(float(((data >> shift.y) & mask)) / 255.0) ,(float(((data >> shift.z) & mask)) / 255.0)); } -vec4 unpackRGBA(uint data) +vec4 unpackRGBA(int data) { return vec4( (float(((data >> shift.x) & mask)) / 255.0) ,(float(((data >> shift.y) & mask)) / 255.0) @@ -35,7 +35,7 @@ vec4 unpackRGBA(uint data) struct LightData { - uvec4 packedColors; // diffuse, ambient, specular + ivec4 packedColors; // diffuse, ambient, specular vec4 position; vec4 attenuation; // constant, linear, quadratic, radius }; @@ -73,7 +73,7 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi vec3 lightDir = normalize(getLight[0].position.xyz); #if @lightingModel == LIGHTING_MODEL_SINGLE_UBO - uvec4 data = getLight[0].packedColors; + ivec4 data = getLight[0].packedColors; ambientOut = unpackRGB(data.y); vec3 sunDiffuse = unpackRGB(data.x); #else @@ -122,7 +122,7 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec #endif #if @useUBO - uvec4 data = getLight[lightIndex].packedColors; + ivec4 data = getLight[lightIndex].packedColors; ambientOut = unpackRGB(data.y) * illumination; #else ambientOut = getLight[lightIndex].ambient.xyz * illumination; From 328ec85757b4dae0ca7b9466148781989c2bdfd6 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 28 Mar 2021 11:00:52 -0700 Subject: [PATCH 0569/2859] Code review cleanup, add setting documentation --- apps/openmw/engine.cpp | 1 - components/sceneutil/lightmanager.cpp | 102 +++++++++--------- .../reference/modding/settings/shaders.rst | 63 +++++++++++ files/shaders/objects_fragment.glsl | 2 +- files/shaders/terrain_fragment.glsl | 2 +- 5 files changed, 117 insertions(+), 53 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 019e9f67a9..d7c315323d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -31,7 +31,6 @@ #include #include -#include #include diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index d4275e5d28..3c1f31364f 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -16,7 +16,7 @@ #include "apps/openmw/mwrender/vismask.hpp" -namespace +namespace { /* similar to the boost::hash_combine */ template @@ -136,33 +136,35 @@ namespace SceneUtil switch (method) { case LightingMethod::FFP: - break; + { + break; + } case LightingMethod::PerObjectUniform: - { - stateset->addUniform(new osg::Uniform("LightBuffer[0].diffuse", light->getDiffuse()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].ambient", light->getAmbient()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].specular", light->getSpecular()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].position", light->getPosition()), mode); + { + stateset->addUniform(new osg::Uniform("LightBuffer[0].diffuse", light->getDiffuse()), mode); + stateset->addUniform(new osg::Uniform("LightBuffer[0].ambient", light->getAmbient()), mode); + stateset->addUniform(new osg::Uniform("LightBuffer[0].specular", light->getSpecular()), mode); + stateset->addUniform(new osg::Uniform("LightBuffer[0].position", light->getPosition()), mode); - break; - } + break; + } case LightingMethod::SingleUBO: - { - osg::ref_ptr buffer = new LightBuffer(1); + { + osg::ref_ptr buffer = new LightBuffer(1); - buffer->setDiffuse(0, light->getDiffuse()); - buffer->setAmbient(0, light->getAmbient()); - buffer->setSpecular(0, light->getSpecular()); - buffer->setPosition(0, light->getPosition()); + buffer->setDiffuse(0, light->getDiffuse()); + buffer->setAmbient(0, light->getAmbient()); + buffer->setSpecular(0, light->getSpecular()); + buffer->setPosition(0, light->getPosition()); - osg::ref_ptr ubo = new osg::UniformBufferObject; - buffer->mData->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->mData.get(), 0, buffer->mData->getTotalDataSize()); + osg::ref_ptr ubo = new osg::UniformBufferObject; + buffer->mData->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->mData.get(), 0, buffer->mData->getTotalDataSize()); - stateset->setAttributeAndModes(ubb, mode); + stateset->setAttributeAndModes(ubb, mode); - break; - } + break; + } } } @@ -201,9 +203,9 @@ namespace SceneUtil void apply(osg::State& state) const override { int lightNum = GL_LIGHT0 + mIndex; - glLightfv( lightNum, GL_AMBIENT, mnullptr.ptr() ); - glLightfv( lightNum, GL_DIFFUSE, mnullptr.ptr() ); - glLightfv( lightNum, GL_SPECULAR, mnullptr.ptr() ); + glLightfv(lightNum, GL_AMBIENT, mnullptr.ptr()); + glLightfv(lightNum, GL_DIFFUSE, mnullptr.ptr()); + glLightfv(lightNum, GL_SPECULAR, mnullptr.ptr()); LightStateCache* cache = getLightStateCache(state.getContextID()); cache->lastAppliedLight[mIndex] = nullptr; @@ -334,7 +336,7 @@ namespace SceneUtil cache->lastAppliedLight[i] = mLights[i]; } } - + state.applyModelViewMatrix(modelViewMatrix); } @@ -344,7 +346,7 @@ namespace SceneUtil }; struct StateSetGenerator - { + { LightManager* mLightManager; virtual ~StateSetGenerator() {} @@ -475,9 +477,9 @@ namespace SceneUtil mLightManager = findLightManager(nv->getNodePath()); if (!mLightManager) - throw std::runtime_error("can't find parent LightManager"); + throw std::runtime_error("can't find parent LightManager"); } - + mLightManager->addLight(static_cast(node), osg::computeLocalToWorld(nv->getNodePath()), nv->getTraversalNumber()); traverse(node, nv); @@ -523,7 +525,7 @@ namespace SceneUtil mLastFrameNumber = cv->getTraversalNumber(); if (mLightManager->getLightingMethod() == LightingMethod::SingleUBO) - { + { auto stateset = mLightManager->getStateSet(); auto bo = mLightManager->getLightBuffer(mLastFrameNumber); osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData().get(), 0, bo->getData()->getTotalDataSize()); @@ -548,7 +550,7 @@ namespace SceneUtil buf->setDiffuse(0, sun->getDiffuse()); buf->setAmbient(0, sun->getAmbient()); buf->setSpecular(0, sun->getSpecular()); - buf->setPosition(0, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + buf->setPosition(0, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); } } } @@ -560,12 +562,12 @@ namespace SceneUtil LightManager* mLightManager; size_t mLastFrameNumber; }; - + class LightManagerStateAttribute : public osg::StateAttribute { public: LightManagerStateAttribute() : mLightManager(nullptr) {} - LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} + LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} LightManagerStateAttribute(const LightManagerStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop),mLightManager(copy.mLightManager) {} @@ -595,7 +597,7 @@ namespace SceneUtil : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) - , mPointLightRadiusMultiplier(std::max(0.f, Settings::Manager::getFloat("light bounds multiplier", "Shaders"))) + , mPointLightRadiusMultiplier(std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f)) , mPointLightFadeStart(0.f) { mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); @@ -608,7 +610,7 @@ namespace SceneUtil auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); bool validLightingModel = isValidLightingModelString(lightingModelString); if (!validLightingModel) - Log(Debug::Error) << "Invalid option for 'lighting model': got '" << lightingModelString + Log(Debug::Error) << "Invalid option for 'lighting model': got '" << lightingModelString << "', expected legacy, default, or experimental."; if (ffp || !validLightingModel) @@ -625,10 +627,10 @@ namespace SceneUtil osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); bool supportsUBO = exts && exts->isUniformBufferObjectSupported; - bool supportsGPU4 = exts && exts->isGpuShader4Supported; + bool supportsGPU4 = exts && exts->isGpuShader4Supported; if (!supportsUBO) - Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; + Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; else if (!supportsGPU4) Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; @@ -648,7 +650,7 @@ namespace SceneUtil osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); - + mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); mLightUniforms[i].emplace(UniformKey::Ambient, uambient); mLightUniforms[i].emplace(UniformKey::Specular, uspecular); @@ -681,7 +683,7 @@ namespace SceneUtil stateset->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); } - + stateset->addUniform(new osg::Uniform("PointLightCount", 0)); setUpdateCallback(new LightManagerUpdateCallback); @@ -720,10 +722,10 @@ namespace SceneUtil { mMaxLights = value; } - + int LightManager::getMaxLightsInScene() const { - static constexpr int max = 16384 / LightBuffer::queryBlockSize(1); + static constexpr int max = 16384 / LightBuffer::queryBlockSize(1); return max; } @@ -732,7 +734,7 @@ namespace SceneUtil Shader::ShaderManager::DefineMap defines; bool ffp = usingFFP(); - + defines["ffpLighting"] = ffp ? "1" : "0"; defines["maxLights"] = std::to_string(getMaxLights()); defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene()); @@ -794,7 +796,7 @@ namespace SceneUtil } void LightManager::update(size_t frameNum) - { + { getLightIndexMap(frameNum).clear(); mLights.clear(); mLightsInViewSpace.clear(); @@ -802,7 +804,7 @@ namespace SceneUtil // Do an occasional cleanup for orphaned lights. for (int i=0; i<2; ++i) { - if (mStateSetCache[i].size() > 5000) + if (mStateSetCache[i].size() > 5000) mStateSetCache[i].clear(); } } @@ -839,9 +841,9 @@ namespace SceneUtil auto id = lightList[i]->mLightSource->getId(); hash_combine(hash, id); - if (getLightingMethod() != LightingMethod::SingleUBO) + if (getLightingMethod() != LightingMethod::SingleUBO) continue; - + if (getLightIndexMap(frameNum).find(id) != getLightIndexMap(frameNum).end()) continue; @@ -875,11 +877,11 @@ namespace SceneUtil if (it == mLightsInViewSpace.end()) { it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; - + for (const auto& transform : mLights) { osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix); - + float radius = transform.mLightSource->getRadius(); osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius * mPointLightRadiusMultiplier); @@ -901,7 +903,7 @@ namespace SceneUtil LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; - it->second.push_back(l); + it->second.push_back(l); } } @@ -985,7 +987,7 @@ namespace SceneUtil // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); - + // get the node bounds in view space // NB do not node->getBound() * modelView, that would apply the node's transformation twice osg::BoundingSphere nodeBound; @@ -1026,7 +1028,7 @@ namespace SceneUtil for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights; ) { osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); - + osg::BoundingSphere bs = (*it)->mViewBound; bs._radius = bs._radius * 2.0; osg::CullingSet& cullingSet = stack.front(); diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index acc8482991..e1acd9411b 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -148,6 +148,69 @@ By default, the fog becomes thicker proportionally to your distance from the cli This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. +lighting method +--------------- + +:Type: string +:Range: legacy|default|experimental +:Default: default + +Sets the internal handling of light sources. + +'legacy' is restricted to a maximum of 8 lights per object and guarantees fixed function pipeline compatible lighting. + +'default' removes the light limit via :ref:`max lights` and follows a new attenuation formula which can drastically reduce light popping and seams. +It is recommended to use this mode with older hardware, as the technique ensures a range of compatibility equal to that of 'legacy'. + +'experimental' carries all of the benefits that 'legacy' has, but uses a modern approach that allows for a higher 'max lights' count with little to no performance penalties on modern hardware. + +light bounds multiplier +----------------------- + +:Type: float +:Range: 0.0-10.0 +:Default: 2.0 + +Controls the bounding sphere radius of point lights, which is used to determine if an object should receive lighting from a particular light source. +Note, this has no direct effect on the overall illumination of lights. +Larger multipliers will allow for smoother transitions of light sources, but may require an increase in :ref:`max lights` and thus carries a performance penalty. +This especially helps with abrupt light popping with handheld light sources such as torches and lanterns. + +It is recommended to keep this at 1.0 if :ref:`lighting method` is set to 'legacy', as the number of lights is fixed in that mode. + +maximum light distance +---------------------- + +:Type: float +:Range: The whole range of 32-bit floating point +:Default: 8192 + +The maximum distance from the camera that lights will be illuminated, applies to both interiors and exteriors. +A lower distance will improve performance. +Set this to a non-positive value to disable fading. + +light fade start +---------------- + +:Type: float +:Range: 0.0-1.0 +:Default: 0.85 + +The fraction of the maximum distance at which lights will begin to fade away. +Tweaking it will make the transition proportionally more or less smooth. +This setting has no effect if the maximum light distance is non-positive. + +max lights +---------- + +:Type: integer +:Range: >=2 +:Default: 16 + +Sets the maximum number of lights that each object can receive lighting from. +Has no effect if :ref:`force shaders` option is off or :ref:`lighting method` is 'legacy'. In this case the maximum number of lights is fixed at 8. +Increasing this too much can cause significant performance loss, especially if :ref:`lighting method` is not set to 'experimental' or :ref:`force per pixel lighting` is on. + antialias alpha test --------------------------------------- diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index e26d0f44cd..615b57a8a2 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -76,8 +76,8 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_fragment.glsl" -#include "parallax.glsl" #include "lighting.glsl" +#include "parallax.glsl" #include "alpha.glsl" void main() diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index a8bf735528..6a7ac0bcc4 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -34,8 +34,8 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_fragment.glsl" -#include "parallax.glsl" #include "lighting.glsl" +#include "parallax.glsl" void main() { From 08b568128416d7e16211993f555a1ac79ee557a9 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 28 Mar 2021 11:06:00 -0700 Subject: [PATCH 0570/2859] Missed redundant formatting changes --- files/shaders/objects_vertex.glsl | 1 + files/shaders/terrain_vertex.glsl | 1 + 2 files changed, 2 insertions(+) diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 104f42c09c..7baaa4176a 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -60,6 +60,7 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_vertex.glsl" + #include "lighting.glsl" void main(void) diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 2bf74a0e12..ad1201b932 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -23,6 +23,7 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_vertex.glsl" + #include "lighting.glsl" void main(void) From 142c6d2993141a65d3d1cb37b312beddac638590 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 28 Mar 2021 16:46:51 -0700 Subject: [PATCH 0571/2859] Enable groundcover lighting for non FFP --- apps/openmw/mwrender/groundcover.cpp | 3 +++ docs/source/reference/modding/settings/shaders.rst | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 0baa85c52a..fd22462539 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwbase/environment.hpp" @@ -271,6 +272,8 @@ namespace MWRender group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); group->getBound(); group->setNodeMask(Mask_Groundcover); + if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) + group->setCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", false, true); return group; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index e1acd9411b..6af5e9ba02 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -160,6 +160,7 @@ Sets the internal handling of light sources. 'legacy' is restricted to a maximum of 8 lights per object and guarantees fixed function pipeline compatible lighting. 'default' removes the light limit via :ref:`max lights` and follows a new attenuation formula which can drastically reduce light popping and seams. +This mode also enables vertex lighting on groundcover, which is otherwise completely disabled with 'legacy'. It is recommended to use this mode with older hardware, as the technique ensures a range of compatibility equal to that of 'legacy'. 'experimental' carries all of the benefits that 'legacy' has, but uses a modern approach that allows for a higher 'max lights' count with little to no performance penalties on modern hardware. From 690995988bd4f0251ebb2de0a71aa6813566139e Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 29 Mar 2021 00:13:35 -0700 Subject: [PATCH 0572/2859] More formatting, OpenCS cells are unbroken --- apps/opencs/model/world/data.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 10 +- components/sceneutil/lightmanager.cpp | 257 +++++++++++------- components/sceneutil/lightmanager.hpp | 26 +- components/shader/shadermanager.cpp | 1 - .../reference/modding/settings/shaders.rst | 68 +++-- files/settings-default.cfg | 31 ++- files/shaders/lighting.glsl | 14 +- files/shaders/water_fragment.glsl | 2 + 9 files changed, 252 insertions(+), 159 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 25ac051f41..31778b9047 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -83,7 +83,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat defines["clamp"] = "1"; // Clamp lighting defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["radialFog"] = "0"; - defines["ffpLighting"] = "0"; + defines["ffpLighting"] = "1"; 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 79f34c42ed..229c34f452 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -199,8 +199,8 @@ namespace MWRender , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) { - auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); - bool usingFFPLighting = lightingModelString == "legacy" && SceneUtil::LightManager::isValidLightingModelString(lightingModelString); + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); + bool usingFFPLighting = lightingMethod == SceneUtil::LightingMethod::FFP; resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); @@ -217,9 +217,9 @@ namespace MWRender resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); - + osg::ref_ptr sceneRoot = new SceneUtil::LightManager(!forceShaders || usingFFPLighting); - // Let LightManager choose which backend to use based, mostly depends on support for UBOs + // Let LightManager choose which backend to use based on our hint, mostly depends on support for UBOs resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); @@ -262,7 +262,7 @@ namespace MWRender float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); - + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 3c1f31364f..72470b8c2c 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -14,8 +14,6 @@ #include -#include "apps/openmw/mwrender/vismask.hpp" - namespace { /* similar to the boost::hash_combine */ @@ -28,7 +26,8 @@ namespace bool sortLights(const SceneUtil::LightManager::LightSourceViewBound* left, const SceneUtil::LightManager::LightSourceViewBound* right) { - return left->mViewBound.center().length2() - left->mViewBound.radius2()*81 < right->mViewBound.center().length2() - right->mViewBound.radius2()*81; + static auto constexpr illuminationBias = 81.f; + return left->mViewBound.center().length2() - left->mViewBound.radius2()*illuminationBias < right->mViewBound.center().length2() - right->mViewBound.radius2()*illuminationBias; } float getLightRadius(const osg::Light* light) @@ -99,8 +98,15 @@ namespace SceneUtil return (*mData)[3*index+1]; } - auto& getData() { return mData; } - void dirty() { mData->dirty(); } + auto& getData() + { + return mData; + } + + void dirty() + { + mData->dirty(); + } static constexpr int queryBlockSize(int sz) { @@ -135,6 +141,7 @@ namespace SceneUtil { switch (method) { + case LightingMethod::Undefined: case LightingMethod::FFP: { break; @@ -232,7 +239,7 @@ namespace SceneUtil bool getModeUsage(ModeUsage & usage) const override { - for (size_t i=0; ilastAppliedLight[i+mIndex]; if (current != mLights[i].get()) @@ -269,29 +276,28 @@ namespace SceneUtil void applyLight(GLenum lightNum, const osg::Light* light) const { - glLightfv( lightNum, GL_AMBIENT, light->getAmbient().ptr() ); - glLightfv( lightNum, GL_DIFFUSE, light->getDiffuse().ptr() ); - glLightfv( lightNum, GL_SPECULAR, light->getSpecular().ptr() ); - glLightfv( lightNum, GL_POSITION, light->getPosition().ptr() ); + glLightfv(lightNum, GL_AMBIENT, light->getAmbient().ptr()); + glLightfv(lightNum, GL_DIFFUSE, light->getDiffuse().ptr()); + glLightfv(lightNum, GL_SPECULAR, light->getSpecular().ptr()); + glLightfv(lightNum, GL_POSITION, light->getPosition().ptr()); // TODO: enable this once spot lights are supported // need to transform SPOT_DIRECTION by the world matrix? - //glLightfv( lightNum, GL_SPOT_DIRECTION, light->getDirection().ptr() ); - //glLightf ( lightNum, GL_SPOT_EXPONENT, light->getSpotExponent() ); - //glLightf ( lightNum, GL_SPOT_CUTOFF, light->getSpotCutoff() ); - glLightf ( lightNum, GL_CONSTANT_ATTENUATION, light->getConstantAttenuation() ); - glLightf ( lightNum, GL_LINEAR_ATTENUATION, light->getLinearAttenuation() ); - glLightf ( lightNum, GL_QUADRATIC_ATTENUATION, light->getQuadraticAttenuation() ); + //glLightfv(lightNum, GL_SPOT_DIRECTION, light->getDirection().ptr()); + //glLightf(lightNum, GL_SPOT_EXPONENT, light->getSpotExponent()); + //glLightf(lightNum, GL_SPOT_CUTOFF, light->getSpotCutoff()); + glLightf(lightNum, GL_CONSTANT_ATTENUATION, light->getConstantAttenuation()); + glLightf(lightNum, GL_LINEAR_ATTENUATION, light->getLinearAttenuation()); + glLightf(lightNum, GL_QUADRATIC_ATTENUATION, light->getQuadraticAttenuation()); } private: size_t mIndex; - std::vector> mLights; }; LightManager* findLightManager(const osg::NodePath& path) { - for (size_t i=0;i(path[i])) return lightManager; @@ -406,8 +412,8 @@ namespace SceneUtil return stateset; } - // Cached statesets must be re-validated in case the light indicies change. There is no actual link between - // a lights ID and the buffer index it will eventually be assigned (or reassigned) to. + // Cached statesets must be revalidated in case the light indices change. There is no actual link between + // a light's ID and the buffer index it will eventually be assigned (or reassigned) to. void update(osg::StateSet* stateset, const LightManager::LightList& lightList, size_t frameNum) override { int newCount = 0; @@ -587,104 +593,88 @@ namespace SceneUtil LightManager* mLightManager; }; + const std::unordered_map LightManager::mLightingMethodSettingMap = { + {"legacy", LightingMethod::FFP} + ,{"shaders compatibility", LightingMethod::PerObjectUniform} + ,{"shaders", LightingMethod::SingleUBO} + }; + bool LightManager::isValidLightingModelString(const std::string& value) { - static const std::unordered_set validLightingModels = {"legacy", "default", "experimental"}; - return validLightingModels.count(value) != 0; + return LightManager::mLightingMethodSettingMap.find(value) != LightManager::mLightingMethodSettingMap.end(); + } + + LightingMethod LightManager::getLightingMethodFromString(const std::string& value) + { + auto it = LightManager::mLightingMethodSettingMap.find(value); + if (it != LightManager::mLightingMethodSettingMap.end()) + return it->second; + else + return LightingMethod::Undefined; } LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) - , mPointLightRadiusMultiplier(std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f)) + , mPointLightRadiusMultiplier(1.f) + , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { - mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); - if (mPointLightFadeEnd > 0) + if (ffp) { - mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); - mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; + setLightingMethod(LightingMethod::FFP); + initFFP(LightManager::mFFPMaxLights); + return; } - auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); - bool validLightingModel = isValidLightingModelString(lightingModelString); - if (!validLightingModel) - Log(Debug::Error) << "Invalid option for 'lighting model': got '" << lightingModelString - << "', expected legacy, default, or experimental."; - - if (ffp || !validLightingModel) + std::string lightingMethodString = Settings::Manager::getString("lighting method", "Shaders"); + auto lightingMethod = LightManager::getLightingMethodFromString(lightingMethodString); + if (lightingMethod == LightingMethod::Undefined) { - setMaxLights(LightManager::mFFPMaxLights); - setLightingMethod(LightingMethod::FFP); + Log(Debug::Error) << "Invalid option for 'lighting method': got '" << lightingMethodString + << "', expected legacy, shaders compatible, or shaders. Falling back to 'shaders compatible'."; + setLightingMethod(LightingMethod::PerObjectUniform); + } + else + { + setLightingMethod(lightingMethod); + } - for (int i=0; i >())); + mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f); - setUpdateCallback(new LightManagerUpdateCallback); - return; + mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); + if (mPointLightFadeEnd > 0) + { + mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); + mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; } osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); bool supportsUBO = exts && exts->isUniformBufferObjectSupported; bool supportsGPU4 = exts && exts->isGpuShader4Supported; - if (!supportsUBO) - Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; - else if (!supportsGPU4) - Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; + if (getLightingMethod() == LightingMethod::SingleUBO) + { + if (!supportsUBO) + Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; + else if (!supportsGPU4) + Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; + } int targetLights = Settings::Manager::getInt("max lights", "Shaders"); - auto* stateset = getOrCreateStateSet(); - if (!supportsUBO || !supportsGPU4 || lightingModelString == "default") + if (!supportsUBO || !supportsGPU4 || getLightingMethod() == LightingMethod::PerObjectUniform) { setLightingMethod(LightingMethod::PerObjectUniform); - setMaxLights(std::max(2, targetLights)); - - mLightUniforms.resize(getMaxLights()+1); - for (size_t i = 0; i < mLightUniforms.size(); ++i) - { - osg::ref_ptr udiffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].diffuse").c_str()); - osg::ref_ptr uspecular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].specular").c_str()); - osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); - osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); - osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); - - mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); - mLightUniforms[i].emplace(UniformKey::Ambient, uambient); - mLightUniforms[i].emplace(UniformKey::Specular, uspecular); - mLightUniforms[i].emplace(UniformKey::Position, uposition); - mLightUniforms[i].emplace(UniformKey::Attenuation, uattenuation); - - stateset->addUniform(udiffuse); - stateset->addUniform(uambient); - stateset->addUniform(uposition); - stateset->addUniform(uattenuation); - - // specular isn't used besides sun, complete waste to upload it - if (i == 0) - stateset->addUniform(uspecular); - } + initPerObjectUniform(targetLights); } else { - setLightingMethod(LightingMethod::SingleUBO); - setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); - - for (int i = 0; i < 2; ++i) - { - mLightBuffers[i] = new LightBuffer(getMaxLightsInScene()); - osg::ref_ptr ubo = new osg::UniformBufferObject; - ubo->setUsage(GL_STREAM_DRAW); - - mLightBuffers[i]->getData()->setBufferObject(ubo); - } - - stateset->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); + initSingleUBO(targetLights); } - stateset->addUniform(new osg::Uniform("PointLightCount", 0)); + getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); setUpdateCallback(new LightManagerUpdateCallback); addCullCallback(new LightManagerCullCallback(this)); @@ -726,7 +716,7 @@ namespace SceneUtil int LightManager::getMaxLightsInScene() const { static constexpr int max = 16384 / LightBuffer::queryBlockSize(1); - return max; + return max; } Shader::ShaderManager::DefineMap LightManager::getLightDefines() const @@ -746,6 +736,66 @@ namespace SceneUtil return defines; } + void LightManager::initFFP(int targetLights) + { + setMaxLights(targetLights); + + for (int i = 0; i < getMaxLights(); ++i) + mDummies.push_back(new FFPLightStateAttribute(i, std::vector>())); + + setUpdateCallback(new LightManagerUpdateCallback); + } + + void LightManager::initPerObjectUniform(int targetLights) + { + auto* stateset = getOrCreateStateSet(); + + setMaxLights(std::max(2, targetLights)); + + mLightUniforms.resize(getMaxLights()+1); + for (size_t i = 0; i < mLightUniforms.size(); ++i) + { + osg::ref_ptr udiffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].diffuse").c_str()); + osg::ref_ptr uspecular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].specular").c_str()); + osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); + osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); + osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); + + mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); + mLightUniforms[i].emplace(UniformKey::Ambient, uambient); + mLightUniforms[i].emplace(UniformKey::Specular, uspecular); + mLightUniforms[i].emplace(UniformKey::Position, uposition); + mLightUniforms[i].emplace(UniformKey::Attenuation, uattenuation); + + stateset->addUniform(udiffuse); + stateset->addUniform(uambient); + stateset->addUniform(uposition); + stateset->addUniform(uattenuation); + + // specular isn't used besides sun, complete waste to upload it + if (i == 0) + stateset->addUniform(uspecular); + } + } + + void LightManager::initSingleUBO(int targetLights) + { + setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); + + for (int i = 0; i < 2; ++i) + { + mLightBuffers[i] = new LightBuffer(getMaxLightsInScene()); + + osg::ref_ptr ubo = new osg::UniformBufferObject; + ubo->setUsage(GL_STREAM_DRAW); + + mLightBuffers[i]->getData()->setBufferObject(ubo); + } + + getOrCreateStateSet()->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); + } + + void LightManager::setLightingMethod(LightingMethod method) { mLightingMethod = method; @@ -760,6 +810,9 @@ namespace SceneUtil case LightingMethod::PerObjectUniform: mStateSetGenerator = std::make_unique(); break; + case LightingMethod::Undefined: + mStateSetGenerator = nullptr; + break; } mStateSetGenerator->mLightManager = this; } @@ -783,7 +836,7 @@ namespace SceneUtil // Set default light state to zero // This is necessary because shaders don't respect glDisable(GL_LIGHTX) so in addition to disabling // we'll have to set a light state that has no visible effect - for (int i=start; i defaultLight (new DisableLight(i)); getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); @@ -802,7 +855,7 @@ namespace SceneUtil mLightsInViewSpace.clear(); // Do an occasional cleanup for orphaned lights. - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { if (mStateSetCache[i].size() > 5000) mStateSetCache[i].clear(); @@ -836,7 +889,7 @@ namespace SceneUtil { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; - for (size_t i=0; imLightSource->getId(); hash_combine(hash, id); @@ -860,13 +913,10 @@ namespace SceneUtil mStateSetGenerator->update(found->second, lightList, frameNum); return found->second; } - else - { - auto stateset = mStateSetGenerator->generate(lightList, frameNum); - stateSetCache.emplace(hash, stateset); - return stateset; - } - return new osg::StateSet; + + auto stateset = mStateSetGenerator->generate(lightList, frameNum); + stateSetCache.emplace(hash, stateset); + return stateset; } const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) @@ -946,7 +996,7 @@ namespace SceneUtil { mId = sLightId++; - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } @@ -981,7 +1031,6 @@ namespace SceneUtil // makes sure we don't update it more than once per frame when rendering with multiple cameras if (mLastFrameNumber != cv->getTraversalNumber()) { - mLastFrameNumber = cv->getTraversalNumber(); // Don't use Camera::getViewMatrix, that one might be relative to another camera! @@ -994,7 +1043,7 @@ namespace SceneUtil osg::Transform* transform = node->asTransform(); if (transform) { - for (size_t i=0; igetNumChildren(); ++i) + for (size_t i = 0; i < transform->getNumChildren(); ++i) nodeBound.expandBy(transform->getChild(i)->getBound()); } else @@ -1003,7 +1052,7 @@ namespace SceneUtil transformBoundingSphere(mat, nodeBound); mLightList.clear(); - for (size_t i=0; igetMaxLights() - mLightManager->getStartLight(); @@ -1025,7 +1073,7 @@ namespace SceneUtil { // remove lights culled by this camera LightManager::LightList lightList = mLightList; - for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights; ) + for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) { osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); @@ -1053,6 +1101,7 @@ namespace SceneUtil else stateset = mLightManager->getLightListStateSet(mLightList, cv->getTraversalNumber(), cv->getCurrentRenderStage()->getInitialViewMatrix()); + cv->pushStateSet(stateset); return true; } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 214ffa1385..cd8f2d3127 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -2,12 +2,12 @@ #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H #include -#include -#include #include +#include #include #include + #include #include #include @@ -18,6 +18,7 @@ namespace osgUtil { class CullVisitor; } + namespace SceneUtil { class LightBuffer; @@ -27,7 +28,8 @@ namespace SceneUtil { FFP, SingleUBO, - PerObjectUniform + PerObjectUniform, + Undefined }; void configureStateSetSunOverride(LightingMethod method, const osg::Light* light, osg::StateSet* stateset, int mode = osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); @@ -98,8 +100,8 @@ namespace SceneUtil class LightManager : public osg::Group { public: - static bool isValidLightingModelString(const std::string& value); + static LightingMethod getLightingMethodFromString(const std::string& value); enum class UniformKey { @@ -136,7 +138,7 @@ namespace SceneUtil /// By default, it's ~0u i.e. always on. /// If you have some views that do not require lighting, then set the Camera's cull mask to not include /// the lightingMask for a much faster cull and rendering. - void setLightingMask (size_t mask); + void setLightingMask(size_t mask); size_t getLightingMask() const; /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. @@ -167,7 +169,7 @@ namespace SceneUtil auto& getDummies() { return mDummies; } auto& getLightIndexMap(size_t frameNum) { return mLightIndexMaps[frameNum%2]; } - + auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; } auto& getLightUniform(int index, UniformKey key) { return mLightUniforms[index][key]; } @@ -175,10 +177,13 @@ namespace SceneUtil std::map getLightDefines() const; private: - friend class LightManagerStateAttribute; friend class LightManagerCullCallback; + void initFFP(int targetLights); + void initPerObjectUniform(int targetLights); + void initSingleUBO(int targetLights); + void setLightingMethod(LightingMethod method); void setMaxLights(int value); @@ -188,7 +193,7 @@ namespace SceneUtil using LightSourceViewBoundCollection = std::vector; std::map, LightSourceViewBoundCollection> mLightsInViewSpace; - + // < Light list hash , StateSet > using LightStateSetMap = std::map>; LightStateSetMap mStateSetCache[2]; @@ -221,6 +226,8 @@ namespace SceneUtil int mMaxLights; static constexpr auto mFFPMaxLights = 8; + + static const std::unordered_map mLightingMethodSettingMap; }; /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via @@ -259,7 +266,8 @@ namespace SceneUtil size_t mLastFrameNumber; LightManager::LightList mLightList; std::set mIgnoredLightSources; - }; + }; + } #endif diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 12980dffcc..3a5b464405 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 6af5e9ba02..ddffbfefb2 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -40,7 +40,7 @@ Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. 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. +but the lighting may appear dull and there might be colour shifts. Setting this option to 'false' results in more dynamic lighting. auto use object normal maps @@ -152,32 +152,51 @@ lighting method --------------- :Type: string -:Range: legacy|default|experimental +:Range: legacy|shaders compatibility|shaders :Default: default -Sets the internal handling of light sources. +Sets the internal handling of light sources. -'legacy' is restricted to a maximum of 8 lights per object and guarantees fixed function pipeline compatible lighting. +'legacy' is restricted to 8 lights per object and emulates fixed function +pipeline compatible lighting. -'default' removes the light limit via :ref:`max lights` and follows a new attenuation formula which can drastically reduce light popping and seams. -This mode also enables vertex lighting on groundcover, which is otherwise completely disabled with 'legacy'. -It is recommended to use this mode with older hardware, as the technique ensures a range of compatibility equal to that of 'legacy'. +'shaders compatibility' removes the light limit via :ref:`max lights` and +follows a modifed attenuation formula which can drastically reduce light popping +and seams. 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. Because of its wide range of compatibility it is set as the +default. -'experimental' carries all of the benefits that 'legacy' has, but uses a modern approach that allows for a higher 'max lights' count with little to no performance penalties on modern hardware. +'shaders' carries all of the benefits that 'shaders compatibility' does, but +uses a modern approach that allows for a higher :ref:`max lights` count with +little to no performance penalties on modern hardware. It is recommended to use +this mode when supported and where the GPU is not a bottleneck. On some weaker +devices, using this mode along with :ref:`force per pixel lighting` can carry +performance penalties. + +Note that when enabled, groundcover lighting is forced to be vertex lighting, +unless 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. + +This setting has no effect if :ref:`force shaders` is 'false'. light bounds multiplier ----------------------- :Type: float :Range: 0.0-10.0 -:Default: 2.0 +:Default: 1.75 -Controls the bounding sphere radius of point lights, which is used to determine if an object should receive lighting from a particular light source. -Note, this has no direct effect on the overall illumination of lights. -Larger multipliers will allow for smoother transitions of light sources, but may require an increase in :ref:`max lights` and thus carries a performance penalty. -This especially helps with abrupt light popping with handheld light sources such as torches and lanterns. +Controls the bounding sphere radius of point lights, which is used to determine +if an object should receive lighting from a particular light source. Note, this +has no direct effect on the overall illumination of lights. Larger multipliers +will allow for smoother transitions of light sources, but may require an +increase in :ref:`max lights` and thus carries a performance penalty. This +especially helps with abrupt light popping with handheld light sources such as +torches and lanterns. -It is recommended to keep this at 1.0 if :ref:`lighting method` is set to 'legacy', as the number of lights is fixed in that mode. +This setting has no effect if :ref:`lighting method` is 'legacy'. maximum light distance ---------------------- @@ -186,9 +205,11 @@ maximum light distance :Range: The whole range of 32-bit floating point :Default: 8192 -The maximum distance from the camera that lights will be illuminated, applies to both interiors and exteriors. -A lower distance will improve performance. -Set this to a non-positive value to disable fading. +The maximum distance from the camera that lights will be illuminated, applies to +both interiors and exteriors. A lower distance will improve performance. Set +this to a non-positive value to disable fading. + +This setting has no effect if :ref:`lighting method` is 'legacy'. light fade start ---------------- @@ -199,18 +220,23 @@ light fade start The fraction of the maximum distance at which lights will begin to fade away. Tweaking it will make the transition proportionally more or less smooth. -This setting has no effect if the maximum light distance is non-positive. + +This setting has no effect if the :ref:`maximum light distance` is non-positive +or :ref:`lighting method` is 'legacy'. max lights ---------- :Type: integer :Range: >=2 -:Default: 16 +:Default: 8 Sets the maximum number of lights that each object can receive lighting from. -Has no effect if :ref:`force shaders` option is off or :ref:`lighting method` is 'legacy'. In this case the maximum number of lights is fixed at 8. -Increasing this too much can cause significant performance loss, especially if :ref:`lighting method` is not set to 'experimental' or :ref:`force per pixel lighting` is on. +Increasing this too much can cause significant performance loss, especially if +:ref:`lighting method` is not set to 'shaders' or :ref:`force per pixel +lighting` is on. + +This setting has no effect if :ref:`lighting method` is 'legacy'. antialias alpha test --------------------------------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 102c1aa66c..9525595883 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -442,23 +442,32 @@ apply lighting to environment maps = false # This makes fogging independent from the viewing angle. Shaders will be used to render all objects. radial fog = false -# Internal handling of lights, values are 'legacy', 'default', 'experimental' -lighting method = experimental - -# Sets the bounding sphere multiplier of light sources, which are used to determine if an object should -# receive lighting. Higher values will allow for smoother transitions of light sources, but may have a performance cost and -# requires a higher number of 'max lights' set. It is recommended to keep this at 1.0 with 'legacy' lighting enabled. -light bounds multiplier = 1.0 - -# The distance from the camera at which lights fade away completely. Set to 0 to disable fading. +# Internal handling of lights, ignored if 'force shaders' is off. "legacy" +# provides fixed function pipeline emulation."shaders compatibility" (default) +# uncaps the light limit, enables groundcover lighting, and uses a modified +# attenuation formula to reduce popping and light seams. "shaders" comes with +# all these benefits and is meant for larger light limits, but may not be +# supported on older hardware and may be less performant on weaker hardware when +# 'force per pixel lighting' is enabled. +lighting method = shaders compatibility + +# Sets the bounding sphere multiplier of light sources, which are used to +# determine if an object should receive lighting. Higher values will allow for +# smoother transitions of light sources, but may have a performance cost and +# requires a higher number of 'max lights' set. It is recommended to keep this +# at 1.0 with 'legacy' lighting enabled. +light bounds multiplier = 1.75 + +# The distance from the camera at which lights fade away completely. +# Set to 0 to disable fading. maximum light distance = 8192 # Fraction of the maximum distance at which lights begin to gradually fade away. light fade start = 0.85 # Set maximum number of lights per object. -# Only used when 'lighting method' is not set to 'legacy' -max lights = 16 +# When 'lighting method' is set to 'legacy', this setting will have no effect. +max lights = 8 # Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. # This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index c6c03f2232..3678a35704 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -69,7 +69,7 @@ uniform int PointLightCount; #endif void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal) -{ +{ vec3 lightDir = normalize(getLight[0].position.xyz); #if @lightingModel == LIGHTING_MODEL_SINGLE_UBO @@ -98,9 +98,9 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) { - vec3 lightDir = getLight[lightIndex].position.xyz - viewPos; + vec3 lightPos = getLight[lightIndex].position.xyz - viewPos; - float lightDistance = length(lightDir); + float lightDistance = length(lightPos); #if !@ffpLighting // This has a *considerable* performance uplift where GPU is a bottleneck @@ -112,7 +112,7 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec } #endif - lightDir = normalize(lightDir); + lightPos = normalize(lightPos); #if @ffpLighting float illumination = clamp(1.0 / (getLight[lightIndex].constantAttenuation + getLight[lightIndex].linearAttenuation * lightDistance + getLight[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); @@ -128,8 +128,8 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec ambientOut = getLight[lightIndex].ambient.xyz * illumination; #endif - float lambert = dot(viewNormal.xyz, lightDir) * illumination; - + float lambert = dot(viewNormal.xyz, lightPos) * illumination; + #ifndef GROUNDCOVER lambert = max(lambert, 0.0); #else @@ -179,7 +179,7 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a for (int i=1; i <= PointLightCount; ++i) { perLightPoint(ambientOut, diffuseOut, i, viewPos, viewNormal); -#else +#else for (int i=0; i < PointLightCount; ++i) { perLightPoint(ambientOut, diffuseOut, PointLightIndex[i], viewPos, viewNormal); diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 1397f65e7e..06a3213011 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -151,6 +151,8 @@ uniform vec3 nodePosition; uniform float rainIntensity; +#define PER_PIXEL_LIGHTING 0 + #include "shadows_fragment.glsl" #include "lighting.glsl" From cc31e1eea1c066ccca40031a3150847f98f9c75e Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Tue, 30 Mar 2021 18:12:57 -0700 Subject: [PATCH 0573/2859] Ambient luminance threshold setting --- apps/openmw/mwrender/renderingmanager.cpp | 22 +++++++++++++++++++ apps/openmw/mwrender/renderingmanager.hpp | 1 + .../reference/modding/settings/shaders.rst | 19 ++++++++++++++++ files/settings-default.cfg | 7 +++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 229c34f452..865bae2f10 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -195,6 +195,7 @@ namespace MWRender , mWorkQueue(workQueue) , mUnrefQueue(new SceneUtil::UnrefQueue) , mNavigator(navigator) + , mMinimumAmbientLuminance(0.f) , mNightEyeFactor(0.f) , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) @@ -223,6 +224,9 @@ namespace MWRender resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); + if (sceneRoot->getLightingMethod() != SceneUtil::LightingMethod::FFP) + mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); + sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); @@ -1072,7 +1076,25 @@ namespace MWRender osg::Vec4f color = mAmbientColor; if (mNightEyeFactor > 0.f) + { color += osg::Vec4f(0.7, 0.7, 0.7, 0.0) * mNightEyeFactor; + } + // optionally brighten up ambient interiors when using a non-FFP emulated lighting method + else if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) + { + static constexpr float pR = 0.2126; + static constexpr float pG = 0.7152; + static constexpr float pB = 0.0722; + + // we already work in linear RGB so no conversions are needed for the luminosity function + float relativeLuminance = pR*color.r() + pG*color.g() + pB*color.b(); + if (relativeLuminance < mMinimumAmbientLuminance) + { + // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can + float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; + color *= targetBrightnessIncreaseFactor; + } + } mStateUpdater->setAmbientColor(color); } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index a7afa2fa0d..a0a74bd5c4 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -296,6 +296,7 @@ namespace MWRender osg::ref_ptr mStateUpdater; osg::Vec4f mAmbientColor; + float mMinimumAmbientLuminance; float mNightEyeFactor; float mNearClip; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index ddffbfefb2..3b0f6df5a6 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -238,6 +238,25 @@ lighting` is on. This setting has no effect if :ref:`lighting method` is 'legacy'. +minimum interior brightness +------------------------ + +:Type: float +:Range: 0.0-1.0 +:Default: 0.1 + +Sets the minimum interior ambient brightness for interior cells when +:ref:`lighting method` is not 'legacy'. A consequence of the new lighting system +is that interiors will sometimes be darker since light sources now have sensible +fall-offs. A couple solutions are to either add more lights or increase their +radii to compensate, but these require content changes. For best results it is +recommended to set this to 0.0 to retain the colors that level designers +intended. If brighter interiors are wanted, however, this setting should be +increased. Note, it is advised to keep this number small (< 0.1) to avoid the +aforementioned changes in visuals. + +This setting has no effect if :ref:`lighting method` is 'legacy'. + antialias alpha test --------------------------------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9525595883..2d543303e9 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -465,10 +465,15 @@ maximum light distance = 8192 # Fraction of the maximum distance at which lights begin to gradually fade away. light fade start = 0.85 -# Set maximum number of lights per object. +# Set maximum number of lights per object. # When 'lighting method' is set to 'legacy', this setting will have no effect. max lights = 8 +# Sets minimum ambient brightness of interior cells. Levels below this threshold will have their +# ambient values adjusted to balance the darker interiors. +# When 'lighting method' is set to 'legacy', this setting will have no effect. +minimum interior brightness = 0.1 + # Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. # This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. # When MSAA is off, this setting will have no visible effect, but might have a performance cost. From d195602a9dcdc199b48a85ad5be8fdd860dcb110 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 31 Mar 2021 21:22:16 -0700 Subject: [PATCH 0574/2859] Switch to shared layout, some rewording --- components/sceneutil/lightmanager.cpp | 201 ++++++++++++++++++++++---- files/settings-default.cfg | 2 +- files/shaders/lighting.glsl | 10 +- 3 files changed, 175 insertions(+), 38 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 72470b8c2c..c7a4834299 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -47,55 +47,81 @@ namespace SceneUtil { static int sLightId = 0; + // Handles a GLSL shared layout by using configured offsets and strides to fill a continuous buffer, making the data upload to GPU simpler. class LightBuffer : public osg::Referenced { public: - LightBuffer(int count) : mData(new osg::Vec4Array(3*count)), mEndian(osg::getCpuByteOrder()) {} + enum LayoutOffset + { + Diffuse, + DiffuseSign, + Ambient, + Specular, + Position, + AttenuationRadius + }; + + LightBuffer(int count) + : mData(new osg::FloatArray(3*4*count)) + , mEndian(osg::getCpuByteOrder()) + , mCount(count) + , mStride(12) + { + mOffsets[Diffuse] = 0; + mOffsets[Ambient] = 1; + mOffsets[Specular] = 2; + mOffsets[DiffuseSign] = 3; + mOffsets[Position] = 4; + mOffsets[AttenuationRadius] = 8; + } + + LightBuffer(const LightBuffer& copy) + : osg::Referenced() + , mData(copy.mData) + , mEndian(copy.mEndian) + , mCount(copy.mCount) + , mStride(copy.mStride) + , mOffsets(copy.mOffsets) + {} void setDiffuse(int index, const osg::Vec4& value) { - auto signedValue = value; + // Deal with negative lights (negative diffuse) by passing a sign bit in the unused alpha component + auto positiveColor = value; float signBit = 1.0; if (value[0] < 0) { - signedValue *= -1.0; + positiveColor *= -1.0; signBit = -1.0; } - *(unsigned int*)(&(*mData)[3*index][0]) = asRGBA(signedValue); - *(int*)(&(*mData)[3*index][3]) = signBit; + *(unsigned int*)(&(*mData)[getOffset(index, Diffuse)]) = asRGBA(positiveColor); + *(int*)(&(*mData)[getOffset(index, DiffuseSign)]) = signBit; } void setAmbient(int index, const osg::Vec4& value) { - *(unsigned int*)(&(*mData)[3*index][1]) = asRGBA(value); + *(unsigned int*)(&(*mData)[getOffset(index, Ambient)]) = asRGBA(value); } void setSpecular(int index, const osg::Vec4& value) { - *(unsigned int*)(&(*mData)[3*index][2]) = asRGBA(value); + *(unsigned int*)(&(*mData)[getOffset(index, Specular)]) = asRGBA(value); } void setPosition(int index, const osg::Vec4& value) { - (*mData)[3*index+1] = value; - } - - void setAttenuation(int index, float c, float l, float q) - { - (*mData)[3*index+2][0] = c; - (*mData)[3*index+2][1] = l; - (*mData)[3*index+2][2] = q; + *(osg::Vec4*)(&(*mData)[getOffset(index, Position)]) = value; } - void setRadius(int index, float value) + void setAttenuationRadius(int index, const osg::Vec4& value) { - (*mData)[3*index+2][3] = value; + *(osg::Vec4*)(&(*mData)[getOffset(index, AttenuationRadius)]) = value; } auto getPosition(int index) { - return (*mData)[3*index+1]; + return *(osg::Vec4*)(&(*mData)[getOffset(index, Position)]); } auto& getData() @@ -118,8 +144,40 @@ namespace SceneUtil return mEndian == osg::BigEndian ? value.asABGR() : value.asRGBA(); } - osg::ref_ptr mData; + int getOffset(int index, LayoutOffset slot) + { + return mStride * index + mOffsets[slot]; + } + + void configureLayout(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int size, int stride) + { + static constexpr auto sizeofVec4 = sizeof(GL_FLOAT) * osg::Vec4::num_components; + static constexpr auto sizeofFloat = sizeof(GL_FLOAT); + + mOffsets[Diffuse] = offsetColors / sizeofFloat; + mOffsets[Ambient] = mOffsets[Diffuse] + 1; + mOffsets[Specular] = mOffsets[Diffuse] + 2; + mOffsets[DiffuseSign] = mOffsets[Diffuse] + 3; + mOffsets[Position] = offsetPosition / sizeofFloat; + mOffsets[AttenuationRadius] = offsetAttenuationRadius / sizeofFloat; + mStride = (offsetAttenuationRadius + sizeofVec4 + stride) / 4; + + // Copy over previous buffers light data. Buffers populate before we know the layout. + LightBuffer oldBuffer = LightBuffer(*this); + for (int i = 0; i < oldBuffer.mCount; ++i) + { + *(osg::Vec4*)(&(*mData)[getOffset(i, Diffuse)]) = *(osg::Vec4*)(&(*mData)[oldBuffer.getOffset(i, Diffuse)]); + *(osg::Vec4*)(&(*mData)[getOffset(i, Position)]) = *(osg::Vec4*)(&(*mData)[oldBuffer.getOffset(i, Position)]); + *(osg::Vec4*)(&(*mData)[getOffset(i, AttenuationRadius)]) = *(osg::Vec4*)(&(*mData)[oldBuffer.getOffset(i, AttenuationRadius)]); + } + } + + private: + osg::ref_ptr mData; osg::Endian mEndian; + int mCount; + int mStride; + std::unordered_map mOffsets; }; class LightStateCache @@ -165,8 +223,8 @@ namespace SceneUtil buffer->setPosition(0, light->getPosition()); osg::ref_ptr ubo = new osg::UniformBufferObject; - buffer->mData->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->mData.get(), 0, buffer->mData->getTotalDataSize()); + buffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->getData().get(), 0, buffer->getData()->getTotalDataSize()); stateset->setAttributeAndModes(ubb, mode); @@ -572,11 +630,24 @@ namespace SceneUtil class LightManagerStateAttribute : public osg::StateAttribute { public: - LightManagerStateAttribute() : mLightManager(nullptr) {} - LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} + LightManagerStateAttribute() + : mLightManager(nullptr) {} - LightManagerStateAttribute(const LightManagerStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop),mLightManager(copy.mLightManager) {} + LightManagerStateAttribute(LightManager* lightManager) + : mLightManager(lightManager) + , mDummyProgram(new osg::Program) + { + static const std::string dummyVertSource = generateDummyShader(mLightManager->getMaxLightsInScene()); + + mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); + mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); + // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably + // available, regardless of extensions, until GLSL 140. + mLightManager->getOrCreateStateSet()->setAttributeAndModes(mDummyProgram, osg::StateAttribute::ON|osg::StateAttribute::PROTECTED); + } + + LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mLightManager(copy.mLightManager) {} int compare(const StateAttribute &sa) const override { @@ -585,12 +656,76 @@ namespace SceneUtil META_StateAttribute(NifOsg, LightManagerStateAttribute, osg::StateAttribute::LIGHT) + void initSharedLayout(osg::GLExtensions* ext, int handle) const + { + std::vector index = { static_cast(Shader::UBOBinding::LightBuffer) }; + int totalBlockSize = -1; + int stride = -1; + + ext->glGetActiveUniformBlockiv(handle, 0, GL_UNIFORM_BLOCK_DATA_SIZE, &totalBlockSize); + ext->glGetActiveUniformsiv(handle, index.size(), index.data(), GL_UNIFORM_ARRAY_STRIDE, &stride); + + std::vector names = { + "LightBuffer[0].packedColors" + ,"LightBuffer[0].position" + ,"LightBuffer[0].attenuation" + }; + std::vector indices(names.size()); + std::vector offsets(names.size()); + + ext->glGetUniformIndices(handle, names.size(), names.data(), indices.data()); + ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data()); + + for (int i = 0; i < 2; ++i) + { + auto& buf = mLightManager->getLightBuffer(i); + buf->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride); + } + } + void apply(osg::State& state) const override { - mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); + static bool init = false; + if (!init) + { + auto handle = mDummyProgram->getPCP(state)->getHandle(); + auto* ext = state.get(); + + int activeUniformBlocks = 0; + ext->glGetProgramiv(handle, GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks); + + // wait until the UBO binding is created + if (activeUniformBlocks > 0) + { + initSharedLayout(ext, handle); + init = true; + } + } + else + { + mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); + } + } + + private: + + std::string generateDummyShader(int maxLightsInScene) + { + return "#version 120\n" + "#extension GL_ARB_uniform_buffer_object : require\n" + "struct LightData {\n" + " ivec4 packedColors;\n" + " vec4 position;\n" + " vec4 attenuation;\n" + "};\n" + "uniform LightBufferBinding {\n" + " LightData LightBuffer[" + std::to_string(mLightManager->getMaxLightsInScene()) + "];\n" + "};\n" + "void main() { gl_Position = vec4(0.0); }\n"; } LightManager* mLightManager; + osg::ref_ptr mDummyProgram; }; const std::unordered_map LightManager::mLightingMethodSettingMap = { @@ -654,12 +789,15 @@ namespace SceneUtil bool supportsUBO = exts && exts->isUniformBufferObjectSupported; bool supportsGPU4 = exts && exts->isGpuShader4Supported; - if (getLightingMethod() == LightingMethod::SingleUBO) + static bool hasLoggedWarnings = false; + + if (getLightingMethod() == LightingMethod::SingleUBO && !hasLoggedWarnings) { if (!supportsUBO) - Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; - else if (!supportsGPU4) - Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; + Log(Debug::Warning) << "GL_ARB_uniform_buffer_object not supported: switching to shader compatibility lighting mode"; + if (!supportsGPU4) + Log(Debug::Warning) << "GL_EXT_gpu_shader4 not supported: switching to shader compatibility lighting mode"; + hasLoggedWarnings = true; } int targetLights = Settings::Manager::getInt("max lights", "Shaders"); @@ -978,8 +1116,7 @@ namespace SceneUtil auto& buf = getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); buf->setAmbient(index, light->getAmbient()); - buf->setAttenuation(index, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation()); - buf->setRadius(index, lightSource->getRadius()); + buf->setAttenuationRadius(index, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); buf->setPosition(index, light->getPosition() * (*viewMatrix)); } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 2d543303e9..c931789068 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -453,7 +453,7 @@ lighting method = shaders compatibility # Sets the bounding sphere multiplier of light sources, which are used to # determine if an object should receive lighting. Higher values will allow for -# smoother transitions of light sources, but may have a performance cost and +# smoother transitions of light sources, but may carry a performance cost and # requires a higher number of 'max lights' set. It is recommended to keep this # at 1.0 with 'legacy' lighting enabled. light bounds multiplier = 1.75 diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 3678a35704..a3df6666fa 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -35,15 +35,16 @@ vec4 unpackRGBA(int data) struct LightData { - ivec4 packedColors; // diffuse, ambient, specular + ivec4 packedColors; // diffuse, ambient, specular vec4 position; - vec4 attenuation; // constant, linear, quadratic, radius + vec4 attenuation; // constant, linear, quadratic, radius }; uniform int PointLightIndex[@maxLights]; uniform int PointLightCount; -layout(std140) uniform LightBufferBinding +// Defaults to shared layout. If we ever move to GLSL 140, std140 layout should be considered +uniform LightBufferBinding { LightData LightBuffer[@maxLightsInScene]; }; @@ -56,7 +57,7 @@ struct LightData vec4 diffuse; vec4 ambient; vec4 specular; - vec4 attenuation; // constant, linear, quadratic, radius + vec4 attenuation; // constant, linear, quadratic, radius }; uniform LightData LightBuffer[@maxLights]; @@ -92,7 +93,6 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi } lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif - diffuseOut = sunDiffuse * lambert; } From 157717693a0ab59b041c1998425b24b33edd807b Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 31 Mar 2021 22:03:06 -0700 Subject: [PATCH 0575/2859] Changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1fea773e9..8ced951e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -147,6 +147,7 @@ Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5813: Instanced groundcover support + Feature #5828: Support more than 8 lights Feature #5910: Fall back to delta time when physics can't keep up Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation From 71f7f30c0b3018b9451ea8ad39ebccffca9c7ef1 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 31 Mar 2021 22:05:47 -0700 Subject: [PATCH 0576/2859] Don't break NV shaders --- files/shaders/nv_default_fragment.glsl | 4 ++++ files/shaders/nv_default_vertex.glsl | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 03fa378a6d..1245069f60 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -1,5 +1,9 @@ #version 120 +#if @useUBO + #extension GL_ARB_uniform_buffer_object : require +#endif + #if @useGPUShader4 #extension GL_EXT_gpu_shader4: require #endif diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index 7c9d434f18..50f5daf25e 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -1,5 +1,13 @@ #version 120 +#if @useUBO + #extension GL_ARB_uniform_buffer_object : require +#endif + +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + #if @diffuseMap varying vec2 diffuseMapUV; #endif From 3d713e8602b71711db609ddc6746e3f3331dd74a Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 31 Mar 2021 22:35:02 -0700 Subject: [PATCH 0577/2859] Fix incorrect minimum ambient --- AUTHORS.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 45 +++++++------ components/sceneutil/lightmanager.cpp | 67 +++++++------------ .../reference/modding/settings/shaders.rst | 16 ++--- files/settings-default.cfg | 12 ++-- 5 files changed, 65 insertions(+), 76 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index d2de857477..07aef455c0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -49,6 +49,7 @@ Programmers Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) + Cody Glassman (Wazabear) Coleman Smith (olcoal) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 865bae2f10..c14431518d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -537,7 +537,32 @@ namespace MWRender void RenderingManager::configureAmbient(const ESM::Cell *cell) { - setAmbientColour(SceneUtil::colourFromRGB(cell->mAmbi.mAmbient)); + bool needsAdjusting = false; + if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) + needsAdjusting = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); + + auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient); + + if (needsAdjusting) + { + static constexpr float pR = 0.2126; + static constexpr float pG = 0.7152; + static constexpr float pB = 0.0722; + + // we already work in linear RGB so no conversions are needed for the luminosity function + float relativeLuminance = pR*ambient.r() + pG*ambient.g() + pB*ambient.b(); + if (relativeLuminance < mMinimumAmbientLuminance) + { + // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can + float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; + if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f) + ambient = osg::Vec4(targetBrightnessIncreaseFactor, targetBrightnessIncreaseFactor, targetBrightnessIncreaseFactor, ambient.a()); + else + ambient *= targetBrightnessIncreaseFactor; + } + } + + setAmbientColour(ambient); osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); mSunLight->setDiffuse(diffuse); @@ -1076,25 +1101,7 @@ namespace MWRender osg::Vec4f color = mAmbientColor; if (mNightEyeFactor > 0.f) - { color += osg::Vec4f(0.7, 0.7, 0.7, 0.0) * mNightEyeFactor; - } - // optionally brighten up ambient interiors when using a non-FFP emulated lighting method - else if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) - { - static constexpr float pR = 0.2126; - static constexpr float pG = 0.7152; - static constexpr float pB = 0.0722; - - // we already work in linear RGB so no conversions are needed for the luminosity function - float relativeLuminance = pR*color.r() + pG*color.g() + pB*color.b(); - if (relativeLuminance < mMinimumAmbientLuminance) - { - // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can - float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; - color *= targetBrightnessIncreaseFactor; - } - } mStateUpdater->setAmbientColor(color); } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index c7a4834299..f20e28a7e3 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -381,27 +381,14 @@ namespace SceneUtil void apply(osg::State &state) const override { - osg::Matrix modelViewMatrix = state.getModelViewMatrix(); - - state.applyModelViewMatrix(state.getInitialViewMatrix()); - - LightStateCache* cache = getLightStateCache(state.getContextID(), mLightManager->getMaxLights()); for (size_t i = 0; i < mLights.size(); ++i) { - osg::Light* current = cache->lastAppliedLight[i]; auto light = mLights[i]; - if (current != light.get()) - { - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Diffuse)->set(light->getDiffuse()); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Ambient)->set(light->getAmbient()); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Attenuation)->set(osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light))); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Position)->set(light->getPosition() * state.getModelViewMatrix()); - - cache->lastAppliedLight[i] = mLights[i]; - } + mLightManager->getLightUniform(i+1, LightManager::UniformKey::Diffuse)->set(light->getDiffuse()); + mLightManager->getLightUniform(i+1, LightManager::UniformKey::Ambient)->set(light->getAmbient()); + mLightManager->getLightUniform(i+1, LightManager::UniformKey::Attenuation)->set(osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light))); + mLightManager->getLightUniform(i+1, LightManager::UniformKey::Position)->set(light->getPosition() * state.getInitialViewMatrix()); } - - state.applyModelViewMatrix(modelViewMatrix); } private: @@ -643,7 +630,7 @@ namespace SceneUtil mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably // available, regardless of extensions, until GLSL 140. - mLightManager->getOrCreateStateSet()->setAttributeAndModes(mDummyProgram, osg::StateAttribute::ON|osg::StateAttribute::PROTECTED); + mLightManager->getOrCreateStateSet()->setAttributeAndModes(mDummyProgram, osg::StateAttribute::ON); } LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) @@ -756,9 +743,10 @@ namespace SceneUtil , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { + setUpdateCallback(new LightManagerUpdateCallback); + if (ffp) { - setLightingMethod(LightingMethod::FFP); initFFP(LightManager::mFFPMaxLights); return; } @@ -769,11 +757,7 @@ namespace SceneUtil { Log(Debug::Error) << "Invalid option for 'lighting method': got '" << lightingMethodString << "', expected legacy, shaders compatible, or shaders. Falling back to 'shaders compatible'."; - setLightingMethod(LightingMethod::PerObjectUniform); - } - else - { - setLightingMethod(lightingMethod); + lightingMethod = LightingMethod::PerObjectUniform; } mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f); @@ -791,7 +775,7 @@ namespace SceneUtil static bool hasLoggedWarnings = false; - if (getLightingMethod() == LightingMethod::SingleUBO && !hasLoggedWarnings) + if (lightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings) { if (!supportsUBO) Log(Debug::Warning) << "GL_ARB_uniform_buffer_object not supported: switching to shader compatibility lighting mode"; @@ -802,19 +786,13 @@ namespace SceneUtil int targetLights = Settings::Manager::getInt("max lights", "Shaders"); - if (!supportsUBO || !supportsGPU4 || getLightingMethod() == LightingMethod::PerObjectUniform) - { - setLightingMethod(LightingMethod::PerObjectUniform); + if (!supportsUBO || !supportsGPU4 || lightingMethod == LightingMethod::PerObjectUniform) initPerObjectUniform(targetLights); - } else - { initSingleUBO(targetLights); - } getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); - setUpdateCallback(new LightManagerUpdateCallback); addCullCallback(new LightManagerCullCallback(this)); } @@ -876,18 +854,18 @@ namespace SceneUtil void LightManager::initFFP(int targetLights) { + setLightingMethod(LightingMethod::FFP); setMaxLights(targetLights); for (int i = 0; i < getMaxLights(); ++i) mDummies.push_back(new FFPLightStateAttribute(i, std::vector>())); - - setUpdateCallback(new LightManagerUpdateCallback); } void LightManager::initPerObjectUniform(int targetLights) { auto* stateset = getOrCreateStateSet(); + setLightingMethod(LightingMethod::PerObjectUniform); setMaxLights(std::max(2, targetLights)); mLightUniforms.resize(getMaxLights()+1); @@ -918,6 +896,7 @@ namespace SceneUtil void LightManager::initSingleUBO(int targetLights) { + setLightingMethod(LightingMethod::SingleUBO); setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); for (int i = 0; i < 2; ++i) @@ -1059,6 +1038,7 @@ namespace SceneUtil const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { + bool isReflectionCamera = camera->getName() == "ReflectionCamera"; osg::observer_ptr camPtr (camera); auto it = mLightsInViewSpace.find(camPtr); @@ -1075,17 +1055,18 @@ namespace SceneUtil osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius * mPointLightRadiusMultiplier); transformBoundingSphere(worldViewMat, viewBound); - static const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; - - if (mPointLightFadeEnd != 0.f) + if (!isReflectionCamera) { - float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); - - if (fade == 0.f) - continue; + static const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; + if (mPointLightFadeEnd != 0.f) + { + float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); + if (fade == 0.f) + continue; - auto* light = transform.mLightSource->getLight(frameNum); - light->setDiffuse(light->getDiffuse() * fade); + auto* light = transform.mLightSource->getLight(frameNum); + light->setDiffuse(light->getDiffuse() * fade); + } } LightSourceViewBound l; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 3b0f6df5a6..be38bd3ec4 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -160,12 +160,12 @@ Sets the internal handling of light sources. 'legacy' is restricted to 8 lights per object and emulates fixed function pipeline compatible lighting. -'shaders compatibility' removes the light limit via :ref:`max lights` and -follows a modifed attenuation formula which can drastically reduce light popping -and seams. 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. Because of its wide range of compatibility it is set as the -default. +'shaders compatibility' removes the light limit controllable through :ref:`max +lights` and follows a modifed attenuation formula which can drastically reduce +light popping and seams. 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. Because of its wide range of compatibility it is set as +the default. 'shaders' carries all of the benefits that 'shaders compatibility' does, but uses a modern approach that allows for a higher :ref:`max lights` count with @@ -174,9 +174,9 @@ this mode when supported and where the GPU is not a bottleneck. On some weaker devices, using this mode along with :ref:`force per pixel lighting` can carry performance penalties. -Note that when enabled, groundcover lighting is forced to be vertex lighting, +Note that when enabled groundcover lighting is forced to be vertex lighting, unless 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 +Z-Up normals technique to avoid some common issues with shading. As a consequence, per pixel lighting would give undesirable results. This setting has no effect if :ref:`force shaders` is 'false'. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c931789068..de2c237e8e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -451,11 +451,11 @@ radial fog = false # 'force per pixel lighting' is enabled. lighting method = shaders compatibility -# Sets the bounding sphere multiplier of light sources, which are used to -# determine if an object should receive lighting. Higher values will allow for -# smoother transitions of light sources, but may carry a performance cost and -# requires a higher number of 'max lights' set. It is recommended to keep this -# at 1.0 with 'legacy' lighting enabled. +# Sets the bounding sphere multiplier of light sources if 'lighting method' is +# not 'legacy'. These are used to determine if an object should receive +# lighting. Higher values will allow for smoother transitions of light sources, +# but may carry a performance cost and requires a higher number of 'max lights' +# set. light bounds multiplier = 1.75 # The distance from the camera at which lights fade away completely. @@ -470,7 +470,7 @@ light fade start = 0.85 max lights = 8 # Sets minimum ambient brightness of interior cells. Levels below this threshold will have their -# ambient values adjusted to balance the darker interiors. +# ambient values adjusted to balance the darker interiors. # When 'lighting method' is set to 'legacy', this setting will have no effect. minimum interior brightness = 0.1 From 71c30a31df20242a1554c3c632172a96336a081d Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 3 Apr 2021 22:25:13 -0700 Subject: [PATCH 0578/2859] in-game settings, some require restart --- apps/openmw/mwgui/settingswindow.cpp | 90 +++++++++++- apps/openmw/mwgui/settingswindow.hpp | 9 +- apps/openmw/mwrender/renderingmanager.cpp | 20 ++- components/sceneutil/lightmanager.cpp | 136 +++++++++++------- components/sceneutil/lightmanager.hpp | 11 +- .../reference/modding/settings/shaders.rst | 4 +- files/mygui/openmw_settings_window.layout | 89 ++++++++++++ files/shaders/lighting.glsl | 81 +++++++---- files/shaders/water_fragment.glsl | 7 +- 9 files changed, 352 insertions(+), 95 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 538b3db5ed..a93400490c 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -107,7 +109,7 @@ namespace namespace MWGui { - void SettingsWindow::configureWidgets(MyGUI::Widget* widget) + void SettingsWindow::configureWidgets(MyGUI::Widget* widget, bool init) { MyGUI::EnumeratorWidgetPtr widgets = widget->getEnumerator(); while (widgets.next()) @@ -121,7 +123,8 @@ namespace MWGui getSettingCategory(current)) ? "#{sOn}" : "#{sOff}"; current->castType()->setCaptionWithReplacing(initialValue); - current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + if (init) + current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); } if (type == sliderType) { @@ -141,6 +144,12 @@ namespace MWGui ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; valueStr = ss.str(); } + else if (valueType == "Float") + { + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << value; + valueStr = ss.str(); + } else valueStr = MyGUI::utility::toString(int(value)); @@ -155,12 +164,13 @@ namespace MWGui valueStr = MyGUI::utility::toString(value); scroll->setScrollPosition(value); } - scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + if (init) + scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); if (scroll->getVisible()) updateSliderLabel(scroll, valueStr); } - configureWidgets(current); + configureWidgets(current, init); } } @@ -187,7 +197,7 @@ namespace MWGui getWidget(unusedSlider, widgetName); unusedSlider->setVisible(false); - configureWidgets(mMainWidget); + configureWidgets(mMainWidget, true); setTitle("#{sOptions}"); @@ -204,6 +214,9 @@ namespace MWGui getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); + getWidget(mLightingMethodButton, "LightingMethodButton"); + getWidget(mMaxLightsSlider, "MaxLightsSlider"); + getWidget(mLightsResetButton, "LightsResetButton"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -229,6 +242,9 @@ namespace MWGui mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); + mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodChanged); + mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); + mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); @@ -272,6 +288,21 @@ namespace MWGui waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); + switch (lightingMethod) + { + case SceneUtil::LightingMethod::Undefined: + case SceneUtil::LightingMethod::FFP: + mLightingMethodButton->setIndexSelected(0); + break; + case SceneUtil::LightingMethod::PerObjectUniform: + mLightingMethodButton->setIndexSelected(1); + break; + case SceneUtil::LightingMethod::SingleUBO: + mLightingMethodButton->setIndexSelected(2); + break; + } + mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); @@ -358,6 +389,49 @@ namespace MWGui apply(); } + void SettingsWindow::onLightsResetButtonClicked(MyGUI::Widget* _sender) + { + std::vector buttons = {"#{sYes}", "#{sNo}"}; + std::string message = "This will reset all lighting settings to default, some changes will require a restart. Would you like to continue?"; + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true); + int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); + if (selectedButton == 1 || selectedButton == -1) + return; + + Settings::Manager::setString("lighting method", "Shaders", "shaders compatibility"); + Settings::Manager::setFloat("light bounds multiplier", "Shaders", 1.75); + Settings::Manager::setInt("maximum light distance", "Shaders", 8192); + Settings::Manager::setFloat("light fade start", "Shaders", 0.85); + Settings::Manager::setFloat("minimum interior brightness", "Shaders", 0.1); + Settings::Manager::setInt("max lights", "Shaders", 8); + + mLightingMethodButton->setIndexSelected(1); + + apply(); + configureWidgets(mMainWidget, false); + } + + void SettingsWindow::onLightingMethodChanged(MyGUI::ComboBox* _sender, size_t pos) + { + std::string setting; + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(_sender->getItemNameAt(pos)); + switch (lightingMethod) + { + case SceneUtil::LightingMethod::FFP: + setting = "legacy"; + break; + case SceneUtil::LightingMethod::Undefined: + case SceneUtil::LightingMethod::PerObjectUniform: + setting = "shaders compatibility"; + break; + case SceneUtil::LightingMethod::SingleUBO: + setting = "shaders"; + break; + } + Settings::Manager::setString("lighting method", "Shaders", setting); + apply(); + } + void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); @@ -460,6 +534,12 @@ namespace MWGui ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; valueStr = ss.str(); } + else if (valueType == "Float") + { + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << value; + valueStr = ss.str(); + } else valueStr = MyGUI::utility::toString(int(value)); } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 6f25dd1143..3ec142731e 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -35,6 +35,10 @@ namespace MWGui MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; + MyGUI::ComboBox* mLightingMethodButton; + MyGUI::ScrollBar* mMaxLightsSlider; + MyGUI::Button* mLightsResetButton; + // controls MyGUI::ScrollView* mControlsBox; MyGUI::Button* mResetControlsButton; @@ -55,6 +59,9 @@ namespace MWGui void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); + void onLightsResetButtonClicked(MyGUI::Widget* _sender); + void onLightingMethodChanged(MyGUI::ComboBox* _sender, size_t pos); + void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); void onResetDefaultBindings(MyGUI::Widget* _sender); @@ -66,7 +73,7 @@ namespace MWGui void apply(); - void configureWidgets(MyGUI::Widget* widget); + void configureWidgets(MyGUI::Widget* widget, bool init); void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); void layoutControlsBox(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c14431518d..e5c5ace9d5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -52,6 +52,7 @@ #include "../mwgui/loadingscreen.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" #include "sky.hpp" #include "effectmanager.hpp" @@ -224,8 +225,7 @@ namespace MWRender resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); - if (sceneRoot->getLightingMethod() != SceneUtil::LightingMethod::FFP) - mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); + mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; @@ -1144,9 +1144,25 @@ namespace MWRender else if (it->first == "General" && (it->second == "texture filter" || it->second == "texture mipmap" || it->second == "anisotropy")) + { updateTextureFiltering(); + } else if (it->first == "Water") + { mWater->processChangedSettings(changed); + } + else if (it->first == "Shaders" && it->second == "minimum interior brightness") + { + mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); + if (MWMechanics::getPlayer().getCell()) + configureAmbient(MWMechanics::getPlayer().getCell()->getCell()); + } + else if (it->first == "Shaders" && (it->second == "light bounds multiplier" || + it->second == "maximum light distance" || + it->second == "light fade start")) + { + static_cast(getLightRoot())->processChangedSettings(changed); + } } } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index f20e28a7e3..8e4c9910f8 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -10,8 +10,6 @@ #include -#include - #include namespace @@ -41,6 +39,43 @@ namespace { light->setUserValue("radius", value); } + + void configurePosition(osg::Matrixf& mat, const osg::Vec4& pos) + { + mat(0, 0) = pos.x(); + mat(0, 1) = pos.y(); + mat(0, 2) = pos.z(); + } + + void configureAmbient(osg::Matrixf& mat, const osg::Vec4& color) + { + mat(1, 0) = color.r(); + mat(1, 1) = color.g(); + mat(1, 2) = color.b(); + } + + void configureDiffuse(osg::Matrixf& mat, const osg::Vec4& color) + { + mat(2, 0) = color.r(); + mat(2, 1) = color.g(); + mat(2, 2) = color.b(); + } + + void configureSpecular(osg::Matrixf& mat, const osg::Vec4& color) + { + mat(3, 0) = color.r(); + mat(3, 1) = color.g(); + mat(3, 2) = color.b(); + mat(3, 3) = color.a(); + } + + void configureAttenuation(osg::Matrixf& mat, float c, float l, float q, float r) + { + mat(0, 3) = c; + mat(1, 3) = l; + mat(2, 3) = q; + mat(3, 3) = r; + } } namespace SceneUtil @@ -206,11 +241,12 @@ namespace SceneUtil } case LightingMethod::PerObjectUniform: { - stateset->addUniform(new osg::Uniform("LightBuffer[0].diffuse", light->getDiffuse()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].ambient", light->getAmbient()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].specular", light->getSpecular()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].position", light->getPosition()), mode); - + osg::Matrixf lightMat; + configurePosition(lightMat, light->getPosition()); + configureAmbient(lightMat, light->getAmbient()); + configureDiffuse(lightMat, light->getDiffuse()); + configureSpecular(lightMat, light->getSpecular()); + stateset->addUniform(new osg::Uniform("LightBuffer", lightMat), mode); break; } case LightingMethod::SingleUBO: @@ -381,14 +417,20 @@ namespace SceneUtil void apply(osg::State &state) const override { + auto* lightUniform = mLightManager->getStateSet()->getUniform("LightBuffer"); for (size_t i = 0; i < mLights.size(); ++i) { auto light = mLights[i]; - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Diffuse)->set(light->getDiffuse()); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Ambient)->set(light->getAmbient()); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Attenuation)->set(osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light))); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Position)->set(light->getPosition() * state.getInitialViewMatrix()); + osg::Matrixf lightMat; + + configurePosition(lightMat, light->getPosition() * state.getInitialViewMatrix()); + configureAmbient(lightMat, light->getAmbient()); + configureDiffuse(lightMat, light->getDiffuse()); + configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light)); + + lightUniform->setElement(i+1, lightMat); } + lightUniform->dirty(); } private: @@ -589,10 +631,12 @@ namespace SceneUtil { if (mLightManager->getLightingMethod() == LightingMethod::PerObjectUniform) { - mLightManager->getLightUniform(0, LightManager::UniformKey::Diffuse)->set(sun->getDiffuse()); - mLightManager->getLightUniform(0, LightManager::UniformKey::Ambient)->set(sun->getAmbient()); - mLightManager->getLightUniform(0, LightManager::UniformKey::Specular)->set(sun->getSpecular()); - mLightManager->getLightUniform(0, LightManager::UniformKey::Position)->set(sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + osg::Matrixf lightMat; + configurePosition(lightMat, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + configureAmbient(lightMat, sun->getAmbient()); + configureDiffuse(lightMat, sun->getDiffuse()); + configureSpecular(lightMat, sun->getSpecular()); + mLightManager->getStateSet()->getUniform("LightBuffer")->setElement(0, lightMat); } else { @@ -760,14 +804,7 @@ namespace SceneUtil lightingMethod = LightingMethod::PerObjectUniform; } - mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f); - - mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); - if (mPointLightFadeEnd > 0) - { - mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); - mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; - } + updateSettings(); osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); bool supportsUBO = exts && exts->isUniformBufferObjectSupported; @@ -839,9 +876,6 @@ namespace SceneUtil { Shader::ShaderManager::DefineMap defines; - bool ffp = usingFFP(); - - defines["ffpLighting"] = ffp ? "1" : "0"; defines["maxLights"] = std::to_string(getMaxLights()); defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene()); defines["lightingModel"] = std::to_string(static_cast(mLightingMethod)); @@ -852,6 +886,26 @@ namespace SceneUtil return defines; } + void LightManager::processChangedSettings(const Settings::CategorySettingVector& changed) + { + updateSettings(); + } + + void LightManager::updateSettings() + { + if (getLightingMethod() == LightingMethod::FFP) + return; + + mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 5.f); + + mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); + if (mPointLightFadeEnd > 0) + { + mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); + mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; + } + } + void LightManager::initFFP(int targetLights) { setLightingMethod(LightingMethod::FFP); @@ -866,38 +920,15 @@ namespace SceneUtil auto* stateset = getOrCreateStateSet(); setLightingMethod(LightingMethod::PerObjectUniform); - setMaxLights(std::max(2, targetLights)); + setMaxLights(std::clamp(targetLights, 2, 64)); - mLightUniforms.resize(getMaxLights()+1); - for (size_t i = 0; i < mLightUniforms.size(); ++i) - { - osg::ref_ptr udiffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].diffuse").c_str()); - osg::ref_ptr uspecular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].specular").c_str()); - osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); - osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); - osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); - - mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); - mLightUniforms[i].emplace(UniformKey::Ambient, uambient); - mLightUniforms[i].emplace(UniformKey::Specular, uspecular); - mLightUniforms[i].emplace(UniformKey::Position, uposition); - mLightUniforms[i].emplace(UniformKey::Attenuation, uattenuation); - - stateset->addUniform(udiffuse); - stateset->addUniform(uambient); - stateset->addUniform(uposition); - stateset->addUniform(uattenuation); - - // specular isn't used besides sun, complete waste to upload it - if (i == 0) - stateset->addUniform(uspecular); - } + stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights() + 1)); } void LightManager::initSingleUBO(int targetLights) { setLightingMethod(LightingMethod::SingleUBO); - setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); + setMaxLights(std::clamp(targetLights, 2, 64)); for (int i = 0; i < 2; ++i) { @@ -912,7 +943,6 @@ namespace SceneUtil getOrCreateStateSet()->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); } - void LightManager::setLightingMethod(LightingMethod method) { mLightingMethod = method; diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index cd8f2d3127..2fdc14a8da 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -14,6 +14,8 @@ #include +#include + namespace osgUtil { class CullVisitor; @@ -172,10 +174,10 @@ namespace SceneUtil auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; } - auto& getLightUniform(int index, UniformKey key) { return mLightUniforms[index][key]; } - std::map getLightDefines() const; + void processChangedSettings(const Settings::CategorySettingVector& changed); + private: friend class LightManagerStateAttribute; friend class LightManagerCullCallback; @@ -184,6 +186,8 @@ namespace SceneUtil void initPerObjectUniform(int targetLights); void initSingleUBO(int targetLights); + void updateSettings(); + void setLightingMethod(LightingMethod method); void setMaxLights(int value); @@ -212,9 +216,6 @@ namespace SceneUtil using LightIndexMap = std::unordered_map; LightIndexMap mLightIndexMaps[2]; - using UniformMap = std::vector>>; - UniformMap mLightUniforms; - std::unique_ptr mStateSetGenerator; LightingMethod mLightingMethod; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index be38bd3ec4..0537332a81 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -185,7 +185,7 @@ light bounds multiplier ----------------------- :Type: float -:Range: 0.0-10.0 +:Range: 0.0-5.0 :Default: 1.75 Controls the bounding sphere radius of point lights, which is used to determine @@ -228,7 +228,7 @@ max lights ---------- :Type: integer -:Range: >=2 +:Range: 2-64 :Default: 8 Sets the maximum number of lights that each object can receive lighting from. diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index babb5c28f9..ef5d4a6cb4 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -456,6 +456,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8d2d24ce65761d0a8e3de309c51f2f08a5df90d8 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 24 May 2021 18:46:45 +0200 Subject: [PATCH 0965/2859] Store screen capture operation as osg::ref_ptr --- apps/openmw/engine.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 1aef62df53..49ae92abc1 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -62,7 +62,7 @@ namespace OMW boost::filesystem::path mResDir; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; - osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; + osg::ref_ptr mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; std::vector mGroundcoverFiles; From 33aa4d08224e1ddf6b9a7c02f5a643545ae38e75 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 24 May 2021 18:47:23 +0200 Subject: [PATCH 0966/2859] Move WriteScreenshotToFileOperation to components --- apps/openmw/engine.cpp | 52 ++---------------- components/CMakeLists.txt | 1 + components/sceneutil/screencapture.cpp | 73 ++++++++++++++++++++++++++ components/sceneutil/screencapture.hpp | 31 +++++++++++ 4 files changed, 108 insertions(+), 49 deletions(-) create mode 100644 components/sceneutil/screencapture.cpp create mode 100644 components/sceneutil/screencapture.hpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a06faa4d71..d03891fdc6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -40,6 +40,8 @@ #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -781,54 +783,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } } -class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation -{ -public: - WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat) - : mScreenshotPath(screenshotPath) - , mScreenshotFormat(screenshotFormat) - { - } - - void operator()(const osg::Image& image, const unsigned int context_id) override - { - // Count screenshots. - int shotCount = 0; - - // Find the first unused filename with a do-while - std::ostringstream stream; - do - { - // Reset the stream - stream.str(""); - stream.clear(); - - stream << mScreenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << mScreenshotFormat; - - } while (boost::filesystem::exists(stream.str())); - - boost::filesystem::ofstream outStream; - outStream.open(boost::filesystem::path(stream.str()), std::ios::binary); - - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat); - if (!readerwriter) - { - Log(Debug::Error) << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found"; - return; - } - - osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); - if (!result.success()) - { - Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status(); - } - } - -private: - std::string mScreenshotPath; - std::string mScreenshotFormat; -}; - // Initialise and enter main loop. void OMW::Engine::go() { @@ -860,7 +814,7 @@ void OMW::Engine::go() mViewer->setUseConfigureAffinity(false); #endif - mScreenCaptureOperation = new WriteScreenshotToFileOperation( + mScreenCaptureOperation = new SceneUtil::WriteScreenshotToFileOperation( mCfgMgr.getScreenshotPath().string(), Settings::Manager::getString("screenshot format", "General")); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7860f492ce..43987d6c7b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -53,6 +53,7 @@ add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller + screencapture ) add_component_dir (nif diff --git a/components/sceneutil/screencapture.cpp b/components/sceneutil/screencapture.cpp new file mode 100644 index 0000000000..660f296b63 --- /dev/null +++ b/components/sceneutil/screencapture.cpp @@ -0,0 +1,73 @@ +#include "screencapture.hpp" + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace SceneUtil +{ + void writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, + const osg::Image& image) + { + // Count screenshots. + int shotCount = 0; + + // Find the first unused filename with a do-while + std::ostringstream stream; + do + { + // Reset the stream + stream.str(""); + stream.clear(); + + stream << screenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << screenshotFormat; + + } while (boost::filesystem::exists(stream.str())); + + boost::filesystem::ofstream outStream; + outStream.open(boost::filesystem::path(stream.str()), std::ios::binary); + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(screenshotFormat); + if (!readerwriter) + { + Log(Debug::Error) << "Error: Can't write screenshot, no '" << screenshotFormat << "' readerwriter found"; + return; + } + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); + if (!result.success()) + { + Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status(); + } + } + + WriteScreenshotToFileOperation::WriteScreenshotToFileOperation(const std::string& screenshotPath, + const std::string& screenshotFormat) + : mScreenshotPath(screenshotPath) + , mScreenshotFormat(screenshotFormat) + { + } + + void WriteScreenshotToFileOperation::operator()(const osg::Image& image, const unsigned int /*context_id*/) + { + try + { + writeScreenshotToFile(mScreenshotPath, mScreenshotFormat, image); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Failed to write screenshot to file with path=\"" << mScreenshotPath + << "\", format=\"" << mScreenshotFormat << "\": " << e.what(); + } + } +} diff --git a/components/sceneutil/screencapture.hpp b/components/sceneutil/screencapture.hpp new file mode 100644 index 0000000000..37ff5e39cc --- /dev/null +++ b/components/sceneutil/screencapture.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H +#define OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H + +#include + +#include + +namespace osg +{ + class Image; +} + +namespace SceneUtil +{ + void writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, + const osg::Image& image); + + class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation + { + public: + WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat); + + void operator()(const osg::Image& image, const unsigned int /*context_id*/) override; + + private: + const std::string mScreenshotPath; + const std::string mScreenshotFormat; + }; +} + +#endif From f8e02000ece0459f501cbebf5c335707058d50d3 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 24 May 2021 19:27:09 +0200 Subject: [PATCH 0967/2859] Write screenshots to file asynchronously --- CHANGELOG.md | 1 + apps/openmw/engine.cpp | 20 ++++++---- components/sceneutil/screencapture.cpp | 53 +++++++++++++++++++++++++- components/sceneutil/screencapture.hpp | 18 ++++++++- 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3b4e3354..a676b70b9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Bug #6129: Player avatar not displayed correctly for large window sizes when GUI scaling active Bug #6131: Item selection in the avatar window not working correctly for large window sizes Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player + Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive Feature #2780: A way to see current OpenMW version in the console Feature #5489: MCP: Telekinesis fix for activators Feature #6017: Separate persistent and temporary cell references when saving diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d03891fdc6..76f02fb683 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -670,6 +670,18 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) throw std::runtime_error("Invalid setting: 'preload num threads' must be >0"); mWorkQueue = new SceneUtil::WorkQueue(numThreads); + mScreenCaptureOperation = new SceneUtil::AsyncScreenCaptureOperation( + mWorkQueue, + new SceneUtil::WriteScreenshotToFileOperation( + mCfgMgr.getScreenshotPath().string(), + Settings::Manager::getString("screenshot format", "General") + ) + ); + + mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); + + mViewer->addEventHandler(mScreenCaptureHandler); + // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so @@ -814,14 +826,6 @@ void OMW::Engine::go() mViewer->setUseConfigureAffinity(false); #endif - mScreenCaptureOperation = new SceneUtil::WriteScreenshotToFileOperation( - mCfgMgr.getScreenshotPath().string(), - Settings::Manager::getString("screenshot format", "General")); - - mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); - - mViewer->addEventHandler(mScreenCaptureHandler); - mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); prepareEngine (settings); diff --git a/components/sceneutil/screencapture.cpp b/components/sceneutil/screencapture.cpp index 660f296b63..c010b390aa 100644 --- a/components/sceneutil/screencapture.cpp +++ b/components/sceneutil/screencapture.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -10,9 +11,43 @@ #include #include -#include -#include +#include #include +#include +#include + +namespace +{ + class ScreenCaptureWorkItem : public SceneUtil::WorkItem + { + public: + ScreenCaptureWorkItem(const osg::ref_ptr& impl, + const osg::Image& image, unsigned int contextId) + : mImpl(impl), + mImage(new osg::Image(image)), + mContextId(contextId) + { + assert(mImpl != nullptr); + } + + void doWork() override + { + try + { + (*mImpl)(*mImage, mContextId); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "ScreenCaptureWorkItem exception: " << e.what(); + } + } + + private: + const osg::ref_ptr mImpl; + const osg::ref_ptr mImage; + const unsigned int mContextId; + }; +} namespace SceneUtil { @@ -70,4 +105,18 @@ namespace SceneUtil << "\", format=\"" << mScreenshotFormat << "\": " << e.what(); } } + + AsyncScreenCaptureOperation::AsyncScreenCaptureOperation(osg::ref_ptr queue, + osg::ref_ptr impl) + : mQueue(std::move(queue)), + mImpl(std::move(impl)) + { + assert(mQueue != nullptr); + assert(mImpl != nullptr); + } + + void AsyncScreenCaptureOperation::operator()(const osg::Image& image, const unsigned int context_id) + { + mQueue->addWorkItem(new ScreenCaptureWorkItem(mImpl, image, context_id)); + } } diff --git a/components/sceneutil/screencapture.hpp b/components/sceneutil/screencapture.hpp index 37ff5e39cc..8087095e4e 100644 --- a/components/sceneutil/screencapture.hpp +++ b/components/sceneutil/screencapture.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H #define OPENMW_COMPONENTS_SCENEUTIL_SCREENCAPTURE_H +#include #include #include @@ -12,6 +13,8 @@ namespace osg namespace SceneUtil { + class WorkQueue; + void writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, const osg::Image& image); @@ -20,12 +23,25 @@ namespace SceneUtil public: WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat); - void operator()(const osg::Image& image, const unsigned int /*context_id*/) override; + void operator()(const osg::Image& image, const unsigned int context_id) override; private: const std::string mScreenshotPath; const std::string mScreenshotFormat; }; + + class AsyncScreenCaptureOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation + { + public: + AsyncScreenCaptureOperation(osg::ref_ptr queue, + osg::ref_ptr impl); + + void operator()(const osg::Image& image, const unsigned int context_id) override; + + private: + const osg::ref_ptr mQueue; + const osg::ref_ptr mImpl; + }; } #endif From f7a6be053d55af0776bee13c91faa201bce48327 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 25 May 2021 13:44:40 +0200 Subject: [PATCH 0968/2859] Stop engine work queue before destructing environment To avoid access to null and dangling pointers from active work items on quitting. --- apps/openmw/engine.cpp | 2 ++ components/sceneutil/workqueue.cpp | 17 ++++++++++++++--- components/sceneutil/workqueue.hpp | 6 +++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 76f02fb683..9651aaafbd 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -407,6 +407,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) OMW::Engine::~Engine() { + mWorkQueue->stop(); + mEnvironment.cleanup(); delete mScriptContext; diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index 3f8ed8aaf2..3c1df80ac4 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -33,14 +33,25 @@ bool WorkItem::isDone() const return mDone; } -WorkQueue::WorkQueue(int workerThreads) +WorkQueue::WorkQueue(std::size_t workerThreads) : mIsReleased(false) { - for (int i=0; i(*this)); + start(workerThreads); } WorkQueue::~WorkQueue() +{ + stop(); +} + +void WorkQueue::start(std::size_t workerThreads) +{ + while (mThreads.size() < workerThreads) + mThreads.emplace_back(std::make_unique(*this)); + mIsReleased = false; +} + +void WorkQueue::stop() { { std::unique_lock lock(mMutex); diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 5b51c59e59..6eb6a36a3e 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -44,9 +44,13 @@ namespace SceneUtil class WorkQueue : public osg::Referenced { public: - WorkQueue(int numWorkerThreads=1); + WorkQueue(std::size_t workerThreads); ~WorkQueue(); + void start(std::size_t workerThreads); + + void stop(); + /// Add a new work item to the back of the queue. /// @par The work item's waitTillDone() method may be used by the caller to wait until the work is complete. /// @param front If true, add item to the front of the queue. If false (default), add to the back. From 5103120eefac41e7b53ca8dfafd91b0e072235b6 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 25 May 2021 01:49:27 +0200 Subject: [PATCH 0969/2859] Notify about saved screenshot Show message about saved screenshot via schedule message box. Since screenshot saving happens not in the main thread calling messageBox directly is unsafe. WindowManager::scheduleMessageBox delays message box showing until next update in the main thread. --- apps/openmw/engine.cpp | 11 ++++++++- apps/openmw/mwbase/windowmanager.hpp | 2 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 15 +++++++++++++ apps/openmw/mwgui/windowmanagerimp.hpp | 16 +++++++++++++ components/sceneutil/screencapture.cpp | 31 +++++++++++++++++++------- components/sceneutil/screencapture.hpp | 8 ++++--- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9651aaafbd..278c97f029 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -218,6 +218,14 @@ namespace if (Settings::Manager::getInt("async num threads", "Physics") == 0) profiler.removeUserStatsLine(" -Async"); } + + struct ScheduleNonDialogMessageBox + { + void operator()(std::string message) const + { + MWBase::Environment::get().getWindowManager()->scheduleMessageBox(std::move(message), MWGui::ShowInDialogueMode_Never); + } + }; } void OMW::Engine::executeLocalScripts() @@ -676,7 +684,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mWorkQueue, new SceneUtil::WriteScreenshotToFileOperation( mCfgMgr.getScreenshotPath().string(), - Settings::Manager::getString("screenshot format", "General") + Settings::Manager::getString("screenshot format", "General"), + ScheduleNonDialogMessageBox {} ) ); diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 6e3c615de3..f256cc387e 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -228,6 +228,8 @@ namespace MWBase virtual void exitCurrentGuiMode() = 0; virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; + /// Puts message into a queue to show on the next update. Thread safe alternative for messageBox. + virtual void scheduleMessageBox(std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; virtual void staticMessageBox(const std::string& message) = 0; virtual void removeStaticMessageBox() = 0; virtual void interactiveMessageBox (const std::string& message, diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1694206504..eebcf4b094 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -749,6 +749,11 @@ namespace MWGui } } + void WindowManager::scheduleMessageBox(std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode) + { + mScheduledMessageBoxes.lock()->emplace_back(std::move(message), showInDialogueMode); + } + void WindowManager::staticMessageBox(const std::string& message) { mMessageBoxManager->createMessageBox(message, true); @@ -803,6 +808,8 @@ namespace MWGui void WindowManager::update (float frameDuration) { + handleScheduledMessageBoxes(); + bool gameRunning = MWBase::Environment::get().getStateManager()->getState()!= MWBase::StateManager::State_NoGame; @@ -2204,4 +2211,12 @@ namespace MWGui { return mVersionDescription; } + + void WindowManager::handleScheduledMessageBoxes() + { + const auto scheduledMessageBoxes = mScheduledMessageBoxes.lock(); + for (const ScheduledMessageBox& v : *scheduledMessageBoxes) + messageBox(v.mMessage, v.mShowInDialogueMode); + scheduledMessageBoxes->clear(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index ef411a39df..8c7e365ec7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -8,6 +8,7 @@ **/ #include +#include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include "mapwindow.hpp" #include "statswatcher.hpp" @@ -264,6 +266,7 @@ namespace MWGui void exitCurrentGuiMode() override; void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override; + void scheduleMessageBox (std::string message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override; void staticMessageBox(const std::string& message) override; void removeStaticMessageBox() override; void interactiveMessageBox (const std::string& message, @@ -524,6 +527,17 @@ namespace MWGui float mScalingFactor; + struct ScheduledMessageBox + { + std::string mMessage; + MWGui::ShowInDialogueMode mShowInDialogueMode; + + ScheduledMessageBox(std::string&& message, MWGui::ShowInDialogueMode showInDialogueMode) + : mMessage(std::move(message)), mShowInDialogueMode(showInDialogueMode) {} + }; + + Misc::ScopeGuarded> mScheduledMessageBoxes; + /** * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. * Supported syntax: @@ -555,6 +569,8 @@ namespace MWGui void updatePinnedWindows(); void enableScene(bool enable); + + void handleScheduledMessageBoxes(); }; } diff --git a/components/sceneutil/screencapture.cpp b/components/sceneutil/screencapture.cpp index c010b390aa..47119f3644 100644 --- a/components/sceneutil/screencapture.cpp +++ b/components/sceneutil/screencapture.cpp @@ -51,59 +51,74 @@ namespace namespace SceneUtil { - void writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, - const osg::Image& image) + std::string writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, + const osg::Image& image) { // Count screenshots. int shotCount = 0; // Find the first unused filename with a do-while std::ostringstream stream; + std::string lastFileName; + std::string lastFilePath; do { // Reset the stream stream.str(""); stream.clear(); - stream << screenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << screenshotFormat; + stream << "screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << screenshotFormat; - } while (boost::filesystem::exists(stream.str())); + lastFileName = stream.str(); + lastFilePath = screenshotPath + "/" + lastFileName; + + } while (boost::filesystem::exists(lastFilePath)); boost::filesystem::ofstream outStream; - outStream.open(boost::filesystem::path(stream.str()), std::ios::binary); + outStream.open(boost::filesystem::path(std::move(lastFilePath)), std::ios::binary); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(screenshotFormat); if (!readerwriter) { Log(Debug::Error) << "Error: Can't write screenshot, no '" << screenshotFormat << "' readerwriter found"; - return; + return std::string(); } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); if (!result.success()) { Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status(); + return std::string(); } + + return lastFileName; } WriteScreenshotToFileOperation::WriteScreenshotToFileOperation(const std::string& screenshotPath, - const std::string& screenshotFormat) + const std::string& screenshotFormat, + std::function callback) : mScreenshotPath(screenshotPath) , mScreenshotFormat(screenshotFormat) + , mCallback(callback) { } void WriteScreenshotToFileOperation::operator()(const osg::Image& image, const unsigned int /*context_id*/) { + std::string fileName; try { - writeScreenshotToFile(mScreenshotPath, mScreenshotFormat, image); + fileName = writeScreenshotToFile(mScreenshotPath, mScreenshotFormat, image); } catch (const std::exception& e) { Log(Debug::Error) << "Failed to write screenshot to file with path=\"" << mScreenshotPath << "\", format=\"" << mScreenshotFormat << "\": " << e.what(); } + if (fileName.empty()) + mCallback("Failed to save screenshot"); + else + mCallback(fileName + " has been saved"); } AsyncScreenCaptureOperation::AsyncScreenCaptureOperation(osg::ref_ptr queue, diff --git a/components/sceneutil/screencapture.hpp b/components/sceneutil/screencapture.hpp index 8087095e4e..6395d989d8 100644 --- a/components/sceneutil/screencapture.hpp +++ b/components/sceneutil/screencapture.hpp @@ -15,19 +15,21 @@ namespace SceneUtil { class WorkQueue; - void writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, - const osg::Image& image); + std::string writeScreenshotToFile(const std::string& screenshotPath, const std::string& screenshotFormat, + const osg::Image& image); class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation { public: - WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat); + WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat, + std::function callback); void operator()(const osg::Image& image, const unsigned int context_id) override; private: const std::string mScreenshotPath; const std::string mScreenshotFormat; + const std::function mCallback; }; class AsyncScreenCaptureOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation From 4259f7f230a502462a53fd88e0ef28ff7487ebf1 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 25 May 2021 13:58:16 +0200 Subject: [PATCH 0970/2859] Add setting to enable/disabled notification for saved screenshots --- apps/launcher/advancedpage.cpp | 4 ++++ apps/openmw/engine.cpp | 9 ++++++++- docs/source/reference/modding/settings/general.rst | 9 +++++++++ files/settings-default.cfg | 3 +++ files/ui/advancedpage.ui | 9 ++++++++- 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index bddb70aa02..00021268e3 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -213,6 +213,8 @@ bool Launcher::AdvancedPage::loadSettings() if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) screenshotFormatComboBox->addItem(screenshotFormatString); screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); + + loadSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General"); } // Testing @@ -376,6 +378,8 @@ void Launcher::AdvancedPage::saveSettings() std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General")) Settings::Manager::setString("screenshot format", "General", screenshotFormatString); + + saveSettingBool(notifyOnSavedScreenshotCheckBox, "notify on saved screenshot", "General"); } // Testing diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 278c97f029..93d3530c0a 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -226,6 +226,11 @@ namespace MWBase::Environment::get().getWindowManager()->scheduleMessageBox(std::move(message), MWGui::ShowInDialogueMode_Never); } }; + + struct IgnoreString + { + void operator()(std::string) const {} + }; } void OMW::Engine::executeLocalScripts() @@ -685,7 +690,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) new SceneUtil::WriteScreenshotToFileOperation( mCfgMgr.getScreenshotPath().string(), Settings::Manager::getString("screenshot format", "General"), - ScheduleNonDialogMessageBox {} + Settings::Manager::getBool("notify on saved screenshot", "General") + ? std::function(ScheduleNonDialogMessageBox {}) + : std::function(IgnoreString {}) ) ); diff --git a/docs/source/reference/modding/settings/general.rst b/docs/source/reference/modding/settings/general.rst index c885f77aa1..f0ebe4f972 100644 --- a/docs/source/reference/modding/settings/general.rst +++ b/docs/source/reference/modding/settings/general.rst @@ -59,3 +59,12 @@ texture mipmap Set the texture mipmap type to control the method mipmaps are created. Mipmapping is a way of reducing the processing power needed during minification by pregenerating a series of smaller textures. + +notify on saved screenshot +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Show message box when screenshot is saved to a file. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 88a6b48126..522762555d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -386,6 +386,9 @@ texture min filter = linear # Texture mipmap type. (none, nearest, or linear). texture mipmap = nearest +# Show message box when screenshot is saved to a file. +notify on saved screenshot = false + [Shaders] # Force rendering with shaders. By default, only bump-mapped objects will use shaders. diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 8abe8f59a4..f72150df22 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -1121,7 +1121,7 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C - + Screenshot Format @@ -1149,6 +1149,13 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C + + + + Notify on saved screenshot + + + From d8cd5b361a88862a7f9a828af6fddc66db719561 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 25 May 2021 21:04:05 +0200 Subject: [PATCH 0971/2859] Hide message boxes when HUD is hidden --- apps/openmw/mwgui/messagebox.cpp | 15 ++++++++++++--- apps/openmw/mwgui/messagebox.hpp | 4 ++++ apps/openmw/mwgui/windowmanagerimp.cpp | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 5bd8ceb5ae..4907a247ff 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -101,6 +101,8 @@ namespace MWGui if(stat) mStaticMessageBox = box; + box->setVisible(mVisible); + mMessageBoxes.push_back(box); if(mMessageBoxes.size() > 3) { @@ -167,8 +169,12 @@ namespace MWGui return pressed; } - - + void MessageBoxManager::setVisible(bool value) + { + mVisible = value; + for (MessageBox* messageBox : mMessageBoxes) + messageBox->setVisible(value); + } MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message) : Layout("openmw_messagebox.layout") @@ -201,7 +207,10 @@ namespace MWGui return mMainWidget->getHeight()+mNextBoxPadding; } - + void MessageBox::setVisible(bool value) + { + mMainWidget->setVisible(value); + } InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons) : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index aeb1b83002..26d26bac56 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -47,12 +47,15 @@ namespace MWGui void onButtonPressed(int button) { eventButtonPressed(button); eventButtonPressed.clear(); } + void setVisible(bool value); + private: std::vector mMessageBoxes; InteractiveMessageBox* mInterMessageBoxe; MessageBox* mStaticMessageBox; float mMessageBoxSpeed; int mLastButtonPressed; + bool mVisible = true; }; class MessageBox : public Layout @@ -62,6 +65,7 @@ namespace MWGui void setMessage (const std::string& message); int getHeight (); void update (int height); + void setVisible(bool value); float mCurrentTime; float mMaxTime; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index eebcf4b094..544a0927e7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1499,6 +1499,7 @@ namespace MWGui { mHudEnabled = !mHudEnabled; updateVisible(); + mMessageBoxManager->setVisible(mHudEnabled); return mHudEnabled; } From 9e168fd9ccc4912893ea886dd6e39d4db393c364 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 25 Nov 2020 05:55:20 +0100 Subject: [PATCH 0972/2859] Add Lua/LuaJit and sol3 to openmw --- CI/before_install.android.sh | 2 +- CI/before_script.msvc.sh | 24 ++++++++++++++++++++++++ CI/install_debian_deps.sh | 4 ++-- CMakeLists.txt | 32 +++++++++++++++++++++++++++----- apps/openmw/CMakeLists.txt | 1 + cmake/FindLuaJit.cmake | 14 ++++++++++++++ extern/sol_config/sol/config.hpp | 15 +++++++++++++++ 7 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 cmake/FindLuaJit.cmake create mode 100644 extern/sol_config/sol/config.hpp diff --git a/CI/before_install.android.sh b/CI/before_install.android.sh index 0243a96092..59d98f48c4 100755 --- a/CI/before_install.android.sh +++ b/CI/before_install.android.sh @@ -1,4 +1,4 @@ #!/bin/sh -ex -curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201129.zip -o ~/openmw-android-deps.zip +curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201230.zip -o ~/openmw-android-deps.zip unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 8d0b8647b4..0a6123505e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -575,6 +575,11 @@ if [ -z $SKIP_DOWNLOAD ]; then "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/lz4_win${BITS}_v1_9_2.7z" \ "lz4_win${BITS}_v1_9_2.7z" + # LuaJIT + download "LuaJIT 2.1.0-beta3" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/LuaJIT-2.1.0-beta3-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \ + "LuaJIT-2.1.0-beta3-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" + # Google test and mock if [ ! -z $TEST_FRAMEWORK ]; then echo "Google test 1.10.0..." @@ -934,6 +939,25 @@ printf "LZ4 1.9.2... " } cd $DEPS echo +# LuaJIT 2.1.0-beta3 +printf "LuaJIT 2.1.0-beta3... " +{ + if [ -d LuaJIT ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf LuaJIT + eval 7z x -y LuaJIT-2.1.0-beta3-msvc${MSVC_REAL_YEAR}-win${BITS}.7z -o$(real_pwd)/LuaJIT $STRIP + fi + export LUAJIT_DIR="$(real_pwd)/LuaJIT" + add_cmake_opts -DLuaJit_INCLUDE_DIR="${LUAJIT_DIR}/include" \ + -DLuaJit_LIBRARY="${LUAJIT_DIR}/lib/lua51.lib" + for CONFIGURATION in ${CONFIGURATIONS[@]}; do + add_runtime_dlls $CONFIGURATION "$(pwd)/LuaJIT/bin/lua51.dll" + done + echo Done. +} +cd $DEPS +echo # Google Test and Google Mock if [ ! -z $TEST_FRAMEWORK ]; then printf "Google test 1.10.0 ..." diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 2f905314b5..6a7f4a84a7 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -18,10 +18,10 @@ declare -rA GROUPED_DEPS=( libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev - + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev - libbullet-dev liblz4-dev libpng-dev libjpeg-dev + libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev ca-certificates " # TODO: add librecastnavigation-dev when debian is ready diff --git a/CMakeLists.txt b/CMakeLists.txt index 4661f673f1..d20e451676 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -394,6 +394,22 @@ endif() find_package(SDL2 2.0.9 REQUIRED) find_package(OpenAL REQUIRED) +option(USE_LUAJIT "Switch Lua/LuaJit (TRUE is highly recommended)" TRUE) +if(USE_LUAJIT) + find_package(LuaJit REQUIRED) + set(LUA_INCLUDE_DIR ${LuaJit_INCLUDE_DIR}) + set(LUA_LIBRARIES ${LuaJit_LIBRARIES}) +else(USE_LUAJIT) + find_package(Lua REQUIRED) + add_compile_definitions(NO_LUAJIT) +endif(USE_LUAJIT) + +# Download sol - C++ library binding to Lua +file(DOWNLOAD + "https://github.com/ThePhD/sol2/releases/download/v3.2.2/sol.hpp" "${OpenMW_BINARY_DIR}/extern/sol3.2.2/sol/sol.hpp" + EXPECTED_MD5 ba113cf458f60672917108e69bb4d958) +set(SOL_INCLUDE_DIRS ${OpenMW_BINARY_DIR}/extern/sol3.2.2 ${OpenMW_SOURCE_DIR}/extern/sol_config) + include_directories( BEFORE SYSTEM "." @@ -403,6 +419,8 @@ include_directories( ${OPENAL_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} + ${LUA_INCLUDE_DIR} + ${SOL_INCLUDE_DIRS} ) link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) @@ -661,11 +679,10 @@ if (WIN32) endif() if (BUILD_OPENMW) - if (OPENMW_UNITY_BUILD) - set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") - else() - set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") - endif() + # \bigobj is required: + # 1) for OPENMW_UNITY_BUILD; + # 2) to compile lua binginds, because sol3 is heavily templated. + set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") endif() if (BUILD_WIZARD) @@ -682,6 +699,11 @@ if (WIN32) #set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") endif() +if (BUILD_OPENMW AND APPLE) + # Without these flags LuaJit crashes on startup on OSX + set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") +endif() + # Apple bundling if (OPENMW_OSX_DEPLOYMENT AND APPLE) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3fb762d305..c5c85dae86 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -146,6 +146,7 @@ target_link_libraries(openmw "osg-ffmpeg-videoplayer" "oics" components + ${LUA_LIBRARIES} ) if(OSG_STATIC) diff --git a/cmake/FindLuaJit.cmake b/cmake/FindLuaJit.cmake new file mode 100644 index 0000000000..0f38da9b4b --- /dev/null +++ b/cmake/FindLuaJit.cmake @@ -0,0 +1,14 @@ +# Once found, defines: +# LuaJit_FOUND +# LuaJit_INCLUDE_DIR +# LuaJit_LIBRARIES + +include(LibFindMacros) + +libfind_pkg_detect(LuaJit luajit + FIND_PATH luajit.h PATH_SUFFIXES luajit luajit-2.1 + FIND_LIBRARY luajit-5.1 luajit + ) + +libfind_process(LuaJit) + diff --git a/extern/sol_config/sol/config.hpp b/extern/sol_config/sol/config.hpp new file mode 100644 index 0000000000..fc8c93e64c --- /dev/null +++ b/extern/sol_config/sol/config.hpp @@ -0,0 +1,15 @@ +#ifndef SOL_SINGLE_CONFIG_HPP +#define SOL_SINGLE_CONFIG_HPP + +#define SOL_SAFE_USERTYPE 1 +#define SOL_SAFE_REFERENCES 1 +#define SOL_SAFE_FUNCTION_CALLS 1 +#define SOL_SAFE_FUNCTION 1 +#define SOL_NO_NIL 0 + +#ifndef NO_LUAJIT +#define SOL_LUAJIT 1 +#define SOL_EXCEPTIONS_SAFE_PROPAGATION 0 +#endif + +#endif // SOL_SINGLE_CONFIG_HPP From 9a5229a8217c8bb15b253ec6f63709914f514d41 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 26 Apr 2021 23:58:49 +0200 Subject: [PATCH 0973/2859] Temporary fix for MacOS build. Remove this commit after resolving #5990. --- CI/before_install.osx.sh | 4 +++- CI/before_script.osx.sh | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 1ca0fc6119..d962e76086 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -15,8 +15,10 @@ ccache --version cmake --version qmake --version +brew install lua + curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20210617.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null # additional libraries -[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfig \ No newline at end of file +[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfig diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 265e05b8ee..27667c1c82 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -25,5 +25,6 @@ cmake \ -D BUILD_BSATOOL=TRUE \ -D BUILD_ESSIMPORTER=TRUE \ -D BUILD_NIFTEST=TRUE \ +-D USE_LUAJIT=FALSE \ -G"Unix Makefiles" \ .. From b432a1a1c1528e25f334e19fb3e464c8fa9b8e73 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 12 Mar 2021 02:05:35 +0100 Subject: [PATCH 0974/2859] Add a directory for builtin lua scripts. Dehardcoded game mechanics are supposed to live there. --- files/CMakeLists.txt | 1 + files/builtin_scripts/CMakeLists.txt | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 files/builtin_scripts/CMakeLists.txt diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index 98123acb97..7e1e9059bd 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(mygui) add_subdirectory(shaders) add_subdirectory(vfs) +add_subdirectory(builtin_scripts) diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt new file mode 100644 index 0000000000..5906680e1b --- /dev/null +++ b/files/builtin_scripts/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB_RECURSE BUILTIN_SCRIPTS LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*") + +foreach (f ${BUILTIN_SCRIPTS}) + if (NOT ("CMakeLists.txt" STREQUAL "${f}")) + copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OpenMW_BINARY_DIR}" "resources/vfs/${f}") + endif() +endforeach (f) + From 4b068b27ca854ce90beefb1b57ef2380edd1b8ff Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 12 Jan 2021 23:17:48 +0100 Subject: [PATCH 0975/2859] Add components/lua/luastate and components/lua/utilpackage --- apps/openmw_test_suite/CMakeLists.txt | 5 +- apps/openmw_test_suite/lua/test_lua.cpp | 167 +++++++++++++++++ .../lua/test_utilpackage.cpp | 80 +++++++++ apps/openmw_test_suite/lua/testing_util.hpp | 59 ++++++ components/CMakeLists.txt | 4 + components/lua/luastate.cpp | 169 ++++++++++++++++++ components/lua/luastate.hpp | 107 +++++++++++ components/lua/utilpackage.cpp | 98 ++++++++++ components/lua/utilpackage.hpp | 13 ++ 9 files changed, 701 insertions(+), 1 deletion(-) create mode 100644 apps/openmw_test_suite/lua/test_lua.cpp create mode 100644 apps/openmw_test_suite/lua/test_utilpackage.cpp create mode 100644 apps/openmw_test_suite/lua/testing_util.hpp create mode 100644 components/lua/luastate.cpp create mode 100644 components/lua/luastate.hpp create mode 100644 components/lua/utilpackage.cpp create mode 100644 components/lua/utilpackage.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index d78cb69553..3f82df1a52 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -15,6 +15,9 @@ if (GTEST_FOUND AND GMOCK_FOUND) esm/test_fixed_string.cpp esm/variant.cpp + lua/test_lua.cpp + lua/test_utilpackage.cpp + misc/test_stringops.cpp misc/test_endianness.cpp @@ -39,7 +42,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components) + target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components ${LUA_LIBRARIES}) # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp new file mode 100644 index 0000000000..69a326060c --- /dev/null +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -0,0 +1,167 @@ +#include "gmock/gmock.h" +#include + +#include + +#include "testing_util.hpp" + +namespace +{ + using namespace testing; + + TestFile counterFile(R"X( +x = 42 +return { + get = function() return x end, + inc = function(v) x = x + v end +} +)X"); + + TestFile invalidScriptFile("Invalid script"); + + TestFile testsFile(R"X( +return { + -- should work + sin = function(x) return math.sin(x) end, + requireMathSin = function(x) return require('math').sin(x) end, + useCounter = function() + local counter = require('aaa.counter') + counter.inc(1) + return counter.get() + end, + callRawset = function() + t = {a = 1, b = 2} + rawset(t, 'b', 3) + return t.b + end, + print = print, + + -- should throw an error + incorrectRequire = function() require('counter') end, + modifySystemLib = function() math.sin = 5 end, + rawsetSystemLib = function() rawset(math, 'sin', 5) end, + callLoadstring = function() loadstring('print(1)') end, + setSqr = function() require('sqrlib').sqr = math.sin end, + setOmwName = function() require('openmw').name = 'abc' end, + + -- should work if API is registered + sqr = function(x) return require('sqrlib').sqr(x) end, + apiName = function() return require('test.api').name end +} +)X"); + + struct LuaStateTest : Test + { + std::unique_ptr mVFS = createTestVFS({ + {"aaa/counter.lua", &counterFile}, + {"bbb/tests.lua", &testsFile}, + {"invalid.lua", &invalidScriptFile} + }); + + LuaUtil::LuaState mLua{mVFS.get()}; + }; + + TEST_F(LuaStateTest, Sandbox) + { + sol::table script1 = mLua.runInNewSandbox("aaa/counter.lua"); + + EXPECT_EQ(LuaUtil::call(script1["get"]).get(), 42); + LuaUtil::call(script1["inc"], 3); + EXPECT_EQ(LuaUtil::call(script1["get"]).get(), 45); + + sol::table script2 = mLua.runInNewSandbox("aaa/counter.lua"); + EXPECT_EQ(LuaUtil::call(script2["get"]).get(), 42); + LuaUtil::call(script2["inc"], 1); + EXPECT_EQ(LuaUtil::call(script2["get"]).get(), 43); + + EXPECT_EQ(LuaUtil::call(script1["get"]).get(), 45); + } + + TEST_F(LuaStateTest, ErrorHandling) + { + EXPECT_ERROR(mLua.runInNewSandbox("invalid.lua"), "[string \"invalid.lua\"]:1:"); + } + + TEST_F(LuaStateTest, CustomRequire) + { + sol::table script = mLua.runInNewSandbox("bbb/tests.lua"); + + EXPECT_FLOAT_EQ(LuaUtil::call(script["sin"], 1).get(), + -LuaUtil::call(script["requireMathSin"], -1).get()); + + EXPECT_EQ(LuaUtil::call(script["useCounter"]).get(), 43); + EXPECT_EQ(LuaUtil::call(script["useCounter"]).get(), 44); + { + sol::table script2 = mLua.runInNewSandbox("bbb/tests.lua"); + EXPECT_EQ(LuaUtil::call(script2["useCounter"]).get(), 43); + } + EXPECT_EQ(LuaUtil::call(script["useCounter"]).get(), 45); + + EXPECT_ERROR(LuaUtil::call(script["incorrectRequire"]), "Resource 'counter.lua' not found"); + } + + TEST_F(LuaStateTest, ReadOnly) + { + sol::table script = mLua.runInNewSandbox("bbb/tests.lua"); + + // rawset itself is allowed + EXPECT_EQ(LuaUtil::call(script["callRawset"]).get(), 3); + + // but read-only object can not be modified even with rawset + EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)"); + EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value"); + + EXPECT_EQ(mLua.getMutableFromReadOnly(mLua.makeReadOnly(script)), script); + } + + TEST_F(LuaStateTest, Print) + { + { + sol::table script = mLua.runInNewSandbox("bbb/tests.lua"); + testing::internal::CaptureStdout(); + LuaUtil::call(script["print"], 1, 2, 3); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "[bbb/tests.lua]:\t1\t2\t3\n"); + } + { + sol::table script = mLua.runInNewSandbox("bbb/tests.lua", "prefix"); + testing::internal::CaptureStdout(); + LuaUtil::call(script["print"]); // print with no arguments + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "prefix[bbb/tests.lua]:\n"); + } + } + + TEST_F(LuaStateTest, UnsafeFunction) + { + sol::table script = mLua.runInNewSandbox("bbb/tests.lua"); + EXPECT_ERROR(LuaUtil::call(script["callLoadstring"]), "a nil value"); + } + + TEST_F(LuaStateTest, ProvideAPI) + { + LuaUtil::LuaState lua(mVFS.get()); + + sol::table api1 = lua.makeReadOnly(lua.sol().create_table_with("name", "api1")); + sol::table api2 = lua.makeReadOnly(lua.sol().create_table_with("name", "api2")); + + sol::table script1 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api1}}); + + lua.addCommonPackage( + "sqrlib", lua.sol().create_table_with("sqr", [](int x) { return x * x; })); + + sol::table script2 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api2}}); + + EXPECT_ERROR(LuaUtil::call(script1["sqr"], 3), "Resource 'sqrlib.lua' not found"); + EXPECT_EQ(LuaUtil::call(script2["sqr"], 3).get(), 9); + + EXPECT_EQ(LuaUtil::call(script1["apiName"]).get(), "api1"); + EXPECT_EQ(LuaUtil::call(script2["apiName"]).get(), "api2"); + } + + TEST_F(LuaStateTest, GetLuaVersion) + { + EXPECT_THAT(LuaUtil::getLuaVersion(), HasSubstr("Lua")); + } + +} diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp new file mode 100644 index 0000000000..afd9fa2d3c --- /dev/null +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -0,0 +1,80 @@ +#include "gmock/gmock.h" +#include + +#include + +#include "testing_util.hpp" + +namespace +{ + using namespace testing; + + TEST(LuaUtilPackageTest, Vector2) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua.safe_script("v = util.vector2(3, 4)"); + EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), 3); + EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 4); + EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(3, 4)"); + EXPECT_FLOAT_EQ(lua.safe_script("return v:length()").get(), 5); + EXPECT_FLOAT_EQ(lua.safe_script("return v:length2()").get(), 25); + EXPECT_FALSE(lua.safe_script("return util.vector2(1, 2) == util.vector2(1, 3)").get()); + EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)").get()); + EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)").get()); + EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) == util.vector2(2, 4) / 2").get()); + EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) * 2 == util.vector2(2, 4)").get()); + EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2) * v").get(), 17); + EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2):dot(v)").get(), 17); + EXPECT_ERROR(lua.safe_script("v2, len = v.normalize()"), "value is not a valid userdata"); // checks that it doesn't segfault + lua.safe_script("v2, len = v:normalize()"); + EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 5); + EXPECT_TRUE(lua.safe_script("return v2 == util.vector2(3/5, 4/5)").get()); + lua.safe_script("_, len = util.vector2(0, 0):normalize()"); + EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 0); + } + + TEST(LuaUtilPackageTest, Vector3) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua.safe_script("v = util.vector3(5, 12, 13)"); + EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), 5); + EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 12); + EXPECT_FLOAT_EQ(lua.safe_script("return v.z").get(), 13); + EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(5, 12, 13)"); + EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length()").get(), 5); + EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length2()").get(), 25); + EXPECT_FALSE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(1, 3, 2)").get()); + EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)").get()); + EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)").get()); + EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2").get()); + EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)").get()); + EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1) * v").get(), 5*3 + 12*2 + 13*1); + EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1):dot(v)").get(), 5*3 + 12*2 + 13*1); + EXPECT_TRUE(lua.safe_script("return util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)").get()); + EXPECT_ERROR(lua.safe_script("v2, len = util.vector3(3, 4, 0).normalize()"), "value is not a valid userdata"); + lua.safe_script("v2, len = util.vector3(3, 4, 0):normalize()"); + EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 5); + EXPECT_TRUE(lua.safe_script("return v2 == util.vector3(3/5, 4/5, 0)").get()); + lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()"); + EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 0); + } + + TEST(LuaUtilPackageTest, UtilityFunctions) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))"); + EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), -0.5); + EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 0.86602539); + EXPECT_FLOAT_EQ(lua.safe_script("return util.normalizeAngle(math.pi * 10 + 0.1)").get(), 0.1); + EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(0.1, 0, 1.5)").get(), 0.1); + EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(-0.1, 0, 1.5)").get(), 0); + EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(2.1, 0, 1.5)").get(), 1.5); + } + +} diff --git a/apps/openmw_test_suite/lua/testing_util.hpp b/apps/openmw_test_suite/lua/testing_util.hpp new file mode 100644 index 0000000000..28c4d59930 --- /dev/null +++ b/apps/openmw_test_suite/lua/testing_util.hpp @@ -0,0 +1,59 @@ +#ifndef LUA_TESTING_UTIL_H +#define LUA_TESTING_UTIL_H + +#include + +#include +#include + +namespace +{ + + class TestFile : public VFS::File + { + public: + explicit TestFile(std::string content) : mContent(std::move(content)) {} + + Files::IStreamPtr open() override + { + return std::make_shared(mContent, std::ios_base::in); + } + + private: + const std::string mContent; + }; + + struct TestData : public VFS::Archive + { + std::map mFiles; + + TestData(std::map files) : mFiles(std::move(files)) {} + + void listResources(std::map& out, char (*normalize_function) (char)) override + { + out = mFiles; + } + + bool contains(const std::string& file, char (*normalize_function) (char)) const override + { + return mFiles.count(file) != 0; + } + + std::string getDescription() const override { return "TestData"; } + + }; + + inline std::unique_ptr createTestVFS(std::map files) + { + auto vfs = std::make_unique(true); + vfs->addArchive(new TestData(std::move(files))); + vfs->buildIndex(); + return vfs; + } + + #define EXPECT_ERROR(X, ERR_SUBSTR) try { X; FAIL() << "Expected error"; } \ + catch (std::exception& e) { EXPECT_THAT(e.what(), HasSubstr(ERR_SUBSTR)); } + +} + +#endif // LUA_TESTING_UTIL_H diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 43987d6c7b..c09e118cbe 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -28,6 +28,10 @@ endif (GIT_CHECKOUT) # source files +add_component_dir (lua + luastate utilpackage + ) + add_component_dir (settings settings parser ) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp new file mode 100644 index 0000000000..4020c815d0 --- /dev/null +++ b/components/lua/luastate.cpp @@ -0,0 +1,169 @@ +#include "luastate.hpp" + +#ifndef NO_LUAJIT +#include +#endif // NO_LUAJIT + +#include + +namespace LuaUtil +{ + + static std::string packageNameToPath(std::string_view packageName) + { + std::string res(packageName); + for (size_t i = 0; i < res.size(); ++i) + if (res[i] == '.') + res[i] = '/'; + res.append(".lua"); + return res; + } + + static const std::string safeFunctions[] = { + "assert", "error", "ipairs", "next", "pairs", "pcall", "select", "tonumber", "tostring", + "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "getmetatable", "setmetatable"}; + static const std::string safePackages[] = {"coroutine", "math", "string", "table"}; + + LuaState::LuaState(const VFS::Manager* vfs) : mVFS(vfs) + { + mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::string, sol::lib::table); + + mLua["math"]["randomseed"](static_cast(time(NULL))); + mLua["math"]["randomseed"] = sol::nil; + + mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; + mLua.script(R"(printToLog = function(name, ...) + local msg = name + for _, v in ipairs({...}) do + msg = msg .. '\t' .. tostring(v) + end + return writeToLog(msg) + end)"); + mLua.script("printGen = function(name) return function(...) return printToLog(name, ...) end end"); + + // Some fixes for compatibility between different Lua versions + if (mLua["unpack"] == sol::nil) + mLua["unpack"] = mLua["table"]["unpack"]; + else if (mLua["table"]["unpack"] == sol::nil) + mLua["table"]["unpack"] = mLua["unpack"]; + + mSandboxEnv = sol::table(mLua, sol::create); + mSandboxEnv["_VERSION"] = mLua["_VERSION"]; + for (const std::string& s : safeFunctions) + { + if (mLua[s] == sol::nil) throw std::logic_error("Lua function not found: " + s); + mSandboxEnv[s] = mLua[s]; + } + for (const std::string& s : safePackages) + { + if (mLua[s] == sol::nil) throw std::logic_error("Lua package not found: " + s); + mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]); + } + } + + LuaState::~LuaState() + { + // Should be cleaned before destructing mLua. + mCommonPackages.clear(); + mSandboxEnv = sol::nil; + } + + sol::table LuaState::makeReadOnly(sol::table table) + { + if (table.is()) + return table; // it is already userdata, no sense to wrap it again + + table[sol::meta_function::index] = table; + sol::stack::push(mLua, std::move(table)); + lua_newuserdata(mLua, 0); + lua_pushvalue(mLua, -2); + lua_setmetatable(mLua, -2); + return sol::stack::pop(mLua); + } + + sol::table LuaState::getMutableFromReadOnly(const sol::userdata& ro) + { + sol::stack::push(mLua, ro); + lua_getmetatable(mLua, -1); + sol::table res = sol::stack::pop(mLua); + lua_pop(mLua, 1); + return res; + } + + void LuaState::addCommonPackage(const std::string& packageName, const sol::object& package) + { + if (package.is()) + mCommonPackages[packageName] = package; + else + mCommonPackages[packageName] = makeReadOnly(package); + } + + sol::protected_function_result LuaState::runInNewSandbox( + const std::string& path, const std::string& namePrefix, + const std::map& packages, const sol::object& hiddenData) + { + sol::protected_function script = loadScript(path); + + sol::environment env(mLua, sol::create, mSandboxEnv); + std::string envName = namePrefix + "[" + path + "]:"; + env["print"] = mLua["printGen"](envName); + + sol::table loaded(mLua, sol::create); + for (const auto& [key, value] : mCommonPackages) + loaded[key] = value; + for (const auto& [key, value] : packages) + loaded[key] = value; + env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) + { + sol::table packages = loaded; + sol::object package = packages[packageName]; + if (package == sol::nil) + { + sol::protected_function packageLoader = loadScript(packageNameToPath(packageName)); + sol::set_environment(env, packageLoader); + package = throwIfError(packageLoader()); + if (!package.is()) + throw std::runtime_error("Lua package must return a table."); + packages[packageName] = package; + } + else if (package.is()) + package = packages[packageName] = call(package.as(), hiddenData); + return package; + }; + + sol::set_environment(env, script); + return call(script); + } + + sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) + { + if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) + throw std::runtime_error("Lua error: " + res.get()); + else + return std::move(res); + } + + sol::protected_function LuaState::loadScript(const std::string& path) + { + auto iter = mCompiledScripts.find(path); + if (iter != mCompiledScripts.end()) + return mLua.load(iter->second.as_string_view(), path, sol::load_mode::binary); + + std::string fileContent(std::istreambuf_iterator(*mVFS->get(path)), {}); + sol::load_result res = mLua.load(fileContent, path, sol::load_mode::text); + if (!res.valid()) + throw std::runtime_error("Lua error: " + res.get()); + mCompiledScripts[path] = res.get().dump(); + return res; + } + + std::string getLuaVersion() + { + #ifdef NO_LUAJIT + return LUA_RELEASE; + #else + return LUA_RELEASE " (" LUAJIT_VERSION ")"; + #endif + } + +} diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp new file mode 100644 index 0000000000..9cb27fb114 --- /dev/null +++ b/components/lua/luastate.hpp @@ -0,0 +1,107 @@ +#ifndef COMPONENTS_LUA_LUASTATE_H +#define COMPONENTS_LUA_LUASTATE_H + +#include + +#include + +#include + +namespace LuaUtil +{ + + std::string getLuaVersion(); + + // Holds Lua state. + // Provides additional features: + // - Load scripts from the virtual filesystem; + // - Caching of loaded scripts; + // - Disable unsafe Lua functions; + // - Run every instance of every script in a separate sandbox; + // - Forbid any interactions between sandboxes except than via provided API; + // - Access to common read-only resources from different sandboxes; + // - Replace standard `require` with a safe version that allows to search + // Lua libraries (only source, no dll's) in the virtual filesystem; + // - Make `print` to add the script name to the every message and + // write to Log rather than directly to stdout; + class LuaState + { + public: + explicit LuaState(const VFS::Manager* vfs); + ~LuaState(); + + // Returns underlying sol::state. + sol::state& sol() { return mLua; } + + // A shortcut to create a new Lua table. + sol::table newTable() { return sol::table(mLua, sol::create); } + + // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. + // Needed to forbid any changes in common resources that can accessed from different sandboxes. + sol::table makeReadOnly(sol::table); + sol::table getMutableFromReadOnly(const sol::userdata&); + + // Registers a package that will be available from every sandbox via `require(name)`. + // The package can be either a sol::table with an API or a sol::function. If it is a function, + // it will be evaluated (once per sandbox) the first time when requested. If the package + // is a table, then `makeReadOnly` is applied to it automatically (but not to other tables it contains). + void addCommonPackage(const std::string& packageName, const sol::object& package); + + // Creates a new sandbox, runs a script, and returns the result + // (the result is expected to be an interface of the script). + // Args: + // path: path to the script in the virtual filesystem; + // namePrefix: sandbox name will be "[]". Sandbox name + // will be added to every `print` output. + // packages: additional packages that should be available from the sandbox via `require`. Each package + // should be either a sol::table or a sol::function. If it is a function, it will be evaluated + // (once per sandbox) with the argument 'hiddenData' the first time when requested. + sol::protected_function_result runInNewSandbox(const std::string& path, + const std::string& namePrefix = "", + const std::map& packages = {}, + const sol::object& hiddenData = sol::nil); + + void dropScriptCache() { mCompiledScripts.clear(); } + + private: + static sol::protected_function_result throwIfError(sol::protected_function_result&&); + template + friend sol::protected_function_result call(sol::protected_function fn, Args&&... args); + + sol::protected_function loadScript(const std::string& path); + + sol::state mLua; + sol::table mSandboxEnv; + std::map mCompiledScripts; + std::map mCommonPackages; + const VFS::Manager* mVFS; + }; + + // Should be used for every call of every Lua function. + // It is a workaround for a bug in `sol`. See https://github.com/ThePhD/sol2/issues/1078 + template + sol::protected_function_result call(sol::protected_function fn, Args&&... args) + { + try + { + return LuaState::throwIfError(fn(std::forward(args)...)); + } + catch (std::exception&) { throw; } + catch (...) { throw std::runtime_error("Unknown error"); } + } + + // getFieldOrNil(table, "a", "b", "c") returns table["a"]["b"]["c"] or nil if some of the fields doesn't exist. + template + sol::object getFieldOrNil(const sol::object& table, std::string_view first, const Str&... str) + { + if (!table.is()) + return sol::nil; + if constexpr (sizeof...(str) == 0) + return table.as()[first]; + else + return getFieldOrNil(table.as()[first], str...); + } + +} + +#endif // COMPONENTS_LUA_LUASTATE_H diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp new file mode 100644 index 0000000000..abcc6d424e --- /dev/null +++ b/components/lua/utilpackage.cpp @@ -0,0 +1,98 @@ +#include "utilpackage.hpp" + +#include +#include + +#include + +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; +} + +namespace LuaUtil +{ + + sol::table initUtilPackage(sol::state& lua) + { + sol::table util(lua, sol::create); + + // TODO: Add bindings for osg::Matrix + + // Lua bindings for osg::Vec2f + util["vector2"] = [](float x, float y) { return osg::Vec2f(x, y); }; + sol::usertype vec2Type = lua.new_usertype("Vec2"); + vec2Type["x"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.x(); } ); + vec2Type["y"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.y(); } ); + vec2Type[sol::meta_function::to_string] = [](const osg::Vec2f& v) { + std::stringstream ss; + ss << "(" << v.x() << ", " << v.y() << ")"; + return ss.str(); + }; + vec2Type[sol::meta_function::unary_minus] = [](const osg::Vec2f& a) { return -a; }; + vec2Type[sol::meta_function::addition] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a + b; }; + vec2Type[sol::meta_function::subtraction] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a - b; }; + vec2Type[sol::meta_function::equal_to] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a == b; }; + vec2Type[sol::meta_function::multiplication] = sol::overload( + [](const osg::Vec2f& a, float c) { return a * c; }, + [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; }); + vec2Type[sol::meta_function::division] = [](const osg::Vec2f& a, float c) { return a / c; }; + vec2Type["dot"] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; }; + vec2Type["length"] = &osg::Vec2f::length; + vec2Type["length2"] = &osg::Vec2f::length2; + vec2Type["normalize"] = [](const osg::Vec2f& v) { + float len = v.length(); + if (len == 0) + return std::make_tuple(osg::Vec2f(), 0.f); + else + return std::make_tuple(v * (1.f / len), len); + }; + vec2Type["rotate"] = &Misc::rotateVec2f; + + // Lua bindings for osg::Vec3f + util["vector3"] = [](float x, float y, float z) { return osg::Vec3f(x, y, z); }; + sol::usertype vec3Type = lua.new_usertype("Vec3"); + vec3Type["x"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.x(); } ); + vec3Type["y"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.y(); } ); + vec3Type["z"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.z(); } ); + vec3Type[sol::meta_function::to_string] = [](const osg::Vec3f& v) { + std::stringstream ss; + ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; + return ss.str(); + }; + vec3Type[sol::meta_function::unary_minus] = [](const osg::Vec3f& a) { return -a; }; + vec3Type[sol::meta_function::addition] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a + b; }; + vec3Type[sol::meta_function::subtraction] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a - b; }; + vec3Type[sol::meta_function::equal_to] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a == b; }; + vec3Type[sol::meta_function::multiplication] = sol::overload( + [](const osg::Vec3f& a, float c) { return a * c; }, + [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; }); + vec3Type[sol::meta_function::division] = [](const osg::Vec3f& a, float c) { return a / c; }; + vec3Type[sol::meta_function::involution] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; }; + vec3Type["dot"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; }; + vec3Type["cross"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; }; + vec3Type["length"] = &osg::Vec3f::length; + vec3Type["length2"] = &osg::Vec3f::length2; + vec3Type["normalize"] = [](const osg::Vec3f& v) { + float len = v.length(); + if (len == 0) + return std::make_tuple(osg::Vec3f(), 0.f); + else + return std::make_tuple(v * (1.f / len), len); + }; + + // Utility functions + util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); }; + // NOTE: `util["clamp"] = std::clamp` causes error 'AddressSanitizer: stack-use-after-scope' + util["normalizeAngle"] = &Misc::normalizeAngle; + + return util; + } + +} diff --git a/components/lua/utilpackage.hpp b/components/lua/utilpackage.hpp new file mode 100644 index 0000000000..06996fb96a --- /dev/null +++ b/components/lua/utilpackage.hpp @@ -0,0 +1,13 @@ +#ifndef COMPONENTS_LUA_UTILPACKAGE_H +#define COMPONENTS_LUA_UTILPACKAGE_H + +#include + +namespace LuaUtil +{ + + sol::table initUtilPackage(sol::state&); + +} + +#endif // COMPONENTS_LUA_UTILPACKAGE_H From 8dbaf6022cdf88d7ab44c7cfa34326344d02d9c2 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 10 Jan 2021 14:41:50 +0100 Subject: [PATCH 0976/2859] Add components/lua/serilalization --- apps/openmw_test_suite/CMakeLists.txt | 1 + .../lua/test_serialization.cpp | 207 ++++++++++++++ components/CMakeLists.txt | 2 +- components/lua/serialization.cpp | 255 ++++++++++++++++++ components/lua/serialization.hpp | 34 +++ 5 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 apps/openmw_test_suite/lua/test_serialization.cpp create mode 100644 components/lua/serialization.cpp create mode 100644 components/lua/serialization.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 3f82df1a52..8c54af0e00 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -17,6 +17,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) lua/test_lua.cpp lua/test_utilpackage.cpp + lua/test_serialization.cpp misc/test_stringops.cpp misc/test_endianness.cpp diff --git a/apps/openmw_test_suite/lua/test_serialization.cpp b/apps/openmw_test_suite/lua/test_serialization.cpp new file mode 100644 index 0000000000..d3c01f6298 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_serialization.cpp @@ -0,0 +1,207 @@ +#include "gmock/gmock.h" +#include + +#include +#include + +#include + +#include + +#include "testing_util.hpp" + +namespace +{ + using namespace testing; + + TEST(LuaSerializationTest, Nil) + { + sol::state lua; + EXPECT_EQ(LuaUtil::serialize(sol::nil), ""); + EXPECT_EQ(LuaUtil::deserialize(lua, ""), sol::nil); + } + + TEST(LuaSerializationTest, Number) + { + sol::state lua; + std::string serialized = LuaUtil::serialize(sol::make_object(lua, 3.14)); + EXPECT_EQ(serialized.size(), 10); // version, type, 8 bytes value + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_FLOAT_EQ(value.as(), 3.14); + } + + TEST(LuaSerializationTest, Boolean) + { + sol::state lua; + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, true)); + EXPECT_EQ(serialized.size(), 3); // version, type, 1 byte value + sol::object value = LuaUtil::deserialize(lua, serialized); + EXPECT_FALSE(value.is()); + ASSERT_TRUE(value.is()); + EXPECT_TRUE(value.as()); + } + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, false)); + EXPECT_EQ(serialized.size(), 3); // version, type, 1 byte value + sol::object value = LuaUtil::deserialize(lua, serialized); + EXPECT_FALSE(value.is()); + ASSERT_TRUE(value.is()); + EXPECT_FALSE(value.as()); + } + } + + TEST(LuaSerializationTest, String) + { + sol::state lua; + std::string_view emptyString = ""; + std::string_view shortString = "abc"; + std::string_view longString = "It is a string with more than 32 characters..........................."; + + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, emptyString)); + EXPECT_EQ(serialized.size(), 2); // version, type + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), emptyString); + } + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, shortString)); + EXPECT_EQ(serialized.size(), 2 + shortString.size()); // version, type, str data + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), shortString); + } + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, longString)); + EXPECT_EQ(serialized.size(), 6 + longString.size()); // version, type, size, str data + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), longString); + } + } + + TEST(LuaSerializationTest, Vector) + { + sol::state lua; + osg::Vec2f vec2(1, 2); + osg::Vec3f vec3(1, 2, 3); + + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec2)); + EXPECT_EQ(serialized.size(), 10); // version, type, 2x float + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), vec2); + } + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec3)); + EXPECT_EQ(serialized.size(), 14); // version, type, 3x float + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), vec3); + } + } + + TEST(LuaSerializationTest, Table) + { + sol::state lua; + sol::table table(lua, sol::create); + table["aa"] = 1; + table["ab"] = true; + table["nested"] = sol::table(lua, sol::create); + table["nested"]["aa"] = 2; + table["nested"]["bb"] = "something"; + table["nested"][5] = -0.5; + table["nested_empty"] = sol::table(lua, sol::create); + table[1] = osg::Vec2f(1, 2); + table[2] = osg::Vec2f(2, 1); + + std::string serialized = LuaUtil::serialize(table); + EXPECT_EQ(serialized.size(), 123); + sol::table res_table = LuaUtil::deserialize(lua, serialized); + + EXPECT_EQ(res_table.get("aa"), 1); + EXPECT_EQ(res_table.get("ab"), true); + EXPECT_EQ(res_table.get("nested").get("aa"), 2); + EXPECT_EQ(res_table.get("nested").get("bb"), "something"); + EXPECT_FLOAT_EQ(res_table.get("nested").get(5), -0.5); + EXPECT_EQ(res_table.get(1), osg::Vec2f(1, 2)); + EXPECT_EQ(res_table.get(2), osg::Vec2f(2, 1)); + } + + struct TestStruct1 { double a, b; }; + struct TestStruct2 { int a, b; }; + + class TestSerializer final : public LuaUtil::UserdataSerializer + { + bool serialize(LuaUtil::BinaryData& out, const sol::userdata& data) const override + { + if (data.is()) + { + TestStruct1 t = data.as(); + t.a = Misc::toLittleEndian(t.a); + t.b = Misc::toLittleEndian(t.b); + append(out, "ts1", &t, sizeof(t)); + return true; + } + if (data.is()) + { + TestStruct2 t = data.as(); + t.a = Misc::toLittleEndian(t.a); + t.b = Misc::toLittleEndian(t.b); + append(out, "test_struct2", &t, sizeof(t)); + return true; + } + return false; + } + + bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state& lua) const override + { + if (typeName == "ts1") + { + if (sizeof(TestStruct1) != binaryData.size()) + throw std::runtime_error("Incorrect binaryData.size() for TestStruct1: " + std::to_string(binaryData.size())); + TestStruct1 t = *reinterpret_cast(binaryData.data()); + t.a = Misc::fromLittleEndian(t.a); + t.b = Misc::fromLittleEndian(t.b); + sol::stack::push(lua, t); + return true; + } + if (typeName == "test_struct2") + { + if (sizeof(TestStruct2) != binaryData.size()) + throw std::runtime_error("Incorrect binaryData.size() for TestStruct2: " + std::to_string(binaryData.size())); + TestStruct2 t = *reinterpret_cast(binaryData.data()); + t.a = Misc::fromLittleEndian(t.a); + t.b = Misc::fromLittleEndian(t.b); + sol::stack::push(lua, t); + return true; + } + return false; + } + }; + + TEST(LuaSerializationTest, UserdataSerializer) + { + sol::state lua; + sol::table table(lua, sol::create); + table["x"] = TestStruct1{1.5, 2.5}; + table["y"] = TestStruct2{4, 3}; + TestSerializer serializer; + + EXPECT_ERROR(LuaUtil::serialize(table), "Unknown userdata"); + std::string serialized = LuaUtil::serialize(table, &serializer); + EXPECT_ERROR(LuaUtil::deserialize(lua, serialized), "Unknown type:"); + sol::table res = LuaUtil::deserialize(lua, serialized, &serializer); + + TestStruct1 rx = res.get("x"); + TestStruct2 ry = res.get("y"); + EXPECT_EQ(rx.a, 1.5); + EXPECT_EQ(rx.b, 2.5); + EXPECT_EQ(ry.a, 4); + EXPECT_EQ(ry.b, 3); + } + +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c09e118cbe..6724a9fa29 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -29,7 +29,7 @@ endif (GIT_CHECKOUT) # source files add_component_dir (lua - luastate utilpackage + luastate utilpackage serialization ) add_component_dir (settings diff --git a/components/lua/serialization.cpp b/components/lua/serialization.cpp new file mode 100644 index 0000000000..acbe3c23c7 --- /dev/null +++ b/components/lua/serialization.cpp @@ -0,0 +1,255 @@ +#include "serialization.hpp" + +#include +#include + +#include + +namespace LuaUtil +{ + + constexpr unsigned char FORMAT_VERSION = 0; + + enum class SerializedType + { + NUMBER = 0x0, + LONG_STRING = 0x1, + BOOLEAN = 0x2, + TABLE_START = 0x3, + TABLE_END = 0x4, + + VEC2 = 0x10, + VEC3 = 0x11, + }; + constexpr unsigned char SHORT_STRING_FLAG = 0x20; // 0x001SSSSS. SSSSS = string length + constexpr unsigned char CUSTOM_FULL_FLAG = 0x40; // 0b01TTTTTT + 32bit dataSize + constexpr unsigned char CUSTOM_COMPACT_FLAG = 0x80; // 0b1SSSSTTT. SSSS = dataSize, TTT = (typeName size - 1) + + static void appendType(BinaryData& out, SerializedType type) + { + out.push_back(static_cast(type)); + } + + template + static void appendValue(BinaryData& out, T v) + { + v = Misc::toLittleEndian(v); + out.append(reinterpret_cast(&v), sizeof(v)); + } + + template + static T getValue(std::string_view& binaryData) + { + if (binaryData.size() < sizeof(T)) + throw std::runtime_error("Unexpected end"); + T v; + std::memcpy(&v, binaryData.data(), sizeof(T)); + binaryData = binaryData.substr(sizeof(T)); + return Misc::fromLittleEndian(v); + } + + static void appendString(BinaryData& out, std::string_view str) + { + if (str.size() < 32) + out.push_back(SHORT_STRING_FLAG | char(str.size())); + else + { + appendType(out, SerializedType::LONG_STRING); + appendValue(out, str.size()); + } + out.append(str.data(), str.size()); + } + + static void appendData(BinaryData& out, const void* data, size_t dataSize) + { + out.append(reinterpret_cast(data), dataSize); + } + + void UserdataSerializer::append(BinaryData& out, std::string_view typeName, const void* data, size_t dataSize) + { + assert(!typeName.empty() && typeName.size() <= 64); + if (typeName.size() <= 8 && dataSize < 16) + { // Compact form: 0b1SSSSTTT. SSSS = dataSize, TTT = (typeName size - 1). + unsigned char t = CUSTOM_COMPACT_FLAG | (dataSize << 3) | (typeName.size() - 1); + out.push_back(t); + } + else + { // Full form: 0b01TTTTTT + 32bit dataSize. + unsigned char t = CUSTOM_FULL_FLAG | (typeName.size() - 1); + out.push_back(t); + appendValue(out, dataSize); + } + out.append(typeName.data(), typeName.size()); + appendData(out, data, dataSize); + } + + static void serializeUserdata(BinaryData& out, const sol::userdata& data, const UserdataSerializer* customSerializer) + { + if (data.is()) + { + appendType(out, SerializedType::VEC2); + osg::Vec2f v = data.as(); + appendValue(out, v.x()); + appendValue(out, v.y()); + return; + } + if (data.is()) + { + appendType(out, SerializedType::VEC3); + osg::Vec3f v = data.as(); + appendValue(out, v.x()); + appendValue(out, v.y()); + appendValue(out, v.z()); + return; + } + if (customSerializer && customSerializer->serialize(out, data)) + return; + else + throw std::runtime_error("Unknown userdata"); + } + + static void serialize(BinaryData& out, const sol::object& obj, const UserdataSerializer* customSerializer, int recursionCounter) + { + if (obj.get_type() == sol::type::lightuserdata) + throw std::runtime_error("light userdata is not allowed to be serialized"); + if (obj.is()) + throw std::runtime_error("functions are not allowed to be serialized"); + else if (obj.is()) + serializeUserdata(out, obj, customSerializer); + else if (obj.is()) + { + if (recursionCounter >= 32) + throw std::runtime_error("Can not serialize more than 32 nested tables. Likely the table contains itself."); + sol::table table = obj; + appendType(out, SerializedType::TABLE_START); + for (auto& [key, value] : table) + { + serialize(out, key, customSerializer, recursionCounter + 1); + serialize(out, value, customSerializer, recursionCounter + 1); + } + appendType(out, SerializedType::TABLE_END); + } + else if (obj.is()) + { + appendType(out, SerializedType::NUMBER); + appendValue(out, obj.as()); + } + else if (obj.is()) + appendString(out, obj.as()); + else if (obj.is()) + { + char v = obj.as() ? 1 : 0; + appendType(out, SerializedType::BOOLEAN); + out.push_back(v); + } else + throw std::runtime_error("Unknown lua type"); + } + + static void deserializeImpl(sol::state& lua, std::string_view& binaryData, const UserdataSerializer* customSerializer) + { + if (binaryData.empty()) + throw std::runtime_error("Unexpected end"); + unsigned char type = binaryData[0]; + binaryData = binaryData.substr(1); + if (type & (CUSTOM_COMPACT_FLAG | CUSTOM_FULL_FLAG)) + { + size_t typeNameSize, dataSize; + if (type & CUSTOM_COMPACT_FLAG) + { // Compact form: 0b1SSSSTTT. SSSS = dataSize, TTT = (typeName size - 1). + typeNameSize = (type & 7) + 1; + dataSize = (type >> 3) & 15; + } + else + { // Full form: 0b01TTTTTT + 32bit dataSize. + typeNameSize = (type & 63) + 1; + dataSize = getValue(binaryData); + } + std::string_view typeName = binaryData.substr(0, typeNameSize); + std::string_view data = binaryData.substr(typeNameSize, dataSize); + binaryData = binaryData.substr(typeNameSize + dataSize); + if (!customSerializer || !customSerializer->deserialize(typeName, data, lua)) + throw std::runtime_error("Unknown type: " + std::string(typeName)); + return; + } + if (type & SHORT_STRING_FLAG) + { + size_t size = type & 0x1f; + sol::stack::push(lua.lua_state(), binaryData.substr(0, size)); + binaryData = binaryData.substr(size); + return; + } + switch (static_cast(type)) + { + case SerializedType::NUMBER: + sol::stack::push(lua.lua_state(), getValue(binaryData)); + return; + case SerializedType::BOOLEAN: + sol::stack::push(lua.lua_state(), getValue(binaryData) != 0); + return; + case SerializedType::LONG_STRING: + { + uint32_t size = getValue(binaryData); + sol::stack::push(lua.lua_state(), binaryData.substr(0, size)); + binaryData = binaryData.substr(size); + return; + } + case SerializedType::TABLE_START: + { + lua_createtable(lua, 0, 0); + while (!binaryData.empty() && binaryData[0] != char(SerializedType::TABLE_END)) + { + deserializeImpl(lua, binaryData, customSerializer); + deserializeImpl(lua, binaryData, customSerializer); + lua_settable(lua, -3); + } + if (binaryData.empty()) + throw std::runtime_error("Unexpected end"); + binaryData = binaryData.substr(1); + return; + } + case SerializedType::TABLE_END: + throw std::runtime_error("Unexpected table end"); + case SerializedType::VEC2: + { + float x = getValue(binaryData); + float y = getValue(binaryData); + sol::stack::push(lua.lua_state(), osg::Vec2f(x, y)); + return; + } + case SerializedType::VEC3: + { + float x = getValue(binaryData); + float y = getValue(binaryData); + float z = getValue(binaryData); + sol::stack::push(lua.lua_state(), osg::Vec3f(x, y, z)); + return; + } + default: throw std::runtime_error("Unknown type: " + std::to_string(type)); + } + } + + BinaryData serialize(const sol::object& obj, const UserdataSerializer* customSerializer) + { + if (obj == sol::nil) + return ""; + BinaryData res; + res.push_back(FORMAT_VERSION); + serialize(res, obj, customSerializer, 0); + return res; + } + + sol::object deserialize(sol::state& lua, std::string_view binaryData, const UserdataSerializer* customSerializer) + { + if (binaryData.empty()) + return sol::nil; + if (binaryData[0] != FORMAT_VERSION) + throw std::runtime_error("Incorrect version of Lua serialization format: " + + std::to_string(static_cast(binaryData[0]))); + binaryData = binaryData.substr(1); + deserializeImpl(lua, binaryData, customSerializer); + if (!binaryData.empty()) + throw std::runtime_error("Unexpected data after serialized object"); + return sol::stack::pop(lua.lua_state()); + } + +} diff --git a/components/lua/serialization.hpp b/components/lua/serialization.hpp new file mode 100644 index 0000000000..63f93baac8 --- /dev/null +++ b/components/lua/serialization.hpp @@ -0,0 +1,34 @@ +#ifndef COMPONENTS_LUA_SERIALIZATION_H +#define COMPONENTS_LUA_SERIALIZATION_H + +#include + +namespace LuaUtil +{ + + // Note: it can contain \0 + using BinaryData = std::string; + + class UserdataSerializer + { + public: + virtual ~UserdataSerializer() {} + + // Appends serialized sol::userdata to the end of BinaryData. + // Returns false if this type of userdata is not supported by this serializer. + virtual bool serialize(BinaryData&, const sol::userdata&) const = 0; + + // Deserializes userdata of type "typeName" from binaryData. Should push the result on stack using sol::stack::push. + // Returns false if this type is not supported by this serializer. + virtual bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state&) const = 0; + + protected: + static void append(BinaryData&, std::string_view typeName, const void* data, size_t dataSize); + }; + + BinaryData serialize(const sol::object&, const UserdataSerializer* customSerializer = nullptr); + sol::object deserialize(sol::state& lua, std::string_view binaryData, const UserdataSerializer* customSerializer = nullptr); + +} + +#endif // COMPONENTS_LUA_SERIALIZATION_H From 479856f8123cbcfe80e3f768bf8d4069070409ec Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 12 Jan 2021 23:18:49 +0100 Subject: [PATCH 0977/2859] Add components/lua/scriptscontainer and components/esm/luascripts --- apps/openmw_test_suite/CMakeLists.txt | 1 + .../lua/test_scriptscontainer.cpp | 375 +++++++++++++++ components/CMakeLists.txt | 4 +- components/esm/luascripts.cpp | 80 ++++ components/esm/luascripts.hpp | 47 ++ components/lua/scriptscontainer.cpp | 428 ++++++++++++++++++ components/lua/scriptscontainer.hpp | 212 +++++++++ 7 files changed, 1145 insertions(+), 2 deletions(-) create mode 100644 apps/openmw_test_suite/lua/test_scriptscontainer.cpp create mode 100644 components/esm/luascripts.cpp create mode 100644 components/esm/luascripts.hpp create mode 100644 components/lua/scriptscontainer.cpp create mode 100644 components/lua/scriptscontainer.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 8c54af0e00..f9d0e8140f 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -16,6 +16,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) esm/variant.cpp lua/test_lua.cpp + lua/test_scriptscontainer.cpp lua/test_utilpackage.cpp lua/test_serialization.cpp diff --git a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp new file mode 100644 index 0000000000..072ad334e8 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp @@ -0,0 +1,375 @@ +#include "gmock/gmock.h" +#include + +#include + +#include +#include + +#include "testing_util.hpp" + +namespace +{ + using namespace testing; + + TestFile invalidScript("not a script"); + TestFile incorrectScript("return { incorrectSection = {}, engineHandlers = { incorrectHandler = function() end } }"); + TestFile emptyScript(""); + + TestFile testScript(R"X( +return { + engineHandlers = { onUpdate = function(dt) print(' update ' .. tostring(dt)) end }, + eventHandlers = { + Event1 = function(eventData) print(' event1 ' .. tostring(eventData.x)) end, + Event2 = function(eventData) print(' event2 ' .. tostring(eventData.x)) end, + Print = function() print('print') end + } +} +)X"); + + TestFile stopEventScript(R"X( +return { + eventHandlers = { + Event1 = function(eventData) + print(' event1 ' .. tostring(eventData.x)) + return eventData.x >= 1 + end + } +} +)X"); + + TestFile loadSaveScript(R"X( +x = 0 +y = 0 +return { + engineHandlers = { + onSave = function(state) + return {x = x, y = y} + end, + onLoad = function(state) + x, y = state.x, state.y + end + }, + eventHandlers = { + Set = function(eventData) + eventData.n = eventData.n - 1 + if eventData.n == 0 then + x, y = eventData.x, eventData.y + end + end, + Print = function() + print(x, y) + end + } +} +)X"); + + TestFile interfaceScript(R"X( +return { + interfaceName = "TestInterface", + interface = { + fn = function(x) print('FN', x) end, + value = 3.5 + }, +} +)X"); + + TestFile overrideInterfaceScript(R"X( +local old = require('openmw.interfaces').TestInterface +return { + interfaceName = "TestInterface", + interface = { + fn = function(x) + print('NEW FN', x) + old.fn(x) + end, + value = old.value + 1 + }, +} +)X"); + + TestFile useInterfaceScript(R"X( +local interfaces = require('openmw.interfaces') +return { + engineHandlers = { + onUpdate = function() + interfaces.TestInterface.fn(interfaces.TestInterface.value) + end, + }, +} +)X"); + + struct LuaScriptsContainerTest : Test + { + std::unique_ptr mVFS = createTestVFS({ + {"invalid.lua", &invalidScript}, + {"incorrect.lua", &incorrectScript}, + {"empty.lua", &emptyScript}, + {"test1.lua", &testScript}, + {"test2.lua", &testScript}, + {"stopEvent.lua", &stopEventScript}, + {"loadSave1.lua", &loadSaveScript}, + {"loadSave2.lua", &loadSaveScript}, + {"testInterface.lua", &interfaceScript}, + {"overrideInterface.lua", &overrideInterfaceScript}, + {"useInterface.lua", &useInterfaceScript}, + }); + + LuaUtil::LuaState mLua{mVFS.get()}; + }; + + TEST_F(LuaScriptsContainerTest, VerifyStructure) + { + LuaUtil::ScriptsContainer scripts(&mLua, "Test"); + { + testing::internal::CaptureStdout(); + EXPECT_FALSE(scripts.addNewScript("invalid.lua")); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_THAT(output, HasSubstr("Can't start Test[invalid.lua]")); + } + { + testing::internal::CaptureStdout(); + EXPECT_TRUE(scripts.addNewScript("incorrect.lua")); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_THAT(output, HasSubstr("Not supported handler 'incorrectHandler' in Test[incorrect.lua]")); + EXPECT_THAT(output, HasSubstr("Not supported section 'incorrectSection' in Test[incorrect.lua]")); + } + { + testing::internal::CaptureStdout(); + EXPECT_TRUE(scripts.addNewScript("empty.lua")); + EXPECT_FALSE(scripts.addNewScript("empty.lua")); // already present + EXPECT_EQ(internal::GetCapturedStdout(), ""); + } + } + + TEST_F(LuaScriptsContainerTest, CallHandler) + { + LuaUtil::ScriptsContainer scripts(&mLua, "Test"); + testing::internal::CaptureStdout(); + EXPECT_TRUE(scripts.addNewScript("test1.lua")); + EXPECT_TRUE(scripts.addNewScript("stopEvent.lua")); + EXPECT_TRUE(scripts.addNewScript("test2.lua")); + scripts.update(1.5f); + EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\t update 1.5\n" + "Test[test2.lua]:\t update 1.5\n"); + } + + TEST_F(LuaScriptsContainerTest, CallEvent) + { + LuaUtil::ScriptsContainer scripts(&mLua, "Test"); + EXPECT_TRUE(scripts.addNewScript("test1.lua")); + EXPECT_TRUE(scripts.addNewScript("stopEvent.lua")); + EXPECT_TRUE(scripts.addNewScript("test2.lua")); + + std::string X0 = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5)); + std::string X1 = LuaUtil::serialize(mLua.sol().create_table_with("x", 1.5)); + + { + testing::internal::CaptureStdout(); + scripts.receiveEvent("SomeEvent", X1); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test has received event 'SomeEvent', but there are no handlers for this event\n"); + } + { + testing::internal::CaptureStdout(); + scripts.receiveEvent("Event1", X1); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[test2.lua]:\t event1 1.5\n" + "Test[stopEvent.lua]:\t event1 1.5\n" + "Test[test1.lua]:\t event1 1.5\n"); + } + { + testing::internal::CaptureStdout(); + scripts.receiveEvent("Event2", X1); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[test2.lua]:\t event2 1.5\n" + "Test[test1.lua]:\t event2 1.5\n"); + } + { + testing::internal::CaptureStdout(); + scripts.receiveEvent("Event1", X0); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[test2.lua]:\t event1 0.5\n" + "Test[stopEvent.lua]:\t event1 0.5\n"); + } + { + testing::internal::CaptureStdout(); + scripts.receiveEvent("Event2", X0); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[test2.lua]:\t event2 0.5\n" + "Test[test1.lua]:\t event2 0.5\n"); + } + } + + TEST_F(LuaScriptsContainerTest, RemoveScript) + { + LuaUtil::ScriptsContainer scripts(&mLua, "Test"); + EXPECT_TRUE(scripts.addNewScript("test1.lua")); + EXPECT_TRUE(scripts.addNewScript("stopEvent.lua")); + EXPECT_TRUE(scripts.addNewScript("test2.lua")); + std::string X = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5)); + + { + testing::internal::CaptureStdout(); + scripts.update(1.5f); + scripts.receiveEvent("Event1", X); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[test1.lua]:\t update 1.5\n" + "Test[test2.lua]:\t update 1.5\n" + "Test[test2.lua]:\t event1 0.5\n" + "Test[stopEvent.lua]:\t event1 0.5\n"); + } + { + testing::internal::CaptureStdout(); + EXPECT_TRUE(scripts.removeScript("stopEvent.lua")); + EXPECT_FALSE(scripts.removeScript("stopEvent.lua")); // already removed + scripts.update(1.5f); + scripts.receiveEvent("Event1", X); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[test1.lua]:\t update 1.5\n" + "Test[test2.lua]:\t update 1.5\n" + "Test[test2.lua]:\t event1 0.5\n" + "Test[test1.lua]:\t event1 0.5\n"); + } + { + testing::internal::CaptureStdout(); + EXPECT_TRUE(scripts.removeScript("test1.lua")); + scripts.update(1.5f); + scripts.receiveEvent("Event1", X); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[test2.lua]:\t update 1.5\n" + "Test[test2.lua]:\t event1 0.5\n"); + } + } + + TEST_F(LuaScriptsContainerTest, Interface) + { + LuaUtil::ScriptsContainer scripts(&mLua, "Test"); + testing::internal::CaptureStdout(); + EXPECT_TRUE(scripts.addNewScript("testInterface.lua")); + EXPECT_TRUE(scripts.addNewScript("overrideInterface.lua")); + EXPECT_TRUE(scripts.addNewScript("useInterface.lua")); + scripts.update(1.5f); + EXPECT_TRUE(scripts.removeScript("overrideInterface.lua")); + scripts.update(1.5f); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[overrideInterface.lua]:\tNEW FN\t4.5\n" + "Test[testInterface.lua]:\tFN\t4.5\n" + "Test[testInterface.lua]:\tFN\t3.5\n"); + } + + TEST_F(LuaScriptsContainerTest, LoadSave) + { + LuaUtil::ScriptsContainer scripts1(&mLua, "Test"); + LuaUtil::ScriptsContainer scripts2(&mLua, "Test"); + LuaUtil::ScriptsContainer scripts3(&mLua, "Test"); + + EXPECT_TRUE(scripts1.addNewScript("loadSave1.lua")); + EXPECT_TRUE(scripts1.addNewScript("test1.lua")); + EXPECT_TRUE(scripts1.addNewScript("loadSave2.lua")); + + EXPECT_TRUE(scripts3.addNewScript("test2.lua")); + EXPECT_TRUE(scripts3.addNewScript("loadSave2.lua")); + + scripts1.receiveEvent("Set", LuaUtil::serialize(mLua.sol().create_table_with( + "n", 1, + "x", 0.5, + "y", 3.5))); + scripts1.receiveEvent("Set", LuaUtil::serialize(mLua.sol().create_table_with( + "n", 2, + "x", 2.5, + "y", 1.5))); + + ESM::LuaScripts data; + scripts1.save(data); + scripts2.load(data, true); + scripts3.load(data, false); + + { + testing::internal::CaptureStdout(); + scripts2.receiveEvent("Print", ""); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[loadSave2.lua]:\t0.5\t3.5\n" + "Test[test1.lua]:\tprint\n" + "Test[loadSave1.lua]:\t2.5\t1.5\n"); + } + { + testing::internal::CaptureStdout(); + scripts3.receiveEvent("Print", ""); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[loadSave2.lua]:\t0.5\t3.5\n" + "Test[test2.lua]:\tprint\n"); + } + } + + TEST_F(LuaScriptsContainerTest, Timers) + { + LuaUtil::ScriptsContainer scripts(&mLua, "Test"); + EXPECT_TRUE(scripts.addNewScript("test1.lua")); + EXPECT_TRUE(scripts.addNewScript("test2.lua")); + + int counter1 = 0, counter2 = 0, counter3 = 0, counter4 = 0; + sol::function fn1 = sol::make_object(mLua.sol(), [&]() { counter1++; }); + sol::function fn2 = sol::make_object(mLua.sol(), [&]() { counter2++; }); + sol::function fn3 = sol::make_object(mLua.sol(), [&](int d) { counter3 += d; }); + sol::function fn4 = sol::make_object(mLua.sol(), [&](int d) { counter4 += d; }); + + scripts.registerTimerCallback("test1.lua", "A", fn3); + scripts.registerTimerCallback("test1.lua", "B", fn4); + scripts.registerTimerCallback("test2.lua", "B", fn3); + scripts.registerTimerCallback("test2.lua", "A", fn4); + + scripts.processTimers(1, 2); + + scripts.setupSerializableTimer(false, 10, "test1.lua", "B", sol::make_object(mLua.sol(), 3)); + scripts.setupSerializableTimer(true, 10, "test2.lua", "B", sol::make_object(mLua.sol(), 4)); + scripts.setupSerializableTimer(false, 5, "test1.lua", "A", sol::make_object(mLua.sol(), 1)); + scripts.setupSerializableTimer(true, 5, "test2.lua", "A", sol::make_object(mLua.sol(), 2)); + scripts.setupSerializableTimer(false, 15, "test1.lua", "A", sol::make_object(mLua.sol(), 10)); + scripts.setupSerializableTimer(false, 15, "test1.lua", "B", sol::make_object(mLua.sol(), 20)); + + scripts.setupUnsavableTimer(false, 10, "test2.lua", fn2); + scripts.setupUnsavableTimer(true, 10, "test1.lua", fn2); + scripts.setupUnsavableTimer(false, 5, "test2.lua", fn1); + scripts.setupUnsavableTimer(true, 5, "test1.lua", fn1); + scripts.setupUnsavableTimer(false, 15, "test2.lua", fn1); + + EXPECT_EQ(counter1, 0); + EXPECT_EQ(counter3, 0); + + scripts.processTimers(6, 4); + + EXPECT_EQ(counter1, 1); + EXPECT_EQ(counter3, 1); + EXPECT_EQ(counter4, 0); + + scripts.processTimers(6, 8); + + EXPECT_EQ(counter1, 2); + EXPECT_EQ(counter2, 0); + EXPECT_EQ(counter3, 1); + EXPECT_EQ(counter4, 2); + + scripts.processTimers(11, 12); + + EXPECT_EQ(counter1, 2); + EXPECT_EQ(counter2, 2); + EXPECT_EQ(counter3, 5); + EXPECT_EQ(counter4, 5); + + ESM::LuaScripts data; + scripts.save(data); + scripts.load(data, true); + scripts.registerTimerCallback("test1.lua", "B", fn4); + + testing::internal::CaptureStdout(); + scripts.processTimers(20, 20); + EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua] callTimer failed: Callback 'A' doesn't exist\n"); + + EXPECT_EQ(counter1, 2); + EXPECT_EQ(counter2, 2); + EXPECT_EQ(counter3, 5); + EXPECT_EQ(counter4, 25); + } + +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6724a9fa29..8b66baab28 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -29,7 +29,7 @@ endif (GIT_CHECKOUT) # source files add_component_dir (lua - luastate utilpackage serialization + luastate scriptscontainer utilpackage serialization ) add_component_dir (settings @@ -84,7 +84,7 @@ add_component_dir (esm loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile - aisequence magiceffects util custommarkerstate stolenitems transport animationstate controlsstate mappings + aisequence magiceffects util custommarkerstate stolenitems transport animationstate controlsstate mappings luascripts ) add_component_dir (esmterrain diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp new file mode 100644 index 0000000000..41d25ee8be --- /dev/null +++ b/components/esm/luascripts.cpp @@ -0,0 +1,80 @@ +#include "luascripts.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +// List of all records, that are related to Lua. +// +// Record: +// LUAM - MWLua::LuaManager +// +// Subrecords: +// LUAW - Start of MWLua::WorldView data +// LUAE - Start of MWLua::LocalEvent or MWLua::GlobalEvent (eventName) +// LUAS - Start LuaUtil::ScriptsContainer data (scriptName) +// LUAD - Serialized Lua variable +// LUAT - MWLua::ScriptsContainer::Timer +// LUAC - Name of a timer callback (string) + +void ESM::saveLuaBinaryData(ESMWriter& esm, const std::string& data) +{ + if (data.empty()) + return; + esm.startSubRecord("LUAD"); + esm.write(data.data(), data.size()); + esm.endRecord("LUAD"); +} + +std::string ESM::loadLuaBinaryData(ESMReader& esm) +{ + std::string data; + if (esm.isNextSub("LUAD")) + { + esm.getSubHeader(); + data.resize(esm.getSubSize()); + esm.getExact(data.data(), data.size()); + } + return data; +} + +void ESM::LuaScripts::load(ESMReader& esm) +{ + while (esm.isNextSub("LUAS")) + { + std::string name = esm.getHString(); + std::string data = loadLuaBinaryData(esm); + std::vector timers; + while (esm.isNextSub("LUAT")) + { + esm.getSubHeader(); + LuaTimer timer; + esm.getT(timer.mHours); + esm.getT(timer.mTime); + timer.mCallbackName = esm.getHNString("LUAC"); + timer.mCallbackArgument = loadLuaBinaryData(esm); + timers.push_back(std::move(timer)); + } + mScripts.push_back({std::move(name), std::move(data), std::move(timers)}); + } +} + +void ESM::LuaScripts::save(ESMWriter& esm) const +{ + for (const LuaScript& script : mScripts) + { + esm.writeHNString("LUAS", script.mScriptPath); + if (!script.mData.empty()) + saveLuaBinaryData(esm, script.mData); + for (const LuaTimer& timer : script.mTimers) + { + esm.startSubRecord("LUAT"); + esm.writeT(timer.mHours); + esm.writeT(timer.mTime); + esm.endRecord("LUAT"); + esm.writeHNString("LUAC", timer.mCallbackName); + if (!timer.mCallbackArgument.empty()) + saveLuaBinaryData(esm, timer.mCallbackArgument); + + } + } +} diff --git a/components/esm/luascripts.hpp b/components/esm/luascripts.hpp new file mode 100644 index 0000000000..ef3553e2fb --- /dev/null +++ b/components/esm/luascripts.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_ESM_LUASCRIPTS_H +#define OPENMW_ESM_LUASCRIPTS_H + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // Storage structure for LuaUtil::ScriptsContainer. This is not a top-level record. + // Used either for global scripts or for local scripts on a specific object. + + struct LuaTimer + { + double mTime; + bool mHours; // false - game seconds, true - game hours + std::string mCallbackName; + std::string mCallbackArgument; // Serialized Lua table. It is a binary data. Can contain '\0'. + }; + + struct LuaScript + { + std::string mScriptPath; + std::string mData; // Serialized Lua table. It is a binary data. Can contain '\0'. + std::vector mTimers; + }; + + struct LuaScripts + { + std::vector mScripts; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + + // Saves binary string `data` (can contain '\0') as record LUAD. + void saveLuaBinaryData(ESM::ESMWriter& esm, const std::string& data); + + // Loads LUAD as binary string. If next subrecord is not LUAD, then returns an empty string. + std::string loadLuaBinaryData(ESM::ESMReader& esm); + +} + +#endif + diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp new file mode 100644 index 0000000000..cb2ab0a97e --- /dev/null +++ b/components/lua/scriptscontainer.cpp @@ -0,0 +1,428 @@ +#include "scriptscontainer.hpp" + +#include + +namespace LuaUtil +{ + static constexpr std::string_view ENGINE_HANDLERS = "engineHandlers"; + static constexpr std::string_view EVENT_HANDLERS = "eventHandlers"; + + static constexpr std::string_view INTERFACE_NAME = "interfaceName"; + static constexpr std::string_view INTERFACE = "interface"; + + static constexpr std::string_view HANDLER_SAVE = "onSave"; + static constexpr std::string_view HANDLER_LOAD = "onLoad"; + + static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers"; + static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers"; + + ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua) + { + registerEngineHandlers({&mUpdateHandlers}); + mPublicInterfaces = sol::table(lua->sol(), sol::create); + addPackage("openmw.interfaces", mPublicInterfaces); + } + + void ScriptsContainer::addPackage(const std::string& packageName, sol::object package) + { + API[packageName] = mLua.makeReadOnly(std::move(package)); + } + + bool ScriptsContainer::addNewScript(const std::string& path) + { + if (mScripts.count(path) != 0) + return false; // already present + + try + { + sol::table hiddenData(mLua.sol(), sol::create); + hiddenData[ScriptId::KEY] = ScriptId{this, path}; + hiddenData[REGISTERED_TIMER_CALLBACKS] = mLua.newTable(); + hiddenData[TEMPORARY_TIMER_CALLBACKS] = mLua.newTable(); + mScripts[path].mHiddenData = hiddenData; + sol::object script = mLua.runInNewSandbox(path, mNamePrefix, API, hiddenData); + std::string interfaceName = ""; + sol::object publicInterface = sol::nil; + if (script != sol::nil) + { + for (auto& [key, value] : sol::table(script)) + { + std::string_view sectionName = key.as(); + if (sectionName == ENGINE_HANDLERS) + parseEngineHandlers(value, path); + else if (sectionName == EVENT_HANDLERS) + parseEventHandlers(value, path); + else if (sectionName == INTERFACE_NAME) + interfaceName = value.as(); + else if (sectionName == INTERFACE) + publicInterface = value.as(); + else + Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << mNamePrefix << "[" << path << "]"; + } + } + if (interfaceName.empty() != (publicInterface == sol::nil)) + Log(Debug::Error) << mNamePrefix << "[" << path << "]: 'interfaceName' should always be used together with 'interface'"; + else if (!interfaceName.empty()) + script.as()[INTERFACE] = mPublicInterfaces[interfaceName] = mLua.makeReadOnly(publicInterface); + mScriptOrder.push_back(path); + mScripts[path].mInterface = std::move(script); + return true; + } + catch (std::exception& e) + { + mScripts.erase(path); + Log(Debug::Error) << "Can't start " << mNamePrefix << "[" << path << "]; " << e.what(); + return false; + } + } + + bool ScriptsContainer::removeScript(const std::string& path) + { + auto it = mScripts.find(path); + if (it == mScripts.end()) + return false; // no such script + sol::object& script = it->second.mInterface; + if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil) + { + std::string_view interfaceName = getFieldOrNil(script, INTERFACE_NAME).as(); + if (mPublicInterfaces[interfaceName] == getFieldOrNil(script, INTERFACE)) + { + mPublicInterfaces[interfaceName] = sol::nil; + auto prevIt = mScriptOrder.rbegin(); + while (*prevIt != path) + prevIt++; + prevIt++; + while (prevIt != mScriptOrder.rend()) + { + sol::object& prevScript = mScripts[*(prevIt++)].mInterface; + sol::object prevInterfaceName = getFieldOrNil(prevScript, INTERFACE_NAME); + if (prevInterfaceName != sol::nil && prevInterfaceName.as() == interfaceName) + { + mPublicInterfaces[interfaceName] = getFieldOrNil(prevScript, INTERFACE); + break; + } + } + } + } + sol::object engineHandlers = getFieldOrNil(script, ENGINE_HANDLERS); + if (engineHandlers != sol::nil) + { + for (auto& [key, value] : sol::table(engineHandlers)) + { + std::string_view handlerName = key.as(); + auto it = mEngineHandlers.find(handlerName); + if (it == mEngineHandlers.end()) + continue; + std::vector& list = it->second->mList; + list.erase(std::find(list.begin(), list.end(), value.as())); + } + } + sol::object eventHandlers = getFieldOrNil(script, EVENT_HANDLERS); + if (eventHandlers != sol::nil) + { + for (auto& [key, value] : sol::table(eventHandlers)) + { + EventHandlerList& list = mEventHandlers.find(key.as())->second; + list.erase(std::find(list.begin(), list.end(), value.as())); + } + } + mScripts.erase(it); + mScriptOrder.erase(std::find(mScriptOrder.begin(), mScriptOrder.end(), path)); + return true; + } + + void ScriptsContainer::parseEventHandlers(sol::table handlers, std::string_view scriptPath) + { + for (auto& [key, value] : handlers) + { + std::string_view eventName = key.as(); + auto it = mEventHandlers.find(eventName); + if (it == mEventHandlers.end()) + it = mEventHandlers.insert({std::string(eventName), EventHandlerList()}).first; + it->second.push_back(value); + } + } + + void ScriptsContainer::parseEngineHandlers(sol::table handlers, std::string_view scriptPath) + { + for (auto& [key, value] : handlers) + { + std::string_view handlerName = key.as(); + if (handlerName == HANDLER_LOAD || handlerName == HANDLER_SAVE) + continue; // save and load are handled separately + auto it = mEngineHandlers.find(handlerName); + if (it == mEngineHandlers.end()) + Log(Debug::Error) << "Not supported handler '" << handlerName << "' in " << mNamePrefix << "[" << scriptPath << "]"; + else + it->second->mList.push_back(value); + } + } + + void ScriptsContainer::receiveEvent(std::string_view eventName, std::string_view eventData) + { + auto it = mEventHandlers.find(eventName); + if (it == mEventHandlers.end()) + { + Log(Debug::Warning) << mNamePrefix << " has received event '" << eventName << "', but there are no handlers for this event"; + return; + } + sol::object data; + try + { + data = LuaUtil::deserialize(mLua.sol(), eventData, mSerializer); + } + catch (std::exception& e) + { + Log(Debug::Error) << mNamePrefix << " can not parse eventData for '" << eventName << "': " << e.what(); + return; + } + EventHandlerList& list = it->second; + for (int i = list.size() - 1; i >= 0; --i) + { + try + { + sol::object res = LuaUtil::call(list[i], data); + if (res != sol::nil && !res.as()) + break; // Skip other handlers if 'false' was returned. + } + catch (std::exception& e) + { + Log(Debug::Error) << mNamePrefix << " eventHandler[" << eventName << "] failed. " << e.what(); + } + } + } + + void ScriptsContainer::registerEngineHandlers(std::initializer_list handlers) + { + for (EngineHandlerList* h : handlers) + mEngineHandlers[h->mName] = h; + } + + void ScriptsContainer::save(ESM::LuaScripts& data) + { + std::map> timers; + auto saveTimerFn = [&](const Timer& timer, bool inHours) + { + if (!timer.mSerializable) + return; + ESM::LuaTimer savedTimer; + savedTimer.mTime = timer.mTime; + savedTimer.mHours = inHours; + savedTimer.mCallbackName = std::get(timer.mCallback); + savedTimer.mCallbackArgument = timer.mSerializedArg; + if (timers.count(timer.mScript) == 0) + timers[timer.mScript] = {}; + timers[timer.mScript].push_back(std::move(savedTimer)); + }; + for (const Timer& timer : mSecondsTimersQueue) + saveTimerFn(timer, false); + for (const Timer& timer : mHoursTimersQueue) + saveTimerFn(timer, true); + data.mScripts.clear(); + for (const std::string& path : mScriptOrder) + { + ESM::LuaScript savedScript; + savedScript.mScriptPath = path; + sol::object handler = getFieldOrNil(mScripts[path].mInterface, ENGINE_HANDLERS, HANDLER_SAVE); + if (handler != sol::nil) + { + try + { + sol::object state = LuaUtil::call(handler); + savedScript.mData = serialize(state, mSerializer); + } + catch (std::exception& e) + { + Log(Debug::Error) << mNamePrefix << "[" << path << "] onSave failed: " << e.what(); + } + } + auto timersIt = timers.find(path); + if (timersIt != timers.end()) + savedScript.mTimers = std::move(timersIt->second); + data.mScripts.push_back(std::move(savedScript)); + } + } + + void ScriptsContainer::load(const ESM::LuaScripts& data, bool resetScriptList) + { + std::map scriptsWithoutSavedData; + if (resetScriptList) + { + removeAllScripts(); + for (const ESM::LuaScript& script : data.mScripts) + addNewScript(script.mScriptPath); + } + else + scriptsWithoutSavedData = mScripts; + mSecondsTimersQueue.clear(); + mHoursTimersQueue.clear(); + for (const ESM::LuaScript& script : data.mScripts) + { + auto iter = mScripts.find(script.mScriptPath); + if (iter == mScripts.end()) + continue; + scriptsWithoutSavedData.erase(iter->first); + iter->second.mHiddenData.get(TEMPORARY_TIMER_CALLBACKS).clear(); + try + { + sol::object handler = getFieldOrNil(iter->second.mInterface, ENGINE_HANDLERS, HANDLER_LOAD); + if (handler != sol::nil) + { + sol::object state = deserialize(mLua.sol(), script.mData, mSerializer); + LuaUtil::call(handler, state); + } + } + catch (std::exception& e) + { + Log(Debug::Error) << mNamePrefix << "[" << script.mScriptPath << "] onLoad failed: " << e.what(); + } + for (const ESM::LuaTimer& savedTimer : script.mTimers) + { + Timer timer; + timer.mCallback = savedTimer.mCallbackName; + timer.mSerializable = true; + timer.mScript = script.mScriptPath; + timer.mTime = savedTimer.mTime; + + try + { + timer.mArg = deserialize(mLua.sol(), savedTimer.mCallbackArgument, mSerializer); + // It is important if the order of content files was changed. The deserialize-serialize procedure + // updates refnums, so timer.mSerializedArg may be not equal to savedTimer.mCallbackArgument. + timer.mSerializedArg = serialize(timer.mArg, mSerializer); + + if (savedTimer.mHours) + mHoursTimersQueue.push_back(std::move(timer)); + else + mSecondsTimersQueue.push_back(std::move(timer)); + } + catch (std::exception& e) + { + Log(Debug::Error) << mNamePrefix << "[" << script.mScriptPath << "] can not load timer: " << e.what(); + } + } + } + for (auto& [path, script] : scriptsWithoutSavedData) + { + script.mHiddenData.get(TEMPORARY_TIMER_CALLBACKS).clear(); + sol::object handler = getFieldOrNil(script.mInterface, ENGINE_HANDLERS, HANDLER_LOAD); + if (handler == sol::nil) + continue; + try { LuaUtil::call(handler); } + catch (std::exception& e) + { + Log(Debug::Error) << mNamePrefix << "[" << path << "] onLoad failed: " << e.what(); + } + } + std::make_heap(mSecondsTimersQueue.begin(), mSecondsTimersQueue.end()); + std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end()); + } + + void ScriptsContainer::removeAllScripts() + { + mScripts.clear(); + mScriptOrder.clear(); + for (auto& [_, handlers] : mEngineHandlers) + handlers->mList.clear(); + mEventHandlers.clear(); + mSecondsTimersQueue.clear(); + mHoursTimersQueue.clear(); + + mPublicInterfaces.clear(); + // Assigned by mLua.makeReadOnly, but `clear` removes it, so we need to assign it again. + mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces; + } + + sol::table ScriptsContainer::getHiddenData(const std::string& scriptPath) + { + auto it = mScripts.find(scriptPath); + if (it == mScripts.end()) + throw std::logic_error("ScriptsContainer::getHiddenData: script doesn't exist"); + return it->second.mHiddenData; + } + + void ScriptsContainer::registerTimerCallback(const std::string& scriptPath, std::string_view callbackName, sol::function callback) + { + getHiddenData(scriptPath)[REGISTERED_TIMER_CALLBACKS][callbackName] = std::move(callback); + } + + void ScriptsContainer::insertTimer(std::vector& timerQueue, Timer&& t) + { + timerQueue.push_back(std::move(t)); + std::push_heap(timerQueue.begin(), timerQueue.end()); + } + + void ScriptsContainer::setupSerializableTimer(bool inHours, double time, const std::string& scriptPath, + std::string_view callbackName, sol::object callbackArg) + { + Timer t; + t.mCallback = std::string(callbackName); + t.mScript = scriptPath; + t.mSerializable = true; + t.mTime = time; + t.mArg = callbackArg; + t.mSerializedArg = serialize(t.mArg, mSerializer); + insertTimer(inHours ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t)); + } + + void ScriptsContainer::setupUnsavableTimer(bool inHours, double time, const std::string& scriptPath, sol::function callback) + { + Timer t; + t.mScript = scriptPath; + t.mSerializable = false; + t.mTime = time; + + t.mCallback = mTemporaryCallbackCounter; + getHiddenData(scriptPath)[TEMPORARY_TIMER_CALLBACKS][mTemporaryCallbackCounter] = std::move(callback); + mTemporaryCallbackCounter++; + + insertTimer(inHours ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t)); + } + + void ScriptsContainer::callTimer(const Timer& t) + { + try + { + sol::table data = getHiddenData(t.mScript); + if (t.mSerializable) + { + const std::string& callbackName = std::get(t.mCallback); + sol::object callback = data[REGISTERED_TIMER_CALLBACKS][callbackName]; + if (!callback.is()) + throw std::logic_error("Callback '" + callbackName + "' doesn't exist"); + LuaUtil::call(callback, t.mArg); + } + else + { + int64_t id = std::get(t.mCallback); + sol::table callbacks = data[TEMPORARY_TIMER_CALLBACKS]; + sol::object callback = callbacks[id]; + if (!callback.is()) + throw std::logic_error("Temporary timer callback doesn't exist"); + LuaUtil::call(callback); + callbacks[id] = sol::nil; + } + } + catch (std::exception& e) + { + Log(Debug::Error) << mNamePrefix << "[" << t.mScript << "] callTimer failed: " << e.what(); + } + } + + void ScriptsContainer::updateTimerQueue(std::vector& timerQueue, double time) + { + while (!timerQueue.empty() && timerQueue.front().mTime <= time) + { + callTimer(timerQueue.front()); + std::pop_heap(timerQueue.begin(), timerQueue.end()); + timerQueue.pop_back(); + } + } + + void ScriptsContainer::processTimers(double gameSeconds, double gameHours) + { + updateTimerQueue(mSecondsTimersQueue, gameSeconds); + updateTimerQueue(mHoursTimersQueue, gameHours); + } + +} diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp new file mode 100644 index 0000000000..a5cb7d3e58 --- /dev/null +++ b/components/lua/scriptscontainer.hpp @@ -0,0 +1,212 @@ +#ifndef COMPONENTS_LUA_SCRIPTSCONTAINER_H +#define COMPONENTS_LUA_SCRIPTSCONTAINER_H + +#include +#include +#include + +#include +#include + +#include "luastate.hpp" +#include "serialization.hpp" + +namespace LuaUtil +{ + +// ScriptsContainer is a base class for all scripts containers (LocalScripts, +// GlobalScripts, PlayerScripts, etc). Each script runs in a separate sandbox. +// Scripts from different containers can interact to each other only via events. +// Scripts within one container can interact via interfaces (not implemented yet). +// All scripts from one container have the same set of API packages available. +// +// Each script should return a table in a specific format that describes its +// handlers and interfaces. Every section of the table is optional. Basic structure: +// +// local function update(dt) +// print("Update") +// end +// +// local function someEventHandler(eventData) +// print("'SomeEvent' received") +// end +// +// return { +// -- Provides interface for other scripts in the same container +// interfaceName = "InterfaceName", +// interface = { +// someFunction = function() print("someFunction was called from another script") end, +// }, +// +// -- Script interface for the engine. Not available for other script. +// -- An error is printed if unknown handler is specified. +// engineHandlers = { +// onUpdate = update, +// onSave = function() return ... end, +// onLoad = function(state) ... end, -- "state" is the data that was earlier returned by onSave +// +// -- Works only if ScriptsContainer::registerEngineHandler is overloaded in a child class +// -- and explicitly supports 'onSomethingElse' +// onSomethingElse = function() print("something else") end +// }, +// +// -- Handlers for events, sent from other scripts. Engine itself never sent events. Any name can be used for an event. +// eventHandlers = { +// SomeEvent = someEventHandler +// } +// } + + class ScriptsContainer + { + public: + struct ScriptId + { + // ScriptId is stored in hidden data (see getHiddenData) with this key. + constexpr static std::string_view KEY = "_id"; + + ScriptsContainer* mContainer; + std::string mPath; + }; + + // `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print` output. + ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix); + ScriptsContainer(const ScriptsContainer&) = delete; + ScriptsContainer(ScriptsContainer&&) = delete; + virtual ~ScriptsContainer() {} + + // Adds package that will be available (via `require`) for all scripts in the container. + // Automatically applies LuaState::makeReadOnly to the package. + void addPackage(const std::string& packageName, sol::object package); + + // Finds a file with given path in the virtual file system, starts as a new script, and adds it to the container. + // Returns `true` if the script was successfully added. Otherwise prints an error message and returns `false`. + // `false` can be returned if either file not found or has syntax errors or such script already exists in the container. + bool addNewScript(const std::string& path); + + // Removes script. Returns `true` if it was successfully removed. + bool removeScript(const std::string& path); + void removeAllScripts(); + + // Processes timers. gameSeconds and gameHours are time (in seconds and in game hours) passed from the game start. + void processTimers(double gameSeconds, double gameHours); + + // Calls `onUpdate` (if present) for every script in the container. + // Handlers are called in the same order as scripts were added. + void update(float dt) { callEngineHandlers(mUpdateHandlers, dt); } + + // Calls event handlers `eventName` (if present) for every script. + // If several scripts register handlers for `eventName`, they are called in reverse order. + // If some handler returns `false`, all remaining handlers are ignored. Any other return value + // (including `nil`) has no effect. + void receiveEvent(std::string_view eventName, std::string_view eventData); + + // Serializer defines how to serialize/deserialize userdata. If serializer is not provided, + // only built-in types and types from util package can be serialized. + void setSerializer(const UserdataSerializer* serializer) { mSerializer = serializer; } + + // Calls engineHandler "onSave" for every script and saves the list of the scripts with serialized data to ESM::LuaScripts. + void save(ESM::LuaScripts&); + + // Calls engineHandler "onLoad" for every script with given data. + // If resetScriptList=true, then removes all currently active scripts and runs the scripts that were saved in ESM::LuaScripts. + // If resetScriptList=false, then list of running scripts is not changed, only engineHandlers "onLoad" are called. + void load(const ESM::LuaScripts&, bool resetScriptList); + + // Returns the hidden data of a script. + // Each script has a corresponding "hidden data" - a lua table that is not accessible from the script itself, + // but can be used by built-in packages. It contains ScriptId and can contain any arbitrary data. + sol::table getHiddenData(const std::string& scriptPath); + + // Callbacks for serializable timers should be registered in advance. + // The script with the given path should already present in the container. + void registerTimerCallback(const std::string& scriptPath, std::string_view callbackName, sol::function callback); + + // Sets up a timer, that can be automatically saved and loaded. + // inHours - false if time unit is game seconds and true if time unit if game hours. + // time - the absolute game time (in seconds or in hours) when the timer should be executed. + // scriptPath - script path in VFS is used as script id. The script with the given path should already present in the container. + // callbackName - callback (should be registered in advance) for this timer. + // callbackArg - parameter for the callback (should be serializable). + void setupSerializableTimer(bool inHours, double time, const std::string& scriptPath, + std::string_view callbackName, sol::object callbackArg); + + // Creates a timer. `callback` is an arbitrary Lua function. This type of timers is called "unsavable" + // because it can not be stored in saves. I.e. loading a saved game will not fully restore the state. + void setupUnsavableTimer(bool inHours, double time, const std::string& scriptPath, sol::function callback); + + protected: + struct EngineHandlerList + { + std::string_view mName; + std::vector mList; + + // "name" must be string literal + explicit EngineHandlerList(std::string_view name) : mName(name) {} + }; + + // Calls given handlers in direct order. + template + void callEngineHandlers(EngineHandlerList& handlers, const Args&... args) + { + for (sol::protected_function& handler : handlers.mList) + { + try { LuaUtil::call(handler, args...); } + catch (std::exception& e) + { + Log(Debug::Error) << mNamePrefix << " " << handlers.mName << " failed. " << e.what(); + } + } + } + + // To add a new engine handler a derived class should register the corresponding EngineHandlerList and define + // a public function (see how ScriptsContainer::update is implemented) that calls `callEngineHandlers`. + void registerEngineHandlers(std::initializer_list handlers); + + const std::string mNamePrefix; + + private: + struct Script + { + sol::object mInterface; // returned value of the script (sol::table or nil) + sol::table mHiddenData; + }; + struct Timer + { + double mTime; + bool mSerializable; + std::string mScript; + std::variant mCallback; // string if serializable, integer otherwise + sol::object mArg; + std::string mSerializedArg; + + bool operator<(const Timer& t) const { return mTime > t.mTime; } + }; + using EventHandlerList = std::vector; + + void parseEngineHandlers(sol::table handlers, std::string_view scriptPath); + void parseEventHandlers(sol::table handlers, std::string_view scriptPath); + + void callTimer(const Timer& t); + void updateTimerQueue(std::vector& timerQueue, double time); + static void insertTimer(std::vector& timerQueue, Timer&& t); + + LuaUtil::LuaState& mLua; + const UserdataSerializer* mSerializer = nullptr; + std::map API; + + std::vector mScriptOrder; + std::map mScripts; + sol::table mPublicInterfaces; + + EngineHandlerList mUpdateHandlers{"onUpdate"}; + std::map mEngineHandlers; + std::map> mEventHandlers; + + std::vector mSecondsTimersQueue; + std::vector mHoursTimersQueue; + int64_t mTemporaryCallbackCounter = 0; + }; + +} + +#endif // COMPONENTS_LUA_SCRIPTSCONTAINER_H From b53667d5551e53e8bde4d17d9e6b2cf0e4e52c18 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 26 Apr 2021 23:20:16 +0200 Subject: [PATCH 0978/2859] Queries. Data structures and lua bindings. --- apps/openmw_test_suite/CMakeLists.txt | 1 + .../lua/test_querypackage.cpp | 29 +++ components/CMakeLists.txt | 4 + components/queries/luabindings.cpp | 118 +++++++++++ components/queries/luabindings.hpp | 8 + components/queries/query.cpp | 185 ++++++++++++++++++ components/queries/query.hpp | 99 ++++++++++ 7 files changed, 444 insertions(+) create mode 100644 apps/openmw_test_suite/lua/test_querypackage.cpp create mode 100644 components/queries/luabindings.cpp create mode 100644 components/queries/luabindings.hpp create mode 100644 components/queries/query.cpp create mode 100644 components/queries/query.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index f9d0e8140f..bd8b032378 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -19,6 +19,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) lua/test_scriptscontainer.cpp lua/test_utilpackage.cpp lua/test_serialization.cpp + lua/test_querypackage.cpp misc/test_stringops.cpp misc/test_endianness.cpp diff --git a/apps/openmw_test_suite/lua/test_querypackage.cpp b/apps/openmw_test_suite/lua/test_querypackage.cpp new file mode 100644 index 0000000000..aeaf992db0 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_querypackage.cpp @@ -0,0 +1,29 @@ +#include "gmock/gmock.h" +#include + +#include + +namespace +{ + using namespace testing; + + TEST(LuaQueryPackageTest, basic) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::string); + Queries::registerQueryBindings(lua); + lua["query"] = Queries::Query("test"); + lua["fieldX"] = Queries::Field({ "x" }, typeid(std::string)); + lua["fieldY"] = Queries::Field({ "y" }, typeid(int)); + lua.safe_script("t = query:where(fieldX:eq('abc') + fieldX:like('%abcd%'))"); + lua.safe_script("t = t:where(fieldY:gt(5))"); + lua.safe_script("t = t:orderBy(fieldX)"); + lua.safe_script("t = t:orderByDesc(fieldY)"); + lua.safe_script("t = t:groupBy(fieldY)"); + lua.safe_script("t = t:limit(10):offset(5)"); + EXPECT_EQ( + lua.safe_script("return tostring(t)").get(), + "SELECT test WHERE ((x == \"abc\") OR (x LIKE \"%abcd%\")) AND (y > 5) ORDER BY x, y DESC GROUP BY y LIMIT 10 OFFSET 5"); + } +} + diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8b66baab28..7629c9798f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -156,6 +156,10 @@ add_component_dir (fallback fallback validate ) +add_component_dir (queries + query luabindings + ) + if(WIN32) add_component_dir (crashcatcher windows_crashcatcher diff --git a/components/queries/luabindings.cpp b/components/queries/luabindings.cpp new file mode 100644 index 0000000000..c830a140f7 --- /dev/null +++ b/components/queries/luabindings.cpp @@ -0,0 +1,118 @@ +#include "luabindings.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; +} + +namespace Queries +{ + template + struct CondBuilder + { + Filter operator()(const Field& field, const sol::object& o) + { + FieldValue value; + if (field.type() == typeid(bool) && o.is()) + value = o.as(); + else if (field.type() == typeid(int32_t) && o.is()) + value = o.as(); + else if (field.type() == typeid(int64_t) && o.is()) + value = o.as(); + else if (field.type() == typeid(float) && o.is()) + value = o.as(); + else if (field.type() == typeid(double) && o.is()) + value = o.as(); + else if (field.type() == typeid(std::string) && o.is()) + value = o.as(); + else + throw std::logic_error("Invalid value for field " + field.toString()); + Filter filter; + filter.add({&field, type, value}); + return filter; + } + }; + + void registerQueryBindings(sol::state& lua) + { + sol::usertype field = lua.new_usertype("QueryField"); + sol::usertype filter = lua.new_usertype("QueryFilter"); + sol::usertype query = lua.new_usertype("Query"); + + field[sol::meta_function::to_string] = [](const Field& f) { return f.toString(); }; + field["eq"] = CondBuilder(); + field["neq"] = CondBuilder(); + field["lt"] = CondBuilder(); + field["lte"] = CondBuilder(); + field["gt"] = CondBuilder(); + field["gte"] = CondBuilder(); + field["like"] = CondBuilder(); + + filter[sol::meta_function::to_string] = [](const Filter& filter) { return filter.toString(); }; + filter[sol::meta_function::multiplication] = [](const Filter& a, const Filter& b) + { + Filter res = a; + res.add(b, Operation::AND); + return res; + }; + filter[sol::meta_function::addition] = [](const Filter& a, const Filter& b) + { + Filter res = a; + res.add(b, Operation::OR); + return res; + }; + filter[sol::meta_function::unary_minus] = [](const Filter& a) + { + Filter res = a; + if (!a.mConditions.empty()) + res.mOperations.push_back({Operation::NOT, 0}); + return res; + }; + + query[sol::meta_function::to_string] = [](const Query& q) { return q.toString(); }; + query["where"] = [](const Query& q, const Filter& filter) + { + Query res = q; + res.mFilter.add(filter, Operation::AND); + return res; + }; + query["orderBy"] = [](const Query& q, const Field& field) + { + Query res = q; + res.mOrderBy.push_back({&field, false}); + return res; + }; + query["orderByDesc"] = [](const Query& q, const Field& field) + { + Query res = q; + res.mOrderBy.push_back({&field, true}); + return res; + }; + query["groupBy"] = [](const Query& q, const Field& field) + { + Query res = q; + res.mGroupBy.push_back(&field); + return res; + }; + query["offset"] = [](const Query& q, int64_t offset) + { + Query res = q; + res.mOffset = offset; + return res; + }; + query["limit"] = [](const Query& q, int64_t limit) + { + Query res = q; + res.mLimit = limit; + return res; + }; + } +} + diff --git a/components/queries/luabindings.hpp b/components/queries/luabindings.hpp new file mode 100644 index 0000000000..a23dfa932b --- /dev/null +++ b/components/queries/luabindings.hpp @@ -0,0 +1,8 @@ +#include + +#include "query.hpp" + +namespace Queries +{ + void registerQueryBindings(sol::state& lua); +} diff --git a/components/queries/query.cpp b/components/queries/query.cpp new file mode 100644 index 0000000000..3c7f1517ee --- /dev/null +++ b/components/queries/query.cpp @@ -0,0 +1,185 @@ +#include "query.hpp" + +#include +#include + +namespace Queries +{ + Field::Field(std::vector path, std::type_index type) + : mPath(std::move(path)) + , mType(type) {} + + std::string Field::toString() const + { + std::string result; + for (const std::string& segment : mPath) + { + if (!result.empty()) + result += "."; + result += segment; + } + return result; + } + + std::string toString(const FieldValue& value) + { + return std::visit([](auto&& arg) -> std::string + { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + std::ostringstream oss; + oss << std::quoted(arg); + return oss.str(); + } + else if constexpr (std::is_same_v) + return arg ? "true" : "false"; + else + return std::to_string(arg); + }, value); + } + + std::string Condition::toString() const + { + std::string res; + res += mField->toString(); + switch (mType) + { + case Condition::EQUAL: res += " == "; break; + case Condition::NOT_EQUAL: res += " != "; break; + case Condition::LESSER: res += " < "; break; + case Condition::LESSER_OR_EQUAL: res += " <= "; break; + case Condition::GREATER: res += " > "; break; + case Condition::GREATER_OR_EQUAL: res += " >= "; break; + case Condition::LIKE: res += " LIKE "; break; + } + res += Queries::toString(mValue); + return res; + } + + void Filter::add(const Condition& c, Operation::Type op) + { + mOperations.push_back({Operation::PUSH, mConditions.size()}); + mConditions.push_back(c); + if (mConditions.size() > 1) + mOperations.push_back({op, 0}); + } + + void Filter::add(const Filter& f, Operation::Type op) + { + size_t conditionOffset = mConditions.size(); + size_t operationsBefore = mOperations.size(); + mConditions.insert(mConditions.end(), f.mConditions.begin(), f.mConditions.end()); + mOperations.insert(mOperations.end(), f.mOperations.begin(), f.mOperations.end()); + for (size_t i = operationsBefore; i < mOperations.size(); ++i) + mOperations[i].mConditionIndex += conditionOffset; + if (conditionOffset > 0 && !f.mConditions.empty()) + mOperations.push_back({op, 0}); + } + + std::string Filter::toString() const + { + if(mOperations.empty()) + return ""; + std::vector stack; + auto pop = [&stack](){ auto v = stack.back(); stack.pop_back(); return v; }; + auto push = [&stack](const std::string& s) { stack.push_back(s); }; + for (const Operation& op : mOperations) + { + if(op.mType == Operation::PUSH) + push(mConditions[op.mConditionIndex].toString()); + else if(op.mType == Operation::AND) + { + auto rhs = pop(); + auto lhs = pop(); + std::string res; + res += "("; + res += lhs; + res += ") AND ("; + res += rhs; + res += ")"; + push(res); + } + else if (op.mType == Operation::OR) + { + auto rhs = pop(); + auto lhs = pop(); + std::string res; + res += "("; + res += lhs; + res += ") OR ("; + res += rhs; + res += ")"; + push(res); + } + else if (op.mType == Operation::NOT) + { + std::string res; + res += "NOT ("; + res += pop(); + res += ")"; + push(res); + } + else + throw std::logic_error("Unknown operation type!"); + } + return pop(); + } + + std::string Query::toString() const + { + std::string res; + res += "SELECT "; + res += mQueryType; + + std::string filter = mFilter.toString(); + if(!filter.empty()) + { + res += " WHERE "; + res += filter; + } + + std::string order; + for(const OrderBy& ord : mOrderBy) + { + if(!order.empty()) + order += ", "; + order += ord.mField->toString(); + if(ord.mDescending) + order += " DESC"; + } + if (!order.empty()) + { + res += " ORDER BY "; + res += order; + } + + std::string group; + for (const Field* f: mGroupBy) + { + if (!group.empty()) + group += " ,"; + group += f->toString(); + } + if (!group.empty()) + { + res += " GROUP BY "; + res += group; + } + + if (mLimit != sNoLimit) + { + res += " LIMIT "; + res += std::to_string(mLimit); + } + + if (mOffset != 0) + { + res += " OFFSET "; + res += std::to_string(mOffset); + } + + return res; + } +} + diff --git a/components/queries/query.hpp b/components/queries/query.hpp new file mode 100644 index 0000000000..45144fed62 --- /dev/null +++ b/components/queries/query.hpp @@ -0,0 +1,99 @@ +#ifndef COMPONENTS_QUERIES_QUERY +#define COMPONENTS_QUERIES_QUERY + +#include +#include +#include +#include +#include + +namespace Queries +{ + class Field + { + public: + Field(std::vector path, std::type_index type); + + const std::vector& path() const { return mPath; } + const std::type_index type() const { return mType; } + + std::string toString() const; + + private: + std::vector mPath; + std::type_index mType; + }; + + struct OrderBy + { + const Field* mField; + bool mDescending; + }; + + using FieldValue = std::variant; + std::string toString(const FieldValue& value); + + struct Condition + { + enum Type + { + EQUAL = 0, + NOT_EQUAL = 1, + GREATER = 2, + GREATER_OR_EQUAL = 3, + LESSER = 4, + LESSER_OR_EQUAL = 5, + LIKE = 6, + }; + + std::string toString() const; + + const Field* mField; + Type mType; + FieldValue mValue; + }; + + struct Operation + { + enum Type + { + PUSH = 0, // push condition on stack + NOT = 1, // invert top condition on stack + AND = 2, // logic AND for two top conditions + OR = 3, // logic OR for two top conditions + }; + + Type mType; + size_t mConditionIndex; // used only if mType == PUSH + }; + + struct Filter + { + std::string toString() const; + + // combines with given condition or filter using operation `AND` or `OR`. + void add(const Condition& c, Operation::Type op = Operation::AND); + void add(const Filter& f, Operation::Type op = Operation::AND); + + std::vector mConditions; + std::vector mOperations; // operations on conditions in reverse polish notation + }; + + struct Query + { + static constexpr int64_t sNoLimit = -1; + + Query(std::string type) : mQueryType(std::move(type)) {} + std::string toString() const; + + std::string mQueryType; + Filter mFilter; + std::vector mOrderBy; + std::vector mGroupBy; + int64_t mOffset = 0; + int64_t mLimit = sNoLimit; + }; +} + +#endif // !COMPONENTS_QUERIES_QUERY + From 6db2450c90c7de74004c1d89b771080a1eaae99f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 22 Jan 2021 15:48:37 +0100 Subject: [PATCH 0979/2859] Initial support of generated RefNums with negative mContentFile. --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 2 +- apps/openmw/mwworld/cellref.cpp | 22 ++++++++++++++++++++++ apps/openmw/mwworld/cellref.hpp | 4 ++++ apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- components/esm/cellref.cpp | 3 ++- components/esm/cellref.hpp | 7 ++++--- components/esm/globalscript.cpp | 2 +- 10 files changed, 38 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 9416a48ec8..dd64c851f1 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -225,7 +225,7 @@ namespace MWRender esm.resize(index+1); cell->restore(esm[index], i); ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + ref.mRefNum.unset(); bool deleted = false; while(cell->getNextRef(esm[index], ref, deleted)) { diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index a5792d56b9..6b5f9a6e34 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -422,7 +422,7 @@ namespace MWRender esm.resize(index+1); cell->restore(esm[index], i); ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + ref.mRefNum.unset(); ESM::MovedCellRef cMRef; cMRef.mRefNum.mIndex = 0; bool deleted = false; diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 188a80ae14..88c38736e8 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -1,5 +1,8 @@ #include "cellref.hpp" +#include + +#include #include namespace MWWorld @@ -10,6 +13,25 @@ namespace MWWorld return mCellRef.mRefNum; } + const ESM::RefNum& CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum) + { + if (!mCellRef.mRefNum.isSet()) + { + // Generated RefNums have negative mContentFile + assert(lastAssignedRefNum.mContentFile < 0); + lastAssignedRefNum.mIndex++; + if (lastAssignedRefNum.mIndex == 0) // mIndex overflow, so mContentFile should be changed + { + lastAssignedRefNum.mContentFile--; + if (lastAssignedRefNum.mContentFile > 0) + Log(Debug::Error) << "RefNum counter overflow in CellRef::getOrAssignRefNum"; + } + mCellRef.mRefNum = lastAssignedRefNum; + mChanged = true; + } + return mCellRef.mRefNum; + } + bool CellRef::hasContentFile() const { return mCellRef.mRefNum.hasContentFile(); diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index f9f6dbdda2..6a6ac69c57 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -25,6 +25,10 @@ namespace MWWorld // Note: Currently unused for items in containers const ESM::RefNum& getRefNum() const; + // Returns RefNum. + // If RefNum is not set, assigns a generated one and changes the "lastAssignedRefNum" counter. + const ESM::RefNum& getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum); + // Set RefNum to its default state. void unsetRefNum(); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3aaf6061e3..0d2ee85deb 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -615,7 +615,7 @@ namespace MWWorld mCell->restore (esm[index], i); ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + ref.mRefNum.unset(); // Get each reference in turn ESM::MovedCellRef cMRef; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 864cb0d9d1..4b9bdf7426 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -34,7 +34,7 @@ namespace readers.resize(index + 1); cell.restore(readers[index], i); ESM::CellRef ref; - ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + ref.mRefNum.unset(); bool deleted = false; while(cell.getNextRef(readers[index], ref, deleted)) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 14c7716f2e..69167e5ccb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2020,7 +2020,7 @@ namespace MWWorld rayToObject = mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer); facedObject = rayToObject.mHitObject; - if (facedObject.isEmpty() && rayToObject.mHitRefnum.hasContentFile()) + if (facedObject.isEmpty() && rayToObject.mHitRefnum.isSet()) { for (CellStore* cellstore : mWorldScene->getActiveCells()) { diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 7ec7e00f1a..81e0b9f557 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -24,8 +24,9 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const esm.writeHNT (tag, *this, 8); else { + if (isSet() && !hasContentFile()) + Log(Debug::Error) << "Generated RefNum can not be saved in 32bit format"; int refNum = (mIndex & 0xffffff) | ((hasContentFile() ? mContentFile : 0xff)<<24); - esm.writeHNT (tag, refNum, 4); } } diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index c2f7ff6de5..f6eff24cbf 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -23,9 +23,10 @@ namespace ESM void save (ESMWriter &esm, bool wide = false, const std::string& tag = "FRMR") const; - enum { RefNum_NoContentFile = -1 }; - inline bool hasContentFile() const { return mContentFile != RefNum_NoContentFile; } - inline void unset() { mIndex = 0; mContentFile = RefNum_NoContentFile; } + inline bool hasContentFile() const { return mContentFile >= 0; } + + inline bool isSet() const { return mIndex != 0 || mContentFile != -1; } + inline void unset() { *this = {0, -1}; } // Note: this method should not be used for objects with invalid RefNum // (for example, for objects from disabled plugins in savegames). diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp index 016ea4f0cf..a8a8e79cf5 100644 --- a/components/esm/globalscript.cpp +++ b/components/esm/globalscript.cpp @@ -30,7 +30,7 @@ void ESM::GlobalScript::save (ESMWriter &esm) const if (!mTargetId.empty()) { esm.writeHNOString ("TARG", mTargetId); - if (mTargetRef.hasContentFile()) + if (mTargetRef.isSet()) mTargetRef.save (esm, true, "FRMR"); } } From 7df500c385e352196b896e8e6fa7ee60c66fe87b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 5 Apr 2021 23:25:51 +0200 Subject: [PATCH 0980/2859] Put RefData move constructor to cpp file --- apps/openmw/mwworld/refdata.cpp | 3 +++ apps/openmw/mwworld/refdata.hpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 29c669caf9..9aa40ed4b1 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -130,6 +130,9 @@ namespace MWWorld {} } + RefData::RefData(RefData&& other) noexcept = default; + RefData& RefData::operator=(RefData&& other) noexcept = default; + void RefData::setBaseNode(SceneUtil::PositionAttitudeTransform *base) { mBaseNode = base; diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index e2f77d09e0..640670d8e9 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -72,7 +72,7 @@ namespace MWWorld /// perform these operations). RefData (const RefData& refData); - RefData (RefData&& other) noexcept = default; + RefData (RefData&& other) noexcept; ~RefData(); @@ -81,7 +81,7 @@ namespace MWWorld /// perform this operations). RefData& operator= (const RefData& refData); - RefData& operator= (RefData&& other) noexcept = default; + RefData& operator= (RefData&& other) noexcept; /// Return base node (can be a null pointer). SceneUtil::PositionAttitudeTransform* getBaseNode(); From 3d7e3060646e678d29741632e9ad449375c8b14f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 18 Dec 2020 23:21:10 +0100 Subject: [PATCH 0981/2859] Add apps/openmw/mwlua --- apps/openmw/CMakeLists.txt | 5 + apps/openmw/engine.cpp | 45 +++++ apps/openmw/engine.hpp | 7 + apps/openmw/mwbase/environment.cpp | 17 +- apps/openmw/mwbase/environment.hpp | 6 + apps/openmw/mwbase/luamanager.hpp | 42 +++++ apps/openmw/mwlua/eventqueue.hpp | 23 +++ apps/openmw/mwlua/globalscripts.hpp | 36 ++++ apps/openmw/mwlua/localscripts.cpp | 44 +++++ apps/openmw/mwlua/localscripts.hpp | 39 +++++ apps/openmw/mwlua/luabindings.cpp | 39 +++++ apps/openmw/mwlua/luabindings.hpp | 36 ++++ apps/openmw/mwlua/luamanagerimp.cpp | 202 +++++++++++++++++++++++ apps/openmw/mwlua/luamanagerimp.hpp | 78 +++++++++ apps/openmw/mwlua/object.cpp | 111 +++++++++++++ apps/openmw/mwlua/object.hpp | 103 ++++++++++++ apps/openmw/mwlua/objectbindings.cpp | 106 ++++++++++++ apps/openmw/mwlua/playerscripts.hpp | 25 +++ apps/openmw/mwlua/userdataserializer.cpp | 64 +++++++ apps/openmw/mwlua/userdataserializer.hpp | 19 +++ apps/openmw/mwlua/worldview.cpp | 68 ++++++++ apps/openmw/mwlua/worldview.hpp | 47 ++++++ apps/openmw/mwworld/refdata.cpp | 10 ++ apps/openmw/mwworld/refdata.hpp | 9 + 24 files changed, 1180 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwbase/luamanager.hpp create mode 100644 apps/openmw/mwlua/eventqueue.hpp create mode 100644 apps/openmw/mwlua/globalscripts.hpp create mode 100644 apps/openmw/mwlua/localscripts.cpp create mode 100644 apps/openmw/mwlua/localscripts.hpp create mode 100644 apps/openmw/mwlua/luabindings.cpp create mode 100644 apps/openmw/mwlua/luabindings.hpp create mode 100644 apps/openmw/mwlua/luamanagerimp.cpp create mode 100644 apps/openmw/mwlua/luamanagerimp.hpp create mode 100644 apps/openmw/mwlua/object.cpp create mode 100644 apps/openmw/mwlua/object.hpp create mode 100644 apps/openmw/mwlua/objectbindings.cpp create mode 100644 apps/openmw/mwlua/playerscripts.hpp create mode 100644 apps/openmw/mwlua/userdataserializer.cpp create mode 100644 apps/openmw/mwlua/userdataserializer.hpp create mode 100644 apps/openmw/mwlua/worldview.cpp create mode 100644 apps/openmw/mwlua/worldview.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c5c85dae86..1cf87388a6 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -55,6 +55,11 @@ add_openmw_dir (mwscript animationextensions transformationextensions consoleextensions userextensions ) +add_openmw_dir (mwlua + luamanagerimp localscripts object worldview luabindings userdataserializer + objectbindings + ) + add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 93d3530c0a..3b7d17e69b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,5 +1,6 @@ #include "engine.hpp" +#include #include #include #include @@ -46,6 +47,8 @@ #include "mwgui/windowmanagerimp.hpp" +#include "mwlua/luamanagerimp.hpp" + #include "mwscript/scriptmanagerimp.hpp" #include "mwscript/interpretercontext.hpp" @@ -101,6 +104,7 @@ namespace PhysicsWorker, World, Gui, + Lua, Number, }; @@ -138,6 +142,9 @@ namespace template <> const UserStats UserStatsValue::sValue {"Gui", "gui"}; + template <> + const UserStats UserStatsValue::sValue {"Lua", "lua"}; + template struct ForEachUserStatsValue { @@ -700,6 +707,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mViewer->addEventHandler(mScreenCaptureHandler); + mLuaManager = new MWLua::LuaManager(mVFS.get()); + mEnvironment.setLuaManager(mLuaManager); + // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so @@ -811,6 +821,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) << 100*static_cast (result.second)/result.first << "%)"; } + + mLuaManager->init(); } // Initialise and enter main loop. @@ -895,6 +907,28 @@ void OMW::Engine::go() mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } + // Start Lua scripting thread + std::atomic_bool luaUpdateRequest; + double luaDt = 0; + std::thread scriptingThread([&]() { + const osg::Timer* const timer = osg::Timer::instance(); + osg::Stats* const stats = mViewer->getViewerStats(); + while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) + { + while (!luaUpdateRequest) + std::this_thread::yield(); + + { + const osg::Timer_t frameStart = mViewer->getStartTick(); + const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); + ScopedProfile profile(frameStart, frameNumber, *timer, *stats); + + mLuaManager->update(mEnvironment.getWindowManager()->isGuiMode(), luaDt); + } + luaUpdateRequest = false; + } + }); + // Start the main rendering loop double simulationTime = 0.0; Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); @@ -920,8 +954,17 @@ void OMW::Engine::go() mEnvironment.getWorld()->updateWindowManager(); + // scriptingThread starts processing Lua scripts + luaDt = dt; + luaUpdateRequest = true; + mViewer->renderingTraversals(); + // wait for scriptingThread to finish + while (luaUpdateRequest) + std::this_thread::yield(); + mLuaManager->applyQueuedChanges(); + bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); if (!guiActive) simulationTime += dt; @@ -943,6 +986,8 @@ void OMW::Engine::go() frameRateLimiter.limit(); } + scriptingThread.join(); + // Save user settings settings.saveUser(settingspath); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 49ae92abc1..efa8c688dd 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -33,6 +33,11 @@ namespace Compiler class Context; } +namespace MWLua +{ + class LuaManager; +} + namespace Files { struct ConfigurationManager; @@ -85,6 +90,8 @@ namespace OMW Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; + MWLua::LuaManager* mLuaManager; + Files::Collections mFileCollections; bool mFSStrict; Translation::Storage mTranslationDataStorage; diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index b7235edd4b..71940f67d2 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -13,13 +13,14 @@ #include "inputmanager.hpp" #include "windowmanager.hpp" #include "statemanager.hpp" +#include "luamanager.hpp" MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() : mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), - mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) + mStateManager (nullptr), mLuaManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { assert (!sThis); sThis = this; @@ -76,6 +77,11 @@ void MWBase::Environment::setStateManager (StateManager *stateManager) mStateManager = stateManager; } +void MWBase::Environment::setLuaManager (LuaManager *luaManager) +{ + mLuaManager = luaManager; +} + void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem) { mResourceSystem = resourceSystem; @@ -150,6 +156,12 @@ MWBase::StateManager *MWBase::Environment::getStateManager() const return mStateManager; } +MWBase::LuaManager *MWBase::Environment::getLuaManager() const +{ + assert (mLuaManager); + return mLuaManager; +} + Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const { return mResourceSystem; @@ -188,6 +200,9 @@ void MWBase::Environment::cleanup() delete mStateManager; mStateManager = nullptr; + + delete mLuaManager; + mLuaManager = nullptr; } const MWBase::Environment& MWBase::Environment::get() diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 3b57e4e7c1..b2600e7dd5 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -22,6 +22,7 @@ namespace MWBase class InputManager; class WindowManager; class StateManager; + class LuaManager; /// \brief Central hub for mw-subsystems /// @@ -42,6 +43,7 @@ namespace MWBase Journal *mJournal; InputManager *mInputManager; StateManager *mStateManager; + LuaManager *mLuaManager; Resource::ResourceSystem *mResourceSystem; float mFrameDuration; float mFrameRateLimit; @@ -76,6 +78,8 @@ namespace MWBase void setStateManager (StateManager *stateManager); + void setLuaManager (LuaManager *luaManager); + void setResourceSystem (Resource::ResourceSystem *resourceSystem); void setFrameDuration (float duration); @@ -102,6 +106,8 @@ namespace MWBase StateManager *getStateManager() const; + LuaManager *getLuaManager() const; + Resource::ResourceSystem *getResourceSystem() const; float getFrameDuration() const; diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp new file mode 100644 index 0000000000..ad374c9dba --- /dev/null +++ b/apps/openmw/mwbase/luamanager.hpp @@ -0,0 +1,42 @@ +#ifndef GAME_MWBASE_LUAMANAGER_H +#define GAME_MWBASE_LUAMANAGER_H + +#include + +namespace MWWorld +{ + class Ptr; +} + +namespace MWBase +{ + + class LuaManager + { + public: + virtual ~LuaManager() = default; + + virtual void newGameStarted() = 0; + virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; + virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; + virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; + + struct ActorControls { + bool controlledFromLua; + + bool jump; + bool run; + float movement; + float sideMovement; + float turn; + }; + + virtual const ActorControls* getActorControls(const MWWorld::Ptr&) const = 0; + + virtual void clear() = 0; + virtual void setupPlayer(const MWWorld::Ptr&) = 0; + }; + +} + +#endif // GAME_MWBASE_LUAMANAGER_H diff --git a/apps/openmw/mwlua/eventqueue.hpp b/apps/openmw/mwlua/eventqueue.hpp new file mode 100644 index 0000000000..cd79e05dc0 --- /dev/null +++ b/apps/openmw/mwlua/eventqueue.hpp @@ -0,0 +1,23 @@ +#ifndef MWLUA_EVENTQUEUE_H +#define MWLUA_EVENTQUEUE_H + +#include "object.hpp" + +namespace MWLua +{ + struct GlobalEvent + { + std::string eventName; + std::string eventData; + }; + struct LocalEvent + { + ObjectId dest; + std::string eventName; + std::string eventData; + }; + using GlobalEventQueue = std::vector; + using LocalEventQueue = std::vector; +} + +#endif // MWLUA_EVENTQUEUE_H diff --git a/apps/openmw/mwlua/globalscripts.hpp b/apps/openmw/mwlua/globalscripts.hpp new file mode 100644 index 0000000000..9a371809ac --- /dev/null +++ b/apps/openmw/mwlua/globalscripts.hpp @@ -0,0 +1,36 @@ +#ifndef MWLUA_GLOBALSCRIPTS_H +#define MWLUA_GLOBALSCRIPTS_H + +#include +#include +#include + +#include +#include + +#include "object.hpp" + +namespace MWLua +{ + + class GlobalScripts : public LuaUtil::ScriptsContainer + { + public: + GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global") + { + registerEngineHandlers({&mActorActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers}); + } + + void newGameStarted() { callEngineHandlers(mNewGameHandlers); } + void actorActive(const GObject& obj) { callEngineHandlers(mActorActiveHandlers, obj); } + void playerAdded(const GObject& obj) { callEngineHandlers(mPlayerAddedHandlers, obj); } + + private: + EngineHandlerList mActorActiveHandlers{"onActorActive"}; + EngineHandlerList mNewGameHandlers{"onNewGame"}; + EngineHandlerList mPlayerAddedHandlers{"onPlayerAdded"}; + }; + +} + +#endif // MWLUA_GLOBALSCRIPTS_H diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp new file mode 100644 index 0000000000..1c35b60131 --- /dev/null +++ b/apps/openmw/mwlua/localscripts.cpp @@ -0,0 +1,44 @@ +#include "localscripts.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + + void LocalScripts::initializeSelfPackage(const Context& context) + { + using ActorControls = MWBase::LuaManager::ActorControls; + sol::usertype controls = context.mLua->sol().new_usertype("ActorControls"); + controls["movement"] = &ActorControls::movement; + controls["sideMovement"] = &ActorControls::sideMovement; + controls["turn"] = &ActorControls::turn; + controls["run"] = &ActorControls::run; + controls["jump"] = &ActorControls::jump; + + sol::usertype selfAPI = + context.mLua->sol().new_usertype("SelfObject", sol::base_classes, sol::bases()); + selfAPI[sol::meta_function::to_string] = [](SelfObject& self) { return "openmw.self[" + self.toString() + "]"; }; + selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); + selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); + selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.controlledFromLua = v; }; + } + + std::unique_ptr LocalScripts::create(LuaUtil::LuaState* lua, const LObject& obj) + { + return std::unique_ptr(new LocalScripts(lua, obj)); + } + + LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj) + : LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj) + { + mData.mControls.controlledFromLua = false; + this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); + } + +} diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp new file mode 100644 index 0000000000..c8d85aad58 --- /dev/null +++ b/apps/openmw/mwlua/localscripts.hpp @@ -0,0 +1,39 @@ +#ifndef MWLUA_LOCALSCRIPTS_H +#define MWLUA_LOCALSCRIPTS_H + +#include +#include +#include + +#include +#include + +#include "../mwbase/luamanager.hpp" + +#include "object.hpp" +#include "luabindings.hpp" + +namespace MWLua +{ + + class LocalScripts : public LuaUtil::ScriptsContainer + { + public: + static std::unique_ptr create(LuaUtil::LuaState* lua, const LObject& obj); + static void initializeSelfPackage(const Context&); + + const MWBase::LuaManager::ActorControls* getActorControls() const { return &mData.mControls; } + + struct SelfObject : public LObject + { + SelfObject(const LObject& obj) : LObject(obj) {} + MWBase::LuaManager::ActorControls mControls; + }; + protected: + LocalScripts(LuaUtil::LuaState* lua, const LObject& obj); + SelfObject mData; + }; + +} + +#endif // MWLUA_LOCALSCRIPTS_H diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp new file mode 100644 index 0000000000..4f6519397e --- /dev/null +++ b/apps/openmw/mwlua/luabindings.cpp @@ -0,0 +1,39 @@ +#include "luabindings.hpp" + +#include + +#include "eventqueue.hpp" +#include "worldview.hpp" + +namespace MWLua +{ + + sol::table initCorePackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) + { + context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); + }; + return context.mLua->makeReadOnly(api); + } + + sol::table initWorldPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + WorldView* worldView = context.mWorldView; + api["activeActors"] = GObjectList{worldView->getActorsInScene()}; + return context.mLua->makeReadOnly(api); + } + + sol::table initNearbyPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + WorldView* worldView = context.mWorldView; + api["actors"] = LObjectList{worldView->getActorsInScene()}; + api["items"] = LObjectList{worldView->getItemsInScene()}; + return context.mLua->makeReadOnly(api); + } + +} + diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp new file mode 100644 index 0000000000..7e544b7eb4 --- /dev/null +++ b/apps/openmw/mwlua/luabindings.hpp @@ -0,0 +1,36 @@ +#ifndef MWLUA_LUABINDINGS_H +#define MWLUA_LUABINDINGS_H + +#include +#include + +#include "eventqueue.hpp" +#include "object.hpp" +#include "worldview.hpp" + +namespace MWLua +{ + class LuaManager; + + struct Context + { + LuaManager* mLuaManager; + LuaUtil::LuaState* mLua; + LuaUtil::UserdataSerializer* mSerializer; + WorldView* mWorldView; + LocalEventQueue* mLocalEventQueue; + GlobalEventQueue* mGlobalEventQueue; + }; + + sol::table initCorePackage(const Context&); + sol::table initWorldPackage(const Context&); + sol::table initNearbyPackage(const Context&); + + // Implemented in objectbindings.cpp + void initObjectBindingsForLocalScripts(const Context&); + void initObjectBindingsForGlobalScripts(const Context&); + + // openmw.self package is implemented in localscripts.cpp +} + +#endif // MWLUA_LUABINDINGS_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp new file mode 100644 index 0000000000..023e95e7b4 --- /dev/null +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -0,0 +1,202 @@ +#include "luamanagerimp.hpp" + +#include +#include + +#include "../mwworld/class.hpp" +#include "../mwworld/ptr.hpp" + +#include "luabindings.hpp" +#include "userdataserializer.hpp" + +namespace MWLua +{ + + LuaManager::LuaManager(const VFS::Manager* vfs) : mLua(vfs) + { + Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); + mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry()); + mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry()); + mGlobalScripts.setSerializer(mGlobalSerializer.get()); + + Context context; + context.mLuaManager = this; + context.mLua = &mLua; + context.mWorldView = &mWorldView; + context.mLocalEventQueue = &mLocalEvents; + context.mGlobalEventQueue = &mGlobalEvents; + context.mSerializer = mGlobalSerializer.get(); + + Context localContext = context; + localContext.mSerializer = mLocalSerializer.get(); + + initObjectBindingsForGlobalScripts(context); + initObjectBindingsForLocalScripts(localContext); + LocalScripts::initializeSelfPackage(localContext); + + mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); + mLua.addCommonPackage("openmw.core", initCorePackage(context)); + mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); + mNearbyPackage = initNearbyPackage(localContext); + } + + void LuaManager::init() + { + mKeyPressEvents.clear(); + if (mGlobalScripts.addNewScript("test.lua")) + Log(Debug::Info) << "Global script started: test.lua"; + } + + void LuaManager::update(bool paused, float dt) + { + mWorldView.update(); + + if (paused) + { + mKeyPressEvents.clear(); + return; + } + + std::vector globalEvents = std::move(mGlobalEvents); + std::vector localEvents = std::move(mLocalEvents); + mGlobalEvents = std::vector(); + mLocalEvents = std::vector(); + + for (GlobalEvent& e : globalEvents) + mGlobalScripts.receiveEvent(e.eventName, e.eventData); + for (LocalEvent& e : localEvents) + { + LObject obj(e.dest, mWorldView.getObjectRegistry()); + LocalScripts* scripts = obj.isValid() ? obj.ptr().getRefData().getLuaScripts() : nullptr; + if (scripts) + scripts->receiveEvent(e.eventName, e.eventData); + else + Log(Debug::Debug) << "Ignored event " << e.eventName << " to L" << idToString(e.dest) + << ". Object not found or has no attached scripts"; + } + + if (mPlayerChanged) + { + mPlayerChanged = false; + mGlobalScripts.playerAdded(GObject(getId(mPlayer), mWorldView.getObjectRegistry())); + } + + if (mPlayerScripts) + { + for (const SDL_Keysym key : mKeyPressEvents) + mPlayerScripts->keyPress(key.sym, key.mod); + } + mKeyPressEvents.clear(); + + for (ObjectId id : mActorAddedEvents) + mGlobalScripts.actorActive(GObject(id, mWorldView.getObjectRegistry())); + mActorAddedEvents.clear(); + + mGlobalScripts.update(dt); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->update(dt); + } + + void LuaManager::applyQueuedChanges() + { + } + + void LuaManager::clear() + { + mActiveLocalScripts.clear(); + mLocalEvents.clear(); + mGlobalEvents.clear(); + mKeyPressEvents.clear(); + mActorAddedEvents.clear(); + mPlayerChanged = false; + mPlayerScripts = nullptr; + mWorldView.clear(); + if (!mPlayer.isEmpty()) + { + mPlayer.getCellRef().unsetRefNum(); + mPlayer.getRefData().setLuaScripts(nullptr); + mPlayer = MWWorld::Ptr(); + } + } + + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) + { + mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. + + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (localScripts) + mActiveLocalScripts.insert(localScripts); + + if (ptr.getClass().isActor() && ptr != mPlayer) + mActorAddedEvents.push_back(getId(ptr)); + } + + void LuaManager::setupPlayer(const MWWorld::Ptr& ptr) + { + if (!mPlayer.isEmpty()) + throw std::logic_error("Player is initialized twice"); + mWorldView.objectAddedToScene(ptr); + mPlayer = ptr; + MWWorld::RefData& refData = ptr.getRefData(); + if (!refData.getLuaScripts()) + createLocalScripts(ptr); + if (!mPlayerScripts) + throw std::logic_error("mPlayerScripts not initialized"); + mActiveLocalScripts.insert(mPlayerScripts); + mPlayerChanged = true; + } + + void LuaManager::objectRemovedFromScene(const MWWorld::Ptr& ptr) + { + mWorldView.objectRemovedFromScene(ptr); + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (localScripts) + mActiveLocalScripts.erase(localScripts); + + // TODO: call mWorldView.objectUnloaded if object is unloaded from memory (does it ever happen?) and ptr becomes invalid. + } + + void LuaManager::keyPressed(const SDL_KeyboardEvent& arg) + { + mKeyPressEvents.push_back(arg.keysym); + } + + const MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const + { + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts) + return nullptr; + return localScripts->getActorControls(); + } + + void LuaManager::addLocalScript(const MWWorld::Ptr& ptr, const std::string& scriptPath) + { + MWWorld::RefData& refData = ptr.getRefData(); + if (!refData.getLuaScripts()) + mActiveLocalScripts.insert(createLocalScripts(ptr)); + refData.getLuaScripts()->addNewScript(scriptPath); + } + + LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr) + { + std::unique_ptr scripts; + // When loading a game, it can be called before LuaManager::setPlayer, + // so we can't just check ptr == mPlayer here. + if (*ptr.getCellRef().getRefIdPtr() == "player") + { + mPlayerScripts = new PlayerScripts(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); + scripts = std::unique_ptr(mPlayerScripts); + // TODO: scripts->addPackage("openmw.ui", ...); + // TODO: scripts->addPackage("openmw.camera", ...); + } + else + scripts = LocalScripts::create(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); + scripts->addPackage("openmw.nearby", mNearbyPackage); + scripts->setSerializer(mLocalSerializer.get()); + + MWWorld::RefData& refData = ptr.getRefData(); + refData.setLuaScripts(std::move(scripts)); + return refData.getLuaScripts(); + } + +} diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp new file mode 100644 index 0000000000..3420ac5dbb --- /dev/null +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -0,0 +1,78 @@ +#ifndef MWLUA_LUAMANAGERIMP_H +#define MWLUA_LUAMANAGERIMP_H + +#include +#include + +#include + +#include "../mwbase/luamanager.hpp" + +#include "object.hpp" +#include "eventqueue.hpp" +#include "globalscripts.hpp" +#include "localscripts.hpp" +#include "playerscripts.hpp" +#include "worldview.hpp" + +namespace MWLua +{ + + class LuaManager : public MWBase::LuaManager + { + public: + LuaManager(const VFS::Manager* vfs); + ~LuaManager() {} + + // Called by engine.cpp when environment is fully initialized. + void init(); + + // Called by engine.cpp every frame. For performance reasons it works in a separate + // thread (in parallel with osg Cull). Can not use scene graph. + void update(bool paused, float dt); + + // Called by engine.cpp from the main thread. Can use scene graph. + void applyQueuedChanges(); + + // Available everywhere through the MWBase::LuaManager interface. + // LuaManager queues these events and propagates to scripts on the next `update` call. + void newGameStarted() override { mGlobalScripts.newGameStarted(); } + void objectAddedToScene(const MWWorld::Ptr& ptr) override; + void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; + void keyPressed(const SDL_KeyboardEvent &arg) override; + + const MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; + + void clear() override; // should be called before loading game or starting a new game to reset internal state. + void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear". + + // Used only in luabindings.cpp + void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath); + + private: + LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr); + + LuaUtil::LuaState mLua; + sol::table mNearbyPackage; + + GlobalScripts mGlobalScripts{&mLua}; + std::set mActiveLocalScripts; + WorldView mWorldView; + + bool mPlayerChanged = false; + MWWorld::Ptr mPlayer; + PlayerScripts* mPlayerScripts = nullptr; + + GlobalEventQueue mGlobalEvents; + LocalEventQueue mLocalEvents; + + std::unique_ptr mGlobalSerializer; + std::unique_ptr mLocalSerializer; + + std::vector mKeyPressEvents; + std::vector mActorAddedEvents; + }; + +} + +#endif // MWLUA_LUAMANAGERIMP_H diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp new file mode 100644 index 0000000000..fa0f9daffc --- /dev/null +++ b/apps/openmw/mwlua/object.cpp @@ -0,0 +1,111 @@ +#include "object.hpp" + +#include +#include + +namespace MWLua +{ + + std::string idToString(const ObjectId& id) + { + return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile); + } + + std::string Object::toString() const + { + std::string res = idToString(mId); + if (isValid()) + { + res.append(" ("); + res.append(type()); + res.append(", "); + res.append(*ptr().getCellRef().getRefIdPtr()); + res.append(")"); + } + else + res.append(" (not found)"); + return res; + } + + std::string_view Object::type() const + { + if (*ptr().getCellRef().getRefIdPtr() == "player") + return "Player"; + const std::string& typeName = ptr().getTypeName(); + if (typeName == typeid(ESM::NPC).name()) + return "NPC"; + else if (typeName == typeid(ESM::Creature).name()) + return "Creature"; + else + return typeName; + } + + bool Object::isValid() const + { + if (mLastUpdate < mObjectRegistry->mUpdateCounter) + { + updatePtr(); + mLastUpdate = mObjectRegistry->mUpdateCounter; + } + return !mPtr.isEmpty(); + } + + const MWWorld::Ptr& Object::ptr() const + { + if (!isValid()) + throw std::runtime_error("Object is not available: " + idToString(mId)); + return mPtr; + } + + void ObjectRegistry::update() + { + if (mChanged) + { + mUpdateCounter++; + mChanged = false; + } + } + + void ObjectRegistry::clear() + { + mObjectMapping.clear(); + mChanged = false; + mUpdateCounter = 0; + mLastAssignedId.unset(); + } + + MWWorld::Ptr ObjectRegistry::getPtr(ObjectId id, bool onlyActive) + { + MWWorld::Ptr ptr; + auto it = mObjectMapping.find(id); + if (it != mObjectMapping.end()) + ptr = it->second; + if (onlyActive) + { + // TODO: add flag `isActive` to LiveCellRefBase. Return empty Ptr if the flag is not set. + // Needed because in multiplayer mode inactive objects will not be synchronized, so will likely be out of date. + } + else + { + // TODO: If Ptr is empty then try to load the object from esp/esm. + } + return ptr; + } + + ObjectId ObjectRegistry::registerPtr(const MWWorld::Ptr& ptr) + { + ObjectId id = ptr.getCellRef().getOrAssignRefNum(mLastAssignedId); + mChanged = true; + mObjectMapping[id] = ptr; + return id; + } + + ObjectId ObjectRegistry::deregisterPtr(const MWWorld::Ptr& ptr) + { + ObjectId id = getId(ptr); + mChanged = true; + mObjectMapping.erase(id); + return id; + } + +} diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp new file mode 100644 index 0000000000..59e6534166 --- /dev/null +++ b/apps/openmw/mwlua/object.hpp @@ -0,0 +1,103 @@ +#ifndef MWLUA_OBJECT_H +#define MWLUA_OBJECT_H + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWLua +{ + // ObjectId is a unique identifier of a game object. + // It can change only if the order of content files was change. + using ObjectId = ESM::RefNum; + inline const ObjectId& getId(const MWWorld::Ptr& ptr) { return ptr.getCellRef().getRefNum(); } + std::string idToString(const ObjectId& id); + + // Holds a mapping ObjectId -> MWWord::Ptr. + class ObjectRegistry + { + public: + ObjectRegistry() { mLastAssignedId.unset(); } + + void update(); // Should be called every frame. + void clear(); // Should be called before starting or loading a new game. + + ObjectId registerPtr(const MWWorld::Ptr& ptr); + ObjectId deregisterPtr(const MWWorld::Ptr& ptr); + + // Returns Ptr by id. If object is not found, returns empty Ptr. + // If onlyActive = true, returns non-empty ptr only if it is registered and is in an active cell. + // If onlyActive = false, tries to load and register the object if it is not loaded yet. + // NOTE: `onlyActive` logic is not yet implemented. + MWWorld::Ptr getPtr(ObjectId id, bool onlyActive); + + // Needed only for saving/loading. + const ObjectId& getLastAssignedId() const { return mLastAssignedId; } + void setLastAssignedId(ObjectId id) { mLastAssignedId = id; } + + private: + friend class Object; + friend class GObject; + friend class LObject; + + bool mChanged = false; + int64_t mUpdateCounter = 0; + std::map mObjectMapping; + ObjectId mLastAssignedId; + }; + + // Lua scripts can't use MWWorld::Ptr directly, because lifetime of a script can be longer than lifetime of Ptr. + // `GObject` and `LObject` are intended to be passed to Lua as a userdata. + // It automatically updates the underlying Ptr when needed. + class Object + { + public: + Object(ObjectId id, ObjectRegistry* reg) : mId(id), mObjectRegistry(reg) {} + virtual ~Object() {} + ObjectId id() const { return mId; } + + std::string toString() const; + std::string_view type() const; + + // Updates and returns the underlying Ptr. Throws an exception if object is not available. + const MWWorld::Ptr& ptr() const; + + // Returns `true` if calling `ptr()` is safe. + bool isValid() const; + + protected: + virtual void updatePtr() const = 0; + + const ObjectId mId; + ObjectRegistry* mObjectRegistry; + + mutable MWWorld::Ptr mPtr; + mutable int64_t mLastUpdate = -1; + }; + + // Used only in local scripts + class LObject : public Object + { + using Object::Object; + void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, true); } + }; + + // Used only in global scripts + class GObject : public Object + { + using Object::Object; + void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, false); } + }; + + using ObjectIdList = std::shared_ptr>; + template + struct ObjectList { ObjectIdList mIds; }; + using GObjectList = ObjectList; + using LObjectList = ObjectList; + +} + +#endif // MWLUA_OBJECT_H diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp new file mode 100644 index 0000000000..806907ef35 --- /dev/null +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -0,0 +1,106 @@ +#include "luabindings.hpp" + +#include + +#include "eventqueue.hpp" +#include "luamanagerimp.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + + template + static void registerObjectList(const std::string& prefix, const Context& context) + { + using ListT = ObjectList; + sol::state& lua = context.mLua->sol(); + ObjectRegistry* registry = context.mWorldView->getObjectRegistry(); + sol::usertype listT = lua.new_usertype(prefix + "ObjectList"); + listT[sol::meta_function::to_string] = + [](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; }; + listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); }; + listT[sol::meta_function::index] = [registry](const ListT& list, size_t index) + { + if (index > 0 && index <= list.mIds->size()) + return ObjectT((*list.mIds)[index - 1], registry); + else + throw std::runtime_error("Index out of range"); + }; + listT["ipairs"] = [registry](const ListT& list) + { + auto iter = [registry](const ListT& l, int64_t i) -> sol::optional> + { + if (i >= 0 && i < static_cast(l.mIds->size())) + return std::make_tuple(i + 1, ObjectT((*l.mIds)[i], registry)); + else + return sol::nullopt; + }; + return std::make_tuple(iter, list, 0); + }; + } + + template + static void addBasicBindings(sol::usertype& objectT, const Context& context) + { + objectT["isValid"] = [](const ObjectT& o) { return o.isValid(); }; + objectT["recordId"] = sol::readonly_property([](const ObjectT& o) -> std::string + { + return o.ptr().getCellRef().getRefId(); + }); + objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f + { + return o.ptr().getRefData().getPosition().asVec3(); + }); + objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f + { + return o.ptr().getRefData().getPosition().asRotationVec3(); + }); + objectT["type"] = sol::readonly_property(&ObjectT::type); + objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; + objectT[sol::meta_function::to_string] = &ObjectT::toString; + objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) + { + context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); + }; + + if constexpr (std::is_same_v) + { // Only for global scripts + objectT["addScript"] = [luaManager=context.mLuaManager](const GObject& object, const std::string& path) + { + luaManager->addLocalScript(object.ptr(), path); + }; + } + } + + template + static void initObjectBindings(const std::string& prefix, const Context& context) + { + sol::usertype objectT = context.mLua->sol().new_usertype(prefix + "Object"); + addBasicBindings(objectT, context); + + registerObjectList(prefix, context); + } + + void initObjectBindingsForLocalScripts(const Context& context) + { + initObjectBindings("L", context); + } + + void initObjectBindingsForGlobalScripts(const Context& context) + { + initObjectBindings("G", context); + } + +} + diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp new file mode 100644 index 0000000000..9a08f917e7 --- /dev/null +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -0,0 +1,25 @@ +#ifndef MWLUA_PLAYERSCRIPTS_H +#define MWLUA_PLAYERSCRIPTS_H + +#include "localscripts.hpp" + +namespace MWLua +{ + + class PlayerScripts : public LocalScripts + { + public: + PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj) + { + registerEngineHandlers({&mKeyPressHandlers}); + } + + void keyPress(int sym, int mod) { callEngineHandlers(mKeyPressHandlers, sym, mod); } + + private: + EngineHandlerList mKeyPressHandlers{"onKeyPress"}; + }; + +} + +#endif // MWLUA_PLAYERSCRIPTS_H diff --git a/apps/openmw/mwlua/userdataserializer.cpp b/apps/openmw/mwlua/userdataserializer.cpp new file mode 100644 index 0000000000..a0315d4777 --- /dev/null +++ b/apps/openmw/mwlua/userdataserializer.cpp @@ -0,0 +1,64 @@ +#include "userdataserializer.hpp" + +#include +#include + +#include "object.hpp" + +namespace MWLua +{ + + class Serializer final : public LuaUtil::UserdataSerializer + { + public: + explicit Serializer(bool localSerializer, ObjectRegistry* registry) + : mLocalSerializer(localSerializer), mObjectRegistry(registry) {} + + private: + // Appends serialized sol::userdata to the end of BinaryData. + // Returns false if this type of userdata is not supported by this serializer. + bool serialize(LuaUtil::BinaryData& out, const sol::userdata& data) const override + { + if (data.is() || data.is()) + { + ObjectId id = data.as().id(); + static_assert(sizeof(ObjectId) == 8); + id.mIndex = Misc::toLittleEndian(id.mIndex); + id.mContentFile = Misc::toLittleEndian(id.mContentFile); + append(out, "o", &id, sizeof(ObjectId)); + return true; + } + return false; + } + + // Deserializes userdata of type "typeName" from binaryData. Should push the result on stack using sol::stack::push. + // Returns false if this type is not supported by this serializer. + bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state& lua) const override + { + if (typeName == "o") + { + if (binaryData.size() != sizeof(ObjectId)) + throw std::runtime_error("Incorrect serialization format. Size of ObjectId doesn't match."); + ObjectId id; + std::memcpy(&id, binaryData.data(), sizeof(ObjectId)); + id.mIndex = Misc::fromLittleEndian(id.mIndex); + id.mContentFile = Misc::fromLittleEndian(id.mContentFile); + if (mLocalSerializer) + sol::stack::push(lua, LObject(id, mObjectRegistry)); + else + sol::stack::push(lua, GObject(id, mObjectRegistry)); + return true; + } + return false; + } + + bool mLocalSerializer; + ObjectRegistry* mObjectRegistry; + }; + + std::unique_ptr createUserdataSerializer(bool local, ObjectRegistry* registry) + { + return std::make_unique(local, registry); + } + +} diff --git a/apps/openmw/mwlua/userdataserializer.hpp b/apps/openmw/mwlua/userdataserializer.hpp new file mode 100644 index 0000000000..72fab0f53b --- /dev/null +++ b/apps/openmw/mwlua/userdataserializer.hpp @@ -0,0 +1,19 @@ +#ifndef MWLUA_USERDATASERIALIZER_H +#define MWLUA_USERDATASERIALIZER_H + +#include "object.hpp" + +namespace LuaUtil +{ + class UserdataSerializer; +} + +namespace MWLua +{ + // UserdataSerializer is an extension for components/lua/serialization.hpp + // Needed to serialize references to objects. + // If local=true, then during deserialization creates LObject, otherwise creates GObject. + std::unique_ptr createUserdataSerializer(bool local, ObjectRegistry* registry); +} + +#endif // MWLUA_USERDATASERIALIZER_H diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp new file mode 100644 index 0000000000..e088dda252 --- /dev/null +++ b/apps/openmw/mwlua/worldview.cpp @@ -0,0 +1,68 @@ +#include "worldview.hpp" + +#include "../mwworld/class.hpp" + +namespace MWLua +{ + + void WorldView::update() + { + mObjectRegistry.update(); + mActorsInScene.updateList(); + mItemsInScene.updateList(); + } + + void WorldView::clear() + { + mObjectRegistry.clear(); + mActorsInScene.clear(); + mItemsInScene.clear(); + } + + void WorldView::objectAddedToScene(const MWWorld::Ptr& ptr) + { + if (ptr.getClass().isActor()) + addToGroup(mActorsInScene, ptr); + else + addToGroup(mItemsInScene, ptr); + } + + void WorldView::objectRemovedFromScene(const MWWorld::Ptr& ptr) + { + if (ptr.getClass().isActor()) + removeFromGroup(mActorsInScene, ptr); + else + removeFromGroup(mItemsInScene, ptr); + } + + void WorldView::ObjectGroup::updateList() + { + if (mChanged) + { + mList->clear(); + for (const ObjectId& id : mSet) + mList->push_back(id); + mChanged = false; + } + } + + void WorldView::ObjectGroup::clear() + { + mChanged = false; + mList->clear(); + mSet.clear(); + } + + void WorldView::addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr) + { + group.mSet.insert(getId(ptr)); + group.mChanged = true; + } + + void WorldView::removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr) + { + group.mSet.erase(getId(ptr)); + group.mChanged = true; + } + +} diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp new file mode 100644 index 0000000000..fbfefccdc6 --- /dev/null +++ b/apps/openmw/mwlua/worldview.hpp @@ -0,0 +1,47 @@ +#ifndef MWLUA_WORLDVIEW_H +#define MWLUA_WORLDVIEW_H + +#include "object.hpp" + +namespace MWLua +{ + + // Tracks all used game objects. + class WorldView + { + public: + void update(); // Should be called every frame. + void clear(); // Should be called every time before starting or loading a new game. + + ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } + ObjectIdList getItemsInScene() const { return mItemsInScene.mList; } + + ObjectRegistry* getObjectRegistry() { return &mObjectRegistry; } + + void objectUnloaded(const MWWorld::Ptr& ptr) { mObjectRegistry.deregisterPtr(ptr); } + + void objectAddedToScene(const MWWorld::Ptr& ptr); + void objectRemovedFromScene(const MWWorld::Ptr& ptr); + + private: + struct ObjectGroup + { + void updateList(); + void clear(); + + bool mChanged = false; + ObjectIdList mList = std::make_shared>(); + std::set mSet; + }; + + void addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); + void removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); + + ObjectRegistry mObjectRegistry; + ObjectGroup mActorsInScene; + ObjectGroup mItemsInScene; + }; + +} + +#endif // MWLUA_WORLDVIEW_H diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 9aa40ed4b1..b506671086 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -8,6 +8,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwlua/localscripts.hpp" + namespace { enum RefDataFlags @@ -21,6 +23,12 @@ enum RefDataFlags namespace MWWorld { + void RefData::setLuaScripts(std::unique_ptr&& scripts) + { + mChanged = true; + mLuaScripts = std::move(scripts); + } + void RefData::copy (const RefData& refData) { mBaseNode = refData.mBaseNode; @@ -36,12 +44,14 @@ namespace MWWorld mAnimationState = refData.mAnimationState; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : nullptr; + mLuaScripts = refData.mLuaScripts; } void RefData::cleanup() { mBaseNode = nullptr; mCustomData = nullptr; + mLuaScripts = nullptr; } RefData::RefData() diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 640670d8e9..3ae6a03293 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -22,6 +22,11 @@ namespace ESM struct ObjectState; } +namespace MWLua +{ + class LocalScripts; +} + namespace MWWorld { @@ -32,6 +37,7 @@ namespace MWWorld SceneUtil::PositionAttitudeTransform* mBaseNode; MWScript::Locals mLocals; + std::shared_ptr mLuaScripts; /// separate delete flag used for deletion by a content file /// @note not stored in the save game file. @@ -96,6 +102,9 @@ namespace MWWorld void setLocals (const ESM::Script& script); + MWLua::LocalScripts* getLuaScripts() { return mLuaScripts.get(); } + void setLuaScripts(std::unique_ptr&&); + void setCount (int count); ///< Set object count (an object pile is a simple object with a count >1). /// From f2d0a702e9d08f1b1c13b74c19df01cc84009d28 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 12 Mar 2021 18:29:51 +0100 Subject: [PATCH 0982/2859] Add Lua timers --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/asyncbindings.cpp | 55 +++++++++++++++++++++++++++++ apps/openmw/mwlua/luabindings.cpp | 2 ++ apps/openmw/mwlua/luabindings.hpp | 10 ++++++ apps/openmw/mwlua/luamanagerimp.cpp | 11 ++++++ apps/openmw/mwlua/worldview.cpp | 8 +++++ apps/openmw/mwlua/worldview.hpp | 10 ++++++ 7 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwlua/asyncbindings.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 1cf87388a6..555aa443c9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp localscripts object worldview luabindings userdataserializer - objectbindings + objectbindings asyncbindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/asyncbindings.cpp b/apps/openmw/mwlua/asyncbindings.cpp new file mode 100644 index 0000000000..83c1dbd81b --- /dev/null +++ b/apps/openmw/mwlua/asyncbindings.cpp @@ -0,0 +1,55 @@ +#include "luabindings.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + + struct TimerCallback + { + AsyncPackageId mAsyncId; + std::string mName; + }; + + sol::function getAsyncPackageInitializer(const Context& context) + { + sol::usertype api = context.mLua->sol().new_usertype("AsyncPackage"); + api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback) + { + asyncId.mContainer->registerTimerCallback(asyncId.mScript, name, std::move(callback)); + return TimerCallback{asyncId, std::string(name)}; + }; + api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay, + const TimerCallback& callback, sol::object callbackArg) + { + callback.mAsyncId.mContainer->setupSerializableTimer( + false, world->getGameTimeInSeconds() + delay, callback.mAsyncId.mScript, callback.mName, std::move(callbackArg)); + }; + api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay, + const TimerCallback& callback, sol::object callbackArg) + { + callback.mAsyncId.mContainer->setupSerializableTimer( + true, world->getGameTimeInHours() + delay, callback.mAsyncId.mScript, callback.mName, std::move(callbackArg)); + }; + api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) + { + asyncId.mContainer->setupUnsavableTimer(false, world->getGameTimeInSeconds() + delay, asyncId.mScript, std::move(callback)); + }; + api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) + { + asyncId.mContainer->setupUnsavableTimer(true, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback)); + }; + + auto initializer = [](sol::table hiddenData) + { + LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY]; + return AsyncPackageId{id.mContainer, id.mPath}; + }; + return sol::make_object(context.mLua->sol(), initializer); + } + +} diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 4f6519397e..5ebb6670f8 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -15,6 +15,8 @@ namespace MWLua { context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; + api["getGameTimeInSeconds"] = [world=context.mWorldView]() { return world->getGameTimeInSeconds(); }; + api["getGameTimeInHours"] = [world=context.mWorldView]() { return world->getGameTimeInHours(); }; return context.mLua->makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 7e544b7eb4..2eae569ef0 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "eventqueue.hpp" #include "object.hpp" @@ -30,6 +31,15 @@ namespace MWLua void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&); + // Implemented in asyncbindings.cpp + struct AsyncPackageId + { + // TODO: add ObjectId mLocalObject; + LuaUtil::ScriptsContainer* mContainer; + std::string mScript; + }; + sol::function getAsyncPackageInitializer(const Context&); + // openmw.self package is implemented in localscripts.cpp } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 023e95e7b4..92e4a257e7 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -34,6 +34,7 @@ namespace MWLua initObjectBindingsForLocalScripts(localContext); LocalScripts::initializeSelfPackage(localContext); + mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context)); mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); mLua.addCommonPackage("openmw.core", initCorePackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); @@ -62,6 +63,16 @@ namespace MWLua mGlobalEvents = std::vector(); mLocalEvents = std::vector(); + { // Update time and process timers + double seconds = mWorldView.getGameTimeInSeconds() + dt; + mWorldView.setGameTimeInSeconds(seconds); + double hours = mWorldView.getGameTimeInHours(); + + mGlobalScripts.processTimers(seconds, hours); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->processTimers(seconds, hours); + } + for (GlobalEvent& e : globalEvents) mGlobalScripts.receiveEvent(e.eventName, e.eventData); for (LocalEvent& e : localEvents) diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index e088dda252..a5b427a011 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -1,6 +1,7 @@ #include "worldview.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/timestamp.hpp" namespace MWLua { @@ -35,6 +36,13 @@ namespace MWLua removeFromGroup(mItemsInScene, ptr); } + double WorldView::getGameTimeInHours() const + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::TimeStamp timeStamp = world->getTimeStamp(); + return static_cast(timeStamp.getDay()) * 24 + timeStamp.getHour(); + } + void WorldView::ObjectGroup::updateList() { if (mChanged) diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index fbfefccdc6..ba6c8cccec 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -13,6 +13,14 @@ namespace MWLua void update(); // Should be called every frame. void clear(); // Should be called every time before starting or loading a new game. + // Returns the number of seconds passed from the beginning of the game. + double getGameTimeInSeconds() const { return mGameSeconds; } + void setGameTimeInSeconds(double t) { mGameSeconds = t; } + + // Returns the number of game hours passed from the beginning of the game. + // Note that the number of seconds in a game hour is not fixed. + double getGameTimeInHours() const; + ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } ObjectIdList getItemsInScene() const { return mItemsInScene.mList; } @@ -40,6 +48,8 @@ namespace MWLua ObjectRegistry mObjectRegistry; ObjectGroup mActorsInScene; ObjectGroup mItemsInScene; + + double mGameSeconds = 0; }; } From 914e604e068a8ba691fe0822c0fa737099b2e573 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 19 Dec 2020 00:02:31 +0100 Subject: [PATCH 0983/2859] Interactions between LuaManager and other parts of OpenMW --- apps/openmw/mwinput/keyboardmanager.cpp | 4 ++++ apps/openmw/mwmechanics/actors.cpp | 24 ++++++++++++++++++++++-- apps/openmw/mwstate/statemanagerimp.cpp | 4 +++- apps/openmw/mwworld/scene.cpp | 5 +++++ apps/openmw/mwworld/worldimp.cpp | 3 +++ 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwinput/keyboardmanager.cpp b/apps/openmw/mwinput/keyboardmanager.cpp index 8540858461..03db584192 100644 --- a/apps/openmw/mwinput/keyboardmanager.cpp +++ b/apps/openmw/mwinput/keyboardmanager.cpp @@ -6,6 +6,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/player.hpp" @@ -58,6 +59,9 @@ namespace MWInput if (!input->controlsDisabled() && !consumed) mBindingsManager->keyPressed(arg); + if (!consumed) + MWBase::Environment::get().getLuaManager()->keyPressed(arg); + input->setJoystickLastUsed(false); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 160cac0dc9..9d3f16c31c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -24,6 +24,7 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwmechanics/aibreathe.hpp" @@ -2009,6 +2010,25 @@ namespace MWMechanics return; // for now abort update of the old cell when cell changes by teleportation magic effect // a better solution might be to apply cell changes at the end of the frame } + bool controlledFromLua; + { + const MWBase::LuaManager::ActorControls* luaControls = + MWBase::Environment::get().getLuaManager()->getActorControls(iter->first); + controlledFromLua = luaControls && luaControls->controlledFromLua; + if (controlledFromLua && isConscious(iter->first)) + { + Movement& mov = iter->first.getClass().getMovementSettings(iter->first); + mov.mPosition[0] = luaControls->sideMovement; + mov.mPosition[1] = luaControls->movement; + mov.mPosition[2] = luaControls->jump ? 1 : 0; + mov.mRotation[0] = mov.mRotation[1] = 0; + mov.mRotation[2] = luaControls->turn; + mov.mSpeedFactor = osg::Vec2(luaControls->movement, luaControls->sideMovement).length(); + + CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->run); + } + } if (aiActive && inProcessingRange) { if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed) @@ -2062,7 +2082,7 @@ namespace MWMechanics if (iter->first != player) { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); - if (isConscious(iter->first)) + if (isConscious(iter->first) && !controlledFromLua) { stats.getAiSequence().execute(iter->first, *ctrl, duration); updateGreetingState(iter->first, *iter->second, timerUpdateHello > 0); @@ -2071,7 +2091,7 @@ namespace MWMechanics } } } - else if (aiActive && iter->first != player && isConscious(iter->first)) + else if (aiActive && iter->first != player && isConscious(iter->first) && !controlledFromLua) { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); stats.getAiSequence().execute(iter->first, *ctrl, duration, /*outOfRange*/true); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index b242862772..c1563719f0 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -27,6 +27,7 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -59,6 +60,7 @@ void MWState::StateManager::cleanup (bool force) MWMechanics::CreatureStats::cleanup(); } + MWBase::Environment::get().getLuaManager()->clear(); } std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) @@ -146,7 +148,7 @@ void MWState::StateManager::newGame (bool bypass) { Log(Debug::Info) << "Starting a new game"; MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - + MWBase::Environment::get().getLuaManager()->newGameStarted(); MWBase::Environment::get().getWorld()->startNewGame (bypass); mState = State_Running; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index d3b381d2af..7d3a6c7893 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -25,6 +25,7 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwrender/renderingmanager.hpp" #include "../mwrender/landmanager.hpp" @@ -138,6 +139,8 @@ namespace if (!physics.getObject(ptr)) ptr.getClass().insertObject (ptr, model, rotation, physics); + + MWBase::Environment::get().getLuaManager()->objectAddedToScene(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) @@ -385,6 +388,7 @@ namespace MWWorld mRendering.removeActorPath(ptr); mPhysics->remove(ptr); } + MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(ptr); } const auto cellX = cell->getCell()->getGridX(); @@ -1006,6 +1010,7 @@ namespace MWWorld { MWBase::Environment::get().getMechanicsManager()->remove (ptr); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); + MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(ptr); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); if (const auto object = mPhysics->getObject(ptr)) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 69167e5ccb..a7915dd30d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -36,6 +36,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -2483,12 +2484,14 @@ namespace MWWorld mNavigator->removeAgent(getPathfindingHalfExtents(getPlayerConstPtr())); mPhysics->remove(getPlayerPtr()); mRendering->removePlayer(getPlayerPtr()); + MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(getPlayerPtr()); mPlayer->set(player); } Ptr ptr = mPlayer->getPlayer(); mRendering->setupPlayer(ptr); + MWBase::Environment::get().getLuaManager()->setupPlayer(ptr); } void World::renderPlayer() From 8c6d303730d4c3dc8832eb5015e6bbf4cd393412 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 02:38:09 +0100 Subject: [PATCH 0984/2859] Saving/loading for Lua scripts (saves format is changed) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/luamanager.hpp | 24 +++++++++ apps/openmw/mwlua/eventqueue.cpp | 63 ++++++++++++++++++++++++ apps/openmw/mwlua/eventqueue.hpp | 20 ++++++++ apps/openmw/mwlua/luamanagerimp.cpp | 61 +++++++++++++++++++++++ apps/openmw/mwlua/luamanagerimp.hpp | 13 +++++ apps/openmw/mwlua/userdataserializer.cpp | 16 ++++-- apps/openmw/mwlua/userdataserializer.hpp | 5 +- apps/openmw/mwlua/worldview.cpp | 17 +++++++ apps/openmw/mwlua/worldview.hpp | 9 ++++ apps/openmw/mwstate/statemanagerimp.cpp | 9 ++++ apps/openmw/mwworld/livecellref.cpp | 4 ++ components/esm/defs.hpp | 5 +- components/esm/objectstate.cpp | 4 ++ components/esm/objectstate.hpp | 2 + components/esm/savedgame.cpp | 2 +- 16 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 apps/openmw/mwlua/eventqueue.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 555aa443c9..240d4a8e42 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -56,7 +56,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwlua - luamanagerimp localscripts object worldview luabindings userdataserializer + luamanagerimp localscripts object worldview luabindings userdataserializer eventqueue objectbindings asyncbindings ) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index ad374c9dba..5b4594728e 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -8,6 +8,18 @@ namespace MWWorld class Ptr; } +namespace Loading +{ + class Listener; +} + +namespace ESM +{ + class ESMReader; + class ESMWriter; + class LuaScripts; +} + namespace MWBase { @@ -35,6 +47,18 @@ namespace MWBase virtual void clear() = 0; virtual void setupPlayer(const MWWorld::Ptr&) = 0; + + // Saving + int countSavedGameRecords() const { return 1; }; + virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) = 0; + virtual void saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) = 0; + + // Loading from a save + virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; + virtual void loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) = 0; + + // Should be called before loading. The map is used to fix refnums if the order of content files was changed. + virtual void setContentFileMapping(const std::map&) = 0; }; } diff --git a/apps/openmw/mwlua/eventqueue.cpp b/apps/openmw/mwlua/eventqueue.cpp new file mode 100644 index 0000000000..5e63fee57a --- /dev/null +++ b/apps/openmw/mwlua/eventqueue.cpp @@ -0,0 +1,63 @@ +#include "eventqueue.hpp" + +#include + +#include +#include +#include + +#include + +namespace MWLua +{ + + template + void saveEvent(ESM::ESMWriter& esm, const ObjectId& dest, const Event& event) + { + esm.writeHNString("LUAE", event.eventName); + dest.save(esm, true); + if (!event.eventData.empty()) + saveLuaBinaryData(esm, event.eventData); + } + + void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents, LocalEventQueue& localEvents, + const std::map& contentFileMapping, const LuaUtil::UserdataSerializer* serializer) + { + while (esm.isNextSub("LUAE")) + { + std::string name = esm.getHString(); + ObjectId dest; + dest.load(esm, true); + std::string data = loadLuaBinaryData(esm); + try + { + data = LuaUtil::serialize(LuaUtil::deserialize(lua, data, serializer), serializer); + } + catch (std::exception& e) + { + Log(Debug::Error) << "loadEvent: invalid event data: " << e.what(); + } + if (dest.isSet()) + { + auto it = contentFileMapping.find(dest.mContentFile); + if (it != contentFileMapping.end()) + dest.mContentFile = it->second; + localEvents.push_back({dest, std::move(name), std::move(data)}); + } + else + globalEvents.push_back({std::move(name), std::move(data)}); + } + } + + void saveEvents(ESM::ESMWriter& esm, const GlobalEventQueue& globalEvents, const LocalEventQueue& localEvents) + { + ObjectId globalId; + globalId.unset(); // Used as a marker of a global event. + + for (const GlobalEvent& e : globalEvents) + saveEvent(esm, globalId, e); + for (const LocalEvent& e : localEvents) + saveEvent(esm, e.dest, e); + } + +} diff --git a/apps/openmw/mwlua/eventqueue.hpp b/apps/openmw/mwlua/eventqueue.hpp index cd79e05dc0..b692d7162d 100644 --- a/apps/openmw/mwlua/eventqueue.hpp +++ b/apps/openmw/mwlua/eventqueue.hpp @@ -3,6 +3,22 @@ #include "object.hpp" +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + +namespace LuaUtil +{ + class UserdataSerializer; +} + +namespace sol +{ + class state; +} + namespace MWLua { struct GlobalEvent @@ -18,6 +34,10 @@ namespace MWLua }; using GlobalEventQueue = std::vector; using LocalEventQueue = std::vector; + + void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue&, LocalEventQueue&, + const std::map& contentFileMapping, const LuaUtil::UserdataSerializer* serializer); + void saveEvents(ESM::ESMWriter& esm, const GlobalEventQueue&, const LocalEventQueue&); } #endif // MWLUA_EVENTQUEUE_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 92e4a257e7..00b8c10cfe 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -1,6 +1,11 @@ #include "luamanagerimp.hpp" #include + +#include +#include +#include + #include #include "../mwworld/class.hpp" @@ -15,8 +20,12 @@ namespace MWLua LuaManager::LuaManager(const VFS::Manager* vfs) : mLua(vfs) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); + mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry()); mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry()); + mGlobalLoader = createUserdataSerializer(false, mWorldView.getObjectRegistry(), &mContentFileMapping); + mLocalLoader = createUserdataSerializer(true, mWorldView.getObjectRegistry(), &mContentFileMapping); + mGlobalScripts.setSerializer(mGlobalSerializer.get()); Context context; @@ -210,4 +219,56 @@ namespace MWLua return refData.getLuaScripts(); } + void LuaManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) + { + writer.startRecord(ESM::REC_LUAM); + + mWorldView.save(writer); + ESM::LuaScripts globalScripts; + mGlobalScripts.save(globalScripts); + globalScripts.save(writer); + saveEvents(writer, mGlobalEvents, mLocalEvents); + + writer.endRecord(ESM::REC_LUAM); + } + + void LuaManager::readRecord(ESM::ESMReader& reader, uint32_t type) + { + if (type != ESM::REC_LUAM) + throw std::runtime_error("ESM::REC_LUAM is expected"); + + mWorldView.load(reader); + ESM::LuaScripts globalScripts; + globalScripts.load(reader); + loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get()); + + mGlobalScripts.setSerializer(mGlobalLoader.get()); + mGlobalScripts.load(globalScripts, false); + mGlobalScripts.setSerializer(mGlobalSerializer.get()); + } + + void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) + { + if (ptr.getRefData().getLuaScripts()) + ptr.getRefData().getLuaScripts()->save(data); + else + data.mScripts.clear(); + } + + void LuaManager::loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) + { + if (data.mScripts.empty()) + { + if (ptr.getRefData().getLuaScripts()) + ptr.getRefData().setLuaScripts(nullptr); + return; + } + + mWorldView.getObjectRegistry()->registerPtr(ptr); + LocalScripts* scripts = createLocalScripts(ptr); + + scripts->setSerializer(mLocalLoader.get()); + scripts->load(data, true); + scripts->setSerializer(mLocalSerializer.get()); + } } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 3420ac5dbb..fcde053009 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -49,6 +49,15 @@ namespace MWLua // Used only in luabindings.cpp void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath); + // Saving + void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; + void saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) override; + + // Loading from a save + void readRecord(ESM::ESMReader& reader, uint32_t type) override; + void loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) override; + void setContentFileMapping(const std::map& mapping) override { mContentFileMapping = mapping; } + private: LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr); @@ -69,6 +78,10 @@ namespace MWLua std::unique_ptr mGlobalSerializer; std::unique_ptr mLocalSerializer; + std::map mContentFileMapping; + std::unique_ptr mGlobalLoader; + std::unique_ptr mLocalLoader; + std::vector mKeyPressEvents; std::vector mActorAddedEvents; }; diff --git a/apps/openmw/mwlua/userdataserializer.cpp b/apps/openmw/mwlua/userdataserializer.cpp index a0315d4777..6946cd5532 100644 --- a/apps/openmw/mwlua/userdataserializer.cpp +++ b/apps/openmw/mwlua/userdataserializer.cpp @@ -11,8 +11,8 @@ namespace MWLua class Serializer final : public LuaUtil::UserdataSerializer { public: - explicit Serializer(bool localSerializer, ObjectRegistry* registry) - : mLocalSerializer(localSerializer), mObjectRegistry(registry) {} + explicit Serializer(bool localSerializer, ObjectRegistry* registry, std::map* contentFileMapping) + : mLocalSerializer(localSerializer), mObjectRegistry(registry), mContentFileMapping(contentFileMapping) {} private: // Appends serialized sol::userdata to the end of BinaryData. @@ -43,6 +43,12 @@ namespace MWLua std::memcpy(&id, binaryData.data(), sizeof(ObjectId)); id.mIndex = Misc::fromLittleEndian(id.mIndex); id.mContentFile = Misc::fromLittleEndian(id.mContentFile); + if (id.hasContentFile() && mContentFileMapping) + { + auto iter = mContentFileMapping->find(id.mContentFile); + if (iter != mContentFileMapping->end()) + id.mContentFile = iter->second; + } if (mLocalSerializer) sol::stack::push(lua, LObject(id, mObjectRegistry)); else @@ -54,11 +60,13 @@ namespace MWLua bool mLocalSerializer; ObjectRegistry* mObjectRegistry; + std::map* mContentFileMapping; }; - std::unique_ptr createUserdataSerializer(bool local, ObjectRegistry* registry) + std::unique_ptr createUserdataSerializer( + bool local, ObjectRegistry* registry, std::map* contentFileMapping) { - return std::make_unique(local, registry); + return std::make_unique(local, registry, contentFileMapping); } } diff --git a/apps/openmw/mwlua/userdataserializer.hpp b/apps/openmw/mwlua/userdataserializer.hpp index 72fab0f53b..70af7ebd06 100644 --- a/apps/openmw/mwlua/userdataserializer.hpp +++ b/apps/openmw/mwlua/userdataserializer.hpp @@ -13,7 +13,10 @@ namespace MWLua // UserdataSerializer is an extension for components/lua/serialization.hpp // Needed to serialize references to objects. // If local=true, then during deserialization creates LObject, otherwise creates GObject. - std::unique_ptr createUserdataSerializer(bool local, ObjectRegistry* registry); + // contentFileMapping is used only for deserialization. Needed to fix references if the order + // of content files was changed. + std::unique_ptr createUserdataSerializer( + bool local, ObjectRegistry* registry, std::map* contentFileMapping = nullptr); } #endif // MWLUA_USERDATASERIALIZER_H diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index a5b427a011..fc08b60037 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -1,5 +1,8 @@ #include "worldview.hpp" +#include +#include + #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" @@ -43,6 +46,20 @@ namespace MWLua return static_cast(timeStamp.getDay()) * 24 + timeStamp.getHour(); } + void WorldView::load(ESM::ESMReader& esm) + { + esm.getHNT(mGameSeconds, "LUAW"); + ObjectId lastAssignedId; + lastAssignedId.load(esm, true); + mObjectRegistry.setLastAssignedId(lastAssignedId); + } + + void WorldView::save(ESM::ESMWriter& esm) const + { + esm.writeHNT("LUAW", mGameSeconds); + mObjectRegistry.getLastAssignedId().save(esm, true); + } + void WorldView::ObjectGroup::updateList() { if (mChanged) diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index ba6c8cccec..45046c0b73 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -3,6 +3,12 @@ #include "object.hpp" +namespace ESM +{ + class ESMWriter; + class ESMReader; +} + namespace MWLua { @@ -31,6 +37,9 @@ namespace MWLua void objectAddedToScene(const MWWorld::Ptr& ptr); void objectRemovedFromScene(const MWWorld::Ptr& ptr); + void load(ESM::ESMReader& esm); + void save(ESM::ESMWriter& esm) const; + private: struct ObjectGroup { diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c1563719f0..4e4a8e95d6 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -251,6 +251,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot int recordCount = 1 // saved game header +MWBase::Environment::get().getJournal()->countSavedGameRecords() + +MWBase::Environment::get().getLuaManager()->countSavedGameRecords() +MWBase::Environment::get().getWorld()->countSavedGameRecords() +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() @@ -274,6 +275,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::Environment::get().getJournal()->write (writer, listener); MWBase::Environment::get().getDialogueManager()->write (writer, listener); + // LuaManager::write should be called before World::write because world also saves + // local scripts that depend on LuaManager. + MWBase::Environment::get().getLuaManager()->write(writer, listener); MWBase::Environment::get().getWorld()->write (writer, listener); MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); MWBase::Environment::get().getWindowManager()->write(writer, listener); @@ -384,6 +388,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str throw std::runtime_error("This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file."); std::map contentFileMap = buildContentFileIndexMap (reader); + MWBase::Environment::get().getLuaManager()->setContentFileMapping(contentFileMap); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); @@ -482,6 +487,10 @@ void MWState::StateManager::loadGame (const Character *character, const std::str MWBase::Environment::get().getInputManager()->readRecord(reader, n.intval); break; + case ESM::REC_LUAM: + MWBase::Environment::get().getLuaManager()->readRecord(reader, n.intval); + break; + default: // ignore invalid records diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 9cf8a0fe04..4203e1ac55 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -5,6 +5,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/luamanager.hpp" #include "ptr.hpp" #include "class.hpp" @@ -52,6 +53,8 @@ void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) Log(Debug::Warning) << "Soul '" << mRef.getSoul() << "' not found, removing the soul from soul gem"; mRef.setSoul(std::string()); } + + MWBase::Environment::get().getLuaManager()->loadLocalScripts(ptr, state.mLuaScripts); } void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const @@ -61,6 +64,7 @@ void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const ConstPtr ptr (this); mData.write (state, mClass->getScript (ptr)); + MWBase::Environment::get().getLuaManager()->saveLocalScripts(Ptr(const_cast(this)), state.mLuaScripts); mClass->writeAdditionalState (ptr, state); } diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 1b623f69f0..7f2fe19cc5 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -164,7 +164,10 @@ enum RecNameInts // format 1 REC_FILT = FourCC<'F','I','L','T'>::value, - REC_DBGP = FourCC<'D','B','G','P'>::value ///< only used in project files + REC_DBGP = FourCC<'D','B','G','P'>::value, ///< only used in project files + + // format 16 - Lua scripts in saved games + REC_LUAM = FourCC<'L','U','A','M'>::value, // LuaManager data }; /// Common subrecords diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 9709bf4ff6..76967e497c 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -20,6 +20,8 @@ void ESM::ObjectState::load (ESMReader &esm) if (mHasLocals) mLocals.load (esm); + mLuaScripts.load(esm); + mEnabled = 1; esm.getHNOT (mEnabled, "ENAB"); @@ -56,6 +58,8 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const mLocals.save (esm); } + mLuaScripts.save(esm); + if (!mEnabled && !inInventory) esm.writeHNT ("ENAB", mEnabled); diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index 6b0fca5ea6..b30f44b5e1 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -6,6 +6,7 @@ #include "cellref.hpp" #include "locals.hpp" +#include "luascripts.hpp" #include "animationstate.hpp" namespace ESM @@ -27,6 +28,7 @@ namespace ESM unsigned char mHasLocals; Locals mLocals; + LuaScripts mLuaScripts; unsigned char mEnabled; int mCount; ESM::Position mPosition; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 7cb30f2dd2..3f8bf10c56 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 15; +int ESM::SavedGame::sCurrentFormat = 16; void ESM::SavedGame::load (ESMReader &esm) { From 87b5afb9bf34c5d163abed014ef72692be10c42b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 7 Feb 2021 12:44:03 +0100 Subject: [PATCH 0985/2859] Control active Lua scripts from openmw.cfg --- apps/openmw/engine.cpp | 7 +++++- apps/openmw/engine.hpp | 2 ++ apps/openmw/main.cpp | 7 ++++++ apps/openmw/mwlua/luamanagerimp.cpp | 38 ++++++++++++++++++++++++++--- apps/openmw/mwlua/luamanagerimp.hpp | 3 ++- 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3b7d17e69b..b9f499ffb4 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -493,6 +493,11 @@ void OMW::Engine::addGroundcoverFile(const std::string& file) mGroundcoverFiles.emplace_back(file); } +void OMW::Engine::addLuaScriptListFile(const std::string& file) +{ + mLuaScriptListFiles.push_back(file); +} + void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; @@ -707,7 +712,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mViewer->addEventHandler(mScreenCaptureHandler); - mLuaManager = new MWLua::LuaManager(mVFS.get()); + mLuaManager = new MWLua::LuaManager(mVFS.get(), mLuaScriptListFiles); mEnvironment.setLuaManager(mLuaManager); // Create input and UI first to set up a bootstrapping environment for diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index efa8c688dd..0645939694 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -71,6 +71,7 @@ namespace OMW std::string mCellName; std::vector mContentFiles; std::vector mGroundcoverFiles; + std::vector mLuaScriptListFiles; bool mSkipMenu; bool mUseSound; bool mCompileAll; @@ -144,6 +145,7 @@ namespace OMW */ void addContentFile(const std::string& file); void addGroundcoverFile(const std::string& file); + void addLuaScriptListFile(const std::string& file); /// Disable or enable all sounds void setSoundUsage(bool soundUsage); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 709ffda2cb..324a18bdee 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -65,6 +65,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") + ("lua-scripts", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "file(s) with a list of global Lua scripts: omwscripts") + ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") @@ -204,6 +207,10 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.addGroundcoverFile(file); } + StringsVector luaScriptLists = variables["lua-scripts"].as().toStdStringVector(); + for (const auto& file : luaScriptLists) + engine.addLuaScriptListFile(file); + // startup-settings engine.setCell(variables["start"].as().toStdString()); engine.setSkipMenu (variables["skip-menu"].as(), variables["new-game"].as()); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 00b8c10cfe..c725c0f936 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -17,7 +17,7 @@ namespace MWLua { - LuaManager::LuaManager(const VFS::Manager* vfs) : mLua(vfs) + LuaManager::LuaManager(const VFS::Manager* vfs, const std::vector& globalScriptLists) : mLua(vfs) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); @@ -48,13 +48,45 @@ namespace MWLua mLua.addCommonPackage("openmw.core", initCorePackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); mNearbyPackage = initNearbyPackage(localContext); + + auto endsWith = [](std::string_view s, std::string_view suffix) + { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + }; + for (const std::string& scriptListFile : globalScriptLists) + { + if (!endsWith(scriptListFile, ".omwscripts")) + { + Log(Debug::Error) << "Script list should have suffix '.omwscripts', got: '" << scriptListFile << "'"; + continue; + } + std::string content(std::istreambuf_iterator(*vfs->get(scriptListFile)), {}); + std::string_view view(content); + while (!view.empty()) + { + size_t pos = 0; + while (pos < view.size() && view[pos] != '\n') + pos++; + std::string_view line = view.substr(0, pos); + view = view.substr(pos + 1); + if (line.empty() || line[0] == '#') + continue; + if (line.back() == '\r') + line = line.substr(0, pos - 1); + if (endsWith(line, ".lua")) + mGlobalScriptList.push_back(std::string(line)); + else + Log(Debug::Error) << "Lua script should have suffix '.lua', got: '" << line.substr(0, 300) << "'"; + } + } } void LuaManager::init() { mKeyPressEvents.clear(); - if (mGlobalScripts.addNewScript("test.lua")) - Log(Debug::Info) << "Global script started: test.lua"; + for (const std::string& path : mGlobalScriptList) + if (mGlobalScripts.addNewScript(path)) + Log(Debug::Info) << "Global script started: " << path; } void LuaManager::update(bool paused, float dt) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index fcde053009..833b22cf0f 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -21,7 +21,7 @@ namespace MWLua class LuaManager : public MWBase::LuaManager { public: - LuaManager(const VFS::Manager* vfs); + LuaManager(const VFS::Manager* vfs, const std::vector& globalScriptLists); ~LuaManager() {} // Called by engine.cpp when environment is fully initialized. @@ -64,6 +64,7 @@ namespace MWLua LuaUtil::LuaState mLua; sol::table mNearbyPackage; + std::vector mGlobalScriptList; GlobalScripts mGlobalScripts{&mLua}; std::set mActiveLocalScripts; WorldView mWorldView; From 32218f6dd5d90e0c957b051b77bf9af94e18d091 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Jan 2021 01:54:54 +0100 Subject: [PATCH 0986/2859] More Lua bindings --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/camerabindings.cpp | 13 +++++ apps/openmw/mwlua/localscripts.cpp | 27 ++++++++++ apps/openmw/mwlua/luabindings.cpp | 16 ++++++ apps/openmw/mwlua/luabindings.hpp | 6 +++ apps/openmw/mwlua/luamanagerimp.cpp | 12 ++++- apps/openmw/mwlua/luamanagerimp.hpp | 6 +++ apps/openmw/mwlua/object.cpp | 81 ++++++++++++++++++++-------- apps/openmw/mwlua/object.hpp | 7 ++- apps/openmw/mwlua/objectbindings.cpp | 40 ++++++++++++++ apps/openmw/mwlua/uibindings.cpp | 18 +++++++ apps/openmw/mwlua/worldview.cpp | 39 +++++++++++--- apps/openmw/mwlua/worldview.hpp | 7 +++ 13 files changed, 240 insertions(+), 34 deletions(-) create mode 100644 apps/openmw/mwlua/camerabindings.cpp create mode 100644 apps/openmw/mwlua/uibindings.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 240d4a8e42..be80584882 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp localscripts object worldview luabindings userdataserializer eventqueue - objectbindings asyncbindings + objectbindings asyncbindings camerabindings uibindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp new file mode 100644 index 0000000000..68f2331b9e --- /dev/null +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -0,0 +1,13 @@ +#include "luabindings.hpp" + +namespace MWLua +{ + + sol::table initCameraPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + // TODO + return context.mLua->makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 1c35b60131..573188c004 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -1,5 +1,10 @@ #include "localscripts.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/class.hpp" +#include "../mwmechanics/aisequence.hpp" +#include "../mwmechanics/aicombat.hpp" + namespace sol { template <> @@ -27,6 +32,28 @@ namespace MWLua selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.controlledFromLua = v; }; + selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + MWWorld::Ptr target; + if (ai.getCombatTarget(target)) + return LObject(getId(target), worldView->getObjectRegistry()); + else + return {}; + }; + selfAPI["stopCombat"] = [](SelfObject& self) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stopCombat(); + }; + selfAPI["startCombat"] = [](SelfObject& self, const LObject& target) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiCombat(target.ptr()), ptr); + }; } std::unique_ptr LocalScripts::create(LuaUtil::LuaState* lua, const LObject& obj) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 5ebb6670f8..f8d7812c61 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -8,6 +8,14 @@ namespace MWLua { + static sol::table definitionList(LuaUtil::LuaState& lua, std::initializer_list values) + { + sol::table res(lua.sol(), sol::create); + for (const std::string& v : values) + res[v] = v; + return lua.makeReadOnly(res); + } + sol::table initCorePackage(const Context& context) { sol::table api(context.mLua->sol(), sol::create); @@ -17,6 +25,11 @@ namespace MWLua }; api["getGameTimeInSeconds"] = [world=context.mWorldView]() { return world->getGameTimeInSeconds(); }; api["getGameTimeInHours"] = [world=context.mWorldView]() { return world->getGameTimeInHours(); }; + api["OBJECT_TYPE"] = definitionList(*context.mLua, + { + "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", + "Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon" + }); return context.mLua->makeReadOnly(api); } @@ -32,7 +45,10 @@ namespace MWLua { sol::table api(context.mLua->sol(), sol::create); WorldView* worldView = context.mWorldView; + api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; + api["containers"] = LObjectList{worldView->getContainersInScene()}; + api["doors"] = LObjectList{worldView->getDoorsInScene()}; api["items"] = LObjectList{worldView->getItemsInScene()}; return context.mLua->makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 2eae569ef0..251ed3998b 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -40,6 +40,12 @@ namespace MWLua }; sol::function getAsyncPackageInitializer(const Context&); + // Implemented in camerabindings.cpp + sol::table initCameraPackage(const Context&); + + // Implemented in uibindings.cpp + sol::table initUserInterfacePackage(const Context&); + // openmw.self package is implemented in localscripts.cpp } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index c725c0f936..b629e22ab2 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -8,6 +8,8 @@ #include +#include "../mwbase/windowmanager.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" @@ -47,6 +49,8 @@ namespace MWLua mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); mLua.addCommonPackage("openmw.core", initCorePackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); + mCameraPackage = initCameraPackage(localContext); + mUserInterfacePackage = initUserInterfacePackage(localContext); mNearbyPackage = initNearbyPackage(localContext); auto endsWith = [](std::string_view s, std::string_view suffix) @@ -151,6 +155,10 @@ namespace MWLua void LuaManager::applyQueuedChanges() { + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); + for (const std::string& message : mUIMessages) + windowManager->messageBox(message); + mUIMessages.clear(); } void LuaManager::clear() @@ -238,8 +246,8 @@ namespace MWLua { mPlayerScripts = new PlayerScripts(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); scripts = std::unique_ptr(mPlayerScripts); - // TODO: scripts->addPackage("openmw.ui", ...); - // TODO: scripts->addPackage("openmw.camera", ...); + scripts->addPackage("openmw.ui", mUserInterfacePackage); + scripts->addPackage("openmw.camera", mCameraPackage); } else scripts = LocalScripts::create(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 833b22cf0f..e8babc9695 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -48,6 +48,7 @@ namespace MWLua // Used only in luabindings.cpp void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath); + void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } // Saving void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; @@ -63,6 +64,8 @@ namespace MWLua LuaUtil::LuaState mLua; sol::table mNearbyPackage; + sol::table mUserInterfacePackage; + sol::table mCameraPackage; std::vector mGlobalScriptList; GlobalScripts mGlobalScripts{&mLua}; @@ -85,6 +88,9 @@ namespace MWLua std::vector mKeyPressEvents; std::vector mActorAddedEvents; + + // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). + std::vector mUIMessages; }; } diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index fa0f9daffc..2eb8d16f3c 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -1,7 +1,19 @@ #include "object.hpp" -#include -#include +#include "../mwclass/activator.hpp" +#include "../mwclass/armor.hpp" +#include "../mwclass/book.hpp" +#include "../mwclass/clothing.hpp" +#include "../mwclass/container.hpp" +#include "../mwclass/creature.hpp" +#include "../mwclass/door.hpp" +#include "../mwclass/ingredient.hpp" +#include "../mwclass/light.hpp" +#include "../mwclass/misc.hpp" +#include "../mwclass/npc.hpp" +#include "../mwclass/potion.hpp" +#include "../mwclass/static.hpp" +#include "../mwclass/weapon.hpp" namespace MWLua { @@ -11,33 +23,58 @@ namespace MWLua return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile); } - std::string Object::toString() const + const static std::map classNames = { + {typeid(MWClass::Activator), "Activator"}, + {typeid(MWClass::Armor), "Armor"}, + {typeid(MWClass::Book), "Book"}, + {typeid(MWClass::Clothing), "Clothing"}, + {typeid(MWClass::Container), "Container"}, + {typeid(MWClass::Creature), "Creature"}, + {typeid(MWClass::Door), "Door"}, + {typeid(MWClass::Ingredient), "Ingredient"}, + {typeid(MWClass::Light), "Light"}, + {typeid(MWClass::Miscellaneous), "Miscellaneous"}, + {typeid(MWClass::Npc), "NPC"}, + {typeid(MWClass::Potion), "Potion"}, + {typeid(MWClass::Static), "Static"}, + {typeid(MWClass::Weapon), "Weapon"}, + }; + + std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback) { - std::string res = idToString(mId); - if (isValid()) - { - res.append(" ("); - res.append(type()); - res.append(", "); - res.append(*ptr().getCellRef().getRefIdPtr()); - res.append(")"); - } + auto it = classNames.find(cls_type); + if (it != classNames.end()) + return it->second; else - res.append(" (not found)"); - return res; + return fallback; } - std::string_view Object::type() const + std::string_view getMWClassName(const MWWorld::Ptr& ptr) { - if (*ptr().getCellRef().getRefIdPtr() == "player") + if (*ptr.getCellRef().getRefIdPtr() == "player") return "Player"; - const std::string& typeName = ptr().getTypeName(); - if (typeName == typeid(ESM::NPC).name()) - return "NPC"; - else if (typeName == typeid(ESM::Creature).name()) - return "Creature"; else - return typeName; + return getMWClassName(typeid(ptr.getClass()), ptr.getTypeName()); + } + + std::string ptrToString(const MWWorld::Ptr& ptr) + { + std::string res = "object"; + res.append(idToString(getId(ptr))); + res.append(" ("); + res.append(getMWClassName(ptr)); + res.append(", "); + res.append(*ptr.getCellRef().getRefIdPtr()); + res.append(")"); + return res; + } + + std::string Object::toString() const + { + if (isValid()) + return ptrToString(ptr()); + else + return "object" + idToString(mId) + " (not found)"; } bool Object::isValid() const diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 59e6534166..ac079fb38f 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -1,6 +1,8 @@ #ifndef MWLUA_OBJECT_H #define MWLUA_OBJECT_H +#include + #include #include "../mwbase/environment.hpp" @@ -15,6 +17,9 @@ namespace MWLua using ObjectId = ESM::RefNum; inline const ObjectId& getId(const MWWorld::Ptr& ptr) { return ptr.getCellRef().getRefNum(); } std::string idToString(const ObjectId& id); + std::string ptrToString(const MWWorld::Ptr& ptr); + std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback = "Unknown"); + std::string_view getMWClassName(const MWWorld::Ptr& ptr); // Holds a mapping ObjectId -> MWWord::Ptr. class ObjectRegistry @@ -60,7 +65,7 @@ namespace MWLua ObjectId id() const { return mId; } std::string toString() const; - std::string_view type() const; + std::string_view type() const { return getMWClassName(ptr()); } // Updates and returns the underlying Ptr. Throws an exception if object is not available. const MWWorld::Ptr& ptr() const; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 806907ef35..dbe0a54480 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -2,6 +2,8 @@ #include +#include "../mwclass/door.hpp" + #include "eventqueue.hpp" #include "luamanagerimp.hpp" @@ -20,6 +22,20 @@ namespace sol namespace MWLua { + template + static const MWWorld::Ptr& requireClass(const MWWorld::Ptr& ptr) + { + if (typeid(Class) != typeid(ptr.getClass())) + { + std::string msg = "Requires type '"; + msg.append(getMWClassName(typeid(Class))); + msg.append("', but applied to "); + msg.append(ptrToString(ptr)); + throw std::runtime_error(msg); + } + return ptr; + } + template static void registerObjectList(const std::string& prefix, const Context& context) { @@ -83,11 +99,35 @@ namespace MWLua } } + template + static void addDoorBindings(sol::usertype& objectT, const Context& context) + { + auto ptr = [](const ObjectT& o) -> const MWWorld::Ptr& { return requireClass(o.ptr()); }; + + objectT["isTeleport"] = sol::readonly_property([ptr](const ObjectT& o) + { + return ptr(o).getCellRef().getTeleport(); + }); + objectT["destPosition"] = sol::readonly_property([ptr](const ObjectT& o) -> osg::Vec3f + { + return ptr(o).getCellRef().getDoorDest().asVec3(); + }); + objectT["destRotation"] = sol::readonly_property([ptr](const ObjectT& o) -> osg::Vec3f + { + return ptr(o).getCellRef().getDoorDest().asRotationVec3(); + }); + objectT["destCell"] = sol::readonly_property([ptr](const ObjectT& o) -> std::string_view + { + return ptr(o).getCellRef().getDestCell(); + }); + } + template static void initObjectBindings(const std::string& prefix, const Context& context) { sol::usertype objectT = context.mLua->sol().new_usertype(prefix + "Object"); addBasicBindings(objectT, context); + addDoorBindings(objectT, context); registerObjectList(prefix, context); } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp new file mode 100644 index 0000000000..cb14c41621 --- /dev/null +++ b/apps/openmw/mwlua/uibindings.cpp @@ -0,0 +1,18 @@ +#include "luabindings.hpp" + +#include "luamanagerimp.hpp" + +namespace MWLua +{ + + sol::table initUserInterfacePackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + api["showMessage"] = [luaManager=context.mLuaManager](std::string_view message) + { + luaManager->addUIMessage(message); + }; + return context.mLua->makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index fc08b60037..6e54efac39 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -3,6 +3,8 @@ #include #include +#include "../mwclass/container.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" @@ -12,31 +14,52 @@ namespace MWLua void WorldView::update() { mObjectRegistry.update(); + mActivatorsInScene.updateList(); mActorsInScene.updateList(); + mContainersInScene.updateList(); + mDoorsInScene.updateList(); mItemsInScene.updateList(); } void WorldView::clear() { mObjectRegistry.clear(); + mActivatorsInScene.clear(); mActorsInScene.clear(); + mContainersInScene.clear(); + mDoorsInScene.clear(); mItemsInScene.clear(); } + WorldView::ObjectGroup* WorldView::chooseGroup(const MWWorld::Ptr& ptr) + { + const MWWorld::Class& cls = ptr.getClass(); + if (cls.isActivator()) + return &mActivatorsInScene; + if (cls.isActor()) + return &mActorsInScene; + if (cls.isDoor()) + return &mDoorsInScene; + if (typeid(cls) == typeid(MWClass::Container)) + return &mContainersInScene; + if (cls.hasToolTip(ptr)) + return &mItemsInScene; + return nullptr; + } + void WorldView::objectAddedToScene(const MWWorld::Ptr& ptr) { - if (ptr.getClass().isActor()) - addToGroup(mActorsInScene, ptr); - else - addToGroup(mItemsInScene, ptr); + mObjectRegistry.registerPtr(ptr); + ObjectGroup* group = chooseGroup(ptr); + if (group) + addToGroup(*group, ptr); } void WorldView::objectRemovedFromScene(const MWWorld::Ptr& ptr) { - if (ptr.getClass().isActor()) - removeFromGroup(mActorsInScene, ptr); - else - removeFromGroup(mItemsInScene, ptr); + ObjectGroup* group = chooseGroup(ptr); + if (group) + removeFromGroup(*group, ptr); } double WorldView::getGameTimeInHours() const diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index 45046c0b73..89f5afa0c3 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -27,7 +27,10 @@ namespace MWLua // Note that the number of seconds in a game hour is not fixed. double getGameTimeInHours() const; + ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; } ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } + ObjectIdList getContainersInScene() const { return mContainersInScene.mList; } + ObjectIdList getDoorsInScene() const { return mDoorsInScene.mList; } ObjectIdList getItemsInScene() const { return mItemsInScene.mList; } ObjectRegistry* getObjectRegistry() { return &mObjectRegistry; } @@ -51,11 +54,15 @@ namespace MWLua std::set mSet; }; + ObjectGroup* chooseGroup(const MWWorld::Ptr& ptr); void addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); void removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr); ObjectRegistry mObjectRegistry; + ObjectGroup mActivatorsInScene; ObjectGroup mActorsInScene; + ObjectGroup mContainersInScene; + ObjectGroup mDoorsInScene; ObjectGroup mItemsInScene; double mGameSeconds = 0; From bdccf161c473070aa6efcceb9ec72414ad803427 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 17 Feb 2021 22:56:14 +0100 Subject: [PATCH 0987/2859] Add lua package 'openmw.query' --- apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwlua/context.hpp | 30 ++++++ apps/openmw/mwlua/luabindings.cpp | 66 ++++++++++++ apps/openmw/mwlua/luabindings.hpp | 16 +-- apps/openmw/mwlua/luamanagerimp.cpp | 3 + apps/openmw/mwlua/objectbindings.cpp | 5 + apps/openmw/mwlua/query.cpp | 150 +++++++++++++++++++++++++++ apps/openmw/mwlua/query.hpp | 38 +++++++ apps/openmw/mwlua/worldview.hpp | 4 + 9 files changed, 303 insertions(+), 13 deletions(-) create mode 100644 apps/openmw/mwlua/context.hpp create mode 100644 apps/openmw/mwlua/query.cpp create mode 100644 apps/openmw/mwlua/query.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index be80584882..de55c36475 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -56,8 +56,8 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwlua - luamanagerimp localscripts object worldview luabindings userdataserializer eventqueue - objectbindings asyncbindings camerabindings uibindings + luamanagerimp localscripts object worldview userdataserializer eventqueue query + luabindings objectbindings asyncbindings camerabindings uibindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/context.hpp b/apps/openmw/mwlua/context.hpp new file mode 100644 index 0000000000..b3e3703a46 --- /dev/null +++ b/apps/openmw/mwlua/context.hpp @@ -0,0 +1,30 @@ +#ifndef MWLUA_CONTEXT_H +#define MWLUA_CONTEXT_H + +#include "eventqueue.hpp" + +namespace LuaUtil +{ + class LuaState; + class UserdataSerializer; +} + +namespace MWLua +{ + class LuaManager; + class WorldView; + + struct Context + { + bool mIsGlobal; + LuaManager* mLuaManager; + LuaUtil::LuaState* mLua; + LuaUtil::UserdataSerializer* mSerializer; + WorldView* mWorldView; + LocalEventQueue* mLocalEventQueue; + GlobalEventQueue* mGlobalEventQueue; + }; + +} + +#endif // MWLUA_CONTEXT_H diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index f8d7812c61..8d9cbb8bee 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -1,6 +1,7 @@ #include "luabindings.hpp" #include +#include #include "eventqueue.hpp" #include "worldview.hpp" @@ -38,6 +39,24 @@ namespace MWLua sol::table api(context.mLua->sol(), sol::create); WorldView* worldView = context.mWorldView; api["activeActors"] = GObjectList{worldView->getActorsInScene()}; + api["selectObjects"] = [context](const Queries::Query& query) + { + ObjectIdList list; + WorldView* worldView = context.mWorldView; + if (query.mQueryType == "activators") + list = worldView->getActivatorsInScene(); + else if (query.mQueryType == "actors") + list = worldView->getActorsInScene(); + else if (query.mQueryType == "containers") + list = worldView->getContainersInScene(); + else if (query.mQueryType == "doors") + list = worldView->getDoorsInScene(); + else if (query.mQueryType == "items") + list = worldView->getItemsInScene(); + return GObjectList{selectObjectsFromList(query, list, context)}; + // TODO: Use sqlite to search objects that are not in the scene + // return GObjectList{worldView->selectObjects(query, false)}; + }; return context.mLua->makeReadOnly(api); } @@ -50,8 +69,55 @@ namespace MWLua api["containers"] = LObjectList{worldView->getContainersInScene()}; api["doors"] = LObjectList{worldView->getDoorsInScene()}; api["items"] = LObjectList{worldView->getItemsInScene()}; + api["selectObjects"] = [context](const Queries::Query& query) + { + ObjectIdList list; + WorldView* worldView = context.mWorldView; + if (query.mQueryType == "activators") + list = worldView->getActivatorsInScene(); + else if (query.mQueryType == "actors") + list = worldView->getActorsInScene(); + else if (query.mQueryType == "containers") + list = worldView->getContainersInScene(); + else if (query.mQueryType == "doors") + list = worldView->getDoorsInScene(); + else if (query.mQueryType == "items") + list = worldView->getItemsInScene(); + return LObjectList{selectObjectsFromList(query, list, context)}; + // TODO: Maybe use sqlite + // return LObjectList{worldView->selectObjects(query, true)}; + }; return context.mLua->makeReadOnly(api); } + sol::table initQueryPackage(const Context& context) + { + Queries::registerQueryBindings(context.mLua->sol()); + sol::table query(context.mLua->sol(), sol::create); + for (std::string_view t : ObjectQueryTypes::types) + query[t] = Queries::Query(std::string(t)); + for (const QueryFieldGroup& group : getBasicQueryFieldGroups()) + query[group.mName] = initFieldGroup(context, group); + return query; // makeReadonly is applied by LuaState::addCommonPackage + } + + sol::table initFieldGroup(const Context& context, const QueryFieldGroup& group) + { + sol::table res(context.mLua->sol(), sol::create); + for (const Queries::Field* field : group.mFields) + { + sol::table subgroup = res; + for (int i = 0; i < static_cast(field->path().size()) - 1; ++i) + { + const std::string& name = field->path()[i]; + if (subgroup[name] == sol::nil) + subgroup[name] = context.mLua->makeReadOnly(context.mLua->newTable()); + subgroup = context.mLua->getMutableFromReadOnly(subgroup[name]); + } + subgroup[field->path().back()] = field; + } + return context.mLua->makeReadOnly(res); + } + } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 251ed3998b..cb38e63fb7 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -5,27 +5,21 @@ #include #include +#include "context.hpp" #include "eventqueue.hpp" #include "object.hpp" +#include "query.hpp" #include "worldview.hpp" namespace MWLua { - class LuaManager; - - struct Context - { - LuaManager* mLuaManager; - LuaUtil::LuaState* mLua; - LuaUtil::UserdataSerializer* mSerializer; - WorldView* mWorldView; - LocalEventQueue* mLocalEventQueue; - GlobalEventQueue* mGlobalEventQueue; - }; sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); sol::table initNearbyPackage(const Context&); + sol::table initQueryPackage(const Context&); + + sol::table initFieldGroup(const Context&, const QueryFieldGroup&); // Implemented in objectbindings.cpp void initObjectBindingsForLocalScripts(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index b629e22ab2..4e7dcf2b24 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -31,6 +31,7 @@ namespace MWLua mGlobalScripts.setSerializer(mGlobalSerializer.get()); Context context; + context.mIsGlobal = true; context.mLuaManager = this; context.mLua = &mLua; context.mWorldView = &mWorldView; @@ -39,6 +40,7 @@ namespace MWLua context.mSerializer = mGlobalSerializer.get(); Context localContext = context; + localContext.mIsGlobal = false; localContext.mSerializer = mLocalSerializer.get(); initObjectBindingsForGlobalScripts(context); @@ -48,6 +50,7 @@ namespace MWLua mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context)); mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); mLua.addCommonPackage("openmw.core", initCorePackage(context)); + mLua.addCommonPackage("openmw.query", initQueryPackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); mCameraPackage = initCameraPackage(localContext); mUserInterfacePackage = initUserInterfacePackage(localContext); diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index dbe0a54480..8b4f9d52d3 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -1,6 +1,7 @@ #include "luabindings.hpp" #include +#include #include "../mwclass/door.hpp" @@ -64,6 +65,10 @@ namespace MWLua }; return std::make_tuple(iter, list, 0); }; + listT["select"] = [context](const ListT& list, const Queries::Query& query) + { + return ListT{selectObjectsFromList(query, list.mIds, context)}; + }; } template diff --git a/apps/openmw/mwlua/query.cpp b/apps/openmw/mwlua/query.cpp new file mode 100644 index 0000000000..f51b99d8d5 --- /dev/null +++ b/apps/openmw/mwlua/query.cpp @@ -0,0 +1,150 @@ +#include "query.hpp" + +#include + +#include + +#include "../mwclass/container.hpp" + +#include "worldview.hpp" + +namespace MWLua +{ + + static std::vector initBasicFieldGroups() + { + auto createGroup = [](std::string name, const auto& arr) -> QueryFieldGroup + { + std::vector fieldPtrs; + fieldPtrs.reserve(arr.size()); + for (const Queries::Field& field : arr) + fieldPtrs.push_back(&field); + return {std::move(name), std::move(fieldPtrs)}; + }; + static std::array objectFields = { + Queries::Field({"type"}, typeid(std::string)), + Queries::Field({"recordId"}, typeid(std::string)), + Queries::Field({"count"}, typeid(int32_t)), + }; + static std::array doorFields = { + Queries::Field({"isTeleport"}, typeid(bool)), + Queries::Field({"destCell"}, typeid(std::string)), + }; + return std::vector{ + createGroup("OBJECT", objectFields), + createGroup("DOOR", doorFields), + }; + } + + const std::vector& getBasicQueryFieldGroups() + { + static std::vector fieldGroups = initBasicFieldGroups(); + return fieldGroups; + } + + ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context& context) + { + if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0) + throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported"); + + ObjectIdList res = std::make_shared>(); + std::vector condStack; + auto compareFn = [](auto&& a, auto&& b, Queries::Condition::Type t) + { + switch (t) + { + case Queries::Condition::EQUAL: return a == b; + case Queries::Condition::NOT_EQUAL: return a != b; + case Queries::Condition::GREATER: return a > b; + case Queries::Condition::GREATER_OR_EQUAL: return a >= b; + case Queries::Condition::LESSER: return a < b; + case Queries::Condition::LESSER_OR_EQUAL: return a <= b; + default: + throw std::runtime_error("Unsupported condition type"); + } + }; + for (const ObjectId& id : *list) + { + if (static_cast(res->size()) == query.mLimit) + break; + sol::object obj; + MWWorld::Ptr ptr; + if (context.mIsGlobal) + { + GObject g(id, context.mWorldView->getObjectRegistry()); + if (!g.isValid()) continue; + ptr = g.ptr(); + obj = sol::make_object(context.mLua->sol(), g); + } + else + { + LObject l(id, context.mWorldView->getObjectRegistry()); + if (!l.isValid()) continue; + ptr = l.ptr(); + obj = sol::make_object(context.mLua->sol(), l); + } + const MWWorld::Class& cls = ptr.getClass(); + if (cls.isActivator() && query.mQueryType != ObjectQueryTypes::ACTIVATORS) + continue; + if (cls.isActor() && query.mQueryType != ObjectQueryTypes::ACTORS) + continue; + if (cls.isDoor() && query.mQueryType != ObjectQueryTypes::DOORS) + continue; + if (typeid(cls) == typeid(MWClass::Container) && query.mQueryType != ObjectQueryTypes::CONTAINERS) + continue; + + condStack.clear(); + for (const Queries::Operation& op : query.mFilter.mOperations) + { + switch(op.mType) + { + case Queries::Operation::PUSH: + { + const Queries::Condition& cond = query.mFilter.mConditions[op.mConditionIndex]; + sol::object fieldObj = obj; + for (const std::string& field : cond.mField->path()) + fieldObj = sol::table(fieldObj)[field]; + bool c; + if (cond.mField->type() == typeid(std::string)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(float)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(double)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(bool)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(int32_t)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(int64_t)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else + throw std::runtime_error("Unknown field type"); + condStack.push_back(c); + break; + } + case Queries::Operation::NOT: + condStack.back() = !condStack.back(); + break; + case Queries::Operation::AND: + { + bool v = condStack.back(); + condStack.pop_back(); + condStack.back() = condStack.back() && v; + break; + } + case Queries::Operation::OR: + { + bool v = condStack.back(); + condStack.pop_back(); + condStack.back() = condStack.back() || v; + break; + } + } + } + if (condStack.empty() || condStack.back() != 0) + res->push_back(id); + } + return res; + } + +} diff --git a/apps/openmw/mwlua/query.hpp b/apps/openmw/mwlua/query.hpp new file mode 100644 index 0000000000..6bb3fe09fa --- /dev/null +++ b/apps/openmw/mwlua/query.hpp @@ -0,0 +1,38 @@ +#ifndef MWLUA_QUERY_H +#define MWLUA_QUERY_H + +#include + +#include + +#include "context.hpp" +#include "object.hpp" + +namespace MWLua +{ + + struct ObjectQueryTypes + { + static constexpr std::string_view ACTIVATORS = "activators"; + static constexpr std::string_view ACTORS = "actors"; + static constexpr std::string_view CONTAINERS = "containers"; + static constexpr std::string_view DOORS = "doors"; + static constexpr std::string_view ITEMS = "items"; + + static constexpr std::string_view types[] = {ACTIVATORS, ACTORS, CONTAINERS, DOORS, ITEMS}; + }; + + struct QueryFieldGroup + { + std::string mName; + std::vector mFields; + }; + const std::vector& getBasicQueryFieldGroups(); + + // TODO: Implement custom fields. QueryFieldGroup registerCustomFields(...); + + ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context&); + +} + +#endif // MWLUA_QUERY_H diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index 89f5afa0c3..bf2b458792 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -40,6 +40,10 @@ namespace MWLua void objectAddedToScene(const MWWorld::Ptr& ptr); void objectRemovedFromScene(const MWWorld::Ptr& ptr); + // Returns list of objects that meets the `query` criteria. + // If onlyActive = true, then search only among the objects that are currently in the scene. + // TODO: ObjectIdList selectObjects(const Queries::Query& query, bool onlyActive); + void load(ESM::ESMReader& esm); void save(ESM::ESMWriter& esm) const; From f5722f9ba0c12096a238bece9a3664e9015b58e9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 23 Jan 2021 21:08:50 +0100 Subject: [PATCH 0988/2859] Inventory bindings --- apps/openmw/mwlua/localscripts.cpp | 4 + apps/openmw/mwlua/luabindings.cpp | 31 ++++++- apps/openmw/mwlua/objectbindings.cpp | 123 ++++++++++++++++++++++++++- 3 files changed, 154 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 573188c004..5b65c3f984 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -32,6 +32,10 @@ namespace MWLua selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.controlledFromLua = v; }; + selfAPI["setEquipment"] = [](const GObject& obj, const sol::table& equipment) + { + throw std::logic_error("Not implemented"); + }; selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional { const MWWorld::Ptr& ptr = self.ptr(); diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 8d9cbb8bee..76a01ee416 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -3,6 +3,8 @@ #include #include +#include "../mwworld/inventorystore.hpp" + #include "eventqueue.hpp" #include "worldview.hpp" @@ -19,19 +21,41 @@ namespace MWLua sol::table initCorePackage(const Context& context) { - sol::table api(context.mLua->sol(), sol::create); + auto* lua = context.mLua; + sol::table api(lua->sol(), sol::create); api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; api["getGameTimeInSeconds"] = [world=context.mWorldView]() { return world->getGameTimeInSeconds(); }; api["getGameTimeInHours"] = [world=context.mWorldView]() { return world->getGameTimeInHours(); }; - api["OBJECT_TYPE"] = definitionList(*context.mLua, + api["OBJECT_TYPE"] = definitionList(*lua, { "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", "Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon" }); - return context.mLua->makeReadOnly(api); + api["EQUIPMENT_SLOT"] = lua->makeReadOnly(lua->sol().create_table_with( + "Helmet", MWWorld::InventoryStore::Slot_Helmet, + "Cuirass", MWWorld::InventoryStore::Slot_Cuirass, + "Greaves", MWWorld::InventoryStore::Slot_Greaves, + "LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron, + "RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron, + "LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet, + "RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet, + "Boots", MWWorld::InventoryStore::Slot_Boots, + "Shirt", MWWorld::InventoryStore::Slot_Shirt, + "Pants", MWWorld::InventoryStore::Slot_Pants, + "Skirt", MWWorld::InventoryStore::Slot_Skirt, + "Robe", MWWorld::InventoryStore::Slot_Robe, + "LeftRing", MWWorld::InventoryStore::Slot_LeftRing, + "RightRing", MWWorld::InventoryStore::Slot_RightRing, + "Amulet", MWWorld::InventoryStore::Slot_Amulet, + "Belt", MWWorld::InventoryStore::Slot_Belt, + "CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight, + "CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft, + "Ammunition", MWWorld::InventoryStore::Slot_Ammunition + )); + return lua->makeReadOnly(api); } sol::table initWorldPackage(const Context& context) @@ -57,6 +81,7 @@ namespace MWLua // TODO: Use sqlite to search objects that are not in the scene // return GObjectList{worldView->selectObjects(query, false)}; }; + // TODO: add world.placeNewObject(recordId, cell, pos, [rot]) return context.mLua->makeReadOnly(api); } diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 8b4f9d52d3..3684521ceb 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -5,9 +5,21 @@ #include "../mwclass/door.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" + #include "eventqueue.hpp" #include "luamanagerimp.hpp" +namespace MWLua +{ + template + struct Inventory + { + ObjectT mObj; + }; +} + namespace sol { template <> @@ -18,6 +30,10 @@ namespace sol struct is_automagical : std::false_type {}; template <> struct is_automagical : std::false_type {}; + template <> + struct is_automagical> : std::false_type {}; + template <> + struct is_automagical> : std::false_type {}; } namespace MWLua @@ -88,6 +104,7 @@ namespace MWLua return o.ptr().getRefData().getPosition().asRotationVec3(); }); objectT["type"] = sol::readonly_property(&ObjectT::type); + objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; objectT[sol::meta_function::to_string] = &ObjectT::toString; objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) @@ -101,6 +118,13 @@ namespace MWLua { luaManager->addLocalScript(object.ptr(), path); }; + + objectT["teleport"] = [luaManager=context.mLuaManager](const GObject& object, std::string_view cell, + const osg::Vec3f& pos, const sol::optional& rot) + { + // TODO + throw std::logic_error("Not implemented"); + }; } } @@ -127,12 +151,110 @@ namespace MWLua }); } + template + static void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) + { + using InventoryT = Inventory; + sol::usertype inventoryT = context.mLua->sol().new_usertype(prefix + "Inventory"); + + objectT["getEquipment"] = [context](const ObjectT& o) + { + const MWWorld::Ptr& ptr = o.ptr(); + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + sol::table equipment(context.mLua->sol(), sol::create); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + auto it = store.getSlot(slot); + if (it == store.end()) + continue; + context.mWorldView->getObjectRegistry()->registerPtr(*it); + equipment[slot] = ObjectT(getId(*it), context.mWorldView->getObjectRegistry()); + } + return equipment; + }; + objectT["isEquipped"] = [](const ObjectT& actor, const ObjectT& item) + { + const MWWorld::Ptr& ptr = actor.ptr(); + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + return store.isEquipped(item.ptr()); + }; + + objectT["inventory"] = sol::readonly_property([](const ObjectT& o) { return InventoryT{o}; }); + inventoryT[sol::meta_function::to_string] = + [](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; }; + + auto getWithMask = [context](const InventoryT& inventory, int mask) + { + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + ObjectIdList list = std::make_shared>(); + auto it = store.begin(mask); + while (it.getType() != -1) + { + const MWWorld::Ptr& item = *(it++); + context.mWorldView->getObjectRegistry()->registerPtr(item); + list->push_back(getId(item)); + } + return ObjectList{list}; + }; + + inventoryT["getAll"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_All); }; + inventoryT["getPotions"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Potion); }; + inventoryT["getApparatuses"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Apparatus); }; + inventoryT["getArmor"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Armor); }; + inventoryT["getBooks"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Book); }; + inventoryT["getClothing"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Clothing); }; + inventoryT["getIngredients"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Ingredient); }; + inventoryT["getLights"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Light); }; + inventoryT["getLockpicks"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Lockpick); }; + inventoryT["getMiscellaneous"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Miscellaneous); }; + inventoryT["getProbes"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Probe); }; + inventoryT["getRepairKits"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Repair); }; + inventoryT["getWeapons"] = + [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Weapon); }; + + inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId) + { + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + return store.count(recordId); + }; + + if constexpr (std::is_same_v) + { // Only for global scripts + // TODO + objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {}; + objectT["setEquipment"] = [](const GObject& obj, const sol::table& equipment) {}; + + // obj.inventory:drop(obj2, [count]) + // obj.inventory:drop(recordId, [count]) + // obj.inventory:addNew(recordId, [count]) + // obj.inventory:remove(obj/recordId, [count]) + inventoryT["drop"] = [](const InventoryT& inventory) {}; + inventoryT["addNew"] = [](const InventoryT& inventory) {}; + inventoryT["remove"] = [](const InventoryT& inventory) {}; + } + } + template static void initObjectBindings(const std::string& prefix, const Context& context) { sol::usertype objectT = context.mLua->sol().new_usertype(prefix + "Object"); addBasicBindings(objectT, context); addDoorBindings(objectT, context); + addInventoryBindings(objectT, prefix, context); registerObjectList(prefix, context); } @@ -148,4 +270,3 @@ namespace MWLua } } - From 9746800eedfd25611049ea8d3719228c14a9eab7 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 23 Feb 2021 21:45:03 +0100 Subject: [PATCH 0989/2859] Documentation for Lua scripting --- docs/source/reference/index.rst | 3 +- docs/source/reference/lua-scripting/api.rst | 442 ++++++++++++++++++ docs/source/reference/lua-scripting/index.rst | 14 + .../reference/lua-scripting/overview.rst | 317 +++++++++++++ docs/source/reference/modding/extended.rst | 18 + 5 files changed, 793 insertions(+), 1 deletion(-) create mode 100644 docs/source/reference/lua-scripting/api.rst create mode 100644 docs/source/reference/lua-scripting/index.rst create mode 100644 docs/source/reference/lua-scripting/overview.rst diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index cd947745e1..aa6ff1d96f 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -6,4 +6,5 @@ Reference Material :maxdepth: 2 modding/index - documentationHowTo \ No newline at end of file + lua-scripting/index + documentationHowTo diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst new file mode 100644 index 0000000000..a7b153c039 --- /dev/null +++ b/docs/source/reference/lua-scripting/api.rst @@ -0,0 +1,442 @@ +################# +Lua API reference +################# + +.. toctree:: + :caption: Table of Contents + :maxdepth: 2 + +Engine handlers reference +========================= + +Engine handler is a function defined by a script, that can be called by the engine. + ++------------------------------------------------------------------------------------------------+ +| **Can be defined by any script** | ++----------------------------------+-------------------------------------------------------------+ +| onUpdate(dt) | | Called every frame if game not paused. `dt` is the time | +| | | from the last update in seconds. | ++----------------------------------+-------------------------------------------------------------+ +| onSave() -> data | Called when the game is saving. | ++----------------------------------+-------------------------------------------------------------+ +| onLoad(data) | Called on loading with the data previosly returned by onSave| ++----------------------------------+-------------------------------------------------------------+ +| **Only for global scripts** | ++----------------------------------+-------------------------------------------------------------+ +| onNewGame() | New game is started | ++----------------------------------+-------------------------------------------------------------+ +| onPlayerAdded(player) |Player added to game world. The argument is a `Game object`_.| ++----------------------------------+-------------------------------------------------------------+ +| onActorActive(actor) | Actor (NPC or Creature) becomes active. | ++----------------------------------+-------------------------------------------------------------+ +| **Only for local scripts attached to a player** | ++----------------------------------+-------------------------------------------------------------+ +| onKeyPress(symbol, modifiers) | | Key pressed. `Symbol` is an ASCII code, `modifiers` is | +| | | a binary OR of flags of special keys (ctrl, shift, alt). | ++----------------------------------+-------------------------------------------------------------+ + +.. _Game object: + +Game object reference +===================== + +Game object is a universal reference to an object in the game world. Anything that has a reference number. + +**Can be used on any object:** + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| object:isValid() | | Returns true if the object exists and loaded, | +| | | and false otherwise. If false, then every | +| | | access to the object will raise an error. | ++---------------------------------------------+--------------------------------------------------+ +| object:sendEvent(eventName, eventData) | Sends local event to the object. | ++---------------------------------------------+--------------------------------------------------+ +| object:isEquipped(item) | Returns true if `item` is equipped on `object`. | ++---------------------------------------------+--------------------------------------------------+ +| object:getEquipment() -> table | | Returns a table `slot` -> `object` of currently| +| | | equipped items. See `core.EQUIPMENT_SLOT`. | +| | | Returns empty table if `object` doesn't have | +| | | equipment slots. | ++---------------------------------------------+--------------------------------------------------+ +| object:setEquipment(table) | | Sets equipment. Keys in the table are equipment| +| | | slots (see `core.EQUIPMENT_SLOT`). Each value | +| | | can be either an object or `recordId`. Raises | +| | | an error if `object` doesn't have equipment | +| | | slots and `table` is not empty. Can be called | +| | | only on `self` or from a global script. | ++---------------------------------------------+--------------------------------------------------+ +| object:addScript(scriptPath) | | Adds new script to the object. | +| | | Can be called only from a global script. | ++---------------------------------------------+--------------------------------------------------+ +| object:teleport(cell, pos, [rot]) | | Moves object to given cell and position. | +| | | The effect is not immediate: the position will | +| | | be updated only in the next frame. | +| | | Can be called only from a global script. | ++---------------------------------------------+--------------------------------------------------+ + ++-----------------------+---------------------+--------------------------------------------------+ +| Field | Type | Description | ++=======================+=====================+==================================================+ +| object.position | vector3_ | Position | ++-----------------------+---------------------+--------------------------------------------------+ +| object.rotation | vector3_ | Rotation | ++-----------------------+---------------------+--------------------------------------------------+ +| object.cell | string | Cell | ++-----------------------+---------------------+--------------------------------------------------+ +| object.type | string | :ref:`Type ` of the object | ++-----------------------+---------------------+--------------------------------------------------+ +| object.count | integer | Count (makes sense if holded in a container) | ++-----------------------+---------------------+--------------------------------------------------+ +| object.recordId | string | Record ID | ++-----------------------+---------------------+--------------------------------------------------+ +| object.inventory | Inventory | Inventory of an actor or content of a container | ++-----------------------+---------------------+--------------------------------------------------+ + +**Can be used if object.type == 'Door':** + ++-----------------------+---------------------+--------------------------------------------------+ +| Field | Type | Description | ++=======================+=====================+==================================================+ +| object.isTeleport | boolean | True if it is a teleport door | ++-----------------------+---------------------+--------------------------------------------------+ +| object.destPosition | vector3_ | Destination (only if a teleport door) | ++-----------------------+---------------------+--------------------------------------------------+ +| object.destRotation | vector3_ | Destination rotation (only if a teleport door) | ++-----------------------+---------------------+--------------------------------------------------+ +| object.destCell | string | Destination cell (only if a teleport door) | ++-----------------------+---------------------+--------------------------------------------------+ + +Object type +----------- + +Type is represented as a string. Can be one of: + +- "Activator" +- "Armor" +- "Book" +- "Clothing" +- "Container" +- "Creature" +- "Door" +- "Ingredient" +- "Light" +- "Miscellaneous" +- "NPC" +- "Player" +- "Potion" +- "Static" +- "Weapon" + +ObjectList +---------- + +List of game objects. Can't be created or modified by a script. + +.. code-block:: Lua + + -- Iteration by index + for i = 1, #someList do + doSomething(someList[i]) + end + + -- Generic for (equivalent to iteration by index) + for i, item in someList:ipairs() do + doSomething(item) + end + + -- WRONG: for i, item in ipairs(someList) do + -- It doesn't work because Lua 5.1 doesn't allow to overload ipairs for userdata. + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| list:ipairs() | Returns an iterator | ++---------------------------------------------+--------------------------------------------------+ +| list:select(query) -> ObjectList | Returns a filtered list | ++---------------------------------------------+--------------------------------------------------+ + +Object inventory +---------------- + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| inv:countOf(recordId) -> int | The number of items with given recordId | ++---------------------------------------------+--------------------------------------------------+ +| inv:getAll(recordId) -> ObjectList_ | All contained items | ++---------------------------------------------+--------------------------------------------------+ +| inv:getPotions() -> ObjectList_ | All potions from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getApparatuses() -> ObjectList_ | All apparatuses from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getArmors() -> ObjectList_ | All armors from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getBooks() -> ObjectList_ | All books from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getClothing() -> ObjectList_ | All clothing from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getIngredients() -> ObjectList_ | All ingredients from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getLights() -> ObjectList_ | All lights from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getLockpicks() -> ObjectList_ | All lockpicks from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getMiscellaneous() -> ObjectList_ | All miscellaneous items from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getProbes() -> ObjectList_ | All probes from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getRepairKits() -> ObjectList_ | All repair kits from the inventory | ++---------------------------------------------+--------------------------------------------------+ +| inv:getWeapons() -> ObjectList_ | All weapon from the inventory | ++---------------------------------------------+--------------------------------------------------+ + +openmw.util +=========== + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| vector2(x, y) -> vector2_ | Creates 2D vector | ++---------------------------------------------+--------------------------------------------------+ +| vector3(x, y, z) -> vector3_ | Creates 3D vector | ++---------------------------------------------+--------------------------------------------------+ +| clamp(value, from, to) -> number | Limits given value to the interval [from, to] | ++---------------------------------------------+--------------------------------------------------+ +| normalizeAngle(radians) -> number | Adds 2pi*k and puts the angle in range [-pi, pi] | ++---------------------------------------------+--------------------------------------------------+ + +vector2 +------- + +Immutable 2D vector. + +.. code-block:: Lua + + v = vector2(3, 4) + v.x, v.y -- 3.0, 4.0 + str(v) -- "(3.0, 4.0)" + v:length() -- 5.0 length + v:length2() -- 25.0 square of the length + v:normalize() -- vector2(3/5, 4/5) + v:rotate(radians) -- rotate clockwise (returns rotated vector) + v1:dot(v2) -- dot product (returns a number) + v1 * v2 -- dot product + v1 + v2 -- vector addition + v1 - v2 -- vector subtraction + v1 * x -- multiplication by a number + v1 / x -- division by a number + +vector3 +------- + +.. code-block:: Lua + + v = vector3(3, 4, 5) + v.x, v.y, v.z -- 3.0, 4.0, 5.0 + str(v) -- "(3.0, 4.0, 4.5)" + v:length() -- length + v:length2() -- square of the length + v:normalize() -- normalized vector + v1:dot(v2) -- dot product (returns a number) + v1 * v2 -- dot product (returns a number) + v1:cross(v2) -- cross product (returns a vector) + v1 ^ v2 -- cross product (returns a vector) + v1 + v2 -- vector addition + v1 - v2 -- vector subtraction + v1 * x -- multiplication by a number + v1 / x -- division by a number + +openmw.core +=========== + ++-----------------------+---------------------+---------------------------------------------------+ +| Field | Type | Description | ++=======================+=====================+===================================================+ +| OBJECT_TYPE | Readonly table | Possible :ref:`object type ` values | ++-----------------------+---------------------+---------------------------------------------------+ +| EQUIPMENT_SLOTS | Readonly table | | Contains keys that can be used in | +| | | | `object:getEquipment` and `object:setEquipment`.| ++-----------------------+---------------------+---------------------------------------------------+ + +``EQUIPMENT_SLOTS`` contains fields: "Helmet", "Cuirass", "Greaves", "LeftPauldron", "RightPauldron", +"LeftGauntlet", "RightGauntlet", "Boots", "Shirt", "Pants", "Skirt", "Robe", "LeftRing", "RightRing", +"Amulet", "Belt", "CarriedRight", "CarriedLeft", "Ammunition". + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| sendGlobalEvent(eventName, eventData) | Sends an event to global scripts | ++---------------------------------------------+--------------------------------------------------+ +| getRealTime() | | The number of seconds (with floating point) | +| | | passed from 00:00 UTC 1 Januar 1970 (unix time)| ++---------------------------------------------+--------------------------------------------------+ +| getGameTimeInSeconds() | | The number of seconds in the game world, passed| +| | | from starting a new game. | ++---------------------------------------------+--------------------------------------------------+ +| getGameTimeInHours() | | Current time of the game world in hours. | +| | | Note that the number of game seconds in a game | +| | | hour is not guaranteed to be fixed. | ++---------------------------------------------+--------------------------------------------------+ + +openmw.async +============ + +Timers and coroutine utils. +All functions require the package itself as a first argument. +I.e. functions should be called with ``:`` rather than with ``.``. + ++-----------------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=====================================================+==================================================+ +| async:registerTimerCallback(name, func) -> Callback | Registers a function as a timer callback | ++-----------------------------------------------------+--------------------------------------------------+ +| async:newTimerInSeconds(delay, Callback, arg) | | Calls `Callback(arg)` in `delay` seconds. | +| | | `Callback` must be registered in advance. | ++-----------------------------------------------------+--------------------------------------------------+ +| async:newTimerInHours(delay, Callback, arg) | | Calls `Callback(arg)` in `delay` game hours. | +| | | `Callback` must be registered in advance. | ++-----------------------------------------------------+--------------------------------------------------+ +| async:newUnsavableTimerInSeconds(delay, func) | | Call `func()` in `delay` seconds. The timer | +| | | will be lost if the game is saved and loaded. | ++-----------------------------------------------------+--------------------------------------------------+ +| async:newUnsavableTimerInHours(delay, func) | | Call `func()` in `delay` game hours. The timer | +| | | will be lost if the game is saved and loaded. | ++-----------------------------------------------------+--------------------------------------------------+ + +openmw.query +============ + +**TODO** + +openmw.world +============ + +Interface to the game world. Can be used only by global scripts. + ++-----------------------+---------------------+--------------------------------------------------+ +| Field | Type | Description | ++=======================+=====================+==================================================+ +| activeActors | ObjectList_ | List of currently active actors | ++-----------------------+---------------------+--------------------------------------------------+ + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| selectObjects(query) -> ObjectList_ | Evaluates a query | ++---------------------------------------------+--------------------------------------------------+ + +openmw.nearby +============= + +Can be used only by local scripts. +Read-only access to the nearest area of the game world. + ++-----------------------+---------------------+--------------------------------------------------+ +| Field | Type | Description | ++=======================+=====================+==================================================+ +| activators | ObjectList_ | List of nearby activators | ++-----------------------+---------------------+--------------------------------------------------+ +| actors | ObjectList_ | List of nearby actors | ++-----------------------+---------------------+--------------------------------------------------+ +| containers | ObjectList_ | List of nearby containers | ++-----------------------+---------------------+--------------------------------------------------+ +| doors | ObjectList_ | List of nearby doors | ++-----------------------+---------------------+--------------------------------------------------+ +| items | ObjectList_ | Everything that can be picked up in the nearby | ++-----------------------+---------------------+--------------------------------------------------+ + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| selectObjects(query) -> ObjectList_ | Evaluates a query | ++---------------------------------------------+--------------------------------------------------+ + +openmw.self +=========== + +Can be used only by local scripts. Full access to the object the script is attached to. +All fields and function of `Game object`_ are also available for `openmw.self`. For example: + +.. code-block:: Lua + + local self = require('openmw.self') + if self.type == 'Player' then + self:sendEvent("something", self.position) + end + +Note that `self` is not a Game Object. If you need an actual object, use `self.object`: + +.. code-block:: Lua + + if self == someObject then ... -- Incorrect, this condition is always false + core.sendGlobalEvent('something', self) -- Incorrect, will raise an error + + if self.object == someObject then ... -- Correct + core.sendGlobalEvent('something', self.object) -- Correct + + ++-----------------------+---------------------+--------------------------------------------------+ +| Field | Type | Description | ++=======================+=====================+==================================================+ +| self.object | `Game object`_ | The object the script is attached to (readonly) | ++-----------------------+---------------------+--------------------------------------------------+ +| self.controls | `Actor controls`_ | Movement controls (only for actors) | ++-----------------------+---------------------+--------------------------------------------------+ + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| self:setDirectControl(bool) | Enables or disables direct movement control | ++---------------------------------------------+--------------------------------------------------+ +| self:setEquipment(table) | | Sets equipment. Keys in the table are equipment| +| | | slots (see `core.EQUIPMENT_SLOT`). Each value | +| | | can be either an object or `recordId`. Raises | +| | | an error if the object has no equipment | +| | | slots and `table` is not empty. | ++---------------------------------------------+--------------------------------------------------+ +| self:getCombatTarget() -> `Game object`_ | Returns current target or nil if not in combat | ++---------------------------------------------+--------------------------------------------------+ +| self:stopCombat() | Removes all combat packages from the actor | ++---------------------------------------------+--------------------------------------------------+ +| self:startCombat(target) | Attack `target` | ++---------------------------------------------+--------------------------------------------------+ + + +Actor controls +-------------- + +Allows to control movements of an actor. Makes an effect only if `setDirectControl(true)` was called. +All fields are mutable. + ++-----------------------+---------------------+--------------------------------------------------+ +| Field | Type | Description | ++=======================+=====================+==================================================+ +| movement | float number | +1 - move forward, -1 - move backward | ++-----------------------+---------------------+--------------------------------------------------+ +| sideMovement | float number | +1 - move right, -1 - move left | ++-----------------------+---------------------+--------------------------------------------------+ +| turn | float number | turn right (radians); if negative - turn left | ++-----------------------+---------------------+--------------------------------------------------+ +| run | boolean | true - run, false - walk | ++-----------------------+---------------------+--------------------------------------------------+ +| jump | boolean | if true - initiate a jump | ++-----------------------+---------------------+--------------------------------------------------+ + +openmw.ui +========= + +Can be used only by local scripts, that are attached to a player. + ++---------------------------------------------+--------------------------------------------------+ +| Function | Description | ++=============================================+==================================================+ +| showMessage(string) | Shows given message at the bottom of the screen. | ++---------------------------------------------+--------------------------------------------------+ + +openmw.camera +============= + +.. warning:: + Not implemented yet. diff --git a/docs/source/reference/lua-scripting/index.rst b/docs/source/reference/lua-scripting/index.rst new file mode 100644 index 0000000000..b779b8975c --- /dev/null +++ b/docs/source/reference/lua-scripting/index.rst @@ -0,0 +1,14 @@ +#################### +OpenMW Lua scripting +#################### + +.. warning:: + OpenMW Lua scripting is in early stage of development. Also note that OpenMW Lua is not compatible with MWSE. + +.. toctree:: + :caption: Table of Contents + :maxdepth: 2 + + overview + api + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst new file mode 100644 index 0000000000..119d4b14c4 --- /dev/null +++ b/docs/source/reference/lua-scripting/overview.rst @@ -0,0 +1,317 @@ +Overview of Lua scripting +######################### + +Language and sandboxing +======================= + +OpenMW supports scripts written in Lua 5.1. +There are no plans to switch to any newer version of the language, because newer versions are not supported by LuaJIT. + +Here are starting points for learning Lua: + +- `Programing in Lua `__ (first edition, aimed at Lua 5.0) +- `Lua 5.1 Reference Manual `__ + +Each script works in a separate sandbox and doesn't have any access to operation system. +Only limited list of allowed standard libraries can be used: +`coroutine `__, +`math `__, +`string `__, +`table `__. +These libraries are loaded automatically and are always available (except the function `math.randomseed` -- it is called by the engine on startup and not available from scripts). + +Allowed `basic functions `__: +``assert``, ``error``, ``ipairs``, ``next``, ``pairs``, ``pcall``, ``print``, ``tonumber``, ``tostring``, ``type``, ``unpack``, ``xpcall``, ``rawequal``, ``rawget``, ``rawset``, ``setmetatable``. + +Loading libraries with ``require('library_name')`` is allowed, but limited. It works this way: + +1. If `library_name` is one of the standard libraries, then return the library. +2. If `library_name` is one of the built-in `API packages`_, then return the package. +3. Otherwise search for a Lua source file with such name in :ref:`data folders `. For example ``require('my_lua_library.something')`` will try to open the file ``my_lua_library/something.lua``. + +Loading DLLs and precompiled Lua files is intentionally prohibited for reasons of safety and compatibility between different platforms. + +Basic concepts +============== + +Game object + Any object that exists in the game world and has a specific location. Player, actors, items, and statics are game objects. + +Record + Persistent information about an object. Includes starting stats and links to assets, but doesn't have a location. Game objects are instances of records. Some records (e.g. a unique NPC) have a single instance, some (e.g. a specific potion) may correspond to multiple objects. + +.. note:: + Don't be confused with MWSE terminology. In MWSE game objects are "references" and records are "objects". + +Cell + An area of the game world. A position in the world is a link to a cell and coordinates X, Y, Z in the cell. At a specific moment in time each cell can be active or inactive. Inactive cells don't perform physics updates. + +Global scripts + Lua scripts that are not attached to any game object and are always active. Global scripts can not be started or stopped during a game session. List of global scripts is defined by `omwscripts` files, that should be :ref:`registered ` in `openmw.cfg`. + +Local scripts + Lua scripts that are attached to some game object. A local script is active only if the object it is attached to is in an active cell. There are no limitations to the number of local scripts on one object. Local scripts can be attached to (or detached from) any object at any moment by a global script. + +Player scripts + It is a specific case of local scripts. *Player script* is a local script that is attached to a player. It can do everything that a normal local script can do, plus some player-specific functionality (e.g. control UI and camera). + +Scripting API was developed to be conceptually compatible with `multiplayer `__. In multiplayer the server is lightweight and delegates most of the work to clients. Each client processes some part of the game world. Global scripts are server-side and local scripts are client-side. It leads to several rules of Lua scripting API: + +1. A local script can see only some area of the game world (cells that are active on a specific client). Any data from inactive cells can't be used, as they are not synchronized and could be already changed on another client. +2. A local script can modify only the object it is attached to. Other objects can theoretically be processed by another client. To prevent synchronization problems the access to them is read only. +3. Global scripts can access and modify the whole game world including unloaded areas, but the global scripts API is different from the local scripts API and in some aspects limited, because it is not always possible to have all game assets in memory at the same time. +4. Though the scripting system doesn't actually work with multiplayer yet, the API assumes that there can be several players. That's why any command related to UI, camera, and everything else that is player-specific can be used only by player scripts. + + +How to run a script +=================== + +Let's write a simple example of a `Player script`: + +.. code-block:: Lua + + -- Saved to my_lua_mod/example/player.lua + + local ui = require('openmw.ui') + + return { + engineHandlers = { + onKeyPress = function(code, modifiers) + if code == string.byte('x') then + ui.showMessage('You have pressed "X"') + end + end + } + } + +In order to attach it to the player we also need a global script: + +.. code-block:: Lua + + -- Saved to my_lua_mod/example/global.lua + + return { + engineHandlers = { + onPlayerAdded = function(player) player:addScript('example/player.lua') end + } + } + +And one more file -- to start the global script: + +:: + + # Saved to my_lua_mod/my_lua_mod.omwscripts + + # It is just a list of global scripts to run. Each file is on a separate line. + example/global.lua + +Finally :ref:`register ` it in ``openmw.cfg``: + +:: + + data=path/to/my_lua_mod + lua-scripts=my_lua_mod.omwscripts + +Now every time the player presses "X" on a keyboard, a message is shown. + +Script structure +================ + +Each script is a separate file in game assets. +`Starting a script` means that the engine runs the file, parses the table it returns, and registers its interface, event handlers, and engine handlers. The handlers are permanent and exist until the script is stopped (if it is a local script, because global scripts can not be stopped). + +Here is an example of a basic script structure: + +.. code-block:: Lua + + local util = require('openmw.util') + + local function onUpdate(dt) + ... + end + + local function onSave() + ... + return data + end + + local function onLoad(data) + ... + end + + local function myEventHandler(eventData) + ... + end + + local function somePublicFunction(params, ...) + ... + end + + return { + name = 'MyScriptInterface', + interface = { + somePublicFunction = somePublicFunction, + }, + + eventHandlers = { MyEvent = myEventHandler }, + + engineHandlers = { + onUpdate = onUpdate, + onSave = onSave, + onLoad = onLoad, + } + } + + +.. note:: + Every instance of every script works in a separate enviroment, so it is not necessary + to make everything local. It's local just because it makes the code a bit faster. + +All sections in the returned table are optional. +If you just want to do something every frame, it is enough to write the following: + +.. code-block:: Lua + + return { + engineHandlers = { + onUpdate = function() + print('Hello, World!') + end + } + } + + +Engine handlers +=============== + +An engine handler is a function defined by a script, that can be called by the engine. I.e. it is an engine-to-script interaction. +Not visible for other scripts. If several scripts register an engine handler with the same name, +the engine calls all of them in the same order as the scripts were started. + +Some engine handlers are allowed only for global, or only for local/player scripts. Some are universal. +See :ref:`Engine handlers reference`. + + +onSave and onLoad +================= + +When game is saved or loaded, the engine calls engine handlers `onSave` or `onLoad` for every script. +The value that `onSave` returns will be passed to `onLoad` when the game is loaded. +It is the only way to save internal state of a script. All other script vatiables will be lost after closing the game. +The saved state must be :ref:`serializable `. + +`onSave` and `onLoad` are special: + +- Unlike all other engine handlers it is called even for objects in inactive cells. +- During saving and loading the environment may be not fully initialized, so these handlers shouldn't use any API calls. + +TODO: example, explain how global scripts are loading + +Serializable data +----------------- + +`Serializable` value means that OpenMW is able to convert it to a sequence of bytes and then (probably on a different computer and with different OpenMW version) restore it back to the same form. + +Serializable value is one of: + +- `nil` value +- a number +- a string +- a game object +- a value of a type, defined by :ref:`openmw.util` +- a table whith serializable keys and values + +Serializable data can not contain: + +- Functions +- Tables with custom metatables +- Several references to the same table. For example ``{ x = some_table, y = some_table }`` is not allowed. +- Circular references (i.e. when some table contains itself). + +API packages +============ + +API packages provide functions that can be called by scripts. I.e. it is a script-to-engine interaction. +A package can be loaded with ``require('')``. +It can not be overloaded even if there is a lua file with the same name. +The list of available packages is different for global and for local scripts. +Player scripts are local scripts that are attached to a player. + ++----------------------+--------------------+---------------------------------------------------------------+ +| Package | Can be used | Description | ++======================+====================+===============================================================+ +|:ref:`openmw.util` | everywhere | | Defines utility functions and classes like 3D vectors, | +| | | | that don't depend on the game world. | ++----------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.core` | everywhere | | Functions that are common for both global and local scripts | ++----------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.async` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | ++----------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.query` | everywhere | **TODO: write description** | ++----------------------+--------------------+---------------------------------------------------------------+ +|: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. | ++----------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.nearby` | by local scripts | | Read-only access to the nearest area of the game world. | ++----------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.ui` | by player scripts | | Controls user interface | ++----------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.camera` | by player scripts | | Controls camera (not implemented) | ++----------------------+--------------------+---------------------------------------------------------------+ + + +Script interfaces +================= + +.. warning:: + Not implemented yet. + +Each script can provide a named interface for other scripts. +It is a script-to-script interaction. This mechanism is not used by the engine itself. + +A script can use an interface of another script only if either both a global scripts, or both are local scripts on the same object. +In other cases events should be used. + +TODO: example, overloading + + +Event system +============ + +It is another way of script-to-script interactions. The differences: + +- Any script can send an event to any object or a global event to global scripts. +- Events are always delivered with a delay. +- Event handlers can not return any data to a sender. +- Event handlers have a single argument `eventData` (must be :ref:`serializable `) + +Events are the main way of interactions between local and global scripts. +It is not recommended to use for interactions between two global scripts, because in this case interfaces are more convenient. + +If several scripts register handlers for the same event, it will be called in the reversed order (opposite to engine handlers). +I.e. handler from the last attached script will be called first. +Return value 'false' in a handler means "skip all other handlers for this event". +Any other return value (including nil) means nothing. + +TODO: example + + +Timers +====== + +**TODO** + + +Queries +======= + +**TODO:** describe the concepts of `openmw.query` and `world.selectObjects`/`nearby.selectObjects`. + + +Using IDE for Lua scripting +=========================== + +.. warning:: + This section is not written yet. Later it will explain how to setup Lua Development Tools (eclipse-based IDE) with code autocompletion and integrated OpenMW API reference. + diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index cd739cb1b2..f107617b33 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -327,6 +327,24 @@ Also groundcover detection should be enabled via settings.cfg: [Groundcover] enabled = true +Lua scripting +------------- + +OpenMW supports Lua scripts. See :ref:`Lua scripting documentation `. +It is not compatible with MWSE. A mod with Lua scripts will work only if it was developed specifically for OpenMW. + +Mods can contain ``*.omwscripts`` files. They should be registered in the ``openmw.cfg`` via "lua-scripts" entries. The order of the "lua-scripts" entries can be important. If "some_lua_mod" uses API provided by "another_lua_mod", then omwscripts from "another_lua_mod" should be registered first. For example: + +:: + + data="path/to/another_lua_mod" + content=another_lua_mod.omwaddon + lua-scripts=another_lua_mod.omwscripts + + data="path/to/some_lua_mod" + content=some_lua_mod.omwaddon + lua-scripts=some_lua_mod.omwscripts + .. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 .. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 .. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886 From 8facf2952ab227ae8acafb19af88091d6d629924 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 14 Mar 2021 16:39:26 +0100 Subject: [PATCH 0990/2859] Documentation for lua package 'openmw.query' --- .../reference/lua-scripting/overview.rst | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 119d4b14c4..1603529ab5 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -247,7 +247,7 @@ Player scripts are local scripts that are attached to a player. +----------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +----------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.query` | everywhere | **TODO: write description** | +|:ref:`openmw.query` | everywhere | | Tools for constructing queries: base queries and fields. | +----------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.world` | by global scripts | | Read-write access to the game world. | +----------------------+--------------------+---------------------------------------------------------------+ @@ -306,7 +306,95 @@ Timers Queries ======= -**TODO:** describe the concepts of `openmw.query` and `world.selectObjects`/`nearby.selectObjects`. +`openmw.query` contains base queries of each type (e.g. `query.doors`, `query.containers`...), which return all of the objects of given type in no particular order. You can then modify that query to filter the results, sort them, group them, etc. Queries are immutable, so any operations on them return a new copy, leaving the original unchanged. + +`openmw.world.selectObjects` and `openmw.nearby.selectObjects` both accept a query and return objects that match it. However, `nearby.selectObjects` is only available in local scripts, and returns only objects from currently active cells, while `world.selectObjects` is only available in global scripts, and returns objects regardless of them being in active cells. +**TODO:** describe how to filter out inactive objects from world queries + +A minimal example of an object query: + +.. code-block:: Lua + + local query = require('openmw.query') + local nearby = require('openmw.nearby') + local ui = require('openmw.ui') + + local doorQuery = query.doors:orderBy(query.DOOR.destPosition.x) + + local function selectDoors(namePattern) + local query = doorQuery:where(query.DOOR.destCell:like(namePattern)) + return nearby.selectObjects(query) + end + + local function showGuildDoors() + ui.showMessage('Here are all the entrances to guilds!') + for _, door in selectDoors("%Guild%"):ipairs() do + local pos = door.position + local message = string.format("%.0f;%.0f;%.0f", pos.x, pos.y, pos.z) + ui.showMessage(message) + end + end + + return { + engineHandlers = { + onKeyPress = function(code, modifiers) + if code == string.byte('e') then + showGuildDoors() + end + end + } + } + +.. warning:: + The example above uses operation `like` that is not implemented yet. + +**TODO:** add non-object queries, explain how relations work, and define what a field is + +Queries are constructed through the following method calls: (if you've used SQL before, you will find them familiar) + +- `:where(filter)` - filters the results to match the combination of conditions passed as the argument +- `:orderBy(field)` and `:orderByDesc(field)` sort the result by the `field` argument. Sorts in descending order in case of `:orderByDesc`. Multiple calls can be chained, with the first call having priority. (i. e. if the first field is equal, objects are sorted by the second one...) +- `:groupBy(field)` returns only one result for each value of the `field` argument. The choice of the result is arbitrary. Useful for counting only unique objects, or checking if certain objects exist. +- `:limit(number)` will only return `number` of results (or fewer) +- `:offset(number)` skips the first `number` results. Particularly useful in combination with `:limit` + +Filters consist of conditions, which are combined with "and" (operator `*`), "or" (operator `+`), "not" (operator `-`) and braces `()`. + +To make a condition, take a field from the `openmw.query` package and call any of the following methods: + +- `:eq` equal to +- `:neq` not equal to +- `:gt` greater than +- `:gte` greater or equal to +- `:lt` less than +- `:lte` less or equal to +- `:like` matches a pattern. Only applicable to text (strings) + +**TODO:** describe the pattern format + +All the condition methods are type sensitive, and will throw an error if you pass a value of the wrong type into them. + +A few examples of filters: + +.. warning:: + `openmw.query.ACTOR` is not implemented yet + +.. code-block:: Lua + + local query = require('openmw.query') + local ACTOR = query.ACTOR + + local strong_guys_from_capital = (ACTOR.stats.level:gt(10) + ACTOR.stats.strength:gt(70)) + * ACTOR.cell.name:eq("Default city") + + -- could also write like this: + local strong_guys = ACTOR.stats.level:gt(10) + ACTOR.stats.strength:gt(70) + local guys_from_capital = ACTOR.cell.name:eq("Default city") + local strong_guys_from_capital_2 = strong_guys * guys_from_capital + + local DOOR = query.DOOR + + local interestingDoors = -DOOR.name:eq("") * DOOR.isTeleport:eq(true) * Door.destCell:neq("") Using IDE for Lua scripting From 9d09ecf8cacd26b80d87540a3acd7fdf43207480 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 28 Mar 2021 17:44:08 +0200 Subject: [PATCH 0991/2859] Add mwlua/actions --- apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwlua/actions.cpp | 140 +++++++++++++++++++++++++++ apps/openmw/mwlua/actions.hpp | 55 +++++++++++ apps/openmw/mwlua/localscripts.cpp | 21 +++- apps/openmw/mwlua/luamanagerimp.cpp | 20 ++++ apps/openmw/mwlua/luamanagerimp.hpp | 7 +- apps/openmw/mwlua/objectbindings.cpp | 54 +++++++++-- 7 files changed, 288 insertions(+), 13 deletions(-) create mode 100644 apps/openmw/mwlua/actions.cpp create mode 100644 apps/openmw/mwlua/actions.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index de55c36475..0cf609652b 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -56,8 +56,8 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwlua - luamanagerimp localscripts object worldview userdataserializer eventqueue query - luabindings objectbindings asyncbindings camerabindings uibindings + luamanagerimp actions object worldview userdataserializer eventqueue query + luabindings localscripts objectbindings asyncbindings camerabindings uibindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp new file mode 100644 index 0000000000..bc9cdc3aad --- /dev/null +++ b/apps/openmw/mwlua/actions.cpp @@ -0,0 +1,140 @@ +#include "actions.hpp" + +#include + +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" + +namespace MWLua +{ + + void TeleportAction::apply(WorldView& worldView) const + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + bool exterior = mCell.empty() || world->getExterior(mCell); + MWWorld::CellStore* cell; + if (exterior) + { + int cellX, cellY; + world->positionToIndex(mPos.x(), mPos.y(), cellX, cellY); + cell = world->getExterior(cellX, cellY); + } + else + cell = world->getInterior(mCell); + if (!cell) + { + Log(Debug::Error) << "LuaManager::applyTeleport -> cell not found: '" << mCell << "'"; + return; + } + + MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); + const MWWorld::Class& cls = obj.getClass(); + bool isPlayer = obj == world->getPlayerPtr(); + if (cls.isActor()) + cls.getCreatureStats(obj).land(isPlayer); + if (isPlayer) + { + ESM::Position esmPos; + static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); + std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); + std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); + world->getPlayer().setTeleported(true); + if (exterior) + world->changeToExteriorCell(esmPos, true); + else + world->changeToInteriorCell(mCell, esmPos, true); + } + else + { + MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos.x(), mPos.y(), mPos.z()); + world->rotateObject(newObj, mRot.x(), mRot.y(), mRot.z()); + worldView.getObjectRegistry()->registerPtr(newObj); + } + } + + void SetEquipmentAction::apply(WorldView& worldView) const + { + MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false); + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + std::array usedSlots; + std::fill(usedSlots.begin(), usedSlots.end(), false); + + constexpr int anySlot = -1; + auto tryEquipToSlot = [&](int slot, const Item& item) -> bool + { + auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); + MWWorld::Ptr itemPtr; + if (std::holds_alternative(item)) + { + itemPtr = worldView.getObjectRegistry()->getPtr(std::get(item), false); + if (old_it != store.end() && *old_it == itemPtr) + return true; // already equipped + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 || + itemPtr.getContainerStore() != static_cast(&store)) + { + Log(Debug::Warning) << "Object" << idToString(std::get(item)) << " is not in inventory"; + return false; + } + } + else + { + const std::string& recordId = std::get(item); + if (old_it != store.end() && *old_it->getCellRef().getRefIdPtr() == recordId) + return true; // already equipped + itemPtr = store.search(recordId); + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) + { + Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; + return false; + } + } + + auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); + bool requestedSlotIsAllowed = false; + for (int allowedSlot : allowedSlots) + requestedSlotIsAllowed = requestedSlotIsAllowed || allowedSlot == slot; + if (!requestedSlotIsAllowed) + { + slot = anySlot; + for (int allowedSlot : allowedSlots) + if (!usedSlots[allowedSlot]) + { + slot = allowedSlot; + break; + } + if (slot == anySlot) + { + Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); + return false; + } + } + + // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. + MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); + if (it == store.end()) // should never happen + throw std::logic_error("Item not found in container"); + + store.equip(slot, it, actor); + return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed + }; + + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + auto old_it = store.getSlot(slot); + auto new_it = mEquipment.find(slot); + if (new_it == mEquipment.end()) + { + if (old_it != store.end()) + store.unequipSlot(slot, actor); + continue; + } + if (tryEquipToSlot(slot, new_it->second)) + usedSlots[slot] = true; + } + for (auto [slot, item] : mEquipment) + if (slot >= MWWorld::InventoryStore::Slots) + tryEquipToSlot(anySlot, item); + } + +} diff --git a/apps/openmw/mwlua/actions.hpp b/apps/openmw/mwlua/actions.hpp new file mode 100644 index 0000000000..900b175320 --- /dev/null +++ b/apps/openmw/mwlua/actions.hpp @@ -0,0 +1,55 @@ +#ifndef MWLUA_ACTIONS_H +#define MWLUA_ACTIONS_H + +#include + +#include "object.hpp" +#include "worldview.hpp" + +namespace MWLua +{ + + // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), + // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited + // from MWLua::Action. + + class Action + { + public: + virtual ~Action() {} + virtual void apply(WorldView&) const = 0; + }; + + class TeleportAction final : public Action + { + public: + TeleportAction(ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) + : mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} + + void apply(WorldView&) const override; + + private: + ObjectId mObject; + std::string mCell; + osg::Vec3f mPos; + osg::Vec3f mRot; + }; + + class SetEquipmentAction final : public Action + { + public: + using Item = std::variant; // recordId or ObjectId + using Equipment = std::map; // slot to item + + SetEquipmentAction(ObjectId actor, Equipment equipment) : mActor(actor), mEquipment(std::move(equipment)) {} + + void apply(WorldView&) const override; + + private: + ObjectId mActor; + Equipment mEquipment; + }; + +} + +#endif // MWLUA_ACTIONS_H diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 5b65c3f984..473ffd3fef 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -5,6 +5,8 @@ #include "../mwmechanics/aisequence.hpp" #include "../mwmechanics/aicombat.hpp" +#include "luamanagerimp.hpp" + namespace sol { template <> @@ -32,9 +34,24 @@ namespace MWLua selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.controlledFromLua = v; }; - selfAPI["setEquipment"] = [](const GObject& obj, const sol::table& equipment) + selfAPI["setEquipment"] = [manager=context.mLuaManager](const SelfObject& obj, sol::table equipment) { - throw std::logic_error("Not implemented"); + if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) + { + if (!equipment.empty()) + throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); + return; + } + SetEquipmentAction::Equipment eqp; + for (auto& [key, value] : equipment) + { + int slot = key.as(); + if (value.is()) + eqp[slot] = value.as().id(); + else + eqp[slot] = value.as(); + } + manager->addAction(std::make_unique(obj.id(), std::move(eqp))); }; selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional { diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 4e7dcf2b24..fa885c88f5 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -98,6 +98,17 @@ namespace MWLua void LuaManager::update(bool paused, float dt) { + if (!mPlayer.isEmpty()) + { + MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (!(getId(mPlayer) == getId(newPlayerPtr))) + throw std::logic_error("Player Refnum was changed unexpectedly"); + if (!mPlayer.isInCell() || !newPlayerPtr.isInCell() || mPlayer.getCell() != newPlayerPtr.getCell()) + { + mPlayer = newPlayerPtr; + mWorldView.getObjectRegistry()->registerPtr(mPlayer); + } + } mWorldView.update(); if (paused) @@ -162,6 +173,14 @@ namespace MWLua for (const std::string& message : mUIMessages) windowManager->messageBox(message); mUIMessages.clear(); + + for (std::unique_ptr& action : mActionQueue) + action->apply(mWorldView); + mActionQueue.clear(); + + if (mTeleportPlayerAction) + mTeleportPlayerAction->apply(mWorldView); + mTeleportPlayerAction.reset(); } void LuaManager::clear() @@ -314,4 +333,5 @@ namespace MWLua scripts->load(data, true); scripts->setSerializer(mLocalSerializer.get()); } + } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index e8babc9695..d9934135ec 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -8,6 +8,7 @@ #include "../mwbase/luamanager.hpp" +#include "actions.hpp" #include "object.hpp" #include "eventqueue.hpp" #include "globalscripts.hpp" @@ -46,8 +47,10 @@ namespace MWLua void clear() override; // should be called before loading game or starting a new game to reset internal state. void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear". - // Used only in luabindings.cpp + // Used only in luabindings void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath); + void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } + void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } // Saving @@ -90,6 +93,8 @@ namespace MWLua std::vector mActorAddedEvents; // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). + std::vector> mActionQueue; + std::unique_ptr mTeleportPlayerAction; std::vector mUIMessages; }; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 3684521ceb..ca6fbae31f 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -95,6 +95,11 @@ namespace MWLua { return o.ptr().getCellRef().getRefId(); }); + objectT["cell"] = sol::readonly_property([](const ObjectT& o) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + return world->getCellName(o.ptr().getCell()); + }); objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asVec3(); @@ -120,10 +125,15 @@ namespace MWLua }; objectT["teleport"] = [luaManager=context.mLuaManager](const GObject& object, std::string_view cell, - const osg::Vec3f& pos, const sol::optional& rot) + const osg::Vec3f& pos, const sol::optional& optRot) { - // TODO - throw std::logic_error("Not implemented"); + MWWorld::Ptr ptr = object.ptr(); + osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); + auto action = std::make_unique(object.id(), std::string(cell), pos, rot); + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + luaManager->addTeleportPlayerAction(std::move(action)); + else + luaManager->addAction(std::move(action)); }; } } @@ -151,6 +161,20 @@ namespace MWLua }); } + static SetEquipmentAction::Equipment parseEquipmentTable(sol::table equipment) + { + SetEquipmentAction::Equipment eqp; + for (auto& [key, value] : equipment) + { + int slot = key.as(); + if (value.is()) + eqp[slot] = value.as().id(); + else + eqp[slot] = value.as(); + } + return eqp; + } + template static void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) { @@ -160,8 +184,11 @@ namespace MWLua objectT["getEquipment"] = [context](const ObjectT& o) { const MWWorld::Ptr& ptr = o.ptr(); - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); sol::table equipment(context.mLua->sol(), sol::create); + if (!ptr.getClass().hasInventoryStore(ptr)) + return equipment; + + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { auto it = store.getSlot(slot); @@ -175,6 +202,8 @@ namespace MWLua objectT["isEquipped"] = [](const ObjectT& actor, const ObjectT& item) { const MWWorld::Ptr& ptr = actor.ptr(); + if (!ptr.getClass().hasInventoryStore(ptr)) + return false; MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); return store.isEquipped(item.ptr()); }; @@ -234,17 +263,26 @@ namespace MWLua if constexpr (std::is_same_v) { // Only for global scripts - // TODO - objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {}; - objectT["setEquipment"] = [](const GObject& obj, const sol::table& equipment) {}; + objectT["setEquipment"] = [manager=context.mLuaManager](const GObject& obj, sol::table equipment) + { + if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) + { + if (!equipment.empty()) + throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); + return; + } + manager->addAction(std::make_unique(obj.id(), parseEquipmentTable(equipment))); + }; + // TODO // obj.inventory:drop(obj2, [count]) // obj.inventory:drop(recordId, [count]) // obj.inventory:addNew(recordId, [count]) // obj.inventory:remove(obj/recordId, [count]) + /*objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {}; inventoryT["drop"] = [](const InventoryT& inventory) {}; inventoryT["addNew"] = [](const InventoryT& inventory) {}; - inventoryT["remove"] = [](const InventoryT& inventory) {}; + inventoryT["remove"] = [](const InventoryT& inventory) {};*/ } } From d5cda6185519b8197960120b8bae0df82e57e1d9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 12 Apr 2021 03:15:17 +0200 Subject: [PATCH 0992/2859] Console command "reload lua" --- apps/openmw/mwbase/luamanager.hpp | 3 +++ apps/openmw/mwgui/console.cpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 26 +++++++++++++++++++ apps/openmw/mwlua/luamanagerimp.hpp | 3 +++ apps/openmw/mwlua/object.hpp | 1 + apps/openmw/mwscript/docs/vmformat.txt | 3 ++- apps/openmw/mwscript/miscextensions.cpp | 15 +++++++++-- components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 1 + .../reference/lua-scripting/overview.rst | 7 +++++ 10 files changed, 58 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 5b4594728e..b9109518cc 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -59,6 +59,9 @@ namespace MWBase // Should be called before loading. The map is used to fix refnums if the order of content files was changed. virtual void setContentFileMapping(const std::map&) = 0; + + // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. + virtual void reloadAllScripts() = 0; }; } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index b9004fcdbb..3bc2607045 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -20,6 +20,7 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index fa885c88f5..b961550280 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -334,4 +334,30 @@ namespace MWLua scripts->setSerializer(mLocalSerializer.get()); } + void LuaManager::reloadAllScripts() + { + Log(Debug::Info) << "Reload Lua"; + mLua.dropScriptCache(); + + { // Reload global scripts + ESM::LuaScripts data; + mGlobalScripts.save(data); + mGlobalScripts.removeAllScripts(); + for (const std::string& path : mGlobalScriptList) + if (mGlobalScripts.addNewScript(path)) + Log(Debug::Info) << "Global script restarted: " << path; + mGlobalScripts.load(data, false); + } + + for (const auto& [id, ptr] : mWorldView.getObjectRegistry()->mObjectMapping) + { // Reload local scripts + LocalScripts* scripts = ptr.getRefData().getLuaScripts(); + if (scripts == nullptr) + continue; + ESM::LuaScripts data; + scripts->save(data); + scripts->load(data, true); + } + } + } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index d9934135ec..275ee61614 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -62,6 +62,9 @@ namespace MWLua void loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) override; void setContentFileMapping(const std::map& mapping) override { mContentFileMapping = mapping; } + // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. + void reloadAllScripts() override; + private: LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index ac079fb38f..c4f1dc32f1 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -47,6 +47,7 @@ namespace MWLua friend class Object; friend class GObject; friend class LObject; + friend class LuaManager; bool mChanged = false; int64_t mUpdateCounter = 0; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 7c470fe413..aaba5e5986 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -480,5 +480,6 @@ op 0x200031d: StartScript, explicit op 0x200031e: GetDistance op 0x200031f: GetDistance, explicit op 0x2000320: Help +op 0x2000321: ReloadLua -opcodes 0x2000321-0x3ffffff unused +opcodes 0x2000322-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index ca9bcbe002..0b36d8bb48 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -28,6 +28,7 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -169,8 +170,6 @@ namespace MWScript void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - if(!ptr.isEmpty() && !ptr.mRef->mData.isEnabled()) - ptr.mRef->mData.mPhysicsPostponed = false; MWBase::Environment::get().getWorld()->enable (ptr); } }; @@ -1599,6 +1598,17 @@ namespace MWScript } }; + class OpReloadLua : public Interpreter::Opcode0 + { + public: + + void execute (Interpreter::Runtime& runtime) override + { + MWBase::Environment::get().getLuaManager()->reloadAllScripts(); + runtime.getContext().report("All Lua scripts are reloaded"); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeMenuMode, new OpMenuMode); @@ -1719,6 +1729,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMeExplicit, new OpRepairedOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeToggleRecastMesh, new OpToggleRecastMesh); interpreter.installSegment5 (Compiler::Misc::opcodeHelp, new OpHelp); + interpreter.installSegment5 (Compiler::Misc::opcodeReloadLua, new OpReloadLua); } } } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 6de35d7137..3dfcadab10 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -338,6 +338,7 @@ namespace Compiler extensions.registerFunction ("repairedonme", 'l', "S", opcodeRepairedOnMe, opcodeRepairedOnMeExplicit); extensions.registerInstruction ("togglerecastmesh", "", opcodeToggleRecastMesh); extensions.registerInstruction ("help", "", opcodeHelp); + extensions.registerInstruction ("reloadlua", "", opcodeReloadLua); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 46ee31133c..49adfbe209 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -319,6 +319,7 @@ namespace Compiler const int opcodeGetDisabledExplicit = 0x200031c; const int opcodeStartScriptExplicit = 0x200031d; const int opcodeHelp = 0x2000320; + const int opcodeReloadLua = 0x2000321; } namespace Sky diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 1603529ab5..f941c190a8 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -114,6 +114,13 @@ Finally :ref:`register ` it in ``openmw.cfg``: Now every time the player presses "X" on a keyboard, a message is shown. +Hot reloading +============= + +It is possible to modify a script without restarting OpenMW. To apply changes open in-game console and run the command ``reloadlua``. +It will restart all Lua scripts using `onSave and onLoad`_ handlers the same way as if the game was saved and loaded. +It works only with existing ``*.lua`` files that are not packed to any archives. Adding new scripts or modifying ``*.omwscripts`` files always requires restarting the game. + Script structure ================ From de349962d13f7b283e0480e518fa1810b40720da Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 14 Apr 2021 00:00:51 +0200 Subject: [PATCH 0993/2859] Engine handlers onActive/onInactive and a function self:isActive() --- apps/openmw/mwlua/localscripts.cpp | 13 +++++++++++++ apps/openmw/mwlua/localscripts.hpp | 9 ++++++++- apps/openmw/mwlua/luamanagerimp.cpp | 19 ++++++++++++++++++- apps/openmw/mwlua/luamanagerimp.hpp | 2 ++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 473ffd3fef..4b556faa4d 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -33,6 +33,7 @@ namespace MWLua selfAPI[sol::meta_function::to_string] = [](SelfObject& self) { return "openmw.self[" + self.toString() + "]"; }; selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); + selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.controlledFromLua = v; }; selfAPI["setEquipment"] = [manager=context.mLuaManager](const SelfObject& obj, sol::table equipment) { @@ -87,6 +88,18 @@ namespace MWLua { mData.mControls.controlledFromLua = false; this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); + registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers}); + } + + void LocalScripts::becomeActive() + { + mData.mIsActive = true; + callEngineHandlers(mOnActiveHandlers); + } + void LocalScripts::becomeInactive() + { + mData.mIsActive = false; + callEngineHandlers(mOnInactiveHandlers); } } diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index c8d85aad58..a738dfede5 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -26,12 +26,19 @@ namespace MWLua struct SelfObject : public LObject { - SelfObject(const LObject& obj) : LObject(obj) {} + SelfObject(const LObject& obj) : LObject(obj), mIsActive(false) {} MWBase::LuaManager::ActorControls mControls; + bool mIsActive; }; + + void becomeActive(); + void becomeInactive(); protected: LocalScripts(LuaUtil::LuaState* lua, const LObject& obj); SelfObject mData; + private: + EngineHandlerList mOnActiveHandlers{"onActive"}; + EngineHandlerList mOnInactiveHandlers{"onInactive"}; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index b961550280..09b3eb6e13 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -44,7 +44,9 @@ namespace MWLua localContext.mSerializer = mLocalSerializer.get(); initObjectBindingsForGlobalScripts(context); + initCellBindingsForGlobalScripts(context); initObjectBindingsForLocalScripts(localContext); + initCellBindingsForLocalScripts(localContext); LocalScripts::initializeSelfPackage(localContext); mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context)); @@ -158,6 +160,13 @@ namespace MWLua } mKeyPressEvents.clear(); + for (LocalScripts* localScripts : mObjectInactiveEvents) + localScripts->becomeInactive(); + for (LocalScripts* localScripts : mObjectActiveEvents) + localScripts->becomeActive(); + mObjectActiveEvents.clear(); + mObjectInactiveEvents.clear(); + for (ObjectId id : mActorAddedEvents) mGlobalScripts.actorActive(GObject(id, mWorldView.getObjectRegistry())); mActorAddedEvents.clear(); @@ -190,6 +199,8 @@ namespace MWLua mGlobalEvents.clear(); mKeyPressEvents.clear(); mActorAddedEvents.clear(); + mObjectActiveEvents.clear(); + mObjectInactiveEvents.clear(); mPlayerChanged = false; mPlayerScripts = nullptr; mWorldView.clear(); @@ -207,7 +218,10 @@ namespace MWLua LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (localScripts) + { mActiveLocalScripts.insert(localScripts); + mObjectActiveEvents.push_back(localScripts); + } if (ptr.getClass().isActor() && ptr != mPlayer) mActorAddedEvents.push_back(getId(ptr)); @@ -233,7 +247,10 @@ namespace MWLua mWorldView.objectRemovedFromScene(ptr); LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (localScripts) + { mActiveLocalScripts.erase(localScripts); + mObjectInactiveEvents.push_back(localScripts); + } // TODO: call mWorldView.objectUnloaded if object is unloaded from memory (does it ever happen?) and ptr becomes invalid. } @@ -242,7 +259,7 @@ namespace MWLua { mKeyPressEvents.push_back(arg.keysym); } - + const MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 275ee61614..53f2955675 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -94,6 +94,8 @@ namespace MWLua std::vector mKeyPressEvents; std::vector mActorAddedEvents; + std::vector mObjectActiveEvents; + std::vector mObjectInactiveEvents; // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). std::vector> mActionQueue; From c463f523907fad671f97238daa09ea0451d6bb0b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 17 Apr 2021 11:50:20 +0200 Subject: [PATCH 0994/2859] Add Cell Lua bindings --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/actions.cpp | 16 +-- apps/openmw/mwlua/cellbindings.cpp | 62 +++++++++ apps/openmw/mwlua/luabindings.cpp | 16 +++ apps/openmw/mwlua/luabindings.hpp | 17 +++ apps/openmw/mwlua/object.cpp | 11 +- apps/openmw/mwlua/object.hpp | 1 + apps/openmw/mwlua/objectbindings.cpp | 40 +++++- apps/openmw/mwlua/query.cpp | 198 ++++++++++++++++----------- apps/openmw/mwlua/query.hpp | 1 + apps/openmw/mwlua/worldview.cpp | 36 +++++ apps/openmw/mwlua/worldview.hpp | 4 + 12 files changed, 304 insertions(+), 100 deletions(-) create mode 100644 apps/openmw/mwlua/cellbindings.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0cf609652b..3c29baf3aa 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query - luabindings localscripts objectbindings asyncbindings camerabindings uibindings + luabindings localscripts objectbindings cellbindings asyncbindings camerabindings uibindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index bc9cdc3aad..00707b3d31 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -2,6 +2,7 @@ #include +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" @@ -11,23 +12,14 @@ namespace MWLua void TeleportAction::apply(WorldView& worldView) const { - MWBase::World* world = MWBase::Environment::get().getWorld(); - bool exterior = mCell.empty() || world->getExterior(mCell); - MWWorld::CellStore* cell; - if (exterior) - { - int cellX, cellY; - world->positionToIndex(mPos.x(), mPos.y(), cellX, cellY); - cell = world->getExterior(cellX, cellY); - } - else - cell = world->getInterior(mCell); + MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); if (!cell) { Log(Debug::Error) << "LuaManager::applyTeleport -> cell not found: '" << mCell << "'"; return; } + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); const MWWorld::Class& cls = obj.getClass(); bool isPlayer = obj == world->getPlayerPtr(); @@ -40,7 +32,7 @@ namespace MWLua std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); world->getPlayer().setTeleported(true); - if (exterior) + if (cell->isExterior()) world->changeToExteriorCell(esmPos, true); else world->changeToInteriorCell(mCell, esmPos, true); diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp new file mode 100644 index 0000000000..a23fb47c32 --- /dev/null +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -0,0 +1,62 @@ +#include "luabindings.hpp" + +#include + +#include "../mwworld/cellstore.hpp" + +namespace MWLua +{ + + template + static void initCellBindings(const std::string& prefix, const Context& context) + { + sol::usertype cellT = context.mLua->sol().new_usertype(prefix + "Cell"); + + cellT[sol::meta_function::equal_to] = [](const CellT& a, const CellT& b) { return a.mStore == b.mStore; }; + cellT[sol::meta_function::to_string] = [](const CellT& c) + { + const ESM::Cell* cell = c.mStore->getCell(); + std::stringstream res; + if (cell->isExterior()) + res << "exterior(" << cell->getGridX() << ", " << cell->getGridY() << ")"; + else + res << "interior(" << cell->mName << ")"; + return res.str(); + }; + + cellT["name"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->mName; }); + cellT["region"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->mRegion; }); + cellT["gridX"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridX(); }); + cellT["gridY"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridY(); }); + cellT["isExterior"] = sol::readonly_property([](const CellT& c) { return c.mStore->isExterior(); }); + cellT["hasWater"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->hasWater(); }); + + cellT["isInSameSpace"] = [](const CellT& c, const ObjectT& obj) + { + const MWWorld::Ptr& ptr = obj.ptr(); + if (!ptr.isInCell()) + return false; + MWWorld::CellStore* cell = ptr.getCell(); + return cell == c.mStore || (cell->isExterior() && c.mStore->isExterior()); + }; + + if constexpr (std::is_same_v) + { // only for global scripts + cellT["selectObjects"] = [context](const CellT& cell, const Queries::Query& query) + { + return GObjectList{selectObjectsFromCellStore(query, cell.mStore, context)}; + }; + } + } + + void initCellBindingsForLocalScripts(const Context& context) + { + initCellBindings("L", context); + } + + void initCellBindingsForGlobalScripts(const Context& context) + { + initCellBindings("G", context); + } + +} diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 76a01ee416..bc50062546 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -62,6 +62,22 @@ namespace MWLua { sol::table api(context.mLua->sol(), sol::create); WorldView* worldView = context.mWorldView; + api["getCellByName"] = [worldView=context.mWorldView](const std::string& name) -> sol::optional + { + MWWorld::CellStore* cell = worldView->findNamedCell(name); + if (cell) + return GCell{cell}; + else + return {}; + }; + api["getExteriorCell"] = [worldView=context.mWorldView](int x, int y) -> sol::optional + { + MWWorld::CellStore* cell = worldView->findExteriorCell(x, y); + if (cell) + return GCell{cell}; + else + return {}; + }; api["activeActors"] = GObjectList{worldView->getActorsInScene()}; api["selectObjects"] = [context](const Queries::Query& query) { diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index cb38e63fb7..ab83ce995f 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -11,6 +11,11 @@ #include "query.hpp" #include "worldview.hpp" +namespace MWWorld +{ + class CellStore; +} + namespace MWLua { @@ -25,6 +30,18 @@ namespace MWLua void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&); + // Implemented in cellbindings.cpp + struct LCell // for local scripts + { + MWWorld::CellStore* mStore; + }; + struct GCell // for global scripts + { + MWWorld::CellStore* mStore; + }; + void initCellBindingsForLocalScripts(const Context&); + void initCellBindingsForGlobalScripts(const Context&); + // Implemented in asyncbindings.cpp struct AsyncPackageId { diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 2eb8d16f3c..e3f5323736 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -49,12 +49,19 @@ namespace MWLua return fallback; } + bool isMarker(const MWWorld::Ptr& ptr) + { + std::string_view id = *ptr.getCellRef().getRefIdPtr(); + return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; + } + std::string_view getMWClassName(const MWWorld::Ptr& ptr) { if (*ptr.getCellRef().getRefIdPtr() == "player") return "Player"; - else - return getMWClassName(typeid(ptr.getClass()), ptr.getTypeName()); + if (isMarker(ptr)) + return "Marker"; + return getMWClassName(typeid(ptr.getClass()), ptr.getTypeName()); } std::string ptrToString(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index c4f1dc32f1..b45c8e247c 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -18,6 +18,7 @@ namespace MWLua inline const ObjectId& getId(const MWWorld::Ptr& ptr) { return ptr.getCellRef().getRefNum(); } std::string idToString(const ObjectId& id); std::string ptrToString(const MWWorld::Ptr& ptr); + bool isMarker(const MWWorld::Ptr& ptr); std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback = "Unknown"); std::string_view getMWClassName(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index ca6fbae31f..64ac84e4f0 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -39,6 +39,9 @@ namespace sol namespace MWLua { + template + using Cell = std::conditional_t, LCell, GCell>; + template static const MWWorld::Ptr& requireClass(const MWWorld::Ptr& ptr) { @@ -95,10 +98,13 @@ namespace MWLua { return o.ptr().getCellRef().getRefId(); }); - objectT["cell"] = sol::readonly_property([](const ObjectT& o) + objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional> { - MWBase::World* world = MWBase::Environment::get().getWorld(); - return world->getCellName(o.ptr().getCell()); + const MWWorld::Ptr& ptr = o.ptr(); + if (ptr.isInCell()) + return Cell{ptr.getCell()}; + else + return {}; }); objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f { @@ -117,6 +123,22 @@ namespace MWLua context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; + objectT["canMove"] = [context](const ObjectT& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getMaxSpeed(o.ptr()) > 0; + }; + objectT["getRunSpeed"] = [context](const ObjectT& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getRunSpeed(o.ptr()); + }; + objectT["getWalkSpeed"] = [context](const ObjectT& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getWalkSpeed(o.ptr()); + }; + if constexpr (std::is_same_v) { // Only for global scripts objectT["addScript"] = [luaManager=context.mLuaManager](const GObject& object, const std::string& path) @@ -155,9 +177,17 @@ namespace MWLua { return ptr(o).getCellRef().getDoorDest().asRotationVec3(); }); - objectT["destCell"] = sol::readonly_property([ptr](const ObjectT& o) -> std::string_view + objectT["destCell"] = sol::readonly_property( + [ptr, worldView=context.mWorldView](const ObjectT& o) -> sol::optional> { - return ptr(o).getCellRef().getDestCell(); + const MWWorld::CellRef& cellRef = ptr(o).getCellRef(); + if (!cellRef.getTeleport()) + return {}; + MWWorld::CellStore* cell = worldView->findCell(cellRef.getDestCell(), cellRef.getDoorDest().asVec3()); + if (cell) + return Cell{cell}; + else + return {}; }); } diff --git a/apps/openmw/mwlua/query.cpp b/apps/openmw/mwlua/query.cpp index f51b99d8d5..f42c7ae2d4 100644 --- a/apps/openmw/mwlua/query.cpp +++ b/apps/openmw/mwlua/query.cpp @@ -5,6 +5,7 @@ #include #include "../mwclass/container.hpp" +#include "../mwworld/cellstore.hpp" #include "worldview.hpp" @@ -24,11 +25,16 @@ namespace MWLua static std::array objectFields = { Queries::Field({"type"}, typeid(std::string)), Queries::Field({"recordId"}, typeid(std::string)), + Queries::Field({"cell", "name"}, typeid(std::string)), + Queries::Field({"cell", "region"}, typeid(std::string)), + Queries::Field({"cell", "isExterior"}, typeid(bool)), Queries::Field({"count"}, typeid(int32_t)), }; static std::array doorFields = { Queries::Field({"isTeleport"}, typeid(bool)), - Queries::Field({"destCell"}, typeid(std::string)), + Queries::Field({"destCell", "name"}, typeid(std::string)), + Queries::Field({"destCell", "region"}, typeid(std::string)), + Queries::Field({"destCell", "isExterior"}, typeid(bool)), }; return std::vector{ createGroup("OBJECT", objectFields), @@ -42,13 +48,8 @@ namespace MWLua return fieldGroups; } - ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context& context) + bool checkQueryConditions(const Queries::Query& query, const ObjectId& id, const Context& context) { - if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0) - throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported"); - - ObjectIdList res = std::make_shared>(); - std::vector condStack; auto compareFn = [](auto&& a, auto&& b, Queries::Condition::Type t) { switch (t) @@ -63,88 +64,125 @@ namespace MWLua throw std::runtime_error("Unsupported condition type"); } }; - for (const ObjectId& id : *list) + sol::object obj; + MWWorld::Ptr ptr; + if (context.mIsGlobal) { - if (static_cast(res->size()) == query.mLimit) - break; - sol::object obj; - MWWorld::Ptr ptr; - if (context.mIsGlobal) - { - GObject g(id, context.mWorldView->getObjectRegistry()); - if (!g.isValid()) continue; - ptr = g.ptr(); - obj = sol::make_object(context.mLua->sol(), g); - } - else - { - LObject l(id, context.mWorldView->getObjectRegistry()); - if (!l.isValid()) continue; - ptr = l.ptr(); - obj = sol::make_object(context.mLua->sol(), l); - } - const MWWorld::Class& cls = ptr.getClass(); - if (cls.isActivator() && query.mQueryType != ObjectQueryTypes::ACTIVATORS) - continue; - if (cls.isActor() && query.mQueryType != ObjectQueryTypes::ACTORS) - continue; - if (cls.isDoor() && query.mQueryType != ObjectQueryTypes::DOORS) - continue; - if (typeid(cls) == typeid(MWClass::Container) && query.mQueryType != ObjectQueryTypes::CONTAINERS) - continue; + GObject g(id, context.mWorldView->getObjectRegistry()); + if (!g.isValid()) + return false; + ptr = g.ptr(); + obj = sol::make_object(context.mLua->sol(), g); + } + else + { + LObject l(id, context.mWorldView->getObjectRegistry()); + if (!l.isValid()) + return false; + ptr = l.ptr(); + obj = sol::make_object(context.mLua->sol(), l); + } + if (ptr.getRefData().getCount() == 0) + return false; + // It is stupid, but "prisonmarker" has class "Door" despite that it is only an invisible marker. So we ignore all markers. + if (isMarker(ptr)) + return false; + const MWWorld::Class& cls = ptr.getClass(); + if (cls.isActivator() != (query.mQueryType == ObjectQueryTypes::ACTIVATORS)) + return false; + if (cls.isActor() != (query.mQueryType == ObjectQueryTypes::ACTORS)) + return false; + if (cls.isDoor() != (query.mQueryType == ObjectQueryTypes::DOORS)) + return false; + if ((typeid(cls) == typeid(MWClass::Container)) != (query.mQueryType == ObjectQueryTypes::CONTAINERS)) + return false; - condStack.clear(); - for (const Queries::Operation& op : query.mFilter.mOperations) + std::vector condStack; + for (const Queries::Operation& op : query.mFilter.mOperations) + { + switch(op.mType) { - switch(op.mType) + case Queries::Operation::PUSH: { - case Queries::Operation::PUSH: - { - const Queries::Condition& cond = query.mFilter.mConditions[op.mConditionIndex]; - sol::object fieldObj = obj; - for (const std::string& field : cond.mField->path()) - fieldObj = sol::table(fieldObj)[field]; - bool c; - if (cond.mField->type() == typeid(std::string)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(float)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(double)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(bool)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(int32_t)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(int64_t)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else - throw std::runtime_error("Unknown field type"); - condStack.push_back(c); - break; - } - case Queries::Operation::NOT: - condStack.back() = !condStack.back(); - break; - case Queries::Operation::AND: - { - bool v = condStack.back(); - condStack.pop_back(); - condStack.back() = condStack.back() && v; - break; - } - case Queries::Operation::OR: - { - bool v = condStack.back(); - condStack.pop_back(); - condStack.back() = condStack.back() || v; - break; - } + const Queries::Condition& cond = query.mFilter.mConditions[op.mConditionIndex]; + sol::object fieldObj = obj; + for (const std::string& field : cond.mField->path()) + fieldObj = LuaUtil::getFieldOrNil(fieldObj, field); + bool c; + if (fieldObj == sol::nil) + c = false; + else if (cond.mField->type() == typeid(std::string)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(float)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(double)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(bool)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(int32_t)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else if (cond.mField->type() == typeid(int64_t)) + c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); + else + throw std::runtime_error("Unknown field type"); + condStack.push_back(c); + break; + } + case Queries::Operation::NOT: + condStack.back() = !condStack.back(); + break; + case Queries::Operation::AND: + { + bool v = condStack.back(); + condStack.pop_back(); + condStack.back() = condStack.back() && v; + break; + } + case Queries::Operation::OR: + { + bool v = condStack.back(); + condStack.pop_back(); + condStack.back() = condStack.back() || v; + break; } } - if (condStack.empty() || condStack.back() != 0) + } + return condStack.empty() || condStack.back() != 0; + } + + ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context& context) + { + if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0) + throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported"); + + ObjectIdList res = std::make_shared>(); + for (const ObjectId& id : *list) + { + if (static_cast(res->size()) == query.mLimit) + break; + if (checkQueryConditions(query, id, context)) res->push_back(id); } return res; } + ObjectIdList selectObjectsFromCellStore(const Queries::Query& query, MWWorld::CellStore* store, const Context& context) + { + if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0) + throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported"); + + ObjectIdList res = std::make_shared>(); + auto visitor = [&](const MWWorld::Ptr& ptr) + { + if (static_cast(res->size()) == query.mLimit) + return false; + context.mWorldView->getObjectRegistry()->registerPtr(ptr); + if (checkQueryConditions(query, getId(ptr), context)) + res->push_back(getId(ptr)); + return static_cast(res->size()) != query.mLimit; + }; + store->forEach(std::move(visitor)); // TODO: maybe use store->forEachType depending on query.mType + return res; + } + } diff --git a/apps/openmw/mwlua/query.hpp b/apps/openmw/mwlua/query.hpp index 6bb3fe09fa..65bf0c5105 100644 --- a/apps/openmw/mwlua/query.hpp +++ b/apps/openmw/mwlua/query.hpp @@ -32,6 +32,7 @@ namespace MWLua // TODO: Implement custom fields. QueryFieldGroup registerCustomFields(...); ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context&); + ObjectIdList selectObjectsFromCellStore(const Queries::Query& query, MWWorld::CellStore* store, const Context&); } diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index 6e54efac39..beaa2d4770 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../mwclass/container.hpp" @@ -33,6 +34,10 @@ namespace MWLua WorldView::ObjectGroup* WorldView::chooseGroup(const MWWorld::Ptr& ptr) { + // It is important to check `isMarker` first. + // For example "prisonmarker" has class "Door" despite that it is only an invisible marker. + if (isMarker(ptr)) + return nullptr; const MWWorld::Class& cls = ptr.getClass(); if (cls.isActivator()) return &mActivatorsInScene; @@ -113,4 +118,35 @@ namespace MWLua group.mChanged = true; } + // TODO: If Lua scripts will use several threads at the same time, then `find*Cell` functions should have critical sections. + MWWorld::CellStore* WorldView::findCell(const std::string& name, osg::Vec3f position) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + bool exterior = name.empty() || world->getExterior(name); + if (exterior) + { + int cellX, cellY; + world->positionToIndex(position.x(), position.y(), cellX, cellY); + return world->getExterior(cellX, cellY); + } + else + return world->getInterior(name); + } + + MWWorld::CellStore* WorldView::findNamedCell(const std::string& name) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + const ESM::Cell* esmCell = world->getExterior(name); + if (esmCell) + return world->getExterior(esmCell->getGridX(), esmCell->getGridY()); + else + return world->getInterior(name); + } + + MWWorld::CellStore* WorldView::findExteriorCell(int x, int y) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + return world->getExterior(x, y); + } + } diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index bf2b458792..ea27e0ff84 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -44,6 +44,10 @@ namespace MWLua // If onlyActive = true, then search only among the objects that are currently in the scene. // TODO: ObjectIdList selectObjects(const Queries::Query& query, bool onlyActive); + MWWorld::CellStore* findCell(const std::string& name, osg::Vec3f position); + MWWorld::CellStore* findNamedCell(const std::string& name); + MWWorld::CellStore* findExteriorCell(int x, int y); + void load(ESM::ESMReader& esm); void save(ESM::ESMWriter& esm) const; From 7087eb1a4e4072d5f03d68a62abd8f010c5d3265 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 14 Apr 2021 23:37:57 +0200 Subject: [PATCH 0995/2859] Add built-in Lua library 'openmw_aux' that extends OpenMW Lua API --- files/builtin_scripts/openmw_aux/util.lua | 99 +++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 files/builtin_scripts/openmw_aux/util.lua diff --git a/files/builtin_scripts/openmw_aux/util.lua b/files/builtin_scripts/openmw_aux/util.lua new file mode 100644 index 0000000000..ba52ced2bd --- /dev/null +++ b/files/builtin_scripts/openmw_aux/util.lua @@ -0,0 +1,99 @@ +------------------------------------------------------------------------------- +-- `openmw_aux.util` defines utility functions that are implemented in Lua rather than in C++. +-- Implementation can be found in `resources/vfs/openmw_aux/util.lua`. +-- @module util +-- @usage local aux_util = require('openmw_aux.util') + +local aux_util = {} + +------------------------------------------------------------------------------- +-- Finds the nearest object to the given point in the given list. +-- Ignores cells, uses only coordinates. Returns the nearest object, +-- and the distance to it. If objectList is empty, returns nil. +-- @function [parent=#util] findNearestTo +-- @param openmw.util#Vector3 point +-- @param openmw.core#ObjectList objectList +-- @return openmw.core#GameObject, #number the nearest object and the distance +function aux_util.findNearestTo(point, objectList) + local res = nil + local resDist = nil + for i = 1, #objectList do + local obj = objectList[i] + local dist = (obj.position - point):length() + if i == 1 or dist < resDist then + res = obj + resDist = dist + end + end + return res, resDist +end + +------------------------------------------------------------------------------- +-- Runs given function every N game seconds (seconds when the game is not paused). +-- Note that loading a save stops the evaluation. If it should work always, call it in 2 places -- +-- when a script starts and in the engine handler `onLoad`. +-- @function [parent=#util] runEveryNSeconds +-- @param #number N interval in seconds +-- @param #function fn the function that should be called every N seconds +-- @param #number initialDelay optional argument -- delay in seconds before the first call. If missed then the delay is a random number in range [0, N]. Randomization is used for performance reasons -- to prevent all scripts from doing time consuming operations at the same time. +-- @return #function a function without arguments that can be used to stop the periodical evaluation. +-- @usage +-- local stopFn = aux_util.runEveryNSeconds(5, function() print('Test') end) -- print 'Test' every 5 seconds +-- stopFn() -- stop printing 'Test' +-- aux_util.runEveryNSeconds(5, function() print('Test2') end, 1) -- print 'Test' every 5 seconds starting from the next second +function aux_util.runEveryNSeconds(N, fn, initialDelay) + if N <= 0 then + error('Interval must be positive. If you want it to be as small '.. + 'as possible, use the engine handler `onUpdate` instead', 2) + end + local async = require('openmw.async') + local core = require('openmw.core') + local breakFlag = false + local initialDelay = initialDelay or math.random() * N + local baseTime = core.getGameTimeInSeconds() + initialDelay + local wrappedFn + wrappedFn = function() + if breakFlag then return end + fn() + local nextDelay = 1.5 * N - math.fmod(core.getGameTimeInSeconds() - baseTime + N / 2, N) + async:newUnsavableTimerInSeconds(nextDelay, wrappedFn) + end + async:newUnsavableTimerInSeconds(initialDelay, wrappedFn) + return function() breakFlag = true end +end + +------------------------------------------------------------------------------- +-- Runs given function every N game hours. +-- Note that loading a save stops the evaluation. If it should work always, call it in 2 places -- +-- when a script starts and in the engine handler `onLoad`. +-- @function [parent=#util] runEveryNHours +-- @param #number N interval in game hours +-- @param #function fn the function that should be called every N game hours +-- @param #number initialDelay optional argument -- delay in game hours before the first call. If missed then the delay is a random number in range [0, N]. Randomization is used for performance reasons -- to prevent all scripts from doing time consuming operations at the same time. +-- @return #function a function without arguments that can be used to stop the periodical evaluation. +-- @usage +-- local timeBeforeMidnight = 24 - math.fmod(core.getGameTimeInHours(), 24) +-- aux_util.runEveryNHours(24, doSomething, timeBeforeMidnight) -- call `doSomething` at the end of every game day. +function aux_util.runEveryNHours(N, fn, initialDelay) + if N <= 0 then + error('Interval must be positive. If you want it to be as small '.. + 'as possible, use the engine handler `onUpdate` instead', 2) + end + local async = require('openmw.async') + local core = require('openmw.core') + local breakFlag = false + local initialDelay = initialDelay or math.random() * N + local baseTime = core.getGameTimeInHours() + initialDelay + local wrappedFn + wrappedFn = function() + if breakFlag then return end + fn() + local nextDelay = 1.5 * N - math.fmod(core.getGameTimeInHours() - baseTime + N / 2, N) + async:newUnsavableTimerInHours(nextDelay, wrappedFn) + end + async:newUnsavableTimerInHours(initialDelay, wrappedFn) + return function() breakFlag = true end +end + +return aux_util + From a8f76260bc0519a9ba217d74da2523469ddee0c7 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 18 Apr 2021 22:09:01 +0200 Subject: [PATCH 0996/2859] A possibility to view actor controls from lua scripts without disabling AI. --- apps/openmw/mwbase/luamanager.hpp | 3 +- apps/openmw/mwlua/localscripts.cpp | 2 ++ apps/openmw/mwlua/localscripts.hpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 51 +++++++++++++++++------------ 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index b9109518cc..0d6aee38f1 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -34,6 +34,7 @@ namespace MWBase virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; struct ActorControls { + bool disableAI; bool controlledFromLua; bool jump; @@ -43,7 +44,7 @@ namespace MWBase float turn; }; - virtual const ActorControls* getActorControls(const MWWorld::Ptr&) const = 0; + virtual ActorControls* getActorControls(const MWWorld::Ptr&) const = 0; virtual void clear() = 0; virtual void setupPlayer(const MWWorld::Ptr&) = 0; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 4b556faa4d..5b5347af03 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -35,6 +35,7 @@ namespace MWLua selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.controlledFromLua = v; }; + selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.disableAI = !v; }; selfAPI["setEquipment"] = [manager=context.mLuaManager](const SelfObject& obj, sol::table equipment) { if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) @@ -87,6 +88,7 @@ namespace MWLua : LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj) { mData.mControls.controlledFromLua = false; + mData.mControls.disableAI = false; this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers}); } diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index a738dfede5..40bff371a1 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -22,7 +22,7 @@ namespace MWLua static std::unique_ptr create(LuaUtil::LuaState* lua, const LObject& obj); static void initializeSelfPackage(const Context&); - const MWBase::LuaManager::ActorControls* getActorControls() const { return &mData.mControls; } + MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; } struct SelfObject : public LObject { diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 09b3eb6e13..15e45eba1a 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -260,7 +260,7 @@ namespace MWLua mKeyPressEvents.push_back(arg.keysym); } - const MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const + MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 53f2955675..c0b08ee546 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -42,7 +42,7 @@ namespace MWLua void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; void keyPressed(const SDL_KeyboardEvent &arg) override; - const MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; + MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; void clear() override; // should be called before loading game or starting a new game to reset internal state. void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear". diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 9d3f16c31c..97d95251a4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1962,6 +1962,8 @@ namespace MWMechanics { bool isPlayer = iter->first == player; CharacterController* ctrl = iter->second->getCharacterController(); + MWBase::LuaManager::ActorControls* luaControls = + MWBase::Environment::get().getLuaManager()->getActorControls(iter->first); float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); // AI processing is only done within given distance to the player. @@ -2010,25 +2012,6 @@ namespace MWMechanics return; // for now abort update of the old cell when cell changes by teleportation magic effect // a better solution might be to apply cell changes at the end of the frame } - bool controlledFromLua; - { - const MWBase::LuaManager::ActorControls* luaControls = - MWBase::Environment::get().getLuaManager()->getActorControls(iter->first); - controlledFromLua = luaControls && luaControls->controlledFromLua; - if (controlledFromLua && isConscious(iter->first)) - { - Movement& mov = iter->first.getClass().getMovementSettings(iter->first); - mov.mPosition[0] = luaControls->sideMovement; - mov.mPosition[1] = luaControls->movement; - mov.mPosition[2] = luaControls->jump ? 1 : 0; - mov.mRotation[0] = mov.mRotation[1] = 0; - mov.mRotation[2] = luaControls->turn; - mov.mSpeedFactor = osg::Vec2(luaControls->movement, luaControls->sideMovement).length(); - - CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); - stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->run); - } - } if (aiActive && inProcessingRange) { if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed) @@ -2082,7 +2065,7 @@ namespace MWMechanics if (iter->first != player) { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); - if (isConscious(iter->first) && !controlledFromLua) + if (isConscious(iter->first) && !(luaControls && luaControls->disableAI)) { stats.getAiSequence().execute(iter->first, *ctrl, duration); updateGreetingState(iter->first, *iter->second, timerUpdateHello > 0); @@ -2091,7 +2074,7 @@ namespace MWMechanics } } } - else if (aiActive && iter->first != player && isConscious(iter->first) && !controlledFromLua) + else if (aiActive && iter->first != player && isConscious(iter->first) && !(luaControls && luaControls->disableAI)) { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); stats.getAiSequence().execute(iter->first, *ctrl, duration, /*outOfRange*/true); @@ -2108,6 +2091,32 @@ namespace MWMechanics if (timerUpdateEquippedLight == 0) updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); } + + if (luaControls && isConscious(iter->first)) + { + Movement& mov = iter->first.getClass().getMovementSettings(iter->first); + CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + float speedFactor = isPlayer ? 1.f : mov.mSpeedFactor; + osg::Vec2f movement = osg::Vec2f(mov.mPosition[0], mov.mPosition[1]) * speedFactor; + float rotationZ = mov.mRotation[2]; + bool jump = mov.mPosition[2] == 1; + bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run); + if (luaControls->controlledFromLua) + { + mov.mPosition[0] = luaControls->sideMovement; + mov.mPosition[1] = luaControls->movement; + mov.mPosition[2] = luaControls->jump ? 1 : 0; + mov.mRotation[1] = 0; + mov.mRotation[2] = luaControls->turn; + mov.mSpeedFactor = osg::Vec2(luaControls->movement, luaControls->sideMovement).length(); + stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->run); + } + luaControls->sideMovement = movement.x(); + luaControls->movement = movement.y(); + luaControls->turn = rotationZ; + luaControls->jump = jump; + luaControls->run = runFlag; + } } } From 403d31313c75c8d99d1e4804988e6dae6ddcd6bc Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 18 Apr 2021 16:50:37 +0200 Subject: [PATCH 0997/2859] New setting "lua num threads". Thread syncronization is changed from std::thread::yield to std::condition_variable. --- apps/openmw/engine.cpp | 111 +++++++++++++----- apps/openmw/engine.hpp | 1 + .../reference/modding/settings/index.rst | 1 + .../source/reference/modding/settings/lua.rst | 17 +++ files/settings-default.cfg | 7 ++ 5 files changed, 107 insertions(+), 30 deletions(-) create mode 100644 docs/source/reference/modding/settings/lua.rst diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index b9f499ffb4..71d169c0bc 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,6 +1,6 @@ #include "engine.hpp" -#include +#include #include #include #include @@ -830,6 +830,82 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mLuaManager->init(); } +class OMW::Engine::LuaWorker +{ +public: + explicit LuaWorker(Engine* engine) : + mEngine(engine), + mSeparateThread(Settings::Manager::getInt("lua num threads", "Lua") > 0) + { + if (mSeparateThread) + mThread = std::thread([this]{ threadBody(); }); + }; + + void allowUpdate(double dt) + { + mDt = dt; + if (!mSeparateThread) + return; + { + std::lock_guard lk(mMutex); + mUpdateRequest = true; + } + mCV.notify_one(); + } + + void finishUpdate() + { + if (mSeparateThread) + { + std::unique_lock lk(mMutex); + mCV.wait(lk, [&]{ return !mUpdateRequest; }); + } + else + update(); + mEngine->mLuaManager->applyQueuedChanges(); + }; + + void join() + { + if (mSeparateThread) + mThread.join(); + } + +private: + void update() + { + const auto& viewer = mEngine->mViewer; + const osg::Timer_t frameStart = viewer->getStartTick(); + const unsigned int frameNumber = viewer->getFrameStamp()->getFrameNumber(); + ScopedProfile profile(frameStart, frameNumber, *osg::Timer::instance(), *viewer->getViewerStats()); + + mEngine->mLuaManager->update(mEngine->mEnvironment.getWindowManager()->isGuiMode(), mDt); + } + + void threadBody() + { + while (!mEngine->mViewer->done() && !mEngine->mEnvironment.getStateManager()->hasQuitRequest()) + { + std::unique_lock lk(mMutex); + mCV.wait(lk, [&]{ return mUpdateRequest; }); + + update(); + + mUpdateRequest = false; + lk.unlock(); + mCV.notify_one(); + } + } + + Engine* mEngine; + const bool mSeparateThread; + std::mutex mMutex; + std::condition_variable mCV; + bool mUpdateRequest; + double mDt = 0; + std::thread mThread; +}; + // Initialise and enter main loop. void OMW::Engine::go() { @@ -912,27 +988,7 @@ void OMW::Engine::go() mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } - // Start Lua scripting thread - std::atomic_bool luaUpdateRequest; - double luaDt = 0; - std::thread scriptingThread([&]() { - const osg::Timer* const timer = osg::Timer::instance(); - osg::Stats* const stats = mViewer->getViewerStats(); - while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) - { - while (!luaUpdateRequest) - std::this_thread::yield(); - - { - const osg::Timer_t frameStart = mViewer->getStartTick(); - const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); - ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - - mLuaManager->update(mEnvironment.getWindowManager()->isGuiMode(), luaDt); - } - luaUpdateRequest = false; - } - }); + LuaWorker luaWorker(this); // starts a separate lua thread if "lua num threads" > 0 // Start the main rendering loop double simulationTime = 0.0; @@ -959,16 +1015,11 @@ void OMW::Engine::go() mEnvironment.getWorld()->updateWindowManager(); - // scriptingThread starts processing Lua scripts - luaDt = dt; - luaUpdateRequest = true; + luaWorker.allowUpdate(dt); // if there is a separate Lua thread, it starts the update now mViewer->renderingTraversals(); - // wait for scriptingThread to finish - while (luaUpdateRequest) - std::this_thread::yield(); - mLuaManager->applyQueuedChanges(); + luaWorker.finishUpdate(); bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); if (!guiActive) @@ -991,7 +1042,7 @@ void OMW::Engine::go() frameRateLimiter.limit(); } - scriptingThread.join(); + luaWorker.join(); // Save user settings settings.saveUser(settingspath); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 0645939694..180e06bcbc 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -194,6 +194,7 @@ namespace OMW private: Files::ConfigurationManager& mCfgMgr; + class LuaWorker; }; } diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index e9607fd9d1..b1cfad44ff 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -58,6 +58,7 @@ The ranges included with each setting are the physically possible ranges, not re HUD game general + lua shaders shadows input diff --git a/docs/source/reference/modding/settings/lua.rst b/docs/source/reference/modding/settings/lua.rst new file mode 100644 index 0000000000..4d1a3ca808 --- /dev/null +++ b/docs/source/reference/modding/settings/lua.rst @@ -0,0 +1,17 @@ +Lua Settings +############ + +lua num threads +--------------- + +:Type: integer +:Range: 0, 1 +:Default: 1 + +The maximum number of threads used for Lua scripts. +If zero, Lua scripts are processed in the main thread. +If one, a separate thread is used. +Values >1 are not yet supported. + +This setting can only be configured by editing the settings configuration file. + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 522762555d..d539ba34da 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1096,3 +1096,10 @@ stomp mode = 2 # 1 - Reduced levels. # 0 - Gentle levels. stomp intensity = 1 + +[Lua] + +# Set the maximum number of threads used for Lua scripts. +# If zero, Lua scripts are processed in the main thread. +lua num threads = 1 + From 12685976762294193c97d8ecec4f1ae778d25ebc Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 19 Apr 2021 13:33:25 +0200 Subject: [PATCH 0998/2859] Make loaded but inactive objects available in Lua scripts. --- apps/openmw/mwbase/luamanager.hpp | 2 + apps/openmw/mwlua/luamanagerimp.cpp | 67 ++++++++++++++++++----------- apps/openmw/mwlua/luamanagerimp.hpp | 2 + apps/openmw/mwworld/cellstore.cpp | 17 +++----- apps/openmw/mwworld/worldimp.cpp | 4 +- 5 files changed, 56 insertions(+), 36 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 0d6aee38f1..52d3102a45 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -31,6 +31,8 @@ namespace MWBase virtual void newGameStarted() = 0; virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; + virtual void registerObject(const MWWorld::Ptr& ptr) = 0; + virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0; virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; struct ActorControls { diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 15e45eba1a..f695e1245b 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -134,6 +134,7 @@ namespace MWLua scripts->processTimers(seconds, hours); } + // Receive events for (GlobalEvent& e : globalEvents) mGlobalScripts.receiveEvent(e.eventName, e.eventData); for (LocalEvent& e : localEvents) @@ -147,12 +148,7 @@ namespace MWLua << ". Object not found or has no attached scripts"; } - if (mPlayerChanged) - { - mPlayerChanged = false; - mGlobalScripts.playerAdded(GObject(getId(mPlayer), mWorldView.getObjectRegistry())); - } - + // Engine handlers in local scripts if (mPlayerScripts) { for (const SDL_Keysym key : mKeyPressEvents) @@ -167,13 +163,21 @@ namespace MWLua mObjectActiveEvents.clear(); mObjectInactiveEvents.clear(); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->update(dt); + + // Engine handlers in global scripts + if (mPlayerChanged) + { + mPlayerChanged = false; + mGlobalScripts.playerAdded(GObject(getId(mPlayer), mWorldView.getObjectRegistry())); + } + for (ObjectId id : mActorAddedEvents) mGlobalScripts.actorActive(GObject(id, mWorldView.getObjectRegistry())); mActorAddedEvents.clear(); mGlobalScripts.update(dt); - for (LocalScripts* scripts : mActiveLocalScripts) - scripts->update(dt); } void LuaManager::applyQueuedChanges() @@ -212,21 +216,6 @@ namespace MWLua } } - void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) - { - mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. - - LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); - if (localScripts) - { - mActiveLocalScripts.insert(localScripts); - mObjectActiveEvents.push_back(localScripts); - } - - if (ptr.getClass().isActor() && ptr != mPlayer) - mActorAddedEvents.push_back(getId(ptr)); - } - void LuaManager::setupPlayer(const MWWorld::Ptr& ptr) { if (!mPlayer.isEmpty()) @@ -239,9 +228,25 @@ namespace MWLua if (!mPlayerScripts) throw std::logic_error("mPlayerScripts not initialized"); mActiveLocalScripts.insert(mPlayerScripts); + mObjectActiveEvents.push_back(mPlayerScripts); mPlayerChanged = true; } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) + { + mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. + + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (localScripts) + { + mActiveLocalScripts.insert(localScripts); + mObjectActiveEvents.push_back(localScripts); + } + + if (ptr.getClass().isActor() && ptr != mPlayer) + mActorAddedEvents.push_back(getId(ptr)); + } + void LuaManager::objectRemovedFromScene(const MWWorld::Ptr& ptr) { mWorldView.objectRemovedFromScene(ptr); @@ -249,10 +254,19 @@ namespace MWLua if (localScripts) { mActiveLocalScripts.erase(localScripts); - mObjectInactiveEvents.push_back(localScripts); + if (!mWorldView.getObjectRegistry()->getPtr(getId(ptr), true).isEmpty()) + mObjectInactiveEvents.push_back(localScripts); } + } - // TODO: call mWorldView.objectUnloaded if object is unloaded from memory (does it ever happen?) and ptr becomes invalid. + void LuaManager::registerObject(const MWWorld::Ptr& ptr) + { + mWorldView.getObjectRegistry()->registerPtr(ptr); + } + + void LuaManager::deregisterObject(const MWWorld::Ptr& ptr) + { + mWorldView.getObjectRegistry()->deregisterPtr(ptr); } void LuaManager::keyPressed(const SDL_KeyboardEvent& arg) @@ -349,6 +363,9 @@ namespace MWLua scripts->setSerializer(mLocalLoader.get()); scripts->load(data, true); scripts->setSerializer(mLocalSerializer.get()); + + // LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered. + mWorldView.getObjectRegistry()->deregisterPtr(ptr); } void LuaManager::reloadAllScripts() diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index c0b08ee546..d3191e8428 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -40,6 +40,8 @@ namespace MWLua void newGameStarted() override { mGlobalScripts.newGameStarted(); } void objectAddedToScene(const MWWorld::Ptr& ptr) override; void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; + void registerObject(const MWWorld::Ptr& ptr) override; + void deregisterObject(const MWWorld::Ptr& ptr) override; void keyPressed(const SDL_KeyboardEvent &arg) override; MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 0d2ee85deb..53245be247 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -18,6 +18,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" @@ -195,6 +196,8 @@ namespace iter->mData.enable(); MWBase::Environment::get().getWorld()->disable(MWWorld::Ptr(&*iter, cellstore)); } + else + MWBase::Environment::get().getLuaManager()->registerObject(MWWorld::Ptr(&*iter, cellstore)); return; } @@ -206,6 +209,9 @@ namespace MWWorld::LiveCellRef ref (record); ref.load (state); collection.mList.push_back (ref); + + MWWorld::LiveCellRefBase* base = &collection.mList.back(); + MWBase::Environment::get().getLuaManager()->registerObject(MWWorld::Ptr(base, cellstore)); } } @@ -286,16 +292,7 @@ namespace MWWorld if (searchViaRefNum(object.getCellRef().getRefNum()).isEmpty()) throw std::runtime_error("moveTo: object is not in this cell"); - - // Objects with no refnum can't be handled correctly in the merging process that happens - // on a save/load, so do a simple copy & delete for these objects. - if (!object.getCellRef().getRefNum().hasContentFile()) - { - MWWorld::Ptr copied = object.getClass().copyToCell(object, *cellToMoveTo, object.getRefData().getCount()); - object.getRefData().setCount(0); - object.getRefData().setBaseNode(nullptr); - return copied; - } + MWBase::Environment::get().getLuaManager()->registerObject(MWWorld::Ptr(object.getBase(), cellToMoveTo)); MovedRefTracker::iterator found = mMovedHere.find(object.getBase()); if (found != mMovedHere.end()) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a7915dd30d..99bd53ef3f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -813,7 +813,8 @@ namespace MWWorld void World::enable (const Ptr& reference) { - // enable is a no-op for items in containers + MWBase::Environment::get().getLuaManager()->registerObject(reference); + if (!reference.isInCell()) return; @@ -864,6 +865,7 @@ namespace MWWorld if (reference == getPlayerPtr()) throw std::runtime_error("can not disable player object"); + MWBase::Environment::get().getLuaManager()->deregisterObject(reference); reference.getRefData().disable(); if (reference.getCellRef().getRefNum().hasContentFile()) From b1a6441c2329612eb30ba052b815797ae8b52b86 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 21 Apr 2021 04:11:11 +0200 Subject: [PATCH 0999/2859] Add LocalScripts::LocalEngineEvent. Add `OnConsume` engine handler. --- apps/openmw/mwbase/luamanager.hpp | 11 +++++-- apps/openmw/mwclass/npc.cpp | 2 ++ apps/openmw/mwlua/eventqueue.cpp | 8 ++--- apps/openmw/mwlua/eventqueue.hpp | 10 +++--- apps/openmw/mwlua/localscripts.cpp | 30 ++++++++++++------ apps/openmw/mwlua/localscripts.hpp | 13 ++++++-- apps/openmw/mwlua/luamanagerimp.cpp | 49 ++++++++++++++++++----------- apps/openmw/mwlua/luamanagerimp.hpp | 10 ++++-- 8 files changed, 90 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 52d3102a45..24c1780984 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -29,11 +29,16 @@ namespace MWBase virtual ~LuaManager() = default; virtual void newGameStarted() = 0; - virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; - virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; + virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; + virtual void registerObject(const MWWorld::Ptr& ptr) = 0; virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0; - virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; + virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; + virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; + virtual void appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) = 0; + // TODO: notify LuaManager about other events + // virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, + // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0; struct ActorControls { bool disableAI; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6131f86269..5bf81caf9d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -17,6 +17,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -1095,6 +1096,7 @@ namespace MWClass bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const { + MWBase::Environment::get().getLuaManager()->appliedToObject(ptr, id, actor); MWMechanics::CastSpell cast(ptr, ptr); return cast.cast(id); } diff --git a/apps/openmw/mwlua/eventqueue.cpp b/apps/openmw/mwlua/eventqueue.cpp index 5e63fee57a..1c136551c4 100644 --- a/apps/openmw/mwlua/eventqueue.cpp +++ b/apps/openmw/mwlua/eventqueue.cpp @@ -14,10 +14,10 @@ namespace MWLua template void saveEvent(ESM::ESMWriter& esm, const ObjectId& dest, const Event& event) { - esm.writeHNString("LUAE", event.eventName); + esm.writeHNString("LUAE", event.mEventName); dest.save(esm, true); - if (!event.eventData.empty()) - saveLuaBinaryData(esm, event.eventData); + if (!event.mEventData.empty()) + saveLuaBinaryData(esm, event.mEventData); } void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents, LocalEventQueue& localEvents, @@ -57,7 +57,7 @@ namespace MWLua for (const GlobalEvent& e : globalEvents) saveEvent(esm, globalId, e); for (const LocalEvent& e : localEvents) - saveEvent(esm, e.dest, e); + saveEvent(esm, e.mDest, e); } } diff --git a/apps/openmw/mwlua/eventqueue.hpp b/apps/openmw/mwlua/eventqueue.hpp index b692d7162d..0e5f2dfcb4 100644 --- a/apps/openmw/mwlua/eventqueue.hpp +++ b/apps/openmw/mwlua/eventqueue.hpp @@ -23,14 +23,14 @@ namespace MWLua { struct GlobalEvent { - std::string eventName; - std::string eventData; + std::string mEventName; + std::string mEventData; }; struct LocalEvent { - ObjectId dest; - std::string eventName; - std::string eventData; + ObjectId mDest; + std::string mEventName; + std::string mEventData; }; using GlobalEventQueue = std::vector; using LocalEventQueue = std::vector; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 5b5347af03..e4c50334d4 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -90,18 +90,30 @@ namespace MWLua mData.mControls.controlledFromLua = false; mData.mControls.disableAI = false; this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); - registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers}); + registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers}); } - void LocalScripts::becomeActive() + void LocalScripts::receiveEngineEvent(const EngineEvent& event, ObjectRegistry*) { - mData.mIsActive = true; - callEngineHandlers(mOnActiveHandlers); - } - void LocalScripts::becomeInactive() - { - mData.mIsActive = false; - callEngineHandlers(mOnInactiveHandlers); + std::visit([this](auto&& arg) + { + using EventT = std::decay_t; + if constexpr (std::is_same_v) + { + mData.mIsActive = true; + callEngineHandlers(mOnActiveHandlers); + } + else if constexpr (std::is_same_v) + { + mData.mIsActive = false; + callEngineHandlers(mOnInactiveHandlers); + } + else + { + static_assert(std::is_same_v); + callEngineHandlers(mOnConsumeHandlers, arg.mRecordId); + } + }, event); } } diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 40bff371a1..49612be441 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -31,14 +31,23 @@ namespace MWLua bool mIsActive; }; - void becomeActive(); - void becomeInactive(); + struct OnActive {}; + struct OnInactive {}; + struct OnConsume + { + std::string mRecordId; + }; + using EngineEvent = std::variant; + + void receiveEngineEvent(const EngineEvent&, ObjectRegistry*); + protected: LocalScripts(LuaUtil::LuaState* lua, const LObject& obj); SelfObject mData; private: EngineHandlerList mOnActiveHandlers{"onActive"}; EngineHandlerList mOnInactiveHandlers{"onInactive"}; + EngineHandlerList mOnConsumeHandlers{"onConsume"}; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index f695e1245b..604dde8495 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -100,6 +100,8 @@ namespace MWLua void LuaManager::update(bool paused, float dt) { + ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); + if (!mPlayer.isEmpty()) { MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -108,7 +110,7 @@ namespace MWLua if (!mPlayer.isInCell() || !newPlayerPtr.isInCell() || mPlayer.getCell() != newPlayerPtr.getCell()) { mPlayer = newPlayerPtr; - mWorldView.getObjectRegistry()->registerPtr(mPlayer); + objectRegistry->registerPtr(mPlayer); } } mWorldView.update(); @@ -136,15 +138,15 @@ namespace MWLua // Receive events for (GlobalEvent& e : globalEvents) - mGlobalScripts.receiveEvent(e.eventName, e.eventData); + mGlobalScripts.receiveEvent(e.mEventName, e.mEventData); for (LocalEvent& e : localEvents) { - LObject obj(e.dest, mWorldView.getObjectRegistry()); + LObject obj(e.mDest, objectRegistry); LocalScripts* scripts = obj.isValid() ? obj.ptr().getRefData().getLuaScripts() : nullptr; if (scripts) - scripts->receiveEvent(e.eventName, e.eventData); + scripts->receiveEvent(e.mEventName, e.mEventData); else - Log(Debug::Debug) << "Ignored event " << e.eventName << " to L" << idToString(e.dest) + Log(Debug::Debug) << "Ignored event " << e.mEventName << " to L" << idToString(e.mDest) << ". Object not found or has no attached scripts"; } @@ -156,12 +158,19 @@ namespace MWLua } mKeyPressEvents.clear(); - for (LocalScripts* localScripts : mObjectInactiveEvents) - localScripts->becomeInactive(); - for (LocalScripts* localScripts : mObjectActiveEvents) - localScripts->becomeActive(); - mObjectActiveEvents.clear(); - mObjectInactiveEvents.clear(); + for (const LocalEngineEvent& e : mLocalEngineEvents) + { + LObject obj(e.mDest, objectRegistry); + if (!obj.isValid()) + { + Log(Debug::Verbose) << "Can not call engine handlers: object" << idToString(e.mDest) << " is not found"; + continue; + } + LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + if (scripts) + scripts->receiveEngineEvent(e.mEvent, objectRegistry); + } + mLocalEngineEvents.clear(); for (LocalScripts* scripts : mActiveLocalScripts) scripts->update(dt); @@ -170,11 +179,11 @@ namespace MWLua if (mPlayerChanged) { mPlayerChanged = false; - mGlobalScripts.playerAdded(GObject(getId(mPlayer), mWorldView.getObjectRegistry())); + mGlobalScripts.playerAdded(GObject(getId(mPlayer), objectRegistry)); } for (ObjectId id : mActorAddedEvents) - mGlobalScripts.actorActive(GObject(id, mWorldView.getObjectRegistry())); + mGlobalScripts.actorActive(GObject(id, objectRegistry)); mActorAddedEvents.clear(); mGlobalScripts.update(dt); @@ -203,8 +212,7 @@ namespace MWLua mGlobalEvents.clear(); mKeyPressEvents.clear(); mActorAddedEvents.clear(); - mObjectActiveEvents.clear(); - mObjectInactiveEvents.clear(); + mLocalEngineEvents.clear(); mPlayerChanged = false; mPlayerScripts = nullptr; mWorldView.clear(); @@ -228,7 +236,7 @@ namespace MWLua if (!mPlayerScripts) throw std::logic_error("mPlayerScripts not initialized"); mActiveLocalScripts.insert(mPlayerScripts); - mObjectActiveEvents.push_back(mPlayerScripts); + mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}}); mPlayerChanged = true; } @@ -240,7 +248,7 @@ namespace MWLua if (localScripts) { mActiveLocalScripts.insert(localScripts); - mObjectActiveEvents.push_back(localScripts); + mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}}); } if (ptr.getClass().isActor() && ptr != mPlayer) @@ -255,7 +263,7 @@ namespace MWLua { mActiveLocalScripts.erase(localScripts); if (!mWorldView.getObjectRegistry()->getPtr(getId(ptr), true).isEmpty()) - mObjectInactiveEvents.push_back(localScripts); + mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnInactive{}}); } } @@ -274,6 +282,11 @@ namespace MWLua mKeyPressEvents.push_back(arg.keysym); } + void LuaManager::appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) + { + mLocalEngineEvents.push_back({getId(toPtr), LocalScripts::OnConsume{std::string(recordId)}}); + } + MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index d3191e8428..5cfadebc40 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -43,6 +43,7 @@ namespace MWLua void registerObject(const MWWorld::Ptr& ptr) override; void deregisterObject(const MWWorld::Ptr& ptr) override; void keyPressed(const SDL_KeyboardEvent &arg) override; + void appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) override; MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; @@ -96,8 +97,13 @@ namespace MWLua std::vector mKeyPressEvents; std::vector mActorAddedEvents; - std::vector mObjectActiveEvents; - std::vector mObjectInactiveEvents; + + struct LocalEngineEvent + { + ObjectId mDest; + LocalScripts::EngineEvent mEvent; + }; + std::vector mLocalEngineEvents; // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). std::vector> mActionQueue; From cc7dbabd191c8e64ec420fe11cb1c8cab212934a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 9 Jul 2021 20:18:23 +0200 Subject: [PATCH 1000/2859] Change argument of `onKeyPress` --- apps/openmw/mwlua/luabindings.cpp | 20 ++++++++++++++++++++ apps/openmw/mwlua/luabindings.hpp | 2 ++ apps/openmw/mwlua/luamanagerimp.cpp | 5 +++-- apps/openmw/mwlua/playerscripts.hpp | 4 +++- components/lua/scriptscontainer.hpp | 2 +- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index bc50062546..5ed5378c0a 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -1,5 +1,7 @@ #include "luabindings.hpp" +#include + #include #include @@ -8,6 +10,12 @@ #include "eventqueue.hpp" #include "worldview.hpp" +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + namespace MWLua { @@ -160,5 +168,17 @@ namespace MWLua return context.mLua->makeReadOnly(res); } + void initInputBindings(const Context& context) + { + sol::usertype keyEvent = context.mLua->sol().new_usertype("KeyEvent"); + keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { return std::string(1, static_cast(e.sym)); }); + keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.sym; }); + keyEvent["modifiers"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.mod; }); + keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; }); + keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; }); + keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); + keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); + } + } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index ab83ce995f..8be96763a5 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -26,6 +26,8 @@ namespace MWLua sol::table initFieldGroup(const Context&, const QueryFieldGroup&); + void initInputBindings(const Context&); + // Implemented in objectbindings.cpp void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 604dde8495..bd07c191f9 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -48,6 +48,7 @@ namespace MWLua initObjectBindingsForLocalScripts(localContext); initCellBindingsForLocalScripts(localContext); LocalScripts::initializeSelfPackage(localContext); + initInputBindings(localContext); mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context)); mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); @@ -153,8 +154,8 @@ namespace MWLua // Engine handlers in local scripts if (mPlayerScripts) { - for (const SDL_Keysym key : mKeyPressEvents) - mPlayerScripts->keyPress(key.sym, key.mod); + for (const SDL_Keysym& key : mKeyPressEvents) + mPlayerScripts->keyPress(key); } mKeyPressEvents.clear(); diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index 9a08f917e7..72e064bb9b 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -1,6 +1,8 @@ #ifndef MWLUA_PLAYERSCRIPTS_H #define MWLUA_PLAYERSCRIPTS_H +#include + #include "localscripts.hpp" namespace MWLua @@ -14,7 +16,7 @@ namespace MWLua registerEngineHandlers({&mKeyPressHandlers}); } - void keyPress(int sym, int mod) { callEngineHandlers(mKeyPressHandlers, sym, mod); } + void keyPress(const SDL_Keysym& key) { callEngineHandlers(mKeyPressHandlers, key); } private: EngineHandlerList mKeyPressHandlers{"onKeyPress"}; diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index a5cb7d3e58..da36109851 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -163,6 +163,7 @@ namespace LuaUtil void registerEngineHandlers(std::initializer_list handlers); const std::string mNamePrefix; + LuaUtil::LuaState& mLua; private: struct Script @@ -190,7 +191,6 @@ namespace LuaUtil void updateTimerQueue(std::vector& timerQueue, double time); static void insertTimer(std::vector& timerQueue, Timer&& t); - LuaUtil::LuaState& mLua; const UserdataSerializer* mSerializer = nullptr; std::map API; From 4eb5841c60afdb8a12602a93e2e68ffdec19bac4 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 2 Apr 2021 19:42:19 +0200 Subject: [PATCH 1001/2859] Update OpenMW Lua documentation --- docs/source/conf.py | 2 +- docs/source/reference/lua-scripting/api.rst | 455 ++---------------- .../reference/lua-scripting/overview.rst | 446 ++++++++++++++--- files/CMakeLists.txt | 1 + files/lua_api/CMakeLists.txt | 21 + files/lua_api/README.md | 13 + files/lua_api/coroutine.doclua | 71 +++ files/lua_api/global.doclua | 269 +++++++++++ files/lua_api/math.doclua | 200 ++++++++ files/lua_api/openmw/README.md | 14 + files/lua_api/openmw/async.lua | 52 ++ files/lua_api/openmw/core.lua | 328 +++++++++++++ files/lua_api/openmw/generate_luadoc.sh | 4 + files/lua_api/openmw/nearby.lua | 36 ++ files/lua_api/openmw/query.lua | 116 +++++ files/lua_api/openmw/self.lua | 68 +++ files/lua_api/openmw/ui.lua | 15 + files/lua_api/openmw/util.lua | 150 ++++++ files/lua_api/openmw/world.lua | 34 ++ files/lua_api/string.doclua | 231 +++++++++ files/lua_api/table.doclua | 66 +++ 21 files changed, 2111 insertions(+), 481 deletions(-) create mode 100644 files/lua_api/CMakeLists.txt create mode 100644 files/lua_api/README.md create mode 100644 files/lua_api/coroutine.doclua create mode 100644 files/lua_api/global.doclua create mode 100644 files/lua_api/math.doclua create mode 100644 files/lua_api/openmw/README.md create mode 100644 files/lua_api/openmw/async.lua create mode 100644 files/lua_api/openmw/core.lua create mode 100755 files/lua_api/openmw/generate_luadoc.sh create mode 100644 files/lua_api/openmw/nearby.lua create mode 100644 files/lua_api/openmw/query.lua create mode 100644 files/lua_api/openmw/self.lua create mode 100644 files/lua_api/openmw/ui.lua create mode 100644 files/lua_api/openmw/util.lua create mode 100644 files/lua_api/openmw/world.lua create mode 100644 files/lua_api/string.doclua create mode 100644 files/lua_api/table.doclua diff --git a/docs/source/conf.py b/docs/source/conf.py index 7653b94edf..72bb17adbb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -176,7 +176,7 @@ html_static_path = [ # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +html_extra_path = ['generated-luadoc'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index a7b153c039..f98d4ef074 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -17,426 +17,69 @@ Engine handler is a function defined by a script, that can be called by the engi | onUpdate(dt) | | Called every frame if game not paused. `dt` is the time | | | | from the last update in seconds. | +----------------------------------+-------------------------------------------------------------+ -| onSave() -> data | Called when the game is saving. | +| onSave() -> data | | Called when the game is saving. May be called in inactive | +| | | state, so it shouldn't use `openmw.nearby`. | +----------------------------------+-------------------------------------------------------------+ -| onLoad(data) | Called on loading with the data previosly returned by onSave| +| onLoad(data) | | Called on loading with the data previosly returned by | +| | | onSave. During loading the object is always in inactive. | +----------------------------------+-------------------------------------------------------------+ | **Only for global scripts** | +----------------------------------+-------------------------------------------------------------+ | onNewGame() | New game is started | +----------------------------------+-------------------------------------------------------------+ -| onPlayerAdded(player) |Player added to game world. The argument is a `Game object`_.| +| onPlayerAdded(player) |Player added to game world. The argument is a `Game object`. | +----------------------------------+-------------------------------------------------------------+ | onActorActive(actor) | Actor (NPC or Creature) becomes active. | +----------------------------------+-------------------------------------------------------------+ +| **Only for local scripts** | ++----------------------------------+-------------------------------------------------------------+ +| onActive() | | Called when the object becomes active (either a player | +| | | came to this cell again, or a save was loaded). | ++----------------------------------+-------------------------------------------------------------+ +| onInactive() | | Object became inactive. Since it is inactive the handler | +| | | can not access anything nearby, but it is possible to send| +| | | an event to global scripts. | ++----------------------------------+-------------------------------------------------------------+ +| onConsume(recordId) | | Called if `recordId` (e.g. a potion) is consumed. | ++----------------------------------+-------------------------------------------------------------+ | **Only for local scripts attached to a player** | +----------------------------------+-------------------------------------------------------------+ | onKeyPress(symbol, modifiers) | | Key pressed. `Symbol` is an ASCII code, `modifiers` is | | | | a binary OR of flags of special keys (ctrl, shift, alt). | +----------------------------------+-------------------------------------------------------------+ -.. _Game object: - -Game object reference -===================== - -Game object is a universal reference to an object in the game world. Anything that has a reference number. - -**Can be used on any object:** - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| object:isValid() | | Returns true if the object exists and loaded, | -| | | and false otherwise. If false, then every | -| | | access to the object will raise an error. | -+---------------------------------------------+--------------------------------------------------+ -| object:sendEvent(eventName, eventData) | Sends local event to the object. | -+---------------------------------------------+--------------------------------------------------+ -| object:isEquipped(item) | Returns true if `item` is equipped on `object`. | -+---------------------------------------------+--------------------------------------------------+ -| object:getEquipment() -> table | | Returns a table `slot` -> `object` of currently| -| | | equipped items. See `core.EQUIPMENT_SLOT`. | -| | | Returns empty table if `object` doesn't have | -| | | equipment slots. | -+---------------------------------------------+--------------------------------------------------+ -| object:setEquipment(table) | | Sets equipment. Keys in the table are equipment| -| | | slots (see `core.EQUIPMENT_SLOT`). Each value | -| | | can be either an object or `recordId`. Raises | -| | | an error if `object` doesn't have equipment | -| | | slots and `table` is not empty. Can be called | -| | | only on `self` or from a global script. | -+---------------------------------------------+--------------------------------------------------+ -| object:addScript(scriptPath) | | Adds new script to the object. | -| | | Can be called only from a global script. | -+---------------------------------------------+--------------------------------------------------+ -| object:teleport(cell, pos, [rot]) | | Moves object to given cell and position. | -| | | The effect is not immediate: the position will | -| | | be updated only in the next frame. | -| | | Can be called only from a global script. | -+---------------------------------------------+--------------------------------------------------+ - -+-----------------------+---------------------+--------------------------------------------------+ -| Field | Type | Description | -+=======================+=====================+==================================================+ -| object.position | vector3_ | Position | -+-----------------------+---------------------+--------------------------------------------------+ -| object.rotation | vector3_ | Rotation | -+-----------------------+---------------------+--------------------------------------------------+ -| object.cell | string | Cell | -+-----------------------+---------------------+--------------------------------------------------+ -| object.type | string | :ref:`Type ` of the object | -+-----------------------+---------------------+--------------------------------------------------+ -| object.count | integer | Count (makes sense if holded in a container) | -+-----------------------+---------------------+--------------------------------------------------+ -| object.recordId | string | Record ID | -+-----------------------+---------------------+--------------------------------------------------+ -| object.inventory | Inventory | Inventory of an actor or content of a container | -+-----------------------+---------------------+--------------------------------------------------+ - -**Can be used if object.type == 'Door':** - -+-----------------------+---------------------+--------------------------------------------------+ -| Field | Type | Description | -+=======================+=====================+==================================================+ -| object.isTeleport | boolean | True if it is a teleport door | -+-----------------------+---------------------+--------------------------------------------------+ -| object.destPosition | vector3_ | Destination (only if a teleport door) | -+-----------------------+---------------------+--------------------------------------------------+ -| object.destRotation | vector3_ | Destination rotation (only if a teleport door) | -+-----------------------+---------------------+--------------------------------------------------+ -| object.destCell | string | Destination cell (only if a teleport door) | -+-----------------------+---------------------+--------------------------------------------------+ - -Object type ------------ - -Type is represented as a string. Can be one of: - -- "Activator" -- "Armor" -- "Book" -- "Clothing" -- "Container" -- "Creature" -- "Door" -- "Ingredient" -- "Light" -- "Miscellaneous" -- "NPC" -- "Player" -- "Potion" -- "Static" -- "Weapon" - -ObjectList ----------- - -List of game objects. Can't be created or modified by a script. - -.. code-block:: Lua - - -- Iteration by index - for i = 1, #someList do - doSomething(someList[i]) - end - - -- Generic for (equivalent to iteration by index) - for i, item in someList:ipairs() do - doSomething(item) - end - - -- WRONG: for i, item in ipairs(someList) do - -- It doesn't work because Lua 5.1 doesn't allow to overload ipairs for userdata. - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| list:ipairs() | Returns an iterator | -+---------------------------------------------+--------------------------------------------------+ -| list:select(query) -> ObjectList | Returns a filtered list | -+---------------------------------------------+--------------------------------------------------+ - -Object inventory ----------------- - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| inv:countOf(recordId) -> int | The number of items with given recordId | -+---------------------------------------------+--------------------------------------------------+ -| inv:getAll(recordId) -> ObjectList_ | All contained items | -+---------------------------------------------+--------------------------------------------------+ -| inv:getPotions() -> ObjectList_ | All potions from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getApparatuses() -> ObjectList_ | All apparatuses from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getArmors() -> ObjectList_ | All armors from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getBooks() -> ObjectList_ | All books from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getClothing() -> ObjectList_ | All clothing from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getIngredients() -> ObjectList_ | All ingredients from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getLights() -> ObjectList_ | All lights from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getLockpicks() -> ObjectList_ | All lockpicks from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getMiscellaneous() -> ObjectList_ | All miscellaneous items from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getProbes() -> ObjectList_ | All probes from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getRepairKits() -> ObjectList_ | All repair kits from the inventory | -+---------------------------------------------+--------------------------------------------------+ -| inv:getWeapons() -> ObjectList_ | All weapon from the inventory | -+---------------------------------------------+--------------------------------------------------+ - -openmw.util -=========== - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| vector2(x, y) -> vector2_ | Creates 2D vector | -+---------------------------------------------+--------------------------------------------------+ -| vector3(x, y, z) -> vector3_ | Creates 3D vector | -+---------------------------------------------+--------------------------------------------------+ -| clamp(value, from, to) -> number | Limits given value to the interval [from, to] | -+---------------------------------------------+--------------------------------------------------+ -| normalizeAngle(radians) -> number | Adds 2pi*k and puts the angle in range [-pi, pi] | -+---------------------------------------------+--------------------------------------------------+ - -vector2 -------- - -Immutable 2D vector. - -.. code-block:: Lua - - v = vector2(3, 4) - v.x, v.y -- 3.0, 4.0 - str(v) -- "(3.0, 4.0)" - v:length() -- 5.0 length - v:length2() -- 25.0 square of the length - v:normalize() -- vector2(3/5, 4/5) - v:rotate(radians) -- rotate clockwise (returns rotated vector) - v1:dot(v2) -- dot product (returns a number) - v1 * v2 -- dot product - v1 + v2 -- vector addition - v1 - v2 -- vector subtraction - v1 * x -- multiplication by a number - v1 / x -- division by a number - -vector3 -------- - -.. code-block:: Lua - - v = vector3(3, 4, 5) - v.x, v.y, v.z -- 3.0, 4.0, 5.0 - str(v) -- "(3.0, 4.0, 4.5)" - v:length() -- length - v:length2() -- square of the length - v:normalize() -- normalized vector - v1:dot(v2) -- dot product (returns a number) - v1 * v2 -- dot product (returns a number) - v1:cross(v2) -- cross product (returns a vector) - v1 ^ v2 -- cross product (returns a vector) - v1 + v2 -- vector addition - v1 - v2 -- vector subtraction - v1 * x -- multiplication by a number - v1 / x -- division by a number - -openmw.core -=========== - -+-----------------------+---------------------+---------------------------------------------------+ -| Field | Type | Description | -+=======================+=====================+===================================================+ -| OBJECT_TYPE | Readonly table | Possible :ref:`object type ` values | -+-----------------------+---------------------+---------------------------------------------------+ -| EQUIPMENT_SLOTS | Readonly table | | Contains keys that can be used in | -| | | | `object:getEquipment` and `object:setEquipment`.| -+-----------------------+---------------------+---------------------------------------------------+ - -``EQUIPMENT_SLOTS`` contains fields: "Helmet", "Cuirass", "Greaves", "LeftPauldron", "RightPauldron", -"LeftGauntlet", "RightGauntlet", "Boots", "Shirt", "Pants", "Skirt", "Robe", "LeftRing", "RightRing", -"Amulet", "Belt", "CarriedRight", "CarriedLeft", "Ammunition". - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| sendGlobalEvent(eventName, eventData) | Sends an event to global scripts | -+---------------------------------------------+--------------------------------------------------+ -| getRealTime() | | The number of seconds (with floating point) | -| | | passed from 00:00 UTC 1 Januar 1970 (unix time)| -+---------------------------------------------+--------------------------------------------------+ -| getGameTimeInSeconds() | | The number of seconds in the game world, passed| -| | | from starting a new game. | -+---------------------------------------------+--------------------------------------------------+ -| getGameTimeInHours() | | Current time of the game world in hours. | -| | | Note that the number of game seconds in a game | -| | | hour is not guaranteed to be fixed. | -+---------------------------------------------+--------------------------------------------------+ - -openmw.async -============ - -Timers and coroutine utils. -All functions require the package itself as a first argument. -I.e. functions should be called with ``:`` rather than with ``.``. - -+-----------------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=====================================================+==================================================+ -| async:registerTimerCallback(name, func) -> Callback | Registers a function as a timer callback | -+-----------------------------------------------------+--------------------------------------------------+ -| async:newTimerInSeconds(delay, Callback, arg) | | Calls `Callback(arg)` in `delay` seconds. | -| | | `Callback` must be registered in advance. | -+-----------------------------------------------------+--------------------------------------------------+ -| async:newTimerInHours(delay, Callback, arg) | | Calls `Callback(arg)` in `delay` game hours. | -| | | `Callback` must be registered in advance. | -+-----------------------------------------------------+--------------------------------------------------+ -| async:newUnsavableTimerInSeconds(delay, func) | | Call `func()` in `delay` seconds. The timer | -| | | will be lost if the game is saved and loaded. | -+-----------------------------------------------------+--------------------------------------------------+ -| async:newUnsavableTimerInHours(delay, func) | | Call `func()` in `delay` game hours. The timer | -| | | will be lost if the game is saved and loaded. | -+-----------------------------------------------------+--------------------------------------------------+ - -openmw.query -============ - -**TODO** - -openmw.world -============ - -Interface to the game world. Can be used only by global scripts. - -+-----------------------+---------------------+--------------------------------------------------+ -| Field | Type | Description | -+=======================+=====================+==================================================+ -| activeActors | ObjectList_ | List of currently active actors | -+-----------------------+---------------------+--------------------------------------------------+ - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| selectObjects(query) -> ObjectList_ | Evaluates a query | -+---------------------------------------------+--------------------------------------------------+ - -openmw.nearby -============= - -Can be used only by local scripts. -Read-only access to the nearest area of the game world. - -+-----------------------+---------------------+--------------------------------------------------+ -| Field | Type | Description | -+=======================+=====================+==================================================+ -| activators | ObjectList_ | List of nearby activators | -+-----------------------+---------------------+--------------------------------------------------+ -| actors | ObjectList_ | List of nearby actors | -+-----------------------+---------------------+--------------------------------------------------+ -| containers | ObjectList_ | List of nearby containers | -+-----------------------+---------------------+--------------------------------------------------+ -| doors | ObjectList_ | List of nearby doors | -+-----------------------+---------------------+--------------------------------------------------+ -| items | ObjectList_ | Everything that can be picked up in the nearby | -+-----------------------+---------------------+--------------------------------------------------+ - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| selectObjects(query) -> ObjectList_ | Evaluates a query | -+---------------------------------------------+--------------------------------------------------+ - -openmw.self -=========== - -Can be used only by local scripts. Full access to the object the script is attached to. -All fields and function of `Game object`_ are also available for `openmw.self`. For example: - -.. code-block:: Lua - - local self = require('openmw.self') - if self.type == 'Player' then - self:sendEvent("something", self.position) - end - -Note that `self` is not a Game Object. If you need an actual object, use `self.object`: - -.. code-block:: Lua - - if self == someObject then ... -- Incorrect, this condition is always false - core.sendGlobalEvent('something', self) -- Incorrect, will raise an error - - if self.object == someObject then ... -- Correct - core.sendGlobalEvent('something', self.object) -- Correct - - -+-----------------------+---------------------+--------------------------------------------------+ -| Field | Type | Description | -+=======================+=====================+==================================================+ -| self.object | `Game object`_ | The object the script is attached to (readonly) | -+-----------------------+---------------------+--------------------------------------------------+ -| self.controls | `Actor controls`_ | Movement controls (only for actors) | -+-----------------------+---------------------+--------------------------------------------------+ - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| self:setDirectControl(bool) | Enables or disables direct movement control | -+---------------------------------------------+--------------------------------------------------+ -| self:setEquipment(table) | | Sets equipment. Keys in the table are equipment| -| | | slots (see `core.EQUIPMENT_SLOT`). Each value | -| | | can be either an object or `recordId`. Raises | -| | | an error if the object has no equipment | -| | | slots and `table` is not empty. | -+---------------------------------------------+--------------------------------------------------+ -| self:getCombatTarget() -> `Game object`_ | Returns current target or nil if not in combat | -+---------------------------------------------+--------------------------------------------------+ -| self:stopCombat() | Removes all combat packages from the actor | -+---------------------------------------------+--------------------------------------------------+ -| self:startCombat(target) | Attack `target` | -+---------------------------------------------+--------------------------------------------------+ - - -Actor controls --------------- - -Allows to control movements of an actor. Makes an effect only if `setDirectControl(true)` was called. -All fields are mutable. - -+-----------------------+---------------------+--------------------------------------------------+ -| Field | Type | Description | -+=======================+=====================+==================================================+ -| movement | float number | +1 - move forward, -1 - move backward | -+-----------------------+---------------------+--------------------------------------------------+ -| sideMovement | float number | +1 - move right, -1 - move left | -+-----------------------+---------------------+--------------------------------------------------+ -| turn | float number | turn right (radians); if negative - turn left | -+-----------------------+---------------------+--------------------------------------------------+ -| run | boolean | true - run, false - walk | -+-----------------------+---------------------+--------------------------------------------------+ -| jump | boolean | if true - initiate a jump | -+-----------------------+---------------------+--------------------------------------------------+ - -openmw.ui -========= - -Can be used only by local scripts, that are attached to a player. - -+---------------------------------------------+--------------------------------------------------+ -| Function | Description | -+=============================================+==================================================+ -| showMessage(string) | Shows given message at the bottom of the screen. | -+---------------------------------------------+--------------------------------------------------+ -openmw.camera -============= +Packages reference +================== + +API packages provide functions that can be called by scripts. I.e. it is a script-to-engine interaction. +A package can be loaded with ``require('')``. +It can not be overloaded even if there is a lua file with the same name. +The list of available packages is different for global and for local scripts. +Player scripts are local scripts that are attached to a player. + ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +| Package | Can be used | Description | ++=========================================================+====================+===============================================================+ +|:ref:`openmw.interfaces + From d0677c3f07678a5eb351572c4eb9b14b0e1f03e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Wed, 28 Jul 2021 21:31:41 +0200 Subject: [PATCH 1109/2859] Move reference to the right cell according to its geographical position --- apps/openmw/mwrender/objectpaging.cpp | 10 ++++++++++ apps/openmw/mwworld/store.cpp | 12 ++++++++++++ components/esm/loadcell.hpp | 5 +++++ 3 files changed, 27 insertions(+) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 2cbffbc6eb..aae8f2e4f1 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -434,6 +434,7 @@ namespace MWRender continue; if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; + if (std::find(cell->mMovedRefsByPos.begin(), cell->mMovedRefsByPos.end(), ref.mRefNum) != cell->mMovedRefsByPos.end()) continue; Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; @@ -455,6 +456,15 @@ namespace MWRender if (!typeFilter(type,size>=2)) continue; refs[ref.mRefNum] = std::move(ref); } + + for (auto [ref, deleted] : cell->mLeasedRefsByPos) + { + if (deleted) { refs.erase(ref.mRefNum); continue; } + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + int type = store.findStatic(ref.mRefID); + if (!typeFilter(type, size >= 2)) continue; + refs[ref.mRefNum] = std::move(ref); + } } } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 125239e9a3..add8b541e0 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -478,6 +478,15 @@ namespace MWWorld // implementation when the oher implementation works as well. while (cell->getNextRef(esm, ref, deleted, cMRef, moved)) { + + auto x = static_cast(std::floor(ref.mPos.pos[0] / float(ESM::Land::REAL_SIZE))); + auto y = static_cast(std::floor(ref.mPos.pos[1] / float(ESM::Land::REAL_SIZE))); + if (x != cell->getGridX() || y != cell->getGridY()) + { + ESM::Cell* cellAlt = const_cast(searchOrCreate(x, y)); + cellAlt->mLeasedRefsByPos.emplace_back(ref, deleted); + cell->mMovedRefsByPos.push_back(ref.mRefNum); + } if (!moved) continue; @@ -678,6 +687,9 @@ namespace MWWorld oldcell->mMovedRefs.push_back(*it); } + oldcell->mLeasedRefsByPos.splice(oldcell->mLeasedRefsByPos.end(), cell.mLeasedRefsByPos); + oldcell->mMovedRefsByPos.splice(oldcell->mMovedRefsByPos.end(), cell.mMovedRefsByPos); + // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a // reference to this cell, so the list for the new cell should be empty. The list for oldcell, // however, could have leased refs in it and so should be kept. diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 3a8133d881..7770f719e3 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -124,6 +124,11 @@ struct Cell CellRefTracker mLeasedRefs; MovedCellRefTracker mMovedRefs; + // References "adopted" from another cell (i.e. a different cell + // introduced this ref, and it has been moved here as it geographically in this cell) + CellRefTracker mLeasedRefsByPos; + std::list mMovedRefsByPos; + void postLoad(ESMReader &esm); // This method is left in for compatibility with esmtool. Parsing moved references currently requires From 1fff39d5fa21f30a3c9eeeafcbcfaba9c35d080b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Fri, 30 Jul 2021 18:24:20 +0200 Subject: [PATCH 1110/2859] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f94c6952..90fda783a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Bug #5379: Wandering NPCs falling through cantons Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost + Bug #5755: Active grid object paging - disappearing textures Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6051: NaN water height in ESM file is not handled gracefully From 4e7c128d2523243e105470d39ed6ca27fd75332c Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 30 Jul 2021 22:23:42 +0200 Subject: [PATCH 1111/2859] The LOS cache is now unconditionally used without async physics as well with a TTL of 0 frame. It helps performance when several subsystems request the same LOS in the same frame (combat, headtracking, etc). Except it doesn't work if the cache is never trimmed. --- apps/openmw/mwphysics/mtphysics.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 10bec49612..f66af8bae4 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -565,6 +565,7 @@ namespace MWPhysics if (mAdvanceSimulation) actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); } + refreshLOSCache(); } void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) From 88a5ca440b8afecc70cad318d7f302d74fb6f34b Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 11 Apr 2021 18:18:10 +0200 Subject: [PATCH 1112/2859] Change moveObject() to take a osg::Vec3f argument instead of 3 floats for readability. --- apps/openmw/mwbase/world.hpp | 4 +-- apps/openmw/mwclass/creature.cpp | 4 +-- apps/openmw/mwclass/npc.cpp | 4 +-- apps/openmw/mwlua/actions.cpp | 2 +- apps/openmw/mwmechanics/aitravel.cpp | 5 +-- apps/openmw/mwmechanics/aiwander.cpp | 4 +-- apps/openmw/mwphysics/physicssystem.cpp | 2 +- .../mwscript/transformationextensions.cpp | 9 +++-- apps/openmw/mwworld/actionteleport.cpp | 4 +-- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/scene.cpp | 4 +-- apps/openmw/mwworld/worldimp.cpp | 34 +++++++++---------- apps/openmw/mwworld/worldimp.hpp | 4 +-- 13 files changed, 38 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4110c8f489..2c651796f0 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -283,10 +283,10 @@ namespace MWBase virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; - virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0; + virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) = 0; ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; + virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) = 0; ///< @return an updated Ptr virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 7e231c60fe..d03ea70a87 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -834,9 +834,7 @@ namespace MWClass ptr.getRefData().setCustomData(nullptr); // Reset to original position - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], - ptr.getCellRef().getPosition().pos[1], - ptr.getCellRef().getPosition().pos[2]); + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); } } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 691dfe5871..736ae537fe 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1388,9 +1388,7 @@ namespace MWClass ptr.getRefData().setCustomData(nullptr); // Reset to original position - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], - ptr.getCellRef().getPosition().pos[1], - ptr.getCellRef().getPosition().pos[2]); + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); } } } diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 95a33fed0d..719d713e6f 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -39,7 +39,7 @@ namespace MWLua } else { - MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos.x(), mPos.y(), mPos.z()); + MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); world->rotateObject(newObj, mRot.x(), mRot.y(), mRot.z()); } } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 8e5372c46d..91fe96a2aa 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -93,11 +93,12 @@ namespace MWMechanics void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) { - if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) + osg::Vec3f pos(mX, mY, mZ); + if (!isWithinMaxRange(pos, actor.getRefData().getPosition().asVec3())) return; // does not do any validation on the travel target (whether it's in air, inside collision geometry, etc), // that is the user's responsibility - MWBase::Environment::get().getWorld()->moveObject(actor, mX, mY, mZ); + MWBase::Environment::get().getWorld()->moveObject(actor, pos); actor.getClass().adjustPosition(actor, false); reset(); } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 0e424b2f8b..24fdff7065 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -744,8 +744,8 @@ namespace MWMechanics state.moveIn(new AiWanderStorage()); - MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), - static_cast(dest.mY), static_cast(dest.mZ)); + osg::Vec3f pos(static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); + MWBase::Environment::get().getWorld()->moveObject(actor, pos); actor.getClass().adjustPosition(actor, false); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9814af09f5..ab880abaaa 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -958,7 +958,7 @@ namespace MWPhysics if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) { mPosition.z() = mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false); + MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition, false); } mOldHeight = mPosition.z(); mRefpos = mActorRaw->getPtr().getRefData().getPosition(); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 096e78d34b..47733ea24b 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -393,7 +393,7 @@ namespace MWScript if(store) { MWWorld::Ptr base = ptr; - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,osg::Vec3f(x,y,z)); dynamic_cast(runtime.getContext()).updatePtr(base,ptr); float ax = ptr.getRefData().getPosition().rot[0]; @@ -444,11 +444,11 @@ namespace MWScript if (ptr == MWMechanics::getPlayer()) { MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, cell, osg::Vec3(x, y, z)); } else { - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, osg::Vec3f(x, y, z), true, true); } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); @@ -689,8 +689,7 @@ namespace MWScript MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], - ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2])); + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3())); } }; diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 9cd8469a39..fcfafc5f4f 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -55,10 +55,10 @@ namespace MWWorld int cellY; world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); world->moveObject(actor,world->getExterior(cellX,cellY), - mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); + mPosition.asVec3()); } else - world->moveObject(actor,world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); + world->moveObject(actor,world->getInterior(mCellName),mPosition.asVec3()); } } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 39c00cd4e8..37c4e178ad 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -190,7 +190,7 @@ namespace const ESM::Position & newpos = iter->mData.getPosition(); const MWWorld::Ptr ptr(&*iter, cellstore); if ((oldscale != iter->mRef.getScale() || oldpos.asVec3() != newpos.asVec3() || oldpos.rot[0] != newpos.rot[0] || oldpos.rot[1] != newpos.rot[1] || oldpos.rot[2] != newpos.rot[2]) && !ptr.getClass().isActor()) - MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.pos[0], newpos.pos[1], newpos.pos[2]); + MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.asVec3()); if (!iter->mData.isEnabled()) { iter->mData.enable(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3111a28f04..aee6493c08 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -842,7 +842,7 @@ namespace MWWorld mRendering.updatePlayerPtr(player); if (adjustPlayerPos) { - world->moveObject(player, pos.pos[0], pos.pos[1], pos.pos[2]); + world->moveObject(player, pos.asVec3()); float x = pos.rot[0]; float y = pos.rot[1]; @@ -921,7 +921,7 @@ namespace MWWorld if(mCurrentCell != nullptr && *mCurrentCell == *cell) { MWBase::World *world = MWBase::Environment::get().getWorld(); - world->moveObject(world->getPlayerPtr(), position.pos[0], position.pos[1], position.pos[2]); + world->moveObject(world->getPlayerPtr(), position.asVec3()); float x = position.rot[0]; float y = position.rot[1]; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8fea737d8d..d2fbd9ed5a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1128,18 +1128,16 @@ namespace MWWorld } } - MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z, bool movePhysics) + MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics) { ESM::Position pos = ptr.getRefData().getPosition(); - pos.pos[0] = x; - pos.pos[1] = y; - pos.pos[2] = z; + pos.pos[0] = position.x(); + pos.pos[1] = position.y(); + pos.pos[2] = position.z(); ptr.getRefData().setPosition(pos); - osg::Vec3f vec(x, y, z); - CellStore *currCell = ptr.isInCell() ? ptr.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || (currCell && mWorldScene->isCellActive(*currCell)); @@ -1228,7 +1226,7 @@ namespace MWWorld } if (haveToMove && newPtr.getRefData().getBaseNode()) { - mWorldScene->updateObjectPosition(newPtr, vec, movePhysics); + mWorldScene->updateObjectPosition(newPtr, position, movePhysics); if (movePhysics) { if (const auto object = mPhysics->getObject(ptr)) @@ -1237,7 +1235,7 @@ namespace MWWorld } if (isPlayer) - mWorldScene->playerMoved(vec); + mWorldScene->playerMoved(position); else { mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); @@ -1247,10 +1245,10 @@ namespace MWWorld return newPtr; } - MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) + MWWorld::Ptr World::moveObject(const Ptr& ptr, const osg::Vec3f& position, bool movePhysics, bool moveToActive) { int cellX, cellY; - positionToIndex(x, y, cellX, cellY); + positionToIndex(position.x(), position.y(), cellX, cellY); CellStore* cell = ptr.getCell(); CellStore* newCell = getExterior(cellX, cellY); @@ -1259,7 +1257,7 @@ namespace MWWorld if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor())) cell = newCell; - return moveObject(ptr, cell, x, y, z, movePhysics); + return moveObject(ptr, cell, position, movePhysics); } MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) @@ -1269,8 +1267,8 @@ namespace MWWorld if (actor) actor->adjustPosition(vec, ignoreCollisions); if (ptr.getClass().isActor()) - return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr()); - return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); + return moveObject(ptr, newpos, false, moveToActive && ptr != getPlayerPtr()); + return moveObject(ptr, newpos); } void World::scaleObject (const Ptr& ptr, float scale) @@ -1377,7 +1375,7 @@ namespace MWWorld pos.z() = std::min(pos.z(), traced.z()); } - moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); + moveObject(ptr, ptr.getCell(), pos); } void World::fixPosition() @@ -1536,7 +1534,7 @@ namespace MWWorld auto* physactor = mPhysics->getActor(actor); assert(physactor); const auto position = physactor->getSimulationPosition(); - moveObject(actor, position.x(), position.y(), position.z(), false, false); + moveObject(actor, position, false, false); } } @@ -1546,7 +1544,7 @@ namespace MWWorld auto* physactor = mPhysics->getActor(*player); assert(physactor); const auto position = physactor->getSimulationPosition(); - moveObject(*player, position.x(), position.y(), position.z(), false, false); + moveObject(*player, position, false, false); } } @@ -2279,7 +2277,7 @@ namespace MWWorld pos.pos[0] -= adjust.x(); pos.pos[1] -= adjust.y(); pos.pos[2] -= adjust.z(); - moveObject(dropped, pos.pos[0], pos.pos[1], pos.pos[2]); + moveObject(dropped, pos.asVec3()); } } @@ -3859,7 +3857,7 @@ namespace MWWorld return true; const ESM::Position& origPos = ptr.getCellRef().getPosition(); - MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.pos[0], origPos.pos[1], origPos.pos[2]); + MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.asVec3()); MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.rot[0], origPos.rot[1], origPos.rot[2]); ptr.getClass().adjustPosition(ptr, true); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 87cd14dd5c..4da790f6b8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -372,10 +372,10 @@ namespace MWWorld void undeleteObject (const Ptr& ptr) override; - MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) override; + MWWorld::Ptr moveObject (const Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) override; ///< @return an updated Ptr in case the Ptr's cell changes - MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; + MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) override; ///< @return an updated Ptr MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) override; From a7b190ad29daafbcd8d53fd846279248a1c9761b Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 11 Apr 2021 19:17:17 +0200 Subject: [PATCH 1113/2859] Change rotateObject() to take a osg::Vec3f argument instead of 3 floats for readability. --- apps/openmw/mwbase/world.hpp | 3 +- apps/openmw/mwinput/controlswitch.cpp | 2 +- apps/openmw/mwlua/actions.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 7 +++- .../mwscript/transformationextensions.cpp | 42 ++++++++----------- apps/openmw/mwworld/scene.cpp | 12 +----- apps/openmw/mwworld/worldimp.cpp | 27 +++++------- apps/openmw/mwworld/worldimp.hpp | 5 +-- 8 files changed, 39 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 2c651796f0..bd806c7cac 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -294,8 +294,7 @@ namespace MWBase virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; - virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z, - RotationFlags flags = RotationFlag_inverseOrder) = 0; + virtual void rotateObject(const MWWorld::Ptr& ptr, const osg::Vec3f& rot, RotationFlags flags = RotationFlag_inverseOrder) = 0; virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; ///< Place an object. Makes a copy of the Ptr. diff --git a/apps/openmw/mwinput/controlswitch.cpp b/apps/openmw/mwinput/controlswitch.cpp index 33c4b75dcc..f31744fca6 100644 --- a/apps/openmw/mwinput/controlswitch.cpp +++ b/apps/openmw/mwinput/controlswitch.cpp @@ -57,7 +57,7 @@ namespace MWInput } else if (key == "playerlooking" && !value) { - MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), 0.f, 0.f, 0.f); + MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f()); } mSwitches[key] = value; } diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 719d713e6f..500ad98490 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -40,7 +40,7 @@ namespace MWLua else { MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); - world->rotateObject(newObj, mRot.x(), mRot.y(), mRot.z()); + world->rotateObject(newObj, mRot); } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d5a1d46a88..fe21da0840 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2394,12 +2394,15 @@ void CharacterController::update(float duration) if(!isKnockedDown() && !isKnockedOut()) { if (rot != osg::Vec3f()) - world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); + world->rotateObject(mPtr, rot, true); } else //avoid z-rotating for knockdown { if (rot.x() != 0 && rot.y() != 0) - world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); + { + rot.z() = 0.0f; + world->rotateObject(mPtr, rot, true); + } } if (!mMovementAnimationControlled) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 47733ea24b..3d00b24ef8 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -167,17 +167,17 @@ namespace MWScript // XYZ axis use the inverse (XYZ) rotation order like vanilla SetAngle. // UWV axis use the standard (ZYX) rotation order like TESCS/OpenMW-CS and the rest of the game. if (axis == "x") - MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_inverseOrder); + MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(angle,ay,az),MWBase::RotationFlag_inverseOrder); else if (axis == "y") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_inverseOrder); + MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(ax,angle,az),MWBase::RotationFlag_inverseOrder); else if (axis == "z") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_inverseOrder); + MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(ax,ay,angle),MWBase::RotationFlag_inverseOrder); else if (axis == "u") - MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_none); + MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(angle,ay,az),MWBase::RotationFlag_none); else if (axis == "w") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_none); + MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(ax,angle,az),MWBase::RotationFlag_none); else if (axis == "v") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_none); + MWBase::Environment::get().getWorld()->rotateObject(ptr,osg::Vec3f(ax,ay,angle),MWBase::RotationFlag_none); } }; @@ -396,14 +396,14 @@ namespace MWScript ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,osg::Vec3f(x,y,z)); dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = ptr.getRefData().getPosition().rot[0]; - float ay = ptr.getRefData().getPosition().rot[1]; + auto rot = ptr.getRefData().getPosition().asRotationVec3(); // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); + rot.z() = osg::DegreesToRadians(zRot); + MWBase::Environment::get().getWorld()->rotateObject(ptr,rot); ptr.getClass().adjustPosition(ptr, false); } @@ -452,14 +452,14 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = ptr.getRefData().getPosition().rot[0]; - float ay = ptr.getRefData().getPosition().rot[1]; + auto rot = ptr.getRefData().getPosition().asRotationVec3(); // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); + rot.z() = osg::DegreesToRadians(zRot); + MWBase::Environment::get().getWorld()->rotateObject(ptr,rot); ptr.getClass().adjustPosition(ptr, false); } }; @@ -621,16 +621,14 @@ namespace MWScript Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - float ax = ptr.getRefData().getPosition().rot[0]; - float ay = ptr.getRefData().getPosition().rot[1]; - float az = ptr.getRefData().getPosition().rot[2]; - + auto rot = ptr.getRefData().getPosition().asRotationVec3(); if (axis == "x") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax+rotation,ay,az); + rot.x() += rotation; else if (axis == "y") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az); + rot.y() += rotation; else if (axis == "z") - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation); + rot.z() += rotation; + MWBase::Environment::get().getWorld()->rotateObject(ptr,rot); } }; @@ -682,11 +680,7 @@ namespace MWScript if (!ptr.isInCell()) return; - float xr = ptr.getCellRef().getPosition().rot[0]; - float yr = ptr.getCellRef().getPosition().rot[1]; - float zr = ptr.getCellRef().getPosition().rot[2]; - - MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr); + MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3()); dynamic_cast(runtime.getContext()).updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3())); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index aee6493c08..8c401dcf12 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -843,11 +843,7 @@ namespace MWWorld if (adjustPlayerPos) { world->moveObject(player, pos.asVec3()); - - float x = pos.rot[0]; - float y = pos.rot[1]; - float z = pos.rot[2]; - world->rotateObject(player, x, y, z); + world->rotateObject(player, pos.asRotationVec3()); player.getClass().adjustPosition(player, true); } @@ -922,11 +918,7 @@ namespace MWWorld { MWBase::World *world = MWBase::Environment::get().getWorld(); world->moveObject(world->getPlayerPtr(), position.asVec3()); - - float x = position.rot[0]; - float y = position.rot[1]; - float z = position.rot[2]; - world->rotateObject(world->getPlayerPtr(), x, y, z); + world->rotateObject(world->getPlayerPtr(), position.asRotationVec3()); if (adjustPlayerPos) world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d2fbd9ed5a..38785740a9 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1131,11 +1131,7 @@ namespace MWWorld MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics) { ESM::Position pos = ptr.getRefData().getPosition(); - - pos.pos[0] = position.x(); - pos.pos[1] = position.y(); - pos.pos[2] = position.z(); - + std::memcpy(pos.pos, &position, sizeof(osg::Vec3f)); ptr.getRefData().setPosition(pos); CellStore *currCell = ptr.isInCell() ? ptr.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup @@ -1292,7 +1288,7 @@ namespace MWWorld updateNavigatorObject(*object); } - void World::rotateObjectImp(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags) + void World::rotateObject(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags) { const float pi = static_cast(osg::PI); @@ -1414,11 +1410,6 @@ namespace MWWorld } } - void World::rotateObject (const Ptr& ptr, float x, float y, float z, MWBase::RotationFlags flags) - { - rotateObjectImp(ptr, osg::Vec3f(x, y, z), flags); - } - void World::rotateWorldObject (const Ptr& ptr, osg::Quat rotate) { if(ptr.getRefData().getBaseNode() != nullptr) @@ -1602,14 +1593,16 @@ namespace MWWorld bool World::rotateDoor(const Ptr door, MWWorld::DoorState state, float duration) { const ESM::Position& objPos = door.getRefData().getPosition(); - float oldRot = objPos.rot[2]; + auto oldRot = objPos.asRotationVec3(); + auto newRot = oldRot; float minRot = door.getCellRef().getPosition().rot[2]; float maxRot = minRot + osg::DegreesToRadians(90.f); float diff = duration * osg::DegreesToRadians(90.f) * (state == MWWorld::DoorState::Opening ? 1 : -1); - float targetRot = std::min(std::max(minRot, oldRot + diff), maxRot); - rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot, MWBase::RotationFlag_none); + float targetRot = std::clamp(oldRot.z() + diff, minRot, maxRot); + newRot.z() = targetRot; + rotateObject(door, newRot, MWBase::RotationFlag_none); bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot; @@ -1660,7 +1653,7 @@ namespace MWWorld MWBase::Environment::get().getSoundManager()->stopSound3D(door, closeSound); } - rotateObject(door, objPos.rot[0], objPos.rot[1], oldRot, MWBase::RotationFlag_none); + rotateObject(door, oldRot, MWBase::RotationFlag_none); } return reached; @@ -2511,7 +2504,7 @@ namespace MWWorld player.getClass().getInventoryStore(player).setContListener(anim); scaleObject(player, player.getCellRef().getScale()); // apply race height - rotateObject(player, 0.f, 0.f, 0.f, MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust); + rotateObject(player, osg::Vec3f(), MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust); MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr()); MWBase::Environment::get().getWindowManager()->watchActor(getPlayerPtr()); @@ -3858,7 +3851,7 @@ namespace MWWorld const ESM::Position& origPos = ptr.getCellRef().getPosition(); MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.asVec3()); - MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.rot[0], origPos.rot[1], origPos.rot[2]); + MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.asRotationVec3()); ptr.getClass().adjustPosition(ptr, true); } return true; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4da790f6b8..11769372f8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -133,8 +133,6 @@ namespace MWWorld void updateWeather(float duration, bool paused = false); - void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags); - Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); @@ -387,8 +385,7 @@ namespace MWWorld /// @note Rotations via this method use a different rotation order than the initial rotations in the CS. This /// could be considered a bug, but is needed for MW compatibility. /// \param adjust indicates rotation should be set or adjusted - void rotateObject (const Ptr& ptr, float x, float y, float z, - MWBase::RotationFlags flags = MWBase::RotationFlag_inverseOrder) override; + void rotateObject (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags = MWBase::RotationFlag_inverseOrder) override; MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) override; ///< Place an object. Makes a copy of the Ptr. From c76387162bbb0f776a811222246962de5668e0b2 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 31 Jul 2021 23:08:50 +0200 Subject: [PATCH 1114/2859] Add projectiles number to the resources stats --- apps/openmw/mwphysics/physicssystem.cpp | 1 + components/resource/stats.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ab880abaaa..63f25eb5f5 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -925,6 +925,7 @@ namespace MWPhysics { stats.setAttribute(frameNumber, "Physics Actors", mActors.size()); stats.setAttribute(frameNumber, "Physics Objects", mObjects.size()); + stats.setAttribute(frameNumber, "Physics Projectiles", mProjectiles.size()); stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size()); } diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 4d07889d0c..b5975e28e3 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -401,6 +401,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "", "Physics Actors", "Physics Objects", + "Physics Projectiles", "Physics HeightFields", }); From 35928cf4d3bfa5de77879402fef8806bf6369509 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 31 Jul 2021 23:26:10 +0200 Subject: [PATCH 1115/2859] Refactor a bit the physics simulation to make it not actor centric: - inline PhysicsSystem::applyQueuedMovements() into PhysicsSystem::stepSimulation() - rename PhysicsTaskScheduler::moveActors() to PhysicsTaskScheduler::applyQueuedMovements() - move the actor movement code from World::doPhysics() to PhysicsSystem::moveActors() (analogically to the projectile manager) --- apps/openmw/mwphysics/mtphysics.cpp | 14 +++------- apps/openmw/mwphysics/mtphysics.hpp | 5 ++-- apps/openmw/mwphysics/physicssystem.cpp | 36 ++++++++++++++++--------- apps/openmw/mwphysics/physicssystem.hpp | 11 ++++---- apps/openmw/mwworld/worldimp.cpp | 27 ++----------------- 5 files changed, 37 insertions(+), 56 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 10bec49612..efc67a8e42 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -230,7 +230,7 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - const std::vector& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. @@ -239,8 +239,6 @@ namespace MWPhysics double timeStart = mTimer->tick(); - mMovedActors.clear(); - // start by finishing previous background computation if (mNumThreads != 0) { @@ -260,7 +258,6 @@ namespace MWPhysics if (mAdvanceSimulation) data.mActorRaw->setStandingOnPtr(data.mStandingOn); data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); - mMovedActors.emplace_back(data.mActorRaw->getPtr()); } } if(mAdvanceSimulation) @@ -296,7 +293,7 @@ namespace MWPhysics syncComputation(); if(mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor); - return mMovedActors; + return; } mAsyncStartTime = mTimer->tick(); @@ -304,23 +301,19 @@ namespace MWPhysics mHasJob.notify_all(); if (mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); - return mMovedActors; } - const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) + void PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { std::unique_lock lock(mSimulationMutex); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); - mMovedActors.clear(); mActorsFrameData.clear(); for (const auto& [_, actor] : actors) { actor->updatePosition(); actor->updateCollisionObjectPosition(); - mMovedActors.emplace_back(actor->getPtr()); } - return mMovedActors; } void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const @@ -561,7 +554,6 @@ namespace MWPhysics handleFall(actorData, mAdvanceSimulation); actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); updateMechanics(actorData); - mMovedActors.emplace_back(actorData.mActorRaw->getPtr()); if (mAdvanceSimulation) actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index ae82727c56..21698e52bd 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -38,9 +38,9 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - const std::vector& moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - const std::vector& resetSimulation(const ActorMap& actors); + void resetSimulation(const ActorMap& actors); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; @@ -72,7 +72,6 @@ namespace MWPhysics std::unique_ptr mWorldFrameData; std::vector mActorsFrameData; - std::vector mMovedActors; float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ab880abaaa..a4d8bdb457 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -751,17 +751,6 @@ namespace MWPhysics actor->setVelocity(osg::Vec3f()); } - const std::vector& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) - { - mTimeAccum += dt; - - if (skipSimulation) - return mTaskScheduler->resetSimulation(mActors); - - // modifies mTimeAccum - return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats); - } - std::vector PhysicsSystem::prepareFrameData(bool willSimulate) { std::vector actorsFrameData; @@ -798,20 +787,43 @@ namespace MWPhysics return actorsFrameData; } - void PhysicsSystem::stepSimulation() + void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { for (Object* animatedObject : mAnimatedObjects) + { if (animatedObject->animateCollisionShapes()) { auto obj = mObjects.find(animatedObject->getPtr()); assert(obj != mObjects.end()); mTaskScheduler->updateSingleAabb(obj->second); } + } #ifndef BT_NO_PROFILE CProfileManager::Reset(); CProfileManager::Increment_Frame_Counter(); #endif + + mTimeAccum += dt; + + if (skipSimulation) + mTaskScheduler->resetSimulation(mActors); + else + // modifies mTimeAccum + mTaskScheduler->applyQueuedMovements(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats); + } + + void PhysicsSystem::moveActors() + { + auto* player = getActor(MWMechanics::getPlayer()); + auto* world = MWBase::Environment::get().getWorld(); + for (auto& [ptr, physicActor] : mActors) + { + if (physicActor.get() == player) + continue; + world->moveObject(physicActor->getPtr(), physicActor->getSimulationPosition(), false, false); + } + world->moveObject(player->getPtr(), player->getSimulationPosition(), false, false); } void PhysicsSystem::updateAnimatedCollisionShape(const MWWorld::Ptr& object) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 1eb1fd419a..ee62ab6ace 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -154,7 +154,11 @@ namespace MWPhysics bool toggleCollisionMode(); - void stepSimulation(); + /// Determine new position based on all queued movements, then clear the list. + void stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + + /// Apply new positions to actors + void moveActors(); void debugDraw(); std::vector getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with @@ -204,12 +208,9 @@ namespace MWPhysics osg::BoundingBox getBoundingBox(const MWWorld::ConstPtr &object) const; /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will - /// be overwritten. Valid until the next call to applyQueuedMovement. + /// be overwritten. Valid until the next call to stepSimulation void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); - /// Apply all queued movements, then clear the list. - const std::vector& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 38785740a9..5fcab477cf 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1508,35 +1508,12 @@ namespace MWWorld void World::doPhysics(float duration, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { - mPhysics->stepSimulation(); processDoors(duration); - mProjectileManager->update(duration); - - const auto& results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); + mPhysics->stepSimulation(duration, mDiscardMovements, frameStart, frameNumber, stats); mProjectileManager->processHits(); mDiscardMovements = false; - - for(const auto& actor : results) - { - // Handle player last, in case a cell transition occurs - if(actor != getPlayerPtr()) - { - auto* physactor = mPhysics->getActor(actor); - assert(physactor); - const auto position = physactor->getSimulationPosition(); - moveObject(actor, position, false, false); - } - } - - const auto player = std::find(results.begin(), results.end(), getPlayerPtr()); - if (player != results.end()) - { - auto* physactor = mPhysics->getActor(*player); - assert(physactor); - const auto position = physactor->getSimulationPosition(); - moveObject(*player, position, false, false); - } + mPhysics->moveActors(); } void World::updateNavigator() From 4727ae4b3b274eddc13feb7649df496809336a61 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 1 Aug 2021 02:47:10 +0100 Subject: [PATCH 1116/2859] Make it possible to opt out of composing variables --- apps/opencs/model/doc/runner.cpp | 2 ++ apps/openmw/main.cpp | 4 ++++ components/files/configurationmanager.cpp | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 8dafbaf5ae..f1179b1624 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -83,6 +83,8 @@ void CSMDoc::Runner::start (bool delayed) arguments << QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str()); + arguments << "--replace=content"; + for (std::vector::const_iterator iter (mContentFiles.begin()); iter!=mContentFiles.end(); ++iter) { diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 324a18bdee..78be8974b3 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -44,6 +44,10 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat desc.add_options() ("help", "print help message") ("version", "print version information and quit") + + ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") + ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 92d35a6b65..35679ef293 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -100,6 +100,17 @@ boost::program_options::variables_map ConfigurationManager::separateComposingVar void ConfigurationManager::mergeComposingVariables(boost::program_options::variables_map & first, boost::program_options::variables_map & second, boost::program_options::options_description& description) { + // There are a few places this assumes all variables are present in second, but it's never crashed in the wild, so it looks like that's guaranteed. + std::set replacedVariables; + if (description.find_nothrow("replace", false)) + { + auto replace = second["replace"]; + if (!replace.defaulted() && !replace.empty()) + { + std::vector replaceVector = replace.as().toStdStringVector(); + replacedVariables.insert(replaceVector.begin(), replaceVector.end()); + } + } for (const auto& option : description.options()) { if (option->semantic()->is_composing()) @@ -113,6 +124,12 @@ void ConfigurationManager::mergeComposingVariables(boost::program_options::varia continue; } + if (replacedVariables.count(name)) + { + firstPosition->second = second[name]; + continue; + } + if (second[name].defaulted() || second[name].empty()) continue; From 04e9b6d242f2fc110a65839a7b4051c09c67a026 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 1 Aug 2021 03:04:12 +0100 Subject: [PATCH 1117/2859] Abort on duplicate content file --- apps/openmw/main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 78be8974b3..de0fb0df03 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -199,6 +199,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; return false; } + std::set contentDedupe; + for (const auto& contentFile : content) + { + if (!contentDedupe.insert(contentFile).second) + { + Log(Debug::Error) << "Content file specified more than once: " << contentFile << ". Aborting..."; + return false; + } + } for (auto& file : content) { From 67cad2c5158c32b309e6983222e48a99bf1d9949 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 1 Aug 2021 14:16:37 +1000 Subject: [PATCH 1118/2859] Fix `CSMWorld::InfoCollection::getTopicRange()` returning one too many. --- apps/opencs/model/world/infocollection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index b4223d03ab..978ce3595d 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -256,8 +257,8 @@ CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const s // Find end (one past the range) RecordConstIterator end = begin + iter->second.size(); - if (end != getRecords().end()) - ++end; + + assert(static_cast(std::distance(begin, end)) == iter->second.size()); return Range (begin, end); } From bede1ea1ec3d6d5cf6b24f4219c719a8d2a407e9 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 1 Aug 2021 13:13:47 +0200 Subject: [PATCH 1119/2859] Fix build --- apps/opencs/model/world/infocollection.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 8974cbb8fb..c1dcbafb01 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -1,6 +1,8 @@ #ifndef CSM_WOLRD_INFOCOLLECTION_H #define CSM_WOLRD_INFOCOLLECTION_H +#include + #include "collection.hpp" #include "info.hpp" From 36e33b0cf20eb99b87e5a72c0c2278f0de36dd41 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 1 Aug 2021 13:14:05 +0200 Subject: [PATCH 1120/2859] Add missing override --- apps/opencs/model/world/infocollection.hpp | 10 +++++----- apps/opencs/model/world/record.hpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index c1dcbafb01..3e8455c399 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -74,19 +74,19 @@ namespace CSMWorld void removeDialogueInfos(const std::string& dialogueId); - void removeRows (int index, int count); + void removeRows (int index, int count) override; void appendBlankRecord (const std::string& id, - UniversalId::Type type = UniversalId::Type_None); + UniversalId::Type type = UniversalId::Type_None) override; - int searchId (const std::string& id) const; + int searchId (const std::string& id) const override; void appendRecord (std::unique_ptr record, - UniversalId::Type type = UniversalId::Type_None); + UniversalId::Type type = UniversalId::Type_None) override; void insertRecord (std::unique_ptr record, int index, - UniversalId::Type type = UniversalId::Type_None); + UniversalId::Type type = UniversalId::Type_None) override; }; } diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index eb4a74c343..bb43612e5e 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -46,9 +46,9 @@ namespace CSMWorld Record(State state, const ESXRecordT *base = 0, const ESXRecordT *modified = 0); - std::unique_ptr clone() const; + std::unique_ptr clone() const override; - std::unique_ptr modifiedCopy() const; + std::unique_ptr modifiedCopy() const override; void assign (const RecordBase& record) override; From 9ba459662d509a070d0ca5fa1073b339be44e9bb Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 1 Aug 2021 13:05:40 +0200 Subject: [PATCH 1121/2859] Remove unneeded std::move [19/199] Building CXX object apps/opencs/CMakeFiles/openmw-cs.dir/model/world/commands.cpp.o ../../../apps/opencs/model/world/commands.cpp:298:12: warning: moving a temporary object prevents copy elision [-Wpessimizing-move] mOld = std::move(model.getRecord (id).clone()); ^ ../../../apps/opencs/model/world/commands.cpp:298:12: note: remove std::move call here mOld = std::move(model.getRecord (id).clone()); ^~~~~~~~~~ ~ ../../../apps/opencs/model/world/commands.cpp:333:12: warning: moving a temporary object prevents copy elision [-Wpessimizing-move] mOld = std::move(model.getRecord (id).clone()); ^ ../../../apps/opencs/model/world/commands.cpp:333:12: note: remove std::move call here mOld = std::move(model.getRecord (id).clone()); ^~~~~~~~~~ ~ 2 warnings generated. --- apps/opencs/model/world/commands.cpp | 4 ++-- apps/opencs/model/world/refidcollection.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index e69600eff4..7c5bafb52f 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -295,7 +295,7 @@ CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, Q { setText (("Revert record " + id).c_str()); - mOld = std::move(model.getRecord (id).clone()); + mOld = model.getRecord (id).clone(); } CSMWorld::RevertCommand::~RevertCommand() @@ -330,7 +330,7 @@ CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, { setText (("Delete record " + id).c_str()); - mOld = std::move(model.getRecord (id).clone()); + mOld = model.getRecord (id).clone(); } CSMWorld::DeleteCommand::~DeleteCommand() diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 7f3e71106c..03337106ae 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -805,8 +805,7 @@ void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin, const std::string& destination, const CSMWorld::UniversalId::Type type) { - std::unique_ptr newRecord = - std::move(mData.getRecord(mData.searchId(origin)).modifiedCopy()); + std::unique_ptr newRecord = mData.getRecord(mData.searchId(origin)).modifiedCopy(); mAdapters.find(type)->second->setId(*newRecord, destination); mData.insertRecord(std::move(newRecord), type, destination); } From 139119415283a7f6c0d8b7431b1cdf2125311f5a Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 1 Aug 2021 13:11:16 +0200 Subject: [PATCH 1122/2859] Remove unneeded return statement --- apps/opencs/model/world/refidcollection.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 03337106ae..4ee99a2702 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -764,7 +764,6 @@ void CSMWorld::RefIdCollection::setNestedData(int row, int column, const QVarian const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); nestedAdapter.setNestedData(&mColumns.at (column), mData, localIndex.first, data, subRow, subColumn); - return; } void CSMWorld::RefIdCollection::removeRows (int index, int count) @@ -778,7 +777,6 @@ void CSMWorld::RefIdCollection::removeNestedRows(int row, int column, int subRow const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); nestedAdapter.removeNestedRow(&mColumns.at (column), mData, localIndex.first, subRow); - return; } void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) @@ -895,7 +893,6 @@ void CSMWorld::RefIdCollection::addNestedRow(int row, int col, int position) const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(col), localIndex.second); nestedAdapter.addNestedRow(&mColumns.at(col), mData, localIndex.first, position); - return; } void CSMWorld::RefIdCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) @@ -904,7 +901,6 @@ void CSMWorld::RefIdCollection::setNestedTable(int row, int column, const CSMWor const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); nestedAdapter.setNestedTable(&mColumns.at(column), mData, localIndex.first, nestedTable); - return; } CSMWorld::NestedTableWrapperBase* CSMWorld::RefIdCollection::nestedTable(int row, int column) const From 09ee2a0a36bc029e9544170e90d5938e2827e9c2 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 1 Aug 2021 19:42:41 +0300 Subject: [PATCH 1123/2859] fix texture brush index search --- apps/opencs/view/widget/scenetooltexturebrush.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index 272a5de42e..80eca21785 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -179,10 +179,10 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture) undoStack.endMacro(); } - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) + if (index != -1 && !landtexturesCollection.getRecord(rowInNew).isDeleted()) { mBrushTextureLabel = "Selected texture: " + newBrushTextureId + " "; - mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value()); + mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(rowInNew, landTextureFilename).value()); } else { newBrushTextureId = ""; From 0b9b7240200247e18b42cf839afac0bdd7a5d28e Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 1 Aug 2021 18:19:39 -0700 Subject: [PATCH 1124/2859] correct reflection label for in-game settings menu --- files/mygui/openmw_settings_window.layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index b602f35260..a07d8125d5 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -443,7 +443,7 @@ - + From 1e52ca2b642e9f1f11e915e4e3189e16598cb1b3 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 1 Aug 2021 23:46:45 -0700 Subject: [PATCH 1125/2859] properly initialize light settings --- components/sceneutil/lightmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 2c83c43fef..63a475566b 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -884,8 +884,6 @@ namespace SceneUtil std::string lightingMethodString = Settings::Manager::getString("lighting method", "Shaders"); auto lightingMethod = LightManager::getLightingMethodFromString(lightingMethodString); - updateSettings(); - static bool hasLoggedWarnings = false; if (lightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings) @@ -904,6 +902,8 @@ namespace SceneUtil else initSingleUBO(targetLights); + updateSettings(); + getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); addCullCallback(new LightManagerCullCallback(this)); From bc738c56406384d22ba0756fe3641e0070cf2fc5 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 3 Aug 2021 07:00:42 +0200 Subject: [PATCH 1126/2859] Use extract/insert instead of erase/emplace When we call moveObject(), we might trigger a change of cell for the actor, which in turn triggers updatePtr(). The erase/emplace construct invalidate references, whereas extract/insert do not. The reason is was working before !1075 is because we were always "refreshing" the reference by a call to getActor(). --- apps/openmw/mwphysics/physicssystem.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 67766e4bf9..85fb463a95 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -519,13 +519,12 @@ namespace MWPhysics mObjects.emplace(updated, std::move(obj)); } - ActorMap::iterator foundActor = mActors.find(old); - if (foundActor != mActors.end()) + auto actorNode = mActors.extract(old); + if (!actorNode.empty()) { - auto actor = foundActor->second; - actor->updatePtr(updated); - mActors.erase(foundActor); - mActors.emplace(updated, std::move(actor)); + actorNode.key() = updated; + actorNode.mapped()->updatePtr(updated); + mActors.insert(std::move(actorNode)); } for (auto& [_, actor] : mActors) From 4574e5f565cec7c60aa7e962fc8ea776ed47d9fb Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Aug 2021 01:27:41 +0200 Subject: [PATCH 1127/2859] Remove redundant Navigator API functions --- .../detournavigator/navigator.cpp | 30 +++++++++---------- components/detournavigator/navigator.hpp | 18 ----------- components/detournavigator/navigatorimpl.cpp | 14 ++------- components/detournavigator/navigatorimpl.hpp | 4 --- components/detournavigator/navigatorstub.hpp | 10 ------- 5 files changed, 17 insertions(+), 59 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index a75275079b..05d8b4b663 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -221,7 +221,7 @@ namespace Vec3fEq(204, -204, 1.99998295307159423828125) )) << mPath; - mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&compoundShape), ObjectShapes(compoundShape, nullptr), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -273,7 +273,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&compoundShape), ObjectShapes(compoundShape, nullptr), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -307,7 +307,7 @@ namespace compoundShape.updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); - mNavigator->updateObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); + mNavigator->updateObject(ObjectId(&compoundShape), ObjectShapes(compoundShape, nullptr), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -364,8 +364,8 @@ namespace shape2.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&shape2), shape2, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), ObjectShapes(shape, nullptr), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape2), ObjectShapes(shape2, nullptr), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -670,7 +670,7 @@ namespace shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), ObjectShapes(shape, nullptr), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -678,7 +678,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), ObjectShapes(shape, nullptr), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -813,7 +813,7 @@ namespace for (std::size_t i = 0; i < boxShapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10, i * 10, i * 10)); - mNavigator->addObject(ObjectId(&boxShapes[i]), boxShapes[i], transform); + mNavigator->addObject(ObjectId(&boxShapes[i]), ObjectShapes(boxShapes[i], nullptr), transform); } std::this_thread::sleep_for(std::chrono::microseconds(1)); @@ -821,7 +821,7 @@ namespace for (std::size_t i = 0; i < boxShapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10 + 1, i * 10 + 1, i * 10 + 1)); - mNavigator->updateObject(ObjectId(&boxShapes[i]), boxShapes[i], transform); + mNavigator->updateObject(ObjectId(&boxShapes[i]), ObjectShapes(boxShapes[i], nullptr), transform); } mNavigator->update(mPlayerPosition); @@ -865,7 +865,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); - mNavigator->addObject(ObjectId(&shapes[i]), shapes[i], transform); + mNavigator->addObject(ObjectId(&shapes[i]), ObjectShapes(shapes[i], nullptr), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -874,7 +874,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1)); - mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform); + mNavigator->updateObject(ObjectId(&shapes[i]), ObjectShapes(shapes[i], nullptr), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -882,7 +882,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2)); - mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform); + mNavigator->updateObject(ObjectId(&shapes[i]), ObjectShapes(shapes[i], nullptr), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -932,10 +932,10 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - mNavigator->addObject(ObjectId(&oscillatingBoxShape), oscillatingBoxShape, + mNavigator->addObject(ObjectId(&oscillatingBoxShape), ObjectShapes(oscillatingBoxShape, nullptr), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations - mNavigator->addObject(ObjectId(&boderBoxShape), boderBoxShape, + mNavigator->addObject(ObjectId(&boderBoxShape), ObjectShapes(boderBoxShape, nullptr), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -952,7 +952,7 @@ namespace { const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10), oscillatingBoxShapePosition); - mNavigator->updateObject(ObjectId(&oscillatingBoxShape), oscillatingBoxShape, transform); + mNavigator->updateObject(ObjectId(&oscillatingBoxShape), ObjectShapes(oscillatingBoxShape, nullptr), transform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 6e0f773fb6..e1c4694642 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -72,15 +72,6 @@ namespace DetourNavigator */ virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; - /** - * @brief addObject is used to add object represented by single btCollisionShape and btTransform. - * @param id is used to distinguish different objects. - * @param shape must live until object is updated by another shape removed from Navigator. - * @param transform allows to setup object geometry according to its world state. - * @return true if object is added, false if there is already object with given id. - */ - virtual bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) = 0; - /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes * @param id is used to distinguish different objects @@ -99,15 +90,6 @@ namespace DetourNavigator */ virtual bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; - /** - * @brief updateObject replace object geometry by given data. - * @param id is used to find object. - * @param shape must live until object is updated by another shape removed from Navigator. - * @param transform allows to setup objects geometry according to its world state. - * @return true if object is updated, false if there is no object with given id. - */ - virtual bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) = 0; - /** * @brief updateObject replace object geometry by given data. * @param id is used to find object. diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index e256d8f76f..2168497cc3 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -32,14 +32,9 @@ namespace DetourNavigator --it->second; } - bool NavigatorImpl::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) - { - return mNavMeshManager.addObject(id, shape, transform, AreaType_ground); - } - bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - bool result = addObject(id, shapes.mShape, transform); + bool result = mNavMeshManager.addObject(id, shapes.mShape, transform, AreaType_ground); if (shapes.mAvoid) { const ObjectId avoidId(shapes.mAvoid); @@ -65,14 +60,9 @@ namespace DetourNavigator return false; } - bool NavigatorImpl::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) - { - return mNavMeshManager.updateObject(id, shape, transform, AreaType_ground); - } - bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - bool result = updateObject(id, shapes.mShape, transform); + bool result = mNavMeshManager.updateObject(id, shapes.mShape, transform, AreaType_ground); if (shapes.mAvoid) { const ObjectId avoidId(shapes.mAvoid); diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 35a6888551..cda6158958 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -21,14 +21,10 @@ namespace DetourNavigator void removeAgent(const osg::Vec3f& agentHalfExtents) override; - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) override; - bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; - bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) override; - bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 2d2fc936ca..40de90f256 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -19,11 +19,6 @@ namespace DetourNavigator void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} - bool addObject(const ObjectId /*id*/, const btCollisionShape& /*shape*/, const btTransform& /*transform*/) override - { - return false; - } - bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override { return false; @@ -34,11 +29,6 @@ namespace DetourNavigator return false; } - bool updateObject(const ObjectId /*id*/, const btCollisionShape& /*shape*/, const btTransform& /*transform*/) override - { - return false; - } - bool updateObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override { return false; From c8987bda2f1c19fe877c90ae20f9e4ca5904e806 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Aug 2021 02:13:55 +0200 Subject: [PATCH 1128/2859] Store reference to BulletShapeInstance for btCollisionShape To keep btCollisionShape lifetime. --- apps/openmw/mwworld/scene.cpp | 16 +-- apps/openmw/mwworld/worldimp.cpp | 5 +- .../detournavigator/navigator.cpp | 116 +++++++++++------- .../detournavigator/recastmeshobject.cpp | 16 +-- .../tilecachedrecastmeshmanager.cpp | 75 ++++++----- .../cachedrecastmeshmanager.cpp | 2 +- .../cachedrecastmeshmanager.hpp | 2 +- components/detournavigator/navigator.hpp | 13 +- components/detournavigator/navigatorimpl.cpp | 20 +-- components/detournavigator/navmeshmanager.cpp | 7 +- components/detournavigator/navmeshmanager.hpp | 4 +- .../detournavigator/recastmeshmanager.cpp | 2 +- .../detournavigator/recastmeshmanager.hpp | 2 +- .../detournavigator/recastmeshobject.cpp | 45 ++++--- .../detournavigator/recastmeshobject.hpp | 27 +++- .../tilecachedrecastmeshmanager.cpp | 6 +- .../tilecachedrecastmeshmanager.hpp | 8 +- components/resource/bulletshape.cpp | 4 + 18 files changed, 221 insertions(+), 149 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 8c401dcf12..3de4940a37 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -151,11 +151,9 @@ namespace { if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport()) { - const auto shape = object->getShapeInstance()->getCollisionShape(); - btVector3 aabbMin; btVector3 aabbMax; - shape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); const auto center = (aabbMax + aabbMin) * 0.5f; @@ -182,12 +180,7 @@ namespace navigator.addObject( DetourNavigator::ObjectId(object), - DetourNavigator::DoorShapes( - *shape, - object->getShapeInstance()->getAvoidCollisionShape(), - connectionStart, - connectionEnd - ), + DetourNavigator::DoorShapes(object->getShapeInstance(), connectionStart, connectionEnd), transform ); } @@ -195,10 +188,7 @@ namespace { navigator.addObject( DetourNavigator::ObjectId(object), - DetourNavigator::ObjectShapes { - *object->getShapeInstance()->getCollisionShape(), - object->getShapeInstance()->getAvoidCollisionShape() - }, + DetourNavigator::ObjectShapes(object->getShapeInstance()), object->getTransform() ); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5fcab477cf..54b15e4412 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1533,10 +1533,7 @@ namespace MWWorld void World::updateNavigatorObject(const MWPhysics::Object& object) { - const DetourNavigator::ObjectShapes shapes { - *object.getShapeInstance()->getCollisionShape(), - object.getShapeInstance()->getAvoidCollisionShape() - }; + const DetourNavigator::ObjectShapes shapes(object.getShapeInstance()); mShouldUpdateNavigator = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform()) || mShouldUpdateNavigator; } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 05d8b4b663..35fde5b657 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -5,6 +5,9 @@ #include #include #include +#include + +#include #include #include @@ -15,6 +18,7 @@ #include #include +#include MATCHER_P3(Vec3fEq, x, y, z, "") { @@ -84,14 +88,15 @@ namespace }; template - btHeightfieldTerrainShape makeSquareHeightfieldTerrainShape(const std::array& values, + std::unique_ptr makeSquareHeightfieldTerrainShape(const std::array& values, btScalar heightScale = 1, int upAxis = 2, PHY_ScalarType heightDataType = PHY_FLOAT, bool flipQuadEdges = false) { const int width = static_cast(std::sqrt(size)); const btScalar min = *std::min_element(values.begin(), values.end()); const btScalar max = *std::max_element(values.begin(), values.end()); const btScalar greater = std::max(std::abs(min), std::abs(max)); - return btHeightfieldTerrainShape(width, width, values.data(), heightScale, -greater, greater, upAxis, heightDataType, flipQuadEdges); + return std::make_unique(width, width, values.data(), heightScale, -greater, greater, + upAxis, heightDataType, flipQuadEdges); } template @@ -107,6 +112,27 @@ namespace return surface; } + template + osg::ref_ptr makeBulletShapeInstance(std::unique_ptr&& shape) + { + osg::ref_ptr bulletShape(new Resource::BulletShape); + bulletShape->mCollisionShape = std::move(shape).release(); + return new Resource::BulletShapeInstance(bulletShape); + } + + template + class CollisionShapeInstance + { + public: + CollisionShapeInstance(std::unique_ptr&& shape) : mInstance(makeBulletShapeInstance(std::move(shape))) {} + + T& shape() { return static_cast(*mInstance->mCollisionShape); } + const osg::ref_ptr& instance() const { return mInstance; } + + private: + osg::ref_ptr mInstance; + }; + TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), @@ -185,9 +211,8 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - btBoxShape boxShape(btVector3(20, 20, 100)); - btCompoundShape compoundShape; - compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape); + CollisionShapeInstance compound(std::make_unique()); + compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); @@ -221,7 +246,7 @@ namespace Vec3fEq(204, -204, 1.99998295307159423828125) )) << mPath; - mNavigator->addObject(ObjectId(&compoundShape), ObjectShapes(compoundShape, nullptr), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -267,13 +292,12 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - btBoxShape boxShape(btVector3(20, 20, 100)); - btCompoundShape compoundShape; - compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape); + CollisionShapeInstance compound(std::make_unique()); + compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - mNavigator->addObject(ObjectId(&compoundShape), ObjectShapes(compoundShape, nullptr), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -305,9 +329,9 @@ namespace Vec3fEq(204, -204, 1.99998295307159423828125) )) << mPath; - compoundShape.updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); + compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); - mNavigator->updateObject(ObjectId(&compoundShape), ObjectShapes(compoundShape, nullptr), btTransform::getIdentity()); + mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -350,8 +374,8 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData1); - shape.setLocalScaling(btVector3(128, 128, 1)); + CollisionShapeInstance heightfield1(makeSquareHeightfieldTerrainShape(heightfieldData1)); + heightfield1.shape().setLocalScaling(btVector3(128, 128, 1)); const std::array heightfieldData2 {{ -25, -25, -25, -25, -25, @@ -360,12 +384,12 @@ namespace -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; - btHeightfieldTerrainShape shape2 = makeSquareHeightfieldTerrainShape(heightfieldData2); - shape2.setLocalScaling(btVector3(128, 128, 1)); + CollisionShapeInstance heightfield2(makeSquareHeightfieldTerrainShape(heightfieldData2)); + heightfield2.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), ObjectShapes(shape, nullptr), btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&shape2), ObjectShapes(shape2, nullptr), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance()), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -424,6 +448,8 @@ namespace TEST_F(DetourNavigatorNavigatorTest, path_should_be_around_avoid_shape) { + osg::ref_ptr bulletShape(new Resource::BulletShape); + std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, @@ -431,8 +457,9 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); - shape.setLocalScaling(btVector3(128, 128, 1)); + std::unique_ptr shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + shapePtr->setLocalScaling(btVector3(128, 128, 1)); + bulletShape->mCollisionShape = shapePtr.release(); std::array heightfieldDataAvoid {{ -25, -25, -25, -25, -25, @@ -441,11 +468,14 @@ namespace -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; - btHeightfieldTerrainShape shapeAvoid = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid); - shapeAvoid.setLocalScaling(btVector3(128, 128, 1)); + std::unique_ptr shapeAvoidPtr = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid); + shapeAvoidPtr->setLocalScaling(btVector3(128, 128, 1)); + bulletShape->mAvoidCollisionShape = shapeAvoidPtr.release(); + + osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), ObjectShapes {shape, &shapeAvoid}, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(instance->getCollisionShape()), ObjectShapes(instance), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -666,19 +696,19 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); - shape.setLocalScaling(btVector3(128, 128, 1)); + CollisionShapeInstance heightfield(makeSquareHeightfieldTerrainShape(heightfieldData)); + heightfield.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), ObjectShapes(shape, nullptr), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->removeObject(ObjectId(&shape)); + mNavigator->removeObject(ObjectId(&heightfield.shape())); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->addObject(ObjectId(&shape), ObjectShapes(shape, nullptr), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -804,24 +834,25 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const std::vector boxShapes(100, btVector3(20, 20, 100)); + std::vector> boxes; + std::generate_n(std::back_inserter(boxes), 100, [] { return std::make_unique(btVector3(20, 20, 100)); }); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - for (std::size_t i = 0; i < boxShapes.size(); ++i) + for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10, i * 10, i * 10)); - mNavigator->addObject(ObjectId(&boxShapes[i]), ObjectShapes(boxShapes[i], nullptr), transform); + mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); } std::this_thread::sleep_for(std::chrono::microseconds(1)); - for (std::size_t i = 0; i < boxShapes.size(); ++i) + for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10 + 1, i * 10 + 1, i * 10 + 1)); - mNavigator->updateObject(ObjectId(&boxShapes[i]), ObjectShapes(boxShapes[i], nullptr), transform); + mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); } mNavigator->update(mPlayerPosition); @@ -858,14 +889,15 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_changed_multiple_times_object_should_delay_navmesh_change) { - const std::vector shapes(100, btVector3(64, 64, 64)); + std::vector> shapes; + std::generate_n(std::back_inserter(shapes), 100, [] { return std::make_unique(btVector3(64, 64, 64)); }); mNavigator->addAgent(mAgentHalfExtents); for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); - mNavigator->addObject(ObjectId(&shapes[i]), ObjectShapes(shapes[i], nullptr), transform); + mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -874,7 +906,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1)); - mNavigator->updateObject(ObjectId(&shapes[i]), ObjectShapes(shapes[i], nullptr), transform); + mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -882,7 +914,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2)); - mNavigator->updateObject(ObjectId(&shapes[i]), ObjectShapes(shapes[i], nullptr), transform); + mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -926,16 +958,16 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const btBoxShape oscillatingBoxShape(btVector3(20, 20, 20)); + CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); const btVector3 oscillatingBoxShapePosition(32, 32, 400); - const btBoxShape boderBoxShape(btVector3(50, 50, 50)); + CollisionShapeInstance boderBox(std::make_unique(btVector3(50, 50, 50))); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - mNavigator->addObject(ObjectId(&oscillatingBoxShape), ObjectShapes(oscillatingBoxShape, nullptr), + mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations - mNavigator->addObject(ObjectId(&boderBoxShape), ObjectShapes(boderBoxShape, nullptr), + mNavigator->addObject(ObjectId(&boderBox.shape()), ObjectShapes(boderBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -952,7 +984,7 @@ namespace { const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10), oscillatingBoxShapePosition); - mNavigator->updateObject(ObjectId(&oscillatingBoxShape), ObjectShapes(oscillatingBoxShape, nullptr), transform); + mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), transform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); } diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index 621db51a89..7751d5220c 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -14,20 +14,22 @@ namespace struct DetourNavigatorRecastMeshObjectTest : Test { - btBoxShape mBoxShape {btVector3(1, 2, 3)}; - btCompoundShape mCompoundShape {true}; + btBoxShape mBoxShapeImpl {btVector3(1, 2, 3)}; + CollisionShape mBoxShape {nullptr, mBoxShapeImpl}; + btCompoundShape mCompoundShapeImpl {true}; + CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl}; btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; DetourNavigatorRecastMeshObjectTest() { - mCompoundShape.addChildShape(mTransform, std::addressof(mBoxShape)); + mCompoundShapeImpl.addChildShape(mTransform, std::addressof(mBoxShapeImpl)); } }; TEST_F(DetourNavigatorRecastMeshObjectTest, constructed_object_should_have_shape_and_transform) { const RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); - EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShape)); + EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShapeImpl)); EXPECT_EQ(object.getTransform(), mTransform); } @@ -58,14 +60,14 @@ namespace TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_changed_child_transform_should_return_true) { RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); - mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); + mCompoundShapeImpl.updateChildTransform(0, btTransform::getIdentity()); EXPECT_TRUE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, repeated_update_for_compound_shape_without_changes_should_return_false) { RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); - mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); + mCompoundShapeImpl.updateChildTransform(0, btTransform::getIdentity()); object.update(mTransform, AreaType_ground); EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } @@ -73,7 +75,7 @@ namespace TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_changed_local_scaling_should_return_true) { RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); - mBoxShape.setLocalScaling(btVector3(2, 2, 2)); + mBoxShapeImpl.setLocalScaling(btVector3(2, 2, 2)); EXPECT_TRUE(object.update(mTransform, AreaType_ground)); } } diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 4c49e75dc9..51580906ce 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -62,22 +62,25 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const CollisionShape shape(nullptr, boxShape); + EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const CollisionShape shape(nullptr, boxShape); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); @@ -88,8 +91,9 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground); - EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); EXPECT_THAT( mChangedTiles, @@ -102,8 +106,9 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); EXPECT_EQ(mChangedTiles, std::vector()); } @@ -112,7 +117,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); @@ -123,7 +129,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); } @@ -132,14 +139,15 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + const CollisionShape shape(nullptr, boxShape); - manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); @@ -151,12 +159,13 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + const CollisionShape shape(nullptr, boxShape); - manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); } @@ -165,7 +174,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); @@ -177,14 +187,15 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(nullptr, boxShape); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); @@ -196,7 +207,8 @@ namespace TileCachedRecastMeshManager manager(mSettings); const auto initialRevision = manager.getRevision(); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getRevision(), initialRevision + 1); } @@ -204,9 +216,10 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeAddRevision = manager.getRevision(); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getRevision(), beforeAddRevision); } @@ -215,9 +228,10 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); } @@ -225,9 +239,10 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); } @@ -235,7 +250,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeRemoveRevision = manager.getRevision(); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); @@ -272,7 +288,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const CollisionShape shape(nullptr, boxShape); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = std::numeric_limits::max(); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); @@ -314,7 +331,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const CollisionShape shape(nullptr, boxShape); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); @@ -330,7 +348,8 @@ namespace const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; const btBoxShape boxShape(btVector3(20, 20, 100)); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const CollisionShape shape(nullptr, boxShape); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); for (int x = -6; x < 6; ++x) diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 154da806d7..36f5e9e03a 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -8,7 +8,7 @@ namespace DetourNavigator : mImpl(settings, bounds, generation) {} - bool CachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, + bool CachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { if (!mImpl.addObject(id, shape, transform, areaType)) diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index ab7bb9e7cb..1af249404b 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -12,7 +12,7 @@ namespace DetourNavigator public: CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation); - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index e1c4694642..1a78b602cd 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -10,6 +10,8 @@ #include "waitconditiontype.hpp" #include "heightfieldshape.hpp" +#include + #include namespace ESM @@ -27,11 +29,10 @@ namespace DetourNavigator { struct ObjectShapes { - const btCollisionShape& mShape; - const btCollisionShape* mAvoid; + osg::ref_ptr mShapeInstance; - ObjectShapes(const btCollisionShape& shape, const btCollisionShape* avoid) - : mShape(shape), mAvoid(avoid) + ObjectShapes(const osg::ref_ptr& shapeInstance) + : mShapeInstance(shapeInstance) {} }; @@ -40,9 +41,9 @@ namespace DetourNavigator osg::Vec3f mConnectionStart; osg::Vec3f mConnectionEnd; - DoorShapes(const btCollisionShape& shape, const btCollisionShape* avoid, + DoorShapes(const osg::ref_ptr& shapeInstance, const osg::Vec3f& connectionStart,const osg::Vec3f& connectionEnd) - : ObjectShapes(shape, avoid) + : ObjectShapes(shapeInstance) , mConnectionStart(connectionStart) , mConnectionEnd(connectionEnd) {} diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 2168497cc3..5a8488c8d0 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -34,11 +34,13 @@ namespace DetourNavigator bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - bool result = mNavMeshManager.addObject(id, shapes.mShape, transform, AreaType_ground); - if (shapes.mAvoid) + CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->getCollisionShape()}; + bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); + if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { - const ObjectId avoidId(shapes.mAvoid); - if (mNavMeshManager.addObject(avoidId, *shapes.mAvoid, transform, AreaType_null)) + const ObjectId avoidId(avoidShape); + CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; + if (mNavMeshManager.addObject(avoidId, collisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; @@ -62,11 +64,13 @@ namespace DetourNavigator bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - bool result = mNavMeshManager.updateObject(id, shapes.mShape, transform, AreaType_ground); - if (shapes.mAvoid) + const CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->getCollisionShape()}; + bool result = mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground); + if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { - const ObjectId avoidId(shapes.mAvoid); - if (mNavMeshManager.updateObject(avoidId, *shapes.mAvoid, transform, AreaType_null)) + const ObjectId avoidId(avoidShape); + const CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; + if (mNavMeshManager.updateObject(avoidId, collisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 287a3c8ef9..c378230845 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -47,16 +47,17 @@ namespace DetourNavigator , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) {} - bool NavMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { + const btCollisionShape& collisionShape = shape.getShape(); if (!mRecastMeshManager.addObject(id, shape, transform, areaType)) return false; - addChangedTiles(shape, transform, ChangeType::add); + addChangedTiles(collisionShape, transform, ChangeType::add); return true; } - bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { return mRecastMeshManager.updateObject(id, shape, transform, areaType, diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 390ba41ec8..76c9b1e0b9 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -24,10 +24,10 @@ namespace DetourNavigator public: NavMeshManager(const Settings& settings); - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool removeObject(const ObjectId id); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 151b3161c1..872996d9e6 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -35,7 +35,7 @@ namespace DetourNavigator { } - bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool RecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { const auto object = mObjects.lower_bound(id); diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index 88124c44cf..fb7018d922 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -35,7 +35,7 @@ namespace DetourNavigator public: RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation); - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 3f35462781..862460b34b 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -22,15 +22,36 @@ namespace DetourNavigator } return result; } + + std::vector makeChildrenObjects(const osg::ref_ptr& instance, + const btCompoundShape& shape, const AreaType areaType) + { + std::vector result; + for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) + { + const CollisionShape collisionShape {instance, *shape.getChildShape(i)}; + result.emplace_back(collisionShape, shape.getChildTransform(i), areaType); + } + return result; + } + + std::vector makeChildrenObjects(const osg::ref_ptr& instance, + const btCollisionShape& shape, const AreaType areaType) + { + if (shape.isCompound()) + return makeChildrenObjects(std::move(instance), static_cast(shape), areaType); + return std::vector(); + } } - RecastMeshObject::RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, + RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType) - : mShape(shape) + : mShapeInstance(shape.getShapeInstance()) + , mShape(shape.getShape()) , mTransform(transform) , mAreaType(areaType) - , mLocalScaling(shape.getLocalScaling()) - , mChildren(makeChildrenObjects(shape, mAreaType)) + , mLocalScaling(mShape.get().getLocalScaling()) + , mChildren(makeChildrenObjects(mShapeInstance, mShape.get(), mAreaType)) { } @@ -57,20 +78,4 @@ namespace DetourNavigator || result; return result; } - - std::vector makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType) - { - if (shape.isCompound()) - return makeChildrenObjects(static_cast(shape), areaType); - else - return std::vector(); - } - - std::vector makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType) - { - std::vector result; - for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) - result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i), areaType); - return result; - } } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index e659300e68..81199c5bad 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -3,8 +3,12 @@ #include "areatype.hpp" +#include + #include +#include + #include #include @@ -13,10 +17,26 @@ class btCompoundShape; namespace DetourNavigator { + class CollisionShape + { + public: + CollisionShape(osg::ref_ptr instance, const btCollisionShape& shape) + : mShapeInstance(std::move(instance)) + , mShape(shape) + {} + + const osg::ref_ptr& getShapeInstance() const { return mShapeInstance; } + const btCollisionShape& getShape() const { return mShape; } + + private: + osg::ref_ptr mShapeInstance; + std::reference_wrapper mShape; + }; + class RecastMeshObject { public: - RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); + RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool update(const btTransform& transform, const AreaType areaType); @@ -36,16 +56,13 @@ namespace DetourNavigator } private: + osg::ref_ptr mShapeInstance; std::reference_wrapper mShape; btTransform mTransform; AreaType mAreaType; btVector3 mLocalScaling; std::vector mChildren; }; - - std::vector makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType); - - std::vector makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType); } #endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index a37d24399d..61bd973cbf 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -14,14 +14,14 @@ namespace DetourNavigator : mSettings(settings) {} - bool TileCachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, + bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { std::vector tilesPositions; const auto border = getBorderSize(mSettings); { auto tiles = mTiles.lock(); - getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) + getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) tilesPositions.push_back(tilePosition); @@ -218,7 +218,7 @@ namespace DetourNavigator it->second.reportNavMeshChange(recastMeshVersion, navMeshVersion); } - bool TileCachedRecastMeshManager::addTile(const ObjectId id, const btCollisionShape& shape, + bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, std::map& tiles) { diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f3292cbf9b..38d004ccca 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -22,11 +22,11 @@ namespace DetourNavigator public: TileCachedRecastMeshManager(const Settings& settings); - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); template - bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, OnChangedTile&& onChangedTile) { const auto object = mObjectsTilesPositions.find(id); @@ -56,7 +56,7 @@ namespace DetourNavigator changed = true; } }; - getTilesPositions(shape, transform, mSettings, onTilePosition); + getTilesPositions(shape.getShape(), transform, mSettings, onTilePosition); std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { @@ -110,7 +110,7 @@ namespace DetourNavigator std::size_t mRevision = 0; std::size_t mTilesGeneration = 0; - bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, std::map& tiles); diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index c6f6369dba..ce896610a9 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace Resource { @@ -75,6 +76,9 @@ btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *s return new btBoxShape(*boxshape); } + if (shape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) + return new btHeightfieldTerrainShape(static_cast(*shape)); + throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); } From 050b7d31aac95b97a69fa9b122e75297327cbfba Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Aug 2021 02:43:36 +0200 Subject: [PATCH 1129/2859] Create RecastMesh outside critical section To not lock main thread when it tries to update objects. --- .../cachedrecastmeshmanager.cpp | 23 +++++---- .../cachedrecastmeshmanager.hpp | 4 +- .../detournavigator/recastmeshmanager.cpp | 32 +++++++++--- .../detournavigator/recastmeshmanager.hpp | 6 ++- .../tilecachedrecastmeshmanager.cpp | 50 +++++++++++-------- .../tilecachedrecastmeshmanager.hpp | 13 ++--- 6 files changed, 79 insertions(+), 49 deletions(-) diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 36f5e9e03a..e7e5886589 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -13,7 +13,7 @@ namespace DetourNavigator { if (!mImpl.addObject(id, shape, transform, areaType)) return false; - mCached.reset(); + mCached.lock()->reset(); return true; } @@ -21,7 +21,7 @@ namespace DetourNavigator { if (!mImpl.updateObject(id, transform, areaType)) return false; - mCached.reset(); + mCached.lock()->reset(); return true; } @@ -29,7 +29,7 @@ namespace DetourNavigator { const auto object = mImpl.removeObject(id); if (object) - mCached.reset(); + mCached.lock()->reset(); return object; } @@ -38,7 +38,7 @@ namespace DetourNavigator { if (!mImpl.addWater(cellPosition, cellSize, shift)) return false; - mCached.reset(); + mCached.lock()->reset(); return true; } @@ -46,7 +46,7 @@ namespace DetourNavigator { const auto water = mImpl.removeWater(cellPosition); if (water) - mCached.reset(); + mCached.lock()->reset(); return water; } @@ -55,7 +55,7 @@ namespace DetourNavigator { if (!mImpl.addHeightfield(cellPosition, cellSize, shift, shape)) return false; - mCached.reset(); + mCached.lock()->reset(); return true; } @@ -63,15 +63,18 @@ namespace DetourNavigator { const auto cell = mImpl.removeHeightfield(cellPosition); if (cell) - mCached.reset(); + mCached.lock()->reset(); return cell; } std::shared_ptr CachedRecastMeshManager::getMesh() { - if (!mCached) - mCached = mImpl.getMesh(); - return mCached; + std::shared_ptr cached = *mCached.lock(); + if (cached != nullptr) + return cached; + cached = mImpl.getMesh(); + *mCached.lock() = cached; + return cached; } bool CachedRecastMeshManager::isEmpty() const diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 1af249404b..b506f807fa 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -5,6 +5,8 @@ #include "version.hpp" #include "heightfieldshape.hpp" +#include + namespace DetourNavigator { class CachedRecastMeshManager @@ -38,7 +40,7 @@ namespace DetourNavigator private: RecastMeshManager mImpl; - std::shared_ptr mCached; + Misc::ScopeGuarded> mCached; }; } diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 872996d9e6..5bbbbe4dea 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -38,6 +38,7 @@ namespace DetourNavigator bool RecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { + const std::lock_guard lock(mMutex); const auto object = mObjects.lower_bound(id); if (object != mObjects.end() && object->first == id) return false; @@ -49,6 +50,7 @@ namespace DetourNavigator bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { + const std::lock_guard lock(mMutex); const auto object = mObjects.find(id); if (object == mObjects.end()) return false; @@ -62,6 +64,7 @@ namespace DetourNavigator std::optional RecastMeshManager::removeObject(const ObjectId id) { + const std::lock_guard lock(mMutex); const auto object = mObjects.find(id); if (object == mObjects.end()) return std::nullopt; @@ -73,6 +76,7 @@ namespace DetourNavigator bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift) { + const std::lock_guard lock(mMutex); if (!mWater.emplace(cellPosition, Cell {cellSize, shift}).second) return false; ++mRevision; @@ -81,6 +85,7 @@ namespace DetourNavigator std::optional RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { + const std::lock_guard lock(mMutex); const auto water = mWater.find(cellPosition); if (water == mWater.end()) return std::nullopt; @@ -93,6 +98,7 @@ namespace DetourNavigator bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, const HeightfieldShape& shape) { + const std::lock_guard lock(mMutex); if (!mHeightfields.emplace(cellPosition, Heightfield {Cell {cellSize, shift}, shape}).second) return false; ++mRevision; @@ -101,6 +107,7 @@ namespace DetourNavigator std::optional RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { + const std::lock_guard lock(mMutex); const auto it = mHeightfields.find(cellPosition); if (it == mHeightfields.end()) return std::nullopt; @@ -116,20 +123,27 @@ namespace DetourNavigator tileBounds.mMin /= mSettings.mRecastScaleFactor; tileBounds.mMax /= mSettings.mRecastScaleFactor; RecastMeshBuilder builder(tileBounds); - for (const auto& [k, v] : mWater) - builder.addWater(v.mSize, v.mShift); - for (const auto& [k, object] : mObjects) + std::vector objects; + std::size_t revision; { - const RecastMeshObject& v = object.getImpl(); - builder.addObject(v.getShape(), v.getTransform(), v.getAreaType()); + const std::lock_guard lock(mMutex); + for (const auto& [k, v] : mWater) + builder.addWater(v.mSize, v.mShift); + for (const auto& [cellPosition, v] : mHeightfields) + std::visit(AddHeightfield {v.mCell, builder}, v.mShape); + objects.reserve(mObjects.size()); + for (const auto& [k, object] : mObjects) + objects.push_back(object.getImpl()); + revision = mRevision; } - for (const auto& [cellPosition, v] : mHeightfields) - std::visit(AddHeightfield {v.mCell, builder}, v.mShape); - return std::move(builder).create(mGeneration, mRevision); + for (const auto& v : objects) + builder.addObject(v.getShape(), v.getTransform(), v.getAreaType()); + return std::move(builder).create(mGeneration, revision); } bool RecastMeshManager::isEmpty() const { + const std::lock_guard lock(mMutex); return mObjects.empty() && mWater.empty() && mHeightfields.empty(); } @@ -137,6 +151,7 @@ namespace DetourNavigator { if (recastMeshVersion.mGeneration != mGeneration) return; + const std::lock_guard lock(mMutex); if (mLastNavMeshReport.has_value() && navMeshVersion < mLastNavMeshReport->mNavMeshVersion) return; mLastNavMeshReport = {recastMeshVersion.mRevision, navMeshVersion}; @@ -147,6 +162,7 @@ namespace DetourNavigator Version RecastMeshManager::getVersion() const { + const std::lock_guard lock(mMutex); return Version {mGeneration, mRevision}; } } diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index fb7018d922..1cd35ca467 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -16,6 +16,7 @@ #include #include #include +#include class btCollisionShape; @@ -73,9 +74,10 @@ namespace DetourNavigator }; const Settings& mSettings; + const std::size_t mGeneration; + const TileBounds mTileBounds; + mutable std::mutex mMutex; std::size_t mRevision = 0; - std::size_t mGeneration; - TileBounds mTileBounds; std::map mObjects; std::map mWater; std::map mHeightfields; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 61bd973cbf..2b8c85b8a3 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -69,7 +69,7 @@ namespace DetourNavigator const auto tiles = mTiles.lock(); for (auto& tile : *tiles) { - if (tile.second.addWater(cellPosition, cellSize, shift)) + if (tile.second->addWater(cellPosition, cellSize, shift)) { tilesPositions.push_back(tile.first); result = true; @@ -88,9 +88,9 @@ namespace DetourNavigator tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); tile = tiles->insert(std::make_pair(tilePosition, - CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first; + std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; } - if (tile->second.addWater(cellPosition, cellSize, shift)) + if (tile->second->addWater(cellPosition, cellSize, shift)) { tilesPositions.push_back(tilePosition); result = true; @@ -116,8 +116,8 @@ namespace DetourNavigator const auto tile = tiles->find(tilePosition); if (tile == tiles->end()) continue; - const auto tileResult = tile->second.removeWater(cellPosition); - if (tile->second.isEmpty()) + const auto tileResult = tile->second->removeWater(cellPosition); + if (tile->second->isEmpty()) { tiles->erase(tile); ++mTilesGeneration; @@ -149,9 +149,9 @@ namespace DetourNavigator tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); tile = tiles->insert(std::make_pair(tilePosition, - CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first; + std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; } - if (tile->second.addHeightfield(cellPosition, cellSize, shift, shape)) + if (tile->second->addHeightfield(cellPosition, cellSize, shift, shape)) { tilesPositions.push_back(tilePosition); result = true; @@ -176,8 +176,8 @@ namespace DetourNavigator const auto tile = tiles->find(tilePosition); if (tile == tiles->end()) continue; - const auto tileResult = tile->second.removeHeightfield(cellPosition); - if (tile->second.isEmpty()) + const auto tileResult = tile->second->removeHeightfield(cellPosition); + if (tile->second->isEmpty()) { tiles->erase(tile); ++mTilesGeneration; @@ -192,11 +192,17 @@ namespace DetourNavigator std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) { - const auto tiles = mTiles.lock(); - const auto it = tiles->find(tilePosition); - if (it == tiles->end()) + const auto manager = [&] () -> std::shared_ptr + { + const auto tiles = mTiles.lock(); + const auto it = tiles->find(tilePosition); + if (it == tiles->end()) + return nullptr; + return it->second; + } (); + if (manager == nullptr) return nullptr; - return it->second.getMesh(); + return manager->getMesh(); } bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition) @@ -215,12 +221,12 @@ namespace DetourNavigator const auto it = tiles->find(tilePosition); if (it == tiles->end()) return; - it->second.reportNavMeshChange(recastMeshVersion, navMeshVersion); + it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); } bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, - std::map& tiles) + TilesMap& tiles) { auto tile = tiles.find(tilePosition); if (tile == tiles.end()) @@ -229,26 +235,26 @@ namespace DetourNavigator tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); tile = tiles.insert(std::make_pair( - tilePosition, CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first; + tilePosition, std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; } - return tile->second.addObject(id, shape, transform, areaType); + return tile->second->addObject(id, shape, transform, areaType); } bool TileCachedRecastMeshManager::updateTile(const ObjectId id, const btTransform& transform, - const AreaType areaType, const TilePosition& tilePosition, std::map& tiles) + const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles) { const auto tile = tiles.find(tilePosition); - return tile != tiles.end() && tile->second.updateObject(id, transform, areaType); + return tile != tiles.end() && tile->second->updateObject(id, transform, areaType); } std::optional TileCachedRecastMeshManager::removeTile(const ObjectId id, - const TilePosition& tilePosition, std::map& tiles) + const TilePosition& tilePosition, TilesMap& tiles) { const auto tile = tiles.find(tilePosition); if (tile == tiles.end()) return std::optional(); - const auto tileResult = tile->second.removeObject(id); - if (tile->second.isEmpty()) + const auto tileResult = tile->second->removeObject(id); + if (tile->second->isEmpty()) { tiles.erase(tile); ++mTilesGeneration; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 38d004ccca..f8cb5a8b0e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -94,7 +94,7 @@ namespace DetourNavigator void forEachTile(Function&& function) { for (auto& [tilePosition, recastMeshManager] : *mTiles.lock()) - function(tilePosition, recastMeshManager); + function(tilePosition, *recastMeshManager); } std::size_t getRevision() const; @@ -102,8 +102,10 @@ namespace DetourNavigator void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion); private: + using TilesMap = std::map>; + const Settings& mSettings; - Misc::ScopeGuarded> mTiles; + Misc::ScopeGuarded mTiles; std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::map> mHeightfieldTilesPositions; @@ -111,14 +113,13 @@ namespace DetourNavigator std::size_t mTilesGeneration = 0; bool addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType, const TilePosition& tilePosition, float border, - std::map& tiles); + const AreaType areaType, const TilePosition& tilePosition, float border, TilesMap& tiles); bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, - const TilePosition& tilePosition, std::map& tiles); + const TilePosition& tilePosition, TilesMap& tiles); std::optional removeTile(const ObjectId id, const TilePosition& tilePosition, - std::map& tiles); + TilesMap& tiles); }; } From 0f7f5ce140c19df5c813c8c83e33949d37c89d8d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 3 Aug 2021 02:40:44 +0300 Subject: [PATCH 1130/2859] Remove Lua command "self:setDirectControl" --- apps/openmw/mwbase/luamanager.hpp | 2 +- apps/openmw/mwlua/localscripts.cpp | 17 +++++++++-------- apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 3 ++- files/lua_api/openmw/self.lua | 9 +-------- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 7f3c50a9cf..cc416c9ab0 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -43,7 +43,7 @@ namespace MWBase struct ActorControls { bool mDisableAI = false; - bool mControlledFromLua = false; + bool mChanged = false; bool mJump = false; bool mRun = false; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index d9bb5ff26e..8a1b76a8ce 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -22,11 +22,15 @@ namespace MWLua { using ActorControls = MWBase::LuaManager::ActorControls; sol::usertype controls = context.mLua->sol().new_usertype("ActorControls"); - controls["movement"] = &ActorControls::mMovement; - controls["sideMovement"] = &ActorControls::mSideMovement; - controls["turn"] = &ActorControls::mTurn; - controls["run"] = &ActorControls::mRun; - controls["jump"] = &ActorControls::mJump; + +#define CONTROL(TYPE, FIELD) sol::property([](const ActorControls& c) { return c.FIELD; },\ + [](ActorControls& c, const TYPE& v) { c.FIELD = v; c.mChanged = true; }) + controls["movement"] = CONTROL(float, mMovement); + controls["sideMovement"] = CONTROL(float, mSideMovement); + controls["turn"] = CONTROL(float, mTurn); + controls["run"] = CONTROL(bool, mRun); + controls["jump"] = CONTROL(bool, mJump); +#undef CONTROL sol::usertype selfAPI = context.mLua->sol().new_usertype("SelfObject", sol::base_classes, sol::bases()); @@ -34,7 +38,6 @@ namespace MWLua selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; - selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.mControlledFromLua = v; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; selfAPI["setEquipment"] = [manager=context.mLuaManager](const SelfObject& obj, sol::table equipment) { @@ -82,8 +85,6 @@ namespace MWLua LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj) : LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj) { - mData.mControls.mControlledFromLua = false; - mData.mControls.mDisableAI = false; this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers}); } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 48174f55ee..24640c1863 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -31,7 +31,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 1; + api["API_REVISION"] = 2; api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 21ec5d987b..3669fd9391 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2095,7 +2095,7 @@ namespace MWMechanics float rotationZ = mov.mRotation[2]; bool jump = mov.mPosition[2] == 1; bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run); - if (luaControls->mControlledFromLua) + if (luaControls->mChanged) { mov.mPosition[0] = luaControls->mSideMovement; mov.mPosition[1] = luaControls->mMovement; @@ -2104,6 +2104,7 @@ namespace MWMechanics mov.mRotation[2] = luaControls->mTurn; mov.mSpeedFactor = osg::Vec2(luaControls->mMovement, luaControls->mSideMovement).length(); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->mRun); + luaControls->mChanged = false; } luaControls->mSideMovement = movement.x(); luaControls->mMovement = movement.y(); diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index b65b20bab2..98f1071cf6 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -26,8 +26,7 @@ -- @field [parent=#self] #ActorControls controls ------------------------------------------------------------------------------- --- Allows to view and/or modify controls of an actor. Makes an effect only if --- `setDirectControl(true)` was called. All fields are mutable. +-- Allows to view and/or modify controls of an actor. All fields are mutable. -- @type ActorControls -- @field [parent=#ActorControls] #number movement +1 - move forward, -1 - move backward -- @field [parent=#ActorControls] #number sideMovement +1 - move right, -1 - move left @@ -35,12 +34,6 @@ -- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump -------------------------------------------------------------------------------- --- Enables or disables direct movement control (disabled by default). --- @function [parent=#self] setDirectControl --- @param self --- @param #boolean control - ------------------------------------------------------------------------------- -- Enables or disables standart AI (enabled by default). -- @function [parent=#self] enableAI From 3ce5e9e680ee12ab2fe4e7ff6fb779d2008c571f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 3 Aug 2021 14:46:53 +0300 Subject: [PATCH 1131/2859] Improve error messages in components/lua/serialization.cpp --- .../lua/test_serialization.cpp | 4 ++-- components/lua/serialization.cpp | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_serialization.cpp b/apps/openmw_test_suite/lua/test_serialization.cpp index f8c6960c51..1983daa158 100644 --- a/apps/openmw_test_suite/lua/test_serialization.cpp +++ b/apps/openmw_test_suite/lua/test_serialization.cpp @@ -193,9 +193,9 @@ namespace table["y"] = TestStruct2{4, 3}; TestSerializer serializer; - EXPECT_ERROR(LuaUtil::serialize(table), "Unknown userdata"); + EXPECT_ERROR(LuaUtil::serialize(table), "Value is not serializable."); std::string serialized = LuaUtil::serialize(table, &serializer); - EXPECT_ERROR(LuaUtil::deserialize(lua, serialized), "Unknown type:"); + EXPECT_ERROR(LuaUtil::deserialize(lua, serialized), "Unknown type in serialized data:"); sol::table res = LuaUtil::deserialize(lua, serialized, &serializer); TestStruct1 rx = res.get("x"); diff --git a/components/lua/serialization.cpp b/components/lua/serialization.cpp index 53b6fe3b92..2e13cfe29f 100644 --- a/components/lua/serialization.cpp +++ b/components/lua/serialization.cpp @@ -43,7 +43,7 @@ namespace LuaUtil static T getValue(std::string_view& binaryData) { if (binaryData.size() < sizeof(T)) - throw std::runtime_error("Unexpected end"); + throw std::runtime_error("Unexpected end of serialized data."); T v; std::memcpy(&v, binaryData.data(), sizeof(T)); binaryData = binaryData.substr(sizeof(T)); @@ -107,15 +107,15 @@ namespace LuaUtil if (customSerializer && customSerializer->serialize(out, data)) return; else - throw std::runtime_error("Unknown userdata"); + throw std::runtime_error("Value is not serializable."); } static void serialize(BinaryData& out, const sol::object& obj, const UserdataSerializer* customSerializer, int recursionCounter) { if (obj.get_type() == sol::type::lightuserdata) - throw std::runtime_error("light userdata is not allowed to be serialized"); + throw std::runtime_error("Light userdata is not allowed to be serialized."); if (obj.is()) - throw std::runtime_error("functions are not allowed to be serialized"); + throw std::runtime_error("Functions are not allowed to be serialized."); else if (obj.is()) serializeUserdata(out, obj, customSerializer); else if (obj.is()) @@ -144,13 +144,13 @@ namespace LuaUtil appendType(out, SerializedType::BOOLEAN); out.push_back(v); } else - throw std::runtime_error("Unknown lua type"); + throw std::runtime_error("Unknown Lua type."); } static void deserializeImpl(sol::state& lua, std::string_view& binaryData, const UserdataSerializer* customSerializer) { if (binaryData.empty()) - throw std::runtime_error("Unexpected end"); + throw std::runtime_error("Unexpected end of serialized data."); unsigned char type = binaryData[0]; binaryData = binaryData.substr(1); if (type & (CUSTOM_COMPACT_FLAG | CUSTOM_FULL_FLAG)) @@ -170,7 +170,7 @@ namespace LuaUtil std::string_view data = binaryData.substr(typeNameSize, dataSize); binaryData = binaryData.substr(typeNameSize + dataSize); if (!customSerializer || !customSerializer->deserialize(typeName, data, lua)) - throw std::runtime_error("Unknown type: " + std::string(typeName)); + throw std::runtime_error("Unknown type in serialized data: " + std::string(typeName)); return; } if (type & SHORT_STRING_FLAG) @@ -205,12 +205,12 @@ namespace LuaUtil lua_settable(lua, -3); } if (binaryData.empty()) - throw std::runtime_error("Unexpected end"); + throw std::runtime_error("Unexpected end of serialized data."); binaryData = binaryData.substr(1); return; } case SerializedType::TABLE_END: - throw std::runtime_error("Unexpected table end"); + throw std::runtime_error("Unexpected end of table during deserialization."); case SerializedType::VEC2: { float x = getValue(binaryData); @@ -227,7 +227,7 @@ namespace LuaUtil return; } } - throw std::runtime_error("Unknown type: " + std::to_string(type)); + throw std::runtime_error("Unknown type in serialized data: " + std::to_string(type)); } BinaryData serialize(const sol::object& obj, const UserdataSerializer* customSerializer) From 99e691fbe36cdd7922cab8d3a79057dc3f65f278 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 4 Aug 2021 06:47:14 +1000 Subject: [PATCH 1132/2859] Avoid the inclusion of header due to Ubuntu Bionic (18.04) which as gcc version less than 8.1 Partially reverts commit fd67ebde2549bf5eb626c8c4ea76536c1f003a56 --- apps/opencs/model/world/refcollection.cpp | 17 +++++------------ apps/opencs/model/world/refcollection.hpp | 2 +- apps/opencs/view/doc/loader.cpp | 2 +- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 1f6fc76c4d..f47a79accd 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,7 +1,5 @@ #include "refcollection.hpp" -#include - #include #include "ref.hpp" @@ -183,19 +181,14 @@ std::string CSMWorld::RefCollection::getNewId() return "ref#" + std::to_string(mNextId++); } -unsigned int CSMWorld::RefCollection::extractIdNum(std::string_view id) const +unsigned int CSMWorld::RefCollection::extractIdNum (const std::string& id) const { - const auto separator = id.find_last_of('#'); - - if (separator == std::string_view::npos) - throw std::runtime_error("invalid ref ID: " + std::string(id)); + std::string::size_type separator = id.find_last_of('#'); - const std::string_view number = id.substr(separator + 1); - unsigned int result; - if (std::from_chars(number.data(), number.data() + number.size(), result).ec != std::errc()) - throw std::runtime_error("invalid ref ID number: " + std::string(number)); + if (separator == std::string::npos) + throw std::runtime_error("invalid ref ID: " + id); - return result; + return static_cast(std::stoi(id.substr(separator+1))); } int CSMWorld::RefCollection::getIntIndex (unsigned int id) const diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index e0e88d721f..1bdfda3925 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -29,7 +29,7 @@ namespace CSMWorld int mNextId; - unsigned int extractIdNum(std::string_view id) const; + unsigned int extractIdNum(const std::string& id) const; int getIntIndex (unsigned int id) const; diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 7929b13f6c..2420b87c6c 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -17,7 +17,7 @@ void CSVDoc::LoadingDocument::closeEvent (QCloseEvent *event) } CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) -: mDocument (document), mAborted (false), mMessages (nullptr), mRecordsLabel (0), mTotalRecordsLabel (0) +: mDocument (document), mTotalRecordsLabel (0), mRecordsLabel (0), mAborted (false), mMessages (nullptr), mRecords(0) { setWindowTitle (QString::fromUtf8((std::string("Opening ") + document->getSavePath().filename().string()).c_str())); From bf06898a79a5460c571b1d6f5b2b9cb7c7f52052 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 4 Aug 2021 08:23:22 +1000 Subject: [PATCH 1133/2859] Retain the use of std::string_view in the function signature. --- apps/opencs/model/world/refcollection.cpp | 6 +++--- apps/opencs/model/world/refcollection.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index f47a79accd..4782bde6b9 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -181,14 +181,14 @@ std::string CSMWorld::RefCollection::getNewId() return "ref#" + std::to_string(mNextId++); } -unsigned int CSMWorld::RefCollection::extractIdNum (const std::string& id) const +unsigned int CSMWorld::RefCollection::extractIdNum(std::string_view id) const { std::string::size_type separator = id.find_last_of('#'); if (separator == std::string::npos) - throw std::runtime_error("invalid ref ID: " + id); + throw std::runtime_error("invalid ref ID: " + std::string(id)); - return static_cast(std::stoi(id.substr(separator+1))); + return static_cast(std::stoi(std::string(id.substr(separator+1)))); } int CSMWorld::RefCollection::getIntIndex (unsigned int id) const diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index 1bdfda3925..e0e88d721f 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -29,7 +29,7 @@ namespace CSMWorld int mNextId; - unsigned int extractIdNum(const std::string& id) const; + unsigned int extractIdNum(std::string_view id) const; int getIntIndex (unsigned int id) const; From fa18f049afcfd98b4281d4c323b742ede0d8436e Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 3 Aug 2021 23:02:41 +0000 Subject: [PATCH 1134/2859] Add #5788 to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90fda783a2..bcdf269464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5755: Active grid object paging - disappearing textures + Bug #5788: Texture editing parses the selected indexes wrongly Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6051: NaN water height in ESM file is not handled gracefully From 9b7e14ec00a82f3887294df22521aaed00ebc743 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 3 Aug 2021 23:08:57 +0000 Subject: [PATCH 1135/2859] Merge branch 'OpenCS-moved-reference' into 'master' OpenCS - Fix moved reference - Issues #3514 and #4752 See merge request OpenMW/openmw!1051 (cherry picked from commit 2bee171c7990522da33c2667f7d079fa35f4ede0) 36c30f7f Fix for Issue #3514 where moving a reference to another cell is not handled properly. 40327681 Update the changelog. --- CHANGELOG.md | 2 + apps/opencs/model/doc/savingstages.cpp | 3 +- apps/opencs/model/world/commanddispatcher.cpp | 69 +++++++++++++------ components/esm/loadcell.cpp | 2 +- components/esm/loadcell.hpp | 2 +- 5 files changed, 54 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcdf269464..f79eca3e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ 0.48.0 ------ + Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI + Bug #4752: UpdateCellCommand doesn't undo properly Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system Bug #5379: Wandering NPCs falling through cantons diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index a410d34b2a..9da2c2c144 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -285,7 +285,8 @@ void CSMDoc::WriteCellCollectionStage::writeReferences (const std::deque& r { refRecord.mRefNum.mIndex = newRefNum++; } - else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) + + if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { // An empty mOriginalCell is meant to indicate that it is the same as diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 36b3ba2e00..b517fed0fb 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -139,13 +139,28 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (mLocked) return; - std::unique_ptr modifyCell; + std::unique_ptr clonedData; + std::unique_ptr deleteData; + + std::string newId; - std::unique_ptr modifyDataRefNum; + std::unique_ptr modifyData; + std::unique_ptr modifyCell; int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); - if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos) + int stateColumn = dynamic_cast(*model).findColumnIndex(Columns::ColumnId_Modification); + + CSMWorld::IdTable& table = dynamic_cast(*model); // for getId() + QModelIndex stateIndex = table.getModelIndex(table.getId(index.row()), stateColumn); + RecordBase::State state = static_cast (model->data(stateIndex).toInt()); + + // This is not guaranteed to be the same as \a model, since a proxy could be used. + IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel(mId)); + + // DeleteCommand triggers a signal to the whole row from IdTable::setData(), so ignore the second call + if (state != RecordBase::State_Deleted && + (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos)) { const float oldPosition = model->data (index).toFloat(); @@ -157,12 +172,9 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons int row = proxy ? proxy->mapToSource (index).row() : index.row(); - // This is not guaranteed to be the same as \a model, since a proxy could be used. - IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel (mId)); - int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell); - if (cellColumn!=-1) + if (cellColumn != -1) { QModelIndex cellIndex = model2.index (row, cellColumn); @@ -170,31 +182,46 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (cellId.find ('#')!=std::string::npos) { - // Need to recalculate the cell and (if necessary) clear the instance's refNum - modifyCell.reset (new UpdateCellCommand (model2, row)); + RefCollection& collection = mDocument.getData().getReferences(); + newId = collection.getNewId(); - // Not sure which model this should be applied to - int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum); + clonedData.reset(new CloneCommand(table, + table.getId(row), + newId, + CSMWorld::UniversalId::Type_Reference)); - if (refNumColumn!=-1) - modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0)); + deleteData.reset(new DeleteCommand(table, + table.getId(row), + CSMWorld::UniversalId::Type_Reference)); } } } } - std::unique_ptr modifyData ( - new CSMWorld::ModifyCommand (*model, index, new_)); + if (!clonedData.get()) + { + // DeleteCommand will trigger executeModify after setting the state to State_Deleted + // from CommandDelegate::setModelDataImp() - ignore + if (state != RecordBase::State_Deleted) + modifyData.reset(new CSMWorld::ModifyCommand(*model, index, new_)); + } - if (modifyCell.get()) + if (clonedData.get()) { CommandMacro macro (mDocument.getUndoStack()); - macro.push (modifyData.release()); - macro.push (modifyCell.release()); - if (modifyDataRefNum.get()) - macro.push (modifyDataRefNum.release()); + macro.push(clonedData.release()); + macro.push(deleteData.release()); + + // cannot do these earlier because newIndex is not available until CloneCommand is executed + QModelIndex newIndex = model2.getModelIndex (newId, index.column()); + modifyData.reset (new CSMWorld::ModifyCommand (*model, newIndex, new_)); + macro.push(modifyData.release()); + + // once the data is updated update the cell location + modifyCell.reset(new UpdateCellCommand(model2, newIndex.row())); + macro.push(modifyCell.release()); } - else + else if (!clonedData.get() && modifyData.get()) mDocument.getUndoStack().push (modifyData.release()); } diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index f84373fb9d..d2cb23146f 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -311,7 +311,7 @@ namespace ESM mWater = 0; mWaterInt = false; mMapColor = 0; - mRefNumCounter = -1; + mRefNumCounter = 0; mData.mFlags = 0; mData.mX = 0; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 7770f719e3..23f3d38cc8 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -94,7 +94,7 @@ struct Cell mWater(0), mWaterInt(false), mMapColor(0), - mRefNumCounter(-1) + mRefNumCounter(0) {} // Interior cells are indexed by this (it's the 'id'), for exterior From 8aee84c46ea68ebb2a66d959decab54be53818a5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 4 Aug 2021 09:06:04 +1000 Subject: [PATCH 1136/2859] Disallow entry of strings longer than the lengths allowed by the file format. It is possible to allow longer strings but that will require an extension in the omwaddon format as well as changes to the reader to handle that extension. Such changes should be a separate MR. (applied the patch in https://gitlab.com/OpenMW/openmw/-/issues/3066) --- CHANGELOG.md | 1 + apps/opencs/model/world/columnbase.cpp | 3 ++- apps/opencs/model/world/columnbase.hpp | 1 + apps/opencs/model/world/columnimp.hpp | 3 ++- apps/opencs/model/world/data.cpp | 4 +-- apps/opencs/model/world/refidcollection.cpp | 9 ++++--- apps/opencs/view/world/cellcreator.cpp | 7 +++++ apps/opencs/view/world/genericcreator.cpp | 5 ++++ apps/opencs/view/world/genericcreator.hpp | 2 ++ .../view/world/idcompletiondelegate.cpp | 17 ++++++++++++ .../view/world/referenceablecreator.cpp | 26 +++++++++++++++++++ .../view/world/referenceablecreator.hpp | 3 +++ apps/opencs/view/world/util.cpp | 8 ++++++ 13 files changed, 82 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90fda783a2..42a50a4ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1942,6 +1942,7 @@ Bug #2025: Missing mouse-over text for non affordable items Bug #2028: [MOD: Tamriel Rebuilt] Crashing when trying to enter interior cell "Ruinous Keep, Great Hall" Bug #2029: Ienith Brothers Thiev's Guild quest journal entry not adding + Bug #3066: Editor doesn't check if IDs and other strings are longer than their hardcoded field length Feature #471: Editor: Special case implementation for top-level window with single sub-window Feature #472: Editor: Sub-Window re-use settings Feature #704: Font colors import from fallback settings diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index cf333c1b1a..6f7bb12b3c 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -104,7 +104,8 @@ bool CSMWorld::ColumnBase::isId (Display display) bool CSMWorld::ColumnBase::isText (Display display) { return display==Display_String || display==Display_LongString || - display==Display_String32 || display==Display_LongString256; + display==Display_String32 || display==Display_String64 || + display==Display_LongString256; } bool CSMWorld::ColumnBase::isScript (Display display) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 6dc58bd634..b7c0d8a248 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -135,6 +135,7 @@ namespace CSMWorld Display_InfoCondVar, Display_InfoCondComp, Display_String32, + Display_String64, Display_LongString256, Display_BookType, Display_BloodType, diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 17518937c2..e0148d6cc6 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -334,7 +334,8 @@ namespace CSMWorld template struct NameColumn : public Column { - NameColumn() : Column (Columns::ColumnId_Name, ColumnBase::Display_String) {} + NameColumn(ColumnBase::Display display = ColumnBase::Display_String) + : Column (Columns::ColumnId_Name, display) {} QVariant get (const Record& record) const override { diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 192602290d..01bd858c62 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -130,7 +130,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat mFactions.addColumn (new StringIdColumn); mFactions.addColumn (new RecordStateColumn); mFactions.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Faction)); - mFactions.addColumn (new NameColumn); + mFactions.addColumn (new NameColumn(ColumnBase::Display_String32)); mFactions.addColumn (new AttributesColumn (0)); mFactions.addColumn (new AttributesColumn (1)); mFactions.addColumn (new HiddenColumn); @@ -339,7 +339,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); mCells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Cell)); - mCells.addColumn (new NameColumn); + mCells.addColumn (new NameColumn(ColumnBase::Display_String64)); mCells.addColumn (new FlagColumn (Columns::ColumnId_SleepForbidden, ESM::Cell::NoSleep)); mCells.addColumn (new FlagColumn (Columns::ColumnId_InteriorWater, ESM::Cell::HasWater, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 4ee99a2702..177844a31d 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -59,7 +59,9 @@ CSMWorld::RefIdCollection::RefIdCollection() NameColumns nameColumns (modelColumns); - mColumns.emplace_back(Columns::ColumnId_Name, ColumnBase::Display_String); + // Only items that can be placed in a container have the 32 character limit, but enforce + // that for all referenceable types for now. + mColumns.emplace_back(Columns::ColumnId_Name, ColumnBase::Display_String32); nameColumns.mName = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Script, ColumnBase::Display_Script); nameColumns.mScript = &mColumns.back(); @@ -231,9 +233,9 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String32)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String32)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiTargetCell, CSMWorld::ColumnBase::Display_String)); mColumns.back().addColumn( @@ -479,6 +481,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.emplace_back(Columns::ColumnId_Class, ColumnBase::Display_Class); npcColumns.mClass = &mColumns.back(); + // NAME32 enforced in IdCompletionDelegate::createEditor() mColumns.emplace_back(Columns::ColumnId_Faction, ColumnBase::Display_Faction); npcColumns.mFaction = &mColumns.back(); diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index 5b428a4b37..22c27c3d74 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -84,6 +84,13 @@ void CSVWorld::CellCreator::setType (int index) mYLabel->setVisible (index==1); mY->setVisible (index==1); + // The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell) + std::string text = mType->currentText().toStdString(); + if (text == "Interior Cell") + GenericCreator::setEditorMaxLength (64); + else + GenericCreator::setEditorMaxLength (32767); + update(); } diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index 23813f8066..8ae8f8764d 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -184,6 +184,11 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); } +void CSVWorld::GenericCreator::setEditorMaxLength (int length) +{ + mId->setMaxLength (length); +} + void CSVWorld::GenericCreator::setEditLock (bool locked) { mLocked = locked; diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 3e2a43c918..90c5946ae5 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -84,6 +84,8 @@ namespace CSVWorld std::string getNamespace() const; + void setEditorMaxLength(int length); + private: void updateNamespace(); diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 447bcc25d9..9ef04ec3ab 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -81,6 +81,23 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); + + // The savegame format limits the player faction string to 32 characters. + // The region sound name is limited to 32 characters. (ESM::Region::SoundRef::mSound) + // The script name is limited to 32 characters. (ESM::Script::SCHD::mName) + // The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell) + if (display == CSMWorld::ColumnBase::Display_Faction || + display == CSMWorld::ColumnBase::Display_Sound || + display == CSMWorld::ColumnBase::Display_Script || + display == CSMWorld::ColumnBase::Display_Referenceable) + { + editor->setMaxLength (32); + } + else if (display == CSMWorld::ColumnBase::Display_Cell) + { + editor->setMaxLength (64); + } + return editor; } diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 836e8ac7dc..1a2f2bbaa3 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -33,6 +33,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd } insertBeforeButtons (mType, false); + + connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); } void CSVWorld::ReferenceableCreator::reset() @@ -41,6 +43,30 @@ void CSVWorld::ReferenceableCreator::reset() GenericCreator::reset(); } +void CSVWorld::ReferenceableCreator::setType (int index) +{ + // container items have name limit of 32 characters + std::string text = mType->currentText().toStdString(); + if (text == "Potion" || + text == "Apparatus" || + text == "Armor" || + text == "Book" || + text == "Clothing" || + text == "Ingredient" || + text == "ItemLevelledList" || + text == "Light" || + text == "Lockpick" || + text == "Miscellaneous" || + text == "Probe" || + text == "Repair" || + text == "Weapon") + { + GenericCreator::setEditorMaxLength (32); + } + else + GenericCreator::setEditorMaxLength (32767); +} + void CSVWorld::ReferenceableCreator::cloneMode (const std::string& originId, const CSMWorld::UniversalId::Type type) { diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp index d4657bcf7f..354347cc88 100644 --- a/apps/opencs/view/world/referenceablecreator.hpp +++ b/apps/opencs/view/world/referenceablecreator.hpp @@ -29,6 +29,9 @@ namespace CSVWorld void toggleWidgets(bool active = true) override; + private slots: + + void setType (int index); }; } diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 58d3d49e44..80bd4580c8 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -291,6 +291,14 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return widget; } + case CSMWorld::ColumnBase::Display_String64: + { + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + CSVWidget::DropLineEdit *widget = new CSVWidget::DropLineEdit(display, parent); + widget->setMaxLength (64); + return widget; + } + default: return QStyledItemDelegate::createEditor (parent, option, index); From 8a14daaac70985087c790f6bd36be1f9de63a4e1 Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 4 Aug 2021 09:53:31 +0000 Subject: [PATCH 1137/2859] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f79eca3e5d..a9ec5a946e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map + Feature #4737: Handle instance move from one cell to another Feature #5489: MCP: Telekinesis fix for activators Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving From a94072243bcc24c9cb34973e380076c1eda5a247 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 4 Aug 2021 19:14:24 +0300 Subject: [PATCH 1138/2859] Lua command `core.quit` --- apps/openmw/mwlua/luabindings.cpp | 10 +++++++++- components/lua/luastate.cpp | 3 ++- files/lua_api/openmw/core.lua | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 24640c1863..9d978ddd52 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -5,6 +5,8 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwworld/inventorystore.hpp" #include "eventqueue.hpp" @@ -31,7 +33,13 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 2; + api["API_REVISION"] = 3; + api["quit"] = [lua]() + { + std::string traceback = lua->sol()["debug"]["traceback"]().get(); + Log(Debug::Warning) << "Quit requested by a Lua script.\n" << traceback; + MWBase::Environment::get().getStateManager()->requestQuit(); + }; api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 753e3369f6..1b52db11ad 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -24,7 +24,8 @@ namespace LuaUtil LuaState::LuaState(const VFS::Manager* vfs) : mVFS(vfs) { - mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::string, sol::lib::table); + mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, + sol::lib::string, sol::lib::table, sol::lib::debug); mLua["math"]["randomseed"](static_cast(std::time(nullptr))); mLua["math"]["randomseed"] = sol::nil; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 040e6968dd..affd052700 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -10,6 +10,10 @@ -- The revision of OpenMW Lua API. It is an integer that is incremented every time the API is changed. -- @field [parent=#core] #number API_REVISION +------------------------------------------------------------------------------- +-- Terminates the game and quits to the OS. Should be used only for testing purposes. +-- @function [parent=#core] quit + ------------------------------------------------------------------------------- -- Send an event to global scripts. -- @function [parent=#core] sendGlobalEvent From 1d69681c0aeb3bef14d158e8a03325e559bd8ce5 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 4 Aug 2021 21:48:43 +0200 Subject: [PATCH 1139/2859] Add a script to analyze OpenSceneGraph log --- scripts/osg_stats.py | 238 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100755 scripts/osg_stats.py diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py new file mode 100755 index 0000000000..140a911fea --- /dev/null +++ b/scripts/osg_stats.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +""" +osg_stats.py is a script to analyze OpenSceneGraph log. It parses given file +and builds timeseries, histograms, plots, calculate statistics for a given +set of keys over given range of frames. +""" + +import click +import collections +import matplotlib.pyplot +import numpy +import statistics +import sys +import termtables + +@click.command() +@click.option('--print_keys', is_flag=True, + help='Print a list of all present keys in the input file.') +@click.option('--timeseries', type=str, multiple=True, + help='Show a graph for given metric over time.') +@click.option('--hist', type=str, multiple=True, + help='Show a histogram for all values of given metric.') +@click.option('--hist_ratio', nargs=2, type=str, multiple=True, + help='Show a histogram for a ratio of two given metric (first / second). ' + 'Format: --hist_ratio .') +@click.option('--stdev_hist', nargs=2, type=str, multiple=True, + help='Show a histogram for a standard deviation of a given metric at given scale (number). ' + 'Format: --stdev_hist .') +@click.option('--plot', nargs=3, type=str, multiple=True, + help='Show a 2D plot for relation between two metrix (first is axis x, second is y)' + 'using one of aggregation functions (mean, median). For example show a relation ' + '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('--timeseries_sum', is_flag=True, + help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') +@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, + help='End processing at this frame.') +@click.argument('path', default='', type=click.Path()) +def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, + timeseries_sum, stats_sum, begin_frame, end_frame, path): + data = list(read_data(path)) + keys = collect_unique_keys(data) + frames = collect_per_frame(data=data, keys=keys, begin_frame=begin_frame, end_frame=end_frame) + if print_keys: + for v in keys: + print(v) + if timeseries: + draw_timeseries(frames=frames, keys=timeseries, timeseries_sum=timeseries_sum) + if hist: + draw_hists(frames=frames, keys=hist) + if hist_ratio: + draw_hist_ratio(frames=frames, pairs=hist_ratio) + if stdev_hist: + draw_stdev_hists(frames=frames, stdev_hists=stdev_hist) + if plot: + draw_plots(frames=frames, plots=plot) + if stats: + print_stats(frames=frames, keys=stats, stats_sum=stats_sum) + matplotlib.pyplot.show() + + +def read_data(path): + with open(path) if path else sys.stdin as stream: + frame = dict() + camera = 0 + for line in stream: + if line.startswith('Stats Viewer'): + if frame: + camera = 0 + yield frame + _, _, key, value = line.split(' ') + frame = {key: int(value)} + elif line.startswith('Stats Camera'): + camera += 1 + elif line.startswith(' '): + key, value = line.strip().rsplit(maxsplit=1) + if camera: + key = f'{key} Camera {camera}' + frame[key] = to_number(value) + + +def collect_per_frame(data, keys, begin_frame, end_frame): + result = collections.defaultdict(list) + for frame in data: + for key in keys: + if key in frame: + result[key].append(frame[key]) + else: + result[key].append(None) + for key, values in result.items(): + result[key] = numpy.array(values[begin_frame:end_frame]) + return result + + +def collect_unique_keys(frames): + result = set() + for frame in frames: + for key in frame.keys(): + result.add(key) + return sorted(result) + + +def draw_timeseries(frames, keys, timeseries_sum): + fig, ax = matplotlib.pyplot.subplots() + x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) + for key in keys: + ax.plot(x, frames[key], label=key) + if timeseries_sum: + ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label='sum') + ax.grid(True) + ax.legend() + fig.canvas.set_window_title('timeseries') + + +def draw_hists(frames, keys): + fig, ax = matplotlib.pyplot.subplots() + bins = numpy.linspace( + start=min(min(v) for k, v in frames.items() if k in keys), + stop=max(max(v) for k, v in frames.items() if k in keys), + num=20, + ) + for key in keys: + ax.hist(frames[key], bins=bins, label=key, alpha=1 / len(keys)) + ax.set_xticks(bins) + ax.grid(True) + ax.legend() + fig.canvas.set_window_title('hists') + + +def draw_hist_ratio(frames, pairs): + fig, ax = matplotlib.pyplot.subplots() + bins = numpy.linspace( + start=min(min(a / b for a, b in zip(frames[a], frames[b])) for a, b in pairs), + stop=max(max(a / b for a, b in zip(frames[a], frames[b])) for a, b in pairs), + num=20, + ) + for a, b in pairs: + ax.hist(frames[a] / frames[b], bins=bins, label=f'{a} / {b}', alpha=1 / len(pairs)) + ax.set_xticks(bins) + ax.grid(True) + ax.legend() + fig.canvas.set_window_title('hists') + + +def draw_stdev_hists(frames, stdev_hists): + for key, scale in stdev_hists: + scale = float(scale) + fig, ax = matplotlib.pyplot.subplots() + median = statistics.median(frames[key]) + stdev = statistics.stdev(frames[key]) + start = median - stdev / 2 * scale + stop = median + stdev / 2 * scale + bins = numpy.linspace(start=start, stop=stop, num=9) + values = [v for v in frames[key] if start <= v <= stop] + ax.hist(values, bins=bins, label=key, alpha=1 / len(stdev_hists)) + ax.set_xticks(bins) + ax.grid(True) + ax.legend() + fig.canvas.set_window_title('stdev_hists') + + +def draw_plots(frames, plots): + fig, ax = matplotlib.pyplot.subplots() + for x_key, y_key, agg in plots: + if agg is None: + ax.plot(frames[x_key], frames[y_key], label=f'x={x_key}, y={y_key}') + elif agg: + agg_f = dict( + mean=statistics.mean, + median=statistics.median, + )[agg] + grouped = collections.defaultdict(list) + for x, y in zip(frames[x_key], frames[y_key]): + grouped[x].append(y) + aggregated = sorted((k, agg_f(v)) for k, v in grouped.items()) + ax.plot( + numpy.array([v[0] for v in aggregated]), + numpy.array([v[1] for v in aggregated]), + label=f'x={x_key}, y={y_key}, agg={agg}', + ) + ax.grid(True) + ax.legend() + fig.canvas.set_window_title('plots') + + +def print_stats(frames, keys, stats_sum): + stats = [make_stats(key=key, values=filter_not_none(frames[key])) for key in keys] + if stats_sum: + stats.append(make_stats(key='sum', values=sum_multiple(frames, keys))) + metrics = list(stats[0].keys()) + max_key_size = max(len(tuple(v.values())[0]) for v in stats) + termtables.print( + [list(v.values()) for v in stats], + header=metrics, + style=termtables.styles.markdown, + ) + + +def filter_not_none(values): + return [v for v in values if v is not None] + + +def sum_multiple(frames, keys): + result = collections.Counter() + for key in keys: + values = frames[key] + for i, value in enumerate(values): + if value is not None: + result[i] += float(value) + return numpy.array([result[k] for k in sorted(result.keys())]) + + +def make_stats(key, values): + return collections.OrderedDict( + key=key, + number=len(values), + min=min(values), + max=max(values), + mean=statistics.mean(values), + median=statistics.median(values), + stdev=statistics.stdev(values), + q95=numpy.quantile(values, 0.95), + ) + + +def to_number(value): + try: + return int(value) + except ValueError: + return float(value) + +if __name__ == '__main__': + main() From 70fac33940f11096ee1a8448f8dc9dd03b012b1c Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Tue, 1 Jun 2021 12:15:25 -0700 Subject: [PATCH 1140/2859] initial reverse-z depth implementation --- apps/opencs/view/render/cellwater.cpp | 2 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/bookpage.cpp | 8 +- apps/openmw/mwgui/mapwindow.cpp | 4 +- apps/openmw/mwgui/mapwindow.hpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 5 +- apps/openmw/mwrender/characterpreview.cpp | 5 +- apps/openmw/mwrender/globalmap.cpp | 6 +- apps/openmw/mwrender/globalmap.hpp | 4 +- apps/openmw/mwrender/localmap.cpp | 17 +- apps/openmw/mwrender/localmap.hpp | 3 +- apps/openmw/mwrender/npcanimation.cpp | 6 +- apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/postprocessor.cpp | 216 +++++++++++++++++++++ apps/openmw/mwrender/postprocessor.hpp | 46 +++++ apps/openmw/mwrender/renderingmanager.cpp | 47 ++++- apps/openmw/mwrender/renderingmanager.hpp | 2 + apps/openmw/mwrender/ripplesimulation.cpp | 3 +- apps/openmw/mwrender/sky.cpp | 21 +- apps/openmw/mwrender/water.cpp | 4 +- components/nifosg/nifloader.cpp | 14 +- components/nifosg/nifloader.hpp | 4 + components/resource/scenemanager.cpp | 10 + components/resource/scenemanager.hpp | 4 + components/sceneutil/mwshadowtechnique.cpp | 79 ++++++-- components/sceneutil/mwshadowtechnique.hpp | 6 + components/sceneutil/optimizer.cpp | 5 +- components/sceneutil/optimizer.hpp | 13 +- components/sceneutil/shadow.cpp | 18 +- components/sceneutil/shadow.hpp | 5 +- components/sceneutil/util.cpp | 39 ++++ components/sceneutil/util.hpp | 18 ++ components/sceneutil/waterutil.cpp | 6 +- components/sceneutil/waterutil.hpp | 2 +- components/terrain/chunkmanager.cpp | 4 +- components/terrain/material.cpp | 18 +- components/terrain/material.hpp | 6 +- files/shaders/CMakeLists.txt | 1 + files/shaders/depth.glsl | 12 ++ files/shaders/groundcover_vertex.glsl | 3 +- files/shaders/nv_default_vertex.glsl | 3 +- files/shaders/nv_nolighting_vertex.glsl | 3 +- files/shaders/objects_vertex.glsl | 3 +- files/shaders/terrain_vertex.glsl | 3 +- files/shaders/water_fragment.glsl | 13 +- files/shaders/water_vertex.glsl | 6 +- 46 files changed, 612 insertions(+), 90 deletions(-) create mode 100644 apps/openmw/mwrender/postprocessor.cpp create mode 100644 apps/openmw/mwrender/postprocessor.hpp create mode 100644 files/shaders/depth.glsl diff --git a/apps/opencs/view/render/cellwater.cpp b/apps/opencs/view/render/cellwater.cpp index f8857c3afc..485eed00fe 100644 --- a/apps/opencs/view/render/cellwater.cpp +++ b/apps/opencs/view/render/cellwater.cpp @@ -161,7 +161,7 @@ namespace CSVRender } mWaterGeometry = SceneUtil::createWaterGeometry(size, segments, textureRepeats); - mWaterGeometry->setStateSet(SceneUtil::createSimpleWaterStateSet(Alpha, RenderBin)); + mWaterGeometry->setStateSet(SceneUtil::createSimpleWaterStateSet(Alpha, RenderBin, false)); // Add water texture std::string textureName = Fallback::Map::getString("Water_SurfaceTexture"); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6d58e880ed..a6d9402c4f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 49fae04619..c902ceb88a 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -8,7 +8,11 @@ #include "MyGUI_FactoryManager.h" #include +#include +#include +#include +#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -1217,8 +1221,10 @@ public: RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); + float z = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getReverseZ() ? 1.0 : -1.0; + GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), static_cast(mCoord.top - mViewTop), - -1 /*mNode->getNodeDepth()*/, vertices, renderXform); + z /*mNode->getNodeDepth()*/, vertices, renderXform); int visit_top = (std::max) (mViewTop, mViewTop + int (renderXform.clipTop )); int visit_bottom = (std::min) (mViewBottom, mViewTop + int (renderXform.clipBottom)); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index efb434d3bc..538b866cac 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -741,7 +741,7 @@ namespace MWGui } // ------------------------------------------------------------------------------------------ - MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) + MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue, bool reverseZ) : WindowPinnableBase("openmw_map_window.layout") , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) @@ -751,7 +751,7 @@ namespace MWGui , mGlobal(Settings::Manager::getBool("global", "Map")) , mEventBoxGlobal(nullptr) , mEventBoxLocal(nullptr) - , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue)) + , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue, reverseZ)) , mEditNoteDialog() { static bool registered = false; diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index cb0d368b30..b7664505af 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -222,7 +222,7 @@ namespace MWGui class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop { public: - MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue); + MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue, bool reverseZ); virtual ~MapWindow(); void setCellName(const std::string& cellName); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 6eeb2d3654..5feed41864 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -305,8 +305,9 @@ namespace MWGui mGuiModeStates[GM_MainMenu] = GuiModeState(menu); mWindows.push_back(menu); - mLocalMapRender = new MWRender::LocalMap(mViewer->getSceneData()->asGroup()); - mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender, mWorkQueue); + bool reverseZ = mResourceSystem->getSceneManager()->getReverseZ(); + mLocalMapRender = new MWRender::LocalMap(mViewer->getSceneData()->asGroup(), reverseZ); + mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender, mWorkQueue, reverseZ); mWindows.push_back(mMap); mMap->renderGlobalMap(); trackWindow(mMap, "map"); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index e88c4cee32..c4c628e04d 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -168,7 +168,7 @@ namespace MWRender mCamera->setRenderOrder(osg::Camera::PRE_RENDER); mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); mCamera->setName("CharacterPreview"); - mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + mCamera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mCamera->setCullMask(~(Mask_UpdateVisitor)); mCamera->setNodeMask(Mask_RenderToTexture); @@ -188,6 +188,9 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); + osg::ref_ptr depth = new osg::Depth; + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); // assign large value to effectively turn off fog diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index dcaf9c1612..f9bbb3d2f4 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -15,6 +15,7 @@ #include #include +#include #include @@ -219,13 +220,14 @@ namespace MWRender osg::ref_ptr mOverlayTexture; }; - GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue) + GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue, bool reverseZ) : mRoot(root) , mWorkQueue(workQueue) , mWidth(0) , mHeight(0) , mMinX(0), mMaxX(0) , mMinY(0), mMaxY(0) + , mReverseZ(reverseZ) { mCellSize = Settings::Manager::getInt("global map cell size", "Map"); @@ -323,7 +325,7 @@ namespace MWRender if (texture) { osg::ref_ptr geom = createTexturedQuad(srcLeft, srcTop, srcRight, srcBottom); - osg::ref_ptr depth = new osg::Depth; + auto depth = SceneUtil::createDepth(mReverseZ); depth->setWriteMask(0); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setAttribute(depth); diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index fd8a8d1016..a27621c9f4 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -33,7 +33,7 @@ namespace MWRender class GlobalMap { public: - GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue); + GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue, bool reverseZ); ~GlobalMap(); void render(); @@ -126,6 +126,8 @@ namespace MWRender int mHeight; int mMinX, mMaxX, mMinY, mMaxY; + + bool mReverseZ; }; } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 858d577335..e0b3d6c69b 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -83,13 +83,14 @@ namespace namespace MWRender { -LocalMap::LocalMap(osg::Group* root) +LocalMap::LocalMap(osg::Group* root, bool reverseZ) : mRoot(root) , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) , mMapWorldSize(Constants::CellSizeInUnits) , mCellDistance(Constants::CellGridRadius) , mAngle(0.f) , mInterior(false) + , mReverseZ(reverseZ) { // Increase map resolution, if use UI scaling float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); @@ -176,7 +177,12 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) { osg::ref_ptr camera (new osg::Camera); - camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); + + if (mReverseZ) + camera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10)); + else + camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); @@ -195,6 +201,13 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f osg::ref_ptr stateset = new osg::StateSet; stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); + if (mReverseZ) + { + camera->setClearDepth(0.0); + auto depth = SceneUtil::createDepth(mReverseZ); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog (new osg::Fog); diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index f9ccd5a011..316cbd53d3 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -36,7 +36,7 @@ namespace MWRender class LocalMap { public: - LocalMap(osg::Group* root); + LocalMap(osg::Group* root, bool reverseZ); ~LocalMap(); /** @@ -156,6 +156,7 @@ namespace MWRender void setupRenderToTexture(osg::ref_ptr camera, int x, int y); bool mInterior; + bool mReverseZ; osg::BoundingBox mBounds; }; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d338d70873..d103234f31 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -370,9 +370,9 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) class DepthClearCallback : public osgUtil::RenderBin::DrawCallback { public: - DepthClearCallback() + DepthClearCallback(bool reverseZ) { - mDepth = new osg::Depth; + mDepth = SceneUtil::createDepth(reverseZ); mDepth->setWriteMask(true); } @@ -432,7 +432,7 @@ void NpcAnimation::setRenderBin() if (!prototypeAdded) { osg::ref_ptr depthClearBin (new osgUtil::RenderBin); - depthClearBin->setDrawCallback(new DepthClearCallback); + depthClearBin->setDrawCallback(new DepthClearCallback(mResourceSystem->getSceneManager()->getReverseZ())); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); prototypeAdded = true; } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index aae8f2e4f1..709c67bc2d 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -661,6 +661,7 @@ namespace MWRender if (mergeGroup->getNumChildren()) { SceneUtil::Optimizer optimizer; + optimizer.setReverseZ(mSceneManager->getReverseZ()); if (size > 1/8.f) { optimizer.setViewPoint(relativeViewPoint); diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp new file mode 100644 index 0000000000..ed8171bd91 --- /dev/null +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -0,0 +1,216 @@ +#include "postprocessor.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +namespace +{ + osg::ref_ptr createFullScreenTri() + { + osg::ref_ptr geom = new osg::Geometry; + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-1, -1, 0)); + verts->push_back(osg::Vec3f(-1, 3, 0)); + verts->push_back(osg::Vec3f(3, -1, 0)); + + geom->setVertexArray(verts); + + geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); + + return geom; + } + + class CullCallback : public osg::NodeCallback + { + public: + CullCallback(MWRender::PostProcessor* postProcessor) + : mPostProcessor(postProcessor) + , mLastFrameNumber(0) + {} + + void operator()(osg::Node* node, osg::NodeVisitor* nv) override + { + osgUtil::RenderStage* renderStage = nv->asCullVisitor()->getCurrentRenderStage(); + + unsigned int frame = nv->getTraversalNumber(); + if (frame != mLastFrameNumber) + { + mLastFrameNumber = frame; + if (!mPostProcessor->getMsaaFbo()) + { + renderStage->setFrameBufferObject(mPostProcessor->getFbo()); + } + else + { + renderStage->setMultisampleResolveFramebufferObject(mPostProcessor->getFbo()); + renderStage->setFrameBufferObject(mPostProcessor->getMsaaFbo()); + } + } + + traverse(node, nv); + } + MWRender::PostProcessor* mPostProcessor; + unsigned int mLastFrameNumber; + }; + + struct ResizedCallback : osg::GraphicsContext::ResizedCallback + { + ResizedCallback(MWRender::PostProcessor* postProcessor) + : mPostProcessor(postProcessor) + { + } + + void resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height) override + { + mPostProcessor->resize(width, height); + } + + MWRender::PostProcessor* mPostProcessor; + }; +} + +namespace MWRender +{ + PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode) + : mViewer(viewer) + , mRootNode(new osg::Group) + { + int width = viewer->getCamera()->getViewport()->width(); + int height = viewer->getCamera()->getViewport()->height(); + + createTexturesAndCamera(width, height); + resize(width, height); + + mRootNode->addChild(mHUDCamera); + mRootNode->addChild(rootNode); + mViewer->setSceneData(mRootNode); + + // Main camera is treated specially, we need to manually set the FBO and + // resolve FBO during the cull callback. + mViewer->getCamera()->addCullCallback(new CullCallback(this)); + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); + mViewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, mDepthTex); + + mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); + } + + void PostProcessor::resize(int width, int height) + { + mDepthTex->setTextureSize(width, height); + mSceneTex->setTextureSize(width, height); + mDepthTex->dirtyTextureObject(); + mSceneTex->dirtyTextureObject(); + + int samples = Settings::Manager::getInt("antialiasing", "Video"); + + mFbo = new osg::FrameBufferObject; + mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex)); + mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(mDepthTex)); + + // When MSAA is enabled we must first render to a render buffer, then + // blit the result to the FBO which is either passed to the main frame + // buffer for display or used as the entry point for a post process chain. + if (samples > 0) + { + mMsaaFbo = new osg::FrameBufferObject; + osg::ref_ptr colorRB = new osg::RenderBuffer(width, height, mSceneTex->getInternalFormat(), samples); + osg::ref_ptr depthRB = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); + mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); + mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthRB)); + } + + double prevWidth = mViewer->getCamera()->getViewport()->width(); + double prevHeight = mViewer->getCamera()->getViewport()->height(); + double scaleX = prevWidth / width; + double scaleY = prevHeight / height; + + mViewer->getCamera()->resize(width,height); + mHUDCamera->resize(width,height); + + mViewer->getCamera()->getProjectionMatrix() *= osg::Matrix::scale(scaleX, scaleY, 1.0); + } + + void PostProcessor::createTexturesAndCamera(int width, int height) + { + mDepthTex = new osg::Texture2D; + mDepthTex->setTextureSize(width, height); + mDepthTex->setSourceFormat(GL_DEPTH_COMPONENT); + mDepthTex->setSourceType(GL_FLOAT); + mDepthTex->setInternalFormat(GL_DEPTH_COMPONENT32F); + mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); + mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); + mDepthTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mDepthTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mDepthTex->setResizeNonPowerOfTwoHint(false); + + mSceneTex = new osg::Texture2D; + mSceneTex->setTextureSize(width, height); + mSceneTex->setSourceFormat(GL_RGB); + mSceneTex->setSourceType(GL_UNSIGNED_BYTE); + mSceneTex->setInternalFormat(GL_RGB); + mSceneTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); + mSceneTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); + mSceneTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mSceneTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mSceneTex->setResizeNonPowerOfTwoHint(false); + + mHUDCamera = new osg::Camera; + mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); + mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); + mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); + mHUDCamera->setAllowEventFocus(false); + mHUDCamera->setViewport(0, 0, width, height); + + // Shaders calculate correct UV coordinates for our fullscreen triangle + constexpr char vertSrc[] = R"GLSL( + #version 120 + + varying vec2 uv; + + void main() + { + gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0); + uv = gl_Position.xy * 0.5 + 0.5; + } + )GLSL"; + + constexpr char fragSrc[] = R"GLSL( + #version 120 + + varying vec2 uv; + uniform sampler2D sceneTex; + + void main() + { + gl_FragData[0] = texture2D(sceneTex, uv); + } + )GLSL"; + + osg::ref_ptr vertShader = new osg::Shader(osg::Shader::VERTEX, vertSrc); + osg::ref_ptr fragShader = new osg::Shader(osg::Shader::FRAGMENT, fragSrc); + + osg::ref_ptr program = new osg::Program; + program->addShader(vertShader); + program->addShader(fragShader); + + mHUDCamera->addChild(createFullScreenTri()); + + auto* stateset = mHUDCamera->getOrCreateStateSet(); + stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON); + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("sceneTex", 0)); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } + +} diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp new file mode 100644 index 0000000000..c9d353f8cb --- /dev/null +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -0,0 +1,46 @@ +#ifndef OPENMW_MWRENDER_POSTPROCESSOR_H +#define OPENMW_MWRENDER_POSTPROCESSOR_H + +#include + +namespace osg +{ + class Texture2D; + class Group; + class FrameBufferObject; + class Camera; +} + +namespace osgViewer +{ + class Viewer; +} + +namespace MWRender +{ + class PostProcessor + { + public: + PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode); + + auto getMsaaFbo() { return mMsaaFbo; } + auto getFbo() { return mFbo; } + + void resize(int width, int height); + + private: + osgViewer::Viewer* mViewer; + osg::ref_ptr mRootNode; + osg::ref_ptr mHUDCamera; + + osg::ref_ptr mMsaaFbo; + osg::ref_ptr mFbo; + + osg::ref_ptr mSceneTex; + osg::ref_ptr mDepthTex; + + void createTexturesAndCamera(int width, int height); + }; +} + +#endif \ No newline at end of file diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 81687a712a..dc439f00b8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include @@ -68,6 +70,7 @@ #include "objectpaging.hpp" #include "screenshotmanager.hpp" #include "groundcover.hpp" +#include "postprocessor.hpp" namespace MWRender { @@ -198,6 +201,17 @@ namespace MWRender , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) { + auto ext = osg::GLExtensions::Get(0, false); + bool reverseZ = ext && ext->isClipControlSupported; + + if (getenv("OPENMW_DISABLE_REVERSEZ") != nullptr) + reverseZ = false; + + if (reverseZ) + Log(Debug::Info) << "Using reverse-z depth buffer"; + else + Log(Debug::Info) << "Using standard depth buffer"; + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); @@ -216,6 +230,7 @@ namespace MWRender resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); + resourceSystem->getSceneManager()->setReverseZ(reverseZ); // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this depends on support for various OpenGL extensions. osg::ref_ptr sceneRoot = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); @@ -242,7 +257,7 @@ namespace MWRender if (Settings::Manager::getBool("object shadows", "Shadows")) shadowCastingTraversalMask |= (Mask_Object|Mask_Static); - mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); + mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager(), reverseZ)); Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); @@ -267,6 +282,8 @@ namespace MWRender globalDefines["groundcoverStompMode"] = std::to_string(std::clamp(Settings::Manager::getInt("stomp mode", "Groundcover"), 0, 2)); globalDefines["groundcoverStompIntensity"] = std::to_string(std::clamp(Settings::Manager::getInt("stomp intensity", "Groundcover"), 0, 2)); + globalDefines["reverseZ"] = reverseZ ? "1" : "0"; + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); @@ -413,6 +430,7 @@ namespace MWRender mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); + NifOsg::Loader::setReverseZ(reverseZ); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); mNearClip = Settings::Manager::getFloat("near clip", "Camera"); @@ -434,6 +452,18 @@ namespace MWRender mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); + + if (reverseZ) + { + auto depth = SceneUtil::createDepth(reverseZ); + osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::ZERO_TO_ONE); + mViewer->getCamera()->setClearDepth(0.0); + mRootNode->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + mRootNode->getOrCreateStateSet()->setAttributeAndModes(clipcontrol, osg::StateAttribute::ON); + } + + mPostProcessor.reset(new PostProcessor(viewer, mRootNode)); + updateProjectionMatrix(); } @@ -1066,7 +1096,20 @@ namespace MWRender float fov = mFieldOfView; if (mFieldOfViewOverridden) fov = mFieldOfViewOverride; - mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); + + if (mResourceSystem->getSceneManager()->getReverseZ()) + { + mViewer->getCamera()->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspectiveInf(fov, aspect, mNearClip)); + float linearFac = -mNearClip / (mViewDistance - mNearClip) - 1.0; + mRootNode->getOrCreateStateSet()->getOrCreateUniform("linearFac", osg::Uniform::FLOAT, 1)->set(linearFac); + + osg::Matrix shadowProj = osg::Matrix::perspective(fov, aspect, mNearClip, mViewDistance); + mViewer->getCamera()->setUserValue("shadowProj", shadowProj); + mViewer->getCamera()->setUserValue("near", static_cast(mNearClip)); + mViewer->getCamera()->setUserValue("far", static_cast(mViewDistance)); + } + else + mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); mUniformNear->set(mNearClip); mUniformFar->set(mViewDistance); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index a0a74bd5c4..b63841494b 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -89,6 +89,7 @@ namespace MWRender class RecastMesh; class ObjectPaging; class Groundcover; + class PostProcessor; class RenderingManager : public MWRender::RenderingInterface { @@ -287,6 +288,7 @@ namespace MWRender std::unique_ptr mScreenshotManager; std::unique_ptr mEffectManager; std::unique_ptr mShadowManager; + std::unique_ptr mPostProcessor; osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 6788f53f44..c7deac51de 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "vismask.hpp" @@ -55,7 +56,7 @@ namespace stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); - osg::ref_ptr depth (new osg::Depth); + auto depth = SceneUtil::createDepth(resourceSystem->getSceneManager()->getReverseZ()); depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 8bac90604b..604e417a45 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -473,7 +473,7 @@ const float CelestialBody::mDistance = 1000.0f; class Sun : public CelestialBody { public: - Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) + Sun(osg::Group* parentNode, Resource::ImageManager& imageManager, bool reverseZ) : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) , mUpdater(new Updater) { @@ -502,8 +502,8 @@ public: mTransform->addChild(queryNode); - mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); - mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); + mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true, reverseZ); + mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false, reverseZ); createSunFlash(imageManager); createSunGlare(); @@ -556,7 +556,7 @@ private: }; /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. - osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible) + osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible, bool reverseZ) { osg::ref_ptr oqn = new osg::OcclusionQueryNode; oqn->setQueriesEnabled(true); @@ -594,13 +594,13 @@ private: osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { - osg::ref_ptr depth (new osg::Depth); - depth->setFunction(osg::Depth::LEQUAL); + auto depth = SceneUtil::createDepth(reverseZ); // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. - depth->setZNear(1.0); - depth->setZFar(1.0); + float far = reverseZ ? 0.0 : 1.0; + depth->setZNear(far); + depth->setZFar(far); depth->setWriteMask(false); queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); } @@ -1188,7 +1188,8 @@ void SkyManager::create() mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager()); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); + bool reverseZ = mSceneManager->getReverseZ(); + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), reverseZ)); mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); @@ -1209,7 +1210,7 @@ void SkyManager::create() mCloudMesh2->addUpdateCallback(mCloudUpdater2); mCloudMesh2->setNodeMask(0); - osg::ref_ptr depth = new osg::Depth; + auto depth = SceneUtil::createDepth(reverseZ); depth->setWriteMask(false); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 621a5e7c25..1318921688 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -273,6 +273,7 @@ public: void setDefaults(osg::Camera* camera) override { + camera->setClearDepth(1.0); camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("RefractionCamera"); @@ -338,6 +339,7 @@ public: void setDefaults(osg::Camera* camera) override { + camera->setClearDepth(1.0); camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("ReflectionCamera"); @@ -544,7 +546,7 @@ osg::Node* Water::getRefractionNode() void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { - osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); + osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water, mResourceSystem->getSceneManager()->getReverseZ()); node->setStateSet(stateset); node->setUpdateCallback(nullptr); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7c32bb04f8..384c640388 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -226,6 +226,18 @@ namespace NifOsg return sIntersectionDisabledNodeMask; } + bool Loader::sReverseZ = false; + + void Loader::setReverseZ(bool reverseZ) + { + sReverseZ = reverseZ; + } + + bool Loader::getReverseZ() + { + return sReverseZ; + } + class LoaderImpl { public: @@ -1823,7 +1835,7 @@ namespace NifOsg // Depth test flag stateset->setMode(GL_DEPTH_TEST, zprop->flags&1 ? osg::StateAttribute::ON : osg::StateAttribute::OFF); - osg::ref_ptr depth = new osg::Depth; + auto depth = SceneUtil::createDepth(Loader::getReverseZ()); // Depth write flag depth->setWriteMask((zprop->flags>>1)&1); // Morrowind ignores depth test function diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 8ee6b41674..cee75de6d2 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -51,10 +51,14 @@ namespace NifOsg static void setIntersectionDisabledNodeMask(unsigned int mask); static unsigned int getIntersectionDisabledNodeMask(); + static void setReverseZ(bool reverseZ); + static bool getReverseZ(); + private: static unsigned int sHiddenNodeMask; static unsigned int sIntersectionDisabledNodeMask; static bool sShowMarkers; + static bool sReverseZ; }; } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index f6035a47dd..70e849b234 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -275,6 +275,16 @@ namespace Resource return mClampLighting; } + void SceneManager::setReverseZ(bool reverseZ) + { + mReverseZ = reverseZ; + } + + bool SceneManager::getReverseZ() const + { + return mReverseZ; + } + void SceneManager::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index de014165bf..260fd4717f 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -92,6 +92,9 @@ namespace Resource void setClampLighting(bool clamp); bool getClampLighting() const; + void setReverseZ(bool reverseZ); + bool getReverseZ() const; + /// @see ShaderVisitor::setAutoUseNormalMaps void setAutoUseNormalMaps(bool use); @@ -202,6 +205,7 @@ namespace Resource SceneUtil::LightingMethod mLightingMethod; SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; + bool mReverseZ; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index ad3fc5fd65..bbda5442bf 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -23,10 +23,13 @@ #include #include #include +#include #include #include "shadowsbin.hpp" +#include + namespace { using namespace osgShadow; @@ -347,6 +350,11 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) _projectionMatrix = cv->getProjectionMatrix(); } +bool isOrthographicViewFrustum(const osg::Matrix& m) +{ + return m(0,3)==0.0 && m(1,3)==0.0 && m(2,3)==0.0; +} + } // namespace MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix) : @@ -890,6 +898,16 @@ void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() } } +void SceneUtil::MWShadowTechnique::enableReverseZ() +{ + _reverseZ = true; +} + +void SceneUtil::MWShadowTechnique::disableReverseZ() +{ + _reverseZ = false; +} + void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) { // This can't be part of the constructor as OSG mandates that there be a trivial constructor available @@ -970,6 +988,9 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) return; } + osg::Matrix shadowProj; + cv.getCurrentCamera()->getUserValue("shadowProj", shadowProj); + ViewDependentData* vdd = getViewDependentData(&cv); if (!vdd) @@ -985,34 +1006,41 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) osg::CullSettings::ComputeNearFarMode cachedNearFarMode = cv.getComputeNearFarMode(); - osg::RefMatrix& viewProjectionMatrix = *cv.getProjectionMatrix(); - - // check whether this main views projection is perspective or orthographic - bool orthographicViewFrustum = viewProjectionMatrix(0,3)==0.0 && - viewProjectionMatrix(1,3)==0.0 && - viewProjectionMatrix(2,3)==0.0; - double minZNear = 0.0; double maxZFar = dbl_max; + bool orthographicViewFrustum; - if (cachedNearFarMode==osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + if (_reverseZ) { - double left, right, top, bottom; - if (orthographicViewFrustum) + cv.getCurrentCamera()->getUserValue("near", minZNear); + cv.getCurrentCamera()->getUserValue("far", maxZFar); + orthographicViewFrustum = isOrthographicViewFrustum(shadowProj); + } + else + { + osg::RefMatrix& viewProjectionMatrix = *cv.getProjectionMatrix(); + + // check whether this main views projection is perspective or orthographic + orthographicViewFrustum = isOrthographicViewFrustum(viewProjectionMatrix); + + if (cachedNearFarMode==osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { - viewProjectionMatrix.getOrtho(left, right, bottom, top, minZNear, maxZFar); + double left, right, top, bottom; + if (orthographicViewFrustum) + { + viewProjectionMatrix.getOrtho(left, right, bottom, top, minZNear, maxZFar); + } + else + { + viewProjectionMatrix.getFrustum(left, right, bottom, top, minZNear, maxZFar); + } } - else + + // set the compute near/far mode to the highest quality setting to ensure we push the near plan out as far as possible + if (settings->getComputeNearFarModeOverride()!=osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { - viewProjectionMatrix.getFrustum(left, right, bottom, top, minZNear, maxZFar); + cv.setComputeNearFarMode(settings->getComputeNearFarModeOverride()); } - OSG_INFO<<"minZNear="<addUniform(new osg::Uniform("alphaTestShadows", false)); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(true); + osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::NEGATIVE_ONE_TO_ONE); + if (_reverseZ) + _shadowCastingStateSet->setAttribute(clipcontrol, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index de57bf1fdc..7fde0d7dbb 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -85,6 +85,10 @@ namespace SceneUtil { virtual void disableFrontFaceCulling(); + virtual void enableReverseZ(); + + virtual void disableReverseZ(); + virtual void setupCastingShader(Shader::ShaderManager &shaderManager); class ComputeLightSpaceBounds : public osg::NodeVisitor, public osg::CullStack @@ -266,6 +270,8 @@ namespace SceneUtil { float _shadowFadeStart = 0.0; + bool _reverseZ = false; + class DebugHUD final : public osg::Referenced { public: diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index b48ceda409..6bde81c12d 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -40,6 +40,8 @@ #include +#include + using namespace osgUtil; namespace SceneUtil @@ -107,6 +109,7 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) MergeGeometryVisitor mgv(this); mgv.setTargetMaximumNumberOfVertices(1000000); mgv.setMergeAlphaBlending(_mergeAlphaBlending); + mgv.setReverseZ(_reverseZ); mgv.setViewPoint(_viewPoint); node->accept(mgv); @@ -1560,7 +1563,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) } if (_alphaBlendingActive && _mergeAlphaBlending && !geom->getStateSet()) { - osg::Depth* d = new osg::Depth; + auto d = createDepth(_reverseZ); d->setWriteMask(0); geom->getOrCreateStateSet()->setAttribute(d); } diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 2d6293e231..9f9c788445 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -65,7 +65,7 @@ class Optimizer public: - Optimizer() : _mergeAlphaBlending(false) {} + Optimizer() : _mergeAlphaBlending(false), _reverseZ(false) {} virtual ~Optimizer() {} enum OptimizationOptions @@ -119,6 +119,7 @@ class Optimizer }; void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } + void setReverseZ(bool reverseZ) { _reverseZ = reverseZ; } void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; } /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ @@ -257,6 +258,7 @@ class Optimizer osg::Vec3f _viewPoint; bool _mergeAlphaBlending; + bool _reverseZ; public: @@ -377,12 +379,18 @@ class Optimizer /// default to traversing all children. MergeGeometryVisitor(Optimizer* optimizer=0) : BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY), - _targetMaximumNumberOfVertices(10000), _alphaBlendingActive(false), _mergeAlphaBlending(false) {} + _targetMaximumNumberOfVertices(10000), _alphaBlendingActive(false), _mergeAlphaBlending(false), _reverseZ(false) {} void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } + + void setReverseZ(bool reverseZ) + { + _reverseZ = reverseZ; + } + void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; @@ -421,6 +429,7 @@ class Optimizer std::vector _stateSetStack; bool _alphaBlendingActive; bool _mergeAlphaBlending; + bool _reverseZ; osg::Vec3f _viewPoint; }; diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 3244dc672a..32c2edeccd 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -94,10 +94,12 @@ namespace SceneUtil } } - ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), - mShadowTechnique(new MWShadowTechnique), - mOutdoorShadowCastingMask(outdoorShadowCastingMask), - mIndoorShadowCastingMask(indoorShadowCastingMask) + ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager, bool reverseZ) + : mShadowedScene(new osgShadow::ShadowedScene) + , mReverseZ(reverseZ) + , mShadowTechnique(new MWShadowTechnique) + , mOutdoorShadowCastingMask(outdoorShadowCastingMask) + , mIndoorShadowCastingMask(indoorShadowCastingMask) { mShadowedScene->setShadowTechnique(mShadowTechnique); @@ -108,6 +110,9 @@ namespace SceneUtil mShadowSettings = mShadowedScene->getShadowSettings(); setupShadowSettings(); + if (mReverseZ) + mShadowTechnique->enableReverseZ(); + mShadowTechnique->setupCastingShader(shaderManager); enableOutdoorMode(); @@ -180,4 +185,9 @@ namespace SceneUtil mShadowTechnique->enableShadows(); mShadowSettings->setCastsShadowTraversalMask(mOutdoorShadowCastingMask); } + + bool ShadowManager::getReverseZ() const + { + return mReverseZ; + } } diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index c823ecf860..8092f77f93 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -17,7 +17,7 @@ namespace SceneUtil static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); - ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager); + ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager, bool reverseZ); void setupShadowSettings(); @@ -26,8 +26,11 @@ namespace SceneUtil void enableIndoorMode(); void enableOutdoorMode(); + + bool getReverseZ() const; protected: bool mEnableShadows; + bool mReverseZ; osg::ref_ptr mShadowedScene; osg::ref_ptr mShadowSettings; diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 9b4fb9d3fb..2e37260eb7 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -285,4 +285,43 @@ bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg:: return addMSAAIntermediateTarget; } +osg::ref_ptr createDepth(bool reverseZ) +{ + static osg::Depth::Function func = reverseZ ? osg::Depth::GEQUAL : osg::Depth::LEQUAL; + return new osg::Depth(func); +} + +osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near) +{ + double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0); + return osg::Matrix( + A/aspect, 0, 0, 0, + 0, A, 0, 0, + 0, 0, 0, -1, + 0, 0, near, 0 + ); +} + +osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far) +{ + double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0); + return osg::Matrix( + A/aspect, 0, 0, 0, + 0, A, 0, 0, + 0, 0, far/(far-near)-1.0, -1, + 0, 0, -(far*near)/(far - near), 0 + ); +} + +osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far) +{ + return osg::Matrix( + 2/(right-left), 0, 0, 0, + 0, 2/(top-bottom), 0, 0, + 0, 0, 1/(far-near), 0, + (right+left)/(left-right), (top+bottom)/(bottom-top), far/(far-near), 1 + ); +} + + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index f297c42d2e..0164e0c99e 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -64,6 +65,23 @@ namespace SceneUtil // Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false); + + // Returns a suitable depth state attribute dependent on whether a reverse-z + // depth buffer is in use. + osg::ref_ptr createDepth(bool reverseZ); + + // Returns a perspective projection matrix for use with a reversed z-buffer + // and an infinite far plane. This is derived by mapping the default z-range + // of [0,1] to [1,0], then taking the limit as far plane approaches + // infinity. + osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near); + + // Returns a perspective projection matrix for use with a reversed z-buffer. + osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far); + + // Returns an orthographic projection matrix for use with a reversed z-buffer. + osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far); + } #endif diff --git a/components/sceneutil/waterutil.cpp b/components/sceneutil/waterutil.cpp index 8a434105c8..dccc534ecc 100644 --- a/components/sceneutil/waterutil.cpp +++ b/components/sceneutil/waterutil.cpp @@ -5,6 +5,8 @@ #include #include +#include "util.hpp" + namespace SceneUtil { // disable nonsense test against a worldsize bb what will always pass @@ -62,7 +64,7 @@ namespace SceneUtil return waterGeom; } - osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin) + osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin, bool reverseZ) { osg::ref_ptr stateset (new osg::StateSet); @@ -76,7 +78,7 @@ namespace SceneUtil stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - osg::ref_ptr depth (new osg::Depth); + auto depth = createDepth(reverseZ); depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); diff --git a/components/sceneutil/waterutil.hpp b/components/sceneutil/waterutil.hpp index 7b8c380109..7f08dbcfa9 100644 --- a/components/sceneutil/waterutil.hpp +++ b/components/sceneutil/waterutil.hpp @@ -13,7 +13,7 @@ namespace SceneUtil { osg::ref_ptr createWaterGeometry(float size, int segments, float textureRepeats); - osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin); + osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin, bool reverseZ); } #endif diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index a744471de5..5354b9d8a2 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -163,7 +163,7 @@ std::vector > ChunkManager::createPasses(float chunk float blendmapScale = mStorage->getBlendmapScale(chunkSize); - return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); + return ::Terrain::createPasses(useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile) @@ -219,7 +219,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve layer.mDiffuseMap = compositeMap->mTexture; layer.mParallax = false; layer.mSpecular = false; - geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); + geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager, std::vector(1, layer), std::vector >(), 1.f, 1.f)); } else { diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index e662f4439f..dc74b58560 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include @@ -95,19 +97,18 @@ namespace class LequalDepth { public: - static const osg::ref_ptr& value() + static const osg::ref_ptr& value(bool reverseZ) { - static LequalDepth instance; + static LequalDepth instance(reverseZ); return instance.mValue; } private: osg::ref_ptr mValue; - LequalDepth() - : mValue(new osg::Depth) + LequalDepth(bool reverseZ) + : mValue(SceneUtil::createDepth(reverseZ)) { - mValue->setFunction(osg::Depth::LEQUAL); } }; @@ -170,9 +171,10 @@ namespace namespace Terrain { - std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector &layers, + std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { + Shader::ShaderManager* shaderManager = &sceneManager->getShaderManager(); std::vector > passes; unsigned int blendmapIndex = 0; @@ -195,7 +197,7 @@ namespace Terrain else { stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); - stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(LequalDepth::value(sceneManager->getReverseZ()), osg::StateAttribute::ON); } } @@ -238,7 +240,7 @@ namespace Terrain if (!vertexShader || !fragmentShader) { // Try again without shader. Error already logged by above - return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); + return createPasses(false, sceneManager, layers, blendmaps, blendmapScale, layerTileSize); } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 5f78af6a06..d5ef40a29e 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -10,9 +10,9 @@ namespace osg class Texture2D; } -namespace Shader +namespace Resource { - class ShaderManager; + class SceneManager; } namespace Terrain @@ -26,7 +26,7 @@ namespace Terrain bool mSpecular; }; - std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, + std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 5297b2e752..5e049b8a3f 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -13,6 +13,7 @@ set(SHADER_FILES water_fragment.glsl water_nm.png alpha.glsl + depth.glsl objects_vertex.glsl objects_fragment.glsl terrain_vertex.glsl diff --git a/files/shaders/depth.glsl b/files/shaders/depth.glsl new file mode 100644 index 0000000000..0adde7a2a7 --- /dev/null +++ b/files/shaders/depth.glsl @@ -0,0 +1,12 @@ +#if @reverseZ +uniform float linearFac; +#endif + +float getLinearDepth(in vec4 viewPos) +{ +#if @reverseZ + return linearFac*viewPos.z; +#else + return gl_Position.z; +#endif +} \ No newline at end of file diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index a12fb3d9d9..94586734b6 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -39,6 +39,7 @@ centroid varying vec3 shadowDiffuseLighting; #include "shadows_vertex.glsl" #include "lighting.glsl" +#include "depth.glsl" uniform float osg_SimulationTime; uniform mat4 osg_ViewMatrixInverse; @@ -143,7 +144,7 @@ void main(void) else gl_Position = gl_ProjectionMatrix * viewPos; - linearDepth = gl_Position.z; + linearDepth = getLinearDepth(viewPos); #if (!PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index 50f5daf25e..1c5483dcda 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -32,6 +32,7 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_vertex.glsl" #include "lighting.glsl" +#include "depth.glsl" void main(void) { @@ -40,7 +41,7 @@ void main(void) vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); - linearDepth = gl_Position.z; + linearDepth = getLinearDepth(viewPos); #if @diffuseMap diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; diff --git a/files/shaders/nv_nolighting_vertex.glsl b/files/shaders/nv_nolighting_vertex.glsl index 275f1e5730..02c28eb5e3 100644 --- a/files/shaders/nv_nolighting_vertex.glsl +++ b/files/shaders/nv_nolighting_vertex.glsl @@ -17,6 +17,7 @@ varying vec3 passViewPos; varying float passFalloff; #include "vertexcolors.glsl" +#include "depth.glsl" void main(void) { @@ -27,7 +28,7 @@ void main(void) #if @radialFog euclideanDepth = length(viewPos.xyz); #else - linearDepth = gl_Position.z; + linearDepth = getLinearDepth(viewPos); #endif #if @diffuseMap diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 64c35a21cc..34484ebd41 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -62,6 +62,7 @@ varying vec3 passNormal; #include "shadows_vertex.glsl" #include "lighting.glsl" +#include "depth.glsl" void main(void) { @@ -70,7 +71,7 @@ void main(void) vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); - linearDepth = gl_Position.z; + linearDepth = getLinearDepth(viewPos); #if (@envMap || !PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 638d6cca0b..221e0c27a9 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -25,6 +25,7 @@ varying vec3 passNormal; #include "shadows_vertex.glsl" #include "lighting.glsl" +#include "depth.glsl" void main(void) { @@ -33,7 +34,7 @@ void main(void) vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); - linearDepth = gl_Position.z; + linearDepth = getLinearDepth(viewPos); #if (!PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index d1e795eca7..ed0ebdc913 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -158,11 +158,14 @@ uniform float rainIntensity; float frustumDepth; float linearizeDepth(float depth) - { - float z_n = 2.0 * depth - 1.0; - depth = 2.0 * near * far / (far + near - z_n * frustumDepth); - return depth; - } +{ +#if @reverseZ + depth = 1.0 - depth; +#endif + float z_n = 2.0 * depth - 1.0; + depth = 2.0 * near * far / (far + near - z_n * frustumDepth); + return depth; +} void main(void) { diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 02a395f95d..9513001847 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -5,6 +5,7 @@ varying vec4 position; varying float linearDepth; #include "shadows_vertex.glsl" +#include "depth.glsl" void main(void) { @@ -20,7 +21,8 @@ void main(void) position = gl_Vertex; - linearDepth = gl_Position.z; + vec4 viewPos = gl_ModelViewMatrix * gl_Vertex; + linearDepth = getLinearDepth(viewPos); - setupShadowCoords(gl_ModelViewMatrix * gl_Vertex, normalize((gl_NormalMatrix * gl_Normal).xyz)); + setupShadowCoords(viewPos, normalize((gl_NormalMatrix * gl_Normal).xyz)); } From d89e37d6895c19b558668b6d4f193250807bc36d Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 2 Jun 2021 14:46:26 -0700 Subject: [PATCH 1141/2859] add framebuffer extension check and fix issues with manual screenshots --- apps/openmw/mwgui/savegamedialog.cpp | 1 + apps/openmw/mwrender/postprocessor.cpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 5 ++++- apps/openmw/mwrender/screenshotmanager.cpp | 23 +++++++++++++++++++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index c4d608443c..987d27c9a6 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -450,6 +450,7 @@ namespace MWGui osg::ref_ptr texture (new osg::Texture2D); texture->setImage(result.getImage()); + texture->setInternalFormat(GL_RGBA); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index ed8171bd91..43765a25ba 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -116,6 +116,8 @@ namespace MWRender mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex)); mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(mDepthTex)); + mViewer->getCamera()->setUserData(mFbo); + // When MSAA is enabled we must first render to a render buffer, then // blit the result to the FBO which is either passed to the main frame // buffer for display or used as the entry point for a post process chain. diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index dc439f00b8..65aa4a5a6b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -462,7 +462,10 @@ namespace MWRender mRootNode->getOrCreateStateSet()->setAttributeAndModes(clipcontrol, osg::StateAttribute::ON); } - mPostProcessor.reset(new PostProcessor(viewer, mRootNode)); + if (ext && ext->isFrameBufferObjectSupported) + mPostProcessor.reset(new PostProcessor(viewer, mRootNode)); + else + Log(Debug::Warning) << "Disabling postprocessing and using default framebuffer for rendering: FrameBufferObjects not supported"; updateProjectionMatrix(); } diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index b24572dbaa..ba4282e5b6 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -88,6 +89,19 @@ namespace MWRender int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); int width = screenW - leftPadding*2; int height = screenH - topPadding*2; + + // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer when in use. + // glReadPixel() cannot read from multisampled targets. + + osg::FrameBufferObject* fbo = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); + + if (fbo) + { + osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); + if (ext) + ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo->getHandle(renderInfo.getContextID())); + } + mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); mImage->scaleImage(mWidth, mHeight, 1); } @@ -236,6 +250,7 @@ namespace MWRender osg::ref_ptr screenshotCamera(new osg::Camera); osg::ref_ptr quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0))); + quad->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); std::map defineMap; @@ -283,6 +298,9 @@ namespace MWRender camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); + if (MWBase::Environment::get().getResourceSystem()->getSceneManager()->getReverseZ()) + camera->setClearDepth(0.0); + camera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); @@ -318,7 +336,10 @@ namespace MWRender float nearClip = Settings::Manager::getFloat("near clip", "Camera"); float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); // each cubemap side sees 90 degrees - rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); + if (MWBase::Environment::get().getResourceSystem()->getSceneManager()->getReverseZ()) + rttCamera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspectiveInf(90.0, w/float(h), nearClip)); + else + rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); rttCamera->setUpdateCallback(new NoTraverseCallback); From b457dfd8b838ad63613c4c19edf51f43af4943a5 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Fri, 4 Jun 2021 12:13:52 -0700 Subject: [PATCH 1142/2859] fix water RTTs and minor math error in non-infinite projection matrix --- components/sceneutil/util.cpp | 31 +++++++++++++++++++++++++++++-- components/sceneutil/util.hpp | 17 +++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 2e37260eb7..79fe5d13c5 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -150,6 +150,33 @@ void GlowUpdater::setDuration(float duration) mDuration = duration; } +AttachMultisampledDepthColorCallback::AttachMultisampledDepthColorCallback(const osg::ref_ptr& colorTex, const osg::ref_ptr& depthTex, int samples, int colorSamples) +{ + int width = colorTex->getTextureWidth(); + int height = colorTex->getTextureHeight(); + + osg::ref_ptr rbColor = new osg::RenderBuffer(width, height, colorTex->getInternalFormat(), samples, colorSamples); + osg::ref_ptr rbDepth = new osg::RenderBuffer(width, height, depthTex->getInternalFormat(), samples, colorSamples); + + mMsaaFbo = new osg::FrameBufferObject; + mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(rbColor)); + mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(rbDepth)); + + mFbo = new osg::FrameBufferObject; + mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorTex)); + mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthTex)); +} + +void AttachMultisampledDepthColorCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) +{ + osgUtil::RenderStage* renderStage = nv->asCullVisitor()->getCurrentRenderStage(); + + renderStage->setMultisampleResolveFramebufferObject(mFbo); + renderStage->setFrameBufferObject(mMsaaFbo); + + traverse(node, nv); +} + void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere) { osg::BoundingSphere::vec_type xdash = bsphere._center; @@ -308,8 +335,8 @@ osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, return osg::Matrix( A/aspect, 0, 0, 0, 0, A, 0, 0, - 0, 0, far/(far-near)-1.0, -1, - 0, 0, -(far*near)/(far - near), 0 + 0, 0, near/(far-near), -1, + 0, 0, (far*near)/(far - near), 0 ); } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 0164e0c99e..69e66bb8b3 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -48,6 +49,22 @@ namespace SceneUtil bool mDone; }; + // Allows camera to render to a color and floating point depth texture with a multisampled framebuffer. + // Must be set on a cameras cull callback. + // When the depth texture isn't needed as a sampler, use osg::Camera::attach(osg::Camera::DEPTH_COMPONENT, GL_DEPTH_COMPONENT32F) instead. + // If multisampling is not being used on the color buffer attachment, use the osg::Camera::attach() method. + class AttachMultisampledDepthColorCallback : public osg::NodeCallback + { + public: + AttachMultisampledDepthColorCallback(const osg::ref_ptr& colorTex, const osg::ref_ptr& depthTex, int samples, int colorSamples); + + void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + + private: + osg::ref_ptr mFbo; + osg::ref_ptr mMsaaFbo; + }; + // Transform a bounding sphere by a matrix // based off private code in osg::Transform // TODO: patch osg to make public From cad0b151cb915556f9e30fcc36c193009c37f818 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Fri, 9 Jul 2021 10:05:27 -0700 Subject: [PATCH 1143/2859] enable shaders path and dehardcode depth formats --- CHANGELOG.md | 1 + apps/openmw/mwgui/bookpage.cpp | 8 +- apps/openmw/mwgui/mapwindow.cpp | 4 +- apps/openmw/mwgui/mapwindow.hpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 6 +- apps/openmw/mwrender/actorspaths.cpp | 5 ++ apps/openmw/mwrender/bulletdebugdraw.cpp | 14 +++- apps/openmw/mwrender/characterpreview.cpp | 3 +- apps/openmw/mwrender/globalmap.cpp | 6 +- apps/openmw/mwrender/globalmap.hpp | 4 +- apps/openmw/mwrender/localmap.cpp | 12 +-- apps/openmw/mwrender/localmap.hpp | 3 +- apps/openmw/mwrender/navmesh.cpp | 6 ++ apps/openmw/mwrender/npcanimation.cpp | 7 +- apps/openmw/mwrender/pathgrid.cpp | 5 ++ apps/openmw/mwrender/postprocessor.cpp | 72 +++++++++++++---- apps/openmw/mwrender/postprocessor.hpp | 12 ++- apps/openmw/mwrender/recastmesh.cpp | 6 ++ apps/openmw/mwrender/renderingmanager.cpp | 77 ++++++++++++++----- apps/openmw/mwrender/renderingmanager.hpp | 6 +- apps/openmw/mwrender/screenshotmanager.cpp | 4 +- apps/openmw/mwrender/sky.cpp | 18 ++--- apps/openmw/mwrender/util.cpp | 8 ++ apps/openmw/mwrender/util.hpp | 2 + components/resource/scenemanager.cpp | 10 +++ components/resource/scenemanager.hpp | 4 + components/sceneutil/mwshadowtechnique.cpp | 69 ++++++----------- components/sceneutil/shadow.cpp | 4 +- components/sceneutil/util.cpp | 8 +- components/sceneutil/util.hpp | 4 +- components/terrain/cellborder.cpp | 13 +++- components/terrain/cellborder.hpp | 8 +- components/terrain/world.cpp | 3 +- .../reference/modding/settings/camera.rst | 24 +++++- files/settings-default.cfg | 5 +- files/shaders/CMakeLists.txt | 2 + files/shaders/debug_fragment.glsl | 8 ++ files/shaders/debug_vertex.glsl | 12 +++ files/shaders/depth.glsl | 6 +- files/shaders/groundcover_vertex.glsl | 5 +- files/shaders/nv_default_vertex.glsl | 6 +- files/shaders/nv_nolighting_vertex.glsl | 6 +- files/shaders/objects_vertex.glsl | 7 +- files/shaders/terrain_vertex.glsl | 6 +- files/shaders/water_vertex.glsl | 8 +- 45 files changed, 353 insertions(+), 156 deletions(-) create mode 100644 files/shaders/debug_fragment.glsl create mode 100644 files/shaders/debug_vertex.glsl diff --git a/CHANGELOG.md b/CHANGELOG.md index a9ec5a946e..7e136d707a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Feature #5489: MCP: Telekinesis fix for activators Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving + Feature #6032: Reverse-z depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly 0.47.0 diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index c902ceb88a..e4ce2a9593 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -8,14 +8,12 @@ #include "MyGUI_FactoryManager.h" #include -#include -#include -#include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwrender/util.hpp" + namespace MWGui { struct TypesetBookImpl; @@ -1221,7 +1219,7 @@ public: RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); - float z = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getReverseZ() ? 1.0 : -1.0; + float z = MWRender::getReverseZ() ? 1.f : -1.f; GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), static_cast(mCoord.top - mViewTop), z /*mNode->getNodeDepth()*/, vertices, renderXform); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 538b866cac..efb434d3bc 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -741,7 +741,7 @@ namespace MWGui } // ------------------------------------------------------------------------------------------ - MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue, bool reverseZ) + MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) : WindowPinnableBase("openmw_map_window.layout") , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) @@ -751,7 +751,7 @@ namespace MWGui , mGlobal(Settings::Manager::getBool("global", "Map")) , mEventBoxGlobal(nullptr) , mEventBoxLocal(nullptr) - , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue, reverseZ)) + , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() { static bool registered = false; diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index b7664505af..cb0d368b30 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -222,7 +222,7 @@ namespace MWGui class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop { public: - MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue, bool reverseZ); + MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue); virtual ~MapWindow(); void setCellName(const std::string& cellName); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 5feed41864..e42d606966 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -35,7 +35,6 @@ #include #include -#include #include @@ -305,9 +304,8 @@ namespace MWGui mGuiModeStates[GM_MainMenu] = GuiModeState(menu); mWindows.push_back(menu); - bool reverseZ = mResourceSystem->getSceneManager()->getReverseZ(); - mLocalMapRender = new MWRender::LocalMap(mViewer->getSceneData()->asGroup(), reverseZ); - mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender, mWorkQueue, reverseZ); + mLocalMapRender = new MWRender::LocalMap(mViewer->getSceneData()->asGroup()); + mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender, mWorkQueue); mWindows.push_back(mMap); mMap->renderGlobalMap(); trackWindow(mMap, "map"); diff --git a/apps/openmw/mwrender/actorspaths.cpp b/apps/openmw/mwrender/actorspaths.cpp index 35b2553555..4e3bfd79a6 100644 --- a/apps/openmw/mwrender/actorspaths.cpp +++ b/apps/openmw/mwrender/actorspaths.cpp @@ -2,9 +2,13 @@ #include "vismask.hpp" #include +#include +#include #include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" namespace MWRender { ActorsPaths::ActorsPaths(const osg::ref_ptr& root, bool enabled) @@ -43,6 +47,7 @@ namespace MWRender const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings); if (newGroup) { + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(newGroup, "debug"); newGroup->setNodeMask(Mask_Debug); mRootNode->addChild(newGroup); mGroups[actor] = newGroup; diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index 207b0ee504..9039ec53ce 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -10,10 +10,19 @@ #include #include #include +#include #include "bulletdebugdraw.hpp" #include "vismask.hpp" +#include +#include + +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + namespace MWRender { @@ -30,7 +39,6 @@ void DebugDrawer::createGeometry() { mGeometry = new osg::Geometry; mGeometry->setNodeMask(Mask_Debug); - mVertices = new osg::Vec3Array; mColors = new osg::Vec4Array; @@ -42,6 +50,8 @@ void DebugDrawer::createGeometry() mGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); mGeometry->setDataVariance(osg::Object::DYNAMIC); mGeometry->addPrimitiveSet(mDrawArrays); + // make this friendly to recreateShaders() + mGeometry->setStateSet(new osg::StateSet); mParentNode->addChild(mGeometry); @@ -52,6 +62,8 @@ void DebugDrawer::createGeometry() mShapesRoot->setDataVariance(osg::Object::DYNAMIC); mShapesRoot->setNodeMask(Mask_Debug); mParentNode->addChild(mShapesRoot); + + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mGeometry, "debug"); } } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index c4c628e04d..580451ff8f 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -168,7 +168,7 @@ namespace MWRender mCamera->setRenderOrder(osg::Camera::PRE_RENDER); mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); mCamera->setName("CharacterPreview"); - mCamera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); mCamera->setCullMask(~(Mask_UpdateVisitor)); mCamera->setNodeMask(Mask_RenderToTexture); @@ -187,6 +187,7 @@ namespace MWRender defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); + stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(mCamera->getProjectionMatrix()))); osg::ref_ptr depth = new osg::Depth; stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index f9bbb3d2f4..2f160933fd 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -25,6 +25,7 @@ #include "../mwworld/esmstore.hpp" #include "vismask.hpp" +#include "util.hpp" namespace { @@ -220,14 +221,13 @@ namespace MWRender osg::ref_ptr mOverlayTexture; }; - GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue, bool reverseZ) + GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue) : mRoot(root) , mWorkQueue(workQueue) , mWidth(0) , mHeight(0) , mMinX(0), mMaxX(0) , mMinY(0), mMaxY(0) - , mReverseZ(reverseZ) { mCellSize = Settings::Manager::getInt("global map cell size", "Map"); @@ -325,7 +325,7 @@ namespace MWRender if (texture) { osg::ref_ptr geom = createTexturedQuad(srcLeft, srcTop, srcRight, srcBottom); - auto depth = SceneUtil::createDepth(mReverseZ); + auto depth = SceneUtil::createDepth(getReverseZ()); depth->setWriteMask(0); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setAttribute(depth); diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index a27621c9f4..fd8a8d1016 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -33,7 +33,7 @@ namespace MWRender class GlobalMap { public: - GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue, bool reverseZ); + GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue); ~GlobalMap(); void render(); @@ -126,8 +126,6 @@ namespace MWRender int mHeight; int mMinX, mMaxX, mMinY, mMaxY; - - bool mReverseZ; }; } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index e0b3d6c69b..72f2828ace 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -30,6 +30,7 @@ #include "../mwworld/cellstore.hpp" #include "vismask.hpp" +#include "util.hpp" namespace { @@ -83,14 +84,13 @@ namespace namespace MWRender { -LocalMap::LocalMap(osg::Group* root, bool reverseZ) +LocalMap::LocalMap(osg::Group* root) : mRoot(root) , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) , mMapWorldSize(Constants::CellSizeInUnits) , mCellDistance(Constants::CellGridRadius) , mAngle(0.f) , mInterior(false) - , mReverseZ(reverseZ) { // Increase map resolution, if use UI scaling float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); @@ -178,7 +178,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f { osg::ref_ptr camera (new osg::Camera); - if (mReverseZ) + if (getReverseZ()) camera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10)); else camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); @@ -201,13 +201,15 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f osg::ref_ptr stateset = new osg::StateSet; stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); - if (mReverseZ) + if (getReverseZ()) { camera->setClearDepth(0.0); - auto depth = SceneUtil::createDepth(mReverseZ); + auto depth = SceneUtil::createDepth(true); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } + stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(camera->getProjectionMatrix())), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog (new osg::Fog); diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 316cbd53d3..f9ccd5a011 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -36,7 +36,7 @@ namespace MWRender class LocalMap { public: - LocalMap(osg::Group* root, bool reverseZ); + LocalMap(osg::Group* root); ~LocalMap(); /** @@ -156,7 +156,6 @@ namespace MWRender void setupRenderToTexture(osg::ref_ptr camera, int x, int y); bool mInterior; - bool mReverseZ; osg::BoundingBox mBounds; }; diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 791c41a1a0..523f7531af 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -2,9 +2,14 @@ #include "vismask.hpp" #include +#include +#include #include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + namespace MWRender { NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) @@ -45,6 +50,7 @@ namespace MWRender mGroup = SceneUtil::createNavMeshGroup(navMesh, settings); if (mGroup) { + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mGroup, "debug"); mGroup->setNodeMask(Mask_Debug); mRootNode->addChild(mGroup); } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d103234f31..64dc9ecfe0 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -43,6 +43,7 @@ #include "rotatecontroller.hpp" #include "renderbin.hpp" #include "vismask.hpp" +#include "util.hpp" namespace { @@ -370,9 +371,9 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) class DepthClearCallback : public osgUtil::RenderBin::DrawCallback { public: - DepthClearCallback(bool reverseZ) + DepthClearCallback() { - mDepth = SceneUtil::createDepth(reverseZ); + mDepth = SceneUtil::createDepth(getReverseZ()); mDepth->setWriteMask(true); } @@ -432,7 +433,7 @@ void NpcAnimation::setRenderBin() if (!prototypeAdded) { osg::ref_ptr depthClearBin (new osgUtil::RenderBin); - depthClearBin->setDrawCallback(new DepthClearCallback(mResourceSystem->getSceneManager()->getReverseZ())); + depthClearBin->setDrawCallback(new DepthClearCallback); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); prototypeAdded = true; } diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index c20e81bb2d..42d9150811 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -8,7 +8,10 @@ #include #include +#include #include +#include +#include #include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwbase/environment.hpp" @@ -112,6 +115,8 @@ void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) osg::ref_ptr geometry = SceneUtil::createPathgridGeometry(*pathgrid); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(geometry, "debug"); + cellPathGrid->addChild(geometry); mPathGridRoot->addChild(cellPathGrid); diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 43765a25ba..a412ca8cfa 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -9,6 +9,11 @@ #include #include +#include +#include + +#include "vismask.hpp" +#include "renderingmanager.hpp" namespace { @@ -57,6 +62,8 @@ namespace traverse(node, nv); } + + private: MWRender::PostProcessor* mPostProcessor; unsigned int mLastFrameNumber; }; @@ -70,6 +77,7 @@ namespace void resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height) override { + gc->resizedImplementation(x, y, width, height); mPostProcessor->resize(width, height); } @@ -79,22 +87,55 @@ namespace namespace MWRender { - PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode) + PostProcessor::PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode) : mViewer(viewer) , mRootNode(new osg::Group) + , mRendering(rendering) { + osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); + unsigned int contextID = gc->getState()->getContextID(); + osg::GLExtensions* ext = gc->getState()->get(); + + constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: "; + + if (!ext->isFrameBufferObjectSupported) + { + Log(Debug::Warning) << errPreamble << "FrameBufferObject unsupported."; + return; + } + + if (Settings::Manager::getInt("antialiasing", "Video") > 1 && !ext->isRenderbufferMultisampleSupported()) + { + Log(Debug::Warning) << errPreamble << "RenderBufferMultiSample unsupported. Disabling antialiasing will resolve this issue."; + return; + } + + if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) + mDepthFormat = GL_DEPTH_COMPONENT32F; + else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) + mDepthFormat = GL_DEPTH_COMPONENT32F_NV; + else + { + // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. + // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no + // benefits if no floating point depth formats are supported. + mDepthFormat = GL_DEPTH_COMPONENT24; + Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; + return; + } + int width = viewer->getCamera()->getViewport()->width(); int height = viewer->getCamera()->getViewport()->height(); createTexturesAndCamera(width, height); - resize(width, height); + resize(width, height, true); mRootNode->addChild(mHUDCamera); mRootNode->addChild(rootNode); mViewer->setSceneData(mRootNode); - // Main camera is treated specially, we need to manually set the FBO and - // resolve FBO during the cull callback. + // We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate + // RTT camera this would not be needed. mViewer->getCamera()->addCullCallback(new CullCallback(this)); mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); @@ -103,12 +144,12 @@ namespace MWRender mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); } - void PostProcessor::resize(int width, int height) + void PostProcessor::resize(int width, int height, bool init) { mDepthTex->setTextureSize(width, height); mSceneTex->setTextureSize(width, height); - mDepthTex->dirtyTextureObject(); - mSceneTex->dirtyTextureObject(); + mDepthTex->dirtyTextureObject(); + mSceneTex->dirtyTextureObject(); int samples = Settings::Manager::getInt("antialiasing", "Video"); @@ -130,15 +171,13 @@ namespace MWRender mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthRB)); } - double prevWidth = mViewer->getCamera()->getViewport()->width(); - double prevHeight = mViewer->getCamera()->getViewport()->height(); - double scaleX = prevWidth / width; - double scaleY = prevHeight / height; + if (init) + return; - mViewer->getCamera()->resize(width,height); - mHUDCamera->resize(width,height); + mViewer->getCamera()->resize(width, height); + mHUDCamera->resize(width, height); - mViewer->getCamera()->getProjectionMatrix() *= osg::Matrix::scale(scaleX, scaleY, 1.0); + mRendering.updateProjectionMatrix(); } void PostProcessor::createTexturesAndCamera(int width, int height) @@ -146,8 +185,8 @@ namespace MWRender mDepthTex = new osg::Texture2D; mDepthTex->setTextureSize(width, height); mDepthTex->setSourceFormat(GL_DEPTH_COMPONENT); - mDepthTex->setSourceType(GL_FLOAT); - mDepthTex->setInternalFormat(GL_DEPTH_COMPONENT32F); + mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT : GL_UNSIGNED_INT); + mDepthTex->setInternalFormat(mDepthFormat); mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); mDepthTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); @@ -206,6 +245,7 @@ namespace MWRender program->addShader(fragShader); mHUDCamera->addChild(createFullScreenTri()); + mHUDCamera->setNodeMask(Mask_RenderToTexture); auto* stateset = mHUDCamera->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index c9d353f8cb..5be58f33ac 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -18,15 +18,19 @@ namespace osgViewer namespace MWRender { + class RenderingManager; + class PostProcessor { public: - PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode); + PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode); auto getMsaaFbo() { return mMsaaFbo; } auto getFbo() { return mFbo; } - void resize(int width, int height); + int getDepthFormat() { return mDepthFormat; } + + void resize(int width, int height, bool init=false); private: osgViewer::Viewer* mViewer; @@ -39,7 +43,11 @@ namespace MWRender osg::ref_ptr mSceneTex; osg::ref_ptr mDepthTex; + int mDepthFormat; + void createTexturesAndCamera(int width, int height); + + RenderingManager& mRendering; }; } diff --git a/apps/openmw/mwrender/recastmesh.cpp b/apps/openmw/mwrender/recastmesh.cpp index 7c7c6b8eb1..5afa78cd93 100644 --- a/apps/openmw/mwrender/recastmesh.cpp +++ b/apps/openmw/mwrender/recastmesh.cpp @@ -1,11 +1,15 @@ #include "recastmesh.hpp" #include +#include +#include #include #include "vismask.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" namespace MWRender { RecastMesh::RecastMesh(const osg::ref_ptr& root, bool enabled) @@ -49,6 +53,7 @@ namespace MWRender || it->second.mRevision != tile->second->getRevision()) { const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mRootNode->removeChild(it->second.mValue); mRootNode->addChild(group); @@ -66,6 +71,7 @@ namespace MWRender if (mGroups.count(tile.first)) continue; const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group}); mRootNode->addChild(group); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 65aa4a5a6b..c64bcd3383 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -74,6 +74,45 @@ namespace MWRender { + class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater + { + public: + SharedUniformStateUpdater() + : mLinearFac(0.f) + { + } + + void setDefaults(osg::StateSet *stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); + stateset->addUniform(new osg::Uniform("linearFac", 0.f)); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(mProjectionMatrix); + + auto* uLinearFac = stateset->getUniform("linearFac"); + if (uLinearFac) + uLinearFac->set(mLinearFac); + } + + void setProjectionMatrix(const osg::Matrixf& projectionMatrix) + { + mProjectionMatrix = projectionMatrix; + } + + void setLinearFac(float linearFac) + { + mLinearFac = linearFac; + } + + private: + osg::Matrixf mProjectionMatrix; + float mLinearFac; + }; class StateUpdater : public SceneUtil::StateSetUpdater { @@ -202,10 +241,7 @@ namespace MWRender , mFieldOfViewOverride(0.f) { auto ext = osg::GLExtensions::Get(0, false); - bool reverseZ = ext && ext->isClipControlSupported; - - if (getenv("OPENMW_DISABLE_REVERSEZ") != nullptr) - reverseZ = false; + bool reverseZ = Settings::Manager::getBool("reverse z", "Camera") && ext && ext->isClipControlSupported; if (reverseZ) Log(Debug::Info) << "Using reverse-z depth buffer"; @@ -219,7 +255,8 @@ namespace MWRender bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") - || lightingMethod != SceneUtil::LightingMethod::FFP; + || lightingMethod != SceneUtil::LightingMethod::FFP + || reverseZ; resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); @@ -370,6 +407,10 @@ namespace MWRender // Use a stub grid to avoid splitting between chunks for active grid and chunks for distant cells. mGroundcoverWorld->setActiveGrid(osg::Vec4i(0, 0, 0, 0)); } + + mPostProcessor.reset(new PostProcessor(*this, viewer, mRootNode)); + resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat()); + // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -412,6 +453,9 @@ namespace MWRender mStateUpdater = new StateUpdater; sceneRoot->addUpdateCallback(mStateUpdater); + mSharedUniformStateUpdater = new SharedUniformStateUpdater; + rootNode->addUpdateCallback(mSharedUniformStateUpdater); + osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING; if (!Settings::Manager::getBool("small feature culling", "Camera")) @@ -433,7 +477,9 @@ namespace MWRender NifOsg::Loader::setReverseZ(reverseZ); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); - mNearClip = Settings::Manager::getFloat("near clip", "Camera"); + // TODO: Near clip should not need to be bounded like this, but too small values break OSG shadow calculations CPU-side. + // See issue: #6072 + mNearClip = std::max(0.005f, Settings::Manager::getFloat("near clip", "Camera")); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); float fov = Settings::Manager::getFloat("field of view", "Camera"); mFieldOfView = std::min(std::max(1.f, fov), 179.f); @@ -462,11 +508,6 @@ namespace MWRender mRootNode->getOrCreateStateSet()->setAttributeAndModes(clipcontrol, osg::StateAttribute::ON); } - if (ext && ext->isFrameBufferObjectSupported) - mPostProcessor.reset(new PostProcessor(viewer, mRootNode)); - else - Log(Debug::Warning) << "Disabling postprocessing and using default framebuffer for rendering: FrameBufferObjects not supported"; - updateProjectionMatrix(); } @@ -1100,19 +1141,15 @@ namespace MWRender if (mFieldOfViewOverridden) fov = mFieldOfViewOverride; + mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); + if (mResourceSystem->getSceneManager()->getReverseZ()) { - mViewer->getCamera()->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspectiveInf(fov, aspect, mNearClip)); - float linearFac = -mNearClip / (mViewDistance - mNearClip) - 1.0; - mRootNode->getOrCreateStateSet()->getOrCreateUniform("linearFac", osg::Uniform::FLOAT, 1)->set(linearFac); - - osg::Matrix shadowProj = osg::Matrix::perspective(fov, aspect, mNearClip, mViewDistance); - mViewer->getCamera()->setUserValue("shadowProj", shadowProj); - mViewer->getCamera()->setUserValue("near", static_cast(mNearClip)); - mViewer->getCamera()->setUserValue("far", static_cast(mViewDistance)); + mSharedUniformStateUpdater->setLinearFac(-mNearClip / (mViewDistance - mNearClip) - 1.f); + mSharedUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); } else - mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); + mSharedUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix()); mUniformNear->set(mNearClip); mUniformFar->set(mViewDistance); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b63841494b..cbe25addf1 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -72,6 +72,7 @@ namespace MWRender { class GroundcoverUpdater; class StateUpdater; + class SharedUniformStateUpdater; class EffectManager; class ScreenshotManager; @@ -240,9 +241,9 @@ namespace MWRender void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr); bool pagingUnlockCache(); void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); - - private: + void updateProjectionMatrix(); + private: void updateTextureFiltering(); void updateAmbient(); void setFogColor(const osg::Vec4f& color); @@ -296,6 +297,7 @@ namespace MWRender osg::Vec3f mCurrentCameraPos; osg::ref_ptr mStateUpdater; + osg::ref_ptr mSharedUniformStateUpdater; osg::Vec4f mAmbientColor; float mMinimumAmbientLuminance; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index ba4282e5b6..967e282f30 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -298,7 +298,7 @@ namespace MWRender camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); - if (MWBase::Environment::get().getResourceSystem()->getSceneManager()->getReverseZ()) + if (getReverseZ()) camera->setClearDepth(0.0); camera->setViewport(0, 0, w, h); @@ -336,7 +336,7 @@ namespace MWRender float nearClip = Settings::Manager::getFloat("near clip", "Camera"); float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); // each cubemap side sees 90 degrees - if (MWBase::Environment::get().getResourceSystem()->getSceneManager()->getReverseZ()) + if (getReverseZ()) rttCamera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspectiveInf(90.0, w/float(h), nearClip)); else rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 604e417a45..dd97f79eda 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -54,6 +54,7 @@ #include "vismask.hpp" #include "renderbin.hpp" +#include "util.hpp" namespace { @@ -473,7 +474,7 @@ const float CelestialBody::mDistance = 1000.0f; class Sun : public CelestialBody { public: - Sun(osg::Group* parentNode, Resource::ImageManager& imageManager, bool reverseZ) + Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) , mUpdater(new Updater) { @@ -502,8 +503,8 @@ public: mTransform->addChild(queryNode); - mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true, reverseZ); - mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false, reverseZ); + mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); + mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); createSunFlash(imageManager); createSunGlare(); @@ -556,7 +557,7 @@ private: }; /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. - osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible, bool reverseZ) + osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible) { osg::ref_ptr oqn = new osg::OcclusionQueryNode; oqn->setQueriesEnabled(true); @@ -594,11 +595,11 @@ private: osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { - auto depth = SceneUtil::createDepth(reverseZ); + auto depth = SceneUtil::createDepth(getReverseZ()); // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. - float far = reverseZ ? 0.0 : 1.0; + double far = getReverseZ() ? 0.0 : 1.0; depth->setZNear(far); depth->setZFar(far); depth->setWriteMask(false); @@ -1188,8 +1189,7 @@ void SkyManager::create() mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager()); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - bool reverseZ = mSceneManager->getReverseZ(); - mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), reverseZ)); + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); @@ -1210,7 +1210,7 @@ void SkyManager::create() mCloudMesh2->addUpdateCallback(mCloudUpdater2); mCloudMesh2->setNodeMask(0); - auto depth = SceneUtil::createDepth(reverseZ); + auto depth = SceneUtil::createDepth(mSceneManager->getReverseZ()); depth->setWriteMask(false); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index e3fc48040f..5740f7df6c 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -4,10 +4,13 @@ #include #include +#include #include #include #include +#include "../mwbase/environment.hpp" + namespace MWRender { @@ -64,4 +67,9 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou node->setStateSet(stateset); } +bool getReverseZ() +{ + return MWBase::Environment::get().getResourceSystem()->getSceneManager()->getReverseZ(); +} + } diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index a89baa22b1..c005dd9b23 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -32,6 +32,8 @@ namespace MWRender // no traverse() } }; + + bool getReverseZ(); } #endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 70e849b234..3c548ead23 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -285,6 +285,16 @@ namespace Resource return mReverseZ; } + void SceneManager::setDepthFormat(GLenum format) + { + mDepthFormat = format; + } + + GLenum SceneManager::getDepthFormat() const + { + return mDepthFormat; + } + void SceneManager::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 260fd4717f..780375f77e 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -95,6 +95,9 @@ namespace Resource void setReverseZ(bool reverseZ); bool getReverseZ() const; + void setDepthFormat(GLenum format); + GLenum getDepthFormat() const; + /// @see ShaderVisitor::setAutoUseNormalMaps void setAutoUseNormalMaps(bool use); @@ -206,6 +209,7 @@ namespace Resource SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; bool mReverseZ; + GLenum mDepthFormat; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index bbda5442bf..0004045dc8 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -28,8 +28,6 @@ #include #include "shadowsbin.hpp" -#include - namespace { using namespace osgShadow; @@ -350,11 +348,6 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) _projectionMatrix = cv->getProjectionMatrix(); } -bool isOrthographicViewFrustum(const osg::Matrix& m) -{ - return m(0,3)==0.0 && m(1,3)==0.0 && m(2,3)==0.0; -} - } // namespace MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix) : @@ -988,9 +981,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) return; } - osg::Matrix shadowProj; - cv.getCurrentCamera()->getUserValue("shadowProj", shadowProj); - ViewDependentData* vdd = getViewDependentData(&cv); if (!vdd) @@ -1006,41 +996,34 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) osg::CullSettings::ComputeNearFarMode cachedNearFarMode = cv.getComputeNearFarMode(); + osg::RefMatrix& viewProjectionMatrix = *cv.getProjectionMatrix(); + + // check whether this main views projection is perspective or orthographic + bool orthographicViewFrustum = viewProjectionMatrix(0,3)==0.0 && + viewProjectionMatrix(1,3)==0.0 && + viewProjectionMatrix(2,3)==0.0; + double minZNear = 0.0; double maxZFar = dbl_max; - bool orthographicViewFrustum; - if (_reverseZ) - { - cv.getCurrentCamera()->getUserValue("near", minZNear); - cv.getCurrentCamera()->getUserValue("far", maxZFar); - orthographicViewFrustum = isOrthographicViewFrustum(shadowProj); - } - else + if (cachedNearFarMode==osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { - osg::RefMatrix& viewProjectionMatrix = *cv.getProjectionMatrix(); - - // check whether this main views projection is perspective or orthographic - orthographicViewFrustum = isOrthographicViewFrustum(viewProjectionMatrix); - - if (cachedNearFarMode==osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + double left, right, top, bottom; + if (orthographicViewFrustum) { - double left, right, top, bottom; - if (orthographicViewFrustum) - { - viewProjectionMatrix.getOrtho(left, right, bottom, top, minZNear, maxZFar); - } - else - { - viewProjectionMatrix.getFrustum(left, right, bottom, top, minZNear, maxZFar); - } + viewProjectionMatrix.getOrtho(left, right, bottom, top, minZNear, maxZFar); } - - // set the compute near/far mode to the highest quality setting to ensure we push the near plan out as far as possible - if (settings->getComputeNearFarModeOverride()!=osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + else { - cv.setComputeNearFarMode(settings->getComputeNearFarModeOverride()); + viewProjectionMatrix.getFrustum(left, right, bottom, top, minZNear, maxZFar); } + OSG_INFO<<"minZNear="<addUniform(new osg::Uniform("alphaTestShadows", false)); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(true); - osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::NEGATIVE_ONE_TO_ONE); if (_reverseZ) + { + osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::NEGATIVE_ONE_TO_ONE); _shadowCastingStateSet->setAttribute(clipcontrol, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } _shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 32c2edeccd..7ca15e612d 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -95,8 +95,8 @@ namespace SceneUtil } ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager, bool reverseZ) - : mShadowedScene(new osgShadow::ShadowedScene) - , mReverseZ(reverseZ) + : mReverseZ(reverseZ) + , mShadowedScene(new osgShadow::ShadowedScene) , mShadowTechnique(new MWShadowTechnique) , mOutdoorShadowCastingMask(outdoorShadowCastingMask) , mIndoorShadowCastingMask(indoorShadowCastingMask) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 79fe5d13c5..1f79469a19 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -314,8 +315,7 @@ bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg:: osg::ref_ptr createDepth(bool reverseZ) { - static osg::Depth::Function func = reverseZ ? osg::Depth::GEQUAL : osg::Depth::LEQUAL; - return new osg::Depth(func); + return new osg::Depth(reverseZ ? osg::Depth::GEQUAL : osg::Depth::LEQUAL); } osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near) @@ -350,5 +350,9 @@ osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, doubl ); } +bool isFloatingPointDepthFormat(GLenum format) +{ + return format == GL_DEPTH_COMPONENT32F || format == GL_DEPTH_COMPONENT32F_NV; +} } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 69e66bb8b3..bd4df5441a 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -50,7 +50,7 @@ namespace SceneUtil }; // Allows camera to render to a color and floating point depth texture with a multisampled framebuffer. - // Must be set on a cameras cull callback. + // Must be set on a camera's cull callback. // When the depth texture isn't needed as a sampler, use osg::Camera::attach(osg::Camera::DEPTH_COMPONENT, GL_DEPTH_COMPONENT32F) instead. // If multisampling is not being used on the color buffer attachment, use the osg::Camera::attach() method. class AttachMultisampledDepthColorCallback : public osg::NodeCallback @@ -99,6 +99,8 @@ namespace SceneUtil // Returns an orthographic projection matrix for use with a reversed z-buffer. osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far); + // Returns true if the GL format is a floating point depth format + bool isFloatingPointDepthFormat(GLenum format); } #endif diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 47c567f544..c34ed9ab0a 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -8,13 +8,16 @@ #include "world.hpp" #include "../esm/loadland.hpp" +#include + namespace Terrain { -CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask): - mWorld(world), - mRoot(root), - mBorderMask(borderMask) +CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask, Resource::SceneManager* sceneManager) + : mWorld(world) + , mSceneManager(sceneManager) + , mRoot(root) + , mBorderMask(borderMask) { } @@ -73,6 +76,8 @@ void CellBorder::createCellBorderGeometry(int x, int y) polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); stateSet->setAttributeAndModes(polygonmode,osg::StateAttribute::ON); + mSceneManager->recreateShaders(borderGeode, "debug"); + borderGeode->setNodeMask(mBorderMask); mRoot->addChild(borderGeode); diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index 908cdea097..ee8fd72593 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -4,6 +4,11 @@ #include #include +namespace Resource +{ + class SceneManager; +} + namespace Terrain { class World; @@ -16,7 +21,7 @@ namespace Terrain public: typedef std::map, osg::ref_ptr > CellGrid; - CellBorder(Terrain::World *world, osg::Group *root, int borderMask); + CellBorder(Terrain::World *world, osg::Group *root, int borderMask, Resource::SceneManager* sceneManager); void createCellBorderGeometry(int x, int y); void destroyCellBorderGeometry(int x, int y); @@ -28,6 +33,7 @@ namespace Terrain protected: Terrain::World *mWorld; + Resource::SceneManager* mSceneManager; osg::Group *mRoot; CellGrid mCellBorderNodes; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index d1581724e0..17a51913e5 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "storage.hpp" #include "texturemanager.hpp" @@ -43,7 +44,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager())); mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); mChunkManager->setNodeMask(nodeMask); - mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask)); + mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask,mResourceSystem->getSceneManager())); mResourceSystem->addResourceManager(mChunkManager.get()); mResourceSystem->addResourceManager(mTextureManager.get()); diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index a880a2b62d..fe6217b22b 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -5,8 +5,8 @@ near clip --------- :Type: floating point -:Range: > 0 -:Default: 3.0 +:Range: >= 0.005 +:Default: 1.0 This setting controls the distance to the near clipping plane. The value must be greater than zero. Values greater than approximately 18.0 will occasionally clip objects in the world in front of the character. @@ -235,3 +235,23 @@ Maximum roll angle in degrees. This setting can only be configured by editing the settings configuration file. +reverse z +--------- + +:Type: boolean +:Range: True/False +:Default: True + +Enables a reverse-z depth buffer in which the depth range is reversed. This +allows for small :ref:`near clip` values and removes almost all z-fighting with +terrain and even tightly coupled meshes at extreme view distances. For this to +be useful, a floating point depth buffer is required. These features require +driver and hardware support, but should work on any semi-modern desktop hardware +through OpenGL extensions. The exception is macOS, which has since dropped +development of OpenGL drivers. If unsupported, this setting has no effect. + +Note, this will force OpenMW to use shaders as if :ref:`force shaders` was enabled. +The performance impact of this feature should be negligible. + +This setting can only be configured by editing the settings configuration file. + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 36eca7f1ab..4943ce969b 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -15,7 +15,7 @@ [Camera] # Near clipping plane (>0.0, e.g. 0.01 to 18.0). -near clip = 3 +near clip = 1 # Cull objects that occupy less than 'small feature culling pixel size' on the screen. small feature culling = true @@ -67,6 +67,9 @@ head bobbing height = 3.0 # Maximum camera roll angle (degrees) head bobbing roll = 0.2 +# Reverse the depth range from [0,1] to [1,0]. +reverse z = true + [Cells] # Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 5e049b8a3f..04446d2982 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -34,6 +34,8 @@ set(SHADER_FILES nv_nolighting_fragment.glsl gui_vertex.glsl gui_fragment.glsl + debug_vertex.glsl + debug_fragment.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/debug_fragment.glsl b/files/shaders/debug_fragment.glsl new file mode 100644 index 0000000000..263d4e1f24 --- /dev/null +++ b/files/shaders/debug_fragment.glsl @@ -0,0 +1,8 @@ +#version 120 + +varying vec4 passColor; + +void main() +{ + gl_FragData[0] = passColor; +} diff --git a/files/shaders/debug_vertex.glsl b/files/shaders/debug_vertex.glsl new file mode 100644 index 0000000000..f68c3d6459 --- /dev/null +++ b/files/shaders/debug_vertex.glsl @@ -0,0 +1,12 @@ +#version 120 + +uniform mat4 projectionMatrix; + +varying vec4 passColor; + +void main() +{ + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + + passColor = gl_Color; +} diff --git a/files/shaders/depth.glsl b/files/shaders/depth.glsl index 0adde7a2a7..aa6f54b99d 100644 --- a/files/shaders/depth.glsl +++ b/files/shaders/depth.glsl @@ -2,11 +2,11 @@ uniform float linearFac; #endif -float getLinearDepth(in vec4 viewPos) +float getLinearDepth(in float z, in float viewZ) { #if @reverseZ - return linearFac*viewPos.z; + return linearFac*viewZ; #else - return gl_Position.z; + return z; #endif } \ No newline at end of file diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 94586734b6..c8db4be000 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -46,6 +46,7 @@ uniform mat4 osg_ViewMatrixInverse; uniform mat4 osg_ViewMatrix; uniform float windSpeed; uniform vec3 playerPos; +uniform mat4 projectionMatrix; #if @groundcoverStompMode == 0 #else @@ -142,9 +143,9 @@ void main(void) if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd) gl_Position = vec4(0.0, 0.0, 0.0, 1.0); else - gl_Position = gl_ProjectionMatrix * viewPos; + gl_Position = projectionMatrix * viewPos; - linearDepth = getLinearDepth(viewPos); + linearDepth = getLinearDepth(gl_Position.z, viewPos.z); #if (!PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index 1c5483dcda..34d5415250 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -8,6 +8,8 @@ #extension GL_EXT_gpu_shader4: require #endif +uniform mat4 projectionMatrix; + #if @diffuseMap varying vec2 diffuseMapUV; #endif @@ -36,12 +38,12 @@ varying vec3 passNormal; void main(void) { - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); - linearDepth = getLinearDepth(viewPos); + linearDepth = getLinearDepth(gl_Position.z, viewPos.z); #if @diffuseMap diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; diff --git a/files/shaders/nv_nolighting_vertex.glsl b/files/shaders/nv_nolighting_vertex.glsl index 02c28eb5e3..617f0489a4 100644 --- a/files/shaders/nv_nolighting_vertex.glsl +++ b/files/shaders/nv_nolighting_vertex.glsl @@ -1,5 +1,7 @@ #version 120 +uniform mat4 projectionMatrix; + #if @diffuseMap varying vec2 diffuseMapUV; #endif @@ -21,14 +23,14 @@ varying float passFalloff; void main(void) { - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; #if @radialFog euclideanDepth = length(viewPos.xyz); #else - linearDepth = getLinearDepth(viewPos); + linearDepth = getLinearDepth(gl_Position.z, viewPos.z); #endif #if @diffuseMap diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 34484ebd41..969fe5903e 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -8,6 +8,8 @@ #extension GL_EXT_gpu_shader4: require #endif +uniform mat4 projectionMatrix; + #if @diffuseMap varying vec2 diffuseMapUV; #endif @@ -66,12 +68,13 @@ varying vec3 passNormal; void main(void) { - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); - linearDepth = getLinearDepth(viewPos); + linearDepth = getLinearDepth(gl_Position.z, viewPos.z); #if (@envMap || !PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 221e0c27a9..b46b299e2b 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -8,6 +8,8 @@ #extension GL_EXT_gpu_shader4: require #endif +uniform mat4 projectionMatrix; + varying vec2 uv; varying float euclideanDepth; varying float linearDepth; @@ -29,12 +31,12 @@ varying vec3 passNormal; void main(void) { - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); - linearDepth = getLinearDepth(viewPos); + linearDepth = getLinearDepth(gl_Position.z, viewPos.z); #if (!PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 9513001847..8e506a57f8 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -1,5 +1,7 @@ #version 120 - + +uniform mat4 projectionMatrix; + varying vec3 screenCoordsPassthrough; varying vec4 position; varying float linearDepth; @@ -9,7 +11,7 @@ varying float linearDepth; void main(void) { - gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, @@ -22,7 +24,7 @@ void main(void) position = gl_Vertex; vec4 viewPos = gl_ModelViewMatrix * gl_Vertex; - linearDepth = getLinearDepth(viewPos); + linearDepth = getLinearDepth(gl_Position.z, viewPos.z); setupShadowCoords(viewPos, normalize((gl_NormalMatrix * gl_Normal).xyz)); } From 09e03fde2e689fd1dcff054a16a761128d36ffff Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 4 Aug 2021 17:49:57 -0700 Subject: [PATCH 1144/2859] refactor and fix wobbly shores --- CHANGELOG.md | 1 + apps/opencs/model/world/data.cpp | 1 + apps/opencs/view/render/cellwater.cpp | 2 +- apps/openmw/mwgui/bookpage.cpp | 5 +- apps/openmw/mwgui/savegamedialog.cpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 1 + apps/openmw/mwrender/bulletdebugdraw.cpp | 11 ++- apps/openmw/mwrender/globalmap.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 12 +-- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 1 - apps/openmw/mwrender/postprocessor.cpp | 34 ++++--- apps/openmw/mwrender/postprocessor.hpp | 18 ++-- apps/openmw/mwrender/renderingmanager.cpp | 54 +++++++---- apps/openmw/mwrender/renderingmanager.hpp | 8 +- apps/openmw/mwrender/ripplesimulation.cpp | 6 +- apps/openmw/mwrender/screenshotmanager.cpp | 16 ++-- apps/openmw/mwrender/sky.cpp | 6 +- apps/openmw/mwrender/util.cpp | 8 -- apps/openmw/mwrender/util.hpp | 2 - apps/openmw/mwrender/water.cpp | 8 +- components/nifosg/nifloader.cpp | 14 +-- components/nifosg/nifloader.hpp | 4 - components/resource/scenemanager.cpp | 10 -- components/resource/scenemanager.hpp | 4 - components/sceneutil/agentpath.cpp | 6 ++ components/sceneutil/mwshadowtechnique.cpp | 15 +-- components/sceneutil/mwshadowtechnique.hpp | 6 -- components/sceneutil/navmesh.cpp | 6 ++ components/sceneutil/optimizer.cpp | 3 +- components/sceneutil/optimizer.hpp | 13 +-- components/sceneutil/pathgridutil.cpp | 6 ++ components/sceneutil/recastmesh.cpp | 6 ++ components/sceneutil/shadow.cpp | 18 +--- components/sceneutil/shadow.hpp | 5 +- components/sceneutil/util.cpp | 106 ++++++++++++++++----- components/sceneutil/util.hpp | 27 ++---- components/sceneutil/waterutil.cpp | 4 +- components/sceneutil/waterutil.hpp | 2 +- components/terrain/chunkmanager.cpp | 4 +- components/terrain/material.cpp | 16 ++-- components/terrain/material.hpp | 6 +- files/settings-default.cfg | 2 +- files/shaders/debug_fragment.glsl | 4 +- files/shaders/water_fragment.glsl | 8 +- 45 files changed, 256 insertions(+), 239 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e136d707a..801d05d357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Feature #6017: Separate persistent and temporary cell references when saving Feature #6032: Reverse-z depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly + Feature #6199: Support FBO Rendering 0.47.0 ------ diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 192602290d..5e8c548368 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -84,6 +84,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["radialFog"] = "0"; defines["lightingModel"] = "0"; + defines["reverseZ"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); diff --git a/apps/opencs/view/render/cellwater.cpp b/apps/opencs/view/render/cellwater.cpp index 485eed00fe..f8857c3afc 100644 --- a/apps/opencs/view/render/cellwater.cpp +++ b/apps/opencs/view/render/cellwater.cpp @@ -161,7 +161,7 @@ namespace CSVRender } mWaterGeometry = SceneUtil::createWaterGeometry(size, segments, textureRepeats); - mWaterGeometry->setStateSet(SceneUtil::createSimpleWaterStateSet(Alpha, RenderBin, false)); + mWaterGeometry->setStateSet(SceneUtil::createSimpleWaterStateSet(Alpha, RenderBin)); // Add water texture std::string textureName = Fallback::Map::getString("Water_SurfaceTexture"); diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index e4ce2a9593..2f12b83572 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -8,12 +8,11 @@ #include "MyGUI_FactoryManager.h" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwrender/util.hpp" - namespace MWGui { struct TypesetBookImpl; @@ -1219,7 +1218,7 @@ public: RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); - float z = MWRender::getReverseZ() ? 1.f : -1.f; + float z = SceneUtil::getReverseZ() ? 1.f : -1.f; GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), static_cast(mCoord.top - mViewTop), z /*mNode->getNodeDepth()*/, vertices, renderXform); diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 987d27c9a6..086135d035 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -450,7 +450,7 @@ namespace MWGui osg::ref_ptr texture (new osg::Texture2D); texture->setImage(result.getImage()); - texture->setInternalFormat(GL_RGBA); + texture->setInternalFormat(GL_RGB); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e42d606966..6eeb2d3654 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -35,6 +35,7 @@ #include #include +#include #include diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index 9039ec53ce..b008ebc6d9 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -4,13 +4,13 @@ #include #include +#include #include #include #include #include #include -#include #include "bulletdebugdraw.hpp" #include "vismask.hpp" @@ -18,8 +18,6 @@ #include #include -#include - #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -39,6 +37,7 @@ void DebugDrawer::createGeometry() { mGeometry = new osg::Geometry; mGeometry->setNodeMask(Mask_Debug); + mVertices = new osg::Vec3Array; mColors = new osg::Vec4Array; @@ -50,8 +49,10 @@ void DebugDrawer::createGeometry() mGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); mGeometry->setDataVariance(osg::Object::DYNAMIC); mGeometry->addPrimitiveSet(mDrawArrays); - // make this friendly to recreateShaders() - mGeometry->setStateSet(new osg::StateSet); + + osg::ref_ptr material = new osg::Material; + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + mGeometry->getOrCreateStateSet()->setAttribute(material); mParentNode->addChild(mGeometry); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 2f160933fd..7d7c6e45df 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -325,7 +325,7 @@ namespace MWRender if (texture) { osg::ref_ptr geom = createTexturedQuad(srcLeft, srcTop, srcRight, srcBottom); - auto depth = SceneUtil::createDepth(getReverseZ()); + auto depth = SceneUtil::createDepth(); depth->setWriteMask(0); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setAttribute(depth); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 72f2828ace..02f59e0ed0 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -178,7 +178,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f { osg::ref_ptr camera (new osg::Camera); - if (getReverseZ()) + if (SceneUtil::getReverseZ()) camera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10)); else camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); @@ -201,12 +201,10 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f osg::ref_ptr stateset = new osg::StateSet; stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); - if (getReverseZ()) - { - camera->setClearDepth(0.0); - auto depth = SceneUtil::createDepth(true); - stateset->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } + if (SceneUtil::getReverseZ()) + stateset->setAttributeAndModes(SceneUtil::createDepth(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + SceneUtil::setCameraClearDepth(camera); stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(camera->getProjectionMatrix())), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 64dc9ecfe0..12ed88e55d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -373,7 +373,7 @@ class DepthClearCallback : public osgUtil::RenderBin::DrawCallback public: DepthClearCallback() { - mDepth = SceneUtil::createDepth(getReverseZ()); + mDepth = SceneUtil::createDepth(); mDepth->setWriteMask(true); } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 709c67bc2d..aae8f2e4f1 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -661,7 +661,6 @@ namespace MWRender if (mergeGroup->getNumChildren()) { SceneUtil::Optimizer optimizer; - optimizer.setReverseZ(mSceneManager->getReverseZ()); if (size > 1/8.f) { optimizer.setViewPoint(relativeViewPoint); diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index a412ca8cfa..eff7d46892 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -36,10 +36,10 @@ namespace class CullCallback : public osg::NodeCallback { public: - CullCallback(MWRender::PostProcessor* postProcessor) - : mPostProcessor(postProcessor) - , mLastFrameNumber(0) - {} + CullCallback() + : mLastFrameNumber(0) + { + } void operator()(osg::Node* node, osg::NodeVisitor* nv) override { @@ -49,14 +49,24 @@ namespace if (frame != mLastFrameNumber) { mLastFrameNumber = frame; - if (!mPostProcessor->getMsaaFbo()) + + MWRender::PostProcessor* postProcessor = dynamic_cast(nv->asCullVisitor()->getCurrentCamera()->getUserData()); + + if (!postProcessor) { - renderStage->setFrameBufferObject(mPostProcessor->getFbo()); + Log(Debug::Error) << "Failed retrieving user data for master camera: FBO setup failed"; + traverse(node, nv); + return; + } + + if (!postProcessor->getMsaaFbo()) + { + renderStage->setFrameBufferObject(postProcessor->getFbo()); } else { - renderStage->setMultisampleResolveFramebufferObject(mPostProcessor->getFbo()); - renderStage->setFrameBufferObject(mPostProcessor->getMsaaFbo()); + renderStage->setMultisampleResolveFramebufferObject(postProcessor->getFbo()); + renderStage->setFrameBufferObject(postProcessor->getMsaaFbo()); } } @@ -64,7 +74,6 @@ namespace } private: - MWRender::PostProcessor* mPostProcessor; unsigned int mLastFrameNumber; }; @@ -117,7 +126,7 @@ namespace MWRender else { // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. - // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no + // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no // benefits if no floating point depth formats are supported. mDepthFormat = GL_DEPTH_COMPONENT24; Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; @@ -136,12 +145,13 @@ namespace MWRender // We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate // RTT camera this would not be needed. - mViewer->getCamera()->addCullCallback(new CullCallback(this)); + mViewer->getCamera()->addCullCallback(new CullCallback); mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); mViewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, mDepthTex); mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); + mViewer->getCamera()->setUserData(this); } void PostProcessor::resize(int width, int height, bool init) @@ -157,8 +167,6 @@ namespace MWRender mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex)); mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(mDepthTex)); - mViewer->getCamera()->setUserData(mFbo); - // When MSAA is enabled we must first render to a render buffer, then // blit the result to the FBO which is either passed to the main frame // buffer for display or used as the entry point for a post process chain. diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 5be58f33ac..ad922c1cbc 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -1,16 +1,12 @@ #ifndef OPENMW_MWRENDER_POSTPROCESSOR_H #define OPENMW_MWRENDER_POSTPROCESSOR_H +#include +#include +#include +#include #include -namespace osg -{ - class Texture2D; - class Group; - class FrameBufferObject; - class Camera; -} - namespace osgViewer { class Viewer; @@ -20,7 +16,7 @@ namespace MWRender { class RenderingManager; - class PostProcessor + class PostProcessor : public osg::Referenced { public: PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode); @@ -33,6 +29,8 @@ namespace MWRender void resize(int width, int height, bool init=false); private: + void createTexturesAndCamera(int width, int height); + osgViewer::Viewer* mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mHUDCamera; @@ -45,8 +43,6 @@ namespace MWRender int mDepthFormat; - void createTexturesAndCamera(int width, int height); - RenderingManager& mRendering; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c64bcd3383..5ffc54f3f8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -79,6 +79,8 @@ namespace MWRender public: SharedUniformStateUpdater() : mLinearFac(0.f) + , mNear(0.f) + , mFar(0.f) { } @@ -86,6 +88,8 @@ namespace MWRender { stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); stateset->addUniform(new osg::Uniform("linearFac", 0.f)); + stateset->addUniform(new osg::Uniform("near", 0.f)); + stateset->addUniform(new osg::Uniform("far", 0.f)); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override @@ -97,6 +101,15 @@ namespace MWRender auto* uLinearFac = stateset->getUniform("linearFac"); if (uLinearFac) uLinearFac->set(mLinearFac); + + auto* uNear = stateset->getUniform("near"); + if (uNear) + uNear->set(mNear); + + auto* uFar = stateset->getUniform("far"); + if (uFar) + uFar->set(mFar); + } void setProjectionMatrix(const osg::Matrixf& projectionMatrix) @@ -109,9 +122,21 @@ namespace MWRender mLinearFac = linearFac; } + void setNear(float near) + { + mNear = near; + } + + void setFar(float far) + { + mFar = far; + } + private: osg::Matrixf mProjectionMatrix; float mLinearFac; + float mNear; + float mFar; }; class StateUpdater : public SceneUtil::StateSetUpdater @@ -240,8 +265,7 @@ namespace MWRender , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) { - auto ext = osg::GLExtensions::Get(0, false); - bool reverseZ = Settings::Manager::getBool("reverse z", "Camera") && ext && ext->isClipControlSupported; + bool reverseZ = SceneUtil::getReverseZ(); if (reverseZ) Log(Debug::Info) << "Using reverse-z depth buffer"; @@ -267,7 +291,6 @@ namespace MWRender resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); - resourceSystem->getSceneManager()->setReverseZ(reverseZ); // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this depends on support for various OpenGL extensions. osg::ref_ptr sceneRoot = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); @@ -294,7 +317,7 @@ namespace MWRender if (Settings::Manager::getBool("object shadows", "Shadows")) shadowCastingTraversalMask |= (Mask_Object|Mask_Static); - mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager(), reverseZ)); + mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); @@ -408,9 +431,12 @@ namespace MWRender mGroundcoverWorld->setActiveGrid(osg::Vec4i(0, 0, 0, 0)); } - mPostProcessor.reset(new PostProcessor(*this, viewer, mRootNode)); + mPostProcessor = new PostProcessor(*this, viewer, mRootNode); resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat()); + if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(mPostProcessor->getDepthFormat())) + Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it."; + // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -474,7 +500,6 @@ namespace MWRender mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); - NifOsg::Loader::setReverseZ(reverseZ); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); // TODO: Near clip should not need to be bounded like this, but too small values break OSG shadow calculations CPU-side. @@ -487,8 +512,6 @@ namespace MWRender mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it @@ -496,18 +519,15 @@ namespace MWRender // The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and breaks things. mRootNode->getOrCreateStateSet()->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); - mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); - mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); - if (reverseZ) { - auto depth = SceneUtil::createDepth(reverseZ); osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::ZERO_TO_ONE); - mViewer->getCamera()->setClearDepth(0.0); - mRootNode->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + mRootNode->getOrCreateStateSet()->setAttributeAndModes(SceneUtil::createDepth(), osg::StateAttribute::ON); mRootNode->getOrCreateStateSet()->setAttributeAndModes(clipcontrol, osg::StateAttribute::ON); } + SceneUtil::setCameraClearDepth(mViewer->getCamera()); + updateProjectionMatrix(); } @@ -1143,7 +1163,7 @@ namespace MWRender mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); - if (mResourceSystem->getSceneManager()->getReverseZ()) + if (SceneUtil::getReverseZ()) { mSharedUniformStateUpdater->setLinearFac(-mNearClip / (mViewDistance - mNearClip) - 1.f); mSharedUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); @@ -1151,8 +1171,8 @@ namespace MWRender else mSharedUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix()); - mUniformNear->set(mNearClip); - mUniformFar->set(mViewDistance); + mSharedUniformStateUpdater->setNear(mNearClip); + mSharedUniformStateUpdater->setFar(mViewDistance); // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index cbe25addf1..d6d7e74634 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -110,9 +110,6 @@ namespace MWRender SceneUtil::UnrefQueue* getUnrefQueue(); Terrain::World* getTerrain(); - osg::Uniform* mUniformNear; - osg::Uniform* mUniformFar; - void preloadCommonAssets(); double getReferenceTime() const; @@ -241,8 +238,9 @@ namespace MWRender void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr); bool pagingUnlockCache(); void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); - + void updateProjectionMatrix(); + private: void updateTextureFiltering(); void updateAmbient(); @@ -289,7 +287,7 @@ namespace MWRender std::unique_ptr mScreenshotManager; std::unique_ptr mEffectManager; std::unique_ptr mShadowManager; - std::unique_ptr mPostProcessor; + osg::ref_ptr mPostProcessor; osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index c7deac51de..fdfc3db63d 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -56,13 +56,13 @@ namespace stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); - auto depth = SceneUtil::createDepth(resourceSystem->getSceneManager()->getReverseZ()); + auto depth = SceneUtil::createDepth(); depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); osg::ref_ptr polygonOffset (new osg::PolygonOffset); - polygonOffset->setUnits(-1); - polygonOffset->setFactor(-1); + polygonOffset->setUnits(SceneUtil::getReverseZ() ? 1 : -1); + polygonOffset->setFactor(SceneUtil::getReverseZ() ? 1 : -1); stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 967e282f30..96a8a9450a 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -23,6 +23,7 @@ #include "util.hpp" #include "vismask.hpp" #include "water.hpp" +#include "postprocessor.hpp" namespace MWRender { @@ -92,14 +93,13 @@ namespace MWRender // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer when in use. // glReadPixel() cannot read from multisampled targets. + PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); - osg::FrameBufferObject* fbo = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); - - if (fbo) + if (postProcessor && postProcessor->getFbo() && postProcessor->getMsaaFbo()) { osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); if (ext) - ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo->getHandle(renderInfo.getContextID())); + ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, postProcessor->getFbo()->getHandle(renderInfo.getContextID())); } mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); @@ -297,12 +297,10 @@ namespace MWRender camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); - - if (getReverseZ()) - camera->setClearDepth(0.0); - camera->setViewport(0, 0, w, h); + SceneUtil::setCameraClearDepth(camera); + osg::ref_ptr texture (new osg::Texture2D); texture->setInternalFormat(GL_RGB); texture->setTextureSize(w,h); @@ -336,7 +334,7 @@ namespace MWRender float nearClip = Settings::Manager::getFloat("near clip", "Camera"); float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); // each cubemap side sees 90 degrees - if (getReverseZ()) + if (SceneUtil::getReverseZ()) rttCamera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspectiveInf(90.0, w/float(h), nearClip)); else rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index dd97f79eda..2b58730fe1 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -595,11 +595,11 @@ private: osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { - auto depth = SceneUtil::createDepth(getReverseZ()); + auto depth = SceneUtil::createDepth(); // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. - double far = getReverseZ() ? 0.0 : 1.0; + double far = SceneUtil::getReverseZ() ? 0.0 : 1.0; depth->setZNear(far); depth->setZFar(far); depth->setWriteMask(false); @@ -1210,7 +1210,7 @@ void SkyManager::create() mCloudMesh2->addUpdateCallback(mCloudUpdater2); mCloudMesh2->setNodeMask(0); - auto depth = SceneUtil::createDepth(mSceneManager->getReverseZ()); + auto depth = SceneUtil::createDepth(); depth->setWriteMask(false); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 5740f7df6c..e3fc48040f 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -4,13 +4,10 @@ #include #include -#include #include #include #include -#include "../mwbase/environment.hpp" - namespace MWRender { @@ -67,9 +64,4 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou node->setStateSet(stateset); } -bool getReverseZ() -{ - return MWBase::Environment::get().getResourceSystem()->getSceneManager()->getReverseZ(); -} - } diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index c005dd9b23..a89baa22b1 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -32,8 +32,6 @@ namespace MWRender // no traverse() } }; - - bool getReverseZ(); } #endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 1318921688..da26595c8c 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -273,7 +273,7 @@ public: void setDefaults(osg::Camera* camera) override { - camera->setClearDepth(1.0); + SceneUtil::setCameraClearDepth(camera); camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("RefractionCamera"); @@ -339,7 +339,7 @@ public: void setDefaults(osg::Camera* camera) override { - camera->setClearDepth(1.0); + SceneUtil::setCameraClearDepth(camera); camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("ReflectionCamera"); @@ -546,7 +546,7 @@ osg::Node* Water::getRefractionNode() void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { - osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water, mResourceSystem->getSceneManager()->getReverseZ()); + osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); node->setStateSet(stateset); node->setUpdateCallback(nullptr); @@ -616,7 +616,7 @@ public: { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); - osg::ref_ptr depth(new osg::Depth); + osg::ref_ptr depth = SceneUtil::createDepth(); depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 384c640388..83b9fb61c2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -226,18 +226,6 @@ namespace NifOsg return sIntersectionDisabledNodeMask; } - bool Loader::sReverseZ = false; - - void Loader::setReverseZ(bool reverseZ) - { - sReverseZ = reverseZ; - } - - bool Loader::getReverseZ() - { - return sReverseZ; - } - class LoaderImpl { public: @@ -1835,7 +1823,7 @@ namespace NifOsg // Depth test flag stateset->setMode(GL_DEPTH_TEST, zprop->flags&1 ? osg::StateAttribute::ON : osg::StateAttribute::OFF); - auto depth = SceneUtil::createDepth(Loader::getReverseZ()); + auto depth = SceneUtil::createDepth(); // Depth write flag depth->setWriteMask((zprop->flags>>1)&1); // Morrowind ignores depth test function diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index cee75de6d2..8ee6b41674 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -51,14 +51,10 @@ namespace NifOsg static void setIntersectionDisabledNodeMask(unsigned int mask); static unsigned int getIntersectionDisabledNodeMask(); - static void setReverseZ(bool reverseZ); - static bool getReverseZ(); - private: static unsigned int sHiddenNodeMask; static unsigned int sIntersectionDisabledNodeMask; static bool sShowMarkers; - static bool sReverseZ; }; } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 3c548ead23..ffcd4c3308 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -275,16 +275,6 @@ namespace Resource return mClampLighting; } - void SceneManager::setReverseZ(bool reverseZ) - { - mReverseZ = reverseZ; - } - - bool SceneManager::getReverseZ() const - { - return mReverseZ; - } - void SceneManager::setDepthFormat(GLenum format) { mDepthFormat = format; diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 780375f77e..9d798177a1 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -92,9 +92,6 @@ namespace Resource void setClampLighting(bool clamp); bool getClampLighting() const; - void setReverseZ(bool reverseZ); - bool getReverseZ() const; - void setDepthFormat(GLenum format); GLenum getDepthFormat() const; @@ -208,7 +205,6 @@ namespace Resource SceneUtil::LightingMethod mLightingMethod; SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; - bool mReverseZ; GLenum mDepthFormat; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/agentpath.cpp b/components/sceneutil/agentpath.cpp index abe332f758..5f9b574e7d 100644 --- a/components/sceneutil/agentpath.cpp +++ b/components/sceneutil/agentpath.cpp @@ -1,6 +1,8 @@ #include "agentpath.hpp" #include "detourdebugdraw.hpp" +#include + #include #include @@ -65,6 +67,10 @@ namespace SceneUtil debugDraw.depthMask(true); + osg::ref_ptr material = new osg::Material; + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + group->getOrCreateStateSet()->setAttribute(material); + return group; } } diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 0004045dc8..19222eda23 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -26,6 +26,9 @@ #include #include + +#include + #include "shadowsbin.hpp" namespace { @@ -891,16 +894,6 @@ void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() } } -void SceneUtil::MWShadowTechnique::enableReverseZ() -{ - _reverseZ = true; -} - -void SceneUtil::MWShadowTechnique::disableReverseZ() -{ - _reverseZ = false; -} - void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) { // This can't be part of the constructor as OSG mandates that there be a trivial constructor available @@ -1647,7 +1640,7 @@ void MWShadowTechnique::createShaders() _shadowCastingStateSet->addUniform(new osg::Uniform("alphaTestShadows", false)); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(true); - if (_reverseZ) + if (SceneUtil::getReverseZ()) { osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::NEGATIVE_ONE_TO_ONE); _shadowCastingStateSet->setAttribute(clipcontrol, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 7fde0d7dbb..de57bf1fdc 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -85,10 +85,6 @@ namespace SceneUtil { virtual void disableFrontFaceCulling(); - virtual void enableReverseZ(); - - virtual void disableReverseZ(); - virtual void setupCastingShader(Shader::ShaderManager &shaderManager); class ComputeLightSpaceBounds : public osg::NodeVisitor, public osg::CullStack @@ -270,8 +266,6 @@ namespace SceneUtil { float _shadowFadeStart = 0.0; - bool _reverseZ = false; - class DebugHUD final : public osg::Referenced { public: diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp index aeb1779bd6..b0f356f089 100644 --- a/components/sceneutil/navmesh.cpp +++ b/components/sceneutil/navmesh.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace SceneUtil { @@ -17,6 +18,11 @@ namespace SceneUtil navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); duDebugDrawNavMeshWithClosedList(&debugDraw, navMesh, navMeshQuery, DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); + + osg::ref_ptr material = new osg::Material; + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + group->getOrCreateStateSet()->setAttribute(material); + return group; } } diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 6bde81c12d..5fbeb681fc 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -109,7 +109,6 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) MergeGeometryVisitor mgv(this); mgv.setTargetMaximumNumberOfVertices(1000000); mgv.setMergeAlphaBlending(_mergeAlphaBlending); - mgv.setReverseZ(_reverseZ); mgv.setViewPoint(_viewPoint); node->accept(mgv); @@ -1563,7 +1562,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) } if (_alphaBlendingActive && _mergeAlphaBlending && !geom->getStateSet()) { - auto d = createDepth(_reverseZ); + auto d = createDepth(); d->setWriteMask(0); geom->getOrCreateStateSet()->setAttribute(d); } diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 9f9c788445..2d6293e231 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -65,7 +65,7 @@ class Optimizer public: - Optimizer() : _mergeAlphaBlending(false), _reverseZ(false) {} + Optimizer() : _mergeAlphaBlending(false) {} virtual ~Optimizer() {} enum OptimizationOptions @@ -119,7 +119,6 @@ class Optimizer }; void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } - void setReverseZ(bool reverseZ) { _reverseZ = reverseZ; } void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; } /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ @@ -258,7 +257,6 @@ class Optimizer osg::Vec3f _viewPoint; bool _mergeAlphaBlending; - bool _reverseZ; public: @@ -379,18 +377,12 @@ class Optimizer /// default to traversing all children. MergeGeometryVisitor(Optimizer* optimizer=0) : BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY), - _targetMaximumNumberOfVertices(10000), _alphaBlendingActive(false), _mergeAlphaBlending(false), _reverseZ(false) {} + _targetMaximumNumberOfVertices(10000), _alphaBlendingActive(false), _mergeAlphaBlending(false) {} void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } - - void setReverseZ(bool reverseZ) - { - _reverseZ = reverseZ; - } - void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; @@ -429,7 +421,6 @@ class Optimizer std::vector _stateSetStack; bool _alphaBlendingActive; bool _mergeAlphaBlending; - bool _reverseZ; osg::Vec3f _viewPoint; }; diff --git a/components/sceneutil/pathgridutil.cpp b/components/sceneutil/pathgridutil.cpp index f37a8ba597..9ebdef61b0 100644 --- a/components/sceneutil/pathgridutil.cpp +++ b/components/sceneutil/pathgridutil.cpp @@ -1,6 +1,7 @@ #include "pathgridutil.hpp" #include +#include #include @@ -174,6 +175,11 @@ namespace SceneUtil gridGeometry->addPrimitiveSet(lineIndices); gridGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } + + osg::ref_ptr material = new osg::Material; + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + gridGeometry->getOrCreateStateSet()->setAttribute(material); + return gridGeometry; } diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index 85f3e7177b..8d6d47c2b6 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -65,6 +66,11 @@ namespace SceneUtil const auto texScale = 1.0f / (settings.mCellSize * 10.0f); duDebugDrawTriMesh(&debugDraw, vertices.data(), static_cast(vertices.size() / 3), indices.data(), normals.data(), static_cast(indices.size() / 3), nullptr, texScale); + + osg::ref_ptr material = new osg::Material; + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + group->getOrCreateStateSet()->setAttribute(material); + return group; } } diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 7ca15e612d..3244dc672a 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -94,12 +94,10 @@ namespace SceneUtil } } - ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager, bool reverseZ) - : mReverseZ(reverseZ) - , mShadowedScene(new osgShadow::ShadowedScene) - , mShadowTechnique(new MWShadowTechnique) - , mOutdoorShadowCastingMask(outdoorShadowCastingMask) - , mIndoorShadowCastingMask(indoorShadowCastingMask) + ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), + mShadowTechnique(new MWShadowTechnique), + mOutdoorShadowCastingMask(outdoorShadowCastingMask), + mIndoorShadowCastingMask(indoorShadowCastingMask) { mShadowedScene->setShadowTechnique(mShadowTechnique); @@ -110,9 +108,6 @@ namespace SceneUtil mShadowSettings = mShadowedScene->getShadowSettings(); setupShadowSettings(); - if (mReverseZ) - mShadowTechnique->enableReverseZ(); - mShadowTechnique->setupCastingShader(shaderManager); enableOutdoorMode(); @@ -185,9 +180,4 @@ namespace SceneUtil mShadowTechnique->enableShadows(); mShadowSettings->setCastsShadowTraversalMask(mOutdoorShadowCastingMask); } - - bool ShadowManager::getReverseZ() const - { - return mReverseZ; - } } diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index 8092f77f93..c823ecf860 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -17,7 +17,7 @@ namespace SceneUtil static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); - ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager, bool reverseZ); + ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager); void setupShadowSettings(); @@ -26,11 +26,8 @@ namespace SceneUtil void enableIndoorMode(); void enableOutdoorMode(); - - bool getReverseZ() const; protected: bool mEnableShadows; - bool mReverseZ; osg::ref_ptr mShadowedScene; osg::ref_ptr mShadowSettings; diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 1f79469a19..02cd9295e7 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -4,16 +4,31 @@ #include #include +#include + #include #include #include #include #include -#include #include #include #include +#include + +namespace +{ + +bool isReverseZSupported() +{ + if (!Settings::Manager::mDefaultSettings.count({"Camera", "reverse z"})) + return false; + auto ext = osg::GLExtensions::Get(0, false); + return Settings::Manager::getBool("reverse z", "Camera") && ext && ext->isClipControlSupported; +} + +} namespace SceneUtil { @@ -151,32 +166,42 @@ void GlowUpdater::setDuration(float duration) mDuration = duration; } -AttachMultisampledDepthColorCallback::AttachMultisampledDepthColorCallback(const osg::ref_ptr& colorTex, const osg::ref_ptr& depthTex, int samples, int colorSamples) +// Allows camera to render to a color and floating point depth texture with a multisampled framebuffer. +// Must be set on a camera's cull callback. +class AttachMultisampledDepthColorCallback : public osg::NodeCallback { - int width = colorTex->getTextureWidth(); - int height = colorTex->getTextureHeight(); +public: + AttachMultisampledDepthColorCallback(osg::Texture2D* colorTex, osg::Texture2D* depthTex, int samples, int colorSamples) + { + int width = colorTex->getTextureWidth(); + int height = colorTex->getTextureHeight(); - osg::ref_ptr rbColor = new osg::RenderBuffer(width, height, colorTex->getInternalFormat(), samples, colorSamples); - osg::ref_ptr rbDepth = new osg::RenderBuffer(width, height, depthTex->getInternalFormat(), samples, colorSamples); + osg::ref_ptr rbColor = new osg::RenderBuffer(width, height, colorTex->getInternalFormat(), samples, colorSamples); + osg::ref_ptr rbDepth = new osg::RenderBuffer(width, height, depthTex->getInternalFormat(), samples, colorSamples); - mMsaaFbo = new osg::FrameBufferObject; - mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(rbColor)); - mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(rbDepth)); + mMsaaFbo = new osg::FrameBufferObject; + mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(rbColor)); + mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(rbDepth)); - mFbo = new osg::FrameBufferObject; - mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorTex)); - mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthTex)); -} + mFbo = new osg::FrameBufferObject; + mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorTex)); + mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthTex)); + } -void AttachMultisampledDepthColorCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) -{ - osgUtil::RenderStage* renderStage = nv->asCullVisitor()->getCurrentRenderStage(); + void operator()(osg::Node* node, osg::NodeVisitor* nv) override + { + osgUtil::RenderStage* renderStage = nv->asCullVisitor()->getCurrentRenderStage(); - renderStage->setMultisampleResolveFramebufferObject(mFbo); - renderStage->setFrameBufferObject(mMsaaFbo); + renderStage->setMultisampleResolveFramebufferObject(mFbo); + renderStage->setFrameBufferObject(mMsaaFbo); - traverse(node, nv); -} + traverse(node, nv); + } + +private: + osg::ref_ptr mFbo; + osg::ref_ptr mMsaaFbo; +}; void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere) { @@ -313,9 +338,37 @@ bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg:: return addMSAAIntermediateTarget; } -osg::ref_ptr createDepth(bool reverseZ) +void attachAlphaToCoverageFriendlyDepthColor(osg::Camera* camera, osg::Texture2D* colorTex, osg::Texture2D* depthTex, GLenum depthFormat) +{ + bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1; + + if (isFloatingPointDepthFormat(depthFormat) && addMSAAIntermediateTarget) + { + camera->attach(osg::Camera::COLOR_BUFFER0, colorTex); + camera->attach(osg::Camera::DEPTH_BUFFER, depthTex); + camera->addCullCallback(new AttachMultisampledDepthColorCallback(colorTex, depthTex, 2, 1)); + } + else + { + attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, colorTex); + camera->attach(osg::Camera::DEPTH_BUFFER, depthTex); + } +} + +bool getReverseZ() +{ + static bool reverseZ = isReverseZSupported(); + return reverseZ; +} + +void setCameraClearDepth(osg::Camera* camera) +{ + camera->setClearDepth(getReverseZ() ? 0.0 : 1.0); +} + +osg::ref_ptr createDepth() { - return new osg::Depth(reverseZ ? osg::Depth::GEQUAL : osg::Depth::LEQUAL); + return new osg::Depth(getReverseZ() ? osg::Depth::GEQUAL : osg::Depth::LEQUAL); } osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near) @@ -352,7 +405,14 @@ osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, doubl bool isFloatingPointDepthFormat(GLenum format) { - return format == GL_DEPTH_COMPONENT32F || format == GL_DEPTH_COMPONENT32F_NV; + constexpr std::array formats = { + GL_DEPTH_COMPONENT32F, + GL_DEPTH_COMPONENT32F_NV, + GL_DEPTH32F_STENCIL8, + GL_DEPTH32F_STENCIL8_NV, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); } } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index bd4df5441a..0bca32c325 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -8,7 +8,6 @@ #include #include #include -#include #include @@ -49,22 +48,6 @@ namespace SceneUtil bool mDone; }; - // Allows camera to render to a color and floating point depth texture with a multisampled framebuffer. - // Must be set on a camera's cull callback. - // When the depth texture isn't needed as a sampler, use osg::Camera::attach(osg::Camera::DEPTH_COMPONENT, GL_DEPTH_COMPONENT32F) instead. - // If multisampling is not being used on the color buffer attachment, use the osg::Camera::attach() method. - class AttachMultisampledDepthColorCallback : public osg::NodeCallback - { - public: - AttachMultisampledDepthColorCallback(const osg::ref_ptr& colorTex, const osg::ref_ptr& depthTex, int samples, int colorSamples); - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; - - private: - osg::ref_ptr mFbo; - osg::ref_ptr mMsaaFbo; - }; - // Transform a bounding sphere by a matrix // based off private code in osg::Transform // TODO: patch osg to make public @@ -83,9 +66,15 @@ namespace SceneUtil // Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false); + void attachAlphaToCoverageFriendlyDepthColor(osg::Camera* camera, osg::Texture2D* colorTex, osg::Texture2D* depthTex, GLenum depthFormat); + + bool getReverseZ(); + + void setCameraClearDepth(osg::Camera* camera); + // Returns a suitable depth state attribute dependent on whether a reverse-z // depth buffer is in use. - osg::ref_ptr createDepth(bool reverseZ); + osg::ref_ptr createDepth(); // Returns a perspective projection matrix for use with a reversed z-buffer // and an infinite far plane. This is derived by mapping the default z-range @@ -100,7 +89,7 @@ namespace SceneUtil osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far); // Returns true if the GL format is a floating point depth format - bool isFloatingPointDepthFormat(GLenum format); + bool isFloatingPointDepthFormat(GLenum format); } #endif diff --git a/components/sceneutil/waterutil.cpp b/components/sceneutil/waterutil.cpp index dccc534ecc..ac171005ae 100644 --- a/components/sceneutil/waterutil.cpp +++ b/components/sceneutil/waterutil.cpp @@ -64,7 +64,7 @@ namespace SceneUtil return waterGeom; } - osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin, bool reverseZ) + osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin) { osg::ref_ptr stateset (new osg::StateSet); @@ -78,7 +78,7 @@ namespace SceneUtil stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - auto depth = createDepth(reverseZ); + auto depth = createDepth(); depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); diff --git a/components/sceneutil/waterutil.hpp b/components/sceneutil/waterutil.hpp index 7f08dbcfa9..7b8c380109 100644 --- a/components/sceneutil/waterutil.hpp +++ b/components/sceneutil/waterutil.hpp @@ -13,7 +13,7 @@ namespace SceneUtil { osg::ref_ptr createWaterGeometry(float size, int segments, float textureRepeats); - osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin, bool reverseZ); + osg::ref_ptr createSimpleWaterStateSet(float alpha, int renderBin); } #endif diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 5354b9d8a2..a744471de5 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -163,7 +163,7 @@ std::vector > ChunkManager::createPasses(float chunk float blendmapScale = mStorage->getBlendmapScale(chunkSize); - return ::Terrain::createPasses(useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); + return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile) @@ -219,7 +219,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve layer.mDiffuseMap = compositeMap->mTexture; layer.mParallax = false; layer.mSpecular = false; - geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager, std::vector(1, layer), std::vector >(), 1.f, 1.f)); + geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); } else { diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index dc74b58560..2fbd0a7fc0 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -97,17 +96,17 @@ namespace class LequalDepth { public: - static const osg::ref_ptr& value(bool reverseZ) + static const osg::ref_ptr& value() { - static LequalDepth instance(reverseZ); + static LequalDepth instance; return instance.mValue; } private: osg::ref_ptr mValue; - LequalDepth(bool reverseZ) - : mValue(SceneUtil::createDepth(reverseZ)) + LequalDepth() + : mValue(SceneUtil::createDepth()) { } }; @@ -171,10 +170,9 @@ namespace namespace Terrain { - std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector &layers, + std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { - Shader::ShaderManager* shaderManager = &sceneManager->getShaderManager(); std::vector > passes; unsigned int blendmapIndex = 0; @@ -197,7 +195,7 @@ namespace Terrain else { stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); - stateset->setAttributeAndModes(LequalDepth::value(sceneManager->getReverseZ()), osg::StateAttribute::ON); + stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); } } @@ -240,7 +238,7 @@ namespace Terrain if (!vertexShader || !fragmentShader) { // Try again without shader. Error already logged by above - return createPasses(false, sceneManager, layers, blendmaps, blendmapScale, layerTileSize); + return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index d5ef40a29e..5f78af6a06 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -10,9 +10,9 @@ namespace osg class Texture2D; } -namespace Resource +namespace Shader { - class SceneManager; + class ShaderManager; } namespace Terrain @@ -26,7 +26,7 @@ namespace Terrain bool mSpecular; }; - std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, + std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4943ce969b..2c287a889c 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -67,7 +67,7 @@ head bobbing height = 3.0 # Maximum camera roll angle (degrees) head bobbing roll = 0.2 -# Reverse the depth range from [0,1] to [1,0]. +# Reverse the depth range, reduces z-fighting of distant objects and terrain reverse z = true [Cells] diff --git a/files/shaders/debug_fragment.glsl b/files/shaders/debug_fragment.glsl index 263d4e1f24..1b25510d66 100644 --- a/files/shaders/debug_fragment.glsl +++ b/files/shaders/debug_fragment.glsl @@ -1,8 +1,8 @@ #version 120 -varying vec4 passColor; +#include "vertexcolors.glsl" void main() { - gl_FragData[0] = passColor; + gl_FragData[0] = getDiffuseColor(); } diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index ed0ebdc913..26f83e052c 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -51,6 +51,8 @@ const float WIND_SPEED = 0.2f; const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); +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 + // ---------------- rain ripples related stuff --------------------- const float RAIN_RIPPLE_GAPS = 5.0; @@ -270,10 +272,10 @@ void main(void) 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 shoreOffset = verticalWaterDepth - (normal2.r + mix(0, normalShoreRippleRain.r, rainIntensity) + 0.15)*8; - float fuzzFactor = min(1.0, 1000.0/surfaceDepth) * mix(abs(vVec.z), 1, 0.2); + 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); shoreOffset *= fuzzFactor; - shoreOffset = clamp(shoreOffset, 0, 1); + 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); #else gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + vec3(rainRipple.w) * 0.7; From cdf5b315c3d8078beec13b77f4a8d5bf47b7d9c2 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Thu, 5 Aug 2021 16:52:40 -0700 Subject: [PATCH 1145/2859] fix mac runner undefined macro --- components/sceneutil/util.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 02cd9295e7..14bfd35cb4 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -17,6 +17,10 @@ #include #include +#ifndef GL_DEPTH32F_STENCIL8_NV +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#endif + namespace { From 76760cd993557330b9df7cf640b05b12c635fc5e Mon Sep 17 00:00:00 2001 From: jvoisin Date: Fri, 6 Aug 2021 14:50:09 +0200 Subject: [PATCH 1146/2859] Don't try to build openmw-cs in coverity We need more than 3h to build everything with coverity. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 90698edfa8..a727fce684 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ Coverity: script: - CI/before_script.linux.sh # Remove the specific targets and build everything once we can do it under 3h - - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-cs openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME From 29921bf9faaf7084014130d64acc37e6fb662463 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 7 Aug 2021 10:06:56 +0200 Subject: [PATCH 1147/2859] Don't stack cast packages --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aisequence.cpp | 5 +++-- apps/openmw/mwscript/miscextensions.cpp | 15 +++++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b490405a..8d8405fb41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Bug #6165: Paralyzed player character can pickup items when the inventory is open Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla + Bug #6197: Infinite Casting Loop Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index fada7761df..bf4cf28deb 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -361,11 +361,12 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo // insert new package in correct place depending on priority for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { - // We should keep current AiCast package, if we try to add a new one. + // We should override current AiCast package, if we try to add a new one. if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Cast && package.getTypeId() == MWMechanics::AiPackageTypeId::Cast) { - continue; + *it = package.clone(); + return; } if((*it)->getPriority() <= package.getPriority()) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 0b36d8bb48..b34a359dc4 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -25,6 +25,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" @@ -1233,8 +1234,11 @@ namespace MWScript if (ptr.getClass().isActor()) { - MWMechanics::AiCast castPackage(targetId, spellId, true); - ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr); + if (!MWBase::Environment::get().getMechanicsManager()->isCastingSpell(ptr)) + { + MWMechanics::AiCast castPackage(targetId, spellId, true); + ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr); + } return; } @@ -1276,8 +1280,11 @@ namespace MWScript if (ptr.getClass().isActor()) { - MWMechanics::AiCast castPackage(ptr.getCellRef().getRefId(), spellId, true); - ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr); + if (!MWBase::Environment::get().getMechanicsManager()->isCastingSpell(ptr)) + { + MWMechanics::AiCast castPackage(ptr.getCellRef().getRefId(), spellId, true); + ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr); + } return; } From c91ef34a70ecc4176b2bfa809c9feb03a8981260 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 4 Aug 2021 11:35:53 +0200 Subject: [PATCH 1148/2859] Avoid using a specific type for stored ref_ptr to extend lifetime --- components/detournavigator/recastmeshobject.cpp | 12 ++++++------ components/detournavigator/recastmeshobject.hpp | 13 ++++++------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 862460b34b..8b4bc2fd6f 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -23,35 +23,35 @@ namespace DetourNavigator return result; } - std::vector makeChildrenObjects(const osg::ref_ptr& instance, + std::vector makeChildrenObjects(const osg::ref_ptr& holder, const btCompoundShape& shape, const AreaType areaType) { std::vector result; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) { - const CollisionShape collisionShape {instance, *shape.getChildShape(i)}; + const CollisionShape collisionShape {holder, *shape.getChildShape(i)}; result.emplace_back(collisionShape, shape.getChildTransform(i), areaType); } return result; } - std::vector makeChildrenObjects(const osg::ref_ptr& instance, + std::vector makeChildrenObjects(const osg::ref_ptr& holder, const btCollisionShape& shape, const AreaType areaType) { if (shape.isCompound()) - return makeChildrenObjects(std::move(instance), static_cast(shape), areaType); + return makeChildrenObjects(holder, static_cast(shape), areaType); return std::vector(); } } RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType) - : mShapeInstance(shape.getShapeInstance()) + : mHolder(shape.getHolder()) , mShape(shape.getShape()) , mTransform(transform) , mAreaType(areaType) , mLocalScaling(mShape.get().getLocalScaling()) - , mChildren(makeChildrenObjects(mShapeInstance, mShape.get(), mAreaType)) + , mChildren(makeChildrenObjects(mHolder, mShape.get(), mAreaType)) { } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index 81199c5bad..445dfe50f5 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -3,11 +3,10 @@ #include "areatype.hpp" -#include - #include #include +#include #include #include @@ -20,16 +19,16 @@ namespace DetourNavigator class CollisionShape { public: - CollisionShape(osg::ref_ptr instance, const btCollisionShape& shape) - : mShapeInstance(std::move(instance)) + CollisionShape(osg::ref_ptr holder, const btCollisionShape& shape) + : mHolder(std::move(holder)) , mShape(shape) {} - const osg::ref_ptr& getShapeInstance() const { return mShapeInstance; } + const osg::ref_ptr& getHolder() const { return mHolder; } const btCollisionShape& getShape() const { return mShape; } private: - osg::ref_ptr mShapeInstance; + osg::ref_ptr mHolder; std::reference_wrapper mShape; }; @@ -56,7 +55,7 @@ namespace DetourNavigator } private: - osg::ref_ptr mShapeInstance; + osg::ref_ptr mHolder; std::reference_wrapper mShape; btTransform mTransform; AreaType mAreaType; From 41b02ff1aa9a4f07d0ef61d79a9c9521d4709df8 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 4 Aug 2021 18:35:25 +0200 Subject: [PATCH 1149/2859] Copy only required RecastMeshObject fields --- .../detournavigator/recastmeshmanager.cpp | 17 +++++++++++++---- components/detournavigator/recastmeshobject.hpp | 5 +++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 5bbbbe4dea..e4e5b76449 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -123,7 +123,13 @@ namespace DetourNavigator tileBounds.mMin /= mSettings.mRecastScaleFactor; tileBounds.mMax /= mSettings.mRecastScaleFactor; RecastMeshBuilder builder(tileBounds); - std::vector objects; + using Object = std::tuple< + osg::ref_ptr, + std::reference_wrapper, + btTransform, + AreaType + >; + std::vector objects; std::size_t revision; { const std::lock_guard lock(mMutex); @@ -133,11 +139,14 @@ namespace DetourNavigator std::visit(AddHeightfield {v.mCell, builder}, v.mShape); objects.reserve(mObjects.size()); for (const auto& [k, object] : mObjects) - objects.push_back(object.getImpl()); + { + const RecastMeshObject& impl = object.getImpl(); + objects.emplace_back(impl.getHolder(), impl.getShape(), impl.getTransform(), impl.getAreaType()); + } revision = mRevision; } - for (const auto& v : objects) - builder.addObject(v.getShape(), v.getTransform(), v.getAreaType()); + for (const auto& [holder, shape, transform, areaType] : objects) + builder.addObject(shape, transform, areaType); return std::move(builder).create(mGeneration, revision); } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index 445dfe50f5..0c50c2f346 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -39,6 +39,11 @@ namespace DetourNavigator bool update(const btTransform& transform, const AreaType areaType); + const osg::ref_ptr& getHolder() const + { + return mHolder; + } + const btCollisionShape& getShape() const { return mShape; From 9472728fa49a8e7820d3e7543205d1dc2dcfebb0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 20 Jul 2021 05:38:06 +0200 Subject: [PATCH 1150/2859] Do not generate data for immobile actors instead of early out from the solver --- apps/openmw/mwphysics/movementsolver.cpp | 11 ----------- apps/openmw/mwphysics/physicssystem.cpp | 2 ++ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fd0e090fcb..66530e37ca 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -120,13 +120,6 @@ namespace MWPhysics { auto* physicActor = actor.mActorRaw; const ESM::Position& refpos = actor.mRefpos; - // Early-out for totally static creatures - // (Not sure if gravity should still apply?) - { - const auto ptr = physicActor->getPtr(); - if (!ptr.getClass().isMobile(ptr)) - return; - } // Reset per-frame data physicActor->setWalkingOnWater(false); @@ -432,10 +425,6 @@ namespace MWPhysics void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { - const auto& ptr = actor.mActorRaw->getPtr(); - if (!ptr.getClass().isMobile(ptr)) - return; - auto* physicActor = actor.mActorRaw; if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl return; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 85fb463a95..1028c0062e 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -757,6 +757,8 @@ namespace MWPhysics const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [ptr, physicActor] : mActors) { + if (!ptr.getClass().isMobile(physicActor->getPtr())) + continue; float waterlevel = -std::numeric_limits::max(); const MWWorld::CellStore *cell = ptr.getCell(); if(cell->getCell()->hasWater()) From 1bfaf353bec713636ab927cc5b4c21cd60ada0d2 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 21 Jul 2021 19:04:36 +0200 Subject: [PATCH 1151/2859] Explicitely store all the potential states an Actor can have into the ActActorFrameData structure. It makes it easier to reason about the simulation (and hopefully simplify it). Remove atomics from Actor class as a side effect. Rename mFloatToSurface to mInert to make is explicit what it represent, not what it is used for Store the Actor rotation (1 Vec2) instead of the whole ESM::Position (2 Vec3) --- apps/openmw/mwphysics/actor.cpp | 10 +++---- apps/openmw/mwphysics/actor.hpp | 15 +++++----- apps/openmw/mwphysics/movementsolver.cpp | 37 ++++++++++++------------ apps/openmw/mwphysics/mtphysics.cpp | 12 +++++++- apps/openmw/mwphysics/physicssystem.cpp | 27 +++++++++++++---- apps/openmw/mwphysics/physicssystem.hpp | 7 +++-- 6 files changed, 67 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 67bfb4dffe..5416223264 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -82,7 +82,7 @@ Actor::~Actor() void Actor::enableCollisionMode(bool collision) { - mInternalCollisionMode.store(collision, std::memory_order_release); + mInternalCollisionMode = collision; } void Actor::enableCollisionBody(bool collision) @@ -250,22 +250,22 @@ void Actor::setInertialForce(const osg::Vec3f &force) void Actor::setOnGround(bool grounded) { - mOnGround.store(grounded, std::memory_order_release); + mOnGround = grounded; } void Actor::setOnSlope(bool slope) { - mOnSlope.store(slope, std::memory_order_release); + mOnSlope = slope; } bool Actor::isWalkingOnWater() const { - return mWalkingOnWater.load(std::memory_order_acquire); + return mWalkingOnWater; } void Actor::setWalkingOnWater(bool walkingOnWater) { - mWalkingOnWater.store(walkingOnWater, std::memory_order_release); + mWalkingOnWater = walkingOnWater; } void Actor::setCanWaterWalk(bool waterWalk) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 703d8a191b..eb81064937 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_MWPHYSICS_ACTOR_H #define OPENMW_MWPHYSICS_ACTOR_H -#include #include #include @@ -37,7 +36,7 @@ namespace MWPhysics bool getCollisionMode() const { - return mInternalCollisionMode.load(std::memory_order_acquire); + return mInternalCollisionMode; } btConvexShape* getConvexShape() const { return mConvexShape; } @@ -123,14 +122,14 @@ namespace MWPhysics bool getOnGround() const { - return mInternalCollisionMode.load(std::memory_order_acquire) && mOnGround.load(std::memory_order_acquire); + return mInternalCollisionMode && mOnGround; } void setOnSlope(bool slope); bool getOnSlope() const { - return mInternalCollisionMode.load(std::memory_order_acquire) && mOnSlope.load(std::memory_order_acquire); + return mInternalCollisionMode && mOnSlope; } btCollisionObject* getCollisionObject() const @@ -182,7 +181,7 @@ namespace MWPhysics osg::Vec3f getScaledMeshTranslation() const; bool mCanWaterWalk; - std::atomic mWalkingOnWater; + bool mWalkingOnWater; bool mRotationallyInvariant; @@ -211,9 +210,9 @@ namespace MWPhysics osg::Vec3f mLastStuckPosition; osg::Vec3f mForce; - std::atomic mOnGround; - std::atomic mOnSlope; - std::atomic mInternalCollisionMode; + bool mOnGround; + bool mOnSlope; + bool mInternalCollisionMode; bool mExternalCollisionMode; PhysicsTaskScheduler* mTaskScheduler; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 66530e37ca..76f7a8d5a2 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -119,15 +119,14 @@ namespace MWPhysics WorldFrameData& worldData) { auto* physicActor = actor.mActorRaw; - const ESM::Position& refpos = actor.mRefpos; // Reset per-frame data - physicActor->setWalkingOnWater(false); + actor.mWalkingOnWater = false; // Anything to collide with? - if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) + if(actor.mSkipCollisionDetection) { - actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * - osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)) + actor.mPosition += (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * + osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1)) ) * actor.mMovement * time; return; } @@ -151,22 +150,22 @@ namespace MWPhysics if (actor.mPosition.z() < swimlevel || actor.mFlying) { - velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; } else { - velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + velocity = (osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; - if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope()) - || (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope())) + if ((velocity.z() > 0.f && actor.mIsOnGround && !actor.mIsOnSlope) + || (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && actor.mIsOnSlope)) inertia = velocity; - else if (!physicActor->getOnGround() || physicActor->getOnSlope()) + else if (!actor.mIsOnGround || actor.mIsOnSlope) velocity = velocity + inertia; } // Dead and paralyzed actors underwater will float to the surface, // if the CharacterController tells us to do so - if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel) + if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; if (actor.mWantJump) @@ -190,7 +189,7 @@ namespace MWPhysics * The initial velocity was set earlier (see above). */ float remainingTime = time; - bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying; + bool seenGround = actor.mIsOnGround && !actor.mIsOnSlope && !actor.mFlying; int numTimesSlid = 0; osg::Vec3f lastSlideNormal(0,0,1); @@ -349,7 +348,7 @@ namespace MWPhysics if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; - auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0); + auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); tracer.doTrace(colobj, from, to, collisionWorld); if(tracer.mFraction < 1.0f) @@ -365,7 +364,7 @@ namespace MWPhysics actor.mStandingOn = ptrHolder->getPtr(); if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) - physicActor->setWalkingOnWater(true); + actor.mWalkingOnWater = true; if (!actor.mFlying && !isOnSlope) { if (tracer.mFraction*dropDistance > sGroundOffset) @@ -408,8 +407,8 @@ namespace MWPhysics } physicActor->setInertialForce(inertia); } - physicActor->setOnGround(isOnGround); - physicActor->setOnSlope(isOnSlope); + actor.mIsOnGround = isOnGround; + actor.mIsOnSlope = isOnSlope; actor.mPosition = newPosition; // remove what was added earlier in compensating for doTrace not taking interior transformation into account @@ -426,7 +425,7 @@ namespace MWPhysics void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { auto* physicActor = actor.mActorRaw; - if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl + if(actor.mSkipCollisionDetection) // noclipping/tcl return; auto* collisionObject = physicActor->getCollisionObject(); @@ -448,9 +447,9 @@ namespace MWPhysics const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); // use a 3d approximation of the movement vector to better judge player intent - auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + auto velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; // try to pop outside of the world before doing anything else if we're inside of it - if (!physicActor->getOnGround() || physicActor->getOnSlope()) + if (!actor.mIsOnGround || actor.mIsOnSlope) velocity += physicActor->getInertialForce(); // because of the internal collision box offset hack, and the fact that we're moving the collision box manually, diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 81b1dc34d6..eaa1948cba 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -54,7 +54,7 @@ namespace { const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; - const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mActorRaw->getOnGround()); + const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1) actorData.mNeedLand = true; @@ -256,7 +256,12 @@ namespace MWPhysics // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values if (mAdvanceSimulation) + { data.mActorRaw->setStandingOnPtr(data.mStandingOn); + data.mActorRaw->setOnGround(data.mIsOnGround); + data.mActorRaw->setOnSlope(data.mIsOnSlope); + data.mActorRaw->setWalkingOnWater(data.mWalkingOnWater); + } data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); } } @@ -555,7 +560,12 @@ namespace MWPhysics actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); updateMechanics(actorData); if (mAdvanceSimulation) + { actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); + actorData.mActorRaw->setOnGround(actorData.mIsOnGround); + actorData.mActorRaw->setOnSlope(actorData.mIsOnSlope); + actorData.mActorRaw->setWalkingOnWater(actorData.mWalkingOnWater); + } } refreshLOSCache(); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 1028c0062e..3130f90bd0 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -950,9 +950,24 @@ namespace MWPhysics ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool waterCollision, float slowFall, float waterlevel) - : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mSkipCollisionDetection(actor->skipCollisions()), - mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(actor->velocity()), mPosition(), mRefpos() + : mActor(actor) + , mActorRaw(actor.get()) + , mStandingOn(standingOn) + , mWasOnGround(actor->getOnGround()) + , mIsOnGround(actor->getOnGround()) + , mIsOnSlope(actor->getOnSlope()) + , mDidJump(false) + , mNeedLand(false) + , mWaterCollision(waterCollision) + , mWalkingOnWater(false) + , mSkipCollisionDetection(actor->skipCollisions() || !actor->getCollisionMode()) + , mWaterlevel(waterlevel) + , mSlowFall(slowFall) + , mOldHeight(0) + , mFallHeight(0) + , mMovement(actor->velocity()) + , mPosition() + , mRotation() { const MWBase::World *world = MWBase::Environment::get().getWorld(); const auto ptr = actor->getPtr(); @@ -961,8 +976,7 @@ namespace MWPhysics mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); - mFloatToSurface = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); - mWasOnGround = actor->getOnGround(); + mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); } void ActorFrameData::updatePosition(btCollisionWorld* world) @@ -975,7 +989,8 @@ namespace MWPhysics MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition, false); } mOldHeight = mPosition.z(); - mRefpos = mActorRaw->getPtr().getRefData().getPosition(); + const auto rotation = mActorRaw->getPtr().getRefData().getPosition().asRotationVec3(); + mRotation = osg::Vec2f(rotation.x(), rotation.z()); } WorldFrameData::WorldFrameData() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ee62ab6ace..59f4de10ea 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -86,11 +86,14 @@ namespace MWPhysics bool mFlying; bool mSwimming; bool mWasOnGround; + bool mIsOnGround; + bool mIsOnSlope; bool mWantJump; bool mDidJump; - bool mFloatToSurface; + bool mInert; bool mNeedLand; bool mWaterCollision; + bool mWalkingOnWater; bool mSkipCollisionDetection; float mWaterlevel; float mSlowFall; @@ -98,7 +101,7 @@ namespace MWPhysics float mFallHeight; osg::Vec3f mMovement; osg::Vec3f mPosition; - ESM::Position mRefpos; + osg::Vec2f mRotation; }; struct WorldFrameData From 51514e44cc137ee362c0e4f2166848fec444a163 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 21 Jul 2021 19:40:22 +0200 Subject: [PATCH 1152/2859] Handle jump as part of the simulation preparation (inside of PhysicsSystem) instead of inside the simulaiton. For mechanics, we don't care how the jump is handled, just that it will be. --- apps/openmw/mwphysics/movementsolver.cpp | 3 -- apps/openmw/mwphysics/mtphysics.cpp | 28 ------------- apps/openmw/mwphysics/physicssystem.cpp | 50 ++++++++++++++++++++---- apps/openmw/mwphysics/physicssystem.hpp | 2 - 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 76f7a8d5a2..4eb1417cdd 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -168,9 +168,6 @@ namespace MWPhysics if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; - if (actor.mWantJump) - actor.mDidJump = true; - // Now that we have the effective movement vector, apply wind forces to it if (worldData.mIsInStorm) { diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index eaa1948cba..68a69a5c6d 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -11,7 +11,6 @@ #include "../mwmechanics/movement.hpp" #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "actor.hpp" #include "contacttestwrapper.h" @@ -62,36 +61,9 @@ namespace actorData.mFallHeight += heightDiff; } - void handleJump(const MWWorld::Ptr &ptr) - { - const bool isPlayer = (ptr == MWMechanics::getPlayer()); - // Advance acrobatics and set flag for GetPCJumping - if (isPlayer) - { - ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); - MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); - } - - // Decrease fatigue - if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState()) - { - const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); - const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); - const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr)); - const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult; - MWMechanics::DynamicStat fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue(); - fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); - ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue); - } - ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; - } - void updateMechanics(MWPhysics::ActorFrameData& actorData) { auto ptr = actorData.mActorRaw->getPtr(); - if (actorData.mDidJump) - handleJump(ptr); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (actorData.mNeedLand) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 3130f90bd0..7757b3eb65 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -38,6 +38,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/player.hpp" #include "../mwrender/bulletdebugdraw.hpp" @@ -72,6 +73,36 @@ namespace tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); return (tracer.mFraction >= 1.0f); } + + void handleJump(const MWWorld::Ptr &ptr) + { + if (!ptr.getClass().isActor()) + return; + if (ptr.getClass().getMovementSettings(ptr).mPosition[2] == 0) + return; + const bool isPlayer = (ptr == MWMechanics::getPlayer()); + // Advance acrobatics and set flag for GetPCJumping + if (isPlayer) + { + ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); + MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); + } + + // Decrease fatigue + if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState()) + { + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); + const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); + const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr)); + const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult; + MWMechanics::DynamicStat fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue); + } + ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; + } + } namespace MWPhysics @@ -755,21 +786,22 @@ namespace MWPhysics std::vector actorsFrameData; actorsFrameData.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); - for (const auto& [ptr, physicActor] : mActors) + for (const auto& [actor, physicActor] : mActors) { - if (!ptr.getClass().isMobile(physicActor->getPtr())) + auto ptr = physicActor->getPtr(); + if (!actor.getClass().isMobile(ptr)) continue; float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = ptr.getCell(); + const MWWorld::CellStore *cell = actor.getCell(); if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); - const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(physicActor->getPtr()).getMagicEffects(); + const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(ptr).getMagicEffects(); bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))) + if (physicActor->getCollisionMode() || !world->isUnderwater(actor.getCell(), actor.getRefData().getPosition().asVec3())) waterCollision = true; } @@ -784,6 +816,10 @@ namespace MWPhysics standingOn = physicActor->getStandingOnPtr(); actorsFrameData.emplace_back(physicActor, standingOn, waterCollision, slowFall, waterlevel); + + // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. + if (willSimulate) + handleJump(ptr); } return actorsFrameData; } @@ -956,7 +992,6 @@ namespace MWPhysics , mWasOnGround(actor->getOnGround()) , mIsOnGround(actor->getOnGround()) , mIsOnSlope(actor->getOnSlope()) - , mDidJump(false) , mNeedLand(false) , mWaterCollision(waterCollision) , mWalkingOnWater(false) @@ -973,8 +1008,7 @@ namespace MWPhysics const auto ptr = actor->getPtr(); mFlying = world->isFlying(ptr); mSwimming = world->isSwimming(ptr); - mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; - auto& stats = ptr.getClass().getCreatureStats(ptr); + const auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 59f4de10ea..e5e1aa0c06 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -88,8 +88,6 @@ namespace MWPhysics bool mWasOnGround; bool mIsOnGround; bool mIsOnSlope; - bool mWantJump; - bool mDidJump; bool mInert; bool mNeedLand; bool mWaterCollision; From 6e51a9a512f360d9eed83b45d045145c6d17d53d Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 21 Jul 2021 23:19:17 +0200 Subject: [PATCH 1153/2859] Simplify a bit the solver --- apps/openmw/mwphysics/movementsolver.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 4eb1417cdd..fd72ea3220 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -148,7 +148,13 @@ namespace MWPhysics osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f velocity; - if (actor.mPosition.z() < swimlevel || actor.mFlying) + // Dead and paralyzed actors underwater will float to the surface, + // if the CharacterController tells us to do so + if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) + { + velocity = osg::Vec3f(0,0,1) * 25; + } + else if (actor.mPosition.z() < swimlevel || actor.mFlying) { velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; } @@ -163,11 +169,6 @@ namespace MWPhysics velocity = velocity + inertia; } - // Dead and paralyzed actors underwater will float to the surface, - // if the CharacterController tells us to do so - if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) - velocity = osg::Vec3f(0,0,1) * 25; - // Now that we have the effective movement vector, apply wind forces to it if (worldData.mIsInStorm) { @@ -186,7 +187,6 @@ namespace MWPhysics * The initial velocity was set earlier (see above). */ float remainingTime = time; - bool seenGround = actor.mIsOnGround && !actor.mIsOnSlope && !actor.mFlying; int numTimesSlid = 0; osg::Vec3f lastSlideNormal(0,0,1); @@ -196,9 +196,10 @@ namespace MWPhysics for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { osg::Vec3f nextpos = newPosition + velocity * remainingTime; + bool underwater = newPosition.z() < swimlevel; // If not able to fly, don't allow to swim up into the air - if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel) + if(!actor.mFlying && nextpos.z() > swimlevel && underwater) { const osg::Vec3f down(0,0,-1); velocity = reject(velocity, down); @@ -230,8 +231,7 @@ namespace MWPhysics break; } - if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel) - seenGround = true; + bool seenGround = !actor.mFlying && !underwater && ((actor.mIsOnGround && !actor.mIsOnSlope) || isWalkableSlope(tracer.mPlaneNormal)); // We hit something. Check if we can step up. float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); From b04c9584105781bb475c01aee5366421a544aa91 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 22 Jul 2021 00:08:44 +0200 Subject: [PATCH 1154/2859] Modify the way swimming is handled: - compute the swimming state instead of storing it, it changes as part of the simulation and was not updated, so it was wrong anyway. - store the swim level in ActorFrameData, it is constant per Actor so no need to compute it inside the simulation --- apps/openmw/mwphysics/movementsolver.cpp | 3 +-- apps/openmw/mwphysics/mtphysics.cpp | 9 +++++++-- apps/openmw/mwphysics/physicssystem.cpp | 3 ++- apps/openmw/mwphysics/physicssystem.hpp | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fd72ea3220..ccb9105147 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -140,8 +140,7 @@ namespace MWPhysics osg::Vec3f halfExtents = physicActor->getHalfExtents(); actor.mPosition.z() += halfExtents.z(); // vanilla-accurate - static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); - float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); + float swimlevel = actor.mSwimLevel + halfExtents.z(); ActorTracer tracer; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 68a69a5c6d..de390aee1f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -49,13 +49,18 @@ namespace bool mCanBeSharedLock; }; + bool isUnderWater(const MWPhysics::ActorFrameData& actorData) + { + return actorData.mPosition.z() < actorData.mSwimLevel; + } + void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed) { const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); - if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1) + if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1) actorData.mNeedLand = true; else if (heightDiff < 0) actorData.mFallHeight += heightDiff; @@ -67,7 +72,7 @@ namespace MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (actorData.mNeedLand) - stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming)); + stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData))); else if (actorData.mFallHeight < 0) stats.addToFallHeight(-actorData.mFallHeight); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 7757b3eb65..f67e732751 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1007,10 +1007,11 @@ namespace MWPhysics const MWBase::World *world = MWBase::Environment::get().getWorld(); const auto ptr = actor->getPtr(); mFlying = world->isFlying(ptr); - mSwimming = world->isSwimming(ptr); const auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); + static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); + mSwimLevel = mWaterlevel - (actor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); } void ActorFrameData::updatePosition(btCollisionWorld* world) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index e5e1aa0c06..e4c67ed188 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -84,7 +84,6 @@ namespace MWPhysics Actor* mActorRaw; MWWorld::Ptr mStandingOn; bool mFlying; - bool mSwimming; bool mWasOnGround; bool mIsOnGround; bool mIsOnSlope; @@ -94,6 +93,7 @@ namespace MWPhysics bool mWalkingOnWater; bool mSkipCollisionDetection; float mWaterlevel; + float mSwimLevel; float mSlowFall; float mOldHeight; float mFallHeight; From 9e911cc8b5a6d069ef6f98474debbbcf3efcc84a Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Jul 2021 19:07:01 +0200 Subject: [PATCH 1155/2859] Introduce helper function to write back updated values inside parent Actor class --- apps/openmw/mwphysics/mtphysics.cpp | 32 +++++++++++++---------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index de390aee1f..be94638301 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -83,6 +83,18 @@ namespace return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } + void updateActor(MWPhysics::ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) + { + actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, timeAccum, dt)); + if (simulationPerformed) + { + actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); + actorData.mActorRaw->setOnGround(actorData.mIsOnGround); + actorData.mActorRaw->setOnSlope(actorData.mIsOnSlope); + actorData.mActorRaw->setWalkingOnWater(actorData.mWalkingOnWater); + } + } + namespace Config { /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading @@ -230,16 +242,7 @@ namespace MWPhysics if (std::any_of(actorsData.begin(), actorsData.end(), actorActive)) { updateMechanics(data); - - // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values - if (mAdvanceSimulation) - { - data.mActorRaw->setStandingOnPtr(data.mStandingOn); - data.mActorRaw->setOnGround(data.mIsOnGround); - data.mActorRaw->setOnSlope(data.mIsOnSlope); - data.mActorRaw->setWalkingOnWater(data.mWalkingOnWater); - } - data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); + updateActor(data, mAdvanceSimulation, mTimeAccum, mPhysicsDt); } } if(mAdvanceSimulation) @@ -534,15 +537,8 @@ namespace MWPhysics for (auto& actorData : mActorsFrameData) { handleFall(actorData, mAdvanceSimulation); - actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); updateMechanics(actorData); - if (mAdvanceSimulation) - { - actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); - actorData.mActorRaw->setOnGround(actorData.mIsOnGround); - actorData.mActorRaw->setOnSlope(actorData.mIsOnSlope); - actorData.mActorRaw->setWalkingOnWater(actorData.mWalkingOnWater); - } + updateActor(actorData, mAdvanceSimulation, mTimeAccum, mPhysicsDt); } refreshLOSCache(); } From f68273c3c05d6f1d71d0334db2acf651ad252a71 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 22 Jul 2021 19:29:20 +0200 Subject: [PATCH 1156/2859] Remove Actor* from ActorFrameData --- apps/openmw/mwphysics/movementsolver.cpp | 85 +++++++++++------------- apps/openmw/mwphysics/mtphysics.cpp | 46 +++++++------ apps/openmw/mwphysics/physicssystem.cpp | 19 ++++-- apps/openmw/mwphysics/physicssystem.hpp | 9 ++- 4 files changed, 80 insertions(+), 79 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index ccb9105147..990000b257 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -118,8 +118,6 @@ namespace MWPhysics void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData) { - auto* physicActor = actor.mActorRaw; - // Reset per-frame data actor.mWalkingOnWater = false; // Anything to collide with? @@ -131,20 +129,16 @@ namespace MWPhysics return; } - const btCollisionObject *colobj = physicActor->getCollisionObject(); - // Adjust for collision mesh offset relative to actor's "location" // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own) // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() - osg::Vec3f halfExtents = physicActor->getHalfExtents(); - actor.mPosition.z() += halfExtents.z(); // vanilla-accurate + actor.mPosition.z() += actor.mHalfExtentsZ; // vanilla-accurate - float swimlevel = actor.mSwimLevel + halfExtents.z(); + float swimlevel = actor.mSwimLevel + actor.mHalfExtentsZ; ActorTracer tracer; - osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f velocity; // Dead and paralyzed actors underwater will float to the surface, @@ -162,10 +156,10 @@ namespace MWPhysics velocity = (osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; if ((velocity.z() > 0.f && actor.mIsOnGround && !actor.mIsOnSlope) - || (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && actor.mIsOnSlope)) - inertia = velocity; + || (velocity.z() > 0.f && velocity.z() + actor.mInertia.z() <= -velocity.z() && actor.mIsOnSlope)) + actor.mInertia = velocity; else if (!actor.mIsOnGround || actor.mIsOnSlope) - velocity = velocity + inertia; + velocity = velocity + actor.mInertia; } // Now that we have the effective movement vector, apply wind forces to it @@ -177,7 +171,7 @@ namespace MWPhysics velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); } - Stepper stepper(collisionWorld, colobj); + Stepper stepper(collisionWorld, actor.mCollisionObject); osg::Vec3f origVelocity = velocity; osg::Vec3f newPosition = actor.mPosition; /* @@ -209,7 +203,7 @@ namespace MWPhysics if((newPosition - nextpos).length2() > 0.0001) { // trace to where character would go if there were no obstructions - tracer.doTrace(colobj, newPosition, nextpos, collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld); // check for obstructions if(tracer.mFraction >= 1.0f) @@ -233,7 +227,7 @@ namespace MWPhysics bool seenGround = !actor.mFlying && !underwater && ((actor.mIsOnGround && !actor.mIsOnSlope) || isWalkableSlope(tracer.mPlaneNormal)); // We hit something. Check if we can step up. - float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); + float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ; osg::Vec3f oldPosition = newPosition; bool usedStepLogic = false; if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject)) @@ -244,9 +238,7 @@ namespace MWPhysics } if (usedStepLogic) { - // don't let pure water creatures move out of water after stepMove - const auto ptr = physicActor->getPtr(); - if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel) + if (actor.mIsAquatic && newPosition.z() + actor.mHalfExtentsZ > actor.mWaterlevel) newPosition = oldPosition; else if(!actor.mFlying && actor.mPosition.z() >= swimlevel) forceGroundTest = true; @@ -305,7 +297,7 @@ namespace MWPhysics // version of surface rejection for acute crevices/seams auto averageNormal = bestNormal + planeNormal; averageNormal.normalize(); - tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; usedSeamLogic = true; @@ -321,7 +313,7 @@ namespace MWPhysics // but this is along the collision normal if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f)) { - tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; } @@ -341,12 +333,12 @@ namespace MWPhysics bool isOnGround = false; bool isOnSlope = false; - if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel)) + if (forceGroundTest || (actor.mInertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); - tracer.doTrace(colobj, from, to, collisionWorld); + tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld); if(tracer.mFraction < 1.0f) { if (!isActor(tracer.mHitObject)) @@ -368,7 +360,7 @@ namespace MWPhysics else { newPosition.z() = tracer.mEndPos.z(); - tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld); newPosition = (newPosition+tracer.mEndPos)/2.0; } } @@ -383,7 +375,7 @@ namespace MWPhysics } } // forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection - if(physicActor->getStuckFrames() > 0) + if(actor.mStuckFrames > 0) { isOnGround = true; isOnSlope = false; @@ -391,24 +383,23 @@ namespace MWPhysics } if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) - physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f)); + actor.mInertia = osg::Vec3f(0.f, 0.f, 0.f); else { - inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; - if (inertia.z() < 0) - inertia.z() *= actor.mSlowFall; + actor.mInertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; + if (actor.mInertia.z() < 0) + actor.mInertia.z() *= actor.mSlowFall; if (actor.mSlowFall < 1.f) { - inertia.x() *= actor.mSlowFall; - inertia.y() *= actor.mSlowFall; + actor.mInertia.x() *= actor.mSlowFall; + actor.mInertia.y() *= actor.mSlowFall; } - physicActor->setInertialForce(inertia); } actor.mIsOnGround = isOnGround; actor.mIsOnSlope = isOnSlope; actor.mPosition = newPosition; // remove what was added earlier in compensating for doTrace not taking interior transformation into account - actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate + actor.mPosition.z() -= actor.mHalfExtentsZ; // vanilla-accurate } btVector3 addMarginToDelta(btVector3 delta) @@ -420,49 +411,47 @@ namespace MWPhysics void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { - auto* physicActor = actor.mActorRaw; if(actor.mSkipCollisionDetection) // noclipping/tcl return; - auto* collisionObject = physicActor->getCollisionObject(); auto tempPosition = actor.mPosition; - if(physicActor->getStuckFrames() >= 10) + if(actor.mStuckFrames >= 10) { - if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100) + if((actor.mLastStuckPosition - actor.mPosition).length2() < 100) return; else { - physicActor->setStuckFrames(0); - physicActor->setLastStuckPosition({0, 0, 0}); + actor.mStuckFrames = 0; + actor.mLastStuckPosition = {0, 0, 0}; } } // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() - const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); + const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, actor.mHalfExtentsZ); // use a 3d approximation of the movement vector to better judge player intent auto velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; // try to pop outside of the world before doing anything else if we're inside of it if (!actor.mIsOnGround || actor.mIsOnSlope) - velocity += physicActor->getInertialForce(); + velocity += actor.mInertia; // because of the internal collision box offset hack, and the fact that we're moving the collision box manually, // we need to replicate part of the collision box's transform process from scratch osg::Vec3f refPosition = tempPosition + verticalHalfExtent; osg::Vec3f goodPosition = refPosition; - const btTransform oldTransform = collisionObject->getWorldTransform(); + const btTransform oldTransform = actor.mCollisionObject->getWorldTransform(); btTransform newTransform = oldTransform; auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback { goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset)); newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); - collisionObject->setWorldTransform(newTransform); + actor.mCollisionObject->setWorldTransform(newTransform); - ContactCollectionCallback callback{collisionObject, velocity}; - ContactTestWrapper::contactTest(const_cast(collisionWorld), collisionObject, callback); + ContactCollectionCallback callback{actor.mCollisionObject, velocity}; + ContactTestWrapper::contactTest(const_cast(collisionWorld), actor.mCollisionObject, callback); return callback; }; @@ -470,8 +459,8 @@ namespace MWPhysics auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); if(contactCallback.mDistance < -sAllowedPenetration) { - physicActor->setStuckFrames(physicActor->getStuckFrames() + 1); - physicActor->setLastStuckPosition(actor.mPosition); + ++actor.mStuckFrames; + actor.mLastStuckPosition = actor.mPosition; // we are; try moving it out of the world auto positionDelta = contactCallback.mContactSum; // limit rejection delta to the largest known individual rejections @@ -506,11 +495,11 @@ namespace MWPhysics } else { - physicActor->setStuckFrames(0); - physicActor->setLastStuckPosition({0, 0, 0}); + actor.mStuckFrames = 0; + actor.mLastStuckPosition = {0, 0, 0}; } - collisionObject->setWorldTransform(oldTransform); + actor.mCollisionObject->setWorldTransform(oldTransform); actor.mPosition = tempPosition; } } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index be94638301..9fc7b08675 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -66,9 +66,9 @@ namespace actorData.mFallHeight += heightDiff; } - void updateMechanics(MWPhysics::ActorFrameData& actorData) + void updateMechanics(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData) { - auto ptr = actorData.mActorRaw->getPtr(); + auto ptr = actor.getPtr(); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (actorData.mNeedLand) @@ -77,21 +77,24 @@ namespace stats.addToFallHeight(-actorData.mFallHeight); } - osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) + osg::Vec3f interpolateMovements(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); - return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); + return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor); } - void updateActor(MWPhysics::ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) + void updateActor(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) { - actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, timeAccum, dt)); + actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); + actor.setLastStuckPosition(actorData.mLastStuckPosition); + actor.setStuckFrames(actorData.mStuckFrames); if (simulationPerformed) { - actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); - actorData.mActorRaw->setOnGround(actorData.mIsOnGround); - actorData.mActorRaw->setOnSlope(actorData.mIsOnSlope); - actorData.mActorRaw->setWalkingOnWater(actorData.mWalkingOnWater); + actor.setStandingOnPtr(actorData.mStandingOn); + actor.setOnGround(actorData.mIsOnGround); + actor.setOnSlope(actorData.mIsOnSlope); + actor.setWalkingOnWater(actorData.mWalkingOnWater); + actor.setInertialForce(actorData.mInertia); } } @@ -233,16 +236,10 @@ namespace MWPhysics { for (auto& data : mActorsFrameData) { - const auto actorActive = [&data](const auto& newFrameData) -> bool + if (auto actor = data.mActor.lock()) { - const auto actor = data.mActor.lock(); - return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr(); - }; - // Only return actors that are still part of the scene - if (std::any_of(actorsData.begin(), actorsData.end(), actorActive)) - { - updateMechanics(data); - updateActor(data, mAdvanceSimulation, mTimeAccum, mPhysicsDt); + updateMechanics(*actor, data); + updateActor(*actor, data, mAdvanceSimulation, mTimeAccum, mPhysicsDt); } } if(mAdvanceSimulation) @@ -255,7 +252,10 @@ namespace MWPhysics // init for (auto& data : actorsData) - data.updatePosition(mCollisionWorld); + { + assert(data.mActor.lock()); + data.updatePosition(*data.mActor.lock(), mCollisionWorld); + } mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; @@ -536,9 +536,11 @@ namespace MWPhysics for (auto& actorData : mActorsFrameData) { + auto actor = actorData.mActor.lock(); + assert(actor); handleFall(actorData, mAdvanceSimulation); - updateMechanics(actorData); - updateActor(actorData, mAdvanceSimulation, mTimeAccum, mPhysicsDt); + updateMechanics(*actor, actorData); + updateActor(*actor, actorData, mAdvanceSimulation, mTimeAccum, mPhysicsDt); } refreshLOSCache(); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f67e732751..11fbf74e4b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -987,7 +987,7 @@ namespace MWPhysics ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool waterCollision, float slowFall, float waterlevel) : mActor(actor) - , mActorRaw(actor.get()) + , mCollisionObject(actor->getCollisionObject()) , mStandingOn(standingOn) , mWasOnGround(actor->getOnGround()) , mIsOnGround(actor->getOnGround()) @@ -1000,6 +1000,7 @@ namespace MWPhysics , mSlowFall(slowFall) , mOldHeight(0) , mFallHeight(0) + , mHalfExtentsZ(actor->getHalfExtents().z()) , mMovement(actor->velocity()) , mPosition() , mRotation() @@ -1007,6 +1008,7 @@ namespace MWPhysics const MWBase::World *world = MWBase::Environment::get().getWorld(); const auto ptr = actor->getPtr(); mFlying = world->isFlying(ptr); + mIsAquatic = ptr.getClass().isPureWaterCreature(ptr); const auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); @@ -1014,18 +1016,21 @@ namespace MWPhysics mSwimLevel = mWaterlevel - (actor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); } - void ActorFrameData::updatePosition(btCollisionWorld* world) + void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world) { - mActorRaw->applyOffsetChange(); - mPosition = mActorRaw->getPosition(); - if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) + actor.applyOffsetChange(); + mPosition = actor.getPosition(); + if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(&actor, mWaterlevel, world)) { mPosition.z() = mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition, false); + MWBase::Environment::get().getWorld()->moveObject(actor.getPtr(), mPosition, false); } mOldHeight = mPosition.z(); - const auto rotation = mActorRaw->getPtr().getRefData().getPosition().asRotationVec3(); + const auto rotation = actor.getPtr().getRefData().getPosition().asRotationVec3(); mRotation = osg::Vec2f(rotation.x(), rotation.z()); + mInertia = actor.getInertialForce(); + mStuckFrames = actor.getStuckFrames(); + mLastStuckPosition = actor.getLastStuckPosition(); } WorldFrameData::WorldFrameData() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index e4c67ed188..52e72c69e8 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -79,9 +79,9 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); - void updatePosition(btCollisionWorld* world); + void updatePosition(Actor& actor, btCollisionWorld* world); std::weak_ptr mActor; - Actor* mActorRaw; + btCollisionObject* mCollisionObject; MWWorld::Ptr mStandingOn; bool mFlying; bool mWasOnGround; @@ -89,17 +89,22 @@ namespace MWPhysics bool mIsOnSlope; bool mInert; bool mNeedLand; + bool mIsAquatic; bool mWaterCollision; bool mWalkingOnWater; bool mSkipCollisionDetection; + unsigned int mStuckFrames; float mWaterlevel; float mSwimLevel; float mSlowFall; float mOldHeight; float mFallHeight; + float mHalfExtentsZ; osg::Vec3f mMovement; osg::Vec3f mPosition; osg::Vec2f mRotation; + osg::Vec3f mInertia; + osg::Vec3f mLastStuckPosition; }; struct WorldFrameData From 26d9052b8ca79210b9898dfcf773602fc387a347 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Jul 2021 20:39:26 +0200 Subject: [PATCH 1157/2859] Move the weak_ptr outside of ActorFrameData. --- apps/openmw/mwphysics/mtphysics.cpp | 50 +++++++++++++------------ apps/openmw/mwphysics/mtphysics.hpp | 3 +- apps/openmw/mwphysics/physicssystem.cpp | 38 ++++++++++--------- apps/openmw/mwphysics/physicssystem.hpp | 5 +-- 4 files changed, 51 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 9fc7b08675..8c2250c05c 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -222,7 +222,7 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. @@ -234,12 +234,12 @@ namespace MWPhysics // start by finishing previous background computation if (mNumThreads != 0) { - for (auto& data : mActorsFrameData) + for (size_t i = 0; i < mActors.size(); ++i) { - if (auto actor = data.mActor.lock()) + if (auto actor = mActors[i].lock()) { - updateMechanics(*actor, data); - updateActor(*actor, data, mAdvanceSimulation, mTimeAccum, mPhysicsDt); + updateMechanics(*actor, mActorsFrameData[i]); + updateActor(*actor, mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); } } if(mAdvanceSimulation) @@ -251,15 +251,16 @@ namespace MWPhysics timeAccum -= numSteps*newDelta; // init - for (auto& data : actorsData) + for (size_t i = 0; i < actors.size(); ++i) { - assert(data.mActor.lock()); - data.updatePosition(*data.mActor.lock(), mCollisionWorld); + assert(actors[i].lock()); + actorsData[i].updatePosition(*actors[i].lock(), mCollisionWorld); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; mPhysicsDt = newDelta; + mActors = std::move(actors); mActorsFrameData = std::move(actorsData); mAdvanceSimulation = (mRemainingSteps != 0); mNewFrame = true; @@ -463,7 +464,7 @@ namespace MWPhysics int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - if(const auto actor = mActorsFrameData[job].mActor.lock()) + if(const auto actor = mActors[job].lock()) { MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); @@ -476,10 +477,9 @@ namespace MWPhysics { while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - if(const auto actor = mActorsFrameData[job].mActor.lock()) + if(const auto actor = mActors[job].lock()) { - auto& actorData = mActorsFrameData[job]; - handleFall(actorData, mAdvanceSimulation); + handleFall(mActorsFrameData[job], mAdvanceSimulation); } } @@ -491,14 +491,14 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActorsPositions() { - for (auto& actorData : mActorsFrameData) + for (size_t i = 0; i < mActors.size(); ++i) { - if(const auto actor = actorData.mActor.lock()) + if(const auto actor = mActors[i].lock()) { - if (actor->setPosition(actorData.mPosition)) + if (actor->setPosition(mActorsFrameData[i].mPosition)) { std::scoped_lock lock(mCollisionWorldMutex); - actorData.mPosition = actor->getPosition(); // account for potential position change made by script + mActorsFrameData[i].mPosition = actor->getPosition(); // account for potential position change made by script actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } @@ -534,13 +534,13 @@ namespace MWPhysics updateActorsPositions(); } - for (auto& actorData : mActorsFrameData) + for (size_t i = 0; i < mActors.size(); ++i) { - auto actor = actorData.mActor.lock(); + auto actor = mActors[i].lock(); assert(actor); - handleFall(actorData, mAdvanceSimulation); - updateMechanics(*actor, actorData); - updateActor(*actor, actorData, mAdvanceSimulation, mTimeAccum, mPhysicsDt); + handleFall(mActorsFrameData[i], mAdvanceSimulation); + updateMechanics(*actor, mActorsFrameData[i]); + updateActor(*actor, mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); } refreshLOSCache(); } @@ -571,12 +571,14 @@ namespace MWPhysics updateAabbs(); if (!mRemainingSteps) return; - for (auto& data : mActorsFrameData) - if (const auto actor = data.mActor.lock()) + for (size_t i = 0; i < mActors.size(); ++i) + { + if (auto actor = mActors[i].lock()) { std::unique_lock lock(mCollisionWorldMutex); - MovementSolver::unstuck(data, mCollisionWorld); + MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld); } + } } void PhysicsTaskScheduler::afterPostStep() diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 21698e52bd..6c2116f18f 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -38,7 +38,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -71,6 +71,7 @@ namespace MWPhysics void afterPostSim(); std::unique_ptr mWorldFrameData; + std::vector> mActors; std::vector mActorsFrameData; float mDefaultPhysicsDt; float mPhysicsDt; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 11fbf74e4b..c7d19a3b5c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -781,10 +781,11 @@ namespace MWPhysics actor->setVelocity(osg::Vec3f()); } - std::vector PhysicsSystem::prepareFrameData(bool willSimulate) + std::pair>, std::vector> PhysicsSystem::prepareFrameData(bool willSimulate) { - std::vector actorsFrameData; - actorsFrameData.reserve(mActors.size()); + std::pair>, std::vector> framedata; + framedata.first.reserve(mActors.size()); + framedata.second.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [actor, physicActor] : mActors) { @@ -815,13 +816,14 @@ namespace MWPhysics if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); - actorsFrameData.emplace_back(physicActor, standingOn, waterCollision, slowFall, waterlevel); + framedata.first.emplace_back(physicActor); + framedata.second.emplace_back(*physicActor, standingOn, waterCollision, slowFall, waterlevel); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) handleJump(ptr); } - return actorsFrameData; + return framedata; } void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) @@ -846,8 +848,11 @@ namespace MWPhysics if (skipSimulation) mTaskScheduler->resetSimulation(mActors); else + { + auto [actors, framedata] = prepareFrameData(mTimeAccum >= mPhysicsDt); // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(actors), std::move(framedata), frameStart, frameNumber, stats); + } } void PhysicsSystem::moveActors() @@ -984,36 +989,35 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, + ActorFrameData::ActorFrameData(Actor& actor, const MWWorld::Ptr standingOn, bool waterCollision, float slowFall, float waterlevel) - : mActor(actor) - , mCollisionObject(actor->getCollisionObject()) + : mCollisionObject(actor.getCollisionObject()) , mStandingOn(standingOn) - , mWasOnGround(actor->getOnGround()) - , mIsOnGround(actor->getOnGround()) - , mIsOnSlope(actor->getOnSlope()) + , mWasOnGround(actor.getOnGround()) + , mIsOnGround(actor.getOnGround()) + , mIsOnSlope(actor.getOnSlope()) , mNeedLand(false) , mWaterCollision(waterCollision) , mWalkingOnWater(false) - , mSkipCollisionDetection(actor->skipCollisions() || !actor->getCollisionMode()) + , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) , mWaterlevel(waterlevel) , mSlowFall(slowFall) , mOldHeight(0) , mFallHeight(0) - , mHalfExtentsZ(actor->getHalfExtents().z()) - , mMovement(actor->velocity()) + , mHalfExtentsZ(actor.getHalfExtents().z()) + , mMovement(actor.velocity()) , mPosition() , mRotation() { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const auto ptr = actor->getPtr(); + const auto ptr = actor.getPtr(); mFlying = world->isFlying(ptr); mIsAquatic = ptr.getClass().isPureWaterCreature(ptr); const auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); - mSwimLevel = mWaterlevel - (actor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); + mSwimLevel = mWaterlevel - (actor.getRenderingHalfExtents().z() * 2 * fSwimHeightScale); } void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 52e72c69e8..9d0d661d22 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,9 +78,8 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); + ActorFrameData(Actor& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); void updatePosition(Actor& actor, btCollisionWorld* world); - std::weak_ptr mActor; btCollisionObject* mCollisionObject; MWWorld::Ptr mStandingOn; bool mFlying; @@ -260,7 +259,7 @@ namespace MWPhysics void updateWater(); - std::vector prepareFrameData(bool willSimulate); + std::pair>, std::vector> prepareFrameData(bool willSimulate); osg::ref_ptr mUnrefQueue; From 0c5cf6ec19cf9ee28895031bbee72af707841cc0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Jul 2021 22:37:55 +0200 Subject: [PATCH 1158/2859] Store the btCollisionObject* of the object we're standing on instead of MWWorld::Ptr: - they are equivalent - btCollisionObject* is readily available from the simulation, it saves a call to a mutex - btCollisionObject* is smaller --- apps/openmw/mwphysics/movementsolver.cpp | 8 ++--- apps/openmw/mwphysics/mtphysics.cpp | 46 ++++++++++++++++-------- apps/openmw/mwphysics/mtphysics.hpp | 4 +++ apps/openmw/mwphysics/physicssystem.cpp | 12 ++----- apps/openmw/mwphysics/physicssystem.hpp | 4 +-- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 990000b257..2401f78367 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -345,13 +345,9 @@ namespace MWPhysics { isOnGround = true; isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); + actor.mStandingOn = tracer.mHitObject; - const btCollisionObject* standingOn = tracer.mHitObject; - PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); - if (ptrHolder) - actor.mStandingOn = ptrHolder->getPtr(); - - if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) + if (actor.mStandingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) actor.mWalkingOnWater = true; if (!actor.mFlying && !isOnSlope) { diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 8c2250c05c..41cec6facc 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -83,21 +83,6 @@ namespace return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor); } - void updateActor(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) - { - actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); - actor.setLastStuckPosition(actorData.mLastStuckPosition); - actor.setStuckFrames(actorData.mStuckFrames); - if (simulationPerformed) - { - actor.setStandingOnPtr(actorData.mStandingOn); - actor.setOnGround(actorData.mIsOnGround); - actor.setOnSlope(actorData.mIsOnSlope); - actor.setWalkingOnWater(actorData.mWalkingOnWater); - actor.setInertialForce(actorData.mInertia); - } - } - namespace Config { /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading @@ -357,12 +342,14 @@ namespace MWPhysics void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) { + mCollisionObjects.insert(collisionObject); std::unique_lock lock(mCollisionWorldMutex); mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask); } void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject) { + mCollisionObjects.erase(collisionObject); std::unique_lock lock(mCollisionWorldMutex); mCollisionWorld->removeCollisionObject(collisionObject); } @@ -506,6 +493,27 @@ namespace MWPhysics } } + void PhysicsTaskScheduler::updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const + { + actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); + actor.setLastStuckPosition(actorData.mLastStuckPosition); + actor.setStuckFrames(actorData.mStuckFrames); + if (simulationPerformed) + { + MWWorld::Ptr standingOn; + auto* ptrHolder = static_cast(getUserPointer(actorData.mStandingOn)); + if (ptrHolder) + standingOn = ptrHolder->getPtr(); + actor.setStandingOnPtr(standingOn); + // the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the change + if (actor.getOnGround() == actorData.mWasOnGround) + actor.setOnGround(actorData.mIsOnGround); + actor.setOnSlope(actorData.mIsOnSlope); + actor.setWalkingOnWater(actorData.mWalkingOnWater); + actor.setInertialForce(actorData.mInertia); + } + } + bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) { btVector3 pos1 = Misc::Convert::toBullet(actor1->getCollisionObjectPosition() + osg::Vec3f(0,0,actor1->getHalfExtents().z() * 0.9)); // eye level @@ -566,6 +574,14 @@ namespace MWPhysics mDebugDrawer->step(); } + void* PhysicsTaskScheduler::getUserPointer(const btCollisionObject* object) const + { + auto it = mCollisionObjects.find(object); + if (it == mCollisionObjects.end()) + return nullptr; + return (*it)->getUserPointer(); + } + void PhysicsTaskScheduler::afterPreStep() { updateAabbs(); diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 6c2116f18f..e758c557e2 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -55,11 +56,13 @@ namespace MWPhysics void updateSingleAabb(std::weak_ptr ptr, bool immediate=false); bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); void debugDraw(); + void* getUserPointer(const btCollisionObject* object) const; private: void syncComputation(); void worker(); void updateActorsPositions(); + void updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const; bool hasLineOfSight(const Actor* actor1, const Actor* actor2); void refreshLOSCache(); void updateAabbs(); @@ -73,6 +76,7 @@ namespace MWPhysics std::unique_ptr mWorldFrameData; std::vector> mActors; std::vector mActorsFrameData; + std::unordered_set mCollisionObjects; float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index c7d19a3b5c..d65231322a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -811,13 +811,8 @@ namespace MWPhysics // Slow fall reduces fall speed by a factor of (effect magnitude / 200) const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); - // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. - MWWorld::Ptr standingOn; - if (!willSimulate) - standingOn = physicActor->getStandingOnPtr(); - framedata.first.emplace_back(physicActor); - framedata.second.emplace_back(*physicActor, standingOn, waterCollision, slowFall, waterlevel); + framedata.second.emplace_back(*physicActor, waterCollision, slowFall, waterlevel); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) @@ -989,10 +984,9 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(Actor& actor, const MWWorld::Ptr standingOn, - bool waterCollision, float slowFall, float waterlevel) + ActorFrameData::ActorFrameData(Actor& actor, bool waterCollision, float slowFall, float waterlevel) : mCollisionObject(actor.getCollisionObject()) - , mStandingOn(standingOn) + , mStandingOn(nullptr) , mWasOnGround(actor.getOnGround()) , mIsOnGround(actor.getOnGround()) , mIsOnSlope(actor.getOnSlope()) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 9d0d661d22..208e6cc71d 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,10 +78,10 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(Actor& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); + ActorFrameData(Actor& actor, bool moveToWaterSurface, float slowFall, float waterlevel); void updatePosition(Actor& actor, btCollisionWorld* world); btCollisionObject* mCollisionObject; - MWWorld::Ptr mStandingOn; + const btCollisionObject* mStandingOn; bool mFlying; bool mWasOnGround; bool mIsOnGround; From bcd6541d3e259e9998b9c43fec10b6bb21788c10 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Jul 2021 23:08:35 +0200 Subject: [PATCH 1159/2859] Reorganize ActorFrameData members: - constify all read-only variables - order them so that all variables modified as aprt of the simulation fits in one cache line --- apps/openmw/mwphysics/physicssystem.cpp | 42 ++++++++++++------------- apps/openmw/mwphysics/physicssystem.hpp | 38 +++++++++++----------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index d65231322a..f8862ea400 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -797,7 +797,8 @@ namespace MWPhysics if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); - const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(ptr).getMagicEffects(); + const auto& stats = ptr.getClass().getCreatureStats(ptr); + const MWMechanics::MagicEffects& effects = stats.getMagicEffects(); bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) @@ -810,9 +811,11 @@ namespace MWPhysics // Slow fall reduces fall speed by a factor of (effect magnitude / 200) const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); + const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); + const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); framedata.first.emplace_back(physicActor); - framedata.second.emplace_back(*physicActor, waterCollision, slowFall, waterlevel); + framedata.second.emplace_back(*physicActor, inert, waterCollision, slowFall, waterlevel); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) @@ -984,34 +987,29 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(Actor& actor, bool waterCollision, float slowFall, float waterlevel) - : mCollisionObject(actor.getCollisionObject()) + ActorFrameData::ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel) + : mPosition() , mStandingOn(nullptr) - , mWasOnGround(actor.getOnGround()) , mIsOnGround(actor.getOnGround()) , mIsOnSlope(actor.getOnSlope()) - , mNeedLand(false) - , mWaterCollision(waterCollision) , mWalkingOnWater(false) - , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) - , mWaterlevel(waterlevel) + , mInert(inert) + , mCollisionObject(actor.getCollisionObject()) + , mSwimLevel(waterlevel - (actor.getRenderingHalfExtents().z() * 2 * MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat())) , mSlowFall(slowFall) + , mRotation() + , mMovement(actor.velocity()) + , mWaterlevel(waterlevel) + , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) , mFallHeight(0) - , mHalfExtentsZ(actor.getHalfExtents().z()) - , mMovement(actor.velocity()) - , mPosition() - , mRotation() + , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) + , mWasOnGround(actor.getOnGround()) + , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) + , mWaterCollision(waterCollision) + , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) + , mNeedLand(false) { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const auto ptr = actor.getPtr(); - mFlying = world->isFlying(ptr); - mIsAquatic = ptr.getClass().isPureWaterCreature(ptr); - const auto& stats = ptr.getClass().getCreatureStats(ptr); - const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); - mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); - static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); - mSwimLevel = mWaterlevel - (actor.getRenderingHalfExtents().z() * 2 * fSwimHeightScale); } void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 208e6cc71d..f1b6bc147a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,32 +78,32 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(Actor& actor, bool moveToWaterSurface, float slowFall, float waterlevel); + ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel); void updatePosition(Actor& actor, btCollisionWorld* world); - btCollisionObject* mCollisionObject; + osg::Vec3f mPosition; + osg::Vec3f mInertia; const btCollisionObject* mStandingOn; - bool mFlying; - bool mWasOnGround; bool mIsOnGround; bool mIsOnSlope; - bool mInert; - bool mNeedLand; - bool mIsAquatic; - bool mWaterCollision; bool mWalkingOnWater; - bool mSkipCollisionDetection; - unsigned int mStuckFrames; - float mWaterlevel; - float mSwimLevel; - float mSlowFall; - float mOldHeight; - float mFallHeight; - float mHalfExtentsZ; - osg::Vec3f mMovement; - osg::Vec3f mPosition; + const bool mInert; + btCollisionObject* mCollisionObject; + const float mSwimLevel; + const float mSlowFall; osg::Vec2f mRotation; - osg::Vec3f mInertia; + osg::Vec3f mMovement; osg::Vec3f mLastStuckPosition; + const float mWaterlevel; + const float mHalfExtentsZ; + float mOldHeight; + float mFallHeight; + unsigned int mStuckFrames; + const bool mFlying; + const bool mWasOnGround; + const bool mIsAquatic; + const bool mWaterCollision; + const bool mSkipCollisionDetection; + bool mNeedLand; }; struct WorldFrameData From 7dd5e715d727f8ce38aaf2727d01b64388adbbd7 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 24 Jul 2021 16:44:21 +0200 Subject: [PATCH 1160/2859] Compute and store actor half extents. This is faster for 2 reasons: - half extents changes when actor scale is modified, which is very rare - we don't need to protect them by the actor mutex. --- apps/openmw/mwphysics/actor.cpp | 29 ++++++++++++++--------------- apps/openmw/mwphysics/actor.hpp | 3 ++- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 5416223264..c566c3a1ed 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -21,7 +21,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) - , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) + , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents) , mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) @@ -33,7 +33,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic // We can not create actor without collisions - he will fall through the ground. // In this case we should autogenerate collision box based on mesh shape // (NPCs have bodyparts and use a different approach) - if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f) + if (!ptr.getClass().isNpc() && mOriginalHalfExtents.length2() == 0.f) { if (shape->mCollisionShape) { @@ -43,19 +43,19 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic btVector3 max; shape->mCollisionShape->getAabb(transform, min, max); - mHalfExtents.x() = (max[0] - min[0])/2.f; - mHalfExtents.y() = (max[1] - min[1])/2.f; - mHalfExtents.z() = (max[2] - min[2])/2.f; + mOriginalHalfExtents.x() = (max[0] - min[0])/2.f; + mOriginalHalfExtents.y() = (max[1] - min[1])/2.f; + mOriginalHalfExtents.z() = (max[2] - min[2])/2.f; - mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z()); + mMeshTranslation = osg::Vec3f(0.f, 0.f, mOriginalHalfExtents.z()); } - if (mHalfExtents.length2() == 0.f) + if (mOriginalHalfExtents.length2() == 0.f) Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; } - mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); - mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2; + mShape.reset(new btBoxShape(Misc::Convert::toBullet(mOriginalHalfExtents))); + mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mOriginalHalfExtents.x() - mOriginalHalfExtents.y()) < 2.2; mConvexShape = static_cast(mShape.get()); @@ -220,27 +220,26 @@ void Actor::updateScale() mPtr.getClass().adjustScale(mPtr, scaleVec, false); mScale = scaleVec; + mHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec); scaleVec = osg::Vec3f(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, true); - mRenderingScale = scaleVec; + mRenderingHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec); } osg::Vec3f Actor::getHalfExtents() const { - std::scoped_lock lock(mPositionMutex); - return osg::componentMultiply(mHalfExtents, mScale); + return mHalfExtents; } osg::Vec3f Actor::getOriginalHalfExtents() const { - return mHalfExtents; + return mOriginalHalfExtents; } osg::Vec3f Actor::getRenderingHalfExtents() const { - std::scoped_lock lock(mPositionMutex); - return osg::componentMultiply(mHalfExtents, mRenderingScale); + return mRenderingHalfExtents; } void Actor::setInertialForce(const osg::Vec3f &force) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index eb81064937..e52a4caca7 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -191,11 +191,12 @@ namespace MWPhysics std::unique_ptr mCollisionObject; osg::Vec3f mMeshTranslation; + osg::Vec3f mOriginalHalfExtents; osg::Vec3f mHalfExtents; + osg::Vec3f mRenderingHalfExtents; osg::Quat mRotation; osg::Vec3f mScale; - osg::Vec3f mRenderingScale; osg::Vec3f mSimulationPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; From 5fc3b80406a16930f412d3266bdb9ca8152681a2 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 24 Jul 2021 21:12:02 +0200 Subject: [PATCH 1161/2859] Don't query actor halfextent in a tight loop, use the already existing variable. These repeated calls become costly with > 150 actors. --- apps/openmw/mwmechanics/actors.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3669fd9391..c5b46c3764 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1831,7 +1831,7 @@ namespace MWMechanics osg::Vec2f baseSpeed = origMovement * maxSpeed; osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3(); float baseRotZ = ptr.getRefData().getPosition().rot[2]; - osg::Vec3f halfExtents = world->getHalfExtents(ptr); + const osg::Vec3f halfExtents = world->getHalfExtents(ptr); float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding; float timeToCollision = maxTimeToCheck; @@ -1849,7 +1849,7 @@ namespace MWMechanics if (otherPtr == ptr || otherPtr == currentTarget) continue; - osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); + const osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos; osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ); float dist = deltaPos.length(); @@ -1867,7 +1867,7 @@ namespace MWMechanics float rotZ = otherPtr.getRefData().getPosition().rot[2]; osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed; - float collisionDist = minGap + world->getHalfExtents(ptr).x() + world->getHalfExtents(otherPtr).x(); + float collisionDist = minGap + halfExtents.x() + otherHalfExtents.x(); collisionDist = std::min(collisionDist, relPos.length()); // Find the earliest `t` when |relPos + relSpeed * t| == collisionDist. From fa1fb2a6b5c0d1050bf5779cbbc86aa0a802d782 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Aug 2021 19:29:08 +0200 Subject: [PATCH 1162/2859] Reset mIsReleased before starting threads To fix TSAN warning: WARNING: ThreadSanitizer: data race (pid=68597) Write of size 1 at 0x7b3800079234 by main thread: #0 SceneUtil::WorkQueue::start(unsigned long) /home/elsid/dev/openmw/components/sceneutil/workqueue.cpp:51 (openmw+0x10daa10) #1 SceneUtil::WorkQueue::WorkQueue(unsigned long) /home/elsid/dev/openmw/components/sceneutil/workqueue.cpp:39 (openmw+0x10dad97) #2 OMW::Engine::prepareEngine(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:700 (openmw+0xf7cb5a) #3 OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:949 (openmw+0xf82688) #4 runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:316 (openmw+0xf62611) #5 wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:205 (openmw+0x125df1c) #6 main /home/elsid/dev/openmw/apps/openmw/main.cpp:328 (openmw+0x596323) Previous read of size 1 at 0x7b3800079234 by thread T10 (mutexes: write M19275778865205896): #0 SceneUtil::WorkQueue::removeWorkItem() /home/elsid/dev/openmw/components/sceneutil/workqueue.cpp:86 (openmw+0x10d9e51) #1 SceneUtil::WorkThread::run() /home/elsid/dev/openmw/components/sceneutil/workqueue.cpp:127 (openmw+0x10da52a) #2 operator() /home/elsid/dev/openmw/components/sceneutil/workqueue.cpp:114 (openmw+0x10da664) #3 __invoke_impl > /usr/include/c++/11.1.0/bits/invoke.h:61 (openmw+0x10da664) #4 __invoke > /usr/include/c++/11.1.0/bits/invoke.h:96 (openmw+0x10da664) #5 _M_invoke<0> /usr/include/c++/11.1.0/bits/std_thread.h:253 (openmw+0x10da664) #6 operator() /usr/include/c++/11.1.0/bits/std_thread.h:260 (openmw+0x10da664) #7 _M_run /usr/include/c++/11.1.0/bits/std_thread.h:211 (openmw+0x10da664) #8 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:82 (libstdc++.so.6+0xd33c3) Location is heap block of size 216 at 0x7b3800079220 allocated by main thread: #0 operator new(unsigned long) /build/gcc/src/gcc/libsanitizer/tsan/tsan_new_delete.cpp:64 (libtsan.so.0+0x91824) #1 OMW::Engine::prepareEngine(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:700 (openmw+0xf7cb4c) #2 OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:949 (openmw+0xf82688) #3 runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:316 (openmw+0xf62611) #4 wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:205 (openmw+0x125df1c) #5 main /home/elsid/dev/openmw/apps/openmw/main.cpp:328 (openmw+0x596323) Mutex M19275778865205896 is already destroyed. Thread T10 (tid=68609, running) created by main thread at: #0 pthread_create /build/gcc/src/gcc/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x61c3a) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:663 (libstdc++.so.6+0xd36aa) #2 std::_MakeUniq::__single_object std::make_unique(SceneUtil::WorkQueue&) /usr/include/c++/11.1.0/bits/unique_ptr.h:962 (openmw+0x10da987) #3 SceneUtil::WorkQueue::start(unsigned long) /home/elsid/dev/openmw/components/sceneutil/workqueue.cpp:50 (openmw+0x10da987) #4 SceneUtil::WorkQueue::WorkQueue(unsigned long) /home/elsid/dev/openmw/components/sceneutil/workqueue.cpp:39 (openmw+0x10dad97) #5 OMW::Engine::prepareEngine(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:700 (openmw+0xf7cb5a) #6 OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:949 (openmw+0xf82688) #7 runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:316 (openmw+0xf62611) #8 wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:205 (openmw+0x125df1c) #9 main /home/elsid/dev/openmw/apps/openmw/main.cpp:328 (openmw+0x596323) --- components/sceneutil/workqueue.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index 0c68c61921..eb7a7b2cae 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -46,9 +46,12 @@ WorkQueue::~WorkQueue() void WorkQueue::start(std::size_t workerThreads) { + { + const std::lock_guard lock(mMutex); + mIsReleased = false; + } while (mThreads.size() < workerThreads) mThreads.emplace_back(std::make_unique(*this)); - mIsReleased = false; } void WorkQueue::stop() From 3cbe93358abd28b7acd5108836f00b1b019a4578 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Aug 2021 11:58:02 +0200 Subject: [PATCH 1163/2859] Move dtNavMeshParams initialization to where it's required --- components/detournavigator/makenavmesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 07e9c7da66..246f9d85b6 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -517,8 +517,6 @@ namespace DetourNavigator " playerTile=(" << playerTile << ")" << " changedTileDistance=" << getDistance(changedTile, playerTile); - const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams(); - if (!recastMesh) { Log(Debug::Debug) << "Ignore add tile: recastMesh is null"; @@ -542,6 +540,8 @@ namespace DetourNavigator return navMeshCacheItem->lock()->removeTile(changedTile); } + const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); + if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles))) { Log(Debug::Debug) << "Ignore add tile: too far from player"; From 1fc7cb8191116c551dbabc69bd0204af6a86fbc3 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 8 Aug 2021 03:36:35 +0300 Subject: [PATCH 1164/2859] Don't use FreezeOnCull for any particle system (#4744) --- CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 3 --- apps/openmw/mwrender/sky.cpp | 3 --- apps/openmw/mwworld/projectilemanager.cpp | 3 --- components/nifosg/nifloader.cpp | 2 -- components/sceneutil/visitor.cpp | 11 ----------- components/sceneutil/visitor.hpp | 14 -------------- 7 files changed, 1 insertion(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8405fb41..f735bd5a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI + Bug #4744: Invisible particles must still be processed Bug #4752: UpdateCellCommand doesn't undo properly Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 53673d8a22..07b1deeff3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1678,9 +1678,6 @@ namespace MWRender SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); - // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters - SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; - node->accept(disableFreezeOnCullVisitor); node->setNodeMask(Mask_Effect); params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2b58730fe1..05ce0f1560 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1731,9 +1731,6 @@ void SkyManager::setWeather(const WeatherResult& weather) mParticleEffect->accept(alphaFaderSetupVisitor); - SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; - mParticleEffect->accept(disableFreezeOnCullVisitor); - SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); mParticleEffect->accept(findPSVisitor); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 439f5a4605..04f4573369 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -238,9 +238,6 @@ namespace MWWorld state.mNode->addChild(projectileLightSource); projectileLightSource->setLight(projectileLight); } - - SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; - state.mNode->accept(disableFreezeOnCullVisitor); state.mNode->addCullCallback(new SceneUtil::LightListCallback); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 83b9fb61c2..17551e0b91 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1103,8 +1103,6 @@ namespace NifOsg partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(osg::Vec4f(1.f,1.f,1.f,1.f), osg::Vec4f(1.f,1.f,1.f,1.f))); partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); - partsys->setFreezeOnCull(true); - if (!partctrl->emitter.empty()) { osg::ref_ptr emitter = handleParticleEmitter(partctrl); diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 1f5a0ea4cf..f1f15f786f 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -46,17 +46,6 @@ namespace SceneUtil { } - void DisableFreezeOnCullVisitor::apply(osg::MatrixTransform &node) - { - traverse(node); - } - - void DisableFreezeOnCullVisitor::apply(osg::Drawable& drw) - { - if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) - partsys->setFreezeOnCull(false); - } - void NodeMapVisitor::apply(osg::MatrixTransform& trans) { // Take transformation for first found node in file diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index 5e041dc454..fcf3c1f944 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -45,20 +45,6 @@ namespace SceneUtil std::vector mFoundNodes; }; - // Disable freezeOnCull for all visited particlesystems - class DisableFreezeOnCullVisitor : public osg::NodeVisitor - { - public: - DisableFreezeOnCullVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { - } - - void apply(osg::MatrixTransform& node) override; - - void apply(osg::Drawable& drw) override; - }; - /// Maps names to nodes class NodeMapVisitor : public osg::NodeVisitor { From 86e6d3dac8bd542b04148521d81bde9d5d3a0695 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Aug 2021 12:03:21 +0200 Subject: [PATCH 1165/2859] Do not cache navmesh when only object transformation is changed This saves cache capacity when a scene contains objects contantly transforming by scripts and causing changes in navmesh. The probability to get cache hit for such states is almost zero because even a constant change in a single float value may give up to 2^32 different states. --- components/detournavigator/asyncnavmeshupdater.cpp | 11 ++++++++++- components/detournavigator/makenavmesh.cpp | 6 +++++- components/detournavigator/makenavmesh.hpp | 8 +++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 11de607456..a0c9cba613 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -18,6 +18,8 @@ namespace { using DetourNavigator::ChangeType; using DetourNavigator::TilePosition; + using DetourNavigator::UpdateType; + using DetourNavigator::ChangeType; int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs) { @@ -35,6 +37,13 @@ namespace result = std::min(result, getManhattanDistance(position, tile)); return result; } + + UpdateType getUpdateType(ChangeType changeType) noexcept + { + if (changeType == ChangeType::update) + return UpdateType::Temporary; + return UpdateType::Persistent; + } } namespace DetourNavigator @@ -282,7 +291,7 @@ namespace DetourNavigator const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, - offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache); + offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, getUpdateType(job.mChangeType)); if (recastMesh != nullptr) { diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 246f9d85b6..892f9c2dd2 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -506,7 +506,7 @@ namespace DetourNavigator UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, - const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache) + const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType) { Log(Debug::Debug) << std::fixed << std::setprecision(2) << "Update NavMesh with multiple tiles:" << @@ -562,6 +562,10 @@ namespace DetourNavigator return navMeshCacheItem->lock()->removeTile(changedTile); } + if (updateType == UpdateType::Temporary) + return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(), + makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings)); + cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, std::move(prepared)); if (!cachedNavMeshData) diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 3e07341106..5b4169374b 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -47,10 +47,16 @@ namespace DetourNavigator NavMeshPtr makeEmptyNavMesh(const Settings& settings); + enum class UpdateType + { + Persistent, + Temporary + }; + UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, - const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache); + const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType); } #endif From 81267e7be76556f935dfde91cb9536fb1fbd9573 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 7 Aug 2021 18:04:11 -0700 Subject: [PATCH 1166/2859] add missing centroid to debug vertex shaders --- files/shaders/debug_vertex.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/debug_vertex.glsl b/files/shaders/debug_vertex.glsl index f68c3d6459..a26d2573cf 100644 --- a/files/shaders/debug_vertex.glsl +++ b/files/shaders/debug_vertex.glsl @@ -2,7 +2,7 @@ uniform mat4 projectionMatrix; -varying vec4 passColor; +centroid varying vec4 passColor; void main() { From e88b94d0b0b19d491d566f390b97fe07b5e49df6 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 5 Aug 2021 06:26:05 +0200 Subject: [PATCH 1167/2859] Move btCollisionObject* into PtrHolder Remove unused function --- apps/openmw/mwphysics/actor.cpp | 2 +- apps/openmw/mwphysics/actor.hpp | 7 ------- apps/openmw/mwphysics/object.cpp | 10 ---------- apps/openmw/mwphysics/object.hpp | 3 --- apps/openmw/mwphysics/projectile.hpp | 6 ------ apps/openmw/mwphysics/ptrholder.hpp | 11 +++++++---- 6 files changed, 8 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index c566c3a1ed..e140141e32 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -21,7 +21,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) - , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents) + , mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents) , mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index e52a4caca7..0846401c1d 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -132,11 +132,6 @@ namespace MWPhysics return mInternalCollisionMode && mOnSlope; } - btCollisionObject* getCollisionObject() const - { - return mCollisionObject.get(); - } - /// Sets whether this actor should be able to collide with the water surface void setCanWaterWalk(bool waterWalk); @@ -188,8 +183,6 @@ namespace MWPhysics std::unique_ptr mShape; btConvexShape* mConvexShape; - std::unique_ptr mCollisionObject; - osg::Vec3f mMeshTranslation; osg::Vec3f mOriginalHalfExtents; osg::Vec3f mHalfExtents; diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 9575a97da2..a95672f8cf 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -83,16 +83,6 @@ namespace MWPhysics } } - btCollisionObject* Object::getCollisionObject() - { - return mCollisionObject.get(); - } - - const btCollisionObject* Object::getCollisionObject() const - { - return mCollisionObject.get(); - } - btTransform Object::getTransform() const { std::unique_lock lock(mPositionMutex); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index fe395dc89b..1484c1472c 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -33,8 +33,6 @@ namespace MWPhysics void setRotation(osg::Quat quat); void updatePosition(); void commitPositionChange(); - btCollisionObject* getCollisionObject(); - const btCollisionObject* getCollisionObject() const; btTransform getTransform() const; /// Return solid flag. Not used by the object itself, true by default. bool isSolid() const; @@ -45,7 +43,6 @@ namespace MWPhysics bool animateCollisionShapes(); private: - std::unique_ptr mCollisionObject; osg::ref_ptr mShapeInstance; std::map mRecIndexToNodePath; bool mSolid; diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index f18c455020..b5ccee4f63 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -42,11 +42,6 @@ namespace MWPhysics void setPosition(const osg::Vec3f& position); osg::Vec3f getPosition() const; - btCollisionObject* getCollisionObject() const - { - return mCollisionObject.get(); - } - bool isActive() const { return mActive.load(std::memory_order_acquire); @@ -76,7 +71,6 @@ namespace MWPhysics std::unique_ptr mShape; btConvexShape* mConvexShape; - std::unique_ptr mCollisionObject; bool mTransformUpdatePending; bool mCanCrossWaterSurface; bool mCrossedWaterSurface; diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 152a5d64fc..4d0238ecd9 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -2,6 +2,9 @@ #define OPENMW_MWPHYSICS_PTRHOLDER_H #include +#include + +#include #include "../mwworld/ptr.hpp" @@ -10,7 +13,7 @@ namespace MWPhysics class PtrHolder { public: - virtual ~PtrHolder() {} + virtual ~PtrHolder() = default; void updatePtr(const MWWorld::Ptr& updated) { @@ -24,14 +27,14 @@ namespace MWPhysics return mPtr; } - MWWorld::ConstPtr getPtr() const + btCollisionObject* getCollisionObject() const { - std::scoped_lock lock(mMutex); - return mPtr; + return mCollisionObject.get(); } protected: MWWorld::Ptr mPtr; + std::unique_ptr mCollisionObject; private: mutable std::mutex mMutex; From 07fa1803f77efbdf5861fefd32e6a88573c38b87 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 5 Aug 2021 10:55:19 +0200 Subject: [PATCH 1168/2859] Use btCollisionObject* instead of MWWorld::Ptr inside of Projectile collision handling and castRay() to avoid calling getPtr(). It is a step forward removing the mutex inside of PtrHolder. Do the same for DeepestNotMeContactTestResultCallback. It is used only for not-ranged combat for now, but do it anyway for parity with all other callback. This way, once the PtrHolder mutex is gone one will not have to worry about wether it is safe to use the callback in a specific context. To avoid use-after-free with projectile / projectile collision, defer deletion of projectile. Since instead of storing a copy of target Ptr we have a pointer to its collision object, we can't delete projectiles until after we finished iterating over the loops. --- apps/openmw/mwphysics/actorconvexcallback.cpp | 6 +- .../closestnotmerayresultcallback.cpp | 14 ++--- .../deepestnotmecontacttestresultcallback.cpp | 9 +-- apps/openmw/mwphysics/physicssystem.cpp | 14 +---- apps/openmw/mwphysics/projectile.cpp | 58 ++++++++++++------- apps/openmw/mwphysics/projectile.hpp | 19 +++--- .../mwphysics/projectileconvexcallback.cpp | 35 +++++------ .../mwphysics/projectileconvexcallback.hpp | 3 +- apps/openmw/mwworld/projectilemanager.cpp | 16 ++++- 9 files changed, 90 insertions(+), 84 deletions(-) diff --git a/apps/openmw/mwphysics/actorconvexcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp index ef52e07c21..672af05058 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -77,10 +77,8 @@ namespace MWPhysics auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) return btScalar(1); - auto* targetHolder = static_cast(mMe->getUserPointer()); - const MWWorld::Ptr target = targetHolder->getPtr(); - if (projectileHolder->isValidTarget(target)) - projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); + if (projectileHolder->isValidTarget(mMe)) + projectileHolder->hit(mMe, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); return btScalar(1); } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 32d97d6c75..3f6cb2b727 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -7,6 +7,7 @@ #include "../mwworld/class.hpp" +#include "collisiontype.hpp" #include "ptrholder.hpp" namespace MWPhysics @@ -19,17 +20,14 @@ namespace MWPhysics btScalar ClosestNotMeRayResultCallback::addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { - if (rayResult.m_collisionObject == mMe) + const auto* hitObject = rayResult.m_collisionObject; + if (hitObject == mMe) return 1.f; - if (!mTargets.empty()) + if (hitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) { - if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) - { - auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); - if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) - return 1.f; - } + if ((std::find(mTargets.begin(), mTargets.end(), hitObject) == mTargets.end())) + return 1.f; } return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp index 7744af14b5..3531cc8eb8 100644 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp +++ b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp @@ -6,6 +6,7 @@ #include "../mwworld/class.hpp" +#include "collisiontype.hpp" #include "ptrholder.hpp" namespace MWPhysics @@ -23,14 +24,10 @@ namespace MWPhysics const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; if (collisionObject != mMe) { - if (!mTargets.empty()) + if (collisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) - { - PtrHolder* holder = static_cast(collisionObject->getUserPointer()); - if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) - return 0.f; - } + return 0.f; } btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f8862ea400..45120d15b2 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -635,19 +635,7 @@ namespace MWPhysics if (btFrom == btTo) return; - const auto casterPtr = projectile->getCaster(); - const auto* caster = [this,&casterPtr]() -> const btCollisionObject* - { - const Actor* actor = getActor(casterPtr); - if (actor) - return actor->getCollisionObject(); - const Object* object = getObject(casterPtr); - if (object) - return object->getCollisionObject(); - return nullptr; - }(); - - ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile); + ProjectileConvexCallback resultCallback(projectile->getCasterCollisionObject(), projectile->getCollisionObject(), btFrom, btTo, projectile); resultCallback.m_collisionFilterMask = 0xff; resultCallback.m_collisionFilterGroup = CollisionType_Projectile; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 1a94de01ee..a8bb444956 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -7,8 +7,10 @@ #include "../mwworld/class.hpp" +#include "actor.hpp" #include "collisiontype.hpp" #include "mtphysics.hpp" +#include "object.hpp" #include "projectile.hpp" namespace MWPhysics @@ -17,7 +19,7 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f : mCanCrossWaterSurface(canCrossWaterSurface) , mCrossedWaterSurface(false) , mActive(true) - , mCaster(caster) + , mHitTarget(nullptr) , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) @@ -32,6 +34,7 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f mCollisionObject->setUserPointer(this); setPosition(position); + setCaster(caster); const int collisionMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; @@ -77,54 +80,67 @@ bool Projectile::canTraverseWater() const return mCanCrossWaterSurface; } -void Projectile::hit(const MWWorld::Ptr& target, btVector3 pos, btVector3 normal) +void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal) { - if (!mActive.load(std::memory_order_acquire)) + bool active = true; + if (!mActive.compare_exchange_strong(active, false, std::memory_order_relaxed) || !active) return; - std::scoped_lock lock(mMutex); mHitTarget = target; mHitPosition = pos; mHitNormal = normal; - mActive.store(false, std::memory_order_release); +} + +MWWorld::Ptr Projectile::getTarget() const +{ + assert(!mActive); + auto* target = static_cast(mHitTarget->getUserPointer()); + return target ? target->getPtr() : MWWorld::Ptr(); } MWWorld::Ptr Projectile::getCaster() const { - std::scoped_lock lock(mMutex); return mCaster; } void Projectile::setCaster(const MWWorld::Ptr& caster) { - std::scoped_lock lock(mMutex); mCaster = caster; + mCasterColObj = [this,&caster]() -> const btCollisionObject* + { + const Actor* actor = mPhysics->getActor(caster); + if (actor) + return actor->getCollisionObject(); + const Object* object = mPhysics->getObject(caster); + if (object) + return object->getCollisionObject(); + return nullptr; + }(); } void Projectile::setValidTargets(const std::vector& targets) { std::scoped_lock lock(mMutex); - mValidTargets = targets; + mValidTargets.clear(); + for (const auto& ptr : targets) + { + const auto* physicActor = mPhysics->getActor(ptr); + if (physicActor) + mValidTargets.push_back(physicActor->getCollisionObject()); + } } -bool Projectile::isValidTarget(const MWWorld::Ptr& target) const +bool Projectile::isValidTarget(const btCollisionObject* target) const { + assert(target); std::scoped_lock lock(mMutex); - if (mCaster == target) + if (mCasterColObj == target) return false; - if (target.isEmpty() || mValidTargets.empty()) + if (mValidTargets.empty()) return true; - bool validTarget = false; - for (const auto& targetActor : mValidTargets) - { - if (targetActor == target) - { - validTarget = true; - break; - } - } - return validTarget; + return std::any_of(mValidTargets.begin(), mValidTargets.end(), + [target](const btCollisionObject* actor) { return target == actor; }); } std::optional Projectile::getWaterHitPosition() diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index b5ccee4f63..dd659b6581 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -47,21 +47,21 @@ namespace MWPhysics return mActive.load(std::memory_order_acquire); } - MWWorld::Ptr getTarget() const - { - assert(!mActive); - return mHitTarget; - } + MWWorld::Ptr getTarget() const; MWWorld::Ptr getCaster() const; void setCaster(const MWWorld::Ptr& caster); + const btCollisionObject* getCasterCollisionObject() const + { + return mCasterColObj; + } bool canTraverseWater() const; - void hit(const MWWorld::Ptr& target, btVector3 pos, btVector3 normal); + void hit(const btCollisionObject* target, btVector3 pos, btVector3 normal); void setValidTargets(const std::vector& targets); - bool isValidTarget(const MWWorld::Ptr& target) const; + bool isValidTarget(const btCollisionObject* target) const; std::optional getWaterHitPosition(); void setWaterHitPosition(btVector3 pos); @@ -76,13 +76,14 @@ namespace MWPhysics bool mCrossedWaterSurface; std::atomic mActive; MWWorld::Ptr mCaster; - MWWorld::Ptr mHitTarget; + const btCollisionObject* mCasterColObj; + const btCollisionObject* mHitTarget; std::optional mWaterHitPosition; osg::Vec3f mPosition; btVector3 mHitPosition; btVector3 mHitNormal; - std::vector mValidTargets; + std::vector mValidTargets; mutable std::mutex mMutex; diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index b803c4400b..687253e1cc 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -1,3 +1,5 @@ +#include + #include "../mwworld/class.hpp" #include "actor.hpp" @@ -8,41 +10,41 @@ namespace MWPhysics { - ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj) + ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj) : btCollisionWorld::ClosestConvexResultCallback(from, to) - , mMe(me), mProjectile(proj) + , mCaster(caster) + , mMe(me) + , mProjectile(proj) { assert(mProjectile); } btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) { + const auto* hitObject = result.m_hitCollisionObject; // don't hit the caster - if (result.m_hitCollisionObject == mMe) + if (hitObject == mCaster) return 1.f; // don't hit the projectile - if (result.m_hitCollisionObject == mProjectile->getCollisionObject()) + if (hitObject == mMe) return 1.f; btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); - switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup) + switch (hitObject->getBroadphaseHandle()->m_collisionFilterGroup) { case CollisionType_Actor: { - auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getPtr())) + if (!mProjectile->isValidTarget(hitObject)) return 1.f; - mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal); break; } case CollisionType_Projectile: { - auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getCaster())) + auto* target = static_cast(hitObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getCasterCollisionObject())) return 1.f; - target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + target->hit(mMe, m_hitPointWorld, m_hitNormalWorld); break; } case CollisionType_Water: @@ -50,17 +52,10 @@ namespace MWPhysics mProjectile->setWaterHitPosition(m_hitPointWorld); if (mProjectile->canTraverseWater()) return 1.f; - mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - default: - { - auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); - auto ptr = target ? target->getPtr() : MWWorld::Ptr(); - mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld); break; } } + mProjectile->hit(hitObject, m_hitPointWorld, m_hitNormalWorld); return result.m_hitFraction; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp index 96c84b1fe2..f35cfbd3c8 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.hpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -12,11 +12,12 @@ namespace MWPhysics class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj); + ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj); btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; private: + const btCollisionObject* mCaster; const btCollisionObject* mMe; Projectile* mProjectile; }; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 04f4573369..76c6ca7548 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -521,7 +521,7 @@ namespace MWWorld } MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); - cleanupProjectile(projectileState); + projectileState.mToDelete = true; } for (auto& magicBoltState : mMagicBolts) { @@ -550,7 +550,19 @@ namespace MWWorld cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); - cleanupMagicBolt(magicBoltState); + magicBoltState.mToDelete = true; + } + + for (auto& projectileState : mProjectiles) + { + if (projectileState.mToDelete) + cleanupProjectile(projectileState); + } + + for (auto& magicBoltState : mMagicBolts) + { + if (magicBoltState.mToDelete) + cleanupMagicBolt(magicBoltState); } mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), mProjectiles.end()); From ee09f3095f84bccb2c6135102dfb34570c653efd Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 5 Aug 2021 14:21:56 +0200 Subject: [PATCH 1169/2859] At last kill PtrHolder mutex --- apps/openmw/mwphysics/ptrholder.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 4d0238ecd9..e84f3d1cfe 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -17,13 +17,11 @@ namespace MWPhysics void updatePtr(const MWWorld::Ptr& updated) { - std::scoped_lock lock(mMutex); mPtr = updated; } MWWorld::Ptr getPtr() { - std::scoped_lock lock(mMutex); return mPtr; } @@ -35,9 +33,6 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - - private: - mutable std::mutex mMutex; }; } From b4dd9e6b4d20ad949c1832cc950adb552aab8e2c Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 8 Aug 2021 17:19:18 +0200 Subject: [PATCH 1170/2859] Avoid division by zero in movementsolver when an actor is immobile and in a storm --- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 2401f78367..df9239dd83 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -163,7 +163,7 @@ namespace MWPhysics } // Now that we have the effective movement vector, apply wind forces to it - if (worldData.mIsInStorm) + if (worldData.mIsInStorm && velocity.length() > 0) { osg::Vec3f stormDirection = worldData.mStormDirection; float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length()))); From 8b4c2d205dc39aa7e02c36000675e99fe51862d6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 23 Mar 2021 22:40:19 +0000 Subject: [PATCH 1171/2859] WIP-ish glPolygonOffset for Bullet debug geometry --- apps/openmw/mwrender/bulletdebugdraw.cpp | 126 ++++++++++++++++------- apps/openmw/mwrender/bulletdebugdraw.hpp | 13 ++- 2 files changed, 99 insertions(+), 40 deletions(-) diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index b008ebc6d9..a6081936d2 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -8,7 +8,9 @@ #include #include +#include #include +#include #include #include @@ -33,50 +35,68 @@ DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld * void DebugDrawer::createGeometry() { - if (!mGeometry) + if (!mLinesGeometry) { - mGeometry = new osg::Geometry; - mGeometry->setNodeMask(Mask_Debug); + mLinesGeometry = new osg::Geometry; + mTrisGeometry = new osg::Geometry; + mLinesGeometry->setNodeMask(Mask_Debug); + mTrisGeometry->setNodeMask(Mask_Debug); - mVertices = new osg::Vec3Array; - mColors = new osg::Vec4Array; + mLinesVertices = new osg::Vec3Array; + mTrisVertices = new osg::Vec3Array; + mLinesColors = new osg::Vec4Array; - mDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); + mLinesDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); + mTrisDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES); - mGeometry->setUseDisplayList(false); - mGeometry->setVertexArray(mVertices); - mGeometry->setColorArray(mColors); - mGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); - mGeometry->setDataVariance(osg::Object::DYNAMIC); - mGeometry->addPrimitiveSet(mDrawArrays); + mLinesGeometry->setUseDisplayList(false); + mLinesGeometry->setVertexArray(mLinesVertices); + mLinesGeometry->setColorArray(mLinesColors); + mLinesGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); + mLinesGeometry->setDataVariance(osg::Object::DYNAMIC); + mLinesGeometry->addPrimitiveSet(mLinesDrawArrays); - osg::ref_ptr material = new osg::Material; - material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - mGeometry->getOrCreateStateSet()->setAttribute(material); + mTrisGeometry->setUseDisplayList(false); + mTrisGeometry->setVertexArray(mTrisVertices); + mTrisGeometry->setDataVariance(osg::Object::DYNAMIC); + mTrisGeometry->addPrimitiveSet(mTrisDrawArrays); - mParentNode->addChild(mGeometry); + mParentNode->addChild(mLinesGeometry); + mParentNode->addChild(mTrisGeometry); auto* stateSet = new osg::StateSet; stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); + stateSet->setAttributeAndModes(new osg::PolygonOffset(SceneUtil::getReverseZ() ? 1.0 : -1.0, SceneUtil::getReverseZ() ? 1.0 : -1.0)); + osg::ref_ptr material = new osg::Material; + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + stateSet->setAttribute(material); + mTrisGeometry->setStateSet(stateSet); mShapesRoot = new osg::Group; mShapesRoot->setStateSet(stateSet); mShapesRoot->setDataVariance(osg::Object::DYNAMIC); mShapesRoot->setNodeMask(Mask_Debug); mParentNode->addChild(mShapesRoot); - MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mGeometry, "debug"); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mLinesGeometry, "debug"); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mTrisGeometry, "debug"); + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mShapesRoot, "debug"); } } void DebugDrawer::destroyGeometry() { - if (mGeometry) + if (mLinesGeometry) { - mParentNode->removeChild(mGeometry); + mParentNode->removeChild(mLinesGeometry); + mParentNode->removeChild(mTrisGeometry); mParentNode->removeChild(mShapesRoot); - mGeometry = nullptr; - mVertices = nullptr; - mDrawArrays = nullptr; + mLinesGeometry = nullptr; + mLinesVertices = nullptr; + mLinesColors = nullptr; + mLinesDrawArrays = nullptr; + mTrisGeometry = nullptr; + mTrisVertices = nullptr; + mTrisDrawArrays = nullptr; } } @@ -89,24 +109,58 @@ void DebugDrawer::step() { if (mDebugOn) { - mVertices->clear(); - mColors->clear(); + mLinesVertices->clear(); + mTrisVertices->clear(); + mLinesColors->clear(); mShapesRoot->removeChildren(0, mShapesRoot->getNumChildren()); mWorld->debugDrawWorld(); showCollisions(); - mDrawArrays->setCount(mVertices->size()); - mVertices->dirty(); - mColors->dirty(); - mGeometry->dirtyBound(); + mLinesDrawArrays->setCount(mLinesVertices->size()); + mTrisDrawArrays->setCount(mTrisVertices->size()); + mLinesVertices->dirty(); + mTrisVertices->dirty(); + mLinesColors->dirty(); + mLinesGeometry->dirtyBound(); + mTrisGeometry->dirtyBound(); } } void DebugDrawer::drawLine(const btVector3 &from, const btVector3 &to, const btVector3 &color) { - mVertices->push_back(Misc::Convert::toOsg(from)); - mVertices->push_back(Misc::Convert::toOsg(to)); - mColors->push_back({1,1,1,1}); - mColors->push_back({1,1,1,1}); + mLinesVertices->push_back(Misc::Convert::toOsg(from)); + mLinesVertices->push_back(Misc::Convert::toOsg(to)); + mLinesColors->push_back({1,1,1,1}); + mLinesColors->push_back({1,1,1,1}); + + size_t size = mLinesVertices->size(); + if (size >= 6 + && (*mLinesVertices)[size - 1] == (*mLinesVertices)[size - 6] + && (*mLinesVertices)[size - 2] == (*mLinesVertices)[size - 3] + && (*mLinesVertices)[size - 4] == (*mLinesVertices)[size - 5]) + { + mTrisVertices->push_back(mLinesVertices->back()); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mTrisVertices->push_back(mLinesVertices->back()); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mTrisVertices->push_back(mLinesVertices->back()); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + mLinesVertices->pop_back(); + mLinesColors->pop_back(); + } +} + +void DebugDrawer::drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar) +{ + mTrisVertices->push_back(Misc::Convert::toOsg(v0)); + mTrisVertices->push_back(Misc::Convert::toOsg(v1)); + mTrisVertices->push_back(Misc::Convert::toOsg(v2)); } void DebugDrawer::addCollision(const btVector3& orig, const btVector3& normal) @@ -121,10 +175,10 @@ void DebugDrawer::showCollisions() { if (now - created < std::chrono::seconds(2)) { - mVertices->push_back(Misc::Convert::toOsg(from)); - mVertices->push_back(Misc::Convert::toOsg(to)); - mColors->push_back({1,0,0,1}); - mColors->push_back({1,0,0,1}); + mLinesVertices->push_back(Misc::Convert::toOsg(from)); + mLinesVertices->push_back(Misc::Convert::toOsg(to)); + mLinesColors->push_back({1,0,0,1}); + mLinesColors->push_back({1,0,0,1}); } } mCollisionViews.erase(std::remove_if(mCollisionViews.begin(), mCollisionViews.end(), diff --git a/apps/openmw/mwrender/bulletdebugdraw.hpp b/apps/openmw/mwrender/bulletdebugdraw.hpp index b24640427d..cea5794ba7 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.hpp +++ b/apps/openmw/mwrender/bulletdebugdraw.hpp @@ -37,10 +37,13 @@ private: protected: osg::ref_ptr mParentNode; btCollisionWorld *mWorld; - osg::ref_ptr mGeometry; - osg::ref_ptr mVertices; - osg::ref_ptr mColors; - osg::ref_ptr mDrawArrays; + osg::ref_ptr mLinesGeometry; + osg::ref_ptr mTrisGeometry; + osg::ref_ptr mLinesVertices; + osg::ref_ptr mTrisVertices; + osg::ref_ptr mLinesColors; + osg::ref_ptr mLinesDrawArrays; + osg::ref_ptr mTrisDrawArrays; bool mDebugOn; @@ -56,6 +59,8 @@ public: void drawLine(const btVector3& from,const btVector3& to,const btVector3& color) override; + void drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar) override; + void addCollision(const btVector3& orig, const btVector3& normal); void showCollisions(); From cf15803e67aeb53f2aa96bfc7c59862221c92c00 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 15 May 2021 21:26:42 +0100 Subject: [PATCH 1172/2859] Disable triangle detection workaround when Bullet actually uses triangles --- apps/openmw/mwrender/bulletdebugdraw.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index a6081936d2..bf56e9a330 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -132,6 +132,7 @@ void DebugDrawer::drawLine(const btVector3 &from, const btVector3 &to, const btV mLinesColors->push_back({1,1,1,1}); mLinesColors->push_back({1,1,1,1}); +#if BT_BULLET_VERSION < 317 size_t size = mLinesVertices->size(); if (size >= 6 && (*mLinesVertices)[size - 1] == (*mLinesVertices)[size - 6] @@ -154,6 +155,7 @@ void DebugDrawer::drawLine(const btVector3 &from, const btVector3 &to, const btV mLinesVertices->pop_back(); mLinesColors->pop_back(); } +#endif } void DebugDrawer::drawTriangle(const btVector3& v0, const btVector3& v1, const btVector3& v2, const btVector3& color, btScalar) From 8d93748f87eb9088b0b413fd9389809dd9de2b6b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 8 Aug 2021 17:25:57 +0100 Subject: [PATCH 1173/2859] Fix rebase issue --- apps/openmw/mwrender/bulletdebugdraw.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index bf56e9a330..9155132871 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -70,6 +70,7 @@ void DebugDrawer::createGeometry() osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); stateSet->setAttribute(material); + mLinesGeometry->setStateSet(stateSet); mTrisGeometry->setStateSet(stateSet); mShapesRoot = new osg::Group; mShapesRoot->setStateSet(stateSet); From c1e50f530b6b7c229864b20ab26be284e3edec42 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 7 Aug 2021 12:40:08 +0200 Subject: [PATCH 1174/2859] Calls directly MovementSolver::traceDown instead of PhysicsSystem::traceDown before inserting into mActors. The latter does nothing until the actor is inserted into mActors. We can't move the call after the insertion either because then the actor is part of the simulation, and we'd have a race. --- apps/openmw/mwphysics/physicssystem.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f8862ea400..0afeb2d622 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -725,8 +725,7 @@ namespace MWPhysics auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); // check if Actor is on the ground or in the air - traceDown(ptr, ptr.getRefData().getPosition().asVec3(), 10.f); - + MovementSolver::traceDown(ptr, ptr.getRefData().getPosition().asVec3(), actor.get(), mCollisionWorld.get(), 10); mActors.emplace(ptr, std::move(actor)); } From 586d8684d00d28b6e645f3df3b5d6dc871afbce4 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 9 Aug 2021 12:43:30 +0200 Subject: [PATCH 1175/2859] Fix two coverity issues about uninitialised variables --- apps/openmw/mwphysics/physicssystem.cpp | 1 + components/resource/scenemanager.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 602f95104f..bec273daaf 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -996,6 +996,7 @@ namespace MWPhysics , mWaterCollision(waterCollision) , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) , mNeedLand(false) + , mStuckFrames(0) { } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ffcd4c3308..974ae6a7fd 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -237,6 +237,7 @@ namespace Resource , mMaxAnisotropy(1) , mUnRefImageDataAfterApply(false) , mParticleSystemMask(~0u) + , mDepthFormat(0) { } From 8e3984ae5a2cf0f5361a833b7f83afa24b1275c3 Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 9 Aug 2021 14:46:41 +0200 Subject: [PATCH 1176/2859] Clear mActors when reseting the simulation. Otherwise during next run we can try to read past the end of mActorsFrameData. --- apps/openmw/mwphysics/mtphysics.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 41cec6facc..e9bedcbc75 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -279,6 +279,7 @@ namespace MWPhysics std::unique_lock lock(mSimulationMutex); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); + mActors.clear(); mActorsFrameData.clear(); for (const auto& [_, actor] : actors) { From a6260453ea91b32e37893df38c2ac0108bb18f68 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 1 Jul 2021 19:00:22 +0200 Subject: [PATCH 1177/2859] Add missing initialization --- components/detournavigator/navmeshdata.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/detournavigator/navmeshdata.hpp b/components/detournavigator/navmeshdata.hpp index 501e971cba..6e400af3bc 100644 --- a/components/detournavigator/navmeshdata.hpp +++ b/components/detournavigator/navmeshdata.hpp @@ -20,7 +20,7 @@ namespace DetourNavigator struct NavMeshData { NavMeshDataValue mValue; - int mSize; + int mSize = 0; NavMeshData() = default; From 0985d8e03dda74ff011ec72af480ecf7caf850ef Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Jul 2021 04:08:03 +0200 Subject: [PATCH 1178/2859] Handle failed NavMesh allocation --- components/detournavigator/makenavmesh.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 892f9c2dd2..3f133f5033 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -495,6 +495,10 @@ namespace DetourNavigator params.maxPolys = 1 << polysBits; NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh); + + if (navMesh == nullptr) + throw NavigatorException("Failed to allocate navmesh"); + const auto status = navMesh->init(¶ms); if (!dtStatusSucceed(status)) From 94e71d9b148c83c85f60e3b8d64ad3206cb03f3a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Aug 2021 15:51:08 +0200 Subject: [PATCH 1179/2859] Avoid division by zero --- components/detournavigator/navmeshtilescache.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 4710a0a4f6..3d595f13a8 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -85,7 +85,8 @@ namespace DetourNavigator out.setAttribute(frameNumber, "NavMesh CacheSize", stats.mNavMeshCacheSize); out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles); out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles); - out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(stats.mHitCount) / stats.mGetCount * 100.0); + if (stats.mGetCount > 0) + out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(stats.mHitCount) / stats.mGetCount * 100.0); } void NavMeshTilesCache::removeLeastRecentlyUsed() From b27de8f1888b643fb6bf1665ac3f8b87b41c73a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sun, 8 Aug 2021 12:09:40 +0200 Subject: [PATCH 1180/2859] Disable zooming when setting is off --- apps/openmw/mwgui/mapwindow.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index efb434d3bc..9f7cfc73e6 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -791,14 +791,17 @@ namespace MWGui getWidget(mEventBoxGlobal, "EventBoxGlobal"); mEventBoxGlobal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxGlobal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); - mEventBoxGlobal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); + const bool allowZooming = Settings::Manager::getBool("allow zooming", "Map"); + if(allowZooming) + mEventBoxGlobal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); mEventBoxGlobal->setDepth(Global_ExploreOverlayLayer); getWidget(mEventBoxLocal, "EventBoxLocal"); mEventBoxLocal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); - mEventBoxLocal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); + if (allowZooming) + mEventBoxLocal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); LocalMapBase::init(mLocalMap, mPlayerArrowLocal, getLocalViewingDistance()); From b0e30e4bb6fd965bf9c3873d02dc1342f7993e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sun, 8 Aug 2021 12:10:17 +0200 Subject: [PATCH 1181/2859] Change setting to clamp the local viewing distance to fix value (instead of coeff) --- apps/openmw/mwgui/mapwindow.cpp | 7 +++---- docs/source/reference/modding/settings/map.rst | 14 +++++++++----- files/settings-default.cfg | 4 ++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 9f7cfc73e6..7f1ecd89d8 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -99,10 +99,9 @@ namespace return Constants::CellGridRadius; if (!Settings::Manager::getBool("distant terrain", "Terrain")) return Constants::CellGridRadius; - const float localViewingDistanceCoef = Settings::Manager::getFloat("local viewing distance coef", "Map"); - const int viewingDistance = Settings::Manager::getInt("viewing distance", "Camera"); - const int localViewingDistanceInCells = (viewingDistance * localViewingDistanceCoef) / double(Constants::CellSizeInUnits); - return std::max(Constants::CellGridRadius, localViewingDistanceInCells); + const int maxLocalViewingDistance = std::max(Settings::Manager::getInt("max local viewing distance", "Map"), Constants::CellGridRadius); + const int viewingDistanceInCells = Settings::Manager::getFloat("viewing distance", "Camera") / Constants::CellSizeInUnits; + return std::min(maxLocalViewingDistance, viewingDistanceInCells); } } diff --git a/docs/source/reference/modding/settings/map.rst b/docs/source/reference/modding/settings/map.rst index eeef0a1f39..a4d3cd7e0d 100644 --- a/docs/source/reference/modding/settings/map.rst +++ b/docs/source/reference/modding/settings/map.rst @@ -115,14 +115,18 @@ If this setting is true the user can zoom in/out on local and global map with th This setting can be controlled in Advanced tab of the launcher. -local viewing distance coef +max local viewing distance --------------------------- -:Type: float -:Range: > 0 and <= 1 -:Default: 0.5 +:Type: integer +:Range: > 0 +:Default: 10 -This setting controls viewing distance on local map. It is the coefficient of the viewing distance viewable on the local map if 'distant terrain' is enabled otherwise you will see the default value (a 3x3 square centered on the player). +This setting controls the viewing distance on local map when 'distant terrain' is enabled. +If this setting is greater than the viewing distance then only up to the viewing distance is used for local map, otherwise the viewing distance is used. If view distance is changed in settings menu during the game, then viewable distance on the local map is not updated. +.. warning:: + Increasing this setting can increase cell load times, + because the localmap take a snapshot of each cell contained in a square of 2 x (max local viewing distance) + 1 square. This setting can not be configured except by editing the settings configuration file. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 2c287a889c..4737fcf5ee 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -205,8 +205,8 @@ global = false # If true, allow zoom on local and global maps allow zooming = false -# The local view distance coefficient (1.0 is full view distance if distant terrain is enabled) -local viewing distance coef = 0.5 +# The local view distance in number of cells (up to the view distance) +max local viewing distance = 10 [GUI] From bfc9ea9e32c30f1b0c73fa7c49202e22493d147d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 9 Aug 2021 14:14:26 +0200 Subject: [PATCH 1182/2859] Fix warning on MSVC convertion from double to float --- apps/openmw/mwgui/mapwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 7f1ecd89d8..485e7f7ed0 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -36,7 +36,7 @@ namespace { const int cellSize = Constants::CellSizeInUnits; - constexpr float speed = 1.08; //the zoom speed, it should be greater than 1 + constexpr float speed = 1.08f; //the zoom speed, it should be greater than 1 enum LocalMapWidgetDepth { From 956edca52492b261744f474263d7222cf6ecf7ec Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Mon, 9 Aug 2021 21:17:13 +0300 Subject: [PATCH 1183/2859] Don't re-enable specularity w/ material controller for MW models --- components/nifosg/nifloader.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 17551e0b91..81c3d24dcc 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -845,8 +845,10 @@ namespace NifOsg const Nif::NiMaterialColorController* matctrl = static_cast(ctrl.getPtr()); if (matctrl->data.empty() && matctrl->interpolator.empty()) continue; - osg::ref_ptr osgctrl; auto targetColor = static_cast(matctrl->targetColor); + if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW && targetColor == MaterialColorController::TargetColor::Specular) + continue; + osg::ref_ptr osgctrl; if (!matctrl->interpolator.empty()) osgctrl = new MaterialColorController(matctrl->interpolator.getPtr(), targetColor, baseMaterial); else // if (!matctrl->data.empty()) @@ -1967,6 +1969,7 @@ namespace NifOsg case Nif::RC_NiSpecularProperty: { // Specular property can turn specular lighting off. + // FIXME: NiMaterialColorController doesn't care about this. auto specprop = static_cast(property); specEnabled = specprop->flags & 1; break; From 7dfaef7644ef5bbf9cb49d30746c789b8b84b037 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 9 Aug 2021 22:05:12 +0200 Subject: [PATCH 1184/2859] resolve issue of meshes that cross boundaries; should only be calculating the maxBound, not the minBound of y; also fix it in groundcover as it was copied blindly over; --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index dd64c851f1..947bdad202 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -172,7 +172,7 @@ namespace MWRender osg::Vec3f pos = ref.mPos.asVec3(); osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) - || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) return false; return true; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index aae8f2e4f1..ec0a381b84 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -507,7 +507,7 @@ namespace MWRender { osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) - || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) continue; } From c99bddc8dcebf2e49aa7bd90b548f7583c6e5e7a Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 9 Aug 2021 22:11:00 +0200 Subject: [PATCH 1185/2859] Revert "Move reference to the right cell according to its geographical position" This reverts commit d0677c3f07678a5eb351572c4eb9b14b0e1f03e4. --- apps/openmw/mwrender/objectpaging.cpp | 10 ---------- apps/openmw/mwworld/store.cpp | 12 ------------ components/esm/loadcell.hpp | 5 ----- 3 files changed, 27 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index ec0a381b84..db81027d9b 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -434,7 +434,6 @@ namespace MWRender continue; if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; - if (std::find(cell->mMovedRefsByPos.begin(), cell->mMovedRefsByPos.end(), ref.mRefNum) != cell->mMovedRefsByPos.end()) continue; Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; @@ -456,15 +455,6 @@ namespace MWRender if (!typeFilter(type,size>=2)) continue; refs[ref.mRefNum] = std::move(ref); } - - for (auto [ref, deleted] : cell->mLeasedRefsByPos) - { - if (deleted) { refs.erase(ref.mRefNum); continue; } - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - int type = store.findStatic(ref.mRefID); - if (!typeFilter(type, size >= 2)) continue; - refs[ref.mRefNum] = std::move(ref); - } } } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index add8b541e0..125239e9a3 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -478,15 +478,6 @@ namespace MWWorld // implementation when the oher implementation works as well. while (cell->getNextRef(esm, ref, deleted, cMRef, moved)) { - - auto x = static_cast(std::floor(ref.mPos.pos[0] / float(ESM::Land::REAL_SIZE))); - auto y = static_cast(std::floor(ref.mPos.pos[1] / float(ESM::Land::REAL_SIZE))); - if (x != cell->getGridX() || y != cell->getGridY()) - { - ESM::Cell* cellAlt = const_cast(searchOrCreate(x, y)); - cellAlt->mLeasedRefsByPos.emplace_back(ref, deleted); - cell->mMovedRefsByPos.push_back(ref.mRefNum); - } if (!moved) continue; @@ -687,9 +678,6 @@ namespace MWWorld oldcell->mMovedRefs.push_back(*it); } - oldcell->mLeasedRefsByPos.splice(oldcell->mLeasedRefsByPos.end(), cell.mLeasedRefsByPos); - oldcell->mMovedRefsByPos.splice(oldcell->mMovedRefsByPos.end(), cell.mMovedRefsByPos); - // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a // reference to this cell, so the list for the new cell should be empty. The list for oldcell, // however, could have leased refs in it and so should be kept. diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 23f3d38cc8..18e929e13b 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -124,11 +124,6 @@ struct Cell CellRefTracker mLeasedRefs; MovedCellRefTracker mMovedRefs; - // References "adopted" from another cell (i.e. a different cell - // introduced this ref, and it has been moved here as it geographically in this cell) - CellRefTracker mLeasedRefsByPos; - std::list mMovedRefsByPos; - void postLoad(ESMReader &esm); // This method is left in for compatibility with esmtool. Parsing moved references currently requires From 436c640da2fa807b2e68f146ce96788e0dc17841 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 9 Aug 2021 22:56:04 +0200 Subject: [PATCH 1186/2859] the old switch-a-roo because we shoudl only StopTraversal when the node size is larger than 1 and not the other way around --- components/terrain/quadtreeworld.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index e9f5805566..e26fc1b617 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -65,8 +65,7 @@ public: { const osg::Vec2f& center = node->getCenter(); bool activeGrid = (center.x() > mActiveGrid.x() && center.y() > mActiveGrid.y() && center.x() < mActiveGrid.z() && center.y() < mActiveGrid.w()); - if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded - return StopTraversal; + if (node->getSize()>1) { float halfSize = node->getSize()/2; @@ -77,6 +76,9 @@ public: return Deeper; } + if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded + return StopTraversal; + int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); From 0e6fbbc126c58d46c62f817211751c770880d840 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 26 Jun 2021 23:10:24 +0200 Subject: [PATCH 1187/2859] Lua package 'openmw.input' --- apps/openmw/CMakeLists.txt | 3 +- apps/openmw/mwbase/inputmanager.hpp | 16 +- apps/openmw/mwbase/luamanager.hpp | 10 +- apps/openmw/mwinput/actionmanager.cpp | 2 + apps/openmw/mwinput/actionmanager.hpp | 1 + apps/openmw/mwinput/bindingsmanager.cpp | 10 ++ apps/openmw/mwinput/bindingsmanager.hpp | 3 +- apps/openmw/mwinput/controllermanager.cpp | 10 ++ apps/openmw/mwinput/inputmanagerimp.cpp | 41 ++++- apps/openmw/mwinput/inputmanagerimp.hpp | 15 +- apps/openmw/mwinput/keyboardmanager.cpp | 6 +- apps/openmw/mwinput/mousemanager.cpp | 5 + apps/openmw/mwinput/mousemanager.hpp | 8 + apps/openmw/mwlua/inputbindings.cpp | 150 ++++++++++++++++ apps/openmw/mwlua/luabindings.cpp | 22 +-- apps/openmw/mwlua/luabindings.hpp | 5 +- apps/openmw/mwlua/luamanagerimp.cpp | 20 +-- apps/openmw/mwlua/luamanagerimp.hpp | 5 +- apps/openmw/mwlua/playerscripts.hpp | 33 +++- docs/source/reference/lua-scripting/api.rst | 3 + .../lua-scripting/engine_handlers.rst | 19 +- .../reference/lua-scripting/openmw_input.rst | 6 + .../reference/lua-scripting/overview.rst | 2 + files/lua_api/openmw/core.lua | 10 -- files/lua_api/openmw/input.lua | 170 ++++++++++++++++++ 25 files changed, 509 insertions(+), 66 deletions(-) create mode 100644 apps/openmw/mwlua/inputbindings.cpp create mode 100644 docs/source/reference/lua-scripting/openmw_input.rst create mode 100644 files/lua_api/openmw/input.lua diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a6d9402c4f..53e35f3310 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,8 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query - luabindings localscripts objectbindings cellbindings asyncbindings camerabindings uibindings settingsbindings + luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings + camerabindings uibindings inputbindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 951b5053a2..5ac7c218b5 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace Loading @@ -51,9 +52,17 @@ namespace MWBase virtual void toggleControlSwitch (const std::string& sw, bool value) = 0; virtual bool getControlSwitch (const std::string& sw) = 0; - virtual std::string getActionDescription (int action) = 0; - virtual std::string getActionKeyBindingName (int action) = 0; - virtual std::string getActionControllerBindingName (int action) = 0; + virtual std::string getActionDescription (int action) const = 0; + virtual std::string getActionKeyBindingName (int action) const = 0; + virtual std::string getActionControllerBindingName (int action) const = 0; + virtual bool actionIsActive(int action) const = 0; + + virtual float getActionValue(int action) const = 0; // returns value in range [0, 1] + virtual float getControllerAxisValue(SDL_GameControllerAxis axis) const = 0; // returns value in range [-1, 1] + virtual uint32_t getMouseButtonsState() const = 0; + virtual int getMouseMoveX() const = 0; + virtual int getMouseMoveY() const = 0; + ///Actions available for binding to keyboard buttons virtual std::vector getActionKeySorting() = 0; ///Actions available for binding to controller buttons @@ -74,6 +83,7 @@ namespace MWBase virtual void readRecord(ESM::ESMReader& reader, uint32_t type) = 0; virtual void resetIdleTime() = 0; + virtual bool isIdle() const = 0; virtual void executeAction(int action) = 0; diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index cc416c9ab0..ebcd8f50b3 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWBASE_LUAMANAGER_H #define GAME_MWBASE_LUAMANAGER_H +#include #include namespace MWWorld @@ -29,8 +30,6 @@ namespace MWBase virtual ~LuaManager() = default; virtual void newGameStarted() = 0; - virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; - virtual void registerObject(const MWWorld::Ptr& ptr) = 0; virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0; virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; @@ -40,6 +39,13 @@ namespace MWBase // virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0; + struct InputEvent + { + enum {KeyPressed, KeyReleased, ControllerPressed, ControllerReleased, Action} mType; + std::variant mValue; + }; + virtual void inputEvent(const InputEvent& event) = 0; + struct ActorControls { bool mDisableAI = false; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index c91722fbfb..e080437d92 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -9,6 +9,7 @@ #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -195,6 +196,7 @@ namespace MWInput void ActionManager::executeAction(int action) { + MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action}); auto* inputManager = MWBase::Environment::get().getInputManager(); auto* windowManager = MWBase::Environment::get().getWindowManager(); // trigger action activated diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index eceac2e94f..2180e1944e 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -48,6 +48,7 @@ namespace MWInput void showQuickKeysMenu(); void resetIdleTime(); + float getIdleTime() const { return mTimeIdle; } bool isAlwaysRunActive() const { return mAlwaysRunActive; }; bool isSneaking() const { return mSneaking; }; diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 851e33a87c..ca7911ecc2 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -653,6 +653,16 @@ namespace MWInput return mInputBinder->getKeyBinding(mInputBinder->getControl(actionId), ICS::Control::INCREASE); } + float BindingsManager::getControllerAxisValue(SDL_GameControllerAxis axis) const + { + const auto& controllers = mInputBinder->getJoystickInstanceMap(); + if (controllers.empty()) + return 0; + SDL_GameController* cntrl = controllers.begin()->second; + constexpr int AXIS_MAX_ABSOLUTE_VALUE = 32768; + return SDL_GameControllerGetAxis(cntrl, axis) / static_cast(AXIS_MAX_ABSOLUTE_VALUE); + } + void BindingsManager::actionValueChanged(int action, float currentValue, float previousValue) { MWBase::Environment::get().getInputManager()->resetIdleTime(); diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index 74416d3c7f..5c653f0b3e 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -42,7 +42,8 @@ namespace MWInput bool isLeftOrRightButton(int action, bool joystick) const; bool actionIsActive(int id) const; - float getActionValue(int id) const; + float getActionValue(int id) const; // returns value in range [0, 1] + float getControllerAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1] void mousePressed(const SDL_MouseButtonEvent &evt, int deviceID); void mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID); diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 03d492c9cf..7a91f8643b 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -8,6 +8,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -198,6 +199,9 @@ namespace MWInput if (!mJoystickEnabled || mBindingsManager->isDetectingBindingState()) return; + MWBase::Environment::get().getLuaManager()->inputEvent( + {MWBase::LuaManager::InputEvent::ControllerPressed, arg.button}); + mJoystickLastUsed = true; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { @@ -240,6 +244,12 @@ namespace MWInput return; } + if (mJoystickEnabled) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + {MWBase::LuaManager::InputEvent::ControllerReleased, arg.button}); + } + if (!mJoystickEnabled || MWBase::Environment::get().getInputManager()->controlsDisabled()) return; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 436eab7ad3..31f515afb0 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -150,21 +150,56 @@ namespace MWInput mActionManager->resetIdleTime(); } - std::string InputManager::getActionDescription(int action) + bool InputManager::isIdle() const + { + return mActionManager->getIdleTime() > 0.5; + } + + std::string InputManager::getActionDescription(int action) const { return mBindingsManager->getActionDescription(action); } - std::string InputManager::getActionKeyBindingName(int action) + std::string InputManager::getActionKeyBindingName(int action) const { return mBindingsManager->getActionKeyBindingName(action); } - std::string InputManager::getActionControllerBindingName(int action) + std::string InputManager::getActionControllerBindingName(int action) const { return mBindingsManager->getActionControllerBindingName(action); } + bool InputManager::actionIsActive(int action) const + { + return mBindingsManager->actionIsActive(action); + } + + float InputManager::getActionValue(int action) const + { + return mBindingsManager->getActionValue(action); + } + + float InputManager::getControllerAxisValue(SDL_GameControllerAxis axis) const + { + return mBindingsManager->getControllerAxisValue(axis); + } + + uint32_t InputManager::getMouseButtonsState() const + { + return mMouseManager->getButtonsState(); + } + + int InputManager::getMouseMoveX() const + { + return mMouseManager->getMouseMoveX(); + } + + int InputManager::getMouseMoveY() const + { + return mMouseManager->getMouseMoveY(); + } + std::vector InputManager::getActionKeySorting() { return mBindingsManager->getActionKeySorting(); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index f930836d1c..adb8319498 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -73,9 +73,17 @@ namespace MWInput void toggleControlSwitch (const std::string& sw, bool value) override; bool getControlSwitch (const std::string& sw) override; - std::string getActionDescription (int action) override; - std::string getActionKeyBindingName (int action) override; - std::string getActionControllerBindingName (int action) override; + std::string getActionDescription (int action) const override; + std::string getActionKeyBindingName (int action) const override; + std::string getActionControllerBindingName (int action) const override; + bool actionIsActive(int action) const override; + + float getActionValue(int action) const override; + float getControllerAxisValue(SDL_GameControllerAxis axis) const override; + uint32_t getMouseButtonsState() const override; + int getMouseMoveX() const override; + int getMouseMoveY() const override; + int getNumActions() override { return A_Last; } std::vector getActionKeySorting() override; std::vector getActionControllerSorting() override; @@ -91,6 +99,7 @@ namespace MWInput void readRecord(ESM::ESMReader& reader, uint32_t type) override; void resetIdleTime() override; + bool isIdle() const override; void executeAction(int action) override; diff --git a/apps/openmw/mwinput/keyboardmanager.cpp b/apps/openmw/mwinput/keyboardmanager.cpp index 03db584192..b8019b12ba 100644 --- a/apps/openmw/mwinput/keyboardmanager.cpp +++ b/apps/openmw/mwinput/keyboardmanager.cpp @@ -60,7 +60,10 @@ namespace MWInput mBindingsManager->keyPressed(arg); if (!consumed) - MWBase::Environment::get().getLuaManager()->keyPressed(arg); + { + MWBase::Environment::get().getLuaManager()->inputEvent( + {MWBase::LuaManager::InputEvent::KeyPressed, arg.keysym}); + } input->setJoystickLastUsed(false); } @@ -73,5 +76,6 @@ namespace MWInput if (!mBindingsManager->isDetectingBindingState()) mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->keyReleased(arg); + MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::KeyReleased, arg.keysym}); } } diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index cf151dfac7..7810a40ad2 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -34,6 +34,9 @@ namespace MWInput , mMouseWheel(0) , mMouseLookEnabled(false) , mGuiCursorEnabled(true) + , mButtonsState(0) + , mMouseMoveX(0) + , mMouseMoveY(0) { int w,h; SDL_GetWindowSize(window, &w, &h); @@ -196,6 +199,8 @@ namespace MWInput void MouseManager::update(float dt) { + mButtonsState = SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY); + if (!mMouseLookEnabled) return; diff --git a/apps/openmw/mwinput/mousemanager.hpp b/apps/openmw/mwinput/mousemanager.hpp index 000e7cd0b6..d5504c5f5a 100644 --- a/apps/openmw/mwinput/mousemanager.hpp +++ b/apps/openmw/mwinput/mousemanager.hpp @@ -38,6 +38,10 @@ namespace MWInput void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } + uint32_t getButtonsState() const { return mButtonsState; } + int getMouseMoveX() const { return mMouseMoveX; } + int getMouseMoveY() const { return mMouseMoveY; } + private: bool mInvertX; bool mInvertY; @@ -53,6 +57,10 @@ namespace MWInput int mMouseWheel; bool mMouseLookEnabled; bool mGuiCursorEnabled; + + uint32_t mButtonsState; + int mMouseMoveX; + int mMouseMoveY; }; } #endif diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp new file mode 100644 index 0000000000..f815d9c344 --- /dev/null +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -0,0 +1,150 @@ +#include "luabindings.hpp" + +#include +#include + +#include "../mwbase/inputmanager.hpp" +#include "../mwinput/actions.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + + sol::table initInputPackage(const Context& context) + { + sol::usertype keyEvent = context.mLua->sol().new_usertype("KeyEvent"); + keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { return std::string(1, static_cast(e.sym)); }); + keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.sym; }); + keyEvent["modifiers"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.mod; }); + keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; }); + keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; }); + keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); + keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); + + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); + sol::table api(context.mLua->sol(), sol::create); + + api["isIdle"] = [input]() { return input->isIdle(); }; + api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); }; + api["isMouseButtonPressed"] = [input](int button) -> bool + { + return input->getMouseButtonsState() & (1 << (button - 1)); + }; + api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; + api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; + api["getAxisValue"] = [input](int axis) + { + if (axis < SDL_CONTROLLER_AXIS_MAX) + return input->getControllerAxisValue(static_cast(axis)); + else + return input->getActionValue(axis - SDL_CONTROLLER_AXIS_MAX) * 2 - 1; + }; + + api["getControlSwitch"] = [input](const std::string& key) { return input->getControlSwitch(key); }; + api["setControlSwitch"] = [input](const std::string& key, bool v) { input->toggleControlSwitch(key, v); }; + + api["ACTION"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + "GameMenu", MWInput::A_GameMenu, + "Screenshot", MWInput::A_Screenshot, + "Inventory", MWInput::A_Inventory, + "Console", MWInput::A_Console, + + "MoveLeft", MWInput::A_MoveLeft, + "MoveRight", MWInput::A_MoveRight, + "MoveForward", MWInput::A_MoveForward, + "MoveBackward", MWInput::A_MoveBackward, + + "Activate", MWInput::A_Activate, + "Use", MWInput::A_Use, + "Jump", MWInput::A_Jump, + "AutoMove", MWInput::A_AutoMove, + "Rest", MWInput::A_Rest, + "Journal", MWInput::A_Journal, + "Weapon", MWInput::A_Weapon, + "Spell", MWInput::A_Spell, + "Run", MWInput::A_Run, + "CycleSpellLeft", MWInput::A_CycleSpellLeft, + "CycleSpellRight", MWInput::A_CycleSpellRight, + "CycleWeaponLeft", MWInput::A_CycleWeaponLeft, + "CycleWeaponRight", MWInput::A_CycleWeaponRight, + "ToggleSneak", MWInput::A_ToggleSneak, + "AlwaysRun", MWInput::A_AlwaysRun, + "Sneak", MWInput::A_Sneak, + + "QuickSave", MWInput::A_QuickSave, + "QuickLoad", MWInput::A_QuickLoad, + "QuickMenu", MWInput::A_QuickMenu, + "ToggleWeapon", MWInput::A_ToggleWeapon, + "ToggleSpell", MWInput::A_ToggleSpell, + "TogglePOV", MWInput::A_TogglePOV, + + "QuickKey1", MWInput::A_QuickKey1, + "QuickKey2", MWInput::A_QuickKey2, + "QuickKey3", MWInput::A_QuickKey3, + "QuickKey4", MWInput::A_QuickKey4, + "QuickKey5", MWInput::A_QuickKey5, + "QuickKey6", MWInput::A_QuickKey6, + "QuickKey7", MWInput::A_QuickKey7, + "QuickKey8", MWInput::A_QuickKey8, + "QuickKey9", MWInput::A_QuickKey9, + "QuickKey10", MWInput::A_QuickKey10, + "QuickKeysMenu", MWInput::A_QuickKeysMenu, + + "ToggleHUD", MWInput::A_ToggleHUD, + "ToggleDebug", MWInput::A_ToggleDebug, + + "ZoomIn", MWInput::A_ZoomIn, + "ZoomOut", MWInput::A_ZoomOut + )); + + api["CONTROL_SWITCH"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + "Controls", "playercontrols", + "Fighting", "playerfighting", + "Jumping", "playerjumping", + "Looking", "playerlooking", + "Magic", "playermagic", + "ViewMode", "playerviewswitch", + "VanityMode", "vanitymode" + )); + + api["CONTROLLER_BUTTON"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + "A", SDL_CONTROLLER_BUTTON_A, + "B", SDL_CONTROLLER_BUTTON_B, + "X", SDL_CONTROLLER_BUTTON_X, + "Y", SDL_CONTROLLER_BUTTON_Y, + "Back", SDL_CONTROLLER_BUTTON_BACK, + "Guide", SDL_CONTROLLER_BUTTON_GUIDE, + "Start", SDL_CONTROLLER_BUTTON_START, + "LeftStick", SDL_CONTROLLER_BUTTON_LEFTSTICK, + "RightStick", SDL_CONTROLLER_BUTTON_RIGHTSTICK, + "LeftShoulder", SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + "RightShoulder", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + "DPadUp", SDL_CONTROLLER_BUTTON_DPAD_UP, + "DPadDown", SDL_CONTROLLER_BUTTON_DPAD_DOWN, + "DPadLeft", SDL_CONTROLLER_BUTTON_DPAD_LEFT, + "DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT + )); + + api["CONTROLLER_AXIS"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + "LeftX", SDL_CONTROLLER_AXIS_LEFTX, + "LeftY", SDL_CONTROLLER_AXIS_LEFTY, + "RightX", SDL_CONTROLLER_AXIS_RIGHTX, + "RightY", SDL_CONTROLLER_AXIS_RIGHTY, + "TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT, + "TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + + "LookUpDown", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookUpDown, + "LookLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookLeftRight, + "MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveForwardBackward, + "MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight + )); + + return context.mLua->makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 9d978ddd52..e50af836b5 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -1,7 +1,5 @@ #include "luabindings.hpp" -#include - #include #include @@ -12,12 +10,6 @@ #include "eventqueue.hpp" #include "worldview.hpp" -namespace sol -{ - template <> - struct is_automagical : std::false_type {}; -} - namespace MWLua { @@ -33,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 3; + api["API_REVISION"] = 4; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); @@ -179,17 +171,5 @@ namespace MWLua return context.mLua->makeReadOnly(res); } - void initInputBindings(const Context& context) - { - sol::usertype keyEvent = context.mLua->sol().new_usertype("KeyEvent"); - keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { return std::string(1, static_cast(e.sym)); }); - keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.sym; }); - keyEvent["modifiers"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.mod; }); - keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; }); - keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; }); - keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); - keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); - } - } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 568eb4e1ef..c58b556a3d 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -26,8 +26,6 @@ namespace MWLua sol::table initFieldGroup(const Context&, const QueryFieldGroup&); - void initInputBindings(const Context&); - // Implemented in objectbindings.cpp void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&); @@ -59,6 +57,9 @@ namespace MWLua // Implemented in uibindings.cpp sol::table initUserInterfacePackage(const Context&); + // Implemented in inputbindings.cpp + sol::table initInputPackage(const Context&); + // Implemented in settingsbindings.cpp sol::table initGlobalSettingsPackage(const Context&); sol::table initLocalSettingsPackage(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index bbfe0b8c3a..d358979aec 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -53,7 +53,6 @@ namespace MWLua initObjectBindingsForLocalScripts(localContext); initCellBindingsForLocalScripts(localContext); LocalScripts::initializeSelfPackage(localContext); - initInputBindings(localContext); mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context)); mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); @@ -63,11 +62,12 @@ namespace MWLua mGlobalScripts.addPackage("openmw.settings", initGlobalSettingsPackage(context)); mCameraPackage = initCameraPackage(localContext); mUserInterfacePackage = initUserInterfacePackage(localContext); + mInputPackage = initInputPackage(localContext); mNearbyPackage = initNearbyPackage(localContext); mLocalSettingsPackage = initLocalSettingsPackage(localContext); mPlayerSettingsPackage = initPlayerSettingsPackage(localContext); - mKeyPressEvents.clear(); + mInputEvents.clear(); for (const std::string& path : mGlobalScriptList) if (mGlobalScripts.addNewScript(path)) Log(Debug::Info) << "Global script started: " << path; @@ -93,7 +93,7 @@ namespace MWLua if (paused) { - mKeyPressEvents.clear(); + mInputEvents.clear(); return; } @@ -130,10 +130,10 @@ namespace MWLua PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts) { - for (const SDL_Keysym& key : mKeyPressEvents) - playerScripts->keyPress(key); + for (const auto& event : mInputEvents) + playerScripts->processInputEvent(event); } - mKeyPressEvents.clear(); + mInputEvents.clear(); for (const LocalEngineEvent& e : mLocalEngineEvents) { @@ -187,7 +187,7 @@ namespace MWLua mActiveLocalScripts.clear(); mLocalEvents.clear(); mGlobalEvents.clear(); - mKeyPressEvents.clear(); + mInputEvents.clear(); mActorAddedEvents.clear(); mLocalEngineEvents.clear(); mPlayerChanged = false; @@ -253,11 +253,6 @@ namespace MWLua mWorldView.getObjectRegistry()->deregisterPtr(ptr); } - void LuaManager::keyPressed(const SDL_KeyboardEvent& arg) - { - mKeyPressEvents.push_back(arg.keysym); - } - void LuaManager::appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) { mLocalEngineEvents.push_back({getId(toPtr), LocalScripts::OnConsume{std::string(recordId)}}); @@ -294,6 +289,7 @@ namespace MWLua scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); scripts->addPackage("openmw.ui", mUserInterfacePackage); scripts->addPackage("openmw.camera", mCameraPackage); + scripts->addPackage("openmw.input", mInputPackage); scripts->addPackage("openmw.settings", mPlayerSettingsPackage); } else diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 48cdeba026..8fdfb02701 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -41,7 +41,7 @@ namespace MWLua void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; void registerObject(const MWWorld::Ptr& ptr) override; void deregisterObject(const MWWorld::Ptr& ptr) override; - void keyPressed(const SDL_KeyboardEvent &arg) override; + void inputEvent(const InputEvent& event) override { mInputEvents.push_back(event); } void appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) override; MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; @@ -75,6 +75,7 @@ namespace MWLua sol::table mNearbyPackage; sol::table mUserInterfacePackage; sol::table mCameraPackage; + sol::table mInputPackage; sol::table mLocalSettingsPackage; sol::table mPlayerSettingsPackage; @@ -96,7 +97,7 @@ namespace MWLua std::unique_ptr mGlobalLoader; std::unique_ptr mLocalLoader; - std::vector mKeyPressEvents; + std::vector mInputEvents; std::vector mActorAddedEvents; struct LocalEngineEvent diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index 72e064bb9b..ff0349b3c6 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -3,6 +3,8 @@ #include +#include "../mwbase/luamanager.hpp" + #include "localscripts.hpp" namespace MWLua @@ -13,13 +15,40 @@ namespace MWLua public: PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj) { - registerEngineHandlers({&mKeyPressHandlers}); + registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers, + &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, + &mActionHandlers}); } - void keyPress(const SDL_Keysym& key) { callEngineHandlers(mKeyPressHandlers, key); } + void processInputEvent(const MWBase::LuaManager::InputEvent& event) + { + using InputEvent = MWBase::LuaManager::InputEvent; + switch (event.mType) + { + case InputEvent::KeyPressed: + callEngineHandlers(mKeyPressHandlers, std::get(event.mValue)); + break; + case InputEvent::KeyReleased: + callEngineHandlers(mKeyReleaseHandlers, std::get(event.mValue)); + break; + case InputEvent::ControllerPressed: + callEngineHandlers(mControllerButtonPressHandlers, std::get(event.mValue)); + break; + case InputEvent::ControllerReleased: + callEngineHandlers(mControllerButtonReleaseHandlers, std::get(event.mValue)); + break; + case InputEvent::Action: + callEngineHandlers(mActionHandlers, std::get(event.mValue)); + break; + } + } private: EngineHandlerList mKeyPressHandlers{"onKeyPress"}; + EngineHandlerList mKeyReleaseHandlers{"onKeyRelease"}; + EngineHandlerList mControllerButtonPressHandlers{"onControllerButtonPress"}; + EngineHandlerList mControllerButtonReleaseHandlers{"onControllerButtonRelease"}; + EngineHandlerList mActionHandlers{"onInputAction"}; }; } diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 356fc16cbb..5075f8a5fe 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -14,6 +14,7 @@ Lua API reference openmw_world openmw_self openmw_nearby + openmw_input openmw_ui openmw_aux_util @@ -53,6 +54,8 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.nearby ` | by local scripts | | Read-only access to the nearest area of the game world. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.input ` | by player scripts | | User input | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.ui ` | by player scripts | | Controls user interface | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |openmw.camera | by player scripts | | Controls camera (not implemented) | diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index f08892be36..bcbee4349e 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -36,8 +36,21 @@ Engine handler is a function defined by a script, that can be called by the engi +----------------------------------+----------------------------------------------------------------------+ | **Only for local scripts attached to a player** | +----------------------------------+----------------------------------------------------------------------+ -| onKeyPress(key) | | `Key `_ pressed. Usage example: | -| | | ``if key.symbol == 'z' and key.withShift then ...`` | +| onKeyPress(key) | | `Key `_ is pressed. | +| | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` | ++----------------------------------+----------------------------------------------------------------------+ +| onKeyRelease(key) | | `Key `_ is released. | +| | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` | ++----------------------------------+----------------------------------------------------------------------+ +| onControllerButtonPress(id) | | A `button `_ on a game | +| | controller is pressed. Usage example: | +| | | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` | ++----------------------------------+----------------------------------------------------------------------+ +| onControllerButtonRelease(id) | | A `button `_ on a game | +| | controller is released. Usage example: | +| | | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` | ++----------------------------------+----------------------------------------------------------------------+ +| onInputAction(id) | | `Game control `_ is pressed. | +| | | Usage example: ``if id == input.ACTION.ToggleWeapon then ...`` | +----------------------------------+----------------------------------------------------------------------+ - diff --git a/docs/source/reference/lua-scripting/openmw_input.rst b/docs/source/reference/lua-scripting/openmw_input.rst new file mode 100644 index 0000000000..39136dc4c2 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_input.rst @@ -0,0 +1,6 @@ +Package openmw.input +==================== + +.. raw:: html + :file: generated_html/openmw_input.html + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index c193886340..44c79c35d8 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -314,6 +314,8 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.nearby ` | by local scripts | | Read-only access to the nearest area of the game world. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.input ` | by player scripts | | User input | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.ui ` | by player scripts | | Controls user interface | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |openmw.camera | by player scripts | | Controls camera (not implemented) | diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index affd052700..eed2005cfa 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -318,15 +318,5 @@ -- @return #ObjectList -------------------------------------------------------------------------------- --- Argument of `onKeyPress` engine handler --- @type KeyboardEvent --- @field [parent=#KeyboardEvent] #string symbol The pressed symbol (1-symbol string). --- @field [parent=#KeyboardEvent] #string code Key code. --- @field [parent=#KeyboardEvent] #boolean withShift Is `Shift` key pressed. --- @field [parent=#KeyboardEvent] #boolean withCtrl Is `Control` key pressed. --- @field [parent=#KeyboardEvent] #boolean withAlt Is `Alt` key pressed. --- @field [parent=#KeyboardEvent] #boolean withSuper Is `Super`/`Win` key pressed. - return nil diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua new file mode 100644 index 0000000000..0975ce6e6a --- /dev/null +++ b/files/lua_api/openmw/input.lua @@ -0,0 +1,170 @@ +------------------------------------------------------------------------------- +-- `openmw.input` can be used only in scripts attached to a player. +-- @module input +-- @usage local input = require('openmw.input') + + + +------------------------------------------------------------------------------- +-- Is player idle. +-- @function [parent=#input] isIdle +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is a specific control currently pressed. +-- Input bindings can be changed ingame using Options/Controls menu. +-- @function [parent=#input] isActionPressed +-- @param #number actionId One of @{openmw.input#ACTION} +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is a mouse button currently pressed. +-- @function [parent=#input] isMouseButtonPressed +-- @param #number buttonId Button index (1 - left, 2 - middle, 3 - right, 4 - X1, 5 - X2) +-- @return #boolean + +------------------------------------------------------------------------------- +-- Horizontal mouse movement during the last frame. +-- @function [parent=#input] getMouseMoveX +-- @return #number + +------------------------------------------------------------------------------- +-- Vertical mouse movement during the last frame. +-- @function [parent=#input] getMouseMoveY +-- @return #number + +------------------------------------------------------------------------------- +-- Get value of an axis of a game controller. +-- @function [parent=#input] getAxisValue +-- @param #number axisId Index of a controller axis, one of @{openmw.input#CONTROLLER_AXIS}. +-- @return #number Value in range [-1, 1]. + +------------------------------------------------------------------------------- +-- Get state of a control switch. I.e. is player able to move/fight/jump/etc. +-- @function [parent=#input] getControlSwitch +-- @param #string key Control type (see @{openmw.input#CONTROL_SWITCH}) +-- @return #boolean + +------------------------------------------------------------------------------- +-- Set state of a control switch. I.e. forbid or allow player to move/fight/jump/etc. +-- @function [parent=#input] setControlSwitch +-- @param #string key Control type (see @{openmw.input#CONTROL_SWITCH}) +-- @param #boolean value + +------------------------------------------------------------------------------- +-- @type CONTROL_SWITCH +-- @field [parent=#CONTROL_SWITCH] #string Controls Ability to move +-- @field [parent=#CONTROL_SWITCH] #string Fighting Ability to attack +-- @field [parent=#CONTROL_SWITCH] #string Jumping Ability to jump +-- @field [parent=#CONTROL_SWITCH] #string Looking Ability to change view direction +-- @field [parent=#CONTROL_SWITCH] #string Magic Ability to use magic +-- @field [parent=#CONTROL_SWITCH] #string ViewMode Ability to toggle 1st/3rd person view +-- @field [parent=#CONTROL_SWITCH] #string VanityMode Vanity view if player doesn't touch controls for a long time + +------------------------------------------------------------------------------- +-- Values that can be used with getControlSwitch/setControlSwitch. +-- @field [parent=#input] #CONTROL_SWITCH CONTROL_SWITCH + +------------------------------------------------------------------------------- +-- @type ACTION +-- @field [parent=#ACTION] #number GameMenu +-- @field [parent=#ACTION] #number Screenshot +-- @field [parent=#ACTION] #number Inventory +-- @field [parent=#ACTION] #number Console +-- @field [parent=#ACTION] #number MoveLeft +-- @field [parent=#ACTION] #number MoveRight +-- @field [parent=#ACTION] #number MoveForward +-- @field [parent=#ACTION] #number MoveBackward +-- @field [parent=#ACTION] #number Activate +-- @field [parent=#ACTION] #number Use +-- @field [parent=#ACTION] #number Jump +-- @field [parent=#ACTION] #number AutoMove +-- @field [parent=#ACTION] #number Journal +-- @field [parent=#ACTION] #number Weapon +-- @field [parent=#ACTION] #number Spell +-- @field [parent=#ACTION] #number Run +-- @field [parent=#ACTION] #number CycleSpellLeft +-- @field [parent=#ACTION] #number CycleSpellRight +-- @field [parent=#ACTION] #number CycleWeaponLeft +-- @field [parent=#ACTION] #number CycleWeaponRight +-- @field [parent=#ACTION] #number ToggleSneak +-- @field [parent=#ACTION] #number AlwaysRun +-- @field [parent=#ACTION] #number Sneak +-- @field [parent=#ACTION] #number QuickSave +-- @field [parent=#ACTION] #number QuickLoad +-- @field [parent=#ACTION] #number QuickMenu +-- @field [parent=#ACTION] #number ToggleWeapon +-- @field [parent=#ACTION] #number ToggleSpell +-- @field [parent=#ACTION] #number TogglePOV +-- @field [parent=#ACTION] #number QuickKey1 +-- @field [parent=#ACTION] #number QuickKey2 +-- @field [parent=#ACTION] #number QuickKey3 +-- @field [parent=#ACTION] #number QuickKey4 +-- @field [parent=#ACTION] #number QuickKey5 +-- @field [parent=#ACTION] #number QuickKey6 +-- @field [parent=#ACTION] #number QuickKey7 +-- @field [parent=#ACTION] #number QuickKey8 +-- @field [parent=#ACTION] #number QuickKey9 +-- @field [parent=#ACTION] #number QuickKey10 +-- @field [parent=#ACTION] #number QuickKeysMenu +-- @field [parent=#ACTION] #number ToggleHUD +-- @field [parent=#ACTION] #number ToggleDebug +-- @field [parent=#ACTION] #number ZoomIn +-- @field [parent=#ACTION] #number ZoomOut + +------------------------------------------------------------------------------- +-- Values that can be used with isActionPressed. +-- @field [parent=#input] #ACTION ACTION + +------------------------------------------------------------------------------- +-- @type CONTROLLER_BUTTON +-- @field [parent=#CONTROLLER_BUTTON] #number A +-- @field [parent=#CONTROLLER_BUTTON] #number B +-- @field [parent=#CONTROLLER_BUTTON] #number X +-- @field [parent=#CONTROLLER_BUTTON] #number Y +-- @field [parent=#CONTROLLER_BUTTON] #number Back +-- @field [parent=#CONTROLLER_BUTTON] #number Guide +-- @field [parent=#CONTROLLER_BUTTON] #number Start +-- @field [parent=#CONTROLLER_BUTTON] #number LeftStick +-- @field [parent=#CONTROLLER_BUTTON] #number RightStick +-- @field [parent=#CONTROLLER_BUTTON] #number LeftShoulder +-- @field [parent=#CONTROLLER_BUTTON] #number RightShoulder +-- @field [parent=#CONTROLLER_BUTTON] #number DPadUp +-- @field [parent=#CONTROLLER_BUTTON] #number DPadDown +-- @field [parent=#CONTROLLER_BUTTON] #number DPadLeft +-- @field [parent=#CONTROLLER_BUTTON] #number DPadRight + +------------------------------------------------------------------------------- +-- Values that can be passed to onControllerButtonPress/onControllerButtonRelease engine handlers. +-- @field [parent=#input] #CONTROLLER_BUTTON CONTROLLER_BUTTON + +------------------------------------------------------------------------------- +-- Ids of game controller axises. Used as an argument in getAxisValue. +-- @type CONTROLLER_AXIS +-- @field [parent=#CONTROLLER_AXIS] #number LeftX Left stick horizontal axis (from -1 to 1) +-- @field [parent=#CONTROLLER_AXIS] #number LeftY Left stick vertical axis (from -1 to 1) +-- @field [parent=#CONTROLLER_AXIS] #number RightX Right stick horizontal axis (from -1 to 1) +-- @field [parent=#CONTROLLER_AXIS] #number RightY Right stick vertical axis (from -1 to 1) +-- @field [parent=#CONTROLLER_AXIS] #number TriggerLeft Left trigger (from 0 to 1) +-- @field [parent=#CONTROLLER_AXIS] #number TriggerRight Right trigger (from 0 to 1) +-- @field [parent=#CONTROLLER_AXIS] #number LookUpDown View direction vertical axis (RightY by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number LookLeftRight View direction horizontal axis (RightX by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number MoveForwardBackward Movement forward/backward (LeftY by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number MoveLeftRight Side movement (LeftX by default, can be mapped to another axis in Options/Controls menu) + +------------------------------------------------------------------------------- +-- Values that can be used with getAxisValue. +-- @field [parent=#input] #CONTROLLER_AXIS CONTROLLER_AXIS + +------------------------------------------------------------------------------- +-- The argument of `onKeyPress`/`onKeyRelease` engine handlers. +-- @type KeyboardEvent +-- @field [parent=#KeyboardEvent] #string symbol The pressed symbol (1-symbol string). +-- @field [parent=#KeyboardEvent] #string code Key code. +-- @field [parent=#KeyboardEvent] #boolean withShift Is `Shift` key pressed. +-- @field [parent=#KeyboardEvent] #boolean withCtrl Is `Control` key pressed. +-- @field [parent=#KeyboardEvent] #boolean withAlt Is `Alt` key pressed. +-- @field [parent=#KeyboardEvent] #boolean withSuper Is `Super`/`Win` key pressed. + +return nil + From de9ee2f196574952cfa2c5f10365d8af854c843b Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 11 Aug 2021 21:53:04 +0200 Subject: [PATCH 1188/2859] Revert "Calls directly MovementSolver::traceDown instead of" This reverts commit c1e50f530b6b7c229864b20ab26be284e3edec42. --- apps/openmw/mwphysics/physicssystem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index bec273daaf..846f80ec64 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -713,7 +713,8 @@ namespace MWPhysics auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); // check if Actor is on the ground or in the air - MovementSolver::traceDown(ptr, ptr.getRefData().getPosition().asVec3(), actor.get(), mCollisionWorld.get(), 10); + traceDown(ptr, ptr.getRefData().getPosition().asVec3(), 10.f); + mActors.emplace(ptr, std::move(actor)); } From e84a177a917fca82c7d61adc5ecc355f2349f0f0 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 12 Aug 2021 02:02:22 +0200 Subject: [PATCH 1189/2859] Always print object refnum Also print content file index even when it's negative. To be able to identify the object unambiguously. --- apps/openmw/mwscript/miscextensions.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index b34a359dc4..869a1d540a 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1363,18 +1363,19 @@ namespace MWScript std::time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); msg << std::put_time(std::gmtime(¤tTime), "%Y.%m.%d %T UTC") << std::endl; - msg << "Content file: "; + msg << "Content file: " << ptr.getCellRef().getRefNum().mContentFile; if (!ptr.getCellRef().hasContentFile()) - msg << "[None]" << std::endl; + msg << " [None]" << std::endl; else { std::vector contentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); - msg << contentFiles.at (ptr.getCellRef().getRefNum().mContentFile) << std::endl; - msg << "RefNum: " << ptr.getCellRef().getRefNum().mIndex << std::endl; + msg << " [" << contentFiles.at (ptr.getCellRef().getRefNum().mContentFile) << "]" << std::endl; } + msg << "RefNum: " << ptr.getCellRef().getRefNum().mIndex << std::endl; + if (ptr.getRefData().isDeletedByContentFile()) msg << "[Deleted by content file]" << std::endl; if (!ptr.getRefData().getCount()) From f9c3e1272b0730fa3794048fa81021126d261178 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 12 Aug 2021 12:43:52 +0200 Subject: [PATCH 1190/2859] Add #4595 to changelog Resolved by https://gitlab.com/OpenMW/openmw/-/merge_requests/430 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f735bd5a13..c53c8401f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map + Feature #4595: Unique object identifier Feature #4737: Handle instance move from one cell to another Feature #5489: MCP: Telekinesis fix for activators Feature #5996: Support Lua scripts in OpenMW From b49f51cbfce92a8448454d10b9aff6f3f10a2786 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 12 Aug 2021 18:33:06 +0300 Subject: [PATCH 1191/2859] Serialize NifOsg::MatrixTransform children properly --- components/sceneutil/serialize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 1b5c1feed8..703b63af7d 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -80,7 +80,7 @@ class MatrixTransformSerializer : public osgDB::ObjectWrapper { public: MatrixTransformSerializer() - : osgDB::ObjectWrapper(createInstanceFunc, "NifOsg::MatrixTransform", "osg::Object osg::Node osg::Transform osg::MatrixTransform NifOsg::MatrixTransform") + : osgDB::ObjectWrapper(createInstanceFunc, "NifOsg::MatrixTransform", "osg::Object osg::Node osg::Group osg::Transform osg::MatrixTransform NifOsg::MatrixTransform") { } }; From 8287c5ee4e8ce0cb68d92bcbe638d7fc4e732a9b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 12 Aug 2021 17:38:09 +0000 Subject: [PATCH 1192/2859] Remove incremental link files from the artefacts They make the files people download much bigger. --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a727fce684..5ed6c97da0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -217,6 +217,7 @@ variables: &tests-targets - cmake --build . --config $config --target ($targets.Split(',')) - 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 + - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt @@ -318,6 +319,7 @@ Windows_Ninja_Tests_RelWithDebInfo: - cmake --build . --config $config --target ($targets.Split(',')) - 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 + - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt From 9460a8760e5617e60234a0f9a1bb5fcc643c7bc8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Aug 2021 11:48:37 +0200 Subject: [PATCH 1193/2859] Move operator<< for UpdateNavMeshStatus to header --- .../detournavigator/asyncnavmeshupdater.cpp | 26 ------------------ .../detournavigator/navmeshcacheitem.hpp | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index a0c9cba613..8d43449f87 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -48,32 +48,6 @@ namespace namespace DetourNavigator { - static std::ostream& operator <<(std::ostream& stream, UpdateNavMeshStatus value) - { - switch (value) - { - case UpdateNavMeshStatus::ignored: - return stream << "ignore"; - case UpdateNavMeshStatus::removed: - return stream << "removed"; - case UpdateNavMeshStatus::added: - return stream << "add"; - case UpdateNavMeshStatus::replaced: - return stream << "replaced"; - case UpdateNavMeshStatus::failed: - return stream << "failed"; - case UpdateNavMeshStatus::lost: - return stream << "lost"; - case UpdateNavMeshStatus::cached: - return stream << "cached"; - case UpdateNavMeshStatus::unchanged: - return stream << "unchanged"; - case UpdateNavMeshStatus::restored: - return stream << "restored"; - } - return stream << "unknown(" << static_cast(value) << ")"; - } - AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager) : mSettings(settings) diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index 5558f4e1f5..ac68caedb3 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -10,6 +10,7 @@ #include #include +#include struct dtMeshTile; @@ -33,6 +34,32 @@ namespace DetourNavigator return (static_cast(value) & static_cast(UpdateNavMeshStatus::failed)) == 0; } + inline std::ostream& operator <<(std::ostream& stream, UpdateNavMeshStatus value) + { + switch (value) + { + case UpdateNavMeshStatus::ignored: + return stream << "ignore"; + case UpdateNavMeshStatus::removed: + return stream << "removed"; + case UpdateNavMeshStatus::added: + return stream << "add"; + case UpdateNavMeshStatus::replaced: + return stream << "replaced"; + case UpdateNavMeshStatus::failed: + return stream << "failed"; + case UpdateNavMeshStatus::lost: + return stream << "lost"; + case UpdateNavMeshStatus::cached: + return stream << "cached"; + case UpdateNavMeshStatus::unchanged: + return stream << "unchanged"; + case UpdateNavMeshStatus::restored: + return stream << "restored"; + } + return stream << "unknown(" << static_cast(value) << ")"; + } + class UpdateNavMeshStatusBuilder { public: From f1d05a93bf38a283f5ff9f9883d4aec1fb96fd3b Mon Sep 17 00:00:00 2001 From: unelsson Date: Thu, 12 Aug 2021 22:28:16 +0300 Subject: [PATCH 1194/2859] Get index and model from proxy --- apps/opencs/model/world/commanddispatcher.cpp | 15 ++++++++++++++- apps/opencs/model/world/commanddispatcher.hpp | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index b517fed0fb..f753ec2e31 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include + #include #include @@ -134,7 +137,7 @@ std::vector CSMWorld::CommandDispatcher::getExtendedTypes return tables; } -void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_) +void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *sourceModel, const QModelIndex& sourceIndex, const QVariant& new_) { if (mLocked) return; @@ -147,6 +150,16 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons std::unique_ptr modifyData; std::unique_ptr modifyCell; + QAbstractItemModel *model; + QModelIndex index; + + if (QAbstractProxyModel *proxy = dynamic_cast (sourceModel)) + { + // Replace proxy with actual model + index = proxy->mapToSource (sourceIndex); + model = proxy->sourceModel(); + } + int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); int stateColumn = dynamic_cast(*model).findColumnIndex(Columns::ColumnId_Modification); diff --git a/apps/opencs/model/world/commanddispatcher.hpp b/apps/opencs/model/world/commanddispatcher.hpp index 538fd7f188..13724f5dbd 100644 --- a/apps/opencs/model/world/commanddispatcher.hpp +++ b/apps/opencs/model/world/commanddispatcher.hpp @@ -60,7 +60,7 @@ namespace CSMWorld /// /// \attention model must either be a model for the table operated on by this /// dispatcher or a proxy of it. - void executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_); + void executeModify (QAbstractItemModel *sourceModel, const QModelIndex& sourceIndex, const QVariant& new_); public slots: From 298db2ef76e6ea8a8f9b251b100e66c602db45fa Mon Sep 17 00:00:00 2001 From: unelsson Date: Thu, 12 Aug 2021 22:31:37 +0300 Subject: [PATCH 1195/2859] Initialize and check pointer. --- apps/opencs/model/world/commanddispatcher.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index f753ec2e31..13e66fd5cd 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -150,7 +150,7 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *sourceModel std::unique_ptr modifyData; std::unique_ptr modifyCell; - QAbstractItemModel *model; + QAbstractItemModel *model(nullptr); QModelIndex index; if (QAbstractProxyModel *proxy = dynamic_cast (sourceModel)) @@ -160,6 +160,8 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *sourceModel model = proxy->sourceModel(); } + if (!model) return; + int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); int stateColumn = dynamic_cast(*model).findColumnIndex(Columns::ColumnId_Modification); From a99266a60e5478609a7f2ecc1ed77e3847c2ec58 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 12 Aug 2021 21:44:07 +0200 Subject: [PATCH 1196/2859] Do not measure total navmesh generation duration This is not a useful thing anymore. --- components/detournavigator/asyncnavmeshupdater.cpp | 12 ------------ components/detournavigator/asyncnavmeshupdater.hpp | 3 --- 2 files changed, 15 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 8d43449f87..a62734e926 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -253,8 +253,6 @@ namespace DetourNavigator const auto start = std::chrono::steady_clock::now(); - const auto firstStart = setFirstStart(start); - const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); if (!navMeshCacheItem) @@ -305,7 +303,6 @@ namespace DetourNavigator " generation=" << locked->getGeneration() << " revision=" << locked->getNavMeshRevision() << " time=" << std::chrono::duration_cast(finish - start).count() << "ms" << - " total_time=" << std::chrono::duration_cast(finish - firstStart).count() << "ms" " thread=" << std::this_thread::get_id(); return isSuccess(status); @@ -327,7 +324,6 @@ namespace DetourNavigator if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) { - mFirstStart.lock()->reset(); if (mJobs.empty() && getTotalThreadJobsUnsafe() == 0) mDone.notify_all(); return std::nullopt; @@ -396,14 +392,6 @@ namespace DetourNavigator writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } - std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) - { - const auto locked = mFirstStart.lock(); - if (!*locked) - *locked = value; - return *locked.get(); - } - void AsyncNavMeshUpdater::repost(Job&& job) { if (mShouldStop || job.mTryNumber > 2) diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index e8b2611e97..5743bc8f9c 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -111,7 +111,6 @@ namespace DetourNavigator Jobs mJobs; std::map> mPushed; Misc::ScopeGuarded mPlayerTile; - Misc::ScopeGuarded> mFirstStart; NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded>> mProcessingTiles; std::map> mLastUpdates; @@ -131,8 +130,6 @@ namespace DetourNavigator void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; - std::chrono::steady_clock::time_point setFirstStart(const std::chrono::steady_clock::time_point& value); - void repost(Job&& job); std::thread::id lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); From 8db640289c864ab129cf1d1a2200eb7e481f3ba2 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 12 Aug 2021 22:05:44 +0200 Subject: [PATCH 1197/2859] Use single set to store pushed jobs for tiles --- .../detournavigator/asyncnavmeshupdater.cpp | 39 +++++++++---------- .../detournavigator/asyncnavmeshupdater.hpp | 14 ++----- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index a62734e926..dda4268571 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -173,8 +173,6 @@ namespace DetourNavigator return true; } minDistanceToPlayer = getMinDistanceTo(playerPosition, maxDistanceToPlayer, mPushed, mPresentTiles); - for (const auto& [threadId, queue] : mThreadsQueues) - minDistanceToPlayer = getMinDistanceTo(playerPosition, minDistanceToPlayer, queue.mPushed, mPresentTiles); return minDistanceToPlayer >= maxDistanceToPlayer; }; std::unique_lock lock(mMutex); @@ -319,7 +317,7 @@ namespace DetourNavigator { const auto hasJob = [&] { return (!mJobs.empty() && mJobs.front().mProcessTime <= std::chrono::steady_clock::now()) - || !threadQueue.mJobs.empty(); + || !threadQueue.empty(); }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) @@ -330,11 +328,11 @@ namespace DetourNavigator } Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and " - << threadQueue.mJobs.size() << " thread jobs by thread=" << std::this_thread::get_id(); + << threadQueue.size() << " thread jobs by thread=" << std::this_thread::get_id(); - auto job = threadQueue.mJobs.empty() - ? getJob(mJobs, mPushed, true) - : getJob(threadQueue.mJobs, threadQueue.mPushed, false); + auto job = threadQueue.empty() + ? getJob(mJobs, true) + : getJob(threadQueue, false); if (!job) continue; @@ -342,13 +340,22 @@ namespace DetourNavigator const auto owner = lockTile(job->mAgentHalfExtents, job->mChangedTile); if (owner == threadId) + { + const auto it = mPushed.find(job->mAgentHalfExtents); + if (it != mPushed.end()) + { + it->second.erase(job->mChangedTile); + if (it->second.empty()) + mPushed.erase(it); + } return job; + } postThreadJob(std::move(*job), mThreadsQueues[owner]); } } - std::optional AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate) + std::optional AsyncNavMeshUpdater::getJob(Jobs& jobs, bool changeLastUpdate) { const auto now = std::chrono::steady_clock::now(); @@ -361,11 +368,6 @@ namespace DetourNavigator if (changeLastUpdate && job.mChangeType == ChangeType::update) mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] = now; - const auto it = pushed.find(job.mAgentHalfExtents); - it->second.erase(job.mChangedTile); - if (it->second.empty()) - pushed.erase(it); - return job; } @@ -407,13 +409,10 @@ namespace DetourNavigator } } - void AsyncNavMeshUpdater::postThreadJob(Job&& job, Queue& queue) + void AsyncNavMeshUpdater::postThreadJob(Job&& job, Jobs& queue) { - if (queue.mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second) - { - queue.mJobs.push_back(std::move(job)); - mHasJob.notify_all(); - } + queue.push_back(std::move(job)); + mHasJob.notify_all(); } std::thread::id AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) @@ -475,7 +474,7 @@ namespace DetourNavigator std::size_t AsyncNavMeshUpdater::getTotalThreadJobsUnsafe() const { return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0), - [] (auto r, const auto& v) { return r + v.second.mJobs.size(); }); + [] (auto r, const auto& v) { return r + v.second.size(); }); } void AsyncNavMeshUpdater::cleanupLastUpdates() diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 5743bc8f9c..cc6ae26833 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -92,14 +92,6 @@ namespace DetourNavigator using Jobs = std::deque; using Pushed = std::map>; - struct Queue - { - Jobs mJobs; - Pushed mPushed; - - Queue() = default; - }; - std::reference_wrapper mSettings; std::reference_wrapper mRecastMeshManager; std::reference_wrapper mOffMeshConnectionsManager; @@ -115,7 +107,7 @@ namespace DetourNavigator Misc::ScopeGuarded>> mProcessingTiles; std::map> mLastUpdates; std::set> mPresentTiles; - std::map mThreadsQueues; + std::map mThreadsQueues; std::vector mThreads; void process() noexcept; @@ -124,9 +116,9 @@ namespace DetourNavigator std::optional getNextJob(); - std::optional getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate); + std::optional getJob(Jobs& jobs, bool changeLastUpdate); - void postThreadJob(Job&& job, Queue& queue); + void postThreadJob(Job&& job, Jobs& queue); void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; From b0b60b05b81d0b3fd1b050b9a91908dd67d8dec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Thu, 12 Aug 2021 22:06:21 +0200 Subject: [PATCH 1198/2859] Do not update doors markers at each frame, only when needed --- apps/openmw/mwgui/mapwindow.cpp | 17 ++++++++++++++++- apps/openmw/mwgui/mapwindow.hpp | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 485e7f7ed0..e42808776c 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -192,6 +192,7 @@ namespace MWGui , mMarkerUpdateTimer(0.0f) , mLastDirectionX(0.0f) , mLastDirectionY(0.0f) + , mNeedDoorMarkersUpdate(false) { mCustomMarkers.eventMarkersChanged += MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } @@ -440,6 +441,10 @@ namespace MWGui } } + // Delay the door markers update until scripts have been given a chance to run. + // If we don't do this, door markers that should be disabled will still appear on the map. + mNeedDoorMarkersUpdate = true; + for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) widget->setCoord(getMarkerCoordinates(widget, 8)); @@ -544,7 +549,11 @@ namespace MWGui void LocalMapBase::onFrame(float dt) { - updateDoorMarkers(); + if (mNeedDoorMarkersUpdate) + { + updateDoorMarkers(); + mNeedDoorMarkersUpdate = false; + } mMarkerUpdateTimer += dt; @@ -950,6 +959,9 @@ namespace MWGui zoomOnCursor(zoomRatio); return; //the zoom out is too big, we switch to the global map } + + if (zoomOut) + mNeedDoorMarkersUpdate = true; } zoomOnCursor(speedDiff); } @@ -1172,7 +1184,10 @@ namespace MWGui MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; if (!mGlobal) + { + mNeedDoorMarkersUpdate = true; mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff ); + } else mGlobalMap->setViewOffset( mGlobalMap->getViewOffset() + diff ); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index cb0d368b30..d3cd626475 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -187,6 +187,8 @@ namespace MWGui float mLastDirectionX; float mLastDirectionY; + bool mNeedDoorMarkersUpdate; + private: void updateDoorMarkers(); }; From 902b0f9f84344a00a55b089c0cba8236671b44dd Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 5 Aug 2021 23:18:03 +0200 Subject: [PATCH 1199/2859] Store jobs in the same container until they are processed Push to queue and reorder only iterators. --- .../detournavigator/asyncnavmeshupdater.cpp | 116 +++++++++++------- .../detournavigator/asyncnavmeshupdater.hpp | 57 ++++----- 2 files changed, 94 insertions(+), 79 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index dda4268571..1f7134dcea 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -20,6 +20,8 @@ namespace using DetourNavigator::TilePosition; using DetourNavigator::UpdateType; using DetourNavigator::ChangeType; + using DetourNavigator::Job; + using DetourNavigator::JobIt; int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs) { @@ -44,6 +46,25 @@ namespace return UpdateType::Temporary; return UpdateType::Persistent; } + + auto getPriority(const Job& job) noexcept + { + return std::make_tuple(job.mProcessTime, job.mTryNumber, job.mChangeType, job.mDistanceToPlayer, job.mDistanceToOrigin); + } + + struct LessByJobPriority + { + bool operator()(JobIt lhs, JobIt rhs) const noexcept + { + return getPriority(*lhs) < getPriority(*rhs); + } + }; + + void insertPrioritizedJob(JobIt job, std::deque& queue) + { + const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {}); + queue.insert(it, job); + } } namespace DetourNavigator @@ -64,7 +85,8 @@ namespace DetourNavigator { mShouldStop = true; std::unique_lock lock(mMutex); - mJobs = decltype(mJobs)(); + mThreadsQueues.clear(); + mWaiting.clear(); mHasJob.notify_all(); lock.unlock(); for (auto& thread : mThreads) @@ -88,8 +110,8 @@ namespace DetourNavigator const std::lock_guard lock(mMutex); if (playerTileChanged) - for (auto& job : mJobs) - job.mDistanceToPlayer = getManhattanDistance(job.mChangedTile, playerTile); + for (JobIt job : mWaiting) + job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); for (const auto& changedTile : changedTiles) { @@ -108,24 +130,21 @@ namespace DetourNavigator ? mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); + const JobIt it = mJobs.insert(mJobs.end(), std::move(job)); + if (playerTileChanged) - { - mJobs.push_back(std::move(job)); - } + mWaiting.push_back(it); else - { - const auto it = std::upper_bound(mJobs.begin(), mJobs.end(), job); - mJobs.insert(it, std::move(job)); - } + insertPrioritizedJob(it, mWaiting); } } if (playerTileChanged) - std::sort(mJobs.begin(), mJobs.end()); + std::sort(mWaiting.begin(), mWaiting.end(), LessByJobPriority {}); Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs"; - if (!mJobs.empty()) + if (!mWaiting.empty()) mHasJob.notify_all(); } @@ -166,7 +185,7 @@ namespace DetourNavigator int minDistanceToPlayer = 0; const auto isDone = [&] { - jobsLeft = mJobs.size() + getTotalThreadJobsUnsafe(); + jobsLeft = mJobs.size(); if (jobsLeft == 0) { minDistanceToPlayer = 0; @@ -199,7 +218,7 @@ namespace DetourNavigator { { std::unique_lock lock(mMutex); - mDone.wait(lock, [this] { return mJobs.size() + getTotalThreadJobsUnsafe() == 0; }); + mDone.wait(lock, [this] { return mJobs.size() == 0; }); } mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); } @@ -210,7 +229,7 @@ namespace DetourNavigator { const std::lock_guard lock(mMutex); - jobs = mJobs.size() + getTotalThreadJobsUnsafe(); + jobs = mJobs.size(); } stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs); @@ -226,12 +245,14 @@ namespace DetourNavigator { try { - if (auto job = getNextJob()) + if (JobIt job = getNextJob(); job != mJobs.end()) { const auto processed = processJob(*job); unlockTile(job->mAgentHalfExtents, job->mChangedTile); - if (!processed) - repost(std::move(*job)); + if (processed) + removeJob(job); + else + repost(job); } else cleanupLastUpdates(); @@ -306,7 +327,7 @@ namespace DetourNavigator return isSuccess(status); } - std::optional AsyncNavMeshUpdater::getNextJob() + JobIt AsyncNavMeshUpdater::getNextJob() { std::unique_lock lock(mMutex); @@ -316,25 +337,25 @@ namespace DetourNavigator while (true) { const auto hasJob = [&] { - return (!mJobs.empty() && mJobs.front().mProcessTime <= std::chrono::steady_clock::now()) + return (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()) || !threadQueue.empty(); }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) { - if (mJobs.empty() && getTotalThreadJobsUnsafe() == 0) + if (mJobs.empty()) mDone.notify_all(); - return std::nullopt; + return mJobs.end(); } Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and " << threadQueue.size() << " thread jobs by thread=" << std::this_thread::get_id(); - auto job = threadQueue.empty() - ? getJob(mJobs, true) + const JobIt job = threadQueue.empty() + ? getJob(mWaiting, true) : getJob(threadQueue, false); - if (!job) + if (job == mJobs.end()) continue; const auto owner = lockTile(job->mAgentHalfExtents, job->mChangedTile); @@ -351,22 +372,22 @@ namespace DetourNavigator return job; } - postThreadJob(std::move(*job), mThreadsQueues[owner]); + postThreadJob(job, mThreadsQueues[owner]); } } - std::optional AsyncNavMeshUpdater::getJob(Jobs& jobs, bool changeLastUpdate) + JobIt AsyncNavMeshUpdater::getJob(std::deque& jobs, bool changeLastUpdate) { const auto now = std::chrono::steady_clock::now(); + JobIt job = jobs.front(); - if (jobs.front().mProcessTime > now) - return {}; + if (job->mProcessTime > now) + return mJobs.end(); - Job job = jobs.front(); jobs.pop_front(); - if (changeLastUpdate && job.mChangeType == ChangeType::update) - mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] = now; + if (changeLastUpdate && job->mChangeType == ChangeType::update) + mLastUpdates[job->mAgentHalfExtents][job->mChangedTile] = now; return job; } @@ -394,24 +415,27 @@ namespace DetourNavigator writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } - void AsyncNavMeshUpdater::repost(Job&& job) + void AsyncNavMeshUpdater::repost(JobIt job) { - if (mShouldStop || job.mTryNumber > 2) + if (mShouldStop || job->mTryNumber > 2) return; const std::lock_guard lock(mMutex); - if (mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second) + if (mPushed[job->mAgentHalfExtents].insert(job->mChangedTile).second) { - ++job.mTryNumber; - mJobs.push_back(std::move(job)); + ++job->mTryNumber; + mWaiting.push_back(job); mHasJob.notify_all(); + return; } + + mJobs.erase(job); } - void AsyncNavMeshUpdater::postThreadJob(Job&& job, Jobs& queue) + void AsyncNavMeshUpdater::postThreadJob(JobIt job, std::deque& queue) { - queue.push_back(std::move(job)); + queue.push_back(job); mHasJob.notify_all(); } @@ -468,13 +492,7 @@ namespace DetourNavigator std::size_t AsyncNavMeshUpdater::getTotalJobs() const { const std::scoped_lock lock(mMutex); - return mJobs.size() + getTotalThreadJobsUnsafe(); - } - - std::size_t AsyncNavMeshUpdater::getTotalThreadJobsUnsafe() const - { - return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0), - [] (auto r, const auto& v) { return r + v.second.size(); }); + return mJobs.size(); } void AsyncNavMeshUpdater::cleanupLastUpdates() @@ -499,4 +517,10 @@ namespace DetourNavigator ++agent; } } + + void AsyncNavMeshUpdater::removeJob(JobIt job) + { + const std::lock_guard lock(mMutex); + mJobs.erase(job); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index cc6ae26833..ab4be0b8d1 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -19,6 +19,7 @@ #include #include #include +#include class dtNavMesh; @@ -52,6 +53,20 @@ namespace DetourNavigator return stream << "ChangeType::" << static_cast(value); } + struct Job + { + osg::Vec3f mAgentHalfExtents; + std::weak_ptr mNavMeshCacheItem; + TilePosition mChangedTile; + unsigned mTryNumber; + ChangeType mChangeType; + int mDistanceToPlayer; + int mDistanceToOrigin; + std::chrono::steady_clock::time_point mProcessTime; + }; + + using JobIt = std::list::iterator; + class AsyncNavMeshUpdater { public: @@ -67,31 +82,6 @@ namespace DetourNavigator void reportStats(unsigned int frameNumber, osg::Stats& stats) const; private: - struct Job - { - osg::Vec3f mAgentHalfExtents; - std::weak_ptr mNavMeshCacheItem; - TilePosition mChangedTile; - unsigned mTryNumber; - ChangeType mChangeType; - int mDistanceToPlayer; - int mDistanceToOrigin; - std::chrono::steady_clock::time_point mProcessTime; - - std::tuple getPriority() const - { - return std::make_tuple(mProcessTime, mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin); - } - - friend inline bool operator <(const Job& lhs, const Job& rhs) - { - return lhs.getPriority() < rhs.getPriority(); - } - }; - - using Jobs = std::deque; - using Pushed = std::map>; - std::reference_wrapper mSettings; std::reference_wrapper mRecastMeshManager; std::reference_wrapper mOffMeshConnectionsManager; @@ -100,29 +90,30 @@ namespace DetourNavigator std::condition_variable mHasJob; std::condition_variable mDone; std::condition_variable mProcessed; - Jobs mJobs; + std::list mJobs; + std::deque mWaiting; std::map> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded>> mProcessingTiles; std::map> mLastUpdates; std::set> mPresentTiles; - std::map mThreadsQueues; + std::map> mThreadsQueues; std::vector mThreads; void process() noexcept; bool processJob(const Job& job); - std::optional getNextJob(); + JobIt getNextJob(); - std::optional getJob(Jobs& jobs, bool changeLastUpdate); + JobIt getJob(std::deque& jobs, bool changeLastUpdate); - void postThreadJob(Job&& job, Jobs& queue); + void postThreadJob(JobIt job, std::deque& queue); void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; - void repost(Job&& job); + void repost(JobIt job); std::thread::id lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); @@ -130,13 +121,13 @@ namespace DetourNavigator inline std::size_t getTotalJobs() const; - inline std::size_t getTotalThreadJobsUnsafe() const; - void cleanupLastUpdates(); int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); void waitUntilAllJobsDone(); + + inline void removeJob(JobIt job); }; } From bfcc430822a1dbfc46dbba072ddde04b9eaa3599 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 Aug 2021 19:18:46 +0200 Subject: [PATCH 1200/2859] Use single map to store processing tiles --- .../detournavigator/asyncnavmeshupdater.cpp | 32 +++---------------- .../detournavigator/asyncnavmeshupdater.hpp | 2 +- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 1f7134dcea..5e785ea56f 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -445,23 +445,13 @@ namespace DetourNavigator return std::this_thread::get_id(); auto locked = mProcessingTiles.lock(); - - auto agent = locked->find(agentHalfExtents); - if (agent == locked->end()) - { - const auto threadId = std::this_thread::get_id(); - locked->emplace(agentHalfExtents, std::map({{changedTile, threadId}})); - return threadId; - } - - auto tile = agent->second.find(changedTile); - if (tile == agent->second.end()) + const auto tile = locked->find(std::make_tuple(agentHalfExtents, changedTile)); + if (tile == locked->end()) { const auto threadId = std::this_thread::get_id(); - agent->second.emplace(changedTile, threadId); + locked->emplace(std::tie(agentHalfExtents, changedTile), threadId); return threadId; } - return tile->second; } @@ -469,22 +459,8 @@ namespace DetourNavigator { if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) return; - auto locked = mProcessingTiles.lock(); - - auto agent = locked->find(agentHalfExtents); - if (agent == locked->end()) - return; - - auto tile = agent->second.find(changedTile); - if (tile == agent->second.end()) - return; - - agent->second.erase(tile); - - if (agent->second.empty()) - locked->erase(agent); - + locked->erase(std::tie(agentHalfExtents, changedTile)); if (locked->empty()) mProcessed.notify_all(); } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index ab4be0b8d1..3ad5acfb13 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -95,7 +95,7 @@ namespace DetourNavigator std::map> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; - Misc::ScopeGuarded>> mProcessingTiles; + Misc::ScopeGuarded, std::thread::id>> mProcessingTiles; std::map> mLastUpdates; std::set> mPresentTiles; std::map> mThreadsQueues; From a97b2ced27ecdf9bb774633d501c5528bd4b8765 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 Aug 2021 19:21:52 +0200 Subject: [PATCH 1201/2859] Use single map to store last updates --- .../detournavigator/asyncnavmeshupdater.cpp | 25 ++++++++----------- .../detournavigator/asyncnavmeshupdater.hpp | 2 +- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 5e785ea56f..ce5a3ea2e6 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -65,6 +65,11 @@ namespace const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {}); queue.insert(it, job); } + + auto getAgentAndTile(const Job& job) noexcept + { + return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile); + } } namespace DetourNavigator @@ -127,7 +132,7 @@ namespace DetourNavigator job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile); job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0}); job.mProcessTime = job.mChangeType == ChangeType::update - ? mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] + mSettings.get().mMinUpdateInterval + ? mLastUpdates[getAgentAndTile(job)] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); const JobIt it = mJobs.insert(mJobs.end(), std::move(job)); @@ -387,7 +392,7 @@ namespace DetourNavigator jobs.pop_front(); if (changeLastUpdate && job->mChangeType == ChangeType::update) - mLastUpdates[job->mAgentHalfExtents][job->mChangedTile] = now; + mLastUpdates[getAgentAndTile(*job)] = now; return job; } @@ -477,20 +482,12 @@ namespace DetourNavigator const std::lock_guard lock(mMutex); - for (auto agent = mLastUpdates.begin(); agent != mLastUpdates.end();) + for (auto it = mLastUpdates.begin(); it != mLastUpdates.end();) { - for (auto tile = agent->second.begin(); tile != agent->second.end();) - { - if (now - tile->second > mSettings.get().mMinUpdateInterval) - tile = agent->second.erase(tile); - else - ++tile; - } - - if (agent->second.empty()) - agent = mLastUpdates.erase(agent); + if (now - it->second > mSettings.get().mMinUpdateInterval) + it = mLastUpdates.erase(it); else - ++agent; + ++it; } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 3ad5acfb13..501212bd46 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -96,7 +96,7 @@ namespace DetourNavigator Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded, std::thread::id>> mProcessingTiles; - std::map> mLastUpdates; + std::map, std::chrono::steady_clock::time_point> mLastUpdates; std::set> mPresentTiles; std::map> mThreadsQueues; std::vector mThreads; From 8bca9eec80d5c7416448e5c47c0891cd73d05c6c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 Aug 2021 19:55:34 +0200 Subject: [PATCH 1202/2859] Use single set to store pushed tiles --- .../detournavigator/asyncnavmeshupdater.cpp | 21 +++++++------------ .../detournavigator/asyncnavmeshupdater.hpp | 2 +- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index ce5a3ea2e6..a82f8f7db2 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -29,14 +29,13 @@ namespace } int getMinDistanceTo(const TilePosition& position, int maxDistance, - const std::map>& tilesPerHalfExtents, + const std::set>& pushedTiles, const std::set>& presentTiles) { int result = maxDistance; - for (const auto& [halfExtents, tiles] : tilesPerHalfExtents) - for (const TilePosition& tile : tiles) - if (presentTiles.find(std::make_tuple(halfExtents, tile)) == presentTiles.end()) - result = std::min(result, getManhattanDistance(position, tile)); + for (const auto& [halfExtents, tile] : pushedTiles) + if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end()) + result = std::min(result, getManhattanDistance(position, tile)); return result; } @@ -120,7 +119,7 @@ namespace DetourNavigator for (const auto& changedTile : changedTiles) { - if (mPushed[agentHalfExtents].insert(changedTile.first).second) + if (mPushed.emplace(agentHalfExtents, changedTile.first).second) { Job job; @@ -367,13 +366,7 @@ namespace DetourNavigator if (owner == threadId) { - const auto it = mPushed.find(job->mAgentHalfExtents); - if (it != mPushed.end()) - { - it->second.erase(job->mChangedTile); - if (it->second.empty()) - mPushed.erase(it); - } + mPushed.erase(getAgentAndTile(*job)); return job; } @@ -427,7 +420,7 @@ namespace DetourNavigator const std::lock_guard lock(mMutex); - if (mPushed[job->mAgentHalfExtents].insert(job->mChangedTile).second) + if (mPushed.emplace(job->mAgentHalfExtents, job->mChangedTile).second) { ++job->mTryNumber; mWaiting.push_back(job); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 501212bd46..ac6a82a49d 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -92,7 +92,7 @@ namespace DetourNavigator std::condition_variable mProcessed; std::list mJobs; std::deque mWaiting; - std::map> mPushed; + std::set> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded, std::thread::id>> mProcessingTiles; From 21ce4fe63726670c7be807e1dc693f3a2d3219b2 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 Aug 2021 22:26:53 +0200 Subject: [PATCH 1203/2859] Use structured binding --- components/detournavigator/asyncnavmeshupdater.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index a82f8f7db2..3e9c81678a 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -117,19 +117,19 @@ namespace DetourNavigator for (JobIt job : mWaiting) job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); - for (const auto& changedTile : changedTiles) + for (const auto& [changedTile, changeType] : changedTiles) { - if (mPushed.emplace(agentHalfExtents, changedTile.first).second) + if (mPushed.emplace(agentHalfExtents, changedTile).second) { Job job; job.mAgentHalfExtents = agentHalfExtents; job.mNavMeshCacheItem = navMeshCacheItem; - job.mChangedTile = changedTile.first; + job.mChangedTile = changedTile; job.mTryNumber = 0; - job.mChangeType = changedTile.second; - job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile); - job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0}); + job.mChangeType = changeType; + job.mDistanceToPlayer = getManhattanDistance(changedTile, playerTile); + job.mDistanceToOrigin = getManhattanDistance(changedTile, TilePosition {0, 0}); job.mProcessTime = job.mChangeType == ChangeType::update ? mLastUpdates[getAgentAndTile(job)] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); From 09b1a2e3c67eaf2721782ba77c0fad978bad990b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Aug 2021 02:23:43 +0200 Subject: [PATCH 1204/2859] Make unchanging Job fields const --- .../detournavigator/asyncnavmeshupdater.cpp | 29 +++++++++++-------- .../detournavigator/asyncnavmeshupdater.hpp | 18 +++++++----- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 3e9c81678a..7fcfdf98c5 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -73,6 +73,19 @@ namespace namespace DetourNavigator { + Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr navMeshCacheItem, + const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::chrono::steady_clock::time_point processTime) + : mAgentHalfExtents(agentHalfExtents) + , mNavMeshCacheItem(std::move(navMeshCacheItem)) + , mChangedTile(changedTile) + , mProcessTime(processTime) + , mChangeType(changeType) + , mDistanceToPlayer(distanceToPlayer) + , mDistanceToOrigin(getManhattanDistance(changedTile, TilePosition {0, 0})) + { + } + AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager) : mSettings(settings) @@ -121,20 +134,12 @@ namespace DetourNavigator { if (mPushed.emplace(agentHalfExtents, changedTile).second) { - Job job; - - job.mAgentHalfExtents = agentHalfExtents; - job.mNavMeshCacheItem = navMeshCacheItem; - job.mChangedTile = changedTile; - job.mTryNumber = 0; - job.mChangeType = changeType; - job.mDistanceToPlayer = getManhattanDistance(changedTile, playerTile); - job.mDistanceToOrigin = getManhattanDistance(changedTile, TilePosition {0, 0}); - job.mProcessTime = job.mChangeType == ChangeType::update - ? mLastUpdates[getAgentAndTile(job)] + mSettings.get().mMinUpdateInterval + const auto processTime = changeType == ChangeType::update + ? mLastUpdates[std::tie(agentHalfExtents, changedTile)] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); - const JobIt it = mJobs.insert(mJobs.end(), std::move(job)); + const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, changedTile, + changeType, getManhattanDistance(changedTile, playerTile), processTime); if (playerTileChanged) mWaiting.push_back(it); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index ac6a82a49d..b770230673 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -55,14 +55,18 @@ namespace DetourNavigator struct Job { - osg::Vec3f mAgentHalfExtents; - std::weak_ptr mNavMeshCacheItem; - TilePosition mChangedTile; - unsigned mTryNumber; - ChangeType mChangeType; + const osg::Vec3f mAgentHalfExtents; + const std::weak_ptr mNavMeshCacheItem; + const TilePosition mChangedTile; + const std::chrono::steady_clock::time_point mProcessTime; + unsigned mTryNumber = 0; + const ChangeType mChangeType; int mDistanceToPlayer; - int mDistanceToOrigin; - std::chrono::steady_clock::time_point mProcessTime; + const int mDistanceToOrigin; + + Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr navMeshCacheItem, + const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::chrono::steady_clock::time_point processTime); }; using JobIt = std::list::iterator; From 3caf45807f571c44138211593a1f0461db22c139 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 29 Jun 2021 03:47:23 +0200 Subject: [PATCH 1205/2859] Use common implementation to filter hidden markers --- apps/openmw/mwrender/objectpaging.cpp | 4 ++-- apps/openmw/mwworld/scene.cpp | 6 ++---- components/misc/resourcehelpers.cpp | 6 ++++++ components/misc/resourcehelpers.hpp | 4 ++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index db81027d9b..b23f079888 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -510,8 +510,8 @@ namespace MWRender continue; } - if (ref.mRefID == "prisonmarker" || ref.mRefID == "divinemarker" || ref.mRefID == "templemarker" || ref.mRefID == "northmarker") - continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player + if (Misc::ResourceHelpers::isHiddenMarker(ref.mRefID)) + continue; int type = store.findStatic(ref.mRefID); std::string model = getModel(type, ref.mRefID, store); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3de4940a37..d1b06131b9 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -94,14 +94,12 @@ namespace std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) { + if (Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId())) + return {}; bool useAnim = ptr.getClass().useAnim(); std::string model = ptr.getClass().getModel(ptr); if (useAnim) model = Misc::ResourceHelpers::correctActorModelPath(model, vfs); - - const std::string &id = ptr.getCellRef().getRefId(); - if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") - model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player return model; } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 5cf4378b85..4d54baafc6 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -1,6 +1,7 @@ #include "resourcehelpers.hpp" #include +#include #include @@ -138,3 +139,8 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string &resP } return mdlname; } + +bool Misc::ResourceHelpers::isHiddenMarker(std::string_view id) +{ + return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; +} diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index fa50cce228..9e87954e9d 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -2,6 +2,7 @@ #define MISC_RESOURCEHELPERS_H #include +#include namespace VFS { @@ -23,6 +24,9 @@ namespace Misc std::string correctBookartPath(const std::string &resPath, int width, int height, const VFS::Manager* vfs); /// Use "xfoo.nif" instead of "foo.nif" if available std::string correctActorModelPath(const std::string &resPath, const VFS::Manager* vfs); + + /// marker objects that have a hardcoded function in the game logic, should be hidden from the player + bool isHiddenMarker(std::string_view id); } } From 8ac8d56e8c7f190b9f4f2ddbd5b659d3785d0864 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 8 Aug 2021 16:58:47 +0200 Subject: [PATCH 1206/2859] Mark TileCachedRecastMeshManager member functions as const where possible --- components/detournavigator/recastmeshmanager.cpp | 2 +- components/detournavigator/recastmeshmanager.hpp | 2 +- .../detournavigator/tilecachedrecastmeshmanager.cpp | 10 +++++----- .../detournavigator/tilecachedrecastmeshmanager.hpp | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index e4e5b76449..2eeb90a9b5 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -117,7 +117,7 @@ namespace DetourNavigator return result; } - std::shared_ptr RecastMeshManager::getMesh() + std::shared_ptr RecastMeshManager::getMesh() const { TileBounds tileBounds = mTileBounds; tileBounds.mMin /= mSettings.mRecastScaleFactor; diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index 1cd35ca467..e1c1567cb3 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -52,7 +52,7 @@ namespace DetourNavigator std::optional removeHeightfield(const osg::Vec2i& cellPosition); - std::shared_ptr getMesh(); + std::shared_ptr getMesh() const; bool isEmpty() const; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 2b8c85b8a3..8033ca89ac 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -190,11 +190,11 @@ namespace DetourNavigator return result; } - std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) + std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) const { const auto manager = [&] () -> std::shared_ptr { - const auto tiles = mTiles.lock(); + const auto tiles = mTiles.lockConst(); const auto it = tiles->find(tilePosition); if (it == tiles->end()) return nullptr; @@ -205,7 +205,7 @@ namespace DetourNavigator return manager->getMesh(); } - bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition) + bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition) const { return mTiles.lockConst()->count(tilePosition); } @@ -215,9 +215,9 @@ namespace DetourNavigator return mRevision; } - void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) + void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const { - const auto tiles = mTiles.lock(); + const auto tiles = mTiles.lockConst(); const auto it = tiles->find(tilePosition); if (it == tiles->end()) return; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f8cb5a8b0e..f10260a4dd 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -86,20 +86,20 @@ namespace DetourNavigator std::optional removeHeightfield(const osg::Vec2i& cellPosition); - std::shared_ptr getMesh(const TilePosition& tilePosition); + std::shared_ptr getMesh(const TilePosition& tilePosition) const; - bool hasTile(const TilePosition& tilePosition); + bool hasTile(const TilePosition& tilePosition) const; template - void forEachTile(Function&& function) + void forEachTile(Function&& function) const { - for (auto& [tilePosition, recastMeshManager] : *mTiles.lock()) + for (auto& [tilePosition, recastMeshManager] : *mTiles.lockConst()) function(tilePosition, *recastMeshManager); } std::size_t getRevision() const; - void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion); + void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const; private: using TilesMap = std::map>; From 05258ed644dbf8f9466fb3ea2e399781d8c85755 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 8 Aug 2021 17:59:34 +0200 Subject: [PATCH 1207/2859] Remove redundant TileCachedRecastMeshManager::hasTile function It's used only for tests. getMesh is a valid replacement. --- .../tilecachedrecastmeshmanager.cpp | 18 ++++++------------ .../tilecachedrecastmeshmanager.cpp | 5 ----- .../tilecachedrecastmeshmanager.hpp | 2 -- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 51580906ce..6209ec9c2a 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -38,12 +38,6 @@ namespace EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); } - TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, has_tile_for_empty_should_return_false) - { - TileCachedRecastMeshManager manager(mSettings); - EXPECT_FALSE(manager.hasTile(TilePosition(0, 0))); - } - TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero) { const TileCachedRecastMeshManager manager(mSettings); @@ -83,7 +77,7 @@ namespace ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) - ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); + ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) @@ -281,7 +275,7 @@ namespace ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); + ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) @@ -295,7 +289,7 @@ namespace ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0); + ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt) @@ -324,7 +318,7 @@ namespace ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_FALSE(manager.hasTile(TilePosition(x, y))); + ASSERT_EQ(manager.getMesh(TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) @@ -339,7 +333,7 @@ namespace ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0); + ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water) @@ -354,6 +348,6 @@ namespace ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); + ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); } } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 8033ca89ac..38314f08a5 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -205,11 +205,6 @@ namespace DetourNavigator return manager->getMesh(); } - bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition) const - { - return mTiles.lockConst()->count(tilePosition); - } - std::size_t TileCachedRecastMeshManager::getRevision() const { return mRevision; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f10260a4dd..f6bc40d668 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -88,8 +88,6 @@ namespace DetourNavigator std::shared_ptr getMesh(const TilePosition& tilePosition) const; - bool hasTile(const TilePosition& tilePosition) const; - template void forEachTile(Function&& function) const { From 54a676f2e35f9e15a2418eeeb8e5e0e6ec04c2c2 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 26 Jul 2021 20:18:01 +0200 Subject: [PATCH 1208/2859] Add functions to get length of recast type arrays To avoid duplicating same formulas in multiple places. --- .../detournavigator/navmeshtilescache.cpp | 39 +++++++------- .../detournavigator/preparednavmeshdata.hpp | 18 ++++--- .../preparednavmeshdatatuple.hpp | 17 ++++--- components/detournavigator/recast.hpp | 51 +++++++++++++++++++ 4 files changed, 90 insertions(+), 35 deletions(-) create mode 100644 components/detournavigator/recast.hpp diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index 9405bd7739..309266142f 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -48,11 +49,11 @@ namespace value.ch = 1.0f / (std::rand() % 999 + 1); value.borderSize = std::rand(); value.maxEdgeError = 1.0f / (std::rand() % 999 + 1); - generate(value.verts, 3 * value.nverts); - generate(value.polys, value.maxpolys * 2 * value.nvp); - generate(value.regs, value.maxpolys); - generate(value.flags, value.maxpolys); - generate(value.areas, value.maxpolys); + generate(value.verts, getVertsLength(value)); + generate(value.polys, getPolysLength(value)); + generate(value.regs, getRegsLength(value)); + generate(value.flags, getFlagsLength(value)); + generate(value.areas, getAreasLength(value)); } void generate(rcPolyMeshDetail& value, int size) @@ -60,9 +61,9 @@ namespace value.nmeshes = size; value.nverts = size; value.ntris = size; - generate(value.meshes, 4 * value.nmeshes); - generate(value.verts, 3 * value.nverts); - generate(value.tris, 4 * value.ntris); + generate(value.meshes, getMeshesLength(value)); + generate(value.verts, getVertsLength(value)); + generate(value.tris, getTrisLength(value)); } void generate(PreparedNavMeshData& value, int size) @@ -82,10 +83,10 @@ namespace } template - void clone(const T* src, T*& dst, int size) + void clone(const T* src, T*& dst, std::size_t size) { - dst = static_cast(permRecastAlloc(size * sizeof(T))); - std::memcpy(dst, src, static_cast(size) * sizeof(T)); + dst = static_cast(permRecastAlloc(static_cast(size) * sizeof(T))); + std::memcpy(dst, src, size * sizeof(T)); } void clone(const rcPolyMesh& src, rcPolyMesh& dst) @@ -100,11 +101,11 @@ namespace dst.ch = src.ch; dst.borderSize = src.borderSize; dst.maxEdgeError = src.maxEdgeError; - clone(src.verts, dst.verts, 3 * dst.nverts); - clone(src.polys, dst.polys, dst.maxpolys * 2 * dst.nvp); - clone(src.regs, dst.regs, dst.maxpolys); - clone(src.flags, dst.flags, dst.maxpolys); - clone(src.areas, dst.areas, dst.maxpolys); + clone(src.verts, dst.verts, getVertsLength(dst)); + clone(src.polys, dst.polys, getPolysLength(dst)); + clone(src.regs, dst.regs, getRegsLength(dst)); + clone(src.flags, dst.flags, getFlagsLength(dst)); + clone(src.areas, dst.areas, getAreasLength(dst)); } void clone(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst) @@ -112,9 +113,9 @@ namespace dst.nmeshes = src.nmeshes; dst.nverts = src.nverts; dst.ntris = src.ntris; - clone(src.meshes, dst.meshes, 4 * dst.nmeshes); - clone(src.verts, dst.verts, 3 * dst.nverts); - clone(src.tris, dst.tris, 4 * dst.ntris); + clone(src.meshes, dst.meshes, getMeshesLength(dst)); + clone(src.verts, dst.verts, getVertsLength(dst)); + clone(src.tris, dst.tris, getTrisLength(dst)); } std::unique_ptr clone(const PreparedNavMeshData& value) diff --git a/components/detournavigator/preparednavmeshdata.hpp b/components/detournavigator/preparednavmeshdata.hpp index 4a92265231..3566cfc71b 100644 --- a/components/detournavigator/preparednavmeshdata.hpp +++ b/components/detournavigator/preparednavmeshdata.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATA_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATA_H +#include "recast.hpp" + #include #include @@ -25,18 +27,18 @@ namespace DetourNavigator inline constexpr std::size_t getSize(const rcPolyMesh& value) noexcept { - return static_cast(3 * value.nverts) * sizeof(*value.verts) - + static_cast(value.maxpolys * 2 * value.nvp) * sizeof(*value.polys) - + static_cast(value.maxpolys) * sizeof(*value.regs) - + static_cast(value.maxpolys) * sizeof(*value.flags) - + static_cast(value.maxpolys) * sizeof(*value.areas); + return getVertsLength(value) * sizeof(*value.verts) + + getPolysLength(value) * sizeof(*value.polys) + + getRegsLength(value) * sizeof(*value.regs) + + getFlagsLength(value) * sizeof(*value.flags) + + getAreasLength(value) * sizeof(*value.areas); } inline constexpr std::size_t getSize(const rcPolyMeshDetail& value) noexcept { - return static_cast(4 * value.nmeshes) * sizeof(*value.meshes) - + static_cast(4 * value.ntris) * sizeof(*value.tris) - + static_cast(3 * value.nverts) * sizeof(*value.verts); + return getMeshesLength(value) * sizeof(*value.meshes) + + getVertsLength(value) * sizeof(*value.verts) + + getTrisLength(value) * sizeof(*value.tris); } inline constexpr std::size_t getSize(const PreparedNavMeshData& value) noexcept diff --git a/components/detournavigator/preparednavmeshdatatuple.hpp b/components/detournavigator/preparednavmeshdatatuple.hpp index 5b5a72f629..8ff1267370 100644 --- a/components/detournavigator/preparednavmeshdatatuple.hpp +++ b/components/detournavigator/preparednavmeshdatatuple.hpp @@ -3,6 +3,7 @@ #include "preparednavmeshdata.hpp" #include "ref.hpp" +#include "recast.hpp" #include @@ -13,11 +14,11 @@ namespace DetourNavigator constexpr auto makeTuple(const rcPolyMesh& v) noexcept { return std::tuple( - Span(v.verts, 3 * v.nverts), - Span(v.polys, v.maxpolys * 2 * v.nvp), - Span(v.regs, v.maxpolys), - Span(v.flags, v.maxpolys), - Span(v.areas, v.maxpolys), + Span(v.verts, getVertsLength(v)), + Span(v.polys, getPolysLength(v)), + Span(v.regs, getRegsLength(v)), + Span(v.flags, getFlagsLength(v)), + Span(v.areas, getAreasLength(v)), ArrayRef(v.bmin), ArrayRef(v.bmax), v.cs, @@ -30,9 +31,9 @@ namespace DetourNavigator constexpr auto makeTuple(const rcPolyMeshDetail& v) noexcept { return std::tuple( - Span(v.meshes, 4 * v.nmeshes), - Span(v.verts, 3 * v.nverts), - Span(v.tris, 4 * v.ntris) + Span(v.meshes, getMeshesLength(v)), + Span(v.verts, getVertsLength(v)), + Span(v.tris, getTrisLength(v)) ); } diff --git a/components/detournavigator/recast.hpp b/components/detournavigator/recast.hpp new file mode 100644 index 0000000000..c51f4b9279 --- /dev/null +++ b/components/detournavigator/recast.hpp @@ -0,0 +1,51 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H + +#include + +#include + +namespace DetourNavigator +{ + constexpr std::size_t getVertsLength(const rcPolyMesh& value) noexcept + { + return 3 * static_cast(value.nverts); + } + + constexpr std::size_t getPolysLength(const rcPolyMesh& value) noexcept + { + return 2 * static_cast(value.maxpolys * value.nvp); + } + + constexpr std::size_t getRegsLength(const rcPolyMesh& value) noexcept + { + return static_cast(value.maxpolys); + } + + constexpr std::size_t getFlagsLength(const rcPolyMesh& value) noexcept + { + return static_cast(value.maxpolys); + } + + constexpr std::size_t getAreasLength(const rcPolyMesh& value) noexcept + { + return static_cast(value.maxpolys); + } + + constexpr std::size_t getMeshesLength(const rcPolyMeshDetail& value) noexcept + { + return 4 * static_cast(value.nmeshes); + } + + constexpr std::size_t getVertsLength(const rcPolyMeshDetail& value) noexcept + { + return 3 * static_cast(value.nverts); + } + + constexpr std::size_t getTrisLength(const rcPolyMeshDetail& value) noexcept + { + return 4 * static_cast(value.ntris); + } +} + +#endif From 46fa3ce083f2f79ae5ba01439bffee9139113c23 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 10 Aug 2021 23:26:31 +0200 Subject: [PATCH 1209/2859] Fix flags length for rcPolyMesh recastnavigation documentation of rcPolyMesh is misleading. It says flags field length is maxpolys when actually it's allocated as npolys. --- components/detournavigator/recast.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/detournavigator/recast.hpp b/components/detournavigator/recast.hpp index c51f4b9279..4e9ab329b7 100644 --- a/components/detournavigator/recast.hpp +++ b/components/detournavigator/recast.hpp @@ -24,7 +24,7 @@ namespace DetourNavigator constexpr std::size_t getFlagsLength(const rcPolyMesh& value) noexcept { - return static_cast(value.maxpolys); + return static_cast(value.npolys); } constexpr std::size_t getAreasLength(const rcPolyMesh& value) noexcept From baf62f4922100edae3690b9f1543b803ab7b4bd4 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Fri, 13 Aug 2021 13:53:37 +0200 Subject: [PATCH 1210/2859] Don't save artifacts when using coverity --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5ed6c97da0..b756b97875 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -53,7 +53,7 @@ Coverity: variables: CC: gcc CXX: g++ - timeout: 8h + artifacts: Debian_GCC: extends: .Debian From b01ef2629c30c93a36590e45b29454c7dfdcda07 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Fri, 13 Aug 2021 13:59:57 +0200 Subject: [PATCH 1211/2859] Fix two Wreorder clang warnings --- apps/openmw/mwphysics/physicssystem.cpp | 2 +- components/resource/scenemanager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 846f80ec64..9f0208f1dc 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -991,13 +991,13 @@ namespace MWPhysics , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) , mFallHeight(0) + , mStuckFrames(0) , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) , mWasOnGround(actor.getOnGround()) , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) , mWaterCollision(waterCollision) , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) , mNeedLand(false) - , mStuckFrames(0) { } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 974ae6a7fd..33a9ab99a1 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -228,6 +228,7 @@ namespace Resource , mApplyLightingToEnvMaps(false) , mLightingMethod(SceneUtil::LightingMethod::FFP) , mConvertAlphaTestToAlphaToCoverage(false) + , mDepthFormat(0) , mInstanceCache(new MultiObjectCache) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) @@ -237,7 +238,6 @@ namespace Resource , mMaxAnisotropy(1) , mUnRefImageDataAfterApply(false) , mParticleSystemMask(~0u) - , mDepthFormat(0) { } From 713f612bdb7c2141256fcb5c5036cad545c05f08 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 13 Aug 2021 18:01:16 +0200 Subject: [PATCH 1212/2859] Partially revert !1046: the player is added before the scene exists, so we need to check again the grounded state, as it correctly was. --- apps/openmw/mwphysics/physicssystem.cpp | 3 --- apps/openmw/mwworld/scene.cpp | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 846f80ec64..abd4c79e7b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -712,9 +712,6 @@ namespace MWPhysics auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); - // check if Actor is on the ground or in the air - traceDown(ptr, ptr.getRefData().getPosition().asVec3(), 10.f); - mActors.emplace(ptr, std::move(actor)); } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3de4940a37..2b7877938f 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -504,6 +504,12 @@ namespace MWWorld const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + // The player is loaded before the scene and by default it is grounded, with the scene fully loaded, we validate and correct this. + if (player.mCell == cell) // Only run once, during initial cell load. + { + mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); + } + mNavigator.update(player.getRefData().getPosition().asVec3()); if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) From d40c18234a83a76005d27bded5b4de69973cdc07 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 14 Aug 2021 17:41:58 +0000 Subject: [PATCH 1213/2859] Double buffering the water's node position uniform --- apps/openmw/mwrender/water.cpp | 12 +++++++----- apps/openmw/mwrender/water.hpp | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index da26595c8c..7a8677e43a 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -544,6 +544,11 @@ osg::Node* Water::getRefractionNode() return mRefraction; } +osg::Vec3d Water::getPosition() const +{ + return mWaterNode->getPosition(); +} + void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); @@ -620,6 +625,7 @@ public: depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } + stateset->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWater->getPosition()))); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override @@ -632,6 +638,7 @@ public: stateset->setTextureAttributeAndModes(2, mRefraction->getColorTexture(cv), osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(3, mRefraction->getDepthTexture(cv), osg::StateAttribute::ON); } + stateset->getUniform("nodePosition")->set(osg::Vec3f(mWater->getPosition())); } private: @@ -728,11 +735,6 @@ void Water::changeCell(const MWWorld::CellStore* store) } if(mInterior != wasInterior && mReflection) mReflection->setInterior(mInterior); - - // create a new StateSet to prevent threading issues - osg::ref_ptr nodeStateSet (new osg::StateSet); - nodeStateSet->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWaterNode->getPosition()))); - mWaterNode->setStateSet(nodeStateSet); } void Water::setHeight(const float height) diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 8b6c5c83e5..870b8949c7 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -120,6 +120,8 @@ namespace MWRender osg::Node* getReflectionNode(); osg::Node* getRefractionNode(); + osg::Vec3d getPosition() const; + void processChangedSettings(const Settings::CategorySettingVector& settings); }; From 6cc71745acbb355ec9ad014391e31dc6539194f3 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 14 Aug 2021 14:50:33 -0700 Subject: [PATCH 1214/2859] ensure character preview is using standard depth --- apps/openmw/mwrender/characterpreview.cpp | 26 +++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 580451ff8f..5c805ca856 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -131,6 +131,29 @@ namespace MWRender newStateSet->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); newStateSet->addUniform(mNoAlphaUniform); } + if (SceneUtil::getReverseZ() && stateset->getAttribute(osg::StateAttribute::DEPTH)) + { + if (!newStateSet) + { + newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); + node.setStateSet(newStateSet); + } + // Setup standard depth ranges + osg::Depth* depth = static_cast(stateset->getAttribute(osg::StateAttribute::DEPTH)); + osg::ref_ptr newDepth = new osg::Depth(*depth); + switch (newDepth->getFunction()) + { + case osg::Depth::LESS: + newDepth->setFunction(osg::Depth::GREATER); + case osg::Depth::LEQUAL: + newDepth->setFunction(osg::Depth::GEQUAL); + case osg::Depth::GREATER: + newDepth->setFunction(osg::Depth::LESS); + case osg::Depth::GEQUAL: + newDepth->setFunction(osg::Depth::LEQUAL); + } + newStateSet->setAttribute(newDepth, osg::StateAttribute::ON); + } } traverse(node); } @@ -189,8 +212,7 @@ namespace MWRender stateset->setAttribute(defaultMat); stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(mCamera->getProjectionMatrix()))); - osg::ref_ptr depth = new osg::Depth; - stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + stateset->setAttributeAndModes(new osg::Depth, osg::StateAttribute::ON); SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); From ec871e6bf7d9feb2f5e99a25320e69c720464835 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 14 Aug 2021 23:15:11 +0200 Subject: [PATCH 1215/2859] Use shared_ptr instead of weak_ptr for actors handle inside the simulation The purpose of weak_ptr is to avoid performing the simulation on deleted Actor by promoting it to a shared_ptr via a lock() call. This clutter the code with a lot of branches, whereas the overwhelmingly common case is for the call to succeed. Since the simulation is decoupled from the engine state, we can use a shared_ptr instead of a weak_ptr. This allow us to ignore (ie not handle) the rarer case where the actor is delete from the scene. This means that the simulation will run for one frame more than before for each actor, whereas the rest of the engine will be ignorant of this. --- apps/openmw/mwphysics/mtphysics.cpp | 106 ++++++++++-------------- apps/openmw/mwphysics/mtphysics.hpp | 14 ++-- apps/openmw/mwphysics/physicssystem.cpp | 18 ++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- 4 files changed, 61 insertions(+), 79 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index e9bedcbc75..7363b9964a 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -207,12 +207,13 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. std::unique_lock lock(mSimulationMutex); + assert(actors.size() == actorsData.size()); double timeStart = mTimer->tick(); @@ -221,11 +222,8 @@ namespace MWPhysics { for (size_t i = 0; i < mActors.size(); ++i) { - if (auto actor = mActors[i].lock()) - { - updateMechanics(*actor, mActorsFrameData[i]); - updateActor(*actor, mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); - } + updateMechanics(*mActors[i], mActorsFrameData[i]); + updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); } if(mAdvanceSimulation) mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); @@ -238,8 +236,7 @@ namespace MWPhysics // init for (size_t i = 0; i < actors.size(); ++i) { - assert(actors[i].lock()); - actorsData[i].updatePosition(*actors[i].lock(), mCollisionWorld); + actorsData[i].updatePosition(*actors[i], mCollisionWorld); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; @@ -355,7 +352,7 @@ namespace MWPhysics mCollisionWorld->removeCollisionObject(collisionObject); } - void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr, bool immediate) + void PhysicsTaskScheduler::updateSingleAabb(std::shared_ptr ptr, bool immediate) { if (immediate || mNumThreads == 0) { @@ -368,20 +365,15 @@ namespace MWPhysics } } - bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2) + bool PhysicsTaskScheduler::getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2) { std::unique_lock lock(mLOSCacheMutex); - auto actorPtr1 = actor1.lock(); - auto actorPtr2 = actor2.lock(); - if (!actorPtr1 || !actorPtr2) - return false; - auto req = LOSRequest(actor1, actor2); auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req); if (result == mLOSCache.end()) { - req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get()); + req.mResult = hasLineOfSight(actor1.get(), actor2.get()); mLOSCache.push_back(req); return req.mResult; } @@ -412,31 +404,28 @@ namespace MWPhysics { std::scoped_lock lock(mUpdateAabbMutex); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), - [this](const std::weak_ptr& ptr) { updatePtrAabb(ptr); }); + [this](const std::shared_ptr& ptr) { updatePtrAabb(ptr); }); mUpdateAabb.clear(); } - void PhysicsTaskScheduler::updatePtrAabb(const std::weak_ptr& ptr) + void PhysicsTaskScheduler::updatePtrAabb(const std::shared_ptr& ptr) { - if (const auto p = ptr.lock()) + std::scoped_lock lock(mCollisionWorldMutex); + if (const auto actor = std::dynamic_pointer_cast(ptr)) { - std::scoped_lock lock(mCollisionWorldMutex); - if (const auto actor = std::dynamic_pointer_cast(p)) - { - actor->updateCollisionObjectPosition(); - mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); - } - else if (const auto object = std::dynamic_pointer_cast(p)) - { - object->commitPositionChange(); - mCollisionWorld->updateSingleAabb(object->getCollisionObject()); - } - else if (const auto projectile = std::dynamic_pointer_cast(p)) - { - projectile->commitPositionChange(); - mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); - } - }; + actor->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); + } + else if (const auto object = std::dynamic_pointer_cast(ptr)) + { + object->commitPositionChange(); + mCollisionWorld->updateSingleAabb(object->getCollisionObject()); + } + else if (const auto projectile = std::dynamic_pointer_cast(ptr)) + { + projectile->commitPositionChange(); + mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); + } } void PhysicsTaskScheduler::worker() @@ -452,11 +441,8 @@ namespace MWPhysics int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - if(const auto actor = mActors[job].lock()) - { - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); - MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); - } + MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); + MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); } mPostStepBarrier->wait([this] { afterPostStep(); }); @@ -465,10 +451,7 @@ namespace MWPhysics { while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - if(const auto actor = mActors[job].lock()) - { - handleFall(mActorsFrameData[job], mAdvanceSimulation); - } + handleFall(mActorsFrameData[job], mAdvanceSimulation); } refreshLOSCache(); @@ -481,15 +464,12 @@ namespace MWPhysics { for (size_t i = 0; i < mActors.size(); ++i) { - if(const auto actor = mActors[i].lock()) + if (mActors[i]->setPosition(mActorsFrameData[i].mPosition)) { - if (actor->setPosition(mActorsFrameData[i].mPosition)) - { - std::scoped_lock lock(mCollisionWorldMutex); - mActorsFrameData[i].mPosition = actor->getPosition(); // account for potential position change made by script - actor->updateCollisionObjectPosition(); - mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); - } + std::scoped_lock lock(mCollisionWorldMutex); + mActorsFrameData[i].mPosition = mActors[i]->getPosition(); // account for potential position change made by script + mActors[i]->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(mActors[i]->getCollisionObject()); } } } @@ -545,11 +525,9 @@ namespace MWPhysics for (size_t i = 0; i < mActors.size(); ++i) { - auto actor = mActors[i].lock(); - assert(actor); handleFall(mActorsFrameData[i], mAdvanceSimulation); - updateMechanics(*actor, mActorsFrameData[i]); - updateActor(*actor, mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); + updateMechanics(*mActors[i], mActorsFrameData[i]); + updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); } refreshLOSCache(); } @@ -583,6 +561,13 @@ namespace MWPhysics return (*it)->getUserPointer(); } + void PhysicsTaskScheduler::releaseSharedStates() + { + std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex); + mActors.clear(); + mUpdateAabb.clear(); + } + void PhysicsTaskScheduler::afterPreStep() { updateAabbs(); @@ -590,11 +575,8 @@ namespace MWPhysics return; for (size_t i = 0; i < mActors.size(); ++i) { - if (auto actor = mActors[i].lock()) - { - std::unique_lock lock(mCollisionWorldMutex); - MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld); - } + std::unique_lock lock(mCollisionWorldMutex); + MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld); } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index e758c557e2..e2cfccffb0 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -39,7 +39,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -53,11 +53,13 @@ namespace MWPhysics void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask); void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); void removeCollisionObject(btCollisionObject* collisionObject); - void updateSingleAabb(std::weak_ptr ptr, bool immediate=false); - bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); + void updateSingleAabb(std::shared_ptr ptr, bool immediate=false); + bool getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2); void debugDraw(); void* getUserPointer(const btCollisionObject* object) const; + void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler() + private: void syncComputation(); void worker(); @@ -66,7 +68,7 @@ namespace MWPhysics bool hasLineOfSight(const Actor* actor1, const Actor* actor2); void refreshLOSCache(); void updateAabbs(); - void updatePtrAabb(const std::weak_ptr& ptr); + void updatePtrAabb(const std::shared_ptr& ptr); void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::tuple calculateStepConfig(float timeAccum) const; void afterPreStep(); @@ -74,7 +76,7 @@ namespace MWPhysics void afterPostSim(); std::unique_ptr mWorldFrameData; - std::vector> mActors; + std::vector> mActors; std::vector mActorsFrameData; std::unordered_set mCollisionObjects; float mDefaultPhysicsDt; @@ -83,7 +85,7 @@ namespace MWPhysics btCollisionWorld* mCollisionWorld; MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; - std::set, std::owner_less>> mUpdateAabb; + std::set> mUpdateAabb; // TODO: use std::experimental::flex_barrier or std::barrier once it becomes a thing std::unique_ptr mPreStepBarrier; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 846f80ec64..d881285e64 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -153,6 +153,7 @@ namespace MWPhysics if (mWaterCollisionObject) mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get()); + mTaskScheduler->releaseSharedStates(); mHeightFields.clear(); mObjects.clear(); mActors.clear(); @@ -373,15 +374,12 @@ namespace MWPhysics bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const { - const auto getWeakPtr = [&](const MWWorld::ConstPtr &ptr) -> std::weak_ptr - { - const auto found = mActors.find(ptr); - if (found != mActors.end()) - return { found->second }; - return {}; - }; + const auto it1 = mActors.find(actor1); + const auto it2 = mActors.find(actor2); + if (it1 == mActors.end() || it2 == mActors.end()) + return false; - return mTaskScheduler->getLineOfSight(getWeakPtr(actor1), getWeakPtr(actor2)); + return mTaskScheduler->getLineOfSight(it1->second, it2->second); } bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) @@ -769,9 +767,9 @@ namespace MWPhysics actor->setVelocity(osg::Vec3f()); } - std::pair>, std::vector> PhysicsSystem::prepareFrameData(bool willSimulate) + std::pair>, std::vector> PhysicsSystem::prepareFrameData(bool willSimulate) { - std::pair>, std::vector> framedata; + std::pair>, std::vector> framedata; framedata.first.reserve(mActors.size()); framedata.second.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index f1b6bc147a..b20c8f88e1 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -259,7 +259,7 @@ namespace MWPhysics void updateWater(); - std::pair>, std::vector> prepareFrameData(bool willSimulate); + std::pair>, std::vector> prepareFrameData(bool willSimulate); osg::ref_ptr mUnrefQueue; From ed72f3335be61cdec9d3ecb2559607b60df37e65 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 15 Aug 2021 08:02:00 -0700 Subject: [PATCH 1216/2859] silence compiler warning --- apps/openmw/mwrender/characterpreview.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 5c805ca856..9db1330923 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -145,12 +145,18 @@ namespace MWRender { case osg::Depth::LESS: newDepth->setFunction(osg::Depth::GREATER); + break; case osg::Depth::LEQUAL: newDepth->setFunction(osg::Depth::GEQUAL); + break; case osg::Depth::GREATER: newDepth->setFunction(osg::Depth::LESS); + break; case osg::Depth::GEQUAL: newDepth->setFunction(osg::Depth::LEQUAL); + break; + default: + break; } newStateSet->setAttribute(newDepth, osg::StateAttribute::ON); } From 0792bb212a9d3b53164379c319119aec7f6cf843 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 15 Aug 2021 18:57:33 +0200 Subject: [PATCH 1217/2859] Use an emplace instead of an insert --- apps/openmw/mwmechanics/actors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c5b46c3764..083b67640c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1609,7 +1609,7 @@ namespace MWMechanics MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (!anim) return; - mActors.insert(std::make_pair(ptr, new Actor(ptr, anim))); + mActors.emplace(ptr, new Actor(ptr, anim)); CharacterController* ctrl = mActors[ptr]->getCharacterController(); if (updateImmediately) From 7a015d24c66c3328b4e896f03d4623c3611761fb Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 15 Aug 2021 19:50:28 +0200 Subject: [PATCH 1218/2859] Sprinkle some const-ref --- apps/openmw/mwbase/world.hpp | 6 +++--- apps/openmw/mwgui/bookpage.cpp | 2 +- apps/openmw/mwgui/containeritemmodel.cpp | 2 +- apps/openmw/mwgui/containeritemmodel.hpp | 2 +- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwgui/inventoryitemmodel.cpp | 2 +- apps/openmw/mwgui/inventoryitemmodel.hpp | 2 +- apps/openmw/mwgui/itemmodel.cpp | 2 +- apps/openmw/mwgui/itemmodel.hpp | 4 ++-- apps/openmw/mwgui/travelwindow.cpp | 2 +- apps/openmw/mwgui/travelwindow.hpp | 2 +- apps/openmw/mwrender/actoranimation.cpp | 2 +- apps/openmw/mwrender/actoranimation.hpp | 2 +- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.hpp | 2 +- apps/openmw/mwrender/screenshotmanager.cpp | 2 +- apps/openmw/mwrender/screenshotmanager.hpp | 2 +- apps/openmw/mwrender/weaponanimation.cpp | 2 +- apps/openmw/mwrender/weaponanimation.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 6 +++--- apps/openmw/mwworld/worldimp.hpp | 6 +++--- components/shader/shadervisitor.cpp | 2 +- components/widgets/sharedstatebutton.cpp | 2 +- components/widgets/sharedstatebutton.hpp | 2 +- 28 files changed, 35 insertions(+), 35 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index bd806c7cac..1dbb2b96f6 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -289,14 +289,14 @@ namespace MWBase virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) = 0; ///< @return an updated Ptr - virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) = 0; + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void rotateObject(const MWWorld::Ptr& ptr, const osg::Vec3f& rot, RotationFlags flags = RotationFlag_inverseOrder) = 0; - virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; + virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) = 0; ///< Place an object. Makes a copy of the Ptr. virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) = 0; @@ -624,7 +624,7 @@ namespace MWBase virtual void setPlayerTraveling(bool traveling) = 0; virtual bool isPlayerTraveling() const = 0; - virtual void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) = 0; + virtual void rotateWorldObject (const MWWorld::Ptr& ptr, const osg::Quat& rotate) = 0; /// Return terrain height at \a worldPos position. virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0; diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 2f12b83572..2bfec8105b 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -108,7 +108,7 @@ struct TypesetBookImpl : TypesetBook virtual ~TypesetBookImpl () {} - Range addContent (BookTypesetter::Utf8Span text) + Range addContent (const BookTypesetter::Utf8Span &text) { Contents::iterator i = mContents.insert (mContents.end (), Content (text.first, text.second)); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index a1e42ba45f..81dde1c059 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -86,7 +86,7 @@ size_t ContainerItemModel::getItemCount() return mItems.size(); } -ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) +ItemModel::ModelIndex ContainerItemModel::getIndex (const ItemStack& item) { size_t i = 0; for (ItemStack& itemStack : mItems) diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index df2eebea22..11fed06913 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -28,7 +28,7 @@ namespace MWGui bool onTakeItem(const MWWorld::Ptr &item, int count) override; ItemStack getItem (ModelIndex index) override; - ModelIndex getIndex (ItemStack item) override; + ModelIndex getIndex (const ItemStack &item) override; size_t getItemCount() override; MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 7e999bd582..5a7b5a9590 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -53,7 +53,7 @@ namespace MWGui } void removeItem (const ItemStack& item, size_t count) override { throw std::runtime_error("removeItem not implemented"); } - ModelIndex getIndex (ItemStack item) override { throw std::runtime_error("getIndex not implemented"); } + ModelIndex getIndex (const ItemStack &item) override { throw std::runtime_error("getIndex not implemented"); } void update() override {} size_t getItemCount() override { return 0; } ItemStack getItem (ModelIndex index) override { throw std::runtime_error("getItem not implemented"); } diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 91d2fdbf76..f96cfd865e 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -34,7 +34,7 @@ size_t InventoryItemModel::getItemCount() return mItems.size(); } -ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item) +ItemModel::ModelIndex InventoryItemModel::getIndex (const ItemStack& item) { size_t i = 0; for (ItemStack& itemStack : mItems) diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index 85e2a5a8d2..87d874c946 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -12,7 +12,7 @@ namespace MWGui InventoryItemModel (const MWWorld::Ptr& actor); ItemStack getItem (ModelIndex index) override; - ModelIndex getIndex (ItemStack item) override; + ModelIndex getIndex (const ItemStack &item) override; size_t getItemCount() override; bool onTakeItem(const MWWorld::Ptr &item, int count) override; diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 3db74916e5..7f74d3fb5a 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -129,7 +129,7 @@ namespace MWGui return -1; } - ItemModel::ModelIndex ProxyItemModel::getIndex (ItemStack item) + ItemModel::ModelIndex ProxyItemModel::getIndex (const ItemStack& item) { return mSourceModel->getIndex(item); } diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 6ead0f8d1f..d538a040db 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -55,7 +55,7 @@ namespace MWGui virtual size_t getItemCount() = 0; /// Returns an invalid index if the item was not found - virtual ModelIndex getIndex (ItemStack item) = 0; + virtual ModelIndex getIndex (const ItemStack &item) = 0; /// Rebuild the item model, this will invalidate existing model indices virtual void update() = 0; @@ -98,7 +98,7 @@ namespace MWGui MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem (const ItemStack& item, size_t count) override; - ModelIndex getIndex (ItemStack item) override; + ModelIndex getIndex (const ItemStack &item) override; /// @note Takes ownership of the passed pointer. void setSourceModel(ItemModel* sourceModel); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index ed7a74b95f..22e3a55566 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -44,7 +44,7 @@ namespace MWGui mSelect->getHeight()); } - void TravelWindow::addDestination(const std::string& name, ESM::Position pos, bool interior) + void TravelWindow::addDestination(const std::string& name, const ESM::Position &pos, bool interior) { int price; diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 00b7db7305..dd970ee10e 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -31,7 +31,7 @@ namespace MWGui void onCancelButtonClicked(MyGUI::Widget* _sender); void onTravelButtonClick(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void addDestination(const std::string& name, ESM::Position pos, bool interior); + void addDestination(const std::string& name, const ESM::Position& pos, bool interior); void clearDestinations(); int mCurrentY; diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 59bc327656..4109d61e8c 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -84,7 +84,7 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st return PartHolderPtr(new PartHolder(instance)); } -std::string ActorAnimation::getShieldMesh(MWWorld::ConstPtr shield) const +std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield) const { std::string mesh = shield.getClass().getModel(shield); const ESM::Armor *armor = shield.get()->mBase; diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index e149e44148..e6bba48d56 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -45,7 +45,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener virtual void updateHolsteredWeapon(bool showHolsteredWeapons); virtual void updateHolsteredShield(bool showCarriedLeft); virtual void updateQuiver(); - virtual std::string getShieldMesh(MWWorld::ConstPtr shield) const; + virtual std::string getShieldMesh(const MWWorld::ConstPtr& shield) const; virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor); virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 07b1deeff3..591e666d01 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1839,7 +1839,7 @@ namespace MWRender mRootController = addRotateController("bip01"); } - RotateController* Animation::addRotateController(std::string bone) + RotateController* Animation::addRotateController(const std::string &bone) { auto iter = getNodeMap().find(bone); if (iter == getNodeMap().end()) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 24478439c1..ffcd9b3260 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -271,7 +271,7 @@ protected: float mLegsYawRadians; float mBodyPitchRadians; - RotateController* addRotateController(std::string bone); + RotateController* addRotateController(const std::string& bone); bool mHasMagicEffects; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 12ed88e55d..d142996f40 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -551,7 +551,7 @@ void NpcAnimation::updateNpcBase() mWeaponAnimationTime->updateStartTime(); } -std::string NpcAnimation::getShieldMesh(MWWorld::ConstPtr shield) const +std::string NpcAnimation::getShieldMesh(const MWWorld::ConstPtr& shield) const { std::string mesh = shield.getClass().getModel(shield); const ESM::Armor *armor = shield.get()->mBase; diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 7e55001daf..768ac01622 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -106,7 +106,7 @@ private: protected: void addControllers() override; bool isArrowAttached() const override; - std::string getShieldMesh(MWWorld::ConstPtr shield) const override; + std::string getShieldMesh(const MWWorld::ConstPtr& shield) const override; public: /** diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 96a8a9450a..b9ea5daacf 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -328,7 +328,7 @@ namespace MWRender mRootNode->removeChild(camera); } - void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) + void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, const osg::Matrixd& cameraTransform) { osg::ref_ptr rttCamera (new osg::Camera); float nearClip = Settings::Manager::getFloat("near clip", "Camera"); diff --git a/apps/openmw/mwrender/screenshotmanager.hpp b/apps/openmw/mwrender/screenshotmanager.hpp index 373fe3be84..094a4a20f4 100644 --- a/apps/openmw/mwrender/screenshotmanager.hpp +++ b/apps/openmw/mwrender/screenshotmanager.hpp @@ -37,7 +37,7 @@ namespace MWRender void traversalsAndWait(unsigned int frame); void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); - void makeCubemapScreenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); + void makeCubemapScreenshot(osg::Image* image, int w, int h, const osg::Matrixd &cameraTransform=osg::Matrixd()); }; } diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 0c2a11466b..fb92a4980a 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -60,7 +60,7 @@ WeaponAnimation::~WeaponAnimation() } -void WeaponAnimation::attachArrow(MWWorld::Ptr actor) +void WeaponAnimation::attachArrow(const MWWorld::Ptr& actor) { const MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); MWWorld::ConstContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index d02107333e..333dccc915 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -34,7 +34,7 @@ namespace MWRender virtual ~WeaponAnimation(); /// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op. - void attachArrow(MWWorld::Ptr actor); + void attachArrow(const MWWorld::Ptr &actor); void detachArrow(MWWorld::Ptr actor); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index fec8ca77b0..81e1248e6d 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -29,7 +29,7 @@ namespace MWWorld { std::vector mObjects; - bool operator() (MWWorld::Ptr ptr) + bool operator() (const MWWorld::Ptr& ptr) { mObjects.push_back (ptr); return true; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index d1b06131b9..aac69bab80 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -86,7 +86,7 @@ namespace return rot; } - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation) + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, const osg::Quat &rotation) { if (ptr.getRefData().getBaseNode()) rendering.rotateObject(ptr, rotation); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 54b15e4412..bc28c2eb7b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1256,7 +1256,7 @@ namespace MWWorld return moveObject(ptr, cell, position, movePhysics); } - MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) + MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) { auto* actor = mPhysics->getActor(ptr); osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; @@ -1410,7 +1410,7 @@ namespace MWWorld } } - void World::rotateWorldObject (const Ptr& ptr, osg::Quat rotate) + void World::rotateWorldObject (const Ptr& ptr, const osg::Quat& rotate) { if(ptr.getRefData().getBaseNode() != nullptr) { @@ -1425,7 +1425,7 @@ namespace MWWorld } } - MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) + MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) { return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 11769372f8..32fc9b101a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -224,7 +224,7 @@ namespace MWWorld void setWaterHeight(const float height) override; - void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) override; + void rotateWorldObject (const MWWorld::Ptr& ptr, const osg::Quat& rotate) override; bool toggleWater() override; bool toggleWorld() override; @@ -376,7 +376,7 @@ namespace MWWorld MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) override; ///< @return an updated Ptr - MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) override; + MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) override; ///< @return an updated Ptr void scaleObject (const Ptr& ptr, float scale) override; @@ -387,7 +387,7 @@ namespace MWWorld /// \param adjust indicates rotation should be set or adjusted void rotateObject (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags = MWBase::RotationFlag_inverseOrder) override; - MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) override; + MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) override; ///< Place an object. Makes a copy of the Ptr. MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) override; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 67a8ec86ad..26511d4654 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -56,7 +56,7 @@ namespace Shader bool hasUniform(const std::string& name) { return mUniforms.count(name); } bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } - bool hasAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { return mAttributes.count(typeMemberPair); } + bool hasAttribute(const osg::StateAttribute::TypeMemberPair &typeMemberPair) { return mAttributes.count(typeMemberPair); } bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); } const std::set& getAttributes() { return mAttributes; } diff --git a/components/widgets/sharedstatebutton.cpp b/components/widgets/sharedstatebutton.cpp index f4456275bd..91436a1b96 100644 --- a/components/widgets/sharedstatebutton.cpp +++ b/components/widgets/sharedstatebutton.cpp @@ -18,7 +18,7 @@ namespace Gui } } - void SharedStateButton::shareStateWith(ButtonGroup shared) + void SharedStateButton::shareStateWith(const ButtonGroup &shared) { mSharedWith = shared; } diff --git a/components/widgets/sharedstatebutton.hpp b/components/widgets/sharedstatebutton.hpp index 42c6424b2c..d643bf2430 100644 --- a/components/widgets/sharedstatebutton.hpp +++ b/components/widgets/sharedstatebutton.hpp @@ -34,7 +34,7 @@ namespace Gui bool _setState(const std::string &_value); public: - void shareStateWith(ButtonGroup shared); + void shareStateWith(const ButtonGroup &shared); /// @note The ButtonGroup connection will be destroyed when any widget in the group gets destroyed. static void createButtonGroup(ButtonGroup group); From ff2a51a484a4e99edb0ae76658ed4e9438d89cce Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 15 Aug 2021 19:57:20 +0200 Subject: [PATCH 1219/2859] Use an initialization list in WrapAroundOperator's constructor --- apps/openmw/mwrender/sky.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 05ce0f1560..c1b79843a7 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1350,11 +1350,9 @@ void SkyManager::setCamera(osg::Camera *camera) class WrapAroundOperator : public osgParticle::Operator { public: - WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange): osgParticle::Operator() + WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange): osgParticle::Operator(), + mCamera(camera), mWrapRange(wrapRange), mHalfWrapRange(mWrapRange / 2.0) { - mCamera = camera; - mWrapRange = wrapRange; - mHalfWrapRange = mWrapRange / 2.0; mPreviousCameraPosition = getCameraPosition(); } From 9622def79c058b6d3da3ef80fc759d7abe4b0535 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 16 Aug 2021 12:10:45 +0200 Subject: [PATCH 1220/2859] Don't build everything in clang-tidy --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 059716fe29..8d82d6ed79 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,7 @@ Clang_Tidy: script: - CI/before_script.linux.sh - cd build - - cmake --build . -- -j $(nproc) + - cmake --build . -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter variables: CC: clang CXX: clang++ From 5793f5cf187fb67baf7f23c17ddd93b703164b97 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 15 Aug 2021 20:11:55 +0200 Subject: [PATCH 1221/2859] Sprinkle a couple of std::move and a const --- components/resource/keyframemanager.cpp | 2 +- components/sceneutil/mwshadowtechnique.cpp | 2 +- components/sceneutil/shadowsbin.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 69986fbcd9..77c31d9ad7 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -40,7 +40,7 @@ namespace Resource } osg::ref_ptr mergedAnimationTrack = new Resource::Animation; - std::string animationName = animation->getName(); + const std::string animationName = animation->getName(); mergedAnimationTrack->setName(animationName); const osgAnimation::ChannelList& channels = animation->getChannels(); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 19222eda23..95ff8f2b01 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -2141,7 +2141,7 @@ struct ConvexHull finalEdges.push_back(edge); } - _edges = finalEdges; + _edges = std::move(finalEdges); } void transform(const osg::Matrixd& m) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index abc1fa8b44..14dbc14178 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -221,7 +221,7 @@ void ShadowsBin::sortImplementation() } if (!noTestRoot->_leaves.empty()) newList.push_back(noTestRoot); - _stateGraphList = newList; + _stateGraphList = std::move(newList); } } From 39cd679ca96128f057f65f4b9df53a708133c58e Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 15 Aug 2021 18:18:32 +0200 Subject: [PATCH 1222/2859] Minor factorisation in apps/openmw/mwmechanics/actors.cpp --- apps/openmw/mwmechanics/actors.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c5b46c3764..78ec6ed55c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -60,22 +60,24 @@ int getBoundItemSlot (const std::string& itemId) static std::map boundItemsMap; if (boundItemsMap.empty()) { - std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->mValue.getString(); + const auto& store = MWBase::Environment::get().getWorld()->getStore().get(); + + std::string boundId = store.find("sMagicBoundBootsID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->mValue.getString(); + boundId = store.find("sMagicBoundCuirassID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->mValue.getString(); + boundId = store.find("sMagicBoundLeftGauntletID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(); + boundId = store.find("sMagicBoundRightGauntletID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->mValue.getString(); + boundId = store.find("sMagicBoundHelmID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->mValue.getString(); + boundId = store.find("sMagicBoundShieldID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; } From 47a841d3b7fb700fc254150f73b378b166e0cb69 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 17 Aug 2021 12:29:28 +1000 Subject: [PATCH 1223/2859] Fix/workaround for Issue #3246 OpenMW save file assumes the presence of NPC/Creature data but the vanilla save file provides only the delta changes in most situations. The base data are not available without loading all the relevant dependency content files. Duplicating that code in the ESSImporter is not desirable. Ideally a flag should be set but that will mean a change in the save file format. For a minor change such as this doing so seems like an overkill. So a temporary workaround is introduced where the gold carried by the NPC/Creature is used as an indicator as the lack of ACDT data. --- apps/essimporter/converter.cpp | 5 +++++ apps/openmw/mwclass/creature.cpp | 25 +++++++++++++++-------- apps/openmw/mwclass/npc.cpp | 13 +++++++++--- apps/openmw/mwmechanics/creaturestats.cpp | 20 +++++++++++------- apps/openmw/mwmechanics/creaturestats.hpp | 2 ++ 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 320f7224bc..874b936baf 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -2,6 +2,7 @@ #include #include +#include // INT_MIN #include @@ -369,6 +370,8 @@ namespace ESSImport // from the ESM with default values if (cellref.mHasACDT) convertACDT(cellref.mACDT, objstate.mCreatureStats); + else + objstate.mCreatureStats.mGoldPool = INT_MIN; // HACK: indicates no ACDT if (cellref.mHasACSC) convertACSC(cellref.mACSC, objstate.mCreatureStats); convertNpcData(cellref, objstate.mNpcStats); @@ -410,6 +413,8 @@ namespace ESSImport // from the ESM with default values if (cellref.mHasACDT) convertACDT(cellref.mACDT, objstate.mCreatureStats); + else + objstate.mCreatureStats.mGoldPool = INT_MIN; // HACK: indicates no ACDT if (cellref.mHasACSC) convertACSC(cellref.mACSC, objstate.mCreatureStats); convertCREC(crecIt->second, objstate); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index d03ea70a87..8a9f33e1bd 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -748,26 +748,35 @@ namespace MWClass if (!state.mHasCustomState) return; + const ESM::CreatureState& creatureState = state.asCreatureState(); + if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::unique_ptr data (new CreatureCustomData); - - if (hasInventoryStore(ptr)) - data->mContainerStore = std::make_unique(); + // FIXME: the use of mGoldPool can be replaced with another flag the next time + // the save file format is changed + if (creatureState.mCreatureStats.mGoldPool == INT_MIN) + ensureCustomData(ptr); else - data->mContainerStore = std::make_unique(); + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::unique_ptr data (new CreatureCustomData); - ptr.getRefData().setCustomData (std::move(data)); + if (hasInventoryStore(ptr)) + data->mContainerStore = std::make_unique(); + else + data->mContainerStore = std::make_unique(); + + ptr.getRefData().setCustomData (std::move(data)); + } } } else ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); - const ESM::CreatureState& creatureState = state.asCreatureState(); + customData.mContainerStore->readState (creatureState.mInventory); bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get()->mBase->mId); if(spellsInitialised) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 736ae537fe..030c533b23 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1291,19 +1291,26 @@ namespace MWClass if (!state.mHasCustomState) return; + const ESM::NpcState& npcState = state.asNpcState(); + if (state.mVersion > 0) { if (!ptr.getRefData().getCustomData()) { - // Create a CustomData, but don't fill it from ESM records (not needed) - ptr.getRefData().setCustomData(std::make_unique()); + // FIXME: the use of mGoldPool can be replaced with another flag the next time + // the save file format is changed + if (npcState.mCreatureStats.mGoldPool == INT_MIN) + ensureCustomData(ptr); + else + // Create a CustomData, but don't fill it from ESM records (not needed) + ptr.getRefData().setCustomData(std::make_unique()); } } else ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); - const ESM::NpcState& npcState = state.asNpcState(); + customData.mInventoryStore.readState (npcState.mInventory); customData.mNpcStats.readState (npcState.mNpcStats); bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get()->mBase->mId); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 1ff44fcbb0..8d99664f27 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -1,6 +1,7 @@ #include "creaturestats.hpp" #include +#include #include #include @@ -562,22 +563,27 @@ namespace MWMechanics void CreatureStats::readState (const ESM::CreatureStats& state) { - for (int i=0; i Date: Tue, 17 Aug 2021 12:31:06 +1000 Subject: [PATCH 1224/2859] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c53c8401f3..cb461154d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.48.0 ------ + Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes From b1fb3e2313f8fcd99de15c193e8eba1c9ab7a7d2 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 17 Aug 2021 13:53:14 +1000 Subject: [PATCH 1225/2859] Add missing includes. --- apps/openmw/mwclass/creature.cpp | 2 ++ apps/openmw/mwclass/npc.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 8a9f33e1bd..50dafba39b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,5 +1,7 @@ #include "creature.hpp" +#include // INT_MIN + #include #include #include diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 030c533b23..41cf3beed5 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,6 +1,7 @@ #include "npc.hpp" #include +#include // INT_MIN #include #include From d63eb3325f990a6745ec33477d7c114e0f202ab0 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Tue, 17 Aug 2021 17:45:50 -0700 Subject: [PATCH 1226/2859] fix coverity warning and build on some osg --- apps/openmw/mwrender/postprocessor.cpp | 20 ++++++++++---------- apps/openmw/mwrender/postprocessor.hpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 12 ++++++------ components/sceneutil/util.cpp | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index eff7d46892..f846a05872 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -43,14 +43,15 @@ namespace void operator()(osg::Node* node, osg::NodeVisitor* nv) override { - osgUtil::RenderStage* renderStage = nv->asCullVisitor()->getCurrentRenderStage(); + osgUtil::CullVisitor* cv = static_cast(nv); + osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); unsigned int frame = nv->getTraversalNumber(); if (frame != mLastFrameNumber) { mLastFrameNumber = frame; - MWRender::PostProcessor* postProcessor = dynamic_cast(nv->asCullVisitor()->getCurrentCamera()->getUserData()); + MWRender::PostProcessor* postProcessor = dynamic_cast(cv->getCurrentCamera()->getUserData()); if (!postProcessor) { @@ -99,8 +100,12 @@ namespace MWRender PostProcessor::PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode) : mViewer(viewer) , mRootNode(new osg::Group) + , mDepthFormat(GL_DEPTH_COMPONENT24) , mRendering(rendering) { + if (!SceneUtil::getReverseZ()) + return; + osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); unsigned int contextID = gc->getState()->getContextID(); osg::GLExtensions* ext = gc->getState()->get(); @@ -128,7 +133,6 @@ namespace MWRender // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no // benefits if no floating point depth formats are supported. - mDepthFormat = GL_DEPTH_COMPONENT24; Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; return; } @@ -137,7 +141,7 @@ namespace MWRender int height = viewer->getCamera()->getViewport()->height(); createTexturesAndCamera(width, height); - resize(width, height, true); + resize(width, height); mRootNode->addChild(mHUDCamera); mRootNode->addChild(rootNode); @@ -154,7 +158,7 @@ namespace MWRender mViewer->getCamera()->setUserData(this); } - void PostProcessor::resize(int width, int height, bool init) + void PostProcessor::resize(int width, int height) { mDepthTex->setTextureSize(width, height); mSceneTex->setTextureSize(width, height); @@ -170,7 +174,7 @@ namespace MWRender // When MSAA is enabled we must first render to a render buffer, then // blit the result to the FBO which is either passed to the main frame // buffer for display or used as the entry point for a post process chain. - if (samples > 0) + if (samples > 1) { mMsaaFbo = new osg::FrameBufferObject; osg::ref_ptr colorRB = new osg::RenderBuffer(width, height, mSceneTex->getInternalFormat(), samples); @@ -179,12 +183,8 @@ namespace MWRender mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthRB)); } - if (init) - return; - mViewer->getCamera()->resize(width, height); mHUDCamera->resize(width, height); - mRendering.updateProjectionMatrix(); } diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index ad922c1cbc..f93f3a5b66 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -26,7 +26,7 @@ namespace MWRender int getDepthFormat() { return mDepthFormat; } - void resize(int width, int height, bool init=false); + void resize(int width, int height); private: void createTexturesAndCamera(int width, int height); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5ffc54f3f8..381a2e5f62 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -431,6 +431,12 @@ namespace MWRender mGroundcoverWorld->setActiveGrid(osg::Vec4i(0, 0, 0, 0)); } + mStateUpdater = new StateUpdater; + sceneRoot->addUpdateCallback(mStateUpdater); + + mSharedUniformStateUpdater = new SharedUniformStateUpdater; + rootNode->addUpdateCallback(mSharedUniformStateUpdater); + mPostProcessor = new PostProcessor(*this, viewer, mRootNode); resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat()); @@ -476,12 +482,6 @@ namespace MWRender source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); - mStateUpdater = new StateUpdater; - sceneRoot->addUpdateCallback(mStateUpdater); - - mSharedUniformStateUpdater = new SharedUniformStateUpdater; - rootNode->addUpdateCallback(mSharedUniformStateUpdater); - osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING; if (!Settings::Manager::getBool("small feature culling", "Camera")) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 14bfd35cb4..007a6637a8 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -194,7 +194,7 @@ public: void operator()(osg::Node* node, osg::NodeVisitor* nv) override { - osgUtil::RenderStage* renderStage = nv->asCullVisitor()->getCurrentRenderStage(); + osgUtil::RenderStage* renderStage = static_cast(nv)->getCurrentRenderStage(); renderStage->setMultisampleResolveFramebufferObject(mFbo); renderStage->setFrameBufferObject(mMsaaFbo); From 9112c65afcfb8c9826a6a285ae72941fa94f14a7 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Aug 2021 19:52:08 +0200 Subject: [PATCH 1227/2859] Use pathgrid path when destination is closer to different graph component node Partially revert d863267d5cb5f4062937f86c37af3b0c8f9479cf to restore 0.46 behaviour for pathgrid based pathfinding. --- apps/openmw/mwmechanics/pathfinding.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 2f8e830434..0e704cd466 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -206,9 +206,6 @@ namespace MWMechanics endPointInLocalCoords, startNode); - if (!endNode.second) - return; - // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); @@ -279,7 +276,8 @@ namespace MWMechanics // unreachable pathgrid point. // // The AI routines will have to deal with such situations. - *out++ = endPoint; + if (endNode.second) + *out++ = endPoint; } float PathFinder::getZAngleToNext(float x, float y) const From fea4fb6e692ddeb76e357a282414c3c5523a4ae9 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Aug 2021 22:46:14 +0200 Subject: [PATCH 1228/2859] Make AiPursue path destination to be as close as possible to target Even when target is not reachable actor will try to run there either because target navmesh polygon is selected within extended area or because partial path is built to the closest possible polygon. --- apps/openmw/mwmechanics/aicombat.cpp | 6 +- apps/openmw/mwmechanics/aipackage.cpp | 5 +- apps/openmw/mwmechanics/aipackage.hpp | 3 +- apps/openmw/mwmechanics/aipursue.cpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 8 +- apps/openmw/mwmechanics/pathfinding.cpp | 32 ++-- apps/openmw/mwmechanics/pathfinding.hpp | 16 +- .../detournavigator/navigator.cpp | 144 +++++++++++++++--- components/detournavigator/debug.hpp | 1 + .../findrandompointaroundcircle.cpp | 2 +- components/detournavigator/findsmoothpath.cpp | 11 +- components/detournavigator/findsmoothpath.hpp | 24 ++- components/detournavigator/navigator.hpp | 5 +- components/detournavigator/status.hpp | 3 + 14 files changed, 204 insertions(+), 58 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index f6123c12c0..4b490d7bc8 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -269,7 +269,8 @@ namespace MWMechanics const auto navigatorFlags = getNavigatorFlags(actor); const auto areaCosts = getAreaCosts(actor); const auto pathGridGraph = getPathGridGraph(actor.getCell()); - mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts); + mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, halfExtents, + navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full); if (!mPathFinder.isPathConstructed()) { @@ -281,7 +282,8 @@ namespace MWMechanics if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack) { // If the point is close enough, try to find a path to that point. - mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts); + mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, halfExtents, + navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full); if (mPathFinder.isPathConstructed()) { // If path to that point is found use it as custom destination. diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8ad9447514..46e4729a8a 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -88,7 +88,8 @@ void MWMechanics::AiPackage::reset() mObstacleCheck.clear(); } -bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance) +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, + float destTolerance, float endTolerance, PathType pathType) { const Misc::TimerStatus timerStatus = mReaction.update(duration); @@ -133,7 +134,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& { const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), - pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); + pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, pathType); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 6d8af0d92b..5270b74166 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -129,7 +129,8 @@ namespace MWMechanics protected: /// Handles path building and shortcutting with obstacles avoiding /** \return If the actor has arrived at his destination **/ - bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f); + bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, + float destTolerance = 0.0f, float endTolerance = 0.0f, PathType pathType = PathType::Full); /// Check if there aren't any obstacles along the path to make shortcut possible /// If a shortcut is possible then path will be cleared and filled with the destination point. diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 05605529ad..2d896ddbdc 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -53,7 +53,7 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte const float pathTolerance = 100.f; // check the true distance in case the target is far away in Z-direction - bool reached = pathTo(actor, dest, duration, pathTolerance) && + bool reached = pathTo(actor, dest, duration, pathTolerance, (actorPos - dest).length(), PathType::Partial) && std::abs(dest.z() - actorPos.z()) < pathTolerance; if (reached) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 24fdff7065..19365bcb08 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -201,8 +201,10 @@ namespace MWMechanics else { const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + constexpr float endTolerance = 0; mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), - getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor), getAreaCosts(actor)); + getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor), getAreaCosts(actor), + endTolerance, PathType::Full); } if (mPathFinder.isPathConstructed()) @@ -361,11 +363,13 @@ namespace MWMechanics if (isAreaOccupiedByOtherActor(actor, mDestination)) continue; + constexpr float endTolerance = 0; + if (isWaterCreature || isFlyingCreature) mPathFinder.buildStraightPath(mDestination); else mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents, navigatorFlags, - areaCosts); + areaCosts, endTolerance, PathType::Full); if (mPathFinder.isPathConstructed()) { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 2f8e830434..c61b64f411 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -364,13 +364,13 @@ namespace MWMechanics void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, - const DetourNavigator::AreaCosts& areaCosts) + const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType) { mPath.clear(); // If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, - areaCosts, std::back_inserter(mPath)); + areaCosts, endTolerance, pathType, std::back_inserter(mPath)); if (status != DetourNavigator::Status::Success) mPath.clear(); @@ -383,7 +383,8 @@ namespace MWMechanics void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, + PathType pathType) { mPath.clear(); mCell = cell; @@ -392,7 +393,8 @@ namespace MWMechanics if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor)) { - status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)); + status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, + endTolerance, pathType, std::back_inserter(mPath)); if (status != DetourNavigator::Status::Success) mPath.clear(); } @@ -400,7 +402,7 @@ namespace MWMechanics if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty()) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, - flags | DetourNavigator::Flag_usePathgrid, areaCosts, std::back_inserter(mPath)); + flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, std::back_inserter(mPath)); if (status != DetourNavigator::Status::Success) mPath.clear(); } @@ -416,12 +418,17 @@ namespace MWMechanics DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, - const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out) + const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType, + std::back_insert_iterator> out) { const auto world = MWBase::Environment::get().getWorld(); const auto stepSize = getPathStepSize(actor); const auto navigator = world->getNavigator(); - const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, areaCosts, out); + const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, areaCosts, + endTolerance, out); + + if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath) + return DetourNavigator::Status::Success; if (status != DetourNavigator::Status::Success) { @@ -449,8 +456,9 @@ namespace MWMechanics const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); std::deque prePath; auto prePathInserter = std::back_inserter(prePath); + const float endTolerance = 0; const auto status = navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, areaCosts, - prePathInserter); + endTolerance, prePathInserter); if (status == DetourNavigator::Status::NavMeshNotFound) return; @@ -475,7 +483,8 @@ namespace MWMechanics void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts) + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, + PathType pathType) { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto maxDistance = std::min( @@ -485,8 +494,9 @@ namespace MWMechanics const auto startToEnd = endPoint - startPoint; const auto distance = startToEnd.length(); if (distance <= maxDistance) - return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, halfExtents, flags, areaCosts); + return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, halfExtents, flags, areaCosts, + endTolerance, pathType); const auto end = startPoint + startToEnd * maxDistance / distance; - buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts); + buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts, endTolerance, pathType); } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 0ada08d3fb..f0a5040334 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -70,6 +70,12 @@ namespace MWMechanics // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY); + enum class PathType + { + Full, + Partial, + }; + class PathFinder { public: @@ -93,18 +99,20 @@ namespace MWMechanics void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, - const DetourNavigator::AreaCosts& areaCosts); + const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType); void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, + PathType pathType); void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts); + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, + PathType pathType); /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, @@ -212,7 +220,7 @@ namespace MWMechanics [[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, - const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType, std::back_insert_iterator> out); }; } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 35fde5b657..62d3f9de04 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -46,6 +46,7 @@ namespace const osg::Vec2i mCellPosition {0, 0}; const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); const osg::Vec3f mShift {0, 0, 0}; + const float mEndTolerance = 0; DetourNavigatorNavigatorTest() : mPlayerPosition(0, 0, 0) @@ -135,7 +136,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::NavMeshNotFound); EXPECT_EQ(mPath, std::deque()); } @@ -143,7 +144,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) { mNavigator->addAgent(mAgentHalfExtents); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::StartPolygonNotFound); } @@ -152,7 +153,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::StartPolygonNotFound); } @@ -172,7 +173,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204.0000152587890625, 204, 1.99998295307159423828125), @@ -219,7 +221,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), @@ -252,7 +255,8 @@ namespace mPath.clear(); mOut = std::back_inserter(mPath); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), @@ -301,7 +305,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), @@ -337,7 +342,8 @@ namespace mPath.clear(); mOut = std::back_inserter(mPath); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), @@ -393,7 +399,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.999981403350830078125), @@ -479,7 +486,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99997997283935546875), @@ -530,7 +538,8 @@ namespace mEnd.x() = 0; mEnd.z() = 300; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(0, 204, 185.33331298828125), @@ -574,7 +583,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -619,7 +628,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mOut), + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -664,7 +673,8 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(0, 204, -98.000030517578125), @@ -712,7 +722,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), @@ -764,7 +775,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), @@ -858,7 +870,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 1.99998295307159423828125), @@ -1006,7 +1019,8 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); EXPECT_THAT(mPath, ElementsAre( Vec3fEq(-204, 204, 101.99999237060546875), @@ -1033,4 +1047,98 @@ namespace Vec3fEq(204, -204, 101.99999237060546875) )) << mPath; } + + TEST_F(DetourNavigatorNavigatorTest, for_not_reachable_destination_find_path_should_provide_partial_path) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + + CollisionShapeInstance compound(std::make_unique()); + compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), + new btBoxShape(btVector3(200, 200, 1000))); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); + + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::PartialPath); + + EXPECT_THAT(mPath, ElementsAre( + Vec3fEq(-204, 204, -2.666739940643310546875), + Vec3fEq(-193.730682373046875, 177.59320068359375, -3.9535934925079345703125), + Vec3fEq(-183.4613800048828125, 151.1864166259765625, -5.240451335906982421875), + Vec3fEq(-173.1920623779296875, 124.77962493896484375, -6.527309417724609375), + Vec3fEq(-162.922760009765625, 98.37282562255859375, -7.814167022705078125), + Vec3fEq(-152.6534423828125, 71.96602630615234375, -9.898590087890625), + Vec3fEq(-142.384124755859375, 45.559230804443359375, -17.641445159912109375), + Vec3fEq(-132.1148223876953125, 19.152431488037109375, -25.3843059539794921875), + Vec3fEq(-121.8455047607421875, -7.254369258880615234375, -27.97742462158203125), + Vec3fEq(-111.57619476318359375, -33.66117095947265625, -16.974590301513671875), + Vec3fEq(-101.30689239501953125, -60.06797027587890625, -5.9717559814453125), + Vec3fEq(-91.0375823974609375, -86.47476959228515625, -2.6667339801788330078125), + Vec3fEq(-80.76827239990234375, -112.88156890869140625, -2.6667339801788330078125), + Vec3fEq(-70.49897003173828125, -139.2883758544921875, -2.6667339801788330078125), + Vec3fEq(-60.229663848876953125, -165.6951751708984375, -2.6667339801788330078125), + Vec3fEq(-49.96035003662109375, -192.1019744873046875, -2.6667339801788330078125), + Vec3fEq(-45.333343505859375, -204, -2.6667339801788330078125) + )) << mPath; + } + + TEST_F(DetourNavigatorNavigatorTest, end_tolerance_should_extent_available_destinations) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + + CollisionShapeInstance compound(std::make_unique()); + compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), + new btBoxShape(btVector3(100, 100, 1000))); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); + + const float endTolerance = 1000.0f; + + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, mOut), + Status::Success); + + EXPECT_THAT(mPath, ElementsAre( + Vec3fEq(-204, 204, -2.666739940643310546875), + Vec3fEq(-188.745635986328125, 180.1236114501953125, -4.578275203704833984375), + Vec3fEq(-173.49127197265625, 156.247222900390625, -6.489814281463623046875), + Vec3fEq(-158.2369232177734375, 132.370819091796875, -8.4013538360595703125), + Vec3fEq(-142.9825592041015625, 108.49443817138671875, -10.31289386749267578125), + Vec3fEq(-127.7281951904296875, 84.6180419921875, -17.4810466766357421875), + Vec3fEq(-112.47383880615234375, 60.7416534423828125, -27.6026020050048828125), + Vec3fEq(-97.21947479248046875, 36.865261077880859375, -37.724163055419921875), + Vec3fEq(-81.965118408203125, 12.98886966705322265625, -47.84572601318359375), + Vec3fEq(-66.71075439453125, -10.887523651123046875, -46.691577911376953125), + Vec3fEq(-51.45639801025390625, -34.763916015625, -32.085445404052734375), + Vec3fEq(-36.202037811279296875, -58.640308380126953125, -28.5217914581298828125), + Vec3fEq(-20.947673797607421875, -82.5167083740234375, -32.16143035888671875), + Vec3fEq(-5.693310260772705078125, -106.393096923828125, -35.8010711669921875), + Vec3fEq(9.56105327606201171875, -130.2694854736328125, -29.6399688720703125), + Vec3fEq(24.8154163360595703125, -154.1458740234375, -17.6428318023681640625), + Vec3fEq(40.0697784423828125, -178.0222625732421875, -10.46006107330322265625), + Vec3fEq(55.3241424560546875, -201.8986663818359375, -3.297139644622802734375), + Vec3fEq(56.66666412353515625, -204, -2.6667373180389404296875) + )) << mPath; + } } diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index a17eec16a7..2128f96be4 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -34,6 +34,7 @@ namespace DetourNavigator switch (value) { OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(Success) + OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(PartialPath) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(NavMeshNotFound) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(StartPolygonNotFound) OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_STATUS_MESSAGE(EndPolygonNotFound) diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index ed73dca61a..a3407a61c3 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -19,7 +19,7 @@ namespace DetourNavigator dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); - dtPolyRef startRef = findNearestPolyExpanding(navMeshQuery, queryFilter, start, halfExtents); + dtPolyRef startRef = findNearestPoly(navMeshQuery, queryFilter, start, halfExtents * 4); if (startRef == 0) return std::optional(); diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp index 6598263398..14fe696f10 100644 --- a/components/detournavigator/findsmoothpath.cpp +++ b/components/detournavigator/findsmoothpath.cpp @@ -146,16 +146,13 @@ namespace DetourNavigator return result; } - dtPolyRef findNearestPolyExpanding(const dtNavMeshQuery& query, const dtQueryFilter& filter, + dtPolyRef findNearestPoly(const dtNavMeshQuery& query, const dtQueryFilter& filter, const osg::Vec3f& center, const osg::Vec3f& halfExtents) { dtPolyRef ref = 0; - for (int i = 0; i < 3; ++i) - { - const dtStatus status = query.findNearestPoly(center.ptr(), (halfExtents * (1 << i)).ptr(), &filter, &ref, nullptr); - if (!dtStatusFailed(status) && ref != 0) - break; - } + const dtStatus status = query.findNearestPoly(center.ptr(), halfExtents.ptr(), &filter, &ref, nullptr); + if (!dtStatusSucceed(status)) + return 0; return ref; } } diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index db2219b2cd..8eb0bf9f34 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -99,7 +99,7 @@ namespace DetourNavigator return dtStatusSucceed(status); } - dtPolyRef findNearestPolyExpanding(const dtNavMeshQuery& query, const dtQueryFilter& filter, + dtPolyRef findNearestPoly(const dtNavMeshQuery& query, const dtQueryFilter& filter, const osg::Vec3f& center, const osg::Vec3f& halfExtents); struct MoveAlongSurfaceResult @@ -256,7 +256,7 @@ namespace DetourNavigator template Status findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const float stepSize, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, - const Settings& settings, OutputIterator& out) + const Settings& settings, float endTolerance, OutputIterator& out) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) @@ -269,11 +269,15 @@ namespace DetourNavigator queryFilter.setAreaCost(AreaType_pathgrid, areaCosts.mPathgrid); queryFilter.setAreaCost(AreaType_ground, areaCosts.mGround); - dtPolyRef startRef = findNearestPolyExpanding(navMeshQuery, queryFilter, start, halfExtents); + constexpr float polyDistanceFactor = 4; + const osg::Vec3f polyHalfExtents = halfExtents * polyDistanceFactor; + + const dtPolyRef startRef = findNearestPoly(navMeshQuery, queryFilter, start, polyHalfExtents); if (startRef == 0) return Status::StartPolygonNotFound; - dtPolyRef endRef = findNearestPolyExpanding(navMeshQuery, queryFilter, end, halfExtents); + const dtPolyRef endRef = findNearestPoly(navMeshQuery, queryFilter, end, + polyHalfExtents + osg::Vec3f(endTolerance, endTolerance, endTolerance)); if (endRef == 0) return Status::EndPolygonNotFound; @@ -283,12 +287,18 @@ namespace DetourNavigator if (!polygonPath) return Status::FindPathOverPolygonsFailed; - if (polygonPath->empty() || polygonPath->back() != endRef) + if (polygonPath->empty()) return Status::Success; + const bool partialPath = polygonPath->back() != endRef; auto outTransform = OutputTransformIterator(out, settings); - return makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, std::move(*polygonPath), - settings.mMaxSmoothPathSize, outTransform); + const Status smoothStatus = makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, + std::move(*polygonPath), settings.mMaxSmoothPathSize, outTransform); + + if (smoothStatus != Status::Success) + return smoothStatus; + + return partialPath ? Status::PartialPath : Status::Success; } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 1a78b602cd..d2e77892b9 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -173,13 +173,14 @@ namespace DetourNavigator * @param end path at given point. * @param includeFlags setup allowed surfaces for actor to walk. * @param out the beginning of the destination range. + * @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents * @return Output iterator to the element in the destination range, one past the last element of found path. * Equal to out if no path is found. */ template Status findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const DetourNavigator::AreaCosts& areaCosts, - OutputIterator& out) const + float endTolerance, OutputIterator& out) const { static_assert( std::is_same< @@ -194,7 +195,7 @@ namespace DetourNavigator const auto settings = getSettings(); return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, out); + toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, endTolerance, out); } /** diff --git a/components/detournavigator/status.hpp b/components/detournavigator/status.hpp index 3715489acc..5f01f04758 100644 --- a/components/detournavigator/status.hpp +++ b/components/detournavigator/status.hpp @@ -6,6 +6,7 @@ namespace DetourNavigator enum class Status { Success, + PartialPath, NavMeshNotFound, StartPolygonNotFound, EndPolygonNotFound, @@ -21,6 +22,8 @@ namespace DetourNavigator { case Status::Success: return "success"; + case Status::PartialPath: + return "partial path is found"; case Status::NavMeshNotFound: return "navmesh is not found"; case Status::StartPolygonNotFound: From a6c7fcd43691d8bec1138a489db00b0e512409c2 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 18 Aug 2021 15:46:25 -0700 Subject: [PATCH 1229/2859] don't pingpong depth function on character preview update --- apps/openmw/mwrender/characterpreview.cpp | 54 +++++++++++++---------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 9db1330923..44b7b2f457 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -133,32 +133,40 @@ namespace MWRender } if (SceneUtil::getReverseZ() && stateset->getAttribute(osg::StateAttribute::DEPTH)) { - if (!newStateSet) - { - newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); - node.setStateSet(newStateSet); - } - // Setup standard depth ranges + bool depthModified = false; osg::Depth* depth = static_cast(stateset->getAttribute(osg::StateAttribute::DEPTH)); - osg::ref_ptr newDepth = new osg::Depth(*depth); - switch (newDepth->getFunction()) + depth->getUserValue("depthModified", depthModified); + + if (!depthModified) { - case osg::Depth::LESS: - newDepth->setFunction(osg::Depth::GREATER); - break; - case osg::Depth::LEQUAL: - newDepth->setFunction(osg::Depth::GEQUAL); - break; - case osg::Depth::GREATER: - newDepth->setFunction(osg::Depth::LESS); - break; - case osg::Depth::GEQUAL: - newDepth->setFunction(osg::Depth::LEQUAL); - break; - default: - break; + if (!newStateSet) + { + newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); + node.setStateSet(newStateSet); + } + // Setup standard depth ranges + osg::ref_ptr newDepth = new osg::Depth(*depth); + + switch (newDepth->getFunction()) + { + case osg::Depth::LESS: + newDepth->setFunction(osg::Depth::GREATER); + break; + case osg::Depth::LEQUAL: + newDepth->setFunction(osg::Depth::GEQUAL); + break; + case osg::Depth::GREATER: + newDepth->setFunction(osg::Depth::LESS); + break; + case osg::Depth::GEQUAL: + newDepth->setFunction(osg::Depth::LEQUAL); + break; + default: + break; + } + newStateSet->setAttribute(newDepth, osg::StateAttribute::ON); + newDepth->setUserValue("depthModified", true); } - newStateSet->setAttribute(newDepth, osg::StateAttribute::ON); } } traverse(node); From 1d925154f256c09276b1e0b9247f3d385b713c73 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 10 Aug 2021 18:00:49 +1000 Subject: [PATCH 1230/2859] Do not store (or worse, overwrite) empty PGRD records. Should resolve Issue #6209. --- apps/openmw/mwworld/store.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 125239e9a3..5b223544b0 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -866,6 +866,10 @@ namespace MWWorld pathgrid.load(esm, isDeleted); + // deal with MODs that have empty pathgrid records (Issue #6209) + if (pathgrid.mPoints.empty() || pathgrid.mEdges.empty()) + return RecordId("", isDeleted); + // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. From 8244ba8957403c6a6b0e0981b97547f9a8fbadc8 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 10 Aug 2021 19:39:46 +1000 Subject: [PATCH 1231/2859] Erase old record instead (i.e. assume an empty Pathgrid record was placed in purpose) --- apps/openmw/mwworld/store.cpp | 38 +++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 5b223544b0..957a01faf6 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -866,10 +866,6 @@ namespace MWWorld pathgrid.load(esm, isDeleted); - // deal with MODs that have empty pathgrid records (Issue #6209) - if (pathgrid.mPoints.empty() || pathgrid.mEdges.empty()) - return RecordId("", isDeleted); - // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. @@ -877,6 +873,40 @@ namespace MWWorld // A proper fix should be made for future versions of the file format. bool interior = pathgrid.mData.mX == 0 && pathgrid.mData.mY == 0 && mCells->search(pathgrid.mCell) != nullptr; + // deal with MODs that have empty pathgrid records (Issue #6209) + // we assume that these records are empty on purpose (i.e. to remove old pathgrid on an updated cell) + if (pathgrid.mPoints.empty() || pathgrid.mEdges.empty()) + { + std::string contentfile = esm.getContext().filename; + size_t pos = contentfile.find_last_of("/\\"); + if (pos != std::string::npos) + contentfile = contentfile.substr(pos+1); + + if (interior) + { + Interior::iterator it = mInt.find(pathgrid.mCell); + if (it != mInt.end()) + { + mInt.erase(it); + + Log(Debug::Warning) << "Warning: Empty pathgrid overwriting cell '" + << pathgrid.mCell << "' : " << contentfile; + } + } + else + { + Exterior::iterator it = mExt.find(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY)); + if (it != mExt.end()) + { + mExt.erase(it); + Log(Debug::Warning) << "Warning: Empty pathgrid overwriting cell (" + << pathgrid.mData.mX << ", " << pathgrid.mData.mY << ") : " << contentfile; + } + } + + return RecordId("", isDeleted); + } + // Try to overwrite existing record if (interior) { From 802a202455487d878156dbc89a7cb9df34a19676 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 19 Aug 2021 12:45:09 +1000 Subject: [PATCH 1232/2859] Remove warning log messages as they are more suitable for OpenCS. --- apps/openmw/mwworld/store.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 957a01faf6..8faa86597f 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -873,35 +873,21 @@ namespace MWWorld // A proper fix should be made for future versions of the file format. bool interior = pathgrid.mData.mX == 0 && pathgrid.mData.mY == 0 && mCells->search(pathgrid.mCell) != nullptr; - // deal with MODs that have empty pathgrid records (Issue #6209) + // deal with mods that have empty pathgrid records (Issue #6209) // we assume that these records are empty on purpose (i.e. to remove old pathgrid on an updated cell) if (pathgrid.mPoints.empty() || pathgrid.mEdges.empty()) { - std::string contentfile = esm.getContext().filename; - size_t pos = contentfile.find_last_of("/\\"); - if (pos != std::string::npos) - contentfile = contentfile.substr(pos+1); - if (interior) { Interior::iterator it = mInt.find(pathgrid.mCell); if (it != mInt.end()) - { mInt.erase(it); - - Log(Debug::Warning) << "Warning: Empty pathgrid overwriting cell '" - << pathgrid.mCell << "' : " << contentfile; - } } else { Exterior::iterator it = mExt.find(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY)); if (it != mExt.end()) - { mExt.erase(it); - Log(Debug::Warning) << "Warning: Empty pathgrid overwriting cell (" - << pathgrid.mData.mX << ", " << pathgrid.mData.mY << ") : " << contentfile; - } } return RecordId("", isDeleted); From bd3ef506cd65f34c9a75024c92b2b205377473c6 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 19 Aug 2021 19:30:01 +1000 Subject: [PATCH 1233/2859] Empty Pathgrid record is considered as the modder's intention to delete any existing record. --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/idcollection.cpp | 43 ++++++++++++++++++++++++ apps/opencs/model/world/idcollection.hpp | 4 +++ apps/opencs/model/world/pathgrid.hpp | 2 +- 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 apps/opencs/model/world/idcollection.cpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 88c4233c9c..4265490afb 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -19,7 +19,7 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel - actoradapter + actoradapter idcollection ) diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp new file mode 100644 index 0000000000..d06a47e32b --- /dev/null +++ b/apps/opencs/model/world/idcollection.cpp @@ -0,0 +1,43 @@ +#include "idcollection.hpp" + +namespace CSMWorld +{ + template<> + int IdCollection >::load (ESM::ESMReader& reader, bool base) + { + Pathgrid record; + bool isDeleted = false; + + loadRecord (record, reader, isDeleted); + + std::string id = IdAccessor().getId (record); + int index = this->searchId (id); + + if (record.mPoints.empty() || record.mEdges.empty()) + isDeleted = true; + + if (isDeleted) + { + if (index==-1) + { + // deleting a record that does not exist + // ignore it for now + /// \todo report the problem to the user + return -1; + } + + if (base) + { + this->removeRows (index, 1); + return -1; + } + + std::unique_ptr > baseRecord(new Record(this->getRecord(index))); + baseRecord->mState = RecordBase::State_Deleted; + this->setRecord(index, std::move(baseRecord)); + return index; + } + + return load (record, base, index); + } +} diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 9f834d8fae..bbc49f18c1 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -5,6 +5,7 @@ #include "collection.hpp" #include "land.hpp" +#include "pathgrid.hpp" namespace CSMWorld { @@ -153,6 +154,9 @@ namespace CSMWorld return true; } + + template<> + int IdCollection >::load(ESM::ESMReader& reader, bool base); } #endif diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index 22d01b0710..ce74d419e4 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -20,7 +20,7 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells); + void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection >& cells); void load (ESM::ESMReader &esm, bool &isDeleted); }; } From 277a1268c0b6f5979dcfbcc199e28160fbb2b6a8 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Aug 2021 23:23:57 +0200 Subject: [PATCH 1234/2859] Avoid access to removed map item To fix ASAN warning: =11799==ERROR: AddressSanitizer: heap-use-after-free on address 0x60c000436058 at pc 0x55c90acccaa8 bp 0x7f787eeac830 sp 0x7f787eeac820 READ of size 8 at 0x60c000436058 thread T18 #0 0x55c90acccaa7 in std::operator==(std::_Deque_iterator, std::_List_iterator&, std::_List_iterator*> const&, std::_Deque_iterator, std::_List_iterator&, std::_List_iterator*> const&) /usr/include/c++/11.1.0/bits/stl_deque.h:269 #1 0x55c90acccaa7 in std::deque, std::allocator > >::empty() const /usr/include/c++/11.1.0/bits/stl_deque.h:1311 #2 0x55c90acccaa7 in operator() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:350 #3 0x55c90acccaa7 in wait_until >, DetourNavigator::AsyncNavMeshUpdater::getNextJob():: > /usr/include/c++/11.1.0/condition_variable:151 #4 0x55c90acccaa7 in wait_for, DetourNavigator::AsyncNavMeshUpdater::getNextJob():: > /usr/include/c++/11.1.0/condition_variable:175 #5 0x55c90acccaa7 in DetourNavigator::AsyncNavMeshUpdater::getNextJob() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:353 #6 0x55c90accdb2d in DetourNavigator::AsyncNavMeshUpdater::process() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:257 #7 0x55c90acce464 in operator() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:98 #8 0x55c90acce464 in __invoke_impl > /usr/include/c++/11.1.0/bits/invoke.h:61 #9 0x55c90acce464 in __invoke > /usr/include/c++/11.1.0/bits/invoke.h:96 #10 0x55c90acce464 in _M_invoke<0> /usr/include/c++/11.1.0/bits/std_thread.h:253 #11 0x55c90acce464 in operator() /usr/include/c++/11.1.0/bits/std_thread.h:260 #12 0x55c90acce464 in _M_run /usr/include/c++/11.1.0/bits/std_thread.h:211 #13 0x7f78c38fd3c3 in execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:82 #14 0x7f78c36b1258 in start_thread (/usr/lib/libpthread.so.0+0x9258) #15 0x7f78c35da5e2 in __GI___clone (/usr/lib/libc.so.6+0xfe5e2) 0x60c000436058 is located 88 bytes inside of 120-byte region [0x60c000436000,0x60c000436078) freed by thread T0 here: #0 0x7f78c688cd69 in operator delete(void*, unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cpp:172 #1 0x55c90acdbe20 in __gnu_cxx::new_allocator, std::allocator > > > > >::deallocate(std::_Rb_tree_node, std::allocator > > > >*, unsigned long) /usr/include/c++/11.1.0/ext/new_allocator.h:139 #2 0x55c90acdbe20 in std::allocator_traits, std::allocator > > > > > >::deallocate(std::allocator, std::allocator > > > > >&, std::_Rb_tree_node, std::allocator > > > >*, unsigned long) /usr/include/c++/11.1.0/bits/alloc_traits.h:492 #3 0x55c90acdbe20 in std::_Rb_tree, std::allocator > > >, std::_Select1st, std::allocator > > > >, std::less, std::allocator, std::allocator > > > > >::_M_put_node(std::_Rb_tree_node, std::allocator > > > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:565 #4 0x55c90acdbe20 in std::_Rb_tree, std::allocator > > >, std::_Select1st, std::allocator > > > >, std::less, std::allocator, std::allocator > > > > >::_M_drop_node(std::_Rb_tree_node, std::allocator > > > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:632 #5 0x55c90acdbe20 in std::_Rb_tree, std::allocator > > >, std::_Select1st, std::allocator > > > >, std::less, std::allocator, std::allocator > > > > >::_M_erase(std::_Rb_tree_node, std::allocator > > > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:1889 #6 0x55c90acc0569 in std::_Rb_tree, std::allocator > > >, std::_Select1st, std::allocator > > > >, std::less, std::allocator, std::allocator > > > > >::clear() /usr/include/c++/11.1.0/bits/stl_tree.h:1254 #7 0x55c90acc0569 in std::map, std::allocator > >, std::less, std::allocator, std::allocator > > > > >::clear() /usr/include/c++/11.1.0/bits/stl_map.h:1134 #8 0x55c90acc0569 in DetourNavigator::AsyncNavMeshUpdater::~AsyncNavMeshUpdater() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:105 #9 0x55c90acab2dc in DetourNavigator::NavigatorImpl::~NavigatorImpl() (/home/elsid/dev/openmw/build/gcc/asan/openmw+0x2d152dc) #10 0x55c9097db4b5 in std::default_delete::operator()(DetourNavigator::Navigator*) const /usr/include/c++/11.1.0/bits/unique_ptr.h:85 #11 0x55c9097db4b5 in std::unique_ptr >::~unique_ptr() /usr/include/c++/11.1.0/bits/unique_ptr.h:361 #12 0x55c9097db4b5 in MWWorld::World::~World() /home/elsid/dev/openmw/apps/openmw/mwworld/worldimp.cpp:534 #13 0x55c9097dc3a4 in MWWorld::World::~World() /home/elsid/dev/openmw/apps/openmw/mwworld/worldimp.cpp:534 #14 0x55c90a1925e4 in MWBase::Environment::cleanup() /home/elsid/dev/openmw/apps/openmw/mwbase/environment.cpp:192 #15 0x55c90a1f544d in OMW::Engine::~Engine() /home/elsid/dev/openmw/apps/openmw/engine.cpp:434 #16 0x55c90a1f6700 in OMW::Engine::~Engine() /home/elsid/dev/openmw/apps/openmw/engine.cpp:455 #17 0x55c90a19d523 in std::default_delete::operator()(OMW::Engine*) const /usr/include/c++/11.1.0/bits/unique_ptr.h:85 #18 0x55c90a19d523 in std::unique_ptr >::~unique_ptr() /usr/include/c++/11.1.0/bits/unique_ptr.h:361 #19 0x55c90a19d523 in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:320 #20 0x55c90a955634 in wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:205 #21 0x55c90a193be0 in main /home/elsid/dev/openmw/apps/openmw/main.cpp:328 #22 0x7f78c3503b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24) previously allocated by thread T18 here: #0 0x7f78c688bca1 in operator new(unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cpp:99 #1 0x55c90ace8c73 in __gnu_cxx::new_allocator, std::allocator > > > > >::allocate(unsigned long, void const*) /usr/include/c++/11.1.0/ext/new_allocator.h:121 #2 0x55c90ace8c73 in std::allocator_traits, std::allocator > > > > > >::allocate(std::allocator, std::allocator > > > > >&, unsigned long) /usr/include/c++/11.1.0/bits/alloc_traits.h:460 #3 0x55c90ace8c73 in std::_Rb_tree, std::allocator > > >, std::_Select1st, std::allocator > > > >, std::less, std::allocator, std::allocator > > > > >::_M_get_node() /usr/include/c++/11.1.0/bits/stl_tree.h:561 #4 0x55c90ace8c73 in std::_Rb_tree_node, std::allocator > > > >* std::_Rb_tree, std::allocator > > >, std::_Select1st, std::allocator > > > >, std::less, std::allocator, std::allocator > > > > >::_M_create_node, std::tuple<> >(std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) /usr/include/c++/11.1.0/bits/stl_tree.h:611 #5 0x55c90ace8c73 in std::_Rb_tree_iterator, std::allocator > > > > std::_Rb_tree, std::allocator > > >, std::_Select1st, std::allocator > > > >, std::less, std::allocator, std::allocator > > > > >::_M_emplace_hint_unique, std::tuple<> >(std::_Rb_tree_const_iterator, std::allocator > > > >, std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) /usr/include/c++/11.1.0/bits/stl_tree.h:2429 #6 0x55c90accc5fb in std::map, std::allocator > >, std::less, std::allocator, std::allocator > > > > >::operator[](std::thread::id const&) /usr/include/c++/11.1.0/bits/stl_map.h:501 #7 0x55c90accc5fb in DetourNavigator::AsyncNavMeshUpdater::getNextJob() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:344 #8 0x55c90accdb2d in DetourNavigator::AsyncNavMeshUpdater::process() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:257 #9 0x55c90acce464 in operator() /home/elsid/dev/openmw/components/detournavigator/asyncnavmeshupdater.cpp:98 #10 0x55c90acce464 in __invoke_impl > /usr/include/c++/11.1.0/bits/invoke.h:61 #11 0x55c90acce464 in __invoke > /usr/include/c++/11.1.0/bits/invoke.h:96 #12 0x55c90acce464 in _M_invoke<0> /usr/include/c++/11.1.0/bits/std_thread.h:253 #13 0x55c90acce464 in operator() /usr/include/c++/11.1.0/bits/std_thread.h:260 #14 0x55c90acce464 in _M_run /usr/include/c++/11.1.0/bits/std_thread.h:211 #15 0x7f78c38fd3c3 in execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:82 Thread T18 created by T0 here: #0 0x7f78c682bfa7 in __interceptor_pthread_create /build/gcc/src/gcc/libsanitizer/asan/asan_interceptors.cpp:216 #1 0x7f78c38fd6aa in std::thread::_M_start_thread(std::unique_ptr >, void (*)()) /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:663 #2 0x55c90ae008d1 in DetourNavigator::NavMeshManager::NavMeshManager(DetourNavigator::Settings const&) /home/elsid/dev/openmw/components/detournavigator/navmeshmanager.cpp:47 #3 0x55c90aca3bfe in DetourNavigator::NavigatorImpl::NavigatorImpl(DetourNavigator::Settings const&) /home/elsid/dev/openmw/components/detournavigator/navigatorimpl.cpp:13 #4 0x55c9097d9e6f in MWWorld::World::World(osgViewer::Viewer*, osg::ref_ptr, Resource::ResourceSystem*, SceneUtil::WorkQueue*, Files::Collections const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, ToUTF8::Utf8Encoder*, int, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwworld/worldimp.cpp:196 #5 0x55c90a1e9992 in OMW::Engine::prepareEngine(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:789 #6 0x55c90a1ed138 in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:949 #7 0x55c90a19d4cb in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:316 #8 0x55c90a955634 in wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:205 #9 0x55c90a193be0 in main /home/elsid/dev/openmw/apps/openmw/main.cpp:328 #10 0x7f78c3503b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24) SUMMARY: AddressSanitizer: heap-use-after-free /usr/include/c++/11.1.0/bits/stl_deque.h:269 in std::operator==(std::_Deque_iterator, std::_List_iterator&, std::_List_iterator*> const&, std::_Deque_iterator, std::_List_iterator&, std::_List_iterator*> const&) Shadow bytes around the buggy address: 0x0c188007ebb0: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd 0x0c188007ebc0: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa 0x0c188007ebd0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c188007ebe0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x0c188007ebf0: 00 00 00 00 00 00 07 fa fa fa fa fa fa fa fa fa =>0x0c188007ec00: fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd fd fa 0x0c188007ec10: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x0c188007ec20: 00 00 00 00 00 00 02 fa fa fa fa fa fa fa fa fa 0x0c188007ec30: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c188007ec40: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x0c188007ec50: 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==11799==ABORTING --- components/detournavigator/asyncnavmeshupdater.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 7fcfdf98c5..562420955d 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -345,8 +345,12 @@ namespace DetourNavigator while (true) { + bool shouldStop = false; + const auto hasJob = [&] { - return (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()) + shouldStop = mShouldStop; + return shouldStop + || (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()) || !threadQueue.empty(); }; @@ -357,6 +361,9 @@ namespace DetourNavigator return mJobs.end(); } + if (shouldStop) + return mJobs.end(); + Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and " << threadQueue.size() << " thread jobs by thread=" << std::this_thread::get_id(); From e9b8933b2fbee645130bf33cf18521457b8ee722 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 19 Aug 2021 01:37:31 +0200 Subject: [PATCH 1235/2859] Do no link binaries with Qt where it's not used Define components_qt static library with all qt dependent components that also depends on other components. Link only cs, wizard and launcher with qt dependent components. --- CHANGELOG.md | 1 + CMakeLists.txt | 1 - apps/launcher/CMakeLists.txt | 2 +- apps/opencs/CMakeLists.txt | 2 +- apps/wizard/CMakeLists.txt | 2 +- cmake/OpenMWMacros.cmake | 2 +- components/CMakeLists.txt | 8 +++++--- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb461154d3..e61acf9568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bug #5379: Wandering NPCs falling through cantons Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost + Bug #5508: Engine binary links to Qt without using it Bug #5755: Active grid object paging - disappearing textures Bug #5788: Texture editing parses the selected indexes wrongly Bug #5842: GetDisposition adds temporary disposition change from different actors diff --git a/CMakeLists.txt b/CMakeLists.txt index b69cb5b71f..c733c22ff5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -542,7 +542,6 @@ endif() # Components add_subdirectory (components) -target_compile_definitions(components PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") # Apps and tools if (BUILD_OPENMW) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 3018237705..e9ec906e6a 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -99,7 +99,7 @@ endif (WIN32) target_link_libraries(openmw-launcher ${SDL2_LIBRARY_ONLY} ${OPENAL_LIBRARY} - components + components_qt ) target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 88c4233c9c..c76e322055 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -229,7 +229,7 @@ target_link_libraries(openmw-cs ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} - components + components_qt ) if(OSG_STATIC) diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 10e06d1ff0..2558ec81de 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -99,7 +99,7 @@ openmw_add_executable(openmw-wizard ) target_link_libraries(openmw-wizard - components + components_qt ) target_link_libraries(openmw-wizard Qt5::Widgets Qt5::Core) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 1621a08cfd..176a02d507 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -78,7 +78,7 @@ foreach (u ${ARGN}) file (GLOB ALL "${dir}/${u}.[ch]pp") foreach (f ${ALL}) list (APPEND files "${f}") -list (APPEND COMPONENT_FILES "${f}") +list (APPEND COMPONENT_QT_FILES "${f}") endforeach (f) file (GLOB MOC_H "${dir}/${u}.hpp") foreach (fi ${MOC_H}) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3388d34a09..3de864ea52 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -217,7 +217,7 @@ if (USE_QT) processinvoker ) - add_component_dir (misc + add_component_qt_dir (misc helpviewer ) @@ -233,7 +233,7 @@ endif () include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) -add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) +add_library(components STATIC ${COMPONENT_FILES}) target_link_libraries(components # CMake's built-in OSG finder does not use pkgconfig, so we have to @@ -276,7 +276,9 @@ if (WIN32) endif() if (USE_QT) - target_link_libraries(components Qt5::Widgets Qt5::Core) + add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) + target_link_libraries(components_qt components Qt5::Widgets Qt5::Core) + target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") endif() if (GIT_CHECKOUT) From 431501e23a3b144edeb5b9dab936c88d56599d1f Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Aug 2021 11:25:01 +0200 Subject: [PATCH 1236/2859] Remove redundant job distribution between threads Instead don't take jobs from queue until job for the same tile is processing. --- .../detournavigator/asyncnavmeshupdater.cpp | 98 ++++++------------- .../detournavigator/asyncnavmeshupdater.hpp | 5 +- 2 files changed, 30 insertions(+), 73 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 562420955d..20105aba06 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -102,7 +102,6 @@ namespace DetourNavigator { mShouldStop = true; std::unique_lock lock(mMutex); - mThreadsQueues.clear(); mWaiting.clear(); mHasJob.notify_all(); lock.unlock(); @@ -340,64 +339,38 @@ namespace DetourNavigator { std::unique_lock lock(mMutex); - const auto threadId = std::this_thread::get_id(); - auto& threadQueue = mThreadsQueues[threadId]; - - while (true) + bool shouldStop = false; + const auto hasJob = [&] { - bool shouldStop = false; - - const auto hasJob = [&] { - shouldStop = mShouldStop; - return shouldStop - || (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()) - || !threadQueue.empty(); - }; - - if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) - { - if (mJobs.empty()) - mDone.notify_all(); - return mJobs.end(); - } - - if (shouldStop) - return mJobs.end(); - - Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and " - << threadQueue.size() << " thread jobs by thread=" << std::this_thread::get_id(); - - const JobIt job = threadQueue.empty() - ? getJob(mWaiting, true) - : getJob(threadQueue, false); + shouldStop = mShouldStop; + return shouldStop + || (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()); + }; - if (job == mJobs.end()) - continue; + if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) + { + if (mJobs.empty()) + mDone.notify_all(); + return mJobs.end(); + } - const auto owner = lockTile(job->mAgentHalfExtents, job->mChangedTile); + if (shouldStop) + return mJobs.end(); - if (owner == threadId) - { - mPushed.erase(getAgentAndTile(*job)); - return job; - } + const JobIt job = mWaiting.front(); - postThreadJob(job, mThreadsQueues[owner]); - } - } + mWaiting.pop_front(); - JobIt AsyncNavMeshUpdater::getJob(std::deque& jobs, bool changeLastUpdate) - { - const auto now = std::chrono::steady_clock::now(); - JobIt job = jobs.front(); - - if (job->mProcessTime > now) + if (!lockTile(job->mAgentHalfExtents, job->mChangedTile)) + { + ++job->mTryNumber; + insertPrioritizedJob(job, mWaiting); return mJobs.end(); + } - jobs.pop_front(); - - if (changeLastUpdate && job->mChangeType == ChangeType::update) - mLastUpdates[getAgentAndTile(*job)] = now; + if (job->mChangeType == ChangeType::update) + mLastUpdates[getAgentAndTile(*job)] = std::chrono::steady_clock::now(); + mPushed.erase(getAgentAndTile(*job)); return job; } @@ -435,7 +408,7 @@ namespace DetourNavigator if (mPushed.emplace(job->mAgentHalfExtents, job->mChangedTile).second) { ++job->mTryNumber; - mWaiting.push_back(job); + insertPrioritizedJob(job, mWaiting); mHasJob.notify_all(); return; } @@ -443,26 +416,11 @@ namespace DetourNavigator mJobs.erase(job); } - void AsyncNavMeshUpdater::postThreadJob(JobIt job, std::deque& queue) - { - queue.push_back(job); - mHasJob.notify_all(); - } - - std::thread::id AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) + bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) { if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) - return std::this_thread::get_id(); - - auto locked = mProcessingTiles.lock(); - const auto tile = locked->find(std::make_tuple(agentHalfExtents, changedTile)); - if (tile == locked->end()) - { - const auto threadId = std::this_thread::get_id(); - locked->emplace(std::tie(agentHalfExtents, changedTile), threadId); - return threadId; - } - return tile->second; + return true; + return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second; } void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index b770230673..9e521ad509 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -99,10 +99,9 @@ namespace DetourNavigator std::set> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; - Misc::ScopeGuarded, std::thread::id>> mProcessingTiles; + Misc::ScopeGuarded>> mProcessingTiles; std::map, std::chrono::steady_clock::time_point> mLastUpdates; std::set> mPresentTiles; - std::map> mThreadsQueues; std::vector mThreads; void process() noexcept; @@ -119,7 +118,7 @@ namespace DetourNavigator void repost(JobIt job); - std::thread::id lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); + bool lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); From 0f11acf709643fd829683e1ef4f6d0906950f349 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 Aug 2021 19:50:29 +0200 Subject: [PATCH 1237/2859] Report more stats from AsyncNavMeshUpdater --- components/detournavigator/asyncnavmeshupdater.cpp | 9 ++++++++- components/resource/stats.cpp | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 20105aba06..c6db079ce8 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -234,13 +234,20 @@ namespace DetourNavigator void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const { std::size_t jobs = 0; + std::size_t waiting = 0; + std::size_t pushed = 0; { const std::lock_guard lock(mMutex); jobs = mJobs.size(); + waiting = mWaiting.size(); + pushed = mPushed.size(); } - stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs); + stats.setAttribute(frameNumber, "NavMesh Jobs", jobs); + stats.setAttribute(frameNumber, "NavMesh Waiting", waiting); + stats.setAttribute(frameNumber, "NavMesh Pushed", pushed); + stats.setAttribute(frameNumber, "NavMesh Processing", mProcessingTiles.lockConst()->size()); mNavMeshTilesCache.reportStats(frameNumber, stats); } diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index b5975e28e3..9881d0458b 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -390,7 +390,10 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Land", "Composite", "", - "NavMesh UpdateJobs", + "NavMesh Jobs", + "NavMesh Waiting", + "NavMesh Pushed", + "NavMesh Processing", "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", From 0066c446f828570e029aa4267bbdb29fb9aa1642 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 17 Aug 2021 12:10:22 +0200 Subject: [PATCH 1238/2859] Remove navmesh tiles outside allowed range first * Change job change type to remove when tile is outside allowed range. * Swap try number and change type in job priority. To make sure remove jobs always processed before any other. --- components/detournavigator/asyncnavmeshupdater.cpp | 12 +++++++++++- components/detournavigator/asyncnavmeshupdater.hpp | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index c6db079ce8..5c88d0cfa2 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include @@ -48,7 +50,7 @@ namespace auto getPriority(const Job& job) noexcept { - return std::make_tuple(job.mProcessTime, job.mTryNumber, job.mChangeType, job.mDistanceToPlayer, job.mDistanceToOrigin); + return std::make_tuple(job.mProcessTime, job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); } struct LessByJobPriority @@ -123,11 +125,19 @@ namespace DetourNavigator if (!playerTileChanged && changedTiles.empty()) return; + const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); + const std::lock_guard lock(mMutex); if (playerTileChanged) + { for (JobIt job : mWaiting) + { job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); + if (!shouldAddTile(job->mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles))) + job->mChangeType = ChangeType::remove; + } + } for (const auto& [changedTile, changeType] : changedTiles) { diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 9e521ad509..2d915ad434 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -60,7 +60,7 @@ namespace DetourNavigator const TilePosition mChangedTile; const std::chrono::steady_clock::time_point mProcessTime; unsigned mTryNumber = 0; - const ChangeType mChangeType; + ChangeType mChangeType; int mDistanceToPlayer; const int mDistanceToOrigin; From 7801f420059ad4540dbda554acaaa343ff0f1919 Mon Sep 17 00:00:00 2001 From: unelsson Date: Fri, 20 Aug 2021 22:32:41 +0300 Subject: [PATCH 1239/2859] Don't do mapToSource at executeModify --- apps/opencs/model/world/commanddispatcher.cpp | 25 ++++++------------- apps/opencs/model/world/commanddispatcher.hpp | 2 +- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 13e66fd5cd..3da3e3d22c 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -137,7 +137,7 @@ std::vector CSMWorld::CommandDispatcher::getExtendedTypes return tables; } -void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *sourceModel, const QModelIndex& sourceIndex, const QVariant& new_) +void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_) { if (mLocked) return; @@ -150,26 +150,17 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *sourceModel std::unique_ptr modifyData; std::unique_ptr modifyCell; - QAbstractItemModel *model(nullptr); - QModelIndex index; + QAbstractItemModel* sourceModel = model; + if (IdTableProxyModel* proxy = dynamic_cast (model)) + sourceModel = proxy->sourceModel(); - if (QAbstractProxyModel *proxy = dynamic_cast (sourceModel)) - { - // Replace proxy with actual model - index = proxy->mapToSource (sourceIndex); - model = proxy->sourceModel(); - } - - if (!model) return; + CSMWorld::IdTable& table = dynamic_cast(*sourceModel); // for getId() + int stateColumn = table.findColumnIndex(Columns::ColumnId_Modification); + QModelIndex stateIndex = table.getModelIndex(table.getId(index.row()), stateColumn); + RecordBase::State state = static_cast (sourceModel->data(stateIndex).toInt()); int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); - int stateColumn = dynamic_cast(*model).findColumnIndex(Columns::ColumnId_Modification); - - CSMWorld::IdTable& table = dynamic_cast(*model); // for getId() - QModelIndex stateIndex = table.getModelIndex(table.getId(index.row()), stateColumn); - RecordBase::State state = static_cast (model->data(stateIndex).toInt()); - // This is not guaranteed to be the same as \a model, since a proxy could be used. IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel(mId)); diff --git a/apps/opencs/model/world/commanddispatcher.hpp b/apps/opencs/model/world/commanddispatcher.hpp index 13724f5dbd..538fd7f188 100644 --- a/apps/opencs/model/world/commanddispatcher.hpp +++ b/apps/opencs/model/world/commanddispatcher.hpp @@ -60,7 +60,7 @@ namespace CSMWorld /// /// \attention model must either be a model for the table operated on by this /// dispatcher or a proxy of it. - void executeModify (QAbstractItemModel *sourceModel, const QModelIndex& sourceIndex, const QVariant& new_); + void executeModify (QAbstractItemModel *model, const QModelIndex& index, const QVariant& new_); public slots: From 28ad139464b5d08c5d213e6fda128a2906d74345 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 21 Aug 2021 07:20:10 +1000 Subject: [PATCH 1240/2859] Check if deleted, just in case. --- apps/openmw/mwworld/store.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 8faa86597f..f6de1d485b 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -875,7 +875,7 @@ namespace MWWorld // deal with mods that have empty pathgrid records (Issue #6209) // we assume that these records are empty on purpose (i.e. to remove old pathgrid on an updated cell) - if (pathgrid.mPoints.empty() || pathgrid.mEdges.empty()) + if (isDeleted || pathgrid.mPoints.empty() || pathgrid.mEdges.empty()) { if (interior) { From 3771e523f1a778c38a0301b2454fae91c1461f89 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 9 Jul 2021 00:31:57 +0200 Subject: [PATCH 1241/2859] More object bindings --- apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/objectbindings.cpp | 26 ++++++++++++++++++++++++ files/lua_api/openmw/core.lua | 30 ++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index e50af836b5..ed788eb525 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 4; + api["API_REVISION"] = 5; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index d4ae041549..b7607c8b2c 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -158,6 +158,32 @@ namespace MWLua luaManager->addAction(std::move(action)); }; } + else + { // Only for local scripts + objectT["isOnGround"] = [](const ObjectT& o) + { + return MWBase::Environment::get().getWorld()->isOnGround(o.ptr()); + }; + objectT["isSwimming"] = [](const ObjectT& o) + { + return MWBase::Environment::get().getWorld()->isSwimming(o.ptr()); + }; + objectT["isInWeaponStance"] = [](const ObjectT& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.isActor() && cls.getCreatureStats(o.ptr()).getDrawState() == MWMechanics::DrawState_Weapon; + }; + objectT["isInMagicStance"] = [](const ObjectT& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.isActor() && cls.getCreatureStats(o.ptr()).getDrawState() == MWMechanics::DrawState_Spell; + }; + objectT["getCurrentSpeed"] = [](const ObjectT& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getCurrentSpeed(o.ptr()); + }; + } } template diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index eed2005cfa..50706b9770 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -127,6 +127,36 @@ -- @param self -- @return #number +------------------------------------------------------------------------------- +-- Current speed. Can be called only from a local script. +-- @function [parent=#GameObject] getCurrentSpeed +-- @param self +-- @return #number + +------------------------------------------------------------------------------- +-- Is the actor standing on ground. Can be called only from a local script. +-- @function [parent=#GameObject] isOnGround +-- @param self +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is the actor in water. Can be called only from a local script. +-- @function [parent=#GameObject] isSwimming +-- @param self +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is the actor in weapon stance. Can be called only from a local script. +-- @function [parent=#GameObject] isInWeaponStance +-- @param self +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is the actor in magic stance. Can be called only from a local script. +-- @function [parent=#GameObject] isInMagicStance +-- @param self +-- @return #boolean + ------------------------------------------------------------------------------- -- Send local event to the object. -- @function [parent=#GameObject] sendEvent From 0922d0b105e0461052a829de99407f538cadf40d Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 22 Aug 2021 05:46:46 +0300 Subject: [PATCH 1242/2859] Clean up text key extraction --- apps/openmw/mwmechanics/character.cpp | 11 ++------- components/nifosg/nifloader.cpp | 35 +++++---------------------- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index aee94f15b7..62c130b36e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -23,6 +23,7 @@ #include #include +#include #include @@ -942,14 +943,6 @@ CharacterController::~CharacterController() } } -void split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } -} - void CharacterController::handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map) { const std::string &evt = key->second; @@ -969,7 +962,7 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil: if (soundgen.find(' ') != std::string::npos) { std::vector tokens; - split(soundgen, ' ', tokens); + Misc::StringUtils::split(soundgen, tokens); soundgen = tokens[0]; if (tokens.size() >= 2) { diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 81c3d24dcc..0432aef5b4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -153,37 +153,14 @@ namespace { for(size_t i = 0;i < tk->list.size();i++) { - const std::string &str = tk->list[i].text; - std::string::size_type pos = 0; - while(pos < str.length()) + std::vector results; + Misc::StringUtils::split(tk->list[i].text, results, "\r\n"); + for (std::string &result : results) { - if(::isspace(str[pos])) - { - pos++; - continue; - } - - std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos)); - if(nextpos != std::string::npos) - { - do { - nextpos--; - } while(nextpos > pos && ::isspace(str[nextpos])); - nextpos++; - } - else if(::isspace(*str.rbegin())) - { - std::string::const_iterator last = str.end(); - do { - --last; - } while(last != str.begin() && ::isspace(*last)); - nextpos = std::distance(str.begin(), ++last); - } - std::string result = str.substr(pos, nextpos-pos); + Misc::StringUtils::trim(result); Misc::StringUtils::lowerCaseInPlace(result); - textkeys.emplace(tk->list[i].time, std::move(result)); - - pos = nextpos; + if (!result.empty()) + textkeys.emplace(tk->list[i].time, std::move(result)); } } } From 716eebe61628ba7a6a96de239388fba0c61dd5f9 Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 22 Aug 2021 19:22:13 +0000 Subject: [PATCH 1243/2859] Merge branch 'terrainselectioncrashfix' into 'openmw-47' Fix terrain selection crash See merge request OpenMW/openmw!1165 (cherry picked from commit ec4e3b04a7b394acaaa8319b89ce1237e9d4d6e6) b84e41bd Avoid storing ref, dynamic cast worldspacewidget for safety d62ddc00 Use QPointer to detect object existence, less verbose debug messages cb42b528 Remove friend, make getEditMode public to allow editmode testing. 4b148180 Restucture code 08f7c73e Fix text --- apps/opencs/view/render/commands.cpp | 33 ++++++++++++++++--- apps/opencs/view/render/commands.hpp | 11 +++++-- apps/opencs/view/render/terrainshapemode.cpp | 13 +++++--- apps/opencs/view/render/terrainshapemode.hpp | 4 ++- .../opencs/view/render/terraintexturemode.cpp | 5 +++ .../opencs/view/render/terraintexturemode.hpp | 4 ++- apps/opencs/view/render/worldspacewidget.cpp | 10 +++--- apps/opencs/view/render/worldspacewidget.hpp | 4 +-- 8 files changed, 65 insertions(+), 19 deletions(-) diff --git a/apps/opencs/view/render/commands.cpp b/apps/opencs/view/render/commands.cpp index 7b37602961..699bf5d016 100644 --- a/apps/opencs/view/render/commands.cpp +++ b/apps/opencs/view/render/commands.cpp @@ -1,19 +1,44 @@ #include "commands.hpp" +#include + +#include #include +#include "editmode.hpp" #include "terrainselection.hpp" +#include "terrainshapemode.hpp" +#include "terraintexturemode.hpp" +#include "worldspacewidget.hpp" -CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(TerrainSelection& terrainSelection, QUndoCommand* parent) - : mTerrainSelection(terrainSelection) +CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent) + : mWorldspaceWidget(worldspaceWidget) { } void CSVRender::DrawTerrainSelectionCommand::redo() { - mTerrainSelection.update(); + tryUpdate(); } void CSVRender::DrawTerrainSelectionCommand::undo() { - mTerrainSelection.update(); + tryUpdate(); +} + +void CSVRender::DrawTerrainSelectionCommand::tryUpdate() +{ + if (!mWorldspaceWidget) + { + Log(Debug::Verbose) << "Can't update terrain selection, no WorldspaceWidget found!"; + return; + } + + auto terrainMode = dynamic_cast(mWorldspaceWidget->getEditMode()); + if (!terrainMode) + { + Log(Debug::Verbose) << "Can't update terrain selection in current EditMode"; + return; + } + + terrainMode->getTerrainSelection()->update(); } diff --git a/apps/opencs/view/render/commands.hpp b/apps/opencs/view/render/commands.hpp index cdc389e33a..62b7fbfdcd 100644 --- a/apps/opencs/view/render/commands.hpp +++ b/apps/opencs/view/render/commands.hpp @@ -1,8 +1,12 @@ #ifndef CSV_RENDER_COMMANDS_HPP #define CSV_RENDER_COMMANDS_HPP +#include + #include +#include "worldspacewidget.hpp" + namespace CSVRender { class TerrainSelection; @@ -21,14 +25,17 @@ namespace CSVRender */ class DrawTerrainSelectionCommand : public QUndoCommand { + private: - TerrainSelection& mTerrainSelection; + QPointer mWorldspaceWidget; public: - DrawTerrainSelectionCommand(TerrainSelection& terrainSelection, QUndoCommand* parent = nullptr); + DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent = nullptr); void redo() override; void undo() override; + + void tryUpdate(); }; } diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 866ff69cde..0504944954 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -287,7 +287,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() undoStack.beginMacro ("Edit shape and normal records"); // One command at the beginning of the macro for redrawing the terrain-selection grid when undoing the changes. - undoStack.push(new DrawTerrainSelectionCommand(*mTerrainShapeSelection)); + undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget())); for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) { @@ -358,7 +358,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); } // One command at the end of the macro for redrawing the terrain-selection grid when redoing the changes. - undoStack.push(new DrawTerrainSelectionCommand(*mTerrainShapeSelection)); + undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget())); undoStack.endMacro(); clearTransientEdits(); @@ -1049,7 +1049,7 @@ void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int glob */ if (xIsAtCellBorder && yIsAtCellBorder) { - /* + /* Handle the NW, NE, and SE corner vertices. NW corner: (+1, -1) offset to reach current cell. NE corner: (-1, -1) offset to reach current cell. @@ -1132,7 +1132,7 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); else selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); - + if (selectAction == "Select only") mTerrainShapeSelection->onlySelect(selections); else if (selectAction == "Add to selection") @@ -1444,6 +1444,11 @@ void CSVRender::TerrainShapeMode::mouseMoveEvent (QMouseEvent *event) mBrushDraw->hide(); } +std::shared_ptr CSVRender::TerrainShapeMode::getTerrainSelection() +{ + return mTerrainShapeSelection; +} + void CSVRender::TerrainShapeMode::setBrushSize(int brushSize) { mBrushSize = brushSize; diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index a88e60c9c4..abc4b8aba8 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -92,6 +92,8 @@ namespace CSVRender void dragMoveEvent (QDragMoveEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override; + std::shared_ptr getTerrainSelection(); + private: /// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse @@ -176,7 +178,7 @@ namespace CSVRender int mDragMode = InteractionType_None; osg::Group* mParentNode; bool mIsEditing = false; - std::unique_ptr mTerrainShapeSelection; + std::shared_ptr mTerrainShapeSelection; int mTotalDiffY = 0; std::vector mAlteredCells; osg::Vec3d mEditingPos; diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 4e267e9426..dfcc29ae01 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -712,6 +712,11 @@ void CSVRender::TerrainTextureMode::mouseMoveEvent (QMouseEvent *event) mBrushDraw->hide(); } +std::shared_ptr CSVRender::TerrainTextureMode::getTerrainSelection() +{ + return mTerrainTextureSelection; +} + void CSVRender::TerrainTextureMode::setBrushSize(int brushSize) { diff --git a/apps/opencs/view/render/terraintexturemode.hpp b/apps/opencs/view/render/terraintexturemode.hpp index 31932df217..e0c6e4b40f 100644 --- a/apps/opencs/view/render/terraintexturemode.hpp +++ b/apps/opencs/view/render/terraintexturemode.hpp @@ -85,6 +85,8 @@ namespace CSVRender void mouseMoveEvent (QMouseEvent *event) override; + std::shared_ptr getTerrainSelection(); + private: /// \brief Handle brush mechanics, maths regarding worldspace hit etc. void editTerrainTextureGrid (const WorldspaceHitResult& hit); @@ -115,7 +117,7 @@ namespace CSVRender int mDragMode; osg::Group* mParentNode; bool mIsEditing; - std::unique_ptr mTerrainTextureSelection; + std::shared_ptr mTerrainTextureSelection; const int cellSize {ESM::Land::REAL_SIZE}; const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index b14b7953ab..c51479f897 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -452,6 +452,11 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo return hit; } +CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() +{ + return dynamic_cast (mEditMode->getCurrent()); +} + void CSVRender::WorldspaceWidget::abortDrag() { if (mDragging) @@ -697,11 +702,6 @@ void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitRes editMode.primaryOpenPressed (hit); } -CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() -{ - return dynamic_cast (mEditMode->getCurrent()); -} - void CSVRender::WorldspaceWidget::primaryOpen(bool activate) { handleInteraction(InteractionType_PrimaryOpen, activate); diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 5e224b3803..cf244ce712 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -189,6 +189,8 @@ namespace CSVRender /// Erase all overrides and restore the visual representation to its true state. virtual void reset (unsigned int elementMask) = 0; + EditMode *getEditMode(); + protected: /// Visual elements in a scene @@ -215,8 +217,6 @@ namespace CSVRender void settingChanged (const CSMPrefs::Setting *setting) override; - EditMode *getEditMode(); - bool getSpeedMode(); private: From 45549da0f5d9e0414ad293a9f5d62350c22e6599 Mon Sep 17 00:00:00 2001 From: unelsson Date: Tue, 10 Aug 2021 11:47:58 +0300 Subject: [PATCH 1244/2859] For most commands, set mOld in redo, not in constructor --- apps/opencs/model/world/commands.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 7c5bafb52f..a8410b1304 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -24,11 +24,11 @@ CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUnd , mChanged(false) { setText(("Touch " + mId).c_str()); - mOld.reset(mTable.getRecord(mId).clone().get()); } void CSMWorld::TouchCommand::redo() { + mOld.reset(mTable.getRecord(mId).clone().get()); mChanged = mTable.touchRecord(mId); } @@ -159,7 +159,6 @@ CSMWorld::TouchLandCommand::TouchLandCommand(IdTable& landTable, IdTable& ltexTa , mChanged(false) { setText(("Touch " + mId).c_str()); - mOld.reset(mLands.getRecord(mId).clone().get()); } const std::string& CSMWorld::TouchLandCommand::getOriginId() const @@ -174,6 +173,7 @@ const std::string& CSMWorld::TouchLandCommand::getDestinationId() const void CSMWorld::TouchLandCommand::onRedo() { + mOld.reset(mLands.getRecord(mId).clone().get()); mChanged = mLands.touchRecord(mId); } @@ -291,11 +291,9 @@ void CSMWorld::CreateCommand::undo() } CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id) +: QUndoCommand (parent), mModel (model), mId (id), mOld(nullptr) { setText (("Revert record " + id).c_str()); - - mOld = model.getRecord (id).clone(); } CSMWorld::RevertCommand::~RevertCommand() @@ -304,6 +302,8 @@ CSMWorld::RevertCommand::~RevertCommand() void CSMWorld::RevertCommand::redo() { + mOld = mModel.getRecord (mId).clone(); + int column = mModel.findColumnIndex (Columns::ColumnId_Modification); QModelIndex index = mModel.getModelIndex (mId, column); @@ -326,11 +326,9 @@ void CSMWorld::RevertCommand::undo() CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent) -: QUndoCommand (parent), mModel (model), mId (id), mType(type) +: QUndoCommand (parent), mModel (model), mId (id), mOld(nullptr), mType(type) { setText (("Delete record " + id).c_str()); - - mOld = model.getRecord (id).clone(); } CSMWorld::DeleteCommand::~DeleteCommand() @@ -339,6 +337,8 @@ CSMWorld::DeleteCommand::~DeleteCommand() void CSMWorld::DeleteCommand::redo() { + mOld = mModel.getRecord (mId).clone(); + int column = mModel.findColumnIndex (Columns::ColumnId_Modification); QModelIndex index = mModel.getModelIndex (mId, column); From b2fe5915900c0435354fa3920f5c1ca845936a3d Mon Sep 17 00:00:00 2001 From: unelsson Date: Thu, 12 Aug 2021 15:24:00 +0300 Subject: [PATCH 1245/2859] Don't do any storing in the constructor --- apps/opencs/model/world/commands.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index a8410b1304..cdb4663390 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -190,10 +190,14 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI const QVariant& new_, QUndoCommand* parent) : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) { - if (QAbstractProxyModel *proxy = dynamic_cast (&model)) +} + +void CSMWorld::ModifyCommand::redo() +{ + if (QAbstractProxyModel *proxy = dynamic_cast (mModel)) { // Replace proxy with actual model - mIndex = proxy->mapToSource (index); + mIndex = proxy->mapToSource (mIndex); mModel = proxy->sourceModel(); } @@ -223,10 +227,7 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mOldRecordState = static_cast(table->data(mRecordStateIndex).toInt()); } -} -void CSMWorld::ModifyCommand::redo() -{ mOld = mModel->data (mIndex, Qt::EditRole); mModel->setData (mIndex, mNew); } From 11ec96e1e76eb7d54ff58a2d4887e3b0932396d6 Mon Sep 17 00:00:00 2001 From: unelsson Date: Thu, 12 Aug 2021 22:35:19 +0300 Subject: [PATCH 1246/2859] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e61acf9568..17337f7346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI + Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed Bug #4752: UpdateCellCommand doesn't undo properly Bug #5100: Persuasion doesn't always clamp the resulting disposition From 0cfabd6f3b49ed90373975289acf48e15ce83bb5 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 21 Aug 2021 13:42:55 +0300 Subject: [PATCH 1247/2859] Move mapToSource back to constructor --- apps/opencs/model/world/commands.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index cdb4663390..12c8fa767f 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -189,10 +189,6 @@ void CSMWorld::TouchLandCommand::onUndo() CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) -{ -} - -void CSMWorld::ModifyCommand::redo() { if (QAbstractProxyModel *proxy = dynamic_cast (mModel)) { @@ -200,7 +196,10 @@ void CSMWorld::ModifyCommand::redo() mIndex = proxy->mapToSource (mIndex); mModel = proxy->sourceModel(); } +} +void CSMWorld::ModifyCommand::redo() +{ if (mIndex.parent().isValid()) { CSMWorld::IdTree* tree = &dynamic_cast(*mModel); From 890ce1eefbb33d38a478eb49fc17a5eb4efa21a4 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 21 Aug 2021 14:14:00 +0300 Subject: [PATCH 1248/2859] Reverse action order for redo and undo --- apps/opencs/model/world/commands.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 12c8fa767f..c7b62312ca 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -498,8 +498,8 @@ CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model, void CSMWorld::DeleteNestedCommand::redo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); - mModel.removeRows (mNestedRow, 1, parentIndex); mModifyParentCommand->redo(); + mModel.removeRows (mNestedRow, 1, parentIndex); } @@ -529,8 +529,8 @@ CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& i void CSMWorld::AddNestedCommand::redo() { QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); - mModel.addNestedRow (parentIndex, mNewRow); mModifyParentCommand->redo(); + mModel.addNestedRow (parentIndex, mNewRow); } void CSMWorld::AddNestedCommand::undo() From de3497d2911fbcfc5b0a01c823f19955f1b10e39 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 22 Aug 2021 23:41:46 +0300 Subject: [PATCH 1249/2859] Fix undo-redo crash --- apps/opencs/model/world/commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index c7b62312ca..a264ebef7f 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -173,8 +173,8 @@ const std::string& CSMWorld::TouchLandCommand::getDestinationId() const void CSMWorld::TouchLandCommand::onRedo() { - mOld.reset(mLands.getRecord(mId).clone().get()); mChanged = mLands.touchRecord(mId); + if (mChanged) mOld.reset(mLands.getRecord(mId).clone().get()); } void CSMWorld::TouchLandCommand::onUndo() From 42ce22f568526153a24bd6ebaa6f27e62f6e335c Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Mon, 23 Aug 2021 18:31:52 -0500 Subject: [PATCH 1250/2859] Adding saveSettingInt() and loadSettingInt() Convenience methods for saving and loading integer settings. Versions for QSpinBox and QComboBox are included. Right now, only simple settings are used for it; we have some additional settings that include validation that aren't using these new methods. --- apps/launcher/advancedpage.cpp | 46 +++++++++++++++++++++++----------- apps/launcher/advancedpage.hpp | 8 ++++-- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 2c33992ac3..97c2870b30 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -207,7 +207,7 @@ bool Launcher::AdvancedPage::loadSettings() { // Saves loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); - maximumQuicksavesComboBox->setValue(Settings::Manager::getInt("max quicksaves", "Saves")); + loadSettingInt(maximumQuicksavesComboBox,"max quicksaves", "Saves"); // Other Settings QString screenshotFormatString = QString::fromStdString(Settings::Manager::getString("screenshot format", "General")).toUpper(); @@ -252,14 +252,10 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game"); saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game"); - int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); - if (unarmedFactorsStrengthIndex != Settings::Manager::getInt("strength influences hand to hand", "Game")) - Settings::Manager::setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); + saveSettingInt(unarmedFactorsStrengthComboBox, "strength influences hand to hand", "Game"); saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); - int numPhysicsThreads = physicsThreadsSpinBox->value(); - if (numPhysicsThreads != Settings::Manager::getInt("async num threads", "Physics")) - Settings::Manager::setInt("async num threads", "Physics", numPhysicsThreads); + saveSettingInt(physicsThreadsSpinBox, "async num threads", "Physics"); } // Visuals @@ -349,9 +345,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); - int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); - if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game")) - Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex); + saveSettingInt(showOwnedComboBox,"show owned", "Game"); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map"); saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); @@ -370,11 +364,7 @@ void Launcher::AdvancedPage::saveSettings() { // Saves Settings saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); - int maximumQuicksaves = maximumQuicksavesComboBox->value(); - if (maximumQuicksaves != Settings::Manager::getInt("max quicksaves", "Saves")) - { - Settings::Manager::setInt("max quicksaves", "Saves", maximumQuicksaves); - } + saveSettingInt(maximumQuicksavesComboBox, "max quicksaves", "Saves"); // Other Settings std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); @@ -416,6 +406,32 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str Settings::Manager::setBool(setting, group, cValue); } +void Launcher::AdvancedPage::loadSettingInt(QComboBox *comboBox, const std::string &setting, const std::string &group) +{ + int currentIndex = Settings::Manager::getInt(setting, group); + comboBox->setCurrentIndex(currentIndex); +} + +void Launcher::AdvancedPage::saveSettingInt(QComboBox *comboBox, const std::string &setting, const std::string &group) +{ + int currentIndex = comboBox->currentIndex(); + if (currentIndex != Settings::Manager::getInt(setting, group)) + Settings::Manager::setInt(setting, group, currentIndex); +} + +void Launcher::AdvancedPage::loadSettingInt(QSpinBox *spinBox, const std::string &setting, const std::string &group) +{ + int value = Settings::Manager::getInt(setting, group); + spinBox->setValue(value); +} + +void Launcher::AdvancedPage::saveSettingInt(QSpinBox *spinBox, const std::string &setting, const std::string &group) +{ + int value = spinBox->value(); + if (value != Settings::Manager::getInt(setting, group)) + Settings::Manager::setInt(setting, group, value); +} + void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) { loadCellsForAutocomplete(cellNames); diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index 9685dcefeb..1d16fae706 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -41,8 +41,12 @@ namespace Launcher * @param filePaths the file paths of the content files to be examined */ void loadCellsForAutocomplete(QStringList filePaths); - void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); - void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); + static void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); + static void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group); + static void loadSettingInt(QComboBox *comboBox, const std::string& setting, const std::string& group); + static void saveSettingInt(QComboBox *comboBox, const std::string& setting, const std::string& group); + static void loadSettingInt(QSpinBox *spinBox, const std::string& setting, const std::string& group); + static void saveSettingInt(QSpinBox *spinBox, const std::string& setting, const std::string& group); }; } #endif From ee63ee9d93e4a044069446d71cbd666300784b5a Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 25 Aug 2021 18:34:42 +0200 Subject: [PATCH 1251/2859] Fix coverity warning CID 332936 (#1 of 1): Result is not floating-point (UNINTENDED_INTEGER_DIVISION) integer_division: Dividing integer expressions size and 2, and then converting the integer quotient to type float. Any remainder, or fractional part of the quotient, is ignored. --- components/detournavigator/recastmeshbuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 6ee16cdc6c..19125e0a9b 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -36,7 +36,7 @@ namespace DetourNavigator TileBounds maxCellTileBounds(int size, const osg::Vec3f& shift) { - const float halfCellSize = size / 2; + const float halfCellSize = static_cast(size) / 2; return TileBounds { osg::Vec2f(shift.x() - halfCellSize, shift.y() - halfCellSize), osg::Vec2f(shift.x() + halfCellSize, shift.y() + halfCellSize) From 7227a83e60153cd05e9d4ef299a4f0ddd8d3d0c7 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 25 Jul 2021 19:53:41 +1000 Subject: [PATCH 1252/2859] Preserve "blocked" record flags when saving with OpenCS. This will help outputs of OpenCS to be used with vanilla Morrowind. Sample use case: users are using the Morrowind Code Patch feature that allows modders to enable this flag to differentiate editor-made potions from player crafted potions for tooltips. --- apps/opencs/model/doc/savingstages.hpp | 2 +- components/esm/debugprofile.cpp | 1 + components/esm/debugprofile.hpp | 1 + components/esm/filter.cpp | 1 + components/esm/filter.hpp | 1 + components/esm/loadbody.cpp | 1 + components/esm/loadbody.hpp | 1 + components/esm/loadbsgn.cpp | 1 + components/esm/loadbsgn.hpp | 1 + components/esm/loadclas.cpp | 1 + components/esm/loadclas.hpp | 1 + components/esm/loadench.cpp | 1 + components/esm/loadench.hpp | 1 + components/esm/loadfact.cpp | 1 + components/esm/loadfact.hpp | 1 + components/esm/loadglob.cpp | 1 + components/esm/loadglob.hpp | 1 + components/esm/loadgmst.cpp | 1 + components/esm/loadgmst.hpp | 1 + components/esm/loadmgef.cpp | 1 + components/esm/loadmgef.hpp | 1 + components/esm/loadrace.cpp | 1 + components/esm/loadrace.hpp | 1 + components/esm/loadregn.cpp | 1 + components/esm/loadregn.hpp | 1 + components/esm/loadscpt.cpp | 1 + components/esm/loadscpt.hpp | 1 + components/esm/loadskil.cpp | 1 + components/esm/loadskil.hpp | 1 + components/esm/loadsndg.cpp | 1 + components/esm/loadsndg.hpp | 1 + components/esm/loadsoun.cpp | 1 + components/esm/loadsoun.hpp | 1 + components/esm/loadspel.cpp | 1 + components/esm/loadspel.hpp | 1 + components/esm/loadsscr.cpp | 1 + components/esm/loadsscr.hpp | 1 + 37 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index d59a1efe5e..9ccd1772e1 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -108,7 +108,7 @@ namespace CSMDoc state == CSMWorld::RecordBase::State_ModifiedOnly || state == CSMWorld::RecordBase::State_Deleted) { - writer.startRecord (record.sRecordId); + writer.startRecord (record.sRecordId, record.mRecordFlags); record.save (writer, state == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 1dcf661fc9..6276258c48 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -9,6 +9,7 @@ unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); while (esm.hasMoreSubs()) { diff --git a/components/esm/debugprofile.hpp b/components/esm/debugprofile.hpp index c056750a88..8340404c23 100644 --- a/components/esm/debugprofile.hpp +++ b/components/esm/debugprofile.hpp @@ -19,6 +19,7 @@ namespace ESM Flag_Global = 4 // make available from main menu (i.e. not location specific) }; + unsigned int mRecordFlags; std::string mId; std::string mDescription; diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 5bae1ed03e..8d1b755055 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -9,6 +9,7 @@ unsigned int ESM::Filter::sRecordId = REC_FILT; void ESM::Filter::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); while (esm.hasMoreSubs()) { diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index b1c511ebba..78d51cec00 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -12,6 +12,7 @@ namespace ESM { static unsigned int sRecordId; + unsigned int mRecordFlags; std::string mId; std::string mDescription; diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 4ddefc92c7..239cff7c8b 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -11,6 +11,7 @@ namespace ESM void BodyPart::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index bf320330ff..1be775ffec 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -58,6 +58,7 @@ struct BodyPart }; BYDTstruct mData; + unsigned int mRecordFlags; std::string mId, mModel, mRace; void load(ESMReader &esm, bool &isDeleted); diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 1f679af39a..7514f1f85b 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -11,6 +11,7 @@ namespace ESM void BirthSign::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mPowers.mList.clear(); diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 24d27a7f85..806323bf35 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -17,6 +17,7 @@ struct BirthSign /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "BirthSign"; } + unsigned int mRecordFlags; std::string mId, mName, mDescription, mTexture; // List of powers and abilities that come with this birth sign. diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index b76fc57067..7526fe4f52 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -41,6 +41,7 @@ namespace ESM void Class::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 833dd6757d..1000879c4c 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -70,6 +70,7 @@ struct Class ///< Throws an exception for invalid values of \a index. }; // 60 bytes + unsigned int mRecordFlags; std::string mId, mName, mDescription; CLDTstruct mData; diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 3c1a2f1eda..ed3de90b50 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -11,6 +11,7 @@ namespace ESM void Enchantment::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mEffects.mList.clear(); bool hasName = false; diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index b98549ef35..a4e1e8362c 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -42,6 +42,7 @@ struct Enchantment int mFlags; }; + unsigned int mRecordFlags; std::string mId; ENDTstruct mData; EffectList mEffects; diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index bd0962721b..b71348de44 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -29,6 +29,7 @@ namespace ESM void Faction::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mReactions.clear(); for (int i=0;i<10;++i) diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 098ed43096..6a42377901 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -34,6 +34,7 @@ struct Faction /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Faction"; } + unsigned int mRecordFlags; std::string mId, mName; struct FADTstruct diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index 72ecce503c..d2226d1738 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -11,6 +11,7 @@ namespace ESM void Global::load (ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mId = esm.getHNString ("NAME"); diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 0533cc95ea..9dd58e6c67 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -21,6 +21,7 @@ struct Global /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Global"; } + unsigned int mRecordFlags; std::string mId; Variant mValue; diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index da8d256e7d..6d4ac1b202 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -11,6 +11,7 @@ namespace ESM void GameSetting::load (ESMReader &esm, bool &isDeleted) { isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future) + mRecordFlags = esm.getRecordFlags(); mId = esm.getHNString("NAME"); mValue.read (esm, ESM::Variant::Format_Gmst); diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index c40d348fe4..931ee286a4 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -22,6 +22,7 @@ struct GameSetting /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "GameSetting"; } + unsigned int mRecordFlags; std::string mId; Variant mValue; diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 75a94f828a..4bc09920e9 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -189,6 +189,7 @@ namespace ESM void MagicEffect::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) + mRecordFlags = esm.getRecordFlags(); esm.getHNT(mIndex, "INDX"); diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index d718aaccf5..480478d81e 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -16,6 +16,7 @@ struct MagicEffect /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "MagicEffect"; } + unsigned int mRecordFlags; std::string mId; enum Flags diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index ce3cc95bf8..44dbde7742 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -21,6 +21,7 @@ namespace ESM void Race::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mPowers.mList.clear(); diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index d014744472..50fa73ad74 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -65,6 +65,7 @@ struct Race RADTstruct mData; + unsigned int mRecordFlags; std::string mId, mName, mDescription; SpellList mPowers; diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 91ea92e305..e39887a1b1 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -11,6 +11,7 @@ namespace ESM void Region::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; while (esm.hasMoreSubs()) diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 6f39dc0bff..74f6b123ec 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -45,6 +45,7 @@ struct Region WEATstruct mData; int mMapColor; // RGBA + unsigned int mRecordFlags; // sleepList refers to a leveled list of creatures you can meet if // you sleep outside in this region. std::string mId, mName, mSleepList; diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 19602fef62..8715c83dcb 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -83,6 +83,7 @@ namespace ESM void Script::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mVarNames.clear(); diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index e1ffe1b864..d518a048ff 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -35,6 +35,7 @@ public: Script::SCHDstruct mData; }; + unsigned int mRecordFlags; std::string mId; SCHDstruct mData; diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 61cca7d0d7..9f58176f35 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -130,6 +130,7 @@ namespace ESM void Skill::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; // Skill record can't be deleted now (may be changed in the future) + mRecordFlags = esm.getRecordFlags(); bool hasIndex = false; bool hasData = false; diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index 099264fab7..ae44a51045 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -22,6 +22,7 @@ struct Skill /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Skill"; } + unsigned int mRecordFlags; std::string mId; struct SKDTstruct diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index c6ea930a20..c439d0ca66 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -11,6 +11,7 @@ namespace ESM void SoundGenerator::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 70b221e98c..99aae06e0e 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -34,6 +34,7 @@ struct SoundGenerator // Type int mType; + unsigned int mRecordFlags; std::string mId, mCreature, mSound; void load(ESMReader &esm, bool &isDeleted); diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index ccb5f6fdce..ed8fc519a6 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -11,6 +11,7 @@ namespace ESM void Sound::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index 937e22be88..14f1178650 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -21,6 +21,7 @@ struct Sound static std::string getRecordType() { return "Sound"; } SOUNstruct mData; + unsigned int mRecordFlags; std::string mId, mSound; void load(ESMReader &esm, bool &isDeleted); diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 34e146501c..5983cdcdf5 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -11,6 +11,7 @@ namespace ESM void Spell::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mEffects.mList.clear(); diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 1763d0991c..ef74c2c312 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -42,6 +42,7 @@ struct Spell }; SPDTstruct mData; + unsigned int mRecordFlags; std::string mId, mName; EffectList mEffects; diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index f436c32a1c..9e060ab1a7 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -11,6 +11,7 @@ namespace ESM void StartScript::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasData = false; bool hasName = false; diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index ce2ff49e77..3e84027076 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -24,6 +24,7 @@ struct StartScript static std::string getRecordType() { return "StartScript"; } std::string mData; + unsigned int mRecordFlags; std::string mId; // Load a record and add it to the list From 7264a10c072a9abdb9674de275848d5fd8b5109d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 28 Aug 2021 09:49:45 +1000 Subject: [PATCH 1253/2859] Partially revert commit dab1a9e7fbf8c6926405dd09c64e6a309140f92a --- apps/opencs/model/doc/savingstages.cpp | 3 +- apps/opencs/model/world/commanddispatcher.cpp | 73 +++++-------------- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 9da2c2c144..a410d34b2a 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -285,8 +285,7 @@ void CSMDoc::WriteCellCollectionStage::writeReferences (const std::deque& r { refRecord.mRefNum.mIndex = newRefNum++; } - - if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) + else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { // An empty mOriginalCell is meant to indicate that it is the same as diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 3da3e3d22c..36b3ba2e00 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -3,9 +3,6 @@ #include #include -#include -#include - #include #include @@ -142,31 +139,13 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (mLocked) return; - std::unique_ptr clonedData; - std::unique_ptr deleteData; - - std::string newId; - - std::unique_ptr modifyData; std::unique_ptr modifyCell; - QAbstractItemModel* sourceModel = model; - if (IdTableProxyModel* proxy = dynamic_cast (model)) - sourceModel = proxy->sourceModel(); - - CSMWorld::IdTable& table = dynamic_cast(*sourceModel); // for getId() - int stateColumn = table.findColumnIndex(Columns::ColumnId_Modification); - QModelIndex stateIndex = table.getModelIndex(table.getId(index.row()), stateColumn); - RecordBase::State state = static_cast (sourceModel->data(stateIndex).toInt()); + std::unique_ptr modifyDataRefNum; int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); - // This is not guaranteed to be the same as \a model, since a proxy could be used. - IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel(mId)); - - // DeleteCommand triggers a signal to the whole row from IdTable::setData(), so ignore the second call - if (state != RecordBase::State_Deleted && - (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos)) + if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos) { const float oldPosition = model->data (index).toFloat(); @@ -178,9 +157,12 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons int row = proxy ? proxy->mapToSource (index).row() : index.row(); + // This is not guaranteed to be the same as \a model, since a proxy could be used. + IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel (mId)); + int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell); - if (cellColumn != -1) + if (cellColumn!=-1) { QModelIndex cellIndex = model2.index (row, cellColumn); @@ -188,46 +170,31 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (cellId.find ('#')!=std::string::npos) { - RefCollection& collection = mDocument.getData().getReferences(); - newId = collection.getNewId(); + // Need to recalculate the cell and (if necessary) clear the instance's refNum + modifyCell.reset (new UpdateCellCommand (model2, row)); - clonedData.reset(new CloneCommand(table, - table.getId(row), - newId, - CSMWorld::UniversalId::Type_Reference)); + // Not sure which model this should be applied to + int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum); - deleteData.reset(new DeleteCommand(table, - table.getId(row), - CSMWorld::UniversalId::Type_Reference)); + if (refNumColumn!=-1) + modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0)); } } } } - if (!clonedData.get()) - { - // DeleteCommand will trigger executeModify after setting the state to State_Deleted - // from CommandDelegate::setModelDataImp() - ignore - if (state != RecordBase::State_Deleted) - modifyData.reset(new CSMWorld::ModifyCommand(*model, index, new_)); - } + std::unique_ptr modifyData ( + new CSMWorld::ModifyCommand (*model, index, new_)); - if (clonedData.get()) + if (modifyCell.get()) { CommandMacro macro (mDocument.getUndoStack()); - macro.push(clonedData.release()); - macro.push(deleteData.release()); - - // cannot do these earlier because newIndex is not available until CloneCommand is executed - QModelIndex newIndex = model2.getModelIndex (newId, index.column()); - modifyData.reset (new CSMWorld::ModifyCommand (*model, newIndex, new_)); - macro.push(modifyData.release()); - - // once the data is updated update the cell location - modifyCell.reset(new UpdateCellCommand(model2, newIndex.row())); - macro.push(modifyCell.release()); + macro.push (modifyData.release()); + macro.push (modifyCell.release()); + if (modifyDataRefNum.get()) + macro.push (modifyDataRefNum.release()); } - else if (!clonedData.get() && modifyData.get()) + else mDocument.getUndoStack().push (modifyData.release()); } From b2bd97f283884cc446fa8fbb28df327b3a66b261 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 28 Aug 2021 14:09:55 +1000 Subject: [PATCH 1254/2859] A different implementation to MR 1051 to fix Issue #6067. This MR doesn't delete the original record, but instead the original record is updated with the moved record when loading. Issue #4752 is no longer addressed in this MR. --- CHANGELOG.md | 1 - apps/opencs/model/world/refcollection.cpp | 38 ++++++++++++++++++++++- apps/opencs/model/world/refcollection.hpp | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17337f7346..296f7d8d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,6 @@ Bug #4203: Resurrecting an actor should close the loot GUI Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed - Bug #4752: UpdateCellCommand doesn't undo properly Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system Bug #5379: Wandering NPCs falling through cantons diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 4782bde6b9..f70e9c9fa2 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -89,12 +89,48 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool else ref.mCell = cell2.mId; + if (ref.mRefNum.mContentFile != -1 && !base) + { + ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; + ref.mRefNum.mIndex &= 0x00ffffff; + } + unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) | (ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24; std::map::iterator iter = cache.find(refNum); - if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; + if (isMoved) + { + if (iter == cache.end()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, + mCells.getId(cellIndex)); + + messages.add(id, "Attempt to move a non-existent reference - RefNum index " + + std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index " + + std::to_string(ref.mRefNum.mContentFile), + /*hint*/"", + CSMDoc::Message::Severity_Warning); + continue; + } + + int index = getIntIndex(iter->second); + + // ensure we have the same record id for setRecord() + ref.mId = getRecord(index).get().mId; + ref.mIdNum = extractIdNum(ref.mId); + + std::unique_ptr > record(new Record); + // TODO: check whether a base record be moved + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record->mBase : record->mModified) = std::move(ref); + + // overwrite original record + setRecord(index, std::move(record)); + + continue; // NOTE: assumed moved references are not deleted at the same time + } if (isDeleted) { diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index e0e88d721f..2031d2be63 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -25,7 +25,7 @@ namespace CSMWorld class RefCollection : public Collection { Collection& mCells; - std::map mRefIndex; + std::map mRefIndex; // CellRef index keyed by CSMWorld::CellRef::mIdNum int mNextId; From 2eb210f31a29689393aeda67c1dc913ba31e499b Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 28 Aug 2021 14:33:47 +1000 Subject: [PATCH 1255/2859] Partially undo commit 71be4cdd20657b616c772e1f074f157c65e24c45 so that moved references retain the original refnum. This is consistent with vanilla CS's behaviour. --- apps/opencs/model/world/commanddispatcher.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 36b3ba2e00..c5d78b6586 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -141,7 +141,6 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons std::unique_ptr modifyCell; - std::unique_ptr modifyDataRefNum; int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); @@ -170,14 +169,8 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (cellId.find ('#')!=std::string::npos) { - // Need to recalculate the cell and (if necessary) clear the instance's refNum + // Need to recalculate the cell modifyCell.reset (new UpdateCellCommand (model2, row)); - - // Not sure which model this should be applied to - int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum); - - if (refNumColumn!=-1) - modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0)); } } } @@ -191,8 +184,6 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons CommandMacro macro (mDocument.getUndoStack()); macro.push (modifyData.release()); macro.push (modifyCell.release()); - if (modifyDataRefNum.get()) - macro.push (modifyDataRefNum.release()); } else mDocument.getUndoStack().push (modifyData.release()); From b0d5ca386d043297aa6a1786b2a8610d34ab7cc9 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 28 Aug 2021 16:11:47 +1000 Subject: [PATCH 1256/2859] Update original cell column and do not modify the refnum when moving a reference to another cell with 3D editing. --- apps/opencs/view/render/object.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index d0e4dbe04a..789fad0587 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -682,18 +682,20 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) int cellColumn = collection.findColumnIndex (static_cast ( CSMWorld::Columns::ColumnId_Cell)); - int refNumColumn = collection.findColumnIndex (static_cast ( - CSMWorld::Columns::ColumnId_RefNum)); + int origCellColumn = collection.findColumnIndex(static_cast ( + CSMWorld::Columns::ColumnId_OriginalCell)); if (cellIndex != originalIndex) { /// \todo figure out worldspace (not important until multiple worldspaces are supported) + std::string origCellId = CSMWorld::CellCoordinates(originalIndex).getId(""); std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str()))); - commands.push (new CSMWorld::ModifyCommand( *model, - model->index (recordIndex, refNumColumn), 0)); + model->index (recordIndex, origCellColumn), QString::fromUtf8 (origCellId.c_str()))); + commands.push(new CSMWorld::ModifyCommand(*model, + model->index(recordIndex, cellColumn), QString::fromUtf8(cellId.c_str()))); + // NOTE: refnum is not modified for moving a reference to another cell } } From b4486992f02757b871a3a469c1ba0b04485803bd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 28 Aug 2021 10:45:00 +0200 Subject: [PATCH 1257/2859] Allow Rieklings and Goblins to attack again --- 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 62c130b36e..ed26e2b9a4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1653,7 +1653,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) MWRender::Animation::BlendMask_All, false, weapSpeed, startKey, stopKey, 0.0f, 0); - if(mAnimation->isPlaying(mCurrentWeapon)) + if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) mUpperBodyState = UpperCharState_StartToMinAttack; } } From 0a5571f19ee88a6f2c3e4905dd46af59433771ba Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 29 Aug 2021 15:27:59 +1000 Subject: [PATCH 1258/2859] Disable editing for blocked records in both table and dialogue edit widget. --- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 1 + apps/opencs/model/world/idtable.cpp | 8 +++++++ apps/opencs/model/world/refidadapterimp.hpp | 25 +++++++++++++++++++++ apps/opencs/model/world/refidcollection.cpp | 7 ++++-- apps/opencs/view/world/dialoguesubview.cpp | 16 ++++++++++--- 6 files changed, 53 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index cf04d96753..fc39fa8f7d 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -371,6 +371,7 @@ namespace CSMWorld { ColumnId_Skill7, "Skill 7" }, { ColumnId_Persistent, "Persistent" }, + { ColumnId_Blocked, "Blocked" }, { -1, 0 } // end marker }; diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 16482d4898..8cf02b46ac 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -344,6 +344,7 @@ namespace CSMWorld ColumnId_FactionAttrib2 = 312, ColumnId_Persistent = 313, + ColumnId_Blocked = 314, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 6d3882c015..5b4a9b31bc 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -123,6 +123,14 @@ Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const if (mIdCollection->getColumn (index.column()).isUserEditable()) flags |= Qt::ItemIsEditable; + int blockedColumn = searchColumnIndex(Columns::ColumnId_Blocked); + if (blockedColumn != -1 && blockedColumn != index.column()) + { + bool isBlocked = mIdCollection->getData(index.row(), blockedColumn).toInt(); + if (isBlocked) + flags = Qt::ItemIsSelectable; // not enabled (to grey out) + } + return flags; } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 1d1b5a94a6..84fec5bed0 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -25,6 +25,9 @@ namespace CSMWorld const RefIdColumn *mId; const RefIdColumn *mModified; const RefIdColumn *mType; + const RefIdColumn *mBlocked; + + BaseColumns () : mBlocked(nullptr) {} }; /// \brief Base adapter for all refereceable record types @@ -90,6 +93,9 @@ namespace CSMWorld if (column==mBase.mType) return static_cast (mType); + if (column==mBase.mBlocked) + return (record.get().mRecordFlags & ESM::FLAG_Blocked) != 0; + return QVariant(); } @@ -102,6 +108,17 @@ namespace CSMWorld if (column==mBase.mModified) record.mState = static_cast (value.toInt()); + else if (column==mBase.mBlocked) + { + RecordT record2 = record.get(); + + if (value.toInt() != 0) + record2.mRecordFlags |= ESM::FLAG_Blocked; + else + record2.mRecordFlags &= ~ESM::FLAG_Blocked; + + record.setModified(record2); + } } template @@ -110,6 +127,14 @@ namespace CSMWorld return mType; } + // NOTE: Body Part should not have persistence (but BodyPart is not listed in the Objects + // table at the moment). + // + // Spellmaking - not persistent - currently not part of objects table + // Enchanting - not persistent - currently not part of objects table + // + // Leveled Creature - no model, so not persistent + // Leveled Item - no model, so not persistent struct ModelColumns : public BaseColumns { diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 177844a31d..6ffb7969a8 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -49,13 +49,16 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.emplace_back(Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false); baseColumns.mType = &mColumns.back(); + mColumns.emplace_back(Columns::ColumnId_Blocked, ColumnBase::Display_Boolean, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh); + baseColumns.mBlocked = &mColumns.back(); ModelColumns modelColumns (baseColumns); - mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh); - modelColumns.mModel = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean); modelColumns.mPersistence = &mColumns.back(); + mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh); + modelColumns.mModel = &mColumns.back(); NameColumns nameColumns (modelColumns); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index f2360b1378..152472f504 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -538,6 +538,9 @@ void CSVWorld::EditWidget::remake(int row) mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred); mainLayout->addStretch(1); + int blockedColumn = mTable->searchColumnIndex(CSMWorld::Columns::ColumnId_Blocked); + bool isBlocked = mTable->data(mTable->index(row, blockedColumn)).toInt(); + int unlocked = 0; int locked = 0; const int columns = mTable->columnCount(); @@ -583,6 +586,8 @@ void CSVWorld::EditWidget::remake(int row) NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); table->resizeColumnsToContents(); + if (isBlocked) + table->setEditTriggers(QAbstractItemView::NoEditTriggers); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); @@ -617,7 +622,9 @@ void CSVWorld::EditWidget::remake(int row) label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - if (! (mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable)) + // HACK: the blocked checkbox needs to keep the same position + // FIXME: unfortunately blocked record displays a little differently to unblocked one + if (!(mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable) || i == blockedColumn) { lockedLayout->addWidget (label, locked, 0); lockedLayout->addWidget (editor, locked, 1); @@ -639,7 +646,7 @@ void CSVWorld::EditWidget::remake(int row) createEditorContextMenu(editor, display, row); } } - else + else // Flag_Dialogue_List { CSMWorld::IdTree *tree = static_cast(mTable); mNestedTableMapper = new QDataWidgetMapper (this); @@ -686,7 +693,10 @@ void CSVWorld::EditWidget::remake(int row) label->setEnabled(false); } - createEditorContextMenu(editor, display, row); + if (!isBlocked) + createEditorContextMenu(editor, display, row); + else + editor->setEnabled(false); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); From 0ada3fa1d85ec4f3c4d0bbdf5a503555dde8409f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 29 Aug 2021 15:32:45 +1000 Subject: [PATCH 1259/2859] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17337f7346..ef07fe4eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Feature #6032: Reverse-z depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering + Editor: Preserve the "blocked" record flag for referenceable objects. 0.47.0 ------ From a6b1e38eab0fe9e96975834d26b68cfc70294b5e Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 29 Aug 2021 14:12:14 +0200 Subject: [PATCH 1260/2859] Don't use comma where there is no need to. --- apps/openmw/mwgui/bookpage.cpp | 7 +++++-- apps/openmw/mwgui/mapwindow.cpp | 6 ++++-- apps/openmw/mwrender/terrainstorage.cpp | 5 ++++- apps/openmw/mwworld/store.cpp | 6 ++++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 2bfec8105b..874d1d866b 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -491,7 +491,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter { add_partial_text(); stream.consume (); - mLine = nullptr, mRun = nullptr; + mLine = nullptr; + mRun = nullptr; continue; } @@ -551,7 +552,9 @@ struct TypesetBookImpl::Typesetter : BookTypesetter if (left + space_width + word_width > mPageWidth) { - mLine = nullptr, mRun = nullptr, left = 0; + mLine = nullptr; + mRun = nullptr; + left = 0; } else { diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index e42808776c..388bbc7d48 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -988,7 +988,8 @@ namespace MWGui if (mInterior) { auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); - x = pos.x(), y = pos.y(); + x = pos.x(); + y = pos.y(); } setGlobalMapPlayerPosition(x, y); @@ -1160,7 +1161,8 @@ namespace MWGui void MapWindow::worldPosToGlobalMapImageSpace(float x, float y, float& imageX, float& imageY) const { mGlobalMapRender->worldPosToImageSpace(x, y, imageX, imageY); - imageX *= mGlobalMapZoom, imageY *= mGlobalMapZoom; + imageX *= mGlobalMapZoom; + imageY *= mGlobalMapZoom; } void MapWindow::updateCustomMarkers() diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 528ce70ea3..879a2ef68b 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -33,7 +33,10 @@ namespace MWRender void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) { - minX = 0, minY = 0, maxX = 0, maxY = 0; + minX = 0; + minY = 0; + maxX = 0; + maxY = 0; const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index f6de1d485b..718cebd790 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -520,7 +520,8 @@ namespace MWWorld const ESM::Cell *Store::search(int x, int y) const { ESM::Cell cell; - cell.mData.mX = x, cell.mData.mY = y; + cell.mData.mX = x; + cell.mData.mY = y; std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); @@ -538,7 +539,8 @@ namespace MWWorld const ESM::Cell *Store::searchStatic(int x, int y) const { ESM::Cell cell; - cell.mData.mX = x, cell.mData.mY = y; + cell.mData.mX = x; + cell.mData.mY = y; std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); From a15cca5763fa32c3d1b66f288c3ebae196f72d5c Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 29 Aug 2021 19:20:49 +0200 Subject: [PATCH 1261/2859] Use `reserve` on vectors for fixed loops This is a bit useless, but has the merit of appeasing clang-tiday --- apps/openmw/mwgui/class.cpp | 2 ++ apps/openmw/mwrender/screenshotmanager.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index ee5fe59399..1732bc24ea 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -517,6 +517,7 @@ namespace MWGui std::vector CreateClassDialog::getMajorSkills() const { std::vector v; + v.reserve(5); for(int i = 0; i < 5; i++) { v.push_back(mMajorSkill[i]->getSkillId()); @@ -527,6 +528,7 @@ namespace MWGui std::vector CreateClassDialog::getMinorSkills() const { std::vector v; + v.reserve(5); for(int i=0; i < 5; i++) { v.push_back(mMinorSkill[i]->getSkillId()); diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index b9ea5daacf..5a047a1566 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -191,6 +191,7 @@ namespace MWRender screenshotH = screenshotW; // use square resolution for planet mapping std::vector> images; + images.reserve(6); for (int i = 0; i < 6; ++i) images.push_back(new osg::Image); From deb2af6accb3d6c8063f618703a24f03e7681612 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 29 Aug 2021 20:22:34 +0200 Subject: [PATCH 1262/2859] Dont copy-construct from a const-ref when used only as a const-ref This also makes clang-tiny a bit happier --- apps/opencs/view/tools/searchsubview.cpp | 2 +- apps/openmw/mwgui/inventorywindow.cpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 4 ++-- components/resource/bulletshape.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 07ba7907e7..d687cbeb3f 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -36,7 +36,7 @@ void CSVTools::SearchSubView::replace (bool selection) // in a single string. for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) { - CSMWorld::UniversalId id = model.getUniversalId (*iter); + const CSMWorld::UniversalId& id = model.getUniversalId (*iter); CSMWorld::UniversalId::Type type = CSMWorld::UniversalId::getParentType (id.getType()); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index eead3919e0..f53ba21f9f 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -704,7 +704,7 @@ namespace MWGui if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory)) return; // make sure the object is of a type that can be picked up - std::string type = object.getTypeName(); + const std::string& type = object.getTypeName(); if ( (type != typeid(ESM::Apparatus).name()) && (type != typeid(ESM::Armor).name()) && (type != typeid(ESM::Book).name()) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index d96447f87d..0b8af4463c 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -143,7 +143,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, if (allowAutoEquip && actorPtr != MWMechanics::getPlayer() && actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()) { - std::string type = itemPtr.getTypeName(); + const std::string& type = itemPtr.getTypeName(); if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) autoEquip(actorPtr); } @@ -748,7 +748,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) && actor.getClass().isNpc() && !actor.getClass().getNpcStats(actor).isWerewolf()) { - std::string type = item.getTypeName(); + const std::string& type = item.getTypeName(); if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) autoEquip(actor); } diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index ce896610a9..798a6778e6 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -58,7 +58,7 @@ btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *s for(int i = 0;i < numShapes;++i) { btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); - btTransform trans = comp->getChildTransform(i); + const btTransform& trans = comp->getChildTransform(i); newShape->addChildShape(trans, child); } From db4fe11a44cd4ddd232cf22c296dd7d83863f075 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 29 Aug 2021 14:47:08 +0200 Subject: [PATCH 1263/2859] Don't pass a nullptr to a std::string constructor This is undefined behaviour. --- apps/openmw/mwworld/store.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 88edb71ddf..4b1d648703 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -428,7 +428,7 @@ namespace MWWorld const ESM::WeaponType *search(const int id) const; const ESM::WeaponType *find(const int id) const; - RecordId load(ESM::ESMReader &esm) override { return RecordId(nullptr, false); } + RecordId load(ESM::ESMReader &esm) override { return RecordId({}, false); } ESM::WeaponType* insert(const ESM::WeaponType &weaponType); From 33d4d884474d83951445283c58e094cb6043dd3a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 31 Aug 2021 09:37:27 +0200 Subject: [PATCH 1264/2859] Function LuaUtil::toString --- apps/openmw_test_suite/lua/test_lua.cpp | 8 ++++++++ apps/openmw_test_suite/lua/test_utilpackage.cpp | 2 ++ components/lua/luastate.cpp | 10 ++++++++++ components/lua/luastate.hpp | 3 +++ 4 files changed, 23 insertions(+) diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 69a326060c..9405dba4b2 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -77,6 +77,14 @@ return { EXPECT_EQ(LuaUtil::call(script1["get"]).get(), 45); } + TEST_F(LuaStateTest, ToString) + { + EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), 3.14)), "3.14"); + EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), true)), "true"); + EXPECT_EQ(LuaUtil::toString(sol::nil), "nil"); + EXPECT_EQ(LuaUtil::toString(sol::make_object(mLua.sol(), "something")), "\"something\""); + } + TEST_F(LuaStateTest, ErrorHandling) { EXPECT_ERROR(mLua.runInNewSandbox("invalid.lua"), "[string \"invalid.lua\"]:1:"); diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index afd9fa2d3c..fb8e48e461 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -1,6 +1,7 @@ #include "gmock/gmock.h" #include +#include #include #include "testing_util.hpp" @@ -45,6 +46,7 @@ namespace EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 12); EXPECT_FLOAT_EQ(lua.safe_script("return v.z").get(), 13); EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(5, 12, 13)"); + EXPECT_EQ(LuaUtil::toString(lua.safe_script("return v")), "(5, 12, 13)"); EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length()").get(), 5); EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length2()").get(), 25); EXPECT_FALSE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(1, 3, 2)").get()); diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 1b52db11ad..fd42aa7fb2 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -167,4 +167,14 @@ namespace LuaUtil #endif } + std::string toString(const sol::object& obj) + { + if (obj == sol::nil) + return "nil"; + else if (obj.get_type() == sol::type::string) + return "\"" + obj.as() + "\""; + else + return call(sol::state_view(obj.lua_state())["tostring"], obj); + } + } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index 6bac1612e7..acaaadea76 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -103,6 +103,9 @@ namespace LuaUtil return getFieldOrNil(table.as()[first], str...); } + // String representation of a Lua object. Should be used for debugging/logging purposes only. + std::string toString(const sol::object&); + } #endif // COMPONENTS_LUA_LUASTATE_H From d1a5bc207b5d1a63d30bf3e3d5baf23f5b529155 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 31 Aug 2021 16:53:52 +0200 Subject: [PATCH 1265/2859] Iterate over mInactiveCells when unloading cells in TestCells / TestInteriorCells. Otherwise we dereference an invalid iterator after deactiveCell(). --- apps/openmw/mwworld/scene.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 5fa9be1be5..90e5bc265e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -742,14 +742,12 @@ namespace MWWorld { loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); - CellStoreCollection::iterator iter = mActiveCells.begin(); - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); loadInactiveCell (cell, loadingListener, true); activateCell (cell, loadingListener, false, true); - iter = mActiveCells.begin(); - while (iter != mActiveCells.end()) + auto iter = mInactiveCells.begin(); + while (iter != mInactiveCells.end()) { if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) @@ -796,8 +794,8 @@ namespace MWWorld loadInactiveCell (cell, loadingListener, true); activateCell (cell, loadingListener, false, true); - CellStoreCollection::iterator iter = mActiveCells.begin(); - while (iter != mActiveCells.end()) + auto iter = mInactiveCells.begin(); + while (iter != mInactiveCells.end()) { assert (!(*iter)->getCell()->isExterior()); From 0d511da615991a332edc574763f9639fe1dfaaa2 Mon Sep 17 00:00:00 2001 From: unelsson Date: Mon, 30 Aug 2021 00:26:26 +0300 Subject: [PATCH 1266/2859] Test of basic mouse-plane use --- apps/opencs/view/render/instancemode.cpp | 35 ++++++------------------ apps/opencs/view/render/instancemode.hpp | 1 + 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 99ddce7f7d..d511523574 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -305,6 +305,9 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) if (mSubModeId == "move") { objectTag->mObject->setEdited (Object::Override_Position); + mDragStart.x() = objectTag->mObject->getPosition().pos[0]; + mDragStart.y() = objectTag->mObject->getPosition().pos[1]; + mDragStart.z() = objectTag->mObject->getPosition().pos[2]; mDragMode = DragMode_Move; } else if (mSubModeId == "rotate") @@ -392,29 +395,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); - if (mDragMode == DragMode_Move) - { - osg::Vec3f eye, centre, up; - getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); - - if (diffY) - { - offset += up * diffY * speedFactor; - } - if (diffX) - { - offset += ((centre-eye) ^ up) * diffX * speedFactor; - } - - if (mDragAxis!=-1) - { - for (int i=0; i<3; ++i) - { - if (i!=mDragAxis) - offset[i] = 0; - } - } - } + if (mDragMode == DragMode_Move) {} else if (mDragMode == DragMode_Rotate) { osg::Vec3f eye, centre, up; @@ -522,10 +503,10 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou if (mDragMode == DragMode_Move) { ESM::Position position = objectTag->mObject->getPosition(); - for (int i=0; i<3; ++i) - { - position.pos[i] += offset[i]; - } + osg::Vec3d mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); + position.pos[0] = mousePos.x(); + position.pos[1] = mousePos.y(); + position.pos[2] = mousePos.z(); objectTag->mObject->setPosition(position.pos); } diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 73b7fff12a..3eb140a9af 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -45,6 +45,7 @@ namespace CSVRender bool mLocked; float mUnitScaleDist; osg::ref_ptr mParentNode; + osg::Vec3d mDragStart; int getSubModeFromId (const std::string& id) const; From 840e7615f5eaa27e1beb2d1b0f9e137e16dca1b7 Mon Sep 17 00:00:00 2001 From: unelsson Date: Wed, 1 Sep 2021 00:15:10 +0300 Subject: [PATCH 1267/2859] Store object origins and move difference --- apps/opencs/view/render/instancemode.cpp | 24 +++++++++++++++++------- apps/opencs/view/render/instancemode.hpp | 1 + 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index d511523574..5d07bf1422 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -297,6 +297,8 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) return false; } + mObjectsAtDragStart.clear(); + for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) { @@ -305,9 +307,12 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) if (mSubModeId == "move") { objectTag->mObject->setEdited (Object::Override_Position); - mDragStart.x() = objectTag->mObject->getPosition().pos[0]; - mDragStart.y() = objectTag->mObject->getPosition().pos[1]; - mDragStart.z() = objectTag->mObject->getPosition().pos[2]; + double x = objectTag->mObject->getPosition().pos[0]; + double y = objectTag->mObject->getPosition().pos[1]; + double z = objectTag->mObject->getPosition().pos[2]; + osg::Vec3d thisPoint(x, y, z); + mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); + mObjectsAtDragStart.emplace_back(thisPoint); mDragMode = DragMode_Move; } else if (mSubModeId == "rotate") @@ -495,8 +500,10 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou return; } + int i = 0; + // Apply - for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) + for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter, i++) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { @@ -504,9 +511,12 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou { ESM::Position position = objectTag->mObject->getPosition(); osg::Vec3d mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); - position.pos[0] = mousePos.x(); - position.pos[1] = mousePos.y(); - position.pos[2] = mousePos.z(); + float addToX = mousePos.x() - mDragStart.x(); + float addToY = mousePos.y() - mDragStart.y(); + float addToZ = mousePos.z() - mDragStart.z(); + position.pos[0] = mObjectsAtDragStart[i].x() + addToX; + position.pos[1] = mObjectsAtDragStart[i].y() + addToY; + position.pos[2] = mObjectsAtDragStart[i].z() + addToZ; objectTag->mObject->setPosition(position.pos); } diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 3eb140a9af..5ddc5f7c27 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -46,6 +46,7 @@ namespace CSVRender float mUnitScaleDist; osg::ref_ptr mParentNode; osg::Vec3d mDragStart; + std::vector mObjectsAtDragStart; int getSubModeFromId (const std::string& id) const; From 23fe3d74ab9b66f060b684d0509895485c1c5787 Mon Sep 17 00:00:00 2001 From: unelsson Date: Wed, 1 Sep 2021 00:24:50 +0300 Subject: [PATCH 1268/2859] Use floats, not doubles --- apps/opencs/view/render/instancemode.cpp | 10 +++++----- apps/opencs/view/render/instancemode.hpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 5d07bf1422..535179b61f 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -307,10 +307,10 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) if (mSubModeId == "move") { objectTag->mObject->setEdited (Object::Override_Position); - double x = objectTag->mObject->getPosition().pos[0]; - double y = objectTag->mObject->getPosition().pos[1]; - double z = objectTag->mObject->getPosition().pos[2]; - osg::Vec3d thisPoint(x, y, z); + float x = objectTag->mObject->getPosition().pos[0]; + float y = objectTag->mObject->getPosition().pos[1]; + float z = objectTag->mObject->getPosition().pos[2]; + osg::Vec3f thisPoint(x, y, z); mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); mObjectsAtDragStart.emplace_back(thisPoint); mDragMode = DragMode_Move; @@ -510,7 +510,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou if (mDragMode == DragMode_Move) { ESM::Position position = objectTag->mObject->getPosition(); - osg::Vec3d mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); + osg::Vec3f mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); float addToX = mousePos.x() - mDragStart.x(); float addToY = mousePos.y() - mDragStart.y(); float addToZ = mousePos.z() - mDragStart.z(); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 5ddc5f7c27..4ece934e93 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -45,8 +45,8 @@ namespace CSVRender bool mLocked; float mUnitScaleDist; osg::ref_ptr mParentNode; - osg::Vec3d mDragStart; - std::vector mObjectsAtDragStart; + osg::Vec3f mDragStart; + std::vector mObjectsAtDragStart; int getSubModeFromId (const std::string& id) const; From 53d315c862f75121865fbfacb33a1bdee9ebf543 Mon Sep 17 00:00:00 2001 From: unelsson Date: Wed, 1 Sep 2021 01:37:17 +0300 Subject: [PATCH 1269/2859] XYZ-locking, mouse wheel move fix --- apps/opencs/view/render/instancemode.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 535179b61f..ead1a8fdba 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -518,6 +518,16 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou position.pos[1] = mObjectsAtDragStart[i].y() + addToY; position.pos[2] = mObjectsAtDragStart[i].z() + addToZ; + // XYZ-locking + if (mDragAxis != -1) + { + for (int j = 0; j < 3; ++j) + { + if (j != mDragAxis) + position.pos[j] = mObjectsAtDragStart[i][j]; + } + } + objectTag->mObject->setPosition(position.pos); } else if (mDragMode == DragMode_Rotate) @@ -625,8 +635,10 @@ void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); + int j = 0; + for (std::vector >::iterator iter (selection.begin()); - iter!=selection.end(); ++iter) + iter!=selection.end(); ++iter, j++) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { @@ -634,6 +646,9 @@ void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) for (int i=0; i<3; ++i) position.pos[i] += offset[i]; objectTag->mObject->setPosition (position.pos); + osg::Vec3f thisPoint(position.pos[0], position.pos[1], position.pos[2]); + mDragStart = getMousePlaneCoords(getWorldspaceWidget().mapFromGlobal(QCursor::pos()), getProjectionSpaceCoords(thisPoint)); + mObjectsAtDragStart[j] = thisPoint; } } } From f876ff2c14de2204e4dbda30c73d0cc898f5fd26 Mon Sep 17 00:00:00 2001 From: unelsson Date: Wed, 1 Sep 2021 01:56:11 +0300 Subject: [PATCH 1270/2859] Clear temporary movement data at the end of the drag --- apps/opencs/view/render/instancemode.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index ead1a8fdba..ebb7f46fa5 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -609,6 +609,7 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) } } + mObjectsAtDragStart.clear(); mDragMode = DragMode_None; } From 8edcaeb323fb4395537e1ca414a0aa262aea10a5 Mon Sep 17 00:00:00 2001 From: unelsson Date: Wed, 1 Sep 2021 02:03:03 +0300 Subject: [PATCH 1271/2859] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17337f7346..bc9c79411b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Feature #6032: Reverse-z depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering + Feature #6251: OpenMW-CS: Set instance movement based on camera zoom 0.47.0 ------ From d4e3575f1d5b091ec47c55002c6ca0b947cb506f Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 29 Aug 2021 20:13:32 +0200 Subject: [PATCH 1272/2859] Don't use `const` for objects returned by value. This prevents the usage of std::move semantics, and makes clang-tidy sad. --- apps/opencs/model/world/actoradapter.cpp | 4 ++-- apps/opencs/model/world/actoradapter.hpp | 2 +- apps/openmw/engine.cpp | 2 +- components/detournavigator/cachedrecastmeshmanager.cpp | 2 +- components/detournavigator/tilecachedrecastmeshmanager.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 8558aa9bc9..86a621970c 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -121,7 +121,7 @@ namespace CSMWorld return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); } - const std::string ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const + const std::string& ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { auto it = mParts.find(index); if (it == mParts.end()) @@ -131,7 +131,7 @@ namespace CSMWorld if (mFemale) { // Note: we should use male parts for females as fallback - const std::string femalePart = mRaceData->getFemalePart(index); + const std::string& femalePart = mRaceData->getFemalePart(index); if (!femalePart.empty()) return femalePart; } diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 912a6bcb38..df3eeff64e 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -93,7 +93,7 @@ namespace CSMWorld /// Returns the skeleton the actor should use for attaching parts to std::string getSkeleton() const; /// Retrieves the associated actor part - const std::string getPart(ESM::PartReferenceType index) const; + const std::string& getPart(ESM::PartReferenceType index) const; /// Checks if the actor has a data dependency bool hasDependency(const std::string& id) const; diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f968bbfac7..772af3759e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -521,7 +521,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); // load user settings if they exist - const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index e7e5886589..19b87aa820 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -27,7 +27,7 @@ namespace DetourNavigator std::optional CachedRecastMeshManager::removeObject(const ObjectId id) { - const auto object = mImpl.removeObject(id); + auto object = mImpl.removeObject(id); if (object) mCached.lock()->reset(); return object; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 38314f08a5..63d7e13f6b 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -248,7 +248,7 @@ namespace DetourNavigator const auto tile = tiles.find(tilePosition); if (tile == tiles.end()) return std::optional(); - const auto tileResult = tile->second->removeObject(id); + auto tileResult = tile->second->removeObject(id); if (tile->second->isEmpty()) { tiles.erase(tile); From 0bce6c09e1a74e1cd77a8602300201b571af3ee1 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 31 Aug 2021 16:25:45 +0200 Subject: [PATCH 1273/2859] Change projectile behaviour to be like in vanilla wrt. water plane: - enchanted arrow explode upon hit the water plane - non enchanted arrow disappear (or more accurately, they hit nothingness) - enchanted arrow shot underwater explode immediately - non enchanted arrow disappear immediately Also, solve a bug that occured previously and could theoritically still happens where we use the last tested collision position for instead of the last registered hit: Use the hit position as saved inside Projectile::hit() instead of the last position saved inside the callback. If a projectile collides with several objects (bottom of the sea and water surface for instance), the last collision tested won't necessarily be the impact position as we have no control over the order in which the tests are performed. --- apps/openmw/mwphysics/physicssystem.cpp | 6 ++--- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/projectile.cpp | 24 ++----------------- apps/openmw/mwphysics/projectile.hpp | 23 +++++++++++------- .../mwphysics/projectileconvexcallback.cpp | 4 +--- apps/openmw/mwworld/projectilemanager.cpp | 13 +++++----- apps/openmw/mwworld/worldimp.cpp | 1 + 7 files changed, 29 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5b50962be9..e9ab3864fd 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -643,7 +643,7 @@ namespace MWPhysics mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); - const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld); + const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(projectile->getHitPosition()); projectile->setPosition(newpos); mTaskScheduler->updateSingleAabb(foundProjectile->second); } @@ -713,7 +713,7 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); assert(shapeInstance); @@ -721,7 +721,7 @@ namespace MWPhysics mProjectileId++; - auto projectile = std::make_shared(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this); + auto projectile = std::make_shared(caster, position, radius, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b20c8f88e1..14e4b678c5 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -130,7 +130,7 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World, bool skipAnimated = false); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); void setCaster(int projectileId, const MWWorld::Ptr& caster); void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index a8bb444956..4efb245149 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -15,12 +15,10 @@ namespace MWPhysics { -Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) - : mCanCrossWaterSurface(canCrossWaterSurface) - , mCrossedWaterSurface(false) +Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) + : mHitWater(false) , mActive(true) , mHitTarget(nullptr) - , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) { @@ -75,11 +73,6 @@ osg::Vec3f Projectile::getPosition() const return mPosition; } -bool Projectile::canTraverseWater() const -{ - return mCanCrossWaterSurface; -} - void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal) { bool active = true; @@ -143,17 +136,4 @@ bool Projectile::isValidTarget(const btCollisionObject* target) const [target](const btCollisionObject* actor) { return target == actor; }); } -std::optional Projectile::getWaterHitPosition() -{ - return std::exchange(mWaterHitPosition, std::nullopt); -} - -void Projectile::setWaterHitPosition(btVector3 pos) -{ - if (mCrossedWaterSurface) - return; - mCrossedWaterSurface = true; - mWaterHitPosition = pos; -} - } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index dd659b6581..5e4e487c03 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -32,7 +31,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -56,15 +55,25 @@ namespace MWPhysics return mCasterColObj; } - bool canTraverseWater() const; + void setHitWater() + { + mHitWater = true; + } + + bool getHitWater() const + { + return mHitWater; + } void hit(const btCollisionObject* target, btVector3 pos, btVector3 normal); void setValidTargets(const std::vector& targets); bool isValidTarget(const btCollisionObject* target) const; - std::optional getWaterHitPosition(); - void setWaterHitPosition(btVector3 pos); + btVector3 getHitPosition() const + { + return mHitPosition; + } private: @@ -72,13 +81,11 @@ namespace MWPhysics btConvexShape* mConvexShape; bool mTransformUpdatePending; - bool mCanCrossWaterSurface; - bool mCrossedWaterSurface; + bool mHitWater; std::atomic mActive; MWWorld::Ptr mCaster; const btCollisionObject* mCasterColObj; const btCollisionObject* mHitTarget; - std::optional mWaterHitPosition; osg::Vec3f mPosition; btVector3 mHitPosition; btVector3 mHitNormal; diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index 687253e1cc..6520be787d 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -49,9 +49,7 @@ namespace MWPhysics } case CollisionType_Water: { - mProjectile->setWaterHitPosition(m_hitPointWorld); - if (mProjectile->canTraverseWater()) - return 1.f; + mProjectile->setHitWater(); break; } } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 76c6ca7548..25e7f0c7de 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -317,7 +317,7 @@ namespace MWWorld // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape if (state.mIdMagic.size() > 1) model = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get().find(state.mIdMagic[1])->mModel; - state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true, false); + state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -342,7 +342,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false, true); + state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false); state.mToDelete = false; mProjectiles.push_back(state); } @@ -493,9 +493,6 @@ namespace MWWorld auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); - if (const auto hitWaterPos = projectile->getWaterHitPosition()) - mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos)); - const auto pos = projectile->getPosition(); projectileState.mNode->setPosition(pos); @@ -519,6 +516,8 @@ namespace MWWorld if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) bow = *invIt; } + if (projectile->getHitWater()) + mRendering->emitWaterRipple(pos); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); projectileState.mToDelete = true; @@ -663,7 +662,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false, true); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false); } catch(...) { @@ -716,7 +715,7 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true, false); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bc28c2eb7b..3f93aa033a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3145,6 +3145,7 @@ namespace MWWorld bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); if (underwater) { + MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); mRendering->emitWaterRipple(worldPos); return; } From d20730458db884ef0f05c06ab8497772ca15312b Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 12 Aug 2021 20:54:42 +0200 Subject: [PATCH 1274/2859] Replace std::map with std::unordered_map for mActors and mObjects. Use Ptr.mRef as a key instead of Ptr: it is constant for the lifetime of the object. --- apps/openmw/mwphysics/physicssystem.cpp | 109 +++++++++--------------- apps/openmw/mwphysics/physicssystem.hpp | 5 +- 2 files changed, 44 insertions(+), 70 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5b50962be9..d248b599ee 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -181,7 +181,7 @@ namespace MWPhysics void PhysicsSystem::markAsNonSolid(const MWWorld::ConstPtr &ptr) { - ObjectMap::iterator found = mObjects.find(ptr); + ObjectMap::iterator found = mObjects.find(ptr.mRef); if (found == mObjects.end()) return; @@ -198,7 +198,7 @@ namespace MWPhysics if (obj.isEmpty()) return true; // assume standing on terrain (which is a non-object, so not collision tracked) - ObjectMap::const_iterator foundObj = mObjects.find(obj); + ObjectMap::const_iterator foundObj = mObjects.find(obj.mRef); if (foundObj == mObjects.end()) return false; @@ -374,8 +374,8 @@ namespace MWPhysics bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const { - const auto it1 = mActors.find(actor1); - const auto it2 = mActors.find(actor2); + const auto it1 = mActors.find(actor1.mRef); + const auto it2 = mActors.find(actor2.mRef); if (it1 == mActors.end() || it2 == mActors.end()) return false; @@ -441,7 +441,7 @@ namespace MWPhysics { btCollisionObject* me = nullptr; - auto found = mObjects.find(ptr); + auto found = mObjects.find(ptr.mRef); if (found != mObjects.end()) me = found->second->getCollisionObject(); else @@ -464,7 +464,7 @@ namespace MWPhysics osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight) { - ActorMap::iterator found = mActors.find(ptr); + ActorMap::iterator found = mActors.find(ptr.mRef); if (found == mActors.end()) return ptr.getRefData().getPosition().asVec3(); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); @@ -504,7 +504,7 @@ namespace MWPhysics assert(!getObject(ptr)); auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); - mObjects.emplace(ptr, obj); + mObjects.emplace(ptr.mRef, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj.get()); @@ -512,8 +512,7 @@ namespace MWPhysics void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) { if (mUnrefQueue.get()) mUnrefQueue->push(found->second->getShapeInstance()); @@ -522,11 +521,9 @@ namespace MWPhysics mObjects.erase(found); } - - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) { - mActors.erase(foundActor); + mActors.erase(found); } } @@ -539,22 +536,10 @@ namespace MWPhysics void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { - ObjectMap::iterator found = mObjects.find(old); - if (found != mObjects.end()) - { - auto obj = found->second; - obj->updatePtr(updated); - mObjects.erase(found); - mObjects.emplace(updated, std::move(obj)); - } - - auto actorNode = mActors.extract(old); - if (!actorNode.empty()) - { - actorNode.key() = updated; - actorNode.mapped()->updatePtr(updated); - mActors.insert(std::move(actorNode)); - } + if (auto found = mObjects.find(old.mRef); found != mObjects.end()) + found->second->updatePtr(updated); + else if (auto found = mActors.find(old.mRef); found != mActors.end()) + found->second->updatePtr(updated); for (auto& [_, actor] : mActors) { @@ -572,7 +557,7 @@ namespace MWPhysics Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) { - ActorMap::iterator found = mActors.find(ptr); + ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) return found->second.get(); return nullptr; @@ -580,7 +565,7 @@ namespace MWPhysics const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const { - ActorMap::const_iterator found = mActors.find(ptr); + ActorMap::const_iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) return found->second.get(); return nullptr; @@ -588,7 +573,7 @@ namespace MWPhysics const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr &ptr) const { - ObjectMap::const_iterator found = mObjects.find(ptr); + ObjectMap::const_iterator found = mObjects.find(ptr.mRef); if (found != mObjects.end()) return found->second.get(); return nullptr; @@ -604,20 +589,16 @@ namespace MWPhysics void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) { float scale = ptr.getCellRef().getScale(); found->second->setScale(scale); mTaskScheduler->updateSingleAabb(found->second); - return; } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) { - foundActor->second->updateScale(); - mTaskScheduler->updateSingleAabb(foundActor->second); - return; + found->second->updateScale(); + mTaskScheduler->updateSingleAabb(found->second); } } @@ -650,40 +631,32 @@ namespace MWPhysics void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) { found->second->setRotation(rotate); mTaskScheduler->updateSingleAabb(found->second); - return; } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) { - if (!foundActor->second->isRotationallyInvariant()) + if (!found->second->isRotationallyInvariant()) { - foundActor->second->setRotation(rotate); - mTaskScheduler->updateSingleAabb(foundActor->second); + found->second->setRotation(rotate); + mTaskScheduler->updateSingleAabb(found->second); } - return; } } void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr) { - ObjectMap::iterator found = mObjects.find(ptr); - if (found != mObjects.end()) + if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) { found->second->updatePosition(); mTaskScheduler->updateSingleAabb(found->second); - return; } - ActorMap::iterator foundActor = mActors.find(ptr); - if (foundActor != mActors.end()) + else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) { - foundActor->second->updatePosition(); - mTaskScheduler->updateSingleAabb(foundActor->second, true); - return; + found->second->updatePosition(); + mTaskScheduler->updateSingleAabb(found->second, true); } } @@ -710,7 +683,7 @@ namespace MWPhysics auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); - mActors.emplace(ptr, std::move(actor)); + mActors.emplace(ptr.mRef, std::move(actor)); } int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater) @@ -738,7 +711,7 @@ namespace MWPhysics bool PhysicsSystem::toggleCollisionMode() { - ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); + ActorMap::iterator found = mActors.find(MWMechanics::getPlayer().mRef); if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); @@ -753,7 +726,7 @@ namespace MWPhysics void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) { - ActorMap::iterator found = mActors.find(ptr); + ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) found->second->setVelocity(velocity); } @@ -770,13 +743,13 @@ namespace MWPhysics framedata.first.reserve(mActors.size()); framedata.second.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); - for (const auto& [actor, physicActor] : mActors) + for (const auto& [ref, physicActor] : mActors) { auto ptr = physicActor->getPtr(); - if (!actor.getClass().isMobile(ptr)) + if (!ptr.getClass().isMobile(ptr)) continue; float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = actor.getCell(); + const MWWorld::CellStore *cell = ptr.getCell(); if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); @@ -786,7 +759,7 @@ namespace MWPhysics bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (physicActor->getCollisionMode() || !world->isUnderwater(actor.getCell(), actor.getRefData().getPosition().asVec3())) + if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), ptr.getRefData().getPosition().asVec3())) waterCollision = true; } @@ -813,7 +786,7 @@ namespace MWPhysics { if (animatedObject->animateCollisionShapes()) { - auto obj = mObjects.find(animatedObject->getPtr()); + auto obj = mObjects.find(animatedObject->getPtr().mRef); assert(obj != mObjects.end()); mTaskScheduler->updateSingleAabb(obj->second); } @@ -851,7 +824,7 @@ namespace MWPhysics void PhysicsSystem::updateAnimatedCollisionShape(const MWWorld::Ptr& object) { - ObjectMap::iterator found = mObjects.find(object); + ObjectMap::iterator found = mObjects.find(object.mRef); if (found != mObjects.end()) if (found->second->animateCollisionShapes()) mTaskScheduler->updateSingleAabb(found->second); @@ -865,7 +838,7 @@ namespace MWPhysics bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const { - const auto physActor = mActors.find(actor); + const auto physActor = mActors.find(actor.mRef); if (physActor != mActors.end()) return physActor->second->getStandingOnPtr() == object; return false; @@ -943,7 +916,7 @@ namespace MWPhysics bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const { btCollisionObject* object = nullptr; - const auto it = mActors.find(ignore); + const auto it = mActors.find(ignore.mRef); if (it != mActors.end()) object = it->second->getCollisionObject(); const auto bulletPosition = Misc::Convert::toBullet(position); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b20c8f88e1..78569bb0c3 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -56,7 +57,7 @@ namespace MWPhysics class PhysicsTaskScheduler; class Projectile; - using ActorMap = std::map>; + using ActorMap = std::unordered_map>; struct ContactPoint { @@ -272,7 +273,7 @@ namespace MWPhysics std::unique_ptr mShapeManager; Resource::ResourceSystem* mResourceSystem; - using ObjectMap = std::map>; + using ObjectMap = std::unordered_map>; ObjectMap mObjects; std::set mAnimatedObjects; // stores pointers to elements in mObjects From a8c16071dc8977aabf258578bbfd912f91c51701 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 4 Sep 2021 18:04:43 +0200 Subject: [PATCH 1275/2859] Fix -Wreturn-local-addr warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /home/elsid/dev/openmw/apps/opencs/model/world/actoradapter.cpp: In member function ‘const string& CSMWorld::ActorAdapter::ActorData::getPart(ESM::PartReferenceType) const’: /home/elsid/dev/openmw/apps/opencs/model/world/actoradapter.cpp:142:20: error: returning reference to temporary [-Werror=return-local-addr] 142 | return ""; | ^~ --- apps/opencs/model/world/actoradapter.cpp | 7 +++++-- apps/opencs/model/world/actoradapter.hpp | 4 +++- apps/opencs/view/render/actor.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 86a621970c..7882dd4535 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -9,6 +9,9 @@ #include "data.hpp" +#include +#include + namespace CSMWorld { const std::string& ActorAdapter::RaceData::getId() const @@ -121,7 +124,7 @@ namespace CSMWorld return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); } - const std::string& ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const + std::string_view ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { auto it = mParts.find(index); if (it == mParts.end()) @@ -139,7 +142,7 @@ namespace CSMWorld return mRaceData->getMalePart(index); } - return ""; + return {}; } const std::string& partName = it->second.first; diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index df3eeff64e..826e3b9179 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -93,7 +95,7 @@ namespace CSMWorld /// Returns the skeleton the actor should use for attaching parts to std::string getSkeleton() const; /// Retrieves the associated actor part - const std::string& getPart(ESM::PartReferenceType index) const; + std::string_view getPart(ESM::PartReferenceType index) const; /// Checks if the actor has a data dependency bool hasDependency(const std::string& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index d6077a65a5..10f7330d1c 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -96,7 +96,7 @@ namespace CSVRender for (int i = 0; i < ESM::PRT_Count; ++i) { auto type = (ESM::PartReferenceType) i; - std::string partId = mActorData->getPart(type); + const std::string partId(mActorData->getPart(type)); attachBodyPart(type, getBodyPartMesh(partId)); } } From cb08f490d7efb9d4cede6edbfadec1f2af650aca Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 29 Aug 2021 15:15:45 +0200 Subject: [PATCH 1276/2859] Sprinkle some const-ref in loop This was done on the good advices of clang-tidy --- components/resource/keyframemanager.cpp | 2 +- components/shader/shadermanager.cpp | 17 ++++++++--------- components/terrain/buffercache.cpp | 8 ++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 77c31d9ad7..444d2bd7aa 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -30,7 +30,7 @@ namespace Resource std::vector emulatedAnimations; - for (auto animation : mAnimationManager->getAnimationList()) + for (const auto& animation : mAnimationManager->getAnimationList()) { if (animation) { diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 9b057cdfc6..33f79415f1 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -371,11 +371,10 @@ namespace Shader void ShaderManager::setGlobalDefines(DefineMap & globalDefines) { mGlobalDefines = globalDefines; - for (auto shaderMapElement: mShaders) + for (const auto& [key, shader]: mShaders) { - std::string templateId = shaderMapElement.first.first; - ShaderManager::DefineMap defines = shaderMapElement.first.second; - osg::ref_ptr shader = shaderMapElement.second; + std::string templateId = key.first; + ShaderManager::DefineMap defines = key.second; if (shader == nullptr) // I'm not sure how to handle a shader that was already broken as there's no way to get a potential replacement to the nodes that need it. continue; @@ -391,13 +390,13 @@ namespace Shader void ShaderManager::releaseGLObjects(osg::State *state) { std::lock_guard lock(mMutex); - for (auto shader : mShaders) + for (const auto& [_, shader] : mShaders) { - if (shader.second != nullptr) - shader.second->releaseGLObjects(state); + if (shader != nullptr) + shader->releaseGLObjects(state); } - for (auto program : mPrograms) - program.second->releaseGLObjects(state); + for (const auto& [_, program] : mPrograms) + program->releaseGLObjects(state); } } diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index f9eb7ae635..399df16d34 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -245,13 +245,13 @@ namespace Terrain { { std::lock_guard lock(mIndexBufferMutex); - for (auto indexbuffer : mIndexBufferMap) - indexbuffer.second->releaseGLObjects(state); + for (const auto& [_, indexbuffer] : mIndexBufferMap) + indexbuffer->releaseGLObjects(state); } { std::lock_guard lock(mUvBufferMutex); - for (auto uvbuffer : mUvBufferMap) - uvbuffer.second->releaseGLObjects(state); + for (const auto& [_, uvbuffer] : mUvBufferMap) + uvbuffer->releaseGLObjects(state); } } From 1b1deeb59b56738c62dee2fe99aab51cadd8531c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 4 Sep 2021 16:39:06 +0200 Subject: [PATCH 1277/2859] Fail CI build when not allowed warnings are present Put -Wno-error after -Wall to make it work properly for clang. --- CI/before_script.linux.sh | 8 ++++++++ CMakeLists.txt | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 2687946f41..bc0eb0013d 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -14,6 +14,12 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then BUILD_BENCHMARKS=ON fi +CXX_FLAGS='-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy' + +if [[ "${CXX}" == 'clang++' ]]; then + CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments" +fi + declare -a CMAKE_CONF_OPTS=( -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" @@ -24,6 +30,8 @@ declare -a CMAKE_CONF_OPTS=( -DBUILD_SHARED_LIBS=OFF -DUSE_SYSTEM_TINYXML=ON -DCMAKE_INSTALL_PREFIX=install + -DCMAKE_C_FLAGS='-Werror' + -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" ) if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then diff --git a/CMakeLists.txt b/CMakeLists.txt index c733c22ff5..52a824d4e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -512,7 +512,7 @@ endif() if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long") + set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long ${CMAKE_CXX_FLAGS}") add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON ) if (APPLE) From 43538a5ca56955e7466237fa4d5610f5e41efad8 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 6 Sep 2021 00:00:56 +0200 Subject: [PATCH 1278/2859] Support commulative timeseries graph for osg stats --- scripts/osg_stats.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 140a911fea..3397b58f26 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -13,11 +13,14 @@ import statistics import sys import termtables + @click.command() @click.option('--print_keys', is_flag=True, help='Print a list of all present keys in the input file.') @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('--hist', type=str, multiple=True, help='Show a histogram for all values of given metric.') @click.option('--hist_ratio', nargs=2, type=str, multiple=True, @@ -34,6 +37,8 @@ import termtables help='Print table with stats for a given metric containing min, max, mean, median etc.') @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('--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, @@ -42,7 +47,8 @@ import termtables help='End processing at this frame.') @click.argument('path', default='', type=click.Path()) def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, - timeseries_sum, stats_sum, begin_frame, end_frame, path): + timeseries_sum, stats_sum, begin_frame, end_frame, path, + commulative_timeseries, commulative_timeseries_sum): data = list(read_data(path)) keys = collect_unique_keys(data) frames = collect_per_frame(data=data, keys=keys, begin_frame=begin_frame, end_frame=end_frame) @@ -50,7 +56,9 @@ def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, for v in keys: print(v) if timeseries: - draw_timeseries(frames=frames, keys=timeseries, timeseries_sum=timeseries_sum) + draw_timeseries(frames=frames, keys=timeseries, add_sum=timeseries_sum) + if commulative_timeseries: + draw_commulative_timeseries(frames=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum) if hist: draw_hists(frames=frames, keys=hist) if hist_ratio: @@ -105,18 +113,30 @@ def collect_unique_keys(frames): return sorted(result) -def draw_timeseries(frames, keys, timeseries_sum): +def draw_timeseries(frames, keys, add_sum): fig, ax = matplotlib.pyplot.subplots() x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) for key in keys: ax.plot(x, frames[key], label=key) - if timeseries_sum: + if add_sum: ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label='sum') ax.grid(True) ax.legend() fig.canvas.set_window_title('timeseries') +def draw_commulative_timeseries(frames, keys, add_sum): + fig, ax = matplotlib.pyplot.subplots() + x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) + for key in keys: + ax.plot(x, numpy.cumsum(frames[key]), label=key) + if add_sum: + ax.plot(x, numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)), label='sum') + ax.grid(True) + ax.legend() + fig.canvas.set_window_title('commulative_timeseries') + + def draw_hists(frames, keys): fig, ax = matplotlib.pyplot.subplots() bins = numpy.linspace( From 7b26058fa51fc1db24d8583b094d20add4b1a0a1 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Mon, 6 Sep 2021 11:47:21 +0200 Subject: [PATCH 1279/2859] moveObject() has side effects that might invalidate iterators from mActors. Instead of iterating over mActors, make a copy of needed data and iterate over the copies. --- apps/openmw/mwphysics/physicssystem.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index d248b599ee..b3c4ef6168 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -813,12 +813,20 @@ namespace MWPhysics { auto* player = getActor(MWMechanics::getPlayer()); auto* world = MWBase::Environment::get().getWorld(); - for (auto& [ptr, physicActor] : mActors) + + // copy new ptr position in temporary vector. player is handled separately as its movement might change active cell. + std::vector> newPositions; + newPositions.reserve(mActors.size() - 1); + for (const auto& [ptr, physicActor] : mActors) { if (physicActor.get() == player) continue; - world->moveObject(physicActor->getPtr(), physicActor->getSimulationPosition(), false, false); + newPositions.emplace_back(physicActor->getPtr(), physicActor->getSimulationPosition()); } + + for (auto& [ptr, pos] : newPositions) + world->moveObject(ptr, pos, false, false); + world->moveObject(player->getPtr(), player->getSimulationPosition(), false, false); } From b0f772af405c60f74fd98279ef509e4d32495e81 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 25 Aug 2021 20:49:22 +0200 Subject: [PATCH 1280/2859] Define dependency to OSG plugins in one place Each binary depending on components library requires OSG plugins to be linked. Duplicating dependecies for each binary does not give benefits and brings problems when new binary is added. --- apps/opencs/CMakeLists.txt | 25 ------------------------- apps/openmw/CMakeLists.txt | 25 ------------------------- components/CMakeLists.txt | 25 +++++++++++++++++++++++++ 3 files changed, 25 insertions(+), 50 deletions(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 5435de07e2..4b3b8030e6 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -232,31 +232,6 @@ target_link_libraries(openmw-cs components_qt ) -if(OSG_STATIC) - unset(_osg_plugins_static_files) - add_library(openmw_cs_osg_plugins INTERFACE) - foreach(_plugin ${USED_OSG_PLUGINS}) - string(TOUPPER ${_plugin} _plugin_uc) - if(OPENMW_USE_SYSTEM_OSG) - list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) - else() - list(APPEND _osg_plugins_static_files $) - target_link_libraries(openmw_cs_osg_plugins INTERFACE $) - add_dependencies(openmw_cs_osg_plugins ${${_plugin_uc}_LIBRARY}) - endif() - endforeach() - # We use --whole-archive because OSG plugins use registration. - get_whole_archive_options(_opts ${_osg_plugins_static_files}) - target_link_options(openmw_cs_osg_plugins INTERFACE ${_opts}) - target_link_libraries(openmw-cs openmw_cs_osg_plugins) - - if(OPENMW_USE_SYSTEM_OSG) - # OSG plugin pkgconfig files are missing these dependencies. - # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 - target_link_libraries(openmw freetype jpeg png) - endif() -endif(OSG_STATIC) - target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) if (WIN32) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 53e35f3310..5605ff229e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -155,31 +155,6 @@ target_link_libraries(openmw ${LUA_LIBRARIES} ) -if(OSG_STATIC) - unset(_osg_plugins_static_files) - add_library(openmw_osg_plugins INTERFACE) - foreach(_plugin ${USED_OSG_PLUGINS}) - string(TOUPPER ${_plugin} _plugin_uc) - if(OPENMW_USE_SYSTEM_OSG) - list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) - else() - list(APPEND _osg_plugins_static_files $) - target_link_libraries(openmw_osg_plugins INTERFACE $) - add_dependencies(openmw_osg_plugins ${${_plugin_uc}_LIBRARY}) - endif() - endforeach() - # We use --whole-archive because OSG plugins use registration. - get_whole_archive_options(_opts ${_osg_plugins_static_files}) - target_link_options(openmw_osg_plugins INTERFACE ${_opts}) - target_link_libraries(openmw openmw_osg_plugins) - - if(OPENMW_USE_SYSTEM_OSG) - # OSG plugin pkgconfig files are missing these dependencies. - # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 - target_link_libraries(openmw freetype jpeg png) - endif() -endif(OSG_STATIC) - if (ANDROID) target_link_libraries(openmw EGL android log z) endif (ANDROID) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3de864ea52..6e7d6e379d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -304,3 +304,28 @@ endif() set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) + +if(OSG_STATIC) + unset(_osg_plugins_static_files) + add_library(components_osg_plugins INTERFACE) + foreach(_plugin ${USED_OSG_PLUGINS}) + string(TOUPPER ${_plugin} _plugin_uc) + if(OPENMW_USE_SYSTEM_OSG) + list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY}) + else() + list(APPEND _osg_plugins_static_files $) + target_link_libraries(components_osg_plugins INTERFACE $) + add_dependencies(components_osg_plugins ${${_plugin_uc}_LIBRARY}) + endif() + endforeach() + # We use --whole-archive because OSG plugins use registration. + get_whole_archive_options(_opts ${_osg_plugins_static_files}) + target_link_options(components_osg_plugins INTERFACE ${_opts}) + target_link_libraries(components components_osg_plugins) + + if(OPENMW_USE_SYSTEM_OSG) + # OSG plugin pkgconfig files are missing these dependencies. + # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 + target_link_libraries(components freetype jpeg png) + endif() +endif(OSG_STATIC) From e910dd7a25833e69098c08531efa5dcedcf61e01 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 20 Aug 2021 00:09:31 +0200 Subject: [PATCH 1281/2859] Rename CellRef::getRefIdPtr -> getRefIdRef and return reference Return value can't be nullptr. Pointer complicates the code because has to be dereferenced. Also move function definition to hpp to make it easier for compiler to optimize calls. --- apps/openmw/mwlua/actions.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- apps/openmw/mwlua/object.cpp | 6 +++--- apps/openmw/mwworld/cellref.cpp | 5 ----- apps/openmw/mwworld/cellref.hpp | 4 ++-- apps/openmw/mwworld/cells.cpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 4 ++-- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 500ad98490..1f75760f7c 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -71,7 +71,7 @@ namespace MWLua else { const std::string& recordId = std::get(item); - if (old_it != store.end() && *old_it->getCellRef().getRefIdPtr() == recordId) + if (old_it != store.end() && old_it->getCellRef().getRefIdRef() == recordId) return true; // already equipped itemPtr = store.search(recordId); if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index d358979aec..1e009081ff 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -284,7 +284,7 @@ namespace MWLua std::shared_ptr scripts; // When loading a game, it can be called before LuaManager::setPlayer, // so we can't just check ptr == mPlayer here. - if (*ptr.getCellRef().getRefIdPtr() == "player") + if (ptr.getCellRef().getRefIdRef() == "player") { scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); scripts->addPackage("openmw.ui", mUserInterfacePackage); diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 696179d003..d94ce1f49d 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -51,13 +51,13 @@ namespace MWLua bool isMarker(const MWWorld::Ptr& ptr) { - std::string_view id = *ptr.getCellRef().getRefIdPtr(); + std::string_view id = ptr.getCellRef().getRefIdRef(); return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; } std::string_view getMWClassName(const MWWorld::Ptr& ptr) { - if (*ptr.getCellRef().getRefIdPtr() == "player") + if (ptr.getCellRef().getRefIdRef() == "player") return "Player"; if (isMarker(ptr)) return "Marker"; @@ -71,7 +71,7 @@ namespace MWLua res.append(" ("); res.append(getMWClassName(ptr)); res.append(", "); - res.append(*ptr.getCellRef().getRefIdPtr()); + res.append(ptr.getCellRef().getRefIdRef()); res.append(")"); return res; } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 5e91ddcc67..0b16964043 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -48,11 +48,6 @@ namespace MWWorld return mCellRef.mRefID; } - const std::string* CellRef::getRefIdPtr() const - { - return &mCellRef.mRefID; - } - bool CellRef::getTeleport() const { return mCellRef.mTeleport; diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 6a6ac69c57..78170a766f 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -38,8 +38,8 @@ namespace MWWorld // Id of object being referenced std::string getRefId() const; - // Pointer to ID of the object being referenced - const std::string* getRefIdPtr() const; + // Reference to ID of the object being referenced + const std::string& getRefIdRef() const { return mCellRef.mRefID; } // For doors - true if this door teleports to somewhere else, false // if it should open through animation. diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index ad4dfcfb90..d020eace45 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -36,7 +36,7 @@ namespace } bool cont = cell.second.forEach([&] (MWWorld::Ptr ptr) { - if(*ptr.getCellRef().getRefIdPtr() == id) + if (ptr.getCellRef().getRefIdRef() == id) { return visitor(ptr); } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 37c4e178ad..3d82a30c29 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -181,7 +181,7 @@ namespace { for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) - if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID) + if (iter->mRef.getRefNum()==state.mRef.mRefNum && iter->mRef.getRefIdRef() == state.mRef.mRefID) { // overwrite existing reference float oldscale = iter->mRef.getScale(); @@ -417,7 +417,7 @@ namespace MWWorld const std::string *mIdToFind; bool operator()(const PtrType& ptr) { - if (*ptr.getCellRef().getRefIdPtr() == *mIdToFind) + if (ptr.getCellRef().getRefIdRef() == *mIdToFind) { mFound = ptr; return false; From 403f0a72f0b1ace7647f3b2421bc156604bbd16e Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 20 Aug 2021 00:13:39 +0200 Subject: [PATCH 1282/2859] Do not copy RefId when need to compare Makes ContainerStore::stacks ~4x times faster when adding 4k different items in a single frame. --- apps/openmw/mwworld/containerstore.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index cced17688d..b02c2bb407 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -184,7 +184,7 @@ int MWWorld::ContainerStore::count(const std::string &id) const { int total=0; for (const auto&& iter : *this) - if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefIdRef(), id)) total += iter.getRefData().getCount(); return total; } @@ -249,7 +249,7 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const MWWorld::Class& cls1 = ptr1.getClass(); const MWWorld::Class& cls2 = ptr2.getClass(); - if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefId(), ptr2.getCellRef().getRefId())) + if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefIdRef(), ptr2.getCellRef().getRefIdRef())) return false; // If it has an enchantment, don't stack when some of the charge is already used @@ -364,7 +364,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { - if (Misc::StringUtils::ciEqual((*iter).getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefIdRef(), MWWorld::ContainerStore::sGoldId)) { iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); flagAsModified(); @@ -465,7 +465,7 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefIdRef(), itemId)) toRemove -= remove(*iter, toRemove, actor, equipReplacement, resolveFirst); flagAsModified(); @@ -740,7 +740,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) for (auto&& iter : *this) { int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1; - if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefIdRef(), id)) { // Prefer the stack with the lowest remaining uses // Try to get item with zero durability only if there are no other items found From e30709170d10644a6d64e63f54972925ec6cfb33 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 7 Sep 2021 00:04:44 +0200 Subject: [PATCH 1283/2859] Add script to find missing merge requests --- scripts/find_missing_merge_requests.py | 82 ++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100755 scripts/find_missing_merge_requests.py diff --git a/scripts/find_missing_merge_requests.py b/scripts/find_missing_merge_requests.py new file mode 100755 index 0000000000..09d3e9a581 --- /dev/null +++ b/scripts/find_missing_merge_requests.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +import click +import multiprocessing +import pathlib +import requests +import urllib.parse + + +@click.command() +@click.option('--token_path', type=str, default=pathlib.Path.home() / '.gitlab_token', + help='Path to text file with Gitlab token.') +@click.option('--project_id', type=int, default=7107382, + help='Gitlab project id.') +@click.option('--host', type=str, default='gitlab.com', + help='Gitlab host.') +@click.option('--workers', type=int, default=10, + help='Number of parallel workers.') +@click.option('--target_branch', type=str, default='master', + help='Merge request target branch.') +@click.option('--begin_page', type=int, default=1, + help='Begin with given /merge_requests page.') +@click.option('--end_page', type=int, default=4, + help='End before given /merge_requests page.') +@click.option('--per_page', type=int, default=100, + help='Number of merge requests per page.') +def main(token_path, project_id, host, workers, target_branch, begin_page, end_page, per_page): + token = read_token(token_path) + base_url = f'https://{host}/api/v4/projects/{project_id}/' + checked = 0 + filtered = 0 + missing = 0 + for page in range(begin_page, end_page): + merge_requests = requests.get( + url=urllib.parse.urljoin(base_url, 'merge_requests'), + headers={'PRIVATE-TOKEN': token}, + params=dict(state='merged', per_page=per_page, page=page), + ).json() + if not merge_requests: + break + checked += len(merge_requests) + merge_requests = [v for v in merge_requests if v['target_branch'] == target_branch] + if not merge_requests: + continue + filtered += len(merge_requests) + with multiprocessing.Pool(workers) as pool: + missing_merge_requests = pool.map(FilterMissingMergeRequest(token, base_url), merge_requests) + for mr in missing_merge_requests: + if mr is not None: + missing += 1 + print(f"MR {mr['reference']} ({mr['id']}) is missing from branch {mr['target_branch']}," + f" previously was merged as {mr['merge_commit_sha']}") + print(f'Checked {checked} MRs ({filtered} with {target_branch} target branch), {missing} are missing') + + +class FilterMissingMergeRequest: + def __init__(self, token, base_url): + self.token = token + self.base_url = base_url + + def __call__(self, merge_request): + commit_refs = requests.get( + url=urllib.parse.urljoin(self.base_url, f"repository/commits/{merge_request['merge_commit_sha']}/refs"), + headers={'PRIVATE-TOKEN': self.token}, + ).json() + if 'message' in commit_refs and commit_refs['message'] == '404 Commit Not Found': + return merge_request + if not present_in_branch(commit_refs, branch=merge_request['target_branch']): + return merge_request + + +def present_in_branch(commit_refs, branch): + return bool(next((v for v in commit_refs if v['type'] == 'branch' and v['name'] == branch), None)) + + +def read_token(path): + with open(path) as stream: + return stream.readline().strip() + + +if __name__ == '__main__': + main() From 605cb8db7c2f6fa8f9fea4e5ac5641867ba7a20e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 5 Sep 2021 17:43:46 +0200 Subject: [PATCH 1284/2859] Make sync terrain preloading sleep free This reduces average time spent on in. 5 milliseconds as a base precision is quite a lot considering that for 60 FPS frame time is 1000/16 = ~16.67 ms when it's a cell loading frame and there is more important work to do rather than sleeping. --- apps/openmw/mwworld/cellpreloader.cpp | 22 ++++++------- apps/openmw/mwworld/cellpreloader.hpp | 7 ++++- apps/openmw/mwworld/scene.cpp | 21 +++---------- components/CMakeLists.txt | 4 +++ components/loadinglistener/reporter.cpp | 41 +++++++++++++++++++++++++ components/loadinglistener/reporter.hpp | 32 +++++++++++++++++++ components/terrain/quadtreeworld.cpp | 13 +++++--- components/terrain/quadtreeworld.hpp | 2 +- components/terrain/world.hpp | 7 ++++- 9 files changed, 113 insertions(+), 36 deletions(-) create mode 100644 components/loadinglistener/reporter.cpp create mode 100644 components/loadinglistener/reporter.hpp diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index a2167c562f..7b6f640373 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "../mwrender/landmanager.hpp" @@ -157,8 +158,6 @@ namespace MWWorld public: TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) : mAbort(false) - , mProgress(views.size()) - , mProgressRange(0) , mTerrainViews(views) , mWorld(world) , mPreloadPositions(preloadPositions) @@ -178,8 +177,9 @@ namespace MWWorld for (unsigned int i=0; ireset(); - mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mProgress[i], mProgressRange); + mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mLoadingReporter); } + mLoadingReporter.complete(); } void abort() override @@ -187,16 +187,17 @@ namespace MWWorld mAbort = true; } - int getProgress() const { return !mProgress.empty() ? mProgress[0].load() : 0; } - int getProgressRange() const { return !mProgress.empty() && mProgress[0].load() ? mProgressRange : 0; } + void wait(Loading::Listener& listener) const + { + mLoadingReporter.wait(listener); + } private: std::atomic mAbort; - std::vector> mProgress; - int mProgressRange; std::vector > mTerrainViews; Terrain::World* mWorld; std::vector mPreloadPositions; + Loading::Reporter mLoadingReporter; }; /// Worker thread item: update the resource system's cache, effectively deleting unused entries. @@ -415,7 +416,7 @@ namespace MWWorld mUnrefQueue = unrefQueue; } - bool CellPreloader::syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp) + bool CellPreloader::syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener) { if (!mTerrainPreloadItem) return true; @@ -435,9 +436,8 @@ namespace MWWorld } else { - progress = mTerrainPreloadItem->getProgress(); - progressRange = mTerrainPreloadItem->getProgressRange(); - return false; + mTerrainPreloadItem->wait(listener); + return true; } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index e719f2e606..e2eea33146 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -29,6 +29,11 @@ namespace MWRender class LandManager; } +namespace Loading +{ + class Listener; +} + namespace MWWorld { class CellStore; @@ -72,7 +77,7 @@ namespace MWWorld typedef std::pair PositionCellGrid; void setTerrainPreloadPositions(const std::vector& positions); - bool syncTerrainLoad(const std::vector &positions, int& progress, int& progressRange, double timestamp); + bool syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener); void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); private: diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 90e5bc265e..399d0e6d7f 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1250,23 +1250,10 @@ namespace MWWorld Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); - int progress = 0, initialProgress = -1, progressRange = 0; - while (!mPreloader->syncTerrainLoad(vec, progress, progressRange, mRendering.getReferenceTime())) - { - if (initialProgress == -1) - { - loadingListener->setLabel("#{sLoadingMessage4}"); - initialProgress = progress; - } - if (progress) - { - loadingListener->setProgressRange(std::max(0, progressRange-initialProgress)); - loadingListener->setProgress(progress-initialProgress); - } - else - loadingListener->setProgress(0); - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } + + loadingListener->setLabel("#{sLoadingMessage4}"); + + while (!mPreloader->syncTerrainLoad(vec, mRendering.getReferenceTime(), *loadingListener)) {} } void Scene::reloadTerrain() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6e7d6e379d..7983de6190 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -197,6 +197,10 @@ add_component_dir(detournavigator navmeshcacheitem ) +add_component_dir(loadinglistener + reporter + ) + set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) diff --git a/components/loadinglistener/reporter.cpp b/components/loadinglistener/reporter.cpp new file mode 100644 index 0000000000..0ad04fded1 --- /dev/null +++ b/components/loadinglistener/reporter.cpp @@ -0,0 +1,41 @@ +#include "reporter.hpp" +#include "loadinglistener.hpp" + +#include +#include +#include + +namespace Loading +{ + void Reporter::addTotal(std::size_t value) + { + const std::lock_guard lock(mMutex); + mTotal += value; + mUpdated.notify_all(); + } + + void Reporter::addProgress(std::size_t value) + { + const std::lock_guard lock(mMutex); + mProgress += value; + mUpdated.notify_all(); + } + + void Reporter::complete() + { + const std::lock_guard lock(mMutex); + mDone = true; + mUpdated.notify_all(); + } + + void Reporter::wait(Listener& listener) const + { + std::unique_lock lock(mMutex); + while (!mDone) + { + listener.setProgressRange(mTotal); + listener.setProgress(mProgress); + mUpdated.wait(lock); + } + } +} diff --git a/components/loadinglistener/reporter.hpp b/components/loadinglistener/reporter.hpp new file mode 100644 index 0000000000..b59c519082 --- /dev/null +++ b/components/loadinglistener/reporter.hpp @@ -0,0 +1,32 @@ +#ifndef COMPONENTS_LOADINGLISTENER_REPORTER_H +#define COMPONENTS_LOADINGLISTENER_REPORTER_H + +#include +#include +#include + +namespace Loading +{ + class Listener; + + class Reporter + { + public: + void addTotal(std::size_t value); + + void addProgress(std::size_t value); + + void complete(); + + void wait(Listener& listener) const; + + private: + std::size_t mProgress = 0; + std::size_t mTotal = 0; + bool mDone = false; + mutable std::mutex mMutex; + mutable std::condition_variable mUpdated; + }; +} + +#endif diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index e26fc1b617..9ca504ebeb 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -496,7 +497,7 @@ View* QuadTreeWorld::createView() return mViewDataMap->createIndependentView(); } -void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, std::atomic &progress, int& progressTotal) +void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, Loading::Reporter& reporter) { ensureQuadTreeBuilt(); @@ -506,16 +507,18 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); - if (!progressTotal) - for (unsigned int i=0; igetNumEntries(); ++i) - progressTotal += vd->getEntry(i).mNode->getSize(); + std::size_t progressTotal = 0; + for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) + progressTotal += vd->getEntry(i).mNode->getSize(); + + reporter.addTotal(progressTotal); const float cellWorldSize = mStorage->getCellWorldSize(); for (unsigned int i=0; igetNumEntries() && !abort; ++i) { ViewData::Entry& entry = vd->getEntry(i); loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true); - progress += entry.mNode->getSize(); + reporter.addProgress(entry.mNode->getSize()); } vd->markUnchanged(); } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 2ddea42049..3a2aa8349d 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -39,7 +39,7 @@ namespace Terrain void unloadCell(int x, int y) override; View* createView() override; - void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, std::atomic& progress, int& progressRange) override; + void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) override; bool storeView(const View* view, double referenceTime) override; void rebuildViews() override; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 5797d894ef..b62a1cb568 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -32,6 +32,11 @@ namespace SceneUtil class WorkQueue; } +namespace Loading +{ + class Reporter; +} + namespace Terrain { class Storage; @@ -148,7 +153,7 @@ namespace Terrain /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. - virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort, std::atomic& progress, int& progressRange) {} + virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) {} /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. /// @note Not thread safe. From b19da7f6505a334d5ed33af20be9620de8947aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Tue, 7 Sep 2021 18:57:03 +0000 Subject: [PATCH 1285/2859] Update an obsolete link pointing to teh old wiki. The information is moved to the new wiki at gitlab and the link now points there. --- docs/source/manuals/installation/install-openmw.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-openmw.rst b/docs/source/manuals/installation/install-openmw.rst index fab00ab9b7..362637746d 100644 --- a/docs/source/manuals/installation/install-openmw.rst +++ b/docs/source/manuals/installation/install-openmw.rst @@ -18,7 +18,7 @@ and run the install package once downloaded. It's now installed! The (bleeding edge) Source Way ============================== -Visit the `Development Environment Setup `_ +Visit the `Development Environment Setup `_ section of the Wiki for detailed instructions on how to build the engine. The Ubuntu Way From bdbc6c0cbae128f0e65b0698ffc4fff0a18c7f99 Mon Sep 17 00:00:00 2001 From: JanuarySnow Date: Wed, 8 Sep 2021 10:07:54 +0000 Subject: [PATCH 1286/2859] Dead animation code removal --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 114 +---------------------------- 3 files changed, 4 insertions(+), 112 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index beb2b490f2..abc3f1a8d1 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -99,6 +99,7 @@ Programmers James Stephens (james-h-stephens) Jan-Peter Nilsson (peppe) Jan Borsodi (am0s) + JanuarySnow Jason Hooks (jhooks) jeaye jefetienne diff --git a/CHANGELOG.md b/CHANGELOG.md index bc9c79411b..3ad818bd25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering Feature #6251: OpenMW-CS: Set instance movement based on camera zoom + Task #6264: Remove the old classes in animation.cpp 0.47.0 ------ diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 591e666d01..a04724c70a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -196,32 +196,6 @@ namespace return 0.0f; } - /// @brief Base class for visitors that remove nodes from a scene graph. - /// Subclasses need to fill the mToRemove vector. - /// To use, node->accept(removeVisitor); removeVisitor.remove(); - class RemoveVisitor : public osg::NodeVisitor - { - public: - RemoveVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { - } - - void remove() - { - for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) - { - if (!it->second->removeChild(it->first)) - Log(Debug::Error) << "Error removing " << it->first->getName(); - } - } - - protected: - // - typedef std::vector > RemoveVec; - std::vector > mToRemove; - }; - class GetExtendedBonesVisitor : public osg::NodeVisitor { public: @@ -244,7 +218,7 @@ namespace std::vector > mFoundBones; }; - class RemoveFinishedCallbackVisitor : public RemoveVisitor + class RemoveFinishedCallbackVisitor : public SceneUtil::RemoveVisitor { public: bool mHasMagicEffects; @@ -289,7 +263,7 @@ namespace } }; - class RemoveCallbackVisitor : public RemoveVisitor + class RemoveCallbackVisitor : public SceneUtil::RemoveVisitor { public: bool mHasMagicEffects; @@ -397,90 +371,6 @@ namespace private: int mEffectId; }; - - // Removes all drawables from a graph. - class CleanObjectRootVisitor : public RemoveVisitor - { - public: - void apply(osg::Drawable& drw) override - { - applyDrawable(drw); - } - - void apply(osg::Group& node) override - { - applyNode(node); - } - void apply(osg::MatrixTransform& node) override - { - applyNode(node); - } - void apply(osg::Node& node) override - { - applyNode(node); - } - - void applyNode(osg::Node& node) - { - if (node.getStateSet()) - node.setStateSet(nullptr); - - if (node.getNodeMask() == 0x1 && node.getNumParents() == 1) - mToRemove.emplace_back(&node, node.getParent(0)); - else - traverse(node); - } - void applyDrawable(osg::Node& node) - { - osg::NodePath::iterator parent = getNodePath().end()-2; - // We know that the parent is a Group because only Groups can have children. - osg::Group* parentGroup = static_cast(*parent); - - // Try to prune nodes that would be empty after the removal - if (parent != getNodePath().begin()) - { - // This could be extended to remove the parent's parent, and so on if they are empty as well. - // But for NIF files, there won't be a benefit since only TriShapes can be set to STATIC dataVariance. - osg::Group* parentParent = static_cast(*(parent - 1)); - if (parentGroup->getNumChildren() == 1 && parentGroup->getDataVariance() == osg::Object::STATIC) - { - mToRemove.emplace_back(parentGroup, parentParent); - return; - } - } - - mToRemove.emplace_back(&node, parentGroup); - } - }; - - class RemoveTriBipVisitor : public RemoveVisitor - { - public: - void apply(osg::Drawable& drw) override - { - applyImpl(drw); - } - - void apply(osg::Group& node) override - { - traverse(node); - } - void apply(osg::MatrixTransform& node) override - { - traverse(node); - } - - void applyImpl(osg::Node& node) - { - const std::string toFind = "tri bip"; - if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) - { - osg::Group* parent = static_cast(*(getNodePath().end()-2)); - // Not safe to remove in apply(), since the visitor is still iterating the child list - mToRemove.emplace_back(&node, parent); - } - } - }; } namespace MWRender From d36c373cc71b907cdeb598690718f682524d88db Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 8 Sep 2021 17:23:35 +0000 Subject: [PATCH 1287/2859] visitor.cpp early out --- components/sceneutil/visitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index f1f15f786f..3fc1898959 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -32,13 +32,13 @@ namespace SceneUtil void FindByNameVisitor::apply(osg::Group &group) { - if (!checkGroup(group)) + if (!mFoundNode && !checkGroup(group)) traverse(group); } void FindByNameVisitor::apply(osg::MatrixTransform &node) { - if (!checkGroup(node)) + if (!mFoundNode && !checkGroup(node)) traverse(node); } From 5ab5419f7bc68653f7eb928c4831c19278d533bb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 8 Sep 2021 21:08:04 +0000 Subject: [PATCH 1288/2859] Remove unsafe characters from zip filenames on Windows --- .gitlab-ci.yml | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8d82d6ed79..c9b8cf9341 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -223,6 +223,18 @@ variables: &tests-targets - choco install ninja -y - choco install python -y - refreshenv + - | + function Make-SafeFileName { + param( + [Parameter(Mandatory=$true)] + [String] + $FileName + ) + [IO.Path]::GetInvalidFileNameChars() | ForEach-Object { + $FileName = $FileName.Replace($_, '_') + } + return $FileName + } stage: build script: - $time = (Get-Date -Format "HH:mm:ss") @@ -237,10 +249,10 @@ variables: &tests-targets - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log @@ -326,6 +338,18 @@ Windows_Ninja_Tests_RelWithDebInfo: - choco install vswhere -y - choco install python -y - refreshenv + - | + function Make-SafeFileName { + param( + [Parameter(Mandatory=$true)] + [String] + $FileName + ) + [IO.Path]::GetInvalidFileNameChars() | ForEach-Object { + $FileName = $FileName.Replace($_, '_') + } + return $FileName + } stage: build script: - $time = (Get-Date -Format "HH:mm:ss") @@ -339,10 +363,10 @@ Windows_Ninja_Tests_RelWithDebInfo: - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log From dc623597b4155459bfad0b32f8f3d805dcc13e92 Mon Sep 17 00:00:00 2001 From: "Hristos N. Triantafillou" Date: Wed, 8 Sep 2021 19:19:40 -0500 Subject: [PATCH 1289/2859] This is the right path for saves --- docs/source/reference/modding/paths.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/paths.rst b/docs/source/reference/modding/paths.rst index 97cfe37a5c..bc955b703d 100644 --- a/docs/source/reference/modding/paths.rst +++ b/docs/source/reference/modding/paths.rst @@ -29,7 +29,7 @@ Savegames +--------------+-----------------------------------------------------------------------------------------------------+ | OS | Location | +==============+=====================================================================================================+ -| Linux | ``$HOME/.config/openmw/saves`` | +| Linux | ``$HOME/.local/share/openmw/saves`` | +--------------+-----------------------------------------------------------------------------------------------------+ | Mac | ``$HOME/Library/Application\ Support/openmw/saves`` | +--------------+---------------+-------------------------------------------------------------------------------------+ From 6b7434ca696d59f5c0400b25a0efb7511aab6121 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 4 Sep 2021 18:07:23 +0200 Subject: [PATCH 1290/2859] Pass std::string_view instead of const std::string& * Starting with Actor::getBodyPartMesh and ending with Misc::StringUtils::ciEqual. * Add tests for Misc::StringUtils::ciEqual. --- apps/opencs/model/world/actoradapter.cpp | 3 +- apps/opencs/model/world/collection.hpp | 5 +- apps/opencs/model/world/collectionbase.hpp | 3 +- apps/opencs/model/world/columns.cpp | 2 +- apps/opencs/model/world/infocollection.cpp | 6 +- apps/opencs/model/world/infocollection.hpp | 5 +- apps/opencs/model/world/refcollection.cpp | 4 +- apps/opencs/model/world/refcollection.hpp | 3 +- apps/opencs/model/world/refidcollection.cpp | 3 +- apps/opencs/model/world/refidcollection.hpp | 3 +- apps/opencs/model/world/refiddata.cpp | 4 +- apps/opencs/model/world/refiddata.hpp | 3 +- apps/opencs/model/world/resources.cpp | 3 +- apps/opencs/model/world/resources.hpp | 3 +- apps/opencs/view/render/actor.cpp | 4 +- apps/opencs/view/render/actor.hpp | 3 +- apps/openmw/mwgui/messagebox.cpp | 4 +- .../openmw_test_suite/misc/test_stringops.cpp | 102 +++++++++++++++++- components/misc/stringops.hpp | 44 +++++--- components/sceneutil/visitor.cpp | 5 +- 20 files changed, 170 insertions(+), 42 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 7882dd4535..7e7f926384 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -145,8 +145,7 @@ namespace CSMWorld return {}; } - const std::string& partName = it->second.first; - return partName; + return it->second.first; } bool ActorAdapter::ActorData::hasDependency(const std::string& id) const diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 9d9c69a5da..6ab9d7ff9d 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -153,7 +154,7 @@ namespace CSMWorld ///< Change the state of a record from base to modified, if it is not already. /// \return True if the record was changed. - int searchId (const std::string& id) const override; + int searchId(std::string_view id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) @@ -476,7 +477,7 @@ namespace CSMWorld } template - int Collection::searchId (const std::string& id) const + int Collection::searchId(std::string_view id) const { std::string id2 = Misc::StringUtils::lowerCase(id); diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index 13471b9886..be6131ee52 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "universalid.hpp" #include "columns.hpp" @@ -61,7 +62,7 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None) = 0; ///< \param type Will be ignored, unless the collection supports multiple record types - virtual int searchId (const std::string& id) const = 0; + virtual int searchId(std::string_view id) const = 0; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index cf04d96753..d6066aa04d 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -391,7 +391,7 @@ int CSMWorld::Columns::getId (const std::string& name) std::string name2 = Misc::StringUtils::lowerCase (name); for (int i=0; sNames[i].mName; ++i) - if (Misc::StringUtils::ciEqual(sNames[i].mName, name2)) + if (Misc::StringUtils::ciEqual(std::string_view(sNames[i].mName), name2)) return sNames[i].mId; return -1; diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 978ce3595d..d58a8327f2 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -97,7 +97,7 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) } } -int CSMWorld::InfoCollection::getInfoIndex (const std::string& id, const std::string& topic) const +int CSMWorld::InfoCollection::getInfoIndex(std::string_view id, std::string_view topic) const { // find the topic first std::unordered_map > >::const_iterator iter @@ -345,12 +345,12 @@ void CSMWorld::InfoCollection::appendBlankRecord (const std::string& id, Univer insertRecord(std::move(record2), getInsertIndex(id, type, nullptr), type); // call InfoCollection::insertRecord() } -int CSMWorld::InfoCollection::searchId (const std::string& id) const +int CSMWorld::InfoCollection::searchId(std::string_view id) const { std::string::size_type separator = id.find_last_of('#'); if (separator == std::string::npos) - throw std::runtime_error("invalid info ID: " + id); + throw std::runtime_error("invalid info ID: " + std::string(id)); return getInfoIndex(id.substr(separator+1), id.substr(0, separator)); } diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 3e8455c399..96061fb03c 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_INFOCOLLECTION_H #include +#include #include "collection.hpp" #include "info.hpp" @@ -43,7 +44,7 @@ namespace CSMWorld void load (const Info& record, bool base); - int getInfoIndex (const std::string& id, const std::string& topic) const; + int getInfoIndex(std::string_view id, std::string_view topic) const; ///< Return index for record \a id or -1 (if not present; deleted records are considered) /// /// \param id info ID without topic prefix @@ -79,7 +80,7 @@ namespace CSMWorld void appendBlankRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None) override; - int searchId (const std::string& id) const override; + int searchId(std::string_view id) const override; void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None) override; diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 4782bde6b9..4f56bbb463 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -7,6 +7,8 @@ #include "universalid.hpp" #include "record.hpp" +#include + namespace CSMWorld { template<> @@ -261,7 +263,7 @@ void CSMWorld::RefCollection::cloneRecord (const std::string& origin, insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord() } -int CSMWorld::RefCollection::searchId (const std::string& id) const +int CSMWorld::RefCollection::searchId(std::string_view id) const { return searchId(extractIdNum(id)); } diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index e0e88d721f..34e258c11b 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_REFCOLLECTION_H #include +#include #include "../doc/stage.hpp" @@ -56,7 +57,7 @@ namespace CSMWorld const std::string& destination, const UniversalId::Type type); - virtual int searchId (const std::string& id) const; + virtual int searchId(std::string_view id) const; virtual void appendRecord (std::unique_ptr record, UniversalId::Type type = UniversalId::Type_None); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 177844a31d..71629694c0 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -787,7 +788,7 @@ void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, Univer mData.appendRecord (type, id, false); } -int CSMWorld::RefIdCollection::searchId (const std::string& id) const +int CSMWorld::RefIdCollection::searchId(std::string_view id) const { RefIdData::LocalIndex localIndex = mData.searchId (id); diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index dc722055ff..ee17bb3214 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "columnbase.hpp" #include "collectionbase.hpp" @@ -85,7 +86,7 @@ namespace CSMWorld void appendBlankRecord (const std::string& id, UniversalId::Type type) override; ///< \param type Will be ignored, unless the collection supports multiple record types - int searchId (const std::string& id) const override; + int searchId(std::string_view id) const override; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index df8e06c251..3bd8bfd5fc 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -2,6 +2,7 @@ #include #include +#include CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} @@ -74,8 +75,7 @@ int CSMWorld::RefIdData::localToGlobalIndex (const LocalIndex& index) return globalIndex; } -CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId ( - const std::string& id) const +CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId(std::string_view id) const { std::string id2 = Misc::StringUtils::lowerCase (id); diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 175aaef410..b9dee80638 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -277,7 +278,7 @@ namespace CSMWorld int localToGlobalIndex (const LocalIndex& index) const; - LocalIndex searchId (const std::string& id) const; + LocalIndex searchId(std::string_view id) const; void erase (int index, int count); diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index c3eb9762e7..2544886f3e 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -83,7 +84,7 @@ int CSMWorld::Resources::getIndex (const std::string& id) const return index; } -int CSMWorld::Resources::searchId (const std::string& id) const +int CSMWorld::Resources::searchId(std::string_view id) const { std::string id2 = Misc::StringUtils::lowerCase (id); diff --git a/apps/opencs/model/world/resources.hpp b/apps/opencs/model/world/resources.hpp index c217b793d3..2de13a259e 100644 --- a/apps/opencs/model/world/resources.hpp +++ b/apps/opencs/model/world/resources.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "universalid.hpp" @@ -35,7 +36,7 @@ namespace CSMWorld int getIndex (const std::string& id) const; - int searchId (const std::string& id) const; + int searchId(std::string_view id) const; UniversalId::Type getType() const; }; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 10f7330d1c..271ca2365a 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -96,7 +96,7 @@ namespace CSVRender for (int i = 0; i < ESM::PRT_Count; ++i) { auto type = (ESM::PartReferenceType) i; - const std::string partId(mActorData->getPart(type)); + const std::string_view partId = mActorData->getPart(type); attachBodyPart(type, getBodyPartMesh(partId)); } } @@ -115,7 +115,7 @@ namespace CSVRender } } - std::string Actor::getBodyPartMesh(const std::string& bodyPartId) + std::string Actor::getBodyPartMesh(std::string_view bodyPartId) { const auto& bodyParts = mData.getBodyParts(); diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 2f19454f78..8172e6fff7 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -2,6 +2,7 @@ #define OPENCS_VIEW_RENDER_ACTOR_H #include +#include #include @@ -54,7 +55,7 @@ namespace CSVRender void loadBodyParts(); void attachBodyPart(ESM::PartReferenceType, const std::string& mesh); - std::string getBodyPartMesh(const std::string& bodyPartId); + std::string getBodyPartMesh(std::string_view bodyPartId); static const std::string MeshPrefix; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 4907a247ff..ed6633c983 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -376,7 +376,9 @@ namespace MWGui { for (const std::string& keyword : keywords) { - if(Misc::StringUtils::ciEqual(MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}"), button->getCaption())) + if (Misc::StringUtils::ciEqual( + MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}").asUTF8(), + button->getCaption().asUTF8())) { return button; } diff --git a/apps/openmw_test_suite/misc/test_stringops.cpp b/apps/openmw_test_suite/misc/test_stringops.cpp index 086908692d..173cfa4447 100644 --- a/apps/openmw_test_suite/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/misc/test_stringops.cpp @@ -1,6 +1,10 @@ #include #include "components/misc/stringops.hpp" +#include +#include +#include + struct PartialBinarySearchTest : public ::testing::Test { protected: @@ -12,10 +16,6 @@ struct PartialBinarySearchTest : public ::testing::Test std::sort(mDataVec.begin(), mDataVec.end(), Misc::StringUtils::ciLess); } - void TearDown() override - { - } - bool matches(const std::string& keyword) { return Misc::StringUtils::partialBinarySearch(mDataVec.begin(), mDataVec.end(), keyword) != mDataVec.end(); @@ -51,3 +51,97 @@ TEST_F (PartialBinarySearchTest, ci_test) std::string unicode1 = "\u04151 \u0418"; // CYRILLIC CAPITAL LETTER IE, CYRILLIC CAPITAL LETTER I EXPECT_TRUE( Misc::StringUtils::lowerCase(unicode1) == unicode1 ); } + +namespace +{ + using ::Misc::StringUtils; + using namespace ::testing; + + template + struct MiscStringUtilsCiEqualEmptyTest : Test {}; + + TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualEmptyTest); + + TYPED_TEST_P(MiscStringUtilsCiEqualEmptyTest, empty_strings_should_be_equal) + { + EXPECT_TRUE(StringUtils::ciEqual(typename TypeParam::first_type {}, typename TypeParam::second_type {})); + } + + REGISTER_TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualEmptyTest, + empty_strings_should_be_equal + ); + + using EmptyStringTypePairsTypes = Types< + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair + >; + + INSTANTIATE_TYPED_TEST_SUITE_P(EmptyStringTypePairs, MiscStringUtilsCiEqualEmptyTest, EmptyStringTypePairsTypes); + + template + struct MiscStringUtilsCiEqualNotEmptyTest : Test {}; + + TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualNotEmptyTest); + + using RawValue = const char[4]; + + constexpr RawValue foo = "foo"; + constexpr RawValue fooUpper = "FOO"; + constexpr RawValue bar = "bar"; + + template + using Value = std::conditional_t, RawValue&, T>; + + TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, same_strings_should_be_equal) + { + const Value a {foo}; + const Value b {foo}; + EXPECT_TRUE(StringUtils::ciEqual(a, b)) << a << "\n" << b; + } + + TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, same_strings_with_different_case_sensetivity_should_be_equal) + { + const Value a {foo}; + const Value b {fooUpper}; + EXPECT_TRUE(StringUtils::ciEqual(a, b)) << a << "\n" << b; + } + + TYPED_TEST_P(MiscStringUtilsCiEqualNotEmptyTest, different_strings_content_should_not_be_equal) + { + const Value a {foo}; + const Value b {bar}; + EXPECT_FALSE(StringUtils::ciEqual(a, b)) << a << "\n" << b; + } + + REGISTER_TYPED_TEST_SUITE_P(MiscStringUtilsCiEqualNotEmptyTest, + same_strings_should_be_equal, + same_strings_with_different_case_sensetivity_should_be_equal, + different_strings_content_should_not_be_equal + ); + + using NotEmptyStringTypePairsTypes = Types< + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair + >; + + INSTANTIATE_TYPED_TEST_SUITE_P(NotEmptyStringTypePairs, MiscStringUtilsCiEqualNotEmptyTest, NotEmptyStringTypePairsTypes); + + TEST(MiscStringUtilsCiEqualTest, string_with_different_length_should_not_be_equal) + { + EXPECT_FALSE(StringUtils::ciEqual(std::string("a"), std::string("aa"))); + } +} diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 2a865606fd..0863522356 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "utf8stream.hpp" @@ -109,18 +111,34 @@ public: return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); } - static bool ciEqual(const std::string &x, const std::string &y) { - if (x.size() != y.size()) { + template + static bool ciEqual(const X& x, const Y& y) + { + if (std::size(x) != std::size(y)) return false; - } - std::string::const_iterator xit = x.begin(); - std::string::const_iterator yit = y.begin(); - for (; xit != x.end(); ++xit, ++yit) { - if (toLower(*xit) != toLower(*yit)) { - return false; - } - } - return true; + return std::equal(std::begin(x), std::end(x), std::begin(y), + [] (char l, char r) { return toLower(l) == toLower(r); }); + } + + template + static auto ciEqual(const char(& x)[n], const char(& y)[n]) + { + static_assert(n > 0); + return ciEqual(std::string_view(x, n - 1), std::string_view(y, n - 1)); + } + + template + static auto ciEqual(const char(& x)[n], const T& y) + { + static_assert(n > 0); + return ciEqual(std::string_view(x, n - 1), y); + } + + template + static auto ciEqual(const T& x, const char(& y)[n]) + { + static_assert(n > 0); + return ciEqual(x, std::string_view(y, n - 1)); } static int ciCompareLen(const std::string &x, const std::string &y, size_t len) @@ -157,9 +175,9 @@ public: } /// Returns lower case copy of input string - static std::string lowerCase(const std::string &in) + static std::string lowerCase(std::string_view in) { - std::string out = in; + std::string out(in); lowerCaseInPlace(out); return out; } diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index f1f15f786f..2d990c1b9e 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -9,6 +9,9 @@ #include +#include +#include + namespace SceneUtil { @@ -24,7 +27,7 @@ namespace SceneUtil void FindByClassVisitor::apply(osg::Node &node) { - if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) + if (Misc::StringUtils::ciEqual(std::string_view(node.className()), mNameToFind)) mFoundNodes.push_back(&node); traverse(node); From 6e6214bc43ec3dc3c98a7a1aa423d2d24e77fd1e Mon Sep 17 00:00:00 2001 From: Lamoot Date: Thu, 9 Sep 2021 20:46:27 +0200 Subject: [PATCH 1291/2859] In OpenMW-CS, when creating a new object, sort the entries in the drop-down menu alphabetically. Also have the menu be tall enough to show all of them at once (without scroll bars). --- apps/opencs/view/world/referenceablecreator.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 1a2f2bbaa3..6bc0126b3e 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -22,7 +22,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd std::vector types = CSMWorld::UniversalId::listReferenceableTypes(); mType = new QComboBox (this); - + mType->setMaxVisibleItems(20); + for (std::vector::const_iterator iter (types.begin()); iter!=types.end(); ++iter) { @@ -31,7 +32,9 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(), static_cast (id2.getType())); } - + + mType->model()->sort(0); + insertBeforeButtons (mType, false); connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); From 147ed39900645a8e9ac46aacce53ea79a8391712 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 9 Sep 2021 20:56:57 +0000 Subject: [PATCH 1292/2859] This PR solves a crash with Robert's bodies logged on your bugtracker. (#3095) * attach.cpp [ci skip] * attach.cpp [ci skip] * attach.cpp [ci skip] * npcanimation.cpp [ci skip] * attach.hpp [ci skip] * attach.cpp [ci skip] * creatureanimation.cpp [ci skip] * creatureanimation.cpp [ci skip] * cellpreloader.cpp * npcanimation.cpp * attach.cpp * make android adk happy * make android adk happy * changelog.md [ci skip] * authors.md [ci skip] --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +-- apps/openmw/mwworld/cellpreloader.cpp | 5 +-- components/sceneutil/attach.cpp | 38 ++++++++++------------ components/sceneutil/attach.hpp | 4 +-- 7 files changed, 26 insertions(+), 29 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index abc3f1a8d1..62121a797b 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -44,6 +44,7 @@ Programmers Austin Salgat (Salgat) Ben Shealy (bentsherman) Berulacks + Bo Svensson Britt Mathis (galdor557) Capostrophic Carl Maxwell diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad818bd25..b1132a9e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI + Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed Bug #4752: UpdateCellCommand doesn't undo properly diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 2987111621..f1d28b0634 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -158,7 +158,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) try { - osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(itemModel); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(itemModel); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d142996f40..7fc488020c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -714,14 +714,14 @@ void NpcAnimation::updateParts() PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) { - osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model); + osg::ref_ptr templateNode = mResourceSystem->getSceneManager()->getTemplate(model); const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); - osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second); + osg::ref_ptr attached = SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second); if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 7b6f640373..b2dba1d452 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -114,10 +114,7 @@ namespace MWWorld } } } - if (mPreloadInstances && animated) - mPreloadedObjects.insert(mSceneManager->cacheInstance(mesh)); - else - mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); + mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); if (mPreloadInstances) mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh)); else diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 597c7adf48..fe8aad0879 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -15,6 +15,7 @@ #include #include "visitor.hpp" +#include "clone.hpp" namespace SceneUtil { @@ -49,10 +50,10 @@ namespace SceneUtil return; osg::Node* node = &drawable; - while (node->getNumParents()) + for (auto it = getNodePath().rbegin()+1; it != getNodePath().rend(); ++it) { - osg::Group* parent = node->getParent(0); - if (!parent || !filterMatches(parent->getName())) + osg::Node* parent = *it; + if (!filterMatches(parent->getName())) break; node = parent; } @@ -63,12 +64,7 @@ namespace SceneUtil { for (const osg::ref_ptr& node : mToCopy) { - if (node->getNumParents() > 1) - Log(Debug::Error) << "Error CopyRigVisitor: node has " << node->getNumParents() << " parents"; - while (node->getNumParents()) - node->getParent(0)->removeChild(node); - - mParent->addChild(node); + mParent->addChild(static_cast(node->clone(SceneUtil::CopyOp()))); } mToCopy.clear(); } @@ -90,25 +86,25 @@ namespace SceneUtil std::string mFilter2; }; - void mergeUserData(osg::UserDataContainer* source, osg::Object* target) + void mergeUserData(const osg::UserDataContainer* source, osg::Object* target) { if (!target->getUserDataContainer()) - target->setUserDataContainer(source); + target->setUserDataContainer(osg::clone(source, osg::CopyOp::SHALLOW_COPY)); else { for (unsigned int i=0; igetNumUserObjects(); ++i) - target->getUserDataContainer()->addUserObject(source->getUserObject(i)); + target->getUserDataContainer()->addUserObject(osg::clone(source->getUserObject(i), osg::CopyOp::SHALLOW_COPY)); } } - osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode) + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode) { - if (dynamic_cast(toAttach.get())) + if (dynamic_cast(toAttach.get())) { osg::ref_ptr handle = new osg::Group; CopyRigVisitor copyVisitor(handle, filter); - toAttach->accept(copyVisitor); + const_cast(toAttach.get())->accept(copyVisitor); copyVisitor.doCopy(); if (handle->getNumChildren() == 1) @@ -122,14 +118,16 @@ namespace SceneUtil else { master->asGroup()->addChild(handle); - handle->setUserDataContainer(toAttach->getUserDataContainer()); + mergeUserData(toAttach->getUserDataContainer(), handle); return handle; } } else { + osg::ref_ptr clonedToAttach = static_cast(toAttach->clone(SceneUtil::CopyOp())); + FindByNameVisitor findBoneOffset("BoneOffset"); - toAttach->accept(findBoneOffset); + clonedToAttach->accept(findBoneOffset); osg::ref_ptr trans; @@ -172,13 +170,13 @@ namespace SceneUtil if (trans) { attachNode->addChild(trans); - trans->addChild(toAttach); + trans->addChild(clonedToAttach); return trans; } else { - attachNode->addChild(toAttach); - return toAttach; + attachNode->addChild(clonedToAttach); + return clonedToAttach; } } } diff --git a/components/sceneutil/attach.hpp b/components/sceneutil/attach.hpp index a8a2239a84..806fc53488 100644 --- a/components/sceneutil/attach.hpp +++ b/components/sceneutil/attach.hpp @@ -14,12 +14,12 @@ namespace osg namespace SceneUtil { - /// Attach parts of the \a toAttach scenegraph to the \a master scenegraph, using the specified filter and attachment node. + /// Clone and attach parts of the \a toAttach scenegraph to the \a master scenegraph, using the specified filter and attachment node. /// If the \a toAttach scene graph contains skinned objects, we will attach only those (filtered by the \a filter). /// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering. /// @note The master scene graph is expected to include a skeleton. /// @return A newly created node that is directly attached to the master scene graph - osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode); + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode); } From afba1884ab1977556e56055547e1c6c17704b0ac Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 9 Sep 2021 21:04:11 +0000 Subject: [PATCH 1293/2859] clone.cpp remove dynamic_cast (#3097) --- components/sceneutil/clone.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 07a6e691ad..eaf5668c4a 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -2,8 +2,6 @@ #include -#include -#include #include #include @@ -35,11 +33,6 @@ namespace SceneUtil mUpdaterToOldPs[cloned] = updater->getParticleSystem(0); return cloned; } - - if (dynamic_cast(node) || dynamic_cast(node)) - { - return osg::clone(node, *this); - } return osg::CopyOp::operator()(node); } From c284d0cf5c6ca0a6a55e92f9de7351469946628d Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 9 Sep 2021 21:04:38 +0000 Subject: [PATCH 1294/2859] actoranimation.cpp faster getbonebyname (#3099) --- apps/openmw/mwrender/actoranimation.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 4109d61e8c..8b0c09068a 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -67,17 +67,14 @@ ActorAnimation::~ActorAnimation() PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor) { - osg::Group* parent = getBoneByName(bonename); - if (!parent) - return nullptr; - - osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); - const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) return PartHolderPtr(); + osg::Group* parent = found->second; + osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); + if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); @@ -136,9 +133,9 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell) { - SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); - mObjectRoot->accept(findVisitor); - if (findVisitor.mFoundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) + osg::Group* foundNode = getBoneByName ("Bip01 AttachShield"); + + if (foundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) { const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -276,10 +273,11 @@ osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) if (!mObjectRoot) return nullptr; - SceneUtil::FindByNameVisitor findVisitor (boneName); - mObjectRoot->accept(findVisitor); - - return findVisitor.mFoundNode; + const NodeMap& nodeMap = getNodeMap(); + NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(boneName)); + if (found == nodeMap.end()) + return nullptr; + return found->second; } std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon) From 9d661359a160c3fb265c71cbcacbd08945407e4c Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 9 Sep 2021 21:10:22 +0000 Subject: [PATCH 1295/2859] Groundcover consolidation (#3096) * chunkmanager.hpp viewdistance * chunkmanager.cpp viewdistance * chunkmanager.hpp viewdistance * quadtreeworld.cpp viewdistance * quadtreeworld.cpp consolidate * quadtreeworld.hpp consolidate * renderingmanager.cpp groundcover consolidate * renderingmanager.hpp groundcover consolidate * renderingmanager.cpp updater move * renderingmanager.hpp updater move * groundcover.hpp activegrid consolidation * groundcover.cpp activegrid consolidation * settings-default.cfg dead settings remove * viewdata.cpp revert * wrong file paste mistake * wrong file paste mistake * wrong file paste mistake * renderingmanager.cpp updatecallback fix * renderingmanager.cpp namespace fix * groundcover.hpp redefinition fix * groundcover.cpp redefinition fix * renderingmanager.cpp crash fix * renderingmanager.cpp euclidean groundcover distance * viewdata.hpp getreusedistance * quadtreeworld.cpp reusedistance * groundcover.rst [ci skip] --- apps/openmw/mwrender/groundcover.cpp | 4 +- apps/openmw/mwrender/groundcover.hpp | 4 +- apps/openmw/mwrender/renderingmanager.cpp | 52 +++++-------------- apps/openmw/mwrender/renderingmanager.hpp | 4 +- components/terrain/quadtreeworld.cpp | 26 +++++----- components/terrain/quadtreeworld.hpp | 7 ++- components/terrain/viewdata.cpp | 9 +--- components/terrain/viewdata.hpp | 2 + .../modding/settings/groundcover.rst | 14 ----- files/settings-default.cfg | 3 -- 10 files changed, 40 insertions(+), 85 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 947bdad202..a00d21bc97 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -180,7 +180,7 @@ namespace MWRender osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { - ChunkId id = std::make_tuple(center, size, activeGrid); + GroundcoverChunkId id = std::make_tuple(center, size); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) @@ -196,7 +196,7 @@ namespace MWRender } Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) - : GenericResourceManager(nullptr) + : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) { diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index b92ab97c95..10874b7e8b 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -29,8 +29,8 @@ namespace MWRender osg::Vec3f mPlayerPos; }; - typedef std::tuple ChunkId; // Center, Size, ActiveGrid - class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager + typedef std::tuple GroundcoverChunkId; // Center, Size + class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: Groundcover(Resource::SceneManager* sceneManager, float density); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 381a2e5f62..56d3ba4a43 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -373,7 +373,9 @@ namespace MWRender mTerrainStorage.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps)); const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); - if (Settings::Manager::getBool("distant terrain", "Terrain")) + bool groundcover = Settings::Manager::getBool("enabled", "Groundcover"); + bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + if (distantTerrain || groundcover) { const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); @@ -398,41 +400,27 @@ namespace MWRender mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); - if (Settings::Manager::getBool("enabled", "Groundcover")) - { - osg::ref_ptr groundcoverRoot = new osg::Group; - groundcoverRoot->setNodeMask(Mask_Groundcover); - groundcoverRoot->setName("Groundcover Root"); - sceneRoot->addChild(groundcoverRoot); - - mGroundcoverUpdater = new GroundcoverUpdater; - groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); - - float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover"); - if (chunkSize >= 1.0f) - chunkSize = 1.0f; - else if (chunkSize >= 0.5f) - chunkSize = 0.5f; - else if (chunkSize >= 0.25f) - chunkSize = 0.25f; - else if (chunkSize != 0.125f) - chunkSize = 0.125f; + osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; + if (groundcover) + { float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); - mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize)); + mGroundcoverUpdater = new GroundcoverUpdater; + composite->addController(mGroundcoverUpdater); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); - static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); + static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); - // Groundcover it is handled in the same way indifferently from if it is from active grid or from distant cell. - // Use a stub grid to avoid splitting between chunks for active grid and chunks for distant cells. - mGroundcoverWorld->setActiveGrid(osg::Vec4i(0, 0, 0, 0)); + float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); + mGroundcover->setViewDistance(groundcoverDistance); } mStateUpdater = new StateUpdater; - sceneRoot->addUpdateCallback(mStateUpdater); + composite->addController(mStateUpdater); + sceneRoot->addUpdateCallback(composite); mSharedUniformStateUpdater = new SharedUniformStateUpdater; rootNode->addUpdateCallback(mSharedUniformStateUpdater); @@ -693,8 +681,6 @@ namespace MWRender if (store->getCell()->isExterior()) { mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); - if (mGroundcoverWorld) - mGroundcoverWorld->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } } void RenderingManager::removeCell(const MWWorld::CellStore *store) @@ -706,8 +692,6 @@ namespace MWRender if (store->getCell()->isExterior()) { mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); - if (mGroundcoverWorld) - mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); } mWater->removeCell(store); @@ -718,8 +702,6 @@ namespace MWRender if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); - if (mGroundcoverWorld) - mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) @@ -1179,12 +1161,6 @@ namespace MWRender fov = std::min(mFieldOfView, 140.f); float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); - - if (mGroundcoverWorld) - { - float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); - mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); - } } void RenderingManager::updateTextureFiltering() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index d6d7e74634..b991b08efa 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -262,8 +262,6 @@ namespace MWRender osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; - osg::ref_ptr mGroundcoverUpdater; - osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; @@ -278,10 +276,10 @@ namespace MWRender std::unique_ptr mObjects; std::unique_ptr mWater; std::unique_ptr mTerrain; - std::unique_ptr mGroundcoverWorld; std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; std::unique_ptr mGroundcover; + osg::ref_ptr mGroundcoverUpdater; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 9ca504ebeb..6a228a75af 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -259,17 +259,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManagers.push_back(mChunkManager.get()); } -QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, unsigned int nodeMask, float lodFactor, float chunkSize) - : TerrainGrid(parent, storage, nodeMask) - , mViewDataMap(new ViewDataMap) - , mQuadTreeBuilt(false) - , mLodFactor(lodFactor) - , mVertexLodMod(0) - , mViewDistance(std::numeric_limits::max()) - , mMinSize(chunkSize) -{ -} - QuadTreeWorld::~QuadTreeWorld() { } @@ -325,7 +314,7 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const return lodFlags; } -void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector& chunkManagers, bool compile) +void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector& chunkManagers, bool compile, float reuseDistance) { if (!vd->hasChanged() && entry.mRenderingNode) return; @@ -353,6 +342,8 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f for (QuadTreeWorld::ChunkManager* m : chunkManagers) { + if (m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance*10) + continue; osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); if (n) pat->addChild(n); } @@ -447,7 +438,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false); + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false, mViewDataMap->getReuseDistance()); entry.mRenderingNode->accept(nv); } @@ -517,8 +508,15 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: for (unsigned int i=0; igetNumEntries() && !abort; ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true); + + + + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true, mViewDataMap->getReuseDistance()); reporter.addProgress(entry.mNode->getSize()); + + + + } vd->markUnchanged(); } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 3a2aa8349d..3dd96a0b84 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -22,8 +22,6 @@ namespace Terrain public: QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); - QuadTreeWorld(osg::Group *parent, Storage *storage, unsigned int nodeMask, float lodFactor, float chunkSize); - ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); @@ -51,6 +49,11 @@ namespace Terrain virtual ~ChunkManager(){} virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) = 0; virtual unsigned int getNodeMask() { return 0; } + + void setViewDistance(float viewDistance) { mViewDistance = viewDistance; } + float getViewDistance() const { return mViewDistance; } + private: + float mViewDistance = 0.f; }; void addChunkManager(ChunkManager*); diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index 996bf909c8..e4d043ffc4 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -141,9 +141,9 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo vd = found->second; needsUpdate = false; - if (!vd->suitableToUse(activeGrid) || (vd->getViewPoint()-viewPoint).length2() >= mReuseDistance*mReuseDistance || vd->getWorldUpdateRevision() < mWorldUpdateRevision) + if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - float shortestDist = viewer ? mReuseDistance*mReuseDistance : std::numeric_limits::max(); + float shortestDist = std::numeric_limits::max(); const ViewData* mostSuitableView = nullptr; for (const ViewData* other : mUsedViews) { @@ -162,11 +162,6 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo vd->copyFrom(*mostSuitableView); return vd; } - else if (!mostSuitableView) - { - vd->setViewPoint(viewPoint); - needsUpdate = true; - } } if (!vd->suitableToUse(activeGrid)) { diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 0289352585..378d07663c 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -95,6 +95,8 @@ namespace Terrain void rebuildViews(); bool storeView(const ViewData* view, double referenceTime); + float getReuseDistance() const { return mReuseDistance; } + private: std::list mViewVector; diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst index 7c5a965e01..3e943e4284 100644 --- a/docs/source/reference/modding/settings/groundcover.rst +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -40,20 +40,6 @@ May affect performance a lot. This setting can only be configured by editing the settings configuration file. -min chunk size --------------- - -:Type: floating point -:Range: 0.125, 0.25, 0.5, 1.0 -:Default: 0.5 - -Determines a minimum size of groundcover chunks in cells. For example, with 0.5 value -chunks near player will have size 4096x4096 game units. Larger chunks reduce CPU usage -(Draw and Cull bars), but can increase GPU usage (GPU bar) since culling becomes less efficient. -Smaller values do an opposite. - -This setting can only be configured by editing the settings configuration file. - stomp mode ---------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4737fcf5ee..7f57ba529d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1087,9 +1087,6 @@ density = 1.0 # A maximum distance in game units on which groundcover is rendered. rendering distance = 6144.0 -# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) -min chunk size = 0.5 - # Whether grass should respond to the player treading on it. # 0 - Grass cannot be trampled. # 1 - The player's XY position is taken into account. From 1edeffbafca621dce1888b75c0d6564f04d48390 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 9 Sep 2021 23:57:20 +0200 Subject: [PATCH 1296/2859] constify getBoneByName --- apps/openmw/mwrender/actoranimation.cpp | 2 +- apps/openmw/mwrender/actoranimation.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 8b0c09068a..cb46354590 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -268,7 +268,7 @@ bool ActorAnimation::useShieldAnimations() const return false; } -osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) +osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) const { if (!mObjectRoot) return nullptr; diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index e6bba48d56..61ad1ca235 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -41,7 +41,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener bool updateCarriedLeftVisible(const int weaptype) const override; protected: - osg::Group* getBoneByName(const std::string& boneName); + osg::Group* getBoneByName(const std::string& boneName) const; virtual void updateHolsteredWeapon(bool showHolsteredWeapons); virtual void updateHolsteredShield(bool showCarriedLeft); virtual void updateQuiver(); From 01a8998e3bca50cce6b749a510a4b3ee8cc3c9fb Mon Sep 17 00:00:00 2001 From: psi29a Date: Fri, 10 Sep 2021 14:04:11 +0000 Subject: [PATCH 1297/2859] return if source is null in mergeUserData --- components/sceneutil/attach.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index fe8aad0879..6690148c74 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -88,6 +88,9 @@ namespace SceneUtil void mergeUserData(const osg::UserDataContainer* source, osg::Object* target) { + if (!source) + return; + if (!target->getUserDataContainer()) target->setUserDataContainer(osg::clone(source, osg::CopyOp::SHALLOW_COPY)); else From ac72f3d636fedae5fbb5491bd785652fe8e33688 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Fri, 10 Sep 2021 15:58:57 +0000 Subject: [PATCH 1298/2859] reduces virtual function calls in a hotspot of cache retrieval (#3100) * chunkmanager.cpp static_cast [ci skip] * groundcover.cpp static_cast [ci skip] * Update objectpaging.cpp objectpaging.cpp static_cast [ci skip] --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 2 +- components/terrain/chunkmanager.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index a00d21bc97..39022709fb 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -184,7 +184,7 @@ namespace MWRender osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) - return obj->asNode(); + return static_cast(obj.get()); else { InstanceMap instances; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index b23f079888..907631436f 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -77,7 +77,7 @@ namespace MWRender osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) - return obj->asNode(); + return static_cast(obj.get()); else { osg::ref_ptr node = createChunk(size, center, activeGrid, viewPoint, compile); diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index a744471de5..8809a75bb9 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -45,7 +45,7 @@ osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& cen ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) - return obj->asNode(); + return static_cast(obj.get()); else { osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile); From 36ba56a5137b598f824b867e4c93a9fa9772df6c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 11 Sep 2021 13:49:00 +0200 Subject: [PATCH 1299/2859] Support multiple file sources for osg stats --- scripts/osg_stats.py | 167 +++++++++++++++++++++++-------------------- 1 file changed, 91 insertions(+), 76 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 3397b58f26..f0e6ce4010 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -45,30 +45,30 @@ import termtables help='Start processing from this frame.') @click.option('--end_frame', type=int, default=sys.maxsize, help='End processing at this frame.') -@click.argument('path', default='', type=click.Path()) +@click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, timeseries_sum, stats_sum, begin_frame, end_frame, path, commulative_timeseries, commulative_timeseries_sum): - data = list(read_data(path)) - keys = collect_unique_keys(data) - frames = collect_per_frame(data=data, keys=keys, begin_frame=begin_frame, end_frame=end_frame) + sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} + keys = collect_unique_keys(sources) + frames = collect_per_frame(sources=sources, keys=keys, begin_frame=begin_frame, end_frame=end_frame) if print_keys: for v in keys: print(v) if timeseries: - draw_timeseries(frames=frames, keys=timeseries, add_sum=timeseries_sum) + draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum) if commulative_timeseries: - draw_commulative_timeseries(frames=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum) + draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum) if hist: - draw_hists(frames=frames, keys=hist) + draw_hists(sources=frames, keys=hist) if hist_ratio: - draw_hist_ratio(frames=frames, pairs=hist_ratio) + draw_hist_ratio(sources=frames, pairs=hist_ratio) if stdev_hist: - draw_stdev_hists(frames=frames, stdev_hists=stdev_hist) + draw_stdev_hists(sources=frames, stdev_hists=stdev_hist) if plot: - draw_plots(frames=frames, plots=plot) + draw_plots(sources=frames, plots=plot) if stats: - print_stats(frames=frames, keys=stats, stats_sum=stats_sum) + print_stats(sources=frames, keys=stats, stats_sum=stats_sum) matplotlib.pyplot.show() @@ -92,126 +92,140 @@ def read_data(path): frame[key] = to_number(value) -def collect_per_frame(data, keys, begin_frame, end_frame): - result = collections.defaultdict(list) - for frame in data: - for key in keys: - if key in frame: - result[key].append(frame[key]) - else: - result[key].append(None) - for key, values in result.items(): - result[key] = numpy.array(values[begin_frame:end_frame]) +def collect_per_frame(sources, keys, begin_frame, end_frame): + result = collections.defaultdict(lambda: collections.defaultdict(list)) + for name, frames in sources.items(): + for frame in frames: + for key in keys: + if key in frame: + result[name][key].append(frame[key]) + else: + result[name][key].append(None) + for name, sources in result.items(): + for key, values in sources.items(): + result[name][key] = numpy.array(values[begin_frame:end_frame]) return result -def collect_unique_keys(frames): +def collect_unique_keys(sources): result = set() - for frame in frames: - for key in frame.keys(): - result.add(key) + for frames in sources.values(): + for frame in frames: + for key in frame.keys(): + result.add(key) return sorted(result) -def draw_timeseries(frames, keys, add_sum): +def draw_timeseries(sources, keys, add_sum): fig, ax = matplotlib.pyplot.subplots() - x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) - for key in keys: - ax.plot(x, frames[key], label=key) - if add_sum: - ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label='sum') + for name, frames in sources.items(): + x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) + for key in keys: + print(key, name) + ax.plot(x, frames[key], 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}') ax.grid(True) ax.legend() fig.canvas.set_window_title('timeseries') -def draw_commulative_timeseries(frames, keys, add_sum): +def draw_commulative_timeseries(sources, keys, add_sum): fig, ax = matplotlib.pyplot.subplots() - x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) - for key in keys: - ax.plot(x, numpy.cumsum(frames[key]), label=key) - if add_sum: - ax.plot(x, numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)), label='sum') + for name, frames in sources.items(): + x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) + for key in keys: + ax.plot(x, numpy.cumsum(frames[key]), 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}') ax.grid(True) ax.legend() fig.canvas.set_window_title('commulative_timeseries') -def draw_hists(frames, keys): +def draw_hists(sources, keys): fig, ax = matplotlib.pyplot.subplots() bins = numpy.linspace( - start=min(min(v) for k, v in frames.items() if k in keys), - stop=max(max(v) for k, v in frames.items() if k in keys), + start=min(min(min(v) for k, v in f.items() if k in keys) for f in sources.values()), + stop=max(max(max(v) for k, v in f.items() if k in keys) for f in sources.values()), num=20, ) - for key in keys: - ax.hist(frames[key], bins=bins, label=key, alpha=1 / len(keys)) + for name, frames in sources.items(): + for key in keys: + ax.hist(frames[key], bins=bins, label=f'{key}:{name}', alpha=1 / (len(keys) * len(sources))) ax.set_xticks(bins) ax.grid(True) ax.legend() fig.canvas.set_window_title('hists') -def draw_hist_ratio(frames, pairs): +def draw_hist_ratio(sources, pairs): fig, ax = matplotlib.pyplot.subplots() bins = numpy.linspace( - start=min(min(a / b for a, b in zip(frames[a], frames[b])) for a, b in pairs), - stop=max(max(a / b for a, b in zip(frames[a], frames[b])) for a, b in pairs), + start=min(min(min(a / b for a, b in zip(f[a], f[b])) for a, b in pairs) for f in sources.values()), + stop=max(max(max(a / b for a, b in zip(f[a], f[b])) for a, b in pairs) for f in sources.values()), num=20, ) - for a, b in pairs: - ax.hist(frames[a] / frames[b], bins=bins, label=f'{a} / {b}', alpha=1 / len(pairs)) + for name, frames in sources.items(): + for a, b in pairs: + ax.hist(frames[a] / frames[b], bins=bins, label=f'{a} / {b}:{name}', alpha=1 / (len(pairs) * len(sources))) ax.set_xticks(bins) ax.grid(True) ax.legend() - fig.canvas.set_window_title('hists') + fig.canvas.set_window_title('hists_ratio') -def draw_stdev_hists(frames, stdev_hists): +def draw_stdev_hists(sources, stdev_hists): for key, scale in stdev_hists: scale = float(scale) fig, ax = matplotlib.pyplot.subplots() - median = statistics.median(frames[key]) - stdev = statistics.stdev(frames[key]) + first_frames = next(v for v in sources.values()) + median = statistics.median(first_frames[key]) + stdev = statistics.stdev(first_frames[key]) start = median - stdev / 2 * scale stop = median + stdev / 2 * scale bins = numpy.linspace(start=start, stop=stop, num=9) - values = [v for v in frames[key] if start <= v <= stop] - ax.hist(values, bins=bins, label=key, alpha=1 / len(stdev_hists)) + for name, frames in sources.items(): + values = [v for v in frames[key] if start <= v <= stop] + ax.hist(values, bins=bins, label=f'{key}:{name}', alpha=1 / (len(stdev_hists) * len(sources))) ax.set_xticks(bins) ax.grid(True) ax.legend() fig.canvas.set_window_title('stdev_hists') -def draw_plots(frames, plots): +def draw_plots(sources, plots): fig, ax = matplotlib.pyplot.subplots() - for x_key, y_key, agg in plots: - if agg is None: - ax.plot(frames[x_key], frames[y_key], label=f'x={x_key}, y={y_key}') - elif agg: - agg_f = dict( - mean=statistics.mean, - median=statistics.median, - )[agg] - grouped = collections.defaultdict(list) - for x, y in zip(frames[x_key], frames[y_key]): - grouped[x].append(y) - aggregated = sorted((k, agg_f(v)) for k, v in grouped.items()) - ax.plot( - numpy.array([v[0] for v in aggregated]), - numpy.array([v[1] for v in aggregated]), - label=f'x={x_key}, y={y_key}, agg={agg}', - ) + for name, frames in sources.items(): + for x_key, y_key, agg in plots: + if agg is None: + ax.plot(frames[x_key], frames[y_key], label=f'x={x_key}, y={y_key}:{name}') + elif agg: + agg_f = dict( + mean=statistics.mean, + median=statistics.median, + )[agg] + grouped = collections.defaultdict(list) + for x, y in zip(frames[x_key], frames[y_key]): + grouped[x].append(y) + aggregated = sorted((k, agg_f(v)) for k, v in grouped.items()) + ax.plot( + numpy.array([v[0] for v in aggregated]), + numpy.array([v[1] for v in aggregated]), + label=f'x={x_key}, y={y_key}, agg={agg}:{name}', + ) ax.grid(True) ax.legend() fig.canvas.set_window_title('plots') -def print_stats(frames, keys, stats_sum): - stats = [make_stats(key=key, values=filter_not_none(frames[key])) for key in keys] - if stats_sum: - stats.append(make_stats(key='sum', values=sum_multiple(frames, keys))) +def print_stats(sources, keys, stats_sum): + stats = list() + for name, frames in sources.items(): + for key in keys: + stats.append(make_stats(source=name, key=key, values=filter_not_none(frames[key]))) + if stats_sum: + stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys))) metrics = list(stats[0].keys()) max_key_size = max(len(tuple(v.values())[0]) for v in stats) termtables.print( @@ -235,8 +249,9 @@ def sum_multiple(frames, keys): return numpy.array([result[k] for k in sorted(result.keys())]) -def make_stats(key, values): +def make_stats(source, key, values): return collections.OrderedDict( + source=source, key=key, number=len(values), min=min(values), From 7b482bc2e27bd11c6d143149aaf556a5ae882764 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 18 Jan 2021 18:13:57 +0000 Subject: [PATCH 1300/2859] Maybe force dedicated GPU on dual-AMD machines --- apps/openmw/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index de0fb0df03..8ac725ee86 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -15,6 +15,8 @@ #include // makes __argc and __argv available on windows #include + +extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; #endif #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) From e2d0e860208b6f82f9b7e3d3529597a6e7520e7f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 11 Sep 2021 18:58:42 +0000 Subject: [PATCH 1301/2859] cellpreloader.cpp unused variable (#3102) --- apps/openmw/mwworld/cellpreloader.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index b2dba1d452..c29d975c24 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -95,7 +95,6 @@ namespace MWWorld { mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); - bool animated = false; size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) { @@ -107,10 +106,7 @@ namespace MWWorld { kfname.replace(kfname.size()-4, 4, ".kf"); if (mSceneManager->getVFS()->exists(kfname)) - { mPreloadedObjects.insert(mKeyframeManager->get(kfname)); - animated = true; - } } } } From 98a0819d523e1cf7ba40ac8df279e2579896e284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Thu, 29 Jul 2021 15:33:58 +0200 Subject: [PATCH 1302/2859] Debug terrain chunks --- components/terrain/cellborder.cpp | 19 +++++++++++++------ components/terrain/cellborder.hpp | 3 +++ components/terrain/chunkmanager.cpp | 18 +++++++++++++++++- components/terrain/chunkmanager.hpp | 1 + components/terrain/quadtreeworld.cpp | 2 +- .../reference/modding/settings/terrain.rst | 9 +++++++++ files/settings-default.cfg | 3 +++ 7 files changed, 47 insertions(+), 8 deletions(-) diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index c34ed9ab0a..e391036902 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -9,6 +9,7 @@ #include "../esm/loadland.hpp" #include +#include namespace Terrain { @@ -21,13 +22,14 @@ CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask, { } -void CellBorder::createCellBorderGeometry(int x, int y) +osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask) { const int cellSize = ESM::Land::REAL_SIZE; const int borderSegments = 40; const float offset = 10.0; osg::Vec3 cellCorner = osg::Vec3(x * cellSize,y * cellSize,0); + size *= cellSize; osg::ref_ptr vertices = new osg::Vec3Array; osg::ref_ptr colors = new osg::Vec4Array; @@ -35,16 +37,16 @@ void CellBorder::createCellBorderGeometry(int x, int y) normals->push_back(osg::Vec3(0.0f,-1.0f, 0.0f)); - float borderStep = cellSize / ((float) borderSegments); + float borderStep = size / ((float)borderSegments); for (int i = 0; i <= 2 * borderSegments; ++i) { osg::Vec3f pos = i < borderSegments ? osg::Vec3(i * borderStep,0.0f,0.0f) : - osg::Vec3(cellSize,(i - borderSegments) * borderStep,0.0f); + osg::Vec3(size, (i - borderSegments) * borderStep,0.0f); pos += cellCorner; - pos += osg::Vec3f(0,0,mWorld->getHeightAt(pos) + offset); + pos += osg::Vec3f(0,0, terrain->getHeightAt(pos) + offset); vertices->push_back(pos); @@ -76,10 +78,15 @@ void CellBorder::createCellBorderGeometry(int x, int y) polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); stateSet->setAttributeAndModes(polygonmode,osg::StateAttribute::ON); - mSceneManager->recreateShaders(borderGeode, "debug"); + sceneManager->recreateShaders(borderGeode, "debug"); + borderGeode->setNodeMask(mask); - borderGeode->setNodeMask(mBorderMask); + return borderGeode; +} +void CellBorder::createCellBorderGeometry(int x, int y) +{ + auto borderGeode = createBorderGeometry(x, y, 1.f, mWorld->getStorage(), mSceneManager, mBorderMask); mRoot->addChild(borderGeode); mCellBorderNodes[std::make_pair(x,y)] = borderGeode; diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index ee8fd72593..d72241e3dc 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -11,6 +11,7 @@ namespace Resource namespace Terrain { + class Storage; class World; /** @@ -31,6 +32,8 @@ namespace Terrain */ void destroyCellBorderGeometry(); + static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask); + protected: Terrain::World *mWorld; Resource::SceneManager* mSceneManager; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 8809a75bb9..53b743c03a 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -12,12 +13,14 @@ #include #include +#include #include "terraindrawable.hpp" #include "material.hpp" #include "storage.hpp" #include "texturemanager.hpp" #include "compositemaprenderer.hpp" +#include namespace Terrain { @@ -32,6 +35,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T , mCompositeMapSize(512) , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) + , mDebugChunks(Settings::Manager::getBool("debug chunks", "Terrain")) { mMultiPassRoot = new osg::StateSet; mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN); @@ -234,7 +238,19 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve } geometry->setNodeMask(mNodeMask); - return geometry; + osg::ref_ptr result(new osg::Group); + result->addChild(geometry); + if (mDebugChunks) + { + auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask()); + osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; + osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); + trans->setDataVariance(osg::Object::STATIC); + trans->addChild(chunkBorder); + result->addChild(trans); + } + + return result; } } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 9b7dbf3ee1..4e030e018c 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -72,6 +72,7 @@ namespace Terrain unsigned int mCompositeMapSize; float mCompositeMapLevel; float mMaxCompGeometrySize; + bool mDebugChunks = false; }; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 6a228a75af..ca28e1cc05 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -367,7 +367,7 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); + osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)->asGroup()->getChild(0))->getWaterBoundingBox(); if (!bb.valid()) continue; osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index e6018a8655..80c085f4c9 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -100,6 +100,15 @@ max composite geometry size Controls the maximum size of simple composite geometry chunk in cell units. With small values there will more draw calls and small textures, but higher values create more overdraw (not every texture layer is used everywhere). +debug chunks +------------ + +:Type: boolean +:Range: True/False +:Default: False + +This debug setting allows you to see the borders of each chunks of the world by drawing lines arround them (as with toggleborder). + object paging ------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 7f57ba529d..08039f5fbe 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -139,6 +139,9 @@ composite map resolution = 512 # Controls the maximum size of composite geometry, should be >= 1.0. With low values there will be many small chunks, with high values - lesser count of bigger chunks. max composite geometry size = 4.0 +# Draw lines arround chunks. +debug chunks = false + # Use object paging for non active cells object paging = true From 4b7d0bba53cb3d4f84da72b53828afce9ce6e80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 6 Sep 2021 21:10:03 +0200 Subject: [PATCH 1303/2859] Avoid adding redundant osg;;Group in non debug mode --- components/terrain/chunkmanager.cpp | 7 ++++--- components/terrain/quadtreeworld.cpp | 11 ++++++++--- components/terrain/quadtreeworld.hpp | 1 + 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 53b743c03a..1f3279e5c5 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -238,19 +238,20 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve } geometry->setNodeMask(mNodeMask); - osg::ref_ptr result(new osg::Group); - result->addChild(geometry); if (mDebugChunks) { + osg::ref_ptr result(new osg::Group); + result->addChild(geometry); auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask()); osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); trans->setDataVariance(osg::Object::STATIC); trans->addChild(chunkBorder); result->addChild(trans); + return result; } - return result; + return geometry; } } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index ca28e1cc05..bbf212942e 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -252,6 +252,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) , mMinSize(1/8.f) + , mDebugTerrainChunks(Settings::Manager::getBool("debug chunks", "Terrain")) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -351,7 +352,7 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f } } -void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) +void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld, bool debugTerrainChunk) { if (!(cv->getTraversalMask() & callback->getCullMask())) return; @@ -367,7 +368,11 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)->asGroup()->getChild(0))->getWaterBoundingBox(); + osg::BoundingBox bb; + if(debugTerrainChunk) + bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)->asGroup()->getChild(0))->getWaterBoundingBox(); + else + bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); if (!bb.valid()) continue; osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); @@ -443,7 +448,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) } if (mHeightCullCallback && isCullVisitor) - updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); + updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty(), mDebugTerrainChunks); vd->markUnchanged(); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 3dd96a0b84..f7cbf8097a 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -72,6 +72,7 @@ namespace Terrain int mVertexLodMod; float mViewDistance; float mMinSize; + bool mDebugTerrainChunks; }; } From 080c909c285a3719f1804517ab831005fc47131d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Wed, 8 Sep 2021 19:59:53 +0200 Subject: [PATCH 1304/2859] Merge the 'debug chunks' and 'object paging debug batches' settings into a single one --- apps/openmw/mwrender/objectpaging.cpp | 2 +- docs/source/reference/modding/settings/terrain.rst | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 907631436f..376c517d59 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -390,7 +390,7 @@ namespace MWRender , mRefTrackerLocked(false) { mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); - mDebugBatches = Settings::Manager::getBool("object paging debug batches", "Terrain"); + mDebugBatches = Settings::Manager::getBool("debug chunks", "Terrain"); mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index 80c085f4c9..752260fd48 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -107,7 +107,10 @@ debug chunks :Range: True/False :Default: False -This debug setting allows you to see the borders of each chunks of the world by drawing lines arround them (as with toggleborder). +This debug setting allows you to see the borders of each chunks of the world by drawing lines arround them (as with toggleborder). +If object paging is set to true then this debug setting will allows you to see what objects have been merged in the scene +by making them colored randomly. + object paging ------------- @@ -203,12 +206,3 @@ object paging min size cost multiplier This setting adjusts the calculated cost of merging an object used in the mentioned functionality. The larger this value is, the less expensive objects can be before they are discarded. See the formula above to figure out the math. - -object paging debug batches ---------------------------- -:Type: boolean -:Range: True/False -:Default: False - -This debug setting allows you to see what objects have been merged in the scene -by making them colored randomly. From d7352ded36f05330df2c392b06edc3411a762eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 11 Sep 2021 08:25:41 +0200 Subject: [PATCH 1305/2859] Add configurable color and offset --- components/terrain/cellborder.cpp | 6 +++--- components/terrain/cellborder.hpp | 2 +- components/terrain/chunkmanager.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index e391036902..280a55faca 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -22,11 +22,11 @@ CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask, { } -osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask) +osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, + float offset, osg::Vec4f color) { const int cellSize = ESM::Land::REAL_SIZE; const int borderSegments = 40; - const float offset = 10.0; osg::Vec3 cellCorner = osg::Vec3(x * cellSize,y * cellSize,0); size *= cellSize; @@ -52,7 +52,7 @@ osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, floa osg::Vec4f col = i % 2 == 0 ? osg::Vec4f(0,0,0,1) : - osg::Vec4f(1,1,0,1); + color; colors->push_back(col); } diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index d72241e3dc..785c441b86 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -32,7 +32,7 @@ namespace Terrain */ void destroyCellBorderGeometry(); - static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask); + static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset = 10.0, osg::Vec4f color = { 1,1,0,0 }); protected: Terrain::World *mWorld; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 1f3279e5c5..7a6d4fdb0a 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -242,7 +242,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve { osg::ref_ptr result(new osg::Group); result->addChild(geometry); - auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask()); + auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask(), 5.f, { 1, 0, 0, 0 }); osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); trans->setDataVariance(osg::Object::STATIC); From c40f921396927eacbadb24c198692fe4e416acb5 Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 12 Sep 2021 07:56:20 +0000 Subject: [PATCH 1306/2859] Revert "actoranimation.cpp faster getbonebyname (#3099)" This reverts commit c284d0cf5c6ca0a6a55e92f9de7351469946628d --- apps/openmw/mwrender/actoranimation.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index cb46354590..f556e6891a 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -67,14 +67,17 @@ ActorAnimation::~ActorAnimation() PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor) { + osg::Group* parent = getBoneByName(bonename); + if (!parent) + return nullptr; + + osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); + const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) return PartHolderPtr(); - osg::Group* parent = found->second; - osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); - if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); @@ -133,9 +136,9 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell) { - osg::Group* foundNode = getBoneByName ("Bip01 AttachShield"); - - if (foundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) + SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); + mObjectRoot->accept(findVisitor); + if (findVisitor.mFoundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) { const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -273,11 +276,10 @@ osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) const if (!mObjectRoot) return nullptr; - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(boneName)); - if (found == nodeMap.end()) - return nullptr; - return found->second; + SceneUtil::FindByNameVisitor findVisitor (boneName); + mObjectRoot->accept(findVisitor); + + return findVisitor.mFoundNode; } std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon) From 52a9b4d989d3489e2238ce538516904574edb04d Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 12 Sep 2021 09:21:10 +0000 Subject: [PATCH 1307/2859] shadowsbin.cpp uniform --- components/sceneutil/shadowsbin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 14dbc14178..af62b581c9 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -54,6 +54,7 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); + mShaderAlphaTestStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); for (size_t i = 0; i < sCastingPrograms.size(); ++i) From 6d12a240a39492f9ea7f3a94709cb641b9697ced Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 12 Sep 2021 09:23:36 +0000 Subject: [PATCH 1308/2859] shadervisitor.cpp uniform --- components/shader/shadervisitor.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 26511d4654..a72d772afe 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -340,15 +340,6 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } - - if (diffuseMap) - { - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - // We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out. - // Also it should probably belong to the shader manager or be applied by the shadows bin - writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); - } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); From b6f572578e04cf2e7a6fa2b9d09c2ea27e6e42e7 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 13 Sep 2021 10:20:00 +0000 Subject: [PATCH 1309/2859] Groundcover optimisation (#3101) * groundcover.cpp share state * groundcover.hpp share state --- apps/openmw/mwrender/groundcover.cpp | 54 ++++++++-------------------- apps/openmw/mwrender/groundcover.hpp | 1 + 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 39022709fb..a199201d41 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -1,6 +1,7 @@ #include "groundcover.hpp" #include +#include #include #include @@ -66,18 +67,6 @@ namespace MWRender { } - void apply(osg::Node& node) override - { - osg::ref_ptr ss = node.getStateSet(); - if (ss != nullptr) - { - ss->removeAttribute(osg::StateAttribute::MATERIAL); - removeAlpha(ss); - } - - traverse(node); - } - void apply(osg::Geometry& geom) override { for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) @@ -110,32 +99,14 @@ namespace MWRender // Display lists do not support instancing in OSG 3.4 geom.setUseDisplayList(false); + geom.setUseVertexBufferObjects(true); geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); - - osg::ref_ptr ss = geom.getOrCreateStateSet(); - ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); - ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); - - ss->removeAttribute(osg::StateAttribute::MATERIAL); - removeAlpha(ss); - - traverse(geom); } private: std::vector mInstances; osg::Vec3f mChunkPosition; - - void removeAlpha(osg::StateSet* stateset) - { - // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties - stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); - stateset->removeMode(GL_ALPHA_TEST); - stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); - stateset->removeMode(GL_BLEND); - stateset->setRenderBinToInherit(); - } }; class DensityCalculator @@ -199,7 +170,16 @@ namespace MWRender : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) + , mStateset(new osg::StateSet) { + // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); + mStateset->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + mStateset->setAttributeAndModes(new osg::BlendFunc, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); + mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); } void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) @@ -255,27 +235,23 @@ namespace MWRender for (auto& pair : instances) { const osg::Node* temp = mSceneManager->getTemplate(pair.first); - osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); + osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES|osg::CopyOp::DEEP_COPY_USERDATA|osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES)); // Keep link to original mesh to keep it in cache group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); - mSceneManager->reinstateRemovedState(node); - InstancingVisitor visitor(pair.second, worldCenter); node->accept(visitor); group->addChild(node); } - // Force a unified alpha handling instead of data from meshes - osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); - group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); - group->getBound(); + group->setStateSet(mStateset); group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->setCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", false, true); - + mSceneManager->shareState(group); + group->getBound(); return group; } diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index 10874b7e8b..4874bea899 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -56,6 +56,7 @@ namespace MWRender private: Resource::SceneManager* mSceneManager; float mDensity; + osg::ref_ptr mStateset; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); From e4eeb9cce9b29702bfde7dc9fdd3b983a8d69249 Mon Sep 17 00:00:00 2001 From: pi03k Date: Tue, 7 Sep 2021 23:10:18 +0200 Subject: [PATCH 1310/2859] Remove 'no relevant classes' moc warning --- CHANGELOG.md | 1 + CMakeLists.txt | 2 -- apps/launcher/CMakeLists.txt | 21 +++------------------ apps/opencs/CMakeLists.txt | 5 ++++- apps/opencs/view/tools/merge.hpp | 4 ++-- apps/wizard/CMakeLists.txt | 23 ++++------------------- cmake/OpenMWMacros.cmake | 6 ------ components/CMakeLists.txt | 7 +++++-- 8 files changed, 19 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1132a9e68..ffabc0b037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering Feature #6251: OpenMW-CS: Set instance movement based on camera zoom + Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp 0.47.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 52a824d4e9..d14762ef5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,8 +202,6 @@ if (USE_QT) find_package(Qt5Widgets REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5OpenGL REQUIRED) - # Instruct CMake to run moc automatically when needed. - #set(CMAKE_AUTOMOC ON) endif() set(USED_OSG_COMPONENTS diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index e9ec906e6a..7e8bd67e94 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -36,23 +36,6 @@ set(LAUNCHER_HEADER ) # Headers that must be pre-processed -set(LAUNCHER_HEADER_MOC - datafilespage.hpp - graphicspage.hpp - maindialog.hpp - playpage.hpp - textslotmsgbox.hpp - settingspage.hpp - advancedpage.hpp - - utils/cellnameloader.hpp - utils/textinputdialog.hpp - utils/profilescombobox.hpp - utils/lineedit.hpp - utils/openalutil.hpp - -) - set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui @@ -74,7 +57,6 @@ if(WIN32) endif(WIN32) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) -QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -109,4 +91,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-launcher gcov) endif() +if(USE_QT) + set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 4b3b8030e6..60bd0f4ded 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -150,7 +150,6 @@ if(WIN32) endif(WIN32) qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) -qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) # for compiled .ui files @@ -259,3 +258,7 @@ endif (MSVC) if(APPLE) INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle) endif() + +if(USE_QT) + set_property(TARGET openmw-cs PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp index d394a431ed..c7c4585979 100644 --- a/apps/opencs/view/tools/merge.hpp +++ b/apps/opencs/view/tools/merge.hpp @@ -1,5 +1,5 @@ -#ifndef CSV_TOOLS_REPORTTABLE_H -#define CSV_TOOLS_REPORTTABLE_H +#ifndef CSV_TOOLS_MERGE_H +#define CSV_TOOLS_MERGE_H #include diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 2558ec81de..03cd63480a 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -1,4 +1,3 @@ - set(WIZARD componentselectionpage.cpp conclusionpage.cpp @@ -34,21 +33,6 @@ set(WIZARD_HEADER utils/componentlistwidget.hpp ) -# Headers that must be pre-processed -set(WIZARD_HEADER_MOC - componentselectionpage.hpp - conclusionpage.hpp - existinginstallationpage.hpp - importpage.hpp - installationtargetpage.hpp - intropage.hpp - languageselectionpage.hpp - mainwizard.hpp - methodselectionpage.hpp - - utils/componentlistwidget.hpp -) - set(WIZARD_UI ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui @@ -63,7 +47,6 @@ set(WIZARD_UI if (OPENMW_USE_UNSHIELD) set (WIZARD ${WIZARD} installationpage.cpp unshield/unshieldworker.cpp) set (WIZARD_HEADER ${WIZARD_HEADER} installationpage.hpp unshield/unshieldworker.hpp) - set (WIZARD_HEADER_MOC ${WIZARD_HEADER_MOC} installationpage.hpp unshield/unshieldworker.hpp) set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui) add_definitions(-DOPENMW_USE_UNSHIELD) endif (OPENMW_USE_UNSHIELD) @@ -80,7 +63,6 @@ if(WIN32) endif(WIN32) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) -QT5_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) QT5_WRAP_UI(UI_HDRS ${WIZARD_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -94,7 +76,6 @@ openmw_add_executable(openmw-wizard ${WIZARD} ${WIZARD_HEADER} ${RCC_SRCS} - ${MOC_SRCS} ${UI_HDRS} ) @@ -125,3 +106,7 @@ endif() if (WIN32) INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION ".") endif(WIN32) + +if(USE_QT) + set_property(TARGET openmw-wizard PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 176a02d507..5bee345344 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -80,10 +80,6 @@ foreach (f ${ALL}) list (APPEND files "${f}") list (APPEND COMPONENT_QT_FILES "${f}") endforeach (f) -file (GLOB MOC_H "${dir}/${u}.hpp") -foreach (fi ${MOC_H}) -list (APPEND COMPONENT_MOC_FILES "${fi}") -endforeach (fi) endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_qt_dir) @@ -99,7 +95,6 @@ endmacro (add_unit) macro (add_qt_unit project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") endmacro (add_qt_unit) @@ -109,7 +104,6 @@ endmacro (add_hdr) macro (add_qt_hdr project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") endmacro (add_qt_hdr) macro (opencs_units dir) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7983de6190..5b79a096cd 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -226,7 +226,6 @@ if (USE_QT) ) QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) endif() if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -280,7 +279,7 @@ if (WIN32) endif() if (USE_QT) - add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) + add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${ESM_UI_HDR}) target_link_libraries(components_qt components Qt5::Widgets Qt5::Core) target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") endif() @@ -333,3 +332,7 @@ if(OSG_STATIC) target_link_libraries(components freetype jpeg png) endif() endif(OSG_STATIC) + +if(USE_QT) + set_property(TARGET components_qt PROPERTY AUTOMOC ON) +endif(USE_QT) From 3415f12e128a2de023872d8cfb277de1fa2af275 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 14 Sep 2021 11:30:07 +0000 Subject: [PATCH 1311/2859] shadervisitor.cpp --- components/shader/shadervisitor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a72d772afe..cb0173c648 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -458,6 +458,9 @@ namespace Shader defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } + if (defineMap["diffuseMap"] == 0) + writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); + defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); From 2cebd194327b285b8a151b81b9f95dd8fbdd03ce Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 14 Sep 2021 11:37:23 +0000 Subject: [PATCH 1312/2859] shadervisitor.cpp --- components/shader/shadervisitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index cb0173c648..cbd2863e46 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -458,7 +458,7 @@ namespace Shader defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } - if (defineMap["diffuseMap"] == 0) + if (defineMap["diffuseMap"] == "0") writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; From 60298cd66de1bc9a1fefd1cec23218a64c21678b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 14 Sep 2021 16:53:57 +0200 Subject: [PATCH 1313/2859] Reset rotation when respawning actors --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 1 + apps/openmw/mwclass/npc.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1132a9e68..91602e5408 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop + Bug #6273: Respawning NPCs rotation is inconsistent Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 50dafba39b..54623e6699 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -846,6 +846,7 @@ namespace MWClass // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 41cf3beed5..718b4d972d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1397,6 +1397,7 @@ namespace MWClass // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } From d4e26746a32404d620c8e847335ba096668fee17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 6 Sep 2021 21:56:32 +0200 Subject: [PATCH 1314/2859] Use recurse subdirectory iterator to iterate over the VFS without exposing internal details --- apps/niftest/niftest.cpp | 5 +-- apps/opencs/model/world/resources.cpp | 4 +- apps/openmw/mwgui/loadingscreen.cpp | 23 ++++------ apps/openmw/mwrender/animation.cpp | 36 ++++------------ apps/openmw/mwsound/soundmanagerimp.cpp | 18 ++------ components/fontloader/fontloader.cpp | 18 ++------ components/vfs/manager.cpp | 10 ++--- components/vfs/manager.hpp | 56 +++++++++++++++++++++++-- 8 files changed, 82 insertions(+), 88 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 2e81885c75..cb6205ef5c 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -52,11 +52,8 @@ void readVFS(VFS::Archive* anArchive,std::string archivePath = "") myManager.addArchive(anArchive); myManager.buildIndex(); - std::map files=myManager.getIndex(); - for(auto it=files.begin(); it!=files.end(); ++it) + for(const auto& name : myManager.getRecursiveDirectoryIterator("")) { - std::string name = it->first; - try{ if(isNIF(name)) { diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 2544886f3e..cd9f58e848 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -23,10 +23,8 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const * size_t baseSize = mBaseDirectory.size(); - const std::map& index = vfs->getIndex(); - for (std::map::const_iterator it = index.begin(); it != index.end(); ++it) + for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) { - std::string filepath = it->first; if (filepath.size()& index = mResourceSystem->getVFS()->getIndex(); std::string pattern = "Splash/"; mResourceSystem->getVFS()->normalizeFilename(pattern); /* priority given to the left */ const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; - auto found = index.lower_bound(pattern); - while (found != index.end()) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(pattern)) { - const std::string& name = found->first; - if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos) + for (auto const& extension : supported_extensions) { - for(auto const& extension: supported_extensions) + if (name.compare(pos, name.size() - pos, extension) == 0) { - if (name.compare(pos, name.size() - pos, extension) == 0) - { - mSplashScreens.push_back(found->first); - break; /* based on priority */ - } + mSplashScreens.push_back(name); + break; /* based on priority */ } } } - else - break; - ++found; } if (mSplashScreens.empty()) Log(Debug::Warning) << "Warning: no splash screens found!"; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index a04724c70a..4eafd05f5f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -591,8 +591,6 @@ namespace MWRender void Animation::loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel) { - const std::map& index = mResourceSystem->getVFS()->getIndex(); - std::string animationPath = model; if (animationPath.find("meshes") == 0) { @@ -602,19 +600,11 @@ namespace MWRender mResourceSystem->getVFS()->normalizeFilename(animationPath); - std::map::const_iterator found = index.lower_bound(animationPath); - while (found != index.end()) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - const std::string& name = found->first; - if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".kf") == 0) - addSingleAnimSource(name, baseModel); - } - else - break; - ++found; + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".kf") == 0) + addSingleAnimSource(name, baseModel); } } @@ -1295,8 +1285,6 @@ namespace MWRender if (model.empty()) return; - const std::map& index = resourceSystem->getVFS()->getIndex(); - std::string animationPath = model; if (animationPath.find("meshes") == 0) { @@ -1306,19 +1294,11 @@ namespace MWRender resourceSystem->getVFS()->normalizeFilename(animationPath); - std::map::const_iterator found = index.lower_bound(animationPath); - while (found != index.end()) + for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - const std::string& name = found->first; - if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".nif") == 0) - loadBonesFromFile(node, name, resourceSystem); - } - else - break; - ++found; + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".nif") == 0) + loadBonesFromFile(node, name, resourceSystem); } } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index fc9735d576..f5c3231c99 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -294,20 +294,12 @@ namespace MWSound if (mMusicFiles.find(playlist) == mMusicFiles.end()) { std::vector filelist; - const std::map& index = mVFS->getIndex(); std::string pattern = "Music/" + playlist; mVFS->normalizeFilename(pattern); - std::map::const_iterator found = index.lower_bound(pattern); - while (found != index.end()) - { - if (found->first.size() >= pattern.size() && found->first.substr(0, pattern.size()) == pattern) - filelist.push_back(found->first); - else - break; - ++found; - } + for (const auto& name : mVFS->getRecursiveDirectoryIterator(pattern)) + filelist.push_back(name); mMusicFiles[playlist] = filelist; } @@ -327,13 +319,11 @@ namespace MWSound if (mMusicFiles.find("Title") == mMusicFiles.end()) { std::vector filelist; - const std::map& index = mVFS->getIndex(); // Is there an ini setting for this filename or something? std::string filename = "music/special/morrowind title.mp3"; - auto found = index.find(filename); - if (found != index.end()) + if (mVFS->exists(filename)) { - filelist.emplace_back(found->first); + filelist.emplace_back(filename); mMusicFiles["Title"] = filelist; } else diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 487b7bd70b..998e9c0c64 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -191,24 +191,14 @@ namespace Gui void FontLoader::loadBitmapFonts(bool exportToFile) { - const std::map& index = mVFS->getIndex(); - std::string pattern = "Fonts/"; mVFS->normalizeFilename(pattern); - std::map::const_iterator found = index.lower_bound(pattern); - while (found != index.end()) + for (const auto& name : mVFS->getRecursiveDirectoryIterator(pattern)) { - const std::string& name = found->first; - if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".fnt") == 0) - loadBitmapFont(name, exportToFile); - } - else - break; - ++found; + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".fnt") == 0) + loadBitmapFont(name, exportToFile); } } diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 045fe3cf5c..f799719e31 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -86,11 +86,6 @@ namespace VFS return mIndex.find(normalized) != mIndex.end(); } - const std::map& Manager::getIndex() const - { - return mIndex; - } - void Manager::normalizeFilename(std::string &name) const { normalize_path(name, mStrict); @@ -107,4 +102,9 @@ namespace VFS } return {}; } + + RecursiveDirectoryIterator Manager::getRecursiveDirectoryIterator(const std::string& path) const + { + return RecursiveDirectoryIterator(mIndex, path); + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 5a09a995eb..8656f3bf10 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -12,6 +12,51 @@ namespace VFS class Archive; class File; + class RecursiveDirectoryIterator; + RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); + + class RecursiveDirectoryIterator + { + public: + RecursiveDirectoryIterator(const std::map& index, const std::string& path) + : mPath(path) + , mIndex(&index) + , mIt(index.lower_bound(path)) + {} + + RecursiveDirectoryIterator(const RecursiveDirectoryIterator&) = default; + + const std::string& operator*() const { return mIt->first; } + const std::string* operator->() const { return &mIt->first; } + + bool operator!=(const RecursiveDirectoryIterator& other) { return mPath != other.mPath || mIt != other.mIt; } + + RecursiveDirectoryIterator& operator++() + { + if (++mIt == mIndex->end() || !starts_with(mIt->first, mPath)) + *this = end(*this); + return *this; + } + + friend RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); + + private: + static bool starts_with(const std::string& text, const std::string& start) { return text.rfind(start, 0) == 0; } + + std::string mPath; + const std::map* mIndex; + std::map::const_iterator mIt; + }; + + inline RecursiveDirectoryIterator begin(RecursiveDirectoryIterator iter) { return iter; } + + inline RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter) + { + RecursiveDirectoryIterator result(iter); + result.mIt = result.mIndex->end(); + return result; + } + /// @brief The main class responsible for loading files from a virtual file system. /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) /// can be registered, and will be merged into a single file tree. If the same filename is @@ -40,10 +85,6 @@ namespace VFS /// @note May be called from any thread once the index has been built. bool exists(const std::string& name) const; - /// Get a complete list of files from all archives - /// @note May be called from any thread once the index has been built. - const std::map& getIndex() const; - /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. /// @note May be called from any thread once the index has been built. void normalizeFilename(std::string& name) const; @@ -59,6 +100,13 @@ namespace VFS Files::IStreamPtr getNormalized(const std::string& normalizedName) const; std::string getArchive(const std::string& name) const; + + /// Recursivly iterate over the elements of the given path + /// In practice it return all files of the VFS starting with the given path + /// @note the path is normalized + /// @note May be called from any thread once the index has been built. + RecursiveDirectoryIterator getRecursiveDirectoryIterator(const std::string& path) const; + private: bool mStrict; From c2df0949e2d739830bf3c3838f644cfdd9bb3371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 6 Sep 2021 22:01:41 +0200 Subject: [PATCH 1315/2859] Change normalizeFilename signature --- apps/openmw/mwgui/loadingscreen.cpp | 5 +---- apps/openmw/mwrender/animation.cpp | 4 ---- apps/openmw/mwsound/sound_buffer.cpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 15 +++------------ components/fontloader/fontloader.cpp | 5 +---- components/resource/bulletshapemanager.cpp | 9 +++------ components/resource/imagemanager.cpp | 3 +-- components/resource/keyframemanager.cpp | 3 +-- components/resource/scenemanager.cpp | 14 ++++---------- components/vfs/manager.cpp | 8 +++++--- components/vfs/manager.hpp | 2 +- 11 files changed, 21 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 2d65e4e508..7f454925fd 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -66,13 +66,10 @@ namespace MWGui void LoadingScreen::findSplashScreens() { - std::string pattern = "Splash/"; - mResourceSystem->getVFS()->normalizeFilename(pattern); - /* priority given to the left */ const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; - for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(pattern)) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) { size_t pos = name.find_last_of('.'); if (pos != std::string::npos) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 4eafd05f5f..3df902e469 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -598,8 +598,6 @@ namespace MWRender } animationPath.replace(animationPath.size()-3, 3, "/"); - mResourceSystem->getVFS()->normalizeFilename(animationPath); - for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { size_t pos = name.find_last_of('.'); @@ -1292,8 +1290,6 @@ namespace MWRender } animationPath.replace(animationPath.size()-4, 4, "/"); - resourceSystem->getVFS()->normalizeFilename(animationPath); - for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { size_t pos = name.find_last_of('.'); diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index cb71cb56d2..e64e89d775 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -131,7 +131,7 @@ namespace MWSound max = std::max(min, max); Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); - mVfs->normalizeFilename(sfx.mResourceName); + sfx.mResourceName = mVfs->normalizeFilename(sfx.mResourceName); mBufferNameMap.emplace(soundId, &sfx); return &sfx; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index f5c3231c99..d2422870d7 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -295,10 +295,7 @@ namespace MWSound { std::vector filelist; - std::string pattern = "Music/" + playlist; - mVFS->normalizeFilename(pattern); - - for (const auto& name : mVFS->getRecursiveDirectoryIterator(pattern)) + for (const auto& name : mVFS->getRecursiveDirectoryIterator("Music/" + playlist)) filelist.push_back(name); mMusicFiles[playlist] = filelist; @@ -345,10 +342,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - std::string voicefile = "Sound/"+filename; - - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + DecoderPtr decoder = loadVoice(mVFS->normalizeFilename("Sound/" + filename)); if (!decoder) return; @@ -379,10 +373,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - std::string voicefile = "Sound/"+filename; - - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + DecoderPtr decoder = loadVoice(mVFS->normalizeFilename("Sound/" + filename)); if (!decoder) return; diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 998e9c0c64..9ba62f7694 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -191,10 +191,7 @@ namespace Gui void FontLoader::loadBitmapFonts(bool exportToFile) { - std::string pattern = "Fonts/"; - mVFS->normalizeFilename(pattern); - - for (const auto& name : mVFS->getRecursiveDirectoryIterator(pattern)) + for (const auto& name : mVFS->getRecursiveDirectoryIterator("Fonts/")) { size_t pos = name.find_last_of('.'); if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".fnt") == 0) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index b1b0f74176..98878df152 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -121,8 +121,7 @@ BulletShapeManager::~BulletShapeManager() osg::ref_ptr BulletShapeManager::getShape(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr shape; osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); @@ -180,8 +179,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & osg::ref_ptr BulletShapeManager::cacheInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr instance = createInstance(normalized); if (instance) @@ -191,8 +189,7 @@ osg::ref_ptr BulletShapeManager::cacheInstance(const std::s osg::ref_ptr BulletShapeManager::getInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); if (obj.get()) diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 37e76359ff..ba46b5cce9 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -83,8 +83,7 @@ namespace Resource osg::ref_ptr ImageManager::getImage(const std::string &filename) { - std::string normalized = filename; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(filename); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 444d2bd7aa..6d431fb3a5 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -133,8 +133,7 @@ namespace Resource osg::ref_ptr KeyframeManager::get(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 33a9ab99a1..ec6bea02d7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -358,10 +358,7 @@ namespace Resource bool SceneManager::checkLoaded(const std::string &name, double timeStamp) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); - - return mCache->checkInObjectCache(normalized, timeStamp); + return mCache->checkInObjectCache(mVFS->normalizeFilename(name), timeStamp); } /// @brief Callback to read image files from the VFS. @@ -533,8 +530,7 @@ namespace Resource osg::ref_ptr SceneManager::getTemplate(const std::string &name, bool compile) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) @@ -603,8 +599,7 @@ namespace Resource osg::ref_ptr SceneManager::cacheInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr node = createInstance(normalized); @@ -642,8 +637,7 @@ namespace Resource osg::ref_ptr SceneManager::getInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); if (obj.get()) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index f799719e31..1cb8745497 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -86,9 +86,11 @@ namespace VFS return mIndex.find(normalized) != mIndex.end(); } - void Manager::normalizeFilename(std::string &name) const + std::string Manager::normalizeFilename(const std::string& name) const { - normalize_path(name, mStrict); + std::string result = name; + normalize_path(result, mStrict); + return result; } std::string Manager::getArchive(const std::string& name) const @@ -105,6 +107,6 @@ namespace VFS RecursiveDirectoryIterator Manager::getRecursiveDirectoryIterator(const std::string& path) const { - return RecursiveDirectoryIterator(mIndex, path); + return RecursiveDirectoryIterator(mIndex, normalizeFilename(path)); } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 8656f3bf10..b98d8021d4 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -87,7 +87,7 @@ namespace VFS /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. /// @note May be called from any thread once the index has been built. - void normalizeFilename(std::string& name) const; + [[nodiscard]] std::string normalizeFilename(const std::string& name) const; /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. From 681728209735520de5daf3657cc5c754783c5028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 11 Sep 2021 15:49:47 +0200 Subject: [PATCH 1316/2859] Move getFileExtension to common header and use instead of repeating same code --- apps/openmw/mwgui/loadingscreen.cpp | 21 +++++++-------------- apps/openmw/mwrender/animation.cpp | 7 +++---- components/fontloader/fontloader.cpp | 4 ++-- components/misc/pathhelpers.hpp | 19 +++++++++++++++++++ components/resource/bulletshapemanager.cpp | 8 ++------ components/resource/imagemanager.cpp | 6 ++---- components/resource/keyframemanager.cpp | 4 ++-- components/resource/scenemanager.cpp | 13 +++---------- 8 files changed, 40 insertions(+), 42 deletions(-) create mode 100644 components/misc/pathhelpers.hpp diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 7f454925fd..ef8eea0104 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -66,23 +67,15 @@ namespace MWGui void LoadingScreen::findSplashScreens() { - /* priority given to the left */ - const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; + auto isSupportedExtension = [](const std::string_view& ext) { + static const std::array supported_extensions{ {"tga", "dds", "ktx", "png", "bmp", "jpeg", "jpg"} }; + return !ext.empty() && std::find(supported_extensions.begin(), supported_extensions.end(), ext) != supported_extensions.end(); + }; for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos) - { - for (auto const& extension : supported_extensions) - { - if (name.compare(pos, name.size() - pos, extension) == 0) - { - mSplashScreens.push_back(name); - break; /* based on priority */ - } - } - } + if (isSupportedExtension(Misc::getFileExtension(name))) + mSplashScreens.push_back(name); } if (mSplashScreens.empty()) Log(Debug::Warning) << "Warning: no splash screens found!"; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3df902e469..1dc42db47c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -600,8 +601,7 @@ namespace MWRender for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".kf") == 0) + if (Misc::getFileExtension(name) == "kf") addSingleAnimSource(name, baseModel); } } @@ -1292,8 +1292,7 @@ namespace MWRender for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".nif") == 0) + if (Misc::getFileExtension(name) == "nif") loadBonesFromFile(node, name, resourceSystem); } } diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 9ba62f7694..da43cc38ec 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -193,8 +194,7 @@ namespace Gui { for (const auto& name : mVFS->getRecursiveDirectoryIterator("Fonts/")) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".fnt") == 0) + if (Misc::getFileExtension(name) == "fnt") loadBitmapFont(name, exportToFile); } } diff --git a/components/misc/pathhelpers.hpp b/components/misc/pathhelpers.hpp new file mode 100644 index 0000000000..88913a1f7b --- /dev/null +++ b/components/misc/pathhelpers.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_COMPONENTS_MISC_PATHHELPERS_H +#define OPENMW_COMPONENTS_MISC_PATHHELPERS_H + +#include + +namespace Misc +{ + inline std::string_view getFileExtension(std::string_view file) + { + if (auto extPos = file.find_last_of('.'); extPos != std::string::npos) + { + file.remove_prefix(extPos + 1); + return file; + } + return {}; + } +} + +#endif diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 98878df152..0d0f81962b 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -129,12 +130,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & shape = osg::ref_ptr(static_cast(obj.get())); else { - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); - - if (ext == "nif") + if (Misc::getFileExtension(normalized) == "nif") { NifBullet::BulletNifLoader loader; shape = loader.load(*mNifFileManager->get(normalized)); diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index ba46b5cce9..a544b7b621 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "objectcache.hpp" @@ -102,10 +103,7 @@ namespace Resource return mWarningImage; } - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); + const std::string ext(Misc::getFileExtension(normalized)); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); if (!reader) { diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 6d431fb3a5..01a8639aa2 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "animation.hpp" @@ -141,8 +142,7 @@ namespace Resource else { osg::ref_ptr loaded (new SceneUtil::KeyframeHolder); - std::string ext = Resource::getFileExtension(normalized); - if (ext == "kf") + if (Misc::getFileExtension(normalized) == "kf") { NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ec6bea02d7..76e73da384 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -388,12 +389,12 @@ namespace Resource osg::ref_ptr load (const std::string& normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { - std::string ext = Resource::getFileExtension(normalizedFilename); + auto ext = Misc::getFileExtension(normalizedFilename); if (ext == "nif") return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); else { - osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { std::stringstream errormsg; @@ -816,12 +817,4 @@ namespace Resource shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } - - std::string getFileExtension(const std::string& file) - { - size_t extPos = file.find_last_of('.'); - if (extPos != std::string::npos && extPos+1 < file.size()) - return file.substr(extPos+1); - return std::string(); - } } From dd6649d519939b157d2f9b2089aa0c1defc2f356 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 14 Sep 2021 22:59:04 +0200 Subject: [PATCH 1317/2859] Create cmake.yml (#3107) * Create cmake.yml * Update cmake.yml * Update cmake.yml * Update cmake.yml * Update cmake.yml --- .github/workflows/cmake.yml | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/cmake.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000000..18221b3cb6 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,53 @@ +name: CMake + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + BUILD_TYPE: RelWithDebInfo + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Add OpenMW PPA Dependancies + run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update + + - name: Install Building Dependancies + run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic + + - name: Configure CMake + run: | + mkdir build + mkdir instdir + cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX:PATH=instdir + + - name: Build + run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -C ${{env.BUILD_TYPE}} + + - name: Install OpenMW + shell: bash + run: cmake --install . + + - name: Upload OpenMW Artifact + shell: bash + working-directory: instdir + run: | + ls -laR + 7z a ../build_artifact.7z . + + - name: Upload + uses: actions/upload-artifact@v1 + with: + path: ./build_artifact.7z + name: build_artifact.7z From c658acc2b311bb6676597845fe4464d2409314c4 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 15 Sep 2021 16:50:19 +0200 Subject: [PATCH 1318/2859] pipeline only on pull_requests (#3109) * Create debs * Update cmake.yml * give ccache a try * ccache round 2 * Update cmake.yml --- .github/workflows/cmake.yml | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 18221b3cb6..c220030050 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -1,8 +1,6 @@ name: CMake on: - push: - branches: [ master ] pull_request: branches: [ master ] @@ -22,31 +20,30 @@ jobs: - name: Install Building Dependancies run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic - - name: Configure CMake - run: | - mkdir build - mkdir instdir - cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX:PATH=instdir + - name: Prime ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} + max-size: 1000M - - name: Build - run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 + - name: Configure + run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - - name: Test - working-directory: ${{github.workspace}}/build - run: ctest -C ${{env.BUILD_TYPE}} + - name: Buil + run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 - - name: Install OpenMW + - name: Install shell: bash run: cmake --install . - - name: Upload OpenMW Artifact + - name: Create Artifact shell: bash - working-directory: instdir + working-directory: install run: | ls -laR 7z a ../build_artifact.7z . - - name: Upload + - name: Upload Artifact uses: actions/upload-artifact@v1 with: path: ./build_artifact.7z From f62adab43a821a75beb9eed0f68ae7af82af81ce Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:11:19 +0000 Subject: [PATCH 1319/2859] Avoid the terrain sync completely in most cases (#3103) We can take elsid's commit 605cb8d further by avoiding the terrain sync completely in most cases. Currently in changeCellGrid we wait for a new preloading task to ensure the getPagedRefnums for the new active cells have been filled in by object paging. This is usually not necessary because we have already completed a preload in the past containing these active cells. With this PR we remember what we preloaded and skip the terrain sync if it is not needed. --- apps/openmw/mwworld/cellpreloader.cpp | 60 ++++++++++++++++--------- apps/openmw/mwworld/cellpreloader.hpp | 4 ++ apps/openmw/mwworld/scene.cpp | 10 ++--- components/resource/resourcemanager.hpp | 1 + 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index c29d975c24..f2f2ebc5a6 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -21,6 +21,29 @@ #include "cellstore.hpp" #include "class.hpp" +namespace +{ + template + bool contains(const std::vector& container, + const Contained& contained, float tolerance=1.f) + { + for (const auto& pos : contained) + { + bool found = false; + for (const auto& pos2 : container) + { + if ((pos.first-pos2.first).length2() < tolerance*tolerance && pos.second == pos2.second) + { + found = true; + break; + } + } + if (!found) return false; + } + return true; + } +} + namespace MWWorld { @@ -224,6 +247,7 @@ namespace MWWorld , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) , mStoreViewsFailCount(0) + , mLoadedTerrainTimestamp(0.0) { } @@ -369,7 +393,11 @@ namespace MWWorld setTerrainPreloadPositions(std::vector()); } else + { mStoreViewsFailCount = 0; + mLoadedTerrainPositions = mTerrainPreloadPositions; + mLoadedTerrainTimestamp = timestamp; + } mTerrainPreloadItem = nullptr; } } @@ -436,10 +464,8 @@ namespace MWWorld void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) { - const float resetThreshold = ESM::Land::REAL_SIZE; - for (const auto& pos : mTerrainPreloadPositions) - if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second) - return; + if (exceptPos && contains(mTerrainPreloadPositions, std::array {*exceptPos}, ESM::Land::REAL_SIZE)) + return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) { mTerrainPreloadItem->abort(); @@ -448,28 +474,13 @@ namespace MWWorld setTerrainPreloadPositions(std::vector()); } - bool contains(const std::vector& container, const std::vector& contained) - { - for (const auto& pos : contained) - { - bool found = false; - for (const auto& pos2 : container) - { - if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) - { - found = true; - break; - } - } - if (!found) return false; - } - return true; - } - void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) { if (positions.empty()) + { mTerrainPreloadPositions.clear(); + mLoadedTerrainPositions.clear(); + } else if (contains(mTerrainPreloadPositions, positions)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) @@ -496,5 +507,10 @@ namespace MWWorld } } } + + bool CellPreloader::isTerrainLoaded(const CellPreloader::PositionCellGrid &position, double referenceTime) const + { + return mLoadedTerrainTimestamp + mResourceSystem->getSceneManager()->getExpiryDelay() > referenceTime && contains(mLoadedTerrainPositions, std::array {position}, ESM::Land::REAL_SIZE); + } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index e2eea33146..39436dc5ad 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -79,6 +79,7 @@ namespace MWWorld bool syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener); void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); + bool isTerrainLoaded(const CellPreloader::PositionCellGrid &position, double referenceTime) const; private: Resource::ResourceSystem* mResourceSystem; @@ -119,6 +120,9 @@ namespace MWWorld std::vector mTerrainPreloadPositions; osg::ref_ptr mTerrainPreloadItem; osg::ref_ptr mUpdateCacheItem; + + std::vector mLoadedTerrainPositions; + double mLoadedTerrainTimestamp; }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 399d0e6d7f..89349d329a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -628,7 +628,10 @@ namespace MWWorld osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); mRendering.setActiveGrid(newGrid); - preloadTerrain(pos, true); + if (mRendering.pagingUnlockCache()) + mPreloader->abortTerrainPreloadExcept(nullptr); + if (!mPreloader->isTerrainLoaded(std::make_pair(pos, newGrid), mRendering.getReferenceTime())) + preloadTerrain(pos, true); mPagedRefs.clear(); mRendering.getPagedRefnums(newGrid, mPagedRefs); @@ -1241,10 +1244,7 @@ namespace MWWorld { std::vector vec; vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); - if (sync && mRendering.pagingUnlockCache()) - mPreloader->abortTerrainPreloadExcept(nullptr); - else - mPreloader->abortTerrainPreloadExcept(&vec[0]); + mPreloader->abortTerrainPreloadExcept(&vec[0]); mPreloader->setTerrainPreloadPositions(vec); if (!sync) return; diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index ccb065e3bf..b2b71f4635 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -59,6 +59,7 @@ namespace Resource /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay (double expiryDelay) override { mExpiryDelay = expiryDelay; } + float getExpiryDelay() const { return mExpiryDelay; } const VFS::Manager* getVFS() const { return mVFS; } From 4ff5a04e9b3470847dd77efe3261e43c047c2b4f Mon Sep 17 00:00:00 2001 From: Pi03k Date: Thu, 16 Sep 2021 20:45:23 +0200 Subject: [PATCH 1320/2859] Remove redundant qt-related cmake macros --- apps/opencs/CMakeLists.txt | 28 ++++++++++++++-------------- cmake/OpenMWMacros.cmake | 25 ++----------------------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 60bd0f4ded..0ffa3da559 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -8,11 +8,11 @@ opencs_units (model/doc document operation saving documentmanager loader runner operationholder ) -opencs_units_noqt (model/doc +opencs_units (model/doc stage savingstate savingstages blacklist messages ) -opencs_hdrs_noqt (model/doc +opencs_hdrs (model/doc state ) @@ -23,14 +23,14 @@ opencs_units (model/world ) -opencs_units_noqt (model/world +opencs_units (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro ) -opencs_hdrs_noqt (model/world +opencs_hdrs (model/world columnimp idcollection collection info subcellcollection ) @@ -39,14 +39,14 @@ opencs_units (model/tools tools reportmodel mergeoperation ) -opencs_units_noqt (model/tools +opencs_units (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck ) -opencs_hdrs_noqt (model/tools +opencs_hdrs (model/tools mergestate ) @@ -57,11 +57,11 @@ opencs_units (view/doc ) -opencs_units_noqt (view/doc +opencs_units (view/doc subviewfactory ) -opencs_hdrs_noqt (view/doc +opencs_hdrs (view/doc subviewfactoryimp ) @@ -74,7 +74,7 @@ opencs_units (view/world bodypartcreator landtexturecreator landcreator ) -opencs_units_noqt (view/world +opencs_units (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate scripthighlighter idvalidator dialoguecreator idcompletiondelegate colordelegate dragdroputils @@ -92,12 +92,12 @@ opencs_units (view/render cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands ) -opencs_units_noqt (view/render +opencs_units (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase cellarrow cellmarker cellborder pathgrid ) -opencs_hdrs_noqt (view/render +opencs_hdrs (view/render mask ) @@ -106,7 +106,7 @@ opencs_units (view/tools reportsubview reporttable searchsubview searchbox merge ) -opencs_units_noqt (view/tools +opencs_units (view/tools subviews ) @@ -119,11 +119,11 @@ opencs_units (model/prefs shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting ) -opencs_units_noqt (model/prefs +opencs_units (model/prefs category ) -opencs_units_noqt (model/filter +opencs_units (model/filter node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode ) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 5bee345344..0adc185b38 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -93,42 +93,21 @@ add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") endmacro (add_unit) -macro (add_qt_unit project dir unit) -add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") -endmacro (add_qt_unit) - macro (add_hdr project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") endmacro (add_hdr) -macro (add_qt_hdr project dir unit) -add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -endmacro (add_qt_hdr) - macro (opencs_units dir) foreach (u ${ARGN}) -add_qt_unit (OPENCS ${dir} ${u}) -endforeach (u) -endmacro (opencs_units) - -macro (opencs_units_noqt dir) -foreach (u ${ARGN}) add_unit (OPENCS ${dir} ${u}) endforeach (u) -endmacro (opencs_units_noqt) +endmacro (opencs_units) macro (opencs_hdrs dir) foreach (u ${ARGN}) -add_qt_hdr (OPENCS ${dir} ${u}) -endforeach (u) -endmacro (opencs_hdrs) - -macro (opencs_hdrs_noqt dir) -foreach (u ${ARGN}) add_hdr (OPENCS ${dir} ${u}) endforeach (u) -endmacro (opencs_hdrs_noqt) +endmacro (opencs_hdrs) include(CMakeParseArguments) From e4648cec48ba9b28e8907b23ffa68d63c9f1e6e8 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Fri, 17 Sep 2021 17:24:11 +0000 Subject: [PATCH 1321/2859] kill buil (#3118) He was a great fellow. We will forever be in debt for his services. Time to say good-bye. --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index c220030050..f9375a3ba5 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -29,7 +29,7 @@ jobs: - name: Configure run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - - name: Buil + - name: Build run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 - name: Install From b9825afb8a8c97085560f58febd2a9175a14971b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 2 Sep 2021 01:06:27 +0200 Subject: [PATCH 1322/2859] Fix build with system static OpenSceneGraph * Add dependency to libraries required by OSG but missing when linking with OSG system library. * Use find_package for already defined dependencies. --- CMakeLists.txt | 5 +++++ components/CMakeLists.txt | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d14762ef5f..1b8350a04b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,11 @@ if(POLICY CMP0083) cmake_policy(SET CMP0083 NEW) endif() +# to link with freetype library +if(POLICY CMP0079) + cmake_policy(SET CMP0079 NEW) +endif() + option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF) if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5b79a096cd..7ee3d184d8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -288,6 +288,15 @@ if (GIT_CHECKOUT) add_dependencies (components git-version) endif (GIT_CHECKOUT) +if (OSG_STATIC AND CMAKE_SYSTEM_NAME MATCHES "Linux") + find_package(X11 REQUIRED COMPONENTS Xinerama Xrandr) + target_link_libraries(components ${CMAKE_DL_LIBS} X11::X11 X11::Xinerama X11::Xrandr) + find_package(Fontconfig MODULE) + if(Fontconfig_FOUND) + target_link_libraries(components Fontconfig::Fontconfig) + endif() +endif() + if (WIN32) target_link_libraries(components shlwapi) endif() @@ -329,7 +338,10 @@ if(OSG_STATIC) if(OPENMW_USE_SYSTEM_OSG) # OSG plugin pkgconfig files are missing these dependencies. # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 - target_link_libraries(components freetype jpeg png) + find_package(Freetype REQUIRED) + find_package(JPEG REQUIRED) + find_package(PNG REQUIRED) + target_link_libraries(components Freetype::Freetype JPEG::JPEG PNG::PNG) endif() endif(OSG_STATIC) From 70e210735af9b355f64b3b8cb749b579b1cfc0e5 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 18 Sep 2021 15:57:00 +0300 Subject: [PATCH 1323/2859] Optimize terrain editing brush drawing performance --- apps/opencs/view/render/brushdraw.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/brushdraw.cpp b/apps/opencs/view/render/brushdraw.cpp index 255a13a12e..6b33e336ea 100644 --- a/apps/opencs/view/render/brushdraw.cpp +++ b/apps/opencs/view/render/brushdraw.cpp @@ -18,6 +18,8 @@ CSVRender::BrushDraw::BrushDraw(osg::ref_ptr parentNode, bool textur mBrushDrawNode = new osg::Group(); mGeometry = new osg::Geometry(); mBrushDrawNode->addChild(mGeometry); + mBrushDrawNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mBrushDrawNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin"); mParentNode->addChild(mBrushDrawNode); if (mTextureMode) mLandSizeFactor = static_cast(ESM::Land::REAL_SIZE) / static_cast(ESM::Land::LAND_TEXTURE_SIZE); @@ -122,7 +124,14 @@ void CSVRender::BrushDraw::buildSquareGeometry(const float& radius, const osg::V const float brushOutlineHeight (1.0f); float diameter = radius * 2; int resolution = static_cast(2.f * diameter / mLandSizeFactor); //half a vertex resolution - float resAdjustedLandSizeFactor = mLandSizeFactor / 2; + float resAdjustedLandSizeFactor = mLandSizeFactor / 2; //128 + + if (resolution > 128) // limit accuracy for performance + { + resolution = 128; + resAdjustedLandSizeFactor = diameter / resolution; + } + osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); for (int i = 0; i < resolution; i++) @@ -215,7 +224,8 @@ void CSVRender::BrushDraw::buildCircleGeometry(const float& radius, const osg::V osg::ref_ptr geom (new osg::Geometry()); osg::ref_ptr vertices (new osg::Vec3Array()); osg::ref_ptr colors (new osg::Vec4Array()); - const int amountOfPoints = (osg::PI * 2.0f) * radius / 20; + + const int amountOfPoints = 128; const float step ((osg::PI * 2.0f) / static_cast(amountOfPoints)); const float brushOutlineHeight (1.0f); osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); From 179f91276af6b766c168900ae5ccc718b6b25e64 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 18 Sep 2021 16:21:11 +0000 Subject: [PATCH 1324/2859] lightmanager.cpp (#3121) --- components/sceneutil/lightmanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 63a475566b..50d57fd471 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1204,7 +1204,6 @@ namespace SceneUtil const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { - bool isReflection = isReflectionCamera(camera); osg::observer_ptr camPtr (camera); auto it = mLightsInViewSpace.find(camPtr); @@ -1212,6 +1211,8 @@ namespace SceneUtil { it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; + bool isReflection = isReflectionCamera(camera); + for (const auto& transform : mLights) { osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix); From 76be2e91e5f69b61077c8287331319097529492c Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sat, 18 Sep 2021 18:25:04 +0200 Subject: [PATCH 1325/2859] Fixed an issue where keyword search expected the text to be all ASCII characters --- apps/openmw/mwdialogue/keywordsearch.hpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index f296f223fb..3cd8517651 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -68,6 +68,19 @@ public: return false; } + static bool isWhitespaceUTF8(const int utf8Char) + { + if (utf8Char >= 0 && utf8Char <= UCHAR_MAX) + { + //That function has undefined behavior if the character doesn't fit in unsigned char + return std::isspace(utf8Char); + } + else + { + return false; + } + } + static bool sortMatches(const Match& left, const Match& right) { return left.mBeg < right.mBeg; @@ -83,7 +96,7 @@ public: { Point prev = i; --prev; - if(isalpha(*prev)) + if(!isWhitespaceUTF8(*prev)) continue; } From 8d86d90782b495fa6366641b5ad139f278bb780a Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 18 Sep 2021 22:00:26 +0200 Subject: [PATCH 1326/2859] remove whitespace --- components/sceneutil/lightmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 50d57fd471..1f7d9d2db2 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1212,7 +1212,7 @@ namespace SceneUtil it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; bool isReflection = isReflectionCamera(camera); - + for (const auto& transform : mLights) { osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix); From f1e3c55ea3fbab43ae6268b1e2332048fd34b61f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 19 Sep 2021 10:27:48 +0200 Subject: [PATCH 1327/2859] Fix deadlock in Lua worker thread (#6286) --- apps/openmw/engine.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 772af3759e..bbe4f25f69 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -875,7 +875,14 @@ public: void join() { if (mThread) + { + { + std::lock_guard lk(mMutex); + mJoinRequest = true; + } + mCV.notify_one(); mThread->join(); + } } private: @@ -891,10 +898,12 @@ private: void threadBody() { - while (!mEngine->mViewer->done() && !mEngine->mEnvironment.getStateManager()->hasQuitRequest()) + while (true) { std::unique_lock lk(mMutex); - mCV.wait(lk, [&]{ return mUpdateRequest; }); + mCV.wait(lk, [&]{ return mUpdateRequest || mJoinRequest; }); + if (mJoinRequest) + break; update(); @@ -908,6 +917,7 @@ private: std::mutex mMutex; std::condition_variable mCV; bool mUpdateRequest = false; + bool mJoinRequest = false; double mDt = 0; bool mIsGuiMode = false; std::optional mThread; From 095f4b2ed58584e77bdbdcb9202e3359bb02b9ba Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 19 Sep 2021 11:13:28 +0000 Subject: [PATCH 1328/2859] material.cpp (#3117) --- components/terrain/material.cpp | 59 +++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 2fbd0a7fc0..28297fb456 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -166,6 +166,29 @@ namespace mValue->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); } }; + + class UniformCollection + { + public: + static const UniformCollection& value() + { + static UniformCollection instance; + return instance; + } + + osg::ref_ptr mDiffuseMap; + osg::ref_ptr mBlendMap; + osg::ref_ptr mNormalMap; + osg::ref_ptr mColorMode; + + UniformCollection() + : mDiffuseMap(new osg::Uniform("diffuseMap", 0)) + , mBlendMap(new osg::Uniform("blendMap", 1)) + , mNormalMap(new osg::Uniform("normalMap", 2)) + , mColorMode(new osg::Uniform("colorMode", 2)) + { + } + }; } namespace Terrain @@ -199,32 +222,28 @@ namespace Terrain } } - int texunit = 0; - if (useShaders) { - stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); + stateset->setTextureAttributeAndModes(0, it->mDiffuseMap); if (layerTileSize != 1.f) - stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); + stateset->addUniform(UniformCollection::value().mDiffuseMap); if (!blendmaps.empty()) { - ++texunit; osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); - stateset->setTextureAttributeAndModes(texunit, blendmap.get()); - stateset->setTextureAttributeAndModes(texunit, BlendmapTexMat::value(blendmapScale)); - stateset->addUniform(new osg::Uniform("blendMap", texunit)); + stateset->setTextureAttributeAndModes(1, blendmap.get()); + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + stateset->addUniform(UniformCollection::value().mBlendMap); } if (it->mNormalMap) { - ++texunit; - stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); - stateset->addUniform(new osg::Uniform("normalMap", texunit)); + stateset->setTextureAttributeAndModes(2, it->mNormalMap); + stateset->addUniform(UniformCollection::value().mNormalMap); } Shader::ShaderManager::DefineMap defineMap; @@ -242,31 +261,27 @@ namespace Terrain } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); - stateset->addUniform(new osg::Uniform("colorMode", 2)); + stateset->addUniform(UniformCollection::value().mColorMode); } else { // Add the actual layer texture osg::ref_ptr tex = it->mDiffuseMap; - stateset->setTextureAttributeAndModes(texunit, tex.get()); + stateset->setTextureAttributeAndModes(0, tex.get()); if (layerTileSize != 1.f) - stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); - - ++texunit; + stateset->setTextureAttributeAndModes(0, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); // Multiply by the alpha map if (!blendmaps.empty()) { osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); - stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + stateset->setTextureAttributeAndModes(1, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. - stateset->setTextureAttributeAndModes(texunit, BlendmapTexMat::value(blendmapScale)); - stateset->setTextureAttributeAndModes(texunit, TexEnvCombine::value(), osg::StateAttribute::ON); - - ++texunit; + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + stateset->setTextureAttributeAndModes(1, TexEnvCombine::value(), osg::StateAttribute::ON); } } From 2f25257a3e1ed8a91328bd1e33cdb30710865628 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 19 Sep 2021 14:38:27 +0200 Subject: [PATCH 1329/2859] Move LuaState::makeReadOnly(sol::table) out of the class because it doesn't need to access LuaState internals. --- apps/openmw/mwlua/camerabindings.cpp | 2 +- apps/openmw/mwlua/inputbindings.cpp | 10 +++++----- apps/openmw/mwlua/luabindings.cpp | 18 ++++++++--------- apps/openmw/mwlua/settingsbindings.cpp | 2 +- apps/openmw/mwlua/uibindings.cpp | 2 +- apps/openmw_test_suite/lua/test_lua.cpp | 6 +++--- components/lua/luastate.cpp | 26 ++++++++++++++----------- components/lua/luastate.hpp | 11 +++++------ components/lua/scriptscontainer.cpp | 6 +++--- components/lua/scriptscontainer.hpp | 2 +- 10 files changed, 44 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index 68f2331b9e..46bf079d65 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -7,7 +7,7 @@ namespace MWLua { sol::table api(context.mLua->sol(), sol::create); // TODO - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } } diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index f815d9c344..aaa00f3da9 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -48,7 +48,7 @@ namespace MWLua api["getControlSwitch"] = [input](const std::string& key) { return input->getControlSwitch(key); }; api["setControlSwitch"] = [input](const std::string& key, bool v) { input->toggleControlSwitch(key, v); }; - api["ACTION"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "GameMenu", MWInput::A_GameMenu, "Screenshot", MWInput::A_Screenshot, "Inventory", MWInput::A_Inventory, @@ -102,7 +102,7 @@ namespace MWLua "ZoomOut", MWInput::A_ZoomOut )); - api["CONTROL_SWITCH"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "Controls", "playercontrols", "Fighting", "playerfighting", "Jumping", "playerjumping", @@ -112,7 +112,7 @@ namespace MWLua "VanityMode", "vanitymode" )); - api["CONTROLLER_BUTTON"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "A", SDL_CONTROLLER_BUTTON_A, "B", SDL_CONTROLLER_BUTTON_B, "X", SDL_CONTROLLER_BUTTON_X, @@ -130,7 +130,7 @@ namespace MWLua "DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT )); - api["CONTROLLER_AXIS"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "LeftX", SDL_CONTROLLER_AXIS_LEFTX, "LeftY", SDL_CONTROLLER_AXIS_LEFTY, "RightX", SDL_CONTROLLER_AXIS_RIGHTX, @@ -144,7 +144,7 @@ namespace MWLua "MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight )); - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index ed788eb525..e37241d692 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -18,7 +18,7 @@ namespace MWLua sol::table res(lua.sol(), sol::create); for (const std::string& v : values) res[v] = v; - return lua.makeReadOnly(res); + return LuaUtil::makeReadOnly(res); } sol::table initCorePackage(const Context& context) @@ -43,7 +43,7 @@ namespace MWLua "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", "Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon" }); - api["EQUIPMENT_SLOT"] = lua->makeReadOnly(lua->sol().create_table_with( + api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(lua->sol().create_table_with( "Helmet", MWWorld::InventoryStore::Slot_Helmet, "Cuirass", MWWorld::InventoryStore::Slot_Cuirass, "Greaves", MWWorld::InventoryStore::Slot_Greaves, @@ -64,7 +64,7 @@ namespace MWLua "CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft, "Ammunition", MWWorld::InventoryStore::Slot_Ammunition )); - return lua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } sol::table initWorldPackage(const Context& context) @@ -107,7 +107,7 @@ namespace MWLua // return GObjectList{worldView->selectObjects(query, false)}; }; // TODO: add world.placeNewObject(recordId, cell, pos, [rot]) - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } sol::table initNearbyPackage(const Context& context) @@ -137,7 +137,7 @@ namespace MWLua // TODO: Maybe use sqlite // return LObjectList{worldView->selectObjects(query, true)}; }; - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } sol::table initQueryPackage(const Context& context) @@ -148,7 +148,7 @@ namespace MWLua query[t] = Queries::Query(std::string(t)); for (const QueryFieldGroup& group : getBasicQueryFieldGroups()) query[group.mName] = initFieldGroup(context, group); - return query; // makeReadonly is applied by LuaState::addCommonPackage + return query; // makeReadOnly is applied by LuaState::addCommonPackage } sol::table initFieldGroup(const Context& context, const QueryFieldGroup& group) @@ -163,12 +163,12 @@ namespace MWLua { const std::string& name = field->path()[i]; if (subgroup[name] == sol::nil) - subgroup[name] = context.mLua->makeReadOnly(context.mLua->newTable()); - subgroup = context.mLua->getMutableFromReadOnly(subgroup[name]); + subgroup[name] = LuaUtil::makeReadOnly(context.mLua->newTable()); + subgroup = LuaUtil::getMutableFromReadOnly(subgroup[name]); } subgroup[field->path().back()] = field; } - return context.mLua->makeReadOnly(res); + return LuaUtil::makeReadOnly(res); } } diff --git a/apps/openmw/mwlua/settingsbindings.cpp b/apps/openmw/mwlua/settingsbindings.cpp index 0e267182ac..12dd69f73a 100644 --- a/apps/openmw/mwlua/settingsbindings.cpp +++ b/apps/openmw/mwlua/settingsbindings.cpp @@ -62,7 +62,7 @@ namespace MWLua else return sol::make_object(lua->sol(), value.getFloat()); }; - return lua->makeReadOnly(config); + return LuaUtil::makeReadOnly(config); } sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, true, false); } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index cb14c41621..4fae84cd40 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -12,7 +12,7 @@ namespace MWLua { luaManager->addUIMessage(message); }; - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } } diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 9405dba4b2..32d4ea49b8 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -119,7 +119,7 @@ return { EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)"); EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value"); - EXPECT_EQ(mLua.getMutableFromReadOnly(mLua.makeReadOnly(script)), script); + EXPECT_EQ(LuaUtil::getMutableFromReadOnly(LuaUtil::makeReadOnly(script)), script); } TEST_F(LuaStateTest, Print) @@ -150,8 +150,8 @@ return { { LuaUtil::LuaState lua(mVFS.get()); - sol::table api1 = lua.makeReadOnly(lua.sol().create_table_with("name", "api1")); - sol::table api2 = lua.makeReadOnly(lua.sol().create_table_with("name", "api2")); + sol::table api1 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api1")); + sol::table api2 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api2")); sol::table script1 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api1}}); diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index fd42aa7fb2..8e4719dba4 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -67,27 +67,31 @@ namespace LuaUtil mSandboxEnv = sol::nil; } - sol::table LuaState::makeReadOnly(sol::table table) + sol::table makeReadOnly(sol::table table) { + if (table == sol::nil) + return table; if (table.is()) return table; // it is already userdata, no sense to wrap it again + lua_State* lua = table.lua_state(); table[sol::meta_function::index] = table; - sol::stack::push(mLua, std::move(table)); - lua_newuserdata(mLua, 0); - lua_pushvalue(mLua, -2); - lua_setmetatable(mLua, -2); - return sol::stack::pop(mLua); + sol::stack::push(lua, std::move(table)); + lua_newuserdata(lua, 0); + lua_pushvalue(lua, -2); + lua_setmetatable(lua, -2); + return sol::stack::pop(lua); } - sol::table LuaState::getMutableFromReadOnly(const sol::userdata& ro) + sol::table getMutableFromReadOnly(const sol::userdata& ro) { - sol::stack::push(mLua, ro); - int ok = lua_getmetatable(mLua, -1); + lua_State* lua = ro.lua_state(); + sol::stack::push(lua, ro); + int ok = lua_getmetatable(lua, -1); assert(ok); (void)ok; - sol::table res = sol::stack::pop(mLua); - lua_pop(mLua, 1); + sol::table res = sol::stack::pop(lua); + lua_pop(lua, 1); return res; } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index acaaadea76..8982b49b36 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -3,7 +3,6 @@ #include -#include // missing from sol/sol.hpp #include #include @@ -37,11 +36,6 @@ namespace LuaUtil // A shortcut to create a new Lua table. sol::table newTable() { return sol::table(mLua, sol::create); } - // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. - // Needed to forbid any changes in common resources that can accessed from different sandboxes. - sol::table makeReadOnly(sol::table); - sol::table getMutableFromReadOnly(const sol::userdata&); - // Registers a package that will be available from every sandbox via `require(name)`. // The package can be either a sol::table with an API or a sol::function. If it is a function, // it will be evaluated (once per sandbox) the first time when requested. If the package @@ -106,6 +100,11 @@ namespace LuaUtil // String representation of a Lua object. Should be used for debugging/logging purposes only. std::string toString(const sol::object&); + // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. + // Needed to forbid any changes in common resources that can accessed from different sandboxes. + sol::table makeReadOnly(sol::table); + sol::table getMutableFromReadOnly(const sol::userdata&); + } #endif // COMPONENTS_LUA_LUASTATE_H diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index a53b1d0404..6e0a19b120 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -25,7 +25,7 @@ namespace LuaUtil void ScriptsContainer::addPackage(const std::string& packageName, sol::object package) { - API[packageName] = mLua.makeReadOnly(std::move(package)); + API[packageName] = makeReadOnly(std::move(package)); } bool ScriptsContainer::addNewScript(const std::string& path) @@ -63,7 +63,7 @@ namespace LuaUtil if (interfaceName.empty() != (publicInterface == sol::nil)) Log(Debug::Error) << mNamePrefix << "[" << path << "]: 'interfaceName' should always be used together with 'interface'"; else if (!interfaceName.empty()) - script.as()[INTERFACE] = mPublicInterfaces[interfaceName] = mLua.makeReadOnly(publicInterface); + script.as()[INTERFACE] = mPublicInterfaces[interfaceName] = makeReadOnly(publicInterface); mScriptOrder.push_back(path); mScripts[path].mInterface = std::move(script); return true; @@ -329,7 +329,7 @@ namespace LuaUtil mHoursTimersQueue.clear(); mPublicInterfaces.clear(); - // Assigned by mLua.makeReadOnly, but `clear` removes it, so we need to assign it again. + // Assigned by LuaUtil::makeReadOnly, but `clear` removes it, so we need to assign it again. mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces; } diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 7b2b2a7aa9..d3141a2ca3 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -76,7 +76,7 @@ namespace LuaUtil virtual ~ScriptsContainer() {} // Adds package that will be available (via `require`) for all scripts in the container. - // Automatically applies LuaState::makeReadOnly to the package. + // Automatically applies LuaUtil::makeReadOnly to the package. void addPackage(const std::string& packageName, sol::object package); // Finds a file with given path in the virtual file system, starts as a new script, and adds it to the container. From bcb0526268ad529fe51929f4b21af12357b823a7 Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sun, 19 Sep 2021 15:00:51 +0200 Subject: [PATCH 1330/2859] Added climits, it compiled on VS2019, but not with g++ --- apps/openmw/mwdialogue/keywordsearch.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 3cd8517651..fc6f2a91e8 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -2,6 +2,7 @@ #define GAME_MWDIALOGUE_KEYWORDSEARCH_H #include +#include #include #include #include From c1c501ca3559571c8292cd0f684274e09f07f9ff Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sun, 19 Sep 2021 15:05:48 +0200 Subject: [PATCH 1331/2859] Added test to make sure keyword search works even with non ascii characters --- .../mwdialogue/test_keywordsearch.cpp | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index 431725be2c..25ea566165 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -65,3 +65,26 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution3) ASSERT_TRUE (matches.size() == 1); ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); } + + +TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) +{ + // We make sure that the search works well even if the character is not ASCII + MWDialogue::KeywordSearch search; + search.seed("tats", 0); + search.seed("rradis", 0); + search.seed("a nous dois", 0); + + + std::string text = "les nations unis ont runis le monde entier, tats units inclus pour parler du problme des gens rradis et a nous dois"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + ASSERT_TRUE (matches.size() == 3); + ASSERT_TRUE(std::string( matches[0].mBeg, matches[0].mEnd) == "tats"); + ASSERT_TRUE(std::string( matches[1].mBeg, matches[1].mEnd) == "rradis"); + ASSERT_TRUE(std::string( matches[2].mBeg, matches[2].mEnd) == "a nous dois"); + + //ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); +} From d879e4aba5db6c13e4a6c10d36dbee1670b374d6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 19 Sep 2021 19:11:27 +0200 Subject: [PATCH 1332/2859] Use real frame number for axis x --- scripts/osg_stats.py | 51 +++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index f0e6ce4010..8a36eb5963 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -45,20 +45,27 @@ import termtables help='Start processing from this frame.') @click.option('--end_frame', type=int, default=sys.maxsize, help='End processing at this frame.') +@click.option('--frame_number_name', type=str, default='FrameNumber', + help='Frame number metric name.') @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, timeseries_sum, stats_sum, begin_frame, end_frame, path, - commulative_timeseries, commulative_timeseries_sum): + commulative_timeseries, commulative_timeseries_sum, frame_number_name): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} keys = collect_unique_keys(sources) - frames = collect_per_frame(sources=sources, keys=keys, begin_frame=begin_frame, end_frame=end_frame) + frames, begin_frame, end_frame = collect_per_frame( + sources=sources, keys=keys, begin_frame=begin_frame, + end_frame=end_frame, frame_number_name=frame_number_name, + ) if print_keys: for v in keys: print(v) if timeseries: - draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum) + draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum, + begin_frame=begin_frame, end_frame=end_frame) if commulative_timeseries: - draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum) + draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum, + begin_frame=begin_frame, end_frame=end_frame) if hist: draw_hists(sources=frames, keys=hist) if hist_ratio: @@ -92,19 +99,26 @@ def read_data(path): frame[key] = to_number(value) -def collect_per_frame(sources, keys, begin_frame, end_frame): +def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): + assert begin_frame < end_frame result = collections.defaultdict(lambda: collections.defaultdict(list)) + begin_frame = max(begin_frame, min(v[0][frame_number_name] for v in sources.values())) + end_frame = min(end_frame, begin_frame + max(len(v) for v in sources.values())) + for name in sources.keys(): + for key in keys: + result[name][key] = [0] * (end_frame - begin_frame) for name, frames in sources.items(): for frame in frames: - for key in keys: - if key in frame: - result[name][key].append(frame[key]) - else: - result[name][key].append(None) - for name, sources in result.items(): - for key, values in sources.items(): - result[name][key] = numpy.array(values[begin_frame:end_frame]) - return result + number = frame[frame_number_name] + if begin_frame <= number < end_frame: + index = number - begin_frame + for key in keys: + if key in frame: + result[name][key][index] = frame[key] + for name in result.keys(): + for key in keys: + result[name][key] = numpy.array(result[name][key]) + return result, begin_frame, end_frame def collect_unique_keys(sources): @@ -116,12 +130,11 @@ def collect_unique_keys(sources): return sorted(result) -def draw_timeseries(sources, keys, add_sum): +def draw_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(): - x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) for key in keys: - print(key, name) ax.plot(x, frames[key], 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}') @@ -130,10 +143,10 @@ def draw_timeseries(sources, keys, add_sum): fig.canvas.set_window_title('timeseries') -def draw_commulative_timeseries(sources, keys, add_sum): +def draw_commulative_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(): - x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) for key in keys: ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}') if add_sum: From 6d7cb3883480d43c3870be757289fa54727ef5df Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 19 Sep 2021 19:53:38 +0200 Subject: [PATCH 1333/2859] Remove duplicate GetSquareRoot implementation --- apps/openmw/mwscript/miscextensions.cpp | 3 ++ components/compiler/exprparser.cpp | 40 +++++++---------------- components/compiler/generator.cpp | 10 ------ components/compiler/generator.hpp | 2 -- components/compiler/scanner.cpp | 1 - components/compiler/scanner.hpp | 3 +- components/compiler/stringparser.cpp | 2 +- components/interpreter/docs/vmformat.txt | 2 +- components/interpreter/installopcodes.cpp | 1 - components/interpreter/mathopcodes.hpp | 20 +----------- 10 files changed, 19 insertions(+), 65 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 869a1d540a..149f9f3f3f 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -864,6 +864,9 @@ namespace MWScript float param = runtime[0].mFloat; runtime.pop(); + if (param < 0) + throw std::runtime_error("square root of negative number (we aren't that imaginary)"); + runtime.push(std::sqrt (param)); } }; diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index fb7ca7aac4..23f15c8bf5 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -421,42 +421,26 @@ namespace Compiler if (mNextOperand) { - if (keyword==Scanner::K_getsquareroot) + // check for custom extensions + if (const Extensions *extensions = getContext().getExtensions()) { start(); - mTokenLoc = loc; - parseArguments ("f", scanner); + char returnType; + std::string argumentType; - Generator::squareRoot (mCode); - mOperands.push_back ('f'); + bool hasExplicit = false; - mNextOperand = false; - return true; - } - else - { - // check for custom extensions - if (const Extensions *extensions = getContext().getExtensions()) + if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit)) { - start(); - - char returnType; - std::string argumentType; + mTokenLoc = loc; + int optionals = parseArguments (argumentType, scanner); - bool hasExplicit = false; + extensions->generateFunctionCode (keyword, mCode, mLiterals, "", optionals); + mOperands.push_back (returnType); - if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit)) - { - mTokenLoc = loc; - int optionals = parseArguments (argumentType, scanner); - - extensions->generateFunctionCode (keyword, mCode, mLiterals, "", optionals); - mOperands.push_back (returnType); - - mNextOperand = false; - return true; - } + mNextOperand = false; + return true; } } } diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index 34da1c4120..3d8b87e69e 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -104,11 +104,6 @@ namespace code.push_back (Compiler::Generator::segment5 (17)); } - void opSquareRoot (Compiler::Generator::CodeContainer& code) - { - code.push_back (Compiler::Generator::segment5 (19)); - } - void opReturn (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (20)); @@ -452,11 +447,6 @@ namespace Compiler::Generator } } - void squareRoot (CodeContainer& code) - { - opSquareRoot (code); - } - void exit (CodeContainer& code) { opReturn (code); diff --git a/components/compiler/generator.hpp b/components/compiler/generator.hpp index 55bba2a75c..00c6e56825 100644 --- a/components/compiler/generator.hpp +++ b/components/compiler/generator.hpp @@ -74,8 +74,6 @@ namespace Compiler void convert (CodeContainer& code, char fromType, char toType); - void squareRoot (CodeContainer& code); - void exit (CodeContainer& code); void message (CodeContainer& code, Literals& literals, const std::string& message, diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 829df35be5..cd607b2e73 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -265,7 +265,6 @@ namespace Compiler "return", "messagebox", "set", "to", - "getsquareroot", nullptr }; diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 15781f9240..7901297325 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -205,8 +205,7 @@ namespace Compiler K_while, K_endwhile, K_return, K_messagebox, - K_set, K_to, - K_getsquareroot + K_set, K_to }; enum special diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index 8b20377e1f..d9c3c04947 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -63,7 +63,7 @@ namespace Compiler keyword==Scanner::K_elseif || keyword==Scanner::K_while || keyword==Scanner::K_endwhile || keyword==Scanner::K_return || keyword==Scanner::K_messagebox || keyword==Scanner::K_set || - keyword==Scanner::K_to || keyword==Scanner::K_getsquareroot) + keyword==Scanner::K_to) { // pretend this is not a keyword std::string name = loc.mLiteral; diff --git a/components/interpreter/docs/vmformat.txt b/components/interpreter/docs/vmformat.txt index b5c9cf0ae0..7eac8b26e0 100644 --- a/components/interpreter/docs/vmformat.txt +++ b/components/interpreter/docs/vmformat.txt @@ -77,7 +77,7 @@ op 15: div (integer) stack[1] by stack[0], pop twice, push result op 16: div (float) stack[1] by stack[0], pop twice, push result op 17: convert stack[1] from integer to float op 18: convert stack[1] from float to integer -op 19: take square root of stack[0] (float) +opcode 19 unused op 20: return op 21: replace stack[0] with local short stack[0] op 22: replace stack[0] with local long stack[0] diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index afee36bc28..b5cb229e84 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -59,7 +59,6 @@ namespace Interpreter interpreter.installSegment5 (14, new OpMulInt); interpreter.installSegment5 (15, new OpDivInt); interpreter.installSegment5 (16, new OpDivInt); - interpreter.installSegment5 (19, new OpSquareRoot); interpreter.installSegment5 (26, new OpCompare >); interpreter.installSegment5 (27, diff --git a/components/interpreter/mathopcodes.hpp b/components/interpreter/mathopcodes.hpp index 42cb486b9c..bf580c6c2e 100644 --- a/components/interpreter/mathopcodes.hpp +++ b/components/interpreter/mathopcodes.hpp @@ -74,24 +74,6 @@ namespace Interpreter } }; - class OpSquareRoot : public Opcode0 - { - public: - - void execute (Runtime& runtime) override - { - Type_Float value = runtime[0].mFloat; - - if (value<0) - throw std::runtime_error ( - "square root of negative number (we aren't that imaginary)"); - - value = std::sqrt (value); - - runtime[0].mFloat = value; - } - }; - template class OpCompare : public Opcode0 { @@ -105,7 +87,7 @@ namespace Interpreter runtime[0].mInteger = result; } - }; + }; } #endif From 0e06e9b22188e7f23feacd0ddef1cdc286a3f732 Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sun, 19 Sep 2021 20:29:32 +0200 Subject: [PATCH 1334/2859] Removed useless comment and converted the file to UTF8 to keep special characters --- .../mwdialogue/test_keywordsearch.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index 25ea566165..d633feecd9 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -71,20 +71,18 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) { // We make sure that the search works well even if the character is not ASCII MWDialogue::KeywordSearch search; - search.seed("tats", 0); - search.seed("rradis", 0); - search.seed("a nous dois", 0); + search.seed("états", 0); + search.seed("ïrradiés", 0); + search.seed("ça nous déçois", 0); - std::string text = "les nations unis ont runis le monde entier, tats units inclus pour parler du problme des gens rradis et a nous dois"; + std::string text = "les nations unis ont réunis le monde entier, états units inclus pour parler du problème des gens ïrradiés et ça nous déçois"; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); ASSERT_TRUE (matches.size() == 3); - ASSERT_TRUE(std::string( matches[0].mBeg, matches[0].mEnd) == "tats"); - ASSERT_TRUE(std::string( matches[1].mBeg, matches[1].mEnd) == "rradis"); - ASSERT_TRUE(std::string( matches[2].mBeg, matches[2].mEnd) == "a nous dois"); - - //ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); + ASSERT_TRUE(std::string( matches[0].mBeg, matches[0].mEnd) == "états"); + ASSERT_TRUE(std::string( matches[1].mBeg, matches[1].mEnd) == "ïrradiés"); + ASSERT_TRUE(std::string( matches[2].mBeg, matches[2].mEnd) == "ça nous déçois"); } From b3d1d106af76e748f695d50e621d57c28f0091a1 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 29 Aug 2021 14:26:17 +0300 Subject: [PATCH 1335/2859] Collada alpha testing and uniforms --- components/resource/scenemanager.cpp | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 76e73da384..8056e6911b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -217,7 +218,52 @@ namespace Resource int mMaxAnisotropy; }; + // Check Collada extra descriptions + class ColladaAlphaTrickVisitor : public osg::NodeVisitor + { + public: + ColladaAlphaTrickVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + osg::AlphaFunc::ComparisonFunction getTestMode(std::string mode) + { + if (mode == "ALWAYS") return osg::AlphaFunc::ALWAYS; + if (mode == "LESS") return osg::AlphaFunc::LESS; + if (mode == "EQUAL") return osg::AlphaFunc::EQUAL; + if (mode == "LEQUAL") return osg::AlphaFunc::LEQUAL; + if (mode == "GREATER") return osg::AlphaFunc::GREATER; + if (mode == "NOTEQUAL") return osg::AlphaFunc::NOTEQUAL; + if (mode == "GEQUAL") return osg::AlphaFunc::GEQUAL; + if (mode == "NEVER") return osg::AlphaFunc::NEVER; + + Log(Debug::Info) << "Unexpected alpha testing mode: " << mode; + return osg::AlphaFunc::LEQUAL; + } + + void apply(osg::Node& node) override + { + std::vector descriptions = node.getDescriptions(); + for (auto description : descriptions) + { + std::vector descriptionParts; + std::istringstream descriptionStringStream(description); + for (std::string part; std::getline(descriptionStringStream, part, ' ');) + descriptionParts.emplace_back(part); + + if (descriptionParts.at(0) == "alphatest") + { + osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); + osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.back()))); + node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + Log(Debug::Info) << "Setting collada alpha test " << description << " for " << node.getName(); + } + } + traverse(node); + } + }; SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) @@ -424,6 +470,18 @@ namespace Resource if (nameFinder.mFoundNode) nameFinder.mFoundNode->setNodeMask(hiddenNodeMask); + if (ext == "dae") + { + // Collada alpha testing + Resource::ColladaAlphaTrickVisitor colladaAlphaTrickVisitor; + result.getNode()->accept(colladaAlphaTrickVisitor); + + result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); + result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); + result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); + } + + return result.getNode(); } } From 89982eb9e333d74ed69f57a313096da486b18262 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 29 Aug 2021 14:31:11 +0300 Subject: [PATCH 1336/2859] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 917de55fe2..8a92127dca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Feature #6032: Reverse-z depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering + Feature #6249: Alpha testing support for Collada Feature #6251: OpenMW-CS: Set instance movement based on camera zoom Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp From 40497d6fe55b68f708cd1063e7a36f7a75ab7389 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 11 Sep 2021 13:55:17 +0300 Subject: [PATCH 1337/2859] Set depth testing for alpha blend & test, depth writes off for blend. --- components/resource/scenemanager.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 8056e6911b..b4a26f0f35 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -244,6 +244,17 @@ namespace Resource void apply(osg::Node& node) override { + bool osgDepthCreated(false); + + if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + depth->setWriteMask(false); + osgDepthCreated = true; + + node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + std::vector descriptions = node.getDescriptions(); for (auto description : descriptions) { @@ -255,6 +266,12 @@ namespace Resource if (descriptionParts.at(0) == "alphatest") { + if (!osgDepthCreated) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.back()))); node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); From 96f02ab32c4d2fb329f4c382499154673bb56dbb Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 11 Sep 2021 17:03:16 +0300 Subject: [PATCH 1338/2859] Per-material alpha testing for collada --- components/resource/scenemanager.cpp | 36 ++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b4a26f0f35..80d8f1ecdc 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -255,31 +255,47 @@ namespace Resource node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); } + /* Check if the has + correct format for OpenMW: alphatest mode value MaterialName + e.g alphatest GEQUAL 0.8 MyAlphaTestedMaterial */ std::vector descriptions = node.getDescriptions(); for (auto description : descriptions) { - std::vector descriptionParts; + mDescriptions.emplace_back(description); + } + // Iterate each description, and see if the current node uses the specified material for alpha testing + for (auto description : mDescriptions) + { + std::vector descriptionParts; std::istringstream descriptionStringStream(description); for (std::string part; std::getline(descriptionStringStream, part, ' ');) + { descriptionParts.emplace_back(part); + } - if (descriptionParts.at(0) == "alphatest") + if (descriptionParts.size() > (3) && descriptionParts.at(3) == node.getOrCreateStateSet()->getName()) { - if (!osgDepthCreated) + if (descriptionParts.at(0) == "alphatest") { - osg::ref_ptr depth = SceneUtil::createDepth(); - node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + if (!osgDepthCreated) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + + osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); + osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); + node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + Log(Debug::Info) << "Setting collada alpha test for " << node.getName(); } - - osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); - osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.back()))); - node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - Log(Debug::Info) << "Setting collada alpha test " << description << " for " << node.getName(); } } + traverse(node); } + private: + std::vector mDescriptions; }; SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) From ec0b36d21d4035e7a4fbe7b3c222a15f04248523 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 12 Sep 2021 20:31:34 +0300 Subject: [PATCH 1339/2859] Don't make a new osg::depth to alpha tested node --- components/resource/scenemanager.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 80d8f1ecdc..b9bab66faa 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -244,13 +244,10 @@ namespace Resource void apply(osg::Node& node) override { - bool osgDepthCreated(false); - if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) { osg::ref_ptr depth = SceneUtil::createDepth(); depth->setWriteMask(false); - osgDepthCreated = true; node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); } @@ -278,12 +275,6 @@ namespace Resource { if (descriptionParts.at(0) == "alphatest") { - if (!osgDepthCreated) - { - osg::ref_ptr depth = SceneUtil::createDepth(); - node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - } - osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); From f2a894024a646204c283c969b8acb33db0de255e Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 12 Sep 2021 21:08:59 +0300 Subject: [PATCH 1340/2859] Change debug levels --- components/resource/scenemanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b9bab66faa..3112e3c837 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -238,7 +238,7 @@ namespace Resource if (mode == "GEQUAL") return osg::AlphaFunc::GEQUAL; if (mode == "NEVER") return osg::AlphaFunc::NEVER; - Log(Debug::Info) << "Unexpected alpha testing mode: " << mode; + Log(Debug::Warning) << "Unexpected alpha testing mode: " << mode; return osg::AlphaFunc::LEQUAL; } @@ -278,7 +278,6 @@ namespace Resource osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - Log(Debug::Info) << "Setting collada alpha test for " << node.getName(); } } } From 67894349a97e9949b2057055fd06933538cfa2e9 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 18 Sep 2021 14:02:28 +0300 Subject: [PATCH 1341/2859] Add a check for OPAQUE_BIN --- components/resource/scenemanager.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 3112e3c837..b468b430ff 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -251,6 +251,13 @@ namespace Resource node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); } + else if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::OPAQUE_BIN) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + depth->setWriteMask(true); + + node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } /* Check if the has correct format for OpenMW: alphatest mode value MaterialName From 2d329548887ff91b38a2b757bf216ac1065b20ee Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Mon, 20 Sep 2021 19:56:47 +0200 Subject: [PATCH 1342/2859] Replaced Assert_true with expect_eq --- .../mwdialogue/test_keywordsearch.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index d633feecd9..62b6f67aae 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -27,9 +27,9 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution) search.highlightKeywords(text.begin(), text.end(), matches); // Should contain: "foo bar", "lock switch" - ASSERT_TRUE (matches.size() == 2); - ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "foo bar"); - ASSERT_TRUE (std::string(matches.rbegin()->mBeg, matches.rbegin()->mEnd) == "lock switch"); + EXPECT_EQ (matches.size() , 2); + EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "foo bar"); + EXPECT_EQ (std::string(matches.rbegin()->mBeg, matches.rbegin()->mEnd) , "lock switch"); } TEST_F(KeywordSearchTest, keyword_test_conflict_resolution2) @@ -43,8 +43,8 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution2) std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); - ASSERT_TRUE (matches.size() == 1); - ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "dwemer language"); + EXPECT_EQ (matches.size() , 1); + EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "dwemer language"); } @@ -62,8 +62,8 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution3) std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); - ASSERT_TRUE (matches.size() == 1); - ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); + EXPECT_EQ (matches.size() , 1); + EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "bar lock"); } @@ -81,8 +81,8 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); - ASSERT_TRUE (matches.size() == 3); - ASSERT_TRUE(std::string( matches[0].mBeg, matches[0].mEnd) == "états"); - ASSERT_TRUE(std::string( matches[1].mBeg, matches[1].mEnd) == "ïrradiés"); - ASSERT_TRUE(std::string( matches[2].mBeg, matches[2].mEnd) == "ça nous déçois"); + EXPECT_EQ (matches.size() , 3); + EXPECT_EQ (std::string( matches[0].mBeg, matches[0].mEnd) , "états"); + EXPECT_EQ (std::string( matches[1].mBeg, matches[1].mEnd) , "ïrradiés"); + EXPECT_EQ (std::string( matches[2].mBeg, matches[2].mEnd) , "ça nous déçois"); } From 99df1c695c32ad4bb636501554e2430d3ce87b40 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:17:55 +0000 Subject: [PATCH 1343/2859] fixes wireframe is broken (#3123) * renderingmanager.cpp [ci skip] * groundcover.hpp [ci skip] * groundcover.cpp * renderingmanager.cpp [ci skip] * renderingmanager.hpp --- apps/openmw/mwrender/groundcover.cpp | 30 ------------ apps/openmw/mwrender/groundcover.hpp | 22 --------- apps/openmw/mwrender/renderingmanager.cpp | 56 ++++++++++++++++------- apps/openmw/mwrender/renderingmanager.hpp | 2 - 4 files changed, 39 insertions(+), 71 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index a199201d41..94bd900aea 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -27,36 +27,6 @@ namespace MWRender } } - void GroundcoverUpdater::setWindSpeed(float windSpeed) - { - mWindSpeed = windSpeed; - } - - void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos) - { - mPlayerPos = playerPos; - } - - void GroundcoverUpdater::setDefaults(osg::StateSet *stateset) - { - osg::ref_ptr windUniform = new osg::Uniform("windSpeed", 0.0f); - stateset->addUniform(windUniform.get()); - - osg::ref_ptr playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)); - stateset->addUniform(playerPosUniform.get()); - } - - void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) - { - osg::ref_ptr windUniform = stateset->getUniform("windSpeed"); - if (windUniform != nullptr) - windUniform->set(mWindSpeed); - - osg::ref_ptr playerPosUniform = stateset->getUniform("playerPos"); - if (playerPosUniform != nullptr) - playerPosUniform->set(mPlayerPos); - } - class InstancingVisitor : public osg::NodeVisitor { public: diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index 4874bea899..0a83976441 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -3,32 +3,10 @@ #include #include -#include #include namespace MWRender { - class GroundcoverUpdater : public SceneUtil::StateSetUpdater - { - public: - GroundcoverUpdater() - : mWindSpeed(0.f) - , mPlayerPos(osg::Vec3f()) - { - } - - void setWindSpeed(float windSpeed); - void setPlayerPos(osg::Vec3f playerPos); - - protected: - void setDefaults(osg::StateSet *stateset) override; - void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; - - private: - float mWindSpeed; - osg::Vec3f mPlayerPos; - }; - typedef std::tuple GroundcoverChunkId; // Center, Size class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 56d3ba4a43..19a887fe2e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -77,10 +77,12 @@ namespace MWRender class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater { public: - SharedUniformStateUpdater() + SharedUniformStateUpdater(bool usePlayerUniforms) : mLinearFac(0.f) , mNear(0.f) , mFar(0.f) + , mUsePlayerUniforms(usePlayerUniforms) + , mWindSpeed(0.f) { } @@ -90,6 +92,11 @@ namespace MWRender stateset->addUniform(new osg::Uniform("linearFac", 0.f)); stateset->addUniform(new osg::Uniform("near", 0.f)); stateset->addUniform(new osg::Uniform("far", 0.f)); + if (mUsePlayerUniforms) + { + stateset->addUniform(new osg::Uniform("windSpeed", 0.0f)); + stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f))); + } } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override @@ -110,6 +117,16 @@ namespace MWRender if (uFar) uFar->set(mFar); + if (mUsePlayerUniforms) + { + auto* windSpeed = stateset->getUniform("windSpeed"); + if (windSpeed) + windSpeed->set(mWindSpeed); + + auto* playerPos = stateset->getUniform("playerPos"); + if (playerPos) + playerPos->set(mPlayerPos); + } } void setProjectionMatrix(const osg::Matrixf& projectionMatrix) @@ -132,11 +149,25 @@ namespace MWRender mFar = far; } + void setWindSpeed(float windSpeed) + { + mWindSpeed = windSpeed; + } + + void setPlayerPos(osg::Vec3f playerPos) + { + mPlayerPos = playerPos; + } + + private: osg::Matrixf mProjectionMatrix; float mLinearFac; float mNear; float mFar; + bool mUsePlayerUniforms; + float mWindSpeed; + osg::Vec3f mPlayerPos; }; class StateUpdater : public SceneUtil::StateSetUpdater @@ -400,16 +431,11 @@ namespace MWRender mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); - osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; - if (groundcover) { float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); - mGroundcoverUpdater = new GroundcoverUpdater; - composite->addController(mGroundcoverUpdater); - mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); @@ -419,10 +445,9 @@ namespace MWRender } mStateUpdater = new StateUpdater; - composite->addController(mStateUpdater); - sceneRoot->addUpdateCallback(composite); + sceneRoot->addUpdateCallback(mStateUpdater); - mSharedUniformStateUpdater = new SharedUniformStateUpdater; + mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover); rootNode->addUpdateCallback(mSharedUniformStateUpdater); mPostProcessor = new PostProcessor(*this, viewer, mRootNode); @@ -791,15 +816,12 @@ namespace MWRender mSky->update(dt); mWater->update(dt); - if (mGroundcoverUpdater) - { - const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); - osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); - float windSpeed = mSky->getBaseWindSpeed(); - mGroundcoverUpdater->setWindSpeed(windSpeed); - mGroundcoverUpdater->setPlayerPos(playerPos); - } + float windSpeed = mSky->getBaseWindSpeed(); + mSharedUniformStateUpdater->setWindSpeed(windSpeed); + mSharedUniformStateUpdater->setPlayerPos(playerPos); } updateNavMesh(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b991b08efa..bd8bbe4694 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -70,7 +70,6 @@ namespace DetourNavigator namespace MWRender { - class GroundcoverUpdater; class StateUpdater; class SharedUniformStateUpdater; @@ -279,7 +278,6 @@ namespace MWRender std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; std::unique_ptr mGroundcover; - osg::ref_ptr mGroundcoverUpdater; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; From d9c3ba03f4e846128d318bd09d94a8a900d52899 Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Mon, 20 Sep 2021 21:01:28 +0200 Subject: [PATCH 1344/2859] Uses limits instead of climits --- 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 fc6f2a91e8..8ba3349bc8 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -1,9 +1,9 @@ #ifndef GAME_MWDIALOGUE_KEYWORDSEARCH_H #define GAME_MWDIALOGUE_KEYWORDSEARCH_H -#include -#include #include +#include +#include #include #include #include // std::reverse @@ -71,7 +71,7 @@ public: static bool isWhitespaceUTF8(const int utf8Char) { - if (utf8Char >= 0 && utf8Char <= UCHAR_MAX) + if (utf8Char >= 0 && utf8Char <= static_cast( std::numeric_limits::max())) { //That function has undefined behavior if the character doesn't fit in unsigned char return std::isspace(utf8Char); From b22fb7a7bf8e7a3e198d1d40bdc12b15f4fc049a Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 21 Sep 2021 20:39:31 +0000 Subject: [PATCH 1345/2859] consolidate node mask checks (#3125) * consolidate node mask checks This PR simplifies a few checks against node masks in object paging by using the osg provided `setTraversalMask`. * objectpaging.cpp --- apps/openmw/mwrender/objectpaging.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 376c517d59..47eacbe1a2 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -272,7 +272,7 @@ namespace MWRender : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mCurrentStateSet(nullptr) , mCurrentDistance(0.f) - , mAnalyzeMask(analyzeMask) {} + { setTraversalMask(analyzeMask); } typedef std::unordered_map StateSetCounter; struct Result @@ -283,9 +283,6 @@ namespace MWRender void apply(osg::Node& node) override { - if (!(node.getNodeMask() & mAnalyzeMask)) - return; - if (node.getStateSet()) mCurrentStateSet = node.getStateSet(); @@ -308,9 +305,6 @@ namespace MWRender } void apply(osg::Geometry& geom) override { - if (!(geom.getNodeMask() & mAnalyzeMask)) - return; - if (osg::Array* array = geom.getVertexArray()) mResult.mNumVerts += array->getNumElements(); @@ -345,7 +339,6 @@ namespace MWRender osg::StateSet* mCurrentStateSet; StateSetCounter mGlobalStateSetCounter; float mCurrentDistance; - osg::Node::NodeMask mAnalyzeMask; }; class DebugVisitor : public osg::NodeVisitor From d38c8c6dcbe76b88ed7b1d8b267d0d10734deec8 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 21 Sep 2021 20:41:07 +0000 Subject: [PATCH 1346/2859] optimise chunk drawing order (#3116) * material.cpp * material.cpp --- components/terrain/material.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 28297fb456..22f507b3a2 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -199,7 +199,6 @@ namespace Terrain std::vector > passes; unsigned int blendmapIndex = 0; - unsigned int passIndex = 0; for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) { bool firstLayer = (it == layers.begin()); @@ -209,7 +208,7 @@ namespace Terrain if (!blendmaps.empty()) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setRenderBinDetails(passIndex++, "RenderBin"); + stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin"); if (!firstLayer) { stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); From 5893b8840935b86843066cf82d7dd9e7fee469aa Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 22 Sep 2021 01:46:04 +0200 Subject: [PATCH 1347/2859] Ignore time to destination when giving way (#6296) --- apps/openmw/mwmechanics/actors.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 37b438aefa..f4cb40c2f5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1807,6 +1807,7 @@ namespace MWMechanics // Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either // follow player or have a AIWander package with non-empty wander area. bool shouldAvoidCollision = isMoving; + bool shouldGiveWay = false; bool shouldTurnToApproachingActor = !isMoving; MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets). const auto& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence(); @@ -1817,7 +1818,7 @@ namespace MWMechanics else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) { if (!static_cast(package.get())->isStationary()) - shouldAvoidCollision = true; + shouldGiveWay = true; } else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue) { @@ -1827,7 +1828,7 @@ namespace MWMechanics break; } } - if (!shouldAvoidCollision) + if (!shouldAvoidCollision && !shouldGiveWay) continue; osg::Vec2f baseSpeed = origMovement * maxSpeed; @@ -1836,14 +1837,14 @@ namespace MWMechanics const osg::Vec3f halfExtents = world->getHalfExtents(ptr); float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding; - float timeToCollision = maxTimeToCheck; + float timeToCheck = maxTimeToCheck; + if (!shouldGiveWay && !aiSequence.isEmpty()) + timeToCheck = std::min(timeToCheck, getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents)); + + float timeToCollision = timeToCheck; osg::Vec2f movementCorrection(0, 0); float angleToApproachingActor = 0; - const float timeToDestination = aiSequence.isEmpty() - ? std::numeric_limits::max() - : getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents); - // Iterate through all other actors and predict collisions. for(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter) { @@ -1880,7 +1881,7 @@ namespace MWMechanics continue; // No solution; distance is always >= collisionDist. float t = (-vr - std::sqrt(Dh)) / v2; - if (t < 0 || t > timeToCollision || t > timeToDestination) + if (t < 0 || t > timeToCollision) continue; // Check visibility and awareness last as it's expensive. @@ -1900,7 +1901,7 @@ namespace MWMechanics movementCorrection.y() *= 0.5f; } - if (timeToCollision < maxTimeToCheck) + if (timeToCollision < timeToCheck) { // Try to evade the nearest collision. osg::Vec2f newMovement = origMovement + movementCorrection; From e641bea606171542ab52f5c7e099b030d2ed4aac Mon Sep 17 00:00:00 2001 From: Pi03k Date: Sun, 19 Sep 2021 19:07:54 +0200 Subject: [PATCH 1348/2859] Toggling table columns visibility --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/scenesubview.hpp | 1 - apps/opencs/view/world/table.cpp | 3 + .../world/tableheadermouseeventhandler.cpp | 64 +++++++++++++++++++ .../world/tableheadermouseeventhandler.hpp | 25 ++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 apps/opencs/view/world/tableheadermouseeventhandler.cpp create mode 100644 apps/opencs/view/world/tableheadermouseeventhandler.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0ffa3da559..952bbbdbda 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -71,7 +71,7 @@ opencs_units (view/world cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator - bodypartcreator landtexturecreator landcreator + bodypartcreator landtexturecreator landcreator tableheadermouseeventhandler ) opencs_units (view/world diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index aabb7ca2a7..53cd54e7ac 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -32,7 +32,6 @@ namespace CSVWidget namespace CSVWorld { - class Table; class TableBottomBox; class CreatorFactoryBase; diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 2834159b7f..643396a057 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -28,6 +28,7 @@ #include "../../model/prefs/shortcut.hpp" #include "tableeditidaction.hpp" +#include "tableheadermouseeventhandler.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) @@ -422,6 +423,8 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); CSMPrefs::get()["ID Tables"].update(); + + new TableHeaderMouseEventHandler(this); } void CSVWorld::Table::setEditLock (bool locked) diff --git a/apps/opencs/view/world/tableheadermouseeventhandler.cpp b/apps/opencs/view/world/tableheadermouseeventhandler.cpp new file mode 100644 index 0000000000..866c6149db --- /dev/null +++ b/apps/opencs/view/world/tableheadermouseeventhandler.cpp @@ -0,0 +1,64 @@ +#include "tableheadermouseeventhandler.hpp" +#include "dragrecordtable.hpp" + +#include +#include + +namespace CSVWorld +{ + +TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable * parent) + : QWidget(parent) + , table(*parent) + , header(*table.horizontalHeader()) +{ + header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); + connect( + &header, &QHeaderView::customContextMenuRequested, [=](const QPoint & position) { showContextMenu(position); }); + + header.viewport()->installEventFilter(this); +} + +bool TableHeaderMouseEventHandler::eventFilter(QObject * tableWatched, QEvent * event) +{ + if (event->type() == QEvent::Type::MouseButtonPress) + { + auto & clickEvent = static_cast(*event); + if ((clickEvent.button() == Qt::MiddleButton)) + { + const auto & index = table.indexAt(clickEvent.pos()); + table.setColumnHidden(index.column(), true); + clickEvent.accept(); + return true; + } + } + return false; +} + +void TableHeaderMouseEventHandler::showContextMenu(const QPoint & position) +{ + auto & menu{createContextMenu()}; + menu.popup(header.viewport()->mapToGlobal(position)); +} + +QMenu & TableHeaderMouseEventHandler::createContextMenu() +{ + auto * menu = new QMenu(this); + for (int i = 0; i < table.model()->columnCount(); ++i) + { + const auto & name = table.model()->headerData(i, Qt::Horizontal, Qt::DisplayRole); + QAction * action{new QAction(name.toString(), this)}; + action->setCheckable(true); + action->setChecked(!table.isColumnHidden(i)); + menu->addAction(action); + + connect(action, &QAction::triggered, [=]() { + table.setColumnHidden(i, !action->isChecked()); + action->setChecked(!action->isChecked()); + action->toggle(); + }); + } + return *menu; +} + +} // namespace CSVWorld diff --git a/apps/opencs/view/world/tableheadermouseeventhandler.hpp b/apps/opencs/view/world/tableheadermouseeventhandler.hpp new file mode 100644 index 0000000000..934bc1dbb7 --- /dev/null +++ b/apps/opencs/view/world/tableheadermouseeventhandler.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace CSVWorld +{ +class DragRecordTable; + +class TableHeaderMouseEventHandler : public QWidget +{ +public: + explicit TableHeaderMouseEventHandler(DragRecordTable * parent); + + void showContextMenu(const QPoint &); + +private: + DragRecordTable & table; + QHeaderView & header; + + QMenu & createContextMenu(); + bool eventFilter(QObject *, QEvent *) override; + +}; // class TableHeaderMouseEventHandler +} // namespace CSVWorld From d36595e09efd85bf9d419b97bc50d9e4c37533c2 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 23 Sep 2021 01:47:19 +0200 Subject: [PATCH 1349/2859] Make sure PathFinder::getClosestPoint is not called with failing precondition Pathgrid should be not nullptr and points should be not empty. --- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4b490d7bc8..eb139c918d 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -348,7 +348,7 @@ namespace MWMechanics bool runFallback = true; - if (pathgrid && !actor.getClass().isPureWaterCreature(actor)) + if (pathgrid != nullptr && !pathgrid->mPoints.empty() && !actor.getClass().isPureWaterCreature(actor)) { ESM::Pathgrid::PointList points; Misc::CoordinateConverter coords(storage.mCell->getCell()); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 19365bcb08..624abe10f9 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -758,6 +758,9 @@ namespace MWMechanics const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); + if (pathgrid == nullptr || pathgrid->mPoints.empty()) + return; + int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest)); getPathGridGraph(currentCell).getNeighbouringPoints(index, points); From b676d93e03f52645d827da19d87659f6343c6650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Thu, 23 Sep 2021 18:47:59 +0200 Subject: [PATCH 1350/2859] Use a pair of iterator to represents a range for directory listing --- components/vfs/manager.cpp | 19 ++++++++++-- components/vfs/manager.hpp | 63 ++++++++++++++------------------------ 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 1cb8745497..faebc782aa 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -105,8 +105,23 @@ namespace VFS return {}; } - RecursiveDirectoryIterator Manager::getRecursiveDirectoryIterator(const std::string& path) const + namespace { - return RecursiveDirectoryIterator(mIndex, normalizeFilename(path)); + bool startsWith(std::string_view text, std::string_view start) + { + return text.rfind(start, 0) == 0; + } + } + + Manager::RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(const std::string& path) const + { + if (path.empty()) + return { mIndex.begin(), mIndex.end() }; + auto normalized = normalizeFilename(path); + const auto it = mIndex.lower_bound(normalized); + if (it == mIndex.end() || !startsWith(it->first, normalized)) + return { it, it }; + ++normalized.back(); + return { it, mIndex.lower_bound(normalized) }; } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index b98d8021d4..8568e8e784 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -12,51 +12,19 @@ namespace VFS class Archive; class File; - class RecursiveDirectoryIterator; - RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); - - class RecursiveDirectoryIterator + template + class IteratorPair { public: - RecursiveDirectoryIterator(const std::map& index, const std::string& path) - : mPath(path) - , mIndex(&index) - , mIt(index.lower_bound(path)) - {} - - RecursiveDirectoryIterator(const RecursiveDirectoryIterator&) = default; - - const std::string& operator*() const { return mIt->first; } - const std::string* operator->() const { return &mIt->first; } - - bool operator!=(const RecursiveDirectoryIterator& other) { return mPath != other.mPath || mIt != other.mIt; } - - RecursiveDirectoryIterator& operator++() - { - if (++mIt == mIndex->end() || !starts_with(mIt->first, mPath)) - *this = end(*this); - return *this; - } - - friend RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); + IteratorPair(Iterator first, Iterator last) : mFirst(first), mLast(last) {} + Iterator begin() const { return mFirst; } + Iterator end() const { return mLast; } private: - static bool starts_with(const std::string& text, const std::string& start) { return text.rfind(start, 0) == 0; } - - std::string mPath; - const std::map* mIndex; - std::map::const_iterator mIt; + Iterator mFirst; + Iterator mLast; }; - inline RecursiveDirectoryIterator begin(RecursiveDirectoryIterator iter) { return iter; } - - inline RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter) - { - RecursiveDirectoryIterator result(iter); - result.mIt = result.mIndex->end(); - return result; - } - /// @brief The main class responsible for loading files from a virtual file system. /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) /// can be registered, and will be merged into a single file tree. If the same filename is @@ -64,6 +32,21 @@ namespace VFS /// @par Most of the methods in this class are considered thread-safe, see each method documentation for details. class Manager { + class RecursiveDirectoryIterator + { + public: + RecursiveDirectoryIterator(std::map::const_iterator it) : mIt(it) {} + const std::string& operator*() const { return mIt->first; } + const std::string* operator->() const { return &mIt->first; } + bool operator!=(const RecursiveDirectoryIterator& other) { return mIt != other.mIt; } + RecursiveDirectoryIterator& operator++() { ++mIt; return *this; } + + private: + std::map::const_iterator mIt; + }; + + using RecursiveDirectoryRange = IteratorPair; + public: /// @param strict Use strict path handling? If enabled, no case folding will /// be done, but slash/backslash conversions are always done. @@ -105,7 +88,7 @@ namespace VFS /// In practice it return all files of the VFS starting with the given path /// @note the path is normalized /// @note May be called from any thread once the index has been built. - RecursiveDirectoryIterator getRecursiveDirectoryIterator(const std::string& path) const; + RecursiveDirectoryRange getRecursiveDirectoryIterator(const std::string& path) const; private: bool mStrict; From 98a7d90ee258ceef9c70b0b2955d0458ec46f048 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 24 Sep 2021 19:40:29 +0200 Subject: [PATCH 1351/2859] Assume SIGSTKSZ is not a constant SIGSTKSZ is not defined as constant since glibc 2.34: https://sourceware.org/git/?p=glibc.git;a=commit;h=6c57d320484988e87e446e2e60ce42816bf51d53 --- components/crashcatcher/crashcatcher.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 86571e1e3a..c828e1ca81 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -56,8 +56,6 @@ static const char exec_err[] = "!!! Failed to exec debug process\n"; static char argv0[PATH_MAX]; -static char altstack[SIGSTKSZ]; - static struct { int signum; @@ -475,9 +473,10 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig /* Set an alternate signal stack so SIGSEGVs caused by stack overflows * still run */ + static char* altstack = new char [SIGSTKSZ]; altss.ss_sp = altstack; altss.ss_flags = 0; - altss.ss_size = sizeof(altstack); + altss.ss_size = SIGSTKSZ; sigaltstack(&altss, nullptr); memset(&sa, 0, sizeof(sa)); From 5878b1fce6487206dbabc0dc50ed904ebe33893c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 24 Sep 2021 22:23:55 +0200 Subject: [PATCH 1352/2859] Use same logic for testing cell as for loading cell Having different branches makes testing less useful. If something fails in regular executing it should fail in testing. To make it possible there should be none differences in the execution paths. --- apps/openmw/mwworld/scene.cpp | 131 ++++++++++++++++------------------ apps/openmw/mwworld/scene.hpp | 10 +-- 2 files changed, 66 insertions(+), 75 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 89349d329a..e52f5532b4 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -200,13 +200,12 @@ namespace struct InsertVisitor { MWWorld::CellStore& mCell; - Loading::Listener& mLoadingListener; + Loading::Listener* mLoadingListener; bool mOnlyObjects; - bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyObjects, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects); bool operator() (const MWWorld::Ptr& ptr); @@ -214,8 +213,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyObjects, bool test) - : mCell (cell), mLoadingListener (loadingListener), mOnlyObjects(onlyObjects), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects) + : mCell(cell), mLoadingListener(loadingListener), mOnlyObjects(onlyObjects) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -244,8 +243,8 @@ namespace } } - if (!mTest) - mLoadingListener.increaseProgress (1); + if (mLoadingListener != nullptr) + mLoadingListener->increaseProgress(1); } } @@ -325,12 +324,12 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadInactiveCell (CellStore* cell, bool test) + void Scene::unloadInactiveCell (CellStore* cell) { assert(mActiveCells.find(cell) == mActiveCells.end()); assert(mInactiveCells.find(cell) != mInactiveCells.end()); - if (!test) - Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); + + Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); ListObjectsVisitor visitor; @@ -351,13 +350,13 @@ namespace MWWorld mInactiveCells.erase(cell); } - void Scene::deactivateCell(CellStore* cell, bool test) + void Scene::deactivateCell(CellStore* cell) { assert(mInactiveCells.find(cell) != mInactiveCells.end()); if (mActiveCells.find(cell) == mActiveCells.end()) return; - if (!test) - Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); + + Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); ListAndResetObjectsVisitor visitor; @@ -409,7 +408,7 @@ namespace MWWorld mActiveCells.erase(cell); } - void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn) { using DetourNavigator::HeightfieldShape; @@ -417,17 +416,14 @@ namespace MWWorld assert(mInactiveCells.find(cell) != mInactiveCells.end()); mActiveCells.insert(cell); - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); const auto world = MWBase::Environment::get().getWorld(); const int cellX = cell->getCell()->getGridX(); const int cellY = cell->getCell()->getGridY(); - if (!test && cell->getCell()->isExterior()) + if (cell->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) { @@ -466,69 +462,64 @@ namespace MWWorld if (respawn) cell->respawn(); - insertCell (*cell, loadingListener, false, test); + insertCell(*cell, loadingListener, false); mRendering.addCell(cell); - if (!test) + + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) - { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); - if (cell->getCell()->isExterior()) - { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - { - const btTransform& transform =heightField->getCollisionObject()->getWorldTransform(); - mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - osg::Vec3f(static_cast(transform.getOrigin().x()), - static_cast(transform.getOrigin().y()), - waterLevel)); - } - } - else + if (cell->getCell()->isExterior()) + { + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) { - mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - osg::Vec3f(0, 0, waterLevel)); + const btTransform& transform =heightField->getCollisionObject()->getWorldTransform(); + mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + osg::Vec3f(static_cast(transform.getOrigin().x()), + static_cast(transform.getOrigin().y()), + waterLevel)); } } else - mPhysics->disableWater(); - - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - // The player is loaded before the scene and by default it is grounded, with the scene fully loaded, we validate and correct this. - if (player.mCell == cell) // Only run once, during initial cell load. { - mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); + mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + osg::Vec3f(0, 0, waterLevel)); } + } + else + mPhysics->disableWater(); - mNavigator.update(player.getRefData().getPosition().asVec3()); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - mRendering.configureAmbient(cell->getCell()); + // The player is loaded before the scene and by default it is grounded, with the scene fully loaded, we validate and correct this. + if (player.mCell == cell) // Only run once, during initial cell load. + { + mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); } + mNavigator.update(player.getRefData().getPosition().asVec3()); + + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + mRendering.configureAmbient(cell->getCell()); + mPreloader->notifyLoaded(cell); } - void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) + void Scene::loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener) { assert(mActiveCells.find(cell) == mActiveCells.end()); assert(mInactiveCells.find(cell) == mInactiveCells.end()); mInactiveCells.insert(cell); - if (test) - Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); - if (!test && cell->getCell()->isExterior()) + if (cell->getCell()->isExterior()) { float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; @@ -550,7 +541,7 @@ namespace MWWorld } } - insertCell (*cell, loadingListener, true, test); + insertCell(*cell, loadingListener, true); } void Scene::clear() @@ -746,8 +737,8 @@ namespace MWWorld loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadInactiveCell(cell, nullptr); + activateCell(cell, nullptr, false); auto iter = mInactiveCells.begin(); while (iter != mInactiveCells.end()) @@ -755,8 +746,8 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + deactivateCell(*iter); + unloadInactiveCell(*iter); break; } @@ -794,8 +785,8 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadInactiveCell(cell, nullptr); + activateCell(cell, nullptr, false); auto iter = mInactiveCells.begin(); while (iter != mInactiveCells.end()) @@ -804,8 +795,8 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + deactivateCell(*iter); + unloadInactiveCell(*iter); break; } @@ -988,9 +979,9 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects) { - InsertVisitor insertVisitor (cell, *loadingListener, onlyObjects, test); + InsertVisitor insertVisitor(cell, loadingListener, onlyObjects); cell.forEach (insertVisitor); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyObjects); }); if (!onlyObjects) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 75c070dd1e..3f5d7bf2f5 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -100,7 +100,7 @@ namespace MWWorld std::vector> mWorkItems; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects, bool test = false); + void insertCell(CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -116,10 +116,10 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; - void unloadInactiveCell (CellStore* cell, bool test = false); - void deactivateCell (CellStore* cell, bool test = false); - void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); + void unloadInactiveCell(CellStore* cell); + void deactivateCell(CellStore* cell); + void activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn); + void loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener); public: From e9f253c473e2343f903e325aa26038568a2eed87 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 19 Sep 2021 19:16:39 +0200 Subject: [PATCH 1353/2859] Support stacked histogram for frames with duration over given threshold --- scripts/osg_stats.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 8a36eb5963..037aafea00 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -38,7 +38,7 @@ import termtables @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.') + help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') @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, @@ -47,10 +47,17 @@ import termtables help='End processing at this frame.') @click.option('--frame_number_name', type=str, default='FrameNumber', help='Frame number metric name.') +@click.option('--hist_threshold', type=str, multiple=True, + help='Show a histogram for given metric only for frames with threshold_name metric over threshold_value.') +@click.option('--threshold_name', type=str, default='Frame duration', + help='Frame duration metric name.') +@click.option('--threshold_value', type=float, default=1.05/60, + help='Threshold for hist_over.') @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, timeseries_sum, stats_sum, begin_frame, end_frame, path, - commulative_timeseries, commulative_timeseries_sum, frame_number_name): + commulative_timeseries, commulative_timeseries_sum, frame_number_name, + hist_threshold, threshold_name, threshold_value): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} keys = collect_unique_keys(sources) frames, begin_frame, end_frame = collect_per_frame( @@ -76,6 +83,9 @@ def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, draw_plots(sources=frames, plots=plot) if stats: print_stats(sources=frames, keys=stats, stats_sum=stats_sum) + if hist_threshold: + draw_hist_threshold(sources=frames, keys=hist_threshold, begin_frame=begin_frame, + threshold_name=threshold_name, threshold_value=threshold_value) matplotlib.pyplot.show() @@ -240,7 +250,6 @@ def print_stats(sources, keys, stats_sum): if stats_sum: stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys))) metrics = list(stats[0].keys()) - max_key_size = max(len(tuple(v.values())[0]) for v in stats) termtables.print( [list(v.values()) for v in stats], header=metrics, @@ -248,6 +257,27 @@ def print_stats(sources, keys, stats_sum): ) +def draw_hist_threshold(sources, keys, begin_frame, threshold_name, threshold_value): + for name, frames in sources.items(): + indices = [n for n, v in enumerate(frames[threshold_name]) if v > threshold_value] + numbers = [v + begin_frame for v in indices] + x = [v for v in range(0, len(indices))] + fig, ax = matplotlib.pyplot.subplots() + ax.set_title(f'Frames with "{threshold_name}" > {threshold_value} ({len(indices)})') + ax.bar(x, [frames[threshold_name][v] for v in indices], label=threshold_name, color='black', alpha=0.2) + prev = 0 + for key in keys: + values = [frames[key][v] for v in indices] + ax.bar(x, values, bottom=prev, label=key) + prev = values + ax.hlines(threshold_value, x[0] - 1, x[-1] + 1, color='black', label='threshold', linestyles='dashed') + ax.xaxis.set_major_locator(matplotlib.pyplot.FixedLocator(x)) + ax.xaxis.set_major_formatter(matplotlib.pyplot.FixedFormatter(numbers)) + ax.grid(True) + ax.legend() + fig.canvas.set_window_title(f'hist_threshold:{name}') + + def filter_not_none(values): return [v for v in values if v is not None] @@ -282,5 +312,6 @@ def to_number(value): except ValueError: return float(value) + if __name__ == '__main__': main() From 83e0d9aeefd70413d4a04882eed0a8bda03cd19d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 26 Sep 2021 17:28:51 +0200 Subject: [PATCH 1354/2859] Minor refactoring: use Misc::normalizeAngle in worldimp.cpp --- apps/openmw/mwworld/worldimp.cpp | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bc28c2eb7b..9d52d7ee77 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -76,21 +77,6 @@ #include "contentloader.hpp" #include "esmloader.hpp" -namespace -{ - -// Wraps a value to (-PI, PI] -void wrap(float& rad) -{ - const float pi = static_cast(osg::PI); - if (rad>0) - rad = std::fmod(rad+pi, 2.0f*pi)-pi; - else - rad = std::fmod(rad-pi, 2.0f*pi)+pi; -} - -} - namespace MWWorld { struct GameContentLoader : public ContentLoader @@ -1290,8 +1276,6 @@ namespace MWWorld void World::rotateObject(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags) { - const float pi = static_cast(osg::PI); - ESM::Position pos = ptr.getRefData().getPosition(); float *objRot = pos.rot; if (flags & MWBase::RotationFlag_adjust) @@ -1313,13 +1297,9 @@ namespace MWWorld * currently it's done so for rotating the camera, which needs * clamping. */ - const float half_pi = pi/2.f; - - if(objRot[0] < -half_pi) objRot[0] = -half_pi; - else if(objRot[0] > half_pi) objRot[0] = half_pi; - - wrap(objRot[1]); - wrap(objRot[2]); + objRot[0] = osg::clampBetween(objRot[0], -osg::PIf / 2, osg::PIf / 2); + objRot[1] = Misc::normalizeAngle(objRot[1]); + objRot[2] = Misc::normalizeAngle(objRot[2]); } ptr.getRefData().setPosition(pos); From 413ac067ec8ecc0430b19c275b6ea30589bff435 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 1 Sep 2021 01:53:23 +0200 Subject: [PATCH 1355/2859] Allow creating omwaddons without a dependency on an omwgame --- apps/opencs/view/doc/filedialog.cpp | 2 +- .../contentselector/model/contentmodel.cpp | 24 +++++++------------ .../contentselector/view/contentselector.cpp | 15 ++++++------ .../contentselector/view/contentselector.hpp | 2 +- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 69490edca2..8ff063ed31 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -161,7 +161,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) bool isNew = (mAction == ContentAction_New); if (isNew) - success = success && !(name.isEmpty()); + success = !name.isEmpty(); else if (success) { ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 141ad13517..690f968142 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -107,34 +107,28 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index // addon can be checked if its gamefile is // ... special case, addon with no dependency can be used with any gamefile. - bool gamefileChecked = (file->gameFiles().count() == 0); + bool gamefileChecked = false; + bool noGameFiles = true; for (const QString &fileName : file->gameFiles()) { for (QListIterator dependencyIter(mFiles); dependencyIter.hasNext(); dependencyIter.next()) { //compare filenames only. Multiple instances //of the filename (with different paths) is not relevant here. - bool depFound = (dependencyIter.peekNext()->fileName().compare(fileName, Qt::CaseInsensitive) == 0); - - if (!depFound) + EsmFile* depFile = dependencyIter.peekNext(); + if (!depFile->isGameFile() || depFile->fileName().compare(fileName, Qt::CaseInsensitive) != 0) continue; - if (!gamefileChecked) + noGameFiles = false; + if (isChecked(depFile->filePath())) { - if (isChecked (dependencyIter.peekNext()->filePath())) - gamefileChecked = (dependencyIter.peekNext()->isGameFile()); - } - - // force it to iterate all files in cases where the current - // dependency is a game file to ensure that a later duplicate - // game file is / is not checked. - // (i.e., break only if it's not a gamefile or the game file has been checked previously) - if (gamefileChecked || !(dependencyIter.peekNext()->isGameFile())) + gamefileChecked = true; break; + } } } - if (gamefileChecked) + if (gamefileChecked || noGameFiles) { returnFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; } diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 6bb8e6e2c7..d7996dfae3 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -29,14 +29,12 @@ void ContentSelectorView::ContentSelector::buildContentModel() void ContentSelectorView::ContentSelector::buildGameFileView() { - ui.gameFileView->setVisible (true); - - ui.gameFileView->setPlaceholderText(QString("Select a game file...")); + ui.gameFileView->addItem(""); + ui.gameFileView->setVisible(true); connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); - ui.gameFileView->setCurrentIndex(-1); ui.gameFileView->setCurrentIndex(0); } @@ -108,7 +106,7 @@ void ContentSelectorView::ContentSelector::setProfileContent(const QStringList & void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) { - int index = -1; + int index = 0; if (!filename.isEmpty()) { @@ -168,10 +166,11 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) } } - if (ui.gameFileView->currentIndex() != -1) - ui.gameFileView->setCurrentIndex(-1); + if (ui.gameFileView->currentIndex() != 0) + ui.gameFileView->setCurrentIndex(0); mContentModel->uncheckAll(); + mContentModel->checkForLoadOrderErrors(); } void ContentSelectorView::ContentSelector::clearFiles() @@ -183,7 +182,7 @@ QString ContentSelectorView::ContentSelector::currentFile() const { QModelIndex currentIdx = ui.addonView->currentIndex(); - if (!currentIdx.isValid()) + if (!currentIdx.isValid() && ui.gameFileView->currentIndex() > 0) return ui.gameFileView->currentText(); QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex()); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 1b50f1e5e8..cda68fa1b7 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -40,7 +40,7 @@ namespace ContentSelectorView void setGameFile (const QString &filename = QString("")); bool isGamefileSelected() const - { return ui.gameFileView->currentIndex() != -1; } + { return ui.gameFileView->currentIndex() > 0; } QWidget *uiWidget() const { return ui.contentGroupBox; } From 5620e8f0e275db5b6b91d8b3d9bbf3fe8621b1b7 Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sun, 26 Sep 2021 20:40:11 +0200 Subject: [PATCH 1356/2859] Added the fix to the changelog, and my name to the authors --- AUTHORS.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 62121a797b..a1f00bca10 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -80,6 +80,7 @@ Programmers Federico Guerra (FedeWar) Fil Krynicki (filkry) Finbar Crago (finbar-crago) + Florent Teppe (Tetramir) Florian Weber (Florianjw) Frédéric Chardon (fr3dz10) Gaëtan Dezeiraud (Brouilles) diff --git a/CHANGELOG.md b/CHANGELOG.md index f81a490b49..80fc4aac02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6273: Respawning NPCs rotation is inconsistent + Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map From c679565893f1480205eb4438cf60aca1514f5cdc Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 16 Sep 2021 18:06:46 +0200 Subject: [PATCH 1357/2859] Make names starting with digits use normal name parsing code --- CHANGELOG.md | 1 + components/compiler/scanner.cpp | 25 +++---------------------- components/compiler/scanner.hpp | 2 +- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9ea47c56..73588cb749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6273: Respawning NPCs rotation is inconsistent + Bug #6282: Laura craft doesn't follow the player character Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index cd607b2e73..1054e2e269 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -164,8 +164,6 @@ namespace Compiler std::string value; c.appendTo(value); - bool error = false; - while (get (c)) { if (c.isDigit()) @@ -174,16 +172,11 @@ namespace Compiler } else if (!c.isMinusSign() && isStringCharacter (c)) { - error = true; - c.appendTo(value); + /// workaround that allows names to begin with digits + return scanName(c, parser, cont, value); } else if (c=='.') { - if (error) - { - putback (c); - break; - } return scanFloat (value, parser, cont); } else @@ -193,17 +186,6 @@ namespace Compiler } } - if (error) - { - /// workaround that allows names to begin with digits - /// \todo disable - TokenLoc loc (mLoc); - mLoc.mLiteral.clear(); - cont = parser.parseName (value, loc, *this); - return true; -// return false; - } - TokenLoc loc (mLoc); mLoc.mLiteral.clear(); @@ -268,9 +250,8 @@ namespace Compiler nullptr }; - bool Scanner::scanName (MultiChar& c, Parser& parser, bool& cont) + bool Scanner::scanName (MultiChar& c, Parser& parser, bool& cont, std::string name) { - std::string name; c.appendTo(name); if (!scanName (name)) diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 7901297325..8ee2672132 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -236,7 +236,7 @@ namespace Compiler bool scanFloat (const std::string& intValue, Parser& parser, bool& cont); - bool scanName (MultiChar& c, Parser& parser, bool& cont); + bool scanName (MultiChar& c, Parser& parser, bool& cont, std::string name = {}); /// \param name May contain the start of the name (one or more characters) bool scanName (std::string& name); From 34cf66139ab2ef9313bf5f9df31943b2a801eb60 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 16 Sep 2021 16:56:13 +0200 Subject: [PATCH 1358/2859] Make GetCurrentAIPackage return -1 for non-actors and dead actors --- CHANGELOG.md | 1 + apps/openmw/mwscript/aiextensions.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9ea47c56..aa117463dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6273: Respawning NPCs rotation is inconsistent + Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index d4de8ded5d..c5a4bb6dfc 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -362,7 +362,15 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - const auto value = static_cast(ptr.getClass().getCreatureStats (ptr).getAiSequence().getLastRunTypeId()); + Interpreter::Type_Integer value = -1; + if(ptr.getClass().isActor()) + { + const auto& stats = ptr.getClass().getCreatureStats(ptr); + if(!stats.isDead() || !stats.isDeathAnimationFinished()) + { + value = static_cast(stats.getAiSequence().getLastRunTypeId()); + } + } runtime.push (value); } From c6f7137ee1da615f602c18f365373d5d324481ac Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 27 Sep 2021 18:41:24 +0000 Subject: [PATCH 1359/2859] fixes bugs with share state (#3111) * optimizer.cpp merge fix * objectpaging.cpp * optimizer.hpp setSharedStateManager * optimizer.cpp shareState * scenemanager.cpp shareState * scenemanager.cpp * optimizer.cpp * optimizer.cpp * scenemanager.cpp * optimizer.cpp --- apps/openmw/mwrender/objectpaging.cpp | 2 +- components/resource/scenemanager.cpp | 12 ++++-------- components/sceneutil/optimizer.cpp | 12 +++++++++++- components/sceneutil/optimizer.hpp | 13 ++++++++++++- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 47eacbe1a2..88c3d4ba02 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -651,7 +651,7 @@ namespace MWRender } optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY; - mSceneManager->shareState(mergeGroup); + optimizer.optimize(mergeGroup, options); group->addChild(mergeGroup); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b468b430ff..cc38144794 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -659,22 +659,18 @@ namespace Resource osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); - // share state - // do this before optimizing so the optimizer will be able to combine nodes more aggressively - // note, because StateSets will be shared at this point, StateSets can not be modified inside the optimizer - mSharedStateMutex.lock(); - mSharedStateManager->share(loaded.get()); - mSharedStateMutex.unlock(); - if (canOptimize(normalized)) { SceneUtil::Optimizer optimizer; + optimizer.setSharedStateManager(mSharedStateManager, &mSharedStateMutex); optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); - static const unsigned int options = getOptimizationOptions(); + static const unsigned int options = getOptimizationOptions()|SceneUtil::Optimizer::SHARE_DUPLICATE_STATE; optimizer.optimize(loaded, options); } + else + shareState(loaded); if (compile && mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 5fbeb681fc..dffbe62ec7 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -30,6 +30,8 @@ #include #include +#include + #include #include #include @@ -84,6 +86,13 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) cstv.removeTransforms(node); } + if (options & SHARE_DUPLICATE_STATE && _sharedStateManager) + { + if (_sharedStateMutex) _sharedStateMutex->lock(); + _sharedStateManager->share(node); + if (_sharedStateMutex) _sharedStateMutex->unlock(); + } + if (options & REMOVE_REDUNDANT_NODES) { OSG_INFO<<"Optimizer::optimize() doing REMOVE_REDUNDANT_NODES"<getNumChildren()==1 && transform->getChild(0)->asTransform()!=0 && transform->getChild(0)->asTransform()->asMatrixTransform()!=0 && - transform->getChild(0)->asTransform()->getDataVariance()==osg::Object::STATIC) + (!transform->getChild(0)->getStateSet() || transform->getChild(0)->getStateSet()->referenceCount()==1) && + transform->getChild(0)->getDataVariance()==osg::Object::STATIC) { // now combine with its child. osg::MatrixTransform* child = transform->getChild(0)->asTransform()->asMatrixTransform(); diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 2d6293e231..7b32bafee2 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -25,6 +25,12 @@ //#include #include +#include + +namespace osgDB +{ + class SharedStateManager; +} //namespace osgUtil { namespace SceneUtil { @@ -65,7 +71,7 @@ class Optimizer public: - Optimizer() : _mergeAlphaBlending(false) {} + Optimizer() : _mergeAlphaBlending(false), _sharedStateManager(nullptr), _sharedStateMutex(nullptr) {} virtual ~Optimizer() {} enum OptimizationOptions @@ -121,6 +127,8 @@ class Optimizer void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; } + void setSharedStateManager(osgDB::SharedStateManager* sharedStateManager, std::mutex* sharedStateMutex) { _sharedStateMutex = sharedStateMutex; _sharedStateManager = sharedStateManager; } + /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ void reset(); @@ -258,6 +266,9 @@ class Optimizer osg::Vec3f _viewPoint; bool _mergeAlphaBlending; + osgDB::SharedStateManager* _sharedStateManager; + mutable std::mutex* _sharedStateMutex; + public: /** Flatten Static Transform nodes by applying their transform to the From 449539c0ed66484c9837a2d752de8c9905943bb7 Mon Sep 17 00:00:00 2001 From: psi29a Date: Mon, 27 Sep 2021 19:04:38 +0000 Subject: [PATCH 1360/2859] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9ea47c56..cc824b7540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,8 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6273: Respawning NPCs rotation is inconsistent - Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map From 3f68ddd8f4b44be062e122c7ef73a3f558274a6f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 27 Sep 2021 19:25:39 +0000 Subject: [PATCH 1361/2859] alternate debug chunks (#3127) * quadtreeworld.cpp * chunkmanager.cpp * chunkmanager.hpp * quadtreeworld.hpp * chunkmanager.cpp * quadtreeworld.cpp * quadtreeworld.cpp * quadtreeworld.cpp [ci skip] * quadtreeworld.hpp * quadtreeworld.cpp * quadtreeworld.cpp * quadtreeworld.cpp * chunkmanager.cpp * chunkmanager.cpp --- components/terrain/chunkmanager.cpp | 18 -------------- components/terrain/chunkmanager.hpp | 1 - components/terrain/quadtreeworld.cpp | 37 ++++++++++++++++++++++------ components/terrain/quadtreeworld.hpp | 3 +++ 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 7a6d4fdb0a..476805aa3c 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -3,9 +3,7 @@ #include #include -#include #include -#include #include @@ -13,14 +11,12 @@ #include #include -#include #include "terraindrawable.hpp" #include "material.hpp" #include "storage.hpp" #include "texturemanager.hpp" #include "compositemaprenderer.hpp" -#include namespace Terrain { @@ -35,7 +31,6 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T , mCompositeMapSize(512) , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) - , mDebugChunks(Settings::Manager::getBool("debug chunks", "Terrain")) { mMultiPassRoot = new osg::StateSet; mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN); @@ -238,19 +233,6 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve } geometry->setNodeMask(mNodeMask); - if (mDebugChunks) - { - osg::ref_ptr result(new osg::Group); - result->addChild(geometry); - auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask(), 5.f, { 1, 0, 0, 0 }); - osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; - osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); - trans->setDataVariance(osg::Object::STATIC); - trans->addChild(chunkBorder); - result->addChild(trans); - return result; - } - return geometry; } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 4e030e018c..9b7dbf3ee1 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -72,7 +72,6 @@ namespace Terrain unsigned int mCompositeMapSize; float mCompositeMapLevel; float mMaxCompGeometrySize; - bool mDebugChunks = false; }; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index bbf212942e..ef37b63ef6 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -244,6 +245,26 @@ private: osg::ref_ptr mRootNode; }; +class DebugChunkManager : public QuadTreeWorld::ChunkManager +{ +public: + DebugChunkManager(Resource::SceneManager* sceneManager, Storage* storage, unsigned int nodeMask) : mSceneManager(sceneManager), mStorage(storage), mNodeMask(nodeMask) {} + osg::ref_ptr getChunk(float size, const osg::Vec2f& chunkCenter, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; + auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size, mStorage, mSceneManager, mNodeMask, 5.f, { 1, 0, 0, 0 }); + osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); + trans->setDataVariance(osg::Object::STATIC); + trans->addChild(chunkBorder); + return trans; + } + unsigned int getNodeMask() { return mNodeMask; } +private: + Resource::SceneManager* mSceneManager; + Storage* mStorage; + unsigned int mNodeMask; +}; + QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) @@ -258,6 +279,12 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManager->setCompositeMapLevel(compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); mChunkManagers.push_back(mChunkManager.get()); + + if (mDebugTerrainChunks) + { + mDebugChunkManager = std::unique_ptr(new DebugChunkManager(mResourceSystem->getSceneManager(), mStorage, borderMask)); + addChunkManager(mDebugChunkManager.get()); + } } QuadTreeWorld::~QuadTreeWorld() @@ -352,7 +379,7 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f } } -void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld, bool debugTerrainChunk) +void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) { if (!(cv->getTraversalMask() & callback->getCullMask())) return; @@ -368,11 +395,7 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - osg::BoundingBox bb; - if(debugTerrainChunk) - bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)->asGroup()->getChild(0))->getWaterBoundingBox(); - else - bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); + osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); if (!bb.valid()) continue; osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); @@ -448,7 +471,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) } if (mHeightCullCallback && isCullVisitor) - updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty(), mDebugTerrainChunks); + updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); vd->markUnchanged(); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index f7cbf8097a..cedb0ae9e4 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -5,6 +5,7 @@ #include "terraingrid.hpp" #include +#include namespace osg { @@ -15,6 +16,7 @@ namespace Terrain { class RootNode; class ViewDataMap; + class DebugChunkManager; /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) @@ -73,6 +75,7 @@ namespace Terrain float mViewDistance; float mMinSize; bool mDebugTerrainChunks; + std::unique_ptr mDebugChunkManager; }; } From 01cc61087b2f8cb23d73699bb76e796a749e63ab Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 27 Sep 2021 19:32:18 +0000 Subject: [PATCH 1362/2859] improves paging preloader (#3126) * Return check for distance when we try to reuse data * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * cellpreloader.cpp * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * quadtreeworld.cpp * chunkmanager.cpp * chunkmanager.cpp * cellpreloader.cpp * jvoisin * whitespace * whitespace --- apps/openmw/mwworld/cellpreloader.cpp | 43 ++------- components/terrain/chunkmanager.cpp | 116 +++++++++++++++++-------- components/terrain/chunkmanager.hpp | 3 +- components/terrain/quadtreeworld.cpp | 54 +++++++----- components/terrain/quadtreeworld.hpp | 1 - components/terrain/terraindrawable.cpp | 4 +- components/terrain/terraindrawable.hpp | 2 + components/terrain/viewdata.cpp | 27 ++---- components/terrain/viewdata.hpp | 1 - components/terrain/world.hpp | 4 - 10 files changed, 133 insertions(+), 122 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index f2f2ebc5a6..590e1e9c00 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -25,7 +25,7 @@ namespace { template bool contains(const std::vector& container, - const Contained& contained, float tolerance=1.f) + const Contained& contained, float tolerance) { for (const auto& pos : contained) { @@ -180,14 +180,6 @@ namespace MWWorld { } - bool storeViews(double referenceTime) - { - for (unsigned int i=0; istoreView(mTerrainViews[i], referenceTime)) - return false; - return true; - } - void doWork() override { for (unsigned int i=0; iisDone()) { - if (!mTerrainPreloadItem->storeViews(timestamp)) - { - if (++mStoreViewsFailCount > 100) - { - OSG_ALWAYS << "paging views are rebuilt every frame, please check for faulty enable/disable scripts." << std::endl; - mStoreViewsFailCount = 0; - } - setTerrainPreloadPositions(std::vector()); - } - else - { - mStoreViewsFailCount = 0; - mLoadedTerrainPositions = mTerrainPreloadPositions; - mLoadedTerrainTimestamp = timestamp; - } - mTerrainPreloadItem = nullptr; + mLoadedTerrainPositions = mTerrainPreloadPositions; + mLoadedTerrainTimestamp = timestamp; } } @@ -443,17 +420,7 @@ namespace MWWorld return true; else if (mTerrainPreloadItem->isDone()) { - if (mTerrainPreloadItem->storeViews(timestamp)) - { - mTerrainPreloadItem = nullptr; - return true; - } - else - { - setTerrainPreloadPositions(std::vector()); - setTerrainPreloadPositions(positions); - return false; - } + return true; } else { @@ -481,7 +448,7 @@ namespace MWWorld mTerrainPreloadPositions.clear(); mLoadedTerrainPositions.clear(); } - else if (contains(mTerrainPreloadPositions, positions)) + else if (contains(mTerrainPreloadPositions, positions, 128.f)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 476805aa3c..0c84ddd9b9 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -39,6 +39,17 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } +struct FindChunkTemplate +{ + void operator() (ChunkId id, osg::Object* obj) + { + if (std::get<0>(id) == std::get<0>(mId) && std::get<1>(id) == std::get<1>(mId)) + mFoundTemplate = obj; + } + ChunkId mId; + osg::ref_ptr mFoundTemplate; +}; + osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { ChunkId id = std::make_tuple(center, lod, lodFlags); @@ -47,7 +58,11 @@ osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& cen return static_cast(obj.get()); else { - osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile); + FindChunkTemplate find; + find.mId = id; + mCache->call(find); + TerrainDrawable* templateGeometry = (find.mFoundTemplate && !mDebugChunks) ? static_cast(find.mFoundTemplate.get()) : nullptr; + osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); mCache->addEntryToObjectCache(id, node.get()); return node; } @@ -165,24 +180,45 @@ std::vector > ChunkManager::createPasses(float chunk return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); } -osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile) +osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry) { - osg::ref_ptr positions (new osg::Vec3Array); - osg::ref_ptr normals (new osg::Vec3Array); - osg::ref_ptr colors (new osg::Vec4ubArray); - colors->setNormalize(true); - - osg::ref_ptr vbo (new osg::VertexBufferObject); - positions->setVertexBufferObject(vbo); - normals->setVertexBufferObject(vbo); - colors->setVertexBufferObject(vbo); - - mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors); - osg::ref_ptr geometry (new TerrainDrawable); - geometry->setVertexArray(positions); - geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); - geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + + if (!templateGeometry) + { + osg::ref_ptr positions (new osg::Vec3Array); + osg::ref_ptr normals (new osg::Vec3Array); + osg::ref_ptr colors (new osg::Vec4ubArray); + colors->setNormalize(true); + + mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors); + + osg::ref_ptr vbo (new osg::VertexBufferObject); + positions->setVertexBufferObject(vbo); + normals->setVertexBufferObject(vbo); + colors->setVertexBufferObject(vbo); + + geometry->setVertexArray(positions); + geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + } + else + { + // Unfortunately we need to copy vertex data because of poor coupling with VertexBufferObject. + osg::ref_ptr positions = static_cast(templateGeometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); + osg::ref_ptr normals = static_cast(templateGeometry->getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); + osg::ref_ptr colors = static_cast(templateGeometry->getColorArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); + + osg::ref_ptr vbo (new osg::VertexBufferObject); + positions->setVertexBufferObject(vbo); + normals->setVertexBufferObject(vbo); + colors->setVertexBufferObject(vbo); + + geometry->setVertexArray(positions); + geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + } + geometry->setUseDisplayList(false); geometry->setUseVertexBufferObjects(true); @@ -202,32 +238,44 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->setStateSet(mMultiPassRoot); - if (useCompositeMap) + if (templateGeometry) { - osg::ref_ptr compositeMap = new CompositeMap; - compositeMap->mTexture = createCompositeMapRTT(); + if (templateGeometry->getCompositeMap()) + { + geometry->setCompositeMap(templateGeometry->getCompositeMap()); + geometry->setCompositeMapRenderer(mCompositeMapRenderer); + } + geometry->setPasses(templateGeometry->getPasses()); + } + else + { + if (useCompositeMap) + { + osg::ref_ptr compositeMap = new CompositeMap; + compositeMap->mTexture = createCompositeMapRTT(); - createCompositeMapGeometry(chunkSize, chunkCenter, osg::Vec4f(0,0,1,1), *compositeMap); + createCompositeMapGeometry(chunkSize, chunkCenter, osg::Vec4f(0,0,1,1), *compositeMap); - mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false); + mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false); - geometry->setCompositeMap(compositeMap); - geometry->setCompositeMapRenderer(mCompositeMapRenderer); + geometry->setCompositeMap(compositeMap); + geometry->setCompositeMapRenderer(mCompositeMapRenderer); - TextureLayer layer; - layer.mDiffuseMap = compositeMap->mTexture; - layer.mParallax = false; - layer.mSpecular = false; - geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); - } - else - { - geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); + TextureLayer layer; + layer.mDiffuseMap = compositeMap->mTexture; + layer.mParallax = false; + layer.mSpecular = false; + geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); + } + else + { + geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); + } } geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts); - if (compile && mSceneManager->getIncrementalCompileOperation()) + if (!templateGeometry && compile && mSceneManager->getIncrementalCompileOperation()) { mSceneManager->getIncrementalCompileOperation()->add(geometry); } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 9b7dbf3ee1..9b85e81330 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -26,6 +26,7 @@ namespace Terrain class CompositeMapRenderer; class Storage; class CompositeMap; + class TerrainDrawable; typedef std::tuple ChunkId; // Center, Lod, Lod Flags @@ -51,7 +52,7 @@ namespace Terrain void releaseGLObjects(osg::State* state) override; private: - osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool compile); + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry); osg::ref_ptr createCompositeMapRTT(); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index ef37b63ef6..24f042b941 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -55,11 +55,12 @@ namespace Terrain class DefaultLodCallback : public LodCallback { public: - DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid) + DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid, float distanceModifier=0.f) : mFactor(factor) , mMinSize(minSize) , mViewDistance(viewDistance) , mActiveGrid(grid) + , mDistanceModifier(distanceModifier) { } @@ -78,6 +79,8 @@ public: return Deeper; } + dist = std::max(0.f, dist + mDistanceModifier); + if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded return StopTraversal; @@ -92,6 +95,7 @@ private: float mMinSize; float mViewDistance; osg::Vec4i mActiveGrid; + float mDistanceModifier; }; class RootNode : public QuadTreeNode @@ -370,7 +374,7 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f for (QuadTreeWorld::ChunkManager* m : chunkManagers) { - if (m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance*10) + if (m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance) continue; osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); if (n) pat->addChild(n); @@ -519,39 +523,43 @@ View* QuadTreeWorld::createView() void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, Loading::Reporter& reporter) { ensureQuadTreeBuilt(); + const float cellWorldSize = mStorage->getCellWorldSize(); ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); - DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); - mRootNode->traverseNodes(vd, viewPoint, &lodCallback); - - std::size_t progressTotal = 0; - for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) - progressTotal += vd->getEntry(i).mNode->getSize(); - - reporter.addTotal(progressTotal); - const float cellWorldSize = mStorage->getCellWorldSize(); - for (unsigned int i=0; igetNumEntries() && !abort; ++i) + for (unsigned int pass=0; pass<3; ++pass) { - ViewData::Entry& entry = vd->getEntry(i); - - + unsigned int startEntry = vd->getNumEntries(); - loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true, mViewDataMap->getReuseDistance()); - reporter.addProgress(entry.mNode->getSize()); + float distanceModifier=0.f; + if (pass == 1) + distanceModifier = 1024; + else if (pass == 2) + distanceModifier = -1024; + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, distanceModifier); + mRootNode->traverseNodes(vd, viewPoint, &lodCallback); + if (pass==0) + { + std::size_t progressTotal = 0; + for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) + progressTotal += vd->getEntry(i).mNode->getSize(); + reporter.addTotal(progressTotal); + } + const float reuseDistance = std::max(mViewDataMap->getReuseDistance(), std::abs(distanceModifier)); + for (unsigned int i=startEntry; igetNumEntries() && !abort; ++i) + { + ViewData::Entry& entry = vd->getEntry(i); + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true, reuseDistance); + if (pass==0) reporter.addProgress(entry.mNode->getSize()); + entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass + } } - vd->markUnchanged(); -} - -bool QuadTreeWorld::storeView(const View* view, double referenceTime) -{ - return mViewDataMap->storeView(static_cast(view), referenceTime); } void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index cedb0ae9e4..3aabc7233e 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -40,7 +40,6 @@ namespace Terrain View* createView() override; void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) override; - bool storeView(const View* view, double referenceTime) override; void rebuildViews() override; void reportStats(unsigned int frameNumber, osg::Stats* stats) override; diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index 746534abb4..231b6f4fed 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -94,10 +94,10 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) return; } - if (mCompositeMap) + if (mCompositeMap && mCompositeMapRenderer) { mCompositeMapRenderer->setImmediate(mCompositeMap); - mCompositeMap = nullptr; + mCompositeMapRenderer = nullptr; } bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); diff --git a/components/terrain/terraindrawable.hpp b/components/terrain/terraindrawable.hpp index dbfdd3c80a..721abe7481 100644 --- a/components/terrain/terraindrawable.hpp +++ b/components/terrain/terraindrawable.hpp @@ -45,6 +45,7 @@ namespace Terrain typedef std::vector > PassVector; void setPasses (const PassVector& passes); + const PassVector& getPasses() const { return mPasses; } void setLightListCallback(SceneUtil::LightListCallback* lightListCallback); @@ -56,6 +57,7 @@ namespace Terrain const osg::BoundingBox& getWaterBoundingBox() const { return mWaterBoundingBox; } void setCompositeMap(CompositeMap* map) { mCompositeMap = map; } + CompositeMap* getCompositeMap() { return mCompositeMap; } void setCompositeMapRenderer(CompositeMapRenderer* renderer) { mCompositeMapRenderer = renderer; } private: diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index e4d043ffc4..c5070fdf0d 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -143,7 +143,7 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - float shortestDist = std::numeric_limits::max(); + float shortestDist = mReuseDistance*mReuseDistance; const ViewData* mostSuitableView = nullptr; for (const ViewData* other : mUsedViews) { @@ -157,31 +157,22 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } } } - if (mostSuitableView && mostSuitableView != vd) + if (mostSuitableView) { vd->copyFrom(*mostSuitableView); return vd; } - } - if (!vd->suitableToUse(activeGrid)) - { - vd->setViewPoint(viewPoint); - vd->setActiveGrid(activeGrid); - needsUpdate = true; + else + { + vd->setViewPoint(viewPoint); + vd->setActiveGrid(activeGrid); + vd->setWorldUpdateRevision(mWorldUpdateRevision); + needsUpdate = true; + } } return vd; } -bool ViewDataMap::storeView(const ViewData* view, double referenceTime) -{ - if (view->getWorldUpdateRevision() < mWorldUpdateRevision) - return false; - ViewData* store = createOrReuseView(); - store->copyFrom(*view); - store->setLastUsageTimeStamp(referenceTime); - return true; -} - ViewData *ViewDataMap::createOrReuseView() { ViewData* vd = nullptr; diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 378d07663c..5d814251ea 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -93,7 +93,6 @@ namespace Terrain void clearUnusedViews(double referenceTime); void rebuildViews(); - bool storeView(const ViewData* view, double referenceTime); float getReuseDistance() const { return mReuseDistance; } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index b62a1cb568..8d60e4c5b6 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -155,10 +155,6 @@ namespace Terrain virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) {} - /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. - /// @note Not thread safe. - virtual bool storeView(const View* view, double referenceTime) {return true;} - virtual void rebuildViews() {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} From 9fabf9925057071502d7054e2bd8264d53fcd8af Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 27 Sep 2021 21:38:12 +0200 Subject: [PATCH 1363/2859] remove mDebugChunks from chunkManager --- components/terrain/chunkmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 0c84ddd9b9..e040cbdd93 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -61,7 +61,7 @@ osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& cen FindChunkTemplate find; find.mId = id; mCache->call(find); - TerrainDrawable* templateGeometry = (find.mFoundTemplate && !mDebugChunks) ? static_cast(find.mFoundTemplate.get()) : nullptr; + TerrainDrawable* templateGeometry = find.mFoundTemplate ? static_cast(find.mFoundTemplate.get()) : nullptr; osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); mCache->addEntryToObjectCache(id, node.get()); return node; From e760a6c7e64dd9a4692a957aa5663ac21b58864b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 27 Sep 2021 19:34:26 +0000 Subject: [PATCH 1364/2859] Merge branch 'forlua' into 'master' Simple Physics API modification for Lua See merge request OpenMW/openmw!1216 (cherry picked from commit d88494c90b501d0832ae0330a0ca81d8b8e5aa50) 02dd055a Save hitObject in castSphere() just like in castRay() 0793d0bf Allow to override collision mask and group for castSphere() as for castRay() --- apps/openmw/mwphysics/collisiontype.hpp | 3 ++- apps/openmw/mwphysics/physicssystem.cpp | 8 +++++--- apps/openmw/mwphysics/physicssystem.hpp | 5 +++-- apps/openmw/mwphysics/raycasting.hpp | 5 +++-- apps/openmw/mwrender/camera.cpp | 5 +++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwphysics/collisiontype.hpp b/apps/openmw/mwphysics/collisiontype.hpp index 0d6a32fc09..e69534cf68 100644 --- a/apps/openmw/mwphysics/collisiontype.hpp +++ b/apps/openmw/mwphysics/collisiontype.hpp @@ -10,7 +10,8 @@ enum CollisionType { CollisionType_Actor = 1<<2, CollisionType_HeightMap = 1<<3, CollisionType_Projectile = 1<<4, - CollisionType_Water = 1<<5 + CollisionType_Water = 1<<5, + CollisionType_Default = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5b0a180354..8499dc74c1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -348,11 +348,11 @@ namespace MWPhysics return result; } - RayCastingResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius) const + RayCastingResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius, int mask, int group) const { btCollisionWorld::ClosestConvexResultCallback callback(Misc::Convert::toBullet(from), Misc::Convert::toBullet(to)); - callback.m_collisionFilterGroup = 0xff; - callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; + callback.m_collisionFilterGroup = group; + callback.m_collisionFilterMask = mask; btSphereShape shape(radius); const btQuaternion btrot = btQuaternion::getIdentity(); @@ -368,6 +368,8 @@ namespace MWPhysics { result.mHitPos = Misc::Convert::toOsg(callback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(callback.m_hitNormalWorld); + if (auto* ptrHolder = static_cast(callback.m_hitCollisionObject->getUserPointer())) + result.mHitObject = ptrHolder->getPtr(); } return result; } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 5ee99d2d15..52515b563f 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -186,9 +186,10 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), const std::vector& targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; + int mask = CollisionType_Default, int group=0xff) const override; - RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; + RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, + int mask = CollisionType_Default, int group=0xff) const override; /// Return true if actor1 can see actor2. bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const override; diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index e13e745fec..d00f23e2c4 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -29,9 +29,10 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), const std::vector& targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; + int mask = CollisionType_Default, int group=0xff) const = 0; - virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; + virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, + int mask = CollisionType_Default, int group=0xff) const = 0; /// Return true if actor1 can see actor2. virtual bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const = 0; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 3e5d1d0b31..edb017c2a2 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -251,6 +251,7 @@ namespace MWRender const float cameraObstacleLimit = 5.0f; const float focalObstacleLimit = 10.f; + const int collisionType = (MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor); const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); @@ -260,7 +261,7 @@ namespace MWRender float offsetLen = focalOffset.length(); if (offsetLen > 0) { - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, collisionType); if (result.mHit) { double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; @@ -274,7 +275,7 @@ namespace MWRender mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance); osg::Vec3d cameraPos; getPosition(focal, cameraPos); - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit, collisionType); if (result.mHit) mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); } From 77a23dab099ba69ae0c208e179a981ec4c6d4fcf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 27 Sep 2021 22:23:00 +0200 Subject: [PATCH 1365/2859] Only add enabled objects to the scene --- CHANGELOG.md | 1 + apps/openmw/mwworld/worldimp.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d98a802a4f..8dc9510f6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Bug #6302: Teleporting disabled actor breaks its disabled state Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5e93aadf4d..da5daed295 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1159,7 +1159,8 @@ namespace MWWorld if (!currCellActive && newCellActive) { newPtr = currCell->moveTo(ptr, newCell); - mWorldScene->addObjectToScene(newPtr); + if(newPtr.getRefData().isEnabled()) + mWorldScene->addObjectToScene(newPtr); std::string script = newPtr.getClass().getScript(newPtr); if (!script.empty()) From d854e247b82373524a50f17c916546d8c127dd15 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 28 Sep 2021 06:25:12 +0000 Subject: [PATCH 1366/2859] reword warning in settings-default.cfg (#3124) * reword warning in settings-default.cfg The warning at the head of settings-default.cfg is somewhat confusing for users, overly verbose and lacking the most crucial piece of information in its first sentence. Hence, it is unsurprising some users fail to heed the warning, as observed through user support requests on Matrix. * settings-default.cfg --- files/settings-default.cfg | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 08039f5fbe..5ed8109465 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1,15 +1,7 @@ -# WARNING: If this file is named settings-default.cfg or was generated -# from defaults.bin, then editing this file might have no effect, as -# these settings may be overwritten by your user settings.cfg file -# (see documentation for its location). +# WARNING: Users should NOT edit this file. Users should add their personal preferences to the settings.cfg file overriding this file. +# For the location of the settings.cfg file, as well as more detailed settings documentation, refer to: # -# This file provides minimal documentation for each setting, and -# ranges of recommended values. For detailed explanations of the -# significance of each setting, interaction with other settings, hard -# limits on value ranges and more information in general, please read -# the detailed documentation at: -# -# https://openmw.readthedocs.io/en/master/reference/modding/settings/index.html +# https://openmw.readthedocs.io/en/latest/reference/modding/settings/index.html # [Camera] From 5fde6867a2b72d90bdfb5cb888c10cf6be79bc60 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 28 Sep 2021 07:07:49 +0000 Subject: [PATCH 1367/2859] removes unused code (#3129) * scenemanager.cpp * scenemanager.hpp * scenemanager.cpp * stats.cpp * renderingmanager.cpp --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- components/resource/scenemanager.cpp | 31 +---------------------- components/resource/scenemanager.hpp | 10 -------- components/resource/stats.cpp | 1 - 4 files changed, 2 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 19a887fe2e..74e183693c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -262,7 +262,7 @@ namespace MWRender try { for (std::vector::const_iterator it = mModels.begin(); it != mModels.end(); ++it) - mResourceSystem->getSceneManager()->cacheInstance(*it); + mResourceSystem->getSceneManager()->getTemplate(*it); for (std::vector::const_iterator it = mTextures.begin(); it != mTextures.end(); ++it) mResourceSystem->getImageManager()->getImage(*it); for (std::vector::const_iterator it = mKeyframes.begin(); it != mKeyframes.end(); ++it) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index cc38144794..68596985ce 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -35,7 +35,6 @@ #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" -#include "multiobjectcache.hpp" namespace { @@ -306,7 +305,6 @@ namespace Resource , mLightingMethod(SceneUtil::LightingMethod::FFP) , mConvertAlphaTestToAlphaToCoverage(false) , mDepthFormat(0) - , mInstanceCache(new MultiObjectCache) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -682,21 +680,6 @@ namespace Resource } } - osg::ref_ptr SceneManager::cacheInstance(const std::string &name) - { - const std::string normalized = mVFS->normalizeFilename(name); - - osg::ref_ptr node = createInstance(normalized); - - // Note: osg::clone() does not calculate bound volumes. - // Do it immediately, otherwise we will need to update them for all objects - // during first update traversal, what may lead to stuttering during cell transitions - node->getBound(); - - mInstanceCache->addEntryToObjectCache(normalized, node.get()); - return node; - } - osg::ref_ptr SceneManager::createInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); @@ -722,14 +705,7 @@ namespace Resource osg::ref_ptr SceneManager::getInstance(const std::string &name) { - const std::string normalized = mVFS->normalizeFilename(name); - - osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); - if (obj.get()) - return static_cast(obj.get()); - - return createInstance(normalized); - + return createInstance(name); } osg::ref_ptr SceneManager::getInstance(const std::string &name, osg::Group* parentNode) @@ -747,7 +723,6 @@ namespace Resource void SceneManager::releaseGLObjects(osg::State *state) { mCache->releaseGLObjects(state); - mInstanceCache->releaseGLObjects(state); mShaderManager->releaseGLObjects(state); @@ -835,8 +810,6 @@ namespace Resource { ResourceManager::updateCache(referenceTime); - mInstanceCache->removeUnreferencedObjectsInCache(); - mSharedStateMutex.lock(); mSharedStateManager->prune(); mSharedStateMutex.unlock(); @@ -866,7 +839,6 @@ namespace Resource std::lock_guard lock(mSharedStateMutex); mSharedStateManager->clearCache(); - mInstanceCache->clear(); } void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const @@ -884,7 +856,6 @@ namespace Resource } stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); - stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); } Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 9d798177a1..7434a25309 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -65,8 +65,6 @@ namespace Resource std::vector> mObjects; }; - class MultiObjectCache; - /// @brief Handles loading and caching of scenes, e.g. .nif files or .osg files /// @note Some methods of the scene manager can be used from any thread, see the methods documentation for more details. class SceneManager : public ResourceManager @@ -129,12 +127,6 @@ namespace Resource /// @note Thread safe. osg::ref_ptr getTemplate(const std::string& name, bool compile=true); - /// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply - /// return this cached object instead of creating a new one. - /// @note The returned ref_ptr may be kept around by the caller to ensure that the object stays in cache for as long as needed. - /// @note Thread safe. - osg::ref_ptr cacheInstance(const std::string& name); - osg::ref_ptr createInstance(const std::string& name); osg::ref_ptr createInstance(const osg::Node* base); @@ -207,8 +199,6 @@ namespace Resource bool mConvertAlphaTestToAlphaToCoverage; GLenum mDepthFormat; - osg::ref_ptr mInstanceCache; - osg::ref_ptr mSharedStateManager; mutable std::mutex mSharedStateMutex; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 9881d0458b..b3705f69cc 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -376,7 +376,6 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Texture", "StateSet", "Node", - "Node Instance", "Shape", "Shape Instance", "Image", From fd251dfe55611ee11d3d082822eaa70527ca3e45 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 28 Sep 2021 09:19:48 +0200 Subject: [PATCH 1368/2859] remove unused member variable --- apps/openmw/mwworld/cellpreloader.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 39436dc5ad..0c3bcb4f99 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -94,7 +94,6 @@ namespace MWWorld bool mPreloadInstances; double mLastResourceCacheUpdate; - int mStoreViewsFailCount; struct PreloadEntry { From 0bd1c22e241f78d529019558bb544eb5deaf576b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 28 Aug 2021 10:47:57 +0200 Subject: [PATCH 1369/2859] Raycasting in Lua --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 32 +------- apps/openmw/mwlua/luabindings.hpp | 4 +- apps/openmw/mwlua/nearbybindings.cpp | 108 +++++++++++++++++++++++++++ files/lua_api/openmw/nearby.lua | 42 +++++++++++ 5 files changed, 155 insertions(+), 33 deletions(-) create mode 100644 apps/openmw/mwlua/nearbybindings.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5605ff229e..82a91699a9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings - camerabindings uibindings inputbindings + camerabindings uibindings inputbindings nearbybindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index e37241d692..6526a18367 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 5; + api["API_REVISION"] = 6; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); @@ -110,36 +110,6 @@ namespace MWLua return LuaUtil::makeReadOnly(api); } - sol::table initNearbyPackage(const Context& context) - { - sol::table api(context.mLua->sol(), sol::create); - WorldView* worldView = context.mWorldView; - api["activators"] = LObjectList{worldView->getActivatorsInScene()}; - api["actors"] = LObjectList{worldView->getActorsInScene()}; - api["containers"] = LObjectList{worldView->getContainersInScene()}; - api["doors"] = LObjectList{worldView->getDoorsInScene()}; - api["items"] = LObjectList{worldView->getItemsInScene()}; - api["selectObjects"] = [context](const Queries::Query& query) - { - ObjectIdList list; - WorldView* worldView = context.mWorldView; - if (query.mQueryType == "activators") - list = worldView->getActivatorsInScene(); - else if (query.mQueryType == "actors") - list = worldView->getActorsInScene(); - else if (query.mQueryType == "containers") - list = worldView->getContainersInScene(); - else if (query.mQueryType == "doors") - list = worldView->getDoorsInScene(); - else if (query.mQueryType == "items") - list = worldView->getItemsInScene(); - return LObjectList{selectObjectsFromList(query, list, context)}; - // TODO: Maybe use sqlite - // return LObjectList{worldView->selectObjects(query, true)}; - }; - return LuaUtil::makeReadOnly(api); - } - sol::table initQueryPackage(const Context& context) { Queries::registerQueryBindings(context.mLua->sol()); diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index c58b556a3d..d34ff3d727 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -21,11 +21,13 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); - sol::table initNearbyPackage(const Context&); sol::table initQueryPackage(const Context&); sol::table initFieldGroup(const Context&, const QueryFieldGroup&); + // Implemented in nearbybindings.cpp + sol::table initNearbyPackage(const Context&); + // Implemented in objectbindings.cpp void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&); diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp new file mode 100644 index 0000000000..16e55bd6b1 --- /dev/null +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -0,0 +1,108 @@ +#include "luabindings.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwphysics/raycasting.hpp" + +#include "worldview.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + sol::table initNearbyPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + WorldView* worldView = context.mWorldView; + + sol::usertype rayResult = + context.mLua->sol().new_usertype("RayCastingResult"); + rayResult["hit"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) { return r.mHit; }); + rayResult["hitPos"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional + { + if (r.mHit) + return r.mHitPos; + else + return sol::nullopt; + }); + rayResult["hitNormal"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional + { + if (r.mHit) + return r.mHitNormal; + else + return sol::nullopt; + }); + rayResult["hitObject"] = sol::readonly_property([worldView](const MWPhysics::RayCastingResult& r) -> sol::optional + { + if (r.mHitObject.isEmpty()) + return sol::nullopt; + else + return LObject(getId(r.mHitObject), worldView->getObjectRegistry()); + }); + + constexpr int defaultCollisionType = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | + MWPhysics::CollisionType_Actor | MWPhysics::CollisionType_Door; + api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( + "World", MWPhysics::CollisionType_World, + "Door", MWPhysics::CollisionType_Door, + "Actor", MWPhysics::CollisionType_Actor, + "HeightMap", MWPhysics::CollisionType_HeightMap, + "Projectile", MWPhysics::CollisionType_Projectile, + "Water", MWPhysics::CollisionType_Water, + "Default", defaultCollisionType)); + + api["castRay"] = [defaultCollisionType](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) + { + MWWorld::Ptr ignore; + int collisionType = defaultCollisionType; + float radius = 0; + if (options) + { + sol::optional ignoreObj = options->get>("ignore"); + if (ignoreObj) ignore = ignoreObj->ptr(); + collisionType = options->get>("collisionType").value_or(collisionType); + radius = options->get>("radius").value_or(0); + } + const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + if (radius <= 0) + return rayCasting->castRay(from, to, ignore, std::vector(), collisionType); + else + { + if (!ignore.isEmpty()) throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + return rayCasting->castSphere(from, to, radius, collisionType); + } + }; + + api["activators"] = LObjectList{worldView->getActivatorsInScene()}; + api["actors"] = LObjectList{worldView->getActorsInScene()}; + api["containers"] = LObjectList{worldView->getContainersInScene()}; + api["doors"] = LObjectList{worldView->getDoorsInScene()}; + api["items"] = LObjectList{worldView->getItemsInScene()}; + api["selectObjects"] = [context](const Queries::Query& query) + { + ObjectIdList list; + WorldView* worldView = context.mWorldView; + if (query.mQueryType == "activators") + list = worldView->getActivatorsInScene(); + else if (query.mQueryType == "actors") + list = worldView->getActorsInScene(); + else if (query.mQueryType == "containers") + list = worldView->getContainersInScene(); + else if (query.mQueryType == "doors") + list = worldView->getDoorsInScene(); + else if (query.mQueryType == "items") + list = worldView->getItemsInScene(); + return LObjectList{selectObjectsFromList(query, list, context)}; + // TODO: Maybe use sqlite + // return LObjectList{worldView->selectObjects(query, true)}; + }; + return LuaUtil::makeReadOnly(api); + } +} diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index c24d5e3d10..f1aeb07b24 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -32,5 +32,47 @@ -- @param openmw.query#Query query -- @return openmw.core#ObjectList +------------------------------------------------------------------------------- +-- @type COLLISION_TYPE +-- @field [parent=#COLLISION_TYPE] #number World +-- @field [parent=#COLLISION_TYPE] #number Door +-- @field [parent=#COLLISION_TYPE] #number Actor +-- @field [parent=#COLLISION_TYPE] #number HeightMap +-- @field [parent=#COLLISION_TYPE] #number Projectile +-- @field [parent=#COLLISION_TYPE] #number Water +-- @field [parent=#COLLISION_TYPE] #number Default Used by deafult: World+Door+Actor+HeightMap + +------------------------------------------------------------------------------- +-- Collision types that are used in `castRay`. +-- Several types can be combined with '+'. +-- @field [parent=#nearby] #COLLISION_TYPE COLLISION_TYPE + +------------------------------------------------------------------------------- +-- Result of raycasing +-- @type RayCastingResult +-- @field [parent=#RayCastingResult] #boolean hit Is there a collision? (true/false) +-- @field [parent=#RayCastingResult] openmw.util#Vector3 hitPos Position of the collision point (nil if no collision) +-- @field [parent=#RayCastingResult] openmw.util#Vector3 hitNormal Normal to the surface in the collision point (nil if no collision) +-- @field [parent=#RayCastingResult] openmw.core#GameObject hitObject The object the ray has collided with (can be nil) + +------------------------------------------------------------------------------- +-- Cast ray from one point to another and return the first collision. +-- @function [parent=#nearby] castRay +-- @param openmw.util#Vector3 from Start point of the ray. +-- @param openmw.util#Vector3 to End point of the ray. +-- @param #table options An optional table with additional optional arguments. Can contain: +-- `ignore` - an object to ignore (specify here the source of the ray); +-- `collisionType` - object types to work with (see @{openmw.nearby#COLLISION_TYPE}), several types can be combined with '+'; +-- `radius` - the radius of the ray (zero by default). If not zero then castRay actually casts a sphere with given radius. +-- NOTE: currently `ignore` is not supported if `radius>0`. +-- @return #RayCastingResult +-- @usage if nearby.castRay(pointA, pointB).hit then print('obstacle between A and B') end +-- @usage local res = nearby.castRay(self.position, enemy.position, {ignore=self}) +-- if res.hitObject and res.hitObject ~= enemy then obstacle = res.hitObject end +-- @usage local res = nearby.castRay(self.position, targetPos, { +-- collisionType=nearby.COLLISION_TYPE.HeightMap + nearby.COLLISION_TYPE.Water, +-- radius = 10, +-- }) + return nil From fb3917fc1ae00e2deb3dd5a3c85c6ed22c344172 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 27 Aug 2021 09:26:38 +0200 Subject: [PATCH 1370/2859] Lua callbacks --- apps/openmw/mwlua/asyncbindings.cpp | 9 +++++++- apps/openmw/mwlua/luabindings.hpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 15 +++++++++++++ apps/openmw/mwlua/luamanagerimp.hpp | 32 ++++++++++++++++++++++++++++ apps/openmw/mwlua/nearbybindings.cpp | 14 ++++++++++++ components/lua/scriptscontainer.cpp | 12 +++++++++++ components/lua/scriptscontainer.hpp | 4 +++- files/lua_api/openmw/async.lua | 7 ++++++ 8 files changed, 92 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/asyncbindings.cpp b/apps/openmw/mwlua/asyncbindings.cpp index fee6788b89..9fdda53d9d 100644 --- a/apps/openmw/mwlua/asyncbindings.cpp +++ b/apps/openmw/mwlua/asyncbindings.cpp @@ -1,5 +1,7 @@ #include "luabindings.hpp" +#include "luamanagerimp.hpp" + namespace sol { template <> @@ -48,11 +50,16 @@ namespace MWLua asyncId.mContainer->setupUnsavableTimer( TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback)); }; + api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) + { + return Callback{std::move(fn), asyncId.mHiddenData}; + }; auto initializer = [](sol::table hiddenData) { LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY]; - return AsyncPackageId{id.mContainer, id.mPath}; + hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString(); + return AsyncPackageId{id.mContainer, id.mPath, hiddenData}; }; return sol::make_object(context.mLua->sol(), initializer); } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index d34ff3d727..d1c62e43e3 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -47,9 +47,9 @@ namespace MWLua // Implemented in asyncbindings.cpp struct AsyncPackageId { - // TODO: add ObjectId mLocalObject; LuaUtil::ScriptsContainer* mContainer; std::string mScript; + sol::table mHiddenData; }; sol::function getAsyncPackageInitializer(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 1e009081ff..38055c99b7 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -74,6 +74,16 @@ namespace MWLua mInitialized = true; } + void Callback::operator()(sol::object arg) const + { + if (mHiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY] != sol::nil) + LuaUtil::call(mFunc, std::move(arg)); + else + { + Log(Debug::Debug) << "Ignored callback to removed script " << mHiddenData.get(SCRIPT_NAME_KEY); + } + } + void LuaManager::update(bool paused, float dt) { ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); @@ -126,6 +136,11 @@ namespace MWLua << ". Object not found or has no attached scripts"; } + // Run queued callbacks + for (CallbackWithData& c : mQueuedCallbacks) + c.mCallback(c.mArg); + mQueuedCallbacks.clear(); + // Engine handlers in local scripts PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 8fdfb02701..91f48171f3 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -19,6 +19,19 @@ namespace MWLua { + // Wrapper for a single-argument Lua function. + // Holds information about the script the function belongs to. + // Needed to prevent callback calls if the script was removed. + struct Callback + { + static constexpr std::string_view SCRIPT_NAME_KEY = "name"; + + sol::function mFunc; + sol::table mHiddenData; + + void operator()(sol::object arg) const; + }; + class LuaManager : public MWBase::LuaManager { public: @@ -67,6 +80,18 @@ namespace MWLua // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. void reloadAllScripts() override; + // Used to call Lua callbacks from C++ + void queueCallback(Callback callback, sol::object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); } + + // Wraps Lua callback into an std::function. + // NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or + // any other Lua-related function is running. + template + std::function wrapLuaCallback(const Callback& c) + { + return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); }; + } + private: LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr); @@ -100,6 +125,13 @@ namespace MWLua std::vector mInputEvents; std::vector mActorAddedEvents; + struct CallbackWithData + { + Callback mCallback; + sol::object mArg; + }; + std::vector mQueuedCallbacks; + struct LocalEngineEvent { ObjectId mDest; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 16e55bd6b1..8eae46a9b7 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -79,6 +79,20 @@ namespace MWLua return rayCasting->castSphere(from, to, radius, collisionType); } }; + // TODO: async raycasting + /*api["asyncCastRay"] = [luaManager = context.mLuaManager, defaultCollisionType]( + const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) + { + std::function callback = + luaManager->wrapLuaCallback(luaCallback); + MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + + // Handle options the same way as in `castRay`. + + // NOTE: `callback` is not thread safe. If MWPhysics works in separate thread, it must put results to a queue + // and use this callback from the main thread at the beginning of the next frame processing. + rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); + };*/ api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 6e0a19b120..b6882b0988 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -16,6 +16,15 @@ namespace LuaUtil static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers"; static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers"; + std::string ScriptsContainer::ScriptId::toString() const + { + std::string res = mContainer->mNamePrefix; + res.push_back('['); + res.append(mPath); + res.push_back(']'); + return res; + } + ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua) { registerEngineHandlers({&mUpdateHandlers}); @@ -81,6 +90,7 @@ namespace LuaUtil auto scriptIter = mScripts.find(path); if (scriptIter == mScripts.end()) return false; // no such script + scriptIter->second.mHiddenData[ScriptId::KEY] = sol::nil; sol::object& script = scriptIter->second.mInterface; if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil) { @@ -320,6 +330,8 @@ namespace LuaUtil void ScriptsContainer::removeAllScripts() { + for (auto& [_, script] : mScripts) + script.mHiddenData[ScriptId::KEY] = sol::nil; mScripts.clear(); mScriptOrder.clear(); for (auto& [_, handlers] : mEngineHandlers) diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index d3141a2ca3..0bf50b8793 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -66,6 +66,8 @@ namespace LuaUtil ScriptsContainer* mContainer; std::string mPath; + + std::string toString() const; }; using TimeUnit = ESM::LuaTimer::TimeUnit; @@ -73,7 +75,7 @@ namespace LuaUtil ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix); ScriptsContainer(const ScriptsContainer&) = delete; ScriptsContainer(ScriptsContainer&&) = delete; - virtual ~ScriptsContainer() {} + virtual ~ScriptsContainer() { removeAllScripts(); } // Adds package that will be available (via `require`) for all scripts in the container. // Automatically applies LuaUtil::makeReadOnly to the package. diff --git a/files/lua_api/openmw/async.lua b/files/lua_api/openmw/async.lua index 68f63b5193..cc3a233f8a 100644 --- a/files/lua_api/openmw/async.lua +++ b/files/lua_api/openmw/async.lua @@ -48,5 +48,12 @@ -- @param #number delay -- @param #function func +------------------------------------------------------------------------------- +-- Wraps Lua function with `Callback` object that can be used in async API calls. +-- @function [parent=#async] callback +-- @param self +-- @param #function func +-- @return #Callback + return nil From e41fe7573a90ac649ec6d4c8775c39956103d3da Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 28 Sep 2021 08:17:12 +0000 Subject: [PATCH 1371/2859] avoids creating empty statesets for collada nodes (#3128) * avoids creating empty statesets for collada nodes With this PR we avoid creating empty statesets for collada nodes which will be detrimental to osg's draw performance. * scenemanager.cpp --- components/resource/scenemanager.cpp | 50 ++++++++++++++++------------ 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 68596985ce..2099d514f7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -243,19 +243,22 @@ namespace Resource void apply(osg::Node& node) override { - if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) + if (osg::StateSet* stateset = node.getStateSet()) { - osg::ref_ptr depth = SceneUtil::createDepth(); - depth->setWriteMask(false); + if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + depth->setWriteMask(false); - node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - } - else if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::OPAQUE_BIN) - { - osg::ref_ptr depth = SceneUtil::createDepth(); - depth->setWriteMask(true); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + else if (stateset->getRenderingHint() == osg::StateSet::OPAQUE_BIN) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + depth->setWriteMask(true); - node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } } /* Check if the has @@ -268,22 +271,25 @@ namespace Resource } // Iterate each description, and see if the current node uses the specified material for alpha testing - for (auto description : mDescriptions) + if (node.getStateSet()) { - std::vector descriptionParts; - std::istringstream descriptionStringStream(description); - for (std::string part; std::getline(descriptionStringStream, part, ' ');) + for (auto description : mDescriptions) { - descriptionParts.emplace_back(part); - } + std::vector descriptionParts; + std::istringstream descriptionStringStream(description); + for (std::string part; std::getline(descriptionStringStream, part, ' ');) + { + descriptionParts.emplace_back(part); + } - if (descriptionParts.size() > (3) && descriptionParts.at(3) == node.getOrCreateStateSet()->getName()) - { - if (descriptionParts.at(0) == "alphatest") + if (descriptionParts.size() > (3) && descriptionParts.at(3) == node.getStateSet()->getName()) { - osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); - osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); - node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + if (descriptionParts.at(0) == "alphatest") + { + osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); + osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); + node.getStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } } } } From eb2f863b7d6ff5e1340e31ed6430693ce3d939db Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 28 Sep 2021 12:25:37 +0200 Subject: [PATCH 1372/2859] Resolve `unused-lambda-capture` warnings --- apps/openmw/mwlua/actions.cpp | 4 ++-- apps/openmw/mwlua/nearbybindings.cpp | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 1f75760f7c..aad70d1a57 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -51,8 +51,8 @@ namespace MWLua std::array usedSlots; std::fill(usedSlots.begin(), usedSlots.end(), false); - constexpr int anySlot = -1; - auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView, anySlot](int slot, const Item& item) -> bool + static constexpr int anySlot = -1; + auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool { auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); MWWorld::Ptr itemPtr; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 8eae46a9b7..011d0ae9f3 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -47,8 +47,6 @@ namespace MWLua return LObject(getId(r.mHitObject), worldView->getObjectRegistry()); }); - constexpr int defaultCollisionType = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | - MWPhysics::CollisionType_Actor | MWPhysics::CollisionType_Door; api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "World", MWPhysics::CollisionType_World, "Door", MWPhysics::CollisionType_Door, @@ -56,12 +54,12 @@ namespace MWLua "HeightMap", MWPhysics::CollisionType_HeightMap, "Projectile", MWPhysics::CollisionType_Projectile, "Water", MWPhysics::CollisionType_Water, - "Default", defaultCollisionType)); + "Default", MWPhysics::CollisionType_Default)); - api["castRay"] = [defaultCollisionType](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) + api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { MWWorld::Ptr ignore; - int collisionType = defaultCollisionType; + int collisionType = MWPhysics::CollisionType_Default; float radius = 0; if (options) { @@ -80,7 +78,7 @@ namespace MWLua } }; // TODO: async raycasting - /*api["asyncCastRay"] = [luaManager = context.mLuaManager, defaultCollisionType]( + /*api["asyncCastRay"] = [luaManager = context.mLuaManager]( const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { std::function callback = From 48538d5ceff0ca4d809e4606d2cecc94903a1dcf Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 19 Sep 2021 14:43:15 +0200 Subject: [PATCH 1373/2859] 3D transforms in Lua --- apps/openmw/mwlua/luabindings.cpp | 2 +- .../lua/test_utilpackage.cpp | 121 ++++++++---- components/lua/utilpackage.cpp | 174 +++++++++++++----- components/lua/utilpackage.hpp | 15 +- files/lua_api/openmw/util.lua | 87 ++++++++- 5 files changed, 309 insertions(+), 90 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 6526a18367..aceffc24db 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 6; + api["API_REVISION"] = 7; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index fb8e48e461..934cbf761a 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -10,30 +10,41 @@ namespace { using namespace testing; + template + T get(sol::state& lua, std::string luaCode) + { + return lua.safe_script("return " + luaCode).get(); + } + + std::string getAsString(sol::state& lua, std::string luaCode) + { + return LuaUtil::toString(lua.safe_script("return " + luaCode)); + } + TEST(LuaUtilPackageTest, Vector2) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector2(3, 4)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), 3); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 4); - EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(3, 4)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v:length()").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return v:length2()").get(), 25); - EXPECT_FALSE(lua.safe_script("return util.vector2(1, 2) == util.vector2(1, 3)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) == util.vector2(2, 4) / 2").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) * 2 == util.vector2(2, 4)").get()); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2) * v").get(), 17); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2):dot(v)").get(), 17); + EXPECT_FLOAT_EQ(get(lua, "v.x"), 3); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 4); + EXPECT_EQ(get(lua, "tostring(v)"), "(3, 4)"); + EXPECT_FLOAT_EQ(get(lua, "v:length()"), 5); + EXPECT_FLOAT_EQ(get(lua, "v:length2()"), 25); + EXPECT_FALSE(get(lua, "util.vector2(1, 2) == util.vector2(1, 3)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) == util.vector2(2, 4) / 2")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) * 2 == util.vector2(2, 4)")); + EXPECT_FLOAT_EQ(get(lua, "util.vector2(3, 2) * v"), 17); + EXPECT_FLOAT_EQ(get(lua, "util.vector2(3, 2):dot(v)"), 17); EXPECT_ERROR(lua.safe_script("v2, len = v.normalize()"), "value is not a valid userdata"); // checks that it doesn't segfault lua.safe_script("v2, len = v:normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 5); - EXPECT_TRUE(lua.safe_script("return v2 == util.vector2(3/5, 4/5)").get()); + EXPECT_FLOAT_EQ(get(lua, "len"), 5); + EXPECT_TRUE(get(lua, "v2 == util.vector2(3/5, 4/5)")); lua.safe_script("_, len = util.vector2(0, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 0); + EXPECT_FLOAT_EQ(get(lua, "len"), 0); } TEST(LuaUtilPackageTest, Vector3) @@ -42,27 +53,59 @@ namespace lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector3(5, 12, 13)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 12); - EXPECT_FLOAT_EQ(lua.safe_script("return v.z").get(), 13); - EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(5, 12, 13)"); - EXPECT_EQ(LuaUtil::toString(lua.safe_script("return v")), "(5, 12, 13)"); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length()").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length2()").get(), 25); - EXPECT_FALSE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(1, 3, 2)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)").get()); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1) * v").get(), 5*3 + 12*2 + 13*1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1):dot(v)").get(), 5*3 + 12*2 + 13*1); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)").get()); + EXPECT_FLOAT_EQ(get(lua, "v.x"), 5); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 12); + EXPECT_FLOAT_EQ(get(lua, "v.z"), 13); + EXPECT_EQ(get(lua, "tostring(v)"), "(5, 12, 13)"); + EXPECT_EQ(getAsString(lua, "v"), "(5, 12, 13)"); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(4, 0, 3):length()"), 5); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(4, 0, 3):length2()"), 25); + EXPECT_FALSE(get(lua, "util.vector3(1, 2, 3) == util.vector3(1, 3, 2)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)")); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(3, 2, 1) * v"), 5*3 + 12*2 + 13*1); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(3, 2, 1):dot(v)"), 5*3 + 12*2 + 13*1); + EXPECT_TRUE(get(lua, "util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)")); EXPECT_ERROR(lua.safe_script("v2, len = util.vector3(3, 4, 0).normalize()"), "value is not a valid userdata"); lua.safe_script("v2, len = util.vector3(3, 4, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 5); - EXPECT_TRUE(lua.safe_script("return v2 == util.vector3(3/5, 4/5, 0)").get()); + EXPECT_FLOAT_EQ(get(lua, "len"), 5); + EXPECT_TRUE(get(lua, "v2 == util.vector3(3/5, 4/5, 0)")); lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 0); + EXPECT_FLOAT_EQ(get(lua, "len"), 0); + } + + TEST(LuaUtilPackageTest, Transform) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua["T"] = lua["util"]["transform"]; + lua["v"] = lua["util"]["vector3"]; + EXPECT_ERROR(lua.safe_script("T.identity = nil"), "attempt to index"); + EXPECT_EQ(getAsString(lua, "T.identity * v(3, 4, 5)"), "(3, 4, 5)"); + EXPECT_EQ(getAsString(lua, "T.move(1, 2, 3) * v(3, 4, 5)"), "(4, 6, 8)"); + EXPECT_EQ(getAsString(lua, "T.scale(1, -2, 3) * v(3, 4, 5)"), "(3, -8, 15)"); + EXPECT_EQ(getAsString(lua, "T.scale(v(1, 2, 3)) * v(3, 4, 5)"), "(3, 8, 15)"); + lua.safe_script("moveAndScale = T.move(v(1, 2, 3)) * T.scale(0.5, 1, 0.5) * T.move(10, 20, 30)"); + EXPECT_EQ(getAsString(lua, "moveAndScale * v(0, 0, 0)"), "(6, 22, 18)"); + EXPECT_EQ(getAsString(lua, "moveAndScale * v(300, 200, 100)"), "(156, 222, 68)"); + EXPECT_EQ(getAsString(lua, "moveAndScale"), "TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) }"); + EXPECT_EQ(getAsString(lua, "T.identity"), "TransformM{ }"); + lua.safe_script("rx = T.rotateX(math.pi / 2)"); + lua.safe_script("ry = T.rotateY(math.pi / 2)"); + lua.safe_script("rz = T.rotateZ(math.pi / 2)"); + EXPECT_LT(get(lua, "(rx * v(1, 2, 3) - v(1, -3, 2)):length()"), 1e-6); + EXPECT_LT(get(lua, "(ry * v(1, 2, 3) - v(3, 2, -1)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rz * v(1, 2, 3) - v(-2, 1, 3)):length()"), 1e-6); + lua.safe_script("rot = T.rotate(math.pi / 2, v(-1, -1, 0)) * T.rotateZ(-math.pi / 4)"); + EXPECT_THAT(getAsString(lua, "rot"), HasSubstr("TransformQ")); + EXPECT_LT(get(lua, "(rot * v(1, 0, 0) - v(0, 0, 1)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rot * rot:inverse() * v(1, 0, 0) - v(1, 0, 0)):length()"), 1e-6); + lua.safe_script("rz_move_rx = rz * T.move(0, 3, 0) * rx"); + EXPECT_LT(get(lua, "(rz_move_rx * v(1, 2, 3) - v(0, 1, 2)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rz_move_rx:inverse() * v(0, 1, 2) - v(1, 2, 3)):length()"), 1e-6); } TEST(LuaUtilPackageTest, UtilityFunctions) @@ -71,12 +114,12 @@ namespace lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), -0.5); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 0.86602539); - EXPECT_FLOAT_EQ(lua.safe_script("return util.normalizeAngle(math.pi * 10 + 0.1)").get(), 0.1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(0.1, 0, 1.5)").get(), 0.1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(-0.1, 0, 1.5)").get(), 0); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(2.1, 0, 1.5)").get(), 1.5); + EXPECT_FLOAT_EQ(get(lua, "v.x"), -0.5); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 0.86602539); + EXPECT_FLOAT_EQ(get(lua, "util.normalizeAngle(math.pi * 10 + 0.1)"), 0.1); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(0.1, 0, 1.5)"), 0.1); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(-0.1, 0, 1.5)"), 0); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(2.1, 0, 1.5)"), 1.5); } } diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index abcc6d424e..17cb64461a 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -3,17 +3,23 @@ #include #include -#include - #include +#include "luastate.hpp" + namespace sol { template <> - struct is_automagical : std::false_type {}; + struct is_automagical : std::false_type {}; template <> - struct is_automagical : std::false_type {}; + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; } namespace LuaUtil @@ -23,70 +29,148 @@ namespace LuaUtil { sol::table util(lua, sol::create); - // TODO: Add bindings for osg::Matrix - - // Lua bindings for osg::Vec2f - util["vector2"] = [](float x, float y) { return osg::Vec2f(x, y); }; - sol::usertype vec2Type = lua.new_usertype("Vec2"); - vec2Type["x"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.x(); } ); - vec2Type["y"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.y(); } ); - vec2Type[sol::meta_function::to_string] = [](const osg::Vec2f& v) { + // Lua bindings for Vec2 + util["vector2"] = [](float x, float y) { return Vec2(x, y); }; + sol::usertype vec2Type = lua.new_usertype("Vec2"); + vec2Type["x"] = sol::readonly_property([](const Vec2& v) -> float { return v.x(); } ); + vec2Type["y"] = sol::readonly_property([](const Vec2& v) -> float { return v.y(); } ); + vec2Type[sol::meta_function::to_string] = [](const Vec2& v) { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ")"; return ss.str(); }; - vec2Type[sol::meta_function::unary_minus] = [](const osg::Vec2f& a) { return -a; }; - vec2Type[sol::meta_function::addition] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a + b; }; - vec2Type[sol::meta_function::subtraction] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a - b; }; - vec2Type[sol::meta_function::equal_to] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a == b; }; + vec2Type[sol::meta_function::unary_minus] = [](const Vec2& a) { return -a; }; + vec2Type[sol::meta_function::addition] = [](const Vec2& a, const Vec2& b) { return a + b; }; + vec2Type[sol::meta_function::subtraction] = [](const Vec2& a, const Vec2& b) { return a - b; }; + vec2Type[sol::meta_function::equal_to] = [](const Vec2& a, const Vec2& b) { return a == b; }; vec2Type[sol::meta_function::multiplication] = sol::overload( - [](const osg::Vec2f& a, float c) { return a * c; }, - [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; }); - vec2Type[sol::meta_function::division] = [](const osg::Vec2f& a, float c) { return a / c; }; - vec2Type["dot"] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; }; - vec2Type["length"] = &osg::Vec2f::length; - vec2Type["length2"] = &osg::Vec2f::length2; - vec2Type["normalize"] = [](const osg::Vec2f& v) { + [](const Vec2& a, float c) { return a * c; }, + [](const Vec2& a, const Vec2& b) { return a * b; }); + vec2Type[sol::meta_function::division] = [](const Vec2& a, float c) { return a / c; }; + vec2Type["dot"] = [](const Vec2& a, const Vec2& b) { return a * b; }; + vec2Type["length"] = &Vec2::length; + vec2Type["length2"] = &Vec2::length2; + vec2Type["normalize"] = [](const Vec2& v) { float len = v.length(); if (len == 0) - return std::make_tuple(osg::Vec2f(), 0.f); + return std::make_tuple(Vec2(), 0.f); else return std::make_tuple(v * (1.f / len), len); }; vec2Type["rotate"] = &Misc::rotateVec2f; - // Lua bindings for osg::Vec3f - util["vector3"] = [](float x, float y, float z) { return osg::Vec3f(x, y, z); }; - sol::usertype vec3Type = lua.new_usertype("Vec3"); - vec3Type["x"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.x(); } ); - vec3Type["y"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.y(); } ); - vec3Type["z"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.z(); } ); - vec3Type[sol::meta_function::to_string] = [](const osg::Vec3f& v) { + // Lua bindings for Vec3 + util["vector3"] = [](float x, float y, float z) { return Vec3(x, y, z); }; + sol::usertype vec3Type = lua.new_usertype("Vec3"); + vec3Type["x"] = sol::readonly_property([](const Vec3& v) -> float { return v.x(); } ); + vec3Type["y"] = sol::readonly_property([](const Vec3& v) -> float { return v.y(); } ); + vec3Type["z"] = sol::readonly_property([](const Vec3& v) -> float { return v.z(); } ); + vec3Type[sol::meta_function::to_string] = [](const Vec3& v) { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; return ss.str(); }; - vec3Type[sol::meta_function::unary_minus] = [](const osg::Vec3f& a) { return -a; }; - vec3Type[sol::meta_function::addition] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a + b; }; - vec3Type[sol::meta_function::subtraction] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a - b; }; - vec3Type[sol::meta_function::equal_to] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a == b; }; + vec3Type[sol::meta_function::unary_minus] = [](const Vec3& a) { return -a; }; + vec3Type[sol::meta_function::addition] = [](const Vec3& a, const Vec3& b) { return a + b; }; + vec3Type[sol::meta_function::subtraction] = [](const Vec3& a, const Vec3& b) { return a - b; }; + vec3Type[sol::meta_function::equal_to] = [](const Vec3& a, const Vec3& b) { return a == b; }; vec3Type[sol::meta_function::multiplication] = sol::overload( - [](const osg::Vec3f& a, float c) { return a * c; }, - [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; }); - vec3Type[sol::meta_function::division] = [](const osg::Vec3f& a, float c) { return a / c; }; - vec3Type[sol::meta_function::involution] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; }; - vec3Type["dot"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; }; - vec3Type["cross"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; }; - vec3Type["length"] = &osg::Vec3f::length; - vec3Type["length2"] = &osg::Vec3f::length2; - vec3Type["normalize"] = [](const osg::Vec3f& v) { + [](const Vec3& a, float c) { return a * c; }, + [](const Vec3& a, const Vec3& b) { return a * b; }); + vec3Type[sol::meta_function::division] = [](const Vec3& a, float c) { return a / c; }; + vec3Type[sol::meta_function::involution] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; + vec3Type["dot"] = [](const Vec3& a, const Vec3& b) { return a * b; }; + vec3Type["cross"] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; + vec3Type["length"] = &Vec3::length; + vec3Type["length2"] = &Vec3::length2; + vec3Type["normalize"] = [](const Vec3& v) { float len = v.length(); if (len == 0) - return std::make_tuple(osg::Vec3f(), 0.f); + return std::make_tuple(Vec3(), 0.f); else return std::make_tuple(v * (1.f / len), len); }; + // Lua bindings for Transform + sol::usertype transMType = lua.new_usertype("TransformM"); + sol::usertype transQType = lua.new_usertype("TransformQ"); + sol::table transforms(lua, sol::create); + util["transform"] = LuaUtil::makeReadOnly(transforms); + + transforms["identity"] = sol::make_object(lua, TransformM{osg::Matrixf::identity()}); + transforms["move"] = sol::overload( + [](const Vec3& v) { return TransformM{osg::Matrixf::translate(v)}; }, + [](float x, float y, float z) { return TransformM{osg::Matrixf::translate(x, y, z)}; }); + transforms["scale"] = sol::overload( + [](const Vec3& v) { return TransformM{osg::Matrixf::scale(v)}; }, + [](float x, float y, float z) { return TransformM{osg::Matrixf::scale(x, y, z)}; }); + transforms["rotate"] = [](float angle, const Vec3& axis) { return TransformQ{osg::Quat(angle, axis)}; }; + transforms["rotateX"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(1, 0, 0))}; }; + transforms["rotateY"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 1, 0))}; }; + transforms["rotateZ"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 0, 1))}; }; + + transMType[sol::meta_function::multiplication] = sol::overload( + [](const TransformM& a, const Vec3& b) { return a.mM.preMult(b); }, + [](const TransformM& a, const TransformM& b) { return TransformM{b.mM * a.mM}; }, + [](const TransformM& a, const TransformQ& b) + { + TransformM res{a.mM}; + res.mM.preMultRotate(b.mQ); + return res; + }); + transMType[sol::meta_function::to_string] = [](const TransformM& m) + { + osg::Vec3f trans, scale; + osg::Quat rotation, so; + m.mM.decompose(trans, rotation, scale, so); + osg::Quat::value_type rot_angle, so_angle; + osg::Vec3f rot_axis, so_axis; + rotation.getRotate(rot_angle, rot_axis); + so.getRotate(so_angle, so_axis); + std::stringstream ss; + ss << "TransformM{ "; + if (trans.length2() > 0) + ss << "move(" << trans.x() << ", " << trans.y() << ", " << trans.z() << ") "; + if (rot_angle != 0) + ss << "rotation(angle=" << rot_angle << ", axis=(" + << rot_axis.x() << ", " << rot_axis.y() << ", " << rot_axis.z() << ")) "; + if (scale.x() != 1 || scale.y() != 1 || scale.z() != 1) + ss << "scale(" << scale.x() << ", " << scale.y() << ", " << scale.z() << ") "; + if (so_angle != 0) + ss << "rotation(angle=" << so_angle << ", axis=(" + << so_axis.x() << ", " << so_axis.y() << ", " << so_axis.z() << ")) "; + ss << "}"; + return ss.str(); + }; + transMType["inverse"] = [](const TransformM& m) + { + TransformM res; + if (!res.mM.invert_4x3(m.mM)) + throw std::runtime_error("This Transform is not invertible"); + return res; + }; + + transQType[sol::meta_function::multiplication] = sol::overload( + [](const TransformQ& a, const Vec3& b) { return a.mQ * b; }, + [](const TransformQ& a, const TransformQ& b) { return TransformQ{b.mQ * a.mQ}; }, + [](const TransformQ& a, const TransformM& b) + { + TransformM res{b}; + res.mM.postMultRotate(a.mQ); + return res; + }); + transQType[sol::meta_function::to_string] = [](const TransformQ& q) + { + osg::Quat::value_type angle; + osg::Vec3f axis; + q.mQ.getRotate(angle, axis); + std::stringstream ss; + ss << "TransformQ{ rotation(angle=" << angle << ", axis=(" + << axis.x() << ", " << axis.y() << ", " << axis.z() << ")) }"; + return ss.str(); + }; + transQType["inverse"] = [](const TransformQ& q) { return TransformQ{q.mQ.inverse()}; }; + // Utility functions util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); }; // NOTE: `util["clamp"] = std::clamp` causes error 'AddressSanitizer: stack-use-after-scope' diff --git a/components/lua/utilpackage.hpp b/components/lua/utilpackage.hpp index 9e73723561..d26bfdb027 100644 --- a/components/lua/utilpackage.hpp +++ b/components/lua/utilpackage.hpp @@ -1,11 +1,24 @@ #ifndef COMPONENTS_LUA_UTILPACKAGE_H #define COMPONENTS_LUA_UTILPACKAGE_H -#include // missing from sol/sol.hpp +#include +#include +#include + #include namespace LuaUtil { + using Vec2 = osg::Vec2f; + using Vec3 = osg::Vec3f; + + // For performance reasons "Transform" is implemented as 2 types with the same interface. + // Transform supports only composition, inversion, and applying to a 3d vector. + struct TransformM { osg::Matrixf mM; }; + struct TransformQ { osg::Quat mQ; }; + + inline TransformM asTransform(const osg::Matrixf& m) { return {m}; } + inline TransformQ asTransform(const osg::Quat& q) { return {q}; } sol::table initUtilPackage(sol::state&); diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index 7d31ced408..84e640ff40 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -6,7 +6,7 @@ ------------------------------------------------------------------------------- --- Limits given value to the interval [`from`, `to`] +-- Limits given value to the interval [`from`, `to`]. -- @function [parent=#util] clamp -- @param #number value -- @param #number from @@ -14,7 +14,7 @@ -- @return #number min(max(value, from), to) ------------------------------------------------------------------------------- --- Adds `2pi*k` and puts the angle in range `[-pi, pi]` +-- Adds `2pi*k` and puts the angle in range `[-pi, pi]`. -- @function [parent=#util] normalizeAngle -- @param #number angle Angle in radians -- @return #number Angle in range `[-pi, pi]` @@ -48,13 +48,13 @@ -- @return #Vector2. ------------------------------------------------------------------------------- --- Length of the vector +-- Length of the vector. -- @function [parent=#Vector2] length -- @param self -- @return #number ------------------------------------------------------------------------------- --- Square of the length of the vector +-- Square of the length of the vector. -- @function [parent=#Vector2] length2 -- @param self -- @return #number @@ -146,5 +146,84 @@ -- @param #Vector3 v -- @return #Vector3 +------------------------------------------------------------------------------- +-- @type Transform + +------------------------------------------------------------------------------- +-- Returns the inverse transform. +-- @function [parent=#Transform] inverse +-- @param self +-- @return #Transform. + +------------------------------------------------------------------------------- +-- @type TRANSFORM +-- @field [parent=#TRANSFORM] #Transform identity Empty transform. + +------------------------------------------------------------------------------- +-- Movement by given vector. +-- @function [parent=#TRANSFORM] move +-- @param #Vector3 offset. +-- @return #Transform. +-- @usage +-- -- Accepts either 3 numbers or a 3D vector +-- util.transform.move(x, y, z) +-- util.transform.move(util.vector3(x, y, z)) + +------------------------------------------------------------------------------- +-- Scale transform. +-- @function [parent=#TRANSFORM] scale +-- @param #number scaleX. +-- @param #number scaleY. +-- @param #number scaleZ. +-- @return #Transform. +-- @usage +-- -- Accepts either 3 numbers or a 3D vector +-- util.transform.scale(x, y, z) +-- util.transform.scale(util.vector3(x, y, z)) + + +------------------------------------------------------------------------------- +-- Rotation (any axis). +-- @function [parent=#TRANSFORM] rotate +-- @param #number angle +-- @param #Vector3 axis. +-- @return #Transform. + +------------------------------------------------------------------------------- +-- X-axis rotation. +-- @function [parent=#TRANSFORM] rotateX +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- Y-axis rotation. +-- @function [parent=#TRANSFORM] rotateY +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- Z-axis rotation. +-- @function [parent=#TRANSFORM] rotateZ +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- 3D transforms (scale/move/rotate) that can be applied to 3D vectors. +-- Several transforms can be combined and applied to a vector using multiplication. +-- Combined transforms apply in reverse order (from right to left). +-- @field [parent=#util] #TRANSFORM transform +-- @usage +-- local util = require('openmw.util') +-- local trans = util.transform +-- local fromActorSpace = trans.move(actor.position) * trans.rotateZ(actor.rotation.z) +-- +-- -- rotation is applied first, movement is second +-- local posBehindActor = fromActorSpace * util.vector3(0, -100, 0) +-- +-- -- equivalent to trans.rotateZ(-actor.rotation.z) * trans.move(-actor.position) +-- local toActorSpace = fromActorSpace:inverse() +-- local relativeTargetPos = toActorSpace * target.position +-- local deltaAngle = math.atan2(relativeTargetPos.y, relativeTargetPos.x) + return nil From 163968f578e3a764596cc90b8ad99f18f9bcdd3e Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 28 Sep 2021 22:42:44 +0200 Subject: [PATCH 1374/2859] Normalize area cost factor Recastnavigation uses path cost and heuristic based on distance to find shortest path by A* algorithm. Using raw speed values makes cost much lower value than heuristic (1000 times less at maximum). Heuristic is defined as distance from node to target * 0.999 that means area cost should never be less than 0.999 or 1 for simplicity. Use max speed to make lowest possible cost factor equal to 1. --- apps/openmw/mwmechanics/aipackage.cpp | 33 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 46e4729a8a..c358a75b55 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -457,20 +457,31 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); - if (flags & DetourNavigator::Flag_swim) - costs.mWater = divOrMax(costs.mWater, actorClass.getSwimSpeed(actor)); + const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0 + ? 0.0f + : actorClass.getSwimSpeed(actor); - if (flags & DetourNavigator::Flag_walk) + const float walkSpeed = [&] { - float walkCost; + if ((flags & DetourNavigator::Flag_walk) == 0) + return 0.0f; if (getTypeId() == AiPackageTypeId::Wander) - walkCost = divOrMax(1.0, actorClass.getWalkSpeed(actor)); - else - walkCost = divOrMax(1.0, actorClass.getRunSpeed(actor)); - costs.mDoor = costs.mDoor * walkCost; - costs.mPathgrid = costs.mPathgrid * walkCost; - costs.mGround = costs.mGround * walkCost; - } + return actorClass.getWalkSpeed(actor); + return actorClass.getRunSpeed(actor); + } (); + + const float maxSpeed = std::max(swimSpeed, walkSpeed); + + if (maxSpeed == 0) + return costs; + + const float swimFactor = swimSpeed / maxSpeed; + const float walkFactor = walkSpeed / maxSpeed; + + costs.mWater = divOrMax(costs.mWater, swimFactor); + costs.mDoor = divOrMax(costs.mDoor, walkFactor); + costs.mPathgrid = divOrMax(costs.mPathgrid, walkFactor); + costs.mGround = divOrMax(costs.mGround, walkFactor); return costs; } From 9950132a5f3dd750fb357665834820212fae91a1 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 28 Sep 2021 23:16:56 +0200 Subject: [PATCH 1375/2859] Allow travelling actors find path over pathgrid navmesh area type In addition to other navmesh areas. This makes actors behaviour closer to vanilla when pathgrid is correct. --- apps/openmw/mwmechanics/aipackage.cpp | 4 ++++ apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index c358a75b55..3e37a4612d 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -443,7 +443,11 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0) + { result |= DetourNavigator::Flag_walk; + if (getTypeId() == AiPackageTypeId::Travel) + result |= DetourNavigator::Flag_usePathgrid; + } if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander) result |= DetourNavigator::Flag_openDoor; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 7e63190edb..4b06993a49 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -397,7 +397,7 @@ namespace MWMechanics mPath.clear(); } - if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty()) + if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty() && (flags & DetourNavigator::Flag_usePathgrid) == 0) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, std::back_inserter(mPath)); From c0ef4417c3bf42fea006efbb4655015953bc4319 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 29 Sep 2021 00:42:29 +0200 Subject: [PATCH 1376/2859] Check AiTravel destination for other actors presence Use faster aabbTest but without destance filter. To avoid dependency on a specific constant and correctly handle situations when there is a big difference between actors sizes. --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 3 +- apps/openmw/mwmechanics/aitravel.cpp | 31 ++++++++++++++----- apps/openmw/mwmechanics/aitravel.hpp | 2 ++ apps/openmw/mwmechanics/aiwander.cpp | 8 ----- apps/openmw/mwmechanics/obstacle.cpp | 11 +++++++ apps/openmw/mwmechanics/obstacle.hpp | 6 ++++ .../mwphysics/hasspherecollisioncallback.hpp | 21 +++++++++---- apps/openmw/mwphysics/physicssystem.cpp | 17 ++++++++-- apps/openmw/mwphysics/physicssystem.hpp | 3 +- apps/openmw/mwworld/worldimp.cpp | 5 +-- apps/openmw/mwworld/worldimp.hpp | 3 +- 12 files changed, 82 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc9510f6e..5bd70b9407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6302: Teleporting disabled actor breaks its disabled state + Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1dbb2b96f6..c8036385db 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -653,7 +653,8 @@ namespace MWBase virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; - virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0; + virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors = nullptr) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 91fe96a2aa..2594adcb34 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,5 +1,7 @@ #include "aitravel.hpp" +#include + #include #include "../mwbase/environment.hpp" @@ -23,6 +25,11 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) return (pos1 - pos2).length2() <= 7168*7168; } + float getActorRadius(const MWWorld::ConstPtr& actor) + { + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + return std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + } } namespace MWMechanics @@ -70,16 +77,24 @@ namespace MWMechanics // Unfortunately, with vanilla assets destination is sometimes blocked by other actor. // If we got close to target, check for actors nearby. If they are, finish AI package. - int destinationTolerance = 64; - if (distance(actorPos, targetPos) <= destinationTolerance) + if (mDestinationCheck.update(duration) == Misc::TimerStatus::Elapsed) { - std::vector targetActors; - std::pair result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors); - - if (!result.first.isEmpty()) + std::vector occupyingActors; + if (isAreaOccupiedByOtherActor(actor, targetPos, &occupyingActors)) { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - return true; + const float actorRadius = getActorRadius(actor); + const float distanceToTarget = distance(actorPos, targetPos); + for (const MWWorld::Ptr& other : occupyingActors) + { + const float otherRadius = getActorRadius(other); + const auto [minRadius, maxRadius] = std::minmax(actorRadius, otherRadius); + constexpr float toleranceFactor = 1.25; + if (minRadius * toleranceFactor + maxRadius > distanceToTarget) + { + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + return true; + } + } } } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 2ea2a8f717..ee4ff9ad4d 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -52,6 +52,8 @@ namespace MWMechanics const float mZ; const bool mHidden; + + AiReactionTimer mDestinationCheck; }; struct AiInternalTravel final : public AiTravel diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 624abe10f9..9c84555404 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -85,14 +85,6 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor); } - bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) - { - const auto world = MWBase::Environment::get().getWorld(); - const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); - const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); - return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor); - } - void stopMovement(const MWWorld::Ptr& actor) { actor.getClass().getMovementSettings(actor).mPosition[0] = 0; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 88325ee7c7..344cc82058 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -4,6 +4,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "movement.hpp" @@ -72,6 +74,15 @@ namespace MWMechanics return MWWorld::Ptr(); // none found } + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + std::vector* occupyingActors) + { + const auto world = MWBase::Environment::get().getWorld(); + const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); + const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor, occupyingActors); + } + ObstacleCheck::ObstacleCheck() : mWalkState(WalkState::Initial) , mStateDuration(0) diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index b574bab67f..24bd5ed1c1 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -3,9 +3,12 @@ #include +#include + namespace MWWorld { class Ptr; + class ConstPtr; } namespace MWMechanics @@ -21,6 +24,9 @@ namespace MWMechanics /** \return Pointer to the door, or empty pointer if none exists **/ const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + std::vector* occupyingActors = nullptr); + class ObstacleCheck { public: diff --git a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp index 275325cf67..fc8725f5f4 100644 --- a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp +++ b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp @@ -22,28 +22,36 @@ namespace MWPhysics return nearest.distance(position) < radius; } + template class HasSphereCollisionCallback final : public btBroadphaseAabbCallback { public: HasSphereCollisionCallback(const btVector3& position, const btScalar radius, btCollisionObject* object, - const int mask, const int group) + const int mask, const int group, OnCollision* onCollision) : mPosition(position), mRadius(radius), mCollisionObject(object), mCollisionFilterMask(mask), - mCollisionFilterGroup(group) + mCollisionFilterGroup(group), + mOnCollision(onCollision) { } bool process(const btBroadphaseProxy* proxy) override { - if (mResult) + if (mResult && mOnCollision == nullptr) return false; const auto collisionObject = static_cast(proxy->m_clientObject); - if (collisionObject == mCollisionObject) + if (collisionObject == mCollisionObject + || !needsCollision(*proxy) + || !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius)) return true; - if (needsCollision(*proxy)) - mResult = testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius); + mResult = true; + if (mOnCollision != nullptr) + { + (*mOnCollision)(collisionObject); + return true; + } return !mResult; } @@ -58,6 +66,7 @@ namespace MWPhysics btCollisionObject* mCollisionObject; int mCollisionFilterMask; int mCollisionFilterGroup; + OnCollision* mOnCollision; bool mResult = false; bool needsCollision(const btBroadphaseProxy& proxy) const diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8499dc74c1..fb363bdac0 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -923,7 +923,8 @@ namespace MWPhysics CollisionType_Actor|CollisionType_Projectile); } - bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const { btCollisionObject* object = nullptr; const auto it = mActors.find(ignore.mRef); @@ -934,7 +935,19 @@ namespace MWPhysics const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); const int mask = MWPhysics::CollisionType_Actor; const int group = 0xff; - HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group); + if (occupyingActors == nullptr) + { + HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group, + static_cast(nullptr)); + mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); + return callback.getResult(); + } + const auto onCollision = [&] (const btCollisionObject* object) + { + if (PtrHolder* holder = static_cast(object->getUserPointer())) + occupyingActors->push_back(holder->getPtr()); + }; + HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group, &onCollision); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 52515b563f..06cfee8e38 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -252,7 +252,8 @@ namespace MWPhysics std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); } - bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const; + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const; void reportStats(unsigned int frameNumber, osg::Stats& stats) const; void reportCollision(const btVector3& position, const btVector3& normal); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index da5daed295..df7357da27 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3935,9 +3935,10 @@ namespace MWWorld return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal); } - bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const { - return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore); + return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore, occupyingActors); } void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 32fc9b101a..4d9841a79a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -735,7 +735,8 @@ namespace MWWorld bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override; - bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const override; + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; From fc2076db1a3bc57f031f6680a0aad31295530669 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Sep 2021 11:36:05 +0400 Subject: [PATCH 1377/2859] Fix MSVC warnings about local variables redeclaration (#3130) --- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 50 +++++++++---------- apps/openmw/mwrender/renderingmanager.cpp | 1 - apps/openmw/mwworld/scene.cpp | 6 +-- components/detournavigator/navigatorimpl.cpp | 4 +- .../detournavigator/recastmeshbuilder.cpp | 24 ++++----- components/sceneutil/recastmesh.cpp | 6 +-- 7 files changed, 46 insertions(+), 47 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index f4cb40c2f5..542f185854 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1171,7 +1171,7 @@ namespace MWMechanics creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Undead) { - Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); stat.setModifier(static_cast(effects.get(ESM::MagicEffect::TurnUndead).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8499dc74c1..903e383370 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -514,18 +514,18 @@ namespace MWPhysics void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { - if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { if (mUnrefQueue.get()) - mUnrefQueue->push(found->second->getShapeInstance()); + mUnrefQueue->push(foundObject->second->getShapeInstance()); - mAnimatedObjects.erase(found->second.get()); + mAnimatedObjects.erase(foundObject->second.get()); - mObjects.erase(found); + mObjects.erase(foundObject); } - else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { - mActors.erase(found); + mActors.erase(foundActor); } } @@ -591,16 +591,16 @@ namespace MWPhysics void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { - if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { float scale = ptr.getCellRef().getScale(); - found->second->setScale(scale); - mTaskScheduler->updateSingleAabb(found->second); + foundObject->second->setScale(scale); + mTaskScheduler->updateSingleAabb(foundObject->second); } - else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { - found->second->updateScale(); - mTaskScheduler->updateSingleAabb(found->second); + foundActor->second->updateScale(); + mTaskScheduler->updateSingleAabb(foundActor->second); } } @@ -633,32 +633,32 @@ namespace MWPhysics void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { - if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { - found->second->setRotation(rotate); - mTaskScheduler->updateSingleAabb(found->second); + foundObject->second->setRotation(rotate); + mTaskScheduler->updateSingleAabb(foundObject->second); } - else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { - if (!found->second->isRotationallyInvariant()) + if (!foundActor->second->isRotationallyInvariant()) { - found->second->setRotation(rotate); - mTaskScheduler->updateSingleAabb(found->second); + foundActor->second->setRotation(rotate); + mTaskScheduler->updateSingleAabb(foundActor->second); } } } void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr) { - if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { - found->second->updatePosition(); - mTaskScheduler->updateSingleAabb(found->second); + foundObject->second->updatePosition(); + mTaskScheduler->updateSingleAabb(foundObject->second); } - else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { - found->second->updatePosition(); - mTaskScheduler->updateSingleAabb(found->second, true); + foundActor->second->updatePosition(); + mTaskScheduler->updateSingleAabb(foundActor->second, true); } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 74e183693c..5661ecfbe7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -440,7 +440,6 @@ namespace MWRender static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); - float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); mGroundcover->setViewDistance(groundcoverDistance); } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index e52f5532b4..559ab4aef4 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -917,9 +917,9 @@ namespace MWWorld // unload for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell(cell); + auto* cellToUnload = *iter++; + deactivateCell(cellToUnload); + unloadInactiveCell(cellToUnload); } assert(mActiveCells.empty()); assert(mInactiveCells.empty()); diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 5a8488c8d0..750901c73e 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -39,8 +39,8 @@ namespace DetourNavigator if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { const ObjectId avoidId(avoidShape); - CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; - if (mNavMeshManager.addObject(avoidId, collisionShape, transform, AreaType_null)) + CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; + if (mNavMeshManager.addObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 19125e0a9b..73b731c247 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -49,9 +49,9 @@ namespace DetourNavigator std::vector uniqueVertices; uniqueVertices.reserve(3 * triangles.size()); - for (const RecastMeshTriangle& v : triangles) - for (const osg::Vec3f& v : v.mVertices) - uniqueVertices.push_back(v); + for (const RecastMeshTriangle& triangle : triangles) + for (const osg::Vec3f& vertex : triangle.mVertices) + uniqueVertices.push_back(vertex); std::sort(uniqueVertices.begin(), uniqueVertices.end()); uniqueVertices.erase(std::unique(uniqueVertices.begin(), uniqueVertices.end()), uniqueVertices.end()); @@ -61,15 +61,15 @@ namespace DetourNavigator std::vector areaTypes; areaTypes.reserve(triangles.size()); - for (const RecastMeshTriangle& v : triangles) + for (const RecastMeshTriangle& triangle : triangles) { - areaTypes.push_back(v.mAreaType); + areaTypes.push_back(triangle.mAreaType); - for (const osg::Vec3f& v : v.mVertices) + for (const osg::Vec3f& vertex : triangle.mVertices) { - const auto it = std::lower_bound(uniqueVertices.begin(), uniqueVertices.end(), v); + const auto it = std::lower_bound(uniqueVertices.begin(), uniqueVertices.end(), vertex); assert(it != uniqueVertices.end()); - assert(*it == v); + assert(*it == vertex); indices.push_back(static_cast(it - uniqueVertices.begin())); } } @@ -79,11 +79,11 @@ namespace DetourNavigator std::vector vertices; vertices.reserve(3 * uniqueVertices.size()); - for (const osg::Vec3f& v : uniqueVertices) + for (const osg::Vec3f& vertex : uniqueVertices) { - vertices.push_back(v.x() + shift.x()); - vertices.push_back(v.y() + shift.y()); - vertices.push_back(v.z() + shift.z()); + vertices.push_back(vertex.x() + shift.x()); + vertices.push_back(vertex.y() + shift.y()); + vertices.push_back(vertex.z() + shift.z()); } return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes)); diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index 8d6d47c2b6..97efe010df 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -52,10 +52,10 @@ namespace SceneUtil for (const Heightfield& heightfield : recastMesh.getHeightfields()) { - const Mesh mesh = makeMesh(heightfield); + const Mesh heightfieldMesh = makeMesh(heightfield); const int indexShift = static_cast(vertices.size() / 3); - std::copy(mesh.getVertices().begin(), mesh.getVertices().end(), std::back_inserter(vertices)); - std::transform(mesh.getIndices().begin(), mesh.getIndices().end(), std::back_inserter(indices), + std::copy(heightfieldMesh.getVertices().begin(), heightfieldMesh.getVertices().end(), std::back_inserter(vertices)); + std::transform(heightfieldMesh.getIndices().begin(), heightfieldMesh.getIndices().end(), std::back_inserter(indices), [&] (int index) { return index + indexShift; }); } From 957c25a491b86772b02ab9057679ad1fc970f353 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 29 Sep 2021 07:58:19 +0000 Subject: [PATCH 1378/2859] avoids creating empty statesets on drawables (#3132) * avoids creating empty statesets on drawables Currently, we attempt to skip creating state on drawable nodes when this state matches the default state. This attempt is incomplete because we still create an avoidable empty stateset in the default case. * renderingmanager.cpp * nifloader.cpp --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/nifosg/nifloader.cpp | 20 +++++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5661ecfbe7..c749d6aa78 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -486,6 +486,7 @@ namespace MWRender defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); mFog.reset(new FogManager()); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0432aef5b4..f05f906c8f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1134,8 +1134,6 @@ namespace NifOsg trans->addChild(toAttach); parentNode->addChild(trans); } - // create partsys stateset in order to pass in ShaderVisitor like all other Drawables - partsys->getOrCreateStateSet(); } void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) @@ -1923,8 +1921,6 @@ namespace NifOsg void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { - osg::StateSet* stateset = node->getOrCreateStateSet(); - // Specular lighting is enabled by default, but there's a quirk... bool specEnabled = true; osg::ref_ptr mat (new osg::Material); @@ -2006,15 +2002,15 @@ namespace NifOsg if (blendFunc->getDestination() == GL_DST_ALPHA) blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); bool noSort = (alphaprop->flags>>13)&1; if (!noSort) - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); else - stateset->setRenderBinToInherit(); + node->getOrCreateStateSet()->setRenderBinToInherit(); } - else + else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); @@ -2025,9 +2021,9 @@ namespace NifOsg { osg::ref_ptr alphaFunc (new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f)); alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } - else + else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); @@ -2083,8 +2079,10 @@ namespace NifOsg mat = shareAttribute(mat); + osg::StateSet* stateset = node->getStateSet(); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); + if (emissiveMult != 1.f) + stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; From 1109cc3ac7fa77ed1e057d893f9b5e5c4f437e8b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Sep 2021 14:08:51 +0400 Subject: [PATCH 1379/2859] Remove unused data field (#3133) --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/groundcover.hpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 94bd900aea..c7113f5193 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -191,7 +191,7 @@ namespace MWRender if (model.empty()) continue; model = "meshes/" + model; - instances[model].emplace_back(std::move(ref), std::move(model)); + instances[model].emplace_back(std::move(ref)); } } } diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index 0a83976441..f2693d6c24 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -24,10 +24,8 @@ namespace MWRender { ESM::Position mPos; float mScale; - std::string mModel; - GroundcoverEntry(const ESM::CellRef& ref, const std::string& model): - mPos(ref.mPos), mScale(ref.mScale), mModel(model) + GroundcoverEntry(const ESM::CellRef& ref) : mPos(ref.mPos), mScale(ref.mScale) {} }; From 83584185556c7cef7071a65dd9c2dcffda781243 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 29 Sep 2021 13:40:37 +0000 Subject: [PATCH 1380/2859] set the correct program link parameters (#3110) * shadermanager.hpp setProgramTemplate * shadermanager.hpp * shadermanager.cpp setProgramTemplate * shadervisitor.hpp setProgramTemplate * shadervisitor.cpp setProgramTemplate * scenemanager.cpp setProgramTemplate * scenemanager.hpp setProgramTemplate * renderingmanager.cpp * groundcover.cpp setProgramTemplate * groundcover.hpp * groundcover.cpp * shadervisitor.cpp * util.cpp * lightmanager.cpp * scenemanager.cpp * scenemanager.hpp * lightmanager.cpp * lightmanager.cpp * lightmanager.cpp * scenemanager.hpp [ci skip] * water.cpp * groundcover.cpp * shadermanager.hpp --- apps/openmw/mwrender/groundcover.cpp | 7 ++++- apps/openmw/mwrender/groundcover.hpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 1 - apps/openmw/mwrender/water.cpp | 3 --- components/resource/scenemanager.cpp | 11 +++++++- components/resource/scenemanager.hpp | 7 ++++- components/sceneutil/lightmanager.cpp | 13 +++++----- components/sceneutil/util.cpp | 3 +++ components/shader/shadermanager.cpp | 16 +++--------- components/shader/shadermanager.hpp | 31 +++++------------------ components/shader/shadervisitor.cpp | 5 +++- components/shader/shadervisitor.hpp | 5 ++++ 12 files changed, 53 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index c7113f5193..3691c6270b 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwbase/environment.hpp" @@ -150,6 +151,10 @@ namespace MWRender mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); + + mProgramTemplate = mSceneManager->getShaderManager().getProgramTemplate() ? static_cast(mSceneManager->getShaderManager().getProgramTemplate()->clone(osg::CopyOp::SHALLOW_COPY)) : new osg::Program; + mProgramTemplate->addBindAttribLocation("aOffset", 6); + mProgramTemplate->addBindAttribLocation("aRotation", 7); } void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) @@ -219,7 +224,7 @@ namespace MWRender group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->setCullCallback(new SceneUtil::LightListCallback); - mSceneManager->recreateShaders(group, "groundcover", false, true); + mSceneManager->recreateShaders(group, "groundcover", false, true, mProgramTemplate); mSceneManager->shareState(group); group->getBound(); return group; diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index f2693d6c24..ed88f7fe24 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace MWRender { @@ -33,6 +34,7 @@ namespace MWRender Resource::SceneManager* mSceneManager; float mDensity; osg::ref_ptr mStateset; + osg::ref_ptr mProgramTemplate; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c749d6aa78..fad2c25e20 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -325,7 +325,6 @@ namespace MWRender // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this depends on support for various OpenGL extensions. osg::ref_ptr sceneRoot = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); - resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setSupportedLightingMethods(sceneRoot->getSupportedLightingMethods()); mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 7a8677e43a..88e422fcf3 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -670,9 +670,6 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - auto method = mResourceSystem->getSceneManager()->getLightingMethod(); - if (method == SceneUtil::LightingMethod::SingleUBO) - program->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); mRainIntensityUpdater = new RainIntensityUpdater(); node->setUpdateCallback(mRainIntensityUpdater); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 2099d514f7..be32539d40 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -332,10 +333,11 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode, const osg::Program* programTemplate) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); + shaderVisitor->setProgramTemplate(programTemplate); if (forceShadersForNode) shaderVisitor->setForceShaders(true); node->accept(*shaderVisitor); @@ -410,6 +412,13 @@ namespace Resource void SceneManager::setLightingMethod(SceneUtil::LightingMethod method) { mLightingMethod = method; + + if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO) + { + osg::ref_ptr program = new osg::Program; + program->addBindUniformBlock("LightBufferBinding", static_cast(UBOBinding::LightBuffer)); + mShaderManager->setProgramTemplate(program); + } } SceneUtil::LightingMethod SceneManager::getLightingMethod() const diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 7434a25309..1443476fd5 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); /// Applying shaders to a node may replace some fixed-function state. /// This restores it. @@ -111,6 +111,11 @@ namespace Resource void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; + enum class UBOBinding + { + // If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate + LightBuffer + }; void setLightingMethod(SceneUtil::LightingMethod method); SceneUtil::LightingMethod getLightingMethod() const; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 1f7d9d2db2..ba60cb3185 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -294,9 +295,9 @@ namespace SceneUtil osg::ref_ptr ubo = new osg::UniformBufferObject; buffer->getData()->setBufferObject(ubo); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); #else - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), ubo, 0, buffer->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), ubo, 0, buffer->getData()->getTotalDataSize()); #endif stateset->setAttributeAndModes(ubb, mode); @@ -676,9 +677,9 @@ namespace SceneUtil auto bo = mLightManager->getLightBuffer(mLastFrameNumber); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); #else - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData()->getBufferObject(), 0, bo->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), bo->getData()->getBufferObject(), 0, bo->getData()->getTotalDataSize()); #endif stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); } @@ -736,7 +737,7 @@ namespace SceneUtil // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably // available, regardless of extensions, until GLSL 140. mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); - mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); + mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Resource::SceneManager::UBOBinding::LightBuffer)); } LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) @@ -751,7 +752,7 @@ namespace SceneUtil void initSharedLayout(osg::GLExtensions* ext, int handle) const { - constexpr std::array index = { static_cast(Shader::UBOBinding::LightBuffer) }; + constexpr std::array index = { static_cast(Resource::SceneManager::UBOBinding::LightBuffer) }; int totalBlockSize = -1; int stride = -1; diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 007a6637a8..442b164981 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -11,6 +11,9 @@ #include #include #include +#include +#include +#include #include #include diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 33f79415f1..e057cfac02 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -17,7 +16,6 @@ namespace Shader { ShaderManager::ShaderManager() - : mLightingMethod(SceneUtil::LightingMethod::FFP) { } @@ -26,11 +24,6 @@ namespace Shader mPath = path; } - void ShaderManager::setLightingMethod(SceneUtil::LightingMethod method) - { - mLightingMethod = method; - } - bool addLineDirectivesAfterConditionalBlocks(std::string& source) { for (size_t position = 0; position < source.length(); ) @@ -345,19 +338,16 @@ namespace Shader return shaderIt->second; } - osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader) + osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader, const osg::Program* programTemplate) { std::lock_guard lock(mMutex); ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); if (found == mPrograms.end()) { - osg::ref_ptr program (new osg::Program); + if (!programTemplate) programTemplate = mProgramTemplate; + osg::ref_ptr program = programTemplate ? static_cast(programTemplate->clone(osg::CopyOp::SHALLOW_COPY)) : new osg::Program; program->addShader(vertexShader); program->addShader(fragmentShader); - program->addBindAttribLocation("aOffset", 6); - program->addBindAttribLocation("aRotation", 7); - if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO) - program->addBindUniformBlock("LightBufferBinding", static_cast(UBOBinding::LightBuffer)); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 2450f0d6dc..d0ee069b10 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -8,29 +8,11 @@ #include #include - -#include - -#include - -namespace Resource -{ - class SceneManager; -} - -namespace SceneUtil -{ - enum class LightingMethod; -} +#include namespace Shader { - enum class UBOBinding - { - LightBuffer - }; - /// @brief Reads shader template files and turns them into a concrete shader, based on a list of define's. /// @par Shader templates can get the value of a define with the syntax @define. class ShaderManager @@ -41,8 +23,6 @@ namespace Shader void setShaderPath(const std::string& path); - void setLightingMethod(SceneUtil::LightingMethod method); - typedef std::map DefineMap; /// Create or retrieve a shader instance. @@ -53,7 +33,10 @@ namespace Shader /// @note Thread safe. osg::ref_ptr getShader(const std::string& templateName, const DefineMap& defines, osg::Shader::Type shaderType); - osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader, const osg::Program* programTemplate=nullptr); + + const osg::Program* getProgramTemplate() const { return mProgramTemplate; } + void setProgramTemplate(const osg::Program* program) { mProgramTemplate = program; } /// Get (a copy of) the DefineMap used to construct all shaders DefineMap getGlobalDefines(); @@ -81,9 +64,9 @@ namespace Shader typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; - SceneUtil::LightingMethod mLightingMethod; - std::mutex mMutex; + + osg::ref_ptr mProgramTemplate; }; bool parseFors(std::string& source, const std::string& templateName); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index cbd2863e46..0d0710853f 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,5 +1,8 @@ #include "shadervisitor.hpp" +#include +#include + #include #include #include @@ -553,7 +556,7 @@ namespace Shader if (vertexShader && fragmentShader) { - auto program = mShaderManager.getProgram(vertexShader, fragmentShader); + auto program = mShaderManager.getProgram(vertexShader, fragmentShader, mProgramTemplate); writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); addedState->setAttributeAndModes(program); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index a5add473a6..3cd8b5d85a 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_SHADERVISITOR_H #include +#include namespace Resource { @@ -19,6 +20,8 @@ namespace Shader public: ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultShaderPrefix); + void setProgramTemplate(const osg::Program* programTemplate) { mProgramTemplate = programTemplate; } + /// By default, only bump mapped objects will have a shader added to them. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. void setForceShaders(bool force); @@ -109,6 +112,8 @@ namespace Shader void createProgram(const ShaderRequirements& reqs); void ensureFFP(osg::Node& node); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); + + osg::ref_ptr mProgramTemplate; }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor From 803195a05fc87b06385fbf4619faa9de51426c95 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 29 Sep 2021 16:29:10 +0200 Subject: [PATCH 1381/2859] add back some explicit includes --- apps/openmw/mwrender/groundcover.cpp | 2 +- components/shader/shadervisitor.hpp | 2 +- components/terrain/quadtreeworld.cpp | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 3691c6270b..71bddb1f9d 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -151,7 +151,7 @@ namespace MWRender mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); - + mProgramTemplate = mSceneManager->getShaderManager().getProgramTemplate() ? static_cast(mSceneManager->getShaderManager().getProgramTemplate()->clone(osg::CopyOp::SHALLOW_COPY)) : new osg::Program; mProgramTemplate->addBindAttribLocation("aOffset", 6); mProgramTemplate->addBindAttribLocation("aRotation", 7); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 3cd8b5d85a..3380a66cca 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -112,7 +112,7 @@ namespace Shader void createProgram(const ShaderRequirements& reqs); void ensureFFP(osg::Node& node); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); - + osg::ref_ptr mProgramTemplate; }; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 24f042b941..c05a964209 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" From d8707a763fa79dcfd0576b5754bfe31eaf0163c9 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 29 Sep 2021 15:10:58 +0000 Subject: [PATCH 1382/2859] fixes build (#3134) * quadtreeworld.cpp * renderingmanager.cpp [ci skip] * quadtreeworld.hpp * cellborder.hpp * cellborder.cpp --- apps/openmw/mwrender/renderingmanager.cpp | 3 ++- components/terrain/cellborder.cpp | 7 +++---- components/terrain/cellborder.hpp | 2 +- components/terrain/quadtreeworld.cpp | 4 ++-- components/terrain/quadtreeworld.hpp | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fad2c25e20..be143510b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -414,9 +414,10 @@ namespace MWRender const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); + bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain"); mTerrain.reset(new Terrain::QuadTreeWorld( sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, - compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); + compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks)); if (Settings::Manager::getBool("object paging", "Terrain")) { mObjectPaging.reset(new ObjectPaging(mResourceSystem->getSceneManager())); diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 280a55faca..927530660e 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include "world.hpp" #include "../esm/loadland.hpp" @@ -22,7 +21,7 @@ CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask, { } -osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, +osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset, osg::Vec4f color) { const int cellSize = ESM::Land::REAL_SIZE; @@ -66,8 +65,8 @@ osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, floa border->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP,0,vertices->size())); - osg::ref_ptr borderGeode = new osg::Geode; - borderGeode->addDrawable(border.get()); + osg::ref_ptr borderGeode = new osg::Group; + borderGeode->addChild(border.get()); osg::StateSet *stateSet = borderGeode->getOrCreateStateSet(); osg::ref_ptr material (new osg::Material); diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index 785c441b86..4481816a55 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -32,7 +32,7 @@ namespace Terrain */ void destroyCellBorderGeometry(); - static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset = 10.0, osg::Vec4f color = { 1,1,0,0 }); + static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset = 10.0, osg::Vec4f color = { 1,1,0,0 }); protected: Terrain::World *mWorld; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index c05a964209..b47896d775 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -271,7 +271,7 @@ private: unsigned int mNodeMask; }; -QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) +QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, bool debugChunks) : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) @@ -279,7 +279,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) , mMinSize(1/8.f) - , mDebugTerrainChunks(Settings::Manager::getBool("debug chunks", "Terrain")) + , mDebugTerrainChunks(debugChunks) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 3aabc7233e..3bd606d6c6 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -22,7 +22,7 @@ namespace Terrain class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) { public: - QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); + QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, bool debugChunks); ~QuadTreeWorld(); From b7c1d9edb0c8a1cea73d52ba79e588f40d8846cd Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 29 Sep 2021 17:13:40 +0200 Subject: [PATCH 1383/2859] remove unnecessary includes --- components/terrain/quadtreeworld.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index b47896d775..4baf08149d 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -13,7 +12,6 @@ #include #include #include -#include #include "quadtreenode.hpp" #include "storage.hpp" From bec04c66133380d8f36e08193369dd0c485dbe0f Mon Sep 17 00:00:00 2001 From: "Hristos N. Triantafillou" Date: Wed, 29 Sep 2021 11:13:38 -0500 Subject: [PATCH 1384/2859] Unbreak the formatting broken by !1208 --- docs/source/reference/modding/paths.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/paths.rst b/docs/source/reference/modding/paths.rst index bc955b703d..db5000527b 100644 --- a/docs/source/reference/modding/paths.rst +++ b/docs/source/reference/modding/paths.rst @@ -29,7 +29,7 @@ Savegames +--------------+-----------------------------------------------------------------------------------------------------+ | OS | Location | +==============+=====================================================================================================+ -| Linux | ``$HOME/.local/share/openmw/saves`` | +| Linux | ``$HOME/.local/share/openmw/saves`` | +--------------+-----------------------------------------------------------------------------------------------------+ | Mac | ``$HOME/Library/Application\ Support/openmw/saves`` | +--------------+---------------+-------------------------------------------------------------------------------------+ From e109d864895e472c805fcb090ca03f08ac952562 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Sep 2021 21:01:22 +0400 Subject: [PATCH 1385/2859] Revert "avoids creating empty statesets on drawables (#3132)" This reverts commit 957c25a491b86772b02ab9057679ad1fc970f353. --- apps/openmw/mwrender/renderingmanager.cpp | 1 - components/nifosg/nifloader.cpp | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index be143510b2..07b0418f2b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -486,7 +486,6 @@ namespace MWRender defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); - sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); mFog.reset(new FogManager()); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f05f906c8f..0432aef5b4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1134,6 +1134,8 @@ namespace NifOsg trans->addChild(toAttach); parentNode->addChild(trans); } + // create partsys stateset in order to pass in ShaderVisitor like all other Drawables + partsys->getOrCreateStateSet(); } void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) @@ -1921,6 +1923,8 @@ namespace NifOsg void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { + osg::StateSet* stateset = node->getOrCreateStateSet(); + // Specular lighting is enabled by default, but there's a quirk... bool specEnabled = true; osg::ref_ptr mat (new osg::Material); @@ -2002,15 +2006,15 @@ namespace NifOsg if (blendFunc->getDestination() == GL_DST_ALPHA) blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); - node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); bool noSort = (alphaprop->flags>>13)&1; if (!noSort) - node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); else - node->getOrCreateStateSet()->setRenderBinToInherit(); + stateset->setRenderBinToInherit(); } - else if (osg::StateSet* stateset = node->getStateSet()) + else { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); @@ -2021,9 +2025,9 @@ namespace NifOsg { osg::ref_ptr alphaFunc (new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f)); alphaFunc = shareAttribute(alphaFunc); - node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } - else if (osg::StateSet* stateset = node->getStateSet()) + else { stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); @@ -2079,10 +2083,8 @@ namespace NifOsg mat = shareAttribute(mat); - osg::StateSet* stateset = node->getStateSet(); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - if (emissiveMult != 1.f) - stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); + stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; From dc1fe62dded40110fefb3f26f86e8b0e70b614b9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 27 Aug 2021 20:07:50 +0200 Subject: [PATCH 1386/2859] Overhaul magic effects to work with onApply and onEnd events --- CHANGELOG.md | 9 + apps/essimporter/converter.hpp | 4 +- apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwbase/mechanicsmanager.hpp | 2 - apps/openmw/mwbase/world.hpp | 4 +- apps/openmw/mwgui/charactercreation.cpp | 16 - apps/openmw/mwgui/container.cpp | 2 +- apps/openmw/mwgui/jailscreen.cpp | 5 +- apps/openmw/mwgui/spellbuyingwindow.cpp | 8 +- apps/openmw/mwgui/spellcreationdialog.cpp | 4 +- apps/openmw/mwgui/spellicons.cpp | 54 +- apps/openmw/mwgui/spellicons.hpp | 14 - apps/openmw/mwgui/spellmodel.cpp | 3 +- apps/openmw/mwmechanics/activespells.cpp | 569 +++++---- apps/openmw/mwmechanics/activespells.hpp | 129 ++- apps/openmw/mwmechanics/actors.cpp | 824 ++----------- apps/openmw/mwmechanics/actors.hpp | 7 +- apps/openmw/mwmechanics/aicombataction.cpp | 12 +- apps/openmw/mwmechanics/creaturestats.cpp | 44 +- apps/openmw/mwmechanics/creaturestats.hpp | 13 +- apps/openmw/mwmechanics/disease.hpp | 7 +- apps/openmw/mwmechanics/linkedeffects.cpp | 75 -- apps/openmw/mwmechanics/linkedeffects.hpp | 32 - apps/openmw/mwmechanics/magiceffects.cpp | 12 +- apps/openmw/mwmechanics/magiceffects.hpp | 10 - .../mwmechanics/mechanicsmanagerimp.cpp | 22 +- .../mwmechanics/mechanicsmanagerimp.hpp | 2 - apps/openmw/mwmechanics/spellabsorption.cpp | 45 +- apps/openmw/mwmechanics/spellcasting.cpp | 401 ++----- apps/openmw/mwmechanics/spellcasting.hpp | 13 +- apps/openmw/mwmechanics/spelleffects.cpp | 1032 +++++++++++++++++ apps/openmw/mwmechanics/spelleffects.hpp | 20 + apps/openmw/mwmechanics/spelllist.hpp | 6 - apps/openmw/mwmechanics/spellpriority.cpp | 22 +- apps/openmw/mwmechanics/spells.cpp | 234 +--- apps/openmw/mwmechanics/spells.hpp | 26 +- apps/openmw/mwmechanics/summoning.cpp | 112 +- apps/openmw/mwmechanics/summoning.hpp | 23 +- apps/openmw/mwmechanics/tickableeffects.cpp | 216 ---- apps/openmw/mwmechanics/tickableeffects.hpp | 20 - apps/openmw/mwrender/npcanimation.cpp | 28 - apps/openmw/mwrender/npcanimation.hpp | 1 - apps/openmw/mwscript/miscextensions.cpp | 10 +- apps/openmw/mwscript/statsextensions.cpp | 18 +- apps/openmw/mwworld/cellstore.cpp | 8 + apps/openmw/mwworld/esmloader.cpp | 203 ++++ apps/openmw/mwworld/esmloader.hpp | 5 + apps/openmw/mwworld/inventorystore.cpp | 271 +---- apps/openmw/mwworld/inventorystore.hpp | 43 +- apps/openmw/mwworld/player.cpp | 11 +- apps/openmw/mwworld/projectilemanager.cpp | 10 +- apps/openmw/mwworld/projectilemanager.hpp | 3 +- apps/openmw/mwworld/worldimp.cpp | 78 +- apps/openmw/mwworld/worldimp.hpp | 4 +- components/esm/activespells.cpp | 97 +- components/esm/activespells.hpp | 35 +- components/esm/creaturestats.cpp | 52 +- components/esm/creaturestats.hpp | 1 + components/esm/magiceffects.cpp | 18 +- components/esm/magiceffects.hpp | 4 +- components/esm/projectilestate.cpp | 5 + components/esm/projectilestate.hpp | 1 + components/esm/savedgame.cpp | 2 +- components/esm/spellstate.cpp | 69 +- components/esm/spellstate.hpp | 8 +- 65 files changed, 2334 insertions(+), 2708 deletions(-) delete mode 100644 apps/openmw/mwmechanics/linkedeffects.cpp delete mode 100644 apps/openmw/mwmechanics/linkedeffects.hpp create mode 100644 apps/openmw/mwmechanics/spelleffects.cpp create mode 100644 apps/openmw/mwmechanics/spelleffects.hpp delete mode 100644 apps/openmw/mwmechanics/tickableeffects.cpp delete mode 100644 apps/openmw/mwmechanics/tickableeffects.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc9510f6e..ffe687a04c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.48.0 ------ + Bug #1751: Birthsign abilities increase modified attribute values instead of base ones Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) @@ -16,13 +17,18 @@ Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5508: Engine binary links to Qt without using it + Bug #5596: Effects in constant spells should not be merged + Bug #5621: Drained stats cannot be restored Bug #5755: Active grid object paging - disappearing textures Bug #5788: Texture editing parses the selected indexes wrongly + Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention Bug #5842: GetDisposition adds temporary disposition change from different actors + Bug #5863: GetEffect should return true after the player has teleported Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6051: NaN water height in ESM file is not handled gracefully Bug #6066: addtopic "return" does not work from within script. No errors thrown Bug #6067: esp loader fails in for certain subrecord orders + Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed Bug #6115: Showmap overzealous matching @@ -36,6 +42,7 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop + Bug #6223: Some Constant Effect Bound Items inconsistencies Bug #6273: Respawning NPCs rotation is inconsistent Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death @@ -45,8 +52,10 @@ Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map + Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4595: Unique object identifier Feature #4737: Handle instance move from one cell to another + Feature #5198: Implement "Magic effect expired" event Feature #5489: MCP: Telekinesis fix for activators Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 46e4426dc5..81b9711bbf 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -124,11 +124,9 @@ public: { mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel; mContext->mPlayerBase = npc; - ESM::SpellState::SpellParams empty; // FIXME: player start spells and birthsign spells aren't listed here, // need to fix openmw to account for this - for (const auto & spell : npc.mSpells.mList) - mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty; + mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList; // Clear the list now that we've written it, this prevents issues cropping up with // ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal. diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 82a91699a9..7ad9856a77 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -92,8 +92,8 @@ 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 actor summoning - character actors objects aistate trading weaponpriority spellpriority weapontype spellutil tickableeffects - spellabsorption linkedeffects + character actors objects aistate trading weaponpriority spellpriority weapontype spellutil + spellabsorption spelleffects ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index b638ecade9..ccc60afc27 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -277,8 +277,6 @@ namespace MWBase virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0; virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0; virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0; - - virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1dbb2b96f6..e9fc6e5c7a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -539,7 +539,7 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0; - virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; + virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) = 0; virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; virtual void updateProjectilesCasters() = 0; @@ -591,7 +591,7 @@ namespace MWBase virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, - const std::string& sourceName, const bool fromProjectile=false) = 0; + const std::string& sourceName, const bool fromProjectile=false, int slot = 0) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 827f87c7d6..43da1ef83f 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -72,13 +72,6 @@ namespace return {question, {r2, r1, r0}, sound}; } } - - void updatePlayerHealth() - { - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); - npcStats.updateHealth(); - } } namespace MWGui @@ -372,8 +365,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); mPickClassDialog = nullptr; } - - updatePlayerHealth(); } void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) @@ -448,8 +439,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); mRaceDialog = nullptr; } - - updatePlayerHealth(); } void CharacterCreation::onRaceDialogBack() @@ -477,8 +466,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); mBirthSignDialog = nullptr; } - - updatePlayerHealth(); } void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) @@ -527,7 +514,6 @@ namespace MWGui // Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later mCreateClassDialog->setVisible(false); } - updatePlayerHealth(); } void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) @@ -719,8 +705,6 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get().find(mGenerateClass); mPlayerClass = *klass; - - updatePlayerHealth(); } void CharacterCreation::onGenerateClassBack() diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index b649e41b0f..df490943d2 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -262,7 +262,7 @@ namespace MWGui } // Clean up summoned creatures as well - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + auto& creatureMap = creatureStats.getSummonedCreatureMap(); for (const auto& creature : creatureMap) MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second); creatureMap.clear(); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index cc793073e3..a029fe54b6 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -82,10 +82,7 @@ namespace MWGui MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); // We should not worsen corprus when in prison - for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells()) - { - spell.second.mNextWorsening += mDays * 24; - } + player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24); std::set skills; for (int day=0; day spellsToSort; - for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter) + for (const ESM::Spell* spell : merchantSpells) { - const ESM::Spell* spell = iter->first; - if (spell->mData.mType!=ESM::Spell::ST_Spell) continue; // don't try to sell diseases, curses or powers @@ -115,10 +113,10 @@ namespace MWGui continue; } - if (playerHasSpell(iter->first->mId)) + if (playerHasSpell(spell->mId)) continue; - spellsToSort.push_back(iter->first); + spellsToSort.push_back(spell); } std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 01653d9e6f..8c0ea865a8 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -520,10 +520,8 @@ namespace MWGui std::vector knownEffects; - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; - // only normal spells count if (spell->mData.mType != ESM::Spell::ST_Spell) continue; diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 405abfbae7..4a86503f46 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -24,50 +24,33 @@ namespace MWGui { - - void EffectSourceVisitor::visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime, float totalTime) - { - MagicEffectInfo newEffectSource; - newEffectSource.mKey = key; - newEffectSource.mMagnitude = static_cast(magnitude); - newEffectSource.mPermanent = mIsPermanent; - newEffectSource.mRemainingTime = remainingTime; - newEffectSource.mSource = sourceName; - newEffectSource.mTotalTime = totalTime; - - mEffectSources[key.mId].push_back(newEffectSource); - } - - void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) { - // TODO: Tracking add/remove/expire would be better than force updating every frame - MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - - EffectSourceVisitor visitor; - - // permanent item enchantments & permanent spells - visitor.mIsPermanent = true; - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - store.visitEffectSources(visitor); - stats.getSpells().visitEffectSources(visitor); - - // now add lasting effects - visitor.mIsPermanent = false; - stats.getActiveSpells().visitEffectSources(visitor); - - std::map >& effects = visitor.mEffectSources; + std::map> effects; + for(const auto& params : stats.getActiveSpells()) + { + for(const auto& effect : params.getEffects()) + { + if(!effect.mMagnitude) + continue; + MagicEffectInfo newEffectSource; + newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg); + newEffectSource.mMagnitude = static_cast(effect.mMagnitude); + newEffectSource.mPermanent = effect.mDuration == -1.f; + newEffectSource.mRemainingTime = effect.mTimeLeft; + newEffectSource.mSource = params.getDisplayName(); + newEffectSource.mTotalTime = effect.mDuration; + effects[effect.mEffectId].push_back(newEffectSource); + } + } int w=2; - for (auto& effectInfoPair : effects) + for (const auto& [effectId, effectInfos] : effects) { - const int effectId = effectInfoPair.first; const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld ()->getStore ().get().find(effectId); @@ -78,7 +61,6 @@ namespace MWGui static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->mValue.getFloat(); - std::vector& effectInfos = effectInfoPair.second; bool addNewLine = false; for (const MagicEffectInfo& effectInfo : effectInfos) { diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index b6aa49e69e..9825162a33 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -37,20 +37,6 @@ namespace MWGui bool mPermanent; // the effect is permanent }; - class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor - { - public: - bool mIsPermanent; - - std::map > mEffectSources; - - virtual ~EffectSourceVisitor() {} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override; - }; - class SpellIcons { public: diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index fe18b0f082..61ea9ce93a 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -92,9 +92,8 @@ namespace MWGui std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter); - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) continue; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 319d797257..6ad6ef369e 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -5,347 +5,442 @@ #include +#include "creaturestats.hpp" +#include "spellcasting.hpp" +#include "spelleffects.hpp" + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" -namespace MWMechanics +namespace { - void ActiveSpells::update(float duration) const + bool merge(std::vector& present, const std::vector& queued) { - bool rebuild = false; - - // Erase no longer active spells and effects - if (duration > 0) + // Can't merge if we already have an effect with the same effect index + auto problem = std::find_if(queued.begin(), queued.end(), [&] (const auto& qEffect) { - TContainer::iterator iter (mSpells.begin()); - while (iter!=mSpells.end()) - { - if (!timeToExpire (iter)) - { - mSpells.erase (iter++); - rebuild = true; - } - else - { - bool interrupt = false; - std::vector& effects = iter->second.mEffects; - for (std::vector::iterator effectIt = effects.begin(); effectIt != effects.end();) - { - if (effectIt->mTimeLeft <= 0) - { - rebuild = true; - - // Note: it we expire a Corprus effect, we should remove the whole spell. - if (effectIt->mEffectId == ESM::MagicEffect::Corprus) - { - iter = mSpells.erase (iter); - interrupt = true; - break; - } - - effectIt = effects.erase(effectIt); - } - else - { - effectIt->mTimeLeft -= duration; - ++effectIt; - } - } - - if (!interrupt) - ++iter; - } - } - } - - if (mSpellsChanged) - { - mSpellsChanged = false; - rebuild = true; - } - - if (rebuild) - rebuildEffects(); + return std::find_if(present.begin(), present.end(), [&] (const auto& pEffect) { return pEffect.mEffectIndex == qEffect.mEffectIndex; }) != present.end(); + }); + if(problem != queued.end()) + return false; + present.insert(present.end(), queued.begin(), queued.end()); + return true; } - void ActiveSpells::rebuildEffects() const + void addEffects(std::vector& effects, const ESM::EffectList& list, bool ignoreResistances = false) { - mEffects = MagicEffects(); - - for (TIterator iter (begin()); iter!=end(); ++iter) + int currentEffectIndex = 0; + for(const auto& enam : list.mList) { - const std::vector& effects = iter->second.mEffects; - - for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) - { - if (effectIt->mTimeLeft > 0) - mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude)); - } + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mMagnMin; + effect.mMaxMagnitude = enam.mMagnMax; + effect.mEffectIndex = currentEffectIndex++; + effect.mFlags = ESM::ActiveEffect::Flag_None; + if(ignoreResistances) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effects.emplace_back(effect); } } +} - ActiveSpells::ActiveSpells() - : mSpellsChanged (false) - {} +namespace MWMechanics +{ + ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells) + { + mActiveSpells.mIterating = true; + } - const MagicEffects& ActiveSpells::getMagicEffects() const + ActiveSpells::IterationGuard::~IterationGuard() { - update(0.f); - return mEffects; + mActiveSpells.mIterating = false; } - ActiveSpells::TIterator ActiveSpells::begin() const + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster) + : mId(cast.mId), mDisplayName(cast.mSourceName), mCasterActorId(-1), mSlot(cast.mSlot), mType(cast.mType), mWorsenings(-1) { - return mSpells.begin(); + if(!caster.isEmpty() && caster.getClass().isActor()) + mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId(); } - ActiveSpells::TIterator ActiveSpells::end() const + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances) + : mId(spell->mId), mDisplayName(spell->mName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()), mSlot(0) + , mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability : ESM::ActiveSpells::Type_Permanent), mWorsenings(-1) { - return mSpells.end(); + assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power); + addEffects(mEffects, spell->mEffects, ignoreResistances); } - double ActiveSpells::timeToExpire (const TIterator& iterator) const + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor) + : mId(item.getCellRef().getRefId()), mDisplayName(item.getClass().getName(item)), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) + , mSlot(slotIndex), mType(ESM::ActiveSpells::Type_Enchantment), mWorsenings(-1) { - const std::vector& effects = iterator->second.mEffects; + assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect); + addEffects(mEffects, enchantment->mEffects); + } - float duration = 0; + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params) + : mId(params.mId), mEffects(params.mEffects), mDisplayName(params.mDisplayName), mCasterActorId(params.mCasterActorId) + , mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0) + , mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening}) + {} - for (std::vector::const_iterator iter (effects.begin()); - iter!=effects.end(); ++iter) + ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const + { + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = mId; + params.mEffects = mEffects; + params.mDisplayName = mDisplayName; + params.mCasterActorId = mCasterActorId; + params.mItem.unset(); + if(mSlot) { - if (iter->mTimeLeft > duration) - duration = iter->mTimeLeft; + // Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing + // mSlot needs to be replaced with a RefNum once inventory items get persistent RefNum (#4508 #6148) + params.mItem = { static_cast(mSlot), 0 }; } + params.mType = mType; + params.mWorsenings = mWorsenings; + params.mNextWorsening = mNextWorsening.toEsm(); + return params; + } - if (duration < 0) - return 0; - - return duration; + void ActiveSpells::ActiveSpellParams::worsen() + { + ++mWorsenings; + if(!mWorsenings) + mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp(); + mNextWorsening += CorprusStats::sWorseningPeriod; } - bool ActiveSpells::isSpellActive(const std::string& id) const + bool ActiveSpells::ActiveSpellParams::shouldWorsen() const { - for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) - { - if (Misc::StringUtils::ciEqual(iter->first, id)) - return true; - } - return false; + return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening; } - const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const + void ActiveSpells::ActiveSpellParams::resetWorsenings() { - return mSpells; + mWorsenings = -1; } - void ActiveSpells::addSpell(const std::string &id, bool stack, const std::vector& effects, - const std::string &displayName, int casterActorId) + void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) { - TContainer::iterator it(mSpells.find(id)); + const auto& creatureStats = ptr.getClass().getCreatureStats(ptr); + assert(&creatureStats.getActiveSpells() == this); + IterationGuard guard{*this}; + // 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) + { + ++spellIt; + continue; + } + bool removedSpell = false; + for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();) + { + if(effectIt->mFlags & ESM::ActiveEffect::Flag_Remove && effectIt->mTimeLeft <= 0.f) + { + auto effect = *effectIt; + effectIt = spellIt->mEffects.erase(effectIt); + onMagicEffectRemoved(ptr, *spellIt, effect); + removedSpell = applyPurges(ptr, &spellIt, &effectIt); + if(removedSpell) + break; + } + else + { + ++effectIt; + } + } + if(removedSpell) + continue; + if(spellIt->mEffects.empty()) + spellIt = mSpells.erase(spellIt); + else + ++spellIt; + } - ActiveSpellParams params; - params.mEffects = effects; - params.mDisplayName = displayName; - params.mCasterActorId = casterActorId; + for(const auto& spell : mQueue) + addToSpells(ptr, spell); + mQueue.clear(); - if (it == end() || stack) + // Vanilla only does this on cell change I think + const auto& spells = creatureStats.getSpells(); + for(const ESM::Spell* spell : spells) { - mSpells.insert(std::make_pair(id, params)); + if(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId)) + mSpells.emplace_back(ActiveSpellParams{spell, ptr}); } - else + + if(ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())) { - // addSpell() is called with effects for a range. - // but a spell may have effects with different ranges (e.g. Touch & Target) - // so, if we see new effects for same spell assume additional - // spell effects and add to existing effects of spell - mergeEffects(params.mEffects, it->second.mEffects); - it->second = params; + auto& store = ptr.getClass().getInventoryStore(ptr); + if(store.getInvListener() != nullptr) + { + bool playNonLooping = !store.isFirstEquip(); + const auto world = MWBase::Environment::get().getWorld(); + for(int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) + { + auto slot = store.getSlot(slotIndex); + if(slot == store.end()) + continue; + const auto& enchantmentId = slot->getClass().getEnchantment(*slot); + if(enchantmentId.empty()) + continue; + const ESM::Enchantment* enchantment = world->getStore().get().find(enchantmentId); + if(enchantment->mData.mType != ESM::Enchantment::ConstantEffect) + continue; + if(std::find_if(mSpells.begin(), mSpells.end(), [&] (const ActiveSpellParams& params) + { + return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId(); + }) != mSpells.end()) + continue; + ActiveSpellParams params(*slot, enchantment, slotIndex, ptr); + mSpells.emplace_back(params); + for(const auto& effect : params.mEffects) + MWMechanics::playEffects(ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); + } + } } - mSpellsChanged = true; - } - - void ActiveSpells::mergeEffects(std::vector& addTo, const std::vector& from) - { - for (std::vector::const_iterator effect(from.begin()); effect != from.end(); ++effect) + // Update effects + for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - // if effect is not in addTo, add it - bool missing = true; - for (std::vector::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter) + const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid? + bool removedSpell = false; + for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) { - if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg)) - { - missing = false; + bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration); + if(remove) + it = spellIt->mEffects.erase(it); + else + ++it; + removedSpell = applyPurges(ptr, &spellIt, &it); + if(removedSpell) break; - } } - if (missing) + if(removedSpell) + continue; + + bool remove = false; + if(spellIt->mType == ESM::ActiveSpells::Type_Ability || spellIt->mType == ESM::ActiveSpells::Type_Permanent) + remove = !spells.hasSpell(spellIt->mId); + else if(spellIt->mType == ESM::ActiveSpells::Type_Enchantment) { - addTo.push_back(*effect); + const auto& store = ptr.getClass().getInventoryStore(ptr); + auto slot = store.getSlot(spellIt->mSlot); + remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId; } + if(remove) + { + auto params = *spellIt; + spellIt = mSpells.erase(spellIt); + for(const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); + applyPurges(ptr, &spellIt); + continue; + } + ++spellIt; } } - void ActiveSpells::removeEffects(const std::string &id) + void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) { - for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) + if(spell.mType != ESM::ActiveSpells::Type_Consumable) { - if (spell->first == id) + auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing) + { + return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId && spell.mSlot == existing.mSlot; + }); + if(found != mSpells.end()) { - spell->second.mEffects.clear(); - mSpellsChanged = true; + if(merge(found->mEffects, spell.mEffects)) + return; + auto params = *found; + mSpells.erase(found); + for(const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); } } + mSpells.emplace_back(spell); } - void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const + ActiveSpells::ActiveSpells() : mIterating(false) + {} + + ActiveSpells::TIterator ActiveSpells::begin() const { - for (TContainer::const_iterator it = begin(); it != end(); ++it) - { - for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end(); ++effectIt) - { - std::string name = it->second.mDisplayName; + return mSpells.begin(); + } - float magnitude = effectIt->mMagnitude; - if (magnitude) - visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration); - } - } + ActiveSpells::TIterator ActiveSpells::end() const + { + return mSpells.end(); } - void ActiveSpells::purgeAll(float chance, bool spellOnly) + bool ActiveSpells::isSpellActive(const std::string& id) const { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) + return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell) { - const std::string spellId = it->first; + return Misc::StringUtils::ciEqual(spell.mId, id); + }) != mSpells.end(); + } - // if spellOnly is true, dispell only spells. Leave potions, enchanted items etc. - if (spellOnly) - { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); - if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) - { - ++it; - continue; - } - } + void ActiveSpells::addSpell(const ActiveSpellParams& params) + { + mQueue.emplace_back(params); + } - if (Misc::Rng::roll0to99() < chance) - mSpells.erase(it++); - else - ++it; - } - mSpellsChanged = true; + void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor) + { + mQueue.emplace_back(ActiveSpellParams{spell, actor, true}); } - void ActiveSpells::purgeEffect(short effectId) + void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr) { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this); + mPurges.emplace(predicate); + if(!mIterating) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end();) - { - if (effectIt->mEffectId == effectId) - effectIt = it->second.mEffects.erase(effectIt); - else - ++effectIt; - } + IterationGuard guard{*this}; + applyPurges(ptr); } - mSpellsChanged = true; } - void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex) + void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr) { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this); + mPurges.emplace(predicate); + if(!mIterating) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end();) - { - if (effectIt->mEffectId == effectId && it->first == sourceId && (effectIndex < 0 || effectIndex == effectIt->mEffectIndex)) - effectIt = it->second.mEffects.erase(effectIt); - else - ++effectIt; - } + IterationGuard guard{*this}; + applyPurges(ptr); } - mSpellsChanged = true; } - void ActiveSpells::purge(int casterActorId) + bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell, std::vector::iterator* currentEffect) { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + bool removedCurrentSpell = false; + while(!mPurges.empty()) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end();) + auto predicate = mPurges.front(); + mPurges.pop(); + for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - if (it->second.mCasterActorId == casterActorId) - effectIt = it->second.mEffects.erase(effectIt); - else - ++effectIt; + bool isCurrentSpell = currentSpell && *currentSpell == spellIt; + std::visit([&] (auto&& variant) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + if(variant(*spellIt)) + { + auto params = *spellIt; + spellIt = mSpells.erase(spellIt); + if(isCurrentSpell) + { + *currentSpell = spellIt; + removedCurrentSpell = true; + } + for(const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); + } + else + ++spellIt; + } + else + { + static_assert(std::is_same_v, "Non-exhaustive visitor"); + for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();) + { + if(variant(*spellIt, *effectIt)) + { + auto effect = *effectIt; + if(isCurrentSpell && currentEffect) + { + auto distance = std::distance(spellIt->mEffects.begin(), *currentEffect); + if(effectIt <= *currentEffect) + distance--; + effectIt = spellIt->mEffects.erase(effectIt); + *currentEffect = spellIt->mEffects.begin() + distance; + } + else + effectIt = spellIt->mEffects.erase(effectIt); + onMagicEffectRemoved(ptr, *spellIt, effect); + } + else + ++effectIt; + } + ++spellIt; + } + }, predicate); } } - mSpellsChanged = true; + return removedCurrentSpell; } - void ActiveSpells::purgeCorprusDisease() + void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const std::string &id) { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + purge([=] (const ActiveSpellParams& params) { - bool hasCorprusEffect = false; - for (std::vector::iterator effectIt = iter->second.mEffects.begin(); - effectIt != iter->second.mEffects.end();++effectIt) - { - if (effectIt->mEffectId == ESM::MagicEffect::Corprus) - { - hasCorprusEffect = true; - break; - } - } - - if (hasCorprusEffect) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + return params.mId == id; + }, ptr); } - void ActiveSpells::clear() + void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId) { - mSpells.clear(); - mSpellsChanged = true; + purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect) + { + return effect.mEffectId == effectId; + }, ptr); } - void ActiveSpells::writeState(ESM::ActiveSpells &state) const + void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId) { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + purge([=] (const ActiveSpellParams& params) { - // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp - ESM::ActiveSpells::ActiveSpellParams params; - params.mEffects = it->second.mEffects; - params.mCasterActorId = it->second.mCasterActorId; - params.mDisplayName = it->second.mDisplayName; + return params.mCasterActorId == casterActorId; + }, ptr); + } - state.mSpells.insert (std::make_pair(it->first, params)); - } + void ActiveSpells::clear(const MWWorld::Ptr& ptr) + { + mQueue.clear(); + purge([] (const ActiveSpellParams& params) { return true; }, ptr); } - void ActiveSpells::readState(const ESM::ActiveSpells &state) + void ActiveSpells::skipWorsenings(double hours) { - for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + for(auto& spell : mSpells) { - // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp - ActiveSpellParams params; - params.mEffects = it->second.mEffects; - params.mCasterActorId = it->second.mCasterActorId; - params.mDisplayName = it->second.mDisplayName; - - mSpells.insert (std::make_pair(it->first, params)); - mSpellsChanged = true; + if(spell.mWorsenings >= 0) + spell.mNextWorsening += hours; } } + + void ActiveSpells::writeState(ESM::ActiveSpells &state) const + { + for(const auto& spell : mSpells) + state.mSpells.emplace_back(spell.toEsm()); + for(const auto& spell : mQueue) + state.mQueue.emplace_back(spell.toEsm()); + } + + void ActiveSpells::readState(const ESM::ActiveSpells &state) + { + for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells) + mSpells.emplace_back(ActiveSpellParams{spell}); + for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue) + mQueue.emplace_back(ActiveSpellParams{spell}); + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 8bc29aa444..0538d7a8b7 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -1,40 +1,83 @@ #ifndef GAME_MWMECHANICS_ACTIVESPELLS_H #define GAME_MWMECHANICS_ACTIVESPELLS_H -#include -#include +#include +#include +#include #include +#include +#include #include #include "../mwworld/timestamp.hpp" +#include "../mwworld/ptr.hpp" #include "magiceffects.hpp" +#include "spellcasting.hpp" + +namespace ESM +{ + struct Enchantment; + struct Spell; +} namespace MWMechanics { /// \brief Lasting spell effects /// - /// \note The name of this class is slightly misleading, since it also handels lasting potion + /// \note The name of this class is slightly misleading, since it also handles lasting potion /// effects. class ActiveSpells { public: - typedef ESM::ActiveEffect ActiveEffect; - - struct ActiveSpellParams + using ActiveEffect = ESM::ActiveEffect; + class ActiveSpellParams { - std::vector mEffects; - MWWorld::TimeStamp mTimeStamp; - std::string mDisplayName; + std::string mId; + std::vector mEffects; + std::string mDisplayName; + int mCasterActorId; + int mSlot; + ESM::ActiveSpells::EffectType mType; + int mWorsenings; + MWWorld::TimeStamp mNextWorsening; + + ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params); + + ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false); + + ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor); + + ESM::ActiveSpells::ActiveSpellParams toEsm() const; + + friend class ActiveSpells; + public: + ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster); + + const std::string& getId() const { return mId; } + + const std::vector& getEffects() const { return mEffects; } + std::vector& getEffects() { return mEffects; } - // The caster that inflicted this spell on us - int mCasterActorId; + ESM::ActiveSpells::EffectType getType() const { return mType; } + + int getCasterActorId() const { return mCasterActorId; } + + int getWorsenings() const { return mWorsenings; } + + const std::string& getDisplayName() const { return mDisplayName; } + + // Increments worsenings count and sets the next timestamp + void worsen(); + + bool shouldWorsen() const; + + void resetWorsenings(); }; - typedef std::multimap TContainer; - typedef TContainer::const_iterator TIterator; + typedef std::list::const_iterator TIterator; void readState (const ESM::ActiveSpells& state); void writeState (ESM::ActiveSpells& state) const; @@ -43,24 +86,29 @@ namespace MWMechanics TIterator end() const; - void update(float duration) const; + void update(const MWWorld::Ptr& ptr, float duration); private: + using ParamsPredicate = std::function; + using EffectPredicate = std::function; + using Predicate = std::variant; - mutable TContainer mSpells; - mutable MagicEffects mEffects; - mutable bool mSpellsChanged; + struct IterationGuard + { + ActiveSpells& mActiveSpells; - void rebuildEffects() const; + IterationGuard(ActiveSpells& spells); + ~IterationGuard(); + }; - /// Add any effects that are in "from" and not in "addTo" to "addTo" - void mergeEffects(std::vector& addTo, const std::vector& from); + std::list mSpells; + std::vector mQueue; + std::queue mPurges; + bool mIterating; - double timeToExpire (const TIterator& iterator) const; - ///< Returns time (in in-game hours) until the spell pointed to by \a iterator - /// expires. + void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell); - const TContainer& getActiveSpells() const; + bool applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell = nullptr, std::vector::iterator* currentEffect = nullptr); public: @@ -70,40 +118,31 @@ namespace MWMechanics /// /// \brief addSpell /// \param id ID for stacking purposes. - /// \param stack If false, the spell is not added if one with the same ID exists already. - /// \param effects - /// \param displayName Name for display in magic menu. /// - void addSpell (const std::string& id, bool stack, const std::vector& effects, - const std::string& displayName, int casterActorId); + void addSpell (const ActiveSpellParams& params); + + /// Bypasses resistances + void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// Removes the active effects from this spell/potion/.. with \a id - void removeEffects (const std::string& id); + void removeEffects (const MWWorld::Ptr& ptr, const std::string& id); /// Remove all active effects with this effect id - void purgeEffect (short effectId); - - /// Remove all active effects with this effect id and source id - void purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1); + void purgeEffect (const MWWorld::Ptr& ptr, short effectId); - /// Remove all active effects, if roll succeeds (for each effect) - void purgeAll(float chance, bool spellOnly = false); + void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr); + void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr); - /// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId - void purge (int casterActorId); + /// Remove all effects that were cast by \a casterActorId + void purge (const MWWorld::Ptr& ptr, int casterActorId); /// Remove all spells - void clear(); + void clear(const MWWorld::Ptr& ptr); bool isSpellActive (const std::string& id) const; ///< case insensitive - void purgeCorprusDisease(); - - const MagicEffects& getMagicEffects() const; - - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - + void skipWorsenings(double hours); }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 542f185854..b1aa246385 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -44,7 +44,6 @@ #include "actor.hpp" #include "summoning.hpp" #include "actorutil.hpp" -#include "tickableeffects.hpp" namespace { @@ -55,67 +54,26 @@ bool isConscious(const MWWorld::Ptr& ptr) return !stats.isDead() && !stats.getKnockedDown(); } -int getBoundItemSlot (const std::string& itemId) +bool isCommanded(const MWWorld::Ptr& actor) { - static std::map boundItemsMap; - if (boundItemsMap.empty()) + const auto& stats = actor.getClass().getCreatureStats(actor); + for(const auto& params : stats.getActiveSpells()) { - const auto& store = MWBase::Environment::get().getWorld()->getStore().get(); - - std::string boundId = store.find("sMagicBoundBootsID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; - - boundId = store.find("sMagicBoundCuirassID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; - - boundId = store.find("sMagicBoundLeftGauntletID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; - - boundId = store.find("sMagicBoundRightGauntletID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; - - boundId = store.find("sMagicBoundHelmID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; - - boundId = store.find("sMagicBoundShieldID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; + for(const auto& effect : params.getEffects()) + { + if(((effect.mEffectId == ESM::MagicEffect::CommandHumanoid && actor.getClass().isNpc()) + || (effect.mEffectId == ESM::MagicEffect::CommandCreature && !actor.getClass().isNpc())) + && effect.mMagnitude >= stats.getLevel()) + return true; + } } - - int slot = MWWorld::InventoryStore::Slot_CarriedRight; - std::map::iterator it = boundItemsMap.find(itemId); - if (it != boundItemsMap.end()) - slot = it->second; - - return slot; + return false; } -class CheckActorCommanded : public MWMechanics::EffectSourceVisitor -{ - MWWorld::Ptr mActor; -public: - bool mCommanded; - - CheckActorCommanded(const MWWorld::Ptr& actor) - : mActor(actor) - , mCommanded(false){} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) - || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name())) - && magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel()) - mCommanded = true; - } -}; - // Check for command effects having ended and remove package if necessary void adjustCommandedActor (const MWWorld::Ptr& actor) { - CheckActorCommanded check(actor); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - stats.getActiveSpells().visitEffectSources(check); bool hasCommandPackage = false; @@ -130,7 +88,7 @@ void adjustCommandedActor (const MWWorld::Ptr& actor) } } - if (!check.mCommanded && hasCommandPackage) + if (!isCommanded(actor) && hasCommandPackage) stats.getAiSequence().erase(it); } @@ -169,129 +127,43 @@ void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWW } } -} - -namespace MWMechanics +float getStuntedMagickaDuration(const MWWorld::Ptr& actor) { - static const int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player - static const int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player - static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement - static const float DECELERATE_DISTANCE = 512.f; - - namespace + float remainingTime = 0.f; + for(const auto& params : actor.getClass().getCreatureStats(actor).getActiveSpells()) { - float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents) + for(const auto& effect : params.getEffects()) { - const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length(); - return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed; - } - } - - class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor - { - public: - float mRemainingTime; - - GetStuntedMagickaDuration(const MWWorld::Ptr& actor) - : mRemainingTime(0.f){} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (mRemainingTime == -1) return; - - if (key.mId == ESM::MagicEffect::StuntedMagicka) + if(effect.mEffectId == ESM::MagicEffect::StuntedMagicka) { - if (totalTime == -1) - { - mRemainingTime = -1; - return; - } - - if (remainingTime > mRemainingTime) - mRemainingTime = remainingTime; + if(effect.mDuration == -1.f) + return -1.f; + remainingTime = std::max(remainingTime, effect.mTimeLeft); } } - }; - - class GetCurrentMagnitudes : public MWMechanics::EffectSourceVisitor - { - std::string mSpellId; - - public: - GetCurrentMagnitudes(const std::string& spellId) - : mSpellId(spellId) - { - } - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (magnitude <= 0) - return; - - if (sourceId != mSpellId) - return; - - mMagnitudes.emplace_back(key, magnitude); - } - - std::vector> mMagnitudes; - }; - - class GetCorprusSpells : public MWMechanics::EffectSourceVisitor - { - - public: - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (key.mId != ESM::MagicEffect::Corprus) - return; - - mSpells.push_back(sourceId); - } - - std::vector mSpells; - }; - - class SoulTrap : public MWMechanics::EffectSourceVisitor - { - MWWorld::Ptr mCreature; - MWWorld::Ptr mActor; - bool mTrapped; - public: - SoulTrap(const MWWorld::Ptr& trappedCreature) - : mCreature(trappedCreature) - , mTrapped(false) - { - } - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (mTrapped) - return; - if (key.mId != ESM::MagicEffect::Soultrap) - return; - if (magnitude <= 0) - return; - - MWBase::World* world = MWBase::Environment::get().getWorld(); + } + return remainingTime; +} - MWWorld::Ptr caster = world->searchPtrViaActorId(casterActorId); +void soulTrap(const MWWorld::Ptr& creature) +{ + const auto& stats = creature.getClass().getCreatureStats(creature); + if(!stats.getMagicEffects().get(ESM::MagicEffect::Soultrap).getMagnitude()) + return; + int creatureSoulValue = creature.get()->mBase->mData.mSoul; + if (creatureSoulValue == 0) + return; + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); + for(const auto& params : stats.getActiveSpells()) + { + for(const auto& effect : params.getEffects()) + { + if(effect.mEffectId != ESM::MagicEffect::Soultrap || effect.mMagnitude <= 0.f) + continue; + MWWorld::Ptr caster = world->searchPtrViaActorId(params.getCasterActorId()); if (caster.isEmpty() || !caster.getClass().isActor()) - return; - - static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); - - int creatureSoulValue = mCreature.get()->mBase->mData.mSoul; - if (creatureSoulValue == 0) - return; + continue; // Use the smallest soulgem that is large enough to hold the soul MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster); @@ -316,126 +188,50 @@ namespace MWMechanics } if (gem == container.end()) - return; + continue; // Set the soul on just one of the gems, not the whole stack gem->getContainerStore()->unstack(*gem, caster); - gem->getCellRef().setSoul(mCreature.getCellRef().getRefId()); + gem->getCellRef().setSoul(creature.getCellRef().getRefId()); // Restack the gem with other gems with the same soul gem->getContainerStore()->restack(*gem); - mTrapped = true; - - if (caster == getPlayer()) + if (caster == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + const ESM::Static* fx = world->getStore().get() .search("VFX_Soul_Trap"); if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", mCreature.getRefData().getPosition().asVec3()); + world->spawnEffect("meshes\\" + fx->mModel, "", creature.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getSoundManager()->playSound3D( - mCreature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f - ); + MWBase::Environment::get().getSoundManager()->playSound3D(creature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f); + return; //remove to get vanilla behaviour } - }; - - void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) - { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - int slot = getBoundItemSlot(itemId); - - if (actor.getClass().getContainerStore(actor).count(itemId) != 0) - return; - - MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); - - MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); - MWWorld::ActionEquip action(boundPtr); - action.execute(actor); - - if (actor != MWMechanics::getPlayer()) - return; - - MWWorld::Ptr newItem; - auto it = store.getSlot(slot); - // Equip can fail because beast races cannot equip boots/helmets - if(it != store.end()) - newItem = *it; - - if (newItem.isEmpty() || boundPtr != newItem) - return; - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - - // change draw state only if the item is in player's right hand - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - player.setDrawState(MWMechanics::DrawState_Weapon); - - if (prevItem != store.end()) - player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); } +} +} - void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) - { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - int slot = getBoundItemSlot(itemId); - - MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); - - bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId); - - if (actor != MWMechanics::getPlayer()) - { - store.remove(itemId, 1, actor); - - // Equip a replacement - if (!wasEquipped) - return; - - std::string type = currentItem->getTypeName(); - if (type != typeid(ESM::Weapon).name() && type != typeid(ESM::Armor).name() && type != typeid(ESM::Clothing).name()) - return; - - if (actor.getClass().getCreatureStats(actor).isDead()) - return; - - if (!actor.getClass().hasInventoryStore(actor)) - return; - - if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) - return; - - actor.getClass().getInventoryStore(actor).autoEquip(actor); - - return; - } - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - std::string prevItemId = player.getPreviousItem(itemId); - player.erasePreviousItem(itemId); +namespace MWMechanics +{ + static const int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player + static const int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player + static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement + static const float DECELERATE_DISTANCE = 512.f; - if (!prevItemId.empty()) + namespace + { + float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents) { - // Find previous item (or its replacement) by id. - // we should equip previous item only if expired bound item was equipped. - MWWorld::Ptr item = store.findReplacement(prevItemId); - if (!item.isEmpty() && wasEquipped) - { - MWWorld::ActionEquip action(item); - action.execute(actor); - } + const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length(); + return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed; } - - store.remove(itemId, 1, actor); } void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { // magic effects - adjustMagicEffects (ptr); + adjustMagicEffects (ptr, duration); if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) calculateDynamicStats (ptr); @@ -793,23 +589,61 @@ namespace MWMechanics } } - void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) + void Actors::adjustMagicEffects (const MWWorld::Ptr& creature, float duration) { - CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); - if (creatureStats.isDeathAnimationFinished()) - return; + CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); + bool wasDead = creatureStats.isDead(); - MagicEffects now = creatureStats.getSpells().getMagicEffects(); + creatureStats.getActiveSpells().update(creature, duration); - if (creature.getClass().hasInventoryStore(creature)) + if (!wasDead && creatureStats.isDead()) { - MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature); - now += store.getMagicEffects(); + // The actor was killed by a magic effect. Figure out if the player was responsible for it. + const ActiveSpells& spells = creatureStats.getActiveSpells(); + MWWorld::Ptr player = getPlayer(); + std::set playerFollowers; + getActorsSidingWith(player, playerFollowers); + + for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + bool actorKilled = false; + + const ActiveSpells::ActiveSpellParams& spell = *it; + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.getCasterActorId()); + for (const auto& effect : spell.getEffects()) + { + static const std::array damageEffects{ + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, + ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth + }; + + bool isDamageEffect = std::find(damageEffects.begin(), damageEffects.end(), effect.mEffectId) != damageEffects.end(); + + if (isDamageEffect) + { + if (caster.getClass().isNpc() && caster.getClass().getNpcStats(caster).isWerewolf()) + caster.getClass().getNpcStats(caster).addWerewolfKill(); + if (caster == player || playerFollowers.find(caster) != playerFollowers.end()) + { + MWBase::Environment::get().getMechanicsManager()->actorKilled(creature, player); + actorKilled = true; + break; + } + } + } + + if (actorKilled) + break; + } } - now += creatureStats.getActiveSpells().getMagicEffects(); + // updateSummons assumes the actor belongs to a cell. + // This assumption isn't always valid for the player character. + if (!creature.isInCell()) + return; - creatureStats.modifyMagicEffects(now); + if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty()) + updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f); } void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) @@ -857,22 +691,18 @@ namespace MWMechanics if (stunted) { // Stunted Magicka effect should be taken into account. - GetStuntedMagickaDuration visitor(ptr); - stats.getActiveSpells().visitEffectSources(visitor); - stats.getSpells().visitEffectSources(visitor); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).visitEffectSources(visitor); + float remainingTime = getStuntedMagickaDuration(ptr); // Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours. - if (visitor.mRemainingTime > 0) + if (remainingTime > 0) { double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); if(timeScale == 0.0) timeScale = 1; - restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f); + restoreHours = std::max(0.0, hours - remainingTime * timeScale / 3600.f); } - else if (visitor.mRemainingTime == -1) + else if (remainingTime == -1) restoreHours = 0; } @@ -933,398 +763,12 @@ namespace MWMechanics stats.setFatigue (fatigue); } - class ExpiryVisitor : public EffectSourceVisitor - { - private: - MWWorld::Ptr mActor; - float mDuration; - - public: - ExpiryVisitor(const MWWorld::Ptr& actor, float duration) - : mActor(actor), mDuration(duration) - { - } - - void visit (MWMechanics::EffectKey key, int /*effectIndex*/, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float magnitude, float remainingTime = -1, float /*totalTime*/ = -1) override - { - if (magnitude > 0 && remainingTime > 0 && remainingTime < mDuration) - { - CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); - if (effectTick(creatureStats, mActor, key, magnitude * remainingTime)) - creatureStats.getMagicEffects().add(key, -magnitude); - } - } - }; - - void Actors::applyCureEffects(const MWWorld::Ptr& actor) - { - CreatureStats &creatureStats = actor.getClass().getCreatureStats(actor); - const MagicEffects &effects = creatureStats.getMagicEffects(); - - if (effects.get(ESM::MagicEffect::CurePoison).getModifier() > 0) - { - creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); - creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Poison); - if (actor.getClass().hasInventoryStore(actor)) - actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison); - } - if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) - { - creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); - creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze); - if (actor.getClass().hasInventoryStore(actor)) - actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze); - } - if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) - { - creatureStats.getSpells().purgeCommonDisease(); - } - if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) - { - creatureStats.getSpells().purgeBlightDisease(); - } - if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) - { - creatureStats.getActiveSpells().purgeCorprusDisease(); - creatureStats.getSpells().purgeCorprusDisease(); - if (actor.getClass().hasInventoryStore(actor)) - actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true); - } - if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) - { - creatureStats.getSpells().purgeCurses(); - } - } - void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); - const MagicEffects &effects = creatureStats.getMagicEffects(); - bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - applyCureEffects(ptr); - - bool wasDead = creatureStats.isDead(); - - if (duration > 0) - { - // Apply correct magnitude for tickable effects that have just expired, - // in case duration > remaining time of effect. - // One case where this will happen is when the player uses the rest/wait command - // while there is a tickable effect active that should expire before the end of the rest/wait. - ExpiryVisitor visitor(ptr, duration); - creatureStats.getActiveSpells().visitEffectSources(visitor); - - for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) - { - // tickable effects (i.e. effects having a lasting impact after expiry) - effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration); - - // instant effects are already applied on spell impact in spellcasting.cpp, but may also come from permanent abilities - if (it->second.getMagnitude() > 0) - { - CastSpell cast(ptr, ptr); - if (cast.applyInstantEffect(ptr, ptr, it->first, it->second.getMagnitude())) - { - creatureStats.getSpells().purgeEffect(it->first.mId); - creatureStats.getActiveSpells().purgeEffect(it->first.mId); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first.mId); - } - } - } - } - - // purge levitate effect if levitation is disabled - // check only modifier, because base value can be setted from SetFlying console command. - if (MWBase::Environment::get().getWorld()->isLevitationEnabled() == false && effects.get(ESM::MagicEffect::Levitate).getModifier() > 0) - { - creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Levitate); - creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Levitate); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).purgeEffect(ESM::MagicEffect::Levitate); - - if (ptr == getPlayer()) - { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); - } - } - - // dynamic stats - for (int i = 0; i < 3; ++i) - { - DynamicStat stat = creatureStats.getDynamic(i); - float fortify = effects.get(ESM::MagicEffect::FortifyHealth + i).getMagnitude(); - float drain = 0.f; - if (!godmode) - drain = effects.get(ESM::MagicEffect::DrainHealth + i).getMagnitude(); - stat.setCurrentModifier(fortify - drain, - // Magicka can be decreased below zero due to a fortify effect wearing off - // Fatigue can be decreased below zero meaning the actor will be knocked out - i == 1 || i == 2); - - creatureStats.setDynamic(i, stat); - } - - // attributes - for(int i = 0;i < ESM::Attribute::Length;++i) - { - AttributeValue stat = creatureStats.getAttribute(i); - float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude(); - float drain = 0.f, absorb = 0.f; - if (!godmode) - { - drain = effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude(); - absorb = effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude(); - } - stat.setModifier(static_cast(fortify - drain - absorb)); - - creatureStats.setAttribute(i, stat); - } if (creatureStats.needToRecalcDynamicStats()) calculateDynamicStats(ptr); - - if (ptr == getPlayer()) - { - GetCorprusSpells getCorprusSpellsVisitor; - creatureStats.getSpells().visitEffectSources(getCorprusSpellsVisitor); - creatureStats.getActiveSpells().visitEffectSources(getCorprusSpellsVisitor); - ptr.getClass().getInventoryStore(ptr).visitEffectSources(getCorprusSpellsVisitor); - std::vector corprusSpells = getCorprusSpellsVisitor.mSpells; - std::vector corprusSpellsToRemove; - - for (auto it = creatureStats.getCorprusSpells().begin(); it != creatureStats.getCorprusSpells().end(); ++it) - { - if(std::find(corprusSpells.begin(), corprusSpells.end(), it->first) == corprusSpells.end()) - { - // Corprus effect expired, remove entry and restore stats. - MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, it->first); - corprusSpellsToRemove.push_back(it->first); - corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); - continue; - } - - corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); - - if (MWBase::Environment::get().getWorld()->getTimeStamp() >= it->second.mNextWorsening) - { - it->second.mNextWorsening += CorprusStats::sWorseningPeriod; - GetCurrentMagnitudes getMagnitudesVisitor (it->first); - creatureStats.getSpells().visitEffectSources(getMagnitudesVisitor); - creatureStats.getActiveSpells().visitEffectSources(getMagnitudesVisitor); - ptr.getClass().getInventoryStore(ptr).visitEffectSources(getMagnitudesVisitor); - for (auto& effectMagnitude : getMagnitudesVisitor.mMagnitudes) - { - if (effectMagnitude.first.mId == ESM::MagicEffect::FortifyAttribute) - { - AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); - attr.damage(-effectMagnitude.second); - creatureStats.setAttribute(effectMagnitude.first.mArg, attr); - it->second.mWorsenings[effectMagnitude.first.mArg] = 0; - } - else if (effectMagnitude.first.mId == ESM::MagicEffect::DrainAttribute) - { - AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); - int currentDamage = attr.getDamage(); - if (currentDamage >= 0) - it->second.mWorsenings[effectMagnitude.first.mArg] = std::min(it->second.mWorsenings[effectMagnitude.first.mArg], currentDamage); - - it->second.mWorsenings[effectMagnitude.first.mArg] += effectMagnitude.second; - attr.damage(effectMagnitude.second); - creatureStats.setAttribute(effectMagnitude.first.mArg, attr); - } - } - - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); - } - } - - for (std::string& oldCorprusSpell : corprusSpellsToRemove) - { - creatureStats.removeCorprusSpell(oldCorprusSpell); - } - - for (std::string& newCorprusSpell : corprusSpells) - { - CorprusStats corprus; - for (int i=0; igetTimeStamp() + CorprusStats::sWorseningPeriod; - - creatureStats.addCorprusSpell(newCorprusSpell, corprus); - } - } - - // AI setting modifiers - int creature = !ptr.getClass().isNpc(); - Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Fight); - stat.setModifier(static_cast(effects.get(ESM::MagicEffect::FrenzyHumanoid + creature).getMagnitude() - - effects.get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude())); - creatureStats.setAiSetting(CreatureStats::AI_Fight, stat); - - stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); - stat.setModifier(static_cast(effects.get(ESM::MagicEffect::DemoralizeHumanoid + creature).getMagnitude() - - effects.get(ESM::MagicEffect::RallyHumanoid+creature).getMagnitude())); - creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); - if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Undead) - { - stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); - stat.setModifier(static_cast(effects.get(ESM::MagicEffect::TurnUndead).getMagnitude())); - creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); - } - - if (!wasDead && creatureStats.isDead()) - { - // The actor was killed by a magic effect. Figure out if the player was responsible for it. - const ActiveSpells& spells = creatureStats.getActiveSpells(); - MWWorld::Ptr player = getPlayer(); - std::set playerFollowers; - getActorsSidingWith(player, playerFollowers); - - for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) - { - bool actorKilled = false; - - const ActiveSpells::ActiveSpellParams& spell = it->second; - MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); - for (std::vector::const_iterator effectIt = spell.mEffects.begin(); - effectIt != spell.mEffects.end(); ++effectIt) - { - int effectId = effectIt->mEffectId; - bool isDamageEffect = false; - - int damageEffects[] = { - ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, - ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth - }; - - for (unsigned int i=0; iactorKilled(ptr, player); - actorKilled = true; - break; - } - } - } - - if (actorKilled) - break; - } - } - - // TODO: dirty flag for magic effects to avoid some unnecessary work below? - - // any value of calm > 0 will stop the actor from fighting - if ((effects.get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) - || (effects.get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) - creatureStats.getAiSequence().stopCombat(); - - // Update bound effects - // Note: in vanilla MW multiple bound items of the same type can be created by different spells. - // As these extra copies are kinda useless this may or may not be important. - static std::map boundItemsMap; - if (boundItemsMap.empty()) - { - boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID"; - boundItemsMap[ESM::MagicEffect::BoundBoots] = "sMagicBoundBootsID"; - boundItemsMap[ESM::MagicEffect::BoundCuirass] = "sMagicBoundCuirassID"; - boundItemsMap[ESM::MagicEffect::BoundDagger] = "sMagicBoundDaggerID"; - boundItemsMap[ESM::MagicEffect::BoundGloves] = "sMagicBoundLeftGauntletID"; // Note: needs RightGauntlet variant too (see below) - boundItemsMap[ESM::MagicEffect::BoundHelm] = "sMagicBoundHelmID"; - boundItemsMap[ESM::MagicEffect::BoundLongbow] = "sMagicBoundLongbowID"; - boundItemsMap[ESM::MagicEffect::BoundLongsword] = "sMagicBoundLongswordID"; - boundItemsMap[ESM::MagicEffect::BoundMace] = "sMagicBoundMaceID"; - boundItemsMap[ESM::MagicEffect::BoundShield] = "sMagicBoundShieldID"; - boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID"; - } - - if(ptr.getClass().hasInventoryStore(ptr)) - { - for (const auto& [effect, itemGmst] : boundItemsMap) - { - bool found = creatureStats.mBoundItems.find(effect) != creatureStats.mBoundItems.end(); - float magnitude = effects.get(effect).getMagnitude(); - if (found != (magnitude > 0)) - { - if (magnitude > 0) - creatureStats.mBoundItems.insert(effect); - else - creatureStats.mBoundItems.erase(effect); - - std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( - itemGmst)->mValue.getString(); - - magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); - - if (effect == ESM::MagicEffect::BoundGloves) - { - item = MWBase::Environment::get().getWorld()->getStore().get().find( - "sMagicBoundRightGauntletID")->mValue.getString(); - magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); - } - } - } - } - - // Summoned creature update visitor assumes the actor belongs to a cell. - // This assumption isn't always valid for the player character. - if (!ptr.isInCell()) - return; - - bool hasSummonEffect = false; - for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) - { - if (isSummoningEffect(it->first.mId)) - { - hasSummonEffect = true; - break; - } - } - - if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty() || hasSummonEffect) - { - UpdateSummonedCreatures updateSummonedCreatures(ptr); - creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures); - creatureStats.getSpells().visitEffectSources(updateSummonedCreatures); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures); - updateSummonedCreatures.process(mTimerDisposeSummonsCorpses == 0.f); - } - } - - void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration) - { - NpcStats &npcStats = ptr.getClass().getNpcStats(ptr); - const MagicEffects &effects = npcStats.getMagicEffects(); - bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - // skills - for(int i = 0;i < ESM::Skill::Length;++i) - { - SkillValue& skill = npcStats.getSkill(i); - float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude(); - float drain = 0.f, absorb = 0.f; - if (!godmode) - { - drain = effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude(); - absorb = effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude(); - } - skill.setModifier(static_cast(fortify - drain - absorb)); - } } bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) @@ -1979,8 +1423,6 @@ namespace MWMechanics player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); } - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); - const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration); // For dead actors we need to update looping spell particles @@ -1988,7 +1430,7 @@ namespace MWMechanics { // They can be added during the death animation if (!iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()) - adjustMagicEffects(iter->first); + adjustMagicEffects(iter->first, duration); ctrl->updateContinuousVfx(); } else @@ -2083,8 +1525,6 @@ namespace MWMechanics if (inProcessingRange) updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); - calculateNpcStatModifiers(iter->first, duration); - if (timerUpdateEquippedLight == 0) updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); } @@ -2251,10 +1691,7 @@ namespace MWMechanics // Apply soultrap if (iter->first.getTypeName() == typeid(ESM::Creature).name()) - { - SoulTrap soulTrap (iter->first); - stats.getActiveSpells().visitEffectSources(soulTrap); - } + soulTrap(iter->first); // Magic effects will be reset later, and the magic effect that could kill the actor // needs to be determined now @@ -2270,30 +1707,27 @@ namespace MWMechanics // Reset magic effects and recalculate derived effects // One case where we need this is to make sure bound items are removed upon death - stats.modifyMagicEffects(MWMechanics::MagicEffects()); - stats.getActiveSpells().clear(); + float vampirism = stats.getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude(); + stats.getActiveSpells().clear(iter->first); // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); // Reset dynamic stats, attributes and skills calculateCreatureStatModifiers(iter->first, 0); - if (iter->first.getClass().isNpc()) - calculateNpcStatModifiers(iter->first, 0); + stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism); if (isPlayer) { //player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); + // Play Death Music if it was the player dying + MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); } else { // NPC death animation is over, disable actor collision MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); } - - // Play Death Music if it was the player dying - if(iter->first == getPlayer()) - MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); } } } @@ -2313,7 +1747,7 @@ namespace MWMechanics // Remove the summoned creature's summoned creatures as well MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - std::map& creatureMap = stats.getSummonedCreatureMap(); + auto& creatureMap = stats.getSummonedCreatureMap(); for (const auto& creature : creatureMap) cleanupSummonedCreature(stats, creature.second); creatureMap.clear(); @@ -2334,7 +1768,7 @@ namespace MWMechanics for (PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { MWMechanics::ActiveSpells& spells = iter->first.getClass().getCreatureStats(iter->first).getActiveSpells(); - spells.purge(casterActorId); + spells.purge(iter->first, casterActorId); } } @@ -2352,7 +1786,7 @@ namespace MWMechanics { if (iter->first.getClass().getCreatureStats(iter->first).isDead()) { - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); + adjustMagicEffects (iter->first, duration); continue; } @@ -2363,15 +1797,11 @@ namespace MWMechanics (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) continue; - adjustMagicEffects (iter->first); + adjustMagicEffects (iter->first, duration); if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats()) calculateDynamicStats (iter->first); calculateCreatureStatModifiers (iter->first, duration); - if (iter->first.getClass().isNpc()) - calculateNpcStatModifiers(iter->first, duration); - - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first); if (animation) @@ -2760,10 +2190,8 @@ namespace MWMechanics void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) { - adjustMagicEffects(ptr); + adjustMagicEffects(ptr, 0.f); calculateCreatureStatModifiers(ptr, 0.f); - if (ptr.getClass().isNpc()) - calculateNpcStatModifiers(ptr, 0.f); } bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 171b45e270..3efe28204d 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -41,15 +41,11 @@ namespace MWMechanics { std::map mDeathCount; - void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); - void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); - - void adjustMagicEffects (const MWWorld::Ptr& creature); + void adjustMagicEffects (const MWWorld::Ptr& creature, float duration); void calculateDynamicStats (const MWWorld::Ptr& ptr); void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); - void calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateRestoration (const MWWorld::Ptr& ptr, float duration); @@ -208,7 +204,6 @@ namespace MWMechanics private: void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); - void applyCureEffects (const MWWorld::Ptr& actor); PtrActorMap mActors; float mTimerDisposeSummonsCorpses; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index c26454aab5..e6176c869c 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -208,14 +208,14 @@ namespace MWMechanics } } - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - float rating = rateSpell(it->first, actor, enemy); + float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; - bestAction.reset(new ActionSpell(it->first->mId)); - antiFleeRating = vanillaRateSpell(it->first, actor, enemy); + bestAction.reset(new ActionSpell(spell->mId)); + antiFleeRating = vanillaRateSpell(spell, actor, enemy); } } @@ -266,9 +266,9 @@ namespace MWMechanics } } - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - float rating = rateSpell(it->first, actor, enemy); + float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 8d99664f27..8ca6e8b766 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -545,20 +545,12 @@ namespace MWMechanics mAiSequence.writeState(state.mAiSequence); mMagicEffects.writeState(state.mMagicEffects); - state.mSummonedCreatureMap = mSummonedCreatures; + state.mSummonedCreatures = mSummonedCreatures; state.mSummonGraveyard = mSummonGraveyard; state.mHasAiSettings = true; for (int i=0; i<4; ++i) mAiSettings[i].writeState (state.mAiSettings[i]); - - for (auto it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) - { - for (int i=0; ifirst].mWorsenings[i] = mCorprusSpells.at(it->first).mWorsenings[i]; - - state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); - } } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -618,7 +610,7 @@ namespace MWMechanics // We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell) { - const auto& effects = spell.second.mEffects; + const auto& effects = spell.getEffects(); return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect) { return effect.mEffectId == effectId; @@ -629,21 +621,12 @@ namespace MWMechanics } } - mSummonedCreatures = state.mSummonedCreatureMap; + mSummonedCreatures = state.mSummonedCreatures; mSummonGraveyard = state.mSummonGraveyard; if (state.mHasAiSettings) for (int i=0; i<4; ++i) mAiSettings[i].readState(state.mAiSettings[i]); - - mCorprusSpells.clear(); - for (auto it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) - { - for (int i=0; ifirst].mWorsenings[i] = state.mCorprusSpells.at(it->first).mWorsenings[i]; - - mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - } } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) @@ -710,7 +693,7 @@ namespace MWMechanics return mTimeOfDeath; } - std::map& CreatureStats::getSummonedCreatureMap() + std::multimap& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } @@ -719,23 +702,4 @@ namespace MWMechanics { return mSummonGraveyard; } - - std::map &CreatureStats::getCorprusSpells() - { - return mCorprusSpells; - } - - void CreatureStats::addCorprusSpell(const std::string& sourceId, CorprusStats& stats) - { - mCorprusSpells[sourceId] = stats; - } - - void CreatureStats::removeCorprusSpell(const std::string& sourceId) - { - auto corprusIt = mCorprusSpells.find(sourceId); - if (corprusIt != mCorprusSpells.end()) - { - mCorprusSpells.erase(corprusIt); - } - } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 06d526cc5a..d234d14486 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWMECHANICS_CREATURESTATS_H #define GAME_MWMECHANICS_CREATURESTATS_H +#include #include #include #include @@ -87,14 +88,12 @@ namespace MWMechanics float mSideMovementAngle; private: - std::map mSummonedCreatures; // + std::multimap mSummonedCreatures; // // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. // This may be necessary when the creature is in an inactive cell. std::vector mSummonGraveyard; - std::map mCorprusSpells; - protected: int mLevel; @@ -236,7 +235,7 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; - std::map& getSummonedCreatureMap(); // + std::multimap& getSummonedCreatureMap(); // std::vector& getSummonedCreatureGraveyard(); // ActorIds enum Flag @@ -297,12 +296,6 @@ namespace MWMechanics static void cleanup(); - std::map & getCorprusSpells(); - - void addCorprusSpell(const std::string& sourceId, CorprusStats& stats); - - void removeCorprusSpell(const std::string& sourceId); - float getSideMovementAngle() const { return mSideMovementAngle; } void setSideMovementAngle(float angle) { mSideMovementAngle = angle; } }; diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 5d4bfb682f..426a66ecf2 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -30,12 +30,11 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find( "fDiseaseXferChance")->mValue.getFloat(); - MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); + const MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId)) continue; @@ -56,7 +55,7 @@ namespace MWMechanics if (Misc::Rng::rollDice(10000) < x) { // Contracted disease! - actor.getClass().getCreatureStats(actor).getSpells().add(it->first); + actor.getClass().getCreatureStats(actor).getSpells().add(spell); MWBase::Environment::get().getWorld()->applyLoopingParticles(actor); std::string msg = "sMagicContractDisease"; diff --git a/apps/openmw/mwmechanics/linkedeffects.cpp b/apps/openmw/mwmechanics/linkedeffects.cpp deleted file mode 100644 index b0defac7d2..0000000000 --- a/apps/openmw/mwmechanics/linkedeffects.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "linkedeffects.hpp" - -#include -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwrender/animation.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "creaturestats.hpp" - -namespace MWMechanics -{ - - bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects) - { - if (caster.isEmpty() || caster == target || !target.getClass().isActor()) - return false; - - bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; - bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable; - if (!isHarmful || isUnreflectable) - return false; - - float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); - if (Misc::Rng::roll0to99() >= reflect) - return false; - - const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation && !reflectStatic->mModel.empty()) - animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string()); - reflectedEffects.mList.emplace_back(effect); - return true; - } - - void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source) - { - if (caster.isEmpty() || caster == target) - return; - - if (!target.getClass().isActor() || !caster.getClass().isActor()) - return; - - // Make sure callers don't do something weird - if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill) - throw std::runtime_error("invalid absorb stat effect"); - - if (appliedEffect.mMagnitude == 0) - return; - - std::vector absorbEffects; - ActiveSpells::ActiveEffect absorbEffect = appliedEffect; - absorbEffect.mMagnitude *= -1; - absorbEffect.mEffectIndex = appliedEffect.mEffectIndex; - absorbEffects.emplace_back(absorbEffect); - - // Morrowind negates reflected Absorb spells so the original caster won't be harmed. - if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game")) - { - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true, - absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId()); - return; - } - - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true, - absorbEffects, source, target.getClass().getCreatureStats(target).getActorId()); - } -} diff --git a/apps/openmw/mwmechanics/linkedeffects.hpp b/apps/openmw/mwmechanics/linkedeffects.hpp deleted file mode 100644 index a6dea2a3a2..0000000000 --- a/apps/openmw/mwmechanics/linkedeffects.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef MWMECHANICS_LINKEDEFFECTS_H -#define MWMECHANICS_LINKEDEFFECTS_H - -#include - -namespace ESM -{ - struct ActiveEffect; - struct EffectList; - struct ENAMstruct; - struct MagicEffect; - struct Spell; -} - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - - // Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list. - bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects); - - // Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster. - void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source); -} - -#endif diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 1a1a44f638..e75361628b 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -193,22 +193,22 @@ namespace MWMechanics void MagicEffects::writeState(ESM::MagicEffects &state) const { - // Don't need to save Modifiers, they are recalculated every frame anyway. - for (Collection::const_iterator iter (begin()); iter!=end(); ++iter) + for (const auto& [key, params] : mCollection) { - if (iter->second.getBase() != 0) + if (params.getBase() != 0 || params.getModifier() != 0.f) { // Don't worry about mArg, never used by magic effect script instructions - state.mEffects.insert(std::make_pair(iter->first.mId, iter->second.getBase())); + state.mEffects[key.mId] = {params.getBase(), params.getModifier()}; } } } void MagicEffects::readState(const ESM::MagicEffects &state) { - for (std::map::const_iterator it = state.mEffects.begin(); it != state.mEffects.end(); ++it) + for (const auto& [key, params] : state.mEffects) { - mCollection[EffectKey(it->first)].setBase(it->second); + mCollection[EffectKey(key)].setBase(params.first); + mCollection[EffectKey(key)].setModifier(params.second); } } } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 12735a87fc..50f4dab050 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -69,16 +69,6 @@ namespace MWMechanics return param -= right; } - // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display - struct EffectSourceVisitor - { - virtual ~EffectSourceVisitor() { } - - virtual void visit (EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) = 0; - }; - /// \brief Effects currently affecting a NPC or creature class MagicEffects { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 24601d79c8..fb8b3fa2b0 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -84,7 +84,7 @@ namespace MWMechanics // reset creatureStats.setLevel(player->mNpdt.mLevel); creatureStats.getSpells().clear(true); - creatureStats.modifyMagicEffects(MagicEffects()); + creatureStats.getActiveSpells().clear(ptr); for (int i=0; i<27; ++i) npcStats.getSkill (i).setBase (player->mNpdt.mSkills[i]); @@ -213,6 +213,7 @@ namespace MWMechanics int attributes[ESM::Attribute::Length]; for (int i=0; i selectedSpells = autoCalcPlayerSpells(skills, attributes, race); @@ -221,6 +222,7 @@ namespace MWMechanics // forced update and current value adjustments mActors.updateActor (ptr, 0); + mActors.updateActor (ptr, 0); for (int i=0; i<3; ++i) { @@ -282,24 +284,6 @@ namespace MWMechanics mObjects.dropObjects(cellStore); } - void MechanicsManager::restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) - { - auto& stats = actor.getClass().getCreatureStats (actor); - auto& corprusSpells = stats.getCorprusSpells(); - - auto corprusIt = corprusSpells.find(sourceId); - - if (corprusIt != corprusSpells.end()) - { - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - MWMechanics::AttributeValue attr = stats.getAttribute(i); - attr.restore(corprusIt->second.mWorsenings[i]); - actor.getClass().getCreatureStats(actor).setAttribute(i, attr); - } - } - } - void MechanicsManager::update(float duration, bool paused) { // Note: we should do it here since game mechanics and world updates use these values diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 0aaeb23435..206b5c5420 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -230,8 +230,6 @@ namespace MWMechanics GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override; bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override; - void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) override; - private: bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set &playerFollowers); diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp index cb80921b33..82531132ca 100644 --- a/apps/openmw/mwmechanics/spellabsorption.cpp +++ b/apps/openmw/mwmechanics/spellabsorption.cpp @@ -16,33 +16,30 @@ namespace MWMechanics { - - class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor + float getProbability(const MWMechanics::ActiveSpells& activeSpells) { - public: - float mProbability{0.f}; - - GetAbsorptionProbability() = default; - - void visit (MWMechanics::EffectKey key, int /*effectIndex*/, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float magnitude, float /*remainingTime*/, float /*totalTime*/) override + float probability = 0.f; + for(const auto& params : activeSpells) { - if (key.mId == ESM::MagicEffect::SpellAbsorption) + for(const auto& effect : params.getEffects()) { - if (mProbability == 0.f) - mProbability = magnitude / 100; - else + if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption) { - // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. - // Real absorption probability will be the (1 - total fail chance) in this case. - float failProbability = 1.f - mProbability; - failProbability *= 1.f - magnitude / 100; - mProbability = 1.f - failProbability; + if(probability == 0.f) + probability = effect.mMagnitude / 100; + else + { + // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. + // Real absorption probability will be the (1 - total fail chance) in this case. + float failProbability = 1.f - probability; + failProbability *= 1.f - effect.mMagnitude / 100; + probability = 1.f - failProbability; + } } } } - }; + return static_cast(probability * 100); + } bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) { @@ -53,13 +50,7 @@ namespace MWMechanics if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f) return false; - GetAbsorptionProbability check; - stats.getActiveSpells().visitEffectSources(check); - stats.getSpells().visitEffectSources(check); - if (target.getClass().hasInventoryStore(target)) - target.getClass().getInventoryStore(target).visitEffectSources(check); - - int chance = check.mProbability * 100; + int chance = getProbability(stats.getActiveSpells()); if (Misc::Rng::roll0to99() >= chance) return false; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 502e56edf7..53fbe69ab3 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -22,14 +22,38 @@ #include "actorutil.hpp" #include "aifollow.hpp" #include "creaturestats.hpp" -#include "linkedeffects.hpp" #include "spellabsorption.hpp" -#include "spellresistance.hpp" +#include "spelleffects.hpp" #include "spellutil.hpp" #include "summoning.hpp" -#include "tickableeffects.hpp" #include "weapontype.hpp" +namespace +{ + bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, + const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects) + { + if (caster.isEmpty() || caster == target || !target.getClass().isActor()) + return false; + + bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; + bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable; + if (!isHarmful || isUnreflectable) + return false; + + float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); + if (Misc::Rng::roll0to99() >= reflect) + return false; + + const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + if (animation && !reflectStatic->mModel.empty()) + animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string()); + reflectedEffects.mList.emplace_back(effect); + return true; + } +} + namespace MWMechanics { CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) @@ -54,7 +78,7 @@ namespace MWMechanics (mTarget.getRefData().getPosition().asVec3() + offset) - (mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, @@ -100,20 +124,13 @@ namespace MWMechanics } ESM::EffectList reflectedEffects; - std::vector appliedLastingEffects; - - // HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance. - // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. - // Otherwise, they'd only apply after the whole spell was added. - MagicEffects targetEffects; - if (targetIsActor) - targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); + ActiveSpells::ActiveSpellParams params(*this, caster); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); - ActiveSpells targetSpells; + const ActiveSpells* targetSpells = nullptr; if (targetIsActor) - targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); + targetSpells = &target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false // throughout the iteration of this spell's @@ -134,7 +151,7 @@ namespace MWMechanics effectIt->mEffectID); // Re-casting a bound equipment effect has no effect if the spell is still active - if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells.isSpellActive(mId)) + if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells && targetSpells->isSpellActive(mId)) { if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); @@ -163,307 +180,70 @@ namespace MWMechanics if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) continue; - // Try resisting. - float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); - if (magnitudeMult == 0) + ActiveSpells::ActiveEffect effect; + effect.mEffectId = effectIt->mEffectID; + effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = effectIt->mMagnMin; + effect.mMaxMagnitude = effectIt->mMagnMax; + effect.mTimeLeft = 0.f; + effect.mEffectIndex = currentEffectIndex; + effect.mFlags = ESM::ActiveEffect::Flag_None; + + // Avoid applying harmful effects to the player in god mode + if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) { - // Fully resisted, show message - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + effect.mMinMagnitude = 0; + effect.mMaxMagnitude = 0; } - else - { - float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1); - magnitude *= magnitudeMult; - if (!target.getClass().isActor()) - { - // non-actor objects have no list of active magic effects, so have to apply instantly - if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude)) - continue; - } - else // target.getClass().isActor() == true - { - ActiveSpells::ActiveEffect effect; - effect.mEffectId = effectIt->mEffectID; - effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; - effect.mMagnitude = magnitude; - effect.mTimeLeft = 0.f; - effect.mEffectIndex = currentEffectIndex; - - // Avoid applying absorb effects if the caster is the target - // We still need the spell to be added - if (caster == target - && effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute - && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill) - { - effect.mMagnitude = 0; - } + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; - // Avoid applying harmful effects to the player in god mode - if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) - { - effect.mMagnitude = 0; - } + bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; + if (!appliedOnce) + effect.mDuration = std::max(1.f, effect.mDuration); - bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; - if (castByPlayer && target != caster && !target.getClass().getCreatureStats(target).isDead() && effectAffectsHealth) - { - // If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar. - MWBase::Environment::get().getWindowManager()->setEnemy(target); - } - - bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; + effect.mTimeLeft = effect.mDuration; - bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; - if (!appliedOnce) - effect.mDuration = std::max(1.f, effect.mDuration); + // add to list of active effects, to apply in next frame + params.getEffects().emplace_back(effect); - if (effect.mDuration == 0) - { - // We still should add effect to list to allow GetSpellEffects to detect this spell - appliedLastingEffects.push_back(effect); - - // duration 0 means apply full magnitude instantly - bool wasDead = target.getClass().getCreatureStats(target).isDead(); - effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude); - bool isDead = target.getClass().getCreatureStats(target).isDead(); - - if (!wasDead && isDead) - MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster); - } - else - { - effect.mTimeLeft = effect.mDuration; - - targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); - - // add to list of active effects, to apply in next frame - appliedLastingEffects.push_back(effect); - - // Unequip all items, if a spell with the ExtraSpell effect was casted - if (effectIt->mEffectID == ESM::MagicEffect::ExtraSpell && target.getClass().hasInventoryStore(target)) - { - MWWorld::InventoryStore& store = target.getClass().getInventoryStore(target); - store.unequipAll(target); - } - - // Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target - if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc()) - || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) - && !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) - { - MWMechanics::AiFollow package(caster, true); - target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); - } - - // For absorb effects, also apply the effect to the caster - but with a negative - // magnitude, since we're transferring stats from the target to the caster - if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill) - absorbStat(*effectIt, effect, caster, target, reflected, mSourceName); - } - } - - // Re-casting a summon effect will remove the creature from previous castings of that effect. - if (isSummoningEffect(effectIt->mEffectID) && targetIsActor) - { - CreatureStats& targetStats = target.getClass().getCreatureStats(target); - ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex); - auto findCreature = targetStats.getSummonedCreatureMap().find(key); - if (findCreature != targetStats.getSummonedCreatureMap().end()) - { - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, findCreature->second); - targetStats.getSummonedCreatureMap().erase(findCreature); - } - } - - if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; + bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; + if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth) + { + // If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar. + MWBase::Environment::get().getWindowManager()->setEnemy(target); + } - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - - // Add VFX - const ESM::Static* castStatic; - if (!magicEffect->mHit.empty()) - castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - else - castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); - - bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; - // Note: in case of non actor, a free effect should be fine as well - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); - if (anim && !castStatic->mModel.empty()) - anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); - } + if (targetIsActor || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + { + playEffects(target, *magicEffect); } } if (!exploded) - MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); + MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile, mSlot); if (!target.isEmpty()) { if (!reflectedEffects.mList.empty()) inflict(caster, target, reflectedEffects, range, true, exploded); - if (!appliedLastingEffects.empty()) - { - int casterActorId = -1; - if (!caster.isEmpty() && caster.getClass().isActor()) - casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, casterActorId); - } - } - } - - bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) - { - short effectId = effect.mId; - if (target.getClass().canLock(target)) - { - if (effectId == ESM::MagicEffect::Lock) - { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::MagicEffect *magiceffect = store.get().find(effectId); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation) - animation->addSpellCastGlow(magiceffect); - if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude - { - if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); - target.getCellRef().lock(static_cast(magnitude)); - } - return true; - } - else if (effectId == ESM::MagicEffect::Open) + if (!params.getEffects().empty()) { - if (!caster.isEmpty()) - { - MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target); - // Use the player instead of the caster for vanilla crime compatibility - } - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::MagicEffect *magiceffect = store.get().find(effectId); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation) - animation->addSpellCastGlow(magiceffect); - if (target.getCellRef().getLockLevel() <= magnitude) - { - if (target.getCellRef().getLockLevel() > 0) - { - MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); - - if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); - } - target.getCellRef().unlock(); - } + if(targetIsActor) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); else { - MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + // Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway + for(auto& effect : params.getEffects()) + applyMagicEffect(target, caster, params, effect, 0.f); } - - return true; - } - } - else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel) - { - target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude, true); - return true; - } - else if(target.getClass().isActor() && effectId >= ESM::MagicEffect::CalmHumanoid && effectId <= ESM::MagicEffect::RallyCreature) - { - // Treat X Humanoid spells on creatures and X Creature spells on NPCs as instant effects and remove their VFX - bool affectsCreatures = (effectId - ESM::MagicEffect::CalmHumanoid) & 1; - if(affectsCreatures == target.getClass().isNpc()) - { - MWBase::Environment::get().getWorld()->getAnimation(target)->removeEffect(effectId); - return true; - } - } - else if(target.getClass().isActor() && effectId == ESM::MagicEffect::TurnUndead) - { - // Diverge from vanilla by giving scripts a chance to detect Turn Undead on non-undead, but still remove the effect and VFX - if(target.getClass().isNpc() || target.get()->mBase->mData.mType != ESM::Creature::Undead) - { - MWBase::Environment::get().getWorld()->getAnimation(target)->removeEffect(effectId); - return true; } } - else if (target.getClass().isActor() && target == getPlayer()) - { - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - bool teleportingEnabled = MWBase::Environment::get().getWorld()->isTeleportingEnabled(); - - if (effectId == ESM::MagicEffect::DivineIntervention || effectId == ESM::MagicEffect::AlmsiviIntervention) - { - if (!teleportingEnabled) - { - if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - return true; - } - std::string marker = (effectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; - MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, marker); - anim->removeEffect(effectId); - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_end"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1); - return true; - } - else if (effectId == ESM::MagicEffect::Mark) - { - if (teleportingEnabled) - { - MWBase::Environment::get().getWorld()->getPlayer().markPosition( - target.getCell(), target.getRefData().getPosition()); - } - else if (caster == getPlayer()) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - } - return true; - } - else if (effectId == ESM::MagicEffect::Recall) - { - if (!teleportingEnabled) - { - if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - return true; - } - - MWWorld::CellStore* markedCell = nullptr; - ESM::Position markedPosition; - - MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); - if (markedCell) - { - MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, - markedPosition, false); - action.execute(target); - anim->removeEffect(effectId); - } - return true; - } - } - return false; } - bool CastSpell::cast(const std::string &id) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); @@ -479,7 +259,7 @@ namespace MWMechanics throw std::runtime_error("ID type cannot be casted"); } - bool CastSpell::cast(const MWWorld::Ptr &item, bool launchProjectile) + bool CastSpell::cast(const MWWorld::Ptr &item, int slot, bool launchProjectile) { std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) @@ -490,7 +270,7 @@ namespace MWMechanics const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); - mStack = false; + mSlot = slot; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool isProjectile = false; @@ -570,7 +350,7 @@ namespace MWMechanics { mSourceName = potion->mName; mId = potion->mId; - mStack = true; + mType = ESM::ActiveSpells::Type_Consumable; inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); @@ -581,7 +361,6 @@ namespace MWMechanics { mSourceName = spell->mName; mId = spell->mId; - mStack = false; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); @@ -656,7 +435,7 @@ namespace MWMechanics bool CastSpell::cast (const ESM::Ingredient* ingredient) { mId = ingredient->mId; - mStack = true; + mType = ESM::ActiveSpells::Type_Consumable; mSourceName = ingredient->mName; ESM::ENAMstruct effect; @@ -799,4 +578,36 @@ namespace MWMechanics sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); } } + + void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping) + { + if (playNonLooping) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect.mHitSound.empty()) + sndMgr->playSound3D(target, magicEffect.mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(target, schools[magicEffect.mData.mSchool]+" hit", 1.0f, 1.0f); + } + + // Add VFX + const ESM::Static* castStatic; + if (!magicEffect.mHit.empty()) + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect.mHit); + else + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); + + bool loop = (magicEffect.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); + if(anim && !castStatic->mModel.empty()) + { + // Don't play particle VFX unless the effect is new or it should be looping. + if (playNonLooping || loop) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect.mIndex, loop, "", magicEffect.mParticle); + } + } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index cc525d2db6..a21ea33e0b 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -1,6 +1,7 @@ #ifndef MWMECHANICS_SPELLCASTING_H #define MWMECHANICS_SPELLCASTING_H +#include #include #include "../mwworld/ptr.hpp" @@ -11,6 +12,7 @@ namespace ESM struct Ingredient; struct Potion; struct EffectList; + struct MagicEffect; } namespace MWMechanics @@ -26,13 +28,14 @@ namespace MWMechanics void playSpellCastingEffects(const std::vector& effects); public: - bool mStack{false}; std::string mId; // ID of spell, potion, item etc std::string mSourceName; // Display name for spell, potion, etc osg::Vec3f mHitPosition{0,0,0}; // Used for spawning area orb bool mAlwaysSucceed{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.) + int mSlot{0}; + ESM::ActiveSpells::EffectType mType{ESM::ActiveSpells::Type_Temporary}; public: CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false); @@ -41,7 +44,7 @@ namespace MWMechanics /// @note mCaster must be an actor /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched as projectile originating from the caster. - bool cast (const MWWorld::Ptr& item, bool launchProjectile=true); + bool cast (const MWWorld::Ptr& item, int slot, bool launchProjectile=true); /// @note mCaster must be an NPC bool cast (const ESM::Ingredient* ingredient); @@ -60,11 +63,9 @@ namespace MWMechanics /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); - - /// @note \a caster can be any type of object, or even an empty object. - /// @return was the target suitable for the effect? - bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; + + void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true); } #endif diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp new file mode 100644 index 0000000000..895e908bd2 --- /dev/null +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -0,0 +1,1032 @@ +#include "spelleffects.hpp" + +#include + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/aifollow.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellresistance.hpp" +#include "../mwmechanics/summoning.hpp" + +#include "../mwrender/animation.hpp" + +#include "../mwworld/actionequip.hpp" +#include "../mwworld/actionteleport.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" + +namespace +{ + float roll(const ESM::ActiveEffect& effect) + { + if(effect.mMinMagnitude == effect.mMaxMagnitude) + return effect.mMinMagnitude; + return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1); + } + + void modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, ESM::MagicEffect::Effects creatureEffect, MWMechanics::CreatureStats::AiSetting setting, float magnitude, bool& invalid) + { + if(target == MWMechanics::getPlayer() || (effect.mEffectId == creatureEffect) == target.getClass().isNpc()) + invalid = true; + else + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getAiSetting(setting); + stat.setModifier(static_cast(stat.getModifier() - magnitude)); + creatureStats.setAiSetting(setting, stat); + } + } + + void adjustDynamicStat(const MWWorld::Ptr& target, int index, float magnitude, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getDynamic(index); + stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero, allowIncreaseAboveModified); + creatureStats.setDynamic(index, stat); + } + + void modDynamicStat(const MWWorld::Ptr& target, int index, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getDynamic(index); + float current = stat.getCurrent(); + stat.setModified(stat.getModified() - magnitude, 0); + stat.setCurrentModified(stat.getCurrentModified() - magnitude); + stat.setCurrent(current - magnitude); + creatureStats.setDynamic(index, stat); + } + + void damageAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attr = creatureStats.getAttribute(effect.mArg); + attr.damage(magnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + + void restoreAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attr = creatureStats.getAttribute(effect.mArg); + attr.restore(magnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + + void fortifyAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attr = creatureStats.getAttribute(effect.mArg); + attr.setModifier(attr.getModifier() + magnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + + void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.damage(magnitude); + } + + void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.restore(magnitude); + } + + void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.setModifier(skill.getModifier() + magnitude); + } + + bool disintegrateSlot(const MWWorld::Ptr& ptr, int slot, float disintegrate) + { + MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStoreIterator item = inv.getSlot(slot); + + if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) + { + if (!item->getClass().hasItemHealth(*item)) + return false; + int charge = item->getClass().getItemHealth(*item); + if (charge == 0) + return false; + + // Store remainder of disintegrate amount (automatically subtracted if > 1) + item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); + + charge = item->getClass().getItemHealth(*item); + charge -= std::min(static_cast(disintegrate), charge); + item->getCellRef().setCharge(charge); + + if (charge == 0) + { + // Will unequip the broken item and try to find a replacement + if (ptr != MWMechanics::getPlayer()) + inv.autoEquip(ptr); + else + inv.unequipItem(*item, ptr); + } + + return true; + } + + return false; + } + + int getBoundItemSlot(const MWWorld::Ptr& boundPtr) + { + const auto [slots, _] = boundPtr.getClass().getEquipmentSlots(boundPtr); + if(!slots.empty()) + return slots[0]; + return -1; + } + + void addBoundItem(const std::string& itemId, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); + + int slot = getBoundItemSlot(boundPtr); + auto prevItem = slot >= 0 ? store.getSlot(slot) : store.end(); + + MWWorld::ActionEquip action(boundPtr); + action.execute(actor); + + if (actor != MWMechanics::getPlayer()) + return; + + MWWorld::Ptr newItem; + auto it = slot >= 0 ? store.getSlot(slot) : store.end(); + // Equip can fail because beast races cannot equip boots/helmets + if(it != store.end()) + newItem = *it; + + if (newItem.isEmpty() || boundPtr != newItem) + return; + + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + + // change draw state only if the item is in player's right hand + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + player.setDrawState(MWMechanics::DrawState_Weapon); + + if (prevItem != store.end()) + player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); + } + + void removeBoundItem(const std::string& itemId, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + auto item = std::find_if(store.begin(), store.end(), [&] (const auto& it) + { + return Misc::StringUtils::ciEqual(it.getCellRef().getRefId(), itemId); + }); + if(item == store.end()) + return; + int slot = getBoundItemSlot(*item); + + auto currentItem = store.getSlot(slot); + + bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId); + + if (actor != MWMechanics::getPlayer()) + { + store.remove(itemId, 1, actor); + + // Equip a replacement + if (!wasEquipped) + return; + + std::string type = currentItem->getTypeName(); + if (type != typeid(ESM::Weapon).name() && type != typeid(ESM::Armor).name() && type != typeid(ESM::Clothing).name()) + return; + + if (actor.getClass().getCreatureStats(actor).isDead()) + return; + + if (!actor.getClass().hasInventoryStore(actor)) + return; + + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + return; + + actor.getClass().getInventoryStore(actor).autoEquip(actor); + + return; + } + + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + std::string prevItemId = player.getPreviousItem(itemId); + player.erasePreviousItem(itemId); + + if (!prevItemId.empty() && wasEquipped) + { + // Find previous item (or its replacement) by id. + // we should equip previous item only if expired bound item was equipped. + MWWorld::Ptr prevItem = store.findReplacement(prevItemId); + if (!prevItem.isEmpty()) + { + MWWorld::ActionEquip action(prevItem); + action.execute(actor); + } + } + + store.remove(itemId, 1, actor); + } + + bool isCorprusEffect(const MWMechanics::ActiveSpells::ActiveEffect& effect, bool harmfulOnly = false) + { + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && effect.mEffectId != ESM::MagicEffect::Corprus) + { + const auto* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectId); + if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce && (!harmfulOnly || magicEffect->mData.mFlags & ESM::MagicEffect::Flags::Harmful)) + return true; + } + return false; + } + + static const std::map sBoundItemsMap{ + {ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"}, + {ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"}, + {ESM::MagicEffect::BoundCuirass, "sMagicBoundCuirassID"}, + {ESM::MagicEffect::BoundDagger, "sMagicBoundDaggerID"}, + {ESM::MagicEffect::BoundGloves, "sMagicBoundLeftGauntletID"}, + {ESM::MagicEffect::BoundHelm, "sMagicBoundHelmID"}, + {ESM::MagicEffect::BoundLongbow, "sMagicBoundLongbowID"}, + {ESM::MagicEffect::BoundLongsword, "sMagicBoundLongswordID"}, + {ESM::MagicEffect::BoundMace, "sMagicBoundMaceID"}, + {ESM::MagicEffect::BoundShield, "sMagicBoundShieldID"}, + {ESM::MagicEffect::BoundSpear, "sMagicBoundSpearID"} + }; +} + +namespace MWMechanics +{ + +void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage) +{ + const auto world = MWBase::Environment::get().getWorld(); + bool godmode = target == getPlayer() && world->getGodModeState(); + switch(effect.mEffectId) + { + case ESM::MagicEffect::CureCommonDisease: + target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); + break; + case ESM::MagicEffect::CureBlightDisease: + target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); + break; + case ESM::MagicEffect::RemoveCurse: + target.getClass().getCreatureStats(target).getSpells().purgeCurses(); + break; + case ESM::MagicEffect::CureCorprusDisease: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Corprus); + break; + case ESM::MagicEffect::CurePoison: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Poison); + break; + case ESM::MagicEffect::CureParalyzation: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Paralyze); + break; + case ESM::MagicEffect::Dispel: + // 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) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(params.getId()); + if(spell && spell->mData.mType == ESM::Spell::ST_Spell) + return Misc::Rng::roll0to99() < magnitude; + } + return false; + }, target); + break; + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + { + auto marker = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; + world->teleportToClosestMarker(target, marker); + if(!caster.isEmpty()) + { + MWRender::Animation* anim = world->getAnimation(caster); + anim->removeEffect(effect.mEffectId); + const ESM::Static* fx = world->getStore().get().search("VFX_Summon_end"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1); + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::Mark: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition()); + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::Recall: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + { + MWWorld::CellStore* markedCell = nullptr; + ESM::Position markedPosition; + + world->getPlayer().getMarkedPosition(markedCell, markedPosition); + if (markedCell) + { + MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false); + action.execute(target); + if(!caster.isEmpty()) + { + MWRender::Animation* anim = world->getAnimation(caster); + anim->removeEffect(effect.mEffectId); + } + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::CommandCreature: + case ESM::MagicEffect::CommandHumanoid: + if(caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc()) + invalid = true; + else if(effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) + { + MWMechanics::AiFollow package(caster, true); + target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); + } + break; + case ESM::MagicEffect::ExtraSpell: + if(target.getClass().hasInventoryStore(target)) + { + auto& store = target.getClass().getInventoryStore(target); + store.unequipAll(target); + } + else + invalid = true; + break; + case ESM::MagicEffect::TurnUndead: + if(target.getClass().isNpc() || target.get()->mBase->mData.mType != ESM::Creature::Undead) + invalid = true; + else + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat.setModifier(static_cast(stat.getModifier() + effect.mMagnitude)); + creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); + } + break; + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::FrenzyHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::CalmHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid); + if(!invalid && effect.mMagnitude > 0) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + creatureStats.getAiSequence().stopCombat(); + } + break; + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, CreatureStats::AI_Flee, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::RallyCreature: + case ESM::MagicEffect::RallyHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, CreatureStats::AI_Flee, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::SummonScamp: + case ESM::MagicEffect::SummonClannfear: + case ESM::MagicEffect::SummonDaedroth: + case ESM::MagicEffect::SummonDremora: + case ESM::MagicEffect::SummonAncestralGhost: + case ESM::MagicEffect::SummonSkeletalMinion: + case ESM::MagicEffect::SummonBonewalker: + case ESM::MagicEffect::SummonGreaterBonewalker: + case ESM::MagicEffect::SummonBonelord: + case ESM::MagicEffect::SummonWingedTwilight: + case ESM::MagicEffect::SummonHunger: + case ESM::MagicEffect::SummonGoldenSaint: + case ESM::MagicEffect::SummonFlameAtronach: + case ESM::MagicEffect::SummonFrostAtronach: + case ESM::MagicEffect::SummonStormAtronach: + case ESM::MagicEffect::SummonCenturionSphere: + case ESM::MagicEffect::SummonFabricant: + case ESM::MagicEffect::SummonWolf: + case ESM::MagicEffect::SummonBear: + case ESM::MagicEffect::SummonBonewolf: + case ESM::MagicEffect::SummonCreature04: + case ESM::MagicEffect::SummonCreature05: + if(!target.isInCell()) + invalid = true; + else + effect.mArg = summonCreature(effect.mEffectId, target); + break; + case ESM::MagicEffect::BoundGloves: + if(!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + addBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); + // left gauntlet added below + case ESM::MagicEffect::BoundDagger: + case ESM::MagicEffect::BoundLongsword: + case ESM::MagicEffect::BoundMace: + case ESM::MagicEffect::BoundBattleAxe: + case ESM::MagicEffect::BoundSpear: + case ESM::MagicEffect::BoundLongbow: + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundHelm: + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundShield: + if(!target.getClass().hasInventoryStore(target)) + invalid = true; + else + { + const std::string& item = sBoundItemsMap.at(effect.mEffectId); + addBoundItem(world->getStore().get().find(item)->mValue.getString(), target); + } + break; + case ESM::MagicEffect::FireDamage: + case ESM::MagicEffect::ShockDamage: + case ESM::MagicEffect::FrostDamage: + case ESM::MagicEffect::DamageHealth: + case ESM::MagicEffect::Poison: + case ESM::MagicEffect::DamageMagicka: + case ESM::MagicEffect::DamageFatigue: + if(!godmode) + { + int index = 0; + if(effect.mEffectId == ESM::MagicEffect::DamageMagicka) + index = 1; + else if(effect.mEffectId == ESM::MagicEffect::DamageFatigue) + index = 2; + // Damage "Dynamic" abilities reduce the base value + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, index, effect.mMagnitude); + else + { + static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); + adjustDynamicStat(target, index, -effect.mMagnitude, index == 2 && uncappedDamageFatigue); + if(index == 0) + receivedMagicDamage = true; + } + } + break; + case ESM::MagicEffect::DamageAttribute: + if(!godmode) + damageAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::DamageSkill: + if(!target.getClass().isNpc()) + invalid = true; + else if(!godmode) + { + // Damage Skill abilities reduce base skill :todd: + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& npcStats = target.getClass().getNpcStats(target); + SkillValue& skill = npcStats.getSkill(effect.mArg); + // Damage Skill abilities reduce base skill :todd: + skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); + } + else + damageSkill(target, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::RestoreAttribute: + restoreAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::RestoreSkill: + if(!target.getClass().isNpc()) + invalid = true; + else + restoreSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::RestoreHealth: + case ESM::MagicEffect::RestoreMagicka: + case ESM::MagicEffect::RestoreFatigue: + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::RestoreHealth, effect.mMagnitude); + break; + case ESM::MagicEffect::SunDamage: + { + // isInCell shouldn't be needed, but updateActor called during game start + if (!target.isInCell() || !target.getCell()->isExterior() || godmode) + break; + float time = world->getTimeStamp().getHour(); + float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); + float damageScale = 1.f - timeDiff / 7.f; + // When cloudy, the sun damage effect is halved + static float fMagicSunBlockedMult = world->getStore().get().find("fMagicSunBlockedMult")->mValue.getFloat(); + + int weather = world->getCurrentWeather(); + if (weather > 1) + damageScale *= fMagicSunBlockedMult; + float damage = effect.mMagnitude * damageScale; + adjustDynamicStat(target, 0, -damage); + if (damage > 0.f) + receivedMagicDamage = true; + } + break; + case ESM::MagicEffect::DrainHealth: + case ESM::MagicEffect::DrainMagicka: + case ESM::MagicEffect::DrainFatigue: + if(!godmode) + { + static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); + int index = effect.mEffectId - ESM::MagicEffect::DrainHealth; + adjustDynamicStat(target, index, -effect.mMagnitude, uncappedDamageFatigue && index == 2); + if(index == 0) + receivedMagicDamage = true; + } + break; + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude); + else + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude, false, true); + break; + case ESM::MagicEffect::DrainAttribute: + if(!godmode) + damageAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyAttribute: + // Abilities affect base stats, but not for drain + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + AttributeValue attr = creatureStats.getAttribute(effect.mArg); + attr.setBase(attr.getBase() + effect.mMagnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + else + fortifyAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::DrainSkill: + if(!target.getClass().isNpc()) + invalid = true; + else if(!godmode) + damageSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifySkill: + if(!target.getClass().isNpc()) + invalid = true; + else if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + // Abilities affect base stats, but not for drain + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.setBase(skill.getBase() + effect.mMagnitude); + } + else + fortifySkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyMaximumMagicka: + target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true); + break; + case ESM::MagicEffect::AbsorbHealth: + case ESM::MagicEffect::AbsorbMagicka: + case ESM::MagicEffect::AbsorbFatigue: + if(!godmode) + { + int index = effect.mEffectId - ESM::MagicEffect::AbsorbHealth; + adjustDynamicStat(target, index, -effect.mMagnitude); + if(!caster.isEmpty()) + adjustDynamicStat(caster, index, effect.mMagnitude); + if(index == 0) + receivedMagicDamage = true; + } + break; + case ESM::MagicEffect::AbsorbAttribute: + if(!godmode) + { + damageAttribute(target, effect, effect.mMagnitude); + if(!caster.isEmpty()) + fortifyAttribute(caster, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::AbsorbSkill: + if(!target.getClass().isNpc()) + invalid = true; + else if(!godmode) + { + damageSkill(target, effect, effect.mMagnitude); + if(!caster.isEmpty()) + fortifySkill(caster, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::DisintegrateArmor: + { + if (!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + if (godmode) + break; + static const std::array priorities + { + MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, + MWWorld::InventoryStore::Slot_RightGauntlet, + MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots + }; + for (const int priority : priorities) + { + if (disintegrateSlot(target, priority, effect.mMagnitude)) + break; + } + break; + } + case ESM::MagicEffect::DisintegrateWeapon: + if (!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + if (!godmode) + disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude); + break; + } +} + +bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) +{ + const auto world = MWBase::Environment::get().getWorld(); + bool invalid = false; + bool receivedMagicDamage = false; + if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen()) + { + spellParams.worsen(); + for(auto& otherEffect : spellParams.getEffects()) + { + if(isCorprusEffect(otherEffect)) + applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage); + } + if(target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); + return false; + } + else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled()) + { + if(target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); + return true; + } + const auto* magicEffect = world->getStore().get().find(effect.mEffectId); + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + { + effect.mTimeLeft -= dt; + return false; + } + if(effect.mEffectId == ESM::MagicEffect::Lock) + { + if(target.getClass().canLock(target)) + { + MWRender::Animation* animation = world->getAnimation(target); + if(animation) + animation->addSpellCastGlow(magicEffect); + 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 + { + if(caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); + target.getCellRef().lock(magnitude); + } + } + else + invalid = true; + } + else if(effect.mEffectId == ESM::MagicEffect::Open) + { + if(target.getClass().canLock(target)) + { + // Use the player instead of the caster for vanilla crime compatibility + MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target); + + MWRender::Animation* animation = world->getAnimation(target); + if(animation) + animation->addSpellCastGlow(magicEffect); + int magnitude = static_cast(roll(effect)); + if(target.getCellRef().getLockLevel() <= magnitude) + { + if(target.getCellRef().getLockLevel() > 0) + { + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); + + if(caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); + } + target.getCellRef().unlock(); + } + else + { + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + } + } + else + invalid = true; + } + else if(!target.getClass().isActor()) + { + invalid = true; + } + else + { + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & (ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Ignore_Resistances))) + { + const ESM::Spell* spell = nullptr; + if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + spell = world->getStore().get().search(spellParams.getId()); + float magnitudeMult = getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + return true; + } + effect.mMinMagnitude *= magnitudeMult; + effect.mMaxMagnitude *= magnitudeMult; + } + float oldMagnitude = 0.f; + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) + oldMagnitude = effect.mMagnitude; + //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here + effect.mMagnitude = roll(effect); + if(!(magicEffect->mData.mFlags & (ESM::MagicEffect::Flags::NoMagnitude | ESM::MagicEffect::Flags::AppliedOnce))) + { + if(effect.mDuration != 0) + { + float mult = dt; + if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + mult = std::min(effect.mTimeLeft, dt); + effect.mMagnitude *= mult; + } + if(effect.mMagnitude == 0) + { + effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; + effect.mTimeLeft -= dt; + return false; + } + } + if(effect.mEffectId == ESM::MagicEffect::Corprus) + spellParams.worsen(); + else + applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage); + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude)); + } + effect.mTimeLeft -= dt; + if(invalid) + { + effect.mTimeLeft = 0; + effect.mFlags |= ESM::ActiveEffect::Flag_Remove; + auto anim = world->getAnimation(target); + if(anim) + anim->removeEffect(effect.mEffectId); + } + else + effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; + if (receivedMagicDamage && target == getPlayer()) + MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); + return false; +} + +void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) +{ + const auto world = MWBase::Environment::get().getWorld(); + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + bool invalid; + switch(effect.mEffectId) + { + case ESM::MagicEffect::CommandCreature: + case ESM::MagicEffect::CommandHumanoid: + if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) + { + auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); + auto it = std::find_if(seq.begin(), seq.end(), [&](const auto& package) + { + return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast(package.get())->isCommanded(); + }); + if(it != seq.end()) + seq.erase(it); + } + break; + case ESM::MagicEffect::ExtraSpell: + if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) + target.getClass().getInventoryStore(target).autoEquip(target); + break; + case ESM::MagicEffect::TurnUndead: + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat.setModifier(static_cast(stat.getModifier() - effect.mMagnitude)); + creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); + } + break; + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::FrenzyHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::CalmHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, CreatureStats::AI_Flee, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::RallyCreature: + case ESM::MagicEffect::RallyHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, CreatureStats::AI_Flee, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::SummonScamp: + case ESM::MagicEffect::SummonClannfear: + case ESM::MagicEffect::SummonDaedroth: + case ESM::MagicEffect::SummonDremora: + case ESM::MagicEffect::SummonAncestralGhost: + case ESM::MagicEffect::SummonSkeletalMinion: + case ESM::MagicEffect::SummonBonewalker: + case ESM::MagicEffect::SummonGreaterBonewalker: + case ESM::MagicEffect::SummonBonelord: + case ESM::MagicEffect::SummonWingedTwilight: + case ESM::MagicEffect::SummonHunger: + case ESM::MagicEffect::SummonGoldenSaint: + case ESM::MagicEffect::SummonFlameAtronach: + case ESM::MagicEffect::SummonFrostAtronach: + case ESM::MagicEffect::SummonStormAtronach: + case ESM::MagicEffect::SummonCenturionSphere: + case ESM::MagicEffect::SummonFabricant: + case ESM::MagicEffect::SummonWolf: + case ESM::MagicEffect::SummonBear: + case ESM::MagicEffect::SummonBonewolf: + case ESM::MagicEffect::SummonCreature04: + case ESM::MagicEffect::SummonCreature05: + { + if(effect.mArg != -1) + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, effect.mArg); + auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap(); + auto [begin, end] = summons.equal_range(effect.mEffectId); + for(auto it = begin; it != end; ++it) + { + if(it->second == effect.mArg) + { + summons.erase(it); + break; + } + } + } + break; + case ESM::MagicEffect::BoundGloves: + removeBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); + case ESM::MagicEffect::BoundDagger: + case ESM::MagicEffect::BoundLongsword: + case ESM::MagicEffect::BoundMace: + case ESM::MagicEffect::BoundBattleAxe: + case ESM::MagicEffect::BoundSpear: + case ESM::MagicEffect::BoundLongbow: + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundHelm: + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundShield: + { + const std::string& item = sBoundItemsMap.at(effect.mEffectId); + removeBoundItem(world->getStore().get().find(item)->mValue.getString(), target); + } + break; + case ESM::MagicEffect::DrainHealth: + case ESM::MagicEffect::DrainMagicka: + case ESM::MagicEffect::DrainFatigue: + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::DrainHealth, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude); + else + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude, true); + break; + case ESM::MagicEffect::DrainAttribute: + restoreAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyAttribute: + // Abilities affect base stats, but not for drain + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + AttributeValue attr = creatureStats.getAttribute(effect.mArg); + attr.setBase(attr.getBase() - effect.mMagnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + else + fortifyAttribute(target, effect, -effect.mMagnitude); + break; + case ESM::MagicEffect::DrainSkill: + restoreSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifySkill: + // Abilities affect base stats, but not for drain + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.setBase(skill.getBase() - effect.mMagnitude); + } + else + fortifySkill(target, effect, -effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyMaximumMagicka: + target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true); + break; + case ESM::MagicEffect::AbsorbAttribute: + { + const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); + restoreAttribute(target, effect, effect.mMagnitude); + if(!caster.isEmpty()) + fortifyAttribute(caster, effect, -effect.mMagnitude); + } + break; + case ESM::MagicEffect::AbsorbSkill: + { + const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); + restoreSkill(target, effect, effect.mMagnitude); + if(!caster.isEmpty()) + fortifySkill(caster, effect, -effect.mMagnitude); + } + break; + case ESM::MagicEffect::Corprus: + { + int worsenings = spellParams.getWorsenings(); + spellParams.resetWorsenings(); + if(worsenings > 0) + { + for(const auto& otherEffect : spellParams.getEffects()) + { + if(isCorprusEffect(otherEffect, true)) + { + for(int i = 0; i < worsenings; i++) + removeMagicEffect(target, spellParams, otherEffect); + } + } + } + //Note that we remove the effects, but keep the params + target.getClass().getCreatureStats(target).getActiveSpells().purge([&spellParams] (const ActiveSpells::ActiveSpellParams& params, const auto&) + { + return &spellParams == ¶ms; + }, target); + } + break; + } +} + +void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) +{ + if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) + return; + const auto world = MWBase::Environment::get().getWorld(); + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + const auto* magicEffect = world->getStore().get().find(effect.mEffectId); + if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMinMagnitude)); + removeMagicEffect(target, spellParams, effect); + auto anim = world->getAnimation(target); + if(anim) + anim->removeEffect(effect.mEffectId); +} + +} \ No newline at end of file diff --git a/apps/openmw/mwmechanics/spelleffects.hpp b/apps/openmw/mwmechanics/spelleffects.hpp new file mode 100644 index 0000000000..a9e7b066f3 --- /dev/null +++ b/apps/openmw/mwmechanics/spelleffects.hpp @@ -0,0 +1,20 @@ +#ifndef GAME_MWMECHANICS_SPELLEFFECTS_H +#define GAME_MWMECHANICS_SPELLEFFECTS_H + +#include "activespells.hpp" + +#include "../mwworld/ptr.hpp" + +// These functions should probably be split up into separate Lua functions for each magic effect when magic is dehardcoded. +// That way ESM::MGEF could point to two Lua scripts for each effect. Needs discussion. + +namespace MWMechanics +{ + // Applies a tick of a single effect. Returns true if the effect should be removed immediately + bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt); + + // Undoes permanent effects created by ESM::MagicEffect::AppliedOnce + void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect); +} + +#endif diff --git a/apps/openmw/mwmechanics/spelllist.hpp b/apps/openmw/mwmechanics/spelllist.hpp index 5920949d65..f5759fd7ee 100644 --- a/apps/openmw/mwmechanics/spelllist.hpp +++ b/apps/openmw/mwmechanics/spelllist.hpp @@ -16,12 +16,6 @@ namespace ESM namespace MWMechanics { - struct SpellParams - { - std::map mEffectRands; // - std::set mPurgedEffects; // indices of purged effects - }; - class Spells; /// Multiple instances of the same actor share the same spell list in Morrowind. diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index bd9c5f7cb3..974d297f10 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -31,21 +31,20 @@ namespace // if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted items etc. if (effectFilter == -1) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->getId()); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) continue; } - const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; - for (std::vector::const_iterator effectIt = params.mEffects.begin(); - effectIt != params.mEffects.end(); ++effectIt) + const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; + for (const auto& effect : params.getEffects()) { - int effectId = effectIt->mEffectId; + int effectId = effect.mEffectId; if (effectFilter != -1 && effectId != effectFilter) continue; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); - if (effectIt->mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway + if (effect.mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway continue; if (negative && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) @@ -64,15 +63,14 @@ namespace const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { - if (it->first != spellId) + if (it->getId() != spellId) continue; - const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; - for (std::vector::const_iterator effectIt = params.mEffects.begin(); - effectIt != params.mEffects.end(); ++effectIt) + const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; + for (const auto& effect : params.getEffects()) { - if (effectIt->mDuration > duration) - duration = effectIt->mDuration; + if (effect.mDuration > duration) + duration = effect.mDuration; } } return duration; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index b871376003..743eacfe2c 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -20,72 +20,33 @@ namespace MWMechanics { Spells::Spells() - : mSpellsChanged(false) { } Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells), - mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers), - mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects) + mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers) { if(mSpellList) mSpellList->addListener(this); } Spells::Spells(Spells&& spells) : mSpellList(std::move(spells.mSpellList)), mSpells(std::move(spells.mSpells)), - mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers)), - mSpellsChanged(std::move(spells.mSpellsChanged)), mEffects(std::move(spells.mEffects)), - mSourcedEffects(std::move(spells.mSourcedEffects)) + mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers)) { if (mSpellList) mSpellList->updateListener(&spells, this); } - std::map::const_iterator Spells::begin() const + std::vector::const_iterator Spells::begin() const { return mSpells.begin(); } - std::map::const_iterator Spells::end() const + std::vector::const_iterator Spells::end() const { return mSpells.end(); } - void Spells::rebuildEffects() const - { - mEffects = MagicEffects(); - mSourcedEffects.clear(); - - for (const auto& iter : mSpells) - { - const ESM::Spell *spell = iter.first; - - if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || - spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) - { - int i=0; - for (const auto& effect : spell->mEffects.mList) - { - if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end()) - { - ++i; - continue; // effect was purged - } - - float random = 1.f; - if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end()) - random = iter.second.mEffectRands.at(i); - - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random; - mEffects.add (effect, magnitude); - mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude); - - ++i; - } - } - } - } - bool Spells::hasSpell(const std::string &spell) const { return hasSpell(SpellList::getSpell(spell)); @@ -93,7 +54,7 @@ namespace MWMechanics bool Spells::hasSpell(const ESM::Spell *spell) const { - return mSpells.find(spell) != mSpells.end(); + return std::find(mSpells.begin(), mSpells.end(), spell) != mSpells.end(); } void Spells::add (const ESM::Spell* spell) @@ -108,29 +69,8 @@ namespace MWMechanics void Spells::addSpell(const ESM::Spell* spell) { - if (mSpells.find (spell)==mSpells.end()) - { - std::map random; - - // Determine the random magnitudes (unless this is a castable spell, in which case - // they will be determined when the spell is cast) - if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) - { - for (unsigned int i=0; imEffects.mList.size();++i) - { - if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax) - { - int delta = spell->mEffects.mList[i].mMagnMax - spell->mEffects.mList[i].mMagnMin; - random[i] = Misc::Rng::rollDice(delta + 1) / static_cast(delta); - } - } - } - - SpellParams params; - params.mEffectRands = random; - mSpells.emplace(spell, params); - mSpellsChanged = true; - } + if (!hasSpell(spell)) + mSpells.emplace_back(spell); } void Spells::remove (const std::string& spellId) @@ -145,27 +85,14 @@ namespace MWMechanics void Spells::removeSpell(const ESM::Spell* spell) { - const auto it = mSpells.find(spell); + const auto it = std::find(mSpells.begin(), mSpells.end(), spell); if(it != mSpells.end()) - { mSpells.erase(it); - mSpellsChanged = true; - } - } - - MagicEffects Spells::getMagicEffects() const - { - if (mSpellsChanged) { - rebuildEffects(); - mSpellsChanged = false; - } - return mEffects; } void Spells::removeAllSpells() { mSpells.clear(); - mSpellsChanged = true; } void Spells::clear(bool modifyBase) @@ -185,26 +112,10 @@ namespace MWMechanics return mSelectedSpell; } - bool Spells::isSpellActive(const std::string &id) const - { - if (id.empty()) - return false; - - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); - if (spell && hasSpell(spell)) - { - auto type = spell->mData.mType; - return (type==ESM::Spell::ST_Ability || type==ESM::Spell::ST_Blight || type==ESM::Spell::ST_Disease || type==ESM::Spell::ST_Curse); - } - - return false; - } - bool Spells::hasDisease(const ESM::Spell::SpellType type) const { - for (const auto& iter : mSpells) + for (const auto spell : mSpells) { - const ESM::Spell *spell = iter.first; if (spell->mData.mType == type) return true; } @@ -227,12 +138,11 @@ namespace MWMechanics std::vector purged; for (auto iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = iter->first; + const ESM::Spell *spell = *iter; if (filter(spell)) { mSpells.erase(iter++); purged.push_back(spell->mId); - mSpellsChanged = true; } else ++iter; @@ -261,43 +171,6 @@ namespace MWMechanics purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; }); } - void Spells::removeEffects(const std::string &id) - { - if (isSpellActive(id)) - { - for (auto& spell : mSpells) - { - if (spell.first == SpellList::getSpell(id)) - { - for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++) - { - spell.second.mPurgedEffects.insert(i); - } - } - } - - mSpellsChanged = true; - } - } - - void Spells::visitEffectSources(EffectSourceVisitor &visitor) const - { - if (mSpellsChanged) { - rebuildEffects(); - mSpellsChanged = false; - } - - for (const auto& it : mSourcedEffects) - { - const ESM::Spell * spell = it.first; - for (const auto& effectIt : it.second) - { - // FIXME: since Spells merges effects with the same ID, there is no sense to use multiple effects with same ID here - visitor.visit(effectIt.first, -1, spell->mName, spell->mId, -1, effectIt.second.getMagnitude()); - } - } - } - bool Spells::hasCorprusEffect(const ESM::Spell *spell) { for (const auto& effectIt : spell->mEffects.mList) @@ -310,46 +183,6 @@ namespace MWMechanics return false; } - void Spells::purgeEffect(int effectId) - { - for (auto& spellIt : mSpells) - { - int i = 0; - for (auto& effectIt : spellIt.first->mEffects.mList) - { - if (effectIt.mEffectID == effectId) - { - spellIt.second.mPurgedEffects.insert(i); - mSpellsChanged = true; - } - ++i; - } - } - } - - void Spells::purgeEffect(int effectId, const std::string & sourceId) - { - // Effect source may be not a spell - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(sourceId); - if (spell == nullptr) - return; - - auto spellIt = mSpells.find(spell); - if (spellIt == mSpells.end()) - return; - - int index = 0; - for (auto& effectIt : spellIt->first->mEffects.mList) - { - if (effectIt.mEffectID == effectId) - { - spellIt->second.mPurgedEffects.insert(index); - mSpellsChanged = true; - } - ++index; - } - } - bool Spells::canUsePower(const ESM::Spell* spell) const { const auto it = mUsedPowers.find(spell); @@ -365,17 +198,16 @@ namespace MWMechanics { const auto& baseSpells = mSpellList->getSpells(); - for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + for (const std::string& id : state.mSpells) { // Discard spells that are no longer available due to changed content files - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); if (spell) { - mSpells[spell].mEffectRands = it->second.mEffectRands; - mSpells[spell].mPurgedEffects = it->second.mPurgedEffects; + mSpells.emplace_back(spell); - if (it->first == state.mSelectedSpell) - mSelectedSpell = it->first; + if (id == state.mSelectedSpell) + mSelectedSpell = id; } } // Add spells from the base record @@ -394,31 +226,6 @@ namespace MWMechanics mUsedPowers[spell] = MWWorld::TimeStamp(it->second); } - for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) - { - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); - if (!spell) - continue; - - CorprusStats stats; - - int worsening = state.mCorprusSpells.at(it->first).mWorsenings; - - for (int i=0; imEffects.mList) - { - if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) - stats.mWorsenings[effect.mAttribute] = worsening; - } - stats.mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - - creatureStats->addCorprusSpell(it->first, stats); - } - - mSpellsChanged = true; - // Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and only in old saves. Convert data to the new approach. for (std::map >::const_iterator it = state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) @@ -457,16 +264,13 @@ namespace MWMechanics void Spells::writeState(ESM::SpellState &state) const { const auto& baseSpells = mSpellList->getSpells(); - for (const auto& it : mSpells) + for (const auto spell : mSpells) { // Don't save spells and powers stored in the base record - if((it.first->mData.mType != ESM::Spell::ST_Spell && it.first->mData.mType != ESM::Spell::ST_Power) || - std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end()) + if((spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power) || + std::find(baseSpells.begin(), baseSpells.end(), spell->mId) == baseSpells.end()) { - ESM::SpellState::SpellParams params; - params.mEffectRands = it.second.mEffectRands; - params.mPurgedEffects = it.second.mPurgedEffects; - state.mSpells.emplace(it.first->mId, params); + state.mSpells.emplace_back(spell->mId); } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 9737b72cd0..29f505d369 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -29,18 +29,13 @@ namespace MWMechanics class Spells { std::shared_ptr mSpellList; - std::map mSpells; + std::vector mSpells; // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; std::map mUsedPowers; - mutable bool mSpellsChanged; - mutable MagicEffects mEffects; - mutable std::map mSourcedEffects; - void rebuildEffects() const; - bool hasDisease(const ESM::Spell::SpellType type) const; using SpellFilter = bool (*)(const ESM::Spell*); @@ -52,8 +47,6 @@ namespace MWMechanics friend class SpellList; public: - using TIterator = std::map::const_iterator; - Spells(); Spells(const Spells&); @@ -64,9 +57,6 @@ namespace MWMechanics static bool hasCorprusEffect(const ESM::Spell *spell); - void purgeEffect(int effectId); - void purgeEffect(int effectId, const std::string & sourceId); - bool canUsePower (const ESM::Spell* spell) const; void usePower (const ESM::Spell* spell); @@ -75,9 +65,9 @@ namespace MWMechanics void purgeCorprusDisease(); void purgeCurses(); - TIterator begin() const; + std::vector::const_iterator begin() const; - TIterator end() const; + std::vector::const_iterator end() const; bool hasSpell(const std::string& spell) const; bool hasSpell(const ESM::Spell* spell) const; @@ -92,9 +82,6 @@ namespace MWMechanics ///< If the spell to be removed is the selected spell, the selected spell will be changed to /// no spell (empty string). - MagicEffects getMagicEffects() const; - ///< Return sum of magic effects resulting from abilities, blights, deseases and curses. - void clear(bool modifyBase = false); ///< Remove all spells of al types. @@ -104,17 +91,10 @@ namespace MWMechanics const std::string getSelectedSpell() const; ///< May return an empty string. - bool isSpellActive(const std::string& id) const; - ///< Are we under the effects of the given spell ID? - bool hasCommonDisease() const; bool hasBlightDisease() const; - void removeEffects(const std::string& id); - - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void writeState (ESM::SpellState& state) const; diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index d90545fc60..953db077fb 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -60,90 +60,60 @@ namespace MWMechanics return std::string(); } - UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) - : mActor(actor) + int summonCreature(int effectId, const MWWorld::Ptr& summoner) { - } - - void UpdateSummonedCreatures::visit(EffectKey key, int effectIndex, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) - { - if (isSummoningEffect(key.mId) && magnitude > 0) + std::string creatureID = getSummonedCreature(effectId); + int creatureActorId = -1; + if (!creatureID.empty()) { - mActiveEffects.insert(ESM::SummonKey(key.mId, sourceId, effectIndex)); - } - } + try + { + auto world = MWBase::Environment::get().getWorld(); + MWWorld::ManualRef ref(world->getStore(), creatureID, 1); - void UpdateSummonedCreatures::process(bool cleanup) - { - MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - for (std::set::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it) - { - bool found = creatureMap.find(*it) != creatureMap.end(); - if (!found) - { - std::string creatureID = getSummonedCreature(it->mEffectId); - if (!creatureID.empty()) + // Make the summoned creature follow its master and help in fights + AiFollow package(summoner); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + creatureActorId = summonedCreatureStats.getActorId(); + + MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f); + + MWRender::Animation* anim = world->getAnimation(placed); + if (anim) { - int creatureActorId = -1; - try - { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - - MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - - // Make the summoned creature follow its master and help in fights - AiFollow package(mActor); - summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); - creatureActorId = summonedCreatureStats.getActorId(); - - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), mActor, mActor.getCell(), 0, 120.f); - - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); - if (anim) - { - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_Start"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1, false); - } - } - catch (std::exception& e) - { - Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what(); - // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log - } - - creatureMap.emplace(*it, creatureActorId); + const ESM::Static* fx = world->getStore().get().search("VFX_Summon_Start"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1, false); } } - } - - // Update summon effects - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) - { - bool found = mActiveEffects.find(it->first) != mActiveEffects.end(); - if (!found) + catch (std::exception& e) { - // Effect has ended - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); - creatureMap.erase(it++); - continue; + Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what(); + // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log } - ++it; + + summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creatureActorId); } + return creatureActorId; + } + + void updateSummons(const MWWorld::Ptr& summoner, bool cleanup) + { + MWMechanics::CreatureStats& creatureStats = summoner.getClass().getCreatureStats(summoner); + auto& creatureMap = creatureStats.getSummonedCreatureMap(); std::vector graveyard = creatureStats.getSummonedCreatureGraveyard(); creatureStats.getSummonedCreatureGraveyard().clear(); for (const int creature : graveyard) - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, creature); + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, creature); if (!cleanup) return; - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + for (auto it = creatureMap.begin(); it != creatureMap.end(); ) { if(it->second == -1) { @@ -155,7 +125,7 @@ namespace MWMechanics if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) { // Purge the magic effect so a new creature can be summoned if desired - purgeSummonEffect(mActor, *it); + purgeSummonEffect(summoner, *it); creatureMap.erase(it++); } else @@ -163,13 +133,13 @@ namespace MWMechanics } } - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) { auto& creatureStats = summoner.getClass().getCreatureStats(summoner); - creatureStats.getActiveSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId, summon.first.mEffectIndex); - creatureStats.getSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId); - if (summoner.getClass().hasInventoryStore(summoner)) - summoner.getClass().getInventoryStore(summoner).purgeEffect(summon.first.mEffectId, summon.first.mSourceId, false, summon.first.mEffectIndex); + creatureStats.getActiveSpells().purge([summon] (const auto& spell, const auto& effect) + { + return effect.mEffectId == summon.first && effect.mArg == summon.second; + }, summoner); MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second); } diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index 3c3e18a96b..3186eef986 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -11,32 +11,15 @@ namespace MWMechanics { - class CreatureStats; - bool isSummoningEffect(int effectId); std::string getSummonedCreature(int effectId); - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); - - struct UpdateSummonedCreatures : public EffectSourceVisitor - { - UpdateSummonedCreatures(const MWWorld::Ptr& actor); - virtual ~UpdateSummonedCreatures() = default; - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override; - - /// To call after all effect sources have been visited - void process(bool cleanup); - - private: - MWWorld::Ptr mActor; + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); - std::set mActiveEffects; - }; + int summonCreature(int effectId, const MWWorld::Ptr& summoner); + void updateSummons(const MWWorld::Ptr& summoner, bool cleanup); } #endif diff --git a/apps/openmw/mwmechanics/tickableeffects.cpp b/apps/openmw/mwmechanics/tickableeffects.cpp deleted file mode 100644 index 5056179f8f..0000000000 --- a/apps/openmw/mwmechanics/tickableeffects.cpp +++ /dev/null @@ -1,216 +0,0 @@ -#include "tickableeffects.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/cellstore.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" - -#include "actorutil.hpp" -#include "npcstats.hpp" - -namespace MWMechanics -{ - void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false) - { - DynamicStat stat = creatureStats.getDynamic(index); - stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero); - creatureStats.setDynamic(index, stat); - } - - bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate) - { - if (!ptr.getClass().hasInventoryStore(ptr)) - return false; - - MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); - MWWorld::ContainerStoreIterator item = inv.getSlot(slot); - - if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) - { - if (!item->getClass().hasItemHealth(*item)) - return false; - int charge = item->getClass().getItemHealth(*item); - if (charge == 0) - return false; - - // Store remainder of disintegrate amount (automatically subtracted if > 1) - item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); - - charge = item->getClass().getItemHealth(*item); - charge -= std::min(static_cast(disintegrate), charge); - item->getCellRef().setCharge(charge); - - if (charge == 0) - { - // Will unequip the broken item and try to find a replacement - if (ptr != getPlayer()) - inv.autoEquip(ptr); - else - inv.unequipItem(*item, ptr); - } - - return true; - } - - return false; - } - - bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) - { - if (magnitude == 0.f) - return false; - - bool receivedMagicDamage = false; - bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - switch (effectKey.mId) - { - case ESM::MagicEffect::DamageAttribute: - { - if (godmode) - break; - AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); - attr.damage(magnitude); - creatureStats.setAttribute(effectKey.mArg, attr); - break; - } - case ESM::MagicEffect::RestoreAttribute: - { - AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); - attr.restore(magnitude); - creatureStats.setAttribute(effectKey.mArg, attr); - break; - } - case ESM::MagicEffect::RestoreHealth: - case ESM::MagicEffect::RestoreMagicka: - case ESM::MagicEffect::RestoreFatigue: - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); - break; - case ESM::MagicEffect::DamageHealth: - if (godmode) - break; - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); - break; - - case ESM::MagicEffect::DamageMagicka: - case ESM::MagicEffect::DamageFatigue: - { - if (godmode) - break; - int index = effectKey.mId-ESM::MagicEffect::DamageHealth; - static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); - adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue); - break; - } - case ESM::MagicEffect::AbsorbHealth: - if (!godmode || magnitude <= 0) - { - if (magnitude > 0.f) - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - } - break; - - case ESM::MagicEffect::AbsorbMagicka: - case ESM::MagicEffect::AbsorbFatigue: - if (!godmode || magnitude <= 0) - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - break; - - case ESM::MagicEffect::DisintegrateArmor: - { - if (godmode) - break; - static const std::array priorities - { - MWWorld::InventoryStore::Slot_CarriedLeft, - MWWorld::InventoryStore::Slot_Cuirass, - MWWorld::InventoryStore::Slot_LeftPauldron, - MWWorld::InventoryStore::Slot_RightPauldron, - MWWorld::InventoryStore::Slot_LeftGauntlet, - MWWorld::InventoryStore::Slot_RightGauntlet, - MWWorld::InventoryStore::Slot_Helmet, - MWWorld::InventoryStore::Slot_Greaves, - MWWorld::InventoryStore::Slot_Boots - }; - for (const int priority : priorities) - { - if (disintegrateSlot(actor, priority, magnitude)) - break; - } - - break; - } - case ESM::MagicEffect::DisintegrateWeapon: - if (!godmode) - disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude); - break; - - case ESM::MagicEffect::SunDamage: - { - // isInCell shouldn't be needed, but updateActor called during game start - if (!actor.isInCell() || !actor.getCell()->isExterior() || godmode) - break; - float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); - float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); - float damageScale = 1.f - timeDiff / 7.f; - // When cloudy, the sun damage effect is halved - static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fMagicSunBlockedMult")->mValue.getFloat(); - - int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); - if (weather > 1) - damageScale *= fMagicSunBlockedMult; - - adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); - if (magnitude * damageScale > 0.f) - receivedMagicDamage = true; - - break; - } - - case ESM::MagicEffect::FireDamage: - case ESM::MagicEffect::ShockDamage: - case ESM::MagicEffect::FrostDamage: - case ESM::MagicEffect::Poison: - { - if (godmode) - break; - adjustDynamicStat(creatureStats, 0, -magnitude); - receivedMagicDamage = true; - break; - } - - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::RestoreSkill: - { - if (!actor.getClass().isNpc()) - break; - if (godmode && effectKey.mId == ESM::MagicEffect::DamageSkill) - break; - NpcStats &npcStats = actor.getClass().getNpcStats(actor); - SkillValue& skill = npcStats.getSkill(effectKey.mArg); - if (effectKey.mId == ESM::MagicEffect::RestoreSkill) - skill.restore(magnitude); - else - skill.damage(magnitude); - break; - } - - default: - return false; - } - - if (receivedMagicDamage && actor == getPlayer()) - MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); - return true; - } -} diff --git a/apps/openmw/mwmechanics/tickableeffects.hpp b/apps/openmw/mwmechanics/tickableeffects.hpp deleted file mode 100644 index ccd42ca19b..0000000000 --- a/apps/openmw/mwmechanics/tickableeffects.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef MWMECHANICS_TICKABLEEFFECTS_H -#define MWMECHANICS_TICKABLEEFFECTS_H - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - class CreatureStats; - struct EffectKey; - - /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed - /// Note: this function works in loop, so magic effects should not be removed here to avoid iterator invalidation. - /// @return Was the effect a tickable effect with a magnitude? - bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude); -} - -#endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 7fc488020c..bcb2d36f5d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1101,34 +1101,6 @@ Resource::ResourceSystem* NpcAnimation::getResourceSystem() return mResourceSystem; } -void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) -{ - // During first auto equip, we don't play any sounds. - // Basically we don't want sounds when the actor is first loaded, - // the items should appear as if they'd always been equipped. - if (isNew) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - } - - if (!magicEffect->mHit.empty()) - { - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; - // Don't play particle VFX unless the effect is new or it should be looping. - if (isNew || loop) - addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); - } -} - void NpcAnimation::enableHeadAnimation(bool enable) { mHeadAnimationTime->setEnabled(enable); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 768ac01622..9f7a186c5e 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -31,7 +31,6 @@ class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWor { public: void equipmentChanged() override; - void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) override; public: typedef std::map PartBoneMap; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 149f9f3f3f..3642f68dc2 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -563,13 +563,7 @@ namespace MWScript const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects(); - effects += stats.getActiveSpells().getMagicEffects(); - if (ptr.getClass().hasInventoryStore(ptr) && !stats.isDeathAnimationFinished()) - { - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - effects += store.getMagicEffects(); - } + const MWMechanics::MagicEffects& effects = stats.getMagicEffects(); for (const auto& activeEffect : effects) { @@ -821,7 +815,7 @@ namespace MWScript } const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - runtime.push(stats.getActiveSpells().isSpellActive(id) || stats.getSpells().isSpellActive(id)); + runtime.push(stats.getActiveSpells().isSpellActive(id)); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index fe2df68473..ccad186a0d 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -473,6 +473,8 @@ namespace MWScript ESM::Spell::SpellType type = static_cast(spell->mData.mType); if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Power) { + // Add spell effect to *this actor's* queue immediately + creatureStats.getActiveSpells().addSpell(spell, ptr); // Apply looping particles immediately for constant effects MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } @@ -492,17 +494,6 @@ namespace MWScript runtime.pop(); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); - // The spell may have an instant effect which must be handled before the spell's removal. - for (const auto& effect : creatureStats.getSpells().getMagicEffects()) - { - if (effect.second.getMagnitude() <= 0) - continue; - MWMechanics::CastSpell cast(ptr, ptr); - if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude())) - creatureStats.getSpells().purgeEffect(effect.first.mId); - } - - MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, id); creatureStats.getSpells().remove (id); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); @@ -527,8 +518,7 @@ namespace MWScript std::string spellid = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); - ptr.getClass().getCreatureStats (ptr).getSpells().removeEffects(spellid); + ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(ptr, spellid); } }; @@ -544,7 +534,7 @@ namespace MWScript Interpreter::Type_Integer effectId = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId); + ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(ptr, effectId); } }; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3d82a30c29..b2ac511509 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -26,6 +26,7 @@ #include "../mwmechanics/recharge.hpp" #include "ptr.hpp" +#include "esmloader.hpp" #include "esmstore.hpp" #include "class.hpp" #include "containerstore.hpp" @@ -176,6 +177,13 @@ namespace if (state.mVersion < 15) fixRestocking(record, state); + if (state.mVersion < 17) + { + if constexpr (std::is_same_v) + MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory); + else if constexpr (std::is_same_v) + MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); + } if (state.mRef.mRefNum.hasContentFile()) { diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index b12d646e70..a01128fe36 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -2,6 +2,26 @@ #include "esmstore.hpp" #include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/magiceffects.hpp" + +namespace +{ + template + void getEnchantedItem(const std::string& id, std::string& enchantment, std::string& itemName) + { + const T* item = MWBase::Environment::get().getWorld()->getStore().get().search(id); + if(item) + { + enchantment = item->mEnchant; + itemName = item->mName; + } + } +} namespace MWWorld { @@ -28,4 +48,187 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index) mStore.load(mEsm[index], &mListener); } + void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats) + { + const auto& store = MWBase::Environment::get().getWorld()->getStore(); + // Convert corprus to format 10 + for (const auto& [id, oldStats] : creatureStats.mSpells.mCorprusSpells) + { + const ESM::Spell* spell = store.get().search(id); + if (!spell) + continue; + + ESM::CreatureStats::CorprusStats stats; + stats.mNextWorsening = oldStats.mNextWorsening; + for (int i=0; imEffects.mList) + { + if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; + } + creatureStats.mCorprusSpells[id] = stats; + } + // Convert to format 17 + for(const auto& [id, oldParams] : creatureStats.mSpells.mSpellParams) + { + const ESM::Spell* spell = store.get().search(id); + 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.mDisplayName = spell->mName; + params.mItem.unset(); + params.mCasterActorId = creatureStats.mActorId; + if(spell->mData.mType == ESM::Spell::ST_Ability) + params.mType = ESM::ActiveSpells::Type_Ability; + else + params.mType = ESM::ActiveSpells::Type_Permanent; + params.mWorsenings = -1; + int effectIndex = 0; + for(const auto& enam : spell->mEffects.mList) + { + if(oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) + { + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effect.mEffectIndex = effectIndex; + auto rand = oldParams.mEffectRands.find(effectIndex); + if(rand != oldParams.mEffectRands.end()) + { + float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; + effect.mMagnitude = magnitude; + effect.mMinMagnitude = magnitude; + effect.mMaxMagnitude = magnitude; + // Prevent recalculation of resistances + effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances; + } + else + { + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mMagnMin; + effect.mMaxMagnitude = enam.mMagnMax; + effect.mFlags = ESM::ActiveEffect::Flag_None; + } + params.mEffects.emplace_back(effect); + } + effectIndex++; + } + creatureStats.mActiveSpells.mSpells.emplace_back(params); + } + std::multimap equippedItems; + for(std::size_t i = 0; i < inventory.mItems.size(); ++i) + { + const ESM::ObjectState& item = inventory.mItems[i]; + auto slot = inventory.mEquipmentSlots.find(i); + if(slot != inventory.mEquipmentSlots.end()) + equippedItems.emplace(item.mRef.mRefID, slot->second); + } + for(const auto& [id, oldMagnitudes] : inventory.mPermanentMagicEffectMagnitudes) + { + std::string eId; + std::string name; + switch(store.find(id)) + { + case ESM::REC_ARMO: + getEnchantedItem(id, eId, name); + break; + case ESM::REC_CLOT: + getEnchantedItem(id, eId, name); + break; + case ESM::REC_WEAP: + getEnchantedItem(id, eId, name); + break; + } + if(eId.empty()) + continue; + const ESM::Enchantment* enchantment = store.get().search(eId); + if(!enchantment) + continue; + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = id; + params.mDisplayName = name; + params.mCasterActorId = creatureStats.mActorId; + params.mType = ESM::ActiveSpells::Type_Enchantment; + params.mWorsenings = -1; + for(std::size_t effectIndex = 0; effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) + { + const auto& enam = enchantment->mEffects.mList[effectIndex]; + auto [random, multiplier] = oldMagnitudes[effectIndex]; + float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; + magnitude *= multiplier; + if(magnitude <= 0) + continue; + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mMagnitude = magnitude; + effect.mMinMagnitude = magnitude; + effect.mMaxMagnitude = magnitude; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effect.mEffectIndex = static_cast(effectIndex); + // Prevent recalculation of resistances + effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances; + params.mEffects.emplace_back(effect); + } + auto [begin, end] = equippedItems.equal_range(id); + for(auto it = begin; it != end; ++it) + { + params.mItem = { static_cast(it->second), 0 }; + creatureStats.mActiveSpells.mSpells.emplace_back(params); + } + } + for(const auto& spell : creatureStats.mCorprusSpells) + { + auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), [&] (const auto& params) { return params.mId == spell.first; }); + if(it != creatureStats.mActiveSpells.mSpells.end()) + { + it->mNextWorsening = spell.second.mNextWorsening; + int worsenings = 0; + for(int i = 0; i < ESM::Attribute::Length; ++i) + worsenings = std::max(spell.second.mWorsenings[i], worsenings); + it->mWorsenings = worsenings; + } + } + for(const auto& [key, actorId] : creatureStats.mSummonedCreatureMap) + { + if(actorId == -1) + continue; + for(auto& params : creatureStats.mActiveSpells.mSpells) + { + if(params.mId == key.mSourceId) + { + bool found = false; + for(auto& effect : params.mEffects) + { + if(effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex) + { + effect.mArg = actorId; + found = true; + break; + } + } + if(found) + break; + } + } + } + // Reset modifiers that were previously recalculated each frame + for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) + creatureStats.mAttributes[i].mMod = 0.f; + for(std::size_t i = 0; i < 3; ++i) + creatureStats.mDynamic[i].mMod = 0.f; + for(std::size_t i = 0; i < 4; ++i) + creatureStats.mAiSettings[i].mMod = 0.f; + if(npcStats) + { + for(std::size_t i = 0; i < ESM::Skill::Length; ++i) + npcStats->mSkills[i].mMod = 0.f; + } + } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 506105bebb..50631603de 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -13,6 +13,9 @@ namespace ToUTF8 namespace ESM { class ESMReader; + struct CreatureStats; + struct InventoryState; + struct NpcStats; } namespace MWWorld @@ -33,6 +36,8 @@ struct EsmLoader : public ContentLoader ToUTF8::Utf8Encoder* mEncoder; }; +void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr); + } /* namespace MWWorld */ #endif // ESMLOADER_HPP diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 0b8af4463c..49e60af1fd 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -108,11 +108,9 @@ MWWorld::InventoryStore::InventoryStore() MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) : ContainerStore (store) - , mMagicEffects(store.mMagicEffects) , mInventoryListener(store.mInventoryListener) , mUpdatesEnabled(store.mUpdatesEnabled) , mFirstAutoEquip(store.mFirstAutoEquip) - , mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes) , mSelectedEnchantItem(end()) { copySlots (store); @@ -125,9 +123,7 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStor mListener = store.mListener; mInventoryListener = store.mInventoryListener; - mMagicEffects = store.mMagicEffects; mFirstAutoEquip = store.mFirstAutoEquip; - mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; mRechargingItemsUpToDate = false; ContainerStore::operator= (store); mSlots.clear(); @@ -186,8 +182,6 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite flagAsModified(); fireEquipmentChangedEvent(actor); - - updateMagicEffects(actor); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -199,7 +193,6 @@ void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) mUpdatesEnabled = true; fireEquipmentChangedEvent(actor); - updateMagicEffects(actor); } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) @@ -554,7 +547,6 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { mSlots.swap (slots_); fireEquipmentChangedEvent(actor); - updateMagicEffects(actor); flagAsModified(); } } @@ -567,129 +559,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield(cons return slots[Slot_CarriedLeft]; } -const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const -{ - return mMagicEffects; -} - -void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) -{ - // To avoid excessive updates during auto-equip - if (!mUpdatesEnabled) - return; - - // Delay update until the listener is set up - if (!mInventoryListener) - return; - - mMagicEffects = MWMechanics::MagicEffects(); - - const auto& stats = actor.getClass().getCreatureStats(actor); - if (stats.isDead() && stats.isDeathAnimationFinished()) - return; - - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter==end()) - continue; - - std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); - - if (!enchantmentId.empty()) - { - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - std::vector params; - - bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) != mPermanentMagicEffectMagnitudes.end()); - if (!existed) - { - params.resize(enchantment.mEffects.mList.size()); - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - int delta = effect.mMagnMax - effect.mMagnMin; - // Roll some dice, one for each effect - if (delta) - params[i].mRandom = Misc::Rng::rollDice(delta + 1) / static_cast(delta); - // Try resisting each effect - params[i].mMultiplier = MWMechanics::getEffectMultiplier(effect.mEffectID, actor, actor); - ++i; - } - - // Note that using the RefID as a key here is not entirely correct. - // Consider equipping the same item twice (e.g. a ring) - // However, permanent enchantments with a random magnitude are kind of an exploit anyway, - // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. - mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()] = params; - } - else - params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()]; - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effect.mEffectID); - - // Fully resisted or can't be applied to target? - if (params[i].mMultiplier == 0 || !MWMechanics::checkEffectTarget(effect.mEffectID, actor, actor, actor == MWMechanics::getPlayer())) - { - i++; - continue; - } - - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params[i].mRandom; - magnitude *= params[i].mMultiplier; - - if (!existed) - { - // During first auto equip, we don't play any sounds. - // Basically we don't want sounds when the actor is first loaded, - // the items should appear as if they'd always been equipped. - mInventoryListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip); - } - - if (magnitude) - mMagicEffects.add (effect, magnitude); - - i++; - } - } - } - - // Now drop expired effects - for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin(); - it != mPermanentMagicEffectMagnitudes.end();) - { - bool found = false; - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter == end()) - continue; - if ((**iter).getCellRef().getRefId() == it->first) - { - found = true; - } - } - if (!found) - mPermanentMagicEffectMagnitudes.erase(it++); - else - ++it; - } - - // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping - MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); - - mFirstAutoEquip = false; -} - bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const { bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); @@ -800,7 +669,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c if (applyUpdates) { fireEquipmentChangedEvent(actor); - updateMagicEffects(actor); } return retval; @@ -848,7 +716,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con return unstack(item, actor, item.getRefData().getCount() - count); } -MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() +MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const { return mInventoryListener; } @@ -856,7 +724,6 @@ MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() void MWWorld::InventoryStore::setInvListener(InventoryStoreListener *listener, const Ptr& actor) { mInventoryListener = listener; - updateMagicEffects(actor); } void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) @@ -875,105 +742,6 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) */ } -void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) -{ - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter==end()) - continue; - - std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); - if (enchantmentId.empty()) - continue; - - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) == mPermanentMagicEffectMagnitudes.end()) - continue; - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - i++; - // Don't get spell icon display information for enchantments that weren't actually applied - if (mMagicEffects.get(MWMechanics::EffectKey(effect)).getMagnitude() == 0) - continue; - const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i-1]; - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params.mRandom; - magnitude *= params.mMultiplier; - if (magnitude > 0) - visitor.visit(MWMechanics::EffectKey(effect), i-1, (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude); - } - } -} - -void MWWorld::InventoryStore::purgeEffect(short effectId, bool wholeSpell) -{ - for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it) - { - if (*it != end()) - purgeEffect(effectId, (*it)->getCellRef().getRefId(), wholeSpell); - } -} - -void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell, int effectIndex) -{ - TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId); - if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end()) - return; - - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter==end()) - continue; - - if ((*iter)->getCellRef().getRefId() != sourceId) - continue; - - std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); - - if (!enchantmentId.empty()) - { - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - std::vector& params = effectMagnitudeIt->second; - - int i=0; - for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) - { - if (effectIt->mEffectID != effectId) - continue; - - if (effectIndex >= 0 && effectIndex != i) - continue; - - if (wholeSpell) - { - mPermanentMagicEffectMagnitudes.erase(sourceId); - return; - } - - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; - magnitude *= params[i].mMultiplier; - - if (magnitude) - mMagicEffects.add (*effectIt, -magnitude); - - params[i].mMultiplier = 0; - } - } - } -} - void MWWorld::InventoryStore::clear() { mSlots.clear(); @@ -991,38 +759,9 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item) return false; } -void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const -{ - MWWorld::ContainerStore::writeState(state); - - for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) - { - std::vector > params; - for (std::vector::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) - { - params.emplace_back(pIt->mRandom, pIt->mMultiplier); - } - - state.mPermanentMagicEffectMagnitudes[it->first] = params; - } -} - -void MWWorld::InventoryStore::readState(const ESM::InventoryState &state) +bool MWWorld::InventoryStore::isFirstEquip() { - MWWorld::ContainerStore::readState(state); - - for (ESM::InventoryState::TEffectMagnitudes::const_iterator it = state.mPermanentMagicEffectMagnitudes.begin(); - it != state.mPermanentMagicEffectMagnitudes.end(); ++it) - { - std::vector params; - for (std::vector >::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) - { - EffectParams p; - p.mRandom = pIt->first; - p.mMultiplier = pIt->second; - params.push_back(p); - } - - mPermanentMagicEffectMagnitudes[it->first] = params; - } + bool first = mFirstAutoEquip; + mFirstAutoEquip = false; + return first; } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 32dc0d2e91..01c53d7028 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -25,15 +25,6 @@ namespace MWWorld */ virtual void equipmentChanged () {} - /** - * @param effect - * @param isNew Is this effect new (e.g. the item for it was just now manually equipped) - * or was it loaded from a savegame / initial game state? \n - * If it isn't new, non-looping VFX should not be played. - * @param playSound Play effect sound? - */ - virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew) {} - virtual ~InventoryStoreListener() = default; }; @@ -68,8 +59,6 @@ namespace MWWorld private: - MWMechanics::MagicEffects mMagicEffects; - InventoryStoreListener* mInventoryListener; // Enables updates of magic effects and actor model whenever items are equipped or unequipped. @@ -78,19 +67,6 @@ namespace MWWorld bool mFirstAutoEquip; - // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. - // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. - struct EffectParams - { - // Modifier to scale between min and max magnitude - float mRandom; - // Multiplier for when an effect was fully or partially resisted - float mMultiplier; - }; - - typedef std::map > TEffectMagnitudes; - TEffectMagnitudes mPermanentMagicEffectMagnitudes; - typedef std::vector TSlots; TSlots mSlots; @@ -106,8 +82,6 @@ namespace MWWorld void initSlots (TSlots& slots_); - void updateMagicEffects(const Ptr& actor); - void fireEquipmentChangedEvent(const Ptr& actor); void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; @@ -161,9 +135,6 @@ namespace MWWorld void autoEquip (const MWWorld::Ptr& actor); ///< Auto equip items according to stats and item value. - const MWMechanics::MagicEffects& getMagicEffects() const; - ///< Return magic effects from worn items. - bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const override; ///< @return true if the two specified objects can stack with each other @@ -198,22 +169,12 @@ namespace MWWorld void setInvListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener - InventoryStoreListener* getInvListener(); - - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); - - void purgeEffect (short effectId, bool wholeSpell = false); - ///< Remove a magic effect - - void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false, int effectIndex=-1); - ///< Remove a magic effect + InventoryStoreListener* getInvListener() const; void clear() override; ///< Empty container. - void writeState (ESM::InventoryState& state) const override; - - void readState (const ESM::InventoryState& state) override; + bool isFirstEquip(); }; } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index d8e2fb2f0d..caa0600f7c 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -22,9 +22,10 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellutil.hpp" +#include "cellstore.hpp" #include "class.hpp" +#include "esmloader.hpp" #include "ptr.hpp" -#include "cellstore.hpp" namespace MWWorld { @@ -381,6 +382,14 @@ namespace MWWorld // this is the one object we can not silently drop. throw std::runtime_error ("invalid player state record (object state)"); } + if (reader.getFormat() < 17) + { + convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats); + for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) + player.mSaveAttributes[i].mMod = 0.f; + for(std::size_t i = 0; i < ESM::Skill::Length; ++i) + player.mSaveSkills[i].mMod = 0.f; + } if (!player.mObject.mEnabled) { diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 25e7f0c7de..3b7be64ad1 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -256,7 +256,7 @@ namespace MWWorld state.mEffectAnimationTime->addTime(duration); } - void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection) + void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection, int slot) { osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) @@ -278,6 +278,7 @@ namespace MWWorld MagicBoltState state; state.mSpellId = spellId; state.mCasterHandle = caster; + state.mSlot = slot; if (caster.getClass().isActor()) state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else @@ -545,10 +546,10 @@ namespace MWWorld cast.mHitPosition = pos; cast.mId = magicBoltState.mSpellId; cast.mSourceName = magicBoltState.mSourceName; - cast.mStack = false; + cast.mSlot = magicBoltState.mSlot; cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); - MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName, false, magicBoltState.mSlot); magicBoltState.mToDelete = true; } @@ -628,7 +629,7 @@ namespace MWWorld state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; - + state.mSlot = it->mSlot; state.mSpellId = it->mSpellId; state.mSpeed = it->mSpeed; @@ -684,6 +685,7 @@ namespace MWWorld state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; state.mToDelete = false; + state.mSlot = esm.mSlot; std::string texture; try diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 4dc250dc5f..f889250e1d 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -49,7 +49,7 @@ namespace MWWorld MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. - void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection); + void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot); void launchProjectile (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& projectile, const osg::Vec3f& pos, const osg::Quat& orient, const MWWorld::Ptr& bow, float speed, float attackStrength); @@ -107,6 +107,7 @@ namespace MWWorld ESM::EffectList mEffects; float mSpeed; + int mSlot; std::vector mSounds; std::set mSoundIds; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index da5daed295..ea455a31cf 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1862,7 +1862,7 @@ namespace MWWorld mRendering->getCamera()->setSneakOffset(0.f); int blind = 0; - auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects(); + const auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects(); if (!mGodMode) blind = static_cast(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude()); MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind))); @@ -3109,7 +3109,20 @@ namespace MWWorld { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); if (inv.getSelectedEnchantItem() != inv.end()) - cast.cast(*inv.getSelectedEnchantItem()); + { + const auto& itemPtr = *inv.getSelectedEnchantItem(); + auto [slots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); + int slot = 0; + for(std::size_t i = 0; i < slots.size(); ++i) + { + if(inv.getSlot(slots[i]) == inv.getSelectedEnchantItem()) + { + slot = slots[i]; + break; + } + } + cast.cast(itemPtr, slot); + } } } @@ -3144,9 +3157,9 @@ namespace MWWorld mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } - void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) + void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) { - mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); + mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection, slot); } void World::updateProjectilesCasters() @@ -3154,46 +3167,24 @@ namespace MWWorld mProjectileManager->updateCasters(); } - class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor - { - private: - MWWorld::Ptr mActor; - - public: - ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor) - : mActor(actor) - { - } - - void visit (MWMechanics::EffectKey key, int /*effectIndex*/, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1) override - { - const ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const auto magicEffect = store.get().find(key.mId); - if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0) - return; - const ESM::Static* castStatic; - if (!magicEffect->mHit.empty()) - castStatic = store.get().find (magicEffect->mHit); - else - castStatic = store.get().find ("VFX_DefaultHit"); - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor); - if (anim && !castStatic->mModel.empty()) - anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle); - } - }; - void World::applyLoopingParticles(const MWWorld::Ptr& ptr) { const MWWorld::Class &cls = ptr.getClass(); if (cls.isActor()) { - ApplyLoopingParticlesVisitor visitor(ptr); - cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor); - cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor); - if (cls.hasInventoryStore(ptr)) - cls.getInventoryStore(ptr).visitEffectSources(visitor); + std::set playing; + for(const auto& params : cls.getCreatureStats(ptr).getActiveSpells()) + { + for(const auto& effect : params.getEffects()) + { + if(playing.insert(effect.mEffectId).second) + { + const auto magicEffect = getStore().get().find(effect.mEffectId); + if(magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) + MWMechanics::playEffects(ptr, *magicEffect, false); + } + } + } } } @@ -3204,10 +3195,7 @@ namespace MWWorld void World::breakInvisibility(const Ptr &actor) { - actor.getClass().getCreatureStats(actor).getSpells().purgeEffect(ESM::MagicEffect::Invisibility); - actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); - if (actor.getClass().hasInventoryStore(actor)) - actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility); + actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(actor, ESM::MagicEffect::Invisibility); // Normally updated once per frame, but here it is kinda important to do it right away. MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); @@ -3700,7 +3688,7 @@ namespace MWWorld } void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, - const std::string& id, const std::string& sourceName, const bool fromProjectile) + const std::string& id, const std::string& sourceName, const bool fromProjectile, int slot) { std::map > toApply; for (const ESM::ENAMstruct& effectInfo : effects.mList) @@ -3778,7 +3766,7 @@ namespace MWWorld cast.mHitPosition = origin; cast.mId = id; cast.mSourceName = sourceName; - cast.mStack = false; + cast.mSlot = slot; ESM::EffectList effectsToApply; effectsToApply.mList = applyPair.second; cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 32fc9b101a..9ead6926c2 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -636,7 +636,7 @@ namespace MWWorld */ void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) override; - void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override; + void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) override; void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; void updateProjectilesCasters() override; @@ -681,7 +681,7 @@ namespace MWWorld void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, - const bool fromProjectile=false) override; + const bool fromProjectile=false, int slot = 0) override; void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override; diff --git a/components/esm/activespells.cpp b/components/esm/activespells.cpp index 4017a4933e..22f862b6e4 100644 --- a/components/esm/activespells.cpp +++ b/components/esm/activespells.cpp @@ -3,44 +3,67 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -namespace ESM +namespace { - - void ActiveSpells::save(ESMWriter &esm) const + void save(ESM::ESMWriter& esm, const std::vector& spells, const std::string& tag) { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + for (const auto& params : spells) { - esm.writeHNString ("ID__", it->first); - - const ActiveSpellParams& params = it->second; + esm.writeHNString (tag, params.mId); esm.writeHNT ("CAST", params.mCasterActorId); esm.writeHNString ("DISP", params.mDisplayName); + esm.writeHNT ("TYPE", params.mType); + if(params.mItem.isSet()) + params.mItem.save(esm, true, "ITEM"); + if(params.mWorsenings >= 0) + { + esm.writeHNT ("WORS", params.mWorsenings); + esm.writeHNT ("TIME", params.mNextWorsening); + } - for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) + for (auto effect : params.mEffects) { - esm.writeHNT ("MGEF", effectIt->mEffectId); - if (effectIt->mArg != -1) - esm.writeHNT ("ARG_", effectIt->mArg); - esm.writeHNT ("MAGN", effectIt->mMagnitude); - esm.writeHNT ("DURA", effectIt->mDuration); - esm.writeHNT ("EIND", effectIt->mEffectIndex); - esm.writeHNT ("LEFT", effectIt->mTimeLeft); + esm.writeHNT ("MGEF", effect.mEffectId); + if (effect.mArg != -1) + esm.writeHNT ("ARG_", effect.mArg); + esm.writeHNT ("MAGN", effect.mMagnitude); + esm.writeHNT ("MAGN", effect.mMinMagnitude); + esm.writeHNT ("MAGN", effect.mMaxMagnitude); + esm.writeHNT ("DURA", effect.mDuration); + esm.writeHNT ("EIND", effect.mEffectIndex); + esm.writeHNT ("LEFT", effect.mTimeLeft); + esm.writeHNT ("FLAG", effect.mFlags); } } } - void ActiveSpells::load(ESMReader &esm) + void load(ESM::ESMReader& esm, std::vector& spells, const char* tag) { int format = esm.getFormat(); - while (esm.isNextSub("ID__")) + while (esm.isNextSub(tag)) { - std::string spellId = esm.getHString(); - - ActiveSpellParams params; + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = esm.getHString(); esm.getHNT (params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString ("DISP"); + params.mItem.unset(); + if (format < 17) + params.mType = ESM::ActiveSpells::Type_Temporary; + else + { + esm.getHNT (params.mType, "TYPE"); + if(esm.peekNextSub("ITEM")) + params.mItem.load(esm, true, "ITEM"); + } + if(esm.isNextSub("WORS")) + { + esm.getHT(params.mWorsenings); + esm.getHNT(params.mNextWorsening, "TIME"); + } + else + params.mWorsenings = -1; // spell casting timestamp, no longer used if (esm.isNextSub("TIME")) @@ -48,11 +71,21 @@ namespace ESM while (esm.isNextSub("MGEF")) { - ActiveEffect effect; + ESM::ActiveEffect effect; esm.getHT(effect.mEffectId); effect.mArg = -1; esm.getHNOT(effect.mArg, "ARG_"); esm.getHNT (effect.mMagnitude, "MAGN"); + if (format < 17) + { + effect.mMinMagnitude = effect.mMagnitude; + effect.mMaxMagnitude = effect.mMagnitude; + } + else + { + esm.getHNT (effect.mMinMagnitude, "MAGN"); + esm.getHNT (effect.mMaxMagnitude, "MAGN"); + } esm.getHNT (effect.mDuration, "DURA"); effect.mEffectIndex = -1; esm.getHNOT (effect.mEffectIndex, "EIND"); @@ -60,10 +93,30 @@ namespace ESM effect.mTimeLeft = effect.mDuration; else esm.getHNT (effect.mTimeLeft, "LEFT"); + if (format < 17) + effect.mFlags = ESM::ActiveEffect::Flag_None; + else + esm.getHNT (effect.mFlags, "FLAG"); params.mEffects.push_back(effect); } - mSpells.insert(std::make_pair(spellId, params)); + spells.emplace_back(params); } } } + +namespace ESM +{ + + void ActiveSpells::save(ESMWriter &esm) const + { + ::save(esm, mSpells, "ID__"); + ::save(esm, mQueue, "QID_"); + } + + void ActiveSpells::load(ESMReader &esm) + { + ::load(esm, mSpells, "ID__"); + ::load(esm, mQueue, "QID_"); + } +} diff --git a/components/esm/activespells.hpp b/components/esm/activespells.hpp index 1b7f8b319c..8b5f1f1946 100644 --- a/components/esm/activespells.hpp +++ b/components/esm/activespells.hpp @@ -1,11 +1,12 @@ #ifndef OPENMW_ESM_ACTIVESPELLS_H #define OPENMW_ESM_ACTIVESPELLS_H -#include "effectlist.hpp" +#include "cellref.hpp" #include "defs.hpp" +#include "effectlist.hpp" #include -#include +#include namespace ESM { @@ -14,29 +15,53 @@ namespace ESM // Parameters of an effect concerning lasting effects. // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. - // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. struct ActiveEffect { + enum Flags + { + Flag_None = 0, + Flag_Applied = 1 << 0, + Flag_Remove = 1 << 1, + Flag_Ignore_Resistances = 1 << 2 + }; + int mEffectId; float mMagnitude; + float mMinMagnitude; + float mMaxMagnitude; int mArg; // skill or attribute float mDuration; float mTimeLeft; int mEffectIndex; + int mFlags; }; // format 0, saved games only struct ActiveSpells { + enum EffectType + { + Type_Temporary, + Type_Ability, + Type_Enchantment, + Type_Permanent, + Type_Consumable + }; + struct ActiveSpellParams { + std::string mId; std::vector mEffects; std::string mDisplayName; int mCasterActorId; + RefNum mItem; + EffectType mType; + int mWorsenings; + TimeStamp mNextWorsening; }; - typedef std::multimap TContainer; - TContainer mSpells; + std::vector mSpells; + std::vector mQueue; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index cb383992c6..d5030a6580 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -110,16 +110,31 @@ void ESM::CreatureStats::load (ESMReader &esm) mAiSequence.load(esm); mMagicEffects.load(esm); - while (esm.isNextSub("SUMM")) + if (esm.getFormat() < 17) { - int magicEffect; - esm.getHT(magicEffect); - std::string source = esm.getHNOString("SOUR"); - int effectIndex = -1; - esm.getHNOT (effectIndex, "EIND"); - int actorId; - esm.getHNT (actorId, "ACID"); - mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId; + while (esm.isNextSub("SUMM")) + { + int magicEffect; + esm.getHT(magicEffect); + std::string source = esm.getHNOString("SOUR"); + int effectIndex = -1; + esm.getHNOT (effectIndex, "EIND"); + int actorId; + esm.getHNT (actorId, "ACID"); + mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId; + mSummonedCreatures.emplace(magicEffect, actorId); + } + } + else + { + while (esm.isNextSub("SUMM")) + { + int magicEffect; + esm.getHT(magicEffect); + int actorId; + esm.getHNT (actorId, "ACID"); + mSummonedCreatures.emplace(magicEffect, actorId); + } } while (esm.isNextSub("GRAV")) @@ -214,14 +229,10 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mAiSequence.save(esm); mMagicEffects.save(esm); - for (const auto& summon : mSummonedCreatureMap) + for (const auto& [effectId, actorId] : mSummonedCreatures) { - esm.writeHNT ("SUMM", summon.first.mEffectId); - esm.writeHNString ("SOUR", summon.first.mSourceId); - int effectIndex = summon.first.mEffectIndex; - if (effectIndex != -1) - esm.writeHNT ("EIND", effectIndex); - esm.writeHNT ("ACID", summon.second); + esm.writeHNT ("SUMM", effectId); + esm.writeHNT ("ACID", actorId); } for (int key : mSummonGraveyard) @@ -235,15 +246,6 @@ void ESM::CreatureStats::save (ESMWriter &esm) const for (int i=0; i<4; ++i) mAiSettings[i].save(esm); } - - for (const auto& corprusSpell : mCorprusSpells) - { - esm.writeHNString("CORP", corprusSpell.first); - - const CorprusStats & stats = corprusSpell.second; - esm.writeHNT("WORS", stats.mWorsenings); - esm.writeHNT("TIME", stats.mNextWorsening); - } } void ESM::CreatureStats::blank() diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 13bc50008c..651b126d0e 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -40,6 +40,7 @@ namespace ESM StatState mAiSettings[4]; std::map mSummonedCreatureMap; + std::multimap mSummonedCreatures; std::vector mSummonGraveyard; ESM::TimeStamp mTradeTime; diff --git a/components/esm/magiceffects.cpp b/components/esm/magiceffects.cpp index 898e7e4b18..a1f943a93d 100644 --- a/components/esm/magiceffects.cpp +++ b/components/esm/magiceffects.cpp @@ -8,10 +8,11 @@ namespace ESM void MagicEffects::save(ESMWriter &esm) const { - for (std::map::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + for (const auto& [key, params] : mEffects) { - esm.writeHNT("EFID", it->first); - esm.writeHNT("BASE", it->second); + esm.writeHNT("EFID", key); + esm.writeHNT("BASE", params.first); + esm.writeHNT("MODI", params.second); } } @@ -19,10 +20,15 @@ void MagicEffects::load(ESMReader &esm) { while (esm.isNextSub("EFID")) { - int id, base; + int id; + std::pair params; esm.getHT(id); - esm.getHNT(base, "BASE"); - mEffects.insert(std::make_pair(id, base)); + esm.getHNT(params.first, "BASE"); + if(esm.getFormat() < 17) + params.second = 0.f; + else + esm.getHNT(params.second, "MODI"); + mEffects.emplace(id, params); } } diff --git a/components/esm/magiceffects.hpp b/components/esm/magiceffects.hpp index 5b8b0c924a..4b54692c5f 100644 --- a/components/esm/magiceffects.hpp +++ b/components/esm/magiceffects.hpp @@ -12,8 +12,8 @@ namespace ESM // format 0, saved games only struct MagicEffects { - // - std::map mEffects; + // + std::map> mEffects; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/projectilestate.cpp b/components/esm/projectilestate.cpp index 8ade9d5b2e..3421c19526 100644 --- a/components/esm/projectilestate.cpp +++ b/components/esm/projectilestate.cpp @@ -28,6 +28,7 @@ namespace ESM esm.writeHNString ("SPEL", mSpellId); esm.writeHNT ("SPED", mSpeed); + esm.writeHNT ("SLOT", mSlot); } void MagicBoltState::load(ESMReader &esm) @@ -39,6 +40,10 @@ namespace ESM esm.skipHSub(); ESM::EffectList().load(esm); // for backwards compatibility esm.getHNT (mSpeed, "SPED"); + if(esm.getFormat() < 17) + mSlot = 0; + else + esm.getHNT(mSlot, "SLOT"); if (esm.isNextSub("STCK")) // for backwards compatibility esm.skipHSub(); if (esm.isNextSub("SOUN")) // for backwards compatibility diff --git a/components/esm/projectilestate.hpp b/components/esm/projectilestate.hpp index 67ec89bb6d..84292813ce 100644 --- a/components/esm/projectilestate.hpp +++ b/components/esm/projectilestate.hpp @@ -32,6 +32,7 @@ namespace ESM { std::string mSpellId; float mSpeed; + int mSlot; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 3f8bf10c56..8a98a63419 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 16; +int ESM::SavedGame::sCurrentFormat = 17; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/spellstate.cpp b/components/esm/spellstate.cpp index 2eb1e78679..b1ddb6523c 100644 --- a/components/esm/spellstate.cpp +++ b/components/esm/spellstate.cpp @@ -8,29 +8,38 @@ namespace ESM void SpellState::load(ESMReader &esm) { - while (esm.isNextSub("SPEL")) + if(esm.getFormat() < 17) { - std::string id = esm.getHString(); - - SpellParams state; - while (esm.isNextSub("INDX")) + while (esm.isNextSub("SPEL")) { - int index; - esm.getHT(index); + std::string id = esm.getHString(); - float magnitude; - esm.getHNT(magnitude, "RAND"); + SpellParams state; + while (esm.isNextSub("INDX")) + { + int index; + esm.getHT(index); - state.mEffectRands[index] = magnitude; - } + float magnitude; + esm.getHNT(magnitude, "RAND"); - while (esm.isNextSub("PURG")) { - int index; - esm.getHT(index); - state.mPurgedEffects.insert(index); - } + state.mEffectRands[index] = magnitude; + } - mSpells[id] = state; + while (esm.isNextSub("PURG")) { + int index; + esm.getHT(index); + state.mPurgedEffects.insert(index); + } + + mSpellParams[id] = state; + mSpells.emplace_back(id); + } + } + else + { + while (esm.isNextSub("SPEL")) + mSpells.emplace_back(esm.getHString()); } // Obsolete @@ -88,30 +97,8 @@ namespace ESM void SpellState::save(ESMWriter &esm) const { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) - { - esm.writeHNString("SPEL", it->first); - - const std::map& random = it->second.mEffectRands; - for (std::map::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt) - { - esm.writeHNT("INDX", rIt->first); - esm.writeHNT("RAND", rIt->second); - } - - const std::set& purges = it->second.mPurgedEffects; - for (std::set::const_iterator pIt = purges.begin(); pIt != purges.end(); ++pIt) - esm.writeHNT("PURG", *pIt); - } - - for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) - { - esm.writeHNString("CORP", it->first); - - const CorprusStats & stats = it->second; - esm.writeHNT("WORS", stats.mWorsenings); - esm.writeHNT("TIME", stats.mNextWorsening); - } + for (const std::string& spell : mSpells) + esm.writeHNString("SPEL", spell); for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) { diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp index 55c57611a2..e7067dae8c 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm/spellstate.hpp @@ -31,13 +31,13 @@ namespace ESM struct SpellParams { - std::map mEffectRands; - std::set mPurgedEffects; + std::map mEffectRands; // + std::set mPurgedEffects; // indices of purged effects }; - typedef std::map TContainer; - TContainer mSpells; + std::vector mSpells; // FIXME: obsolete, used only for old saves + std::map mSpellParams; std::map > mPermanentSpellEffects; std::map mCorprusSpells; From 161e042e2ac72c84280bdb7005190fc3b53b2cad Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 28 Aug 2021 11:06:47 +0200 Subject: [PATCH 1387/2859] Prevent iterator invalidation when cleaning up summons --- apps/openmw/mwgui/container.cpp | 3 ++- apps/openmw/mwmechanics/summoning.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index df490943d2..de771051ef 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -277,8 +277,9 @@ namespace MWGui auto it = std::find_if(summons.begin(), summons.end(), [&] (const auto& entry) { return entry.second == creatureStats.getActorId(); }); if(it != summons.end()) { - MWMechanics::purgeSummonEffect(summoner, *it); + auto summon = *it; summons.erase(it); + MWMechanics::purgeSummonEffect(summoner, summon); break; } } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 953db077fb..c1923d4337 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -125,8 +125,9 @@ namespace MWMechanics if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) { // Purge the magic effect so a new creature can be summoned if desired - purgeSummonEffect(summoner, *it); + auto summon = *it; creatureMap.erase(it++); + purgeSummonEffect(summoner, summon); } else ++it; From b8e4f1875197621ce4587d8f42081b260ab90ddd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 28 Aug 2021 14:36:30 +0200 Subject: [PATCH 1388/2859] Clear temporary effects before unloading actors to prevent absorb effects becoming permanent --- CHANGELOG.md | 1 + apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 15 +++++++++++++-- apps/openmw/mwmechanics/actors.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 6 +++--- apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwworld/actionteleport.cpp | 4 ++-- apps/openmw/mwworld/scene.cpp | 4 ++-- apps/openmw/mwworld/scene.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 8 ++++---- apps/openmw/mwworld/worldimp.hpp | 2 +- 12 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffe687a04c..34e7829274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Feature #4595: Unique object identifier Feature #4737: Handle instance move from one cell to another Feature #5198: Implement "Magic effect expired" event + Feature #5454: Clear active spells from actor when he disappears from scene Feature #5489: MCP: Telekinesis fix for activators Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index ccc60afc27..6bedbb5b4d 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -58,7 +58,7 @@ namespace MWBase virtual void add (const MWWorld::Ptr& ptr) = 0; ///< Register an object for management - virtual void remove (const MWWorld::Ptr& ptr) = 0; + virtual void remove (const MWWorld::Ptr& ptr, bool keepActive) = 0; ///< Deregister an object for management virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index e9fc6e5c7a..22fe42c5ed 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -286,7 +286,7 @@ namespace MWBase virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) = 0; ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) = 0; + virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) = 0; ///< @return an updated Ptr virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b1aa246385..d71a8f53ee 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -210,6 +210,14 @@ void soulTrap(const MWWorld::Ptr& creature) } } } + +void removeTemporaryEffects(const MWWorld::Ptr& ptr) +{ + ptr.getClass().getCreatureStats(ptr).getActiveSpells().purge([] (const auto& spell) + { + return spell.getType() == ESM::ActiveSpells::Type_Consumable || spell.getType() == ESM::ActiveSpells::Type_Temporary; + }, ptr); +} } namespace MWMechanics @@ -1050,7 +1058,7 @@ namespace MWMechanics void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { - removeActor(ptr); + removeActor(ptr, true); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (!anim) @@ -1098,11 +1106,13 @@ namespace MWMechanics ctrl->setVisibility(visibilityRatio); } - void Actors::removeActor (const MWWorld::Ptr& ptr) + void Actors::removeActor (const MWWorld::Ptr& ptr, bool keepActive) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) { + if(!keepActive) + removeTemporaryEffects(iter->first); delete iter->second; mActors.erase(iter); } @@ -1167,6 +1177,7 @@ namespace MWMechanics { if((iter->first.isInCell() && iter->first.getCell()==cellStore) && iter->first != ignore) { + removeTemporaryEffects(iter->first); delete iter->second; mActors.erase(iter++); } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 3efe28204d..9950a591ab 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -90,7 +90,7 @@ namespace MWMechanics /// /// \note Dead actors are ignored. - void removeActor (const MWWorld::Ptr& ptr); + void removeActor (const MWWorld::Ptr& ptr, bool keepActive); ///< Deregister an actor for stats management /// /// \note Ignored, if \a ptr is not a registered actor. diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fb8b3fa2b0..5e18e737df 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -259,11 +259,11 @@ namespace MWMechanics mActors.castSpell(ptr, spellId, manualSpell); } - void MechanicsManager::remove(const MWWorld::Ptr& ptr) + void MechanicsManager::remove(const MWWorld::Ptr& ptr, bool keepActive) { if(ptr == MWBase::Environment::get().getWindowManager()->getWatchedActor()) MWBase::Environment::get().getWindowManager()->watchActor(MWWorld::Ptr()); - mActors.removeActor(ptr); + mActors.removeActor(ptr, keepActive); mObjects.removeObject(ptr); } @@ -317,7 +317,7 @@ namespace MWMechanics // HACK? The player has been changed, so a new Animation object may // have been made for them. Make sure they're properly updated. - mActors.removeActor(ptr); + mActors.removeActor(ptr, true); mActors.addActor(ptr, true); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 206b5c5420..06da2fde51 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -45,7 +45,7 @@ namespace MWMechanics void add (const MWWorld::Ptr& ptr) override; ///< Register an object for management - void remove (const MWWorld::Ptr& ptr) override; + void remove (const MWWorld::Ptr& ptr, bool keepActive) override; ///< Deregister an object for management void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) override; diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index fcfafc5f4f..56274eb9a0 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -55,10 +55,10 @@ namespace MWWorld int cellY; world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); world->moveObject(actor,world->getExterior(cellX,cellY), - mPosition.asVec3()); + mPosition.asVec3(), true, true); } else - world->moveObject(actor,world->getInterior(mCellName),mPosition.asVec3()); + world->moveObject(actor,world->getInterior(mCellName),mPosition.asVec3(), true, true); } } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 559ab4aef4..57a5bd18f7 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1010,9 +1010,9 @@ namespace MWWorld } } - void Scene::removeObjectFromScene (const Ptr& ptr) + void Scene::removeObjectFromScene (const Ptr& ptr, bool keepActive) { - MWBase::Environment::get().getMechanicsManager()->remove (ptr); + MWBase::Environment::get().getMechanicsManager()->remove (ptr, keepActive); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(ptr); if (const auto object = mPhysics->getObject(ptr)) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 3f5d7bf2f5..86cb1ee5b7 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -161,7 +161,7 @@ namespace MWWorld void addObjectToScene (const Ptr& ptr); ///< Add an object that already exists in the world model to the scene. - void removeObjectFromScene (const Ptr& ptr); + void removeObjectFromScene (const Ptr& ptr, bool keepActive = false); ///< Remove an object from the scene, but not from the world model. void removeFromPagedRefs(const Ptr &ptr); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ea455a31cf..b9b8af0987 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1114,7 +1114,7 @@ namespace MWWorld } } - MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics) + MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics, bool keepActive) { ESM::Position pos = ptr.getRefData().getPosition(); std::memcpy(pos.pos, &position, sizeof(osg::Vec3f)); @@ -1171,7 +1171,7 @@ namespace MWWorld } else if (!newCellActive && currCellActive) { - mWorldScene->removeObjectFromScene(ptr); + mWorldScene->removeObjectFromScene(ptr, keepActive); mLocalScripts.remove(ptr); removeContainerScripts (ptr); haveToMove = false; @@ -2433,7 +2433,7 @@ namespace MWWorld else { // Remove the old CharacterController - MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); + MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr(), true); mNavigator->removeAgent(getPathfindingHalfExtents(getPlayerConstPtr())); mPhysics->remove(getPlayerPtr()); mRendering->removePlayer(getPlayerPtr()); @@ -2449,7 +2449,7 @@ namespace MWWorld void World::renderPlayer() { - MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); + MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr(), true); MWWorld::Ptr player = getPlayerPtr(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 9ead6926c2..d6b7eb3fbf 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -373,7 +373,7 @@ namespace MWWorld MWWorld::Ptr moveObject (const Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) override; ///< @return an updated Ptr in case the Ptr's cell changes - MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) override; + MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) override; ///< @return an updated Ptr MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) override; From 43074347e82066c3601edf195e959a0e14d7f9f9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 31 Aug 2021 19:59:55 +0200 Subject: [PATCH 1389/2859] Prevent spell duplication --- apps/openmw/mwmechanics/spells.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 743eacfe2c..6520ae3ab3 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -141,7 +141,7 @@ namespace MWMechanics const ESM::Spell *spell = *iter; if (filter(spell)) { - mSpells.erase(iter++); + iter = mSpells.erase(iter); purged.push_back(spell->mId); } else @@ -204,7 +204,7 @@ namespace MWMechanics const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); if (spell) { - mSpells.emplace_back(spell); + addSpell(spell); if (id == state.mSelectedSpell) mSelectedSpell = id; From 63a9203dde6cd4cccc64fa81fb33ae6b3af1a4e5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 31 Aug 2021 20:07:30 +0200 Subject: [PATCH 1390/2859] Fix icon magnitude --- apps/openmw/mwgui/spellicons.cpp | 2 +- apps/openmw/mwmechanics/spelleffects.cpp | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 4a86503f46..0673446fe7 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -34,7 +34,7 @@ namespace MWGui { for(const auto& effect : params.getEffects()) { - if(!effect.mMagnitude) + if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) continue; MagicEffectInfo newEffectSource; newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 895e908bd2..96416f00c0 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -705,10 +705,15 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac return true; } const auto* magicEffect = world->getStore().get().find(effect.mEffectId); - if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) { - effect.mTimeLeft -= dt; - return false; + if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + { + effect.mTimeLeft -= dt; + return false; + } + else if(!dt) + return false; } if(effect.mEffectId == ESM::MagicEffect::Lock) { @@ -786,8 +791,9 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac float oldMagnitude = 0.f; if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) oldMagnitude = effect.mMagnitude; + float magnitude = roll(effect); //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here - effect.mMagnitude = roll(effect); + effect.mMagnitude = magnitude; if(!(magicEffect->mData.mFlags & (ESM::MagicEffect::Flags::NoMagnitude | ESM::MagicEffect::Flags::AppliedOnce))) { if(effect.mDuration != 0) @@ -808,6 +814,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac spellParams.worsen(); else applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage); + effect.mMagnitude = magnitude; magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude)); } effect.mTimeLeft -= dt; From 46ce2ff10cab2ad3a41306b254ca69e68b25aed7 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 31 Aug 2021 20:12:11 +0200 Subject: [PATCH 1391/2859] Add #4414 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34e7829274..0550725bc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map Feature #4297: Implement APPLIED_ONCE flag for magic effects + Feature #4414: Handle duration of EXTRA SPELL magic effect Feature #4595: Unique object identifier Feature #4737: Handle instance move from one cell to another Feature #5198: Implement "Magic effect expired" event From 4abcb0d7b960c79e5990e5893654512a22f9f20e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 8 Sep 2021 19:25:22 +0200 Subject: [PATCH 1392/2859] Remove applied magnitude instead of min magnitude --- apps/openmw/mwmechanics/spelleffects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 96416f00c0..24816b9ed1 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -1029,7 +1029,7 @@ void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellP auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); const auto* magicEffect = world->getStore().get().find(effect.mEffectId); if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) - magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMinMagnitude)); + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMagnitude)); removeMagicEffect(target, spellParams, effect); auto anim = world->getAnimation(target); if(anim) From e4994054ecabbd5eb5199e62ec84ff82ad57f258 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 21 Sep 2021 18:07:39 +0200 Subject: [PATCH 1393/2859] Add #5207 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0550725bc8..25df64c65c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Bug #4744: Invisible particles must still be processed Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system + Bug #5207: Loose summons can be present in scene Bug #5379: Wandering NPCs falling through cantons Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost From 2de7b8e2fba5daaedb76ef567b6e12d73fa0b349 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 29 Sep 2021 21:21:52 +0200 Subject: [PATCH 1394/2859] Allow non-biped creatures using weapons to open doors --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aipackage.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc9510f6e..9b78cdfa81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive Bug #6165: Paralyzed player character can pickup items when the inventory is open + Bug #6172: Some creatures can't open doors Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 46e4729a8a..ad0a3943ad 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -34,6 +34,11 @@ namespace const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y()); return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance); } + + bool canOpenDoors(const MWWorld::Ptr& ptr) + { + return ptr.getClass().isBipedal(ptr) || ptr.getClass().hasInventoryStore(ptr); + } } MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : @@ -118,7 +123,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed) { - if (actor.getClass().isBipedal(actor)) + if (canOpenDoors(actor)) openDoors(actor); const bool wasShortcutting = mIsShortcutting; @@ -232,7 +237,7 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor) static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); const MWWorld::Ptr door = getNearbyDoor(actor, distance); - if (!door.isEmpty() && actor.getClass().isBipedal(actor)) + if (!door.isEmpty() && canOpenDoors(actor)) { openDoors(actor); } @@ -445,7 +450,7 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0) result |= DetourNavigator::Flag_walk; - if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander) + if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander) result |= DetourNavigator::Flag_openDoor; return result; From 24ecdc37a78384c365b2bb643310a68b8e4d2eed Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 30 Sep 2021 02:59:38 +0200 Subject: [PATCH 1395/2859] Fix crash in LuaUtil::ScriptsContainer::~ScriptsContainer() --- components/lua/scriptscontainer.cpp | 6 ++++++ components/lua/scriptscontainer.hpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index b6882b0988..703381a453 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -328,6 +328,12 @@ namespace LuaUtil std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end()); } + ScriptsContainer::~ScriptsContainer() + { + for (auto& [_, script] : mScripts) + script.mHiddenData[ScriptId::KEY] = sol::nil; + } + void ScriptsContainer::removeAllScripts() { for (auto& [_, script] : mScripts) diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 0bf50b8793..69aa18e940 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -75,7 +75,7 @@ namespace LuaUtil ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix); ScriptsContainer(const ScriptsContainer&) = delete; ScriptsContainer(ScriptsContainer&&) = delete; - virtual ~ScriptsContainer() { removeAllScripts(); } + virtual ~ScriptsContainer(); // Adds package that will be available (via `require`) for all scripts in the container. // Automatically applies LuaUtil::makeReadOnly to the package. From f6c39b9f19ea0758427d4aaf10073daf528a928a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 30 Sep 2021 22:24:25 +0200 Subject: [PATCH 1396/2859] Fix issue numbers --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b78cdfa81..b21639d144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5508: Engine binary links to Qt without using it - Bug #5755: Active grid object paging - disappearing textures + Bug #5766: Active grid object paging - disappearing textures Bug #5788: Texture editing parses the selected indexes wrongly Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher @@ -47,8 +47,8 @@ Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map Feature #4595: Unique object identifier - Feature #4737: Handle instance move from one cell to another Feature #5489: MCP: Telekinesis fix for activators + Feature #5737: Handle instance move from one cell to another Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving Feature #6032: Reverse-z depth buffer From 8309910d9de491f3a10c56708aa070e434f9b018 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 30 Sep 2021 22:58:16 +0200 Subject: [PATCH 1397/2859] Restore the cell grid to its former non-exorbitant size, reducing stutter and also threw in a simple alternative fix for the actor position adjustment issue. --- apps/openmw/mwclass/activator.cpp | 8 +- apps/openmw/mwclass/activator.hpp | 4 +- apps/openmw/mwclass/actor.cpp | 2 +- apps/openmw/mwclass/actor.hpp | 2 +- apps/openmw/mwclass/container.cpp | 8 +- apps/openmw/mwclass/container.hpp | 4 +- apps/openmw/mwclass/door.cpp | 8 +- apps/openmw/mwclass/door.hpp | 4 +- apps/openmw/mwclass/light.cpp | 8 +- apps/openmw/mwclass/light.hpp | 4 +- apps/openmw/mwclass/static.cpp | 8 +- apps/openmw/mwclass/static.hpp | 4 +- apps/openmw/mwmechanics/actor.cpp | 11 ++ apps/openmw/mwmechanics/actor.hpp | 4 + apps/openmw/mwmechanics/actors.cpp | 7 + apps/openmw/mwphysics/physicssystem.cpp | 5 +- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 10 -- apps/openmw/mwworld/class.cpp | 4 +- apps/openmw/mwworld/class.hpp | 4 +- apps/openmw/mwworld/scene.cpp | 201 ++++++------------------ apps/openmw/mwworld/scene.hpp | 9 +- 22 files changed, 113 insertions(+), 208 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index e4ba7af4f0..6285bdbf7e 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,15 +38,15 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); } - void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 32bf1564c9..48a679e0b7 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,9 +17,9 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index b142fd2643..ad43bd6e5f 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -22,7 +22,7 @@ namespace MWClass MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 6a5d4c5150..886ffe4771 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -24,7 +24,7 @@ namespace MWClass ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 051fbafeb5..06980f55da 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -106,15 +106,15 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); } - void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 9407dab243..0b290a73e1 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -42,8 +42,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 70c4a27582..b5fe705ca6 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -55,9 +55,9 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) @@ -70,10 +70,10 @@ namespace MWClass } } - void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); } bool Door::isDoor() const diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 1fd59f8f52..f9288a88ce 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,8 +18,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 0281861980..69cc1a09bf 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,13 +33,13 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); assert (ref->mBase != nullptr); - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, @@ -47,11 +47,11 @@ namespace MWClass MWSound::PlayMode::Loop); } - void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ptr.get()->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } bool Light::useAnim() const diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 206abede43..e8aa4e5878 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,8 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 5cbe219580..0805ca3dd1 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,15 +23,15 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); } - void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 7c326987a8..c747eebf2f 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,8 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwmechanics/actor.cpp b/apps/openmw/mwmechanics/actor.cpp index a5c55633ac..5801ea751d 100644 --- a/apps/openmw/mwmechanics/actor.cpp +++ b/apps/openmw/mwmechanics/actor.cpp @@ -5,6 +5,7 @@ namespace MWMechanics { Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation) + : mPositionAdjusted(false) { mCharacterController.reset(new CharacterController(ptr, animation)); } @@ -58,4 +59,14 @@ namespace MWMechanics { mIsTurningToPlayer = turning; } + + void Actor::setPositionAdjusted(bool adjusted) + { + mPositionAdjusted = adjusted; + } + + bool Actor::getPositionAdjusted() const + { + return mPositionAdjusted; + } } diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp index be4f425378..c25fc1cb73 100644 --- a/apps/openmw/mwmechanics/actor.hpp +++ b/apps/openmw/mwmechanics/actor.hpp @@ -48,6 +48,9 @@ namespace MWMechanics return mEngageCombat.update(duration); } + void setPositionAdjusted(bool adjusted); + bool getPositionAdjusted() const; + private: std::unique_ptr mCharacterController; int mGreetingTimer{0}; @@ -55,6 +58,7 @@ namespace MWMechanics GreetingState mGreetingState{Greet_None}; bool mIsTurningToPlayer{false}; Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)}; + bool mPositionAdjusted; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 542f185854..1cd66291f0 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2157,7 +2157,14 @@ namespace MWMechanics continue; } else if (!isPlayer) + { iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); + if (!iter->second->getPositionAdjusted()) + { + iter->first.getClass().adjustPosition(iter->first, false); + iter->second->setPositionAdjusted(true); + } + } const bool isDead = iter->first.getClass().getCreatureStats(iter->first).isDead(); if (!isDead && (!godmode || !isPlayer) && iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 903e383370..23ece987ef 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -492,7 +492,7 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType, bool skipAnimated) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) { if (ptr.mRef->mData.mPhysicsPostponed) return; @@ -500,9 +500,6 @@ namespace MWPhysics if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - if (skipAnimated && shapeInstance->isAnimated()) - return; - assert(!getObject(ptr)); auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 52515b563f..bc358cb3c4 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -128,7 +128,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World, bool skipAnimated = false); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index 81e1248e6d..77f33fa84b 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -25,16 +25,6 @@ namespace MWWorld } }; - struct ListObjectsVisitor - { - std::vector mObjects; - - bool operator() (const MWWorld::Ptr& ptr) - { - mObjects.push_back (ptr); - return true; - } - }; } #endif diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 55e39de55e..da4dd9d99e 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -30,12 +30,12 @@ namespace MWWorld } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { } - void Class::insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Class::insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const {} bool Class::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 4779a3065b..8e451ea580 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -78,9 +78,9 @@ namespace MWWorld } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). - virtual void insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const; + virtual void insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const; virtual std::string getName (const ConstPtr& ptr) const = 0; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 559ab4aef4..765464c8de 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -104,7 +104,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) + MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -114,12 +114,6 @@ namespace std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - if (onlyPhysics && !physics.getObject(ptr) && !ptr.getClass().isActor()) - { - // When we preload physics object we need to skip animated objects. They are dependant on the scene graph which doesn't yet exist. - ptr.getClass().insertObject (ptr, model, rotation, physics, true); - return; - } const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) @@ -137,8 +131,7 @@ namespace // Restore effect particles MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); + ptr.getClass().insertObject (ptr, model, rotation, physics); MWBase::Environment::get().getLuaManager()->objectAddedToScene(ptr); } @@ -201,11 +194,10 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener* mLoadingListener; - bool mOnlyObjects; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener); bool operator() (const MWWorld::Ptr& ptr); @@ -213,8 +205,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects) - : mCell(cell), mLoadingListener(loadingListener), mOnlyObjects(onlyObjects) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener) + : mCell(cell), mLoadingListener(loadingListener) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -230,7 +222,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && (!mOnlyObjects || !ptr.getClass().isActor())) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) { try { @@ -248,16 +240,6 @@ namespace } } - struct PositionVisitor - { - bool operator() (const MWWorld::Ptr& ptr) - { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) - ptr.getClass().adjustPosition (ptr, false); - return true; - } - }; - int getCellPositionDistanceToOrigin(const std::pair& cellPosition) { return std::abs(cellPosition.first) + std::abs(cellPosition.second); @@ -324,39 +306,12 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadInactiveCell (CellStore* cell) + void Scene::unloadCell(CellStore* cell) { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - - Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); - - ListObjectsVisitor visitor; - - cell->forEach(visitor); - for (const auto& ptr : visitor.mObjects) - { - mPhysics->remove(ptr); - ptr.mRef->mData.mPhysicsPostponed = false; - } - - if (cell->getCell()->isExterior()) - { - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); - mPhysics->removeHeightField(cellX, cellY); - } - - mInactiveCells.erase(cell); - } - - void Scene::deactivateCell(CellStore* cell) - { - assert(mInactiveCells.find(cell) != mInactiveCells.end()); if (mActiveCells.find(cell) == mActiveCells.end()) return; - Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); ListAndResetObjectsVisitor visitor; @@ -367,8 +322,8 @@ namespace MWWorld if (const auto object = mPhysics->getObject(ptr)) { mNavigator.removeObject(DetourNavigator::ObjectId(object)); - if (object->isAnimated()) - mPhysics->remove(ptr); + mPhysics->remove(ptr); + ptr.mRef->mData.mPhysicsPostponed = false; } else if (mPhysics->getActor(ptr)) { @@ -386,6 +341,8 @@ namespace MWWorld { if (mPhysics->getHeightField(cellX, cellY) != nullptr) mNavigator.removeHeightfield(osg::Vec2i(cellX, cellY)); + + mPhysics->removeHeightField(cellX, cellY); } if (cell->getCell()->hasWater()) @@ -408,12 +365,11 @@ namespace MWWorld mActiveCells.erase(cell); } - void Scene::activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn) + void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn) { using DetourNavigator::HeightfieldShape; assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); mActiveCells.insert(cell); Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); @@ -425,13 +381,25 @@ namespace MWWorld if (cell->getCell()->isExterior()) { + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + const float verts = ESM::Land::LAND_SIZE; + const float worldsize = ESM::Land::REAL_SIZE; + if (data) + { + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + } + else + { + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cellX, cellY, worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + } if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) { const osg::Vec2i cellPosition(cellX, cellY); const btVector3& origin = heightField->getCollisionObject()->getWorldTransform().getOrigin(); const osg::Vec3f shift(origin.x(), origin.y(), origin.z()); - const osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* const data = land == nullptr ? nullptr : land->getData(ESM::Land::DATA_VHGT); const HeightfieldShape shape = [&] () -> HeightfieldShape { if (data == nullptr) @@ -462,7 +430,7 @@ namespace MWWorld if (respawn) cell->respawn(); - insertCell(*cell, loadingListener, false); + insertCell(*cell, loadingListener); mRendering.addCell(cell); @@ -511,49 +479,14 @@ namespace MWWorld mPreloader->notifyLoaded(cell); } - void Scene::loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener) - { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) == mInactiveCells.end()); - mInactiveCells.insert(cell); - - Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); - - if (cell->getCell()->isExterior()) - { - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; - - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); - - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) - { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); - } - else - { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cellX, cellY, worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); - } - } - - insertCell(*cell, loadingListener, true); - } - void Scene::clear() { - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); ) { auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell (cell); + unloadCell (cell); } assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -595,7 +528,7 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) + for (auto iter = mActiveCells.begin(); iter != mActiveCells.end(); ) { auto* cell = *iter++; if (cell->getCell()->isExterior()) @@ -603,16 +536,10 @@ namespace MWWorld const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); if (dx > mHalfGridSize || dy > mHalfGridSize) - deactivateCell(cell); - - if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) - unloadInactiveCell(cell); + unloadCell(cell); } else - { - deactivateCell(cell); - unloadInactiveCell(cell); - } + unloadCell (cell); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -662,7 +589,6 @@ namespace MWWorld } auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); - auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -685,26 +611,12 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); - std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), - [&] (const std::pair& lhs, const std::pair& rhs) { - return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); - }); - - // Load cells - for (const auto& [x,y] : cellsPositionsToLoadInactive) - { - if (!isCellInCollection(x, y, mInactiveCells)) - { - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadInactiveCell (cell, loadingListener); - } - } for (const auto& [x,y] : cellsPositionsToLoad) { if (!isCellInCollection(x, y, mActiveCells)) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - activateCell (cell, loadingListener, changeEvent); + loadCell (cell, loadingListener, changeEvent); } } @@ -737,17 +649,15 @@ namespace MWWorld loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadInactiveCell(cell, nullptr); - activateCell(cell, nullptr, false); + loadCell(cell, nullptr, false); - auto iter = mInactiveCells.begin(); - while (iter != mInactiveCells.end()) + auto iter = mActiveCells.begin(); + while (iter != mActiveCells.end()) { if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - deactivateCell(*iter); - unloadInactiveCell(*iter); + unloadCell(*iter); break; } @@ -785,18 +695,16 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadInactiveCell(cell, nullptr); - activateCell(cell, nullptr, false); + loadCell(cell, nullptr, false); - auto iter = mInactiveCells.begin(); - while (iter != mInactiveCells.end()) + auto iter = mActiveCells.begin(); + while (iter != mActiveCells.end()) { assert (!(*iter)->getCell()->isExterior()); if (it->mName == (*iter)->getCell()->mName) { - deactivateCell(*iter); - unloadInactiveCell(*iter); + unloadCell(*iter); break; } @@ -915,11 +823,10 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); ) { auto* cellToUnload = *iter++; - deactivateCell(cellToUnload); - unloadInactiveCell(cellToUnload); + unloadCell(cellToUnload); } assert(mActiveCells.empty()); assert(mInactiveCells.empty()); @@ -928,8 +835,7 @@ namespace MWWorld // Load cell. mPagedRefs.clear(); - loadInactiveCell (cell, loadingListener); - activateCell (cell, loadingListener, changeEvent); + loadCell(cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -979,26 +885,19 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener) { - InsertVisitor insertVisitor(cell, loadingListener, onlyObjects); + InsertVisitor insertVisitor(cell, loadingListener); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyObjects); }); - if (!onlyObjects) - { - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); - } + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); + addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 3f5d7bf2f5..12d79d3d11 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -77,7 +77,6 @@ namespace MWWorld CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; - CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -100,7 +99,7 @@ namespace MWWorld std::vector> mWorkItems; - void insertCell(CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects); + void insertCell(CellStore &cell, Loading::Listener* loadingListener); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -116,10 +115,8 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; - void unloadInactiveCell(CellStore* cell); - void deactivateCell(CellStore* cell); - void activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn); - void loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener); + void unloadCell(CellStore* cell); + void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn); public: From b5af192888c8f38a2f04f77e0cffce61cf683f5b Mon Sep 17 00:00:00 2001 From: andrewapp Date: Fri, 1 Oct 2021 09:19:26 +1000 Subject: [PATCH 1398/2859] gamepad cursor speed fix --- AUTHORS.md | 1 + apps/openmw/mwinput/controllermanager.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index a1f00bca10..2080f12a99 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -229,6 +229,7 @@ Programmers Yuri Krupenin zelurker Noah Gooder + Andrew Appuhamy (andrew-app) Documentation ------------- diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 7a91f8643b..fa10ce03cd 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -99,8 +99,8 @@ namespace MWInput // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - float xMove = xAxis * dt * 1500.0f / uiScale; - float yMove = yAxis * dt * 1500.0f / uiScale; + float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; + float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; float mouseWheelMove = -zAxis * dt * 1500.0f; if (xMove != 0 || yMove != 0 || mouseWheelMove != 0) From 2568f119a4e31ed192e54e3fecb8bd62f33194f3 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Fri, 1 Oct 2021 08:11:00 +0000 Subject: [PATCH 1399/2859] reapplies PR without npe (#3137) * avoids creating empty statesets on drawables Currently, we attempt to skip creating state on drawable nodes when this state matches the default state. This attempt is incomplete because we still create an avoidable empty stateset in the default case. * renderingmanager.cpp * nifloader.cpp * nifloader.cpp * shadervisitor.cpp --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/nifosg/nifloader.cpp | 20 +++++++++----------- components/shader/shadervisitor.cpp | 6 ++++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 07b0418f2b..be143510b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -486,6 +486,7 @@ namespace MWRender defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); mFog.reset(new FogManager()); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0432aef5b4..a564844ce6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1134,8 +1134,6 @@ namespace NifOsg trans->addChild(toAttach); parentNode->addChild(trans); } - // create partsys stateset in order to pass in ShaderVisitor like all other Drawables - partsys->getOrCreateStateSet(); } void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) @@ -1923,8 +1921,6 @@ namespace NifOsg void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { - osg::StateSet* stateset = node->getOrCreateStateSet(); - // Specular lighting is enabled by default, but there's a quirk... bool specEnabled = true; osg::ref_ptr mat (new osg::Material); @@ -2006,15 +2002,15 @@ namespace NifOsg if (blendFunc->getDestination() == GL_DST_ALPHA) blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); bool noSort = (alphaprop->flags>>13)&1; if (!noSort) - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); else - stateset->setRenderBinToInherit(); + node->getOrCreateStateSet()->setRenderBinToInherit(); } - else + else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); @@ -2025,9 +2021,9 @@ namespace NifOsg { osg::ref_ptr alphaFunc (new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f)); alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } - else + else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); @@ -2083,8 +2079,10 @@ namespace NifOsg mat = shareAttribute(mat); + osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); + if (emissiveMult != 1.f) + stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 0d0710853f..cc2b781cfd 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -116,7 +116,6 @@ namespace Shader , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) { - mRequirements.emplace_back(); } void ShaderVisitor::setForceShaders(bool force) @@ -421,7 +420,10 @@ namespace Shader void ShaderVisitor::pushRequirements(osg::Node& node) { - mRequirements.push_back(mRequirements.back()); + if (mRequirements.empty()) + mRequirements.emplace_back(); + else + mRequirements.push_back(mRequirements.back()); mRequirements.back().mNode = &node; } From 52a10a4bc076d0235a5f382ed3980d34e1059cf9 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 1 Oct 2021 12:24:29 +0200 Subject: [PATCH 1400/2859] remove one last assert --- apps/openmw/mwworld/scene.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 765464c8de..1e21dc2f57 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -829,7 +829,6 @@ namespace MWWorld unloadCell(cellToUnload); } assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); loadingListener->setProgressRange(cell->count()); From 1c9f06f74208eeb28b12a917c84ff94a53babc7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Sun, 3 Oct 2021 19:13:51 +0000 Subject: [PATCH 1401/2859] Minor UI tweaks all around OpenMW-CS --- apps/opencs/view/doc/view.cpp | 50 +++++++++++++++---------- apps/opencs/view/render/cellarrow.cpp | 8 ++-- apps/opencs/view/world/enumdelegate.cpp | 2 + 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 4514cfb585..c89437d70d 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -65,6 +65,8 @@ void CSVDoc::View::setupFileMenu() QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); connect (save, SIGNAL (triggered()), this, SLOT (save())); mSave = save; + + file->addSeparator(); QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); connect (verify, SIGNAL (triggered()), this, SLOT (verify())); @@ -80,6 +82,8 @@ void CSVDoc::View::setupFileMenu() QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); + file->addSeparator(); + QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); connect (close, SIGNAL (triggered()), this, SLOT (close())); @@ -156,17 +160,16 @@ void CSVDoc::View::setupWorldMenu() { QMenu *world = menuBar()->addMenu (tr ("World")); - QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); - connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); - - QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); - connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); - QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables"); connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView())); QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references"); connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView())); + + world->addSeparator(); + + QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); + connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands"); connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView())); @@ -177,7 +180,10 @@ void CSVDoc::View::setupWorldMenu() QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid"); connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); - world->addSeparator(); // items that don't represent single record lists follow here + world->addSeparator(); + + QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); + connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap"); connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); @@ -187,14 +193,19 @@ void CSVDoc::View::setupMechanicsMenu() { QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics")); + QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); + connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); + + QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); + connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); + QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals"); connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings"); connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); - - QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); - connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); + + mechanics->addSeparator(); QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells"); connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); @@ -204,9 +215,6 @@ void CSVDoc::View::setupMechanicsMenu() QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects"); connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView())); - - QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); - connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); } void CSVDoc::View::setupCharacterMenu() @@ -227,21 +235,25 @@ void CSVDoc::View::setupCharacterMenu() QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns"); connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); + + QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); + connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); + characters->addSeparator(); + QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics"); connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); - QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); - connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); - QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); + + characters->addSeparator(); + + QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); + connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); - - QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); - connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); } void CSVDoc::View::setupAssetsMenu() diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index a654171fce..776dbf7890 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -60,7 +60,7 @@ void CSVRender::CellArrow::adjustTransform() { // position const int cellSize = Constants::CellSizeInUnits; - const int offset = cellSize / 2 + 800; + const int offset = cellSize / 2 + 600; int x = mCoordinates.getX()*cellSize + cellSize/2; int y = mCoordinates.getY()*cellSize + cellSize/2; @@ -92,9 +92,9 @@ void CSVRender::CellArrow::buildShape() { osg::ref_ptr geometry (new osg::Geometry); - const int arrowWidth = 4000; - const int arrowLength = 1500; - const int arrowHeight = 500; + const int arrowWidth = 2700; + const int arrowLength = 1350; + const int arrowHeight = 300; osg::Vec3Array *vertices = new osg::Vec3Array; for (int i2=0; i2<2; ++i2) diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 65ded46c7f..2681a398b2 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -78,6 +78,8 @@ QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptio for (std::vector >::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) comboBox->addItem (iter->second); + + comboBox->setMaxVisibleItems(20); return comboBox; } From 686268b2f90de54a39000b5424fe02c125d6c034 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 3 Oct 2021 21:39:43 +0200 Subject: [PATCH 1402/2859] Remove incorrect changelog entries --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7ef5b62a..9a813d704f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,6 @@ Bug #5508: Engine binary links to Qt without using it Bug #5596: Effects in constant spells should not be merged Bug #5621: Drained stats cannot be restored - Bug #5755: Active grid object paging - disappearing textures Bug #5766: Active grid object paging - disappearing textures Bug #5788: Texture editing parses the selected indexes wrongly Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention @@ -45,7 +44,6 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop - Bug #6223: Some Constant Effect Bound Items inconsistencies Bug #6273: Respawning NPCs rotation is inconsistent Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death @@ -59,7 +57,6 @@ Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4414: Handle duration of EXTRA SPELL magic effect Feature #4595: Unique object identifier - Feature #4737: Handle instance move from one cell to another Feature #5198: Implement "Magic effect expired" event Feature #5454: Clear active spells from actor when he disappears from scene Feature #5489: MCP: Telekinesis fix for activators From d680aa26e9033615befede2e915dc0b67a69869c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 3 Oct 2021 21:58:10 +0200 Subject: [PATCH 1403/2859] Disallow switch fallthrough --- CMakeLists.txt | 4 ++++ apps/opencs/model/world/refidadapterimp.hpp | 1 + apps/openmw/mwmechanics/spelleffects.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b8350a04b..ae4584a4cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -543,6 +543,10 @@ if (BUILD_OPENCS) add_subdirectory (extern/osgQt) endif() +if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=implicit-fallthrough") +endif() + # Components add_subdirectory (components) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 84fec5bed0..c35d3c5a7c 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1870,6 +1870,7 @@ namespace CSMWorld content.mWander.mDuration = static_cast(value.toInt()); else return; // return without saving + break; case 3: if (content.mType == ESM::AI_Wander) content.mWander.mTimeOfDay = static_cast(value.toInt()); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 24816b9ed1..fa2733d837 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -453,6 +453,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co } addBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); // left gauntlet added below + [[fallthrough]]; case ESM::MagicEffect::BoundDagger: case ESM::MagicEffect::BoundLongsword: case ESM::MagicEffect::BoundMace: @@ -920,6 +921,7 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara break; case ESM::MagicEffect::BoundGloves: removeBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); + [[fallthrough]]; case ESM::MagicEffect::BoundDagger: case ESM::MagicEffect::BoundLongsword: case ESM::MagicEffect::BoundMace: From 49da33a12981b1af7a91c5d1bd5e91fdc97537b6 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Mon, 4 Oct 2021 08:12:15 +0800 Subject: [PATCH 1404/2859] comiit --- apps/openmw/mwgui/tradewindow.cpp | 23 +++++++++++++++++++++-- apps/openmw/mwgui/tradewindow.hpp | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 19ea383483..d236305f37 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -52,6 +52,7 @@ namespace MWGui , mItemToSell(-1) , mCurrentBalance(0) , mCurrentMerchantOffer(0) + , mReceiveMoney(false) { getWidget(mFilterAll, "AllButton"); getWidget(mFilterWeapon, "WeaponButton"); @@ -405,10 +406,15 @@ namespace MWGui void TradeWindow::onBalanceValueChanged(int value) { + int previousBalance = mCurrentBalance; + // Entering a "-" sign inverts the buying/selling state mCurrentBalance = (mCurrentBalance >= 0 ? 1 : -1) * value; updateLabels(); + if (mReceiveMoney && mCurrentBalance == 0) + mCurrentBalance = previousBalance; + if (value != std::abs(value)) mTotalBalance->setValue(std::abs(value)); } @@ -434,10 +440,23 @@ namespace MWGui { MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + MyGUI::utility::toString(playerGold)); - if (mCurrentBalance < 0) + TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + const std::vector& playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); + const std::vector& merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); + + if (playerBorrowed.empty() && merchantBorrowed.empty()) { + mCurrentBalance = 0; + } + else if (playerBorrowed.empty()) { + mReceiveMoney = false; + } + else if (merchantBorrowed.empty()) { + mReceiveMoney = true; + } + + if (mCurrentBalance < 0 || mReceiveMoney) { mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalCost}"); } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index f82d7b0f72..523cb84098 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -79,6 +79,7 @@ namespace MWGui int mCurrentBalance; int mCurrentMerchantOffer; + bool mReceiveMoney; void sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance void buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem); ///< only used for adjusting the gold balance From 0c1b78c42a3d4a93471fdd70db4492e76d9cb305 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Mon, 4 Oct 2021 02:16:01 +0000 Subject: [PATCH 1405/2859] Update tradewindow.cpp curly --- apps/openmw/mwgui/tradewindow.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 4b8279065c..35466059a2 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -446,13 +446,16 @@ namespace MWGui const std::vector& playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); const std::vector& merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); - if (playerBorrowed.empty() && merchantBorrowed.empty()) { + if (playerBorrowed.empty() && merchantBorrowed.empty()) + { mCurrentBalance = 0; } - else if (playerBorrowed.empty()) { + else if (playerBorrowed.empty()) + { mReceiveMoney = false; } - else if (merchantBorrowed.empty()) { + else if (merchantBorrowed.empty()) + { mReceiveMoney = true; } From aaf7b423d6796169f382883a669ebfb9c248dc96 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 4 Oct 2021 08:56:55 +0000 Subject: [PATCH 1406/2859] adds a replacement for osg::NodeCallback (#3144) * nodecallback.hpp * lightmanager.hpp * lightmanager.cpp * lightmanager.hpp * nodecallback.hpp * nodecallback.hpp * [ci skip] * lightmanager.hpp * nodecallback.hpp * nodecallback.hpp * lightmanager.cpp * lightmanager.cpp * nodecallback.hpp * [ci skip] * [ci skip] * controller.cpp * [ci skip] * osgacontroller.cpp * keyframe.hpp * controller.hpp * keyframe.hpp * [ci skip] * keyframe.hpp * animation.hpp * [ci skip] * weaponanimation.cpp * nodecallback.hpp --- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwrender/weaponanimation.cpp | 2 +- apps/openmw/mwrender/weaponanimation.hpp | 2 +- components/nifosg/controller.cpp | 1 + components/nifosg/controller.hpp | 5 +-- components/sceneutil/keyframe.hpp | 8 ++--- components/sceneutil/lightmanager.cpp | 24 ++++---------- components/sceneutil/lightmanager.hpp | 7 ++-- components/sceneutil/nodecallback.hpp | 41 ++++++++++++++++++++++++ components/sceneutil/osgacontroller.cpp | 2 +- components/sceneutil/osgacontroller.hpp | 5 +-- 11 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 components/sceneutil/nodecallback.hpp diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ffcd9b3260..c2f0f351f3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -245,7 +245,7 @@ protected: // Keep track of controllers that we added to our scene graph. // We may need to rebuild these controllers when the active animation groups / sources change. - std::vector, osg::ref_ptr>> mActiveControllers; + std::vector, osg::ref_ptr>> mActiveControllers; std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index fb92a4980a..b89cfd8df1 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -173,7 +173,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) } void WeaponAnimation::addControllers(const std::map >& nodes, - std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) + std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) { for (int i=0; i<2; ++i) { diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index 333dccc915..1f614463a6 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -43,7 +43,7 @@ namespace MWRender /// Add WeaponAnimation-related controllers to \a nodes and store the added controllers in \a map. void addControllers(const std::map >& nodes, - std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); + std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); void deleteControllers(); diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index d48c55ad7d..c8a6ef74ab 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -72,6 +72,7 @@ KeyframeController::KeyframeController() KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) + , SceneUtil::NodeCallback(copy, copyop) , mRotations(copy.mRotations) , mXRotations(copy.mXRotations) , mYRotations(copy.mYRotations) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index b459166931..078b7e14ce 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -223,7 +224,7 @@ namespace NifOsg std::vector mKeyFrames; }; - class KeyframeController : public SceneUtil::KeyframeController + class KeyframeController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: // This is used if there's no interpolator but there is data (Morrowind meshes). @@ -241,7 +242,7 @@ namespace NifOsg osg::Vec3f getTranslation(float time) const override; - void operator() (osg::Node*, osg::NodeVisitor*) override; + void operator() (osg::Node*, osg::NodeVisitor*); private: QuaternionInterpolator mRotations; diff --git a/components/sceneutil/keyframe.hpp b/components/sceneutil/keyframe.hpp index e09541cb9a..5be6924a09 100644 --- a/components/sceneutil/keyframe.hpp +++ b/components/sceneutil/keyframe.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include #include @@ -11,20 +11,18 @@ namespace SceneUtil { - class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller + class KeyframeController : public SceneUtil::Controller, public virtual osg::Callback { public: KeyframeController() {} KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) + : osg::Callback(copy, copyop) , SceneUtil::Controller(copy) {} META_Object(SceneUtil, KeyframeController) virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } - - virtual void operator() (osg::Node* node, osg::NodeVisitor* nodeVisitor) override { traverse(node, nodeVisitor); } }; /// Wrapper object containing an animation track as a ref-countable osg::Object. diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index ba60cb3185..09fa302457 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -605,19 +605,19 @@ namespace SceneUtil // Set on a LightSource. Adds the light source to its light manager for the current frame. // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager. - class CollectLightCallback : public osg::NodeCallback + class CollectLightCallback : public SceneUtil::NodeCallback { public: CollectLightCallback() : mLightManager(nullptr) { } CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , mLightManager(nullptr) { } META_Object(SceneUtil, SceneUtil::CollectLightCallback) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (!mLightManager) { @@ -637,19 +637,11 @@ namespace SceneUtil }; // Set on a LightManager. Clears the data from the previous frame. - class LightManagerUpdateCallback : public osg::NodeCallback + class LightManagerUpdateCallback : public SceneUtil::NodeCallback { public: - LightManagerUpdateCallback() - { } - - LightManagerUpdateCallback(const LightManagerUpdateCallback& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) - { } - - META_Object(SceneUtil, LightManagerUpdateCallback) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { LightManager* lightManager = static_cast(node); lightManager->update(nv->getTraversalNumber()); @@ -1285,12 +1277,10 @@ namespace SceneUtil mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } - void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) + void LightListCallback::operator()(osg::Node *node, osgUtil::CullVisitor *cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - bool pushedState = pushLightState(node, cv); - traverse(node, nv); + traverse(node, cv); if (pushedState) cv->popStateSet(); } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 6dbe7a3f75..4048bf9b09 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -16,6 +16,7 @@ #include #include +#include namespace osgUtil { @@ -254,7 +255,7 @@ namespace SceneUtil /// starting point is to attach a LightListCallback to each game object's base node. /// @note Not thread safe for CullThreadPerCamera threading mode. /// @note Due to lack of OSG support, the callback does not work on Drawables. - class LightListCallback : public osg::NodeCallback + class LightListCallback : public SceneUtil::NodeCallback { public: LightListCallback() @@ -262,7 +263,7 @@ namespace SceneUtil , mLastFrameNumber(0) {} LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop) - : osg::Object(copy, copyop), osg::NodeCallback(copy, copyop) + : osg::Object(copy, copyop), SceneUtil::NodeCallback(copy, copyop) , mLightManager(copy.mLightManager) , mLastFrameNumber(0) , mIgnoredLightSources(copy.mIgnoredLightSources) @@ -270,7 +271,7 @@ namespace SceneUtil META_Object(SceneUtil, LightListCallback) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::Node* node, osgUtil::CullVisitor* nv); bool pushLightState(osg::Node* node, osgUtil::CullVisitor* nv); diff --git a/components/sceneutil/nodecallback.hpp b/components/sceneutil/nodecallback.hpp new file mode 100644 index 0000000000..6f0140d64c --- /dev/null +++ b/components/sceneutil/nodecallback.hpp @@ -0,0 +1,41 @@ +#ifndef SCENEUTIL_NODECALLBACK_H +#define SCENEUTIL_NODECALLBACK_H + +#include + +namespace osg +{ + class Node; + class NodeVisitor; +} + +namespace SceneUtil +{ + +template +class NodeCallback : public virtual osg::Callback +{ +public: + NodeCallback(){} + NodeCallback(const NodeCallback& nc,const osg::CopyOp& copyop): + osg::Callback(nc, copyop) {} + META_Object(SceneUtil, NodeCallback) + + bool run(osg::Object* object, osg::Object* data) override + { + static_cast(this)->operator()((NodeType)object, (VisitorType)data->asNodeVisitor()); + return true; + } + + template + void traverse(NodeType object, VT data) + { + if (_nestedCallback.valid()) + _nestedCallback->run(object, data); + else + data->traverse(*object); + } +}; + +} +#endif diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 02c9558ab3..b986c92884 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -102,7 +102,7 @@ namespace SceneUtil apply(static_cast(node)); } - OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) + OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop), SceneUtil::NodeCallback(copy, copyop) , mEmulatedAnimations(copy.mEmulatedAnimations) { mLinker = nullptr; diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 46538fa813..427a79ca97 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -44,7 +45,7 @@ namespace SceneUtil Resource::Animation* mAnimation; }; - class OsgAnimationController : public SceneUtil::KeyframeController + class OsgAnimationController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: /// @brief Handles the animation for osgAnimation formats @@ -61,7 +62,7 @@ namespace SceneUtil void update(float time, const std::string& animationName); /// @brief Called every frame for osgAnimation - void operator() (osg::Node*, osg::NodeVisitor*) override; + void operator() (osg::Node*, osg::NodeVisitor*); /// @brief Sets details of the animations void setEmulatedAnimations(const std::vector& emulatedAnimations); From 61168c3583870cc0fc6c8d91a9839e3b9489c086 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 4 Oct 2021 13:12:54 +0400 Subject: [PATCH 1407/2859] Add missing changelog entries for 0.47 (#3145) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7ef5b62a..cfa8c2e79f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -209,6 +209,7 @@ Bug #6043: Actor can have torch missing when torch animation is played Bug #6047: Mouse bindings can be triggered during save loading Bug #6136: Game freezes when NPCs try to open doors that are about to be closed + Bug #6294: Game crashes with empty pathgrid Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu @@ -238,6 +239,7 @@ Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload Feature #5545: Option to allow stealing from an unconscious NPC during combat + Feature #5551: Do not reboot PC after OpenMW installation on Windows Feature #5563: Run physics update in background thread Feature #5579: MCP SetAngle enhancement Feature #5580: Service refusal filtering @@ -252,6 +254,7 @@ Feature #5814: Bsatool should be able to create BSA archives, not only to extract it Feature #5828: Support more than 8 lights Feature #5910: Fall back to delta time when physics can't keep up + Feature #5980: Support Bullet with double precision instead of one with single precision Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes Feature #6033: Include pathgrid to navigation mesh Feature #6034: Find path based on area cost depending on NPC stats From e4a9eefc2aceb75d491f98e9b36985f70f93e7ee Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 4 Oct 2021 10:12:00 +0000 Subject: [PATCH 1408/2859] uses a bitfield in refdata.hpp (#3143) * uses a bitfield in refdata.hpp With this simple change we pack boolean values to reduce the memory requirements of game objects. * refdata.hpp [ci skip] * refdata.cpp --- apps/openmw/mwworld/refdata.cpp | 8 ++++---- apps/openmw/mwworld/refdata.hpp | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 7398aef77e..f4c4beb7e8 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -65,21 +65,21 @@ namespace MWWorld } RefData::RefData (const ESM::CellRef& cellRef) - : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), + : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mPhysicsPostponed(false), mCount (1), mPosition (cellRef.mPos), mCustomData (nullptr), - mChanged(false), mFlags(0), mPhysicsPostponed(false) // Loading from ESM/ESP files -> assume unchanged + mChanged(false), mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } RefData::RefData (const ESM::ObjectState& objectState, bool deletedByContentFile) : mBaseNode(nullptr), mDeletedByContentFile(deletedByContentFile), - mEnabled (objectState.mEnabled != 0), + mEnabled (objectState.mEnabled != 0), mPhysicsPostponed(false), mCount (objectState.mCount), mPosition (objectState.mPosition), mAnimationState(objectState.mAnimationState), mCustomData (nullptr), - mChanged(true), mFlags(objectState.mFlags), mPhysicsPostponed(false) // Loading from a savegame -> assume changed + mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed { // "Note that the ActivationFlag_UseEnabled is saved to the reference, // which will result in permanently suppressed activation if the reference script is removed. diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index d90224b9bb..c98eb0f2af 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -41,9 +41,12 @@ namespace MWWorld /// separate delete flag used for deletion by a content file /// @note not stored in the save game file. - bool mDeletedByContentFile; + bool mDeletedByContentFile:1; - bool mEnabled; + bool mEnabled:1; + public: + bool mPhysicsPostponed:1; + private: /// 0: deleted int mCount; @@ -63,9 +66,6 @@ namespace MWWorld unsigned int mFlags; public: - - bool mPhysicsPostponed; - RefData(); /// @param cellRef Used to copy constant data such as position into this class where it can From 14d15dcfac77647450e30561153cce3c2aac1f74 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 4 Oct 2021 10:20:33 +0000 Subject: [PATCH 1409/2859] cleans up osgacontroller.cpp (#3142) `handle_stateset` is not needed because `UpdateMatrixTransform` is a `NodeCallback` only allowed to be set on a `Node`. `Geode` and `Drawable` do not need explicit logic because they are both derived from `Node`. --- components/sceneutil/osgacontroller.cpp | 38 ++----------------------- components/sceneutil/osgacontroller.hpp | 10 ------- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index b986c92884..520b2d177b 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -1,19 +1,12 @@ #include -#include #include #include #include -#include #include -#include #include -#include -#include #include -#include -#include #include #include @@ -55,19 +48,6 @@ namespace SceneUtil } } - void LinkVisitor::handle_stateset(osg::StateSet* stateset) - { - if (!stateset) - return; - const osg::StateSet::AttributeList& attributeList = stateset->getAttributeList(); - for (auto attribute : attributeList) - { - osg::StateAttribute* sattr = attribute.second.first.get(); - osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(sattr->getUpdateCallback()); //Can this even be in sa? - if (umt) link(umt); - } - } - void LinkVisitor::setAnimation(Resource::Animation* animation) { mAnimation = animation; @@ -75,10 +55,6 @@ namespace SceneUtil void LinkVisitor::apply(osg::Node& node) { - osg::StateSet* st = node.getStateSet(); - if (st) - handle_stateset(st); - osg::Callback* cb = node.getUpdateCallback(); while (cb) { @@ -88,18 +64,8 @@ namespace SceneUtil cb = cb->getNestedCallback(); } - traverse( node ); - } - - void LinkVisitor::apply(osg::Geode& node) - { - for (unsigned int i = 0; i < node.getNumDrawables(); i++) - { - osg::Drawable* drawable = node.getDrawable(i); - if (drawable && drawable->getStateSet()) - handle_stateset(drawable->getStateSet()); - } - apply(static_cast(node)); + if (node.getNumChildrenRequiringUpdateTraversal()) + traverse( node ); } OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop), SceneUtil::NodeCallback(copy, copyop) diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 427a79ca97..26212a3b99 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -3,13 +3,7 @@ #include #include -#include #include -#include -#include -#include -#include -#include #include #include @@ -33,14 +27,10 @@ namespace SceneUtil virtual void link(osgAnimation::UpdateMatrixTransform* umt); - virtual void handle_stateset(osg::StateSet* stateset); - virtual void setAnimation(Resource::Animation* animation); virtual void apply(osg::Node& node) override; - virtual void apply(osg::Geode& node) override; - protected: Resource::Animation* mAnimation; }; From 3f731cd102cefe246ad897bc9c5ec8c2d720c6e5 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 4 Oct 2021 20:00:31 +0000 Subject: [PATCH 1410/2859] attempts to fix spellcasting freezes (#3146) Firstly, this PR reintroduces commit "Recreate a special case for IntersectionVisitor on QuadTreeWorld" we forgot to reapply while reverting a revert commit. Secondly, in cases we still need to build a view for an intersection visitor, we now use the available `osgUtil::IntersectionVisitor::getReferenceEyePoint` instead of falling back to the origin position that was previously causing long rebuild times. --- components/terrain/quadtreeworld.cpp | 6 +++--- components/terrain/viewdata.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 4baf08149d..7e5cd001ba 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -456,13 +456,13 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) osg::Object * viewer = isCullVisitor ? static_cast(&nv)->getCurrentCamera() : nullptr; bool needsUpdate = true; - ViewData *vd = mViewDataMap->getViewData(viewer, nv.getViewPoint(), mActiveGrid, needsUpdate); - + osg::Vec3f viewPoint = viewer ? nv.getViewPoint() : nv.getEyePoint(); + ViewData *vd = mViewDataMap->getViewData(viewer, viewPoint, mActiveGrid, needsUpdate); if (needsUpdate) { vd->reset(); DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); - mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); + mRootNode->traverseNodes(vd, viewPoint, &lodCallback); } const float cellWorldSize = mStorage->getCellWorldSize(); diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index c5070fdf0d..3ebc99f1df 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -143,7 +143,7 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - float shortestDist = mReuseDistance*mReuseDistance; + float shortestDist = viewer ? mReuseDistance*mReuseDistance : std::numeric_limits::max(); const ViewData* mostSuitableView = nullptr; for (const ViewData* other : mUsedViews) { @@ -157,12 +157,12 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } } } - if (mostSuitableView) + if (mostSuitableView && mostSuitableView != vd) { vd->copyFrom(*mostSuitableView); return vd; } - else + else if (!mostSuitableView) { vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); From f28d6738bf5c946e8b3789342d0922f39458f537 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Mon, 4 Oct 2021 20:44:56 +0000 Subject: [PATCH 1411/2859] Update tradewindow.cpp --- apps/openmw/mwgui/tradewindow.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 35466059a2..48457ee6d9 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -52,7 +52,6 @@ namespace MWGui , mItemToSell(-1) , mCurrentBalance(0) , mCurrentMerchantOffer(0) - , mReceiveMoney(false) { getWidget(mFilterAll, "AllButton"); getWidget(mFilterWeapon, "WeaponButton"); @@ -412,7 +411,7 @@ namespace MWGui mCurrentBalance = (mCurrentBalance >= 0 ? 1 : -1) * value; updateLabels(); - if (mReceiveMoney && mCurrentBalance == 0) + if (mCurrentBalance == 0) mCurrentBalance = previousBalance; if (value != std::abs(value)) @@ -424,6 +423,7 @@ namespace MWGui // prevent overflows, and prevent entering INT_MIN since abs(INT_MIN) is undefined if (mCurrentBalance == std::numeric_limits::max() || mCurrentBalance == std::numeric_limits::min()+1) return; + if (mTotalBalance->getValue() == 0) mCurrentBalance = 0; if (mCurrentBalance < 0) mCurrentBalance -= 1; else mCurrentBalance += 1; updateLabels(); @@ -431,6 +431,7 @@ namespace MWGui void TradeWindow::onDecreaseButtonTriggered() { + if (mTotalBalance->getValue() == 0) mCurrentBalance = 0; if (mCurrentBalance < 0) mCurrentBalance += 1; else mCurrentBalance -= 1; updateLabels(); @@ -450,16 +451,8 @@ namespace MWGui { mCurrentBalance = 0; } - else if (playerBorrowed.empty()) - { - mReceiveMoney = false; - } - else if (merchantBorrowed.empty()) - { - mReceiveMoney = true; - } - if (mCurrentBalance < 0 || mReceiveMoney) + if (mCurrentBalance < 0) { mTotalBalanceLabel->setCaptionWithReplacing("#{sTotalCost}"); } From d9b254178f6ee2da388d95bb6ab1d04b3c73bc07 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Mon, 4 Oct 2021 20:47:45 +0000 Subject: [PATCH 1412/2859] Update tradewindow.hpp remover redundant --- apps/openmw/mwgui/tradewindow.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index e39d1ebd8e..5ace09e8e2 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -81,7 +81,6 @@ namespace MWGui int mCurrentBalance; int mCurrentMerchantOffer; - bool mReceiveMoney; void sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance void buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem); ///< only used for adjusting the gold balance From 2a1cb939baadc0a0ad3c015a202099055ff83f16 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Mon, 4 Oct 2021 20:50:40 +0000 Subject: [PATCH 1413/2859] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7ef5b62a..44b7634ebb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,12 +46,14 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6223: Some Constant Effect Bound Items inconsistencies + Bug #6258: Barter menu glitches out when modifying prices Bug #6273: Respawning NPCs rotation is inconsistent Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6302: Teleporting disabled actor breaks its disabled state Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken + Bug #6322: Total sold/cost should reset to 0 when there are no items offered Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console From 4ce8c1e1ab4797126cf1ac59e9df9ce51ac50152 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Mon, 4 Oct 2021 20:54:28 +0000 Subject: [PATCH 1414/2859] Update AUTHORS.md --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 2080f12a99..8ca9db90b9 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -177,6 +177,7 @@ Programmers PlutonicOverkill Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) + Randy Davin (Kindi) rdimesio rexelion riothamus From 8e9851c97c43596b269fbb9d739ff28999b06729 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 11:52:19 +0000 Subject: [PATCH 1415/2859] npcanimation.cpp (#3148) --- apps/openmw/mwrender/npcanimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index bcb2d36f5d..f430432659 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -403,7 +403,7 @@ public: { osgUtil::CullVisitor* cv = static_cast(nv); float fov, aspect, zNear, zFar; - if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar)) + if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar) && std::abs(fov-mFov) > 0.001) { fov = mFov; osg::ref_ptr newProjectionMatrix = new osg::RefMatrix(); From 9f58616aa032a716013d543788ba26b0727c7209 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 12:02:49 +0000 Subject: [PATCH 1416/2859] fixes -Wreorder warning --- apps/openmw/mwworld/refdata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f4c4beb7e8..e8c5ba35e4 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -55,7 +55,7 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (nullptr), mChanged(false), mFlags(0), mPhysicsPostponed(false) + : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mPhysicsPostponed(false), mCount (1), mCustomData (nullptr), mChanged(false), mFlags(0) { for (int i=0; i<3; ++i) { From b2af81bc18bc66f00551731d59a2699ebacc3a4e Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 12:21:12 +0000 Subject: [PATCH 1417/2859] converts remaining osg::NodeCallback (#3147) With this PR we convert remaining instantiations of the deprecated osg::NodeCallback in Open MW to SceneUtil::NodeCallback. --- apps/openmw/mwrender/animation.cpp | 16 +++--- apps/openmw/mwrender/animation.hpp | 5 +- apps/openmw/mwrender/camera.cpp | 9 ++- apps/openmw/mwrender/camera.hpp | 4 +- apps/openmw/mwrender/characterpreview.cpp | 13 ++--- apps/openmw/mwrender/globalmap.cpp | 9 +-- apps/openmw/mwrender/localmap.cpp | 8 +-- apps/openmw/mwrender/npcanimation.cpp | 40 +------------- apps/openmw/mwrender/npcanimation.hpp | 4 +- apps/openmw/mwrender/postprocessor.cpp | 12 ++-- apps/openmw/mwrender/rotatecontroller.cpp | 16 ++++-- apps/openmw/mwrender/rotatecontroller.hpp | 15 +++-- apps/openmw/mwrender/sky.cpp | 31 +++++------ apps/openmw/mwrender/water.cpp | 37 +++++-------- apps/openmw/mwrender/water.hpp | 5 +- apps/openmw/mwworld/projectilemanager.cpp | 9 ++- .../myguiplatform/myguirendermanager.cpp | 16 ++---- components/nifosg/controller.cpp | 55 +++++++++---------- components/nifosg/controller.hpp | 43 +++++++++------ components/nifosg/nifloader.cpp | 11 ++-- components/nifosg/particle.cpp | 6 +- components/nifosg/particle.hpp | 10 ++-- components/resource/keyframemanager.cpp | 1 + components/resource/scenemanager.cpp | 10 ++-- components/sceneutil/lightcontroller.cpp | 7 +-- components/sceneutil/lightcontroller.hpp | 9 +-- components/sceneutil/lightmanager.cpp | 27 ++++----- components/sceneutil/rtt.cpp | 14 ++--- components/sceneutil/statesetupdater.cpp | 2 +- components/sceneutil/statesetupdater.hpp | 6 +- components/sceneutil/util.cpp | 10 ++-- components/sceneutil/util.hpp | 1 - components/terrain/world.hpp | 7 ++- 33 files changed, 206 insertions(+), 262 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1dc42db47c..475c656cc9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -87,22 +87,22 @@ namespace std::vector > mToRemove; }; - class DayNightCallback : public osg::NodeCallback + class DayNightCallback : public SceneUtil::NodeCallback { public: DayNightCallback() : mCurrentState(0) { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Switch* node, osg::NodeVisitor* nv) { unsigned int state = MWBase::Environment::get().getWorld()->getNightDayMode(); - const unsigned int newState = node->asGroup()->getNumChildren() > state ? state : 0; + const unsigned int newState = node->getNumChildren() > state ? state : 0; if (newState != mCurrentState) { mCurrentState = newState; - node->asSwitch()->setSingleChildOn(mCurrentState); + node->setSingleChildOn(mCurrentState); } traverse(node, nv); @@ -472,20 +472,18 @@ namespace MWRender } } - class ResetAccumRootCallback : public osg::NodeCallback + class ResetAccumRootCallback : public SceneUtil::NodeCallback { public: - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix mat = transform->getMatrix(); osg::Vec3f position = mat.getTrans(); position = osg::componentMultiply(mResetAxes, position); mat.setTrans(position); transform->setMatrix(mat); - traverse(node, nv); + traverse(transform, nv); } void setAccumulate(const osg::Vec3f& accumulate) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index c2f0f351f3..84788c1e2d 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -506,7 +507,7 @@ public: bool canBeHarvested() const override; }; -class UpdateVfxCallback : public osg::NodeCallback +class UpdateVfxCallback : public SceneUtil::NodeCallback { public: UpdateVfxCallback(EffectParams& params) @@ -519,7 +520,7 @@ public: bool mFinished; EffectParams mParams; - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::Node* node, osg::NodeVisitor* nv); private: double mStartingTime; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index edb017c2a2..e750fcad46 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -25,7 +26,7 @@ namespace { -class UpdateRenderCameraCallback : public osg::NodeCallback +class UpdateRenderCameraCallback : public SceneUtil::NodeCallback { public: UpdateRenderCameraCallback(MWRender::Camera* cam) @@ -33,12 +34,10 @@ public: { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Camera* cam, osg::NodeVisitor* nv) { - osg::Camera* cam = static_cast(node); - // traverse first to update animations, in case the camera is attached to an animated node - traverse(node, nv); + traverse(cam, nv); mCamera->updateCamera(cam); } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 9e2b608dfd..1a5477e89c 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -12,7 +12,7 @@ namespace osg { class Camera; - class NodeCallback; + class Callback; class Node; } @@ -82,7 +82,7 @@ namespace MWRender void updatePosition(); float getCameraDistanceCorrection() const; - osg::ref_ptr mUpdateCallback; + osg::ref_ptr mUpdateCallback; // Used to rotate player to the direction of view after exiting preview or vanity mode. osg::Vec3f mDeferredRotation; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 44b7b2f457..51e5a536f7 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" @@ -36,7 +37,7 @@ namespace MWRender { - class DrawOnceCallback : public osg::NodeCallback + class DrawOnceCallback : public SceneUtil::NodeCallback { public: DrawOnceCallback () @@ -45,7 +46,7 @@ namespace MWRender { } - void operator () (osg::Node* node, osg::NodeVisitor* nv) override + void operator () (osg::Node* node, osg::NodeVisitor* nv) { if (!mRendered) { @@ -523,7 +524,7 @@ namespace MWRender rebuild(); } - class UpdateCameraCallback : public osg::NodeCallback + class UpdateCameraCallback : public SceneUtil::NodeCallback { public: UpdateCameraCallback(osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) @@ -533,12 +534,10 @@ namespace MWRender { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Camera* cam, osg::NodeVisitor* nv) { - osg::Camera* cam = static_cast(node); - // Update keyframe controllers in the scene graph first... - traverse(node, nv); + traverse(cam, nv); // Now update camera utilizing the updated head position osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 7d7c6e45df..b248efad4e 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -16,6 +16,7 @@ #include #include +#include #include @@ -62,7 +63,7 @@ namespace } - class CameraUpdateGlobalCallback : public osg::NodeCallback + class CameraUpdateGlobalCallback : public SceneUtil::NodeCallback { public: CameraUpdateGlobalCallback(MWRender::GlobalMap* parent) @@ -71,14 +72,14 @@ namespace { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Camera* node, osg::NodeVisitor* nv) { if (mRendered) { - if (mParent->copyResult(static_cast(node), nv->getTraversalNumber())) + if (mParent->copyResult(node, nv->getTraversalNumber())) { node->setNodeMask(0); - mParent->markForRemoval(static_cast(node)); + mParent->markForRemoval(node); } return; } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 02f59e0ed0..0560d1485a 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -35,7 +36,7 @@ namespace { - class CameraLocalUpdateCallback : public osg::NodeCallback + class CameraLocalUpdateCallback : public SceneUtil::NodeCallback { public: CameraLocalUpdateCallback(MWRender::LocalMap* parent) @@ -44,7 +45,7 @@ namespace { } - void operator()(osg::Node* node, osg::NodeVisitor*) override + void operator()(osg::Camera* node, osg::NodeVisitor*) { if (mRendered) node->setNodeMask(0); @@ -52,12 +53,11 @@ namespace if (!mRendered) { mRendered = true; - mParent->markForRemoval(static_cast(node)); + mParent->markForRemoval(node); } // Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's, // so it has been updated already. - //traverse(node, nv); } private: diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f430432659..7c6d82d83d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -148,44 +148,6 @@ public: float getValue(osg::NodeVisitor* nv) override; }; -// -------------------------------------------------------------------------------- - -/// Subclass RotateController to add a Z-offset for sneaking in first person mode. -/// @note We use inheritance instead of adding another controller, so that we do not have to compute the worldOrient twice. -/// @note Must be set on a MatrixTransform. -class NeckController : public RotateController -{ -public: - NeckController(osg::Node* relativeTo) - : RotateController(relativeTo) - { - } - - void setOffset(const osg::Vec3f& offset) - { - mOffset = offset; - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix matrix = transform->getMatrix(); - - osg::Quat worldOrient = getWorldOrientation(node); - osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); - - matrix.setRotate(orient); - matrix.setTrans(matrix.getTrans() + worldOrient.inverse() * mOffset); - - transform->setMatrix(matrix); - - traverse(node,nv); - } - -private: - osg::Vec3f mOffset; -}; - // -------------------------------------------------------------------------------------------------------------- HeadAnimationTime::HeadAnimationTime(const MWWorld::Ptr& reference) @@ -954,7 +916,7 @@ void NpcAnimation::addControllers() if (found != mNodeMap.end()) { osg::MatrixTransform* node = found->second.get(); - mFirstPersonNeckController = new NeckController(mObjectRoot.get()); + mFirstPersonNeckController = new RotateController(mObjectRoot.get()); node->addUpdateCallback(mFirstPersonNeckController); mActiveControllers.emplace_back(node, mFirstPersonNeckController); } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 9f7a186c5e..b511a52f37 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -24,7 +24,7 @@ namespace MWSound namespace MWRender { -class NeckController; +class RotateController; class HeadAnimationTime; class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener @@ -96,7 +96,7 @@ private: void setRenderBin(); - osg::ref_ptr mFirstPersonNeckController; + osg::ref_ptr mFirstPersonNeckController; static bool isFirstPersonPart(const ESM::BodyPart* bodypart); static bool isFemalePart(const ESM::BodyPart* bodypart); diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index f846a05872..bc00676d7b 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include "vismask.hpp" @@ -33,7 +34,7 @@ namespace return geom; } - class CullCallback : public osg::NodeCallback + class CullCallback : public SceneUtil::NodeCallback { public: CullCallback() @@ -41,12 +42,11 @@ namespace { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); - unsigned int frame = nv->getTraversalNumber(); + unsigned int frame = cv->getTraversalNumber(); if (frame != mLastFrameNumber) { mLastFrameNumber = frame; @@ -56,7 +56,7 @@ namespace if (!postProcessor) { Log(Debug::Error) << "Failed retrieving user data for master camera: FBO setup failed"; - traverse(node, nv); + traverse(node, cv); return; } @@ -71,7 +71,7 @@ namespace } } - traverse(node, nv); + traverse(node, cv); } private: diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 534cc74906..d3df4364d0 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -22,21 +22,27 @@ void RotateController::setRotate(const osg::Quat &rotate) mRotate = rotate; } -void RotateController::operator()(osg::Node *node, osg::NodeVisitor *nv) +void RotateController::setOffset(const osg::Vec3f& offset) +{ + mOffset = offset; +} + +void RotateController::operator()(osg::MatrixTransform *node, osg::NodeVisitor *nv) { if (!mEnabled) { traverse(node, nv); return; } - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix matrix = transform->getMatrix(); + osg::Matrix matrix = node->getMatrix(); osg::Quat worldOrient = getWorldOrientation(node); + osg::Quat worldOrientInverse = worldOrient.inverse(); - osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); + osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); + matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); - transform->setMatrix(matrix); + node->setMatrix(matrix); traverse(node,nv); } diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp index 9d4080ac6e..1f3ee0f845 100644 --- a/apps/openmw/mwrender/rotatecontroller.hpp +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -1,31 +1,36 @@ #ifndef OPENMW_MWRENDER_ROTATECONTROLLER_H #define OPENMW_MWRENDER_ROTATECONTROLLER_H -#include +#include #include +namespace osg +{ + class MatrixTransform; +} + namespace MWRender { /// Applies a rotation in \a relativeTo's space. /// @note Assumes that the node being rotated has its "original" orientation set every frame by a different controller. /// The rotation is then applied on top of that orientation. -/// @note Must be set on a MatrixTransform. -class RotateController : public osg::NodeCallback +class RotateController : public SceneUtil::NodeCallback { public: RotateController(osg::Node* relativeTo); void setEnabled(bool enabled); - + void setOffset(const osg::Vec3f& offset); void setRotate(const osg::Quat& rotate); - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); protected: osg::Quat getWorldOrientation(osg::Node* node); bool mEnabled; + osg::Vec3f mOffset; osg::Quat mRotate; osg::Node* mRelativeTo; }; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index c1b79843a7..c2ed62ef87 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include @@ -301,13 +302,11 @@ public: return osg::BoundingSphere(osg::Vec3f(0,0,0), 0); } - class CullCallback : public osg::NodeCallback + class CullCallback : public SceneUtil::NodeCallback { public: - void operator() (osg::Node* node, osg::NodeVisitor* nv) override + void operator() (osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - // XXX have to remove unwanted culling plane of the water reflection camera // Remove all planes that aren't from the standard frustum @@ -336,7 +335,7 @@ public: cv->getProjectionCullingStack().back().pushCurrentMask(); cv->getCurrentCullingSet().pushCurrentMask(); - traverse(node, nv); + traverse(node, cv); cv->getProjectionCullingStack().back().popCurrentMask(); cv->getCurrentCullingSet().popCurrentMask(); @@ -398,7 +397,7 @@ private: /// @note Must be added as cull callback. /// @note Meant to be used on a node that is child of a CameraRelativeTransform. /// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. -class UnderwaterSwitchCallback : public osg::NodeCallback +class UnderwaterSwitchCallback : public SceneUtil::NodeCallback { public: UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) @@ -414,7 +413,7 @@ public: return mEnabled && viewPoint.z() < mWaterLevel; } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (isUnderwater()) return; @@ -717,7 +716,7 @@ private: } }; - class OcclusionCallback : public osg::NodeCallback + class OcclusionCallback { public: OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) @@ -760,7 +759,7 @@ private: }; /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. - class SunFlashCallback : public OcclusionCallback + class SunFlashCallback : public OcclusionCallback, public SceneUtil::NodeCallback { public: SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) @@ -769,10 +768,8 @@ private: { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); osg::ref_ptr stateset; @@ -811,7 +808,7 @@ private: cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); - traverse(node, nv); + traverse(node, cv); cv->popModelViewMatrix(); @@ -832,7 +829,7 @@ private: /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. /// Must be attached as a cull callback to the node above the glare node. - class SunGlareCallback : public OcclusionCallback + class SunGlareCallback : public OcclusionCallback, public SceneUtil::NodeCallback { public: SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, @@ -854,10 +851,8 @@ private: mColor[i] = std::min(1.f, mColor[i]); } - void operator ()(osg::Node* node, osg::NodeVisitor* nv) override + void operator ()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); @@ -885,7 +880,7 @@ private: stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); cv->pushStateSet(stateset); - traverse(node, nv); + traverse(node, cv); cv->popStateSet(); } } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 88e422fcf3..37368b8e7a 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -58,20 +58,17 @@ namespace MWRender /// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); class ClipCullNode : public osg::Group { - class PlaneCullCallback : public osg::NodeCallback + class PlaneCullCallback : public SceneUtil::NodeCallback { public: /// @param cullPlane The culling plane (in world space). PlaneCullCallback(const osg::Plane* cullPlane) - : osg::NodeCallback() - , mCullPlane(cullPlane) + : mCullPlane(cullPlane) { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - osg::Polytope::PlaneList origPlaneList = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); osg::Plane plane = *mCullPlane; @@ -83,7 +80,7 @@ class ClipCullNode : public osg::Group cv->getProjectionCullingStack().back().getFrustum().add(plane); - traverse(node, nv); + traverse(node, cv); // undo cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); @@ -93,7 +90,7 @@ class ClipCullNode : public osg::Group const osg::Plane* mCullPlane; }; - class FlipCallback : public osg::NodeCallback + class FlipCallback : public SceneUtil::NodeCallback { public: FlipCallback(const osg::Plane* cullPlane) @@ -101,9 +98,8 @@ class ClipCullNode : public osg::Group { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); osg::Vec3d eyePoint = cv->getEyePoint(); osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); @@ -123,7 +119,7 @@ class ClipCullNode : public osg::Group modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); - traverse(node, nv); + traverse(node, cv); cv->popModelViewMatrix(); } @@ -166,31 +162,28 @@ private: /// This callback on the Camera has the effect of a RELATIVE_RF_INHERIT_VIEWPOINT transform mode (which does not exist in OSG). /// We want to keep the View Point of the parent camera so we will not have to recreate LODs. -class InheritViewPointCallback : public osg::NodeCallback +class InheritViewPointCallback : public SceneUtil::NodeCallback { public: - InheritViewPointCallback() {} + InheritViewPointCallback() {} - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); osg::ref_ptr modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); cv->popModelViewMatrix(); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT); - traverse(node, nv); + traverse(node, cv); } }; /// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. /// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). /// Must be added as a Cull callback. -class FudgeCallback : public osg::NodeCallback +class FudgeCallback : public SceneUtil::NodeCallback { public: - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - const float fudge = 0.2; if (std::abs(cv->getEyeLocal().z()) < fudge) { @@ -203,11 +196,11 @@ public: modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,diff)); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); - traverse(node, nv); + traverse(node, cv); cv->popModelViewMatrix(); } else - traverse(node, nv); + traverse(node, cv); } }; diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 870b8949c7..719e4fdc2b 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include @@ -16,6 +16,7 @@ namespace osg class PositionAttitudeTransform; class Geometry; class Node; + class Callback; } namespace osgUtil @@ -72,7 +73,7 @@ namespace MWRender bool mInterior; osg::Callback* mCullCallback; - osg::ref_ptr mShaderWaterStateSetUpdater; + osg::ref_ptr mShaderWaterStateSetUpdater; osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3b7be64ad1..9ee137fab6 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -161,7 +162,7 @@ namespace MWWorld } /// Rotates an osg::PositionAttitudeTransform over time. - class RotateCallback : public osg::NodeCallback + class RotateCallback : public SceneUtil::NodeCallback { public: RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2) @@ -170,14 +171,12 @@ namespace MWWorld { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::PositionAttitudeTransform* node, osg::NodeVisitor* nv) { - osg::PositionAttitudeTransform* transform = static_cast(node); - double time = nv->getFrameStamp()->getSimulationTime(); osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis); - transform->setAttitude(orient); + node->setAttitude(orient); traverse(node, nv); } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index abc170c02e..3061b329c2 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -15,6 +15,7 @@ #include #include +#include #include @@ -51,7 +52,7 @@ class Drawable : public osg::Drawable { public: // Stage 0: update widget animations and controllers. Run during the Update traversal. - class FrameUpdate : public osg::Drawable::UpdateCallback + class FrameUpdate : public SceneUtil::NodeCallback { public: FrameUpdate() @@ -64,10 +65,9 @@ public: mRenderManager = renderManager; } - void update(osg::NodeVisitor*, osg::Drawable*) override + void operator()(osg::Node*, osg::NodeVisitor*) { - if (mRenderManager) - mRenderManager->update(); + mRenderManager->update(); } private: @@ -75,7 +75,7 @@ public: }; // Stage 1: collect draw calls. Run during the Cull traversal. - class CollectDrawCalls : public osg::Drawable::CullCallback + class CollectDrawCalls : public SceneUtil::NodeCallback { public: CollectDrawCalls() @@ -88,13 +88,9 @@ public: mRenderManager = renderManager; } - bool cull(osg::NodeVisitor*, osg::Drawable*, osg::State*) const override + void operator()(osg::Node*, osg::NodeVisitor*) { - if (!mRenderManager) - return false; - mRenderManager->collectDrawCalls(); - return false; } private: diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index c8a6ef74ab..ddded82156 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -72,7 +72,7 @@ KeyframeController::KeyframeController() KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) - , SceneUtil::NodeCallback(copy, copyop) + , SceneUtil::NodeCallback(copy, copyop) , mRotations(copy.mRotations) , mXRotations(copy.mXRotations) , mYRotations(copy.mYRotations) @@ -134,16 +134,15 @@ osg::Vec3f KeyframeController::getTranslation(float time) const return osg::Vec3f(); } -void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) +void KeyframeController::operator() (NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) { if (hasInput()) { - NifOsg::MatrixTransform* trans = static_cast(node); - osg::Matrix mat = trans->getMatrix(); + osg::Matrix mat = node->getMatrix(); float time = getInputValue(nv); - Nif::Matrix3& rot = trans->mRotationScale; + Nif::Matrix3& rot = node->mRotationScale; bool setRot = false; if(!mRotations.empty()) @@ -169,7 +168,7 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) for (int j=0;j<3;++j) rot.mValues[i][j] = mat(j,i); // NB column/row major difference - float& scale = trans->mScale; + float& scale = node->mScale; if(!mScales.empty()) scale = mScales.interpKey(time); @@ -180,7 +179,7 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) if(!mTranslations.empty()) mat.setTrans(mTranslations.interpKey(time)); - trans->setMatrix(mat); + node->setMatrix(mat); } traverse(node, nv); @@ -191,8 +190,8 @@ GeomMorpherController::GeomMorpherController() } GeomMorpherController::GeomMorpherController(const GeomMorpherController ©, const osg::CopyOp ©op) - : osg::Drawable::UpdateCallback(copy, copyop) - , Controller(copy) + : Controller(copy) + , SceneUtil::NodeCallback(copy, copyop) , mKeyFrames(copy.mKeyFrames) { } @@ -218,9 +217,8 @@ GeomMorpherController::GeomMorpherController(const Nif::NiGeomMorpherController* } } -void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable) +void GeomMorpherController::operator()(SceneUtil::MorphGeometry* node, osg::NodeVisitor *nv) { - SceneUtil::MorphGeometry* morphGeom = static_cast(drawable); if (hasInput()) { if (mKeyFrames.size() <= 1) @@ -233,11 +231,11 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable if (!(*it).empty()) val = it->interpKey(input); - SceneUtil::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i); + SceneUtil::MorphGeometry::MorphTarget& target = node->getMorphTarget(i); if (target.getWeight() != val) { target.setWeight(val); - morphGeom->dirty(); + node->dirty(); } } } @@ -312,7 +310,7 @@ VisController::VisController() } VisController::VisController(const VisController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mData(copy.mData) , mMask(copy.mMask) @@ -353,14 +351,14 @@ RollController::RollController(const Nif::NiFloatInterpolator* interpolator) } RollController::RollController(const RollController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mData(copy.mData) , mStartingTime(copy.mStartingTime) { } -void RollController::operator() (osg::Node* node, osg::NodeVisitor* nv) +void RollController::operator() (osg::MatrixTransform* node, osg::NodeVisitor* nv) { traverse(node, nv); @@ -371,15 +369,14 @@ void RollController::operator() (osg::Node* node, osg::NodeVisitor* nv) mStartingTime = newTime; float value = mData.interpKey(getInputValue(nv)); - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix matrix = transform->getMatrix(); + osg::Matrix matrix = node->getMatrix(); // Rotate around "roll" axis. // Note: in original game rotation speed is the framerate-dependent in a very tricky way. // Do not replicate this behaviour until we will really need it. // For now consider controller's current value as an angular speed in radians per 1/60 seconds. matrix = osg::Matrix::rotate(value * duration * 60.f, 0, 0, 1) * matrix; - transform->setMatrix(matrix); + node->setMatrix(matrix); } } @@ -545,29 +542,28 @@ ParticleSystemController::ParticleSystemController() } ParticleSystemController::ParticleSystemController(const ParticleSystemController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mEmitStart(copy.mEmitStart) , mEmitStop(copy.mEmitStop) { } -void ParticleSystemController::operator() (osg::Node* node, osg::NodeVisitor* nv) +void ParticleSystemController::operator() (osgParticle::ParticleProcessor* node, osg::NodeVisitor* nv) { - osgParticle::ParticleProcessor* emitter = static_cast(node); if (hasInput()) { float time = getInputValue(nv); - emitter->getParticleSystem()->setFrozen(false); - emitter->setEnabled(time >= mEmitStart && time < mEmitStop); + node->getParticleSystem()->setFrozen(false); + node->setEnabled(time >= mEmitStart && time < mEmitStop); } else - emitter->getParticleSystem()->setFrozen(true); + node->getParticleSystem()->setFrozen(true); traverse(node, nv); } PathController::PathController(const PathController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mPath(copy.mPath) , mPercent(copy.mPercent) @@ -592,7 +588,7 @@ float PathController::getPercent(float time) const return percent; } -void PathController::operator() (osg::Node* node, osg::NodeVisitor* nv) +void PathController::operator() (osg::MatrixTransform* node, osg::NodeVisitor* nv) { if (mPath.empty() || mPercent.empty() || !hasInput()) { @@ -600,14 +596,13 @@ void PathController::operator() (osg::Node* node, osg::NodeVisitor* nv) return; } - osg::MatrixTransform* trans = static_cast(node); - osg::Matrix mat = trans->getMatrix(); + osg::Matrix mat = node->getMatrix(); float time = getInputValue(nv); float percent = getPercent(time); osg::Vec3f pos(mPath.interpKey(percent)); mat.setTrans(pos); - trans->setMatrix(mat); + node->setMatrix(mat); traverse(node, nv); } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 078b7e14ce..c6311fd5fc 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -15,19 +15,27 @@ #include -#include -#include -#include - - namespace osg { class Material; + class MatrixTransform; +} + +namespace osgParticle +{ + class ParticleProcessor; +} + +namespace SceneUtil +{ + class MorphGeometry; } namespace NifOsg { + class MatrixTransform; + // interpolation of keyframes template class ValueInterpolator @@ -208,8 +216,7 @@ namespace NifOsg float getMaximum() const override; }; - /// Must be set on a SceneUtil::MorphGeometry. - class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller + class GeomMorpherController : public SceneUtil::Controller, public SceneUtil::NodeCallback { public: GeomMorpherController(const Nif::NiGeomMorpherController* ctrl); @@ -218,13 +225,13 @@ namespace NifOsg META_Object(NifOsg, GeomMorpherController) - void update(osg::NodeVisitor* nv, osg::Drawable* drawable) override; + void operator()(SceneUtil::MorphGeometry*, osg::NodeVisitor*); private: std::vector mKeyFrames; }; - class KeyframeController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback + class KeyframeController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: // This is used if there's no interpolator but there is data (Morrowind meshes). @@ -242,7 +249,7 @@ namespace NifOsg osg::Vec3f getTranslation(float time) const override; - void operator() (osg::Node*, osg::NodeVisitor*); + void operator() (NifOsg::MatrixTransform*, osg::NodeVisitor*); private: QuaternionInterpolator mRotations; @@ -277,7 +284,7 @@ namespace NifOsg std::set mTextureUnits; }; - class VisController : public osg::NodeCallback, public SceneUtil::Controller + class VisController : public SceneUtil::NodeCallback, public SceneUtil::Controller { private: std::vector mData; @@ -292,10 +299,10 @@ namespace NifOsg META_Object(NifOsg, VisController) - void operator() (osg::Node* node, osg::NodeVisitor* nv) override; + void operator() (osg::Node* node, osg::NodeVisitor* nv); }; - class RollController : public osg::NodeCallback, public SceneUtil::Controller + class RollController : public SceneUtil::NodeCallback, public SceneUtil::Controller { private: FloatInterpolator mData; @@ -307,7 +314,7 @@ namespace NifOsg RollController() = default; RollController(const RollController& copy, const osg::CopyOp& copyop); - void operator() (osg::Node* node, osg::NodeVisitor* nv) override; + void operator() (osg::MatrixTransform* node, osg::NodeVisitor* nv); META_Object(NifOsg, RollController) }; @@ -378,7 +385,7 @@ namespace NifOsg void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; }; - class ParticleSystemController : public osg::NodeCallback, public SceneUtil::Controller + class ParticleSystemController : public SceneUtil::NodeCallback, public SceneUtil::Controller { public: ParticleSystemController(const Nif::NiParticleSystemController* ctrl); @@ -387,14 +394,14 @@ namespace NifOsg META_Object(NifOsg, ParticleSystemController) - void operator() (osg::Node* node, osg::NodeVisitor* nv) override; + void operator() (osgParticle::ParticleProcessor* node, osg::NodeVisitor* nv); private: float mEmitStart; float mEmitStop; }; - class PathController : public osg::NodeCallback, public SceneUtil::Controller + class PathController : public SceneUtil::NodeCallback, public SceneUtil::Controller { public: PathController(const Nif::NiPathController* ctrl); @@ -403,7 +410,7 @@ namespace NifOsg META_Object(NifOsg, PathController) - void operator() (osg::Node*, osg::NodeVisitor*) override; + void operator() (osg::MatrixTransform*, osg::NodeVisitor*); private: Vec3Interpolator mPath; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a564844ce6..838895eb47 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -109,24 +109,21 @@ namespace // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. - // Must be set as a cull callback. - class BillboardCallback : public osg::NodeCallback + class BillboardCallback : public SceneUtil::NodeCallback { public: BillboardCallback() { } BillboardCallback(const BillboardCallback& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) { } META_Object(NifOsg, BillboardCallback) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - osg::Matrix modelView = *cv->getModelViewMatrix(); // attempt to preserve scale @@ -143,7 +140,7 @@ namespace cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); - traverse(node, nv); + traverse(node, cv); cv->popModelViewMatrix(); } diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index e2e1f92cf3..0f103588e8 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -68,19 +68,17 @@ void ParticleSystem::drawImplementation(osg::RenderInfo& renderInfo) const osgParticle::ParticleSystem::drawImplementation(renderInfo); } -void InverseWorldMatrix::operator()(osg::Node *node, osg::NodeVisitor *nv) +void InverseWorldMatrix::operator()(osg::MatrixTransform *node, osg::NodeVisitor *nv) { if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) { osg::NodePath path = nv->getNodePath(); path.pop_back(); - osg::MatrixTransform* trans = static_cast(node); - osg::Matrix mat = osg::computeLocalToWorld( path ); mat.orthoNormalize(mat); // don't undo the scale mat.invert(mat); - trans->setMatrix(mat); + node->setMatrix(mat); } traverse(node,nv); } diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index b0a46f0ca5..8b724545f4 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "controller.hpp" // ValueInterpolator @@ -57,20 +57,20 @@ namespace NifOsg // Node callback used to set the inverse of the parent's world matrix on the MatrixTransform // that the callback is attached to. Used for certain particle systems, // so that the particles do not move with the node they are attached to. - class InverseWorldMatrix : public osg::NodeCallback + class InverseWorldMatrix : public SceneUtil::NodeCallback { public: InverseWorldMatrix() { } - InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& op) - : osg::Object(), osg::NodeCallback() + InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& copyop) + : osg::Object(copy, copyop), SceneUtil::NodeCallback(copy, copyop) { } META_Object(NifOsg, InverseWorldMatrix) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); }; class ParticleShooter : public osgParticle::Shooter diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 01a8639aa2..8aa32a28bc 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index be32539d40..7c730b24a7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -40,16 +40,14 @@ namespace { - class InitWorldSpaceParticlesCallback : public osg::NodeCallback + class InitWorldSpaceParticlesCallback : public SceneUtil::NodeCallback { public: - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osgParticle::ParticleSystem* node, osg::NodeVisitor* nv) { - osgParticle::ParticleSystem* partsys = static_cast(node); - // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to - if (partsys->getNumParents() && partsys->getParent(0)->getNumParents()) - transformInitialParticles(partsys, partsys->getParent(0)->getParent(0)); + if (node->getNumParents() && node->getParent(0)->getNumParents()) + transformInitialParticles(node, node->getParent(0)->getParent(0)); node->removeUpdateCallback(this); } diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index cc320aecf8..13e367baa4 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -26,7 +26,7 @@ namespace SceneUtil mType = type; } - void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv) + void LightController::operator ()(SceneUtil::LightSource* node, osg::NodeVisitor* nv) { double time = nv->getFrameStamp()->getSimulationTime(); if (mStartTime == 0) @@ -38,7 +38,7 @@ namespace SceneUtil if (mType == LT_Normal) { - static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); + node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); traverse(node, nv); return; } @@ -62,8 +62,7 @@ namespace SceneUtil mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } - auto* lightSource = static_cast(node); - lightSource->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * lightSource->getActorFade()); + node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * node->getActorFade()); traverse(node, nv); } diff --git a/components/sceneutil/lightcontroller.hpp b/components/sceneutil/lightcontroller.hpp index 36b2e868e5..b67cd59472 100644 --- a/components/sceneutil/lightcontroller.hpp +++ b/components/sceneutil/lightcontroller.hpp @@ -1,15 +1,16 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H -#include +#include #include namespace SceneUtil { + class LightSource; + /// @brief Controller class to handle a pulsing and/or flickering light - /// @note Must be set on a SceneUtil::LightSource. - class LightController : public osg::NodeCallback + class LightController : public SceneUtil::NodeCallback { public: enum LightType { @@ -26,7 +27,7 @@ namespace SceneUtil void setDiffuse(const osg::Vec4f& color); - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(SceneUtil::LightSource* node, osg::NodeVisitor* nv); private: LightType mType; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 09fa302457..0abebd4884 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -650,23 +650,21 @@ namespace SceneUtil } }; - class LightManagerCullCallback : public osg::NodeCallback + class LightManagerCullCallback : public SceneUtil::NodeCallback { public: - LightManagerCullCallback(LightManager* lightManager) : mLightManager(lightManager), mLastFrameNumber(0) {} + LightManagerCullCallback() : mLastFrameNumber(0) {} - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(LightManager* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - if (mLastFrameNumber != cv->getTraversalNumber()) { mLastFrameNumber = cv->getTraversalNumber(); - if (mLightManager->getLightingMethod() == LightingMethod::SingleUBO) + if (node->getLightingMethod() == LightingMethod::SingleUBO) { - auto stateset = mLightManager->getStateSet(); - auto bo = mLightManager->getLightBuffer(mLastFrameNumber); + auto stateset = node->getStateSet(); + auto bo = node->getLightBuffer(mLastFrameNumber); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); @@ -676,23 +674,23 @@ namespace SceneUtil stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); } - auto sun = mLightManager->getSunlight(); + auto sun = node->getSunlight(); if (sun) { // we must defer uploading the transformation to view-space position to deal with different cameras (e.g. reflection RTT). - if (mLightManager->getLightingMethod() == LightingMethod::PerObjectUniform) + if (node->getLightingMethod() == LightingMethod::PerObjectUniform) { osg::Matrixf lightMat; configurePosition(lightMat, sun->getPosition()); configureAmbient(lightMat, sun->getAmbient()); configureDiffuse(lightMat, sun->getDiffuse()); configureSpecular(lightMat, sun->getSpecular()); - mLightManager->setSunlightBuffer(lightMat, mLastFrameNumber); + node->setSunlightBuffer(lightMat, mLastFrameNumber); } else { - auto buf = mLightManager->getLightBuffer(mLastFrameNumber); + auto buf = node->getLightBuffer(mLastFrameNumber); buf->setCachedSunPos(sun->getPosition()); buf->setAmbient(0, sun->getAmbient()); @@ -702,11 +700,10 @@ namespace SceneUtil } } - traverse(node, nv); + traverse(node, cv); } private: - LightManager* mLightManager; size_t mLastFrameNumber; }; @@ -899,7 +896,7 @@ namespace SceneUtil getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); - addCullCallback(new LightManagerCullCallback(this)); + addCullCallback(new LightManagerCullCallback()); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index a661a90279..3b60c80afd 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -6,23 +6,19 @@ #include #include +#include #include namespace SceneUtil { - // RTTNode's cull callback - class CullCallback : public osg::NodeCallback + class CullCallback : public SceneUtil::NodeCallback { public: - CullCallback(RTTNode* group) - : mGroup(group) {} - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(RTTNode* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - mGroup->cull(cv); + node->cull(cv); } - RTTNode* mGroup; }; RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, int renderOrderNum, bool doPerViewMapping) @@ -31,7 +27,7 @@ namespace SceneUtil , mRenderOrderNum(renderOrderNum) , mDoPerViewMapping(doPerViewMapping) { - addCullCallback(new CullCallback(this)); + addCullCallback(new CullCallback); setCullingActive(false); } diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index a8232f938e..9ef82b9271 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -68,7 +68,7 @@ namespace SceneUtil } StateSetUpdater::StateSetUpdater(const StateSetUpdater ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) { } diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index 263f76ae54..ab091fd39f 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_STATESETCONTROLLER_H #define OPENMW_COMPONENTS_SCENEUTIL_STATESETCONTROLLER_H -#include +#include #include #include @@ -28,7 +28,7 @@ namespace SceneUtil /// @note When used as a CullCallback, StateSetUpdater will have no effect on leaf nodes such as osg::Geometry and must be used on branch nodes only. /// @note Do not add the same StateSetUpdater to multiple nodes. /// @note Do not add multiple StateSetUpdaters on the same Node as they will conflict - instead use the CompositeStateSetUpdater. - class StateSetUpdater : public osg::NodeCallback + class StateSetUpdater : public SceneUtil::NodeCallback { public: StateSetUpdater(); @@ -36,7 +36,7 @@ namespace SceneUtil META_Object(SceneUtil, StateSetUpdater) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::Node* node, osg::NodeVisitor* nv); /// Apply state - to override in derived classes /// @note Due to the double buffering approach you *have* to apply all state diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 442b164981..5c2f8c2934 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #ifndef GL_DEPTH32F_STENCIL8_NV #define GL_DEPTH32F_STENCIL8_NV 0x8DAC @@ -174,8 +175,7 @@ void GlowUpdater::setDuration(float duration) } // Allows camera to render to a color and floating point depth texture with a multisampled framebuffer. -// Must be set on a camera's cull callback. -class AttachMultisampledDepthColorCallback : public osg::NodeCallback +class AttachMultisampledDepthColorCallback : public SceneUtil::NodeCallback { public: AttachMultisampledDepthColorCallback(osg::Texture2D* colorTex, osg::Texture2D* depthTex, int samples, int colorSamples) @@ -195,14 +195,14 @@ public: mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthTex)); } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::RenderStage* renderStage = static_cast(nv)->getCurrentRenderStage(); + osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); renderStage->setMultisampleResolveFramebufferObject(mFbo); renderStage->setFrameBufferObject(mMsaaFbo); - traverse(node, nv); + traverse(node, cv); } private: diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 0bca32c325..4d19714d66 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 8d60e4c5b6..def7a2eccf 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -4,13 +4,14 @@ #include #include #include -#include #include #include #include #include +#include + #include "defs.hpp" #include "cellborder.hpp" @@ -45,7 +46,7 @@ namespace Terrain class ChunkManager; class CompositeMapRenderer; - class HeightCullCallback : public osg::NodeCallback + class HeightCullCallback : public SceneUtil::NodeCallback { public: void setLowZ(float z) @@ -75,7 +76,7 @@ namespace Terrain return mMask; } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (mLowZ <= mHighZ) traverse(node, nv); From 4b1c009ffd5ed0d0698b967c29edf0d6c912ea92 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 12:37:08 +0000 Subject: [PATCH 1418/2859] use StateSet define for translucentFramebuffer (#3138) With this PR we test out osg's shader define system for a somewhat harmless feature. As we can see, our code becomes more concise and efficient in this case. Most importantly, we no longer create unneeded vertex shader objects. --- apps/openmw/mwrender/characterpreview.cpp | 9 +++------ apps/openmw/mwrender/groundcover.cpp | 2 +- components/resource/scenemanager.cpp | 7 +++---- components/resource/scenemanager.hpp | 4 ++-- components/shader/shadervisitor.cpp | 10 +--------- components/shader/shadervisitor.hpp | 4 ---- files/shaders/nv_default_fragment.glsl | 9 ++++----- files/shaders/nv_nolighting_fragment.glsl | 8 +++----- files/shaders/objects_fragment.glsl | 7 +++---- 9 files changed, 20 insertions(+), 40 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 51e5a536f7..a9733cddf8 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -91,7 +91,7 @@ namespace MWRender class SetUpBlendVisitor : public osg::NodeVisitor { public: - SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mNoAlphaUniform(new osg::Uniform("noAlpha", false)) + SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } @@ -130,7 +130,7 @@ namespace MWRender } // Disable noBlendAlphaEnv newStateSet->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); - newStateSet->addUniform(mNoAlphaUniform); + newStateSet->setDefine("FORCE_OPAQUE", "0", osg::StateAttribute::ON); } if (SceneUtil::getReverseZ() && stateset->getAttribute(osg::StateAttribute::DEPTH)) { @@ -172,8 +172,6 @@ namespace MWRender } traverse(node); } - private: - osg::ref_ptr mNoAlphaUniform; }; CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, @@ -216,6 +214,7 @@ namespace MWRender osg::ref_ptr lightManager = new SceneUtil::LightManager(ffp); lightManager->setStartLight(1); osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); + stateset->setDefine("FORCE_OPAQUE", "1", osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); @@ -253,7 +252,6 @@ namespace MWRender dummyTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("noAlpha", true)); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0)); @@ -328,7 +326,6 @@ namespace MWRender void CharacterPreview::setBlendMode() { - mResourceSystem->getSceneManager()->recreateShaders(mNode, "objects", true); SetUpBlendVisitor visitor; mNode->accept(visitor); } diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 71bddb1f9d..77c4f0fab5 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -224,7 +224,7 @@ namespace MWRender group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->setCullCallback(new SceneUtil::LightListCallback); - mSceneManager->recreateShaders(group, "groundcover", false, true, mProgramTemplate); + mSceneManager->recreateShaders(group, "groundcover", true, mProgramTemplate); mSceneManager->shareState(group); group->getBound(); return group; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 7c730b24a7..c5ef957c3e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -331,9 +331,9 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode, const osg::Program* programTemplate) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate) { - osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); + osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix)); shaderVisitor->setAllowedToModifyStateSets(false); shaderVisitor->setProgramTemplate(programTemplate); if (forceShadersForNode) @@ -871,7 +871,7 @@ namespace Resource stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); } - Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) + Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix) { Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix); shaderVisitor->setForceShaders(mForceShaders); @@ -882,7 +882,6 @@ namespace Resource shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); - shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 1443476fd5..b5d3e453a0 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); /// Applying shaders to a node may replace some fixed-function state. /// This restores it. @@ -188,7 +188,7 @@ namespace Resource private: - Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); + Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); std::unique_ptr mShaderManager; bool mForceShaders; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index cc2b781cfd..6709ee842e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -111,7 +111,6 @@ namespace Shader , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) , mConvertAlphaTestToAlphaToCoverage(false) - , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) @@ -266,7 +265,7 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } - else if (!mTranslucentFramebuffer) + else Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } @@ -547,8 +546,6 @@ namespace Shader updateAddedState(*writableUserData, addedState); } - defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; - std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -764,11 +761,6 @@ namespace Shader mConvertAlphaTestToAlphaToCoverage = convert; } - void ShaderVisitor::setTranslucentFramebuffer(bool translucent) - { - mTranslucentFramebuffer = translucent; - } - ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 3380a66cca..5f9739ea90 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -45,8 +45,6 @@ namespace Shader void setConvertAlphaTestToAlphaToCoverage(bool convert); - void setTranslucentFramebuffer(bool translucent); - void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -72,8 +70,6 @@ namespace Shader bool mConvertAlphaTestToAlphaToCoverage; - bool mTranslucentFramebuffer; - ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 1bd0f4e310..38dca66c40 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -1,4 +1,5 @@ #version 120 +#pragma import_defines(FORCE_OPAQUE) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -24,8 +25,6 @@ varying vec2 normalMapUV; varying vec4 passTangent; #endif -uniform bool noAlpha; - varying float euclideanDepth; varying float linearDepth; @@ -95,9 +94,9 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if @translucentFramebuffer - if (noAlpha) - gl_FragData[0].a = 1.0; +#if FORCE_OPAQUE + // having testing & blending isn't enough - we need to write an opaque pixel to be opaque + gl_FragData[0].a = 1.0; #endif applyShadowDebugOverlay(); diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl index 27679a069f..76b01828d6 100644 --- a/files/shaders/nv_nolighting_fragment.glsl +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -1,4 +1,5 @@ #version 120 +#pragma import_defines(FORCE_OPAQUE) #if @useGPUShader4 #extension GL_EXT_gpu_shader4: require @@ -9,8 +10,6 @@ uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; #endif -uniform bool noAlpha; - #if @radialFog varying float euclideanDepth; #else @@ -46,9 +45,8 @@ void main() float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #endif -#if @translucentFramebuffer - if (noAlpha) - gl_FragData[0].a = 1.0; +#if FORCE_OPAQUE + gl_FragData[0].a = 1.0; #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index d304b2abe8..c343b6a189 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,4 +1,5 @@ #version 120 +#pragma import_defines(FORCE_OPAQUE) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -58,7 +59,6 @@ uniform mat2 bumpMapMatrix; #endif uniform bool simpleWater; -uniform bool noAlpha; varying float euclideanDepth; varying float linearDepth; @@ -220,10 +220,9 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if @translucentFramebuffer +#if FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque - if (noAlpha) - gl_FragData[0].a = 1.0; + gl_FragData[0].a = 1.0; #endif applyShadowDebugOverlay(); From 499b161e7bab9f11857e7bd347546ba1963c3fb1 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Sat, 25 Sep 2021 16:06:30 +0200 Subject: [PATCH 1419/2859] Merge handleFall() and updateMechanics() into updateActor(). It remove gratuitious differences between sync and async cases. Also, it will make the after simulation update logic easier to follow when projectiles move into the simulation thread. --- apps/openmw/mwphysics/mtphysics.cpp | 48 +++++++------------------ apps/openmw/mwphysics/physicssystem.cpp | 2 -- apps/openmw/mwphysics/physicssystem.hpp | 2 -- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 7363b9964a..bb5447341b 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -54,29 +54,6 @@ namespace return actorData.mPosition.z() < actorData.mSwimLevel; } - void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed) - { - const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; - - const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); - - if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1) - actorData.mNeedLand = true; - else if (heightDiff < 0) - actorData.mFallHeight += heightDiff; - } - - void updateMechanics(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData) - { - auto ptr = actor.getPtr(); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - if (actorData.mNeedLand) - stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData))); - else if (actorData.mFallHeight < 0) - stats.addToFallHeight(-actorData.mFallHeight); - } - osg::Vec3f interpolateMovements(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); @@ -221,10 +198,8 @@ namespace MWPhysics if (mNumThreads != 0) { for (size_t i = 0; i < mActors.size(); ++i) - { - updateMechanics(*mActors[i], mActorsFrameData[i]); updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); - } + if(mAdvanceSimulation) mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); updateStats(frameStart, frameNumber, stats); @@ -449,11 +424,6 @@ namespace MWPhysics if (!mRemainingSteps) { - while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) - { - handleFall(mActorsFrameData[job], mAdvanceSimulation); - } - refreshLOSCache(); mPostSimBarrier->wait([this] { afterPostSim(); }); } @@ -476,6 +446,17 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const { + auto ptr = actor.getPtr(); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; + const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); + + if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1) + stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData))); + else if (heightDiff < 0) + stats.addToFallHeight(-heightDiff); + actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); actor.setLastStuckPosition(actorData.mLastStuckPosition); actor.setStuckFrames(actorData.mStuckFrames); @@ -524,11 +505,8 @@ namespace MWPhysics } for (size_t i = 0; i < mActors.size(); ++i) - { - handleFall(mActorsFrameData[i], mAdvanceSimulation); - updateMechanics(*mActors[i], mActorsFrameData[i]); updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); - } + refreshLOSCache(); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index a8c39efee2..6b2600069a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -978,14 +978,12 @@ namespace MWPhysics , mWaterlevel(waterlevel) , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) - , mFallHeight(0) , mStuckFrames(0) , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) , mWasOnGround(actor.getOnGround()) , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) , mWaterCollision(waterCollision) , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) - , mNeedLand(false) { } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4fb7a3518f..cf4ca40a54 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -97,14 +97,12 @@ namespace MWPhysics const float mWaterlevel; const float mHalfExtentsZ; float mOldHeight; - float mFallHeight; unsigned int mStuckFrames; const bool mFlying; const bool mWasOnGround; const bool mIsAquatic; const bool mWaterCollision; const bool mSkipCollisionDetection; - bool mNeedLand; }; struct WorldFrameData From 21dbe314bf3e487975becd35e6ae8ae3af7916b3 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 29 Sep 2021 10:17:31 +0200 Subject: [PATCH 1420/2859] Use common function for sync and async case. Now both cases follow the same flow, synchronous simulation is just a special case. --- apps/openmw/mwphysics/mtphysics.cpp | 80 +++++++++++++---------------- apps/openmw/mwphysics/mtphysics.hpp | 4 +- components/misc/barrier.hpp | 15 +++--- 3 files changed, 46 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index bb5447341b..ecf1e6dadd 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -22,31 +22,31 @@ namespace { - /// @brief A scoped lock that is either shared or exclusive depending on configuration + /// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration template class MaybeSharedLock { public: /// @param mutex a shared mutex - /// @param canBeSharedLock decide wether the lock will be shared or exclusive - MaybeSharedLock(Mutex& mutex, bool canBeSharedLock) : mMutex(mutex), mCanBeSharedLock(canBeSharedLock) + /// @param threadCount decide wether the lock will be shared, exclusive or inexistent + MaybeSharedLock(Mutex& mutex, int threadCount) : mMutex(mutex), mThreadCount(threadCount) { - if (mCanBeSharedLock) + if (mThreadCount > 1) mMutex.lock_shared(); - else + else if(mThreadCount == 1) mMutex.lock(); } ~MaybeSharedLock() { - if (mCanBeSharedLock) + if (mThreadCount > 1) mMutex.unlock_shared(); - else + else if(mThreadCount == 1) mMutex.unlock(); } private: Mutex& mMutex; - bool mCanBeSharedLock; + int mThreadCount; }; bool isUnderWater(const MWPhysics::ActorFrameData& actorData) @@ -62,14 +62,14 @@ namespace namespace Config { - /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading - int computeNumThreads(bool& threadSafeBullet) + /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading and user requested more than 1 background threads + int computeNumThreads() { int wantedThread = Settings::Manager::getInt("async num threads", "Physics"); auto broad = std::make_unique(); auto maxSupportedThreads = broad->m_rayTestStacks.size(); - threadSafeBullet = (maxSupportedThreads > 1); + auto threadSafeBullet = (maxSupportedThreads > 1); if (!threadSafeBullet && wantedThread > 1) { Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used"; @@ -88,6 +88,7 @@ namespace MWPhysics , mTimeAccum(0.f) , mCollisionWorld(collisionWorld) , mDebugDrawer(debugDrawer) + , mNumThreads(Config::computeNumThreads()) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) @@ -107,8 +108,6 @@ namespace MWPhysics , mTimeEnd(0) , mFrameStart(0) { - mNumThreads = Config::computeNumThreads(mThreadSafeBullet); - if (mNumThreads >= 1) { for (int i = 0; i < mNumThreads; ++i) @@ -197,8 +196,7 @@ namespace MWPhysics // start by finishing previous background computation if (mNumThreads != 0) { - for (size_t i = 0; i < mActors.size(); ++i) - updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); + syncWithMainThread(); if(mAdvanceSimulation) mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); @@ -233,7 +231,8 @@ namespace MWPhysics if (mNumThreads == 0) { - syncComputation(); + doSimulation(); + syncWithMainThread(); if(mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor); return; @@ -262,13 +261,13 @@ namespace MWPhysics void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback); } void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback); } @@ -280,7 +279,7 @@ namespace MWPhysics std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); // target the collision object's world origin, this should be the center of the collision object btTransform rayTo; rayTo.setIdentity(); @@ -411,22 +410,7 @@ namespace MWPhysics if (!mNewFrame) mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; }); - mPreStepBarrier->wait([this] { afterPreStep(); }); - - int job = 0; - while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) - { - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); - MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); - } - - mPostStepBarrier->wait([this] { afterPostStep(); }); - - if (!mRemainingSteps) - { - refreshLOSCache(); - mPostSimBarrier->wait([this] { afterPostSim(); }); - } + doSimulation(); } } @@ -485,29 +469,29 @@ namespace MWPhysics resultCallback.m_collisionFilterGroup = 0xFF; resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); + MaybeSharedLock lockColWorld(mCollisionWorldMutex, mNumThreads); mCollisionWorld->rayTest(pos1, pos2, resultCallback); return !resultCallback.hasHit(); } - void PhysicsTaskScheduler::syncComputation() + void PhysicsTaskScheduler::doSimulation() { - while (mRemainingSteps--) + while (mRemainingSteps) { - for (auto& actorData : mActorsFrameData) + mPreStepBarrier->wait([this] { afterPreStep(); }); + int job = 0; + while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - MovementSolver::unstuck(actorData, mCollisionWorld); - MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData); + MaybeSharedLock lockColWorld(mCollisionWorldMutex, mNumThreads); + MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); } - updateActorsPositions(); + mPostStepBarrier->wait([this] { afterPostStep(); }); } - for (size_t i = 0; i < mActors.size(); ++i) - updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); - refreshLOSCache(); + mPostSimBarrier->wait([this] { afterPostSim(); }); } void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) @@ -580,4 +564,10 @@ namespace MWPhysics } mTimeEnd = mTimer->tick(); } + + void PhysicsTaskScheduler::syncWithMainThread() + { + for (size_t i = 0; i < mActors.size(); ++i) + updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); + } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index e2cfccffb0..08997947e4 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -61,7 +61,7 @@ namespace MWPhysics void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler() private: - void syncComputation(); + void doSimulation(); void worker(); void updateActorsPositions(); void updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const; @@ -74,6 +74,7 @@ namespace MWPhysics void afterPreStep(); void afterPostStep(); void afterPostSim(); + void syncWithMainThread(); std::unique_ptr mWorldFrameData; std::vector> mActors; @@ -98,7 +99,6 @@ namespace MWPhysics int mLOSCacheExpiry; bool mNewFrame; bool mAdvanceSimulation; - bool mThreadSafeBullet; bool mQuit; std::atomic mNextJob; std::atomic mNextLOS; diff --git a/components/misc/barrier.hpp b/components/misc/barrier.hpp index b3fe944b04..277e40814a 100644 --- a/components/misc/barrier.hpp +++ b/components/misc/barrier.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_BARRIER_H #define OPENMW_BARRIER_H +#include #include #include @@ -12,7 +13,9 @@ namespace Misc public: /// @param count number of threads to wait on explicit Barrier(int count) : mThreadCount(count), mRendezvousCount(0), mGeneration(0) - {} + { + assert(count >= 0); + } /// @brief stop execution of threads until count distinct threads reach this point /// @param func callable to be executed once after all threads have met @@ -22,8 +25,8 @@ namespace Misc std::unique_lock lock(mMutex); ++mRendezvousCount; - const int currentGeneration = mGeneration; - if (mRendezvousCount == mThreadCount) + const unsigned int currentGeneration = mGeneration; + if (mRendezvousCount == mThreadCount || mThreadCount == 0) { ++mGeneration; mRendezvousCount = 0; @@ -37,9 +40,9 @@ namespace Misc } private: - int mThreadCount; - int mRendezvousCount; - int mGeneration; + unsigned int mThreadCount; + unsigned int mRendezvousCount; + unsigned int mGeneration; mutable std::mutex mMutex; std::condition_variable mRendezvous; }; From 3b174d72d85688fa7355ca5ae2cadc5af09378b3 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 5 Oct 2021 15:43:21 +0200 Subject: [PATCH 1421/2859] Introduce 3 scoped mutex wrappers to allow to conditionally skip taking mutexes in synchronous case. --- apps/openmw/mwphysics/mtphysics.cpp | 115 ++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index ecf1e6dadd..a0dde67c2e 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -22,22 +22,73 @@ namespace { - /// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration + /// @brief A scoped lock that is either exclusive or inexistent depending on configuration + template + class MaybeExclusiveLock + { + public: + /// @param mutex a mutex + /// @param threadCount decide wether the excluse lock will be taken + MaybeExclusiveLock(Mutex& mutex, int threadCount) : mMutex(mutex), mThreadCount(threadCount) + { + assert(threadCount >= 0); + if (mThreadCount > 0) + mMutex.lock(); + } + + ~MaybeExclusiveLock() + { + if (mThreadCount > 0) + mMutex.unlock(); + } + + private: + Mutex& mMutex; + unsigned int mThreadCount; + }; + + /// @brief A scoped lock that is either shared or inexistent depending on configuration template class MaybeSharedLock { public: /// @param mutex a shared mutex - /// @param threadCount decide wether the lock will be shared, exclusive or inexistent + /// @param threadCount decide wether the shared lock will be taken MaybeSharedLock(Mutex& mutex, int threadCount) : mMutex(mutex), mThreadCount(threadCount) { + assert(threadCount >= 0); + if (mThreadCount > 0) + mMutex.lock_shared(); + } + + ~MaybeSharedLock() + { + if (mThreadCount > 0) + mMutex.unlock_shared(); + } + + private: + Mutex& mMutex; + unsigned int mThreadCount; + }; + + /// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration + template + class MaybeLock + { + public: + /// @param mutex a shared mutex + /// @param threadCount decide wether the lock will be shared, exclusive or inexistent + MaybeLock(Mutex& mutex, int threadCount) : mMutex(mutex), mThreadCount(threadCount) + { + assert(threadCount >= 0); if (mThreadCount > 1) mMutex.lock_shared(); else if(mThreadCount == 1) mMutex.lock(); } - ~MaybeSharedLock() + ~MaybeLock() { if (mThreadCount > 1) mMutex.unlock_shared(); @@ -46,7 +97,7 @@ namespace } private: Mutex& mMutex; - int mThreadCount; + unsigned int mThreadCount; }; bool isUnderWater(const MWPhysics::ActorFrameData& actorData) @@ -127,11 +178,12 @@ namespace MWPhysics PhysicsTaskScheduler::~PhysicsTaskScheduler() { - std::unique_lock lock(mSimulationMutex); - mQuit = true; - mNumJobs = 0; - mRemainingSteps = 0; - lock.unlock(); + { + MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); + mQuit = true; + mNumJobs = 0; + mRemainingSteps = 0; + } mHasJob.notify_all(); for (auto& thread : mThreads) thread.join(); @@ -188,7 +240,7 @@ namespace MWPhysics // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. - std::unique_lock lock(mSimulationMutex); + MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); assert(actors.size() == actorsData.size()); double timeStart = mTimer->tick(); @@ -239,7 +291,6 @@ namespace MWPhysics } mAsyncStartTime = mTimer->tick(); - lock.unlock(); mHasJob.notify_all(); if (mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); @@ -247,7 +298,7 @@ namespace MWPhysics void PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { - std::unique_lock lock(mSimulationMutex); + MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); mActors.clear(); @@ -261,25 +312,25 @@ namespace MWPhysics void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); + MaybeLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback); } void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); + MaybeLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback); } void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) { - MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); + MaybeLock lock(mCollisionWorldMutex, mNumThreads); // target the collision object's world origin, this should be the center of the collision object btTransform rayTo; rayTo.setIdentity(); @@ -296,33 +347,33 @@ namespace MWPhysics void PhysicsTaskScheduler::aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); } void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max); } void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask) { - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask; } void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) { mCollisionObjects.insert(collisionObject); - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask); } void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject) { mCollisionObjects.erase(collisionObject); - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->removeCollisionObject(collisionObject); } @@ -334,14 +385,14 @@ namespace MWPhysics } else { - std::unique_lock lock(mUpdateAabbMutex); + MaybeExclusiveLock lock(mUpdateAabbMutex, mNumThreads); mUpdateAabb.insert(std::move(ptr)); } } bool PhysicsTaskScheduler::getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2) { - std::unique_lock lock(mLOSCacheMutex); + MaybeExclusiveLock lock(mLOSCacheMutex, mNumThreads); auto req = LOSRequest(actor1, actor2); auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req); @@ -357,7 +408,7 @@ namespace MWPhysics void PhysicsTaskScheduler::refreshLOSCache() { - std::shared_lock lock(mLOSCacheMutex); + MaybeSharedLock lock(mLOSCacheMutex, mNumThreads); int job = 0; int numLOS = mLOSCache.size(); while ((job = mNextLOS.fetch_add(1, std::memory_order_relaxed)) < numLOS) @@ -376,7 +427,7 @@ namespace MWPhysics void PhysicsTaskScheduler::updateAabbs() { - std::scoped_lock lock(mUpdateAabbMutex); + MaybeExclusiveLock lock(mUpdateAabbMutex, mNumThreads); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), [this](const std::shared_ptr& ptr) { updatePtrAabb(ptr); }); mUpdateAabb.clear(); @@ -384,7 +435,7 @@ namespace MWPhysics void PhysicsTaskScheduler::updatePtrAabb(const std::shared_ptr& ptr) { - std::scoped_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); if (const auto actor = std::dynamic_pointer_cast(ptr)) { actor->updateCollisionObjectPosition(); @@ -420,7 +471,7 @@ namespace MWPhysics { if (mActors[i]->setPosition(mActorsFrameData[i].mPosition)) { - std::scoped_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); mActorsFrameData[i].mPosition = mActors[i]->getPosition(); // account for potential position change made by script mActors[i]->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(mActors[i]->getCollisionObject()); @@ -469,7 +520,7 @@ namespace MWPhysics resultCallback.m_collisionFilterGroup = 0xFF; resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mNumThreads); + MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads); mCollisionWorld->rayTest(pos1, pos2, resultCallback); return !resultCallback.hasHit(); @@ -483,7 +534,7 @@ namespace MWPhysics int job = 0; while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mNumThreads); + MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); } @@ -511,7 +562,7 @@ namespace MWPhysics void PhysicsTaskScheduler::debugDraw() { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); mDebugDrawer->step(); } @@ -537,7 +588,7 @@ namespace MWPhysics return; for (size_t i = 0; i < mActors.size(); ++i) { - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld); } } @@ -556,7 +607,7 @@ namespace MWPhysics { mNewFrame = false; { - std::unique_lock lock(mLOSCacheMutex); + MaybeExclusiveLock lock(mLOSCacheMutex, mNumThreads); mLOSCache.erase( std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), From 442a0e84345bd30a332ba257d778064d213c4cc7 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:00:30 +0000 Subject: [PATCH 1422/2859] avoids two transforms in sky.cpp (#3150) As we discovered in #3148, `Transform` nodes and their low level equivalence `pushModelViewMatrix` are somewhat costly involving a `Matrix::invert` operation per frame. With this PR we avoid one `Transform` node for sun flashes and avoid another `pushModelViewMatrix` call in case the sun is fully visible. --- apps/openmw/mwrender/sky.cpp | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index c2ed62ef87..dd62d6678b 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -81,15 +81,15 @@ namespace return mat; } - osg::ref_ptr createTexturedQuad(int numUvSets=1) + osg::ref_ptr createTexturedQuad(int numUvSets=1, float scale=1.f) { osg::ref_ptr geom = new osg::Geometry; osg::ref_ptr verts = new osg::Vec3Array; - verts->push_back(osg::Vec3f(-0.5, -0.5, 0)); - verts->push_back(osg::Vec3f(-0.5, 0.5, 0)); - verts->push_back(osg::Vec3f(0.5, 0.5, 0)); - verts->push_back(osg::Vec3f(0.5, -0.5, 0)); + verts->push_back(osg::Vec3f(-0.5*scale, -0.5*scale, 0)); + verts->push_back(osg::Vec3f(-0.5*scale, 0.5*scale, 0)); + verts->push_back(osg::Vec3f(0.5*scale, 0.5*scale, 0)); + verts->push_back(osg::Vec3f(0.5*scale, -0.5*scale, 0)); geom->setVertexArray(verts); @@ -621,14 +621,13 @@ private: tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - osg::ref_ptr transform (new osg::PositionAttitudeTransform); - const float scale = 2.6f; - transform->setScale(osg::Vec3f(scale,scale,scale)); + osg::ref_ptr group (new osg::Group); - mTransform->addChild(transform); + mTransform->addChild(group); - osg::ref_ptr geom = createTexturedQuad(); - transform->addChild(geom); + const float scale = 2.6f; + osg::ref_ptr geom = createTexturedQuad(1, scale); + group->addChild(geom); osg::StateSet* stateset = geom->getOrCreateStateSet(); @@ -637,7 +636,7 @@ private: stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); - mSunFlashNode = transform; + mSunFlashNode = group; mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); mSunFlashNode->addCullCallback(mSunFlashCallback); @@ -785,9 +784,11 @@ private: stateset = new osg::StateSet; stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } - - const float threshold = 0.6; - visibleRatio = visibleRatio * (1.f - threshold) + threshold; + else if (visibleRatio < 1.f) + { + const float threshold = 0.6; + visibleRatio = visibleRatio * (1.f - threshold) + threshold; + } } float scale = visibleRatio; @@ -797,11 +798,13 @@ private: // no traverse return; } + else if (scale == 1.f) + traverse(node, cv); else { osg::Matrix modelView = *cv->getModelViewMatrix(); - modelView.preMultScale(osg::Vec3f(visibleRatio, visibleRatio, visibleRatio)); + modelView.preMultScale(osg::Vec3f(scale, scale, scale)); if (stateset) cv->pushStateSet(stateset); From 40f18d6a8bdbf913be35d9ce1479a9712254ff77 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 5 Oct 2021 16:39:50 +0200 Subject: [PATCH 1423/2859] Update cmake.yml to modify cxxflags (#3151) Use the same CXXFLAGS in Github Pipelines as we do in Gitlab Pipelines --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index f9375a3ba5..54966fdfb0 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -27,7 +27,7 @@ jobs: max-size: 1000M - name: Configure - run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" - name: Build run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 From ce54edf011c7a36f7a0fee5736d5593021f26472 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Tue, 5 Oct 2021 18:00:53 +0000 Subject: [PATCH 1424/2859] Update tradewindow.cpp Additional check --- apps/openmw/mwgui/tradewindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 48457ee6d9..8e08a417f8 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -265,6 +265,8 @@ namespace MWGui const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + if (mTotalBalance->getValue() == 0) mCurrentBalance = 0; + // were there any items traded at all? const std::vector& playerBought = playerItemModel->getItemsBorrowedToUs(); const std::vector& merchantBought = mTradeModel->getItemsBorrowedToUs(); From 035307b012f151260b98aa9de62f5d26da26bd4d Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 30 Sep 2021 03:48:47 +0200 Subject: [PATCH 1425/2859] Add tests for openmw options In attempt to document current behaviour. Add commented out checks as desired behaviour. --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/main.cpp | 106 +----- apps/openmw/options.cpp | 115 +++++++ apps/openmw/options.hpp | 11 + apps/openmw_test_suite/CMakeLists.txt | 3 + apps/openmw_test_suite/openmw/options.cpp | 379 ++++++++++++++++++++++ components/files/configurationmanager.cpp | 35 +- components/files/configurationmanager.hpp | 17 +- 8 files changed, 555 insertions(+), 112 deletions(-) create mode 100644 apps/openmw/options.cpp create mode 100644 apps/openmw/options.hpp create mode 100644 apps/openmw_test_suite/openmw/options.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 82a91699a9..ff89b2af7c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -2,6 +2,7 @@ set(GAME main.cpp engine.cpp + options.cpp ${CMAKE_SOURCE_DIR}/files/windows/openmw.rc ${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index de0fb0df03..d1f9fd1787 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -7,6 +7,7 @@ #include #include "engine.hpp" +#include "options.hpp" #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN @@ -39,108 +40,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat namespace bpo = boost::program_options; typedef std::vector StringsVector; - bpo::options_description desc("Syntax: openmw \nAllowed options"); - - desc.add_options() - ("help", "print help message") - ("version", "print version information and quit") - - ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") - - ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") - ->multitoken()->composing(), "set data directories (later directories have higher priority)") - - ("data-local", bpo::value()->default_value(Files::EscapePath(), ""), - "set local data directory (highest priority)") - - ("fallback-archive", bpo::value()->default_value(Files::EscapeStringVector(), "fallback-archive") - ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") - - ("resources", bpo::value()->default_value(Files::EscapePath(), "resources"), - "set resources directory") - - ("start", bpo::value()->default_value(""), - "set initial cell") - - ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") - - ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") - - ("lua-scripts", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "file(s) with a list of global Lua scripts: omwscripts") - - ("no-sound", bpo::value()->implicit_value(true) - ->default_value(false), "disable all sounds") - - ("script-all", bpo::value()->implicit_value(true) - ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") - - ("script-all-dialogue", bpo::value()->implicit_value(true) - ->default_value(false), "compile all dialogue scripts at startup") - - ("script-console", bpo::value()->implicit_value(true) - ->default_value(false), "enable console-only script functionality") - - ("script-run", bpo::value()->default_value(""), - "select a file containing a list of console commands that is executed on startup") - - ("script-warn", bpo::value()->implicit_value (1) - ->default_value (1), - "handling of warnings when compiling scripts\n" - "\t0 - ignore warning\n" - "\t1 - show warning but consider script as correctly compiled anyway\n" - "\t2 - treat warnings as errors") - - ("script-blacklist", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)") - - ("script-blacklist-use", bpo::value()->implicit_value(true) - ->default_value(true), "enable script blacklisting") - - ("load-savegame", bpo::value()->default_value(Files::EscapePath(), ""), - "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") - - ("skip-menu", bpo::value()->implicit_value(true) - ->default_value(false), "skip main menu on game startup") - - ("new-game", bpo::value()->implicit_value(true) - ->default_value(false), "run new game sequence (ignored if skip-menu=0)") - - ("fs-strict", bpo::value()->implicit_value(true) - ->default_value(false), "strict file system handling (no case folding)") - - ("encoding", bpo::value()-> - default_value("win1252"), - "Character encoding used in OpenMW game messages:\n" - "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" - "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" - "\n\twin1252 - Western European (Latin) alphabet, used by default") - - ("fallback", bpo::value()->default_value(FallbackMap(), "") - ->multitoken()->composing(), "fallback values") - - ("no-grab", bpo::value()->implicit_value(true)->default_value(false), "Don't grab mouse cursor") - - ("export-fonts", bpo::value()->implicit_value(true) - ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") - - ("activate-dist", bpo::value ()->default_value (-1), "activation distance override") - - ("random-seed", bpo::value () - ->default_value(Misc::Rng::generateDefaultSeed()), - "seed value for random number generator") - ; - - bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) - .options(desc).allow_unregistered().run(); + bpo::options_description desc = OpenMW::makeOptionsDescription(); bpo::variables_map variables; - // Runtime options override settings from all configs - bpo::store(valid_opts, variables); + Files::parseArgs(argc, argv, variables, desc); bpo::notify(variables); if (variables.count ("help")) @@ -158,9 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - bpo::variables_map composingVariables = cfgMgr.separateComposingVariables(variables, desc); + bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc); cfgMgr.readConfiguration(variables, desc); - cfgMgr.mergeComposingVariables(variables, composingVariables, desc); + Files::mergeComposingVariables(variables, composingVariables, desc); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); Log(Debug::Info) << v.describe(); diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp new file mode 100644 index 0000000000..62ea0910dd --- /dev/null +++ b/apps/openmw/options.cpp @@ -0,0 +1,115 @@ +#include "options.hpp" + +#include +#include +#include + +#include + +namespace +{ + namespace bpo = boost::program_options; +} + +namespace OpenMW +{ + bpo::options_description makeOptionsDescription() + { + bpo::options_description desc("Syntax: openmw \nAllowed options"); + + desc.add_options() + ("help", "print help message") + ("version", "print version information and quit") + + ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") + + ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") + ->multitoken()->composing(), "set data directories (later directories have higher priority)") + + ("data-local", bpo::value()->default_value(Files::EscapePath(), ""), + "set local data directory (highest priority)") + + ("fallback-archive", bpo::value()->default_value(Files::EscapeStringVector(), "fallback-archive") + ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") + + ("resources", bpo::value()->default_value(Files::EscapePath(), "resources"), + "set resources directory") + + ("start", bpo::value()->default_value(""), + "set initial cell") + + ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + + ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") + + ("lua-scripts", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "file(s) with a list of global Lua scripts: omwscripts") + + ("no-sound", bpo::value()->implicit_value(true) + ->default_value(false), "disable all sounds") + + ("script-all", bpo::value()->implicit_value(true) + ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") + + ("script-all-dialogue", bpo::value()->implicit_value(true) + ->default_value(false), "compile all dialogue scripts at startup") + + ("script-console", bpo::value()->implicit_value(true) + ->default_value(false), "enable console-only script functionality") + + ("script-run", bpo::value()->default_value(""), + "select a file containing a list of console commands that is executed on startup") + + ("script-warn", bpo::value()->implicit_value (1) + ->default_value (1), + "handling of warnings when compiling scripts\n" + "\t0 - ignore warning\n" + "\t1 - show warning but consider script as correctly compiled anyway\n" + "\t2 - treat warnings as errors") + + ("script-blacklist", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)") + + ("script-blacklist-use", bpo::value()->implicit_value(true) + ->default_value(true), "enable script blacklisting") + + ("load-savegame", bpo::value()->default_value(Files::EscapePath(), ""), + "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") + + ("skip-menu", bpo::value()->implicit_value(true) + ->default_value(false), "skip main menu on game startup") + + ("new-game", bpo::value()->implicit_value(true) + ->default_value(false), "run new game sequence (ignored if skip-menu=0)") + + ("fs-strict", bpo::value()->implicit_value(true) + ->default_value(false), "strict file system handling (no case folding)") + + ("encoding", bpo::value()-> + default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default") + + ("fallback", bpo::value()->default_value(Fallback::FallbackMap(), "") + ->multitoken()->composing(), "fallback values") + + ("no-grab", bpo::value()->implicit_value(true)->default_value(false), "Don't grab mouse cursor") + + ("export-fonts", bpo::value()->implicit_value(true) + ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") + + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override") + + ("random-seed", bpo::value () + ->default_value(Misc::Rng::generateDefaultSeed()), + "seed value for random number generator") + ; + + return desc; + } +} diff --git a/apps/openmw/options.hpp b/apps/openmw/options.hpp new file mode 100644 index 0000000000..246999eb19 --- /dev/null +++ b/apps/openmw/options.hpp @@ -0,0 +1,11 @@ +#ifndef APPS_OPENMW_OPTIONS_H +#define APPS_OPENMW_OPTIONS_H + +#include + +namespace OpenMW +{ + boost::program_options::options_description makeOptionsDescription(); +} + +#endif diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 7800efac3d..5665f59508 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -40,6 +40,9 @@ if (GTEST_FOUND AND GMOCK_FOUND) shader/parsedefines.cpp shader/parsefors.cpp shader/shadermanager.cpp + + ../openmw/options.cpp + openmw/options.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp new file mode 100644 index 0000000000..ebb956b10c --- /dev/null +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -0,0 +1,379 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace OpenMW; + + namespace bpo = boost::program_options; + + std::vector generateSupportedCharacters(std::vector&& base = {}) + { + std::vector result = std::move(base); + for (int i = 1; i <= std::numeric_limits::max(); ++i) + if (i != '&' && i != '"' && i != ' ' && i != '@' && i != '\n') + result.push_back(std::string(1, i)); + return result; + } + + constexpr std::array supportedAtSignEscapings { + std::pair {'a', '@'}, + std::pair {'h', '#'}, + }; + + MATCHER_P(IsEscapePath, v, "") { return arg.mPath.string() == v; } + + template + void parseArgs(const T& arguments, bpo::variables_map& variables, bpo::options_description& description) + { + Files::parseArgs(static_cast(arguments.size()), arguments.data(), variables, description); + } + + TEST(OpenMWOptionsFromArguments, should_support_equality_to_separate_flag_and_value) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame=save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_single_word_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_multi_component_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "/home/user/openmw/save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "/home/user/openmw/save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_windows_multi_component_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"(C:\OpenMW\save.omwsave)"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(C:\OpenMW\save.omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_spaces) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "my save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my"); +// EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_number_sign) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "my#save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my#save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_at_sign) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "my@save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my?ave.omwsave"); +// EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my@save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_quote) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"(my"save.omwsave)"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(my"save.omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_support_quted_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"("save".omwsave)"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(save)"); +// EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"("save".omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"("save&".omwsave")"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(save".omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"("save.omwsave&&")"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave&"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "save&.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save&.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_multiple_quotes) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"(my"save".omwsave)"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(my"save".omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_compose_data) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--data", "1", "--data", "2"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsEscapePath("1"), IsEscapePath("2"))); + } + + TEST(OpenMWOptionsFromArguments, should_compose_data_from_single_flag) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--data", "1", "2"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsEscapePath("1"), IsEscapePath("2"))); + } + + TEST(OpenMWOptionsFromArguments, should_throw_on_multiple_load_savegame) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "1.omwsave", "--load-savegame", "2.omwsave"}; + bpo::variables_map variables; + EXPECT_THROW(parseArgs(arguments, variables, description), std::exception); + } + + struct OpenMWOptionsFromArgumentsStrings : TestWithParam {}; + + TEST_P(OpenMWOptionsFromArgumentsStrings, should_support_paths_with_certain_characters_in_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::string path = "save_" + std::string(GetParam()) + ".omwsave"; + const std::string pathArgument = "\"" + path + "\""; + const std::array arguments {"openmw", "--load-savegame", pathArgument.c_str()}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), path); + } + + INSTANTIATE_TEST_SUITE_P( + SupportedCharacters, + OpenMWOptionsFromArgumentsStrings, + ValuesIn(generateSupportedCharacters({u8"👍", u8"Ъ", u8"Ǽ", "\n"})) + ); + + struct OpenMWOptionsFromArgumentsEscapings : TestWithParam> {}; + + TEST_P(OpenMWOptionsFromArgumentsEscapings, should_support_escaping_with_at_sign_in_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::string path = "save_@" + std::string(1, GetParam().first) + ".omwsave"; + const std::array arguments {"openmw", "--load-savegame", path.c_str()}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), + "save_" + std::string(1, GetParam().second) + ".omwsave"); + } + + INSTANTIATE_TEST_SUITE_P( + SupportedEscapingsWithAtSign, + OpenMWOptionsFromArgumentsEscapings, + ValuesIn(supportedAtSignEscapings) + ); + + TEST(OpenMWOptionsFromConfig, should_support_single_word_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=save.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="save.omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_strip_outer_quotes_from_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame=""save".omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), ""); +// EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(""save".omwsave")"); + } + + TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path_with_space) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="my save.omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my save.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_number_sign) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=save#.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save#.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_at_sign) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=save@.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save@.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_quote) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame=save".omwsave)"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(save".omwsave)"); + } + + TEST(OpenMWOptionsFromConfig, should_ignore_commented_option) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("#load-savegame=save.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), ""); + } + + TEST(OpenMWOptionsFromConfig, should_throw_on_multiple_load_savegame) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=1.omwsave\nload-savegame=2.omwsave"); + bpo::variables_map variables; + EXPECT_THROW(Files::parseConfig(stream, variables, description), std::exception); + } + + TEST(OpenMWOptionsFromConfig, should_support_multi_component_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=/home/user/openmw/save.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "/home/user/openmw/save.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_support_windows_multi_component_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame=C:\OpenMW\save.omwsave)"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(C:\OpenMW\save.omwsave)"); + } + + TEST(OpenMWOptionsFromConfig, should_compose_data) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("data=1\ndata=2"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsEscapePath("1"), IsEscapePath("2"))); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="save&".omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(save".omwsave)"); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="save.omwsave&&")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave&"); + } + + TEST(OpenMWOptionsFromConfig, should_support_load_savegame_path_with_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=save&.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save&.omwsave"); + } + + struct OpenMWOptionsFromConfigStrings : TestWithParam {}; + + TEST_P(OpenMWOptionsFromConfigStrings, should_support_paths_with_certain_characters_in_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::string path = "save_" + std::string(GetParam()) + ".omwsave"; + std::istringstream stream("load-savegame=\"" + path + "\""); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), path); + } + + INSTANTIATE_TEST_SUITE_P( + SupportedCharacters, + OpenMWOptionsFromConfigStrings, + ValuesIn(generateSupportedCharacters({u8"👍", u8"Ъ", u8"Ǽ"})) + ); +} diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 35679ef293..b5d8b415f5 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -82,7 +82,8 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m mSilent = silent; } -boost::program_options::variables_map ConfigurationManager::separateComposingVariables(boost::program_options::variables_map & variables, boost::program_options::options_description& description) +boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, + boost::program_options::options_description& description) { boost::program_options::variables_map composingVariables; for (auto itr = variables.begin(); itr != variables.end();) @@ -98,7 +99,8 @@ boost::program_options::variables_map ConfigurationManager::separateComposingVar return composingVariables; } -void ConfigurationManager::mergeComposingVariables(boost::program_options::variables_map & first, boost::program_options::variables_map & second, boost::program_options::options_description& description) +void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, + boost::program_options::options_description& description) { // There are a few places this assumes all variables are present in second, but it's never crashed in the wild, so it looks like that's guaranteed. std::set replacedVariables; @@ -234,13 +236,10 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, Log(Debug::Info) << "Loading config file: " << cfgFile.string(); boost::filesystem::ifstream configFileStreamUnfiltered(cfgFile); - boost::iostreams::filtering_istream configFileStream; - configFileStream.push(escape_hash_filter()); - configFileStream.push(configFileStreamUnfiltered); + if (configFileStreamUnfiltered.is_open()) { - boost::program_options::store(boost::program_options::parse_config_file( - configFileStream, description, true), variables); + parseConfig(configFileStreamUnfiltered, variables, description); return true; } @@ -300,4 +299,26 @@ const boost::filesystem::path& ConfigurationManager::getScreenshotPath() const return mScreenshotPath; } +void parseArgs(int argc, const char* const argv[], boost::program_options::variables_map& variables, + boost::program_options::options_description& description) +{ + boost::program_options::store( + boost::program_options::command_line_parser(argc, argv).options(description).allow_unregistered().run(), + variables + ); +} + +void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, + boost::program_options::options_description& description) +{ + boost::iostreams::filtering_istream configFileStream; + configFileStream.push(escape_hash_filter()); + configFileStream.push(stream); + + boost::program_options::store( + boost::program_options::parse_config_file(configFileStream, description, true), + variables + ); +} + } /* namespace Cfg */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 0ec0a1f67d..99e65a7658 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -25,10 +25,6 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet=false); - boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map& variables, boost::program_options::options_description& description); - - void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, boost::program_options::options_description& description); - void processPaths(Files::PathContainer& dataDirs, bool create = false); ///< \param create Try creating the directory, if it does not exist. @@ -68,6 +64,19 @@ struct ConfigurationManager bool mSilent; }; + +boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map& variables, + boost::program_options::options_description& description); + +void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, + boost::program_options::options_description& description); + +void parseArgs(int argc, const char* const argv[], boost::program_options::variables_map& variables, + boost::program_options::options_description& description); + +void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, + boost::program_options::options_description& description); + } /* namespace Cfg */ #endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */ From e581b61ecb758e7d2c6141c18699710817a964ff Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 6 Oct 2021 08:05:10 +0200 Subject: [PATCH 1426/2859] check if FORCE_OPAQUE is available before using it. --- files/shaders/nv_default_fragment.glsl | 2 +- files/shaders/nv_nolighting_fragment.glsl | 2 +- files/shaders/objects_fragment.glsl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 38dca66c40..245f83b620 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -94,7 +94,7 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if FORCE_OPAQUE +#if defined(FORCE_OPAQUE) && FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque gl_FragData[0].a = 1.0; #endif diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl index 76b01828d6..7c4f4737e0 100644 --- a/files/shaders/nv_nolighting_fragment.glsl +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -45,7 +45,7 @@ void main() float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #endif -#if FORCE_OPAQUE +#if defined(FORCE_OPAQUE) && FORCE_OPAQUE gl_FragData[0].a = 1.0; #endif diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index c343b6a189..6f6cede4e4 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -220,7 +220,7 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if FORCE_OPAQUE +#if defined(FORCE_OPAQUE) && FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque gl_FragData[0].a = 1.0; #endif From 787f91211d100d2ecbab8eb16becb6a1e22fb625 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:15:47 +0000 Subject: [PATCH 1427/2859] resets state updater to apply light settings (#3141) resets state updater to apply light settings With this PR we achieve the same effect with fewer lines of code. --- apps/openmw/mwrender/renderingmanager.cpp | 6 +----- components/sceneutil/statesetupdater.hpp | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index be143510b2..331ddeca7b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1281,11 +1281,7 @@ namespace MWRender defines[name] = key; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); - mSceneRoot->removeUpdateCallback(mStateUpdater); - mStateUpdater = new StateUpdater; - mSceneRoot->addUpdateCallback(mStateUpdater); - mStateUpdater->setFogEnd(mViewDistance); - updateAmbient(); + mStateUpdater->reset(); mViewer->startThreading(); } diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index ab091fd39f..35be9cb434 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -46,8 +46,7 @@ namespace SceneUtil /// Set default state - optionally override in derived classes /// @par May be used e.g. to allocate StateAttributes. virtual void setDefaults(osg::StateSet* stateset) {} - - protected: + /// Reset mStateSets, forcing a setDefaults() on the next frame. Can be used to change the defaults if needed. void reset(); From cd4d76f8c5121575b711cc7e19a46f1acf49014b Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:53:24 +0000 Subject: [PATCH 1428/2859] discard off-screen lights (#3120) Currently, we run culling tests against all lights in the scene during LightListCallback::pushLightState. We can avoid most of these tests by removing off-screen lights at an earlier stage. We should benchmark the cumulative time spent within LightListCallback::pushLightState before and after this PR. --- components/sceneutil/lightmanager.cpp | 104 +++++++++++++------------- components/sceneutil/lightmanager.hpp | 2 +- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 0abebd4884..f38fd80d26 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1192,8 +1192,10 @@ namespace SceneUtil return stateset; } - const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) + const std::vector& LightManager::getLightsInViewSpace(osgUtil::CullVisitor *cv, const osg::RefMatrix* viewMatrix, size_t frameNum) { + osg::Camera* camera = cv->getCurrentCamera(); + osg::observer_ptr camPtr (camera); auto it = mLightsInViewSpace.find(camPtr); @@ -1209,7 +1211,7 @@ namespace SceneUtil float radius = transform.mLightSource->getRadius(); - osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius * mPointLightRadiusMultiplier); + osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius); transformBoundingSphere(worldViewMat, viewBound); if (!isReflection && mPointLightFadeEnd != 0.f) @@ -1223,6 +1225,15 @@ namespace SceneUtil light->setDiffuse(light->getDiffuse() * fade); } + // remove lights culled by this camera + if (!usingFFP()) + { + viewBound._radius *= 2.f; + if (cv->getModelViewCullingStack().front().isCulled(viewBound)) + continue; + viewBound._radius /= 2.f; + } + viewBound._radius *= mPointLightRadiusMultiplier; LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; @@ -1295,46 +1306,41 @@ namespace SceneUtil return false; // Possible optimizations: - // - cull list of lights by the camera frustum // - organize lights in a quad tree - // update light list if necessary - // makes sure we don't update it more than once per frame when rendering with multiple cameras - if (mLastFrameNumber != cv->getTraversalNumber()) - { - mLastFrameNumber = cv->getTraversalNumber(); + mLastFrameNumber = cv->getTraversalNumber(); - // Don't use Camera::getViewMatrix, that one might be relative to another camera! - const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); - const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); + // Don't use Camera::getViewMatrix, that one might be relative to another camera! + const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); + const std::vector& lights = mLightManager->getLightsInViewSpace(cv, viewMatrix, mLastFrameNumber); - // get the node bounds in view space - // NB do not node->getBound() * modelView, that would apply the node's transformation twice - osg::BoundingSphere nodeBound; - osg::Transform* transform = node->asTransform(); - if (transform) - { - for (size_t i = 0; i < transform->getNumChildren(); ++i) - nodeBound.expandBy(transform->getChild(i)->getBound()); - } - else - nodeBound = node->getBound(); - osg::Matrixf mat = *cv->getModelViewMatrix(); - transformBoundingSphere(mat, nodeBound); + // get the node bounds in view space + // NB do not node->getBound() * modelView, that would apply the node's transformation twice + osg::BoundingSphere nodeBound; + osg::Transform* transform = node->asTransform(); + if (transform) + { + for (size_t i = 0; i < transform->getNumChildren(); ++i) + nodeBound.expandBy(transform->getChild(i)->getBound()); + } + else + nodeBound = node->getBound(); + osg::Matrixf mat = *cv->getModelViewMatrix(); + transformBoundingSphere(mat, nodeBound); - mLightList.clear(); - for (size_t i = 0; i < lights.size(); ++i) - { - const LightManager::LightSourceViewBound& l = lights[i]; + mLightList.clear(); + for (size_t i = 0; i < lights.size(); ++i) + { + const LightManager::LightSourceViewBound& l = lights[i]; - if (mIgnoredLightSources.count(l.mLightSource)) - continue; + if (mIgnoredLightSources.count(l.mLightSource)) + continue; - if (l.mViewBound.intersects(nodeBound)) - mLightList.push_back(&l); - } + if (l.mViewBound.intersects(nodeBound)) + mLightList.push_back(&l); } + if (!mLightList.empty()) { size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight(); @@ -1343,31 +1349,25 @@ namespace SceneUtil if (mLightList.size() > maxLights) { - // remove lights culled by this camera LightManager::LightList lightList = mLightList; - for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) - { - osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); - osg::BoundingSphere bs = (*it)->mViewBound; - bs._radius = bs._radius * 2.0; - osg::CullingSet& cullingSet = stack.front(); - if (cullingSet.isCulled(bs)) + if (mLightManager->usingFFP()) + { + for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) { - it = lightList.erase(it); - continue; + osg::BoundingSphere bs = (*it)->mViewBound; + bs._radius = bs._radius * 2.0; + if (cv->getModelViewCullingStack().front().isCulled(bs)) + it = lightList.erase(it); + else + ++it; } - else - ++it; } - if (lightList.size() > maxLights) - { - // sort by proximity to camera, then get rid of furthest away lights - std::sort(lightList.begin(), lightList.end(), sortLights); - while (lightList.size() > maxLights) - lightList.pop_back(); - } + // sort by proximity to camera, then get rid of furthest away lights + std::sort(lightList.begin(), lightList.end(), sortLights); + while (lightList.size() > maxLights) + lightList.pop_back(); stateset = mLightManager->getLightListStateSet(lightList, cv->getTraversalNumber(), cv->getCurrentRenderStage()->getInitialViewMatrix()); } else diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 4048bf9b09..b518a4723c 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -157,7 +157,7 @@ namespace SceneUtil /// Internal use only, called automatically by the LightSource's UpdateCallback void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); - const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum); + const std::vector& getLightsInViewSpace(osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum); osg::ref_ptr getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix); From 87d52dc1fd106b659424cca5fbbc3610d30888fc Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 6 Oct 2021 10:04:03 +0000 Subject: [PATCH 1429/2859] fixes coverity-ci warning --- components/nifosg/particle.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 0f103588e8..7821b9c2b8 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -70,16 +70,14 @@ void ParticleSystem::drawImplementation(osg::RenderInfo& renderInfo) const void InverseWorldMatrix::operator()(osg::MatrixTransform *node, osg::NodeVisitor *nv) { - if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) - { - osg::NodePath path = nv->getNodePath(); - path.pop_back(); + osg::NodePath path = nv->getNodePath(); + path.pop_back(); + + osg::Matrix mat = osg::computeLocalToWorld( path ); + mat.orthoNormalize(mat); // don't undo the scale + mat.invert(mat); + node->setMatrix(mat); - osg::Matrix mat = osg::computeLocalToWorld( path ); - mat.orthoNormalize(mat); // don't undo the scale - mat.invert(mat); - node->setMatrix(mat); - } traverse(node,nv); } From 75402654320dfd3dc810e41fc432a68b66ec2259 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Oct 2021 17:14:00 +0200 Subject: [PATCH 1430/2859] Fix regression #6328 --- apps/openmw/mwmechanics/spelleffects.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 24816b9ed1..e8795b8bc4 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -63,9 +63,9 @@ namespace auto& creatureStats = target.getClass().getCreatureStats(target); auto stat = creatureStats.getDynamic(index); float current = stat.getCurrent(); - stat.setModified(stat.getModified() - magnitude, 0); - stat.setCurrentModified(stat.getCurrentModified() - magnitude); - stat.setCurrent(current - magnitude); + stat.setModified(stat.getModified() + magnitude, 0); + stat.setCurrentModified(stat.getCurrentModified() + magnitude); + stat.setCurrent(current + magnitude); creatureStats.setDynamic(index, stat); } @@ -487,7 +487,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co index = 2; // Damage "Dynamic" abilities reduce the base value if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) - modDynamicStat(target, index, effect.mMagnitude); + modDynamicStat(target, index, -effect.mMagnitude); else { static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); From a1825980c496eb100e719a918ab2cc9f96389f52 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Oct 2021 17:28:48 +0200 Subject: [PATCH 1431/2859] Define OpenMW specific C++ flags --- CI/before_script.linux.sh | 1 + CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index bc0eb0013d..5d20fa75ce 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -32,6 +32,7 @@ declare -a CMAKE_CONF_OPTS=( -DCMAKE_INSTALL_PREFIX=install -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" + -DOPENMW_CXX_FLAGS="-Werror=implicit-fallthrough" ) if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then diff --git a/CMakeLists.txt b/CMakeLists.txt index ae4584a4cd..2564379847 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -543,8 +543,8 @@ if (BUILD_OPENCS) add_subdirectory (extern/osgQt) endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=implicit-fallthrough") +if (OPENMW_CXX_FLAGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}") endif() # Components From dfc72e9b7e7a78176d2ab38348a2dc608131617f Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Oct 2021 18:25:28 +0200 Subject: [PATCH 1432/2859] Make StayOutside only block teleportation from exteriors to interiors --- CHANGELOG.md | 1 + apps/openmw/mwgui/travelwindow.cpp | 2 +- apps/openmw/mwworld/actionteleport.cpp | 7 ++++--- apps/openmw/mwworld/actionteleport.hpp | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa8c2e79f..7fd364bfe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6302: Teleporting disabled actor breaks its disabled state Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken + Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 22e3a55566..3fc0673735 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -74,7 +74,7 @@ namespace MWGui // Add price for the travelling followers std::set followers; - MWWorld::ActionTeleport::getFollowers(player, followers); + MWWorld::ActionTeleport::getFollowers(player, followers, !interior); // Apply followers cost, unlike vanilla the first follower doesn't travel for free price *= 1 + static_cast(followers.size()); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 56274eb9a0..d74dcd82f7 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -6,6 +6,7 @@ #include "../mwmechanics/creaturestats.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "player.hpp" @@ -24,7 +25,7 @@ namespace MWWorld { // Find any NPCs that are following the actor and teleport them with him std::set followers; - getFollowers(actor, followers, true); + getFollowers(actor, followers, mCellName.empty(), true); for (std::set::iterator it = followers.begin(); it != followers.end(); ++it) teleport(*it); @@ -62,7 +63,7 @@ namespace MWWorld } } - void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set& out, bool includeHostiles) { + void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set& out, bool toExterior, bool includeHostiles) { std::set followers; MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers); @@ -75,7 +76,7 @@ namespace MWWorld if (!includeHostiles && follower.getClass().getCreatureStats(follower).getAiSequence().isInCombat(actor)) continue; - if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) + if (!toExterior && !script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1 && follower.getCell()->getCell()->isExterior()) continue; if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() > 800 * 800) diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp index 0a981a418e..fcbf59a203 100644 --- a/apps/openmw/mwworld/actionteleport.hpp +++ b/apps/openmw/mwworld/actionteleport.hpp @@ -30,7 +30,7 @@ namespace MWWorld /// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the output, /// e.g. so that the teleport action can calm them. - static void getFollowers(const MWWorld::Ptr& actor, std::set& out, bool includeHostiles = false); + static void getFollowers(const MWWorld::Ptr& actor, std::set& out, bool toExterior, bool includeHostiles = false); }; } From 5037def3b32654b2bdad94ab353d44e851aeab81 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Oct 2021 21:27:08 +0200 Subject: [PATCH 1433/2859] Parse local variables sharing a name with a function as variables --- CHANGELOG.md | 1 + components/compiler/exprparser.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa8c2e79f..65736756bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod Bug #6302: Teleporting disabled actor breaks its disabled state Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken Feature #890: OpenMW-CS: Column filtering diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 23f15c8bf5..2d525e6f8c 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -353,15 +353,21 @@ namespace Compiler { if (const Extensions *extensions = getContext().getExtensions()) { + char returnType; // ignored std::string argumentType; // ignored bool hasExplicit = false; // ignored - if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + bool isInstruction = extensions->isInstruction (keyword, argumentType, hasExplicit); + + if(isInstruction || (mExplicit.empty() && extensions->isFunction(keyword, returnType, argumentType, hasExplicit))) { - // pretend this is not a keyword std::string name = loc.mLiteral; if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') name = name.substr (1, name.size()-2); - return parseName (name, loc, scanner); + if(isInstruction || mLocals.getType(Misc::StringUtils::lowerCase(name)) != ' ') + { + // pretend this is not a keyword + return parseName (name, loc, scanner); + } } } From e382f71aea92d05578a41bd6469601549f64561b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 7 Oct 2021 00:39:23 +0100 Subject: [PATCH 1434/2859] Add implementation of config file parser lifted from Boost --- components/CMakeLists.txt | 2 +- components/files/configfileparser.cpp | 289 ++++++++++++++++++++++ components/files/configfileparser.hpp | 17 ++ components/files/configurationmanager.cpp | 3 +- 4 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 components/files/configfileparser.cpp create mode 100644 components/files/configfileparser.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7983de6190..ccac8f8cb7 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -105,7 +105,7 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF() add_component_dir (files linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager escape - lowlevelfile constrainedfilestream memorystream + lowlevelfile constrainedfilestream memorystream configfileparser ) add_component_dir (compiler diff --git a/components/files/configfileparser.cpp b/components/files/configfileparser.cpp new file mode 100644 index 0000000000..7c43e1f67c --- /dev/null +++ b/components/files/configfileparser.cpp @@ -0,0 +1,289 @@ +#include "configfileparser.hpp" + +#include +#include + +namespace Files +{ + namespace + { + /** Standalone parser for config files in ini-line format. + The parser is a model of single-pass lvalue iterator, and + default constructor creates past-the-end-iterator. The typical usage is: + config_file_iterator i(is, ... set of options ...), e; + for(; i !=e; ++i) { + *i; + } + + Syntax conventions: + + - config file can not contain positional options + - '#' is comment character: it is ignored together with + the rest of the line. + - variable assignments are in the form + name '=' value. + spaces around '=' are trimmed. + - Section names are given in brackets. + + The actual option name is constructed by combining current section + name and specified option name, with dot between. If section_name + already contains dot at the end, new dot is not inserted. For example: + @verbatim + [gui.accessibility] + visual_bell=yes + @endverbatim + will result in option "gui.accessibility.visual_bell" with value + "yes" been returned. + + TODO: maybe, we should just accept a pointer to options_description + class. + */ + class common_config_file_iterator + : public boost::eof_iterator + { + public: + common_config_file_iterator() { found_eof(); } + common_config_file_iterator( + const std::set& allowed_options, + bool allow_unregistered = false); + + virtual ~common_config_file_iterator() {} + + public: // Method required by eof_iterator + + void get(); + +#if BOOST_WORKAROUND(_MSC_VER, <= 1900) + void decrement() {} + void advance(difference_type) {} +#endif + + protected: // Stubs for derived classes + + // Obtains next line from the config file + // Note: really, this design is a bit ugly + // The most clean thing would be to pass 'line_iterator' to + // constructor of this class, but to avoid templating this class + // we'd need polymorphic iterator, which does not exist yet. + virtual bool getline(std::string&) { return false; } + + protected: + /** Adds another allowed option. If the 'name' ends with + '*', then all options with the same prefix are + allowed. For example, if 'name' is 'foo*', then 'foo1' and + 'foo_bar' are allowed. */ + void add_option(const char* name); + + // Returns true if 's' is a registered option name. + bool allowed_option(const std::string& s) const; + + // That's probably too much data for iterator, since + // it will be copied, but let's not bother for now. + std::set allowed_options; + // Invariant: no element is prefix of other element. + std::set allowed_prefixes; + std::string m_prefix; + bool m_allow_unregistered; + }; + + common_config_file_iterator::common_config_file_iterator( + const std::set& allowed_options, + bool allow_unregistered) + : allowed_options(allowed_options), + m_allow_unregistered(allow_unregistered) + { + for (std::set::const_iterator i = allowed_options.begin(); + i != allowed_options.end(); + ++i) + { + add_option(i->c_str()); + } + } + + void common_config_file_iterator::add_option(const char* name) + { + std::string s(name); + assert(!s.empty()); + if (*s.rbegin() == '*') { + s.resize(s.size() - 1); + bool bad_prefixes(false); + // If 's' is a prefix of one of allowed suffix, then + // lower_bound will return that element. + // If some element is prefix of 's', then lower_bound will + // return the next element. + std::set::iterator i = allowed_prefixes.lower_bound(s); + if (i != allowed_prefixes.end()) { + if (i->find(s) == 0) + bad_prefixes = true; + } + if (i != allowed_prefixes.begin()) { + --i; + if (s.find(*i) == 0) + bad_prefixes = true; + } + if (bad_prefixes) + boost::throw_exception(bpo::error("options '" + std::string(name) + "' and '" + + *i + "*' will both match the same " + "arguments from the configuration file")); + allowed_prefixes.insert(s); + } + } + + std::string trim_ws(const std::string& s) + { + std::string::size_type n, n2; + n = s.find_first_not_of(" \t\r\n"); + if (n == std::string::npos) + return std::string(); + else { + n2 = s.find_last_not_of(" \t\r\n"); + return s.substr(n, n2 - n + 1); + } + } + + void common_config_file_iterator::get() + { + std::string s; + std::string::size_type n; + bool found = false; + + while (this->getline(s)) { + + // strip '#' comments and whitespace + if ((n = s.find('#')) != std::string::npos) + s = s.substr(0, n); + s = trim_ws(s); + + if (!s.empty()) { + // Handle section name + if (*s.begin() == '[' && *s.rbegin() == ']') { + m_prefix = s.substr(1, s.size() - 2); + if (*m_prefix.rbegin() != '.') + m_prefix += '.'; + } + else if ((n = s.find('=')) != std::string::npos) { + + std::string name = m_prefix + trim_ws(s.substr(0, n)); + std::string value = trim_ws(s.substr(n + 1)); + + bool registered = allowed_option(name); + if (!registered && !m_allow_unregistered) + boost::throw_exception(bpo::unknown_option(name)); + + found = true; + this->value().string_key = name; + this->value().value.clear(); + this->value().value.push_back(value); + this->value().unregistered = !registered; + this->value().original_tokens.clear(); + this->value().original_tokens.push_back(name); + this->value().original_tokens.push_back(value); + break; + + } else { + boost::throw_exception(bpo::invalid_config_file_syntax(s, bpo::invalid_syntax::unrecognized_line)); + } + } + } + if (!found) + found_eof(); + } + + bool common_config_file_iterator::allowed_option(const std::string& s) const + { + std::set::const_iterator i = allowed_options.find(s); + if (i != allowed_options.end()) + return true; + // If s is "pa" where "p" is allowed prefix then + // lower_bound should find the element after "p". + // This depends on 'allowed_prefixes' invariant. + i = allowed_prefixes.lower_bound(s); + if (i != allowed_prefixes.begin() && s.find(*--i) == 0) + return true; + return false; + } + + + + template + class basic_config_file_iterator : public Files::common_config_file_iterator { + public: + basic_config_file_iterator() + { + found_eof(); + } + + /** Creates a config file parser for the specified stream. + */ + basic_config_file_iterator(std::basic_istream& is, + const std::set& allowed_options, + bool allow_unregistered = false); + + private: // base overrides + + bool getline(std::string&); + + private: // internal data + std::shared_ptr > is; + }; + + template + basic_config_file_iterator:: + basic_config_file_iterator(std::basic_istream& is, + const std::set& allowed_options, + bool allow_unregistered) + : common_config_file_iterator(allowed_options, allow_unregistered) + { + this->is.reset(&is, bpo::detail::null_deleter()); + get(); + } + + template + bool basic_config_file_iterator::getline(std::string& s) + { + std::basic_string in; + if (std::getline(*is, in)) { + s = bpo::to_internal(in); + return true; + } else { + return false; + } + } + } + + template + bpo::basic_parsed_options + parse_config_file(std::basic_istream& is, + const bpo::options_description& desc, + bool allow_unregistered) + { + std::set allowed_options; + + const std::vector >& options = desc.options(); + for (unsigned i = 0; i < options.size(); ++i) + { + const bpo::option_description& d = *options[i]; + + if (d.long_name().empty()) + boost::throw_exception( + bpo::error("abbreviated option names are not permitted in options configuration files")); + + allowed_options.insert(d.long_name()); + } + + // Parser return char strings + bpo::parsed_options result(&desc); + copy(basic_config_file_iterator( + is, allowed_options, allow_unregistered), + basic_config_file_iterator(), + back_inserter(result.options)); + // Convert char strings into desired type. + return bpo::basic_parsed_options(result); + } + + template + bpo::basic_parsed_options + parse_config_file(std::basic_istream& is, + const bpo::options_description& desc, + bool allow_unregistered); +} diff --git a/components/files/configfileparser.hpp b/components/files/configfileparser.hpp new file mode 100644 index 0000000000..60ab27a6d4 --- /dev/null +++ b/components/files/configfileparser.hpp @@ -0,0 +1,17 @@ +#ifndef COMPONENTS_FILES_CONFIGFILEPARSER_HPP +#define COMPONENTS_FILES_CONFIGFILEPARSER_HPP + +#include + +namespace Files +{ + +namespace bpo = boost::program_options; + +template +bpo::basic_parsed_options parse_config_file(std::basic_istream&, const bpo::options_description&, + bool allow_unregistered = false); + +} + +#endif // COMPONENTS_FILES_CONFIGFILEPARSER_HPP \ No newline at end of file diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 35679ef293..7fd134cf0f 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -1,6 +1,7 @@ #include "configurationmanager.hpp" #include +#include #include #include @@ -239,7 +240,7 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, configFileStream.push(configFileStreamUnfiltered); if (configFileStreamUnfiltered.is_open()) { - boost::program_options::store(boost::program_options::parse_config_file( + boost::program_options::store(Files::parse_config_file( configFileStream, description, true), variables); return true; From cf3596fbb4bd05f30c586f5a0a4111c038f313d4 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 7 Oct 2021 00:44:50 +0100 Subject: [PATCH 1435/2859] Add copyright preamble --- components/files/configfileparser.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/files/configfileparser.cpp b/components/files/configfileparser.cpp index 7c43e1f67c..fe98ea11d2 100644 --- a/components/files/configfileparser.cpp +++ b/components/files/configfileparser.cpp @@ -1,3 +1,11 @@ +// This file's contents is largely lifted from boost::program_options with only minor modification. +// Its original preamble (without updated dates) from those source files is below: + +// Copyright Vladimir Prus 2002-2004. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + #include "configfileparser.hpp" #include From 08608da62cd7b941dff11110b95e49ce39cda999 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 7 Oct 2021 08:29:38 +0000 Subject: [PATCH 1436/2859] optimizer.cpp --- components/sceneutil/optimizer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index dffbe62ec7..2cfabbfe19 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -913,11 +913,11 @@ void Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() unsigned int childIndex = (*pitr)->getChildIndex(group); for (unsigned int i=0; igetNumChildren(); ++i) { - osg::Node* child = group->getChild(i); - (*pitr)->insertChild(childIndex++, child); + if (i==0) + (*pitr)->setChild(childIndex, group->getChild(i)); + else + (*pitr)->insertChild(childIndex+i, group->getChild(i)); } - - (*pitr)->removeChild(group); } group->removeChildren(0, group->getNumChildren()); From e5abadc234517beb421a84f410cbc8607e1848dd Mon Sep 17 00:00:00 2001 From: florent teppe Date: Thu, 7 Oct 2021 13:26:40 +0000 Subject: [PATCH 1437/2859] Fix keyword search when the keyword is preceded by a non whitespace non alpha character --- apps/openmw/mwdialogue/keywordsearch.hpp | 23 --------- .../mwdialogue/test_keywordsearch.cpp | 50 ++++++++++++++++++- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 8ba3349bc8..0c14ea8f8d 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include // std::reverse @@ -69,18 +68,6 @@ public: return false; } - static bool isWhitespaceUTF8(const int utf8Char) - { - if (utf8Char >= 0 && utf8Char <= static_cast( std::numeric_limits::max())) - { - //That function has undefined behavior if the character doesn't fit in unsigned char - return std::isspace(utf8Char); - } - else - { - return false; - } - } static bool sortMatches(const Match& left, const Match& right) { @@ -92,16 +79,6 @@ public: std::vector matches; for (Point i = beg; i != end; ++i) { - // check if previous character marked start of new word - if (i != beg) - { - Point prev = i; - --prev; - if(!isWhitespaceUTF8(*prev)) - continue; - } - - // check first character typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index 62b6f67aae..6128c7e5dd 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -74,7 +74,7 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) search.seed("états", 0); search.seed("ïrradiés", 0); search.seed("ça nous déçois", 0); - + search.seed("ois", 0); std::string text = "les nations unis ont réunis le monde entier, états units inclus pour parler du problème des gens ïrradiés et ça nous déçois"; @@ -86,3 +86,51 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) EXPECT_EQ (std::string( matches[1].mBeg, matches[1].mEnd) , "ïrradiés"); EXPECT_EQ (std::string( matches[2].mBeg, matches[2].mEnd) , "ça nous déçois"); } + +TEST_F(KeywordSearchTest, keyword_test_non_alpha_non_whitespace_word_begin) +{ + // We make sure that the search works well even if the separator is not a whitespace + MWDialogue::KeywordSearch search; + search.seed("Report to caius cosades", 0); + + + + std::string text = "I was told to \"Report to caius cosades\""; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 1); + EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Report to caius cosades"); +} + +TEST_F(KeywordSearchTest, keyword_test_russian_non_ascii_before) +{ + // We make sure that the search works well even if the separator is not a whitespace with russian chars + MWDialogue::KeywordSearch search; + search.seed("Доложить Каю Косадесу", 0); + + std::string text = "Что? Да. Я Кай Косадес. То есть как это, вам велели «Доложить Каю Косадесу»? О чем вы говорите?"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 1); + EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Доложить Каю Косадесу"); +} + +TEST_F(KeywordSearchTest, keyword_test_russian_ascii_before) +{ + // We make sure that the search works well even if the separator is not a whitespace with russian chars + MWDialogue::KeywordSearch search; + search.seed("Доложить Каю Косадесу", 0); + + std::string text = "Что? Да. Я Кай Косадес. То есть как это, вам велели 'Доложить Каю Косадесу'? О чем вы говорите?"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 1); + EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Доложить Каю Косадесу"); +} + From caafd0c66736638d22d93a6f9b3bac4a1b020120 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 7 Oct 2021 21:09:43 +0200 Subject: [PATCH 1438/2859] Remove CE enchantments before saving stats when turning into a werewolf --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5e18e737df..e7ae900086 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1761,17 +1761,6 @@ namespace MWMechanics MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); - if (actor == player->getPlayer()) - { - if (werewolf) - { - player->saveStats(); - player->setWerewolfStats(); - } - else - player->restoreStats(); - } - // Werewolfs can not cast spells, so we need to unset the prepared spell if there is one. if (npcStats.getDrawState() == MWMechanics::DrawState_Spell) npcStats.setDrawState(MWMechanics::DrawState_Nothing); @@ -1800,11 +1789,16 @@ namespace MWMechanics if (werewolf) { + // Remove CE enchantments before saving stats + mActors.updateActor(actor, 0.f); + player->saveStats(); + player->setWerewolfStats(); windowManager->forceHide(MWGui::GW_Inventory); windowManager->forceHide(MWGui::GW_Magic); } else { + player->restoreStats(); windowManager->unsetForceHide(MWGui::GW_Inventory); windowManager->unsetForceHide(MWGui::GW_Magic); } From 5242e2695c3cf908820607bd28b50c42355421f2 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Fri, 8 Oct 2021 07:56:55 +0000 Subject: [PATCH 1439/2859] avoids memory allocations within ComputeLightSpaceBounds (#3156) Currently, we create a new ComputeLightSpaceBounds visitor per frame. Within this visitor, we require excessive memory allocations, mainly a new osg::RefMatrix per encountered Transform node. With this PR we reuse a single ComputeLightSpaceBounds visitor across frames and enable the createOrReuseMatrix functionality to avoid allocating new matrices every frame. osgUtil::CullVisitor internally uses the same approach. --- components/sceneutil/mwshadowtechnique.cpp | 29 ++++++++++++++++------ components/sceneutil/mwshadowtechnique.hpp | 6 ++++- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 95ff8f2b01..db7aef7cb1 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -353,18 +353,20 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) } // namespace -MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix) : +MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) { setCullingMode(osg::CullSettings::VIEW_FRUSTUM_CULLING); - pushViewport(viewport); - pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); - pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); - setName("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds,AcceptedByComponentsTerrainQuadTreeWorld"); } +void MWShadowTechnique::ComputeLightSpaceBounds::reset() +{ + osg::CullStack::reset(); + _bb = osg::BoundingBox(); +} + void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Node& node) { if (isCulled(node)) return; @@ -421,9 +423,9 @@ void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Transform& transform // absolute transforms won't affect a shadow map so their subgraphs should be ignored. if (transform.getReferenceFrame() == osg::Transform::RELATIVE_RF) { - osg::ref_ptr matrix = new osg::RefMatrix(*getModelViewMatrix()); + osg::RefMatrix* matrix = createOrReuseMatrix(*getModelViewMatrix()); transform.computeLocalToWorldMatrix(*matrix, this); - pushModelViewMatrix(matrix.get(), transform.getReferenceFrame()); + pushModelViewMatrix(matrix, transform.getReferenceFrame()); traverse(transform); @@ -1125,7 +1127,12 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) // osg::ElapsedTime timer; osg::ref_ptr viewport = new osg::Viewport(0,0,2048,2048); - ComputeLightSpaceBounds clsb(viewport.get(), projectionMatrix, viewMatrix); + if (!_clsb) _clsb = new ComputeLightSpaceBounds; + ComputeLightSpaceBounds& clsb = *_clsb; + clsb.reset(); + clsb.pushViewport(viewport); + clsb.pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); + clsb.pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); clsb.setTraversalMask(_shadowedScene->getCastsShadowTraversalMask()); osg::Matrixd invertModelView; @@ -1139,6 +1146,12 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) _shadowedScene->accept(clsb); + clsb.popCullingSet(); + + clsb.popModelViewMatrix(); + clsb.popProjectionMatrix(); + clsb.popViewport(); + // OSG_NOTICE<<"Extents of LightSpace "<\nAllowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing()) - ("data-local", boost::program_options::value()->default_value(Files::EscapePath(), "")) + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) + ("data-local", boost::program_options::value()->default_value(Files::PathContainer::value_type(), "")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) - ("encoding", boost::program_options::value()->default_value("win1252")) - ("resources", boost::program_options::value()->default_value(Files::EscapePath(), "resources")) - ("fallback-archive", boost::program_options::value()-> - default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken()) + ("encoding", boost::program_options::value()->default_value("win1252")) + ("resources", boost::program_options::value()->default_value(boost::filesystem::path(), "resources")) + ("fallback-archive", boost::program_options::value>()-> + default_value(std::vector(), "fallback-archive")->multitoken()) ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") - ("script-blacklist", boost::program_options::value()->default_value(Files::EscapeStringVector(), "") + ("script-blacklist", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ("script-blacklist-use", boost::program_options::value()->implicit_value(true) ->default_value(true), "enable script blacklisting"); @@ -108,24 +108,24 @@ std::pair > CS::Editor::readConfi Fallback::Map::init(variables["fallback"].as().mMap); - mEncodingName = variables["encoding"].as().toStdString(); + mEncodingName = variables["encoding"].as(); mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str())); - mDocumentManager.setResourceDir (mResources = variables["resources"].as().mPath); + mDocumentManager.setResourceDir (mResources = variables["resources"].as()); if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( - variables["script-blacklist"].as().toStdStringVector()); + variables["script-blacklist"].as>()); mFsStrict = variables["fs-strict"].as(); Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = Files::PathContainer(Files::EscapePath::toPathContainer(variables["data"].as())); + dataDirs = variables["data"].as(); } - Files::PathContainer::value_type local(variables["data-local"].as().mPath); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) dataLocal.push_back(local); @@ -155,7 +155,7 @@ std::pair > CS::Editor::readConfi mFileDialog.addFiles(path); } - return std::make_pair (dataDirs, variables["fallback-archive"].as().toStdStringVector()); + return std::make_pair (dataDirs, variables["fallback-archive"].as>()); } void CS::Editor::createGame() diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index de0fb0df03..5de177a907 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -45,31 +45,31 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("help", "print help message") ("version", "print version information and quit") - ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") + ("replace", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") - ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") + ("data", bpo::value()->default_value(Files::PathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") - ("data-local", bpo::value()->default_value(Files::EscapePath(), ""), + ("data-local", bpo::value()->default_value(Files::PathContainer::value_type(), ""), "set local data directory (highest priority)") - ("fallback-archive", bpo::value()->default_value(Files::EscapeStringVector(), "fallback-archive") + ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") - ("resources", bpo::value()->default_value(Files::EscapePath(), "resources"), + ("resources", bpo::value()->default_value(boost::filesystem::path(), "resources"), "set resources directory") - ("start", bpo::value()->default_value(""), + ("start", bpo::value()->default_value(""), "set initial cell") - ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") + ("content", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") - ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") + ("groundcover", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") - ("lua-scripts", bpo::value()->default_value(Files::EscapeStringVector(), "") + ("lua-scripts", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "file(s) with a list of global Lua scripts: omwscripts") ("no-sound", bpo::value()->implicit_value(true) @@ -84,7 +84,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-console", bpo::value()->implicit_value(true) ->default_value(false), "enable console-only script functionality") - ("script-run", bpo::value()->default_value(""), + ("script-run", bpo::value()->default_value(""), "select a file containing a list of console commands that is executed on startup") ("script-warn", bpo::value()->implicit_value (1) @@ -94,13 +94,13 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat "\t1 - show warning but consider script as correctly compiled anyway\n" "\t2 - treat warnings as errors") - ("script-blacklist", bpo::value()->default_value(Files::EscapeStringVector(), "") + ("script-blacklist", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)") ("script-blacklist-use", bpo::value()->implicit_value(true) ->default_value(true), "enable script blacklisting") - ("load-savegame", bpo::value()->default_value(Files::EscapePath(), ""), + ("load-savegame", bpo::value()->default_value(boost::filesystem::path(), ""), "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") ("skip-menu", bpo::value()->implicit_value(true) @@ -112,7 +112,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") - ("encoding", bpo::value()-> + ("encoding", bpo::value()-> default_value("win1252"), "Character encoding used in OpenMW game messages:\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" @@ -153,7 +153,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat { cfgMgr.readConfiguration(variables, desc, true); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); getRawStdout() << v.describe() << std::endl; return false; } @@ -162,38 +162,38 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc); cfgMgr.mergeComposingVariables(variables, composingVariables, desc); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); Log(Debug::Info) << v.describe(); engine.setGrabMouse(!variables["no-grab"].as()); // Font encoding settings - std::string encoding(variables["encoding"].as().toStdString()); + std::string encoding(variables["encoding"].as()); Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); engine.setEncoding(ToUTF8::calculateEncoding(encoding)); // directory settings engine.enableFSStrict(variables["fs-strict"].as()); - Files::PathContainer dataDirs(Files::EscapePath::toPathContainer(variables["data"].as())); + Files::PathContainer dataDirs(variables["data"].as()); - Files::PathContainer::value_type local(variables["data-local"].as().mPath); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) dataDirs.push_back(local); cfgMgr.processPaths(dataDirs); - engine.setResourceDir(variables["resources"].as().mPath); + engine.setResourceDir(variables["resources"].as()); engine.setDataDirs(dataDirs); // fallback archives - StringsVector archives = variables["fallback-archive"].as().toStdStringVector(); + StringsVector archives = variables["fallback-archive"].as(); for (StringsVector::const_iterator it = archives.begin(); it != archives.end(); ++it) { engine.addArchive(*it); } - StringsVector content = variables["content"].as().toStdStringVector(); + StringsVector content = variables["content"].as(); if (content.empty()) { Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; @@ -214,18 +214,18 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.addContentFile(file); } - StringsVector groundcover = variables["groundcover"].as().toStdStringVector(); + StringsVector groundcover = variables["groundcover"].as(); for (auto& file : groundcover) { engine.addGroundcoverFile(file); } - StringsVector luaScriptLists = variables["lua-scripts"].as().toStdStringVector(); + StringsVector luaScriptLists = variables["lua-scripts"].as(); for (const auto& file : luaScriptLists) engine.addLuaScriptListFile(file); // startup-settings - engine.setCell(variables["start"].as().toStdString()); + engine.setCell(variables["start"].as()); engine.setSkipMenu (variables["skip-menu"].as(), variables["new-game"].as()); if (!variables["skip-menu"].as() && variables["new-game"].as()) Log(Debug::Warning) << "Warning: new-game used without skip-menu -> ignoring it"; @@ -234,11 +234,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setCompileAll(variables["script-all"].as()); engine.setCompileAllDialogue(variables["script-all-dialogue"].as()); engine.setScriptConsoleMode (variables["script-console"].as()); - engine.setStartupScript (variables["script-run"].as().toStdString()); + engine.setStartupScript (variables["script-run"].as()); engine.setWarningsMode (variables["script-warn"].as()); - engine.setScriptBlacklist (variables["script-blacklist"].as().toStdStringVector()); + engine.setScriptBlacklist (variables["script-blacklist"].as()); engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); - engine.setSaveGameFile (variables["load-savegame"].as().mPath.string()); + engine.setSaveGameFile (variables["load-savegame"].as().string()); // other settings Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index f3b2bb3dcb..13a9ebee9c 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -59,10 +59,10 @@ struct ContentFileTest : public ::testing::Test boost::program_options::options_description desc("Allowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing()) - ("content", boost::program_options::value()->default_value(Files::EscapeStringVector(), "") + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) + ("content", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") - ("data-local", boost::program_options::value()->default_value(Files::EscapePath(), "")); + ("data-local", boost::program_options::value()->default_value(Files::PathContainer::value_type(), "")); boost::program_options::notify(variables); @@ -70,10 +70,10 @@ struct ContentFileTest : public ::testing::Test Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = Files::EscapePath::toPathContainer(variables["data"].as()); + dataDirs = variables["data"].as(); } - Files::PathContainer::value_type local(variables["data-local"].as().mPath); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) { dataLocal.push_back(local); } @@ -86,7 +86,7 @@ struct ContentFileTest : public ::testing::Test Files::Collections collections (dataDirs, true); - std::vector contentFiles = variables["content"].as().toStdStringVector(); + std::vector contentFiles = variables["content"].as>(); for (auto & contentFile : contentFiles) mContentFiles.push_back(collections.getPath(contentFile)); } diff --git a/components/files/configfileparser.cpp b/components/files/configfileparser.cpp index fe98ea11d2..7fcc033157 100644 --- a/components/files/configfileparser.cpp +++ b/components/files/configfileparser.cpp @@ -158,8 +158,8 @@ namespace Files while (this->getline(s)) { // strip '#' comments and whitespace - if ((n = s.find('#')) != std::string::npos) - s = s.substr(0, n); + if (s.find('#') == s.find_first_not_of(" \t\r\n")) + continue; s = trim_ws(s); if (!s.empty()) { diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 7fd134cf0f..8aa6853237 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -108,7 +108,7 @@ void ConfigurationManager::mergeComposingVariables(boost::program_options::varia auto replace = second["replace"]; if (!replace.defaulted() && !replace.empty()) { - std::vector replaceVector = replace.as().toStdStringVector(); + std::vector replaceVector = replace.as>(); replacedVariables.insert(replaceVector.begin(), replaceVector.end()); } } @@ -137,19 +137,19 @@ void ConfigurationManager::mergeComposingVariables(boost::program_options::varia boost::any& firstValue = firstPosition->second.value(); const boost::any& secondValue = second[name].value(); - if (firstValue.type() == typeid(Files::EscapePathContainer)) + if (firstValue.type() == typeid(Files::PathContainer)) { - auto& firstPathContainer = boost::any_cast(firstValue); - const auto& secondPathContainer = boost::any_cast(secondValue); + auto& firstPathContainer = boost::any_cast(firstValue); + const auto& secondPathContainer = boost::any_cast(secondValue); firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); } - else if (firstValue.type() == typeid(Files::EscapeStringVector)) + else if (firstValue.type() == typeid(std::vector)) { - auto& firstVector = boost::any_cast(firstValue); - const auto& secondVector = boost::any_cast(secondValue); + auto& firstVector = boost::any_cast&>(firstValue); + const auto& secondVector = boost::any_cast&>(secondValue); - firstVector.mVector.insert(firstVector.mVector.end(), secondVector.mVector.begin(), secondVector.mVector.end()); + firstVector.insert(firstVector.end(), secondVector.begin(), secondVector.end()); } else if (firstValue.type() == typeid(Fallback::FallbackMap)) { @@ -234,11 +234,8 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, if (!mSilent) Log(Debug::Info) << "Loading config file: " << cfgFile.string(); - boost::filesystem::ifstream configFileStreamUnfiltered(cfgFile); - boost::iostreams::filtering_istream configFileStream; - configFileStream.push(escape_hash_filter()); - configFileStream.push(configFileStreamUnfiltered); - if (configFileStreamUnfiltered.is_open()) + boost::filesystem::ifstream configFileStream(cfgFile); + if (configFileStream.is_open()) { boost::program_options::store(Files::parse_config_file( configFileStream, description, true), variables); From b61140b8ba930ecfdf8d9523c62e28e257fb555f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 9 Oct 2021 09:14:22 +0000 Subject: [PATCH 1443/2859] optimises skeleton.cpp (#3158) With this PR we optimise a function that is called quite often when loading new cells. We remove avoidable dynamic_casts. We remove an unused pair.second element. We convert a map to an unordered_map because its ordering is irrelevant in this case. We avoid adding the root Skeleton node to the bones' node path. --- components/sceneutil/skeleton.cpp | 28 +++++++++++----------------- components/sceneutil/skeleton.hpp | 3 ++- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index 40f524e0a2..c465333fb4 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -1,6 +1,5 @@ #include "skeleton.hpp" -#include #include #include @@ -12,24 +11,24 @@ namespace SceneUtil class InitBoneCacheVisitor : public osg::NodeVisitor { public: - InitBoneCacheVisitor(std::map >& cache) + typedef std::vector TransformPath; + InitBoneCacheVisitor(std::unordered_map& cache) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mCache(cache) { } - void apply(osg::Transform &node) override + void apply(osg::MatrixTransform &node) override { - osg::MatrixTransform* bone = node.asMatrixTransform(); - if (!bone) - return; - - mCache[Misc::StringUtils::lowerCase(bone->getName())] = std::make_pair(getNodePath(), bone); - + mPath.push_back(&node); + mCache[Misc::StringUtils::lowerCase(node.getName())] = mPath; traverse(node); + mPath.pop_back(); } + private: - std::map >& mCache; + TransformPath mPath; + std::unordered_map& mCache; }; Skeleton::Skeleton() @@ -73,18 +72,13 @@ Bone* Skeleton::getBone(const std::string &name) mRootBone.reset(new Bone); } - const osg::NodePath& path = found->second.first; Bone* bone = mRootBone.get(); - for (osg::NodePath::const_iterator it = path.begin(); it != path.end(); ++it) + for (osg::MatrixTransform* matrixTransform : found->second) { - osg::MatrixTransform* matrixTransform = dynamic_cast(*it); - if (!matrixTransform) - continue; - Bone* child = nullptr; for (unsigned int i=0; imChildren.size(); ++i) { - if (bone->mChildren[i]->mNode == *it) + if (bone->mChildren[i]->mNode == matrixTransform) { child = bone->mChildren[i]; break; diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp index 22988dfd5f..10f4640249 100644 --- a/components/sceneutil/skeleton.hpp +++ b/components/sceneutil/skeleton.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace SceneUtil { @@ -72,7 +73,7 @@ namespace SceneUtil // As far as the scene graph goes we support multiple root bones. std::unique_ptr mRootBone; - typedef std::map > BoneCache; + typedef std::unordered_map > BoneCache; BoneCache mBoneCache; bool mBoneCacheInit; From 3d5950f790dd5e6baf59e00f1cf6fe3143acde70 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Oct 2021 12:27:17 +0200 Subject: [PATCH 1444/2859] Remove active effects from spells that no longer exist --- apps/openmw/mwmechanics/activespells.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 6ad6ef369e..fc35e40b74 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,5 +1,7 @@ #include "activespells.hpp" +#include + #include #include @@ -234,7 +236,17 @@ namespace MWMechanics bool remove = false; if(spellIt->mType == ESM::ActiveSpells::Type_Ability || spellIt->mType == ESM::ActiveSpells::Type_Permanent) - remove = !spells.hasSpell(spellIt->mId); + { + try + { + remove = !spells.hasSpell(spellIt->mId); + } + catch(const std::runtime_error& e) + { + remove = true; + Log(Debug::Error) << "Removing active effect: " << e.what(); + } + } else if(spellIt->mType == ESM::ActiveSpells::Type_Enchantment) { const auto& store = ptr.getClass().getInventoryStore(ptr); From 88ac77df1f4aa1241ea9fe5d0c4ab01de91d0159 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 6 Oct 2021 17:36:05 +0200 Subject: [PATCH 1445/2859] Fix writing to file for RecastMesh Add missing scaling and y, z coordinates swap. --- .../detournavigator/asyncnavmeshupdater.cpp | 2 +- components/detournavigator/debug.cpp | 22 ++++++++----------- components/detournavigator/debug.hpp | 3 ++- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 5c88d0cfa2..966e07bc5a 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -409,7 +409,7 @@ namespace DetourNavigator } if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) - + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision); + + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings); if (mSettings.get().mEnableWriteNavMeshToFile) if (const auto shared = job.mNavMeshCacheItem.lock()) writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 4cb5b248b0..07832d748f 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -1,6 +1,8 @@ #include "debug.hpp" #include "exceptions.hpp" #include "recastmesh.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" #include @@ -9,7 +11,7 @@ namespace DetourNavigator { - void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision) + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const Settings& settings) { const auto path = pathPrefix + "recastmesh" + revision + ".obj"; boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out); @@ -17,20 +19,14 @@ namespace DetourNavigator throw NavigatorException("Open file failed: " + path); file.exceptions(std::ios::failbit | std::ios::badbit); file.precision(std::numeric_limits::max_exponent10); - std::size_t count = 0; - for (float v : recastMesh.getMesh().getVertices()) + std::vector vertices = recastMesh.getMesh().getVertices(); + for (std::size_t i = 0; i < vertices.size(); i += 3) { - if (count % 3 == 0) - { - if (count != 0) - file << '\n'; - file << 'v'; - } - file << ' ' << v; - ++count; + file << "v " << toNavMeshCoordinates(settings, vertices[i]) << ' ' + << toNavMeshCoordinates(settings, vertices[i + 2]) << ' ' + << toNavMeshCoordinates(settings, vertices[i + 1]) << '\n'; } - file << '\n'; - count = 0; + std::size_t count = 0; for (int v : recastMesh.getMesh().getIndices()) { if (count % 3 == 0) diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index 2128f96be4..d86d923a41 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -48,8 +48,9 @@ namespace DetourNavigator } class RecastMesh; + struct Settings; - void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision); + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const Settings& settings); void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision); } From daff7aba012dd91f8db1496eb304bbbeabf61002 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Oct 2021 17:44:25 +0200 Subject: [PATCH 1446/2859] Use different colors for walkable and non-walkable triangles --- components/sceneutil/recastmesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index 97efe010df..18cec022ff 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -64,8 +64,8 @@ namespace SceneUtil const auto normals = calculateNormals(vertices, indices); const auto texScale = 1.0f / (settings.mCellSize * 10.0f); - duDebugDrawTriMesh(&debugDraw, vertices.data(), static_cast(vertices.size() / 3), - indices.data(), normals.data(), static_cast(indices.size() / 3), nullptr, texScale); + duDebugDrawTriMeshSlope(&debugDraw, vertices.data(), static_cast(vertices.size() / 3), + indices.data(), normals.data(), static_cast(indices.size() / 3), settings.mMaxSlope, texScale); osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); From 0c8a811ad5f4bf382155595fced2133612e6f009 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Oct 2021 18:36:37 +0200 Subject: [PATCH 1447/2859] Render only cached recast mesh To avoid waiting while recast mesh is generating. Remove redundant continue. --- apps/openmw/mwrender/recastmesh.cpp | 1 - .../cachedrecastmeshmanager.cpp | 35 ++++++++++++------- .../cachedrecastmeshmanager.hpp | 5 +++ components/detournavigator/navigator.hpp | 2 +- components/detournavigator/navigatorimpl.cpp | 2 +- components/detournavigator/navigatorimpl.hpp | 2 +- components/detournavigator/navigatorstub.hpp | 2 +- components/detournavigator/navmeshmanager.cpp | 7 ++-- components/detournavigator/navmeshmanager.hpp | 2 +- .../tilecachedrecastmeshmanager.cpp | 30 ++++++++++------ .../tilecachedrecastmeshmanager.hpp | 4 +++ 11 files changed, 59 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwrender/recastmesh.cpp b/apps/openmw/mwrender/recastmesh.cpp index 5afa78cd93..91035907e9 100644 --- a/apps/openmw/mwrender/recastmesh.cpp +++ b/apps/openmw/mwrender/recastmesh.cpp @@ -60,7 +60,6 @@ namespace MWRender it->second.mValue = group; it->second.mGeneration = tile->second->getGeneration(); it->second.mRevision = tile->second->getRevision(); - continue; } ++it; diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 19b87aa820..d82310dc38 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -13,7 +13,7 @@ namespace DetourNavigator { if (!mImpl.addObject(id, shape, transform, areaType)) return false; - mCached.lock()->reset(); + mOutdatedCache = true; return true; } @@ -21,7 +21,7 @@ namespace DetourNavigator { if (!mImpl.updateObject(id, transform, areaType)) return false; - mCached.lock()->reset(); + mOutdatedCache = true; return true; } @@ -29,7 +29,7 @@ namespace DetourNavigator { auto object = mImpl.removeObject(id); if (object) - mCached.lock()->reset(); + mOutdatedCache = true; return object; } @@ -38,7 +38,7 @@ namespace DetourNavigator { if (!mImpl.addWater(cellPosition, cellSize, shift)) return false; - mCached.lock()->reset(); + mOutdatedCache = true; return true; } @@ -46,7 +46,7 @@ namespace DetourNavigator { const auto water = mImpl.removeWater(cellPosition); if (water) - mCached.lock()->reset(); + mOutdatedCache = true; return water; } @@ -55,7 +55,7 @@ namespace DetourNavigator { if (!mImpl.addHeightfield(cellPosition, cellSize, shift, shape)) return false; - mCached.lock()->reset(); + mOutdatedCache = true; return true; } @@ -63,18 +63,27 @@ namespace DetourNavigator { const auto cell = mImpl.removeHeightfield(cellPosition); if (cell) - mCached.lock()->reset(); + mOutdatedCache = true; return cell; } std::shared_ptr CachedRecastMeshManager::getMesh() { - std::shared_ptr cached = *mCached.lock(); - if (cached != nullptr) - return cached; - cached = mImpl.getMesh(); - *mCached.lock() = cached; - return cached; + bool outdated = true; + if (!mOutdatedCache.compare_exchange_strong(outdated, false)) + { + std::shared_ptr cached = getCachedMesh(); + if (cached != nullptr) + return cached; + } + std::shared_ptr mesh = mImpl.getMesh(); + *mCached.lock() = mesh; + return mesh; + } + + std::shared_ptr CachedRecastMeshManager::getCachedMesh() const + { + return *mCached.lockConst(); } bool CachedRecastMeshManager::isEmpty() const diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index b506f807fa..b1e2570b0a 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -7,6 +7,8 @@ #include +#include + namespace DetourNavigator { class CachedRecastMeshManager @@ -32,6 +34,8 @@ namespace DetourNavigator std::shared_ptr getMesh(); + std::shared_ptr getCachedMesh() const; + bool isEmpty() const; void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion); @@ -41,6 +45,7 @@ namespace DetourNavigator private: RecastMeshManager mImpl; Misc::ScopeGuarded> mCached; + std::atomic_bool mOutdatedCache {true}; }; } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index d2e77892b9..265d69b6e1 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -236,7 +236,7 @@ namespace DetourNavigator std::optional raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags) const; - virtual RecastMeshTiles getRecastMeshTiles() = 0; + virtual RecastMeshTiles getRecastMeshTiles() const = 0; virtual float getMaxNavmeshAreaRealRadius() const = 0; }; diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 750901c73e..f29ae1bb95 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -187,7 +187,7 @@ namespace DetourNavigator mNavMeshManager.reportStats(frameNumber, stats); } - RecastMeshTiles NavigatorImpl::getRecastMeshTiles() + RecastMeshTiles NavigatorImpl::getRecastMeshTiles() const { return mNavMeshManager.getRecastMeshTiles(); } diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index cda6158958..8ed16b9a5b 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -60,7 +60,7 @@ namespace DetourNavigator void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; - RecastMeshTiles getRecastMeshTiles() override; + RecastMeshTiles getRecastMeshTiles() const override; float getMaxNavmeshAreaRealRadius() const override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 40de90f256..758519769b 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -94,7 +94,7 @@ namespace DetourNavigator void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {} - RecastMeshTiles getRecastMeshTiles() override + RecastMeshTiles getRecastMeshTiles() const override { return {}; } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index c378230845..362c6938c0 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -232,14 +232,15 @@ namespace DetourNavigator mAsyncNavMeshUpdater.reportStats(frameNumber, stats); } - RecastMeshTiles NavMeshManager::getRecastMeshTiles() + RecastMeshTiles NavMeshManager::getRecastMeshTiles() const { std::vector tiles; mRecastMeshManager.forEachTile( [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); RecastMeshTiles result; - std::transform(tiles.begin(), tiles.end(), std::inserter(result, result.end()), - [this] (const TilePosition& tile) { return std::make_pair(tile, mRecastMeshManager.getMesh(tile)); }); + for (const TilePosition& tile : tiles) + if (auto mesh = mRecastMeshManager.getCachedMesh(tile)) + result.emplace(tile, std::move(mesh)); return result; } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 76c9b1e0b9..b1496dc817 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -59,7 +59,7 @@ namespace DetourNavigator void reportStats(unsigned int frameNumber, osg::Stats& stats) const; - RecastMeshTiles getRecastMeshTiles(); + RecastMeshTiles getRecastMeshTiles() const; private: const Settings& mSettings; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 63d7e13f6b..d6e3e55005 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -192,17 +192,16 @@ namespace DetourNavigator std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) const { - const auto manager = [&] () -> std::shared_ptr - { - const auto tiles = mTiles.lockConst(); - const auto it = tiles->find(tilePosition); - if (it == tiles->end()) - return nullptr; - return it->second; - } (); - if (manager == nullptr) - return nullptr; - return manager->getMesh(); + if (const auto manager = getManager(tilePosition)) + return manager->getMesh(); + return nullptr; + } + + std::shared_ptr TileCachedRecastMeshManager::getCachedMesh(const TilePosition& tilePosition) const + { + if (const auto manager = getManager(tilePosition)) + return manager->getCachedMesh(); + return nullptr; } std::size_t TileCachedRecastMeshManager::getRevision() const @@ -256,4 +255,13 @@ namespace DetourNavigator } return tileResult; } + + std::shared_ptr TileCachedRecastMeshManager::getManager(const TilePosition& tilePosition) const + { + const auto tiles = mTiles.lockConst(); + const auto it = tiles->find(tilePosition); + if (it == tiles->end()) + return nullptr; + return it->second; + } } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f6bc40d668..7b5480e006 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -88,6 +88,8 @@ namespace DetourNavigator std::shared_ptr getMesh(const TilePosition& tilePosition) const; + std::shared_ptr getCachedMesh(const TilePosition& tilePosition) const; + template void forEachTile(Function&& function) const { @@ -118,6 +120,8 @@ namespace DetourNavigator std::optional removeTile(const ObjectId id, const TilePosition& tilePosition, TilesMap& tiles); + + inline std::shared_ptr getManager(const TilePosition& tilePosition) const; }; } From 031871cd480b729498d9574bf7636428620169bc Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 10 Oct 2021 16:09:15 +0000 Subject: [PATCH 1448/2859] speeds up optimizer (#3162) We can expect marginally improved loading times with this PR. Drawable, Transform and Node counts in stats panels are expected to remain unchanged - this PR does not add new scene graph optimisations, it just increases the speed with which we apply existing ones. 1. We add explicit `NodeVisitor::apply` overrides for commonly encountered node types to avoid additional virtual function calls per node associated with the default `apply` implementation. 2. We skip pushing `StateSet`s when `_mergeAlphaBlending` is enabled or the `StateSet` contains no relevant state. 3. We add a specialised variant of `CollectLowestTransformsVisitor::addTransform` accepting `MatrixTransform` to avoid matrix copies and multiplications. --- components/sceneutil/optimizer.cpp | 77 ++++++++++++++++++++---------- components/sceneutil/optimizer.hpp | 12 +++-- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 2cfabbfe19..b9d2f5bac7 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -171,7 +171,7 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor setTraversalMode(osg::NodeVisitor::TRAVERSE_PARENTS); } - void apply(osg::Node& node) override + void apply(osg::Group& node) override { if (node.getNumParents()) { @@ -180,7 +180,7 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor else { // for all current objects mark a nullptr transform for them. - registerWithCurrentObjects(0); + registerWithCurrentObjects(static_cast(nullptr)); } } @@ -198,15 +198,19 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor // for all current objects associated this transform with them. registerWithCurrentObjects(&transform); } + void apply(osg::MatrixTransform& transform) override + { + // for all current objects associated this transform with them. + registerWithCurrentObjects(&transform); + } - void apply(osg::Geode& geode) override + void apply(osg::Node& node) override { - traverse(geode); + traverse(node); } - void apply(osg::Billboard& geode) override + void apply(osg::Geometry& geode) override { - traverse(geode); } void collectDataFor(osg::Node* node) @@ -293,7 +297,19 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor ObjectStruct():_canBeApplied(true),_moreThanOneMatrixRequired(false) {} - void add(osg::Transform* transform, bool canOptimize) + inline const osg::Matrix& getMatrix(osg::MatrixTransform* transform) + { + return transform->getMatrix(); + } + osg::Matrix getMatrix(osg::Transform* transform) + { + osg::Matrix matrix; + transform->computeLocalToWorldMatrix(matrix, 0); + return matrix; + } + + template + void add(T* transform, bool canOptimize) { if (transform) { @@ -301,12 +317,10 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor else if (transform->getReferenceFrame()!=osg::Transform::RELATIVE_RF) _moreThanOneMatrixRequired=true; else { - if (_transformSet.empty()) transform->computeLocalToWorldMatrix(_firstMatrix,0); + if (_transformSet.empty()) _firstMatrix = getMatrix(transform); else { - osg::Matrix matrix; - transform->computeLocalToWorldMatrix(matrix,0); - if (_firstMatrix!=matrix) _moreThanOneMatrixRequired=true; + if (_firstMatrix!=getMatrix(transform)) _moreThanOneMatrixRequired=true; } } } @@ -327,8 +341,8 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor TransformSet _transformSet; }; - - void registerWithCurrentObjects(osg::Transform* transform) + template + void registerWithCurrentObjects(T* transform) { for(ObjectList::iterator itr=_currentObjectList.begin(); itr!=_currentObjectList.end(); @@ -633,19 +647,23 @@ osg::Array* cloneArray(osg::Array* array, osg::VertexBufferObject*& vbo, const o return array; } -void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable) +void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Geometry& geometry) { - osg::Geometry *geometry = drawable.asGeometry(); - if((geometry) && (isOperationPermissibleForObject(&drawable))) + if(isOperationPermissibleForObject(&geometry)) { osg::VertexBufferObject* vbo = nullptr; - if(geometry->getVertexArray() && geometry->getVertexArray()->referenceCount() > 1) - geometry->setVertexArray(cloneArray(geometry->getVertexArray(), vbo, geometry)); - if(geometry->getNormalArray() && geometry->getNormalArray()->referenceCount() > 1) - geometry->setNormalArray(cloneArray(geometry->getNormalArray(), vbo, geometry)); - if(geometry->getTexCoordArray(7) && geometry->getTexCoordArray(7)->referenceCount() > 1) // tangents - geometry->setTexCoordArray(7, cloneArray(geometry->getTexCoordArray(7), vbo, geometry)); + if(geometry.getVertexArray() && geometry.getVertexArray()->referenceCount() > 1) + geometry.setVertexArray(cloneArray(geometry.getVertexArray(), vbo, &geometry)); + if(geometry.getNormalArray() && geometry.getNormalArray()->referenceCount() > 1) + geometry.setNormalArray(cloneArray(geometry.getNormalArray(), vbo, &geometry)); + if(geometry.getTexCoordArray(7) && geometry.getTexCoordArray(7)->referenceCount() > 1) // tangents + geometry.setTexCoordArray(7, cloneArray(geometry.getTexCoordArray(7), vbo, &geometry)); } + _drawableSet.insert(&geometry); +} + +void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable) +{ _drawableSet.insert(&drawable); } @@ -673,6 +691,11 @@ void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Transform& transform) _transformStack.pop_back(); } +void Optimizer::FlattenStaticTransformsVisitor::apply(osg::MatrixTransform& transform) +{ + apply(static_cast(transform)); +} + bool Optimizer::FlattenStaticTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRemove) { CollectLowestTransformsVisitor cltv(_optimizer); @@ -1112,10 +1135,13 @@ bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2) } -void Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) +bool Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) { + if (_mergeAlphaBlending || !stateSet || stateSet->getRenderBinMode() & osg::StateSet::INHERIT_RENDERBIN_DETAILS) + return false; _stateSetStack.push_back(stateSet); checkAlphaBlendingActive(); + return true; } void Optimizer::MergeGeometryVisitor::popStateSet() @@ -1145,15 +1171,14 @@ void Optimizer::MergeGeometryVisitor::checkAlphaBlendingActive() void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) { - if (group.getStateSet()) - pushStateSet(group.getStateSet()); + bool pushed = pushStateSet(group.getStateSet()); if (!_alphaBlendingActive || _mergeAlphaBlending) mergeGroup(group); traverse(group); - if (group.getStateSet()) + if (pushed) popStateSet(); } diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 7b32bafee2..5191bcf322 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -285,9 +285,11 @@ class Optimizer BaseOptimizerVisitor(optimizer, FLATTEN_STATIC_TRANSFORMS) {} void apply(osg::Node& geode) override; + void apply(osg::Geometry& drawable) override; void apply(osg::Drawable& drawable) override; void apply(osg::Billboard& geode) override; - void apply(osg::Transform& transform) override; + void apply(osg::Transform& transform) override final; + void apply(osg::MatrixTransform& transform) override; bool removeTransforms(osg::Node* nodeWeCannotRemove); @@ -316,6 +318,7 @@ class Optimizer BaseOptimizerVisitor(optimizer, FLATTEN_STATIC_TRANSFORMS) {} void apply(osg::MatrixTransform& transform) override; + void apply(osg::Geometry&) override { } bool removeTransforms(osg::Node* nodeWeCannotRemove); @@ -338,6 +341,7 @@ class Optimizer BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {} void apply(osg::Group& group) override; + void apply(osg::Geometry&) override { } void removeEmptyNodes(); @@ -358,6 +362,7 @@ class Optimizer void apply(osg::Transform& transform) override; void apply(osg::LOD& lod) override; void apply(osg::Switch& switchNode) override; + void apply(osg::Geometry&) override { } bool isOperationPermissible(osg::Node& node); @@ -376,6 +381,7 @@ class Optimizer bool isOperationPermissible(osg::Group& node); + void apply(osg::Geometry&) override { } void apply(osg::Group& group) override; void apply(osg::LOD& lod) override; void apply(osg::Switch& switchNode) override; @@ -409,10 +415,10 @@ class Optimizer return _targetMaximumNumberOfVertices; } - void pushStateSet(osg::StateSet* stateSet); + bool pushStateSet(osg::StateSet* stateSet); void popStateSet(); void checkAlphaBlendingActive(); - + void apply(osg::Geometry&) override { } void apply(osg::Group& group) override; void apply(osg::Billboard&) override { /* don't do anything*/ } From af759683b863b8513704b67ed2c782da5e940582 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 10 Oct 2021 16:15:40 +0000 Subject: [PATCH 1449/2859] enables std::move in keywordsearch.hpp (#3163) For some reason we have commented std::move keywords in keywordsearch.hpp while they are quite beneficial. openmw_test_suite for keywordsearch.hpp takes 30% less time with these changes. --- apps/openmw/mwdialogue/keywordsearch.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 0c14ea8f8d..39599457ef 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -30,7 +30,7 @@ public: { if (keyword.empty()) return; - seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), 0, mRoot); + seed_impl (std::move(keyword), std::move (value), 0, mRoot); } void clear () @@ -39,7 +39,7 @@ public: mRoot.mKeyword.clear (); } - bool containsKeyword (string_t keyword, value_t& value) + bool containsKeyword (const string_t& keyword, value_t& value) { typename Entry::childen_t::iterator current; typename Entry::childen_t::iterator next; @@ -209,8 +209,8 @@ private: if (j == entry.mChildren.end ()) { - entry.mChildren [ch].mValue = /*std::move*/ (value); - entry.mChildren [ch].mKeyword = /*std::move*/ (keyword); + entry.mChildren [ch].mValue = std::move (value); + entry.mChildren [ch].mKeyword = std::move (keyword); } else { @@ -219,22 +219,22 @@ private: if (keyword == j->second.mKeyword) throw std::runtime_error ("duplicate keyword inserted"); - value_t pushValue = /*std::move*/ (j->second.mValue); - string_t pushKeyword = /*std::move*/ (j->second.mKeyword); + value_t pushValue = j->second.mValue; + string_t pushKeyword = j->second.mKeyword; if (depth >= pushKeyword.size ()) throw std::runtime_error ("unexpected"); if (depth+1 < pushKeyword.size()) { - seed_impl (/*std::move*/ (pushKeyword), /*std::move*/ (pushValue), depth+1, j->second); + seed_impl (std::move (pushKeyword), std::move (pushValue), depth+1, j->second); j->second.mKeyword.clear (); } } if (depth+1 == keyword.size()) j->second.mKeyword = value; else // depth+1 < keyword.size() - seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), depth+1, j->second); + seed_impl (std::move (keyword), std::move (value), depth+1, j->second); } } From ff9aabafd15008b2514aad23696888d1147afa64 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 10 Oct 2021 14:48:00 +0200 Subject: [PATCH 1450/2859] Change max walkable slope angle to 46 degrees This is the value used by vanilla Morrowind engine. At this angle player character starts sliding down the slope. This fixes navigation and movement issue in Mournhold, Godsreach. --- apps/openmw/mwphysics/constants.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index eaeb308d58..2d7cd53d38 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -12,7 +12,7 @@ namespace MWPhysics static constexpr bool sDoExtraStairHacks = true; static constexpr float sGroundOffset = 1.0f; - static constexpr float sMaxSlope = 49.0f; + static constexpr float sMaxSlope = 46.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static constexpr int sMaxIterations = 8; From c2d836c6c4b06cf1ba638d5a73f9ea9dfafa163a Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 10 Oct 2021 16:37:34 +0000 Subject: [PATCH 1451/2859] optimises riggeometry.cpp (#3165) We skip this during node path iterations. this is not a node we are interested in. We avoid allocating a new mGeomToSkelMatrix per frame and avoid a ref_ptr associated with its update. We speed up a search for the Skeleton node by adding a continue; condition prior to an expensive dynamic_cast. --- components/sceneutil/riggeometry.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index b9201fdf66..2c1efb9f36 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "skeleton.hpp" #include "util.hpp" @@ -114,9 +115,11 @@ osg::ref_ptr RigGeometry::getSourceGeometry() const bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) { const osg::NodePath& path = nv->getNodePath(); - for (osg::NodePath::const_reverse_iterator it = path.rbegin(); it != path.rend(); ++it) + for (osg::NodePath::const_reverse_iterator it = path.rbegin()+1; it != path.rend(); ++it) { osg::Node* node = *it; + if (node->asTransform()) + continue; if (Skeleton* skel = dynamic_cast(node)) { mSkeleton = skel; @@ -310,8 +313,10 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath) { bool foundSkel = false; - osg::ref_ptr geomToSkelMatrix; - for (osg::NodePath::const_iterator it = nodePath.begin(); it != nodePath.end(); ++it) + osg::RefMatrix* geomToSkelMatrix = mGeomToSkelMatrix; + if (geomToSkelMatrix) + geomToSkelMatrix->makeIdentity(); + for (osg::NodePath::const_iterator it = nodePath.begin(); it != nodePath.end()-1; ++it) { osg::Node* node = *it; if (!foundSkel) @@ -323,14 +328,15 @@ void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath) { if (osg::Transform* trans = node->asTransform()) { + osg::MatrixTransform* matrixTrans = trans->asMatrixTransform(); + if (matrixTrans && matrixTrans->getMatrix().isIdentity()) + continue; if (!geomToSkelMatrix) - geomToSkelMatrix = new osg::RefMatrix; + geomToSkelMatrix = mGeomToSkelMatrix = new osg::RefMatrix; trans->computeWorldToLocalMatrix(*geomToSkelMatrix, nullptr); } } } - if (geomToSkelMatrix && !geomToSkelMatrix->isIdentity()) - mGeomToSkelMatrix = geomToSkelMatrix; } void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) From 7fb5b773dde512986d964138eada0e606f87555b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 11 Oct 2021 00:01:58 +0100 Subject: [PATCH 1452/2859] Remove obsolete tests The old behaviour was basically a bug and @ doesn't mean anything special now --- apps/openmw_test_suite/openmw/options.cpp | 27 +---------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index 9de7613d19..0f9ad39053 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -22,16 +22,11 @@ namespace { std::vector result = std::move(base); for (int i = 1; i <= std::numeric_limits::max(); ++i) - if (i != '&' && i != '"' && i != ' ' && i != '@' && i != '\n') + if (i != '&' && i != '"' && i != ' ' && i != '\n') result.push_back(std::string(1, i)); return result; } - constexpr std::array supportedAtSignEscapings { - std::pair {'a', '@'}, - std::pair {'h', '#'}, - }; - MATCHER_P(IsPath, v, "") { return arg.string() == v; } template @@ -101,7 +96,6 @@ namespace const std::array arguments {"openmw", "--load-savegame", "my@save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); -// EXPECT_EQ(variables["load-savegame"].as().string(), "my?ave.omwsave"); EXPECT_EQ(variables["load-savegame"].as().string(), "my@save.omwsave"); } @@ -205,25 +199,6 @@ namespace ValuesIn(generateSupportedCharacters({u8"👍", u8"Ъ", u8"Ǽ", "\n"})) ); - struct OpenMWOptionsFromArgumentsEscapings : TestWithParam> {}; - -/* TEST_P(OpenMWOptionsFromArgumentsEscapings, should_support_escaping_with_at_sign_in_load_savegame_path) - { - bpo::options_description description = makeOptionsDescription(); - const std::string path = "save_@" + std::string(1, GetParam().first) + ".omwsave"; - const std::array arguments {"openmw", "--load-savegame", path.c_str()}; - bpo::variables_map variables; - parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), - "save_" + std::string(1, GetParam().second) + ".omwsave"); - } - - INSTANTIATE_TEST_SUITE_P( - SupportedEscapingsWithAtSign, - OpenMWOptionsFromArgumentsEscapings, - ValuesIn(supportedAtSignEscapings) - );*/ - TEST(OpenMWOptionsFromConfig, should_support_single_word_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); From 18869c2c7505d5fe80b38351be8b0edf74f9ed6e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 11 Oct 2021 00:02:33 +0100 Subject: [PATCH 1453/2859] Add some extra tests The behaviour here is unchanged, but was previously untested. --- apps/openmw_test_suite/openmw/options.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index 0f9ad39053..b087fd03fb 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -272,6 +272,24 @@ namespace EXPECT_EQ(variables["load-savegame"].as().string(), ""); } + TEST(OpenMWOptionsFromConfig, should_ignore_whitespace_prefixed_commented_option) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(" \t#load-savegame=save.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().string(), ""); + } + + TEST(OpenMWOptionsFromConfig, should_support_whitespace_around_option) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(" load-savegame = save.omwsave "); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + } + TEST(OpenMWOptionsFromConfig, should_throw_on_multiple_load_savegame) { bpo::options_description description = makeOptionsDescription(); From cbcdd705ee2b169259d0a15b25ba2da02cca8567 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 11 Oct 2021 08:09:01 +0000 Subject: [PATCH 1454/2859] minor objectpaging.cpp scene graph optimisations (#3155) We now use PositionAttitudeTransform for unmerged nodes because I have been informed PositionAttitudeTransform's scene graph performance is measurably faster than MatrixTransform's. We still need to use MatrixTransform for merged nodes because the Optimizer has limited support for non-MatrixTransform nodes. These MatrixTransforms will be removed in the merging process anyway. --- apps/openmw/mwrender/objectpaging.cpp | 32 +++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 88c3d4ba02..5c22c3fc8d 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -590,14 +591,31 @@ namespace MWRender if (!activeGrid && minSizeMerged != minSize && cnode->getBound().radius2() * cref->mScale*cref->mScale < (viewPoint-pos).length2()*minSizeMerged*minSizeMerged) continue; - osg::Matrixf matrix; - matrix.preMultTranslate(pos - worldCenter); - matrix.preMultRotate( osg::Quat(ref.mPos.rot[2], osg::Vec3f(0,0,-1)) * + osg::Vec3f nodePos = pos - worldCenter; + osg::Quat nodeAttitude = osg::Quat(ref.mPos.rot[2], osg::Vec3f(0,0,-1)) * osg::Quat(ref.mPos.rot[1], osg::Vec3f(0,-1,0)) * - osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1,0,0)) ); - matrix.preMultScale(osg::Vec3f(ref.mScale, ref.mScale, ref.mScale)); - osg::ref_ptr trans = new osg::MatrixTransform(matrix); - trans->setDataVariance(osg::Object::STATIC); + osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1,0,0)); + osg::Vec3f nodeScale = osg::Vec3f(ref.mScale, ref.mScale, ref.mScale); + + osg::ref_ptr trans; + if (merge) + { + // Optimizer currently supports only MatrixTransforms. + osg::Matrixf matrix; + matrix.preMultTranslate(nodePos); + matrix.preMultRotate(nodeAttitude); + matrix.preMultScale(nodeScale); + trans = new osg::MatrixTransform(matrix); + trans->setDataVariance(osg::Object::STATIC); + } + else + { + trans = new SceneUtil::PositionAttitudeTransform; + SceneUtil::PositionAttitudeTransform* pat = static_cast(trans.get()); + pat->setPosition(nodePos); + pat->setScale(nodeScale); + pat->setAttitude(nodeAttitude); + } copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES); copyop.mOptimizeBillboards = (size > 1/4.f); From 7c50f823d83460a605008877eed0be74bdc2a268 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 11 Oct 2021 08:09:48 +0000 Subject: [PATCH 1455/2859] devirtualises BSAFile (#3161) Currently, Open MW's basic file access speed is limited by a peculiar layer of virtualisation in BSAFile's interface. This PR removes such virtualisation by properly separating BSAFile from CompressedBSAFile in low level contexts. --- components/bsa/bsa_file.cpp | 5 --- components/bsa/bsa_file.hpp | 5 ++- components/bsa/compressedbsafile.hpp | 2 +- components/vfs/bsaarchive.cpp | 53 ++++++++++++++++++++++------ components/vfs/bsaarchive.hpp | 27 +++++++++++++- components/vfs/registerarchives.cpp | 6 +++- 6 files changed, 79 insertions(+), 19 deletions(-) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index ecbea5e7d9..d5610bddae 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -290,11 +290,6 @@ Files::IStreamPtr BSAFile::getFile(const char *file) return Files::openConstrainedFileStream (mFilename.c_str (), fs.offset, fs.fileSize); } -Files::IStreamPtr BSAFile::getFile(const FileStruct *file) -{ - return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize); -} - void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) { if (!mIsLoaded) diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index d30fc2feb0..9a76e1bd23 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -154,7 +154,10 @@ public: /** Open a file contained in the archive. * @note Thread safe. */ - virtual Files::IStreamPtr getFile(const FileStruct* file); + Files::IStreamPtr getFile(const FileStruct *file) + { + return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize); + } virtual void addFile(const std::string& filename, std::istream& file); diff --git a/components/bsa/compressedbsafile.hpp b/components/bsa/compressedbsafile.hpp index ac6e6fdf78..ffd88fe044 100644 --- a/components/bsa/compressedbsafile.hpp +++ b/components/bsa/compressedbsafile.hpp @@ -93,7 +93,7 @@ namespace Bsa void readHeader() override; Files::IStreamPtr getFile(const char* filePath) override; - Files::IStreamPtr getFile(const FileStruct* fileStruct) override; + Files::IStreamPtr getFile(const FileStruct* fileStruct); void addFile(const std::string& filename, std::istream& file) override; }; } diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index 90899ac612..86d21060c0 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -1,5 +1,5 @@ #include "bsaarchive.hpp" -#include + #include namespace VFS @@ -7,15 +7,7 @@ namespace VFS BsaArchive::BsaArchive(const std::string &filename) { - Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(filename); - - if (bsaVersion == Bsa::BSAVER_COMPRESSED) { - mFile = std::make_unique(Bsa::CompressedBSAFile()); - } - else { - mFile = std::make_unique(Bsa::BSAFile()); - } - + mFile = std::make_unique(Bsa::BSAFile()); mFile->open(filename); const Bsa::BSAFile::FileList &filelist = mFile->getList(); @@ -25,6 +17,10 @@ BsaArchive::BsaArchive(const std::string &filename) } } +BsaArchive::BsaArchive() +{ +} + BsaArchive::~BsaArchive() { } @@ -56,6 +52,31 @@ std::string BsaArchive::getDescription() const return std::string{"BSA: "} + mFile->getFilename(); } +CompressedBsaArchive::CompressedBsaArchive(const std::string &filename) + : BsaArchive() +{ + mFile = std::make_unique(Bsa::CompressedBSAFile()); + mFile->open(filename); + + const Bsa::BSAFile::FileList &filelist = mFile->getList(); + for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it) + { + mResources.emplace_back(&*it, mFile.get()); + mCompressedResources.emplace_back(&*it, static_cast(mFile.get())); + } +} + +void CompressedBsaArchive::listResources(std::map &out, char (*normalize_function)(char)) +{ + for (std::vector::iterator it = mCompressedResources.begin(); it != mCompressedResources.end(); ++it) + { + std::string ent = it->mInfo->name(); + std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); + + out[ent] = &*it; + } +} + // ------------------------------------------------------------------------------ BsaArchiveFile::BsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::BSAFile* bsa) @@ -70,4 +91,16 @@ Files::IStreamPtr BsaArchiveFile::open() return mFile->getFile(mInfo); } +CompressedBsaArchiveFile::CompressedBsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::CompressedBSAFile* bsa) + : BsaArchiveFile(info, bsa) + , mCompressedFile(bsa) +{ + +} + +Files::IStreamPtr CompressedBsaArchiveFile::open() +{ + return mCompressedFile->getFile(mInfo); +} + } diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index c979b5ce7e..1015f8d220 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -4,6 +4,7 @@ #include "archive.hpp" #include +#include namespace VFS { @@ -18,19 +19,43 @@ namespace VFS Bsa::BSAFile* mFile; }; + class CompressedBsaArchiveFile : public BsaArchiveFile + { + public: + CompressedBsaArchiveFile(const Bsa::BSAFile::FileStruct* info, Bsa::CompressedBSAFile* bsa); + + Files::IStreamPtr open() override; + Bsa::CompressedBSAFile* mCompressedFile; + }; + + class BsaArchive : public Archive { public: BsaArchive(const std::string& filename); + BsaArchive(); virtual ~BsaArchive(); void listResources(std::map& out, char (*normalize_function) (char)) override; bool contains(const std::string& file, char (*normalize_function) (char)) const override; std::string getDescription() const override; - private: + protected: std::unique_ptr mFile; std::vector mResources; }; + + class CompressedBsaArchive : public BsaArchive + { + public: + CompressedBsaArchive(const std::string& filename); + void listResources(std::map& out, char (*normalize_function) (char)) override; + virtual ~CompressedBsaArchive() {} + + private: + std::unique_ptr mCompressedFile; + std::vector mCompressedResources; + }; + } #endif diff --git a/components/vfs/registerarchives.cpp b/components/vfs/registerarchives.cpp index 80e639f350..4dc44fb25e 100644 --- a/components/vfs/registerarchives.cpp +++ b/components/vfs/registerarchives.cpp @@ -23,8 +23,12 @@ namespace VFS // Last BSA has the highest priority const std::string archivePath = collections.getPath(*archive).string(); Log(Debug::Info) << "Adding BSA archive " << archivePath; + Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(archivePath); - vfs->addArchive(new BsaArchive(archivePath)); + if (bsaVersion == Bsa::BSAVER_COMPRESSED) + vfs->addArchive(new CompressedBsaArchive(archivePath)); + else + vfs->addArchive(new BsaArchive(archivePath)); } else { From 98f8295765fe52e381a770375d5efcde2e173c5a Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 11 Oct 2021 09:27:50 +0000 Subject: [PATCH 1456/2859] allows to skip ComputeLightSpaceBounds traversal (#3152) Currently, we always traverse the scene graph an additional time with a ComputeLightSpaceBounds visitor during shadow casting. ComputeLightSpaceBounds is only useful when the shadow casting mask allows us to shrink the bounds of the rendered scene, so we guard its traversal with a check against getCastsShadowTraversalMask. In practice, this guard never works because we build the traversal mask inclusively. With this PR we limit the getCastsShadowTraversalMask check to relevant masks. This new check allows us to skip a superfluous ComputeLightSpaceBounds traversal with most settings. --- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 6 +++--- components/sceneutil/mwshadowtechnique.cpp | 12 +----------- components/sceneutil/mwshadowtechnique.hpp | 7 ++++--- components/sceneutil/shadow.cpp | 10 +++++++++- components/sceneutil/shadow.hpp | 18 +++++++++++++----- components/terrain/quadtreeworld.cpp | 22 +++++----------------- 7 files changed, 36 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 0560d1485a..70f0ff02bb 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -195,7 +195,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setNodeMask(Mask_RenderToTexture); // Disable small feature culling, it's not going to be reliable for this camera - osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING) & ~(osg::CullStack::SMALL_FEATURE_CULLING); + osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING); camera->setCullingMode(cullingMode); osg::ref_ptr stateset = new osg::StateSet; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 331ddeca7b..120f97e892 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -340,14 +340,14 @@ namespace MWRender shadowCastingTraversalMask |= Mask_Actor; if (Settings::Manager::getBool("player shadows", "Shadows")) shadowCastingTraversalMask |= Mask_Player; - if (Settings::Manager::getBool("terrain shadows", "Shadows")) - shadowCastingTraversalMask |= Mask_Terrain; int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; if (Settings::Manager::getBool("object shadows", "Shadows")) shadowCastingTraversalMask |= (Mask_Object|Mask_Static); + if (Settings::Manager::getBool("terrain shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Terrain; - mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); + mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, Mask_Terrain|Mask_Object|Mask_Static, mResourceSystem->getSceneManager()->getShaderManager())); Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index db7aef7cb1..89d872dbfe 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -357,8 +357,6 @@ MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) { setCullingMode(osg::CullSettings::VIEW_FRUSTUM_CULLING); - - setName("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds,AcceptedByComponentsTerrainQuadTreeWorld"); } void MWShadowTechnique::ComputeLightSpaceBounds::reset() @@ -393,14 +391,6 @@ void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Drawable& drawable) popCurrentMask(); } -void MWShadowTechnique::ComputeLightSpaceBounds::apply(Terrain::QuadTreeWorld & quadTreeWorld) -{ - // For now, just expand the bounds fully as terrain will fill them up and possible ways to detect which terrain definitely won't cast shadows aren't implemented. - - update(osg::Vec3(-1.0, -1.0, 0.0)); - update(osg::Vec3(1.0, 1.0, 0.0)); -} - void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Billboard&) { OSG_INFO << "Warning Billboards not yet supported" << std::endl; @@ -1122,7 +1112,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) // if we are using multiple shadow maps and CastShadowTraversalMask is being used // traverse the scene to compute the extents of the objects - if (/*numShadowMapsPerLight>1 &&*/ _shadowedScene->getCastsShadowTraversalMask()!=0xffffffff) + if (/*numShadowMapsPerLight>1 &&*/ (_shadowedScene->getCastsShadowTraversalMask() & _worldMask) == 0) { // osg::ElapsedTime timer; diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 229d444c2b..fd562184e0 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -31,7 +31,6 @@ #include #include -#include namespace SceneUtil { @@ -96,8 +95,6 @@ namespace SceneUtil { void apply(osg::Drawable& drawable) override; - void apply(Terrain::QuadTreeWorld& quadTreeWorld); - void apply(osg::Billboard&) override; void apply(osg::Projection&) override; @@ -237,6 +234,8 @@ namespace SceneUtil { virtual osg::StateSet* prepareStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const; + void setWorldMask(unsigned int worldMask) { _worldMask = worldMask; } + protected: virtual ~MWShadowTechnique(); @@ -270,6 +269,8 @@ namespace SceneUtil { float _shadowFadeStart = 0.0; + unsigned int _worldMask = ~0u; + class DebugHUD final : public osg::Referenced { public: diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 3244dc672a..8c3758e980 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -1,10 +1,13 @@ #include "shadow.hpp" #include +#include #include #include +#include "mwshadowtechnique.hpp" + namespace SceneUtil { using namespace osgShadow; @@ -94,7 +97,7 @@ namespace SceneUtil } } - ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), + ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), mShadowTechnique(new MWShadowTechnique), mOutdoorShadowCastingMask(outdoorShadowCastingMask), mIndoorShadowCastingMask(indoorShadowCastingMask) @@ -109,10 +112,15 @@ namespace SceneUtil setupShadowSettings(); mShadowTechnique->setupCastingShader(shaderManager); + mShadowTechnique->setWorldMask(worldMask); enableOutdoorMode(); } + ShadowManager::~ShadowManager() + { + } + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() { if (!mEnableShadows) diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index c823ecf860..4c34944f4a 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -1,15 +1,22 @@ #ifndef COMPONENTS_SCENEUTIL_SHADOW_H #define COMPONENTS_SCENEUTIL_SHADOW_H -#include -#include - #include -#include "mwshadowtechnique.hpp" +namespace osg +{ + class StateSet; + class Group; +} +namespace osgShadow +{ + class ShadowSettings; + class ShadowedScene; +} namespace SceneUtil { + class MWShadowTechnique; class ShadowManager { public: @@ -17,7 +24,8 @@ namespace SceneUtil static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); - ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager); + ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, Shader::ShaderManager &shaderManager); + ~ShadowManager(); void setupShadowSettings(); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 7e5cd001ba..c2d212f8a5 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -3,12 +3,12 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -257,10 +257,10 @@ public: { osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size, mStorage, mSceneManager, mNodeMask, 5.f, { 1, 0, 0, 0 }); - osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); - trans->setDataVariance(osg::Object::STATIC); - trans->addChild(chunkBorder); - return trans; + osg::ref_ptr pat = new SceneUtil::PositionAttitudeTransform; + pat->setPosition(-center*Constants::CellSizeInUnits); + pat->addChild(chunkBorder); + return pat; } unsigned int getNodeMask() { return mNodeMask; } private: @@ -440,19 +440,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) { bool isCullVisitor = nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR; if (!isCullVisitor && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR) - { - if (nv.getName().find("AcceptedByComponentsTerrainQuadTreeWorld") != std::string::npos) - { - if (nv.getName().find("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds") != std::string::npos) - { - SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds* clsb = static_cast(&nv); - clsb->apply(*this); - } - else - nv.apply(*mRootNode); - } return; - } osg::Object * viewer = isCullVisitor ? static_cast(&nv)->getCurrentCamera() : nullptr; bool needsUpdate = true; From ef906cbfa89b9c9dc680db035778f7a80f92f65b Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:46:21 +0000 Subject: [PATCH 1457/2859] improves MWClass mapping (#3166) Currently, we use a peculiar mapping of ESM classes by their std::type_info::name. This mapping is an undefined behaviour because std::type_info::name is strictly implementation defined. It could return a non-unique value on some platforms. With this PR we use the unsigned int sRecordId of the ESM class as a more efficient lookup type that does not build on undefined behaviour. We can expect marginally faster save-game loading with these changes as well. --- apps/openmw/mwclass/activator.cpp | 2 +- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/armor.cpp | 4 +- apps/openmw/mwclass/bodypart.cpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/container.cpp | 2 +- apps/openmw/mwclass/creature.cpp | 4 +- apps/openmw/mwclass/creaturelevlist.cpp | 2 +- apps/openmw/mwclass/door.cpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/itemlevlist.cpp | 2 +- apps/openmw/mwclass/light.cpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 16 ++-- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/static.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwdialogue/filter.cpp | 8 +- apps/openmw/mwgui/alchemywindow.cpp | 2 +- apps/openmw/mwgui/containeritemmodel.cpp | 2 +- apps/openmw/mwgui/dialogue.cpp | 6 +- apps/openmw/mwgui/inventorywindow.cpp | 42 ++++----- apps/openmw/mwgui/quickkeysmenu.cpp | 6 +- apps/openmw/mwgui/sortfilteritemmodel.cpp | 86 +++++++++---------- apps/openmw/mwgui/travelwindow.cpp | 2 +- apps/openmw/mwlua/object.cpp | 8 +- apps/openmw/mwlua/object.hpp | 4 +- apps/openmw/mwmechanics/actors.cpp | 10 +-- apps/openmw/mwmechanics/character.cpp | 16 ++-- apps/openmw/mwmechanics/combat.cpp | 2 +- apps/openmw/mwmechanics/enchanting.cpp | 10 +-- apps/openmw/mwmechanics/enchanting.hpp | 2 +- apps/openmw/mwmechanics/levelledlist.hpp | 6 +- .../mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwmechanics/objects.cpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- apps/openmw/mwmechanics/spelleffects.cpp | 6 +- apps/openmw/mwmechanics/spellpriority.cpp | 2 +- apps/openmw/mwmechanics/spellutil.cpp | 2 +- apps/openmw/mwmechanics/trading.cpp | 2 +- apps/openmw/mwmechanics/weaponpriority.cpp | 2 +- apps/openmw/mwmechanics/weapontype.cpp | 6 +- apps/openmw/mwrender/actoranimation.cpp | 38 ++++---- apps/openmw/mwrender/animation.cpp | 4 +- apps/openmw/mwrender/characterpreview.cpp | 4 +- apps/openmw/mwrender/creatureanimation.cpp | 8 +- apps/openmw/mwrender/npcanimation.cpp | 16 ++-- apps/openmw/mwrender/weaponanimation.cpp | 4 +- apps/openmw/mwscript/containerextensions.cpp | 16 ++-- apps/openmw/mwscript/miscextensions.cpp | 2 +- apps/openmw/mwworld/class.cpp | 15 ++-- apps/openmw/mwworld/class.hpp | 13 ++- apps/openmw/mwworld/containerstore.cpp | 29 +++---- apps/openmw/mwworld/inventorystore.cpp | 12 +-- apps/openmw/mwworld/livecellref.cpp | 2 +- apps/openmw/mwworld/livecellref.hpp | 12 +-- apps/openmw/mwworld/localscripts.cpp | 2 +- apps/openmw/mwworld/ptr.cpp | 8 +- apps/openmw/mwworld/ptr.hpp | 22 +++-- apps/openmw/mwworld/worldimp.cpp | 18 ++-- 64 files changed, 265 insertions(+), 258 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 6285bdbf7e..6c53ba72f3 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -89,7 +89,7 @@ namespace MWClass { std::shared_ptr instance (new Activator); - registerClass (typeid (ESM::Activator).name(), instance); + registerClass (ESM::Activator::sRecordId, instance); } bool Activator::hasToolTip (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e09e4804ce..6e9d8b3779 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -69,7 +69,7 @@ namespace MWClass { std::shared_ptr instance (new Apparatus); - registerClass (typeid (ESM::Apparatus).name(), instance); + registerClass (ESM::Apparatus::sRecordId, instance); } std::string Apparatus::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 817adbc1f5..bc2d788b5b 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -163,7 +163,7 @@ namespace MWClass { std::shared_ptr instance (new Armor); - registerClass (typeid (ESM::Armor).name(), instance); + registerClass (ESM::Armor::sRecordId, instance); } std::string Armor::getUpSoundId (const MWWorld::ConstPtr& ptr) const @@ -322,7 +322,7 @@ namespace MWClass if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft) { MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != invStore.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()) + if(weapon != invStore.end() && weapon->getType() == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); if (MWMechanics::getWeaponType(ref->mBase->mData.mType)->mFlags & ESM::WeaponType::TwoHanded) diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 7fe798e27d..06b460f75c 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -36,7 +36,7 @@ namespace MWClass { std::shared_ptr instance (new BodyPart); - registerClass (typeid (ESM::BodyPart).name(), instance); + registerClass (ESM::BodyPart::sRecordId, instance); } std::string BodyPart::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 51b9e39d7a..eef8e02808 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -85,7 +85,7 @@ namespace MWClass { std::shared_ptr instance (new Book); - registerClass (typeid (ESM::Book).name(), instance); + registerClass (ESM::Book::sRecordId, instance); } std::string Book::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 400cd97e41..ce8c79d02e 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -121,7 +121,7 @@ namespace MWClass { std::shared_ptr instance (new Clothing); - registerClass (typeid (ESM::Clothing).name(), instance); + registerClass (ESM::Clothing::sRecordId, instance); } std::string Clothing::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 06980f55da..0f45c25744 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -242,7 +242,7 @@ namespace MWClass { std::shared_ptr instance (new Container); - registerClass (typeid (ESM::Container).name(), instance); + registerClass (ESM::Container::sRecordId, instance); } bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 54623e6699..c20a005724 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -238,7 +238,7 @@ namespace MWClass { MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponslot != inv.end() && weaponslot->getTypeName() == typeid(ESM::Weapon).name()) + if (weaponslot != inv.end() && weaponslot->getType() == ESM::Weapon::sRecordId) weapon = *weaponslot; } @@ -497,7 +497,7 @@ namespace MWClass { std::shared_ptr instance (new Creature); - registerClass (typeid (ESM::Creature).name(), instance); + registerClass (ESM::Creature::sRecordId, instance); } float Creature::getMaxSpeed(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index f86004c619..e8023ce26b 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -70,7 +70,7 @@ namespace MWClass { std::shared_ptr instance (new CreatureLevList); - registerClass (typeid (ESM::CreatureLevList).name(), instance); + registerClass (ESM::CreatureLevList::sRecordId, instance); } void CreatureLevList::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index b5fe705ca6..01ff2aa440 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -264,7 +264,7 @@ namespace MWClass { std::shared_ptr instance (new Door); - registerClass (typeid (ESM::Door).name(), instance); + registerClass (ESM::Door::sRecordId, instance); } MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 20f9576dff..f582812934 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -81,7 +81,7 @@ namespace MWClass { std::shared_ptr instance (new Ingredient); - registerClass (typeid (ESM::Ingredient).name(), instance); + registerClass (ESM::Ingredient::sRecordId, instance); } std::string Ingredient::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index 5608a8d233..4ca45152a1 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -19,6 +19,6 @@ namespace MWClass { std::shared_ptr instance (new ItemLevList); - registerClass (typeid (ESM::ItemLevList).name(), instance); + registerClass (ESM::ItemLevList::sRecordId, instance); } } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 69cc1a09bf..dbd4e8a184 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -124,7 +124,7 @@ namespace MWClass { std::shared_ptr instance (new Light); - registerClass (typeid (ESM::Light).name(), instance); + registerClass (ESM::Light::sRecordId, instance); } std::string Light::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 985b087711..ccb5bbbd58 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -80,7 +80,7 @@ namespace MWClass { std::shared_ptr instance (new Lockpick); - registerClass (typeid (ESM::Lockpick).name(), instance); + registerClass (ESM::Lockpick::sRecordId, instance); } std::string Lockpick::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index facab9d51c..30e68d2377 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -106,7 +106,7 @@ namespace MWClass { std::shared_ptr instance (new Miscellaneous); - registerClass (typeid (ESM::Miscellaneous).name(), instance); + registerClass (ESM::Miscellaneous::sRecordId, instance); } std::string Miscellaneous::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 718b4d972d..59280a0f02 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -459,12 +459,12 @@ namespace MWClass if (equipped != invStore.end()) { std::vector parts; - if(equipped->getTypeName() == typeid(ESM::Clothing).name()) + if(equipped->getType() == ESM::Clothing::sRecordId) { const ESM::Clothing *clothes = equipped->get()->mBase; parts = clothes->mParts.mParts; } - else if(equipped->getTypeName() == typeid(ESM::Armor).name()) + else if(equipped->getType() == ESM::Armor::sRecordId) { const ESM::Armor *armor = equipped->get()->mBase; parts = armor->mParts.mParts; @@ -543,7 +543,7 @@ namespace MWClass MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr()); - if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) + if(!weapon.isEmpty() && weapon.getType() != ESM::Weapon::sRecordId) weapon = MWWorld::Ptr(); MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); @@ -766,7 +766,7 @@ namespace MWClass MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); - bool hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name(); + bool hasArmor = !armor.isEmpty() && armor.getType() == ESM::Armor::sRecordId; // If there's no item in the carried left slot or if it is not a shield redistribute the hit. if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft) { @@ -778,7 +778,7 @@ namespace MWClass if (armorslot != inv.end()) { armor = *armorslot; - hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name(); + hasArmor = !armor.isEmpty() && armor.getType() == ESM::Armor::sRecordId; } } if (hasArmor) @@ -1036,7 +1036,7 @@ namespace MWClass void Npc::registerSelf() { std::shared_ptr instance (new Npc); - registerClass (typeid (ESM::NPC).name(), instance); + registerClass (ESM::NPC::sRecordId, instance); } bool Npc::hasToolTip(const MWWorld::ConstPtr& ptr) const @@ -1135,7 +1135,7 @@ namespace MWClass for(int i = 0;i < MWWorld::InventoryStore::Slots;i++) { MWWorld::ConstContainerStoreIterator it = invStore.getSlot(i); - if (it == invStore.end() || it->getTypeName() != typeid(ESM::Armor).name()) + if (it == invStore.end() || it->getType() != ESM::Armor::sRecordId) { // unarmored ratings[i] = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); @@ -1232,7 +1232,7 @@ namespace MWClass const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); - if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name()) + if(boots == inv.end() || boots->getType() != ESM::Armor::sRecordId) return (name == "left") ? "FootBareLeft" : "FootBareRight"; switch(boots->getClass().getEquipmentSkill(*boots)) diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 56d9dff279..e0f8cf8397 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -74,7 +74,7 @@ namespace MWClass { std::shared_ptr instance (new Potion); - registerClass (typeid (ESM::Potion).name(), instance); + registerClass (ESM::Potion::sRecordId, instance); } std::string Potion::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 51273337a6..8291fb8f3c 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -80,7 +80,7 @@ namespace MWClass { std::shared_ptr instance (new Probe); - registerClass (typeid (ESM::Probe).name(), instance); + registerClass (ESM::Probe::sRecordId, instance); } std::string Probe::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index f1b88e422b..42581a8b6b 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -69,7 +69,7 @@ namespace MWClass { std::shared_ptr instance (new Repair); - registerClass (typeid (ESM::Repair).name(), instance); + registerClass (ESM::Repair::sRecordId, instance); } std::string Repair::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 0805ca3dd1..fc350c8351 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -59,7 +59,7 @@ namespace MWClass { std::shared_ptr instance (new Static); - registerClass (typeid (ESM::Static).name(), instance); + registerClass (ESM::Static::sRecordId, instance); } MWWorld::Ptr Static::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 6246c8fb09..e7337c83b7 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -125,7 +125,7 @@ namespace MWClass { std::shared_ptr instance (new Weapon); - registerClass (typeid (ESM::Weapon).name(), instance); + registerClass (ESM::Weapon::sRecordId, instance); } std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 334a9db39f..a47334a2dd 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -23,7 +23,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const { - bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name()); + bool isCreature = (mActor.getType() != ESM::NPC::sRecordId); // actor id if (!info.mActor.empty()) @@ -160,7 +160,7 @@ bool MWDialogue::Filter::testSelectStructs (const ESM::DialInfo& info) const bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert) const { - bool isCreature = (mActor.getTypeName() != typeid (ESM::NPC).name()); + bool isCreature = (mActor.getType() != ESM::NPC::sRecordId); if (isCreature) return true; @@ -207,7 +207,7 @@ bool MWDialogue::Filter::testFunctionLocal(const MWDialogue::SelectWrapper& sele bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const { - if (select.isNpcOnly() && (mActor.getTypeName() != typeid (ESM::NPC).name())) + if (select.isNpcOnly() && (mActor.getType() != ESM::NPC::sRecordId)) // If the actor is a creature, we pass all conditions only applicable to NPCs. return true; @@ -452,7 +452,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con { if (target.getClass().isNpc() && target.getClass().getNpcStats(target).isWerewolf()) return 2; - if (target.getTypeName() == typeid(ESM::Creature).name()) + if (target.getType() == ESM::Creature::sRecordId) return 1; } } diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index bacd1c7695..fd4a21cf10 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -194,7 +194,7 @@ namespace MWGui for (size_t i = 0; i < mModel->getItemCount(); ++i) { MWWorld::Ptr item = mModel->getItem(i).mBase; - if (item.getTypeName() != typeid(ESM::Ingredient).name()) + if (item.getType() != ESM::Ingredient::sRecordId) continue; itemNames.insert(item.getClass().getName(item)); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index 81dde1c059..9f202108a2 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -209,7 +209,7 @@ bool ContainerItemModel::onDropItem(const MWWorld::Ptr &item, int count) MWWorld::Ptr target = mItemSources[0].first; - if (target.getTypeName() != typeid(ESM::Container).name()) + if (target.getType() != ESM::Container::sRecordId) return true; // check container organic flag diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 93180adad2..1da77f5d06 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -508,13 +508,13 @@ namespace MWGui int services = mPtr.getClass().getServices(mPtr); - bool travel = (mPtr.getTypeName() == typeid(ESM::NPC).name() && !mPtr.get()->mBase->getTransport().empty()) - || (mPtr.getTypeName() == typeid(ESM::Creature).name() && !mPtr.get()->mBase->getTransport().empty()); + bool travel = (mPtr.getType() == ESM::NPC::sRecordId && !mPtr.get()->mBase->getTransport().empty()) + || (mPtr.getType() == ESM::Creature::sRecordId && !mPtr.get()->mBase->getTransport().empty()); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + if (mPtr.getType() == ESM::NPC::sRecordId) mTopicsList->addItem(gmst.find("sPersuasion")->mValue.getString()); if (services & ESM::NPC::AllItems) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index f53ba21f9f..a586991888 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -46,7 +46,7 @@ namespace bool isRightHandWeapon(const MWWorld::Ptr& item) { - if (item.getClass().getTypeName() != typeid(ESM::Weapon).name()) + if (item.getClass().getType() != ESM::Weapon::sRecordId) return false; std::vector equipmentSlots = item.getClass().getEquipmentSlots(item).first; return (!equipmentSlots.empty() && equipmentSlots.front() == MWWorld::InventoryStore::Slot_CarriedRight); @@ -281,7 +281,7 @@ namespace MWGui // If we unequip weapon during attack, it can lead to unexpected behaviour if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr)) { - bool isWeapon = item.mBase.getTypeName() == typeid(ESM::Weapon).name(); + bool isWeapon = item.mBase.getType() == ESM::Weapon::sRecordId; MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); if (isWeapon && invStore.isEquipped(item.mBase)) @@ -555,9 +555,9 @@ namespace MWGui if (!script.empty()) { // Ingredients, books and repair hammers must not have OnPCEquip set to 1 here - const std::string& type = ptr.getTypeName(); - bool isBook = type == typeid(ESM::Book).name(); - if (!isBook && type != typeid(ESM::Ingredient).name() && type != typeid(ESM::Repair).name()) + auto type = ptr.getType(); + bool isBook = type == ESM::Book::sRecordId; + if (!isBook && type != ESM::Ingredient::sRecordId && type != ESM::Repair::sRecordId) ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1); // Books must have PCSkipEquip set to 1 instead else if (isBook) @@ -593,8 +593,8 @@ namespace MWGui useItem(ptr); // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 item - if ((ptr.getTypeName() == typeid(ESM::Potion).name() || - ptr.getTypeName() == typeid(ESM::Ingredient).name()) + if ((ptr.getType() == ESM::Potion::sRecordId || + ptr.getType() == ESM::Ingredient::sRecordId) && mDragAndDrop->mDraggedCount > 1) { // Item can be provided from other window for example container. @@ -704,19 +704,19 @@ namespace MWGui if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory)) return; // make sure the object is of a type that can be picked up - const std::string& type = object.getTypeName(); - if ( (type != typeid(ESM::Apparatus).name()) - && (type != typeid(ESM::Armor).name()) - && (type != typeid(ESM::Book).name()) - && (type != typeid(ESM::Clothing).name()) - && (type != typeid(ESM::Ingredient).name()) - && (type != typeid(ESM::Light).name()) - && (type != typeid(ESM::Miscellaneous).name()) - && (type != typeid(ESM::Lockpick).name()) - && (type != typeid(ESM::Probe).name()) - && (type != typeid(ESM::Repair).name()) - && (type != typeid(ESM::Weapon).name()) - && (type != typeid(ESM::Potion).name())) + auto type = object.getType(); + if ( (type != ESM::Apparatus::sRecordId) + && (type != ESM::Armor::sRecordId) + && (type != ESM::Book::sRecordId) + && (type != ESM::Clothing::sRecordId) + && (type != ESM::Ingredient::sRecordId) + && (type != ESM::Light::sRecordId) + && (type != ESM::Miscellaneous::sRecordId) + && (type != ESM::Lockpick::sRecordId) + && (type != ESM::Probe::sRecordId) + && (type != ESM::Repair::sRecordId) + && (type != ESM::Weapon::sRecordId) + && (type != ESM::Potion::sRecordId)) return; // An object that can be picked up must have a tooltip. @@ -809,7 +809,7 @@ namespace MWGui lastId = item.getCellRef().getRefId(); - if (item.getClass().getTypeName() == typeid(ESM::Weapon).name() && + if (item.getClass().getType() == ESM::Weapon::sRecordId && isRightHandWeapon(item) && item.getClass().canBeEquipped(item, player).first) { diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index a6bfac2a45..ad61f63b90 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -387,9 +387,9 @@ namespace MWGui if (key->type == Type_Item) { - bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); - bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || - item.getTypeName() == typeid(ESM::Lockpick).name(); + bool isWeapon = item.getType() == ESM::Weapon::sRecordId; + bool isTool = item.getType() == ESM::Probe::sRecordId || + item.getType() == ESM::Lockpick::sRecordId; // delay weapon switching if player is busy if (isDelayNeeded && (isWeapon || isTool)) diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 28b13cdf0d..eb7ebd0e43 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -27,22 +27,22 @@ namespace { - bool compareType(const std::string& type1, const std::string& type2) + bool compareType(unsigned int type1, unsigned int type2) { // this defines the sorting order of types. types that are first in the vector appear before other types. - std::vector mapping; - mapping.emplace_back(typeid(ESM::Weapon).name() ); - mapping.emplace_back(typeid(ESM::Armor).name() ); - mapping.emplace_back(typeid(ESM::Clothing).name() ); - mapping.emplace_back(typeid(ESM::Potion).name() ); - mapping.emplace_back(typeid(ESM::Ingredient).name() ); - mapping.emplace_back(typeid(ESM::Apparatus).name() ); - mapping.emplace_back(typeid(ESM::Book).name() ); - mapping.emplace_back(typeid(ESM::Light).name() ); - mapping.emplace_back(typeid(ESM::Miscellaneous).name() ); - mapping.emplace_back(typeid(ESM::Lockpick).name() ); - mapping.emplace_back(typeid(ESM::Repair).name() ); - mapping.emplace_back(typeid(ESM::Probe).name() ); + std::vector mapping; + mapping.emplace_back(ESM::Weapon::sRecordId ); + mapping.emplace_back(ESM::Armor::sRecordId ); + mapping.emplace_back(ESM::Clothing::sRecordId ); + mapping.emplace_back(ESM::Potion::sRecordId ); + mapping.emplace_back(ESM::Ingredient::sRecordId ); + mapping.emplace_back(ESM::Apparatus::sRecordId ); + mapping.emplace_back(ESM::Book::sRecordId ); + mapping.emplace_back(ESM::Light::sRecordId ); + mapping.emplace_back(ESM::Miscellaneous::sRecordId ); + mapping.emplace_back(ESM::Lockpick::sRecordId ); + mapping.emplace_back(ESM::Repair::sRecordId ); + mapping.emplace_back(ESM::Probe::sRecordId ); assert( std::find(mapping.begin(), mapping.end(), type1) != mapping.end() ); assert( std::find(mapping.begin(), mapping.end(), type2) != mapping.end() ); @@ -62,15 +62,15 @@ namespace float result = 0; // compare items by type - std::string leftName = left.mBase.getTypeName(); - std::string rightName = right.mBase.getTypeName(); + auto leftType = left.mBase.getType(); + auto rightType = right.mBase.getType(); - if (leftName != rightName) - return compareType(leftName, rightName); + if (leftType != rightType) + return compareType(leftType, rightType); // compare items by name - leftName = Misc::StringUtils::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase)); - rightName = Misc::StringUtils::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase)); + std::string leftName = Misc::StringUtils::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase)); + std::string rightName = Misc::StringUtils::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase)); result = leftName.compare(rightName); if (result != 0) @@ -179,22 +179,22 @@ namespace MWGui MWWorld::Ptr base = item.mBase; int category = 0; - if (base.getTypeName() == typeid(ESM::Armor).name() - || base.getTypeName() == typeid(ESM::Clothing).name()) + if (base.getType() == ESM::Armor::sRecordId + || base.getType() == ESM::Clothing::sRecordId) category = Category_Apparel; - else if (base.getTypeName() == typeid(ESM::Weapon).name()) + else if (base.getType() == ESM::Weapon::sRecordId) category = Category_Weapon; - else if (base.getTypeName() == typeid(ESM::Ingredient).name() - || base.getTypeName() == typeid(ESM::Potion).name()) + else if (base.getType() == ESM::Ingredient::sRecordId + || base.getType() == ESM::Potion::sRecordId) category = Category_Magic; - else if (base.getTypeName() == typeid(ESM::Miscellaneous).name() - || base.getTypeName() == typeid(ESM::Ingredient).name() - || base.getTypeName() == typeid(ESM::Repair).name() - || base.getTypeName() == typeid(ESM::Lockpick).name() - || base.getTypeName() == typeid(ESM::Light).name() - || base.getTypeName() == typeid(ESM::Apparatus).name() - || base.getTypeName() == typeid(ESM::Book).name() - || base.getTypeName() == typeid(ESM::Probe).name()) + else if (base.getType() == ESM::Miscellaneous::sRecordId + || base.getType() == ESM::Ingredient::sRecordId + || base.getType() == ESM::Repair::sRecordId + || base.getType() == ESM::Lockpick::sRecordId + || base.getType() == ESM::Light::sRecordId + || base.getType() == ESM::Apparatus::sRecordId + || base.getType() == ESM::Book::sRecordId + || base.getType() == ESM::Probe::sRecordId) category = Category_Misc; if (item.mFlags & ItemStack::Flag_Enchanted) @@ -205,7 +205,7 @@ namespace MWGui if (mFilter & Filter_OnlyIngredients) { - if (base.getTypeName() != typeid(ESM::Ingredient).name()) + if (base.getType() != ESM::Ingredient::sRecordId) return false; if (!mNameFilter.empty() && !mEffectFilter.empty()) @@ -238,18 +238,18 @@ namespace MWGui if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) return false; - if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name() + if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getType() != ESM::Miscellaneous::sRecordId || base.getCellRef().getSoul() == "" || !MWBase::Environment::get().getWorld()->getStore().get().search(base.getCellRef().getSoul()))) return false; - if ((mFilter & Filter_OnlyRepairTools) && (base.getTypeName() != typeid(ESM::Repair).name())) + if ((mFilter & Filter_OnlyRepairTools) && (base.getType() != ESM::Repair::sRecordId)) return false; if ((mFilter & Filter_OnlyEnchantable) && (item.mFlags & ItemStack::Flag_Enchanted - || (base.getTypeName() != typeid(ESM::Armor).name() - && base.getTypeName() != typeid(ESM::Clothing).name() - && base.getTypeName() != typeid(ESM::Weapon).name() - && base.getTypeName() != typeid(ESM::Book).name()))) + || (base.getType() != ESM::Armor::sRecordId + && base.getType() != ESM::Clothing::sRecordId + && base.getType() != ESM::Weapon::sRecordId + && base.getType() != ESM::Book::sRecordId))) return false; - if ((mFilter & Filter_OnlyEnchantable) && base.getTypeName() == typeid(ESM::Book).name() + if ((mFilter & Filter_OnlyEnchantable) && base.getType() == ESM::Book::sRecordId && !base.get()->mBase->mData.mIsScroll) return false; @@ -263,8 +263,8 @@ namespace MWGui if ((mFilter & Filter_OnlyRepairable) && ( !base.getClass().hasItemHealth(base) || (base.getClass().getItemHealth(base) == base.getClass().getItemMaxHealth(base)) - || (base.getTypeName() != typeid(ESM::Weapon).name() - && base.getTypeName() != typeid(ESM::Armor).name()))) + || (base.getType() != ESM::Weapon::sRecordId + && base.getType() != ESM::Armor::sRecordId))) return false; if (mFilter & Filter_OnlyRechargable) diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 3fc0673735..a42028ee87 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -115,7 +115,7 @@ namespace MWGui std::vector transport; if (mPtr.getClass().isNpc()) transport = mPtr.get()->mBase->getTransport(); - else if (mPtr.getTypeName() == typeid(ESM::Creature).name()) + else if (mPtr.getType() == ESM::Creature::sRecordId) transport = mPtr.get()->mBase->getTransport(); for(unsigned int i = 0;i classNames = { + const static std::map classNames = { {typeid(MWClass::Activator), "Activator"}, {typeid(MWClass::Armor), "Armor"}, {typeid(MWClass::Book), "Book"}, @@ -40,7 +40,7 @@ namespace MWLua {typeid(MWClass::Weapon), "Weapon"}, }; - std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback) + std::string getMWClassName(const std::type_index& cls_type, std::string fallback) { auto it = classNames.find(cls_type); if (it != classNames.end()) @@ -55,13 +55,13 @@ namespace MWLua return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; } - std::string_view getMWClassName(const MWWorld::Ptr& ptr) + std::string getMWClassName(const MWWorld::Ptr& ptr) { if (ptr.getCellRef().getRefIdRef() == "player") return "Player"; if (isMarker(ptr)) return "Marker"; - return getMWClassName(typeid(ptr.getClass()), ptr.getTypeName()); + return ptr.getTypeDescription(); } std::string ptrToString(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index c0b6bf1919..5cd8f399d7 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -19,8 +19,8 @@ namespace MWLua std::string idToString(const ObjectId& id); std::string ptrToString(const MWWorld::Ptr& ptr); bool isMarker(const MWWorld::Ptr& ptr); - std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback = "Unknown"); - std::string_view getMWClassName(const MWWorld::Ptr& ptr); + std::string getMWClassName(const std::type_index& cls_type, std::string fallback = "Unknown"); + std::string getMWClassName(const MWWorld::Ptr& ptr); // Holds a mapping ObjectId -> MWWord::Ptr. class ObjectRegistry diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index bde18aa584..d85946dbd5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -879,7 +879,7 @@ namespace MWMechanics MWWorld::ContainerStoreIterator torch = inventoryStore.end(); for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it) { - if (it->getTypeName() == typeid(ESM::Light).name() && + if (it->getType() == ESM::Light::sRecordId && it->getClass().canBeEquipped(*it, ptr).first) { torch = it; @@ -894,10 +894,10 @@ namespace MWMechanics if (!ptr.getClass().getCreatureStats (ptr).getAiSequence().isInCombat()) { // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. - if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) + if (heldIter != inventoryStore.end() && heldIter->getType() != ESM::Light::sRecordId) inventoryStore.unequipItem(*heldIter, ptr); } - else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name()) + else if (heldIter == inventoryStore.end() || heldIter->getType() == ESM::Light::sRecordId) { // For hostile NPCs, see if they have anything better to equip first auto shield = inventoryStore.getPreferredShield(ptr); @@ -916,7 +916,7 @@ namespace MWMechanics } else { - if (heldIter != inventoryStore.end() && heldIter->getTypeName() == typeid(ESM::Light).name()) + if (heldIter != inventoryStore.end() && heldIter->getType() == ESM::Light::sRecordId) { // At day, unequip lights and auto equip shields or other suitable items // (Note: autoEquip will ignore lights) @@ -1708,7 +1708,7 @@ namespace MWMechanics MWBase::Environment::get().getDialogueManager()->say(iter->first, "hit"); // Apply soultrap - if (iter->first.getTypeName() == typeid(ESM::Creature).name()) + if (iter->first.getType() == ESM::Creature::sRecordId) soulTrap(iter->first); // Magic effects will be reset later, and the magic effect that could kill the actor diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ed26e2b9a4..58339d0228 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -383,7 +383,7 @@ void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, Ju bool CharacterController::onOpen() { - if (mPtr.getTypeName() == typeid(ESM::Container).name()) + if (mPtr.getType() == ESM::Container::sRecordId) { if (!mAnimation->hasAnimation("containeropen")) return true; @@ -404,7 +404,7 @@ bool CharacterController::onOpen() void CharacterController::onClose() { - if (mPtr.getTypeName() == typeid(ESM::Container).name()) + if (mPtr.getType() == ESM::Container::sRecordId) { if (!mAnimation->hasAnimation("containerclose")) return; @@ -591,7 +591,7 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. std::string anim = mCurrentMovement; mAdjustMovementAnimSpeed = true; - if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() + if (mPtr.getClass().getType() == ESM::Creature::sRecordId && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) { CharacterState walkState = runStateToWalkState(mMovementState); @@ -1432,7 +1432,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); - isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); + isWeapon = (weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId); if (isWeapon) { weapSpeed = weapon->get()->mBase->mData.mSpeed; @@ -1467,7 +1467,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mAttackStrength = 0; // Randomize attacks for non-bipedal creatures with Weapon flag - if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() && + if (mPtr.getClass().getType() == ESM::Creature::sRecordId && !mPtr.getClass().isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { @@ -1590,9 +1590,9 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if(!target.isEmpty()) { - if(item.getTypeName() == typeid(ESM::Lockpick).name()) + if(item.getType() == ESM::Lockpick::sRecordId) Security(mPtr).pickLock(target, item, resultMessage, resultSound); - else if(item.getTypeName() == typeid(ESM::Probe).name()) + else if(item.getType() == ESM::Probe::sRecordId) Security(mPtr).probeTrap(target, item, resultMessage, resultSound); } mAnimation->play(mCurrentWeapon, priorityWeapon, @@ -1867,7 +1867,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() + if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId && updateCarriedLeftVisible(mWeaponType)) { if (mAnimation->isPlaying("shield")) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 9e5774ea9e..994b2b015a 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -71,7 +71,7 @@ namespace MWMechanics MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker); MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) + if (shield == inv.end() || shield->getType() != ESM::Armor::sRecordId) return false; if (!blocker.getRefData().getBaseNode()) diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 1717ba06fe..0410dd02dd 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -29,11 +29,11 @@ namespace MWMechanics { mOldItemPtr=oldItem; mWeaponType = -1; - mObjectType.clear(); + mObjectType = 0; if(!itemEmpty()) { - mObjectType = mOldItemPtr.getTypeName(); - if (mObjectType == typeid(ESM::Weapon).name()) + mObjectType = mOldItemPtr.getType(); + if (mObjectType == ESM::Weapon::sRecordId) mWeaponType = mOldItemPtr.get()->mBase->mData.mType; } } @@ -115,7 +115,7 @@ namespace MWMechanics const bool powerfulSoul = getGemCharge() >= \ MWBase::Environment::get().getWorld()->getStore().get().find ("iSoulAmountForConstantEffect")->mValue.getInteger(); - if ((mObjectType == typeid(ESM::Armor).name()) || (mObjectType == typeid(ESM::Clothing).name())) + if ((mObjectType == ESM::Armor::sRecordId) || (mObjectType == ESM::Clothing::sRecordId)) { // Armor or Clothing switch(mCastStyle) { @@ -150,7 +150,7 @@ namespace MWMechanics return; } } - else if(mObjectType == typeid(ESM::Book).name()) + else if(mObjectType == ESM::Book::sRecordId) { // Scroll or Book mCastStyle = ESM::Enchantment::CastOnce; return; diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 33a2820938..256c5dad48 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -23,7 +23,7 @@ namespace MWMechanics ESM::EffectList mEffectList; std::string mNewItemName; - std::string mObjectType; + unsigned int mObjectType; int mWeaponType; const ESM::Enchantment* getRecord(const ESM::Enchantment& newEnchantment) const; diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index c8368101a7..25064123ce 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -66,14 +66,14 @@ namespace MWMechanics // Is this another levelled item or a real item? MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); - if (ref.getPtr().getTypeName() != typeid(ESM::ItemLevList).name() - && ref.getPtr().getTypeName() != typeid(ESM::CreatureLevList).name()) + if (ref.getPtr().getType() != ESM::ItemLevList::sRecordId + && ref.getPtr().getType() != ESM::CreatureLevList::sRecordId) { return item; } else { - if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) + if (ref.getPtr().getType() == ESM::ItemLevList::sRecordId) return getLevelledItem(ref.getPtr().get()->mBase, false, seed); else return getLevelledItem(ref.getPtr().get()->mBase, true, seed); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5245e09f36..149056e605 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -555,7 +555,7 @@ namespace MWMechanics { // Make sure zero base price items/services can't be bought/sold for 1 gold // and return the intended base price for creature merchants - if (basePrice == 0 || ptr.getTypeName() == typeid(ESM::Creature).name()) + if (basePrice == 0 || ptr.getType() == ESM::Creature::sRecordId) return basePrice; const MWMechanics::NpcStats &sellerStats = ptr.getClass().getNpcStats(ptr); diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index eea0655dd8..9125eb527b 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -84,7 +84,7 @@ void Objects::update(float duration, bool paused) for(auto& object : mObjects) { - if (object.first.getTypeName() != typeid(ESM::Container).name()) + if (object.first.getType() != ESM::Container::sRecordId) continue; if (object.second->isAnimPlaying("containeropen")) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 53fbe69ab3..adbaa30c15 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -274,7 +274,7 @@ namespace MWMechanics bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool isProjectile = false; - if (item.getTypeName() == typeid(ESM::Weapon).name()) + if (item.getType() == ESM::Weapon::sRecordId) { int type = item.get()->mBase->mData.mType; ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index d9e72ee43b..bc84a1df5e 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -213,8 +213,8 @@ namespace if (!wasEquipped) return; - std::string type = currentItem->getTypeName(); - if (type != typeid(ESM::Weapon).name() && type != typeid(ESM::Armor).name() && type != typeid(ESM::Clothing).name()) + auto type = currentItem->getType(); + if (type != ESM::Weapon::sRecordId && type != ESM::Armor::sRecordId && type != ESM::Clothing::sRecordId) return; if (actor.getClass().getCreatureStats(actor).isDead()) @@ -1038,4 +1038,4 @@ void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellP anim->removeEffect(effect.mEffectId); } -} \ No newline at end of file +} diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 974d297f10..eb276deb78 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -96,7 +96,7 @@ namespace MWMechanics float ratePotion (const MWWorld::Ptr &item, const MWWorld::Ptr& actor) { - if (item.getTypeName() != typeid(ESM::Potion).name()) + if (item.getType() != ESM::Potion::sRecordId) return 0.f; const ESM::Potion* potion = item.get()->mBase; diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 18a1c0004e..b18ad288ad 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -214,7 +214,7 @@ namespace MWMechanics case ESM::MagicEffect::Soultrap: { if (!target.getClass().isNpc() // no messagebox for NPCs - && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) + && (target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0)) { if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index b824d7c450..750fd803d4 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -23,7 +23,7 @@ namespace MWMechanics } // reject if npc is a creature - if ( merchant.getTypeName() != typeid(ESM::NPC).name() ) { + if ( merchant.getType() != ESM::NPC::sRecordId ) { return false; } diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 8480dc208e..570e89a17d 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -20,7 +20,7 @@ namespace MWMechanics float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type, float arrowRating, float boltRating) { - if (enemy.isEmpty() || item.getTypeName() != typeid(ESM::Weapon).name()) + if (enemy.isEmpty() || item.getType() != ESM::Weapon::sRecordId) return 0.f; if (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) == 0) diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp index feecd468ad..30f554f80b 100644 --- a/apps/openmw/mwmechanics/weapontype.cpp +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -21,13 +21,13 @@ namespace MWMechanics *weaptype = ESM::Weapon::HandToHand; else { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) + auto type = weapon->getType(); + if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); *weaptype = ref->mBase->mData.mType; } - else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId) *weaptype = ESM::Weapon::PickProbe; } diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index f556e6891a..b346d4ac6c 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -143,21 +143,21 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Armor).name() && !getShieldMesh(*shield).empty()) + if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getShieldMesh(*shield).empty()) { if(stats.getDrawState() != MWMechanics::DrawState_Weapon) return false; if (weapon != inv.end()) { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) + auto type = weapon->getType(); + if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); } - else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId) return true; } } @@ -184,7 +184,7 @@ void ActorAnimation::updateHolsteredShield(bool showCarriedLeft) const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) + if (shield == inv.end() || shield->getType() != ESM::Armor::sRecordId) return; // Can not show holdstered shields with two-handed weapons at all @@ -192,8 +192,8 @@ void ActorAnimation::updateHolsteredShield(bool showCarriedLeft) if(weapon == inv.end()) return; - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) + auto type = weapon->getType(); + if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; @@ -254,17 +254,17 @@ bool ActorAnimation::useShieldAnimations() const const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (weapon != inv.end() && shield != inv.end() && - shield->getTypeName() == typeid(ESM::Armor).name() && + shield->getType() == ESM::Armor::sRecordId && !getShieldMesh(*shield).empty()) { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) + auto type = weapon->getType(); + if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon->get(); ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); } - else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId) return true; } @@ -288,8 +288,8 @@ std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& if(weapon.isEmpty()) return boneName; - const std::string &type = weapon.getClass().getTypeName(); - if(type == typeid(ESM::Weapon).name()) + auto type = weapon.getClass().getType(); + if(type == ESM::Weapon::sRecordId) { const MWWorld::LiveCellRef *ref = weapon.get(); int weaponType = ref->mBase->mData.mType; @@ -323,7 +323,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; // Since throwing weapons stack themselves, do not show such weapon itself @@ -399,7 +399,7 @@ void ActorAnimation::updateQuiver() const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; std::string mesh = weapon->getClass().getModel(*weapon); @@ -471,7 +471,7 @@ void ActorAnimation::updateQuiver() void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) { - if (item.getTypeName() == typeid(ESM::Light).name()) + if (item.getType() == ESM::Light::sRecordId) { const ESM::Light* light = item.get()->mBase; if (!(light->mData.mFlags & ESM::Light::Carry)) @@ -486,7 +486,7 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) // If the count of equipped ammo or throwing weapon was changed, we should update quiver const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); @@ -502,7 +502,7 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/) { - if (item.getTypeName() == typeid(ESM::Light).name()) + if (item.getType() == ESM::Light::sRecordId) { ItemLightMap::iterator iter = mItemLights.find(item); if (iter != mItemLights.end()) @@ -520,7 +520,7 @@ void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/) // If the count of equipped ammo or throwing weapon was changed, we should update quiver const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 475c656cc9..10624c8bca 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1794,7 +1794,7 @@ namespace MWRender if (!ptr.getClass().getEnchantment(ptr).empty()) mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); } - if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight) + if (ptr.getType() == ESM::Light::sRecordId && allowLight) addExtraLight(getOrCreateObjectRoot(), ptr.get()->mBase); if (!allowLight && mObjectRoot) @@ -1823,7 +1823,7 @@ namespace MWRender bool ObjectAnimation::canBeHarvested() const { - if (mPtr.getTypeName() != typeid(ESM::Container).name()) + if (mPtr.getType() != ESM::Container::sRecordId) return false; const MWWorld::LiveCellRef* ref = mPtr.get(); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index a9733cddf8..4a84bf4f23 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -395,7 +395,7 @@ namespace MWRender if(iter != inv.end()) { groupname = "inventoryweapononehand"; - if(iter->getTypeName() == typeid(ESM::Weapon).name()) + if(iter->getType() == ESM::Weapon::sRecordId) { MWWorld::LiveCellRef *ref = iter->get(); int type = ref->mBase->mData.mType; @@ -428,7 +428,7 @@ namespace MWRender mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && showCarriedLeft) + if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft) { if(!mAnimation->getInfo("torch")) mAnimation->play("torch", 2, Animation::BlendMask_LeftArm, false, diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index f1d28b0634..502340d4b9 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -119,7 +119,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) std::string itemModel = item.getClass().getModel(item); if (slot == MWWorld::InventoryStore::Slot_CarriedRight) { - if(item.getTypeName() == typeid(ESM::Weapon).name()) + if(item.getType() == ESM::Weapon::sRecordId) { int type = item.get()->mBase->mData.mType; bonename = MWMechanics::getWeaponType(type)->mAttachBone; @@ -137,7 +137,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) else { bonename = "Shield Bone"; - if (item.getTypeName() == typeid(ESM::Armor).name()) + if (item.getType() == ESM::Armor::sRecordId) { // Shield body part model should be used if possible. const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -174,7 +174,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) // Crossbows start out with a bolt attached // FIXME: code duplicated from NpcAnimation if (slot == MWWorld::InventoryStore::Slot_CarriedRight && - item.getTypeName() == typeid(ESM::Weapon).name() && + item.getType() == ESM::Weapon::sRecordId && item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow); @@ -246,7 +246,7 @@ osg::Group *CreatureWeaponAnimation::getArrowBone() const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return nullptr; int type = weapon->get()->mBase->mData.mType; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 7c6d82d83d..28987064f3 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -594,13 +594,13 @@ void NpcAnimation::updateParts() int prio = 1; bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); osg::Vec4f glowColor = store->getClass().getEnchantmentColor(*store); - if(store->getTypeName() == typeid(ESM::Clothing).name()) + if(store->getType() == ESM::Clothing::sRecordId) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; const ESM::Clothing *clothes = store->get()->mBase; addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor); } - else if(store->getTypeName() == typeid(ESM::Armor).name()) + else if(store->getType() == ESM::Armor::sRecordId) { prio = ((slotlist[i].mBasePriority+1)<<1) + 1; const ESM::Armor *armor = store->get()->mBase; @@ -640,7 +640,7 @@ void NpcAnimation::updateParts() { MWWorld::ConstContainerStoreIterator store = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); MWWorld::ConstPtr part; - if(store != inv.end() && (part=*store).getTypeName() == typeid(ESM::Light).name()) + if(store != inv.end() && (part=*store).getType() == ESM::Light::sRecordId) { const ESM::Light *light = part.get()->mBase; addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, @@ -771,7 +771,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g { const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()) + if(weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId) { int weaponType = weapon->get()->mBase->mData.mType; const std::string weaponBonename = MWMechanics::getWeaponType(weaponType)->mAttachBone; @@ -943,7 +943,7 @@ void NpcAnimation::showWeapons(bool showWeapon) mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); // Crossbows start out with a bolt attached - if (weapon->getTypeName() == typeid(ESM::Weapon).name() && + if (weapon->getType() == ESM::Weapon::sRecordId && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { int ammotype = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow)->mAmmoType; @@ -975,7 +975,7 @@ void NpcAnimation::showCarriedLeft(bool show) osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter); std::string mesh = iter->getClass().getModel(*iter); // For shields we must try to use the body part model - if (iter->getTypeName() == typeid(ESM::Armor).name()) + if (iter->getType() == ESM::Armor::sRecordId) { const ESM::Armor *armor = iter->get()->mBase; const std::vector& bodyparts = armor->mParts.mParts; @@ -987,7 +987,7 @@ void NpcAnimation::showCarriedLeft(bool show) { if (mesh.empty()) reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1); - if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield]) + if (iter->getType() == ESM::Light::sRecordId && mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get()->mBase); } } @@ -1033,7 +1033,7 @@ osg::Group* NpcAnimation::getArrowBone() const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + if(weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return nullptr; int type = weapon->get()->mBase->mData.mType; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index b89cfd8df1..3db415126b 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -66,7 +66,7 @@ void WeaponAnimation::attachArrow(const MWWorld::Ptr& actor) MWWorld::ConstContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weaponSlot == inv.end()) return; - if (weaponSlot->getTypeName() != typeid(ESM::Weapon).name()) + if (weaponSlot->getType() != ESM::Weapon::sRecordId) return; int type = weaponSlot->get()->mBase->mData.mType; @@ -109,7 +109,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weapon == inv.end()) return; - if (weapon->getTypeName() != typeid(ESM::Weapon).name()) + if (weapon->getType() != ESM::Weapon::sRecordId) return; // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 7eeb3bfaba..501404e958 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -50,7 +50,7 @@ namespace void addRandomToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::Ptr& owner, MWWorld::ContainerStore& store, bool topLevel = true) { - if(itemPtr.getTypeName() == typeid(ESM::ItemLevList).name()) + if(itemPtr.getType() == ESM::ItemLevList::sRecordId) { const ESM::ItemLevList* levItemList = itemPtr.get()->mBase; @@ -108,7 +108,7 @@ namespace MWScript // Check if "item" can be placed in a container MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), item, 1); MWWorld::Ptr itemPtr = manualRef.getPtr(); - bool isLevelledList = itemPtr.getClass().getTypeName() == typeid(ESM::ItemLevList).name(); + bool isLevelledList = itemPtr.getClass().getType() == ESM::ItemLevList::sRecordId; if(!isLevelledList) MWWorld::ContainerStore::getType(itemPtr); @@ -120,7 +120,7 @@ namespace MWScript } // Calls to unresolved containers affect the base record - if(ptr.getClass().getTypeName() == typeid(ESM::Container).name() && (!ptr.getRefData().getCustomData() || + if(ptr.getClass().getType() == ESM::Container::sRecordId && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count); @@ -232,7 +232,7 @@ namespace MWScript return; } // Calls to unresolved containers affect the base record instead - else if(ptr.getClass().getTypeName() == typeid(ESM::Container).name() && + else if(ptr.getClass().getType() == ESM::Container::sRecordId && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); @@ -380,7 +380,7 @@ namespace MWScript const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot); - if (it == invStore.end() || it->getTypeName () != typeid(ESM::Armor).name()) + if (it == invStore.end() || it->getType () != ESM::Armor::sRecordId) { runtime.push(-1); return; @@ -464,13 +464,13 @@ namespace MWScript runtime.push(-1); return; } - else if (it->getTypeName() != typeid(ESM::Weapon).name()) + else if (it->getType() != ESM::Weapon::sRecordId) { - if (it->getTypeName() == typeid(ESM::Lockpick).name()) + if (it->getType() == ESM::Lockpick::sRecordId) { runtime.push(-2); } - else if (it->getTypeName() == typeid(ESM::Probe).name()) + else if (it->getType() == ESM::Probe::sRecordId) { runtime.push(-3); } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 3642f68dc2..5d9f037357 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -310,7 +310,7 @@ namespace MWScript // Instantly reset door to closed state // This is done when using Lock in scripts, but not when using Lock spells. - if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport()) + if (ptr.getType() == ESM::Door::sRecordId && !ptr.getCellRef().getTeleport()) { MWBase::Environment::get().getWorld()->activateDoor(ptr, MWWorld::DoorState::Idle); } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index da4dd9d99e..24cd09ad37 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -23,7 +23,7 @@ namespace MWWorld { - std::map > Class::sClasses; + std::map > Class::sClasses; void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { @@ -228,15 +228,12 @@ namespace MWWorld throw std::runtime_error("Class does not support armor rating"); } - const Class& Class::get (const std::string& key) + const Class& Class::get (unsigned int key) { - if (key.empty()) - throw std::logic_error ("Class::get(): attempting to get an empty key"); - - std::map >::const_iterator iter = sClasses.find (key); + auto iter = sClasses.find (key); if (iter==sClasses.end()) - throw std::logic_error ("Class::get(): unknown class key: " + key); + throw std::logic_error ("Class::get(): unknown class key: " + std::to_string(key)); return *iter->second; } @@ -246,9 +243,9 @@ namespace MWWorld throw std::runtime_error ("class does not support persistence"); } - void Class::registerClass(const std::string& key, std::shared_ptr instance) + void Class::registerClass(unsigned int key, std::shared_ptr instance) { - instance->mTypeName = key; + instance->mType = key; sClasses.insert(std::make_pair(key, instance)); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 8e451ea580..64ac873960 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -54,9 +54,8 @@ namespace MWWorld /// \brief Base class for referenceable esm records class Class { - static std::map > sClasses; - - std::string mTypeName; + static std::map > sClasses; + unsigned int mType; protected: @@ -73,8 +72,8 @@ namespace MWWorld Class (const Class&) = delete; Class& operator= (const Class&) = delete; - const std::string& getTypeName() const { - return mTypeName; + unsigned int getType() const { + return mType; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; @@ -338,10 +337,10 @@ namespace MWWorld const; ///< Write additional state from \a ptr into \a state. - static const Class& get (const std::string& key); + static const Class& get (unsigned int key); ///< If there is no class for this \a key, an exception is thrown. - static void registerClass (const std::string& key, std::shared_ptr instance); + static void registerClass (unsigned int key, std::shared_ptr instance); virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index b02c2bb407..58f215932e 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -1,7 +1,6 @@ #include "containerstore.hpp" #include -#include #include #include @@ -567,7 +566,7 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel) { - if (ptr.getTypeName()==typeid (ESM::ItemLevList).name()) + if (ptr.getType()==ESM::ItemLevList::sRecordId) { if(!seed) return; @@ -693,44 +692,44 @@ int MWWorld::ContainerStore::getType (const ConstPtr& ptr) if (ptr.isEmpty()) throw std::runtime_error ("can't put a non-existent object into a container"); - if (ptr.getTypeName()==typeid (ESM::Potion).name()) + if (ptr.getType()==ESM::Potion::sRecordId) return Type_Potion; - if (ptr.getTypeName()==typeid (ESM::Apparatus).name()) + if (ptr.getType()==ESM::Apparatus::sRecordId) return Type_Apparatus; - if (ptr.getTypeName()==typeid (ESM::Armor).name()) + if (ptr.getType()==ESM::Armor::sRecordId) return Type_Armor; - if (ptr.getTypeName()==typeid (ESM::Book).name()) + if (ptr.getType()==ESM::Book::sRecordId) return Type_Book; - if (ptr.getTypeName()==typeid (ESM::Clothing).name()) + if (ptr.getType()==ESM::Clothing::sRecordId) return Type_Clothing; - if (ptr.getTypeName()==typeid (ESM::Ingredient).name()) + if (ptr.getType()==ESM::Ingredient::sRecordId) return Type_Ingredient; - if (ptr.getTypeName()==typeid (ESM::Light).name()) + if (ptr.getType()==ESM::Light::sRecordId) return Type_Light; - if (ptr.getTypeName()==typeid (ESM::Lockpick).name()) + if (ptr.getType()==ESM::Lockpick::sRecordId) return Type_Lockpick; - if (ptr.getTypeName()==typeid (ESM::Miscellaneous).name()) + if (ptr.getType()==ESM::Miscellaneous::sRecordId) return Type_Miscellaneous; - if (ptr.getTypeName()==typeid (ESM::Probe).name()) + if (ptr.getType()==ESM::Probe::sRecordId) return Type_Probe; - if (ptr.getTypeName()==typeid (ESM::Repair).name()) + if (ptr.getType()==ESM::Repair::sRecordId) return Type_Repair; - if (ptr.getTypeName()==typeid (ESM::Weapon).name()) + if (ptr.getType()==ESM::Weapon::sRecordId) return Type_Weapon; throw std::runtime_error ( - "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " can not be placed into a container"); + "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeDescription() + " can not be placed into a container"); } MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 49e60af1fd..8cb0b9b012 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -139,8 +139,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, if (allowAutoEquip && actorPtr != MWMechanics::getPlayer() && actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()) { - const std::string& type = itemPtr.getTypeName(); - if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) + auto type = itemPtr.getType(); + if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) autoEquip(actorPtr); } @@ -431,7 +431,7 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& if (iter.getType() == ContainerStore::Type_Armor) { - if (old.getTypeName() == typeid(ESM::Armor).name()) + if (old.getType() == ESM::Armor::sRecordId) { if (old.get()->mBase->mData.mType < test.get()->mBase->mData.mType) continue; @@ -465,7 +465,7 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& } } - if (old.getTypeName() == typeid(ESM::Clothing).name()) + if (old.getType() == ESM::Clothing::sRecordId) { // check value if (old.getClass().getValue (old) >= test.getClass().getValue (test)) @@ -617,8 +617,8 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) && actor.getClass().isNpc() && !actor.getClass().getNpcStats(actor).isWerewolf()) { - const std::string& type = item.getTypeName(); - if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) + auto type = item.getType(); + if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) autoEquip(actor); } diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 4203e1ac55..c74817911e 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -11,7 +11,7 @@ #include "class.hpp" #include "esmstore.hpp" -MWWorld::LiveCellRefBase::LiveCellRefBase(const std::string& type, const ESM::CellRef &cref) +MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef &cref) : mClass(&Class::get(type)), mRef(cref), mData(cref) { } diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 414fde42bd..af0f84dc8d 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -1,8 +1,6 @@ #ifndef GAME_MWWORLD_LIVECELLREF_H #define GAME_MWWORLD_LIVECELLREF_H -#include - #include "cellref.hpp" #include "refdata.hpp" @@ -31,7 +29,7 @@ namespace MWWorld /** runtime-data */ RefData mData; - LiveCellRefBase(const std::string& type, const ESM::CellRef &cref=ESM::CellRef()); + LiveCellRefBase(unsigned int type, const ESM::CellRef &cref=ESM::CellRef()); /* Need this for the class to be recognized as polymorphic */ virtual ~LiveCellRefBase() { } @@ -43,6 +41,8 @@ namespace MWWorld virtual void save (ESM::ObjectState& state) const = 0; ///< Save LiveCellRef state into \a state. + virtual std::string getTypeDescription() const { return ""; } + protected: void loadImp (const ESM::ObjectState& state); @@ -77,11 +77,11 @@ namespace MWWorld struct LiveCellRef : public LiveCellRefBase { LiveCellRef(const ESM::CellRef& cref, const X* b = nullptr) - : LiveCellRefBase(typeid(X).name(), cref), mBase(b) + : LiveCellRefBase(X::sRecordId, cref), mBase(b) {} LiveCellRef(const X* b = nullptr) - : LiveCellRefBase(typeid(X).name()), mBase(b) + : LiveCellRefBase(X::sRecordId), mBase(b) {} // The object that this instance is based on. @@ -95,6 +95,8 @@ namespace MWWorld void save (ESM::ObjectState& state) const override; ///< Save LiveCellRef state into \a state. + std::string getTypeDescription() const override { return X::getRecordType(); } + static bool checkState (const ESM::ObjectState& state); ///< Check if state is valid and report errors. /// diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 1661d6b9f7..0e7dd0771f 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -43,7 +43,7 @@ namespace bool operator()(const MWWorld::Ptr& containerPtr) { // Ignore containers without generated content - if (containerPtr.getTypeName() == typeid(ESM::Container).name() && + if (containerPtr.getType() == ESM::Container::sRecordId && containerPtr.getRefData().getCustomData() == nullptr) return true; diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index e16a196297..b18e3b1689 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -6,10 +6,10 @@ #include "class.hpp" #include "livecellref.hpp" -const std::string& MWWorld::Ptr::getTypeName() const +unsigned int MWWorld::Ptr::getType() const { if(mRef != nullptr) - return mRef->mClass->getTypeName(); + return mRef->mClass->getType(); throw std::runtime_error("Can't get type name from an empty object."); } @@ -55,10 +55,10 @@ MWWorld::Ptr::operator const void *() // ------------------------------------------------------------------------------- -const std::string &MWWorld::ConstPtr::getTypeName() const +unsigned int MWWorld::ConstPtr::getType() const { if(mRef != nullptr) - return mRef->mClass->getTypeName(); + return mRef->mClass->getType(); throw std::runtime_error("Can't get type name from an empty object."); } diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 9ab18d7f48..6813b7d124 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -35,7 +35,12 @@ namespace MWWorld return mRef == nullptr; } - const std::string& getTypeName() const; + unsigned int getType() const; + + std::string getTypeDescription() const + { + return mRef ? mRef->getTypeDescription() : "nullptr"; + } const Class& getClass() const { @@ -51,8 +56,8 @@ namespace MWWorld if(ref) return ref; std::stringstream str; - str<< "Bad LiveCellRef cast to "<getTypeDescription() : "nullptr"; + } const Class& getClass() const { @@ -127,8 +137,8 @@ namespace MWWorld if(ref) return ref; std::stringstream str; - str<< "Bad LiveCellRef cast to "< Date: Mon, 11 Oct 2021 13:11:59 +0000 Subject: [PATCH 1458/2859] avoids virtual function calls in ComputeLightSpaceBounds (#3167) osg::NodeVisitor is designed to recursively call virtual apply signatures until we find an implemented signature. Encountered nodes that we do not explicitely handle will trigger additional virtual function calls. With this PR we avoid these additional virtual function calls in the particularly costly ComputeLightSpaceBounds by adding some explicit signatures. --- components/sceneutil/mwshadowtechnique.cpp | 14 ++++++++++++++ components/sceneutil/mwshadowtechnique.hpp | 11 ++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 89d872dbfe..024123b3e1 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -378,6 +378,11 @@ void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Node& node) popCurrentMask(); } +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Group& node) +{ + apply(static_cast(node)); +} + void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Drawable& drawable) { if (isCulled(drawable)) return; @@ -391,6 +396,11 @@ void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Drawable& drawable) popCurrentMask(); } +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Geometry& drawable) +{ + apply(static_cast(drawable)); +} + void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Billboard&) { OSG_INFO << "Warning Billboards not yet supported" << std::endl; @@ -424,7 +434,11 @@ void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Transform& transform // pop the culling mode. popCurrentMask(); +} +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::MatrixTransform& transform) +{ + apply(static_cast(transform)); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Camera&) diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index fd562184e0..3f6c0fb765 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -91,20 +91,21 @@ namespace SceneUtil { public: ComputeLightSpaceBounds(); - void apply(osg::Node& node) override; + void apply(osg::Node& node) override final; + void apply(osg::Group& node) override; - void apply(osg::Drawable& drawable) override; + void apply(osg::Drawable& drawable) override final; + void apply(osg::Geometry& drawable) override; void apply(osg::Billboard&) override; void apply(osg::Projection&) override; - void apply(osg::Transform& transform) override; + void apply(osg::Transform& transform) override final; + void apply(osg::MatrixTransform& transform) override; void apply(osg::Camera&) override; - using osg::NodeVisitor::apply; - void updateBound(const osg::BoundingBox& bb); void update(const osg::Vec3& v); From 5cf9995a3128c26bfd89af8d53e395f3f0dafdcf Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 11 Oct 2021 13:28:37 +0000 Subject: [PATCH 1459/2859] fixes windows build --- apps/openmw/mwworld/player.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 1e4b0ffdf5..a7e42d95e1 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -11,10 +11,10 @@ #include #include +#include namespace ESM { - struct NPC; class ESMWriter; class ESMReader; } From d6613d3677adf5a921a4d93a9e7afafe17aff7d7 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 11 Oct 2021 14:56:26 +0000 Subject: [PATCH 1460/2859] mostly reverts MWLua changes (#3170) We just can no longer use the former ptr.getTypeName() as a fallback, but I do not suppose it matters. This value was an implementation defined string that usually contained a garbled mess. Probably we should additionally refactor Lua types completely now that we have Ptr::getType(). I will leave this up to MWLua developers. --- apps/openmw/mwlua/object.cpp | 8 ++++---- apps/openmw/mwlua/object.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index eb88ab69d0..266e628dd6 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -23,7 +23,7 @@ namespace MWLua return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile); } - const static std::map classNames = { + const static std::map classNames = { {typeid(MWClass::Activator), "Activator"}, {typeid(MWClass::Armor), "Armor"}, {typeid(MWClass::Book), "Book"}, @@ -40,7 +40,7 @@ namespace MWLua {typeid(MWClass::Weapon), "Weapon"}, }; - std::string getMWClassName(const std::type_index& cls_type, std::string fallback) + std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback) { auto it = classNames.find(cls_type); if (it != classNames.end()) @@ -55,13 +55,13 @@ namespace MWLua return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; } - std::string getMWClassName(const MWWorld::Ptr& ptr) + std::string_view getMWClassName(const MWWorld::Ptr& ptr) { if (ptr.getCellRef().getRefIdRef() == "player") return "Player"; if (isMarker(ptr)) return "Marker"; - return ptr.getTypeDescription(); + return getMWClassName(typeid(ptr.getClass())); } std::string ptrToString(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 5cd8f399d7..c0b6bf1919 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -19,8 +19,8 @@ namespace MWLua std::string idToString(const ObjectId& id); std::string ptrToString(const MWWorld::Ptr& ptr); bool isMarker(const MWWorld::Ptr& ptr); - std::string getMWClassName(const std::type_index& cls_type, std::string fallback = "Unknown"); - std::string getMWClassName(const MWWorld::Ptr& ptr); + std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback = "Unknown"); + std::string_view getMWClassName(const MWWorld::Ptr& ptr); // Holds a mapping ObjectId -> MWWord::Ptr. class ObjectRegistry From 6986feb81b3a5a0154fafe415b5fa68fa6cdd223 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Jul 2021 04:09:55 +0200 Subject: [PATCH 1461/2859] Initialize navigator max climb and max slope settings in makeSettingsFromSettingsManager To avoid having multiple places to initialize them when they will be required by multiple binaries. --- apps/openmw/mwphysics/constants.hpp | 2 -- apps/openmw/mwphysics/movementsolver.cpp | 2 +- apps/openmw/mwphysics/movementsolver.hpp | 5 +++-- apps/openmw/mwphysics/stepper.cpp | 16 +++++++++------- apps/openmw/mwworld/worldimp.cpp | 9 ++++----- components/detournavigator/settings.cpp | 10 ++++------ components/detournavigator/settings.hpp | 2 +- components/misc/constants.hpp | 3 +++ 8 files changed, 25 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index 2d7cd53d38..c275b63c7b 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -3,7 +3,6 @@ namespace MWPhysics { - static constexpr float sStepSizeUp = 34.0f; static constexpr float sStepSizeDown = 62.0f; static constexpr float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes @@ -12,7 +11,6 @@ namespace MWPhysics static constexpr bool sDoExtraStairHacks = true; static constexpr float sGroundOffset = 1.0f; - static constexpr float sMaxSlope = 46.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static constexpr int sMaxIterations = 8; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index df9239dd83..44a5391f0d 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -230,7 +230,7 @@ namespace MWPhysics float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ; osg::Vec3f oldPosition = newPosition; bool usedStepLogic = false; - if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject)) + if (hitHeight < Constants::sStepSizeUp && !isActor(tracer.mHitObject)) { // Try to step up onto it. // NOTE: this modifies newPosition and velocity on its own if successful diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 90b10c275e..30733eeec8 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -3,7 +3,8 @@ #include -#include "constants.hpp" +#include + #include "../mwworld/ptr.hpp" class btCollisionWorld; @@ -30,7 +31,7 @@ namespace MWPhysics template static bool isWalkableSlope(const Vec3 &normal) { - static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); + static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(Constants::sMaxSlope)); return (normal.z() > sMaxSlopeCos); } diff --git a/apps/openmw/mwphysics/stepper.cpp b/apps/openmw/mwphysics/stepper.cpp index 1f53c1ac51..af85658910 100644 --- a/apps/openmw/mwphysics/stepper.cpp +++ b/apps/openmw/mwphysics/stepper.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "collisiontype.hpp" #include "constants.hpp" #include "movementsolver.hpp" @@ -13,7 +15,7 @@ namespace MWPhysics { if (!stepper.mHitObject) return false; - static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope)); + static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(Constants::sMaxSlope)); if (stepper.mPlaneNormal.z() <= sMaxSlopeCos) return false; @@ -34,13 +36,13 @@ namespace MWPhysics // Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the ground. // This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and just prevent stepping on insane geometry. - mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld); + mUpStepper.doTrace(mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld); float upDistance = 0; if(!mUpStepper.mHitObject) - upDistance = sStepSizeUp; - else if(mUpStepper.mFraction*sStepSizeUp > sCollisionMargin) - upDistance = mUpStepper.mFraction*sStepSizeUp - sCollisionMargin; + upDistance = Constants::sStepSizeUp; + else if(mUpStepper.mFraction * Constants::sStepSizeUp > sCollisionMargin) + upDistance = mUpStepper.mFraction * Constants::sStepSizeUp - sCollisionMargin; else { return false; @@ -76,9 +78,9 @@ namespace MWPhysics } else if(attempt == 3) { - if(upDistance > sStepSizeUp) + if(upDistance > Constants::sStepSizeUp) { - upDistance = sStepSizeUp; + upDistance = Constants::sStepSizeUp; tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance); } moveDistance = sMinStep2; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4fd9235586..57ad8fc149 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -173,13 +173,12 @@ namespace MWWorld mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); - if (auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager()) + if (Settings::Manager::getBool("enable", "Navigator")) { - navigatorSettings->mMaxClimb = MWPhysics::sStepSizeUp; - navigatorSettings->mMaxSlope = MWPhysics::sMaxSlope; - navigatorSettings->mSwimHeightScale = mSwimHeightScale; + auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); + navigatorSettings.mSwimHeightScale = mSwimHeightScale; DetourNavigator::RecastGlobalAllocator::init(); - mNavigator.reset(new DetourNavigator::NavigatorImpl(*navigatorSettings)); + mNavigator.reset(new DetourNavigator::NavigatorImpl(navigatorSettings)); } else { diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index ff99fae20c..e428f3695a 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -1,14 +1,12 @@ #include "settings.hpp" #include +#include namespace DetourNavigator { - std::optional makeSettingsFromSettingsManager() + Settings makeSettingsFromSettingsManager() { - if (!::Settings::Manager::getBool("enable", "Navigator")) - return std::optional(); - Settings navigatorSettings; navigatorSettings.mBorderSize = ::Settings::Manager::getInt("border size", "Navigator"); @@ -16,9 +14,9 @@ namespace DetourNavigator navigatorSettings.mCellSize = ::Settings::Manager::getFloat("cell size", "Navigator"); navigatorSettings.mDetailSampleDist = ::Settings::Manager::getFloat("detail sample dist", "Navigator"); navigatorSettings.mDetailSampleMaxError = ::Settings::Manager::getFloat("detail sample max error", "Navigator"); - navigatorSettings.mMaxClimb = 0; + navigatorSettings.mMaxClimb = Constants::sStepSizeUp; navigatorSettings.mMaxSimplificationError = ::Settings::Manager::getFloat("max simplification error", "Navigator"); - navigatorSettings.mMaxSlope = 0; + navigatorSettings.mMaxSlope = Constants::sMaxSlope; navigatorSettings.mRecastScaleFactor = ::Settings::Manager::getFloat("recast scale factor", "Navigator"); navigatorSettings.mSwimHeightScale = 0; navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator"); diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 39f5815b8e..800ad6b2bb 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -41,7 +41,7 @@ namespace DetourNavigator std::chrono::milliseconds mMinUpdateInterval; }; - std::optional makeSettingsFromSettingsManager(); + Settings makeSettingsFromSettingsManager(); } #endif diff --git a/components/misc/constants.hpp b/components/misc/constants.hpp index bfd3933fc7..01d783a4fc 100644 --- a/components/misc/constants.hpp +++ b/components/misc/constants.hpp @@ -36,6 +36,9 @@ const std::string HerbalismLabel = "HerbalismSwitch"; // Percentage height at which projectiles are spawned from an actor const float TorsoHeight = 0.75f; +static constexpr float sStepSizeUp = 34.0f; +static constexpr float sMaxSlope = 46.0f; + } #endif From 7c5a590890f9c0404cca74a931c131a48b1cfda6 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 11 Oct 2021 19:23:08 +0200 Subject: [PATCH 1462/2859] Replace float type for arguments to create heightfield with int To reduce amount of computations on the caller side and restrict possible values. * verts can't be non-int because it's a number of things. * worldsize is initially defined as int by ESM::Land::REAL_SIZE. * Put function to calculate heightfied shift into components to be able to reuse by other binaries. --- apps/openmw/mwphysics/heightfield.cpp | 27 +++++++++++++----------- apps/openmw/mwphysics/heightfield.hpp | 3 ++- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 8 +++---- components/bullethelpers/heightfield.hpp | 14 ++++++++++++ 6 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 components/bullethelpers/heightfield.hpp diff --git a/apps/openmw/mwphysics/heightfield.cpp b/apps/openmw/mwphysics/heightfield.cpp index e210bc3903..d363ddef11 100644 --- a/apps/openmw/mwphysics/heightfield.cpp +++ b/apps/openmw/mwphysics/heightfield.cpp @@ -1,6 +1,8 @@ #include "heightfield.hpp" #include "mtphysics.hpp" +#include + #include #include @@ -19,17 +21,17 @@ namespace { template - auto makeHeights(const T* heights, float sqrtVerts) + auto makeHeights(const T* heights, int verts) -> std::enable_if_t::value, std::vector> { return {}; } template - auto makeHeights(const T* heights, float sqrtVerts) + auto makeHeights(const T* heights, int verts) -> std::enable_if_t::value, std::vector> { - return std::vector(heights, heights + static_cast(sqrtVerts * sqrtVerts)); + return std::vector(heights, heights + static_cast(verts * verts)); } template @@ -50,16 +52,17 @@ namespace namespace MWPhysics { - HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) + HeightField::HeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, + const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) : mHoldObject(holdObject) #if BT_BULLET_VERSION < 310 - , mHeights(makeHeights(heights, sqrtVerts)) + , mHeights(makeHeights(heights, verts)) #endif , mTaskScheduler(scheduler) { #if BT_BULLET_VERSION < 310 mShape = std::make_unique( - sqrtVerts, sqrtVerts, + verts, verts, getHeights(heights, mHeights), 1, minH, maxH, 2, @@ -67,10 +70,12 @@ namespace MWPhysics ); #else mShape = std::make_unique( - sqrtVerts, sqrtVerts, heights, minH, maxH, 2, false); + verts, verts, heights, minH, maxH, 2, false); #endif mShape->setUseDiamondSubdivision(true); - mShape->setLocalScaling(btVector3(triSize, triSize, 1)); + + const float scaling = static_cast(size) / static_cast(verts - 1); + mShape->setLocalScaling(btVector3(scaling, scaling, 1)); #if BT_BULLET_VERSION >= 289 // Accelerates some collision tests. @@ -81,10 +86,8 @@ namespace MWPhysics mShape->buildAccelerator(); #endif - btTransform transform(btQuaternion::getIdentity(), - btVector3((x+0.5f) * triSize * (sqrtVerts-1), - (y+0.5f) * triSize * (sqrtVerts-1), - (maxH+minH)*0.5f)); + const btTransform transform(btQuaternion::getIdentity(), + BulletHelpers::getHeightfieldShift(x, y, size, minH, maxH)); mCollisionObject = std::make_unique(); mCollisionObject->setCollisionShape(mShape.get()); diff --git a/apps/openmw/mwphysics/heightfield.hpp b/apps/openmw/mwphysics/heightfield.hpp index 93b2733f31..c320225258 100644 --- a/apps/openmw/mwphysics/heightfield.hpp +++ b/apps/openmw/mwphysics/heightfield.hpp @@ -23,7 +23,8 @@ namespace MWPhysics class HeightField { public: - HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); + HeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, + const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); ~HeightField(); btCollisionObject* getCollisionObject(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 6b2600069a..5f7ee0523a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -472,9 +472,9 @@ namespace MWPhysics return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); } - void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) + void PhysicsSystem::addHeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, const osg::Object* holdObject) { - mHeightFields[std::make_pair(x,y)] = std::make_unique(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject, mTaskScheduler.get()); + mHeightFields[std::make_pair(x,y)] = std::make_unique(heights, x, y, size, verts, minH, maxH, holdObject, mTaskScheduler.get()); } void PhysicsSystem::removeHeightField (int x, int y) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index cf4ca40a54..30fdbcc122 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -150,7 +150,7 @@ namespace MWPhysics void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); void updatePosition (const MWWorld::Ptr& ptr); - void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); + void addHeightField(const float* heights, int x, int y, int size, int verts, float minH, float maxH, const osg::Object* holdObject); void removeHeightField (int x, int y); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index ed68a46059..b94a729faf 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -383,17 +383,17 @@ namespace MWWorld { osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - const float verts = ESM::Land::LAND_SIZE; - const float worldsize = ESM::Land::REAL_SIZE; + const int verts = ESM::Land::LAND_SIZE; + const int worldsize = ESM::Land::REAL_SIZE; if (data) { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + mPhysics->addHeightField(data->mHeights, cellX, cellY, worldsize, verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { static std::vector defaultHeight; defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cellX, cellY, worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + mPhysics->addHeightField(defaultHeight.data(), cellX, cellY, worldsize, verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) { diff --git a/components/bullethelpers/heightfield.hpp b/components/bullethelpers/heightfield.hpp new file mode 100644 index 0000000000..6b1f0ccc2e --- /dev/null +++ b/components/bullethelpers/heightfield.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_BULLETHELPERS_HEIGHTFIELD_H +#define OPENMW_COMPONENTS_BULLETHELPERS_HEIGHTFIELD_H + +#include + +namespace BulletHelpers +{ + inline btVector3 getHeightfieldShift(int x, int y, int size, float minHeight, float maxHeight) + { + return btVector3((x + 0.5f) * size, (y + 0.5f) * size, (maxHeight + minHeight) * 0.5f); + } +} + +#endif From 2bace703d55a06defb1bd133ac3266f3914b866d Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 11 Oct 2021 19:34:03 +0200 Subject: [PATCH 1463/2859] Add dependency to SQLite3 This will be required by navmeshtool. --- CI/before_script.android.sh | 1 + CI/before_script.linux.sh | 1 + CI/before_script.msvc.sh | 1 + CI/before_script.osx.sh | 1 + CI/install_debian_deps.sh | 2 +- CMakeLists.txt | 2 ++ extern/CMakeLists.txt | 20 ++++++++++++++++++++ 7 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CI/before_script.android.sh b/CI/before_script.android.sh index 3ea429f1bb..3219f3a4ba 100755 --- a/CI/before_script.android.sh +++ b/CI/before_script.android.sh @@ -24,4 +24,5 @@ cmake \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_OSG=OFF \ -DOPENMW_USE_SYSTEM_BULLET=OFF \ +-DOPENMW_USE_SYSTEM_SQLITE3=OFF \ .. diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 5d20fa75ce..b9fed204e3 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -40,6 +40,7 @@ if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then -DOPENMW_USE_SYSTEM_MYGUI=OFF -DOPENMW_USE_SYSTEM_OSG=OFF -DOPENMW_USE_SYSTEM_BULLET=OFF + -DOPENMW_USE_SYSTEM_SQLITE3=OFF ) fi diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 0a6123505e..1341289335 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1018,6 +1018,7 @@ echo echo "Setting up OpenMW build..." add_cmake_opts -DOPENMW_MP_BUILD=on add_cmake_opts -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" +add_cmake_opts -DOPENMW_USE_SYSTEM_SQLITE3=OFF if [ ! -z $CI ]; then case $STEP in components ) diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 265e05b8ee..6d0fe8c99e 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -19,6 +19,7 @@ cmake \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \ -D CMAKE_BUILD_TYPE=RELEASE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ +-D OPENMW_USE_SYSTEM_SQLITE3=OFF \ -D BUILD_OPENMW=TRUE \ -D BUILD_OPENCS=TRUE \ -D BUILD_ESMTOOL=TRUE \ diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 4b47c937da..a8843207d9 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -27,7 +27,7 @@ declare -rA GROUPED_DEPS=( # TODO: add librecastnavigation-dev when debian is ready # These dependencies can alternatively be built and linked statically. - [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" + [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev" [coverity]="curl" [clang-tidy]="clang-tidy" diff --git a/CMakeLists.txt b/CMakeLists.txt index 2564379847..0451b639e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,6 +149,8 @@ else() endif() option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_recastnavigation_static_default}) +option(OPENMW_USE_SYSTEM_SQLITE3 "Use system provided SQLite3 library" ON) + option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 6ed0bffa6b..59d3d15176 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -188,3 +188,23 @@ if(NOT OPENMW_USE_SYSTEM_RECASTNAVIGATION) ) FetchContent_MakeAvailableExcludeFromAll(recastnavigation) endif() + +if (NOT OPENMW_USE_SYSTEM_SQLITE3) + include(FetchContent) + FetchContent_Declare(sqlite3 + URL https://www.sqlite.org/2021/sqlite-amalgamation-3360000.zip + URL_HASH MD5=c5d360c74111bafae1b704721ff18fe6 + SOURCE_DIR fetched/sqlite3 + ) + FetchContent_MakeAvailableExcludeFromAll(sqlite3) + + add_library(sqlite3 STATIC ${sqlite3_SOURCE_DIR}/sqlite3.c) + target_include_directories(sqlite3 INTERFACE ${sqlite3_SOURCE_DIR}/) + if (UNIX) + target_link_libraries(sqlite3 ${CMAKE_DL_LIBS}) + endif() + add_library(SQLite::SQLite3 ALIAS sqlite3) + + set(SQLite3_INCLUDE_DIR ${sqlite3_SOURCE_DIR}/ PARENT_SCOPE) + set(SQLite3_LIBRARY sqlite3 PARENT_SCOPE) +endif() From 144862aa35333c3854bcf7bec135df71b9ab1984 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 29 Jun 2021 21:41:15 +0200 Subject: [PATCH 1464/2859] Define default actor half extents in settings Player's half extents may change over time when wolfskin model is used for example. Having it in settings is a more presistent approach. --- apps/openmw/mwworld/worldimp.cpp | 5 +++-- docs/source/reference/modding/settings/game.rst | 9 +++++++++ files/settings-default.cfg | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4fd9235586..b9ef635263 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -135,7 +135,9 @@ namespace MWWorld : mResourceSystem(resourceSystem), mLocalScripts (mStore), mCells (mStore, mEsm), mSky (true), mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles), - mUserDataPath(userDataPath), mShouldUpdateNavigator(false), + mUserDataPath(userDataPath), + mDefaultHalfExtents(Settings::Manager::getVector3("default actor pathfind half extents", "Game")), + mShouldUpdateNavigator(false), mActivationDistanceOverride (activationDistanceOverride), mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), @@ -2471,7 +2473,6 @@ namespace MWWorld applyLoopingParticles(player); - mDefaultHalfExtents = mPhysics->getOriginalHalfExtents(getPlayerPtr()); mNavigator->addAgent(getPathfindingHalfExtents(getPlayerConstPtr())); } diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 878485b3b3..fb7b537701 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -455,3 +455,12 @@ If disabled actors without the ability to swim will not follow other actors to t Has effect only when Navigator is enabled. This setting can be controlled in Advanced tab of the launcher. + +default actor pathfind half extents +----------------------------------- + +:Type: 3D vector floating point +:Range: All components > 0 +:Default: 29.27999496459961 28.479997634887695 66.5 + +Actor half extents used for exterior cells to generate navmesh. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 5ed8109465..27a9544ea6 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -373,6 +373,9 @@ graphic herbalism = true # (true, false) allow actors to follow over water surface = true +# Default size of actor for navmesh generation +default actor pathfind half extents = 29.27999496459961 28.479997634887695 66.5 + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). From fec65acd18bf18293fb3cef339c1ff49fc12f309 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 11 Oct 2021 21:33:35 +0200 Subject: [PATCH 1465/2859] Move getters to header in order to allow inlining --- apps/openmw/mwworld/cellref.cpp | 105 -------------------------------- apps/openmw/mwworld/cellref.hpp | 44 ++++++------- 2 files changed, 22 insertions(+), 127 deletions(-) diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 0b16964043..2f4702b1eb 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -8,11 +8,6 @@ namespace MWWorld { - const ESM::RefNum& CellRef::getRefNum() const - { - return mCellRef.mRefNum; - } - const ESM::RefNum& CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum) { if (!mCellRef.mRefNum.isSet()) @@ -33,41 +28,11 @@ namespace MWWorld return mCellRef.mRefNum; } - bool CellRef::hasContentFile() const - { - return mCellRef.mRefNum.hasContentFile(); - } - void CellRef::unsetRefNum() { mCellRef.mRefNum.unset(); } - std::string CellRef::getRefId() const - { - return mCellRef.mRefID; - } - - bool CellRef::getTeleport() const - { - return mCellRef.mTeleport; - } - - ESM::Position CellRef::getDoorDest() const - { - return mCellRef.mDoorDest; - } - - std::string CellRef::getDestCell() const - { - return mCellRef.mDestCell; - } - - float CellRef::getScale() const - { - return mCellRef.mScale; - } - void CellRef::setScale(float scale) { if (scale != mCellRef.mScale) @@ -77,22 +42,12 @@ namespace MWWorld } } - ESM::Position CellRef::getPosition() const - { - return mCellRef.mPos; - } - void CellRef::setPosition(const ESM::Position &position) { mChanged = true; mCellRef.mPos = position; } - float CellRef::getEnchantmentCharge() const - { - return mCellRef.mEnchantmentCharge; - } - float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const { if (maxCharge == 0) @@ -118,11 +73,6 @@ namespace MWWorld } } - int CellRef::getCharge() const - { - return mCellRef.mChargeInt; - } - void CellRef::setCharge(int charge) { if (charge != mCellRef.mChargeInt) @@ -150,11 +100,6 @@ namespace MWWorld } } - float CellRef::getChargeFloat() const - { - return mCellRef.mChargeFloat; - } - void CellRef::setChargeFloat(float charge) { if (charge != mCellRef.mChargeFloat) @@ -164,16 +109,6 @@ namespace MWWorld } } - std::string CellRef::getOwner() const - { - return mCellRef.mOwner; - } - - std::string CellRef::getGlobalVariable() const - { - return mCellRef.mGlobalVariable; - } - void CellRef::resetGlobalVariable() { if (!mCellRef.mGlobalVariable.empty()) @@ -192,11 +127,6 @@ namespace MWWorld } } - int CellRef::getFactionRank() const - { - return mCellRef.mFactionRank; - } - void CellRef::setOwner(const std::string &owner) { if (owner != mCellRef.mOwner) @@ -206,11 +136,6 @@ namespace MWWorld } } - std::string CellRef::getSoul() const - { - return mCellRef.mSoul; - } - void CellRef::setSoul(const std::string &soul) { if (soul != mCellRef.mSoul) @@ -220,11 +145,6 @@ namespace MWWorld } } - std::string CellRef::getFaction() const - { - return mCellRef.mFaction; - } - void CellRef::setFaction(const std::string &faction) { if (faction != mCellRef.mFaction) @@ -234,11 +154,6 @@ namespace MWWorld } } - int CellRef::getLockLevel() const - { - return mCellRef.mLockLevel; - } - void CellRef::setLockLevel(int lockLevel) { if (lockLevel != mCellRef.mLockLevel) @@ -261,16 +176,6 @@ namespace MWWorld setLockLevel(-abs(mCellRef.mLockLevel)); //Makes lockLevel negative } - std::string CellRef::getKey() const - { - return mCellRef.mKey; - } - - std::string CellRef::getTrap() const - { - return mCellRef.mTrap; - } - void CellRef::setTrap(const std::string& trap) { if (trap != mCellRef.mTrap) @@ -280,11 +185,6 @@ namespace MWWorld } } - int CellRef::getGoldValue() const - { - return mCellRef.mGoldValue; - } - void CellRef::setGoldValue(int value) { if (value != mCellRef.mGoldValue) @@ -299,9 +199,4 @@ namespace MWWorld state.mRef = mCellRef; } - bool CellRef::hasChanged() const - { - return mChanged; - } - } diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 78170a766f..b5e80930ed 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -23,7 +23,7 @@ namespace MWWorld } // Note: Currently unused for items in containers - const ESM::RefNum& getRefNum() const; + const ESM::RefNum& getRefNum() const { return mCellRef.mRefNum; } // Returns RefNum. // If RefNum is not set, assigns a generated one and changes the "lastAssignedRefNum" counter. @@ -33,35 +33,35 @@ namespace MWWorld void unsetRefNum(); /// Does the RefNum have a content file? - bool hasContentFile() const; + bool hasContentFile() const { return mCellRef.mRefNum.hasContentFile(); } // Id of object being referenced - std::string getRefId() const; + const std::string& getRefId() const { return mCellRef.mRefID; } // Reference to ID of the object being referenced - const std::string& getRefIdRef() const { return mCellRef.mRefID; } + const std::string& getRefIdRef() const { return mCellRef.mRefID; } // TODO replace with getRefId // For doors - true if this door teleports to somewhere else, false // if it should open through animation. - bool getTeleport() const; + bool getTeleport() const { return mCellRef.mTeleport; } // Teleport location for the door, if this is a teleporting door. - ESM::Position getDoorDest() const; + const ESM::Position& getDoorDest() const { return mCellRef.mDoorDest; } // Destination cell for doors (optional) - std::string getDestCell() const; + const std::string& getDestCell() const { return mCellRef.mDestCell; } // Scale applied to mesh - float getScale() const; + float getScale() const { return mCellRef.mScale; } void setScale(float scale); // The *original* position and rotation as it was given in the Construction Set. // Current position and rotation of the object is stored in RefData. - ESM::Position getPosition() const; + const ESM::Position& getPosition() const { return mCellRef.mPos; } void setPosition (const ESM::Position& position); // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). - float getEnchantmentCharge() const; + float getEnchantmentCharge() const { return mCellRef.mEnchantmentCharge; } // Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment). float getNormalizedEnchantmentCharge(int maxCharge) const; @@ -71,57 +71,57 @@ namespace MWWorld // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. // If this returns int(-1) it means full health. - int getCharge() const; - float getChargeFloat() const; // Implemented as union with int charge + int getCharge() const { return mCellRef.mChargeInt; } + float getChargeFloat() const { return mCellRef.mChargeFloat; } // Implemented as union with int charge void setCharge(int charge); void setChargeFloat(float charge); void applyChargeRemainderToBeSubtracted(float chargeRemainder); // Stores remainders and applies if > 1 // The NPC that owns this object (and will get angry if you steal it) - std::string getOwner() const; + const std::string& getOwner() const { return mCellRef.mOwner; } void setOwner(const std::string& owner); // Name of a global variable. If the global variable is set to '1', using the object is temporarily allowed // even if it has an Owner field. // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. - std::string getGlobalVariable() const; + const std::string& getGlobalVariable() const { return mCellRef.mGlobalVariable; } void resetGlobalVariable(); // ID of creature trapped in this soul gem - std::string getSoul() const; + const std::string& getSoul() const { return mCellRef.mSoul; } void setSoul(const std::string& soul); // The faction that owns this object (and will get angry if // you take it and are not a faction member) - std::string getFaction() const; + const std::string& getFaction() const { return mCellRef.mFaction; } void setFaction (const std::string& faction); // PC faction rank required to use the item. Sometimes is -1, which means "any rank". void setFactionRank(int factionRank); - int getFactionRank() const; + int getFactionRank() const { return mCellRef.mFactionRank; } // Lock level for doors and containers // Positive for a locked door. 0 for a door that was never locked. // For an unlocked door, it is set to -(previous locklevel) - int getLockLevel() const; + int getLockLevel() const { return mCellRef.mLockLevel; } void setLockLevel(int lockLevel); void lock(int lockLevel); void unlock(); // Key and trap ID names, if any - std::string getKey() const; - std::string getTrap() const; + const std::string& getKey() const { return mCellRef.mKey; } + const std::string& getTrap() const { return mCellRef.mTrap; } void setTrap(const std::string& trap); // This is 5 for Gold_005 references, 100 for Gold_100 and so on. - int getGoldValue() const; + int getGoldValue() const { return mCellRef.mGoldValue; } void setGoldValue(int value); // Write the content of this CellRef into the given ObjectState void writeState (ESM::ObjectState& state) const; // Has this CellRef changed since it was originally loaded? - bool hasChanged() const; + bool hasChanged() const { return mChanged; } private: bool mChanged; From ae4578f566b04f0786e1804c4ac0160a45effbcf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 11 Oct 2021 21:46:15 +0200 Subject: [PATCH 1466/2859] Fix soul trapping for one shot kills --- apps/openmw/mwmechanics/combat.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 994b2b015a..7ae26c82b8 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -48,6 +48,8 @@ namespace MWMechanics MWMechanics::CastSpell cast(attacker, victim, fromProjectile); cast.mHitPosition = hitPosition; cast.cast(object, false); + // Apply magic effects directly instead of waiting a frame to allow soul trap to work on one-hit kills + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim); return true; } } From c9279d413780096404f4785b99e6471c410c8e62 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 9 Oct 2021 17:54:51 +0200 Subject: [PATCH 1467/2859] Add a simple HOWTO for the builtin tracing functionality. --- scripts/HOWTO-benchmark.md | 112 +++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 scripts/HOWTO-benchmark.md diff --git a/scripts/HOWTO-benchmark.md b/scripts/HOWTO-benchmark.md new file mode 100644 index 0000000000..9641bfcd66 --- /dev/null +++ b/scripts/HOWTO-benchmark.md @@ -0,0 +1,112 @@ +OpenMW ships with its own benchmarking tool. This document describes how to collect performance data and vizualise it. + +Collecting data +=============== + +The data collected is the same as displayed by repeatedly pressing F3 and/or F4 while in the game (correspondance is shown below). +We can select what should be collected by setting the `OPENMW_OSG_STATS_LIST` environment variables to a semi-colon separated list of metrics to collect. There can be any combination and order doesn't matter. Note that data collection can **significantly reduce** performance + +| Metric to collect | Equivalent in-game profiler | +|-------------------|-------------------------------------------------------------------| +| `frame_rate` | Frame rate | +| `engine` | OpenMW specifics metric (white bars) | +| `event` | Event traversal bar | +| `update` | Update traversal bar | +| `rendering` | Draw bar | +| `gpu` | GPU bar | +| `times` | Alias for `event;update;rendering;engine;gpu`, that is all graphs | +| `cameraobjects` | Table shown when pressing F3 3 times | +| `viewerobjects` | Table shown when pressing F3 4 times | +| `resource` | Table shown when pressing F4 | + +It is necessary to write the collected metrics to a file which needs to be defined by the `OPENMW_OSG_STATS_FILE` environment variable. + +Example +------- + +Posix shell +```sh +OPENMW_OSG_STATS_FILE=/tmp/stats OPENMW_OSG_STATS_LIST="resource;engine" /usr/local/bin/openmw +``` + +Windows PowerShell +```powershell +$env:OPENMW_OSG_STATS_FILE="c:\stats" +$env:OPENMW_OSG_STATS_LIST="resource;engine" +openmw +``` + + +Analyzing results +================= + +`scripts/osg_stats.py` is a python script that can perform statistical analysis and generate graphs from a trace. It doesn't need to be run on the same machine as the trace was taken. + +`osg_stats.py --help` list the available options. + +Examples +-------- + +### Print the available metrics in a given trace file + +`osg_stats.py --print_keys /tmp/stats` + +Sample output +>>> +gui_time_begin +gui_time_end +gui_time_taken +input_time_begin +input_time_end +input_time_taken +lua_time_begin +lua_time_end +lua_time_taken +mechanics_time_begin +mechanics_time_end +mechanics_time_taken +physics_time_begin +physics_time_end +physics_time_taken +physicsworker_time_begin +physicsworker_time_end +physicsworker_time_taken +script_time_begin +script_time_end +script_time_taken +sound_time_begin +sound_time_end +sound_time_taken +state_time_begin +state_time_end +state_time_taken +world_time_begin +world_time_end +world_time_taken +>>> + +### Print a table with statistics data for some metrics + +`osg_stats.py --stats physicsworker_time_taken --stats mechanics_time_taken --stats world_time_taken --stats physics_time_taken /tmp/stats` + +Sample output + +> | source | key | number | min | max | mean | median | stdev | q95 | +> |------------|--------------------------|--------|-----|----------|------------------------|----------|------------------------|----------------------| +> | /tmp/stats | physicsworker_time_taken | 56245 | 0.0 | 0.01526 | 0.0018826463330073784 | 7.7e-05 | 0.003210274653689913 | 0.009509799999999995 | +> | /tmp/stats | mechanics_time_taken | 56245 | 0.0 | 0.015808 | 0.0030202102942483776 | 0.002177 | 0.002565895193458489 | 0.008489799999999995 | +> | /tmp/stats | world_time_taken | 56245 | 0.0 | 0.003643 | 5.230777846919726e-05 | 5.1e-05 | 1.7322475294417906e-05 | 6.6e-05 | +> | /tmp/stats | physics_time_taken | 56245 | 0.0 | 0.004407 | 0.00019985403146946396 | 0.000129 | 0.0002106166003676915 | 0.000697 | + + +### Plot a timeserie of aforementioned metrics, ignoring first 1000 frames + +`osg_stats.py --begin_frame 1000 --end_frame 1200 --timeseries physicsworker_time_taken --timeseries mechanics_time_taken --timeseries world_time_taken --timeseries physics_time_taken /tmp/stats` + +### Plot time spent in physics and mechanics depending on number of actors in the scene + +`osg_stats.py --plot 'Physics Actors' physicsworker_time_taken mean --plot 'Physics Actors' mechanics_time_taken mean --plot 'Physics Actors' physics_time_taken mean /tmp/stats` + +### Plot timeserie from 2 traces + +`osg_stats.py --timeseries 'Frame Duration' /tmp/shadowson /tmp/shadowsoff` From 405e81419073218c81664a6bfb5d5cab22775f65 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 28 Jun 2021 23:35:34 +0200 Subject: [PATCH 1468/2859] Move btCollisionObject creation for MWPhysics::Object into components --- apps/openmw/mwphysics/object.cpp | 19 +++++++-------- apps/openmw/mwworld/scene.cpp | 15 ++---------- components/bullethelpers/collisionobject.hpp | 24 +++++++++++++++++++ components/misc/convert.hpp | 25 ++++++++++++++++++++ 4 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 components/bullethelpers/collisionobject.hpp diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index a95672f8cf..879c12124e 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -15,22 +16,18 @@ namespace MWPhysics { Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) - : mShapeInstance(shapeInstance) + : mShapeInstance(std::move(shapeInstance)) , mSolid(true) + , mScale(ptr.getCellRef().getScale(), ptr.getCellRef().getScale(), ptr.getCellRef().getScale()) + , mPosition(ptr.getRefData().getPosition().asVec3()) + , mRotation(rotation) , mTaskScheduler(scheduler) { mPtr = ptr; - - mCollisionObject = std::make_unique(); - mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); - + mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->getCollisionShape(), + Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation)); mCollisionObject->setUserPointer(this); - - setScale(ptr.getCellRef().getScale()); - setRotation(rotation); - updatePosition(); - commitPositionChange(); - + mShapeInstance->setLocalScaling(mScale); mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index ed68a46059..bb0f7437bd 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -65,23 +65,12 @@ namespace * osg::Quat(zr, osg::Vec3(0, 0, -1)); } - osg::Quat makeObjectOsgQuat(const ESM::Position& position) - { - const float xr = position.rot[0]; - const float yr = position.rot[1]; - const float zr = position.rot[2]; - - return osg::Quat(zr, osg::Vec3(0, 0, -1)) - * osg::Quat(yr, osg::Vec3(0, -1, 0)) - * osg::Quat(xr, osg::Vec3(-1, 0, 0)); - } - osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) { const auto pos = ptr.getRefData().getPosition(); const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) - : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos)); + : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos)); return rot; } @@ -155,7 +144,7 @@ namespace const auto transform = object->getTransform(); const btTransform closedDoorTransform( - Misc::Convert::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())), + Misc::Convert::makeBulletQuaternion(ptr.getCellRef().getPosition()), transform.getOrigin() ); diff --git a/components/bullethelpers/collisionobject.hpp b/components/bullethelpers/collisionobject.hpp new file mode 100644 index 0000000000..0951c2af38 --- /dev/null +++ b/components/bullethelpers/collisionobject.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_COMPONENTS_BULLETHELPERS_COLLISIONOBJECT_H +#define OPENMW_COMPONENTS_BULLETHELPERS_COLLISIONOBJECT_H + +#include + +#include +#include +#include + +#include + +namespace BulletHelpers +{ + inline std::unique_ptr makeCollisionObject(btCollisionShape* shape, + const btVector3& position, const btQuaternion& rotation) + { + std::unique_ptr result = std::make_unique(); + result->setCollisionShape(shape); + result->setWorldTransform(btTransform(rotation, position)); + return result; + } +} + +#endif diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index c5784d33ae..81270c0c0b 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_MISC_CONVERT_H #define OPENMW_COMPONENTS_MISC_CONVERT_H +#include #include #include @@ -47,6 +48,30 @@ namespace Convert { return osg::Quat(quat.x(), quat.y(), quat.z(), quat.w()); } + + inline osg::Quat makeOsgQuat(const float (&rotation)[3]) + { + return osg::Quat(rotation[2], osg::Vec3f(0, 0, -1)) + * osg::Quat(rotation[1], osg::Vec3f(0, -1, 0)) + * osg::Quat(rotation[0], osg::Vec3f(-1, 0, 0)); + } + + inline osg::Quat makeOsgQuat(const ESM::Position& position) + { + return makeOsgQuat(position.rot); + } + + inline btQuaternion makeBulletQuaternion(const float (&rotation)[3]) + { + return btQuaternion(btVector3(0, 0, -1), rotation[2]) + * btQuaternion(btVector3(0, -1, 0), rotation[1]) + * btQuaternion(btVector3(-1, 0, 0), rotation[0]); + } + + inline btQuaternion makeBulletQuaternion(const ESM::Position& position) + { + return makeBulletQuaternion(position.rot); + } } } From a8acc19988e3cb9095669e15846cf276f6fd9d69 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 12 Oct 2021 00:18:23 +0200 Subject: [PATCH 1469/2859] Avoid copying std::string in MWWorld::Ptr::getTypeDescription() --- apps/opencs/model/tools/topicinfocheck.cpp | 4 ++-- apps/openmw/mwworld/containerstore.cpp | 4 ++-- apps/openmw/mwworld/livecellref.hpp | 4 ++-- apps/openmw/mwworld/ptr.hpp | 4 ++-- apps/openmw/mwworld/store.cpp | 10 ++++++---- components/esm/loadacti.hpp | 2 +- components/esm/loadalch.hpp | 2 +- components/esm/loadappa.hpp | 2 +- components/esm/loadarmo.hpp | 2 +- components/esm/loadbody.hpp | 2 +- components/esm/loadbook.hpp | 2 +- components/esm/loadbsgn.hpp | 2 +- components/esm/loadcell.hpp | 2 +- components/esm/loadclas.hpp | 2 +- components/esm/loadclot.hpp | 2 +- components/esm/loadcont.hpp | 2 +- components/esm/loadcrea.hpp | 2 +- components/esm/loaddial.hpp | 2 +- components/esm/loaddoor.hpp | 2 +- components/esm/loadench.hpp | 2 +- components/esm/loadfact.hpp | 2 +- components/esm/loadglob.hpp | 2 +- components/esm/loadgmst.hpp | 2 +- components/esm/loadinfo.hpp | 2 +- components/esm/loadingr.hpp | 2 +- components/esm/loadland.hpp | 2 +- components/esm/loadlevlist.hpp | 4 ++-- components/esm/loadligh.hpp | 2 +- components/esm/loadlock.hpp | 2 +- components/esm/loadltex.hpp | 2 +- components/esm/loadmgef.hpp | 2 +- components/esm/loadmisc.hpp | 2 +- components/esm/loadnpc.hpp | 2 +- components/esm/loadpgrd.hpp | 2 +- components/esm/loadprob.hpp | 2 +- components/esm/loadrace.hpp | 2 +- components/esm/loadregn.hpp | 2 +- components/esm/loadrepa.hpp | 2 +- components/esm/loadscpt.hpp | 2 +- components/esm/loadskil.hpp | 2 +- components/esm/loadsndg.hpp | 2 +- components/esm/loadsoun.hpp | 2 +- components/esm/loadspel.hpp | 2 +- components/esm/loadsscr.hpp | 2 +- components/esm/loadstat.hpp | 2 +- components/esm/loadweap.hpp | 2 +- 46 files changed, 56 insertions(+), 54 deletions(-) diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp index fe9bc991d4..74643a46ee 100644 --- a/apps/opencs/model/tools/topicinfocheck.cpp +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -395,12 +395,12 @@ bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMW if (index == -1) { - messages.add(id, T::getRecordType() + " '" + name + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, std::string(T::getRecordType()) + " '" + name + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (collection.getRecord(index).isDeleted()) { - messages.add(id, "Deleted " + T::getRecordType() + " record '" + name + "' is being referenced", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Deleted " + std::string(T::getRecordType()) + " record '" + name + "' is being referenced", "", CSMDoc::Message::Severity_Error); return false; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 58f215932e..112f56abfc 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -728,8 +728,8 @@ int MWWorld::ContainerStore::getType (const ConstPtr& ptr) if (ptr.getType()==ESM::Weapon::sRecordId) return Type_Weapon; - throw std::runtime_error ( - "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeDescription() + " can not be placed into a container"); + throw std::runtime_error("Object '" + ptr.getCellRef().getRefId() + "' of type " + + std::string(ptr.getTypeDescription()) + " can not be placed into a container"); } MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index af0f84dc8d..48e237bce4 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -41,7 +41,7 @@ namespace MWWorld virtual void save (ESM::ObjectState& state) const = 0; ///< Save LiveCellRef state into \a state. - virtual std::string getTypeDescription() const { return ""; } + virtual std::string_view getTypeDescription() const = 0; protected: @@ -95,7 +95,7 @@ namespace MWWorld void save (ESM::ObjectState& state) const override; ///< Save LiveCellRef state into \a state. - std::string getTypeDescription() const override { return X::getRecordType(); } + std::string_view getTypeDescription() const override { return X::getRecordType(); } static bool checkState (const ESM::ObjectState& state); ///< Check if state is valid and report errors. diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 6813b7d124..611eeefe16 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -37,7 +37,7 @@ namespace MWWorld unsigned int getType() const; - std::string getTypeDescription() const + std::string_view getTypeDescription() const { return mRef ? mRef->getTypeDescription() : "nullptr"; } @@ -118,7 +118,7 @@ namespace MWWorld unsigned int getType() const; - std::string getTypeDescription() const + std::string_view getTypeDescription() const { return mRef ? mRef->getTypeDescription() : "nullptr"; } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 718cebd790..963e3b78cd 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -64,8 +64,9 @@ namespace MWWorld const T *ptr = search(index); if (ptr == nullptr) { - const std::string msg = T::getRecordType() + " with index " + std::to_string(index) + " not found"; - throw std::runtime_error(msg); + std::stringstream msg; + msg << T::getRecordType() << " with index " << index << " not found"; + throw std::runtime_error(msg.str()); } return ptr; } @@ -145,8 +146,9 @@ namespace MWWorld const T *ptr = search(id); if (ptr == nullptr) { - const std::string msg = T::getRecordType() + " '" + id + "' not found"; - throw std::runtime_error(msg); + std::stringstream msg; + msg << T::getRecordType() << " '" << id << "' not found"; + throw std::runtime_error(msg.str()); } return ptr; } diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index 5b88ad379f..c0cf274ed9 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -13,7 +13,7 @@ struct Activator { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Activator"; } + static std::string_view getRecordType() { return "Activator"; } unsigned int mRecordFlags; std::string mId, mName, mScript, mModel; diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index d573531134..e032464abe 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -20,7 +20,7 @@ struct Potion static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Potion"; } + static std::string_view getRecordType() { return "Potion"; } struct ALDTstruct { diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index fcd9100be4..026a471f5b 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -17,7 +17,7 @@ struct Apparatus { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Apparatus"; } + static std::string_view getRecordType() { return "Apparatus"; } enum AppaType { diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 195230fbf2..f11e3509d5 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -67,7 +67,7 @@ struct Armor { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Armor"; } + static std::string_view getRecordType() { return "Armor"; } enum Type { diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index 1be775ffec..145fe4b6f6 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -13,7 +13,7 @@ struct BodyPart { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "BodyPart"; } + static std::string_view getRecordType() { return "BodyPart"; } enum MeshPart { diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 60b03f7a36..e46bec6272 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -16,7 +16,7 @@ struct Book { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Book"; } + static std::string_view getRecordType() { return "Book"; } struct BKDTstruct { diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 806323bf35..a199503a76 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -15,7 +15,7 @@ struct BirthSign { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "BirthSign"; } + static std::string_view getRecordType() { return "BirthSign"; } unsigned int mRecordFlags; std::string mId, mName, mDescription, mTexture; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 18e929e13b..c2a694b744 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -65,7 +65,7 @@ struct Cell { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Cell"; } + static std::string_view getRecordType() { return "Cell"; } enum Flags { diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 1000879c4c..e1e8b2ff0f 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -19,7 +19,7 @@ struct Class { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Class"; } + static std::string_view getRecordType() { return "Class"; } enum AutoCalc { diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index bbc8449a95..26e82254ab 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -19,7 +19,7 @@ struct Clothing { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Clothing"; } + static std::string_view getRecordType() { return "Clothing"; } enum Type { diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index ade9004214..eac791e3f3 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -37,7 +37,7 @@ struct Container { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Container"; } + static std::string_view getRecordType() { return "Container"; } enum Flags { diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index f6b188d96e..9d664d440e 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -23,7 +23,7 @@ struct Creature { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Creature"; } + static std::string_view getRecordType() { return "Creature"; } // Default is 0x48? enum Flags diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index b80cbd74c3..7adc8b1cf8 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -22,7 +22,7 @@ struct Dialogue { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Dialogue"; } + static std::string_view getRecordType() { return "Dialogue"; } enum Type { diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 167eda20f5..84dbcbfa56 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -13,7 +13,7 @@ struct Door { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Door"; } + static std::string_view getRecordType() { return "Door"; } unsigned int mRecordFlags; std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index a4e1e8362c..3f094f754c 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -19,7 +19,7 @@ struct Enchantment { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Enchantment"; } + static std::string_view getRecordType() { return "Enchantment"; } enum Type { diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 6a42377901..da01c004e9 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -32,7 +32,7 @@ struct Faction { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Faction"; } + static std::string_view getRecordType() { return "Faction"; } unsigned int mRecordFlags; std::string mId, mName; diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 9dd58e6c67..7fcbe4b8e6 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -19,7 +19,7 @@ struct Global { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Global"; } + static std::string_view getRecordType() { return "Global"; } unsigned int mRecordFlags; std::string mId; diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index 931ee286a4..a3981736f1 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -20,7 +20,7 @@ struct GameSetting { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "GameSetting"; } + static std::string_view getRecordType() { return "GameSetting"; } unsigned int mRecordFlags; std::string mId; diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 2fbc782ec9..d68301c91b 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -22,7 +22,7 @@ struct DialInfo { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "DialInfo"; } + static std::string_view getRecordType() { return "DialInfo"; } enum Gender { diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 34e05da2c1..2ee572123d 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -17,7 +17,7 @@ struct Ingredient { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Ingredient"; } + static std::string_view getRecordType() { return "Ingredient"; } struct IRDTstruct { diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 9cba41b160..67dd2e76a2 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -21,7 +21,7 @@ struct Land { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Land"; } + static std::string_view getRecordType() { return "Land"; } Land(); ~Land(); diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index d3451b70ae..0ebd7a64cb 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -48,7 +48,7 @@ struct CreatureLevList: LevelledListBase { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "CreatureLevList"; } + static std::string_view getRecordType() { return "CreatureLevList"; } enum Flags { @@ -68,7 +68,7 @@ struct ItemLevList: LevelledListBase { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "ItemLevList"; } + static std::string_view getRecordType() { return "ItemLevList"; } enum Flags { diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 901fe6ec85..9bd608e4d9 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -18,7 +18,7 @@ struct Light { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Light"; } + static std::string_view getRecordType() { return "Light"; } enum Flags { diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index 9cd40c0d4f..4f8a3575fc 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -13,7 +13,7 @@ struct Lockpick { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Lockpick"; } + static std::string_view getRecordType() { return "Lockpick"; } struct Data { diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index e3e2582462..b2e937c01a 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -20,7 +20,7 @@ struct LandTexture { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "LandTexture"; } + static std::string_view getRecordType() { return "LandTexture"; } // mId is merely a user friendly name for the texture in the editor. std::string mId, mTexture; diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index 480478d81e..00cadf99c0 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -14,7 +14,7 @@ struct MagicEffect { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "MagicEffect"; } + static std::string_view getRecordType() { return "MagicEffect"; } unsigned int mRecordFlags; std::string mId; diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 72aaa5de3b..a0f46349a8 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -18,7 +18,7 @@ struct Miscellaneous { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Miscellaneous"; } + static std::string_view getRecordType() { return "Miscellaneous"; } struct MCDTstruct { diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index ba9f415760..f0354cb603 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -24,7 +24,7 @@ struct NPC { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "NPC"; } + static std::string_view getRecordType() { return "NPC"; } // Services enum Services diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index 4e74c9a24d..02ce231fe3 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -17,7 +17,7 @@ struct Pathgrid { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Pathgrid"; } + static std::string_view getRecordType() { return "Pathgrid"; } struct DATAstruct { diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index 930e31a971..583aa24523 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -13,7 +13,7 @@ struct Probe { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Probe"; } + static std::string_view getRecordType() { return "Probe"; } struct Data { diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index 50fa73ad74..ba42261e73 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -19,7 +19,7 @@ struct Race { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Race"; } + static std::string_view getRecordType() { return "Race"; } struct SkillBonus { diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 74f6b123ec..64991c9b3a 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -20,7 +20,7 @@ struct Region { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Region"; } + static std::string_view getRecordType() { return "Region"; } #pragma pack(push) #pragma pack(1) diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index 312c87b7b4..c3b2a076af 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -13,7 +13,7 @@ struct Repair { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Repair"; } + static std::string_view getRecordType() { return "Repair"; } struct Data { diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index d518a048ff..f737698184 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -21,7 +21,7 @@ class Script public: static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Script"; } + static std::string_view getRecordType() { return "Script"; } struct SCHDstruct { diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index ae44a51045..404ef06692 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -20,7 +20,7 @@ struct Skill { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Skill"; } + static std::string_view getRecordType() { return "Skill"; } unsigned int mRecordFlags; std::string mId; diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 99aae06e0e..565a29b156 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -17,7 +17,7 @@ struct SoundGenerator { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "SoundGenerator"; } + static std::string_view getRecordType() { return "SoundGenerator"; } enum Type { diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index 14f1178650..4149a34b7d 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -18,7 +18,7 @@ struct Sound { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Sound"; } + static std::string_view getRecordType() { return "Sound"; } SOUNstruct mData; unsigned int mRecordFlags; diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index ef74c2c312..6a2d02d286 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -15,7 +15,7 @@ struct Spell { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Spell"; } + static std::string_view getRecordType() { return "Spell"; } enum SpellType { diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index 3e84027076..277c4eb731 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -21,7 +21,7 @@ struct StartScript { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "StartScript"; } + static std::string_view getRecordType() { return "StartScript"; } std::string mData; unsigned int mRecordFlags; diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index 038fb9f52b..26d8fda3a8 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -24,7 +24,7 @@ struct Static { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Static"; } + static std::string_view getRecordType() { return "Static"; } unsigned int mRecordFlags; std::string mId, mModel; diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index 8d1591854b..98ba57b8b8 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -19,7 +19,7 @@ struct Weapon { static unsigned int sRecordId; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. - static std::string getRecordType() { return "Weapon"; } + static std::string_view getRecordType() { return "Weapon"; } enum Type { From b0132be53fcdb93da7f0b1e37b20e9931dd5801a Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 12 Oct 2021 11:47:42 +0000 Subject: [PATCH 1470/2859] do not dirty unchanged arrays (#3164) This PR proposes a simple change to `RigGeometry` `dirtyGLObjects` logic. 1. We will avoid dirtying unmodified arrays. 2. We can drop an osg version guard since `Drawable::dirtyGLObjects` is not nearly as harmful as `Geometry::dirtyGLObjects`. 3. We will avoid crashes in an as yet unfinished future PR concerning `Array` sharing improvements. --- components/sceneutil/morphgeometry.cpp | 6 +----- components/sceneutil/riggeometry.cpp | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 04fd6fb365..355403b066 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -2,8 +2,6 @@ #include -#include - namespace SceneUtil { @@ -178,9 +176,7 @@ void MorphGeometry::cull(osg::NodeVisitor *nv) positionDst->dirty(); -#if OSG_MIN_VERSION_REQUIRED(3, 5, 6) - geom.dirtyGLObjects(); -#endif + geom.osg::Drawable::dirtyGLObjects(); nv->pushOntoNodePath(&geom); nv->apply(geom); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 2c1efb9f36..2ca90d2c78 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -1,7 +1,5 @@ #include "riggeometry.hpp" -#include - #include #include @@ -249,9 +247,7 @@ void RigGeometry::cull(osg::NodeVisitor* nv) if (tangentDst) tangentDst->dirty(); -#if OSG_MIN_VERSION_REQUIRED(3, 5, 6) - geom.dirtyGLObjects(); -#endif + geom.osg::Drawable::dirtyGLObjects(); nv->pushOntoNodePath(&geom); nv->apply(geom); From 4e9d691715ccf40c73f396baba194a2438c1017c Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 12 Oct 2021 21:25:34 +0200 Subject: [PATCH 1471/2859] Add comment for MWWorld::Ptr::getType() because it is not obvious. --- apps/openmw/mwworld/ptr.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 6813b7d124..3474f0c79d 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -35,6 +35,11 @@ namespace MWWorld return mRef == nullptr; } + // Returns a 32-bit id of the ESM record this object is based on. + // Specific values of ids are defined in ESM::RecNameInts. + // Note 1: ids are not sequential. E.g. for a creature `getType` returns 0x41455243. + // Note 2: Life is not easy and full of surprises. For example + // prison marker reuses ESM::Door record. Player is ESM::NPC. unsigned int getType() const; std::string getTypeDescription() const From fc9ab9795012b7f2f59d5c323b7bf28a53d1ca50 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 13 Oct 2021 10:08:28 +0000 Subject: [PATCH 1472/2859] sky.cpp --- apps/openmw/mwrender/sky.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index dd62d6678b..ebf901b5ab 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -299,7 +299,7 @@ public: osg::BoundingSphere computeBound() const override { - return osg::BoundingSphere(osg::Vec3f(0,0,0), 0); + return osg::BoundingSphere(); } class CullCallback : public SceneUtil::NodeCallback From 617eec338a9cc3813309a4c1c78cad0f1ab7680b Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 13 Oct 2021 14:12:47 +0000 Subject: [PATCH 1473/2859] removes version guard (#3173) We currently use a version guard to adapt to a change in the number of parameters supplied to osg::TriangleFunctor's operator() template functor. The differing parameter is unused in our code. Crucially, operator() is not an override, so we can just add a default value for the differing parameter. Such a default allows us to apply identical code to both versions of the library without regressing functionality. --- components/resource/bulletshapemanager.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 0d0f81962b..5b6dce067c 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -45,11 +44,7 @@ struct GetTriangleFunctor return btVector3(vec.x(), vec.y(), vec.z()); } -#if OSG_MIN_VERSION_REQUIRED(3,5,6) - void inline operator()( const osg::Vec3& v1, const osg::Vec3& v2, const osg::Vec3& v3 ) -#else - void inline operator()( const osg::Vec3& v1, const osg::Vec3& v2, const osg::Vec3& v3, bool _temp ) -#endif + void inline operator()( const osg::Vec3& v1, const osg::Vec3& v2, const osg::Vec3& v3, bool _temp=false ) // Note: unused temp argument left here for OSG versions less than 3.5.6 { if (mTriMesh) mTriMesh->addTriangle( toBullet(mMatrix.preMult(v1)), toBullet(mMatrix.preMult(v2)), toBullet(mMatrix.preMult(v3))); From 42bd10f3560902387db13f71501db29e1200b946 Mon Sep 17 00:00:00 2001 From: Dan Church Date: Wed, 13 Oct 2021 13:46:08 -0500 Subject: [PATCH 1474/2859] Fix FTBFS against older versions of osg Bring back osg version guard. Drawable::dirtyGLObjects wasn't available until OpenSceneGraph-3.5.10. --- components/sceneutil/morphgeometry.cpp | 4 ++++ components/sceneutil/riggeometry.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 355403b066..78be559989 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -2,6 +2,8 @@ #include +#include + namespace SceneUtil { @@ -176,7 +178,9 @@ void MorphGeometry::cull(osg::NodeVisitor *nv) positionDst->dirty(); +#if OSG_MIN_VERSION_REQUIRED(3, 5, 10) geom.osg::Drawable::dirtyGLObjects(); +#endif nv->pushOntoNodePath(&geom); nv->apply(geom); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 2ca90d2c78..ec00efa535 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -1,5 +1,7 @@ #include "riggeometry.hpp" +#include + #include #include @@ -247,7 +249,9 @@ void RigGeometry::cull(osg::NodeVisitor* nv) if (tangentDst) tangentDst->dirty(); +#if OSG_MIN_VERSION_REQUIRED(3, 5, 10) geom.osg::Drawable::dirtyGLObjects(); +#endif nv->pushOntoNodePath(&geom); nv->apply(geom); From 4a6464ea676de573fe9b189931f2a403aea88eca Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 13 Oct 2021 22:40:07 +0200 Subject: [PATCH 1475/2859] Stop trying to play non-existent music files started by scripts --- apps/openmw/mwsound/soundmanagerimp.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index d2422870d7..96bfc27951 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -224,7 +224,16 @@ namespace MWSound stopMusic(); DecoderPtr decoder = getDecoder(); - decoder->open(filename); + try + { + decoder->open(filename); + } + catch(std::exception &e) + { + Log(Debug::Error) << "Failed to load audio from " << filename << ": " << e.what(); + return; + } + mMusic = getStreamRef(); mMusic->init([&] { From db3d938ee95a79387b68fd70c7bc69d7bf42d231 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 14 Oct 2021 12:46:44 +0000 Subject: [PATCH 1476/2859] cleans up BSAFile (#3177) We currently build a large map of a BSAFile's contents unused by Open MW. We already map archive contents in VFS. With this PR we remove the map from BSAFile and reimplement its only current use in BSATool. --- apps/bsatool/bsatool.cpp | 18 ++++++++++----- components/bsa/bsa_file.cpp | 34 ---------------------------- components/bsa/bsa_file.hpp | 31 ------------------------- components/bsa/compressedbsafile.cpp | 2 +- components/bsa/compressedbsafile.hpp | 4 +++- components/vfs/bsaarchive.cpp | 1 + 6 files changed, 17 insertions(+), 73 deletions(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 8e8cf89186..4057d627c4 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -233,7 +233,17 @@ int extract(std::unique_ptr& bsa, Arguments& info) std::string extractPath = info.extractfile; Misc::StringUtils::replaceAll(extractPath, "\\", "/"); - if (!bsa->exists(archivePath.c_str())) + Files::IStreamPtr stream; + // Get a stream for the file to extract + for (auto it = bsa->getList().rbegin(); it != bsa->getList().rend(); ++it) + { + if (Misc::StringUtils::ciEqual(std::string(it->name()), archivePath)) + { + stream = bsa->getFile(&*it); + break; + } + } + if (!stream) { std::cout << "ERROR: file '" << archivePath << "' not found\n"; std::cout << "In archive: " << info.filename << std::endl; @@ -260,9 +270,6 @@ int extract(std::unique_ptr& bsa, Arguments& info) return 3; } - // Get a stream for the file to extract - Files::IStreamPtr stream = bsa->getFile(archivePath.c_str()); - bfs::ofstream out(target, std::ios::binary); // Write the file to disk @@ -296,8 +303,7 @@ int extractAll(std::unique_ptr& bsa, Arguments& info) } // Get a stream for the file to extract - // (inefficient because getFile iter on the list again) - Files::IStreamPtr data = bsa->getFile(file.name()); + Files::IStreamPtr data = bsa->getFile(&file); bfs::ofstream out(target, std::ios::binary); // Write the file to disk diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index d5610bddae..e5b730b4c4 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -189,13 +189,6 @@ void BSAFile::readHeader() return left.offset < right.offset; }); - for (size_t i = 0; i < filenum; i++) - { - FileStruct& fs = mFiles[i]; - // Add the file name to the lookup - mLookup[fs.name()] = i; - } - mIsLoaded = true; } @@ -237,18 +230,6 @@ void Bsa::BSAFile::writeHeader() output.write(reinterpret_cast(hashes.data()), sizeof(Hash)*hashes.size()); } -/// Get the index of a given file name, or -1 if not found -int BSAFile::getIndex(const char *str) const -{ - auto it = mLookup.find(str); - if(it == mLookup.end()) - return -1; - - size_t res = it->second; - assert(res < mFiles.size()); - return static_cast(res); -} - /// Open an archive file. void BSAFile::open(const std::string &file) { @@ -274,22 +255,9 @@ void Bsa::BSAFile::close() mFiles.clear(); mStringBuf.clear(); - mLookup.clear(); mIsLoaded = false; } -Files::IStreamPtr BSAFile::getFile(const char *file) -{ - assert(file); - int i = getIndex(file); - if(i == -1) - fail("File not found: " + std::string(file)); - - const FileStruct &fs = mFiles[i]; - - return Files::openConstrainedFileStream (mFilename.c_str (), fs.offset, fs.fileSize); -} - void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) { if (!mIsLoaded) @@ -338,8 +306,6 @@ void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) mHasChanged = true; - mLookup[filename.c_str()] = mFiles.size() - 1; - stream.seekp(0, std::ios::end); file.seekg(0, std::ios::beg); stream << file.rdbuf(); diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 9a76e1bd23..f6c5d03139 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -27,9 +27,6 @@ #include #include #include -#include - -#include #include @@ -91,20 +88,6 @@ protected: /// Used for error messages std::string mFilename; - /// Case insensitive string comparison - struct iltstr - { - bool operator()(const std::string& s1, const std::string& s2) const - { return Misc::StringUtils::ciLess(s1, s2); } - }; - - /** A map used for fast file name lookup. The value is the index into - the files[] vector above. The iltstr ensures that file name - checks are case insensitive. - */ - typedef std::map Lookup; - Lookup mLookup; - /// Error handling [[noreturn]] void fail(const std::string &msg); @@ -112,10 +95,6 @@ protected: virtual void readHeader(); virtual void writeHeader(); - /// Get the index of a given file name, or -1 if not found - /// @note Thread safe. - int getIndex(const char *str) const; - public: /* ----------------------------------- * BSA management methods @@ -141,16 +120,6 @@ public: * ----------------------------------- */ - /// Check if a file exists - virtual bool exists(const char *file) const - { return getIndex(file) != -1; } - - /** Open a file contained in the archive. Throws an exception if the - file doesn't exist. - * @note Thread safe. - */ - virtual Files::IStreamPtr getFile(const char *file); - /** Open a file contained in the archive. * @note Thread safe. */ diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index f066489184..ae8209a39a 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -47,6 +47,7 @@ #include #include +#include namespace Bsa { @@ -286,7 +287,6 @@ void CompressedBSAFile::readHeader() mFiles[fileIndex].setNameInfos(mStringBuffOffset, &mStringBuf); - mLookup[reinterpret_cast(mStringBuf.data() + mStringBuffOffset)] = fileIndex; mStringBuffOffset += stringLength + 1u; } diff --git a/components/bsa/compressedbsafile.hpp b/components/bsa/compressedbsafile.hpp index ffd88fe044..f19815dcf3 100644 --- a/components/bsa/compressedbsafile.hpp +++ b/components/bsa/compressedbsafile.hpp @@ -26,6 +26,8 @@ #ifndef BSA_COMPRESSED_BSA_FILE_H #define BSA_COMPRESSED_BSA_FILE_H +#include + #include namespace Bsa @@ -92,7 +94,7 @@ namespace Bsa /// Read header information from the input source void readHeader() override; - Files::IStreamPtr getFile(const char* filePath) override; + Files::IStreamPtr getFile(const char* filePath); Files::IStreamPtr getFile(const FileStruct* fileStruct); void addFile(const std::string& filename, std::istream& file) override; }; diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index 86d21060c0..b0caa5c305 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -1,6 +1,7 @@ #include "bsaarchive.hpp" #include +#include namespace VFS { From bba80b07a2c68051e37137ca0820b525ee02eef8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 14 Oct 2021 16:49:30 +0200 Subject: [PATCH 1477/2859] Check if the target is an actor --- apps/openmw/mwmechanics/combat.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 7ae26c82b8..3ed1a8678c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -49,7 +49,8 @@ namespace MWMechanics cast.mHitPosition = hitPosition; cast.cast(object, false); // Apply magic effects directly instead of waiting a frame to allow soul trap to work on one-hit kills - MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim); + if(victim.getClass().isActor()) + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim); return true; } } From 5805c0114a46f1fe23a415dd63a161c498107797 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 14 Oct 2021 17:40:03 +0200 Subject: [PATCH 1478/2859] Don't hit the ground --- apps/openmw/mwmechanics/combat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 3ed1a8678c..6407d03f23 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -49,7 +49,7 @@ namespace MWMechanics cast.mHitPosition = hitPosition; cast.cast(object, false); // Apply magic effects directly instead of waiting a frame to allow soul trap to work on one-hit kills - if(victim.getClass().isActor()) + if(!victim.isEmpty() && victim.getClass().isActor()) MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim); return true; } From 79665cea6685930c06fa823e197e326d2506da05 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 14 Oct 2021 23:30:37 +0200 Subject: [PATCH 1479/2859] Avoid access to the path vector element out of range polygonPath.front() in some cases might reference to a first element of empty vector. Copy the value into a local variable to be able to access later. --- components/detournavigator/findsmoothpath.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 8eb0bf9f34..c3a33c42a6 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -199,11 +199,14 @@ namespace DetourNavigator ++smoothPathSize; break; } - else if (offMeshConnection && inRange(result->mResultPos, steerTarget->steerPos, slop)) + + dtPolyRef polyRef = polygonPath.front(); + osg::Vec3f polyPos = result->mResultPos; + + if (offMeshConnection && inRange(polyPos, steerTarget->steerPos, slop)) { // Advance the path up to and over the off-mesh connection. dtPolyRef prevRef = 0; - dtPolyRef polyRef = polygonPath.front(); std::size_t npos = 0; while (npos < polygonPath.size() && polyRef != steerTarget->steerPosRef) { @@ -233,14 +236,11 @@ namespace DetourNavigator } // Move position at the other side of the off-mesh link. - if (dtStatusFailed(navMeshQuery.getPolyHeight(polygonPath.front(), endPos.ptr(), &iterPos.y()))) - return Status::GetPolyHeightFailed; - iterPos.x() = endPos.x(); - iterPos.z() = endPos.z(); + polyPos = endPos; } } - if (dtStatusFailed(navMeshQuery.getPolyHeight(polygonPath.front(), result->mResultPos.ptr(), &iterPos.y()))) + if (dtStatusFailed(navMeshQuery.getPolyHeight(polyRef, polyPos.ptr(), &iterPos.y()))) return Status::GetPolyHeightFailed; iterPos.x() = result->mResultPos.x(); iterPos.z() = result->mResultPos.z(); From f6d1689bb9d69ec0d66680d7fe4e88c4b4d0577b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 14 Oct 2021 23:42:53 +0200 Subject: [PATCH 1480/2859] Avoid redundant polygon path reallocations Use separate variable to store size and make all operations in-place. --- components/detournavigator/findsmoothpath.cpp | 67 ++++++++----------- components/detournavigator/findsmoothpath.hpp | 40 ++++++----- 2 files changed, 49 insertions(+), 58 deletions(-) diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp index 14fe696f10..e166ff90e9 100644 --- a/components/detournavigator/findsmoothpath.cpp +++ b/components/detournavigator/findsmoothpath.cpp @@ -7,12 +7,16 @@ namespace DetourNavigator { - std::vector fixupCorridor(const std::vector& path, const std::vector& visited) + std::size_t fixupCorridor(dtPolyRef* path, std::size_t pathSize, const std::vector& visited) { std::vector::const_reverse_iterator furthestVisited; // Find furthest common polygon. - const auto it = std::find_if(path.rbegin(), path.rend(), [&] (dtPolyRef pathValue) + const auto begin = path; + const auto end = path + pathSize; + const std::reverse_iterator rbegin(end); + const std::reverse_iterator rend(begin); + const auto it = std::find_if(rbegin, rend, [&] (dtPolyRef pathValue) { const auto it = std::find(visited.rbegin(), visited.rend(), pathValue); if (it == visited.rend()) @@ -22,8 +26,8 @@ namespace DetourNavigator }); // If no intersection found just return current path. - if (it == path.rend()) - return path; + if (it == rend) + return pathSize; const auto furthestPath = it.base() - 1; // Concatenate paths. @@ -34,36 +38,22 @@ namespace DetourNavigator // ^ furthestPath // result: x b_n ... b_1 D - std::vector result; - result.reserve(static_cast(furthestVisited - visited.rbegin()) - + static_cast(path.end() - furthestPath) - 1); - std::copy(visited.rbegin(), furthestVisited + 1, std::back_inserter(result)); - std::copy(furthestPath + 1, path.end(), std::back_inserter(result)); + auto newEnd = std::copy(visited.rbegin(), furthestVisited + 1, begin); + newEnd = std::copy(furthestPath + 1, end, newEnd); - return result; + return static_cast(newEnd - begin); } - // This function checks if the path has a small U-turn, that is, - // a polygon further in the path is adjacent to the first polygon - // in the path. If that happens, a shortcut is taken. - // This can happen if the target (T) location is at tile boundary, - // and we're (S) approaching it parallel to the tile edge. - // The choice at the vertex can be arbitrary, - // +---+---+ - // |:::|:::| - // +-S-+-T-+ - // |:::| | <-- the step can end up in here, resulting U-turn path. - // +---+---+ - std::vector fixupShortcuts(const std::vector& path, const dtNavMeshQuery& navQuery) + std::size_t fixupShortcuts(dtPolyRef* path, std::size_t pathSize, const dtNavMeshQuery& navQuery) { - if (path.size() < 3) - return path; + if (pathSize < 3) + return pathSize; // Get connected polygons const dtMeshTile* tile = nullptr; const dtPoly* poly = nullptr; if (dtStatusFailed(navQuery.getAttachedNavMesh()->getTileAndPolyByRef(path[0], &tile, &poly))) - return path; + return pathSize; const std::size_t maxNeis = 16; std::array neis; @@ -83,7 +73,7 @@ namespace DetourNavigator // in the path, short cut to that polygon directly. const std::size_t maxLookAhead = 6; std::size_t cut = 0; - for (std::size_t i = std::min(maxLookAhead, path.size()) - 1; i > 1 && cut == 0; i--) + for (std::size_t i = std::min(maxLookAhead, pathSize) - 1; i > 1 && cut == 0; i--) { for (std::size_t j = 0; j < nneis; j++) { @@ -95,18 +85,15 @@ namespace DetourNavigator } } if (cut <= 1) - return path; + return pathSize; - std::vector result; - const auto offset = cut - 1; - result.reserve(1 + path.size() - offset); - result.push_back(path.front()); - std::copy(path.begin() + std::ptrdiff_t(offset), path.end(), std::back_inserter(result)); - return result; + const std::ptrdiff_t offset = static_cast(cut) - 1; + std::copy(path + offset, path + pathSize, path); + return pathSize - offset; } std::optional getSteerTarget(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& startPos, - const osg::Vec3f& endPos, const float minTargetDist, const std::vector& path) + const osg::Vec3f& endPos, const float minTargetDist, const dtPolyRef* path, const std::size_t pathSize) { // Find steer target. SteerTarget result; @@ -115,8 +102,8 @@ namespace DetourNavigator std::array steerPathFlags; std::array steerPathPolys; int nsteerPath = 0; - const dtStatus status = navMeshQuery.findStraightPath(startPos.ptr(), endPos.ptr(), path.data(), - static_cast(path.size()), steerPath.data(), steerPathFlags.data(), steerPathPolys.data(), + const dtStatus status = navMeshQuery.findStraightPath(startPos.ptr(), endPos.ptr(), path, + static_cast(pathSize), steerPath.data(), steerPathFlags.data(), steerPathPolys.data(), &nsteerPath, maxSteerPoints); if (dtStatusFailed(status)) return std::nullopt; @@ -138,10 +125,10 @@ namespace DetourNavigator if (ns >= static_cast(nsteerPath)) return std::nullopt; - dtVcopy(result.steerPos.ptr(), &steerPath[ns * 3]); - result.steerPos.y() = startPos[1]; - result.steerPosFlag = steerPathFlags[ns]; - result.steerPosRef = steerPathPolys[ns]; + dtVcopy(result.mSteerPos.ptr(), &steerPath[ns * 3]); + result.mSteerPos.y() = startPos[1]; + result.mSteerPosFlag = steerPathFlags[ns]; + result.mSteerPosRef = steerPathPolys[ns]; return result; } diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index c3a33c42a6..71e8a1df88 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -30,7 +30,7 @@ namespace DetourNavigator return (osg::Vec2f(v1.x(), v1.z()) - osg::Vec2f(v2.x(), v2.z())).length() < r; } - std::vector fixupCorridor(const std::vector& path, const std::vector& visited); + std::size_t fixupCorridor(dtPolyRef* path, std::size_t pathSize, const std::vector& visited); // This function checks if the path has a small U-turn, that is, // a polygon further in the path is adjacent to the first polygon @@ -43,17 +43,17 @@ namespace DetourNavigator // +-S-+-T-+ // |:::| | <-- the step can end up in here, resulting U-turn path. // +---+---+ - std::vector fixupShortcuts(const std::vector& path, const dtNavMeshQuery& navQuery); + std::size_t fixupShortcuts(dtPolyRef* path, std::size_t pathSize, const dtNavMeshQuery& navQuery); struct SteerTarget { - osg::Vec3f steerPos; - unsigned char steerPosFlag; - dtPolyRef steerPosRef; + osg::Vec3f mSteerPos; + unsigned char mSteerPosFlag; + dtPolyRef mSteerPosRef; }; std::optional getSteerTarget(const dtNavMeshQuery& navQuery, const osg::Vec3f& startPos, - const osg::Vec3f& endPos, const float minTargetDist, const std::vector& path); + const osg::Vec3f& endPos, const float minTargetDist, const dtPolyRef* path, const std::size_t pathSize); template class OutputTransformIterator @@ -158,22 +158,23 @@ namespace DetourNavigator *out++ = iterPos; std::size_t smoothPathSize = 1; + std::size_t polygonPathSize = polygonPath.size(); // Move towards target a small advancement at a time until target reached or // when ran out of memory to store the path. - while (!polygonPath.empty() && smoothPathSize < maxSmoothPathSize) + while (polygonPathSize > 0 && smoothPathSize < maxSmoothPathSize) { // Find location to steer towards. - const auto steerTarget = getSteerTarget(navMeshQuery, iterPos, targetPos, slop, polygonPath); + const auto steerTarget = getSteerTarget(navMeshQuery, iterPos, targetPos, slop, polygonPath.data(), polygonPathSize); if (!steerTarget) break; - const bool endOfPath = bool(steerTarget->steerPosFlag & DT_STRAIGHTPATH_END); - const bool offMeshConnection = bool(steerTarget->steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION); + const bool endOfPath = bool(steerTarget->mSteerPosFlag & DT_STRAIGHTPATH_END); + const bool offMeshConnection = bool(steerTarget->mSteerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION); // Find movement delta. - const osg::Vec3f delta = steerTarget->steerPos - iterPos; + const osg::Vec3f delta = steerTarget->mSteerPos - iterPos; float len = delta.length(); // If the steer target is end of path or off-mesh link, do not move past the location. if ((endOfPath || offMeshConnection) && len < stepSize) @@ -187,11 +188,11 @@ namespace DetourNavigator if (!result) return Status::MoveAlongSurfaceFailed; - polygonPath = fixupCorridor(polygonPath, result->mVisited); - polygonPath = fixupShortcuts(polygonPath, navMeshQuery); + polygonPathSize = fixupCorridor(polygonPath.data(), polygonPathSize, result->mVisited); + polygonPathSize = fixupShortcuts(polygonPath.data(), polygonPathSize, navMeshQuery); // Handle end of path and off-mesh links when close enough. - if (endOfPath && inRange(result->mResultPos, steerTarget->steerPos, slop)) + if (endOfPath && inRange(result->mResultPos, steerTarget->mSteerPos, slop)) { // Reached end of path. iterPos = targetPos; @@ -203,19 +204,22 @@ namespace DetourNavigator dtPolyRef polyRef = polygonPath.front(); osg::Vec3f polyPos = result->mResultPos; - if (offMeshConnection && inRange(polyPos, steerTarget->steerPos, slop)) + if (offMeshConnection && inRange(polyPos, steerTarget->mSteerPos, slop)) { // Advance the path up to and over the off-mesh connection. dtPolyRef prevRef = 0; std::size_t npos = 0; - while (npos < polygonPath.size() && polyRef != steerTarget->steerPosRef) + while (npos < polygonPathSize && polyRef != steerTarget->mSteerPosRef) { prevRef = polyRef; polyRef = polygonPath[npos]; ++npos; } - std::copy(polygonPath.begin() + std::ptrdiff_t(npos), polygonPath.end(), polygonPath.begin()); - polygonPath.resize(polygonPath.size() - npos); + if (npos > 0) + { + std::copy(polygonPath.begin() + npos, polygonPath.begin() + polygonPathSize, polygonPath.begin()); + polygonPathSize -= npos; + } // Reached off-mesh connection. osg::Vec3f startPos; From 7602d677fa3b0ccecee3134fbded53563ee05a01 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 2 Oct 2021 14:23:43 +0300 Subject: [PATCH 1481/2859] Terrain selection optimizations, measurements, renderBin change --- apps/opencs/view/render/terrainselection.cpp | 96 +++++++++++++++++--- apps/opencs/view/render/terrainselection.hpp | 10 +- apps/opencs/view/render/terrainstorage.cpp | 15 ++- 3 files changed, 98 insertions(+), 23 deletions(-) diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 0593917e0a..dd1b982337 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -1,12 +1,14 @@ #include "terrainselection.hpp" #include +#include #include #include #include #include +#include #include "../../model/world/cellcoordinates.hpp" #include "../../model/world/columnimp.hpp" @@ -21,6 +23,8 @@ mParentNode(parentNode), mWorldspaceWidget (worldspaceWidget), mDraggedOperation mGeometry = new osg::Geometry(); mSelectionNode = new osg::Group(); + mSelectionNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mSelectionNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin"); mSelectionNode->addChild(mGeometry); activate(); @@ -92,6 +96,7 @@ void CSVRender::TerrainSelection::update() void CSVRender::TerrainSelection::drawShapeSelection(const osg::ref_ptr vertices) { + resetMeasurements(); if (!mSelection.empty()) { for (std::pair &localPos : mSelection) @@ -102,32 +107,34 @@ void CSVRender::TerrainSelection::drawShapeSelection(const osg::ref_ptrpush_back(pointXY); - vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y - 1), calculateLandHeight(x, y - 1) + 2)); + vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y - 1), calculateLandHeight(x, y - 1))); vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x - 1), yWorldCoord, calculateLandHeight(x - 1, y) + 2)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x - 1), yWorldCoord, calculateLandHeight(x - 1, y))); const auto north = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y + 1)); if (north == mSelection.end()) { vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), calculateLandHeight(x, y + 1) + 2)); + vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), calculateLandHeight(x, y + 1))); } const auto east = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x + 1, y)); if (east == mSelection.end()) { vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, calculateLandHeight(x + 1, y) + 2)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, calculateLandHeight(x + 1, y))); } } } + printMeasurements(); } void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptr vertices) { + resetMeasurements(); if (!mSelection.empty()) { const int landHeightsNudge = (ESM::Land::REAL_SIZE / ESM::Land::LAND_SIZE) / (ESM::Land::LAND_SIZE - 1); // Does this work with all land size configurations? @@ -154,8 +161,8 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptrpush_back(osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+(i-1), y2)+2)); - vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+i, y2)+2)); + vertices->push_back(osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+(i-1), y2))); + vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y + 1), calculateLandHeight(x1+i, y2))); } } @@ -166,8 +173,8 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptrpush_back(osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+(i-1), y1)+2)); - vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+i, y1)+2)); + vertices->push_back(osg::Vec3f(drawPreviousX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+(i-1), y1))); + vertices->push_back(osg::Vec3f(drawCurrentX, CSMWorld::CellCoordinates::textureGlobalYToWorldCoords(y), calculateLandHeight(x1+i, y1))); } } @@ -178,8 +185,8 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptrpush_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawPreviousY, calculateLandHeight(x2, y1+(i-1))+2)); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawCurrentY, calculateLandHeight(x2, y1+i)+2)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawPreviousY, calculateLandHeight(x2, y1+(i-1)))); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x + 1), drawCurrentY, calculateLandHeight(x2, y1+i))); } } @@ -190,12 +197,13 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptrpush_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawPreviousY, calculateLandHeight(x1, y1+(i-1))+2)); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawCurrentY, calculateLandHeight(x1, y1+i)+2)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawPreviousY, calculateLandHeight(x1, y1+(i-1)))); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalXToWorldCoords(x), drawCurrentY, calculateLandHeight(x1, y1+i))); } } } } + printMeasurements(); } void CSVRender::TerrainSelection::handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod) @@ -321,6 +329,41 @@ bool CSVRender::TerrainSelection::isLandLoaded(const std::string& cellId) int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global vertex coordinates { + auto start = std::chrono::high_resolution_clock::now(); + int cellX = std::floor(static_cast(x) / (ESM::Land::LAND_SIZE - 1)); + int cellY = std::floor(static_cast(y) / (ESM::Land::LAND_SIZE - 1)); + int localX = x - cellX * (ESM::Land::LAND_SIZE - 1); + int localY = y - cellY * (ESM::Land::LAND_SIZE - 1); + + CSMWorld::CellCoordinates coords (cellX, cellY); + + float landHeight = 0.f; + if (CSVRender::Cell* cell = dynamic_cast(mWorldspaceWidget->getCell(coords))) + { + landHeight = cell->getSumOfAlteredAndTrueHeight(cellX, cellY, localX, localY); + } + else if (isLandLoaded(CSMWorld::CellCoordinates::generateId(cellX, cellY))) + { + CSMDoc::Document& document = mWorldspaceWidget->getDocument(); + std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); + const ESM::Land::LandData* landData = document.getData().getLand().getRecord(cellId).get().getLandData(ESM::Land::DATA_VHGT); + auto stop = std::chrono::high_resolution_clock::now(); + int duration = std::chrono::duration_cast(stop - start).count(); + mDurationsA += duration; + mDurationsAMeasurements++; + return landData->mHeights[localY*ESM::Land::LAND_SIZE + localX]; + } + + auto stop = std::chrono::high_resolution_clock::now(); + int duration = std::chrono::duration_cast(stop - start).count(); + mDurationsB += duration; + mDurationsBMeasurements++; + return landHeight; +} + +/*int CSVRender::TerrainSelection::OldCalculateLandHeight(int x, int y) // global vertex coordinates +{ + auto start = std::chrono::high_resolution_clock::now(); int cellX = std::floor(static_cast(x) / (ESM::Land::LAND_SIZE - 1)); int cellY = std::floor(static_cast(y) / (ESM::Land::LAND_SIZE - 1)); int localX = x - cellX * (ESM::Land::LAND_SIZE - 1); @@ -340,8 +383,33 @@ int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global ver std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); const CSMWorld::LandHeightsColumn::DataType mPointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + auto stop = std::chrono::high_resolution_clock::now(); + int duration = std::chrono::duration_cast(stop - start).count(); + mDurationsA += duration; + mDurationsAMeasurements++; return mPointer[localY*ESM::Land::LAND_SIZE + localX]; } - + auto stop = std::chrono::high_resolution_clock::now(); + int duration = std::chrono::duration_cast(stop - start).count(); + mDurationsB += duration; + mDurationsBMeasurements++; return landHeight; +}*/ + + +void CSVRender::TerrainSelection::resetMeasurements() +{ + mDurationsA = 0; + mDurationsB = 0; + mDurationsAMeasurements = 0; + mDurationsBMeasurements = 0; +} + +void CSVRender::TerrainSelection::printMeasurements() +{ + if (mDurationsAMeasurements != 0) + Log(Debug::Warning) << "A (total) " << mDurationsA << "(avg) " << mDurationsA / mDurationsAMeasurements << "(meas.) " << mDurationsAMeasurements; + + if (mDurationsBMeasurements != 0) + Log(Debug::Warning) << "B (total) " << mDurationsB << "(avg) " << mDurationsB / mDurationsBMeasurements << "(meas.) " << mDurationsBMeasurements; } diff --git a/apps/opencs/view/render/terrainselection.hpp b/apps/opencs/view/render/terrainselection.hpp index 18622ad13a..c8c7bc7aab 100644 --- a/apps/opencs/view/render/terrainselection.hpp +++ b/apps/opencs/view/render/terrainselection.hpp @@ -64,6 +64,10 @@ namespace CSVRender private: + void resetMeasurements(); + + void printMeasurements(); + void handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod); bool noCell(const std::string& cellId); @@ -73,7 +77,7 @@ namespace CSVRender bool noLandLoaded(const std::string& cellId); bool isLandLoaded(const std::string& cellId); - + osg::Group* mParentNode; WorldspaceWidget *mWorldspaceWidget; osg::ref_ptr mBaseNode; @@ -83,6 +87,10 @@ namespace CSVRender std::vector> mTemporarySelection; // Used during toggle to compare the most recent drag operation bool mDraggedOperationFlag; //true during drag operation, false when click-operation TerrainSelectionType mSelectionType; + int mDurationsA; + int mDurationsB; + int mDurationsAMeasurements; + int mDurationsBMeasurements; }; } diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index d9cc3015e1..035c073567 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -48,15 +48,14 @@ namespace CSVRender float TerrainStorage::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) { float height = 0.f; - osg::ref_ptr land = getLand (cellX, cellY); - if (land) - { - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) height = getVertexHeight(data, inCellX, inCellY); - } - else return height; - return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height; + int index = mData.getLand().searchId(CSMWorld::Land::createUniqueRecordId(cellX, cellY)); + if (index == -1) // no land! + return height; + + const ESM::Land::LandData* landData = mData.getLand().getRecord(index).get().getLandData(ESM::Land::DATA_VHGT); + height = landData->mHeights[inCellY*ESM::Land::LAND_SIZE + inCellX]; + return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height; } float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY) From 3df9ceda032f50400327ac9c42d016bf2a2743e4 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 2 Oct 2021 14:58:47 +0300 Subject: [PATCH 1482/2859] Remove measurement code --- apps/opencs/view/render/terrainselection.cpp | 66 -------------------- apps/opencs/view/render/terrainselection.hpp | 8 --- 2 files changed, 74 deletions(-) diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index dd1b982337..8b855fc91b 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -96,7 +96,6 @@ void CSVRender::TerrainSelection::update() void CSVRender::TerrainSelection::drawShapeSelection(const osg::ref_ptr vertices) { - resetMeasurements(); if (!mSelection.empty()) { for (std::pair &localPos : mSelection) @@ -129,12 +128,10 @@ void CSVRender::TerrainSelection::drawShapeSelection(const osg::ref_ptr vertices) { - resetMeasurements(); if (!mSelection.empty()) { const int landHeightsNudge = (ESM::Land::REAL_SIZE / ESM::Land::LAND_SIZE) / (ESM::Land::LAND_SIZE - 1); // Does this work with all land size configurations? @@ -203,7 +200,6 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptr>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod) @@ -329,7 +325,6 @@ bool CSVRender::TerrainSelection::isLandLoaded(const std::string& cellId) int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global vertex coordinates { - auto start = std::chrono::high_resolution_clock::now(); int cellX = std::floor(static_cast(x) / (ESM::Land::LAND_SIZE - 1)); int cellY = std::floor(static_cast(y) / (ESM::Land::LAND_SIZE - 1)); int localX = x - cellX * (ESM::Land::LAND_SIZE - 1); @@ -347,69 +342,8 @@ int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global ver CSMDoc::Document& document = mWorldspaceWidget->getDocument(); std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); const ESM::Land::LandData* landData = document.getData().getLand().getRecord(cellId).get().getLandData(ESM::Land::DATA_VHGT); - auto stop = std::chrono::high_resolution_clock::now(); - int duration = std::chrono::duration_cast(stop - start).count(); - mDurationsA += duration; - mDurationsAMeasurements++; return landData->mHeights[localY*ESM::Land::LAND_SIZE + localX]; } - auto stop = std::chrono::high_resolution_clock::now(); - int duration = std::chrono::duration_cast(stop - start).count(); - mDurationsB += duration; - mDurationsBMeasurements++; return landHeight; } - -/*int CSVRender::TerrainSelection::OldCalculateLandHeight(int x, int y) // global vertex coordinates -{ - auto start = std::chrono::high_resolution_clock::now(); - int cellX = std::floor(static_cast(x) / (ESM::Land::LAND_SIZE - 1)); - int cellY = std::floor(static_cast(y) / (ESM::Land::LAND_SIZE - 1)); - int localX = x - cellX * (ESM::Land::LAND_SIZE - 1); - int localY = y - cellY * (ESM::Land::LAND_SIZE - 1); - - CSMWorld::CellCoordinates coords (cellX, cellY); - - float landHeight = 0.f; - if (CSVRender::Cell* cell = dynamic_cast(mWorldspaceWidget->getCell(coords))) - { - landHeight = cell->getSumOfAlteredAndTrueHeight(cellX, cellY, localX, localY); - } - else if (isLandLoaded(CSMWorld::CellCoordinates::generateId(cellX, cellY))) - { - CSMDoc::Document& document = mWorldspaceWidget->getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); - int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - const CSMWorld::LandHeightsColumn::DataType mPointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - auto stop = std::chrono::high_resolution_clock::now(); - int duration = std::chrono::duration_cast(stop - start).count(); - mDurationsA += duration; - mDurationsAMeasurements++; - return mPointer[localY*ESM::Land::LAND_SIZE + localX]; - } - auto stop = std::chrono::high_resolution_clock::now(); - int duration = std::chrono::duration_cast(stop - start).count(); - mDurationsB += duration; - mDurationsBMeasurements++; - return landHeight; -}*/ - - -void CSVRender::TerrainSelection::resetMeasurements() -{ - mDurationsA = 0; - mDurationsB = 0; - mDurationsAMeasurements = 0; - mDurationsBMeasurements = 0; -} - -void CSVRender::TerrainSelection::printMeasurements() -{ - if (mDurationsAMeasurements != 0) - Log(Debug::Warning) << "A (total) " << mDurationsA << "(avg) " << mDurationsA / mDurationsAMeasurements << "(meas.) " << mDurationsAMeasurements; - - if (mDurationsBMeasurements != 0) - Log(Debug::Warning) << "B (total) " << mDurationsB << "(avg) " << mDurationsB / mDurationsBMeasurements << "(meas.) " << mDurationsBMeasurements; -} diff --git a/apps/opencs/view/render/terrainselection.hpp b/apps/opencs/view/render/terrainselection.hpp index c8c7bc7aab..868c3aea42 100644 --- a/apps/opencs/view/render/terrainselection.hpp +++ b/apps/opencs/view/render/terrainselection.hpp @@ -64,10 +64,6 @@ namespace CSVRender private: - void resetMeasurements(); - - void printMeasurements(); - void handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod); bool noCell(const std::string& cellId); @@ -87,10 +83,6 @@ namespace CSVRender std::vector> mTemporarySelection; // Used during toggle to compare the most recent drag operation bool mDraggedOperationFlag; //true during drag operation, false when click-operation TerrainSelectionType mSelectionType; - int mDurationsA; - int mDurationsB; - int mDurationsAMeasurements; - int mDurationsBMeasurements; }; } From 89a20b6ea5e8677d2c9139201aea41cc2bb58a93 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 3 Oct 2021 23:50:42 +0300 Subject: [PATCH 1483/2859] Call dragEnded instead of pressed also after aborted operation --- apps/opencs/view/render/worldspacewidget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index c51479f897..d3bf1fcf0c 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -464,7 +464,6 @@ void CSVRender::WorldspaceWidget::abortDrag() EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); editMode.dragAborted(); - mDragging = false; mDragMode = InteractionType_None; } } From 46960825ef8c3ba1d722e92353f5c08cbdf2c5f8 Mon Sep 17 00:00:00 2001 From: unelsson Date: Mon, 4 Oct 2021 00:20:39 +0300 Subject: [PATCH 1484/2859] Various terrain selection and editmode dragging fixes --- apps/opencs/view/render/terrainselection.cpp | 79 +++++-------------- apps/opencs/view/render/terrainselection.hpp | 10 +-- apps/opencs/view/render/terrainshapemode.cpp | 39 ++++----- apps/opencs/view/render/terrainshapemode.hpp | 2 +- .../opencs/view/render/terraintexturemode.cpp | 37 ++++++--- .../opencs/view/render/terraintexturemode.hpp | 2 +- 6 files changed, 68 insertions(+), 101 deletions(-) diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 8b855fc91b..03009e8701 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -8,7 +8,6 @@ #include #include -#include #include "../../model/world/cellcoordinates.hpp" #include "../../model/world/columnimp.hpp" @@ -18,7 +17,7 @@ #include "worldspacewidget.hpp" CSVRender::TerrainSelection::TerrainSelection(osg::Group* parentNode, WorldspaceWidget *worldspaceWidget, TerrainSelectionType type): -mParentNode(parentNode), mWorldspaceWidget (worldspaceWidget), mDraggedOperationFlag(false), mSelectionType(type) +mParentNode(parentNode), mWorldspaceWidget (worldspaceWidget), mSelectionType(type) { mGeometry = new osg::Geometry(); @@ -47,19 +46,25 @@ void CSVRender::TerrainSelection::onlySelect(const std::vector>& localPositions, bool toggleInProgress) +void CSVRender::TerrainSelection::addSelect(const std::vector>& localPositions) { - handleSelection(localPositions, toggleInProgress, SelectionMethod::AddSelect); + handleSelection(localPositions, SelectionMethod::AddSelect); } -void CSVRender::TerrainSelection::removeSelect(const std::vector>& localPositions, bool toggleInProgress) +void CSVRender::TerrainSelection::removeSelect(const std::vector>& localPositions) { - handleSelection(localPositions, toggleInProgress, SelectionMethod::RemoveSelect); + handleSelection(localPositions, SelectionMethod::RemoveSelect); } -void CSVRender::TerrainSelection::toggleSelect(const std::vector>& localPositions, bool toggleInProgress) +void CSVRender::TerrainSelection::toggleSelect(const std::vector>& localPositions) { - handleSelection(localPositions, toggleInProgress, SelectionMethod::ToggleSelect); + handleSelection(localPositions, SelectionMethod::ToggleSelect); +} + +void CSVRender::TerrainSelection::clearTemporarySelection() +{ + mTemporarySelection.clear(); + update(); } void CSVRender::TerrainSelection::activate() @@ -202,56 +207,13 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptr>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod) +void CSVRender::TerrainSelection::handleSelection(const std::vector>& localPositions, SelectionMethod selectionMethod) { - if (toggleInProgress) + for (auto const& localPos : localPositions) { - for (auto const& localPos : localPositions) - { - auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); - mDraggedOperationFlag = true; + auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); - if (iterTemp == mTemporarySelection.end()) - { - auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); - - switch (selectionMethod) - { - case SelectionMethod::AddSelect: - if (iter == mSelection.end()) - { - mSelection.emplace_back(localPos); - } - - break; - case SelectionMethod::RemoveSelect: - if (iter != mSelection.end()) - { - mSelection.erase(iter); - } - - break; - case SelectionMethod::ToggleSelect: - if (iter == mSelection.end()) - { - mSelection.emplace_back(localPos); - } - else - { - mSelection.erase(iter); - } - - break; - default: break; - } - } - - mTemporarySelection.push_back(localPos); - } - } - else if (mDraggedOperationFlag == false) - { - for (auto const& localPos : localPositions) + if (iterTemp == mTemporarySelection.end()) { const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); @@ -285,14 +247,9 @@ void CSVRender::TerrainSelection::handleSelection(const std::vector> &localPositions); - void addSelect(const std::vector>& localPositions, bool toggleInProgress); - void removeSelect(const std::vector>& localPositions, bool toggleInProgress); - void toggleSelect(const std::vector> &localPositions, bool toggleInProgress); + void addSelect(const std::vector>& localPositions); + void removeSelect(const std::vector>& localPositions); + void toggleSelect(const std::vector> &localPositions); + void clearTemporarySelection(); void activate(); void deactivate(); @@ -64,7 +65,7 @@ namespace CSVRender private: - void handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod); + void handleSelection(const std::vector>& localPositions, SelectionMethod selectionMethod); bool noCell(const std::string& cellId); @@ -81,7 +82,6 @@ namespace CSVRender osg::ref_ptr mSelectionNode; std::vector> mSelection; // Global terrain selection coordinate in either vertex or texture units std::vector> mTemporarySelection; // Used during toggle to compare the most recent drag operation - bool mDraggedOperationFlag; //true during drag operation, false when click-operation TerrainSelectionType mSelectionType; }; } diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 0504944954..21359a8684 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -106,16 +106,6 @@ void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); applyTerrainEditChanges(); } - - if (mDragMode == InteractionType_PrimarySelect) - { - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); - } - - if (mDragMode == InteractionType_SecondarySelect) - { - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); - } } clearTransientEdits(); } @@ -124,7 +114,7 @@ void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult { if(hit.hit && hit.tag == nullptr) { - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, false); + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); } } @@ -132,7 +122,7 @@ void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResu { if(hit.hit && hit.tag == nullptr) { - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, false); + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); } } @@ -167,8 +157,8 @@ bool CSVRender::TerrainShapeMode::primarySelectStartDrag (const QPoint& pos) mDragMode = InteractionType_None; return false; } - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); - return false; + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); + return true; } bool CSVRender::TerrainShapeMode::secondarySelectStartDrag (const QPoint& pos) @@ -180,8 +170,8 @@ bool CSVRender::TerrainShapeMode::secondarySelectStartDrag (const QPoint& pos) mDragMode = InteractionType_None; return false; } - selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); - return false; + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); + return true; } void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) @@ -200,13 +190,13 @@ void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, if (mDragMode == InteractionType_PrimarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); } } @@ -217,12 +207,17 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) applyTerrainEditChanges(); clearTransientEdits(); } + if (mDragMode == InteractionType_PrimarySelect || mDragMode == InteractionType_SecondarySelect) + { + mTerrainShapeSelection->clearTemporarySelection(); + } } void CSVRender::TerrainShapeMode::dragAborted() { clearTransientEdits(); + mDragMode = InteractionType_None; } void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) @@ -1076,7 +1071,7 @@ void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int glob } } -void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation) +void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode) { int r = mBrushSize / 2; std::vector> selections; @@ -1136,11 +1131,11 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& if (selectAction == "Select only") mTerrainShapeSelection->onlySelect(selections); else if (selectAction == "Add to selection") - mTerrainShapeSelection->addSelect(selections, dragOperation); + mTerrainShapeSelection->addSelect(selections); else if (selectAction == "Remove from selection") - mTerrainShapeSelection->removeSelect(selections, dragOperation); + mTerrainShapeSelection->removeSelect(selections); else if (selectAction == "Invert selection") - mTerrainShapeSelection->toggleSelect(selections, dragOperation); + mTerrainShapeSelection->toggleSelect(selections); } void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index abc4b8aba8..8b40483be4 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -142,7 +142,7 @@ namespace CSVRender void handleSelection(int globalSelectionX, int globalSelectionY, std::vector>* selections); /// Handle brush mechanics for terrain shape selection - void selectTerrainShapes (const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation); + void selectTerrainShapes (const std::pair& vertexCoords, unsigned char selectMode); /// Push terrain shape edits to command macro void pushEditToCommand (const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index dfcc29ae01..5f1cc354f6 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -129,7 +129,7 @@ void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResu { if(hit.hit && hit.tag == nullptr) { - selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, false); + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); } } @@ -137,7 +137,7 @@ void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitRe { if(hit.hit && hit.tag == nullptr) { - selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, false); + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); } } @@ -188,8 +188,8 @@ bool CSVRender::TerrainTextureMode::primarySelectStartDrag (const QPoint& pos) mDragMode = InteractionType_None; return false; } - selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, true); - return false; + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); + return true; } bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos) @@ -201,8 +201,8 @@ bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos) mDragMode = InteractionType_None; return false; } - selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); - return false; + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); + return true; } void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) @@ -225,13 +225,13 @@ void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diff if (mDragMode == InteractionType_PrimarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, true); + if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); + if (hit.hit && hit.tag == nullptr) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); } } @@ -251,6 +251,8 @@ void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) mIsEditing = false; } } + + mTerrainTextureSelection->clearTemporarySelection(); } void CSVRender::TerrainTextureMode::dragAborted() @@ -496,7 +498,7 @@ bool CSVRender::TerrainTextureMode::isInCellSelection(int globalSelectionX, int } -void CSVRender::TerrainTextureMode::selectTerrainTextures(const std::pair& texCoords, unsigned char selectMode, bool dragOperation) +void CSVRender::TerrainTextureMode::selectTerrainTextures(const std::pair& texCoords, unsigned char selectMode) { int r = mBrushSize / 2; std::vector> selections; @@ -559,8 +561,21 @@ void CSVRender::TerrainTextureMode::selectTerrainTextures(const std::paironlySelect(selections); - if(selectMode == 1) mTerrainTextureSelection->toggleSelect(selections, dragOperation); + std::string selectAction; + + if (selectMode == 0) + selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); + else + selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); + + if (selectAction == "Select only") + mTerrainTextureSelection->onlySelect(selections); + else if (selectAction == "Add to selection") + mTerrainTextureSelection->addSelect(selections); + else if (selectAction == "Remove from selection") + mTerrainTextureSelection->removeSelect(selections); + else if (selectAction == "Invert selection") + mTerrainTextureSelection->toggleSelect(selections); } void CSVRender::TerrainTextureMode::pushEditToCommand(CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, diff --git a/apps/opencs/view/render/terraintexturemode.hpp b/apps/opencs/view/render/terraintexturemode.hpp index e0c6e4b40f..06c95e3d79 100644 --- a/apps/opencs/view/render/terraintexturemode.hpp +++ b/apps/opencs/view/render/terraintexturemode.hpp @@ -95,7 +95,7 @@ namespace CSVRender bool isInCellSelection(int globalSelectionX, int globalSelectionY); /// \brief Handle brush mechanics for texture selection - void selectTerrainTextures (const std::pair& texCoords, unsigned char selectMode, bool dragOperation); + void selectTerrainTextures (const std::pair& texCoords, unsigned char selectMode); /// \brief Push texture edits to command macro void pushEditToCommand (CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, From 55456a19fc179893ba86ea2311dce45979dad019 Mon Sep 17 00:00:00 2001 From: unelsson Date: Mon, 4 Oct 2021 01:03:01 +0300 Subject: [PATCH 1485/2859] Only iterate through temporary selection when using toggle --- apps/opencs/view/render/terrainselection.cpp | 40 ++++++++++--------- apps/opencs/view/render/terrainshapemode.cpp | 2 + .../opencs/view/render/terraintexturemode.cpp | 2 + 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 03009e8701..9c052c9d5a 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -64,7 +64,6 @@ void CSVRender::TerrainSelection::toggleSelect(const std::vectorclearTemporarySelection(); } } @@ -123,6 +124,7 @@ void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResu if(hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); + mTerrainShapeSelection->clearTemporarySelection(); } } diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 5f1cc354f6..113acd2a21 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -130,6 +130,7 @@ void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResu if(hit.hit && hit.tag == nullptr) { selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0); + mTerrainTextureSelection->clearTemporarySelection(); } } @@ -138,6 +139,7 @@ void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitRe if(hit.hit && hit.tag == nullptr) { selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1); + mTerrainTextureSelection->clearTemporarySelection(); } } From 28278e9a826f20e7d4d62d0c5a4b2c7d63b2f40b Mon Sep 17 00:00:00 2001 From: unelsson Date: Mon, 4 Oct 2021 01:27:57 +0300 Subject: [PATCH 1486/2859] Drawing duplicate lines is faster than iterating through big containers --- apps/opencs/view/render/terrainselection.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 9c052c9d5a..5a8448e1d6 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -116,20 +116,10 @@ void CSVRender::TerrainSelection::drawShapeSelection(const osg::ref_ptrpush_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y - 1), calculateLandHeight(x, y - 1))); vertices->push_back(pointXY); vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x - 1), yWorldCoord, calculateLandHeight(x - 1, y))); - - const auto north = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y + 1)); - if (north == mSelection.end()) - { - vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), calculateLandHeight(x, y + 1))); - } - - const auto east = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x + 1, y)); - if (east == mSelection.end()) - { - vertices->push_back(pointXY); - vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, calculateLandHeight(x + 1, y))); - } + vertices->push_back(pointXY); + vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), calculateLandHeight(x, y + 1))); + vertices->push_back(pointXY); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, calculateLandHeight(x + 1, y))); } } } From 14c4bba4a3230b468eeeca61dc472c3c26a191f8 Mon Sep 17 00:00:00 2001 From: unelsson Date: Fri, 15 Oct 2021 21:53:15 +0300 Subject: [PATCH 1487/2859] Remove unused include --- apps/opencs/view/render/terrainselection.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 5a8448e1d6..1fc183ba47 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -1,7 +1,6 @@ #include "terrainselection.hpp" #include -#include #include #include From e65a7f10e7d2e619ae7327eb6f5088455711fe70 Mon Sep 17 00:00:00 2001 From: unelsson Date: Fri, 15 Oct 2021 21:55:09 +0300 Subject: [PATCH 1488/2859] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe6dc8db19..22a48cd510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Bug #6273: Respawning NPCs rotation is inconsistent Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death + Bug #6285: Brush template drawing and terrain selection drawing performance is very bad Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod Bug #6302: Teleporting disabled actor breaks its disabled state From 5f8e4870129809351911628a739d67d24c26dbd4 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 15 Oct 2021 22:40:26 +0200 Subject: [PATCH 1489/2859] Let's get testy! (#3172) Let's try to get testing happening. We also disable artefact building for now. --- .github/workflows/cmake.yml | 42 ++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 54966fdfb0..d53d2380d4 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -26,25 +26,37 @@ jobs: key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} max-size: 1000M + - name: Install gtest + run: | + export CONFIGURATION="Release" + export GOOGLETEST_DIR="." + export GENERATOR="Unix Makefiles" + export CC="gcc" + export CXX="g++" + sudo -E CI/build_googletest.sh + - name: Configure - run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" + run: cmake -S . -B . -DGTEST_ROOT="$(pwd)/googletest/build" -DGMOCK_ROOT="$(pwd)/googletest/build" -DBUILD_UNITTESTS=ON -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" - name: Build run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 - - name: Install - shell: bash - run: cmake --install . + - name: Test + run: ./openmw_test_suite + +# - name: Install +# shell: bash +# run: cmake --install . - - name: Create Artifact - shell: bash - working-directory: install - run: | - ls -laR - 7z a ../build_artifact.7z . +# - name: Create Artifact +# shell: bash +# working-directory: install +# run: | +# ls -laR +# 7z a ../build_artifact.7z . - - name: Upload Artifact - uses: actions/upload-artifact@v1 - with: - path: ./build_artifact.7z - name: build_artifact.7z +# - name: Upload Artifact +# uses: actions/upload-artifact@v1 +# with: +# path: ./build_artifact.7z +# name: build_artifact.7z From d06e69735694afdb96a68a9842260c6691cac35b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Oct 2021 00:22:40 +0200 Subject: [PATCH 1490/2859] Initialize RenderingManager fields before use PostProcessor is constructed before mNearClip, mViewDistance, mFieldOfView are initialized and the constructor calls updateProjectionMatrix that uses these fields. ==16218== Conditional jump or move depends on uninitialised value(s) ==16218== at 0x722EBE: min (stl_algobase.h:235) ==16218== by 0x722EBE: MWRender::RenderingManager::updateProjectionMatrix() (renderingmanager.cpp:1183) ==16218== by 0x7D022F: MWRender::PostProcessor::PostProcessor(MWRender::RenderingManager&, osgViewer::Viewer*, osg::Group*) (postprocessor.cpp:144) ==16218== by 0x728B41: MWRender::RenderingManager::RenderingManager(osgViewer::Viewer*, osg::ref_ptr, Resource::ResourceSystem*, SceneUtil::WorkQueue*, std::__cxx11::basic_string, std::allocator > const&, DetourNavigator::Navigator&) (renderingmanager.cpp:452) ==16218== by 0xBB5E65: MWWorld::World::World(osgViewer::Viewer*, osg::ref_ptr, Resource::ResourceSystem*, SceneUtil::WorkQueue*, Files::Collections const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, ToUTF8::Utf8Encoder*, int, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&) (worldimp.cpp:190) ==16218== by 0xDEEA6F: OMW::Engine::prepareEngine(Settings::Manager&) (engine.cpp:789) ==16218== by 0xDF2068: OMW::Engine::go() (engine.cpp:959) ==16218== by 0xDE7B8F: runApplication(int, char**) (main.cpp:220) ==16218== by 0x1006593: wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) (debugging.cpp:205) ==16218== by 0x7152C3: main (main.cpp:232) ==16218== ==17455== Conditional jump or move depends on uninitialised value(s) ==17455== at 0x4F8613F: osg::Matrixd::makeFrustum(double, double, double, double, double, double) (Matrix_implementation.cpp:916) ==17455== by 0x4EC0E11: osg::Camera::setProjectionMatrixAsPerspective(double, double, double, double) (Matrixd:580) ==17455== by 0x722DDA: MWRender::RenderingManager::updateProjectionMatrix() (renderingmanager.cpp:1167) ==17455== by 0x7D01EF: MWRender::PostProcessor::PostProcessor(MWRender::RenderingManager&, osgViewer::Viewer*, osg::Group*) (postprocessor.cpp:144) ==17455== by 0x728BA1: MWRender::RenderingManager::RenderingManager(osgViewer::Viewer*, osg::ref_ptr, Resource::ResourceSystem*, SceneUtil::WorkQueue*, std::__cxx11::basic_string, std::allocator > const&, DetourNavigator::Navigator&) (renderingmanager.cpp:453) ==17455== by 0xBB5E25: MWWorld::World::World(osgViewer::Viewer*, osg::ref_ptr, Resource::ResourceSystem*, SceneUtil::WorkQueue*, Files::Collections const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, ToUTF8::Utf8Encoder*, int, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&, std::__cxx11::basic_string, std::allocator > const&) (worldimp.cpp:190) ==17455== by 0xDEEA3F: OMW::Engine::prepareEngine(Settings::Manager&) (engine.cpp:789) ==17455== by 0xDF2038: OMW::Engine::go() (engine.cpp:959) ==17455== by 0xDE7B5F: runApplication(int, char**) (main.cpp:220) ==17455== by 0x1006563: wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) (debugging.cpp:205) ==17455== by 0x7152C3: main (main.cpp:232) ==17455== --- apps/openmw/mwrender/renderingmanager.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 120f97e892..14000273db 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -293,8 +293,13 @@ namespace MWRender , mNavigator(navigator) , mMinimumAmbientLuminance(0.f) , mNightEyeFactor(0.f) + // TODO: Near clip should not need to be bounded like this, but too small values break OSG shadow calculations CPU-side. + // See issue: #6072 + , mNearClip(std::max(0.005f, Settings::Manager::getFloat("near clip", "Camera"))) + , mViewDistance(Settings::Manager::getFloat("viewing distance", "Camera")) , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) + , mFieldOfView(std::min(std::max(1.f, Settings::Manager::getFloat("field of view", "Camera")), 179.f)) { bool reverseZ = SceneUtil::getReverseZ(); @@ -515,12 +520,6 @@ namespace MWRender NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); - // TODO: Near clip should not need to be bounded like this, but too small values break OSG shadow calculations CPU-side. - // See issue: #6072 - mNearClip = std::max(0.005f, Settings::Manager::getFloat("near clip", "Camera")); - mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - float fov = Settings::Manager::getFloat("field of view", "Camera"); - mFieldOfView = std::min(std::max(1.f, fov), 179.f); float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera"); mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); From 501316a9f73c5702b0d6d3ea650773f128eaf0b5 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Oct 2021 00:25:14 +0200 Subject: [PATCH 1491/2859] Add missing initialization in MWPhysics::Object ==16218== Conditional jump or move depends on uninitialised value(s) ==16218== at 0xCDBC27: MWPhysics::Object::commitPositionChange() (object.cpp:68) ==16218== by 0xCE5B64: MWPhysics::PhysicsTaskScheduler::updatePtrAabb(std::shared_ptr const&) (mtphysics.cpp:446) ==16218== by 0xCE5CC3: operator() (mtphysics.cpp:432) ==16218== by 0xCE5CC3: for_each >, MWPhysics::PhysicsTaskScheduler::updateAabbs()::&)> > (stl_algo.h:3820) ==16218== by 0xCE5CC3: MWPhysics::PhysicsTaskScheduler::updateAabbs() (mtphysics.cpp:431) ==16218== by 0xCE5E35: MWPhysics::PhysicsTaskScheduler::afterPreStep() (mtphysics.cpp:586) ==16218== by 0xCE6238: operator() (mtphysics.cpp:533) ==16218== by 0xCE6238: wait > (barrier.hpp:33) ==16218== by 0xCE6238: MWPhysics::PhysicsTaskScheduler::doSimulation() (mtphysics.cpp:533) ==16218== by 0xCE6377: MWPhysics::PhysicsTaskScheduler::worker() (mtphysics.cpp:464) ==16218== by 0x74523C3: execute_native_thread_routine (thread.cc:82) ==16218== by 0x76FF258: start_thread (in /usr/lib/libpthread-2.33.so) ==16218== by 0x78155E2: clone (in /usr/lib/libc-2.33.so) ==16218== ==16218== Conditional jump or move depends on uninitialised value(s) ==16218== at 0xCDBC30: MWPhysics::Object::commitPositionChange() (object.cpp:73) ==16218== by 0xCE5B64: MWPhysics::PhysicsTaskScheduler::updatePtrAabb(std::shared_ptr const&) (mtphysics.cpp:446) ==16218== by 0xCE5CC3: operator() (mtphysics.cpp:432) ==16218== by 0xCE5CC3: for_each >, MWPhysics::PhysicsTaskScheduler::updateAabbs()::&)> > (stl_algo.h:3820) ==16218== by 0xCE5CC3: MWPhysics::PhysicsTaskScheduler::updateAabbs() (mtphysics.cpp:431) ==16218== by 0xCE5E35: MWPhysics::PhysicsTaskScheduler::afterPreStep() (mtphysics.cpp:586) ==16218== by 0xCE6238: operator() (mtphysics.cpp:533) ==16218== by 0xCE6238: wait > (barrier.hpp:33) ==16218== by 0xCE6238: MWPhysics::PhysicsTaskScheduler::doSimulation() (mtphysics.cpp:533) ==16218== by 0xCE6377: MWPhysics::PhysicsTaskScheduler::worker() (mtphysics.cpp:464) ==16218== by 0x74523C3: execute_native_thread_routine (thread.cc:82) ==16218== by 0x76FF258: start_thread (in /usr/lib/libpthread-2.33.so) ==16218== by 0x78155E2: clone (in /usr/lib/libc-2.33.so) ==16218== --- apps/openmw/mwphysics/object.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index 1484c1472c..520a57a4e8 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -49,8 +49,8 @@ namespace MWPhysics btVector3 mScale; osg::Vec3f mPosition; osg::Quat mRotation; - bool mScaleUpdatePending; - bool mTransformUpdatePending; + bool mScaleUpdatePending = false; + bool mTransformUpdatePending = false; mutable std::mutex mPositionMutex; PhysicsTaskScheduler* mTaskScheduler; }; From c88367b6a6d517eb543bd05cc8b7968d5d0d6168 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Oct 2021 13:03:15 +0200 Subject: [PATCH 1492/2859] Add sqlite3 helpers --- apps/openmw_test_suite/CMakeLists.txt | 5 + apps/openmw_test_suite/sqlite3/db.cpp | 15 + apps/openmw_test_suite/sqlite3/request.cpp | 270 +++++++++++++++++ apps/openmw_test_suite/sqlite3/statement.cpp | 25 ++ .../openmw_test_suite/sqlite3/transaction.cpp | 67 +++++ components/CMakeLists.txt | 9 + components/sqlite3/db.cpp | 31 ++ components/sqlite3/db.hpp | 21 ++ components/sqlite3/request.hpp | 275 ++++++++++++++++++ components/sqlite3/statement.cpp | 24 ++ components/sqlite3/statement.hpp | 35 +++ components/sqlite3/transaction.cpp | 33 +++ components/sqlite3/transaction.hpp | 27 ++ 13 files changed, 837 insertions(+) create mode 100644 apps/openmw_test_suite/sqlite3/db.cpp create mode 100644 apps/openmw_test_suite/sqlite3/request.cpp create mode 100644 apps/openmw_test_suite/sqlite3/statement.cpp create mode 100644 apps/openmw_test_suite/sqlite3/transaction.cpp create mode 100644 components/sqlite3/db.cpp create mode 100644 components/sqlite3/db.hpp create mode 100644 components/sqlite3/request.hpp create mode 100644 components/sqlite3/statement.cpp create mode 100644 components/sqlite3/statement.hpp create mode 100644 components/sqlite3/transaction.cpp create mode 100644 components/sqlite3/transaction.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 5665f59508..056a8d0a63 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -43,6 +43,11 @@ if (GTEST_FOUND AND GMOCK_FOUND) ../openmw/options.cpp openmw/options.cpp + + sqlite3/db.cpp + sqlite3/request.cpp + sqlite3/statement.cpp + sqlite3/transaction.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/sqlite3/db.cpp b/apps/openmw_test_suite/sqlite3/db.cpp new file mode 100644 index 0000000000..d2c0cd8674 --- /dev/null +++ b/apps/openmw_test_suite/sqlite3/db.cpp @@ -0,0 +1,15 @@ +#include + +#include + +namespace +{ + using namespace testing; + using namespace Sqlite3; + + TEST(Sqlite3DbTest, makeDbShouldCreateInMemoryDbWithSchema) + { + const auto db = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )"); + EXPECT_NE(db, nullptr); + } +} diff --git a/apps/openmw_test_suite/sqlite3/request.cpp b/apps/openmw_test_suite/sqlite3/request.cpp new file mode 100644 index 0000000000..e0094827d1 --- /dev/null +++ b/apps/openmw_test_suite/sqlite3/request.cpp @@ -0,0 +1,270 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace Sqlite3; + + template + struct InsertInt + { + static std::string_view text() noexcept { return "INSERT INTO ints (value) VALUES (:value)"; } + + static void bind(sqlite3& db, sqlite3_stmt& statement, T value) + { + bindParameter(db, statement, ":value", value); + } + }; + + struct InsertReal + { + static std::string_view text() noexcept { return "INSERT INTO reals (value) VALUES (:value)"; } + + static void bind(sqlite3& db, sqlite3_stmt& statement, double value) + { + bindParameter(db, statement, ":value", value); + } + }; + + struct InsertText + { + static std::string_view text() noexcept { return "INSERT INTO texts (value) VALUES (:value)"; } + + static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view value) + { + bindParameter(db, statement, ":value", value); + } + }; + + struct InsertBlob + { + static std::string_view text() noexcept { return "INSERT INTO blobs (value) VALUES (:value)"; } + + static void bind(sqlite3& db, sqlite3_stmt& statement, const std::vector& value) + { + bindParameter(db, statement, ":value", value); + } + }; + + struct GetAll + { + std::string mQuery; + + explicit GetAll(const std::string& table) : mQuery("SELECT value FROM " + table + " ORDER BY value") {} + + std::string_view text() noexcept { return mQuery; } + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + template + struct GetExact + { + std::string mQuery; + + explicit GetExact(const std::string& table) : mQuery("SELECT value FROM " + table + " WHERE value = :value") {} + + std::string_view text() noexcept { return mQuery; } + + static void bind(sqlite3& db, sqlite3_stmt& statement, const T& value) + { + bindParameter(db, statement, ":value", value); + } + }; + + struct GetInt64 + { + static std::string_view text() noexcept { return "SELECT value FROM ints WHERE value = :value"; } + + static void bind(sqlite3& db, sqlite3_stmt& statement, std::int64_t value) + { + bindParameter(db, statement, ":value", value); + } + }; + + struct GetNull + { + static std::string_view text() noexcept { return "SELECT NULL"; } + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + struct Int + { + int mValue = 0; + + Int() = default; + + explicit Int(int value) : mValue(value) {} + + Int& operator=(int value) + { + mValue = value; + return *this; + } + + friend bool operator==(const Int& l, const Int& r) + { + return l.mValue == r.mValue; + } + }; + + constexpr const char schema[] = R"( + CREATE TABLE ints ( value INTEGER ); + CREATE TABLE reals ( value REAL ); + CREATE TABLE texts ( value TEXT ); + CREATE TABLE blobs ( value BLOB ); + )"; + + struct Sqlite3RequestTest : Test + { + const Db mDb = makeDb(":memory:", schema); + }; + + TEST_F(Sqlite3RequestTest, executeShouldSupportInt) + { + Statement insert(*mDb, InsertInt {}); + EXPECT_EQ(execute(*mDb, insert, 13), 1); + EXPECT_EQ(execute(*mDb, insert, 42), 1); + Statement select(*mDb, GetAll("ints")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); + EXPECT_THAT(result, ElementsAre(std::tuple(13), std::tuple(42))); + } + + TEST_F(Sqlite3RequestTest, executeShouldSupportInt64) + { + Statement insert(*mDb, InsertInt {}); + const std::int64_t value = 1099511627776; + EXPECT_EQ(execute(*mDb, insert, value), 1); + Statement select(*mDb, GetAll("ints")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); + EXPECT_THAT(result, ElementsAre(std::tuple(value))); + } + + TEST_F(Sqlite3RequestTest, executeShouldSupportReal) + { + Statement insert(*mDb, InsertReal {}); + EXPECT_EQ(execute(*mDb, insert, 3.14), 1); + Statement select(*mDb, GetAll("reals")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); + EXPECT_THAT(result, ElementsAre(std::tuple(3.14))); + } + + TEST_F(Sqlite3RequestTest, executeShouldSupportText) + { + Statement insert(*mDb, InsertText {}); + const std::string text = "foo"; + EXPECT_EQ(execute(*mDb, insert, text), 1); + Statement select(*mDb, GetAll("texts")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); + EXPECT_THAT(result, ElementsAre(std::tuple(text))); + } + + TEST_F(Sqlite3RequestTest, executeShouldSupportBlob) + { + Statement insert(*mDb, InsertBlob {}); + const std::vector blob({std::byte(42), std::byte(13)}); + EXPECT_EQ(execute(*mDb, insert, blob), 1); + Statement select(*mDb, GetAll("blobs")); + std::vector>> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); + EXPECT_THAT(result, ElementsAre(std::tuple(blob))); + } + + TEST_F(Sqlite3RequestTest, requestShouldSupportInt) + { + Statement insert(*mDb, InsertInt {}); + const int value = 42; + EXPECT_EQ(execute(*mDb, insert, value), 1); + Statement select(*mDb, GetExact("ints")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); + EXPECT_THAT(result, ElementsAre(std::tuple(value))); + } + + TEST_F(Sqlite3RequestTest, requestShouldSupportInt64) + { + Statement insert(*mDb, InsertInt {}); + const std::int64_t value = 1099511627776; + EXPECT_EQ(execute(*mDb, insert, value), 1); + Statement select(*mDb, GetExact("ints")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); + EXPECT_THAT(result, ElementsAre(std::tuple(value))); + } + + TEST_F(Sqlite3RequestTest, requestShouldSupportReal) + { + Statement insert(*mDb, InsertReal {}); + const double value = 3.14; + EXPECT_EQ(execute(*mDb, insert, value), 1); + Statement select(*mDb, GetExact("reals")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); + EXPECT_THAT(result, ElementsAre(std::tuple(value))); + } + + TEST_F(Sqlite3RequestTest, requestShouldSupportText) + { + Statement insert(*mDb, InsertText {}); + const std::string text = "foo"; + EXPECT_EQ(execute(*mDb, insert, text), 1); + Statement select(*mDb, GetExact("texts")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), text); + EXPECT_THAT(result, ElementsAre(std::tuple(text))); + } + + TEST_F(Sqlite3RequestTest, requestShouldSupportBlob) + { + Statement insert(*mDb, InsertBlob {}); + const std::vector blob({std::byte(42), std::byte(13)}); + EXPECT_EQ(execute(*mDb, insert, blob), 1); + Statement select(*mDb, GetExact>("blobs")); + std::vector>> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), blob); + EXPECT_THAT(result, ElementsAre(std::tuple(blob))); + } + + TEST_F(Sqlite3RequestTest, requestResultShouldSupportNull) + { + Statement select(*mDb, GetNull {}); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); + EXPECT_THAT(result, ElementsAre(std::tuple(nullptr))); + } + + TEST_F(Sqlite3RequestTest, requestResultShouldSupportConstructibleFromInt) + { + Statement insert(*mDb, InsertInt {}); + const int value = 42; + EXPECT_EQ(execute(*mDb, insert, value), 1); + Statement select(*mDb, GetExact("ints")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); + EXPECT_THAT(result, ElementsAre(std::tuple(Int(value)))); + } + + TEST_F(Sqlite3RequestTest, requestShouldLimitOutput) + { + Statement insert(*mDb, InsertInt {}); + EXPECT_EQ(execute(*mDb, insert, 13), 1); + EXPECT_EQ(execute(*mDb, insert, 42), 1); + Statement select(*mDb, GetAll("ints")); + std::vector> result; + request(*mDb, select, std::back_inserter(result), 1); + EXPECT_THAT(result, ElementsAre(std::tuple(13))); + } +} diff --git a/apps/openmw_test_suite/sqlite3/statement.cpp b/apps/openmw_test_suite/sqlite3/statement.cpp new file mode 100644 index 0000000000..fb53ceebb2 --- /dev/null +++ b/apps/openmw_test_suite/sqlite3/statement.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace Sqlite3; + + struct Query + { + static std::string_view text() noexcept { return "SELECT 1"; } + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + TEST(Sqlite3StatementTest, makeStatementShouldCreateStatementWithPreparedQuery) + { + const auto db = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )"); + const Statement statement(*db, Query {}); + EXPECT_FALSE(statement.mNeedReset); + EXPECT_NE(statement.mHandle, nullptr); + EXPECT_EQ(statement.mQuery.text(), "SELECT 1"); + } +} diff --git a/apps/openmw_test_suite/sqlite3/transaction.cpp b/apps/openmw_test_suite/sqlite3/transaction.cpp new file mode 100644 index 0000000000..913fd34bce --- /dev/null +++ b/apps/openmw_test_suite/sqlite3/transaction.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace Sqlite3; + + struct InsertId + { + static std::string_view text() noexcept { return "INSERT INTO test (id) VALUES (42)"; } + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + struct GetIds + { + static std::string_view text() noexcept { return "SELECT id FROM test"; } + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + struct Sqlite3TransactionTest : Test + { + const Db mDb = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )"); + + void insertId() const + { + Statement insertId(*mDb, InsertId {}); + EXPECT_EQ(execute(*mDb, insertId), 1); + } + + std::vector> getIds() const + { + Statement getIds(*mDb, GetIds {}); + std::vector> result; + request(*mDb, getIds, std::back_inserter(result), std::numeric_limits::max()); + return result; + } + }; + + TEST_F(Sqlite3TransactionTest, shouldRollbackOnDestruction) + { + { + const Transaction transaction(*mDb); + insertId(); + } + EXPECT_THAT(getIds(), IsEmpty()); + } + + TEST_F(Sqlite3TransactionTest, commitShouldCommitTransaction) + { + { + Transaction transaction(*mDb); + insertId(); + transaction.commit(); + } + EXPECT_THAT(getIds(), ElementsAre(std::tuple(42))); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7ee3d184d8..8fc8c2046b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -201,6 +201,12 @@ add_component_dir(loadinglistener reporter ) +add_component_dir(sqlite3 + db + statement + transaction +) + set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) @@ -236,6 +242,8 @@ endif () include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) +find_package(SQLite3 REQUIRED) + add_library(components STATIC ${COMPONENT_FILES}) target_link_libraries(components @@ -268,6 +276,7 @@ target_link_libraries(components RecastNavigation::Recast Base64 + SQLite::SQLite3 ) target_link_libraries(components ${BULLET_LIBRARIES}) diff --git a/components/sqlite3/db.cpp b/components/sqlite3/db.cpp new file mode 100644 index 0000000000..54a156057d --- /dev/null +++ b/components/sqlite3/db.cpp @@ -0,0 +1,31 @@ +#include "db.hpp" + +#include + +#include +#include +#include + +namespace Sqlite3 +{ + void CloseSqlite3::operator()(sqlite3* handle) const noexcept + { + sqlite3_close(handle); + } + + Db makeDb(std::string_view path, const char* schema) + { + sqlite3* handle = nullptr; + const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + if (const int ec = sqlite3_open_v2(std::string(path).c_str(), &handle, flags, nullptr); ec != SQLITE_OK) + { + const std::string message(sqlite3_errmsg(handle)); + sqlite3_close(handle); + throw std::runtime_error("Failed to open database: " + message); + } + Db result(handle); + if (const int ec = sqlite3_exec(result.get(), schema, nullptr, nullptr, nullptr); ec != SQLITE_OK) + throw std::runtime_error("Failed create database schema: " + std::string(sqlite3_errmsg(handle))); + return result; + } +} diff --git a/components/sqlite3/db.hpp b/components/sqlite3/db.hpp new file mode 100644 index 0000000000..293ace4375 --- /dev/null +++ b/components/sqlite3/db.hpp @@ -0,0 +1,21 @@ +#ifndef OPENMW_COMPONENTS_SQLITE3_DB_H +#define OPENMW_COMPONENTS_SQLITE3_DB_H + +#include +#include + +struct sqlite3; + +namespace Sqlite3 +{ + struct CloseSqlite3 + { + void operator()(sqlite3* handle) const noexcept; + }; + + using Db = std::unique_ptr; + + Db makeDb(std::string_view path, const char* schema); +} + +#endif diff --git a/components/sqlite3/request.hpp b/components/sqlite3/request.hpp new file mode 100644 index 0000000000..378dd5fdf7 --- /dev/null +++ b/components/sqlite3/request.hpp @@ -0,0 +1,275 @@ +#ifndef OPENMW_COMPONENTS_SQLITE3_REQUEST_H +#define OPENMW_COMPONENTS_SQLITE3_REQUEST_H + +#include "statement.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Sqlite3 +{ + inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, int value) + { + if (const int ec = sqlite3_bind_int(&stmt, index, value); ec != SQLITE_OK) + throw std::runtime_error("Failed to bind int to parameter " + std::to_string(index) + + ": " + std::string(sqlite3_errmsg(&db))); + } + + inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, std::int64_t value) + { + if (const int ec = sqlite3_bind_int64(&stmt, index, value); ec != SQLITE_OK) + throw std::runtime_error("Failed to bind int64 to parameter " + std::to_string(index) + + ": " + std::string(sqlite3_errmsg(&db))); + } + + inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, double value) + { + if (const int ec = sqlite3_bind_double(&stmt, index, value); ec != SQLITE_OK) + throw std::runtime_error("Failed to bind double to parameter " + std::to_string(index) + + ": " + std::string(sqlite3_errmsg(&db))); + } + + inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, std::string_view value) + { + if (sqlite3_bind_text(&stmt, index, value.data(), static_cast(value.size()), SQLITE_STATIC) != SQLITE_OK) + throw std::runtime_error("Failed to bind text to parameter " + std::to_string(index) + + ": " + std::string(sqlite3_errmsg(&db))); + } + + inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, const std::vector& value) + { + if (sqlite3_bind_blob(&stmt, index, value.data(), static_cast(value.size()), SQLITE_STATIC) != SQLITE_OK) + throw std::runtime_error("Failed to bind blob to parameter " + std::to_string(index) + + ": " + std::string(sqlite3_errmsg(&db))); + } + + template + inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, const char* name, const T& value) + { + const int index = sqlite3_bind_parameter_index(&stmt, name); + if (index == 0) + throw std::logic_error("Parameter \"" + std::string(name) + "\" is not found"); + bindParameter(db, stmt, index, value); + } + + inline std::string sqliteTypeToString(int value) + { + switch (value) + { + case SQLITE_INTEGER: return "SQLITE_INTEGER"; + case SQLITE_FLOAT: return "SQLITE_FLOAT"; + case SQLITE_TEXT: return "SQLITE_TEXT"; + case SQLITE_BLOB: return "SQLITE_BLOB"; + case SQLITE_NULL: return "SQLITE_NULL"; + } + return "unsupported(" + std::to_string(value) + ")"; + } + + template + inline auto copyColumn(sqlite3& /*db*/, sqlite3_stmt& /*statement*/, int index, int type, T*& value) + { + if (type != SQLITE_NULL) + throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + + " that does not match expected output type: SQLITE_INTEGER or SQLITE_FLOAT"); + value = nullptr; + } + + template + inline auto copyColumn(sqlite3& /*db*/, sqlite3_stmt& statement, int index, int type, T& value) + { + switch (type) + { + case SQLITE_INTEGER: + value = static_cast(sqlite3_column_int64(&statement, index)); + return; + case SQLITE_FLOAT: + value = static_cast(sqlite3_column_double(&statement, index)); + return; + case SQLITE_NULL: + value = std::decay_t{}; + return; + } + throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + + " that does not match expected output type: SQLITE_INTEGER or SQLITE_FLOAT or SQLITE_NULL"); + } + + inline void copyColumn(sqlite3& db, sqlite3_stmt& statement, int index, int type, std::string& value) + { + if (type != SQLITE_TEXT) + throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + + " that does not match expected output type: SQLITE_TEXT"); + const unsigned char* const text = sqlite3_column_text(&statement, index); + if (text == nullptr) + { + if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) + throw std::runtime_error("Failed to read text from column " + std::to_string(index) + + ": " + sqlite3_errmsg(&db)); + value.clear(); + return; + } + const int size = sqlite3_column_bytes(&statement, index); + if (size <= 0) + { + if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) + throw std::runtime_error("Failed to get column bytes " + std::to_string(index) + + ": " + sqlite3_errmsg(&db)); + value.clear(); + return; + } + value.reserve(static_cast(size)); + value.assign(reinterpret_cast(text), reinterpret_cast(text) + size); + } + + inline void copyColumn(sqlite3& db, sqlite3_stmt& statement, int index, int type, std::vector& value) + { + if (type != SQLITE_BLOB) + throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + + " that does not match expected output type: SQLITE_BLOB"); + const void* const blob = sqlite3_column_blob(&statement, index); + if (blob == nullptr) + { + if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) + throw std::runtime_error("Failed to read blob from column " + std::to_string(index) + + ": " + sqlite3_errmsg(&db)); + value.clear(); + return; + } + const int size = sqlite3_column_bytes(&statement, index); + if (size <= 0) + { + if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) + throw std::runtime_error("Failed to get column bytes " + std::to_string(index) + + ": " + sqlite3_errmsg(&db)); + value.clear(); + return; + } + value.reserve(static_cast(size)); + value.assign(static_cast(blob), static_cast(blob) + size); + } + + template + inline void getColumnsImpl(sqlite3& db, sqlite3_stmt& statement, T& row) + { + if constexpr (0 < index && index <= std::tuple_size_v) + { + const int column = index - 1; + if (const int number = sqlite3_column_count(&statement); column >= number) + throw std::out_of_range("Column number is out of range: " + std::to_string(column) + + " >= " + std::to_string(number)); + const int type = sqlite3_column_type(&statement, column); + switch (type) + { + case SQLITE_INTEGER: + case SQLITE_FLOAT: + case SQLITE_TEXT: + case SQLITE_BLOB: + case SQLITE_NULL: + copyColumn(db, statement, column, type, std::get(row)); + break; + default: + throw std::runtime_error("Column " + std::to_string(column) + + " has unnsupported column type: " + sqliteTypeToString(type)); + } + getColumnsImpl(db, statement, row); + } + } + + template + inline void getColumns(sqlite3& db, sqlite3_stmt& statement, T& row) + { + getColumnsImpl>(db, statement, row); + } + + template + inline void getRow(sqlite3& db, sqlite3_stmt& statement, T& row) + { + auto tuple = std::tie(row); + getColumns(db, statement, tuple); + } + + template + inline void getRow(sqlite3& db, sqlite3_stmt& statement, std::tuple& row) + { + getColumns(db, statement, row); + } + + template + inline void getRow(sqlite3& db, sqlite3_stmt& statement, std::back_insert_iterator& it) + { + typename T::value_type row; + getRow(db, statement, row); + it = std::move(row); + } + + template + inline void prepare(sqlite3& db, Statement& statement, Args&& ... args) + { + if (statement.mNeedReset) + { + if (sqlite3_reset(statement.mHandle.get()) == SQLITE_OK + && sqlite3_clear_bindings(statement.mHandle.get()) == SQLITE_OK) + statement.mNeedReset = false; + else + statement.mHandle = makeStatementHandle(db, statement.mQuery.text()); + } + statement.mQuery.bind(db, *statement.mHandle, std::forward(args) ...); + } + + template + inline bool executeStep(sqlite3& db, const Statement& statement) + { + switch (sqlite3_step(statement.mHandle.get())) + { + case SQLITE_ROW: return true; + case SQLITE_DONE: return false; + } + throw std::runtime_error("Failed to execute statement step: " + std::string(sqlite3_errmsg(&db))); + } + + template + inline I request(sqlite3& db, Statement& statement, I out, std::size_t max, Args&& ... args) + { + try + { + statement.mNeedReset = true; + prepare(db, statement, std::forward(args) ...); + for (std::size_t i = 0; executeStep(db, statement) && i < max; ++i) + getRow(db, *statement.mHandle, *out++); + return out; + } + catch (const std::exception& e) + { + throw std::runtime_error("Failed perform request \"" + std::string(statement.mQuery.text()) + + "\": " + std::string(e.what())); + } + } + + template + inline int execute(sqlite3& db, Statement& statement, Args&& ... args) + { + try + { + statement.mNeedReset = true; + prepare(db, statement, std::forward(args) ...); + if (executeStep(db, statement)) + throw std::logic_error("Execute cannot return rows"); + return sqlite3_changes(&db); + } + catch (const std::exception& e) + { + throw std::runtime_error("Failed to execute statement \"" + std::string(statement.mQuery.text()) + + "\": " + std::string(e.what())); + } + } +} + +#endif diff --git a/components/sqlite3/statement.cpp b/components/sqlite3/statement.cpp new file mode 100644 index 0000000000..07ca6b8ddf --- /dev/null +++ b/components/sqlite3/statement.cpp @@ -0,0 +1,24 @@ +#include "statement.hpp" + +#include + +#include +#include +#include + +namespace Sqlite3 +{ + void CloseSqlite3Stmt::operator()(sqlite3_stmt* handle) const noexcept + { + sqlite3_finalize(handle); + } + + StatementHandle makeStatementHandle(sqlite3& db, std::string_view query) + { + sqlite3_stmt* stmt = nullptr; + if (const int ec = sqlite3_prepare_v2(&db, query.data(), static_cast(query.size()), &stmt, nullptr); ec != SQLITE_OK) + throw std::runtime_error("Failed to prepare statement for query \"" + std::string(query) + "\": " + + std::string(sqlite3_errmsg(&db))); + return StatementHandle(stmt); + } +} diff --git a/components/sqlite3/statement.hpp b/components/sqlite3/statement.hpp new file mode 100644 index 0000000000..469e63933c --- /dev/null +++ b/components/sqlite3/statement.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_COMPONENTS_SQLITE3_STATEMENT_H +#define OPENMW_COMPONENTS_SQLITE3_STATEMENT_H + +#include +#include +#include + +struct sqlite3; +struct sqlite3_stmt; + +namespace Sqlite3 +{ + struct CloseSqlite3Stmt + { + void operator()(sqlite3_stmt* handle) const noexcept; + }; + + using StatementHandle = std::unique_ptr; + + StatementHandle makeStatementHandle(sqlite3& db, std::string_view query); + + template + struct Statement + { + bool mNeedReset = false; + StatementHandle mHandle; + Query mQuery; + + explicit Statement(sqlite3& db, Query query = Query {}) + : mHandle(makeStatementHandle(db, query.text())), + mQuery(std::move(query)) {} + }; +} + +#endif diff --git a/components/sqlite3/transaction.cpp b/components/sqlite3/transaction.cpp new file mode 100644 index 0000000000..3012538652 --- /dev/null +++ b/components/sqlite3/transaction.cpp @@ -0,0 +1,33 @@ +#include "transaction.hpp" + +#include + +#include + +#include +#include + +namespace Sqlite3 +{ + void Rollback::operator()(sqlite3* db) const + { + if (db == nullptr) + return; + if (const int ec = sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr); ec != SQLITE_OK) + Log(Debug::Warning) << "Failed to rollback SQLite3 transaction: " << std::string(sqlite3_errmsg(db)); + } + + Transaction::Transaction(sqlite3& db) + : mDb(&db) + { + if (const int ec = sqlite3_exec(mDb.get(), "BEGIN", nullptr, nullptr, nullptr); ec != SQLITE_OK) + throw std::runtime_error("Failed to start transaction: " + std::string(sqlite3_errmsg(mDb.get()))); + } + + void Transaction::commit() + { + if (const int ec = sqlite3_exec(mDb.get(), "COMMIT", nullptr, nullptr, nullptr); ec != SQLITE_OK) + throw std::runtime_error("Failed to commit transaction: " + std::string(sqlite3_errmsg(mDb.get()))); + (void) mDb.release(); + } +} diff --git a/components/sqlite3/transaction.hpp b/components/sqlite3/transaction.hpp new file mode 100644 index 0000000000..88b780a0a5 --- /dev/null +++ b/components/sqlite3/transaction.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_COMPONENTS_SQLITE3_TRANSACTION_H +#define OPENMW_COMPONENTS_SQLITE3_TRANSACTION_H + +#include + +struct sqlite3; + +namespace Sqlite3 +{ + struct Rollback + { + void operator()(sqlite3* handle) const; + }; + + class Transaction + { + public: + Transaction(sqlite3& db); + + void commit(); + + private: + std::unique_ptr mDb; + }; +} + +#endif From add6eb9c95bcb4fea5cdb1e797cb5e78a57c3770 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 16 Oct 2021 21:40:41 +0200 Subject: [PATCH 1493/2859] Fix particles and magnitudes for (damage) over time effects --- apps/openmw/mwmechanics/spelleffects.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index bc84a1df5e..0e67f06ba2 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -806,6 +806,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac } if(effect.mMagnitude == 0) { + effect.mMagnitude = oldMagnitude; effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; effect.mTimeLeft -= dt; return false; @@ -1027,15 +1028,15 @@ void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellP { if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) return; - const auto world = MWBase::Environment::get().getWorld(); auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); - const auto* magicEffect = world->getStore().get().find(effect.mEffectId); - if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) - magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMagnitude)); + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMagnitude)); removeMagicEffect(target, spellParams, effect); - auto anim = world->getAnimation(target); - if(anim) - anim->removeEffect(effect.mEffectId); + if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) + { + auto anim = MWBase::Environment::get().getWorld()->getAnimation(target); + if(anim) + anim->removeEffect(effect.mEffectId); + } } } From 01605433cb6fc43928f1456ebe43105d2e6907c0 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 16 Oct 2021 19:48:13 +0000 Subject: [PATCH 1494/2859] quadtreeworld.cpp (#3174) Currently, we disable a paging root node that we only need in exterior cells by setting its node mask to 0 when transitioning into an interior cell. Node masks are not ideal for this usage case because Node::getBound is unaware of masks. With this PR we just detach the unused node from the scene graph. _shadowedScene->getBound() in the MWShadowTechnique should return a much better value in interior cells with these changes. --- components/terrain/quadtreeworld.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index c2d212f8a5..0282eb8de1 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -498,9 +498,8 @@ void QuadTreeWorld::enable(bool enabled) if (!mRootNode->getNumParents()) mTerrainRoot->addChild(mRootNode); } - - if (mRootNode) - mRootNode->setNodeMask(enabled ? ~0 : 0); + else if (mRootNode) + mTerrainRoot->removeChild(mRootNode); } View* QuadTreeWorld::createView() From cc009d246fb81cc4269950912eed445da3e0e9b3 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 16 Oct 2021 18:37:34 -0700 Subject: [PATCH 1495/2859] do not clear depth buffer in first person --- CHANGELOG.md | 1 + apps/openmw/mwrender/npcanimation.cpp | 40 ++++++++++++++++++++++++-- apps/openmw/mwrender/postprocessor.cpp | 3 ++ apps/openmw/mwrender/postprocessor.hpp | 2 ++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b84dfd7e80..e2c57f1632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving Feature #6032: Reverse-z depth buffer + Feature #6078: First person should not clear depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering Feature #6249: Alpha testing support for Collada diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 28987064f3..0e100326dd 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -44,6 +44,7 @@ #include "renderbin.hpp" #include "vismask.hpp" #include "util.hpp" +#include "postprocessor.hpp" namespace { @@ -330,6 +331,8 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) } /// @brief A RenderBin callback to clear the depth buffer before rendering. +/// Switches depth attachments to a proxy renderbuffer, reattaches original depth then redraws first person root. +/// This gives a complete depth buffer which can be used for postprocessing, buffer resolves as if depth was never cleared. class DepthClearCallback : public osgUtil::RenderBin::DrawCallback { public: @@ -337,18 +340,49 @@ public: { mDepth = SceneUtil::createDepth(); mDepth->setWriteMask(true); + + mStateSet = new osg::StateSet; + mStateSet->setAttributeAndModes(new osg::ColorMask(false, false, false, false), osg::StateAttribute::ON); + mStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); } void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override { - renderInfo.getState()->applyAttribute(mDepth); + osg::State* state = renderInfo.getState(); + + PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); + + state->applyAttribute(mDepth); + + if (postProcessor && postProcessor->getFirstPersonRBProxy()) + { + osg::GLExtensions* ext = state->get(); + + osg::FrameBufferAttachment(postProcessor->getFirstPersonRBProxy()).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); - glClear(GL_DEPTH_BUFFER_BIT); + glClear(GL_DEPTH_BUFFER_BIT); + // color accumulation pass + bin->drawImplementation(renderInfo, previous); - bin->drawImplementation(renderInfo, previous); + auto primaryFBO = postProcessor->getMsaaFbo() ? postProcessor->getMsaaFbo() : postProcessor->getFbo(); + primaryFBO->getAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); + + state->pushStateSet(mStateSet); + state->apply(); + // depth accumulation pass + bin->drawImplementation(renderInfo, previous); + state->popStateSet(); + } + else + { + // fallback to standard depth clear when we are not rendering our main scene via an intermediate FBO + glClear(GL_DEPTH_BUFFER_BIT); + bin->drawImplementation(renderInfo, previous); + } } osg::ref_ptr mDepth; + osg::ref_ptr mStateSet; }; /// Overrides Field of View to given value for rendering the subgraph. diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index bc00676d7b..1ee5745cb4 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -183,6 +183,9 @@ namespace MWRender mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthRB)); } + if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY")) + mFirstPersonDepthRBProxy = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); + mViewer->getCamera()->resize(width, height); mHUDCamera->resize(width, height); mRendering.updateProjectionMatrix(); diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index f93f3a5b66..0d03d4b500 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -23,6 +23,7 @@ namespace MWRender auto getMsaaFbo() { return mMsaaFbo; } auto getFbo() { return mFbo; } + auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; } int getDepthFormat() { return mDepthFormat; } @@ -37,6 +38,7 @@ namespace MWRender osg::ref_ptr mMsaaFbo; osg::ref_ptr mFbo; + osg::ref_ptr mFirstPersonDepthRBProxy; osg::ref_ptr mSceneTex; osg::ref_ptr mDepthTex; From 362c124e8fe6274c7f543c8ad8a43fe46de8c0f9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 17 Oct 2021 17:44:07 +0400 Subject: [PATCH 1496/2859] Allow to place levelled creatures to the game world (bug 6347) --- CHANGELOG.md | 2 +- apps/openmw/mwclass/creaturelevlist.cpp | 19 +++++++++++++++++++ apps/openmw/mwclass/creaturelevlist.hpp | 4 ++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25ae0c45d0..d3c70897cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Bug #6322: Total sold/cost should reset to 0 when there are no items offered Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Bug #6326: Detect Enchantment/Key should detect items in unresolved containers + Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console @@ -79,7 +80,6 @@ Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp - 0.47.0 ------ diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index e8023ce26b..ddb5fd2b38 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -5,6 +5,7 @@ #include "../mwmechanics/levelledlist.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -27,6 +28,24 @@ namespace MWClass } }; + MWWorld::Ptr CreatureLevList::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); + + return MWWorld::Ptr(cell.insert(ref), &cell); + } + + void CreatureLevList::adjustPosition(const MWWorld::Ptr& ptr, bool force) const + { + if (ptr.getRefData().getCustomData() == nullptr) + return; + + CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); + MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (!creature.isEmpty()) + MWBase::Environment::get().getWorld()->adjustPosition(creature, force); + } + std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 35152a9422..b3a940682c 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -32,6 +32,10 @@ namespace MWClass ///< Write additional state from \a ptr into \a state. void respawn (const MWWorld::Ptr& ptr) const override; + + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + + void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; }; } From 18d3102148bab1a74e48e467401100a3259da2e3 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Oct 2021 02:52:22 +0200 Subject: [PATCH 1497/2859] Do not use union to access FIXED_STRING<4> as int https://en.cppreference.com/w/cpp/language/union: > It's undefined behavior to read from the member of the union that wasn't most recently written. --- apps/esmtool/esmtool.cpp | 12 ++++++------ apps/esmtool/record.cpp | 2 +- apps/essimporter/importer.cpp | 4 ++-- apps/launcher/utils/cellnameloader.cpp | 2 +- apps/opencs/model/world/data.cpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 18 +++++++++--------- apps/openmw/mwworld/esmstore.cpp | 12 ++++++------ components/esm/cellref.cpp | 2 +- components/esm/debugprofile.cpp | 2 +- components/esm/esmcommon.hpp | 24 +++++++++++++++++------- components/esm/filter.cpp | 2 +- components/esm/loadacti.cpp | 2 +- components/esm/loadalch.cpp | 2 +- components/esm/loadappa.cpp | 2 +- components/esm/loadarmo.cpp | 2 +- components/esm/loadbody.cpp | 2 +- components/esm/loadbook.cpp | 2 +- components/esm/loadbsgn.cpp | 2 +- components/esm/loadcell.cpp | 4 ++-- components/esm/loadclas.cpp | 2 +- components/esm/loadclot.cpp | 2 +- components/esm/loadcont.cpp | 2 +- components/esm/loadcrea.cpp | 2 +- components/esm/loaddial.cpp | 2 +- components/esm/loaddoor.cpp | 2 +- components/esm/loadench.cpp | 2 +- components/esm/loadfact.cpp | 2 +- components/esm/loadinfo.cpp | 2 +- components/esm/loadingr.cpp | 2 +- components/esm/loadland.cpp | 4 ++-- components/esm/loadlevlist.cpp | 2 +- components/esm/loadligh.cpp | 2 +- components/esm/loadlock.cpp | 2 +- components/esm/loadltex.cpp | 2 +- components/esm/loadmgef.cpp | 2 +- components/esm/loadmisc.cpp | 2 +- components/esm/loadnpc.cpp | 2 +- components/esm/loadpgrd.cpp | 2 +- components/esm/loadprob.cpp | 2 +- components/esm/loadrace.cpp | 2 +- components/esm/loadregn.cpp | 2 +- components/esm/loadrepa.cpp | 2 +- components/esm/loadscpt.cpp | 2 +- components/esm/loadskil.cpp | 2 +- components/esm/loadsndg.cpp | 2 +- components/esm/loadsoun.cpp | 2 +- components/esm/loadspel.cpp | 2 +- components/esm/loadsscr.cpp | 2 +- components/esm/loadstat.cpp | 2 +- components/esm/loadweap.cpp | 2 +- components/esm/transport.cpp | 4 ++-- 51 files changed, 89 insertions(+), 79 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index bf5f47a76d..a0ee654da9 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -367,10 +367,10 @@ int load(Arguments& info) EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); if (record == nullptr) { - if (skipped.count(n.intval) == 0) + if (skipped.count(n.toInt()) == 0) { std::cout << "Skipping " << n.toString() << " records.\n"; - skipped.emplace(n.intval); + skipped.emplace(n.toInt()); } esm.skipRecord(); @@ -402,7 +402,7 @@ int load(Arguments& info) record->print(); } - if (record->getType().intval == ESM::REC_CELL && loadCells && interested) + if (record->getType().toInt() == ESM::REC_CELL && loadCells && interested) { loadCell(record->cast()->get(), esm, info); } @@ -415,7 +415,7 @@ int load(Arguments& info) { delete record; } - ++info.data.mRecordStats[n.intval]; + ++info.data.mRecordStats[n.toInt()]; } } catch(std::exception &e) { @@ -459,7 +459,7 @@ int clone(Arguments& info) for (std::pair stat : info.data.mRecordStats) { ESM::NAME name; - name.intval = stat.first; + name = stat.first; int amount = stat.second; std::cout << std::setw(digitCount) << amount << " " << name.toString() << " "; if (++i % 3 == 0) @@ -496,7 +496,7 @@ int clone(Arguments& info) esm.startRecord(typeName.toString(), record->getFlags()); record->save(esm); - if (typeName.intval == ESM::REC_CELL) { + if (typeName.toInt() == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 55170e232e..1de3288ad4 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -176,7 +176,7 @@ RecordBase::create(const ESM::NAME type) { RecordBase *record = nullptr; - switch (type.intval) { + switch (type.toInt()) { case ESM::REC_ACTI: { record = new EsmTool::Record; diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 27f77e40d1..84f13b8c91 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -324,14 +324,14 @@ namespace ESSImport ESM::NAME n = esm.getRecName(); esm.getRecHeader(); - auto it = converters.find(n.intval); + auto it = converters.find(n.toInt()); if (it != converters.end()) { it->second->read(esm); } else { - if (unknownRecords.insert(n.intval).second) + if (unknownRecords.insert(n.toInt()).second) { std::ios::fmtflags f(std::cerr.flags()); std::cerr << "Error: unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp index e7f6e83e74..594946394c 100644 --- a/apps/launcher/utils/cellnameloader.cpp +++ b/apps/launcher/utils/cellnameloader.cpp @@ -35,7 +35,7 @@ QSet CellNameLoader::getCellNames(QStringList &contentPaths) bool CellNameLoader::isCellRecord(ESM::NAME &recordName) { - return recordName.intval == ESM::REC_CELL; + return recordName.toInt() == ESM::REC_CELL; } QString CellNameLoader::getCellName(ESM::ESMReader &esmReader) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 3723dac46f..ec4c1f6a13 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1086,7 +1086,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) bool unhandledRecord = false; - switch (n.intval) + switch (n.toInt()) { case ESM::REC_GLOB: mGlobals.load (*mReader, mBase); break; case ESM::REC_GMST: mGmsts.load (*mReader, mBase); break; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 4e4a8e95d6..4387d7faa7 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -406,7 +406,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str ESM::NAME n = reader.getRecName(); reader.getRecHeader(); - switch (n.intval) + switch (n.toInt()) { case ESM::REC_SAVE: { @@ -427,12 +427,12 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_JOUR_LEGACY: case ESM::REC_QUES: - MWBase::Environment::get().getJournal()->readRecord (reader, n.intval); + MWBase::Environment::get().getJournal()->readRecord (reader, n.toInt()); break; case ESM::REC_DIAS: - MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.intval); + MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.toInt()); break; case ESM::REC_ALCH: @@ -457,7 +457,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_LEVI: case ESM::REC_CREA: case ESM::REC_CONT: - MWBase::Environment::get().getWorld()->readRecord(reader, n.intval, contentFileMap); + MWBase::Environment::get().getWorld()->readRecord(reader, n.toInt(), contentFileMap); break; case ESM::REC_CAM_: @@ -466,7 +466,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_GSCR: - MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.toInt(), contentFileMap); break; case ESM::REC_GMAP: @@ -474,21 +474,21 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_ASPL: case ESM::REC_MARK: - MWBase::Environment::get().getWindowManager()->readRecord(reader, n.intval); + MWBase::Environment::get().getWindowManager()->readRecord(reader, n.toInt()); break; case ESM::REC_DCOU: case ESM::REC_STLN: - MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.intval); + MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.toInt()); break; case ESM::REC_INPU: - MWBase::Environment::get().getInputManager()->readRecord(reader, n.intval); + MWBase::Environment::get().getInputManager()->readRecord(reader, n.toInt()); break; case ESM::REC_LUAM: - MWBase::Environment::get().getLuaManager()->readRecord(reader, n.intval); + MWBase::Environment::get().getLuaManager()->readRecord(reader, n.toInt()); break; default: diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index befe65d641..c5b0fff00f 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,10 +190,10 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) esm.getRecHeader(); // Look up the record type. - std::map::iterator it = mStores.find(n.intval); + std::map::iterator it = mStores.find(n.toInt()); if (it == mStores.end()) { - if (n.intval == ESM::REC_INFO) { + if (n.toInt() == ESM::REC_INFO) { if (dialogue) { dialogue->readInfo(esm, esm.getIndex() != 0); @@ -203,12 +203,12 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) Log(Debug::Error) << "Error: info record without dialog"; esm.skipRecord(); } - } else if (n.intval == ESM::REC_MGEF) { + } else if (n.toInt() == ESM::REC_MGEF) { mMagicEffects.load (esm); - } else if (n.intval == ESM::REC_SKIL) { + } else if (n.toInt() == ESM::REC_SKIL) { mSkills.load (esm); } - else if (n.intval==ESM::REC_FILT || n.intval == ESM::REC_DBGP) + else if (n.toInt() == ESM::REC_FILT || n.toInt() == ESM::REC_DBGP) { // ignore project file only records esm.skipRecord(); @@ -224,7 +224,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) continue; } - if (n.intval==ESM::REC_DIAL) { + if (n.toInt() == ESM::REC_DIAL) { dialogue = const_cast(mDialogs.find(id.mId)); } else { dialogue = nullptr; diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 81e0b9f557..7126459871 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -66,7 +66,7 @@ void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'U','N','A','M'>::value: esm.getHT(mReferenceBlocked); diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 6276258c48..090d2bfe6d 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -14,7 +14,7 @@ void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 10e05de905..7caaae2afb 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -102,25 +102,35 @@ struct FIXED_STRING : public FIXED_STRING_BASE template <> struct FIXED_STRING<4> : public FIXED_STRING_BASE { - union { - char data[4]; - uint32_t intval; - }; + char data[4]; using FIXED_STRING_BASE::operator==; using FIXED_STRING_BASE::operator!=; - bool operator==(uint32_t v) const { return v == intval; } - bool operator!=(uint32_t v) const { return v != intval; } + bool operator==(uint32_t v) const { return v == toInt(); } + bool operator!=(uint32_t v) const { return v != toInt(); } + + FIXED_STRING<4>& operator=(std::uint32_t value) + { + std::memcpy(data, &value, sizeof(data)); + return *this; + } void assign(const std::string& value) { - intval = 0; + std::memset(data, 0, sizeof(data)); std::memcpy(data, value.data(), std::min(value.size(), sizeof(data))); } char const* ro_data() const { return data; } char* rw_data() { return data; } + + std::uint32_t toInt() const + { + std::uint32_t value; + std::memcpy(&value, data, sizeof(data)); + return value; + } }; typedef FIXED_STRING<4> NAME; diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 8d1b755055..76a518c63d 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -14,7 +14,7 @@ void ESM::Filter::load (ESMReader& esm, bool &isDeleted) while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().intval; + uint32_t name = esm.retSubName().toInt(); switch (name) { case ESM::SREC_NAME: diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index 6a08bca749..fcb0954918 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index b7c81ebdb4..ad30570f74 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -20,7 +20,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index e9ff3ea86c..1113870197 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 902d4a4467..cab0d52a88 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -50,7 +50,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 239cff7c8b..c7f6bce40a 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 756f01da9c..07b9a6b50f 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 7514f1f85b..d767eb66e0 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -19,7 +19,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index d2cb23146f..b2c95ad25f 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -70,7 +70,7 @@ namespace ESM while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mName = esm.getHString(); @@ -117,7 +117,7 @@ namespace ESM while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'I','N','T','V'>::value: int waterl; diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 7526fe4f52..c70f7dd0d3 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -48,7 +48,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index cf03dbad36..8f2aff40fc 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -20,7 +20,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 03092571a4..b7757646a1 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -42,7 +42,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 02d664e1f2..590a68bc35 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -31,7 +31,7 @@ namespace ESM { while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index 59fb13484b..535ea23380 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -28,7 +28,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'D','A','T','A'>::value: { diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index 3c446789b7..d5bf51b4da 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index ed3de90b50..db0727099c 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -19,7 +19,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index b71348de44..61d0e1dcf4 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -41,7 +41,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 15921249f4..6c54b0b9ab 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -23,7 +23,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mData, 12); diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 26873a6eea..1aba000267 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 1fac79082d..d7dcd47c69 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -47,7 +47,7 @@ namespace ESM while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'I','N','T','V'>::value: esm.getSubHeader(); @@ -83,7 +83,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'V','N','M','L'>::value: esm.skipHSub(); diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index 450bf06ecb..acf97f4259 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -16,7 +16,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index e53c82cc3f..32c0b16243 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 88ce77e34e..bea5c86773 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index 6bdc40e5fc..f2a1e17d49 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 4bc09920e9..badbbb2133 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -209,7 +209,7 @@ void MagicEffect::load(ESMReader &esm, bool &isDeleted) while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 39d589eac8..a60012e74b 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index e1fb9b5931..b86ea6f8bc 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -26,7 +26,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 7bf60ca5fc..b10e3c453d 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -46,7 +46,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mCell = esm.getHString(); diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index 8f03189686..edc6b89cd1 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index 44dbde7742..35ad4421f1 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -30,7 +30,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index e39887a1b1..9cfdaaabc0 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -17,7 +17,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index ecb693c0c5..3fe62e740a 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 8715c83dcb..76cb3c0149 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -91,7 +91,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'S','C','H','D'>::value: { diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 9f58176f35..9236302dea 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -137,7 +137,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::FourCC<'I','N','D','X'>::value: esm.getHT(mIndex); diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index c439d0ca66..c1170b36c7 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index ed8fc519a6..9d58e19e99 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 5983cdcdf5..cc26024426 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -20,7 +20,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index 9e060ab1a7..723fb3bf10 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 1073ab48e2..e0c9eb2c7e 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -19,7 +19,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 78b9bb4073..08c5a3b641 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -18,7 +18,7 @@ namespace ESM while (esm.hasMoreSubs()) { esm.getSubName(); - switch (esm.retSubName().intval) + switch (esm.retSubName().toInt()) { case ESM::SREC_NAME: mId = esm.getHString(); diff --git a/components/esm/transport.cpp b/components/esm/transport.cpp index 11676ea723..9d40debf73 100644 --- a/components/esm/transport.cpp +++ b/components/esm/transport.cpp @@ -10,13 +10,13 @@ namespace ESM void Transport::add(ESMReader &esm) { - if (esm.retSubName().intval == ESM::FourCC<'D','O','D','T'>::value) + if (esm.retSubName().toInt() == ESM::FourCC<'D','O','D','T'>::value) { Dest dodt; esm.getHExact(&dodt.mPos, 24); mList.push_back(dodt); } - else if (esm.retSubName().intval == ESM::FourCC<'D','N','A','M'>::value) + else if (esm.retSubName().toInt() == ESM::FourCC<'D','N','A','M'>::value) { const std::string name = esm.getHString(); if (mList.empty()) From 046b5f83ee756169808ab55cd3d8dd5cebfe86ac Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Oct 2021 19:07:55 +0200 Subject: [PATCH 1498/2859] Add ESM data loader A component to load ESM content files with limited support for record types and selection which of them to load. Supported record types are: ACTI, CELL, CONT, DOOR, GMST, LAND, STAT. --- apps/openmw_test_suite/CMakeLists.txt | 15 + apps/openmw_test_suite/esmloader/load.cpp | 104 ++++++ apps/openmw_test_suite/esmloader/settings.hpp | 20 ++ apps/openmw_test_suite/openmw_test_suite.cpp | 5 + components/CMakeLists.txt | 5 + components/esm/esmcommon.hpp | 7 + components/esmloader/esmdata.cpp | 77 ++++ components/esmloader/esmdata.hpp | 52 +++ components/esmloader/lessbyid.hpp | 24 ++ components/esmloader/load.cpp | 334 ++++++++++++++++++ components/esmloader/load.hpp | 39 ++ components/esmloader/record.hpp | 44 +++ 12 files changed, 726 insertions(+) create mode 100644 apps/openmw_test_suite/esmloader/load.cpp create mode 100644 apps/openmw_test_suite/esmloader/settings.hpp create mode 100644 components/esmloader/esmdata.cpp create mode 100644 components/esmloader/esmdata.hpp create mode 100644 components/esmloader/lessbyid.hpp create mode 100644 components/esmloader/load.cpp create mode 100644 components/esmloader/load.hpp create mode 100644 components/esmloader/record.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 056a8d0a63..67001a7885 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -48,6 +48,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) sqlite3/request.cpp sqlite3/statement.cpp sqlite3/transaction.cpp + + esmloader/load.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) @@ -73,4 +75,17 @@ if (GTEST_FOUND AND GMOCK_FOUND) endif (CMAKE_CL_64) endif (MSVC) + include(FetchContent) + FetchContent_Declare(example_suite_template_game + URL https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame + URL_HASH MD5=bf3691034a38611534c74c3b89a7d2c3 + SOURCE_DIR fetched/example_suite_template_game + DOWNLOAD_NO_EXTRACT ON + ) + FetchContent_MakeAvailableExcludeFromAll(example_suite_template_game) + + add_custom_target(example_suite_template_game SOURCES fetched/example_suite_template_game/template.omwgame) + + add_dependencies(openmw_test_suite example_suite_template_game) + endif() diff --git a/apps/openmw_test_suite/esmloader/load.cpp b/apps/openmw_test_suite/esmloader/load.cpp new file mode 100644 index 0000000000..71a8d2534a --- /dev/null +++ b/apps/openmw_test_suite/esmloader/load.cpp @@ -0,0 +1,104 @@ +#include "settings.hpp" + +#include +#include +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace EsmLoader; + using EsmLoaderTests::Settings; + + struct EsmLoaderTest : Test + { + const boost::filesystem::path mDataDir {Settings::impl().mBasePath / "apps/openmw_test_suite/fetched/example_suite_template_game"}; + const Files::PathContainer mDataDirs {{mDataDir.string()}}; + const Files::Collections mFileCollections {mDataDirs, true}; + const std::vector mContentFiles {{"template.omwgame"}}; + }; + + TEST_F(EsmLoaderTest, loadEsmDataShouldSupportOmwgame) + { + Query query; + query.mLoadActivators = true; + query.mLoadCells = true; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = true; + query.mLoadLands = true; + query.mLoadStatics = true; + std::vector readers(mContentFiles.size()); + ToUTF8::Utf8Encoder* const encoder = nullptr; + const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); + EXPECT_EQ(esmData.mActivators.size(), 0); + EXPECT_EQ(esmData.mCells.size(), 1); + EXPECT_EQ(esmData.mContainers.size(), 0); + EXPECT_EQ(esmData.mDoors.size(), 0); + EXPECT_EQ(esmData.mGameSettings.size(), 1521); + EXPECT_EQ(esmData.mLands.size(), 1); + EXPECT_EQ(esmData.mStatics.size(), 2); + } + + TEST_F(EsmLoaderTest, shouldIgnoreCellsWhenQueryLoadCellsIsFalse) + { + Query query; + query.mLoadActivators = true; + query.mLoadCells = false; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = true; + query.mLoadLands = true; + query.mLoadStatics = true; + std::vector readers(mContentFiles.size()); + ToUTF8::Utf8Encoder* const encoder = nullptr; + const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); + EXPECT_EQ(esmData.mActivators.size(), 0); + EXPECT_EQ(esmData.mCells.size(), 0); + EXPECT_EQ(esmData.mContainers.size(), 0); + EXPECT_EQ(esmData.mDoors.size(), 0); + EXPECT_EQ(esmData.mGameSettings.size(), 1521); + EXPECT_EQ(esmData.mLands.size(), 1); + EXPECT_EQ(esmData.mStatics.size(), 2); + } + + TEST_F(EsmLoaderTest, shouldIgnoreCellsGameSettingsWhenQueryLoadGameSettingsIsFalse) + { + Query query; + query.mLoadActivators = true; + query.mLoadCells = true; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = false; + query.mLoadLands = true; + query.mLoadStatics = true; + std::vector readers(mContentFiles.size()); + ToUTF8::Utf8Encoder* const encoder = nullptr; + const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); + EXPECT_EQ(esmData.mActivators.size(), 0); + EXPECT_EQ(esmData.mCells.size(), 1); + EXPECT_EQ(esmData.mContainers.size(), 0); + EXPECT_EQ(esmData.mDoors.size(), 0); + EXPECT_EQ(esmData.mGameSettings.size(), 0); + EXPECT_EQ(esmData.mLands.size(), 1); + EXPECT_EQ(esmData.mStatics.size(), 2); + } + + TEST_F(EsmLoaderTest, shouldIgnoreAllWithDefaultQuery) + { + const Query query; + std::vector readers(mContentFiles.size()); + ToUTF8::Utf8Encoder* const encoder = nullptr; + const EsmData esmData = loadEsmData(query, mContentFiles, mFileCollections, readers, encoder); + EXPECT_EQ(esmData.mActivators.size(), 0); + EXPECT_EQ(esmData.mCells.size(), 0); + EXPECT_EQ(esmData.mContainers.size(), 0); + EXPECT_EQ(esmData.mDoors.size(), 0); + EXPECT_EQ(esmData.mGameSettings.size(), 0); + EXPECT_EQ(esmData.mLands.size(), 0); + EXPECT_EQ(esmData.mStatics.size(), 0); + } +} diff --git a/apps/openmw_test_suite/esmloader/settings.hpp b/apps/openmw_test_suite/esmloader/settings.hpp new file mode 100644 index 0000000000..0f36310bc6 --- /dev/null +++ b/apps/openmw_test_suite/esmloader/settings.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_TEST_SUITE_LOAD_SETTINGS_H +#define OPENMW_TEST_SUITE_LOAD_SETTINGS_H + +#include + +namespace EsmLoaderTests +{ + struct Settings + { + boost::filesystem::path mBasePath; + + static Settings& impl() + { + static Settings value; + return value; + } + }; +} + +#endif diff --git a/apps/openmw_test_suite/openmw_test_suite.cpp b/apps/openmw_test_suite/openmw_test_suite.cpp index 7364b20fdb..5fb9bda901 100644 --- a/apps/openmw_test_suite/openmw_test_suite.cpp +++ b/apps/openmw_test_suite/openmw_test_suite.cpp @@ -1,5 +1,9 @@ +#include "esmloader/settings.hpp" + #include +#include + #ifdef WIN32 //we cannot use GTEST_API_ before main if we're building standalone exe application, //and we're linking GoogleTest / GoogleMock as DLLs and not linking gtest_main / gmock_main @@ -7,6 +11,7 @@ int main(int argc, char **argv) { #else GTEST_API_ int main(int argc, char **argv) { #endif + EsmLoaderTests::Settings::impl().mBasePath = boost::filesystem::path(argv[0]).parent_path(); testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8fc8c2046b..e71aa88f3c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -207,6 +207,11 @@ add_component_dir(sqlite3 transaction ) +add_component_dir(esmloader + load + esmdata +) + set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 10e05de905..2d6caffaba 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -121,6 +121,13 @@ struct FIXED_STRING<4> : public FIXED_STRING_BASE char const* ro_data() const { return data; } char* rw_data() { return data; } + + std::uint32_t toInt() const + { + std::uint32_t value; + std::memcpy(&value, data, sizeof(data)); + return value; + } }; typedef FIXED_STRING<4> NAME; diff --git a/components/esmloader/esmdata.cpp b/components/esmloader/esmdata.cpp new file mode 100644 index 0000000000..06f358ea0f --- /dev/null +++ b/components/esmloader/esmdata.cpp @@ -0,0 +1,77 @@ +#include "esmdata.hpp" +#include "lessbyid.hpp" +#include "record.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace EsmLoader +{ + namespace + { + template + auto returnAs(F&& f) + { + using Result = decltype(std::forward(f)(ESM::Static {})); + if constexpr (!std::is_same_v) + return Result {}; + } + + template + auto withStatic(std::string_view refId, const std::vector& values, F&& f) + { + const auto it = std::lower_bound(values.begin(), values.end(), refId, LessById {}); + + if (it == values.end() && it->mId != refId) + return returnAs(std::forward(f)); + + return std::forward(f)(*it); + } + + template + auto withStatic(std::string_view refId, ESM::RecNameInts type, const EsmData& content, F&& f) + { + switch (type) + { + case ESM::REC_ACTI: return withStatic(refId, content.mActivators, std::forward(f)); + case ESM::REC_CONT: return withStatic(refId, content.mContainers, std::forward(f)); + case ESM::REC_DOOR: return withStatic(refId, content.mDoors, std::forward(f)); + case ESM::REC_STAT: return withStatic(refId, content.mStatics, std::forward(f)); + default: break; + } + + return returnAs(std::forward(f)); + } + } + + EsmData::~EsmData() {} + + std::string_view getModel(const EsmData& content, std::string_view refId, ESM::RecNameInts type) + { + return withStatic(refId, type, content, [] (const auto& v) { return std::string_view(v.mModel); }); + } + + ESM::Variant getGameSetting(const std::vector& records, std::string_view id) + { + const std::string lower = Misc::StringUtils::lowerCase(id); + auto it = std::lower_bound(records.begin(), records.end(), lower, LessById {}); + if (it == records.end() || it->mId != lower) + throw std::runtime_error("Game settings \"" + std::string(id) + "\" is not found"); + return it->mValue; + } +} diff --git a/components/esmloader/esmdata.hpp b/components/esmloader/esmdata.hpp new file mode 100644 index 0000000000..624f50a5e6 --- /dev/null +++ b/components/esmloader/esmdata.hpp @@ -0,0 +1,52 @@ +#ifndef OPENMW_COMPONENTS_ESMLOADER_ESMDATA_H +#define OPENMW_COMPONENTS_ESMLOADER_ESMDATA_H + +#include + +#include +#include + +namespace ESM +{ + struct Activator; + struct Cell; + struct Container; + struct Door; + struct GameSetting; + struct Land; + struct Static; + struct Variant; +} + +namespace EsmLoader +{ + struct RefIdWithType + { + std::string_view mId; + ESM::RecNameInts mType; + }; + + struct EsmData + { + std::vector mActivators; + std::vector mCells; + std::vector mContainers; + std::vector mDoors; + std::vector mGameSettings; + std::vector mLands; + std::vector mStatics; + std::vector mRefIdTypes; + + EsmData() = default; + EsmData(const EsmData&) = delete; + EsmData(EsmData&&) = default; + + ~EsmData(); + }; + + std::string_view getModel(const EsmData& content, std::string_view refId, ESM::RecNameInts type); + + ESM::Variant getGameSetting(const std::vector& records, std::string_view id); +} + +#endif diff --git a/components/esmloader/lessbyid.hpp b/components/esmloader/lessbyid.hpp new file mode 100644 index 0000000000..da835c9e39 --- /dev/null +++ b/components/esmloader/lessbyid.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_COMPONENTS_CONTENT_LESSBYID_H +#define OPENMW_COMPONENTS_CONTENT_LESSBYID_H + +#include + +namespace EsmLoader +{ + struct LessById + { + template + bool operator()(const T& lhs, const T& rhs) const + { + return lhs.mId < rhs.mId; + } + + template + bool operator()(const T& lhs, std::string_view rhs) const + { + return lhs.mId < rhs; + } + }; +} + +#endif diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp new file mode 100644 index 0000000000..789e7619b6 --- /dev/null +++ b/components/esmloader/load.cpp @@ -0,0 +1,334 @@ +#include "load.hpp" +#include "esmdata.hpp" +#include "lessbyid.hpp" +#include "record.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EsmLoader +{ + namespace + { + struct GetKey + { + template + decltype(auto) operator()(const T& v) const + { + return (v.mId); + } + + const ESM::CellId& operator()(const ESM::Cell& v) const + { + return v.mCellId; + } + + std::pair operator()(const ESM::Land& v) const + { + return std::pair(v.mX, v.mY); + } + + template + decltype(auto) operator()(const Record& v) const + { + return (*this)(v.mValue); + } + }; + + struct CellRecords + { + Records mValues; + std::map mByName; + std::map, std::size_t> mByPosition; + }; + + template > + struct HasId : std::false_type {}; + + template + struct HasId> : std::true_type {}; + + template + constexpr bool hasId = HasId::value; + + template + auto loadRecord(ESM::ESMReader& reader, Records& records) + -> std::enable_if_t> + { + T record; + bool deleted = false; + record.load(reader, deleted); + Misc::StringUtils::lowerCaseInPlace(record.mId); + if (Misc::ResourceHelpers::isHiddenMarker(record.mId)) + return; + records.emplace_back(deleted, std::move(record)); + } + + template + auto loadRecord(ESM::ESMReader& reader, Records& records) + -> std::enable_if_t> + { + T record; + bool deleted = false; + record.load(reader, deleted); + records.emplace_back(deleted, std::move(record)); + } + + void loadRecord(ESM::ESMReader& reader, CellRecords& records) + { + ESM::Cell record; + bool deleted = false; + record.loadNameAndData(reader, deleted); + Misc::StringUtils::lowerCaseInPlace(record.mName); + + if ((record.mData.mFlags & ESM::Cell::Interior) != 0) + { + const auto it = records.mByName.find(record.mName); + if (it == records.mByName.end()) + { + record.loadCell(reader, true); + records.mByName.emplace_hint(it, record.mName, records.mValues.size()); + records.mValues.emplace_back(deleted, std::move(record)); + } + else + { + Record& old = records.mValues[it->second]; + old.mValue.mData = record.mData; + old.mValue.loadCell(reader, true); + } + } + else + { + const std::pair position(record.mData.mX, record.mData.mY); + const auto it = records.mByPosition.find(position); + if (it == records.mByPosition.end()) + { + record.loadCell(reader, true); + records.mByPosition.emplace_hint(it, position, records.mValues.size()); + records.mValues.emplace_back(deleted, std::move(record)); + } + else + { + Record& old = records.mValues[it->second]; + old.mValue.mData = record.mData; + old.mValue.loadCell(reader, true); + } + } + } + + struct ShallowContent + { + Records mActivators; + CellRecords mCells; + Records mContainers; + Records mDoors; + Records mGameSettings; + Records mLands; + Records mStatics; + }; + + void loadRecord(const Query& query, const ESM::NAME& name, ESM::ESMReader& reader, ShallowContent& content) + { + switch (name.toInt()) + { + case ESM::REC_ACTI: + if (query.mLoadActivators) + return loadRecord(reader, content.mActivators); + break; + case ESM::REC_CELL: + if (query.mLoadCells) + return loadRecord(reader, content.mCells); + break; + case ESM::REC_CONT: + if (query.mLoadContainers) + return loadRecord(reader, content.mContainers); + break; + case ESM::REC_DOOR: + if (query.mLoadDoors) + return loadRecord(reader, content.mDoors); + break; + case ESM::REC_GMST: + if (query.mLoadGameSettings) + return loadRecord(reader, content.mGameSettings); + break; + case ESM::REC_LAND: + if (query.mLoadLands) + return loadRecord(reader, content.mLands); + break; + case ESM::REC_STAT: + if (query.mLoadStatics) + return loadRecord(reader, content.mStatics); + break; + } + + reader.skipRecord(); + } + + ESM::ESMReader loadEsm(const Query& query, ESM::ESMReader& reader, ShallowContent& content) + { + Log(Debug::Info) << "Loading ESM file " << reader.getName(); + + while (reader.hasMoreRecs()) + { + const ESM::NAME recName = reader.getRecName(); + reader.getRecHeader(); + loadRecord(query, recName, reader, content); + } + + return reader; + } + + ShallowContent shallowLoad(const Query& query, const std::vector& contentFiles, + const Files::Collections& fileCollections, std::vector& readers, + ToUTF8::Utf8Encoder* encoder) + { + ShallowContent result; + + for (std::size_t i = 0; i < contentFiles.size(); ++i) + { + const std::string &file = contentFiles[i]; + const Files::MultiDirCollection& collection = fileCollections.getCollection(boost::filesystem::path(file).extension().string()); + + ESM::ESMReader& reader = readers[i]; + reader.setEncoder(encoder); + reader.setIndex(static_cast(i)); + reader.setGlobalReaderList(&readers); + reader.open(collection.getPath(file).string()); + + loadEsm(query, readers[i], result); + } + + return result; + } + + struct WithType + { + ESM::RecNameInts mType; + + template + RefIdWithType operator()(const T& v) const { return {v.mId, mType}; } + }; + + template + void addRefIdsTypes(const std::vector& values, std::vector& refIdsTypes) + { + std::transform(values.begin(), values.end(), std::back_inserter(refIdsTypes), + WithType {static_cast(T::sRecordId)}); + } + + void addRefIdsTypes(EsmData& content) + { + content.mRefIdTypes.reserve( + content.mActivators.size() + + content.mContainers.size() + + content.mDoors.size() + + content.mStatics.size() + ); + + addRefIdsTypes(content.mActivators, content.mRefIdTypes); + addRefIdsTypes(content.mContainers, content.mRefIdTypes); + addRefIdsTypes(content.mDoors, content.mRefIdTypes); + addRefIdsTypes(content.mStatics, content.mRefIdTypes); + + std::sort(content.mRefIdTypes.begin(), content.mRefIdTypes.end(), LessById {}); + } + + std::vector prepareCellRecords(Records& records) + { + std::vector result; + for (Record& v : records) + if (!v.mDeleted) + result.emplace_back(std::move(v.mValue)); + return result; + } + } + + EsmData loadEsmData(const Query& query, const std::vector& contentFiles, + const Files::Collections& fileCollections, std::vector& readers, ToUTF8::Utf8Encoder* encoder) + { + Log(Debug::Info) << "Loading ESM data..."; + + ShallowContent content = shallowLoad(query, contentFiles, fileCollections, readers, encoder); + + std::ostringstream loaded; + + if (query.mLoadActivators) + loaded << ' ' << content.mActivators.size() << " activators,"; + if (query.mLoadCells) + loaded << ' ' << content.mCells.mValues.size() << " cells,"; + if (query.mLoadContainers) + loaded << ' ' << content.mContainers.size() << " containers,"; + if (query.mLoadDoors) + loaded << ' ' << content.mDoors.size() << " doors,"; + if (query.mLoadGameSettings) + loaded << ' ' << content.mGameSettings.size() << " game settings,"; + if (query.mLoadLands) + loaded << ' ' << content.mLands.size() << " lands,"; + if (query.mLoadStatics) + loaded << ' ' << content.mStatics.size() << " statics,"; + + Log(Debug::Info) << "Loaded" << loaded.str(); + + EsmData result; + + if (query.mLoadActivators) + result.mActivators = prepareRecords(content.mActivators, GetKey {}); + if (query.mLoadCells) + result.mCells = prepareCellRecords(content.mCells.mValues); + if (query.mLoadContainers) + result.mContainers = prepareRecords(content.mContainers, GetKey {}); + if (query.mLoadDoors) + result.mDoors = prepareRecords(content.mDoors, GetKey {}); + if (query.mLoadGameSettings) + result.mGameSettings = prepareRecords(content.mGameSettings, GetKey {}); + if (query.mLoadLands) + result.mLands = prepareRecords(content.mLands, GetKey {}); + if (query.mLoadStatics) + result.mStatics = prepareRecords(content.mStatics, GetKey {}); + + addRefIdsTypes(result); + + std::ostringstream prepared; + + if (query.mLoadActivators) + prepared << ' ' << result.mActivators.size() << " unique activators,"; + if (query.mLoadCells) + prepared << ' ' << result.mCells.size() << " unique cells,"; + if (query.mLoadContainers) + prepared << ' ' << result.mContainers.size() << " unique containers,"; + if (query.mLoadDoors) + prepared << ' ' << result.mDoors.size() << " unique doors,"; + if (query.mLoadGameSettings) + prepared << ' ' << result.mGameSettings.size() << " unique game settings,"; + if (query.mLoadLands) + prepared << ' ' << result.mLands.size() << " unique lands,"; + if (query.mLoadStatics) + prepared << ' ' << result.mStatics.size() << " unique statics,"; + + Log(Debug::Info) << "Prepared" << prepared.str(); + + return result; + } +} diff --git a/components/esmloader/load.hpp b/components/esmloader/load.hpp new file mode 100644 index 0000000000..39d6f48b81 --- /dev/null +++ b/components/esmloader/load.hpp @@ -0,0 +1,39 @@ +#ifndef OPENMW_COMPONENTS_ESMLOADER_LOAD_H +#define OPENMW_COMPONENTS_ESMLOADER_LOAD_H + +#include + +#include +#include + +namespace ToUTF8 +{ + class Utf8Encoder; +} + +namespace Files +{ + class Collections; +} + +namespace EsmLoader +{ + struct EsmData; + + struct Query + { + bool mLoadActivators = false; + bool mLoadCells = false; + bool mLoadContainers = false; + bool mLoadDoors = false; + bool mLoadGameSettings = false; + bool mLoadLands = false; + bool mLoadStatics = false; + }; + + EsmData loadEsmData(const Query& query, const std::vector& contentFiles, + const Files::Collections& fileCollections, std::vector& readers, + ToUTF8::Utf8Encoder* encoder); +} + +#endif diff --git a/components/esmloader/record.hpp b/components/esmloader/record.hpp new file mode 100644 index 0000000000..c076ee72c6 --- /dev/null +++ b/components/esmloader/record.hpp @@ -0,0 +1,44 @@ +#ifndef OPENMW_COMPONENTS_ESMLOADER_RECORD_H +#define OPENMW_COMPONENTS_ESMLOADER_RECORD_H + +#include + +#include +#include +#include + +namespace EsmLoader +{ + template + struct Record + { + bool mDeleted; + T mValue; + + template + explicit Record(bool deleted, Args&& ... args) + : mDeleted(deleted) + , mValue(std::forward(args) ...) + {} + }; + + template + using Records = std::vector>; + + template + inline std::vector prepareRecords(Records& records, const GetKey& getKey) + { + const auto greaterByKey = [&] (const auto& l, const auto& r) { return getKey(r) < getKey(l); }; + const auto equalByKey = [&] (const auto& l, const auto& r) { return getKey(l) == getKey(r); }; + std::stable_sort(records.begin(), records.end(), greaterByKey); + records.erase(std::unique(records.begin(), records.end(), equalByKey), records.end()); + std::reverse(records.begin(), records.end()); + std::vector result; + for (Record& v : records) + if (!v.mDeleted) + result.emplace_back(std::move(v.mValue)); + return result; + } +} + +#endif From 23026caacc40356f7ad2c13ee38a3f0fcf0b559f Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Oct 2021 22:44:04 +0200 Subject: [PATCH 1499/2859] Fix LuaUtilPackageTest.Transform It can fail due to float arithmetic precision with error: Expected equality of these values: getAsString(lua, "moveAndScale") Which is: "TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) rotation(angle=8.53284e-17, axis=(1, 0, 0)) }" "TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) }" --- apps/openmw_test_suite/lua/test_utilpackage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index 934cbf761a..ead3cecc6f 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -91,7 +91,7 @@ namespace lua.safe_script("moveAndScale = T.move(v(1, 2, 3)) * T.scale(0.5, 1, 0.5) * T.move(10, 20, 30)"); EXPECT_EQ(getAsString(lua, "moveAndScale * v(0, 0, 0)"), "(6, 22, 18)"); EXPECT_EQ(getAsString(lua, "moveAndScale * v(300, 200, 100)"), "(156, 222, 68)"); - EXPECT_EQ(getAsString(lua, "moveAndScale"), "TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) }"); + EXPECT_THAT(getAsString(lua, "moveAndScale"), AllOf(StartsWith("TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) "), EndsWith(" }"))); EXPECT_EQ(getAsString(lua, "T.identity"), "TransformM{ }"); lua.safe_script("rx = T.rotateX(math.pi / 2)"); lua.safe_script("ry = T.rotateY(math.pi / 2)"); From 9be606a40de19ea7636694beda2f8790f605774b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 17 Oct 2021 23:40:34 +0100 Subject: [PATCH 1500/2859] Finish removing old Escape classes --- apps/openmw/main.cpp | 1 - apps/openmw/options.cpp | 2 +- apps/openmw_test_suite/mwworld/test_store.cpp | 1 - apps/openmw_test_suite/openmw/options.cpp | 1 - components/CMakeLists.txt | 2 +- components/fallback/validate.cpp | 9 +- components/fallback/validate.hpp | 2 - components/files/configurationmanager.cpp | 1 - components/files/escape.cpp | 145 ------------- components/files/escape.hpp | 191 ------------------ 10 files changed, 6 insertions(+), 349 deletions(-) delete mode 100644 components/files/escape.cpp delete mode 100644 components/files/escape.hpp diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 18b1ab267f..7a3a21b149 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp index 167c275598..dd94590f2a 100644 --- a/apps/openmw/options.cpp +++ b/apps/openmw/options.cpp @@ -1,7 +1,7 @@ #include "options.hpp" #include -#include +#include #include #include diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 13a9ebee9c..9aa5a6758f 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index b087fd03fb..7cf0df1066 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b9db26dbc1..b61ba11466 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -104,7 +104,7 @@ IF(NOT WIN32 AND NOT APPLE) add_definitions(-DGLOBAL_CONFIG_PATH="${GLOBAL_CONFIG_PATH}") ENDIF() add_component_dir (files - linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager escape + linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager lowlevelfile constrainedfilestream memorystream configfileparser ) diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index a482d40faf..6f5d529e05 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -11,13 +11,12 @@ void Fallback::validate(boost::any& v, std::vector const& tokens, F for (const auto& token : tokens) { - std::string temp = Files::EscapeHashString::processString(token); - size_t sep = temp.find(','); - if (sep < 1 || sep == temp.length() - 1 || sep == std::string::npos) + size_t sep = token.find(','); + if (sep < 1 || sep == token.length() - 1 || sep == std::string::npos) throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); - std::string key(temp.substr(0, sep)); - std::string value(temp.substr(sep + 1)); + std::string key(token.substr(0, sep)); + std::string value(token.substr(sep + 1)); map->mMap[key] = value; } diff --git a/components/fallback/validate.hpp b/components/fallback/validate.hpp index 96690f50a5..2b9af88da2 100644 --- a/components/fallback/validate.hpp +++ b/components/fallback/validate.hpp @@ -3,8 +3,6 @@ #include -#include - // Parses and validates a fallback map from boost program_options. // Note: for boost to pick up the validate function, you need to pull in the namespace e.g. // by using namespace Fallback; diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index dad52a7ee4..23c25fd413 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/components/files/escape.cpp b/components/files/escape.cpp deleted file mode 100644 index 8b11504d34..0000000000 --- a/components/files/escape.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "escape.hpp" - -#include - -namespace Files -{ - const int escape_hash_filter::sEscape = '@'; - const int escape_hash_filter::sEscapeIdentifier = 'a'; - const int escape_hash_filter::sHashIdentifier = 'h'; - - escape_hash_filter::escape_hash_filter() : mSeenNonWhitespace(false), mFinishLine(false) - { - } - - escape_hash_filter::~escape_hash_filter() - { - } - - unescape_hash_filter::unescape_hash_filter() : expectingIdentifier(false) - { - } - - unescape_hash_filter::~unescape_hash_filter() - { - } - - std::string EscapeHashString::processString(const std::string & str) - { - std::string temp = str; - - static const char hash[] = { escape_hash_filter::sEscape, escape_hash_filter::sHashIdentifier }; - Misc::StringUtils::replaceAll(temp, hash, "#", 2, 1); - - static const char escape[] = { escape_hash_filter::sEscape, escape_hash_filter::sEscapeIdentifier }; - Misc::StringUtils::replaceAll(temp, escape, "@", 2, 1); - - return temp; - } - - EscapeHashString::EscapeHashString() : mData() - { - } - - EscapeHashString::EscapeHashString(const std::string & str) : mData(EscapeHashString::processString(str)) - { - } - - EscapeHashString::EscapeHashString(const std::string & str, size_t pos, size_t len) : mData(EscapeHashString::processString(str), pos, len) - { - } - - EscapeHashString::EscapeHashString(const char * s) : mData(EscapeHashString::processString(std::string(s))) - { - } - - EscapeHashString::EscapeHashString(const char * s, size_t n) : mData(EscapeHashString::processString(std::string(s)), 0, n) - { - } - - EscapeHashString::EscapeHashString(size_t n, char c) : mData(n, c) - { - } - - template - EscapeHashString::EscapeHashString(InputIterator first, InputIterator last) : mData(EscapeHashString::processString(std::string(first, last))) - { - } - - std::string EscapeHashString::toStdString() const - { - return std::string(mData); - } - - std::istream & operator>> (std::istream & is, EscapeHashString & eHS) - { - std::string temp; - is >> temp; - eHS = EscapeHashString(temp); - return is; - } - - std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS) - { - os << eHS.mData; - return os; - } - - EscapeStringVector::EscapeStringVector() : mVector() - { - } - - EscapeStringVector::~EscapeStringVector() - { - } - - std::vector EscapeStringVector::toStdStringVector() const - { - std::vector temp = std::vector(); - for (std::vector::const_iterator it = mVector.begin(); it != mVector.end(); ++it) - { - temp.push_back(it->toStdString()); - } - return temp; - } - - // boost program options validation - - void validate(boost::any &v, const std::vector &tokens, Files::EscapeHashString * eHS, int a) - { - boost::program_options::validators::check_first_occurrence(v); - - if (v.empty()) - v = boost::any(EscapeHashString(boost::program_options::validators::get_single_string(tokens))); - } - - void validate(boost::any &v, const std::vector &tokens, EscapeStringVector *, int) - { - if (v.empty()) - v = boost::any(EscapeStringVector()); - - EscapeStringVector * eSV = boost::any_cast(&v); - - for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) - eSV->mVector.emplace_back(*it); - } - - PathContainer EscapePath::toPathContainer(const EscapePathContainer & escapePathContainer) - { - PathContainer temp; - for (EscapePathContainer::const_iterator it = escapePathContainer.begin(); it != escapePathContainer.end(); ++it) - temp.push_back(it->mPath); - return temp; - } - - std::istream & operator>> (std::istream & istream, EscapePath & escapePath) - { - boost::iostreams::filtering_istream filteredStream; - filteredStream.push(unescape_hash_filter()); - filteredStream.push(istream); - - filteredStream >> escapePath.mPath; - - return istream; - } -} diff --git a/components/files/escape.hpp b/components/files/escape.hpp deleted file mode 100644 index d01bd8d980..0000000000 --- a/components/files/escape.hpp +++ /dev/null @@ -1,191 +0,0 @@ -#ifndef COMPONENTS_FILES_ESCAPE_HPP -#define COMPONENTS_FILES_ESCAPE_HPP - -#include - -#include - -#include -#include -#include - -/** - * \namespace Files - */ -namespace Files -{ - /** - * \struct escape_hash_filter - */ - struct escape_hash_filter : public boost::iostreams::input_filter - { - static const int sEscape; - static const int sHashIdentifier; - static const int sEscapeIdentifier; - - escape_hash_filter(); - virtual ~escape_hash_filter(); - - template int get(Source & src); - - private: - std::queue mNext; - - bool mSeenNonWhitespace; - bool mFinishLine; - }; - - template - int escape_hash_filter::get(Source & src) - { - if (mNext.empty()) - { - int character = boost::iostreams::get(src); - if (character == boost::iostreams::WOULD_BLOCK) - { - mNext.push(character); - } - else if (character == EOF) - { - mSeenNonWhitespace = false; - mFinishLine = false; - mNext.push(character); - } - else if (character == '\n') - { - mSeenNonWhitespace = false; - mFinishLine = false; - mNext.push(character); - } - else if (mFinishLine) - { - mNext.push(character); - } - else if (character == '#') - { - if (mSeenNonWhitespace) - { - mNext.push(sEscape); - mNext.push(sHashIdentifier); - } - else - { - //it's fine being interpreted by Boost as a comment, and so is anything afterwards - mNext.push(character); - mFinishLine = true; - } - } - else if (character == sEscape) - { - mNext.push(sEscape); - mNext.push(sEscapeIdentifier); - } - else - { - mNext.push(character); - } - if (!mSeenNonWhitespace && !isspace(character)) - mSeenNonWhitespace = true; - } - int retval = mNext.front(); - mNext.pop(); - return retval; - } - - struct unescape_hash_filter : public boost::iostreams::input_filter - { - unescape_hash_filter(); - virtual ~unescape_hash_filter(); - - template int get(Source & src); - - private: - bool expectingIdentifier; - }; - - template - int unescape_hash_filter::get(Source & src) - { - int character; - if (!expectingIdentifier) - character = boost::iostreams::get(src); - else - { - character = escape_hash_filter::sEscape; - expectingIdentifier = false; - } - if (character == escape_hash_filter::sEscape) - { - int nextChar = boost::iostreams::get(src); - int intended; - if (nextChar == escape_hash_filter::sEscapeIdentifier) - intended = escape_hash_filter::sEscape; - else if (nextChar == escape_hash_filter::sHashIdentifier) - intended = '#'; - else if (nextChar == boost::iostreams::WOULD_BLOCK) - { - expectingIdentifier = true; - intended = nextChar; - } - else - intended = '?'; - return intended; - } - else - return character; - } - - /** - * \class EscapeHashString - */ - class EscapeHashString - { - private: - std::string mData; - public: - static std::string processString(const std::string & str); - - EscapeHashString(); - EscapeHashString(const std::string & str); - EscapeHashString(const std::string & str, size_t pos, size_t len = std::string::npos); - EscapeHashString(const char * s); - EscapeHashString(const char * s, size_t n); - EscapeHashString(size_t n, char c); - template - EscapeHashString(InputIterator first, InputIterator last); - - std::string toStdString() const; - - friend std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS); - }; - - std::istream & operator>> (std::istream & is, EscapeHashString & eHS); - - struct EscapeStringVector - { - std::vector mVector; - - EscapeStringVector(); - virtual ~EscapeStringVector(); - - std::vector toStdStringVector() const; - }; - - //boost program options validation - - void validate(boost::any &v, const std::vector &tokens, Files::EscapeHashString * eHS, int a); - - void validate(boost::any &v, const std::vector &tokens, EscapeStringVector *, int); - - struct EscapePath - { - boost::filesystem::path mPath; - - static PathContainer toPathContainer(const std::vector & escapePathContainer); - }; - - typedef std::vector EscapePathContainer; - - std::istream & operator>> (std::istream & istream, EscapePath & escapePath); -} /* namespace Files */ -#endif /* COMPONENTS_FILES_ESCAPE_HPP */ From bb26ba30b6d8e195a6b61d69b60d7543b32859ff Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Oct 2021 22:12:33 +0200 Subject: [PATCH 1501/2859] Add progress reporter type To log/report progress of long duration operations using given time period. --- apps/openmw_test_suite/CMakeLists.txt | 1 + .../misc/progressreporter.cpp | 43 ++++++++++++++++ components/misc/progressreporter.hpp | 50 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 apps/openmw_test_suite/misc/progressreporter.cpp create mode 100644 components/misc/progressreporter.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 67001a7885..e8a296b563 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -24,6 +24,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) misc/test_stringops.cpp misc/test_endianness.cpp + misc/progressreporter.cpp nifloader/testbulletnifloader.cpp diff --git a/apps/openmw_test_suite/misc/progressreporter.cpp b/apps/openmw_test_suite/misc/progressreporter.cpp new file mode 100644 index 0000000000..cd5449acf7 --- /dev/null +++ b/apps/openmw_test_suite/misc/progressreporter.cpp @@ -0,0 +1,43 @@ +#include + +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace Misc; + + struct ReportMock + { + MOCK_METHOD(void, call, (std::size_t, std::size_t), ()); + }; + + struct Report + { + StrictMock* mImpl; + + void operator()(std::size_t provided, std::size_t expected) + { + mImpl->call(provided, expected); + } + }; + + TEST(MiscProgressReporterTest, shouldCallReportWhenPassedInterval) + { + StrictMock report; + EXPECT_CALL(report, call(13, 42)).WillOnce(Return()); + ProgressReporter reporter(std::chrono::steady_clock::duration(0), Report {&report}); + reporter(13, 42); + } + + TEST(MiscProgressReporterTest, shouldNotCallReportWhenIntervalIsNotPassed) + { + StrictMock report; + EXPECT_CALL(report, call(13, 42)).Times(0); + ProgressReporter reporter(std::chrono::seconds(1000), Report {&report}); + reporter(13, 42); + } +} diff --git a/components/misc/progressreporter.hpp b/components/misc/progressreporter.hpp new file mode 100644 index 0000000000..733e36191e --- /dev/null +++ b/components/misc/progressreporter.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_COMPONENTS_MISC_PROGRESSREPORTER_H +#define OPENMW_COMPONENTS_MISC_PROGRESSREPORTER_H + +#include +#include +#include +#include +#include + +namespace Misc +{ + template + class ProgressReporter + { + public: + explicit ProgressReporter(Report&& report = Report {}) + : mReport(std::forward(report)) + {} + + explicit ProgressReporter(std::chrono::steady_clock::duration interval, Report&& report = Report {}) + : mInterval(interval) + , mReport(std::forward(report)) + {} + + void operator()(std::size_t provided, std::size_t expected) + { + expected = std::max(expected, provided); + const bool shouldReport = [&] + { + const std::lock_guard lock(mMutex); + const auto now = std::chrono::steady_clock::now(); + const auto left = mNextReport - now; + if (left.count() > 0 || provided == expected) + return false; + mNextReport += mInterval + left; + return true; + } (); + if (shouldReport) + mReport(provided, expected); + } + + private: + const std::chrono::steady_clock::duration mInterval = std::chrono::seconds(1); + Report mReport; + std::mutex mMutex; + std::chrono::steady_clock::time_point mNextReport {std::chrono::steady_clock::now() + mInterval}; + }; +} + +#endif From e5413c066489a5ed388a78c789de9c347a4616bc Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 18 Oct 2021 01:43:33 +0200 Subject: [PATCH 1502/2859] Add functions to compress/decompress vector of bytes --- apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/misc/compression.cpp | 31 +++++++++++++ components/CMakeLists.txt | 1 + components/misc/compression.cpp | 48 +++++++++++++++++++++ components/misc/compression.hpp | 14 ++++++ 5 files changed, 95 insertions(+) create mode 100644 apps/openmw_test_suite/misc/compression.cpp create mode 100644 components/misc/compression.cpp create mode 100644 components/misc/compression.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index e8a296b563..3d19834066 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -25,6 +25,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) misc/test_stringops.cpp misc/test_endianness.cpp misc/progressreporter.cpp + misc/compression.cpp nifloader/testbulletnifloader.cpp diff --git a/apps/openmw_test_suite/misc/compression.cpp b/apps/openmw_test_suite/misc/compression.cpp new file mode 100644 index 0000000000..e062599f4a --- /dev/null +++ b/apps/openmw_test_suite/misc/compression.cpp @@ -0,0 +1,31 @@ +#include + +#include + +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace Misc; + + TEST(MiscCompressionTest, compressShouldAddPrefixWithDataSize) + { + const std::vector data(1234); + const std::vector compressed = compress(data); + int size = 0; + std::memcpy(&size, compressed.data(), sizeof(size)); + EXPECT_EQ(size, data.size()); + } + + TEST(MiscCompressionTest, decompressIsInverseToCompress) + { + const std::vector data(1024); + const std::vector compressed = compress(data); + EXPECT_LT(compressed.size(), data.size()); + const std::vector decompressed = decompress(compressed); + EXPECT_EQ(decompressed, data); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e71aa88f3c..7b06b6702d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -93,6 +93,7 @@ add_component_dir (esmterrain add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread + compression ) add_component_dir (debug diff --git a/components/misc/compression.cpp b/components/misc/compression.cpp new file mode 100644 index 0000000000..7f76d0900c --- /dev/null +++ b/components/misc/compression.cpp @@ -0,0 +1,48 @@ +#include "compression.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace Misc +{ + std::vector compress(const std::vector& data) + { + const std::size_t originalSize = data.size(); + std::vector result(static_cast(LZ4_compressBound(static_cast(originalSize)) + sizeof(originalSize))); + const int size = LZ4_compress_default( + reinterpret_cast(data.data()), + reinterpret_cast(result.data()) + sizeof(originalSize), + static_cast(data.size()), + static_cast(result.size() - sizeof(originalSize)) + ); + if (size == 0) + throw std::runtime_error("Failed to compress"); + std::memcpy(result.data(), &originalSize, sizeof(originalSize)); + result.resize(static_cast(size) + sizeof(originalSize)); + return result; + } + + std::vector decompress(const std::vector& data) + { + std::size_t originalSize; + std::memcpy(&originalSize, data.data(), sizeof(originalSize)); + std::vector result(originalSize); + const int size = LZ4_decompress_safe( + reinterpret_cast(data.data()) + sizeof(originalSize), + reinterpret_cast(result.data()), + static_cast(data.size() - sizeof(originalSize)), + static_cast(result.size()) + ); + if (size < 0) + throw std::runtime_error("Failed to decompress"); + if (originalSize != static_cast(size)) + throw std::runtime_error("Size of decompressed data (" + std::to_string(size) + + ") doesn't match stored (" + std::to_string(originalSize) + ")"); + return result; + } +} diff --git a/components/misc/compression.hpp b/components/misc/compression.hpp new file mode 100644 index 0000000000..2b951ebed6 --- /dev/null +++ b/components/misc/compression.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_MISC_COMPRESSION_H +#define OPENMW_COMPONENTS_MISC_COMPRESSION_H + +#include +#include + +namespace Misc +{ + std::vector compress(const std::vector& data); + + std::vector decompress(const std::vector& data); +} + +#endif From 1da9c756926df0cbe01bd7b537aed34050c5fd6c Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 18 Oct 2021 22:11:48 +0200 Subject: [PATCH 1503/2859] Add missing initialization for Enchanting::mObjectType --- apps/openmw/mwmechanics/enchanting.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 0410dd02dd..078cbc5f43 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -22,6 +22,7 @@ namespace MWMechanics Enchanting::Enchanting() : mCastStyle(ESM::Enchantment::CastOnce) , mSelfEnchanting(false) + , mObjectType(0) , mWeaponType(-1) {} From b1fdc0eb8a573e5001197c4aba83131661672912 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 19 Oct 2021 14:00:50 +0200 Subject: [PATCH 1504/2859] Make MacOS great again (#3178) * Make MacOS great again * Update cmake.yml --- .github/workflows/cmake.yml | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d53d2380d4..1ce10fa6cd 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -8,7 +8,7 @@ env: BUILD_TYPE: RelWithDebInfo jobs: - build: + Ubuntu: runs-on: ubuntu-latest steps: @@ -60,3 +60,28 @@ jobs: # with: # path: ./build_artifact.7z # name: build_artifact.7z + + MacOS: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install Building Dependancies + run: CI/before_install.osx.sh + + - name: Prime ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} + max-size: 1000M + + - name: Configure + run: | + rm -fr build # remove the build directory + CI/before_script.osx.sh + + - name: Build + run: | + cd build + make -j $(sysctl -n hw.logicalcpu) package From dd4d8b26494ee1dd5bef2f3669f299f5a549bcb3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 19 Oct 2021 21:38:07 +0200 Subject: [PATCH 1505/2859] Prevent floating point issues accumulating --- apps/openmw/mwmechanics/magiceffects.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index e75361628b..8e74d73d5d 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -1,10 +1,20 @@ #include "magiceffects.hpp" +#include #include #include #include +namespace +{ + // Round value to prevent precision issues + void truncate(float& value) + { + value = std::roundf(value * 1000.f) / 1000.f; + } +} + namespace MWMechanics { EffectKey::EffectKey() : mId (0), mArg (-1) {} @@ -74,6 +84,7 @@ namespace MWMechanics { mModifier += param.mModifier; mBase += param.mBase; + truncate(mModifier); return *this; } @@ -81,6 +92,7 @@ namespace MWMechanics { mModifier -= param.mModifier; mBase -= param.mBase; + truncate(mModifier); return *this; } From 06ea47f74b0c26376907d3625859ef19fcf3752f Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 19 Oct 2021 22:54:42 +0200 Subject: [PATCH 1506/2859] Fix crash in DetourNavigator::fixupCorridor Handle situation when resulting path does not fit into destination vector. --- components/detournavigator/findsmoothpath.cpp | 15 +++++---- components/detournavigator/findsmoothpath.hpp | 32 +++++++++---------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp index e166ff90e9..cbaf12305c 100644 --- a/components/detournavigator/findsmoothpath.cpp +++ b/components/detournavigator/findsmoothpath.cpp @@ -7,13 +7,13 @@ namespace DetourNavigator { - std::size_t fixupCorridor(dtPolyRef* path, std::size_t pathSize, const std::vector& visited) + std::size_t fixupCorridor(std::vector& path, std::size_t pathSize, const std::vector& visited) { std::vector::const_reverse_iterator furthestVisited; // Find furthest common polygon. - const auto begin = path; - const auto end = path + pathSize; + const auto begin = path.begin(); + const auto end = path.begin() + pathSize; const std::reverse_iterator rbegin(end); const std::reverse_iterator rend(begin); const auto it = std::find_if(rbegin, rend, [&] (dtPolyRef pathValue) @@ -34,12 +34,13 @@ namespace DetourNavigator // visited: a_1 ... a_n x b_1 ... b_n // furthestVisited ^ - // path: C x D - // ^ furthestPath + // path: C x D E + // ^ furthestPath ^ path.size() - (furthestVisited + 1 - visited.rbegin()) // result: x b_n ... b_1 D - auto newEnd = std::copy(visited.rbegin(), furthestVisited + 1, begin); - newEnd = std::copy(furthestPath + 1, end, newEnd); + const std::size_t required = static_cast(furthestVisited + 1 - visited.rbegin()); + const auto newEnd = std::copy(furthestPath + 1, std::min(begin + path.size(), end), begin + required); + std::copy(visited.rbegin(), furthestVisited + 1, begin); return static_cast(newEnd - begin); } diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 71e8a1df88..07d3054e19 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -30,7 +30,7 @@ namespace DetourNavigator return (osg::Vec2f(v1.x(), v1.z()) - osg::Vec2f(v2.x(), v2.z())).length() < r; } - std::size_t fixupCorridor(dtPolyRef* path, std::size_t pathSize, const std::vector& visited); + std::size_t fixupCorridor(std::vector& path, std::size_t pathSize, const std::vector& visited); // This function checks if the path has a small U-turn, that is, // a polygon further in the path is adjacent to the first polygon @@ -125,40 +125,37 @@ namespace DetourNavigator return {std::move(result)}; } - inline std::optional> findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, + inline std::optional findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter, - const std::size_t maxSize) + dtPolyRef* path, const std::size_t maxSize) { int pathLen = 0; - std::vector result(maxSize); const auto status = navMeshQuery.findPath(startRef, endRef, startPos.ptr(), endPos.ptr(), &queryFilter, - result.data(), &pathLen, static_cast(maxSize)); + path, &pathLen, static_cast(maxSize)); if (!dtStatusSucceed(status)) return {}; assert(pathLen >= 0); assert(static_cast(pathLen) <= maxSize); - result.resize(static_cast(pathLen)); - return {std::move(result)}; + return static_cast(pathLen); } template Status makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery, const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end, const float stepSize, - std::vector polygonPath, std::size_t maxSmoothPathSize, OutputIterator& out) + std::vector& polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize, OutputIterator& out) { // Iterate over the path to find smooth path on the detail mesh surface. osg::Vec3f iterPos; navMeshQuery.closestPointOnPoly(polygonPath.front(), start.ptr(), iterPos.ptr(), nullptr); osg::Vec3f targetPos; - navMeshQuery.closestPointOnPoly(polygonPath.back(), end.ptr(), targetPos.ptr(), nullptr); + navMeshQuery.closestPointOnPoly(polygonPath[polygonPathSize - 1], end.ptr(), targetPos.ptr(), nullptr); constexpr float slop = 0.01f; *out++ = iterPos; std::size_t smoothPathSize = 1; - std::size_t polygonPathSize = polygonPath.size(); // Move towards target a small advancement at a time until target reached or // when ran out of memory to store the path. @@ -188,7 +185,7 @@ namespace DetourNavigator if (!result) return Status::MoveAlongSurfaceFailed; - polygonPathSize = fixupCorridor(polygonPath.data(), polygonPathSize, result->mVisited); + polygonPathSize = fixupCorridor(polygonPath, polygonPathSize, result->mVisited); polygonPathSize = fixupShortcuts(polygonPath.data(), polygonPathSize, navMeshQuery); // Handle end of path and off-mesh links when close enough. @@ -285,19 +282,20 @@ namespace DetourNavigator if (endRef == 0) return Status::EndPolygonNotFound; - const auto polygonPath = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, - settings.mMaxPolygonPathSize); + std::vector polygonPath(settings.mMaxPolygonPathSize); + const auto polygonPathSize = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, + polygonPath.data(), polygonPath.size()); - if (!polygonPath) + if (!polygonPathSize.has_value()) return Status::FindPathOverPolygonsFailed; - if (polygonPath->empty()) + if (*polygonPathSize == 0) return Status::Success; - const bool partialPath = polygonPath->back() != endRef; + const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef; auto outTransform = OutputTransformIterator(out, settings); const Status smoothStatus = makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, - std::move(*polygonPath), settings.mMaxSmoothPathSize, outTransform); + polygonPath, *polygonPathSize, settings.mMaxSmoothPathSize, outTransform); if (smoothStatus != Status::Success) return smoothStatus; From 6cfabe252d3789f72377f07431b704f71df9c6c9 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 2 Sep 2021 01:18:55 +0200 Subject: [PATCH 1507/2859] Remove redundant ShadowsBinAdder --- components/sceneutil/mwshadowtechnique.cpp | 2 +- components/sceneutil/shadowsbin.hpp | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 024123b3e1..60f048b147 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -282,7 +282,7 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) static osg::ref_ptr ss; if (!ss) { - ShadowsBinAdder adder("ShadowsBin", _vdsm->getCastingPrograms()); + ShadowsBin::addPrototype("ShadowsBin", _vdsm->getCastingPrograms()); ss = new osg::StateSet; ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); } diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index 1c63caf4bb..8775dc3139 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -67,13 +67,6 @@ namespace SceneUtil static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms); }; - - class ShadowsBinAdder - { - public: - ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } - }; - } #endif From 9d1b7c4a303bf5d6ba29f16cfdb15f761dddcbe7 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 2 Sep 2021 01:12:47 +0200 Subject: [PATCH 1508/2859] Use type alias to avoid long type names and length redefinition for array --- components/sceneutil/shadowsbin.cpp | 4 ++-- components/sceneutil/shadowsbin.hpp | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index af62b581c9..a6275a60e2 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -42,7 +42,7 @@ namespace namespace SceneUtil { -std::array, GL_ALWAYS - GL_NEVER + 1> ShadowsBin::sCastingPrograms = { +ShadowsBin::CastingPrograms ShadowsBin::sCastingPrograms = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; @@ -150,7 +150,7 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un return sg; } -void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms) +void ShadowsBin::addPrototype(const std::string & name, const CastingPrograms& castingPrograms) { sCastingPrograms = castingPrograms; osg::ref_ptr bin(new ShadowsBin); diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index 8775dc3139..67343c67f5 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -12,18 +12,15 @@ namespace osg namespace SceneUtil { - /// renderbin which culls redundant state for shadow map rendering class ShadowsBin : public osgUtil::RenderBin { - private: - static std::array, GL_ALWAYS - GL_NEVER + 1> sCastingPrograms; + public: + template + using Array = std::array; - osg::ref_ptr mNoTestStateSet; - osg::ref_ptr mShaderAlphaTestStateSet; + using CastingPrograms = Array>; - std::array, GL_ALWAYS - GL_NEVER + 1> mAlphaFuncShaders; - public: META_Object(SceneUtil, ShadowsBin) ShadowsBin(); ShadowsBin(const ShadowsBin& rhs, const osg::CopyOp& copyop) @@ -65,7 +62,15 @@ namespace SceneUtil osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting, bool cullFaceOverridden); - static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms); + static void addPrototype(const std::string& name, const CastingPrograms& castingPrograms); + + private: + static CastingPrograms sCastingPrograms; + + osg::ref_ptr mNoTestStateSet; + osg::ref_ptr mShaderAlphaTestStateSet; + + Array> mAlphaFuncShaders; }; } From 590a340e6e436ae25abeca773ca5ac1895bdfe0e Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 2 Sep 2021 21:31:11 +0200 Subject: [PATCH 1509/2859] Remove redundant ShadowsBin::sCastingPrograms This variable is only used in ShadowsBin constructor and it's initialized each time before constructor call so required value can be just passed into ShadowsBin ctor. Make ShadowsBin default constructor private because it is required by osg even it's not actually called. --- components/sceneutil/shadowsbin.cpp | 13 ++++--------- components/sceneutil/shadowsbin.hpp | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index a6275a60e2..21f25dc4ad 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -42,11 +42,7 @@ namespace namespace SceneUtil { -ShadowsBin::CastingPrograms ShadowsBin::sCastingPrograms = { - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr -}; - -ShadowsBin::ShadowsBin() +ShadowsBin::ShadowsBin(const CastingPrograms& castingPrograms) { mNoTestStateSet = new osg::StateSet; mNoTestStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); @@ -57,10 +53,10 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); - for (size_t i = 0; i < sCastingPrograms.size(); ++i) + for (size_t i = 0; i < castingPrograms.size(); ++i) { mAlphaFuncShaders[i] = new osg::StateSet; - mAlphaFuncShaders[i]->setAttribute(sCastingPrograms[i], osg::StateAttribute::ON | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); + mAlphaFuncShaders[i]->setAttribute(castingPrograms[i], osg::StateAttribute::ON | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); } } @@ -152,8 +148,7 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un void ShadowsBin::addPrototype(const std::string & name, const CastingPrograms& castingPrograms) { - sCastingPrograms = castingPrograms; - osg::ref_ptr bin(new ShadowsBin); + osg::ref_ptr bin(new ShadowsBin(castingPrograms)); osgUtil::RenderBin::addRenderBinPrototype(name, bin); } diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index 67343c67f5..4b0d8cc544 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -22,7 +22,7 @@ namespace SceneUtil using CastingPrograms = Array>; META_Object(SceneUtil, ShadowsBin) - ShadowsBin(); + ShadowsBin(const CastingPrograms& castingPrograms); ShadowsBin(const ShadowsBin& rhs, const osg::CopyOp& copyop) : osgUtil::RenderBin(rhs, copyop) , mNoTestStateSet(rhs.mNoTestStateSet) @@ -65,7 +65,7 @@ namespace SceneUtil static void addPrototype(const std::string& name, const CastingPrograms& castingPrograms); private: - static CastingPrograms sCastingPrograms; + ShadowsBin() {} osg::ref_ptr mNoTestStateSet; osg::ref_ptr mShaderAlphaTestStateSet; From 562590720b4ebc1a758091b36d2be5d94c8edbb3 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 2 Sep 2021 22:01:20 +0200 Subject: [PATCH 1510/2859] Remove ShadowsBin prototype on MWShadowTechnique destruction To prevent crash: ==7733==ERROR: AddressSanitizer: heap-use-after-free on address 0x6040000ca1b0 at pc 0x55fcfa8d1ee3 bp 0x7ffd1c464e00 sp 0x7ffd1c464df0 READ of size 4 at 0x6040000ca1b0 thread T0 #0 0x55fcfa8d1ee2 in std::less::operator()(unsigned int const&, unsigned int const&) const /usr/include/c++/11.1.0/bits/stl_function.h:386 #1 0x55fcfa8d1ee2 in std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_lower_bound(std::_Rb_tree_node > >*, std::_Rb_tree_node_base*, unsigned int const&) /usr/include/c++/11.1.0/bits/stl_tree.h:1903 #2 0x55fcfa8ca76e in std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::lower_bound(unsigned int const&) /usr/include/c++/11.1.0/bits/stl_tree.h:1270 #3 0x55fcfa8ca76e in std::map, std::less, std::allocator > > >::lower_bound(unsigned int const&) /usr/include/c++/11.1.0/bits/stl_map.h:1259 #4 0x55fcfa8ca76e in std::map, std::less, std::allocator > > >::operator[](unsigned int const&) /usr/include/c++/11.1.0/bits/stl_map.h:497 #5 0x55fcfa8ca76e in osg::getOrCreateContextData(unsigned int) /home/elsid/dev/OpenSceneGraph/src/osg/ContextData.cpp:142 #6 0x55fcfab6848a in GLShaderManager* osg::get(unsigned int) /home/elsid/dev/OpenSceneGraph/include/osg/ContextData:154 #7 0x55fcfab6848a in osg::Shader::PerContextShader::~PerContextShader() /home/elsid/dev/OpenSceneGraph/src/osg/Shader.cpp:540 #8 0x55fcfab68dc6 in osg::Shader::PerContextShader::~PerContextShader() /home/elsid/dev/OpenSceneGraph/src/osg/Shader.cpp:541 #9 0x55fcfab4a3f3 in osg::Referenced::signalObserversAndDelete(bool, bool) const /home/elsid/dev/OpenSceneGraph/src/osg/Referenced.cpp:292 #10 0x55fcfab6d0ce in osg::Referenced::unref() const /home/elsid/dev/OpenSceneGraph/include/osg/Referenced:201 #11 0x55fcfab6d0ce in osg::ref_ptr::~ref_ptr() /home/elsid/dev/OpenSceneGraph/include/osg/ref_ptr:44 #12 0x55fcfab6d0ce in void std::_Destroy >(osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:140 #13 0x55fcfab6d0ce in void std::_Destroy_aux::__destroy*>(osg::ref_ptr*, osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:152 #14 0x55fcfab6d0ce in void std::_Destroy*>(osg::ref_ptr*, osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:185 #15 0x55fcfab6d0ce in void std::_Destroy*, osg::ref_ptr >(osg::ref_ptr*, osg::ref_ptr*, std::allocator >&) /usr/include/c++/11.1.0/bits/alloc_traits.h:746 #16 0x55fcfab6d0ce in std::vector, std::allocator > >::~vector() /usr/include/c++/11.1.0/bits/stl_vector.h:680 #17 0x55fcfab6d0ce in osg::Shader::ShaderObjects::~ShaderObjects() /home/elsid/dev/OpenSceneGraph/include/osg/Shader:264 #18 0x55fcfab6d0ce in osg::Shader::ShaderObjects::~ShaderObjects() /home/elsid/dev/OpenSceneGraph/include/osg/Shader:264 #19 0x55fcfab4a3f3 in osg::Referenced::signalObserversAndDelete(bool, bool) const /home/elsid/dev/OpenSceneGraph/src/osg/Referenced.cpp:292 #20 0x55fcfab6c532 in osg::Referenced::unref() const /home/elsid/dev/OpenSceneGraph/include/osg/Referenced:201 #21 0x55fcfab6c532 in osg::ref_ptr::~ref_ptr() /home/elsid/dev/OpenSceneGraph/include/osg/ref_ptr:44 #22 0x55fcfab6c532 in void std::_Destroy >(osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:140 #23 0x55fcfab6c532 in void std::_Destroy_aux::__destroy*>(osg::ref_ptr*, osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:152 #24 0x55fcfab6c532 in void std::_Destroy*>(osg::ref_ptr*, osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:185 #25 0x55fcfab6c532 in void std::_Destroy*, osg::ref_ptr >(osg::ref_ptr*, osg::ref_ptr*, std::allocator >&) /usr/include/c++/11.1.0/bits/alloc_traits.h:746 #26 0x55fcfab6c532 in std::vector, std::allocator > >::~vector() /usr/include/c++/11.1.0/bits/stl_vector.h:680 #27 0x55fcfab6c532 in osg::buffered_value >::~buffered_value() /home/elsid/dev/OpenSceneGraph/include/osg/buffered_value:26 #28 0x55fcfab6c532 in osg::Shader::~Shader() /home/elsid/dev/OpenSceneGraph/src/osg/Shader.cpp:271 #29 0x55fcfab6c80a in osg::Shader::~Shader() /home/elsid/dev/OpenSceneGraph/src/osg/Shader.cpp:271 #30 0x55fcfab4a3f3 in osg::Referenced::signalObserversAndDelete(bool, bool) const /home/elsid/dev/OpenSceneGraph/src/osg/Referenced.cpp:292 #31 0x55fcfab185bd in osg::Referenced::unref() const /home/elsid/dev/OpenSceneGraph/include/osg/Referenced:201 #32 0x55fcfab185bd in osg::ref_ptr::~ref_ptr() /home/elsid/dev/OpenSceneGraph/include/osg/ref_ptr:44 #33 0x55fcfab185bd in void std::_Destroy >(osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:140 #34 0x55fcfab185bd in void std::_Destroy_aux::__destroy*>(osg::ref_ptr*, osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:152 #35 0x55fcfab185bd in void std::_Destroy*>(osg::ref_ptr*, osg::ref_ptr*) /usr/include/c++/11.1.0/bits/stl_construct.h:185 #36 0x55fcfab185bd in void std::_Destroy*, osg::ref_ptr >(osg::ref_ptr*, osg::ref_ptr*, std::allocator >&) /usr/include/c++/11.1.0/bits/alloc_traits.h:746 #37 0x55fcfab185bd in std::vector, std::allocator > >::~vector() /usr/include/c++/11.1.0/bits/stl_vector.h:680 #38 0x55fcfab185bd in osg::Program::~Program() /home/elsid/dev/OpenSceneGraph/src/osg/Program.cpp:147 #39 0x55fcfab18ae0 in osg::Program::~Program() /home/elsid/dev/OpenSceneGraph/src/osg/Program.cpp:147 #40 0x55fcfab4a3f3 in osg::Referenced::signalObserversAndDelete(bool, bool) const /home/elsid/dev/OpenSceneGraph/src/osg/Referenced.cpp:292 #41 0x55fcf8582cfa in osg::Referenced::unref() const /home/elsid/dev/OpenSceneGraph/build/gcc/asan/install/include/osg/Referenced:201 #42 0x55fcf8582cfa in osg::ref_ptr::~ref_ptr() /home/elsid/dev/OpenSceneGraph/build/gcc/asan/install/include/osg/ref_ptr:44 #43 0x55fcf8582cfa in std::pair, unsigned int>::~pair() /usr/include/c++/11.1.0/bits/stl_pair.h:211 #44 0x55fcf8582cfa in std::pair const, std::pair, unsigned int> >::~pair() /usr/include/c++/11.1.0/bits/stl_pair.h:211 #45 0x55fcf8582cfa in void __gnu_cxx::new_allocator const, std::pair, unsigned int> > > >::destroy const, std::pair, unsigned int> > >(std::pair const, std::pair, unsigned int> >*) /usr/include/c++/11.1.0/ext/new_allocator.h:162 #46 0x55fcf8582cfa in void std::allocator_traits const, std::pair, unsigned int> > > > >::destroy const, std::pair, unsigned int> > >(std::allocator const, std::pair, unsigned int> > > >&, std::pair const, std::pair, unsigned int> >*) /usr/include/c++/11.1.0/bits/alloc_traits.h:531 #47 0x55fcf8582cfa in std::_Rb_tree, std::pair const, std::pair, unsigned int> >, std::_Select1st const, std::pair, unsigned int> > >, std::less >, std::allocator const, std::pair, unsigned int> > > >::_M_destroy_node(std::_Rb_tree_node const, std::pair, unsigned int> > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:623 #48 0x55fcf8582cfa in std::_Rb_tree, std::pair const, std::pair, unsigned int> >, std::_Select1st const, std::pair, unsigned int> > >, std::less >, std::allocator const, std::pair, unsigned int> > > >::_M_drop_node(std::_Rb_tree_node const, std::pair, unsigned int> > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:631 #49 0x55fcf8582cfa in std::_Rb_tree, std::pair const, std::pair, unsigned int> >, std::_Select1st const, std::pair, unsigned int> > >, std::less >, std::allocator const, std::pair, unsigned int> > > >::_M_erase(std::_Rb_tree_node const, std::pair, unsigned int> > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:1889 #50 0x55fcfac1f281 in std::_Rb_tree, std::pair const, std::pair, unsigned int> >, std::_Select1st const, std::pair, unsigned int> > >, std::less >, std::allocator const, std::pair, unsigned int> > > >::clear() /usr/include/c++/11.1.0/bits/stl_tree.h:1254 #51 0x55fcfac1f281 in std::map, std::pair, unsigned int>, std::less >, std::allocator const, std::pair, unsigned int> > > >::clear() /usr/include/c++/11.1.0/bits/stl_map.h:1134 #52 0x55fcfac1f281 in osg::StateSet::clear() /home/elsid/dev/OpenSceneGraph/src/osg/StateSet.cpp:738 #53 0x55fcfac2079e in osg::StateSet::~StateSet() /home/elsid/dev/OpenSceneGraph/src/osg/StateSet.cpp:285 #54 0x55fcfac20d3e in osg::StateSet::~StateSet() /home/elsid/dev/OpenSceneGraph/src/osg/StateSet.cpp:286 #55 0x55fcfab4a3f3 in osg::Referenced::signalObserversAndDelete(bool, bool) const /home/elsid/dev/OpenSceneGraph/src/osg/Referenced.cpp:292 #56 0x55fcfb20dcac in osg::Referenced::unref() const /home/elsid/dev/OpenSceneGraph/build/gcc/asan/install/include/osg/Referenced:201 #57 0x55fcfb20dcac in osg::ref_ptr::~ref_ptr() /home/elsid/dev/OpenSceneGraph/build/gcc/asan/install/include/osg/ref_ptr:44 #58 0x55fcfb20dcac in std::array, 8ul>::~array() /usr/include/c++/11.1.0/array:95 #59 0x55fcfb20dcac in SceneUtil::ShadowsBin::~ShadowsBin() /home/elsid/dev/openmw/components/sceneutil/shadowsbin.hpp:16 #60 0x55fcfb20dcac in SceneUtil::ShadowsBin::~ShadowsBin() /home/elsid/dev/openmw/components/sceneutil/shadowsbin.hpp:16 #61 0x55fcfab4a3f3 in osg::Referenced::signalObserversAndDelete(bool, bool) const /home/elsid/dev/OpenSceneGraph/src/osg/Referenced.cpp:292 #62 0x55fcfa6cc17f in osg::Referenced::unref() const /home/elsid/dev/OpenSceneGraph/include/osg/Referenced:201 #63 0x55fcfa6cc17f in osg::ref_ptr::~ref_ptr() /home/elsid/dev/OpenSceneGraph/include/osg/ref_ptr:44 #64 0x55fcfa6cc17f in std::pair, std::allocator > const, osg::ref_ptr >::~pair() /usr/include/c++/11.1.0/bits/stl_pair.h:211 #65 0x55fcfa6cc17f in void __gnu_cxx::new_allocator, std::allocator > const, osg::ref_ptr > > >::destroy, std::allocator > const, osg::ref_ptr > >(std::pair, std::allocator > const, osg::ref_ptr >*) /usr/include/c++/11.1.0/ext/new_allocator.h:162 #66 0x55fcfa6cc17f in void std::allocator_traits, std::allocator > const, osg::ref_ptr > > > >::destroy, std::allocator > const, osg::ref_ptr > >(std::allocator, std::allocator > const, osg::ref_ptr > > >&, std::pair, std::allocator > const, osg::ref_ptr >*) /usr/include/c++/11.1.0/bits/alloc_traits.h:531 #67 0x55fcfa6cc17f in std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, osg::ref_ptr >, std::_Select1st, std::allocator > const, osg::ref_ptr > >, std::less, std::allocator > >, std::allocator, std::allocator > const, osg::ref_ptr > > >::_M_destroy_node(std::_Rb_tree_node, std::allocator > const, osg::ref_ptr > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:623 #68 0x55fcfa6cc17f in std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, osg::ref_ptr >, std::_Select1st, std::allocator > const, osg::ref_ptr > >, std::less, std::allocator > >, std::allocator, std::allocator > const, osg::ref_ptr > > >::_M_drop_node(std::_Rb_tree_node, std::allocator > const, osg::ref_ptr > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:631 #69 0x55fcfa6cc17f in std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, osg::ref_ptr >, std::_Select1st, std::allocator > const, osg::ref_ptr > >, std::less, std::allocator > >, std::allocator, std::allocator > const, osg::ref_ptr > > >::_M_erase(std::_Rb_tree_node, std::allocator > const, osg::ref_ptr > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:1889 #70 0x55fcfa6cc122 in std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, osg::ref_ptr >, std::_Select1st, std::allocator > const, osg::ref_ptr > >, std::less, std::allocator > >, std::allocator, std::allocator > const, osg::ref_ptr > > >::_M_erase(std::_Rb_tree_node, std::allocator > const, osg::ref_ptr > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:1887 #71 0x55fcfa6cc122 in std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, osg::ref_ptr >, std::_Select1st, std::allocator > const, osg::ref_ptr > >, std::less, std::allocator > >, std::allocator, std::allocator > const, osg::ref_ptr > > >::_M_erase(std::_Rb_tree_node, std::allocator > const, osg::ref_ptr > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:1887 #72 0x55fcfa6cc245 in std::_Rb_tree, std::allocator >, std::pair, std::allocator > const, osg::ref_ptr >, std::_Select1st, std::allocator > const, osg::ref_ptr > >, std::less, std::allocator > >, std::allocator, std::allocator > const, osg::ref_ptr > > >::~_Rb_tree() /usr/include/c++/11.1.0/bits/stl_tree.h:984 #73 0x55fcfa6cc245 in std::map, std::allocator >, osg::ref_ptr, std::less, std::allocator > >, std::allocator, std::allocator > const, osg::ref_ptr > > >::~map() /usr/include/c++/11.1.0/bits/stl_map.h:302 #74 0x55fcfa6cc245 in RenderBinPrototypeList::~RenderBinPrototypeList() /home/elsid/dev/OpenSceneGraph/src/osgUtil/RenderBin.cpp:48 #75 0x55fcfa6cc245 in RenderBinPrototypeList::~RenderBinPrototypeList() /home/elsid/dev/OpenSceneGraph/src/osgUtil/RenderBin.cpp:48 #76 0x55fcfab4a3f3 in osg::Referenced::signalObserversAndDelete(bool, bool) const /home/elsid/dev/OpenSceneGraph/src/osg/Referenced.cpp:292 #77 0x55fcfa6cbcc1 in osg::Referenced::unref() const /home/elsid/dev/OpenSceneGraph/include/osg/Referenced:201 #78 0x55fcfa6cbcc1 in osg::ref_ptr::~ref_ptr() /home/elsid/dev/OpenSceneGraph/include/osg/ref_ptr:44 #79 0x7f3977bd04a6 in __run_exit_handlers (/usr/lib/libc.so.6+0x3f4a6) #80 0x7f3977bd064d in exit (/usr/lib/libc.so.6+0x3f64d) #81 0x7f3977bb8b2b in __libc_start_main (/usr/lib/libc.so.6+0x27b2b) #82 0x55fcf81ea12d in _start (/home/elsid/dev/openmw/build/gcc/asan/openmw+0xa4412d) 0x6040000ca1b0 is located 32 bytes inside of 48-byte region [0x6040000ca190,0x6040000ca1c0) freed by thread T0 here: #0 0x7f397a633d69 in operator delete(void*, unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cpp:172 #1 0x55fcfa8d1824 in __gnu_cxx::new_allocator > > >::deallocate(std::_Rb_tree_node > >*, unsigned long) /usr/include/c++/11.1.0/ext/new_allocator.h:139 #2 0x55fcfa8d1824 in std::allocator_traits > > > >::deallocate(std::allocator > > >&, std::_Rb_tree_node > >*, unsigned long) /usr/include/c++/11.1.0/bits/alloc_traits.h:492 #3 0x55fcfa8d1824 in std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_put_node(std::_Rb_tree_node > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:565 #4 0x55fcfa8d1824 in std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_drop_node(std::_Rb_tree_node > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:632 #5 0x55fcfa8d1824 in std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase(std::_Rb_tree_node > >*) /usr/include/c++/11.1.0/bits/stl_tree.h:1889 #6 0x55fcfa8d18da in std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::~_Rb_tree() /usr/include/c++/11.1.0/bits/stl_tree.h:984 #7 0x55fcfa8d18da in std::map, std::less, std::allocator > > >::~map() /usr/include/c++/11.1.0/bits/stl_map.h:302 #8 0x7f3977bd04a6 in __run_exit_handlers (/usr/lib/libc.so.6+0x3f4a6) previously allocated by thread T0 here: #0 0x7f397a632ca1 in operator new(unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cpp:99 #1 0x55fcfa8d3aa1 in __gnu_cxx::new_allocator > > >::allocate(unsigned long, void const*) /usr/include/c++/11.1.0/ext/new_allocator.h:121 #2 0x55fcfa8d3aa1 in std::allocator_traits > > > >::allocate(std::allocator > > >&, unsigned long) /usr/include/c++/11.1.0/bits/alloc_traits.h:460 #3 0x55fcfa8d3aa1 in std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_get_node() /usr/include/c++/11.1.0/bits/stl_tree.h:561 #4 0x55fcfa8d3aa1 in std::_Rb_tree_node > >* std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_create_node, std::tuple<> >(std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) /usr/include/c++/11.1.0/bits/stl_tree.h:611 #5 0x55fcfa8d3aa1 in std::_Rb_tree_iterator > > std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_emplace_hint_unique, std::tuple<> >(std::_Rb_tree_const_iterator > >, std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) /usr/include/c++/11.1.0/bits/stl_tree.h:2429 #6 0x55fcfa8cfd91 in std::map, std::less, std::allocator > > >::operator[](unsigned int const&) /usr/include/c++/11.1.0/bits/stl_map.h:501 #7 0x55fcfa8cfd91 in osg::ContextData::createNewContextID() /home/elsid/dev/OpenSceneGraph/src/osg/ContextData.cpp:171 #8 0x55fcfa9be218 in osg::GraphicsContext::createNewContextID() /home/elsid/dev/OpenSceneGraph/src/osg/GraphicsContext.cpp:320 #9 0x55fcfb68d1ef in SDLUtil::GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits*) /home/elsid/dev/openmw/components/sdlutil/sdlgraphicswindow.cpp:39 #10 0x55fcfa17f93a in OMW::Engine::createWindow(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:611 #11 0x55fcfa18398f in OMW::Engine::prepareEngine(Settings::Manager&) /home/elsid/dev/openmw/apps/openmw/engine.cpp:679 #12 0x55fcfa18c4f2 in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:949 #13 0x55fcfa13c9ff in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:316 #14 0x55fcfb49f562 in wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:205 #15 0x55fcfa133114 in main /home/elsid/dev/openmw/apps/openmw/main.cpp:328 #16 0x7f3977bb8b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24) SUMMARY: AddressSanitizer: heap-use-after-free /usr/include/c++/11.1.0/bits/stl_function.h:386 in std::less::operator()(unsigned int const&, unsigned int const&) const Shadow bytes around the buggy address: 0x0c08800113e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c08800113f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0880011400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0880011410: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0880011420: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c0880011430: fa fa fd fd fd fd[fd]fd fa fa fa fa fa fa fa fa 0x0c0880011440: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0880011450: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0880011460: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0880011470: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0880011480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==7733==ABORTING --- components/sceneutil/mwshadowtechnique.cpp | 26 +++++++++++++++------- components/sceneutil/mwshadowtechnique.hpp | 8 +++++-- components/sceneutil/shadowsbin.cpp | 6 ----- components/sceneutil/shadowsbin.hpp | 2 -- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 60f048b147..770917eda7 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -279,14 +279,7 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) } #endif // bin has to go inside camera cull or the rendertexture stage will override it - static osg::ref_ptr ss; - if (!ss) - { - ShadowsBin::addPrototype("ShadowsBin", _vdsm->getCastingPrograms()); - ss = new osg::StateSet; - ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); - } - cv->pushStateSet(ss); + cv->pushStateSet(_vdsm->getOrCreateShadowsBinStateSet()); if (_vdsm->getShadowedScene()) { _vdsm->getShadowedScene()->osg::Group::traverse(*nv); @@ -811,6 +804,8 @@ MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::C MWShadowTechnique::~MWShadowTechnique() { + if (_shadowsBin != nullptr) + osgUtil::RenderBin::removeRenderBinPrototype(_shadowsBin); } @@ -3282,3 +3277,18 @@ void SceneUtil::MWShadowTechnique::DebugHUD::addAnotherShadowMap() for(auto& uniformVector : mFrustumUniforms) uniformVector.push_back(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "transform")); } + +osg::ref_ptr SceneUtil::MWShadowTechnique::getOrCreateShadowsBinStateSet() +{ + if (_shadowsBinStateSet == nullptr) + { + if (_shadowsBin == nullptr) + { + _shadowsBin = new ShadowsBin(_castingPrograms); + osgUtil::RenderBin::addRenderBinPrototype(_shadowsBinName, _shadowsBin); + } + _shadowsBinStateSet = new osg::StateSet; + _shadowsBinStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, _shadowsBinName, osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); + } + return _shadowsBinStateSet; +} diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 3f6c0fb765..574d054204 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -215,8 +216,6 @@ namespace SceneUtil { virtual void createShaders(); - virtual std::array, GL_ALWAYS - GL_NEVER + 1> getCastingPrograms() const { return _castingPrograms; } - virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); @@ -237,6 +236,8 @@ namespace SceneUtil { void setWorldMask(unsigned int worldMask) { _worldMask = worldMask; } + osg::ref_ptr getOrCreateShadowsBinStateSet(); + protected: virtual ~MWShadowTechnique(); @@ -297,6 +298,9 @@ namespace SceneUtil { osg::ref_ptr _debugHud; std::array, GL_ALWAYS - GL_NEVER + 1> _castingPrograms; + const std::string _shadowsBinName = "ShadowsBin_" + std::to_string(reinterpret_cast(this)); + osg::ref_ptr _shadowsBin; + osg::ref_ptr _shadowsBinStateSet; }; } diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 21f25dc4ad..3e933cbb98 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -146,12 +146,6 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un return sg; } -void ShadowsBin::addPrototype(const std::string & name, const CastingPrograms& castingPrograms) -{ - osg::ref_ptr bin(new ShadowsBin(castingPrograms)); - osgUtil::RenderBin::addRenderBinPrototype(name, bin); -} - inline bool ShadowsBin::State::needTexture() const { return mAlphaBlend || (mAlphaFunc && mAlphaFunc->getFunction() != GL_ALWAYS); diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index 4b0d8cc544..2c838d509e 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -62,8 +62,6 @@ namespace SceneUtil osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting, bool cullFaceOverridden); - static void addPrototype(const std::string& name, const CastingPrograms& castingPrograms); - private: ShadowsBin() {} From a3b6bc7263946a988bacc4f1534997194d9da147 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 20 Oct 2021 16:28:18 +0200 Subject: [PATCH 1511/2859] Fix logic expression for not found value --- apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/esmloader/esmdata.cpp | 101 +++++++++++++++++++ components/esmloader/esmdata.cpp | 2 +- 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 apps/openmw_test_suite/esmloader/esmdata.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 3d19834066..9947f3e233 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -52,6 +52,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) sqlite3/transaction.cpp esmloader/load.cpp + esmloader/esmdata.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/esmloader/esmdata.cpp b/apps/openmw_test_suite/esmloader/esmdata.cpp new file mode 100644 index 0000000000..da78aaa3a2 --- /dev/null +++ b/apps/openmw_test_suite/esmloader/esmdata.cpp @@ -0,0 +1,101 @@ +#include + +#include + +#include +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace EsmLoader; + + struct Params + { + std::string mRefId; + ESM::RecNameInts mType; + std::string mResult; + std::function mPushBack; + }; + + struct EsmLoaderGetModelTest : TestWithParam {}; + + TEST_P(EsmLoaderGetModelTest, shouldReturnFoundModelName) + { + EsmData data; + GetParam().mPushBack(data); + EXPECT_EQ(EsmLoader::getModel(data, GetParam().mRefId, GetParam().mType), GetParam().mResult); + } + + void pushBack(ESM::Activator&& value, EsmData& esmData) + { + esmData.mActivators.push_back(std::move(value)); + } + + void pushBack(ESM::Container&& value, EsmData& esmData) + { + esmData.mContainers.push_back(std::move(value)); + } + + void pushBack(ESM::Door&& value, EsmData& esmData) + { + esmData.mDoors.push_back(std::move(value)); + } + + void pushBack(ESM::Static&& value, EsmData& esmData) + { + esmData.mStatics.push_back(std::move(value)); + } + + template + struct PushBack + { + std::string mId; + std::string mModel; + + void operator()(EsmData& esmData) const + { + T value; + value.mId = mId; + value.mModel = mModel; + pushBack(std::move(value), esmData); + } + }; + + const std::array params = { + Params {"acti_ref_id", ESM::REC_ACTI, "acti_model", PushBack {"acti_ref_id", "acti_model"}}, + Params {"cont_ref_id", ESM::REC_CONT, "cont_model", PushBack {"cont_ref_id", "cont_model"}}, + Params {"door_ref_id", ESM::REC_DOOR, "door_model", PushBack {"door_ref_id", "door_model"}}, + Params {"static_ref_id", ESM::REC_STAT, "static_model", PushBack {"static_ref_id", "static_model"}}, + Params {"acti_ref_id_a", ESM::REC_ACTI, "", PushBack {"acti_ref_id_z", "acti_model"}}, + Params {"cont_ref_id_a", ESM::REC_CONT, "", PushBack {"cont_ref_id_z", "cont_model"}}, + Params {"door_ref_id_a", ESM::REC_DOOR, "", PushBack {"door_ref_id_z", "door_model"}}, + Params {"static_ref_id_a", ESM::REC_STAT, "", PushBack {"static_ref_id_z", "static_model"}}, + Params {"acti_ref_id_z", ESM::REC_ACTI, "", PushBack {"acti_ref_id_a", "acti_model"}}, + Params {"cont_ref_id_z", ESM::REC_CONT, "", PushBack {"cont_ref_id_a", "cont_model"}}, + Params {"door_ref_id_z", ESM::REC_DOOR, "", PushBack {"door_ref_id_a", "door_model"}}, + Params {"static_ref_id_z", ESM::REC_STAT, "", PushBack {"static_ref_id_a", "static_model"}}, + Params {"ref_id", ESM::REC_STAT, "", [] (EsmData&) {}}, + Params {"ref_id", ESM::REC_BOOK, "", [] (EsmData&) {}}, + }; + + INSTANTIATE_TEST_SUITE_P(Params, EsmLoaderGetModelTest, ValuesIn(params)); + + TEST(EsmLoaderGetGameSettingTest, shouldReturnFoundValue) + { + std::vector settings; + ESM::GameSetting setting; + setting.mId = "setting"; + setting.mValue = ESM::Variant(42); + settings.push_back(setting); + EXPECT_EQ(EsmLoader::getGameSetting(settings, "setting"), ESM::Variant(42)); + } + + TEST(EsmLoaderGetGameSettingTest, shouldThrowExceptionWhenNotFound) + { + const std::vector settings; + EXPECT_THROW(EsmLoader::getGameSetting(settings, "setting"), std::runtime_error); + } +} diff --git a/components/esmloader/esmdata.cpp b/components/esmloader/esmdata.cpp index 06f358ea0f..bf2a8675d2 100644 --- a/components/esmloader/esmdata.cpp +++ b/components/esmloader/esmdata.cpp @@ -37,7 +37,7 @@ namespace EsmLoader { const auto it = std::lower_bound(values.begin(), values.end(), refId, LessById {}); - if (it == values.end() && it->mId != refId) + if (it == values.end() || it->mId != refId) return returnAs(std::forward(f)); return std::forward(f)(*it); From a854a6e04a8437d74078906d1d1720c0649e4e6f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 20 Oct 2021 21:02:15 +0000 Subject: [PATCH 1512/2859] removes UnrefQueue (#3181) Currently, we use an `UnrefQueue` which supposedly aims to transfer destruction costs to another thread. The implications of this unusual pattern can not be well understood because some allocators might free resources more efficiently if they are freed by the same thread that allocated them. In addition, `UnrefQueue` complicates the validation of thread safety in our engine. Lastly, our current usage of `UnrefQueue` triggers `ref()`, `unref()` atomic operations as objects are passed into the queue. These operations could be more expensive than the actual destruction. With this PR we thus remove `UnrefQueue`. We can expect these changes to have a minor impact at most because we free most resources elsewhere in `ResourceSystem::updateCache`. --- apps/openmw/mwphysics/physicssystem.cpp | 9 ----- apps/openmw/mwphysics/physicssystem.hpp | 9 ----- apps/openmw/mwrender/objects.cpp | 12 +----- apps/openmw/mwrender/objects.hpp | 9 +---- apps/openmw/mwrender/renderingmanager.cpp | 14 +------ apps/openmw/mwrender/renderingmanager.hpp | 3 -- apps/openmw/mwworld/cellpreloader.cpp | 17 ++------ apps/openmw/mwworld/cellpreloader.hpp | 8 ---- apps/openmw/mwworld/scene.cpp | 6 --- components/sceneutil/unrefqueue.cpp | 39 ------------------ components/sceneutil/unrefqueue.hpp | 44 --------------------- components/terrain/compositemaprenderer.cpp | 17 -------- components/terrain/compositemaprenderer.hpp | 12 ------ components/terrain/world.cpp | 5 --- components/terrain/world.hpp | 8 ---- 15 files changed, 6 insertions(+), 206 deletions(-) delete mode 100644 components/sceneutil/unrefqueue.cpp delete mode 100644 components/sceneutil/unrefqueue.hpp diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5f7ee0523a..6c46fd6d05 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include // FindRecIndexVisitor @@ -160,11 +159,6 @@ namespace MWPhysics mProjectiles.clear(); } - void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) - { - mUnrefQueue = unrefQueue; - } - Resource::BulletShapeManager *PhysicsSystem::getShapeManager() { return mShapeManager.get(); @@ -513,9 +507,6 @@ namespace MWPhysics { if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { - if (mUnrefQueue.get()) - mUnrefQueue->push(foundObject->second->getShapeInstance()); - mAnimatedObjects.erase(foundObject->second.get()); mObjects.erase(foundObject); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 30fdbcc122..6ec4ebfda9 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -36,11 +36,6 @@ namespace Resource class ResourceSystem; } -namespace SceneUtil -{ - class UnrefQueue; -} - class btCollisionWorld; class btBroadphaseInterface; class btDefaultCollisionConfiguration; @@ -118,8 +113,6 @@ namespace MWPhysics PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); virtual ~PhysicsSystem (); - void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); - Resource::BulletShapeManager* getShapeManager(); void enableWater(float height); @@ -262,8 +255,6 @@ namespace MWPhysics std::pair>, std::vector> prepareFrameData(bool willSimulate); - osg::ref_ptr mUnrefQueue; - std::unique_ptr mBroadphase; std::unique_ptr mCollisionConfiguration; std::unique_ptr mDispatcher; diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index ec1c4397bf..f51008fffa 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -4,7 +4,6 @@ #include #include -#include #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" @@ -18,10 +17,9 @@ namespace MWRender { -Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode, SceneUtil::UnrefQueue* unrefQueue) +Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode) : mRootNode(rootNode) , mResourceSystem(resourceSystem) - , mUnrefQueue(unrefQueue) { } @@ -117,9 +115,6 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) PtrAnimationMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) { - if (mUnrefQueue.get()) - mUnrefQueue->push(iter->second); - mObjects.erase(iter); if (ptr.getClass().isActor()) @@ -146,9 +141,6 @@ void Objects::removeCell(const MWWorld::CellStore* store) MWWorld::Ptr ptr = iter->second->getPtr(); if(ptr.getCell() == store) { - if (mUnrefQueue.get()) - mUnrefQueue->push(iter->second); - if (ptr.getClass().isNpc() && ptr.getRefData().getCustomData()) { MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); @@ -166,8 +158,6 @@ void Objects::removeCell(const MWWorld::CellStore* store) if(cell != mCellSceneNodes.end()) { cell->second->getParent(0)->removeChild(cell->second); - if (mUnrefQueue.get()) - mUnrefQueue->push(cell->second); mCellSceneNodes.erase(cell); } } diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 98ebb95d98..5f8b7dfc9e 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -24,11 +24,6 @@ namespace MWWorld class CellStore; } -namespace SceneUtil -{ - class UnrefQueue; -} - namespace MWRender{ class Animation; @@ -66,12 +61,10 @@ class Objects{ Resource::ResourceSystem* mResourceSystem; - osg::ref_ptr mUnrefQueue; - void insertBegin(const MWWorld::Ptr& ptr); public: - Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode, SceneUtil::UnrefQueue* unrefQueue); + Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode); ~Objects(); /// @param animated Attempt to load separate keyframes from a .kf file matching the model file? diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 14000273db..1f29c45182 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -37,7 +37,6 @@ #include #include #include -#include #include #include @@ -289,7 +288,6 @@ namespace MWRender , mRootNode(rootNode) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) - , mUnrefQueue(new SceneUtil::UnrefQueue) , mNavigator(navigator) , mMinimumAmbientLuminance(0.f) , mNightEyeFactor(0.f) @@ -387,7 +385,7 @@ namespace MWRender mRecastMesh.reset(new RecastMesh(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator"))); mPathgrid.reset(new Pathgrid(mRootNode)); - mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); + mObjects.reset(new Objects(mResourceSystem, sceneRoot)); if (getenv("OPENMW_DONT_PRECOMPILE") == nullptr) { @@ -434,7 +432,6 @@ namespace MWRender mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); - mTerrain->setWorkQueue(mWorkQueue.get()); if (groundcover) { @@ -569,11 +566,6 @@ namespace MWRender return mWorkQueue.get(); } - SceneUtil::UnrefQueue* RenderingManager::getUnrefQueue() - { - return mUnrefQueue.get(); - } - Terrain::World* RenderingManager::getTerrain() { return mTerrain.get(); @@ -804,8 +796,6 @@ namespace MWRender { reportStats(); - mUnrefQueue->flush(mWorkQueue.get()); - float rainIntensity = mSky->getPrecipitationAlpha(); mWater->setRainIntensity(rainIntensity); @@ -1223,8 +1213,6 @@ namespace MWRender unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); if (stats->collectStats("resource")) { - stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getNumItems()); - mTerrain->reportStats(frameNumber, stats); } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index bd8bbe4694..b8d5d955c8 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -59,7 +59,6 @@ namespace SceneUtil { class ShadowManager; class WorkQueue; - class UnrefQueue; } namespace DetourNavigator @@ -106,7 +105,6 @@ namespace MWRender Resource::ResourceSystem* getResourceSystem(); SceneUtil::WorkQueue* getWorkQueue(); - SceneUtil::UnrefQueue* getUnrefQueue(); Terrain::World* getTerrain(); void preloadCommonAssets(); @@ -262,7 +260,6 @@ namespace MWRender Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; - osg::ref_ptr mUnrefQueue; osg::ref_ptr mSunLight; diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 590e1e9c00..fc09a6e9a8 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -322,11 +321,10 @@ namespace MWWorld PreloadMap::iterator found = mPreloadCells.find(cell); if (found != mPreloadCells.end()) { - // do the deletion in the background thread if (found->second.mWorkItem) { found->second.mWorkItem->abort(); - mUnrefQueue->push(std::move(found->second.mWorkItem)); + found->second.mWorkItem = nullptr; } mPreloadCells.erase(found); @@ -340,7 +338,7 @@ namespace MWWorld if (it->second.mWorkItem) { it->second.mWorkItem->abort(); - mUnrefQueue->push(it->second.mWorkItem); + it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); @@ -356,7 +354,7 @@ namespace MWWorld if (it->second.mWorkItem) { it->second.mWorkItem->abort(); - mUnrefQueue->push(it->second.mWorkItem); + it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); } @@ -409,11 +407,6 @@ namespace MWWorld mWorkQueue = workQueue; } - void CellPreloader::setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue) - { - mUnrefQueue = unrefQueue; - } - bool CellPreloader::syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener) { if (!mTerrainPreloadItem) @@ -455,11 +448,7 @@ namespace MWWorld else { if (mTerrainViews.size() > positions.size()) - { - for (unsigned int i=positions.size(); ipush(mTerrainViews[i]); mTerrainViews.resize(positions.size()); - } else if (mTerrainViews.size() < positions.size()) { for (unsigned int i=mTerrainViews.size(); i workQueue); - void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); - typedef std::pair PositionCellGrid; void setTerrainPreloadPositions(const std::vector& positions); @@ -87,7 +80,6 @@ namespace MWWorld Terrain::World* mTerrain; MWRender::LandManager* mLandManager; osg::ref_ptr mWorkQueue; - osg::ref_ptr mUnrefQueue; double mExpiryDelay; unsigned int mMinCacheSize; unsigned int mMaxCacheSize; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index e333e00623..d49e6c0e8b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -654,7 +653,6 @@ namespace MWWorld } mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime()); - mRendering.getUnrefQueue()->flush(mRendering.getWorkQueue()); loadingListener->increaseProgress (1); i++; @@ -701,7 +699,6 @@ namespace MWWorld } mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime()); - mRendering.getUnrefQueue()->flush(mRendering.getWorkQueue()); loadingListener->increaseProgress (1); i++; @@ -755,9 +752,6 @@ namespace MWWorld mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager())); mPreloader->setWorkQueue(mRendering.getWorkQueue()); - mPreloader->setUnrefQueue(rendering.getUnrefQueue()); - mPhysics->setUnrefQueue(rendering.getUnrefQueue()); - rendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); mPreloader->setExpiryDelay(Settings::Manager::getFloat("preload cell expiry delay", "Cells")); diff --git a/components/sceneutil/unrefqueue.cpp b/components/sceneutil/unrefqueue.cpp deleted file mode 100644 index 50b23cae92..0000000000 --- a/components/sceneutil/unrefqueue.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "unrefqueue.hpp" - -//#include - -//#include - -namespace SceneUtil -{ - void UnrefWorkItem::doWork() - { - mObjects.clear(); - } - - UnrefQueue::UnrefQueue() - { - mWorkItem = new UnrefWorkItem; - } - - void UnrefQueue::push(const osg::Referenced *obj) - { - mWorkItem->mObjects.emplace_back(obj); - } - - void UnrefQueue::flush(SceneUtil::WorkQueue *workQueue) - { - if (mWorkItem->mObjects.empty()) - return; - - workQueue->addWorkItem(mWorkItem, true); - - mWorkItem = new UnrefWorkItem; - } - - unsigned int UnrefQueue::getNumItems() const - { - return mWorkItem->mObjects.size(); - } - -} diff --git a/components/sceneutil/unrefqueue.hpp b/components/sceneutil/unrefqueue.hpp deleted file mode 100644 index 84372d28c0..0000000000 --- a/components/sceneutil/unrefqueue.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef OPENMW_COMPONENTS_UNREFQUEUE_H -#define OPENMW_COMPONENTS_UNREFQUEUE_H - -#include - -#include -#include - -#include - -namespace SceneUtil -{ - class WorkQueue; - - class UnrefWorkItem : public SceneUtil::WorkItem - { - public: - std::deque > mObjects; - void doWork() override; - }; - - /// @brief Handles unreferencing of objects through the WorkQueue. Typical use scenario - /// would be the main thread pushing objects that are no longer needed, and the background thread deleting them. - class UnrefQueue : public osg::Referenced - { - public: - UnrefQueue(); - - /// Adds an object to the list of objects to be unreferenced. Call from the main thread. - void push(const osg::Referenced* obj); - - /// Adds a WorkItem to the given WorkQueue that will clear the list of objects in a worker thread, thus unreferencing them. - /// Call from the main thread. - void flush(SceneUtil::WorkQueue* workQueue); - - unsigned int getNumItems() const; - - private: - osg::ref_ptr mWorkItem; - }; - -} - -#endif diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp index 2a3fa47daa..55da3d347a 100644 --- a/components/terrain/compositemaprenderer.cpp +++ b/components/terrain/compositemaprenderer.cpp @@ -4,9 +4,6 @@ #include #include -#include -#include - #include namespace Terrain @@ -21,8 +18,6 @@ CompositeMapRenderer::CompositeMapRenderer() mFBO = new osg::FrameBufferObject; - mUnrefQueue = new SceneUtil::UnrefQueue; - getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } @@ -30,11 +25,6 @@ CompositeMapRenderer::~CompositeMapRenderer() { } -void CompositeMapRenderer::setWorkQueue(SceneUtil::WorkQueue* workQueue) -{ - mWorkQueue = workQueue; -} - void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const { double dt = mTimer.time_s(); @@ -45,9 +35,6 @@ void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const double availableTime = std::max((targetFrameTime - dt)*conservativeTimeRatio, mMinimumTimeAvailable); - if (mWorkQueue) - mUnrefQueue->flush(mWorkQueue.get()); - std::lock_guard lock(mMutex); if (mImmediateCompileSet.empty() && mCompileSet.empty()) @@ -139,10 +126,6 @@ void CompositeMapRenderer::compile(CompositeMap &compositeMap, osg::RenderInfo & ++compositeMap.mCompiled; - if (mWorkQueue) - { - mUnrefQueue->push(compositeMap.mDrawables[i]); - } compositeMap.mDrawables[i] = nullptr; if (timeLeft) diff --git a/components/terrain/compositemaprenderer.hpp b/components/terrain/compositemaprenderer.hpp index 257173af46..f0021c88fe 100644 --- a/components/terrain/compositemaprenderer.hpp +++ b/components/terrain/compositemaprenderer.hpp @@ -13,12 +13,6 @@ namespace osg class Texture2D; } -namespace SceneUtil -{ - class UnrefQueue; - class WorkQueue; -} - namespace Terrain { @@ -45,9 +39,6 @@ namespace Terrain void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const; - /// Set a WorkQueue to delete compiled composite map layers in the background thread - void setWorkQueue(SceneUtil::WorkQueue* workQueue); - /// Set the available time in seconds for compiling (non-immediate) composite maps each frame void setMinimumTimeAvailableForCompile(double time); @@ -67,9 +58,6 @@ namespace Terrain double mMinimumTimeAvailable; mutable osg::Timer mTimer; - osg::ref_ptr mUnrefQueue; - osg::ref_ptr mWorkQueue; - typedef std::set > CompileSet; mutable CompileSet mCompileSet; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 17a51913e5..582cc68b1b 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -84,11 +84,6 @@ World::~World() } } -void World::setWorkQueue(SceneUtil::WorkQueue* workQueue) -{ - mCompositeMapRenderer->setWorkQueue(workQueue); -} - void World::setBordersVisible(bool visible) { mBorderVisible = visible; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index def7a2eccf..b9fa7c0160 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -28,11 +28,6 @@ namespace Resource class ResourceSystem; } -namespace SceneUtil -{ - class WorkQueue; -} - namespace Loading { class Reporter; @@ -115,9 +110,6 @@ namespace Terrain World(osg::Group* parent, Storage* storage, unsigned int nodeMask); virtual ~World(); - /// Set a WorkQueue to delete objects in the background thread. - void setWorkQueue(SceneUtil::WorkQueue* workQueue); - /// See CompositeMapRenderer::setTargetFrameRate void setTargetFrameRate(float rate); From 2a0e1697b6634387daa3e9c12091744eec150bd5 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Jun 2020 18:01:22 +0200 Subject: [PATCH 1513/2859] Deduplicate swim speed formula implementation --- apps/openmw/mwclass/actor.hpp | 11 +++++++++++ apps/openmw/mwclass/creature.cpp | 6 +----- apps/openmw/mwclass/npc.cpp | 14 +------------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 886ffe4771..596bdf26ec 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -3,6 +3,8 @@ #include "../mwworld/class.hpp" +#include + namespace ESM { struct GameSetting; @@ -17,6 +19,15 @@ namespace MWClass Actor() = default; + template + float getSwimSpeedImpl(const MWWorld::Ptr& ptr, const GMST& gmst, const MWMechanics::MagicEffects& mageffects, float baseSpeed) const + { + return baseSpeed + * (1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude()) + * (gmst.fSwimRunBase->mValue.getFloat() + + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat()); + } + public: ~Actor() override = default; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index c20a005724..dae2dedc54 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -891,12 +891,8 @@ namespace MWClass float Creature::getSwimSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); - const GMST& gmst = getGmst(); const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); - return getWalkSpeed(ptr) - * (1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude()) - * (gmst.fSwimRunBase->mValue.getFloat() - + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat()); + return getSwimSpeedImpl(ptr, getGmst(), mageffects, getWalkSpeed(ptr)); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 59280a0f02..78d2f1ebd5 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1476,7 +1476,6 @@ namespace MWClass float Npc::getSwimSpeed(const MWWorld::Ptr& ptr) const { - const GMST& gmst = getGmst(); const MWBase::World* world = MWBase::Environment::get().getWorld(); const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const NpcCustomData* npcdata = static_cast(ptr.getRefData().getCustomData()); @@ -1486,17 +1485,6 @@ namespace MWClass const bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run) && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); - float swimSpeed; - - if (running) - swimSpeed = getRunSpeed(ptr); - else - swimSpeed = getWalkSpeed(ptr); - - swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); - swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() - + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat(); - - return swimSpeed; + return getSwimSpeedImpl(ptr, getGmst(), mageffects, running ? getRunSpeed(ptr) : getWalkSpeed(ptr)); } } From 68ed8487b591379a95ad9fa2d12f239657ecbbd7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Jun 2020 17:43:30 +0200 Subject: [PATCH 1514/2859] Use lambda instead of bool to make sure GMST initialized once This is less expensive than having 2 static variables and thread-safe. --- apps/openmw/mwclass/creature.cpp | 12 +++++++----- apps/openmw/mwclass/npc.cpp | 10 +++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index c20a005724..130b1967ae 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -82,12 +82,13 @@ namespace MWClass const Creature::GMST& Creature::getGmst() { - static GMST gmst; - static bool inited = false; - if (!inited) + static const GMST gmst = [] { + GMST gmst; + const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); + gmst.fMinWalkSpeedCreature = store.find("fMinWalkSpeedCreature"); gmst.fMaxWalkSpeedCreature = store.find("fMaxWalkSpeedCreature"); gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); @@ -101,8 +102,9 @@ namespace MWClass gmst.fKnockDownMult = store.find("fKnockDownMult"); gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); - inited = true; - } + + return gmst; + } (); return gmst; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 59280a0f02..462e9442fd 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -266,10 +266,10 @@ namespace MWClass const Npc::GMST& Npc::getGmst() { - static GMST gmst; - static bool inited = false; - if(!inited) + static const GMST gmst = [] { + GMST gmst; + const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); @@ -294,8 +294,8 @@ namespace MWClass gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult"); - inited = true; - } + return gmst; + } (); return gmst; } From 31a7c83cc748028247ef1c247145433c5d8e0ff4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 21 Oct 2021 17:22:15 +0200 Subject: [PATCH 1515/2859] Fix a regression involving default arguments --- apps/openmw/mwmechanics/combat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 6407d03f23..c1d5f711fc 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -47,7 +47,7 @@ namespace MWMechanics { MWMechanics::CastSpell cast(attacker, victim, fromProjectile); cast.mHitPosition = hitPosition; - cast.cast(object, false); + cast.cast(object, 0, false); // Apply magic effects directly instead of waiting a frame to allow soul trap to work on one-hit kills if(!victim.isEmpty() && victim.getClass().isActor()) MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(victim); From 63fa6d3b7c5b7221dcef7e8db45309116e45b904 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 21 Oct 2021 17:28:36 +0200 Subject: [PATCH 1516/2859] Apply on self effects on projectiles to the target instead --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/spellcasting.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25ae0c45d0..d38729248e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod Bug #6302: Teleporting disabled actor breaks its disabled state Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken + Bug #6321: Arrow enchantments should always be applied to the target Bug #6322: Total sold/cost should reset to 0 when there are no items offered Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Bug #6326: Detect Enchantment/Key should detect items in unresolved containers diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index adbaa30c15..026f6b5f19 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -333,7 +333,10 @@ namespace MWMechanics mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); } - inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); + if (isProjectile) + inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Self); + else + inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); if (isProjectile || !mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); From b5f0057ac95db37dae17b8765396eb37ba9489a8 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Oct 2021 19:48:31 +0200 Subject: [PATCH 1517/2859] Fix tests on windows --- apps/openmw_test_suite/CMakeLists.txt | 15 +++++--------- apps/openmw_test_suite/esmloader/load.cpp | 10 +++++----- apps/openmw_test_suite/esmloader/settings.hpp | 20 ------------------- apps/openmw_test_suite/openmw_test_suite.cpp | 5 ----- components/esmloader/esmdata.hpp | 2 +- components/sqlite3/request.hpp | 1 + 6 files changed, 12 insertions(+), 41 deletions(-) delete mode 100644 apps/openmw_test_suite/esmloader/settings.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 3d19834066..e82efaa718 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -77,17 +77,12 @@ if (GTEST_FOUND AND GMOCK_FOUND) endif (CMAKE_CL_64) endif (MSVC) - include(FetchContent) - FetchContent_Declare(example_suite_template_game - URL https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame - URL_HASH MD5=bf3691034a38611534c74c3b89a7d2c3 - SOURCE_DIR fetched/example_suite_template_game - DOWNLOAD_NO_EXTRACT ON + file(DOWNLOAD + https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame + ${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame + EXPECTED_MD5 bf3691034a38611534c74c3b89a7d2c3 ) - FetchContent_MakeAvailableExcludeFromAll(example_suite_template_game) - add_custom_target(example_suite_template_game SOURCES fetched/example_suite_template_game/template.omwgame) - - add_dependencies(openmw_test_suite example_suite_template_game) + target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data") endif() diff --git a/apps/openmw_test_suite/esmloader/load.cpp b/apps/openmw_test_suite/esmloader/load.cpp index 71a8d2534a..0f00847226 100644 --- a/apps/openmw_test_suite/esmloader/load.cpp +++ b/apps/openmw_test_suite/esmloader/load.cpp @@ -1,5 +1,3 @@ -#include "settings.hpp" - #include #include #include @@ -7,16 +5,18 @@ #include +#ifndef OPENMW_DATA_DIR +#error "OPENMW_DATA_DIR is not defined" +#endif + namespace { using namespace testing; using namespace EsmLoader; - using EsmLoaderTests::Settings; struct EsmLoaderTest : Test { - const boost::filesystem::path mDataDir {Settings::impl().mBasePath / "apps/openmw_test_suite/fetched/example_suite_template_game"}; - const Files::PathContainer mDataDirs {{mDataDir.string()}}; + const Files::PathContainer mDataDirs {{std::string(OPENMW_DATA_DIR)}}; const Files::Collections mFileCollections {mDataDirs, true}; const std::vector mContentFiles {{"template.omwgame"}}; }; diff --git a/apps/openmw_test_suite/esmloader/settings.hpp b/apps/openmw_test_suite/esmloader/settings.hpp deleted file mode 100644 index 0f36310bc6..0000000000 --- a/apps/openmw_test_suite/esmloader/settings.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef OPENMW_TEST_SUITE_LOAD_SETTINGS_H -#define OPENMW_TEST_SUITE_LOAD_SETTINGS_H - -#include - -namespace EsmLoaderTests -{ - struct Settings - { - boost::filesystem::path mBasePath; - - static Settings& impl() - { - static Settings value; - return value; - } - }; -} - -#endif diff --git a/apps/openmw_test_suite/openmw_test_suite.cpp b/apps/openmw_test_suite/openmw_test_suite.cpp index 5fb9bda901..7364b20fdb 100644 --- a/apps/openmw_test_suite/openmw_test_suite.cpp +++ b/apps/openmw_test_suite/openmw_test_suite.cpp @@ -1,9 +1,5 @@ -#include "esmloader/settings.hpp" - #include -#include - #ifdef WIN32 //we cannot use GTEST_API_ before main if we're building standalone exe application, //and we're linking GoogleTest / GoogleMock as DLLs and not linking gtest_main / gmock_main @@ -11,7 +7,6 @@ int main(int argc, char **argv) { #else GTEST_API_ int main(int argc, char **argv) { #endif - EsmLoaderTests::Settings::impl().mBasePath = boost::filesystem::path(argv[0]).parent_path(); testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/components/esmloader/esmdata.hpp b/components/esmloader/esmdata.hpp index 624f50a5e6..afdcc1748d 100644 --- a/components/esmloader/esmdata.hpp +++ b/components/esmloader/esmdata.hpp @@ -15,7 +15,7 @@ namespace ESM struct GameSetting; struct Land; struct Static; - struct Variant; + class Variant; } namespace EsmLoader diff --git a/components/sqlite3/request.hpp b/components/sqlite3/request.hpp index 378dd5fdf7..339c7f7521 100644 --- a/components/sqlite3/request.hpp +++ b/components/sqlite3/request.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include From 07d505563e323a424f2f86c017c377d0c52ea9a2 Mon Sep 17 00:00:00 2001 From: Fanael Linithien Date: Fri, 22 Oct 2021 23:33:10 +0000 Subject: [PATCH 1518/2859] Remove non-existent file from CMakeLists --- components/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7b06b6702d..7e1abe0a4e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -55,7 +55,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller - lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer + lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt screencapture ) From 1848f7f9158aeb1a4cf90d25b2bd6c0f3619cd15 Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 22 Oct 2021 21:33:32 -0400 Subject: [PATCH 1519/2859] Overhaul raindrop water ripple effect --- files/shaders/water_fragment.glsl | 62 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 26f83e052c..ac12b355ee 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -70,33 +70,56 @@ float randPhase(vec2 c) return fract((c.x * c.y) / (c.x + c.y + 0.1)); } -vec4 circle(vec2 coords, vec2 i_part, float phase) +float blip(float x) { - vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(i_part) - 1.0); - vec2 toCenter = coords - center; - float d = length(toCenter); - - float r = RAIN_RIPPLE_RADIUS * phase; - - if (d > r) - return vec4(0.0,0.0,1.0,0.0); - - float sinValue = (sin(d / r * 1.2) + 0.7) / 2.0; + x = max(0.0, 1.0-x*x); + return x*x*x; +} - float height = (1.0 - abs(phase)) * pow(sinValue,3.0); +float blipDerivative(float x) +{ + x = clamp(x, -1.0, 1.0); + float n = x*x-1.0; + return -6.0*x*n*n; +} - vec3 normal = normalize(mix(vec3(0.0,0.0,1.0),vec3(normalize(toCenter),0.0),height)); +vec2 randomize_center(vec2 i_part, float time) +{ + time = 1.0 + mod(time, 10000.0); // so things don't get out of hand after long runtimes + vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(i_part*time) - 1.0); + return center; +} - return vec4(normal,height); +vec4 circle(vec2 coords, vec2 uv, float adjusted_time) +{ + vec2 center = randomize_center(floor(uv * RAIN_RIPPLE_GAPS), floor(adjusted_time)); + float phase = fract(adjusted_time); + vec2 toCenter = coords - center; + float r = RAIN_RIPPLE_RADIUS; + float d = length(toCenter); + float ringfollower = (phase-d/r)*6.0-1.0; + float energy = 1.0-phase; + energy = energy*energy; + + vec2 normal = -toCenter*blipDerivative(ringfollower)*5.0; + float height = blip(ringfollower); + vec4 ret = vec4(normal.x, normal.y, height, height); + ret.xyw *= energy; + ret.xyz = normalize(ret.xyz); + return ret; } +const float RAIN_RING_TIME_OFFSET = 1/6.0; vec4 rain(vec2 uv, float time) { vec2 i_part = floor(uv * RAIN_RIPPLE_GAPS); + float time_prog = time * 1.2 + randPhase(i_part); vec2 f_part = fract(uv * RAIN_RIPPLE_GAPS); - return circle(f_part,i_part,fract(time * 1.2 + randPhase(i_part))); + return circle(f_part, uv, time_prog) + - circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET) / 4.0 + + circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET*2.0) / 8.0 + - circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET*3.0) / 16.0; } - vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and ripple height in w { return @@ -107,6 +130,7 @@ vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and r rain(uv * 0.8 + vec2(1.2,3.0),time); } + // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - float fresnel_dielectric(vec3 Incoming, vec3 Normal, float eta) @@ -197,7 +221,7 @@ void main(void) else rainRipple = vec4(0.0); - vec3 rippleAdd = rainRipple.xyz * rainRipple.w * 10.0; + vec3 rippleAdd = rainRipple.xyz * abs(rainRipple.w) * 10.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); @@ -265,7 +289,7 @@ 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 * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + vec3(rainRipple.w) * 0.2; + gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + vec3(abs(rainRipple.w)) * 0.2; gl_FragData[0].w = 1.0; // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping @@ -278,7 +302,7 @@ void main(void) 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); #else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + vec3(rainRipple.w) * 0.7; + gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + vec3(abs(rainRipple.w)) * 0.7; gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); #endif From e23b0edda4a49296c56b29251fe3f00fd6b0adba Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 22 Oct 2021 23:11:23 -0400 Subject: [PATCH 1520/2859] Add pre-pass to collision trace function that traces over less distance if possible --- apps/openmw/mwphysics/trace.cpp | 74 +++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 049d026e8e..b2c5410a62 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -12,31 +12,49 @@ namespace MWPhysics { -void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) +ActorConvexCallback sweepHelper(const btCollisionObject *actor, const btVector3& from, const btVector3& to, const btCollisionWorld* world, bool actorFilter) { - const btVector3 btstart = Misc::Convert::toBullet(start); - const btVector3 btend = Misc::Convert::toBullet(end); - const btTransform &trans = actor->getWorldTransform(); - btTransform from(trans); - btTransform to(trans); - from.setOrigin(btstart); - to.setOrigin(btend); + btTransform transFrom(trans); + btTransform transTo(trans); + transFrom.setOrigin(from); + transTo.setOrigin(to); + + const btCollisionShape *shape = actor->getCollisionShape(); + assert(shape->isConvex()); - const btVector3 motion = btstart-btend; + const btVector3 motion = from - to; // FIXME: this is backwards; means ActorConvexCallback is doing dot product tests backwards too ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; + if(actorFilter) + newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor; - const btCollisionShape *shape = actor->getCollisionShape(); - assert(shape->isConvex()); - world->convexSweepTest(static_cast(shape), from, to, newTraceCallback); + world->convexSweepTest(static_cast(shape), transFrom, transTo, newTraceCallback); + return newTraceCallback; +} + +void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) +{ + const btVector3 btstart = Misc::Convert::toBullet(start); + btVector3 btend = Misc::Convert::toBullet(end); + + bool do_fallback = false; + if((btend-btstart).length2() > 5.0*5.0) + { + btend = btstart + (btend-btstart).normalized()*5.0; + do_fallback = true; + } + + auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); // Copy the hit data over to our trace results struct: if(newTraceCallback.hasHit()) { mFraction = newTraceCallback.m_closestHitFraction; + if((end-start).length2() > 0.0) + mFraction *= (btend-btstart).length() / (end-start).length(); mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); mEndPos = (end-start)*mFraction + start; mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); @@ -44,6 +62,22 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star } else { + if(do_fallback) + { + btend = Misc::Convert::toBullet(end); + auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); + + if(newTraceCallback.hasHit()) + { + mFraction = newTraceCallback.m_closestHitFraction; + mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); + mEndPos = (end-start)*mFraction + start; + mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); + mHitObject = newTraceCallback.m_hitCollisionObject; + return; + } + } + // fallthrough mEndPos = end; mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; @@ -54,21 +88,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { - const btVector3 btstart = Misc::Convert::toBullet(start); - const btVector3 btend = Misc::Convert::toBullet(end); - - const btTransform &trans = actor->getCollisionObject()->getWorldTransform(); - btTransform from(trans.getBasis(), btstart); - btTransform to(trans.getBasis(), btend); - - const btVector3 motion = btstart-btend; - ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world); - // Inherit the actor's collision group and mask - newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; - newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; - newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor; - - world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback); + auto newTraceCallback = sweepHelper(actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true); if(newTraceCallback.hasHit()) { mFraction = newTraceCallback.m_closestHitFraction; From b5c876299d4f7d7cefa57caba57bec28f8719670 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 23 Oct 2021 09:29:16 +0200 Subject: [PATCH 1521/2859] Round to a power of two --- apps/openmw/mwmechanics/magiceffects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 8e74d73d5d..b01c5446a7 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -11,7 +11,7 @@ namespace // Round value to prevent precision issues void truncate(float& value) { - value = std::roundf(value * 1000.f) / 1000.f; + value = std::roundf(value * 1024.f) / 1024.f; } } From c9c8d02332ff334eddf20eeaf1670dbf0b15c861 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 23 Oct 2021 08:31:46 +0000 Subject: [PATCH 1522/2859] fixes a crash (#3183) This PR fixes a crash caused by the improperly ensured lifetime of RigGeometry::mSourceGeometry. mSourceGeometry was not adequate to ensure mSourceGeometry would outlive mGeometry because we extend mGeometrys lifetime beyond this lifetime by passing mGeometry to the draw traversal instead of this. In addition, We add important comments. We detect and prevent generally unsafe operations in high level code. We add a sprinkling of const to help clarify intentions. --- apps/opencs/view/render/actor.cpp | 2 +- apps/openmw/mwrender/animation.cpp | 4 +-- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 7 ++++- components/resource/scenemanager.cpp | 31 ++++++++++++++-------- components/resource/scenemanager.hpp | 14 +++++++--- components/sceneutil/attach.cpp | 22 ++++++++------- components/sceneutil/attach.hpp | 6 ++++- components/sceneutil/clone.hpp | 1 + components/sceneutil/morphgeometry.cpp | 9 +++++++ components/sceneutil/riggeometry.cpp | 11 ++++++++ components/sceneutil/riggeometry.hpp | 7 +++++ 13 files changed, 86 insertions(+), 32 deletions(-) diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 271ca2365a..94b82c96cd 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -111,7 +111,7 @@ namespace CSVRender if (!mesh.empty() && node != mNodeMap.end()) { auto instance = sceneMgr->getInstance(mesh); - SceneUtil::attach(instance, mSkeleton, boneName, node->second); + SceneUtil::attach(instance, mSkeleton, boneName, node->second, sceneMgr); } } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 10624c8bca..ca76856d48 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1319,10 +1319,10 @@ namespace MWRender cache.insert(std::make_pair(model, created)); - return sceneMgr->createInstance(created); + return sceneMgr->getInstance(created); } else - return sceneMgr->createInstance(found->second); + return sceneMgr->getInstance(found->second); } else { diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 502340d4b9..b64b205961 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -164,7 +164,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); - osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get()); + osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get(), mResourceSystem->getSceneManager()); scene.reset(new PartHolder(attached)); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 0e100326dd..42215c00bc 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -717,7 +717,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); - osg::ref_ptr attached = SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second); + osg::ref_ptr attached = SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 5c22c3fc8d..947f23f781 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -617,6 +617,10 @@ namespace MWRender pat->setAttitude(nodeAttitude); } + // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe. + // In this specific case the operation is safe under the following two assumptions: + // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by TemplateMultiRef) + // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by needvbo() in optimizer.cpp) copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES); copyop.mOptimizeBillboards = (size > 1/4.f); copyop.mNodePath.push_back(trans); @@ -645,7 +649,8 @@ namespace MWRender } if (numinstances > 0) { - // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache + // add a ref to the original template to help verify the safety of shallow cloning operations + // in addition, we hint to the cache that it's still being used and should be kept in cache templateRefs->addRef(cnode); if (pair.second.mNeedCompile) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index c5ef957c3e..4184a77c54 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -693,19 +693,33 @@ namespace Resource } } - osg::ref_ptr SceneManager::createInstance(const std::string& name) + osg::ref_ptr SceneManager::getInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); - return createInstance(scene); + return getInstance(scene); } - osg::ref_ptr SceneManager::createInstance(const osg::Node *base) + osg::ref_ptr SceneManager::cloneNode(const osg::Node* base) { - osg::ref_ptr cloned = static_cast(base->clone(SceneUtil::CopyOp())); - - // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache + SceneUtil::CopyOp copyop; + if (const osg::Drawable* drawable = base->asDrawable()) + { + if (drawable->asGeometry()) + { + Log(Debug::Warning) << "SceneManager::cloneNode: attempting to clone osg::Geometry. For safety reasons this will be expensive. Consider avoiding this call."; + copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES); + } + } + osg::ref_ptr cloned = static_cast(base->clone(copyop)); + // add a ref to the original template to help verify the safety of shallow cloning operations + // in addition, if this node is managed by a cache, we hint to the cache that it's still being used and should be kept in cache cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base)); + return cloned; + } + osg::ref_ptr SceneManager::getInstance(const osg::Node *base) + { + osg::ref_ptr cloned = cloneNode(base); // we can skip any scene graphs without update callbacks since we know that particle emitters will have an update callback set if (cloned->getNumChildrenRequiringUpdateTraversal() > 0) { @@ -716,11 +730,6 @@ namespace Resource return cloned; } - osg::ref_ptr SceneManager::getInstance(const std::string &name) - { - return createInstance(name); - } - osg::ref_ptr SceneManager::getInstance(const std::string &name, osg::Group* parentNode) { osg::ref_ptr cloned = getInstance(name); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index b5d3e453a0..85e012071d 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -132,16 +132,22 @@ namespace Resource /// @note Thread safe. osg::ref_ptr getTemplate(const std::string& name, bool compile=true); - osg::ref_ptr createInstance(const std::string& name); + /// Clone osg::Node safely. + /// @note Thread safe. + static osg::ref_ptr cloneNode(const osg::Node* base); - osg::ref_ptr createInstance(const osg::Node* base); void shareState(osg::ref_ptr node); - /// Get an instance of the given scene template + + /// Clone osg::Node and adjust it according to SceneManager's settings. + /// @note Thread safe. + osg::ref_ptr getInstance(const osg::Node* base); + + /// Instance the given scene template. /// @see getTemplate /// @note Thread safe. osg::ref_ptr getInstance(const std::string& name); - /// Get an instance of the given scene template and immediately attach it to a parent node + /// Instance the given scene template and immediately attach it to a parent node /// @see getTemplate /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. osg::ref_ptr getInstance(const std::string& name, osg::Group* parentNode); diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 6690148c74..9dabb282b0 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -13,9 +13,9 @@ #include #include +#include #include "visitor.hpp" -#include "clone.hpp" namespace SceneUtil { @@ -49,10 +49,10 @@ namespace SceneUtil if (!filterMatches(drawable.getName())) return; - osg::Node* node = &drawable; + const osg::Node* node = &drawable; for (auto it = getNodePath().rbegin()+1; it != getNodePath().rend(); ++it) { - osg::Node* parent = *it; + const osg::Node* parent = *it; if (!filterMatches(parent->getName())) break; node = parent; @@ -60,11 +60,11 @@ namespace SceneUtil mToCopy.emplace(node); } - void doCopy() + void doCopy(Resource::SceneManager* sceneManager) { - for (const osg::ref_ptr& node : mToCopy) + for (const osg::ref_ptr& node : mToCopy) { - mParent->addChild(static_cast(node->clone(SceneUtil::CopyOp()))); + mParent->addChild(sceneManager->getInstance(node)); } mToCopy.clear(); } @@ -78,7 +78,7 @@ namespace SceneUtil || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0); } - using NodeSet = std::set>; + using NodeSet = std::set>; NodeSet mToCopy; osg::ref_ptr mParent; @@ -100,7 +100,7 @@ namespace SceneUtil } } - osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode) + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode, Resource::SceneManager* sceneManager) { if (dynamic_cast(toAttach.get())) { @@ -108,7 +108,9 @@ namespace SceneUtil CopyRigVisitor copyVisitor(handle, filter); const_cast(toAttach.get())->accept(copyVisitor); - copyVisitor.doCopy(); + copyVisitor.doCopy(sceneManager); + // add a ref to the original template to hint to the cache that it is still being used and should be kept in cache. + handle->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(toAttach)); if (handle->getNumChildren() == 1) { @@ -127,7 +129,7 @@ namespace SceneUtil } else { - osg::ref_ptr clonedToAttach = static_cast(toAttach->clone(SceneUtil::CopyOp())); + osg::ref_ptr clonedToAttach = sceneManager->getInstance(toAttach); FindByNameVisitor findBoneOffset("BoneOffset"); clonedToAttach->accept(findBoneOffset); diff --git a/components/sceneutil/attach.hpp b/components/sceneutil/attach.hpp index 806fc53488..f5fc693724 100644 --- a/components/sceneutil/attach.hpp +++ b/components/sceneutil/attach.hpp @@ -10,6 +10,10 @@ namespace osg class Node; class Group; } +namespace Resource +{ + class SceneManager; +} namespace SceneUtil { @@ -19,7 +23,7 @@ namespace SceneUtil /// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering. /// @note The master scene graph is expected to include a skeleton. /// @return A newly created node that is directly attached to the master scene graph - osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode); + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode, Resource::SceneManager *sceneManager); } diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp index 1cf00c9e58..35240cbfba 100644 --- a/components/sceneutil/clone.hpp +++ b/components/sceneutil/clone.hpp @@ -18,6 +18,7 @@ namespace SceneUtil /// @par Defines the cloning behaviour we need: /// * Assigns updated ParticleSystem pointers on cloned emitters and programs. /// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones. + /// @warning Avoid using this class directly. The safety of cloning operations depends on the copy flags and the objects involved. Consider using SceneManager::cloneNode for additional safety. /// @warning Do not use an object of this class for more than one copy operation. class CopyOp : public osg::CopyOp { diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 78be559989..59adbffffe 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -1,6 +1,7 @@ #include "morphgeometry.hpp" #include +#include #include @@ -27,11 +28,19 @@ MorphGeometry::MorphGeometry(const MorphGeometry ©, const osg::CopyOp ©o void MorphGeometry::setSourceGeometry(osg::ref_ptr sourceGeom) { + for (unsigned int i=0; i<2; ++i) + mGeometry[i] = nullptr; + mSourceGeometry = sourceGeom; for (unsigned int i=0; i<2; ++i) { + // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe. + // In this specific case the operation is safe under the following two assumptions: + // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by TemplateRef) + // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by vbo below) mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY); + mGeometry[i]->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(mSourceGeometry)); const osg::Geometry& from = *mSourceGeometry; osg::Geometry& to = *mGeometry[i]; diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index ec00efa535..84b31f4afc 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "skeleton.hpp" @@ -60,12 +61,22 @@ RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) { + for (unsigned int i=0; i<2; ++i) + mGeometry[i] = nullptr; + mSourceGeometry = sourceGeometry; for (unsigned int i=0; i<2; ++i) { const osg::Geometry& from = *sourceGeometry; + + // DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe. + // In this specific case the operation is safe under the following two assumptions: + // - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by mSourceGeometry) + // - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by vbo below) mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY); + mGeometry[i]->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(mSourceGeometry)); + osg::Geometry& to = *mGeometry[i]; to.setSupportsDisplayList(false); to.setUseVertexBufferObjects(true); diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index e01583399e..25ae5a3243 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -9,6 +9,13 @@ namespace SceneUtil class Skeleton; class Bone; + // TODO: This class has a lot of issues. + // - We require too many workarounds to ensure safety. + // - mSourceGeometry should be const, but can not be const because of a use case in shadervisitor.cpp. + // - We create useless mGeometry clones in template RigGeometries. + // - We do not support compileGLObjects. + // - We duplicate some code in MorphGeometry. + /// @brief Mesh skinning implementation. /// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton. /// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important. From e65af0bf0678c925efafe0987608fa56d45893c9 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 23 Oct 2021 10:48:37 +0200 Subject: [PATCH 1523/2859] Silence all opengl deprecation warnings for MacOS We know... --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0451b639e5..1ae3841bdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -707,6 +707,7 @@ endif() if (BUILD_OPENMW AND APPLE) # Without these flags LuaJit crashes on startup on OSX set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") + set_target_properties(components PROPERTIES COMPILE_DEFINITIONS "GL_SILENCE_DEPRECATION=1") endif() # Apple bundling From dfb6bdf77ec3cc40126d37b9728160a22fd1bbe4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 13 Oct 2021 22:44:18 +0200 Subject: [PATCH 1524/2859] Allow integer variable names --- components/compiler/declarationparser.cpp | 10 ++++++++++ components/compiler/declarationparser.hpp | 4 ++++ components/compiler/lineparser.cpp | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp index 1c64aaadee..f29fe820c1 100644 --- a/components/compiler/declarationparser.cpp +++ b/components/compiler/declarationparser.cpp @@ -90,6 +90,16 @@ bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, S return Parser::parseSpecial (code, loc, scanner); } +bool Compiler::DeclarationParser::parseInt(int value, const TokenLoc& loc, Scanner& scanner) +{ + if(mState == State_Name) + { + // Allow integers to be used as variable names + return parseName(loc.mLiteral, loc, scanner); + } + return Parser::parseInt(value, loc, scanner); +} + void Compiler::DeclarationParser::reset() { mState = State_Begin; diff --git a/components/compiler/declarationparser.hpp b/components/compiler/declarationparser.hpp index c04f1dc268..d08509fe50 100644 --- a/components/compiler/declarationparser.hpp +++ b/components/compiler/declarationparser.hpp @@ -35,6 +35,10 @@ namespace Compiler ///< Handle a special character token. /// \return fetch another token? + bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; + ///< Handle an int token. + /// \return fetch another token? + void reset() override; }; diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 77afaee8bd..943c052473 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -67,6 +67,11 @@ namespace Compiler parseExpression (scanner, loc); return true; } + else if (mState == SetState) + { + // Allow ints to be used as variable names + return parseName(loc.mLiteral, loc, scanner); + } return Parser::parseInt (value, loc, scanner); } From 31aa19574b31a9bad3efd686ffd909ad976554e6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 21 Oct 2021 16:54:32 +0200 Subject: [PATCH 1525/2859] Make PositionCell take additional junk arguments --- components/compiler/extensions0.cpp | 2 +- components/compiler/stringparser.cpp | 6 ++++++ components/compiler/stringparser.hpp | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 3dfcadab10..64133bee84 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -553,7 +553,7 @@ namespace Compiler extensions.registerFunction("getpos",'f',"c",opcodeGetPos,opcodeGetPosExplicit); extensions.registerFunction("getstartingpos",'f',"c",opcodeGetStartingPos,opcodeGetStartingPosExplicit); extensions.registerInstruction("position","ffffz",opcodePosition,opcodePositionExplicit); - extensions.registerInstruction("positioncell","ffffcX",opcodePositionCell,opcodePositionCellExplicit); + extensions.registerInstruction("positioncell","ffffczz",opcodePositionCell,opcodePositionCellExplicit); extensions.registerInstruction("placeitemcell","ccffffX",opcodePlaceItemCell); extensions.registerInstruction("placeitem","cffffX",opcodePlaceItem); extensions.registerInstruction("placeatpc","clflX",opcodePlaceAtPc); diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index d9c3c04947..4e0114e0a1 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -86,6 +86,12 @@ namespace Compiler return Parser::parseSpecial (code, loc, scanner); } + bool StringParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) + { + reportWarning("Treating integer argument as a string", loc); + return parseName(loc.mLiteral, loc, scanner); + } + void StringParser::append (std::vector& code) { std::copy (mCode.begin(), mCode.end(), std::back_inserter (code)); diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp index 1976628360..07b61d8fda 100644 --- a/components/compiler/stringparser.hpp +++ b/components/compiler/stringparser.hpp @@ -43,6 +43,10 @@ namespace Compiler ///< Handle a special character token. /// \return fetch another token? + bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; + ///< Handle an int token. + /// \return fetch another token? + void append (std::vector& code); ///< Append code for parsed string. From 41318a585fa19e34da86dff1b8fd5354d5163db1 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 23 Oct 2021 10:40:26 +0000 Subject: [PATCH 1526/2859] fixes enable and disable commands (#3186) This PR fixes a recent regression concerning enable and disable commands with object paging. In addition, we add a necessary comment to avoid such issues in the future. --- components/terrain/viewdata.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index 3ebc99f1df..e517390b44 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -74,6 +74,8 @@ const osg::Vec3f& ViewData::getViewPoint() const return mViewPoint; } +// NOTE: As a performance optimisation, we cache mRenderingNodes from previous frames here. +// If this cache becomes invalid (e.g. through mWorldUpdateRevision), we need to use clear() instead of reset(). void ViewData::reset() { // clear any unused entries @@ -164,9 +166,13 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } else if (!mostSuitableView) { + if (vd->getWorldUpdateRevision() != mWorldUpdateRevision) + { + vd->setWorldUpdateRevision(mWorldUpdateRevision); + vd->clear(); + } vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); - vd->setWorldUpdateRevision(mWorldUpdateRevision); needsUpdate = true; } } From 62b59a3c001d43c5468b1e17b8a40fe8015d40d1 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 23 Oct 2021 12:56:02 +0200 Subject: [PATCH 1527/2859] Update CMakeLists.txt --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ae3841bdd..537c90670d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -708,6 +708,7 @@ if (BUILD_OPENMW AND APPLE) # Without these flags LuaJit crashes on startup on OSX set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") set_target_properties(components PROPERTIES COMPILE_DEFINITIONS "GL_SILENCE_DEPRECATION=1") + set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS "GL_SILENCE_DEPRECATION=1") endif() # Apple bundling From 1e40d27318de5aed3f1b148c441ed9ba44e6f6f1 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 23 Oct 2021 17:53:38 -0700 Subject: [PATCH 1528/2859] introduce sky shaders --- CHANGELOG.md | 4 + apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/sky.cpp | 2312 +++++++-------------------- apps/openmw/mwrender/sky.hpp | 95 +- apps/openmw/mwrender/skyutil.cpp | 1142 +++++++++++++ apps/openmw/mwrender/skyutil.hpp | 343 ++++ apps/openmw/mwworld/weather.cpp | 2043 +++++++++++------------ apps/openmw/mwworld/weather.hpp | 4 + components/shader/shadervisitor.cpp | 4 + files/shaders/CMakeLists.txt | 3 + files/shaders/lighting.glsl | 10 + files/shaders/objects_fragment.glsl | 8 +- files/shaders/objects_vertex.glsl | 8 +- files/shaders/sky_fragment.glsl | 84 + files/shaders/sky_vertex.glsl | 20 + files/shaders/skypasses.glsl | 7 + 16 files changed, 3294 insertions(+), 2795 deletions(-) create mode 100644 apps/openmw/mwrender/skyutil.cpp create mode 100644 apps/openmw/mwrender/skyutil.hpp create mode 100644 files/shaders/sky_fragment.glsl create mode 100644 files/shaders/sky_vertex.glsl create mode 100644 files/shaders/skypasses.glsl diff --git a/CHANGELOG.md b/CHANGELOG.md index b37fed31fd..188238f42f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed + Bug #4752: UpdateCellCommand doesn't undo properly + Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system Bug #5207: Loose summons can be present in scene @@ -40,6 +42,7 @@ Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive Bug #6165: Paralyzed player character can pickup items when the inventory is open + Bug #6168: Weather particles flicker for a frame at start of storms Bug #6172: Some creatures can't open doors Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla @@ -73,6 +76,7 @@ Feature #6017: Separate persistent and temporary cell references when saving Feature #6032: Reverse-z depth buffer Feature #6078: First person should not clear depth buffer + Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering Feature #6249: Alpha testing support for Collada diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7334d893db..425d859f31 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -19,7 +19,7 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - actors objects renderingmanager animation rotatecontroller sky npcanimation vismask + actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index ebf901b5ab..217f10f294 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1,1306 +1,185 @@ #include "sky.hpp" -#include - -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include +#include #include #include -#include -#include -#include -#include - -#include #include +#include -#include +#include -#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include - -#include +#include +#include #include +#include "../mwworld/weather.hpp" + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "vismask.hpp" #include "renderbin.hpp" #include "util.hpp" +#include "skyutil.hpp" namespace { - osg::ref_ptr createAlphaTrackingUnlitMaterial() - { - osg::ref_ptr mat = new osg::Material; - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mat->setColorMode(osg::Material::DIFFUSE); - return mat; - } - - osg::ref_ptr createUnlitMaterial() - { - osg::ref_ptr mat = new osg::Material; - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mat->setColorMode(osg::Material::OFF); - return mat; - } - - osg::ref_ptr createTexturedQuad(int numUvSets=1, float scale=1.f) - { - osg::ref_ptr geom = new osg::Geometry; - - osg::ref_ptr verts = new osg::Vec3Array; - verts->push_back(osg::Vec3f(-0.5*scale, -0.5*scale, 0)); - verts->push_back(osg::Vec3f(-0.5*scale, 0.5*scale, 0)); - verts->push_back(osg::Vec3f(0.5*scale, 0.5*scale, 0)); - verts->push_back(osg::Vec3f(0.5*scale, -0.5*scale, 0)); - - geom->setVertexArray(verts); - - osg::ref_ptr texcoords = new osg::Vec2Array; - texcoords->push_back(osg::Vec2f(0, 0)); - texcoords->push_back(osg::Vec2f(0, 1)); - texcoords->push_back(osg::Vec2f(1, 1)); - texcoords->push_back(osg::Vec2f(1, 0)); - - osg::ref_ptr colors = new osg::Vec4Array; - colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); - geom->setColorArray(colors, osg::Array::BIND_OVERALL); - - for (int i=0; isetTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); - - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); - - return geom; - } - -} - -namespace MWRender -{ - -class AtmosphereUpdater : public SceneUtil::StateSetUpdater -{ -public: - void setEmissionColor(const osg::Vec4f& emissionColor) - { - mEmissionColor = emissionColor; - } - -protected: - void setDefaults(osg::StateSet* stateset) override - { - stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override - { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); - } - -private: - osg::Vec4f mEmissionColor; -}; - -class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater -{ -public: - AtmosphereNightUpdater(Resource::ImageManager* imageManager) - { - // we just need a texture, its contents don't really matter - mTexture = new osg::Texture2D(imageManager->getWarningImage()); - } - - void setFade(const float fade) - { - mColor.a() = fade; - } - -protected: - void setDefaults(osg::StateSet* stateset) override - { - osg::ref_ptr texEnv (new osg::TexEnvCombine); - texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - - stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override - { - osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnv->setConstantColor(mColor); - } - - osg::ref_ptr mTexture; - - osg::Vec4f mColor; -}; - -class CloudUpdater : public SceneUtil::StateSetUpdater -{ -public: - CloudUpdater() - : mAnimationTimer(0.f) - , mOpacity(0.f) - { - } - - void setAnimationTimer(float timer) - { - mAnimationTimer = timer; - } - - void setTexture(osg::ref_ptr texture) - { - mTexture = texture; - } - void setEmissionColor(const osg::Vec4f& emissionColor) - { - mEmissionColor = emissionColor; - } - void setOpacity(float opacity) - { - mOpacity = opacity; - } - -protected: - void setDefaults(osg::StateSet *stateset) override - { - osg::ref_ptr texmat (new osg::TexMat); - stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); - stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already - osg::ref_ptr texEnvCombine (new osg::TexEnvCombine); - texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1)); - texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); - - stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON); - - stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override - { - osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); - texMat->setMatrix(osg::Matrix::translate(osg::Vec3f(0, -mAnimationTimer, 0.f))); - - stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); - - osg::TexEnvCombine* texEnvCombine = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); - } - -private: - float mAnimationTimer; - osg::ref_ptr mTexture; - osg::Vec4f mEmissionColor; - float mOpacity; -}; - -/// Transform that removes the eyepoint of the modelview matrix, -/// i.e. its children are positioned relative to the camera. -class CameraRelativeTransform : public osg::Transform -{ -public: - CameraRelativeTransform() - { - // Culling works in node-local space, not in camera space, so we can't cull this node correctly - // That's not a problem though, children of this node can be culled just fine - // Just make sure you do not place a CameraRelativeTransform deep in the scene graph - setCullingActive(false); - - addCullCallback(new CullCallback); - } - - CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) - : osg::Transform(copy, copyop) - { - } - - META_Node(MWRender, CameraRelativeTransform) - - const osg::Vec3f& getLastViewPoint() const - { - return mViewPoint; - } - - bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const override - { - if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) - { - mViewPoint = static_cast(nv)->getViewPoint(); - } - - if (_referenceFrame==RELATIVE_RF) - { - matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); - return false; - } - else // absolute - { - matrix.makeIdentity(); - return true; - } - } - - osg::BoundingSphere computeBound() const override - { - return osg::BoundingSphere(); - } - - class CullCallback : public SceneUtil::NodeCallback - { - public: - void operator() (osg::Node* node, osgUtil::CullVisitor* cv) - { - // XXX have to remove unwanted culling plane of the water reflection camera - - // Remove all planes that aren't from the standard frustum - unsigned int numPlanes = 4; - if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) - ++numPlanes; - if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) - ++numPlanes; - - unsigned int mask = 0x1; - unsigned int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); - for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) - { - if (i >= numPlanes) - { - // turn off this culling plane - resultMask &= (~mask); - } - - mask <<= 1; - } - - cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); - cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); - - cv->getProjectionCullingStack().back().pushCurrentMask(); - cv->getCurrentCullingSet().pushCurrentMask(); - - traverse(node, cv); - - cv->getProjectionCullingStack().back().popCurrentMask(); - cv->getCurrentCullingSet().popCurrentMask(); - } - }; -private: - // viewPoint for the current frame - mutable osg::Vec3f mViewPoint; -}; - -class ModVertexAlphaVisitor : public osg::NodeVisitor -{ -public: - ModVertexAlphaVisitor(int meshType) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mMeshType(meshType) - { - } - - void apply(osg::Drawable& drw) override - { - osg::Geometry* geom = drw.asGeometry(); - if (!geom) - return; - - osg::ref_ptr colors = new osg::Vec4Array(geom->getVertexArray()->getNumElements()); - for (unsigned int i=0; isize(); ++i) - { - float alpha = 1.f; - if (mMeshType == 0) alpha = (i%2) ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row - else if (mMeshType == 1) - { - if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row - else if (i>= 33 && i <= 48) alpha = 0.25098; // second row - else alpha = 1.f; - } - else if (mMeshType == 2) - { - if (geom->getColorArray()) - { - osg::Vec4Array* origColors = static_cast(geom->getColorArray()); - alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; - } - else - alpha = 1.f; - } - - (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); - } - - geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); - } - -private: - int mMeshType; -}; - -/// @brief Hides the node subgraph if the eye point is below water. -/// @note Must be added as cull callback. -/// @note Meant to be used on a node that is child of a CameraRelativeTransform. -/// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. -class UnderwaterSwitchCallback : public SceneUtil::NodeCallback -{ -public: - UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) - : mCameraRelativeTransform(cameraRelativeTransform) - , mEnabled(true) - , mWaterLevel(0.f) - { - } - - bool isUnderwater() - { - osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); - return mEnabled && viewPoint.z() < mWaterLevel; - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - if (isUnderwater()) - return; - - traverse(node, nv); - } - - void setEnabled(bool enabled) - { - mEnabled = enabled; - } - void setWaterLevel(float waterLevel) - { - mWaterLevel = waterLevel; - } - -private: - osg::ref_ptr mCameraRelativeTransform; - bool mEnabled; - float mWaterLevel; -}; - -/// A base class for the sun and moons. -class CelestialBody -{ -public: - CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0u) - : mVisibleMask(visibleMask) - { - mGeom = createTexturedQuad(numUvSets); - mTransform = new osg::PositionAttitudeTransform; - mTransform->setNodeMask(mVisibleMask); - mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); - mTransform->addChild(mGeom); - - parentNode->addChild(mTransform); - } - - virtual ~CelestialBody() {} - - virtual void adjustTransparency(const float ratio) = 0; - - void setVisible(bool visible) - { - mTransform->setNodeMask(visible ? mVisibleMask : 0); - } - -protected: - unsigned int mVisibleMask; - static const float mDistance; - osg::ref_ptr mTransform; - osg::ref_ptr mGeom; -}; - -const float CelestialBody::mDistance = 1000.0f; - -class Sun : public CelestialBody -{ -public: - Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) - : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) - , mUpdater(new Updater) - { - mTransform->addUpdateCallback(mUpdater); - - osg::ref_ptr sunTex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds"))); - sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - - osg::ref_ptr queryNode (new osg::Group); - // Need to render after the world geometry so we can correctly test for occlusions - osg::StateSet* stateset = queryNode->getOrCreateStateSet(); - stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); - stateset->setNestRenderBins(false); - // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun - osg::ref_ptr alphaFunc (new osg::AlphaFunc); - alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); - // 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, osg::StateAttribute::ON); - - mTransform->addChild(queryNode); - - mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); - mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); - - createSunFlash(imageManager); - createSunGlare(); - } - - ~Sun() - { - mTransform->removeUpdateCallback(mUpdater); - destroySunFlash(); - destroySunGlare(); - } - - void setColor(const osg::Vec4f& color) - { - mUpdater->mColor.r() = color.r(); - mUpdater->mColor.g() = color.g(); - mUpdater->mColor.b() = color.b(); - } - - void adjustTransparency(const float ratio) override - { - mUpdater->mColor.a() = ratio; - if (mSunGlareCallback) - mSunGlareCallback->setGlareView(ratio); - if (mSunFlashCallback) - mSunFlashCallback->setGlareView(ratio); - } - - void setDirection(const osg::Vec3f& direction) - { - osg::Vec3f normalizedDirection = direction / direction.length(); - mTransform->setPosition(normalizedDirection * mDistance); - - osg::Quat quat; - quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); - mTransform->setAttitude(quat); - } - - void setGlareTimeOfDayFade(float val) - { - if (mSunGlareCallback) - mSunGlareCallback->setTimeOfDayFade(val); - } - -private: - class DummyComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback - { - public: - osg::BoundingSphere computeBound(const osg::Node& node) const override { return osg::BoundingSphere(); } - }; - - /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. - osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible) - { - osg::ref_ptr oqn = new osg::OcclusionQueryNode; - oqn->setQueriesEnabled(true); - -#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) - // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced - osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); -#else - osg::ref_ptr queryGeom = oqn->getQueryGeometry(); -#endif - - // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, - // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry - // is only called once. - // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. - queryGeom->setDataVariance(osg::Object::STATIC); - - // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, - // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to - // circumvent this. - queryGeom->setVertexArray(mGeom->getVertexArray()); - queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); - queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); - queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); - - // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. - oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); - // Still need a proper bounding sphere. - oqn->setInitialBound(queryGeom->getBound()); - -#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) - oqn->setQueryGeometry(queryGeom.release()); -#endif - - osg::StateSet* queryStateSet = new osg::StateSet; - if (queryVisible) - { - auto depth = SceneUtil::createDepth(); - // This is a trick to make fragments written by the query always use the maximum depth value, - // without having to retrieve the current far clipping distance. - // We want the sun glare to be "infinitely" far away. - double far = SceneUtil::getReverseZ() ? 0.0 : 1.0; - depth->setZNear(far); - depth->setZFar(far); - depth->setWriteMask(false); - queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); - } - else - { - queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - } - oqn->setQueryStateSet(queryStateSet); - - parent->addChild(oqn); - - return oqn; - } - - void createSunFlash(Resource::ImageManager& imageManager) - { - osg::ref_ptr tex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds"))); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - osg::ref_ptr group (new osg::Group); - - mTransform->addChild(group); - - const float scale = 2.6f; - osg::ref_ptr geom = createTexturedQuad(1, scale); - group->addChild(geom); - - osg::StateSet* stateset = geom->getOrCreateStateSet(); - - stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); - stateset->setNestRenderBins(false); - - mSunFlashNode = group; - - mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); - mSunFlashNode->addCullCallback(mSunFlashCallback); - } - void destroySunFlash() - { - if (mSunFlashNode) - { - mSunFlashNode->removeCullCallback(mSunFlashCallback); - mSunFlashCallback = nullptr; - } - } - - void createSunGlare() - { - osg::ref_ptr camera (new osg::Camera); - camera->setProjectionMatrix(osg::Matrix::identity()); - camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? - camera->setViewMatrix(osg::Matrix::identity()); - camera->setClearMask(0); - camera->setRenderOrder(osg::Camera::NESTED_RENDER); - camera->setAllowEventFocus(false); - - osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); - - camera->addChild(geom); - - osg::StateSet* stateset = geom->getOrCreateStateSet(); - - stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); - stateset->setNestRenderBins(false); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - - // set up additive blending - osg::ref_ptr blendFunc (new osg::BlendFunc); - blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); - blendFunc->setDestination(osg::BlendFunc::ONE); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - - mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); - mSunGlareNode = camera; - - mSunGlareNode->addCullCallback(mSunGlareCallback); - - mTransform->addChild(camera); - } - void destroySunGlare() - { - if (mSunGlareNode) - { - mSunGlareNode->removeCullCallback(mSunGlareCallback); - mSunGlareCallback = nullptr; - } - } - - class Updater : public SceneUtil::StateSetUpdater + class WrapAroundOperator : public osgParticle::Operator { public: - osg::Vec4f mColor; - - Updater() - : mColor(1.f, 1.f, 1.f, 1.f) + WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange) + : osgParticle::Operator() + , mCamera(camera) + , mWrapRange(wrapRange) + , mHalfWrapRange(mWrapRange / 2.0) { + mPreviousCameraPosition = getCameraPosition(); } - void setDefaults(osg::StateSet* stateset) override + osg::Object *cloneType() const override { - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + return nullptr; } - void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + osg::Object *clone(const osg::CopyOp &op) const override { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); + return nullptr; } - }; - class OcclusionCallback - { - public: - OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) - : mOcclusionQueryVisiblePixels(oqnVisible) - , mOcclusionQueryTotalPixels(oqnTotal) + void operate(osgParticle::Particle *P, double dt) override { } - protected: - float getVisibleRatio (osg::Camera* camera) + void operateParticles(osgParticle::ParticleSystem *ps, double dt) override { - int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); - int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); - - float visibleRatio = 0.f; - if (total > 0) - visibleRatio = static_cast(visible) / static_cast(total); - - float dt = MWBase::Environment::get().getFrameDuration(); - - float lastRatio = mLastRatio[osg::observer_ptr(camera)]; - - float change = dt*10; - - if (visibleRatio > lastRatio) - visibleRatio = std::min(visibleRatio, lastRatio + change); - else - visibleRatio = std::max(visibleRatio, lastRatio - change); - - mLastRatio[osg::observer_ptr(camera)] = visibleRatio; - - return visibleRatio; - } + osg::Vec3 position = getCameraPosition(); + osg::Vec3 positionDifference = position - mPreviousCameraPosition; - private: - osg::ref_ptr mOcclusionQueryVisiblePixels; - osg::ref_ptr mOcclusionQueryTotalPixels; - - std::map, float> mLastRatio; - }; + osg::Matrix toWorld, toLocal; - /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. - class SunFlashCallback : public OcclusionCallback, public SceneUtil::NodeCallback - { - public: - SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) - : OcclusionCallback(oqnVisible, oqnTotal) - , mGlareView(1.f) - { - } - - void operator()(osg::Node* node, osgUtil::CullVisitor* cv) - { - float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + std::vector worldMatrices = ps->getWorldMatrices(); - osg::ref_ptr stateset; - if (visibleRatio > 0.f) + if (!worldMatrices.empty()) { - const float fadeThreshold = 0.1; - if (visibleRatio < fadeThreshold) - { - float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; - osg::ref_ptr mat (createUnlitMaterial()); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); - stateset = new osg::StateSet; - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - else if (visibleRatio < 1.f) - { - const float threshold = 0.6; - visibleRatio = visibleRatio * (1.f - threshold) + threshold; - } + toWorld = worldMatrices[0]; + toLocal.invert(toWorld); } - float scale = visibleRatio; - - if (scale == 0.f) - { - // no traverse - return; - } - else if (scale == 1.f) - traverse(node, cv); - else - { - osg::Matrix modelView = *cv->getModelViewMatrix(); - - modelView.preMultScale(osg::Vec3f(scale, scale, scale)); - - if (stateset) - cv->pushStateSet(stateset); - - cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); - - traverse(node, cv); - - cv->popModelViewMatrix(); - - if (stateset) - cv->popStateSet(); - } - } - - void setGlareView(float value) - { - mGlareView = value; - } - - private: - float mGlareView; - }; - - - /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. - /// Must be attached as a cull callback to the node above the glare node. - class SunGlareCallback : public OcclusionCallback, public SceneUtil::NodeCallback - { - public: - SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, - osg::ref_ptr sunTransform) - : OcclusionCallback(oqnVisible, oqnTotal) - , mSunTransform(sunTransform) - , mTimeOfDayFade(1.f) - , mGlareView(1.f) - { - mColor = Fallback::Map::getColour("Weather_Sun_Glare_Fader_Color"); - mSunGlareFaderMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Max"); - mSunGlareFaderAngleMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Angle_Max"); - - // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, - // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, - // so the resulting color looks more orange than red. - mColor *= 2; - for (int i=0; i<3; ++i) - mColor[i] = std::min(1.f, mColor[i]); - } - - void operator ()(osg::Node* node, osgUtil::CullVisitor* cv) - { - float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); - float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); - - const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); - - float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); - float fade = value * mSunGlareFaderMax; - - fade *= mTimeOfDayFade * mGlareView * visibleRatio; - - if (fade == 0.f) - { - // no traverse - return; - } - else + for (int i = 0; i < ps->numParticles(); ++i) { - osg::ref_ptr stateset (new osg::StateSet); + osgParticle::Particle *p = ps->getParticle(i); + p->setPosition(toWorld.preMult(p->getPosition())); + p->setPosition(p->getPosition() - positionDifference); - osg::ref_ptr mat (createUnlitMaterial()); + for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions + { + osg::Vec3 pos = p->getPosition(); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); + if (pos[j] < -mHalfWrapRange[j]) + pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j],mWrapRange[j]); + else if (pos[j] > mHalfWrapRange[j]) + pos[j] = fmod(pos[j] + mHalfWrapRange[j],mWrapRange[j]) - mHalfWrapRange[j]; - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + p->setPosition(pos); + } - cv->pushStateSet(stateset); - traverse(node, cv); - cv->popStateSet(); + p->setPosition(toLocal.preMult(p->getPosition())); } - } - void setTimeOfDayFade(float val) - { - mTimeOfDayFade = val; + mPreviousCameraPosition = position; } - void setGlareView(float glareView) - { - mGlareView = glareView; - } + protected: + osg::Camera *mCamera; + osg::Vec3 mPreviousCameraPosition; + osg::Vec3 mWrapRange; + osg::Vec3 mHalfWrapRange; - private: - float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const + osg::Vec3 getCameraPosition() { - osg::Vec3d eye, center, up; - viewMatrix.getLookAt(eye, center, up); - - osg::Vec3d forward = center - eye; - osg::Vec3d sun = mSunTransform->getPosition(); - - forward.normalize(); - sun.normalize(); - float angleRadians = std::acos(forward * sun); - return angleRadians; + return mCamera->getInverseViewMatrix().getTrans(); } - - osg::ref_ptr mSunTransform; - float mTimeOfDayFade; - float mGlareView; - osg::Vec4f mColor; - float mSunGlareFaderMax; - float mSunGlareFaderAngleMax; }; - osg::ref_ptr mUpdater; - osg::ref_ptr mSunFlashCallback; - osg::ref_ptr mSunFlashNode; - osg::ref_ptr mSunGlareCallback; - osg::ref_ptr mSunGlareNode; - osg::ref_ptr mOcclusionQueryVisiblePixels; - osg::ref_ptr mOcclusionQueryTotalPixels; -}; - -class Moon : public CelestialBody -{ -public: - enum Type - { - Type_Masser = 0, - Type_Secunda - }; - - Moon(osg::Group* parentNode, Resource::ImageManager& imageManager, float scaleFactor, Type type) - : CelestialBody(parentNode, scaleFactor, 2) - , mType(type) - , mPhase(MoonState::Phase::Unspecified) - , mUpdater(new Updater(imageManager)) - { - setPhase(MoonState::Phase::Full); - setVisible(true); - - mGeom->addUpdateCallback(mUpdater); - } - - ~Moon() - { - mGeom->removeUpdateCallback(mUpdater); - } - - void adjustTransparency(const float ratio) override - { - mUpdater->mTransparency *= ratio; - } - - void setState(const MoonState& state) - { - float radsX = ((state.mRotationFromHorizon) * static_cast(osg::PI)) / 180.0f; - float radsZ = ((state.mRotationFromNorth) * static_cast(osg::PI)) / 180.0f; - - osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); - osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); - - osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); - mTransform->setPosition(direction * mDistance); - - // The moon quad is initially oriented facing down, so we need to offset its X-axis - // rotation to rotate it to face the camera when sitting at the horizon. - osg::Quat attX((-static_cast(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); - mTransform->setAttitude(attX * rotZ); - - setPhase(state.mPhase); - mUpdater->mTransparency = state.mMoonAlpha; - mUpdater->mShadowBlend = state.mShadowBlend; - } - - void setAtmosphereColor(const osg::Vec4f& color) - { - mUpdater->mAtmosphereColor = color; - } - - void setColor(const osg::Vec4f& color) - { - mUpdater->mMoonColor = color; - } - - unsigned int getPhaseInt() const - { - if (mPhase == MoonState::Phase::New) return 0; - else if (mPhase == MoonState::Phase::WaxingCrescent) return 1; - else if (mPhase == MoonState::Phase::WaningCrescent) return 1; - else if (mPhase == MoonState::Phase::FirstQuarter) return 2; - else if (mPhase == MoonState::Phase::ThirdQuarter) return 2; - else if (mPhase == MoonState::Phase::WaxingGibbous) return 3; - else if (mPhase == MoonState::Phase::WaningGibbous) return 3; - else if (mPhase == MoonState::Phase::Full) return 4; - return 0; - } - -private: - struct Updater : public SceneUtil::StateSetUpdater + class WeatherAlphaOperator : public osgParticle::Operator { - Resource::ImageManager& mImageManager; - osg::ref_ptr mPhaseTex; - osg::ref_ptr mCircleTex; - float mTransparency; - float mShadowBlend; - osg::Vec4f mAtmosphereColor; - osg::Vec4f mMoonColor; - - Updater(Resource::ImageManager& imageManager) - : mImageManager(imageManager) - , mPhaseTex() - , mCircleTex() - , mTransparency(1.0f) - , mShadowBlend(1.0f) - , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) - , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) - { - } - - void setDefaults(osg::StateSet* stateset) override - { - stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); - osg::ref_ptr texEnv = new osg::TexEnvCombine; - texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); - texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor - stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); - - stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); - osg::ref_ptr texEnv2 = new osg::TexEnvCombine; - texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); - texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); - texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency - stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); - - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor*) override - { - osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); - texEnv->setConstantColor(mMoonColor * mShadowBlend); - - osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); - } + public: + WeatherAlphaOperator(float& alpha, bool rain) + : mAlpha(alpha) + , mIsRain(rain) + { } - void setTextures(const std::string& phaseTex, const std::string& circleTex) + osg::Object *cloneType() const override { - mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); - mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); - mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - reset(); + return nullptr; } - }; - - Type mType; - MoonState::Phase mPhase; - osg::ref_ptr mUpdater; - - void setPhase(const MoonState::Phase& phase) - { - if(mPhase == phase) - return; - - mPhase = phase; - - std::string textureName = "textures/tx_"; - - if (mType == Moon::Type_Secunda) - textureName += "secunda_"; - else - textureName += "masser_"; - - if (phase == MoonState::Phase::New) textureName += "new"; - else if(phase == MoonState::Phase::WaxingCrescent) textureName += "one_wax"; - else if(phase == MoonState::Phase::FirstQuarter) textureName += "half_wax"; - else if(phase == MoonState::Phase::WaxingGibbous) textureName += "three_wax"; - else if(phase == MoonState::Phase::WaningCrescent) textureName += "one_wan"; - else if(phase == MoonState::Phase::ThirdQuarter) textureName += "half_wan"; - else if(phase == MoonState::Phase::WaningGibbous) textureName += "three_wan"; - else if(phase == MoonState::Phase::Full) textureName += "full"; - - textureName += ".dds"; - - if (mType == Moon::Type_Secunda) - mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); - else - mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); - } -}; - -SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) - : mSceneManager(sceneManager) - , mCamera(nullptr) - , mAtmosphereNightRoll(0.f) - , mCreated(false) - , mIsStorm(false) - , mDay(0) - , mMonth(0) - , mCloudAnimationTimer(0.f) - , mRainTimer(0.f) - , mStormDirection(0,1,0) - , mClouds() - , mNextClouds() - , mCloudBlendFactor(0.0f) - , mCloudSpeed(0.0f) - , mStarsOpacity(0.0f) - , mRemainingTransitionTime(0.0f) - , mRainEnabled(false) - , mRainSpeed(0) - , mRainDiameter(0) - , mRainMinHeight(0) - , mRainMaxHeight(0) - , mRainEntranceSpeed(1) - , mRainMaxRaindrops(0) - , mWindSpeed(0.f) - , mBaseWindSpeed(0.f) - , mEnabled(true) - , mSunEnabled(true) - , mPrecipitationAlpha(0.f) -{ - osg::ref_ptr skyroot (new CameraRelativeTransform); - skyroot->setName("Sky Root"); - // Assign empty program to specify we don't want shaders - // The shaders generated by the SceneManager can't handle everything we need - skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); - - skyroot->setNodeMask(Mask_Sky); - parentNode->addChild(skyroot); - - mRootNode = skyroot; - - mEarlyRenderBinRoot = new osg::Group; - // render before the world is rendered - mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); - // Prevent unwanted clipping by water reflection camera's clipping plane - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); - mRootNode->addChild(mEarlyRenderBinRoot); - - mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); -} - -void SkyManager::create() -{ - assert(!mCreated); - - mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); - ModVertexAlphaVisitor modAtmosphere(0); - mAtmosphereDay->accept(modAtmosphere); - - mAtmosphereUpdater = new AtmosphereUpdater; - mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); - - mAtmosphereNightNode = new osg::PositionAttitudeTransform; - mAtmosphereNightNode->setNodeMask(0); - mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); - - osg::ref_ptr atmosphereNight; - if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) - atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); - else - atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); - atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - ModVertexAlphaVisitor modStars(2); - atmosphereNight->accept(modStars); - mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager()); - atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - - mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); - - mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); - mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); - - mCloudNode = new osg::PositionAttitudeTransform; - mEarlyRenderBinRoot->addChild(mCloudNode); - mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); - ModVertexAlphaVisitor modClouds(1); - mCloudMesh->accept(modClouds); - mCloudUpdater = new CloudUpdater; - mCloudUpdater->setOpacity(1.f); - mCloudMesh->addUpdateCallback(mCloudUpdater); - - mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); - mCloudMesh2->accept(modClouds); - mCloudUpdater2 = new CloudUpdater; - mCloudUpdater2->setOpacity(0.f); - mCloudMesh2->addUpdateCallback(mCloudUpdater2); - mCloudMesh2->setNodeMask(0); - - auto depth = SceneUtil::createDepth(); - depth->setWriteMask(false); - mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); - - mMoonScriptColor = Fallback::Map::getColour("Moons_Script_Color"); - - mCreated = true; -} - -class RainCounter : public osgParticle::ConstantRateCounter -{ -public: - int numParticlesToCreate(double dt) const override - { - // limit dt to avoid large particle emissions if there are jumps in the simulation time - // 0.2 seconds is the same cap as used in Engine's frame loop - dt = std::min(dt, 0.2); - return ConstantRateCounter::numParticlesToCreate(dt); - } -}; - -class RainShooter : public osgParticle::Shooter -{ -public: - RainShooter() - : mAngle(0.f) - { - } - - void shoot(osgParticle::Particle* particle) const override - { - particle->setVelocity(mVelocity); - particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); - } - - void setVelocity(const osg::Vec3f& velocity) - { - mVelocity = velocity; - } - - void setAngle(float angle) - { - mAngle = angle; - } - osg::Object* cloneType() const override - { - return new RainShooter; - } - osg::Object* clone(const osg::CopyOp &) const override - { - return new RainShooter(*this); - } + osg::Object *clone(const osg::CopyOp &op) const override + { + return nullptr; + } -private: - osg::Vec3f mVelocity; - float mAngle; -}; + void operate(osgParticle::Particle *particle, double dt) override + { + constexpr float rainThreshold = 0.6f; // Rain_Threshold? + float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; + particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); + } -// Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute. -class AlphaFader : public SceneUtil::StateSetUpdater -{ -public: - /// @param alpha the variable alpha value is recovered from - AlphaFader(float& alpha) - : mAlpha(alpha) - { - } + private: + float &mAlpha; + bool mIsRain; + }; - void setDefaults(osg::StateSet* stateset) override + // Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute. + class AlphaFader : public SceneUtil::StateSetUpdater { - // need to create a deep copy of StateAttributes we will modify - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); - } + public: + /// @param alpha the variable alpha value is recovered from + AlphaFader(const float& alpha) + : mAlpha(alpha) + { } - void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override - { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mAlpha)); - } + void setDefaults(osg::StateSet* stateset) override + { + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, mAlpha)); + } + + protected: + const float &mAlpha; + }; // Helper for adding AlphaFaders to a subgraph class SetupVisitor : public osg::NodeVisitor { public: - SetupVisitor(float &alpha) + SetupVisitor(const float &alpha) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAlpha(alpha) - { - } + { } void apply(osg::Node &node) override { @@ -1320,7 +199,7 @@ public: callback = callback->getNestedCallback(); } - osg::ref_ptr alphaFader (new AlphaFader(mAlpha)); + osg::ref_ptr alphaFader = new AlphaFader(mAlpha); if (composite) composite->addController(alphaFader); @@ -1333,608 +212,659 @@ public: } private: - float &mAlpha; + const float &mAlpha; }; - -protected: - float &mAlpha; -}; - -void SkyManager::setCamera(osg::Camera *camera) -{ - mCamera = camera; } -class WrapAroundOperator : public osgParticle::Operator +namespace MWRender { -public: - WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange): osgParticle::Operator(), - mCamera(camera), mWrapRange(wrapRange), mHalfWrapRange(mWrapRange / 2.0) + SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) + : mSceneManager(sceneManager) + , mCamera(nullptr) + , mAtmosphereNightRoll(0.f) + , mCreated(false) + , mIsStorm(false) + , mDay(0) + , mMonth(0) + , mCloudAnimationTimer(0.f) + , mRainTimer(0.f) + , mStormParticleDirection(MWWorld::Weather::defaultDirection()) + , mStormDirection(MWWorld::Weather::defaultDirection()) + , mClouds() + , mNextClouds() + , mCloudBlendFactor(0.f) + , mCloudSpeed(0.f) + , mStarsOpacity(0.f) + , mRemainingTransitionTime(0.f) + , mRainEnabled(false) + , mRainSpeed(0.f) + , mRainDiameter(0.f) + , mRainMinHeight(0.f) + , mRainMaxHeight(0.f) + , mRainEntranceSpeed(1.f) + , mRainMaxRaindrops(0) + , mWindSpeed(0.f) + , mBaseWindSpeed(0.f) + , mEnabled(true) + , mSunEnabled(true) + , mPrecipitationAlpha(0.f) { - mPreviousCameraPosition = getCameraPosition(); - } + osg::ref_ptr skyroot = new CameraRelativeTransform; + skyroot->setName("Sky Root"); + // Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline + if (!mSceneManager->getForceShaders()) + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); + SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); - osg::Object *cloneType() const override - { - return nullptr; + skyroot->setNodeMask(Mask_Sky); + parentNode->addChild(skyroot); + + mRootNode = skyroot; + + mEarlyRenderBinRoot = new osg::Group; + // render before the world is rendered + mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); + // Prevent unwanted clipping by water reflection camera's clipping plane + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); + mRootNode->addChild(mEarlyRenderBinRoot); + + mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } - osg::Object *clone(const osg::CopyOp &op) const override + void SkyManager::create() { - return nullptr; + assert(!mCreated); + + bool forceShaders = mSceneManager->getForceShaders(); + + mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); + ModVertexAlphaVisitor modAtmosphere(ModVertexAlphaVisitor::Atmosphere); + mAtmosphereDay->accept(modAtmosphere); + + mAtmosphereUpdater = new AtmosphereUpdater; + mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); + + mAtmosphereNightNode = new osg::PositionAttitudeTransform; + mAtmosphereNightNode->setNodeMask(0); + mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); + + osg::ref_ptr atmosphereNight; + if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) + atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); + else + atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); + atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + ModVertexAlphaVisitor modStars(ModVertexAlphaVisitor::Stars); + atmosphereNight->accept(modStars); + mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager(), forceShaders); + atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); + + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); + mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); + mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); + + mCloudNode = new osg::Group; + mEarlyRenderBinRoot->addChild(mCloudNode); + + mCloudMesh = new osg::PositionAttitudeTransform; + osg::ref_ptr cloudMeshChild = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudMesh); + mCloudUpdater = new CloudUpdater(forceShaders); + mCloudUpdater->setOpacity(1.f); + cloudMeshChild->addUpdateCallback(mCloudUpdater); + mCloudMesh->addChild(cloudMeshChild); + + mNextCloudMesh = new osg::PositionAttitudeTransform; + osg::ref_ptr nextCloudMeshChild = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mNextCloudMesh); + mNextCloudUpdater = new CloudUpdater(forceShaders); + mNextCloudUpdater->setOpacity(0.f); + nextCloudMeshChild->addUpdateCallback(mNextCloudUpdater); + mNextCloudMesh->setNodeMask(0); + mNextCloudMesh->addChild(nextCloudMeshChild); + + mCloudNode->addChild(mCloudMesh); + mCloudNode->addChild(mNextCloudMesh); + + ModVertexAlphaVisitor modClouds(ModVertexAlphaVisitor::Clouds); + mCloudMesh->accept(modClouds); + mNextCloudMesh->accept(modClouds); + + if (mSceneManager->getForceShaders()) + { + auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", {}, osg::Shader::VERTEX); + auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", {}, osg::Shader::FRAGMENT); + auto program = mSceneManager->getShaderManager().getProgram(vertex, fragment); + mEarlyRenderBinRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", -1)); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(program, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + auto depth = SceneUtil::createDepth(); + depth->setWriteMask(false); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); + + mMoonScriptColor = Fallback::Map::getColour("Moons_Script_Color"); + + mCreated = true; } - void operate(osgParticle::Particle *P, double dt) override + void SkyManager::setCamera(osg::Camera *camera) { + mCamera = camera; } - void operateParticles(osgParticle::ParticleSystem *ps, double dt) override + void SkyManager::createRain() { - osg::Vec3 position = getCameraPosition(); - osg::Vec3 positionDifference = position - mPreviousCameraPosition; + if (mRainNode) + return; - osg::Matrix toWorld, toLocal; + mRainNode = new osg::Group; - std::vector worldMatrices = ps->getWorldMatrices(); - - if (!worldMatrices.empty()) - { - toWorld = worldMatrices[0]; - toLocal.invert(toWorld); - } + mRainParticleSystem = new NifOsg::ParticleSystem; + osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); - for (int i = 0; i < ps->numParticles(); ++i) - { - osgParticle::Particle *p = ps->getParticle(i); - p->setPosition(toWorld.preMult(p->getPosition())); - p->setPosition(p->getPosition() - positionDifference); + mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); + mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); + mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1)); - for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions - { - osg::Vec3 pos = p->getPosition(); + osg::ref_ptr stateset = mRainParticleSystem->getOrCreateStateSet(); - if (pos[j] < -mHalfWrapRange[j]) - pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j],mWrapRange[j]); - else if (pos[j] > mHalfWrapRange[j]) - pos[j] = fmod(pos[j] + mHalfWrapRange[j],mWrapRange[j]) - mHalfWrapRange[j]; + osg::ref_ptr raindropTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds")); + raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - p->setPosition(pos); - } + stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); + stateset->setNestRenderBins(false); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - p->setPosition(toLocal.preMult(p->getPosition())); - } + osg::ref_ptr mat = new osg::Material; + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - mPreviousCameraPosition = position; - } + osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); + particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); + particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); + particleTemplate.setLifeTime(1); -protected: - osg::Camera *mCamera; - osg::Vec3 mPreviousCameraPosition; - osg::Vec3 mWrapRange; - osg::Vec3 mHalfWrapRange; + osg::ref_ptr emitter = new osgParticle::ModularEmitter; + emitter->setParticleSystem(mRainParticleSystem); - osg::Vec3 getCameraPosition() - { - return mCamera->getInverseViewMatrix().getTrans(); + osg::ref_ptr placer = new osgParticle::BoxPlacer; + placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); + placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); + placer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); + emitter->setPlacer(placer); + mPlacer = placer; + + // FIXME: vanilla engine does not use a particle system to handle rain, it uses a NIF-file with 20 raindrops in it. + // It spawns the (maxRaindrops-getParticleSystem()->numParticles())*dt/rainEntranceSpeed batches every frame (near 1-2). + // Since the rain is a regular geometry, it produces water ripples, also in theory it can be removed if collides with something. + osg::ref_ptr counter = new RainCounter; + counter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); + emitter->setCounter(counter); + mCounter = counter; + + osg::ref_ptr shooter = new RainShooter; + mRainShooter = shooter; + emitter->setShooter(shooter); + + osg::ref_ptr updater = new osgParticle::ParticleSystemUpdater; + updater->addParticleSystem(mRainParticleSystem); + + osg::ref_ptr program = new osgParticle::ModularProgram; + program->addOperator(new WrapAroundOperator(mCamera,rainRange)); + program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true)); + program->setParticleSystem(mRainParticleSystem); + mRainNode->addChild(program); + + mRainNode->addChild(emitter); + mRainNode->addChild(mRainParticleSystem); + mRainNode->addChild(updater); + + // Note: if we ever switch to regular geometry rain, it'll need to use an AlphaFader. + mRainNode->addCullCallback(mUnderwaterSwitch); + mRainNode->setNodeMask(Mask_WeatherParticles); + + mRainParticleSystem->setUserValue("simpleLighting", true); + mSceneManager->recreateShaders(mRainNode); + + mRootNode->addChild(mRainNode); } -}; -class WeatherAlphaOperator : public osgParticle::Operator -{ -public: - WeatherAlphaOperator(float& alpha, bool rain) - : mAlpha(alpha) - , mIsRain(rain) + void SkyManager::destroyRain() { + if (!mRainNode) + return; + + mRootNode->removeChild(mRainNode); + mRainNode = nullptr; + mPlacer = nullptr; + mCounter = nullptr; + mRainParticleSystem = nullptr; + mRainShooter = nullptr; } - osg::Object *cloneType() const override + SkyManager::~SkyManager() { - return nullptr; + if (mRootNode) + { + mRootNode->getParent(0)->removeChild(mRootNode); + mRootNode = nullptr; + } } - osg::Object *clone(const osg::CopyOp &op) const override + int SkyManager::getMasserPhase() const { - return nullptr; + if (!mCreated) return 0; + return mMasser->getPhaseInt(); } - void operate(osgParticle::Particle *particle, double dt) override + int SkyManager::getSecundaPhase() const { - constexpr float rainThreshold = 0.6f; // Rain_Threshold? - const float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; - particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); + if (!mCreated) return 0; + return mSecunda->getPhaseInt(); } -private: - float &mAlpha; - bool mIsRain; -}; - -void SkyManager::createRain() -{ - if (mRainNode) - return; - - mRainNode = new osg::Group; - - mRainParticleSystem = new NifOsg::ParticleSystem; - osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); - - mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); - mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); - mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1)); - - osg::ref_ptr stateset (mRainParticleSystem->getOrCreateStateSet()); - - osg::ref_ptr raindropTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds"))); - raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); - stateset->setNestRenderBins(false); - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - - osg::ref_ptr mat (new osg::Material); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - - osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); - particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); - particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); - particleTemplate.setLifeTime(1); - - osg::ref_ptr emitter (new osgParticle::ModularEmitter); - emitter->setParticleSystem(mRainParticleSystem); - - osg::ref_ptr placer (new osgParticle::BoxPlacer); - placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); - placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); - placer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); - emitter->setPlacer(placer); - mPlacer = placer; - - // FIXME: vanilla engine does not use a particle system to handle rain, it uses a NIF-file with 20 raindrops in it. - // It spawns the (maxRaindrops-getParticleSystem()->numParticles())*dt/rainEntranceSpeed batches every frame (near 1-2). - // Since the rain is a regular geometry, it produces water ripples, also in theory it can be removed if collides with something. - osg::ref_ptr counter (new RainCounter); - counter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); - emitter->setCounter(counter); - mCounter = counter; - - osg::ref_ptr shooter (new RainShooter); - mRainShooter = shooter; - emitter->setShooter(shooter); - - osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); - updater->addParticleSystem(mRainParticleSystem); - - osg::ref_ptr program (new osgParticle::ModularProgram); - program->addOperator(new WrapAroundOperator(mCamera,rainRange)); - program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true)); - program->setParticleSystem(mRainParticleSystem); - mRainNode->addChild(program); - - mRainNode->addChild(emitter); - mRainNode->addChild(mRainParticleSystem); - mRainNode->addChild(updater); - - // Note: if we ever switch to regular geometry rain, it'll need to use an AlphaFader. - mRainNode->addCullCallback(mUnderwaterSwitch); - mRainNode->setNodeMask(Mask_WeatherParticles); - - mRootNode->addChild(mRainNode); -} - -void SkyManager::destroyRain() -{ - if (!mRainNode) - return; - - mRootNode->removeChild(mRainNode); - mRainNode = nullptr; - mPlacer = nullptr; - mCounter = nullptr; - mRainParticleSystem = nullptr; - mRainShooter = nullptr; -} - -SkyManager::~SkyManager() -{ - if (mRootNode) + bool SkyManager::isEnabled() { - mRootNode->getParent(0)->removeChild(mRootNode); - mRootNode = nullptr; + return mEnabled; } -} - -int SkyManager::getMasserPhase() const -{ - if (!mCreated) return 0; - return mMasser->getPhaseInt(); -} - -int SkyManager::getSecundaPhase() const -{ - if (!mCreated) return 0; - return mSecunda->getPhaseInt(); -} - -bool SkyManager::isEnabled() -{ - return mEnabled; -} -bool SkyManager::hasRain() const -{ - return mRainNode != nullptr; -} - -float SkyManager::getPrecipitationAlpha() const -{ - if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) - return mPrecipitationAlpha; - - return 0.f; -} + bool SkyManager::hasRain() const + { + return mRainNode != nullptr; + } -void SkyManager::update(float duration) -{ - if (!mEnabled) - return; + float SkyManager::getPrecipitationAlpha() const + { + if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) + return mPrecipitationAlpha; - switchUnderwaterRain(); + return 0.f; + } - if (mIsStorm) + void SkyManager::update(float duration) { - osg::Quat quat; - quat.makeRotate(osg::Vec3f(0,1,0), mStormDirection); + if (!mEnabled) + return; - mCloudNode->setAttitude(quat); - if (mParticleNode) + switchUnderwaterRain(); + + if (mIsStorm && mParticleNode) { + osg::Quat quat; + quat.makeRotate(MWWorld::Weather::defaultDirection(), mStormParticleDirection); // Morrowind deliberately rotates the blizzard mesh, so so should we. if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models")) - quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection); + quat.makeRotate(osg::Vec3f(-1,0,0), mStormParticleDirection); mParticleNode->setAttitude(quat); } - } - else - mCloudNode->setAttitude(osg::Quat()); - - // UV Scroll the clouds - mCloudAnimationTimer += duration * mCloudSpeed * 0.003; - mCloudUpdater->setAnimationTimer(mCloudAnimationTimer); - mCloudUpdater2->setAnimationTimer(mCloudAnimationTimer); - - // rotate the stars by 360 degrees every 4 days - mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f); - if (mAtmosphereNightNode->getNodeMask() != 0) - mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0,0,1))); -} -void SkyManager::setEnabled(bool enabled) -{ - if (enabled && !mCreated) - create(); + // UV Scroll the clouds + mCloudAnimationTimer += duration * mCloudSpeed * 0.003; + mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer); + mCloudUpdater->setTextureCoord(mCloudAnimationTimer); - mRootNode->setNodeMask(enabled ? Mask_Sky : 0u); + // morrowind rotates each cloud mesh independently + osg::Quat rotation; + rotation.makeRotate(MWWorld::Weather::defaultDirection(), mStormDirection); + mCloudMesh->setAttitude(rotation); - mEnabled = enabled; -} + if (mNextCloudMesh->getNodeMask()) + { + rotation.makeRotate(MWWorld::Weather::defaultDirection(), mNextStormDirection); + mNextCloudMesh->setAttitude(rotation); + } -void SkyManager::setMoonColour (bool red) -{ - if (!mCreated) return; - mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1,1,1,1)); -} + // rotate the stars by 360 degrees every 4 days + mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f); + if (mAtmosphereNightNode->getNodeMask() != 0) + mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0,0,1))); + } -void SkyManager::updateRainParameters() -{ - if (mRainShooter) + void SkyManager::setEnabled(bool enabled) { - float angle = -std::atan(mWindSpeed/50.f); - mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed*std::sin(angle), -mRainSpeed/std::cos(angle))); - mRainShooter->setAngle(angle); + if (enabled && !mCreated) + create(); - osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); + mRootNode->setNodeMask(enabled ? Mask_Sky : 0u); - mPlacer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); - mPlacer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); - mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); + if (!enabled && mParticleNode && mParticleEffect) + mCurrentParticleEffect = {}; - mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); + mEnabled = enabled; } -} - -void SkyManager::switchUnderwaterRain() -{ - if (!mRainParticleSystem) - return; - bool freeze = mUnderwaterSwitch->isUnderwater(); - mRainParticleSystem->setFrozen(freeze); -} + void SkyManager::setMoonColour (bool red) + { + if (!mCreated) return; + mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1,1,1,1)); + } -void SkyManager::setWeather(const WeatherResult& weather) -{ - if (!mCreated) return; - - mRainEntranceSpeed = weather.mRainEntranceSpeed; - mRainMaxRaindrops = weather.mRainMaxRaindrops; - mRainDiameter = weather.mRainDiameter; - mRainMinHeight = weather.mRainMinHeight; - mRainMaxHeight = weather.mRainMaxHeight; - mRainSpeed = weather.mRainSpeed; - mWindSpeed = weather.mWindSpeed; - mBaseWindSpeed = weather.mBaseWindSpeed; - - if (mRainEffect != weather.mRainEffect) + void SkyManager::updateRainParameters() { - mRainEffect = weather.mRainEffect; - if (!mRainEffect.empty()) + if (mRainShooter) { - createRain(); - } - else - { - destroyRain(); + float angle = -std::atan(mWindSpeed/50.f); + mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed*std::sin(angle), -mRainSpeed/std::cos(angle))); + mRainShooter->setAngle(angle); + + osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); + + mPlacer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); + mPlacer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); + mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); + + mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); } } - updateRainParameters(); + void SkyManager::switchUnderwaterRain() + { + if (!mRainParticleSystem) + return; - mIsStorm = weather.mIsStorm; + bool freeze = mUnderwaterSwitch->isUnderwater(); + mRainParticleSystem->setFrozen(freeze); + } - if (mCurrentParticleEffect != weather.mParticleEffect) + void SkyManager::setWeather(const WeatherResult& weather) { - mCurrentParticleEffect = weather.mParticleEffect; + if (!mCreated) return; - // cleanup old particles - if (mParticleEffect) - { - mParticleNode->removeChild(mParticleEffect); - mParticleEffect = nullptr; - } + mRainEntranceSpeed = weather.mRainEntranceSpeed; + mRainMaxRaindrops = weather.mRainMaxRaindrops; + mRainDiameter = weather.mRainDiameter; + mRainMinHeight = weather.mRainMinHeight; + mRainMaxHeight = weather.mRainMaxHeight; + mRainSpeed = weather.mRainSpeed; + mWindSpeed = weather.mWindSpeed; + mBaseWindSpeed = weather.mBaseWindSpeed; - if (mCurrentParticleEffect.empty()) + if (mRainEffect != weather.mRainEffect) { - if (mParticleNode) + mRainEffect = weather.mRainEffect; + if (!mRainEffect.empty()) { - mRootNode->removeChild(mParticleNode); - mParticleNode = nullptr; + createRain(); } - } - else - { - if (!mParticleNode) + else { - mParticleNode = new osg::PositionAttitudeTransform; - mParticleNode->addCullCallback(mUnderwaterSwitch); - mParticleNode->setNodeMask(Mask_WeatherParticles); - mRootNode->addChild(mParticleNode); + destroyRain(); } + } - mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); + updateRainParameters(); - SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(new SceneUtil::FrameTimeSource)); - mParticleEffect->accept(assignVisitor); + mIsStorm = weather.mIsStorm; - AlphaFader::SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); + if (mIsStorm) + mStormDirection = weather.mStormDirection; - mParticleEffect->accept(alphaFaderSetupVisitor); + if (mCurrentParticleEffect != weather.mParticleEffect) + { + mCurrentParticleEffect = weather.mParticleEffect; - SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); - mParticleEffect->accept(findPSVisitor); + // cleanup old particles + if (mParticleEffect) + { + mParticleNode->removeChild(mParticleEffect); + mParticleEffect = nullptr; + } - for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) + if (mCurrentParticleEffect.empty()) { - osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); - - osg::ref_ptr program (new osgParticle::ModularProgram); - if (!mIsStorm) - program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); - program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); - program->setParticleSystem(ps); - mParticleNode->addChild(program); + if (mParticleNode) + { + mRootNode->removeChild(mParticleNode); + mParticleNode = nullptr; + } } - } - } + else + { + if (!mParticleNode) + { + mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->addCullCallback(mUnderwaterSwitch); + mParticleNode->setNodeMask(Mask_WeatherParticles); + mParticleNode->getOrCreateStateSet(); + mRootNode->addChild(mParticleNode); + } - if (mClouds != weather.mCloudTexture) - { - mClouds = weather.mCloudTexture; + mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); - std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); + SceneUtil::AssignControllerSourcesVisitor assignVisitor = std::shared_ptr(new SceneUtil::FrameTimeSource); + mParticleEffect->accept(assignVisitor); - osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); - cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); + mParticleEffect->accept(alphaFaderSetupVisitor); - mCloudUpdater->setTexture(cloudTex); - } + SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); + mParticleEffect->accept(findPSVisitor); - if (mNextClouds != weather.mNextCloudTexture) - { - mNextClouds = weather.mNextCloudTexture; + for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) + { + osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); + + osg::ref_ptr program = new osgParticle::ModularProgram; + if (!mIsStorm) + program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); + program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); + program->setParticleSystem(ps); + mParticleNode->addChild(program); - if (!mNextClouds.empty()) + for (int particleIndex = 0; particleIndex < ps->numParticles(); ++particleIndex) + ps->getParticle(particleIndex)->setAlphaRange(osgParticle::rangef(mPrecipitationAlpha, mPrecipitationAlpha)); + + ps->getOrCreateStateSet(); + ps->setUserValue("simpleLighting", true); + } + + mSceneManager->recreateShaders(mParticleNode); + } + } + + if (mClouds != weather.mCloudTexture) { - std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); + mClouds = weather.mCloudTexture; - osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); + std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); + + osg::ref_ptr cloudTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mCloudUpdater2->setTexture(cloudTex); + mCloudUpdater->setTexture(cloudTex); } - } - if (mCloudBlendFactor != weather.mCloudBlendFactor) - { - mCloudBlendFactor = weather.mCloudBlendFactor; + if (mStormDirection != weather.mStormDirection) + mStormDirection = weather.mStormDirection; - mCloudUpdater->setOpacity((1.f-mCloudBlendFactor)); - mCloudUpdater2->setOpacity(mCloudBlendFactor); - mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? ~0u : 0); - } + if (mNextStormDirection != weather.mNextStormDirection) + mNextStormDirection = weather.mNextStormDirection; - if (mCloudColour != weather.mFogColor) - { - osg::Vec4f clr (weather.mFogColor); - clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); + if (mNextClouds != weather.mNextCloudTexture) + { + mNextClouds = weather.mNextCloudTexture; - mCloudUpdater->setEmissionColor(clr); - mCloudUpdater2->setEmissionColor(clr); + if (!mNextClouds.empty()) + { + std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); - mCloudColour = weather.mFogColor; - } + osg::ref_ptr cloudTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)); + cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - if (mSkyColour != weather.mSkyColor) - { - mSkyColour = weather.mSkyColor; + mNextCloudUpdater->setTexture(cloudTex); + mNextStormDirection = weather.mStormDirection; + } + } - mAtmosphereUpdater->setEmissionColor(mSkyColour); - mMasser->setAtmosphereColor(mSkyColour); - mSecunda->setAtmosphereColor(mSkyColour); - } + if (mCloudBlendFactor != weather.mCloudBlendFactor) + { + mCloudBlendFactor = std::clamp(weather.mCloudBlendFactor, 0.f, 1.f); - if (mFogColour != weather.mFogColor) - { - mFogColour = weather.mFogColor; - } + mCloudUpdater->setOpacity(1.f - mCloudBlendFactor); + mNextCloudUpdater->setOpacity(mCloudBlendFactor); + mNextCloudMesh->setNodeMask(mCloudBlendFactor > 0.f ? ~0u : 0); + } + + if (mCloudColour != weather.mFogColor) + { + osg::Vec4f clr (weather.mFogColor); + clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); - mCloudSpeed = weather.mCloudSpeed; + mCloudUpdater->setEmissionColor(clr); + mNextCloudUpdater->setEmissionColor(clr); - mMasser->adjustTransparency(weather.mGlareView); - mSecunda->adjustTransparency(weather.mGlareView); + mCloudColour = weather.mFogColor; + } + + if (mSkyColour != weather.mSkyColor) + { + mSkyColour = weather.mSkyColor; + + mAtmosphereUpdater->setEmissionColor(mSkyColour); + mMasser->setAtmosphereColor(mSkyColour); + mSecunda->setAtmosphereColor(mSkyColour); + } + + if (mFogColour != weather.mFogColor) + { + mFogColour = weather.mFogColor; + } - mSun->setColor(weather.mSunDiscColor); - mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); + mCloudSpeed = weather.mCloudSpeed; - float nextStarsOpacity = weather.mNightFade * weather.mGlareView; + mMasser->adjustTransparency(weather.mGlareView); + mSecunda->adjustTransparency(weather.mGlareView); + + mSun->setColor(weather.mSunDiscColor); + mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); + + float nextStarsOpacity = weather.mNightFade * weather.mGlareView; + + if (weather.mNight && mStarsOpacity != nextStarsOpacity) + { + mStarsOpacity = nextStarsOpacity; + + mAtmosphereNightUpdater->setFade(mStarsOpacity); + } + + mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0u : 0); + mPrecipitationAlpha = weather.mPrecipitationAlpha; + } - if (weather.mNight && mStarsOpacity != nextStarsOpacity) + float SkyManager::getBaseWindSpeed() const { - mStarsOpacity = nextStarsOpacity; + if (!mCreated) return 0.f; - mAtmosphereNightUpdater->setFade(mStarsOpacity); + return mBaseWindSpeed; } - mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0u : 0); + void SkyManager::sunEnable() + { + if (!mCreated) return; - mPrecipitationAlpha = weather.mPrecipitationAlpha; -} + mSun->setVisible(true); + } -float SkyManager::getBaseWindSpeed() const -{ - if (!mCreated) return 0.f; + void SkyManager::sunDisable() + { + if (!mCreated) return; - return mBaseWindSpeed; -} + mSun->setVisible(false); + } -void SkyManager::sunEnable() -{ - if (!mCreated) return; + void SkyManager::setStormParticleDirection(const osg::Vec3f &direction) + { + mStormParticleDirection = direction; + } - mSun->setVisible(true); -} + void SkyManager::setSunDirection(const osg::Vec3f& direction) + { + if (!mCreated) return; -void SkyManager::sunDisable() -{ - if (!mCreated) return; + mSun->setDirection(direction); + } - mSun->setVisible(false); -} + void SkyManager::setMasserState(const MoonState& state) + { + if(!mCreated) return; -void SkyManager::setStormDirection(const osg::Vec3f &direction) -{ - mStormDirection = direction; -} + mMasser->setState(state); + } -void SkyManager::setSunDirection(const osg::Vec3f& direction) -{ - if (!mCreated) return; + void SkyManager::setSecundaState(const MoonState& state) + { + if(!mCreated) return; - mSun->setDirection(direction); -} + mSecunda->setState(state); + } -void SkyManager::setMasserState(const MoonState& state) -{ - if(!mCreated) return; + void SkyManager::setDate(int day, int month) + { + mDay = day; + mMonth = month; + } - mMasser->setState(state); -} + void SkyManager::setGlareTimeOfDayFade(float val) + { + mSun->setGlareTimeOfDayFade(val); + } -void SkyManager::setSecundaState(const MoonState& state) -{ - if(!mCreated) return; + void SkyManager::setWaterHeight(float height) + { + mUnderwaterSwitch->setWaterLevel(height); + } - mSecunda->setState(state); -} + void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) + { + models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); + if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) + models.emplace_back(Settings::Manager::getString("skynight02", "Models")); + models.emplace_back(Settings::Manager::getString("skynight01", "Models")); + models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); -void SkyManager::setDate(int day, int month) -{ - mDay = day; - mMonth = month; -} + models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); + models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); + models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); + models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); -void SkyManager::setGlareTimeOfDayFade(float val) -{ - mSun->setGlareTimeOfDayFade(val); -} + textures.emplace_back("textures/tx_mooncircle_full_s.dds"); + textures.emplace_back("textures/tx_mooncircle_full_m.dds"); -void SkyManager::setWaterHeight(float height) -{ - mUnderwaterSwitch->setWaterLevel(height); -} + textures.emplace_back("textures/tx_masser_new.dds"); + textures.emplace_back("textures/tx_masser_one_wax.dds"); + textures.emplace_back("textures/tx_masser_half_wax.dds"); + textures.emplace_back("textures/tx_masser_three_wax.dds"); + textures.emplace_back("textures/tx_masser_one_wan.dds"); + textures.emplace_back("textures/tx_masser_half_wan.dds"); + textures.emplace_back("textures/tx_masser_three_wan.dds"); + textures.emplace_back("textures/tx_masser_full.dds"); -void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) -{ - models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); - if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) - models.emplace_back(Settings::Manager::getString("skynight02", "Models")); - models.emplace_back(Settings::Manager::getString("skynight01", "Models")); - models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); - - models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); - models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); - models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); - models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); - - textures.emplace_back("textures/tx_mooncircle_full_s.dds"); - textures.emplace_back("textures/tx_mooncircle_full_m.dds"); - - textures.emplace_back("textures/tx_masser_new.dds"); - textures.emplace_back("textures/tx_masser_one_wax.dds"); - textures.emplace_back("textures/tx_masser_half_wax.dds"); - textures.emplace_back("textures/tx_masser_three_wax.dds"); - textures.emplace_back("textures/tx_masser_one_wan.dds"); - textures.emplace_back("textures/tx_masser_half_wan.dds"); - textures.emplace_back("textures/tx_masser_three_wan.dds"); - textures.emplace_back("textures/tx_masser_full.dds"); - - textures.emplace_back("textures/tx_secunda_new.dds"); - textures.emplace_back("textures/tx_secunda_one_wax.dds"); - textures.emplace_back("textures/tx_secunda_half_wax.dds"); - textures.emplace_back("textures/tx_secunda_three_wax.dds"); - textures.emplace_back("textures/tx_secunda_one_wan.dds"); - textures.emplace_back("textures/tx_secunda_half_wan.dds"); - textures.emplace_back("textures/tx_secunda_three_wan.dds"); - textures.emplace_back("textures/tx_secunda_full.dds"); - - textures.emplace_back("textures/tx_sun_05.dds"); - textures.emplace_back("textures/tx_sun_flash_grey_05.dds"); - - textures.emplace_back("textures/tx_raindrop_01.dds"); -} + textures.emplace_back("textures/tx_secunda_new.dds"); + textures.emplace_back("textures/tx_secunda_one_wax.dds"); + textures.emplace_back("textures/tx_secunda_half_wax.dds"); + textures.emplace_back("textures/tx_secunda_three_wax.dds"); + textures.emplace_back("textures/tx_secunda_one_wan.dds"); + textures.emplace_back("textures/tx_secunda_half_wan.dds"); + textures.emplace_back("textures/tx_secunda_three_wan.dds"); + textures.emplace_back("textures/tx_secunda_full.dds"); -void SkyManager::setWaterEnabled(bool enabled) -{ - mUnderwaterSwitch->setEnabled(enabled); -} + textures.emplace_back("textures/tx_sun_05.dds"); + textures.emplace_back("textures/tx_sun_flash_grey_05.dds"); + textures.emplace_back("textures/tx_raindrop_01.dds"); + } + + void SkyManager::setWaterEnabled(bool enabled) + { + mUnderwaterSwitch->setEnabled(enabled); + } } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index f8c501dda6..227dee213b 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -8,10 +8,7 @@ #include #include -namespace osg -{ - class Camera; -} +#include "skyutil.hpp" namespace osg { @@ -19,6 +16,7 @@ namespace osg class Node; class Material; class PositionAttitudeTransform; + class Camera; } namespace osgParticle @@ -45,80 +43,6 @@ namespace MWRender class AlphaFader; class UnderwaterSwitchCallback; - struct WeatherResult - { - std::string mCloudTexture; - std::string mNextCloudTexture; - float mCloudBlendFactor; - - osg::Vec4f mFogColor; - - osg::Vec4f mAmbientColor; - - osg::Vec4f mSkyColor; - - // sun light color - osg::Vec4f mSunColor; - - // alpha is the sun transparency - osg::Vec4f mSunDiscColor; - - float mFogDepth; - - float mDLFogFactor; - float mDLFogOffset; - - float mWindSpeed; - float mBaseWindSpeed; - float mCurrentWindSpeed; - float mNextWindSpeed; - - float mCloudSpeed; - - float mGlareView; - - bool mNight; // use night skybox - float mNightFade; // fading factor for night skybox - - bool mIsStorm; - - std::string mAmbientLoopSoundID; - float mAmbientSoundVolume; - - std::string mParticleEffect; - std::string mRainEffect; - float mPrecipitationAlpha; - - float mRainDiameter; - float mRainMinHeight; - float mRainMaxHeight; - float mRainSpeed; - float mRainEntranceSpeed; - int mRainMaxRaindrops; - }; - - struct MoonState - { - enum class Phase - { - Full = 0, - WaningGibbous, - ThirdQuarter, - WaningCrescent, - New, - WaxingCrescent, - FirstQuarter, - WaxingGibbous, - Unspecified - }; - - float mRotationFromHorizon; - float mRotationFromNorth; - Phase mPhase; - float mShadowBlend; - float mMoonAlpha; - }; - ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to be rendered /// relative to the camera (e.g. weather particle effects) class SkyManager @@ -162,7 +86,7 @@ namespace MWRender void setRainSpeed(float speed); - void setStormDirection(const osg::Vec3f& direction); + void setStormParticleDirection(const osg::Vec3f& direction); void setSunDirection(const osg::Vec3f& direction); @@ -203,12 +127,12 @@ namespace MWRender osg::ref_ptr mParticleEffect; osg::ref_ptr mUnderwaterSwitch; - osg::ref_ptr mCloudNode; + osg::ref_ptr mCloudNode; osg::ref_ptr mCloudUpdater; - osg::ref_ptr mCloudUpdater2; - osg::ref_ptr mCloudMesh; - osg::ref_ptr mCloudMesh2; + osg::ref_ptr mNextCloudUpdater; + osg::ref_ptr mCloudMesh; + osg::ref_ptr mNextCloudMesh; osg::ref_ptr mAtmosphereDay; @@ -239,7 +163,10 @@ namespace MWRender float mRainTimer; + // particle system rotation is independent of cloud rotation internally + osg::Vec3f mStormParticleDirection; osg::Vec3f mStormDirection; + osg::Vec3f mNextStormDirection; // remember some settings so we don't have to apply them again if they didn't change std::string mClouds; @@ -275,4 +202,4 @@ namespace MWRender }; } -#endif // GAME_RENDER_SKY_H +#endif diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp new file mode 100644 index 0000000000..ae689547fd --- /dev/null +++ b/apps/openmw/mwrender/skyutil.cpp @@ -0,0 +1,1142 @@ +#include "skyutil.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/weather.hpp" + +#include "vismask.hpp" +#include "renderbin.hpp" + +namespace +{ + enum class Pass + { + Atmosphere, + Atmosphere_Night, + Clouds, + Moon, + Sun, + Sunflash_Query, + Sunglare, + }; + + osg::ref_ptr createTexturedQuad(int numUvSets = 1, float scale = 1.f) + { + osg::ref_ptr geom = new osg::Geometry; + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-0.5 * scale, -0.5 * scale, 0)); + verts->push_back(osg::Vec3f(-0.5 * scale, 0.5 * scale, 0)); + verts->push_back(osg::Vec3f(0.5 * scale, 0.5 * scale, 0)); + verts->push_back(osg::Vec3f(0.5 * scale, -0.5 * scale, 0)); + + geom->setVertexArray(verts); + + osg::ref_ptr texcoords = new osg::Vec2Array; + texcoords->push_back(osg::Vec2f(0, 1)); + texcoords->push_back(osg::Vec2f(0, 0)); + texcoords->push_back(osg::Vec2f(1, 0)); + texcoords->push_back(osg::Vec2f(1, 1)); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); + geom->setColorArray(colors, osg::Array::BIND_OVERALL); + + for (int i=0; isetTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); + + geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); + + return geom; + } + + struct DummyComputeBoundCallback : osg::Node::ComputeBoundingSphereCallback + { + osg::BoundingSphere computeBound(const osg::Node& node) const override + { + return osg::BoundingSphere(); + } + }; +} + +namespace MWRender +{ + osg::ref_ptr createAlphaTrackingUnlitMaterial() + { + osg::ref_ptr mat = new osg::Material; + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mat->setColorMode(osg::Material::DIFFUSE); + return mat; + } + + osg::ref_ptr createUnlitMaterial() + { + osg::ref_ptr mat = new osg::Material; + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mat->setColorMode(osg::Material::OFF); + return mat; + } + + class SunUpdater : public SceneUtil::StateSetUpdater + { + public: + osg::Vec4f mColor; + + SunUpdater() + : mColor(1.f, 1.f, 1.f, 1.f) + { } + + void setDefaults(osg::StateSet* stateset) override + { + stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); + } + }; + + OcclusionCallback::OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : mOcclusionQueryVisiblePixels(oqnVisible) + , mOcclusionQueryTotalPixels(oqnTotal) + { } + + float OcclusionCallback::getVisibleRatio (osg::Camera* camera) + { + int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); + int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); + + float visibleRatio = 0.f; + if (total > 0) + visibleRatio = static_cast(visible) / static_cast(total); + + float dt = MWBase::Environment::get().getFrameDuration(); + + float lastRatio = mLastRatio[osg::observer_ptr(camera)]; + + float change = dt*10; + + if (visibleRatio > lastRatio) + visibleRatio = std::min(visibleRatio, lastRatio + change); + else + visibleRatio = std::max(visibleRatio, lastRatio - change); + + mLastRatio[osg::observer_ptr(camera)] = visibleRatio; + + return visibleRatio; + } + + /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. + class SunFlashCallback : public OcclusionCallback, public SceneUtil::NodeCallback + { + public: + SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : OcclusionCallback(oqnVisible, oqnTotal) + , mGlareView(1.f) + { } + + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + osg::ref_ptr stateset; + + if (visibleRatio > 0.f) + { + const float fadeThreshold = 0.1; + if (visibleRatio < fadeThreshold) + { + float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; + osg::ref_ptr mat (MWRender::createUnlitMaterial()); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); + stateset = new osg::StateSet; + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + else if (visibleRatio < 1.f) + { + const float threshold = 0.6; + visibleRatio = visibleRatio * (1.f - threshold) + threshold; + } + } + + float scale = visibleRatio; + + if (scale == 0.f) + { + // no traverse + return; + } + else if (scale == 1.f) + traverse(node, cv); + else + { + osg::Matrix modelView = *cv->getModelViewMatrix(); + + modelView.preMultScale(osg::Vec3f(scale, scale, scale)); + + if (stateset) + cv->pushStateSet(stateset); + + cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); + + traverse(node, cv); + + cv->popModelViewMatrix(); + + if (stateset) + cv->popStateSet(); + } + } + + void setGlareView(float value) + { + mGlareView = value; + } + + private: + float mGlareView; + }; + + /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. + /// Must be attached as a cull callback to the node above the glare node. + class SunGlareCallback : public OcclusionCallback, public SceneUtil::NodeCallback + { + public: + SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, + osg::ref_ptr sunTransform) + : OcclusionCallback(oqnVisible, oqnTotal) + , mSunTransform(sunTransform) + , mTimeOfDayFade(1.f) + , mGlareView(1.f) + { + mColor = Fallback::Map::getColour("Weather_Sun_Glare_Fader_Color"); + mSunGlareFaderMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Max"); + mSunGlareFaderAngleMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Angle_Max"); + + // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, + // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, + // so the resulting color looks more orange than red. + mColor *= 2; + for (int i=0; i<3; ++i) + mColor[i] = std::min(1.f, mColor[i]); + } + + void operator ()(osg::Node* node, osgUtil::CullVisitor* cv) + { + float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); + + float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); + float fade = value * mSunGlareFaderMax; + + fade *= mTimeOfDayFade * mGlareView * visibleRatio; + + if (fade == 0.f) + { + // no traverse + return; + + osg::Vec4 v4; + osg::Vec4 v3; + v4 = v3; + } + else + { + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr mat = MWRender::createUnlitMaterial(); + + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); + + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + + cv->pushStateSet(stateset); + traverse(node, cv); + cv->popStateSet(); + } + } + + void setTimeOfDayFade(float val) + { + mTimeOfDayFade = val; + } + + void setGlareView(float glareView) + { + mGlareView = glareView; + } + + private: + float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const + { + osg::Vec3d eye, center, up; + viewMatrix.getLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Vec3d sun = mSunTransform->getPosition(); + + forward.normalize(); + sun.normalize(); + float angleRadians = std::acos(forward * sun); + return angleRadians; + } + + osg::ref_ptr mSunTransform; + float mTimeOfDayFade; + float mGlareView; + osg::Vec4f mColor; + float mSunGlareFaderMax; + float mSunGlareFaderAngleMax; + }; + + struct MoonUpdater : SceneUtil::StateSetUpdater + { + Resource::ImageManager& mImageManager; + osg::ref_ptr mPhaseTex; + osg::ref_ptr mCircleTex; + float mTransparency; + float mShadowBlend; + osg::Vec4f mAtmosphereColor; + osg::Vec4f mMoonColor; + bool mForceShaders; + + MoonUpdater(Resource::ImageManager& imageManager, bool forceShaders) + : mImageManager(imageManager) + , mPhaseTex() + , mCircleTex() + , mTransparency(1.0f) + , mShadowBlend(1.0f) + , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) + , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) + , mForceShaders(forceShaders) + { } + + void setDefaults(osg::StateSet* stateset) override + { + if (mForceShaders) + { + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("moonBlend", osg::Vec4f{})); + stateset->addUniform(new osg::Uniform("atmosphereFade", osg::Vec4f{})); + stateset->addUniform(new osg::Uniform("diffuseMap", 0)); + stateset->addUniform(new osg::Uniform("maskMap", 1)); + stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + else + { + stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor + stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); + + stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + osg::ref_ptr texEnv2 = new osg::TexEnvCombine; + texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); + texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); + texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency + stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); + stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + { + if (mForceShaders) + { + stateset->setTextureAttribute(0, mPhaseTex, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureAttribute(1, mCircleTex, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + if (auto* uMoonBlend = stateset->getUniform("moonBlend")) + uMoonBlend->set(mMoonColor * mShadowBlend); + if (auto* uAtmosphereFade = stateset->getUniform("atmosphereFade")) + uAtmosphereFade->set(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + } + else + { + osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(mMoonColor * mShadowBlend); + + osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + } + } + + void setTextures(const std::string& phaseTex, const std::string& circleTex) + { + mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); + mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); + mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + reset(); + } + }; + + class CameraRelativeTransformCullCallback : public SceneUtil::NodeCallback + { + public: + void operator() (osg::Node* node, osgUtil::CullVisitor* cv) + { + // XXX have to remove unwanted culling plane of the water reflection camera + + // Remove all planes that aren't from the standard frustum + unsigned int numPlanes = 4; + if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) + ++numPlanes; + if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) + ++numPlanes; + + unsigned int mask = 0x1; + unsigned int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); + for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) + { + if (i >= numPlanes) + { + // turn off this culling plane + resultMask &= (~mask); + } + + mask <<= 1; + } + + cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); + cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); + + cv->getProjectionCullingStack().back().pushCurrentMask(); + cv->getCurrentCullingSet().pushCurrentMask(); + + traverse(node, cv); + + cv->getProjectionCullingStack().back().popCurrentMask(); + cv->getCurrentCullingSet().popCurrentMask(); + } + }; + + void AtmosphereUpdater::setEmissionColor(const osg::Vec4f& emissionColor) + { + mEmissionColor = emissionColor; + } + + void AtmosphereUpdater::setDefaults(osg::StateSet* stateset) + { + stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + void AtmosphereUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); + } + + AtmosphereNightUpdater::AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders) + : mColor(osg::Vec4f(0,0,0,0)) + , mTexture(new osg::Texture2D(imageManager->getWarningImage())) + , mForceShaders(forceShaders) + { } + + void AtmosphereNightUpdater::setFade(float fade) + { + mColor.a() = fade; + } + + void AtmosphereNightUpdater::setDefaults(osg::StateSet* stateset) + { + if (mForceShaders) + { + stateset->addUniform(new osg::Uniform("opacity", 0.f), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + else + { + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + + stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + } + + void AtmosphereNightUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + if (mForceShaders) + { + stateset->getUniform("opacity")->set(mColor.a()); + } + else + { + osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(mColor); + } + } + + CloudUpdater::CloudUpdater(bool forceShaders) + : mOpacity(0.f) + , mForceShaders(forceShaders) + { } + + void CloudUpdater::setTexture(osg::ref_ptr texture) + { + mTexture = texture; + } + + void CloudUpdater::setEmissionColor(const osg::Vec4f& emissionColor) + { + mEmissionColor = emissionColor; + } + + void CloudUpdater::setOpacity(float opacity) + { + mOpacity = opacity; + } + + void CloudUpdater::setTextureCoord(float timer) + { + mTexMat = osg::Matrixf::translate(osg::Vec3f(0.f, -timer, 0.f)); + } + + void CloudUpdater::setDefaults(osg::StateSet *stateset) + { + stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::ref_ptr texmat = new osg::TexMat; + stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); + + if (mForceShaders) + { + stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + stateset->addUniform(new osg::Uniform("opacity", 1.f), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + else + { + stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); + // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already + osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; + texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1)); + texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); + + stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON); + + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + } + + void CloudUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); + + osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); + texMat->setMatrix(mTexMat); + + if (mForceShaders) + { + stateset->getUniform("opacity")->set(mOpacity); + } + else + { + stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); + } + } + + CameraRelativeTransform::CameraRelativeTransform() + { + // Culling works in node-local space, not in camera space, so we can't cull this node correctly + // That's not a problem though, children of this node can be culled just fine + // Just make sure you do not place a CameraRelativeTransform deep in the scene graph + setCullingActive(false); + + addCullCallback(new CameraRelativeTransformCullCallback); + } + + CameraRelativeTransform::CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) + : osg::Transform(copy, copyop) + { } + + const osg::Vec3f& CameraRelativeTransform::getLastViewPoint() const + { + return mViewPoint; + } + + bool CameraRelativeTransform::computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const + { + if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + { + mViewPoint = static_cast(nv)->getViewPoint(); + } + + if (_referenceFrame==RELATIVE_RF) + { + matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); + return false; + } + else // absolute + { + matrix.makeIdentity(); + return true; + } + } + + osg::BoundingSphere CameraRelativeTransform::computeBound() const + { + return osg::BoundingSphere(); + } + + UnderwaterSwitchCallback::UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) + : mCameraRelativeTransform(cameraRelativeTransform) + , mEnabled(true) + , mWaterLevel(0.f) + { } + + bool UnderwaterSwitchCallback::isUnderwater() + { + osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); + return mEnabled && viewPoint.z() < mWaterLevel; + } + + void UnderwaterSwitchCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (isUnderwater()) + return; + + traverse(node, nv); + } + + void UnderwaterSwitchCallback::setEnabled(bool enabled) + { + mEnabled = enabled; + } + void UnderwaterSwitchCallback::setWaterLevel(float waterLevel) + { + mWaterLevel = waterLevel; + } + + const float CelestialBody::mDistance = 1000.0f; + + CelestialBody::CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask) + : mVisibleMask(visibleMask) + { + mGeom = createTexturedQuad(numUvSets); + mGeom->getOrCreateStateSet(); + mTransform = new osg::PositionAttitudeTransform; + mTransform->setNodeMask(mVisibleMask); + mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); + mTransform->addChild(mGeom); + + parentNode->addChild(mTransform); + } + + void CelestialBody::setVisible(bool visible) + { + mTransform->setNodeMask(visible ? mVisibleMask : 0); + } + + Sun::Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) + : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) + , mUpdater(new SunUpdater) + { + mTransform->addUpdateCallback(mUpdater); + + osg::ref_ptr sunTex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds")); + sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + sunTex->setName("diffuseMap"); + + mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::ref_ptr queryNode = new osg::Group; + // Need to render after the world geometry so we can correctly test for occlusions + osg::StateSet* stateset = queryNode->getOrCreateStateSet(); + stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); + stateset->setNestRenderBins(false); + // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun + osg::ref_ptr alphaFunc = new osg::AlphaFunc; + alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + // 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, osg::StateAttribute::ON); + + mTransform->addChild(queryNode); + + mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); + mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); + + createSunFlash(imageManager); + createSunGlare(); + } + + Sun::~Sun() + { + mTransform->removeUpdateCallback(mUpdater); + destroySunFlash(); + destroySunGlare(); + } + + void Sun::setColor(const osg::Vec4f& color) + { + mUpdater->mColor.r() = color.r(); + mUpdater->mColor.g() = color.g(); + mUpdater->mColor.b() = color.b(); + } + + void Sun::adjustTransparency(const float ratio) + { + mUpdater->mColor.a() = ratio; + if (mSunGlareCallback) + mSunGlareCallback->setGlareView(ratio); + if (mSunFlashCallback) + mSunFlashCallback->setGlareView(ratio); + } + + void Sun::setDirection(const osg::Vec3f& direction) + { + osg::Vec3f normalizedDirection = direction / direction.length(); + mTransform->setPosition(normalizedDirection * mDistance); + + osg::Quat quat; + quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); + mTransform->setAttitude(quat); + } + + void Sun::setGlareTimeOfDayFade(float val) + { + if (mSunGlareCallback) + mSunGlareCallback->setTimeOfDayFade(val); + } + + osg::ref_ptr Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible) + { + osg::ref_ptr oqn = new osg::OcclusionQueryNode; + oqn->setQueriesEnabled(true); + +#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) + // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced + osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); +#else + osg::ref_ptr queryGeom = oqn->getQueryGeometry(); +#endif + + // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, + // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry + // is only called once. + // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. + queryGeom->setDataVariance(osg::Object::STATIC); + + // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, + // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to + // circumvent this. + queryGeom->setVertexArray(mGeom->getVertexArray()); + queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); + queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); + queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); + + // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. + oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); + // Still need a proper bounding sphere. + oqn->setInitialBound(queryGeom->getBound()); + +#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) + oqn->setQueryGeometry(queryGeom.release()); +#endif + + osg::StateSet* queryStateSet = new osg::StateSet; + if (queryVisible) + { + auto depth = SceneUtil::createDepth(); + // This is a trick to make fragments written by the query always use the maximum depth value, + // without having to retrieve the current far clipping distance. + // We want the sun glare to be "infinitely" far away. + double far = SceneUtil::getReverseZ() ? 0.0 : 1.0; + depth->setZNear(far); + depth->setZFar(far); + depth->setWriteMask(false); + queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + else + { + queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } + oqn->setQueryStateSet(queryStateSet); + + parent->addChild(oqn); + + return oqn; + } + + void Sun::createSunFlash(Resource::ImageManager& imageManager) + { + osg::ref_ptr tex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds")); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + tex->setName("diffuseMap"); + + osg::ref_ptr group (new osg::Group); + + mTransform->addChild(group); + + const float scale = 2.6f; + osg::ref_ptr geom = createTexturedQuad(1, scale); + group->addChild(geom); + + osg::StateSet* stateset = geom->getOrCreateStateSet(); + + stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + mSunFlashNode = group; + + mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); + mSunFlashNode->addCullCallback(mSunFlashCallback); + } + + void Sun::destroySunFlash() + { + if (mSunFlashNode) + { + mSunFlashNode->removeCullCallback(mSunFlashCallback); + mSunFlashCallback = nullptr; + } + } + + void Sun::createSunGlare() + { + osg::ref_ptr camera = new osg::Camera; + camera->setProjectionMatrix(osg::Matrix::identity()); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? + camera->setViewMatrix(osg::Matrix::identity()); + camera->setClearMask(0); + camera->setRenderOrder(osg::Camera::NESTED_RENDER); + camera->setAllowEventFocus(false); + + osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); + camera->addChild(geom); + + osg::StateSet* stateset = geom->getOrCreateStateSet(); + + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + // set up additive blending + osg::ref_ptr blendFunc = new osg::BlendFunc; + blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); + blendFunc->setDestination(osg::BlendFunc::ONE); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + + mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); + mSunGlareNode = camera; + + mSunGlareNode->addCullCallback(mSunGlareCallback); + + mTransform->addChild(camera); + } + + void Sun::destroySunGlare() + { + if (mSunGlareNode) + { + mSunGlareNode->removeCullCallback(mSunGlareCallback); + mSunGlareCallback = nullptr; + } + } + + Moon::Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type) + : CelestialBody(parentNode, scaleFactor, 2) + , mType(type) + , mPhase(MoonState::Phase::Unspecified) + , mUpdater(new MoonUpdater(*sceneManager.getImageManager(), sceneManager.getForceShaders())) + { + setPhase(MoonState::Phase::Full); + setVisible(true); + + mGeom->addUpdateCallback(mUpdater); + } + + Moon::~Moon() + { + mGeom->removeUpdateCallback(mUpdater); + } + + void Moon::adjustTransparency(const float ratio) + { + mUpdater->mTransparency *= ratio; + } + + void Moon::setState(const MoonState state) + { + float radsX = ((state.mRotationFromHorizon) * static_cast(osg::PI)) / 180.0f; + float radsZ = ((state.mRotationFromNorth) * static_cast(osg::PI)) / 180.0f; + + osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); + + osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); + mTransform->setPosition(direction * mDistance); + + // The moon quad is initially oriented facing down, so we need to offset its X-axis + // rotation to rotate it to face the camera when sitting at the horizon. + osg::Quat attX((-static_cast(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + mTransform->setAttitude(attX * rotZ); + + setPhase(state.mPhase); + mUpdater->mTransparency = state.mMoonAlpha; + mUpdater->mShadowBlend = state.mShadowBlend; + } + + void Moon::setAtmosphereColor(const osg::Vec4f& color) + { + mUpdater->mAtmosphereColor = color; + } + + void Moon::setColor(const osg::Vec4f& color) + { + mUpdater->mMoonColor = color; + } + + unsigned int Moon::getPhaseInt() const + { + switch (mPhase) + { + case MoonState::Phase::New: + return 0; + case MoonState::Phase::WaxingCrescent: + return 1; + case MoonState::Phase::WaningCrescent: + return 1; + case MoonState::Phase::FirstQuarter: + return 2; + case MoonState::Phase::ThirdQuarter: + return 2; + case MoonState::Phase::WaxingGibbous: + return 3; + case MoonState::Phase::WaningGibbous: + return 3; + case MoonState::Phase::Full: + return 4; + default: + return 0; + } + } + + void Moon::setPhase(const MoonState::Phase& phase) + { + if(mPhase == phase) + return; + + mPhase = phase; + + std::string textureName = "textures/tx_"; + + if (mType == Moon::Type_Secunda) + textureName += "secunda_"; + else + textureName += "masser_"; + + switch (mPhase) + { + case MoonState::Phase::New: + textureName += "new"; + break; + case MoonState::Phase::WaxingCrescent: + textureName += "one_wax"; + break; + case MoonState::Phase::FirstQuarter: + textureName += "half_wax"; + break; + case MoonState::Phase::WaxingGibbous: + textureName += "three_wax"; + break; + case MoonState::Phase::WaningCrescent: + textureName += "one_wan"; + break; + case MoonState::Phase::ThirdQuarter: + textureName += "half_wan"; + break; + case MoonState::Phase::WaningGibbous: + textureName += "three_wan"; + break; + case MoonState::Phase::Full: + textureName += "full"; + break; + default: + break; + } + + textureName += ".dds"; + + if (mType == Moon::Type_Secunda) + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); + else + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); + } + + int RainCounter::numParticlesToCreate(double dt) const + { + // limit dt to avoid large particle emissions if there are jumps in the simulation time + // 0.2 seconds is the same cap as used in Engine's frame loop + dt = std::min(dt, 0.2); + return ConstantRateCounter::numParticlesToCreate(dt); + } + + RainShooter::RainShooter() + : mAngle(0.f) + { } + + void RainShooter::shoot(osgParticle::Particle* particle) const + { + particle->setVelocity(mVelocity); + particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); + } + + void RainShooter::setVelocity(const osg::Vec3f& velocity) + { + mVelocity = velocity; + } + + void RainShooter::setAngle(float angle) + { + mAngle = angle; + } + + osg::Object* RainShooter::cloneType() const + { + return new RainShooter; + } + + osg::Object* RainShooter::clone(const osg::CopyOp &) const + { + return new RainShooter(*this); + } + + ModVertexAlphaVisitor::ModVertexAlphaVisitor(ModVertexAlphaVisitor::MeshType type) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mType(type) + { } + + void ModVertexAlphaVisitor::apply(osg::Geometry& geometry) + { + osg::ref_ptr colors = new osg::Vec4Array(geometry.getVertexArray()->getNumElements()); + for (unsigned int i=0; isize(); ++i) + { + float alpha = 1.f; + + switch (mType) + { + case ModVertexAlphaVisitor::Atmosphere: + { + // this is a cylinder, so every second vertex belongs to the bottom-most row + alpha = (i%2) ? 0.f : 1.f; + break; + } + case ModVertexAlphaVisitor::Clouds: + { + if (i>= 49 && i <= 64) + alpha = 0.f; // bottom-most row + else if (i>= 33 && i <= 48) + alpha = 0.25098; // second row + else + alpha = 1.f; + break; + } + case ModVertexAlphaVisitor::Stars: + { + if (geometry.getColorArray()) + { + osg::Vec4Array* origColors = static_cast(geometry.getColorArray()); + alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; + } + else + alpha = 1.f; + break; + } + } + + (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); + } + + geometry.setColorArray(colors, osg::Array::BIND_PER_VERTEX); + } +} diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp new file mode 100644 index 0000000000..ae04c88d23 --- /dev/null +++ b/apps/openmw/mwrender/skyutil.hpp @@ -0,0 +1,343 @@ +#ifndef OPENMW_MWRENDER_SKYUTIL_H +#define OPENMW_MWRENDER_SKYUTIL_H + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace Resource +{ + class ImageManager; + class SceneManager; +} + +namespace MWRender +{ + struct MoonUpdater; + class SunUpdater; + class SunFlashCallback; + class SunGlareCallback; + + struct WeatherResult + { + std::string mCloudTexture; + std::string mNextCloudTexture; + float mCloudBlendFactor; + + osg::Vec4f mFogColor; + + osg::Vec4f mAmbientColor; + + osg::Vec4f mSkyColor; + + // sun light color + osg::Vec4f mSunColor; + + // alpha is the sun transparency + osg::Vec4f mSunDiscColor; + + float mFogDepth; + + float mDLFogFactor; + float mDLFogOffset; + + float mWindSpeed; + float mBaseWindSpeed; + float mCurrentWindSpeed; + float mNextWindSpeed; + + float mCloudSpeed; + + float mGlareView; + + bool mNight; // use night skybox + float mNightFade; // fading factor for night skybox + + bool mIsStorm; + + std::string mAmbientLoopSoundID; + float mAmbientSoundVolume; + + std::string mParticleEffect; + std::string mRainEffect; + float mPrecipitationAlpha; + + float mRainDiameter; + float mRainMinHeight; + float mRainMaxHeight; + float mRainSpeed; + float mRainEntranceSpeed; + int mRainMaxRaindrops; + + osg::Vec3f mStormDirection; + osg::Vec3f mNextStormDirection; + }; + + struct MoonState + { + enum class Phase + { + Full, + WaningGibbous, + ThirdQuarter, + WaningCrescent, + New, + WaxingCrescent, + FirstQuarter, + WaxingGibbous, + Unspecified + }; + + float mRotationFromHorizon; + float mRotationFromNorth; + Phase mPhase; + float mShadowBlend; + float mMoonAlpha; + }; + + osg::ref_ptr createAlphaTrackingUnlitMaterial(); + osg::ref_ptr createUnlitMaterial(); + + class OcclusionCallback + { + public: + OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal); + + protected: + float getVisibleRatio (osg::Camera* camera); + + private: + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; + + std::map, float> mLastRatio; + }; + + class AtmosphereUpdater : public SceneUtil::StateSetUpdater + { + public: + void setEmissionColor(const osg::Vec4f& emissionColor); + + protected: + void setDefaults(osg::StateSet* stateset) override; + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override; + + private: + osg::Vec4f mEmissionColor; + }; + + class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater + { + public: + AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders); + + void setFade(float fade); + + protected: + void setDefaults(osg::StateSet* stateset) override; + + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override; + + private: + osg::Vec4f mColor; + osg::ref_ptr mTexture; + bool mForceShaders; + }; + + class CloudUpdater : public SceneUtil::StateSetUpdater + { + public: + CloudUpdater(bool forceShaders); + + void setTexture(osg::ref_ptr texture); + + void setEmissionColor(const osg::Vec4f& emissionColor); + void setOpacity(float opacity); + void setTextureCoord(float timer); + + protected: + void setDefaults(osg::StateSet *stateset) override; + void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; + + private: + osg::ref_ptr mTexture; + osg::Vec4f mEmissionColor; + float mOpacity; + bool mForceShaders; + osg::Matrixf mTexMat; + }; + + /// Transform that removes the eyepoint of the modelview matrix, + /// i.e. its children are positioned relative to the camera. + class CameraRelativeTransform : public osg::Transform + { + public: + CameraRelativeTransform(); + + CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop); + + META_Node(MWRender, CameraRelativeTransform) + + const osg::Vec3f& getLastViewPoint() const; + + bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const override; + + osg::BoundingSphere computeBound() const override; + + private: + // viewPoint for the current frame + mutable osg::Vec3f mViewPoint; + }; + + /// @brief Hides the node subgraph if the eye point is below water. + /// @note Must be added as cull callback. + /// @note Meant to be used on a node that is child of a CameraRelativeTransform. + /// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. + class UnderwaterSwitchCallback : public SceneUtil::NodeCallback + { + public: + UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform); + bool isUnderwater(); + + void operator()(osg::Node* node, osg::NodeVisitor* nv); + void setEnabled(bool enabled); + void setWaterLevel(float waterLevel); + + private: + osg::ref_ptr mCameraRelativeTransform; + bool mEnabled; + float mWaterLevel; + }; + + /// A base class for the sun and moons. + class CelestialBody + { + public: + CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0u); + + virtual ~CelestialBody() = default; + + virtual void adjustTransparency(const float ratio) = 0; + + void setVisible(bool visible); + + protected: + unsigned int mVisibleMask; + static const float mDistance; + osg::ref_ptr mTransform; + osg::ref_ptr mGeom; + }; + + class Sun : public CelestialBody + { + public: + Sun(osg::Group* parentNode, Resource::ImageManager& imageManager); + + ~Sun(); + + void setColor(const osg::Vec4f& color); + void adjustTransparency(const float ratio) override; + + void setDirection(const osg::Vec3f& direction); + void setGlareTimeOfDayFade(float val); + + private: + /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. + osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible); + + void createSunFlash(Resource::ImageManager& imageManager); + void destroySunFlash(); + + void createSunGlare(); + void destroySunGlare(); + + osg::ref_ptr mUpdater; + osg::ref_ptr mSunFlashNode; + osg::ref_ptr mSunGlareNode; + osg::ref_ptr mSunFlashCallback; + osg::ref_ptr mSunGlareCallback; + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; + }; + + class Moon : public CelestialBody + { + public: + enum Type + { + Type_Masser = 0, + Type_Secunda + }; + + Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type); + + ~Moon(); + + void adjustTransparency(const float ratio) override; + void setState(const MoonState state); + void setAtmosphereColor(const osg::Vec4f& color); + void setColor(const osg::Vec4f& color); + + unsigned int getPhaseInt() const; + + private: + Type mType; + MoonState::Phase mPhase; + osg::ref_ptr mUpdater; + + void setPhase(const MoonState::Phase& phase); + }; + + class RainCounter : public osgParticle::ConstantRateCounter + { + public: + int numParticlesToCreate(double dt) const override; + }; + + class RainShooter : public osgParticle::Shooter + { + public: + RainShooter(); + + osg::Object* cloneType() const override; + + osg::Object* clone(const osg::CopyOp &) const override; + + void shoot(osgParticle::Particle* particle) const override; + + void setVelocity(const osg::Vec3f& velocity); + void setAngle(float angle); + + private: + osg::Vec3f mVelocity; + float mAngle; + }; + + class ModVertexAlphaVisitor : public osg::NodeVisitor + { + public: + enum MeshType + { + Atmosphere, + Stars, + Clouds + }; + + ModVertexAlphaVisitor(MeshType type); + + void apply(osg::Geometry& geometry) override; + + private: + MeshType mType; + }; +} + +#endif diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 4bdd784db1..9055b95ee1 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -22,8 +22,6 @@ #include -using namespace MWWorld; - namespace { static const int invalidWeatherID = -1; @@ -38,1226 +36,1241 @@ namespace { return x * (1-factor) + y * factor; } -} -template -T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const -{ - WeatherSetting setting = timeSettings.getSetting(prefix); - float preSunriseTime = setting.mPreSunriseTime; - float postSunriseTime = setting.mPostSunriseTime; - float preSunsetTime = setting.mPreSunsetTime; - float postSunsetTime = setting.mPostSunsetTime; - - // night - if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) - return mNightValue; - // sunrise - else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime) + osg::Vec3f calculateStormDirection(const std::string& particleEffect) { - float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime; - float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f; - - if (gameHour <= middle) - { - // fade in - float advance = middle - gameHour; - float factor = 0.f; - if (duration > 0) - factor = advance / duration * 2; - return lerp(mSunriseValue, mNightValue, factor); - } - else + osg::Vec3f stormDirection = MWWorld::Weather::defaultDirection(); + if (particleEffect == "meshes\\ashcloud.nif" || particleEffect == "meshes\\blightcloud.nif") { - // fade out - float advance = gameHour - middle; - float factor = 1.f; - if (duration > 0) - factor = advance / duration * 2; - return lerp(mSunriseValue, mDayValue, factor); + osg::Vec3f playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); + playerPos.z() = 0; + osg::Vec3f redMountainPos = osg::Vec3f(25000.f, 70000.f, 0.f); + stormDirection = (playerPos - redMountainPos); + stormDirection.normalize(); } + return stormDirection; } - // day - else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) - return mDayValue; - // sunset - else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime) - { - float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime; - float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f; +} - if (gameHour <= middle) +namespace MWWorld +{ + template + T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const + { + WeatherSetting setting = timeSettings.getSetting(prefix); + float preSunriseTime = setting.mPreSunriseTime; + float postSunriseTime = setting.mPostSunriseTime; + float preSunsetTime = setting.mPreSunsetTime; + float postSunsetTime = setting.mPostSunsetTime; + + // night + if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) + return mNightValue; + // sunrise + else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime) { - // fade in - float advance = middle - gameHour; - float factor = 0.f; - if (duration > 0) - factor = advance / duration * 2; - return lerp(mSunsetValue, mDayValue, factor); + float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime; + float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f; + + if (gameHour <= middle) + { + // fade in + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; + return lerp(mSunriseValue, mNightValue, factor); + } + else + { + // fade out + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; + return lerp(mSunriseValue, mDayValue, factor); + } } - else + // day + else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) + return mDayValue; + // sunset + else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime) { - // fade out - float advance = gameHour - middle; - float factor = 1.f; - if (duration > 0) - factor = advance / duration * 2; - return lerp(mSunsetValue, mNightValue, factor); + float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime; + float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f; + + if (gameHour <= middle) + { + // fade in + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; + return lerp(mSunsetValue, mDayValue, factor); + } + else + { + // fade out + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; + return lerp(mSunsetValue, mNightValue, factor); + } } + // shut up compiler + return T(); } - // shut up compiler - return T(); -} - - -template class MWWorld::TimeOfDayInterpolator; -template class MWWorld::TimeOfDayInterpolator; - -Weather::Weather(const std::string& name, - float stormWindSpeed, - float rainSpeed, - float dlFactor, - float dlOffset, - const std::string& particleEffect) - : mCloudTexture(Fallback::Map::getString("Weather_" + name + "_Cloud_Texture")) - , mSkyColor(Fallback::Map::getColour("Weather_" + name +"_Sky_Sunrise_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sky_Day_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sky_Sunset_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sky_Night_Color")) - , mFogColor(Fallback::Map::getColour("Weather_" + name + "_Fog_Sunrise_Color"), - Fallback::Map::getColour("Weather_" + name + "_Fog_Day_Color"), - Fallback::Map::getColour("Weather_" + name + "_Fog_Sunset_Color"), - Fallback::Map::getColour("Weather_" + name + "_Fog_Night_Color")) - , mAmbientColor(Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunrise_Color"), - Fallback::Map::getColour("Weather_" + name + "_Ambient_Day_Color"), - Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunset_Color"), - Fallback::Map::getColour("Weather_" + name + "_Ambient_Night_Color")) - , mSunColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Sunrise_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sun_Day_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sun_Sunset_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sun_Night_Color")) - , mLandFogDepth(Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), - Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), - Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), - Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Night_Depth")) - , mSunDiscSunsetColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Disc_Sunset_Color")) - , mWindSpeed(Fallback::Map::getFloat("Weather_" + name + "_Wind_Speed")) - , mCloudSpeed(Fallback::Map::getFloat("Weather_" + name + "_Cloud_Speed")) - , mGlareView(Fallback::Map::getFloat("Weather_" + name + "_Glare_View")) - , mIsStorm(mWindSpeed > stormWindSpeed) - , mRainSpeed(rainSpeed) - , mRainEntranceSpeed(Fallback::Map::getFloat("Weather_" + name + "_Rain_Entrance_Speed")) - , mRainMaxRaindrops(Fallback::Map::getFloat("Weather_" + name + "_Max_Raindrops")) - , mRainDiameter(Fallback::Map::getFloat("Weather_" + name + "_Rain_Diameter")) - , mRainThreshold(Fallback::Map::getFloat("Weather_" + name + "_Rain_Threshold")) - , mRainMinHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Min")) - , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) - , mParticleEffect(particleEffect) - , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") - , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) - , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) - , mThunderFrequency(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Frequency")) - , mThunderThreshold(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Threshold")) - , mThunderSoundID() - , mFlashDecrement(Fallback::Map::getFloat("Weather_" + name + "_Flash_Decrement")) - , mFlashBrightness(0.0f) -{ - mDL.FogFactor = dlFactor; - mDL.FogOffset = dlOffset; - mThunderSoundID[0] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_0"); - mThunderSoundID[1] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_1"); - mThunderSoundID[2] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_2"); - mThunderSoundID[3] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_3"); - - // TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both sounds at the same time. + template class MWWorld::TimeOfDayInterpolator; + template class MWWorld::TimeOfDayInterpolator; - if (!mRainEffect.empty()) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing Using_Precip has no effect + osg::Vec3f Weather::defaultDirection() { - mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Rain_Loop_Sound_ID"); - if (mAmbientLoopSoundID.empty()) // default to "rain" if not set - mAmbientLoopSoundID = "rain"; + static const osg::Vec3f direction = osg::Vec3f(0.f, 1.f, 0.f); + return direction; } - else - mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID"); - if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None")) - mAmbientLoopSoundID.clear(); -} + Weather::Weather(const std::string& name, + float stormWindSpeed, + float rainSpeed, + float dlFactor, + float dlOffset, + const std::string& particleEffect) + : mCloudTexture(Fallback::Map::getString("Weather_" + name + "_Cloud_Texture")) + , mSkyColor(Fallback::Map::getColour("Weather_" + name +"_Sky_Sunrise_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sky_Day_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sky_Sunset_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sky_Night_Color")) + , mFogColor(Fallback::Map::getColour("Weather_" + name + "_Fog_Sunrise_Color"), + Fallback::Map::getColour("Weather_" + name + "_Fog_Day_Color"), + Fallback::Map::getColour("Weather_" + name + "_Fog_Sunset_Color"), + Fallback::Map::getColour("Weather_" + name + "_Fog_Night_Color")) + , mAmbientColor(Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunrise_Color"), + Fallback::Map::getColour("Weather_" + name + "_Ambient_Day_Color"), + Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunset_Color"), + Fallback::Map::getColour("Weather_" + name + "_Ambient_Night_Color")) + , mSunColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Sunrise_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sun_Day_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sun_Sunset_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sun_Night_Color")) + , mLandFogDepth(Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Night_Depth")) + , mSunDiscSunsetColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Disc_Sunset_Color")) + , mWindSpeed(Fallback::Map::getFloat("Weather_" + name + "_Wind_Speed")) + , mCloudSpeed(Fallback::Map::getFloat("Weather_" + name + "_Cloud_Speed")) + , mGlareView(Fallback::Map::getFloat("Weather_" + name + "_Glare_View")) + , mIsStorm(mWindSpeed > stormWindSpeed) + , mRainSpeed(rainSpeed) + , mRainEntranceSpeed(Fallback::Map::getFloat("Weather_" + name + "_Rain_Entrance_Speed")) + , mRainMaxRaindrops(Fallback::Map::getFloat("Weather_" + name + "_Max_Raindrops")) + , mRainDiameter(Fallback::Map::getFloat("Weather_" + name + "_Rain_Diameter")) + , mRainThreshold(Fallback::Map::getFloat("Weather_" + name + "_Rain_Threshold")) + , mRainMinHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Min")) + , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) + , mParticleEffect(particleEffect) + , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") + , mStormDirection(Weather::defaultDirection()) + , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) + , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) + , mThunderFrequency(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Frequency")) + , mThunderThreshold(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Threshold")) + , mThunderSoundID() + , mFlashDecrement(Fallback::Map::getFloat("Weather_" + name + "_Flash_Decrement")) + , mFlashBrightness(0.0f) + { + mDL.FogFactor = dlFactor; + mDL.FogOffset = dlOffset; + mThunderSoundID[0] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_0"); + mThunderSoundID[1] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_1"); + mThunderSoundID[2] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_2"); + mThunderSoundID[3] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_3"); -float Weather::transitionDelta() const -{ - // Transition Delta describes how quickly transitioning to the weather in question will take, in Hz. Note that the - // measurement is in real time, not in-game time. - return mTransitionDelta; -} + // TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both sounds at the same time. -float Weather::cloudBlendFactor(const float transitionRatio) const -{ - // Clouds Maximum Percent affects how quickly the sky transitions from one sky texture to the next. - return transitionRatio / mCloudsMaximumPercent; -} - -float Weather::calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused) -{ - // When paused, the flash brightness remains the same and no new strikes can occur. - if(!isPaused) - { - // Morrowind doesn't appear to do any calculations unless the transition ratio is higher than the Thunder Threshold. - if(transitionRatio >= mThunderThreshold && mThunderFrequency > 0.0f) + if (!mRainEffect.empty()) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing Using_Precip has no effect { - flashDecrement(elapsedSeconds); - - if(Misc::Rng::rollProbability() <= thunderChance(transitionRatio, elapsedSeconds)) - { - lightningAndThunder(); - } + mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Rain_Loop_Sound_ID"); + if (mAmbientLoopSoundID.empty()) // default to "rain" if not set + mAmbientLoopSoundID = "rain"; } else - { - mFlashBrightness = 0.0f; - } - } + mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID"); - return mFlashBrightness; -} - -inline void Weather::flashDecrement(const float elapsedSeconds) -{ - // The Flash Decrement is measured in whole units per second. This means that if the flash brightness was - // currently 1.0, then it should take approximately 0.25 seconds to decay to 0.0 (the minimum). - float decrement = mFlashDecrement * elapsedSeconds; - mFlashBrightness = decrement > mFlashBrightness ? 0.0f : mFlashBrightness - decrement; -} - -inline float Weather::thunderChance(const float transitionRatio, const float elapsedSeconds) const -{ - // This formula is reversed from the observation that with Thunder Frequency set to 1, there are roughly 10 strikes - // per minute. It doesn't appear to be tied to in game time as Timescale doesn't affect it. Various values of - // Thunder Frequency seem to change the average number of strikes in a linear fashion.. During a transition, it appears to - // scaled based on how far past it is past the Thunder Threshold. - float scaleFactor = (transitionRatio - mThunderThreshold) / (1.0f - mThunderThreshold); - return ((mThunderFrequency * 10.0f) / 60.0f) * elapsedSeconds * scaleFactor; -} - -inline void Weather::lightningAndThunder(void) -{ - // Morrowind seems to vary the intensity of the brightness based on which of the four sound IDs it selects. - // They appear to go from 0 (brightest, closest) to 3 (faintest, farthest). The value of 0.25 per distance - // was derived by setting the Flash Decrement to 0.1 and measuring how long each value took to decay to 0. - // TODO: Determine the distribution of each distance to see if it's evenly weighted. - unsigned int distance = Misc::Rng::rollDice(4); - // Flash brightness appears additive, since if multiple strikes occur, it takes longer for it to decay to 0. - mFlashBrightness += 1 - (distance * 0.25f); - MWBase::Environment::get().getSoundManager()->playSound(mThunderSoundID[distance], 1.0, 1.0); -} + if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None")) + mAmbientLoopSoundID.clear(); + } -RegionWeather::RegionWeather(const ESM::Region& region) - : mWeather(invalidWeatherID) - , mChances() -{ - mChances.reserve(10); - mChances.push_back(region.mData.mClear); - mChances.push_back(region.mData.mCloudy); - mChances.push_back(region.mData.mFoggy); - mChances.push_back(region.mData.mOvercast); - mChances.push_back(region.mData.mRain); - mChances.push_back(region.mData.mThunder); - mChances.push_back(region.mData.mAsh); - mChances.push_back(region.mData.mBlight); - mChances.push_back(region.mData.mA); - mChances.push_back(region.mData.mB); -} + float Weather::transitionDelta() const + { + // Transition Delta describes how quickly transitioning to the weather in question will take, in Hz. Note that the + // measurement is in real time, not in-game time. + return mTransitionDelta; + } -RegionWeather::RegionWeather(const ESM::RegionWeatherState& state) - : mWeather(state.mWeather) - , mChances(state.mChances) -{ -} + float Weather::cloudBlendFactor(const float transitionRatio) const + { + // Clouds Maximum Percent affects how quickly the sky transitions from one sky texture to the next. + return transitionRatio / mCloudsMaximumPercent; + } -RegionWeather::operator ESM::RegionWeatherState() const -{ - ESM::RegionWeatherState state = + float Weather::calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused) + { + // When paused, the flash brightness remains the same and no new strikes can occur. + if(!isPaused) { - mWeather, - mChances - }; + // Morrowind doesn't appear to do any calculations unless the transition ratio is higher than the Thunder Threshold. + if(transitionRatio >= mThunderThreshold && mThunderFrequency > 0.0f) + { + flashDecrement(elapsedSeconds); - return state; -} + if(Misc::Rng::rollProbability() <= thunderChance(transitionRatio, elapsedSeconds)) + { + lightningAndThunder(); + } + } + else + { + mFlashBrightness = 0.0f; + } + } -void RegionWeather::setChances(const std::vector& chances) -{ - if(mChances.size() < chances.size()) + return mFlashBrightness; + } + + inline void Weather::flashDecrement(const float elapsedSeconds) { - mChances.reserve(chances.size()); + // The Flash Decrement is measured in whole units per second. This means that if the flash brightness was + // currently 1.0, then it should take approximately 0.25 seconds to decay to 0.0 (the minimum). + float decrement = mFlashDecrement * elapsedSeconds; + mFlashBrightness = decrement > mFlashBrightness ? 0.0f : mFlashBrightness - decrement; } - int i = 0; - for(char chance : chances) + inline float Weather::thunderChance(const float transitionRatio, const float elapsedSeconds) const { - mChances[i] = chance; - i++; + // This formula is reversed from the observation that with Thunder Frequency set to 1, there are roughly 10 strikes + // per minute. It doesn't appear to be tied to in game time as Timescale doesn't affect it. Various values of + // Thunder Frequency seem to change the average number of strikes in a linear fashion.. During a transition, it appears to + // scaled based on how far past it is past the Thunder Threshold. + float scaleFactor = (transitionRatio - mThunderThreshold) / (1.0f - mThunderThreshold); + return ((mThunderFrequency * 10.0f) / 60.0f) * elapsedSeconds * scaleFactor; } - // Regional weather no longer supports the current type, select a new weather pattern. - if((static_cast(mWeather) >= mChances.size()) || (mChances[mWeather] == 0)) + inline void Weather::lightningAndThunder(void) { - chooseNewWeather(); + // Morrowind seems to vary the intensity of the brightness based on which of the four sound IDs it selects. + // They appear to go from 0 (brightest, closest) to 3 (faintest, farthest). The value of 0.25 per distance + // was derived by setting the Flash Decrement to 0.1 and measuring how long each value took to decay to 0. + // TODO: Determine the distribution of each distance to see if it's evenly weighted. + unsigned int distance = Misc::Rng::rollDice(4); + // Flash brightness appears additive, since if multiple strikes occur, it takes longer for it to decay to 0. + mFlashBrightness += 1 - (distance * 0.25f); + MWBase::Environment::get().getSoundManager()->playSound(mThunderSoundID[distance], 1.0, 1.0); } -} -void RegionWeather::setWeather(int weatherID) -{ - mWeather = weatherID; -} + RegionWeather::RegionWeather(const ESM::Region& region) + : mWeather(invalidWeatherID) + , mChances() + { + mChances.reserve(10); + mChances.push_back(region.mData.mClear); + mChances.push_back(region.mData.mCloudy); + mChances.push_back(region.mData.mFoggy); + mChances.push_back(region.mData.mOvercast); + mChances.push_back(region.mData.mRain); + mChances.push_back(region.mData.mThunder); + mChances.push_back(region.mData.mAsh); + mChances.push_back(region.mData.mBlight); + mChances.push_back(region.mData.mA); + mChances.push_back(region.mData.mB); + } -int RegionWeather::getWeather() -{ - // If the region weather was already set (by ChangeWeather, or by a previous call) then just return that value. - // Note that the region weather will be expired periodically when the weather update timer expires. - if(mWeather == invalidWeatherID) + RegionWeather::RegionWeather(const ESM::RegionWeatherState& state) + : mWeather(state.mWeather) + , mChances(state.mChances) { - chooseNewWeather(); } - return mWeather; -} + RegionWeather::operator ESM::RegionWeatherState() const + { + ESM::RegionWeatherState state = + { + mWeather, + mChances + }; -void RegionWeather::chooseNewWeather() -{ - // All probabilities must add to 100 (responsibility of the user). - // If chances A and B has values 30 and 70 then by generating 100 numbers 1..100, 30% will be lesser or equal 30 - // and 70% will be greater than 30 (in theory). - int chance = Misc::Rng::rollDice(100) + 1; // 1..100 - int sum = 0; - int i = 0; - for(; static_cast(i) < mChances.size(); ++i) + return state; + } + + void RegionWeather::setChances(const std::vector& chances) { - sum += mChances[i]; - if(chance <= sum) + if(mChances.size() < chances.size()) { - mWeather = i; - return; + mChances.reserve(chances.size()); } - } - - // if we hit this path then the chances don't add to 100, choose a default weather instead - mWeather = 0; -} -MoonModel::MoonModel(const std::string& name) - : mFadeInStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Start")) - , mFadeInFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Finish")) - , mFadeOutStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Start")) - , mFadeOutFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Finish")) - , mAxisOffset(Fallback::Map::getFloat("Moons_" + name + "_Axis_Offset")) - , mSpeed(Fallback::Map::getFloat("Moons_" + name + "_Speed")) - , mDailyIncrement(Fallback::Map::getFloat("Moons_" + name + "_Daily_Increment")) - , mFadeStartAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_Start_Angle")) - , mFadeEndAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_End_Angle")) - , mMoonShadowEarlyFadeAngle(Fallback::Map::getFloat("Moons_" + name + "_Moon_Shadow_Early_Fade_Angle")) -{ - // Morrowind appears to have a minimum speed in order to avoid situations where the moon couldn't conceivably - // complete a rotation in a single 24 hour period. The value of 180/23 was deduced from reverse engineering. - mSpeed = std::min(mSpeed, 180.0f / 23.0f); -} + int i = 0; + for(char chance : chances) + { + mChances[i] = chance; + i++; + } -MWRender::MoonState MoonModel::calculateState(const TimeStamp& gameTime) const -{ - float rotationFromHorizon = angle(gameTime); - MWRender::MoonState state = + // Regional weather no longer supports the current type, select a new weather pattern. + if((static_cast(mWeather) >= mChances.size()) || (mChances[mWeather] == 0)) { - rotationFromHorizon, - mAxisOffset, // Reverse engineered from Morrowind's scene graph rotation matrices. - phase(gameTime), - shadowBlend(rotationFromHorizon), - earlyMoonShadowAlpha(rotationFromHorizon) * hourlyAlpha(gameTime.getHour()) - }; + chooseNewWeather(); + } + } - return state; -} + void RegionWeather::setWeather(int weatherID) + { + mWeather = weatherID; + } -inline float MoonModel::angle(const TimeStamp& gameTime) const -{ - // Morrowind's moons start travel on one side of the horizon (let's call it H-rise) and travel 180 degrees to the - // opposite horizon (let's call it H-set). Upon reaching H-set, they reset to H-rise until the next moon rise. + int RegionWeather::getWeather() + { + // If the region weather was already set (by ChangeWeather, or by a previous call) then just return that value. + // Note that the region weather will be expired periodically when the weather update timer expires. + if(mWeather == invalidWeatherID) + { + chooseNewWeather(); + } - // When calculating the angle of the moon, several cases have to be taken into account: - // 1. Moon rises and then sets in one day. - // 2. Moon sets and doesn't rise in one day (occurs when the moon rise hour is >= 24). - // 3. Moon sets and then rises in one day. - float moonRiseHourToday = moonRiseHour(gameTime.getDay()); - float moonRiseAngleToday = 0; + return mWeather; + } - if(gameTime.getHour() < moonRiseHourToday) + void RegionWeather::chooseNewWeather() { - float moonRiseHourYesterday = moonRiseHour(gameTime.getDay() - 1); - if(moonRiseHourYesterday < 24) + // All probabilities must add to 100 (responsibility of the user). + // If chances A and B has values 30 and 70 then by generating 100 numbers 1..100, 30% will be lesser or equal 30 + // and 70% will be greater than 30 (in theory). + int chance = Misc::Rng::rollDice(100) + 1; // 1..100 + int sum = 0; + int i = 0; + for(; static_cast(i) < mChances.size(); ++i) { - float moonRiseAngleYesterday = rotation(24 - moonRiseHourYesterday); - if(moonRiseAngleYesterday < 180) + sum += mChances[i]; + if(chance <= sum) { - // The moon rose but did not set yesterday, so accumulate yesterday's angle with how much we've travelled today. - moonRiseAngleToday = rotation(gameTime.getHour()) + moonRiseAngleYesterday; + mWeather = i; + return; } } + + // if we hit this path then the chances don't add to 100, choose a default weather instead + mWeather = 0; } - else + + MoonModel::MoonModel(const std::string& name) + : mFadeInStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Start")) + , mFadeInFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Finish")) + , mFadeOutStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Start")) + , mFadeOutFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Finish")) + , mAxisOffset(Fallback::Map::getFloat("Moons_" + name + "_Axis_Offset")) + , mSpeed(Fallback::Map::getFloat("Moons_" + name + "_Speed")) + , mDailyIncrement(Fallback::Map::getFloat("Moons_" + name + "_Daily_Increment")) + , mFadeStartAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_Start_Angle")) + , mFadeEndAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_End_Angle")) + , mMoonShadowEarlyFadeAngle(Fallback::Map::getFloat("Moons_" + name + "_Moon_Shadow_Early_Fade_Angle")) { - moonRiseAngleToday = rotation(gameTime.getHour() - moonRiseHourToday); + // Morrowind appears to have a minimum speed in order to avoid situations where the moon couldn't conceivably + // complete a rotation in a single 24 hour period. The value of 180/23 was deduced from reverse engineering. + mSpeed = std::min(mSpeed, 180.0f / 23.0f); } - if(moonRiseAngleToday >= 180) + MWRender::MoonState MoonModel::calculateState(const TimeStamp& gameTime) const { - // The moon set today, reset the angle to the horizon. - moonRiseAngleToday = 0; + float rotationFromHorizon = angle(gameTime); + MWRender::MoonState state = + { + rotationFromHorizon, + mAxisOffset, // Reverse engineered from Morrowind's scene graph rotation matrices. + phase(gameTime), + shadowBlend(rotationFromHorizon), + earlyMoonShadowAlpha(rotationFromHorizon) * hourlyAlpha(gameTime.getHour()) + }; + + return state; } - return moonRiseAngleToday; -} + inline float MoonModel::angle(const TimeStamp& gameTime) const + { + // Morrowind's moons start travel on one side of the horizon (let's call it H-rise) and travel 180 degrees to the + // opposite horizon (let's call it H-set). Upon reaching H-set, they reset to H-rise until the next moon rise. -inline float MoonModel::moonRiseHour(unsigned int daysPassed) const -{ - // This arises from the start date of 16 Last Seed, 427 - // TODO: Find an alternate formula that doesn't rely on this day being fixed. - static const unsigned int startDay = 16; - - // This odd formula arises from the fact that on 16 Last Seed, 17 increments have occurred, meaning - // that upon starting a new game, it must only calculate the moon phase as far back as 1 Last Seed. - // Note that we don't modulo after adding the latest daily increment because other calculations need to - // know if doing so would cause the moon rise to be postponed until the next day (which happens when - // the moon rise hour is >= 24 in Morrowind). - return mDailyIncrement + std::fmod((daysPassed - 1 + startDay) * mDailyIncrement, 24.0f); -} + // When calculating the angle of the moon, several cases have to be taken into account: + // 1. Moon rises and then sets in one day. + // 2. Moon sets and doesn't rise in one day (occurs when the moon rise hour is >= 24). + // 3. Moon sets and then rises in one day. + float moonRiseHourToday = moonRiseHour(gameTime.getDay()); + float moonRiseAngleToday = 0; -inline float MoonModel::rotation(float hours) const -{ - // 15 degrees per hour was reverse engineered from the rotation matrices of the Morrowind scene graph. - // Note that this correlates to 360 / 24, which is a full rotation every 24 hours, so speed is a measure - // of whole rotations that could be completed in a day. - return 15.0f * mSpeed * hours; -} - -MWRender::MoonState::Phase MoonModel::phase(const TimeStamp& gameTime) const -{ - // Morrowind starts with a full moon on 16 Last Seed and then begins to wane 17 Last Seed, working on 3 day phase cycle. - - // If the moon didn't rise yet today, use yesterday's moon phase. - if(gameTime.getHour() < moonRiseHour(gameTime.getDay())) - return static_cast((gameTime.getDay() / 3) % 8); - else - return static_cast(((gameTime.getDay() + 1) / 3) % 8); -} + if(gameTime.getHour() < moonRiseHourToday) + { + float moonRiseHourYesterday = moonRiseHour(gameTime.getDay() - 1); + if(moonRiseHourYesterday < 24) + { + float moonRiseAngleYesterday = rotation(24 - moonRiseHourYesterday); + if(moonRiseAngleYesterday < 180) + { + // The moon rose but did not set yesterday, so accumulate yesterday's angle with how much we've travelled today. + moonRiseAngleToday = rotation(gameTime.getHour()) + moonRiseAngleYesterday; + } + } + } + else + { + moonRiseAngleToday = rotation(gameTime.getHour() - moonRiseHourToday); + } -inline float MoonModel::shadowBlend(float angle) const -{ - // The Fade End Angle and Fade Start Angle describe a region where the moon transitions from a solid disk - // that is roughly the color of the sky, to a textured surface. - // Depending on the current angle, the following values describe the ratio between the textured moon - // and the solid disk: - // 1. From Fade End Angle 1 to Fade Start Angle 1 (during moon rise): 0..1 - // 2. From Fade Start Angle 1 to Fade Start Angle 2 (between moon rise and moon set): 1 (textured) - // 3. From Fade Start Angle 2 to Fade End Angle 2 (during moon set): 1..0 - // 4. From Fade End Angle 2 to Fade End Angle 1 (between moon set and moon rise): 0 (solid disk) - float fadeAngle = mFadeStartAngle - mFadeEndAngle; - float fadeEndAngle2 = 180.0f - mFadeEndAngle; - float fadeStartAngle2 = 180.0f - mFadeStartAngle; - if((angle >= mFadeEndAngle) && (angle < mFadeStartAngle)) - return (angle - mFadeEndAngle) / fadeAngle; - else if((angle >= mFadeStartAngle) && (angle < fadeStartAngle2)) - return 1.0f; - else if((angle >= fadeStartAngle2) && (angle < fadeEndAngle2)) - return (fadeEndAngle2 - angle) / fadeAngle; - else - return 0.0f; -} + if(moonRiseAngleToday >= 180) + { + // The moon set today, reset the angle to the horizon. + moonRiseAngleToday = 0; + } -inline float MoonModel::hourlyAlpha(float gameHour) const -{ - // The Fade Out Start / Finish and Fade In Start / Finish describe the hours at which the moon - // appears and disappears. - // Depending on the current hour, the following values describe how transparent the moon is. - // 1. From Fade Out Start to Fade Out Finish: 1..0 - // 2. From Fade Out Finish to Fade In Start: 0 (transparent) - // 3. From Fade In Start to Fade In Finish: 0..1 - // 4. From Fade In Finish to Fade Out Start: 1 (solid) - if((gameHour >= mFadeOutStart) && (gameHour < mFadeOutFinish)) - return (mFadeOutFinish - gameHour) / (mFadeOutFinish - mFadeOutStart); - else if((gameHour >= mFadeOutFinish) && (gameHour < mFadeInStart)) - return 0.0f; - else if((gameHour >= mFadeInStart) && (gameHour < mFadeInFinish)) - return (gameHour - mFadeInStart) / (mFadeInFinish - mFadeInStart); - else - return 1.0f; -} + return moonRiseAngleToday; + } -inline float MoonModel::earlyMoonShadowAlpha(float angle) const -{ - // The Moon Shadow Early Fade Angle describes an arc relative to Fade End Angle. - // Depending on the current angle, the following values describe how transparent the moon is. - // 1. From Moon Shadow Early Fade Angle 1 to Fade End Angle 1 (during moon rise): 0..1 - // 2. From Fade End Angle 1 to Fade End Angle 2 (between moon rise and moon set): 1 (solid) - // 3. From Fade End Angle 2 to Moon Shadow Early Fade Angle 2 (during moon set): 1..0 - // 4. From Moon Shadow Early Fade Angle 2 to Moon Shadow Early Fade Angle 1: 0 (transparent) - float moonShadowEarlyFadeAngle1 = mFadeEndAngle - mMoonShadowEarlyFadeAngle; - float fadeEndAngle2 = 180.0f - mFadeEndAngle; - float moonShadowEarlyFadeAngle2 = fadeEndAngle2 + mMoonShadowEarlyFadeAngle; - if((angle >= moonShadowEarlyFadeAngle1) && (angle < mFadeEndAngle)) - return (angle - moonShadowEarlyFadeAngle1) / mMoonShadowEarlyFadeAngle; - else if((angle >= mFadeEndAngle) && (angle < fadeEndAngle2)) - return 1.0f; - else if((angle >= fadeEndAngle2) && (angle < moonShadowEarlyFadeAngle2)) - return (moonShadowEarlyFadeAngle2 - angle) / mMoonShadowEarlyFadeAngle; - else - return 0.0f; -} + inline float MoonModel::moonRiseHour(unsigned int daysPassed) const + { + // This arises from the start date of 16 Last Seed, 427 + // TODO: Find an alternate formula that doesn't rely on this day being fixed. + static const unsigned int startDay = 16; + + // This odd formula arises from the fact that on 16 Last Seed, 17 increments have occurred, meaning + // that upon starting a new game, it must only calculate the moon phase as far back as 1 Last Seed. + // Note that we don't modulo after adding the latest daily increment because other calculations need to + // know if doing so would cause the moon rise to be postponed until the next day (which happens when + // the moon rise hour is >= 24 in Morrowind). + return mDailyIncrement + std::fmod((daysPassed - 1 + startDay) * mDailyIncrement, 24.0f); + } -WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, MWWorld::ESMStore& store) - : mStore(store) - , mRendering(rendering) - , mSunriseTime(Fallback::Map::getFloat("Weather_Sunrise_Time")) - , mSunsetTime(Fallback::Map::getFloat("Weather_Sunset_Time")) - , mSunriseDuration(Fallback::Map::getFloat("Weather_Sunrise_Duration")) - , mSunsetDuration(Fallback::Map::getFloat("Weather_Sunset_Duration")) - , mSunPreSunsetTime(Fallback::Map::getFloat("Weather_Sun_Pre-Sunset_Time")) - , mNightFade(0, 0, 0, 1) - , mHoursBetweenWeatherChanges(Fallback::Map::getFloat("Weather_Hours_Between_Weather_Changes")) - , mRainSpeed(Fallback::Map::getFloat("Weather_Precip_Gravity")) - , mUnderwaterFog(Fallback::Map::getFloat("Water_UnderwaterSunriseFog"), - Fallback::Map::getFloat("Water_UnderwaterDayFog"), - Fallback::Map::getFloat("Water_UnderwaterSunsetFog"), - Fallback::Map::getFloat("Water_UnderwaterNightFog")) - , mWeatherSettings() - , mMasser("Masser") - , mSecunda("Secunda") - , mWindSpeed(0.f) - , mCurrentWindSpeed(0.f) - , mNextWindSpeed(0.f) - , mIsStorm(false) - , mPrecipitation(false) - , mStormDirection(0,1,0) - , mCurrentRegion() - , mTimePassed(0) - , mFastForward(false) - , mWeatherUpdateTime(mHoursBetweenWeatherChanges) - , mTransitionFactor(0) - , mNightDayMode(Default) - , mCurrentWeather(0) - , mNextWeather(0) - , mQueuedWeather(0) - , mRegions() - , mResult() - , mAmbientSound(nullptr) - , mPlayingSoundID() -{ - mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; - mTimeSettings.mNightEnd = mSunriseTime; - mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; - mTimeSettings.mDayEnd = mSunsetTime; - - mTimeSettings.addSetting("Sky"); - mTimeSettings.addSetting("Ambient"); - mTimeSettings.addSetting("Fog"); - mTimeSettings.addSetting("Sun"); - - // Morrowind handles stars settings differently for other ones - mTimeSettings.mStarsPostSunsetStart = Fallback::Map::getFloat("Weather_Stars_Post-Sunset_Start"); - mTimeSettings.mStarsPreSunriseFinish = Fallback::Map::getFloat("Weather_Stars_Pre-Sunrise_Finish"); - mTimeSettings.mStarsFadingDuration = Fallback::Map::getFloat("Weather_Stars_Fading_Duration"); - - WeatherSetting starSetting = { - mTimeSettings.mStarsPreSunriseFinish, - mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish, - mTimeSettings.mStarsPostSunsetStart, - mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart - }; - - mTimeSettings.mSunriseTransitions["Stars"] = starSetting; - - mWeatherSettings.reserve(10); - // These distant land fog factor and offset values are the defaults MGE XE provides. Should be - // provided by settings somewhere? - addWeather("Clear", 1.0f, 0.0f); // 0 - addWeather("Cloudy", 0.9f, 0.0f); // 1 - addWeather("Foggy", 0.2f, 30.0f); // 2 - 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 - - Store::iterator it = store.get().begin(); - for(; it != store.get().end(); ++it) + inline float MoonModel::rotation(float hours) const { - std::string regionID = Misc::StringUtils::lowerCase(it->mId); - mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); + // 15 degrees per hour was reverse engineered from the rotation matrices of the Morrowind scene graph. + // Note that this correlates to 360 / 24, which is a full rotation every 24 hours, so speed is a measure + // of whole rotations that could be completed in a day. + return 15.0f * mSpeed * hours; } - forceWeather(0); -} + MWRender::MoonState::Phase MoonModel::phase(const TimeStamp& gameTime) const + { + // Morrowind starts with a full moon on 16 Last Seed and then begins to wane 17 Last Seed, working on 3 day phase cycle. -WeatherManager::~WeatherManager() -{ - stopSounds(); -} + // If the moon didn't rise yet today, use yesterday's moon phase. + if(gameTime.getHour() < moonRiseHour(gameTime.getDay())) + return static_cast((gameTime.getDay() / 3) % 8); + else + return static_cast(((gameTime.getDay() + 1) / 3) % 8); + } -void WeatherManager::changeWeather(const std::string& regionID, const unsigned int weatherID) -{ - // In Morrowind, this seems to have the following behavior, when applied to the current region: - // - When there is no transition in progress, start transitioning to the new weather. - // - If there is a transition in progress, queue up the transition and process it when the current one completes. - // - If there is a transition in progress, and a queued transition, overwrite the queued transition. - // - If multiple calls to ChangeWeather are made while paused (console up), only the last call will be used, - // meaning that if there was no transition in progress, only the last ChangeWeather will be processed. - // If the region isn't current, Morrowind will store the new weather for the region in question. - - if(weatherID < mWeatherSettings.size()) + inline float MoonModel::shadowBlend(float angle) const { - std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); - std::map::iterator it = mRegions.find(lowerCaseRegionID); - if(it != mRegions.end()) - { - it->second.setWeather(weatherID); - regionalWeatherChanged(it->first, it->second); - } + // The Fade End Angle and Fade Start Angle describe a region where the moon transitions from a solid disk + // that is roughly the color of the sky, to a textured surface. + // Depending on the current angle, the following values describe the ratio between the textured moon + // and the solid disk: + // 1. From Fade End Angle 1 to Fade Start Angle 1 (during moon rise): 0..1 + // 2. From Fade Start Angle 1 to Fade Start Angle 2 (between moon rise and moon set): 1 (textured) + // 3. From Fade Start Angle 2 to Fade End Angle 2 (during moon set): 1..0 + // 4. From Fade End Angle 2 to Fade End Angle 1 (between moon set and moon rise): 0 (solid disk) + float fadeAngle = mFadeStartAngle - mFadeEndAngle; + float fadeEndAngle2 = 180.0f - mFadeEndAngle; + float fadeStartAngle2 = 180.0f - mFadeStartAngle; + if((angle >= mFadeEndAngle) && (angle < mFadeStartAngle)) + return (angle - mFadeEndAngle) / fadeAngle; + else if((angle >= mFadeStartAngle) && (angle < fadeStartAngle2)) + return 1.0f; + else if((angle >= fadeStartAngle2) && (angle < fadeEndAngle2)) + return (fadeEndAngle2 - angle) / fadeAngle; + else + return 0.0f; } -} -void WeatherManager::modRegion(const std::string& regionID, const std::vector& chances) -{ - // Sets the region's probability for various weather patterns. Note that this appears to be saved permanently. - // In Morrowind, this seems to have the following behavior when applied to the current region: - // - If the region supports the current weather, no change in current weather occurs. - // - If the region no longer supports the current weather, and there is no transition in progress, begin to - // transition to a new supported weather type. - // - If the region no longer supports the current weather, and there is a transition in progress, queue a - // transition to a new supported weather type. - - std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); - std::map::iterator it = mRegions.find(lowerCaseRegionID); - if(it != mRegions.end()) + inline float MoonModel::hourlyAlpha(float gameHour) const { - it->second.setChances(chances); - regionalWeatherChanged(it->first, it->second); + // The Fade Out Start / Finish and Fade In Start / Finish describe the hours at which the moon + // appears and disappears. + // Depending on the current hour, the following values describe how transparent the moon is. + // 1. From Fade Out Start to Fade Out Finish: 1..0 + // 2. From Fade Out Finish to Fade In Start: 0 (transparent) + // 3. From Fade In Start to Fade In Finish: 0..1 + // 4. From Fade In Finish to Fade Out Start: 1 (solid) + if((gameHour >= mFadeOutStart) && (gameHour < mFadeOutFinish)) + return (mFadeOutFinish - gameHour) / (mFadeOutFinish - mFadeOutStart); + else if((gameHour >= mFadeOutFinish) && (gameHour < mFadeInStart)) + return 0.0f; + else if((gameHour >= mFadeInStart) && (gameHour < mFadeInFinish)) + return (gameHour - mFadeInStart) / (mFadeInFinish - mFadeInStart); + else + return 1.0f; } -} -void WeatherManager::playerTeleported(const std::string& playerRegion, bool isExterior) -{ - // If the player teleports to an outdoors cell in a new region (for instance, by travelling), the weather needs to - // be changed immediately, and any transitions for the previous region discarded. + inline float MoonModel::earlyMoonShadowAlpha(float angle) const { - std::map::iterator it = mRegions.find(playerRegion); - if(it != mRegions.end() && playerRegion != mCurrentRegion) - { - mCurrentRegion = playerRegion; - forceWeather(it->second.getWeather()); - } + // The Moon Shadow Early Fade Angle describes an arc relative to Fade End Angle. + // Depending on the current angle, the following values describe how transparent the moon is. + // 1. From Moon Shadow Early Fade Angle 1 to Fade End Angle 1 (during moon rise): 0..1 + // 2. From Fade End Angle 1 to Fade End Angle 2 (between moon rise and moon set): 1 (solid) + // 3. From Fade End Angle 2 to Moon Shadow Early Fade Angle 2 (during moon set): 1..0 + // 4. From Moon Shadow Early Fade Angle 2 to Moon Shadow Early Fade Angle 1: 0 (transparent) + float moonShadowEarlyFadeAngle1 = mFadeEndAngle - mMoonShadowEarlyFadeAngle; + float fadeEndAngle2 = 180.0f - mFadeEndAngle; + float moonShadowEarlyFadeAngle2 = fadeEndAngle2 + mMoonShadowEarlyFadeAngle; + if((angle >= moonShadowEarlyFadeAngle1) && (angle < mFadeEndAngle)) + return (angle - moonShadowEarlyFadeAngle1) / mMoonShadowEarlyFadeAngle; + else if((angle >= mFadeEndAngle) && (angle < fadeEndAngle2)) + return 1.0f; + else if((angle >= fadeEndAngle2) && (angle < moonShadowEarlyFadeAngle2)) + return (moonShadowEarlyFadeAngle2 - angle) / mMoonShadowEarlyFadeAngle; + else + return 0.0f; } -} -float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed) -{ - float targetSpeed = std::min(8.0f * mWeatherSettings[weatherId].mWindSpeed, 70.f); - if (currentSpeed == 0.f) - currentSpeed = targetSpeed; - - float multiplier = mWeatherSettings[weatherId].mRainEffect.empty() ? 1.f : 0.5f; - float updatedSpeed = (Misc::Rng::rollClosedProbability() - 0.5f) * multiplier * targetSpeed + currentSpeed; + WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, MWWorld::ESMStore& store) + : mStore(store) + , mRendering(rendering) + , mSunriseTime(Fallback::Map::getFloat("Weather_Sunrise_Time")) + , mSunsetTime(Fallback::Map::getFloat("Weather_Sunset_Time")) + , mSunriseDuration(Fallback::Map::getFloat("Weather_Sunrise_Duration")) + , mSunsetDuration(Fallback::Map::getFloat("Weather_Sunset_Duration")) + , mSunPreSunsetTime(Fallback::Map::getFloat("Weather_Sun_Pre-Sunset_Time")) + , mNightFade(0, 0, 0, 1) + , mHoursBetweenWeatherChanges(Fallback::Map::getFloat("Weather_Hours_Between_Weather_Changes")) + , mRainSpeed(Fallback::Map::getFloat("Weather_Precip_Gravity")) + , mUnderwaterFog(Fallback::Map::getFloat("Water_UnderwaterSunriseFog"), + Fallback::Map::getFloat("Water_UnderwaterDayFog"), + Fallback::Map::getFloat("Water_UnderwaterSunsetFog"), + Fallback::Map::getFloat("Water_UnderwaterNightFog")) + , mWeatherSettings() + , mMasser("Masser") + , mSecunda("Secunda") + , mWindSpeed(0.f) + , mCurrentWindSpeed(0.f) + , mNextWindSpeed(0.f) + , mIsStorm(false) + , mPrecipitation(false) + , mStormDirection(Weather::defaultDirection()) + , mCurrentRegion() + , mTimePassed(0) + , mFastForward(false) + , mWeatherUpdateTime(mHoursBetweenWeatherChanges) + , mTransitionFactor(0) + , mNightDayMode(Default) + , mCurrentWeather(0) + , mNextWeather(0) + , mQueuedWeather(0) + , mRegions() + , mResult() + , mAmbientSound(nullptr) + , mPlayingSoundID() + { + mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; + mTimeSettings.mNightEnd = mSunriseTime; + mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; + mTimeSettings.mDayEnd = mSunsetTime; + + mTimeSettings.addSetting("Sky"); + mTimeSettings.addSetting("Ambient"); + mTimeSettings.addSetting("Fog"); + mTimeSettings.addSetting("Sun"); + + // Morrowind handles stars settings differently for other ones + mTimeSettings.mStarsPostSunsetStart = Fallback::Map::getFloat("Weather_Stars_Post-Sunset_Start"); + mTimeSettings.mStarsPreSunriseFinish = Fallback::Map::getFloat("Weather_Stars_Pre-Sunrise_Finish"); + mTimeSettings.mStarsFadingDuration = Fallback::Map::getFloat("Weather_Stars_Fading_Duration"); + + WeatherSetting starSetting = { + mTimeSettings.mStarsPreSunriseFinish, + mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish, + mTimeSettings.mStarsPostSunsetStart, + mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart + }; - if (updatedSpeed > 0.5f * targetSpeed && updatedSpeed < 2.f * targetSpeed) - currentSpeed = updatedSpeed; + mTimeSettings.mSunriseTransitions["Stars"] = starSetting; + + mWeatherSettings.reserve(10); + // These distant land fog factor and offset values are the defaults MGE XE provides. Should be + // provided by settings somewhere? + addWeather("Clear", 1.0f, 0.0f); // 0 + addWeather("Cloudy", 0.9f, 0.0f); // 1 + addWeather("Foggy", 0.2f, 30.0f); // 2 + 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 + + Store::iterator it = store.get().begin(); + for(; it != store.get().end(); ++it) + { + std::string regionID = Misc::StringUtils::lowerCase(it->mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); + } - return currentSpeed; -} + forceWeather(0); + } -void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior) -{ - MWWorld::ConstPtr player = MWMechanics::getPlayer(); + WeatherManager::~WeatherManager() + { + stopSounds(); + } - if(!paused || mFastForward) + void WeatherManager::changeWeather(const std::string& regionID, const unsigned int weatherID) { - // Add new transitions when either the player's current external region changes. - std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); - if(updateWeatherTime() || updateWeatherRegion(playerRegion)) + // In Morrowind, this seems to have the following behavior, when applied to the current region: + // - When there is no transition in progress, start transitioning to the new weather. + // - If there is a transition in progress, queue up the transition and process it when the current one completes. + // - If there is a transition in progress, and a queued transition, overwrite the queued transition. + // - If multiple calls to ChangeWeather are made while paused (console up), only the last call will be used, + // meaning that if there was no transition in progress, only the last ChangeWeather will be processed. + // If the region isn't current, Morrowind will store the new weather for the region in question. + + if(weatherID < mWeatherSettings.size()) { - std::map::iterator it = mRegions.find(mCurrentRegion); + std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); + std::map::iterator it = mRegions.find(lowerCaseRegionID); if(it != mRegions.end()) { - addWeatherTransition(it->second.getWeather()); + it->second.setWeather(weatherID); + regionalWeatherChanged(it->first, it->second); } } - - updateWeatherTransitions(duration); } - bool isDay = time.getHour() >= mSunriseTime && time.getHour() <= mTimeSettings.mNightStart; - if (isExterior && !isDay) - mNightDayMode = ExteriorNight; - else if (!isExterior && isDay && mWeatherSettings[mCurrentWeather].mGlareView >= 0.5f) - mNightDayMode = InteriorDay; - else - mNightDayMode = Default; - - if(!isExterior) + void WeatherManager::modRegion(const std::string& regionID, const std::vector& chances) { - mRendering.setSkyEnabled(false); - stopSounds(); - mWindSpeed = 0.f; - mCurrentWindSpeed = 0.f; - mNextWindSpeed = 0.f; - return; - } + // Sets the region's probability for various weather patterns. Note that this appears to be saved permanently. + // In Morrowind, this seems to have the following behavior when applied to the current region: + // - If the region supports the current weather, no change in current weather occurs. + // - If the region no longer supports the current weather, and there is no transition in progress, begin to + // transition to a new supported weather type. + // - If the region no longer supports the current weather, and there is a transition in progress, queue a + // transition to a new supported weather type. - calculateWeatherResult(time.getHour(), duration, paused); + std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); + std::map::iterator it = mRegions.find(lowerCaseRegionID); + if(it != mRegions.end()) + { + it->second.setChances(chances); + regionalWeatherChanged(it->first, it->second); + } + } - if (!paused) + void WeatherManager::playerTeleported(const std::string& playerRegion, bool isExterior) { - mWindSpeed = mResult.mWindSpeed; - mCurrentWindSpeed = mResult.mCurrentWindSpeed; - mNextWindSpeed = mResult.mNextWindSpeed; + // If the player teleports to an outdoors cell in a new region (for instance, by travelling), the weather needs to + // be changed immediately, and any transitions for the previous region discarded. + { + std::map::iterator it = mRegions.find(playerRegion); + if(it != mRegions.end() && playerRegion != mCurrentRegion) + { + mCurrentRegion = playerRegion; + forceWeather(it->second.getWeather()); + } + } } - mIsStorm = mResult.mIsStorm; + float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed) + { + float targetSpeed = std::min(8.0f * mWeatherSettings[weatherId].mWindSpeed, 70.f); + if (currentSpeed == 0.f) + currentSpeed = targetSpeed; + + float multiplier = mWeatherSettings[weatherId].mRainEffect.empty() ? 1.f : 0.5f; + float updatedSpeed = (Misc::Rng::rollClosedProbability() - 0.5f) * multiplier * targetSpeed + currentSpeed; - // 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"; + if (updatedSpeed > 0.5f * targetSpeed && updatedSpeed < 2.f * targetSpeed) + currentSpeed = updatedSpeed; - if (mIsStorm) + return currentSpeed; + } + + void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior) { - osg::Vec3f stormDirection(0, 1, 0); - if (mResult.mParticleEffect == "meshes\\ashcloud.nif" || mResult.mParticleEffect == "meshes\\blightcloud.nif") + MWWorld::ConstPtr player = MWMechanics::getPlayer(); + + if(!paused || mFastForward) { - osg::Vec3f playerPos (MWMechanics::getPlayer().getRefData().getPosition().asVec3()); - playerPos.z() = 0; - osg::Vec3f redMountainPos (25000, 70000, 0); - stormDirection = (playerPos - redMountainPos); - stormDirection.normalize(); + // Add new transitions when either the player's current external region changes. + std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); + if(updateWeatherTime() || updateWeatherRegion(playerRegion)) + { + std::map::iterator it = mRegions.find(mCurrentRegion); + if(it != mRegions.end()) + { + addWeatherTransition(it->second.getWeather()); + } + } + + updateWeatherTransitions(duration); } - mStormDirection = stormDirection; - mRendering.getSkyManager()->setStormDirection(mStormDirection); - } - // disable sun during night - if (time.getHour() >= mTimeSettings.mNightStart || time.getHour() <= mSunriseTime) - mRendering.getSkyManager()->sunDisable(); - else - mRendering.getSkyManager()->sunEnable(); + bool isDay = time.getHour() >= mSunriseTime && time.getHour() <= mTimeSettings.mNightStart; + if (isExterior && !isDay) + mNightDayMode = ExteriorNight; + else if (!isExterior && isDay && mWeatherSettings[mCurrentWeather].mGlareView >= 0.5f) + mNightDayMode = InteriorDay; + else + mNightDayMode = Default; - // Update the sun direction. Run it east to west at a fixed angle from overhead. - // The sun's speed at day and night may differ, since mSunriseTime and mNightStart - // mark when the sun is level with the horizon. - { - // Shift times into a 24-hour window beginning at mSunriseTime... - float adjustedHour = time.getHour(); - float adjustedNightStart = mTimeSettings.mNightStart; - if ( time.getHour() < mSunriseTime ) - adjustedHour += 24.f; - if ( mTimeSettings.mNightStart < mSunriseTime ) - adjustedNightStart += 24.f; - - const bool is_night = adjustedHour >= adjustedNightStart; - const float dayDuration = adjustedNightStart - mSunriseTime; - const float nightDuration = 24.f - dayDuration; - - double theta; - if ( !is_night ) + if(!isExterior) { - theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; + mRendering.setSkyEnabled(false); + stopSounds(); + mWindSpeed = 0.f; + mCurrentWindSpeed = 0.f; + mNextWindSpeed = 0.f; + return; } - else + + calculateWeatherResult(time.getHour(), duration, paused); + + if (!paused) { - theta = static_cast(osg::PI) - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; + mWindSpeed = mResult.mWindSpeed; + mCurrentWindSpeed = mResult.mCurrentWindSpeed; + mNextWindSpeed = mResult.mNextWindSpeed; } - osg::Vec3f final( - static_cast(cos(theta)), - -0.268f, // approx tan( -15 degrees ) - static_cast(sin(theta))); - mRendering.setSunDirection( final * -1 ); - } + mIsStorm = mResult.mIsStorm; - float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); + // 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"; - float peakHour = mSunriseTime + (mTimeSettings.mNightStart - mSunriseTime) / 2; - float glareFade = 1.f; - if (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart) - glareFade = 0.f; - else if (time.getHour() < peakHour) - glareFade = 1.f - (peakHour - time.getHour()) / (peakHour - mSunriseTime); - else - glareFade = 1.f - (time.getHour() - peakHour) / (mTimeSettings.mNightStart - peakHour); + mStormDirection = calculateStormDirection(mResult.mParticleEffect); + mRendering.getSkyManager()->setStormParticleDirection(mStormDirection); - mRendering.getSkyManager()->setGlareTimeOfDayFade(glareFade); + // disable sun during night + if (time.getHour() >= mTimeSettings.mNightStart || time.getHour() <= mSunriseTime) + mRendering.getSkyManager()->sunDisable(); + else + mRendering.getSkyManager()->sunEnable(); - mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); - mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); + // Update the sun direction. Run it east to west at a fixed angle from overhead. + // The sun's speed at day and night may differ, since mSunriseTime and mNightStart + // mark when the sun is level with the horizon. + { + // Shift times into a 24-hour window beginning at mSunriseTime... + float adjustedHour = time.getHour(); + float adjustedNightStart = mTimeSettings.mNightStart; + if ( time.getHour() < mSunriseTime ) + adjustedHour += 24.f; + if ( mTimeSettings.mNightStart < mSunriseTime ) + adjustedNightStart += 24.f; + + const bool is_night = adjustedHour >= adjustedNightStart; + const float dayDuration = adjustedNightStart - mSunriseTime; + const float nightDuration = 24.f - dayDuration; + + double theta; + if ( !is_night ) + { + theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; + } + else + { + theta = static_cast(osg::PI) - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; + } - mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, - mResult.mDLFogOffset/100.0f, mResult.mFogColor); - mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade); + osg::Vec3f final( + static_cast(cos(theta)), + -0.268f, // approx tan( -15 degrees ) + static_cast(sin(theta))); + mRendering.setSunDirection( final * -1 ); + } - mRendering.getSkyManager()->setWeather(mResult); + float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); - // Play sounds - if (mPlayingSoundID != mResult.mAmbientLoopSoundID) - { - stopSounds(); - if (!mResult.mAmbientLoopSoundID.empty()) - mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound( - mResult.mAmbientLoopSoundID, mResult.mAmbientSoundVolume, 1.0, - MWSound::Type::Sfx, MWSound::PlayMode::Loop - ); - mPlayingSoundID = mResult.mAmbientLoopSoundID; - } - else if (mAmbientSound) - mAmbientSound->setVolume(mResult.mAmbientSoundVolume); -} + float peakHour = mSunriseTime + (mTimeSettings.mNightStart - mSunriseTime) / 2; + float glareFade = 1.f; + if (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart) + glareFade = 0.f; + else if (time.getHour() < peakHour) + glareFade = 1.f - (peakHour - time.getHour()) / (peakHour - mSunriseTime); + else + glareFade = 1.f - (time.getHour() - peakHour) / (mTimeSettings.mNightStart - peakHour); -void WeatherManager::stopSounds() -{ - if (mAmbientSound) - MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); - mAmbientSound = nullptr; - mPlayingSoundID.clear(); -} + mRendering.getSkyManager()->setGlareTimeOfDayFade(glareFade); -float WeatherManager::getWindSpeed() const -{ - return mWindSpeed; -} + mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); + mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); -bool WeatherManager::isInStorm() const -{ - return mIsStorm; -} + mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, + mResult.mDLFogOffset/100.0f, mResult.mFogColor); + mRendering.setAmbientColour(mResult.mAmbientColor); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade); -osg::Vec3f WeatherManager::getStormDirection() const -{ - return mStormDirection; -} + mRendering.getSkyManager()->setWeather(mResult); -void WeatherManager::advanceTime(double hours, bool incremental) -{ - // In Morrowind, when the player sleeps/waits, serves jail time, travels, or trains, all weather transitions are - // immediately applied, regardless of whatever transition time might have been remaining. - mTimePassed += hours; - mFastForward = !incremental ? true : mFastForward; -} + // Play sounds + if (mPlayingSoundID != mResult.mAmbientLoopSoundID) + { + stopSounds(); + if (!mResult.mAmbientLoopSoundID.empty()) + mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound( + mResult.mAmbientLoopSoundID, mResult.mAmbientSoundVolume, 1.0, + MWSound::Type::Sfx, MWSound::PlayMode::Loop + ); + mPlayingSoundID = mResult.mAmbientLoopSoundID; + } + else if (mAmbientSound) + mAmbientSound->setVolume(mResult.mAmbientSoundVolume); + } -unsigned int WeatherManager::getWeatherID() const -{ - return mCurrentWeather; -} + void WeatherManager::stopSounds() + { + if (mAmbientSound) + MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + mAmbientSound = nullptr; + mPlayingSoundID.clear(); + } -NightDayMode WeatherManager::getNightDayMode() const -{ - return mNightDayMode; -} + float WeatherManager::getWindSpeed() const + { + return mWindSpeed; + } -bool WeatherManager::useTorches(float hour) const -{ - bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; + bool WeatherManager::isInStorm() const + { + return mIsStorm; + } - return isDark && !mPrecipitation; -} + osg::Vec3f WeatherManager::getStormDirection() const + { + return mStormDirection; + } -void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) -{ - ESM::WeatherState state; - state.mCurrentRegion = mCurrentRegion; - state.mTimePassed = mTimePassed; - state.mFastForward = mFastForward; - state.mWeatherUpdateTime = mWeatherUpdateTime; - state.mTransitionFactor = mTransitionFactor; - state.mCurrentWeather = mCurrentWeather; - state.mNextWeather = mNextWeather; - state.mQueuedWeather = mQueuedWeather; - - std::map::iterator it = mRegions.begin(); - for(; it != mRegions.end(); ++it) + void WeatherManager::advanceTime(double hours, bool incremental) { - state.mRegions.insert(std::make_pair(it->first, it->second)); + // In Morrowind, when the player sleeps/waits, serves jail time, travels, or trains, all weather transitions are + // immediately applied, regardless of whatever transition time might have been remaining. + mTimePassed += hours; + mFastForward = !incremental ? true : mFastForward; } - writer.startRecord(ESM::REC_WTHR); - state.save(writer); - writer.endRecord(ESM::REC_WTHR); -} + unsigned int WeatherManager::getWeatherID() const + { + return mCurrentWeather; + } -bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type) -{ - if(ESM::REC_WTHR == type) + NightDayMode WeatherManager::getNightDayMode() const { - static const int oldestCompatibleSaveFormat = 2; - if(reader.getFormat() < oldestCompatibleSaveFormat) + return mNightDayMode; + } + + bool WeatherManager::useTorches(float hour) const + { + bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; + + return isDark && !mPrecipitation; + } + + void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) + { + ESM::WeatherState state; + state.mCurrentRegion = mCurrentRegion; + state.mTimePassed = mTimePassed; + state.mFastForward = mFastForward; + state.mWeatherUpdateTime = mWeatherUpdateTime; + state.mTransitionFactor = mTransitionFactor; + state.mCurrentWeather = mCurrentWeather; + state.mNextWeather = mNextWeather; + state.mQueuedWeather = mQueuedWeather; + + std::map::iterator it = mRegions.begin(); + for(; it != mRegions.end(); ++it) { - // Weather state isn't really all that important, so to preserve older save games, we'll just discard the - // older weather records, rather than fail to handle the record. - reader.skipRecord(); + state.mRegions.insert(std::make_pair(it->first, it->second)); } - else + + writer.startRecord(ESM::REC_WTHR); + state.save(writer); + writer.endRecord(ESM::REC_WTHR); + } + + bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type) + { + if(ESM::REC_WTHR == type) { - ESM::WeatherState state; - state.load(reader); - - mCurrentRegion.swap(state.mCurrentRegion); - mTimePassed = state.mTimePassed; - mFastForward = state.mFastForward; - mWeatherUpdateTime = state.mWeatherUpdateTime; - mTransitionFactor = state.mTransitionFactor; - mCurrentWeather = state.mCurrentWeather; - mNextWeather = state.mNextWeather; - mQueuedWeather = state.mQueuedWeather; - - mRegions.clear(); - importRegions(); - - for(std::map::iterator it = state.mRegions.begin(); it != state.mRegions.end(); ++it) + static const int oldestCompatibleSaveFormat = 2; + if(reader.getFormat() < oldestCompatibleSaveFormat) + { + // Weather state isn't really all that important, so to preserve older save games, we'll just discard the + // older weather records, rather than fail to handle the record. + reader.skipRecord(); + } + else { - std::map::iterator found = mRegions.find(it->first); - if (found != mRegions.end()) + ESM::WeatherState state; + state.load(reader); + + mCurrentRegion.swap(state.mCurrentRegion); + mTimePassed = state.mTimePassed; + mFastForward = state.mFastForward; + mWeatherUpdateTime = state.mWeatherUpdateTime; + mTransitionFactor = state.mTransitionFactor; + mCurrentWeather = state.mCurrentWeather; + mNextWeather = state.mNextWeather; + mQueuedWeather = state.mQueuedWeather; + + mRegions.clear(); + importRegions(); + + for(std::map::iterator it = state.mRegions.begin(); it != state.mRegions.end(); ++it) { - found->second = RegionWeather(it->second); + std::map::iterator found = mRegions.find(it->first); + if (found != mRegions.end()) + { + found->second = RegionWeather(it->second); + } } } + + return true; } - return true; + return false; } - return false; -} + void WeatherManager::clear() + { + stopSounds(); -void WeatherManager::clear() -{ - stopSounds(); - - mCurrentRegion = ""; - mTimePassed = 0.0f; - mWeatherUpdateTime = 0.0f; - forceWeather(0); - mRegions.clear(); - importRegions(); -} + mCurrentRegion = ""; + mTimePassed = 0.0f; + mWeatherUpdateTime = 0.0f; + forceWeather(0); + mRegions.clear(); + importRegions(); + } -inline void WeatherManager::addWeather(const std::string& name, - float dlFactor, float dlOffset, - const std::string& particleEffect) -{ - static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->mValue.getFloat(); + inline void WeatherManager::addWeather(const std::string& name, + float dlFactor, float dlOffset, + const std::string& particleEffect) + { + static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->mValue.getFloat(); - Weather weather(name, fStromWindSpeed, mRainSpeed, dlFactor, dlOffset, particleEffect); + Weather weather(name, fStromWindSpeed, mRainSpeed, dlFactor, dlOffset, particleEffect); - mWeatherSettings.push_back(weather); -} + mWeatherSettings.push_back(weather); + } -inline void WeatherManager::importRegions() -{ - for(const ESM::Region& region : mStore.get()) + inline void WeatherManager::importRegions() { - std::string regionID = Misc::StringUtils::lowerCase(region.mId); - mRegions.insert(std::make_pair(regionID, RegionWeather(region))); + for(const ESM::Region& region : mStore.get()) + { + std::string regionID = Misc::StringUtils::lowerCase(region.mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(region))); + } } -} -inline void WeatherManager::regionalWeatherChanged(const std::string& regionID, RegionWeather& region) -{ - // If the region is current, then add a weather transition for it. - MWWorld::ConstPtr player = MWMechanics::getPlayer(); - if(player.isInCell()) + inline void WeatherManager::regionalWeatherChanged(const std::string& regionID, RegionWeather& region) { - if(Misc::StringUtils::ciEqual(regionID, mCurrentRegion)) + // If the region is current, then add a weather transition for it. + MWWorld::ConstPtr player = MWMechanics::getPlayer(); + if(player.isInCell()) { - addWeatherTransition(region.getWeather()); + if(Misc::StringUtils::ciEqual(regionID, mCurrentRegion)) + { + addWeatherTransition(region.getWeather()); + } } } -} -inline bool WeatherManager::updateWeatherTime() -{ - mWeatherUpdateTime -= mTimePassed; - mTimePassed = 0.0f; - if(mWeatherUpdateTime <= 0.0f) + inline bool WeatherManager::updateWeatherTime() { - // Expire all regional weather, so that any call to getWeather() will return a new weather ID. - std::map::iterator it = mRegions.begin(); - for(; it != mRegions.end(); ++it) + mWeatherUpdateTime -= mTimePassed; + mTimePassed = 0.0f; + if(mWeatherUpdateTime <= 0.0f) { - it->second.setWeather(invalidWeatherID); - } + // Expire all regional weather, so that any call to getWeather() will return a new weather ID. + std::map::iterator it = mRegions.begin(); + for(; it != mRegions.end(); ++it) + { + it->second.setWeather(invalidWeatherID); + } - mWeatherUpdateTime += mHoursBetweenWeatherChanges; + mWeatherUpdateTime += mHoursBetweenWeatherChanges; - return true; - } + return true; + } - return false; -} + return false; + } -inline bool WeatherManager::updateWeatherRegion(const std::string& playerRegion) -{ - if(!playerRegion.empty() && playerRegion != mCurrentRegion) + inline bool WeatherManager::updateWeatherRegion(const std::string& playerRegion) { - mCurrentRegion = playerRegion; + if(!playerRegion.empty() && playerRegion != mCurrentRegion) + { + mCurrentRegion = playerRegion; - return true; - } + return true; + } - return false; -} + return false; + } -inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeconds) -{ - // When a player chooses to train, wait, or serves jail time, any transitions will be fast forwarded to the last - // weather type set, regardless of the remaining transition time. - if(!mFastForward && inTransition()) + inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeconds) { - const float delta = mWeatherSettings[mNextWeather].transitionDelta(); - mTransitionFactor -= elapsedRealSeconds * delta; - if(mTransitionFactor <= 0.0f) + // When a player chooses to train, wait, or serves jail time, any transitions will be fast forwarded to the last + // weather type set, regardless of the remaining transition time. + if(!mFastForward && inTransition()) { - mCurrentWeather = mNextWeather; - mNextWeather = mQueuedWeather; - mQueuedWeather = invalidWeatherID; + const float delta = mWeatherSettings[mNextWeather].transitionDelta(); + mTransitionFactor -= elapsedRealSeconds * delta; + if(mTransitionFactor <= 0.0f) + { + mCurrentWeather = mNextWeather; + mNextWeather = mQueuedWeather; + mQueuedWeather = invalidWeatherID; - // We may have begun processing the queued transition, so we need to apply the remaining time towards it. - if(inTransition()) + // We may have begun processing the queued transition, so we need to apply the remaining time towards it. + if(inTransition()) + { + const float newDelta = mWeatherSettings[mNextWeather].transitionDelta(); + const float remainingSeconds = -(mTransitionFactor / delta); + mTransitionFactor = 1.0f - (remainingSeconds * newDelta); + } + else + { + mTransitionFactor = 0.0f; + } + } + } + else + { + if(mQueuedWeather != invalidWeatherID) { - const float newDelta = mWeatherSettings[mNextWeather].transitionDelta(); - const float remainingSeconds = -(mTransitionFactor / delta); - mTransitionFactor = 1.0f - (remainingSeconds * newDelta); + mCurrentWeather = mQueuedWeather; } - else + else if(mNextWeather != invalidWeatherID) { - mTransitionFactor = 0.0f; + mCurrentWeather = mNextWeather; } + + mNextWeather = invalidWeatherID; + mQueuedWeather = invalidWeatherID; + mFastForward = false; } } - else - { - if(mQueuedWeather != invalidWeatherID) - { - mCurrentWeather = mQueuedWeather; - } - else if(mNextWeather != invalidWeatherID) - { - mCurrentWeather = mNextWeather; - } + inline void WeatherManager::forceWeather(const int weatherID) + { + mTransitionFactor = 0.0f; + mCurrentWeather = weatherID; mNextWeather = invalidWeatherID; mQueuedWeather = invalidWeatherID; - mFastForward = false; } -} - -inline void WeatherManager::forceWeather(const int weatherID) -{ - mTransitionFactor = 0.0f; - mCurrentWeather = weatherID; - mNextWeather = invalidWeatherID; - mQueuedWeather = invalidWeatherID; -} -inline bool WeatherManager::inTransition() -{ - return mNextWeather != invalidWeatherID; -} - -inline void WeatherManager::addWeatherTransition(const int weatherID) -{ - // In order to work like ChangeWeather expects, this method begins transitioning to the new weather immediately if - // no transition is in progress, otherwise it queues it to be transitioned. - - assert(weatherID >= 0 && static_cast(weatherID) < mWeatherSettings.size()); - - if(!inTransition() && (weatherID != mCurrentWeather)) + inline bool WeatherManager::inTransition() { - mNextWeather = weatherID; - mTransitionFactor = 1.0f; + return mNextWeather != invalidWeatherID; } - else if(inTransition() && (weatherID != mNextWeather)) - { - mQueuedWeather = weatherID; - } -} -inline void WeatherManager::calculateWeatherResult(const float gameHour, - const float elapsedSeconds, - const bool isPaused) -{ - float flash = 0.0f; - if(!inTransition()) - { - calculateResult(mCurrentWeather, gameHour); - flash = mWeatherSettings[mCurrentWeather].calculateThunder(1.0f, elapsedSeconds, isPaused); - } - else + inline void WeatherManager::addWeatherTransition(const int weatherID) { - calculateTransitionResult(1 - mTransitionFactor, gameHour); - float currentFlash = mWeatherSettings[mCurrentWeather].calculateThunder(mTransitionFactor, - elapsedSeconds, - isPaused); - float nextFlash = mWeatherSettings[mNextWeather].calculateThunder(1 - mTransitionFactor, - elapsedSeconds, - isPaused); - flash = currentFlash + nextFlash; - } - osg::Vec4f flashColor(flash, flash, flash, 0.0f); + // In order to work like ChangeWeather expects, this method begins transitioning to the new weather immediately if + // no transition is in progress, otherwise it queues it to be transitioned. - mResult.mFogColor += flashColor; - mResult.mAmbientColor += flashColor; - mResult.mSunColor += flashColor; -} + assert(weatherID >= 0 && static_cast(weatherID) < mWeatherSettings.size()); -inline void WeatherManager::calculateResult(const int weatherID, const float gameHour) -{ - const Weather& current = mWeatherSettings[weatherID]; - - mResult.mCloudTexture = current.mCloudTexture; - mResult.mCloudBlendFactor = 0; - mResult.mNextWindSpeed = 0; - mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed); - mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed; - - mResult.mCloudSpeed = current.mCloudSpeed; - mResult.mGlareView = current.mGlareView; - mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; - mResult.mAmbientSoundVolume = 1.f; - mResult.mPrecipitationAlpha = 1.f; - - mResult.mIsStorm = current.mIsStorm; - - mResult.mRainSpeed = current.mRainSpeed; - mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; - mResult.mRainDiameter = current.mRainDiameter; - mResult.mRainMinHeight = current.mRainMinHeight; - mResult.mRainMaxHeight = current.mRainMaxHeight; - mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; - - mResult.mParticleEffect = current.mParticleEffect; - mResult.mRainEffect = current.mRainEffect; - - mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration); - - mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog"); - mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog"); - mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient"); - mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun"); - mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky"); - mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars"); - mResult.mDLFogFactor = current.mDL.FogFactor; - mResult.mDLFogOffset = current.mDL.FogOffset; - - WeatherSetting setting = mTimeSettings.getSetting("Sun"); - float preSunsetTime = setting.mPreSunsetTime; - - if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime) - { - float factor = 1.f; - if (preSunsetTime > 0) - factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime; - factor = std::min(1.f, factor); - mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); - // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because - // MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline - // would then clamp the total lighting to (1,1,1). A noticeable change in color tone can be observed when only one of the color components gets clamped. - // Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense. - mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor); - for (int i=0; i<3; ++i) - mResult.mSunDiscColor[i] = std::min(1.f, mResult.mSunDiscColor[i]); + if(!inTransition() && (weatherID != mCurrentWeather)) + { + mNextWeather = weatherID; + mTransitionFactor = 1.0f; + } + else if(inTransition() && (weatherID != mNextWeather)) + { + mQueuedWeather = weatherID; + } } - else - mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); - if (gameHour >= mTimeSettings.mDayEnd) + inline void WeatherManager::calculateWeatherResult(const float gameHour, + const float elapsedSeconds, + const bool isPaused) { - // sunset - float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd)); - fade = fade*fade; - mResult.mSunDiscColor.a() = 1.f - fade; - } - else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f) - { - // sunrise - mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd; - } - else - mResult.mSunDiscColor.a() = 1; - -} - -inline void WeatherManager::calculateTransitionResult(const float factor, const float gameHour) -{ - calculateResult(mCurrentWeather, gameHour); - const MWRender::WeatherResult current = mResult; - calculateResult(mNextWeather, gameHour); - const MWRender::WeatherResult other = mResult; - - mResult.mCloudTexture = current.mCloudTexture; - mResult.mNextCloudTexture = other.mCloudTexture; - mResult.mCloudBlendFactor = mWeatherSettings[mNextWeather].cloudBlendFactor(factor); - - mResult.mFogColor = lerp(current.mFogColor, other.mFogColor, factor); - mResult.mSunColor = lerp(current.mSunColor, other.mSunColor, factor); - mResult.mSkyColor = lerp(current.mSkyColor, other.mSkyColor, factor); - - mResult.mAmbientColor = lerp(current.mAmbientColor, other.mAmbientColor, factor); - mResult.mSunDiscColor = lerp(current.mSunDiscColor, other.mSunDiscColor, factor); - mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor); - mResult.mDLFogFactor = lerp(current.mDLFogFactor, other.mDLFogFactor, factor); - mResult.mDLFogOffset = lerp(current.mDLFogOffset, other.mDLFogOffset, factor); + float flash = 0.0f; + if(!inTransition()) + { + calculateResult(mCurrentWeather, gameHour); + flash = mWeatherSettings[mCurrentWeather].calculateThunder(1.0f, elapsedSeconds, isPaused); + } + else + { + calculateTransitionResult(1 - mTransitionFactor, gameHour); + float currentFlash = mWeatherSettings[mCurrentWeather].calculateThunder(mTransitionFactor, + elapsedSeconds, + isPaused); + float nextFlash = mWeatherSettings[mNextWeather].calculateThunder(1 - mTransitionFactor, + elapsedSeconds, + isPaused); + flash = currentFlash + nextFlash; + } + osg::Vec4f flashColor(flash, flash, flash, 0.0f); - mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed); - mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed); - mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor); + mResult.mFogColor += flashColor; + mResult.mAmbientColor += flashColor; + mResult.mSunColor += flashColor; + } - mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, factor); - mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); - mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor); - mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor); + inline void WeatherManager::calculateResult(const int weatherID, const float gameHour) + { + const Weather& current = mWeatherSettings[weatherID]; - mResult.mNight = current.mNight; + mResult.mCloudTexture = current.mCloudTexture; + mResult.mCloudBlendFactor = 0; + mResult.mNextWindSpeed = 0; + mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed); + mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed; - float threshold = mWeatherSettings[mNextWeather].mRainThreshold; - if (threshold <= 0) - threshold = 0.5f; + mResult.mCloudSpeed = current.mCloudSpeed; + mResult.mGlareView = current.mGlareView; + mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mAmbientSoundVolume = 1.f; + mResult.mPrecipitationAlpha = 1.f; - if(factor < threshold) - { mResult.mIsStorm = current.mIsStorm; - mResult.mParticleEffect = current.mParticleEffect; - mResult.mRainEffect = current.mRainEffect; + mResult.mRainSpeed = current.mRainSpeed; mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; - mResult.mAmbientSoundVolume = 1 - factor / threshold; - mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; - mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mRainDiameter = current.mRainDiameter; mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; + + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + + mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration); + + mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog"); + mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog"); + mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient"); + mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun"); + mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky"); + mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars"); + mResult.mDLFogFactor = current.mDL.FogFactor; + mResult.mDLFogOffset = current.mDL.FogOffset; + + WeatherSetting setting = mTimeSettings.getSetting("Sun"); + float preSunsetTime = setting.mPreSunsetTime; + + if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime) + { + float factor = 1.f; + if (preSunsetTime > 0) + factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime; + factor = std::min(1.f, factor); + mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); + // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because + // MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline + // would then clamp the total lighting to (1,1,1). A noticeable change in color tone can be observed when only one of the color components gets clamped. + // Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense. + mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor); + for (int i=0; i<3; ++i) + mResult.mSunDiscColor[i] = std::min(1.f, mResult.mSunDiscColor[i]); + } + else + mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); + + if (gameHour >= mTimeSettings.mDayEnd) + { + // sunset + float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd)); + fade = fade*fade; + mResult.mSunDiscColor.a() = 1.f - fade; + } + else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f) + { + // sunrise + mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd; + } + else + mResult.mSunDiscColor.a() = 1; + + mResult.mStormDirection = calculateStormDirection(mResult.mParticleEffect); } - else + + inline void WeatherManager::calculateTransitionResult(const float factor, const float gameHour) { - mResult.mIsStorm = other.mIsStorm; - mResult.mParticleEffect = other.mParticleEffect; - mResult.mRainEffect = other.mRainEffect; - mResult.mRainSpeed = other.mRainSpeed; - mResult.mRainEntranceSpeed = other.mRainEntranceSpeed; - mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); - mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; - mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; - - mResult.mRainDiameter = other.mRainDiameter; - mResult.mRainMinHeight = other.mRainMinHeight; - mResult.mRainMaxHeight = other.mRainMaxHeight; - mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; + calculateResult(mCurrentWeather, gameHour); + const MWRender::WeatherResult current = mResult; + calculateResult(mNextWeather, gameHour); + const MWRender::WeatherResult other = mResult; + + mResult.mStormDirection = current.mStormDirection; + mResult.mNextStormDirection = other.mStormDirection; + + mResult.mCloudTexture = current.mCloudTexture; + mResult.mNextCloudTexture = other.mCloudTexture; + mResult.mCloudBlendFactor = mWeatherSettings[mNextWeather].cloudBlendFactor(factor); + + mResult.mFogColor = lerp(current.mFogColor, other.mFogColor, factor); + mResult.mSunColor = lerp(current.mSunColor, other.mSunColor, factor); + mResult.mSkyColor = lerp(current.mSkyColor, other.mSkyColor, factor); + + mResult.mAmbientColor = lerp(current.mAmbientColor, other.mAmbientColor, factor); + mResult.mSunDiscColor = lerp(current.mSunDiscColor, other.mSunDiscColor, factor); + mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor); + mResult.mDLFogFactor = lerp(current.mDLFogFactor, other.mDLFogFactor, factor); + mResult.mDLFogOffset = lerp(current.mDLFogOffset, other.mDLFogOffset, factor); + + mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed); + mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed); + mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor); + + mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, factor); + mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); + mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor); + mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor); + + mResult.mNight = current.mNight; + + float threshold = mWeatherSettings[mNextWeather].mRainThreshold; + if (threshold <= 0.f) + threshold = 0.5f; + + if(factor < threshold) + { + mResult.mIsStorm = current.mIsStorm; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + mResult.mRainSpeed = current.mRainSpeed; + mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; + mResult.mAmbientSoundVolume = 1.f - factor / threshold; + mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; + mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mRainDiameter = current.mRainDiameter; + mResult.mRainMinHeight = current.mRainMinHeight; + mResult.mRainMaxHeight = current.mRainMaxHeight; + mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; + } + else + { + mResult.mIsStorm = other.mIsStorm; + mResult.mParticleEffect = other.mParticleEffect; + mResult.mRainEffect = other.mRainEffect; + mResult.mRainSpeed = other.mRainSpeed; + mResult.mRainEntranceSpeed = other.mRainEntranceSpeed; + mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); + mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; + mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; + + mResult.mRainDiameter = other.mRainDiameter; + mResult.mRainMinHeight = other.mRainMinHeight; + mResult.mRainMaxHeight = other.mRainMaxHeight; + mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; + } } } + diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index a3928465c4..21b2fae9f8 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -115,6 +115,8 @@ namespace MWWorld class Weather { public: + static osg::Vec3f defaultDirection(); + Weather(const std::string& name, float stormWindSpeed, float rainSpeed, @@ -189,6 +191,8 @@ namespace MWWorld std::string mRainEffect; + osg::Vec3f mStormDirection; + // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature // is broken in the vanilla game and was disabled. diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 6709ee842e..dcd4874e90 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -516,6 +516,10 @@ namespace Shader // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } + bool simpleLighting = false; + node.getUserValue("simpleLighting", simpleLighting); + defineMap["simpleLighting"] = simpleLighting ? "1" : "0"; + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); // This disables the deprecated fixed-function alpha test diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 04446d2982..d86719f318 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -36,6 +36,9 @@ set(SHADER_FILES gui_fragment.glsl debug_vertex.glsl debug_fragment.glsl + sky_vertex.glsl + sky_fragment.glsl + skypasses.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 177017f822..dc9c297a2f 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -87,6 +87,16 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a } } +// Simplest lighting which only takes into account sun and ambient. Currently used for our weather particle systems. +void doSimpleLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight) +{ + vec3 ambientOut, diffuseOut; + + perLightSun(diffuseOut, viewPos, viewNormal); + ambientLight = gl_LightModel.ambient.xyz; + diffuseLight = diffuseOut; +} + vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) { vec3 lightDir = normalize(lcalcPosition(0)); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 6f6cede4e4..26107abb10 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -63,7 +63,7 @@ uniform bool simpleWater; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING ((@normalMap || @forcePPL) && !@simpleLighting) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; @@ -170,6 +170,9 @@ void main() #endif float shadowing = unshadowedLightRatio(linearDepth); +#if @simpleLighting + gl_FragData[0].xyz *= passLighting; +#else vec3 lighting; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; @@ -180,8 +183,8 @@ void main() lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; clampLightingResult(lighting); #endif - gl_FragData[0].xyz *= lighting; +#endif #if @envMap && !@preLightEnv gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; @@ -207,6 +210,7 @@ void main() #endif gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; } + #if @radialFog float depth; // For the less detailed mesh of simple water we need to recalculate depth on per-pixel basis diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 969fe5903e..5f208ffc25 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -50,7 +50,7 @@ varying vec2 specularMapUV; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING ((@normalMap || @forcePPL) && !@simpleLighting) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; @@ -126,7 +126,11 @@ void main(void) #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; - doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); +#if @simpleLighting + doSimpleLighting(passViewPos, viewNormal, diffuseLight, ambientLight); +#else + doLighting(passViewPos, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); +#endif vec3 emission = getEmissionColor().xyz * emissiveMult; passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; clampLightingResult(passLighting); diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl new file mode 100644 index 0000000000..c064619579 --- /dev/null +++ b/files/shaders/sky_fragment.glsl @@ -0,0 +1,84 @@ +#version 120 + +#include "skypasses.glsl" + +uniform int pass; +uniform sampler2D diffuseMap; +uniform sampler2D maskMap; // PASS_MOON +uniform float opacity; // PASS_CLOUDS, PASS_ATMOSPHERE_NIGHT +uniform vec4 moonBlend; // PASS_MOON +uniform vec4 atmosphereFade; // PASS_MOON + +varying vec2 diffuseMapUV; +varying vec4 passColor; + +void paintAtmosphere(inout vec4 color) +{ + color = gl_FrontMaterial.emission; + color.a *= passColor.a; +} + +void paintAtmosphereNight(inout vec4 color) +{ + color = texture2D(diffuseMap, diffuseMapUV); + color.a *= passColor.a * opacity; +} + +void paintClouds(inout vec4 color) +{ + color = texture2D(diffuseMap, diffuseMapUV); + color.a *= passColor.a * opacity; + color.xyz = clamp(color.xyz * gl_FrontMaterial.emission.xyz, 0.0, 1.0); +} + +void paintMoon(inout vec4 color) +{ + vec4 phase = texture2D(diffuseMap, diffuseMapUV); + vec4 mask = texture2D(maskMap, diffuseMapUV); + + vec4 blendedLayer = phase * moonBlend; + color = vec4(blendedLayer.xyz + atmosphereFade.xyz, atmosphereFade.a * mask.a); +} + +void paintSun(inout vec4 color) +{ + color = texture2D(diffuseMap, diffuseMapUV); + color.a *= gl_FrontMaterial.diffuse.a; +} + +void paintSunflashQuery(inout vec4 color) +{ + const float threshold = 0.8; + + color = texture2D(diffuseMap, diffuseMapUV); + if (color.a <= threshold) + discard; +} + +void paintSunglare(inout vec4 color) +{ + color = gl_FrontMaterial.emission; + color.a = gl_FrontMaterial.diffuse.a; +} + +void main() +{ + vec4 color = vec4(0.0); + + if (pass == PASS_ATMOSPHERE) + paintAtmosphere(color); + else if (pass == PASS_ATMOSPHERE_NIGHT) + paintAtmosphereNight(color); + else if (pass == PASS_CLOUDS) + paintClouds(color); + else if (pass == PASS_MOON) + paintMoon(color); + else if (pass == PASS_SUN) + paintSun(color); + else if (pass == PASS_SUNFLASH_QUERY) + paintSunflashQuery(color); + else if (pass == PASS_SUNGLARE) + paintSunglare(color); + + gl_FragData[0] = color; +} diff --git a/files/shaders/sky_vertex.glsl b/files/shaders/sky_vertex.glsl new file mode 100644 index 0000000000..9c676140ac --- /dev/null +++ b/files/shaders/sky_vertex.glsl @@ -0,0 +1,20 @@ +#version 120 + +#include "skypasses.glsl" + +uniform mat4 projectionMatrix; +uniform int pass; + +varying vec4 passColor; +varying vec2 diffuseMapUV; + +void main() +{ + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + passColor = gl_Color; + + if (pass == PASS_CLOUDS) + diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy; + else + diffuseMapUV = gl_MultiTexCoord0.xy; +} diff --git a/files/shaders/skypasses.glsl b/files/shaders/skypasses.glsl new file mode 100644 index 0000000000..e80d4eb259 --- /dev/null +++ b/files/shaders/skypasses.glsl @@ -0,0 +1,7 @@ +#define PASS_ATMOSPHERE 0 +#define PASS_ATMOSPHERE_NIGHT 1 +#define PASS_CLOUDS 2 +#define PASS_MOON 3 +#define PASS_SUN 4 +#define PASS_SUNFLASH_QUERY 5 +#define PASS_SUNGLARE 6 From 566380c0d663b397b724c993f0b9dc11d23db36d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Oct 2021 12:37:49 +0400 Subject: [PATCH 1529/2859] Fix showscenegraph warnings --- components/sceneutil/serialize.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 703b63af7d..134f7c29dd 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -125,6 +125,7 @@ void registerSerializers() "SceneUtil::CompositeStateSetUpdater", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", + "SceneUtil::NodeCallback", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", @@ -132,6 +133,7 @@ void registerSerializers() "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", + "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::LightManagerStateAttribute", "NifOsg::FlipController", From a58f1a94e3a960e39e4dd5227f7a26cf9b0ee02d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Oct 2021 00:06:10 +0200 Subject: [PATCH 1530/2859] Add helpers for binary serialization To construct serializer from given entities: * Data source/destination - any value that has to be serialized/deserialized, usually already existing type. * Format - functional object to define high level serialization logic to define specific format and data schema. Like order of fields, allocation. * Visitor - functional object to define low level serialization logic to operator on given data part. * BinaryWriter - copies given value into provided buffer. * BinaryReader - copies value into given destination from provided buffer. * SizeAccumulator - calculates required buffer size for given data. --- apps/openmw_test_suite/CMakeLists.txt | 4 + .../serialization/binaryreader.cpp | 67 ++++++++++++++++ .../serialization/binarywriter.cpp | 57 +++++++++++++ .../detournavigator/serialization/format.hpp | 75 +++++++++++++++++ .../serialization/integration.cpp | 56 +++++++++++++ .../serialization/sizeaccumulator.cpp | 43 ++++++++++ .../serialization/binaryreader.hpp | 62 ++++++++++++++ .../serialization/binarywriter.hpp | 62 ++++++++++++++ .../detournavigator/serialization/format.hpp | 80 +++++++++++++++++++ .../serialization/sizeaccumulator.hpp | 41 ++++++++++ 10 files changed, 547 insertions(+) create mode 100644 apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp create mode 100644 apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp create mode 100644 apps/openmw_test_suite/detournavigator/serialization/format.hpp create mode 100644 apps/openmw_test_suite/detournavigator/serialization/integration.cpp create mode 100644 apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp create mode 100644 components/detournavigator/serialization/binaryreader.hpp create mode 100644 components/detournavigator/serialization/binarywriter.hpp create mode 100644 components/detournavigator/serialization/format.hpp create mode 100644 components/detournavigator/serialization/sizeaccumulator.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 0390b0a772..a2a35e3aab 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -36,6 +36,10 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp detournavigator/tilecachedrecastmeshmanager.cpp + detournavigator/serialization/binaryreader.cpp + detournavigator/serialization/binarywriter.cpp + detournavigator/serialization/sizeaccumulator.cpp + detournavigator/serialization/integration.cpp settings/parser.cpp diff --git a/apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp b/apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp new file mode 100644 index 0000000000..d071326cf5 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp @@ -0,0 +1,67 @@ +#include "format.hpp" + +#include + +#include +#include + +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator::Serialization; + using namespace DetourNavigator::SerializationTesting; + + TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeValue) + { + std::uint32_t value = 42; + std::vector data(sizeof(value)); + std::memcpy(data.data(), &value, sizeof(value)); + BinaryReader binaryReader(data.data(), data.data() + data.size()); + std::uint32_t result = 0; + const TestFormat format; + binaryReader(format, result); + EXPECT_EQ(result, 42); + } + + TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeRangeValue) + { + const std::size_t count = 3; + std::vector data(sizeof(std::size_t) + count * sizeof(std::uint32_t)); + std::memcpy(data.data(), &count, sizeof(count)); + const std::uint32_t value1 = 960900021; + std::memcpy(data.data() + sizeof(count), &value1, sizeof(std::uint32_t)); + const std::uint32_t value2 = 1235496234; + std::memcpy(data.data() + sizeof(count) + sizeof(std::uint32_t), &value2, sizeof(std::uint32_t)); + const std::uint32_t value3 = 2342038092; + std::memcpy(data.data() + sizeof(count) + 2 * sizeof(std::uint32_t), &value3, sizeof(std::uint32_t)); + BinaryReader binaryReader(data.data(), data.data() + data.size()); + std::size_t resultCount = 0; + const TestFormat format; + binaryReader(format, resultCount); + std::vector result(resultCount); + binaryReader(format, result.data(), result.size()); + EXPECT_THAT(result, ElementsAre(value1, value2, value3)); + } + + TEST(DetourNavigatorSerializationBinaryReaderTest, forNotEnoughDataForArithmeticTypeShouldThrowException) + { + std::vector data(3); + BinaryReader binaryReader(data.data(), data.data() + data.size()); + std::uint32_t result = 0; + const TestFormat format; + EXPECT_THROW(binaryReader(format, result), std::runtime_error); + } + + TEST(DetourNavigatorSerializationBinaryReaderTest, forNotEnoughDataForArithmeticTypeRangeShouldThrowException) + { + std::vector data(7); + BinaryReader binaryReader(data.data(), data.data() + data.size()); + std::vector values(2); + const TestFormat format; + EXPECT_THROW(binaryReader(format, values.data(), values.size()), std::runtime_error); + } +} diff --git a/apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp b/apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp new file mode 100644 index 0000000000..fccc2be3da --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp @@ -0,0 +1,57 @@ +#include "format.hpp" + +#include + +#include +#include + +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator::Serialization; + using namespace DetourNavigator::SerializationTesting; + + TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeValue) + { + std::vector result(4); + BinaryWriter binaryWriter(result.data(), result.data() + result.size()); + const TestFormat format; + binaryWriter(format, std::uint32_t(42)); + EXPECT_THAT(result, ElementsAre(std::byte(42), std::byte(0), std::byte(0), std::byte(0))); + } + + TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeRangeValue) + { + std::vector result(8); + BinaryWriter binaryWriter(result.data(), result.data() + result.size()); + std::vector values({42, 13}); + const TestFormat format; + binaryWriter(format, values.data(), values.size()); + constexpr std::array expected { + std::byte(42), std::byte(0), std::byte(0), std::byte(0), + std::byte(13), std::byte(0), std::byte(0), std::byte(0), + }; + EXPECT_THAT(result, ElementsAreArray(expected)); + } + + TEST(DetourNavigatorSerializationBinaryWriterTest, forNotEnoughSpaceForArithmeticTypeShouldThrowException) + { + std::vector result(3); + BinaryWriter binaryWriter(result.data(), result.data() + result.size()); + const TestFormat format; + EXPECT_THROW(binaryWriter(format, std::uint32_t(42)), std::runtime_error); + } + + TEST(DetourNavigatorSerializationBinaryWriterTest, forNotEnoughSpaceForArithmeticTypeRangeShouldThrowException) + { + std::vector result(7); + BinaryWriter binaryWriter(result.data(), result.data() + result.size()); + std::vector values({42, 13}); + const TestFormat format; + EXPECT_THROW(binaryWriter(format, values.data(), values.size()), std::runtime_error); + } +} diff --git a/apps/openmw_test_suite/detournavigator/serialization/format.hpp b/apps/openmw_test_suite/detournavigator/serialization/format.hpp new file mode 100644 index 0000000000..7c5e26a0be --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/serialization/format.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H +#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H + +#include + +#include +#include + +namespace DetourNavigator::SerializationTesting +{ + struct Pod + { + int mInt = 42; + double mDouble = 3.14; + + friend bool operator==(const Pod& l, const Pod& r) + { + const auto tuple = [] (const Pod& v) { return std::tuple(v.mInt, v.mDouble); }; + return tuple(l) == tuple(r); + } + }; + + enum Enum + { + A, + B, + C, + }; + + struct Composite + { + short mFloatArray[3] = {0}; + std::vector mIntVector; + std::vector mEnumVector; + std::vector mPodVector; + std::size_t mPodDataSize = 0; + std::vector mPodBuffer; + std::size_t mCharDataSize = 0; + std::vector mCharBuffer; + }; + + template + struct TestFormat : Serialization::Format> + { + using Serialization::Format>::operator(); + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, Pod>> + { + visitor(*this, value.mInt); + visitor(*this, value.mDouble); + } + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, Composite>> + { + visitor(*this, value.mFloatArray); + visitor(*this, value.mIntVector); + visitor(*this, value.mEnumVector); + visitor(*this, value.mPodVector); + visitor(*this, value.mPodDataSize); + if constexpr (mode == Serialization::Mode::Read) + value.mPodBuffer.resize(value.mPodDataSize); + visitor(*this, value.mPodBuffer.data(), value.mPodDataSize); + visitor(*this, value.mCharDataSize); + if constexpr (mode == Serialization::Mode::Read) + value.mCharBuffer.resize(value.mCharDataSize); + visitor(*this, value.mCharBuffer.data(), value.mCharDataSize); + } + }; +} + +#endif diff --git a/apps/openmw_test_suite/detournavigator/serialization/integration.cpp b/apps/openmw_test_suite/detournavigator/serialization/integration.cpp new file mode 100644 index 0000000000..e7e8eacc20 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/serialization/integration.cpp @@ -0,0 +1,56 @@ +#include "format.hpp" + +#include +#include +#include + +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator::Serialization; + using namespace DetourNavigator::SerializationTesting; + + struct DetourNavigatorSerializationIntegrationTest : Test + { + Composite mComposite; + + DetourNavigatorSerializationIntegrationTest() + { + mComposite.mIntVector = {4, 5, 6}; + mComposite.mEnumVector = {Enum::A, Enum::B, Enum::C}; + mComposite.mPodVector = {Pod {4, 23.87}, Pod {5, -31.76}, Pod {6, 65.12}}; + mComposite.mPodBuffer = {Pod {7, 456.123}, Pod {8, -628.346}}; + mComposite.mPodDataSize = mComposite.mPodBuffer.size(); + std::string charData = "serialization"; + mComposite.mCharBuffer = {charData.begin(), charData.end()}; + mComposite.mCharDataSize = charData.size(); + } + }; + + TEST_F(DetourNavigatorSerializationIntegrationTest, sizeAccumulatorShouldSupportCustomSerializer) + { + SizeAccumulator sizeAccumulator; + TestFormat{}(sizeAccumulator, mComposite); + EXPECT_EQ(sizeAccumulator.value(), 143); + } + + TEST_F(DetourNavigatorSerializationIntegrationTest, binaryReaderShouldDeserializeDataWrittenByBinaryWriter) + { + std::vector data(143); + TestFormat{}(BinaryWriter(data.data(), data.data() + data.size()), mComposite); + Composite result; + TestFormat{}(BinaryReader(data.data(), data.data() + data.size()), result); + EXPECT_EQ(result.mIntVector, mComposite.mIntVector); + EXPECT_EQ(result.mEnumVector, mComposite.mEnumVector); + EXPECT_EQ(result.mPodVector, mComposite.mPodVector); + EXPECT_EQ(result.mPodDataSize, mComposite.mPodDataSize); + EXPECT_EQ(result.mPodBuffer, mComposite.mPodBuffer); + EXPECT_EQ(result.mCharDataSize, mComposite.mCharDataSize); + EXPECT_EQ(result.mCharBuffer, mComposite.mCharBuffer); + } +} diff --git a/apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp b/apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp new file mode 100644 index 0000000000..39b7ea8646 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp @@ -0,0 +1,43 @@ +#include "format.hpp" + +#include + +#include + +#include +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator::Serialization; + using namespace DetourNavigator::SerializationTesting; + + TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticType) + { + SizeAccumulator sizeAccumulator; + constexpr std::monostate format; + sizeAccumulator(format, std::uint32_t()); + EXPECT_EQ(sizeAccumulator.value(), 4); + } + + TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticTypeRange) + { + SizeAccumulator sizeAccumulator; + const std::uint64_t* const data = nullptr; + const std::size_t count = 3; + const std::monostate format; + sizeAccumulator(format, data, count); + EXPECT_EQ(sizeAccumulator.value(), 24); + } + + TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldSupportCustomSerializer) + { + SizeAccumulator sizeAccumulator; + const TestFormat format; + sizeAccumulator(format, Pod {}); + EXPECT_EQ(sizeAccumulator.value(), 12); + } +} diff --git a/components/detournavigator/serialization/binaryreader.hpp b/components/detournavigator/serialization/binaryreader.hpp new file mode 100644 index 0000000000..0d75c3ac99 --- /dev/null +++ b/components/detournavigator/serialization/binaryreader.hpp @@ -0,0 +1,62 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYREADER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYREADER_H + +#include +#include +#include +#include +#include + +namespace DetourNavigator::Serialization +{ + class BinaryReader + { + public: + explicit BinaryReader(const std::byte* pos, const std::byte* end) + : mPos(pos), mEnd(end) + { + assert(mPos <= mEnd); + } + + BinaryReader(const BinaryReader&) = delete; + + template + void operator()(Format&& format, T& value) + { + if constexpr (std::is_arithmetic_v) + { + if (mEnd - mPos < static_cast(sizeof(value))) + throw std::runtime_error("Not enough data"); + std::memcpy(&value, mPos, sizeof(value)); + mPos += sizeof(value); + } + else + { + format(*this, value); + } + } + + template + auto operator()(Format&& format, T* data, std::size_t count) + { + if constexpr (std::is_arithmetic_v) + { + if (mEnd - mPos < static_cast(count * sizeof(T))) + throw std::runtime_error("Not enough data"); + const std::size_t size = sizeof(T) * count; + std::memcpy(data, mPos, size); + mPos += size; + } + else + { + format(*this, data, count); + } + } + + private: + const std::byte* mPos; + const std::byte* const mEnd; + }; +} + +#endif diff --git a/components/detournavigator/serialization/binarywriter.hpp b/components/detournavigator/serialization/binarywriter.hpp new file mode 100644 index 0000000000..5e710d85d5 --- /dev/null +++ b/components/detournavigator/serialization/binarywriter.hpp @@ -0,0 +1,62 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYWRITER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYWRITER_H + +#include +#include +#include +#include +#include + +namespace DetourNavigator::Serialization +{ + struct BinaryWriter + { + public: + explicit BinaryWriter(std::byte* dest, const std::byte* end) + : mDest(dest), mEnd(end) + { + assert(mDest <= mEnd); + } + + BinaryWriter(const BinaryWriter&) = delete; + + template + void operator()(Format&& format, const T& value) + { + if constexpr (std::is_arithmetic_v) + { + if (mEnd - mDest < static_cast(sizeof(value))) + throw std::runtime_error("Not enough space"); + std::memcpy(mDest, &value, sizeof(value)); + mDest += sizeof(value); + } + else + { + format(*this, value); + } + } + + template + auto operator()(Format&& format, const T* data, std::size_t count) + { + if constexpr (std::is_arithmetic_v) + { + const std::size_t size = sizeof(T) * count; + if (mEnd - mDest < static_cast(size)) + throw std::runtime_error("Not enough space"); + std::memcpy(mDest, data, size); + mDest += size; + } + else + { + format(*this, data, count); + } + } + + private: + std::byte* mDest; + const std::byte* const mEnd; + }; +} + +#endif diff --git a/components/detournavigator/serialization/format.hpp b/components/detournavigator/serialization/format.hpp new file mode 100644 index 0000000000..d07ab9da6f --- /dev/null +++ b/components/detournavigator/serialization/format.hpp @@ -0,0 +1,80 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H + +#include +#include +#include +#include +#include +#include + +namespace DetourNavigator::Serialization +{ + enum class Mode + { + Read, + Write, + }; + + template + struct IsContiguousContainer : std::false_type {}; + + template + struct IsContiguousContainer> : std::true_type {}; + + template + constexpr bool isContiguousContainer = IsContiguousContainer>::value; + + template + struct Format + { + template + void operator()(Visitor&& visitor, T* data, std::size_t size) const + { + if constexpr (std::is_arithmetic_v) + { + visitor(self(), data, size); + } + else if constexpr (std::is_enum_v) + { + if constexpr (mode == Mode::Write) + visitor(self(), reinterpret_cast*>(data), size); + else + { + static_assert(mode == Mode::Read); + visitor(self(), reinterpret_cast*>(data), size); + } + } + else + { + std::for_each(data, data + size, [&] (auto& v) { visitor(self(), v); }); + } + } + + template + void operator()(Visitor&& visitor, T(& data)[size]) const + { + self()(std::forward(visitor), data, size); + } + + template + auto operator()(Visitor&& visitor, T&& value) const + -> std::enable_if_t> + { + if constexpr (mode == Mode::Write) + visitor(self(), value.size()); + else + { + static_assert(mode == Mode::Read); + std::size_t size = 0; + visitor(self(), size); + value.resize(size); + } + self()(std::forward(visitor), value.data(), value.size()); + } + + const Derived& self() const { return static_cast(*this); } + }; +} + +#endif diff --git a/components/detournavigator/serialization/sizeaccumulator.hpp b/components/detournavigator/serialization/sizeaccumulator.hpp new file mode 100644 index 0000000000..28bdb5c1cb --- /dev/null +++ b/components/detournavigator/serialization/sizeaccumulator.hpp @@ -0,0 +1,41 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_SIZEACCUMULATOR_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_SIZEACCUMULATOR_H + +#include +#include + +namespace DetourNavigator::Serialization +{ + class SizeAccumulator + { + public: + SizeAccumulator() = default; + + SizeAccumulator(const SizeAccumulator&) = delete; + + std::size_t value() const { return mValue; } + + template + void operator()(Format&& format, const T& value) + { + if constexpr (std::is_arithmetic_v) + mValue += sizeof(T); + else + format(*this, value); + } + + template + auto operator()(Format&& format, const T* data, std::size_t count) + { + if constexpr (std::is_arithmetic_v) + mValue += count * sizeof(T); + else + format(*this, data, count); + } + + private: + std::size_t mValue = 0; + }; +} + +#endif From 107a9ecb1701c753804fd9fed04aafa3980f1aaa Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Oct 2021 18:45:04 +0400 Subject: [PATCH 1531/2859] Fix variables hiding --- apps/openmw/mwclass/creature.cpp | 4 ++-- apps/openmw/mwclass/npc.cpp | 4 ++-- apps/openmw/mwphysics/physicssystem.cpp | 8 ++++---- components/detournavigator/navigatorimpl.cpp | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a9b1f461ca..4eeab5a393 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -82,7 +82,7 @@ namespace MWClass const Creature::GMST& Creature::getGmst() { - static const GMST gmst = [] + static const GMST staticGmst = [] { GMST gmst; @@ -105,7 +105,7 @@ namespace MWClass return gmst; } (); - return gmst; + return staticGmst; } void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index becfb8719f..ca07d99e3f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -266,7 +266,7 @@ namespace MWClass const Npc::GMST& Npc::getGmst() { - static const GMST gmst = [] + static const GMST staticGmst = [] { GMST gmst; @@ -296,7 +296,7 @@ namespace MWClass return gmst; } (); - return gmst; + return staticGmst; } void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 6c46fd6d05..2a5bcb58bd 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -526,10 +526,10 @@ namespace MWPhysics void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { - if (auto found = mObjects.find(old.mRef); found != mObjects.end()) - found->second->updatePtr(updated); - else if (auto found = mActors.find(old.mRef); found != mActors.end()) - found->second->updatePtr(updated); + if (auto foundObject = mObjects.find(old.mRef); foundObject != mObjects.end()) + foundObject->second->updatePtr(updated); + else if (auto foundActor = mActors.find(old.mRef); foundActor != mActors.end()) + foundActor->second->updatePtr(updated); for (auto& [_, actor] : mActors) { diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index f29ae1bb95..9a59ecf6d7 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -69,8 +69,8 @@ namespace DetourNavigator if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { const ObjectId avoidId(avoidShape); - const CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; - if (mNavMeshManager.updateObject(avoidId, collisionShape, transform, AreaType_null)) + const CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; + if (mNavMeshManager.updateObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; From 0f3c0cb0a0dc637b329cab199932022071c01492 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Oct 2021 18:45:46 +0400 Subject: [PATCH 1532/2859] Fix argument types mismatch --- .../detournavigator/preparednavmeshdatatuple.hpp | 16 ++++++++-------- components/esm/luascripts.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/detournavigator/preparednavmeshdatatuple.hpp b/components/detournavigator/preparednavmeshdatatuple.hpp index 8ff1267370..bcca0ace37 100644 --- a/components/detournavigator/preparednavmeshdatatuple.hpp +++ b/components/detournavigator/preparednavmeshdatatuple.hpp @@ -14,11 +14,11 @@ namespace DetourNavigator constexpr auto makeTuple(const rcPolyMesh& v) noexcept { return std::tuple( - Span(v.verts, getVertsLength(v)), - Span(v.polys, getPolysLength(v)), - Span(v.regs, getRegsLength(v)), - Span(v.flags, getFlagsLength(v)), - Span(v.areas, getAreasLength(v)), + Span(v.verts, static_cast(getVertsLength(v))), + Span(v.polys, static_cast(getPolysLength(v))), + Span(v.regs, static_cast(getRegsLength(v))), + Span(v.flags, static_cast(getFlagsLength(v))), + Span(v.areas, static_cast(getAreasLength(v))), ArrayRef(v.bmin), ArrayRef(v.bmax), v.cs, @@ -31,9 +31,9 @@ namespace DetourNavigator constexpr auto makeTuple(const rcPolyMeshDetail& v) noexcept { return std::tuple( - Span(v.meshes, getMeshesLength(v)), - Span(v.verts, getVertsLength(v)), - Span(v.tris, getTrisLength(v)) + Span(v.meshes, static_cast(getMeshesLength(v))), + Span(v.verts, static_cast(getVertsLength(v))), + Span(v.tris, static_cast(getTrisLength(v))) ); } diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index 1dd45ab2b1..7ff841312a 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -32,7 +32,7 @@ std::string ESM::loadLuaBinaryData(ESMReader& esm) { esm.getSubHeader(); data.resize(esm.getSubSize()); - esm.getExact(data.data(), data.size()); + esm.getExact(data.data(), static_cast(data.size())); } return data; } From fef902617aed7271294ab40d377f66f1cc2b224b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 24 Oct 2021 17:23:15 +0200 Subject: [PATCH 1533/2859] Parse integer format arguments as variable names --- CHANGELOG.md | 1 + components/compiler/exprparser.cpp | 4 +++- components/compiler/exprparser.hpp | 2 +- components/compiler/lineparser.cpp | 2 +- components/compiler/scanner.cpp | 10 +++++++--- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37fed31fd..81d3066dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Bug #6326: Detect Enchantment/Key should detect items in unresolved containers Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures + Bug #6363: Some scripts in Morrowland fail to work Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 2d525e6f8c..1aedc8dc59 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -632,7 +632,7 @@ namespace Compiler } int ExprParser::parseArguments (const std::string& arguments, Scanner& scanner, - std::vector& code, int ignoreKeyword) + std::vector& code, int ignoreKeyword, bool expectNames) { bool optional = false; int optionalCount = 0; @@ -717,6 +717,8 @@ namespace Compiler if (optional) parser.setOptional (true); + if(expectNames) + scanner.enableExpectName(); scanner.scan (parser); diff --git a/components/compiler/exprparser.hpp b/components/compiler/exprparser.hpp index 2f3eaa8a9f..42739658ec 100644 --- a/components/compiler/exprparser.hpp +++ b/components/compiler/exprparser.hpp @@ -96,7 +96,7 @@ namespace Compiler /// \return Type ('l': integer, 'f': float) int parseArguments (const std::string& arguments, Scanner& scanner, - std::vector& code, int ignoreKeyword = -1); + std::vector& code, int ignoreKeyword = -1, bool expectNames = false); ///< Parse sequence of arguments specified by \a arguments. /// \param arguments Uses ScriptArgs typedef /// \see Compiler::ScriptArgs diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 943c052473..ec90812ec7 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -145,7 +145,7 @@ namespace Compiler if (!arguments.empty()) { mExprParser.reset(); - mExprParser.parseArguments (arguments, scanner, mCode); + mExprParser.parseArguments (arguments, scanner, mCode, -1, true); } mName = name; diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 1054e2e269..0e2b76cb23 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -130,7 +130,8 @@ namespace Compiler { bool cont = false; - if (scanInt (c, parser, cont)) + bool scanned = mExpectName ? scanName(c, parser, cont) : scanInt(c, parser, cont); + if (scanned) { mLoc.mLiteral.clear(); return cont; @@ -387,6 +388,8 @@ namespace Compiler bool Scanner::scanSpecial (MultiChar& c, Parser& parser, bool& cont) { + bool expectName = mExpectName; + mExpectName = false; int special = -1; if (c=='\n') @@ -541,15 +544,16 @@ namespace Compiler if (special==S_newline) mLoc.mLiteral = ""; - else if (mExpectName && (special == S_member || special == S_minus)) + else if (expectName && (special == S_member || special == S_minus)) { - mExpectName = false; bool tolerant = mTolerantNames; mTolerantNames = true; bool out = scanName(c, parser, cont); mTolerantNames = tolerant; return out; } + else if (expectName && special == S_comma) + mExpectName = true; TokenLoc loc (mLoc); mLoc.mLiteral.clear(); From 9cbbd2fff56a69ea27091abbd687dc9e967f8b9b Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 24 Oct 2021 17:13:42 -0700 Subject: [PATCH 1534/2859] better transitions --- apps/openmw/mwrender/sky.cpp | 3 +++ files/shaders/sky_fragment.glsl | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 217f10f294..9deb6968e3 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -659,7 +659,10 @@ namespace MWRender mParticleNode->addChild(program); for (int particleIndex = 0; particleIndex < ps->numParticles(); ++particleIndex) + { ps->getParticle(particleIndex)->setAlphaRange(osgParticle::rangef(mPrecipitationAlpha, mPrecipitationAlpha)); + ps->getParticle(particleIndex)->update(0, true); + } ps->getOrCreateStateSet(); ps->setUserValue("simpleLighting", true); diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl index c064619579..1ef62ab2cf 100644 --- a/files/shaders/sky_fragment.glsl +++ b/files/shaders/sky_fragment.glsl @@ -29,6 +29,9 @@ void paintClouds(inout vec4 color) color = texture2D(diffuseMap, diffuseMapUV); color.a *= passColor.a * opacity; color.xyz = clamp(color.xyz * gl_FrontMaterial.emission.xyz, 0.0, 1.0); + + // ease transition between clear color and atmosphere/clouds + color = mix(vec4(gl_Fog.color.xyz, color.a), color, passColor.a * passColor.a); } void paintMoon(inout vec4 color) From 6fb376f1cf6102f5f0d17053f68c2105b16f9348 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 25 Oct 2021 07:08:50 +0000 Subject: [PATCH 1535/2859] templatise ptr.hpp (#3185) With this PR we consolidate identical logic for Ptr and ConstPtr with the help of a template. --- apps/openmw/mwworld/livecellref.cpp | 5 + apps/openmw/mwworld/livecellref.hpp | 3 + apps/openmw/mwworld/ptr.cpp | 89 ----------- apps/openmw/mwworld/ptr.hpp | 226 ++++++++-------------------- 4 files changed, 71 insertions(+), 252 deletions(-) delete mode 100644 apps/openmw/mwworld/ptr.cpp diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index c74817911e..62c9f3a2f0 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -73,3 +73,8 @@ bool MWWorld::LiveCellRefBase::checkStateImp (const ESM::ObjectState& state) { return true; } + +unsigned int MWWorld::LiveCellRefBase::getType() const +{ + return mClass->getType(); +} diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 48e237bce4..1ead0395fd 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -43,6 +43,9 @@ namespace MWWorld virtual std::string_view getTypeDescription() const = 0; + unsigned int getType() const; + ///< @see MWWorld::Class::getType + protected: void loadImp (const ESM::ObjectState& state); diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp deleted file mode 100644 index b18e3b1689..0000000000 --- a/apps/openmw/mwworld/ptr.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "ptr.hpp" - -#include - -#include "containerstore.hpp" -#include "class.hpp" -#include "livecellref.hpp" - -unsigned int MWWorld::Ptr::getType() const -{ - if(mRef != nullptr) - return mRef->mClass->getType(); - throw std::runtime_error("Can't get type name from an empty object."); -} - -MWWorld::LiveCellRefBase *MWWorld::Ptr::getBase() const -{ - if (!mRef) - throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); - - return mRef; -} - -MWWorld::CellRef& MWWorld::Ptr::getCellRef() const -{ - assert(mRef); - - return mRef->mRef; -} - -MWWorld::RefData& MWWorld::Ptr::getRefData() const -{ - assert(mRef); - - return mRef->mData; -} - -void MWWorld::Ptr::setContainerStore (ContainerStore *store) -{ - assert (store); - assert (!mCell); - - mContainerStore = store; -} - -MWWorld::ContainerStore *MWWorld::Ptr::getContainerStore() const -{ - return mContainerStore; -} - -MWWorld::Ptr::operator const void *() -{ - return mRef; -} - -// ------------------------------------------------------------------------------- - -unsigned int MWWorld::ConstPtr::getType() const -{ - if(mRef != nullptr) - return mRef->mClass->getType(); - throw std::runtime_error("Can't get type name from an empty object."); -} - -const MWWorld::LiveCellRefBase *MWWorld::ConstPtr::getBase() const -{ - if (!mRef) - throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); - - return mRef; -} - -void MWWorld::ConstPtr::setContainerStore (const ContainerStore *store) -{ - assert (store); - assert (!mCell); - - mContainerStore = store; -} - -const MWWorld::ContainerStore *MWWorld::ConstPtr::getContainerStore() const -{ - return mContainerStore; -} - -MWWorld::ConstPtr::operator const void *() -{ - return mRef; -} diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index a82abaa212..438b87c7af 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -2,7 +2,7 @@ #define GAME_MWWORLD_PTR_H #include - +#include #include #include @@ -15,20 +15,19 @@ namespace MWWorld struct LiveCellRefBase; /// \brief Pointer to a LiveCellRef - - class Ptr + /// @note PtrBase is never used directly and needed only to define Ptr and ConstPtr + template class TypeTransform> + class PtrBase { public: - MWWorld::LiveCellRefBase *mRef; - CellStore *mCell; - ContainerStore *mContainerStore; + typedef TypeTransform LiveCellRefBaseType; + typedef TypeTransform CellStoreType; + typedef TypeTransform ContainerStoreType; - public: - Ptr(MWWorld::LiveCellRefBase *liveCellRef=nullptr, CellStore *cell=nullptr) - : mRef(liveCellRef), mCell(cell), mContainerStore(nullptr) - { - } + LiveCellRefBaseType *mRef; + CellStoreType *mCell; + ContainerStoreType *mContainerStore; bool isEmpty() const { @@ -40,7 +39,12 @@ namespace MWWorld // Note 1: ids are not sequential. E.g. for a creature `getType` returns 0x41455243. // Note 2: Life is not easy and full of surprises. For example // prison marker reuses ESM::Door record. Player is ESM::NPC. - unsigned int getType() const; + unsigned int getType() const + { + if(mRef != nullptr) + return mRef->getType(); + throw std::runtime_error("Can't get type name from an empty object."); + } std::string_view getTypeDescription() const { @@ -55,9 +59,9 @@ namespace MWWorld } template - MWWorld::LiveCellRef *get() const + TypeTransform> *get() const { - MWWorld::LiveCellRef *ref = dynamic_cast*>(mRef); + TypeTransform> *ref = dynamic_cast>*>(mRef); if(ref) return ref; std::stringstream str; @@ -68,13 +72,26 @@ namespace MWWorld throw std::runtime_error(str.str()); } - MWWorld::LiveCellRefBase *getBase() const; + LiveCellRefBaseType *getBase() const + { + if (!mRef) + throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); + return mRef; + } - MWWorld::CellRef& getCellRef() const; + TypeTransform& getCellRef() const + { + assert(mRef); + return mRef->mRef; + } - RefData& getRefData() const; + TypeTransform& getRefData() const + { + assert(mRef); + return mRef->mData; + } - CellStore *getCell() const + CellStoreType *getCell() const { assert(mCell); return mCell; @@ -85,164 +102,47 @@ namespace MWWorld return (mContainerStore == nullptr) && (mCell != nullptr); } - void setContainerStore (ContainerStore *store); + void setContainerStore (ContainerStoreType *store) ///< Must not be called on references that are in a cell. + { + assert (store); + assert (!mCell); + mContainerStore = store; + } - ContainerStore *getContainerStore() const; + ContainerStoreType *getContainerStore() const ///< May return a 0-pointer, if reference is not in a container. + { + return mContainerStore; + } - operator const void *(); + operator const void *() const ///< Return a 0-pointer, if Ptr is empty; return a non-0-pointer, if Ptr is not empty + { + return mRef; + } + + protected: + PtrBase(LiveCellRefBaseType *liveCellRef, CellStoreType *cell, ContainerStoreType* containerStore) : mRef(liveCellRef), mCell(cell), mContainerStore(containerStore) {} }; - /// \brief Pointer to a const LiveCellRef - /// @note a Ptr can be implicitely converted to a ConstPtr, but you can not convert a ConstPtr to a Ptr. - class ConstPtr + /// @note It is possible to get mutable values from const Ptr. So if a function accepts const Ptr&, the object is still mutable. + /// To make it really const the argument should be const ConstPtr&. + class Ptr : public PtrBase { public: - - const MWWorld::LiveCellRefBase *mRef; - const CellStore *mCell; - const ContainerStore *mContainerStore; - - public: - ConstPtr(const MWWorld::LiveCellRefBase *liveCellRef=nullptr, const CellStore *cell=nullptr) - : mRef(liveCellRef), mCell(cell), mContainerStore(nullptr) - { - } - - ConstPtr(const MWWorld::Ptr& ptr) - : mRef(ptr.mRef), mCell(ptr.mCell), mContainerStore(ptr.mContainerStore) - { - } - - bool isEmpty() const - { - return mRef == nullptr; - } - - unsigned int getType() const; - - std::string_view getTypeDescription() const - { - return mRef ? mRef->getTypeDescription() : "nullptr"; - } - - const Class& getClass() const - { - if(mRef != nullptr) - return *(mRef->mClass); - throw std::runtime_error("Cannot get class of an empty object"); - } - - template - const MWWorld::LiveCellRef *get() const - { - const MWWorld::LiveCellRef *ref = dynamic_cast*>(mRef); - if(ref) return ref; - - std::stringstream str; - str<< "Bad LiveCellRef cast to "<mRef; - } - - const RefData& getRefData() const - { - assert(mRef); - return mRef->mData; - } - - const CellStore *getCell() const - { - assert(mCell); - return mCell; - } - - bool isInCell() const - { - return (mContainerStore == nullptr) && (mCell != nullptr); - } - - void setContainerStore (const ContainerStore *store); - ///< Must not be called on references that are in a cell. - - const ContainerStore *getContainerStore() const; - ///< May return a 0-pointer, if reference is not in a container. - - operator const void *(); - ///< Return a 0-pointer, if Ptr is empty; return a non-0-pointer, if Ptr is not empty + Ptr(LiveCellRefBase *liveCellRef=nullptr, CellStoreType *cell=nullptr) : PtrBase(liveCellRef, cell, nullptr) {} }; - inline bool operator== (const Ptr& left, const Ptr& right) - { - return left.mRef==right.mRef; - } - - inline bool operator!= (const Ptr& left, const Ptr& right) - { - return !(left==right); - } - - inline bool operator< (const Ptr& left, const Ptr& right) - { - return left.mRef= (const Ptr& left, const Ptr& right) - { - return !(left (const Ptr& left, const Ptr& right) - { - return rightright); - } - - inline bool operator== (const ConstPtr& left, const ConstPtr& right) - { - return left.mRef==right.mRef; - } - - inline bool operator!= (const ConstPtr& left, const ConstPtr& right) - { - return !(left==right); - } - - inline bool operator< (const ConstPtr& left, const ConstPtr& right) - { - return left.mRef= (const ConstPtr& left, const ConstPtr& right) - { - return !(left (const ConstPtr& left, const ConstPtr& right) + /// @note The difference between Ptr and ConstPtr is that the second one adds const to the underlying pointers. + /// @note a Ptr can be implicitely converted to a ConstPtr, but you can not convert a ConstPtr to a Ptr. + class ConstPtr : public PtrBase { - return rightright); - } } #endif From 7f9beac3a75e8e6d8b3f657591db7f4ecdcbe450 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 25 Oct 2021 07:18:26 +0000 Subject: [PATCH 1536/2859] refactors a case insensitive map (#3184) This PR aims to spark the retirement of a questionable pattern I have found all over our code base. I will illustrate how this pattern encourages code duplication, lacks type safety, requires documentation and can be prone to bugs. ``` std::map mMap; // Stored in all lowercase for a case-insensitive lookup std::string lowerKey = Misc::StringUtils::lowerCase(key); mMap.emplace(lowerKey, object); std::string lowerKey = Misc::StringUtils::lowerCase(key); mMap.find(lowerKey); mMap.find(key); // Not found. Oops! ``` An alternative approach produces no such issues. ``` std::unordered_map mMap; mMap.emplace(key, object); mMap.find(key); ``` Of course, such an alternative will work for a `map` as well, but an `unordered_map` is generally preferable over a `map` with these changes because we have moved `lowerCase` into the comparison operator. In this PR I have refactored `Animation::mNodeMap` accordingly. I have reviewed and adapted all direct and indirect usage of `Animation::mNodeMap` to ensure we do not change behaviour with this PR. --- apps/openmw/mwrender/actoranimation.cpp | 2 +- apps/openmw/mwrender/animation.cpp | 5 ++--- apps/openmw/mwrender/animation.hpp | 6 ++++-- apps/openmw/mwrender/creatureanimation.cpp | 4 ++-- apps/openmw/mwrender/npcanimation.cpp | 4 ++-- apps/openmw/mwrender/weaponanimation.cpp | 5 ++--- apps/openmw/mwrender/weaponanimation.hpp | 3 +-- components/misc/stringops.hpp | 16 ++++++++++++++++ components/sceneutil/visitor.cpp | 13 ++++++------- components/sceneutil/visitor.hpp | 6 +++++- 10 files changed, 41 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index b346d4ac6c..24ca0aa4f3 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -74,7 +74,7 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); + NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) return PartHolderPtr(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index ca76856d48..cdc6c49ab2 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1510,7 +1510,7 @@ namespace MWRender parentNode = mInsert; else { - NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename)); + NodeMap::const_iterator found = getNodeMap().find(bonename); if (found == getNodeMap().end()) throw std::runtime_error("Can't find bone " + bonename); @@ -1619,8 +1619,7 @@ namespace MWRender const osg::Node* Animation::getNode(const std::string &name) const { - std::string lowerName = Misc::StringUtils::lowerCase(name); - NodeMap::const_iterator found = getNodeMap().find(lowerName); + NodeMap::const_iterator found = getNodeMap().find(name); if (found == getNodeMap().end()) return nullptr; else diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 84788c1e2d..d37d548dd3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -7,8 +7,10 @@ #include #include #include +#include #include +#include namespace ESM { @@ -157,6 +159,8 @@ public: virtual bool updateCarriedLeftVisible(const int weaptype) const { return false; }; + typedef std::unordered_map, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> NodeMap; + protected: class AnimationTime : public SceneUtil::ControllerSource { @@ -250,8 +254,6 @@ protected: std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; - // Stored in all lowercase for a case-insensitive lookup - typedef std::map > NodeMap; mutable NodeMap mNodeMap; mutable bool mNodeMapCreated; diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index b64b205961..657179db75 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -126,7 +126,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) if (bonename != "Weapon Bone") { const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); + NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) bonename = "Weapon Bone"; } @@ -161,7 +161,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(itemModel); const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); + NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get(), mResourceSystem->getSceneManager()); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 42215c00bc..f3d026ad3c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -713,7 +713,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st osg::ref_ptr templateNode = mResourceSystem->getSceneManager()->getTemplate(model); const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); + NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); @@ -813,7 +813,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g if (weaponBonename != bonename) { const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(weaponBonename)); + NodeMap::const_iterator found = nodeMap.find(weaponBonename); if (found != nodeMap.end()) bonename = weaponBonename; } diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 3db415126b..0572eae3be 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -172,14 +172,13 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) } } -void WeaponAnimation::addControllers(const std::map >& nodes, - std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) +void WeaponAnimation::addControllers(const Animation::NodeMap& nodes, std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) { for (int i=0; i<2; ++i) { mSpineControllers[i] = nullptr; - std::map >::const_iterator found = nodes.find(i == 0 ? "bip01 spine1" : "bip01 spine2"); + Animation::NodeMap::const_iterator found = nodes.find(i == 0 ? "bip01 spine1" : "bip01 spine2"); if (found != nodes.end()) { osg::Node* node = found->second; diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index 1f614463a6..125587c1bd 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -42,8 +42,7 @@ namespace MWRender void releaseArrow(MWWorld::Ptr actor, float attackStrength); /// Add WeaponAnimation-related controllers to \a nodes and store the added controllers in \a map. - void addControllers(const std::map >& nodes, - std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); + void addControllers(const Animation::NodeMap& nodes, std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); void deleteControllers(); diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 0863522356..6c9c1eefa2 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "utf8stream.hpp" @@ -182,6 +183,21 @@ public: return out; } + struct CiEqual + { + bool operator()(const std::string& left, const std::string& right) const + { + return ciEqual(left, right); + } + }; + struct CiHash + { + std::size_t operator()(std::string str) const + { + lowerCaseInPlace(str); + return std::hash{}(str); + } + }; struct CiComp { bool operator()(const std::string& left, const std::string& right) const diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index fde87c66ba..ea09445678 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -51,18 +51,17 @@ namespace SceneUtil void NodeMapVisitor::apply(osg::MatrixTransform& trans) { - // Take transformation for first found node in file - std::string originalNodeName = Misc::StringUtils::lowerCase(trans.getName()); + // Choose first found node in file if (trans.libraryName() == std::string("osgAnimation")) { + std::string nodeName = trans.getName(); // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) - std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); + std::replace(nodeName.begin(), nodeName.end(), '_', ' '); + mMap.emplace(nodeName, &trans); } - - const std::string nodeName = originalNodeName; - - mMap.emplace(nodeName, &trans); + else + mMap.emplace(trans.getName(), &trans); traverse(trans); } diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index fcf3c1f944..45aa408b9e 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -4,6 +4,10 @@ #include #include +#include + +#include + // Commonly used scene graph visitors namespace SceneUtil { @@ -49,7 +53,7 @@ namespace SceneUtil class NodeMapVisitor : public osg::NodeVisitor { public: - typedef std::map > NodeMap; + typedef std::unordered_map, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> NodeMap; NodeMapVisitor(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) From 1ff8318a526fcfd68a5fe2e5670e01fb4309403f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 25 Oct 2021 07:28:32 +0000 Subject: [PATCH 1537/2859] refactors premultiplied alpha (#3189) With this PR we refactor a `premultiplied alpha` user string set by `characterpreview.cpp` into a more flexible mechanism allowing us to assign any state to GUI textures. We can consider these changes more future proof than the previous approach. --- apps/openmw/mwgui/inventorywindow.cpp | 2 +- apps/openmw/mwgui/race.cpp | 2 +- apps/openmw/mwrender/characterpreview.cpp | 4 +++- apps/openmw/mwrender/characterpreview.hpp | 4 ++++ .../myguiplatform/myguirendermanager.cpp | 20 +++++-------------- components/myguiplatform/myguitexture.cpp | 4 +++- components/myguiplatform/myguitexture.hpp | 6 +++++- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index a586991888..0e76c2429f 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -70,7 +70,7 @@ namespace MWGui , mTrading(false) , mUpdateTimer(0.f) { - mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); + mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture(), mPreview->getTextureStateSet())); mPreview->rebuild(); mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 457594697d..d30eb65eb3 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -138,7 +138,7 @@ namespace MWGui mPreview->rebuild(); mPreview->setAngle (mCurrentAngle); - mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); + mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture(), mPreview->getTextureStateSet())); mPreviewImage->setRenderItemTexture(mPreviewTexture.get()); mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 4a84bf4f23..c717806e35 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -190,7 +190,9 @@ namespace MWRender mTexture->setInternalFormat(GL_RGBA); mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mTexture->setUserValue("premultiplied alpha", true); + + mTextureStateSet = new osg::StateSet; + mTextureStateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA)); mCamera = new osg::Camera; // hints that the camera is not relative to the master camera diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 3eb9688465..808ff0801d 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -18,6 +18,7 @@ namespace osg class Camera; class Group; class Viewport; + class StateSet; } namespace MWRender @@ -41,6 +42,8 @@ namespace MWRender void rebuild(); osg::ref_ptr getTexture(); + /// Get the osg::StateSet required to render the texture correctly, if any. + osg::StateSet* getTextureStateSet() { return mTextureStateSet; } private: CharacterPreview(const CharacterPreview&); @@ -54,6 +57,7 @@ namespace MWRender osg::ref_ptr mParent; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mTexture; + osg::ref_ptr mTextureStateSet; osg::ref_ptr mCamera; osg::ref_ptr mDrawOnceCallback; diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 3061b329c2..43b176c795 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -4,10 +4,8 @@ #include #include -#include #include #include -#include #include @@ -17,8 +15,6 @@ #include #include -#include - #include "myguicompat.h" #include "myguitexture.hpp" @@ -465,23 +461,17 @@ void RenderManager::doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *text batch.mVertexBuffer = static_cast(buffer)->getVertexBuffer(); batch.mArray = static_cast(buffer)->getVertexArray(); static_cast(buffer)->markUsed(); - bool premultipliedAlpha = false; - if (texture) + + if (OSGTexture* osgtexture = static_cast(texture)) { - batch.mTexture = static_cast(texture)->getTexture(); + batch.mTexture = osgtexture->getTexture(); if (batch.mTexture->getDataVariance() == osg::Object::DYNAMIC) mDrawable->setDataVariance(osg::Object::DYNAMIC); // only for this frame, reset in begin() - batch.mTexture->getUserValue("premultiplied alpha", premultipliedAlpha); + if (!mInjectState && osgtexture->getInjectState()) + batch.mStateSet = osgtexture->getInjectState(); } if (mInjectState) batch.mStateSet = mInjectState; - else if (premultipliedAlpha) - { - // This is hacky, but MyGUI made it impossible to use a custom layer for a nested node, so state couldn't be injected 'properly' - osg::ref_ptr stateSet = new osg::StateSet(); - stateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA)); - batch.mStateSet = stateSet; - } mDrawable->addBatch(batch); } diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index ce7332cc7e..d0e4e9a86e 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -21,9 +22,10 @@ namespace osgMyGUI { } - OSGTexture::OSGTexture(osg::Texture2D *texture) + OSGTexture::OSGTexture(osg::Texture2D *texture, osg::StateSet *injectState) : mImageManager(nullptr) , mTexture(texture) + , mInjectState(injectState) , mFormat(MyGUI::PixelFormat::Unknow) , mUsage(MyGUI::TextureUsage::Default) , mNumElemBytes(0) diff --git a/components/myguiplatform/myguitexture.hpp b/components/myguiplatform/myguitexture.hpp index a34f1b7628..e8b49eab04 100644 --- a/components/myguiplatform/myguitexture.hpp +++ b/components/myguiplatform/myguitexture.hpp @@ -15,6 +15,7 @@ namespace osg { class Image; class Texture2D; + class StateSet; } namespace Resource @@ -31,6 +32,7 @@ namespace osgMyGUI osg::ref_ptr mLockedImage; osg::ref_ptr mTexture; + osg::ref_ptr mInjectState; MyGUI::PixelFormat mFormat; MyGUI::TextureUsage mUsage; size_t mNumElemBytes; @@ -40,9 +42,11 @@ namespace osgMyGUI public: OSGTexture(const std::string &name, Resource::ImageManager* imageManager); - OSGTexture(osg::Texture2D* texture); + OSGTexture(osg::Texture2D* texture, osg::StateSet* injectState = nullptr); virtual ~OSGTexture(); + osg::StateSet* getInjectState() { return mInjectState; } + const std::string& getName() const override { return mName; } void createManual(int width, int height, MyGUI::TextureUsage usage, MyGUI::PixelFormat format) override; From 13b84ce798258fd4b1349c13904fe3feac292a46 Mon Sep 17 00:00:00 2001 From: psi29a Date: Mon, 25 Oct 2021 10:42:14 +0000 Subject: [PATCH 1538/2859] Fix MacOS failure for 10.15 by setting it to `allow_failure: true` --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c9b8cf9341..b69e930ae2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -184,7 +184,6 @@ Debian_Clang_tests_Debug: macOS11_Xcode12: extends: .MacOS image: macos-11-xcode-12 - allow_failure: true cache: key: macOS11_Xcode12.v1 variables: @@ -193,6 +192,7 @@ macOS11_Xcode12: macOS10.15_Xcode11: extends: .MacOS image: macos-10.15-xcode-11 + allow_failure: true cache: key: macOS10.15_Xcode11.v1 variables: From 289e1544d21f73ea9bf74c390ee46e35388d0814 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 25 Oct 2021 16:54:50 +0200 Subject: [PATCH 1539/2859] Don't wait a frame to recalculate magicka --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 10 ++--- apps/openmw/mwclass/npc.cpp | 14 +++--- apps/openmw/mwmechanics/actors.cpp | 45 ------------------- apps/openmw/mwmechanics/actors.hpp | 4 -- apps/openmw/mwmechanics/actorutil.cpp | 8 ++++ apps/openmw/mwmechanics/actorutil.hpp | 8 ++++ apps/openmw/mwmechanics/creaturestats.cpp | 45 +++++++++++-------- apps/openmw/mwmechanics/creaturestats.hpp | 5 +-- .../mwmechanics/mechanicsmanagerimp.cpp | 3 +- apps/openmw/mwmechanics/spelleffects.cpp | 13 +++--- 11 files changed, 67 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37fed31fd..58d111dd01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) + Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a9b1f461ca..4ca5b37cf8 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -112,7 +112,10 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data (new CreatureCustomData); + auto tempData = std::make_unique(); + CreatureCustomData* data = tempData.get(); + MWMechanics::CreatureCustomDataResetter resetter(ptr); + ptr.getRefData().setCustomData(std::move(tempData)); MWWorld::LiveCellRef *ref = ptr.get(); @@ -156,10 +159,7 @@ namespace MWClass data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); - data->mCreatureStats.setNeedRecalcDynamicStats(false); - - // store - ptr.getRefData().setCustomData(std::move(data)); + resetter.mPtr = {}; getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index becfb8719f..b8df6747b3 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -303,7 +303,11 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data(new NpcCustomData); + bool recalculate = false; + auto tempData = std::make_unique(); + NpcCustomData* data = tempData.get(); + MWMechanics::CreatureCustomDataResetter resetter(ptr); + ptr.getRefData().setCustomData(std::move(tempData)); MWWorld::LiveCellRef *ref = ptr.get(); @@ -334,8 +338,6 @@ namespace MWClass data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel); data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); - - data->mNpcStats.setNeedRecalcDynamicStats(false); } else { @@ -351,7 +353,7 @@ namespace MWClass autoCalculateAttributes(ref->mBase, data->mNpcStats); autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised); - data->mNpcStats.setNeedRecalcDynamicStats(true); + recalculate = true; } // Persistent actors with 0 health do not play death animation @@ -387,7 +389,9 @@ namespace MWClass data->mNpcStats.setGoldPool(gold); // store - ptr.getRefData().setCustomData(std::move(data)); + resetter.mPtr = {}; + if(recalculate) + data->mNpcStats.recalculateMagicka(); // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d85946dbd5..0af65844f9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -240,10 +240,7 @@ namespace MWMechanics { // magic effects adjustMagicEffects (ptr, duration); - if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) - calculateDynamicStats (ptr); - calculateCreatureStatModifiers (ptr, duration); // fatigue restoration calculateRestoration(ptr, duration); } @@ -654,29 +651,6 @@ namespace MWMechanics updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f); } - void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) - { - CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - - float intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); - - float base = 1.f; - if (ptr == getPlayer()) - base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); - else - base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); - - double magickaFactor = base + - creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; - - DynamicStat magicka = creatureStats.getMagicka(); - float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); - float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0; - magicka.setModified(magicka.getModified() + diff, 0); - magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); - creatureStats.setMagicka(magicka); - } - void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep) { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); @@ -771,14 +745,6 @@ namespace MWMechanics stats.setFatigue (fatigue); } - void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) - { - CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); - - if (creatureStats.needToRecalcDynamicStats()) - calculateDynamicStats(ptr); - } - bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); @@ -1711,10 +1677,6 @@ namespace MWMechanics if (iter->first.getType() == ESM::Creature::sRecordId) soulTrap(iter->first); - // Magic effects will be reset later, and the magic effect that could kill the actor - // needs to be determined now - calculateCreatureStatModifiers(iter->first, 0); - if (cls.isEssential(iter->first)) MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } @@ -1730,8 +1692,6 @@ namespace MWMechanics // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); - // Reset dynamic stats, attributes and skills - calculateCreatureStatModifiers(iter->first, 0); stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism); if (isPlayer) @@ -1816,10 +1776,6 @@ namespace MWMechanics continue; adjustMagicEffects (iter->first, duration); - if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats()) - calculateDynamicStats (iter->first); - - calculateCreatureStatModifiers (iter->first, duration); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first); if (animation) @@ -2209,7 +2165,6 @@ namespace MWMechanics void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) { adjustMagicEffects(ptr, 0.f); - calculateCreatureStatModifiers(ptr, 0.f); } bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 9950a591ab..7d44fd06cd 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -43,10 +43,6 @@ namespace MWMechanics void adjustMagicEffects (const MWWorld::Ptr& creature, float duration); - void calculateDynamicStats (const MWWorld::Ptr& ptr); - - void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); - void calculateRestoration (const MWWorld::Ptr& ptr, float duration); void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer); diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index 04cbb8e9f5..4a587dd662 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -29,4 +29,12 @@ namespace MWMechanics const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; } + + CreatureCustomDataResetter::CreatureCustomDataResetter(const MWWorld::Ptr& ptr) : mPtr(ptr) {} + + CreatureCustomDataResetter::~CreatureCustomDataResetter() + { + if(!mPtr.isEmpty()) + mPtr.getRefData().setCustomData({}); + } } diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index a226fc9cb6..a66d8866bf 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -86,6 +86,14 @@ namespace MWMechanics template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); template void modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount); + + struct CreatureCustomDataResetter + { + MWWorld::Ptr mPtr; + + CreatureCustomDataResetter(const MWWorld::Ptr& ptr); + ~CreatureCustomDataResetter(); + }; } #endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 8ca6e8b766..6710dcd734 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -7,6 +7,7 @@ #include #include +#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" @@ -22,7 +23,7 @@ namespace MWMechanics mTalkedTo (false), mAlarmed (false), mAttacked (false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), - mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), + mFallHeight(0), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) { for (int i=0; i<4; ++i) @@ -146,7 +147,7 @@ namespace MWMechanics mAttributes[index] = value; if (index == ESM::Attribute::Intelligence) - mRecalcMagicka = true; + recalculateMagicka(); else if (index == ESM::Attribute::Strength || index == ESM::Attribute::Willpower || index == ESM::Attribute::Agility || @@ -208,11 +209,10 @@ namespace MWMechanics void CreatureStats::modifyMagicEffects(const MagicEffects &effects) { - if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() - != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier()) - mRecalcMagicka = true; - + bool recalc = effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier(); mMagicEffects.setModifiers(effects); + if(recalc) + recalculateMagicka(); } void CreatureStats::setAiSetting (AiSetting index, Stat value) @@ -400,19 +400,26 @@ namespace MWMechanics return height; } - bool CreatureStats::needToRecalcDynamicStats() + void CreatureStats::recalculateMagicka() { - if (mRecalcMagicka) - { - mRecalcMagicka = false; - return true; - } - return false; - } + auto world = MWBase::Environment::get().getWorld(); + float intelligence = getAttribute(ESM::Attribute::Intelligence).getModified(); - void CreatureStats::setNeedRecalcDynamicStats(bool val) - { - mRecalcMagicka = val; + float base = 1.f; + const auto& player = world->getPlayerPtr(); + if (this == &player.getClass().getCreatureStats(player)) + base = world->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); + else + base = world->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); + + double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; + + DynamicStat magicka = getMagicka(); + float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); + float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0; + magicka.setModified(magicka.getModified() + diff, 0); + magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); + setMagicka(magicka); } void CreatureStats::setKnockedDown(bool value) @@ -532,7 +539,6 @@ namespace MWMechanics state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?) state.mLastHitObject = mLastHitObject; state.mLastHitAttemptObject = mLastHitAttemptObject; - state.mRecalcDynamicStats = mRecalcMagicka; state.mDrawState = mDrawState; state.mLevel = mLevel; state.mActorId = mActorId; @@ -586,7 +592,6 @@ namespace MWMechanics mFallHeight = state.mFallHeight; mLastHitObject = state.mLastHitObject; mLastHitAttemptObject = state.mLastHitAttemptObject; - mRecalcMagicka = state.mRecalcDynamicStats; mDrawState = DrawState_(state.mDrawState); mLevel = state.mLevel; mActorId = state.mActorId; @@ -627,6 +632,8 @@ namespace MWMechanics if (state.mHasAiSettings) for (int i=0; i<4; ++i) mAiSettings[i].readState(state.mAiSettings[i]); + if(state.mRecalcDynamicStats) + recalculateMagicka(); } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index d234d14486..e5c4f6fa41 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -65,8 +65,6 @@ namespace MWMechanics std::string mLastHitObject; // The last object to hit this actor std::string mLastHitAttemptObject; // The last object to attempt to hit this actor - bool mRecalcMagicka; - // For merchants: the last time items were restocked and gold pool refilled. MWWorld::TimeStamp mLastRestock; @@ -103,8 +101,7 @@ namespace MWMechanics DrawState_ getDrawState() const; void setDrawState(DrawState_ state); - bool needToRecalcDynamicStats(); - void setNeedRecalcDynamicStats(bool val); + void recalculateMagicka(); float getFallHeight() const; void addToFallHeight(float height); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 149056e605..e01ed3c425 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -77,7 +77,7 @@ namespace MWMechanics MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr); - npcStats.setNeedRecalcDynamicStats(true); + npcStats.recalculateMagicka(); const ESM::NPC *player = ptr.get()->mBase; @@ -222,7 +222,6 @@ namespace MWMechanics // forced update and current value adjustments mActors.updateActor (ptr, 0); - mActors.updateActor (ptr, 0); for (int i=0; i<3; ++i) { diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 0e67f06ba2..d9e32b8b9f 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -279,7 +279,7 @@ namespace namespace MWMechanics { -void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage) +void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage, bool& recalculateMagicka) { const auto world = MWBase::Environment::get().getWorld(); bool godmode = target == getPlayer() && world->getGodModeState(); @@ -609,7 +609,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co fortifySkill(target, effect, effect.mMagnitude); break; case ESM::MagicEffect::FortifyMaximumMagicka: - target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true); + recalculateMagicka = true; break; case ESM::MagicEffect::AbsorbHealth: case ESM::MagicEffect::AbsorbMagicka: @@ -687,13 +687,14 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac const auto world = MWBase::Environment::get().getWorld(); bool invalid = false; bool receivedMagicDamage = false; + bool recalculateMagicka = false; if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen()) { spellParams.worsen(); for(auto& otherEffect : spellParams.getEffects()) { if(isCorprusEffect(otherEffect)) - applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage); + applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage, recalculateMagicka); } if(target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); @@ -815,7 +816,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac if(effect.mEffectId == ESM::MagicEffect::Corprus) spellParams.worsen(); else - applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage); + applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, recalculateMagicka); effect.mMagnitude = magnitude; magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude)); } @@ -832,6 +833,8 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; if (receivedMagicDamage && target == getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); + if(recalculateMagicka) + target.getClass().getCreatureStats(target).recalculateMagicka(); return false; } @@ -981,7 +984,7 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara fortifySkill(target, effect, -effect.mMagnitude); break; case ESM::MagicEffect::FortifyMaximumMagicka: - target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true); + target.getClass().getCreatureStats(target).recalculateMagicka(); break; case ESM::MagicEffect::AbsorbAttribute: { From 07e32c0fa6be9fa7129c1002b77deb469803d248 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 25 Oct 2021 10:23:16 -0700 Subject: [PATCH 1540/2859] remove object shader path --- apps/openmw/mwworld/weather.cpp | 1 - components/shader/shadervisitor.cpp | 6 +++++- files/shaders/lighting.glsl | 10 ---------- files/shaders/objects_fragment.glsl | 8 ++------ files/shaders/objects_vertex.glsl | 8 ++------ files/shaders/sky_fragment.glsl | 2 +- 6 files changed, 10 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 9055b95ee1..965c690238 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1273,4 +1273,3 @@ namespace MWWorld } } - diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index dcd4874e90..9877ab1863 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -518,7 +518,11 @@ namespace Shader bool simpleLighting = false; node.getUserValue("simpleLighting", simpleLighting); - defineMap["simpleLighting"] = simpleLighting ? "1" : "0"; + if (simpleLighting) + { + defineMap["forcePPL"] = "1"; + defineMap["endLight"] = "0"; + } if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index dc9c297a2f..177017f822 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -87,16 +87,6 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a } } -// Simplest lighting which only takes into account sun and ambient. Currently used for our weather particle systems. -void doSimpleLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight) -{ - vec3 ambientOut, diffuseOut; - - perLightSun(diffuseOut, viewPos, viewNormal); - ambientLight = gl_LightModel.ambient.xyz; - diffuseLight = diffuseOut; -} - vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) { vec3 lightDir = normalize(lcalcPosition(0)); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 26107abb10..6f6cede4e4 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -63,7 +63,7 @@ uniform bool simpleWater; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING ((@normalMap || @forcePPL) && !@simpleLighting) +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; @@ -170,9 +170,6 @@ void main() #endif float shadowing = unshadowedLightRatio(linearDepth); -#if @simpleLighting - gl_FragData[0].xyz *= passLighting; -#else vec3 lighting; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; @@ -183,8 +180,8 @@ void main() lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; clampLightingResult(lighting); #endif + gl_FragData[0].xyz *= lighting; -#endif #if @envMap && !@preLightEnv gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; @@ -210,7 +207,6 @@ void main() #endif gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; } - #if @radialFog float depth; // For the less detailed mesh of simple water we need to recalculate depth on per-pixel basis diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 5f208ffc25..969fe5903e 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -50,7 +50,7 @@ varying vec2 specularMapUV; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING ((@normalMap || @forcePPL) && !@simpleLighting) +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; @@ -126,11 +126,7 @@ void main(void) #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; -#if @simpleLighting - doSimpleLighting(passViewPos, viewNormal, diffuseLight, ambientLight); -#else - doLighting(passViewPos, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); -#endif + doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); vec3 emission = getEmissionColor().xyz * emissiveMult; passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; clampLightingResult(passLighting); diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl index 1ef62ab2cf..cfa3650c02 100644 --- a/files/shaders/sky_fragment.glsl +++ b/files/shaders/sky_fragment.glsl @@ -31,7 +31,7 @@ void paintClouds(inout vec4 color) color.xyz = clamp(color.xyz * gl_FrontMaterial.emission.xyz, 0.0, 1.0); // ease transition between clear color and atmosphere/clouds - color = mix(vec4(gl_Fog.color.xyz, color.a), color, passColor.a * passColor.a); + color = mix(vec4(gl_Fog.color.xyz, color.a), color, passColor.a); } void paintMoon(inout vec4 color) From ba4f1921abf22c0b1af85b52c1eebbfb8c780d47 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 25 Oct 2021 15:04:55 -0700 Subject: [PATCH 1541/2859] overrides --- apps/openmw/mwrender/sky.cpp | 6 ++-- apps/openmw/mwrender/skyutil.cpp | 58 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 9deb6968e3..eb7c4a3882 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -340,7 +340,7 @@ namespace MWRender auto depth = SceneUtil::createDepth(); depth->setWriteMask(false); - mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); @@ -374,7 +374,7 @@ namespace MWRender raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, raindropTex); stateset->setNestRenderBins(false); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); @@ -384,7 +384,7 @@ namespace MWRender mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + stateset->setAttributeAndModes(mat); osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index ae689547fd..9932733fa1 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -126,7 +126,7 @@ namespace MWRender void setDefaults(osg::StateSet* stateset) override { - stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(MWRender::createUnlitMaterial()); } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override @@ -292,7 +292,7 @@ namespace MWRender mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + stateset->setAttributeAndModes(mat); cv->pushStateSet(stateset); traverse(node, cv); @@ -359,9 +359,9 @@ namespace MWRender { if (mForceShaders) { - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon))); + stateset->setTextureAttributeAndModes(0, mPhaseTex); + stateset->setTextureAttributeAndModes(1, mCircleTex); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("moonBlend", osg::Vec4f{})); @@ -372,15 +372,15 @@ namespace MWRender } else { - stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, mPhaseTex); osg::ref_ptr texEnv = new osg::TexEnvCombine; texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor - stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, texEnv); - stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, mCircleTex); osg::ref_ptr texEnv2 = new osg::TexEnvCombine; texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); @@ -389,7 +389,7 @@ namespace MWRender texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency - stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, texEnv2); stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } @@ -477,7 +477,7 @@ namespace MWRender void AtmosphereUpdater::setDefaults(osg::StateSet* stateset) { stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere))); } void AtmosphereUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) @@ -501,8 +501,8 @@ namespace MWRender { if (mForceShaders) { - stateset->addUniform(new osg::Uniform("opacity", 0.f), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("opacity", 0.f)); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night))); } else { @@ -561,18 +561,18 @@ namespace MWRender stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::ref_ptr texmat = new osg::TexMat; - stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, texmat); if (mForceShaders) { stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("opacity", 1.f), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("opacity", 1.f)); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds))); } else { - stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, texmat); // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); @@ -582,7 +582,7 @@ namespace MWRender texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); - stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, texEnvCombine); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); @@ -715,8 +715,8 @@ namespace MWRender sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); sunTex->setName("diffuseMap"); - mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex); + mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); osg::ref_ptr queryNode = new osg::Group; // Need to render after the world geometry so we can correctly test for occlusions @@ -726,14 +726,14 @@ namespace MWRender // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun osg::ref_ptr alphaFunc = new osg::AlphaFunc; alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setAttributeAndModes(alphaFunc); + stateset->setTextureAttributeAndModes(0, sunTex); + stateset->setAttributeAndModes(createUnlitMaterial()); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query))); // 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, osg::StateAttribute::ON); + stateset->setAttributeAndModes(colormask); mTransform->addChild(queryNode); @@ -829,7 +829,7 @@ namespace MWRender depth->setZNear(far); depth->setZFar(far); depth->setWriteMask(false); - queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); + queryStateSet->setAttributeAndModes(depth); } else { @@ -859,11 +859,11 @@ namespace MWRender osg::StateSet* stateset = geom->getOrCreateStateSet(); - stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, tex); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); mSunFlashNode = group; @@ -898,13 +898,13 @@ namespace MWRender stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare))); // set up additive blending osg::ref_ptr blendFunc = new osg::BlendFunc; blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); blendFunc->setDestination(osg::BlendFunc::ONE); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + stateset->setAttributeAndModes(blendFunc); mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); mSunGlareNode = camera; From 03733a30b0c45a704010b43d61bd4c0fa17f74b4 Mon Sep 17 00:00:00 2001 From: wareya Date: Tue, 26 Oct 2021 03:07:33 +0000 Subject: [PATCH 1542/2859] Update water_fragment.glsl --- files/shaders/water_fragment.glsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index ac12b355ee..8d608e53c4 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -105,11 +105,11 @@ vec4 circle(vec2 coords, vec2 uv, float adjusted_time) float height = blip(ringfollower); vec4 ret = vec4(normal.x, normal.y, height, height); ret.xyw *= energy; - ret.xyz = normalize(ret.xyz); + ret.xyz = normalize(ret.x, ret.y, ret.z+0.001); return ret; } -const float RAIN_RING_TIME_OFFSET = 1/6.0; +const float RAIN_RING_TIME_OFFSET = 1.0/6.0; vec4 rain(vec2 uv, float time) { vec2 i_part = floor(uv * RAIN_RIPPLE_GAPS); From 4ec927829f41a2b204feaf477bdc2cc402bbe47d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 10 Oct 2021 16:28:45 +0200 Subject: [PATCH 1543/2859] Give each reflect and spell absorption effect a chance to apply --- CHANGELOG.md | 2 + apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/activespells.cpp | 30 ++++- apps/openmw/mwmechanics/activespells.hpp | 2 + apps/openmw/mwmechanics/spellabsorption.cpp | 82 ------------ apps/openmw/mwmechanics/spellabsorption.hpp | 17 --- apps/openmw/mwmechanics/spellcasting.cpp | 63 +--------- apps/openmw/mwmechanics/spellcasting.hpp | 2 +- apps/openmw/mwmechanics/spelleffects.cpp | 131 ++++++++++++++++---- apps/openmw/mwmechanics/spelleffects.hpp | 7 +- apps/openmw/mwworld/esmloader.cpp | 8 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- components/esm/activespells.hpp | 4 +- 14 files changed, 162 insertions(+), 192 deletions(-) delete mode 100644 apps/openmw/mwmechanics/spellabsorption.cpp delete mode 100644 apps/openmw/mwmechanics/spellabsorption.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d609b0ae7f..d2968ae9d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop + Bug #6253: Multiple instances of Reflect stack additively + Bug #6255: Reflect is different from vanilla Bug #6258: Barter menu glitches out when modifying prices Bug #6273: Respawning NPCs rotation is inconsistent Bug #6282: Laura craft doesn't follow the player character diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7334d893db..89620d6dc1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -94,7 +94,7 @@ add_openmw_dir (mwmechanics aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning character actors objects aistate trading weaponpriority spellpriority weapontype spellutil - spellabsorption spelleffects + spelleffects ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index fc35e40b74..d435bd6c44 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,5 +1,7 @@ #include "activespells.hpp" +#include + #include #include @@ -14,6 +16,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwrender/animation.hpp" + #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -96,6 +100,11 @@ namespace MWMechanics , mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening}) {} + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor) + : mId(params.mId), mDisplayName(params.mDisplayName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) + , mSlot(params.mSlot), mType(params.mType), mWorsenings(-1) + {} + ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const { ESM::ActiveSpells::ActiveSpellParams params; @@ -220,10 +229,19 @@ namespace MWMechanics { const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid? bool removedSpell = false; + std::optional reflected; for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) { - bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration); - if(remove) + auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration); + if(result == MagicApplicationResult::REFLECTED) + { + if(!reflected) + reflected = {*spellIt, ptr}; + auto& reflectedEffect = reflected->mEffects.emplace_back(*it); + reflectedEffect.mFlags = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; + it = spellIt->mEffects.erase(it); + } + else if(result == MagicApplicationResult::REMOVED) it = spellIt->mEffects.erase(it); else ++it; @@ -231,6 +249,14 @@ namespace MWMechanics if(removedSpell) break; } + if(reflected) + { + const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find("VFX_Reflect"); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if(animation && !reflectStatic->mModel.empty()) + animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string()); + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); + } if(removedSpell) continue; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 0538d7a8b7..524bdc0475 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -50,6 +50,8 @@ namespace MWMechanics ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor); + ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor); + ESM::ActiveSpells::ActiveSpellParams toEsm() const; friend class ActiveSpells; diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp deleted file mode 100644 index 82531132ca..0000000000 --- a/apps/openmw/mwmechanics/spellabsorption.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "spellabsorption.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwrender/animation.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" - -#include "creaturestats.hpp" -#include "spellutil.hpp" - -namespace MWMechanics -{ - float getProbability(const MWMechanics::ActiveSpells& activeSpells) - { - float probability = 0.f; - for(const auto& params : activeSpells) - { - for(const auto& effect : params.getEffects()) - { - if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption) - { - if(probability == 0.f) - probability = effect.mMagnitude / 100; - else - { - // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. - // Real absorption probability will be the (1 - total fail chance) in this case. - float failProbability = 1.f - probability; - failProbability *= 1.f - effect.mMagnitude / 100; - probability = 1.f - failProbability; - } - } - } - } - return static_cast(probability * 100); - } - - bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) - { - if (spellId.empty() || target.isEmpty() || caster == target || !target.getClass().isActor()) - return false; - - CreatureStats& stats = target.getClass().getCreatureStats(target); - if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f) - return false; - - int chance = getProbability(stats.getActiveSpells()); - if (Misc::Rng::roll0to99() >= chance) - return false; - - const auto& esmStore = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Static* absorbStatic = esmStore.get().find("VFX_Absorb"); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation && !absorbStatic->mModel.empty()) - animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string()); - const ESM::Spell* spell = esmStore.get().search(spellId); - int spellCost = 0; - if (spell) - { - spellCost = MWMechanics::calcSpellCost(*spell); - } - else - { - const ESM::Enchantment* enchantment = esmStore.get().search(spellId); - if (enchantment) - spellCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), caster); - } - - // Magicka is increased by the cost of the spell - DynamicStat magicka = stats.getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spellCost); - stats.setMagicka(magicka); - return true; - } - -} diff --git a/apps/openmw/mwmechanics/spellabsorption.hpp b/apps/openmw/mwmechanics/spellabsorption.hpp deleted file mode 100644 index 0fe501df91..0000000000 --- a/apps/openmw/mwmechanics/spellabsorption.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MWMECHANICS_SPELLABSORPTION_H -#define MWMECHANICS_SPELLABSORPTION_H - -#include - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - // Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target. - bool absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target); -} - -#endif diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 026f6b5f19..2edc437752 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -22,38 +22,11 @@ #include "actorutil.hpp" #include "aifollow.hpp" #include "creaturestats.hpp" -#include "spellabsorption.hpp" #include "spelleffects.hpp" #include "spellutil.hpp" #include "summoning.hpp" #include "weapontype.hpp" -namespace -{ - bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects) - { - if (caster.isEmpty() || caster == target || !target.getClass().isActor()) - return false; - - bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; - bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable; - if (!isHarmful || isUnreflectable) - return false; - - float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); - if (Misc::Rng::roll0to99() >= reflect) - return false; - - const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation && !reflectStatic->mModel.empty()) - animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string()); - reflectedEffects.mList.emplace_back(effect); - return true; - } -} - namespace MWMechanics { CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) @@ -82,7 +55,7 @@ namespace MWMechanics } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, - const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) + const ESM::EffectList &effects, ESM::RangeType range, bool exploded) { const bool targetIsActor = !target.isEmpty() && target.getClass().isActor(); if (targetIsActor) @@ -123,7 +96,6 @@ namespace MWMechanics } } - ESM::EffectList reflectedEffects; ActiveSpells::ActiveSpellParams params(*this, caster); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); @@ -136,9 +108,6 @@ namespace MWMechanics // throughout the iteration of this spell's // effects, we display a "can't re-cast" message - // Try absorbing the spell. Some handling must still happen for absorbed effects. - bool absorbed = absorbSpell(mId, caster, target); - int currentEffectIndex = 0; for (std::vector::const_iterator effectIt (effects.mList.begin()); !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) @@ -167,19 +136,6 @@ namespace MWMechanics && (caster.isEmpty() || !caster.getClass().isActor())) continue; - // Notify the target actor they've been hit - bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; - if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) - target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); - - // Avoid proceeding further for absorbed spells. - if (absorbed) - continue; - - // Reflect harmful effects - if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) - continue; - ActiveSpells::ActiveEffect effect; effect.mEffectId = effectIt->mEffectID; effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; @@ -189,13 +145,8 @@ namespace MWMechanics effect.mTimeLeft = 0.f; effect.mEffectIndex = currentEffectIndex; effect.mFlags = ESM::ActiveEffect::Flag_None; - - // Avoid applying harmful effects to the player in god mode - if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) - { - effect.mMinMagnitude = 0; - effect.mMaxMagnitude = 0; - } + if(mManualSpell) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; @@ -209,14 +160,14 @@ namespace MWMechanics // add to list of active effects, to apply in next frame params.getEffects().emplace_back(effect); - bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; + bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth) { // If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar. MWBase::Environment::get().getWindowManager()->setEnemy(target); } - if (targetIsActor || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + if (!targetIsActor && magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) { playEffects(target, *magicEffect); } @@ -227,9 +178,6 @@ namespace MWMechanics if (!target.isEmpty()) { - if (!reflectedEffects.mList.empty()) - inflict(caster, target, reflectedEffects, range, true, exploded); - if (!params.getEffects().empty()) { if(targetIsActor) @@ -237,6 +185,7 @@ namespace MWMechanics else { // Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway + // and we can ignore reflection since non-actors cannot reflect spells for(auto& effect : params.getEffects()) applyMagicEffect(target, caster, params, effect, 0.f); } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index a21ea33e0b..4d9cc3a7de 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -62,7 +62,7 @@ namespace MWMechanics /// @note \a target can be any type of object, not just actors. /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, - const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); + const ESM::EffectList& effects, ESM::RangeType range, bool exploded=false); }; void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index d9e32b8b9f..7907642f5e 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -16,6 +16,7 @@ #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellresistance.hpp" +#include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/summoning.hpp" #include "../mwrender/animation.hpp" @@ -261,6 +262,97 @@ namespace return false; } + void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + { + const auto& esmStore = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Static* absorbStatic = esmStore.get().find("VFX_Absorb"); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + if (animation && !absorbStatic->mModel.empty()) + animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string()); + const ESM::Spell* spell = esmStore.get().search(spellId); + int spellCost = 0; + if (spell) + { + spellCost = MWMechanics::calcSpellCost(*spell); + } + else + { + const ESM::Enchantment* enchantment = esmStore.get().search(spellId); + if (enchantment) + spellCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), caster); + } + + // Magicka is increased by the cost of the spell + auto& stats = target.getClass().getCreatureStats(target); + auto magicka = stats.getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spellCost); + stats.setMagicka(magicka); + } + + MWMechanics::MagicApplicationResult applyProtections(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, const ESM::MagicEffect* magicEffect) + { + 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) + { + bool canReflect = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) && + !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) && magnitudes.get(ESM::MagicEffect::Reflect).getMagnitude() > 0.f; + bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f; + if(canReflect || canAbsorb) + { + for(const auto& activeParam : stats.getActiveSpells()) + { + for(const auto& activeEffect : activeParam.getEffects()) + { + if(!(activeEffect.mFlags & ESM::ActiveEffect::Flag_Applied)) + continue; + if(activeEffect.mEffectId == ESM::MagicEffect::Reflect) + { + if(canReflect && Misc::Rng::roll0to99() < activeEffect.mMagnitude) + { + return MWMechanics::MagicApplicationResult::REFLECTED; + } + } + else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption) + { + if(canAbsorb && Misc::Rng::roll0to99() < activeEffect.mMagnitude) + { + absorbSpell(spellParams.getId(), caster, target); + return MWMechanics::MagicApplicationResult::REMOVED; + } + } + } + } + } + } + // Notify the target actor they've been hit + bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; + if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) + target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); + // 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().getWorld()->getStore().get().search(spellParams.getId()); + float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target == MWMechanics::getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else if (caster == MWMechanics::getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + return MWMechanics::MagicApplicationResult::REMOVED; + } + effect.mMinMagnitude *= magnitudeMult; + effect.mMaxMagnitude *= magnitudeMult; + } + return MWMechanics::MagicApplicationResult::APPLIED; + } + static const std::map sBoundItemsMap{ {ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"}, {ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"}, @@ -682,7 +774,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co } } -bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) +MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) { const auto world = MWBase::Environment::get().getWorld(); bool invalid = false; @@ -698,13 +790,13 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac } if(target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); - return false; + return MagicApplicationResult::APPLIED; } else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled()) { if(target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); - return true; + return MagicApplicationResult::REMOVED; } const auto* magicEffect = world->getStore().get().find(effect.mEffectId); if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) @@ -712,10 +804,10 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) { effect.mTimeLeft -= dt; - return false; + return MagicApplicationResult::APPLIED; } else if(!dt) - return false; + return MagicApplicationResult::APPLIED; } if(effect.mEffectId == ESM::MagicEffect::Lock) { @@ -771,28 +863,19 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac } else { - auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); - if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & (ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Ignore_Resistances))) + auto& stats = target.getClass().getCreatureStats(target); + auto& magnitudes = stats.getMagicEffects(); + if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) { - const ESM::Spell* spell = nullptr; - if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary) - spell = world->getStore().get().search(spellParams.getId()); - float magnitudeMult = getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); - if (magnitudeMult == 0) - { - // Fully resisted, show message - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); - return true; - } - effect.mMinMagnitude *= magnitudeMult; - effect.mMaxMagnitude *= magnitudeMult; + MagicApplicationResult result = applyProtections(target, caster, spellParams, effect, magicEffect); + if(result != MagicApplicationResult::APPLIED) + return result; } float oldMagnitude = 0.f; if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) oldMagnitude = effect.mMagnitude; + else if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + playEffects(target, *magicEffect); float magnitude = roll(effect); //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here effect.mMagnitude = magnitude; @@ -810,7 +893,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac effect.mMagnitude = oldMagnitude; effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; effect.mTimeLeft -= dt; - return false; + return MagicApplicationResult::APPLIED; } } if(effect.mEffectId == ESM::MagicEffect::Corprus) @@ -835,7 +918,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); if(recalculateMagicka) target.getClass().getCreatureStats(target).recalculateMagicka(); - return false; + return MagicApplicationResult::APPLIED; } void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) diff --git a/apps/openmw/mwmechanics/spelleffects.hpp b/apps/openmw/mwmechanics/spelleffects.hpp index a9e7b066f3..2861d0d64a 100644 --- a/apps/openmw/mwmechanics/spelleffects.hpp +++ b/apps/openmw/mwmechanics/spelleffects.hpp @@ -10,8 +10,13 @@ namespace MWMechanics { + enum class MagicApplicationResult + { + APPLIED, REMOVED, REFLECTED + }; + // Applies a tick of a single effect. Returns true if the effect should be removed immediately - bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt); + MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt); // Undoes permanent effects created by ESM::MagicEffect::AppliedOnce void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index a01128fe36..06debf4a97 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -104,8 +104,8 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index) effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; - // Prevent recalculation of resistances - effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances; + // 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; } else { @@ -172,8 +172,8 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index) effect.mDuration = -1; effect.mTimeLeft = -1; effect.mEffectIndex = static_cast(effectIndex); - // Prevent recalculation of resistances - effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances; + // 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; params.mEffects.emplace_back(effect); } auto [begin, end] = equippedItems.equal_range(id); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 9ee137fab6..1f07a7ecee 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -546,7 +546,7 @@ namespace MWWorld cast.mId = magicBoltState.mSpellId; cast.mSourceName = magicBoltState.mSourceName; cast.mSlot = magicBoltState.mSlot; - cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); + cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, true); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName, false, magicBoltState.mSlot); magicBoltState.mToDelete = true; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ccd78170bb..a1501f1537 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3791,7 +3791,7 @@ namespace MWWorld cast.mSlot = slot; ESM::EffectList effectsToApply; effectsToApply.mList = applyPair.second; - cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true); + cast.inflict(applyPair.first, caster, effectsToApply, rangeType, true); } } diff --git a/components/esm/activespells.hpp b/components/esm/activespells.hpp index 8b5f1f1946..a79366f9c2 100644 --- a/components/esm/activespells.hpp +++ b/components/esm/activespells.hpp @@ -22,7 +22,9 @@ namespace ESM Flag_None = 0, Flag_Applied = 1 << 0, Flag_Remove = 1 << 1, - Flag_Ignore_Resistances = 1 << 2 + Flag_Ignore_Resistances = 1 << 2, + Flag_Ignore_Reflect = 1 << 3, + Flag_Ignore_SpellAbsorption = 1 << 4 }; int mEffectId; From 6aab2468790c456fe766b908f4fbdca0da617aec Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 26 Sep 2021 15:14:53 +0200 Subject: [PATCH 1544/2859] Add ESM records that are needed to store Lua scripts configuration; Use ptr.getType() (i.e. esm record names) instead of typeid(ptr.getClass()) in apps/openmw/mwlua. --- apps/openmw/mwlua/object.cpp | 83 ++++++++++++++++------------ apps/openmw/mwlua/object.hpp | 12 +++- apps/openmw/mwlua/objectbindings.cpp | 9 ++- components/esm/luascripts.cpp | 33 +++++++++-- components/esm/luascripts.hpp | 45 +++++++++++++-- 5 files changed, 129 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 266e628dd6..69206e8c37 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -1,19 +1,6 @@ #include "object.hpp" -#include "../mwclass/activator.hpp" -#include "../mwclass/armor.hpp" -#include "../mwclass/book.hpp" -#include "../mwclass/clothing.hpp" -#include "../mwclass/container.hpp" -#include "../mwclass/creature.hpp" -#include "../mwclass/door.hpp" -#include "../mwclass/ingredient.hpp" -#include "../mwclass/light.hpp" -#include "../mwclass/misc.hpp" -#include "../mwclass/npc.hpp" -#include "../mwclass/potion.hpp" -#include "../mwclass/static.hpp" -#include "../mwclass/weapon.hpp" +#include namespace MWLua { @@ -23,28 +10,34 @@ namespace MWLua return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile); } - const static std::map classNames = { - {typeid(MWClass::Activator), "Activator"}, - {typeid(MWClass::Armor), "Armor"}, - {typeid(MWClass::Book), "Book"}, - {typeid(MWClass::Clothing), "Clothing"}, - {typeid(MWClass::Container), "Container"}, - {typeid(MWClass::Creature), "Creature"}, - {typeid(MWClass::Door), "Door"}, - {typeid(MWClass::Ingredient), "Ingredient"}, - {typeid(MWClass::Light), "Light"}, - {typeid(MWClass::Miscellaneous), "Miscellaneous"}, - {typeid(MWClass::Npc), "NPC"}, - {typeid(MWClass::Potion), "Potion"}, - {typeid(MWClass::Static), "Static"}, - {typeid(MWClass::Weapon), "Weapon"}, + struct LuaObjectTypeInfo + { + std::string_view mName; + ESM::LuaScriptCfg::Flags mFlag = 0; + }; + + const static std::unordered_map luaObjectTypeInfo = { + {ESM::REC_ACTI, {"Activator", ESM::LuaScriptCfg::sActivator}}, + {ESM::REC_ARMO, {"Armor", ESM::LuaScriptCfg::sArmor}}, + {ESM::REC_BOOK, {"Book", ESM::LuaScriptCfg::sBook}}, + {ESM::REC_CLOT, {"Clothing", ESM::LuaScriptCfg::sClothing}}, + {ESM::REC_CONT, {"Container", ESM::LuaScriptCfg::sContainer}}, + {ESM::REC_CREA, {"Creature", ESM::LuaScriptCfg::sCreature}}, + {ESM::REC_DOOR, {"Door", ESM::LuaScriptCfg::sDoor}}, + {ESM::REC_INGR, {"Ingredient", ESM::LuaScriptCfg::sIngredient}}, + {ESM::REC_LIGH, {"Light", ESM::LuaScriptCfg::sLight}}, + {ESM::REC_MISC, {"Miscellaneous", ESM::LuaScriptCfg::sMiscItem}}, + {ESM::REC_NPC_, {"NPC", ESM::LuaScriptCfg::sNPC}}, + {ESM::REC_ALCH, {"Potion", ESM::LuaScriptCfg::sPotion}}, + {ESM::REC_STAT, {"Static"}}, + {ESM::REC_WEAP, {"Weapon", ESM::LuaScriptCfg::sWeapon}}, }; - std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback) + std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback) { - auto it = classNames.find(cls_type); - if (it != classNames.end()) - return it->second; + auto it = luaObjectTypeInfo.find(type); + if (it != luaObjectTypeInfo.end()) + return it->second.mName; else return fallback; } @@ -55,13 +48,31 @@ namespace MWLua return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; } - std::string_view getMWClassName(const MWWorld::Ptr& ptr) + std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr) { + // Behaviour of this function is a part of OpenMW Lua API. We can not just return + // `ptr.getTypeDescription()` because its implementation is distributed over many files + // and can be accidentally changed. We use `ptr.getTypeDescription()` only as a fallback + // for types that are not present in `luaObjectTypeInfo` (for such types result stability + // is not necessary because they are not listed in OpenMW Lua documentation). if (ptr.getCellRef().getRefIdRef() == "player") return "Player"; if (isMarker(ptr)) return "Marker"; - return getMWClassName(typeid(ptr.getClass())); + return getLuaObjectTypeName(static_cast(ptr.getType()), /*fallback=*/ptr.getTypeDescription()); + } + + ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr) + { + if (ptr.getCellRef().getRefIdRef() == "player") + return ESM::LuaScriptCfg::sPlayer; + if (isMarker(ptr)) + return 0; + auto it = luaObjectTypeInfo.find(static_cast(ptr.getType())); + if (it != luaObjectTypeInfo.end()) + return it->second.mFlag; + else + return 0; } std::string ptrToString(const MWWorld::Ptr& ptr) @@ -69,7 +80,7 @@ namespace MWLua std::string res = "object"; res.append(idToString(getId(ptr))); res.append(" ("); - res.append(getMWClassName(ptr)); + res.append(getLuaObjectTypeName(ptr)); res.append(", "); res.append(ptr.getCellRef().getRefIdRef()); res.append(")"); diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index c0b6bf1919..5b1b5df74e 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -19,8 +21,12 @@ namespace MWLua std::string idToString(const ObjectId& id); std::string ptrToString(const MWWorld::Ptr& ptr); bool isMarker(const MWWorld::Ptr& ptr); - std::string_view getMWClassName(const std::type_index& cls_type, std::string_view fallback = "Unknown"); - std::string_view getMWClassName(const MWWorld::Ptr& ptr); + std::string_view getLuaObjectTypeName(ESM::RecNameInts recordType, std::string_view fallback = "Unknown"); + std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr); + + // Each script has a set of flags that controls to which objects the script should be + // automatically attached. This function maps each object types to one of the flags. + ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr); // Holds a mapping ObjectId -> MWWord::Ptr. class ObjectRegistry @@ -64,7 +70,7 @@ namespace MWLua ObjectId id() const { return mId; } std::string toString() const; - std::string_view type() const { return getMWClassName(ptr()); } + std::string_view type() const { return getLuaObjectTypeName(ptr()); } // Updates and returns the underlying Ptr. Throws an exception if object is not available. const MWWorld::Ptr& ptr() const; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index b7607c8b2c..07b7921a09 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -42,13 +42,12 @@ namespace MWLua template using Cell = std::conditional_t, LCell, GCell>; - template - static const MWWorld::Ptr& requireClass(const MWWorld::Ptr& ptr) + static const MWWorld::Ptr& requireRecord(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr) { - if (typeid(Class) != typeid(ptr.getClass())) + if (ptr.getType() != recordType) { std::string msg = "Requires type '"; - msg.append(getMWClassName(typeid(Class))); + msg.append(getLuaObjectTypeName(recordType)); msg.append("', but applied to "); msg.append(ptrToString(ptr)); throw std::runtime_error(msg); @@ -189,7 +188,7 @@ namespace MWLua template static void addDoorBindings(sol::usertype& objectT, const Context& context) { - auto ptr = [](const ObjectT& o) -> const MWWorld::Ptr& { return requireClass(o.ptr()); }; + auto ptr = [](const ObjectT& o) -> const MWWorld::Ptr& { return requireRecord(ESM::REC_DOOR, o.ptr()); }; objectT["isTeleport"] = sol::readonly_property([ptr](const ObjectT& o) { diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index 7ff841312a..c831cbbbfc 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -5,13 +5,15 @@ // List of all records, that are related to Lua. // -// Record: -// LUAM - MWLua::LuaManager +// Records: +// LUAL - LuaScriptsCfg - list of all scripts (in content files) +// LUAM - MWLua::LuaManager (in saves) // // Subrecords: +// LUAF - LuaScriptCfg::mFlags // LUAW - Start of MWLua::WorldView data // LUAE - Start of MWLua::LocalEvent or MWLua::GlobalEvent (eventName) -// LUAS - Start LuaUtil::ScriptsContainer data (scriptName) +// LUAS - VFS path to a Lua script // LUAD - Serialized Lua variable // LUAT - MWLua::ScriptsContainer::Timer // LUAC - Name of a timer callback (string) @@ -37,6 +39,28 @@ std::string ESM::loadLuaBinaryData(ESMReader& esm) return data; } +void ESM::LuaScriptsCfg::load(ESMReader& esm) +{ + while (esm.isNextSub("LUAS")) + { + std::string name = esm.getHString(); + uint64_t flags; + esm.getHNT(flags, "LUAF"); + std::string data = loadLuaBinaryData(esm); + mScripts.push_back({std::move(name), std::move(data), flags}); + } +} + +void ESM::LuaScriptsCfg::save(ESMWriter& esm) const +{ + for (const LuaScriptCfg& script : mScripts) + { + esm.writeHNString("LUAS", script.mScriptPath); + esm.writeHNT("LUAF", script.mFlags); + saveLuaBinaryData(esm, script.mInitializationData); + } +} + void ESM::LuaScripts::load(ESMReader& esm) { while (esm.isNextSub("LUAS")) @@ -63,8 +87,7 @@ void ESM::LuaScripts::save(ESMWriter& esm) const for (const LuaScript& script : mScripts) { esm.writeHNString("LUAS", script.mScriptPath); - if (!script.mData.empty()) - saveLuaBinaryData(esm, script.mData); + saveLuaBinaryData(esm, script.mData); for (const LuaTimer& timer : script.mTimers) { esm.startSubRecord("LUAT"); diff --git a/components/esm/luascripts.hpp b/components/esm/luascripts.hpp index f268f41536..e6f7113c16 100644 --- a/components/esm/luascripts.hpp +++ b/components/esm/luascripts.hpp @@ -9,7 +9,44 @@ namespace ESM class ESMReader; class ESMWriter; - // Storage structure for LuaUtil::ScriptsContainer. This is not a top-level record. + // LuaScriptCfg, LuaScriptsCfg are used in content files. + + struct LuaScriptCfg + { + using Flags = uint64_t; + static constexpr Flags sGlobal = 1ull << 0; + static constexpr Flags sCustom = 1ull << 1; // local; can be attached/detached by a global script + static constexpr Flags sPlayer = 1ull << 2; // auto attach to players + // auto attach for other classes: + static constexpr Flags sActivator = 1ull << 3; + static constexpr Flags sArmor = 1ull << 4; + static constexpr Flags sBook = 1ull << 5; + static constexpr Flags sClothing = 1ull << 6; + static constexpr Flags sContainer = 1ull << 7; + static constexpr Flags sCreature = 1ull << 8; + static constexpr Flags sDoor = 1ull << 9; + static constexpr Flags sIngredient = 1ull << 10; + static constexpr Flags sLight = 1ull << 11; + static constexpr Flags sMiscItem = 1ull << 12; + static constexpr Flags sNPC = 1ull << 13; + static constexpr Flags sPotion = 1ull << 14; + static constexpr Flags sWeapon = 1ull << 15; + + std::string mScriptPath; + std::string mInitializationData; // Serialized Lua table. It is a binary data. Can contain '\0'. + Flags mFlags; // bitwise OR of Flags. + }; + + struct LuaScriptsCfg + { + std::vector mScripts; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + // LuaTimer, LuaScript, LuaScripts are used in saved game files. + // Storage structure for LuaUtil::ScriptsContainer. These are not top-level records. // Used either for global scripts or for local scripts on a specific object. struct LuaTimer @@ -37,11 +74,11 @@ namespace ESM { std::vector mScripts; - void load (ESMReader &esm); - void save (ESMWriter &esm) const; + void load(ESMReader &esm); + void save(ESMWriter &esm) const; }; - // Saves binary string `data` (can contain '\0') as record LUAD. + // Saves binary string `data` (can contain '\0') as LUAD record. void saveLuaBinaryData(ESM::ESMWriter& esm, const std::string& data); // Loads LUAD as binary string. If next subrecord is not LUAD, then returns an empty string. From 33d71be81f8254c24de685eee3c0ba7cf93ca005 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 6 Oct 2021 20:59:48 +0200 Subject: [PATCH 1545/2859] Add LuaUtil::ScriptsConfiguration --- apps/openmw_test_suite/CMakeLists.txt | 2 +- .../lua/test_configuration.cpp | 58 ++++++ apps/openmw_test_suite/lua/testing_util.hpp | 2 +- components/lua/configuration.cpp | 165 ++++++++++++++++++ components/lua/configuration.hpp | 37 ++++ components/misc/stringops.hpp | 21 ++- 6 files changed, 276 insertions(+), 9 deletions(-) create mode 100644 apps/openmw_test_suite/lua/test_configuration.cpp create mode 100644 components/lua/configuration.cpp create mode 100644 components/lua/configuration.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index a2a35e3aab..2b96d4c633 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -20,7 +20,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) lua/test_utilpackage.cpp lua/test_serialization.cpp lua/test_querypackage.cpp - lua/test_omwscriptsparser.cpp + lua/test_configuration.cpp misc/test_stringops.cpp misc/test_endianness.cpp diff --git a/apps/openmw_test_suite/lua/test_configuration.cpp b/apps/openmw_test_suite/lua/test_configuration.cpp new file mode 100644 index 0000000000..054ea8cbda --- /dev/null +++ b/apps/openmw_test_suite/lua/test_configuration.cpp @@ -0,0 +1,58 @@ +#include "gmock/gmock.h" +#include + +#include + +#include "testing_util.hpp" + +namespace +{ + + TEST(LuaConfigurationTest, ValidConfiguration) + { + ESM::LuaScriptsCfg cfg; + LuaUtil::parseOMWScripts(cfg, R"X( + # Lines starting with '#' are comments + GLOBAL: my_mod/#some_global_script.lua + + # Script that will be automatically attached to the player + PLAYER :my_mod/player.lua + CUSTOM : my_mod/some_other_script.lua + NPC , CREATURE PLAYER : my_mod/some_other_script.lua)X"); + LuaUtil::parseOMWScripts(cfg, ":my_mod/player.LUA \r\nCONTAINER,CUSTOM: my_mod/container.lua\r\n"); + + ASSERT_EQ(cfg.mScripts.size(), 6); + EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[0]), "GLOBAL : my_mod/#some_global_script.lua"); + EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[1]), "PLAYER : my_mod/player.lua"); + EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[2]), "CUSTOM : my_mod/some_other_script.lua"); + EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[3]), "CREATURE NPC PLAYER : my_mod/some_other_script.lua"); + EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[4]), ": my_mod/player.LUA"); + EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[5]), "CONTAINER CUSTOM : my_mod/container.lua"); + + LuaUtil::ScriptsConfiguration conf; + conf.init(std::move(cfg)); + ASSERT_EQ(conf.size(), 3); + EXPECT_EQ(LuaUtil::scriptCfgToString(conf[0]), "GLOBAL : my_mod/#some_global_script.lua"); + // cfg.mScripts[1] is overridden by cfg.mScripts[4] + // cfg.mScripts[2] is overridden by cfg.mScripts[3] + EXPECT_EQ(LuaUtil::scriptCfgToString(conf[1]), "CREATURE NPC PLAYER : my_mod/some_other_script.lua"); + // cfg.mScripts[4] is removed because there are no flags + EXPECT_EQ(LuaUtil::scriptCfgToString(conf[2]), "CONTAINER CUSTOM : my_mod/container.lua"); + + cfg = ESM::LuaScriptsCfg(); + conf.init(std::move(cfg)); + ASSERT_EQ(conf.size(), 0); + } + + TEST(LuaConfigurationTest, Errors) + { + ESM::LuaScriptsCfg cfg; + EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "GLOBAL: something"), + "Lua script should have suffix '.lua', got: GLOBAL: something"); + EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "something.lua"), + "No flags found in: something.lua"); + EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "GLOBAL, PLAYER: something.lua"), + "Global script can not have local flags"); + } + +} diff --git a/apps/openmw_test_suite/lua/testing_util.hpp b/apps/openmw_test_suite/lua/testing_util.hpp index 28c4d59930..2f6810350f 100644 --- a/apps/openmw_test_suite/lua/testing_util.hpp +++ b/apps/openmw_test_suite/lua/testing_util.hpp @@ -52,7 +52,7 @@ namespace } #define EXPECT_ERROR(X, ERR_SUBSTR) try { X; FAIL() << "Expected error"; } \ - catch (std::exception& e) { EXPECT_THAT(e.what(), HasSubstr(ERR_SUBSTR)); } + catch (std::exception& e) { EXPECT_THAT(e.what(), ::testing::HasSubstr(ERR_SUBSTR)); } } diff --git a/components/lua/configuration.cpp b/components/lua/configuration.cpp new file mode 100644 index 0000000000..4598ed2508 --- /dev/null +++ b/components/lua/configuration.cpp @@ -0,0 +1,165 @@ +#include "configuration.hpp" + +#include +#include +#include +#include + +#include + +namespace LuaUtil +{ + + namespace + { + const std::map> flagsByName{ + {"GLOBAL", ESM::LuaScriptCfg::sGlobal}, + {"CUSTOM", ESM::LuaScriptCfg::sCustom}, + {"PLAYER", ESM::LuaScriptCfg::sPlayer}, + {"ACTIVATOR", ESM::LuaScriptCfg::sActivator}, + {"ARMOR", ESM::LuaScriptCfg::sArmor}, + {"BOOK", ESM::LuaScriptCfg::sBook}, + {"CLOTHING", ESM::LuaScriptCfg::sClothing}, + {"CONTAINER", ESM::LuaScriptCfg::sContainer}, + {"CREATURE", ESM::LuaScriptCfg::sCreature}, + {"DOOR", ESM::LuaScriptCfg::sDoor}, + {"INGREDIENT", ESM::LuaScriptCfg::sIngredient}, + {"LIGHT", ESM::LuaScriptCfg::sLight}, + {"MISC_ITEM", ESM::LuaScriptCfg::sMiscItem}, + {"NPC", ESM::LuaScriptCfg::sNPC}, + {"POTION", ESM::LuaScriptCfg::sPotion}, + {"WEAPON", ESM::LuaScriptCfg::sWeapon}, + }; + } + + const std::vector ScriptsConfiguration::sEmpty; + + void ScriptsConfiguration::init(ESM::LuaScriptsCfg cfg) + { + mScripts.clear(); + mScriptsByFlag.clear(); + mPathToIndex.clear(); + + // Find duplicates; only the last occurrence will be used. + // Search for duplicates is case insensitive. + std::vector skip(cfg.mScripts.size(), false); + for (int i = cfg.mScripts.size() - 1; i >= 0; --i) + { + auto [_, inserted] = mPathToIndex.insert_or_assign( + Misc::StringUtils::lowerCase(cfg.mScripts[i].mScriptPath), -1); + if (!inserted || cfg.mScripts[i].mFlags == 0) + skip[i] = true; + } + mPathToIndex.clear(); + int index = 0; + for (size_t i = 0; i < cfg.mScripts.size(); ++i) + { + if (skip[i]) + continue; + ESM::LuaScriptCfg& s = cfg.mScripts[i]; + mPathToIndex[s.mScriptPath] = index; // Stored paths are case sensitive. + ESM::LuaScriptCfg::Flags flags = s.mFlags; + ESM::LuaScriptCfg::Flags flag = 1; + while (flags != 0) + { + if (flags & flag) + mScriptsByFlag[flag].push_back(index); + flags &= ~flag; + flag = flag << 1; + } + mScripts.push_back(std::move(s)); + index++; + } + } + + std::optional ScriptsConfiguration::findId(std::string_view path) const + { + auto it = mPathToIndex.find(path); + if (it != mPathToIndex.end()) + return it->second; + else + return std::nullopt; + } + + const std::vector& ScriptsConfiguration::getListByFlag(ESM::LuaScriptCfg::Flags type) const + { + assert(std::bitset<64>(type).count() <= 1); + auto it = mScriptsByFlag.find(type); + if (it != mScriptsByFlag.end()) + return it->second; + else + return sEmpty; + } + + void parseOMWScripts(ESM::LuaScriptsCfg& cfg, std::string_view data) + { + while (!data.empty()) + { + // Get next line + std::string_view line = data.substr(0, data.find('\n')); + data = data.substr(std::min(line.size() + 1, data.size())); + if (!line.empty() && line.back() == '\r') + line = line.substr(0, line.size() - 1); + + while (!line.empty() && std::isspace(line[0])) + line = line.substr(1); + if (line.empty() || line[0] == '#') // Skip empty lines and comments + continue; + while (!line.empty() && std::isspace(line.back())) + line = line.substr(0, line.size() - 1); + + if (!Misc::StringUtils::ciEndsWith(line, ".lua")) + throw std::runtime_error(Misc::StringUtils::format( + "Lua script should have suffix '.lua', got: %s", std::string(line.substr(0, 300)))); + + // Split flags and script path + size_t semicolonPos = line.find(':'); + if (semicolonPos == std::string::npos) + throw std::runtime_error(Misc::StringUtils::format("No flags found in: %s", std::string(line))); + std::string_view flagsStr = line.substr(0, semicolonPos); + std::string_view scriptPath = line.substr(semicolonPos + 1); + while (std::isspace(scriptPath[0])) + scriptPath = scriptPath.substr(1); + + // Parse flags + ESM::LuaScriptCfg::Flags flags = 0; + size_t flagsPos = 0; + while (true) + { + while (flagsPos < flagsStr.size() && (std::isspace(flagsStr[flagsPos]) || flagsStr[flagsPos] == ',')) + flagsPos++; + size_t startPos = flagsPos; + while (flagsPos < flagsStr.size() && !std::isspace(flagsStr[flagsPos]) && flagsStr[flagsPos] != ',') + flagsPos++; + if (startPos == flagsPos) + break; + std::string_view flagName = flagsStr.substr(startPos, flagsPos - startPos); + auto it = flagsByName.find(flagName); + if (it != flagsByName.end()) + flags |= it->second; + else + throw std::runtime_error(Misc::StringUtils::format("Unknown flag '%s' in: %s", + std::string(flagName), std::string(line))); + } + if ((flags & ESM::LuaScriptCfg::sGlobal) && flags != ESM::LuaScriptCfg::sGlobal) + throw std::runtime_error("Global script can not have local flags"); + + cfg.mScripts.push_back(ESM::LuaScriptCfg{std::string(scriptPath), "", flags}); + } + } + + std::string scriptCfgToString(const ESM::LuaScriptCfg& script) + { + std::stringstream ss; + for (const auto& [flagName, flag] : flagsByName) + { + if (script.mFlags & flag) + ss << flagName << " "; + } + ss << ": " << script.mScriptPath; + if (!script.mInitializationData.empty()) + ss << " (with data, " << script.mInitializationData.size() << " bytes)"; + return ss.str(); + } + +} diff --git a/components/lua/configuration.hpp b/components/lua/configuration.hpp new file mode 100644 index 0000000000..32eddf399c --- /dev/null +++ b/components/lua/configuration.hpp @@ -0,0 +1,37 @@ +#ifndef COMPONENTS_LUA_CONFIGURATION_H +#define COMPONENTS_LUA_CONFIGURATION_H + +#include +#include + +#include + +namespace LuaUtil +{ + + class ScriptsConfiguration + { + public: + void init(ESM::LuaScriptsCfg); + + size_t size() const { return mScripts.size(); } + const ESM::LuaScriptCfg& operator[](int id) const { return mScripts[id]; } + + std::optional findId(std::string_view path) const; + const std::vector& getListByFlag(ESM::LuaScriptCfg::Flags type) const; + + private: + std::vector mScripts; + std::map> mPathToIndex; + std::map> mScriptsByFlag; + static const std::vector sEmpty; + }; + + // Parse ESM::LuaScriptsCfg from text and add to `cfg`. + void parseOMWScripts(ESM::LuaScriptsCfg& cfg, std::string_view data); + + std::string scriptCfgToString(const ESM::LuaScriptCfg& script); + +} + +#endif // COMPONENTS_LUA_CONFIGURATION_H diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 6c9c1eefa2..dcffa7fdf3 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -25,6 +25,7 @@ class StringUtils template static T argument(T value) noexcept { + static_assert(!std::is_same_v, "std::string_view is not supported"); return value; } @@ -324,14 +325,20 @@ public: } } - static inline void replaceLast(std::string& str, const std::string& substr, const std::string& with) - { - size_t pos = str.rfind(substr); - if (pos == std::string::npos) - return; + static inline void replaceLast(std::string& str, const std::string& substr, const std::string& with) + { + size_t pos = str.rfind(substr); + if (pos == std::string::npos) + return; + + str.replace(pos, substr.size(), with); + } - str.replace(pos, substr.size(), with); - } + static inline bool ciEndsWith(std::string_view s, std::string_view suffix) + { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin(), + [](char l, char r) { return toLower(l) == toLower(r); }); + }; }; } From 9adc1902092f32b8dc79c193f3063f72c207cb09 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 25 Sep 2021 10:46:47 +0200 Subject: [PATCH 1546/2859] Redesign LuaUtil::ScriptsContainer to work with ScriptsConfiguration --- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwlua/asyncbindings.cpp | 12 +- apps/openmw/mwlua/globalscripts.hpp | 3 +- apps/openmw/mwlua/localscripts.cpp | 6 +- apps/openmw/mwlua/localscripts.hpp | 4 +- apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/luabindings.hpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 100 +++-- apps/openmw/mwlua/luamanagerimp.hpp | 21 +- apps/openmw/mwlua/objectbindings.cpp | 38 +- apps/openmw/mwlua/playerscripts.hpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 2 + apps/openmw_test_suite/lua/test_lua.cpp | 5 +- .../lua/test_omwscriptsparser.cpp | 59 --- .../lua/test_scriptscontainer.cpp | 193 +++++--- components/CMakeLists.txt | 2 +- components/lua/luastate.cpp | 13 +- components/lua/luastate.hpp | 21 +- components/lua/omwscriptsparser.cpp | 44 -- components/lua/omwscriptsparser.hpp | 14 - components/lua/scriptscontainer.cpp | 421 +++++++++++------- components/lua/scriptscontainer.hpp | 105 +++-- 22 files changed, 621 insertions(+), 449 deletions(-) delete mode 100644 apps/openmw_test_suite/lua/test_omwscriptsparser.cpp delete mode 100644 components/lua/omwscriptsparser.cpp delete mode 100644 components/lua/omwscriptsparser.hpp diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index ebcd8f50b3..cc479a2937 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -30,6 +30,7 @@ namespace MWBase virtual ~LuaManager() = default; virtual void newGameStarted() = 0; + virtual void gameLoaded() = 0; virtual void registerObject(const MWWorld::Ptr& ptr) = 0; virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0; virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwlua/asyncbindings.cpp b/apps/openmw/mwlua/asyncbindings.cpp index 9fdda53d9d..d438518452 100644 --- a/apps/openmw/mwlua/asyncbindings.cpp +++ b/apps/openmw/mwlua/asyncbindings.cpp @@ -23,7 +23,7 @@ namespace MWLua sol::usertype api = context.mLua->sol().new_usertype("AsyncPackage"); api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback) { - asyncId.mContainer->registerTimerCallback(asyncId.mScript, name, std::move(callback)); + asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback)); return TimerCallback{asyncId, std::string(name)}; }; api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay, @@ -31,24 +31,24 @@ namespace MWLua { callback.mAsyncId.mContainer->setupSerializableTimer( TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, - callback.mAsyncId.mScript, callback.mName, std::move(callbackArg)); + callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg)); }; api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay, const TimerCallback& callback, sol::object callbackArg) { callback.mAsyncId.mContainer->setupSerializableTimer( TimeUnit::HOURS, world->getGameTimeInHours() + delay, - callback.mAsyncId.mScript, callback.mName, std::move(callbackArg)); + callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg)); }; api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) { asyncId.mContainer->setupUnsavableTimer( - TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScript, std::move(callback)); + TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScriptId, std::move(callback)); }; api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) { asyncId.mContainer->setupUnsavableTimer( - TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback)); + TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScriptId, std::move(callback)); }; api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) { @@ -59,7 +59,7 @@ namespace MWLua { LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY]; hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString(); - return AsyncPackageId{id.mContainer, id.mPath, hiddenData}; + return AsyncPackageId{id.mContainer, id.mIndex, hiddenData}; }; return sol::make_object(context.mLua->sol(), initializer); } diff --git a/apps/openmw/mwlua/globalscripts.hpp b/apps/openmw/mwlua/globalscripts.hpp index 9a371809ac..2737dabaca 100644 --- a/apps/openmw/mwlua/globalscripts.hpp +++ b/apps/openmw/mwlua/globalscripts.hpp @@ -16,7 +16,8 @@ namespace MWLua class GlobalScripts : public LuaUtil::ScriptsContainer { public: - GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global") + GlobalScripts(LuaUtil::LuaState* lua) : + LuaUtil::ScriptsContainer(lua, "Global", ESM::LuaScriptCfg::sGlobal) { registerEngineHandlers({&mActorActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers}); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8a1b76a8ce..ee23b4b90c 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -82,14 +82,14 @@ namespace MWLua }; } - LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj) - : LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj) + LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode) + : LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj) { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers}); } - void LocalScripts::receiveEngineEvent(const EngineEvent& event, ObjectRegistry*) + void LocalScripts::receiveEngineEvent(const EngineEvent& event) { std::visit([this](auto&& arg) { diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 80d04b7a40..68da0b8b03 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -20,7 +20,7 @@ namespace MWLua { public: static void initializeSelfPackage(const Context&); - LocalScripts(LuaUtil::LuaState* lua, const LObject& obj); + LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode); MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; } @@ -39,7 +39,7 @@ namespace MWLua }; using EngineEvent = std::variant; - void receiveEngineEvent(const EngineEvent&, ObjectRegistry*); + void receiveEngineEvent(const EngineEvent&); protected: SelfObject mData; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index aceffc24db..1c05debc73 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 7; + api["API_REVISION"] = 8; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index d1c62e43e3..aad3183734 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -48,7 +48,7 @@ namespace MWLua struct AsyncPackageId { LuaUtil::ScriptsContainer* mContainer; - std::string mScript; + int mScriptId; sol::table mHiddenData; }; sol::function getAsyncPackageInitializer(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 38055c99b7..7a4b319f27 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -7,7 +7,6 @@ #include #include -#include #include "../mwbase/windowmanager.hpp" @@ -20,10 +19,10 @@ namespace MWLua { - LuaManager::LuaManager(const VFS::Manager* vfs, const std::vector& scriptLists) : mLua(vfs) + LuaManager::LuaManager(const VFS::Manager* vfs, std::vector OMWScriptsFiles) : + mVFS(vfs), mOMWScriptsFiles(std::move(OMWScriptsFiles)), mLua(vfs, &mConfiguration) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); - mGlobalScriptList = LuaUtil::parseOMWScriptsFiles(vfs, scriptLists); mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry()); mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry()); @@ -33,6 +32,30 @@ namespace MWLua mGlobalScripts.setSerializer(mGlobalSerializer.get()); } + void LuaManager::initConfiguration() + { + ESM::LuaScriptsCfg cfg; + for (const std::string& file : mOMWScriptsFiles) + { + if (!Misc::StringUtils::endsWith(file, ".omwscripts")) + { + Log(Debug::Error) << "Script list should have suffix '.omwscripts', got: '" << file << "'"; + continue; + } + try + { + std::string content(std::istreambuf_iterator(*mVFS->get(file)), {}); + LuaUtil::parseOMWScripts(cfg, content); + } + catch (std::exception& e) { Log(Debug::Error) << e.what(); } + } + // TODO: Add data from content files + mConfiguration.init(cfg); + Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):"; + for (size_t i = 0; i < mConfiguration.size(); ++i) + Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]); + } + void LuaManager::init() { Context context; @@ -67,10 +90,7 @@ namespace MWLua mLocalSettingsPackage = initLocalSettingsPackage(localContext); mPlayerSettingsPackage = initPlayerSettingsPackage(localContext); - mInputEvents.clear(); - for (const std::string& path : mGlobalScriptList) - if (mGlobalScripts.addNewScript(path)) - Log(Debug::Info) << "Global script started: " << path; + initConfiguration(); mInitialized = true; } @@ -160,7 +180,7 @@ namespace MWLua } LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); if (scripts) - scripts->receiveEngineEvent(e.mEvent, objectRegistry); + scripts->receiveEngineEvent(e.mEvent); } mLocalEngineEvents.clear(); @@ -173,6 +193,11 @@ namespace MWLua mPlayerChanged = false; mGlobalScripts.playerAdded(GObject(getId(mPlayer), objectRegistry)); } + if (mNewGameStarted) + { + mNewGameStarted = false; + mGlobalScripts.newGameStarted(); + } for (ObjectId id : mActorAddedEvents) mGlobalScripts.actorActive(GObject(id, objectRegistry)); @@ -205,8 +230,11 @@ namespace MWLua mInputEvents.clear(); mActorAddedEvents.clear(); mLocalEngineEvents.clear(); + mNewGameStarted = false; mPlayerChanged = false; mWorldView.clear(); + mGlobalScripts.removeAllScripts(); + mGlobalScriptsStarted = false; if (!mPlayer.isEmpty()) { mPlayer.getCellRef().unsetRefNum(); @@ -225,17 +253,38 @@ namespace MWLua mPlayer = ptr; LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) - localScripts = createLocalScripts(ptr); + localScripts = createLocalScripts(ptr, ESM::LuaScriptCfg::sPlayer); mActiveLocalScripts.insert(localScripts); mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}}); mPlayerChanged = true; } + void LuaManager::newGameStarted() + { + mNewGameStarted = true; + mInputEvents.clear(); + mGlobalScripts.addAutoStartedScripts(); + mGlobalScriptsStarted = true; + } + + void LuaManager::gameLoaded() + { + if (!mGlobalScriptsStarted) + mGlobalScripts.addAutoStartedScripts(); + mGlobalScriptsStarted = true; + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts) + { + ESM::LuaScriptCfg::Flags flag = getLuaScriptFlag(ptr); + if (!mConfiguration.getListByFlag(flag).empty()) + localScripts = createLocalScripts(ptr, flag); // TODO: put to a queue and apply on next `update()` + } if (localScripts) { mActiveLocalScripts.insert(localScripts); @@ -281,26 +330,26 @@ namespace MWLua return localScripts->getActorControls(); } - void LuaManager::addLocalScript(const MWWorld::Ptr& ptr, const std::string& scriptPath) + void LuaManager::addCustomLocalScript(const MWWorld::Ptr& ptr, int scriptId) { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) { - localScripts = createLocalScripts(ptr); + localScripts = createLocalScripts(ptr, getLuaScriptFlag(ptr)); if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell())) mActiveLocalScripts.insert(localScripts); } - localScripts->addNewScript(scriptPath); + localScripts->addCustomScript(scriptId); } - LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr) + LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags flag) { assert(mInitialized); + assert(flag != ESM::LuaScriptCfg::sGlobal); std::shared_ptr scripts; - // When loading a game, it can be called before LuaManager::setPlayer, - // so we can't just check ptr == mPlayer here. - if (ptr.getCellRef().getRefIdRef() == "player") + if (flag == ESM::LuaScriptCfg::sPlayer) { + assert(ptr.getCellRef().getRefIdRef() == "player"); scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); scripts->addPackage("openmw.ui", mUserInterfacePackage); scripts->addPackage("openmw.camera", mCameraPackage); @@ -309,11 +358,12 @@ namespace MWLua } else { - scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); + scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()), flag); scripts->addPackage("openmw.settings", mLocalSettingsPackage); } scripts->addPackage("openmw.nearby", mNearbyPackage); scripts->setSerializer(mLocalSerializer.get()); + scripts->addAutoStartedScripts(); MWWorld::RefData& refData = ptr.getRefData(); refData.setLuaScripts(std::move(scripts)); @@ -344,8 +394,9 @@ namespace MWLua loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get()); mGlobalScripts.setSerializer(mGlobalLoader.get()); - mGlobalScripts.load(globalScripts, false); + mGlobalScripts.load(globalScripts); mGlobalScripts.setSerializer(mGlobalSerializer.get()); + mGlobalScriptsStarted = true; } void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) @@ -366,10 +417,10 @@ namespace MWLua } mWorldView.getObjectRegistry()->registerPtr(ptr); - LocalScripts* scripts = createLocalScripts(ptr); + LocalScripts* scripts = createLocalScripts(ptr, getLuaScriptFlag(ptr)); scripts->setSerializer(mLocalLoader.get()); - scripts->load(data, true); + scripts->load(data); scripts->setSerializer(mLocalSerializer.get()); // LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered. @@ -380,15 +431,12 @@ namespace MWLua { Log(Debug::Info) << "Reload Lua"; mLua.dropScriptCache(); + initConfiguration(); { // Reload global scripts ESM::LuaScripts data; mGlobalScripts.save(data); - mGlobalScripts.removeAllScripts(); - for (const std::string& path : mGlobalScriptList) - if (mGlobalScripts.addNewScript(path)) - Log(Debug::Info) << "Global script restarted: " << path; - mGlobalScripts.load(data, false); + mGlobalScripts.load(data); } for (const auto& [id, ptr] : mWorldView.getObjectRegistry()->mObjectMapping) @@ -398,7 +446,7 @@ namespace MWLua continue; ESM::LuaScripts data; scripts->save(data); - scripts->load(data, true); + scripts->load(data); } } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 91f48171f3..be80cfe2e7 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -35,9 +35,9 @@ namespace MWLua class LuaManager : public MWBase::LuaManager { public: - LuaManager(const VFS::Manager* vfs, const std::vector& globalScriptLists); + LuaManager(const VFS::Manager* vfs, std::vector OMWScriptsFiles); - // Called by engine.cpp when environment is fully initialized. + // Called by engine.cpp when the environment is fully initialized. void init(); // Called by engine.cpp every frame. For performance reasons it works in a separate @@ -49,7 +49,8 @@ namespace MWLua // Available everywhere through the MWBase::LuaManager interface. // LuaManager queues these events and propagates to scripts on the next `update` call. - void newGameStarted() override { mGlobalScripts.newGameStarted(); } + void newGameStarted() override; + void gameLoaded() override; void objectAddedToScene(const MWWorld::Ptr& ptr) override; void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; void registerObject(const MWWorld::Ptr& ptr) override; @@ -62,8 +63,8 @@ namespace MWLua void clear() override; // should be called before loading game or starting a new game to reset internal state. void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear". - // Used only in luabindings - void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath); + // Used only in Lua bindings + void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } @@ -93,9 +94,15 @@ namespace MWLua } private: - LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr); + void initConfiguration(); + LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); + + const VFS::Manager* mVFS; + const std::vector mOMWScriptsFiles; bool mInitialized = false; + bool mGlobalScriptsStarted = false; + LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; sol::table mNearbyPackage; sol::table mUserInterfacePackage; @@ -104,12 +111,12 @@ namespace MWLua sol::table mLocalSettingsPackage; sol::table mPlayerSettingsPackage; - std::vector mGlobalScriptList; GlobalScripts mGlobalScripts{&mLua}; std::set mActiveLocalScripts; WorldView mWorldView; bool mPlayerChanged = false; + bool mNewGameStarted = false; MWWorld::Ptr mPlayer; GlobalEventQueue mGlobalEvents; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 07b7921a09..2eceecc061 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -140,9 +140,43 @@ namespace MWLua if constexpr (std::is_same_v) { // Only for global scripts - objectT["addScript"] = [luaManager=context.mLuaManager](const GObject& object, const std::string& path) + objectT["addScript"] = [lua=context.mLua, luaManager=context.mLuaManager](const GObject& object, std::string_view path) { - luaManager->addLocalScript(object.ptr(), path); + const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + throw std::runtime_error("Unknown script: " + std::string(path)); + if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom)) + throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path)); + luaManager->addCustomLocalScript(object.ptr(), *scriptId); + }; + objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path) + { + const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + return false; + MWWorld::Ptr ptr = object.ptr(); + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (localScripts) + return localScripts->hasScript(*scriptId); + else + return false; + }; + objectT["removeScript"] = [lua=context.mLua](const GObject& object, std::string_view path) + { + const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + throw std::runtime_error("Unknown script: " + std::string(path)); + MWWorld::Ptr ptr = object.ptr(); + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts || !localScripts->hasScript(*scriptId)) + throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr)); + ESM::LuaScriptCfg::Flags flags = cfg[*scriptId].mFlags; + if ((flags & (localScripts->getAutoStartMode() | ESM::LuaScriptCfg::sCustom)) != ESM::LuaScriptCfg::sCustom) + throw std::runtime_error("Autostarted script can not be removed: " + std::string(path)); + localScripts->removeScript(*scriptId); }; objectT["teleport"] = [luaManager=context.mLuaManager](const GObject& object, std::string_view cell, diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index ff0349b3c6..0393a1375d 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -13,7 +13,7 @@ namespace MWLua class PlayerScripts : public LocalScripts { public: - PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj) + PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer) { registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 4387d7faa7..5e51e2f621 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -558,6 +558,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. // But make sure the flag is cleared anyway in case it was set from an earlier game. MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + + MWBase::Environment::get().getLuaManager()->gameLoaded(); } catch (const std::exception& e) { diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 32d4ea49b8..4b3ecdcb2b 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -58,7 +58,8 @@ return { {"invalid.lua", &invalidScriptFile} }); - LuaUtil::LuaState mLua{mVFS.get()}; + LuaUtil::ScriptsConfiguration mCfg; + LuaUtil::LuaState mLua{mVFS.get(), &mCfg}; }; TEST_F(LuaStateTest, Sandbox) @@ -148,7 +149,7 @@ return { TEST_F(LuaStateTest, ProvideAPI) { - LuaUtil::LuaState lua(mVFS.get()); + LuaUtil::LuaState lua(mVFS.get(), &mCfg); sol::table api1 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api1")); sol::table api2 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api2")); diff --git a/apps/openmw_test_suite/lua/test_omwscriptsparser.cpp b/apps/openmw_test_suite/lua/test_omwscriptsparser.cpp deleted file mode 100644 index b1526ef9b6..0000000000 --- a/apps/openmw_test_suite/lua/test_omwscriptsparser.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "gmock/gmock.h" -#include - -#include - -#include "testing_util.hpp" - -namespace -{ - using namespace testing; - - TestFile file1( - "#comment.lua\n" - "\n" - "script1.lua\n" - "some mod/Some Script.lua" - ); - TestFile file2( - "#comment.lua\r\n" - "\r\n" - "script2.lua\r\n" - "some other mod/Some Script.lua\r" - ); - TestFile emptyFile(""); - TestFile invalidFile("Invalid file"); - - struct OMWScriptsParserTest : Test - { - std::unique_ptr mVFS = createTestVFS({ - {"file1.omwscripts", &file1}, - {"file2.omwscripts", &file2}, - {"empty.omwscripts", &emptyFile}, - {"invalid.lua", &file1}, - {"invalid.omwscripts", &invalidFile}, - }); - }; - - TEST_F(OMWScriptsParserTest, Basic) - { - internal::CaptureStdout(); - std::vector res = LuaUtil::parseOMWScriptsFiles( - mVFS.get(), {"file2.omwscripts", "empty.omwscripts", "file1.omwscripts"}); - EXPECT_EQ(internal::GetCapturedStdout(), ""); - EXPECT_THAT(res, ElementsAre("script2.lua", "some other mod/Some Script.lua", - "script1.lua", "some mod/Some Script.lua")); - } - - TEST_F(OMWScriptsParserTest, InvalidFiles) - { - internal::CaptureStdout(); - std::vector res = LuaUtil::parseOMWScriptsFiles( - mVFS.get(), {"invalid.lua", "invalid.omwscripts"}); - EXPECT_EQ(internal::GetCapturedStdout(), - "Script list should have suffix '.omwscripts', got: 'invalid.lua'\n" - "Lua script should have suffix '.lua', got: 'Invalid file'\n"); - EXPECT_THAT(res, ElementsAre()); - } - -} diff --git a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp index 8f05138782..344fbb3c78 100644 --- a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp +++ b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp @@ -18,7 +18,10 @@ namespace TestFile testScript(R"X( return { - engineHandlers = { onUpdate = function(dt) print(' update ' .. tostring(dt)) end }, + engineHandlers = { + onUpdate = function(dt) print(' update ' .. tostring(dt)) end, + onLoad = function() print('load') end, + }, eventHandlers = { Event1 = function(eventData) print(' event1 ' .. tostring(eventData.x)) end, Event2 = function(eventData) print(' event2 ' .. tostring(eventData.x)) end, @@ -75,15 +78,25 @@ return { )X"); TestFile overrideInterfaceScript(R"X( -local old = require('openmw.interfaces').TestInterface +local old = nil +local interface = { + fn = function(x) + print('NEW FN', x) + old.fn(x) + end, + value, +} return { interfaceName = "TestInterface", - interface = { - fn = function(x) - print('NEW FN', x) - old.fn(x) - end, - value = old.value + 1 + interface = interface, + engineHandlers = { + onInit = function() print('init') end, + onLoad = function() print('load') end, + onInterfaceOverride = function(oldInterface) + print('override') + old = oldInterface + interface.value = oldInterface.value + 1 + end }, } )X"); @@ -115,7 +128,25 @@ return { {"useInterface.lua", &useInterfaceScript}, }); - LuaUtil::LuaState mLua{mVFS.get()}; + LuaUtil::ScriptsConfiguration mCfg; + LuaUtil::LuaState mLua{mVFS.get(), &mCfg}; + + LuaScriptsContainerTest() + { + ESM::LuaScriptsCfg cfg; + cfg.mScripts.push_back({"invalid.lua", "", ESM::LuaScriptCfg::sCustom}); + cfg.mScripts.push_back({"incorrect.lua", "", ESM::LuaScriptCfg::sCustom}); + cfg.mScripts.push_back({"empty.lua", "", ESM::LuaScriptCfg::sCustom}); + cfg.mScripts.push_back({"test1.lua", "", ESM::LuaScriptCfg::sCustom}); + cfg.mScripts.push_back({"stopEvent.lua", "", ESM::LuaScriptCfg::sCustom}); + cfg.mScripts.push_back({"test2.lua", "", ESM::LuaScriptCfg::sCustom}); + cfg.mScripts.push_back({"loadSave1.lua", "", ESM::LuaScriptCfg::sNPC}); + cfg.mScripts.push_back({"loadSave2.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sNPC}); + cfg.mScripts.push_back({"testInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer}); + cfg.mScripts.push_back({"overrideInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer}); + cfg.mScripts.push_back({"useInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer}); + mCfg.init(std::move(cfg)); + } }; TEST_F(LuaScriptsContainerTest, VerifyStructure) @@ -123,21 +154,21 @@ return { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); { testing::internal::CaptureStdout(); - EXPECT_FALSE(scripts.addNewScript("invalid.lua")); + EXPECT_FALSE(scripts.addCustomScript(*mCfg.findId("invalid.lua"))); std::string output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, HasSubstr("Can't start Test[invalid.lua]")); } { testing::internal::CaptureStdout(); - EXPECT_TRUE(scripts.addNewScript("incorrect.lua")); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("incorrect.lua"))); std::string output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, HasSubstr("Not supported handler 'incorrectHandler' in Test[incorrect.lua]")); EXPECT_THAT(output, HasSubstr("Not supported section 'incorrectSection' in Test[incorrect.lua]")); } { testing::internal::CaptureStdout(); - EXPECT_TRUE(scripts.addNewScript("empty.lua")); - EXPECT_FALSE(scripts.addNewScript("empty.lua")); // already present + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("empty.lua"))); + EXPECT_FALSE(scripts.addCustomScript(*mCfg.findId("empty.lua"))); // already present EXPECT_EQ(internal::GetCapturedStdout(), ""); } } @@ -146,9 +177,9 @@ return { { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); testing::internal::CaptureStdout(); - EXPECT_TRUE(scripts.addNewScript("test1.lua")); - EXPECT_TRUE(scripts.addNewScript("stopEvent.lua")); - EXPECT_TRUE(scripts.addNewScript("test2.lua")); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua"))); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua"))); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua"))); scripts.update(1.5f); EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\t update 1.5\n" "Test[test2.lua]:\t update 1.5\n"); @@ -157,9 +188,9 @@ return { TEST_F(LuaScriptsContainerTest, CallEvent) { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); - EXPECT_TRUE(scripts.addNewScript("test1.lua")); - EXPECT_TRUE(scripts.addNewScript("stopEvent.lua")); - EXPECT_TRUE(scripts.addNewScript("test2.lua")); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua"))); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua"))); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua"))); std::string X0 = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5)); std::string X1 = LuaUtil::serialize(mLua.sol().create_table_with("x", 1.5)); @@ -204,9 +235,9 @@ return { TEST_F(LuaScriptsContainerTest, RemoveScript) { LuaUtil::ScriptsContainer scripts(&mLua, "Test"); - EXPECT_TRUE(scripts.addNewScript("test1.lua")); - EXPECT_TRUE(scripts.addNewScript("stopEvent.lua")); - EXPECT_TRUE(scripts.addNewScript("test2.lua")); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua"))); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua"))); + EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua"))); std::string X = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5)); { @@ -221,8 +252,10 @@ return { } { testing::internal::CaptureStdout(); - EXPECT_TRUE(scripts.removeScript("stopEvent.lua")); - EXPECT_FALSE(scripts.removeScript("stopEvent.lua")); // already removed + int stopEventScriptId = *mCfg.findId("stopEvent.lua"); + EXPECT_TRUE(scripts.hasScript(stopEventScriptId)); + scripts.removeScript(stopEventScriptId); + EXPECT_FALSE(scripts.hasScript(stopEventScriptId)); scripts.update(1.5f); scripts.receiveEvent("Event1", X); EXPECT_EQ(internal::GetCapturedStdout(), @@ -233,7 +266,7 @@ return { } { testing::internal::CaptureStdout(); - EXPECT_TRUE(scripts.removeScript("test1.lua")); + scripts.removeScript(*mCfg.findId("test1.lua")); scripts.update(1.5f); scripts.receiveEvent("Event1", X); EXPECT_EQ(internal::GetCapturedStdout(), @@ -242,17 +275,41 @@ return { } } + TEST_F(LuaScriptsContainerTest, AutoStart) + { + LuaUtil::ScriptsContainer scripts(&mLua, "Test", ESM::LuaScriptCfg::sPlayer); + testing::internal::CaptureStdout(); + scripts.addAutoStartedScripts(); + scripts.update(1.5f); + EXPECT_EQ(internal::GetCapturedStdout(), + "Test[overrideInterface.lua]:\toverride\n" + "Test[overrideInterface.lua]:\tinit\n" + "Test[overrideInterface.lua]:\tNEW FN\t4.5\n" + "Test[testInterface.lua]:\tFN\t4.5\n"); + } + TEST_F(LuaScriptsContainerTest, Interface) { - LuaUtil::ScriptsContainer scripts(&mLua, "Test"); + LuaUtil::ScriptsContainer scripts(&mLua, "Test", ESM::LuaScriptCfg::sCreature); + int addIfaceId = *mCfg.findId("testInterface.lua"); + int overrideIfaceId = *mCfg.findId("overrideInterface.lua"); + int useIfaceId = *mCfg.findId("useInterface.lua"); + testing::internal::CaptureStdout(); - EXPECT_TRUE(scripts.addNewScript("testInterface.lua")); - EXPECT_TRUE(scripts.addNewScript("overrideInterface.lua")); - EXPECT_TRUE(scripts.addNewScript("useInterface.lua")); + scripts.addAutoStartedScripts(); scripts.update(1.5f); - EXPECT_TRUE(scripts.removeScript("overrideInterface.lua")); + EXPECT_EQ(internal::GetCapturedStdout(), ""); + + testing::internal::CaptureStdout(); + EXPECT_TRUE(scripts.addCustomScript(addIfaceId)); + EXPECT_TRUE(scripts.addCustomScript(overrideIfaceId)); + EXPECT_TRUE(scripts.addCustomScript(useIfaceId)); + scripts.update(1.5f); + scripts.removeScript(overrideIfaceId); scripts.update(1.5f); EXPECT_EQ(internal::GetCapturedStdout(), + "Test[overrideInterface.lua]:\toverride\n" + "Test[overrideInterface.lua]:\tinit\n" "Test[overrideInterface.lua]:\tNEW FN\t4.5\n" "Test[testInterface.lua]:\tFN\t4.5\n" "Test[testInterface.lua]:\tFN\t3.5\n"); @@ -260,16 +317,12 @@ return { TEST_F(LuaScriptsContainerTest, LoadSave) { - LuaUtil::ScriptsContainer scripts1(&mLua, "Test"); - LuaUtil::ScriptsContainer scripts2(&mLua, "Test"); - LuaUtil::ScriptsContainer scripts3(&mLua, "Test"); - - EXPECT_TRUE(scripts1.addNewScript("loadSave1.lua")); - EXPECT_TRUE(scripts1.addNewScript("test1.lua")); - EXPECT_TRUE(scripts1.addNewScript("loadSave2.lua")); + LuaUtil::ScriptsContainer scripts1(&mLua, "Test", ESM::LuaScriptCfg::sNPC); + LuaUtil::ScriptsContainer scripts2(&mLua, "Test", ESM::LuaScriptCfg::sNPC); + LuaUtil::ScriptsContainer scripts3(&mLua, "Test", ESM::LuaScriptCfg::sPlayer); - EXPECT_TRUE(scripts3.addNewScript("test2.lua")); - EXPECT_TRUE(scripts3.addNewScript("loadSave2.lua")); + scripts1.addAutoStartedScripts(); + EXPECT_TRUE(scripts1.addCustomScript(*mCfg.findId("test1.lua"))); scripts1.receiveEvent("Set", LuaUtil::serialize(mLua.sol().create_table_with( "n", 1, @@ -282,23 +335,30 @@ return { ESM::LuaScripts data; scripts1.save(data); - scripts2.load(data, true); - scripts3.load(data, false); { testing::internal::CaptureStdout(); + scripts2.load(data); scripts2.receiveEvent("Print", ""); EXPECT_EQ(internal::GetCapturedStdout(), + "Test[test1.lua]:\tload\n" "Test[loadSave2.lua]:\t0.5\t3.5\n" - "Test[test1.lua]:\tprint\n" - "Test[loadSave1.lua]:\t2.5\t1.5\n"); + "Test[loadSave1.lua]:\t2.5\t1.5\n" + "Test[test1.lua]:\tprint\n"); + EXPECT_FALSE(scripts2.hasScript(*mCfg.findId("testInterface.lua"))); } { testing::internal::CaptureStdout(); + scripts3.load(data); scripts3.receiveEvent("Print", ""); EXPECT_EQ(internal::GetCapturedStdout(), + "Ignoring Test[loadSave1.lua]; this script is not allowed here\n" + "Test[test1.lua]:\tload\n" + "Test[overrideInterface.lua]:\toverride\n" + "Test[overrideInterface.lua]:\tinit\n" "Test[loadSave2.lua]:\t0.5\t3.5\n" - "Test[test2.lua]:\tprint\n"); + "Test[test1.lua]:\tprint\n"); + EXPECT_TRUE(scripts3.hasScript(*mCfg.findId("testInterface.lua"))); } } @@ -306,8 +366,13 @@ return { { using TimeUnit = LuaUtil::ScriptsContainer::TimeUnit; LuaUtil::ScriptsContainer scripts(&mLua, "Test"); - EXPECT_TRUE(scripts.addNewScript("test1.lua")); - EXPECT_TRUE(scripts.addNewScript("test2.lua")); + int test1Id = *mCfg.findId("test1.lua"); + int test2Id = *mCfg.findId("test2.lua"); + + testing::internal::CaptureStdout(); + EXPECT_TRUE(scripts.addCustomScript(test1Id)); + EXPECT_TRUE(scripts.addCustomScript(test2Id)); + EXPECT_EQ(internal::GetCapturedStdout(), ""); int counter1 = 0, counter2 = 0, counter3 = 0, counter4 = 0; sol::function fn1 = sol::make_object(mLua.sol(), [&]() { counter1++; }); @@ -315,25 +380,25 @@ return { sol::function fn3 = sol::make_object(mLua.sol(), [&](int d) { counter3 += d; }); sol::function fn4 = sol::make_object(mLua.sol(), [&](int d) { counter4 += d; }); - scripts.registerTimerCallback("test1.lua", "A", fn3); - scripts.registerTimerCallback("test1.lua", "B", fn4); - scripts.registerTimerCallback("test2.lua", "B", fn3); - scripts.registerTimerCallback("test2.lua", "A", fn4); + scripts.registerTimerCallback(test1Id, "A", fn3); + scripts.registerTimerCallback(test1Id, "B", fn4); + scripts.registerTimerCallback(test2Id, "B", fn3); + scripts.registerTimerCallback(test2Id, "A", fn4); scripts.processTimers(1, 2); - scripts.setupSerializableTimer(TimeUnit::SECONDS, 10, "test1.lua", "B", sol::make_object(mLua.sol(), 3)); - scripts.setupSerializableTimer(TimeUnit::HOURS, 10, "test2.lua", "B", sol::make_object(mLua.sol(), 4)); - scripts.setupSerializableTimer(TimeUnit::SECONDS, 5, "test1.lua", "A", sol::make_object(mLua.sol(), 1)); - scripts.setupSerializableTimer(TimeUnit::HOURS, 5, "test2.lua", "A", sol::make_object(mLua.sol(), 2)); - scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, "test1.lua", "A", sol::make_object(mLua.sol(), 10)); - scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, "test1.lua", "B", sol::make_object(mLua.sol(), 20)); + scripts.setupSerializableTimer(TimeUnit::SECONDS, 10, test1Id, "B", sol::make_object(mLua.sol(), 3)); + scripts.setupSerializableTimer(TimeUnit::HOURS, 10, test2Id, "B", sol::make_object(mLua.sol(), 4)); + scripts.setupSerializableTimer(TimeUnit::SECONDS, 5, test1Id, "A", sol::make_object(mLua.sol(), 1)); + scripts.setupSerializableTimer(TimeUnit::HOURS, 5, test2Id, "A", sol::make_object(mLua.sol(), 2)); + scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, test1Id, "A", sol::make_object(mLua.sol(), 10)); + scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, test1Id, "B", sol::make_object(mLua.sol(), 20)); - scripts.setupUnsavableTimer(TimeUnit::SECONDS, 10, "test2.lua", fn2); - scripts.setupUnsavableTimer(TimeUnit::HOURS, 10, "test1.lua", fn2); - scripts.setupUnsavableTimer(TimeUnit::SECONDS, 5, "test2.lua", fn1); - scripts.setupUnsavableTimer(TimeUnit::HOURS, 5, "test1.lua", fn1); - scripts.setupUnsavableTimer(TimeUnit::SECONDS, 15, "test2.lua", fn1); + scripts.setupUnsavableTimer(TimeUnit::SECONDS, 10, test2Id, fn2); + scripts.setupUnsavableTimer(TimeUnit::HOURS, 10, test1Id, fn2); + scripts.setupUnsavableTimer(TimeUnit::SECONDS, 5, test2Id, fn1); + scripts.setupUnsavableTimer(TimeUnit::HOURS, 5, test1Id, fn1); + scripts.setupUnsavableTimer(TimeUnit::SECONDS, 15, test2Id, fn1); EXPECT_EQ(counter1, 0); EXPECT_EQ(counter3, 0); @@ -358,10 +423,12 @@ return { EXPECT_EQ(counter3, 5); EXPECT_EQ(counter4, 5); + testing::internal::CaptureStdout(); ESM::LuaScripts data; scripts.save(data); - scripts.load(data, true); - scripts.registerTimerCallback("test1.lua", "B", fn4); + scripts.load(data); + scripts.registerTimerCallback(test1Id, "B", fn4); + EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\tload\nTest[test2.lua]:\tload\n"); testing::internal::CaptureStdout(); scripts.processTimers(20, 20); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7e1abe0a4e..a3f77d86bf 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -29,7 +29,7 @@ endif (GIT_CHECKOUT) # source files add_component_dir (lua - luastate scriptscontainer utilpackage serialization omwscriptsparser + luastate scriptscontainer utilpackage serialization configuration ) add_component_dir (settings diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 8e4719dba4..e78f7bed06 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -22,7 +22,7 @@ namespace LuaUtil "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "getmetatable", "setmetatable"}; static const std::string safePackages[] = {"coroutine", "math", "string", "table"}; - LuaState::LuaState(const VFS::Manager* vfs) : mVFS(vfs) + LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf) : mConf(conf), mVFS(vfs) { mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::string, sol::lib::table, sol::lib::debug); @@ -95,12 +95,11 @@ namespace LuaUtil return res; } - void LuaState::addCommonPackage(const std::string& packageName, const sol::object& package) + void LuaState::addCommonPackage(std::string packageName, sol::object package) { - if (package.is()) - mCommonPackages[packageName] = package; - else - mCommonPackages[packageName] = makeReadOnly(package); + if (!package.is()) + package = makeReadOnly(std::move(package)); + mCommonPackages.emplace(std::move(packageName), std::move(package)); } sol::protected_function_result LuaState::runInNewSandbox( @@ -148,7 +147,7 @@ namespace LuaUtil return std::move(res); } - sol::protected_function LuaState::loadScript(const std::string& path) + sol::function LuaState::loadScript(const std::string& path) { auto iter = mCompiledScripts.find(path); if (iter != mCompiledScripts.end()) diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index 8982b49b36..7ac5af0b1b 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -7,6 +7,8 @@ #include +#include "configuration.hpp" + namespace LuaUtil { @@ -22,12 +24,12 @@ namespace LuaUtil // - Access to common read-only resources from different sandboxes; // - Replace standard `require` with a safe version that allows to search // Lua libraries (only source, no dll's) in the virtual filesystem; - // - Make `print` to add the script name to the every message and - // write to Log rather than directly to stdout; + // - Make `print` to add the script name to every message and + // write to the Log rather than directly to stdout; class LuaState { public: - explicit LuaState(const VFS::Manager* vfs); + explicit LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf); ~LuaState(); // Returns underlying sol::state. @@ -40,7 +42,7 @@ namespace LuaUtil // The package can be either a sol::table with an API or a sol::function. If it is a function, // it will be evaluated (once per sandbox) the first time when requested. If the package // is a table, then `makeReadOnly` is applied to it automatically (but not to other tables it contains). - void addCommonPackage(const std::string& packageName, const sol::object& package); + void addCommonPackage(std::string packageName, sol::object package); // Creates a new sandbox, runs a script, and returns the result // (the result is expected to be an interface of the script). @@ -58,14 +60,17 @@ namespace LuaUtil void dropScriptCache() { mCompiledScripts.clear(); } + const ScriptsConfiguration& getConfiguration() const { return *mConf; } + private: static sol::protected_function_result throwIfError(sol::protected_function_result&&); template - friend sol::protected_function_result call(sol::protected_function fn, Args&&... args); + friend sol::protected_function_result call(const sol::protected_function& fn, Args&&... args); - sol::protected_function loadScript(const std::string& path); + sol::function loadScript(const std::string& path); sol::state mLua; + const ScriptsConfiguration* mConf; sol::table mSandboxEnv; std::map mCompiledScripts; std::map mCommonPackages; @@ -75,7 +80,7 @@ namespace LuaUtil // Should be used for every call of every Lua function. // It is a workaround for a bug in `sol`. See https://github.com/ThePhD/sol2/issues/1078 template - sol::protected_function_result call(sol::protected_function fn, Args&&... args) + sol::protected_function_result call(const sol::protected_function& fn, Args&&... args) { try { @@ -101,7 +106,7 @@ namespace LuaUtil std::string toString(const sol::object&); // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. - // Needed to forbid any changes in common resources that can accessed from different sandboxes. + // Needed to forbid any changes in common resources that can be accessed from different sandboxes. sol::table makeReadOnly(sol::table); sol::table getMutableFromReadOnly(const sol::userdata&); diff --git a/components/lua/omwscriptsparser.cpp b/components/lua/omwscriptsparser.cpp deleted file mode 100644 index bc73e013db..0000000000 --- a/components/lua/omwscriptsparser.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "omwscriptsparser.hpp" - -#include - -#include - -std::vector LuaUtil::parseOMWScriptsFiles(const VFS::Manager* vfs, const std::vector& scriptLists) -{ - auto endsWith = [](std::string_view s, std::string_view suffix) - { - return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); - }; - std::vector res; - for (const std::string& scriptListFile : scriptLists) - { - if (!endsWith(scriptListFile, ".omwscripts")) - { - Log(Debug::Error) << "Script list should have suffix '.omwscripts', got: '" << scriptListFile << "'"; - continue; - } - std::string content(std::istreambuf_iterator(*vfs->get(scriptListFile)), {}); - std::string_view view(content); - while (!view.empty()) - { - size_t pos = 0; - while (pos < view.size() && view[pos] != '\n') - pos++; - std::string_view line = view.substr(0, pos); - view = view.substr(std::min(pos + 1, view.size())); - if (!line.empty() && line.back() == '\r') - line = line.substr(0, pos - 1); - // Lines starting with '#' are comments. - // TODO: Maybe make the parser more robust. It is a bit inconsistent that 'path/#to/file.lua' - // is a valid path, but '#path/to/file.lua' is considered as a comment and ignored. - if (line.empty() || line[0] == '#') - continue; - if (endsWith(line, ".lua")) - res.push_back(std::string(line)); - else - Log(Debug::Error) << "Lua script should have suffix '.lua', got: '" << line.substr(0, 300) << "'"; - } - } - return res; -} diff --git a/components/lua/omwscriptsparser.hpp b/components/lua/omwscriptsparser.hpp deleted file mode 100644 index 1da9f123b2..0000000000 --- a/components/lua/omwscriptsparser.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef COMPONENTS_LUA_OMWSCRIPTSPARSER_H -#define COMPONENTS_LUA_OMWSCRIPTSPARSER_H - -#include - -namespace LuaUtil -{ - - // Parses list of `*.omwscripts` files. - std::vector parseOMWScriptsFiles(const VFS::Manager* vfs, const std::vector& scriptLists); - -} - -#endif // COMPONENTS_LUA_OMWSCRIPTSPARSER_H diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 703381a453..f4922a8b6b 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -10,8 +10,10 @@ namespace LuaUtil static constexpr std::string_view INTERFACE_NAME = "interfaceName"; static constexpr std::string_view INTERFACE = "interface"; + static constexpr std::string_view HANDLER_INIT = "onInit"; static constexpr std::string_view HANDLER_SAVE = "onSave"; static constexpr std::string_view HANDLER_LOAD = "onLoad"; + static constexpr std::string_view HANDLER_INTERFACE_OVERRIDE = "onInterfaceOverride"; static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers"; static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers"; @@ -25,147 +27,238 @@ namespace LuaUtil return res; } - ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua) + ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode) + : mNamePrefix(namePrefix), mLua(*lua), mAutoStartMode(autoStartMode) { registerEngineHandlers({&mUpdateHandlers}); mPublicInterfaces = sol::table(lua->sol(), sol::create); addPackage("openmw.interfaces", mPublicInterfaces); } - void ScriptsContainer::addPackage(const std::string& packageName, sol::object package) + void ScriptsContainer::printError(int scriptId, std::string_view msg, const std::exception& e) { - API[packageName] = makeReadOnly(std::move(package)); + Log(Debug::Error) << mNamePrefix << "[" << scriptPath(scriptId) << "] " << msg << ": " << e.what(); } - bool ScriptsContainer::addNewScript(const std::string& path) + void ScriptsContainer::addPackage(std::string packageName, sol::object package) { - if (mScripts.count(path) != 0) + mAPI.emplace(std::move(packageName), makeReadOnly(std::move(package))); + } + + bool ScriptsContainer::addCustomScript(int scriptId) + { + assert(mLua.getConfiguration()[scriptId].mFlags & ESM::LuaScriptCfg::sCustom); + std::optional onInit, onLoad; + bool ok = addScript(scriptId, onInit, onLoad); + if (ok && onInit) + callOnInit(scriptId, *onInit); + return ok; + } + + void ScriptsContainer::addAutoStartedScripts() + { + for (int scriptId : mLua.getConfiguration().getListByFlag(mAutoStartMode)) + { + std::optional onInit, onLoad; + bool ok = addScript(scriptId, onInit, onLoad); + if (ok && onInit) + callOnInit(scriptId, *onInit); + } + } + + bool ScriptsContainer::addScript(int scriptId, std::optional& onInit, std::optional& onLoad) + { + assert(scriptId >= 0 && scriptId < static_cast(mLua.getConfiguration().size())); + if (mScripts.count(scriptId) != 0) return false; // already present + const std::string& path = scriptPath(scriptId); try { - sol::table hiddenData(mLua.sol(), sol::create); - hiddenData[ScriptId::KEY] = ScriptId{this, path}; - hiddenData[REGISTERED_TIMER_CALLBACKS] = mLua.newTable(); - hiddenData[TEMPORARY_TIMER_CALLBACKS] = mLua.newTable(); - mScripts[path].mHiddenData = hiddenData; - sol::object script = mLua.runInNewSandbox(path, mNamePrefix, API, hiddenData); - std::string interfaceName = ""; - sol::object publicInterface = sol::nil; - if (script != sol::nil) + Script& script = mScripts[scriptId]; + script.mHiddenData = mLua.newTable(); + script.mHiddenData[ScriptId::KEY] = ScriptId{this, scriptId, path}; + script.mHiddenData[REGISTERED_TIMER_CALLBACKS] = mLua.newTable(); + script.mHiddenData[TEMPORARY_TIMER_CALLBACKS] = mLua.newTable(); + sol::object scriptOutput = mLua.runInNewSandbox(path, mNamePrefix, mAPI, script.mHiddenData); + if (scriptOutput == sol::nil) + return true; + sol::object engineHandlers = sol::nil, eventHandlers = sol::nil; + for (const auto& [key, value] : sol::table(scriptOutput)) { - for (auto& [key, value] : sol::table(script)) + std::string_view sectionName = key.as(); + if (sectionName == ENGINE_HANDLERS) + engineHandlers = value; + else if (sectionName == EVENT_HANDLERS) + eventHandlers = value; + else if (sectionName == INTERFACE_NAME) + script.mInterfaceName = value.as(); + else if (sectionName == INTERFACE) + script.mInterface = value.as(); + else + Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << mNamePrefix << "[" << path << "]"; + } + if (engineHandlers != sol::nil) + { + for (const auto& [key, fn] : sol::table(engineHandlers)) { - std::string_view sectionName = key.as(); - if (sectionName == ENGINE_HANDLERS) - parseEngineHandlers(value, path); - else if (sectionName == EVENT_HANDLERS) - parseEventHandlers(value, path); - else if (sectionName == INTERFACE_NAME) - interfaceName = value.as(); - else if (sectionName == INTERFACE) - publicInterface = value.as(); + std::string_view handlerName = key.as(); + if (handlerName == HANDLER_INIT) + onInit = sol::function(fn); + else if (handlerName == HANDLER_LOAD) + onLoad = sol::function(fn); + else if (handlerName == HANDLER_SAVE) + script.mOnSave = sol::function(fn); + else if (handlerName == HANDLER_INTERFACE_OVERRIDE) + script.mOnOverride = sol::function(fn); else - Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << mNamePrefix << "[" << path << "]"; + { + auto it = mEngineHandlers.find(handlerName); + if (it == mEngineHandlers.end()) + Log(Debug::Error) << "Not supported handler '" << handlerName + << "' in " << mNamePrefix << "[" << path << "]"; + else + insertHandler(it->second->mList, scriptId, fn); + } } } - if (interfaceName.empty() != (publicInterface == sol::nil)) + if (eventHandlers != sol::nil) + { + for (const auto& [key, fn] : sol::table(eventHandlers)) + { + std::string_view eventName = key.as(); + auto it = mEventHandlers.find(eventName); + if (it == mEventHandlers.end()) + it = mEventHandlers.emplace(std::string(eventName), EventHandlerList()).first; + insertHandler(it->second, scriptId, fn); + } + } + + if (script.mInterfaceName.empty() == script.mInterface.has_value()) + { Log(Debug::Error) << mNamePrefix << "[" << path << "]: 'interfaceName' should always be used together with 'interface'"; - else if (!interfaceName.empty()) - script.as()[INTERFACE] = mPublicInterfaces[interfaceName] = makeReadOnly(publicInterface); - mScriptOrder.push_back(path); - mScripts[path].mInterface = std::move(script); + script.mInterfaceName.clear(); + script.mInterface = sol::nil; + } + else if (script.mInterface) + { + script.mInterface = makeReadOnly(*script.mInterface); + insertInterface(scriptId, script); + } + return true; } catch (std::exception& e) { - mScripts.erase(path); + mScripts.erase(scriptId); Log(Debug::Error) << "Can't start " << mNamePrefix << "[" << path << "]; " << e.what(); return false; } } - bool ScriptsContainer::removeScript(const std::string& path) + void ScriptsContainer::removeScript(int scriptId) { - auto scriptIter = mScripts.find(path); + auto scriptIter = mScripts.find(scriptId); if (scriptIter == mScripts.end()) - return false; // no such script - scriptIter->second.mHiddenData[ScriptId::KEY] = sol::nil; - sol::object& script = scriptIter->second.mInterface; - if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil) + return; // no such script + Script& script = scriptIter->second; + if (script.mInterface) + removeInterface(scriptId, script); + script.mHiddenData[ScriptId::KEY] = sol::nil; + mScripts.erase(scriptIter); + for (auto& [_, handlers] : mEngineHandlers) + removeHandler(handlers->mList, scriptId); + for (auto& [_, handlers] : mEventHandlers) + removeHandler(handlers, scriptId); + } + + void ScriptsContainer::insertInterface(int scriptId, const Script& script) + { + assert(script.mInterface); + const Script* prev = nullptr; + const Script* next = nullptr; + int nextId = 0; + for (const auto& [otherId, otherScript] : mScripts) { - std::string_view interfaceName = getFieldOrNil(script, INTERFACE_NAME).as(); - if (mPublicInterfaces[interfaceName] == getFieldOrNil(script, INTERFACE)) + if (scriptId == otherId || script.mInterfaceName != otherScript.mInterfaceName) + continue; + if (otherId < scriptId) + prev = &otherScript; + else { - mPublicInterfaces[interfaceName] = sol::nil; - auto prevIt = mScriptOrder.rbegin(); - while (*prevIt != path) - prevIt++; - prevIt++; - while (prevIt != mScriptOrder.rend()) - { - sol::object& prevScript = mScripts[*(prevIt++)].mInterface; - sol::object prevInterfaceName = getFieldOrNil(prevScript, INTERFACE_NAME); - if (prevInterfaceName != sol::nil && prevInterfaceName.as() == interfaceName) - { - mPublicInterfaces[interfaceName] = getFieldOrNil(prevScript, INTERFACE); - break; - } - } + next = &otherScript; + nextId = otherId; + break; } } - sol::object engineHandlers = getFieldOrNil(script, ENGINE_HANDLERS); - if (engineHandlers != sol::nil) + if (prev && script.mOnOverride) + { + try { LuaUtil::call(*script.mOnOverride, *prev->mInterface); } + catch (std::exception& e) { printError(scriptId, "onInterfaceOverride failed", e); } + } + if (next && next->mOnOverride) { - for (auto& [key, value] : sol::table(engineHandlers)) + try { LuaUtil::call(*next->mOnOverride, *script.mInterface); } + catch (std::exception& e) { printError(nextId, "onInterfaceOverride failed", e); } + } + if (next == nullptr) + mPublicInterfaces[script.mInterfaceName] = *script.mInterface; + } + + void ScriptsContainer::removeInterface(int scriptId, const Script& script) + { + assert(script.mInterface); + const Script* prev = nullptr; + const Script* next = nullptr; + int nextId = 0; + for (const auto& [otherId, otherScript] : mScripts) + { + if (scriptId == otherId || script.mInterfaceName != otherScript.mInterfaceName) + continue; + if (otherId < scriptId) + prev = &otherScript; + else { - std::string_view handlerName = key.as(); - auto handlerIter = mEngineHandlers.find(handlerName); - if (handlerIter == mEngineHandlers.end()) - continue; - std::vector& list = handlerIter->second->mList; - list.erase(std::find(list.begin(), list.end(), value.as())); + next = &otherScript; + nextId = otherId; + break; } } - sol::object eventHandlers = getFieldOrNil(script, EVENT_HANDLERS); - if (eventHandlers != sol::nil) + if (next) { - for (auto& [key, value] : sol::table(eventHandlers)) + if (next->mOnOverride) { - EventHandlerList& list = mEventHandlers.find(key.as())->second; - list.erase(std::find(list.begin(), list.end(), value.as())); + sol::object prevInterface = sol::nil; + if (prev) + prevInterface = *prev->mInterface; + try { LuaUtil::call(*next->mOnOverride, prevInterface); } + catch (std::exception& e) { printError(nextId, "onInterfaceOverride failed", e); } } } - mScripts.erase(scriptIter); - mScriptOrder.erase(std::find(mScriptOrder.begin(), mScriptOrder.end(), path)); - return true; + else if (prev) + mPublicInterfaces[script.mInterfaceName] = *prev->mInterface; + else + mPublicInterfaces[script.mInterfaceName] = sol::nil; } - void ScriptsContainer::parseEventHandlers(sol::table handlers, std::string_view scriptPath) + void ScriptsContainer::insertHandler(std::vector& list, int scriptId, sol::function fn) { - for (auto& [key, value] : handlers) + list.emplace_back(); + int pos = list.size() - 1; + while (pos > 0 && list[pos - 1].mScriptId > scriptId) { - std::string_view eventName = key.as(); - auto it = mEventHandlers.find(eventName); - if (it == mEventHandlers.end()) - it = mEventHandlers.insert({std::string(eventName), EventHandlerList()}).first; - it->second.push_back(value); + list[pos] = std::move(list[pos - 1]); + pos--; } + list[pos].mScriptId = scriptId; + list[pos].mFn = std::move(fn); } - void ScriptsContainer::parseEngineHandlers(sol::table handlers, std::string_view scriptPath) + void ScriptsContainer::removeHandler(std::vector& list, int scriptId) { - for (auto& [key, value] : handlers) - { - std::string_view handlerName = key.as(); - if (handlerName == HANDLER_LOAD || handlerName == HANDLER_SAVE) - continue; // save and load are handled separately - auto it = mEngineHandlers.find(handlerName); - if (it == mEngineHandlers.end()) - Log(Debug::Error) << "Not supported handler '" << handlerName << "' in " << mNamePrefix << "[" << scriptPath << "]"; - else - it->second->mList.push_back(value); - } + list.erase(std::remove_if(list.begin(), list.end(), + [scriptId](const Handler& h){ return h.mScriptId == scriptId; }), + list.end()); } void ScriptsContainer::receiveEvent(std::string_view eventName, std::string_view eventData) @@ -191,13 +284,14 @@ namespace LuaUtil { try { - sol::object res = LuaUtil::call(list[i], data); + sol::object res = LuaUtil::call(list[i].mFn, data); if (res != sol::nil && !res.as()) break; // Skip other handlers if 'false' was returned. } catch (std::exception& e) { - Log(Debug::Error) << mNamePrefix << " eventHandler[" << eventName << "] failed. " << e.what(); + Log(Debug::Error) << mNamePrefix << "[" << scriptPath(list[i].mScriptId) + << "] eventHandler[" << eventName << "] failed. " << e.what(); } } } @@ -208,9 +302,19 @@ namespace LuaUtil mEngineHandlers[h->mName] = h; } + void ScriptsContainer::callOnInit(int scriptId, const sol::function& onInit) + { + try + { + const std::string& data = mLua.getConfiguration()[scriptId].mInitializationData; + LuaUtil::call(onInit, deserialize(mLua.sol(), data, mSerializer)); + } + catch (std::exception& e) { printError(scriptId, "onInit failed", e); } + } + void ScriptsContainer::save(ESM::LuaScripts& data) { - std::map> timers; + std::map> timers; auto saveTimerFn = [&](const Timer& timer, TimeUnit timeUnit) { if (!timer.mSerializable) @@ -220,78 +324,85 @@ namespace LuaUtil savedTimer.mUnit = timeUnit; savedTimer.mCallbackName = std::get(timer.mCallback); savedTimer.mCallbackArgument = timer.mSerializedArg; - if (timers.count(timer.mScript) == 0) - timers[timer.mScript] = {}; - timers[timer.mScript].push_back(std::move(savedTimer)); + timers[timer.mScriptId].push_back(std::move(savedTimer)); }; for (const Timer& timer : mSecondsTimersQueue) saveTimerFn(timer, TimeUnit::SECONDS); for (const Timer& timer : mHoursTimersQueue) saveTimerFn(timer, TimeUnit::HOURS); data.mScripts.clear(); - for (const std::string& path : mScriptOrder) + for (auto& [scriptId, script] : mScripts) { ESM::LuaScript savedScript; - savedScript.mScriptPath = path; - sol::object handler = getFieldOrNil(mScripts[path].mInterface, ENGINE_HANDLERS, HANDLER_SAVE); - if (handler != sol::nil) + savedScript.mScriptPath = script.mHiddenData.get(ScriptId::KEY).mPath; + if (script.mOnSave) { try { - sol::object state = LuaUtil::call(handler); + sol::object state = LuaUtil::call(*script.mOnSave); savedScript.mData = serialize(state, mSerializer); } - catch (std::exception& e) - { - Log(Debug::Error) << mNamePrefix << "[" << path << "] onSave failed: " << e.what(); - } + catch (std::exception& e) { printError(scriptId, "onSave failed", e); } } - auto timersIt = timers.find(path); + auto timersIt = timers.find(scriptId); if (timersIt != timers.end()) savedScript.mTimers = std::move(timersIt->second); data.mScripts.push_back(std::move(savedScript)); } } - void ScriptsContainer::load(const ESM::LuaScripts& data, bool resetScriptList) + void ScriptsContainer::load(const ESM::LuaScripts& data) { - std::map scriptsWithoutSavedData; - if (resetScriptList) + removeAllScripts(); + const ScriptsConfiguration& cfg = mLua.getConfiguration(); + + std::map scripts; + for (int scriptId : mLua.getConfiguration().getListByFlag(mAutoStartMode)) + scripts[scriptId] = nullptr; + for (const ESM::LuaScript& s : data.mScripts) { - removeAllScripts(); - for (const ESM::LuaScript& script : data.mScripts) - addNewScript(script.mScriptPath); + std::optional scriptId = cfg.findId(s.mScriptPath); + if (!scriptId) + { + Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; script not registered"; + continue; + } + if (!(cfg[*scriptId].mFlags & (ESM::LuaScriptCfg::sCustom | mAutoStartMode))) + { + Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; this script is not allowed here"; + continue; + } + scripts[*scriptId] = &s; } - else - scriptsWithoutSavedData = mScripts; - mSecondsTimersQueue.clear(); - mHoursTimersQueue.clear(); - for (const ESM::LuaScript& script : data.mScripts) + + for (const auto& [scriptId, savedScript] : scripts) { - auto iter = mScripts.find(script.mScriptPath); - if (iter == mScripts.end()) + std::optional onInit, onLoad; + if (!addScript(scriptId, onInit, onLoad)) continue; - scriptsWithoutSavedData.erase(iter->first); - iter->second.mHiddenData.get(TEMPORARY_TIMER_CALLBACKS).clear(); - try + if (savedScript == nullptr) { - sol::object handler = getFieldOrNil(iter->second.mInterface, ENGINE_HANDLERS, HANDLER_LOAD); - if (handler != sol::nil) - { - sol::object state = deserialize(mLua.sol(), script.mData, mSerializer); - LuaUtil::call(handler, state); - } + if (onInit) + callOnInit(scriptId, *onInit); + continue; } - catch (std::exception& e) + if (onLoad) { - Log(Debug::Error) << mNamePrefix << "[" << script.mScriptPath << "] onLoad failed: " << e.what(); + try + { + sol::object state = deserialize(mLua.sol(), savedScript->mData, mSerializer); + sol::object initializationData = + deserialize(mLua.sol(), mLua.getConfiguration()[scriptId].mInitializationData, mSerializer); + LuaUtil::call(*onLoad, state, initializationData); + } + catch (std::exception& e) { printError(scriptId, "onLoad failed", e); } } - for (const ESM::LuaTimer& savedTimer : script.mTimers) + for (const ESM::LuaTimer& savedTimer : savedScript->mTimers) { Timer timer; timer.mCallback = savedTimer.mCallbackName; timer.mSerializable = true; - timer.mScript = script.mScriptPath; + timer.mScriptId = scriptId; timer.mTime = savedTimer.mTime; try @@ -306,24 +417,10 @@ namespace LuaUtil else mSecondsTimersQueue.push_back(std::move(timer)); } - catch (std::exception& e) - { - Log(Debug::Error) << mNamePrefix << "[" << script.mScriptPath << "] can not load timer: " << e.what(); - } - } - } - for (auto& [path, script] : scriptsWithoutSavedData) - { - script.mHiddenData.get(TEMPORARY_TIMER_CALLBACKS).clear(); - sol::object handler = getFieldOrNil(script.mInterface, ENGINE_HANDLERS, HANDLER_LOAD); - if (handler == sol::nil) - continue; - try { LuaUtil::call(handler); } - catch (std::exception& e) - { - Log(Debug::Error) << mNamePrefix << "[" << path << "] onLoad failed: " << e.what(); + catch (std::exception& e) { printError(scriptId, "can not load timer", e); } } } + std::make_heap(mSecondsTimersQueue.begin(), mSecondsTimersQueue.end()); std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end()); } @@ -334,12 +431,13 @@ namespace LuaUtil script.mHiddenData[ScriptId::KEY] = sol::nil; } + // Note: shouldn't be called from destructor because mEngineHandlers has pointers on + // external objects that are already removed during child class destruction. void ScriptsContainer::removeAllScripts() { for (auto& [_, script] : mScripts) script.mHiddenData[ScriptId::KEY] = sol::nil; mScripts.clear(); - mScriptOrder.clear(); for (auto& [_, handlers] : mEngineHandlers) handlers->mList.clear(); mEventHandlers.clear(); @@ -351,17 +449,17 @@ namespace LuaUtil mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces; } - sol::table ScriptsContainer::getHiddenData(const std::string& scriptPath) + sol::table ScriptsContainer::getHiddenData(int scriptId) { - auto it = mScripts.find(scriptPath); + auto it = mScripts.find(scriptId); if (it == mScripts.end()) throw std::logic_error("ScriptsContainer::getHiddenData: script doesn't exist"); return it->second.mHiddenData; } - void ScriptsContainer::registerTimerCallback(const std::string& scriptPath, std::string_view callbackName, sol::function callback) + void ScriptsContainer::registerTimerCallback(int scriptId, std::string_view callbackName, sol::function callback) { - getHiddenData(scriptPath)[REGISTERED_TIMER_CALLBACKS][callbackName] = std::move(callback); + getHiddenData(scriptId)[REGISTERED_TIMER_CALLBACKS][callbackName] = std::move(callback); } void ScriptsContainer::insertTimer(std::vector& timerQueue, Timer&& t) @@ -370,12 +468,12 @@ namespace LuaUtil std::push_heap(timerQueue.begin(), timerQueue.end()); } - void ScriptsContainer::setupSerializableTimer(TimeUnit timeUnit, double time, const std::string& scriptPath, + void ScriptsContainer::setupSerializableTimer(TimeUnit timeUnit, double time, int scriptId, std::string_view callbackName, sol::object callbackArg) { Timer t; t.mCallback = std::string(callbackName); - t.mScript = scriptPath; + t.mScriptId = scriptId; t.mSerializable = true; t.mTime = time; t.mArg = callbackArg; @@ -383,15 +481,15 @@ namespace LuaUtil insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t)); } - void ScriptsContainer::setupUnsavableTimer(TimeUnit timeUnit, double time, const std::string& scriptPath, sol::function callback) + void ScriptsContainer::setupUnsavableTimer(TimeUnit timeUnit, double time, int scriptId, sol::function callback) { Timer t; - t.mScript = scriptPath; + t.mScriptId = scriptId; t.mSerializable = false; t.mTime = time; t.mCallback = mTemporaryCallbackCounter; - getHiddenData(scriptPath)[TEMPORARY_TIMER_CALLBACKS][mTemporaryCallbackCounter] = std::move(callback); + getHiddenData(scriptId)[TEMPORARY_TIMER_CALLBACKS][mTemporaryCallbackCounter] = std::move(callback); mTemporaryCallbackCounter++; insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t)); @@ -401,7 +499,7 @@ namespace LuaUtil { try { - sol::table data = getHiddenData(t.mScript); + sol::table data = getHiddenData(t.mScriptId); if (t.mSerializable) { const std::string& callbackName = std::get(t.mCallback); @@ -421,10 +519,7 @@ namespace LuaUtil callbacks[id] = sol::nil; } } - catch (std::exception& e) - { - Log(Debug::Error) << mNamePrefix << "[" << t.mScript << "] callTimer failed: " << e.what(); - } + catch (std::exception& e) { printError(t.mScriptId, "callTimer failed", e); } } void ScriptsContainer::updateTimerQueue(std::vector& timerQueue, double time) diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 69aa18e940..184072eef1 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -17,7 +17,7 @@ namespace LuaUtil // ScriptsContainer is a base class for all scripts containers (LocalScripts, // GlobalScripts, PlayerScripts, etc). Each script runs in a separate sandbox. // Scripts from different containers can interact to each other only via events. -// Scripts within one container can interact via interfaces (not implemented yet). +// Scripts within one container can interact via interfaces. // All scripts from one container have the same set of API packages available. // // Each script should return a table in a specific format that describes its @@ -42,11 +42,12 @@ namespace LuaUtil // -- An error is printed if unknown handler is specified. // engineHandlers = { // onUpdate = update, +// onInit = function(initData) ... end, -- used when the script is just created (not loaded) // onSave = function() return ... end, -// onLoad = function(state) ... end, -- "state" is the data that was earlier returned by onSave +// onLoad = function(state, initData) ... end, -- "state" is the data that was earlier returned by onSave // -// -- Works only if ScriptsContainer::registerEngineHandler is overloaded in a child class -// -- and explicitly supports 'onSomethingElse' +// -- Works only if a child class has passed a EngineHandlerList +// -- for 'onSomethingElse' to ScriptsContainer::registerEngineHandlers. // onSomethingElse = function() print("something else") end // }, // @@ -65,30 +66,36 @@ namespace LuaUtil constexpr static std::string_view KEY = "_id"; ScriptsContainer* mContainer; - std::string mPath; + int mIndex; // index in LuaUtil::ScriptsConfiguration + std::string mPath; // path to the script source in VFS std::string toString() const; }; using TimeUnit = ESM::LuaTimer::TimeUnit; // `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print` output. - ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix); + // `autoStartMode` specifies the list of scripts that should be autostarted in this container; the list itself is + // stored in ScriptsConfiguration: lua->getConfiguration().getListByFlag(autoStartMode). + ScriptsContainer(LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode = 0); + ScriptsContainer(const ScriptsContainer&) = delete; ScriptsContainer(ScriptsContainer&&) = delete; virtual ~ScriptsContainer(); + ESM::LuaScriptCfg::Flags getAutoStartMode() const { return mAutoStartMode; } + // Adds package that will be available (via `require`) for all scripts in the container. // Automatically applies LuaUtil::makeReadOnly to the package. - void addPackage(const std::string& packageName, sol::object package); + void addPackage(std::string packageName, sol::object package); - // Finds a file with given path in the virtual file system, starts as a new script, and adds it to the container. - // Returns `true` if the script was successfully added. Otherwise prints an error message and returns `false`. - // `false` can be returned if either file not found or has syntax errors or such script already exists in the container. - bool addNewScript(const std::string& path); + // Gets script with given id from ScriptsConfiguration, finds the source in the virtual file system, starts as a new script, + // adds it to the container, and calls onInit for this script. Returns `true` if the script was successfully added. + // The script should have CUSTOM flag. If the flag is not set, or file not found, or has syntax errors, returns false. + // If such script already exists in the container, then also returns false. + bool addCustomScript(int scriptId); - // Removes script. Returns `true` if it was successfully removed. - bool removeScript(const std::string& path); - void removeAllScripts(); + bool hasScript(int scriptId) const { return mScripts.count(scriptId) != 0; } + void removeScript(int scriptId); // Processes timers. gameSeconds and gameHours are time (in seconds and in game hours) passed from the game start. void processTimers(double gameSeconds, double gameHours); @@ -107,22 +114,22 @@ namespace LuaUtil // only built-in types and types from util package can be serialized. void setSerializer(const UserdataSerializer* serializer) { mSerializer = serializer; } + // Starts scripts according to `autoStartMode` and calls `onInit` for them. Not needed if `load` is used. + void addAutoStartedScripts(); + + // Removes all scripts including the auto started. + void removeAllScripts(); + // Calls engineHandler "onSave" for every script and saves the list of the scripts with serialized data to ESM::LuaScripts. void save(ESM::LuaScripts&); - // Calls engineHandler "onLoad" for every script with given data. - // If resetScriptList=true, then removes all currently active scripts and runs the scripts that were saved in ESM::LuaScripts. - // If resetScriptList=false, then list of running scripts is not changed, only engineHandlers "onLoad" are called. - void load(const ESM::LuaScripts&, bool resetScriptList); - - // Returns the hidden data of a script. - // Each script has a corresponding "hidden data" - a lua table that is not accessible from the script itself, - // but can be used by built-in packages. It contains ScriptId and can contain any arbitrary data. - sol::table getHiddenData(const std::string& scriptPath); + // Removes all scripts; starts scripts according to `autoStartMode` and + // loads the savedScripts. Runs "onLoad" for each script. + void load(const ESM::LuaScripts& savedScripts); // Callbacks for serializable timers should be registered in advance. // The script with the given path should already present in the container. - void registerTimerCallback(const std::string& scriptPath, std::string_view callbackName, sol::function callback); + void registerTimerCallback(int scriptId, std::string_view callbackName, sol::function callback); // Sets up a timer, that can be automatically saved and loaded. // timeUnit - game seconds (TimeUnit::Seconds) or game hours (TimeUnit::Hours). @@ -130,18 +137,24 @@ namespace LuaUtil // scriptPath - script path in VFS is used as script id. The script with the given path should already present in the container. // callbackName - callback (should be registered in advance) for this timer. // callbackArg - parameter for the callback (should be serializable). - void setupSerializableTimer(TimeUnit timeUnit, double time, const std::string& scriptPath, + void setupSerializableTimer(TimeUnit timeUnit, double time, int scriptId, std::string_view callbackName, sol::object callbackArg); // Creates a timer. `callback` is an arbitrary Lua function. This type of timers is called "unsavable" // because it can not be stored in saves. I.e. loading a saved game will not fully restore the state. - void setupUnsavableTimer(TimeUnit timeUnit, double time, const std::string& scriptPath, sol::function callback); + void setupUnsavableTimer(TimeUnit timeUnit, double time, int scriptId, sol::function callback); protected: + struct Handler + { + int mScriptId; + sol::function mFn; + }; + struct EngineHandlerList { std::string_view mName; - std::vector mList; + std::vector mList; // "name" must be string literal explicit EngineHandlerList(std::string_view name) : mName(name) {} @@ -151,12 +164,13 @@ namespace LuaUtil template void callEngineHandlers(EngineHandlerList& handlers, const Args&... args) { - for (sol::protected_function& handler : handlers.mList) + for (Handler& handler : handlers.mList) { - try { LuaUtil::call(handler, args...); } + try { LuaUtil::call(handler.mFn, args...); } catch (std::exception& e) { - Log(Debug::Error) << mNamePrefix << " " << handlers.mName << " failed. " << e.what(); + Log(Debug::Error) << mNamePrefix << "[" << scriptPath(handler.mScriptId) << "] " + << handlers.mName << " failed. " << e.what(); } } } @@ -171,34 +185,49 @@ namespace LuaUtil private: struct Script { - sol::object mInterface; // returned value of the script (sol::table or nil) + std::optional mOnSave; + std::optional mOnOverride; + std::optional mInterface; + std::string mInterfaceName; sol::table mHiddenData; }; struct Timer { double mTime; bool mSerializable; - std::string mScript; + int mScriptId; std::variant mCallback; // string if serializable, integer otherwise sol::object mArg; std::string mSerializedArg; bool operator<(const Timer& t) const { return mTime > t.mTime; } }; - using EventHandlerList = std::vector; + using EventHandlerList = std::vector; - void parseEngineHandlers(sol::table handlers, std::string_view scriptPath); - void parseEventHandlers(sol::table handlers, std::string_view scriptPath); + // Add to container without calling onInit/onLoad. + bool addScript(int scriptId, std::optional& onInit, std::optional& onLoad); + + // Returns the hidden data of a script. + // Each script has a corresponding "hidden data" - a lua table that is not accessible from the script itself, + // but can be used by built-in packages. It contains ScriptId and can contain any arbitrary data. + sol::table getHiddenData(int scriptId); + void printError(int scriptId, std::string_view msg, const std::exception& e); + const std::string& scriptPath(int scriptId) const { return mLua.getConfiguration()[scriptId].mScriptPath; } + void callOnInit(int scriptId, const sol::function& onInit); void callTimer(const Timer& t); void updateTimerQueue(std::vector& timerQueue, double time); static void insertTimer(std::vector& timerQueue, Timer&& t); + static void insertHandler(std::vector& list, int scriptId, sol::function fn); + static void removeHandler(std::vector& list, int scriptId); + void insertInterface(int scriptId, const Script& script); + void removeInterface(int scriptId, const Script& script); + ESM::LuaScriptCfg::Flags mAutoStartMode; const UserdataSerializer* mSerializer = nullptr; - std::map API; + std::map mAPI; - std::vector mScriptOrder; - std::map mScripts; + std::map mScripts; sol::table mPublicInterfaces; EngineHandlerList mUpdateHandlers{"onUpdate"}; From 4ec7f0625e08dad96ff016894d865ca8050098a0 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 10 Oct 2021 22:44:50 +0200 Subject: [PATCH 1547/2859] Store Lua timers in std::map rather than in sol::table. --- components/lua/scriptscontainer.cpp | 31 ++++++++++------------------- components/lua/scriptscontainer.hpp | 8 ++++---- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index f4922a8b6b..c92116f1d9 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -15,9 +15,6 @@ namespace LuaUtil static constexpr std::string_view HANDLER_LOAD = "onLoad"; static constexpr std::string_view HANDLER_INTERFACE_OVERRIDE = "onInterfaceOverride"; - static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers"; - static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers"; - std::string ScriptsContainer::ScriptId::toString() const { std::string res = mContainer->mNamePrefix; @@ -78,8 +75,6 @@ namespace LuaUtil Script& script = mScripts[scriptId]; script.mHiddenData = mLua.newTable(); script.mHiddenData[ScriptId::KEY] = ScriptId{this, scriptId, path}; - script.mHiddenData[REGISTERED_TIMER_CALLBACKS] = mLua.newTable(); - script.mHiddenData[TEMPORARY_TIMER_CALLBACKS] = mLua.newTable(); sol::object scriptOutput = mLua.runInNewSandbox(path, mNamePrefix, mAPI, script.mHiddenData); if (scriptOutput == sol::nil) return true; @@ -449,17 +444,17 @@ namespace LuaUtil mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces; } - sol::table ScriptsContainer::getHiddenData(int scriptId) + ScriptsContainer::Script& ScriptsContainer::getScript(int scriptId) { auto it = mScripts.find(scriptId); if (it == mScripts.end()) - throw std::logic_error("ScriptsContainer::getHiddenData: script doesn't exist"); - return it->second.mHiddenData; + throw std::logic_error("Script doesn't exist"); + return it->second; } void ScriptsContainer::registerTimerCallback(int scriptId, std::string_view callbackName, sol::function callback) { - getHiddenData(scriptId)[REGISTERED_TIMER_CALLBACKS][callbackName] = std::move(callback); + getScript(scriptId).mRegisteredCallbacks.emplace(std::string(callbackName), std::move(callback)); } void ScriptsContainer::insertTimer(std::vector& timerQueue, Timer&& t) @@ -489,7 +484,7 @@ namespace LuaUtil t.mTime = time; t.mCallback = mTemporaryCallbackCounter; - getHiddenData(scriptId)[TEMPORARY_TIMER_CALLBACKS][mTemporaryCallbackCounter] = std::move(callback); + getScript(t.mScriptId).mTemporaryCallbacks.emplace(mTemporaryCallbackCounter, std::move(callback)); mTemporaryCallbackCounter++; insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t)); @@ -499,24 +494,20 @@ namespace LuaUtil { try { - sol::table data = getHiddenData(t.mScriptId); + Script& script = getScript(t.mScriptId); if (t.mSerializable) { const std::string& callbackName = std::get(t.mCallback); - sol::object callback = data[REGISTERED_TIMER_CALLBACKS][callbackName]; - if (!callback.is()) + auto it = script.mRegisteredCallbacks.find(callbackName); + if (it == script.mRegisteredCallbacks.end()) throw std::logic_error("Callback '" + callbackName + "' doesn't exist"); - LuaUtil::call(callback, t.mArg); + LuaUtil::call(it->second, t.mArg); } else { int64_t id = std::get(t.mCallback); - sol::table callbacks = data[TEMPORARY_TIMER_CALLBACKS]; - sol::object callback = callbacks[id]; - if (!callback.is()) - throw std::logic_error("Temporary timer callback doesn't exist"); - LuaUtil::call(callback); - callbacks[id] = sol::nil; + LuaUtil::call(script.mTemporaryCallbacks.at(id)); + script.mTemporaryCallbacks.erase(id); } } catch (std::exception& e) { printError(t.mScriptId, "callTimer failed", e); } diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 184072eef1..b25c69b5b4 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -190,6 +190,8 @@ namespace LuaUtil std::optional mInterface; std::string mInterfaceName; sol::table mHiddenData; + std::map mRegisteredCallbacks; + std::map mTemporaryCallbacks; }; struct Timer { @@ -207,10 +209,8 @@ namespace LuaUtil // Add to container without calling onInit/onLoad. bool addScript(int scriptId, std::optional& onInit, std::optional& onLoad); - // Returns the hidden data of a script. - // Each script has a corresponding "hidden data" - a lua table that is not accessible from the script itself, - // but can be used by built-in packages. It contains ScriptId and can contain any arbitrary data. - sol::table getHiddenData(int scriptId); + // Returns script by id (throws an exception if doesn't exist) + Script& getScript(int scriptId); void printError(int scriptId, std::string_view msg, const std::exception& e); const std::string& scriptPath(int scriptId) const { return mLua.getConfiguration()[scriptId].mScriptPath; } From 47c89567fbfe95ebba2f2c501d29baa37be71772 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 26 Sep 2021 15:21:46 +0200 Subject: [PATCH 1548/2859] Load LuaScriptsCfg from both *.omwscripts and *.omwaddon files. --- apps/openmw/engine.cpp | 7 +--- apps/openmw/engine.hpp | 2 - apps/openmw/main.cpp | 8 ++-- apps/openmw/mwlua/luamanagerimp.cpp | 22 ++-------- apps/openmw/mwlua/luamanagerimp.hpp | 5 +-- apps/openmw/mwworld/esmstore.cpp | 42 ++++++++++++++++++- apps/openmw/mwworld/esmstore.hpp | 10 +++++ apps/openmw/mwworld/worldimp.cpp | 14 +++++++ apps/openmw/options.cpp | 5 +-- apps/openmw_test_suite/mwworld/test_store.cpp | 6 ++- components/esm/defs.hpp | 1 + 11 files changed, 81 insertions(+), 41 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index bbe4f25f69..bc31243ffc 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -495,11 +495,6 @@ void OMW::Engine::addGroundcoverFile(const std::string& file) mGroundcoverFiles.emplace_back(file); } -void OMW::Engine::addLuaScriptListFile(const std::string& file) -{ - mLuaScriptListFiles.push_back(file); -} - void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; @@ -714,7 +709,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mViewer->addEventHandler(mScreenCaptureHandler); - mLuaManager = new MWLua::LuaManager(mVFS.get(), mLuaScriptListFiles); + mLuaManager = new MWLua::LuaManager(mVFS.get()); mEnvironment.setLuaManager(mLuaManager); // Create input and UI first to set up a bootstrapping environment for diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 290fd890a6..5fe032d27e 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -72,7 +72,6 @@ namespace OMW std::string mCellName; std::vector mContentFiles; std::vector mGroundcoverFiles; - std::vector mLuaScriptListFiles; bool mSkipMenu; bool mUseSound; bool mCompileAll; @@ -146,7 +145,6 @@ namespace OMW */ void addContentFile(const std::string& file); void addGroundcoverFile(const std::string& file); - void addLuaScriptListFile(const std::string& file); /// Disable or enable all sounds void setSoundUsage(bool soundUsage); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index d1f9fd1787..1cf7abe2f2 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -124,9 +124,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.addGroundcoverFile(file); } - StringsVector luaScriptLists = variables["lua-scripts"].as().toStdStringVector(); - for (const auto& file : luaScriptLists) - engine.addLuaScriptListFile(file); + if (variables.count("lua-scripts")) + { + Log(Debug::Warning) << "Lua scripts have been specified via the old lua-scripts option and will not be loaded. " + "Please update them to a version which uses the new omwscripts format."; + } // startup-settings engine.setCell(variables["start"].as().toStdString()); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 7a4b319f27..30866faf56 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -11,6 +11,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/ptr.hpp" #include "luabindings.hpp" @@ -19,8 +20,7 @@ namespace MWLua { - LuaManager::LuaManager(const VFS::Manager* vfs, std::vector OMWScriptsFiles) : - mVFS(vfs), mOMWScriptsFiles(std::move(OMWScriptsFiles)), mLua(vfs, &mConfiguration) + LuaManager::LuaManager(const VFS::Manager* vfs) : mLua(vfs, &mConfiguration) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); @@ -34,23 +34,7 @@ namespace MWLua void LuaManager::initConfiguration() { - ESM::LuaScriptsCfg cfg; - for (const std::string& file : mOMWScriptsFiles) - { - if (!Misc::StringUtils::endsWith(file, ".omwscripts")) - { - Log(Debug::Error) << "Script list should have suffix '.omwscripts', got: '" << file << "'"; - continue; - } - try - { - std::string content(std::istreambuf_iterator(*mVFS->get(file)), {}); - LuaUtil::parseOMWScripts(cfg, content); - } - catch (std::exception& e) { Log(Debug::Error) << e.what(); } - } - // TODO: Add data from content files - mConfiguration.init(cfg); + mConfiguration.init(MWBase::Environment::get().getWorld()->getStore().getLuaScriptsCfg()); Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):"; for (size_t i = 0; i < mConfiguration.size(); ++i) Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index be80cfe2e7..b9151de685 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -35,7 +35,7 @@ namespace MWLua class LuaManager : public MWBase::LuaManager { public: - LuaManager(const VFS::Manager* vfs, std::vector OMWScriptsFiles); + LuaManager(const VFS::Manager* vfs); // Called by engine.cpp when the environment is fully initialized. void init(); @@ -97,9 +97,6 @@ namespace MWLua void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); - const VFS::Manager* mVFS; - const std::vector mOMWScriptsFiles; - bool mInitialized = false; bool mGlobalScriptsStarted = false; LuaUtil::ScriptsConfiguration mConfiguration; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index c5b0fff00f..66aba9139e 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -1,14 +1,16 @@ #include "esmstore.hpp" #include +#include #include #include #include -#include #include #include +#include +#include #include #include "../mwmechanics/spelllist.hpp" @@ -166,7 +168,10 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) std::string fname = mast.name; int index = ~0; for (int i = 0; i < esm.getIndex(); i++) { - const std::string candidate = allPlugins->at(i).getContext().filename; + ESM::ESMReader& reader = allPlugins->at(i); + if (reader.getFileSize() == 0) + continue; // Content file in non-ESM format + const std::string candidate = reader.getContext().filename; std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { index = i; @@ -213,6 +218,13 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) // ignore project file only records esm.skipRecord(); } + else if (n.toInt() == ESM::REC_LUAL) + { + ESM::LuaScriptsCfg cfg; + cfg.load(esm); + // TODO: update refnums in cfg.mScripts[].mInitializationData according to load order + mLuaContent.push_back(std::move(cfg)); + } else { throw std::runtime_error("Unknown record: " + n.toString()); } @@ -234,6 +246,32 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } } +ESM::LuaScriptsCfg ESMStore::getLuaScriptsCfg() const +{ + ESM::LuaScriptsCfg cfg; + for (const LuaContent& c : mLuaContent) + { + if (std::holds_alternative(c)) + { + // *.omwscripts are intentionally reloaded every time when `getLuaScriptsCfg` is called. + // It is important for the `reloadlua` console command. + try + { + auto file = std::ifstream(std::get(c)); + std::string fileContent(std::istreambuf_iterator(file), {}); + LuaUtil::parseOMWScripts(cfg, fileContent); + } + catch (std::exception& e) { Log(Debug::Error) << e.what(); } + } + else + { + const ESM::LuaScriptsCfg& addition = std::get(c); + cfg.mScripts.insert(cfg.mScripts.end(), addition.mScripts.begin(), addition.mScripts.end()); + } + } + return cfg; +} + void ESMStore::setUp(bool validateRecords) { mIds.clear(); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 6ad479f8bf..22b58f77dd 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "store.hpp" @@ -92,7 +93,16 @@ namespace MWWorld template void removeMissingObjects(Store& store); + + using LuaContent = std::variant< + ESM::LuaScriptsCfg, // data from an omwaddon + std::string>; // path to an omwscripts file + std::vector mLuaContent; + public: + void addOMWScripts(std::string filePath) { mLuaContent.push_back(std::move(filePath)); } + ESM::LuaScriptsCfg getLuaScriptsCfg() const; + /// \todo replace with SharedIterator typedef std::map::const_iterator iterator; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ccd78170bb..21e2d8380a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -111,6 +111,17 @@ namespace MWWorld LoadersContainer mLoaders; }; + struct OMWScriptsLoader : public ContentLoader + { + ESMStore& mStore; + OMWScriptsLoader(Loading::Listener& listener, ESMStore& store) : ContentLoader(listener), mStore(store) {} + void load(const boost::filesystem::path& filepath, int& index) override + { + ContentLoader::load(filepath.filename(), index); + mStore.addOMWScripts(filepath.string()); + } + }; + void World::adjustSky() { if (mSky && (isCellExterior() || isCellQuasiExterior())) @@ -156,6 +167,9 @@ namespace MWWorld gameContentLoader.addLoader(".omwaddon", &esmLoader); gameContentLoader.addLoader(".project", &esmLoader); + OMWScriptsLoader omwScriptsLoader(*listener, mStore); + gameContentLoader.addLoader(".omwscripts", &omwScriptsLoader); + loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader); listener->loadingOff(); diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp index 62ea0910dd..d68809468c 100644 --- a/apps/openmw/options.cpp +++ b/apps/openmw/options.cpp @@ -40,14 +40,11 @@ namespace OpenMW "set initial cell") ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts") ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") - ("lua-scripts", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "file(s) with a list of global Lua scripts: omwscripts") - ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index f3b2bb3dcb..3fe479587c 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwmechanics/spelllist.hpp" @@ -88,7 +89,10 @@ struct ContentFileTest : public ::testing::Test std::vector contentFiles = variables["content"].as().toStdStringVector(); for (auto & contentFile : contentFiles) - mContentFiles.push_back(collections.getPath(contentFile)); + { + if (!Misc::StringUtils::ciEndsWith(contentFile, ".omwscripts")) + mContentFiles.push_back(collections.getPath(contentFile)); + } } protected: diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 7f2fe19cc5..254e66ec3a 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -165,6 +165,7 @@ enum RecNameInts // format 1 REC_FILT = FourCC<'F','I','L','T'>::value, REC_DBGP = FourCC<'D','B','G','P'>::value, ///< only used in project files + REC_LUAL = FourCC<'L','U','A','L'>::value, // LuaScriptsCfg // format 16 - Lua scripts in saved games REC_LUAM = FourCC<'L','U','A','M'>::value, // LuaManager data From 19a0fde278b35609cff2ddf8c6a990f1cde062a2 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 17 Oct 2021 10:29:10 +0200 Subject: [PATCH 1549/2859] Filter saves by the first esm/omwgame rather than by the first content file (that can be a universal omwaddon/omwscripts) --- apps/openmw/engine.cpp | 2 +- apps/openmw/mwstate/character.cpp | 12 ++++++++++-- apps/openmw/mwstate/character.hpp | 2 ++ apps/openmw/mwstate/charactermanager.cpp | 4 ++-- apps/openmw/mwstate/charactermanager.hpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 4 ++-- apps/openmw/mwstate/statemanagerimp.hpp | 2 +- 7 files changed, 19 insertions(+), 9 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index bc31243ffc..58dabd2cee 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -669,7 +669,7 @@ void OMW::Engine::setWindowIcon() void OMW::Engine::prepareEngine (Settings::Manager & settings) { mEnvironment.setStateManager ( - new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); + new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles)); createWindow(settings); diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index df1ab1bdfb..59ddd2cd10 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -13,6 +13,15 @@ bool MWState::operator< (const Slot& left, const Slot& right) return left.mTimeStamp& contentFiles) +{ + for (const std::string& c : contentFiles) + { + if (Misc::StringUtils::ciEndsWith(c, ".esm") || Misc::StringUtils::ciEndsWith(c, ".omwgame")) + return c; + } + return ""; +} void MWState::Character::addSlot (const boost::filesystem::path& path, const std::string& game) { @@ -30,8 +39,7 @@ void MWState::Character::addSlot (const boost::filesystem::path& path, const std slot.mProfile.load (reader); - if (Misc::StringUtils::lowerCase (slot.mProfile.mContentFiles.at (0))!= - Misc::StringUtils::lowerCase (game)) + if (!Misc::StringUtils::ciEqual(getFirstGameFile(slot.mProfile.mContentFiles), game)) return; // this file is for a different game -> ignore mSlots.push_back (slot); diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 32c79a183e..e12de9ca64 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -16,6 +16,8 @@ namespace MWState bool operator< (const Slot& left, const Slot& right); + std::string getFirstGameFile(const std::vector& contentFiles); + class Character { public: diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index a324dfe0f7..027a4f38a4 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -6,8 +6,8 @@ #include MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, - const std::string& game) -: mPath (saves), mCurrent (nullptr), mGame (game) + const std::vector& contentFiles) +: mPath (saves), mCurrent (nullptr), mGame (getFirstGameFile(contentFiles)) { if (!boost::filesystem::is_directory (mPath)) { diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp index 2daf73401f..8b3f2b8f8f 100644 --- a/apps/openmw/mwstate/charactermanager.hpp +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -29,7 +29,7 @@ namespace MWState public: - CharacterManager (const boost::filesystem::path& saves, const std::string& game); + CharacterManager (const boost::filesystem::path& saves, const std::vector& contentFiles); Character *getCurrentCharacter (); ///< @note May return null diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 5e51e2f621..b9825a0f90 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -88,8 +88,8 @@ std::map MWState::StateManager::buildContentFileIndexMap (const ESM::E return map; } -MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game) -: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0) +MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::vector& contentFiles) +: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, contentFiles), mTimePlayed (0) { } diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 3534dabf2b..a29e72b3ad 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -31,7 +31,7 @@ namespace MWState public: - StateManager (const boost::filesystem::path& saves, const std::string& game); + StateManager (const boost::filesystem::path& saves, const std::vector& contentFiles); void requestQuit() override; From 95faeaa9c1e45a62c5ab4a35283224dcd9ae7338 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 17 Oct 2021 19:10:09 +0200 Subject: [PATCH 1550/2859] Fix: after reloadlua call onActive for all active scripts --- apps/openmw/mwlua/luamanagerimp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 30866faf56..5695883691 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -432,6 +432,8 @@ namespace MWLua scripts->save(data); scripts->load(data); } + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->receiveEngineEvent(LocalScripts::OnActive()); } } From dd96eba2b01a3430413532a63c2f9db9c675e109 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 10 Oct 2021 22:43:47 +0200 Subject: [PATCH 1551/2859] Update OpenMW Lua docs --- .../lua-scripting/engine_handlers.rst | 14 +- .../reference/lua-scripting/overview.rst | 122 ++++++++++++------ docs/source/reference/modding/extended.rst | 14 +- files/lua_api/openmw/core.lua | 20 ++- 4 files changed, 111 insertions(+), 59 deletions(-) diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index bcbee4349e..ef74684182 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -6,14 +6,22 @@ Engine handler is a function defined by a script, that can be called by the engi +---------------------------------------------------------------------------------------------------------+ | **Can be defined by any script** | +----------------------------------+----------------------------------------------------------------------+ +| onInit(initData) | | Called once when the script is created (not loaded). `InitData can`| +| | | `be assigned to a script in openmw-cs (not yet implemented)`. | +| | | ``onInterfaceOverride`` can be called before ``onInit``. | ++----------------------------------+----------------------------------------------------------------------+ | onUpdate(dt) | | Called every frame if game not paused. `dt` is the time | | | | from the last update in seconds. | +----------------------------------+----------------------------------------------------------------------+ -| onSave() -> data | | Called when the game is saving. May be called in inactive | +| onSave() -> savedData | | Called when the game is saving. May be called in inactive | | | | state, so it shouldn't use `openmw.nearby`. | +----------------------------------+----------------------------------------------------------------------+ -| onLoad(data) | | Called on loading with the data previosly returned by | -| | | onSave. During loading the object is always inactive. | +| onLoad(savedData, initData) | | Called on loading with the data previosly returned by | +| | | onSave. During loading the object is always inactive. initData is | +| | | the same as in onInit. | ++----------------------------------+----------------------------------------------------------------------+ +| onInterfaceOverride(base) | | Called if the current script has an interface and overrides an | +| | | interface (``base``) of another script. | +----------------------------------+----------------------------------------------------------------------+ | **Only for global scripts** | +----------------------------------+----------------------------------------------------------------------+ diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 44c79c35d8..c32fc74fa5 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -73,7 +73,7 @@ Let's write a simple example of a `Player script`: .. code-block:: Lua - -- Saved to my_lua_mod/example/player.lua + -- Save to my_lua_mod/example/player.lua local ui = require('openmw.ui') @@ -87,42 +87,82 @@ Let's write a simple example of a `Player script`: } } -In order to attach it to the player we also need a global script: +The script will be used only if it is specified in one of content files. +OpenMW Lua is an inclusive OpenMW feature, so it can not be controlled by ESP/ESM. +The options are: -.. code-block:: Lua - -- Saved to my_lua_mod/example/global.lua +1. Create text file "my_lua_mod.omwscripts" with the following line: +:: - return { - engineHandlers = { - onPlayerAdded = function(player) player:addScript('example/player.lua') end - } - } + PLAYER: example/player.lua + +2. (not implemented yet) Add the script in OpenMW CS on "Lua scripts" view and save as "my_lua_mod.omwaddon". -And one more file -- to start the global script: + +Enable it in ``openmw.cfg`` the same way as any other mod: :: - # Saved to my_lua_mod/my_lua_mod.omwscripts + data=path/to/my_lua_mod + content=my_lua_mod.omwscripts # or content=my_lua_mod.omwaddon + +Now every time the player presses "X" on a keyboard, a message is shown. - # It is just a list of global scripts to run. Each file is on a separate line. - example/global.lua -Finally :ref:`register ` it in ``openmw.cfg``: +Format of ``.omwscripts`` +========================= :: - data=path/to/my_lua_mod - lua-scripts=my_lua_mod.omwscripts + # Lines starting with '#' are comments -Now every time the player presses "X" on a keyboard, a message is shown. + GLOBAL: my_mod/some_global_script.lua + + # Script that will be automatically attached to the player + PLAYER: my_mod/player.lua + + # Local script that will be automatically attached to every NPC and every creature in the game + NPC, CREATURE: my_mod/some_other_script.lua + + # Local script that can be attached to any object by a global script + CUSTOM: my_mod/something.lua + + # Local script that will be automatically attached to any Container AND can be + # attached to any other object by a global script. + CONTAINER, CUSTOM: my_mod/container.lua + +Each script is described by one line: +``: ``. +The order of lines determines the script load order (i.e. script priorities). + +Possible flags are: + +- ``GLOBAL`` - a global script; always active, can not by stopped; +- ``CUSTOM`` - dynamic local script that can be started or stopped by a global script; +- ``PLAYER`` - an auto started player script; +- ``ACTIVATOR`` - a local script that will be automatically attached to any activator; +- ``ARMOR`` - a local script that will be automatically attached to any armor; +- ``BOOK`` - a local script that will be automatically attached to any book; +- ``CLOTHING`` - a local script that will be automatically attached to any clothing; +- ``CONTAINER`` - a local script that will be automatically attached to any container; +- ``CREATURE`` - a local script that will be automatically attached to any creature; +- ``DOOR`` - a local script that will be automatically attached to any door; +- ``INGREDIENT`` - a local script that will be automatically attached to any ingredient; +- ``LIGHT`` - a local script that will be automatically attached to any light; +- ``MISC_ITEM`` - a local script that will be automatically attached to any miscellaneous item; +- ``NPC`` - a local script that will be automatically attached to any NPC; +- ``POTION`` - a local script that will be automatically attached to any potion; +- ``WEAPON`` - a local script that will be automatically attached to any weapon. + +Several flags (except ``GLOBAL``) can be used with a single script. Use space or comma as a separator. Hot reloading ============= It is possible to modify a script without restarting OpenMW. To apply changes, open the in-game console and run the command: ``reloadlua``. This will restart all Lua scripts using the `onSave and onLoad`_ handlers the same way as if the game was saved or loaded. -It works only with existing ``*.lua`` files that are not packed to any archives. Adding new scripts or modifying ``*.omwscripts`` files always requires restarting the game. +It reloads all ``.omwscripts`` files and ``.lua`` files that are not packed to any archives. ``.omwaddon`` files and scripts packed to BSA can not be changed without restarting the game. Script structure ================ @@ -196,7 +236,7 @@ Engine handlers An engine handler is a function defined by a script, that can be called by the engine. I.e. it is an engine-to-script interaction. Not visible to other scripts. If several scripts register an engine handler with the same name, -the engine calls all of them in the same order as the scripts were started. +the engine calls all of them according to the load order (i.e. the order of ``content=`` entries in ``openmw.cfg``) and the order of scripts in ``omwaddon/omwscripts``. Some engine handlers are allowed only for global, or only for local/player scripts. Some are universal. See :ref:`Engine handlers reference`. @@ -210,12 +250,6 @@ The value that `onSave` returns will be passed to `onLoad` when the game is load It is the only way to save the internal state of a script. All other script variables will be lost after closing the game. The saved state must be :ref:`serializable `. -The list of active global scripts is controlled by ``*.omwscripts`` files. Loading a save doesn't synchronize -the list of global scripts with those that were active previously, it only calls `onLoad` for those currently active. - -For local scripts the situation is different. When a save is loading, it tries to run all local scripts that were saved. -So if ``lua-scripts=`` entries of some mod are removed, but ``data=`` entries are still enabled, then local scripts from the mod may still run. - `onSave` and `onLoad` can be called even for objects in inactive state, so it shouldn't use `openmw.nearby`. An example: @@ -366,26 +400,28 @@ Overriding the interface and adding a debug output: .. code-block:: Lua - local interfaces = require('openmw.interfaces') - - -- it is important to save it before returning the new interface - local orig = interfaces.SomeUtils + local baseInterface = nil -- will be assigned by `onInterfaceOverride` + interface = { + version = 1, + doSomething = function(x, y) + print(string.format('SomeUtils.doSomething(%d, %d)', x, y)) + baseInterface.doSomething(x, y) -- calls the original `doSomething` + + -- WRONG! Would lead to an infinite recursion. + -- local interfaces = require('openmw.interfaces') + -- interfaces.SomeUtils.doSomething(x, y) + end, + } return { - interfaceName = "SomeUtils" - interface = { - version = orig.version, - doSomething = function(x, y) - print(string.format('SomeUtils.doSomething(%d, %d)', x, y)) - orig.doSomething(x, y) -- calls the original `doSomething` - - -- WRONG! Would lead to an infinite recursion. - -- interfaces.SomeUtils.doSomething(x, y) - end, - } + interfaceName = "SomeUtils", + interface = interface, + engineHandlers = { + onInterfaceOverride = function(base) baseInterface = base end, + }, } -A general recomendation about overriding is that the new interface should be fully compatible with the old one. +A general recommendation about overriding is that the new interface should be fully compatible with the old one. So it is fine to change the behaviour of `SomeUtils.doSomething`, but if you want to add a completely new function, it would be better to create a new interface for it. For example `SomeUtilsExtended` with an additional function `doSomethingElse`. @@ -418,7 +454,7 @@ Events are the main way of interacting between local and global scripts. They are not recommended for interactions between two global scripts, because in this case interfaces are more convenient. If several scripts register handlers for the same event, the handlers will be called in reverse order (opposite to engine handlers). -I.e. the handler from the last attached script will be called first. +I.e. the handler from the last script in the load order will be called first. Return value 'false' means "skip all other handlers for this event". Any other return value (including nil) means nothing. @@ -471,7 +507,7 @@ The protection mod attaches an additional local script to every actor. The scrip eventHandlers = { DamagedByDarkPower = reduceDarkDamage }, } -In order to be able to intercept the event, the protection script should be attached after the original script (i.e. below in the load order). +In order to be able to intercept the event, the protection script should be placed in the load order below the original script. Timers diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index f107617b33..db7df2e916 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -333,17 +333,9 @@ Lua scripting OpenMW supports Lua scripts. See :ref:`Lua scripting documentation `. It is not compatible with MWSE. A mod with Lua scripts will work only if it was developed specifically for OpenMW. -Mods can contain ``*.omwscripts`` files. They should be registered in the ``openmw.cfg`` via "lua-scripts" entries. The order of the "lua-scripts" entries can be important. If "some_lua_mod" uses API provided by "another_lua_mod", then omwscripts from "another_lua_mod" should be registered first. For example: - -:: - - data="path/to/another_lua_mod" - content=another_lua_mod.omwaddon - lua-scripts=another_lua_mod.omwscripts - - data="path/to/some_lua_mod" - content=some_lua_mod.omwaddon - lua-scripts=some_lua_mod.omwscripts +Installation of a Lua mod is the same as of any other mod: add ``data=`` and ``content=`` entries to ``openmw.cfg``. +Files with suffix ``.omwscripts`` are special type of content files and should also be enabled using ``content=`` entries. +Note that for some mods load order can be important. .. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 .. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 50706b9770..35513bbb79 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -192,10 +192,26 @@ ------------------------------------------------------------------------------- -- Add new local script to the object. --- Can be called only from a global script. +-- Can be called only from a global script. Script should be specified in a content +-- file (omwgame/omwaddon/omwscripts) with a CUSTOM flag. -- @function [parent=#GameObject] addScript -- @param self --- @param #string scriptPath Path to the script in OpenMW virtual filesystem +-- @param #string scriptPath Path to the script in OpenMW virtual filesystem. + +------------------------------------------------------------------------------- +-- Whether a script with given path is attached to this object. +-- Can be called only from a global script. +-- @function [parent=#GameObject] hasScript +-- @param self +-- @param #string scriptPath Path to the script in OpenMW virtual filesystem. +-- @return #boolean + +------------------------------------------------------------------------------- +-- Removes script that was attached by `addScript` +-- Can be called only from a global script. +-- @function [parent=#GameObject] removeScript +-- @param self +-- @param #string scriptPath Path to the script in OpenMW virtual filesystem. ------------------------------------------------------------------------------- -- Moves object to given cell and position. From 3ee6657768b6af74489ac6c16d2eecceb64cd412 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 26 Oct 2021 22:23:24 +0400 Subject: [PATCH 1552/2859] Early out from LOS check when source and target is the same (bug 5913) --- CHANGELOG.md | 1 + apps/openmw/mwphysics/physicssystem.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d609b0ae7f..de0d4e17d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #5863: GetEffect should return true after the player has teleported + Bug #5913: Failed assertion during Ritual of Trees quest Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6051: NaN water height in ESM file is not handled gracefully Bug #6066: addtopic "return" does not work from within script. No errors thrown diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2a5bcb58bd..f7678bed27 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -370,6 +370,8 @@ namespace MWPhysics bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const { + if (actor1 == actor2) return true; + const auto it1 = mActors.find(actor1.mRef); const auto it2 = mActors.find(actor2.mRef); if (it1 == mActors.end() || it2 == mActors.end()) From cd358ce1f999c267e61eb57533379bcf8611ffa8 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 27 Oct 2021 11:48:17 +0200 Subject: [PATCH 1553/2859] Update CMakeLists.txt --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 537c90670d..13196a4aa6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -707,8 +707,7 @@ endif() if (BUILD_OPENMW AND APPLE) # Without these flags LuaJit crashes on startup on OSX set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") - set_target_properties(components PROPERTIES COMPILE_DEFINITIONS "GL_SILENCE_DEPRECATION=1") - set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS "GL_SILENCE_DEPRECATION=1") + set_target_properties(components openmw PROPERTIES COMPILE_DEFINITIONS "GL_SILENCE_DEPRECATION=1") endif() # Apple bundling From 1ff2b105110451b63adab5f048a5aef6fa5afd0e Mon Sep 17 00:00:00 2001 From: wareya Date: Wed, 27 Oct 2021 15:09:50 +0000 Subject: [PATCH 1554/2859] Fix hard rain ripple seams and broken shader compilation --- files/shaders/water_fragment.glsl | 42 ++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 8d608e53c4..929bdd603d 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -85,7 +85,7 @@ float blipDerivative(float x) vec2 randomize_center(vec2 i_part, float time) { - time = 1.0 + mod(time, 10000.0); // so things don't get out of hand after long runtimes + time = 0.5 + mod(time/1000.0, 1.0)*0.5; vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(i_part*time) - 1.0); return center; } @@ -99,13 +99,15 @@ vec4 circle(vec2 coords, vec2 uv, float adjusted_time) float d = length(toCenter); float ringfollower = (phase-d/r)*6.0-1.0; float energy = 1.0-phase; - energy = energy*energy; + float range = blip(min(0.0, ringfollower)); vec2 normal = -toCenter*blipDerivative(ringfollower)*5.0; + // for fake specularity float height = blip(ringfollower); - vec4 ret = vec4(normal.x, normal.y, height, height); - ret.xyw *= energy; - ret.xyz = normalize(ret.x, ret.y, ret.z+0.001); + vec4 ret = vec4(normal.x, normal.y, 0.5, height); + ret.xyw *= energy*energy*range; + // do energy adjustment here rather than later, so that we can use the w component for fake specularity + ret.xyz = normalize(ret.xyz) * energy*range; return ret; } @@ -115,12 +117,18 @@ vec4 rain(vec2 uv, float time) vec2 i_part = floor(uv * RAIN_RIPPLE_GAPS); float time_prog = time * 1.2 + randPhase(i_part); vec2 f_part = fract(uv * RAIN_RIPPLE_GAPS); - return circle(f_part, uv, time_prog) - - circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET) / 4.0 - + circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET*2.0) / 8.0 - - circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET*3.0) / 16.0; + vec4 a = circle(f_part, uv, time_prog); + vec4 b = circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET); + vec4 c = circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET*2.0); + vec4 d = circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET*3.0); + vec4 ret = a - b /2.0 + c /4.0 - d /8.0; + // z should always point up + ret.z = a.z + b.z/2.0 + c.z/4.0 + d.z/8.0; + // fake specularity looks weird if we use every single ring, also if the inner rings are too bright + ret.w = a.w + c.w/8.0; + return ret; } -vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and ripple height in w +vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and ripple energy in w { return rain(uv,time) + @@ -217,11 +225,13 @@ void main(void) vec4 rainRipple; if (rainIntensity > 0.01) - rainRipple = rainCombined(position.xy / 1000.0,waterTimer) * clamp(rainIntensity,0.0,1.0); + rainRipple = rainCombined(position.xy/1000.0, waterTimer) * clamp(rainIntensity, 0.0, 1.0); else rainRipple = vec4(0.0); - vec3 rippleAdd = rainRipple.xyz * abs(rainRipple.w) * 10.0; + vec3 rippleAdd = rainRipple.xyz * 10.0; + // artificial specularity to make rain ripples more noticeable + float rainSpecular = abs(rainRipple.w); 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); @@ -289,7 +299,7 @@ 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 * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + vec3(abs(rainRipple.w)) * 0.2; + gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + vec3(rainSpecular) * 0.3; gl_FragData[0].w = 1.0; // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping @@ -302,7 +312,7 @@ void main(void) 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); #else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + vec3(abs(rainRipple.w)) * 0.7; + gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + vec3(rainSpecular) * 0.7; gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); #endif @@ -312,7 +322,9 @@ void main(void) #else float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #endif - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + + applyShadowDebugOverlay(); } From e436147c10f5ef164d42d4cf3ade8548f0f2e1bd Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 27 Oct 2021 09:16:01 -0700 Subject: [PATCH 1555/2859] improved toggle world --- apps/openmw/mwrender/renderingmanager.cpp | 8 +-- apps/openmw/mwrender/vismask.hpp | 3 ++ apps/openmw/mwrender/water.cpp | 62 +++++++++++++++++++---- apps/openmw/mwrender/water.hpp | 3 ++ 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 1f29c45182..7b8393f690 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -753,12 +753,12 @@ namespace MWRender else if (mode == Render_Scene) { unsigned int mask = mViewer->getCamera()->getCullMask(); - bool enabled = mask&Mask_Scene; - enabled = !enabled; + bool enabled = !(mask&sToggleWorldMask); if (enabled) - mask |= Mask_Scene; + mask |= sToggleWorldMask; else - mask &= ~Mask_Scene; + mask &= ~sToggleWorldMask; + mWater->showWorld(enabled); mViewer->getCamera()->setCullMask(mask); return enabled; } diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index 87ca9415fa..a7a28614cb 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -58,6 +58,9 @@ namespace MWRender Mask_Groundcover = (1<<20), }; + // Defines masks to remove when using ToggleWorld command + constexpr static unsigned int sToggleWorldMask = Mask_Debug | Mask_Actor | Mask_Terrain | Mask_Object | Mask_Static | Mask_Groundcover; + } #endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 37368b8e7a..d8f92d1d1f 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -260,6 +260,7 @@ class Refraction : public SceneUtil::RTTNode public: Refraction(uint32_t rttSize) : RTTNode(rttSize, rttSize, 1, false) + , mNodeMask(Refraction::sDefaultCullMask) { mClipCullNode = new ClipCullNode; } @@ -273,8 +274,6 @@ public: camera->addCullCallback(new InheritViewPointCallback); camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); - camera->setCullMask(Mask_Effect | Mask_Scene | Mask_Object | Mask_Static | Mask_Terrain | Mask_Actor | Mask_ParticleSystem | Mask_Sky | Mask_Sun | Mask_Player | Mask_Lighting | Mask_Groundcover); - // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) @@ -293,6 +292,7 @@ public: void apply(osg::Camera* camera) override { camera->setViewMatrix(mViewMatrix); + camera->setCullMask(mNodeMask); } void setScene(osg::Node* scene) @@ -314,10 +314,22 @@ public: mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0, 0, -1), osg::Vec3d(0, 0, waterLevel))); } + void showWorld(bool show) + { + if (show) + mNodeMask = Refraction::sDefaultCullMask; + else + mNodeMask = Refraction::sDefaultCullMask & ~sToggleWorldMask; + } + private: osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; osg::Matrix mViewMatrix{ osg::Matrix::identity() }; + + unsigned int mNodeMask; + + static constexpr unsigned int sDefaultCullMask = Mask_Effect | Mask_Scene | Mask_Object | Mask_Static | Mask_Terrain | Mask_Actor | Mask_ParticleSystem | Mask_Sky | Mask_Sun | Mask_Player | Mask_Lighting | Mask_Groundcover; }; class Reflection : public SceneUtil::RTTNode @@ -357,15 +369,8 @@ public: void setInterior(bool isInterior) { - int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - reflectionDetail = std::min(5, std::max(isInterior ? 2 : 0, reflectionDetail)); - unsigned int extraMask = 0; - if(reflectionDetail >= 1) extraMask |= Mask_Terrain; - if(reflectionDetail >= 2) extraMask |= Mask_Static; - if(reflectionDetail >= 3) extraMask |= Mask_Effect | Mask_ParticleSystem | Mask_Object; - if(reflectionDetail >= 4) extraMask |= Mask_Player | Mask_Actor; - if(reflectionDetail >= 5) extraMask |= Mask_Groundcover; - mNodeMask = Mask_Scene | Mask_Sky | Mask_Lighting | extraMask; + mInterior = isInterior; + mNodeMask = calcNodeMask(); } void setWaterLevel(float waterLevel) @@ -382,11 +387,34 @@ public: mClipCullNode->addChild(scene); } + void showWorld(bool show) + { + if (show) + mNodeMask = calcNodeMask(); + else + mNodeMask = calcNodeMask() & ~sToggleWorldMask; + } + private: + + unsigned int calcNodeMask() + { + int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); + reflectionDetail = std::min(5, std::max(mInterior ? 2 : 0, reflectionDetail)); + unsigned int extraMask = 0; + if(reflectionDetail >= 1) extraMask |= Mask_Terrain; + if(reflectionDetail >= 2) extraMask |= Mask_Static; + if(reflectionDetail >= 3) extraMask |= Mask_Effect | Mask_ParticleSystem | Mask_Object; + if(reflectionDetail >= 4) extraMask |= Mask_Player | Mask_Actor; + if(reflectionDetail >= 5) extraMask |= Mask_Groundcover; + return Mask_Scene | Mask_Sky | Mask_Lighting | extraMask; + } + osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; osg::Node::NodeMask mNodeMask; osg::Matrix mViewMatrix{ osg::Matrix::identity() }; + bool mInterior; }; /// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. @@ -422,6 +450,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem , mToggled(true) , mTop(0) , mInterior(false) + , mShowWorld(true) , mCullCallback(nullptr) , mShaderWaterStateSetUpdater(nullptr) { @@ -519,6 +548,8 @@ void Water::updateWaterMaterial() mParent->addChild(mRefraction); } + showWorld(mShowWorld); + createShaderWaterStateSet(mWaterNode, mReflection, mRefraction); } else @@ -812,4 +843,13 @@ void Water::clearRipples() mSimulation->clear(); } +void Water::showWorld(bool show) +{ + if (mReflection) + mReflection->showWorld(show); + if (mRefraction) + mRefraction->showWorld(show); + mShowWorld = show; +} + } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 719e4fdc2b..c7acbf708f 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -71,6 +71,7 @@ namespace MWRender bool mToggled; float mTop; bool mInterior; + bool mShowWorld; osg::Callback* mCullCallback; osg::ref_ptr mShaderWaterStateSetUpdater; @@ -124,6 +125,8 @@ namespace MWRender osg::Vec3d getPosition() const; void processChangedSettings(const Settings::CategorySettingVector& settings); + + void showWorld(bool show); }; } From 5009b66ef5bccc447603cc0d58592106cf0cd1a0 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Thu, 7 Oct 2021 21:47:05 +0200 Subject: [PATCH 1556/2859] Use std::variant in the physics simulation for the different types of objects. For now only support only for actors. --- apps/openmw/mwphysics/actor.cpp | 12 ++ apps/openmw/mwphysics/actor.hpp | 3 + apps/openmw/mwphysics/movementsolver.cpp | 2 +- apps/openmw/mwphysics/movementsolver.hpp | 2 +- apps/openmw/mwphysics/mtphysics.cpp | 175 ++++++++++++++++------- apps/openmw/mwphysics/mtphysics.hpp | 8 +- apps/openmw/mwphysics/physicssystem.cpp | 34 ++--- apps/openmw/mwphysics/physicssystem.hpp | 6 +- 8 files changed, 155 insertions(+), 87 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index e140141e32..c7e2308e2f 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -12,6 +12,7 @@ #include "collisiontype.hpp" #include "mtphysics.hpp" +#include "trace.h" #include @@ -303,4 +304,15 @@ osg::Vec3f Actor::velocity() return std::exchange(mVelocity, osg::Vec3f()); } +bool Actor::canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const +{ + const float halfZ = getHalfExtents().z(); + const osg::Vec3f actorPosition = getPosition(); + const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); + const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); + MWPhysics::ActorTracer tracer; + tracer.doTrace(getCollisionObject(), startingPosition, destinationPosition, world); + return (tracer.mFraction >= 1.0f); +} + } diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 0846401c1d..d2ebd78379 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -12,6 +12,7 @@ class btCollisionShape; class btCollisionObject; +class btCollisionWorld; class btConvexShape; namespace Resource @@ -165,6 +166,8 @@ namespace MWPhysics void setVelocity(osg::Vec3f velocity); osg::Vec3f velocity(); + bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const; + private: MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 44a5391f0d..29810e9085 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -116,7 +116,7 @@ namespace MWPhysics } void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, - WorldFrameData& worldData) + const WorldFrameData& worldData) { // Reset per-frame data actor.mWalkingOnWater = false; diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 30733eeec8..837004f232 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -43,7 +43,7 @@ namespace MWPhysics { public: static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight); - static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData); + static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, const WorldFrameData& worldData); static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld); }; } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index a0dde67c2e..ad11878369 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -111,6 +111,106 @@ namespace return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor); } + namespace Visitors + { + struct InitPosition + { + const btCollisionWorld* mCollisionWorld; + void operator()(MWPhysics::ActorSimulation& sim) const + { + auto& [actor, frameData] = sim; + actor->applyOffsetChange(); + frameData.mPosition = actor->getPosition(); + if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld)) + { + frameData.mPosition.z() = frameData.mWaterlevel; + MWBase::Environment::get().getWorld()->moveObject(actor->getPtr(), frameData.mPosition, false); + } + frameData.mOldHeight = frameData.mPosition.z(); + const auto rotation = actor->getPtr().getRefData().getPosition().asRotationVec3(); + frameData.mRotation = osg::Vec2f(rotation.x(), rotation.z()); + frameData.mInertia = actor->getInertialForce(); + frameData.mStuckFrames = actor->getStuckFrames(); + frameData.mLastStuckPosition = actor->getLastStuckPosition(); + } + }; + + struct PreStep + { + btCollisionWorld* mCollisionWorld; + void operator()(MWPhysics::ActorSimulation& sim) const + { + MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld); + } + }; + + struct UpdatePosition + { + btCollisionWorld* mCollisionWorld; + void operator()(MWPhysics::ActorSimulation& sim) const + { + auto& [actor, frameData] = sim; + if (actor->setPosition(frameData.mPosition)) + { + frameData.mPosition = actor->getPosition(); // account for potential position change made by script + actor->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); + } + } + }; + + struct Move + { + const float mPhysicsDt; + const btCollisionWorld* mCollisionWorld; + const MWPhysics::WorldFrameData& mWorldFrameData; + void operator()(MWPhysics::ActorSimulation& sim) const + { + MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData); + } + }; + + struct Sync + { + const bool mAdvanceSimulation; + const float mTimeAccum; + const float mPhysicsDt; + const MWPhysics::PhysicsTaskScheduler* scheduler; + void operator()(MWPhysics::ActorSimulation& sim) const + { + auto& [actor, frameData] = sim; + auto ptr = actor->getPtr(); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const float heightDiff = frameData.mPosition.z() - frameData.mOldHeight; + const bool isStillOnGround = (mAdvanceSimulation && frameData.mWasOnGround && frameData.mIsOnGround); + + if (isStillOnGround || frameData.mFlying || isUnderWater(frameData) || frameData.mSlowFall < 1) + stats.land(ptr == MWMechanics::getPlayer() && (frameData.mFlying || isUnderWater(frameData))); + else if (heightDiff < 0) + stats.addToFallHeight(-heightDiff); + + actor->setSimulationPosition(::interpolateMovements(*actor, frameData, mTimeAccum, mPhysicsDt)); + actor->setLastStuckPosition(frameData.mLastStuckPosition); + actor->setStuckFrames(frameData.mStuckFrames); + if (mAdvanceSimulation) + { + MWWorld::Ptr standingOn; + auto* ptrHolder = static_cast(scheduler->getUserPointer(frameData.mStandingOn)); + if (ptrHolder) + standingOn = ptrHolder->getPtr(); + actor->setStandingOnPtr(standingOn); + // the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the change + if (actor->getOnGround() == frameData.mWasOnGround) + actor->setOnGround(frameData.mIsOnGround); + actor->setOnSlope(frameData.mIsOnSlope); + actor->setWalkingOnWater(frameData.mWalkingOnWater); + actor->setInertialForce(frameData.mInertia); + } + } + }; + } + namespace Config { /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading and user requested more than 1 background threads @@ -235,13 +335,12 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); - assert(actors.size() == actorsData.size()); double timeStart = mTimer->tick(); @@ -259,19 +358,19 @@ namespace MWPhysics timeAccum -= numSteps*newDelta; // init - for (size_t i = 0; i < actors.size(); ++i) + const Visitors::InitPosition vis{mCollisionWorld}; + for (auto& sim : simulations) { - actorsData[i].updatePosition(*actors[i], mCollisionWorld); + std::visit(vis, sim); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; mPhysicsDt = newDelta; - mActors = std::move(actors); - mActorsFrameData = std::move(actorsData); + mSimulations = std::move(simulations); mAdvanceSimulation = (mRemainingSteps != 0); mNewFrame = true; - mNumJobs = mActorsFrameData.size(); + mNumJobs = mSimulations.size(); mNextLOS.store(0, std::memory_order_relaxed); mNextJob.store(0, std::memory_order_release); @@ -301,8 +400,7 @@ namespace MWPhysics MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); - mActors.clear(); - mActorsFrameData.clear(); + mSimulations.clear(); for (const auto& [_, actor] : actors) { actor->updatePosition(); @@ -467,47 +565,11 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActorsPositions() { - for (size_t i = 0; i < mActors.size(); ++i) - { - if (mActors[i]->setPosition(mActorsFrameData[i].mPosition)) - { - MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); - mActorsFrameData[i].mPosition = mActors[i]->getPosition(); // account for potential position change made by script - mActors[i]->updateCollisionObjectPosition(); - mCollisionWorld->updateSingleAabb(mActors[i]->getCollisionObject()); - } - } - } - - void PhysicsTaskScheduler::updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const - { - auto ptr = actor.getPtr(); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; - const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); - - if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1) - stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData))); - else if (heightDiff < 0) - stats.addToFallHeight(-heightDiff); - - actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); - actor.setLastStuckPosition(actorData.mLastStuckPosition); - actor.setStuckFrames(actorData.mStuckFrames); - if (simulationPerformed) + const Visitors::UpdatePosition vis{mCollisionWorld}; + for (auto& sim : mSimulations) { - MWWorld::Ptr standingOn; - auto* ptrHolder = static_cast(getUserPointer(actorData.mStandingOn)); - if (ptrHolder) - standingOn = ptrHolder->getPtr(); - actor.setStandingOnPtr(standingOn); - // the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the change - if (actor.getOnGround() == actorData.mWasOnGround) - actor.setOnGround(actorData.mIsOnGround); - actor.setOnSlope(actorData.mIsOnSlope); - actor.setWalkingOnWater(actorData.mWalkingOnWater); - actor.setInertialForce(actorData.mInertia); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); + std::visit(vis, sim); } } @@ -532,10 +594,11 @@ namespace MWPhysics { mPreStepBarrier->wait([this] { afterPreStep(); }); int job = 0; + const Visitors::Move vis{mPhysicsDt, mCollisionWorld, *mWorldFrameData}; while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads); - MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); + std::visit(vis, mSimulations[job]); } mPostStepBarrier->wait([this] { afterPostStep(); }); @@ -577,7 +640,7 @@ namespace MWPhysics void PhysicsTaskScheduler::releaseSharedStates() { std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex); - mActors.clear(); + mSimulations.clear(); mUpdateAabb.clear(); } @@ -586,10 +649,11 @@ namespace MWPhysics updateAabbs(); if (!mRemainingSteps) return; - for (size_t i = 0; i < mActors.size(); ++i) + const Visitors::PreStep vis{mCollisionWorld}; + for (auto& sim : mSimulations) { MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); - MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld); + std::visit(vis, sim); } } @@ -618,7 +682,8 @@ namespace MWPhysics void PhysicsTaskScheduler::syncWithMainThread() { - for (size_t i = 0; i < mActors.size(); ++i) - updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); + const Visitors::Sync vis{mAdvanceSimulation, mTimeAccum, mPhysicsDt, this}; + for (auto& sim : mSimulations) + std::visit(vis, sim); } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 08997947e4..44330b2cc6 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -39,7 +40,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float & timeAccum, std::vector&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -57,14 +58,12 @@ namespace MWPhysics bool getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2); void debugDraw(); void* getUserPointer(const btCollisionObject* object) const; - void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler() private: void doSimulation(); void worker(); void updateActorsPositions(); - void updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const; bool hasLineOfSight(const Actor* actor1, const Actor* actor2); void refreshLOSCache(); void updateAabbs(); @@ -77,8 +76,7 @@ namespace MWPhysics void syncWithMainThread(); std::unique_ptr mWorldFrameData; - std::vector> mActors; - std::vector mActorsFrameData; + std::vector mSimulations; std::unordered_set mCollisionObjects; float mDefaultPhysicsDt; float mPhysicsDt; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2a5bcb58bd..f6fc3902e7 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -60,19 +60,6 @@ namespace { - bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world) - { - if (!physicActor) - return false; - const float halfZ = physicActor->getHalfExtents().z(); - const osg::Vec3f actorPosition = physicActor->getPosition(); - const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); - const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); - MWPhysics::ActorTracer tracer; - tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); - return (tracer.mFraction >= 1.0f); - } - void handleJump(const MWWorld::Ptr &ptr) { if (!ptr.getClass().isActor()) @@ -386,7 +373,8 @@ namespace MWPhysics bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { - return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get()); + const auto* physactor = getActor(actor); + return physactor && physactor->canMoveToWaterSurface(waterlevel, mCollisionWorld.get()); } osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const @@ -727,11 +715,10 @@ namespace MWPhysics actor->setVelocity(osg::Vec3f()); } - std::pair>, std::vector> PhysicsSystem::prepareFrameData(bool willSimulate) + std::vector PhysicsSystem::prepareSimulation(bool willSimulate) { - std::pair>, std::vector> framedata; - framedata.first.reserve(mActors.size()); - framedata.second.reserve(mActors.size()); + std::vector simulations; + simulations.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [ref, physicActor] : mActors) { @@ -760,14 +747,13 @@ namespace MWPhysics const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); - framedata.first.emplace_back(physicActor); - framedata.second.emplace_back(*physicActor, inert, waterCollision, slowFall, waterlevel); + simulations.emplace_back(ActorSimulation{physicActor, ActorFrameData{*physicActor, inert, waterCollision, slowFall, waterlevel}}); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) handleJump(ptr); } - return framedata; + return simulations; } void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) @@ -793,9 +779,9 @@ namespace MWPhysics mTaskScheduler->resetSimulation(mActors); else { - auto [actors, framedata] = prepareFrameData(mTimeAccum >= mPhysicsDt); + auto simulations = prepareSimulation(mTimeAccum >= mPhysicsDt); // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(actors), std::move(framedata), frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(simulations), frameStart, frameNumber, stats); } } @@ -982,7 +968,7 @@ namespace MWPhysics { actor.applyOffsetChange(); mPosition = actor.getPosition(); - if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(&actor, mWaterlevel, world)) + if (mWaterCollision && mPosition.z() < mWaterlevel && actor.canMoveToWaterSurface(mWaterlevel, world)) { mPosition.z() = mWaterlevel; MWBase::Environment::get().getWorld()->moveObject(actor.getPtr(), mPosition, false); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6ec4ebfda9..be9fa10aa6 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -107,6 +108,9 @@ namespace MWPhysics osg::Vec3f mStormDirection; }; + using ActorSimulation = std::pair, ActorFrameData>; + using Simulation = std::variant; + class PhysicsSystem : public RayCastingInterface { public: @@ -253,7 +257,7 @@ namespace MWPhysics void updateWater(); - std::pair>, std::vector> prepareFrameData(bool willSimulate); + std::vector prepareSimulation(bool willSimulate); std::unique_ptr mBroadphase; std::unique_ptr mCollisionConfiguration; From ad7a810a62f669f272ffa55b0609ac611bffcedc Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 9 Oct 2021 18:13:54 +0200 Subject: [PATCH 1557/2859] Unify interface for Actor and Projectile --- apps/openmw/mwphysics/actor.cpp | 27 +------------------ apps/openmw/mwphysics/actor.hpp | 12 --------- apps/openmw/mwphysics/mtphysics.cpp | 2 +- apps/openmw/mwphysics/projectile.cpp | 30 +++++---------------- apps/openmw/mwphysics/projectile.hpp | 7 +---- apps/openmw/mwphysics/ptrholder.hpp | 40 ++++++++++++++++++++++++++++ 6 files changed, 50 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index c7e2308e2f..4857339228 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -23,7 +23,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) , mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents) - , mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0} + , mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) @@ -134,11 +134,6 @@ void Actor::setSimulationPosition(const osg::Vec3f& position) mSimulationPosition = position; } -osg::Vec3f Actor::getSimulationPosition() const -{ - return mSimulationPosition; -} - osg::Vec3f Actor::getScaledMeshTranslation() const { return mRotation * osg::componentMultiply(mMeshTranslation, mScale); @@ -192,16 +187,6 @@ void Actor::applyOffsetChange() mWorldPositionChanged = true; } -osg::Vec3f Actor::getPosition() const -{ - return mPosition; -} - -osg::Vec3f Actor::getPreviousPosition() const -{ - return mPreviousPosition; -} - void Actor::setRotation(osg::Quat quat) { std::scoped_lock lock(mPositionMutex); @@ -294,16 +279,6 @@ bool Actor::skipCollisions() return std::exchange(mSkipCollisions, false); } -void Actor::setVelocity(osg::Vec3f velocity) -{ - mVelocity = velocity; -} - -osg::Vec3f Actor::velocity() -{ - return std::exchange(mVelocity, osg::Vec3f()); -} - bool Actor::canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const { const float halfZ = getHalfExtents().z(); diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index d2ebd78379..eb65928dec 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -60,7 +60,6 @@ namespace MWPhysics * to account for e.g. scripted movements */ void setSimulationPosition(const osg::Vec3f& position); - osg::Vec3f getSimulationPosition() const; void updateCollisionObjectPosition(); @@ -95,10 +94,6 @@ namespace MWPhysics // apply position offset. Can't be called during simulation void applyOffsetChange(); - osg::Vec3f getPosition() const; - - osg::Vec3f getPreviousPosition() const; - /** * Returns the half extents of the collision body (scaled according to rendering scale) * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, @@ -163,9 +158,6 @@ namespace MWPhysics bool skipCollisions(); - void setVelocity(osg::Vec3f velocity); - osg::Vec3f velocity(); - bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const; private: @@ -193,11 +185,7 @@ namespace MWPhysics osg::Quat mRotation; osg::Vec3f mScale; - osg::Vec3f mSimulationPosition; - osg::Vec3f mPosition; - osg::Vec3f mPreviousPosition; osg::Vec3f mPositionOffset; - osg::Vec3f mVelocity; bool mWorldPositionChanged; bool mSkipCollisions; bool mSkipSimulation; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index ad11878369..d3f8bad238 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -546,7 +546,7 @@ namespace MWPhysics } else if (const auto projectile = std::dynamic_pointer_cast(ptr)) { - projectile->commitPositionChange(); + projectile->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); } } diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 4efb245149..9f8962d5e6 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -31,14 +31,15 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setUserPointer(this); - setPosition(position); + mPosition = position; + mPreviousPosition = position; setCaster(caster); const int collisionMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); - commitPositionChange(); + updateCollisionObjectPosition(); } Projectile::~Projectile() @@ -48,29 +49,12 @@ Projectile::~Projectile() mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } -void Projectile::commitPositionChange() -{ - std::scoped_lock lock(mMutex); - if (mTransformUpdatePending) - { - auto& trans = mCollisionObject->getWorldTransform(); - trans.setOrigin(Misc::Convert::toBullet(mPosition)); - mCollisionObject->setWorldTransform(trans); - mTransformUpdatePending = false; - } -} - -void Projectile::setPosition(const osg::Vec3f &position) -{ - std::scoped_lock lock(mMutex); - mPosition = position; - mTransformUpdatePending = true; -} - -osg::Vec3f Projectile::getPosition() const +void Projectile::updateCollisionObjectPosition() { std::scoped_lock lock(mMutex); - return mPosition; + auto& trans = mCollisionObject->getWorldTransform(); + trans.setOrigin(Misc::Convert::toBullet(mPosition)); + mCollisionObject->setWorldTransform(trans); } void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal) diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 5e4e487c03..10ed2c9582 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -36,10 +36,7 @@ namespace MWPhysics btConvexShape* getConvexShape() const { return mConvexShape; } - void commitPositionChange(); - - void setPosition(const osg::Vec3f& position); - osg::Vec3f getPosition() const; + void updateCollisionObjectPosition(); bool isActive() const { @@ -80,13 +77,11 @@ namespace MWPhysics std::unique_ptr mShape; btConvexShape* mConvexShape; - bool mTransformUpdatePending; bool mHitWater; std::atomic mActive; MWWorld::Ptr mCaster; const btCollisionObject* mCasterColObj; const btCollisionObject* mHitTarget; - osg::Vec3f mPosition; btVector3 mHitPosition; btVector3 mHitNormal; diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index e84f3d1cfe..fcd6ce203a 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -30,9 +30,49 @@ namespace MWPhysics return mCollisionObject.get(); } + void setVelocity(osg::Vec3f velocity) + { + mVelocity = velocity; + } + + osg::Vec3f velocity() + { + return std::exchange(mVelocity, osg::Vec3f()); + } + + void setSimulationPosition(const osg::Vec3f& position) + { + mSimulationPosition = position; + } + + osg::Vec3f getSimulationPosition() const + { + return mSimulationPosition; + } + + void setPosition(const osg::Vec3f& position) + { + mPreviousPosition = mPosition; + mPosition = position; + } + + osg::Vec3f getPosition() const + { + return mPosition; + } + + osg::Vec3f getPreviousPosition() const + { + return mPreviousPosition; + } + protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; + osg::Vec3f mVelocity; + osg::Vec3f mSimulationPosition; + osg::Vec3f mPosition; + osg::Vec3f mPreviousPosition; }; } From a245981e4ec1975731838b2b0663ccead30b79c4 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 9 Oct 2021 18:15:45 +0200 Subject: [PATCH 1558/2859] Make interpolateMovement works with PtrHolder instead of Actor --- apps/openmw/mwphysics/mtphysics.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index d3f8bad238..525647230f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -105,10 +105,10 @@ namespace return actorData.mPosition.z() < actorData.mSwimLevel; } - osg::Vec3f interpolateMovements(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) + osg::Vec3f interpolateMovements(const MWPhysics::PtrHolder& ptr, float timeAccum, float physicsDt) { const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); - return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor); + return ptr.getPosition() * interpolationFactor + ptr.getPreviousPosition() * (1.f - interpolationFactor); } namespace Visitors @@ -190,7 +190,7 @@ namespace else if (heightDiff < 0) stats.addToFallHeight(-heightDiff); - actor->setSimulationPosition(::interpolateMovements(*actor, frameData, mTimeAccum, mPhysicsDt)); + actor->setSimulationPosition(::interpolateMovements(*actor, mTimeAccum, mPhysicsDt)); actor->setLastStuckPosition(frameData.mLastStuckPosition); actor->setStuckFrames(frameData.mStuckFrames); if (mAdvanceSimulation) From 3750eb9cd85ca3cbaaa150bda8159d653833bf1d Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 9 Oct 2021 18:16:29 +0200 Subject: [PATCH 1559/2859] Move Projectile simulation to the background thread. --- apps/openmw/mwphysics/movementsolver.cpp | 26 ++++++++++++++ apps/openmw/mwphysics/movementsolver.hpp | 2 ++ apps/openmw/mwphysics/mtphysics.cpp | 22 ++++++++++++ apps/openmw/mwphysics/physicssystem.cpp | 44 +++++++++-------------- apps/openmw/mwphysics/physicssystem.hpp | 14 ++++++-- apps/openmw/mwworld/projectilemanager.cpp | 12 +++---- 6 files changed, 82 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 29810e9085..0585fe91f3 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,8 @@ #include "constants.hpp" #include "contacttestwrapper.h" #include "physicssystem.hpp" +#include "projectile.hpp" +#include "projectileconvexcallback.hpp" #include "stepper.hpp" #include "trace.h" @@ -398,6 +401,29 @@ namespace MWPhysics actor.mPosition.z() -= actor.mHalfExtentsZ; // vanilla-accurate } + void MovementSolver::move(ProjectileFrameData& projectile, float time, const btCollisionWorld* collisionWorld) + { + btVector3 btFrom = Misc::Convert::toBullet(projectile.mPosition); + btVector3 btTo = Misc::Convert::toBullet(projectile.mPosition + projectile.mMovement * time); + + if (btFrom == btTo) + return; + + ProjectileConvexCallback resultCallback(projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile); + resultCallback.m_collisionFilterMask = 0xff; + resultCallback.m_collisionFilterGroup = CollisionType_Projectile; + + const btQuaternion btrot = btQuaternion::getIdentity(); + btTransform from_ (btrot, btFrom); + btTransform to_ (btrot, btTo); + + const btCollisionShape* shape = projectile.mCollisionObject->getCollisionShape(); + assert(shape->isConvex()); + collisionWorld->convexSweepTest(static_cast(shape), from_, to_, resultCallback); + + projectile.mPosition = Misc::Convert::toOsg(projectile.mProjectile->isActive() ? btTo : resultCallback.m_hitPointWorld); + } + btVector3 addMarginToDelta(btVector3 delta) { if(delta.length2() == 0.0) diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 837004f232..1bbe76cbec 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -37,6 +37,7 @@ namespace MWPhysics class Actor; struct ActorFrameData; + struct ProjectileFrameData; struct WorldFrameData; class MovementSolver @@ -44,6 +45,7 @@ namespace MWPhysics public: static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight); static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, const WorldFrameData& worldData); + static void move(ProjectileFrameData& projectile, float time, const btCollisionWorld* collisionWorld); static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld); }; } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 525647230f..a989493f4d 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -133,6 +133,9 @@ namespace frameData.mStuckFrames = actor->getStuckFrames(); frameData.mLastStuckPosition = actor->getLastStuckPosition(); } + void operator()(MWPhysics::ProjectileSimulation& sim) const + { + } }; struct PreStep @@ -142,6 +145,9 @@ namespace { MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld); } + void operator()(MWPhysics::ProjectileSimulation& sim) const + { + } }; struct UpdatePosition @@ -157,6 +163,13 @@ namespace mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } } + void operator()(MWPhysics::ProjectileSimulation& sim) const + { + auto& [proj, frameData] = sim; + proj->setPosition(frameData.mPosition); + proj->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(proj->getCollisionObject()); + } }; struct Move @@ -168,6 +181,10 @@ namespace { MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData); } + void operator()(MWPhysics::ProjectileSimulation& sim) const + { + MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld); + } }; struct Sync @@ -208,6 +225,11 @@ namespace actor->setInertialForce(frameData.mInertia); } } + void operator()(MWPhysics::ProjectileSimulation& sim) const + { + auto& [proj, frameData] = sim; + proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt)); + } }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f6fc3902e7..786378eb8c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -580,33 +580,6 @@ namespace MWPhysics } } - void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const - { - const auto foundProjectile = mProjectiles.find(projectileId); - assert(foundProjectile != mProjectiles.end()); - auto* projectile = foundProjectile->second.get(); - - btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition()); - btVector3 btTo = Misc::Convert::toBullet(position); - - if (btFrom == btTo) - return; - - ProjectileConvexCallback resultCallback(projectile->getCasterCollisionObject(), projectile->getCollisionObject(), btFrom, btTo, projectile); - resultCallback.m_collisionFilterMask = 0xff; - resultCallback.m_collisionFilterGroup = CollisionType_Projectile; - - const btQuaternion btrot = btQuaternion::getIdentity(); - btTransform from_ (btrot, btFrom); - btTransform to_ (btrot, btTo); - - mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); - - const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(projectile->getHitPosition()); - projectile->setPosition(newpos); - mTaskScheduler->updateSingleAabb(foundProjectile->second); - } - void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) @@ -718,7 +691,7 @@ namespace MWPhysics std::vector PhysicsSystem::prepareSimulation(bool willSimulate) { std::vector simulations; - simulations.reserve(mActors.size()); + simulations.reserve(mActors.size() + mProjectiles.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [ref, physicActor] : mActors) { @@ -753,6 +726,12 @@ namespace MWPhysics if (willSimulate) handleJump(ptr); } + + for (const auto& [id, projectile] : mProjectiles) + { + simulations.emplace_back(ProjectileSimulation{projectile, ProjectileFrameData{*projectile}}); + } + return simulations; } @@ -981,6 +960,15 @@ namespace MWPhysics mLastStuckPosition = actor.getLastStuckPosition(); } + ProjectileFrameData::ProjectileFrameData(Projectile& projectile) + : mPosition(projectile.getPosition()) + , mMovement(projectile.velocity()) + , mCaster(projectile.getCasterCollisionObject()) + , mCollisionObject(projectile.getCollisionObject()) + , mProjectile(&projectile) + { + } + WorldFrameData::WorldFrameData() : mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm()) , mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection()) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index be9fa10aa6..a025f1cc8b 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -101,6 +101,16 @@ namespace MWPhysics const bool mSkipCollisionDetection; }; + struct ProjectileFrameData + { + explicit ProjectileFrameData(Projectile& projectile); + osg::Vec3f mPosition; + osg::Vec3f mMovement; + const btCollisionObject* mCaster; + const btCollisionObject* mCollisionObject; + Projectile* mProjectile; + }; + struct WorldFrameData { WorldFrameData(); @@ -109,7 +119,8 @@ namespace MWPhysics }; using ActorSimulation = std::pair, ActorFrameData>; - using Simulation = std::variant; + using ProjectileSimulation = std::pair, ProjectileFrameData>; + using Simulation = std::variant; class PhysicsSystem : public RayCastingInterface { @@ -128,7 +139,6 @@ namespace MWPhysics int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); void setCaster(int projectileId, const MWWorld::Ptr& caster); - void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 9ee137fab6..ea1d6bde1a 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -432,7 +432,7 @@ namespace MWWorld float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); - osg::Vec3f newPos = projectile->getPosition() + direction * duration * speed; + projectile->setVelocity(direction * speed); update(magicBoltState, duration); @@ -441,8 +441,6 @@ namespace MWWorld if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - - mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); } } @@ -460,7 +458,7 @@ namespace MWWorld // simulating aerodynamics at all projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration; + projectile->setVelocity(projectileState.mVelocity); // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. if (!projectileState.mThrown) @@ -479,8 +477,6 @@ namespace MWWorld if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); projectile->setValidTargets(targetActors); - - mPhysics->updateProjectile(projectileState.mProjectileId, newPos); } } @@ -493,7 +489,7 @@ namespace MWWorld auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); - const auto pos = projectile->getPosition(); + const auto pos = projectile->getSimulationPosition(); projectileState.mNode->setPosition(pos); if (projectile->isActive()) @@ -529,7 +525,7 @@ namespace MWWorld auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); - const auto pos = projectile->getPosition(); + const auto pos = projectile->getSimulationPosition(); magicBoltState.mNode->setPosition(pos); for (const auto& sound : magicBoltState.mSounds) sound->setPosition(pos); From 1a51c6eb5dfe4e4a95fa9ec99a9a16ce9f0b6bd3 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 27 Oct 2021 23:40:54 +0200 Subject: [PATCH 1560/2859] Update CMakeLists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 13196a4aa6..49f3f4b3bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -707,7 +707,7 @@ endif() if (BUILD_OPENMW AND APPLE) # Without these flags LuaJit crashes on startup on OSX set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") - set_target_properties(components openmw PROPERTIES COMPILE_DEFINITIONS "GL_SILENCE_DEPRECATION=1") + add_compile_definitions(GL_SILENCE_DEPRECATION=1) endif() # Apple bundling From fa5581942eb5019f84b866f038741d4ff9b692ba Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 27 Oct 2021 23:54:04 +0200 Subject: [PATCH 1561/2859] Update CMakeLists.txt --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49f3f4b3bf..1b4e5609ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -707,7 +707,8 @@ endif() if (BUILD_OPENMW AND APPLE) # Without these flags LuaJit crashes on startup on OSX set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") - add_compile_definitions(GL_SILENCE_DEPRECATION=1) + target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1) + target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1) endif() # Apple bundling From 73e6df1fa3ebf68370151e31427623b861b76d22 Mon Sep 17 00:00:00 2001 From: wareya Date: Thu, 28 Oct 2021 11:51:22 +0000 Subject: [PATCH 1562/2859] Make random displacement more random and double raindrop density in each axis (4x the raindrops) --- files/shaders/water_fragment.glsl | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 929bdd603d..b5d47437c2 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -55,11 +55,19 @@ const float WOBBLY_SHORE_FADE_DISTANCE = 6200.0; // fade out wobbly shores to // ---------------- rain ripples related stuff --------------------- -const float RAIN_RIPPLE_GAPS = 5.0; -const float RAIN_RIPPLE_RADIUS = 0.1; +const float RAIN_RIPPLE_GAPS = 10.0; +const float RAIN_RIPPLE_RADIUS = 0.2; -vec2 randOffset(vec2 c) +float scramble(float x, float z) { + return mod(pow(x*3.0+1.0, z), 1.0); +} + +vec2 randOffset(vec2 c, float time) +{ + time = mod(time/1000.0, 1.0); + c.x *= scramble(scramble(time + c.x/1000.0, 4.0), 3.0) + 1.0; + c.y *= scramble(scramble(time + c.y/1000.0, 3.5), 3.0) + 1.0; return fract(vec2( c.x * c.y / 8.0 + c.y * 0.3 + c.x * 0.2, c.x * c.y / 14.0 + c.y * 0.5 + c.x * 0.7)); @@ -85,8 +93,7 @@ float blipDerivative(float x) vec2 randomize_center(vec2 i_part, float time) { - time = 0.5 + mod(time/1000.0, 1.0)*0.5; - vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(i_part*time) - 1.0); + vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(i_part, time) - 1.0); return center; } @@ -128,7 +135,7 @@ vec4 rain(vec2 uv, float time) ret.w = a.w + c.w/8.0; return ret; } -vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and ripple energy in w +vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake specularity in w { return rain(uv,time) + From 9b565c4cf9413af744fb4224cd66e9ab8a3ad749 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Oct 2021 22:16:43 +0200 Subject: [PATCH 1563/2859] Remove dead code --- apps/opencs/model/world/scriptcontext.cpp | 5 ----- apps/opencs/model/world/scriptcontext.hpp | 3 --- apps/openmw/mwscript/compilercontext.cpp | 10 ---------- apps/openmw/mwscript/compilercontext.hpp | 3 --- components/compiler/context.hpp | 3 --- 5 files changed, 24 deletions(-) diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index 344ae322e9..9b465d7ffb 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -102,11 +102,6 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const return std::binary_search (mIds.begin(), mIds.end(), Misc::StringUtils::lowerCase (name)); } -bool CSMWorld::ScriptContext::isJournalId (const std::string& name) const -{ - return mData.getJournals().searchId (name)!=-1; -} - void CSMWorld::ScriptContext::invalidateIds() { mIdsUpdated = false; diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp index 8e1a5e57b8..cb08fc70bd 100644 --- a/apps/opencs/model/world/scriptcontext.hpp +++ b/apps/opencs/model/world/scriptcontext.hpp @@ -39,9 +39,6 @@ namespace CSMWorld bool isId (const std::string& name) const override; ///< Does \a name match an ID, that can be referenced? - bool isJournalId (const std::string& name) const override; - ///< Does \a name match a journal ID? - void invalidateIds(); void clear(); diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 4a7038e1cb..983365e06a 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -86,14 +86,4 @@ namespace MWScript store.get().search (name) || store.get().search (name); } - - bool CompilerContext::isJournalId (const std::string& name) const - { - const MWWorld::ESMStore &store = - MWBase::Environment::get().getWorld()->getStore(); - - const ESM::Dialogue *topic = store.get().search (name); - - return topic && topic->mType==ESM::Dialogue::Journal; - } } diff --git a/apps/openmw/mwscript/compilercontext.hpp b/apps/openmw/mwscript/compilercontext.hpp index 00b10ea06d..d800781fd8 100644 --- a/apps/openmw/mwscript/compilercontext.hpp +++ b/apps/openmw/mwscript/compilercontext.hpp @@ -39,9 +39,6 @@ namespace MWScript bool isId (const std::string& name) const override; ///< Does \a name match an ID, that can be referenced? - - bool isJournalId (const std::string& name) const override; - ///< Does \a name match a journal ID? }; } diff --git a/components/compiler/context.hpp b/components/compiler/context.hpp index 399e8125bb..d3caba7c53 100644 --- a/components/compiler/context.hpp +++ b/components/compiler/context.hpp @@ -42,9 +42,6 @@ namespace Compiler virtual bool isId (const std::string& name) const = 0; ///< Does \a name match an ID, that can be referenced? - - virtual bool isJournalId (const std::string& name) const = 0; - ///< Does \a name match a journal ID? }; } From 37386f417ed481bc682ffa11359b073dc334f6d3 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 29 Oct 2021 20:09:47 +0200 Subject: [PATCH 1564/2859] Support *.omwscripts in openmw-launcher --- apps/launcher/datafilespage.cpp | 2 +- apps/launcher/utils/cellnameloader.cpp | 2 ++ apps/opencs/view/doc/filedialog.cpp | 2 +- components/contentselector/model/contentmodel.cpp | 14 +++++++++++++- components/contentselector/model/contentmodel.hpp | 3 ++- .../contentselector/view/contentselector.cpp | 8 ++++---- .../contentselector/view/contentselector.hpp | 4 ++-- 7 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 24729d5096..4c66668e4a 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -32,7 +32,7 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: { ui.setupUi (this); setObjectName ("DataFilesPage"); - mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); + mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/true); const QString encoding = mGameSettings.value("encoding", "win1252"); mSelector->setEncoding(encoding); diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp index 594946394c..e8ba54651c 100644 --- a/apps/launcher/utils/cellnameloader.cpp +++ b/apps/launcher/utils/cellnameloader.cpp @@ -10,6 +10,8 @@ QSet CellNameLoader::getCellNames(QStringList &contentPaths) // Loop through all content files for (auto &contentPath : contentPaths) { + if (contentPath.endsWith(".omwscripts", Qt::CaseInsensitive)) + continue; esmReader.open(contentPath.toStdString()); // Loop through all records diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 8ff063ed31..c3d0d8cc50 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -24,7 +24,7 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : resize(400, 400); setObjectName ("FileDialog"); - mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); + mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget, /*showOMWScripts=*/false); mAdjusterWidget = new AdjusterWidget (this); } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 690f968142..208b1315f3 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -9,9 +9,10 @@ #include -ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon) : +ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon, bool showOMWScripts) : QAbstractTableModel(parent), mWarningIcon(warningIcon), + mShowOMWScripts(showOMWScripts), mMimeType ("application/omwcontent"), mMimeTypes (QStringList() << mMimeType), mColumnCount (1), @@ -416,6 +417,8 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) QDir dir(path); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; + if (mShowOMWScripts) + filters << "*.omwscripts"; dir.setNameFilters(filters); for (const QString &path2 : dir.entryList()) @@ -425,6 +428,15 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) if (item(info.fileName())) continue; + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) + { + EsmFile *file = new EsmFile(path2); + file->setDate(info.lastModified()); + file->setFilePath(info.absoluteFilePath()); + addFile(file); + continue; + } + try { ESM::ESMReader fileReader; ToUTF8::Utf8Encoder encoder = diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index d245a0dcbf..f8130e3649 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -23,7 +23,7 @@ namespace ContentSelectorModel { Q_OBJECT public: - explicit ContentModel(QObject *parent, QIcon warningIcon); + explicit ContentModel(QObject *parent, QIcon warningIcon, bool showOMWScripts); ~ContentModel(); void setEncoding(const QString &encoding); @@ -84,6 +84,7 @@ namespace ContentSelectorModel QSet mPluginsWithLoadOrderError; QString mEncoding; QIcon mWarningIcon; + bool mShowOMWScripts; public: diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index d7996dfae3..f18e80dd0a 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -10,21 +10,21 @@ #include #include -ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : +ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, bool showOMWScripts) : QObject(parent) { ui.setupUi(parent); ui.addonView->setDragDropMode(QAbstractItemView::InternalMove); - buildContentModel(); + buildContentModel(showOMWScripts); buildGameFileView(); buildAddonView(); } -void ContentSelectorView::ContentSelector::buildContentModel() +void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts) { QIcon warningIcon(ui.addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); - mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon); + mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts); } void ContentSelectorView::ContentSelector::buildGameFileView() diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index cda68fa1b7..4a9983c1bf 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -23,7 +23,7 @@ namespace ContentSelectorView public: - explicit ContentSelector(QWidget *parent = nullptr); + explicit ContentSelector(QWidget *parent = nullptr, bool showOMWScripts = false); QString currentFile() const; @@ -56,7 +56,7 @@ namespace ContentSelectorView Ui::ContentSelector ui; - void buildContentModel(); + void buildContentModel(bool showOMWScripts); void buildGameFileView(); void buildAddonView(); void buildContextMenu(); From 29a772c33fcc3de36aaa1d0d9517ab1e38010a8b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 28 Oct 2021 01:43:25 +0200 Subject: [PATCH 1565/2859] Rename Resource::BulletShape::CollisionBox fields according to styleguide --- apps/openmw/mwphysics/actor.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 4 +-- .../nifloader/testbulletnifloader.cpp | 36 +++++++++---------- components/nifbullet/bulletnifloader.cpp | 8 ++--- components/resource/bulletshape.hpp | 4 +-- components/resource/bulletshapemanager.cpp | 8 ++--- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 4857339228..9d02d310b0 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -22,7 +22,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) - , mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents) + , mMeshTranslation(shape->mCollisionBox.mCenter), mOriginalHalfExtents(shape->mCollisionBox.mExtents) , mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5b00677fce..636c3492f7 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -618,7 +618,7 @@ namespace MWPhysics osg::ref_ptr shape = mShapeManager->getShape(mesh); // Try to get shape from basic model as fallback for creatures - if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.extents.length2() == 0) + if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.mExtents.length2() == 0) { const std::string fallbackModel = ptr.getClass().getModel(ptr); if (fallbackModel != mesh) @@ -643,7 +643,7 @@ namespace MWPhysics { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); assert(shapeInstance); - float radius = computeRadius ? shapeInstance->mCollisionBox.extents.length() / 2.f : 1.f; + float radius = computeRadius ? shapeInstance->mCollisionBox.mExtents.length() / 2.f : 1.f; mProjectileId++; diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index a1f5d6091d..d0a5b3d2e5 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -162,8 +162,8 @@ namespace Resource { return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) && compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape) - && lhs.mCollisionBox.extents == rhs.mCollisionBox.extents - && lhs.mCollisionBox.center == rhs.mCollisionBox.center + && lhs.mCollisionBox.mExtents == rhs.mCollisionBox.mExtents + && lhs.mCollisionBox.mCenter == rhs.mCollisionBox.mCenter && lhs.mAnimatedShapes == rhs.mAnimatedShapes; } @@ -172,8 +172,8 @@ namespace Resource return stream << "Resource::BulletShape {" << value.mCollisionShape << ", " << value.mAvoidCollisionShape << ", " - << "osg::Vec3f {" << value.mCollisionBox.extents << "}" << ", " - << "osg::Vec3f {" << value.mCollisionBox.center << "}" << ", " + << "osg::Vec3f {" << value.mCollisionBox.mExtents << "}" << ", " + << "osg::Vec3f {" << value.mCollisionBox.mCenter << "}" << ", " << value.mAnimatedShapes << "}"; } @@ -441,8 +441,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); - expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -466,8 +466,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); - expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -496,8 +496,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); - expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -531,8 +531,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); - expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -566,8 +566,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBox.extents = osg::Vec3f(4, 5, 6); - expected.mCollisionBox.center = osg::Vec3f(-4, -5, -6); + expected.mCollisionBox.mExtents = osg::Vec3f(4, 5, 6); + expected.mCollisionBox.mCenter = osg::Vec3f(-4, -5, -6); std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); @@ -589,8 +589,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); - expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } @@ -623,8 +623,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); - expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 6ae1759395..03ef63014b 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -143,8 +143,8 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { if (findBoundingBox(node, filename)) { - const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.extents); - const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.center); + const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents); + const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter); std::unique_ptr compound (new btCompoundShape); std::unique_ptr boxShape(new btBoxShape(extents)); btTransform transform = btTransform::getIdentity(); @@ -206,8 +206,8 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& switch (type) { case Nif::NiBoundingVolume::Type::BOX_BV: - mShape->mCollisionBox.extents = node->bounds.box.extents; - mShape->mCollisionBox.center = node->bounds.box.center; + mShape->mCollisionBox.mExtents = node->bounds.box.extents; + mShape->mCollisionBox.mCenter = node->bounds.box.center; break; default: { diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 6ac8064cb3..b40ab6b6a7 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -29,8 +29,8 @@ namespace Resource struct CollisionBox { - osg::Vec3f extents; - osg::Vec3f center; + osg::Vec3f mExtents; + osg::Vec3f mCenter; }; // Used for actors and projectiles. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures. // For now, use one file <-> one resource for simplicity. diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 5b6dce067c..d295265b5f 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -86,10 +86,10 @@ public: btBvhTriangleMeshShape* triangleMeshShape = new TriangleMeshShape(mTriangleMesh.release(), true); btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); - shape->mCollisionBox.extents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; - shape->mCollisionBox.extents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; - shape->mCollisionBox.extents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; - shape->mCollisionBox.center = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, + shape->mCollisionBox.mExtents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; + shape->mCollisionBox.mExtents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; + shape->mCollisionBox.mExtents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; + shape->mCollisionBox.mCenter = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, (aabbMax[1] + aabbMin[1]) / 2.0f, (aabbMax[2] + aabbMin[2]) / 2.0f ); shape->mCollisionShape = triangleMeshShape; From 8c0102e1eeb1679f0a55df7683e3145f534fc27f Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 29 Oct 2021 15:11:08 -0400 Subject: [PATCH 1566/2859] Optimize short-trace test to only be done when particularly helpful --- apps/openmw/mwphysics/movementsolver.cpp | 4 +- apps/openmw/mwphysics/stepper.cpp | 4 +- apps/openmw/mwphysics/trace.cpp | 50 +++++++++++++++--------- apps/openmw/mwphysics/trace.h | 2 +- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 44a5391f0d..28570e6687 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -203,7 +203,7 @@ namespace MWPhysics if((newPosition - nextpos).length2() > 0.0001) { // trace to where character would go if there were no obstructions - tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); // check for obstructions if(tracer.mFraction >= 1.0f) @@ -338,7 +338,7 @@ namespace MWPhysics osg::Vec3f from = newPosition; auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); - tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld); + tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld, actor.mIsOnGround); if(tracer.mFraction < 1.0f) { if (!isActor(tracer.mHitObject)) diff --git a/apps/openmw/mwphysics/stepper.cpp b/apps/openmw/mwphysics/stepper.cpp index af85658910..5ef6833701 100644 --- a/apps/openmw/mwphysics/stepper.cpp +++ b/apps/openmw/mwphysics/stepper.cpp @@ -36,7 +36,7 @@ namespace MWPhysics // Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the ground. // This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and just prevent stepping on insane geometry. - mUpStepper.doTrace(mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld); + mUpStepper.doTrace(mColObj, position, position + osg::Vec3f(0.0f, 0.0f, Constants::sStepSizeUp), mColWorld, onGround); float upDistance = 0; if(!mUpStepper.mHitObject) @@ -117,7 +117,7 @@ namespace MWPhysics downStepSize = upDistance; else downStepSize = moveDistance + upDistance + sStepSizeDown; - mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld); + mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld, onGround); // can't step down onto air, non-walkable-slopes, or actors // NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were intended to be valid at the bottoms of stairs diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index b2c5410a62..e489ea0eb2 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -24,45 +24,57 @@ ActorConvexCallback sweepHelper(const btCollisionObject *actor, const btVector3& assert(shape->isConvex()); const btVector3 motion = from - to; // FIXME: this is backwards; means ActorConvexCallback is doing dot product tests backwards too - ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world); + ActorConvexCallback traceCallback(actor, motion, btScalar(0.0), world); // Inherit the actor's collision group and mask - newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; - newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; + traceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; + traceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; if(actorFilter) - newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor; + traceCallback.m_collisionFilterMask &= ~CollisionType_Actor; - world->convexSweepTest(static_cast(shape), transFrom, transTo, newTraceCallback); - return newTraceCallback; + world->convexSweepTest(static_cast(shape), transFrom, transTo, traceCallback); + return traceCallback; } -void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) +void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world, bool attempt_short_trace) { const btVector3 btstart = Misc::Convert::toBullet(start); btVector3 btend = Misc::Convert::toBullet(end); - bool do_fallback = false; - if((btend-btstart).length2() > 5.0*5.0) + // Because Bullet's collision trace tests touch *all* geometry in its path, a lot of long collision tests + // will unnecessarily test against complex meshes that are dozens of units away. This wouldn't normally be + // a problem, but bullet isn't the fastest in the world when it comes to doing tests against triangle meshes. + // Therefore, we try out a short trace first, then only fall back to the full length trace if needed. + // This trace needs to be at least a couple units long, but there's no one particular ideal length. + // The length of 2.1 chosen here is a "works well in practice after testing a few random lengths" value. + // (Also, we only do this short test if the intended collision trace is long enough for it to make sense.) + const float fallback_length = 2.1f; + bool doing_short_trace = false; + // For some reason, typical scenes perform a little better if we increase the threshold length for the length test. + // (Multiplying by 2 in 'square distance' units gives us about 1.4x the threshold length. In benchmarks this was + // slightly better for the performance of normal scenes than 4.0, and just plain better than 1.0.) + if(attempt_short_trace && (btend-btstart).length2() > fallback_length*fallback_length*2.0) { - btend = btstart + (btend-btstart).normalized()*5.0; - do_fallback = true; + btend = btstart + (btend-btstart).normalized()*fallback_length; + doing_short_trace = true; } - auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); + auto traceCallback = sweepHelper(actor, btstart, btend, world, false); // Copy the hit data over to our trace results struct: - if(newTraceCallback.hasHit()) + if(traceCallback.hasHit()) { - mFraction = newTraceCallback.m_closestHitFraction; - if((end-start).length2() > 0.0) + mFraction = traceCallback.m_closestHitFraction; + // ensure fraction is correct (covers intended distance traveled instead of actual distance traveled) + if(doing_short_trace && (end-start).length2() > 0.0) mFraction *= (btend-btstart).length() / (end-start).length(); - mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); + mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld); mEndPos = (end-start)*mFraction + start; - mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); - mHitObject = newTraceCallback.m_hitCollisionObject; + mHitPoint = Misc::Convert::toOsg(traceCallback.m_hitPointWorld); + mHitObject = traceCallback.m_hitCollisionObject; } else { - if(do_fallback) + if(doing_short_trace) { btend = Misc::Convert::toBullet(end); auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); diff --git a/apps/openmw/mwphysics/trace.h b/apps/openmw/mwphysics/trace.h index 0297c9e076..af38756b3e 100644 --- a/apps/openmw/mwphysics/trace.h +++ b/apps/openmw/mwphysics/trace.h @@ -20,7 +20,7 @@ namespace MWPhysics float mFraction; - void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); + void doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world, bool attempt_short_trace = false); void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world); }; } From 68f4c336ce5db928efb7e09ee3c988a3eaba45f1 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 19 Oct 2021 18:36:30 +0200 Subject: [PATCH 1567/2859] Rework again SetPos command to make more mods work. Previous version skipped collision the frame immediately after a call to SetPos. It worked for one-off calls (teleports for instance) and continuous call along a pre-defined path (scenic travel). However, in the case of mod which uses SetPos to simulate a player-controlled movement, it is equivalent to using tcl. Solution: 1/ skip update of mPosition and mPreviousPosition to avoid janky interpolation 2/ use back plain moveObject() instead of moveObjectBy() since we don't want physics simulation 3/ rework a little bit waterwalking influence on coordinate because of 1/ --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwphysics/actor.cpp | 18 +++++++----------- apps/openmw/mwphysics/actor.hpp | 5 +---- apps/openmw/mwphysics/physicssystem.cpp | 7 ++++--- .../mwscript/transformationextensions.cpp | 8 ++++---- apps/openmw/mwworld/worldimp.cpp | 6 +++--- apps/openmw/mwworld/worldimp.hpp | 2 +- 7 files changed, 21 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8b33095fde..c1a8d09120 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -289,7 +289,7 @@ namespace MWBase virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) = 0; ///< @return an updated Ptr - virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) = 0; + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 4857339228..4e87f5384f 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -124,7 +124,6 @@ void Actor::updatePosition() mSimulationPosition = worldPosition; mPositionOffset = osg::Vec3f(); mStandingOnPtr = nullptr; - mSkipCollisions = true; mSkipSimulation = true; } @@ -163,17 +162,19 @@ bool Actor::setPosition(const osg::Vec3f& position) { std::scoped_lock lock(mPositionMutex); applyOffsetChange(); - bool hasChanged = mPosition != position || mWorldPositionChanged; - mPreviousPosition = mPosition; - mPosition = position; + bool hasChanged = (mPosition != position && !mSkipSimulation) || mWorldPositionChanged; + if (!mSkipSimulation) + { + mPreviousPosition = mPosition; + mPosition = position; + } return hasChanged; } -void Actor::adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions) +void Actor::adjustPosition(const osg::Vec3f& offset) { std::scoped_lock lock(mPositionMutex); mPositionOffset += offset; - mSkipCollisions = mSkipCollisions || ignoreCollisions; } void Actor::applyOffsetChange() @@ -274,11 +275,6 @@ void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr) mStandingOnPtr = ptr; } -bool Actor::skipCollisions() -{ - return std::exchange(mSkipCollisions, false); -} - bool Actor::canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const { const float halfZ = getHalfExtents().z(); diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index eb65928dec..01d8037f6b 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -89,7 +89,7 @@ namespace MWPhysics void updatePosition(); // register a position offset that will be applied during simulation. - void adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions); + void adjustPosition(const osg::Vec3f& offset); // apply position offset. Can't be called during simulation void applyOffsetChange(); @@ -156,8 +156,6 @@ namespace MWPhysics mLastStuckPosition = position; } - bool skipCollisions(); - bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const; private: @@ -187,7 +185,6 @@ namespace MWPhysics osg::Vec3f mScale; osg::Vec3f mPositionOffset; bool mWorldPositionChanged; - bool mSkipCollisions; bool mSkipSimulation; mutable std::mutex mPositionMutex; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5b00677fce..af6dc109ed 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -941,7 +941,7 @@ namespace MWPhysics , mWasOnGround(actor.getOnGround()) , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) , mWaterCollision(waterCollision) - , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) + , mSkipCollisionDetection(!actor.getCollisionMode()) { } @@ -951,8 +951,9 @@ namespace MWPhysics mPosition = actor.getPosition(); if (mWaterCollision && mPosition.z() < mWaterlevel && actor.canMoveToWaterSurface(mWaterlevel, world)) { - mPosition.z() = mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(actor.getPtr(), mPosition, false); + MWBase::Environment::get().getWorld()->moveObjectBy(actor.getPtr(), osg::Vec3f(0, 0, mWaterlevel - mPosition.z())); + actor.applyOffsetChange(); + mPosition = actor.getPosition(); } mOldHeight = mPosition.z(); const auto rotation = actor.getPtr().getRefData().getPosition().asRotationVec3(); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 3d00b24ef8..5cfb2b2989 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,7 +32,7 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false, false); + MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff); } template @@ -312,7 +312,7 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true, true)); + MWBase::Environment::get().getWorld()->moveObject(ptr, newPos, true, true)); } }; @@ -731,7 +731,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false, true)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); } }; @@ -767,7 +767,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false, true)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 21e2d8380a..e860a114c6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1258,14 +1258,14 @@ namespace MWWorld return moveObject(ptr, cell, position, movePhysics); } - MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) + MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec) { auto* actor = mPhysics->getActor(ptr); osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; if (actor) - actor->adjustPosition(vec, ignoreCollisions); + actor->adjustPosition(vec); if (ptr.getClass().isActor()) - return moveObject(ptr, newpos, false, moveToActive && ptr != getPlayerPtr()); + return moveObject(ptr, newpos, false, ptr != getPlayerPtr()); return moveObject(ptr, newpos); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6e48f045c0..a795b4eafd 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -376,7 +376,7 @@ namespace MWWorld MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) override; ///< @return an updated Ptr - MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) override; + MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec) override; ///< @return an updated Ptr void scaleObject (const Ptr& ptr, float scale) override; From ca8584f6f61ca277272000fad724506e55d7a45c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 02:58:40 +0200 Subject: [PATCH 1568/2859] Move functions independent from BulletShape into anonymous namespace --- components/resource/bulletshape.cpp | 112 +++++++++++++--------------- components/resource/bulletshape.hpp | 6 -- 2 files changed, 53 insertions(+), 65 deletions(-) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 798a6778e6..5e0415e59e 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -10,6 +10,53 @@ namespace Resource { +namespace +{ + btCollisionShape* duplicateCollisionShape(const btCollisionShape *shape) + { + if (shape == nullptr) + return nullptr; + + if (shape->isCompound()) + { + const btCompoundShape *comp = static_cast(shape); + btCompoundShape* newShape = new btCompoundShape; + + for (int i = 0, n = comp->getNumChildShapes(); i < n; ++i) + { + btCollisionShape* child = duplicateCollisionShape(comp->getChildShape(i)); + const btTransform& trans = comp->getChildTransform(i); + newShape->addChildShape(trans, child); + } + + return newShape; + } + + if (const btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) + return new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); + + if (const btBoxShape* boxshape = dynamic_cast(shape)) + return new btBoxShape(*boxshape); + + if (shape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) + return new btHeightfieldTerrainShape(static_cast(*shape)); + + throw std::logic_error(std::string("Unhandled Bullet shape duplication: ") + shape->getName()); + } + + void deleteShape(btCollisionShape* shape) + { + if (shape->isCompound()) + { + btCompoundShape* compound = static_cast(shape); + for (int i = 0, n = compound->getNumChildShapes(); i < n; i++) + if (btCollisionShape* child = compound->getChildShape(i)) + deleteShape(child); + } + + delete shape; + } +} BulletShape::BulletShape() : mCollisionShape(nullptr) @@ -28,58 +75,10 @@ BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) BulletShape::~BulletShape() { - deleteShape(mAvoidCollisionShape); - deleteShape(mCollisionShape); -} - -void BulletShape::deleteShape(btCollisionShape* shape) -{ - if(shape!=nullptr) - { - if(shape->isCompound()) - { - btCompoundShape* ms = static_cast(shape); - int a = ms->getNumChildShapes(); - for(int i=0; i getChildShape(i)); - } - delete shape; - } -} - -btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *shape) const -{ - if(shape->isCompound()) - { - const btCompoundShape *comp = static_cast(shape); - btCompoundShape *newShape = new btCompoundShape; - - int numShapes = comp->getNumChildShapes(); - for(int i = 0;i < numShapes;++i) - { - btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); - const btTransform& trans = comp->getChildTransform(i); - newShape->addChildShape(trans, child); - } - - return newShape; - } - - if(const btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) - { - btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); - return newShape; - } - - if (const btBoxShape* boxshape = dynamic_cast(shape)) - { - return new btBoxShape(*boxshape); - } - - if (shape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) - return new btHeightfieldTerrainShape(static_cast(*shape)); - - throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); + if (mAvoidCollisionShape != nullptr) + deleteShape(mAvoidCollisionShape); + if (mCollisionShape != nullptr) + deleteShape(mCollisionShape); } btCollisionShape *BulletShape::getCollisionShape() const @@ -115,14 +114,9 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) , mSource(source) { mCollisionBox = source->mCollisionBox; - mAnimatedShapes = source->mAnimatedShapes; - - if (source->mCollisionShape) - mCollisionShape = duplicateCollisionShape(source->mCollisionShape); - - if (source->mAvoidCollisionShape) - mAvoidCollisionShape = duplicateCollisionShape(source->mAvoidCollisionShape); + mCollisionShape = duplicateCollisionShape(source->mCollisionShape); + mAvoidCollisionShape = duplicateCollisionShape(source->mAvoidCollisionShape); } } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index b40ab6b6a7..8b30464fe2 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -44,8 +44,6 @@ namespace Resource osg::ref_ptr makeInstance() const; - btCollisionShape* duplicateCollisionShape(const btCollisionShape* shape) const; - btCollisionShape* getCollisionShape() const; btCollisionShape* getAvoidCollisionShape() const; @@ -53,10 +51,6 @@ namespace Resource void setLocalScaling(const btVector3& scale); bool isAnimated() const; - - private: - - void deleteShape(btCollisionShape* shape); }; From 80e3623d9a3c77d6896e9c5787a49ad45256d450 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 03:04:54 +0200 Subject: [PATCH 1569/2859] Avoid dynamic cast in duplicateCollisionShape --- components/resource/bulletshape.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 5e0415e59e..3f599e5386 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -32,11 +32,17 @@ namespace return newShape; } - if (const btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) + if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) + { + const btBvhTriangleMeshShape* trishape = static_cast(shape); return new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); + } - if (const btBoxShape* boxshape = dynamic_cast(shape)) + if (shape->getShapeType() == BOX_SHAPE_PROXYTYPE) + { + const btBoxShape* boxshape = static_cast(shape); return new btBoxShape(*boxshape); + } if (shape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) return new btHeightfieldTerrainShape(static_cast(*shape)); From b905dd17c3e27f9bee9143a27fafcfdf84e6902b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 03:06:22 +0200 Subject: [PATCH 1570/2859] Use unique_ptr to store btCollisionShape in BulletShape --- .../detournavigator/navigator.cpp | 6 +-- .../nifloader/testbulletnifloader.cpp | 42 +++++++++--------- components/nifbullet/bulletnifloader.cpp | 8 ++-- components/resource/bulletshape.cpp | 43 +++++++------------ components/resource/bulletshape.hpp | 14 ++++-- components/resource/bulletshapemanager.cpp | 2 +- 6 files changed, 55 insertions(+), 60 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 62d3f9de04..6579076778 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -117,7 +117,7 @@ namespace osg::ref_ptr makeBulletShapeInstance(std::unique_ptr&& shape) { osg::ref_ptr bulletShape(new Resource::BulletShape); - bulletShape->mCollisionShape = std::move(shape).release(); + bulletShape->mCollisionShape.reset(std::move(shape).release()); return new Resource::BulletShapeInstance(bulletShape); } @@ -466,7 +466,7 @@ namespace }}; std::unique_ptr shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); shapePtr->setLocalScaling(btVector3(128, 128, 1)); - bulletShape->mCollisionShape = shapePtr.release(); + bulletShape->mCollisionShape.reset(shapePtr.release()); std::array heightfieldDataAvoid {{ -25, -25, -25, -25, -25, @@ -477,7 +477,7 @@ namespace }}; std::unique_ptr shapeAvoidPtr = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid); shapeAvoidPtr->setLocalScaling(btVector3(128, 128, 1)); - bulletShape->mAvoidCollisionShape = shapeAvoidPtr.release(); + bulletShape->mAvoidCollisionShape.reset(shapeAvoidPtr.release()); osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index d0a5b3d2e5..3d4628c267 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -160,8 +160,8 @@ namespace Resource { static bool operator ==(const Resource::BulletShape& lhs, const Resource::BulletShape& rhs) { - return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) - && compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape) + return compareObjects(lhs.mCollisionShape.get(), rhs.mCollisionShape.get()) + && compareObjects(lhs.mAvoidCollisionShape.get(), rhs.mAvoidCollisionShape.get()) && lhs.mCollisionBox.mExtents == rhs.mCollisionBox.mExtents && lhs.mCollisionBox.mCenter == rhs.mCollisionBox.mCenter && lhs.mAnimatedShapes == rhs.mAnimatedShapes; @@ -170,8 +170,8 @@ namespace Resource static std::ostream& operator <<(std::ostream& stream, const Resource::BulletShape& value) { return stream << "Resource::BulletShape {" - << value.mCollisionShape << ", " - << value.mAvoidCollisionShape << ", " + << value.mCollisionShape.get() << ", " + << value.mAvoidCollisionShape.get() << ", " << "osg::Vec3f {" << value.mCollisionBox.mExtents << "}" << ", " << "osg::Vec3f {" << value.mCollisionBox.mCenter << "}" << ", " << value.mAnimatedShapes @@ -446,7 +446,7 @@ namespace std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -471,7 +471,7 @@ namespace std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -501,7 +501,7 @@ namespace std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -536,7 +536,7 @@ namespace std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -571,7 +571,7 @@ namespace std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -605,7 +605,7 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; - expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } @@ -641,7 +641,7 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; - expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } @@ -659,7 +659,7 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; - expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } @@ -680,7 +680,7 @@ namespace triangles->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; - expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } @@ -698,7 +698,7 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; - expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } @@ -720,7 +720,7 @@ namespace std::unique_ptr shape(new btCompoundShape); shape->addChildShape(mResultTransform, mesh.release()); Resource::BulletShape expected; - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); @@ -746,7 +746,7 @@ namespace std::unique_ptr shape(new btCompoundShape); shape->addChildShape(mResultTransform2, mesh.release()); Resource::BulletShape expected; - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); @@ -784,7 +784,7 @@ namespace shape->addChildShape(mResultTransform, mesh.release()); shape->addChildShape(mResultTransform, mesh2.release()); Resource::BulletShape expected; - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); @@ -813,7 +813,7 @@ namespace std::unique_ptr shape(new btCompoundShape); shape->addChildShape(mResultTransform2, mesh.release()); Resource::BulletShape expected; - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); @@ -854,7 +854,7 @@ namespace shape->addChildShape(mResultTransform2, mesh2.release()); shape->addChildShape(btTransform::getIdentity(), mesh.release()); Resource::BulletShape expected; - expected.mCollisionShape = shape.release(); + expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = {{-1, 0}}; EXPECT_EQ(*result, expected); @@ -873,7 +873,7 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; - expected.mAvoidCollisionShape = new Resource::TriangleMeshShape(triangles.release(), false); + expected.mAvoidCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), false)); EXPECT_EQ(*result, expected); } @@ -979,7 +979,7 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; - expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 03ef63014b..685a80951a 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -152,7 +152,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) compound->addChildShape(transform, boxShape.get()); boxShape.release(); - mShape->mCollisionShape = compound.release(); + mShape->mCollisionShape.reset(compound.release()); return mShape; } } @@ -179,17 +179,17 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) child.release(); mStaticMesh.release(); } - mShape->mCollisionShape = mCompoundShape.release(); + mShape->mCollisionShape.reset(mCompoundShape.release()); } else if (mStaticMesh) { - mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh.get(), true); + mShape->mCollisionShape.reset(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); mStaticMesh.release(); } if (mAvoidStaticMesh) { - mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false); + mShape->mAvoidCollisionShape.reset(new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false)); mAvoidStaticMesh.release(); } diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 3f599e5386..e52e68ca07 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -12,7 +12,7 @@ namespace Resource { namespace { - btCollisionShape* duplicateCollisionShape(const btCollisionShape *shape) + CollisionShapePtr duplicateCollisionShape(const btCollisionShape *shape) { if (shape == nullptr) return nullptr; @@ -20,13 +20,13 @@ namespace if (shape->isCompound()) { const btCompoundShape *comp = static_cast(shape); - btCompoundShape* newShape = new btCompoundShape; + std::unique_ptr newShape(new btCompoundShape); for (int i = 0, n = comp->getNumChildShapes(); i < n; ++i) { - btCollisionShape* child = duplicateCollisionShape(comp->getChildShape(i)); + auto child = duplicateCollisionShape(comp->getChildShape(i)); const btTransform& trans = comp->getChildTransform(i); - newShape->addChildShape(trans, child); + newShape->addChildShape(trans, child.release()); } return newShape; @@ -35,17 +35,17 @@ namespace if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) { const btBvhTriangleMeshShape* trishape = static_cast(shape); - return new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); + return CollisionShapePtr(new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f))); } if (shape->getShapeType() == BOX_SHAPE_PROXYTYPE) { const btBoxShape* boxshape = static_cast(shape); - return new btBoxShape(*boxshape); + return CollisionShapePtr(new btBoxShape(*boxshape)); } if (shape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) - return new btHeightfieldTerrainShape(static_cast(*shape)); + return CollisionShapePtr(new btHeightfieldTerrainShape(static_cast(*shape))); throw std::logic_error(std::string("Unhandled Bullet shape duplication: ") + shape->getName()); } @@ -64,37 +64,27 @@ namespace } } -BulletShape::BulletShape() - : mCollisionShape(nullptr) - , mAvoidCollisionShape(nullptr) +void DeleteCollisionShape::operator()(btCollisionShape* shape) const { - + deleteShape(shape); } BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) - : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape)) - , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape)) + : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape.get())) + , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape.get())) , mCollisionBox(copy.mCollisionBox) , mAnimatedShapes(copy.mAnimatedShapes) { } -BulletShape::~BulletShape() -{ - if (mAvoidCollisionShape != nullptr) - deleteShape(mAvoidCollisionShape); - if (mCollisionShape != nullptr) - deleteShape(mCollisionShape); -} - btCollisionShape *BulletShape::getCollisionShape() const { - return mCollisionShape; + return mCollisionShape.get(); } btCollisionShape *BulletShape::getAvoidCollisionShape() const { - return mAvoidCollisionShape; + return mAvoidCollisionShape.get(); } void BulletShape::setLocalScaling(const btVector3& scale) @@ -116,13 +106,12 @@ osg::ref_ptr BulletShape::makeInstance() const } BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) - : BulletShape() - , mSource(source) + : mSource(source) { mCollisionBox = source->mCollisionBox; mAnimatedShapes = source->mAnimatedShapes; - mCollisionShape = duplicateCollisionShape(source->mCollisionShape); - mAvoidCollisionShape = duplicateCollisionShape(source->mAvoidCollisionShape); + mCollisionShape = duplicateCollisionShape(source->mCollisionShape.get()); + mAvoidCollisionShape = duplicateCollisionShape(source->mAvoidCollisionShape.get()); } } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 8b30464fe2..369aed18a0 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #include +#include #include #include @@ -13,19 +14,24 @@ class btCollisionShape; namespace Resource { + struct DeleteCollisionShape + { + void operator()(btCollisionShape* shape) const; + }; + + using CollisionShapePtr = std::unique_ptr; class BulletShapeInstance; class BulletShape : public osg::Object { public: - BulletShape(); + BulletShape() = default; BulletShape(const BulletShape& copy, const osg::CopyOp& copyop); - virtual ~BulletShape(); META_Object(Resource, BulletShape) - btCollisionShape* mCollisionShape; - btCollisionShape* mAvoidCollisionShape; + CollisionShapePtr mCollisionShape; + CollisionShapePtr mAvoidCollisionShape; struct CollisionBox { diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index d295265b5f..cde069837a 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -92,7 +92,7 @@ public: shape->mCollisionBox.mCenter = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, (aabbMax[1] + aabbMin[1]) / 2.0f, (aabbMax[2] + aabbMin[2]) / 2.0f ); - shape->mCollisionShape = triangleMeshShape; + shape->mCollisionShape.reset(triangleMeshShape); return shape; } From fc9a405dc5897f335c5317b36296cbffa2cf57b0 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 03:16:21 +0200 Subject: [PATCH 1571/2859] Make BulletShape::makeInstance free function --- components/resource/bulletshape.cpp | 5 ++--- components/resource/bulletshape.hpp | 5 ++--- components/resource/bulletshapemanager.cpp | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index e52e68ca07..4b074ee146 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -99,10 +99,9 @@ bool BulletShape::isAnimated() const return !mAnimatedShapes.empty(); } -osg::ref_ptr BulletShape::makeInstance() const +osg::ref_ptr makeInstance(osg::ref_ptr source) { - osg::ref_ptr instance (new BulletShapeInstance(this)); - return instance; + return {new BulletShapeInstance(std::move(source))}; } BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 369aed18a0..bf249cb364 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -21,7 +21,6 @@ namespace Resource using CollisionShapePtr = std::unique_ptr; - class BulletShapeInstance; class BulletShape : public osg::Object { public: @@ -48,8 +47,6 @@ namespace Resource // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; - osg::ref_ptr makeInstance() const; - btCollisionShape* getCollisionShape() const; btCollisionShape* getAvoidCollisionShape() const; @@ -71,6 +68,8 @@ namespace Resource osg::ref_ptr mSource; }; + osg::ref_ptr makeInstance(osg::ref_ptr source); + // Subclass btBhvTriangleMeshShape to auto-delete the meshInterface struct TriangleMeshShape : public btBvhTriangleMeshShape { diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index cde069837a..ae6f3659a5 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -193,9 +193,8 @@ osg::ref_ptr BulletShapeManager::createInstance(const std:: { osg::ref_ptr shape = getShape(name); if (shape) - return shape->makeInstance(); - else - return osg::ref_ptr(); + return makeInstance(std::move(shape)); + return osg::ref_ptr(); } void BulletShapeManager::updateCache(double referenceTime) From 8e71c246bfcae5c44907c4a06702f2403082b4da Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 03:30:38 +0200 Subject: [PATCH 1572/2859] Remove redundant BulletShape getters --- apps/openmw/mwphysics/object.cpp | 6 +++--- apps/openmw/mwphysics/physicssystem.cpp | 2 +- apps/openmw/mwworld/scene.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw_test_suite/detournavigator/navigator.cpp | 2 +- components/detournavigator/navigatorimpl.cpp | 8 ++++---- components/resource/bulletshape.cpp | 10 ---------- components/resource/bulletshape.hpp | 4 ---- 8 files changed, 11 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 879c12124e..08fcc7e47d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -24,7 +24,7 @@ namespace MWPhysics , mTaskScheduler(scheduler) { mPtr = ptr; - mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->getCollisionShape(), + mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(), Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation)); mCollisionObject->setUserPointer(this); mShapeInstance->setLocalScaling(mScale); @@ -109,9 +109,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - assert (mShapeInstance->getCollisionShape()->isCompound()); + assert (mShapeInstance->mCollisionShape->isCompound()); - btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); + btCompoundShape* compound = static_cast(mShapeInstance->mCollisionShape.get()); for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes) { auto nodePathFound = mRecIndexToNodePath.find(recIndex); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 636c3492f7..9b5f41a5fb 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -481,7 +481,7 @@ namespace MWPhysics if (ptr.mRef->mData.mPhysicsPostponed) return; osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); - if (!shapeInstance || !shapeInstance->getCollisionShape()) + if (!shapeInstance || !shapeInstance->mCollisionShape) return; assert(!getObject(ptr)); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index d49e6c0e8b..f9e792c7d2 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -132,7 +132,7 @@ namespace { btVector3 aabbMin; btVector3 aabbMax; - object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + object->getShapeInstance()->mCollisionShape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); const auto center = (aabbMax + aabbMin) * 0.5f; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 21e2d8380a..efb8b7f370 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3948,7 +3948,7 @@ namespace MWWorld btVector3 aabbMin; btVector3 aabbMax; - object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + object->getShapeInstance()->mCollisionShape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); const auto toLocal = object->getTransform().inverse(); const auto localFrom = toLocal(Misc::Convert::toBullet(position)); diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 6579076778..d4bdcb13b8 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -482,7 +482,7 @@ namespace osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(instance->getCollisionShape()), ObjectShapes(instance), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 9a59ecf6d7..44b42b22c2 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -34,9 +34,9 @@ namespace DetourNavigator bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->getCollisionShape()}; + CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape}; bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); - if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) + if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; @@ -64,9 +64,9 @@ namespace DetourNavigator bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - const CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->getCollisionShape()}; + const CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape}; bool result = mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground); - if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) + if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); const CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 4b074ee146..d8315d08b6 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -77,16 +77,6 @@ BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) { } -btCollisionShape *BulletShape::getCollisionShape() const -{ - return mCollisionShape.get(); -} - -btCollisionShape *BulletShape::getAvoidCollisionShape() const -{ - return mAvoidCollisionShape.get(); -} - void BulletShape::setLocalScaling(const btVector3& scale) { mCollisionShape->setLocalScaling(scale); diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index bf249cb364..1065f3893b 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -47,10 +47,6 @@ namespace Resource // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; - btCollisionShape* getCollisionShape() const; - - btCollisionShape* getAvoidCollisionShape() const; - void setLocalScaling(const btVector3& scale); bool isAnimated() const; From ed5a4e195b1e4bb55c89444eaa5546d3dd5edc35 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 03:38:38 +0200 Subject: [PATCH 1573/2859] Use unique_ptr to avoid possible memory leak --- components/resource/bulletshapemanager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index ae6f3659a5..39ceb4fe7e 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -83,7 +83,8 @@ public: return osg::ref_ptr(); osg::ref_ptr shape (new BulletShape); - btBvhTriangleMeshShape* triangleMeshShape = new TriangleMeshShape(mTriangleMesh.release(), true); + + auto triangleMeshShape = std::make_unique(mTriangleMesh.release(), true); btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); shape->mCollisionBox.mExtents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; @@ -92,7 +93,7 @@ public: shape->mCollisionBox.mCenter = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, (aabbMax[1] + aabbMin[1]) / 2.0f, (aabbMax[2] + aabbMin[2]) / 2.0f ); - shape->mCollisionShape.reset(triangleMeshShape); + shape->mCollisionShape.reset(triangleMeshShape.release()); return shape; } From c83facd9d3d21a19319157edff209499062ec0fc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 03:39:27 +0200 Subject: [PATCH 1574/2859] Avoid redundant osg::ref_ptr copy --- components/resource/bulletshape.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index d8315d08b6..1d4be1d14d 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -95,12 +95,12 @@ osg::ref_ptr makeInstance(osg::ref_ptr s } BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) - : mSource(source) + : mSource(std::move(source)) { - mCollisionBox = source->mCollisionBox; - mAnimatedShapes = source->mAnimatedShapes; - mCollisionShape = duplicateCollisionShape(source->mCollisionShape.get()); - mAvoidCollisionShape = duplicateCollisionShape(source->mAvoidCollisionShape.get()); + mCollisionBox = mSource->mCollisionBox; + mAnimatedShapes = mSource->mAnimatedShapes; + mCollisionShape = duplicateCollisionShape(mSource->mCollisionShape.get()); + mAvoidCollisionShape = duplicateCollisionShape(mSource->mAvoidCollisionShape.get()); } } From b731a981c4267929f2e0fdf055bb1a3e1f27c6a6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 03:41:19 +0200 Subject: [PATCH 1575/2859] Make BulletShape::isAnimated inlined --- components/resource/bulletshape.cpp | 5 ----- components/resource/bulletshape.hpp | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 1d4be1d14d..52d639d272 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -84,11 +84,6 @@ void BulletShape::setLocalScaling(const btVector3& scale) mAvoidCollisionShape->setLocalScaling(scale); } -bool BulletShape::isAnimated() const -{ - return !mAnimatedShapes.empty(); -} - osg::ref_ptr makeInstance(osg::ref_ptr source) { return {new BulletShapeInstance(std::move(source))}; diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 1065f3893b..7188165045 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -49,7 +49,7 @@ namespace Resource void setLocalScaling(const btVector3& scale); - bool isAnimated() const; + bool isAnimated() const { return !mAnimatedShapes.empty(); } }; From a851ac5fea0792f1ef6a4566d92afb16e9b971a9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 03:46:29 +0200 Subject: [PATCH 1576/2859] Use custom deleter for btCompoundShape to delete children shapes --- components/nifbullet/bulletnifloader.cpp | 2 +- components/nifbullet/bulletnifloader.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 685a80951a..f808877a75 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -179,7 +179,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) child.release(); mStaticMesh.release(); } - mShape->mCollisionShape.reset(mCompoundShape.release()); + mShape->mCollisionShape = std::move(mCompoundShape); } else if (mStaticMesh) { diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 71c84566a0..3d6a95e09f 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -61,7 +61,7 @@ private: void handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); - std::unique_ptr mCompoundShape; + std::unique_ptr mCompoundShape; std::unique_ptr mStaticMesh; From f0eb068e4b99281b5d4b1843fd3135aaede7c457 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Fri, 29 Oct 2021 18:50:04 -0700 Subject: [PATCH 1577/2859] remove dead code and fix changelog entry --- CHANGELOG.md | 1 - apps/openmw/mwrender/sky.hpp | 11 ----------- apps/openmw/mwrender/skyutil.cpp | 4 ---- 3 files changed, 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad6734f8c..7a486046e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed - Bug #4752: UpdateCellCommand doesn't undo properly Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 227dee213b..1a30633886 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -32,17 +32,6 @@ namespace Resource namespace MWRender { - class AtmosphereUpdater; - class AtmosphereNightUpdater; - class CloudUpdater; - class Sun; - class Moon; - class RainCounter; - class RainShooter; - class RainFader; - class AlphaFader; - class UnderwaterSwitchCallback; - ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to be rendered /// relative to the camera (e.g. weather particle effects) class SkyManager diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index 9932733fa1..8229fa5925 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -278,10 +278,6 @@ namespace MWRender { // no traverse return; - - osg::Vec4 v4; - osg::Vec4 v3; - v4 = v3; } else { From 9c5f8b8719fbe9e89c929df46383c2a91aae43f9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 04:34:06 +0200 Subject: [PATCH 1578/2859] Store holder only in parent RecastMeshObject --- .../detournavigator/recastmeshobject.cpp | 37 ++++++++------- .../detournavigator/recastmeshobject.hpp | 47 ++++++++++--------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 8b4bc2fd6f..31aa13a208 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -11,7 +11,7 @@ namespace DetourNavigator namespace { bool updateCompoundObject(const btCompoundShape& shape, const AreaType areaType, - std::vector& children) + std::vector& children) { assert(static_cast(shape.getNumChildShapes()) == children.size()); bool result = false; @@ -23,39 +23,33 @@ namespace DetourNavigator return result; } - std::vector makeChildrenObjects(const osg::ref_ptr& holder, - const btCompoundShape& shape, const AreaType areaType) + std::vector makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType) { - std::vector result; + std::vector result; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) - { - const CollisionShape collisionShape {holder, *shape.getChildShape(i)}; - result.emplace_back(collisionShape, shape.getChildTransform(i), areaType); - } + result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i), areaType); return result; } - std::vector makeChildrenObjects(const osg::ref_ptr& holder, - const btCollisionShape& shape, const AreaType areaType) + std::vector makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType) { if (shape.isCompound()) - return makeChildrenObjects(holder, static_cast(shape), areaType); - return std::vector(); + return makeChildrenObjects(static_cast(shape), areaType); + return {}; } } - RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, + ChildRecastMeshObject::ChildRecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) - : mHolder(shape.getHolder()) - , mShape(shape.getShape()) + : mShape(shape) , mTransform(transform) , mAreaType(areaType) - , mLocalScaling(mShape.get().getLocalScaling()) - , mChildren(makeChildrenObjects(mHolder, mShape.get(), mAreaType)) + , mLocalScaling(shape.getLocalScaling()) + , mChildren(makeChildrenObjects(shape, mAreaType)) { } - bool RecastMeshObject::update(const btTransform& transform, const AreaType areaType) + bool ChildRecastMeshObject::update(const btTransform& transform, const AreaType areaType) { bool result = false; if (!(mTransform == transform)) @@ -78,4 +72,11 @@ namespace DetourNavigator || result; return result; } + + RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, + const AreaType areaType) + : mHolder(shape.getHolder()) + , mImpl(shape.getShape(), transform, areaType) + { + } } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index 0c50c2f346..e833ee37e3 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -32,40 +32,45 @@ namespace DetourNavigator std::reference_wrapper mShape; }; - class RecastMeshObject + class ChildRecastMeshObject { public: - RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType); + ChildRecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); bool update(const btTransform& transform, const AreaType areaType); - const osg::ref_ptr& getHolder() const - { - return mHolder; - } - - const btCollisionShape& getShape() const - { - return mShape; - } + const btCollisionShape& getShape() const { return mShape; } - const btTransform& getTransform() const - { - return mTransform; - } + const btTransform& getTransform() const { return mTransform; } - AreaType getAreaType() const - { - return mAreaType; - } + AreaType getAreaType() const { return mAreaType; } private: - osg::ref_ptr mHolder; std::reference_wrapper mShape; btTransform mTransform; AreaType mAreaType; btVector3 mLocalScaling; - std::vector mChildren; + std::vector mChildren; + }; + + class RecastMeshObject + { + public: + RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType); + + bool update(const btTransform& transform, const AreaType areaType) { return mImpl.update(transform, areaType); } + + const osg::ref_ptr& getHolder() const { return mHolder; } + + const btCollisionShape& getShape() const { return mImpl.getShape(); } + + const btTransform& getTransform() const { return mImpl.getTransform(); } + + AreaType getAreaType() const { return mImpl.getAreaType(); } + + private: + osg::ref_ptr mHolder; + ChildRecastMeshObject mImpl; }; } From e1ac871672ea179e97b4571ec02ddd54832ca82c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 27 Oct 2021 18:30:26 +0200 Subject: [PATCH 1579/2859] Start adding compiler tests --- apps/openmw_test_suite/CMakeLists.txt | 2 + .../mwscript/test_scripts.cpp | 140 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 apps/openmw_test_suite/mwscript/test_scripts.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 2b96d4c633..bf235331cf 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -12,6 +12,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) mwdialogue/test_keywordsearch.cpp + mwscript/test_scripts.cpp + esm/test_fixed_string.cpp esm/variant.cpp diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp new file mode 100644 index 0000000000..d019244275 --- /dev/null +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -0,0 +1,140 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace +{ + class TestCompilerContext : public Compiler::Context + { + public: + bool canDeclareLocals() const override { return true; } + char getGlobalType(const std::string& name) const override { return ' '; } + std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } + bool isId(const std::string& name) const override { return false; } + bool isJournalId(const std::string& name) const override { return false; } + }; + + class TestErrorHandler : public Compiler::ErrorHandler + { + std::vector> mErrors; + + void report(const std::string& message, const Compiler::TokenLoc& loc, Compiler::ErrorHandler::Type type) override + { + if(type == Compiler::ErrorHandler::ErrorMessage) + mErrors.emplace_back(message, loc); + } + + void report(const std::string& message, Compiler::ErrorHandler::Type type) override + { + report(message, {}, type); + } + + public: + void reset() override + { + Compiler::ErrorHandler::reset(); + mErrors.clear(); + } + + const std::vector>& getErrors() const { return mErrors; } + }; + + struct CompiledScript + { + std::vector mByteCode; + Compiler::Locals mLocals; + + CompiledScript(const std::vector& code, const Compiler::Locals& locals) : mByteCode(code), mLocals(locals) {} + }; + + struct MWScriptTest : public ::testing::Test + { + MWScriptTest() : mErrorHandler(), mParser(mErrorHandler, mCompilerContext) {} + + std::optional compile(const std::string& scriptBody) + { + mParser.reset(); + mErrorHandler.reset(); + std::istringstream input(scriptBody); + Compiler::Scanner scanner(mErrorHandler, input, mCompilerContext.getExtensions()); + scanner.scan(mParser); + if(mErrorHandler.isGood()) + { + std::vector code; + mParser.getCode(code); + return CompiledScript(code, mParser.getLocals()); + } + return {}; + } + + void logErrors() + { + for(const auto& [error, loc] : mErrorHandler.getErrors()) + { + std::cout << error; + if(loc.mLine) + std::cout << " at line" << loc.mLine << " column " << loc.mColumn << " (" << loc.mLiteral << ")"; + std::cout << "\n"; + } + } + + void run(const CompiledScript& script) + { + // mInterpreter.run(&script.mByteCode[0], script.mByteCode.size(), interpreterContext); + } + protected: + void SetUp() override {} + + void TearDown() override {} + private: + TestErrorHandler mErrorHandler; + TestCompilerContext mCompilerContext; + Compiler::FileParser mParser; + Interpreter::Interpreter mInterpreter; + }; + + const std::string sScript1 = R"mwscript(Begin basic_logic +; Comment +short one +short two + +set one to two + +if ( one == two ) + set one to 1 +elseif ( two == 1 ) + set one to 2 +else + set one to 3 +endif + +while ( one < two ) + set one to ( one + 1 ) +endwhile + +End)mwscript"; + + TEST_F(MWScriptTest, mwscript_test_invalid) + { + EXPECT_THROW(compile("this is not a valid script"), Compiler::SourceException); + } + + TEST_F(MWScriptTest, mwscript_test_compilation) + { + auto script = compile(sScript1); + logErrors(); + EXPECT_FALSE(!script); + } +} \ No newline at end of file From 6ad8549163f543049e739607a23c3981d9fbd6cd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 27 Oct 2021 22:00:53 +0200 Subject: [PATCH 1580/2859] Allow validation of constant arguments --- .../mwscript/test_scripts.cpp | 508 +++++++++++++----- 1 file changed, 369 insertions(+), 139 deletions(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index d019244275..fcb4c3de5d 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -1,140 +1,370 @@ -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -namespace -{ - class TestCompilerContext : public Compiler::Context - { - public: - bool canDeclareLocals() const override { return true; } - char getGlobalType(const std::string& name) const override { return ' '; } - std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } - bool isId(const std::string& name) const override { return false; } - bool isJournalId(const std::string& name) const override { return false; } - }; - - class TestErrorHandler : public Compiler::ErrorHandler - { - std::vector> mErrors; - - void report(const std::string& message, const Compiler::TokenLoc& loc, Compiler::ErrorHandler::Type type) override - { - if(type == Compiler::ErrorHandler::ErrorMessage) - mErrors.emplace_back(message, loc); - } - - void report(const std::string& message, Compiler::ErrorHandler::Type type) override - { - report(message, {}, type); - } - - public: - void reset() override - { - Compiler::ErrorHandler::reset(); - mErrors.clear(); - } - - const std::vector>& getErrors() const { return mErrors; } - }; - - struct CompiledScript - { - std::vector mByteCode; - Compiler::Locals mLocals; - - CompiledScript(const std::vector& code, const Compiler::Locals& locals) : mByteCode(code), mLocals(locals) {} - }; - - struct MWScriptTest : public ::testing::Test - { - MWScriptTest() : mErrorHandler(), mParser(mErrorHandler, mCompilerContext) {} - - std::optional compile(const std::string& scriptBody) - { - mParser.reset(); - mErrorHandler.reset(); - std::istringstream input(scriptBody); - Compiler::Scanner scanner(mErrorHandler, input, mCompilerContext.getExtensions()); - scanner.scan(mParser); - if(mErrorHandler.isGood()) - { - std::vector code; - mParser.getCode(code); - return CompiledScript(code, mParser.getLocals()); - } - return {}; - } - - void logErrors() - { - for(const auto& [error, loc] : mErrorHandler.getErrors()) - { - std::cout << error; - if(loc.mLine) - std::cout << " at line" << loc.mLine << " column " << loc.mColumn << " (" << loc.mLiteral << ")"; - std::cout << "\n"; - } - } - - void run(const CompiledScript& script) - { - // mInterpreter.run(&script.mByteCode[0], script.mByteCode.size(), interpreterContext); - } - protected: - void SetUp() override {} - - void TearDown() override {} - private: - TestErrorHandler mErrorHandler; - TestCompilerContext mCompilerContext; - Compiler::FileParser mParser; - Interpreter::Interpreter mInterpreter; - }; - - const std::string sScript1 = R"mwscript(Begin basic_logic -; Comment -short one -short two - -set one to two - -if ( one == two ) - set one to 1 -elseif ( two == 1 ) - set one to 2 -else - set one to 3 -endif - -while ( one < two ) - set one to ( one + 1 ) -endwhile - -End)mwscript"; - - TEST_F(MWScriptTest, mwscript_test_invalid) - { - EXPECT_THROW(compile("this is not a valid script"), Compiler::SourceException); - } - - TEST_F(MWScriptTest, mwscript_test_compilation) - { - auto script = compile(sScript1); - logErrors(); - EXPECT_FALSE(!script); - } +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + class TestCompilerContext : public Compiler::Context + { + public: + bool canDeclareLocals() const override { return true; } + char getGlobalType(const std::string& name) const override { return ' '; } + std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } + bool isId(const std::string& name) const override { return false; } + bool isJournalId(const std::string& name) const override { return false; } + }; + + class TestErrorHandler : public Compiler::ErrorHandler + { + std::vector> mErrors; + + void report(const std::string& message, const Compiler::TokenLoc& loc, Compiler::ErrorHandler::Type type) override + { + if(type == Compiler::ErrorHandler::ErrorMessage) + mErrors.emplace_back(message, loc); + } + + void report(const std::string& message, Compiler::ErrorHandler::Type type) override + { + report(message, {}, type); + } + + public: + void reset() override + { + Compiler::ErrorHandler::reset(); + mErrors.clear(); + } + + const std::vector>& getErrors() const { return mErrors; } + }; + + class LocalVariables + { + std::vector mShorts; + std::vector mLongs; + std::vector mFloats; + + template + T getLocal(int index, const std::vector& vector) const + { + if(index < vector.size()) + return vector[index]; + return {}; + } + + template + void setLocal(T value, int index, std::vector& vector) + { + if(index >= vector.size()) + vector.resize(index + 1); + vector[index] = value; + } + public: + void clear() + { + mShorts.clear(); + mLongs.clear(); + mFloats.clear(); + } + + int getShort(int index) const { return getLocal(index, mShorts); }; + + int getLong(int index) const { return getLocal(index, mLongs); }; + + float getFloat(int index) const { return getLocal(index, mFloats); }; + + void setShort(int index, int value) { setLocal(value, index, mShorts); }; + + void setLong(int index, int value) { setLocal(value, index, mLongs); }; + + void setFloat(int index, float value) { setLocal(value, index, mFloats); }; + }; + + class GlobalVariables + { + std::map mShorts; + std::map mLongs; + std::map mFloats; + + template + T getGlobal(const std::string& name, const std::map& map) const + { + auto it = map.find(name); + if(it != map.end()) + return it->second; + return {}; + } + public: + void clear() + { + mShorts.clear(); + mLongs.clear(); + mFloats.clear(); + } + + int getShort(const std::string& name) const { return getGlobal(name, mShorts); }; + + int getLong(const std::string& name) const { return getGlobal(name, mLongs); }; + + float getFloat(const std::string& name) const { return getGlobal(name, mFloats); }; + + void setShort(const std::string& name, int value) { mShorts[name] = value; }; + + void setLong(const std::string& name, int value) { mLongs[name] = value; }; + + void setFloat(const std::string& name, float value) { mFloats[name] = value; }; + }; + + class TestInterpreterContext : public Interpreter::Context + { + LocalVariables mLocals; + std::map mMembers; + public: + std::string getTarget() const override { return {}; }; + + int getLocalShort(int index) const override { return mLocals.getShort(index); }; + + int getLocalLong(int index) const override { return mLocals.getLong(index); }; + + float getLocalFloat(int index) const override { return mLocals.getFloat(index); }; + + void setLocalShort(int index, int value) override { mLocals.setShort(index, value); }; + + void setLocalLong(int index, int value) override { mLocals.setLong(index, value); }; + + void setLocalFloat(int index, float value) override { mLocals.setFloat(index, value); }; + + void messageBox(const std::string& message, const std::vector& buttons) override {}; + + void report(const std::string& message) override { std::cout << message << "\n"; }; + + int getGlobalShort(const std::string& name) const override { return {}; }; + + int getGlobalLong(const std::string& name) const override { return {}; }; + + float getGlobalFloat(const std::string& name) const override { return {}; }; + + void setGlobalShort(const std::string& name, int value) override {}; + + void setGlobalLong(const std::string& name, int value) override {}; + + void setGlobalFloat(const std::string& name, float value) override {}; + + std::vector getGlobals() const override { return {}; }; + + char getGlobalType(const std::string& name) const override { return ' '; }; + + std::string getActionBinding(const std::string& action) const override { return {}; }; + + std::string getActorName() const override { return {}; }; + + std::string getNPCRace() const override { return {}; }; + + std::string getNPCClass() const override { return {}; }; + + std::string getNPCFaction() const override { return {}; }; + + std::string getNPCRank() const override { return {}; }; + + std::string getPCName() const override { return {}; }; + + std::string getPCRace() const override { return {}; }; + + std::string getPCClass() const override { return {}; }; + + std::string getPCRank() const override { return {}; }; + + std::string getPCNextRank() const override { return {}; }; + + int getPCBounty() const override { return {}; }; + + std::string getCurrentCellName() const override { return {}; }; + + int getMemberShort(const std::string& id, const std::string& name, bool global) const override + { + auto it = mMembers.find(id); + if(it != mMembers.end()) + return it->second.getShort(name); + return {}; + }; + + int getMemberLong(const std::string& id, const std::string& name, bool global) const override + { + auto it = mMembers.find(id); + if(it != mMembers.end()) + return it->second.getLong(name); + return {}; + }; + + float getMemberFloat(const std::string& id, const std::string& name, bool global) const override + { + auto it = mMembers.find(id); + if(it != mMembers.end()) + return it->second.getFloat(name); + return {}; + }; + + void setMemberShort(const std::string& id, const std::string& name, int value, bool global) override { mMembers[id].setShort(name, value); }; + + void setMemberLong(const std::string& id, const std::string& name, int value, bool global) override { mMembers[id].setLong(name, value); }; + + void setMemberFloat(const std::string& id, const std::string& name, float value, bool global) override { mMembers[id].setFloat(name, value); }; + }; + + struct CompiledScript + { + std::vector mByteCode; + Compiler::Locals mLocals; + + CompiledScript(const std::vector& code, const Compiler::Locals& locals) : mByteCode(code), mLocals(locals) {} + }; + + struct MWScriptTest : public ::testing::Test + { + MWScriptTest() : mErrorHandler(), mParser(mErrorHandler, mCompilerContext) {} + + std::optional compile(const std::string& scriptBody, bool shouldFail = false) + { + mParser.reset(); + mErrorHandler.reset(); + std::istringstream input(scriptBody); + Compiler::Scanner scanner(mErrorHandler, input, mCompilerContext.getExtensions()); + scanner.scan(mParser); + if(mErrorHandler.isGood()) + { + std::vector code; + mParser.getCode(code); + return CompiledScript(code, mParser.getLocals()); + } + else if(!shouldFail) + logErrors(); + return {}; + } + + void logErrors() + { + for(const auto& [error, loc] : mErrorHandler.getErrors()) + { + std::cout << error; + if(loc.mLine) + std::cout << " at line" << loc.mLine << " column " << loc.mColumn << " (" << loc.mLiteral << ")"; + std::cout << "\n"; + } + } + + void registerExtensions() + { + Compiler::registerExtensions(mExtensions); + mCompilerContext.setExtensions(&mExtensions); + } + + void run(const CompiledScript& script, TestInterpreterContext& context) + { + mInterpreter.run(&script.mByteCode[0], script.mByteCode.size(), context); + } + + void installOpcode(int code, Interpreter::Opcode0* opcode) + { + mInterpreter.installSegment5(code, opcode); + } + protected: + void SetUp() override + { + Interpreter::installOpcodes(mInterpreter); + } + + void TearDown() override {} + private: + TestErrorHandler mErrorHandler; + TestCompilerContext mCompilerContext; + Compiler::FileParser mParser; + Compiler::Extensions mExtensions; + Interpreter::Interpreter mInterpreter; + }; + + const std::string sScript1 = R"mwscript(Begin basic_logic +; Comment +short one +short two + +set one to two + +if ( one == two ) + set one to 1 +elseif ( two == 1 ) + set one to 2 +else + set one to 3 +endif + +while ( one < two ) + set one to ( one + 1 ) +endwhile + +End)mwscript"; + + const std::string sScript2 = R"mwscript(Begin addtopic + +AddTopic "OpenMW Unit Test" + +End)mwscript"; + + TEST_F(MWScriptTest, mwscript_test_invalid) + { + EXPECT_THROW(compile("this is not a valid script", true), Compiler::SourceException); + } + + TEST_F(MWScriptTest, mwscript_test_compilation) + { + EXPECT_FALSE(!compile(sScript1)); + } + + TEST_F(MWScriptTest, mwscript_test_no_extensions) + { + EXPECT_THROW(compile(sScript2, true), Compiler::SourceException); + } + + TEST_F(MWScriptTest, mwscript_test_function) + { + registerExtensions(); + if(auto script = compile(sScript2)) + { + class AddTopic : public Interpreter::Opcode0 + { + public: + void execute(Interpreter::Runtime& runtime) + { + const auto topic = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + EXPECT_EQ(topic, "OpenMW Unit Test"); + } + }; + installOpcode(Compiler::Dialogue::opcodeAddTopic, new AddTopic); + TestInterpreterContext context; + run(*script, context); + } + else + { + FAIL(); + } + } } \ No newline at end of file From be759e576a08806ef93ddcec622ab280facd7a82 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 27 Oct 2021 22:08:41 +0200 Subject: [PATCH 1581/2859] Be sure to verify the opcode got executed --- apps/openmw_test_suite/mwscript/test_scripts.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index fcb4c3de5d..3c0cd477d5 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -346,23 +346,28 @@ End)mwscript"; TEST_F(MWScriptTest, mwscript_test_function) { registerExtensions(); + bool failed = true; if(auto script = compile(sScript2)) { class AddTopic : public Interpreter::Opcode0 { + bool& mFailed; public: + AddTopic(bool& failed) : mFailed(failed) {} + void execute(Interpreter::Runtime& runtime) { const auto topic = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); + mFailed = false; EXPECT_EQ(topic, "OpenMW Unit Test"); } }; - installOpcode(Compiler::Dialogue::opcodeAddTopic, new AddTopic); + installOpcode(Compiler::Dialogue::opcodeAddTopic, new AddTopic(failed)); TestInterpreterContext context; run(*script, context); } - else + if(failed) { FAIL(); } From f027acf5751b8fa75ecce9251d5dd70eef9411aa Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 27 Oct 2021 22:13:56 +0200 Subject: [PATCH 1582/2859] Move boilerplate to separate file --- .../mwscript/test_scripts.cpp | 237 +---------------- .../openmw_test_suite/mwscript/test_utils.hpp | 242 ++++++++++++++++++ 2 files changed, 244 insertions(+), 235 deletions(-) create mode 100644 apps/openmw_test_suite/mwscript/test_utils.hpp diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index 3c0cd477d5..90c7fb53c2 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -1,243 +1,10 @@ #include - -#include #include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "test_utils.hpp" namespace { - class TestCompilerContext : public Compiler::Context - { - public: - bool canDeclareLocals() const override { return true; } - char getGlobalType(const std::string& name) const override { return ' '; } - std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } - bool isId(const std::string& name) const override { return false; } - bool isJournalId(const std::string& name) const override { return false; } - }; - - class TestErrorHandler : public Compiler::ErrorHandler - { - std::vector> mErrors; - - void report(const std::string& message, const Compiler::TokenLoc& loc, Compiler::ErrorHandler::Type type) override - { - if(type == Compiler::ErrorHandler::ErrorMessage) - mErrors.emplace_back(message, loc); - } - - void report(const std::string& message, Compiler::ErrorHandler::Type type) override - { - report(message, {}, type); - } - - public: - void reset() override - { - Compiler::ErrorHandler::reset(); - mErrors.clear(); - } - - const std::vector>& getErrors() const { return mErrors; } - }; - - class LocalVariables - { - std::vector mShorts; - std::vector mLongs; - std::vector mFloats; - - template - T getLocal(int index, const std::vector& vector) const - { - if(index < vector.size()) - return vector[index]; - return {}; - } - - template - void setLocal(T value, int index, std::vector& vector) - { - if(index >= vector.size()) - vector.resize(index + 1); - vector[index] = value; - } - public: - void clear() - { - mShorts.clear(); - mLongs.clear(); - mFloats.clear(); - } - - int getShort(int index) const { return getLocal(index, mShorts); }; - - int getLong(int index) const { return getLocal(index, mLongs); }; - - float getFloat(int index) const { return getLocal(index, mFloats); }; - - void setShort(int index, int value) { setLocal(value, index, mShorts); }; - - void setLong(int index, int value) { setLocal(value, index, mLongs); }; - - void setFloat(int index, float value) { setLocal(value, index, mFloats); }; - }; - - class GlobalVariables - { - std::map mShorts; - std::map mLongs; - std::map mFloats; - - template - T getGlobal(const std::string& name, const std::map& map) const - { - auto it = map.find(name); - if(it != map.end()) - return it->second; - return {}; - } - public: - void clear() - { - mShorts.clear(); - mLongs.clear(); - mFloats.clear(); - } - - int getShort(const std::string& name) const { return getGlobal(name, mShorts); }; - - int getLong(const std::string& name) const { return getGlobal(name, mLongs); }; - - float getFloat(const std::string& name) const { return getGlobal(name, mFloats); }; - - void setShort(const std::string& name, int value) { mShorts[name] = value; }; - - void setLong(const std::string& name, int value) { mLongs[name] = value; }; - - void setFloat(const std::string& name, float value) { mFloats[name] = value; }; - }; - - class TestInterpreterContext : public Interpreter::Context - { - LocalVariables mLocals; - std::map mMembers; - public: - std::string getTarget() const override { return {}; }; - - int getLocalShort(int index) const override { return mLocals.getShort(index); }; - - int getLocalLong(int index) const override { return mLocals.getLong(index); }; - - float getLocalFloat(int index) const override { return mLocals.getFloat(index); }; - - void setLocalShort(int index, int value) override { mLocals.setShort(index, value); }; - - void setLocalLong(int index, int value) override { mLocals.setLong(index, value); }; - - void setLocalFloat(int index, float value) override { mLocals.setFloat(index, value); }; - - void messageBox(const std::string& message, const std::vector& buttons) override {}; - - void report(const std::string& message) override { std::cout << message << "\n"; }; - - int getGlobalShort(const std::string& name) const override { return {}; }; - - int getGlobalLong(const std::string& name) const override { return {}; }; - - float getGlobalFloat(const std::string& name) const override { return {}; }; - - void setGlobalShort(const std::string& name, int value) override {}; - - void setGlobalLong(const std::string& name, int value) override {}; - - void setGlobalFloat(const std::string& name, float value) override {}; - - std::vector getGlobals() const override { return {}; }; - - char getGlobalType(const std::string& name) const override { return ' '; }; - - std::string getActionBinding(const std::string& action) const override { return {}; }; - - std::string getActorName() const override { return {}; }; - - std::string getNPCRace() const override { return {}; }; - - std::string getNPCClass() const override { return {}; }; - - std::string getNPCFaction() const override { return {}; }; - - std::string getNPCRank() const override { return {}; }; - - std::string getPCName() const override { return {}; }; - - std::string getPCRace() const override { return {}; }; - - std::string getPCClass() const override { return {}; }; - - std::string getPCRank() const override { return {}; }; - - std::string getPCNextRank() const override { return {}; }; - - int getPCBounty() const override { return {}; }; - - std::string getCurrentCellName() const override { return {}; }; - - int getMemberShort(const std::string& id, const std::string& name, bool global) const override - { - auto it = mMembers.find(id); - if(it != mMembers.end()) - return it->second.getShort(name); - return {}; - }; - - int getMemberLong(const std::string& id, const std::string& name, bool global) const override - { - auto it = mMembers.find(id); - if(it != mMembers.end()) - return it->second.getLong(name); - return {}; - }; - - float getMemberFloat(const std::string& id, const std::string& name, bool global) const override - { - auto it = mMembers.find(id); - if(it != mMembers.end()) - return it->second.getFloat(name); - return {}; - }; - - void setMemberShort(const std::string& id, const std::string& name, int value, bool global) override { mMembers[id].setShort(name, value); }; - - void setMemberLong(const std::string& id, const std::string& name, int value, bool global) override { mMembers[id].setLong(name, value); }; - - void setMemberFloat(const std::string& id, const std::string& name, float value, bool global) override { mMembers[id].setFloat(name, value); }; - }; - - struct CompiledScript - { - std::vector mByteCode; - Compiler::Locals mLocals; - - CompiledScript(const std::vector& code, const Compiler::Locals& locals) : mByteCode(code), mLocals(locals) {} - }; - struct MWScriptTest : public ::testing::Test { MWScriptTest() : mErrorHandler(), mParser(mErrorHandler, mCompilerContext) {} @@ -279,7 +46,7 @@ namespace void run(const CompiledScript& script, TestInterpreterContext& context) { - mInterpreter.run(&script.mByteCode[0], script.mByteCode.size(), context); + mInterpreter.run(&script.mByteCode[0], static_cast(script.mByteCode.size()), context); } void installOpcode(int code, Interpreter::Opcode0* opcode) diff --git a/apps/openmw_test_suite/mwscript/test_utils.hpp b/apps/openmw_test_suite/mwscript/test_utils.hpp new file mode 100644 index 0000000000..58218b7000 --- /dev/null +++ b/apps/openmw_test_suite/mwscript/test_utils.hpp @@ -0,0 +1,242 @@ +#ifndef MWSCRIPT_TESTING_UTIL_H +#define MWSCRIPT_TESTING_UTIL_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + class TestCompilerContext : public Compiler::Context + { + public: + bool canDeclareLocals() const override { return true; } + char getGlobalType(const std::string& name) const override { return ' '; } + std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } + bool isId(const std::string& name) const override { return false; } + bool isJournalId(const std::string& name) const override { return false; } + }; + + class TestErrorHandler : public Compiler::ErrorHandler + { + std::vector> mErrors; + + void report(const std::string& message, const Compiler::TokenLoc& loc, Compiler::ErrorHandler::Type type) override + { + if(type == Compiler::ErrorHandler::ErrorMessage) + mErrors.emplace_back(message, loc); + } + + void report(const std::string& message, Compiler::ErrorHandler::Type type) override + { + report(message, {}, type); + } + + public: + void reset() override + { + Compiler::ErrorHandler::reset(); + mErrors.clear(); + } + + const std::vector>& getErrors() const { return mErrors; } + }; + + class LocalVariables + { + std::vector mShorts; + std::vector mLongs; + std::vector mFloats; + + template + T getLocal(int index, const std::vector& vector) const + { + if(index < vector.size()) + return vector[index]; + return {}; + } + + template + void setLocal(T value, int index, std::vector& vector) + { + if(index >= vector.size()) + vector.resize(index + 1); + vector[index] = value; + } + public: + void clear() + { + mShorts.clear(); + mLongs.clear(); + mFloats.clear(); + } + + int getShort(int index) const { return getLocal(index, mShorts); }; + + int getLong(int index) const { return getLocal(index, mLongs); }; + + float getFloat(int index) const { return getLocal(index, mFloats); }; + + void setShort(int index, int value) { setLocal(value, index, mShorts); }; + + void setLong(int index, int value) { setLocal(value, index, mLongs); }; + + void setFloat(int index, float value) { setLocal(value, index, mFloats); }; + }; + + class GlobalVariables + { + std::map mShorts; + std::map mLongs; + std::map mFloats; + + template + T getGlobal(const std::string& name, const std::map& map) const + { + auto it = map.find(name); + if(it != map.end()) + return it->second; + return {}; + } + public: + void clear() + { + mShorts.clear(); + mLongs.clear(); + mFloats.clear(); + } + + int getShort(const std::string& name) const { return getGlobal(name, mShorts); }; + + int getLong(const std::string& name) const { return getGlobal(name, mLongs); }; + + float getFloat(const std::string& name) const { return getGlobal(name, mFloats); }; + + void setShort(const std::string& name, int value) { mShorts[name] = value; }; + + void setLong(const std::string& name, int value) { mLongs[name] = value; }; + + void setFloat(const std::string& name, float value) { mFloats[name] = value; }; + }; + + class TestInterpreterContext : public Interpreter::Context + { + LocalVariables mLocals; + std::map mMembers; + public: + std::string getTarget() const override { return {}; }; + + int getLocalShort(int index) const override { return mLocals.getShort(index); }; + + int getLocalLong(int index) const override { return mLocals.getLong(index); }; + + float getLocalFloat(int index) const override { return mLocals.getFloat(index); }; + + void setLocalShort(int index, int value) override { mLocals.setShort(index, value); }; + + void setLocalLong(int index, int value) override { mLocals.setLong(index, value); }; + + void setLocalFloat(int index, float value) override { mLocals.setFloat(index, value); }; + + void messageBox(const std::string& message, const std::vector& buttons) override {}; + + void report(const std::string& message) override {}; + + int getGlobalShort(const std::string& name) const override { return {}; }; + + int getGlobalLong(const std::string& name) const override { return {}; }; + + float getGlobalFloat(const std::string& name) const override { return {}; }; + + void setGlobalShort(const std::string& name, int value) override {}; + + void setGlobalLong(const std::string& name, int value) override {}; + + void setGlobalFloat(const std::string& name, float value) override {}; + + std::vector getGlobals() const override { return {}; }; + + char getGlobalType(const std::string& name) const override { return ' '; }; + + std::string getActionBinding(const std::string& action) const override { return {}; }; + + std::string getActorName() const override { return {}; }; + + std::string getNPCRace() const override { return {}; }; + + std::string getNPCClass() const override { return {}; }; + + std::string getNPCFaction() const override { return {}; }; + + std::string getNPCRank() const override { return {}; }; + + std::string getPCName() const override { return {}; }; + + std::string getPCRace() const override { return {}; }; + + std::string getPCClass() const override { return {}; }; + + std::string getPCRank() const override { return {}; }; + + std::string getPCNextRank() const override { return {}; }; + + int getPCBounty() const override { return {}; }; + + std::string getCurrentCellName() const override { return {}; }; + + int getMemberShort(const std::string& id, const std::string& name, bool global) const override + { + auto it = mMembers.find(id); + if(it != mMembers.end()) + return it->second.getShort(name); + return {}; + }; + + int getMemberLong(const std::string& id, const std::string& name, bool global) const override + { + auto it = mMembers.find(id); + if(it != mMembers.end()) + return it->second.getLong(name); + return {}; + }; + + float getMemberFloat(const std::string& id, const std::string& name, bool global) const override + { + auto it = mMembers.find(id); + if(it != mMembers.end()) + return it->second.getFloat(name); + return {}; + }; + + void setMemberShort(const std::string& id, const std::string& name, int value, bool global) override { mMembers[id].setShort(name, value); }; + + void setMemberLong(const std::string& id, const std::string& name, int value, bool global) override { mMembers[id].setLong(name, value); }; + + void setMemberFloat(const std::string& id, const std::string& name, float value, bool global) override { mMembers[id].setFloat(name, value); }; + }; + + struct CompiledScript + { + std::vector mByteCode; + Compiler::Locals mLocals; + + CompiledScript(const std::vector& code, const Compiler::Locals& locals) : mByteCode(code), mLocals(locals) {} + }; +} + +#endif \ No newline at end of file From 3dada0796ae288f8fb6ffc31dfae003aed3f994c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Oct 2021 17:34:31 +0200 Subject: [PATCH 1583/2859] Validate integer math --- .../mwscript/test_scripts.cpp | 60 +++++++++++++++++++ .../openmw_test_suite/mwscript/test_utils.hpp | 16 ++--- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index 90c7fb53c2..b2e534a401 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -93,6 +93,21 @@ End)mwscript"; AddTopic "OpenMW Unit Test" +End)mwscript"; + + const std::string sScript3 = R"mwscript(Begin math + +short a +short b +short c +short d +short e + +set b to ( a + 1 ) +set c to ( a - 1 ) +set d to ( b * c ) +set e to ( d / a ) + End)mwscript"; TEST_F(MWScriptTest, mwscript_test_invalid) @@ -139,4 +154,49 @@ End)mwscript"; FAIL(); } } + + TEST_F(MWScriptTest, mwscript_test_math) + { + if(auto script = compile(sScript3)) + { + struct Algorithm + { + int a; + int b; + int c; + int d; + int e; + + void run(int input) + { + a = input; + b = a + 1; + c = a - 1; + d = b * c; + e = d / a; + } + + void test(const TestInterpreterContext& context) const + { + EXPECT_EQ(a, context.getLocalShort(0)); + EXPECT_EQ(b, context.getLocalShort(1)); + EXPECT_EQ(c, context.getLocalShort(2)); + EXPECT_EQ(d, context.getLocalShort(3)); + EXPECT_EQ(e, context.getLocalShort(4)); + } + } algorithm; + TestInterpreterContext context; + for(int i = 1; i < 1000; ++i) + { + context.setLocalShort(0, i); + run(*script, context); + algorithm.run(i); + algorithm.test(context); + } + } + else + { + FAIL(); + } + } } \ No newline at end of file diff --git a/apps/openmw_test_suite/mwscript/test_utils.hpp b/apps/openmw_test_suite/mwscript/test_utils.hpp index 58218b7000..b1f4e04262 100644 --- a/apps/openmw_test_suite/mwscript/test_utils.hpp +++ b/apps/openmw_test_suite/mwscript/test_utils.hpp @@ -64,7 +64,7 @@ namespace std::vector mFloats; template - T getLocal(int index, const std::vector& vector) const + T getLocal(std::size_t index, const std::vector& vector) const { if(index < vector.size()) return vector[index]; @@ -72,7 +72,7 @@ namespace } template - void setLocal(T value, int index, std::vector& vector) + void setLocal(T value, std::size_t index, std::vector& vector) { if(index >= vector.size()) vector.resize(index + 1); @@ -86,17 +86,17 @@ namespace mFloats.clear(); } - int getShort(int index) const { return getLocal(index, mShorts); }; + int getShort(std::size_t index) const { return getLocal(index, mShorts); }; - int getLong(int index) const { return getLocal(index, mLongs); }; + int getLong(std::size_t index) const { return getLocal(index, mLongs); }; - float getFloat(int index) const { return getLocal(index, mFloats); }; + float getFloat(std::size_t index) const { return getLocal(index, mFloats); }; - void setShort(int index, int value) { setLocal(value, index, mShorts); }; + void setShort(std::size_t index, int value) { setLocal(value, index, mShorts); }; - void setLong(int index, int value) { setLocal(value, index, mLongs); }; + void setLong(std::size_t index, int value) { setLocal(value, index, mLongs); }; - void setFloat(int index, float value) { setLocal(value, index, mFloats); }; + void setFloat(std::size_t index, float value) { setLocal(value, index, mFloats); }; }; class GlobalVariables From b2cdbe2e61f679bd6bfcf424a197ee2de6067990 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Oct 2021 18:16:00 +0200 Subject: [PATCH 1584/2859] Add tests for certain issues --- .../mwscript/test_scripts.cpp | 179 ++++++++++++++++++ .../openmw_test_suite/mwscript/test_utils.hpp | 2 +- 2 files changed, 180 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index b2e534a401..b40fdd9b29 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -108,6 +108,98 @@ set c to ( a - 1 ) set d to ( b * c ) set e to ( d / a ) +End)mwscript"; + + const std::string sIssue3006 = R"mwscript(Begin issue3006 + +short a + +if ( a == 1 ) + set a to 2 +else set a to 3 +endif + +End)mwscript"; + + const std::string sIssue3725 = R"mwscript(Begin issue3725 + +onactivate + +if onactivate + ; do something +endif + +End)mwscript"; + + const std::string sIssue4451 = R"mwscript(Begin, GlassDisplayScript + +;[Script body] + +End, GlassDisplayScript)mwscript"; + + const std::string sIssue4597 = R"mwscript(Begin issue4597 + +short a +short b +short c +short d + +set c to 0 +set d to 0 + +if ( a <> b ) + set c to ( c + 1 ) +endif +if ( a << b ) + set c to ( c + 1 ) +endif +if ( a < b ) + set c to ( c + 1 ) +endif + +if ( a >< b ) + set d to ( d + 1 ) +endif +if ( a >> b ) + set d to ( d + 1 ) +endif +if ( a > b ) + set d to ( d + 1 ) +endif + +End)mwscript"; + + const std::string sIssue4598 = R"mwscript(Begin issue4598 + +StartScript kal_S_Pub_Jejubãr_Faraminos + +End)mwscript"; + + const std::string sIssue4867 = R"mwscript(Begin issue4867 + +float PcMagickaMult : The gameplay setting fPcBaseMagickaMult - 1.0000 + +End)mwscript"; + + const std::string sIssue4888 = R"mwscript(Begin issue4888 + +if (player->GameHour == 10) +set player->GameHour to 20 +endif + +End)mwscript"; + + const std::string sIssue5087 = R"mwscript(Begin Begin + +player->sethealth 0 +stopscript Begin + +End Begin)mwscript"; + + const std::string sIssue5097 = R"mwscript(Begin issue5097 + +setscale "0.3" + End)mwscript"; TEST_F(MWScriptTest, mwscript_test_invalid) @@ -199,4 +291,91 @@ End)mwscript"; FAIL(); } } + + TEST_F(MWScriptTest, mwscript_test_3006) + { + if(auto script = compile(sIssue3006)) + { + TestInterpreterContext context; + context.setLocalShort(0, 0); + run(*script, context); + EXPECT_EQ(context.getLocalShort(0), 0); + context.setLocalShort(0, 1); + run(*script, context); + EXPECT_EQ(context.getLocalShort(0), 2); + } + else + { + FAIL(); + } + } + + TEST_F(MWScriptTest, mwscript_test_3725) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue3725)); + } + + TEST_F(MWScriptTest, mwscript_test_4451) + { + EXPECT_FALSE(!compile(sIssue4451)); + } + + TEST_F(MWScriptTest, mwscript_test_4597) + { + if(auto script = compile(sIssue4597)) + { + TestInterpreterContext context; + for(int a = 0; a < 100; ++a) + { + for(int b = 0; b < 100; ++b) + { + context.setLocalShort(0, a); + context.setLocalShort(1, b); + run(*script, context); + if(a < b) + EXPECT_EQ(context.getLocalShort(2), 3); + else + EXPECT_EQ(context.getLocalShort(2), 0); + if(a > b) + EXPECT_EQ(context.getLocalShort(3), 3); + else + EXPECT_EQ(context.getLocalShort(3), 0); + + } + } + } + else + { + FAIL(); + } + } + + TEST_F(MWScriptTest, mwscript_test_4598) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue4598)); + } + + TEST_F(MWScriptTest, mwscript_test_4867) + { + EXPECT_FALSE(!compile(sIssue4867)); + } + + TEST_F(MWScriptTest, mwscript_test_4888) + { + EXPECT_FALSE(!compile(sIssue4888)); + } + + TEST_F(MWScriptTest, mwscript_test_5087) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue5087)); + } + + TEST_F(MWScriptTest, mwscript_test_5097) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue5097)); + } } \ No newline at end of file diff --git a/apps/openmw_test_suite/mwscript/test_utils.hpp b/apps/openmw_test_suite/mwscript/test_utils.hpp index b1f4e04262..53dfe93ad6 100644 --- a/apps/openmw_test_suite/mwscript/test_utils.hpp +++ b/apps/openmw_test_suite/mwscript/test_utils.hpp @@ -28,7 +28,7 @@ namespace bool canDeclareLocals() const override { return true; } char getGlobalType(const std::string& name) const override { return ' '; } std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } - bool isId(const std::string& name) const override { return false; } + bool isId(const std::string& name) const override { return name == "player"; } bool isJournalId(const std::string& name) const override { return false; } }; From 8e0dfe3a8a11ec8a8221b5a818e9b5d5524e13c0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Oct 2021 18:28:13 +0200 Subject: [PATCH 1585/2859] Add tests for more issues --- .../mwscript/test_scripts.cpp | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index b40fdd9b29..258eef3ae7 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -129,6 +129,10 @@ if onactivate ; do something endif +End)mwscript"; + + const std::string sIssue4061 = R"mwscript(Begin 01_Rz_neuvazhay-koryto2 + End)mwscript"; const std::string sIssue4451 = R"mwscript(Begin, GlassDisplayScript @@ -173,6 +177,12 @@ End)mwscript"; StartScript kal_S_Pub_Jejubãr_Faraminos +End)mwscript"; + + const std::string sIssue4803 = R"mwscript( +-- ++-Begin issue4803 + End)mwscript"; const std::string sIssue4867 = R"mwscript(Begin issue4867 @@ -200,6 +210,21 @@ End Begin)mwscript"; setscale "0.3" +End)mwscript"; + + const std::string sIssue5345 = R"mwscript(Begin issue5345 + +StartScript DN_MinionDrain_s" + +End)mwscript"; + + const std::string sIssue6066 = R"mwscript(Begin issue6066 +addtopic "return" + +End)mwscript"; + + const std::string sIssue6282 = R"mwscript(Begin 11AA_LauraScript7.5 + End)mwscript"; TEST_F(MWScriptTest, mwscript_test_invalid) @@ -316,6 +341,11 @@ End)mwscript"; EXPECT_FALSE(!compile(sIssue3725)); } + TEST_F(MWScriptTest, mwscript_test_4061) + { + EXPECT_FALSE(!compile(sIssue4061)); + } + TEST_F(MWScriptTest, mwscript_test_4451) { EXPECT_FALSE(!compile(sIssue4451)); @@ -357,6 +387,11 @@ End)mwscript"; EXPECT_FALSE(!compile(sIssue4598)); } + TEST_F(MWScriptTest, mwscript_test_4803) + { + EXPECT_FALSE(!compile(sIssue4803)); + } + TEST_F(MWScriptTest, mwscript_test_4867) { EXPECT_FALSE(!compile(sIssue4867)); @@ -378,4 +413,21 @@ End)mwscript"; registerExtensions(); EXPECT_FALSE(!compile(sIssue5097)); } + + TEST_F(MWScriptTest, mwscript_test_5345) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue5345)); + } + + TEST_F(MWScriptTest, mwscript_test_6066) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue6066)); + } + + TEST_F(MWScriptTest, mwscript_test_6282) + { + EXPECT_FALSE(!compile(sIssue6282)); + } } \ No newline at end of file From b3208f4066b63acbfe30b40be4ab3b9f88ce669c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Oct 2021 21:44:08 +0200 Subject: [PATCH 1586/2859] Add more issues and a forum thread --- .../mwscript/test_scripts.cpp | 90 +++++++++++++++++++ .../openmw_test_suite/mwscript/test_utils.hpp | 4 +- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index 258eef3ae7..c7b7af6163 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -108,6 +108,33 @@ set c to ( a - 1 ) set d to ( b * c ) set e to ( d / a ) +End)mwscript"; + +// https://forum.openmw.org/viewtopic.php?f=6&t=2262 + const std::string sScript4 = R"mwscript(Begin scripting_once_again + +player -> addSpell "fire_bite", 645 + +PositionCell "Rabenfels, Taverne" 4480.000 3968.000 15820.000 0 + +End)mwscript"; + + const std::string sIssue1430 = R"mwscript(Begin issue1430 + +short var +If ( menumode == 1 ) + Player->AddItem "fur_boots", 1 + Player->Equip "iron battle axe", 1 + player->addspell "fire bite", 645 + player->additem "ring_keley", 1, +endif + +End)mwscript"; + + const std::string sIssue1767 = R"mwscript(Begin issue1767 + +player->GetPcRank "temple" + End)mwscript"; const std::string sIssue3006 = R"mwscript(Begin issue3006 @@ -225,6 +252,18 @@ End)mwscript"; const std::string sIssue6282 = R"mwscript(Begin 11AA_LauraScript7.5 +End)mwscript"; + + const std::string sIssue6363 = R"mwscript(Begin issue6363 + +short 1 + +if ( "1" == 1 ) + PositionCell 0 1 2 3 4 5 "Morrowland" +endif + +set 1 to 42 + End)mwscript"; TEST_F(MWScriptTest, mwscript_test_invalid) @@ -317,6 +356,24 @@ End)mwscript"; } } + TEST_F(MWScriptTest, mwscript_test_forum_thread) + { + registerExtensions(); + EXPECT_FALSE(!compile(sScript4)); + } + + TEST_F(MWScriptTest, mwscript_test_1430) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue1430)); + } + + TEST_F(MWScriptTest, mwscript_test_1767) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue1767)); + } + TEST_F(MWScriptTest, mwscript_test_3006) { if(auto script = compile(sIssue3006)) @@ -430,4 +487,37 @@ End)mwscript"; { EXPECT_FALSE(!compile(sIssue6282)); } + + TEST_F(MWScriptTest, mwscript_test_6363) + { + registerExtensions(); + if(const auto script = compile(sIssue6363)) + { + class PositionCell : public Interpreter::Opcode0 + { + bool& mRan; + public: + PositionCell(bool& ran) : mRan(ran) {} + + void execute(Interpreter::Runtime& runtime) + { + mRan = true; + } + }; + bool ran = false; + installOpcode(Compiler::Transformation::opcodePositionCell, new PositionCell(ran)); + TestInterpreterContext context; + context.setLocalShort(0, 0); + run(*script, context); + EXPECT_FALSE(ran); + ran = false; + context.setLocalShort(0, 1); + run(*script, context); + EXPECT_TRUE(ran); + } + else + { + FAIL(); + } + } } \ No newline at end of file diff --git a/apps/openmw_test_suite/mwscript/test_utils.hpp b/apps/openmw_test_suite/mwscript/test_utils.hpp index 53dfe93ad6..1d0f39d2b0 100644 --- a/apps/openmw_test_suite/mwscript/test_utils.hpp +++ b/apps/openmw_test_suite/mwscript/test_utils.hpp @@ -20,6 +20,8 @@ #include #include +#include + namespace { class TestCompilerContext : public Compiler::Context @@ -28,7 +30,7 @@ namespace bool canDeclareLocals() const override { return true; } char getGlobalType(const std::string& name) const override { return ' '; } std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } - bool isId(const std::string& name) const override { return name == "player"; } + bool isId(const std::string& name) const override { return Misc::StringUtils::ciEqual(name, "player"); } bool isJournalId(const std::string& name) const override { return false; } }; From 319d30fb85992cbd6610028fba5f587604f0a446 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Oct 2021 22:02:58 +0200 Subject: [PATCH 1587/2859] Add AddTopic testing --- apps/openmw_test_suite/mwscript/test_scripts.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index c7b7af6163..f6ffe0def8 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -156,6 +156,13 @@ if onactivate ; do something endif +End)mwscript"; + + const std::string sIssue3846 = R"mwscript(Begin issue3846 + +Addtopic -spells... +Addtopic -magicka... + End)mwscript"; const std::string sIssue4061 = R"mwscript(Begin 01_Rz_neuvazhay-koryto2 @@ -398,6 +405,12 @@ End)mwscript"; EXPECT_FALSE(!compile(sIssue3725)); } + TEST_F(MWScriptTest, mwscript_test_3846) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue3846)); + } + TEST_F(MWScriptTest, mwscript_test_4061) { EXPECT_FALSE(!compile(sIssue4061)); From 3c5a50cf904d1a1a9541b4daa423a23a28f4636e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 30 Oct 2021 12:09:02 +0200 Subject: [PATCH 1588/2859] Add issues from Redmine --- .../mwscript/test_scripts.cpp | 202 +++++++++++++++++- .../openmw_test_suite/mwscript/test_utils.hpp | 1 - 2 files changed, 197 insertions(+), 6 deletions(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index f6ffe0def8..0facafbe87 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -117,6 +117,36 @@ player -> addSpell "fire_bite", 645 PositionCell "Rabenfels, Taverne" 4480.000 3968.000 15820.000 0 +End)mwscript"; + + const std::string sIssue587 = R"mwscript(Begin stalresetScript + +End stalreset Script)mwscript"; + + const std::string sIssue677 = R"mwscript(Begin _ase_dtree_dtree-owls + +End)mwscript"; + + const std::string sIssue685 = R"mwscript(Begin issue685 + +Choice: "Sicher. Hier, nehmt." 1 "Nein, ich denke nicht. Tut mir Leid." 2 +StartScript GetPCGold + +End)mwscript"; + + const std::string sIssue694 = R"mwscript(Begin issue694 + +float timer + +if ( timer < .1 ) +endif + +End)mwscript"; + + const std::string sIssue1062 = R"mwscript(Begin issue1026 + +short end + End)mwscript"; const std::string sIssue1430 = R"mwscript(Begin issue1430 @@ -129,12 +159,58 @@ If ( menumode == 1 ) player->additem "ring_keley", 1, endif +End)mwscript"; + + const std::string sIssue1593 = R"mwscript(Begin changeWater_-550_400 + +End)mwscript"; + + const std::string sIssue1730 = R"mwscript(Begin 4LOM_Corprusarium_Guards + End)mwscript"; const std::string sIssue1767 = R"mwscript(Begin issue1767 player->GetPcRank "temple" +End)mwscript"; + + const std::string sIssue2206 = R"mwscript(Begin issue2206 + +Choice ."Sklavin kaufen." 1 "Lebt wohl." 2 +Choice Choice "Insister pour qu’il vous réponde." 6 "Le prier de vous accorder un peu de son temps." 6 " Le menacer de révéler qu'il prélève sa part sur les bénéfices de la mine d’ébonite." 7 + +End)mwscript"; + + const std::string sIssue2207 = R"mwscript(Begin issue2207 + +PositionCell -35 –473 -248 0 "Skaal-Dorf, Die Große Halle" + +End)mwscript"; + + const std::string sIssue2794 = R"mwscript(Begin issue2794 + +if ( player->"getlevel" == 1 ) + ; do something +endif + +End)mwscript"; + + const std::string sIssue2830 = R"mwscript(Begin issue2830 + +AddItem "if" 1 +AddItem "endif" 1 +GetItemCount "begin" + +End)mwscript"; + + const std::string sIssue2991 = R"mwscript(Begin issue2991 + +MessageBox "OnActivate" +messagebox "messagebox" +messagebox "if" +messagebox "tcl" + End)mwscript"; const std::string sIssue3006 = R"mwscript(Begin issue3006 @@ -156,6 +232,18 @@ if onactivate ; do something endif +End)mwscript"; + + const std::string sIssue3836 = R"mwscript(Begin issue3836 + +MessageBox " Membership Level: %.0f +Account Balance: %.0f +Your Gold: %.0f +Interest Rate: %.3f +Service Charge Rate: %.3f +Total Service Charges: %.0f +Total Interest Earned: %.0f " Membership BankAccount YourGold InterestRate ServiceRate TotalServiceCharges TotalInterestEarned + End)mwscript"; const std::string sIssue3846 = R"mwscript(Begin issue3846 @@ -292,7 +380,7 @@ End)mwscript"; { registerExtensions(); bool failed = true; - if(auto script = compile(sScript2)) + if(const auto script = compile(sScript2)) { class AddTopic : public Interpreter::Opcode0 { @@ -320,7 +408,7 @@ End)mwscript"; TEST_F(MWScriptTest, mwscript_test_math) { - if(auto script = compile(sScript3)) + if(const auto script = compile(sScript3)) { struct Algorithm { @@ -369,21 +457,94 @@ End)mwscript"; EXPECT_FALSE(!compile(sScript4)); } + TEST_F(MWScriptTest, mwscript_test_587) + { + EXPECT_FALSE(!compile(sIssue587)); + } + + TEST_F(MWScriptTest, mwscript_test_677) + { + EXPECT_FALSE(!compile(sIssue677)); + } + + TEST_F(MWScriptTest, mwscript_test_685) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue685)); + } + + TEST_F(MWScriptTest, mwscript_test_694) + { + EXPECT_FALSE(!compile(sIssue694)); + } + + TEST_F(MWScriptTest, mwscript_test_1062) + { + if(const auto script = compile(sIssue1062)) + { + EXPECT_EQ(script->mLocals.getIndex("end"), 0); + } + else + { + FAIL(); + } + } + TEST_F(MWScriptTest, mwscript_test_1430) { registerExtensions(); EXPECT_FALSE(!compile(sIssue1430)); } + TEST_F(MWScriptTest, mwscript_test_1593) + { + EXPECT_FALSE(!compile(sIssue1593)); + } + + TEST_F(MWScriptTest, mwscript_test_1730) + { + EXPECT_FALSE(!compile(sIssue1730)); + } + TEST_F(MWScriptTest, mwscript_test_1767) { registerExtensions(); EXPECT_FALSE(!compile(sIssue1767)); } + TEST_F(MWScriptTest, mwscript_test_2206) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue2206)); + } + + TEST_F(MWScriptTest, mwscript_test_2207) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue2207)); + } + + TEST_F(MWScriptTest, mwscript_test_2794) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue2794)); + } + + TEST_F(MWScriptTest, mwscript_test_2830) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue2830)); + } + + TEST_F(MWScriptTest, mwscript_test_2991) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue2991)); + } + TEST_F(MWScriptTest, mwscript_test_3006) { - if(auto script = compile(sIssue3006)) + if(const auto script = compile(sIssue3006)) { TestInterpreterContext context; context.setLocalShort(0, 0); @@ -405,10 +566,41 @@ End)mwscript"; EXPECT_FALSE(!compile(sIssue3725)); } + TEST_F(MWScriptTest, mwscript_test_3836) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue3836)); + } + TEST_F(MWScriptTest, mwscript_test_3846) { registerExtensions(); - EXPECT_FALSE(!compile(sIssue3846)); + if(const auto script = compile(sIssue3846)) + { + std::vector topics = { "-spells...", "-magicka..." }; + class AddTopic : public Interpreter::Opcode0 + { + std::vector& mTopics; + public: + AddTopic(std::vector& topics) : mTopics(topics) {} + + void execute(Interpreter::Runtime& runtime) + { + const auto topic = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + EXPECT_EQ(topic, mTopics[0]); + mTopics.erase(mTopics.begin()); + } + }; + installOpcode(Compiler::Dialogue::opcodeAddTopic, new AddTopic(topics)); + TestInterpreterContext context; + run(*script, context); + EXPECT_TRUE(topics.empty()); + } + else + { + FAIL(); + } } TEST_F(MWScriptTest, mwscript_test_4061) @@ -423,7 +615,7 @@ End)mwscript"; TEST_F(MWScriptTest, mwscript_test_4597) { - if(auto script = compile(sIssue4597)) + if(const auto script = compile(sIssue4597)) { TestInterpreterContext context; for(int a = 0; a < 100; ++a) diff --git a/apps/openmw_test_suite/mwscript/test_utils.hpp b/apps/openmw_test_suite/mwscript/test_utils.hpp index 1d0f39d2b0..f29cb7bb89 100644 --- a/apps/openmw_test_suite/mwscript/test_utils.hpp +++ b/apps/openmw_test_suite/mwscript/test_utils.hpp @@ -31,7 +31,6 @@ namespace char getGlobalType(const std::string& name) const override { return ' '; } std::pair getMemberType(const std::string& name, const std::string& id) const override { return {' ', false}; } bool isId(const std::string& name) const override { return Misc::StringUtils::ciEqual(name, "player"); } - bool isJournalId(const std::string& name) const override { return false; } }; class TestErrorHandler : public Compiler::ErrorHandler From ae08f942d5142ff1c3e05f0a3a326732e87021a0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 30 Oct 2021 12:23:14 +0200 Subject: [PATCH 1589/2859] Test binary operators --- .../mwscript/test_scripts.cpp | 124 ++++++++++++++++-- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index 0facafbe87..79db3bb414 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -173,6 +173,49 @@ End)mwscript"; player->GetPcRank "temple" +End)mwscript"; + + const std::string sIssue2185 = R"mwscript(Begin issue2185 + +short a +short b +short eq +short gte +short lte +short ne + +set eq to 0 +if ( a == b ) + set eq to ( eq + 1 ) +endif +if ( a = = b ) + set eq to ( eq + 1 ) +endif + +set gte to 0 +if ( a >= b ) + set gte to ( gte + 1 ) +endif +if ( a > = b ) + set gte to ( gte + 1 ) +endif + +set lte to 0 +if ( a <= b ) + set lte to ( lte + 1 ) +endif +if ( a < = b ) + set lte to ( lte + 1 ) +endif + +set ne to 0 +if ( a != b ) + set ne to ( ne + 1 ) +endif +if ( a ! = b ) + set ne to ( ne + 1 ) +endif + End)mwscript"; const std::string sIssue2206 = R"mwscript(Begin issue2206 @@ -232,6 +275,29 @@ if onactivate ; do something endif +End)mwscript"; + + const std::string sIssue3744 = R"mwscript(Begin issue3744 + +short a +short b +short c + +set c to 0 + +if ( a => b ) + set c to ( c + 1 ) +endif +if ( a =< b ) + set c to ( c + 1 ) +endif +if ( a = b ) + set c to ( c + 1 ) +endif +if ( a == b ) + set c to ( c + 1 ) +endif + End)mwscript"; const std::string sIssue3836 = R"mwscript(Begin issue3836 @@ -512,6 +578,31 @@ End)mwscript"; EXPECT_FALSE(!compile(sIssue1767)); } + TEST_F(MWScriptTest, mwscript_test_2185) + { + if(const auto script = compile(sIssue2185)) + { + TestInterpreterContext context; + for(int a = 0; a < 100; ++a) + { + for(int b = 0; b < 100; ++b) + { + context.setLocalShort(0, a); + context.setLocalShort(1, b); + run(*script, context); + EXPECT_EQ(context.getLocalShort(2), a == b ? 2 : 0); + EXPECT_EQ(context.getLocalShort(3), a >= b ? 2 : 0); + EXPECT_EQ(context.getLocalShort(4), a <= b ? 2 : 0); + EXPECT_EQ(context.getLocalShort(5), a != b ? 2 : 0); + } + } + } + else + { + FAIL(); + } + } + TEST_F(MWScriptTest, mwscript_test_2206) { registerExtensions(); @@ -566,6 +657,28 @@ End)mwscript"; EXPECT_FALSE(!compile(sIssue3725)); } + TEST_F(MWScriptTest, mwscript_test_3744) + { + if(const auto script = compile(sIssue3744)) + { + TestInterpreterContext context; + for(int a = 0; a < 100; ++a) + { + for(int b = 0; b < 100; ++b) + { + context.setLocalShort(0, a); + context.setLocalShort(1, b); + run(*script, context); + EXPECT_EQ(context.getLocalShort(2), a == b ? 4 : 0); + } + } + } + else + { + FAIL(); + } + } + TEST_F(MWScriptTest, mwscript_test_3836) { registerExtensions(); @@ -625,15 +738,8 @@ End)mwscript"; context.setLocalShort(0, a); context.setLocalShort(1, b); run(*script, context); - if(a < b) - EXPECT_EQ(context.getLocalShort(2), 3); - else - EXPECT_EQ(context.getLocalShort(2), 0); - if(a > b) - EXPECT_EQ(context.getLocalShort(3), 3); - else - EXPECT_EQ(context.getLocalShort(3), 0); - + EXPECT_EQ(context.getLocalShort(2), a < b ? 3 : 0); + EXPECT_EQ(context.getLocalShort(3), a > b ? 3 : 0); } } } From 7e6533f0f3d793e74ed38148ececa51397cc75e9 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 30 Oct 2021 17:10:16 +0000 Subject: [PATCH 1590/2859] refactors screenshot360 (#3202) With this PR we refactor screenshot360 not to manually adjust node masks. --- apps/openmw/mwrender/renderingmanager.cpp | 7 ------- apps/openmw/mwrender/screenshotmanager.cpp | 8 ++------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7b8393f690..c4ef1d9d9b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -902,15 +902,8 @@ namespace MWRender return false; } - unsigned int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); - - if (mCamera->isFirstPerson()) - mPlayerAnimation->getObjectRoot()->setNodeMask(0); - mScreenshotManager->screenshot360(image); - mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); - return true; } diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 5a047a1566..ab7d0d93f0 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -251,14 +251,13 @@ namespace MWRender osg::ref_ptr screenshotCamera(new osg::Camera); osg::ref_ptr quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0))); - quad->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); std::map defineMap; Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr fragmentShader(shaderMgr.getShader("s360_fragment.glsl", defineMap,osg::Shader::FRAGMENT)); osg::ref_ptr vertexShader(shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); - osg::ref_ptr stateset = new osg::StateSet; + osg::ref_ptr stateset = quad->getOrCreateStateSet(); osg::ref_ptr program(new osg::Program); program->addShader(fragmentShader); @@ -269,9 +268,6 @@ namespace MWRender stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); - quad->setStateSet(stateset); - quad->setUpdateCallback(nullptr); - screenshotCamera->addChild(quad); renderCameraToImage(screenshotCamera, image, screenshotW, screenshotH); @@ -347,7 +343,7 @@ namespace MWRender rttCamera->addChild(mWater->getReflectionNode()); rttCamera->addChild(mWater->getRefractionNode()); - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson)); rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); From 7d34149adc56f2d924795200c3b5b408fff56517 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 30 Oct 2021 17:19:58 +0000 Subject: [PATCH 1591/2859] consolidates createUnlitMaterial (#3203) With this PR we remove a few duplicate lines of code by adding a parameter to `createUnlitMaterial`. --- apps/openmw/mwrender/skyutil.cpp | 24 +++++++++--------------- apps/openmw/mwrender/skyutil.hpp | 2 +- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index 8229fa5925..0e2955333f 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -93,26 +93,20 @@ namespace namespace MWRender { - osg::ref_ptr createAlphaTrackingUnlitMaterial() + osg::ref_ptr createUnlitMaterial(osg::Material::ColorMode colorMode) { osg::ref_ptr mat = new osg::Material; mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mat->setColorMode(osg::Material::DIFFUSE); + mat->setColorMode(colorMode); return mat; } - osg::ref_ptr createUnlitMaterial() + osg::ref_ptr createAlphaTrackingUnlitMaterial() { - osg::ref_ptr mat = new osg::Material; - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mat->setColorMode(osg::Material::OFF); - return mat; + return createUnlitMaterial(osg::Material::DIFFUSE); } class SunUpdater : public SceneUtil::StateSetUpdater @@ -126,7 +120,7 @@ namespace MWRender void setDefaults(osg::StateSet* stateset) override { - stateset->setAttributeAndModes(MWRender::createUnlitMaterial()); + stateset->setAttributeAndModes(createUnlitMaterial()); } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override @@ -188,7 +182,7 @@ namespace MWRender if (visibleRatio < fadeThreshold) { float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; - osg::ref_ptr mat (MWRender::createUnlitMaterial()); + osg::ref_ptr mat (createUnlitMaterial()); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); stateset = new osg::StateSet; stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); @@ -283,7 +277,7 @@ namespace MWRender { osg::ref_ptr stateset = new osg::StateSet; - osg::ref_ptr mat = MWRender::createUnlitMaterial(); + osg::ref_ptr mat = createUnlitMaterial(); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); @@ -364,7 +358,7 @@ namespace MWRender stateset->addUniform(new osg::Uniform("atmosphereFade", osg::Vec4f{})); stateset->addUniform(new osg::Uniform("diffuseMap", 0)); stateset->addUniform(new osg::Uniform("maskMap", 1)); - stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } else { @@ -386,7 +380,7 @@ namespace MWRender texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency stateset->setTextureAttributeAndModes(1, texEnv2); - stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index ae04c88d23..c2272143a0 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -104,7 +104,7 @@ namespace MWRender }; osg::ref_ptr createAlphaTrackingUnlitMaterial(); - osg::ref_ptr createUnlitMaterial(); + osg::ref_ptr createUnlitMaterial(osg::Material::ColorMode colorMode = osg::Material::OFF); class OcclusionCallback { From 1329f22186d8c2de2d17be57a87ce4c020cbbdeb Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 30 Oct 2021 17:27:57 +0000 Subject: [PATCH 1592/2859] refactors MWWorld::Store maps (#3197) With this PR we clean up `MWWorld::Store` maps according to the approach of PR #3184. --- apps/openmw/mwworld/esmstore.cpp | 13 ++-- apps/openmw/mwworld/esmstore.hpp | 14 ++-- apps/openmw/mwworld/store.cpp | 121 ++++++++++--------------------- apps/openmw/mwworld/store.hpp | 16 ++-- 4 files changed, 57 insertions(+), 107 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 66aba9139e..3cedcd457a 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -61,7 +61,7 @@ namespace } } - std::vector getNPCsToReplace(const MWWorld::Store& factions, const MWWorld::Store& classes, const std::map& npcs) + std::vector getNPCsToReplace(const MWWorld::Store& factions, const MWWorld::Store& classes, const std::unordered_map& npcs) { // Cache first class from store - we will use it if current class is not found std::string defaultCls; @@ -114,8 +114,8 @@ namespace // Custom enchanted items can reference scripts that no longer exist, this doesn't necessarily mean the base item no longer exists however. // So instead of removing the item altogether, we're only removing the script. - template - void removeMissingScripts(const MWWorld::Store& scripts, std::map& items) + template + void removeMissingScripts(const MWWorld::Store& scripts, MapT& items) { for(auto& [id, item] : items) { @@ -324,7 +324,6 @@ void ESMStore::countRecords() if (value.mRefID != deletedRefID) { std::string& refId = refIDs[value.mRefID]; - Misc::StringUtils::lowerCaseInPlace(refId); ++mRefCount[std::move(refId)]; } }; @@ -333,8 +332,7 @@ void ESMStore::countRecords() int ESMStore::getRefCount(const std::string& id) const { - const std::string lowerId = Misc::StringUtils::lowerCase(id); - auto it = mRefCount.find(lowerId); + auto it = mRefCount.find(id); if(it == mRefCount.end()) return 0; return it->second; @@ -533,9 +531,8 @@ void ESMStore::removeMissingObjects(Store& store) throw std::runtime_error ("Invalid player record (race or class unavailable"); } - std::pair, bool> ESMStore::getSpellList(const std::string& originalId) const + std::pair, bool> ESMStore::getSpellList(const std::string& id) const { - const std::string id = Misc::StringUtils::lowerCase(originalId); auto result = mSpellListCache.find(id); std::shared_ptr ptr; if (result != mSpellListCache.end()) diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 22b58f77dd..d1a4942cd3 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,16 +75,17 @@ namespace MWWorld // Lookup of all IDs. Makes looking up references faster. Just // maps the id name to the record type. - std::map mIds; - std::map mStaticIds; + using IDMap = std::unordered_map; + IDMap mIds; + IDMap mStaticIds; - std::unordered_map mRefCount; + IDMap mRefCount; std::map mStores; unsigned int mDynamicCount; - mutable std::map > mSpellListCache; + mutable std::unordered_map, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> mSpellListCache; /// Validate entries in store after setup void validate(); @@ -115,10 +116,9 @@ namespace MWWorld } /// Look up the given ID in 'all'. Returns 0 if not found. - /// \note id must be in lower case. int find(const std::string &id) const { - std::map::const_iterator it = mIds.find(id); + IDMap::const_iterator it = mIds.find(id); if (it == mIds.end()) { return 0; } @@ -126,7 +126,7 @@ namespace MWWorld } int findStatic(const std::string &id) const { - std::map::const_iterator it = mStaticIds.find(id); + IDMap::const_iterator it = mStaticIds.find(id); if (it == mStaticIds.end()) { return 0; } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 963e3b78cd..32458a2e84 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -38,8 +38,8 @@ namespace MWWorld bool isDeleted = false; record.load(esm, isDeleted); - - mStatic.insert_or_assign(record.mIndex, record); + auto idx = record.mIndex; + mStatic.insert_or_assign(idx, std::move(record)); } template int IndexedStore::getSize() const @@ -98,13 +98,11 @@ namespace MWWorld template const T *Store::search(const std::string &id) const { - std::string idLower = Misc::StringUtils::lowerCase(id); - - typename Dynamic::const_iterator dit = mDynamic.find(idLower); + typename Dynamic::const_iterator dit = mDynamic.find(id); if (dit != mDynamic.end()) return &dit->second; - typename std::map::const_iterator it = mStatic.find(idLower); + typename Static::const_iterator it = mStatic.find(id); if (it != mStatic.end()) return &(it->second); @@ -113,8 +111,7 @@ namespace MWWorld template const T *Store::searchStatic(const std::string &id) const { - std::string idLower = Misc::StringUtils::lowerCase(id); - typename std::map::const_iterator it = mStatic.find(idLower); + typename Static::const_iterator it = mStatic.find(id); if (it != mStatic.end()) return &(it->second); @@ -159,7 +156,7 @@ namespace MWWorld bool isDeleted = false; record.load(esm, isDeleted); - Misc::StringUtils::lowerCaseInPlace(record.mId); + Misc::StringUtils::lowerCaseInPlace(record.mId); // TODO: remove this line once we have ported our remaining code base to lowercase on lookup std::pair inserted = mStatic.insert_or_assign(record.mId, record); if (inserted.second) @@ -206,14 +203,13 @@ namespace MWWorld template T *Store::insert(const T &item, bool overrideOnly) { - std::string id = Misc::StringUtils::lowerCase(item.mId); if(overrideOnly) { - auto it = mStatic.find(id); + auto it = mStatic.find(item.mId); if(it == mStatic.end()) return nullptr; } - std::pair result = mDynamic.insert_or_assign(id, item); + std::pair result = mDynamic.insert_or_assign(item.mId, item); T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); @@ -222,8 +218,7 @@ namespace MWWorld template T *Store::insertStatic(const T &item) { - std::string id = Misc::StringUtils::lowerCase(item.mId); - std::pair result = mStatic.insert_or_assign(id, item); + std::pair result = mStatic.insert_or_assign(item.mId, item); T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); @@ -232,9 +227,7 @@ namespace MWWorld template bool Store::eraseStatic(const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); - - typename std::map::iterator it = mStatic.find(idLower); + typename Static::iterator it = mStatic.find(id); if (it != mStatic.end()) { // delete from the static part of mShared @@ -242,7 +235,7 @@ namespace MWWorld typename std::vector::iterator end = sharedIter + mStatic.size(); while (sharedIter != mShared.end() && sharedIter != end) { - if((*sharedIter)->mId == idLower) { + if(Misc::StringUtils::ciEqual((*sharedIter)->mId, id)) { mShared.erase(sharedIter); break; } @@ -257,17 +250,13 @@ namespace MWWorld template bool Store::erase(const std::string &id) { - std::string key = Misc::StringUtils::lowerCase(id); - typename Dynamic::iterator it = mDynamic.find(key); - if (it == mDynamic.end()) { + if (!mDynamic.erase(id)) return false; - } - mDynamic.erase(it); // have to reinit the whole shared part assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - for (it = mDynamic.begin(); it != mDynamic.end(); ++it) { + for (auto it = mDynamic.begin(); it != mDynamic.end(); ++it) { mShared.push_back(&it->second); } return true; @@ -353,12 +342,8 @@ namespace MWWorld ESM::LandTexture* tex = const_cast(search(lt.mIndex, i)); if (tex) { - const std::string texId = Misc::StringUtils::lowerCase(tex->mId); - const std::string ltId = Misc::StringUtils::lowerCase(lt.mId); - if (texId == ltId) - { + if (Misc::StringUtils::ciEqual(tex->mId, lt.mId)) tex->mTexture = lt.mTexture; - } } } @@ -367,9 +352,10 @@ namespace MWWorld ltexl.resize(lt.mIndex+1); // Store it - ltexl[lt.mIndex] = lt; + auto idx = lt.mIndex; + ltexl[idx] = std::move(lt); - return RecordId(lt.mId, isDeleted); + return RecordId(ltexl[idx].mId, isDeleted); } RecordId Store::load(ESM::ESMReader &esm) { @@ -503,16 +489,12 @@ namespace MWWorld } const ESM::Cell *Store::search(const std::string &id) const { - ESM::Cell cell; - cell.mName = Misc::StringUtils::lowerCase(id); - - std::map::const_iterator it = mInt.find(cell.mName); - + DynamicInt::const_iterator it = mInt.find(id); if (it != mInt.end()) { return &(it->second); } - DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); + DynamicInt::const_iterator dit = mDynamicInt.find(id); if (dit != mDynamicInt.end()) { return &dit->second; } @@ -521,48 +503,34 @@ namespace MWWorld } const ESM::Cell *Store::search(int x, int y) const { - ESM::Cell cell; - cell.mData.mX = x; - cell.mData.mY = y; - std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { + if (it != mExt.end()) return &(it->second); - } DynamicExt::const_iterator dit = mDynamicExt.find(key); - if (dit != mDynamicExt.end()) { + if (dit != mDynamicExt.end()) return &dit->second; - } return nullptr; } const ESM::Cell *Store::searchStatic(int x, int y) const { - ESM::Cell cell; - cell.mData.mX = x; - cell.mData.mY = y; - - std::pair key(x, y); - DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { + DynamicExt::const_iterator it = mExt.find(std::make_pair(x,y)); + if (it != mExt.end()) return &(it->second); - } return nullptr; } const ESM::Cell *Store::searchOrCreate(int x, int y) { std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { + if (it != mExt.end()) return &(it->second); - } DynamicExt::const_iterator dit = mDynamicExt.find(key); - if (dit != mDynamicExt.end()) { + if (dit != mDynamicExt.end()) return &dit->second; - } ESM::Cell newCell; newCell.mData.mX = x; @@ -625,12 +593,11 @@ namespace MWWorld // Load the (x,y) coordinates of the cell, if it is an exterior cell, // so we can find the cell we need to merge with cell.loadNameAndData(esm, isDeleted); - std::string idLower = Misc::StringUtils::lowerCase(cell.mName); if(cell.mData.mFlags & ESM::Cell::Interior) { // Store interior cell by name, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(idLower)); + ESM::Cell *oldcell = const_cast(search(cell.mName)); if (oldcell) { // merge new cell into old cell // push the new references on the list of references to manage (saveContext = true) @@ -642,7 +609,7 @@ namespace MWWorld // spawn a new cell cell.loadCell(esm, true); - mInt[idLower] = cell; + mInt[cell.mName] = cell; } } else @@ -780,27 +747,19 @@ namespace MWWorld const std::string cellType = (cell.isExterior()) ? "exterior" : "interior"; throw std::runtime_error("Failed to create " + cellType + " cell"); } - ESM::Cell *ptr; if (cell.isExterior()) { std::pair key(cell.getGridX(), cell.getGridY()); // duplicate insertions are avoided by search(ESM::Cell &) - std::pair result = - mDynamicExt.insert(std::make_pair(key, cell)); - - ptr = &result.first->second; - mSharedExt.push_back(ptr); + DynamicExt::iterator result = mDynamicExt.emplace(key, cell).first; + mSharedExt.push_back(&result->second); + return &result->second; } else { - std::string key = Misc::StringUtils::lowerCase(cell.mName); - // duplicate insertions are avoided by search(ESM::Cell &) - std::pair result = - mDynamicInt.insert(std::make_pair(key, cell)); - - ptr = &result.first->second; - mSharedInt.push_back(ptr); + DynamicInt::iterator result = mDynamicInt.emplace(cell.mName, cell).first; + mSharedInt.push_back(&result->second); + return &result->second; } - return ptr; } bool Store::erase(const ESM::Cell &cell) { @@ -811,8 +770,7 @@ namespace MWWorld } bool Store::erase(const std::string &id) { - std::string key = Misc::StringUtils::lowerCase(id); - DynamicInt::iterator it = mDynamicInt.find(key); + DynamicInt::iterator it = mDynamicInt.find(id); if (it == mDynamicInt.end()) { return false; @@ -1062,12 +1020,11 @@ namespace MWWorld dialogue.loadId(esm); - std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); - std::map::iterator found = mStatic.find(idLower); + Static::iterator found = mStatic.find(dialogue.mId); if (found == mStatic.end()) { dialogue.loadData(esm, isDeleted); - mStatic.insert(std::make_pair(idLower, dialogue)); + mStatic.emplace(dialogue.mId, dialogue); } else { @@ -1081,11 +1038,7 @@ namespace MWWorld template<> bool Store::eraseStatic(const std::string &id) { - auto it = mStatic.find(Misc::StringUtils::lowerCase(id)); - - if (it != mStatic.end()) - mStatic.erase(it); - + mStatic.erase(id); return true; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 4b1d648703..dbb28258ef 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "recordcmp.hpp" @@ -147,14 +148,13 @@ namespace MWWorld template class Store : public StoreBase { - std::map mStatic; - std::vector mShared; // Preserves the record order as it came from the content files (this + typedef std::unordered_map Static; + Static mStatic; + std::vector mShared; // Preserves the record order as it came from the content files (this // is relevant for the spell autocalc code and selection order // for heads/hairs in the character creation) - std::map mDynamic; - - typedef std::map Dynamic; - typedef std::map Static; + typedef std::unordered_map Dynamic; + Dynamic mDynamic; friend class ESMStore; @@ -294,7 +294,7 @@ namespace MWWorld } }; - typedef std::map DynamicInt; + typedef std::unordered_map DynamicInt; typedef std::map, ESM::Cell, DynamicExtCmp> DynamicExt; DynamicInt mInt; @@ -354,7 +354,7 @@ namespace MWWorld class Store : public StoreBase { private: - typedef std::map Interior; + typedef std::unordered_map Interior; typedef std::map, ESM::Pathgrid> Exterior; Interior mInt; From 8c3b00164ebc5783c54050550d8e661e3358b028 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 20 Oct 2021 09:42:18 -0700 Subject: [PATCH 1593/2859] soft particles --- apps/openmw/mwrender/postprocessor.cpp | 79 ++++++++++++++++--- apps/openmw/mwrender/postprocessor.hpp | 2 + apps/openmw/mwrender/renderingmanager.cpp | 13 +++ components/nifosg/nifloader.cpp | 1 + components/resource/scenemanager.cpp | 6 ++ components/resource/scenemanager.hpp | 4 + components/shader/shadervisitor.cpp | 20 +++++ components/shader/shadervisitor.hpp | 4 + .../reference/modding/settings/shaders.rst | 13 +++ files/settings-default.cfg | 2 + files/shaders/CMakeLists.txt | 1 + files/shaders/objects_fragment.glsl | 9 +++ files/shaders/softparticles.glsl | 32 ++++++++ 13 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 files/shaders/softparticles.glsl diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 1ee5745cb4..7405098a03 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -93,6 +93,41 @@ namespace MWRender::PostProcessor* mPostProcessor; }; + + // Copies the currently bound depth attachment to a new texture so drawables in transparent renderbin can safely sample from depth. + class OpaqueDepthCopyCallback : public osgUtil::RenderBin::DrawCallback + { + public: + OpaqueDepthCopyCallback(osg::ref_ptr opaqueDepthTex, osg::ref_ptr sourceFbo) + : mOpaqueDepthFbo(new osg::FrameBufferObject) + , mSourceFbo(sourceFbo) + , mOpaqueDepthTex(opaqueDepthTex) + { + mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER, osg::FrameBufferAttachment(opaqueDepthTex)); + } + + void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override + { + if (bin->getStage()->getFrameBufferObject() == mSourceFbo) + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions* ext = state.get(); + + mSourceFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); + mOpaqueDepthFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + ext->glBlitFramebuffer(0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), 0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); + + mSourceFbo->apply(state); + } + + bin->drawImplementation(renderInfo, previous); + } + private: + osg::ref_ptr mOpaqueDepthFbo; + osg::ref_ptr mSourceFbo; + osg::ref_ptr mOpaqueDepthTex; + }; } namespace MWRender @@ -103,7 +138,9 @@ namespace MWRender , mDepthFormat(GL_DEPTH_COMPONENT24) , mRendering(rendering) { - if (!SceneUtil::getReverseZ()) + bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); + + if (!SceneUtil::getReverseZ() && !softParticles) return; osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); @@ -124,17 +161,22 @@ namespace MWRender return; } - if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) - mDepthFormat = GL_DEPTH_COMPONENT32F; - else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) - mDepthFormat = GL_DEPTH_COMPONENT32F_NV; - else + if (SceneUtil::getReverseZ()) { - // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. - // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no - // benefits if no floating point depth formats are supported. - Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; - return; + if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) + mDepthFormat = GL_DEPTH_COMPONENT32F; + else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) + mDepthFormat = GL_DEPTH_COMPONENT32F_NV; + else + { + // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. + // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no + // benefits if no floating point depth formats are supported. + Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; + + if (!softParticles) + return; + } } int width = viewer->getCamera()->getViewport()->width(); @@ -165,6 +207,12 @@ namespace MWRender mDepthTex->dirtyTextureObject(); mSceneTex->dirtyTextureObject(); + if (mOpaqueDepthTex) + { + mOpaqueDepthTex->setTextureSize(width, height); + mOpaqueDepthTex->dirtyTextureObject(); + } + int samples = Settings::Manager::getInt("antialiasing", "Video"); mFbo = new osg::FrameBufferObject; @@ -186,6 +234,9 @@ namespace MWRender if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY")) mFirstPersonDepthRBProxy = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); + if (Settings::Manager::getBool("soft particles", "Shaders")) + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(new OpaqueDepthCopyCallback(mOpaqueDepthTex, mMsaaFbo ? mMsaaFbo : mFbo)); + mViewer->getCamera()->resize(width, height); mHUDCamera->resize(width, height); mRendering.updateProjectionMatrix(); @@ -204,6 +255,12 @@ namespace MWRender mDepthTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mDepthTex->setResizeNonPowerOfTwoHint(false); + if (Settings::Manager::getBool("soft particles", "Shaders")) + { + mOpaqueDepthTex = new osg::Texture2D(*mDepthTex); + mOpaqueDepthTex->setName("opaqueTexMap"); + } + mSceneTex = new osg::Texture2D; mSceneTex->setTextureSize(width, height); mSceneTex->setSourceFormat(GL_RGB); diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 0d03d4b500..cc5128d8f9 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -26,6 +26,7 @@ namespace MWRender auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; } int getDepthFormat() { return mDepthFormat; } + osg::ref_ptr getOpaqueDepthTex() { return mOpaqueDepthTex; } void resize(int width, int height); @@ -42,6 +43,7 @@ namespace MWRender osg::ref_ptr mSceneTex; osg::ref_ptr mDepthTex; + osg::ref_ptr mOpaqueDepthTex; int mDepthFormat; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7b8393f690..a129543828 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -91,6 +91,7 @@ namespace MWRender stateset->addUniform(new osg::Uniform("linearFac", 0.f)); stateset->addUniform(new osg::Uniform("near", 0.f)); stateset->addUniform(new osg::Uniform("far", 0.f)); + stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{})); if (mUsePlayerUniforms) { stateset->addUniform(new osg::Uniform("windSpeed", 0.0f)); @@ -116,6 +117,10 @@ namespace MWRender if (uFar) uFar->set(mFar); + auto* uScreenRes = stateset->getUniform("screenRes"); + if (uScreenRes) + uScreenRes->set(mScreenRes); + if (mUsePlayerUniforms) { auto* windSpeed = stateset->getUniform("windSpeed"); @@ -148,6 +153,11 @@ namespace MWRender mFar = far; } + void setScreenRes(float width, float height) + { + mScreenRes = osg::Vec2f(width, height); + } + void setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; @@ -167,6 +177,7 @@ namespace MWRender bool mUsePlayerUniforms; float mWindSpeed; osg::Vec3f mPlayerPos; + osg::Vec2f mScreenRes; }; class StateUpdater : public SceneUtil::StateSetUpdater @@ -453,6 +464,7 @@ namespace MWRender mPostProcessor = new PostProcessor(*this, viewer, mRootNode); resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat()); + resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex()); if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(mPostProcessor->getDepthFormat())) Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it."; @@ -1166,6 +1178,7 @@ namespace MWRender mSharedUniformStateUpdater->setNear(mNearClip); mSharedUniformStateUpdater->setFar(mViewDistance); + mSharedUniformStateUpdater->setScreenRes(mViewer->getCamera()->getViewport()->width(), mViewer->getCamera()->getViewport()->height()); // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 838895eb47..a745d50eda 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1045,6 +1045,7 @@ namespace NifOsg void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); + partsys->getOrCreateStateSet(); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); const Nif::NiParticleSystemController* partctrl = nullptr; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 4184a77c54..b6608ab631 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -429,6 +429,11 @@ namespace Resource mConvertAlphaTestToAlphaToCoverage = convert; } + void SceneManager::setOpaqueDepthTex(osg::ref_ptr texture) + { + mOpaqueDepthTex = texture; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -891,6 +896,7 @@ namespace Resource shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); + shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex); return shaderVisitor; } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 85e012071d..58ae8fdb8b 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "resourcemanager.hpp" @@ -111,6 +112,8 @@ namespace Resource void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; + void setOpaqueDepthTex(osg::ref_ptr texture); + enum class UBOBinding { // If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate @@ -209,6 +212,7 @@ namespace Resource SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; GLenum mDepthFormat; + osg::ref_ptr mOpaqueDepthTex; osg::ref_ptr mSharedStateManager; mutable std::mutex mSharedStateMutex; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 9877ab1863..9b54060730 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include @@ -19,6 +21,7 @@ #include #include #include +#include #include "removedalphafunc.hpp" #include "shadermanager.hpp" @@ -554,6 +557,18 @@ namespace Shader updateAddedState(*writableUserData, addedState); } + if (auto partsys = dynamic_cast(&node)) + { + writableStateSet->setDefine("SOFT_PARTICLES", "1", osg::StateAttribute::ON); + + auto depth = SceneUtil::createDepth(); + depth->setWriteMask(false); + writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + writableStateSet->addUniform(new osg::Uniform("particleSize", partsys->getDefaultParticleTemplate().getSizeRange().maximum)); + writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); + writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); + } + std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -769,6 +784,11 @@ namespace Shader mConvertAlphaTestToAlphaToCoverage = convert; } + void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr texture) + { + mOpaqueDepthTex = texture; + } + ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 5f9739ea90..d80e697fd8 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Resource { @@ -45,6 +46,8 @@ namespace Shader void setConvertAlphaTestToAlphaToCoverage(bool convert); + void setOpaqueDepthTex(osg::ref_ptr texture); + void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -110,6 +113,7 @@ namespace Shader bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); osg::ref_ptr mProgramTemplate; + osg::ref_ptr mOpaqueDepthTex; }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 03b7805de6..c62c44e936 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -269,3 +269,16 @@ antialias alpha test Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage when :ref:`antialiasing` is on. This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. When MSAA is off, this setting will have no visible effect, but might have a performance cost. + +soft particles +------------------------ + +:Type: boolean +:Range: True/False +:Default: False + +Enables soft particles for almost all particle effects, excluding precipitation. +This technique softens the intersection between individual particles and other +opaque geometry by blending 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. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 27a9544ea6..2df6eaa0a7 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -494,6 +494,8 @@ minimum interior brightness = 0.08 # When MSAA is off, this setting will have no visible effect, but might have a performance cost. antialias alpha test = false +soft particles = true + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index d86719f318..6e19263a38 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -39,6 +39,7 @@ set(SHADER_FILES sky_vertex.glsl sky_fragment.glsl skypasses.glsl + softparticles.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 6f6cede4e4..cb435c7790 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,5 +1,6 @@ #version 120 #pragma import_defines(FORCE_OPAQUE) +#pragma import_defines(SOFT_PARTICLES) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -80,6 +81,10 @@ varying vec3 passNormal; #include "parallax.glsl" #include "alpha.glsl" +#if defined(SOFT_PARTICLES) && SOFT_PARTICLES +#include "softparticles.glsl" +#endif + void main() { #if @diffuseMap @@ -220,6 +225,10 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +#if defined(SOFT_PARTICLES) && SOFT_PARTICLES + gl_FragData[0].a *= calcSoftParticleFade(); +#endif + #if defined(FORCE_OPAQUE) && FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque gl_FragData[0].a = 1.0; diff --git a/files/shaders/softparticles.glsl b/files/shaders/softparticles.glsl new file mode 100644 index 0000000000..fa8b4de4c1 --- /dev/null +++ b/files/shaders/softparticles.glsl @@ -0,0 +1,32 @@ +uniform float near; +uniform float far; +uniform sampler2D opaqueDepthTex; +uniform vec2 screenRes; +uniform float particleSize; + +float viewDepth(float depth) +{ +#if @reverseZ + depth = 1.0 - depth; +#endif + return (near * far) / ((far - near) * depth - far); +} + +float calcSoftParticleFade() +{ + const float falloffMultiplier = 0.33; + const float contrast = 1.30; + + vec2 screenCoords = gl_FragCoord.xy / screenRes; + float sceneDepth = viewDepth(texture2D(opaqueDepthTex, screenCoords).x); + float particleDepth = viewDepth(gl_FragCoord.z); + float falloff = particleSize * falloffMultiplier; + float delta = particleDepth - sceneDepth; + + if (delta < 0.0) + discard; + + const float shift = 0.845; + + return shift * pow(clamp(delta/falloff, 0.0, 1.0), contrast); +} From 40b6bbbdf34bdc7e337bf394b987e527388a5442 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 23 Oct 2021 13:46:39 -0700 Subject: [PATCH 1594/2859] use openmw define system --- components/shader/shadervisitor.cpp | 4 +++- files/shaders/objects_fragment.glsl | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 9b54060730..2c673200f7 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -559,7 +559,7 @@ namespace Shader if (auto partsys = dynamic_cast(&node)) { - writableStateSet->setDefine("SOFT_PARTICLES", "1", osg::StateAttribute::ON); + defineMap["softParticles"] = "1"; auto depth = SceneUtil::createDepth(); depth->setWriteMask(false); @@ -568,6 +568,8 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); } + else + defineMap["softParticles"] = "0"; std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index cb435c7790..d750a4dd1f 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,6 +1,5 @@ #version 120 #pragma import_defines(FORCE_OPAQUE) -#pragma import_defines(SOFT_PARTICLES) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -81,7 +80,7 @@ varying vec3 passNormal; #include "parallax.glsl" #include "alpha.glsl" -#if defined(SOFT_PARTICLES) && SOFT_PARTICLES +#if @softParticles #include "softparticles.glsl" #endif @@ -225,7 +224,7 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if defined(SOFT_PARTICLES) && SOFT_PARTICLES +#if @softParticles gl_FragData[0].a *= calcSoftParticleFade(); #endif From 356e9d7cf0a6313417515589790f1873cce45054 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 30 Oct 2021 20:43:18 +0000 Subject: [PATCH 1595/2859] refactors osg::Callback virtual inheritance (#3200) With this PR we refactor `SceneUtil::KeyframeController` not to require `virtual osg::Callback` inheritance. I suppose such `virtual` overhead is not justified here because it negatively impacts many other classes we derive from `osg::Callback`. --- apps/openmw/mwrender/animation.cpp | 5 +++-- components/nifosg/controller.hpp | 1 + components/sceneutil/keyframe.hpp | 14 ++++++++------ components/sceneutil/nodecallback.hpp | 2 +- components/sceneutil/osgacontroller.hpp | 2 ++ 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cdc6c49ab2..91ef6cc975 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -968,8 +968,9 @@ namespace MWRender { osg::ref_ptr node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource - node->addUpdateCallback(it->second); - mActiveControllers.emplace_back(node, it->second); + osg::Callback* callback = it->second->getAsCallback(); + node->addUpdateCallback(callback); + mActiveControllers.emplace_back(node, callback); if (blendMask == 0 && node == mAccumRoot) { diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index c6311fd5fc..5d88dda1f1 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -248,6 +248,7 @@ namespace NifOsg META_Object(NifOsg, KeyframeController) osg::Vec3f getTranslation(float time) const override; + osg::Callback* getAsCallback() override { return this; } void operator() (NifOsg::MatrixTransform*, osg::NodeVisitor*); diff --git a/components/sceneutil/keyframe.hpp b/components/sceneutil/keyframe.hpp index 5be6924a09..59a87ab08e 100644 --- a/components/sceneutil/keyframe.hpp +++ b/components/sceneutil/keyframe.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include #include @@ -11,18 +11,20 @@ namespace SceneUtil { - class KeyframeController : public SceneUtil::Controller, public virtual osg::Callback + /// @note Derived classes are expected to derive from osg::Callback and implement getAsCallback(). + class KeyframeController : public SceneUtil::Controller, public virtual osg::Object { public: KeyframeController() {} KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop) - : osg::Callback(copy, copyop) - , SceneUtil::Controller(copy) - {} - META_Object(SceneUtil, KeyframeController) + : osg::Object(copy, copyop) + , SceneUtil::Controller(copy) {} virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } + + /// @note We could drop this function in favour of osg::Object::asCallback from OSG 3.6 on. + virtual osg::Callback* getAsCallback() = 0; }; /// Wrapper object containing an animation track as a ref-countable osg::Object. diff --git a/components/sceneutil/nodecallback.hpp b/components/sceneutil/nodecallback.hpp index 6f0140d64c..942cb17ded 100644 --- a/components/sceneutil/nodecallback.hpp +++ b/components/sceneutil/nodecallback.hpp @@ -13,7 +13,7 @@ namespace SceneUtil { template -class NodeCallback : public virtual osg::Callback +class NodeCallback : public osg::Callback { public: NodeCallback(){} diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 26212a3b99..893b8b1ebe 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -45,6 +45,8 @@ namespace SceneUtil META_Object(SceneUtil, OsgAnimationController) + osg::Callback* getAsCallback() override { return this; } + /// @brief Handles the location of the instance osg::Vec3f getTranslation(float time) const override; From 3c40935ec16cde3d5db8ce2fe26da63fb2d07c17 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 31 Oct 2021 11:57:33 +0100 Subject: [PATCH 1596/2859] Fix regression #6375 --- apps/openmw/mwmechanics/creaturestats.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 6710dcd734..d832c47c87 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -539,6 +539,7 @@ namespace MWMechanics state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?) state.mLastHitObject = mLastHitObject; state.mLastHitAttemptObject = mLastHitAttemptObject; + state.mRecalcDynamicStats = false; state.mDrawState = mDrawState; state.mLevel = mLevel; state.mActorId = mActorId; From d88d006984a27de79fbec9c100403d3af339132f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 31 Oct 2021 11:59:34 +0000 Subject: [PATCH 1597/2859] fixes getViewDistance (#3207) I have been informed by @akortunov that my addition of `Groundcover::getViewDistance` is not working in some cases. Investigations revealed that some `ViewData` code interacting with my additions had been quite thoroughly optimised in a way that was not sufficiently documented and interfered with the new feature. With this PR we repair `getViewDistance` while preserving such optimisations and add a necessary comment to avoid issues in the future. In addition, we now rebuild views when the global `mViewDistance` changes. --- components/terrain/quadtreeworld.cpp | 36 ++++++++++++++++------- components/terrain/quadtreeworld.hpp | 7 ++++- components/terrain/viewdata.cpp | 35 +++------------------- components/terrain/viewdata.hpp | 43 ++++++++++++++-------------- 4 files changed, 57 insertions(+), 64 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 0282eb8de1..7fc0895846 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -278,6 +278,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mViewDistance(std::numeric_limits::max()) , mMinSize(1/8.f) , mDebugTerrainChunks(debugChunks) + , mRevalidateDistance(0.f) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -346,22 +347,25 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const return lodFlags; } -void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector& chunkManagers, bool compile, float reuseDistance) +void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance) { if (!vd->hasChanged() && entry.mRenderingNode) return; - int ourLod = getVertexLod(entry.mNode, vertexLodMod); + int ourLod = getVertexLod(entry.mNode, mVertexLodMod); if (vd->hasChanged()) { // have to recompute the lodFlags in case a neighbour has changed LOD. - unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vertexLodMod, vd); + unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, mVertexLodMod, vd); if (lodFlags != entry.mLodFlags) { entry.mRenderingNode = nullptr; entry.mLodFlags = lodFlags; } + // have to revalidate chunks within a custom view distance. + if (mRevalidateDistance && entry.mNode->distance(vd->getViewPoint()) <= mRevalidateDistance + reuseDistance) + entry.mRenderingNode = nullptr; } if (!entry.mRenderingNode) @@ -372,9 +376,9 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f const osg::Vec2f& center = entry.mNode->getCenter(); bool activeGrid = (center.x() > gridbounds.x() && center.y() > gridbounds.y() && center.x() < gridbounds.z() && center.y() < gridbounds.w()); - for (QuadTreeWorld::ChunkManager* m : chunkManagers) + for (QuadTreeWorld::ChunkManager* m : mChunkManagers) { - if (m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance) + if (mRevalidateDistance && m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance) continue; osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); if (n) pat->addChild(n); @@ -398,7 +402,7 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr; for (unsigned int i=0; igetNumEntries(); ++i) { - ViewData::Entry& entry = vd->getEntry(i); + ViewDataEntry& entry = vd->getEntry(i); osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); if (!bb.valid()) continue; @@ -457,15 +461,15 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) for (unsigned int i=0; igetNumEntries(); ++i) { - ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false, mViewDataMap->getReuseDistance()); + ViewDataEntry& entry = vd->getEntry(i); + loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false, mViewDataMap->getReuseDistance()); entry.mRenderingNode->accept(nv); } if (mHeightCullCallback && isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); - vd->markUnchanged(); + vd->setChanged(false); double referenceTime = nv.getFrameStamp() ? nv.getFrameStamp()->getReferenceTime() : 0.0; if (referenceTime != 0.0) @@ -540,9 +544,9 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: const float reuseDistance = std::max(mViewDataMap->getReuseDistance(), std::abs(distanceModifier)); for (unsigned int i=startEntry; igetNumEntries() && !abort; ++i) { - ViewData::Entry& entry = vd->getEntry(i); + ViewDataEntry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true, reuseDistance); + loadRenderingNode(entry, vd, cellWorldSize, grid, true, reuseDistance); if (pass==0) reporter.addProgress(entry.mNode->getSize()); entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass } @@ -579,6 +583,8 @@ void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m) { mChunkManagers.push_back(m); mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask()); + if (m->getViewDistance()) + mRevalidateDistance = std::max(m->getViewDistance(), mRevalidateDistance); } void QuadTreeWorld::rebuildViews() @@ -586,4 +592,12 @@ void QuadTreeWorld::rebuildViews() mViewDataMap->rebuildViews(); } +void QuadTreeWorld::setViewDistance(float viewDistance) +{ + if (mViewDistance == viewDistance) + return; + mViewDistance = viewDistance; + mViewDataMap->rebuildViews(); +} + } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 3bd606d6c6..9d21d65fc5 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -16,6 +16,9 @@ namespace Terrain { class RootNode; class ViewDataMap; + class ViewData; + struct ViewDataEntry; + class DebugChunkManager; /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. @@ -30,7 +33,7 @@ namespace Terrain void enable(bool enabled) override; - void setViewDistance(float distance) override { mViewDistance = distance; } + void setViewDistance(float distance) override; void cacheCell(View *view, int x, int y) override {} /// @note Not thread safe. @@ -60,6 +63,7 @@ namespace Terrain private: void ensureQuadTreeBuilt(); + void loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance); osg::ref_ptr mRootNode; @@ -75,6 +79,7 @@ namespace Terrain float mMinSize; bool mDebugTerrainChunks; std::unique_ptr mDebugChunkManager; + float mRevalidateDistance; }; } diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index e517390b44..ae23f034a8 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -12,12 +12,10 @@ ViewData::ViewData() , mHasViewPoint(false) , mWorldUpdateRevision(0) { - } ViewData::~ViewData() { - } void ViewData::copyFrom(const ViewData& other) @@ -38,42 +36,17 @@ void ViewData::add(QuadTreeNode *node) if (index+1 > mEntries.size()) mEntries.resize(index+1); - Entry& entry = mEntries[index]; + ViewDataEntry& entry = mEntries[index]; if (entry.set(node)) mChanged = true; } -unsigned int ViewData::getNumEntries() const -{ - return mNumEntries; -} - -ViewData::Entry &ViewData::getEntry(unsigned int i) -{ - return mEntries[i]; -} - -bool ViewData::hasChanged() const -{ - return mChanged; -} - -bool ViewData::hasViewPoint() const -{ - return mHasViewPoint; -} - void ViewData::setViewPoint(const osg::Vec3f &viewPoint) { mViewPoint = viewPoint; mHasViewPoint = true; } -const osg::Vec3f& ViewData::getViewPoint() const -{ - return mViewPoint; -} - // NOTE: As a performance optimisation, we cache mRenderingNodes from previous frames here. // If this cache becomes invalid (e.g. through mWorldUpdateRevision), we need to use clear() instead of reset(). void ViewData::reset() @@ -110,14 +83,13 @@ bool ViewData::contains(QuadTreeNode *node) const return false; } -ViewData::Entry::Entry() +ViewDataEntry::ViewDataEntry() : mNode(nullptr) , mLodFlags(0) { - } -bool ViewData::Entry::set(QuadTreeNode *node) +bool ViewDataEntry::set(QuadTreeNode *node) { if (node == mNode) return false; @@ -173,6 +145,7 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); + vd->setChanged(true); needsUpdate = true; } } diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 5d814251ea..b7dbc977b1 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -13,6 +13,18 @@ namespace Terrain class QuadTreeNode; + struct ViewDataEntry + { + ViewDataEntry(); + + bool set(QuadTreeNode* node); + + QuadTreeNode* mNode; + + unsigned int mLodFlags; + osg::ref_ptr mRenderingNode; + }; + class ViewData : public View { public: @@ -31,33 +43,22 @@ namespace Terrain void copyFrom(const ViewData& other); - struct Entry - { - Entry(); - - bool set(QuadTreeNode* node); - - QuadTreeNode* mNode; - - unsigned int mLodFlags; - osg::ref_ptr mRenderingNode; - }; - - unsigned int getNumEntries() const; - - Entry& getEntry(unsigned int i); + unsigned int getNumEntries() const { return mNumEntries; } + ViewDataEntry& getEntry(unsigned int i) { return mEntries[i]; } double getLastUsageTimeStamp() const { return mLastUsageTimeStamp; } void setLastUsageTimeStamp(double timeStamp) { mLastUsageTimeStamp = timeStamp; } - /// @return Have any nodes changed since the last frame - bool hasChanged() const; - void markUnchanged() { mChanged = false; } + /// Indicates at least one mNode of mEntries has changed or the view point has moved beyond mReuseDistance. + /// @note Such changes may necessitate a revalidation of cached mRenderingNodes elsewhere depending + /// on the parameters that affect the creation of mRenderingNode. + bool hasChanged() const { return mChanged; } + void setChanged(bool changed) { mChanged = changed; } - bool hasViewPoint() const; + bool hasViewPoint() const { return mHasViewPoint; } void setViewPoint(const osg::Vec3f& viewPoint); - const osg::Vec3f& getViewPoint() const; + const osg::Vec3f& getViewPoint() const { return mViewPoint; } void setActiveGrid(const osg::Vec4i &grid) { if (grid != mActiveGrid) {mActiveGrid = grid;mEntries.clear();mNumEntries=0;} } const osg::Vec4i &getActiveGrid() const { return mActiveGrid;} @@ -66,7 +67,7 @@ namespace Terrain void setWorldUpdateRevision(int updateRevision) { mWorldUpdateRevision = updateRevision; } private: - std::vector mEntries; + std::vector mEntries; unsigned int mNumEntries; double mLastUsageTimeStamp; bool mChanged; From b9911da4c799984c639102e5dcae133bd52ce378 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 31 Oct 2021 12:03:42 +0000 Subject: [PATCH 1598/2859] applies lightMask (#3201) With this PR we apply `lightMask` to a `Transform` node we create specifically for a light. This mask will allow us to stop traversing such nodes sooner and avoid costly processing associated with `Transform` nodes in the cull visitor. --- components/sceneutil/lightutil.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 6a1a1376ec..2a5a945558 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -79,6 +79,7 @@ namespace SceneUtil // PositionAttitudeTransform seems to be slightly faster than MatrixTransform osg::ref_ptr trans(new SceneUtil::PositionAttitudeTransform); trans->setPosition(computeBound.getBoundingBox().center()); + trans->setNodeMask(lightMask); node->addChild(trans); From 226d3eac0ddb09b48eb45127825da6485ed2c4be Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 31 Oct 2021 10:33:28 -0400 Subject: [PATCH 1599/2859] Improve performance, add simpler ripples, add a setting, fix nighttime brightness --- apps/openmw/mwgui/settingswindow.cpp | 13 ++ apps/openmw/mwgui/settingswindow.hpp | 2 + apps/openmw/mwrender/water.cpp | 2 + files/mygui/openmw_settings_window.layout | 10 ++ files/settings-default.cfg | 4 + files/shaders/water_fragment.glsl | 147 +++++++++++++++------- 6 files changed, 135 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 3b4afc852f..00bd064bc7 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -232,6 +232,7 @@ namespace MWGui getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); + getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail"); getWidget(mLightingMethodButton, "LightingMethodButton"); getWidget(mLightsResetButton, "LightsResetButton"); getWidget(mMaxLights, "MaxLights"); @@ -259,6 +260,7 @@ namespace MWGui mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); + mWaterRainRippleDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterRainRippleDetailChanged); mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged); mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); @@ -309,6 +311,10 @@ namespace MWGui waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); + int waterRainRippleDetail = Settings::Manager::getInt("rain ripple density", "Water"); + waterRainRippleDetail = std::min(2, std::max(0, waterRainRippleDetail)); + mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail); + updateMaxLightsComboBox(mMaxLights); mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); @@ -397,6 +403,13 @@ namespace MWGui apply(); } + void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos) + { + unsigned int level = std::min((unsigned int)2, (unsigned int)(pos)); + Settings::Manager::setInt("rain ripple density", "Water", level); + apply(); + } + void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 9c28733f9a..aae14de373 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -31,6 +31,7 @@ namespace MWGui MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; + MyGUI::ComboBox* mWaterRainRippleDetail; MyGUI::ComboBox* mMaxLights; MyGUI::ComboBox* mLightingMethodButton; @@ -55,6 +56,7 @@ namespace MWGui void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); + void onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos); void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos); void onLightsResetButtonClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 37368b8e7a..45e0864226 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -619,6 +619,8 @@ public: stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } stateset->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWater->getPosition()))); + + stateset->addUniform(new osg::Uniform("rainRippleDensity", Settings::Manager::getInt("rain ripple density", "Water"))); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index a07d8125d5..8e81764f60 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -454,6 +454,16 @@ + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 27a9544ea6..cc8bc6085d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -649,6 +649,10 @@ refraction = false # Draw objects on water reflections. reflection detail = 2 +# Whether to use fully detailed raindrop ripples. +# 2 means more, 1 means less, 0 means less with simpler ring-only ripples (no normal mapping). +rain ripple density = 2 + # Overrides the value in '[Camera] small feature culling pixel size' specifically for water reflection/refraction textures. small feature culling pixel size = 20.0 diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index ac12b355ee..dbb40c8da7 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -55,14 +55,22 @@ const float WOBBLY_SHORE_FADE_DISTANCE = 6200.0; // fade out wobbly shores to // ---------------- rain ripples related stuff --------------------- -const float RAIN_RIPPLE_GAPS = 5.0; -const float RAIN_RIPPLE_RADIUS = 0.1; +uniform int rainRippleDensity; -vec2 randOffset(vec2 c) +const float RAIN_RIPPLE_GAPS = 10.0; +const float RAIN_RIPPLE_RADIUS = 0.2; + +float scramble(float x, float z) +{ + return fract(pow(x*3.0+1.0, z)); +} + +vec2 randOffset(vec2 c, float time) { - return fract(vec2( - c.x * c.y / 8.0 + c.y * 0.3 + c.x * 0.2, - c.x * c.y / 14.0 + c.y * 0.5 + c.x * 0.7)); + time = fract(time/1000.0); + c.x *= scramble(scramble(time + c.x/1000.0, 4.0), 3.0) + 1.0; + c.y *= scramble(scramble(time + c.y/1000.0, 3.5), 3.0) + 1.0; + return fract(c); } float randPhase(vec2 c) @@ -83,51 +91,97 @@ float blipDerivative(float x) return -6.0*x*n*n; } -vec2 randomize_center(vec2 i_part, float time) +const float RAIN_RING_TIME_OFFSET = 1.0/6.0; +vec4 circle(vec2 coords, vec2 corner, float adjusted_time) { - time = 1.0 + mod(time, 10000.0); // so things don't get out of hand after long runtimes - vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(i_part*time) - 1.0); - return center; -} - -vec4 circle(vec2 coords, vec2 uv, float adjusted_time) -{ - vec2 center = randomize_center(floor(uv * RAIN_RIPPLE_GAPS), floor(adjusted_time)); + vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(corner, floor(adjusted_time)) - 1.0); float phase = fract(adjusted_time); vec2 toCenter = coords - center; + float r = RAIN_RIPPLE_RADIUS; float d = length(toCenter); - float ringfollower = (phase-d/r)*6.0-1.0; + float ringfollower = (phase-d/r)/RAIN_RING_TIME_OFFSET-1.0; // -1.0 ~ +1.0 cover the breadth of the ripple's ring + if(ringfollower < -1.0 || ringfollower > 1.0) + return vec4(0.0); + + if(d > 1.0) // normalize center direction vector, but not for near-center ripples + toCenter /= d; + + float height = blip(ringfollower*2.0+0.5); // brighten up outer edge of ring; for fake specularity + float range_limit = blip(min(0.0, ringfollower)); float energy = 1.0-phase; - energy = energy*energy; - vec2 normal = -toCenter*blipDerivative(ringfollower)*5.0; - float height = blip(ringfollower); - vec4 ret = vec4(normal.x, normal.y, height, height); - ret.xyw *= energy; - ret.xyz = normalize(ret.xyz); + vec2 normal2d = -toCenter*blipDerivative(ringfollower)*5.0; + vec3 normal = vec3(normal2d, 0.5); + vec4 ret = vec4(normal, height); + ret.xyw *= energy*energy; + // do energy adjustment here rather than later, so that we can use the w component for fake specularity + ret.xyz = normalize(ret.xyz) * energy*range_limit; + ret.z *= range_limit; return ret; } - -const float RAIN_RING_TIME_OFFSET = 1/6.0; vec4 rain(vec2 uv, float time) { - vec2 i_part = floor(uv * RAIN_RIPPLE_GAPS); - float time_prog = time * 1.2 + randPhase(i_part); - vec2 f_part = fract(uv * RAIN_RIPPLE_GAPS); - return circle(f_part, uv, time_prog) - - circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET) / 4.0 - + circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET*2.0) / 8.0 - - circle(f_part, uv, time_prog - RAIN_RING_TIME_OFFSET*3.0) / 16.0; + uv *= RAIN_RIPPLE_GAPS; + vec2 f_part = fract(uv); + vec2 i_part = floor(uv); + float adjusted_time = time * 1.2 + randPhase(i_part); + 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); + vec4 d = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*3.0); + vec4 ret; + ret.xy = a.xy - b.xy/2.0 + c.xy/4.0 - d.xy/8.0; + // z should always point up + ret.z = a.z + b.z /2.0 + c.z /4.0 + d.z /8.0; + //ret.xyz *= 1.5; + // fake specularity looks weird if we use every single ring, also if the inner rings are too bright + ret.w = (a.w + c.w /8.0)*1.5; + return ret; +} +vec4 circleSimple(vec2 coords, vec2 corner, float adjusted_time) // only returns fake specularity +{ + vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(corner, floor(adjusted_time)) - 1.0); + float phase = fract(adjusted_time); + vec2 toCenter = coords - center; + + float r = RAIN_RIPPLE_RADIUS; + 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(ringfollower < -1.0 || ringfollower > 0.5) + return vec4(0.0); + + float energy = 1.0-phase; + float height = blip(ringfollower*2.0+0.5)*energy*energy; // fake specularity + + return vec4(0.0, 0.0, 0.0, height); +} +vec4 rainSimple(vec2 uv, float time) +{ + uv *= RAIN_RIPPLE_GAPS; + vec2 f_part = fract(uv); + vec2 i_part = floor(uv); + float adjusted_time = time * 1.2 + randPhase(i_part); + return circleSimple(f_part, i_part, adjusted_time) * 1.5; } -vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and ripple height in w +vec2 complex_mult(vec2 a, vec2 b) { - return - rain(uv,time) + - rain(uv + vec2(10.5,5.7),time) + - rain(uv * 0.75 + vec2(3.7,18.9),time) + - rain(uv * 0.9 + vec2(5.7,30.1),time) + - rain(uv * 0.8 + vec2(1.2,3.0),time); + return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); +} +vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake specularity in w +{ + if(rainRippleDensity == 0) + return rainSimple(uv, time) + rainSimple(complex_mult(uv, vec2(0.4, 0.7)) + vec2( 1.2, 3.0), time); + vec4 ret = + rain(uv, time) + + rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time); + if(rainRippleDensity == 2) + ret += + 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); + return ret; } @@ -217,11 +271,11 @@ void main(void) vec4 rainRipple; if (rainIntensity > 0.01) - rainRipple = rainCombined(position.xy / 1000.0,waterTimer) * clamp(rainIntensity,0.0,1.0); + rainRipple = rainCombined(position.xy/1000.0, waterTimer) * clamp(rainIntensity, 0.0, 1.0); else rainRipple = vec4(0.0); - vec3 rippleAdd = rainRipple.xyz * abs(rainRipple.w) * 10.0; + vec3 rippleAdd = rainRipple.xyz * 10.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); @@ -269,7 +323,14 @@ void main(void) vec4 sunSpec = lcalcSpecular(0); + // 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; + #if REFRACTION + // no alpha here, so make sure raindrop ripple specularity gets properly subdued + rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); + // refraction vec3 refraction = texture2D(refractionMap, screenCoords - screenCoordsOffset).rgb; vec3 rawRefraction = refraction; @@ -289,7 +350,7 @@ 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 * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + vec3(abs(rainRipple.w)) * 0.2; + gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + rainSpecular; gl_FragData[0].w = 1.0; // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping @@ -302,7 +363,7 @@ void main(void) 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); #else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + vec3(abs(rainRipple.w)) * 0.7; + gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + rainSpecular; gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); #endif @@ -312,7 +373,7 @@ void main(void) #else float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #endif - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); applyShadowDebugOverlay(); } From e7ec89573e437e66b8fc26aaf3fab1903183a20e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 31 Oct 2021 17:38:06 +0100 Subject: [PATCH 1600/2859] Refactoring. Lua `Callback` is moved from apps/openmw/mwlua to components/lua. --- apps/openmw/mwlua/asyncbindings.cpp | 5 ++- apps/openmw/mwlua/luamanagerimp.cpp | 10 ------ apps/openmw/mwlua/luamanagerimp.hpp | 22 ++++-------- components/lua/scriptscontainer.cpp | 52 +++++++++++++++++------------ components/lua/scriptscontainer.hpp | 26 +++++++++++---- 5 files changed, 59 insertions(+), 56 deletions(-) diff --git a/apps/openmw/mwlua/asyncbindings.cpp b/apps/openmw/mwlua/asyncbindings.cpp index d438518452..9bddf75ee4 100644 --- a/apps/openmw/mwlua/asyncbindings.cpp +++ b/apps/openmw/mwlua/asyncbindings.cpp @@ -52,13 +52,12 @@ namespace MWLua }; api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) { - return Callback{std::move(fn), asyncId.mHiddenData}; + return LuaUtil::Callback{std::move(fn), asyncId.mHiddenData}; }; auto initializer = [](sol::table hiddenData) { - LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY]; - hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString(); + LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey]; return AsyncPackageId{id.mContainer, id.mIndex, hiddenData}; }; return sol::make_object(context.mLua->sol(), initializer); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 5695883691..4e47bf7167 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -78,16 +78,6 @@ namespace MWLua mInitialized = true; } - void Callback::operator()(sol::object arg) const - { - if (mHiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY] != sol::nil) - LuaUtil::call(mFunc, std::move(arg)); - else - { - Log(Debug::Debug) << "Ignored callback to removed script " << mHiddenData.get(SCRIPT_NAME_KEY); - } - } - void LuaManager::update(bool paused, float dt) { ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index b9151de685..f5ffe9d258 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -19,19 +19,6 @@ namespace MWLua { - // Wrapper for a single-argument Lua function. - // Holds information about the script the function belongs to. - // Needed to prevent callback calls if the script was removed. - struct Callback - { - static constexpr std::string_view SCRIPT_NAME_KEY = "name"; - - sol::function mFunc; - sol::table mHiddenData; - - void operator()(sol::object arg) const; - }; - class LuaManager : public MWBase::LuaManager { public: @@ -82,13 +69,16 @@ namespace MWLua void reloadAllScripts() override; // Used to call Lua callbacks from C++ - void queueCallback(Callback callback, sol::object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); } + void queueCallback(LuaUtil::Callback callback, sol::object arg) + { + mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); + } // Wraps Lua callback into an std::function. // NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or // any other Lua-related function is running. template - std::function wrapLuaCallback(const Callback& c) + std::function wrapLuaCallback(const LuaUtil::Callback& c) { return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); }; } @@ -131,7 +121,7 @@ namespace MWLua struct CallbackWithData { - Callback mCallback; + LuaUtil::Callback mCallback; sol::object mArg; }; std::vector mQueuedCallbacks; diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index c92116f1d9..517ad5f788 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -15,15 +15,6 @@ namespace LuaUtil static constexpr std::string_view HANDLER_LOAD = "onLoad"; static constexpr std::string_view HANDLER_INTERFACE_OVERRIDE = "onInterfaceOverride"; - std::string ScriptsContainer::ScriptId::toString() const - { - std::string res = mContainer->mNamePrefix; - res.push_back('['); - res.append(mPath); - res.push_back(']'); - return res; - } - ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode) : mNamePrefix(namePrefix), mLua(*lua), mAutoStartMode(autoStartMode) { @@ -70,11 +61,19 @@ namespace LuaUtil return false; // already present const std::string& path = scriptPath(scriptId); + std::string debugName = mNamePrefix; + debugName.push_back('['); + debugName.append(path); + debugName.push_back(']'); + + Script& script = mScripts[scriptId]; + script.mHiddenData = mLua.newTable(); + script.mHiddenData[sScriptIdKey] = ScriptId{this, scriptId}; + script.mHiddenData[sScriptDebugNameKey] = debugName; + script.mPath = path; + try { - Script& script = mScripts[scriptId]; - script.mHiddenData = mLua.newTable(); - script.mHiddenData[ScriptId::KEY] = ScriptId{this, scriptId, path}; sol::object scriptOutput = mLua.runInNewSandbox(path, mNamePrefix, mAPI, script.mHiddenData); if (scriptOutput == sol::nil) return true; @@ -91,7 +90,7 @@ namespace LuaUtil else if (sectionName == INTERFACE) script.mInterface = value.as(); else - Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << mNamePrefix << "[" << path << "]"; + Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << debugName; } if (engineHandlers != sol::nil) { @@ -110,8 +109,7 @@ namespace LuaUtil { auto it = mEngineHandlers.find(handlerName); if (it == mEngineHandlers.end()) - Log(Debug::Error) << "Not supported handler '" << handlerName - << "' in " << mNamePrefix << "[" << path << "]"; + Log(Debug::Error) << "Not supported handler '" << handlerName << "' in " << debugName; else insertHandler(it->second->mList, scriptId, fn); } @@ -131,7 +129,7 @@ namespace LuaUtil if (script.mInterfaceName.empty() == script.mInterface.has_value()) { - Log(Debug::Error) << mNamePrefix << "[" << path << "]: 'interfaceName' should always be used together with 'interface'"; + Log(Debug::Error) << debugName << ": 'interfaceName' should always be used together with 'interface'"; script.mInterfaceName.clear(); script.mInterface = sol::nil; } @@ -145,8 +143,9 @@ namespace LuaUtil } catch (std::exception& e) { + mScripts[scriptId].mHiddenData[sScriptIdKey] = sol::nil; mScripts.erase(scriptId); - Log(Debug::Error) << "Can't start " << mNamePrefix << "[" << path << "]; " << e.what(); + Log(Debug::Error) << "Can't start " << debugName << "; " << e.what(); return false; } } @@ -159,7 +158,7 @@ namespace LuaUtil Script& script = scriptIter->second; if (script.mInterface) removeInterface(scriptId, script); - script.mHiddenData[ScriptId::KEY] = sol::nil; + script.mHiddenData[sScriptIdKey] = sol::nil; mScripts.erase(scriptIter); for (auto& [_, handlers] : mEngineHandlers) removeHandler(handlers->mList, scriptId); @@ -329,7 +328,9 @@ namespace LuaUtil for (auto& [scriptId, script] : mScripts) { ESM::LuaScript savedScript; - savedScript.mScriptPath = script.mHiddenData.get(ScriptId::KEY).mPath; + // Note: We can not use `scriptPath(scriptId)` here because `save` can be called during + // evaluating "reloadlua" command when ScriptsConfiguration is already changed. + savedScript.mScriptPath = script.mPath; if (script.mOnSave) { try @@ -423,7 +424,7 @@ namespace LuaUtil ScriptsContainer::~ScriptsContainer() { for (auto& [_, script] : mScripts) - script.mHiddenData[ScriptId::KEY] = sol::nil; + script.mHiddenData[sScriptIdKey] = sol::nil; } // Note: shouldn't be called from destructor because mEngineHandlers has pointers on @@ -431,7 +432,7 @@ namespace LuaUtil void ScriptsContainer::removeAllScripts() { for (auto& [_, script] : mScripts) - script.mHiddenData[ScriptId::KEY] = sol::nil; + script.mHiddenData[sScriptIdKey] = sol::nil; mScripts.clear(); for (auto& [_, handlers] : mEngineHandlers) handlers->mList.clear(); @@ -529,4 +530,13 @@ namespace LuaUtil updateTimerQueue(mHoursTimersQueue, gameHours); } + void Callback::operator()(sol::object arg) const + { + if (mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil) + LuaUtil::call(mFunc, std::move(arg)); + else + Log(Debug::Debug) << "Ignored callback to the removed script " + << mHiddenData.get(ScriptsContainer::sScriptDebugNameKey); + } + } diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index b25c69b5b4..e934868d08 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -60,16 +60,18 @@ namespace LuaUtil class ScriptsContainer { public: + // ScriptId of each script is stored with this key in Script::mHiddenData. + // Removed from mHiddenData when the script if removed. + constexpr static std::string_view sScriptIdKey = "_id"; + + // Debug identifier of each script is stored with this key in Script::mHiddenData. + // Present in mHiddenData even after removal of the script from ScriptsContainer. + constexpr static std::string_view sScriptDebugNameKey = "_name"; + struct ScriptId { - // ScriptId is stored in hidden data (see getHiddenData) with this key. - constexpr static std::string_view KEY = "_id"; - ScriptsContainer* mContainer; int mIndex; // index in LuaUtil::ScriptsConfiguration - std::string mPath; // path to the script source in VFS - - std::string toString() const; }; using TimeUnit = ESM::LuaTimer::TimeUnit; @@ -192,6 +194,7 @@ namespace LuaUtil sol::table mHiddenData; std::map mRegisteredCallbacks; std::map mTemporaryCallbacks; + std::string mPath; }; struct Timer { @@ -239,6 +242,17 @@ namespace LuaUtil int64_t mTemporaryCallbackCounter = 0; }; + // Wrapper for a single-argument Lua function. + // Holds information about the script the function belongs to. + // Needed to prevent callback calls if the script was removed. + struct Callback + { + sol::function mFunc; + sol::table mHiddenData; // same object as Script::mHiddenData in ScriptsContainer + + void operator()(sol::object arg) const; + }; + } #endif // COMPONENTS_LUA_SCRIPTSCONTAINER_H From e1378cd290f1f9a0128913703dc753860defe7ff Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 31 Oct 2021 12:23:36 -0400 Subject: [PATCH 1601/2859] Replace uniform with define --- apps/openmw/mwgui/settingswindow.cpp | 12 +++--- apps/openmw/mwrender/water.cpp | 5 ++- files/settings-default.cfg | 6 +-- files/shaders/water_fragment.glsl | 64 ++++++++++++---------------- 4 files changed, 38 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 00bd064bc7..b4a240af83 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -307,12 +307,10 @@ namespace MWGui if (waterTextureSize >= 2048) mWaterTextureSize->setIndexSelected(2); - int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); + int waterReflectionDetail = std::clamp(Settings::Manager::getInt("reflection detail", "Water"), 0, 5); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); - int waterRainRippleDetail = Settings::Manager::getInt("rain ripple density", "Water"); - waterRainRippleDetail = std::min(2, std::max(0, waterRainRippleDetail)); + int waterRainRippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail); updateMaxLightsComboBox(mMaxLights); @@ -398,15 +396,15 @@ namespace MWGui void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min((unsigned int)5, (unsigned int)pos); + unsigned int level = std::min(unsigned int(pos), unsigned int(5)); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min((unsigned int)2, (unsigned int)(pos)); - Settings::Manager::setInt("rain ripple density", "Water", level); + unsigned int level = std::min(unsigned int(pos), unsigned int(2)); + Settings::Manager::setInt("rain ripple detail", "Water", level); apply(); } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 45e0864226..a84e7d6ee6 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -32,6 +32,7 @@ #include #include +#include #include @@ -619,8 +620,6 @@ public: stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } stateset->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWater->getPosition()))); - - stateset->addUniform(new osg::Uniform("rainRippleDensity", Settings::Manager::getInt("rain ripple density", "Water"))); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override @@ -649,6 +648,8 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R // use a define map to conditionally compile the shader std::map defineMap; defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(mRefraction ? "1" : "0"))); + unsigned int rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); + defineMap.insert(std::make_pair(std::string("rain_ripple_detail"), Misc::StringUtils::format("%u", rippleDetail))); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr vertexShader(shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); diff --git a/files/settings-default.cfg b/files/settings-default.cfg index cc8bc6085d..bac06aee2d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -649,9 +649,9 @@ refraction = false # Draw objects on water reflections. reflection detail = 2 -# Whether to use fully detailed raindrop ripples. -# 2 means more, 1 means less, 0 means less with simpler ring-only ripples (no normal mapping). -rain ripple density = 2 +# Whether to use fully detailed raindrop ripples. (0, 1, 2). +# 0 = rings only; 1 = sparse, high detail; 2 = dense, high detail +rain ripple detail = 2 # Overrides the value in '[Camera] small feature culling pixel size' specifically for water reflection/refraction textures. small feature culling pixel size = 20.0 diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index dbb40c8da7..029c5b9749 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -9,6 +9,7 @@ #endif #define REFRACTION @refraction_enabled +#define RAIN_RIPPLE_DETAIL @rain_ripple_detail // Inspired by Blender GLSL Water by martinsh ( https://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) @@ -55,8 +56,6 @@ const float WOBBLY_SHORE_FADE_DISTANCE = 6200.0; // fade out wobbly shores to // ---------------- rain ripples related stuff --------------------- -uniform int rainRippleDensity; - const float RAIN_RIPPLE_GAPS = 10.0; const float RAIN_RIPPLE_RADIUS = 0.2; @@ -92,6 +91,7 @@ float blipDerivative(float x) } const float RAIN_RING_TIME_OFFSET = 1.0/6.0; + vec4 circle(vec2 coords, vec2 corner, float adjusted_time) { vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(corner, floor(adjusted_time)) - 1.0); @@ -101,6 +101,9 @@ vec4 circle(vec2 coords, vec2 corner, float adjusted_time) float r = RAIN_RIPPLE_RADIUS; 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 +// normal mapped ripples if(ringfollower < -1.0 || ringfollower > 1.0) return vec4(0.0); @@ -119,6 +122,16 @@ vec4 circle(vec2 coords, vec2 corner, float adjusted_time) ret.xyz = normalize(ret.xyz) * energy*range_limit; ret.z *= range_limit; return ret; +#else +// ring-only ripples + if(ringfollower < -1.0 || ringfollower > 0.5) + return vec4(0.0); + + float energy = 1.0-phase; + float height = blip(ringfollower*2.0+0.5)*energy*energy; // fake specularity + + return vec4(0.0, 0.0, 0.0, height); +#endif } vec4 rain(vec2 uv, float time) { @@ -126,6 +139,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 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); @@ -138,50 +152,26 @@ vec4 rain(vec2 uv, float time) // fake specularity looks weird if we use every single ring, also if the inner rings are too bright ret.w = (a.w + c.w /8.0)*1.5; return ret; +#else + return circle(f_part, i_part, adjusted_time) * 1.5; +#endif } -vec4 circleSimple(vec2 coords, vec2 corner, float adjusted_time) // only returns fake specularity -{ - vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(corner, floor(adjusted_time)) - 1.0); - float phase = fract(adjusted_time); - vec2 toCenter = coords - center; - float r = RAIN_RIPPLE_RADIUS; - 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(ringfollower < -1.0 || ringfollower > 0.5) - return vec4(0.0); - - float energy = 1.0-phase; - float height = blip(ringfollower*2.0+0.5)*energy*energy; // fake specularity - - return vec4(0.0, 0.0, 0.0, height); -} -vec4 rainSimple(vec2 uv, float time) -{ - uv *= RAIN_RIPPLE_GAPS; - vec2 f_part = fract(uv); - vec2 i_part = floor(uv); - float adjusted_time = time * 1.2 + randPhase(i_part); - return circleSimple(f_part, i_part, adjusted_time) * 1.5; -} vec2 complex_mult(vec2 a, vec2 b) { return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); } vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake specularity in w { - if(rainRippleDensity == 0) - return rainSimple(uv, time) + rainSimple(complex_mult(uv, vec2(0.4, 0.7)) + vec2( 1.2, 3.0), time); - vec4 ret = + return rain(uv, time) - + rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time); - if(rainRippleDensity == 2) - ret += - 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); - return ret; + + rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time) + #if RAIN_RIPPLE_DETAIL == 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 + ; } From 92bdd44e58941e2e681fde4f590416db6ce239e1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 31 Oct 2021 20:25:37 +0100 Subject: [PATCH 1602/2859] Allow creatures to use torches --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/actors.cpp | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a574338a..11e4e0ae26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Bug #6326: Detect Enchantment/Key should detect items in unresolved containers Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures Bug #6363: Some scripts in Morrowland fail to work + Bug #6376: Creatures should be able to use torches Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0af65844f9..cb4f8237e7 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1496,15 +1496,13 @@ namespace MWMechanics stats.getAiSequence().execute(iter->first, *ctrl, duration, /*outOfRange*/true); } - if(iter->first.getClass().isNpc()) + if(inProcessingRange && iter->first.getClass().isNpc()) { // We can not update drowning state for actors outside of AI distance - they can not resurface to breathe - if (inProcessingRange) - updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); - - if (timerUpdateEquippedLight == 0) - updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); + updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); } + if(timerUpdateEquippedLight == 0 && iter->first.getClass().hasInventoryStore(iter->first)) + updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); if (luaControls && isConscious(iter->first)) { From 838fc634c6303815373326ca92573928aca87aa3 Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 31 Oct 2021 15:32:24 -0400 Subject: [PATCH 1603/2859] address review comments --- apps/openmw/mwgui/settingswindow.cpp | 4 ++-- apps/openmw/mwrender/water.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index b4a240af83..7e848f7442 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -396,14 +396,14 @@ namespace MWGui void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min(unsigned int(pos), unsigned int(5)); + unsigned int level = unsigned int(std::min(pos, 5)); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min(unsigned int(pos), unsigned int(2)); + unsigned int level = unsigned int(std::min(pos, 2)); Settings::Manager::setInt("rain ripple detail", "Water", level); apply(); } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index a84e7d6ee6..5ae71b9979 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -647,9 +647,10 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R { // use a define map to conditionally compile the shader std::map defineMap; - defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(mRefraction ? "1" : "0"))); - unsigned int rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); - defineMap.insert(std::make_pair(std::string("rain_ripple_detail"), Misc::StringUtils::format("%u", rippleDetail))); + defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); + const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); + defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr vertexShader(shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); From 9b030e174fde3a65706ba252716eca7119efa86b Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 31 Oct 2021 15:52:54 -0400 Subject: [PATCH 1604/2859] removing this part was a mistake, made the ripples more repetitive --- files/shaders/water_fragment.glsl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 029c5b9749..5d42a2bdb1 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -67,6 +67,8 @@ float scramble(float x, float z) vec2 randOffset(vec2 c, float time) { time = fract(time/1000.0); + c = vec2(c.x * c.y / 8.0 + c.y * 0.3 + c.x * 0.2, + c.x * c.y / 14.0 + c.y * 0.5 + c.x * 0.7); c.x *= scramble(scramble(time + c.x/1000.0, 4.0), 3.0) + 1.0; c.y *= scramble(scramble(time + c.y/1000.0, 3.5), 3.0) + 1.0; return fract(c); From 4f10d5d544bce22c11937653565260ac6bcaec81 Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 31 Oct 2021 16:13:51 -0400 Subject: [PATCH 1605/2859] update docs --- docs/source/reference/modding/settings/water.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index b3a6f8c1f2..fb3b755c6f 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -77,6 +77,21 @@ Controls what kinds of things are rendered in water reflections. In interiors the lowest level is 2. This setting can be changed ingame with the "Reflection shader detail" dropdown under the Water tab of the Video panel in the Options menu. +rain ripple detail +----------------- + +:Type: integer +:Range: 0, 1, 2 +:Default: 2 + +Controls how detailed the raindrop ripples on water are. + +0: single, non-normal-mapped ring per raindrop +1: normal-mapped raindrops, with multiple rings +2: same as 1, but with a greater number of raindrops + +This setting can be changed ingame with the "Rain ripple detail/density" dropdown under the Water tab of the Video panel in the Options menu. + small feature culling pixel size -------------------------------- From 221f6f134ded2f4148db58874fe904305c59bed3 Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 31 Oct 2021 17:48:03 -0400 Subject: [PATCH 1606/2859] gcc and clang don't like this --- apps/openmw/mwgui/settingswindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 7e848f7442..28143c1af4 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -396,14 +396,14 @@ namespace MWGui void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = unsigned int(std::min(pos, 5)); + unsigned int level = static_cast(std::min(pos, 5)); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = unsigned int(std::min(pos, 2)); + unsigned int level = static_cast(std::min(pos, 2)); Settings::Manager::setInt("rain ripple detail", "Water", level); apply(); } From 8c21b0b503386f583e57e2ce40e9cd5e64a0299b Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 1 Nov 2021 12:44:36 +0100 Subject: [PATCH 1607/2859] Apply waterwalking even when we skip simulation. This chunk was supposed to be part of !1324 but somehow got stuck staged in my tree. --- apps/openmw/mwphysics/mtphysics.cpp | 6 ++++-- apps/openmw/mwphysics/physicssystem.cpp | 18 ------------------ apps/openmw/mwphysics/physicssystem.hpp | 1 - 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index a989493f4d..5c49dee297 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -123,8 +123,10 @@ namespace frameData.mPosition = actor->getPosition(); if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld)) { - frameData.mPosition.z() = frameData.mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(actor->getPtr(), frameData.mPosition, false); + const auto offset = osg::Vec3f(0, 0, frameData.mWaterlevel - frameData.mPosition.z()); + MWBase::Environment::get().getWorld()->moveObjectBy(actor->getPtr(), offset); + actor->applyOffsetChange(); + frameData.mPosition = actor->getPosition(); } frameData.mOldHeight = frameData.mPosition.z(); const auto rotation = actor->getPtr().getRefData().getPosition().asRotationVec3(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index af6dc109ed..a64ee70b96 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -945,24 +945,6 @@ namespace MWPhysics { } - void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world) - { - actor.applyOffsetChange(); - mPosition = actor.getPosition(); - if (mWaterCollision && mPosition.z() < mWaterlevel && actor.canMoveToWaterSurface(mWaterlevel, world)) - { - MWBase::Environment::get().getWorld()->moveObjectBy(actor.getPtr(), osg::Vec3f(0, 0, mWaterlevel - mPosition.z())); - actor.applyOffsetChange(); - mPosition = actor.getPosition(); - } - mOldHeight = mPosition.z(); - const auto rotation = actor.getPtr().getRefData().getPosition().asRotationVec3(); - mRotation = osg::Vec2f(rotation.x(), rotation.z()); - mInertia = actor.getInertialForce(); - mStuckFrames = actor.getStuckFrames(); - mLastStuckPosition = actor.getLastStuckPosition(); - } - ProjectileFrameData::ProjectileFrameData(Projectile& projectile) : mPosition(projectile.getPosition()) , mMovement(projectile.velocity()) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index a025f1cc8b..c31bbfbf65 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -76,7 +76,6 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel); - void updatePosition(Actor& actor, btCollisionWorld* world); osg::Vec3f mPosition; osg::Vec3f mInertia; const btCollisionObject* mStandingOn; From 4461366761207671b67b806d2320f04877568c33 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 1 Nov 2021 09:26:50 -0700 Subject: [PATCH 1608/2859] settings update and launcher option --- CHANGELOG.md | 1 + apps/launcher/advancedpage.cpp | 2 ++ components/shader/shadervisitor.cpp | 29 ++++++++++++------- .../reference/modding/settings/shaders.rst | 16 +++++----- files/settings-default.cfg | 3 +- files/ui/advancedpage.ui | 10 +++++++ 6 files changed, 41 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a486046e8..65110a0677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Feature #6017: Separate persistent and temporary cell references when saving Feature #6032: Reverse-z depth buffer Feature #6078: First person should not clear depth buffer + Feature #6128: Soft Particles Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 2c33992ac3..8d1ab58b79 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -117,6 +117,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); loadSettingBool(radialFogCheckBox, "radial fog", "Shaders"); + loadSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); @@ -270,6 +271,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); saveSettingBool(radialFogCheckBox, "radial fog", "Shaders"); + saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 2c673200f7..1e9acdb35f 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -557,19 +557,26 @@ namespace Shader updateAddedState(*writableUserData, addedState); } - if (auto partsys = dynamic_cast(&node)) + bool softParticles = false; + + if (mOpaqueDepthTex) { - defineMap["softParticles"] = "1"; - - auto depth = SceneUtil::createDepth(); - depth->setWriteMask(false); - writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - writableStateSet->addUniform(new osg::Uniform("particleSize", partsys->getDefaultParticleTemplate().getSizeRange().maximum)); - writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); - writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); + auto partsys = dynamic_cast(&node); + + if (partsys) + { + softParticles = true; + + auto depth = SceneUtil::createDepth(); + depth->setWriteMask(false); + writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + writableStateSet->addUniform(new osg::Uniform("particleSize", partsys->getDefaultParticleTemplate().getSizeRange().maximum)); + writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); + writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); + } } - else - defineMap["softParticles"] = "0"; + + defineMap["softParticles"] = softParticles ? "1" : "0"; std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index c62c44e936..b9d8cfe1b9 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -241,7 +241,7 @@ lighting` is on. This setting has no effect if :ref:`lighting method` is 'legacy'. minimum interior brightness ------------------------- +--------------------------- :Type: float :Range: 0.0-1.0 @@ -260,7 +260,7 @@ aforementioned changes in visuals. This setting has no effect if :ref:`lighting method` is 'legacy'. antialias alpha test ---------------------------------------- +-------------------- :Type: boolean :Range: True/False @@ -271,14 +271,14 @@ This allows MSAA to work with alpha-tested meshes, producing better-looking edge When MSAA is off, this setting will have no visible effect, but might have a performance cost. soft particles ------------------------- +-------------- :Type: boolean :Range: True/False :Default: False -Enables soft particles for almost all particle effects, excluding precipitation. -This technique softens the intersection between individual particles and other -opaque geometry by blending 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. +Enables soft particles for particle effects. This technique softens the +intersection between individual particles and other opaque geometry by blending +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. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 2df6eaa0a7..90e9323db7 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -494,7 +494,8 @@ minimum interior brightness = 0.08 # When MSAA is off, this setting will have no visible effect, but might have a performance cost. antialias alpha test = false -soft particles = true +# Soften intersection of blended particle systems with opaque geometry +soft particles = false [Input] diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 7964399319..0e80f02700 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -444,6 +444,16 @@ + + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + + + Soft Particles + + + From 3660d5cc4cf6d2984d203fe459a63d6d690f8bf0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 1 Nov 2021 19:33:29 +0100 Subject: [PATCH 1609/2859] Reduce code duplication in getting the shield model --- apps/openmw/mwrender/actoranimation.cpp | 38 +++++++++++++------- apps/openmw/mwrender/actoranimation.hpp | 3 +- apps/openmw/mwrender/creatureanimation.cpp | 15 +------- apps/openmw/mwrender/npcanimation.cpp | 42 ++-------------------- apps/openmw/mwrender/npcanimation.hpp | 2 +- 5 files changed, 32 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 24ca0aa4f3..e820eb5672 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -84,30 +84,42 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st return PartHolderPtr(new PartHolder(instance)); } -std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield) const +std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const { - std::string mesh = shield.getClass().getModel(shield); const ESM::Armor *armor = shield.get()->mBase; const std::vector& bodyparts = armor->mParts.mParts; + // Try to recover the body part model, use ground model as a fallback otherwise. if (!bodyparts.empty()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); - - // Try to get shield model from bodyparts first, with ground model as fallback for (const auto& part : bodyparts) { - // Assume all creatures use the male mesh. - if (part.mPart != ESM::PRT_Shield || part.mMale.empty()) + if (part.mPart != ESM::PRT_Shield) continue; - const ESM::BodyPart *bodypart = partStore.search(part.mMale); - if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty()) + + std::string bodypartName; + if (female && !part.mFemale.empty()) + bodypartName = part.mFemale; + else if (!part.mMale.empty()) + bodypartName = part.mMale; + + if (!bodypartName.empty()) { - mesh = "meshes\\" + bodypart->mModel; - break; + const ESM::BodyPart *bodypart = partStore.search(bodypartName); + if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) + return std::string(); + if (!bodypart->mModel.empty()) + return "meshes\\" + bodypart->mModel; } } } + return shield.getClass().getModel(shield); +} + +std::string ActorAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const +{ + std::string mesh = getShieldMesh(shield, false); if (mesh.empty()) return mesh; @@ -143,7 +155,7 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getShieldMesh(*shield).empty()) + if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty()) { if(stats.getDrawState() != MWMechanics::DrawState_Weapon) return false; @@ -201,7 +213,7 @@ void ActorAnimation::updateHolsteredShield(bool showCarriedLeft) return; } - std::string mesh = getShieldMesh(*shield); + std::string mesh = getSheathedShieldMesh(*shield); if (mesh.empty()) return; @@ -255,7 +267,7 @@ bool ActorAnimation::useShieldAnimations() const const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (weapon != inv.end() && shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && - !getShieldMesh(*shield).empty()) + !getSheathedShieldMesh(*shield).empty()) { auto type = weapon->getType(); if(type == ESM::Weapon::sRecordId) diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 61ad1ca235..c68ce4dfe2 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -45,7 +45,8 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener virtual void updateHolsteredWeapon(bool showHolsteredWeapons); virtual void updateHolsteredShield(bool showCarriedLeft); virtual void updateQuiver(); - virtual std::string getShieldMesh(const MWWorld::ConstPtr& shield) const; + std::string getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const; + virtual std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const; virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor); virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 657179db75..7dea8173b6 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -139,20 +139,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) bonename = "Shield Bone"; if (item.getType() == ESM::Armor::sRecordId) { - // Shield body part model should be used if possible. - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - for (const auto& part : item.get()->mBase->mParts.mParts) - { - // Assume all creatures use the male mesh. - if (part.mPart != ESM::PRT_Shield || part.mMale.empty()) - continue; - const ESM::BodyPart *bodypart = store.get().search(part.mMale); - if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty()) - { - itemModel = "meshes\\" + bodypart->mModel; - break; - } - } + itemModel = getShieldMesh(item, false); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f3d026ad3c..16cf4cee69 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -82,34 +82,6 @@ std::string getVampireHead(const std::string& race, bool female) return "meshes\\" + bodyPart->mModel; } -std::string getShieldBodypartMesh(const std::vector& bodyparts, bool female) -{ - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - for (const auto& part : bodyparts) - { - if (part.mPart != ESM::PRT_Shield) - continue; - - std::string bodypartName; - if (female && !part.mFemale.empty()) - bodypartName = part.mFemale; - else if (!part.mMale.empty()) - bodypartName = part.mMale; - - if (!bodypartName.empty()) - { - const ESM::BodyPart *bodypart = partStore.search(bodypartName); - if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) - return std::string(); - if (!bodypart->mModel.empty()) - return "meshes\\" + bodypart->mModel; - } - } - - return std::string(); -} - } @@ -547,14 +519,9 @@ void NpcAnimation::updateNpcBase() mWeaponAnimationTime->updateStartTime(); } -std::string NpcAnimation::getShieldMesh(const MWWorld::ConstPtr& shield) const +std::string NpcAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const { - std::string mesh = shield.getClass().getModel(shield); - const ESM::Armor *armor = shield.get()->mBase; - const std::vector& bodyparts = armor->mParts.mParts; - // Try to recover the body part model, use ground model as a fallback otherwise. - if (!bodyparts.empty()) - mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale()); + std::string mesh = getShieldMesh(shield, !mNpc->isMale()); if (mesh.empty()) return std::string(); @@ -1011,10 +978,7 @@ void NpcAnimation::showCarriedLeft(bool show) // For shields we must try to use the body part model if (iter->getType() == ESM::Armor::sRecordId) { - const ESM::Armor *armor = iter->get()->mBase; - const std::vector& bodyparts = armor->mParts.mParts; - if (!bodyparts.empty()) - mesh = getShieldBodypartMesh(bodyparts, !mNpc->isMale()); + mesh = getShieldMesh(*iter, !mNpc->isMale()); } if (mesh.empty() || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index b511a52f37..d0d9a26fdc 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -105,7 +105,7 @@ private: protected: void addControllers() override; bool isArrowAttached() const override; - std::string getShieldMesh(const MWWorld::ConstPtr& shield) const override; + std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const override; public: /** From 665d756f02df18d91f2273be82fb8f5e8851de9e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 2 Nov 2021 00:05:22 +0100 Subject: [PATCH 1610/2859] Fix readthedocs config --- .readthedocs.yaml | 10 ++++++++++ docs/requirements.txt | 2 +- readthedocs.yml | 2 -- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 readthedocs.yml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000000..e0b39ec495 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,10 @@ +version: 2 + +sphinx: + configuration: docs/source/conf.py + +python: + version: 3.8 + install: + - requirements: docs/requirements.txt + diff --git a/docs/requirements.txt b/docs/requirements.txt index 288d462d0d..14c09d53e7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ parse_cmake -sphinx>=1.7.0 +sphinx==1.8.5 diff --git a/readthedocs.yml b/readthedocs.yml deleted file mode 100644 index e53e54b785..0000000000 --- a/readthedocs.yml +++ /dev/null @@ -1,2 +0,0 @@ -# Don't build any extra formats -formats: [] \ No newline at end of file From a5a895ffd482737316165d175ec0e2d316fc9dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Mon, 1 Nov 2021 18:22:38 -0400 Subject: [PATCH 1611/2859] Fix #6381 Do not use osg::PI_f --- apps/openmw/mwworld/worldimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cad7de9710..ed5872fb6d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1313,7 +1313,7 @@ namespace MWWorld * currently it's done so for rotating the camera, which needs * clamping. */ - objRot[0] = osg::clampBetween(objRot[0], -osg::PIf / 2, osg::PIf / 2); + objRot[0] = osg::clampBetween(objRot[0], -osg::PI_2, osg::PI_2); objRot[1] = Misc::normalizeAngle(objRot[1]); objRot[2] = Misc::normalizeAngle(objRot[2]); } From 20f851b3b50ac26059e3cbea87264fb7c0d8ec08 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 2 Nov 2021 10:21:21 +0100 Subject: [PATCH 1612/2859] Fix readthedocs config, second attempt. --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 14c09d53e7..ac82149f5d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ parse_cmake sphinx==1.8.5 +docutils==0.17.1 From 213faa669521521a6fa137629abb312e6fa1036a Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 2 Nov 2021 13:46:41 +0000 Subject: [PATCH 1613/2859] restores countRecords optimisations (#3210) With this PR we restore @elsid 's optimisations of countRecords we have unintentionally discarded in PR #3197. In addition, we give it a more appropriate name and add comments concerning its peculiar background. --- apps/openmw/mwworld/esmstore.cpp | 12 +++++++++--- apps/openmw/mwworld/esmstore.hpp | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 3cedcd457a..5ec95ecf47 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -29,6 +29,7 @@ namespace void readRefs(const ESM::Cell& cell, std::vector& refs, std::vector& refIDs, std::vector& readers) { + // TODO: we have many similar copies of this code. for (size_t i = 0; i < cell.mContextList.size(); i++) { size_t index = cell.mContextList[i].index; @@ -301,12 +302,14 @@ void ESMStore::setUp(bool validateRecords) if (validateRecords) { validate(); - countRecords(); + countAllCellRefs(); } } -void ESMStore::countRecords() +void ESMStore::countAllCellRefs() { + // TODO: We currently need to read entire files here again. + // We should consider consolidating or deferring this reading. if(!mRefCount.empty()) return; std::vector refs; @@ -324,6 +327,8 @@ void ESMStore::countRecords() if (value.mRefID != deletedRefID) { std::string& refId = refIDs[value.mRefID]; + // We manually lower case IDs here for the time being to improve performance. + Misc::StringUtils::lowerCaseInPlace(refId); ++mRefCount[std::move(refId)]; } }; @@ -332,7 +337,8 @@ void ESMStore::countRecords() int ESMStore::getRefCount(const std::string& id) const { - auto it = mRefCount.find(id); + const std::string lowerId = Misc::StringUtils::lowerCase(id); + auto it = mRefCount.find(lowerId); if(it == mRefCount.end()) return 0; return it->second; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d1a4942cd3..8582a1daca 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -79,7 +79,7 @@ namespace MWWorld IDMap mIds; IDMap mStaticIds; - IDMap mRefCount; + std::unordered_map mRefCount; std::map mStores; @@ -90,7 +90,7 @@ namespace MWWorld /// Validate entries in store after setup void validate(); - void countRecords(); + void countAllCellRefs(); template void removeMissingObjects(Store& store); From 940e338453fd4f03309148ea63f6feed2e5bb376 Mon Sep 17 00:00:00 2001 From: wareya Date: Tue, 2 Nov 2021 15:17:26 +0000 Subject: [PATCH 1614/2859] Constifications --- apps/openmw/mwphysics/trace.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index e489ea0eb2..b7930bfa53 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -58,7 +58,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star doing_short_trace = true; } - auto traceCallback = sweepHelper(actor, btstart, btend, world, false); + const auto traceCallback = sweepHelper(actor, btstart, btend, world, false); // Copy the hit data over to our trace results struct: if(traceCallback.hasHit()) @@ -77,7 +77,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star if(doing_short_trace) { btend = Misc::Convert::toBullet(end); - auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); + const auto newTraceCallback = sweepHelper(actor, btstart, btend, world, false); if(newTraceCallback.hasHit()) { @@ -100,11 +100,11 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { - auto newTraceCallback = sweepHelper(actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true); - if(newTraceCallback.hasHit()) + const auto traceCallback = sweepHelper(actor->getCollisionObject(), Misc::Convert::toBullet(start), Misc::Convert::toBullet(end), world, true); + if(traceCallback.hasHit()) { - mFraction = newTraceCallback.m_closestHitFraction; - mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld); + mFraction = traceCallback.m_closestHitFraction; + mPlaneNormal = Misc::Convert::toOsg(traceCallback.m_hitNormalWorld); mEndPos = (end-start)*mFraction + start; } else From a9106f4d7c239adc6ec2ba2968b594ecf238c5c7 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 2 Nov 2021 18:01:22 +0100 Subject: [PATCH 1615/2859] Rotate torches by 90 degrees --- CHANGELOG.md | 1 + apps/openmw/mwrender/actoranimation.cpp | 17 +++++++++++++++++ apps/openmw/mwrender/actoranimation.hpp | 1 + apps/openmw/mwrender/creatureanimation.cpp | 8 +------- apps/openmw/mwrender/npcanimation.cpp | 19 ++++++------------- apps/openmw/mwrender/npcanimation.hpp | 4 ++-- components/sceneutil/attach.cpp | 11 ++++++++--- components/sceneutil/attach.hpp | 3 ++- 8 files changed, 38 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a574338a..2ae4126b15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #5863: GetEffect should return true after the player has teleported Bug #5913: Failed assertion during Ritual of Trees quest + Bug #5937: Lights always need to be rotated by 90 degrees Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6051: NaN water height in ESM file is not handled gracefully Bug #6066: addtopic "return" does not work from within script. No errors thrown diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index e820eb5672..7706f7d7f1 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -84,6 +85,22 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st return PartHolderPtr(new PartHolder(instance)); } +osg::ref_ptr ActorAnimation::attach(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool isLight) +{ + osg::ref_ptr templateNode = mResourceSystem->getSceneManager()->getTemplate(model); + + const NodeMap& nodeMap = getNodeMap(); + auto found = nodeMap.find(bonename); + if (found == nodeMap.end()) + throw std::runtime_error("Can't find attachment node " + bonename); + if(isLight) + { + osg::Quat rotation(osg::DegreesToRadians(-90.f), osg::Vec3f(1,0,0)); + return SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation); + } + return SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); +} + std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const { const ESM::Armor *armor = shield.get()->mBase; diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index c68ce4dfe2..1ece0c326d 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -54,6 +54,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener osg::Vec4f stubColor = osg::Vec4f(0,0,0,0); return attachMesh(model, bonename, false, &stubColor); }; + osg::ref_ptr attach(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool isLight); PartHolderPtr mScabbard; PartHolderPtr mHolsteredShield; diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7dea8173b6..50dfb68008 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -145,13 +145,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) try { - osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(itemModel); - - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(bonename); - if (found == nodeMap.end()) - throw std::runtime_error("Can't find attachment node " + bonename); - osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get(), mResourceSystem->getSceneManager()); + osg::ref_ptr attached = attach(itemModel, bonename, bonename, item.getType() == ESM::Light::sRecordId); scene.reset(new PartHolder(attached)); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 16cf4cee69..0d6c21c308 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -645,7 +645,7 @@ void NpcAnimation::updateParts() { const ESM::Light *light = part.get()->mBase; addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, - 1, "meshes\\"+light->mModel); + 1, "meshes\\"+light->mModel, false, nullptr, true); if (mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), light); } @@ -675,16 +675,9 @@ void NpcAnimation::updateParts() -PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) +PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight) { - osg::ref_ptr templateNode = mResourceSystem->getSceneManager()->getTemplate(model); - - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(bonename); - if (found == nodeMap.end()) - throw std::runtime_error("Can't find attachment node " + bonename); - - osg::ref_ptr attached = SceneUtil::attach(templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); + osg::ref_ptr attached = attach(model, bonename, bonefilter, isLight); if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); @@ -757,7 +750,7 @@ bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart) return bodypart->mData.mFlags & ESM::BodyPart::BPF_Female; } -bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, osg::Vec4f* glowColor) +bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight) { if(priority <= mPartPriorities[type]) return false; @@ -789,7 +782,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename; - mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor); + mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor, isLight); } catch (std::exception& e) { @@ -981,7 +974,7 @@ void NpcAnimation::showCarriedLeft(bool show) mesh = getShieldMesh(*iter, !mNpc->isMale()); } if (mesh.empty() || addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, - mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) + mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor, iter->getType() == ESM::Light::sRecordId)) { if (mesh.empty()) reserveIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index d0d9a26fdc..2dcfac3036 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -83,13 +83,13 @@ private: NpcType getNpcType() const; PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, - const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr); + const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight); void removeIndividualPart(ESM::PartReferenceType type); void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority); bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, - bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); + bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr, bool isLight = false); void removePartGroup(int group); void addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 9dabb282b0..02c3456425 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -100,7 +100,7 @@ namespace SceneUtil } } - osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode, Resource::SceneManager* sceneManager) + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, osg::Group* attachNode, Resource::SceneManager* sceneManager, const osg::Quat* attitude) { if (dynamic_cast(toAttach.get())) { @@ -144,8 +144,6 @@ namespace SceneUtil trans = new osg::PositionAttitudeTransform; trans->setPosition(boneOffset->getMatrix().getTrans()); - // The BoneOffset rotation seems to be incorrect - trans->setAttitude(osg::Quat(osg::DegreesToRadians(-90.f), osg::Vec3f(1,0,0))); // Now that we used it, get rid of the redundant node. if (boneOffset->getNumChildren() == 0 && boneOffset->getNumParents() == 1) @@ -172,6 +170,13 @@ namespace SceneUtil trans->setStateSet(frontFaceStateSet); } + if(attitude) + { + if (!trans) + trans = new osg::PositionAttitudeTransform; + trans->setAttitude(*attitude); + } + if (trans) { attachNode->addChild(trans); diff --git a/components/sceneutil/attach.hpp b/components/sceneutil/attach.hpp index f5fc693724..ed0299dece 100644 --- a/components/sceneutil/attach.hpp +++ b/components/sceneutil/attach.hpp @@ -9,6 +9,7 @@ namespace osg { class Node; class Group; + class Quat; } namespace Resource { @@ -23,7 +24,7 @@ namespace SceneUtil /// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering. /// @note The master scene graph is expected to include a skeleton. /// @return A newly created node that is directly attached to the master scene graph - osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode, Resource::SceneManager *sceneManager); + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, osg::Group* attachNode, Resource::SceneManager *sceneManager, const osg::Quat* attitude = nullptr); } From 4631d957393059fef18c2cf98e2d6d93140c971c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 31 Oct 2021 15:19:19 +0100 Subject: [PATCH 1616/2859] Add more tests for BulletNifLoader --- .../nifloader/testbulletnifloader.cpp | 214 +++++++++++++++++- components/nifbullet/bulletnifloader.cpp | 9 +- 2 files changed, 216 insertions(+), 7 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 3d4628c267..3d9b56c930 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -60,6 +60,19 @@ namespace { return isNear(lhs.getOrigin(), rhs.getOrigin()) && isNear(lhs.getBasis(), rhs.getBasis()); } + + struct WriteVec3f + { + osg::Vec3f mValue; + + friend std::ostream& operator <<(std::ostream& stream, const WriteVec3f& value) + { + return stream << "osg::Vec3f {" + << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.x() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.y() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.z() << "}"; + } + }; } static std::ostream& operator <<(std::ostream& stream, const btVector3& value) @@ -122,6 +135,17 @@ static std::ostream& operator <<(std::ostream& stream, const TriangleMeshShape& return stream << "}}"; } +static bool operator ==(const BulletShape::CollisionBox& l, const BulletShape::CollisionBox& r) +{ + const auto tie = [] (const BulletShape::CollisionBox& v) { return std::tie(v.mExtents, v.mCenter); }; + return tie(l) == tie(r); +} + +static std::ostream& operator <<(std::ostream& stream, const BulletShape::CollisionBox& value) +{ + return stream << "CollisionBox {" << WriteVec3f {value.mExtents} << ", " << WriteVec3f {value.mCenter} << "}"; +} + } static std::ostream& operator <<(std::ostream& stream, const btCollisionShape& value) @@ -162,8 +186,7 @@ namespace Resource { return compareObjects(lhs.mCollisionShape.get(), rhs.mCollisionShape.get()) && compareObjects(lhs.mAvoidCollisionShape.get(), rhs.mAvoidCollisionShape.get()) - && lhs.mCollisionBox.mExtents == rhs.mCollisionBox.mExtents - && lhs.mCollisionBox.mCenter == rhs.mCollisionBox.mCenter + && lhs.mCollisionBox == rhs.mCollisionBox && lhs.mAnimatedShapes == rhs.mAnimatedShapes; } @@ -172,8 +195,7 @@ namespace Resource return stream << "Resource::BulletShape {" << value.mCollisionShape.get() << ", " << value.mAvoidCollisionShape.get() << ", " - << "osg::Vec3f {" << value.mCollisionBox.mExtents << "}" << ", " - << "osg::Vec3f {" << value.mCollisionBox.mCenter << "}" << ", " + << value.mCollisionBox << ", " << value.mAnimatedShapes << "}"; } @@ -272,6 +294,12 @@ namespace value.recType = Nif::RC_NiTriShape; } + void init(Nif::NiTriStrips& value) + { + init(static_cast(value)); + value.recType = Nif::RC_NiTriStrips; + } + void init(Nif::NiSkinInstance& value) { value.data = Nif::NiSkinDataPtr(nullptr); @@ -330,6 +358,8 @@ namespace Nif::NiTriShape mNiTriShape; Nif::NiTriShapeData mNiTriShapeData2; Nif::NiTriShape mNiTriShape2; + Nif::NiTriStripsData mNiTriStripsData; + Nif::NiTriStrips mNiTriStrips; Nif::NiSkinInstance mNiSkinInstance; Nif::NiStringExtraData mNiStringExtraData; Nif::NiStringExtraData mNiStringExtraData2; @@ -361,6 +391,7 @@ namespace init(mNiNode3); init(mNiTriShape); init(mNiTriShape2); + init(mNiTriStrips); init(mNiSkinInstance); init(mNiStringExtraData); init(mNiStringExtraData2); @@ -375,6 +406,11 @@ namespace mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)}; mNiTriShapeData2.triangles = {0, 1, 2}; mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); + + mNiTriStripsData.recType = Nif::RC_NiTriStripsData; + mNiTriStripsData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0)}; + mNiTriStripsData.strips = {{0, 1, 2, 3}}; + mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); } }; @@ -389,6 +425,18 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F(TestBulletNifLoader, should_ignore_nullptr_root) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(nullptr)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + TEST_F(TestBulletNifLoader, for_default_root_nif_node_should_return_default) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -860,6 +908,34 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F(TestBulletNifLoader, should_add_static_mesh_to_existing_compound_mesh) + { + mNiTriShape.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiTriShape2)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles2.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + TEST_F(TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) { mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); @@ -983,4 +1059,134 @@ namespace EXPECT_EQ(*result, expected); } + + TEST_F(TestBulletNifLoader, should_ignore_tri_shape_data_with_mismatching_data_rec_type) + { + mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_strips_root_node_should_return_shape_with_triangle_mesh_shape) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + triangles->addTriangle(btVector3(1, 0, 0), btVector3(0, 1, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_mismatching_data_rec_type) + { + mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_empty_strips) + { + mNiTriStripsData.strips.clear(); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_static_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) + { + mNiTriStripsData.strips.front() = {0, 1}; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_avoid_collision_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.recType = Nif::RC_AvoidNode; + mNiTriStripsData.strips.front() = {0, 1}; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_animated_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) + { + mNiTriStripsData.strips.front() = {0, 1}; + mNiTriStrips.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriStrips)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(new btCompoundShape); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, should_not_add_static_mesh_with_no_triangles_to_compound_shape) + { + mNiTriStripsData.strips.front() = {0, 1}; + mNiTriShape.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index f808877a75..544ec6b0b4 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -170,7 +170,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) if (mCompoundShape) { - if (mStaticMesh) + if (mStaticMesh != nullptr && mStaticMesh->getNumTriangles() > 0) { btTransform trans; trans.setIdentity(); @@ -181,13 +181,13 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) } mShape->mCollisionShape = std::move(mCompoundShape); } - else if (mStaticMesh) + else if (mStaticMesh != nullptr && mStaticMesh->getNumTriangles() > 0) { mShape->mCollisionShape.reset(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); mStaticMesh.release(); } - if (mAvoidStaticMesh) + if (mAvoidStaticMesh != nullptr && mAvoidStaticMesh->getNumTriangles() > 0) { mShape->mAvoidCollisionShape.reset(new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false)); mAvoidStaticMesh.release(); @@ -376,6 +376,9 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons fillTriangleMesh(*childMesh, niGeometry); + if (childMesh->getNumTriangles() == 0) + return; + std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); From 56eef691a84846fd0076a01acc6be5ac40cda171 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 31 Oct 2021 14:23:44 +0100 Subject: [PATCH 1617/2859] Use reference type to pass nif node as argument where nullptr is not handled --- components/nifbullet/bulletnifloader.cpp | 109 +++++++++++------------ components/nifbullet/bulletnifloader.hpp | 8 +- 2 files changed, 54 insertions(+), 63 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 544ec6b0b4..ed5eba819f 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -18,11 +18,11 @@ namespace { -osg::Matrixf getWorldTransform(const Nif::Node *node) +osg::Matrixf getWorldTransform(const Nif::Node& node) { - if(node->parent != nullptr) - return node->trafo.toMatrix() * getWorldTransform(node->parent); - return node->trafo.toMatrix(); + if(node.parent != nullptr) + return node.trafo.toMatrix() * getWorldTransform(*node.parent); + return node.trafo.toMatrix(); } bool pathFileNameStartsWithX(const std::string& path) @@ -99,12 +99,12 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co } } -void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry* geometry, const osg::Matrixf &transform = osg::Matrixf()) +void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry& geometry, const osg::Matrixf &transform = osg::Matrixf()) { - if (geometry->recType == Nif::RC_NiTriShape || geometry->recType == Nif::RC_BSLODTriShape) - fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); - else if (geometry->recType == Nif::RC_NiTriStrips) - fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); + if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) + fillTriangleMesh(mesh, static_cast(geometry.data.get()), transform); + else if (geometry.recType == Nif::RC_NiTriStrips) + fillTriangleMesh(mesh, static_cast(geometry.data.get()), transform); } } @@ -141,7 +141,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // Try to find a valid bounding box first. If one's found for any root node, use that. for (const Nif::Node* node : roots) { - if (findBoundingBox(node, filename)) + if (findBoundingBox(*node, filename)) { const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents); const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter); @@ -164,8 +164,8 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // from the collision data present in every root node. for (const Nif::Node* node : roots) { - bool autogenerated = hasAutoGeneratedCollision(node); - handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated); + bool autogenerated = hasAutoGeneratedCollision(*node); + handleNode(filename, *node, 0, autogenerated, isAnimated, autogenerated); } if (mCompoundShape) @@ -198,41 +198,40 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // Find a boundingBox in the node hierarchy. // Return: use bounding box for collision? -bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& filename) +bool BulletNifLoader::findBoundingBox(const Nif::Node& node, const std::string& filename) { - if (node->hasBounds) + if (node.hasBounds) { - unsigned int type = node->bounds.type; + unsigned int type = node.bounds.type; switch (type) { case Nif::NiBoundingVolume::Type::BOX_BV: - mShape->mCollisionBox.mExtents = node->bounds.box.extents; - mShape->mCollisionBox.mCenter = node->bounds.box.center; + mShape->mCollisionBox.mExtents = node.bounds.box.extents; + mShape->mCollisionBox.mCenter = node.bounds.box.center; break; default: { std::stringstream warning; - warning << "Unsupported NiBoundingVolume type " << type << " in node " << node->recIndex; + warning << "Unsupported NiBoundingVolume type " << type << " in node " << node.recIndex; warning << " in file " << filename; warn(warning.str()); } } - if (node->flags & Nif::NiNode::Flag_BBoxCollision) + if (node.flags & Nif::NiNode::Flag_BBoxCollision) { return true; } } - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) + if (const Nif::NiNode *ninode = dynamic_cast(&node)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) { - if (findBoundingBox(list[i].getPtr(), filename)) + if (findBoundingBox(list[i].get(), filename)) return true; } } @@ -240,10 +239,9 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& return false; } -bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) +bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node& rootNode) { - const Nif::NiNode *ninode = dynamic_cast(rootNode); - if(ninode) + if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) @@ -258,32 +256,32 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) return true; } -void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags, +void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) { // TODO: allow on-the fly collision switching via toggling this flag - if (node->recType == Nif::RC_NiCollisionSwitch && !(node->flags & Nif::NiNode::Flag_ActiveCollision)) + if (node.recType == Nif::RC_NiCollisionSwitch && !(node.flags & Nif::NiNode::Flag_ActiveCollision)) return; // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. - flags |= node->flags; + flags |= node.flags; - if (!node->controller.empty() && node->controller->recType == Nif::RC_NiKeyframeController - && (node->controller->flags & Nif::NiNode::ControllerFlag_Active)) + if (!node.controller.empty() && node.controller->recType == Nif::RC_NiKeyframeController + && (node.controller->flags & Nif::NiNode::ControllerFlag_Active)) isAnimated = true; - isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); + isCollisionNode = isCollisionNode || (node.recType == Nif::RC_RootCollisionNode); // Don't collide with AvoidNode shapes - avoid = avoid || (node->recType == Nif::RC_AvoidNode); + avoid = avoid || (node.recType == Nif::RC_AvoidNode); // We encountered a RootCollisionNode inside autogenerated mesh. It is not right. - if (node->recType == Nif::RC_RootCollisionNode && autogenerated) + if (node.recType == Nif::RC_RootCollisionNode && autogenerated) Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName << ". Treating it as a common NiTriShape."; // Check for extra data - for (Nif::ExtraPtr e = node->extra; !e.empty(); e = e->next) + for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->next) { if (e->recType == Nif::RC_NiStringExtraData) { @@ -310,61 +308,58 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape - || node->recType == Nif::RC_NiTriStrips - || node->recType == Nif::RC_BSLODTriShape)) + if(!node.hasBounds && (node.recType == Nif::RC_NiTriShape + || node.recType == Nif::RC_NiTriStrips + || node.recType == Nif::RC_BSLODTriShape)) { handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid); } } // For NiNodes, loop through children - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) + if (const Nif::NiNode *ninode = dynamic_cast(&node)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated, avoid); + handleNode(fileName, list[i].get(), flags, isCollisionNode, isAnimated, autogenerated, avoid); } } } -void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf &transform, +void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, int flags, const osg::Matrixf &transform, bool isAnimated, bool avoid) { - assert(nifNode != nullptr); - // If the object was marked "NCO" earlier, it shouldn't collide with // anything. So don't do anything. if ((flags & 0x800)) return; - auto niGeometry = static_cast(nifNode); - if (niGeometry->data.empty() || niGeometry->data->vertices.empty()) + const Nif::NiGeometry& niGeometry = static_cast(nifNode); + if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) return; - if (niGeometry->recType == Nif::RC_NiTriShape || niGeometry->recType == Nif::RC_BSLODTriShape) + if (niGeometry.recType == Nif::RC_NiTriShape || niGeometry.recType == Nif::RC_BSLODTriShape) { - if (niGeometry->data->recType != Nif::RC_NiTriShapeData) + if (niGeometry.data->recType != Nif::RC_NiTriShapeData) return; - auto data = static_cast(niGeometry->data.getPtr()); + auto data = static_cast(niGeometry.data.getPtr()); if (data->triangles.empty()) return; } - else if (niGeometry->recType == Nif::RC_NiTriStrips) + else if (niGeometry.recType == Nif::RC_NiTriStrips) { - if (niGeometry->data->recType != Nif::RC_NiTriStripsData) + if (niGeometry.data->recType != Nif::RC_NiTriStripsData) return; - auto data = static_cast(niGeometry->data.getPtr()); + auto data = static_cast(niGeometry.data.getPtr()); if (data->strips.empty()) return; } - if (!niGeometry->skin.empty()) + if (!niGeometry.skin.empty()) isAnimated = false; if (isAnimated) @@ -382,20 +377,16 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); - float scale = nifNode->trafo.scale; - const Nif::Node* parent = nifNode; - while (parent->parent) - { - parent = parent->parent; + float scale = nifNode.trafo.scale; + for (const Nif::Node* parent = nifNode.parent; parent != nullptr; parent = parent->parent) scale *= parent->trafo.scale; - } osg::Quat q = transform.getRotate(); osg::Vec3f v = transform.getTrans(); childShape->setLocalScaling(btVector3(scale, scale, scale)); btTransform trans(btQuaternion(q.x(), q.y(), q.z(), q.w()), btVector3(v.x(), v.y(), v.z())); - mShape->mAnimatedShapes.emplace(nifNode->recIndex, mCompoundShape->getNumChildShapes()); + mShape->mAnimatedShapes.emplace(nifNode.recIndex, mCompoundShape->getNumChildShapes()); mCompoundShape->addChildShape(trans, childShape.get()); childShape.release(); diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 3d6a95e09f..5a17a4240c 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -52,14 +52,14 @@ public: osg::ref_ptr load(const Nif::File& file); private: - bool findBoundingBox(const Nif::Node* node, const std::string& filename); + bool findBoundingBox(const Nif::Node& node, const std::string& filename); - void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, + void handleNode(const std::string& fileName, const Nif::Node& node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false); - bool hasAutoGeneratedCollision(const Nif::Node *rootNode); + bool hasAutoGeneratedCollision(const Nif::Node& rootNode); - void handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); + void handleNiTriShape(const Nif::Node& nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); std::unique_ptr mCompoundShape; From 4ac83f4c39beab8728a81b83903398efaa3f88d7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 31 Oct 2021 14:27:16 +0100 Subject: [PATCH 1618/2859] Add separate function to handle NiGeometry node To force use a single source of data. All fields of Nif::Node are available in NiGeometry. --- components/nifbullet/bulletnifloader.cpp | 13 +++++++++---- components/nifbullet/bulletnifloader.hpp | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index ed5eba819f..4bafa8af74 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -336,7 +336,12 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, int flags, cons if ((flags & 0x800)) return; - const Nif::NiGeometry& niGeometry = static_cast(nifNode); + handleNiTriShape(static_cast(nifNode), transform, isAnimated, avoid); +} + +void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const osg::Matrixf &transform, + bool isAnimated, bool avoid) +{ if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) return; @@ -377,8 +382,8 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, int flags, cons std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); - float scale = nifNode.trafo.scale; - for (const Nif::Node* parent = nifNode.parent; parent != nullptr; parent = parent->parent) + float scale = niGeometry.trafo.scale; + for (const Nif::Node* parent = niGeometry.parent; parent != nullptr; parent = parent->parent) scale *= parent->trafo.scale; osg::Quat q = transform.getRotate(); osg::Vec3f v = transform.getTrans(); @@ -386,7 +391,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, int flags, cons btTransform trans(btQuaternion(q.x(), q.y(), q.z(), q.w()), btVector3(v.x(), v.y(), v.z())); - mShape->mAnimatedShapes.emplace(nifNode.recIndex, mCompoundShape->getNumChildShapes()); + mShape->mAnimatedShapes.emplace(niGeometry.recIndex, mCompoundShape->getNumChildShapes()); mCompoundShape->addChildShape(trans, childShape.get()); childShape.release(); diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 5a17a4240c..e0fec338c6 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -27,6 +27,7 @@ namespace Nif struct Transformation; struct NiTriShape; struct NiTriStrips; + struct NiGeometry; } namespace NifBullet @@ -61,6 +62,8 @@ private: void handleNiTriShape(const Nif::Node& nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); + void handleNiTriShape(const Nif::NiGeometry& nifNode, const osg::Matrixf& transform, bool isAnimated, bool avoid); + std::unique_ptr mCompoundShape; std::unique_ptr mStaticMesh; From 4e8e8304aae8834aa8868548e18aba42bf79a2fc Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 31 Oct 2021 14:59:35 +0100 Subject: [PATCH 1619/2859] Avoid mesh allocation when data is invalid --- .../nifloader/testbulletnifloader.cpp | 3 +- components/nifbullet/bulletnifloader.cpp | 61 +++++++++++-------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 3d9b56c930..eec09d9a5d 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -1159,8 +1159,7 @@ namespace EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); const auto result = mLoader.load(mNifFile); - Resource::BulletShape expected; - expected.mCollisionShape.reset(new btCompoundShape); + const Resource::BulletShape expected; EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 4bafa8af74..655199afb9 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -1,6 +1,7 @@ #include "bulletnifloader.hpp" #include +#include #include #include @@ -99,12 +100,38 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co } } -void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry& geometry, const osg::Matrixf &transform = osg::Matrixf()) +template +auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) + -> decltype(function(static_cast(geometry.data.get()))) { if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) - fillTriangleMesh(mesh, static_cast(geometry.data.get()), transform); - else if (geometry.recType == Nif::RC_NiTriStrips) - fillTriangleMesh(mesh, static_cast(geometry.data.get()), transform); + return function(static_cast(geometry.data.get())); + + if (geometry.recType == Nif::RC_NiTriStrips) + return function(static_cast(geometry.data.get())); + + return {}; +} + +std::monostate fillTriangleMesh(std::unique_ptr& mesh, const Nif::NiGeometry& geometry, const osg::Matrixf &transform) +{ + return handleNiGeometry(geometry, [&] (const auto& data) + { + if (mesh == nullptr) + mesh.reset(new btTriangleMesh(false)); + fillTriangleMesh(*mesh, data, transform); + return std::monostate {}; + }); +} + +std::unique_ptr makeChildMesh(const Nif::NiGeometry& geometry) +{ + return handleNiGeometry(geometry, [&] (const auto& data) + { + std::unique_ptr mesh(new btTriangleMesh); + fillTriangleMesh(*mesh, data, osg::Matrixf()); + return mesh; + }); } } @@ -369,16 +396,13 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const if (isAnimated) { + std::unique_ptr childMesh = makeChildMesh(niGeometry); + if (childMesh == nullptr || childMesh->getNumTriangles() == 0) + return; + if (!mCompoundShape) mCompoundShape.reset(new btCompoundShape); - std::unique_ptr childMesh(new btTriangleMesh); - - fillTriangleMesh(*childMesh, niGeometry); - - if (childMesh->getNumTriangles() == 0) - return; - std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); @@ -397,20 +421,9 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const childShape.release(); } else if (avoid) - { - if (!mAvoidStaticMesh) - mAvoidStaticMesh.reset(new btTriangleMesh(false)); - - fillTriangleMesh(*mAvoidStaticMesh, niGeometry, transform); - } + fillTriangleMesh(mAvoidStaticMesh, niGeometry, transform); else - { - if (!mStaticMesh) - mStaticMesh.reset(new btTriangleMesh(false)); - - // Static shape, just transform all vertices into position - fillTriangleMesh(*mStaticMesh, niGeometry, transform); - } + fillTriangleMesh(mStaticMesh, niGeometry, transform); } } // namespace NifBullet From 19843af704b53016ed303ec2dc8952bd36f817a0 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 31 Oct 2021 14:55:04 +0100 Subject: [PATCH 1620/2859] Combine data check with data handling logic --- components/nifbullet/bulletnifloader.cpp | 41 ++++++++++++------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 655199afb9..5edf69e17d 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -105,10 +105,28 @@ auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) -> decltype(function(static_cast(geometry.data.get()))) { if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) - return function(static_cast(geometry.data.get())); + { + if (geometry.data->recType != Nif::RC_NiTriShapeData) + return {}; + + auto data = static_cast(geometry.data.getPtr()); + if (data->triangles.empty()) + return {}; + + return function(static_cast(*data)); + } if (geometry.recType == Nif::RC_NiTriStrips) - return function(static_cast(geometry.data.get())); + { + if (geometry.data->recType != Nif::RC_NiTriStripsData) + return {}; + + auto data = static_cast(geometry.data.getPtr()); + if (data->strips.empty()) + return {}; + + return function(static_cast(*data)); + } return {}; } @@ -372,25 +390,6 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) return; - if (niGeometry.recType == Nif::RC_NiTriShape || niGeometry.recType == Nif::RC_BSLODTriShape) - { - if (niGeometry.data->recType != Nif::RC_NiTriShapeData) - return; - - auto data = static_cast(niGeometry.data.getPtr()); - if (data->triangles.empty()) - return; - } - else if (niGeometry.recType == Nif::RC_NiTriStrips) - { - if (niGeometry.data->recType != Nif::RC_NiTriStripsData) - return; - - auto data = static_cast(niGeometry.data.getPtr()); - if (data->strips.empty()) - return; - } - if (!niGeometry.skin.empty()) isAnimated = false; From 2b057f5c15f0224731ae052cc47a7720ab063a87 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 31 Oct 2021 20:34:45 +0100 Subject: [PATCH 1621/2859] Expect nif node children to have parent --- .../nifloader/testbulletnifloader.cpp | 26 ++++++++++++++++++- components/nifbullet/bulletnifloader.cpp | 8 ++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index eec09d9a5d..b37a8cd6c0 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -506,6 +506,7 @@ namespace mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -531,6 +532,7 @@ namespace mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.parent = &mNiNode; mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -561,11 +563,13 @@ namespace mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.parent = &mNiNode; mNode2.hasBounds = true; mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); + mNode2.parent = &mNiNode; mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -595,12 +599,14 @@ namespace mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.parent = &mNiNode; mNode2.hasBounds = true; mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); + mNode2.parent = &mNiNode; mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -679,6 +685,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) { + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -697,7 +704,9 @@ namespace TEST_F(TestBulletNifLoader, for_nested_tri_shape_child_should_return_shape_with_triangle_mesh_shape) { mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); + mNiNode2.parent = &mNiNode; mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiTriShape.parent = &mNiNode2; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); @@ -714,6 +723,8 @@ namespace TEST_F(TestBulletNifLoader, for_two_tri_shape_children_should_return_shape_with_triangle_mesh_shape_with_all_meshes) { + mNiTriShape.parent = &mNiNode; + mNiTriShape2.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2) @@ -736,6 +747,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_shape_with_triangle_mesh_shape) { mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -804,9 +816,11 @@ namespace { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; + mNiTriShape2.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), @@ -873,6 +887,7 @@ namespace mController.flags |= Nif::NiNode::ControllerFlag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; mNiTriShape2.parent = &mNiNode; @@ -889,7 +904,7 @@ namespace const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); - triangles->addTriangle(btVector3(1, 2, 3), btVector3(4, 2, 3), btVector3(4, 4.632747650146484375, 1.56172335147857666015625)); + triangles->addTriangle(btVector3(4, 8, 12), btVector3(16, 8, 12), btVector3(16, 18.5309906005859375, 6.246893405914306640625)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); mesh->setLocalScaling(btVector3(1, 1, 1)); @@ -938,6 +953,7 @@ namespace TEST_F(TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) { + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; @@ -957,6 +973,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -973,6 +990,7 @@ namespace { auto data = static_cast(mNiTriShape.data.getPtr()); data->triangles.clear(); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -990,6 +1008,7 @@ namespace mNiStringExtraData.string = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1008,6 +1027,7 @@ namespace mNiStringExtraData2.string = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1025,6 +1045,7 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1042,8 +1063,10 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parent = &mNiNode2; mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode2.recType = Nif::RC_RootCollisionNode; + mNiNode2.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); mNiNode.recType = Nif::RC_NiNode; @@ -1134,6 +1157,7 @@ namespace TEST_F(TestBulletNifLoader, for_avoid_collision_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; mNiTriStripsData.strips.front() = {0, 1}; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 5edf69e17d..6678d8ff74 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -1,5 +1,6 @@ #include "bulletnifloader.hpp" +#include #include #include @@ -367,8 +368,11 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { - if(!list[i].empty()) - handleNode(fileName, list[i].get(), flags, isCollisionNode, isAnimated, autogenerated, avoid); + if (list[i].empty()) + continue; + + assert(list[i].get().parent == &node); + handleNode(fileName, list[i].get(), flags, isCollisionNode, isAnimated, autogenerated, avoid); } } } From 4657c655b10621f2be75a6fc60ea6dcb721d3727 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:02:06 +0000 Subject: [PATCH 1622/2859] refactors parentFileIndices (#3211) This PR aims to start addressing `ESM` design issues that have silenced errors we incorporated into groundcover `ESM` loading approaches. - We move the resolution of `parentFileIndices` from `ESMStore` to `ESMReader` as suggested in a `TODO` comment. - We improve a highly misleading comment which downplayed the significance of `parentFileIndices`. - We document important preconditions. - We move a user facing error message to the highest level and improve its context. - We remove an inappropriate `setGlobalReaderList` method. We now pass this reader list into the method that requires it. - We remove a thoroughly pointless optimisation of `Store`'s construction that has unnecessarily depended on `getGlobalReaderList`. There should be no functional changes for `master`, but this PR should remove an issue blocking PR #3208. --- apps/openmw/mwworld/esmloader.cpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 40 ++----------------- apps/openmw/mwworld/store.cpp | 5 --- apps/openmw/mwworld/worldimp.cpp | 21 ++++++++++ apps/openmw/mwworld/worldimp.hpp | 1 + apps/openmw_test_suite/mwworld/test_store.cpp | 13 +----- components/esm/esmreader.cpp | 27 ++++++++++++- components/esm/esmreader.hpp | 13 +++--- components/esmloader/load.cpp | 1 - 9 files changed, 60 insertions(+), 63 deletions(-) diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 06debf4a97..647f5d3609 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -42,7 +42,7 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index) ESM::ESMReader lEsm; lEsm.setEncoder(mEncoder); lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); + lEsm.resolveParentFileIndices(mEsm); lEsm.open(filepath.string()); mEsm[index] = lEsm; mStore.load(mEsm[index], &mListener); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 5ec95ecf47..183b13ca09 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include #include #include @@ -153,41 +151,9 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) ESM::Dialogue *dialogue = nullptr; // Land texture loading needs to use a separate internal store for each plugin. - // We set the number of plugins here to avoid continual resizes during loading, - // and so we can properly verify if valid plugin indices are being passed to the - // LandTexture Store retrieval methods. - mLandTextures.resize(esm.getGlobalReaderList()->size()); - - /// \todo Move this to somewhere else. ESMReader? - // Cache parent esX files by tracking their indices in the global list of - // all files/readers used by the engine. This will greaty accelerate - // refnumber mangling, as required for handling moved references. - const std::vector &masters = esm.getGameFiles(); - std::vector *allPlugins = esm.getGlobalReaderList(); - for (size_t j = 0; j < masters.size(); j++) { - const ESM::Header::MasterData &mast = masters[j]; - std::string fname = mast.name; - int index = ~0; - for (int i = 0; i < esm.getIndex(); i++) { - ESM::ESMReader& reader = allPlugins->at(i); - if (reader.getFileSize() == 0) - continue; // Content file in non-ESM format - const std::string candidate = reader.getContext().filename; - std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); - if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { - index = i; - break; - } - } - if (index == (int)~0) { - // Tried to load a parent file that has not been loaded yet. This is bad, - // the launcher should have taken care of this. - std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name - + ", but it has not been loaded yet. Please check your load order."; - esm.fail(fstring); - } - esm.addParentFileIndex(index); - } + // We set the number of plugins here so we can properly verify if valid plugin + // indices are being passed to the LandTexture Store retrieval methods. + mLandTextures.resize(mLandTextures.getSize()+1); // Loop through all records while(esm.hasMoreRecs()) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 32458a2e84..06ea97a859 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -293,11 +293,6 @@ namespace MWWorld //========================================================================= Store::Store() { - mStatic.emplace_back(); - LandTextureList <exl = mStatic[0]; - // More than enough to hold Morrowind.esm. Extra lists for plugins will we - // added on-the-fly in a different method. - ltexl.reserve(128); } const ESM::LandTexture *Store::search(size_t index, size_t plugin) const { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 34907691e9..d35f72e80c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -178,6 +178,10 @@ namespace MWWorld if (mEsm[0].getFormat() == 0) ensureNeededRecords(); + // TODO: We can and should validate before we call loadContentFiles(). + // Currently we validate here to prevent merge conflicts with groundcover ESMStore fixes. + validateMasterFiles(mEsm); + mCurrentDate.reset(new DateTimeManager()); fillGlobalVariables(); @@ -407,6 +411,23 @@ namespace MWWorld } } + void World::validateMasterFiles(const std::vector& readers) + { + for (const auto& esm : readers) + { + assert(esm.getGameFiles().size() == esm.getParentFileIndices().size()); + for (unsigned int i=0; i gmst; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index a795b4eafd..afad359cfd 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -157,6 +157,7 @@ namespace MWWorld void updateNavigatorObject(const MWPhysics::Object& object); void ensureNeededRecords(); + void validateMasterFiles(const std::vector& readers); void fillGlobalVariables(); diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 3fe479587c..29240a1f7f 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -29,19 +29,14 @@ struct ContentFileTest : public ::testing::Test readContentFiles(); // load the content files - std::vector readerList; - readerList.resize(mContentFiles.size()); - int index=0; for (const auto & mContentFile : mContentFiles) { ESM::ESMReader lEsm; lEsm.setEncoder(nullptr); lEsm.setIndex(index); - lEsm.setGlobalReaderList(&readerList); lEsm.open(mContentFile.string()); - readerList[index] = lEsm; - mEsmStore.load(readerList[index], &dummyListener); + mEsmStore.load(lEsm, &dummyListener); ++index; } @@ -254,9 +249,6 @@ TEST_F(StoreTest, delete_test) record.mId = recordId; ESM::ESMReader reader; - std::vector readerList; - readerList.push_back(reader); - reader.setGlobalReaderList(&readerList); // master file inserts a record Files::IStreamPtr file = getEsmFile(record, false); @@ -297,9 +289,6 @@ TEST_F(StoreTest, overwrite_test) record.mId = recordId; ESM::ESMReader reader; - std::vector readerList; - readerList.push_back(reader); - reader.setGlobalReaderList(&readerList); // master file inserts a record Files::IStreamPtr file = getEsmFile(record, false); diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index dbf713315b..316748b53a 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -1,5 +1,8 @@ #include "esmreader.hpp" +#include +#include + #include namespace ESM @@ -17,7 +20,6 @@ ESM_Context ESMReader::getContext() ESMReader::ESMReader() : mRecordFlags(0) , mBuffer(50*1024) - , mGlobalReaderList(nullptr) , mEncoder(nullptr) , mFileSize(0) { @@ -55,6 +57,29 @@ void ESMReader::clearCtx() mCtx.subName.clear(); } +void ESMReader::resolveParentFileIndices(const std::vector& allPlugins) +{ + mCtx.parentFileIndices.clear(); + const std::vector &masters = getGameFiles(); + for (size_t j = 0; j < masters.size(); j++) { + const Header::MasterData &mast = masters[j]; + std::string fname = mast.name; + int index = getIndex(); + for (int i = 0; i < getIndex(); i++) { + const ESMReader& reader = allPlugins.at(i); + if (reader.getFileSize() == 0) + continue; // Content file in non-ESM format + const std::string candidate = reader.getName(); + std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); + if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { + index = i; + break; + } + } + mCtx.parentFileIndices.push_back(index); + } +} + void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) { close(); diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index a438dca0cd..92f2a6673b 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -80,13 +80,15 @@ public: // to the individual load() methods. This hack allows to pass this reference // indirectly to the load() method. void setIndex(const int index) { mCtx.index = index;} - int getIndex() {return mCtx.index;} + int getIndex() const {return mCtx.index;} - void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} - std::vector *getGlobalReaderList() {return mGlobalReaderList;} - - void addParentFileIndex(int index) { mCtx.parentFileIndices.push_back(index); } + // Assign parent esX files by tracking their indices in the global list of + // all files/readers used by the engine. This is required for correct adjustRefNum() results + // as required for handling moved, deleted and edited CellRefs. + /// @note Does not validate. + void resolveParentFileIndices(const std::vector& files); const std::vector& getParentFileIndices() const { return mCtx.parentFileIndices; } + bool isValidParentFileIndex(int i) const { return i != getIndex(); } /************************************************************************* * @@ -279,7 +281,6 @@ private: Header mHeader; - std::vector *mGlobalReaderList; ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp index 789e7619b6..9879f33274 100644 --- a/components/esmloader/load.cpp +++ b/components/esmloader/load.cpp @@ -214,7 +214,6 @@ namespace EsmLoader ESM::ESMReader& reader = readers[i]; reader.setEncoder(encoder); reader.setIndex(static_cast(i)); - reader.setGlobalReaderList(&readers); reader.open(collection.getPath(file).string()); loadEsm(query, readers[i], result); From fac84b5dd36387e6f96325723be545c15ba5de72 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 2 Nov 2021 20:13:41 +0000 Subject: [PATCH 1623/2859] restores ESM::Dialogue order (#3209) With this PR we restore the previous order of `ESM::Dialogue` entries implicitly changed by PR #3197. In the future we may want to consider additional verification and documentation of `mShared` order inconsistencies. We might additionally consider applying this sorting in the particular code that requires it. --- apps/openmw/mwworld/store.cpp | 3 +++ apps/openmw/mwworld/store.hpp | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 06ea97a859..5e3f4079c3 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1005,6 +1005,9 @@ namespace MWWorld mShared.reserve(mStatic.size()); for (auto & [_, dial] : mStatic) mShared.push_back(&dial); + // TODO: verify and document this inconsistent behaviour + // TODO: if we require this behaviour, maybe we should move it to the place that requires it + std::sort(mShared.begin(), mShared.end(), [](const ESM::Dialogue* l, const ESM::Dialogue* r) -> bool { return l->mId < r->mId; }); } template <> diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index dbb28258ef..4c32b65ec7 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -8,7 +8,8 @@ #include #include -#include "recordcmp.hpp" +#include +#include namespace ESM { @@ -150,9 +151,11 @@ namespace MWWorld { typedef std::unordered_map Static; Static mStatic; - std::vector mShared; // Preserves the record order as it came from the content files (this - // is relevant for the spell autocalc code and selection order - // for heads/hairs in the character creation) + /// @par mShared usually preserves the record order as it came from the content files (this + /// is relevant for the spell autocalc code and selection order + /// for heads/hairs in the character creation) + /// @warning ESM::Dialogue Store currently implements a sorted order for unknown reasons. + std::vector mShared; typedef std::unordered_map Dynamic; Dynamic mDynamic; From 2d3c6faec414b1f92729f4a1e1ef7bba919f4445 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 26 Oct 2021 08:59:11 +0000 Subject: [PATCH 1624/2859] Merge branch 'conditional_push_builds' into 'master' Add support for daily builds See merge request OpenMW/openmw!1314 (cherry picked from commit 50ea9869528c984b8ea66864fa08f5f710734eff) 1ee18b88 Update .gitlab-ci.yml file 603b0ad8 Update .gitlab-ci.yml a69cc468 Update .gitlab-ci.yml --- .gitlab-ci.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b69e930ae2..a07dec1d68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,9 @@ stages: - docker - linux image: debian:bullseye + rules: + - if: $CI_PIPELINE_SOURCE == "push" + .Debian: extends: .Debian_Image @@ -52,7 +55,7 @@ Coverity: extends: .Debian_Image stage: build rules: - - if: '$CI_PIPELINE_SOURCE == "schedule"' + - if: $CI_PIPELINE_SOURCE == "schedule" before_script: - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN @@ -70,7 +73,6 @@ Coverity: variables: CC: gcc CXX: g++ - artifacts: Debian_GCC: extends: .Debian @@ -162,6 +164,7 @@ Debian_Clang_tests_Debug: only: variables: - $CI_PROJECT_ID == "7107382" + - $CI_PIPELINE_SOURCE == "push" cache: paths: - ccache/ @@ -213,6 +216,8 @@ variables: &tests-targets .Windows_Ninja_Base: tags: - windows + rules: + - if: $CI_PIPELINE_SOURCE == "push" before_script: - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 @@ -329,6 +334,8 @@ Windows_Ninja_Tests_RelWithDebInfo: .Windows_MSBuild_Base: tags: - windows + rules: + - if: $CI_PIPELINE_SOURCE == "push" before_script: - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 @@ -389,6 +396,15 @@ Windows_Ninja_Tests_RelWithDebInfo: - MSVC2019_64/*/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*/*.log +Daily_Windows_MSBuild_Engine_Release:on-schedule: + extends: + - .Windows_MSBuild_Base + variables: + <<: *engine-targets + config: "Release" + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + Windows_MSBuild_Engine_Release: extends: - .Windows_MSBuild_Base @@ -444,6 +460,8 @@ Debian_AndroidNDK_arm64-v8a: tags: - linux image: debian:bullseye + rules: + - if: $CI_PIPELINE_SOURCE == "push" variables: CCACHE_SIZE: 3G cache: From f684c1da528106abf59e0aacdea93552c59adb36 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 4 Nov 2021 09:15:05 +0000 Subject: [PATCH 1625/2859] fixes assertion (#3215) This PR fixes an assertion introduced by #3211. For some reason my build originally did not contain assertions despite passing `DEBUG` into cmake, but after deleting `CMakeCache.txt` I have now hit the assertion @glassmancody reported as well. --- apps/openmw/mwworld/esmloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 647f5d3609..1917c41428 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -42,8 +42,8 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index) ESM::ESMReader lEsm; lEsm.setEncoder(mEncoder); lEsm.setIndex(index); - lEsm.resolveParentFileIndices(mEsm); lEsm.open(filepath.string()); + lEsm.resolveParentFileIndices(mEsm); mEsm[index] = lEsm; mStore.load(mEsm[index], &mListener); } From ec6af42fb6eb4eebde67c2199f14be5d2903f9dd Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 3 Nov 2021 16:31:21 +0100 Subject: [PATCH 1626/2859] Add CI job to find missing MRs --- .gitlab-ci.yml | 15 ++++ .resubmitted_merge_requests.txt | 5 ++ scripts/find_missing_merge_requests.py | 99 ++++++++++++++++++++++---- 3 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 .resubmitted_merge_requests.txt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a07dec1d68..18fbbd91a1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -493,3 +493,18 @@ Debian_AndroidNDK_arm64-v8a: # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 1h30m +FindMissingMergeRequests: + image: python:latest + stage: build + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + cache: + key: FindMissingMergeRequests.v1 + paths: + - .cache/pip + before_script: + - pip3 install --user requests click discord_webhook + script: + - scripts/find_missing_merge_requests.py --project_id=$CI_PROJECT_ID --ignored_mrs_path=$CI_PROJECT_DIR/.resubmitted_merge_requests.txt diff --git a/.resubmitted_merge_requests.txt b/.resubmitted_merge_requests.txt new file mode 100644 index 0000000000..01af064708 --- /dev/null +++ b/.resubmitted_merge_requests.txt @@ -0,0 +1,5 @@ +1314 +1216 +1172 +1160 +1051 diff --git a/scripts/find_missing_merge_requests.py b/scripts/find_missing_merge_requests.py index 09d3e9a581..be54750098 100755 --- a/scripts/find_missing_merge_requests.py +++ b/scripts/find_missing_merge_requests.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 import click +import discord_webhook import multiprocessing +import os import pathlib import requests import urllib.parse @@ -24,18 +26,23 @@ import urllib.parse help='End before given /merge_requests page.') @click.option('--per_page', type=int, default=100, help='Number of merge requests per page.') -def main(token_path, project_id, host, workers, target_branch, begin_page, end_page, per_page): - token = read_token(token_path) +@click.option('--ignored_mrs_path', type=str, + help='Path to a list of ignored MRs.') +def main(token_path, project_id, host, workers, target_branch, begin_page, end_page, per_page, ignored_mrs_path): + headers = make_headers(token_path) base_url = f'https://{host}/api/v4/projects/{project_id}/' + discord_webhook_url = os.getenv('DISCORD_WEBHOOK_URL') + ignored_mrs = frozenset(read_ignored_mrs(ignored_mrs_path)) checked = 0 filtered = 0 missing = 0 + missing_mrs = list() for page in range(begin_page, end_page): - merge_requests = requests.get( + merge_requests = parse_gitlab_response(requests.get( url=urllib.parse.urljoin(base_url, 'merge_requests'), - headers={'PRIVATE-TOKEN': token}, + headers=headers, params=dict(state='merged', per_page=per_page, page=page), - ).json() + )) if not merge_requests: break checked += len(merge_requests) @@ -44,24 +51,63 @@ def main(token_path, project_id, host, workers, target_branch, begin_page, end_p continue filtered += len(merge_requests) with multiprocessing.Pool(workers) as pool: - missing_merge_requests = pool.map(FilterMissingMergeRequest(token, base_url), merge_requests) + missing_merge_requests = pool.map(FilterMissingMergeRequest(headers, base_url), merge_requests) for mr in missing_merge_requests: - if mr is not None: - missing += 1 - print(f"MR {mr['reference']} ({mr['id']}) is missing from branch {mr['target_branch']}," + if mr is None: + continue + if mr['reference'] in ignored_mrs or mr['reference'].strip('!') in ignored_mrs: + print(f"Ignored MR {mr['reference']} ({mr['id']}) is missing from branch {mr['target_branch']}," f" previously was merged as {mr['merge_commit_sha']}") + continue + missing += 1 + missing_mrs.append(mr) + print(f"MR {mr['reference']} ({mr['id']}) is missing from branch {mr['target_branch']}," + f" previously was merged as {mr['merge_commit_sha']}") print(f'Checked {checked} MRs ({filtered} with {target_branch} target branch), {missing} are missing') + if discord_webhook_url is not None and missing_mrs: + project_web_url = parse_gitlab_response(requests.get(url=base_url, headers=headers))['web_url'] + '/' + discord_message = format_discord_message(missing=missing, filtered=filtered, target_branch=target_branch, + project_web_url=project_web_url, missing_mrs=missing_mrs) + print('Sending Discord notification...') + print(discord_message) + discord_webhook.DiscordWebhook(url=discord_webhook_url, content=discord_message, rate_limit_retry=True).execute() + if missing_mrs: + exit(-1) + + +def format_discord_message(missing, filtered, target_branch, project_web_url, missing_mrs): + target_branch = format_link(target_branch, urllib.parse.urljoin(project_web_url, f'-/tree/{target_branch}')) + return ( + f'Found {missing} missing MRs out of {filtered} from {target_branch} target branch:\n' + + '\n'.join(format_missing_mr_message(v, project_web_url) for v in missing_mrs) + ) + + +def format_missing_mr_message(mr, project_web_url): + web_url = mr.get('web_url') + reference = mr['reference'] + target_branch = mr['target_branch'] + commit = mr['merge_commit_sha'] + if web_url is not None: + reference = format_link(reference, web_url) + target_branch = format_link(target_branch, urllib.parse.urljoin(project_web_url, f'-/tree/{target_branch}')) + commit = format_link(commit, urllib.parse.urljoin(project_web_url, f'-/commit/{commit}')) + return f"MR {reference} is missing from branch {target_branch}, previously was merged as {commit}" + + +def format_link(name, url): + return f'[{name}]({url})' class FilterMissingMergeRequest: - def __init__(self, token, base_url): - self.token = token + def __init__(self, headers, base_url): + self.headers = headers self.base_url = base_url def __call__(self, merge_request): commit_refs = requests.get( url=urllib.parse.urljoin(self.base_url, f"repository/commits/{merge_request['merge_commit_sha']}/refs"), - headers={'PRIVATE-TOKEN': self.token}, + headers=self.headers, ).json() if 'message' in commit_refs and commit_refs['message'] == '404 Commit Not Found': return merge_request @@ -73,10 +119,39 @@ def present_in_branch(commit_refs, branch): return bool(next((v for v in commit_refs if v['type'] == 'branch' and v['name'] == branch), None)) +def make_headers(token_path): + job_token = os.environ.get('CI_JOB_TOKEN') + if job_token is not None: + print('Using auth token from CI_JOB_TOKEN env') + return {'JOB_TOKEN': job_token} + if not os.path.exists(token_path): + print(f'Ignore absent token path: {token_path}') + return dict() + print(f'Using auth token from: {token_path}') + return {'PRIVATE-TOKEN': read_token(token_path)} + + +def read_ignored_mrs(path): + if path is None: + return + with open(path) as stream: + for line in stream: + yield line.strip() + + def read_token(path): with open(path) as stream: return stream.readline().strip() +def parse_gitlab_response(response): + response = response.json() + if isinstance(response, dict): + message = response.get('message') + if message is not None: + raise RuntimeError(f'Gitlab request has failed: {message}') + return response + + if __name__ == '__main__': main() From db04dee29da240f8f671cef0d9a565c6a0e3abdd Mon Sep 17 00:00:00 2001 From: wareya Date: Thu, 4 Nov 2021 10:09:48 -0400 Subject: [PATCH 1627/2859] Force MSVC to build in utf-8 mode --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b4e5609ae..f2ee87b2ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,6 +199,10 @@ if (WIN32) option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) endif() +if(MSVC) + add_compile_options("/utf-8") +endif() + # Dependencies find_package(OpenGL REQUIRED) From 4c3dd1a964eca65f1b68b15abd2877ba64768e4e Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Thu, 4 Nov 2021 08:42:22 -0700 Subject: [PATCH 1628/2859] remove particle node when resetting particle effect --- apps/openmw/mwrender/sky.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index eb7c4a3882..b6b0a3669b 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -535,7 +535,11 @@ namespace MWRender mRootNode->setNodeMask(enabled ? Mask_Sky : 0u); if (!enabled && mParticleNode && mParticleEffect) - mCurrentParticleEffect = {}; + { + mCurrentParticleEffect.clear(); + mParticleNode->removeChild(mParticleEffect); + mParticleEffect = nullptr; + } mEnabled = enabled; } From 3042c000c6d51cc9ed819325ccc8cc0622c8478f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:53:57 +0000 Subject: [PATCH 1629/2859] fixes setActorFade logic errors (#3195) * resets state updater to apply light settings (#3141) resets state updater to apply light settings With this PR we achieve the same effect with fewer lines of code. * fixes LightSource logic errors We currently update `LightSource::setActorFade` in `TransparencyUpdater`. There are several logic errors inherent in this approach: 1. We fail to update `LightSource::setActorFade` for off screen actors because their `TransparencyUpdater` cull callback is not invoked. 2. We fail to update `LightSource::setActorFade` in the instant that a `TransparencyUpdater` is removed. 3. We fail to update `setActorFade` when an `mExtraLightSource` is created after calling `Animation::setAlpha`. With this PR we avoid such issues by updating `LightSource::setActorFade` in `Animation::setAlpha` and `Animation::addExtraLightSource` instead. --- apps/openmw/mwrender/animation.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 91ef6cc975..ecfe65c575 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -389,11 +389,6 @@ namespace MWRender mAlpha = alpha; } - void setLightSource(const osg::ref_ptr& lightSource) - { - mLightSource = lightSource; - } - protected: void setDefaults(osg::StateSet* stateset) override { @@ -416,13 +411,10 @@ namespace MWRender { osg::Material* material = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha); - if (mLightSource) - mLightSource->setActorFade(mAlpha); } private: float mAlpha; - osg::ref_ptr mLightSource; }; struct Animation::AnimSource @@ -1485,6 +1477,7 @@ namespace MWRender bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior); + mExtraLightSource->setActorFade(mAlpha); } void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) @@ -1639,7 +1632,6 @@ namespace MWRender if (mTransparencyUpdater == nullptr) { mTransparencyUpdater = new TransparencyUpdater(alpha); - mTransparencyUpdater->setLightSource(mExtraLightSource); mObjectRoot->addCullCallback(mTransparencyUpdater); } else @@ -1650,6 +1642,8 @@ namespace MWRender mObjectRoot->removeCullCallback(mTransparencyUpdater); mTransparencyUpdater = nullptr; } + if (mExtraLightSource) + mExtraLightSource->setActorFade(alpha); } void Animation::setLightEffect(float effect) From 1979ee1491a2e8065a00e998b2b357445528c6c4 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:54:47 +0000 Subject: [PATCH 1630/2859] refactors hashed std::map (#3199) We currently apply a strange algorithm to `LightManager::mStateSetCache`. For some reason this algorithm inserts hashed keys into `std::map` in a way that fails to handle hash collisions and exhibits worse lookup complexity than `std::unordered_map`. With this PR we just use `std::unordered_map` here. --- components/sceneutil/lightmanager.cpp | 42 +++++++++++++++++---------- components/sceneutil/lightmanager.hpp | 8 +++-- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index f38fd80d26..a5ce448b75 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1,6 +1,8 @@ #include "lightmanager.hpp" #include +#include +#include #include #include @@ -1158,29 +1160,39 @@ namespace SceneUtil return mSun; } - osg::ref_ptr LightManager::getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix) + size_t LightManager::HashLightIdList::operator()(const LightIdList& lightIdList) const { - // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; - for (size_t i = 0; i < lightList.size(); ++i) - { - auto id = lightList[i]->mLightSource->getId(); - Misc::hashCombine(hash, id); + for (size_t i = 0; i < lightIdList.size(); ++i) + Misc::hashCombine(hash, lightIdList[i]); + return hash; + } - if (getLightingMethod() != LightingMethod::SingleUBO) - continue; + osg::ref_ptr LightManager::getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix) + { + // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) - if (getLightIndexMap(frameNum).find(id) != getLightIndexMap(frameNum).end()) - continue; + if (getLightingMethod() == LightingMethod::SingleUBO) + { + for (size_t i = 0; i < lightList.size(); ++i) + { + auto id = lightList[i]->mLightSource->getId(); + if (getLightIndexMap(frameNum).find(id) != getLightIndexMap(frameNum).end()) + continue; - int index = getLightIndexMap(frameNum).size() + 1; - updateGPUPointLight(index, lightList[i]->mLightSource, frameNum, viewMatrix); - getLightIndexMap(frameNum).emplace(lightList[i]->mLightSource->getId(), index); + int index = getLightIndexMap(frameNum).size() + 1; + updateGPUPointLight(index, lightList[i]->mLightSource, frameNum, viewMatrix); + getLightIndexMap(frameNum).emplace(id, index); + } } auto& stateSetCache = mStateSetCache[frameNum%2]; - auto found = stateSetCache.find(hash); + LightIdList lightIdList; + lightIdList.reserve(lightList.size()); + std::transform(lightList.begin(), lightList.end(), std::back_inserter(lightIdList), [] (const LightSourceViewBound* l) { return l->mLightSource->getId(); }); + + auto found = stateSetCache.find(lightIdList); if (found != stateSetCache.end()) { mStateSetGenerator->update(found->second, lightList, frameNum); @@ -1188,7 +1200,7 @@ namespace SceneUtil } auto stateset = mStateSetGenerator->generate(lightList, frameNum); - stateSetCache.emplace(hash, stateset); + stateSetCache.emplace(lightIdList, stateset); return stateset; } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index b518a4723c..4a7dc7dbe7 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -207,8 +207,12 @@ namespace SceneUtil using LightSourceViewBoundCollection = std::vector; std::map, LightSourceViewBoundCollection> mLightsInViewSpace; - // < Light list hash , StateSet > - using LightStateSetMap = std::map>; + using LightIdList = std::vector; + struct HashLightIdList + { + size_t operator()(const LightIdList&) const; + }; + using LightStateSetMap = std::unordered_map, HashLightIdList>; LightStateSetMap mStateSetCache[2]; std::vector> mDummies; From 6cf74f7041c120f25e27d54b579ee19bd62bc2a5 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:55:32 +0000 Subject: [PATCH 1631/2859] refactors ESM::Land (#3213) With this PR we reduce coupling, simplify code, encapsulate a variable and separate actual `ESM` data from its context. --- apps/opencs/model/world/collection.hpp | 4 ++-- apps/opencs/model/world/columnimp.cpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 2 +- apps/openmw/mwworld/store.cpp | 15 ++------------- apps/openmw/mwworld/store.hpp | 5 ++--- components/esm/loadland.cpp | 8 ++------ components/esm/loadland.hpp | 5 ++++- components/esmterrain/storage.hpp | 6 +----- 8 files changed, 15 insertions(+), 32 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 6ab9d7ff9d..d15a4ff54d 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -296,7 +296,7 @@ namespace CSMWorld const std::string& destination, const UniversalId::Type type) { int index = cloneRecordImp(origin, destination, type); - mRecords.at(index)->get().mPlugin = 0; + mRecords.at(index)->get().setPlugin(0); } template @@ -311,7 +311,7 @@ namespace CSMWorld int index = touchRecordImp(id); if (index >= 0) { - mRecords.at(index)->get().mPlugin = 0; + mRecords.at(index)->get().setPlugin(0); return true; } diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index bec5008d35..0244ab1e8d 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -52,7 +52,7 @@ namespace CSMWorld QVariant LandPluginIndexColumn::get(const Record& record) const { - return record.get().mPlugin; + return record.get().getPlugin(); } bool LandPluginIndexColumn::isEditable() const diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 183b13ca09..bafdc8f37d 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -153,7 +153,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) // Land texture loading needs to use a separate internal store for each plugin. // We set the number of plugins here so we can properly verify if valid plugin // indices are being passed to the LandTexture Store retrieval methods. - mLandTextures.resize(mLandTextures.getSize()+1); + mLandTextures.addPlugin(); // Loop through all records while(esm.hasMoreRecs()) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 5e3f4079c3..4d720a11a7 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -322,15 +322,13 @@ namespace MWWorld assert(plugin < mStatic.size()); return mStatic[plugin].size(); } - RecordId Store::load(ESM::ESMReader &esm, size_t plugin) + RecordId Store::load(ESM::ESMReader &esm) { ESM::LandTexture lt; bool isDeleted = false; lt.load(esm, isDeleted); - assert(plugin < mStatic.size()); - // Replace texture for records with given ID and index from all plugins. for (unsigned int i=0; i (int)ltexl.size()) ltexl.resize(lt.mIndex+1); @@ -352,10 +350,6 @@ namespace MWWorld return RecordId(ltexl[idx].mId, isDeleted); } - RecordId Store::load(ESM::ESMReader &esm) - { - return load(esm, esm.getIndex()); - } Store::iterator Store::begin(size_t plugin) const { assert(plugin < mStatic.size()); @@ -366,11 +360,6 @@ namespace MWWorld assert(plugin < mStatic.size()); return mStatic[plugin].end(); } - void Store::resize(size_t num) - { - if (mStatic.size() < num) - mStatic.resize(num); - } // Land //========================================================================= diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 4c32b65ec7..17a37c23ea 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -222,13 +222,12 @@ namespace MWWorld const ESM::LandTexture *search(size_t index, size_t plugin) const; const ESM::LandTexture *find(size_t index, size_t plugin) const; - /// Resize the internal store to hold at least \a num plugins. - void resize(size_t num); + /// Resize the internal store to hold another plugin. + void addPlugin() { mStatic.emplace_back(); } size_t getSize() const override; size_t getSize(size_t plugin) const; - RecordId load(ESM::ESMReader &esm, size_t plugin); RecordId load(ESM::ESMReader &esm) override; iterator begin(size_t plugin) const; diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index d7dcd47c69..3c4a1e0b07 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -15,7 +15,6 @@ namespace ESM : mFlags(0) , mX(0) , mY(0) - , mPlugin(0) , mDataTypes(0) , mLandData(nullptr) { @@ -40,8 +39,6 @@ namespace ESM { isDeleted = false; - mPlugin = esm.getIndex(); - bool hasLocation = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) @@ -192,7 +189,7 @@ namespace ESM void Land::blank() { - mPlugin = 0; + setPlugin(0); std::fill(std::begin(mWnam), std::end(mWnam), 0); @@ -326,7 +323,7 @@ namespace ESM } Land::Land (const Land& land) - : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), + : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mContext (land.mContext), mDataTypes (land.mDataTypes), mLandData (land.mLandData ? new LandData (*land.mLandData) : nullptr) { @@ -345,7 +342,6 @@ namespace ESM std::swap (mFlags, land.mFlags); std::swap (mX, land.mX); std::swap (mY, land.mY); - std::swap (mPlugin, land.mPlugin); std::swap (mContext, land.mContext); std::swap (mDataTypes, land.mDataTypes); std::swap (mLandData, land.mLandData); diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 67dd2e76a2..610dd28fb8 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -29,7 +29,10 @@ struct Land int mFlags; // Only first four bits seem to be used, don't know what // they mean. int mX, mY; // Map coordinates. - int mPlugin; // Plugin index, used to reference the correct material palette. + + // Plugin index, used to reference the correct material palette. + int getPlugin() const { return mContext.index; } + void setPlugin(int index) { mContext.index = index; } // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 68e71574ee..107255a7af 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -36,11 +36,7 @@ namespace ESMTerrain return nullptr; return &mData; } - - inline int getPlugin() const - { - return mLand->mPlugin; - } + inline int getPlugin() const { return mLand->getPlugin(); } private: const ESM::Land* mLand; From cd946ea35a7f5e8d8ecfa873bea637d679d40c1c Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:56:51 +0000 Subject: [PATCH 1632/2859] removes unused recordcmp.hpp (#3214) This PR removes an unused source file. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwworld/recordcmp.hpp | 34 ------------------------------- 2 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 apps/openmw/mwworld/recordcmp.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index fe402cd0b7..f22204eb8c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -72,7 +72,7 @@ add_openmw_dir (mwworld containerstore actiontalk actiontake manualref player cellvisitors failedaction cells localscripts customdata inventorystore ptr actionopen actionread actionharvest actionequip timestamp actionalchemy cellstore actionapply actioneat - store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor + store esmstore fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref weather projectilemanager cellpreloader datetimemanager ) diff --git a/apps/openmw/mwworld/recordcmp.hpp b/apps/openmw/mwworld/recordcmp.hpp deleted file mode 100644 index f749351cea..0000000000 --- a/apps/openmw/mwworld/recordcmp.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef OPENMW_MWWORLD_RECORDCMP_H -#define OPENMW_MWWORLD_RECORDCMP_H - -#include - -#include - -namespace MWWorld -{ - struct RecordCmp - { - template - bool operator()(const T &x, const T& y) const { - return x.mId < y.mId; - } - }; - - template <> - inline bool RecordCmp::operator()(const ESM::Dialogue &x, const ESM::Dialogue &y) const { - return Misc::StringUtils::ciLess(x.mId, y.mId); - } - - template <> - inline bool RecordCmp::operator()(const ESM::Cell &x, const ESM::Cell &y) const { - return Misc::StringUtils::ciLess(x.mName, y.mName); - } - - template <> - inline bool RecordCmp::operator()(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const { - return Misc::StringUtils::ciLess(x.mCell, y.mCell); - } - -} // end namespace -#endif From ad44142ddad2a1482d73497cfe85cdf24ec26959 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 4 Nov 2021 20:41:26 +0100 Subject: [PATCH 1633/2859] Modify the content file sorting algorithm to finish in finite time when encountering circular dependencies --- .../contentselector/model/contentmodel.cpp | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 208b1315f3..fd94b60516 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -504,40 +504,27 @@ QStringList ContentSelectorModel::ContentModel::gameFiles() const void ContentSelectorModel::ContentModel::sortFiles() { - //first, sort the model such that all dependencies are ordered upstream (gamefile) first. - bool movedFiles = true; - int fileCount = mFiles.size(); - //Dependency sort - //iterate until no sorting of files occurs - while (movedFiles) + int sorted = 0; + //iterate each file, obtaining a reference to its gamefiles list + for(int i = sorted; i < mFiles.size(); ++i) { - movedFiles = false; - //iterate each file, obtaining a reference to it's gamefiles list - for (int i = 0; i < fileCount; i++) + const QStringList& gameFiles = mFiles.at(i)->gameFiles(); + int j = sorted; + for(;j > 0; --j) { - QModelIndex idx1 = index (i, 0, QModelIndex()); - const QStringList &gamefiles = mFiles.at(i)->gameFiles(); - //iterate each file after the current file, verifying that none of it's - //dependencies appear. - for (int j = i + 1; j < fileCount; j++) - { - if (gamefiles.contains(mFiles.at(j)->fileName(), Qt::CaseInsensitive) - || (!mFiles.at(i)->isGameFile() && gamefiles.isEmpty() - && mFiles.at(j)->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files - { - mFiles.move(j, i); - - QModelIndex idx2 = index (j, 0, QModelIndex()); - - emit dataChanged (idx1, idx2); - - movedFiles = true; - } - } - if (movedFiles) + const auto file = mFiles.at(j - 1); + if(gameFiles.contains(file->fileName(), Qt::CaseInsensitive) + || (!mFiles.at(i)->isGameFile() && gameFiles.isEmpty() + && file->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files break; } + if(i != j) + { + mFiles.move(i, j); + emit dataChanged(index(i, 0, QModelIndex()), index(j, 0, QModelIndex())); + } + ++sorted; } } From 1583252dd86751bbd5ecabd2f06ae5078440c19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Thu, 4 Nov 2021 15:54:33 -0400 Subject: [PATCH 1634/2859] Improve sound fading * Implement a more general SoundBase::setFade that can be used to fade to any desired volume and not just fading out * Implement SoundBase::setFadeout by using SoundBase::setFade * Implement an exponential fade mode --- apps/openmw/mwsound/sound.hpp | 111 +++++++++++++++++++++--- apps/openmw/mwsound/soundmanagerimp.cpp | 10 +-- 2 files changed, 101 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 17f052aec0..2a07f05779 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -11,7 +11,11 @@ namespace MWSound enum PlayModeEx { Play_2D = 0, + Play_StopAtFadeEnd = 1 << 28, + Play_FadeExponential = 1 << 29, + Play_InFade = 1 << 30, Play_3D = 1 << 31, + Play_FadeFlagsMask = (Play_StopAtFadeEnd | Play_FadeExponential), }; // For testing individual PlayMode flags @@ -21,13 +25,15 @@ namespace MWSound struct SoundParams { osg::Vec3f mPos; - float mVolume = 1; - float mBaseVolume = 1; - float mPitch = 1; - float mMinDistance = 1; - float mMaxDistance = 1000; + float mVolume = 1.0f; + float mBaseVolume = 1.0f; + float mPitch = 1.0f; + float mMinDistance = 1.0f; + float mMaxDistance = 1000.0f; int mFlags = 0; - float mFadeOutTime = 0; + float mFadeVolume = 1.0f; + float mFadeTarget = 0.0f; + float mFadeStep = 0.0f; }; class SoundBase { @@ -46,19 +52,97 @@ namespace MWSound void setPosition(const osg::Vec3f &pos) { mParams.mPos = pos; } void setVolume(float volume) { mParams.mVolume = volume; } void setBaseVolume(float volume) { mParams.mBaseVolume = volume; } - void setFadeout(float duration) { mParams.mFadeOutTime = duration; } - void updateFade(float duration) + void setFadeout(float duration) { setFade(duration, 0.0, Play_StopAtFadeEnd); } + + /// Fade to the given linear gain within the specified amount of time. + /// Note that the fade gain is independent of the sound volume. + /// + /// \param duration specifies the duration of the fade. For *linear* + /// fades (default) this will be exactly the time at which the desired + /// volume is reached. Let v0 be the initial volume, v1 be the target + /// volume, and t0 be the initial time. Then the volume over time is + /// given as + /// + /// v(t) = v0 + (v1 - v0) * (t - t0) / duration if t <= t0 + duration + /// v(t) = v1 if t > t0 + duration + /// + /// For *exponential* fades this determines the time-constant of the + /// exponential process describing the fade. In particular, we guarantee + /// that we reach v0 + 0.99 * (v1 - v0) within the given duration. + /// + /// v(t) = v1 + (v0 - v1) * exp(-4.6 * (t0 - t) / duration) + /// + /// where -4.6 is approximately log(1%) (i.e., -40 dB). + /// + /// This interpolation mode is meant for environmental sound effects to + /// achieve less jarring transitions. + /// + /// \param targetVolume is the linear gain that should be reached at + /// the end of the fade. + /// + /// \param flags may be a combination of Play_FadeExponential and + /// Play_StopAtFadeEnd. If Play_StopAtFadeEnd is set, stops the sound + /// once the fade duration has passed or the target volume has been + /// reached. If Play_FadeExponential is set, enables the exponential + /// fade mode (see above). + void setFade(float duration, float targetVolume, int flags = 0) { + // Approximation of log(1%) (i.e., -40 dB). + constexpr float minus40Decibel = -4.6f; + + // Do nothing if already at the target, unless we need to trigger a stop event + if ((mParams.mFadeVolume == targetVolume) && !(flags & Play_StopAtFadeEnd)) + return; + + mParams.mFadeTarget = targetVolume; + mParams.mFlags = (mParams.mFlags & ~Play_FadeFlagsMask) | (flags & Play_FadeFlagsMask) | Play_InFade; + if (duration > 0.0f) + { + if (mParams.mFlags & Play_FadeExponential) + mParams.mFadeStep = -minus40Decibel / duration; + else + mParams.mFadeStep = (mParams.mFadeTarget - mParams.mFadeVolume) / duration; + } + else + { + mParams.mFadeVolume = mParams.mFadeTarget; + mParams.mFadeStep = 0.0f; + } + } + + /// Updates the internal fading logic. + /// + /// \param dt is the time in seconds since the last call to update. + /// + /// \return true if the sound is still active, false if the sound has + /// reached a fading destination that was marked with Play_StopAtFadeEnd. + bool updateFade(float dt) { - if (mParams.mFadeOutTime > 0.0f) + // Mark fade as done at this volume difference (-80dB when fading to zero) + constexpr float minVolumeDifference = 1e-4f; + + if (!getInFade()) + return true; + + // Perform the actual fade operation + const float deltaBefore = mParams.mFadeTarget - mParams.mFadeVolume; + if (mParams.mFlags & Play_FadeExponential) + mParams.mFadeVolume += mParams.mFadeStep * deltaBefore * dt; + else + mParams.mFadeVolume += mParams.mFadeStep * dt; + const float deltaAfter = mParams.mFadeTarget - mParams.mFadeVolume; + + // Abort fade if we overshot or reached the minimum difference + if ((std::signbit(deltaBefore) != std::signbit(deltaAfter)) || (std::abs(deltaAfter) < minVolumeDifference)) { - float soundDuration = std::min(duration, mParams.mFadeOutTime); - mParams.mVolume *= (mParams.mFadeOutTime - soundDuration) / mParams.mFadeOutTime; - mParams.mFadeOutTime -= soundDuration; + mParams.mFadeVolume = mParams.mFadeTarget; + mParams.mFlags &= ~Play_InFade; } + + return getInFade() || !(mParams.mFlags & Play_StopAtFadeEnd); } const osg::Vec3f &getPosition() const { return mParams.mPos; } - float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume; } + float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume * mParams.mFadeVolume; } float getPitch() const { return mParams.mPitch; } float getMinDistance() const { return mParams.mMinDistance; } float getMaxDistance() const { return mParams.mMaxDistance; } @@ -69,6 +153,7 @@ namespace MWSound bool getIsLooping() const { return mParams.mFlags & MWSound::PlayMode::Loop; } bool getDistanceCull() const { return mParams.mFlags & MWSound::PlayMode::RemoveAtDistance; } bool getIs3D() const { return mParams.mFlags & Play_3D; } + bool getInFade() const { return mParams.mFlags & Play_InFade; } void init(const SoundParams& params) { diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 96bfc27951..8cdc43d2f4 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -897,7 +897,7 @@ namespace MWSound } } - if(!mOutput->isSoundPlaying(sound)) + if(!sound->updateFade(duration) || !mOutput->isSoundPlaying(sound)) { mOutput->finishSound(sound); if (sound == mUnderwaterSound) @@ -909,8 +909,6 @@ namespace MWSound } else { - sound->updateFade(duration); - mOutput->updateSound(sound); ++sndidx; } @@ -939,15 +937,13 @@ namespace MWSound } } - if(!mOutput->isStreamPlaying(sound)) + if(!sound->updateFade(duration) || !mOutput->isStreamPlaying(sound)) { mOutput->finishStream(sound); - mActiveSaySounds.erase(sayiter++); + sayiter = mActiveSaySounds.erase(sayiter); } else { - sound->updateFade(duration); - mOutput->updateStream(sound); ++sayiter; } From 6f2e311c58c0218a5cce7ea54905a73b9644a543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Thu, 4 Nov 2021 15:56:05 -0400 Subject: [PATCH 1635/2859] Fade out sound sources that are no longer close * Handle culling of all sound sources in a separate function cull3DSound * Add two new settings Sound::sfx fade in duration and Sound::sfx fade out duration --- apps/openmw/mwsound/soundmanagerimp.cpp | 71 ++++++++++++++++++------- apps/openmw/mwsound/soundmanagerimp.hpp | 3 ++ 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 8cdc43d2f4..e470507e55 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -33,6 +33,8 @@ namespace MWSound namespace { constexpr float sMinUpdateInterval = 1.0f / 30.0f; + constexpr float sSfxFadeInDuration = 1.0f; + constexpr float sSfxFadeOutDuration = 1.0f; WaterSoundUpdaterSettings makeWaterSoundUpdaterSettings() { @@ -47,6 +49,17 @@ namespace MWSound return settings; } + + float initialFadeVolume(float squaredDist, Sound_Buffer *sfx, Type type, PlayMode mode) + { + // If a sound is farther away than its maximum distance, start playing it with a zero fade volume. + // It can still become audible once the player moves closer. + const float maxDist = sfx->getMaxDist(); + if (squaredDist > (maxDist * maxDist)) + return 0.0f; + + return 1.0; + } } // For combining PlayMode and Type flags @@ -517,7 +530,8 @@ namespace MWSound return nullptr; const osg::Vec3f objpos(ptr.getRefData().getPosition().asVec3()); - if ((mode & PlayMode::RemoveAtDistance) && (mListenerPos - objpos).length2() > 2000 * 2000) + const float squaredDist = (mListenerPos - objpos).length2(); + if ((mode & PlayMode::RemoveAtDistance) && squaredDist > 2000 * 2000) return nullptr; // Look up the sound in the ESM data @@ -548,6 +562,7 @@ namespace MWSound params.mPos = objpos; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); + params.mFadeVolume = initialFadeVolume(squaredDist, sfx, type, mode); params.mPitch = pitch; params.mMinDistance = sfx->getMinDist(); params.mMaxDistance = sfx->getMaxDist(); @@ -576,12 +591,15 @@ namespace MWSound Sound_Buffer *sfx = mSoundBuffers.load(Misc::StringUtils::lowerCase(soundId)); if(!sfx) return nullptr; + const float squaredDist = (mListenerPos - initialPos).length2(); + SoundPtr sound = getSoundRef(); sound->init([&] { SoundParams params; params.mPos = initialPos; params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); + params.mFadeVolume = initialFadeVolume(squaredDist, sfx, type, mode); params.mPitch = pitch; params.mMinDistance = sfx->getMinDist(); params.mMaxDistance = sfx->getMaxDist(); @@ -830,6 +848,28 @@ namespace MWSound return {WaterSoundAction::DoNothing, nullptr}; } + void SoundManager::cull3DSound(SoundBase *sound) + { + // Hard-coded distance of 2000.0f is from vanilla Morrowind + const float maxDist = sound->getDistanceCull() ? 2000.0f : sound->getMaxDistance(); + const float squaredMaxDist = maxDist * maxDist; + + const osg::Vec3f pos = sound->getPosition(); + const float squaredDist = (mListenerPos - pos).length2(); + + if (squaredDist > squaredMaxDist) + { + // If getDistanceCull() is set, delete the sound after it has faded out + sound->setFade(sSfxFadeOutDuration, 0.0f, Play_FadeExponential | (sound->getDistanceCull() ? Play_StopAtFadeEnd : 0)); + } + else + { + // Fade sounds back in once they are in range + sound->setFade(sSfxFadeInDuration, 1.0f, Play_FadeExponential); + } + } + + void SoundManager::updateSounds(float duration) { // We update active say sounds map for specific actors here @@ -884,17 +924,12 @@ namespace MWSound { Sound *sound = sndidx->first.get(); - if(!ptr.isEmpty() && sound->getIs3D()) + if (sound->getIs3D()) { - const ESM::Position &pos = ptr.getRefData().getPosition(); - const osg::Vec3f objpos(pos.asVec3()); - sound->setPosition(objpos); - - if(sound->getDistanceCull()) - { - if((mListenerPos - objpos).length2() > 2000*2000) - mOutput->finishSound(sound); - } + if (!ptr.isEmpty()) + sound->setPosition(ptr.getRefData().getPosition().asVec3()); + + cull3DSound(sound); } if(!sound->updateFade(duration) || !mOutput->isSoundPlaying(sound)) @@ -924,17 +959,15 @@ namespace MWSound { MWWorld::ConstPtr ptr = sayiter->first; Stream *sound = sayiter->second.get(); - if(!ptr.isEmpty() && sound->getIs3D()) + if (sound->getIs3D()) { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); - sound->setPosition(pos); - - if(sound->getDistanceCull()) + if (!ptr.isEmpty()) { - if((mListenerPos - pos).length2() > 2000*2000) - mOutput->finishStream(sound); + MWBase::World *world = MWBase::Environment::get().getWorld(); + sound->setPosition(world->getActorHeadTransform(ptr).getTrans()); } + + cull3DSound(sound); } if(!sound->updateFade(duration) || !mOutput->isStreamPlaying(sound)) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 934402cd4c..e659e7a12a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -34,6 +34,7 @@ namespace MWSound { class Sound_Output; struct Sound_Decoder; + class SoundBase; class Sound; class Stream; @@ -111,6 +112,8 @@ namespace MWSound void advanceMusic(const std::string& filename); void startRandomTitle(); + void cull3DSound(SoundBase *sound); + void updateSounds(float duration); void updateRegionSound(float duration); void updateWaterSound(); From 9cfa2eeab80fefdb1690bd05f1de82a9b1dd573c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Thu, 4 Nov 2021 15:56:16 -0400 Subject: [PATCH 1636/2859] Remove hard maximum distance in the OpenAL driver Hard sound source culling is now handled by the SoundManager. --- apps/openmw/mwsound/openal_output.cpp | 11 +++-------- apps/openmw/mwsound/openal_output.hpp | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 67b52309d5..4615fed8c1 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1109,13 +1109,8 @@ void OpenAL_Output::initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat m alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } -void OpenAL_Output::updateCommon(ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv, bool is3d) +void OpenAL_Output::updateCommon(ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv) { - if(is3d) - { - if((pos - mListenerPos).length2() > maxdist*maxdist) - gain = 0.0f; - } if(useenv && mListenerEnv == Env_Underwater && !mWaterFilter) { gain *= 0.9f; @@ -1243,7 +1238,7 @@ void OpenAL_Output::updateSound(Sound *sound) ALuint source = GET_PTRID(sound->mHandle); updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), - sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); + sound->getPitch(), sound->getUseEnv()); getALError(); } @@ -1369,7 +1364,7 @@ void OpenAL_Output::updateStream(Stream *sound) ALuint source = stream->mSource; updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), - sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); + sound->getPitch(), sound->getUseEnv()); getALError(); } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 2a19e6768a..47845c0802 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -53,7 +53,7 @@ namespace MWSound void initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); void initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv); - void updateCommon(ALuint source, const osg::Vec3f &pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv, bool is3d); + void updateCommon(ALuint source, const osg::Vec3f &pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv); OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); From 1cafef7bdb8e8c7358bb528ee570ab0f8c3b8f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Thu, 4 Nov 2021 15:56:23 -0400 Subject: [PATCH 1637/2859] Fade water sounds more softly --- apps/openmw/mwsound/soundmanagerimp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index e470507e55..32c9a04e2a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -795,10 +795,10 @@ namespace MWSound break; case WaterSoundAction::SetVolume: mNearWaterSound->setVolume(update.mVolume * sfx->getVolume()); + mNearWaterSound->setFade(sSfxFadeInDuration, 1.0f, Play_FadeExponential); break; case WaterSoundAction::FinishSound: - mOutput->finishSound(mNearWaterSound); - mNearWaterSound = nullptr; + mNearWaterSound->setFade(sSfxFadeOutDuration, 0.0f, Play_FadeExponential | Play_StopAtFadeEnd); break; case WaterSoundAction::PlaySound: if (mNearWaterSound) From 3ee4aadd88f7a190b636021e6dd13ea9b414d873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Thu, 4 Nov 2021 15:56:30 -0400 Subject: [PATCH 1638/2859] Heuristic for fading environment sounds in --- apps/openmw/mwsound/soundmanagerimp.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 32c9a04e2a..758ddfdc56 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -58,6 +58,13 @@ namespace MWSound if (squaredDist > (maxDist * maxDist)) return 0.0f; + // This is a *heuristic* that causes environment sounds to fade in. The idea is the following: + // - Only looped sounds playing through the effects channel are environment sounds + // - Do not fade in sounds if the player is already so close that the sound plays at maximum volume + const float minDist = sfx->getMinDist(); + if ((squaredDist > (minDist * minDist)) && (type == Type::Sfx) && (mode & PlayMode::Loop)) + return 0.0f; + return 1.0; } } From e637d36071fec7dfa7acdab7967e40c3e5c30f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Thu, 4 Nov 2021 16:04:57 -0400 Subject: [PATCH 1639/2859] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c491cb83c0..a2f0c95f34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Bug #6326: Detect Enchantment/Key should detect items in unresolved containers Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures + Bug #6354: SFX abruptly cut off after crossing max distance; implement soft fading of sound effects Bug #6363: Some scripts in Morrowland fail to work Bug #6376: Creatures should be able to use torches Feature #890: OpenMW-CS: Column filtering From 03710726315f1e649db747cad37180596fdeb0fc Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 4 Nov 2021 21:20:06 +0000 Subject: [PATCH 1640/2859] removes lowerCaseInPlace (#3217) This PR removes unneeded `lowerCaseInPlace` calls in in a hot path of `objectpaging.cpp` that are no longer necessary after PR #3197. In addition, I have been informed that these changes should by coincidence address a compiler specific compilation error we currently experience. --- apps/openmw/mwrender/objectpaging.cpp | 2 -- components/misc/resourcehelpers.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 947f23f781..b6f12dea37 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -428,7 +428,6 @@ namespace MWRender continue; if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } @@ -444,7 +443,6 @@ namespace MWRender for (auto [ref, deleted] : cell->mLeasedRefs) { if (deleted) { refs.erase(ref.mRefNum); continue; } - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; refs[ref.mRefNum] = std::move(ref); diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 4d54baafc6..610c7a790c 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -142,5 +142,5 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string &resP bool Misc::ResourceHelpers::isHiddenMarker(std::string_view id) { - return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; + return Misc::StringUtils::ciEqual(id, "prisonmarker") || Misc::StringUtils::ciEqual(id, "divinemarker") || Misc::StringUtils::ciEqual(id, "templemarker") || Misc::StringUtils::ciEqual(id, "northmarker"); } From 5debd6e25af486d8234b9ece7d4270b9c63ee099 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 4 Nov 2021 21:31:22 +0000 Subject: [PATCH 1641/2859] removes two dummy serialisers (#3212) This PR removes dummy serialisers for `StateSetUpdater`, `NodeCallback` and the respective `META` macros that trigger serialisation requirement here. `StateSetUpdater` and `NodeCallback` are just base classes that can not be used on their own, so there is no need to incorporate them into serialisation. These changes might have minor effects on derived classes that forget to override `className()`, `libraryName()` through `META`, but it makes hardly a difference to now serialise such classes as a dysfunctional `osg::Callback` instead of a dysfunctional `SceneUtil::NodeCallback`. --- components/sceneutil/nodecallback.hpp | 1 - components/sceneutil/serialize.cpp | 2 -- components/sceneutil/statesetupdater.hpp | 2 -- 3 files changed, 5 deletions(-) diff --git a/components/sceneutil/nodecallback.hpp b/components/sceneutil/nodecallback.hpp index 942cb17ded..96e3ae229e 100644 --- a/components/sceneutil/nodecallback.hpp +++ b/components/sceneutil/nodecallback.hpp @@ -19,7 +19,6 @@ public: NodeCallback(){} NodeCallback(const NodeCallback& nc,const osg::CopyOp& copyop): osg::Callback(nc, copyop) {} - META_Object(SceneUtil, NodeCallback) bool run(osg::Object* object, osg::Object* data) override { diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 134f7c29dd..9da0d6a40e 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -125,11 +125,9 @@ void registerSerializers() "SceneUtil::CompositeStateSetUpdater", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", - "SceneUtil::NodeCallback", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", - "SceneUtil::StateSetUpdater", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index 35be9cb434..cc2e248457 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -34,8 +34,6 @@ namespace SceneUtil StateSetUpdater(); StateSetUpdater(const StateSetUpdater& copy, const osg::CopyOp& copyop); - META_Object(SceneUtil, StateSetUpdater) - void operator()(osg::Node* node, osg::NodeVisitor* nv); /// Apply state - to override in derived classes From d5aaa0394ab1a30d8f2ccfb14f5a61d7966bf694 Mon Sep 17 00:00:00 2001 From: wareya Date: Thu, 4 Nov 2021 10:44:28 -0400 Subject: [PATCH 1642/2859] Fix a missing required include --- apps/openmw/mwworld/ptr.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 438b87c7af..4dbdfa5545 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "livecellref.hpp" From ae84a0c9d5a68c6a7fa30c8fa4701ecc6308edd5 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 4 Nov 2021 23:21:58 +0100 Subject: [PATCH 1643/2859] Fix most of reStructuredText warnings --- .../manuals/installation/install-game-files.rst | 8 ++++---- docs/source/reference/documentationHowTo.rst | 6 +++--- docs/source/reference/modding/settings/game.rst | 2 +- docs/source/reference/modding/settings/general.rst | 2 +- .../reference/modding/settings/groundcover.rst | 2 ++ docs/source/reference/modding/settings/map.rst | 1 + docs/source/reference/modding/settings/navigator.rst | 10 +++++----- docs/source/reference/modding/settings/shaders.rst | 2 +- .../texture-modding/convert-bump-mapped-mods.rst | 12 ++++++------ 9 files changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 538cfd4c6d..2af7946536 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -105,10 +105,10 @@ If you are running macOS, you can also download Morrowind through Steam: #. Launch the Steam client and let it download. You can then find ``Morrowind.esm`` at ``~/Library/Application Support/Steam/steamapps/common/The Elder Scrolls III - Morrowind/Data Files/`` -Linux ----- -Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". ----- +Linux +----- +Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". +--------------------------------------------------------- #. Install Steam from "Ubuntu Software" Center #. Enable Proton (basically WINE under the hood). This is done in the Steam client menu drop down. Select, "Steam | Settings" then in the "SteamPlay" section check the box next to "enable steam play for all other titles" #. Now Morrowind should be selectable in your game list (as long as you own it). You can install it like any other game, choose to install it and remember the directory path of the location you pick. diff --git a/docs/source/reference/documentationHowTo.rst b/docs/source/reference/documentationHowTo.rst index 75dbe8dca2..d2b67d02ca 100644 --- a/docs/source/reference/documentationHowTo.rst +++ b/docs/source/reference/documentationHowTo.rst @@ -154,9 +154,9 @@ A push is just copying those "committed" changes to your online repo. (Commit and push can be combined in one step in PyCharm, so yay) Once you've pushed all the changes you need to contribute something to the project, you will then submit a pull request, so called because you are *requesting* that the project maintainers "pull" - and merge the changes you've made into the project master repository. One of the project maintainers will probably ask - you to make some corrections or clarifications. Go back and repeat this process to make those changes, - and repeat until they're good enough to get merged. +and merge the changes you've made into the project master repository. One of the project maintainers will probably ask +you to make some corrections or clarifications. Go back and repeat this process to make those changes, +and repeat until they're good enough to get merged. So to go over all that again. You rebase *every* time you start working on something to ensure you're working on the most updated version (I do literally every time I open PyCharm). Then make your edits. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index fb7b537701..58d5345f65 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -441,7 +441,7 @@ Some mods add harvestable container models. When this setting is enabled, activa When this setting is turned off or when activating a regular container, the menu will open as usual. allow actors to follow over water surface ---------------------- +----------------------------------------- :Type: boolean :Range: True/False diff --git a/docs/source/reference/modding/settings/general.rst b/docs/source/reference/modding/settings/general.rst index f0ebe4f972..ee5b908b4a 100644 --- a/docs/source/reference/modding/settings/general.rst +++ b/docs/source/reference/modding/settings/general.rst @@ -61,7 +61,7 @@ Mipmapping is a way of reducing the processing power needed during minification by pregenerating a series of smaller textures. notify on saved screenshot --------------- +-------------------------- :Type: boolean :Range: True/False diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst index 3e943e4284..7b060f58ad 100644 --- a/docs/source/reference/modding/settings/groundcover.rst +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -51,6 +51,7 @@ Determines whether grass should respond to the player treading on it. .. list-table:: Modes :header-rows: 1 + * - Mode number - Meaning * - 0 @@ -77,6 +78,7 @@ How far away from the player grass can be before it's unaffected by being trod o .. list-table:: Presets :header-rows: 1 + * - Preset number - Range (Units) - Distance (Units) diff --git a/docs/source/reference/modding/settings/map.rst b/docs/source/reference/modding/settings/map.rst index a4d3cd7e0d..1412d6584f 100644 --- a/docs/source/reference/modding/settings/map.rst +++ b/docs/source/reference/modding/settings/map.rst @@ -125,6 +125,7 @@ max local viewing distance This setting controls the viewing distance on local map when 'distant terrain' is enabled. If this setting is greater than the viewing distance then only up to the viewing distance is used for local map, otherwise the viewing distance is used. If view distance is changed in settings menu during the game, then viewable distance on the local map is not updated. + .. warning:: Increasing this setting can increase cell load times, because the localmap take a snapshot of each cell contained in a square of 2 x (max local viewing distance) + 1 square. diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index fee4b2626e..aea817530e 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -1,5 +1,5 @@ Navigator Settings -################ +################## Main settings ************* @@ -43,7 +43,7 @@ Increasing this value may decrease performance. It's a limitation of `Recastnavigation `_ library. wait until min distance to player ------------------------------- +--------------------------------- :Type: integer :Range: >= 0 @@ -87,7 +87,7 @@ Memory will be consumed in approximately linear dependency from number of nav me But only for new locations or already dropped from cache. min update interval ms ----------------- +---------------------- :Type: integer :Range: >= 0 @@ -181,7 +181,7 @@ Every nav mesh is visible and every update is noticable. Potentially decreases performance. enable agents paths render -------------------- +-------------------------- :Type: boolean :Range: True/False @@ -193,7 +193,7 @@ Works even if Navigator is disabled. Potentially decreases performance. enable recast mesh render ----------------------- +------------------------- :Type: boolean :Range: True/False diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 03b7805de6..296a351435 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -241,7 +241,7 @@ lighting` is on. This setting has no effect if :ref:`lighting method` is 'legacy'. minimum interior brightness ------------------------- +--------------------------- :Type: float :Range: 0.0-1.0 diff --git a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst index 0ad35d7a50..e05177c268 100644 --- a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst +++ b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst @@ -15,7 +15,7 @@ Normal maps from Morrowind to OpenMW - `Tutorial - Morrowind, Part 2`_ General introduction to normal map conversion ------------------------------------------------- +--------------------------------------------- :Authors: Joakim (Lysol) Berg, Alexei (Capo) Dobrohotov :Updated: 2020-03-03 @@ -34,7 +34,7 @@ There are several techniques for bump-mapping, and normal-mapping is the most co So let's get on with it. OpenMW normal-mapping -************************ +********************* Normal-mapping in OpenMW works in a very simple way: The engine just looks for a texture with a *_n.dds* suffix, and you're done. @@ -70,7 +70,7 @@ settings.cfg_-file. Add these rows where it would make sense: See OpenMW's wiki page about `texture modding`_ to read more about it. Morrowind bump-mapping -***************************************************** +********************** **Conversion difficulty:** *Varies. Sometimes quick and easy, sometimes time-consuming and hard.* @@ -93,7 +93,7 @@ In this case you can benefit from OpenMW's normal-mapping support by using these This means that you will have to drop the bump-mapping references from the model and sometimes rename the texture. MGE XE normal-mapping -*************************************** +********************* **Conversion difficulty:** *Easy* @@ -169,7 +169,7 @@ depending on a few circumstances. In this tutorial, we will look at a very easy, although in some cases a bit time-consuming, example. Tutorial - Morrowind, Part 1 -********************** +**************************** We will be converting a quite popular texture replacer of the Hlaalu architecture, namely Lougian's `Hlaalu Bump mapped`_. Since this is just a texture pack and not a model replacer, @@ -201,7 +201,7 @@ We ignored those model files since they are not needed with OpenMW. In this tuto we will convert a mod that includes new, custom-made models. In other words, we cannot just ignore those files this time. Tutorial - Morrowind, Part 2 -********************** +**************************** The sacks included in Apel's `Various Things - Sacks`_ come in two versions – without bump-mapping, and with bump-mapping. Since we want the glory of normal-mapping in our OpenMW setup, we will go with the bump-mapped version. From 1960e976e2640603f7f21af1b46d6ce65af50a52 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Fri, 5 Nov 2021 09:53:52 +0000 Subject: [PATCH 1644/2859] refactors stringops.hpp (#3192) With this PR we refactor `StringUtils::replaceAll` to accept `string_view` as suggested in a code comment. In addition, while we are touching this rebuild happy file, we slim it down a bit by moving a few sparingly used functions elsewhere. --- apps/openmw/mwgui/journalviewmodel.cpp | 4 +- apps/openmw/mwgui/sortfilteritemmodel.cpp | 15 ++- apps/openmw/mwgui/spellmodel.cpp | 9 +- apps/openmw/mwlua/actions.cpp | 2 + .../openmw_test_suite/misc/test_stringops.cpp | 3 +- components/files/escape.cpp | 4 +- components/misc/algorithm.hpp | 26 ++++ components/misc/stringops.hpp | 123 +----------------- components/misc/utf8stream.hpp | 65 +++++++++ components/resource/scenemanager.cpp | 3 +- components/sceneutil/lightmanager.cpp | 1 + 11 files changed, 119 insertions(+), 136 deletions(-) diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index 6b38cd0d9d..fc3fcc3efe 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -313,9 +313,9 @@ struct JournalViewModelImpl : JournalViewModel for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i) { Utf8Stream stream (i->first.c_str()); - Utf8Stream::UnicodeChar first = Misc::StringUtils::toLowerUtf8(stream.peek()); + Utf8Stream::UnicodeChar first = Utf8Stream::toLowerUtf8(stream.peek()); - if (first != Misc::StringUtils::toLowerUtf8(character)) + if (first != Utf8Stream::toLowerUtf8(character)) continue; visitor (i->second.getName()); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index eb7ebd0e43..9d6ed49d3d 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -1,6 +1,7 @@ #include "sortfilteritemmodel.hpp" #include +#include #include #include #include @@ -69,8 +70,8 @@ namespace return compareType(leftType, rightType); // compare items by name - std::string leftName = Misc::StringUtils::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase)); - std::string rightName = Misc::StringUtils::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase)); + std::string leftName = Utf8Stream::lowerCaseUtf8(left.mBase.getClass().getName(left.mBase)); + std::string rightName = Utf8Stream::lowerCaseUtf8(right.mBase.getClass().getName(right.mBase)); result = leftName.compare(rightName); if (result != 0) @@ -213,7 +214,7 @@ namespace MWGui if (!mNameFilter.empty()) { - const auto itemName = Misc::StringUtils::lowerCaseUtf8(base.getClass().getName(base)); + const auto itemName = Utf8Stream::lowerCaseUtf8(base.getClass().getName(base)); return itemName.find(mNameFilter) != std::string::npos; } @@ -226,7 +227,7 @@ namespace MWGui for (const auto& effect : effects) { - const auto ciEffect = Misc::StringUtils::lowerCaseUtf8(effect); + const auto ciEffect = Utf8Stream::lowerCaseUtf8(effect); if (ciEffect.find(mEffectFilter) != std::string::npos) return true; @@ -285,7 +286,7 @@ namespace MWGui return false; } - std::string compare = Misc::StringUtils::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase)); + std::string compare = Utf8Stream::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase)); if(compare.find(mNameFilter) == std::string::npos) return false; @@ -318,12 +319,12 @@ namespace MWGui void SortFilterItemModel::setNameFilter (const std::string& filter) { - mNameFilter = Misc::StringUtils::lowerCaseUtf8(filter); + mNameFilter = Utf8Stream::lowerCaseUtf8(filter); } void SortFilterItemModel::setEffectFilter (const std::string& filter) { - mEffectFilter = Misc::StringUtils::lowerCaseUtf8(filter); + mEffectFilter = Utf8Stream::lowerCaseUtf8(filter); } void SortFilterItemModel::update() diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 61ea9ce93a..455f167415 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -1,6 +1,7 @@ #include "spellmodel.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -69,7 +70,7 @@ namespace MWGui fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], ""); } - std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName); + std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName); if (convert.find(filter) != std::string::npos) { return true; @@ -90,14 +91,14 @@ namespace MWGui const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); - std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter); + std::string filter = Utf8Stream::lowerCaseUtf8(mFilter); for (const ESM::Spell* spell : spells) { if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) continue; - std::string name = Misc::StringUtils::lowerCaseUtf8(spell->mName); + std::string name = Utf8Stream::lowerCaseUtf8(spell->mName); if (name.find(filter) == std::string::npos && !matchingEffectExists(filter, spell->mEffects)) @@ -139,7 +140,7 @@ namespace MWGui if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce) continue; - std::string name = Misc::StringUtils::lowerCaseUtf8(item.getClass().getName(item)); + std::string name = Utf8Stream::lowerCaseUtf8(item.getClass().getName(item)); if (name.find(filter) == std::string::npos && !matchingEffectExists(filter, enchant->mEffects)) diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index aad70d1a57..92a7f915bb 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -1,5 +1,7 @@ #include "actions.hpp" +#include + #include #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw_test_suite/misc/test_stringops.cpp b/apps/openmw_test_suite/misc/test_stringops.cpp index 173cfa4447..7d8e93dc28 100644 --- a/apps/openmw_test_suite/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/misc/test_stringops.cpp @@ -1,5 +1,6 @@ #include #include "components/misc/stringops.hpp" +#include "components/misc/algorithm.hpp" #include #include @@ -18,7 +19,7 @@ struct PartialBinarySearchTest : public ::testing::Test bool matches(const std::string& keyword) { - return Misc::StringUtils::partialBinarySearch(mDataVec.begin(), mDataVec.end(), keyword) != mDataVec.end(); + return Misc::partialBinarySearch(mDataVec.begin(), mDataVec.end(), keyword) != mDataVec.end(); } }; diff --git a/components/files/escape.cpp b/components/files/escape.cpp index 8b11504d34..fcbcc04a16 100644 --- a/components/files/escape.cpp +++ b/components/files/escape.cpp @@ -29,10 +29,10 @@ namespace Files std::string temp = str; static const char hash[] = { escape_hash_filter::sEscape, escape_hash_filter::sHashIdentifier }; - Misc::StringUtils::replaceAll(temp, hash, "#", 2, 1); + Misc::StringUtils::replaceAll(temp, std::string_view(hash, 2), "#"); static const char escape[] = { escape_hash_filter::sEscape, escape_hash_filter::sEscapeIdentifier }; - Misc::StringUtils::replaceAll(temp, escape, "@", 2, 1); + Misc::StringUtils::replaceAll(temp, std::string_view(escape, 2), "@"); return temp; } diff --git a/components/misc/algorithm.hpp b/components/misc/algorithm.hpp index 4d70afa86c..54ac74e97e 100644 --- a/components/misc/algorithm.hpp +++ b/components/misc/algorithm.hpp @@ -4,6 +4,8 @@ #include #include +#include "stringops.hpp" + namespace Misc { template @@ -31,6 +33,30 @@ namespace Misc } return begin; } + + /// Performs a binary search on a sorted container for a string that 'key' starts with + template + static Iterator partialBinarySearch(Iterator begin, Iterator end, const T& key) + { + const Iterator notFound = end; + + while(begin < end) + { + const Iterator middle = begin + (std::distance(begin, end) / 2); + + int comp = Misc::StringUtils::ciCompareLen((*middle), key, (*middle).size()); + + if(comp == 0) + return middle; + else if(comp > 0) + end = middle; + else + begin = middle + 1; + } + + return notFound; + } + } #endif diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index dcffa7fdf3..12633db826 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -8,8 +8,6 @@ #include #include -#include "utf8stream.hpp" - namespace Misc { class StringUtils @@ -45,70 +43,6 @@ public: return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c; } - static Utf8Stream::UnicodeChar toLowerUtf8(Utf8Stream::UnicodeChar ch) - { - // Russian alphabet - if (ch >= 0x0410 && ch < 0x0430) - return ch + 0x20; - - // Cyrillic IO character - if (ch == 0x0401) - return ch + 0x50; - - // Latin alphabet - if (ch >= 0x41 && ch < 0x60) - return ch + 0x20; - - // Deutch characters - if (ch == 0xc4 || ch == 0xd6 || ch == 0xdc) - return ch + 0x20; - if (ch == 0x1e9e) - return 0xdf; - - // TODO: probably we will need to support characters from other languages - - return ch; - } - - static std::string lowerCaseUtf8(const std::string& str) - { - if (str.empty()) - return str; - - // Decode string as utf8 characters, convert to lower case and pack them to string - std::string out; - Utf8Stream stream (str.c_str()); - while (!stream.eof ()) - { - Utf8Stream::UnicodeChar character = toLowerUtf8(stream.peek()); - - if (character <= 0x7f) - out.append(1, static_cast(character)); - else if (character <= 0x7ff) - { - out.append(1, static_cast(0xc0 | ((character >> 6) & 0x1f))); - out.append(1, static_cast(0x80 | (character & 0x3f))); - } - else if (character <= 0xffff) - { - out.append(1, static_cast(0xe0 | ((character >> 12) & 0x0f))); - out.append(1, static_cast(0x80 | ((character >> 6) & 0x3f))); - out.append(1, static_cast(0x80 | (character & 0x3f))); - } - else - { - out.append(1, static_cast(0xf0 | ((character >> 18) & 0x07))); - out.append(1, static_cast(0x80 | ((character >> 12) & 0x3f))); - out.append(1, static_cast(0x80 | ((character >> 6) & 0x3f))); - out.append(1, static_cast(0x80 | (character & 0x3f))); - } - - stream.consume(); - } - - return out; - } - static bool ciLess(const std::string &x, const std::string &y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); } @@ -207,55 +141,21 @@ public: } }; - - /// Performs a binary search on a sorted container for a string that 'key' starts with - template - static Iterator partialBinarySearch(Iterator begin, Iterator end, const T& key) - { - const Iterator notFound = end; - - while(begin < end) - { - const Iterator middle = begin + (std::distance(begin, end) / 2); - - int comp = Misc::StringUtils::ciCompareLen((*middle), key, (*middle).size()); - - if(comp == 0) - return middle; - else if(comp > 0) - end = middle; - else - begin = middle + 1; - } - - return notFound; - } - /** @brief Replaces all occurrences of a string in another string. * * @param str The string to operate on. * @param what The string to replace. * @param with The replacement string. - * @param whatLen The length of the string to replace. - * @param withLen The length of the replacement string. - * * @return A reference to the string passed in @p str. */ - static std::string &replaceAll(std::string &str, const char *what, const char *with, - std::size_t whatLen=std::string::npos, std::size_t withLen=std::string::npos) + static std::string &replaceAll(std::string &str, std::string_view what, std::string_view with) { - if (whatLen == std::string::npos) - whatLen = strlen(what); - - if (withLen == std::string::npos) - withLen = strlen(with); - std::size_t found; std::size_t offset = 0; - while((found = str.find(what, offset, whatLen)) != std::string::npos) + while((found = str.find(what, offset)) != std::string::npos) { - str.replace(found, whatLen, with, withLen); - offset = found + withLen; + str.replace(found, what.size(), with); + offset = found + with.size(); } return str; } @@ -311,26 +211,11 @@ public: cont.push_back(str.substr(previous, current - previous)); } - // TODO: use the std::string_view once we will use the C++17. - // It should allow us to avoid data copying while we still will support both string and literal arguments. - - static inline void replaceAll(std::string& data, const std::string& toSearch, const std::string& replaceStr) - { - size_t pos = data.find(toSearch); - - while( pos != std::string::npos) - { - data.replace(pos, toSearch.size(), replaceStr); - pos = data.find(toSearch, pos + replaceStr.size()); - } - } - static inline void replaceLast(std::string& str, const std::string& substr, const std::string& with) { size_t pos = str.rfind(substr); if (pos == std::string::npos) return; - str.replace(pos, substr.size(), with); } diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index e499d15e60..9dc8aa8208 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -2,6 +2,7 @@ #define MISC_UTF8ITER_HPP #include +#include #include class Utf8Stream @@ -87,6 +88,70 @@ public: return std::make_pair (chr, cur); } + static UnicodeChar toLowerUtf8(UnicodeChar ch) + { + // Russian alphabet + if (ch >= 0x0410 && ch < 0x0430) + return ch + 0x20; + + // Cyrillic IO character + if (ch == 0x0401) + return ch + 0x50; + + // Latin alphabet + if (ch >= 0x41 && ch < 0x60) + return ch + 0x20; + + // German characters + if (ch == 0xc4 || ch == 0xd6 || ch == 0xdc) + return ch + 0x20; + if (ch == 0x1e9e) + return 0xdf; + + // TODO: probably we will need to support characters from other languages + + return ch; + } + + static std::string lowerCaseUtf8(const std::string& str) + { + if (str.empty()) + return str; + + // Decode string as utf8 characters, convert to lower case and pack them to string + std::string out; + Utf8Stream stream (str.c_str()); + while (!stream.eof ()) + { + UnicodeChar character = toLowerUtf8(stream.peek()); + + if (character <= 0x7f) + out.append(1, static_cast(character)); + else if (character <= 0x7ff) + { + out.append(1, static_cast(0xc0 | ((character >> 6) & 0x1f))); + out.append(1, static_cast(0x80 | (character & 0x3f))); + } + else if (character <= 0xffff) + { + out.append(1, static_cast(0xe0 | ((character >> 12) & 0x0f))); + out.append(1, static_cast(0x80 | ((character >> 6) & 0x3f))); + out.append(1, static_cast(0x80 | (character & 0x3f))); + } + else + { + out.append(1, static_cast(0xf0 | ((character >> 18) & 0x07))); + out.append(1, static_cast(0x80 | ((character >> 12) & 0x3f))); + out.append(1, static_cast(0x80 | ((character >> 6) & 0x3f))); + out.append(1, static_cast(0x80 | (character & 0x3f))); + } + + stream.consume(); + } + + return out; + } + private: static std::pair octet_count (unsigned char octet) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 4184a77c54..c1d567c0e1 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -20,6 +20,7 @@ #include #include +#include #include @@ -551,7 +552,7 @@ namespace Resource std::sort(reservedNames.begin(), reservedNames.end(), Misc::StringUtils::ciLess); } - std::vector::iterator it = Misc::StringUtils::partialBinarySearch(reservedNames.begin(), reservedNames.end(), name); + std::vector::iterator it = Misc::partialBinarySearch(reservedNames.begin(), reservedNames.end(), name); return it != reservedNames.end(); } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index a5ce448b75..448f6ca916 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1,6 +1,7 @@ #include "lightmanager.hpp" #include +#include #include #include From 49d2daee6a3562dc2e11c54541897c16e0cae6cd Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 5 Nov 2021 14:45:31 -0400 Subject: [PATCH 1645/2859] Movement solver tweaks 1) As much as I dislike it, upping the collision margin from 0.1 to 0.2 fixes bugs, particularly involving walking into upwards-slanted walls. 2) There were still some problems involving acute crevices/seams; they were using the adjusted instead of unadjusted normal, and also they need to bypass the don't-slide-upwards check to prevent (see #6379) 3) The move-away-from-what-we-just-hit code needs to run always, not just on non-initial iterations. No idea why I did it this way before. 4) Force bullet to give actor boxes a tiny collision margin of 0.001 instead of the default 0.04. I can't tell whether this is actually working or not, but it should reduce unexplained weirdness. 5) A piece of code that was meant to prevent bugs by short-circuiting the movement solver if its direction changed more than 180 degrees actually caused problems instead of preventing them, so I deleted it. --- apps/openmw/mwphysics/actor.cpp | 1 + apps/openmw/mwphysics/constants.hpp | 2 +- apps/openmw/mwphysics/movementsolver.cpp | 26 ++++++++++++++---------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 16501c432d..568da999bc 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -59,6 +59,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mOriginalHalfExtents.x() - mOriginalHalfExtents.y()) < 2.2; mConvexShape = static_cast(mShape.get()); + mConvexShape->setMargin(0.001); // make sure bullet isn't using the huge default convex shape margin of 0.04 mCollisionObject = std::make_unique(); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); diff --git a/apps/openmw/mwphysics/constants.hpp b/apps/openmw/mwphysics/constants.hpp index c275b63c7b..b2d189e874 100644 --- a/apps/openmw/mwphysics/constants.hpp +++ b/apps/openmw/mwphysics/constants.hpp @@ -15,7 +15,7 @@ namespace MWPhysics // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static constexpr int sMaxIterations = 8; // Allows for more precise movement solving without getting stuck or snagging too easily. - static constexpr float sCollisionMargin = 0.1f; + static constexpr float sCollisionMargin = 0.2f; // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues static constexpr float sAllowedPenetration = 0.0f; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fc2ea57b40..1c06c9f00f 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -252,6 +252,8 @@ namespace MWPhysics remainingTime *= (1.0f-tracer.mFraction); auto planeNormal = tracer.mPlaneNormal; + // need to know the unadjusted normal to handle certain types of seams properly + const auto origPlaneNormal = planeNormal; // If we touched the ground this frame, and whatever we ran into is a wall of some sort, // pretend that its collision normal is pointing horizontally @@ -275,10 +277,11 @@ namespace MWPhysics bool usedSeamLogic = false; // check for the current and previous collision planes forming an acute angle; slide along the seam if they do + // for this, we want to use the original plane normal, or else certain types of geometry will snag if(numTimesSlid > 0) { - auto dotA = lastSlideNormal * planeNormal; - auto dotB = lastSlideNormalFallback * planeNormal; + auto dotA = lastSlideNormal * origPlaneNormal; + auto dotB = lastSlideNormalFallback * origPlaneNormal; if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide dotB = 1.0; if(dotA <= 0.0 || dotB <= 0.0) @@ -291,14 +294,14 @@ namespace MWPhysics lastSlideNormal = lastSlideNormalFallback; } - auto constraintVector = bestNormal ^ planeNormal; // cross product + auto constraintVector = bestNormal ^ origPlaneNormal; // cross product if(constraintVector.length2() > 0) // only if it's not zero length { constraintVector.normalize(); newVelocity = project(velocity, constraintVector); // version of surface rejection for acute crevices/seams - auto averageNormal = bestNormal + planeNormal; + auto averageNormal = bestNormal + origPlaneNormal; averageNormal.normalize(); tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; @@ -309,27 +312,28 @@ namespace MWPhysics } // otherwise just keep the normal vector rejection - // if this isn't the first iteration, or if the first iteration is also the last iteration, // move away from the collision plane slightly, if possible // this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings // this is different from the normal collision margin, because the normal collision margin is along the movement path, // but this is along the collision normal - if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f)) + if(!usedSeamLogic) { tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; } // Do not allow sliding up steep slopes if there is gravity. - if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal)) + // The purpose of this is to prevent air control from letting you slide up tall, unwalkable slopes. + // For that purpose, it is not necessary to do it when trying to slide along acute seams/crevices (i.e. usedSeamLogic) + // and doing so would actually break air control in some situations where vanilla allows air control. + // Vanilla actually allows you to slide up slopes as long as you're in the "walking" animation, which can be true even + // in the air, so allowing this for seams isn't a compatibility break. + if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal) && !usedSeamLogic) newVelocity.z() = std::min(newVelocity.z(), velocity.z()); - if (newVelocity * origVelocity <= 0.0f) - break; - numTimesSlid += 1; lastSlideNormalFallback = lastSlideNormal; - lastSlideNormal = planeNormal; + lastSlideNormal = origPlaneNormal; velocity = newVelocity; } } From 5db4898beca5f03c4258877a02f5d09cb6d3d661 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Nov 2021 20:47:11 +0100 Subject: [PATCH 1646/2859] Fix tile bounds scaling OscillatingRecastMeshObject::update should be called with tile bounds in real coordinates not in navmesh. But proper scaling was done only in RecastMeshManager::getMesh and RecastMeshManager::updateObject used tile bounds in navmesh coordinates. Add a new function to create tile bounds with proper scaling and pass correct value into RecastMeshManager constructor through CachedRecastMeshManager constuctor from TileCachedRecastMeshManager member functions. --- .../cachedrecastmeshmanager.cpp | 5 ++- .../cachedrecastmeshmanager.hpp | 2 +- .../detournavigator/recastmeshmanager.cpp | 10 ++---- .../detournavigator/recastmeshmanager.hpp | 3 +- components/detournavigator/settingsutils.hpp | 11 +++++++ .../tilecachedrecastmeshmanager.cpp | 33 +++++++------------ .../tilecachedrecastmeshmanager.hpp | 5 ++- 7 files changed, 31 insertions(+), 38 deletions(-) diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index d82310dc38..2e1ba45959 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -3,9 +3,8 @@ namespace DetourNavigator { - CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds, - std::size_t generation) - : mImpl(settings, bounds, generation) + CachedRecastMeshManager::CachedRecastMeshManager(const TileBounds& bounds, std::size_t generation) + : mImpl(bounds, generation) {} bool CachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index b1e2570b0a..740e0b73f5 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -14,7 +14,7 @@ namespace DetourNavigator class CachedRecastMeshManager { public: - CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation); + explicit CachedRecastMeshManager(const TileBounds& bounds, std::size_t generation); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 2eeb90a9b5..6dcc2b2a42 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -28,9 +28,8 @@ namespace namespace DetourNavigator { - RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation) - : mSettings(settings) - , mGeneration(generation) + RecastMeshManager::RecastMeshManager(const TileBounds& bounds, std::size_t generation) + : mGeneration(generation) , mTileBounds(bounds) { } @@ -119,10 +118,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshManager::getMesh() const { - TileBounds tileBounds = mTileBounds; - tileBounds.mMin /= mSettings.mRecastScaleFactor; - tileBounds.mMax /= mSettings.mRecastScaleFactor; - RecastMeshBuilder builder(tileBounds); + RecastMeshBuilder builder(mTileBounds); using Object = std::tuple< osg::ref_ptr, std::reference_wrapper, diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index e1c1567cb3..33aba97bbf 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -34,7 +34,7 @@ namespace DetourNavigator class RecastMeshManager { public: - RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation); + explicit RecastMeshManager(const TileBounds& bounds, std::size_t generation); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); @@ -73,7 +73,6 @@ namespace DetourNavigator HeightfieldShape mShape; }; - const Settings& mSettings; const std::size_t mGeneration; const TileBounds mTileBounds; mutable std::mutex mMutex; diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 258eb64c65..6f15faaa3e 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -96,6 +96,17 @@ namespace DetourNavigator { return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; } + + inline TileBounds makeRealTileBoundsWithBorder(const Settings& settings, const TilePosition& tilePosition) + { + TileBounds result = makeTileBounds(settings, tilePosition); + const float border = getBorderSize(settings); + result.mMin -= osg::Vec2f(border, border); + result.mMax += osg::Vec2f(border, border); + result.mMin /= settings.mRecastScaleFactor; + result.mMax /= settings.mRecastScaleFactor; + return result; + } } #endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index d6e3e55005..f82ce9de83 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -18,12 +18,11 @@ namespace DetourNavigator const btTransform& transform, const AreaType areaType) { std::vector tilesPositions; - const auto border = getBorderSize(mSettings); { auto tiles = mTiles.lock(); getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) { - if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + if (addTile(id, shape, transform, areaType, tilePosition, tiles.get())) tilesPositions.push_back(tilePosition); }); } @@ -58,8 +57,6 @@ namespace DetourNavigator bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift) { - const auto border = getBorderSize(mSettings); - auto& tilesPositions = mWaterTilesPositions[cellPosition]; bool result = false; @@ -84,11 +81,9 @@ namespace DetourNavigator auto tile = tiles->find(tilePosition); if (tile == tiles->end()) { - auto tileBounds = makeTileBounds(mSettings, tilePosition); - tileBounds.mMin -= osg::Vec2f(border, border); - tileBounds.mMax += osg::Vec2f(border, border); - tile = tiles->insert(std::make_pair(tilePosition, - std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; + const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); + tile = tiles->emplace(tilePosition, + std::make_shared(tileBounds, mTilesGeneration)).first; } if (tile->second->addWater(cellPosition, cellSize, shift)) { @@ -133,8 +128,6 @@ namespace DetourNavigator bool TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, const HeightfieldShape& shape) { - const auto border = getBorderSize(mSettings); - auto& tilesPositions = mHeightfieldTilesPositions[cellPosition]; bool result = false; @@ -145,11 +138,9 @@ namespace DetourNavigator auto tile = tiles->find(tilePosition); if (tile == tiles->end()) { - auto tileBounds = makeTileBounds(mSettings, tilePosition); - tileBounds.mMin -= osg::Vec2f(border, border); - tileBounds.mMax += osg::Vec2f(border, border); - tile = tiles->insert(std::make_pair(tilePosition, - std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; + const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); + tile = tiles->emplace(tilePosition, + std::make_shared(tileBounds, mTilesGeneration)).first; } if (tile->second->addHeightfield(cellPosition, cellSize, shift, shape)) { @@ -219,17 +210,15 @@ namespace DetourNavigator } bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape, - const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, + const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles) { auto tile = tiles.find(tilePosition); if (tile == tiles.end()) { - auto tileBounds = makeTileBounds(mSettings, tilePosition); - tileBounds.mMin -= osg::Vec2f(border, border); - tileBounds.mMax += osg::Vec2f(border, border); - tile = tiles.insert(std::make_pair( - tilePosition, std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; + const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); + tile = tiles.emplace(tilePosition, + std::make_shared(tileBounds, mTilesGeneration)).first; } return tile->second->addObject(id, shape, transform, areaType); } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 7b5480e006..6b930c411f 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -33,7 +33,6 @@ namespace DetourNavigator if (object == mObjectsTilesPositions.end()) return false; auto& currentTiles = object->second; - const auto border = getBorderSize(mSettings); bool changed = false; std::vector newTiles; { @@ -49,7 +48,7 @@ namespace DetourNavigator changed = true; } } - else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + else if (addTile(id, shape, transform, areaType, tilePosition, tiles.get())) { newTiles.push_back(tilePosition); onChangedTile(tilePosition); @@ -113,7 +112,7 @@ namespace DetourNavigator std::size_t mTilesGeneration = 0; bool addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType, const TilePosition& tilePosition, float border, TilesMap& tiles); + const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles); bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles); From 3f80725ebe333ef4bb847b3fe0c173ad85c1a186 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Nov 2021 22:54:50 +0100 Subject: [PATCH 1647/2859] Remove duplicated implementation of Misc::Convert::toOsg --- apps/openmw/mwworld/scene.cpp | 4 ++-- components/detournavigator/gettilespositions.hpp | 6 ++++-- components/detournavigator/recastmeshbuilder.cpp | 2 +- components/misc/convert.hpp | 5 ----- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f9e792c7d2..769817810a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -147,12 +147,12 @@ namespace transform.getOrigin() ); - const auto start = Misc::Convert::makeOsgVec3f(closedDoorTransform(center + toPoint)); + const auto start = Misc::Convert::toOsg(closedDoorTransform(center + toPoint)); const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), ptr, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start; - const auto end = Misc::Convert::makeOsgVec3f(closedDoorTransform(center - toPoint)); + const auto end = Misc::Convert::toOsg(closedDoorTransform(center - toPoint)); const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), ptr, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end; diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 27c8f7a4ac..e8ba8beba9 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -46,13 +46,15 @@ namespace DetourNavigator btVector3 aabbMax; shape.getAabb(transform, aabbMin, aabbMax); - getTilesPositions(Misc::Convert::makeOsgVec3f(aabbMin), Misc::Convert::makeOsgVec3f(aabbMax), settings, std::forward(callback)); + getTilesPositions(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings, std::forward(callback)); } template void getTilesPositions(const int cellSize, const osg::Vec3f& shift, const Settings& settings, Callback&& callback) { + using Misc::Convert::toOsg; + const auto halfCellSize = cellSize / 2; const btTransform transform(btMatrix3x3::getIdentity(), Misc::Convert::toBullet(shift)); auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); @@ -64,7 +66,7 @@ namespace DetourNavigator aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); - getTilesPositions(Misc::Convert::makeOsgVec3f(aabbMin), Misc::Convert::makeOsgVec3f(aabbMax), settings, std::forward(callback)); + getTilesPositions(toOsg(aabbMin), toOsg(aabbMax), settings, std::forward(callback)); } } diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 73b731c247..8f860d2eb1 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -30,7 +30,7 @@ namespace DetourNavigator RecastMeshTriangle result; result.mAreaType = areaType; for (std::size_t i = 0; i < 3; ++i) - result.mVertices[i] = Misc::Convert::makeOsgVec3f(vertices[i]); + result.mVertices[i] = Misc::Convert::toOsg(vertices[i]); return result; } diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index 81270c0c0b..6f4a55cfcc 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -19,11 +19,6 @@ namespace Convert return osg::Vec3f(values[0], values[1], values[2]); } - inline osg::Vec3f makeOsgVec3f(const btVector3& value) - { - return osg::Vec3f(value.x(), value.y(), value.z()); - } - inline osg::Vec3f makeOsgVec3f(const ESM::Pathgrid::Point& value) { return osg::Vec3f(value.mX, value.mY, value.mZ); From 5e99454cc48f3dd05e07065361efffd0fa321d80 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Nov 2021 00:14:41 +0100 Subject: [PATCH 1648/2859] Add factory functions to create navigator implementations --- apps/openmw/mwworld/worldimp.cpp | 10 +++------- components/detournavigator/navigator.cpp | 14 ++++++++++++++ components/detournavigator/navigator.hpp | 4 ++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d35f72e80c..5bb3fa4b58 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -27,10 +27,7 @@ #include -#include -#include -#include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -197,12 +194,11 @@ namespace MWWorld { auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); navigatorSettings.mSwimHeightScale = mSwimHeightScale; - DetourNavigator::RecastGlobalAllocator::init(); - mNavigator.reset(new DetourNavigator::NavigatorImpl(navigatorSettings)); + mNavigator = DetourNavigator::makeNavigator(navigatorSettings); } else { - mNavigator.reset(new DetourNavigator::NavigatorStub()); + mNavigator = DetourNavigator::makeNavigatorStub(); } mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator)); diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 700217c52f..fc7b0ffb6d 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -1,6 +1,9 @@ #include "findrandompointaroundcircle.hpp" #include "navigator.hpp" #include "raycast.hpp" +#include "navigatorimpl.hpp" +#include "navigatorstub.hpp" +#include "recastglobalallocator.hpp" namespace DetourNavigator { @@ -33,4 +36,15 @@ namespace DetourNavigator return {}; return fromNavMeshCoordinates(settings, *result); } + + std::unique_ptr makeNavigator(const Settings& settings) + { + DetourNavigator::RecastGlobalAllocator::init(); + return std::make_unique(settings); + } + + std::unique_ptr makeNavigatorStub() + { + return std::make_unique(); + } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 265d69b6e1..0ab351981a 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -240,6 +240,10 @@ namespace DetourNavigator virtual float getMaxNavmeshAreaRealRadius() const = 0; }; + + std::unique_ptr makeNavigator(const Settings& settings); + + std::unique_ptr makeNavigatorStub(); } #endif From 9f808fbe3a1195ee6fa129a29cf9c394578f3f62 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Nov 2021 00:34:06 +0100 Subject: [PATCH 1649/2859] Move findPath-like navigator functions into a separate header --- apps/openmw/mwmechanics/aicombat.cpp | 4 +- apps/openmw/mwmechanics/aiwander.cpp | 5 +- apps/openmw/mwmechanics/pathfinding.cpp | 13 ++-- apps/openmw/mwworld/worldimp.cpp | 1 + .../detournavigator/navigator.cpp | 45 ++++++------ components/CMakeLists.txt | 1 + components/detournavigator/navigator.cpp | 32 --------- components/detournavigator/navigator.hpp | 61 +---------------- components/detournavigator/navigatorutils.cpp | 37 ++++++++++ components/detournavigator/navigatorutils.hpp | 68 +++++++++++++++++++ components/detournavigator/settings.hpp | 1 - 11 files changed, 143 insertions(+), 125 deletions(-) create mode 100644 components/detournavigator/navigatorutils.cpp create mode 100644 components/detournavigator/navigatorutils.hpp diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index eb139c918d..91a9b15b6f 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "../mwphysics/collisiontype.hpp" @@ -277,7 +277,7 @@ namespace MWMechanics // If there is no path, try to find a point on a line from the actor position to target projected // on navmesh to attack the target from there. const auto navigator = world->getNavigator(); - const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags); + const auto hit = DetourNavigator::raycast(*navigator, halfExtents, vActorPos, vTargetPos, navigatorFlags); if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack) { diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 9c84555404..53f050c0dd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include "../mwbase/world.hpp" @@ -337,7 +337,8 @@ namespace MWMechanics if (!isWaterCreature && !isFlyingCreature) { // findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance - if (const auto destination = navigator->findRandomPointAroundCircle(halfExtents, mInitialActorPosition, wanderDistance, navigatorFlags)) + if (const auto destination = DetourNavigator::findRandomPointAroundCircle(*navigator, halfExtents, + mInitialActorPosition, wanderDistance, navigatorFlags)) mDestination = *destination; else mDestination = getRandomPointAround(mInitialActorPosition, wanderRadius); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 4b06993a49..e727d8585d 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -3,8 +3,7 @@ #include #include -#include -#include +#include #include #include @@ -114,7 +113,7 @@ namespace bool operator()(const osg::Vec3f& start, const osg::Vec3f& end) const { - const auto position = mNavigator->raycast(mHalfExtents, start, end, mFlags); + const auto position = DetourNavigator::raycast(*mNavigator, mHalfExtents, start, end, mFlags); return position.has_value() && std::abs((position.value() - start).length2() - (end - start).length2()) <= 1; } }; @@ -422,8 +421,8 @@ namespace MWMechanics const auto world = MWBase::Environment::get().getWorld(); const auto stepSize = getPathStepSize(actor); const auto navigator = world->getNavigator(); - const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, areaCosts, - endTolerance, out); + const auto status = DetourNavigator::findPath(*navigator, halfExtents, stepSize, + startPoint, endPoint, flags, areaCosts, endTolerance, out); if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath) return DetourNavigator::Status::Success; @@ -455,8 +454,8 @@ namespace MWMechanics std::deque prePath; auto prePathInserter = std::back_inserter(prePath); const float endTolerance = 0; - const auto status = navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, areaCosts, - endTolerance, prePathInserter); + const auto status = DetourNavigator::findPath(*navigator, halfExtents, stepSize, + startPoint, mPath.front(), flags, areaCosts, endTolerance, prePathInserter); if (status == DetourNavigator::Status::NavMeshNotFound) return; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5bb3fa4b58..e1c20d652d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -28,6 +28,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index d4bdcb13b8..d7422cda8b 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -136,7 +137,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::NavMeshNotFound); EXPECT_EQ(mPath, std::deque()); } @@ -144,7 +145,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) { mNavigator->addAgent(mAgentHalfExtents); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::StartPolygonNotFound); } @@ -153,7 +154,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::StartPolygonNotFound); } @@ -173,7 +174,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -221,7 +222,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -255,7 +256,7 @@ namespace mPath.clear(); mOut = std::back_inserter(mPath); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -305,7 +306,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -342,7 +343,7 @@ namespace mPath.clear(); mOut = std::back_inserter(mPath); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -399,7 +400,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -486,7 +487,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -538,7 +539,7 @@ namespace mEnd.x() = 0; mEnd.z() = 300; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -583,7 +584,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -628,7 +629,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -673,7 +674,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -722,7 +723,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -775,7 +776,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -822,7 +823,7 @@ namespace Misc::Rng::init(42); - const auto result = mNavigator->findRandomPointAroundCircle(mAgentHalfExtents, mStart, 100.0, Flag_walk); + const auto result = findRandomPointAroundCircle(*mNavigator, mAgentHalfExtents, mStart, 100.0, Flag_walk); ASSERT_THAT(result, Optional(Vec3fEq(-198.909332275390625, 123.06096649169921875, 1.99998414516448974609375))) << (result ? *result : osg::Vec3f()); @@ -870,7 +871,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -954,7 +955,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - const auto result = mNavigator->raycast(mAgentHalfExtents, mStart, mEnd, Flag_walk); + const auto result = raycast(*mNavigator, mAgentHalfExtents, mStart, mEnd, Flag_walk); ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.99998295307159423828125))) << (result ? *result : osg::Vec3f()); @@ -1019,7 +1020,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( @@ -1069,7 +1070,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::PartialPath); EXPECT_THAT(mPath, ElementsAre( @@ -1116,7 +1117,7 @@ namespace const float endTolerance = 1000.0f; - EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, mOut), + EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a3f77d86bf..0b3c95ff45 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -196,6 +196,7 @@ add_component_dir(detournavigator offmeshconnectionsmanager preparednavmeshdata navmeshcacheitem + navigatorutils ) add_component_dir(loadinglistener diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index fc7b0ffb6d..1877c3dd93 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -1,42 +1,10 @@ -#include "findrandompointaroundcircle.hpp" #include "navigator.hpp" -#include "raycast.hpp" #include "navigatorimpl.hpp" #include "navigatorstub.hpp" #include "recastglobalallocator.hpp" namespace DetourNavigator { - std::optional Navigator::findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents, - const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const - { - const auto navMesh = getNavMesh(agentHalfExtents); - if (!navMesh) - return std::optional(); - const auto settings = getSettings(); - const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(), - toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, maxRadius), includeFlags, settings); - if (!result) - return std::optional(); - return std::optional(fromNavMeshCoordinates(settings, *result)); - } - - std::optional Navigator::raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, - const osg::Vec3f& end, const Flags includeFlags) const - { - const auto navMesh = getNavMesh(agentHalfExtents); - if (navMesh == nullptr) - return {}; - const auto settings = getSettings(); - const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), - toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, end), includeFlags, settings); - if (!result) - return {}; - return fromNavMeshCoordinates(settings, *result); - } - std::unique_ptr makeNavigator(const Settings& settings) { DetourNavigator::RecastGlobalAllocator::init(); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 0ab351981a..5edc12ed84 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -1,9 +1,6 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H -#include "findsmoothpath.hpp" -#include "flags.hpp" -#include "settings.hpp" #include "objectid.hpp" #include "navmeshcacheitem.hpp" #include "recastmeshtiles.hpp" @@ -12,8 +9,6 @@ #include -#include - namespace ESM { struct Cell; @@ -27,6 +22,8 @@ namespace Loading namespace DetourNavigator { + struct Settings; + struct ObjectShapes { osg::ref_ptr mShapeInstance; @@ -166,38 +163,6 @@ namespace DetourNavigator */ virtual void wait(Loading::Listener& listener, WaitConditionType waitConditionType) = 0; - /** - * @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through. - * @param agentHalfExtents allows to find navmesh for given actor. - * @param start path from given point. - * @param end path at given point. - * @param includeFlags setup allowed surfaces for actor to walk. - * @param out the beginning of the destination range. - * @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents - * @return Output iterator to the element in the destination range, one past the last element of found path. - * Equal to out if no path is found. - */ - template - Status findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start, - const osg::Vec3f& end, const Flags includeFlags, const DetourNavigator::AreaCosts& areaCosts, - float endTolerance, OutputIterator& out) const - { - static_assert( - std::is_same< - typename std::iterator_traits::iterator_category, - std::output_iterator_tag - >::value, - "out is not an OutputIterator" - ); - const auto navMesh = getNavMesh(agentHalfExtents); - if (!navMesh) - return Status::NavMeshNotFound; - const auto settings = getSettings(); - return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), - toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, endTolerance, out); - } - /** * @brief getNavMesh returns navmesh for specific agent half extents * @return navmesh @@ -214,28 +179,6 @@ namespace DetourNavigator virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; - /** - * @brief findRandomPointAroundCircle returns random location on navmesh within the reach of specified location. - * @param agentHalfExtents allows to find navmesh for given actor. - * @param start path from given point. - * @param maxRadius limit maximum distance from start. - * @param includeFlags setup allowed surfaces for actor to walk. - * @return not empty optional with position if point is found and empty optional if point is not found. - */ - std::optional findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents, - const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const; - - /** - * @brief raycast finds farest navmesh point from start on a line from start to end that has path from start. - * @param agentHalfExtents allows to find navmesh for given actor. - * @param start of the line - * @param end of the line - * @param includeFlags setup allowed surfaces for actor to walk. - * @return not empty optional with position if point is found and empty optional if point is not found. - */ - std::optional raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, - const osg::Vec3f& end, const Flags includeFlags) const; - virtual RecastMeshTiles getRecastMeshTiles() const = 0; virtual float getMaxNavmeshAreaRealRadius() const = 0; diff --git a/components/detournavigator/navigatorutils.cpp b/components/detournavigator/navigatorutils.cpp new file mode 100644 index 0000000000..82a108db6f --- /dev/null +++ b/components/detournavigator/navigatorutils.cpp @@ -0,0 +1,37 @@ +#include "navigatorutils.hpp" +#include "findrandompointaroundcircle.hpp" +#include "navigator.hpp" +#include "raycast.hpp" + +namespace DetourNavigator +{ + std::optional findRandomPointAroundCircle(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, + const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) + { + const auto navMesh = navigator.getNavMesh(agentHalfExtents); + if (!navMesh) + return std::nullopt; + const auto settings = navigator.getSettings(); + const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(), + toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), + toNavMeshCoordinates(settings, maxRadius), includeFlags, settings); + if (!result) + return std::nullopt; + return std::optional(fromNavMeshCoordinates(settings, *result)); + } + + std::optional raycast(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, + const osg::Vec3f& end, const Flags includeFlags) + { + const auto navMesh = navigator.getNavMesh(agentHalfExtents); + if (navMesh == nullptr) + return std::nullopt; + const auto settings = navigator.getSettings(); + const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), + toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), + toNavMeshCoordinates(settings, end), includeFlags, settings); + if (!result) + return std::nullopt; + return fromNavMeshCoordinates(settings, *result); + } +} diff --git a/components/detournavigator/navigatorutils.hpp b/components/detournavigator/navigatorutils.hpp new file mode 100644 index 0000000000..4ccc238f97 --- /dev/null +++ b/components/detournavigator/navigatorutils.hpp @@ -0,0 +1,68 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORUTILS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORUTILS_H + +#include "findsmoothpath.hpp" +#include "flags.hpp" +#include "settings.hpp" +#include "navigator.hpp" + +#include + +namespace DetourNavigator +{ + /** + * @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through. + * @param agentHalfExtents allows to find navmesh for given actor. + * @param start path from given point. + * @param end path at given point. + * @param includeFlags setup allowed surfaces for actor to walk. + * @param out the beginning of the destination range. + * @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents + * @return Output iterator to the element in the destination range, one past the last element of found path. + * Equal to out if no path is found. + */ + template + inline Status findPath(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start, + const osg::Vec3f& end, const Flags includeFlags, const DetourNavigator::AreaCosts& areaCosts, + float endTolerance, OutputIterator& out) + { + static_assert( + std::is_same< + typename std::iterator_traits::iterator_category, + std::output_iterator_tag + >::value, + "out is not an OutputIterator" + ); + const auto navMesh = navigator.getNavMesh(agentHalfExtents); + if (navMesh == nullptr) + return Status::NavMeshNotFound; + const auto settings = navigator.getSettings(); + return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), + toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), + toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, endTolerance, out); + } + + /** + * @brief findRandomPointAroundCircle returns random location on navmesh within the reach of specified location. + * @param agentHalfExtents allows to find navmesh for given actor. + * @param start path from given point. + * @param maxRadius limit maximum distance from start. + * @param includeFlags setup allowed surfaces for actor to walk. + * @return not empty optional with position if point is found and empty optional if point is not found. + */ + std::optional findRandomPointAroundCircle(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, + const osg::Vec3f& start, const float maxRadius, const Flags includeFlags); + + /** + * @brief raycast finds farest navmesh point from start on a line from start to end that has path from start. + * @param agentHalfExtents allows to find navmesh for given actor. + * @param start of the line + * @param end of the line + * @param includeFlags setup allowed surfaces for actor to walk. + * @return not empty optional with position if point is found and empty optional if point is not found. + */ + std::optional raycast(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, + const osg::Vec3f& end, const Flags includeFlags); +} + +#endif diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 800ad6b2bb..0ea35d9b49 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -2,7 +2,6 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #include -#include #include namespace DetourNavigator From 6b30d375fa1f646003d23571bbd9bbeeb84ad91d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Nov 2021 00:48:39 +0100 Subject: [PATCH 1650/2859] Replace detournavigator includes by forward declarations --- apps/openmw/engine.cpp | 2 -- apps/openmw/mwrender/actorspaths.cpp | 3 +++ apps/openmw/mwrender/actorspaths.hpp | 8 ++++++-- apps/openmw/mwrender/navmesh.cpp | 3 +++ apps/openmw/mwrender/navmesh.hpp | 13 ++++++++++--- apps/openmw/mwrender/recastmesh.cpp | 2 ++ apps/openmw/mwrender/recastmesh.hpp | 7 ++++++- 7 files changed, 30 insertions(+), 8 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 58dabd2cee..9f948de3eb 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -37,8 +37,6 @@ #include -#include - #include #include diff --git a/apps/openmw/mwrender/actorspaths.cpp b/apps/openmw/mwrender/actorspaths.cpp index 4e3bfd79a6..941f37df75 100644 --- a/apps/openmw/mwrender/actorspaths.cpp +++ b/apps/openmw/mwrender/actorspaths.cpp @@ -9,6 +9,9 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" + +#include + namespace MWRender { ActorsPaths::ActorsPaths(const osg::ref_ptr& root, bool enabled) diff --git a/apps/openmw/mwrender/actorspaths.hpp b/apps/openmw/mwrender/actorspaths.hpp index 1f61834d46..12f102093b 100644 --- a/apps/openmw/mwrender/actorspaths.hpp +++ b/apps/openmw/mwrender/actorspaths.hpp @@ -3,18 +3,22 @@ #include -#include - #include #include #include +#include namespace osg { class Group; } +namespace DetourNavigator +{ + struct Settings; +} + namespace MWRender { class ActorsPaths diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 523f7531af..548e85ad10 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -10,11 +10,14 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include + namespace MWRender { NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) : mRootNode(root) , mEnabled(enabled) + , mId(std::numeric_limits::max()) , mGeneration(0) , mRevision(0) { diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index d329b895d7..4c71dc74ec 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -1,16 +1,23 @@ #ifndef OPENMW_MWRENDER_NAVMESH_H #define OPENMW_MWRENDER_NAVMESH_H -#include - #include +#include + +class dtNavMesh; + namespace osg { class Group; class Geometry; } +namespace DetourNavigator +{ + struct Settings; +} + namespace MWRender { class NavMesh @@ -38,7 +45,7 @@ namespace MWRender private: osg::ref_ptr mRootNode; bool mEnabled; - std::size_t mId = std::numeric_limits::max(); + std::size_t mId; std::size_t mGeneration; std::size_t mRevision; osg::ref_ptr mGroup; diff --git a/apps/openmw/mwrender/recastmesh.cpp b/apps/openmw/mwrender/recastmesh.cpp index 91035907e9..f108536242 100644 --- a/apps/openmw/mwrender/recastmesh.cpp +++ b/apps/openmw/mwrender/recastmesh.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include diff --git a/apps/openmw/mwrender/recastmesh.hpp b/apps/openmw/mwrender/recastmesh.hpp index 729438dbe5..194ec04a62 100644 --- a/apps/openmw/mwrender/recastmesh.hpp +++ b/apps/openmw/mwrender/recastmesh.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWRENDER_RECASTMESH_H #define OPENMW_MWRENDER_RECASTMESH_H -#include +#include #include @@ -13,6 +13,11 @@ namespace osg class Geometry; } +namespace DetourNavigator +{ + struct Settings; +} + namespace MWRender { class RecastMesh From 7a0c13fcf87d5916f72b471b41ca833ebf05c811 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 6 Nov 2021 07:30:28 +0300 Subject: [PATCH 1651/2859] Make better use of std::clamp --- apps/opencs/view/render/object.cpp | 2 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 2 +- apps/openmw/mwgui/dialogue.cpp | 3 +-- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwgui/keyboardnavigation.cpp | 2 +- apps/openmw/mwgui/settingswindow.cpp | 4 ++-- apps/openmw/mwgui/statswindow.cpp | 3 +-- apps/openmw/mwinput/controllermanager.cpp | 2 +- apps/openmw/mwinput/mousemanager.cpp | 4 ++-- apps/openmw/mwmechanics/actors.cpp | 11 ++++------- apps/openmw/mwmechanics/character.cpp | 12 ++++++------ apps/openmw/mwmechanics/combat.cpp | 7 +++---- apps/openmw/mwmechanics/difficultyscaling.cpp | 4 +--- apps/openmw/mwmechanics/enchanting.cpp | 6 +++--- .../openmw/mwmechanics/mechanicsmanagerimp.cpp | 18 +++++++++--------- apps/openmw/mwmechanics/npcstats.cpp | 2 +- apps/openmw/mwmechanics/spelleffects.cpp | 2 +- apps/openmw/mwmechanics/spellutil.cpp | 5 ++++- apps/openmw/mwmechanics/weaponpriority.cpp | 3 +-- .../mwphysics/hasspherecollisioncallback.hpp | 6 +++--- apps/openmw/mwphysics/physicssystem.cpp | 2 +- apps/openmw/mwrender/camera.cpp | 6 +++--- apps/openmw/mwrender/localmap.cpp | 6 +++--- apps/openmw/mwrender/objectpaging.cpp | 8 ++------ apps/openmw/mwrender/renderingmanager.cpp | 5 ++--- apps/openmw/mwrender/water.cpp | 9 ++++----- apps/openmw/mwscript/aiextensions.cpp | 3 +-- apps/openmw/mwscript/skyextensions.cpp | 2 +- apps/openmw/mwsound/loudness.cpp | 5 ++--- apps/openmw/mwsound/volumesettings.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 ++-- components/esm/loadland.cpp | 2 +- components/fontloader/fontloader.cpp | 9 +++------ components/nifosg/controller.cpp | 2 +- components/sceneutil/shadow.cpp | 8 +++----- components/widgets/fontwrapper.hpp | 6 +----- components/widgets/numericeditbox.cpp | 2 +- 38 files changed, 80 insertions(+), 103 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 789fad0587..9f4fd29966 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -308,7 +308,7 @@ osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) const float OuterRadius = InnerRadius + MarkerShaftWidth; const float SegmentDistance = 100.f; - const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * osg::PI / SegmentDistance))); + const size_t SegmentCount = std::clamp(OuterRadius * 2 * osg::PI / SegmentDistance, 24, 64); const size_t VerticesPerSegment = 4; const size_t IndicesPerSegment = 24; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 8aa6acd8ed..5270c0143a 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -421,7 +421,7 @@ namespace MWDialogue // Clamp permanent disposition change so that final disposition doesn't go below 0 (could happen with intimidate) npcStats.setBaseDisposition(0); int zero = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor, false); - int disposition = std::min(100 - zero, std::max(mOriginalDisposition + mPermanentDispositionChange, -zero)); + int disposition = std::clamp(mOriginalDisposition + mPermanentDispositionChange, -zero, 100 - zero); npcStats.setBaseDisposition(disposition); } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 1da77f5d06..86cad0fa7a 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -347,8 +347,7 @@ namespace MWGui { if (!mScrollBar->getVisible()) return; - mScrollBar->setScrollPosition(std::min(static_cast(mScrollBar->getScrollRange()-1), - std::max(0, static_cast(mScrollBar->getScrollPosition() - _rel*0.3)))); + mScrollBar->setScrollPosition(std::clamp(mScrollBar->getScrollPosition() - _rel*0.3, 0, mScrollBar->getScrollRange() - 1)); onScrollbarMoved(mScrollBar, mScrollBar->getScrollPosition()); } diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index d0d2118c6e..ee0067f082 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -109,7 +109,7 @@ namespace MWGui { mEnchantmentPoints->setCaption(std::to_string(static_cast(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue())); mCharge->setCaption(std::to_string(mEnchanting.getGemCharge())); - mSuccessChance->setCaption(std::to_string(std::max(0, std::min(100, mEnchanting.getEnchantChance())))); + mSuccessChance->setCaption(std::to_string(std::clamp(mEnchanting.getEnchantChance(), 0, 100))); mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost())); mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice())); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 5a7b5a9590..ab596137cd 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -610,7 +610,7 @@ namespace MWGui static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) - mEnemyHealth->setAlpha(std::max(0.f, std::min(1.f, mEnemyHealthTimer/fNPCHealthBarFade))); + mEnemyHealth->setAlpha(std::clamp(mEnemyHealthTimer / fNPCHealthBarFade, 0.f, 1.f)); } diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index b718b712c0..3220e16b94 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -273,7 +273,7 @@ bool KeyboardNavigation::switchFocus(int direction, bool wrap) if (wrap) index = (index + keyFocusList.size())%keyFocusList.size(); else - index = std::min(std::max(0, index), static_cast(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/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 3b4afc852f..257c129e8c 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -171,7 +171,7 @@ namespace MWGui else valueStr = MyGUI::utility::toString(int(value)); - value = std::max(min, std::min(value, max)); + value = std::clamp(value, min, max); value = (value-min)/(max-min); scroll->setScrollPosition(static_cast(value * (scroll->getScrollRange() - 1))); @@ -306,7 +306,7 @@ namespace MWGui mWaterTextureSize->setIndexSelected(2); int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); + waterReflectionDetail = std::clamp(waterReflectionDetail, 0, 5); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); updateMaxLightsComboBox(mMaxLights); diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 8e6f951291..0830af0744 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -599,8 +599,7 @@ namespace MWGui text += "\n#{fontcolourhtml=normal}#{sExpelled}"; else { - int rank = factionPair.second; - rank = std::max(0, std::min(9, rank)); + const int rank = std::clamp(factionPair.second, 0, 9); text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; if (rank < 9) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index fa10ce03cd..bdb46e31a8 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -70,7 +70,7 @@ namespace MWInput } float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input"); - deadZoneRadius = std::min(std::max(deadZoneRadius, 0.0f), 0.5f); + deadZoneRadius = std::clamp(deadZoneRadius, 0.f, 0.5f); mBindingsManager->setJoystickDeadZone(deadZoneRadius); } diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 7810a40ad2..f2bd4505d1 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -245,8 +245,8 @@ namespace MWInput mMouseWheel += mouseWheelMove; const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - mGuiCursorX = std::max(0.f, std::min(mGuiCursorX, float(viewSize.width - 1))); - mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(viewSize.height - 1))); + mGuiCursorX = std::clamp(mGuiCursorX, 0.f, viewSize.width - 1); + mGuiCursorY = std::clamp(mGuiCursorY, 0.f, viewSize.height - 1); MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), static_cast(mMouseWheel)); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index cb4f8237e7..32562591a5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1013,13 +1013,10 @@ namespace MWMechanics void Actors::updateProcessingRange() { // We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876) - static const float maxProcessingRange = 7168.f; - static const float minProcessingRange = maxProcessingRange / 2.f; + static const float maxRange = 7168.f; + static const float minRange = maxRange / 2.f; - float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game"); - actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange); - actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange); - mActorsProcessingRange = actorsProcessingRange; + mActorsProcessingRange = std::clamp(Settings::Manager::getFloat("actors processing range", "Game"), minRange, maxRange); } void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) @@ -1315,7 +1312,7 @@ namespace MWMechanics angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y()); osg::Vec2f posAtT = relPos + relSpeed * t; float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * collisionDist * maxSpeed); - coef *= osg::clampBetween((maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), 0.f, 1.f); + coef *= std::clamp((maxDistForPartialAvoiding - dist) / (maxDistForPartialAvoiding - maxDistForStrictAvoiding), 0.f, 1.f); movementCorrection = posAtT * coef; if (otherPtr.getClass().getCreatureStats(otherPtr).isDead()) // In case of dead body still try to go around (it looks natural), but reduce the correction twice. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 58339d0228..36d4446580 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2044,7 +2044,7 @@ void CharacterController::update(float duration) mIsMovingBackward = vec.y() < 0; float maxDelta = osg::PI * duration * (2.5f - cosDelta); - delta = osg::clampBetween(delta, -maxDelta, maxDelta); + delta = std::clamp(delta, -maxDelta, maxDelta); stats.setSideMovementAngle(stats.getSideMovementAngle() + delta); effectiveRotation += delta; } @@ -2286,7 +2286,7 @@ void CharacterController::update(float duration) float swimmingPitch = mAnimation->getBodyPitchRadians(); float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0]; float maxSwimPitchDelta = 3.0f * duration; - swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta); + swimmingPitch += std::clamp(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta); mAnimation->setBodyPitchRadians(swimmingPitch); } else @@ -2522,7 +2522,7 @@ void CharacterController::unpersistAnimationState() { float start = mAnimation->getTextKeyTime(anim.mGroup+": start"); float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop"); - float time = std::max(start, std::min(stop, anim.mTime)); + float time = std::clamp(anim.mTime, start, stop); complete = (time - start) / (stop - start); } @@ -2746,7 +2746,7 @@ void CharacterController::setVisibility(float visibility) float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); if (chameleon) { - alpha *= std::min(0.75f, std::max(0.25f, (100.f - chameleon)/100.f)); + alpha *= std::clamp(1.f - chameleon / 100.f, 0.25f, 0.75f); } visibility = std::min(visibility, alpha); @@ -2965,8 +2965,8 @@ void CharacterController::updateHeadTracking(float duration) const double xLimit = osg::DegreesToRadians(40.0); const double zLimit = osg::DegreesToRadians(30.0); double zLimitOffset = mAnimation->getUpperBodyYawRadians(); - xAngleRadians = osg::clampBetween(xAngleRadians, -xLimit, xLimit); - zAngleRadians = osg::clampBetween(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset); + xAngleRadians = std::clamp(xAngleRadians, -xLimit, xLimit); + zAngleRadians = std::clamp(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset); float factor = duration*5; factor = std::min(factor, 1.f); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index c1d5f711fc..2962a25ede 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -113,10 +113,9 @@ namespace MWMechanics + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); attackerTerm *= attackerStats.getFatigueTerm(); - int x = int(blockerTerm - attackerTerm); - int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); - int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); - x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); + const int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); + const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); + int x = std::clamp(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance); if (Misc::Rng::roll0to99() < x) { diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp index 2376989745..e973e0ed52 100644 --- a/apps/openmw/mwmechanics/difficultyscaling.cpp +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -13,9 +13,7 @@ float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr const MWWorld::Ptr& player = MWMechanics::getPlayer(); // [-500, 500] - int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); - difficultySetting = std::min(difficultySetting, 500); - difficultySetting = std::max(difficultySetting, -500); + const int difficultySetting = std::clamp(Settings::Manager::getInt("difficulty", "Game"), -500, 500); static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get().find("fDifficultyMult")->mValue.getFloat(); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 078cbc5f43..a1870cbdb0 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -356,10 +356,10 @@ namespace MWMechanics ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(mWeaponType)->mWeaponClass; if (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) { - static const float multiplier = std::max(0.f, std::min(1.0f, Settings::Manager::getFloat("projectiles enchant multiplier", "Game"))); + static const float multiplier = std::clamp(Settings::Manager::getFloat("projectiles enchant multiplier", "Game"), 0.f, 1.f); MWWorld::Ptr player = getPlayer(); - int itemsInInventoryCount = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId()); - count = std::min(itemsInInventoryCount, std::max(1, int(getGemCharge() * multiplier / enchantPoints))); + count = player.getClass().getContainerStore(player).count(mOldItemPtr.getCellRef().getRefId()); + count = std::clamp(getGemCharge() * multiplier / enchantPoints, 1, count); } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index e01ed3c425..362df54ab7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -545,9 +545,9 @@ namespace MWMechanics x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).getMagnitude(); - if(clamp) - return std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used - return int(x); + if (clamp) + return std::clamp(x, 0, 100);//, normally clamped to [0..100] when used + return static_cast(x); } int MechanicsManager::getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) @@ -649,9 +649,9 @@ namespace MWMechanics int flee = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Flee).getBase(); int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase(); npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, - std::max(0, std::min(100, flee + int(std::max(iPerMinChange, s))))); + std::clamp(flee + int(std::max(iPerMinChange, s)), 0, 100)); npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, - std::max(0, std::min(100, fight + int(std::min(-iPerMinChange, -s))))); + std::clamp(fight + int(std::min(-iPerMinChange, -s)), 0, 100)); } float c = -std::abs(floor(r * fPerDieRollMult)); @@ -689,10 +689,10 @@ namespace MWMechanics float s = c * fPerDieRollMult * fPerTempMult; int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase(); int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase(); - npcStats.setAiSetting (CreatureStats::AI_Flee, - std::max(0, std::min(100, flee + std::min(-int(iPerMinChange), int(-s))))); - npcStats.setAiSetting (CreatureStats::AI_Fight, - std::max(0, std::min(100, fight + std::max(int(iPerMinChange), int(s))))); + npcStats.setAiSetting(CreatureStats::AI_Flee, + std::clamp(flee + std::min(-int(iPerMinChange), int(-s)), 0, 100)); + npcStats.setAiSetting(CreatureStats::AI_Fight, + std::clamp(fight + std::max(int(iPerMinChange), int(s)), 0, 100)); } x = floor(-c * fPerDieRollMult); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 5d19368bf6..1d1dfacce8 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -371,7 +371,7 @@ int MWMechanics::NpcStats::getReputation() const void MWMechanics::NpcStats::setReputation(int reputation) { // Reputation is capped in original engine - mReputation = std::min(255, std::max(0, reputation)); + mReputation = std::clamp(reputation, 0, 255); } int MWMechanics::NpcStats::getCrimeId() const diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 7907642f5e..566fb9eded 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -631,7 +631,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co if (!target.isInCell() || !target.getCell()->isExterior() || godmode) break; float time = world->getTimeStamp().getHour(); - float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); + float timeDiff = std::clamp(std::abs(time - 13.f), 0.f, 7.f); float damageScale = 1.f - timeDiff / 7.f; // When cloudy, the sun damage effect is halved static float fMagicSunBlockedMult = world->getStore().get().find("fMagicSunBlockedMult")->mValue.getFloat(); diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index b18ad288ad..70167abcd3 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -162,7 +162,10 @@ namespace MWMechanics float castChance = baseChance + castBonus; castChance *= stats.getFatigueTerm(); - return std::max(0.f, cap ? std::min(100.f, castChance) : castChance); + if (cap) + return std::clamp(castChance, 0.f, 100.f); + + return std::max(castChance, 0.f); } float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 570e89a17d..1a17cc87e6 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -128,8 +128,7 @@ namespace MWMechanics } // Take hit chance in account, but do not allow rating become negative. - float chance = getHitChance(actor, enemy, value) / 100.f; - rating *= std::min(1.f, std::max(0.01f, chance)); + rating *= std::clamp(getHitChance(actor, enemy, value) / 100.f, 0.01f, 1.f); if (weapclass != ESM::WeaponType::Ammo) rating *= weapon->mData.mSpeed; diff --git a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp index fc8725f5f4..a01ab96301 100644 --- a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp +++ b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp @@ -15,9 +15,9 @@ namespace MWPhysics const btVector3& position, const btScalar radius) { const btVector3 nearest( - std::max(aabbMin.x(), std::min(aabbMax.x(), position.x())), - std::max(aabbMin.y(), std::min(aabbMax.y(), position.y())), - std::max(aabbMin.z(), std::min(aabbMax.z(), position.z())) + std::clamp(position.x(), aabbMin.x(), aabbMax.x()), + std::clamp(position.y(), aabbMin.y(), aabbMax.y()), + std::clamp(position.z(), aabbMin.z(), aabbMax.z()) ); return nearest.distance(position) < radius; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ea595e6abb..98e3bcf737 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -718,7 +718,7 @@ namespace MWPhysics physicActor->setCanWaterWalk(waterCollision); // Slow fall reduces fall speed by a factor of (effect magnitude / 200) - const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); + const float slowFall = 1.f - std::clamp(effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f, 0.f, 1.f); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index e750fcad46..d1e842790b 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -236,7 +236,7 @@ namespace MWRender mTotalMovement += speed * duration; speed /= (1.f + speed / 500.f); float maxDelta = 300.f * duration; - mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta); + mSmoothedSpeed += std::clamp(speed - mSmoothedSpeed, -maxDelta, maxDelta); mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance); updateStandingPreviewMode(); @@ -434,7 +434,7 @@ namespace MWRender { const float epsilon = 0.000001f; float limit = static_cast(osg::PI_2) - epsilon; - mPitch = osg::clampBetween(angle, -limit, limit); + mPitch = std::clamp(angle, -limit, limit); } float Camera::getCameraDistance() const @@ -460,7 +460,7 @@ namespace MWRender } mIsNearest = mBaseCameraDistance <= mNearest; - mBaseCameraDistance = osg::clampBetween(mBaseCameraDistance, mNearest, mFurthest); + mBaseCameraDistance = std::clamp(mBaseCameraDistance, mNearest, mFurthest); Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance); } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 70f0ff02bb..0eb0e7738a 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -562,8 +562,8 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y) if (!segment.mFogOfWarImage) return false; - nX = std::max(0.f, std::min(1.f, nX)); - nY = std::max(0.f, std::min(1.f, nY)); + nX = std::clamp(nX, 0.f, 1.f); + nY = std::clamp(nY, 0.f, 1.f); int texU = static_cast((sFogOfWarResolution - 1) * nX); int texV = static_cast((sFogOfWarResolution - 1) * nY); @@ -648,7 +648,7 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient uint32_t clr = *(uint32_t*)data; uint8_t alpha = (clr >> 24); - alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); + alpha = std::min( alpha, (uint8_t) (std::clamp((sqrDist/sqrExploreRadius)*255, 0.f, 1.f))); uint32_t val = (uint32_t) (alpha << 24); if ( *data != val) { diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index b6f12dea37..4e21d33475 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -733,12 +733,8 @@ namespace MWRender } void clampToCell(osg::Vec3f& cellPos) { - osg::Vec2i min (mCell.x(), mCell.y()); - osg::Vec2i max (mCell.x()+1, mCell.y()+1); - if (cellPos.x() < min.x()) cellPos.x() = min.x(); - if (cellPos.x() > max.x()) cellPos.x() = max.x(); - if (cellPos.y() < min.y()) cellPos.y() = min.y(); - if (cellPos.y() > max.y()) cellPos.y() = max.y(); + cellPos.x() = std::clamp(cellPos.x(), mCell.x(), mCell.x() + 1); + cellPos.y() = std::clamp(cellPos.y(), mCell.y(), mCell.y() + 1); } osg::Vec3f mPosition; osg::Vec2i mCell; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c4ef1d9d9b..4e2da15107 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -297,7 +297,8 @@ namespace MWRender , mViewDistance(Settings::Manager::getFloat("viewing distance", "Camera")) , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) - , mFieldOfView(std::min(std::max(1.f, Settings::Manager::getFloat("field of view", "Camera")), 179.f)) + , mFieldOfView(std::clamp(Settings::Manager::getFloat("field of view", "Camera"), 1.f, 179.f)) + , mFirstPersonFieldOfView(std::clamp(Settings::Manager::getFloat("first person field of view", "Camera"), 1.f, 179.f)) { bool reverseZ = SceneUtil::getReverseZ(); @@ -517,8 +518,6 @@ namespace MWRender NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); - float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera"); - mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index d8f92d1d1f..895486ea31 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -305,8 +305,7 @@ public: void setWaterLevel(float waterLevel) { - const float refractionScale = std::min(1.0f, std::max(0.0f, - Settings::Manager::getFloat("refraction scale", "Water"))); + const float refractionScale = std::clamp(Settings::Manager::getFloat("refraction scale", "Water"), 0.f, 1.f); mViewMatrix = osg::Matrix::scale(1, 1, refractionScale) * osg::Matrix::translate(0, 0, (1.0 - refractionScale) * waterLevel); @@ -400,7 +399,7 @@ private: unsigned int calcNodeMask() { int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - reflectionDetail = std::min(5, std::max(mInterior ? 2 : 0, reflectionDetail)); + reflectionDetail = std::clamp(reflectionDetail, mInterior ? 2 : 0, 5); unsigned int extraMask = 0; if(reflectionDetail >= 1) extraMask |= Mask_Terrain; if(reflectionDetail >= 2) extraMask |= Mask_Static; @@ -583,7 +582,7 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) // Add animated textures std::vector > textures; - int frameCount = std::max(0, std::min(Fallback::Map::getInt("Water_SurfaceFrameCount"), 320)); + const int frameCount = std::clamp(Fallback::Map::getInt("Water_SurfaceFrameCount"), 0, 320); const std::string& texture = Fallback::Map::getString("Water_SurfaceTexture"); for (int i=0; i &textures) { - int frameCount = std::max(0, std::min(Fallback::Map::getInt("Water_SurfaceFrameCount"), 320)); + const int frameCount = std::clamp(Fallback::Map::getInt("Water_SurfaceFrameCount"), 0, 320); const std::string& texture = Fallback::Map::getString("Water_SurfaceTexture"); for (int i=0; i 0) { - chances.push_back(std::max(0, std::min(127, runtime[0].mInteger))); + chances.push_back(std::clamp(runtime[0].mInteger, 0, 127)); runtime.pop(); arg0--; } diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index ae31d60949..a36615ee4a 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -40,7 +40,7 @@ void Sound_Loudness::analyzeLoudness(const std::vector< char >& data) else if (mSampleType == SampleType_Float32) { value = *reinterpret_cast(&mQueue[sample*advance]); - value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already. + value = std::clamp(value, -1.f, 1.f); // Float samples *should* be scaled to [-1,1] already. } sum += value*value; @@ -64,8 +64,7 @@ float Sound_Loudness::getLoudnessAtTime(float sec) const if(mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) return 0.0f; - size_t index = static_cast(sec * mSamplesPerSec); - index = std::max(0, std::min(index, mSamples.size()-1)); + size_t index = std::clamp(sec * mSamplesPerSec, 0, mSamples.size() - 1); return mSamples[index]; } diff --git a/apps/openmw/mwsound/volumesettings.cpp b/apps/openmw/mwsound/volumesettings.cpp index cc4eac3d6d..fd79b97e9b 100644 --- a/apps/openmw/mwsound/volumesettings.cpp +++ b/apps/openmw/mwsound/volumesettings.cpp @@ -10,7 +10,7 @@ namespace MWSound { float clamp(float value) { - return std::max(0.0f, std::min(1.0f, value)); + return std::clamp(value, 0.f, 1.f); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d35f72e80c..cc982e4621 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1334,7 +1334,7 @@ namespace MWWorld * currently it's done so for rotating the camera, which needs * clamping. */ - objRot[0] = osg::clampBetween(objRot[0], -osg::PI_2, osg::PI_2); + objRot[0] = std::clamp(objRot[0], -osg::PI_2, osg::PI_2); objRot[1] = Misc::normalizeAngle(objRot[1]); objRot[2] = Misc::normalizeAngle(objRot[2]); } @@ -1901,7 +1901,7 @@ namespace MWWorld const auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects(); if (!mGodMode) blind = static_cast(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude()); - MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind))); + MWBase::Environment::get().getWindowManager()->setBlindness(std::clamp(blind, 0, 100)); int nightEye = static_cast(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude()); mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f))); diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 3c4a1e0b07..e97ad6b759 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -169,7 +169,7 @@ namespace ESM { float height = mLandData->mHeights[int(row * vertMult) * ESM::Land::LAND_SIZE + int(col * vertMult)]; height /= height > 0 ? 128.f : 16.f; - height = std::min(max, std::max(min, height)); + height = std::clamp(height, min, max); wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast(height); } } diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index da43cc38ec..76f554bec7 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -145,7 +145,7 @@ namespace Gui FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, const std::string& userDataPath, float scalingFactor) : mVFS(vfs) , mUserDataPath(userDataPath) - , mFontHeight(16) + , mFontHeight(std::clamp(Settings::Manager::getInt("font size", "GUI"), 12, 20)) , mScalingFactor(scalingFactor) { if (encoding == ToUTF8::WINDOWS_1252) @@ -153,9 +153,6 @@ namespace Gui else mEncoding = encoding; - int fontSize = Settings::Manager::getInt("font size", "GUI"); - mFontHeight = std::min(std::max(12, fontSize), 20); - MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); MyGUI::ResourceManager::getInstance().registerLoadXmlDelegate("Resource") = MyGUI::newDelegate(this, &FontLoader::loadFontFromXml); } @@ -549,7 +546,7 @@ namespace Gui // to allow to configure font size via config file, without need to edit XML files. // Also we should take UI scaling factor in account. int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); - resolution = std::min(960, std::max(48, resolution)) * mScalingFactor; + resolution = std::clamp(resolution, 48, 960) * mScalingFactor; MyGUI::xml::ElementPtr resolutionNode = resourceNode->createChild("Property"); resolutionNode->addAttribute("key", "Resolution"); @@ -591,7 +588,7 @@ namespace Gui // setup separate fonts with different Resolution to fit these windows. // These fonts have an internal prefix. int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); - resolution = std::min(960, std::max(48, resolution)); + resolution = std::clamp(resolution, 48, 960); float currentX = Settings::Manager::getInt("resolution x", "Video"); float currentY = Settings::Manager::getInt("resolution y", "Video"); diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index ddded82156..956fe2e489 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -57,7 +57,7 @@ float ControllerFunction::calculate(float value) const } case Constant: default: - return std::min(mStopTime, std::max(mStartTime, time)); + return std::clamp(time, mStartTime, mStopTime); } } diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 8c3758e980..dfe4cf1507 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -27,8 +27,7 @@ namespace SceneUtil mShadowSettings->setLightNum(0); mShadowSettings->setReceivesShadowTraversalMask(~0u); - int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); - numberOfShadowMapsPerLight = std::max(1, std::min(numberOfShadowMapsPerLight, 8)); + const int numberOfShadowMapsPerLight = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight); mShadowSettings->setBaseShadowTextureUnit(8 - numberOfShadowMapsPerLight); @@ -36,7 +35,7 @@ namespace SceneUtil const float maximumShadowMapDistance = Settings::Manager::getFloat("maximum shadow map distance", "Shadows"); if (maximumShadowMapDistance > 0) { - const float shadowFadeStart = std::min(std::max(0.f, Settings::Manager::getFloat("shadow fade start", "Shadows")), 1.f); + const float shadowFadeStart = std::clamp(Settings::Manager::getFloat("shadow fade start", "Shadows"), 0.f, 1.f); mShadowSettings->setMaximumShadowMapDistance(maximumShadowMapDistance); mShadowTechnique->setShadowFadeStart(maximumShadowMapDistance * shadowFadeStart); } @@ -78,8 +77,7 @@ namespace SceneUtil if (!Settings::Manager::getBool("enable shadows", "Shadows")) return; - int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); - numberOfShadowMapsPerLight = std::max(1, std::min(numberOfShadowMapsPerLight, 8)); + const int numberOfShadowMapsPerLight = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; diff --git a/components/widgets/fontwrapper.hpp b/components/widgets/fontwrapper.hpp index daa69f9202..16ebba3587 100644 --- a/components/widgets/fontwrapper.hpp +++ b/components/widgets/fontwrapper.hpp @@ -31,15 +31,11 @@ namespace Gui } private: - static int clamp(const int& value, const int& lowBound, const int& highBound) - { - return std::min(std::max(lowBound, value), highBound); - } std::string getFontSize() { // Note: we can not use the FontLoader here, so there is a code duplication a bit. - static const std::string fontSize = std::to_string(clamp(Settings::Manager::getInt("font size", "GUI"), 12, 20)); + static const std::string fontSize = std::to_string(std::clamp(Settings::Manager::getInt("font size", "GUI"), 12, 20)); return fontSize; } }; diff --git a/components/widgets/numericeditbox.cpp b/components/widgets/numericeditbox.cpp index e8ba226f70..c6ff9628ee 100644 --- a/components/widgets/numericeditbox.cpp +++ b/components/widgets/numericeditbox.cpp @@ -31,7 +31,7 @@ namespace Gui try { mValue = std::stoi(newCaption); - int capped = std::min(mMaxValue, std::max(mValue, mMinValue)); + int capped = std::clamp(mValue, mMinValue, mMaxValue); if (capped != mValue) { mValue = capped; From 04692e601217893999801ff1ff9fdec0314f3430 Mon Sep 17 00:00:00 2001 From: cody glassman Date: Sat, 6 Nov 2021 04:38:43 -0700 Subject: [PATCH 1652/2859] blackscreen fix --- apps/openmw/engine.cpp | 8 ++++++++ components/sceneutil/util.cpp | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 58dabd2cee..ead6b7c80c 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -8,6 +8,8 @@ #include +#include + #include #include #include @@ -754,6 +756,12 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) osg::ref_ptr exts = osg::GLExtensions::Get(0, false); bool shadersSupported = exts && (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; +#endif + std::string myguiResources = (mResDir / "mygui").string(); osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 5c2f8c2934..5b6e0a3ee0 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -324,12 +323,6 @@ osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resourc bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture * texture, unsigned int level, unsigned int face, bool mipMapGeneration) { -#if OSG_VERSION_LESS_THAN(3, 6, 6) - // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 - osg::ref_ptr extensions = osg::GLExtensions::Get(0, false); - if (extensions) - extensions->glRenderbufferStorageMultisampleCoverageNV = nullptr; -#endif unsigned int samples = 0; unsigned int colourSamples = 0; bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1; From 303082f6b465ae7f4963acc1c6b625f7ab6b5d72 Mon Sep 17 00:00:00 2001 From: Langerz82 Date: Sat, 8 Feb 2020 07:16:52 +0000 Subject: [PATCH 1653/2859] Finish turning before attacking --- apps/openmw/mwmechanics/aicombat.cpp | 5 ++++- apps/openmw/mwmechanics/aicombat.hpp | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index eb139c918d..fa14b47965 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -137,7 +137,10 @@ namespace MWMechanics } storage.updateCombatMove(duration); + storage.mRotateMove = false; if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); + if (storage.mRotateMove) + return false; storage.updateAttack(characterController); } else @@ -442,7 +445,7 @@ namespace MWMechanics storage.mCurrentAction->getCombatRange(isRangedCombat); float eps = isRangedCombat ? osg::DegreesToRadians(0.5) : osg::DegreesToRadians(3.f); float targetAngleRadians = storage.mMovement.mRotation[axis]; - smoothTurn(actor, targetAngleRadians, axis, eps); + storage.mRotateMove = !smoothTurn(actor, targetAngleRadians, axis, eps); } MWWorld::Ptr AiCombat::getTarget() const diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 5425f1af0b..34e726412c 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -33,6 +33,7 @@ namespace MWMechanics bool mAttack; float mAttackRange; bool mCombatMove; + bool mRotateMove; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; std::shared_ptr mCurrentAction; @@ -65,6 +66,7 @@ namespace MWMechanics mAttack(false), mAttackRange(0.0f), mCombatMove(false), + mRotateMove(false), mLastTargetPos(0,0,0), mCell(nullptr), mCurrentAction(), From 013043ee6e15c8baec356bb50146d82aba9cb690 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 6 Nov 2021 17:45:29 +0100 Subject: [PATCH 1654/2859] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c491cb83c0..548351acc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Bug #1751: Birthsign abilities increase modified attribute values instead of base ones Bug #3246: ESSImporter: Most NPCs are dead on save load + Bug #3488: AI combat aiming is too slow Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change From cd37504d0eb8e23a3c88b278c37bdd3f9520097f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 6 Nov 2021 18:48:48 +0000 Subject: [PATCH 1655/2859] Resave hint variables to cache so they'll still exist after a reconfigure --- cmake/FindLZ4.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/FindLZ4.cmake b/cmake/FindLZ4.cmake index ec854c6b18..5b148cb64e 100644 --- a/cmake/FindLZ4.cmake +++ b/cmake/FindLZ4.cmake @@ -95,6 +95,8 @@ include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LZ4 DEFAULT_MSG LZ4_LIBRARY LZ4_INCLUDE_DIR LZ4_VERSION) +set(LZ4_INCLUDE_DIR ${LZ4_INCLUDE_DIR} CACHE PATH "LZ4 include dir hint") +set(LZ4_LIBRARY ${LZ4_LIBRARY} CACHE FILEPATH "LZ4 library path hint") mark_as_advanced(LZ4_INCLUDE_DIR LZ4_LIBRARY) set(LZ4_LIBRARIES ${LZ4_LIBRARY}) From 726653087eace39ef64e80609f346990b9fa063b Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 7 Nov 2021 09:36:22 +0000 Subject: [PATCH 1656/2859] restores _mergeAlphaBlending behaviour (#3222) This PR restores a minor peculiarity of `_mergeAlphaBlending` behaviour unintentionally changed by PR #3162. --- components/sceneutil/optimizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index b9d2f5bac7..e2fcaedbd7 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -1137,7 +1137,7 @@ bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2) bool Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) { - if (_mergeAlphaBlending || !stateSet || stateSet->getRenderBinMode() & osg::StateSet::INHERIT_RENDERBIN_DETAILS) + if (!stateSet || stateSet->getRenderBinMode() & osg::StateSet::INHERIT_RENDERBIN_DETAILS) return false; _stateSetStack.push_back(stateSet); checkAlphaBlendingActive(); From e125308dd87d84ced535eb762d09c6c308d078e0 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 8 Aug 2021 09:06:01 +0300 Subject: [PATCH 1657/2859] Force assign head animation timer (bug #4389) --- CHANGELOG.md | 1 + apps/openmw/mwrender/actoranimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 16 ++++++++++------ components/sceneutil/controller.cpp | 15 +++++++++++++++ components/sceneutil/controller.hpp | 12 +++++++++++- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c491cb83c0..461afc892e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI + Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 7706f7d7f1..c8607cd97b 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -335,7 +335,7 @@ void ActorAnimation::resetControllers(osg::Node* node) std::shared_ptr src; src.reset(new NullAnimationTime); - SceneUtil::AssignControllerSourcesVisitor removeVisitor(src); + SceneUtil::ForceControllerSourcesVisitor removeVisitor(src); node->accept(removeVisitor); } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 0d6c21c308..7031b5a02b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -837,14 +837,18 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g } } } + SceneUtil::ForceControllerSourcesVisitor assignVisitor(src); + node->accept(assignVisitor); } - else if (type == ESM::PRT_Weapon) - src = mWeaponAnimationTime; else - src.reset(new NullAnimationTime); - - SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); - node->accept(assignVisitor); + { + if (type == ESM::PRT_Weapon) + src = mWeaponAnimationTime; + else + src.reset(new NullAnimationTime); + SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); + node->accept(assignVisitor); + } } return true; diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index dfc72918aa..2c7507d0a3 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -121,6 +121,21 @@ namespace SceneUtil ctrl.setSource(mToAssign); } + ForceControllerSourcesVisitor::ForceControllerSourcesVisitor() + : AssignControllerSourcesVisitor() + { + } + + ForceControllerSourcesVisitor::ForceControllerSourcesVisitor(std::shared_ptr toAssign) + : AssignControllerSourcesVisitor(toAssign) + { + } + + void ForceControllerSourcesVisitor::visit(osg::Node&, Controller &ctrl) + { + ctrl.setSource(mToAssign); + } + FindMaxControllerLengthVisitor::FindMaxControllerLengthVisitor() : SceneUtil::ControllerVisitor() , mMaxLength(0) diff --git a/components/sceneutil/controller.hpp b/components/sceneutil/controller.hpp index 2656d654e1..6ef800f05b 100644 --- a/components/sceneutil/controller.hpp +++ b/components/sceneutil/controller.hpp @@ -85,10 +85,20 @@ namespace SceneUtil /// By default assigns the ControllerSource passed to the constructor of this class if no ControllerSource is assigned to that controller yet. void visit(osg::Node& node, Controller& ctrl) override; - private: + protected: std::shared_ptr mToAssign; }; + class ForceControllerSourcesVisitor : public AssignControllerSourcesVisitor + { + public: + ForceControllerSourcesVisitor(); + ForceControllerSourcesVisitor(std::shared_ptr toAssign); + + /// Assign the wanted ControllerSource even if one is already assigned to the controller. + void visit(osg::Node& node, Controller& ctrl) override; + }; + /// Finds the maximum of all controller functions in the given scene graph class FindMaxControllerLengthVisitor : public ControllerVisitor { From 9dd36a345f195ded8ae1528abb47a775b4bb4301 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 7 Nov 2021 16:02:27 +0300 Subject: [PATCH 1658/2859] Fix a typo in nv_default shader --- files/shaders/nv_default_fragment.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 245f83b620..17204534e3 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -44,7 +44,7 @@ void main() { #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); - gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); #else gl_FragData[0] = vec4(1.0); #endif From a3e039d862983d0ae05d99b4c2b949edcb65093a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Nov 2021 14:15:30 +0100 Subject: [PATCH 1659/2859] Explicitely sort by file name after adding all data dirs --- apps/launcher/datafilespage.cpp | 1 + apps/opencs/editor.cpp | 6 +----- apps/opencs/view/doc/filedialog.cpp | 9 +++++++-- apps/opencs/view/doc/filedialog.hpp | 2 +- components/contentselector/model/contentmodel.cpp | 14 ++++++++------ components/contentselector/model/contentmodel.hpp | 3 +-- .../contentselector/view/contentselector.cpp | 5 +++++ .../contentselector/view/contentselector.hpp | 1 + 8 files changed, 25 insertions(+), 16 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 4c66668e4a..eb0950dae0 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -121,6 +121,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) for (const QString &path : paths) mSelector->addFiles(path); + mSelector->sortFiles(); PathIterator pathIterator(paths); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 3f53a523f4..9cd9f72090 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -149,11 +149,7 @@ std::pair > CS::Editor::readConfi dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); //iterate the data directories and add them to the file dialog for loading - for (Files::PathContainer::const_reverse_iterator iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) - { - QString path = QString::fromUtf8 (iter->string().c_str()); - mFileDialog.addFiles(path); - } + mFileDialog.addFiles(dataDirs); return std::make_pair (dataDirs, variables["fallback-archive"].as().toStdStringVector()); } diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index c3d0d8cc50..946dac047e 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -28,9 +28,14 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : mAdjusterWidget = new AdjusterWidget (this); } -void CSVDoc::FileDialog::addFiles(const QString &path) +void CSVDoc::FileDialog::addFiles(const std::vector& dataDirs) { - mSelector->addFiles(path); + for (auto iter = dataDirs.rbegin(); iter != dataDirs.rend(); ++iter) + { + QString path = QString::fromUtf8(iter->string().c_str()); + mSelector->addFiles(path); + } + mSelector->sortFiles(); } void CSVDoc::FileDialog::setEncoding(const QString &encoding) diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 6c48fa78b9..6a15b46b7c 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -45,7 +45,7 @@ namespace CSVDoc explicit FileDialog(QWidget *parent = nullptr); void showDialog (ContentAction action); - void addFiles (const QString &path); + void addFiles(const std::vector& dataDirs); void setEncoding (const QString &encoding); void clearFiles (); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index fd94b60516..355f5741bf 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -1,6 +1,7 @@ #include "contentmodel.hpp" #include "esmfile.hpp" +#include #include #include @@ -474,8 +475,6 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) } } - - sortFiles(); } void ContentSelectorModel::ContentModel::clearFiles() @@ -504,8 +503,13 @@ QStringList ContentSelectorModel::ContentModel::gameFiles() const void ContentSelectorModel::ContentModel::sortFiles() { + emit layoutAboutToBeChanged(); + std::sort(mFiles.begin(), mFiles.end(), [](const EsmFile* a, const EsmFile* b) + { + return a->fileName().compare(b->fileName(), Qt::CaseInsensitive) > 0; + }); //Dependency sort - int sorted = 0; + int sorted = 1; //iterate each file, obtaining a reference to its gamefiles list for(int i = sorted; i < mFiles.size(); ++i) { @@ -520,12 +524,10 @@ void ContentSelectorModel::ContentModel::sortFiles() break; } if(i != j) - { mFiles.move(i, j); - emit dataChanged(index(i, 0, QModelIndex()), index(j, 0, QModelIndex())); - } ++sorted; } + emit layoutChanged(); } bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index f8130e3649..4bbe73b427 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -44,6 +44,7 @@ namespace ContentSelectorModel bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; void addFiles(const QString &path); + void sortFiles(); void clearFiles(); QModelIndex indexFromItem(const EsmFile *item) const; @@ -68,8 +69,6 @@ namespace ContentSelectorModel void addFile(EsmFile *file); - void sortFiles(); - /// Checks a specific plug-in for load order errors /// \return all errors found for specific plug-in QList checkForLoadOrderErrors(const EsmFile *file, int row) const; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index f18e80dd0a..ef925148ab 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -173,6 +173,11 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) mContentModel->checkForLoadOrderErrors(); } +void ContentSelectorView::ContentSelector::sortFiles() +{ + mContentModel->sortFiles(); +} + void ContentSelectorView::ContentSelector::clearFiles() { mContentModel->clearFiles(); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 4a9983c1bf..b40675bedc 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -28,6 +28,7 @@ namespace ContentSelectorView QString currentFile() const; void addFiles(const QString &path); + void sortFiles(); void clearFiles(); void setProfileContent (const QStringList &fileList); From 2e031f195b571d8133b82c11b508e42c6bf0b9d8 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 7 Nov 2021 17:26:02 +0000 Subject: [PATCH 1660/2859] fixes LightBufferBinding messages (#3223) This PR aims to solve `uniform block LightBufferBinding has no binding` messages @glassmancody has reportedly encountered since PR #3110 due to an apparent bug in OSG. While we do have to add a workaround here that adds a bit of clunkiness, #3216 should allow us to clean up these interactions a bit in the future. --- apps/openmw/mwrender/groundcover.cpp | 2 +- components/shader/shadermanager.cpp | 10 +++++++++- components/shader/shadermanager.hpp | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 77c4f0fab5..e2d1f0c50d 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -152,7 +152,7 @@ namespace MWRender mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); - mProgramTemplate = mSceneManager->getShaderManager().getProgramTemplate() ? static_cast(mSceneManager->getShaderManager().getProgramTemplate()->clone(osg::CopyOp::SHALLOW_COPY)) : new osg::Program; + mProgramTemplate = mSceneManager->getShaderManager().getProgramTemplate() ? Shader::ShaderManager::cloneProgram(mSceneManager->getShaderManager().getProgramTemplate()) : osg::ref_ptr(new osg::Program); mProgramTemplate->addBindAttribLocation("aOffset", 6); mProgramTemplate->addBindAttribLocation("aRotation", 7); } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index e057cfac02..30c7ed1b1d 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -345,7 +345,7 @@ namespace Shader if (found == mPrograms.end()) { if (!programTemplate) programTemplate = mProgramTemplate; - osg::ref_ptr program = programTemplate ? static_cast(programTemplate->clone(osg::CopyOp::SHALLOW_COPY)) : new osg::Program; + osg::ref_ptr program = programTemplate ? cloneProgram(programTemplate) : osg::ref_ptr(new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; @@ -353,6 +353,14 @@ namespace Shader return found->second; } + osg::ref_ptr ShaderManager::cloneProgram(const osg::Program* src) + { + osg::ref_ptr program = static_cast(src->clone(osg::CopyOp::SHALLOW_COPY)); + for (auto [name, idx] : src->getUniformBlockBindingList()) + program->addBindUniformBlock(name, idx); + return program; + } + ShaderManager::DefineMap ShaderManager::getGlobalDefines() { return DefineMap(mGlobalDefines); diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index d0ee069b10..613f33b168 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -38,6 +38,9 @@ namespace Shader const osg::Program* getProgramTemplate() const { return mProgramTemplate; } void setProgramTemplate(const osg::Program* program) { mProgramTemplate = program; } + /// Clone an osg::Program including bindUniformBlocks that osg::Program::clone does not copy for some reason. + static osg::ref_ptr cloneProgram(const osg::Program*); + /// Get (a copy of) the DefineMap used to construct all shaders DefineMap getGlobalDefines(); From 727f784a98f10934dee4a71c01d9d6e17b74cab8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Nov 2021 19:47:02 +0100 Subject: [PATCH 1661/2859] Acquire log lock only when logger should log To minimize overhead for calls when level is less than current. For example Log(Debug::Debug) should not lock mutex when current logging level is Verbose. --- components/debug/debuglog.hpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/components/debug/debuglog.hpp b/components/debug/debuglog.hpp index 0da5b9cbdd..fd5cc4dbb9 100644 --- a/components/debug/debuglog.hpp +++ b/components/debug/debuglog.hpp @@ -29,25 +29,29 @@ class Log std::unique_lock mLock; public: - // Locks a global lock while the object is alive - Log(Debug::Level level) : - mLock(sLock), - mLevel(level) + explicit Log(Debug::Level level) + : mShouldLog(level <= Debug::CurrentDebugLevel) { + // No need to hold the lock if there will be no logging anyway + if (!mShouldLog) + return; + + // Locks a global lock while the object is alive + mLock = std::unique_lock(sLock); + // If the app has no logging system enabled, log level is not specified. // Show all messages without marker - we just use the plain cout in this case. if (Debug::CurrentDebugLevel == Debug::NoLevel) return; - if (mLevel <= Debug::CurrentDebugLevel) - std::cout << static_cast(mLevel); + std::cout << static_cast(level); } // Perfect forwarding wrappers to give the chain of objects to cout template Log& operator<<(T&& rhs) { - if (mLevel <= Debug::CurrentDebugLevel) + if (mShouldLog) std::cout << std::forward(rhs); return *this; @@ -55,12 +59,12 @@ public: ~Log() { - if (mLevel <= Debug::CurrentDebugLevel) + if (mShouldLog) std::cout << std::endl; } private: - Debug::Level mLevel; + const bool mShouldLog; }; #endif From 9394cdde222632c80aa98e77411e01aecb951695 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Nov 2021 21:12:15 +0100 Subject: [PATCH 1662/2859] Only restore focus to widgets with visible parents --- CHANGELOG.md | 1 + apps/openmw/mwgui/keyboardnavigation.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 548351acc0..ad87ceca32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system Bug #5207: Loose summons can be present in scene + Bug #5377: console does not appear after using menutest in inventory Bug #5379: Wandering NPCs falling through cantons Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index 3220e16b94..b4437873c3 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -79,7 +79,7 @@ void KeyboardNavigation::restoreFocus(int mode) if (found != mKeyFocus.end()) { MyGUI::Widget* w = found->second; - if (w && w->getVisible() && w->getEnabled()) + if (w && w->getVisible() && w->getEnabled() && w->getInheritedVisible() && w->getInheritedEnabled()) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(found->second); } } From 11ed5949105bf0d809c046ef4ad211043547982e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Nov 2021 22:13:27 +0100 Subject: [PATCH 1663/2859] Improve AI rating of bound effects --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/spellpriority.cpp | 28 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 548351acc0..675102a328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) Bug #3792: 1 frame late magicka recalc breaks early scripted magicka reactions to Intelligence change Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes + Bug #3855: AI sometimes spams defensive spells Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI Bug #4602: Robert's Bodies: crash inside createInstance() diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index eb276deb78..7338f4b2af 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -368,6 +368,27 @@ namespace MWMechanics return 0.f; break; + case ESM::MagicEffect::BoundShield: + if(actor.getClass().hasInventoryStore(actor)) + { + // If the actor is an NPC they can benefit from the armor rating, otherwise check if we've got a one-handed weapon to use with the shield + if(!actor.getClass().isNpc()) + { + const auto& store = actor.getClass().getInventoryStore(actor); + auto oneHanded = std::find_if(store.cbegin(MWWorld::ContainerStore::Type_Weapon), store.cend(), [](const MWWorld::ConstPtr& weapon) + { + if(weapon.getClass().getItemHealth(weapon) <= 0.f) + return false; + auto type = weapon.get()->mBase->mData.mType; + return !(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded); + }); + if(oneHanded == store.cend()) + return 0.f; + } + } + else + return 0.f; + break; // Creatures can not wear armor case ESM::MagicEffect::BoundCuirass: case ESM::MagicEffect::BoundGloves: @@ -552,6 +573,13 @@ namespace MWMechanics if (!creatureStats.getSummonedCreatureMap().empty()) return 0.f; } + if(effect.mEffectID >= ESM::MagicEffect::BoundDagger && effect.mEffectID <= ESM::MagicEffect::BoundGloves) + { + // While rateSpell prevents actors from recasting the same spell, it doesn't prevent them from casting different spells with the same effect. + // Multiple instances of the same bound item don't stack so if the effect is already active, rate it as useless. + if(actor.getClass().getCreatureStats(actor).getMagicEffects().get(effect.mEffectID).getMagnitude() > 0.f) + return 0.f; + } // Underwater casting not possible if (effect.mRange == ESM::RT_Target) From 6bcef77433bf617c7a1e2b9de9d3bbe34e917ba4 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 7 Nov 2021 22:45:03 +0100 Subject: [PATCH 1664/2859] Add missing controls to OpenMW's TabControl skin --- files/mygui/openmw_resources.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml index 811fa4f7fa..ff4893a9b2 100644 --- a/files/mygui/openmw_resources.xml +++ b/files/mygui/openmw_resources.xml @@ -47,7 +47,13 @@ - + + + + + + + From 90ce50190e292016d164eb2d27d57e189709ae7d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 8 Nov 2021 00:49:10 +0000 Subject: [PATCH 1665/2859] Remove redundant recreateShaders --- apps/openmw/mwrender/actoranimation.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 7706f7d7f1..1679c07bf4 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -261,9 +261,6 @@ void ActorAnimation::updateHolsteredShield(bool showCarriedLeft) if (isEnchanted) SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor); } - - if (mAlpha != 1.f) - mResourceSystem->getSceneManager()->recreateShaders(mHolsteredShield->getNode()); } bool ActorAnimation::useShieldAnimations() const From e0c4f08aa52813f3740fb28dc4872ed3602a71b0 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 8 Nov 2021 01:45:41 +0100 Subject: [PATCH 1666/2859] Remove redundant includes --- apps/openmw/mwgui/mapwindow.hpp | 2 ++ components/debug/debuglog.hpp | 2 -- components/detournavigator/debug.hpp | 10 +--------- components/detournavigator/recastmeshbuilder.hpp | 1 - components/detournavigator/recastmeshmanager.hpp | 2 -- 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index d3cd626475..61f48d5279 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include "windowpinnablebase.hpp" #include diff --git a/components/debug/debuglog.hpp b/components/debug/debuglog.hpp index 0da5b9cbdd..f794768546 100644 --- a/components/debug/debuglog.hpp +++ b/components/debug/debuglog.hpp @@ -4,8 +4,6 @@ #include #include -#include - namespace Debug { enum Level diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index d86d923a41..a868ac2a2e 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -7,16 +7,8 @@ #include #include -#include - -#include -#include -#include -#include -#include -#include + #include -#include class dtNavMesh; diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 120e4b045a..84e9716569 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index 33aba97bbf..d93745c71e 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -14,8 +14,6 @@ #include #include #include -#include -#include #include class btCollisionShape; From a824e112d42a1706bc4eee0c1cc6bfd2fd332fdc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Nov 2021 16:36:59 +0100 Subject: [PATCH 1667/2859] Fix recastmesh.cpp includes recastmesh.cpp is supposed to include recastmesh.hpp not navmesh.hpp. --- components/sceneutil/recastmesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index 18cec022ff..5732f86959 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -1,4 +1,4 @@ -#include "navmesh.hpp" +#include "recastmesh.hpp" #include "detourdebugdraw.hpp" #include From c489b773856974e20156db28ab0545cef1fed821 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 7 Nov 2021 20:01:07 -0800 Subject: [PATCH 1668/2859] fix windows aero snap --- CHANGELOG.md | 1 + apps/openmw/engine.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 548351acc0..6e540d02f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Bug #5120: Scripted object spawning updates physics system Bug #5207: Loose summons can be present in scene Bug #5379: Wandering NPCs falling through cantons + Bug #5394: Windows snapping no longer works Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5508: Engine binary links to Qt without using it diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2d94c7422f..67944e4546 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -546,6 +546,10 @@ void OMW::Engine::createWindow(Settings::Manager& settings) if(fullscreen) flags |= SDL_WINDOW_FULLSCREEN; + // Allows for Windows snapping features to properly work in borderless window + SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "1"); + SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); + if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; From 5f1bf8936927e3ebdb9e08b810e236b51f483436 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 8 Nov 2021 09:27:42 +0000 Subject: [PATCH 1669/2859] improves groundcover view distance (#3219) This PR aims to solve all issues with `Groundcover` view distance handling in a satisfying way while preserving other optimisations that benefit other features. The main idea here is not to rely on `ViewData` updates for distance culling calculations because we can not accurately determine distance against lazily updated views. Instead, we perform an accurate measurement in a cull callback we can run every frame in `Groundcover` itself. While we do have to add some code to handle this feature, it is quite loosely coupled code that could be useful elsewhere in the future. These changes should address a performance regression @akortunov experienced. --- apps/openmw/mwrender/groundcover.cpp | 31 +++++++++-- apps/openmw/mwrender/groundcover.hpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 4 +- components/terrain/buffercache.cpp | 4 +- components/terrain/chunkmanager.cpp | 3 ++ components/terrain/quadtreenode.cpp | 43 +++++++++------- components/terrain/quadtreenode.hpp | 2 + components/terrain/quadtreeworld.cpp | 63 +++++++++++------------ components/terrain/quadtreeworld.hpp | 8 ++- components/terrain/viewdata.cpp | 1 - components/terrain/viewdata.hpp | 2 +- 11 files changed, 98 insertions(+), 65 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index e2d1f0c50d..25c00fb976 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -1,5 +1,6 @@ #include "groundcover.hpp" +#include #include #include #include @@ -7,6 +8,8 @@ #include #include +#include +#include #include #include "apps/openmw/mwworld/esmstore.hpp" @@ -106,6 +109,20 @@ namespace MWRender float mDensity = 0.f; }; + class ViewDistanceCallback : public SceneUtil::NodeCallback + { + public: + ViewDistanceCallback(float dist, const osg::BoundingBox& box) : mViewDistance(dist), mBox(box) {} + void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (Terrain::distance(mBox, nv->getEyePoint()) <= mViewDistance) + traverse(node, nv); + } + private: + float mViewDistance; + osg::BoundingBox mBox; + }; + inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) { osg::Vec2f size = maxBound - minBound; @@ -122,8 +139,9 @@ namespace MWRender osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { + if (lod > getMaxLodLevel()) + return nullptr; GroundcoverChunkId id = std::make_tuple(center, size); - osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return static_cast(obj.get()); @@ -137,12 +155,13 @@ namespace MWRender } } - Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) , mStateset(new osg::StateSet) { + setViewDistance(viewDistance); // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties // Force a unified alpha handling instead of data from meshes osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); @@ -220,10 +239,16 @@ namespace MWRender group->addChild(node); } + osg::ComputeBoundsVisitor cbv; + group->accept(cbv); + osg::BoundingBox box = cbv.getBoundingBox(); + box = osg::BoundingBox(box._min+worldCenter, box._max+worldCenter); + group->addCullCallback(new ViewDistanceCallback(getViewDistance(), box)); + group->setStateSet(mStateset); group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) - group->setCullCallback(new SceneUtil::LightListCallback); + group->addCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", true, mProgramTemplate); mSceneManager->shareState(group); group->getBound(); diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index ed88f7fe24..cd73e46eb0 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -12,7 +12,7 @@ namespace MWRender class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: - Groundcover(Resource::SceneManager* sceneManager, float density); + Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance); ~Groundcover() = default; osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4e2da15107..19972b094a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -439,11 +439,9 @@ namespace MWRender float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); - mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density, groundcoverDistance)); static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); - - mGroundcover->setViewDistance(groundcoverDistance); } mStateUpdater = new StateUpdater; diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 399df16d34..658c431b1b 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -12,8 +12,8 @@ namespace template osg::ref_ptr createIndexBuffer(unsigned int flags, unsigned int verts) { - // LOD level n means every 2^n-th vertex is kept - size_t lodLevel = (flags >> (4*4)); + // LOD level n means every 2^n-th vertex is kept, but we currently handle LOD elsewhere. + size_t lodLevel = 0;//(flags >> (4*4)); size_t lodDeltas[4]; for (int i=0; i<4; ++i) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index e040cbdd93..d57b73768f 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -52,6 +52,9 @@ struct FindChunkTemplate osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { + // Override lod with the vertexLodMod adjusted value. + // TODO: maybe we can refactor this code by moving all vertexLodMod code into this class. + lod = static_cast(lodFlags >> (4*4)); ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index c113a629c5..81d3ccb32d 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -10,6 +10,29 @@ namespace Terrain { +float distance(const osg::BoundingBox& box, const osg::Vec3f& v) +{ + if (box.contains(v)) + return 0; + else + { + osg::Vec3f maxDist(0,0,0); + if (v.x() < box.xMin()) + maxDist.x() = box.xMin() - v.x(); + else if (v.x() > box.xMax()) + maxDist.x() = v.x() - box.xMax(); + if (v.y() < box.yMin()) + maxDist.y() = box.yMin() - v.y(); + else if (v.y() > box.yMax()) + maxDist.y() = v.y() - box.yMax(); + if (v.z() < box.zMin()) + maxDist.z() = box.zMin() - v.z(); + else if (v.z() > box.zMax()) + maxDist.z() = v.z() - box.zMax(); + return maxDist.length(); + } +} + ChildDirection reflect(ChildDirection dir, Direction dir2) { assert(dir != Root); @@ -78,25 +101,7 @@ QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir) float QuadTreeNode::distance(const osg::Vec3f& v) const { const osg::BoundingBox& box = getBoundingBox(); - if (box.contains(v)) - return 0; - else - { - osg::Vec3f maxDist(0,0,0); - if (v.x() < box.xMin()) - maxDist.x() = box.xMin() - v.x(); - else if (v.x() > box.xMax()) - maxDist.x() = v.x() - box.xMax(); - if (v.y() < box.yMin()) - maxDist.y() = box.yMin() - v.y(); - else if (v.y() > box.yMax()) - maxDist.y() = v.y() - box.yMax(); - if (v.z() < box.zMin()) - maxDist.z() = box.zMin() - v.z(); - else if (v.z() > box.zMax()) - maxDist.z() = v.z() - box.zMax(); - return maxDist.length(); - } + return Terrain::distance(box, v); } void QuadTreeNode::initNeighbours() diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 7ae59b92f0..aba78b8b3a 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -34,6 +34,8 @@ namespace Terrain class ViewData; + float distance(const osg::BoundingBox&, const osg::Vec3f& v); + class QuadTreeNode : public osg::Group { public: diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 7fc0895846..6dbed34331 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -40,9 +40,9 @@ namespace return 1 << depth; } - int Log2( unsigned int n ) + unsigned int Log2( unsigned int n ) { - int targetlevel = 0; + unsigned int targetlevel = 0; while (n >>= 1) ++targetlevel; return targetlevel; } @@ -78,16 +78,18 @@ public: if (intersects) return Deeper; } - dist = std::max(0.f, dist + mDistanceModifier); - if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded return StopTraversal; - - int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); - int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); - - return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper; + return getNativeLodLevel(node, mMinSize) <= convertDistanceToLodLevel(dist, mMinSize, mFactor) ? StopTraversalAndUse : Deeper; + } + static unsigned int getNativeLodLevel(const QuadTreeNode* node, float minSize) + { + return Log2(static_cast(node->getSize()/minSize)); + } + static unsigned int convertDistanceToLodLevel(float dist, float minSize, float factor) + { + return Log2(static_cast(dist/(Constants::CellSizeInUnits*minSize*factor))); } private: @@ -278,7 +280,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mViewDistance(std::numeric_limits::max()) , mMinSize(1/8.f) , mDebugTerrainChunks(debugChunks) - , mRevalidateDistance(0.f) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -296,13 +297,14 @@ QuadTreeWorld::~QuadTreeWorld() { } -/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. +/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the vertex data set, +/// NOT relative to mMinSize as is the case with node LODs. unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) { - int lod = Log2(int(node->getSize())); + unsigned int vertexLod = DefaultLodCallback::getNativeLodLevel(node, 1); if (vertexLodMod > 0) { - lod = std::max(0, lod-vertexLodMod); + vertexLod = static_cast(std::max(0, static_cast(vertexLod)-vertexLodMod)); } else if (vertexLodMod < 0) { @@ -313,13 +315,13 @@ unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) size *= 2; vertexLodMod = std::min(0, vertexLodMod+1); } - lod += std::abs(vertexLodMod); + vertexLod += std::abs(vertexLodMod); } - return lod; + return vertexLod; } /// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly -unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const ViewData* vd) +unsigned int getLodFlags(QuadTreeNode* node, unsigned int ourVertexLod, int vertexLodMod, const ViewData* vd) { unsigned int lodFlags = 0; for (unsigned int i=0; i<4; ++i) @@ -332,40 +334,38 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const // our detail and the neighbour would handle stitching by itself. while (neighbour && !vd->contains(neighbour)) neighbour = neighbour->getParent(); - int lod = 0; + unsigned int lod = 0; if (neighbour) lod = getVertexLod(neighbour, vertexLodMod); - if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - + if (lod <= ourVertexLod) // We only need to worry about neighbours less detailed than we are - lod = 0; // neighbours with more detail will do the stitching themselves // Use 4 bits for each LOD delta if (lod > 0) { - lodFlags |= static_cast(lod - ourLod) << (4*i); + lodFlags |= (lod - ourVertexLod) << (4*i); } } + // Use the remaining bits for our vertex LOD + lodFlags |= (ourVertexLod << (4*4)); return lodFlags; } -void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance) +void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile) { if (!vd->hasChanged() && entry.mRenderingNode) return; - int ourLod = getVertexLod(entry.mNode, mVertexLodMod); - if (vd->hasChanged()) { + unsigned int ourVertexLod = getVertexLod(entry.mNode, mVertexLodMod); // have to recompute the lodFlags in case a neighbour has changed LOD. - unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, mVertexLodMod, vd); + unsigned int lodFlags = getLodFlags(entry.mNode, ourVertexLod, mVertexLodMod, vd); if (lodFlags != entry.mLodFlags) { entry.mRenderingNode = nullptr; entry.mLodFlags = lodFlags; } - // have to revalidate chunks within a custom view distance. - if (mRevalidateDistance && entry.mNode->distance(vd->getViewPoint()) <= mRevalidateDistance + reuseDistance) - entry.mRenderingNode = nullptr; } if (!entry.mRenderingNode) @@ -378,9 +378,7 @@ void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float for (QuadTreeWorld::ChunkManager* m : mChunkManagers) { - if (mRevalidateDistance && m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance) - continue; - osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); + osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); if (n) pat->addChild(n); } entry.mRenderingNode = pat; @@ -462,7 +460,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) for (unsigned int i=0; igetNumEntries(); ++i) { ViewDataEntry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false, mViewDataMap->getReuseDistance()); + loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false); entry.mRenderingNode->accept(nv); } @@ -541,12 +539,11 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: reporter.addTotal(progressTotal); } - const float reuseDistance = std::max(mViewDataMap->getReuseDistance(), std::abs(distanceModifier)); for (unsigned int i=startEntry; igetNumEntries() && !abort; ++i) { ViewDataEntry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, cellWorldSize, grid, true, reuseDistance); + loadRenderingNode(entry, vd, cellWorldSize, grid, true); if (pass==0) reporter.addProgress(entry.mNode->getSize()); entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass } @@ -584,7 +581,7 @@ void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m) mChunkManagers.push_back(m); mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask()); if (m->getViewDistance()) - mRevalidateDistance = std::max(m->getViewDistance(), mRevalidateDistance); + m->setMaxLodLevel(DefaultLodCallback::convertDistanceToLodLevel(m->getViewDistance() + mViewDataMap->getReuseDistance(), mMinSize, mLodFactor)); } void QuadTreeWorld::rebuildViews() diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 9d21d65fc5..3748f7df98 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -56,14 +56,19 @@ namespace Terrain void setViewDistance(float viewDistance) { mViewDistance = viewDistance; } float getViewDistance() const { return mViewDistance; } + + // Automatically set by addChunkManager based on getViewDistance() + unsigned int getMaxLodLevel() const { return mMaxLodLevel; } + void setMaxLodLevel(unsigned int level) { mMaxLodLevel = level; } private: float mViewDistance = 0.f; + unsigned int mMaxLodLevel = ~0u; }; void addChunkManager(ChunkManager*); private: void ensureQuadTreeBuilt(); - void loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance); + void loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile); osg::ref_ptr mRootNode; @@ -79,7 +84,6 @@ namespace Terrain float mMinSize; bool mDebugTerrainChunks; std::unique_ptr mDebugChunkManager; - float mRevalidateDistance; }; } diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index ae23f034a8..033b5f5faa 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -145,7 +145,6 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); - vd->setChanged(true); needsUpdate = true; } } diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index b7dbc977b1..d51da011a2 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -49,7 +49,7 @@ namespace Terrain double getLastUsageTimeStamp() const { return mLastUsageTimeStamp; } void setLastUsageTimeStamp(double timeStamp) { mLastUsageTimeStamp = timeStamp; } - /// Indicates at least one mNode of mEntries has changed or the view point has moved beyond mReuseDistance. + /// Indicates at least one mNode of mEntries has changed. /// @note Such changes may necessitate a revalidation of cached mRenderingNodes elsewhere depending /// on the parameters that affect the creation of mRenderingNode. bool hasChanged() const { return mChanged; } From 1a2fde04bb2757ca852584d7d7b3080a89cfdd93 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Nov 2021 17:28:54 +0100 Subject: [PATCH 1670/2859] Clarify logic --- apps/openmw/mwmechanics/spellpriority.cpp | 27 ++++++++++------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 7338f4b2af..acc9fc2a3f 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -369,25 +369,22 @@ namespace MWMechanics break; case ESM::MagicEffect::BoundShield: - if(actor.getClass().hasInventoryStore(actor)) + if(!actor.getClass().hasInventoryStore(actor)) + return 0.f; + else if(!actor.getClass().isNpc()) { // If the actor is an NPC they can benefit from the armor rating, otherwise check if we've got a one-handed weapon to use with the shield - if(!actor.getClass().isNpc()) + const auto& store = actor.getClass().getInventoryStore(actor); + auto oneHanded = std::find_if(store.cbegin(MWWorld::ContainerStore::Type_Weapon), store.cend(), [](const MWWorld::ConstPtr& weapon) { - const auto& store = actor.getClass().getInventoryStore(actor); - auto oneHanded = std::find_if(store.cbegin(MWWorld::ContainerStore::Type_Weapon), store.cend(), [](const MWWorld::ConstPtr& weapon) - { - if(weapon.getClass().getItemHealth(weapon) <= 0.f) - return false; - auto type = weapon.get()->mBase->mData.mType; - return !(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded); - }); - if(oneHanded == store.cend()) - return 0.f; - } + if(weapon.getClass().getItemHealth(weapon) <= 0.f) + return false; + short type = weapon.get()->mBase->mData.mType; + return !(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded); + }); + if(oneHanded == store.cend()) + return 0.f; } - else - return 0.f; break; // Creatures can not wear armor case ESM::MagicEffect::BoundCuirass: From 5f5163905abb8c74876fd2f7f93c1d462f4bbff6 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Nov 2021 14:14:15 +0100 Subject: [PATCH 1671/2859] Remove unused operator< for RecastMesh --- components/detournavigator/recastmesh.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index c8e160603b..e2e7b9e188 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -132,11 +132,6 @@ namespace DetourNavigator std::vector mFlatHeightfields; Bounds mBounds; - friend inline bool operator <(const RecastMesh& lhs, const RecastMesh& rhs) noexcept - { - return std::tie(lhs.mMesh, lhs.mWater) < std::tie(rhs.mMesh, rhs.mWater); - } - friend inline std::size_t getSize(const RecastMesh& value) noexcept { return getSize(value.mMesh) + value.mWater.size() * sizeof(Cell) From 671e1e542402d104a128bff1309f117dfa43dee8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Nov 2021 17:36:53 +0100 Subject: [PATCH 1672/2859] Avoid copy when adding heightfield to vector --- components/detournavigator/recastmeshbuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 8f860d2eb1..ae3458a011 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -242,7 +242,7 @@ namespace DetourNavigator heightfield.mShift = shift + osg::Vec3f(minX, minY, 0) * stepSize - osg::Vec3f(halfCellSize, halfCellSize, 0); heightfield.mScale = stepSize; heightfield.mHeights = std::move(tileHeights); - mHeightfields.emplace_back(heightfield); + mHeightfields.push_back(std::move(heightfield)); } std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) && From 5972520b1abb4ffa971c930e4c8861ecd990d69d Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Nov 2021 17:52:37 +0100 Subject: [PATCH 1673/2859] Make sure areas size is 2 for rectangle --- components/detournavigator/makenavmesh.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 3f133f5033..3043c36308 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -198,7 +198,7 @@ namespace } bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, const rcConfig& config, - const unsigned char* areas, std::size_t areasSize, rcHeightfield& solid) + AreaType areaType, rcHeightfield& solid) { const osg::Vec2f tileBoundsMin( std::clamp(rectangle.mBounds.mMin.x(), config.bmin[0], config.bmax[0]), @@ -224,13 +224,15 @@ namespace 0, 2, 3, }; + const std::array areas {areaType, areaType}; + return rcRasterizeTriangles( &context, vertices.data(), static_cast(vertices.size() / 3), indices.data(), - areas, - static_cast(areasSize), + areas.data(), + static_cast(areas.size()), solid, config.walkableClimb ); @@ -239,11 +241,10 @@ namespace bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector& cells, const Settings& settings, const rcConfig& config, rcHeightfield& solid) { - const std::array areas {{AreaType_water, AreaType_water}}; for (const Cell& cell : cells) { const Rectangle rectangle = getSwimRectangle(cell, settings, agentHalfExtents); - if (!rasterizeTriangles(context, rectangle, config, areas.data(), areas.size(), solid)) + if (!rasterizeTriangles(context, rectangle, config, AreaType_water, solid)) return false; } return true; @@ -254,9 +255,8 @@ namespace { for (const FlatHeightfield& heightfield : heightfields) { - const std::array areas {{AreaType_ground, AreaType_ground}}; const Rectangle rectangle {heightfield.mBounds, toNavMeshCoordinates(settings, heightfield.mHeight)}; - if (!rasterizeTriangles(context, rectangle, config, areas.data(), areas.size(), solid)) + if (!rasterizeTriangles(context, rectangle, config, AreaType_ground, solid)) return false; } return true; From f2bc179b0ac24fa282072af33aa42265124b68a1 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 9 Nov 2021 10:20:32 +0100 Subject: [PATCH 1674/2859] Add groundcover back into view --- apps/openmw/mwrender/groundcover.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 25c00fb976..3f4e592688 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -242,7 +242,6 @@ namespace MWRender osg::ComputeBoundsVisitor cbv; group->accept(cbv); osg::BoundingBox box = cbv.getBoundingBox(); - box = osg::BoundingBox(box._min+worldCenter, box._max+worldCenter); group->addCullCallback(new ViewDistanceCallback(getViewDistance(), box)); group->setStateSet(mStateset); From 3b6184dcdac631ba3ccd234f7db2e827617e9c2b Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 9 Nov 2021 12:32:25 +0100 Subject: [PATCH 1675/2859] Add missing include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In file included from /<>/openmw-0.47.0+git202111080927~ubuntu18.04.1/components/detournavigator/findsmoothpath.cpp:1:0: /<>/openmw-0.47.0+git202111080927~ubuntu18.04.1/components/detournavigator/findsmoothpath.hpp:93:48: error: field ‘mSettings’ has incomplete type ‘std::reference_wrapper’ std::reference_wrapper mSettings; ^~~~~~~~~ In file included from /usr/include/c++/7/bits/move.h:54:0, from /usr/include/c++/7/bits/nested_exception.h:40, from /usr/include/c++/7/exception:143, from /usr/include/c++/7/ios:39, from /usr/include/c++/7/istream:38, from /usr/include/c++/7/sstream:38, from /<>/openmw-0.47.0+git202111080927~ubuntu18.04.1/components/detournavigator/dtstatus.hpp:6, from /<>/openmw-0.47.0+git202111080927~ubuntu18.04.1/components/detournavigator/findsmoothpath.hpp:4, from /<>/openmw-0.47.0+git202111080927~ubuntu18.04.1/components/detournavigator/findsmoothpath.cpp:1: /usr/include/c++/7/type_traits:2125:11: note: declaration of ‘class std::reference_wrapper’ class reference_wrapper; --- components/detournavigator/findsmoothpath.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 07d3054e19..2e63340578 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -18,6 +18,7 @@ #include #include +#include class dtNavMesh; From 099cd8a20c3e4d38894f16f9b75c8245bcc8f6c5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 9 Nov 2021 16:47:42 +0100 Subject: [PATCH 1676/2859] Force alphabetical order per data dir --- .../contentselector/model/contentmodel.cpp | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 355f5741bf..ac7851d99c 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -1,8 +1,8 @@ #include "contentmodel.hpp" #include "esmfile.hpp" -#include #include +#include #include #include @@ -421,6 +421,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) if (mShowOMWScripts) filters << "*.omwscripts"; dir.setNameFilters(filters); + dir.setSorting(QDir::Name); for (const QString &path2 : dir.entryList()) { @@ -504,28 +505,34 @@ QStringList ContentSelectorModel::ContentModel::gameFiles() const void ContentSelectorModel::ContentModel::sortFiles() { emit layoutAboutToBeChanged(); - std::sort(mFiles.begin(), mFiles.end(), [](const EsmFile* a, const EsmFile* b) - { - return a->fileName().compare(b->fileName(), Qt::CaseInsensitive) > 0; - }); //Dependency sort - int sorted = 1; - //iterate each file, obtaining a reference to its gamefiles list - for(int i = sorted; i < mFiles.size(); ++i) + std::unordered_set moved; + for(int i = mFiles.size() - 1; i > 0;) { - const QStringList& gameFiles = mFiles.at(i)->gameFiles(); - int j = sorted; - for(;j > 0; --j) + const auto file = mFiles.at(i); + if(moved.find(file) == moved.end()) { - const auto file = mFiles.at(j - 1); - if(gameFiles.contains(file->fileName(), Qt::CaseInsensitive) - || (!mFiles.at(i)->isGameFile() && gameFiles.isEmpty() - && file->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files - break; + int index = -1; + for(int j = 0; j < i; ++j) + { + const QStringList& gameFiles = mFiles.at(j)->gameFiles(); + if(gameFiles.contains(file->fileName(), Qt::CaseInsensitive) + || (!mFiles.at(j)->isGameFile() && gameFiles.isEmpty() + && file->fileName().compare("Morrowind.esm", Qt::CaseInsensitive) == 0)) // Hack: implicit dependency on Morrowind.esm for dependency-less files + { + index = j; + break; + } + } + if(index >= 0) + { + mFiles.move(i, index); + moved.insert(file); + continue; + } } - if(i != j) - mFiles.move(i, j); - ++sorted; + --i; + moved.clear(); } emit layoutChanged(); } From fbc7cf5e651ecb5b5d762a9aeca3aae0d44a5ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Thu, 4 Nov 2021 00:37:09 -0400 Subject: [PATCH 1677/2859] Fix #6386 Use `gl_FragCoords` instead passing the normalised screen-space coordinates to the fragment shader in a numerically unstable way. --- files/shaders/water_fragment.glsl | 6 +++--- files/shaders/water_vertex.glsl | 9 --------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 26f83e052c..1dbe7d14cf 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -132,7 +132,6 @@ vec2 normalCoords(vec2 uv, float scale, float speed, float time, float timer1, f return uv * (WAVE_SCALE * scale) + WIND_DIR * time * (WIND_SPEED * speed) -(previousNormal.xy/previousNormal.zz) * WAVE_CHOPPYNESS + vec2(time * timer1,time * timer2); } -varying vec3 screenCoordsPassthrough; varying vec4 position; varying float linearDepth; @@ -152,6 +151,8 @@ uniform vec3 nodePosition; uniform float rainIntensity; +uniform vec2 screenRes; + #define PER_PIXEL_LIGHTING 0 #include "shadows_fragment.glsl" @@ -178,8 +179,7 @@ void main(void) float shadow = unshadowedLightRatio(linearDepth); - vec2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; - screenCoords.y = (1.0-screenCoords.y); + vec2 screenCoords = gl_FragCoord.xy / screenRes; #define waterTimer osg_SimulationTime diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 8e506a57f8..3a6c352ac8 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -2,7 +2,6 @@ uniform mat4 projectionMatrix; -varying vec3 screenCoordsPassthrough; varying vec4 position; varying float linearDepth; @@ -13,14 +12,6 @@ void main(void) { gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); - mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, - 0.0, -0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); - - vec4 texcoordProj = ((scalemat) * ( gl_Position)); - screenCoordsPassthrough = texcoordProj.xyw; - position = gl_Vertex; vec4 viewPos = gl_ModelViewMatrix * gl_Vertex; From 81f2fa992f42c7a51e0d7c85c0dddb0ccd658ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=B6ckel?= Date: Tue, 9 Nov 2021 23:27:32 -0500 Subject: [PATCH 1678/2859] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cbf9802e..e50b77229a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ Bug #6354: SFX abruptly cut off after crossing max distance; implement soft fading of sound effects Bug #6363: Some scripts in Morrowland fail to work Bug #6376: Creatures should be able to use torches + Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console From 835bec0ed1da41220ba6766497b46be218c9f6d5 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 09:36:01 +0300 Subject: [PATCH 1679/2859] Fix stupid typo --- apps/openmw/mwrender/localmap.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 0eb0e7738a..66c8c55c60 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -647,8 +647,7 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient uint32_t clr = *(uint32_t*)data; uint8_t alpha = (clr >> 24); - - alpha = std::min( alpha, (uint8_t) (std::clamp((sqrDist/sqrExploreRadius)*255, 0.f, 1.f))); + alpha = std::min(alpha, (uint8_t)(std::clamp(sqrDist/sqrExploreRadius, 0.f, 1.f) * 255)); uint32_t val = (uint32_t) (alpha << 24); if ( *data != val) { From 0d13b8cd3f1a1be2ea0211b0e4339ea7d2896af8 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 15:44:45 +0300 Subject: [PATCH 1680/2859] Allow movement during blocking (bug #6327) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cbf9802e..769f09bedb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Bug #6322: Total sold/cost should reset to 0 when there are no items offered Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Bug #6326: Detect Enchantment/Key should detect items in unresolved containers + Bug #6327: Blocking roots the character in place Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures Bug #6354: SFX abruptly cut off after crossing max distance; implement soft fading of sound effects Bug #6363: Some scripts in Morrowland fail to work diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 36d4446580..0cbe5c284a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2061,7 +2061,7 @@ void CharacterController::update(float duration) vec.x() *= speed; vec.y() *= speed; - if(mHitState != CharState_None && mJumpState == JumpState_None) + if(mHitState != CharState_None && mHitState != CharState_Block && mJumpState == JumpState_None) vec = osg::Vec3f(); CharacterState movestate = CharState_None; From 9500afaa5a265ba49fcf8bb95b73edc4b1c9a4e8 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 19:21:07 +0300 Subject: [PATCH 1681/2859] Load BSFurnitureMarker --- components/nif/extra.cpp | 34 ++++++++++++++++++++++++++++++++++ components/nif/extra.hpp | 24 ++++++++++++++++++++++++ components/nif/niffile.cpp | 1 + components/nif/record.hpp | 3 ++- 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index a45ea8c50b..1401291a0f 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -94,4 +94,38 @@ void BSBound::read(NIFStream *nif) halfExtents = nif->getVector3(); } +void BSFurnitureMarker::LegacyFurniturePosition::read(NIFStream *nif) +{ + offset = nif->getVector3(); + orientation = nif->getUShort(); + positionRef = nif->getChar(); + nif->skip(1); // Position ref 2 +} + +void BSFurnitureMarker::FurniturePosition::read(NIFStream *nif) +{ + offset = nif->getVector3(); + heading = nif->getFloat(); + type = nif->getUShort(); + entryPoint = nif->getUShort(); +} + +void BSFurnitureMarker::read(NIFStream *nif) +{ + Extra::read(nif); + unsigned int num = nif->getUInt(); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + { + legacyMarkers.resize(num); + for (auto& marker : legacyMarkers) + marker.read(nif); + } + else + { + markers.resize(num); + for (auto& marker : markers) + marker.read(nif); + } +} + } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index f4ac1caff9..bbc5c216d8 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -120,5 +120,29 @@ struct BSBound : public Extra void read(NIFStream *nif) override; }; +struct BSFurnitureMarker : public Extra +{ + struct LegacyFurniturePosition + { + osg::Vec3f offset; + uint16_t orientation; + uint8_t positionRef; + void read(NIFStream *nif); + }; + + struct FurniturePosition + { + osg::Vec3f offset; + float heading; + uint16_t type, entryPoint; + void read(NIFStream *nif); + }; + + std::vector legacyMarkers; + std::vector markers; + + void read(NIFStream *nif) override; +}; + } // Namespace #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 6863209988..7f330450a0 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -136,6 +136,7 @@ static std::map makeFactory() factory["BSShaderProperty"] = {&construct , RC_BSShaderProperty }; factory["BSShaderPPLightingProperty"] = {&construct , RC_BSShaderPPLightingProperty }; factory["BSShaderNoLightingProperty"] = {&construct , RC_BSShaderNoLightingProperty }; + factory["BSFurnitureMarker"] = {&construct , RC_BSFurnitureMarker }; return factory; } diff --git a/components/nif/record.hpp b/components/nif/record.hpp index dc81eb69c7..dc7314cc95 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -125,7 +125,8 @@ enum RecordType RC_BSLODTriShape, RC_BSShaderProperty, RC_BSShaderPPLightingProperty, - RC_BSShaderNoLightingProperty + RC_BSShaderNoLightingProperty, + RC_BSFurnitureMarker }; /// Base class for all records From 55710991476930c79c6cc37cc88aa625ea31c58b Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 19:31:28 +0300 Subject: [PATCH 1682/2859] Load NiCollisionObject and bhkCollisionObject --- components/CMakeLists.txt | 2 +- components/nif/niffile.cpp | 2 ++ components/nif/node.hpp | 7 +++- components/nif/physics.cpp | 32 +++++++++++++++++++ components/nif/physics.hpp | 62 ++++++++++++++++++++++++++++++++++++ components/nif/record.hpp | 4 ++- components/nif/recordptr.hpp | 6 ++++ 7 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 components/nif/physics.cpp create mode 100644 components/nif/physics.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0b3c95ff45..5c0ead4ad0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -61,7 +61,7 @@ add_component_dir (sceneutil ) add_component_dir (nif - controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream + controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream physics ) add_component_dir (nifosg diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 7f330450a0..1f6cc256fc 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -137,6 +137,8 @@ static std::map makeFactory() factory["BSShaderPPLightingProperty"] = {&construct , RC_BSShaderPPLightingProperty }; factory["BSShaderNoLightingProperty"] = {&construct , RC_BSShaderNoLightingProperty }; factory["BSFurnitureMarker"] = {&construct , RC_BSFurnitureMarker }; + factory["NiCollisionObject"] = {&construct , RC_NiCollisionObject }; + factory["bhkCollisionObject"] = {&construct , RC_bhkCollisionObject }; return factory; } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 406a4d4549..c01ba9b663 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -8,6 +8,7 @@ #include "niftypes.hpp" #include "controller.hpp" #include "base.hpp" +#include "physics.hpp" #include @@ -143,6 +144,9 @@ struct Node : public Named bool hasBounds{false}; NiBoundingVolume bounds; + // Collision object info + NiCollisionObjectPtr collision; + void read(NIFStream *nif) override { Named::read(nif); @@ -160,7 +164,7 @@ struct Node : public Named bounds.read(nif); // Reference to the collision object in Gamebryo files. if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) - nif->skip(4); + collision.read(nif); parent = nullptr; @@ -171,6 +175,7 @@ struct Node : public Named { Named::post(nif); props.post(nif); + collision.post(nif); } // Parent node, or nullptr for the root node. As far as I'm aware, only diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp new file mode 100644 index 0000000000..fbbc459ab2 --- /dev/null +++ b/components/nif/physics.cpp @@ -0,0 +1,32 @@ +#include "physics.hpp" +#include "node.hpp" + +namespace Nif +{ + void bhkCollisionObject::read(NIFStream *nif) + { + NiCollisionObject::read(nif); + flags = nif->getUShort(); + body.read(nif); + } + + void bhkWorldObject::read(NIFStream *nif) + { + shape.read(nif); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + nif->skip(4); // Unknown + flags = nif->getUInt(); + nif->skip(4); // Unused + worldObjectInfo.phaseType = nif->getChar(); + nif->skip(3); // Unused + worldObjectInfo.data = nif->getUInt(); + worldObjectInfo.size = nif->getUInt(); + worldObjectInfo.capacityAndFlags = nif->getUInt(); + } + + void bhkWorldObject::post(NIFFile *nif) + { + shape.post(nif); + } + +} // Namespace \ No newline at end of file diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp new file mode 100644 index 0000000000..e64eb349eb --- /dev/null +++ b/components/nif/physics.hpp @@ -0,0 +1,62 @@ +#ifndef OPENMW_COMPONENTS_NIF_PHYSICS_HPP +#define OPENMW_COMPONENTS_NIF_PHYSICS_HPP + +#include "base.hpp" + +// This header contains certain record definitions +// specific to Bethesda implementation of Havok physics +namespace Nif +{ + +// Generic collision object +struct NiCollisionObject : public Record +{ + // The node that references this object + NodePtr target; + + void read(NIFStream *nif) override + { + target.read(nif); + } + void post(NIFFile *nif) override + { + target.post(nif); + } +}; + +// Bethesda Havok-specific collision object +struct bhkCollisionObject : public NiCollisionObject +{ + unsigned short flags; + CollisionBodyPtr body; + + void read(NIFStream *nif) override; + void post(NIFFile *nif) override + { + NiCollisionObject::post(nif); + body.post(nif); + } +}; + + +// Abstract Havok shape info record +struct bhkWorldObject : public Record +{ + bhkShapePtr shape; + unsigned int flags; // Havok layer type, collision filter flags and group + struct WorldObjectInfo + { + unsigned char phaseType; + unsigned int data; + unsigned int size; + unsigned int capacityAndFlags; + }; + WorldObjectInfo worldObjectInfo; + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + +struct bhkShape : public Record {}; + +} // Namespace +#endif \ No newline at end of file diff --git a/components/nif/record.hpp b/components/nif/record.hpp index dc7314cc95..88ffbe4141 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -126,7 +126,9 @@ enum RecordType RC_BSShaderProperty, RC_BSShaderPPLightingProperty, RC_BSShaderNoLightingProperty, - RC_BSFurnitureMarker + RC_BSFurnitureMarker, + RC_NiCollisionObject, + RC_bhkCollisionObject }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index b30d99fbe4..a25480fe43 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -148,6 +148,9 @@ struct BSShaderTextureSet; struct NiGeometryData; struct BSShaderProperty; struct NiAlphaProperty; +struct NiCollisionObject; +struct bhkWorldObject; +struct bhkShape; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -175,6 +178,9 @@ using BSShaderTextureSetPtr = RecordPtrT; using NiGeometryDataPtr = RecordPtrT; using BSShaderPropertyPtr = RecordPtrT; using NiAlphaPropertyPtr = RecordPtrT; +using NiCollisionObjectPtr = RecordPtrT; +using CollisionBodyPtr = RecordPtrT; +using bhkShapePtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; From 41097352a8d4fe1ac58490f58fe1b26e0d5369e2 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 19:40:02 +0300 Subject: [PATCH 1683/2859] Load BSDismemberSkinInstance --- components/nif/data.cpp | 7 +++++++ components/nif/data.hpp | 5 +++++ components/nif/niffile.cpp | 1 + components/nif/record.hpp | 3 ++- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index b6674611bc..cac924733d 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -34,6 +34,13 @@ void NiSkinInstance::post(NIFFile *nif) } } +void BSDismemberSkinInstance::read(NIFStream *nif) +{ + NiSkinInstance::read(nif); + unsigned int numPartitions = nif->getUInt(); + nif->skip(4 * numPartitions); // Body part information +} + void NiGeometryData::read(NIFStream *nif) { if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,114)) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index efbe138c53..70171ac47c 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -170,6 +170,11 @@ struct NiSkinInstance : public Record void post(NIFFile *nif) override; }; +struct BSDismemberSkinInstance : public NiSkinInstance +{ + void read(NIFStream *nif) override; +}; + struct NiSkinData : public Record { struct VertWeight diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 1f6cc256fc..4e184f68fa 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -139,6 +139,7 @@ static std::map makeFactory() factory["BSFurnitureMarker"] = {&construct , RC_BSFurnitureMarker }; factory["NiCollisionObject"] = {&construct , RC_NiCollisionObject }; factory["bhkCollisionObject"] = {&construct , RC_bhkCollisionObject }; + factory["BSDismemberSkinInstance"] = {&construct , RC_BSDismemberSkinInstance }; return factory; } diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 88ffbe4141..23a1a6df2b 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -128,7 +128,8 @@ enum RecordType RC_BSShaderNoLightingProperty, RC_BSFurnitureMarker, RC_NiCollisionObject, - RC_bhkCollisionObject + RC_bhkCollisionObject, + RC_BSDismemberSkinInstance }; /// Base class for all records From 923756b407feecb97673e9a557472c582e86b0c5 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 19:42:55 +0300 Subject: [PATCH 1684/2859] Load NiControllerManager --- components/nif/controller.cpp | 9 +++++++++ components/nif/controller.hpp | 6 ++++++ components/nif/niffile.cpp | 1 + components/nif/record.hpp | 3 ++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 704c4928e6..513c781c58 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -270,6 +270,15 @@ namespace Nif nif->getUInt(); // Zero } + void NiControllerManager::read(NIFStream *nif) + { + Controller::read(nif); + cumulative = nif->getBoolean(); + unsigned int numSequences = nif->getUInt(); + nif->skip(4 * numSequences); // Controller sequences + nif->skip(4); // Object palette + } + void NiPoint3Interpolator::read(NIFStream *nif) { defaultVal = nif->getVector3(); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 503710fe90..15a2937b80 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -184,6 +184,12 @@ struct bhkBlendController : public Controller void read(NIFStream *nif) override; }; +struct NiControllerManager : public Controller +{ + bool cumulative; + void read(NIFStream *nif) override; +}; + struct Interpolator : public Record { }; struct NiPoint3Interpolator : public Interpolator diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 4e184f68fa..24b9bbceed 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -140,6 +140,7 @@ static std::map makeFactory() factory["NiCollisionObject"] = {&construct , RC_NiCollisionObject }; factory["bhkCollisionObject"] = {&construct , RC_bhkCollisionObject }; factory["BSDismemberSkinInstance"] = {&construct , RC_BSDismemberSkinInstance }; + factory["NiControllerManager"] = {&construct , RC_NiControllerManager }; return factory; } diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 23a1a6df2b..0b9b2dc998 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -129,7 +129,8 @@ enum RecordType RC_BSFurnitureMarker, RC_NiCollisionObject, RC_bhkCollisionObject, - RC_BSDismemberSkinInstance + RC_BSDismemberSkinInstance, + RC_NiControllerManager }; /// Base class for all records From 9880c43c86b699f30eef2cc78143162d34cff70c Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 19:58:06 +0300 Subject: [PATCH 1685/2859] Add specular strength shader parameter --- apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + components/nifosg/nifloader.cpp | 3 +++ components/resource/scenemanager.cpp | 1 + files/shaders/nv_default_fragment.glsl | 3 ++- files/shaders/objects_fragment.glsl | 2 ++ 6 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 4e21d33475..90069c2d8d 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -359,6 +359,7 @@ namespace MWRender stateset->setAttribute(m); stateset->addUniform(new osg::Uniform("colorMode", 0)); stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); + stateset->addUniform(new osg::Uniform("specStrength", 1.f)); node.setStateSet(stateset); } }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 06bfb3582f..e0cd3f713a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -500,6 +500,7 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); mFog.reset(new FogManager()); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a745d50eda..e46e2d934b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1932,6 +1932,7 @@ namespace NifOsg int lightmode = 1; float emissiveMult = 1.f; + float specStrength = 1.f; for (const Nif::Property* property : properties) { @@ -2081,6 +2082,8 @@ namespace NifOsg stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); if (emissiveMult != 1.f) stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); + if (specStrength != 1.f) + stateset->addUniform(new osg::Uniform("specStrength", specStrength)); } }; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 8a88f7def9..f6c041a879 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -524,6 +524,7 @@ namespace Resource result.getNode()->accept(colladaAlphaTrickVisitor); result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); + result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); } diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 17204534e3..ff81a2b94d 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -39,6 +39,7 @@ varying vec3 passNormal; #include "alpha.glsl" uniform float emissiveMult; +uniform float specStrength; void main() { @@ -80,7 +81,7 @@ void main() gl_FragData[0].xyz *= lighting; float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = getSpecularColor().xyz; + vec3 matSpec = getSpecularColor().xyz * specStrength; #if @normalMap matSpec *= normalTex.a; #endif diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index d750a4dd1f..99ed44919b 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -71,6 +71,7 @@ centroid varying vec3 shadowDiffuseLighting; #else uniform float emissiveMult; #endif +uniform float specStrength; varying vec3 passViewPos; varying vec3 passNormal; @@ -204,6 +205,7 @@ void main() vec3 matSpec = getSpecularColor().xyz; #endif + matSpec *= specStrength; if (matSpec != vec3(0.0)) { #if (!@normalMap && !@parallax && !@forcePPL) From 6de9b49d3dfdaf6934f6abf1a1f5f839109452c4 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 19:59:13 +0300 Subject: [PATCH 1686/2859] Remove an empty line --- components/nif/physics.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index e64eb349eb..a8f053b3be 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -38,7 +38,6 @@ struct bhkCollisionObject : public NiCollisionObject } }; - // Abstract Havok shape info record struct bhkWorldObject : public Record { From 6e5b45453d919a0e952a806f362a79b83c7990f5 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 20:16:05 +0300 Subject: [PATCH 1687/2859] some physics stuff idk --- components/nif/physics.cpp | 22 ++++++++++++++++++++++ components/nif/physics.hpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index fbbc459ab2..fddaaa8cc1 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -29,4 +29,26 @@ namespace Nif shape.post(nif); } + void bhkEntity::read(NIFStream *nif) + { + bhkWorldObject::read(nif); + responseType = static_cast(nif->getChar()); + nif->skip(1); // Unused + processContactDelay = nif->getUShort(); + } + + void HavokMaterial::read(NIFStream *nif) + { + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + nif->skip(4); // Unknown + material = nif->getUInt(); + } + + void hkSubPartData::read(NIFStream *nif) + { + havokFilter = nif->getUInt(); + numVertices = nif->getUInt(); + material.read(nif); + } + } // Namespace \ No newline at end of file diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index a8f053b3be..ae7da6d141 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -57,5 +57,33 @@ struct bhkWorldObject : public Record struct bhkShape : public Record {}; +enum class hkResponseType : uint8_t +{ + Invalid = 0, + SimpleContact = 1, + Reporting = 2, + None = 3 +}; + +struct bhkEntity : public bhkWorldObject +{ + hkResponseType responseType; + unsigned short processContactDelay; + void read(NIFStream *nif) override; +}; + +struct HavokMaterial +{ + unsigned int material; + void read(NIFStream *nif); +}; + +struct hkSubPartData +{ + HavokMaterial material; + unsigned int numVertices, havokFilter; + void read(NIFStream *nif); +}; + } // Namespace #endif \ No newline at end of file From cc4c96d0f1358c9c8cc745f2354c817a858d172f Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 10 Nov 2021 22:50:01 +0300 Subject: [PATCH 1688/2859] Follow global naming convention for new records --- components/nif/controller.cpp | 2 +- components/nif/controller.hpp | 2 +- components/nif/extra.cpp | 22 ++++++++--------- components/nif/extra.hpp | 17 ++++++------- components/nif/physics.cpp | 30 +++++++++++------------ components/nif/physics.hpp | 45 ++++++++++++++++++----------------- 6 files changed, 60 insertions(+), 58 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 513c781c58..16e6b5e40f 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -273,7 +273,7 @@ namespace Nif void NiControllerManager::read(NIFStream *nif) { Controller::read(nif); - cumulative = nif->getBoolean(); + mCumulative = nif->getBoolean(); unsigned int numSequences = nif->getUInt(); nif->skip(4 * numSequences); // Controller sequences nif->skip(4); // Object palette diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 15a2937b80..210eaab217 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -186,7 +186,7 @@ struct bhkBlendController : public Controller struct NiControllerManager : public Controller { - bool cumulative; + bool mCumulative; void read(NIFStream *nif) override; }; diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index 1401291a0f..04217995ac 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -96,18 +96,18 @@ void BSBound::read(NIFStream *nif) void BSFurnitureMarker::LegacyFurniturePosition::read(NIFStream *nif) { - offset = nif->getVector3(); - orientation = nif->getUShort(); - positionRef = nif->getChar(); + mOffset = nif->getVector3(); + mOrientation = nif->getUShort(); + mPositionRef = nif->getChar(); nif->skip(1); // Position ref 2 } void BSFurnitureMarker::FurniturePosition::read(NIFStream *nif) { - offset = nif->getVector3(); - heading = nif->getFloat(); - type = nif->getUShort(); - entryPoint = nif->getUShort(); + mOffset = nif->getVector3(); + mHeading = nif->getFloat(); + mType = nif->getUShort(); + mEntryPoint = nif->getUShort(); } void BSFurnitureMarker::read(NIFStream *nif) @@ -116,14 +116,14 @@ void BSFurnitureMarker::read(NIFStream *nif) unsigned int num = nif->getUInt(); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) { - legacyMarkers.resize(num); - for (auto& marker : legacyMarkers) + mLegacyMarkers.resize(num); + for (auto& marker : mLegacyMarkers) marker.read(nif); } else { - markers.resize(num); - for (auto& marker : markers) + mMarkers.resize(num); + for (auto& marker : mMarkers) marker.read(nif); } } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index bbc5c216d8..8eb14f9b01 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -124,22 +124,23 @@ struct BSFurnitureMarker : public Extra { struct LegacyFurniturePosition { - osg::Vec3f offset; - uint16_t orientation; - uint8_t positionRef; + osg::Vec3f mOffset; + uint16_t mOrientation; + uint8_t mPositionRef; void read(NIFStream *nif); }; struct FurniturePosition { - osg::Vec3f offset; - float heading; - uint16_t type, entryPoint; + osg::Vec3f mOffset; + float mHeading; + uint16_t mType; + uint16_t mEntryPoint; void read(NIFStream *nif); }; - std::vector legacyMarkers; - std::vector markers; + std::vector mLegacyMarkers; + std::vector mMarkers; void read(NIFStream *nif) override; }; diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index fddaaa8cc1..992a56c9b7 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -6,49 +6,49 @@ namespace Nif void bhkCollisionObject::read(NIFStream *nif) { NiCollisionObject::read(nif); - flags = nif->getUShort(); - body.read(nif); + mFlags = nif->getUShort(); + mBody.read(nif); } void bhkWorldObject::read(NIFStream *nif) { - shape.read(nif); + mShape.read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) nif->skip(4); // Unknown - flags = nif->getUInt(); + mFlags = nif->getUInt(); nif->skip(4); // Unused - worldObjectInfo.phaseType = nif->getChar(); + mWorldObjectInfo.mPhaseType = nif->getChar(); nif->skip(3); // Unused - worldObjectInfo.data = nif->getUInt(); - worldObjectInfo.size = nif->getUInt(); - worldObjectInfo.capacityAndFlags = nif->getUInt(); + mWorldObjectInfo.mData = nif->getUInt(); + mWorldObjectInfo.mSize = nif->getUInt(); + mWorldObjectInfo.mCapacityAndFlags = nif->getUInt(); } void bhkWorldObject::post(NIFFile *nif) { - shape.post(nif); + mShape.post(nif); } void bhkEntity::read(NIFStream *nif) { bhkWorldObject::read(nif); - responseType = static_cast(nif->getChar()); + mResponseType = static_cast(nif->getChar()); nif->skip(1); // Unused - processContactDelay = nif->getUShort(); + mProcessContactDelay = nif->getUShort(); } void HavokMaterial::read(NIFStream *nif) { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) nif->skip(4); // Unknown - material = nif->getUInt(); + mMaterial = nif->getUInt(); } void hkSubPartData::read(NIFStream *nif) { - havokFilter = nif->getUInt(); - numVertices = nif->getUInt(); - material.read(nif); + mHavokFilter = nif->getUInt(); + mNumVertices = nif->getUInt(); + mHavokMaterial.read(nif); } } // Namespace \ No newline at end of file diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index ae7da6d141..ca31512c71 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -12,45 +12,45 @@ namespace Nif struct NiCollisionObject : public Record { // The node that references this object - NodePtr target; + NodePtr mTarget; void read(NIFStream *nif) override { - target.read(nif); + mTarget.read(nif); } void post(NIFFile *nif) override { - target.post(nif); + mTarget.post(nif); } }; // Bethesda Havok-specific collision object struct bhkCollisionObject : public NiCollisionObject { - unsigned short flags; - CollisionBodyPtr body; + unsigned short mFlags; + CollisionBodyPtr mBody; void read(NIFStream *nif) override; void post(NIFFile *nif) override { NiCollisionObject::post(nif); - body.post(nif); + mBody.post(nif); } }; // Abstract Havok shape info record struct bhkWorldObject : public Record { - bhkShapePtr shape; - unsigned int flags; // Havok layer type, collision filter flags and group + bhkShapePtr mShape; + unsigned int mFlags; // Havok layer type, collision filter flags and group struct WorldObjectInfo { - unsigned char phaseType; - unsigned int data; - unsigned int size; - unsigned int capacityAndFlags; + unsigned char mPhaseType; + unsigned int mData; + unsigned int mSize; + unsigned int mCapacityAndFlags; }; - WorldObjectInfo worldObjectInfo; + WorldObjectInfo mWorldObjectInfo; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; @@ -59,29 +59,30 @@ struct bhkShape : public Record {}; enum class hkResponseType : uint8_t { - Invalid = 0, - SimpleContact = 1, - Reporting = 2, - None = 3 + Response_Invalid = 0, + Response_SimpleContact = 1, + Response_Reporting = 2, + Response_None = 3 }; struct bhkEntity : public bhkWorldObject { - hkResponseType responseType; - unsigned short processContactDelay; + hkResponseType mResponseType; + unsigned short mProcessContactDelay; void read(NIFStream *nif) override; }; struct HavokMaterial { - unsigned int material; + unsigned int mMaterial; void read(NIFStream *nif); }; struct hkSubPartData { - HavokMaterial material; - unsigned int numVertices, havokFilter; + HavokMaterial mHavokMaterial; + unsigned int mNumVertices; + unsigned int mHavokFilter; void read(NIFStream *nif); }; From 512d64e514967498a2cc2f61448880c754c5ef2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Wed, 10 Nov 2021 23:06:35 +0100 Subject: [PATCH 1689/2859] Extract correctSoundPath method to avoid code duplication. Add unit test for the new method --- apps/openmw/mwsound/openal_output.cpp | 13 +-- apps/openmw/mwsound/soundmanagerimp.cpp | 15 +--- apps/openmw_test_suite/CMakeLists.txt | 1 + .../misc/test_resourcehelpers.cpp | 80 +++++++++++++++++++ components/misc/resourcehelpers.cpp | 22 ++++- components/misc/resourcehelpers.hpp | 2 + 6 files changed, 106 insertions(+), 27 deletions(-) create mode 100644 apps/openmw_test_suite/misc/test_resourcehelpers.cpp diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 4615fed8c1..b3cc81b803 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include "openal_output.hpp" @@ -954,17 +955,7 @@ std::pair OpenAL_Output::loadSound(const std::string &fname try { DecoderPtr decoder = mManager.getDecoder(); - // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if(decoder->mResourceMgr->exists(fname)) - decoder->open(fname); - else - { - std::string file = fname; - std::string::size_type pos = file.rfind('.'); - if(pos != std::string::npos) - file = file.substr(0, pos)+".mp3"; - decoder->open(file); - } + decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, decoder->mResourceMgr)); ChannelConfig chans; SampleType type; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 758ddfdc56..5399b95c97 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -145,19 +146,7 @@ namespace MWSound try { DecoderPtr decoder = getDecoder(); - - // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if(mVFS->exists(voicefile)) - decoder->open(voicefile); - else - { - std::string file = voicefile; - std::string::size_type pos = file.rfind('.'); - if(pos != std::string::npos) - file = file.substr(0, pos)+".mp3"; - decoder->open(file); - } - + decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, decoder->mResourceMgr)); return decoder; } catch(std::exception &e) diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index bf235331cf..a4e9f8323c 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -26,6 +26,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) misc/test_stringops.cpp misc/test_endianness.cpp + misc/test_resourcehelpers.cpp misc/progressreporter.cpp misc/compression.cpp diff --git a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp new file mode 100644 index 0000000000..ee062c6da8 --- /dev/null +++ b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp @@ -0,0 +1,80 @@ +#include +#include "components/misc/resourcehelpers.hpp" +#include "../lua/testing_util.hpp" + +namespace +{ + using namespace Misc::ResourceHelpers; + TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected) + { + std::unique_ptr mVFS = createTestVFS({ + {"sound/bar.wav", nullptr} + }); + EXPECT_EQ(correctSoundPath("sound/bar.wav", mVFS.get()), "sound/bar.wav"); + } + + TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected) + { + std::unique_ptr mVFS = createTestVFS({ + {"sound/foo.mp3", nullptr} + }); + EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); + } + + TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs) + { + std::unique_ptr mVFS = createTestVFS({ + }); + EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); + } + + TEST(CorrectSoundPath, correct_path_normalize_paths) + { + std::unique_ptr mVFS = createTestVFS({ + }); + EXPECT_EQ(correctSoundPath("sound\\foo.wav", mVFS.get()), "sound/foo.mp3"); + EXPECT_EQ(correctSoundPath("SOUND\\foo.WAV", mVFS.get()), "SOUND/foo.mp3"); + } + + namespace + { + std::string checkChangeExtensionToDds(std::string path) + { + changeExtensionToDds(path); + return path; + } + } + + TEST(ChangeExtensionToDds, original_extension_with_same_size_as_dds) + { + EXPECT_EQ(checkChangeExtensionToDds("texture/bar.tga"), "texture/bar.dds"); + } + + TEST(ChangeExtensionToDds, original_extension_greater_than_dds) + { + EXPECT_EQ(checkChangeExtensionToDds("texture/bar.jpeg"), "texture/bar.dds"); + } + + TEST(ChangeExtensionToDds, original_extension_smaller_than_dds) + { + EXPECT_EQ(checkChangeExtensionToDds("texture/bar.xx"), "texture/bar.dds"); + } + + TEST(ChangeExtensionToDds, does_not_change_dds_extension) + { + std::string path = "texture/bar.dds"; + EXPECT_FALSE(changeExtensionToDds(path)); + } + + TEST(ChangeExtensionToDds, does_not_change_when_no_extension) + { + std::string path = "texture/bar"; + EXPECT_FALSE(changeExtensionToDds(path)); + } + + TEST(ChangeExtensionToDds, change_when_there_is_an_extension) + { + std::string path = "texture/bar.jpeg"; + EXPECT_TRUE(changeExtensionToDds(path)); + } +} diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 610c7a790c..0095568653 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -30,17 +30,22 @@ namespace } -bool Misc::ResourceHelpers::changeExtensionToDds(std::string &path) +bool changeExtension(std::string &path, std::string_view ext) { std::string::size_type pos = path.rfind('.'); - if(pos != std::string::npos && path.compare(pos, path.length() - pos, ".dds") != 0) + if(pos != std::string::npos && path.compare(pos, path.length() - pos, ext) != 0) { - path.replace(pos, path.length(), ".dds"); + path.replace(pos, path.length(), ext); return true; } return false; } +bool Misc::ResourceHelpers::changeExtensionToDds(std::string &path) +{ + return changeExtension(path, ".dds"); +} + std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath, const VFS::Manager* vfs) { /* Bethesda at some point converted all their BSA @@ -140,6 +145,17 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string &resP return mdlname; } +std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath, const VFS::Manager* vfs) +{ + std::string sound = resPath; + // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. + if (!vfs->exists(sound)) + changeExtension(sound, ".mp3"); + + return vfs->normalizeFilename(sound); + +} + bool Misc::ResourceHelpers::isHiddenMarker(std::string_view id) { return Misc::StringUtils::ciEqual(id, "prisonmarker") || Misc::StringUtils::ciEqual(id, "divinemarker") || Misc::StringUtils::ciEqual(id, "templemarker") || Misc::StringUtils::ciEqual(id, "northmarker"); diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 9e87954e9d..4ea5f5e121 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -25,6 +25,8 @@ namespace Misc /// Use "xfoo.nif" instead of "foo.nif" if available std::string correctActorModelPath(const std::string &resPath, const VFS::Manager* vfs); + std::string correctSoundPath(const std::string& resPath, const VFS::Manager* vfs); + /// marker objects that have a hardcoded function in the game logic, should be hidden from the player bool isHiddenMarker(std::string_view id); } From d7041613ef03ec4ca491d28686893ea9c808dfce Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 10 Nov 2021 20:24:17 +0100 Subject: [PATCH 1690/2859] Store BulletShape source file name and content hash --- .../nifloader/testbulletnifloader.cpp | 6 ++++ components/CMakeLists.txt | 4 +-- components/files/hash.cpp | 36 +++++++++++++++++++ components/files/hash.hpp | 13 +++++++ components/misc/osguservalues.cpp | 6 ++++ components/misc/osguservalues.hpp | 14 ++++++++ components/nif/niffile.cpp | 4 +++ components/nif/niffile.hpp | 5 +++ components/nifbullet/bulletnifloader.cpp | 3 ++ components/nifosg/nifloader.cpp | 5 +++ components/resource/bulletshape.cpp | 2 ++ components/resource/bulletshape.hpp | 10 ++++++ components/resource/bulletshapemanager.cpp | 12 +++++++ components/resource/scenemanager.cpp | 14 ++++++-- 14 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 components/files/hash.cpp create mode 100644 components/files/hash.hpp create mode 100644 components/misc/osguservalues.cpp create mode 100644 components/misc/osguservalues.hpp diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index b37a8cd6c0..92aece9075 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -335,6 +335,7 @@ namespace MOCK_METHOD(void, setUseSkinning, (bool), (override)); MOCK_METHOD(bool, getUseSkinning, (), (const, override)); MOCK_METHOD(std::string, getFilename, (), (const, override)); + MOCK_METHOD(std::uint64_t, getHash, (), (const, override)); MOCK_METHOD(unsigned int, getVersion, (), (const, override)); MOCK_METHOD(unsigned int, getUserVersion, (), (const, override)); MOCK_METHOD(unsigned int, getBethVersion, (), (const, override)); @@ -381,6 +382,7 @@ namespace ), btVector3(4, 8, 12) }; + const std::uint64_t mHash = 42; TestBulletNifLoader() { @@ -411,6 +413,8 @@ namespace mNiTriStripsData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0)}; mNiTriStripsData.strips = {{0, 1, 2, 3}}; mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); + + EXPECT_CALL(mNifFile, getHash()).WillOnce(Return(mHash)); } }; @@ -423,6 +427,8 @@ namespace Resource::BulletShape expected; EXPECT_EQ(*result, expected); + EXPECT_EQ(result->mFileName, "test.nif"); + EXPECT_EQ(result->mFileHash, mHash); } TEST_F(TestBulletNifLoader, should_ignore_nullptr_root) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0b3c95ff45..5a0f4cd4a2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -93,7 +93,7 @@ add_component_dir (esmterrain add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread - compression + compression osguservalues ) add_component_dir (debug @@ -106,7 +106,7 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF() add_component_dir (files linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager escape - lowlevelfile constrainedfilestream memorystream + lowlevelfile constrainedfilestream memorystream hash ) add_component_dir (compiler diff --git a/components/files/hash.cpp b/components/files/hash.cpp new file mode 100644 index 0000000000..91af1e9283 --- /dev/null +++ b/components/files/hash.cpp @@ -0,0 +1,36 @@ +#include "hash.hpp" + +#include + +#include +#include +#include +#include + +namespace Files +{ + std::uint64_t getHash(const std::string& fileName, std::istream& stream) + { + std::uint64_t hash = std::hash {}(fileName); + try + { + const auto start = stream.tellg(); + const auto exceptions = stream.exceptions(); + stream.exceptions(std::ios_base::badbit); + while (stream) + { + std::uint64_t value = 0; + stream.read(reinterpret_cast(&value), sizeof(value)); + Misc::hashCombine(hash, value); + } + stream.exceptions(exceptions); + stream.clear(); + stream.seekg(start); + } + catch (const std::exception& e) + { + throw std::runtime_error("Error while reading \"" + fileName + "\" to get hash: " + std::string(e.what())); + } + return hash; + } +} diff --git a/components/files/hash.hpp b/components/files/hash.hpp new file mode 100644 index 0000000000..46784ee9be --- /dev/null +++ b/components/files/hash.hpp @@ -0,0 +1,13 @@ +#ifndef COMPONENTS_FILES_HASH_H +#define COMPONENTS_FILES_HASH_H + +#include +#include +#include + +namespace Files +{ + std::uint64_t getHash(const std::string& fileName, std::istream& stream); +} + +#endif diff --git a/components/misc/osguservalues.cpp b/components/misc/osguservalues.cpp new file mode 100644 index 0000000000..3bdd0d1848 --- /dev/null +++ b/components/misc/osguservalues.cpp @@ -0,0 +1,6 @@ +#include "osguservalues.hpp" + +namespace Misc +{ + const std::string OsgUserValues::sFileHash = "fileHash"; +} diff --git a/components/misc/osguservalues.hpp b/components/misc/osguservalues.hpp new file mode 100644 index 0000000000..022e81764f --- /dev/null +++ b/components/misc/osguservalues.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_MISC_OSGUSERVALUES_H +#define OPENMW_COMPONENTS_MISC_OSGUSERVALUES_H + +#include + +namespace Misc +{ + struct OsgUserValues + { + static const std::string sFileHash; + }; +} + +#endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 6863209988..b344553ea9 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -1,6 +1,8 @@ #include "niffile.hpp" #include "effect.hpp" +#include + #include #include #include @@ -156,6 +158,8 @@ std::string NIFFile::printVersion(unsigned int version) void NIFFile::parse(Files::IStreamPtr stream) { + hash = Files::getHash(filename, *stream); + NIFStream nif (this, stream); // Check the header string diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 1ed7cbd5d8..eb851a74ff 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -34,6 +34,8 @@ struct File virtual std::string getFilename() const = 0; + virtual std::uint64_t getHash() const = 0; + virtual unsigned int getVersion() const = 0; virtual unsigned int getUserVersion() const = 0; @@ -50,6 +52,7 @@ class NIFFile final : public File /// File name, used for error messages and opening the file std::string filename; + std::uint64_t hash = 0; /// Record list std::vector records; @@ -141,6 +144,8 @@ public: /// Get the name of the file std::string getFilename() const override { return filename; } + std::uint64_t getHash() const override { return hash; } + /// Get the version of the NIF format used unsigned int getVersion() const override { return ver; } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 6678d8ff74..4be07525a6 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -166,6 +166,8 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) mStaticMesh.reset(); mAvoidStaticMesh.reset(); + mShape->mFileHash = nif.getHash(); + const size_t numRoots = nif.numRoots(); std::vector roots; for (size_t i = 0; i < numRoots; ++i) @@ -178,6 +180,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) roots.emplace_back(node); } const std::string filename = nif.getFilename(); + mShape->mFileName = filename; if (roots.empty()) { warn("Found no root nodes in NIF file " + filename); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a745d50eda..99590e7a2f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -17,6 +17,7 @@ #include #include #include +#include // particle #include @@ -325,6 +326,10 @@ namespace NifOsg if (!textkeys->mTextKeys.empty()) created->getOrCreateUserDataContainer()->addUserObject(textkeys); + const std::uint64_t nifHash = nif->getHash(); + created->setUserValue(Misc::OsgUserValues::sFileHash, + std::string(reinterpret_cast(&nifHash), sizeof(nifHash))); + return created; } diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 52d639d272..74515691c6 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -74,6 +74,8 @@ BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape.get())) , mCollisionBox(copy.mCollisionBox) , mAnimatedShapes(copy.mAnimatedShapes) + , mFileName(copy.mFileName) + , mFileHash(copy.mFileHash) { } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 7188165045..6dfa37aeda 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -12,6 +12,11 @@ class btCollisionShape; +namespace NifBullet +{ + class BulletNifLoader; +} + namespace Resource { struct DeleteCollisionShape @@ -47,6 +52,9 @@ namespace Resource // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; + std::string mFileName; + std::uint64_t mFileHash = 0; + void setLocalScaling(const btVector3& scale); bool isAnimated() const { return !mAnimatedShapes.empty(); } @@ -60,6 +68,8 @@ namespace Resource public: BulletShapeInstance(osg::ref_ptr source); + const osg::ref_ptr& getSource() const { return mSource; } + private: osg::ref_ptr mSource; }; diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 39ceb4fe7e..3803fbf669 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -1,5 +1,7 @@ #include "bulletshapemanager.hpp" +#include + #include #include #include @@ -10,6 +12,7 @@ #include #include #include +#include #include @@ -162,6 +165,15 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & if (!shape) return osg::ref_ptr(); } + + if (shape != nullptr) + { + shape->mFileName = normalized; + std::string fileHash; + constNode->getUserValue(Misc::OsgUserValues::sFileHash, fileHash); + if (!fileHash.empty()) + std::memcpy(&shape->mFileHash, fileHash.data(), std::min(fileHash.size(), sizeof(shape->mFileHash))); + } } mCache->addEntryToObjectCache(normalized, shape); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 8a88f7def9..dfcf384e1b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -34,6 +35,8 @@ #include #include +#include + #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" @@ -502,7 +505,10 @@ namespace Resource options->setReadFileCallback(new ImageReadCallback(imageManager)); if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); - osgDB::ReaderWriter::ReadResult result = reader->readNode(*vfs->get(normalizedFilename), options); + Files::IStreamPtr stream = vfs->get(normalizedFilename); + const std::uint64_t fileHash = Files::getHash(normalizedFilename, *stream); + + osgDB::ReaderWriter::ReadResult result = reader->readNode(*stream, options); if (!result.success()) { std::stringstream errormsg; @@ -528,8 +534,12 @@ namespace Resource result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); } + auto node = result.getNode(); + + node->setUserValue(Misc::OsgUserValues::sFileHash, + std::string(reinterpret_cast(&fileHash), sizeof(fileHash))); - return result.getNode(); + return node; } } From d28542748505608929c8cf0dd79c765e70005ef3 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 6 Nov 2021 01:25:20 +0100 Subject: [PATCH 1691/2859] Update Lua package openmw.input --- apps/openmw/mwbase/inputmanager.hpp | 6 +- apps/openmw/mwinput/bindingsmanager.cpp | 9 +- apps/openmw/mwinput/bindingsmanager.hpp | 3 +- apps/openmw/mwinput/controllermanager.cpp | 20 +++ apps/openmw/mwinput/controllermanager.hpp | 7 +- apps/openmw/mwinput/controlswitch.cpp | 14 ++- apps/openmw/mwinput/controlswitch.hpp | 7 +- apps/openmw/mwinput/inputmanagerimp.cpp | 12 +- apps/openmw/mwinput/inputmanagerimp.hpp | 6 +- apps/openmw/mwinput/mousemanager.cpp | 3 +- apps/openmw/mwinput/mousemanager.hpp | 2 - apps/openmw/mwlua/inputbindings.cpp | 144 +++++++++++++++++++++- apps/openmw/mwlua/luabindings.cpp | 2 +- files/lua_api/openmw/input.lua | 141 ++++++++++++++++++++- 14 files changed, 337 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 5ac7c218b5..d475d93cd1 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -49,8 +49,8 @@ namespace MWBase virtual void setGamepadGuiCursorEnabled(bool enabled) = 0; virtual void setAttemptJump(bool jumping) = 0; - virtual void toggleControlSwitch (const std::string& sw, bool value) = 0; - virtual bool getControlSwitch (const std::string& sw) = 0; + virtual void toggleControlSwitch(std::string_view sw, bool value) = 0; + virtual bool getControlSwitch(std::string_view sw) = 0; virtual std::string getActionDescription (int action) const = 0; virtual std::string getActionKeyBindingName (int action) const = 0; @@ -58,8 +58,8 @@ namespace MWBase virtual bool actionIsActive(int action) const = 0; virtual float getActionValue(int action) const = 0; // returns value in range [0, 1] + virtual bool isControllerButtonPressed(SDL_GameControllerButton button) const = 0; virtual float getControllerAxisValue(SDL_GameControllerAxis axis) const = 0; // returns value in range [-1, 1] - virtual uint32_t getMouseButtonsState() const = 0; virtual int getMouseMoveX() const = 0; virtual int getMouseMoveY() const = 0; diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index ca7911ecc2..f3e1ba2c8d 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -653,14 +653,13 @@ namespace MWInput return mInputBinder->getKeyBinding(mInputBinder->getControl(actionId), ICS::Control::INCREASE); } - float BindingsManager::getControllerAxisValue(SDL_GameControllerAxis axis) const + SDL_GameController* BindingsManager::getControllerOrNull() const { const auto& controllers = mInputBinder->getJoystickInstanceMap(); if (controllers.empty()) - return 0; - SDL_GameController* cntrl = controllers.begin()->second; - constexpr int AXIS_MAX_ABSOLUTE_VALUE = 32768; - return SDL_GameControllerGetAxis(cntrl, axis) / static_cast(AXIS_MAX_ABSOLUTE_VALUE); + return nullptr; + else + return controllers.begin()->second; } void BindingsManager::actionValueChanged(int action, float currentValue, float previousValue) diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index 5c653f0b3e..668cccd4ca 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -43,7 +43,8 @@ namespace MWInput bool actionIsActive(int id) const; float getActionValue(int id) const; // returns value in range [0, 1] - float getControllerAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1] + + SDL_GameController* getControllerOrNull() const; void mousePressed(const SDL_MouseButtonEvent &evt, int deviceID); void mouseReleased(const SDL_MouseButtonEvent &arg, int deviceID); diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index bdb46e31a8..07aa89f554 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -403,4 +403,24 @@ namespace MWInput return true; } + + float ControllerManager::getAxisValue(SDL_GameControllerAxis axis) const + { + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + constexpr int AXIS_MAX_ABSOLUTE_VALUE = 32768; + if (cntrl) + return SDL_GameControllerGetAxis(cntrl, axis) / static_cast(AXIS_MAX_ABSOLUTE_VALUE); + else + return 0; + } + + bool ControllerManager::isButtonPressed(SDL_GameControllerButton button) const + { + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (cntrl) + return SDL_GameControllerGetButton(cntrl, button) > 0; + else + return false; + } + } diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index d8c62d57c4..1e8ef90d0e 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -34,12 +34,15 @@ namespace MWInput void processChangedSettings(const Settings::CategorySettingVector& changed); void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; } - bool joystickLastUsed() { return mJoystickLastUsed; } + bool joystickLastUsed() const { return mJoystickLastUsed; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } void setGamepadGuiCursorEnabled(bool enabled) { mGamepadGuiCursorEnabled = enabled; } - bool gamepadGuiCursorEnabled() { return mGamepadGuiCursorEnabled; } + bool gamepadGuiCursorEnabled() const { return mGamepadGuiCursorEnabled; } + + float getAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1] + bool isButtonPressed(SDL_GameControllerButton button) const; private: // Return true if GUI consumes input. diff --git a/apps/openmw/mwinput/controlswitch.cpp b/apps/openmw/mwinput/controlswitch.cpp index f31744fca6..63d142434e 100644 --- a/apps/openmw/mwinput/controlswitch.cpp +++ b/apps/openmw/mwinput/controlswitch.cpp @@ -29,12 +29,15 @@ namespace MWInput mSwitches["vanitymode"] = true; } - bool ControlSwitch::get(const std::string& key) + bool ControlSwitch::get(std::string_view key) { - return mSwitches[key]; + auto it = mSwitches.find(key); + if (it == mSwitches.end()) + throw std::runtime_error("Incorrect ControlSwitch: " + std::string(key)); + return it->second; } - void ControlSwitch::set(const std::string& key, bool value) + void ControlSwitch::set(std::string_view key, bool value) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); @@ -59,7 +62,10 @@ namespace MWInput { MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f()); } - mSwitches[key] = value; + auto it = mSwitches.find(key); + if (it == mSwitches.end()) + throw std::runtime_error("Incorrect ControlSwitch: " + std::string(key)); + it->second = value; } void ControlSwitch::write(ESM::ESMWriter& writer, Loading::Listener& /*progress*/) diff --git a/apps/openmw/mwinput/controlswitch.hpp b/apps/openmw/mwinput/controlswitch.hpp index 38d01066bd..b4353c31f5 100644 --- a/apps/openmw/mwinput/controlswitch.hpp +++ b/apps/openmw/mwinput/controlswitch.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace ESM { @@ -23,8 +24,8 @@ namespace MWInput public: ControlSwitch(); - bool get(const std::string& key); - void set(const std::string& key, bool value); + bool get(std::string_view key); + void set(std::string_view key, bool value); void clear(); void write(ESM::ESMWriter& writer, Loading::Listener& progress); @@ -32,7 +33,7 @@ namespace MWInput int countSavedGameRecords() const; private: - std::map mSwitches; + std::map> mSwitches; }; } #endif diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 31f515afb0..cf1b0e936d 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -135,12 +135,12 @@ namespace MWInput mSensorManager->processChangedSettings(changed); } - bool InputManager::getControlSwitch(const std::string& sw) + bool InputManager::getControlSwitch(std::string_view sw) { return mControlSwitch->get(sw); } - void InputManager::toggleControlSwitch(const std::string& sw, bool value) + void InputManager::toggleControlSwitch(std::string_view sw, bool value) { mControlSwitch->set(sw, value); } @@ -180,14 +180,14 @@ namespace MWInput return mBindingsManager->getActionValue(action); } - float InputManager::getControllerAxisValue(SDL_GameControllerAxis axis) const + bool InputManager::isControllerButtonPressed(SDL_GameControllerButton button) const { - return mBindingsManager->getControllerAxisValue(axis); + return mControllerManager->isButtonPressed(button); } - uint32_t InputManager::getMouseButtonsState() const + float InputManager::getControllerAxisValue(SDL_GameControllerAxis axis) const { - return mMouseManager->getButtonsState(); + return mControllerManager->getAxisValue(axis); } int InputManager::getMouseMoveX() const diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index adb8319498..41478d5dcb 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -70,8 +70,8 @@ namespace MWInput void setGamepadGuiCursorEnabled(bool enabled) override; void setAttemptJump(bool jumping) override; - void toggleControlSwitch (const std::string& sw, bool value) override; - bool getControlSwitch (const std::string& sw) override; + void toggleControlSwitch(std::string_view sw, bool value) override; + bool getControlSwitch(std::string_view sw) override; std::string getActionDescription (int action) const override; std::string getActionKeyBindingName (int action) const override; @@ -79,8 +79,8 @@ namespace MWInput bool actionIsActive(int action) const override; float getActionValue(int action) const override; + bool isControllerButtonPressed(SDL_GameControllerButton button) const override; float getControllerAxisValue(SDL_GameControllerAxis axis) const override; - uint32_t getMouseButtonsState() const override; int getMouseMoveX() const override; int getMouseMoveY() const override; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index f2bd4505d1..6fbe8cfc98 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -34,7 +34,6 @@ namespace MWInput , mMouseWheel(0) , mMouseLookEnabled(false) , mGuiCursorEnabled(true) - , mButtonsState(0) , mMouseMoveX(0) , mMouseMoveY(0) { @@ -199,7 +198,7 @@ namespace MWInput void MouseManager::update(float dt) { - mButtonsState = SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY); + SDL_GetRelativeMouseState(&mMouseMoveX, &mMouseMoveY); if (!mMouseLookEnabled) return; diff --git a/apps/openmw/mwinput/mousemanager.hpp b/apps/openmw/mwinput/mousemanager.hpp index d5504c5f5a..16ea56d62b 100644 --- a/apps/openmw/mwinput/mousemanager.hpp +++ b/apps/openmw/mwinput/mousemanager.hpp @@ -38,7 +38,6 @@ namespace MWInput void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; } void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } - uint32_t getButtonsState() const { return mButtonsState; } int getMouseMoveX() const { return mMouseMoveX; } int getMouseMoveY() const { return mMouseMoveY; } @@ -58,7 +57,6 @@ namespace MWInput bool mMouseLookEnabled; bool mGuiCursorEnabled; - uint32_t mButtonsState; int mMouseMoveX; int mMouseMoveY; }; diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index aaa00f3da9..b528d1b563 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" @@ -18,9 +19,14 @@ namespace MWLua sol::table initInputPackage(const Context& context) { sol::usertype keyEvent = context.mLua->sol().new_usertype("KeyEvent"); - keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { return std::string(1, static_cast(e.sym)); }); - keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.sym; }); - keyEvent["modifiers"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.mod; }); + keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) + { + if (e.sym > 0 && e.sym <= 255) + return std::string(1, static_cast(e.sym)); + else + return std::string(); + }); + keyEvent["code"] = sol::readonly_property([](const SDL_Keysym& e) -> int { return e.scancode; }); keyEvent["withShift"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_SHIFT; }); keyEvent["withCtrl"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_CTRL; }); keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); @@ -31,9 +37,26 @@ namespace MWLua api["isIdle"] = [input]() { return input->isIdle(); }; api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); }; + api["isKeyPressed"] = [input](SDL_Scancode code) -> bool + { + int maxCode; + const auto* state = SDL_GetKeyboardState(&maxCode); + if (code >= 0 && code < maxCode) + return state[code] != 0; + else + return false; + }; + api["isShiftPressed"] = [input]() -> bool { return SDL_GetModState() & KMOD_SHIFT; }; + api["isCtrlPressed"] = [input]() -> bool { return SDL_GetModState() & KMOD_CTRL; }; + api["isAltPressed"] = [input]() -> bool { return SDL_GetModState() & KMOD_ALT; }; + api["isSuperPressed"] = [input]() -> bool { return SDL_GetModState() & KMOD_GUI; }; + api["isControllerButtonPressed"] = [input](int button) + { + return input->isControllerButtonPressed(static_cast(button)); + }; api["isMouseButtonPressed"] = [input](int button) -> bool { - return input->getMouseButtonsState() & (1 << (button - 1)); + return SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(button); }; api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; @@ -45,8 +68,8 @@ namespace MWLua return input->getActionValue(axis - SDL_CONTROLLER_AXIS_MAX) * 2 - 1; }; - api["getControlSwitch"] = [input](const std::string& key) { return input->getControlSwitch(key); }; - api["setControlSwitch"] = [input](const std::string& key, bool v) { input->toggleControlSwitch(key, v); }; + 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["ACTION"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "GameMenu", MWInput::A_GameMenu, @@ -144,6 +167,115 @@ namespace MWLua "MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight )); + api["KEY"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( + "_0", SDL_SCANCODE_0, + "_1", SDL_SCANCODE_1, + "_2", SDL_SCANCODE_2, + "_3", SDL_SCANCODE_3, + "_4", SDL_SCANCODE_4, + "_5", SDL_SCANCODE_5, + "_6", SDL_SCANCODE_6, + "_7", SDL_SCANCODE_7, + "_8", SDL_SCANCODE_8, + "_9", SDL_SCANCODE_9, + + "NP_0", SDL_SCANCODE_KP_0, + "NP_1", SDL_SCANCODE_KP_1, + "NP_2", SDL_SCANCODE_KP_2, + "NP_3", SDL_SCANCODE_KP_3, + "NP_4", SDL_SCANCODE_KP_4, + "NP_5", SDL_SCANCODE_KP_5, + "NP_6", SDL_SCANCODE_KP_6, + "NP_7", SDL_SCANCODE_KP_7, + "NP_8", SDL_SCANCODE_KP_8, + "NP_9", SDL_SCANCODE_KP_9, + "NP_Divide", SDL_SCANCODE_KP_DIVIDE, + "NP_Enter", SDL_SCANCODE_KP_ENTER, + "NP_Minus", SDL_SCANCODE_KP_MINUS, + "NP_Multiply", SDL_SCANCODE_KP_MULTIPLY, + "NP_Delete", SDL_SCANCODE_KP_PERIOD, + "NP_Plus", SDL_SCANCODE_KP_PLUS, + + "F1", SDL_SCANCODE_F1, + "F2", SDL_SCANCODE_F2, + "F3", SDL_SCANCODE_F3, + "F4", SDL_SCANCODE_F4, + "F5", SDL_SCANCODE_F5, + "F6", SDL_SCANCODE_F6, + "F7", SDL_SCANCODE_F7, + "F8", SDL_SCANCODE_F8, + "F9", SDL_SCANCODE_F9, + "F10", SDL_SCANCODE_F10, + "F11", SDL_SCANCODE_F11, + "F12", SDL_SCANCODE_F12, + + "A", SDL_SCANCODE_A, + "B", SDL_SCANCODE_B, + "C", SDL_SCANCODE_C, + "D", SDL_SCANCODE_D, + "E", SDL_SCANCODE_E, + "F", SDL_SCANCODE_F, + "G", SDL_SCANCODE_G, + "H", SDL_SCANCODE_H, + "I", SDL_SCANCODE_I, + "J", SDL_SCANCODE_J, + "K", SDL_SCANCODE_K, + "L", SDL_SCANCODE_L, + "M", SDL_SCANCODE_M, + "N", SDL_SCANCODE_N, + "O", SDL_SCANCODE_O, + "P", SDL_SCANCODE_P, + "Q", SDL_SCANCODE_Q, + "R", SDL_SCANCODE_R, + "S", SDL_SCANCODE_S, + "T", SDL_SCANCODE_T, + "U", SDL_SCANCODE_U, + "V", SDL_SCANCODE_V, + "W", SDL_SCANCODE_W, + "X", SDL_SCANCODE_X, + "Y", SDL_SCANCODE_Y, + "Z", SDL_SCANCODE_Z, + + "LeftArrow", SDL_SCANCODE_LEFT, + "RightArrow", SDL_SCANCODE_RIGHT, + "UpArrow", SDL_SCANCODE_UP, + "DownArrow", SDL_SCANCODE_DOWN, + + "LeftAlt", SDL_SCANCODE_LALT, + "LeftCtrl", SDL_SCANCODE_LCTRL, + "LeftBracket", SDL_SCANCODE_LEFTBRACKET, + "LeftSuper", SDL_SCANCODE_LGUI, + "LeftShift", SDL_SCANCODE_LSHIFT, + "RightAlt", SDL_SCANCODE_RALT, + "RightCtrl", SDL_SCANCODE_RCTRL, + "RightSuper", SDL_SCANCODE_RGUI, + "RightBracket", SDL_SCANCODE_RIGHTBRACKET, + "RightShift", SDL_SCANCODE_RSHIFT, + + "BackSlash", SDL_SCANCODE_BACKSLASH, + "Backspace", SDL_SCANCODE_BACKSPACE, + "CapsLock", SDL_SCANCODE_CAPSLOCK, + "Comma", SDL_SCANCODE_COMMA, + "Delete", SDL_SCANCODE_DELETE, + "End", SDL_SCANCODE_END, + "Enter", SDL_SCANCODE_RETURN, + "Equals", SDL_SCANCODE_EQUALS, + "Escape", SDL_SCANCODE_ESCAPE, + "Home", SDL_SCANCODE_HOME, + "Insert", SDL_SCANCODE_INSERT, + "Minus", SDL_SCANCODE_MINUS, + "NumLock", SDL_SCANCODE_NUMLOCKCLEAR, + "PageDown", SDL_SCANCODE_PAGEDOWN, + "PageUp", SDL_SCANCODE_PAGEUP, + "Pause", SDL_SCANCODE_PAUSE, + "PrintScreen", SDL_SCANCODE_PRINTSCREEN, + "ScrollLock", SDL_SCANCODE_SCROLLLOCK, + "Semicolon", SDL_SCANCODE_SEMICOLON, + "Slash", SDL_SCANCODE_SLASH, + "Space", SDL_SCANCODE_SPACE, + "Tab", SDL_SCANCODE_TAB + )); + return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 1c05debc73..f9d3911aa8 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 8; + api["API_REVISION"] = 9; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 0975ce6e6a..d4ff7f3475 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -17,6 +17,38 @@ -- @param #number actionId One of @{openmw.input#ACTION} -- @return #boolean +------------------------------------------------------------------------------- +-- Is a keyboard button currently pressed. +-- @function [parent=#input] isKeyPressed +-- @param #number keyCode Key code (the same code that is used in @{openmw.input#KeyEvent}) +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is a controller button currently pressed. +-- @function [parent=#input] isControllerButtonPressed +-- @param #number buttonId Button index (see @{openmw.input#CONTROLLER_BUTTON}) +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is `Shift` key pressed. +-- @function [parent=#input] isShiftPressed +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is `Ctrl` key pressed. +-- @function [parent=#input] isCtrlPressed +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is `Alt` key pressed. +-- @function [parent=#input] isAltPressed +-- @return #boolean + +------------------------------------------------------------------------------- +-- Is `Super`/`Win` key pressed. +-- @function [parent=#input] isSuperPressed +-- @return #boolean + ------------------------------------------------------------------------------- -- Is a mouse button currently pressed. -- @function [parent=#input] isMouseButtonPressed @@ -156,10 +188,117 @@ -- Values that can be used with getAxisValue. -- @field [parent=#input] #CONTROLLER_AXIS CONTROLLER_AXIS +------------------------------------------------------------------------------- +-- @type KEY +-- @field #number _0 +-- @field #number _1 +-- @field #number _2 +-- @field #number _3 +-- @field #number _4 +-- @field #number _5 +-- @field #number _6 +-- @field #number _7 +-- @field #number _8 +-- @field #number _9 +-- @field #number NP_0 +-- @field #number NP_1 +-- @field #number NP_2 +-- @field #number NP_3 +-- @field #number NP_4 +-- @field #number NP_5 +-- @field #number NP_6 +-- @field #number NP_7 +-- @field #number NP_8 +-- @field #number NP_9 +-- @field #number NP_Divide +-- @field #number NP_Enter +-- @field #number NP_Minus +-- @field #number NP_Multiply +-- @field #number NP_Delete +-- @field #number NP_Plus +-- @field #number F1 +-- @field #number F2 +-- @field #number F3 +-- @field #number F4 +-- @field #number F5 +-- @field #number F6 +-- @field #number F7 +-- @field #number F8 +-- @field #number F9 +-- @field #number F10 +-- @field #number F11 +-- @field #number F12 +-- @field #number A +-- @field #number B +-- @field #number C +-- @field #number D +-- @field #number E +-- @field #number F +-- @field #number G +-- @field #number H +-- @field #number I +-- @field #number J +-- @field #number K +-- @field #number L +-- @field #number M +-- @field #number N +-- @field #number O +-- @field #number P +-- @field #number Q +-- @field #number R +-- @field #number S +-- @field #number T +-- @field #number U +-- @field #number V +-- @field #number W +-- @field #number X +-- @field #number Y +-- @field #number Z +-- @field #number LeftArrow +-- @field #number RightArrow +-- @field #number UpArrow +-- @field #number DownArrow +-- @field #number LeftAlt +-- @field #number LeftCtrl +-- @field #number LeftBracket +-- @field #number LeftSuper +-- @field #number LeftShift +-- @field #number RightAlt +-- @field #number RightCtrl +-- @field #number RightBracket +-- @field #number RightSuper +-- @field #number RightShift +-- @field #number BackSlash +-- @field #number Backspace +-- @field #number CapsLock +-- @field #number Comma +-- @field #number Delete +-- @field #number End +-- @field #number Enter +-- @field #number Equals +-- @field #number Escape +-- @field #number Home +-- @field #number Insert +-- @field #number Minus +-- @field #number NumLock +-- @field #number PageDown +-- @field #number PageUp +-- @field #number Pause +-- @field #number PrintScreen +-- @field #number ScrollLock +-- @field #number Semicolon +-- @field #number Slash +-- @field #number Space +-- @field #number Tab + +------------------------------------------------------------------------------- +-- Key codes. +-- @field [parent=#input] #KEY KEY + ------------------------------------------------------------------------------- -- The argument of `onKeyPress`/`onKeyRelease` engine handlers. -- @type KeyboardEvent --- @field [parent=#KeyboardEvent] #string symbol The pressed symbol (1-symbol string). +-- @field [parent=#KeyboardEvent] #string symbol The pressed symbol (1-symbol string if can be represented or an empty string otherwise). -- @field [parent=#KeyboardEvent] #string code Key code. -- @field [parent=#KeyboardEvent] #boolean withShift Is `Shift` key pressed. -- @field [parent=#KeyboardEvent] #boolean withCtrl Is `Control` key pressed. From ed853932885177662cb8090cfe1ba1680ea1906a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 11 Nov 2021 22:35:41 +0100 Subject: [PATCH 1692/2859] Replace sol::table::create_table_with with a more compiler-friendly implementation. It fixes MSVC error "compiler is out of heap space" and also slightly reduces binary size. --- apps/openmw/mwlua/inputbindings.cpp | 374 +++++++++++++-------------- apps/openmw/mwlua/luabindings.cpp | 42 +-- apps/openmw/mwlua/nearbybindings.cpp | 17 +- components/lua/luastate.hpp | 9 + 4 files changed, 226 insertions(+), 216 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index b528d1b563..303c25f864 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -71,210 +71,210 @@ 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["ACTION"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( - "GameMenu", MWInput::A_GameMenu, - "Screenshot", MWInput::A_Screenshot, - "Inventory", MWInput::A_Inventory, - "Console", MWInput::A_Console, + api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"GameMenu", MWInput::A_GameMenu}, + {"Screenshot", MWInput::A_Screenshot}, + {"Inventory", MWInput::A_Inventory}, + {"Console", MWInput::A_Console}, - "MoveLeft", MWInput::A_MoveLeft, - "MoveRight", MWInput::A_MoveRight, - "MoveForward", MWInput::A_MoveForward, - "MoveBackward", MWInput::A_MoveBackward, + {"MoveLeft", MWInput::A_MoveLeft}, + {"MoveRight", MWInput::A_MoveRight}, + {"MoveForward", MWInput::A_MoveForward}, + {"MoveBackward", MWInput::A_MoveBackward}, - "Activate", MWInput::A_Activate, - "Use", MWInput::A_Use, - "Jump", MWInput::A_Jump, - "AutoMove", MWInput::A_AutoMove, - "Rest", MWInput::A_Rest, - "Journal", MWInput::A_Journal, - "Weapon", MWInput::A_Weapon, - "Spell", MWInput::A_Spell, - "Run", MWInput::A_Run, - "CycleSpellLeft", MWInput::A_CycleSpellLeft, - "CycleSpellRight", MWInput::A_CycleSpellRight, - "CycleWeaponLeft", MWInput::A_CycleWeaponLeft, - "CycleWeaponRight", MWInput::A_CycleWeaponRight, - "ToggleSneak", MWInput::A_ToggleSneak, - "AlwaysRun", MWInput::A_AlwaysRun, - "Sneak", MWInput::A_Sneak, + {"Activate", MWInput::A_Activate}, + {"Use", MWInput::A_Use}, + {"Jump", MWInput::A_Jump}, + {"AutoMove", MWInput::A_AutoMove}, + {"Rest", MWInput::A_Rest}, + {"Journal", MWInput::A_Journal}, + {"Weapon", MWInput::A_Weapon}, + {"Spell", MWInput::A_Spell}, + {"Run", MWInput::A_Run}, + {"CycleSpellLeft", MWInput::A_CycleSpellLeft}, + {"CycleSpellRight", MWInput::A_CycleSpellRight}, + {"CycleWeaponLeft", MWInput::A_CycleWeaponLeft}, + {"CycleWeaponRight", MWInput::A_CycleWeaponRight}, + {"ToggleSneak", MWInput::A_ToggleSneak}, + {"AlwaysRun", MWInput::A_AlwaysRun}, + {"Sneak", MWInput::A_Sneak}, - "QuickSave", MWInput::A_QuickSave, - "QuickLoad", MWInput::A_QuickLoad, - "QuickMenu", MWInput::A_QuickMenu, - "ToggleWeapon", MWInput::A_ToggleWeapon, - "ToggleSpell", MWInput::A_ToggleSpell, - "TogglePOV", MWInput::A_TogglePOV, + {"QuickSave", MWInput::A_QuickSave}, + {"QuickLoad", MWInput::A_QuickLoad}, + {"QuickMenu", MWInput::A_QuickMenu}, + {"ToggleWeapon", MWInput::A_ToggleWeapon}, + {"ToggleSpell", MWInput::A_ToggleSpell}, + {"TogglePOV", MWInput::A_TogglePOV}, - "QuickKey1", MWInput::A_QuickKey1, - "QuickKey2", MWInput::A_QuickKey2, - "QuickKey3", MWInput::A_QuickKey3, - "QuickKey4", MWInput::A_QuickKey4, - "QuickKey5", MWInput::A_QuickKey5, - "QuickKey6", MWInput::A_QuickKey6, - "QuickKey7", MWInput::A_QuickKey7, - "QuickKey8", MWInput::A_QuickKey8, - "QuickKey9", MWInput::A_QuickKey9, - "QuickKey10", MWInput::A_QuickKey10, - "QuickKeysMenu", MWInput::A_QuickKeysMenu, + {"QuickKey1", MWInput::A_QuickKey1}, + {"QuickKey2", MWInput::A_QuickKey2}, + {"QuickKey3", MWInput::A_QuickKey3}, + {"QuickKey4", MWInput::A_QuickKey4}, + {"QuickKey5", MWInput::A_QuickKey5}, + {"QuickKey6", MWInput::A_QuickKey6}, + {"QuickKey7", MWInput::A_QuickKey7}, + {"QuickKey8", MWInput::A_QuickKey8}, + {"QuickKey9", MWInput::A_QuickKey9}, + {"QuickKey10", MWInput::A_QuickKey10}, + {"QuickKeysMenu", MWInput::A_QuickKeysMenu}, - "ToggleHUD", MWInput::A_ToggleHUD, - "ToggleDebug", MWInput::A_ToggleDebug, + {"ToggleHUD", MWInput::A_ToggleHUD}, + {"ToggleDebug", MWInput::A_ToggleDebug}, - "ZoomIn", MWInput::A_ZoomIn, - "ZoomOut", MWInput::A_ZoomOut - )); + {"ZoomIn", MWInput::A_ZoomIn}, + {"ZoomOut", MWInput::A_ZoomOut} + })); - api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( - "Controls", "playercontrols", - "Fighting", "playerfighting", - "Jumping", "playerjumping", - "Looking", "playerlooking", - "Magic", "playermagic", - "ViewMode", "playerviewswitch", - "VanityMode", "vanitymode" - )); + api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"Controls", "playercontrols"}, + {"Fighting", "playerfighting"}, + {"Jumping", "playerjumping"}, + {"Looking", "playerlooking"}, + {"Magic", "playermagic"}, + {"ViewMode", "playerviewswitch"}, + {"VanityMode", "vanitymode"} + })); - api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( - "A", SDL_CONTROLLER_BUTTON_A, - "B", SDL_CONTROLLER_BUTTON_B, - "X", SDL_CONTROLLER_BUTTON_X, - "Y", SDL_CONTROLLER_BUTTON_Y, - "Back", SDL_CONTROLLER_BUTTON_BACK, - "Guide", SDL_CONTROLLER_BUTTON_GUIDE, - "Start", SDL_CONTROLLER_BUTTON_START, - "LeftStick", SDL_CONTROLLER_BUTTON_LEFTSTICK, - "RightStick", SDL_CONTROLLER_BUTTON_RIGHTSTICK, - "LeftShoulder", SDL_CONTROLLER_BUTTON_LEFTSHOULDER, - "RightShoulder", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, - "DPadUp", SDL_CONTROLLER_BUTTON_DPAD_UP, - "DPadDown", SDL_CONTROLLER_BUTTON_DPAD_DOWN, - "DPadLeft", SDL_CONTROLLER_BUTTON_DPAD_LEFT, - "DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT - )); + api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"A", SDL_CONTROLLER_BUTTON_A}, + {"B", SDL_CONTROLLER_BUTTON_B}, + {"X", SDL_CONTROLLER_BUTTON_X}, + {"Y", SDL_CONTROLLER_BUTTON_Y}, + {"Back", SDL_CONTROLLER_BUTTON_BACK}, + {"Guide", SDL_CONTROLLER_BUTTON_GUIDE}, + {"Start", SDL_CONTROLLER_BUTTON_START}, + {"LeftStick", SDL_CONTROLLER_BUTTON_LEFTSTICK}, + {"RightStick", SDL_CONTROLLER_BUTTON_RIGHTSTICK}, + {"LeftShoulder", SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {"RightShoulder", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {"DPadUp", SDL_CONTROLLER_BUTTON_DPAD_UP}, + {"DPadDown", SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {"DPadLeft", SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {"DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT} + })); - api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( - "LeftX", SDL_CONTROLLER_AXIS_LEFTX, - "LeftY", SDL_CONTROLLER_AXIS_LEFTY, - "RightX", SDL_CONTROLLER_AXIS_RIGHTX, - "RightY", SDL_CONTROLLER_AXIS_RIGHTY, - "TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT, - "TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"LeftX", SDL_CONTROLLER_AXIS_LEFTX}, + {"LeftY", SDL_CONTROLLER_AXIS_LEFTY}, + {"RightX", SDL_CONTROLLER_AXIS_RIGHTX}, + {"RightY", SDL_CONTROLLER_AXIS_RIGHTY}, + {"TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT}, + {"TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, - "LookUpDown", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookUpDown, - "LookLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookLeftRight, - "MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveForwardBackward, - "MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight - )); + {"LookUpDown", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookUpDown}, + {"LookLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookLeftRight}, + {"MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveForwardBackward}, + {"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight} + })); - api["KEY"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( - "_0", SDL_SCANCODE_0, - "_1", SDL_SCANCODE_1, - "_2", SDL_SCANCODE_2, - "_3", SDL_SCANCODE_3, - "_4", SDL_SCANCODE_4, - "_5", SDL_SCANCODE_5, - "_6", SDL_SCANCODE_6, - "_7", SDL_SCANCODE_7, - "_8", SDL_SCANCODE_8, - "_9", SDL_SCANCODE_9, + api["KEY"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"_0", SDL_SCANCODE_0}, + {"_1", SDL_SCANCODE_1}, + {"_2", SDL_SCANCODE_2}, + {"_3", SDL_SCANCODE_3}, + {"_4", SDL_SCANCODE_4}, + {"_5", SDL_SCANCODE_5}, + {"_6", SDL_SCANCODE_6}, + {"_7", SDL_SCANCODE_7}, + {"_8", SDL_SCANCODE_8}, + {"_9", SDL_SCANCODE_9}, - "NP_0", SDL_SCANCODE_KP_0, - "NP_1", SDL_SCANCODE_KP_1, - "NP_2", SDL_SCANCODE_KP_2, - "NP_3", SDL_SCANCODE_KP_3, - "NP_4", SDL_SCANCODE_KP_4, - "NP_5", SDL_SCANCODE_KP_5, - "NP_6", SDL_SCANCODE_KP_6, - "NP_7", SDL_SCANCODE_KP_7, - "NP_8", SDL_SCANCODE_KP_8, - "NP_9", SDL_SCANCODE_KP_9, - "NP_Divide", SDL_SCANCODE_KP_DIVIDE, - "NP_Enter", SDL_SCANCODE_KP_ENTER, - "NP_Minus", SDL_SCANCODE_KP_MINUS, - "NP_Multiply", SDL_SCANCODE_KP_MULTIPLY, - "NP_Delete", SDL_SCANCODE_KP_PERIOD, - "NP_Plus", SDL_SCANCODE_KP_PLUS, + {"NP_0", SDL_SCANCODE_KP_0}, + {"NP_1", SDL_SCANCODE_KP_1}, + {"NP_2", SDL_SCANCODE_KP_2}, + {"NP_3", SDL_SCANCODE_KP_3}, + {"NP_4", SDL_SCANCODE_KP_4}, + {"NP_5", SDL_SCANCODE_KP_5}, + {"NP_6", SDL_SCANCODE_KP_6}, + {"NP_7", SDL_SCANCODE_KP_7}, + {"NP_8", SDL_SCANCODE_KP_8}, + {"NP_9", SDL_SCANCODE_KP_9}, + {"NP_Divide", SDL_SCANCODE_KP_DIVIDE}, + {"NP_Enter", SDL_SCANCODE_KP_ENTER}, + {"NP_Minus", SDL_SCANCODE_KP_MINUS}, + {"NP_Multiply", SDL_SCANCODE_KP_MULTIPLY}, + {"NP_Delete", SDL_SCANCODE_KP_PERIOD}, + {"NP_Plus", SDL_SCANCODE_KP_PLUS}, - "F1", SDL_SCANCODE_F1, - "F2", SDL_SCANCODE_F2, - "F3", SDL_SCANCODE_F3, - "F4", SDL_SCANCODE_F4, - "F5", SDL_SCANCODE_F5, - "F6", SDL_SCANCODE_F6, - "F7", SDL_SCANCODE_F7, - "F8", SDL_SCANCODE_F8, - "F9", SDL_SCANCODE_F9, - "F10", SDL_SCANCODE_F10, - "F11", SDL_SCANCODE_F11, - "F12", SDL_SCANCODE_F12, + {"F1", SDL_SCANCODE_F1}, + {"F2", SDL_SCANCODE_F2}, + {"F3", SDL_SCANCODE_F3}, + {"F4", SDL_SCANCODE_F4}, + {"F5", SDL_SCANCODE_F5}, + {"F6", SDL_SCANCODE_F6}, + {"F7", SDL_SCANCODE_F7}, + {"F8", SDL_SCANCODE_F8}, + {"F9", SDL_SCANCODE_F9}, + {"F10", SDL_SCANCODE_F10}, + {"F11", SDL_SCANCODE_F11}, + {"F12", SDL_SCANCODE_F12}, - "A", SDL_SCANCODE_A, - "B", SDL_SCANCODE_B, - "C", SDL_SCANCODE_C, - "D", SDL_SCANCODE_D, - "E", SDL_SCANCODE_E, - "F", SDL_SCANCODE_F, - "G", SDL_SCANCODE_G, - "H", SDL_SCANCODE_H, - "I", SDL_SCANCODE_I, - "J", SDL_SCANCODE_J, - "K", SDL_SCANCODE_K, - "L", SDL_SCANCODE_L, - "M", SDL_SCANCODE_M, - "N", SDL_SCANCODE_N, - "O", SDL_SCANCODE_O, - "P", SDL_SCANCODE_P, - "Q", SDL_SCANCODE_Q, - "R", SDL_SCANCODE_R, - "S", SDL_SCANCODE_S, - "T", SDL_SCANCODE_T, - "U", SDL_SCANCODE_U, - "V", SDL_SCANCODE_V, - "W", SDL_SCANCODE_W, - "X", SDL_SCANCODE_X, - "Y", SDL_SCANCODE_Y, - "Z", SDL_SCANCODE_Z, + {"A", SDL_SCANCODE_A}, + {"B", SDL_SCANCODE_B}, + {"C", SDL_SCANCODE_C}, + {"D", SDL_SCANCODE_D}, + {"E", SDL_SCANCODE_E}, + {"F", SDL_SCANCODE_F}, + {"G", SDL_SCANCODE_G}, + {"H", SDL_SCANCODE_H}, + {"I", SDL_SCANCODE_I}, + {"J", SDL_SCANCODE_J}, + {"K", SDL_SCANCODE_K}, + {"L", SDL_SCANCODE_L}, + {"M", SDL_SCANCODE_M}, + {"N", SDL_SCANCODE_N}, + {"O", SDL_SCANCODE_O}, + {"P", SDL_SCANCODE_P}, + {"Q", SDL_SCANCODE_Q}, + {"R", SDL_SCANCODE_R}, + {"S", SDL_SCANCODE_S}, + {"T", SDL_SCANCODE_T}, + {"U", SDL_SCANCODE_U}, + {"V", SDL_SCANCODE_V}, + {"W", SDL_SCANCODE_W}, + {"X", SDL_SCANCODE_X}, + {"Y", SDL_SCANCODE_Y}, + {"Z", SDL_SCANCODE_Z}, - "LeftArrow", SDL_SCANCODE_LEFT, - "RightArrow", SDL_SCANCODE_RIGHT, - "UpArrow", SDL_SCANCODE_UP, - "DownArrow", SDL_SCANCODE_DOWN, + {"LeftArrow", SDL_SCANCODE_LEFT}, + {"RightArrow", SDL_SCANCODE_RIGHT}, + {"UpArrow", SDL_SCANCODE_UP}, + {"DownArrow", SDL_SCANCODE_DOWN}, - "LeftAlt", SDL_SCANCODE_LALT, - "LeftCtrl", SDL_SCANCODE_LCTRL, - "LeftBracket", SDL_SCANCODE_LEFTBRACKET, - "LeftSuper", SDL_SCANCODE_LGUI, - "LeftShift", SDL_SCANCODE_LSHIFT, - "RightAlt", SDL_SCANCODE_RALT, - "RightCtrl", SDL_SCANCODE_RCTRL, - "RightSuper", SDL_SCANCODE_RGUI, - "RightBracket", SDL_SCANCODE_RIGHTBRACKET, - "RightShift", SDL_SCANCODE_RSHIFT, + {"LeftAlt", SDL_SCANCODE_LALT}, + {"LeftCtrl", SDL_SCANCODE_LCTRL}, + {"LeftBracket", SDL_SCANCODE_LEFTBRACKET}, + {"LeftSuper", SDL_SCANCODE_LGUI}, + {"LeftShift", SDL_SCANCODE_LSHIFT}, + {"RightAlt", SDL_SCANCODE_RALT}, + {"RightCtrl", SDL_SCANCODE_RCTRL}, + {"RightSuper", SDL_SCANCODE_RGUI}, + {"RightBracket", SDL_SCANCODE_RIGHTBRACKET}, + {"RightShift", SDL_SCANCODE_RSHIFT}, - "BackSlash", SDL_SCANCODE_BACKSLASH, - "Backspace", SDL_SCANCODE_BACKSPACE, - "CapsLock", SDL_SCANCODE_CAPSLOCK, - "Comma", SDL_SCANCODE_COMMA, - "Delete", SDL_SCANCODE_DELETE, - "End", SDL_SCANCODE_END, - "Enter", SDL_SCANCODE_RETURN, - "Equals", SDL_SCANCODE_EQUALS, - "Escape", SDL_SCANCODE_ESCAPE, - "Home", SDL_SCANCODE_HOME, - "Insert", SDL_SCANCODE_INSERT, - "Minus", SDL_SCANCODE_MINUS, - "NumLock", SDL_SCANCODE_NUMLOCKCLEAR, - "PageDown", SDL_SCANCODE_PAGEDOWN, - "PageUp", SDL_SCANCODE_PAGEUP, - "Pause", SDL_SCANCODE_PAUSE, - "PrintScreen", SDL_SCANCODE_PRINTSCREEN, - "ScrollLock", SDL_SCANCODE_SCROLLLOCK, - "Semicolon", SDL_SCANCODE_SEMICOLON, - "Slash", SDL_SCANCODE_SLASH, - "Space", SDL_SCANCODE_SPACE, - "Tab", SDL_SCANCODE_TAB - )); + {"BackSlash", SDL_SCANCODE_BACKSLASH}, + {"Backspace", SDL_SCANCODE_BACKSPACE}, + {"CapsLock", SDL_SCANCODE_CAPSLOCK}, + {"Comma", SDL_SCANCODE_COMMA}, + {"Delete", SDL_SCANCODE_DELETE}, + {"End", SDL_SCANCODE_END}, + {"Enter", SDL_SCANCODE_RETURN}, + {"Equals", SDL_SCANCODE_EQUALS}, + {"Escape", SDL_SCANCODE_ESCAPE}, + {"Home", SDL_SCANCODE_HOME}, + {"Insert", SDL_SCANCODE_INSERT}, + {"Minus", SDL_SCANCODE_MINUS}, + {"NumLock", SDL_SCANCODE_NUMLOCKCLEAR}, + {"PageDown", SDL_SCANCODE_PAGEDOWN}, + {"PageUp", SDL_SCANCODE_PAGEUP}, + {"Pause", SDL_SCANCODE_PAUSE}, + {"PrintScreen", SDL_SCANCODE_PRINTSCREEN}, + {"ScrollLock", SDL_SCANCODE_SCROLLLOCK}, + {"Semicolon", SDL_SCANCODE_SEMICOLON}, + {"Slash", SDL_SCANCODE_SLASH}, + {"Space", SDL_SCANCODE_SPACE}, + {"Tab", SDL_SCANCODE_TAB} + })); return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index f9d3911aa8..07e8dcc30c 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -43,27 +43,27 @@ namespace MWLua "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", "Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon" }); - api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(lua->sol().create_table_with( - "Helmet", MWWorld::InventoryStore::Slot_Helmet, - "Cuirass", MWWorld::InventoryStore::Slot_Cuirass, - "Greaves", MWWorld::InventoryStore::Slot_Greaves, - "LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron, - "RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron, - "LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet, - "RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet, - "Boots", MWWorld::InventoryStore::Slot_Boots, - "Shirt", MWWorld::InventoryStore::Slot_Shirt, - "Pants", MWWorld::InventoryStore::Slot_Pants, - "Skirt", MWWorld::InventoryStore::Slot_Skirt, - "Robe", MWWorld::InventoryStore::Slot_Robe, - "LeftRing", MWWorld::InventoryStore::Slot_LeftRing, - "RightRing", MWWorld::InventoryStore::Slot_RightRing, - "Amulet", MWWorld::InventoryStore::Slot_Amulet, - "Belt", MWWorld::InventoryStore::Slot_Belt, - "CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight, - "CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft, - "Ammunition", MWWorld::InventoryStore::Slot_Ammunition - )); + api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"Helmet", MWWorld::InventoryStore::Slot_Helmet}, + {"Cuirass", MWWorld::InventoryStore::Slot_Cuirass}, + {"Greaves", MWWorld::InventoryStore::Slot_Greaves}, + {"LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron}, + {"RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron}, + {"LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet}, + {"RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet}, + {"Boots", MWWorld::InventoryStore::Slot_Boots}, + {"Shirt", MWWorld::InventoryStore::Slot_Shirt}, + {"Pants", MWWorld::InventoryStore::Slot_Pants}, + {"Skirt", MWWorld::InventoryStore::Slot_Skirt}, + {"Robe", MWWorld::InventoryStore::Slot_Robe}, + {"LeftRing", MWWorld::InventoryStore::Slot_LeftRing}, + {"RightRing", MWWorld::InventoryStore::Slot_RightRing}, + {"Amulet", MWWorld::InventoryStore::Slot_Amulet}, + {"Belt", MWWorld::InventoryStore::Slot_Belt}, + {"CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight}, + {"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft}, + {"Ammunition", MWWorld::InventoryStore::Slot_Ammunition} + })); return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 011d0ae9f3..624cf69da6 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -47,14 +47,15 @@ namespace MWLua return LObject(getId(r.mHitObject), worldView->getObjectRegistry()); }); - api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( - "World", MWPhysics::CollisionType_World, - "Door", MWPhysics::CollisionType_Door, - "Actor", MWPhysics::CollisionType_Actor, - "HeightMap", MWPhysics::CollisionType_HeightMap, - "Projectile", MWPhysics::CollisionType_Projectile, - "Water", MWPhysics::CollisionType_Water, - "Default", MWPhysics::CollisionType_Default)); + api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"World", MWPhysics::CollisionType_World}, + {"Door", MWPhysics::CollisionType_Door}, + {"Actor", MWPhysics::CollisionType_Actor}, + {"HeightMap", MWPhysics::CollisionType_HeightMap}, + {"Projectile", MWPhysics::CollisionType_Projectile}, + {"Water", MWPhysics::CollisionType_Water}, + {"Default", MWPhysics::CollisionType_Default} + })); api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index 7ac5af0b1b..b302e565f4 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -38,6 +38,15 @@ namespace LuaUtil // A shortcut to create a new Lua table. sol::table newTable() { return sol::table(mLua, sol::create); } + template + sol::table tableFromPairs(std::initializer_list> list) + { + sol::table res(mLua, sol::create); + for (const auto& [k, v] : list) + res[k] = v; + return res; + } + // Registers a package that will be available from every sandbox via `require(name)`. // The package can be either a sol::table with an API or a sol::function. If it is a function, // it will be evaluated (once per sandbox) the first time when requested. If the package From a1d03d178d7f1016b84479b32cb39acd9b9e329c Mon Sep 17 00:00:00 2001 From: wareya Date: Fri, 12 Nov 2021 06:55:19 +0000 Subject: [PATCH 1693/2859] Update water_fragment.glsl --- files/shaders/water_fragment.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 5d42a2bdb1..5e50b4f7b2 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -61,7 +61,7 @@ const float RAIN_RIPPLE_RADIUS = 0.2; float scramble(float x, float z) { - return fract(pow(x*3.0+1.0, z)); + return fract(pow(fract(x)*3.0+1.0, z)); } vec2 randOffset(vec2 c, float time) From 700bace24cb7f52e13d571864adcc9966be51273 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 12 Nov 2021 11:53:21 +0100 Subject: [PATCH 1694/2859] do not emplace_back to our mLandTextures; instead use the heavy handed resize to be explicit about what we intend to do; resolves issue found here: https://github.com/OpenMW/openmw/pull/3220#issuecomment-964650167 --- apps/openmw/mwworld/esmstore.cpp | 2 +- apps/openmw/mwworld/store.hpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index bafdc8f37d..1284df694a 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -153,7 +153,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) // Land texture loading needs to use a separate internal store for each plugin. // We set the number of plugins here so we can properly verify if valid plugin // indices are being passed to the LandTexture Store retrieval methods. - mLandTextures.addPlugin(); + mLandTextures.resize(esm.getIndex()+1); // Loop through all records while(esm.hasMoreRecs()) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 17a37c23ea..22f36da690 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -222,8 +222,7 @@ namespace MWWorld const ESM::LandTexture *search(size_t index, size_t plugin) const; const ESM::LandTexture *find(size_t index, size_t plugin) const; - /// Resize the internal store to hold another plugin. - void addPlugin() { mStatic.emplace_back(); } + void resize(size_t num) { mStatic.resize(num); } size_t getSize() const override; size_t getSize(size_t plugin) const; From 32839cc7276b59a46f54e027b9c205aa2a2fe9f7 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 12 Nov 2021 22:06:59 +0100 Subject: [PATCH 1695/2859] Allow Lua scripts to work on pause (except timers and onUpdate handlers) --- apps/openmw/mwlua/luamanagerimp.cpp | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 4e47bf7167..2b9f0702e2 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -80,32 +80,28 @@ namespace MWLua void LuaManager::update(bool paused, float dt) { + if (mPlayer.isEmpty()) + return; // The game is not started yet. + ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); - if (!mPlayer.isEmpty()) + MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (!(getId(mPlayer) == getId(newPlayerPtr))) + throw std::logic_error("Player Refnum was changed unexpectedly"); + if (!mPlayer.isInCell() || !newPlayerPtr.isInCell() || mPlayer.getCell() != newPlayerPtr.getCell()) { - MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (!(getId(mPlayer) == getId(newPlayerPtr))) - throw std::logic_error("Player Refnum was changed unexpectedly"); - if (!mPlayer.isInCell() || !newPlayerPtr.isInCell() || mPlayer.getCell() != newPlayerPtr.getCell()) - { - mPlayer = newPlayerPtr; // player was moved to another cell, update ptr in registry - objectRegistry->registerPtr(mPlayer); - } + mPlayer = newPlayerPtr; // player was moved to another cell, update ptr in registry + objectRegistry->registerPtr(mPlayer); } - mWorldView.update(); - if (paused) - { - mInputEvents.clear(); - return; - } + mWorldView.update(); std::vector globalEvents = std::move(mGlobalEvents); std::vector localEvents = std::move(mLocalEvents); mGlobalEvents = std::vector(); mLocalEvents = std::vector(); + if (!paused) { // Update time and process timers double seconds = mWorldView.getGameTimeInSeconds() + dt; mWorldView.setGameTimeInSeconds(seconds); @@ -137,7 +133,7 @@ namespace MWLua // Engine handlers in local scripts PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); - if (playerScripts) + if (playerScripts && !paused) { for (const auto& event : mInputEvents) playerScripts->processInputEvent(event); @@ -158,8 +154,11 @@ namespace MWLua } mLocalEngineEvents.clear(); - for (LocalScripts* scripts : mActiveLocalScripts) - scripts->update(dt); + if (!paused) + { + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->update(dt); + } // Engine handlers in global scripts if (mPlayerChanged) @@ -177,7 +176,8 @@ namespace MWLua mGlobalScripts.actorActive(GObject(id, objectRegistry)); mActorAddedEvents.clear(); - mGlobalScripts.update(dt); + if (!paused) + mGlobalScripts.update(dt); } void LuaManager::applyQueuedChanges() From 5f406158b5df73c5d7825fbcde0b9aafbaf36c4a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 13 Nov 2021 12:46:10 +0100 Subject: [PATCH 1696/2859] Zero initialize context index --- components/esm/esmreader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 316748b53a..c84a1798ff 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -24,6 +24,7 @@ ESMReader::ESMReader() , mFileSize(0) { clearCtx(); + mCtx.index = 0; } void ESMReader::restoreContext(const ESM_Context &rc) From c7edca559bb489862c4996314d1231d7ed41e446 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 14:11:37 +0300 Subject: [PATCH 1697/2859] Morph geometry more like NifSkope (bug #6416) --- CHANGELOG.md | 1 + components/nifosg/controller.cpp | 2 +- components/nifosg/nifloader.cpp | 5 +++-- components/sceneutil/morphgeometry.cpp | 22 ++++++++++++---------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb86227f6c..d030b44859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ Bug #6363: Some scripts in Morrowland fail to work Bug #6376: Creatures should be able to use torches Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation + Bug #6416: Morphs are applied to the wrong target Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 956fe2e489..54300d34e8 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -224,7 +224,7 @@ void GeomMorpherController::operator()(SceneUtil::MorphGeometry* node, osg::Node if (mKeyFrames.size() <= 1) return; float input = getInputValue(nv); - int i = 0; + int i = 1; for (std::vector::iterator it = mKeyFrames.begin()+1; it != mKeyFrames.end(); ++it,++i) { float val = 0; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index e46e2d934b..ef935db0a4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1259,8 +1259,9 @@ namespace NifOsg const std::vector& morphs = morpher->data.getPtr()->mMorphs; if (morphs.empty()) return morphGeom; - // Note we are not interested in morph 0, which just contains the original vertices - for (unsigned int i = 1; i < morphs.size(); ++i) + if (morphs[0].mVertices.size() != static_cast(sourceGeometry->getVertexArray())->size()) + return morphGeom; + for (unsigned int i = 0; i < morphs.size(); ++i) morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f); return morphGeom; diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp index 59adbffffe..3e34e3dedc 100644 --- a/components/sceneutil/morphgeometry.cpp +++ b/components/sceneutil/morphgeometry.cpp @@ -104,8 +104,8 @@ void MorphGeometry::accept(osg::PrimitiveFunctor& func) const osg::BoundingBox MorphGeometry::computeBoundingBox() const { bool anyMorphTarget = false; - for (unsigned int i=0; i 0) + for (unsigned int i=1; i(mSourceGeometry->getVertexArray()); - std::vector vertBounds(sourceVerts.size()); + const osg::Vec3Array* sourceVerts = static_cast(mSourceGeometry->getVertexArray()); + if (mMorphTargets.size() != 0) + sourceVerts = mMorphTargets[0].getOffsets(); + std::vector vertBounds(sourceVerts->size()); // Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex. // The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position. @@ -132,7 +134,7 @@ osg::BoundingBox MorphGeometry::computeBoundingBox() const for (unsigned int i=0; igetTraversalNumber() || !mDirty) + if (mLastFrameNumber == nv->getTraversalNumber() || !mDirty || mMorphTargets.size() == 0) { osg::Geometry& geom = *getGeometry(mLastFrameNumber); nv->pushOntoNodePath(&geom); @@ -169,13 +171,13 @@ void MorphGeometry::cull(osg::NodeVisitor *nv) mLastFrameNumber = nv->getTraversalNumber(); osg::Geometry& geom = *getGeometry(mLastFrameNumber); - const osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); + const osg::Vec3Array* positionSrc = mMorphTargets[0].getOffsets(); osg::Vec3Array* positionDst = static_cast(geom.getVertexArray()); assert(positionSrc->size() == positionDst->size()); for (unsigned int vertex=0; vertexsize(); ++vertex) (*positionDst)[vertex] = (*positionSrc)[vertex]; - for (unsigned int i=0; i Date: Sat, 13 Nov 2021 14:06:21 +0100 Subject: [PATCH 1698/2859] Treat commas in scripts as whitespace --- CHANGELOG.md | 1 + .../mwscript/test_scripts.cpp | 15 ++++++++ components/compiler/discardparser.cpp | 19 ++-------- components/compiler/discardparser.hpp | 2 +- components/compiler/exprparser.cpp | 21 ----------- components/compiler/fileparser.cpp | 5 --- components/compiler/lineparser.cpp | 18 ++------- components/compiler/lineparser.hpp | 2 +- components/compiler/scanner.cpp | 4 -- components/compiler/scanner.hpp | 3 +- components/compiler/stringparser.cpp | 37 +++++-------------- components/compiler/stringparser.hpp | 10 ----- 12 files changed, 36 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb86227f6c..aa1cab9eab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ Feature #6249: Alpha testing support for Collada Feature #6251: OpenMW-CS: Set instance movement based on camera zoom Feature #6288: Preserve the "blocked" record flag for referenceable objects. + Feature #6380: Commas are treated as whitespace in vanilla Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index 79db3bb414..c04860afdf 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -427,6 +427,16 @@ set 1 to 42 End)mwscript"; + const std::string sIssue6380 = R"mwscript(,Begin,issue6380, + +,short,a + +,set,a,to,,,,(a,+1) + +messagebox,"this is a %g",a + +,End,)mwscript"; + TEST_F(MWScriptTest, mwscript_test_invalid) { EXPECT_THROW(compile("this is not a valid script", true), Compiler::SourceException); @@ -831,4 +841,9 @@ End)mwscript"; FAIL(); } } + + TEST_F(MWScriptTest, mwscript_test_6380) + { + EXPECT_FALSE(!compile(sIssue6380)); + } } \ No newline at end of file diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp index 0e7c4718cb..0a714d4eb6 100644 --- a/components/compiler/discardparser.cpp +++ b/components/compiler/discardparser.cpp @@ -12,7 +12,7 @@ namespace Compiler bool DiscardParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { - if (mState==StartState || mState==CommaState || mState==MinusState) + if (mState==StartState || mState==MinusState) { if (isEmpty()) mTokenLoc = loc; @@ -26,7 +26,7 @@ namespace Compiler bool DiscardParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) { - if (mState==StartState || mState==CommaState || mState==MinusState) + if (mState==StartState || mState==MinusState) { if (isEmpty()) mTokenLoc = loc; @@ -41,7 +41,7 @@ namespace Compiler bool DiscardParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { - if (mState==StartState || mState==CommaState) + if (mState==StartState) { if (isEmpty()) mTokenLoc = loc; @@ -55,18 +55,7 @@ namespace Compiler bool DiscardParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { - if (code==Scanner::S_comma && mState==StartState) - { - if (isEmpty()) - mTokenLoc = loc; - - start(); - - mState = CommaState; - return true; - } - - if (code==Scanner::S_minus && (mState==StartState || mState==CommaState)) + if (code==Scanner::S_minus && mState==StartState) { if (isEmpty()) mTokenLoc = loc; diff --git a/components/compiler/discardparser.hpp b/components/compiler/discardparser.hpp index 15e06756e2..5286676654 100644 --- a/components/compiler/discardparser.hpp +++ b/components/compiler/discardparser.hpp @@ -11,7 +11,7 @@ namespace Compiler { enum State { - StartState, CommaState, MinusState + StartState, MinusState }; State mState; diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 1aedc8dc59..668946f839 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -244,7 +244,6 @@ namespace Compiler } else { - // no comma was used between arguments scanner.putbackInt (value, loc); return false; } @@ -267,7 +266,6 @@ namespace Compiler } else { - // no comma was used between arguments scanner.putbackFloat (value, loc); return false; } @@ -343,7 +341,6 @@ namespace Compiler } else { - // no comma was used between arguments scanner.putbackName (name, loc); return false; } @@ -452,7 +449,6 @@ namespace Compiler } else { - // no comma was used between arguments scanner.putbackKeyword (keyword, loc); return false; } @@ -487,22 +483,6 @@ namespace Compiler return Parser::parseSpecial (code, loc, scanner); } - if (code==Scanner::S_comma) - { - mTokenLoc = loc; - - if (mFirst) - { - // leading comma - mFirst = false; - return true; - } - - // end marker - scanner.putbackSpecial (code, loc); - return false; - } - mFirst = false; if (code==Scanner::S_newline) @@ -539,7 +519,6 @@ namespace Compiler } else { - // no comma was used between arguments scanner.putbackSpecial (code, loc); return false; } diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index c7459c2ae7..9d5c02785e 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -121,11 +121,6 @@ namespace Compiler return false; } } - else if (code==Scanner::S_comma && (mState==NameState || mState==EndNameState)) - { - // ignoring comma (for now) - return true; - } return Parser::parseSpecial (code, loc, scanner); } diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index ec90812ec7..2e398618a3 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -136,7 +136,7 @@ namespace Compiler return false; } - if (mState==MessageState || mState==MessageCommaState) + if (mState==MessageState) { GetArgumentsFromMessageFormat processor; processor.process(name); @@ -155,7 +155,7 @@ namespace Compiler return true; } - if (mState==MessageButtonState || mState==MessageButtonCommaState) + if (mState==MessageButtonState) { Generator::pushString (mCode, mLiterals, name); mState = MessageButtonState; @@ -198,7 +198,7 @@ namespace Compiler bool LineParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { - if (mState==MessageState || mState==MessageCommaState) + if (mState==MessageState) { if (const Extensions *extensions = getContext().getExtensions()) { @@ -446,12 +446,6 @@ namespace Compiler if (code==Scanner::S_newline && (mState==EndState || mState==BeginState)) return false; - if (code==Scanner::S_comma && mState==MessageState) - { - mState = MessageCommaState; - return true; - } - if (code==Scanner::S_ref && mState==SetPotentialMemberVarState) { getErrorHandler().warning ("Stray explicit reference", loc); @@ -479,12 +473,6 @@ namespace Compiler return false; } - if (code==Scanner::S_comma && mState==MessageButtonState) - { - mState = MessageButtonCommaState; - return true; - } - if (code==Scanner::S_member && mState==SetPotentialMemberVarState) { mState = SetMemberVarState; diff --git a/components/compiler/lineparser.hpp b/components/compiler/lineparser.hpp index c434792d18..2a0e5d6630 100644 --- a/components/compiler/lineparser.hpp +++ b/components/compiler/lineparser.hpp @@ -24,7 +24,7 @@ namespace Compiler BeginState, SetState, SetLocalVarState, SetGlobalVarState, SetPotentialMemberVarState, SetMemberVarState, SetMemberVarState2, - MessageState, MessageCommaState, MessageButtonState, MessageButtonCommaState, + MessageState, MessageButtonState, EndState, PotentialExplicitState, ExplicitState, MemberState }; diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 0e2b76cb23..6cf2d6e7c0 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -531,8 +531,6 @@ namespace Compiler else special = S_cmpGT; } - else if (c==',') - special = S_comma; else if (c=='+') special = S_plus; else if (c=='*') @@ -552,8 +550,6 @@ namespace Compiler mTolerantNames = tolerant; return out; } - else if (expectName && special == S_comma) - mExpectName = true; TokenLoc loc (mLoc); mLoc.mLiteral.clear(); diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 8ee2672132..7a77811f2a 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -63,7 +63,7 @@ namespace Compiler bool isWhitespace() { - return (mData[0]==' ' || mData[0]=='\t') && mData[1]==0 && mData[2]==0 && mData[3]==0; + return (mData[0]==' ' || mData[0]=='\t' || mData[0]==',') && mData[1]==0 && mData[2]==0 && mData[3]==0; } bool isDigit() @@ -214,7 +214,6 @@ namespace Compiler S_open, S_close, S_cmpEQ, S_cmpNE, S_cmpLT, S_cmpLE, S_cmpGT, S_cmpGE, S_plus, S_minus, S_mult, S_div, - S_comma, S_ref, S_member }; diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index 4e0114e0a1..a6423211c2 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -13,7 +13,7 @@ namespace Compiler { StringParser::StringParser (ErrorHandler& errorHandler, const Context& context, Literals& literals) - : Parser (errorHandler, context), mLiterals (literals), mState (StartState), mSmashCase (false), mDiscard (false) + : Parser (errorHandler, context), mLiterals (literals), mSmashCase (false), mDiscard (false) { } @@ -21,23 +21,18 @@ namespace Compiler bool StringParser::parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner) { - if (mState==StartState || mState==CommaState) - { - start(); - mTokenLoc = loc; - - if (!mDiscard) - { - if (mSmashCase) - Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); - else - Generator::pushString (mCode, mLiterals, name); - } + start(); + mTokenLoc = loc; - return false; + if (!mDiscard) + { + if (mSmashCase) + Generator::pushString (mCode, mLiterals, Misc::StringUtils::lowerCase (name)); + else + Generator::pushString (mCode, mLiterals, name); } - return Parser::parseName (name, loc, scanner); + return false; } bool StringParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) @@ -75,17 +70,6 @@ namespace Compiler return Parser::parseKeyword (keyword, loc, scanner); } - bool StringParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) - { - if (code==Scanner::S_comma && mState==StartState) - { - mState = CommaState; - return true; - } - - return Parser::parseSpecial (code, loc, scanner); - } - bool StringParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) { reportWarning("Treating integer argument as a string", loc); @@ -99,7 +83,6 @@ namespace Compiler void StringParser::reset() { - mState = StartState; mCode.clear(); mSmashCase = false; mTokenLoc = TokenLoc(); diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp index 07b61d8fda..cdc7c676a7 100644 --- a/components/compiler/stringparser.hpp +++ b/components/compiler/stringparser.hpp @@ -14,13 +14,7 @@ namespace Compiler class StringParser : public Parser { - enum State - { - StartState, CommaState - }; - Literals& mLiterals; - State mState; std::vector mCode; bool mSmashCase; TokenLoc mTokenLoc; @@ -39,10 +33,6 @@ namespace Compiler ///< Handle a keyword token. /// \return fetch another token? - bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) override; - ///< Handle a special character token. - /// \return fetch another token? - bool parseInt (int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. /// \return fetch another token? From e1d28a1af300032e6ed4263dc276e97b74f421cd Mon Sep 17 00:00:00 2001 From: psi29a Date: Sat, 13 Nov 2021 13:41:07 +0000 Subject: [PATCH 1699/2859] Only allow macOS builds on OpenMW's fork of openmw --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 18fbbd91a1..b9ce5612e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -163,8 +163,7 @@ Debian_Clang_tests_Debug: stage: build only: variables: - - $CI_PROJECT_ID == "7107382" - - $CI_PIPELINE_SOURCE == "push" + - $CI_PROJECT_ID == "7107382" && $CI_PIPELINE_SOURCE == "push" cache: paths: - ccache/ From 73e9d7f3156c52fade63654379d8c41ed44dd8c0 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 13 Nov 2021 13:48:12 +0000 Subject: [PATCH 1700/2859] Recompute minimum settings window size based on tab bar size --- apps/openmw/mwgui/settingswindow.cpp | 28 ++++++++++++++++++++++++++++ apps/openmw/mwgui/settingswindow.hpp | 2 ++ 2 files changed, 30 insertions(+) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 257c129e8c..4951d69844 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -267,6 +267,8 @@ namespace MWGui mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); + computeMinimumWindowSize(); + center(); mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); @@ -739,6 +741,32 @@ namespace MWGui layoutControlsBox(); } + void SettingsWindow::computeMinimumWindowSize() + { + auto* window = mMainWidget->castType(); + auto minSize = window->getMinSize(); + + // Window should be at minimum wide enough to show all tabs. + int tabBarWidth = 0; + for (uint32_t i = 0; i < mSettingsTab->getItemCount(); i++) + { + tabBarWidth += mSettingsTab->getButtonWidthAt(i); + } + + // Need to include window margins + int margins = mMainWidget->getWidth() - mSettingsTab->getWidth(); + int minimumWindowWidth = tabBarWidth + margins; + + if (minimumWindowWidth > minSize.width) + { + minSize.width = minimumWindowWidth; + window->setMinSize(minSize); + + // Make a dummy call to setSize so MyGUI can apply any resize resulting from the change in MinSize + mMainWidget->setSize(mMainWidget->getSize()); + } + } + void SettingsWindow::resetScrollbars() { mResolutionList->setScrollPosition(0); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 9c28733f9a..5b04da553b 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -75,6 +75,8 @@ namespace MWGui void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); void layoutControlsBox(); + + void computeMinimumWindowSize(); private: void resetScrollbars(); From b918135b4b0906934a8c0ada8e5947b8f253e5e3 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 00:23:16 +0300 Subject: [PATCH 1701/2859] Factor race weight into magic projectile speed (bug #6343) --- CHANGELOG.md | 1 + apps/openmw/mwworld/projectilemanager.cpp | 13 +++++++++++-- docs/source/reference/modding/settings/game.rst | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6017f62db4..34ab40098e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Bug #6326: Detect Enchantment/Key should detect items in unresolved containers Bug #6327: Blocking roots the character in place + Bug #6343: Magic projectile speed doesn't take race weight into account Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures Bug #6354: SFX abruptly cut off after crossing max distance; implement soft fading of sound effects Bug #6363: Some scripts in Morrowland fail to work diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index e6a7195f5a..3bff1854a6 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -407,6 +409,7 @@ namespace MWWorld void ProjectileManager::moveMagicBolts(float duration) { + static const bool normaliseRaceSpeed = Settings::Manager::getBool("normalise race speed", "Game"); for (auto& magicBoltState : mMagicBolts) { if (magicBoltState.mToDelete) @@ -426,10 +429,16 @@ namespace MWWorld } } + const auto& store = MWBase::Environment::get().getWorld()->getStore(); osg::Quat orient = magicBoltState.mNode->getAttitude(); - static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() - .find("fTargetSpellMaxSpeed")->mValue.getFloat(); + static float fTargetSpellMaxSpeed = store.get().find("fTargetSpellMaxSpeed")->mValue.getFloat(); float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; + if (!normaliseRaceSpeed && !caster.isEmpty() && caster.getClass().isNpc()) + { + const auto npc = caster.get()->mBase; + const auto race = store.get().find(npc->mRace); + speed *= npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; + } osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); projectile->setVelocity(direction * speed); diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 58d5345f65..605be3f6af 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -275,7 +275,7 @@ normalise race speed :Range: True/False :Default: False -By default race weight is factored into horizontal movement speed like in Morrowind. +By default race weight is factored into horizontal movement and magic projectile speed like in Morrowind. For example, an NPC which has 1.2 race weight is faster than an NPC with the exact same stats and weight 1.0 by a factor of 120%. If this setting is true, race weight is ignored in the calculations which allows for a movement behavior equivalent to the one introduced by the equivalent Morrowind Code Patch feature. From 08a25c2b1fc7c989077be6abea15ddcb678ac3b6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 13 Nov 2021 16:14:50 +0100 Subject: [PATCH 1702/2859] Support seed type different from std::size_t for hashCombine --- components/misc/hash.hpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/components/misc/hash.hpp b/components/misc/hash.hpp index 30a9c41ee9..861df73772 100644 --- a/components/misc/hash.hpp +++ b/components/misc/hash.hpp @@ -1,15 +1,20 @@ #ifndef MISC_HASH_H #define MISC_HASH_H +#include +#include +#include + namespace Misc { /// Implemented similar to the boost::hash_combine - template - inline void hashCombine(std::size_t& seed, const T& v) + template + inline void hashCombine(Seed& seed, const T& v) { + static_assert(sizeof(Seed) >= sizeof(std::size_t), "Resulting hash will be truncated"); std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + seed ^= static_cast(hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2)); } } -#endif \ No newline at end of file +#endif From 59ce00f74271400c5c6484be575caf631a74842f Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 19:14:28 +0300 Subject: [PATCH 1703/2859] Refactor all present Bethesda Havok 'support' --- components/nif/physics.cpp | 73 ++++++++++++++------- components/nif/physics.hpp | 119 ++++++++++++++++++++++++----------- components/nif/recordptr.hpp | 5 +- 3 files changed, 136 insertions(+), 61 deletions(-) diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 992a56c9b7..ba78f7a9cd 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -3,52 +3,79 @@ namespace Nif { - void bhkCollisionObject::read(NIFStream *nif) + + /// Non-record data types + + void bhkWorldObjCInfoProperty::read(NIFStream *nif) { - NiCollisionObject::read(nif); - mFlags = nif->getUShort(); - mBody.read(nif); + mData = nif->getUInt(); + mSize = nif->getUInt(); + mCapacityAndFlags = nif->getUInt(); } - void bhkWorldObject::read(NIFStream *nif) + void bhkWorldObjectCInfo::read(NIFStream *nif) { - mShape.read(nif); - if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) - nif->skip(4); // Unknown - mFlags = nif->getUInt(); nif->skip(4); // Unused - mWorldObjectInfo.mPhaseType = nif->getChar(); + mPhaseType = static_cast(nif->getChar()); nif->skip(3); // Unused - mWorldObjectInfo.mData = nif->getUInt(); - mWorldObjectInfo.mSize = nif->getUInt(); - mWorldObjectInfo.mCapacityAndFlags = nif->getUInt(); + mProperty.read(nif); } - void bhkWorldObject::post(NIFFile *nif) + void HavokMaterial::read(NIFStream *nif) { - mShape.post(nif); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + nif->skip(4); // Unknown + mMaterial = nif->getUInt(); } - void bhkEntity::read(NIFStream *nif) + void HavokFilter::read(NIFStream *nif) + { + mLayer = nif->getChar(); + mFlags = nif->getChar(); + mGroup = nif->getUShort(); + } + + void hkSubPartData::read(NIFStream *nif) + { + mHavokFilter.read(nif); + mNumVertices = nif->getUInt(); + mHavokMaterial.read(nif); + } + + void bhkEntityCInfo::read(NIFStream *nif) { - bhkWorldObject::read(nif); mResponseType = static_cast(nif->getChar()); nif->skip(1); // Unused mProcessContactDelay = nif->getUShort(); } - void HavokMaterial::read(NIFStream *nif) + /// Record types + + void bhkCollisionObject::read(NIFStream *nif) + { + NiCollisionObject::read(nif); + mFlags = nif->getUShort(); + mBody.read(nif); + } + + void bhkWorldObject::read(NIFStream *nif) { + mShape.read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) nif->skip(4); // Unknown - mMaterial = nif->getUInt(); + mHavokFilter.read(nif); + mWorldObjectInfo.read(nif); } - void hkSubPartData::read(NIFStream *nif) + void bhkWorldObject::post(NIFFile *nif) { - mHavokFilter = nif->getUInt(); - mNumVertices = nif->getUInt(); - mHavokMaterial.read(nif); + mShape.post(nif); + } + + void bhkEntity::read(NIFStream *nif) + { + bhkWorldObject::read(nif); + mInfo.read(nif); } } // Namespace \ No newline at end of file diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index ca31512c71..dc365cf099 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -8,6 +8,82 @@ namespace Nif { +/// Non-record data types + +struct bhkWorldObjCInfoProperty +{ + unsigned int mData; + unsigned int mSize; + unsigned int mCapacityAndFlags; + void read(NIFStream *nif); +}; + +enum class BroadPhaseType : uint8_t +{ + BroadPhase_Invalid = 0, + BroadPhase_Entity = 1, + BroadPhase_Phantom = 2, + BroadPhase_Border = 3 +}; + +struct bhkWorldObjectCInfo +{ + BroadPhaseType mPhaseType; + bhkWorldObjCInfoProperty mProperty; + void read(NIFStream *nif); +}; + +struct HavokMaterial +{ + unsigned int mMaterial; + void read(NIFStream *nif); +}; + +struct HavokFilter +{ + unsigned char mLayer; + unsigned char mFlags; + unsigned short mGroup; + void read(NIFStream *nif); +}; + +struct hkSubPartData +{ + HavokMaterial mHavokMaterial; + unsigned int mNumVertices; + HavokFilter mHavokFilter; + void read(NIFStream *nif); +}; + +enum class hkResponseType : uint8_t +{ + Response_Invalid = 0, + Response_SimpleContact = 1, + Response_Reporting = 2, + Response_None = 3 +}; + +struct bhkEntityCInfo +{ + hkResponseType mResponseType; + unsigned short mProcessContactDelay; + void read(NIFStream *nif); +} + +/// Record types + +// Abstract Bethesda Havok object +struct bhkRefObject : public Record {}; + +// Abstract serializable Bethesda Havok object +struct bhkSerializable : public bhkRefObject {}; + +// Abstract narrowphase collision detection object +struct bhkShape : public bhkSerializable {}; + +// Abstract bhkShape collection +struct bhkShapeCollection : public bhkShape {}; + // Generic collision object struct NiCollisionObject : public Record { @@ -28,7 +104,7 @@ struct NiCollisionObject : public Record struct bhkCollisionObject : public NiCollisionObject { unsigned short mFlags; - CollisionBodyPtr mBody; + bhkWorldObjectPtr mBody; void read(NIFStream *nif) override; void post(NIFFile *nif) override @@ -39,52 +115,21 @@ struct bhkCollisionObject : public NiCollisionObject }; // Abstract Havok shape info record -struct bhkWorldObject : public Record +struct bhkWorldObject : public bhkSerializable { bhkShapePtr mShape; - unsigned int mFlags; // Havok layer type, collision filter flags and group - struct WorldObjectInfo - { - unsigned char mPhaseType; - unsigned int mData; - unsigned int mSize; - unsigned int mCapacityAndFlags; - }; - WorldObjectInfo mWorldObjectInfo; + HavokFilter mHavokFilter; + bhkWorldObjectCInfo mWorldObjectInfo; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; -struct bhkShape : public Record {}; - -enum class hkResponseType : uint8_t -{ - Response_Invalid = 0, - Response_SimpleContact = 1, - Response_Reporting = 2, - Response_None = 3 -}; - +// Abstract struct bhkEntity : public bhkWorldObject { - hkResponseType mResponseType; - unsigned short mProcessContactDelay; + bhkEntityCInfo mInfo; void read(NIFStream *nif) override; }; -struct HavokMaterial -{ - unsigned int mMaterial; - void read(NIFStream *nif); -}; - -struct hkSubPartData -{ - HavokMaterial mHavokMaterial; - unsigned int mNumVertices; - unsigned int mHavokFilter; - void read(NIFStream *nif); -}; - } // Namespace #endif \ No newline at end of file diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index a25480fe43..c97957c95a 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -151,6 +151,7 @@ struct NiAlphaProperty; struct NiCollisionObject; struct bhkWorldObject; struct bhkShape; +struct bhkSerializable; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -179,7 +180,7 @@ using NiGeometryDataPtr = RecordPtrT; using BSShaderPropertyPtr = RecordPtrT; using NiAlphaPropertyPtr = RecordPtrT; using NiCollisionObjectPtr = RecordPtrT; -using CollisionBodyPtr = RecordPtrT; +using bhkWorldObjectPtr = RecordPtrT; using bhkShapePtr = RecordPtrT; using NodeList = RecordListT; @@ -188,6 +189,8 @@ using ExtraList = RecordListT; using NiSourceTextureList = RecordListT; using NiFloatInterpolatorList = RecordListT; using NiTriStripsDataList = RecordListT; +using bhkShapeList = RecordListT; +using bhkSerializableList = RecordListT; } // Namespace #endif From e7cc76bba2457d11aaec3af494fb98f11eeac041 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 19:23:37 +0300 Subject: [PATCH 1704/2859] Load bhkMoppBvTreeShape --- components/nif/niffile.cpp | 1 + components/nif/physics.cpp | 29 +++++++++++++++++++++++++++++ components/nif/physics.hpp | 24 ++++++++++++++++++++++++ components/nif/record.hpp | 3 ++- 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index dbe455fbd4..1bd4821395 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -143,6 +143,7 @@ static std::map makeFactory() factory["bhkCollisionObject"] = {&construct , RC_bhkCollisionObject }; factory["BSDismemberSkinInstance"] = {&construct , RC_BSDismemberSkinInstance }; factory["NiControllerManager"] = {&construct , RC_NiControllerManager }; + factory["bhkMoppBvTreeShape"] = {&construct , RC_bhkMoppBvTreeShape }; return factory; } diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index ba78f7a9cd..3c43c160cd 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -42,6 +42,17 @@ namespace Nif mHavokMaterial.read(nif); } + void hkpMoppCode::read(NIFStream *nif) + { + unsigned int size = nif->getUInt(); + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + mOffset = nif->getVector4(); + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + nif->getChar(); // MOPP data build type + if (size) + nif->getChars(mData, size); + } + void bhkEntityCInfo::read(NIFStream *nif) { mResponseType = static_cast(nif->getChar()); @@ -78,4 +89,22 @@ namespace Nif mInfo.read(nif); } + void bhkBvTreeShape::read(NIFStream *nif) + { + mShape.read(nif); + } + + void bhkBvTreeShape::post(NIFFile *nif) + { + mShape.post(nif); + } + + void bhkMoppBvTreeShape::read(NIFStream *nif) + { + bhkBvTreeShape::read(nif); + nif->skip(12); // Unused + mScale = nif->getFloat(); + mMopp.read(nif); + } + } // Namespace \ No newline at end of file diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index dc365cf099..190cb42b1d 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -70,6 +70,13 @@ struct bhkEntityCInfo void read(NIFStream *nif); } +struct hkpMoppCode +{ + osg::Vec4f mOffset; + std::vector mData; + void read(NIFStream *nif); +}; + /// Record types // Abstract Bethesda Havok object @@ -131,5 +138,22 @@ struct bhkEntity : public bhkWorldObject void read(NIFStream *nif) override; }; +// Bethesda extension of hkpBvTreeShape +// hkpBvTreeShape adds a bounding volume tree to an hkpShapeCollection +struct bhkBvTreeShape : public bhkShape +{ + bhkShapePtr mShape; + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + +// bhkBvTreeShape with Havok MOPP code +struct bhkMoppBvTreeShape : public bhkBvTreeShape +{ + float mScale; + hkpMoppCode mMopp; + void read(NIFStream *nif) override; +}; + } // Namespace #endif \ No newline at end of file diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 0b9b2dc998..47ce14969f 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -130,7 +130,8 @@ enum RecordType RC_NiCollisionObject, RC_bhkCollisionObject, RC_BSDismemberSkinInstance, - RC_NiControllerManager + RC_NiControllerManager, + RC_bhkMoppBvTreeShape }; /// Base class for all records From 83aa96e38f27de4fd9b7e1b33e17bf09c7e6673c Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 19:38:00 +0300 Subject: [PATCH 1705/2859] Load a bunch of triangle strip-based Havok records --- components/nif/niffile.cpp | 3 ++ components/nif/physics.cpp | 72 ++++++++++++++++++++++++++++++++++++ components/nif/physics.hpp | 43 +++++++++++++++++++++ components/nif/record.hpp | 5 ++- components/nif/recordptr.hpp | 2 + 5 files changed, 124 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 1bd4821395..5eb55e10d1 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -144,6 +144,9 @@ static std::map makeFactory() factory["BSDismemberSkinInstance"] = {&construct , RC_BSDismemberSkinInstance }; factory["NiControllerManager"] = {&construct , RC_NiControllerManager }; factory["bhkMoppBvTreeShape"] = {&construct , RC_bhkMoppBvTreeShape }; + factory["bhkNiTriStripsShape"] = {&construct , RC_bhkNiTriStripsShape }; + factory["bhkPackedNiTriStripsShape"] = {&construct , RC_bhkPackedNiTriStripsShape }; + factory["hkPackedNiTriStripsData"] = {&construct , RC_hkPackedNiTriStripsData }; return factory; } diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 3c43c160cd..e447bc65df 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -60,6 +60,15 @@ namespace Nif mProcessContactDelay = nif->getUShort(); } + void TriangleData::read(NIFStream *nif) + { + for (int i = 0; i < 3; i++) + mTriangle[i] = nif->getUShort(); + mWeldingInfo = nif->getUShort(); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + mNormal = nif->getVector3(); + } + /// Record types void bhkCollisionObject::read(NIFStream *nif) @@ -107,4 +116,67 @@ namespace Nif mMopp.read(nif); } + void bhkNiTriStripsShape::read(NIFStream *nif) + { + mHavokMaterial.read(nif); + mRadius = nif->getFloat(); + nif->skip(20); // Unused + mGrowBy = nif->getUInt(); + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + mScale = nif->getVector4(); + mData.read(nif); + unsigned int numFilters = nif->getUInt(); + nif->getUInts(mFilters, numFilters); + } + + void bhkNiTriStripsShape::post(NIFFile *nif) + { + mData.post(nif); + } + + void bhkPackedNiTriStripsShape::read(NIFStream *nif) + { + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + { + mSubshapes.resize(nif->getUShort()); + for (hkSubPartData& subshape : mSubshapes) + subshape.read(nif); + } + mUserData = nif->getUInt(); + nif->skip(4); // Unused + mRadius = nif->getFloat(); + nif->skip(4); // Unused + mScale = nif->getVector4(); + nif->skip(20); // Duplicates of the two previous fields + mData.read(nif); + } + + void bhkPackedNiTriStripsShape::post(NIFFile *nif) + { + mData.post(nif); + } + + void hkPackedNiTriStripsData::read(NIFStream *nif) + { + unsigned int numTriangles = nif->getUInt(); + mTriangles.resize(numTriangles); + for (unsigned int i = 0; i < numTriangles; i++) + mTriangles[i].read(nif); + + unsigned int numVertices = nif->getUInt(); + bool compressed = false; + if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) + compressed = nif->getBoolean(); + if (!compressed) + nif->getVector3s(mVertices, numVertices); + else + nif->skip(6 * numVertices); // Half-precision vectors are not currently supported + if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) + { + mSubshapes.resize(nif->getUShort()); + for (hkSubPartData& subshape : mSubshapes) + subshape.read(nif); + } + } + } // Namespace \ No newline at end of file diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 190cb42b1d..6c9fe685ef 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -77,6 +77,14 @@ struct hkpMoppCode void read(NIFStream *nif); }; +struct TriangleData +{ + unsigned short mTriangle[3]; + unsigned short mWeldingInfo; + osg::Vec3f mNormal; + void read(NIFStream *nif); +}; + /// Record types // Abstract Bethesda Havok object @@ -155,5 +163,40 @@ struct bhkMoppBvTreeShape : public bhkBvTreeShape void read(NIFStream *nif) override; }; +// Bethesda triangle strip-based Havok shape collection +struct bhkNiTriStripsShape : public bhkShape +{ + HavokMaterial mHavokMaterial; + float mRadius; + unsigned int mGrowBy; + osg::Vec4f mScale{1.f, 1.f, 1.f, 0.f}; + NiTriStripsDataList mData; + std::vector mFilters; + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + +// Bethesda packed triangle strip-based Havok shape collection +struct bhkPackedNiTriStripsShape : public bhkShapeCollection +{ + std::vector mSubshapes; + unsigned int mUserData; + float mRadius; + osg::Vec4f mScale; + hkPackedNiTriStripsDataPtr mData; + + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + +// bhkPackedNiTriStripsShape data block +struct hkPackedNiTriStripsData : public bhkShapeCollection +{ + std::vector mTriangles; + std::vector mVertices; + std::vector mSubshapes; + void read(NIFStream *nif) override; +}; + } // Namespace #endif \ No newline at end of file diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 47ce14969f..804b13262d 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -131,7 +131,10 @@ enum RecordType RC_bhkCollisionObject, RC_BSDismemberSkinInstance, RC_NiControllerManager, - RC_bhkMoppBvTreeShape + RC_bhkMoppBvTreeShape, + RC_bhkNiTriStripsShape, + RC_bhkPackedNiTriStripsShape, + RC_hkPackedNiTriStripsData }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index c97957c95a..cc62d7b2e0 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -152,6 +152,7 @@ struct NiCollisionObject; struct bhkWorldObject; struct bhkShape; struct bhkSerializable; +struct hkPackedNiTriStripsData; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -182,6 +183,7 @@ using NiAlphaPropertyPtr = RecordPtrT; using NiCollisionObjectPtr = RecordPtrT; using bhkWorldObjectPtr = RecordPtrT; using bhkShapePtr = RecordPtrT; +using hkPackedNiTriStripsDataPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; From c01fff280a45e9cc8edb7e5a9e526d142faa1fbb Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 19:53:03 +0300 Subject: [PATCH 1706/2859] Load bhkConvexVerticesShape, bhkBoxShape, bhkListShape --- components/nif/niffile.cpp | 3 +++ components/nif/physics.cpp | 44 ++++++++++++++++++++++++++++++++++++++ components/nif/physics.hpp | 42 ++++++++++++++++++++++++++++++++++++ components/nif/record.hpp | 5 ++++- 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 5eb55e10d1..a752478d75 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -147,6 +147,9 @@ static std::map makeFactory() factory["bhkNiTriStripsShape"] = {&construct , RC_bhkNiTriStripsShape }; factory["bhkPackedNiTriStripsShape"] = {&construct , RC_bhkPackedNiTriStripsShape }; factory["hkPackedNiTriStripsData"] = {&construct , RC_hkPackedNiTriStripsData }; + factory["bhkConvexVerticesShape"] = {&construct , RC_bhkConvexVerticesShape }; + factory["bhkBoxShape"] = {&construct , RC_bhkBoxShape }; + factory["bhkListShape"] = {&construct , RC_bhkListShape }; return factory; } diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index e447bc65df..4809762576 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -179,4 +179,48 @@ namespace Nif } } + void bhkSphereRepShape::read(NIFStream *nif) + { + mHavokMaterial.read(nif); + } + + void bhkConvexShape::read(NIFStream *nif) + { + bhkSphereRepShape::read(nif); + mRadius = nif->getFloat(); + } + + void bhkConvexVerticesShape::read(NIFStream *nif) + { + bhkConvexShape::read(nif); + mVerticesProperty.read(nif); + mNormalsProperty.read(nif); + unsigned int numVertices = nif->getUInt(); + if (numVertices) + nif->getVector4s(mVertices, numVertices); + unsigned int numNormals = nif->getUInt(); + if (numNormals) + nif->getVector4s(mNormals, numNormals); + } + + void bhkBoxShape::read(NIFStream *nif) + { + bhkConvexShape::read(nif); + nif->skip(8); // Unused + mExtents = nif->getVector3(); + nif->skip(4); // Unused + } + + void bhkListShape::read(NIFStream *nif) + { + mSubshapes.read(nif); + mHavokMaterial.read(nif); + mChildShapeProperty.read(nif); + mChildFilterProperty.read(nif); + unsigned int numFilters = nif->getUInt(); + mHavokFilters.resize(numFilters); + for (HavokFilter& filter : mHavokFilters) + filter.read(nif); + } + } // Namespace \ No newline at end of file diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 6c9fe685ef..4828f34877 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -198,5 +198,47 @@ struct hkPackedNiTriStripsData : public bhkShapeCollection void read(NIFStream *nif) override; }; +// Abstract +struct bhkSphereRepShape : public bhkShape +{ + HavokMaterial mHavokMaterial; + void read(NIFStream *nif) override; +}; + +// Abstract +struct bhkConvexShape : public bhkSphereRepShape +{ + float mRadius; + void read(NIFStream} *nif) override; +}; + +// A convex shape built from vertices +struct bhkConvexVerticesShape : public bhkConvexShape +{ + bhkWorldObjCInfoProperty mVerticesProperty; + bhkWorldObjCInfoProperty mNormalsProperty; + std::vector mVertices; + std::vector mNormals; + void read(NIFStream *nif) override; +}; + +// A box +struct bhkBoxShape : public bhkConvexShape +{ + osg::Vec3f mExtents; + void read(NIFStream *nif) override; +}; + +// A list of shapes +struct bhkListShape : public bhkShapeCollection +{ + bhkShapeList mSubshapes; + HavokMaterial mHavokMaterial; + bhkWorldObjCInfoProperty mChildShapeProperty; + bhkWorldObjCInfoProperty mChildFilterProperty; + std::vector mHavokFilters; + void read(NIFStream *nif) override; +}; + } // Namespace #endif \ No newline at end of file diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 804b13262d..8648050d84 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -134,7 +134,10 @@ enum RecordType RC_bhkMoppBvTreeShape, RC_bhkNiTriStripsShape, RC_bhkPackedNiTriStripsShape, - RC_hkPackedNiTriStripsData + RC_hkPackedNiTriStripsData, + RC_bhkConvexVerticesShape, + RC_bhkBoxShape, + RC_bhkListShape }; /// Base class for all records From 25f4d05c2ebde31376ec84d63cb18d16ee3a8812 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 19:59:30 +0300 Subject: [PATCH 1707/2859] Load bhkRigidBody --- components/nif/niffile.cpp | 2 + components/nif/physics.cpp | 87 +++++++++++++++++++++++++++++++++++++ components/nif/physics.hpp | 89 ++++++++++++++++++++++++++++++++++++++ components/nif/record.hpp | 4 +- 4 files changed, 181 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index a752478d75..82dab3bcee 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -150,6 +150,8 @@ static std::map makeFactory() factory["bhkConvexVerticesShape"] = {&construct , RC_bhkConvexVerticesShape }; factory["bhkBoxShape"] = {&construct , RC_bhkBoxShape }; factory["bhkListShape"] = {&construct , RC_bhkListShape }; + factory["bhkRigidBody"] = {&construct , RC_bhkRigidBody }; + factory["bhkRigidBodyT"] = {&construct , RC_bhkRigidBodyT }; return factory; } diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 4809762576..9bbeb148dd 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -69,6 +69,82 @@ namespace Nif mNormal = nif->getVector3(); } + void bhkRigidBodyCInfo::read(NIFStream *nif) + { + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + { + nif->skip(4); // Unused + mHavokFilter.read(nif); + nif->skip(4); // Unused + if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) + { + if (nif->getBethVersion() >= 83) + nif->skip(4); // Unused + mResponseType = static_cast(nif->getChar()); + nif->skip(1); // Unused + mProcessContactDelay = nif->getUShort(); + } + } + if (nif->getBethVersion() < 83) + nif->skip(4); // Unused + mTranslation = nif->getVector4(); + mRotation = nif->getQuaternion(); + mLinearVelocity = nif->getVector4(); + mAngularVelocity = nif->getVector4(); + for (int i = 0; i < 3; i++) + for (int j = 0; j < 4; j++) + mInertiaTensor[i][j] = nif->getFloat(); + mCenter = nif->getVector4(); + mMass = nif->getFloat(); + mLinearDamping = nif->getFloat(); + mAngularDamping = nif->getFloat(); + if (nif->getBethVersion() >= 83) + { + if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) + mTimeFactor = nif->getFloat(); + mGravityFactor = nif->getFloat(); + } + mFriction = nif->getFloat(); + if (nif->getBethVersion() >= 83) + mRollingFrictionMult = nif->getFloat(); + mRestitution = nif->getFloat(); + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + { + mMaxLinearVelocity = nif->getFloat(); + mMaxAngularVelocity = nif->getFloat(); + if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) + mPenetrationDepth = nif->getFloat(); + } + mMotionType = static_cast(nif->getChar()); + if (nif->getBethVersion() < 83) + mDeactivatorType = static_cast(nif->getChar()); + else + mEnableDeactivation = nif->getBoolean(); + mSolverDeactivation = static_cast(nif->getChar()); + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) + { + nif->skip(1); + mPenetrationDepth = nif->getFloat(); + mTimeFactor = nif->getFloat(); + nif->skip(4); + mResponseType = static_cast(nif->getChar()); + nif->skip(1); // Unused + mProcessContactDelay = nif->getUShort(); + } + mQualityType = static_cast(nif->getChar()); + if (nif->getBethVersion() >= 83) + { + mAutoRemoveLevel = nif->getChar(); + mResponseModifierFlags = nif->getChar(); + mNumContactPointShapeKeys = nif->getChar(); + mForceCollidedOntoPPU = nif->getBoolean(); + } + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) + nif->skip(3); // Unused + else + nif->skip(12); // Unused + } + /// Record types void bhkCollisionObject::read(NIFStream *nif) @@ -223,4 +299,15 @@ namespace Nif filter.read(nif); } + void bhkRigidBody::read(NIFStream *nif) + { + bhkEntity::read(nif); + mInfo.read(nif); + mConstraints.read(nif); + if (nif->getBethVersion() < 76) + mBodyFlags = nif->getUInt(); + else + mBodyFlags = nif->getUShort(); + } + } // Namespace \ No newline at end of file diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 4828f34877..c3adb4f6f0 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -85,6 +85,86 @@ struct TriangleData void read(NIFStream *nif); }; +enum class hkMotionType : uint8_t +{ + Motion_Invalid = 0, + Motion_Dynamic = 1, + Motion_SphereInertia = 2, + Motion_SphereStabilized = 3, + Motion_BoxInertia = 4, + Motion_BoxStabilized = 5, + Motion_Keyframed = 6, + Motion_Fixed = 7, + Motion_ThinBox = 8, + Motion_Character = 9 +}; + +enum class hkDeactivatorType : uint8_t +{ + Deactivator_Invalid = 0, + Deactivator_Never = 1, + Deactivator_Spatial = 2 +}; + +enum class hkSolverDeactivation : uint8_t +{ + SolverDeactivation_Invalid = 0, + SolverDeactivation_Off = 1, + SolverDeactivation_Low = 2, + SolverDeactivation_Medium = 3, + SolverDeactivation_High = 4, + SolverDeactivation_Max = 5 +}; + +enum class hkQualityType : uint8_t +{ + Quality_Invalid = 0, + Quality_Fixed = 1, + Quality_Keyframed = 2, + Quality_Debris = 3, + Quality_Moving = 4, + Quality_Critical = 5, + Quality_Bullet = 6, + Quality_User = 7, + Quality_Character = 8, + Quality_KeyframedReport = 9 +}; + + +struct bhkRigidBodyCInfo +{ + HavokFilter mHavokFilter; + hkResponseType mResponseType; + unsigned short mProcessContactDelay; + osg::Vec4f mTranslation; + osg::Quat mRotation; + osg::Vec4f mLinearVelocity; + osg::Vec4f mAngularVelocity; + float mInertiaTensor[3][4]; + osg::Vec4f mCenter; + float mMass; + float mLinearDamping; + float mAngularDamping; + float mTimeFactor{1.f}; + float mGravityFactor{1.f}; + float mFriction; + float mRollingFrictionMult; + float mRestitution; + float mMaxLinearVelocity; + float mMaxAngularVelocity; + float mPenetrationDepth; + hkMotionType mMotionType; + hkDeactivatorType mDeactivatorType; + bool mEnableDeactivation{true}; + hkSolverDeactivation mSolverDeactivation; + hkQualityType mQualityType; + unsigned char mAutoRemoveLevel; + unsigned char mResponseModifierFlags; + unsigned char mNumContactPointShapeKeys; + bool mForceCollidedOntoPPU; + void read(NIFStream *nif); +}; + /// Record types // Abstract Bethesda Havok object @@ -240,5 +320,14 @@ struct bhkListShape : public bhkShapeCollection void read(NIFStream *nif) override; }; +struct bhkRigidBody : public bhkEntity +{ + bhkRigidBodyCInfo mInfo; + bhkSerializableList mConstraints; + unsigned int mBodyFlags; + + void read(NIFStream *nif) override; +}; + } // Namespace #endif \ No newline at end of file diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 8648050d84..5fb50a05e5 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -137,7 +137,9 @@ enum RecordType RC_hkPackedNiTriStripsData, RC_bhkConvexVerticesShape, RC_bhkBoxShape, - RC_bhkListShape + RC_bhkListShape, + RC_bhkRigidBody, + RC_bhkRigidBodyT }; /// Base class for all records From d347f8f4f01d6e8c9bbe86183658cd294f67a6f6 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 20:04:21 +0300 Subject: [PATCH 1708/2859] Fix build --- components/nif/physics.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index c3adb4f6f0..613ec0ba43 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -68,7 +68,7 @@ struct bhkEntityCInfo hkResponseType mResponseType; unsigned short mProcessContactDelay; void read(NIFStream *nif); -} +}; struct hkpMoppCode { @@ -130,7 +130,6 @@ enum class hkQualityType : uint8_t Quality_KeyframedReport = 9 }; - struct bhkRigidBodyCInfo { HavokFilter mHavokFilter; @@ -289,7 +288,7 @@ struct bhkSphereRepShape : public bhkShape struct bhkConvexShape : public bhkSphereRepShape { float mRadius; - void read(NIFStream} *nif) override; + void read(NIFStream *nif) override; }; // A convex shape built from vertices From d0adbc1b8f193e0d0ce3b78eab467e8a9915464b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Nov 2021 18:24:51 +0000 Subject: [PATCH 1709/2859] Also import GMock::Main Debug library properly --- cmake/FindGMock.cmake | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake index 502910a9df..0e98d4e6c0 100644 --- a/cmake/FindGMock.cmake +++ b/cmake/FindGMock.cmake @@ -182,8 +182,16 @@ endif() set_target_properties(GMock::Main PROPERTIES INTERFACE_LINK_LIBRARIES "GMock::GMock" - IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" - IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}") + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX") + +if(EXISTS "${GMOCK_MAIN_LIBRARY}") + set_target_properties(GMock::Main PROPERTIES + IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}") +endif() +if(EXISTS "${GMOCK_MAIN_LIBRARY_DEBUG}") + set_target_properties(GMock::Main PROPERTIES + IMPORTED_LOCATION "{GMOCK_MAIN_LIBRARY_DEBUG}) +endif() if(GMOCK_FOUND) set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) From 50b90a594d6016f170b44ec622d5515a2c1281f9 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 8 Oct 2020 03:39:27 +0300 Subject: [PATCH 1710/2859] Loading and basic handling of BSLightingShaderProperty --- components/nif/niffile.cpp | 1 + components/nif/property.cpp | 56 ++++++++++++++++++++ components/nif/property.hpp | 63 +++++++++++++++++----- components/nif/record.hpp | 3 +- components/nifosg/nifloader.cpp | 93 +++++++++++++++++++++++++++------ 5 files changed, 186 insertions(+), 30 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 82dab3bcee..f11b75d218 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -152,6 +152,7 @@ static std::map makeFactory() factory["bhkListShape"] = {&construct , RC_bhkListShape }; factory["bhkRigidBody"] = {&construct , RC_bhkRigidBody }; factory["bhkRigidBodyT"] = {&construct , RC_bhkRigidBodyT }; + factory["BSLightingShaderProperty"] = {&construct , RC_BSLightingShaderProperty }; return factory; } diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 1d2dd885d4..70acbb82ae 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -146,6 +146,62 @@ void BSShaderNoLightingProperty::read(NIFStream *nif) falloffParams = nif->getVector4(); } +void BSLightingShaderProperty::read(NIFStream *nif) +{ + type = nif->getUInt(); + BSShaderProperty::read(nif); + flags1 = nif->getUInt(); + flags2 = nif->getUInt(); + nif->skip(8); // UV offset + nif->skip(8); // UV scale + mTextureSet.read(nif); + mEmissive = nif->getVector3(); + mEmissiveMult = nif->getFloat(); + mClamp = nif->getUInt(); + mAlpha = nif->getFloat(); + nif->getFloat(); // Refraction strength + mGlossiness = nif->getFloat(); + mSpecular = nif->getVector3(); + mSpecStrength = nif->getFloat(); + nif->skip(8); // Lighting effects + switch (static_cast(type)) + { + case BSLightingShaderType::ShaderType_EnvMap: + nif->skip(4); // Environment map scale + break; + case BSLightingShaderType::ShaderType_SkinTint: + case BSLightingShaderType::ShaderType_HairTint: + nif->skip(12); // Tint color + break; + case BSLightingShaderType::ShaderType_ParallaxOcc: + nif->skip(4); // Max passes + nif->skip(4); // Scale + break; + case BSLightingShaderType::ShaderType_MultiLayerParallax: + nif->skip(4); // Inner layer thickness + nif->skip(4); // Refraction scale + nif->skip(4); // Inner layer texture scale + nif->skip(4); // Environment map strength + break; + case BSLightingShaderType::ShaderType_SparkleSnow: + nif->skip(16); // Sparkle parameters + break; + case BSLightingShaderType::ShaderType_EyeEnvmap: + nif->skip(4); // Cube map scale + nif->skip(12); // Left eye cube map offset + nif->skip(12); // Right eye cube map offset + break; + default: + break; + } +} + +void BSLightingShaderProperty::post(NIFFile *nif) +{ + BSShaderProperty::post(nif); + mTextureSet.post(nif); +} + void NiFogProperty::read(NIFStream *nif) { Property::read(nif); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 9c76f6d6ec..a428d6a7e1 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -116,20 +116,21 @@ struct NiShadeProperty : public Property } }; -struct BSShaderProperty : public NiShadeProperty + +enum class BSShaderType : unsigned int { - enum BSShaderType - { - SHADER_TALL_GRASS = 0, - SHADER_DEFAULT = 1, - SHADER_SKY = 10, - SHADER_SKIN = 14, - SHADER_WATER = 17, - SHADER_LIGHTING30 = 29, - SHADER_TILE = 32, - SHADER_NOLIGHTING = 33 - }; + ShaderType_TallGrass = 0, + ShaderType_Default = 1, + ShaderType_Sky = 10, + ShaderType_Skin = 14, + ShaderType_Water = 17, + ShaderType_Lighting30 = 29, + ShaderType_Tile = 32, + ShaderType_NoLighting = 33 +}; +struct BSShaderProperty : public NiShadeProperty +{ unsigned int type{0u}, flags1{0u}, flags2{0u}; float envMapIntensity{0.f}; void read(NIFStream *nif) override; @@ -169,6 +170,44 @@ struct BSShaderNoLightingProperty : public BSShaderLightingProperty void read(NIFStream *nif) override; }; +enum class BSLightingShaderType : unsigned int +{ + ShaderType_Default = 0, + ShaderType_EnvMap = 1, + ShaderType_Glow = 2, + ShaderType_Parallax = 3, + ShaderType_FaceTint = 4, + ShaderType_SkinTint = 5, + ShaderType_HairTint = 6, + ShaderType_ParallaxOcc = 7, + ShaderType_MultitexLand = 8, + ShaderType_LODLand = 9, + ShaderType_Snow = 10, + ShaderType_MultiLayerParallax = 11, + ShaderType_TreeAnim = 12, + ShaderType_LODObjects = 13, + ShaderType_SparkleSnow = 14, + ShaderType_LODObjectsHD = 15, + ShaderType_EyeEnvmap = 16, + ShaderType_Cloud = 17, + ShaderType_LODNoise = 18, + ShaderType_MultitexLandLODBlend = 19, + ShaderType_Dismemberment = 20 +}; + +struct BSLightingShaderProperty : public BSShaderProperty +{ + BSShaderTextureSetPtr mTextureSet; + unsigned int mClamp{0u}; + float mAlpha; + float mGlossiness; + osg::Vec3f mEmissive, mSpecular; + float mEmissiveMult, mSpecStrength; + + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + struct NiDitherProperty : public Property { unsigned short flags; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 5fb50a05e5..b38186bd04 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -139,7 +139,8 @@ enum RecordType RC_bhkBoxShape, RC_bhkListShape, RC_bhkRigidBody, - RC_bhkRigidBodyT + RC_bhkRigidBodyT, + RC_BSLightingShaderProperty }; /// Base class for all records diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 2fa533518c..90046ecff1 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1721,28 +1721,64 @@ namespace NifOsg } } - const std::string& getNVShaderPrefix(unsigned int type) const + const std::string& getBSShaderPrefix(unsigned int type) const { - static const std::map mapping = - { - {Nif::BSShaderProperty::SHADER_TALL_GRASS, std::string()}, - {Nif::BSShaderProperty::SHADER_DEFAULT, "nv_default"}, - {Nif::BSShaderProperty::SHADER_SKY, std::string()}, - {Nif::BSShaderProperty::SHADER_SKIN, std::string()}, - {Nif::BSShaderProperty::SHADER_WATER, std::string()}, - {Nif::BSShaderProperty::SHADER_LIGHTING30, std::string()}, - {Nif::BSShaderProperty::SHADER_TILE, std::string()}, - {Nif::BSShaderProperty::SHADER_NOLIGHTING, "nv_nolighting"}, + static const std::unordered_map mapping = + { + {Nif::BSShaderType::ShaderType_TallGrass, std::string()}, + {Nif::BSShaderType::ShaderType_Default, "nv_default"}, + {Nif::BSShaderType::ShaderType_Sky, std::string()}, + {Nif::BSShaderType::ShaderType_Skin, std::string()}, + {Nif::BSShaderType::ShaderType_Water, std::string()}, + {Nif::BSShaderType::ShaderType_Lighting30, std::string()}, + {Nif::BSShaderType::ShaderType_Tile, std::string()}, + {Nif::BSShaderType::ShaderType_NoLighting, "nv_nolighting"}, }; - auto prefix = mapping.find(type); + auto prefix = mapping.find(static_cast(type)); if (prefix == mapping.end()) - Log(Debug::Warning) << "Unknown shader type " << type << " in " << mFilename; + Log(Debug::Warning) << "Unknown BSShaderType " << type << " in " << mFilename; else if (prefix->second.empty()) - Log(Debug::Warning) << "Unhandled shader type " << type << " in " << mFilename; + Log(Debug::Warning) << "Unhandled BSShaderType " << type << " in " << mFilename; else return prefix->second; - return mapping.at(Nif::BSShaderProperty::SHADER_DEFAULT); + return mapping.at(Nif::BSShaderType::ShaderType_Default); + } + + const std::string& getBSLightingShaderPrefix(unsigned int type) const + { + static const std::unordered_map mapping = + { + {Nif::BSLightingShaderType::ShaderType_Default, "nv_default"}, + {Nif::BSLightingShaderType::ShaderType_EnvMap, std::string()}, + {Nif::BSLightingShaderType::ShaderType_Glow, std::string()}, + {Nif::BSLightingShaderType::ShaderType_Parallax, std::string()}, + {Nif::BSLightingShaderType::ShaderType_FaceTint, std::string()}, + {Nif::BSLightingShaderType::ShaderType_HairTint, std::string()}, + {Nif::BSLightingShaderType::ShaderType_ParallaxOcc, std::string()}, + {Nif::BSLightingShaderType::ShaderType_MultitexLand, std::string()}, + {Nif::BSLightingShaderType::ShaderType_LODLand, std::string()}, + {Nif::BSLightingShaderType::ShaderType_Snow, std::string()}, + {Nif::BSLightingShaderType::ShaderType_MultiLayerParallax, std::string()}, + {Nif::BSLightingShaderType::ShaderType_TreeAnim, std::string()}, + {Nif::BSLightingShaderType::ShaderType_LODObjects, std::string()}, + {Nif::BSLightingShaderType::ShaderType_SparkleSnow, std::string()}, + {Nif::BSLightingShaderType::ShaderType_LODObjectsHD, std::string()}, + {Nif::BSLightingShaderType::ShaderType_EyeEnvmap, std::string()}, + {Nif::BSLightingShaderType::ShaderType_Cloud, std::string()}, + {Nif::BSLightingShaderType::ShaderType_LODNoise, std::string()}, + {Nif::BSLightingShaderType::ShaderType_MultitexLandLODBlend, std::string()}, + {Nif::BSLightingShaderType::ShaderType_Dismemberment, std::string()} + }; + auto prefix = mapping.find(static_cast(type)); + if (prefix == mapping.end()) + Log(Debug::Warning) << "Unknown BSLightingShaderType " << type << " in " << mFilename; + else if (prefix->second.empty()) + Log(Debug::Warning) << "Unhandled BSLightingShaderType " << type << " in " << mFilename; + else + return prefix->second; + + return mapping.at(Nif::BSLightingShaderType::ShaderType_Default); } void handleProperty(const Nif::Property *property, @@ -1834,7 +1870,7 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", getNVShaderPrefix(texprop->type)); + node->setUserValue("shaderPrefix", getBSShaderPrefix(texprop->type)); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->textureSet.empty()) @@ -1849,7 +1885,7 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", getNVShaderPrefix(texprop->type)); + node->setUserValue("shaderPrefix", getBSShaderPrefix(texprop->type)); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->filename.empty()) @@ -1887,6 +1923,18 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); break; } + case Nif::RC_BSLightingShaderProperty: + { + auto texprop = static_cast(property); + bool shaderRequired = true; + node->setUserValue("shaderPrefix", getBSLightingShaderPrefix(texprop->type)); + node->setUserValue("shaderRequired", shaderRequired); + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!texprop->mTextureSet.empty()) + handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + break; + } // unused by mw case Nif::RC_NiShadeProperty: case Nif::RC_NiDitherProperty: @@ -2035,6 +2083,17 @@ namespace NifOsg } break; } + case Nif::RC_BSLightingShaderProperty: + { + auto shaderprop = static_cast(property); + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f)); + mat->setShininess(osg::Material::FRONT_AND_BACK, shaderprop->mGlossiness); + emissiveMult = shaderprop->mEmissiveMult; + specStrength = shaderprop->mSpecStrength; + break; + } } } From 63ea57e9cf5ccaf5fd798fa4567e881aadf3b9a2 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 13 Nov 2021 23:36:33 +0300 Subject: [PATCH 1711/2859] Load NiSortAdjustNode and NiAccumulators --- components/nif/niffile.cpp | 3 +++ components/nif/node.hpp | 34 ++++++++++++++++++++++++++++++++++ components/nif/record.hpp | 4 +++- components/nif/recordptr.hpp | 2 ++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index f11b75d218..72071dd79e 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -153,6 +153,9 @@ static std::map makeFactory() factory["bhkRigidBody"] = {&construct , RC_bhkRigidBody }; factory["bhkRigidBodyT"] = {&construct , RC_bhkRigidBodyT }; factory["BSLightingShaderProperty"] = {&construct , RC_BSLightingShaderProperty }; + factory["NiSortAdjustNode"] = {&construct , RC_NiNode }; + factory["NiClusterAccumulator"] = {&construct , RC_NiClusterAccumulator }; + factory["NiAlphaAccumulator"] = {&construct , RC_NiAlphaAccumulator }; return factory; } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index c01ba9b663..7e5bcdb3be 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -427,5 +427,39 @@ struct NiLODNode : public NiSwitchNode } }; +// Abstract +struct NiAccumulator : Record +{ + void read(NIFStream *nif) override {} +}; + +// Node children sorters +struct NiClusterAccumulator : NiAccumulator {}; +struct NiAlphaAccumulator : NiClusterAccumulator {}; + +struct NiSortAdjustNode : NiNode +{ + enum SortingMode + { + SortingMode_Inherit, + SortingMode_Off, + SortingMode_Subsort + }; + + int mMode; + NiAccumulatorPtr mSubSorter; + void read(NIFStream *nif) override + { + NiNode::read(nif); + mMode = nif->getInt(); + if (nif->getVersion() <= NIFStream::generateVersion(20,0,0,3)) + mSubSorter.read(nif); + } + void post(NIFFile *nif) override + { + mSubSorter.post(nif); + } +}; + } // Namespace #endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index b38186bd04..937ce1d1e9 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -140,7 +140,9 @@ enum RecordType RC_bhkListShape, RC_bhkRigidBody, RC_bhkRigidBodyT, - RC_BSLightingShaderProperty + RC_BSLightingShaderProperty, + RC_NiClusterAccumulator, + RC_NiAlphaAccumulator }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index cc62d7b2e0..5ec00b0c92 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -153,6 +153,7 @@ struct bhkWorldObject; struct bhkShape; struct bhkSerializable; struct hkPackedNiTriStripsData; +struct NiAccumulator; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -184,6 +185,7 @@ using NiCollisionObjectPtr = RecordPtrT; using bhkWorldObjectPtr = RecordPtrT; using bhkShapePtr = RecordPtrT; using hkPackedNiTriStripsDataPtr = RecordPtrT; +using NiAccumulatorPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; From a62b22cd31c736cf83e85ec12475633557b440a7 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 13 Nov 2021 22:37:53 +0000 Subject: [PATCH 1712/2859] isolates groundcover content files (#3208) Specifications developed in PR #3206 require that groundcover content files must not be allowed to corrupt normal content files. With this PR we simply isolate our existing loading logic by instantiating a separate `ESMStore` for `Groundcover`. In addition, we remove some outdated workarounds. --- apps/openmw/mwrender/groundcover.cpp | 30 +++++------ apps/openmw/mwrender/groundcover.hpp | 16 ++++-- apps/openmw/mwrender/objectpaging.cpp | 1 - apps/openmw/mwrender/renderingmanager.cpp | 4 +- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 6 +-- apps/openmw/mwworld/worldimp.cpp | 66 +++++++++-------------- apps/openmw/mwworld/worldimp.hpp | 13 ++--- components/esm/cellref.cpp | 5 -- components/esm/cellref.hpp | 5 -- 10 files changed, 62 insertions(+), 86 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 3f4e592688..2998ee509a 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -20,15 +21,12 @@ namespace MWRender { - std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store) + std::string getGroundcoverModel(const std::string& id, const MWWorld::ESMStore& groundcoverStore, const MWWorld::ESMStore& store) { - switch (type) - { - case ESM::REC_STAT: - return store.get().searchStatic(id)->mModel; - default: - return std::string(); - } + const ESM::Static* stat = groundcoverStore.get().searchStatic(id); + if (!stat) + stat = store.get().searchStatic(id); + return stat ? stat->mModel : std::string(); } class InstancingVisitor : public osg::NodeVisitor @@ -155,11 +153,12 @@ namespace MWRender } } - Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance) + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::ESMStore& store) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) , mStateset(new osg::StateSet) + , mGroundcoverStore(store) { setViewDistance(viewDistance); // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties @@ -176,9 +175,13 @@ namespace MWRender mProgramTemplate->addBindAttribLocation("aRotation", 7); } + Groundcover::~Groundcover() + { + } + void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& worldStore = MWBase::Environment::get().getWorld()->getStore(); osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); DensityCalculator calculator(mDensity); @@ -188,7 +191,7 @@ namespace MWRender { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) { - const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); + const ESM::Cell* cell = mGroundcoverStore.get().searchStatic(cellX, cellY); if (!cell) continue; calculator.reset(); @@ -204,14 +207,11 @@ namespace MWRender while(cell->getNextRef(esm[index], ref, deleted)) { if (deleted) continue; - if (!ref.mRefNum.fromGroundcoverFile()) continue; if (!calculator.isInstanceEnabled()) continue; if (!isInChunkBorders(ref, minBound, maxBound)) continue; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - int type = store.findStatic(ref.mRefID); - std::string model = getGroundcoverModel(type, ref.mRefID, store); + std::string model = getGroundcoverModel(ref.mRefID, mGroundcoverStore, worldStore); if (model.empty()) continue; model = "meshes/" + model; diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index cd73e46eb0..ea0cfa9b62 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -4,7 +4,15 @@ #include #include #include -#include + +namespace MWWorld +{ + class ESMStore; +} +namespace osg +{ + class Program; +} namespace MWRender { @@ -12,8 +20,8 @@ namespace MWRender class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: - Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance); - ~Groundcover() = default; + Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::ESMStore& groundcoverStore); + ~Groundcover(); osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; @@ -35,6 +43,8 @@ namespace MWRender float mDensity; osg::ref_ptr mStateset; osg::ref_ptr mProgramTemplate; + /// @note mGroundcoverStore is separated from World's store because groundcover files must not be allowed to corrupt normal content files. + const MWWorld::ESMStore& mGroundcoverStore; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 90069c2d8d..756769bc7d 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -432,7 +432,6 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } - if (ref.mRefNum.fromGroundcoverFile()) continue; refs[ref.mRefNum] = std::move(ref); } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e0cd3f713a..2ba18378f9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -294,7 +294,7 @@ namespace MWRender RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& resourcePath, DetourNavigator::Navigator& navigator) + const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::ESMStore& groundcoverStore) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) @@ -450,7 +450,7 @@ namespace MWRender float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); - mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density, groundcoverDistance)); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density, groundcoverDistance, groundcoverStore)); static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b8d5d955c8..99d0bb5f5e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -95,7 +95,7 @@ namespace MWRender public: RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& resourcePath, DetourNavigator::Navigator& navigator); + const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::ESMStore& groundcoverStore); ~RenderingManager(); osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b2ac511509..0448d0e28a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -741,11 +741,7 @@ namespace MWWorld case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; - case ESM::REC_STAT: - { - if (ref.mRefNum.fromGroundcoverFile()) return; - mStatics.load(ref, deleted, store); break; - } + case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ed963b8b28..f434d059da 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -152,23 +152,16 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { - mEsm.resize(contentFiles.size() + groundcoverFiles.size()); + mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); - - GameContentLoader gameContentLoader(*listener); - EsmLoader esmLoader(mStore, mEsm, encoder, *listener); - - gameContentLoader.addLoader(".esm", &esmLoader); - gameContentLoader.addLoader(".esp", &esmLoader); - gameContentLoader.addLoader(".omwgame", &esmLoader); - gameContentLoader.addLoader(".omwaddon", &esmLoader); - gameContentLoader.addLoader(".project", &esmLoader); - - OMWScriptsLoader omwScriptsLoader(*listener, mStore); - gameContentLoader.addLoader(".omwscripts", &omwScriptsLoader); - - loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader); + + loadContentFiles(fileCollections, contentFiles, mStore, mEsm, encoder, listener); + if (!groundcoverFiles.empty()) + { + std::vector tempReaders (groundcoverFiles.size()); + loadContentFiles(fileCollections, groundcoverFiles, mGroundcoverStore, tempReaders, encoder, listener, false); + } listener->loadingOff(); @@ -176,10 +169,6 @@ namespace MWWorld if (mEsm[0].getFormat() == 0) ensureNeededRecords(); - // TODO: We can and should validate before we call loadContentFiles(). - // Currently we validate here to prevent merge conflicts with groundcover ESMStore fixes. - validateMasterFiles(mEsm); - mCurrentDate.reset(new DateTimeManager()); fillGlobalVariables(); @@ -202,7 +191,7 @@ namespace MWWorld mNavigator = DetourNavigator::makeNavigatorStub(); } - mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator)); + mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator, mGroundcoverStore)); mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); mRendering->preloadCommonAssets(); @@ -2959,9 +2948,22 @@ namespace MWWorld return mScriptsEnabled; } - void World::loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader) + void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener, bool validate) { + GameContentLoader gameContentLoader(*listener); + EsmLoader esmLoader(store, readers, encoder, *listener); + if (validate) + validateMasterFiles(readers); + + gameContentLoader.addLoader(".esm", &esmLoader); + gameContentLoader.addLoader(".esp", &esmLoader); + gameContentLoader.addLoader(".omwgame", &esmLoader); + gameContentLoader.addLoader(".omwaddon", &esmLoader); + gameContentLoader.addLoader(".project", &esmLoader); + + OMWScriptsLoader omwScriptsLoader(*listener, store); + gameContentLoader.addLoader(".omwscripts", &omwScriptsLoader); + int idx = 0; for (const std::string &file : content) { @@ -2969,7 +2971,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx); + gameContentLoader.load(col.getPath(file), idx); } else { @@ -2978,24 +2980,6 @@ namespace MWWorld } idx++; } - - ESM::GroundcoverIndex = idx; - - for (const std::string &file : groundcover) - { - boost::filesystem::path filename(file); - const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); - if (col.doesExist(file)) - { - contentLoader.load(col.getPath(file), idx); - } - else - { - std::string message = "Failed loading " + file + ": the groundcover file does not exist"; - throw std::runtime_error(message); - } - idx++; - } } bool World::startSpellCast(const Ptr &actor) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index afad359cfd..c8eebd9a8b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -80,6 +80,7 @@ namespace MWWorld std::vector mEsm; MWWorld::ESMStore mStore; + MWWorld::ESMStore mGroundcoverStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; @@ -163,14 +164,10 @@ namespace MWWorld void updateSkyDate(); - /** - * @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) - * @param fileCollections- Container which holds content file names and their paths - * @param content - Container which holds content file names - * @param contentLoader - - */ - void loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader); + // A helper method called automatically during World construction. + void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, + ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener, bool validateMasterFiles = true); + float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 7126459871..002a885d92 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -5,11 +5,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -namespace ESM -{ - int GroundcoverIndex = std::numeric_limits::max(); -} - void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index f6eff24cbf..0013329ccc 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -12,7 +12,6 @@ namespace ESM class ESMReader; const int UnbreakableLock = std::numeric_limits::max(); - extern int GroundcoverIndex; struct RefNum { @@ -27,10 +26,6 @@ namespace ESM inline bool isSet() const { return mIndex != 0 || mContentFile != -1; } inline void unset() { *this = {0, -1}; } - - // Note: this method should not be used for objects with invalid RefNum - // (for example, for objects from disabled plugins in savegames). - inline bool fromGroundcoverFile() const { return mContentFile >= GroundcoverIndex; } }; /* Cell reference. This represents ONE object (of many) inside the From 8fb0b5846ea2e059f225e6659b393584305bcb53 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 Nov 2021 00:22:44 +0000 Subject: [PATCH 1713/2859] Allow paths with trailing data, emmitting a warning --- apps/opencs/editor.cpp | 12 +-- apps/openmw/main.cpp | 12 +-- apps/openmw/options.cpp | 8 +- apps/openmw_test_suite/mwworld/test_store.cpp | 8 +- apps/openmw_test_suite/openmw/options.cpp | 102 ++++++++++-------- components/files/configurationmanager.cpp | 24 ++++- components/files/configurationmanager.hpp | 12 +++ 7 files changed, 113 insertions(+), 65 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index b437a36201..6294e5eb7c 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -88,11 +88,11 @@ std::pair > CS::Editor::readConfi boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) - ("data-local", boost::program_options::value()->default_value(Files::PathContainer::value_type(), "")) + ("data", boost::program_options::value()->default_value(Files::ReluctantPathContainer(), "data")->multitoken()->composing()) + ("data-local", boost::program_options::value()->default_value(Files::ReluctantPathContainer::value_type(), "")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) ("encoding", boost::program_options::value()->default_value("win1252")) - ("resources", boost::program_options::value()->default_value(boost::filesystem::path(), "resources")) + ("resources", boost::program_options::value()->default_value(Files::ReluctantPath(), "resources")) ("fallback-archive", boost::program_options::value>()-> default_value(std::vector(), "fallback-archive")->multitoken()) ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") @@ -112,7 +112,7 @@ std::pair > CS::Editor::readConfi mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str())); - mDocumentManager.setResourceDir (mResources = variables["resources"].as()); + mDocumentManager.setResourceDir (mResources = variables["resources"].as()); if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( @@ -122,10 +122,10 @@ std::pair > CS::Editor::readConfi Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = variables["data"].as(); + dataDirs = asPathContainer(variables["data"].as()); } - Files::PathContainer::value_type local(variables["data-local"].as()); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) dataLocal.push_back(local); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 7a3a21b149..50a7c3d844 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -56,7 +56,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat { cfgMgr.readConfiguration(variables, desc, true); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); getRawStdout() << v.describe() << std::endl; return false; } @@ -65,7 +65,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc); Files::mergeComposingVariables(variables, composingVariables, desc); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); Log(Debug::Info) << v.describe(); engine.setGrabMouse(!variables["no-grab"].as()); @@ -78,15 +78,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // directory settings engine.enableFSStrict(variables["fs-strict"].as()); - Files::PathContainer dataDirs(variables["data"].as()); + Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); - Files::PathContainer::value_type local(variables["data-local"].as()); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) dataDirs.push_back(local); cfgMgr.processPaths(dataDirs); - engine.setResourceDir(variables["resources"].as()); + engine.setResourceDir(variables["resources"].as()); engine.setDataDirs(dataDirs); // fallback archives @@ -141,7 +141,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setWarningsMode (variables["script-warn"].as()); engine.setScriptBlacklist (variables["script-blacklist"].as()); engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); - engine.setSaveGameFile (variables["load-savegame"].as().string()); + engine.setSaveGameFile (variables["load-savegame"].as().string()); // other settings Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp index dd94590f2a..a1b2102f08 100644 --- a/apps/openmw/options.cpp +++ b/apps/openmw/options.cpp @@ -25,16 +25,16 @@ namespace OpenMW ("replace", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") - ("data", bpo::value()->default_value(Files::PathContainer(), "data") + ("data", bpo::value()->default_value(Files::ReluctantPathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") - ("data-local", bpo::value()->default_value(Files::PathContainer::value_type(), ""), + ("data-local", bpo::value()->default_value(Files::ReluctantPathContainer::value_type(), ""), "set local data directory (highest priority)") ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") - ("resources", bpo::value()->default_value(boost::filesystem::path(), "resources"), + ("resources", bpo::value()->default_value(Files::ReluctantPath(), "resources"), "set resources directory") ("start", bpo::value()->default_value(""), @@ -77,7 +77,7 @@ namespace OpenMW ("script-blacklist-use", bpo::value()->implicit_value(true) ->default_value(true), "enable script blacklisting") - ("load-savegame", bpo::value()->default_value(boost::filesystem::path(), ""), + ("load-savegame", bpo::value()->default_value(Files::ReluctantPath(), ""), "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") ("skip-menu", bpo::value()->implicit_value(true) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 9aa5a6758f..f2f9459df0 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -58,10 +58,10 @@ struct ContentFileTest : public ::testing::Test boost::program_options::options_description desc("Allowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) + ("data", boost::program_options::value()->default_value(Files::ReluctantPathContainer(), "data")->multitoken()->composing()) ("content", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") - ("data-local", boost::program_options::value()->default_value(Files::PathContainer::value_type(), "")); + ("data-local", boost::program_options::value()->default_value(Files::ReluctantPathContainer::value_type(), "")); boost::program_options::notify(variables); @@ -69,10 +69,10 @@ struct ContentFileTest : public ::testing::Test Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = variables["data"].as(); + dataDirs = asPathContainer(variables["data"].as()); } - Files::PathContainer::value_type local(variables["data-local"].as()); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) { dataLocal.push_back(local); } diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index 7cf0df1066..bae324b134 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -40,7 +40,7 @@ namespace const std::array arguments {"openmw", "--load-savegame=save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_single_word_load_savegame_path) @@ -49,7 +49,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_multi_component_load_savegame_path) @@ -58,7 +58,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "/home/user/openmw/save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_windows_multi_component_load_savegame_path) @@ -67,18 +67,18 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"(C:\OpenMW\save.omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); } - /*TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_spaces) + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_spaces) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", "my save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "my"); -// EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); - }*/ + EXPECT_EQ(variables["load-savegame"].as().string(), "my"); +// EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); + } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_octothorpe) { @@ -86,7 +86,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "my#save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "my#save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "my#save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_at_sign) @@ -95,7 +95,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "my@save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "my@save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "my@save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_quote) @@ -104,18 +104,18 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"(my"save.omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save.omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save.omwsave)"); } -/* TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path) + TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); const std::array arguments {"openmw", "--load-savegame", R"("save".omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(save)"); -// EXPECT_EQ(variables["load-savegame"].as().string(), R"("save".omwsave)"); - }*/ + EXPECT_EQ(variables["load-savegame"].as().string(), R"(save)"); +// EXPECT_EQ(variables["load-savegame"].as().string(), R"("save".omwsave)"); + } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) { @@ -123,7 +123,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"("save&".omwsave")"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_ampersand) @@ -132,7 +132,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"("save.omwsave&&")"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_ampersand) @@ -141,7 +141,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "save&.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_multiple_quotes) @@ -150,7 +150,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"(my"save".omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save".omwsave)"); } TEST(OpenMWOptionsFromArguments, should_compose_data) @@ -159,7 +159,7 @@ namespace const std::array arguments {"openmw", "--data", "1", "--data", "2"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromArguments, should_compose_data_from_single_flag) @@ -168,7 +168,7 @@ namespace const std::array arguments {"openmw", "--data", "1", "2"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromArguments, should_throw_on_multiple_load_savegame) @@ -189,7 +189,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", pathArgument.c_str()}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), path); + EXPECT_EQ(variables["load-savegame"].as().string(), path); } INSTANTIATE_TEST_SUITE_P( @@ -204,7 +204,7 @@ namespace std::istringstream stream("load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path) @@ -213,18 +213,18 @@ namespace std::istringstream stream(R"(load-savegame="save.omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } -/* TEST(OpenMWOptionsFromConfig, should_strip_outer_quotes_from_load_savegame_path) + TEST(OpenMWOptionsFromConfig, should_strip_outer_quotes_from_load_savegame_path) { bpo::options_description description = makeOptionsDescription(); std::istringstream stream(R"(load-savegame=""save".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), ""); -// EXPECT_EQ(variables["load-savegame"].as().string(), R"(""save".omwsave")"); - }*/ + EXPECT_EQ(variables["load-savegame"].as().string(), ""); +// EXPECT_EQ(variables["load-savegame"].as().string(), R"(""save".omwsave")"); + } TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path_with_space) { @@ -232,7 +232,7 @@ namespace std::istringstream stream(R"(load-savegame="my save.omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_octothorpe) @@ -241,7 +241,7 @@ namespace std::istringstream stream("load-savegame=save#.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save#.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save#.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_at_sign) @@ -250,7 +250,7 @@ namespace std::istringstream stream("load-savegame=save@.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save@.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save@.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_quote) @@ -259,7 +259,25 @@ namespace std::istringstream stream(R"(load-savegame=save".omwsave)"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); + } + + TEST(OpenMWOptionsFromConfig, should_support_confusing_savegame_path_with_lots_going_on) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="one &"two"three".omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(one "two)"); + } + + TEST(OpenMWOptionsFromConfig, should_support_confusing_savegame_path_with_even_more_going_on) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="one &"two"three ".omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(one "two)"); } TEST(OpenMWOptionsFromConfig, should_ignore_commented_option) @@ -268,7 +286,7 @@ namespace std::istringstream stream("#load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), ""); + EXPECT_EQ(variables["load-savegame"].as().string(), ""); } TEST(OpenMWOptionsFromConfig, should_ignore_whitespace_prefixed_commented_option) @@ -277,7 +295,7 @@ namespace std::istringstream stream(" \t#load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), ""); + EXPECT_EQ(variables["load-savegame"].as().string(), ""); } TEST(OpenMWOptionsFromConfig, should_support_whitespace_around_option) @@ -286,7 +304,7 @@ namespace std::istringstream stream(" load-savegame = save.omwsave "); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_throw_on_multiple_load_savegame) @@ -303,7 +321,7 @@ namespace std::istringstream stream("load-savegame=/home/user/openmw/save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_windows_multi_component_load_savegame_path) @@ -312,7 +330,7 @@ namespace std::istringstream stream(R"(load-savegame=C:\OpenMW\save.omwsave)"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); } TEST(OpenMWOptionsFromConfig, should_compose_data) @@ -321,7 +339,7 @@ namespace std::istringstream stream("data=1\ndata=2"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) @@ -330,7 +348,7 @@ namespace std::istringstream stream(R"(load-savegame="save&".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_ampersand) @@ -339,7 +357,7 @@ namespace std::istringstream stream(R"(load-savegame="save.omwsave&&")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); } TEST(OpenMWOptionsFromConfig, should_support_load_savegame_path_with_ampersand) @@ -348,7 +366,7 @@ namespace std::istringstream stream("load-savegame=save&.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); } struct OpenMWOptionsFromConfigStrings : TestWithParam {}; @@ -360,7 +378,7 @@ namespace std::istringstream stream("load-savegame=\"" + path + "\""); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), path); + EXPECT_EQ(variables["load-savegame"].as().string(), path); } INSTANTIATE_TEST_SUITE_P( diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 23c25fd413..b04b211967 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -138,10 +138,10 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost boost::any& firstValue = firstPosition->second.value(); const boost::any& secondValue = second[name].value(); - if (firstValue.type() == typeid(Files::PathContainer)) + if (firstValue.type() == typeid(Files::ReluctantPathContainer)) { - auto& firstPathContainer = boost::any_cast(firstValue); - const auto& secondPathContainer = boost::any_cast(secondValue); + auto& firstPathContainer = boost::any_cast(firstValue); + const auto& secondPathContainer = boost::any_cast(secondValue); firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); } @@ -317,4 +317,22 @@ void parseConfig(std::istream& stream, boost::program_options::variables_map& va ); } +std::istream& operator>> (std::istream& istream, ReluctantPath& reluctantPath) +{ + // Read from stream using boost::filesystem::path rules, then discard anything remaining. + // This prevents boost::program_options getting upset that we've not consumed the whole stream. + istream >> static_cast(reluctantPath); + if (istream && !istream.eof() && istream.peek() != EOF) + { + std::string remainder(std::istreambuf_iterator(istream), {}); + Log(Debug::Warning) << "Trailing data in path setting. Used '" << reluctantPath.string() << "' but '" << remainder << "' remained"; + } + return istream; +} + +PathContainer asPathContainer(const ReluctantPathContainer& reluctantPathContainer) +{ + return PathContainer(reluctantPathContainer.begin(), reluctantPathContainer.end()); +} + } /* namespace Cfg */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 99e65a7658..8f3e5f938e 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -77,6 +77,18 @@ void parseArgs(int argc, const char* const argv[], boost::program_options::varia void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, boost::program_options::options_description& description); +class ReluctantPath : public boost::filesystem::path +{ +public: + operator boost::filesystem::path() { return *this; } +}; + +std::istream& operator>> (std::istream& istream, ReluctantPath& reluctantPath); + +typedef std::vector ReluctantPathContainer; + +PathContainer asPathContainer(const ReluctantPathContainer& reluctantPathContainer); + } /* namespace Cfg */ #endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */ From dce4cceb39ee70c0df7880047c67a5ad999e3a8f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 5 Nov 2021 11:06:19 +0400 Subject: [PATCH 1714/2859] Support deleted CellRefs in groundcover (bug 6276) --- CHANGELOG.md | 1 + apps/openmw/mwrender/groundcover.cpp | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc23c39d92..0dea848d7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -235,6 +235,7 @@ Bug #6043: Actor can have torch missing when torch animation is played Bug #6047: Mouse bindings can be triggered during save loading Bug #6136: Game freezes when NPCs try to open doors that are about to be closed + Bug #6276: Deleted groundcover instances are not deleted in game Bug #6294: Game crashes with empty pathgrid Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 2998ee509a..1cabf564e2 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -181,6 +181,8 @@ namespace MWRender void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) { + if (mDensity <=0.f) return; + const MWWorld::ESMStore& worldStore = MWBase::Environment::get().getWorld()->getStore(); osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); @@ -195,6 +197,7 @@ namespace MWRender if (!cell) continue; calculator.reset(); + std::map refs; for (size_t i=0; imContextList.size(); ++i) { unsigned int index = cell->mContextList[i].index; @@ -206,18 +209,21 @@ namespace MWRender bool deleted = false; while(cell->getNextRef(esm[index], ref, deleted)) { - if (deleted) continue; - - if (!calculator.isInstanceEnabled()) continue; - if (!isInChunkBorders(ref, minBound, maxBound)) continue; - - std::string model = getGroundcoverModel(ref.mRefID, mGroundcoverStore, worldStore); - if (model.empty()) continue; - model = "meshes/" + model; + if (!deleted && refs.find(ref.mRefNum) == refs.end() && !calculator.isInstanceEnabled()) deleted = true; + if (!deleted && !isInChunkBorders(ref, minBound, maxBound)) deleted = true; - instances[model].emplace_back(std::move(ref)); + if (deleted) { refs.erase(ref.mRefNum); continue; } + refs[ref.mRefNum] = std::move(ref); } } + + for (auto& pair : refs) + { + ESM::CellRef& ref = pair.second; + const std::string& model = getGroundcoverModel(ref.mRefID, mGroundcoverStore, worldStore); + if (!model.empty()) + instances["meshes\\" + model].emplace_back(std::move(ref)); + } } } } From cca6029088397ec54e9b2fc0e86dfefcf56d1250 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Nov 2021 11:08:05 +0400 Subject: [PATCH 1715/2859] Add missing changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dea848d7b..0d61907f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -235,6 +235,7 @@ Bug #6043: Actor can have torch missing when torch animation is played Bug #6047: Mouse bindings can be triggered during save loading Bug #6136: Game freezes when NPCs try to open doors that are about to be closed + Bug #6142: Groundcover plugins change cells flags Bug #6276: Deleted groundcover instances are not deleted in game Bug #6294: Game crashes with empty pathgrid Feature #390: 3rd person look "over the shoulder" From 8f48a1f030f35929cc277212f093889d80906750 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 9 Nov 2021 21:45:16 +0100 Subject: [PATCH 1716/2859] Handle non-ASCII characters while saving without triggering an assertion --- CHANGELOG.md | 1 + apps/openmw/mwstate/character.cpp | 12 ++++++++---- components/misc/utf8stream.hpp | 5 +++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc23c39d92..fdb30ab110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ Bug #6363: Some scripts in Morrowland fail to work Bug #6376: Creatures should be able to use torches Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation + Bug #6396: Inputting certain Unicode characters triggers an assertion Bug #6416: Morphs are applied to the wrong target Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 59ddd2cd10..7216e45df6 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -8,6 +8,8 @@ #include #include +#include + bool MWState::operator< (const Slot& left, const Slot& right) { return left.mTimeStamp 0 && c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters + stream << static_cast(c); else - stream << "_"; + stream << '_'; } const std::string ext = ".omwsave"; diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 9dc8aa8208..efa3e4939a 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -30,6 +30,11 @@ public: { } + Utf8Stream (const std::string& str) : + Utf8Stream (reinterpret_cast(str.c_str()), reinterpret_cast(str.c_str() + str.size())) + { + } + bool eof () const { return cur == end; From c01ba41aa6a491f26512c1c541354a6e7510d6bb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 10 Nov 2021 16:54:52 +0100 Subject: [PATCH 1717/2859] Handle character directories like save names --- apps/openmw/mwstate/character.cpp | 2 +- apps/openmw/mwstate/charactermanager.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 7216e45df6..52696de104 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -58,7 +58,7 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) while(!description.eof()) { auto c = description.consume(); - if(c > 0 && c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters + if(c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters stream << static_cast(c); else stream << '_'; diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 027a4f38a4..301f33c5df 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -5,6 +5,8 @@ #include +#include + MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, const std::vector& contentFiles) : mPath (saves), mCurrent (nullptr), mGame (getFirstGameFile(contentFiles)) @@ -57,12 +59,14 @@ MWState::Character* MWState::CharacterManager::createCharacter(const std::string std::ostringstream stream; // The character name is user-supplied, so we need to escape the path - for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) + Utf8Stream nameStream(name); + while(!nameStream.eof()) { - if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters - stream << *it; + auto c = nameStream.consume(); + if(c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters + stream << static_cast(c); else - stream << "_"; + stream << '_'; } boost::filesystem::path path = mPath / stream.str(); From 751e8cf76b9d2c792f595088337618af61c1ad04 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 10 Nov 2021 21:25:16 +0100 Subject: [PATCH 1718/2859] Do a bounds check before calling functions defined in cctype --- apps/niftest/niftest.cpp | 14 +++----------- apps/opencs/model/filter/parser.cpp | 21 +++++++++++++++++---- apps/opencs/model/world/idtable.cpp | 7 +++---- apps/openmw/mwinput/keyboardmanager.cpp | 2 +- components/files/escape.hpp | 2 +- components/interpreter/defines.cpp | 3 +-- components/lua/configuration.cpp | 15 ++++++++++----- components/misc/stringops.hpp | 15 +++++++-------- components/settings/parser.cpp | 2 +- 9 files changed, 44 insertions(+), 37 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index cb6205ef5c..c42df4a423 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -2,8 +2,8 @@ #include #include -#include +#include #include #include #include @@ -18,18 +18,10 @@ namespace bpo = boost::program_options; namespace bfs = boost::filesystem; ///See if the file has the named extension -bool hasExtension(std::string filename, std::string extensionToFind) +bool hasExtension(std::string filename, std::string extensionToFind) { std::string extension = filename.substr(filename.find_last_of('.')+1); - - //Convert strings to lower case for comparison - std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); - std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower); - - if(extension == extensionToFind) - return true; - else - return false; + return Misc::StringUtils::ciEqual(extension, extensionToFind); } ///See if the file has the "nif" extension. diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index d363b4849d..b8f125e237 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -17,6 +17,19 @@ #include "textnode.hpp" #include "valuenode.hpp" +namespace +{ + bool isAlpha(char c) + { + return c >= 0 && c <= 255 && std::isalpha(c); + } + + bool isDigit(char c) + { + return c >= 0 && c <= 255 && std::isdigit(c); + } +} + namespace CSMFilter { struct Token @@ -103,7 +116,7 @@ CSMFilter::Token CSMFilter::Parser::getStringToken() { char c = mInput[mIndex]; - if (std::isalpha (c) || c==':' || c=='_' || (!string.empty() && std::isdigit (c)) || c=='"' || + if (isAlpha(c) || c==':' || c=='_' || (!string.empty() && isDigit(c)) || c=='"' || (!string.empty() && string[0]=='"')) string += c; else @@ -150,7 +163,7 @@ CSMFilter::Token CSMFilter::Parser::getNumberToken() { char c = mInput[mIndex]; - if (std::isdigit (c)) + if (isDigit(c)) { string += c; hasDigit = true; @@ -225,10 +238,10 @@ CSMFilter::Token CSMFilter::Parser::getNextToken() case '!': ++mIndex; return Token (Token::Type_OneShot); } - if (c=='"' || c=='_' || std::isalpha (c) || c==':') + if (c=='"' || c=='_' || isAlpha(c) || c==':') return getStringToken(); - if (c=='-' || c=='.' || std::isdigit (c)) + if (c=='-' || c=='.' || isDigit(c)) return getNumberToken(); error(); diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 5b4a9b31bc..215f42133d 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "collectionbase.hpp" #include "columnbase.hpp" @@ -354,8 +355,7 @@ CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::import for (int i = 0; i < idCollection()->getSize(); ++i) { auto& record = static_cast&>(idCollection()->getRecord(i)); - std::string texture = record.get().mTexture; - std::transform(texture.begin(), texture.end(), texture.begin(), tolower); + std::string texture = Misc::StringUtils::lowerCase(record.get().mTexture); if (record.isModified()) reverseLookupMap.emplace(texture, idCollection()->getId(i)); } @@ -376,8 +376,7 @@ CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::import // Look for a pre-existing record auto& record = static_cast&>(idCollection()->getRecord(oldRow)); - std::string texture = record.get().mTexture; - std::transform(texture.begin(), texture.end(), texture.begin(), tolower); + std::string texture = Misc::StringUtils::lowerCase(record.get().mTexture); auto searchIt = reverseLookupMap.find(texture); if (searchIt != reverseLookupMap.end()) { diff --git a/apps/openmw/mwinput/keyboardmanager.cpp b/apps/openmw/mwinput/keyboardmanager.cpp index b8019b12ba..c5a665e5c6 100644 --- a/apps/openmw/mwinput/keyboardmanager.cpp +++ b/apps/openmw/mwinput/keyboardmanager.cpp @@ -42,7 +42,7 @@ namespace MWInput bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable (!(SDLK_SCANCODE_MASK & arg.keysym.sym) && - (std::isprint(arg.keysym.sym) || + (arg.keysym.sym >= 0 && arg.keysym.sym <= 255 && std::isprint(arg.keysym.sym) || // Don't trust isprint for symbols outside the extended ASCII range (kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff))); if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState()) diff --git a/components/files/escape.hpp b/components/files/escape.hpp index d01bd8d980..310042072d 100644 --- a/components/files/escape.hpp +++ b/components/files/escape.hpp @@ -84,7 +84,7 @@ namespace Files { mNext.push(character); } - if (!mSeenNonWhitespace && !isspace(character)) + if (!mSeenNonWhitespace && !(character >= 0 && character <= 255 && isspace(character))) mSeenNonWhitespace = true; } int retval = mNext.front(); diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 48d2ceaa45..164e7b2126 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -172,8 +172,7 @@ namespace Interpreter{ for(unsigned int j = 0; j < globals.size(); j++){ if(globals[j].length() > temp.length()){ // Just in case there's a global with a huuuge name - temp = text.substr(i+1, globals[j].length()); - transform(temp.begin(), temp.end(), temp.begin(), ::tolower); + temp = Misc::StringUtils::lowerCase(text.substr(i+1, globals[j].length())); } found = check(temp, globals[j], &i, &start); diff --git a/components/lua/configuration.cpp b/components/lua/configuration.cpp index 4598ed2508..d059d1716f 100644 --- a/components/lua/configuration.cpp +++ b/components/lua/configuration.cpp @@ -30,6 +30,11 @@ namespace LuaUtil {"POTION", ESM::LuaScriptCfg::sPotion}, {"WEAPON", ESM::LuaScriptCfg::sWeapon}, }; + + bool isSpace(int c) + { + return c >= 0 && c <= 255 && std::isspace(c); + } } const std::vector ScriptsConfiguration::sEmpty; @@ -101,11 +106,11 @@ namespace LuaUtil if (!line.empty() && line.back() == '\r') line = line.substr(0, line.size() - 1); - while (!line.empty() && std::isspace(line[0])) + while (!line.empty() && isSpace(line[0])) line = line.substr(1); if (line.empty() || line[0] == '#') // Skip empty lines and comments continue; - while (!line.empty() && std::isspace(line.back())) + while (!line.empty() && isSpace(line.back())) line = line.substr(0, line.size() - 1); if (!Misc::StringUtils::ciEndsWith(line, ".lua")) @@ -118,7 +123,7 @@ namespace LuaUtil throw std::runtime_error(Misc::StringUtils::format("No flags found in: %s", std::string(line))); std::string_view flagsStr = line.substr(0, semicolonPos); std::string_view scriptPath = line.substr(semicolonPos + 1); - while (std::isspace(scriptPath[0])) + while (isSpace(scriptPath[0])) scriptPath = scriptPath.substr(1); // Parse flags @@ -126,10 +131,10 @@ namespace LuaUtil size_t flagsPos = 0; while (true) { - while (flagsPos < flagsStr.size() && (std::isspace(flagsStr[flagsPos]) || flagsStr[flagsPos] == ',')) + while (flagsPos < flagsStr.size() && (isSpace(flagsStr[flagsPos]) || flagsStr[flagsPos] == ',')) flagsPos++; size_t startPos = flagsPos; - while (flagsPos < flagsStr.size() && !std::isspace(flagsStr[flagsPos]) && flagsStr[flagsPos] != ',') + while (flagsPos < flagsStr.size() && !isSpace(flagsStr[flagsPos]) && flagsStr[flagsPos] != ',') flagsPos++; if (startPos == flagsPos) break; diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 12633db826..a0ec9f62dd 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -184,17 +184,16 @@ public: static inline void trim(std::string &s) { - // left trim - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) + const auto notSpace = [](int ch) { - return !std::isspace(ch); - })); + // TODO Do we care about multibyte whitespace? + return ch < 0 || ch > 255 || !std::isspace(ch); + }; + // left trim + s.erase(s.begin(), std::find_if(s.begin(), s.end(), notSpace)); // right trim - s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) - { - return !std::isspace(ch); - }).base(), s.end()); + s.erase(std::find_if(s.rbegin(), s.rend(), notSpace).base(), s.end()); } template diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index f2419dfdd6..f36f190ee2 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -311,7 +311,7 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con bool Settings::SettingsFileParser::skipWhiteSpace(size_t& i, std::string& str) { - while (i < str.size() && std::isspace(str[i], std::locale::classic())) + while (i < str.size() && str[i] >= 0 && str[i] <= 255 && std::isspace(str[i], std::locale::classic())) { ++i; } From c6470f33d363e5139ffb78c1a6e64c2ed09e0a44 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 10 Nov 2021 21:37:29 +0100 Subject: [PATCH 1719/2859] Use string_view --- components/misc/utf8stream.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index efa3e4939a..8435816c67 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -3,6 +3,7 @@ #include #include +#include #include class Utf8Stream @@ -30,8 +31,8 @@ public: { } - Utf8Stream (const std::string& str) : - Utf8Stream (reinterpret_cast(str.c_str()), reinterpret_cast(str.c_str() + str.size())) + Utf8Stream (std::string_view str) : + Utf8Stream (reinterpret_cast(str.data()), reinterpret_cast(str.data() + str.size())) { } From f23bd51175a432c3fe1c156f3118bae799978701 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 10 Nov 2021 22:53:25 +0100 Subject: [PATCH 1720/2859] Don't check bounds when the type is unambiguously a char --- apps/opencs/model/filter/parser.cpp | 21 ++++----------------- components/lua/configuration.cpp | 15 +++++---------- components/misc/stringops.hpp | 4 ++-- components/settings/parser.cpp | 2 +- 4 files changed, 12 insertions(+), 30 deletions(-) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index b8f125e237..d363b4849d 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -17,19 +17,6 @@ #include "textnode.hpp" #include "valuenode.hpp" -namespace -{ - bool isAlpha(char c) - { - return c >= 0 && c <= 255 && std::isalpha(c); - } - - bool isDigit(char c) - { - return c >= 0 && c <= 255 && std::isdigit(c); - } -} - namespace CSMFilter { struct Token @@ -116,7 +103,7 @@ CSMFilter::Token CSMFilter::Parser::getStringToken() { char c = mInput[mIndex]; - if (isAlpha(c) || c==':' || c=='_' || (!string.empty() && isDigit(c)) || c=='"' || + if (std::isalpha (c) || c==':' || c=='_' || (!string.empty() && std::isdigit (c)) || c=='"' || (!string.empty() && string[0]=='"')) string += c; else @@ -163,7 +150,7 @@ CSMFilter::Token CSMFilter::Parser::getNumberToken() { char c = mInput[mIndex]; - if (isDigit(c)) + if (std::isdigit (c)) { string += c; hasDigit = true; @@ -238,10 +225,10 @@ CSMFilter::Token CSMFilter::Parser::getNextToken() case '!': ++mIndex; return Token (Token::Type_OneShot); } - if (c=='"' || c=='_' || isAlpha(c) || c==':') + if (c=='"' || c=='_' || std::isalpha (c) || c==':') return getStringToken(); - if (c=='-' || c=='.' || isDigit(c)) + if (c=='-' || c=='.' || std::isdigit (c)) return getNumberToken(); error(); diff --git a/components/lua/configuration.cpp b/components/lua/configuration.cpp index d059d1716f..4598ed2508 100644 --- a/components/lua/configuration.cpp +++ b/components/lua/configuration.cpp @@ -30,11 +30,6 @@ namespace LuaUtil {"POTION", ESM::LuaScriptCfg::sPotion}, {"WEAPON", ESM::LuaScriptCfg::sWeapon}, }; - - bool isSpace(int c) - { - return c >= 0 && c <= 255 && std::isspace(c); - } } const std::vector ScriptsConfiguration::sEmpty; @@ -106,11 +101,11 @@ namespace LuaUtil if (!line.empty() && line.back() == '\r') line = line.substr(0, line.size() - 1); - while (!line.empty() && isSpace(line[0])) + while (!line.empty() && std::isspace(line[0])) line = line.substr(1); if (line.empty() || line[0] == '#') // Skip empty lines and comments continue; - while (!line.empty() && isSpace(line.back())) + while (!line.empty() && std::isspace(line.back())) line = line.substr(0, line.size() - 1); if (!Misc::StringUtils::ciEndsWith(line, ".lua")) @@ -123,7 +118,7 @@ namespace LuaUtil throw std::runtime_error(Misc::StringUtils::format("No flags found in: %s", std::string(line))); std::string_view flagsStr = line.substr(0, semicolonPos); std::string_view scriptPath = line.substr(semicolonPos + 1); - while (isSpace(scriptPath[0])) + while (std::isspace(scriptPath[0])) scriptPath = scriptPath.substr(1); // Parse flags @@ -131,10 +126,10 @@ namespace LuaUtil size_t flagsPos = 0; while (true) { - while (flagsPos < flagsStr.size() && (isSpace(flagsStr[flagsPos]) || flagsStr[flagsPos] == ',')) + while (flagsPos < flagsStr.size() && (std::isspace(flagsStr[flagsPos]) || flagsStr[flagsPos] == ',')) flagsPos++; size_t startPos = flagsPos; - while (flagsPos < flagsStr.size() && !isSpace(flagsStr[flagsPos]) && flagsStr[flagsPos] != ',') + while (flagsPos < flagsStr.size() && !std::isspace(flagsStr[flagsPos]) && flagsStr[flagsPos] != ',') flagsPos++; if (startPos == flagsPos) break; diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index a0ec9f62dd..c8d236a9d0 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -184,10 +184,10 @@ public: static inline void trim(std::string &s) { - const auto notSpace = [](int ch) + const auto notSpace = [](char ch) { // TODO Do we care about multibyte whitespace? - return ch < 0 || ch > 255 || !std::isspace(ch); + return !std::isspace(ch); }; // left trim s.erase(s.begin(), std::find_if(s.begin(), s.end(), notSpace)); diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index f36f190ee2..f2419dfdd6 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -311,7 +311,7 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con bool Settings::SettingsFileParser::skipWhiteSpace(size_t& i, std::string& str) { - while (i < str.size() && str[i] >= 0 && str[i] <= 255 && std::isspace(str[i], std::locale::classic())) + while (i < str.size() && std::isspace(str[i], std::locale::classic())) { ++i; } From 6daefe3ddc0a94c507efd52b5769e457af07c870 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 11 Nov 2021 18:14:10 +0100 Subject: [PATCH 1721/2859] Cast to the unsigned char expected by cctype functions --- apps/opencs/model/filter/parser.cpp | 21 +++++++++++++++++---- apps/openmw/mwinput/keyboardmanager.cpp | 4 ++-- components/lua/configuration.cpp | 15 ++++++++++----- components/misc/stringops.hpp | 2 +- components/settings/parser.cpp | 2 +- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index d363b4849d..6f185d60fa 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -17,6 +17,19 @@ #include "textnode.hpp" #include "valuenode.hpp" +namespace +{ + bool isAlpha(char c) + { + return std::isalpha(static_cast(c)); + } + + bool isDigit(char c) + { + return std::isdigit(static_cast(c)); + } +} + namespace CSMFilter { struct Token @@ -103,7 +116,7 @@ CSMFilter::Token CSMFilter::Parser::getStringToken() { char c = mInput[mIndex]; - if (std::isalpha (c) || c==':' || c=='_' || (!string.empty() && std::isdigit (c)) || c=='"' || + if (isAlpha(c) || c==':' || c=='_' || (!string.empty() && isDigit(c)) || c=='"' || (!string.empty() && string[0]=='"')) string += c; else @@ -150,7 +163,7 @@ CSMFilter::Token CSMFilter::Parser::getNumberToken() { char c = mInput[mIndex]; - if (std::isdigit (c)) + if (isDigit(c)) { string += c; hasDigit = true; @@ -225,10 +238,10 @@ CSMFilter::Token CSMFilter::Parser::getNextToken() case '!': ++mIndex; return Token (Token::Type_OneShot); } - if (c=='"' || c=='_' || std::isalpha (c) || c==':') + if (c=='"' || c=='_' || isAlpha(c) || c==':') return getStringToken(); - if (c=='-' || c=='.' || std::isdigit (c)) + if (c=='-' || c=='.' || isDigit(c)) return getNumberToken(); error(); diff --git a/apps/openmw/mwinput/keyboardmanager.cpp b/apps/openmw/mwinput/keyboardmanager.cpp index c5a665e5c6..b121b665e7 100644 --- a/apps/openmw/mwinput/keyboardmanager.cpp +++ b/apps/openmw/mwinput/keyboardmanager.cpp @@ -42,9 +42,9 @@ namespace MWInput bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable (!(SDLK_SCANCODE_MASK & arg.keysym.sym) && - (arg.keysym.sym >= 0 && arg.keysym.sym <= 255 && std::isprint(arg.keysym.sym) || // Don't trust isprint for symbols outside the extended ASCII range - (kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff))); + ((kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff) || + (arg.keysym.sym >= 0 && arg.keysym.sym <= 255 && std::isprint(arg.keysym.sym)))); if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState()) { if (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat)) diff --git a/components/lua/configuration.cpp b/components/lua/configuration.cpp index 4598ed2508..172d958029 100644 --- a/components/lua/configuration.cpp +++ b/components/lua/configuration.cpp @@ -30,6 +30,11 @@ namespace LuaUtil {"POTION", ESM::LuaScriptCfg::sPotion}, {"WEAPON", ESM::LuaScriptCfg::sWeapon}, }; + + bool isSpace(char c) + { + return std::isspace(static_cast(c)); + } } const std::vector ScriptsConfiguration::sEmpty; @@ -101,11 +106,11 @@ namespace LuaUtil if (!line.empty() && line.back() == '\r') line = line.substr(0, line.size() - 1); - while (!line.empty() && std::isspace(line[0])) + while (!line.empty() && isSpace(line[0])) line = line.substr(1); if (line.empty() || line[0] == '#') // Skip empty lines and comments continue; - while (!line.empty() && std::isspace(line.back())) + while (!line.empty() && isSpace(line.back())) line = line.substr(0, line.size() - 1); if (!Misc::StringUtils::ciEndsWith(line, ".lua")) @@ -118,7 +123,7 @@ namespace LuaUtil throw std::runtime_error(Misc::StringUtils::format("No flags found in: %s", std::string(line))); std::string_view flagsStr = line.substr(0, semicolonPos); std::string_view scriptPath = line.substr(semicolonPos + 1); - while (std::isspace(scriptPath[0])) + while (isSpace(scriptPath[0])) scriptPath = scriptPath.substr(1); // Parse flags @@ -126,10 +131,10 @@ namespace LuaUtil size_t flagsPos = 0; while (true) { - while (flagsPos < flagsStr.size() && (std::isspace(flagsStr[flagsPos]) || flagsStr[flagsPos] == ',')) + while (flagsPos < flagsStr.size() && (isSpace(flagsStr[flagsPos]) || flagsStr[flagsPos] == ',')) flagsPos++; size_t startPos = flagsPos; - while (flagsPos < flagsStr.size() && !std::isspace(flagsStr[flagsPos]) && flagsStr[flagsPos] != ',') + while (flagsPos < flagsStr.size() && !isSpace(flagsStr[flagsPos]) && flagsStr[flagsPos] != ',') flagsPos++; if (startPos == flagsPos) break; diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index c8d236a9d0..fe4be00006 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -187,7 +187,7 @@ public: const auto notSpace = [](char ch) { // TODO Do we care about multibyte whitespace? - return !std::isspace(ch); + return !std::isspace(static_cast(ch)); }; // left trim s.erase(s.begin(), std::find_if(s.begin(), s.end(), notSpace)); diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index f2419dfdd6..8d387ab9c7 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -311,7 +311,7 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con bool Settings::SettingsFileParser::skipWhiteSpace(size_t& i, std::string& str) { - while (i < str.size() && std::isspace(str[i], std::locale::classic())) + while (i < str.size() && std::isspace(static_cast(str[i]), std::locale::classic())) { ++i; } From c5ba733855c698e1d07768998c7490135b4966a4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 11 Nov 2021 21:16:25 +0100 Subject: [PATCH 1722/2859] Unbreak the unit test in gcc and clang --- components/settings/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index 8d387ab9c7..f2419dfdd6 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -311,7 +311,7 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con bool Settings::SettingsFileParser::skipWhiteSpace(size_t& i, std::string& str) { - while (i < str.size() && std::isspace(static_cast(str[i]), std::locale::classic())) + while (i < str.size() && std::isspace(str[i], std::locale::classic())) { ++i; } From 3275440f0de2eb09cf045b54769777740ffc5f81 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Oct 2021 16:47:17 +0400 Subject: [PATCH 1723/2859] Use a separate storage for groundcover data --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/groundcover.cpp | 32 +++++--------- apps/openmw/mwrender/groundcover.hpp | 6 +-- apps/openmw/mwrender/renderingmanager.cpp | 3 +- apps/openmw/mwrender/renderingmanager.hpp | 7 ++- apps/openmw/mwworld/groundcoverstore.cpp | 54 +++++++++++++++++++++++ apps/openmw/mwworld/groundcoverstore.hpp | 29 ++++++++++++ apps/openmw/mwworld/worldimp.cpp | 22 +++++---- apps/openmw/mwworld/worldimp.hpp | 8 ++-- components/esmloader/load.cpp | 2 + 10 files changed, 125 insertions(+), 40 deletions(-) create mode 100644 apps/openmw/mwworld/groundcoverstore.cpp create mode 100644 apps/openmw/mwworld/groundcoverstore.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index f22204eb8c..7aa2928da5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -74,7 +74,7 @@ add_openmw_dir (mwworld actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref weather projectilemanager - cellpreloader datetimemanager + cellpreloader datetimemanager groundcoverstore ) add_openmw_dir (mwphysics diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 1cabf564e2..a39e31bb4d 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -13,22 +13,12 @@ #include #include -#include "apps/openmw/mwworld/esmstore.hpp" -#include "apps/openmw/mwbase/environment.hpp" -#include "apps/openmw/mwbase/world.hpp" +#include "../mwworld/groundcoverstore.hpp" #include "vismask.hpp" namespace MWRender { - std::string getGroundcoverModel(const std::string& id, const MWWorld::ESMStore& groundcoverStore, const MWWorld::ESMStore& store) - { - const ESM::Static* stat = groundcoverStore.get().searchStatic(id); - if (!stat) - stat = store.get().searchStatic(id); - return stat ? stat->mModel : std::string(); - } - class InstancingVisitor : public osg::NodeVisitor { public: @@ -153,7 +143,7 @@ namespace MWRender } } - Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::ESMStore& store) + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::GroundcoverStore& store) : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) @@ -183,7 +173,6 @@ namespace MWRender { if (mDensity <=0.f) return; - const MWWorld::ESMStore& worldStore = MWBase::Environment::get().getWorld()->getStore(); osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); DensityCalculator calculator(mDensity); @@ -193,21 +182,22 @@ namespace MWRender { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) { - const ESM::Cell* cell = mGroundcoverStore.get().searchStatic(cellX, cellY); - if (!cell) continue; + ESM::Cell cell; + mGroundcoverStore.initCell(cell, cellX, cellY); + if (cell.mContextList.empty()) continue; calculator.reset(); std::map refs; - for (size_t i=0; imContextList.size(); ++i) + for (size_t i=0; imContextList[i].index; + unsigned int index = cell.mContextList[i].index; if (esm.size() <= index) esm.resize(index+1); - cell->restore(esm[index], i); + cell.restore(esm[index], i); ESM::CellRef ref; ref.mRefNum.unset(); bool deleted = false; - while(cell->getNextRef(esm[index], ref, deleted)) + while(cell.getNextRef(esm[index], ref, deleted)) { if (!deleted && refs.find(ref.mRefNum) == refs.end() && !calculator.isInstanceEnabled()) deleted = true; if (!deleted && !isInChunkBorders(ref, minBound, maxBound)) deleted = true; @@ -220,9 +210,9 @@ namespace MWRender for (auto& pair : refs) { ESM::CellRef& ref = pair.second; - const std::string& model = getGroundcoverModel(ref.mRefID, mGroundcoverStore, worldStore); + const std::string& model = mGroundcoverStore.getGroundcoverModel(ref.mRefID); if (!model.empty()) - instances["meshes\\" + model].emplace_back(std::move(ref)); + instances[model].emplace_back(std::move(ref)); } } } diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index ea0cfa9b62..26ed8530aa 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -8,6 +8,7 @@ namespace MWWorld { class ESMStore; + class GroundcoverStore; } namespace osg { @@ -20,7 +21,7 @@ namespace MWRender class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { public: - Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::ESMStore& groundcoverStore); + Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::GroundcoverStore& store); ~Groundcover(); osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; @@ -43,8 +44,7 @@ namespace MWRender float mDensity; osg::ref_ptr mStateset; osg::ref_ptr mProgramTemplate; - /// @note mGroundcoverStore is separated from World's store because groundcover files must not be allowed to corrupt normal content files. - const MWWorld::ESMStore& mGroundcoverStore; + const MWWorld::GroundcoverStore& mGroundcoverStore; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2ba18378f9..583df469dc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -49,6 +49,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/groundcoverstore.hpp" #include "../mwgui/loadingscreen.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -294,7 +295,7 @@ namespace MWRender RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::ESMStore& groundcoverStore) + const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 99d0bb5f5e..bcfa64ec14 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -67,6 +67,11 @@ namespace DetourNavigator struct Settings; } +namespace MWWorld +{ + class GroundcoverStore; +} + namespace MWRender { class StateUpdater; @@ -95,7 +100,7 @@ namespace MWRender public: RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::ESMStore& groundcoverStore); + const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore); ~RenderingManager(); osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); diff --git a/apps/openmw/mwworld/groundcoverstore.cpp b/apps/openmw/mwworld/groundcoverstore.cpp new file mode 100644 index 0000000000..543cd3e0d8 --- /dev/null +++ b/apps/openmw/mwworld/groundcoverstore.cpp @@ -0,0 +1,54 @@ +#include "groundcoverstore.hpp" + +#include +#include + +namespace MWWorld +{ + void GroundcoverStore::init(const Store& statics, const Files::Collections& fileCollections, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder) + { + EsmLoader::Query query; + query.mLoadStatics = true; + query.mLoadCells = true; + + std::vector readers(groundcoverFiles.size()); + const EsmLoader::EsmData content = EsmLoader::loadEsmData(query, groundcoverFiles, fileCollections, readers, encoder); + + for (const ESM::Static& stat : statics) + { + std::string id = Misc::StringUtils::lowerCase(stat.mId); + mMeshCache[id] = "meshes\\" + Misc::StringUtils::lowerCase(stat.mModel); + } + + for (const ESM::Static& stat : content.mStatics) + { + std::string id = Misc::StringUtils::lowerCase(stat.mId); + mMeshCache[id] = "meshes\\" + Misc::StringUtils::lowerCase(stat.mModel); + } + + for (const ESM::Cell& cell : content.mCells) + { + if (!cell.isExterior()) continue; + auto cellIndex = std::make_pair(cell.getCellId().mIndex.mX, cell.getCellId().mIndex.mY); + mCellContexts[cellIndex] = std::move(cell.mContextList); + } + } + + std::string GroundcoverStore::getGroundcoverModel(const std::string& id) const + { + std::string idLower = Misc::StringUtils::lowerCase(id); + auto search = mMeshCache.find(idLower); + if (search == mMeshCache.end()) return std::string(); + + return search->second; + } + + void GroundcoverStore::initCell(ESM::Cell& cell, int cellX, int cellY) const + { + cell.blank(); + + auto searchCell = mCellContexts.find(std::make_pair(cellX, cellY)); + if (searchCell != mCellContexts.end()) + cell.mContextList = searchCell->second; + } +} diff --git a/apps/openmw/mwworld/groundcoverstore.hpp b/apps/openmw/mwworld/groundcoverstore.hpp new file mode 100644 index 0000000000..197be2a998 --- /dev/null +++ b/apps/openmw/mwworld/groundcoverstore.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_MWWORLD_GROUNDCOVER_STORE_H +#define GAME_MWWORLD_GROUNDCOVER_STORE_H + +#include +#include +#include + +#include +#include +#include + +#include "esmstore.hpp" + +namespace MWWorld +{ + class GroundcoverStore + { + private: + std::map mMeshCache; + std::map, std::vector> mCellContexts; + + public: + void init(const Store& statics, const Files::Collections& fileCollections, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder); + std::string getGroundcoverModel(const std::string& id) const; + void initCell(ESM::Cell& cell, int cellX, int cellY) const; + }; +} + +#endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f434d059da..bb97f23407 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -155,13 +155,9 @@ namespace MWWorld mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); - + loadContentFiles(fileCollections, contentFiles, mStore, mEsm, encoder, listener); - if (!groundcoverFiles.empty()) - { - std::vector tempReaders (groundcoverFiles.size()); - loadContentFiles(fileCollections, groundcoverFiles, mGroundcoverStore, tempReaders, encoder, listener, false); - } + loadGroundcoverFiles(fileCollections, groundcoverFiles, encoder); listener->loadingOff(); @@ -2948,12 +2944,11 @@ namespace MWWorld return mScriptsEnabled; } - void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener, bool validate) + void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { GameContentLoader gameContentLoader(*listener); EsmLoader esmLoader(store, readers, encoder, *listener); - if (validate) - validateMasterFiles(readers); + validateMasterFiles(readers); gameContentLoader.addLoader(".esm", &esmLoader); gameContentLoader.addLoader(".esp", &esmLoader); @@ -2982,6 +2977,15 @@ namespace MWWorld } } + void World::loadGroundcoverFiles(const Files::Collections& fileCollections, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder) + { + if (!Settings::Manager::getBool("enabled", "Groundcover")) return; + + Log(Debug::Info) << "Loading groundcover:"; + + mGroundcoverStore.init(mStore.get(), fileCollections, groundcoverFiles, encoder); + } + bool World::startSpellCast(const Ptr &actor) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c8eebd9a8b..753169107e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -15,6 +15,7 @@ #include "timestamp.hpp" #include "globals.hpp" #include "contentloader.hpp" +#include "groundcoverstore.hpp" namespace osg { @@ -80,7 +81,7 @@ namespace MWWorld std::vector mEsm; MWWorld::ESMStore mStore; - MWWorld::ESMStore mGroundcoverStore; + GroundcoverStore mGroundcoverStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; @@ -164,10 +165,9 @@ namespace MWWorld void updateSkyDate(); - // A helper method called automatically during World construction. - void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, - ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener, bool validateMasterFiles = true); + void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener); + void loadGroundcoverFiles(const Files::Collections& fileCollections, const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder); float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp index 9879f33274..673c272e59 100644 --- a/components/esmloader/load.cpp +++ b/components/esmloader/load.cpp @@ -215,6 +215,8 @@ namespace EsmLoader reader.setEncoder(encoder); reader.setIndex(static_cast(i)); reader.open(collection.getPath(file).string()); + if (query.mLoadCells) + reader.resolveParentFileIndices(readers); loadEsm(query, readers[i], result); } From 365739d6099cc941a2400ec1beba121c6c43fa7a Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 14 Nov 2021 20:17:30 +0000 Subject: [PATCH 1724/2859] Give psi29a's custom android-ndk22 a go --- .gitlab-ci.yml | 8 +++----- CI/before_install.android.sh | 4 ++-- CI/before_script.android.sh | 5 ++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b9ce5612e2..9f985856ec 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -458,23 +458,21 @@ Windows_MSBuild_Tests_RelWithDebInfo: Debian_AndroidNDK_arm64-v8a: tags: - linux - image: debian:bullseye + image: psi29a/android-ndk:focal-ndk22 rules: - if: $CI_PIPELINE_SOURCE == "push" variables: CCACHE_SIZE: 3G cache: - key: Debian_AndroidNDK_arm64-v8a.v3 + key: Ubuntu__Focal_AndroidNDK_r22b_arm64-v8a.v1 paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - - echo "deb http://deb.debian.org/debian unstable main contrib" > /etc/apt/sources.list - - echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections - apt-get update -yq - - apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer + - apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential stage: build script: - export CCACHE_BASEDIR="`pwd`" diff --git a/CI/before_install.android.sh b/CI/before_install.android.sh index 59d98f48c4..712ded2769 100755 --- a/CI/before_install.android.sh +++ b/CI/before_install.android.sh @@ -1,4 +1,4 @@ #!/bin/sh -ex -curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201230.zip -o ~/openmw-android-deps.zip -unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null +curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20211114.zip -o ~/openmw-android-deps.zip +unzip -o ~/openmw-android-deps -d /android-ndk-r22/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null diff --git a/CI/before_script.android.sh b/CI/before_script.android.sh index 3219f3a4ba..bdf7d4a244 100755 --- a/CI/before_script.android.sh +++ b/CI/before_script.android.sh @@ -7,9 +7,10 @@ mkdir -p build cd build cmake \ --DCMAKE_TOOLCHAIN_FILE=/usr/lib/android-sdk/ndk-bundle/build/cmake/android.toolchain.cmake \ +-DCMAKE_TOOLCHAIN_FILE=/android-ndk-r22/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-21 \ +-DANDROID_LD=deprecated \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_INSTALL_PREFIX=install \ @@ -22,7 +23,5 @@ cmake \ -DBUILD_OPENCS=0 \ -DBUILD_WIZARD=0 \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ --DOPENMW_USE_SYSTEM_OSG=OFF \ --DOPENMW_USE_SYSTEM_BULLET=OFF \ -DOPENMW_USE_SYSTEM_SQLITE3=OFF \ .. From c7d6620c352eb643e509537b10cb329782a9aee4 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 15 Nov 2021 00:54:01 +0100 Subject: [PATCH 1725/2859] Add error checking in MWLua::Action --- apps/openmw/mwlua/actions.cpp | 29 ++++++++++++++++++++++++---- apps/openmw/mwlua/actions.hpp | 23 +++++++++++++++++++--- apps/openmw/mwlua/localscripts.cpp | 4 ++-- apps/openmw/mwlua/luabindings.cpp | 3 +-- apps/openmw/mwlua/luamanagerimp.cpp | 4 ++-- apps/openmw/mwlua/objectbindings.cpp | 15 +++++++------- components/lua/luastate.hpp | 5 +++++ 7 files changed, 63 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 92a7f915bb..7389363fe6 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" @@ -12,14 +13,34 @@ namespace MWLua { +#ifdef NDEBUG + Action::Action(LuaUtil::LuaState* state) {} +#else + Action::Action(LuaUtil::LuaState* state) : mCallerTraceback(state->debugTraceback()) {} +#endif + + void Action::safeApply(WorldView& w) const + { + try + { + apply(w); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); +#ifdef NDEBUG + Log(Debug::Error) << "Traceback is available only in debug builds"; +#else + Log(Debug::Error) << "Caller " << mCallerTraceback; +#endif + } + } + void TeleportAction::apply(WorldView& worldView) const { MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); if (!cell) - { - Log(Debug::Error) << "LuaManager::applyTeleport -> cell not found: '" << mCell << "'"; - return; - } + throw std::runtime_error(std::string("cell not found: '") + mCell + "'"); MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); diff --git a/apps/openmw/mwlua/actions.hpp b/apps/openmw/mwlua/actions.hpp index 900b175320..3b71eef4a9 100644 --- a/apps/openmw/mwlua/actions.hpp +++ b/apps/openmw/mwlua/actions.hpp @@ -6,6 +6,11 @@ #include "object.hpp" #include "worldview.hpp" +namespace LuaUtil +{ + class LuaState; +} + namespace MWLua { @@ -16,17 +21,27 @@ namespace MWLua class Action { public: + Action(LuaUtil::LuaState* state); virtual ~Action() {} + + void safeApply(WorldView&) const; virtual void apply(WorldView&) const = 0; + virtual std::string toString() const = 0; + + private: +#ifndef NDEBUG + std::string mCallerTraceback; +#endif }; class TeleportAction final : public Action { public: - TeleportAction(ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) - : mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} + TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) + : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} void apply(WorldView&) const override; + std::string toString() const override { return "TeleportAction"; } private: ObjectId mObject; @@ -41,9 +56,11 @@ namespace MWLua using Item = std::variant; // recordId or ObjectId using Equipment = std::map; // slot to item - SetEquipmentAction(ObjectId actor, Equipment equipment) : mActor(actor), mEquipment(std::move(equipment)) {} + SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment) + : Action(state), mActor(actor), mEquipment(std::move(equipment)) {} void apply(WorldView&) const override; + std::string toString() const override { return "SetEquipmentAction"; } private: ObjectId mActor; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index ee23b4b90c..98cf0a0b1a 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -39,7 +39,7 @@ namespace MWLua selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; - selfAPI["setEquipment"] = [manager=context.mLuaManager](const SelfObject& obj, sol::table equipment) + selfAPI["setEquipment"] = [context](const SelfObject& obj, sol::table equipment) { if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) { @@ -56,7 +56,7 @@ namespace MWLua else eqp[slot] = value.as(); } - manager->addAction(std::make_unique(obj.id(), std::move(eqp))); + context.mLuaManager->addAction(std::make_unique(context.mLua, obj.id(), std::move(eqp))); }; selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional { diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 07e8dcc30c..bf323351a3 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -28,8 +28,7 @@ namespace MWLua api["API_REVISION"] = 9; api["quit"] = [lua]() { - std::string traceback = lua->sol()["debug"]["traceback"]().get(); - Log(Debug::Warning) << "Quit requested by a Lua script.\n" << traceback; + Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); }; api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 2b9f0702e2..dae11513b5 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -188,11 +188,11 @@ namespace MWLua mUIMessages.clear(); for (std::unique_ptr& action : mActionQueue) - action->apply(mWorldView); + action->safeApply(mWorldView); mActionQueue.clear(); if (mTeleportPlayerAction) - mTeleportPlayerAction->apply(mWorldView); + mTeleportPlayerAction->safeApply(mWorldView); mTeleportPlayerAction.reset(); } diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 2eceecc061..9857170b32 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -179,16 +179,16 @@ namespace MWLua localScripts->removeScript(*scriptId); }; - objectT["teleport"] = [luaManager=context.mLuaManager](const GObject& object, std::string_view cell, - const osg::Vec3f& pos, const sol::optional& optRot) + objectT["teleport"] = [context](const GObject& object, std::string_view cell, + const osg::Vec3f& pos, const sol::optional& optRot) { MWWorld::Ptr ptr = object.ptr(); osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); - auto action = std::make_unique(object.id(), std::string(cell), pos, rot); + auto action = std::make_unique(context.mLua, object.id(), std::string(cell), pos, rot); if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - luaManager->addTeleportPlayerAction(std::move(action)); + context.mLuaManager->addTeleportPlayerAction(std::move(action)); else - luaManager->addAction(std::move(action)); + context.mLuaManager->addAction(std::move(action)); }; } else @@ -352,7 +352,7 @@ namespace MWLua if constexpr (std::is_same_v) { // Only for global scripts - objectT["setEquipment"] = [manager=context.mLuaManager](const GObject& obj, sol::table equipment) + objectT["setEquipment"] = [context](const GObject& obj, sol::table equipment) { if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) { @@ -360,7 +360,8 @@ namespace MWLua throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); return; } - manager->addAction(std::make_unique(obj.id(), parseEquipmentTable(equipment))); + context.mLuaManager->addAction(std::make_unique( + context.mLua, obj.id(), parseEquipmentTable(equipment))); }; // TODO diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index b302e565f4..71cdb4f75d 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -35,6 +35,11 @@ namespace LuaUtil // Returns underlying sol::state. sol::state& sol() { return mLua; } + // Can be used by a C++ function that is called from Lua to get the Lua traceback. + // Makes no sense if called not from Lua code. + // Note: It is a slow function, should be used for debug purposes only. + std::string debugTraceback() { return mLua["debug"]["traceback"]().get(); } + // A shortcut to create a new Lua table. sol::table newTable() { return sol::table(mLua, sol::create); } From c277e8bf3fe730e4546cfdd85155a9cfe6755c82 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 15 Nov 2021 16:31:38 +0100 Subject: [PATCH 1726/2859] Unset store listeners for creatures --- apps/openmw/mwrender/objects.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index f51008fffa..e208d7191e 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -141,11 +141,11 @@ void Objects::removeCell(const MWWorld::CellStore* store) MWWorld::Ptr ptr = iter->second->getPtr(); if(ptr.getCell() == store) { - if (ptr.getClass().isNpc() && ptr.getRefData().getCustomData()) + if (ptr.getClass().isActor() && ptr.getRefData().getCustomData()) { - MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); - invStore.setInvListener(nullptr, ptr); - invStore.setContListener(nullptr); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr, ptr); + ptr.getClass().getContainerStore(ptr).setContListener(nullptr); } mObjects.erase(iter++); From e53d5ed91bffe266fabd14af68f615aa2f31ff14 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 15 Nov 2021 17:31:57 +0100 Subject: [PATCH 1727/2859] Fix compiler warnings in apps/openmw/mwlua/inputbindings.cpp --- apps/openmw/mwlua/inputbindings.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 303c25f864..6707d35d3b 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -37,7 +37,7 @@ namespace MWLua api["isIdle"] = [input]() { return input->isIdle(); }; api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); }; - api["isKeyPressed"] = [input](SDL_Scancode code) -> bool + api["isKeyPressed"] = [](SDL_Scancode code) -> bool { int maxCode; const auto* state = SDL_GetKeyboardState(&maxCode); @@ -46,10 +46,10 @@ namespace MWLua else return false; }; - api["isShiftPressed"] = [input]() -> bool { return SDL_GetModState() & KMOD_SHIFT; }; - api["isCtrlPressed"] = [input]() -> bool { return SDL_GetModState() & KMOD_CTRL; }; - api["isAltPressed"] = [input]() -> bool { return SDL_GetModState() & KMOD_ALT; }; - api["isSuperPressed"] = [input]() -> bool { return SDL_GetModState() & KMOD_GUI; }; + api["isShiftPressed"] = []() -> bool { return SDL_GetModState() & KMOD_SHIFT; }; + api["isCtrlPressed"] = []() -> bool { return SDL_GetModState() & KMOD_CTRL; }; + api["isAltPressed"] = []() -> bool { return SDL_GetModState() & KMOD_ALT; }; + api["isSuperPressed"] = []() -> bool { return SDL_GetModState() & KMOD_GUI; }; api["isControllerButtonPressed"] = [input](int button) { return input->isControllerButtonPressed(static_cast(button)); From ee41b94a73e982dfb5023e9fd90ead58cba588b6 Mon Sep 17 00:00:00 2001 From: Brian Kelley Date: Mon, 15 Nov 2021 19:01:52 +0000 Subject: [PATCH 1728/2859] Enable compilation on apple silicon --- CMakeLists.txt | 6 ++++-- components/detournavigator/recastmeshbuilder.cpp | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f2ee87b2ce..067b645a39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -709,8 +709,10 @@ if (WIN32) endif() if (BUILD_OPENMW AND APPLE) - # Without these flags LuaJit crashes on startup on OSX - set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") + if (USE_LUAJIT) + # Without these flags LuaJit crashes on startup on OSX + set_target_properties(openmw PROPERTIES LINK_FLAGS "-pagezero_size 10000 -image_base 100000000") + endif(USE_LUAJIT) target_compile_definitions(components PRIVATE GL_SILENCE_DEPRECATION=1) target_compile_definitions(openmw PRIVATE GL_SILENCE_DEPRECATION=1) endif() diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index ae3458a011..8a0d30616d 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace DetourNavigator { From 68963538ae2013943bd3dad3e79cc71259a852a4 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 18 Nov 2021 15:19:54 +0000 Subject: [PATCH 1729/2859] Lua UI API --- apps/openmw/mwgui/windowmanagerimp.cpp | 3 + apps/openmw/mwlua/uibindings.cpp | 169 +++++++++- apps/openmw_test_suite/CMakeLists.txt | 2 + .../openmw_test_suite/lua/test_ui_content.cpp | 97 ++++++ components/CMakeLists.txt | 5 + components/lua_ui/content.cpp | 106 ++++++ components/lua_ui/content.hpp | 41 +++ components/lua_ui/element.cpp | 151 +++++++++ components/lua_ui/element.hpp | 31 ++ components/lua_ui/text.cpp | 40 +++ components/lua_ui/text.hpp | 26 ++ components/lua_ui/textedit.cpp | 19 ++ components/lua_ui/textedit.hpp | 19 ++ components/lua_ui/widget.cpp | 307 ++++++++++++++++++ components/lua_ui/widget.hpp | 91 ++++++ components/lua_ui/widgetlist.cpp | 31 ++ components/lua_ui/widgetlist.hpp | 14 + components/lua_ui/window.cpp | 94 ++++++ components/lua_ui/window.hpp | 33 ++ docs/source/generate_luadoc.sh | 23 +- docs/source/reference/lua-scripting/api.rst | 5 +- .../reference/lua-scripting/overview.rst | 4 +- .../lua-scripting/user_interface.rst | 151 +++++++++ .../lua-scripting/widgets/widget.rst | 77 +++++ files/lua_api/openmw/ui.lua | 108 +++++- 25 files changed, 1633 insertions(+), 14 deletions(-) create mode 100644 apps/openmw_test_suite/lua/test_ui_content.cpp create mode 100644 components/lua_ui/content.cpp create mode 100644 components/lua_ui/content.hpp create mode 100644 components/lua_ui/element.cpp create mode 100644 components/lua_ui/element.hpp create mode 100644 components/lua_ui/text.cpp create mode 100644 components/lua_ui/text.hpp create mode 100644 components/lua_ui/textedit.cpp create mode 100644 components/lua_ui/textedit.hpp create mode 100644 components/lua_ui/widget.cpp create mode 100644 components/lua_ui/widget.hpp create mode 100644 components/lua_ui/widgetlist.cpp create mode 100644 components/lua_ui/widgetlist.hpp create mode 100644 components/lua_ui/window.cpp create mode 100644 components/lua_ui/window.hpp create mode 100644 docs/source/reference/lua-scripting/user_interface.rst create mode 100644 docs/source/reference/lua-scripting/widgets/widget.rst diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 6eeb2d3654..a935d7f900 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -53,6 +53,8 @@ #include #include +#include + #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -220,6 +222,7 @@ namespace MWGui ItemWidget::registerComponents(); SpellView::registerComponents(); Gui::registerAllWidgets(); + LuaUi::registerAllWidgets(); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 4fae84cd40..7008a0caea 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -1,17 +1,182 @@ -#include "luabindings.hpp" +#include +#include +#include +#include "context.hpp" +#include "actions.hpp" #include "luamanagerimp.hpp" namespace MWLua { + namespace + { + + class UiAction final : public Action + { + public: + enum Type + { + CREATE = 0, + UPDATE, + DESTROY, + }; + + UiAction(Type type, std::shared_ptr element, LuaUtil::LuaState* state) + : Action(state) + , mType{ type } + , mElement{ std::move(element) } + {} + + void apply(WorldView&) const override + { + try { + switch (mType) + { + case CREATE: + mElement->create(); + break; + case UPDATE: + mElement->update(); + break; + case DESTROY: + mElement->destroy(); + break; + } + } + catch (std::exception& e) + { + // prevent any actions on a potentially corrupted widget + mElement->mRoot = nullptr; + throw; + } + } + + std::string toString() const override + { + std::string result; + switch (mType) + { + case CREATE: + result += "Create"; + break; + case UPDATE: + result += "Update"; + break; + case DESTROY: + result += "Destroy"; + break; + } + result += " UI"; + return result; + } + + private: + Type mType; + std::shared_ptr mElement; + }; + + // Lua arrays index from 1 + inline size_t fromLuaIndex(size_t i) { return i - 1; } + inline size_t toLuaIndex(size_t i) { return i + 1; } + } sol::table initUserInterfacePackage(const Context& context) { - sol::table api(context.mLua->sol(), sol::create); + auto uiContent = context.mLua->sol().new_usertype("UiContent"); + uiContent[sol::meta_function::length] = [](const LuaUi::Content& content) + { + return content.size(); + }; + uiContent[sol::meta_function::index] = sol::overload( + [](const LuaUi::Content& content, size_t index) + { + return content.at(fromLuaIndex(index)); + }, + [](const LuaUi::Content& content, std::string_view name) + { + return content.at(name); + }); + uiContent[sol::meta_function::new_index] = sol::overload( + [](LuaUi::Content& content, size_t index, const sol::table& table) + { + content.assign(fromLuaIndex(index), table); + }, + [](LuaUi::Content& content, size_t index, sol::nil_t nil) + { + content.remove(fromLuaIndex(index)); + }, + [](LuaUi::Content& content, std::string_view name, const sol::table& table) + { + content.assign(name, table); + }, + [](LuaUi::Content& content, std::string_view name, sol::nil_t nil) + { + content.remove(name); + }); + uiContent["insert"] = [](LuaUi::Content& content, size_t index, const sol::table& table) + { + content.insert(fromLuaIndex(index), table); + }; + uiContent["add"] = [](LuaUi::Content& content, const sol::table& table) + { + content.insert(content.size(), table); + }; + uiContent["indexOf"] = [](LuaUi::Content& content, const sol::table& table) -> sol::optional + { + size_t index = content.indexOf(table); + if (index < content.size()) + return toLuaIndex(index); + else + return sol::nullopt; + }; + + auto element = context.mLua->sol().new_usertype("Element"); + element["layout"] = sol::property( + [](LuaUi::Element& element) + { + return element.mLayout; + }, + [](LuaUi::Element& element, const sol::table& layout) + { + element.mLayout = layout; + } + ); + element["update"] = [context](const std::shared_ptr& element) + { + if (element->mDestroy || element->mUpdate) + return; + element->mUpdate = true; + context.mLuaManager->addAction(std::make_unique(UiAction::UPDATE, element, context.mLua)); + }; + element["destroy"] = [context](const std::shared_ptr& element) + { + if (element->mDestroy) + return; + element->mDestroy = true; + context.mLuaManager->addAction(std::make_unique(UiAction::DESTROY, element, context.mLua)); + }; + + sol::table api = context.mLua->newTable(); api["showMessage"] = [luaManager=context.mLuaManager](std::string_view message) { luaManager->addUIMessage(message); }; + api["content"] = [](const sol::table& table) + { + return LuaUi::Content(table); + }; + api["create"] = [context](const sol::table& layout) + { + auto element = std::make_shared(layout); + context.mLuaManager->addAction(std::make_unique(UiAction::CREATE, element, context.mLua)); + return element; + }; + + sol::table typeTable = context.mLua->newTable(); + for (const auto& it : LuaUi::widgetTypeToName()) + typeTable.set(it.second, it.first); + api["TYPE"] = LuaUtil::makeReadOnly(typeTable); + return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index a4e9f8323c..29564ef191 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -24,6 +24,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) lua/test_querypackage.cpp lua/test_configuration.cpp + lua/test_ui_content.cpp + misc/test_stringops.cpp misc/test_endianness.cpp misc/test_resourcehelpers.cpp diff --git a/apps/openmw_test_suite/lua/test_ui_content.cpp b/apps/openmw_test_suite/lua/test_ui_content.cpp new file mode 100644 index 0000000000..f478c618dc --- /dev/null +++ b/apps/openmw_test_suite/lua/test_ui_content.cpp @@ -0,0 +1,97 @@ +#include +#include + +#include + +namespace +{ + using namespace testing; + + sol::state state; + + sol::table makeTable() + { + return sol::table(state, sol::create); + } + + sol::table makeTable(std::string name) + { + auto result = makeTable(); + result["name"] = name; + return result; + } + + TEST(LuaUiContentTest, Create) + { + auto table = makeTable(); + table.add(makeTable()); + table.add(makeTable()); + table.add(makeTable()); + LuaUi::Content content(table); + EXPECT_EQ(content.size(), 3); + } + + TEST(LuaUiContentTest, CreateWithHole) + { + auto table = makeTable(); + table.add(makeTable()); + table.add(makeTable()); + table[4] = makeTable(); + EXPECT_ANY_THROW(LuaUi::Content content(table)); + } + + TEST(LuaUiContentTest, WrongType) + { + auto table = makeTable(); + table.add(makeTable()); + table.add("a"); + table.add(makeTable()); + EXPECT_ANY_THROW(LuaUi::Content content(table)); + } + + TEST(LuaUiContentTest, NameAccess) + { + auto table = makeTable(); + table.add(makeTable()); + table.add(makeTable("a")); + LuaUi::Content content(table); + EXPECT_NO_THROW(content.at("a")); + content.remove("a"); + content.assign(content.size(), makeTable("b")); + content.assign("b", makeTable()); + EXPECT_ANY_THROW(content.at("b")); + EXPECT_EQ(content.size(), 2); + content.assign(content.size(), makeTable("c")); + content.assign(content.size(), makeTable("c")); + content.remove("c"); + EXPECT_ANY_THROW(content.at("c")); + } + + TEST(LuaUiContentTest, IndexOf) + { + auto table = makeTable(); + table.add(makeTable()); + table.add(makeTable()); + table.add(makeTable()); + LuaUi::Content content(table); + auto child = makeTable(); + content.assign(2, child); + EXPECT_EQ(content.indexOf(child), 2); + EXPECT_EQ(content.indexOf(makeTable()), content.size()); + } + + TEST(LuaUiContentTest, BoundsChecks) + { + auto table = makeTable(); + LuaUi::Content content(table); + EXPECT_ANY_THROW(content.at(0)); + content.assign(content.size(), makeTable()); + content.assign(content.size(), makeTable()); + content.assign(content.size(), makeTable()); + EXPECT_ANY_THROW(content.at(3)); + EXPECT_ANY_THROW(content.remove(3)); + EXPECT_NO_THROW(content.remove(1)); + EXPECT_NO_THROW(content.at(1)); + EXPECT_EQ(content.size(), 2); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 71a96716ec..bf073a0e78 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -160,6 +160,11 @@ add_component_dir (fallback add_component_dir (queries query luabindings ) + +add_component_dir (lua_ui + widget widgetlist element content text textedit window + ) + if(WIN32) add_component_dir (crashcatcher diff --git a/components/lua_ui/content.cpp b/components/lua_ui/content.cpp new file mode 100644 index 0000000000..6f9cf61f2f --- /dev/null +++ b/components/lua_ui/content.cpp @@ -0,0 +1,106 @@ +#include "content.hpp" + +namespace LuaUi +{ + Content::Content(const sol::table& table) + { + size_t size = table.size(); + for (size_t index = 0; index < size; ++index) + { + sol::object value = table.get(index + 1); + if (value.is()) + assign(index, value.as()); + else + throw std::logic_error("UI Content children must all be tables."); + } + } + + void Content::assign(size_t index, const sol::table& table) + { + if (mOrdered.size() < index) + throw std::logic_error("Can't have gaps in UI Content."); + if (index == mOrdered.size()) + mOrdered.push_back(table); + else + { + sol::optional oldName = mOrdered[index]["name"]; + if (oldName.has_value()) + mNamed.erase(oldName.value()); + mOrdered[index] = table; + } + sol::optional name = table["name"]; + if (name.has_value()) + mNamed[name.value()] = index; + } + + void Content::assign(std::string_view name, const sol::table& table) + { + auto it = mNamed.find(name); + if (it != mNamed.end()) + assign(it->second, table); + else + throw std::logic_error(std::string("Can't find a UI Content child with name ") += name); + } + + void Content::insert(size_t index, const sol::table& table) + { + size_t size = mOrdered.size(); + if (size < index) + throw std::logic_error("Can't have gaps in UI Content."); + mOrdered.insert(mOrdered.begin() + index, table); + for (size_t i = index; i < size; ++i) + { + sol::optional name = mOrdered[i]["name"]; + if (name.has_value()) + mNamed[name.value()] = index; + } + } + + sol::table Content::at(size_t index) const + { + if (index > size()) + throw std::logic_error("Invalid UI Content index."); + return mOrdered.at(index); + } + + sol::table Content::at(std::string_view name) const + { + auto it = mNamed.find(name); + if (it == mNamed.end()) + throw std::logic_error("Invalid UI Content name."); + return mOrdered.at(it->second); + } + + size_t Content::remove(size_t index) + { + sol::table table = at(index); + sol::optional name = table["name"]; + if (name.has_value()) + { + auto it = mNamed.find(name.value()); + if (it != mNamed.end()) + mNamed.erase(it); + } + mOrdered.erase(mOrdered.begin() + index); + return index; + } + + size_t Content::remove(std::string_view name) + { + auto it = mNamed.find(name); + if (it == mNamed.end()) + throw std::logic_error("Invalid UI Content name."); + size_t index = it->second; + remove(index); + return index; + } + + size_t Content::indexOf(const sol::table& table) + { + auto it = std::find(mOrdered.begin(), mOrdered.end(), table); + if (it == mOrdered.end()) + return size(); + else + return it - mOrdered.begin(); + } +} diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp new file mode 100644 index 0000000000..e970744b3d --- /dev/null +++ b/components/lua_ui/content.hpp @@ -0,0 +1,41 @@ +#ifndef COMPONENTS_LUAUI_CONTENT +#define COMPONENTS_LUAUI_CONTENT + +#include +#include + +#include + +namespace LuaUi +{ + class Content + { + public: + using iterator = std::vector::iterator; + + Content() {} + + // expects a Lua array - a table with keys from 1 to n without any nil values in between + // any other keys are ignored + explicit Content(const sol::table&); + + size_t size() const { return mOrdered.size(); } + + void assign(std::string_view name, const sol::table& table); + void assign(size_t index, const sol::table& table); + void insert(size_t index, const sol::table& table); + + sol::table at(size_t index) const; + sol::table at(std::string_view name) const; + size_t remove(size_t index); + size_t remove(std::string_view name); + size_t indexOf(const sol::table& table); + + private: + std::map> mNamed; + std::vector mOrdered; + }; + +} + +#endif // COMPONENTS_LUAUI_CONTENT diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp new file mode 100644 index 0000000000..258da5269f --- /dev/null +++ b/components/lua_ui/element.cpp @@ -0,0 +1,151 @@ +#include "element.hpp" + +#include + +#include "content.hpp" +#include "widgetlist.hpp" + +namespace LuaUi +{ + + std::string widgetType(const sol::table& layout) + { + return layout.get_or("type", std::string("LuaWidget")); + } + + Content content(const sol::table& layout) + { + auto optional = layout.get>("content"); + if (optional.has_value()) + return optional.value(); + else + return Content(); + } + + void setProperties(LuaUi::WidgetExtension* ext, const sol::table& layout) + { + auto props = layout.get>("props"); + if (props.has_value()) + { + props.value().for_each([ext](const sol::object& key, const sol::object& value) + { + if (key.is()) + ext->setProperty(key.as(), value); + else + Log(Debug::Warning) << "UI property key must be a string"; + }); + ext->updateCoord(); + } + } + + void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::table& layout) + { + ext->clearCallbacks(); + auto events = layout.get>("events"); + if (events.has_value()) + { + events.value().for_each([ext](const sol::object& name, const sol::object& callback) + { + if (name.is() && callback.is()) + ext->setCallback(name.as(), callback.as()); + else if (!name.is()) + Log(Debug::Warning) << "UI event key must be a string"; + else if (!callback.is()) + Log(Debug::Warning) << "UI event handler for key \"" << name.as() + << "\" must be an openmw.async.callback"; + }); + } + } + + LuaUi::WidgetExtension* createWidget(const sol::table& layout, LuaUi::WidgetExtension* parent) + { + std::string type = widgetType(layout); + std::string skin = layout.get_or("skin", std::string()); + std::string layer = layout.get_or("layer", std::string("Windows")); + std::string name = layout.get_or("name", std::string()); + + static auto widgetTypeMap = widgetTypeToName(); + if (widgetTypeMap.find(type) == widgetTypeMap.end()) + throw std::logic_error(std::string("Invalid widget type ") += type); + + MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( + type, skin, + MyGUI::IntCoord(), MyGUI::Align::Default, + layer, name); + + LuaUi::WidgetExtension* ext = dynamic_cast(widget); + if (!ext) + throw std::runtime_error("Invalid widget!"); + + ext->create(layout.lua_state(), widget); + if (parent != nullptr) + widget->attachToWidget(parent->widget()); + + setEventCallbacks(ext, layout); + setProperties(ext, layout); + + Content cont = content(layout); + for (size_t i = 0; i < cont.size(); i++) + ext->addChild(createWidget(cont.at(i), ext)); + + return ext; + } + + void destroyWidget(LuaUi::WidgetExtension* ext) + { + ext->destroy(); + MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); + } + + void updateWidget(const sol::table& layout, LuaUi::WidgetExtension* ext) + { + setEventCallbacks(ext, layout); + setProperties(ext, layout); + + Content newContent = content(layout); + + size_t oldSize = ext->childCount(); + size_t newSize = newContent.size(); + size_t minSize = std::min(oldSize, newSize); + for (size_t i = 0; i < minSize; i++) + { + LuaUi::WidgetExtension* oldWidget = ext->childAt(i); + sol::table newChild = newContent.at(i); + + if (oldWidget->widget()->getTypeName() != widgetType(newChild)) + { + destroyWidget(oldWidget); + ext->assignChild(i, createWidget(newChild, ext)); + } + else + updateWidget(newChild, oldWidget); + } + + for (size_t i = minSize; i < oldSize; i++) + destroyWidget(ext->eraseChild(i)); + + for (size_t i = minSize; i < newSize; i++) + ext->addChild(createWidget(newContent.at(i), ext)); + } + + void Element::create() + { + assert(!mRoot); + if (!mRoot) + mRoot = createWidget(mLayout, nullptr); + } + + void Element::update() + { + if (mRoot && mUpdate) + updateWidget(mLayout, mRoot); + mUpdate = false; + } + + void Element::destroy() + { + if (mRoot) + destroyWidget(mRoot); + mRoot = nullptr; + } +} diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp new file mode 100644 index 0000000000..10e10d8960 --- /dev/null +++ b/components/lua_ui/element.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_LUAUI_ELEMENT +#define OPENMW_LUAUI_ELEMENT + +#include "widget.hpp" + +namespace LuaUi +{ + struct Element + { + Element(sol::table layout) + : mRoot{ nullptr } + , mLayout{ layout } + , mUpdate{ false } + , mDestroy{ false } + { + } + + LuaUi::WidgetExtension* mRoot; + sol::table mLayout; + bool mUpdate; + bool mDestroy; + + void create(); + + void update(); + + void destroy(); + }; +} + +#endif // !OPENMW_LUAUI_ELEMENT diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp new file mode 100644 index 0000000000..241af981b0 --- /dev/null +++ b/components/lua_ui/text.cpp @@ -0,0 +1,40 @@ + +#include "text.hpp" + +namespace LuaUi +{ + void LuaText::initialize() + { + WidgetExtension::initialize(); + mAutoSized = true; + } + + bool LuaText::setPropertyRaw(std::string_view name, sol::object value) + { + if (name == "caption") + { + if (!value.is()) + return false; + setCaption(value.as()); + } + else if (name == "autoSize") + { + if (!value.is()) + return false; + mAutoSized = value.as(); + } + else + { + return WidgetExtension::setPropertyRaw(name, value); + } + return true; + } + + MyGUI::IntSize LuaText::calculateSize() + { + if (mAutoSized) + return getTextSize(); + else + return WidgetExtension::calculateSize(); + } +} diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp new file mode 100644 index 0000000000..08de275d23 --- /dev/null +++ b/components/lua_ui/text.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_LUAUI_TEXT +#define OPENMW_LUAUI_TEXT + +#include + +#include "widget.hpp" + +namespace LuaUi +{ + class LuaText : public MyGUI::TextBox, public WidgetExtension + { + MYGUI_RTTI_DERIVED(LuaText) + + public: + virtual void initialize() override; + + private: + bool mAutoSized; + + protected: + virtual MyGUI::IntSize calculateSize() override; + bool setPropertyRaw(std::string_view name, sol::object value) override; + }; +} + +#endif // OPENMW_LUAUI_TEXT diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp new file mode 100644 index 0000000000..2324a770c2 --- /dev/null +++ b/components/lua_ui/textedit.cpp @@ -0,0 +1,19 @@ +#include "textedit.hpp" + +namespace LuaUi +{ + bool LuaTextEdit::setPropertyRaw(std::string_view name, sol::object value) + { + if (name == "caption") + { + if (!value.is()) + return false; + setCaption(value.as()); + } + else + { + return WidgetExtension::setPropertyRaw(name, value); + } + return true; + } +} diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp new file mode 100644 index 0000000000..7d3291692c --- /dev/null +++ b/components/lua_ui/textedit.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_LUAUI_TEXTEDIT +#define OPENMW_LUAUI_TEXTEDIT + +#include + +#include "widget.hpp" + +namespace LuaUi +{ + class LuaTextEdit : public MyGUI::EditBox, public WidgetExtension + { + MYGUI_RTTI_DERIVED(LuaTextEdit) + + protected: + bool setPropertyRaw(std::string_view name, sol::object value) override; + }; +} + +#endif // OPENMW_LUAUI_TEXTEDIT diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp new file mode 100644 index 0000000000..5cbf1f318d --- /dev/null +++ b/components/lua_ui/widget.cpp @@ -0,0 +1,307 @@ +#include "widget.hpp" + +#include + +#include "text.hpp" +#include "textedit.hpp" +#include "window.hpp" + +namespace LuaUi +{ + void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const + { + auto it = mCallbacks.find(name); + if (it != mCallbacks.end()) + it->second(argument); + } + + void WidgetExtension::create(lua_State* lua, MyGUI::Widget* self) + { + mLua = lua; + mWidget = self; + + mWidget->eventChangeCoord += MyGUI::newDelegate(this, &WidgetExtension::updateChildrenCoord); + + initialize(); + } + + void WidgetExtension::initialize() + { + mAbsoluteCoord = MyGUI::IntCoord(); + mRelativeCoord = MyGUI::FloatCoord(); + mAnchor = MyGUI::FloatSize(); + mForcedCoord = MyGUI::IntCoord(); + + // \todo might be more efficient to only register these if there are Lua callbacks + mWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::keyPress); + mWidget->eventKeyButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::keyRelease); + mWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &WidgetExtension::mouseClick); + mWidget->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WidgetExtension::mouseDoubleClick); + mWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::mousePress); + mWidget->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease); + mWidget->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove); + mWidget->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag); + + mWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); + mWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); + mWidget->eventKeySetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); + mWidget->eventKeyLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); + } + + void WidgetExtension::destroy() + { + clearCallbacks(); + deinitialize(); + + for (WidgetExtension* child : mContent) + child->destroy(); + } + + void WidgetExtension::deinitialize() + { + mWidget->eventKeyButtonPressed.clear(); + mWidget->eventKeyButtonReleased.clear(); + mWidget->eventMouseButtonClick.clear(); + mWidget->eventMouseButtonDoubleClick.clear(); + mWidget->eventMouseButtonPressed.clear(); + mWidget->eventMouseButtonReleased.clear(); + mWidget->eventMouseMove.clear(); + mWidget->eventMouseDrag.m_event.clear(); + + mWidget->eventMouseSetFocus.clear(); + mWidget->eventMouseLostFocus.clear(); + mWidget->eventKeySetFocus.clear(); + mWidget->eventKeyLostFocus.clear(); + } + + sol::table WidgetExtension::makeTable() const + { + return sol::table(mLua, sol::create); + } + + sol::object WidgetExtension::keyEvent(MyGUI::KeyCode code) const + { + SDL_Keysym keySym; + // MyGUI key codes are not one to one with SDL key codes + // \todo refactor sdlmappings.cpp to map this back to SDL correctly + keySym.sym = static_cast(code.getValue()); + keySym.scancode = SDL_GetScancodeFromKey(keySym.sym); + keySym.mod = SDL_GetModState(); + return sol::make_object(mLua, keySym); + } + + sol::object WidgetExtension::mouseEvent(int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const + { + auto position = osg::Vec2f(left, top); + auto absolutePosition = mWidget->getAbsolutePosition(); + auto offset = position - osg::Vec2f(absolutePosition.left, absolutePosition.top); + sol::table table = makeTable(); + table["position"] = position; + table["offset"] = offset; + // \todo refactor sdlmappings.cpp to map this back to SDL properly + table["button"] = button.getValue() + 1; + return table; + } + + void WidgetExtension::addChild(WidgetExtension* ext) + { + mContent.push_back(ext); + } + + WidgetExtension* WidgetExtension::childAt(size_t index) const + { + return mContent.at(index); + } + + void WidgetExtension::assignChild(size_t index, WidgetExtension* ext) + { + if (mContent.size() <= index) + throw std::logic_error("Invalid widget child index"); + mContent[index] = ext; + } + + WidgetExtension* WidgetExtension::eraseChild(size_t index) + { + if (mContent.size() <= index) + throw std::logic_error("Invalid widget child index"); + auto it = mContent.begin() + index; + WidgetExtension* ext = *it; + mContent.erase(it); + return ext; + } + + void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) + { + mCallbacks[name] = callback; + } + + void WidgetExtension::clearCallbacks() + { + mCallbacks.clear(); + } + + void WidgetExtension::setProperty(std::string_view name, sol::object value) + { + if (!setPropertyRaw(name, value)) + Log(Debug::Error) << "Invalid value of property " << name + << ": " << LuaUtil::toString(value); + } + + MyGUI::IntCoord WidgetExtension::forcedOffset() + { + return mForcedCoord; + } + + void WidgetExtension::setForcedOffset(const MyGUI::IntCoord& offset) + { + mForcedCoord = offset; + } + + void WidgetExtension::updateCoord() + { + mWidget->setCoord(calculateCoord()); + } + + bool WidgetExtension::setPropertyRaw(std::string_view name, sol::object value) + { + if (name == "position") + { + if (!value.is()) + return false; + auto v = value.as(); + mAbsoluteCoord.left = v.x(); + mAbsoluteCoord.top = v.y(); + } + else if (name == "size") + { + if (!value.is()) + return false; + auto v = value.as(); + mAbsoluteCoord.width = v.x(); + mAbsoluteCoord.height = v.y(); + } + else if (name == "relativePosition") + { + if (!value.is()) + return false; + auto v = value.as(); + mRelativeCoord.left = v.x(); + mRelativeCoord.top = v.y(); + } + else if (name == "relativeSize") + { + if (!value.is()) + return false; + auto v = value.as(); + mRelativeCoord.width = v.x(); + mRelativeCoord.height = v.y(); + } + else if (name == "anchor") + { + if (!value.is()) + return false; + auto v = value.as(); + mAnchor.width = v.x(); + mAnchor.height = v.y(); + } + else if (name == "visible") + { + if (!value.is()) + return false; + mWidget->setVisible(value.as()); + } + return true; + } + + void WidgetExtension::updateChildrenCoord(MyGUI::Widget* _widget) + { + for (auto& child : mContent) + child->updateCoord(); + } + + MyGUI::IntSize WidgetExtension::calculateSize() + { + const MyGUI::IntSize& parentSize = mWidget->getParentSize(); + MyGUI::IntSize newSize; + newSize = mAbsoluteCoord.size() + mForcedCoord.size(); + newSize.width += mRelativeCoord.width * parentSize.width; + newSize.height += mRelativeCoord.height * parentSize.height; + return newSize; + } + + MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) + { + const MyGUI::IntSize& parentSize = mWidget->getParentSize(); + MyGUI::IntPoint newPosition; + newPosition = mAbsoluteCoord.point() + mForcedCoord.point(); + newPosition.left += mRelativeCoord.left * parentSize.width - mAnchor.width * size.width; + newPosition.top += mRelativeCoord.top * parentSize.height - mAnchor.height * size.height; + return newPosition; + } + + MyGUI::IntCoord WidgetExtension::calculateCoord() + { + MyGUI::IntCoord newCoord; + newCoord = calculateSize(); + newCoord = calculatePosition(newCoord.size()); + return newCoord; + } + + void WidgetExtension::keyPress(MyGUI::Widget*, MyGUI::KeyCode code, MyGUI::Char ch) + { + if (code == MyGUI::KeyCode::None) + { + // \todo decide how to handle unicode strings in Lua + MyGUI::UString uString; + uString.push_back(static_cast(ch)); + triggerEvent("textInput", sol::make_object(mLua, uString.asUTF8())); + } + else + triggerEvent("keyPress", keyEvent(code)); + } + + void WidgetExtension::keyRelease(MyGUI::Widget*, MyGUI::KeyCode code) + { + triggerEvent("keyRelease", keyEvent(code)); + } + + void WidgetExtension::mouseMove(MyGUI::Widget*, int left, int top) + { + triggerEvent("mouseMove", mouseEvent(left, top)); + } + + void WidgetExtension::mouseDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) + { + triggerEvent("mouseMove", mouseEvent(left, top, button)); + } + + void WidgetExtension::mouseClick(MyGUI::Widget* _widget) + { + triggerEvent("mouseClick"); + } + + void WidgetExtension::mouseDoubleClick(MyGUI::Widget* _widget) + { + triggerEvent("mouseDoubleClick"); + } + + void WidgetExtension::mousePress(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) + { + triggerEvent("mousePress", mouseEvent(left, top, button)); + } + + void WidgetExtension::mouseRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) + { + triggerEvent("mouseRelease", mouseEvent(left, top, button)); + } + + void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*) + { + triggerEvent("focusGain"); + } + + void WidgetExtension::focusLoss(MyGUI::Widget*, MyGUI::Widget*) + { + triggerEvent("focusLoss"); + } +} diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp new file mode 100644 index 0000000000..fe1ad46716 --- /dev/null +++ b/components/lua_ui/widget.hpp @@ -0,0 +1,91 @@ +#ifndef OPENMW_LUAUI_WIDGET +#define OPENMW_LUAUI_WIDGET + +#include + +#include +#include +#include + +#include + +namespace LuaUi +{ + /* + * extends MyGUI::Widget and its child classes + * memory ownership is controlled by MyGUI + * it is important not to call any WidgetExtension methods after destroying the MyGUI::Widget + */ + class WidgetExtension + { + public: + // must be called after creating the underlying MyGUI::Widget + void create(lua_State* lua, MyGUI::Widget* self); + // must be called after before destroying the underlying MyGUI::Widget + void destroy(); + + void addChild(WidgetExtension* ext); + WidgetExtension* childAt(size_t index) const; + void assignChild(size_t index, WidgetExtension* ext); + WidgetExtension* eraseChild(size_t index); + size_t childCount() const { return mContent.size(); } + + MyGUI::Widget* widget() const { return mWidget; } + + void setCallback(const std::string&, const LuaUtil::Callback&); + void clearCallbacks(); + + void setProperty(std::string_view, sol::object value); + + MyGUI::IntCoord forcedOffset(); + void setForcedOffset(const MyGUI::IntCoord& offset); + void updateCoord(); + + protected: + ~WidgetExtension() {} + sol::table makeTable() const; + sol::object keyEvent(MyGUI::KeyCode) const; + sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; + virtual bool setPropertyRaw(std::string_view name, sol::object value); + virtual void initialize(); + virtual void deinitialize(); + virtual MyGUI::IntSize calculateSize(); + virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); + MyGUI::IntCoord calculateCoord(); + + void triggerEvent(std::string_view name, const sol::object& argument) const; + + MyGUI::IntCoord mForcedCoord; + MyGUI::IntCoord mAbsoluteCoord; + MyGUI::FloatCoord mRelativeCoord; + MyGUI::FloatSize mAnchor; + + private: + // use lua_State* instead of sol::state_view because MyGUI requires a default constructor + lua_State* mLua; + MyGUI::Widget* mWidget; + + std::vector mContent; + std::map> mCallbacks; + + void updateChildrenCoord(MyGUI::Widget*); + + void keyPress(MyGUI::Widget*, MyGUI::KeyCode, MyGUI::Char); + void keyRelease(MyGUI::Widget*, MyGUI::KeyCode); + void mouseMove(MyGUI::Widget*, int, int); + void mouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton); + void mouseClick(MyGUI::Widget*); + void mouseDoubleClick(MyGUI::Widget*); + void mousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); + void mouseRelease(MyGUI::Widget*, int, int, MyGUI::MouseButton); + void focusGain(MyGUI::Widget*, MyGUI::Widget*); + void focusLoss(MyGUI::Widget*, MyGUI::Widget*); + }; + + class LuaWidget : public MyGUI::Widget, public WidgetExtension + { + MYGUI_RTTI_DERIVED(LuaWidget) + }; +} + +#endif // !OPENMW_LUAUI_WIDGET diff --git a/components/lua_ui/widgetlist.cpp b/components/lua_ui/widgetlist.cpp new file mode 100644 index 0000000000..c2a9bef990 --- /dev/null +++ b/components/lua_ui/widgetlist.cpp @@ -0,0 +1,31 @@ +#include "widgetlist.hpp" + +#include + +#include "widget.hpp" +#include "text.hpp" +#include "textedit.hpp" +#include "window.hpp" + +namespace LuaUi +{ + + void registerAllWidgets() + { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + } + + const std::unordered_map& widgetTypeToName() + { + static std::unordered_map types{ + { "LuaWidget", "Widget" }, + { "LuaText", "Text" }, + { "LuaTextEdit", "TextEdit" }, + { "LuaWindow", "Window" }, + }; + return types; + } +} diff --git a/components/lua_ui/widgetlist.hpp b/components/lua_ui/widgetlist.hpp new file mode 100644 index 0000000000..ff033fb6ca --- /dev/null +++ b/components/lua_ui/widgetlist.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_LUAUI_WIDGETLIST +#define OPENMW_LUAUI_WIDGETLIST + +#include +#include + +namespace LuaUi +{ + void registerAllWidgets(); + + const std::unordered_map& widgetTypeToName(); +} + +#endif // OPENMW_LUAUI_WIDGETLIST diff --git a/components/lua_ui/window.cpp b/components/lua_ui/window.cpp new file mode 100644 index 0000000000..874ccb9fa4 --- /dev/null +++ b/components/lua_ui/window.cpp @@ -0,0 +1,94 @@ +#include "window.hpp" + +#include + +namespace LuaUi +{ + void LuaWindow::initialize() + { + WidgetExtension::initialize(); + + assignWidget(mCaption, "Caption"); + if (mCaption) + { + mCaption->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); + mCaption->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); + } + for (auto w : getSkinWidgetsByName("Action")) + { + w->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); + w->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); + } + } + + void LuaWindow::deinitialize() + { + WidgetExtension::deinitialize(); + + if (mCaption) + { + mCaption->eventMouseButtonPressed.clear(); + mCaption->eventMouseDrag.m_event.clear(); + } + for (auto w : getSkinWidgetsByName("Action")) + { + w->eventMouseButtonPressed.clear(); + w->eventMouseDrag.m_event.clear(); + } + } + + bool LuaWindow::setPropertyRaw(std::string_view name, sol::object value) + { + if (name == "caption") + { + if (!value.is()) + return false; + if (mCaption) + mCaption->setCaption(value.as()); + } + else + { + return WidgetExtension::setPropertyRaw(name, value); + } + return true; + } + + void LuaWindow::notifyMousePress(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) + { + if (id != MyGUI::MouseButton::Left) + return; + + mPreviousMouse.left = left; + mPreviousMouse.top = top; + + if (sender->isUserString("Scale")) + mChangeScale = MyGUI::IntCoord::parse(sender->getUserString("Scale")); + else + mChangeScale = MyGUI::IntCoord(1, 1, 0, 0); + } + + void LuaWindow::notifyMouseDrag(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) + { + if (id != MyGUI::MouseButton::Left) + return; + + MyGUI::IntCoord change = mChangeScale; + change.left *= (left - mPreviousMouse.left); + change.top *= (top - mPreviousMouse.top); + change.width *= (left - mPreviousMouse.left); + change.height *= (top - mPreviousMouse.top); + + setForcedOffset(forcedOffset() + change.size()); + MyGUI::IntPoint positionOffset = change.point() + getPosition() - calculateCoord().point(); + setForcedOffset(forcedOffset() + positionOffset); + updateCoord(); + + mPreviousMouse.left = left; + mPreviousMouse.top = top; + + sol::table table = makeTable(); + table["position"] = osg::Vec2f(mCoord.left, mCoord.top); + table["size"] = osg::Vec2f(mCoord.width, mCoord.height); + triggerEvent("windowDrag", table); + } +} diff --git a/components/lua_ui/window.hpp b/components/lua_ui/window.hpp new file mode 100644 index 0000000000..d92bf8dcbf --- /dev/null +++ b/components/lua_ui/window.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_LUAUI_WINDOW +#define OPENMW_LUAUI_WINDOW + +#include + +#include + +#include "widget.hpp" + +namespace LuaUi +{ + class LuaWindow : public MyGUI::Widget, public WidgetExtension + { + MYGUI_RTTI_DERIVED(LuaWindow) + + private: + // \todo replace with LuaText when skins are properly implemented + MyGUI::TextBox* mCaption; + MyGUI::IntPoint mPreviousMouse; + MyGUI::IntCoord mChangeScale; + + protected: + virtual void initialize() override; + virtual void deinitialize() override; + + bool setPropertyRaw(std::string_view name, sol::object value) override; + + void notifyMousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); + void notifyMouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton); + }; +} + +#endif // OPENMW_LUAUI_WINDOW diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index 7a238eca5a..1af9f0e0f7 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -8,6 +8,18 @@ # luarocks --local pack openmwluadocumentor-0.1.1-1.rockspec # luarocks --local install openmwluadocumentor-0.1.1-1.src.rock +# How to install on Windows: + +# install LuaRocks (heavily recommended to use the standalone package) +# https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows +# git clone https://gitlab.com/ptmikheev/openmw-luadocumentor.git +# cd openmw-luadocumentor/luarocks +# open "Developer Command Prompt for VS <2017/2019>" in this directory and run: +# luarocks --local pack openmwluadocumentor-0.1.1-1.rockspec +# luarocks --local install openmwluadocumentor-0.1.1-1.src.rock +# open "Git Bash" in the same directory and run script: +# ./generate_luadoc.sh + if [ -f /.dockerenv ]; then # We are inside readthedocs pipeline echo "Install lua 5.1" @@ -32,7 +44,6 @@ if [ -f /.dockerenv ]; then cd ~ git clone https://gitlab.com/ptmikheev/openmw-luadocumentor.git cd openmw-luadocumentor/luarocks - luarocks --local install checks luarocks --local pack openmwluadocumentor-0.1.1-1.rockspec luarocks --local install openmwluadocumentor-0.1.1-1.src.rock fi @@ -40,12 +51,18 @@ fi DOCS_SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" FILES_DIR=$DOCS_SOURCE_DIR/../../files OUTPUT_DIR=$DOCS_SOURCE_DIR/reference/lua-scripting/generated_html +DOCUMENTOR_PATH=~/.luarocks/bin/openmwluadocumentor + +if [ ! -x $DOCUMENTOR_PATH ]; then + # running on Windows? + DOCUMENTOR_PATH="$APPDATA/LuaRocks/bin/openmwluadocumentor.bat" +fi rm -f $OUTPUT_DIR/*.html cd $FILES_DIR/lua_api -~/.luarocks/bin/openmwluadocumentor -f doc -d $OUTPUT_DIR openmw/*lua +$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw/*lua cd $FILES_DIR/builtin_scripts -~/.luarocks/bin/openmwluadocumentor -f doc -d $OUTPUT_DIR openmw_aux/*lua +$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 5075f8a5fe..6398a19be8 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -6,6 +6,7 @@ Lua API reference :hidden: engine_handlers + user_interface openmw_util openmw_settings openmw_core @@ -20,6 +21,7 @@ Lua API reference - :ref:`Engine handlers reference` +- :ref:`User interface reference ` - `Game object reference `_ - `Cell reference `_ @@ -56,7 +58,7 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.input ` | by player scripts | | User input | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.ui ` | by player scripts | | Controls user interface | +|:ref:`openmw.ui ` | by player scripts | | Controls :ref:`user interface ` | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |openmw.camera | by player scripts | | Controls camera (not implemented) | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ @@ -71,4 +73,3 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid +=========================================================+====================+===============================================================+ |:ref:`openmw_aux.util ` | everywhere | | Miscellaneous utils | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ - diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index c32fc74fa5..5bc413036a 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -350,7 +350,7 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.input ` | by player scripts | | User input | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.ui ` | by player scripts | | Controls user interface | +|:ref:`openmw.ui ` | by player scripts | | Controls :ref:`user interface ` | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |openmw.camera | by player scripts | | Controls camera (not implemented) | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ @@ -750,5 +750,3 @@ You can add special hints to give LDT more information: .. image:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/reference/lua-scripting/_static/lua-ide-code-completion2.png See `LDT Documentation Language `__ for more details. - - diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst new file mode 100644 index 0000000000..5b733a8fe9 --- /dev/null +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -0,0 +1,151 @@ +User interface reference +======================== + +.. toctree:: + :hidden: + + widgets/widget + +Layouts +------- + +Every widget is defined by a layout, which is a Lua table with the following fields (all of them are optional): + +1. `type`: One of the available widget types from `openmw.ui.TYPE`. +2. | `props`: A Lua table, containing all the properties values. + | Properties define most of the information about the widget: its position, data it displays, etc. + | See the widget pages (table below) for details on specific properties. + | Properties of the basic Widget are inherited by all the other widgets. +3. | `events`: A Lua table, containing `openmw.async.callback` values, which trigger on various interactions with the widget. + | See the Widget pages for details on specific events. + | Events of the basic Widget are inherited by all the other widgets. +4. `content`: a Content (`openmw.ui.content`), which contains layouts for the children of this widget. +5. | `name`: an arbitrary string, the only limitatiion is it being unique within a `Content`. + | Helpful for navigatilng through the layouts. +6. `layer`: only applies for the root widget. (Windows, HUD, etc) + +.. TODO: Write a more detailed documentation for layers when they are finished + +Elements +-------- + +Element is the root widget of a layout. +It is an independent part of the UI, connected only to a specific layer, but not any other layouts. +Creating or destroying an element also creates/destroys all of its children. + +Content +------- + +A container holding all the widget's children. It has a few important differences from a Lua table: + +1. All the keys are integers, i. e. it is an "array" +2. Holes are not allowed. At any point all keys from `1` to the highest `n` must contain a value. +3. | You can access the values by their `name` field as a `Content` key. + | While there is nothing preventing you from changing the `name` of a table inside a content, it is not supported, and will lead to undefined behaviour. + | If you have to change the name, assign a new table to the index instead. + +.. TODO: Talk about skins/templates here when they are ready + +Widget types +------------ + +.. list-table:: + :widths: 30 70 + + * - :ref:`Widget` + - Base widget type, all the other widget inherit its properties and events. + * - `Text` + - Displays text. + * - EditText + - Accepts text input from the user. + * - Window + - Can be moved and resized by the user. + +Example +------- + +*scripts/requirePassword.lua* + +.. code-block:: Lua + + local core = require('openmw.core') + local async = require('openmw.async') + local ui = require('openmw.ui') + local v2 = require('openmw.util').vector2 + + local layout = { + layers = 'Windows', + type = ui.TYPE.Window, + skin = 'MW_Window', -- TODO: replace all skins here when they are properly implemented + props = { + size = v2(200, 250), + -- put the window in the middle of the screen + relativePosition = v2(0.5, 0.5), + anchor = v2(0.5, 0.5), + }, + content = ui.content { + { + type = ui.TYPE.Text, + skin = 'SandText', + props = { + caption = 'Input password', + relativePosition = v2(0.5, 0), + anchor = v2(0.5, 0), + }, + }, + { + name = 'input', + type = ui.TYPE.TextEdit, + skin = "MW_TextEdit", + props = { + caption = '', + relativePosition = v2(0.5, 0.5), + anchor = v2(0.5, 0.5), + size = v2(125, 50), + }, + events = {} + }, + { + name = 'submit', + type = ui.TYPE.Text, -- TODO: replace with button when implemented + skin = "MW_Button", + props = { + caption = 'Submit', + -- position at the bottom + relativePosition = v2(0.5, 1.0), + anchor = v2(0.5, 1.0), + autoSize = false, + size = v2(75, 50), + }, + events = {}, + }, + }, + } + + local element = nil + + local input = layout.content.input + -- TODO: replace with a better event when TextEdit is finished + input.events.textInput = async:callback(function(text) + input.props.caption = input.props.caption .. text + end) + + local submit = layout.content.submit + submit.events.mouseClick = async:callback(function() + if input.props.caption == 'very secret password' then + if element then + element:destroy() + end + else + print('wrong password', input.props.caption) + core.quit() + end + end) + + element = ui.create(layout) + +*requirePassword.omwscripts* + +:: + + PLAYER: scripts/requirePassword.lua diff --git a/docs/source/reference/lua-scripting/widgets/widget.rst b/docs/source/reference/lua-scripting/widgets/widget.rst new file mode 100644 index 0000000000..51d6d17203 --- /dev/null +++ b/docs/source/reference/lua-scripting/widgets/widget.rst @@ -0,0 +1,77 @@ +Widget +====== + +Properties +---------- + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - position + - util.vector2 (0, 0) + - | Offsets the position of the widget from its parent's + | top-left corner in pixels. + * - size + - util.vector2 (0, 0) + - Increases the widget's size in pixels. + * - relativePosition + - util.vector2 (0, 0) + - | Offsets the position of the widget from its parent's + | top-left corner as a fraction of the parent's size. + * - relativeSize + - util.vector2 (0, 0) + - Increases the widget's size by a fraction of its parent's size. + * - anchor + - util.vector2 (0, 0) + - | Offsets the widget's position by a fraction of its size. + | Useful for centering or aligning to a corner. + * - visible + - boolean (true) + - Defines if the widget is visible + +Events +------ + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - keyPress + - `KeyboardEvent <../openmw_input.html##(KeyboardEvent)>`_ + - A key was pressed with this widget in focus + * - keyRelease + - `KeyboardEvent <../openmw_input.html##(KeyboardEvent)>`_ + - A key was released with this widget in focus + * - mouseMove + - `MouseEvent <../openmw_ui.html##(MouseEvent)>`_ + - | Mouse cursor moved on this widget + | `MouseEvent.button` is the mouse button being held + | (nil when simply moving, and not dragging) + * - mouseClick + - nil + - Widget was clicked with left mouse button + * - mouseDoubleClick + - nil + - Widget was double clicked with left mouse button + * - mousePress + - `MouseEvent <../openmw_ui.html##(MouseEvent)>`_ + - A mouse button was pressed on this widget + * - mouseRelease + - `MouseEvent <../openmw_ui.html##(MouseEvent)>`_ + - A mouse button was released on this widget + * - focusGain + - nil + - Widget gained focus (either through mouse or keyboard) + * - focusLoss + - nil + - Widget lost focus + * - textInput + - string + - Text input with this widget in focus diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index bf6976c76b..593cbf8043 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -1,15 +1,115 @@ -------------------------------------------------------------------------------- +--- -- `openmw.ui` controls user interface. -- Can be used only by local scripts, that are attached to a player. -- @module ui --- @usage local ui = require('openmw.ui') +-- @usage +-- local ui = require('openmw.ui') +--- +-- @field [parent=#ui] #WIDGET_TYPE WIDGET_TYPE +--- +-- @type WIDGET_TYPE +-- @field [parent=#WIDGET_TYPE] Widget Base widget type +-- @field [parent=#WIDGET_TYPE] Text Display text +-- @field [parent=#WIDGET_TYPE] TextEdit Accepts user text input +-- @field [parent=#WIDGET_TYPE] Window Can be moved and resized by the user -------------------------------------------------------------------------------- +--- -- Shows given message at the bottom of the screen. -- @function [parent=#ui] showMessage -- @param #string msg -return nil +--- +-- Converts a given table of tables into an @{openmw.ui#Content} +-- @function [parent=#ui] content +-- @param #table table +-- @return #Content + +--- +-- Creates a UI element from the given layout table +-- @function [parent=#ui] create +-- @param #Layout layout +-- @return #Element + +--- +-- Layout +-- @type Layout +-- @field #string name Optional name of the layout. Allows access by name from Content +-- @field #table props Optional table of widget properties +-- @field #table events Optional table of event callbacks +-- @field #Content content Optional @{openmw.ui#Content} of children layouts + +--- +-- Content. An array-like container, which allows to reference elements by their name +-- @type Content +-- @list <#Layout> +-- @usage +-- local content = ui.content { +-- { name = 'input' }, +-- } +-- -- bad idea! +-- -- content[1].name = 'otherInput' +-- -- do this instead: +-- content.input = { name = 'otherInput' } +-- @usage +-- local content = ui.content { +-- { name = 'display' }, +-- { name = 'submit' }, +-- } +-- -- allowed, but shifts all the items after it "up" the array +-- content.display = nil +-- -- still no holes after this! +-- @usage +-- -- iterate over a Content +-- for i = 1, #content do +-- print('widget',content[i].name,'at',i) +-- end + +--- +-- Puts the layout at given index by shifting all the elements after it +-- @function [parent=#Content] insert +-- @param self +-- @param #number index +-- @param #Layout layout + +--- +-- Adds the layout at the end of the Content +-- (same as calling insert with `last index + 1`) +-- @function [parent=#Content] add +-- @param self +-- @param #Layout layout + +--- +-- Finds the index of the given layout. If it is not in the container, returns nil +-- @function [parent=#Content] indexOf +-- @param self +-- @param #Layout layout +-- @return #number, #nil index +--- +-- Element. An element of the user interface +-- @type Element + +--- +-- Refreshes the rendered element to match the current layout state +-- @function [parent=#Element] update +-- @param self + +--- +-- Destroys the element +-- @function [parent=#Element] destroy +-- @param self + +--- +-- Access or replace the element's layout +-- @field [parent=#Element] #Layout layout + +--- +-- Mouse event, passed as an argument to relevant UI events +-- @type MouseEvent +-- @field openmw.util#Vector2 position Absolute position of the mouse cursor +-- @field openmw.util#Vector2 offset Position of the mouse cursor relative to the widget +-- @field #number button Mouse button which triggered the event (could be nil) + +return nil From 750514cda2eaaee3217de3ef01ab53f5882c7f3b Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Thu, 18 Nov 2021 19:40:13 -0800 Subject: [PATCH 1730/2859] simply lightmanager and fix racey behavior --- apps/openmw/engine.cpp | 2 - components/sceneutil/lightmanager.cpp | 524 ++++++++++---------------- components/sceneutil/lightmanager.hpp | 53 ++- components/sceneutil/serialize.cpp | 3 +- 4 files changed, 254 insertions(+), 328 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 67944e4546..e3ba74cf58 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1069,8 +1069,6 @@ void OMW::Engine::go() // Save user settings settings.saveUser(settingspath); - mViewer->stopThreading(); - Log(Debug::Info) << "Quitting peacefully."; } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 448f6ca916..29907c542d 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -29,18 +29,6 @@ namespace return left->mViewBound.center().length2() - left->mViewBound.radius2()*illuminationBias < right->mViewBound.center().length2() - right->mViewBound.radius2()*illuminationBias; } - float getLightRadius(const osg::Light* light) - { - float value = 0.0; - light->getUserValue("radius", value); - return value; - } - - void setLightRadius(osg::Light* light, float value) - { - light->setUserValue("radius", value); - } - void configurePosition(osg::Matrixf& mat, const osg::Vec4& pos) { mat(0, 0) = pos.x(); @@ -77,11 +65,6 @@ namespace mat(2, 3) = q; mat(3, 3) = r; } - - bool isReflectionCamera(osg::Camera* camera) - { - return (camera->getName() == "ReflectionCamera"); - } } namespace SceneUtil @@ -188,19 +171,15 @@ namespace SceneUtil void configureLayout(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int size, int stride) { - const Offsets offsets(offsetColors, offsetPosition, offsetAttenuationRadius, stride); + configureLayout(Offsets(offsetColors, offsetPosition, offsetAttenuationRadius, stride), size); + } - // Copy cloned data using current layout into current data using new layout. - // This allows to preserve osg::FloatArray buffer object in mData. - const auto data = mData->asVector(); - mData->resizeArray(static_cast(size)); - for (int i = 0; i < mCount; ++i) - { - std::memcpy(&(*mData)[offsets.get(i, Diffuse)], data.data() + getOffset(i, Diffuse), sizeof(osg::Vec4f)); - std::memcpy(&(*mData)[offsets.get(i, Position)], data.data() + getOffset(i, Position), sizeof(osg::Vec4f)); - std::memcpy(&(*mData)[offsets.get(i, AttenuationRadius)], data.data() + getOffset(i, AttenuationRadius), sizeof(osg::Vec4f)); - } - mOffsets = offsets; + void configureLayout(const LightBuffer* other) + { + mOffsets = other->mOffsets; + int size = other->mData->size(); + + configureLayout(mOffsets, size); } private: @@ -242,6 +221,21 @@ namespace SceneUtil std::array mValues; }; + void configureLayout(const Offsets& offsets, int size) + { + // Copy cloned data using current layout into current data using new layout. + // This allows to preserve osg::FloatArray buffer object in mData. + const auto data = mData->asVector(); + mData->resizeArray(static_cast(size)); + for (int i = 0; i < mCount; ++i) + { + std::memcpy(&(*mData)[offsets.get(i, Diffuse)], data.data() + getOffset(i, Diffuse), sizeof(osg::Vec4f)); + std::memcpy(&(*mData)[offsets.get(i, Position)], data.data() + getOffset(i, Position), sizeof(osg::Vec4f)); + std::memcpy(&(*mData)[offsets.get(i, AttenuationRadius)], data.data() + getOffset(i, AttenuationRadius), sizeof(osg::Vec4f)); + } + mOffsets = offsets; + } + osg::ref_ptr mData; osg::Endian mEndian; int mCount; @@ -249,9 +243,8 @@ namespace SceneUtil osg::Vec4 mCachedSunPos; }; - class LightStateCache + struct LightStateCache { - public: std::vector lastAppliedLight; }; @@ -281,9 +274,7 @@ namespace SceneUtil configureDiffuse(lightMat, light->getDiffuse()); configureSpecular(lightMat, light->getSpecular()); - osg::ref_ptr uni = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", lightManager->getMaxLights()); - uni->setElement(0, lightMat); - stateset->addUniform(uni, mode); + stateset->addUniform(lightManager->generateLightBufferUniform(lightMat), mode); break; } case LightingMethod::SingleUBO: @@ -318,25 +309,20 @@ namespace SceneUtil DisableLight(const DisableLight& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex) {} - osg::Object* cloneType() const override { return new DisableLight(mIndex); } - osg::Object* clone(const osg::CopyOp& copyop) const override { return new DisableLight(*this,copyop); } - bool isSameKindAs(const osg::Object* obj) const override { return dynamic_cast(obj)!=nullptr; } - const char* libraryName() const override { return "SceneUtil"; } - const char* className() const override { return "DisableLight"; } - Type getType() const override { return LIGHT; } + META_StateAttribute(SceneUtil, DisableLight, osg::StateAttribute::LIGHT) unsigned int getMember() const override { return mIndex; } - bool getModeUsage(ModeUsage & usage) const override + bool getModeUsage(ModeUsage& usage) const override { usage.usesMode(GL_LIGHT0 + mIndex); return true; } - int compare(const StateAttribute &sa) const override + int compare(const StateAttribute& sa) const override { throw std::runtime_error("DisableLight::compare: unimplemented"); } @@ -361,7 +347,7 @@ namespace SceneUtil { public: FFPLightStateAttribute() : mIndex(0) {} - FFPLightStateAttribute(size_t index, const std::vector >& lights) : mIndex(index), mLights(lights) {} + FFPLightStateAttribute(size_t index, const std::vector>& lights) : mIndex(index), mLights(lights) {} FFPLightStateAttribute(const FFPLightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex), mLights(copy.mLights) {} @@ -371,19 +357,19 @@ namespace SceneUtil return mIndex; } - bool getModeUsage(ModeUsage & usage) const override + bool getModeUsage(ModeUsage& usage) const override { for (size_t i = 0; i < mLights.size(); ++i) usage.usesMode(GL_LIGHT0 + mIndex + i); return true; } - int compare(const StateAttribute &sa) const override + int compare(const StateAttribute& sa) const override { throw std::runtime_error("FFPLightStateAttribute::compare: unimplemented"); } - META_StateAttribute(NifOsg, FFPLightStateAttribute, osg::StateAttribute::LIGHT) + META_StateAttribute(SceneUtil, FFPLightStateAttribute, osg::StateAttribute::LIGHT) void apply(osg::State& state) const override { @@ -429,69 +415,6 @@ namespace SceneUtil std::vector> mLights; }; - LightManager* findLightManager(const osg::NodePath& path) - { - for (size_t i = 0; i < path.size(); ++i) - { - if (LightManager* lightManager = dynamic_cast(path[i])) - return lightManager; - } - return nullptr; - } - - class LightStateAttributePerObjectUniform : public osg::StateAttribute - { - public: - LightStateAttributePerObjectUniform() : mLightManager(nullptr) {} - LightStateAttributePerObjectUniform(const std::vector>& lights, LightManager* lightManager) : mLights(lights), mLightManager(lightManager) {} - - LightStateAttributePerObjectUniform(const LightStateAttributePerObjectUniform& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop), mLights(copy.mLights), mLightManager(copy.mLightManager) {} - - int compare(const StateAttribute &sa) const override - { - throw std::runtime_error("LightStateAttributePerObjectUniform::compare: unimplemented"); - } - - META_StateAttribute(NifOsg, LightStateAttributePerObjectUniform, osg::StateAttribute::LIGHT) - - void resize(int numLights) - { - mLights.resize(std::min(static_cast(numLights), mLights.size())); - } - - void apply(osg::State &state) const override - { - osg::StateSet* stateSet = mLightManager->getStateSet(); - if (!stateSet) - return; - - auto* lightUniform = stateSet->getUniform("LightBuffer"); - for (size_t i = 0; i < mLights.size(); ++i) - { - auto light = mLights[i]; - osg::Matrixf lightMat; - - configurePosition(lightMat, light->getPosition() * state.getInitialViewMatrix()); - configureAmbient(lightMat, light->getAmbient()); - configureDiffuse(lightMat, light->getDiffuse()); - configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light)); - - lightUniform->setElement(i+1, lightMat); - } - - auto sun = mLightManager->getSunlightBuffer(state.getFrameStamp()->getFrameNumber()); - configurePosition(sun, osg::Vec4(sun(0,0), sun(0,1), sun(0,2), 0.0) * state.getInitialViewMatrix()); - lightUniform->setElement(0, sun); - - lightUniform->dirty(); - } - - private: - std::vector> mLights; - LightManager* mLightManager; - }; - struct StateSetGenerator { LightManager* mLightManager; @@ -501,6 +424,8 @@ namespace SceneUtil virtual osg::ref_ptr generate(const LightManager::LightList& lightList, size_t frameNum) = 0; virtual void update(osg::StateSet* stateset, const LightManager::LightList& lightList, size_t frameNum) {} + + osg::Matrix mViewMatrix; }; struct StateSetGeneratorFFP : StateSetGenerator @@ -588,37 +513,50 @@ namespace SceneUtil osg::ref_ptr generate(const LightManager::LightList& lightList, size_t frameNum) override { osg::ref_ptr stateset = new osg::StateSet; - - std::vector> lights(lightList.size()); + osg::ref_ptr data = mLightManager->generateLightBufferUniform(mLightManager->getSunlightBuffer(frameNum)); for (size_t i = 0; i < lightList.size(); ++i) { auto* light = lightList[i]->mLightSource->getLight(frameNum); - lights[i] = light; - setLightRadius(light, lightList[i]->mLightSource->getRadius()); - } + osg::Matrixf lightMat; + configurePosition(lightMat, light->getPosition() * mViewMatrix); + configureAmbient(lightMat, light->getAmbient()); + configureDiffuse(lightMat, light->getDiffuse()); + configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightList[i]->mLightSource->getRadius()); - stateset->setAttributeAndModes(new LightStateAttributePerObjectUniform(std::move(lights), mLightManager), osg::StateAttribute::ON); + data->setElement(i+1, lightMat); + } + stateset->addUniform(data); stateset->addUniform(new osg::Uniform("PointLightCount", static_cast(lightList.size() + 1))); return stateset; } }; + LightManager* findLightManager(const osg::NodePath& path) + { + for (size_t i = 0; i < path.size(); ++i) + { + if (LightManager* lightManager = dynamic_cast(path[i])) + return lightManager; + } + return nullptr; + } + // Set on a LightSource. Adds the light source to its light manager for the current frame. // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager. - class CollectLightCallback : public SceneUtil::NodeCallback + class CollectLightCallback : public NodeCallback { public: CollectLightCallback() : mLightManager(nullptr) { } CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop) - : SceneUtil::NodeCallback(copy, copyop) + : NodeCallback(copy, copyop) , mLightManager(nullptr) { } - META_Object(SceneUtil, SceneUtil::CollectLightCallback) + META_Object(SceneUtil, CollectLightCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv) { @@ -656,169 +594,163 @@ namespace SceneUtil class LightManagerCullCallback : public SceneUtil::NodeCallback { public: - LightManagerCullCallback() : mLastFrameNumber(0) {} - void operator()(LightManager* node, osgUtil::CullVisitor* cv) { - if (mLastFrameNumber != cv->getTraversalNumber()) - { - mLastFrameNumber = cv->getTraversalNumber(); + osg::ref_ptr stateset = new osg::StateSet; - if (node->getLightingMethod() == LightingMethod::SingleUBO) - { - auto stateset = node->getStateSet(); - auto bo = node->getLightBuffer(mLastFrameNumber); + if (node->getLightingMethod() == LightingMethod::SingleUBO) + { + auto buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber()); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); #else - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), bo->getData()->getBufferObject(), 0, bo->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData()->getBufferObject(), 0, buffer->getData()->getTotalDataSize()); #endif - stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); - } + stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); - auto sun = node->getSunlight(); - - if (sun) + if (auto sun = node->getSunlight()) { - // we must defer uploading the transformation to view-space position to deal with different cameras (e.g. reflection RTT). - if (node->getLightingMethod() == LightingMethod::PerObjectUniform) - { - osg::Matrixf lightMat; - configurePosition(lightMat, sun->getPosition()); - configureAmbient(lightMat, sun->getAmbient()); - configureDiffuse(lightMat, sun->getDiffuse()); - configureSpecular(lightMat, sun->getSpecular()); - node->setSunlightBuffer(lightMat, mLastFrameNumber); - } - else - { - auto buf = node->getLightBuffer(mLastFrameNumber); - - buf->setCachedSunPos(sun->getPosition()); - buf->setAmbient(0, sun->getAmbient()); - buf->setDiffuse(0, sun->getDiffuse()); - buf->setSpecular(0, sun->getSpecular()); - } + buffer->setCachedSunPos(sun->getPosition()); + buffer->setAmbient(0, sun->getAmbient()); + buffer->setDiffuse(0, sun->getDiffuse()); + buffer->setSpecular(0, sun->getSpecular()); + } + } + else if (node->getLightingMethod() == LightingMethod::PerObjectUniform) + { + if (auto sun = node->getSunlight()) + { + osg::Matrixf lightMat; + configurePosition(lightMat, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + configureAmbient(lightMat, sun->getAmbient()); + configureDiffuse(lightMat, sun->getDiffuse()); + configureSpecular(lightMat, sun->getSpecular()); + node->setSunlightBuffer(lightMat, cv->getTraversalNumber()); + stateset->addUniform(node->generateLightBufferUniform(lightMat)); } } + cv->pushStateSet(stateset); traverse(node, cv); + cv->popStateSet(); } - - private: - size_t mLastFrameNumber; }; - class LightManagerStateAttribute : public osg::StateAttribute + UBOManager::UBOManager(int lightCount) + : mDummyProgram(new osg::Program) + , mInitLayout(false) + , mDirty({ true, true }) + , mTemplate(new LightBuffer(lightCount)) { - public: - LightManagerStateAttribute() - : mLightManager(nullptr) - , mInitLayout(false) - { - } + static const std::string dummyVertSource = generateDummyShader(lightCount); - LightManagerStateAttribute(LightManager* lightManager) - : mLightManager(lightManager) - , mDummyProgram(new osg::Program) - , mInitLayout(false) - { - static const std::string dummyVertSource = generateDummyShader(mLightManager->getMaxLightsInScene()); + // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably + // available, regardless of extensions, until GLSL 140. + mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); + mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Resource::SceneManager::UBOBinding::LightBuffer)); - // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably - // available, regardless of extensions, until GLSL 140. - mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); - mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Resource::SceneManager::UBOBinding::LightBuffer)); - } + for (size_t i = 0; i < mLightBuffers.size(); ++i) + { + mLightBuffers[i] = new LightBuffer(lightCount); - LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop), mLightManager(copy.mLightManager), mInitLayout(copy.mInitLayout) {} + osg::ref_ptr ubo = new osg::UniformBufferObject; + ubo->setUsage(GL_STREAM_DRAW); - int compare(const StateAttribute &sa) const override - { - throw std::runtime_error("LightManagerStateAttribute::compare: unimplemented"); + mLightBuffers[i]->getData()->setBufferObject(ubo); } + } - META_StateAttribute(NifOsg, LightManagerStateAttribute, osg::StateAttribute::LIGHT) + UBOManager::UBOManager(const UBOManager& copy, const osg::CopyOp& copyop) : osg::StateAttribute(copy,copyop), mDummyProgram(copy.mDummyProgram), mInitLayout(copy.mInitLayout) {} - void initSharedLayout(osg::GLExtensions* ext, int handle) const - { - constexpr std::array index = { static_cast(Resource::SceneManager::UBOBinding::LightBuffer) }; - int totalBlockSize = -1; - int stride = -1; + void UBOManager::releaseGLObjects(osg::State* state) const + { + mDummyProgram->releaseGLObjects(state); + } - ext->glGetActiveUniformBlockiv(handle, 0, GL_UNIFORM_BLOCK_DATA_SIZE, &totalBlockSize); - ext->glGetActiveUniformsiv(handle, index.size(), index.data(), GL_UNIFORM_ARRAY_STRIDE, &stride); + int UBOManager::compare(const StateAttribute &sa) const + { + throw std::runtime_error("LightManagerStateAttribute::compare: unimplemented"); + } - std::array names = { - "LightBuffer[0].packedColors", - "LightBuffer[0].position", - "LightBuffer[0].attenuation", - }; - std::vector indices(names.size()); - std::vector offsets(names.size()); + void UBOManager::apply(osg::State& state) const + { + unsigned int frame = state.getFrameStamp()->getFrameNumber(); + unsigned int index = frame % 2; - ext->glGetUniformIndices(handle, names.size(), names.data(), indices.data()); - ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data()); + if (!mInitLayout) + { + mDummyProgram->apply(state); + auto handle = mDummyProgram->getPCP(state)->getHandle(); + auto* ext = state.get(); - for (int i = 0; i < 2; ++i) - mLightManager->getLightBuffer(i)->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride); - } + int activeUniformBlocks = 0; + ext->glGetProgramiv(handle, GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks); - void apply(osg::State& state) const override - { - if (!mInitLayout) + // wait until the UBO binding is created + if (activeUniformBlocks > 0) { - mDummyProgram->apply(state); - auto handle = mDummyProgram->getPCP(state)->getHandle(); - auto* ext = state.get(); + initSharedLayout(ext, handle, frame); + mInitLayout = true; + } + } + else if (mDirty[index]) + { + mDirty[index] = false; + mLightBuffers[index]->configureLayout(mTemplate); + } - int activeUniformBlocks = 0; - ext->glGetProgramiv(handle, GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks); + mLightBuffers[index]->uploadCachedSunPos(state.getInitialViewMatrix()); + mLightBuffers[index]->dirty(); + } - // wait until the UBO binding is created - if (activeUniformBlocks > 0) - { - initSharedLayout(ext, handle); - mInitLayout = true; - } + std::string UBOManager::generateDummyShader(int maxLightsInScene) + { + const std::string define = "@maxLightsInScene"; + + std::string shader = R"GLSL( + #version 120 + #extension GL_ARB_uniform_buffer_object : require + struct LightData { + ivec4 packedColors; + vec4 position; + vec4 attenuation; + }; + uniform LightBufferBinding { + LightData LightBuffer[@maxLightsInScene]; + }; + void main() + { + gl_Position = vec4(0.0); } - mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->uploadCachedSunPos(state.getInitialViewMatrix()); - mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); - } + )GLSL"; - private: + shader.replace(shader.find(define), define.length(), std::to_string(maxLightsInScene)); + return shader; + } - std::string generateDummyShader(int maxLightsInScene) - { - const std::string define = "@maxLightsInScene"; - - std::string shader = R"GLSL( - #version 120 - #extension GL_ARB_uniform_buffer_object : require - struct LightData { - ivec4 packedColors; - vec4 position; - vec4 attenuation; - }; - uniform LightBufferBinding { - LightData LightBuffer[@maxLightsInScene]; - }; - void main() - { - gl_Position = vec4(0.0); - } - )GLSL"; + void UBOManager::initSharedLayout(osg::GLExtensions* ext, int handle, unsigned int frame) const + { + constexpr std::array index = { static_cast(Resource::SceneManager::UBOBinding::LightBuffer) }; + int totalBlockSize = -1; + int stride = -1; - shader.replace(shader.find(define), define.length(), std::to_string(maxLightsInScene)); - return shader; - } + ext->glGetActiveUniformBlockiv(handle, 0, GL_UNIFORM_BLOCK_DATA_SIZE, &totalBlockSize); + ext->glGetActiveUniformsiv(handle, index.size(), index.data(), GL_UNIFORM_ARRAY_STRIDE, &stride); - LightManager* mLightManager; - osg::ref_ptr mDummyProgram; - mutable bool mInitLayout; - }; + std::array names = { + "LightBuffer[0].packedColors", + "LightBuffer[0].position", + "LightBuffer[0].attenuation", + }; + std::vector indices(names.size()); + std::vector offsets(names.size()); + + ext->glGetUniformIndices(handle, names.size(), names.data(), indices.data()); + ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data()); + + mTemplate->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride); + } const std::unordered_map LightManager::mLightingMethodSettingMap = { {"legacy", LightingMethod::FFP} @@ -845,11 +777,6 @@ namespace SceneUtil return ""; } - LightManager::~LightManager() - { - getOrCreateStateSet()->removeAttribute(osg::StateAttribute::LIGHT); - } - LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) @@ -899,7 +826,7 @@ namespace SceneUtil getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); - addCullCallback(new LightManagerCullCallback()); + addCullCallback(new LightManagerCullCallback); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) @@ -970,55 +897,16 @@ namespace SceneUtil if (usingFFP()) return; - int targetLights = std::clamp(Settings::Manager::getInt("max lights", "Shaders"), mMaxLightsLowerLimit, mMaxLightsUpperLimit); - setMaxLights(targetLights); + setMaxLights(std::clamp(Settings::Manager::getInt("max lights", "Shaders"), mMaxLightsLowerLimit, mMaxLightsUpperLimit)); if (getLightingMethod() == LightingMethod::PerObjectUniform) { - auto* prevUniform = getStateSet()->getUniform("LightBuffer"); - osg::ref_ptr newUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights()); - - for (int i = 0; i < getMaxLights(); ++i) - { - osg::Matrixf prevLightData; - prevUniform->getElement(i, prevLightData); - newUniform->setElement(i, prevLightData); - } - - getStateSet()->removeUniform(prevUniform); - getStateSet()->addUniform(newUniform); - - for (int i = 0; i < 2; ++i) - { - for (auto& pair : mStateSetCache[i]) - static_cast(pair.second->getAttribute(osg::StateAttribute::LIGHT))->resize(getMaxLights()); - mStateSetCache[i].clear(); - } + getStateSet()->removeUniform("LightBuffer"); + getStateSet()->addUniform(generateLightBufferUniform(osg::Matrixf())); } - else - { - for (int i = 0; i < 2; ++i) - { - for (auto& pair : mStateSetCache[i]) - { - auto& stateset = pair.second; - osg::Uniform* uOldArray = stateset->getUniform("PointLightIndex"); - osg::Uniform* uOldCount = stateset->getUniform("PointLightCount"); - int prevCount; - uOldCount->get(prevCount); - int newCount = std::min(getMaxLights(), prevCount); - uOldCount->set(newCount); - - osg::ref_ptr newArray = uOldArray->getIntArray(); - newArray->resize(newCount); - - stateset->removeUniform(uOldArray); - stateset->addUniform(new osg::Uniform("PointLightIndex", newArray)); - } - mStateSetCache[i].clear(); - } - } + for (auto& cache : mStateSetCache) + cache.clear(); } void LightManager::updateSettings() @@ -1047,14 +935,10 @@ namespace SceneUtil void LightManager::initPerObjectUniform(int targetLights) { - auto* stateset = getOrCreateStateSet(); - setLightingMethod(LightingMethod::PerObjectUniform); setMaxLights(targetLights); - // ensures sunlight element in our uniform array is updated when there are no point lights in scene - stateset->setAttributeAndModes(new LightStateAttributePerObjectUniform({}, this), osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights())); + getOrCreateStateSet()->addUniform(generateLightBufferUniform(osg::Matrixf())); } void LightManager::initSingleUBO(int targetLights) @@ -1062,17 +946,8 @@ namespace SceneUtil setLightingMethod(LightingMethod::SingleUBO); setMaxLights(targetLights); - for (int i = 0; i < 2; ++i) - { - mLightBuffers[i] = new LightBuffer(getMaxLightsInScene()); - - osg::ref_ptr ubo = new osg::UniformBufferObject; - ubo->setUsage(GL_STREAM_DRAW); - - mLightBuffers[i]->getData()->setBufferObject(ubo); - } - - getOrCreateStateSet()->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); + mUBOManager = new UBOManager(getMaxLightsInScene()); + getOrCreateStateSet()->setAttributeAndModes(mUBOManager); } void LightManager::setLightingMethod(LightingMethod method) @@ -1171,6 +1046,12 @@ namespace SceneUtil osg::ref_ptr LightManager::getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix) { + if (getLightingMethod() == LightingMethod::PerObjectUniform) + { + mStateSetGenerator->mViewMatrix = *viewMatrix; + return mStateSetGenerator->generate(lightList, frameNum); + } + // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) if (getLightingMethod() == LightingMethod::SingleUBO) @@ -1205,7 +1086,7 @@ namespace SceneUtil return stateset; } - const std::vector& LightManager::getLightsInViewSpace(osgUtil::CullVisitor *cv, const osg::RefMatrix* viewMatrix, size_t frameNum) + const std::vector& LightManager::getLightsInViewSpace(osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum) { osg::Camera* camera = cv->getCurrentCamera(); @@ -1216,8 +1097,6 @@ namespace SceneUtil { it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; - bool isReflection = isReflectionCamera(camera); - for (const auto& transform : mLights) { osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix); @@ -1227,7 +1106,7 @@ namespace SceneUtil osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius); transformBoundingSphere(worldViewMat, viewBound); - if (!isReflection && mPointLightFadeEnd != 0.f) + if (transform.mLightSource->getLastAppliedFrame() != frameNum && mPointLightFadeEnd != 0.f) { const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); @@ -1236,6 +1115,7 @@ namespace SceneUtil auto* light = transform.mLightSource->getLight(frameNum); light->setDiffuse(light->getDiffuse() * fade); + transform.mLightSource->setLastAppliedFrame(frameNum); } // remove lights culled by this camera @@ -1272,16 +1152,25 @@ namespace SceneUtil void LightManager::updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum,const osg::RefMatrix* viewMatrix) { auto* light = lightSource->getLight(frameNum); - auto& buf = getLightBuffer(frameNum); + auto& buf = getUBOManager()->getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); buf->setAmbient(index, light->getAmbient()); buf->setAttenuationRadius(index, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); buf->setPosition(index, light->getPosition() * (*viewMatrix)); } + osg::ref_ptr LightManager::generateLightBufferUniform(const osg::Matrixf& sun) + { + osg::ref_ptr uniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights()); + uniform->setElement(0, sun); + + return uniform; + } + LightSource::LightSource() : mRadius(0.f) , mActorFade(1.f) + , mLastAppliedFrame(0) { setUpdateCallback(new CollectLightCallback); mId = sLightId++; @@ -1291,10 +1180,11 @@ namespace SceneUtil : osg::Node(copy, copyop) , mRadius(copy.mRadius) , mActorFade(copy.mActorFade) + , mLastAppliedFrame(copy.mLastAppliedFrame) { mId = sLightId++; - for (int i = 0; i < 2; ++i) + for (size_t i = 0; i < mLight.size(); ++i) mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } @@ -1358,7 +1248,7 @@ namespace SceneUtil { size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight(); - osg::StateSet* stateset = nullptr; + osg::ref_ptr stateset = nullptr; if (mLightList.size() > maxLights) { diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 4a7dc7dbe7..64b7e325ad 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -2,13 +2,11 @@ #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H #include -#include #include #include #include #include - #include #include #include @@ -46,7 +44,7 @@ namespace SceneUtil class LightSource : public osg::Node { // double buffered osg::Light's, since one of them may be in use by the draw thread at any given time - osg::ref_ptr mLight[2]; + std::array, 2> mLight; // LightSource will affect objects within this radius float mRadius; @@ -55,6 +53,8 @@ namespace SceneUtil float mActorFade; + unsigned int mLastAppliedFrame; + public: META_Node(SceneUtil, LightSource) @@ -107,6 +107,43 @@ namespace SceneUtil { return mId; } + + void setLastAppliedFrame(unsigned int lastAppliedFrame) + { + mLastAppliedFrame = lastAppliedFrame; + } + + unsigned int getLastAppliedFrame() const + { + return mLastAppliedFrame; + } + }; + + class UBOManager : public osg::StateAttribute + { + public: + UBOManager(int lightCount=1); + UBOManager(const UBOManager& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + void releaseGLObjects(osg::State* state) const override; + + int compare(const StateAttribute& sa) const override; + + META_StateAttribute(SceneUtil, UBOManager, osg::StateAttribute::LIGHT) + + void apply(osg::State& state) const override; + + auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; } + + private: + std::string generateDummyShader(int maxLightsInScene); + void initSharedLayout(osg::GLExtensions* ext, int handle, unsigned int frame) const; + + osg::ref_ptr mDummyProgram; + mutable bool mInitLayout; + mutable std::array, 2> mLightBuffers; + mutable std::array mDirty; + osg::ref_ptr mTemplate; }; /// @brief Decorator node implementing the rendering of any number of LightSources that can be anywhere in the subgraph. @@ -138,8 +175,6 @@ namespace SceneUtil LightManager(const LightManager& copy, const osg::CopyOp& copyop); - ~LightManager(); - /// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired. /// By default, it's ~0u i.e. always on. /// If you have some views that do not require lighting, then set the Camera's cull mask to not include @@ -176,7 +211,7 @@ namespace SceneUtil auto& getLightIndexMap(size_t frameNum) { return mLightIndexMaps[frameNum%2]; } - auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; } + auto& getUBOManager() { return mUBOManager; } osg::Matrixf getSunlightBuffer(size_t frameNum) const { return mSunlightBuffers[frameNum%2]; } void setSunlightBuffer(const osg::Matrixf& buffer, size_t frameNum) { mSunlightBuffers[frameNum%2] = buffer; } @@ -190,6 +225,8 @@ namespace SceneUtil /// Not thread safe, it is the responsibility of the caller to stop/start threading on the viewer void updateMaxLights(); + osg::ref_ptr generateLightBufferUniform(const osg::Matrixf& sun); + private: void initFFP(int targetLights); void initPerObjectUniform(int targetLights); @@ -223,8 +260,6 @@ namespace SceneUtil osg::ref_ptr mSun; - osg::ref_ptr mLightBuffers[2]; - osg::Matrixf mSunlightBuffers[2]; // < Light ID , Buffer Index > @@ -233,6 +268,8 @@ namespace SceneUtil std::unique_ptr mStateSetGenerator; + osg::ref_ptr mUBOManager; + LightingMethod mLightingMethod; float mPointLightRadiusMultiplier; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 9da0d6a40e..964b8bc4c2 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -123,8 +123,10 @@ void registerSerializers() "Resource::TemplateRef", "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", + "SceneUtil::UBOManager", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", + "SceneUtil::FFPLightStateAttribute", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", @@ -133,7 +135,6 @@ void registerSerializers() "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", - "NifOsg::LightManagerStateAttribute", "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", From eceed558be6df1b31958a8376bb12f5797c8efa6 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 19 Nov 2021 16:21:17 +0100 Subject: [PATCH 1731/2859] Fix coverity uninitialized variables --- components/lua_ui/text.cpp | 5 ++++- components/lua_ui/text.hpp | 1 + components/lua_ui/textedit.hpp | 4 ++-- components/lua_ui/widget.cpp | 12 +++++++----- components/lua_ui/widget.hpp | 2 +- components/lua_ui/window.cpp | 6 ++++++ components/lua_ui/window.hpp | 3 +++ 7 files changed, 24 insertions(+), 9 deletions(-) diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index 241af981b0..571bdaf403 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -3,10 +3,13 @@ namespace LuaUi { + LuaText::LuaText() + : mAutoSized(true) + {} + void LuaText::initialize() { WidgetExtension::initialize(); - mAutoSized = true; } bool LuaText::setPropertyRaw(std::string_view name, sol::object value) diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index 08de275d23..90a3fc563c 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -12,6 +12,7 @@ namespace LuaUi MYGUI_RTTI_DERIVED(LuaText) public: + LuaText(); virtual void initialize() override; private: diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index 7d3291692c..3eb5b471c3 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -11,8 +11,8 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaTextEdit) - protected: - bool setPropertyRaw(std::string_view name, sol::object value) override; + protected: + bool setPropertyRaw(std::string_view name, sol::object value) override; }; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 5cbf1f318d..3ce07273f8 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -8,6 +8,13 @@ namespace LuaUi { + WidgetExtension::WidgetExtension() + : mForcedCoord() + , mAbsoluteCoord() + , mRelativeCoord() + , mAnchor() + {} + void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const { auto it = mCallbacks.find(name); @@ -27,11 +34,6 @@ namespace LuaUi void WidgetExtension::initialize() { - mAbsoluteCoord = MyGUI::IntCoord(); - mRelativeCoord = MyGUI::FloatCoord(); - mAnchor = MyGUI::FloatSize(); - mForcedCoord = MyGUI::IntCoord(); - // \todo might be more efficient to only register these if there are Lua callbacks mWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::keyPress); mWidget->eventKeyButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::keyRelease); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index fe1ad46716..48b169572f 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -19,6 +19,7 @@ namespace LuaUi class WidgetExtension { public: + WidgetExtension(); // must be called after creating the underlying MyGUI::Widget void create(lua_State* lua, MyGUI::Widget* self); // must be called after before destroying the underlying MyGUI::Widget @@ -42,7 +43,6 @@ namespace LuaUi void updateCoord(); protected: - ~WidgetExtension() {} sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; diff --git a/components/lua_ui/window.cpp b/components/lua_ui/window.cpp index 874ccb9fa4..b7bb426a63 100644 --- a/components/lua_ui/window.cpp +++ b/components/lua_ui/window.cpp @@ -4,6 +4,12 @@ namespace LuaUi { + LuaWindow::LuaWindow() + : mCaption() + , mPreviousMouse() + , mChangeScale() + {} + void LuaWindow::initialize() { WidgetExtension::initialize(); diff --git a/components/lua_ui/window.hpp b/components/lua_ui/window.hpp index d92bf8dcbf..c04b0a6055 100644 --- a/components/lua_ui/window.hpp +++ b/components/lua_ui/window.hpp @@ -13,6 +13,9 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaWindow) + public: + LuaWindow(); + private: // \todo replace with LuaText when skins are properly implemented MyGUI::TextBox* mCaption; From d5ca091d6e761fb8518f38efbfb8fe22575e5f0d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 18 Nov 2021 15:11:39 +0100 Subject: [PATCH 1732/2859] Make util.rotateX, util.rotateY, rotate.Z consistent with morrowind rotation --- apps/openmw_test_suite/lua/test_utilpackage.cpp | 8 ++++---- components/lua/utilpackage.cpp | 6 +++--- files/lua_api/openmw/util.lua | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index ead3cecc6f..21d2a344d4 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -93,13 +93,13 @@ namespace EXPECT_EQ(getAsString(lua, "moveAndScale * v(300, 200, 100)"), "(156, 222, 68)"); EXPECT_THAT(getAsString(lua, "moveAndScale"), AllOf(StartsWith("TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) "), EndsWith(" }"))); EXPECT_EQ(getAsString(lua, "T.identity"), "TransformM{ }"); - lua.safe_script("rx = T.rotateX(math.pi / 2)"); - lua.safe_script("ry = T.rotateY(math.pi / 2)"); - lua.safe_script("rz = T.rotateZ(math.pi / 2)"); + lua.safe_script("rx = T.rotateX(-math.pi / 2)"); + lua.safe_script("ry = T.rotateY(-math.pi / 2)"); + lua.safe_script("rz = T.rotateZ(-math.pi / 2)"); EXPECT_LT(get(lua, "(rx * v(1, 2, 3) - v(1, -3, 2)):length()"), 1e-6); EXPECT_LT(get(lua, "(ry * v(1, 2, 3) - v(3, 2, -1)):length()"), 1e-6); EXPECT_LT(get(lua, "(rz * v(1, 2, 3) - v(-2, 1, 3)):length()"), 1e-6); - lua.safe_script("rot = T.rotate(math.pi / 2, v(-1, -1, 0)) * T.rotateZ(-math.pi / 4)"); + lua.safe_script("rot = T.rotate(math.pi / 2, v(-1, -1, 0)) * T.rotateZ(math.pi / 4)"); EXPECT_THAT(getAsString(lua, "rot"), HasSubstr("TransformQ")); EXPECT_LT(get(lua, "(rot * v(1, 0, 0) - v(0, 0, 1)):length()"), 1e-6); EXPECT_LT(get(lua, "(rot * rot:inverse() * v(1, 0, 0) - v(1, 0, 0)):length()"), 1e-6); diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index 17cb64461a..b68fc7afa4 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -105,9 +105,9 @@ namespace LuaUtil [](const Vec3& v) { return TransformM{osg::Matrixf::scale(v)}; }, [](float x, float y, float z) { return TransformM{osg::Matrixf::scale(x, y, z)}; }); transforms["rotate"] = [](float angle, const Vec3& axis) { return TransformQ{osg::Quat(angle, axis)}; }; - transforms["rotateX"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(1, 0, 0))}; }; - transforms["rotateY"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 1, 0))}; }; - transforms["rotateZ"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 0, 1))}; }; + transforms["rotateX"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(-1, 0, 0))}; }; + transforms["rotateY"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, -1, 0))}; }; + transforms["rotateZ"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 0, -1))}; }; transMType[sol::meta_function::multiplication] = sol::overload( [](const TransformM& a, const Vec3& b) { return a.mM.preMult(b); }, diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index 84e640ff40..fdb140ad53 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -32,7 +32,7 @@ -- v:length() -- 5.0 length -- v:length2() -- 25.0 square of the length -- v:normalize() -- vector2(3/5, 4/5) --- v:rotate(radians) -- rotate clockwise (returns rotated vector) +-- v:rotate(radians) -- rotate counterclockwise (returns rotated vector) -- v1:dot(v2) -- dot product (returns a number) -- v1 * v2 -- dot product -- v1 + v2 -- vector addition @@ -183,26 +183,26 @@ ------------------------------------------------------------------------------- --- Rotation (any axis). +-- Rotation around a vector (counterclockwise if the vector points to us). -- @function [parent=#TRANSFORM] rotate -- @param #number angle -- @param #Vector3 axis. -- @return #Transform. ------------------------------------------------------------------------------- --- X-axis rotation. +-- X-axis rotation (equivalent to `rotate(angle, vector3(-1, 0, 0))`). -- @function [parent=#TRANSFORM] rotateX -- @param #number angle -- @return #Transform. ------------------------------------------------------------------------------- --- Y-axis rotation. +-- Y-axis rotation (equivalent to `rotate(angle, vector3(0, -1, 0))`). -- @function [parent=#TRANSFORM] rotateY -- @param #number angle -- @return #Transform. ------------------------------------------------------------------------------- --- Z-axis rotation. +-- Z-axis rotation (equivalent to `rotate(angle, vector3(0, 0, -1))`). -- @function [parent=#TRANSFORM] rotateZ -- @param #number angle -- @return #Transform. From 47cbdcba15a7e38d50ab6407c13dac6cf67d29cb Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 20 Jun 2021 00:57:41 +0200 Subject: [PATCH 1733/2859] Camera refactoring --- apps/openmw/mwrender/camera.cpp | 261 +++++++++------------- apps/openmw/mwrender/camera.hpp | 38 ++-- apps/openmw/mwrender/renderingmanager.cpp | 6 +- apps/openmw/mwrender/renderingmanager.hpp | 2 - apps/openmw/mwrender/viewovershoulder.cpp | 8 +- apps/openmw/mwworld/worldimp.cpp | 23 +- 6 files changed, 143 insertions(+), 195 deletions(-) diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index d1e842790b..90f2a28329 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -56,21 +56,19 @@ namespace MWRender mCamera(camera), mAnimation(nullptr), mFirstPersonView(true), - mMode(Mode::Normal), + mMode(Mode::FirstPerson), mVanityAllowed(true), mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")), mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")), mNearest(30.f), mFurthest(800.f), mIsNearest(false), + mProcessViewChange(false), mHeight(124.f), mBaseCameraDistance(Settings::Manager::getFloat("third person camera distance", "Camera")), mPitch(0.f), mYaw(0.f), mRoll(0.f), - mVanityToggleQueued(false), - mVanityToggleQueuedValue(false), - mViewModeToggleQueued(false), mCameraDistance(0.f), mMaxNextCameraDistance(800.f), mFocalPointCurrentOffset(osg::Vec2d()), @@ -100,7 +98,7 @@ namespace MWRender mCamera->removeUpdateCallback(mUpdateCallback); } - osg::Vec3d Camera::getFocalPoint() const + osg::Vec3d Camera::getTrackingNodePosition() const { if (!mTrackingNode) return osg::Vec3d(); @@ -108,54 +106,37 @@ namespace MWRender if (nodepaths.empty()) return osg::Vec3d(); osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); + return worldMat.getTrans(); + } - osg::Vec3d position = worldMat.getTrans(); - if (isFirstPerson()) - position.z() += mHeadBobbingOffset; - else - { - position.z() += mHeight * mHeightScale; + osg::Vec3d Camera::getThirdPersonBasePosition() const + { + osg::Vec3d position = getTrackingNodePosition(); + position.z() += mHeight * mHeightScale; - // We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling. - // Needed because character's head can be a bit higher than collision area. - position.z() -= 10.f; + // We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling. + // Needed because character's head can be a bit higher than collision area. + position.z() -= 10.f; - position += getFocalPointOffset() + mFocalPointAdjustment; - } return position; } osg::Vec3d Camera::getFocalPointOffset() const { osg::Vec3d offset(0, 0, 10.f); - offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw()); - offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw()); + offset.x() += mFocalPointCurrentOffset.x() * cos(mYaw); + offset.y() += mFocalPointCurrentOffset.x() * sin(mYaw); offset.z() += mFocalPointCurrentOffset.y(); return offset; } - void Camera::getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const - { - focal = getFocalPoint(); - osg::Vec3d offset(0,0,0); - if (!isFirstPerson()) - { - osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); - offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); - } - camera = focal + offset; - } - void Camera::updateCamera(osg::Camera *cam) { - osg::Vec3d focal, position; - getPosition(focal, position); - osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, osg::Vec3d(0, 0, 1)); osg::Vec3d forward = orient * osg::Vec3d(0,1,0); osg::Vec3d up = orient * osg::Vec3d(0,0,1); - cam->setViewMatrixAsLookAt(position, position + forward, up); + cam->setViewMatrixAsLookAt(mPosition, mPosition + forward, up); } void Camera::updateHeadBobbing(float duration) { @@ -176,42 +157,12 @@ namespace MWRender mRoll = osg::sign(stepState) * effect * coef * maxRoll; // range from -maxRoll to maxRoll } - void Camera::reset() - { - togglePreviewMode(false); - toggleVanityMode(false); - if (!mFirstPersonView) - toggleViewMode(); - } - - void Camera::rotateCamera(float pitch, float yaw, bool adjust) - { - if (adjust) - { - pitch += getPitch(); - yaw += getYaw(); - } - setYaw(yaw); - setPitch(pitch); - } - void Camera::update(float duration, bool paused) { - if (mAnimation->upperBodyReady()) - { - // Now process the view changes we queued earlier - if (mVanityToggleQueued) - { - toggleVanityMode(mVanityToggleQueuedValue); - mVanityToggleQueued = false; - } - if (mViewModeToggleQueued) - { - togglePreviewMode(false); - toggleViewMode(); - mViewModeToggleQueued = false; - } - } + if (mQueuedMode && mAnimation->upperBodyReady()) + setMode(*mQueuedMode); + if (mProcessViewChange) + processViewChange(); if (paused) return; @@ -222,9 +173,9 @@ namespace MWRender && (mFirstPersonView || mShowCrosshairInThirdPersonMode)); if(mMode == Mode::Vanity) - rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true); + setYaw(mYaw + osg::DegreesToRadians(3.f) * duration); - if (isFirstPerson() && mHeadBobbingEnabled) + if (mMode == Mode::FirstPerson && mHeadBobbingEnabled) updateHeadBobbing(duration); else mRoll = mHeadBobbingOffset = 0; @@ -244,19 +195,24 @@ namespace MWRender void Camera::updatePosition() { - mFocalPointAdjustment = osg::Vec3d(); - if (isFirstPerson()) + if (mMode == Mode::Static) return; + if (mMode == Mode::FirstPerson) + { + mPosition = getTrackingNodePosition(); + mPosition.z() += mHeadBobbingOffset; + return; + } - const float cameraObstacleLimit = 5.0f; - const float focalObstacleLimit = 10.f; - const int collisionType = (MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor); + constexpr float cameraObstacleLimit = 5.0f; + constexpr float focalObstacleLimit = 10.f; const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + constexpr int collisionType = (MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor); // Adjust focal point to prevent clipping. - osg::Vec3d focal = getFocalPoint(); osg::Vec3d focalOffset = getFocalPointOffset(); + osg::Vec3d focal = getThirdPersonBasePosition() + focalOffset; float offsetLen = focalOffset.length(); if (offsetLen > 0) { @@ -264,39 +220,73 @@ namespace MWRender if (result.mHit) { double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; - mFocalPointAdjustment = focalOffset * std::max(-1.0, adjustmentCoef); + focal += focalOffset * std::max(-1.0, adjustmentCoef); } } - // Calculate camera distance. + // Calculate offset from focal point. mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection(); if (mDynamicCameraDistanceEnabled) mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance); - osg::Vec3d cameraPos; - getPosition(focal, cameraPos); - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit, collisionType); + + osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); + osg::Vec3d offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, collisionType); if (result.mHit) + { mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); + offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); + } + + mPosition = focal + offset; } - void Camera::updateStandingPreviewMode() + void Camera::setMode(Mode newMode, bool force) { - if (!mStandingPreviewAllowed) + if (newMode == Mode::StandingPreview) + newMode = Mode::ThirdPerson; + if (mMode == newMode) + return; + Mode oldMode = mMode; + if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && !mAnimation->upperBodyReady()) + { + // Changing the view will stop all playing animations, so if we are playing + // anything important, queue the view change for later + mQueuedMode = newMode; return; + } + mMode = newMode; + mQueuedMode = std::nullopt; + if (newMode == Mode::FirstPerson) + mFirstPersonView = true; + else if (newMode == Mode::ThirdPerson) + mFirstPersonView = false; + calculateDeferredRotation(); + if (oldMode == Mode::FirstPerson || newMode == Mode::FirstPerson) + { + instantTransition(); + mProcessViewChange = true; + } + else if (newMode == Mode::ThirdPerson) + updateStandingPreviewMode(); + } + + void Camera::updateStandingPreviewMode() + { float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); bool combat = mTrackingPtr.getClass().isActor() && mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing; - bool standingStill = speed == 0 && !combat && !mFirstPersonView; + bool standingStill = speed == 0 && !combat && mStandingPreviewAllowed; if (!standingStill && mMode == Mode::StandingPreview) { - mMode = Mode::Normal; + mMode = Mode::ThirdPerson; calculateDeferredRotation(); } - else if (standingStill && mMode == Mode::Normal) + else if (standingStill && mMode == Mode::ThirdPerson) mMode = Mode::StandingPreview; } - void Camera::setFocalPointTargetOffset(osg::Vec2d v) + void Camera::setFocalPointTargetOffset(const osg::Vec2d& v) { mFocalPointTargetOffset = v; mPreviousTransitionSpeed = mFocalPointTransitionSpeed; @@ -346,78 +336,35 @@ namespace MWRender void Camera::toggleViewMode(bool force) { - // Changing the view will stop all playing animations, so if we are playing - // anything important, queue the view change for later - if (!mAnimation->upperBodyReady() && !force) - { - mViewModeToggleQueued = true; - return; - } - else - mViewModeToggleQueued = false; - - mFirstPersonView = !mFirstPersonView; - updateStandingPreviewMode(); - instantTransition(); - processViewChange(); + setMode(mFirstPersonView ? Mode::ThirdPerson : Mode::FirstPerson, force); } void Camera::allowVanityMode(bool allow) { if (!allow && mMode == Mode::Vanity) - { - disableDeferredPreviewRotation(); toggleVanityMode(false); - } mVanityAllowed = allow; } bool Camera::toggleVanityMode(bool enable) { - // Changing the view will stop all playing animations, so if we are playing - // anything important, queue the view change for later - if (mFirstPersonView && !mAnimation->upperBodyReady()) - { - mVanityToggleQueued = true; - mVanityToggleQueuedValue = enable; - return false; - } - - if (!mVanityAllowed && enable) - return false; - - if ((mMode == Mode::Vanity) == enable) - return true; - mMode = enable ? Mode::Vanity : Mode::Normal; - if (!mDeferredRotationAllowed) - disableDeferredPreviewRotation(); if (!enable) - calculateDeferredRotation(); - - processViewChange(); - return true; + setMode(mFirstPersonView ? Mode::FirstPerson : Mode::ThirdPerson, false); + else if (mVanityAllowed) + setMode(Mode::Vanity, false); + return (mMode == Mode::Vanity) == enable; } void Camera::togglePreviewMode(bool enable) { if (mFirstPersonView && !mAnimation->upperBodyReady()) return; - - if((mMode == Mode::Preview) == enable) + if ((mMode == Mode::Preview) == enable) return; - - mMode = enable ? Mode::Preview : Mode::Normal; - if (mMode == Mode::Normal) - updateStandingPreviewMode(); - else if (mFirstPersonView) - instantTransition(); - if (mMode == Mode::Normal) - { - if (!mDeferredRotationAllowed) - disableDeferredPreviewRotation(); - calculateDeferredRotation(); - } - processViewChange(); + if (enable) + setMode(Mode::Preview); + else + setMode(mFirstPersonView ? Mode::FirstPerson : Mode::ThirdPerson); } void Camera::setSneakOffset(float offset) @@ -439,16 +386,16 @@ namespace MWRender float Camera::getCameraDistance() const { - if (isFirstPerson()) - return 0.f; - return mCameraDistance; + return mMode == Mode::FirstPerson ? 0.f : mCameraDistance; } void Camera::adjustCameraDistance(float delta) { - if (!isFirstPerson()) + if (mMode == Mode::Static) + return; + if (mMode != Mode::FirstPerson) { - if(isNearest() && delta < 0.f && getMode() != Mode::Preview && getMode() != Mode::Vanity) + if (mIsNearest && delta < 0.f && mMode != Mode::Preview && mMode != Mode::Vanity) toggleViewMode(); else mBaseCameraDistance = std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance) + delta; @@ -480,12 +427,12 @@ namespace MWRender void Camera::setAnimation(NpcAnimation *anim) { mAnimation = anim; - processViewChange(); + mProcessViewChange = true; } void Camera::processViewChange() { - if(isFirstPerson()) + if (mMode == Mode::FirstPerson) { mAnimation->setViewMode(NpcAnimation::VM_FirstPerson); mTrackingNode = mAnimation->getNode("Camera"); @@ -503,12 +450,12 @@ namespace MWRender else mHeightScale = 1.f; } - rotateCamera(getPitch(), getYaw(), false); + mProcessViewChange = false; } void Camera::applyDeferredPreviewRotationToPlayer(float dt) { - if (isVanityOrPreviewModeEnabled() || mTrackingPtr.isEmpty()) + if (mMode != Mode::ThirdPerson || mTrackingPtr.isEmpty()) return; osg::Vec3f rot = mDeferredRotation; @@ -541,6 +488,8 @@ namespace MWRender void Camera::rotateCameraToTrackingPtr() { + if (mMode == Mode::Static) + return; setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x()); setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z()); } @@ -555,8 +504,13 @@ namespace MWRender void Camera::calculateDeferredRotation() { + if (mMode == Mode::Static) + { + mDeferredRotation = osg::Vec3f(); + return; + } MWWorld::Ptr ptr = mTrackingPtr; - if (isVanityOrPreviewModeEnabled() || ptr.isEmpty()) + if (mMode == Mode::Preview || mMode == Mode::Vanity || ptr.isEmpty()) return; if (mFirstPersonView) { @@ -566,6 +520,13 @@ namespace MWRender mDeferredRotation.x() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[0] - mPitch); mDeferredRotation.z() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[2] - mYaw); + if (!mDeferredRotationAllowed) + mDeferredRotationDisabled = true; + } + + bool Camera::isVanityOrPreviewModeEnabled() const + { + return mMode == Mode::Vanity || mMode == Mode::Preview || mMode == Mode::StandingPreview; } } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 1a5477e89c..cf25aa4852 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWRENDER_CAMERA_H #define GAME_MWRENDER_CAMERA_H +#include #include #include @@ -24,7 +25,7 @@ namespace MWRender class Camera { public: - enum class Mode { Normal, Vanity, Preview, StandingPreview }; + enum class Mode : int {Static = 0, FirstPerson = 1, ThirdPerson = 2, Vanity = 3, Preview = 4, StandingPreview = 5}; private: MWWorld::Ptr mTrackingPtr; @@ -35,8 +36,12 @@ namespace MWRender NpcAnimation *mAnimation; + // Always 'true' if mMode == `FirstPerson`. Also it is 'true' in `Vanity` or `Preview` modes if + // the camera should return to `FirstPerson` view after it. bool mFirstPersonView; + Mode mMode; + std::optional mQueuedMode; bool mVanityAllowed; bool mStandingPreviewAllowed; bool mDeferredRotationAllowed; @@ -45,17 +50,15 @@ namespace MWRender float mFurthest; bool mIsNearest; + bool mProcessViewChange; + float mHeight, mBaseCameraDistance; float mPitch, mYaw, mRoll; - - bool mVanityToggleQueued; - bool mVanityToggleQueuedValue; - bool mViewModeToggleQueued; + osg::Vec3d mPosition; float mCameraDistance; float mMaxNextCameraDistance; - osg::Vec3d mFocalPointAdjustment; osg::Vec2d mFocalPointCurrentOffset; osg::Vec2d mFocalPointTargetOffset; float mFocalPointTransitionSpeedCoef; @@ -78,6 +81,8 @@ namespace MWRender float mTotalMovement; // Needed for head bobbing. void updateHeadBobbing(float duration); + osg::Vec3d getTrackingNodePosition() const; + osg::Vec3d getFocalPointOffset() const; void updateFocalPointOffset(float duration); void updatePosition(); float getCameraDistanceCorrection() const; @@ -99,7 +104,7 @@ namespace MWRender MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; } void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; } - void setFocalPointTargetOffset(osg::Vec2d v); + void setFocalPointTargetOffset(const osg::Vec2d& v); void instantTransition(); void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; } void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; } @@ -108,11 +113,8 @@ namespace MWRender void updateCamera(osg::Camera* cam); /// Reset to defaults - void reset(); + void reset() { setMode(Mode::FirstPerson); } - /// Set where the camera is looking at. Uses Morrowind (euler) angles - /// \param rot Rotation angles in radians - void rotateCamera(float pitch, float yaw, bool adjust); void rotateCameraToTrackingPtr(); float getYaw() const { return mYaw; } @@ -136,8 +138,6 @@ namespace MWRender /// \brief Lowers the camera for sneak. void setSneakOffset(float offset); - bool isFirstPerson() const { return mFirstPersonView && mMode == Mode::Normal; } - void processViewChange(); void update(float duration, bool paused=false); @@ -149,16 +149,12 @@ namespace MWRender void setAnimation(NpcAnimation *anim); - osg::Vec3d getFocalPoint() const; - osg::Vec3d getFocalPointOffset() const; - - /// Stores focal and camera world positions in passed arguments - void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const; + osg::Vec3d getThirdPersonBasePosition() const; + const osg::Vec3d& getPosition() const { return mPosition; } - bool isVanityOrPreviewModeEnabled() const { return mMode != Mode::Normal; } + bool isVanityOrPreviewModeEnabled() const; Mode getMode() const { return mMode; } - - bool isNearest() const { return mIsNearest; } + void setMode(Mode mode, bool force = true); }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2ba18378f9..0542d8ed90 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -830,11 +830,7 @@ namespace MWRender mViewOverShoulderController->update(); mCamera->update(dt, paused); - osg::Vec3d focal, cameraPos; - mCamera->getPosition(focal, cameraPos); - mCurrentCameraPos = cameraPos; - - bool isUnderwater = mWater->isUnderwater(cameraPos); + bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater)); mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater)); setFogColor(mFog->getFogColor(isUnderwater)); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 99d0bb5f5e..672f48c64f 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -207,7 +207,6 @@ namespace MWRender // camera stuff Camera* getCamera() { return mCamera.get(); } - const osg::Vec3f& getCameraPosition() const { return mCurrentCameraPos; } /// temporarily override the field of view with given value. void overrideFieldOfView(float val); @@ -285,7 +284,6 @@ namespace MWRender osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; std::unique_ptr mViewOverShoulderController; - osg::Vec3f mCurrentCameraPos; osg::ref_ptr mStateUpdater; osg::ref_ptr mSharedUniformStateUpdater; diff --git a/apps/openmw/mwrender/viewovershoulder.cpp b/apps/openmw/mwrender/viewovershoulder.cpp index 799e34c992..bd45480cf3 100644 --- a/apps/openmw/mwrender/viewovershoulder.cpp +++ b/apps/openmw/mwrender/viewovershoulder.cpp @@ -33,7 +33,7 @@ namespace MWRender void ViewOverShoulderController::update() { - if (mCamera->isFirstPerson()) + if (mCamera->getMode() == Camera::Mode::FirstPerson || mCamera->getMode() == Camera::Mode::Static) return; Mode oldMode = mMode; @@ -54,7 +54,7 @@ namespace MWRender if (mCamera->getMode() == Camera::Mode::Vanity) // Player doesn't touch controls for a long time. Transition should be very slow. mCamera->setFocalPointTransitionSpeed(0.2f); - else if ((oldMode == Mode::Combat || mMode == Mode::Combat) && mCamera->getMode() == Camera::Mode::Normal) + else if ((oldMode == Mode::Combat || mMode == Mode::Combat) && mCamera->getMode() == Camera::Mode::ThirdPerson) // Transition to/from combat mode and we are not it preview mode. Should be fast. mCamera->setFocalPointTransitionSpeed(5.f); else @@ -77,14 +77,14 @@ namespace MWRender void ViewOverShoulderController::trySwitchShoulder() { - if (mCamera->getMode() != Camera::Mode::Normal) + if (mCamera->getMode() != Camera::Mode::ThirdPerson) return; const float limitToSwitch = 120; // switch to other shoulder if wall is closer than this limit const float limitToSwitchBack = 300; // switch back to default shoulder if there is no walls at this distance auto orient = osg::Quat(mCamera->getYaw(), osg::Vec3d(0,0,1)); - osg::Vec3d playerPos = mCamera->getFocalPoint() - mCamera->getFocalPointOffset(); + osg::Vec3d playerPos = mCamera->getThirdPersonBasePosition(); MWBase::World* world = MWBase::Environment::get().getWorld(); osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f434d059da..71f44f125d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -595,13 +595,7 @@ namespace MWWorld void World::useDeathCamera() { - if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() ) - { - mRendering->getCamera()->togglePreviewMode(false); - mRendering->getCamera()->toggleVanityMode(false); - } - if(mRendering->getCamera()->isFirstPerson()) - mRendering->getCamera()->toggleViewMode(true); + mRendering->getCamera()->setMode(MWRender::Camera::Mode::ThirdPerson); } MWWorld::Player& World::getPlayer() @@ -1858,7 +1852,7 @@ namespace MWWorld } bool isWerewolf = player.getClass().getNpcStats(player).isWerewolf(); - bool isFirstPerson = mRendering->getCamera()->isFirstPerson(); + bool isFirstPerson = this->isFirstPerson(); if (isWerewolf && isFirstPerson) { float werewolfFov = Fallback::Map::getFloat("General_Werewolf_FOV"); @@ -1928,11 +1922,12 @@ namespace MWWorld void World::updateSoundListener() { + osg::Vec3f cameraPosition = mRendering->getCamera()->getPosition(); const ESM::Position& refpos = getPlayerPtr().getRefData().getPosition(); osg::Vec3f listenerPos; if (isFirstPerson()) - listenerPos = mRendering->getCameraPosition(); + listenerPos = cameraPosition; else listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(getPlayerPtr()).z()); @@ -1943,7 +1938,7 @@ namespace MWWorld osg::Vec3f forward = listenerOrient * osg::Vec3f(0,1,0); osg::Vec3f up = listenerOrient * osg::Vec3f(0,0,1); - bool underwater = isUnderwater(getPlayerPtr().getCell(), mRendering->getCameraPosition()); + bool underwater = isUnderwater(getPlayerPtr().getCell(), cameraPosition); MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } @@ -2395,7 +2390,7 @@ namespace MWWorld bool World::isFirstPerson() const { - return mRendering->getCamera()->isFirstPerson(); + return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::FirstPerson; } bool World::isPreviewModeEnabled() const @@ -2430,10 +2425,12 @@ namespace MWWorld bool World::vanityRotateCamera(float * rot) { - if(!mRendering->getCamera()->isVanityOrPreviewModeEnabled()) + auto* camera = mRendering->getCamera(); + if(!camera->isVanityOrPreviewModeEnabled()) return false; - mRendering->getCamera()->rotateCamera(rot[0], rot[2], true); + camera->setPitch(camera->getPitch() + rot[0]); + camera->setYaw(camera->getYaw() + rot[2]); return true; } From e56ee2c73519f26afa728860bb6bc23813a24774 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 17 Nov 2021 03:05:50 +0100 Subject: [PATCH 1734/2859] Apply lua handlers for user input in the main thread in order to reduce latency. --- apps/openmw/engine.cpp | 5 +++- apps/openmw/mwlua/luamanagerimp.cpp | 23 +++++++++++-------- apps/openmw/mwlua/luamanagerimp.hpp | 2 +- apps/openmw/mwlua/playerscripts.hpp | 5 +++- .../lua-scripting/engine_handlers.rst | 5 +++- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 67944e4546..70d5a26c7a 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -293,6 +293,10 @@ bool OMW::Engine::frame(float frametime) // Main menu opened? Then scripts are also paused. bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu); + // Should be called after input manager update and before any change to the game world. + // It applies to the game world queued changes from the previous frame. + mLuaManager->synchronizedUpdate(paused, frametime); + // update game state { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); @@ -874,7 +878,6 @@ public: } else update(); - mEngine->mLuaManager->applyQueuedChanges(); }; void join() diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index dae11513b5..f55ca20f02 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -132,14 +132,6 @@ namespace MWLua mQueuedCallbacks.clear(); // Engine handlers in local scripts - PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); - if (playerScripts && !paused) - { - for (const auto& event : mInputEvents) - playerScripts->processInputEvent(event); - } - mInputEvents.clear(); - for (const LocalEngineEvent& e : mLocalEngineEvents) { LObject obj(e.mDest, objectRegistry); @@ -180,8 +172,21 @@ namespace MWLua mGlobalScripts.update(dt); } - void LuaManager::applyQueuedChanges() + void LuaManager::synchronizedUpdate(bool paused, float dt) { + if (mPlayer.isEmpty()) + return; // The game is not started yet. + + // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. + PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); + if (playerScripts && !paused) + { + for (const auto& event : mInputEvents) + playerScripts->processInputEvent(event); + playerScripts->inputUpdate(dt); + } + mInputEvents.clear(); + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); for (const std::string& message : mUIMessages) windowManager->messageBox(message); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index f5ffe9d258..56d20c2720 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -32,7 +32,7 @@ namespace MWLua void update(bool paused, float dt); // Called by engine.cpp from the main thread. Can use scene graph. - void applyQueuedChanges(); + void synchronizedUpdate(bool paused, float dt); // Available everywhere through the MWBase::LuaManager interface. // LuaManager queues these events and propagates to scripts on the next `update` call. diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index 0393a1375d..e8cdd120ac 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -17,7 +17,7 @@ namespace MWLua { registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, - &mActionHandlers}); + &mActionHandlers, &mInputUpdateHandlers}); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) @@ -43,12 +43,15 @@ namespace MWLua } } + void inputUpdate(float dt) { callEngineHandlers(mInputUpdateHandlers, dt); } + private: EngineHandlerList mKeyPressHandlers{"onKeyPress"}; EngineHandlerList mKeyReleaseHandlers{"onKeyRelease"}; EngineHandlerList mControllerButtonPressHandlers{"onControllerButtonPress"}; EngineHandlerList mControllerButtonReleaseHandlers{"onControllerButtonRelease"}; EngineHandlerList mActionHandlers{"onInputAction"}; + EngineHandlerList mInputUpdateHandlers{"onInputUpdate"}; }; } diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index ef74684182..4f5f99965a 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -10,7 +10,7 @@ Engine handler is a function defined by a script, that can be called by the engi | | | `be assigned to a script in openmw-cs (not yet implemented)`. | | | | ``onInterfaceOverride`` can be called before ``onInit``. | +----------------------------------+----------------------------------------------------------------------+ -| onUpdate(dt) | | Called every frame if game not paused. `dt` is the time | +| onUpdate(dt) | | Called every frame if the game is not paused. `dt` is the time | | | | from the last update in seconds. | +----------------------------------+----------------------------------------------------------------------+ | onSave() -> savedData | | Called when the game is saving. May be called in inactive | @@ -44,6 +44,9 @@ Engine handler is a function defined by a script, that can be called by the engi +----------------------------------+----------------------------------------------------------------------+ | **Only for local scripts attached to a player** | +----------------------------------+----------------------------------------------------------------------+ +| onInputUpdate(dt) | | Called every frame (if the game is not paused) right after | +| | | processing user input. Use it only for latency-critical stuff. | ++----------------------------------+----------------------------------------------------------------------+ | onKeyPress(key) | | `Key `_ is pressed. | | | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` | +----------------------------------+----------------------------------------------------------------------+ From f42badd7be5a7f38f25abf911b695434921e1671 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 14 Jun 2021 19:58:04 +0200 Subject: [PATCH 1735/2859] Dehardcode camera --- apps/openmw/mwbase/world.hpp | 5 +- apps/openmw/mwinput/actionmanager.cpp | 55 +---- apps/openmw/mwinput/actionmanager.hpp | 5 - apps/openmw/mwinput/controllermanager.cpp | 32 +-- apps/openmw/mwinput/controllermanager.hpp | 2 - apps/openmw/mwinput/controlswitch.cpp | 4 - apps/openmw/mwlua/camerabindings.cpp | 72 +++++- apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwrender/camera.cpp | 231 +++++------------- apps/openmw/mwrender/camera.hpp | 163 ++++++------ apps/openmw/mwrender/renderingmanager.cpp | 5 - apps/openmw/mwrender/renderingmanager.hpp | 2 - apps/openmw/mwrender/viewovershoulder.cpp | 110 --------- apps/openmw/mwrender/viewovershoulder.hpp | 30 --- apps/openmw/mwworld/worldimp.cpp | 15 +- apps/openmw/mwworld/worldimp.hpp | 5 +- files/builtin_scripts/builtin.omwscripts | 1 + files/builtin_scripts/scripts/omw/camera.lua | 221 +++++++++++++++++ .../scripts/omw/head_bobbing.lua | 51 ++++ .../scripts/omw/third_person.lua | 139 +++++++++++ files/openmw.cfg | 1 + files/openmw.cfg.local | 1 + 22 files changed, 638 insertions(+), 514 deletions(-) delete mode 100644 apps/openmw/mwrender/viewovershoulder.cpp delete mode 100644 apps/openmw/mwrender/viewovershoulder.hpp create mode 100644 files/builtin_scripts/builtin.omwscripts create mode 100644 files/builtin_scripts/scripts/omw/camera.lua create mode 100644 files/builtin_scripts/scripts/omw/head_bobbing.lua create mode 100644 files/builtin_scripts/scripts/omw/third_person.lua diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c1a8d09120..d1747a2e39 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -62,6 +62,7 @@ namespace MWPhysics namespace MWRender { class Animation; + class Camera; } namespace MWMechanics @@ -433,14 +434,12 @@ namespace MWBase virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; + virtual MWRender::Camera* getCamera() = 0; virtual void togglePOV(bool force = false) = 0; virtual bool isFirstPerson() const = 0; virtual bool isPreviewModeEnabled() const = 0; - virtual void togglePreviewMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0; - virtual void allowVanityMode(bool allow) = 0; virtual bool vanityRotateCamera(float * rot) = 0; - virtual void adjustCameraDistance(float dist) = 0; virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void disableDeferredPreviewRotation() = 0; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index e080437d92..59bdc37bb8 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -27,7 +27,6 @@ namespace MWInput { - const float ZOOM_SCALE = 10.f; /// Used for scrolling camera in and out ActionManager::ActionManager(BindingsManager* bindingsManager, osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, @@ -41,7 +40,6 @@ namespace MWInput , mSneaking(false) , mAttemptJump(false) , mOverencumberedMessageDelay(0.f) - , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) { } @@ -109,27 +107,6 @@ namespace MWInput } } - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) - { - const float switchLimit = 0.25; - MWBase::World* world = MWBase::Environment::get().getWorld(); - if (mBindingsManager->actionIsActive(A_TogglePOV)) - { - if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0) - world->togglePreviewMode(true); - mPreviewPOVDelay += dt; - } - else - { - //disable preview mode - if (mPreviewPOVDelay > 0) - world->togglePreviewMode(false); - if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit) - world->togglePOV(); - mPreviewPOVDelay = 0.f; - } - } - if (triedToMove) MWBase::Environment::get().getInputManager()->resetIdleTime(); @@ -162,38 +139,16 @@ namespace MWInput resetIdleTime(); } else - { - updateIdleTime(dt); - } + mTimeIdle += dt; mAttemptJump = false; } - - bool ActionManager::isPreviewModeEnabled() - { - return MWBase::Environment::get().getWorld()->isPreviewModeEnabled(); - } void ActionManager::resetIdleTime() { - if (mTimeIdle < 0) - MWBase::Environment::get().getWorld()->toggleVanityMode(false); mTimeIdle = 0.f; } - void ActionManager::updateIdleTime(float dt) - { - static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get() - .find("fVanityDelay")->mValue.getFloat(); - if (mTimeIdle >= 0.f) - mTimeIdle += dt; - if (mTimeIdle > vanityDelay) - { - MWBase::Environment::get().getWorld()->toggleVanityMode(true); - mTimeIdle = -1.f; - } - } - void ActionManager::executeAction(int action) { MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action}); @@ -281,14 +236,6 @@ namespace MWInput case A_ToggleDebug: windowManager->toggleDebugWindow(); break; - case A_ZoomIn: - if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode()) - MWBase::Environment::get().getWorld()->adjustCameraDistance(-ZOOM_SCALE); - break; - case A_ZoomOut: - if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode()) - MWBase::Environment::get().getWorld()->adjustCameraDistance(ZOOM_SCALE); - break; case A_QuickSave: quickSave(); break; diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index 2180e1944e..4141767bcc 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -55,13 +55,9 @@ namespace MWInput void setAttemptJump(bool enabled) { mAttemptJump = enabled; } - bool isPreviewModeEnabled(); - private: void handleGuiArrowKey(int action); - void updateIdleTime(float dt); - BindingsManager* mBindingsManager; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; @@ -72,7 +68,6 @@ namespace MWInput bool mAttemptJump; float mOverencumberedMessageDelay; - float mPreviewPOVDelay; float mTimeIdle; }; } diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 07aa89f554..0ed58349d6 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -34,12 +34,10 @@ namespace MWInput , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mSneakToggleShortcutTimer(0.f) - , mGamepadZoom(0) , mGamepadGuiCursorEnabled(true) , mGuiCursorEnabled(true) , mJoystickLastUsed(false) , mSneakGamepadShortcut(false) - , mGamepadPreviewMode(false) { if (!controllerBindingsFile.empty()) { @@ -85,8 +83,6 @@ namespace MWInput bool ControllerManager::update(float dt) { - mGamepadPreviewMode = mActionManager->isPreviewModeEnabled(); - if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) { float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f; @@ -115,7 +111,6 @@ namespace MWInput if (MWBase::Environment::get().getWindowManager()->isGuiMode() || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) { - mGamepadZoom = 0; return false; } @@ -182,15 +177,6 @@ namespace MWInput } } - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch")) - { - if (!mBindingsManager->actionIsActive(A_TogglePOV)) - mGamepadZoom = 0; - - if (mGamepadZoom) - MWBase::Environment::get().getWorld()->adjustCameraDistance(-mGamepadZoom); - } - return triedToMove; } @@ -289,21 +275,11 @@ namespace MWInput { gamepadToGuiControl(arg); } - else + else if (MWBase::Environment::get().getWorld()->isPreviewModeEnabled() && + (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT || arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT)) { - if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming - { - if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) - { - mGamepadZoom = arg.value * 0.85f / 1000.f / 12.f; - return; // Do not propagate event. - } - else if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) - { - mGamepadZoom = -arg.value * 0.85f / 1000.f / 12.f; - return; // Do not propagate event. - } - } + // Preview Mode Gamepad Zooming; do not propagate to mBindingsManager + return; } mBindingsManager->controllerAxisMoved(deviceID, arg); } diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index 1e8ef90d0e..948b48d538 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -56,12 +56,10 @@ namespace MWInput bool mJoystickEnabled; float mGamepadCursorSpeed; float mSneakToggleShortcutTimer; - float mGamepadZoom; bool mGamepadGuiCursorEnabled; bool mGuiCursorEnabled; bool mJoystickLastUsed; bool mSneakGamepadShortcut; - bool mGamepadPreviewMode; }; } #endif diff --git a/apps/openmw/mwinput/controlswitch.cpp b/apps/openmw/mwinput/controlswitch.cpp index 63d142434e..6c22e133bc 100644 --- a/apps/openmw/mwinput/controlswitch.cpp +++ b/apps/openmw/mwinput/controlswitch.cpp @@ -54,10 +54,6 @@ namespace MWInput /// \fixme maybe crouching at this time player.setUpDown(0); } - else if (key == "vanitymode") - { - MWBase::Environment::get().getWorld()->allowVanityMode(value); - } else if (key == "playerlooking" && !value) { MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f()); diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index 46bf079d65..abbd806cef 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -1,12 +1,82 @@ #include "luabindings.hpp" +#include "../mwrender/camera.hpp" + namespace MWLua { + using CameraMode = MWRender::Camera::Mode; + sol::table initCameraPackage(const Context& context) { + MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera(); + sol::table api(context.mLua->sol(), sol::create); - // TODO + api["MODE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( + "Static", CameraMode::Static, + "FirstPerson", CameraMode::FirstPerson, + "ThirdPerson", CameraMode::ThirdPerson, + "Vanity", CameraMode::Vanity, + "Preview", CameraMode::Preview + )); + + api["getMode"] = [camera]() -> int { return static_cast(camera->getMode()); }; + api["getQueuedMode"] = [camera]() -> sol::optional + { + std::optional mode = camera->getQueuedMode(); + if (mode) + return static_cast(*mode); + else + return sol::nullopt; + }; + api["setMode"] = [camera](int mode, sol::optional force) + { + camera->setMode(static_cast(mode), force ? *force : false); + }; + + api["allowCharacterDeferredRotation"] = [camera](bool v) { camera->allowCharacterDeferredRotation(v); }; + api["showCrosshair"] = [camera](bool v) { camera->showCrosshair(v); }; + + api["getTrackedPosition"] = [camera]() -> osg::Vec3f { return camera->getTrackedPosition(); }; + api["getPosition"] = [camera]() -> osg::Vec3f { return camera->getPosition(); }; + + // All angles are negated in order to make camera rotation consistent with objects rotation. + // TODO: Fix the inconsistency of rotation direction in camera.cpp. + api["getPitch"] = [camera]() { return -camera->getPitch(); }; + api["getYaw"] = [camera]() { return -camera->getYaw(); }; + api["getRoll"] = [camera]() { return -camera->getRoll(); }; + + api["setStaticPosition"] = [camera](const osg::Vec3f& pos) { camera->setStaticPosition(pos); }; + api["setPitch"] = [camera](float v) + { + camera->setPitch(-v, true); + if (camera->getMode() == CameraMode::ThirdPerson) + camera->calculateDeferredRotation(); + }; + api["setYaw"] = [camera](float v) + { + camera->setYaw(-v, true); + if (camera->getMode() == CameraMode::ThirdPerson) + camera->calculateDeferredRotation(); + }; + api["setRoll"] = [camera](float v) { camera->setRoll(-v); }; + api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); }; + api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); }; + api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); }; + api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); }; + + api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); }; + api["setPreferredThirdPersonDistance"] = [camera](float v) { camera->setPreferredCameraDistance(v); }; + + api["getFirstPersonOffset"] = [camera]() { return camera->getFirstPersonOffset(); }; + api["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); }; + + api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); }; + api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); }; + api["getFocalTransitionSpeed"] = [camera]() { return camera->getFocalPointTransitionSpeed(); }; + api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); }; + api["instantTransition"] = [camera]() { camera->instantTransition(); }; + return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index bf323351a3..80af77139c 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 9; + api["API_REVISION"] = 10; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 90f2a28329..5ca102bc39 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -58,37 +58,23 @@ namespace MWRender mFirstPersonView(true), mMode(Mode::FirstPerson), mVanityAllowed(true), - mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")), - mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")), - mNearest(30.f), - mFurthest(800.f), - mIsNearest(false), + mDeferredRotationAllowed(true), mProcessViewChange(false), mHeight(124.f), - mBaseCameraDistance(Settings::Manager::getFloat("third person camera distance", "Camera")), mPitch(0.f), mYaw(0.f), mRoll(0.f), mCameraDistance(0.f), - mMaxNextCameraDistance(800.f), + mPreferredCameraDistance(0.f), mFocalPointCurrentOffset(osg::Vec2d()), mFocalPointTargetOffset(osg::Vec2d()), mFocalPointTransitionSpeedCoef(1.f), mSkipFocalPointTransition(true), mPreviousTransitionInfluence(0.f), - mSmoothedSpeed(0.f), - mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")), - mDynamicCameraDistanceEnabled(false), - mShowCrosshairInThirdPersonMode(false), - mHeadBobbingEnabled(Settings::Manager::getBool("head bobbing", "Camera")), - mHeadBobbingOffset(0.f), - mHeadBobbingWeight(0.f), - mTotalMovement(0.f), + mShowCrosshair(false), mDeferredRotation(osg::Vec3f()), mDeferredRotationDisabled(false) { - mCameraDistance = mBaseCameraDistance; - mUpdateCallback = new UpdateRenderCameraCallback(this); mCamera->addUpdateCallback(mUpdateCallback); } @@ -98,7 +84,7 @@ namespace MWRender mCamera->removeUpdateCallback(mUpdateCallback); } - osg::Vec3d Camera::getTrackingNodePosition() const + osg::Vec3d Camera::calculateTrackedPosition() const { if (!mTrackingNode) return osg::Vec3d(); @@ -106,59 +92,46 @@ namespace MWRender if (nodepaths.empty()) return osg::Vec3d(); osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); - return worldMat.getTrans(); - } - - osg::Vec3d Camera::getThirdPersonBasePosition() const - { - osg::Vec3d position = getTrackingNodePosition(); - position.z() += mHeight * mHeightScale; - - // We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling. - // Needed because character's head can be a bit higher than collision area. - position.z() -= 10.f; - - return position; + osg::Vec3d res = worldMat.getTrans(); + if (mMode != Mode::FirstPerson) + res.z() += mHeight * mHeightScale; + return res; } osg::Vec3d Camera::getFocalPointOffset() const { - osg::Vec3d offset(0, 0, 10.f); - offset.x() += mFocalPointCurrentOffset.x() * cos(mYaw); - offset.y() += mFocalPointCurrentOffset.x() * sin(mYaw); - offset.z() += mFocalPointCurrentOffset.y(); + osg::Vec3d offset; + offset.x() = mFocalPointCurrentOffset.x() * cos(mYaw); + offset.y() = mFocalPointCurrentOffset.x() * sin(mYaw); + offset.z() = mFocalPointCurrentOffset.y(); return offset; } void Camera::updateCamera(osg::Camera *cam) { - osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, osg::Vec3d(0, 0, 1)); + osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * + osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * + osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); osg::Vec3d forward = orient * osg::Vec3d(0,1,0); osg::Vec3d up = orient * osg::Vec3d(0,0,1); - cam->setViewMatrixAsLookAt(mPosition, mPosition + forward, up); - } - - void Camera::updateHeadBobbing(float duration) { - static const float doubleStepLength = Settings::Manager::getFloat("head bobbing step", "Camera") * 2; - static const float stepHeight = Settings::Manager::getFloat("head bobbing height", "Camera"); - static const float maxRoll = osg::DegreesToRadians(Settings::Manager::getFloat("head bobbing roll", "Camera")); - - if (MWBase::Environment::get().getWorld()->isOnGround(mTrackingPtr)) - mHeadBobbingWeight = std::min(mHeadBobbingWeight + duration * 5, 1.f); - else - mHeadBobbingWeight = std::max(mHeadBobbingWeight - duration * 5, 0.f); - - float doubleStepState = mTotalMovement / doubleStepLength - std::floor(mTotalMovement / doubleStepLength); // from 0 to 1 during 2 steps - float stepState = std::abs(doubleStepState * 4 - 2) - 1; // from -1 to 1 on even steps and from 1 to -1 on odd steps - float effect = (1 - std::cos(stepState * osg::DegreesToRadians(30.f))) * 7.5f; // range from 0 to 1 - float coef = std::min(mSmoothedSpeed / 300.f, 1.f) * mHeadBobbingWeight; - mHeadBobbingOffset = (0.5f - effect) * coef * stepHeight; // range from -stepHeight/2 to stepHeight/2 - mRoll = osg::sign(stepState) * effect * coef * maxRoll; // range from -maxRoll to maxRoll + osg::Vec3d pos = mPosition; + if (mMode == Mode::FirstPerson) + { + // It is a hack. Camera position depends on neck animation. + // Animations are updated in OSG cull traversal and in order to avoid 1 frame delay we + // recalculate the position here. Note that it becomes different from mPosition that + // is used in other parts of the code. + // TODO: detach camera from OSG animation and get rid of this hack. + osg::Vec3d recalculatedTrackedPosition = calculateTrackedPosition(); + pos = calculateFirstPersonPosition(recalculatedTrackedPosition); + } + cam->setViewMatrixAsLookAt(pos, pos + forward, up); } void Camera::update(float duration, bool paused) { + mLockPitch = mLockYaw = false; if (mQueuedMode && mAnimation->upperBodyReady()) setMode(*mQueuedMode); if (mProcessViewChange) @@ -169,38 +142,31 @@ namespace MWRender // only show the crosshair in game mode MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); - wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity - && (mFirstPersonView || mShowCrosshairInThirdPersonMode)); - - if(mMode == Mode::Vanity) - setYaw(mYaw + osg::DegreesToRadians(3.f) * duration); - - if (mMode == Mode::FirstPerson && mHeadBobbingEnabled) - updateHeadBobbing(duration); - else - mRoll = mHeadBobbingOffset = 0; + wm->showCrosshair(!wm->isGuiMode() && mShowCrosshair); updateFocalPointOffset(duration); updatePosition(); + } - float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); - mTotalMovement += speed * duration; - speed /= (1.f + speed / 500.f); - float maxDelta = 300.f * duration; - mSmoothedSpeed += std::clamp(speed - mSmoothedSpeed, -maxDelta, maxDelta); - - mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance); - updateStandingPreviewMode(); + osg::Vec3d Camera::calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const + { + osg::Vec3d res = trackedPosition; + osg::Vec2f horizontalOffset = Misc::rotateVec2f(osg::Vec2f(mFirstPersonOffset.x(), mFirstPersonOffset.y()), mYaw); + res.x() += horizontalOffset.x(); + res.y() += horizontalOffset.y(); + res.z() += mFirstPersonOffset.z(); + return res; } void Camera::updatePosition() { + mTrackedPosition = calculateTrackedPosition(); if (mMode == Mode::Static) return; if (mMode == Mode::FirstPerson) { - mPosition = getTrackingNodePosition(); - mPosition.z() += mHeadBobbingOffset; + mPosition = calculateFirstPersonPosition(mTrackedPosition); + mCameraDistance = 0; return; } @@ -212,7 +178,9 @@ namespace MWRender // Adjust focal point to prevent clipping. osg::Vec3d focalOffset = getFocalPointOffset(); - osg::Vec3d focal = getThirdPersonBasePosition() + focalOffset; + osg::Vec3d focal = mTrackedPosition + focalOffset; + focalOffset.z() += 10.f; // Needed to avoid camera clipping through the ceiling because + // character's head can be a bit higher than the collision area. float offsetLen = focalOffset.length(); if (offsetLen > 0) { @@ -224,12 +192,9 @@ namespace MWRender } } - // Calculate offset from focal point. - mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection(); - if (mDynamicCameraDistanceEnabled) - mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance); - - osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); + // Adjust camera distance. + mCameraDistance = mPreferredCameraDistance; + osg::Quat orient = osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1,0,0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0,0,1)); osg::Vec3d offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, collisionType); if (result.mHit) @@ -243,8 +208,6 @@ namespace MWRender void Camera::setMode(Mode newMode, bool force) { - if (newMode == Mode::StandingPreview) - newMode = Mode::ThirdPerson; if (mMode == newMode) return; Mode oldMode = mMode; @@ -267,23 +230,6 @@ namespace MWRender instantTransition(); mProcessViewChange = true; } - else if (newMode == Mode::ThirdPerson) - updateStandingPreviewMode(); - } - - void Camera::updateStandingPreviewMode() - { - float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); - bool combat = mTrackingPtr.getClass().isActor() && - mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing; - bool standingStill = speed == 0 && !combat && mStandingPreviewAllowed; - if (!standingStill && mMode == Mode::StandingPreview) - { - mMode = Mode::ThirdPerson; - calculateDeferredRotation(); - } - else if (standingStill && mMode == Mode::ThirdPerson) - mMode = Mode::StandingPreview; } void Camera::setFocalPointTargetOffset(const osg::Vec2d& v) @@ -339,13 +285,6 @@ namespace MWRender setMode(mFirstPersonView ? Mode::ThirdPerson : Mode::FirstPerson, force); } - void Camera::allowVanityMode(bool allow) - { - if (!allow && mMode == Mode::Vanity) - toggleVanityMode(false); - mVanityAllowed = allow; - } - bool Camera::toggleVanityMode(bool enable) { if (!enable) @@ -355,73 +294,34 @@ namespace MWRender return (mMode == Mode::Vanity) == enable; } - void Camera::togglePreviewMode(bool enable) - { - if (mFirstPersonView && !mAnimation->upperBodyReady()) - return; - if ((mMode == Mode::Preview) == enable) - return; - if (enable) - setMode(Mode::Preview); - else - setMode(mFirstPersonView ? Mode::FirstPerson : Mode::ThirdPerson); - } - void Camera::setSneakOffset(float offset) { mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); } - void Camera::setYaw(float angle) + void Camera::setYaw(float angle, bool force) { - mYaw = Misc::normalizeAngle(angle); + if (!mLockYaw || force) + mYaw = Misc::normalizeAngle(angle); + if (force) + mLockYaw = true; } - void Camera::setPitch(float angle) + void Camera::setPitch(float angle, bool force) { const float epsilon = 0.000001f; float limit = static_cast(osg::PI_2) - epsilon; - mPitch = std::clamp(angle, -limit, limit); + if (!mLockPitch || force) + mPitch = std::clamp(angle, -limit, limit); + if (force) + mLockPitch = true; } - float Camera::getCameraDistance() const + void Camera::setStaticPosition(const osg::Vec3d& pos) { - return mMode == Mode::FirstPerson ? 0.f : mCameraDistance; - } - - void Camera::adjustCameraDistance(float delta) - { - if (mMode == Mode::Static) - return; - if (mMode != Mode::FirstPerson) - { - if (mIsNearest && delta < 0.f && mMode != Mode::Preview && mMode != Mode::Vanity) - toggleViewMode(); - else - mBaseCameraDistance = std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance) + delta; - } - else if (delta > 0.f) - { - toggleViewMode(); - mBaseCameraDistance = 0; - } - - mIsNearest = mBaseCameraDistance <= mNearest; - mBaseCameraDistance = std::clamp(mBaseCameraDistance, mNearest, mFurthest); - Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance); - } - - float Camera::getCameraDistanceCorrection() const - { - if (!mDynamicCameraDistanceEnabled) - return 0; - - float pitchCorrection = std::max(-getPitch(), 0.f) * 50.f; - - float smoothedSpeedSqr = mSmoothedSpeed * mSmoothedSpeed; - float speedCorrection = smoothedSpeedSqr / (smoothedSpeedSqr + 300.f*300.f) * mZoomOutWhenMoveCoef; - - return pitchCorrection + speedCorrection; + if (mMode != Mode::Static) + throw std::runtime_error("setStaticPosition can be used only if camera is in Static mode"); + mPosition = pos; } void Camera::setAnimation(NpcAnimation *anim) @@ -432,6 +332,8 @@ namespace MWRender void Camera::processViewChange() { + if (mTrackingPtr.isEmpty()) + return; if (mMode == Mode::FirstPerson) { mAnimation->setViewMode(NpcAnimation::VM_FirstPerson); @@ -488,7 +390,7 @@ namespace MWRender void Camera::rotateCameraToTrackingPtr() { - if (mMode == Mode::Static) + if (mMode == Mode::Static || mTrackingPtr.isEmpty()) return; setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x()); setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z()); @@ -524,9 +426,4 @@ namespace MWRender mDeferredRotationDisabled = true; } - bool Camera::isVanityOrPreviewModeEnabled() const - { - return mMode == Mode::Vanity || mMode == Mode::Preview || mMode == Mode::StandingPreview; - } - } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index cf25aa4852..280d309256 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -25,11 +25,80 @@ namespace MWRender class Camera { public: - enum class Mode : int {Static = 0, FirstPerson = 1, ThirdPerson = 2, Vanity = 3, Preview = 4, StandingPreview = 5}; + enum class Mode : int {Static = 0, FirstPerson = 1, ThirdPerson = 2, Vanity = 3, Preview = 4}; + + Camera(osg::Camera* camera); + ~Camera(); + + /// Attach camera to object + void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; } + MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; } + + void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; } + float getFocalPointTransitionSpeed() const { return mFocalPointTransitionSpeedCoef; } + void setFocalPointTargetOffset(const osg::Vec2d& v); + osg::Vec2d getFocalPointTargetOffset() const { return mFocalPointTargetOffset; } + void instantTransition(); + void showCrosshair(bool v) { mShowCrosshair = v; } + + /// Update the view matrix of \a cam + void updateCamera(osg::Camera* cam); + + /// Reset to defaults + void reset() { setMode(Mode::FirstPerson); } + + void rotateCameraToTrackingPtr(); + + float getPitch() const { return mPitch; } + float getYaw() const { return mYaw; } + float getRoll() const { return mRoll; } + + void setPitch(float angle, bool force = false); + void setYaw(float angle, bool force = false); + void setRoll(float angle) { mRoll = angle; } + + float getExtraPitch() const { return mExtraPitch; } + float getExtraYaw() const { return mExtraYaw; } + void setExtraPitch(float angle) { mExtraPitch = angle; } + void setExtraYaw(float angle) { mExtraYaw = angle; } + + /// @param Force view mode switch, even if currently not allowed by the animation. + void toggleViewMode(bool force=false); + bool toggleVanityMode(bool enable); + + void applyDeferredPreviewRotationToPlayer(float dt); + void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; } + + /// \brief Lowers the camera for sneak. + void setSneakOffset(float offset); + + void processViewChange(); + + void update(float duration, bool paused=false); + + float getCameraDistance() const { return mCameraDistance; } + void setPreferredCameraDistance(float v) { mPreferredCameraDistance = v; } + + void setAnimation(NpcAnimation *anim); + + osg::Vec3d getTrackedPosition() const { return mTrackedPosition; } + const osg::Vec3d& getPosition() const { return mPosition; } + void setStaticPosition(const osg::Vec3d& pos); + + bool isVanityOrPreviewModeEnabled() const { return mMode == Mode::Vanity || mMode == Mode::Preview; } + Mode getMode() const { return mMode; } + std::optional getQueuedMode() const { return mQueuedMode; } + void setMode(Mode mode, bool force = true); + + void allowCharacterDeferredRotation(bool v) { mDeferredRotationAllowed = v; } + void calculateDeferredRotation(); + void setFirstPersonOffset(const osg::Vec3f& v) { mFirstPersonOffset = v; } + osg::Vec3f getFirstPersonOffset() const { return mFirstPersonOffset; } private: MWWorld::Ptr mTrackingPtr; osg::ref_ptr mTrackingNode; + osg::Vec3d mTrackedPosition; float mHeightScale; osg::ref_ptr mCamera; @@ -43,21 +112,19 @@ namespace MWRender Mode mMode; std::optional mQueuedMode; bool mVanityAllowed; - bool mStandingPreviewAllowed; bool mDeferredRotationAllowed; - float mNearest; - float mFurthest; - bool mIsNearest; - bool mProcessViewChange; - float mHeight, mBaseCameraDistance; + float mHeight; float mPitch, mYaw, mRoll; + float mExtraPitch = 0, mExtraYaw = 0; + bool mLockPitch = false, mLockYaw = false; osg::Vec3d mPosition; - float mCameraDistance; - float mMaxNextCameraDistance; + float mCameraDistance, mPreferredCameraDistance; + + osg::Vec3f mFirstPersonOffset{0, 0, 0}; osg::Vec2d mFocalPointCurrentOffset; osg::Vec2d mFocalPointTargetOffset; @@ -70,91 +137,19 @@ namespace MWRender osg::Vec2d mPreviousTransitionSpeed; osg::Vec2d mPreviousExtraOffset; - float mSmoothedSpeed; - float mZoomOutWhenMoveCoef; - bool mDynamicCameraDistanceEnabled; - bool mShowCrosshairInThirdPersonMode; - - bool mHeadBobbingEnabled; - float mHeadBobbingOffset; - float mHeadBobbingWeight; // Value from 0 to 1 for smooth enabling/disabling. - float mTotalMovement; // Needed for head bobbing. - void updateHeadBobbing(float duration); + bool mShowCrosshair; - osg::Vec3d getTrackingNodePosition() const; + osg::Vec3d calculateTrackedPosition() const; + osg::Vec3d calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const; osg::Vec3d getFocalPointOffset() const; void updateFocalPointOffset(float duration); void updatePosition(); - float getCameraDistanceCorrection() const; osg::ref_ptr mUpdateCallback; // Used to rotate player to the direction of view after exiting preview or vanity mode. osg::Vec3f mDeferredRotation; bool mDeferredRotationDisabled; - void calculateDeferredRotation(); - void updateStandingPreviewMode(); - - public: - Camera(osg::Camera* camera); - ~Camera(); - - /// Attach camera to object - void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; } - MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; } - - void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; } - void setFocalPointTargetOffset(const osg::Vec2d& v); - void instantTransition(); - void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; } - void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; } - - /// Update the view matrix of \a cam - void updateCamera(osg::Camera* cam); - - /// Reset to defaults - void reset() { setMode(Mode::FirstPerson); } - - void rotateCameraToTrackingPtr(); - - float getYaw() const { return mYaw; } - void setYaw(float angle); - - float getPitch() const { return mPitch; } - void setPitch(float angle); - - /// @param Force view mode switch, even if currently not allowed by the animation. - void toggleViewMode(bool force=false); - - bool toggleVanityMode(bool enable); - void allowVanityMode(bool allow); - - /// @note this may be ignored if an important animation is currently playing - void togglePreviewMode(bool enable); - - void applyDeferredPreviewRotationToPlayer(float dt); - void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; } - - /// \brief Lowers the camera for sneak. - void setSneakOffset(float offset); - - void processViewChange(); - - void update(float duration, bool paused=false); - - /// Adds distDelta to the camera distance. Switches 3rd/1st person view if distance is less than limit. - void adjustCameraDistance(float distDelta); - - float getCameraDistance() const; - - void setAnimation(NpcAnimation *anim); - - osg::Vec3d getThirdPersonBasePosition() const; - const osg::Vec3d& getPosition() const { return mPosition; } - - bool isVanityOrPreviewModeEnabled() const; - Mode getMode() const { return mMode; } - void setMode(Mode mode, bool force = true); }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 0542d8ed90..b3b0d880c5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -59,7 +59,6 @@ #include "vismask.hpp" #include "pathgrid.hpp" #include "camera.hpp" -#include "viewovershoulder.hpp" #include "water.hpp" #include "terrainstorage.hpp" #include "navmesh.hpp" @@ -472,8 +471,6 @@ namespace MWRender mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); mCamera.reset(new Camera(mViewer->getCamera())); - if (Settings::Manager::getBool("view over shoulder", "Camera")) - mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get())); mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); @@ -826,8 +823,6 @@ namespace MWRender updateNavMesh(); updateRecastMesh(); - if (mViewOverShoulderController) - mViewOverShoulderController->update(); mCamera->update(dt, paused); bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 672f48c64f..91f03e8c27 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -79,7 +79,6 @@ namespace MWRender class NpcAnimation; class Pathgrid; class Camera; - class ViewOverShoulderController; class Water; class TerrainStorage; class LandManager; @@ -283,7 +282,6 @@ namespace MWRender osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; - std::unique_ptr mViewOverShoulderController; osg::ref_ptr mStateUpdater; osg::ref_ptr mSharedUniformStateUpdater; diff --git a/apps/openmw/mwrender/viewovershoulder.cpp b/apps/openmw/mwrender/viewovershoulder.cpp deleted file mode 100644 index bd45480cf3..0000000000 --- a/apps/openmw/mwrender/viewovershoulder.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "viewovershoulder.hpp" - -#include - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/ptr.hpp" -#include "../mwworld/refdata.hpp" - -#include "../mwmechanics/drawstate.hpp" - -namespace MWRender -{ - - ViewOverShoulderController::ViewOverShoulderController(Camera* camera) : - mCamera(camera), mMode(Mode::RightShoulder), - mAutoSwitchShoulder(Settings::Manager::getBool("auto switch shoulder", "Camera")), - mOverShoulderHorizontalOffset(30.f), mOverShoulderVerticalOffset(-10.f) - { - osg::Vec2f offset = Settings::Manager::getVector2("view over shoulder offset", "Camera"); - mOverShoulderHorizontalOffset = std::abs(offset.x()); - mOverShoulderVerticalOffset = offset.y(); - mDefaultShoulderIsRight = offset.x() >= 0; - - mCamera->enableDynamicCameraDistance(true); - mCamera->enableCrosshairInThirdPersonMode(true); - mCamera->setFocalPointTargetOffset(offset); - } - - void ViewOverShoulderController::update() - { - if (mCamera->getMode() == Camera::Mode::FirstPerson || mCamera->getMode() == Camera::Mode::Static) - return; - - Mode oldMode = mMode; - auto ptr = mCamera->getTrackingPtr(); - bool combat = ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing; - if (combat && !mCamera->isVanityOrPreviewModeEnabled()) - mMode = Mode::Combat; - else if (MWBase::Environment::get().getWorld()->isSwimming(ptr)) - mMode = Mode::Swimming; - else if (oldMode == Mode::Combat || oldMode == Mode::Swimming) - mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; - if (mAutoSwitchShoulder && (mMode == Mode::LeftShoulder || mMode == Mode::RightShoulder)) - trySwitchShoulder(); - - if (oldMode == mMode) - return; - - if (mCamera->getMode() == Camera::Mode::Vanity) - // Player doesn't touch controls for a long time. Transition should be very slow. - mCamera->setFocalPointTransitionSpeed(0.2f); - else if ((oldMode == Mode::Combat || mMode == Mode::Combat) && mCamera->getMode() == Camera::Mode::ThirdPerson) - // Transition to/from combat mode and we are not it preview mode. Should be fast. - mCamera->setFocalPointTransitionSpeed(5.f); - else - mCamera->setFocalPointTransitionSpeed(1.f); // Default transition speed. - - switch (mMode) - { - case Mode::RightShoulder: - mCamera->setFocalPointTargetOffset({mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); - break; - case Mode::LeftShoulder: - mCamera->setFocalPointTargetOffset({-mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); - break; - case Mode::Combat: - case Mode::Swimming: - default: - mCamera->setFocalPointTargetOffset({0, 15}); - } - } - - void ViewOverShoulderController::trySwitchShoulder() - { - if (mCamera->getMode() != Camera::Mode::ThirdPerson) - return; - - const float limitToSwitch = 120; // switch to other shoulder if wall is closer than this limit - const float limitToSwitchBack = 300; // switch back to default shoulder if there is no walls at this distance - - auto orient = osg::Quat(mCamera->getYaw(), osg::Vec3d(0,0,1)); - osg::Vec3d playerPos = mCamera->getThirdPersonBasePosition(); - - MWBase::World* world = MWBase::Environment::get().getWorld(); - osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0); - float rayRight = world->getDistToNearestRayHit( - playerPos + sideOffset, orient * osg::Vec3d(1, 0, 0), limitToSwitchBack + 1); - float rayLeft = world->getDistToNearestRayHit( - playerPos - sideOffset, orient * osg::Vec3d(-1, 0, 0), limitToSwitchBack + 1); - float rayRightForward = world->getDistToNearestRayHit( - playerPos + sideOffset, orient * osg::Vec3d(1, 3, 0), limitToSwitchBack + 1); - float rayLeftForward = world->getDistToNearestRayHit( - playerPos - sideOffset, orient * osg::Vec3d(-1, 3, 0), limitToSwitchBack + 1); - float distRight = std::min(rayRight, rayRightForward); - float distLeft = std::min(rayLeft, rayLeftForward); - - if (distLeft < limitToSwitch && distRight > limitToSwitchBack) - mMode = Mode::RightShoulder; - else if (distRight < limitToSwitch && distLeft > limitToSwitchBack) - mMode = Mode::LeftShoulder; - else if (distRight > limitToSwitchBack && distLeft > limitToSwitchBack) - mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; - } - -} diff --git a/apps/openmw/mwrender/viewovershoulder.hpp b/apps/openmw/mwrender/viewovershoulder.hpp deleted file mode 100644 index 80ac308656..0000000000 --- a/apps/openmw/mwrender/viewovershoulder.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef VIEWOVERSHOULDER_H -#define VIEWOVERSHOULDER_H - -#include "camera.hpp" - -namespace MWRender -{ - - class ViewOverShoulderController - { - public: - ViewOverShoulderController(Camera* camera); - - void update(); - - private: - void trySwitchShoulder(); - enum class Mode { RightShoulder, LeftShoulder, Combat, Swimming }; - - Camera* mCamera; - Mode mMode; - bool mAutoSwitchShoulder; - float mOverShoulderHorizontalOffset; - float mOverShoulderVerticalOffset; - bool mDefaultShoulderIsRight; - }; - -} - -#endif // VIEWOVERSHOULDER_H diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 71f44f125d..14b1082ba3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2398,11 +2398,6 @@ namespace MWWorld return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview; } - void World::togglePreviewMode(bool enable) - { - mRendering->getCamera()->togglePreviewMode(enable); - } - bool World::toggleVanityMode(bool enable) { return mRendering->getCamera()->toggleVanityMode(enable); @@ -2418,10 +2413,7 @@ namespace MWWorld mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt); } - void World::allowVanityMode(bool allow) - { - mRendering->getCamera()->allowVanityMode(allow); - } + MWRender::Camera* World::getCamera() { return mRendering->getCamera(); } bool World::vanityRotateCamera(float * rot) { @@ -2434,11 +2426,6 @@ namespace MWWorld return true; } - void World::adjustCameraDistance(float dist) - { - mRendering->getCamera()->adjustCameraDistance(dist); - } - void World::saveLoaded() { mStore.validateDynamic(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c8eebd9a8b..bf61483ecf 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -529,13 +529,10 @@ namespace MWWorld bool isFirstPerson() const override; bool isPreviewModeEnabled() const override; - void togglePreviewMode(bool enable) override; - bool toggleVanityMode(bool enable) override; - void allowVanityMode(bool allow) override; + MWRender::Camera* getCamera() override; bool vanityRotateCamera(float * rot) override; - void adjustCameraDistance(float dist) override; void applyDeferredPreviewRotationToPlayer(float dt) override; void disableDeferredPreviewRotation() override; diff --git a/files/builtin_scripts/builtin.omwscripts b/files/builtin_scripts/builtin.omwscripts new file mode 100644 index 0000000000..30fccad9fa --- /dev/null +++ b/files/builtin_scripts/builtin.omwscripts @@ -0,0 +1 @@ +PLAYER: scripts/omw/camera.lua diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua new file mode 100644 index 0000000000..55460dc615 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -0,0 +1,221 @@ +local camera = require('openmw.camera') +local input = require('openmw.input') +local settings = require('openmw.settings') +local util = require('openmw.util') +local self = require('openmw.self') + +local head_bobbing = require('scripts.omw.head_bobbing') +local third_person = require('scripts.omw.third_person') + +local MODE = camera.MODE + +local previewIfStandSill = settings._getBoolFromSettingsCfg('Camera', 'preview if stand still') +local showCrosshairInThirdPerson = settings._getBoolFromSettingsCfg('Camera', 'view over shoulder') + +local primaryMode + +local noModeControl = 0 +local noStandingPreview = 0 +local noHeadBobbing = 0 +local noZoom = 0 + +function init() + camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation')) + if camera.getMode() == MODE.FirstPerson then + primaryMode = MODE.FirstPerson + else + primaryMode = MODE.ThirdPerson + camera.setMode(MODE.ThirdPerson) + end +end + +local smoothedSpeed = 0 +local previewTimer = 0 + +local function updatePOV(dt) + local switchLimit = 0.25 + if input.isActionPressed(input.ACTION.TogglePOV) and input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) then + previewTimer = previewTimer + dt + if primaryMode == MODE.ThirdPerson or previewTimer >= switchLimit then + third_person.standingPreview = false + camera.setMode(MODE.Preview) + end + elseif previewTimer > 0 then + if previewTimer <= switchLimit then + if primaryMode == MODE.FirstPerson then + primaryMode = MODE.ThirdPerson + else + primaryMode = MODE.FirstPerson + end + end + camera.setMode(primaryMode) + previewTimer = 0 + end +end + +local idleTimer = 0 +local vanityDelay = settings.getGMST('fVanityDelay') + +local function updateVanity(dt) + if input.isIdle() then + idleTimer = idleTimer + dt + else + idleTimer = 0 + end + local vanityAllowed = input.getControlSwitch(input.CONTROL_SWITCH.VanityMode) + if vanityAllowed and idleTimer > vanityDelay and camera.getMode() ~= MODE.Vanity then + camera.setMode(MODE.Vanity) + end + if camera.getMode() == MODE.Vanity then + if not vanityAllowed or idleTimer == 0 then + camera.setMode(primaryMode) + else + camera.setYaw(camera.getYaw() + math.rad(3) * dt) + end + end +end + +local function updateSmoothedSpeed(dt) + local speed = self:getCurrentSpeed() + speed = speed / (1 + speed / 500) + local maxDelta = 300 * dt + smoothedSpeed = smoothedSpeed + util.clamp(speed - smoothedSpeed, -maxDelta, maxDelta) +end + +local minDistance = 30 +local maxDistance = 800 + +local function zoom(delta) + if not input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) or + not input.getControlSwitch(input.CONTROL_SWITCH.Controls) or + camera.getMode() == MODE.Static or noZoom > 0 then + return + end + if camera.getMode() ~= MODE.FirstPerson then + local obstacleDelta = third_person.preferredDistance - camera.getThirdPersonDistance() + if delta > 0 and third_person.baseDistance == minDistance and + (camera.getMode() ~= MODE.Preview or third_person.standingPreview) and noModeControl == 0 then + primaryMode = MODE.FirstPerson + camera.setMode(primaryMode) + elseif delta > 0 or obstacleDelta < -delta then + third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, maxDistance) + end + elseif delta < 0 and noModeControl == 0 then + primaryMode = MODE.ThirdPerson + camera.setMode(primaryMode) + third_person.baseDistance = minDistance + end +end + +local function applyControllerZoom(dt) + if camera.getMode() == MODE.Preview then + local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft) + local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) + local controllerZoom = (triggerRight - triggerLeft) * 100 * dt + if controllerZoom ~= 0 then + zoom(controllerZoom) + end + end +end + +local function updateStandingPreview() + local mode = camera.getMode() + if not previewIfStandSill or noStandingPreview > 0 + or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then + third_person.standingPreview = false + return + end + local standingStill = self:getCurrentSpeed() == 0 and not self:isInWeaponStance() and not self:isInMagicStance() + if standingStill and mode == MODE.ThirdPerson then + third_person.standingPreview = true + camera.setMode(MODE.Preview) + elseif not standingStill and third_person.standingPreview then + third_person.standingPreview = false + camera.setMode(primaryMode) + end +end + +local function updateCrosshair() + camera.showCrosshair( + camera.getMode() == MODE.FirstPerson or + (showCrosshairInThirdPerson and (camera.getMode() == MODE.ThirdPerson or third_person.standingPreview))) +end + +local function onUpdate(dt) + camera.setExtraPitch(0) + camera.setExtraYaw(0) + camera.setRoll(0) + camera.setFirstPersonOffset(util.vector3(0, 0, 0)) + updateSmoothedSpeed(dt) +end + +local function onInputUpdate(dt) + local mode = camera.getMode() + if mode == MODE.FirstPerson or mode == MODE.ThirdPerson then + primaryMode = mode + end + if mode ~= MODE.Static then + if not camera.getQueuedMode() or camera.getQueuedMode() == MODE.Preview then + if noModeControl == 0 then + updatePOV(dt) + updateVanity(dt) + end + updateStandingPreview() + end + updateCrosshair() + end + applyControllerZoom(dt) + third_person.update(dt, smoothedSpeed) + if noHeadBobbing == 0 then head_bobbing.update(dt, smoothedSpeed) end +end + +return { + interfaceName = 'Camera', + interface = { + version = 0, + + getPrimaryMode = function() return primaryMode end, + getBaseThirdPersonDistance = function() return third_person.baseDistance end, + setBaseThirdPersonDistance = function(v) third_person.baseDistance = v end, + getTargetThirdPersonDistance = function() return third_person.preferredDistance end, + + isModeControlEnabled = function() return noModeControl == 0 end, + disableModeControl = function() noModeControl = noModeControl + 1 end, + enableModeControl = function() noModeControl = math.max(0, noModeControl - 1) end, + + isStandingPreviewEnabled = function() return previewIfStandSill and noStandingPreview == 0 end, + disableStandingPreview = function() noStandingPreview = noStandingPreview + 1 end, + enableStandingPreview = function() noStandingPreview = math.max(0, noStandingPreview - 1) end, + + isHeadBobbingEnabled = function() return head_bobbing.enabled and noHeadBobbing == 0 end, + disableHeadBobbing = function() noHeadBobbing = noHeadBobbing + 1 end, + enableHeadBobbing = function() noHeadBobbing = math.max(0, noHeadBobbing - 1) end, + + isZoomEnabled = function() return noZoom == 0 end, + disableZoom = function() noZoom = noZoom + 1 end, + enableZoom = function() noZoom = math.max(0, noZoom - 1) end, + + isThirdPersonOffsetControlEnabled = function() return third_person.noOffsetControl == 0 end, + disableThirdPersonOffsetControl = function() third_person.noOffsetControl = third_person.noOffsetControl + 1 end, + enableThirdPersonOffsetControl = function() third_person.noOffsetControl = math.max(0, third_person.noOffsetControl - 1) end, + }, + engineHandlers = { + onUpdate = onUpdate, + onInputUpdate = onInputUpdate, + onInputAction = function(action) + if action == input.ACTION.ZoomIn then + zoom(10) + elseif action == input.ACTION.ZoomOut then + zoom(-10) + end + end, + onActive = init, + onLoad = function(data) + if data and data.distance then third_person.baseDistance = data.distance end + end, + onSave = function() + return {version = 0, distance = third_person.baseDistance} + end, + }, +} + diff --git a/files/builtin_scripts/scripts/omw/head_bobbing.lua b/files/builtin_scripts/scripts/omw/head_bobbing.lua new file mode 100644 index 0000000000..fe809fca8a --- /dev/null +++ b/files/builtin_scripts/scripts/omw/head_bobbing.lua @@ -0,0 +1,51 @@ +local camera = require('openmw.camera') +local self = require('openmw.self') +local settings = require('openmw.settings') +local util = require('openmw.util') + +local doubleStepLength = settings._getFloatFromSettingsCfg('Camera', 'head bobbing step') * 2 +local stepHeight = settings._getFloatFromSettingsCfg('Camera', 'head bobbing height') +local maxRoll = math.rad(settings._getFloatFromSettingsCfg('Camera', 'head bobbing roll')) + +local effectWeight = 0 +local totalMovement = 0 + +local M = { + enabled = settings._getBoolFromSettingsCfg('Camera', 'head bobbing') +} + +-- Trajectory of each step is a scaled arc of 60 degrees. +local halfArc = math.rad(30) +local sampleArc = function(x) return 1 - math.cos(x * halfArc) end +local arcHeight = sampleArc(1) + +function M.update(dt, smoothedSpeed) + local speed = self:getCurrentSpeed() + speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high + totalMovement = totalMovement + speed * dt + if not M.enabled or camera.getMode() ~= camera.MODE.FirstPerson then + effectWeight = 0 + return + end + if self:isOnGround() then + effectWeight = math.min(1, effectWeight + dt * 5) + else + effectWeight = math.max(0, effectWeight - dt * 5) + end + + local doubleStepState = totalMovement / doubleStepLength + doubleStepState = doubleStepState - math.floor(doubleStepState) -- from 0 to 1 during 2 steps + local stepState = math.abs(doubleStepState * 4 - 2) - 1 -- from -1 to 1 on even steps and from 1 to -1 on odd steps + local effect = sampleArc(stepState) / arcHeight -- range from 0 to 1 + + -- Smoothly reduce the effect to zero when the player stops + local coef = math.min(smoothedSpeed / 300, 1) * effectWeight + + local zOffset = (0.5 - effect) * coef * stepHeight -- range from -stepHeight/2 to stepHeight/2 + local roll = ((stepState > 0 and 1) or -1) * effect * coef * maxRoll -- range from -maxRoll to maxRoll + camera.setFirstPersonOffset(camera.getFirstPersonOffset() + util.vector3(0, 0, zOffset)) + camera.setRoll(camera.getRoll() + roll) +end + +return M + diff --git a/files/builtin_scripts/scripts/omw/third_person.lua b/files/builtin_scripts/scripts/omw/third_person.lua new file mode 100644 index 0000000000..ca00ef5ee9 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/third_person.lua @@ -0,0 +1,139 @@ +local camera = require('openmw.camera') +local settings = require('openmw.settings') +local util = require('openmw.util') +local self = require('openmw.self') +local nearby = require('openmw.nearby') + +local MODE = camera.MODE +local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 } + +local M = { + baseDistance = settings._getFloatFromSettingsCfg('Camera', 'third person camera distance'), + preferredDistance = 0, + standingPreview = false, + noOffsetControl = 0, +} + +local viewOverShoulder = settings._getBoolFromSettingsCfg('Camera', 'view over shoulder') +local autoSwitchShoulder = settings._getBoolFromSettingsCfg('Camera', 'auto switch shoulder') +local shoulderOffset = settings._getVector2FromSettingsCfg('Camera', 'view over shoulder offset') +local zoomOutWhenMoveCoef = settings._getFloatFromSettingsCfg('Camera', 'zoom out when move coef') + +local defaultShoulder = (shoulderOffset.x > 0 and STATE.RightShoulder) or STATE.LeftShoulder +local rightShoulderOffset = util.vector2(math.abs(shoulderOffset.x), shoulderOffset.y) +local leftShoulderOffset = util.vector2(-math.abs(shoulderOffset.x), shoulderOffset.y) +local combatOffset = util.vector2(0, 15) + +local state = defaultShoulder + +local rayOptions = {collisionType = nearby.COLLISION_TYPE.Default - nearby.COLLISION_TYPE.Actor} +local function ray(from, angle, limit) + local to = from + util.transform.rotateZ(angle) * util.vector3(0, limit, 0) + local res = nearby.castRay(from, to, rayOptions) + if res.hit then + return (res.hitPos - from):length() + else + return limit + end +end + +local function trySwitchShoulder() + local limitToSwitch = 120 -- switch to other shoulder if wall is closer than this limit + local limitToSwitchBack = 300 -- switch back to default shoulder if there is no walls at this distance + + local pos = camera.getTrackedPosition() + local rayRight = ray(pos, camera.getYaw() + math.rad(90), limitToSwitchBack + 1) + local rayLeft = ray(pos, camera.getYaw() - math.rad(90), limitToSwitchBack + 1) + local rayRightForward = ray(pos, camera.getYaw() + math.rad(30), limitToSwitchBack + 1) + local rayLeftForward = ray(pos, camera.getYaw() - math.rad(30), limitToSwitchBack + 1) + + local distRight = math.min(rayRight, rayRightForward) + local distLeft = math.min(rayLeft, rayLeftForward) + + if distLeft < limitToSwitch and distRight > limitToSwitchBack then + state = STATE.RightShoulder + elseif distRight < limitToSwitch and distLeft > limitToSwitchBack then + state = STATE.LeftShoulder + elseif distRight > limitToSwitchBack and distLeft > limitToSwitchBack then + state = defaultShoulder + end +end + +local function calculateDistance(smoothedSpeed) + local smoothedSpeedSqr = smoothedSpeed * smoothedSpeed + return (M.baseDistance + math.max(camera.getPitch(), 0) * 50 + + smoothedSpeedSqr / (smoothedSpeedSqr + 300*300) * zoomOutWhenMoveCoef) +end + +local noThirdPersonLastFrame = true + +local function updateState() + local mode = camera.getMode() + local oldState = state + if (self:isInWeaponStance() or self:isInMagicStance()) and mode == MODE.ThirdPerson then + state = STATE.Combat + elseif self:isSwimming() then + state = STATE.Swimming + elseif oldState == STATE.Combat or oldState == STATE.Swimming then + state = defaultShoulder + end + if autoSwitchShoulder and (mode == MODE.ThirdPerson or state ~= oldState or noThirdPersonLastFrame) + and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then + trySwitchShoulder() + end + if oldState ~= state or noThirdPersonLastFrame then + -- State was changed, start focal point transition. + if mode == MODE.Vanity then + -- Player doesn't touch controls for a long time. Transition should be very slow. + camera.setFocalTransitionSpeed(0.2) + elseif (oldState == STATE.Combat or state == STATE.Combat) and + (mode ~= MODE.Preview or M.standingPreview) then + -- Transition to/from combat mode and we are not in preview mode. Should be fast. + camera.setFocalTransitionSpeed(5.0) + else + camera.setFocalTransitionSpeed(1.0) -- Default transition speed. + end + + if state == STATE.RightShoulder then + camera.setFocalPreferredOffset(rightShoulderOffset) + elseif state == STATE.LeftShoulder then + camera.setFocalPreferredOffset(leftShoulderOffset) + else + camera.setFocalPreferredOffset(combatOffset) + end + end +end + +function M.update(dt, smoothedSpeed) + local mode = camera.getMode() + if mode == MODE.FirstPerson or mode == MODE.Static then + noThirdPersonLastFrame = true + return + end + if not viewOverShoulder then + M.preferredDistance = M.baseDistance + camera.setPreferredThirdPersonDistance(M.baseDistance) + noThirdPersonLastFrame = false + return + end + + if M.noOffsetControl == 0 then + updateState() + else + state = nil + end + + M.preferredDistance = calculateDistance(smoothedSpeed) + if noThirdPersonLastFrame then -- just switched to third person view + camera.setPreferredThirdPersonDistance(M.preferredDistance) + camera.instantTransition() + noThirdPersonLastFrame = false + else + local maxIncrease = dt * (100 + M.baseDistance) + camera.setPreferredThirdPersonDistance(math.min( + M.preferredDistance, camera.getThirdPersonDistance() + maxIncrease)) + end +end + +return M + diff --git a/files/openmw.cfg b/files/openmw.cfg index afbf0810cc..d98097c3eb 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -2,6 +2,7 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) +content=builtin.omwscripts data=${MORROWIND_DATA_FILES} data-local="?userdata?data" resources=${OPENMW_RESOURCE_FILES} diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index 76f829379b..704ac68068 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -2,6 +2,7 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) +content=builtin.omwscripts data="?global?data" data=./data data-local="?userdata?data" From 3941c42a7162c2a3773aeb6cc1ddd69ea12397e6 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 17 Oct 2021 21:35:34 +0200 Subject: [PATCH 1736/2859] Use `applyDeferredPreviewRotationToPlayer` only after applying values from `luaControls`. Otherwise camera rotation is not smooth when movement is controlled from lua. --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 -- apps/openmw/mwmechanics/actors.cpp | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index cf1b0e936d..5d737bd446 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -101,8 +101,6 @@ namespace MWInput mMouseManager->update(dt); mSensorManager->update(dt); mActionManager->update(dt, controllerMove); - - MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(dt); } void InputManager::setDragDrop(bool dragDrop) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 32562591a5..b3ddefec3f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1598,6 +1598,7 @@ namespace MWMechanics if (playerCharacter) { + MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(duration); playerCharacter->update(duration); playerCharacter->setVisibility(1.f); } From ded89973629a409c7d0c2ffc4e56e70d33a330b9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 12 Nov 2021 19:22:20 +0100 Subject: [PATCH 1737/2859] Avoid problems if `builtin.omwscripts` is above `Morrowind.esm` in content list. --- apps/openmw/mwworld/worldimp.cpp | 12 +++++++++--- components/contentselector/model/contentmodel.cpp | 4 ++++ components/esm/esmreader.hpp | 6 +++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 14b1082ba3..639c37fe2d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -165,9 +165,15 @@ namespace MWWorld listener->loadingOff(); - // insert records that may not be present in all versions of MW - if (mEsm[0].getFormat() == 0) - ensureNeededRecords(); + // Find main game file + for (const ESM::ESMReader& reader : mEsm) + { + if (!Misc::StringUtils::ciEndsWith(reader.getName(), ".esm") && !Misc::StringUtils::ciEndsWith(reader.getName(), ".omwgame")) + continue; + if (reader.getFormat() == 0) + ensureNeededRecords(); // and insert records that may not be present in all versions of MW. + break; + } mCurrentDate.reset(new DateTimeManager()); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index ac7851d99c..199799025a 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -430,6 +430,10 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) if (item(info.fileName())) continue; + // Enabled by default in system openmw.cfg; shouldn't be shown in content list. + if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) + continue; + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { EsmFile *file = new EsmFile(path2); diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 92f2a6673b..d7eb6ff0a1 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -32,14 +32,14 @@ public: int getVer() const { return mHeader.mData.version; } int getRecordCount() const { return mHeader.mData.records; } float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } - const std::string getAuthor() const { return mHeader.mData.author; } - const std::string getDesc() const { return mHeader.mData.desc; } + const std::string& getAuthor() const { return mHeader.mData.author; } + const std::string& getDesc() const { return mHeader.mData.desc; } const std::vector &getGameFiles() const { return mHeader.mMaster; } const Header& getHeader() const { return mHeader; } int getFormat() const { return mHeader.mFormat; }; const NAME &retSubName() const { return mCtx.subName; } uint32_t getSubSize() const { return mCtx.leftSub; } - std::string getName() const {return mCtx.filename; }; + const std::string& getName() const { return mCtx.filename; }; /************************************************************************* * From 73821ace94f9abbd30bc22e8d736787580f5f603 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 1 Nov 2021 18:39:18 +0100 Subject: [PATCH 1738/2859] Update Lua docs --- docs/source/generate_luadoc.sh | 1 + docs/source/reference/lua-scripting/api.rst | 18 +- .../lua-scripting/interface_camera.rst | 6 + .../reference/lua-scripting/openmw_camera.rst | 5 + .../reference/lua-scripting/overview.rst | 27 ++- files/builtin_scripts/scripts/omw/camera.lua | 24 ++- files/lua_api/openmw/camera.lua | 171 ++++++++++++++++++ 7 files changed, 245 insertions(+), 7 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_camera.rst create mode 100644 docs/source/reference/lua-scripting/openmw_camera.rst create mode 100644 files/lua_api/openmw/camera.lua diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index 1af9f0e0f7..067a1ad4cf 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -65,4 +65,5 @@ $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw/*lua cd $FILES_DIR/builtin_scripts $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua +$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 6398a19be8..dd9d151482 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -17,7 +17,9 @@ Lua API reference openmw_nearby openmw_input openmw_ui + openmw_camera openmw_aux_util + interface_camera - :ref:`Engine handlers reference` @@ -56,11 +58,11 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.nearby ` | by local scripts | | Read-only access to the nearest area of the game world. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.input ` | by player scripts | | User input | +|:ref:`openmw.input ` | by player scripts | | User input. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.ui ` | by player scripts | | Controls :ref:`user interface ` | +|:ref:`openmw.ui ` | by player scripts | | Controls :ref:`user interface `. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|openmw.camera | by player scripts | | Controls camera (not implemented) | +|:ref:`openmw.camera ` | by player scripts | | Controls camera. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ **openmw_aux** @@ -73,3 +75,13 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid +=========================================================+====================+===============================================================+ |:ref:`openmw_aux.util ` | everywhere | | Miscellaneous utils | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ + +**Interfaces of built-in scripts** + ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +| Interface | Can be used | Description | ++=========================================================+====================+===============================================================+ +|:ref:`Camera ` | by player scripts | | Allows to alter behavior of the built-in camera script | +| | | | without overriding the script completely. | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ + diff --git a/docs/source/reference/lua-scripting/interface_camera.rst b/docs/source/reference/lua-scripting/interface_camera.rst new file mode 100644 index 0000000000..c2db83b721 --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_camera.rst @@ -0,0 +1,6 @@ +Interface Camera +================ + +.. raw:: html + :file: generated_html/scripts_omw_camera.html + diff --git a/docs/source/reference/lua-scripting/openmw_camera.rst b/docs/source/reference/lua-scripting/openmw_camera.rst new file mode 100644 index 0000000000..4090843920 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_camera.rst @@ -0,0 +1,5 @@ +Package openmw.camera +===================== + +.. raw:: html + :file: generated_html/openmw_camera.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 5bc413036a..938611635a 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -352,7 +352,7 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.ui ` | by player scripts | | Controls :ref:`user interface ` | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|openmw.camera | by player scripts | | Controls camera (not implemented) | +|:ref:`openmw.camera ` | by player scripts | | Controls camera | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ openmw_aux @@ -439,6 +439,15 @@ Using the interface: The order in which the scripts are started is important. So if one mod should override an interface provided by another mod, make sure that load order (i.e. the sequence of `lua-scripts=...` in `openmw.cfg`) is correct. +**Interfaces of built-in scripts** + ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +| Interface | Can be used | Description | ++=========================================================+====================+===============================================================+ +|:ref:`Camera ` | by player scripts | | Allows to alter behavior of the built-in camera script | +| | | | without overriding the script completely. | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ + Event system ============ @@ -719,8 +728,7 @@ you can import these files to get code autocompletion and integrated OpenMW API .. image:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/reference/lua-scripting/_static/lua-ide-project-settings.png - Press `Next`, choose the `Libraries` tab, and click `Add External Source Folder`. -- Specify there the path to ``resources/lua_api`` in your OpenMW installation. -- If you use `openmw_aux`_, add ``resources/vfs`` as an additional external source folder. +- Specify there paths to ``resources/lua_api`` and ``resources/vfs`` in your OpenMW installation. .. image:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/reference/lua-scripting/_static/lua-ide-import-api.png @@ -749,4 +757,17 @@ You can add special hints to give LDT more information: .. image:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/reference/lua-scripting/_static/lua-ide-code-completion2.png +In order to have autocompletion for script interfaces the information where to find these interfaces should be provided. +For example for the camera interface (defined in ``resources/vfs/scripts/omw/camera.lua``): + +.. code-block:: Lua + + --- @type Interfaces + -- @field scripts.omw.camera#Interface Camera + -- ... other interfaces here + --- @field #Interfaces I + local I = require('openmw.interfaces') + + I.Camera.disableZoom() + See `LDT Documentation Language `__ for more details. diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 55460dc615..1a5d1ca730 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -19,7 +19,7 @@ local noStandingPreview = 0 local noHeadBobbing = 0 local noZoom = 0 -function init() +local function init() camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation')) if camera.getMode() == MODE.FirstPerson then primaryMode = MODE.FirstPerson @@ -171,32 +171,54 @@ end return { interfaceName = 'Camera', + --- @module Camera + -- @usage require('openmw.interfaces').Camera interface = { + --- @field [parent=#Camera] #number version Interface version version = 0, + --- @function [parent=#Camera] getPrimaryMode Returns primary mode (MODE.FirstPerson or MODE.ThirdPerson). getPrimaryMode = function() return primaryMode end, + --- @function [parent=#Camera] getBaseThirdPersonDistance getBaseThirdPersonDistance = function() return third_person.baseDistance end, + --- @function [parent=#Camera] setBaseThirdPersonDistance setBaseThirdPersonDistance = function(v) third_person.baseDistance = v end, + --- @function [parent=#Camera] getTargetThirdPersonDistance getTargetThirdPersonDistance = function() return third_person.preferredDistance end, + --- @function [parent=#Camera] isModeControlEnabled isModeControlEnabled = function() return noModeControl == 0 end, + --- @function [parent=#Camera] disableModeControl disableModeControl = function() noModeControl = noModeControl + 1 end, + --- @function [parent=#Camera] enableModeControl enableModeControl = function() noModeControl = math.max(0, noModeControl - 1) end, + --- @function [parent=#Camera] isStandingPreviewEnabled isStandingPreviewEnabled = function() return previewIfStandSill and noStandingPreview == 0 end, + --- @function [parent=#Camera] disableStandingPreview disableStandingPreview = function() noStandingPreview = noStandingPreview + 1 end, + --- @function [parent=#Camera] enableStandingPreview enableStandingPreview = function() noStandingPreview = math.max(0, noStandingPreview - 1) end, + --- @function [parent=#Camera] isHeadBobbingEnabled isHeadBobbingEnabled = function() return head_bobbing.enabled and noHeadBobbing == 0 end, + --- @function [parent=#Camera] disableHeadBobbing disableHeadBobbing = function() noHeadBobbing = noHeadBobbing + 1 end, + --- @function [parent=#Camera] enableHeadBobbing enableHeadBobbing = function() noHeadBobbing = math.max(0, noHeadBobbing - 1) end, + --- @function [parent=#Camera] isZoomEnabled isZoomEnabled = function() return noZoom == 0 end, + --- @function [parent=#Camera] disableZoom disableZoom = function() noZoom = noZoom + 1 end, + --- @function [parent=#Camera] enableZoom enableZoom = function() noZoom = math.max(0, noZoom - 1) end, + --- @function [parent=#Camera] isThirdPersonOffsetControlEnabled isThirdPersonOffsetControlEnabled = function() return third_person.noOffsetControl == 0 end, + --- @function [parent=#Camera] disableThirdPersonOffsetControl disableThirdPersonOffsetControl = function() third_person.noOffsetControl = third_person.noOffsetControl + 1 end, + --- @function [parent=#Camera] enableThirdPersonOffsetControl enableThirdPersonOffsetControl = function() third_person.noOffsetControl = math.max(0, third_person.noOffsetControl - 1) end, }, engineHandlers = { diff --git a/files/lua_api/openmw/camera.lua b/files/lua_api/openmw/camera.lua new file mode 100644 index 0000000000..0d72214414 --- /dev/null +++ b/files/lua_api/openmw/camera.lua @@ -0,0 +1,171 @@ +------------------------------------------------------------------------------- +-- `openmw.camera` controls camera. +-- Can be used only by player scripts. +-- @module camera +-- @usage local camera = require('openmw.camera') + + +------------------------------------------------------------------------------- +-- @type MODE Camera modes. +-- @field #number Static Camera doesn't track player; player inputs doesn't affect camera; use `setStaticPosition` to move the camera. +-- @field #number FirstPerson First person mode. +-- @field #number ThirdPerson Third person mode; player character turns to the view direction. +-- @field #number Vanity Similar to Preview; camera slowly moves around the player. +-- @field #number Preview Third person mode, but player character doesn't turn to the view direction. + +------------------------------------------------------------------------------- +-- Camera modes. +-- @field [parent=#camera] #MODE MODE + +------------------------------------------------------------------------------- +-- Return the current @{openmw.camera#MODE}. +-- @function [parent=#camera] getMode +-- @return #MODE + +------------------------------------------------------------------------------- +-- Return the mode the camera will switch to after the end of the current animation. Can be nil. +-- @function [parent=#camera] getQueuedMode +-- @return #MODE + +------------------------------------------------------------------------------- +-- Change @{openmw.camera#MODE}; if the second (optional, true by default) argument is set to false, the switching can be delayed (see `getQueuedMode`). +-- @function [parent=#camera] setMode +-- @param #MODE mode +-- @param #boolean force + +------------------------------------------------------------------------------- +-- If set to true then after switching from Preview to ThirdPerson the player character turns to the camera view direction. Otherwise the camera turns to the character view direction. +-- @function [parent=#camera] allowCharacterDeferredRotation +-- @param #boolean boolValue + +------------------------------------------------------------------------------- +-- Show/hide crosshair. +-- @function [parent=#camera] showCrosshair +-- @param #boolean boolValue + +------------------------------------------------------------------------------- +-- Current position of the tracked object (the characters head if there is no animation). +-- @function [parent=#camera] getTrackedPosition +-- @return openmw.util#Vector3 + +------------------------------------------------------------------------------- +-- Current position of the camera. +-- @function [parent=#camera] getPosition +-- @return openmw.util#Vector3 + +------------------------------------------------------------------------------- +-- Camera pitch angle (radians) without taking extraPitch into account. +-- Full pitch is `getPitch()+getExtraPitch()`. +-- @function [parent=#camera] getPitch +-- @return #number + +------------------------------------------------------------------------------- +-- Force the pitch angle to the given value (radians); player input on this axis is ignored in this frame. +-- @function [parent=#camera] setPitch +-- @param #number value + +------------------------------------------------------------------------------- +-- Camera yaw angle (radians) without taking extraYaw into account. +-- Full yaw is `getYaw()+getExtraYaw()`. +-- @function [parent=#camera] getYaw +-- @return #number + +------------------------------------------------------------------------------- +-- Force the yaw angle to the given value (radians); player input on this axis is ignored in this frame. +-- @function [parent=#camera] setYaw +-- @param #number value + +------------------------------------------------------------------------------- +-- Get camera roll angle (radians). +-- @function [parent=#camera] getRoll +-- @return #number + +------------------------------------------------------------------------------- +-- Set camera roll angle (radians). +-- @function [parent=#camera] setRoll +-- @param #number value + +------------------------------------------------------------------------------- +-- Additional summand for the pitch angle that is not affected by player input. +-- Full pitch is `getPitch()+getExtraPitch()`. +-- @function [parent=#camera] getExtraPitch +-- @return #number + +------------------------------------------------------------------------------- +-- Additional summand for the pitch angle; useful for camera shaking effects. +-- Setting extra pitch doesn't block player input. +-- Full pitch is `getPitch()+getExtraPitch()`. +-- @function [parent=#camera] setExtraPitch +-- @param #number value + +------------------------------------------------------------------------------- +-- Additional summand for the yaw angle that is not affected by player input. +-- Full yaw is `getYaw()+getExtraYaw()`. +-- @function [parent=#camera] getExtraYaw +-- @return #number + +------------------------------------------------------------------------------- +-- Additional summand for the yaw angle; useful for camera shaking effects. +-- Setting extra pitch doesn't block player input. +-- Full yaw is `getYaw()+getExtraYaw()`. +-- @function [parent=#camera] setExtraYaw +-- @param #number value + +------------------------------------------------------------------------------- +-- Set camera position; can be used only if camera is in Static mode. +-- @function [parent=#camera] setStaticPosition +-- @param openmw.util#Vector3 pos + +------------------------------------------------------------------------------- +-- The offset between the characters head and the camera in first person mode (3d vector). +-- @function [parent=#camera] getFirstPersonOffset +-- @return openmw.util#Vector3 + +------------------------------------------------------------------------------- +-- Set the offset between the characters head and the camera in first person mode (3d vector). +-- @function [parent=#camera] setFirstPersonOffset +-- @param openmw.util#Vector3 offset + +------------------------------------------------------------------------------- +-- Preferred offset between tracked position (see `getTrackedPosition`) and the camera focal point (the center of the screen) in third person mode. +-- See `setFocalPreferredOffset`. +-- @function [parent=#camera] getFocalPreferredOffset +-- @return openmw.util#Vector2 + +------------------------------------------------------------------------------- +-- Set preferred offset between tracked position (see `getTrackedPosition`) and the camera focal point (the center of the screen) in third person mode. +-- The offset is a 2d vector (X, Y) where X is horizontal (to the right from the character) and Y component is vertical (upward). +-- The real offset can differ from the preferred one during smooth transition of if blocked by an obstacle. +-- Smooth transition happens by default every time when the preferred offset was changed. Use `instantTransition()` to skip the current transition. +-- @function [parent=#camera] setFocalPreferredOffset +-- @param openmw.util#Vector2 offset + +------------------------------------------------------------------------------- +-- The actual distance between the camera and the character in third person mode; can differ from the preferred one if there is an obstacle. +-- @function [parent=#camera] getThirdPersonDistance +-- @return #number + +------------------------------------------------------------------------------- +-- Set preferred distance between the camera and the character in third person mode. +-- @function [parent=#camera] setPreferredThirdPersonDistance +-- @param #number distance + +------------------------------------------------------------------------------- +-- The current speed coefficient of focal point (the center of the screen in third person mode) smooth transition. +-- @function [parent=#camera] getFocalTransitionSpeed +-- @return #number + +------------------------------------------------------------------------------- +-- Set the speed coefficient of focal point (the center of the screen in third person mode) smooth transition. +-- Smooth transition happens by default every time when the preferred offset was changed. Use `instantTransition()` to skip the current transition. +-- @function [parent=#camera] setFocalTransitionSpeed +-- Set the speed coefficient +-- @param #number speed + +------------------------------------------------------------------------------- +-- Make instant the current transition of camera focal point and the current deferred rotation (see `allowCharacterDeferredRotation`). +-- @function [parent=#camera] instantTransition + + +return nil + From 42020bc2c3558915c0e033e6ee4bd9858132cccc Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 20 Nov 2021 00:02:48 +0000 Subject: [PATCH 1739/2859] Ignore the axis argument when rotating the player --- CHANGELOG.md | 1 + apps/openmw/mwscript/transformationextensions.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be789859ea..27a5094e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ Bug #6321: Arrow enchantments should always be applied to the target Bug #6322: Total sold/cost should reset to 0 when there are no items offered Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house + Bug #6324: Special Slave Companions: Can't buy the slave companions Bug #6326: Detect Enchantment/Key should detect items in unresolved containers Bug #6327: Blocking roots the character in place Bug #6343: Magic projectile speed doesn't take race weight into account diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 5cfb2b2989..292965fd94 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -622,12 +622,13 @@ namespace MWScript runtime.pop(); auto rot = ptr.getRefData().getPosition().asRotationVec3(); - if (axis == "x") + // Regardless of the axis argument, the player may only be rotated on Z + if (axis == "z" || MWMechanics::getPlayer() == ptr) + rot.z() += rotation; + else if (axis == "x") rot.x() += rotation; else if (axis == "y") rot.y() += rotation; - else if (axis == "z") - rot.z() += rotation; MWBase::Environment::get().getWorld()->rotateObject(ptr,rot); } }; From fcef92e3ea74b46754f3f4ec4e55730fb34d1e46 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 20 Nov 2021 02:59:57 +0300 Subject: [PATCH 1740/2859] Make AiExtensions less toxic (bug #6429) --- CHANGELOG.md | 1 + apps/openmw/mwscript/aiextensions.cpp | 76 ++++++++++++++++++++------- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be789859ea..d5f157d81a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation Bug #6396: Inputting certain Unicode characters triggers an assertion Bug #6416: Morphs are applied to the wrong target + Bug #6429: Wyrmhaven: Can't add AI packages to player Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 33f16aea3f..d1adffc9fa 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -14,6 +14,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/aiactivate.hpp" #include "../mwmechanics/aiescort.hpp" @@ -50,6 +51,9 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; i(duration), x, y, z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); @@ -148,10 +158,14 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; igetStore().get().find(cellID); + if (!MWBase::Environment::get().getWorld()->getStore().get().search(cellID)) + return; MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); @@ -169,9 +183,11 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats (ptr).getAiSequence().isPackageDone(); + bool done = false; + if (ptr.getClass().isActor()) + done = ptr.getClass().getCreatureStats(ptr).getAiSequence().isPackageDone(); - runtime.push (value); + runtime.push(done); } }; @@ -224,6 +240,9 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; i @@ -256,6 +278,9 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + int modified = ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value; ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex, modified); @@ -309,6 +334,9 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; igetGreetingState(actor) == MWMechanics::Greet_InProgress; - bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor); - targetsAreEqual = (greeting && sayActive) || mechMgr->isTurningToPlayer(actor); + const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); + MWWorld::Ptr targetPtr; + if (creatureStats.getAiSequence().getCombatTarget(targetPtr)) + { + if (!targetPtr.isEmpty() && targetPtr.getCellRef().getRefId() == testedTargetId) + targetsAreEqual = true; + } + else if (testedTargetId == "player") // Currently the player ID is hardcoded + { + MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); + bool greeting = mechMgr->getGreetingState(actor) == MWMechanics::Greet_InProgress; + bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor); + targetsAreEqual = (greeting && sayActive) || mechMgr->isTurningToPlayer(actor); + } } - runtime.push(int(targetsAreEqual)); + runtime.push(targetsAreEqual); } }; @@ -474,6 +507,8 @@ namespace MWScript void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr actor = R()(runtime); + if (!actor.getClass().isActor()) + return; MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); creatureStats.getAiSequence().stopCombat(); } @@ -505,6 +540,9 @@ namespace MWScript Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); + if (!actor.getClass().isActor() || actor == MWMechanics::getPlayer()) + return; + MWMechanics::AiFace facePackage(x, y); actor.getClass().getCreatureStats(actor).getAiSequence().stack(facePackage, actor); } From 231da19aa4e1a01e625c853d613089ec671e4bcb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 16 Nov 2021 22:04:04 +0100 Subject: [PATCH 1741/2859] Load repeat flag and use reset argument --- apps/openmw/mwmechanics/aiactivate.cpp | 4 +-- apps/openmw/mwmechanics/aiactivate.hpp | 2 +- apps/openmw/mwmechanics/aiescort.cpp | 8 ++--- apps/openmw/mwmechanics/aiescort.hpp | 4 +-- apps/openmw/mwmechanics/aifollow.cpp | 24 +++------------ apps/openmw/mwmechanics/aifollow.hpp | 6 ++-- apps/openmw/mwmechanics/aipackage.hpp | 2 +- apps/openmw/mwmechanics/aisequence.cpp | 36 ++++------------------ apps/openmw/mwmechanics/aisequence.hpp | 3 -- apps/openmw/mwmechanics/aitravel.cpp | 8 ++--- apps/openmw/mwmechanics/aitravel.hpp | 4 +-- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/typedaipackage.hpp | 3 ++ apps/openmw/mwscript/aiextensions.cpp | 32 +++++++++++-------- components/esm/aipackage.hpp | 8 +++-- 15 files changed, 56 insertions(+), 90 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index b4ddf0c030..aa36b9af95 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -13,8 +13,8 @@ namespace MWMechanics { - AiActivate::AiActivate(const std::string &objectId) - : mObjectId(objectId) + AiActivate::AiActivate(const std::string &objectId, bool repeat) + : TypedAiPackage(repeat), mObjectId(objectId) { } diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index dc7e0bb26b..ad4d4e6064 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -24,7 +24,7 @@ namespace MWMechanics public: /// Constructor /** \param objectId Reference to object to activate **/ - explicit AiActivate(const std::string &objectId); + explicit AiActivate(const std::string &objectId, bool repeat); explicit AiActivate(const ESM::AiSequence::AiActivate* activate); diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 75c0461105..6a6df50049 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -20,16 +20,16 @@ namespace MWMechanics { - AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) + AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z, bool repeat) + : TypedAiPackage(repeat), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mTargetActorRefId = actorId; } - AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z) - : mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) + AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z, bool repeat) + : TypedAiPackage(repeat), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 27a177893d..c49e227e09 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -22,11 +22,11 @@ namespace MWMechanics /// Implementation of AiEscort /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time \implement AiEscort **/ - AiEscort(const std::string &actorId, int duration, float x, float y, float z); + AiEscort(const std::string &actorId, int duration, float x, float y, float z, bool repeat); /// Implementation of AiEscortCell /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time \implement AiEscortCell **/ - AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z); + AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z, bool repeat); AiEscort(const ESM::AiSequence::AiEscort* escort); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index ec23679977..f44321dfbc 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -28,36 +28,20 @@ namespace MWMechanics { int AiFollow::mFollowIndexCounter = 0; -AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z, bool repeat) +: TypedAiPackage(repeat), mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actorId; } -AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) +AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z, bool repeat) +: TypedAiPackage(repeat), mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { mTargetActorRefId = actorId; } -AiFollow::AiFollow(const MWWorld::Ptr& actor, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actor.getCellRef().getRefId(); - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); -} - -AiFollow::AiFollow(const MWWorld::Ptr& actor, const std::string &cellId, float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) -{ - mTargetActorRefId = actor.getCellRef().getRefId(); - mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId(); -} - AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!commanded)) , mAlwaysFollow(true), mDuration(0), mRemainingDuration(0), mX(0), mY(0), mZ(0) diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index c4ac5eb3f2..b83e464fa1 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -40,12 +40,10 @@ namespace MWMechanics class AiFollow final : public TypedAiPackage { public: - AiFollow(const std::string &actorId, float duration, float x, float y, float z); - AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z); /// Follow Actor for duration or until you arrive at a world position - AiFollow(const MWWorld::Ptr& actor, float duration, float X, float Y, float Z); + AiFollow(const std::string &actorId, float duration, float x, float y, float z, bool repeat); /// Follow Actor for duration or until you arrive at a position in a cell - AiFollow(const MWWorld::Ptr& actor, const std::string &CellId, float duration, float X, float Y, float Z); + AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z, bool repeat); /// Follow Actor indefinitively AiFollow(const MWWorld::Ptr& actor, bool commanded=false); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 5270b74166..498c6e3050 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -108,7 +108,7 @@ namespace MWMechanics /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? bool shouldCancelPreviousAi() const { return mOptions.mShouldCancelPreviousAi; } - /// Return true if this package should repeat. Currently only used for Wander packages. + /// Return true if this package should repeat. bool getRepeat() const { return mOptions.mRepeat; } virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index bf4cf28deb..b1827dd8ef 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -31,14 +31,13 @@ void AiSequence::copy (const AiSequence& sequence) sequence.mAiState.copy(mAiState); } -AiSequence::AiSequence() : mDone (false), mRepeat(false), mLastAiPackage(AiPackageTypeId::None) {} +AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {} AiSequence::AiSequence (const AiSequence& sequence) { copy (sequence); mDone = sequence.mDone; mLastAiPackage = sequence.mLastAiPackage; - mRepeat = sequence.mRepeat; } AiSequence& AiSequence::operator= (const AiSequence& sequence) @@ -281,7 +280,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac if (package->execute(actor, characterController, mAiState, duration)) { // Put repeating noncombat AI packages on the end of the stack so they can be used again - if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) + if (isActualAiPackage(packageTypeId) && package->getRepeat()) { package->reset(); mPackages.push_back(package->clone()); @@ -355,7 +354,6 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo else ++it; } - mRepeat=false; } // insert new package in correct place depending on priority @@ -401,10 +399,6 @@ const AiPackage& MWMechanics::AiSequence::getActivePackage() void AiSequence::fill(const ESM::AIPackageList &list) { - // If there is more than one package in the list, enable repeating - if (list.mList.size() >= 2) - mRepeat = true; - for (const auto& esmPackage : list.mList) { std::unique_ptr package; @@ -420,22 +414,22 @@ void AiSequence::fill(const ESM::AIPackageList &list) else if (esmPackage.mType == ESM::AI_Escort) { ESM::AITarget data = esmPackage.mTarget; - package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); + package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Travel) { ESM::AITravel data = esmPackage.mTravel; - package = std::make_unique(data.mX, data.mY, data.mZ); + package = std::make_unique(data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Activate) { ESM::AIActivate data = esmPackage.mActivate; - package = std::make_unique(data.mName.toString()); + package = std::make_unique(data.mName.toString(), data.mShouldRepeat != 0); } else //if (esmPackage.mType == ESM::AI_Follow) { ESM::AITarget data = esmPackage.mTarget; - package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ); + package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } mPackages.push_back(std::move(package)); } @@ -454,24 +448,6 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) if (!sequence.mPackages.empty()) clear(); - // If there is more than one non-combat, non-pursue package in the list, enable repeating. - int count = 0; - for (auto& container : sequence.mPackages) - { - switch (container.mType) - { - case ESM::AiSequence::Ai_Wander: - case ESM::AiSequence::Ai_Travel: - case ESM::AiSequence::Ai_Escort: - case ESM::AiSequence::Ai_Follow: - case ESM::AiSequence::Ai_Activate: - ++count; - } - } - - if (count > 1) - mRepeat = true; - // Load packages for (auto& container : sequence.mPackages) { diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 645524d381..90bd999c91 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -43,9 +43,6 @@ namespace MWMechanics ///Finished with top AIPackage, set for one frame bool mDone; - ///Does this AI sequence repeat (repeating of Wander packages handled separately) - bool mRepeat; - ///Copy AiSequence void copy (const AiSequence& sequence); diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 2594adcb34..7a2353dbc6 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -34,8 +34,8 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) namespace MWMechanics { - AiTravel::AiTravel(float x, float y, float z, AiTravel*) - : mX(x), mY(y), mZ(z), mHidden(false) + AiTravel::AiTravel(float x, float y, float z, bool repeat, AiTravel*) + : TypedAiPackage(repeat), mX(x), mY(y), mZ(z), mHidden(false) { } @@ -44,8 +44,8 @@ namespace MWMechanics { } - AiTravel::AiTravel(float x, float y, float z) - : AiTravel(x, y, z, this) + AiTravel::AiTravel(float x, float y, float z, bool repeat) + : AiTravel(x, y, z, repeat, this) { } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index ee4ff9ad4d..303df7b105 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -19,11 +19,11 @@ namespace MWMechanics class AiTravel : public TypedAiPackage { public: - AiTravel(float x, float y, float z, AiTravel* derived); + AiTravel(float x, float y, float z, bool repeat, AiTravel* derived); AiTravel(float x, float y, float z, AiInternalTravel* derived); - AiTravel(float x, float y, float z); + AiTravel(float x, float y, float z, bool repeat); explicit AiTravel(const ESM::AiSequence::AiTravel* travel); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 53f050c0dd..664ae105f3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -105,7 +105,7 @@ namespace MWMechanics } AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): - TypedAiPackage(makeDefaultOptions().withRepeat(repeat)), + TypedAiPackage(repeat), mDistance(std::max(0, distance)), mDuration(std::max(0, duration)), mRemainingDuration(duration), mTimeOfDay(timeOfDay), diff --git a/apps/openmw/mwmechanics/typedaipackage.hpp b/apps/openmw/mwmechanics/typedaipackage.hpp index d2d424326c..0ea276999f 100644 --- a/apps/openmw/mwmechanics/typedaipackage.hpp +++ b/apps/openmw/mwmechanics/typedaipackage.hpp @@ -11,6 +11,9 @@ namespace MWMechanics TypedAiPackage() : AiPackage(T::getTypeId(), T::makeDefaultOptions()) {} + TypedAiPackage(bool repeat) : + AiPackage(T::getTypeId(), T::makeDefaultOptions().withRepeat(repeat)) {} + TypedAiPackage(const Options& options) : AiPackage(T::getTypeId(), options) {} diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index d1adffc9fa..5ebc0bc529 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -48,13 +48,14 @@ namespace MWScript std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - // discard additional arguments (reset), because we have no idea what they mean. + // The value of the reset argument doesn't actually matter + bool repeat = arg0; for (unsigned int i=0; i(duration), x, y, z); + MWMechanics::AiEscort escortPackage(actorID, static_cast(duration), x, y, z, repeat); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; @@ -155,7 +158,8 @@ namespace MWScript Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); - // discard additional arguments (reset), because we have no idea what they mean. + // The value of the reset argument doesn't actually matter + bool repeat = arg0; for (unsigned int i=0; igetStore().get().search(cellID)) return; - MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z); + MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z, repeat); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; @@ -237,7 +241,7 @@ namespace MWScript --arg0; } - // discard additional arguments (reset), because we have no idea what they mean. + // discard additional arguments, because we have no idea what they mean. for (unsigned int i=0; i Date: Wed, 17 Nov 2021 20:44:55 +0100 Subject: [PATCH 1742/2859] Save repeat and duration --- CHANGELOG.md | 2 + apps/esmtool/record.cpp | 6 +-- apps/openmw/mwmechanics/aiactivate.cpp | 3 +- apps/openmw/mwmechanics/aiescort.cpp | 9 ++--- apps/openmw/mwmechanics/aifollow.cpp | 18 +++++---- apps/openmw/mwmechanics/aitravel.cpp | 3 +- components/esm/aisequence.cpp | 54 ++++++++++++++++++++++++++ components/esm/aisequence.hpp | 4 ++ components/esm/savedgame.cpp | 2 +- 9 files changed, 82 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6042e37c08..7b326b29a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Bug #6168: Weather particles flicker for a frame at start of storms Bug #6172: Some creatures can't open doors Bug #6174: Spellmaking and Enchanting sliders differences from vanilla + Bug #6177: Followers of player follower stop following after waiting for a day Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6253: Multiple instances of Reflect stack additively @@ -81,6 +82,7 @@ Bug #6416: Morphs are applied to the wrong target Bug #6429: Wyrmhaven: Can't add AI packages to player Feature #890: OpenMW-CS: Column filtering + Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 1de3288ad4..171f64eabe 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -30,7 +30,7 @@ void printAIPackage(const ESM::AIPackage& p) { std::cout << " Travel Coordinates: (" << p.mTravel.mX << "," << p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl; - std::cout << " Travel Unknown: " << p.mTravel.mUnk << std::endl; + std::cout << " Should repeat: " << p.mTravel.mShouldRepeat << std::endl; } else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort) { @@ -38,12 +38,12 @@ void printAIPackage(const ESM::AIPackage& p) << p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl; std::cout << " Duration: " << p.mTarget.mDuration << std::endl; std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl; - std::cout << " Unknown: " << p.mTarget.mUnk << std::endl; + std::cout << " Should repeat: " << p.mTarget.mShouldRepeat << std::endl; } else if (p.mType == ESM::AI_Activate) { std::cout << " Name: " << p.mActivate.mName.toString() << std::endl; - std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl; + std::cout << " Should repeat: " << p.mActivate.mShouldRepeat << std::endl; } else { std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl; diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index aa36b9af95..06aef0cdb0 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -48,6 +48,7 @@ namespace MWMechanics { std::unique_ptr activate(new ESM::AiSequence::AiActivate()); activate->mTargetId = mObjectId; + activate->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Activate; @@ -56,7 +57,7 @@ namespace MWMechanics } AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) - : mObjectId(activate->mTargetId) + : AiActivate(activate->mTargetId, activate->mRepeat) { } } diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 6a6df50049..0184c6e66f 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -37,11 +37,8 @@ namespace MWMechanics } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) - : mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) - // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. - // The exact value of mDuration only matters for repeating packages. - // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. - , mDuration(escort->mRemainingDuration > 0) + : TypedAiPackage(escort->mRepeat), mCellId(escort->mCellId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) + , mDuration(escort->mData.mDuration) , mRemainingDuration(escort->mRemainingDuration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) @@ -103,10 +100,12 @@ namespace MWMechanics escort->mData.mX = mX; escort->mData.mY = mY; escort->mData.mZ = mZ; + escort->mData.mDuration = mDuration; escort->mTargetId = mTargetActorRefId; escort->mTargetActorId = mTargetActorId; escort->mRemainingDuration = mRemainingDuration; escort->mCellId = mCellId; + escort->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Escort; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f44321dfbc..43a0f25c62 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -52,12 +52,9 @@ AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) } AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) - : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded)) + : TypedAiPackage(makeDefaultOptions().withShouldCancelPreviousAi(!follow->mCommanded).withRepeat(follow->mRepeat)) , mAlwaysFollow(follow->mAlwaysFollow) - // mDuration isn't saved in the save file, so just giving it "1" for now if the package had a duration. - // The exact value of mDuration only matters for repeating packages. - // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. - , mDuration(follow->mRemainingDuration) + , mDuration(follow->mData.mDuration) , mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mCellId(follow->mCellId), mActive(follow->mActive), mFollowIndex(mFollowIndexCounter++) @@ -144,12 +141,15 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte if (actor.getCell()->isExterior()) //Outside? { if (mCellId == "") //No cell to travel to + { + mRemainingDuration = mDuration; return true; + } } - else + else if (mCellId == actor.getCell()->getCell()->mName) //Cell to travel to { - if (mCellId == actor.getCell()->getCell()->mName) //Cell to travel to - return true; + mRemainingDuration = mDuration; + return true; } } } @@ -205,6 +205,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const follow->mData.mX = mX; follow->mData.mY = mY; follow->mData.mZ = mZ; + follow->mData.mDuration = mDuration; follow->mTargetId = mTargetActorRefId; follow->mTargetActorId = mTargetActorId; follow->mRemainingDuration = mRemainingDuration; @@ -212,6 +213,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const follow->mAlwaysFollow = mAlwaysFollow; follow->mCommanded = isCommanded(); follow->mActive = mActive; + follow->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 7a2353dbc6..0b4a1411eb 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -50,7 +50,7 @@ namespace MWMechanics } AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) - : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) + : TypedAiPackage(travel->mRepeat), mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) { // Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type assert(!travel->mHidden); @@ -125,6 +125,7 @@ namespace MWMechanics travel->mData.mY = mY; travel->mData.mZ = mZ; travel->mHidden = mHidden; + travel->mRepeat = getRepeat(); ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Travel; diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index ca4c21802c..b99cac3ade 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include #include namespace ESM @@ -34,12 +35,16 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); esm.getHNOT (mHidden, "HIDD"); + mRepeat = false; + esm.getHNOT(mRepeat, "REPT"); } void AiTravel::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); esm.writeHNT ("HIDD", mHidden); + if(mRepeat) + esm.writeHNT("REPT", mRepeat); } void AiEscort::load(ESMReader &esm) @@ -50,6 +55,15 @@ namespace AiSequence esm.getHNOT (mTargetActorId, "TAID"); esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); + mRepeat = false; + esm.getHNOT(mRepeat, "REPT"); + if(esm.getFormat() < 18) + { + // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. + // The exact value of mDuration only matters for repeating packages. + // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. + mData.mDuration = std::max(mRemainingDuration > 0, mRemainingDuration); + } } void AiEscort::save(ESMWriter &esm) const @@ -60,6 +74,8 @@ namespace AiSequence esm.writeHNT ("DURA", mRemainingDuration); if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); + if(mRepeat) + esm.writeHNT("REPT", mRepeat); } void AiFollow::load(ESMReader &esm) @@ -75,6 +91,15 @@ namespace AiSequence esm.getHNOT (mCommanded, "CMND"); mActive = false; esm.getHNOT (mActive, "ACTV"); + mRepeat = false; + esm.getHNOT(mRepeat, "REPT"); + if(esm.getFormat() < 18) + { + // mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration. + // The exact value of mDuration only matters for repeating packages. + // Previously mRemainingDuration could be negative even when mDuration was 0. Checking for > 0 should fix old saves. + mData.mDuration = std::max(mRemainingDuration > 0, mRemainingDuration); + } } void AiFollow::save(ESMWriter &esm) const @@ -89,16 +114,22 @@ namespace AiSequence esm.writeHNT ("CMND", mCommanded); if (mActive) esm.writeHNT("ACTV", mActive); + if(mRepeat) + esm.writeHNT("REPT", mRepeat); } void AiActivate::load(ESMReader &esm) { mTargetId = esm.getHNString("TARG"); + mRepeat = false; + esm.getHNOT(mRepeat, "REPT"); } void AiActivate::save(ESMWriter &esm) const { esm.writeHNString("TARG", mTargetId); + if(mRepeat) + esm.writeHNT("REPT", mRepeat); } void AiCombat::load(ESMReader &esm) @@ -166,6 +197,7 @@ namespace AiSequence void AiSequence::load(ESMReader &esm) { + int count = 0; while (esm.isNextSub("AIPK")) { int type; @@ -181,6 +213,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Travel: @@ -188,6 +221,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Escort: @@ -195,6 +229,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Follow: @@ -202,6 +237,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Activate: @@ -209,6 +245,7 @@ namespace AiSequence std::unique_ptr ptr = std::make_unique(); ptr->load(esm); mPackages.back().mPackage = ptr.release(); + ++count; break; } case Ai_Combat: @@ -231,6 +268,23 @@ namespace AiSequence } esm.getHNOT (mLastAiPackage, "LAST"); + + if(count > 1 && esm.getFormat() < 18) + { + for(auto& pkg : mPackages) + { + if(pkg.mType == Ai_Wander) + static_cast(pkg.mPackage)->mData.mShouldRepeat = true; + else if(pkg.mType == Ai_Travel) + static_cast(pkg.mPackage)->mRepeat = true; + else if(pkg.mType == Ai_Escort) + static_cast(pkg.mPackage)->mRepeat = true; + else if(pkg.mType == Ai_Follow) + static_cast(pkg.mPackage)->mRepeat = true; + else if(pkg.mType == Ai_Activate) + static_cast(pkg.mPackage)->mRepeat = true; + } + } } } } diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index d8c20185f8..00c1316d9c 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -81,6 +81,7 @@ namespace ESM { AiTravelData mData; bool mHidden; + bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -94,6 +95,7 @@ namespace ESM std::string mTargetId; std::string mCellId; float mRemainingDuration; + bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -112,6 +114,7 @@ namespace ESM bool mCommanded; bool mActive; + bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -120,6 +123,7 @@ namespace ESM struct AiActivate : AiPackage { std::string mTargetId; + bool mRepeat; void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 8a98a63419..4ce0876bb8 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 17; +int ESM::SavedGame::sCurrentFormat = 18; void ESM::SavedGame::load (ESMReader &esm) { From 52f963462410a701ccaa9cea55da49c44a955db1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 17 Nov 2021 21:09:01 +0100 Subject: [PATCH 1743/2859] Allow the CS to handle the repeat flag for all types --- apps/opencs/model/world/columns.cpp | 2 +- apps/opencs/model/world/refidadapterimp.hpp | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 6eefdb6e21..8e53b3b270 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -255,7 +255,7 @@ namespace CSMWorld { ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiWanderToD, "Wander ToD" }, - { ColumnId_AiWanderRepeat, "Wander Repeat" }, + { ColumnId_AiWanderRepeat, "Ai Repeat" }, { ColumnId_AiActivateName, "Activate" }, { ColumnId_AiTargetId, "Target ID" }, { ColumnId_AiTargetCell, "Target Cell" }, diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index c35d3c5a7c..153d1bde91 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1678,7 +1678,7 @@ namespace CSMWorld newRow.mWander.mTimeOfDay = 0; for (int i = 0; i < 8; ++i) newRow.mWander.mIdle[i] = 0; - newRow.mWander.mShouldRepeat = 0; + newRow.mWander.mShouldRepeat = 1; newRow.mCellName = ""; if (position >= (int)list.size()) @@ -1784,9 +1784,15 @@ namespace CSMWorld return static_cast(content.mWander.mIdle[subColIndex-4]); else return QVariant(); - case 12: // wander repeat + case 12: // repeat if (content.mType == ESM::AI_Wander) return content.mWander.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Travel) + return content.mTravel.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + return content.mTarget.mShouldRepeat != 0; + else if (content.mType == ESM::AI_Activate) + return content.mActivate.mShouldRepeat != 0; else return QVariant(); case 13: // activate name @@ -1895,6 +1901,12 @@ namespace CSMWorld case 12: if (content.mType == ESM::AI_Wander) content.mWander.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Travel) + content.mTravel.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mShouldRepeat = static_cast(value.toInt()); + else if (content.mType == ESM::AI_Activate) + content.mActivate.mShouldRepeat = static_cast(value.toInt()); else return; // return without saving From 40656b31359797f9ff1594736c8cbf37a94e46bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Fri, 12 Nov 2021 11:08:17 +0100 Subject: [PATCH 1744/2859] Embed error marker in osgt format inside a string defined in misc/errorMarker.hpp. Use the embed error marker we fail to load a mesh. --- components/CMakeLists.txt | 2 +- components/misc/errorMarker.cpp | 1390 ++++++++++++++++++++++++++ components/misc/errorMarker.hpp | 11 + components/resource/scenemanager.cpp | 64 +- 4 files changed, 1438 insertions(+), 29 deletions(-) create mode 100644 components/misc/errorMarker.cpp create mode 100644 components/misc/errorMarker.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 71a96716ec..012677fc0d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -93,7 +93,7 @@ add_component_dir (esmterrain add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread - compression osguservalues + compression osguservalues errorMarker ) add_component_dir (debug diff --git a/components/misc/errorMarker.cpp b/components/misc/errorMarker.cpp new file mode 100644 index 0000000000..fd2b0b53ac --- /dev/null +++ b/components/misc/errorMarker.cpp @@ -0,0 +1,1390 @@ +#include "errorMarker.hpp" + +namespace Misc +{ + const std::string errorMarker = "#Ascii Scene " +"#Version 162 " +"#Generator OpenSceneGraph 3.6.5 " +"" +"osg::Group {" +" UniqueID 1 " +" Children 5 {" +" osg::Geode {" +" UniqueID 2 " +" Name \"Error\" " +" Drawables 1 {" +" osg::Geometry {" +" UniqueID 3 " +" DataVariance STATIC " +" StateSet TRUE {" +" osg::StateSet {" +" UniqueID 4 " +" DataVariance STATIC " +" ModeList 1 {" +" GL_BLEND ON " +" }" +" AttributeList 1 {" +" osg::Material {" +" UniqueID 5 " +" Name \"Error\" " +" Ambient TRUE Front 1 1 1 0.5 Back 1 1 1 0.5 " +" Diffuse TRUE Front 0.8 0.704 0.32 0.5 Back 0.8 0.704 0.32 0.5 " +" Specular TRUE Front 0.5 0.5 0.5 0.5 Back 0.5 0.5 0.5 0.5 " +" Emission TRUE Front 1 0.88 0.4 0.5 Back 1 0.88 0.4 0.5 " +" Shininess TRUE Front 28.8 Back 28.8 " +" }" +" Value OFF " +" }" +" RenderingHint 2 " +" RenderBinMode USE_RENDERBIN_DETAILS " +" BinNumber 10 " +" BinName \"DepthSortedBin\" " +" }" +" }" +" PrimitiveSetList 1 {" +" osg::DrawElementsUShort {" +" UniqueID 6 " +" BufferObject TRUE {" +" osg::ElementBufferObject {" +" UniqueID 7 " +" Target 34963 " +" }" +" }" +" Mode TRIANGLES " +" vector 108 {" +" 0 1 2 3 " +" 0 2 2 4 " +" 3 5 3 4 " +" 4 6 5 6 " +" 7 8 8 9 " +" 10 6 8 10 " +" 5 6 11 11 " +" 6 10 12 5 " +" 11 13 12 11 " +" 11 14 13 10 " +" 15 11 11 15 " +" 16 15 17 16 " +" 18 16 17 17 " +" 19 18 20 21 " +" 22 23 20 22 " +" 22 24 23 25 " +" 23 24 24 26 " +" 25 26 27 28 " +" 28 29 30 26 " +" 28 30 25 26 " +" 31 31 26 30 " +" 32 25 31 33 " +" 32 31 31 34 " +" 33 30 35 31 " +" 31 35 36 35 " +" 37 36 38 36 " +" 37 37 39 38 " +" " +" }" +" }" +" }" +" VertexArray TRUE {" +" osg::Vec3Array {" +" UniqueID 8 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 9 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 40 {" +" -4.51996 -5.9634 -60.7026 " +" 2e-06 -5.96339 -61.6017 " +" 4.51996 -5.96339 -60.7026 " +" -8.3518 -5.9634 -58.1422 " +" 8.3518 -5.96339 -58.1422 " +" -58.1422 -5.96341 -8.3518 " +" 58.1422 -5.96339 -8.3518 " +" 60.7026 -5.96339 -4.51996 " +" 61.6017 -5.96339 0 " +" 60.7026 -5.96339 4.51996 " +" 58.1423 -5.96339 8.3518 " +" -58.1422 -5.96341 8.3518 " +" -60.7026 -5.96341 -4.51996 " +" -61.6017 -5.96341 0 " +" -60.7026 -5.96341 4.51996 " +" 8.3518 -5.9634 58.1422 " +" -8.3518 -5.96341 58.1422 " +" 4.51997 -5.96341 60.7026 " +" -4.51996 -5.96341 60.7026 " +" 2e-06 -5.96341 61.6017 " +" -60.7026 5.96339 -4.51996 " +" -61.6017 5.96339 0 " +" -60.7026 5.96339 4.51996 " +" -58.1423 5.96339 -8.3518 " +" -58.1422 5.96339 8.3518 " +" -8.3518 5.9634 -58.1422 " +" -8.3518 5.96339 58.1422 " +" -4.51996 5.96339 60.7026 " +" -2e-06 5.96339 61.6017 " +" 4.51996 5.9634 60.7026 " +" 8.3518 5.9634 58.1422 " +" 8.3518 5.96341 -58.1422 " +" -4.51997 5.96341 -60.7026 " +" -2e-06 5.96341 -61.6017 " +" 4.51996 5.96341 -60.7026 " +" 58.1422 5.96341 8.3518 " +" 58.1422 5.96341 -8.3518 " +" 60.7026 5.96341 4.51996 " +" 60.7026 5.96341 -4.51996 " +" 61.6017 5.96341 0 " +" }" +" }" +" }" +" NormalArray TRUE {" +" osg::Vec3Array {" +" UniqueID 10 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 9 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 40 {" +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" }" +" }" +" }" +" TexCoordArrayList 1 {" +" osg::Vec2Array {" +" UniqueID 11 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 9 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 40 {" +" 0.37739 0.519384 " +" 0.384197 0.509197 " +" 0.394384 0.50239 " +" 0.375 0.531401 " +" 0.406401 0.5 " +" 0.375 0.718599 " +" 0.593599 0.5 " +" 0.605616 0.50239 " +" 0.615803 0.509197 " +" 0.62261 0.519384 " +" 0.625 0.531401 " +" 0.406401 0.75 " +" 0.37739 0.730616 " +" 0.384197 0.740803 " +" 0.394384 0.74761 " +" 0.625 0.718599 " +" 0.593599 0.75 " +" 0.62261 0.730616 " +" 0.605616 0.74761 " +" 0.615803 0.740803 " +" 0.37739 0.019384 " +" 0.384197 0.009197 " +" 0.394384 0.00239 " +" 0.375 0.031401 " +" 0.406401 0 " +" 0.375 0.218599 " +" 0.593599 0 " +" 0.605616 0.00239 " +" 0.615803 0.009197 " +" 0.62261 0.019384 " +" 0.625 0.031401 " +" 0.406401 0.25 " +" 0.37739 0.230616 " +" 0.384197 0.240803 " +" 0.394384 0.24761 " +" 0.625 0.218599 " +" 0.593599 0.25 " +" 0.62261 0.230616 " +" 0.605616 0.24761 " +" 0.615803 0.240803 " +" }" +" }" +" }" +" }" +" }" +" }" +" osg::Geode {" +" UniqueID 12 " +" Name \"Error\" " +" Drawables 1 {" +" osg::Geometry {" +" UniqueID 13 " +" DataVariance STATIC " +" StateSet TRUE {" +" osg::StateSet {" +" UniqueID 4 " +" }" +" }" +" PrimitiveSetList 1 {" +" osg::DrawElementsUShort {" +" UniqueID 14 " +" BufferObject TRUE {" +" osg::ElementBufferObject {" +" UniqueID 15 " +" Target 34963 " +" }" +" }" +" Mode TRIANGLES " +" vector 120 {" +" 0 1 2 0 " +" 3 1 3 4 " +" 1 3 5 4 " +" 4 5 6 4 " +" 6 7 8 7 " +" 6 8 6 9 " +" 10 8 9 10 " +" 9 11 12 13 " +" 11 12 11 14 " +" 15 12 14 15 " +" 14 16 16 17 " +" 15 16 18 17 " +" 18 19 17 18 " +" 20 19 20 21 " +" 19 20 22 21 " +" 22 23 24 22 " +" 25 23 25 26 " +" 23 25 27 26 " +" 28 26 27 28 " +" 29 26 30 29 " +" 28 30 28 31 " +" 32 30 31 32 " +" 31 33 34 35 " +" 33 34 33 36 " +" 37 34 36 37 " +" 36 38 39 37 " +" 38 39 38 40 " +" 40 41 39 40 " +" 42 41 42 43 " +" 41 42 0 43 " +" " +" }" +" }" +" }" +" VertexArray TRUE {" +" osg::Vec3Array {" +" UniqueID 16 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 17 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 44 {" +" 61.6017 -5.96339 0 " +" 60.7026 5.96341 -4.51996 " +" 61.6017 5.96341 0 " +" 60.7026 -5.96339 -4.51996 " +" 58.1422 5.96341 -8.3518 " +" 58.1422 -5.96339 -8.3518 " +" 8.3518 -5.96339 -58.1422 " +" 8.3518 5.96341 -58.1422 " +" 4.51996 5.96341 -60.7026 " +" 4.51996 -5.96339 -60.7026 " +" -2e-06 5.96341 -61.6017 " +" 2e-06 -5.96339 -61.6017 " +" -4.51997 5.96341 -60.7026 " +" -2e-06 5.96341 -61.6017 " +" -4.51996 -5.9634 -60.7026 " +" -8.3518 5.9634 -58.1422 " +" -8.3518 -5.9634 -58.1422 " +" -58.1423 5.96339 -8.3518 " +" -58.1422 -5.96341 -8.3518 " +" -60.7026 5.96339 -4.51996 " +" -60.7026 -5.96341 -4.51996 " +" -61.6017 5.96339 0 " +" -61.6017 -5.96341 0 " +" -60.7026 5.96339 4.51996 " +" -61.6017 5.96339 0 " +" -60.7026 -5.96341 4.51996 " +" -58.1422 5.96339 8.3518 " +" -58.1422 -5.96341 8.3518 " +" -8.3518 -5.96341 58.1422 " +" -8.3518 5.96339 58.1422 " +" -4.51996 5.96339 60.7026 " +" -4.51996 -5.96341 60.7026 " +" -2e-06 5.96339 61.6017 " +" 2e-06 -5.96341 61.6017 " +" 4.51996 5.9634 60.7026 " +" -2e-06 5.96339 61.6017 " +" 4.51997 -5.96341 60.7026 " +" 8.3518 5.9634 58.1422 " +" 8.3518 -5.9634 58.1422 " +" 58.1422 5.96341 8.3518 " +" 58.1423 -5.96339 8.3518 " +" 60.7026 5.96341 4.51996 " +" 60.7026 -5.96339 4.51996 " +" 61.6017 5.96341 0 " +" }" +" }" +" }" +" NormalArray TRUE {" +" osg::Vec3Array {" +" UniqueID 18 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 17 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 44 {" +" 1 0 0 " +" 0.923877 0 -0.38269 " +" 0.980784 0 -0.195097 " +" 0.923877 0 -0.38269 " +" 0.773003 0 -0.634402 " +" 0.773003 0 -0.634402 " +" 0.634402 0 -0.773003 " +" 0.634402 0 -0.773003 " +" 0.38269 0 -0.923877 " +" 0.38269 0 -0.923877 " +" 0.195097 0 -0.980784 " +" 0 0 -1 " +" -0.38269 -0 -0.923877 " +" -0.195097 -0 -0.980784 " +" -0.38269 -0 -0.923877 " +" -0.634402 -0 -0.773003 " +" -0.634402 -0 -0.773003 " +" -0.773003 -0 -0.634402 " +" -0.773003 -0 -0.634402 " +" -0.923877 -0 -0.38269 " +" -0.923877 -0 -0.38269 " +" -0.980784 -0 -0.195097 " +" -1 0 0 " +" -0.923877 0 0.38269 " +" -0.980784 0 0.195097 " +" -0.923877 0 0.38269 " +" -0.773003 0 0.634402 " +" -0.773003 0 0.634402 " +" -0.634402 0 0.773003 " +" -0.634402 0 0.773003 " +" -0.38269 0 0.923877 " +" -0.38269 0 0.923877 " +" -0.195097 0 0.980784 " +" 0 0 1 " +" 0.38269 0 0.923877 " +" 0.195097 0 0.980784 " +" 0.38269 0 0.923877 " +" 0.634402 0 0.773003 " +" 0.634402 0 0.773003 " +" 0.773003 0 0.634402 " +" 0.773003 0 0.634402 " +" 0.923877 0 0.38269 " +" 0.923877 0 0.38269 " +" 0.980784 0 0.195097 " +" }" +" }" +" }" +" TexCoordArrayList 1 {" +" osg::Vec2Array {" +" UniqueID 19 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 17 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 44 {" +" 0.625 0.5 " +" 0.605616 0.25 " +" 0.625 0.25 " +" 0.605616 0.5 " +" 0.593599 0.25 " +" 0.593599 0.5 " +" 0.406401 0.5 " +" 0.406401 0.25 " +" 0.394384 0.25 " +" 0.394384 0.5 " +" 0.375 0.25 " +" 0.375 0.5 " +" 0.125 0.519384 " +" 0.125 0.5 " +" 0.375 0.519384 " +" 0.125 0.531401 " +" 0.375 0.531401 " +" 0.125 0.718599 " +" 0.375 0.718599 " +" 0.125 0.730616 " +" 0.375 0.730616 " +" 0.125 0.75 " +" 0.375 0.75 " +" 0.394384 1 " +" 0.375 1 " +" 0.394384 0.75 " +" 0.406401 1 " +" 0.406401 0.75 " +" 0.593599 0.75 " +" 0.593599 1 " +" 0.605616 1 " +" 0.605616 0.75 " +" 0.625 1 " +" 0.625 0.75 " +" 0.875 0.730616 " +" 0.875 0.75 " +" 0.625 0.730616 " +" 0.875 0.718599 " +" 0.625 0.718599 " +" 0.875 0.531401 " +" 0.625 0.531401 " +" 0.875 0.519384 " +" 0.625 0.519384 " +" 0.875 0.5 " +" }" +" }" +" }" +" }" +" }" +" }" +" osg::Geode {" +" UniqueID 20 " +" Name \"Error\" " +" Drawables 1 {" +" osg::Geometry {" +" UniqueID 21 " +" DataVariance STATIC " +" StateSet TRUE {" +" osg::StateSet {" +" UniqueID 22 " +" DataVariance STATIC " +" AttributeList 1 {" +" osg::Material {" +" UniqueID 23 " +" Name \"ErrorLabel\" " +" Ambient TRUE Front 1 1 1 1 Back 1 1 1 1 " +" Diffuse TRUE Front 0.176208 0.176208 0.176208 1 Back 0.176208 0.176208 0.176208 1 " +" Specular TRUE Front 0.5 0.5 0.5 1 Back 0.5 0.5 0.5 1 " +" Emission TRUE Front 0.22026 0.22026 0.22026 1 Back 0.22026 0.22026 0.22026 1 " +" Shininess TRUE Front 28.8 Back 28.8 " +" }" +" Value OFF " +" }" +" }" +" }" +" PrimitiveSetList 1 {" +" osg::DrawElementsUShort {" +" UniqueID 24 " +" BufferObject TRUE {" +" osg::ElementBufferObject {" +" UniqueID 25 " +" Target 34963 " +" }" +" }" +" Mode TRIANGLES " +" vector 216 {" +" 0 1 2 3 " +" 0 2 2 4 " +" 3 4 5 3 " +" 5 6 7 5 " +" 8 3 8 5 " +" 7 7 9 8 " +" 10 3 8 8 " +" 11 10 12 13 " +" 10 14 12 10 " +" 15 14 10 10 " +" 11 15 16 15 " +" 11 11 17 16 " +" 18 16 17 17 " +" 19 18 20 21 " +" 22 23 20 22 " +" 22 24 23 25 " +" 23 24 24 26 " +" 25 26 27 28 " +" 28 29 30 26 " +" 28 30 25 26 " +" 31 31 26 30 " +" 32 25 31 33 " +" 32 31 31 34 " +" 33 30 35 31 " +" 31 35 36 35 " +" 37 36 38 36 " +" 37 37 39 38 " +" 40 41 42 43 " +" 40 42 42 44 " +" 43 45 43 44 " +" 44 46 45 47 " +" 45 46 48 47 " +" 46 46 49 48 " +" 44 50 46 51 " +" 46 50 50 52 " +" 53 54 50 53 " +" 53 55 54 50 " +" 54 51 54 56 " +" 51 56 57 51 " +" 58 51 57 57 " +" 59 58 60 61 " +" 62 63 60 62 " +" 62 64 63 65 " +" 63 64 64 66 " +" 65 66 67 68 " +" 69 70 65 71 " +" 69 65 72 71 " +" 65 66 68 73 " +" 65 66 73 68 " +" 74 73 73 72 " +" 65 73 75 72 " +" 72 75 76 75 " +" 77 76 78 76 " +" 77 77 79 78 " +" " +" }" +" }" +" }" +" VertexArray TRUE {" +" osg::Vec3Array {" +" UniqueID 26 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 27 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 80 {" +" -7.12646 -9.95049 -13.7349 " +" -6.35115 -9.95049 -14.8952 " +" -5.1908 -9.95049 -15.6705 " +" -7.39872 -9.95049 -12.3661 " +" -3.82208 -9.95049 -15.9428 " +" 3.82208 -9.95049 -15.9428 " +" 5.1908 -9.95049 -15.6705 " +" 6.35115 -9.95049 -14.8952 " +" 7.39872 -9.95049 -12.3661 " +" 7.12646 -9.95049 -13.7349 " +" -7.39872 -9.95049 33.4208 " +" 7.39872 -9.95049 33.4208 " +" -6.35115 -9.95049 35.9499 " +" -7.12647 -9.95049 34.7895 " +" -5.1908 -9.95049 36.7252 " +" -3.82208 -9.95049 36.9975 " +" 3.82208 -9.95049 36.9975 " +" 7.12646 -9.95049 34.7895 " +" 5.1908 -9.95049 36.7252 " +" 6.35115 -9.95049 35.9499 " +" -7.12646 -9.95042 -36.7346 " +" -6.35115 -9.95042 -37.8949 " +" -5.1908 -9.95042 -38.6702 " +" -7.39872 -9.95042 -35.3659 " +" -3.82208 -9.95042 -38.9425 " +" -7.39872 -9.95042 -27.7217 " +" 3.82208 -9.95042 -38.9425 " +" 5.1908 -9.95042 -38.6702 " +" 6.35115 -9.95042 -37.8949 " +" 7.12646 -9.95042 -36.7346 " +" 7.39872 -9.95042 -35.3659 " +" -3.82208 -9.95042 -24.1451 " +" -7.12647 -9.95042 -26.353 " +" -6.35115 -9.95042 -25.1926 " +" -5.1908 -9.95042 -24.4173 " +" 7.39872 -9.95042 -27.7217 " +" 3.82208 -9.95042 -24.1451 " +" 7.12646 -9.95042 -26.353 " +" 5.1908 -9.95042 -24.4173 " +" 6.35115 -9.95042 -25.1926 " +" -5.1908 9.95055 -15.6705 " +" -6.35115 9.95055 -14.8952 " +" -7.12646 9.95055 -13.7349 " +" -3.82208 9.95055 -15.9428 " +" -7.39872 9.95055 -12.3661 " +" 3.82208 9.95055 -15.9428 " +" 7.39872 9.95055 -12.3661 " +" 5.1908 9.95055 -15.6705 " +" 6.35115 9.95055 -14.8952 " +" 7.12646 9.95055 -13.7349 " +" -7.39872 9.95055 33.4208 " +" 7.39872 9.95055 33.4208 " +" -7.12646 9.95055 34.7895 " +" -6.35115 9.95056 35.9499 " +" -3.82208 9.95056 36.9975 " +" -5.1908 9.95056 36.7252 " +" 3.82208 9.95055 36.9975 " +" 5.19081 9.95055 36.7252 " +" 7.12646 9.95056 34.7895 " +" 6.35115 9.95055 35.9499 " +" -5.1908 9.95062 -38.6702 " +" -6.35115 9.95062 -37.8949 " +" -7.12646 9.95062 -36.7346 " +" -3.82208 9.95062 -38.9425 " +" -7.39872 9.95062 -35.3659 " +" 3.82208 9.95062 -38.9425 " +" -7.39872 9.95063 -27.7217 " +" -7.12646 9.95063 -26.353 " +" -6.35115 9.95063 -25.1926 " +" 6.35115 9.95062 -37.8949 " +" 5.1908 9.95062 -38.6702 " +" 7.12647 9.95062 -36.7346 " +" 7.39872 9.95062 -35.3659 " +" -3.82208 9.95063 -24.1451 " +" -5.1908 9.95063 -24.4173 " +" 3.82208 9.95063 -24.1451 " +" 7.39872 9.95062 -27.7217 " +" 5.1908 9.95063 -24.4173 " +" 7.12646 9.95063 -26.353 " +" 6.35115 9.95063 -25.1926 " +" }" +" }" +" }" +" NormalArray TRUE {" +" osg::Vec3Array {" +" UniqueID 28 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 27 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 80 {" +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" }" +" }" +" }" +" TexCoordArrayList 1 {" +" osg::Vec2Array {" +" UniqueID 29 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 27 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 80 {" +" 0.006133 0.041706 " +" 0.023598 0.006596 " +" 0.149209 0.001714 " +" 0 0.06756 " +" 0.241707 0 " +" 0.758294 0 " +" 0.850791 0.001714 " +" 0.976402 0.006596 " +" 1 0.06756 " +" 0.993867 0.041706 " +" 0 0.93244 " +" 1 0.93244 " +" 0.023598 0.993404 " +" 0.006133 0.958294 " +" 0.149209 0.998286 " +" 0.241706 1 " +" 0.758294 1 " +" 0.993867 0.958294 " +" 0.850791 0.998286 " +" 0.976402 0.993404 " +" 0.006133 0.149209 " +" 0.023598 0.023598 " +" 0.149209 0.006133 " +" 0 0.241707 " +" 0.241707 0 " +" 0 0.758294 " +" 0.758294 0 " +" 0.850791 0.006133 " +" 0.976402 0.023598 " +" 0.993867 0.149209 " +" 1 0.241707 " +" 0.241706 1 " +" 0.006133 0.850791 " +" 0.023598 0.976402 " +" 0.149209 0.993867 " +" 1 0.758293 " +" 0.758294 1 " +" 0.993867 0.850791 " +" 0.850791 0.993867 " +" 0.976402 0.976402 " +" 0.149209 0.001714 " +" 0.023598 0.006596 " +" 0.006133 0.041706 " +" 0.241706 0 " +" 0 0.06756 " +" 0.758294 0 " +" 1 0.06756 " +" 0.850791 0.001714 " +" 0.976402 0.006596 " +" 0.993867 0.041706 " +" 0 0.93244 " +" 1 0.93244 " +" 0.006133 0.958294 " +" 0.023598 0.993404 " +" 0.241707 1 " +" 0.149209 0.998286 " +" 0.758294 1 " +" 0.850791 0.998286 " +" 0.993867 0.958294 " +" 0.976402 0.993404 " +" 0.149209 0.006133 " +" 0.023598 0.023598 " +" 0.006133 0.149209 " +" 0.241706 0 " +" 0 0.241707 " +" 0.758294 0 " +" 0 0.758294 " +" 0.006133 0.850791 " +" 0.023598 0.976402 " +" 0.976402 0.023598 " +" 0.850791 0.006133 " +" 0.993867 0.149209 " +" 1 0.241706 " +" 0.241707 1 " +" 0.149209 0.993867 " +" 0.758294 1 " +" 1 0.758294 " +" 0.850791 0.993867 " +" 0.993867 0.850791 " +" 0.976402 0.976402 " +" }" +" }" +" }" +" }" +" }" +" }" +" osg::Geode {" +" UniqueID 30 " +" Name \"Error\" " +" Drawables 1 {" +" osg::Geometry {" +" UniqueID 31 " +" DataVariance STATIC " +" StateSet TRUE {" +" osg::StateSet {" +" UniqueID 22 " +" }" +" }" +" PrimitiveSetList 1 {" +" osg::DrawElementsUShort {" +" UniqueID 32 " +" BufferObject TRUE {" +" osg::ElementBufferObject {" +" UniqueID 33 " +" Target 34963 " +" }" +" }" +" Mode TRIANGLES " +" vector 240 {" +" 0 1 2 0 " +" 3 1 0 2 " +" 4 0 5 3 " +" 4 2 6 5 " +" 7 3 4 6 " +" 8 5 9 7 " +" 6 10 8 9 " +" 11 7 6 12 " +" 10 9 13 11 " +" 12 14 10 13 " +" 15 11 12 16 " +" 14 13 17 15 " +" 16 18 14 19 " +" 15 17 16 20 " +" 18 19 21 15 " +" 18 20 22 23 " +" 21 19 18 22 " +" 24 23 19 25 " +" 26 24 22 27 " +" 23 25 26 22 " +" 28 27 25 29 " +" 30 26 28 31 " +" 27 29 30 32 " +" 26 31 29 33 " +" 34 32 30 35 " +" 31 33 34 30 " +" 36 35 33 37 " +" 38 34 36 39 " +" 35 37 38 39 " +" 34 39 38 35 " +" 40 41 42 43 " +" 41 40 40 42 " +" 44 43 40 45 " +" 46 44 42 47 " +" 43 45 46 48 " +" 44 47 45 49 " +" 50 48 46 51 " +" 47 49 50 46 " +" 52 51 49 53 " +" 54 50 52 55 " +" 51 53 54 52 " +" 56 55 53 57 " +" 58 54 56 57 " +" 59 55 58 56 " +" 60 57 61 59 " +" 58 60 62 61 " +" 63 59 58 62 " +" 64 61 65 63 " +" 62 66 64 65 " +" 67 63 62 68 " +" 66 65 69 67 " +" 66 68 70 69 " +" 71 67 66 70 " +" 72 69 73 71 " +" 70 74 72 71 " +" 73 75 70 76 " +" 74 71 75 77 " +" 76 78 74 75 " +" 79 77 76 79 " +" 78 75 78 79 " +" " +" }" +" }" +" }" +" VertexArray TRUE {" +" osg::Vec3Array {" +" UniqueID 34 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 35 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 80 {" +" -7.39872 9.95055 -12.3661 " +" -7.39872 -9.95049 -12.3661 " +" -7.39872 -9.95049 33.4208 " +" -7.12646 -9.95049 -13.7349 " +" -7.39872 9.95055 33.4208 " +" -7.12646 9.95055 -13.7349 " +" -7.12647 -9.95049 34.7895 " +" -6.35115 -9.95049 -14.8952 " +" -7.12646 9.95055 34.7895 " +" -6.35115 9.95055 -14.8952 " +" -6.35115 9.95056 35.9499 " +" -5.1908 -9.95049 -15.6705 " +" -6.35115 -9.95049 35.9499 " +" -5.1908 9.95055 -15.6705 " +" -5.1908 9.95056 36.7252 " +" -3.82208 -9.95049 -15.9428 " +" -5.1908 -9.95049 36.7252 " +" -3.82208 9.95055 -15.9428 " +" -3.82208 9.95056 36.9975 " +" 3.82208 9.95055 -15.9428 " +" -3.82208 -9.95049 36.9975 " +" 3.82208 -9.95049 -15.9428 " +" 3.82208 -9.95049 36.9975 " +" 5.1908 -9.95049 -15.6705 " +" 3.82208 9.95055 36.9975 " +" 5.1908 9.95055 -15.6705 " +" 5.19081 9.95055 36.7252 " +" 6.35115 -9.95049 -14.8952 " +" 5.1908 -9.95049 36.7252 " +" 6.35115 9.95055 -14.8952 " +" 6.35115 -9.95049 35.9499 " +" 7.12646 -9.95049 -13.7349 " +" 6.35115 9.95055 35.9499 " +" 7.12646 9.95055 -13.7349 " +" 7.12646 9.95056 34.7895 " +" 7.39872 -9.95049 -12.3661 " +" 7.12646 -9.95049 34.7895 " +" 7.39872 9.95055 -12.3661 " +" 7.39872 -9.95049 33.4208 " +" 7.39872 9.95055 33.4208 " +" -3.82208 9.95063 -24.1451 " +" -3.82208 -9.95042 -24.1451 " +" 3.82208 -9.95042 -24.1451 " +" -5.1908 -9.95042 -24.4173 " +" 3.82208 9.95063 -24.1451 " +" -5.1908 9.95063 -24.4173 " +" 5.1908 -9.95042 -24.4173 " +" -6.35115 -9.95042 -25.1926 " +" 5.1908 9.95063 -24.4173 " +" -6.35115 9.95063 -25.1926 " +" 6.35115 9.95063 -25.1926 " +" -7.12647 -9.95042 -26.353 " +" 6.35115 -9.95042 -25.1926 " +" -7.12646 9.95063 -26.353 " +" 7.12646 9.95063 -26.353 " +" -7.39872 -9.95042 -27.7217 " +" 7.12646 -9.95042 -26.353 " +" -7.39872 9.95063 -27.7217 " +" 7.39872 9.95062 -27.7217 " +" -7.39872 -9.95042 -35.3659 " +" 7.39872 -9.95042 -27.7217 " +" -7.39872 9.95062 -35.3659 " +" 7.39872 -9.95042 -35.3659 " +" -7.12646 -9.95042 -36.7346 " +" 7.39872 9.95062 -35.3659 " +" -7.12646 9.95062 -36.7346 " +" 7.12647 9.95062 -36.7346 " +" -6.35115 -9.95042 -37.8949 " +" 7.12646 -9.95042 -36.7346 " +" -6.35115 9.95062 -37.8949 " +" 6.35115 -9.95042 -37.8949 " +" -5.1908 -9.95042 -38.6702 " +" 6.35115 9.95062 -37.8949 " +" -5.1908 9.95062 -38.6702 " +" 5.1908 9.95062 -38.6702 " +" -3.82208 9.95062 -38.9425 " +" 5.1908 -9.95042 -38.6702 " +" -3.82208 -9.95042 -38.9425 " +" 3.82208 9.95062 -38.9425 " +" 3.82208 -9.95042 -38.9425 " +" }" +" }" +" }" +" NormalArray TRUE {" +" osg::Vec3Array {" +" UniqueID 36 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 35 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 80 {" +" -0.995187 -0 -0.0979987 " +" -0.995187 -0 -0.0979987 " +" -0.995187 0 0.0979987 " +" -0.923877 -0 -0.38269 " +" -0.995187 0 0.0979987 " +" -0.923877 -0 -0.38269 " +" -0.923877 0 0.38269 " +" -0.707107 -0 -0.707107 " +" -0.923877 0 0.38269 " +" -0.707107 -0 -0.707107 " +" -0.707107 0 0.707107 " +" -0.38269 -0 -0.923877 " +" -0.707107 0 0.707107 " +" -0.38269 -0 -0.923877 " +" -0.38269 0 0.923877 " +" -0.0979987 -0 -0.995187 " +" -0.38269 0 0.923877 " +" -0.0979987 -0 -0.995187 " +" -0.0979987 0 0.995187 " +" 0.0979987 0 -0.995187 " +" -0.0979987 0 0.995187 " +" 0.0979987 0 -0.995187 " +" 0.0979987 0 0.995187 " +" 0.38269 0 -0.923877 " +" 0.0979987 0 0.995187 " +" 0.38269 0 -0.923877 " +" 0.38269 0 0.923877 " +" 0.707107 0 -0.707107 " +" 0.38269 0 0.923877 " +" 0.707107 0 -0.707107 " +" 0.707107 0 0.707107 " +" 0.923877 0 -0.38269 " +" 0.707107 0 0.707107 " +" 0.923877 0 -0.38269 " +" 0.923877 0 0.38269 " +" 0.995187 0 -0.0979987 " +" 0.923877 0 0.38269 " +" 0.995187 0 -0.0979987 " +" 0.995187 0 0.0979987 " +" 0.995187 0 0.0979987 " +" -0.0979987 0 0.995187 " +" -0.0979987 0 0.995187 " +" 0.0979987 0 0.995187 " +" -0.38269 0 0.923877 " +" 0.0979987 0 0.995187 " +" -0.38269 0 0.923877 " +" 0.38269 0 0.923877 " +" -0.707107 0 0.707107 " +" 0.38269 0 0.923877 " +" -0.707107 0 0.707107 " +" 0.707107 0 0.707107 " +" -0.923877 0 0.38269 " +" 0.707107 0 0.707107 " +" -0.923877 0 0.38269 " +" 0.923877 0 0.38269 " +" -0.995187 0 0.0979987 " +" 0.923877 0 0.38269 " +" -0.995187 0 0.0979987 " +" 0.995187 0 0.0979987 " +" -0.995187 -0 -0.0979987 " +" 0.995187 0 0.0979987 " +" -0.995187 -0 -0.0979987 " +" 0.995187 0 -0.0979987 " +" -0.923877 -0 -0.38269 " +" 0.995187 0 -0.0979987 " +" -0.923877 -0 -0.38269 " +" 0.923877 0 -0.38269 " +" -0.707107 -0 -0.707107 " +" 0.923877 0 -0.38269 " +" -0.707107 -0 -0.707107 " +" 0.707107 0 -0.707107 " +" -0.38269 -0 -0.923877 " +" 0.707107 0 -0.707107 " +" -0.38269 -0 -0.923877 " +" 0.38269 0 -0.923877 " +" -0.0979987 -0 -0.995187 " +" 0.38269 0 -0.923877 " +" -0.0979987 -0 -0.995187 " +" 0.0979987 0 -0.995187 " +" 0.0979987 0 -0.995187 " +" }" +" }" +" }" +" TexCoordArrayList 1 {" +" osg::Vec2Array {" +" UniqueID 37 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 35 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 80 {" +" 0 0.06756 " +" 0 0.06756 " +" 0 0.93244 " +" 0.006133 0.041706 " +" 0 0.93244 " +" 0.006133 0.041706 " +" 0.006133 0.958294 " +" 0.023598 0.006596 " +" 0.006133 0.958294 " +" 0.023598 0.006596 " +" 0.023598 0.993404 " +" 0.149209 0.001714 " +" 0.023598 0.993404 " +" 0.149209 0.001714 " +" 0.149209 0.998286 " +" 0.241706 0 " +" 0.149209 0.998286 " +" 0.241707 0 " +" 0.241706 1 " +" 0.758294 0 " +" 0.241707 1 " +" 0.758294 0 " +" 0.758294 1 " +" 0.850791 0.001714 " +" 0.758294 1 " +" 0.850791 0.001714 " +" 0.850791 0.998286 " +" 0.976402 0.006596 " +" 0.850791 0.998286 " +" 0.976402 0.006596 " +" 0.976402 0.993404 " +" 0.993867 0.041706 " +" 0.976402 0.993404 " +" 0.993867 0.041706 " +" 0.993867 0.958294 " +" 1 0.06756 " +" 0.993867 0.958294 " +" 1 0.06756 " +" 1 0.93244 " +" 1 0.93244 " +" 0.241706 1 " +" 0.241707 1 " +" 0.758294 1 " +" 0.149209 0.993867 " +" 0.758294 1 " +" 0.149209 0.993867 " +" 0.850791 0.993867 " +" 0.023598 0.976402 " +" 0.850791 0.993867 " +" 0.023598 0.976402 " +" 0.976402 0.976402 " +" 0.006133 0.850791 " +" 0.976402 0.976402 " +" 0.006133 0.850791 " +" 0.993867 0.850791 " +" 0 0.758293 " +" 0.993867 0.850791 " +" 0 0.758294 " +" 1 0.758294 " +" 0 0.241707 " +" 1 0.758294 " +" 0 0.241706 " +" 1 0.241707 " +" 0.006133 0.149209 " +" 1 0.241707 " +" 0.006133 0.149209 " +" 0.993867 0.149209 " +" 0.023598 0.023598 " +" 0.993867 0.149209 " +" 0.023598 0.023598 " +" 0.976402 0.023598 " +" 0.149209 0.006133 " +" 0.976402 0.023598 " +" 0.149209 0.006133 " +" 0.850791 0.006133 " +" 0.241707 0 " +" 0.850791 0.006133 " +" 0.241706 0 " +" 0.758294 0 " +" 0.758294 0 " +" }" +" }" +" }" +" }" +" }" +" }" +" osg::Geode {" +" UniqueID 38 " +" Name \"Cube\" " +" Drawables 1 {" +" osg::Geometry {" +" UniqueID 39 " +" DataVariance STATIC " +" StateSet TRUE {" +" osg::StateSet {" +" UniqueID 40 " +" DataVariance STATIC " +" AttributeList 1 {" +" osg::Material {" +" UniqueID 41 " +" Name \"Material\" " +" Ambient TRUE Front 1 1 1 1 Back 1 1 1 1 " +" Diffuse TRUE Front 0.8 0.8 0.8 1 Back 0.8 0.8 0.8 1 " +" Specular TRUE Front 0.5 0.5 0.5 1 Back 0.5 0.5 0.5 1 " +" Emission TRUE Front 0 0 0 1 Back 0 0 0 1 " +" Shininess TRUE Front 41.344 Back 41.344 " +" }" +" Value OFF " +" }" +" }" +" }" +" PrimitiveSetList 1 {" +" osg::DrawElementsUShort {" +" UniqueID 42 " +" BufferObject TRUE {" +" osg::ElementBufferObject {" +" UniqueID 43 " +" Target 34963 " +" }" +" }" +" Mode TRIANGLES " +" vector 36 {" +" 0 1 2 0 " +" 2 3 4 5 " +" 6 4 6 7 " +" 8 9 10 8 " +" 10 11 12 13 " +" 14 12 14 15 " +" 16 17 18 16 " +" 18 19 20 21 " +" 22 20 22 23 " +" " +" }" +" }" +" }" +" VertexArray TRUE {" +" osg::Vec3Array {" +" UniqueID 44 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 45 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 24 {" +" 1 1 1 " +" -1 1 1 " +" -1 -1 1 " +" 1 -1 1 " +" 1 -1 -1 " +" 1 -1 1 " +" -1 -1 1 " +" -1 -1 -1 " +" -1 -1 -1 " +" -1 -1 1 " +" -1 1 1 " +" -1 1 -1 " +" -1 1 -1 " +" 1 1 -1 " +" 1 -1 -1 " +" -1 -1 -1 " +" 1 1 -1 " +" 1 1 1 " +" 1 -1 1 " +" 1 -1 -1 " +" -1 1 -1 " +" -1 1 1 " +" 1 1 1 " +" 1 1 -1 " +" }" +" }" +" }" +" NormalArray TRUE {" +" osg::Vec3Array {" +" UniqueID 46 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 45 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 24 {" +" 0 0 1 " +" 0 0 1 " +" 0 0 1 " +" 0 0 1 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" 0 -1 0 " +" -1 0 0 " +" -1 0 0 " +" -1 0 0 " +" -1 0 0 " +" 0 0 -1 " +" 0 0 -1 " +" 0 0 -1 " +" 0 0 -1 " +" 1 0 0 " +" 1 0 0 " +" 1 0 0 " +" 1 0 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" 0 1 0 " +" }" +" }" +" }" +" TexCoordArrayList 1 {" +" osg::Vec2Array {" +" UniqueID 47 " +" BufferObject TRUE {" +" osg::VertexBufferObject {" +" UniqueID 45 " +" }" +" }" +" Binding BIND_PER_VERTEX " +" vector 24 {" +" 0.625 0.5 " +" 0.875 0.5 " +" 0.875 0.75 " +" 0.625 0.75 " +" 0.375 0.75 " +" 0.625 0.75 " +" 0.625 1 " +" 0.375 1 " +" 0.375 0 " +" 0.625 0 " +" 0.625 0.25 " +" 0.375 0.25 " +" 0.125 0.5 " +" 0.375 0.5 " +" 0.375 0.75 " +" 0.125 0.75 " +" 0.375 0.5 " +" 0.625 0.5 " +" 0.625 0.75 " +" 0.375 0.75 " +" 0.375 0.25 " +" 0.625 0.25 " +" 0.625 0.5 " +" 0.375 0.5 " +" }" +" }" +" }" +" }" +" }" +" }" +" }" +"}"; + +} diff --git a/components/misc/errorMarker.hpp b/components/misc/errorMarker.hpp new file mode 100644 index 0000000000..05e1fa9557 --- /dev/null +++ b/components/misc/errorMarker.hpp @@ -0,0 +1,11 @@ +#ifndef OPENMW_COMPONENTS_MISC_ERRORMARKER_H +#define OPENMW_COMPONENTS_MISC_ERRORMARKER_H + +#include + +namespace Misc +{ + extern const std::string errorMarker; +} + +#endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 23581a09b8..f39a5919b6 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,7 @@ #include #include +#include #include "imagemanager.hpp" #include "niffilemanager.hpp" @@ -483,13 +485,11 @@ namespace Resource Resource::ImageManager* mImageManager; }; - osg::ref_ptr load (const std::string& normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) + namespace { - auto ext = Misc::getFileExtension(normalizedFilename); - if (ext == "nif") - return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); - else + osg::ref_ptr loadNonNif(const std::string& normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) { + auto ext = Misc::getFileExtension(normalizedFilename); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { @@ -505,10 +505,9 @@ namespace Resource options->setReadFileCallback(new ImageReadCallback(imageManager)); if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); - Files::IStreamPtr stream = vfs->get(normalizedFilename); - const std::uint64_t fileHash = Files::getHash(normalizedFilename, *stream); + const std::uint64_t fileHash = Files::getHash(normalizedFilename, model); - osgDB::ReaderWriter::ReadResult result = reader->readNode(*stream, options); + osgDB::ReaderWriter::ReadResult result = reader->readNode(model, options); if (!result.success()) { std::stringstream errormsg; @@ -519,7 +518,9 @@ namespace Resource // Recognize and hide collision node unsigned int hiddenNodeMask = 0; SceneUtil::FindByNameVisitor nameFinder("Collision"); - result.getNode()->accept(nameFinder); + + auto node = result.getNode(); + node->accept(nameFinder); if (nameFinder.mFoundNode) nameFinder.mFoundNode->setNodeMask(hiddenNodeMask); @@ -527,16 +528,14 @@ namespace Resource { // Collada alpha testing Resource::ColladaAlphaTrickVisitor colladaAlphaTrickVisitor; - result.getNode()->accept(colladaAlphaTrickVisitor); + node->accept(colladaAlphaTrickVisitor); - result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); - result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); - result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); - result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); + node->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); + node->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); + node->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); + node->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); } - auto node = result.getNode(); - node->setUserValue(Misc::OsgUserValues::sFileHash, std::string(reinterpret_cast(&fileHash), sizeof(fileHash))); @@ -544,6 +543,15 @@ namespace Resource } } + osg::ref_ptr load (const std::string& normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) + { + auto ext = Misc::getFileExtension(normalizedFilename); + if (ext == "nif") + return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); + else + return loadNonNif(normalizedFilename, *vfs->get(normalizedFilename), imageManager); + } + class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback { public: @@ -659,23 +667,23 @@ namespace Resource { loaded = load(normalized, mVFS, mImageManager, mNifFileManager); } - catch (std::exception& e) + catch (const std::exception& e) { - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }; + static osg::ref_ptr errorMarkerNode = [&] { + static const char* const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }; - for (unsigned int i=0; iexists(normalized)) + for (unsigned int i=0; iexists(normalized)) + return load(normalized, mVFS, mImageManager, mNifFileManager); } - } + Files::IMemStream file(Misc::errorMarker.data(), Misc::errorMarker.size()); + return loadNonNif("error_marker.osgt", file, mImageManager); + }(); - if (!loaded) - throw; + Log(Debug::Error) << "Failed to load '" << name << "': " << e.what() << ", using marker_error instead"; + loaded = static_cast(errorMarkerNode->clone(osg::CopyOp::DEEP_COPY_ALL)); } // set filtering settings From 8a7b4649f016a20706e2466f19d9d4be0b1dc7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 13 Nov 2021 18:03:37 +0100 Subject: [PATCH 1745/2859] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb86227f6c..6d6dacf106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,7 @@ Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering + Feature #6248: Embedded error marker mesh Feature #6249: Alpha testing support for Collada Feature #6251: OpenMW-CS: Set instance movement based on camera zoom Feature #6288: Preserve the "blocked" record flag for referenceable objects. From eb75e394b34de8f78d38c8d49c7a5eadf2894739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 20 Nov 2021 11:11:12 +0100 Subject: [PATCH 1746/2859] Use Files::MemBuf for Bsa::MemoryInputStream base classe instead of making a duplicate (MemoryInputStreamBuf) --- components/CMakeLists.txt | 2 +- components/bsa/memorystream.cpp | 48 --------------------------------- components/bsa/memorystream.hpp | 28 +++++++++---------- 3 files changed, 13 insertions(+), 65 deletions(-) delete mode 100644 components/bsa/memorystream.cpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 012677fc0d..abcec188f4 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -37,7 +37,7 @@ add_component_dir (settings ) add_component_dir (bsa - bsa_file compressedbsafile memorystream + bsa_file compressedbsafile ) add_component_dir (vfs diff --git a/components/bsa/memorystream.cpp b/components/bsa/memorystream.cpp deleted file mode 100644 index 34e98e6b68..0000000000 --- a/components/bsa/memorystream.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (memorystream.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - Compressed BSA upgrade added by Azdul 2019 - - */ -#include "memorystream.hpp" - - -namespace Bsa -{ -MemoryInputStreamBuf::MemoryInputStreamBuf(size_t bufferSize) : mBufferPtr(bufferSize) -{ - this->setg(mBufferPtr.data(), mBufferPtr.data(), mBufferPtr.data() + bufferSize); -} - -char* MemoryInputStreamBuf::getRawData() { - return mBufferPtr.data(); -} - -MemoryInputStream::MemoryInputStream(size_t bufferSize) : - MemoryInputStreamBuf(bufferSize), - std::istream(static_cast(this)) { - -} - -char* MemoryInputStream::getRawData() { - return MemoryInputStreamBuf::getRawData(); -} -} diff --git a/components/bsa/memorystream.hpp b/components/bsa/memorystream.hpp index d168e93d65..5aae448299 100644 --- a/components/bsa/memorystream.hpp +++ b/components/bsa/memorystream.hpp @@ -28,22 +28,10 @@ #include #include +#include namespace Bsa { -/** -Class used internally by MemoryInputStream. -*/ -class MemoryInputStreamBuf : public std::streambuf { - -public: - explicit MemoryInputStreamBuf(size_t bufferSize); - virtual char* getRawData(); -private: - //correct call to delete [] on C++ 11 - std::vector mBufferPtr; -}; - /** Class replaces Ogre memory streams without introducing any new external dependencies beyond standard library. @@ -52,10 +40,18 @@ private: Memory buffer is freed once the class instance is destroyed. */ -class MemoryInputStream : virtual MemoryInputStreamBuf, std::istream { +class MemoryInputStream : private std::vector, public virtual Files::MemBuf, public std::istream { public: - explicit MemoryInputStream(size_t bufferSize); - char* getRawData() override; + explicit MemoryInputStream(size_t bufferSize) + : std::vector(bufferSize) + , Files::MemBuf(this->data(), this->size()) + , std::istream(static_cast(this)) + {} + + char* getRawData() + { + return this->data(); + } }; } From 85c8c91bcc35df0e9a3b33ce1e980b3d087446ca Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sat, 20 Nov 2021 12:55:36 +0100 Subject: [PATCH 1747/2859] Get rid of problematic / in branch name on OSX Taken from https://devhints.io/bash Should fix #6424 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9f985856ec..5ec31d349d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -176,7 +176,7 @@ Debian_Clang_tests_Debug: - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.osx.sh - cd build; make -j $(sysctl -n hw.logicalcpu) package - - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done + - for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME##*/}_${CI_JOB_ID}.dmg"; done - ccache -s artifacts: paths: From 10b3ca8194295f5d5038fc658ee3b231e7171d8d Mon Sep 17 00:00:00 2001 From: psi29a Date: Sat, 20 Nov 2021 18:12:29 +0000 Subject: [PATCH 1748/2859] small fixup --- cmake/FindGMock.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake index 0e98d4e6c0..6b3caf543e 100644 --- a/cmake/FindGMock.cmake +++ b/cmake/FindGMock.cmake @@ -190,7 +190,7 @@ if(EXISTS "${GMOCK_MAIN_LIBRARY}") endif() if(EXISTS "${GMOCK_MAIN_LIBRARY_DEBUG}") set_target_properties(GMock::Main PROPERTIES - IMPORTED_LOCATION "{GMOCK_MAIN_LIBRARY_DEBUG}) + IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY_DEBUG}") endif() if(GMOCK_FOUND) From 5f195b757690cb5bb5658ddb3961d7f39983d6f6 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 21 Nov 2021 01:48:28 +0100 Subject: [PATCH 1749/2859] Make arguments passed to LuaManager::synchronizedUpdate consistent with arguments passed to LuaManager::update. Fixes #6431. --- apps/openmw/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 70d5a26c7a..f43406f705 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -295,7 +295,7 @@ bool OMW::Engine::frame(float frametime) // Should be called after input manager update and before any change to the game world. // It applies to the game world queued changes from the previous frame. - mLuaManager->synchronizedUpdate(paused, frametime); + mLuaManager->synchronizedUpdate(mEnvironment.getWindowManager()->isGuiMode(), frametime); // update game state { From d85f772269241cb2f6fe95b74734ff675c007c82 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Sun, 21 Nov 2021 02:25:05 +0000 Subject: [PATCH 1750/2859] Depth refactor --- apps/openmw/engine.cpp | 17 +++ apps/openmw/mwgui/bookpage.cpp | 4 +- apps/openmw/mwrender/bulletdebugdraw.cpp | 4 +- apps/openmw/mwrender/characterpreview.cpp | 51 ++------- apps/openmw/mwrender/globalmap.cpp | 7 +- apps/openmw/mwrender/localmap.cpp | 12 +-- apps/openmw/mwrender/npcanimation.cpp | 3 +- apps/openmw/mwrender/postprocessor.cpp | 6 +- apps/openmw/mwrender/renderingmanager.cpp | 14 +-- apps/openmw/mwrender/ripplesimulation.cpp | 8 +- apps/openmw/mwrender/screenshotmanager.cpp | 4 +- apps/openmw/mwrender/sky.cpp | 4 +- apps/openmw/mwrender/skyutil.cpp | 6 +- apps/openmw/mwrender/water.cpp | 6 +- components/CMakeLists.txt | 2 +- components/nifosg/nifloader.cpp | 3 +- components/resource/scenemanager.cpp | 8 +- components/sceneutil/depth.cpp | 63 +++++++++++ components/sceneutil/depth.hpp | 117 +++++++++++++++++++++ components/sceneutil/mwshadowtechnique.cpp | 9 +- components/sceneutil/optimizer.cpp | 6 +- components/sceneutil/rtt.cpp | 2 + components/sceneutil/util.cpp | 98 ----------------- components/sceneutil/util.hpp | 26 ----- components/sceneutil/waterutil.cpp | 4 +- components/shader/shadervisitor.cpp | 4 +- components/terrain/material.cpp | 6 +- 27 files changed, 262 insertions(+), 232 deletions(-) create mode 100644 components/sceneutil/depth.cpp create mode 100644 components/sceneutil/depth.hpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 70d5a26c7a..0d97ebee4e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -42,6 +42,7 @@ #include #include +#include #include "mwinput/inputmanagerimp.hpp" @@ -761,6 +762,22 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) osg::ref_ptr exts = osg::GLExtensions::Get(0, false); bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); + bool enableReverseZ = false; + + if (Settings::Manager::getBool("reverse z", "Camera")) + { + if (exts && exts->isClipControlSupported) + { + enableReverseZ = true; + Log(Debug::Info) << "Using reverse-z depth buffer"; + } + else + Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer"; + } + else + Log(Debug::Info) << "Using standard depth buffer"; + + SceneUtil::AutoDepth::setReversed(enableReverseZ); #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 874d1d866b..b2d2c39fd6 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -8,7 +8,7 @@ #include "MyGUI_FactoryManager.h" #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -1221,7 +1221,7 @@ public: RenderXform renderXform (mCroppedParent, textFormat.mRenderItem->getRenderTarget()->getInfo()); - float z = SceneUtil::getReverseZ() ? 1.f : -1.f; + float z = SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f; GlyphStream glyphStream(textFormat.mFont, static_cast(mCoord.left), static_cast(mCoord.top - mViewTop), z /*mNode->getNodeDepth()*/, vertices, renderXform); diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index 9155132871..b169251465 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include @@ -66,7 +66,7 @@ void DebugDrawer::createGeometry() auto* stateSet = new osg::StateSet; stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); - stateSet->setAttributeAndModes(new osg::PolygonOffset(SceneUtil::getReverseZ() ? 1.0 : -1.0, SceneUtil::getReverseZ() ? 1.0 : -1.0)); + stateSet->setAttributeAndModes(new osg::PolygonOffset(SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0, SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0)); osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); stateSet->setAttribute(material); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index c717806e35..7cca787580 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" @@ -132,43 +133,6 @@ namespace MWRender newStateSet->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); newStateSet->setDefine("FORCE_OPAQUE", "0", osg::StateAttribute::ON); } - if (SceneUtil::getReverseZ() && stateset->getAttribute(osg::StateAttribute::DEPTH)) - { - bool depthModified = false; - osg::Depth* depth = static_cast(stateset->getAttribute(osg::StateAttribute::DEPTH)); - depth->getUserValue("depthModified", depthModified); - - if (!depthModified) - { - if (!newStateSet) - { - newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY); - node.setStateSet(newStateSet); - } - // Setup standard depth ranges - osg::ref_ptr newDepth = new osg::Depth(*depth); - - switch (newDepth->getFunction()) - { - case osg::Depth::LESS: - newDepth->setFunction(osg::Depth::GREATER); - break; - case osg::Depth::LEQUAL: - newDepth->setFunction(osg::Depth::GEQUAL); - break; - case osg::Depth::GREATER: - newDepth->setFunction(osg::Depth::LESS); - break; - case osg::Depth::GEQUAL: - newDepth->setFunction(osg::Depth::LEQUAL); - break; - default: - break; - } - newStateSet->setAttribute(newDepth, osg::StateAttribute::ON); - newDepth->setUserValue("depthModified", true); - } - } } traverse(node); } @@ -200,8 +164,6 @@ namespace MWRender mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); mCamera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); mCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - const float fovYDegrees = 12.3f; - mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed mCamera->setViewport(0, 0, sizeX, sizeY); mCamera->setRenderOrder(osg::Camera::PRE_RENDER); mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); @@ -211,6 +173,8 @@ namespace MWRender mCamera->setNodeMask(Mask_RenderToTexture); + SceneUtil::setCameraClearDepth(mCamera); + bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP; osg::ref_ptr lightManager = new SceneUtil::LightManager(ffp); @@ -226,9 +190,14 @@ namespace MWRender defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(mCamera->getProjectionMatrix()))); - stateset->setAttributeAndModes(new osg::Depth, osg::StateAttribute::ON); + const float fovYDegrees = 12.3f; + const float aspectRatio = static_cast(sizeX) / static_cast(sizeY); + const float znear = 0.1f; + const float zfar = 10000.f; + mCamera->setProjectionMatrixAsPerspective(fovYDegrees, aspectRatio, znear, zfar); + osg::Matrixf projectionMatrix = SceneUtil::AutoDepth::isReversed() ? static_cast(SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, aspectRatio, znear, zfar)) : static_cast(mCamera->getProjectionMatrix()); + stateset->addUniform(new osg::Uniform("projectionMatrix", projectionMatrix)); SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index b248efad4e..ba8749d81c 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -15,8 +14,8 @@ #include #include -#include #include +#include #include @@ -326,8 +325,8 @@ namespace MWRender if (texture) { osg::ref_ptr geom = createTexturedQuad(srcLeft, srcTop, srcRight, srcBottom); - auto depth = SceneUtil::createDepth(); - depth->setWriteMask(0); + osg::ref_ptr depth = new SceneUtil::AutoDepth; + depth->setWriteMask(false); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setAttribute(depth); stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 66c8c55c60..d9982d35c3 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include @@ -178,7 +178,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f { osg::ref_ptr camera (new osg::Camera); - if (SceneUtil::getReverseZ()) + if (SceneUtil::AutoDepth::isReversed()) camera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10)); else camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); @@ -198,14 +198,10 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING); camera->setCullingMode(cullingMode); - osg::ref_ptr stateset = new osg::StateSet; - stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); - - if (SceneUtil::getReverseZ()) - stateset->setAttributeAndModes(SceneUtil::createDepth(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - SceneUtil::setCameraClearDepth(camera); + osg::ref_ptr stateset = new osg::StateSet; + stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(camera->getProjectionMatrix())), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); // assign large value to effectively turn off fog diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 7031b5a02b..da361aec25 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -310,7 +311,7 @@ class DepthClearCallback : public osgUtil::RenderBin::DrawCallback public: DepthClearCallback() { - mDepth = SceneUtil::createDepth(); + mDepth = new SceneUtil::AutoDepth; mDepth->setWriteMask(true); mStateSet = new osg::StateSet; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 7405098a03..b08cfe1f71 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include @@ -140,7 +140,7 @@ namespace MWRender { bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); - if (!SceneUtil::getReverseZ() && !softParticles) + if (!SceneUtil::AutoDepth::isReversed() && !softParticles) return; osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); @@ -161,7 +161,7 @@ namespace MWRender return; } - if (SceneUtil::getReverseZ()) + if (SceneUtil::AutoDepth::isReversed()) { if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) mDepthFormat = GL_DEPTH_COMPONENT32F; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b3b0d880c5..30be74c839 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -32,7 +32,7 @@ #include -#include +#include #include #include #include @@ -310,13 +310,7 @@ namespace MWRender , mFieldOfView(std::clamp(Settings::Manager::getFloat("field of view", "Camera"), 1.f, 179.f)) , mFirstPersonFieldOfView(std::clamp(Settings::Manager::getFloat("first person field of view", "Camera"), 1.f, 179.f)) { - bool reverseZ = SceneUtil::getReverseZ(); - - if (reverseZ) - Log(Debug::Info) << "Using reverse-z depth buffer"; - else - Log(Debug::Info) << "Using standard depth buffer"; - + bool reverseZ = SceneUtil::AutoDepth::isReversed(); auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); @@ -538,7 +532,7 @@ namespace MWRender if (reverseZ) { osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::ZERO_TO_ONE); - mRootNode->getOrCreateStateSet()->setAttributeAndModes(SceneUtil::createDepth(), osg::StateAttribute::ON); + mRootNode->getOrCreateStateSet()->setAttributeAndModes(new SceneUtil::AutoDepth, osg::StateAttribute::ON); mRootNode->getOrCreateStateSet()->setAttributeAndModes(clipcontrol, osg::StateAttribute::ON); } @@ -1150,7 +1144,7 @@ namespace MWRender mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); - if (SceneUtil::getReverseZ()) + if (SceneUtil::AutoDepth::isReversed()) { mSharedUniformStateUpdater->setLinearFac(-mNearClip / (mViewDistance - mNearClip) - 1.f); mSharedUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index fdfc3db63d..282362ea56 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include "vismask.hpp" @@ -56,13 +56,13 @@ namespace stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); - auto depth = SceneUtil::createDepth(); + osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); osg::ref_ptr polygonOffset (new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::getReverseZ() ? 1 : -1); - polygonOffset->setFactor(SceneUtil::getReverseZ() ? 1 : -1); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1 : -1); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 1 : -1); stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index ab7d0d93f0..1973ca4025 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include @@ -331,7 +331,7 @@ namespace MWRender float nearClip = Settings::Manager::getFloat("near clip", "Camera"); float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); // each cubemap side sees 90 degrees - if (SceneUtil::getReverseZ()) + if (SceneUtil::AutoDepth::isReversed()) rttCamera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspectiveInf(90.0, w/float(h), nearClip)); else rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index b6b0a3669b..fcb7447cf3 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include @@ -338,7 +338,7 @@ namespace MWRender mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(program, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } - auto depth = SceneUtil::createDepth(); + osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index 0e2955333f..b98e489738 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -25,7 +25,7 @@ #include #include -#include +#include #include @@ -811,11 +811,11 @@ namespace MWRender osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { - auto depth = SceneUtil::createDepth(); + osg::ref_ptr depth = new SceneUtil::AutoDepth; // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. - double far = SceneUtil::getReverseZ() ? 0.0 : 1.0; + double far = SceneUtil::AutoDepth::isReversed() ? 0.0 : 1.0; depth->setZNear(far); depth->setZFar(far); depth->setWriteMask(false); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index ca42423efe..fd5cbe0f79 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -27,7 +27,7 @@ #include #include -#include +#include #include #include @@ -268,7 +268,6 @@ public: void setDefaults(osg::Camera* camera) override { - SceneUtil::setCameraClearDepth(camera); camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("RefractionCamera"); @@ -344,7 +343,6 @@ public: void setDefaults(osg::Camera* camera) override { - SceneUtil::setCameraClearDepth(camera); camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("ReflectionCamera"); @@ -645,7 +643,7 @@ public: { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); - osg::ref_ptr depth = SceneUtil::createDepth(); + osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7d62824f4d..56371a8c02 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -57,7 +57,7 @@ add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt - screencapture + screencapture depth ) add_component_dir (nif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 90046ecff1..91d2300161 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include // particle @@ -1838,7 +1837,7 @@ namespace NifOsg // Depth test flag stateset->setMode(GL_DEPTH_TEST, zprop->flags&1 ? osg::StateAttribute::ON : osg::StateAttribute::OFF); - auto depth = SceneUtil::createDepth(); + osg::ref_ptr depth = new osg::Depth; // Depth write flag depth->setWriteMask((zprop->flags>>1)&1); // Morrowind ignores depth test function diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index f39a5919b6..16d942ecec 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -252,14 +253,14 @@ namespace Resource { if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) { - osg::ref_ptr depth = SceneUtil::createDepth(); + osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } else if (stateset->getRenderingHint() == osg::StateSet::OPAQUE_BIN) { - osg::ref_ptr depth = SceneUtil::createDepth(); + osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(true); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); @@ -692,6 +693,9 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); + SceneUtil::ReplaceDepthVisitor replaceDepthVisitor; + loaded->accept(replaceDepthVisitor); + osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp new file mode 100644 index 0000000000..5d53b97726 --- /dev/null +++ b/components/sceneutil/depth.cpp @@ -0,0 +1,63 @@ +#include "depth.hpp" + +#include + +#include + +#include + +#ifndef GL_DEPTH32F_STENCIL8_NV +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#endif + +namespace SceneUtil +{ + void setCameraClearDepth(osg::Camera* camera) + { + camera->setClearDepth(AutoDepth::isReversed() ? 0.0 : 1.0); + } + + osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near) + { + double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0); + return osg::Matrix( + A/aspect, 0, 0, 0, + 0, A, 0, 0, + 0, 0, 0, -1, + 0, 0, near, 0 + ); + } + + osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far) + { + double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0); + return osg::Matrix( + A/aspect, 0, 0, 0, + 0, A, 0, 0, + 0, 0, near/(far-near), -1, + 0, 0, (far*near)/(far - near), 0 + ); + } + + osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far) + { + return osg::Matrix( + 2/(right-left), 0, 0, 0, + 0, 2/(top-bottom), 0, 0, + 0, 0, 1/(far-near), 0, + (right+left)/(left-right), (top+bottom)/(bottom-top), far/(far-near), 1 + ); + } + + bool isFloatingPointDepthFormat(GLenum format) + { + constexpr std::array formats = { + GL_DEPTH_COMPONENT32F, + GL_DEPTH_COMPONENT32F_NV, + GL_DEPTH32F_STENCIL8, + GL_DEPTH32F_STENCIL8_NV, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } +} \ No newline at end of file diff --git a/components/sceneutil/depth.hpp b/components/sceneutil/depth.hpp new file mode 100644 index 0000000000..11a863ef7a --- /dev/null +++ b/components/sceneutil/depth.hpp @@ -0,0 +1,117 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_DEPTH_H +#define OPENMW_COMPONENTS_SCENEUTIL_DEPTH_H + +#include + +#include "util.hpp" + +namespace SceneUtil +{ + // Sets camera clear depth to 0 if reversed depth buffer is in use, 1 otherwise. + void setCameraClearDepth(osg::Camera* camera); + + // Returns a perspective projection matrix for use with a reversed z-buffer + // and an infinite far plane. This is derived by mapping the default z-range + // of [0,1] to [1,0], then taking the limit as far plane approaches infinity. + osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near); + + // Returns a perspective projection matrix for use with a reversed z-buffer. + osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far); + + // Returns an orthographic projection matrix for use with a reversed z-buffer. + osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far); + + // Returns true if the GL format is a floating point depth format. + bool isFloatingPointDepthFormat(GLenum format); + + // Brief wrapper around an osg::Depth that applies the reversed depth function when a reversed depth buffer is in use + class AutoDepth : public osg::Depth + { + public: + AutoDepth(osg::Depth::Function func=osg::Depth::LESS, double zNear=0.0, double zFar=1.0, bool writeMask=true) + { + setFunction(func); + setZNear(zNear); + setZFar(zFar); + setWriteMask(writeMask); + } + + AutoDepth(const osg::Depth& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) : osg::Depth(copy, copyop) {} + + osg::Object* cloneType() const override { return new AutoDepth; } + osg::Object* clone(const osg::CopyOp& copyop) const override { return new AutoDepth(*this,copyop); } + + void apply(osg::State& state) const override + { + glDepthFunc(static_cast(AutoDepth::isReversed() ? getReversedDepthFunction() : getFunction())); + glDepthMask(static_cast(getWriteMask())); + #if defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE) + glDepthRangef(getZNear(),getZFar()); + #else + glDepthRange(getZNear(),getZFar()); + #endif + } + + static void setReversed(bool reverseZ) + { + static bool init = false; + + if (!init) + { + AutoDepth::sReversed = reverseZ; + init = true; + } + } + + static bool isReversed() + { + return AutoDepth::sReversed; + } + + private: + + static inline bool sReversed = false; + + osg::Depth::Function getReversedDepthFunction() const + { + const osg::Depth::Function func = getFunction(); + + switch (func) + { + case osg::Depth::LESS: + return osg::Depth::GREATER; + case osg::Depth::LEQUAL: + return osg::Depth::GEQUAL; + case osg::Depth::GREATER: + return osg::Depth::LESS; + case osg::Depth::GEQUAL: + return osg::Depth::LEQUAL; + default: + return func; + } + } + + }; + + // Replaces all nodes osg::Depth state attributes with SceneUtil::AutoDepth. + class ReplaceDepthVisitor : public osg::NodeVisitor + { + public: + ReplaceDepthVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {} + + void apply(osg::Node& node) override + { + osg::StateSet* stateSet = node.getStateSet(); + + if (stateSet) + { + if (osg::Depth* depth = static_cast(stateSet->getAttribute(osg::StateAttribute::DEPTH))) + stateSet->setAttribute(new SceneUtil::AutoDepth(*depth)); + }; + + traverse(node); + } + }; +} + +#endif diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 770917eda7..daf6bb80ab 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -27,8 +27,6 @@ #include -#include - #include "shadowsbin.hpp" namespace { @@ -1652,11 +1650,8 @@ void MWShadowTechnique::createShaders() _shadowCastingStateSet->addUniform(new osg::Uniform("alphaTestShadows", false)); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(true); - if (SceneUtil::getReverseZ()) - { - osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::NEGATIVE_ONE_TO_ONE); - _shadowCastingStateSet->setAttribute(clipcontrol, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } + osg::ref_ptr clipcontrol = new osg::ClipControl(osg::ClipControl::LOWER_LEFT, osg::ClipControl::NEGATIVE_ONE_TO_ONE); + _shadowCastingStateSet->setAttribute(clipcontrol, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index e2fcaedbd7..748ceee952 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -42,7 +42,7 @@ #include -#include +#include using namespace osgUtil; @@ -1597,8 +1597,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) } if (_alphaBlendingActive && _mergeAlphaBlending && !geom->getStateSet()) { - auto d = createDepth(); - d->setWriteMask(0); + osg::ref_ptr d = new SceneUtil::AutoDepth; + d->setWriteMask(false); geom->getOrCreateStateSet()->setAttribute(d); } } diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index 3b60c80afd..0765a2e835 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace SceneUtil { @@ -69,6 +70,7 @@ namespace SceneUtil camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); camera->setViewport(0, 0, mTextureWidth, mTextureHeight); + SceneUtil::setCameraClearDepth(camera); setDefaults(mViewDependentDataMap[cv]->mCamera.get()); diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 5b6e0a3ee0..7065fae933 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include #include #include @@ -16,27 +14,8 @@ #include #include -#include -#include #include -#ifndef GL_DEPTH32F_STENCIL8_NV -#define GL_DEPTH32F_STENCIL8_NV 0x8DAC -#endif - -namespace -{ - -bool isReverseZSupported() -{ - if (!Settings::Manager::mDefaultSettings.count({"Camera", "reverse z"})) - return false; - auto ext = osg::GLExtensions::Get(0, false); - return Settings::Manager::getBool("reverse z", "Camera") && ext && ext->isClipControlSupported; -} - -} - namespace SceneUtil { @@ -338,81 +317,4 @@ bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg:: return addMSAAIntermediateTarget; } -void attachAlphaToCoverageFriendlyDepthColor(osg::Camera* camera, osg::Texture2D* colorTex, osg::Texture2D* depthTex, GLenum depthFormat) -{ - bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1; - - if (isFloatingPointDepthFormat(depthFormat) && addMSAAIntermediateTarget) - { - camera->attach(osg::Camera::COLOR_BUFFER0, colorTex); - camera->attach(osg::Camera::DEPTH_BUFFER, depthTex); - camera->addCullCallback(new AttachMultisampledDepthColorCallback(colorTex, depthTex, 2, 1)); - } - else - { - attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, colorTex); - camera->attach(osg::Camera::DEPTH_BUFFER, depthTex); - } -} - -bool getReverseZ() -{ - static bool reverseZ = isReverseZSupported(); - return reverseZ; -} - -void setCameraClearDepth(osg::Camera* camera) -{ - camera->setClearDepth(getReverseZ() ? 0.0 : 1.0); -} - -osg::ref_ptr createDepth() -{ - return new osg::Depth(getReverseZ() ? osg::Depth::GEQUAL : osg::Depth::LEQUAL); -} - -osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near) -{ - double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0); - return osg::Matrix( - A/aspect, 0, 0, 0, - 0, A, 0, 0, - 0, 0, 0, -1, - 0, 0, near, 0 - ); -} - -osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far) -{ - double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0); - return osg::Matrix( - A/aspect, 0, 0, 0, - 0, A, 0, 0, - 0, 0, near/(far-near), -1, - 0, 0, (far*near)/(far - near), 0 - ); -} - -osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far) -{ - return osg::Matrix( - 2/(right-left), 0, 0, 0, - 0, 2/(top-bottom), 0, 0, - 0, 0, 1/(far-near), 0, - (right+left)/(left-right), (top+bottom)/(bottom-top), far/(far-near), 1 - ); -} - -bool isFloatingPointDepthFormat(GLenum format) -{ - constexpr std::array formats = { - GL_DEPTH_COMPONENT32F, - GL_DEPTH_COMPONENT32F_NV, - GL_DEPTH32F_STENCIL8, - GL_DEPTH32F_STENCIL8_NV, - }; - - return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); -} - } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 4d19714d66..ddc2db845d 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include @@ -64,31 +63,6 @@ namespace SceneUtil // Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false); - - void attachAlphaToCoverageFriendlyDepthColor(osg::Camera* camera, osg::Texture2D* colorTex, osg::Texture2D* depthTex, GLenum depthFormat); - - bool getReverseZ(); - - void setCameraClearDepth(osg::Camera* camera); - - // Returns a suitable depth state attribute dependent on whether a reverse-z - // depth buffer is in use. - osg::ref_ptr createDepth(); - - // Returns a perspective projection matrix for use with a reversed z-buffer - // and an infinite far plane. This is derived by mapping the default z-range - // of [0,1] to [1,0], then taking the limit as far plane approaches - // infinity. - osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near); - - // Returns a perspective projection matrix for use with a reversed z-buffer. - osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far); - - // Returns an orthographic projection matrix for use with a reversed z-buffer. - osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far); - - // Returns true if the GL format is a floating point depth format - bool isFloatingPointDepthFormat(GLenum format); } #endif diff --git a/components/sceneutil/waterutil.cpp b/components/sceneutil/waterutil.cpp index ac171005ae..e22070f40f 100644 --- a/components/sceneutil/waterutil.cpp +++ b/components/sceneutil/waterutil.cpp @@ -5,7 +5,7 @@ #include #include -#include "util.hpp" +#include "depth.hpp" namespace SceneUtil { @@ -78,7 +78,7 @@ namespace SceneUtil stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - auto depth = createDepth(); + osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 1e9acdb35f..95e0a75f18 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include "removedalphafunc.hpp" #include "shadermanager.hpp" @@ -567,7 +567,7 @@ namespace Shader { softParticles = true; - auto depth = SceneUtil::createDepth(); + auto depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); writableStateSet->addUniform(new osg::Uniform("particleSize", partsys->getDefaultParticleTemplate().getSizeRange().maximum)); diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 22f507b3a2..ae432d262e 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include @@ -87,7 +87,7 @@ namespace osg::ref_ptr mValue; EqualDepth() - : mValue(new osg::Depth) + : mValue(new SceneUtil::AutoDepth) { mValue->setFunction(osg::Depth::EQUAL); } @@ -106,7 +106,7 @@ namespace osg::ref_ptr mValue; LequalDepth() - : mValue(SceneUtil::createDepth()) + : mValue(new SceneUtil::AutoDepth) { } }; From ebe8ba717ef96556a14463790938e350ee349f3e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 21 Nov 2021 02:30:57 +0000 Subject: [PATCH 1751/2859] Move essimporter to CS CI job It's the only thing that won't impact most users, and might buy enough seconds to stop the CI timeouts. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5ec31d349d..47511923ad 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -201,11 +201,11 @@ macOS10.15_Xcode11: CCACHE_SIZE: 3G variables: &engine-targets - targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard" + targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard" package: "Engine" variables: &cs-targets - targets: "openmw-cs,bsatool,esmtool,niftest" + targets: "openmw-cs,bsatool,esmtool,niftest,openmw-essimporter" package: "CS" variables: &tests-targets From 9389cfaa42532c532e00f666de259ef983b73554 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 20 Nov 2021 18:39:20 -0800 Subject: [PATCH 1752/2859] mac os driver workaround and shadervisitor fixes --- apps/openmw/mwrender/postprocessor.cpp | 21 +++++ apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwrender/sky.cpp | 2 - components/nifosg/nifloader.cpp | 1 - components/shader/shadervisitor.cpp | 89 +++++++++++++------ components/shader/shadervisitor.hpp | 3 + .../reference/modding/settings/shaders.rst | 3 + files/shaders/objects_fragment.glsl | 2 +- 8 files changed, 90 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index b08cfe1f71..19655c5cdb 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -102,8 +102,15 @@ namespace : mOpaqueDepthFbo(new osg::FrameBufferObject) , mSourceFbo(sourceFbo) , mOpaqueDepthTex(opaqueDepthTex) + , mColorAttached(false) { mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER, osg::FrameBufferAttachment(opaqueDepthTex)); + +#ifdef __APPLE__ + // Mac OS drivers complain that a FBO is incomplete if it has no color attachment + mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_RGB))); + mColorAttached = true; +#endif } void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override @@ -114,7 +121,10 @@ namespace osg::GLExtensions* ext = state.get(); mSourceFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); + postBindOperation(state); + mOpaqueDepthFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + postBindOperation(state); ext->glBlitFramebuffer(0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), 0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); @@ -124,9 +134,20 @@ namespace bin->drawImplementation(renderInfo, previous); } private: + void postBindOperation(osg::State& state) + { + if (mColorAttached) + return; + #if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE) + state.glDrawBuffer(GL_NONE); + state.glReadBuffer(GL_NONE); + #endif + } + osg::ref_ptr mOpaqueDepthFbo; osg::ref_ptr mSourceFbo; osg::ref_ptr mOpaqueDepthTex; + bool mColorAttached; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 30be74c839..9abd2e7487 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -316,6 +316,7 @@ namespace MWRender resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); // Shadows and radial fog have problems with fixed-function mode bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") + || Settings::Manager::getBool("soft particles", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index fcb7447cf3..8d24ada5be 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -636,7 +636,6 @@ namespace MWRender mParticleNode = new osg::PositionAttitudeTransform; mParticleNode->addCullCallback(mUnderwaterSwitch); mParticleNode->setNodeMask(Mask_WeatherParticles); - mParticleNode->getOrCreateStateSet(); mRootNode->addChild(mParticleNode); } @@ -668,7 +667,6 @@ namespace MWRender ps->getParticle(particleIndex)->update(0, true); } - ps->getOrCreateStateSet(); ps->setUserValue("simpleLighting", true); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 91d2300161..f85946f7bd 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1049,7 +1049,6 @@ namespace NifOsg void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); - partsys->getOrCreateStateSet(); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); const Nif::NiParticleSystemController* partctrl = nullptr; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 95e0a75f18..2063546034 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,6 +1,7 @@ #include "shadervisitor.hpp" #include +#include #include #include @@ -37,12 +38,14 @@ namespace Shader , mUniforms(rhs.mUniforms) , mModes(rhs.mModes) , mAttributes(rhs.mAttributes) + , mTextureModes(rhs.mTextureModes) { } void addUniform(const std::string& name) { mUniforms.emplace(name); } void setMode(osg::StateAttribute::GLMode mode) { mModes.emplace(mode); } void setAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { mAttributes.emplace(typeMemberPair); } + void setTextureMode(int unit, osg::StateAttribute::GLMode mode) { mTextureModes[unit].emplace(mode); } void setAttribute(const osg::StateAttribute* attribute) { @@ -64,12 +67,20 @@ namespace Shader bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } bool hasAttribute(const osg::StateAttribute::TypeMemberPair &typeMemberPair) { return mAttributes.count(typeMemberPair); } bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); } + bool hasTextureMode(int unit, osg::StateAttribute::GLMode mode) + { + auto it = mTextureModes.find(unit); + if (it == mTextureModes.cend()) + return false; + + return it->second.count(mode); + } const std::set& getAttributes() { return mAttributes; } bool empty() { - return mUniforms.empty() && mModes.empty() && mAttributes.empty(); + return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureModes.empty(); } META_Object(Shader, AddedState) @@ -86,9 +97,12 @@ namespace Shader AddedState* mTracker; }; + using ModeSet = std::unordered_set; + std::unordered_set mUniforms; - std::unordered_set mModes; + ModeSet mModes; std::set mAttributes; + std::unordered_map mTextureModes; }; ShaderVisitor::ShaderRequirements::ShaderRequirements() @@ -102,6 +116,8 @@ namespace Shader , mAlphaBlend(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) + , mSoftParticles(false) + , mSoftParticleSize(0.f) , mNode(nullptr) { } @@ -213,6 +229,8 @@ namespace Shader if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) mRequirements.back().mShaderRequired = true; + osg::ref_ptr addedState = getAddedState(*stateset); + if (!texAttributes.empty()) { const osg::Texture* diffuseMap = nullptr; @@ -224,6 +242,9 @@ namespace Shader const osg::StateAttribute *attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { + if (addedState && addedState->hasTextureMode(unit, GL_TEXTURE_2D)) + continue; + const osg::Texture* texture = attr->asTexture(); if (texture) { @@ -350,7 +371,6 @@ namespace Shader osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList(); - osg::ref_ptr addedState = getAddedState(*stateset); for (const auto* attributeMap : std::initializer_list{ &attributes, &removedAttributes }) { @@ -545,6 +565,25 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } + if (reqs.mSoftParticles) + { + osg::ref_ptr depth = new SceneUtil::AutoDepth; + depth->setWriteMask(false); + writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + addedState->setAttribute(depth); + + writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); + addedState->addUniform("particleSize"); + + writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); + addedState->addUniform("opaqueDepthTex"); + + writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); + addedState->setTextureMode(2, GL_TEXTURE_2D); + } + + defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; + if (!addedState->empty()) { // user data is normally shallow copied so shared with the original stateset @@ -557,27 +596,6 @@ namespace Shader updateAddedState(*writableUserData, addedState); } - bool softParticles = false; - - if (mOpaqueDepthTex) - { - auto partsys = dynamic_cast(&node); - - if (partsys) - { - softParticles = true; - - auto depth = new SceneUtil::AutoDepth; - depth->setWriteMask(false); - writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - writableStateSet->addUniform(new osg::Uniform("particleSize", partsys->getDefaultParticleTemplate().getSizeRange().maximum)); - writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); - writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); - } - } - - defineMap["softParticles"] = softParticles ? "1" : "0"; - std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -719,13 +737,22 @@ namespace Shader void ShaderVisitor::apply(osg::Drawable& drawable) { - // non-Geometry drawable (e.g. particle system) - bool needPop = (drawable.getStateSet() != nullptr); + auto partsys = dynamic_cast(&drawable); + + bool needPop = drawable.getStateSet() || partsys; - if (drawable.getStateSet()) + if (needPop) { pushRequirements(drawable); - applyStateSet(drawable.getStateSet(), drawable); + + if (partsys && mOpaqueDepthTex) + { + mRequirements.back().mSoftParticles = true; + mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum; + } + + if (drawable.getStateSet()) + applyStateSet(drawable.getStateSet(), drawable); } if (!mRequirements.empty()) @@ -831,6 +858,12 @@ namespace Shader for (const auto& attribute : removedState->getAttributeList()) writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + + for (unsigned int unit = 0; unit < removedState->getTextureModeList().size(); ++unit) + { + for (const auto&[mode, value] : removedState->getTextureModeList()[unit]) + writableStateSet->setTextureMode(unit, mode, value); + } } } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index d80e697fd8..72dec05b5e 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -101,6 +101,9 @@ namespace Shader // -1 == no tangents required int mTexStageRequiringTangents; + bool mSoftParticles; + float mSoftParticleSize; + // the Node that requested these requirements osg::Node* mNode; }; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index b9d8cfe1b9..5629e321d0 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -282,3 +282,6 @@ intersection between individual particles and other opaque geometry by blending 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. +This means that shaders will be used to render all objects and the terrain. \ No newline at end of file diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 99ed44919b..bae9ff7951 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -226,7 +226,7 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if @softParticles +#if !defined(FORCE_OPAQUE) && @softParticles gl_FragData[0].a *= calcSoftParticleFade(); #endif From f9136d4392b4a3d76f373f6d1a609674d8911d81 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 21 Nov 2021 13:12:35 +0100 Subject: [PATCH 1753/2859] Support multiple arguments in Lua callbacks. --- .../lua/test_scriptscontainer.cpp | 20 +++++++++++++++++++ components/lua/scriptscontainer.cpp | 9 --------- components/lua/scriptscontainer.hpp | 12 +++++++++-- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp index 344fbb3c78..8be02a2f13 100644 --- a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp +++ b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp @@ -440,4 +440,24 @@ return { EXPECT_EQ(counter4, 25); } + TEST_F(LuaScriptsContainerTest, CallbackWrapper) + { + LuaUtil::Callback callback{mLua.sol()["print"], mLua.newTable()}; + callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptDebugNameKey] = "some_script.lua"; + callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptsContainer::ScriptId{nullptr, 0}; + + testing::internal::CaptureStdout(); + callback(1.5); + EXPECT_EQ(internal::GetCapturedStdout(), "1.5\n"); + + testing::internal::CaptureStdout(); + callback(1.5, 2.5); + EXPECT_EQ(internal::GetCapturedStdout(), "1.5\t2.5\n"); + + testing::internal::CaptureStdout(); + callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = sol::nil; + callback(1.5, 2.5); + EXPECT_EQ(internal::GetCapturedStdout(), "Ignored callback to the removed script some_script.lua\n"); + } + } diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 517ad5f788..eb9da7a60b 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -530,13 +530,4 @@ namespace LuaUtil updateTimerQueue(mHoursTimersQueue, gameHours); } - void Callback::operator()(sol::object arg) const - { - if (mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil) - LuaUtil::call(mFunc, std::move(arg)); - else - Log(Debug::Debug) << "Ignored callback to the removed script " - << mHiddenData.get(ScriptsContainer::sScriptDebugNameKey); - } - } diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index e934868d08..1863d04669 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -242,7 +242,7 @@ namespace LuaUtil int64_t mTemporaryCallbackCounter = 0; }; - // Wrapper for a single-argument Lua function. + // Wrapper for a Lua function. // Holds information about the script the function belongs to. // Needed to prevent callback calls if the script was removed. struct Callback @@ -250,7 +250,15 @@ namespace LuaUtil sol::function mFunc; sol::table mHiddenData; // same object as Script::mHiddenData in ScriptsContainer - void operator()(sol::object arg) const; + template + void operator()(Args&&... args) const + { + if (mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil) + LuaUtil::call(mFunc, std::forward(args)...); + else + Log(Debug::Debug) << "Ignored callback to the removed script " + << mHiddenData.get(ScriptsContainer::sScriptDebugNameKey); + } }; } From ce7f8c90f8d9aa70078428acf4e39398cac283d3 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 21 Nov 2021 14:21:47 +0100 Subject: [PATCH 1754/2859] Fix unity build --- components/detournavigator/makenavmesh.cpp | 5 +- .../detournavigator/navmeshtileview.cpp | 83 +++++++++++-------- .../detournavigator/navmeshtileview.hpp | 2 +- .../detournavigator/preparednavmeshdata.cpp | 9 -- .../preparednavmeshdatatuple.hpp | 22 +++-- 5 files changed, 67 insertions(+), 54 deletions(-) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 3043c36308..c53fcdcc48 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -26,10 +26,10 @@ #include #include +namespace DetourNavigator +{ namespace { - using namespace DetourNavigator; - struct Rectangle { TileBounds mBounds; @@ -388,6 +388,7 @@ namespace return power; } } +} // namespace DetourNavigator namespace DetourNavigator { diff --git a/components/detournavigator/navmeshtileview.cpp b/components/detournavigator/navmeshtileview.cpp index 336cd1ba84..d12bcecd7e 100644 --- a/components/detournavigator/navmeshtileview.cpp +++ b/components/detournavigator/navmeshtileview.cpp @@ -9,14 +9,11 @@ #include #include -namespace +inline bool operator==(const dtMeshHeader& lhs, const dtMeshHeader& rhs) noexcept { - using DetourNavigator::ArrayRef; - using DetourNavigator::Ref; - using DetourNavigator::Span; - - auto makeTuple(const dtMeshHeader& v) + const auto makeTuple = [] (const dtMeshHeader& v) { + using DetourNavigator::ArrayRef; return std::tuple( v.x, v.y, @@ -39,47 +36,46 @@ namespace ArrayRef(v.bmax), v.bvQuantFactor ); - } + }; + return makeTuple(lhs) == makeTuple(rhs); +} - auto makeTuple(const dtPoly& v) +inline bool operator==(const dtPoly& lhs, const dtPoly& rhs) noexcept +{ + const auto makeTuple = [] (const dtPoly& v) { + using DetourNavigator::ArrayRef; return std::tuple(ArrayRef(v.verts), ArrayRef(v.neis), v.flags, v.vertCount, v.areaAndtype); - } + }; + return makeTuple(lhs) == makeTuple(rhs); +} - auto makeTuple(const dtPolyDetail& v) +inline bool operator==(const dtPolyDetail& lhs, const dtPolyDetail& rhs) noexcept +{ + const auto makeTuple = [] (const dtPolyDetail& v) { return std::tuple(v.vertBase, v.triBase, v.vertCount, v.triCount); - } + }; + return makeTuple(lhs) == makeTuple(rhs); +} - auto makeTuple(const dtBVNode& v) +inline bool operator==(const dtBVNode& lhs, const dtBVNode& rhs) noexcept +{ + const auto makeTuple = [] (const dtBVNode& v) { + using DetourNavigator::ArrayRef; return std::tuple(ArrayRef(v.bmin), ArrayRef(v.bmax), v.i); - } - - auto makeTuple(const dtOffMeshConnection& v) - { - return std::tuple(ArrayRef(v.pos), v.rad, v.poly, v.flags, v.side, v.userId); - } - - auto makeTuple(const DetourNavigator::NavMeshTileConstView& v) - { - return std::tuple( - Ref(*v.mHeader), - Span(v.mPolys, v.mHeader->polyCount), - Span(v.mVerts, v.mHeader->vertCount), - Span(v.mDetailMeshes, v.mHeader->detailMeshCount), - Span(v.mDetailVerts, v.mHeader->detailVertCount), - Span(v.mDetailTris, v.mHeader->detailTriCount), - Span(v.mBvTree, v.mHeader->bvNodeCount), - Span(v.mOffMeshCons, v.mHeader->offMeshConCount) - ); - } + }; + return makeTuple(lhs) == makeTuple(rhs); } -template -inline auto operator==(const T& lhs, const T& rhs) - -> std::enable_if_t, void>, bool> +inline bool operator==(const dtOffMeshConnection& lhs, const dtOffMeshConnection& rhs) noexcept { + const auto makeTuple = [] (const dtOffMeshConnection& v) + { + using DetourNavigator::ArrayRef; + return std::tuple(ArrayRef(v.pos), v.rad, v.poly, v.flags, v.side, v.userId); + }; return makeTuple(lhs) == makeTuple(rhs); } @@ -139,8 +135,23 @@ namespace DetourNavigator return view; } - bool operator==(const NavMeshTileConstView& lhs, const NavMeshTileConstView& rhs) + bool operator==(const NavMeshTileConstView& lhs, const NavMeshTileConstView& rhs) noexcept { + using DetourNavigator::Ref; + using DetourNavigator::Span; + const auto makeTuple = [] (const DetourNavigator::NavMeshTileConstView& v) + { + return std::tuple( + Ref(*v.mHeader), + Span(v.mPolys, v.mHeader->polyCount), + Span(v.mVerts, v.mHeader->vertCount), + Span(v.mDetailMeshes, v.mHeader->detailMeshCount), + Span(v.mDetailVerts, v.mHeader->detailVertCount), + Span(v.mDetailTris, v.mHeader->detailTriCount), + Span(v.mBvTree, v.mHeader->bvNodeCount), + Span(v.mOffMeshCons, v.mHeader->offMeshConCount) + ); + }; return makeTuple(lhs) == makeTuple(rhs); } } diff --git a/components/detournavigator/navmeshtileview.hpp b/components/detournavigator/navmeshtileview.hpp index 92017360c3..b797545b8a 100644 --- a/components/detournavigator/navmeshtileview.hpp +++ b/components/detournavigator/navmeshtileview.hpp @@ -21,7 +21,7 @@ namespace DetourNavigator const dtBVNode* mBvTree; const dtOffMeshConnection* mOffMeshCons; - friend bool operator==(const NavMeshTileConstView& lhs, const NavMeshTileConstView& rhs); + friend bool operator==(const NavMeshTileConstView& lhs, const NavMeshTileConstView& rhs) noexcept; }; NavMeshTileConstView asNavMeshTileConstView(const unsigned char* data); diff --git a/components/detournavigator/preparednavmeshdata.cpp b/components/detournavigator/preparednavmeshdata.cpp index 3fea46b26c..c86e18482c 100644 --- a/components/detournavigator/preparednavmeshdata.cpp +++ b/components/detournavigator/preparednavmeshdata.cpp @@ -6,8 +6,6 @@ namespace { - using namespace DetourNavigator; - void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept { value.meshes = nullptr; @@ -26,13 +24,6 @@ namespace } } -template -inline constexpr auto operator==(const T& lhs, const T& rhs) noexcept - -> std::enable_if_t, void>, bool> -{ - return makeTuple(lhs) == makeTuple(rhs); -} - namespace DetourNavigator { PreparedNavMeshData::PreparedNavMeshData() noexcept diff --git a/components/detournavigator/preparednavmeshdatatuple.hpp b/components/detournavigator/preparednavmeshdatatuple.hpp index bcca0ace37..03b192ad38 100644 --- a/components/detournavigator/preparednavmeshdatatuple.hpp +++ b/components/detournavigator/preparednavmeshdatatuple.hpp @@ -9,10 +9,11 @@ #include -namespace DetourNavigator +inline bool operator==(const rcPolyMesh& lhs, const rcPolyMesh& rhs) noexcept { - constexpr auto makeTuple(const rcPolyMesh& v) noexcept + const auto makeTuple = [] (const rcPolyMesh& v) { + using namespace DetourNavigator; return std::tuple( Span(v.verts, static_cast(getVertsLength(v))), Span(v.polys, static_cast(getPolysLength(v))), @@ -26,18 +27,27 @@ namespace DetourNavigator v.borderSize, v.maxEdgeError ); - } + }; + return makeTuple(lhs) == makeTuple(rhs); +} - constexpr auto makeTuple(const rcPolyMeshDetail& v) noexcept +inline bool operator==(const rcPolyMeshDetail& lhs, const rcPolyMeshDetail& rhs) noexcept +{ + const auto makeTuple = [] (const rcPolyMeshDetail& v) { + using namespace DetourNavigator; return std::tuple( Span(v.meshes, static_cast(getMeshesLength(v))), Span(v.verts, static_cast(getVertsLength(v))), Span(v.tris, static_cast(getTrisLength(v))) ); - } + }; + return makeTuple(lhs) == makeTuple(rhs); +} - constexpr auto makeTuple(const PreparedNavMeshData& v) noexcept +namespace DetourNavigator +{ + inline auto makeTuple(const PreparedNavMeshData& v) noexcept { return std::tuple( v.mUserId, From d04d4ab4991c17619192ed234b5817fcac7f2000 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Nov 2021 14:48:13 +0100 Subject: [PATCH 1755/2859] Add MurmurHash3_x64_128 implementation Sources: https://github.com/aappleby/smhasher/blob/92cf3702fcfaadc84eb7bef59825a23e0cd84f56/src/MurmurHash3.h https://github.com/aappleby/smhasher/blob/92cf3702fcfaadc84eb7bef59825a23e0cd84f56/src/MurmurHash3.cpp Other hash functions and unused implementation details are removed. --- extern/CMakeLists.txt | 2 + extern/smhasher/CMakeLists.txt | 2 + extern/smhasher/MurmurHash3.cpp | 148 ++++++++++++++++++++++++++++++++ extern/smhasher/MurmurHash3.h | 33 +++++++ 4 files changed, 185 insertions(+) create mode 100644 extern/smhasher/CMakeLists.txt create mode 100644 extern/smhasher/MurmurHash3.cpp create mode 100644 extern/smhasher/MurmurHash3.h diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 59d3d15176..0bfdc4c233 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -208,3 +208,5 @@ if (NOT OPENMW_USE_SYSTEM_SQLITE3) set(SQLite3_INCLUDE_DIR ${sqlite3_SOURCE_DIR}/ PARENT_SCOPE) set(SQLite3_LIBRARY sqlite3 PARENT_SCOPE) endif() + +add_subdirectory(smhasher) diff --git a/extern/smhasher/CMakeLists.txt b/extern/smhasher/CMakeLists.txt new file mode 100644 index 0000000000..ee03e6c38e --- /dev/null +++ b/extern/smhasher/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(smhasher STATIC MurmurHash3.cpp) +target_include_directories(smhasher INTERFACE .) diff --git a/extern/smhasher/MurmurHash3.cpp b/extern/smhasher/MurmurHash3.cpp new file mode 100644 index 0000000000..62be4f3cef --- /dev/null +++ b/extern/smhasher/MurmurHash3.cpp @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +// Note - The x86 and x64 versions do _not_ produce the same results, as the +// algorithms are optimized for their respective platforms. You can still +// compile and run any of them on any platform, but your performance with the +// non-native version will be less than optimal. + +#include "MurmurHash3.h" + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +#define FORCE_INLINE __forceinline + +#include + +#define ROTL64(x,y) _rotl64(x,y) + +#define BIG_CONSTANT(x) (x) + +// Other compilers + +#else // defined(_MSC_VER) + +#define FORCE_INLINE inline __attribute__((always_inline)) + +inline uint64_t rotl64 ( uint64_t x, int8_t r ) +{ + return (x << r) | (x >> (64 - r)); +} + +#define ROTL64(x,y) rotl64(x,y) + +#define BIG_CONSTANT(x) (x##LLU) + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- +// Block read - if your platform needs to do endian-swapping or can only +// handle aligned reads, do the conversion here + +FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i ) +{ + return p[i]; +} + +//---------- + +FORCE_INLINE uint64_t fmix64 ( uint64_t k ) +{ + k ^= k >> 33; + k *= BIG_CONSTANT(0xff51afd7ed558ccd); + k ^= k >> 33; + k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); + k ^= k >> 33; + + return k; +} + +//----------------------------------------------------------------------------- + +void MurmurHash3_x64_128 ( const void * key, const int len, + const uint32_t seed, void * out ) +{ + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 16; + + uint64_t h1 = seed; + uint64_t h2 = seed; + + const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); + const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); + + //---------- + // body + + const uint64_t * blocks = (const uint64_t *)(data); + + for(int i = 0; i < nblocks; i++) + { + uint64_t k1 = getblock64(blocks,i*2+0); + uint64_t k2 = getblock64(blocks,i*2+1); + + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + + h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729; + + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5; + } + + //---------- + // tail + + const uint8_t * tail = (const uint8_t*)(data + nblocks*16); + + uint64_t k1 = 0; + uint64_t k2 = 0; + + switch(len & 15) + { + case 15: k2 ^= ((uint64_t)tail[14]) << 48; + case 14: k2 ^= ((uint64_t)tail[13]) << 40; + case 13: k2 ^= ((uint64_t)tail[12]) << 32; + case 12: k2 ^= ((uint64_t)tail[11]) << 24; + case 11: k2 ^= ((uint64_t)tail[10]) << 16; + case 10: k2 ^= ((uint64_t)tail[ 9]) << 8; + case 9: k2 ^= ((uint64_t)tail[ 8]) << 0; + k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2; + + case 8: k1 ^= ((uint64_t)tail[ 7]) << 56; + case 7: k1 ^= ((uint64_t)tail[ 6]) << 48; + case 6: k1 ^= ((uint64_t)tail[ 5]) << 40; + case 5: k1 ^= ((uint64_t)tail[ 4]) << 32; + case 4: k1 ^= ((uint64_t)tail[ 3]) << 24; + case 3: k1 ^= ((uint64_t)tail[ 2]) << 16; + case 2: k1 ^= ((uint64_t)tail[ 1]) << 8; + case 1: k1 ^= ((uint64_t)tail[ 0]) << 0; + k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; h2 ^= len; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + h2 += h1; + + ((uint64_t*)out)[0] = h1; + ((uint64_t*)out)[1] = h2; +} + +//----------------------------------------------------------------------------- + diff --git a/extern/smhasher/MurmurHash3.h b/extern/smhasher/MurmurHash3.h new file mode 100644 index 0000000000..5a7ed73f33 --- /dev/null +++ b/extern/smhasher/MurmurHash3.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +#ifndef _MURMURHASH3_H_ +#define _MURMURHASH3_H_ + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) && (_MSC_VER < 1600) + +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; + +// Other compilers + +#else // defined(_MSC_VER) + +#include + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- + +void MurmurHash3_x64_128 ( const void * key, int len, uint32_t seed, void * out ); + +//----------------------------------------------------------------------------- + +#endif // _MURMURHASH3_H_ From 86bf9d5b8df6b2c136dc9429376a8671c2571d06 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Nov 2021 14:50:04 +0100 Subject: [PATCH 1756/2859] Support 128bit seed for MurmurHash3_x64_128 --- extern/smhasher/MurmurHash3.cpp | 6 +++--- extern/smhasher/MurmurHash3.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extern/smhasher/MurmurHash3.cpp b/extern/smhasher/MurmurHash3.cpp index 62be4f3cef..69d8d6c773 100644 --- a/extern/smhasher/MurmurHash3.cpp +++ b/extern/smhasher/MurmurHash3.cpp @@ -66,13 +66,13 @@ FORCE_INLINE uint64_t fmix64 ( uint64_t k ) //----------------------------------------------------------------------------- void MurmurHash3_x64_128 ( const void * key, const int len, - const uint32_t seed, void * out ) + const uint64_t * seed, void * out ) { const uint8_t * data = (const uint8_t*)key; const int nblocks = len / 16; - uint64_t h1 = seed; - uint64_t h2 = seed; + uint64_t h1 = seed[0]; + uint64_t h2 = seed[1]; const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); diff --git a/extern/smhasher/MurmurHash3.h b/extern/smhasher/MurmurHash3.h index 5a7ed73f33..8aebdc304d 100644 --- a/extern/smhasher/MurmurHash3.h +++ b/extern/smhasher/MurmurHash3.h @@ -26,7 +26,7 @@ typedef unsigned __int64 uint64_t; //----------------------------------------------------------------------------- -void MurmurHash3_x64_128 ( const void * key, int len, uint32_t seed, void * out ); +void MurmurHash3_x64_128 ( const void * key, int len, const uint64_t * seed, void * out ); //----------------------------------------------------------------------------- From f85053d78c7d21484bcf49d75ab2a9fce5ce79f3 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Nov 2021 16:57:58 +0100 Subject: [PATCH 1757/2859] Support unaligned blocks --- extern/smhasher/MurmurHash3.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extern/smhasher/MurmurHash3.cpp b/extern/smhasher/MurmurHash3.cpp index 69d8d6c773..c8b774bab9 100644 --- a/extern/smhasher/MurmurHash3.cpp +++ b/extern/smhasher/MurmurHash3.cpp @@ -9,6 +9,8 @@ #include "MurmurHash3.h" +#include + //----------------------------------------------------------------------------- // Platform-specific functions and macros @@ -47,7 +49,9 @@ inline uint64_t rotl64 ( uint64_t x, int8_t r ) FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i ) { - return p[i]; + uint64_t result = 0; + std::memcpy(&result, p + i, sizeof(result)); + return result; } //---------- From a665a38aca554515b7e6e2d071183ba325134317 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Nov 2021 17:40:22 +0100 Subject: [PATCH 1758/2859] Use MurmurHash3_x64_128 for file hash --- apps/openmw_test_suite/CMakeLists.txt | 2 + apps/openmw_test_suite/files/hash.cpp | 55 +++++++++++++++++++ .../nifloader/testbulletnifloader.cpp | 4 +- components/CMakeLists.txt | 1 + components/files/hash.cpp | 19 ++++--- components/files/hash.hpp | 3 +- components/nif/niffile.cpp | 3 +- components/nif/niffile.hpp | 6 +- components/nifosg/nifloader.cpp | 4 +- components/resource/bulletshape.hpp | 3 +- components/resource/bulletshapemanager.cpp | 5 +- components/resource/scenemanager.cpp | 4 +- 12 files changed, 85 insertions(+), 24 deletions(-) create mode 100644 apps/openmw_test_suite/files/hash.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 29564ef191..ed1d6c413b 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -62,6 +62,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) esmloader/load.cpp esmloader/esmdata.cpp + + files/hash.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/files/hash.cpp b/apps/openmw_test_suite/files/hash.cpp new file mode 100644 index 0000000000..e6dbc8f6cc --- /dev/null +++ b/apps/openmw_test_suite/files/hash.cpp @@ -0,0 +1,55 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace Files; + + struct Params + { + std::size_t mSize; + std::array mHash; + }; + + struct FilesGetHash : TestWithParam {}; + + TEST_P(FilesGetHash, shouldReturnHashForStringStream) + { + const std::string fileName = "fileName"; + std::string content; + std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); + std::istringstream stream(content); + EXPECT_EQ(getHash(fileName, stream), GetParam().mHash); + } + + TEST_P(FilesGetHash, shouldReturnHashForConstrainedFileStream) + { + std::string fileName(UnitTest::GetInstance()->current_test_info()->name()); + std::replace(fileName.begin(), fileName.end(), '/', '_'); + std::string content; + std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); + std::fstream(fileName, std::ios_base::out | std::ios_base::binary) + .write(content.data(), static_cast(content.size())); + const auto stream = Files::openConstrainedFileStream(fileName.data(), 0, content.size()); + EXPECT_EQ(getHash(fileName, *stream), GetParam().mHash); + } + + INSTANTIATE_TEST_SUITE_P(Params, FilesGetHash, Values( + Params {0, {0, 0}}, + Params {1, {9607679276477937801ull, 16624257681780017498ull}}, + Params {128, {15287858148353394424ull, 16818615825966581310ull}}, + Params {1000, {11018119256083894017ull, 6631144854802791578ull}}, + Params {4096, {11972283295181039100ull, 16027670129106775155ull}}, + Params {4097, {16717956291025443060ull, 12856404199748778153ull}}, + Params {5000, {15775925571142117787ull, 10322955217889622896ull}} + )); +} diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 92aece9075..8fbf5c1b5b 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -335,7 +335,7 @@ namespace MOCK_METHOD(void, setUseSkinning, (bool), (override)); MOCK_METHOD(bool, getUseSkinning, (), (const, override)); MOCK_METHOD(std::string, getFilename, (), (const, override)); - MOCK_METHOD(std::uint64_t, getHash, (), (const, override)); + MOCK_METHOD(std::string, getHash, (), (const, override)); MOCK_METHOD(unsigned int, getVersion, (), (const, override)); MOCK_METHOD(unsigned int, getUserVersion, (), (const, override)); MOCK_METHOD(unsigned int, getBethVersion, (), (const, override)); @@ -382,7 +382,7 @@ namespace ), btVector3(4, 8, 12) }; - const std::uint64_t mHash = 42; + const std::string mHash = "hash"; TestBulletNifLoader() { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 56371a8c02..3fc9cb92a1 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -289,6 +289,7 @@ target_link_libraries(components Base64 SQLite::SQLite3 + smhasher ) target_link_libraries(components ${BULLET_LIBRARIES}) diff --git a/components/files/hash.cpp b/components/files/hash.cpp index 91af1e9283..079a169ae5 100644 --- a/components/files/hash.cpp +++ b/components/files/hash.cpp @@ -1,17 +1,17 @@ #include "hash.hpp" -#include +#include +#include #include -#include #include #include namespace Files { - std::uint64_t getHash(const std::string& fileName, std::istream& stream) + std::array getHash(const std::string& fileName, std::istream& stream) { - std::uint64_t hash = std::hash {}(fileName); + std::array hash {0, 0}; try { const auto start = stream.tellg(); @@ -19,9 +19,14 @@ namespace Files stream.exceptions(std::ios_base::badbit); while (stream) { - std::uint64_t value = 0; - stream.read(reinterpret_cast(&value), sizeof(value)); - Misc::hashCombine(hash, value); + std::array value; + stream.read(value.data(), value.size()); + const std::streamsize read = stream.gcount(); + if (read == 0) + break; + std::array blockHash {0, 0}; + MurmurHash3_x64_128(value.data(), static_cast(read), hash.data(), blockHash.data()); + hash = blockHash; } stream.exceptions(exceptions); stream.clear(); diff --git a/components/files/hash.hpp b/components/files/hash.hpp index 46784ee9be..13d56d5824 100644 --- a/components/files/hash.hpp +++ b/components/files/hash.hpp @@ -1,13 +1,14 @@ #ifndef COMPONENTS_FILES_HASH_H #define COMPONENTS_FILES_HASH_H +#include #include #include #include namespace Files { - std::uint64_t getHash(const std::string& fileName, std::istream& stream); + std::array getHash(const std::string& fileName, std::istream& stream); } #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index f11b75d218..f70b9024f9 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -173,7 +173,8 @@ std::string NIFFile::printVersion(unsigned int version) void NIFFile::parse(Files::IStreamPtr stream) { - hash = Files::getHash(filename, *stream); + const std::array fileHash = Files::getHash(filename, *stream); + hash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); NIFStream nif (this, stream); diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index eb851a74ff..6884f51d58 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -34,7 +34,7 @@ struct File virtual std::string getFilename() const = 0; - virtual std::uint64_t getHash() const = 0; + virtual std::string getHash() const = 0; virtual unsigned int getVersion() const = 0; @@ -52,7 +52,7 @@ class NIFFile final : public File /// File name, used for error messages and opening the file std::string filename; - std::uint64_t hash = 0; + std::string hash; /// Record list std::vector records; @@ -144,7 +144,7 @@ public: /// Get the name of the file std::string getFilename() const override { return filename; } - std::uint64_t getHash() const override { return hash; } + std::string getHash() const override { return hash; } /// Get the version of the NIF format used unsigned int getVersion() const override { return ver; } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 91d2300161..99aaaa3323 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -325,9 +325,7 @@ namespace NifOsg if (!textkeys->mTextKeys.empty()) created->getOrCreateUserDataContainer()->addUserObject(textkeys); - const std::uint64_t nifHash = nif->getHash(); - created->setUserValue(Misc::OsgUserValues::sFileHash, - std::string(reinterpret_cast(&nifHash), sizeof(nifHash))); + created->setUserValue(Misc::OsgUserValues::sFileHash, nif->getHash()); return created; } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 6dfa37aeda..cd8922ec8e 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H +#include #include #include @@ -53,7 +54,7 @@ namespace Resource std::map mAnimatedShapes; std::string mFileName; - std::uint64_t mFileHash = 0; + std::string mFileHash; void setLocalScaling(const btVector3& scale); diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 3803fbf669..da4672757a 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -169,10 +169,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & if (shape != nullptr) { shape->mFileName = normalized; - std::string fileHash; - constNode->getUserValue(Misc::OsgUserValues::sFileHash, fileHash); - if (!fileHash.empty()) - std::memcpy(&shape->mFileHash, fileHash.data(), std::min(fileHash.size(), sizeof(shape->mFileHash))); + constNode->getUserValue(Misc::OsgUserValues::sFileHash, shape->mFileHash); } } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 16d942ecec..5f2d78d2ed 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -506,7 +506,7 @@ namespace Resource options->setReadFileCallback(new ImageReadCallback(imageManager)); if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); - const std::uint64_t fileHash = Files::getHash(normalizedFilename, model); + const std::array fileHash = Files::getHash(normalizedFilename, model); osgDB::ReaderWriter::ReadResult result = reader->readNode(model, options); if (!result.success()) @@ -538,7 +538,7 @@ namespace Resource } node->setUserValue(Misc::OsgUserValues::sFileHash, - std::string(reinterpret_cast(&fileHash), sizeof(fileHash))); + std::string(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t))); return node; } From 6b7363bd59c199409c7c8358ccecf93a4f18531f Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 1 Nov 2021 20:21:27 +0100 Subject: [PATCH 1759/2859] Replace generation and revision by version --- apps/openmw/mwrender/navmesh.cpp | 11 +++----- apps/openmw/mwrender/navmesh.hpp | 9 ++++--- apps/openmw/mwrender/renderingmanager.cpp | 3 +-- .../detournavigator/navigator.cpp | 6 ++--- .../detournavigator/asyncnavmeshupdater.cpp | 13 +++------- .../detournavigator/navmeshcacheitem.cpp | 6 ++--- .../detournavigator/navmeshcacheitem.hpp | 17 ++++-------- components/detournavigator/version.hpp | 26 ++++++++++++++++--- 8 files changed, 47 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 548e85ad10..7cf045d0bd 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -18,8 +18,6 @@ namespace MWRender : mRootNode(root) , mEnabled(enabled) , mId(std::numeric_limits::max()) - , mGeneration(0) - , mRevision(0) { } @@ -39,15 +37,14 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id, - const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) + void NavMesh::update(const dtNavMesh& navMesh, std::size_t id, const DetourNavigator::Version& version, + const DetourNavigator::Settings& settings) { - if (!mEnabled || (mGroup && mId == id && mGeneration == generation && mRevision == revision)) + if (!mEnabled || (mGroup && mId == id && mVersion == version)) return; mId = id; - mGeneration = generation; - mRevision = revision; + mVersion = version; if (mGroup) mRootNode->removeChild(mGroup); mGroup = SceneUtil::createNavMeshGroup(navMesh, settings); diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index 4c71dc74ec..c3aa31e05b 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWRENDER_NAVMESH_H #define OPENMW_MWRENDER_NAVMESH_H +#include + #include #include @@ -28,8 +30,8 @@ namespace MWRender bool toggle(); - void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation, - const std::size_t revision, const DetourNavigator::Settings& settings); + void update(const dtNavMesh& navMesh, std::size_t id, const DetourNavigator::Version& version, + const DetourNavigator::Settings& settings); void reset(); @@ -46,8 +48,7 @@ namespace MWRender osg::ref_ptr mRootNode; bool mEnabled; std::size_t mId; - std::size_t mGeneration; - std::size_t mRevision; + DetourNavigator::Version mVersion; osg::ref_ptr mGroup; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 30be74c839..58a1383c7c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1366,8 +1366,7 @@ namespace MWRender try { const auto locked = it->second->lockConst(); - mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getGeneration(), - locked->getNavMeshRevision(), mNavigator.getSettings()); + mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getVersion(), mNavigator.getSettings()); } catch (const std::exception& e) { diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index d7422cda8b..0f4f1e3345 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -990,8 +990,7 @@ namespace ASSERT_EQ(navMeshes.size(), 1); { const auto navMesh = navMeshes.begin()->second->lockConst(); - ASSERT_EQ(navMesh->getGeneration(), 1); - ASSERT_EQ(navMesh->getNavMeshRevision(), 4); + ASSERT_EQ(navMesh->getVersion(), (Version {1, 4})); } for (int n = 0; n < 10; ++n) @@ -1006,8 +1005,7 @@ namespace ASSERT_EQ(navMeshes.size(), 1); { const auto navMesh = navMeshes.begin()->second->lockConst(); - ASSERT_EQ(navMesh->getGeneration(), 1); - ASSERT_EQ(navMesh->getNavMeshRevision(), 4); + ASSERT_EQ(navMesh->getVersion(), (Version {1, 4})); } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 966e07bc5a..583fd1162a 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -311,12 +311,7 @@ namespace DetourNavigator if (recastMesh != nullptr) { - Version navMeshVersion; - { - const auto locked = navMeshCacheItem->lockConst(); - navMeshVersion.mGeneration = locked->getGeneration(); - navMeshVersion.mRevision = locked->getNavMeshRevision(); - } + const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion(); mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile, Version {recastMesh->getGeneration(), recastMesh->getRevision()}, navMeshVersion); @@ -339,13 +334,13 @@ namespace DetourNavigator using FloatMs = std::chrono::duration; - const auto locked = navMeshCacheItem->lockConst(); + const Version version = navMeshCacheItem->lockConst()->getVersion(); Log(Debug::Debug) << std::fixed << std::setprecision(2) << "Cache updated for agent=(" << job.mAgentHalfExtents << ")" << " tile=" << job.mChangedTile << " status=" << status << - " generation=" << locked->getGeneration() << - " revision=" << locked->getNavMeshRevision() << + " generation=" << version.mGeneration << + " revision=" << version.mRevision << " time=" << std::chrono::duration_cast(finish - start).count() << "ms" << " thread=" << std::this_thread::get_id(); diff --git a/components/detournavigator/navmeshcacheitem.cpp b/components/detournavigator/navmeshcacheitem.cpp index ee6f3308d0..889b764c3c 100644 --- a/components/detournavigator/navmeshcacheitem.cpp +++ b/components/detournavigator/navmeshcacheitem.cpp @@ -55,7 +55,7 @@ namespace DetourNavigator if (dtStatusSucceed(addStatus)) { mUsedTiles[position] = std::make_pair(std::move(cached), std::move(navMeshData)); - ++mNavMeshRevision; + ++mVersion.mRevision; return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult(); } else @@ -63,7 +63,7 @@ namespace DetourNavigator if (removed) { mUsedTiles.erase(position); - ++mNavMeshRevision; + ++mVersion.mRevision; } return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult(); } @@ -75,7 +75,7 @@ namespace DetourNavigator if (removed) { mUsedTiles.erase(position); - ++mNavMeshRevision; + ++mVersion.mRevision; } return UpdateNavMeshStatusBuilder().removed(removed).getResult(); } diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index ac68caedb3..6fc9b17189 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -6,6 +6,7 @@ #include "navmeshtilescache.hpp" #include "dtstatus.hpp" #include "navmeshdata.hpp" +#include "version.hpp" #include @@ -127,7 +128,8 @@ namespace DetourNavigator { public: NavMeshCacheItem(const NavMeshPtr& impl, std::size_t generation) - : mImpl(impl), mGeneration(generation), mNavMeshRevision(0) + : mImpl(impl) + , mVersion {generation, 0} { } @@ -136,15 +138,7 @@ namespace DetourNavigator return *mImpl; } - std::size_t getGeneration() const - { - return mGeneration; - } - - std::size_t getNavMeshRevision() const - { - return mNavMeshRevision; - } + const Version& getVersion() const { return mVersion; } UpdateNavMeshStatus updateTile(const TilePosition& position, NavMeshTilesCache::Value&& cached, NavMeshData&& navMeshData); @@ -153,8 +147,7 @@ namespace DetourNavigator private: NavMeshPtr mImpl; - std::size_t mGeneration; - std::size_t mNavMeshRevision; + Version mVersion; std::map> mUsedTiles; }; diff --git a/components/detournavigator/version.hpp b/components/detournavigator/version.hpp index c9de98459d..792680a7d5 100644 --- a/components/detournavigator/version.hpp +++ b/components/detournavigator/version.hpp @@ -8,12 +8,32 @@ namespace DetourNavigator { struct Version { - std::size_t mGeneration; - std::size_t mRevision; + std::size_t mGeneration = 0; + std::size_t mRevision = 0; + + friend inline auto tie(const Version& value) + { + return std::tie(value.mGeneration, value.mRevision); + } friend inline bool operator<(const Version& lhs, const Version& rhs) { - return std::tie(lhs.mGeneration, lhs.mRevision) < std::tie(rhs.mGeneration, rhs.mRevision); + return tie(lhs) < tie(rhs); + } + + friend inline bool operator<=(const Version& lhs, const Version& rhs) + { + return tie(lhs) <= tie(rhs); + } + + friend inline bool operator==(const Version& lhs, const Version& rhs) + { + return tie(lhs) == tie(rhs); + } + + friend inline bool operator!=(const Version& lhs, const Version& rhs) + { + return !(lhs == rhs); } }; } From 66390bd8a4e4a7d9deee5c8ce99f7a7d3b5e2d9c Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 1 Nov 2021 20:54:10 +0100 Subject: [PATCH 1760/2859] Use line width 1 as the only guaranteed to be supported value by glLineWidth --- components/sceneutil/detourdebugdraw.cpp | 5 +++-- components/sceneutil/detourdebugdraw.hpp | 2 +- components/sceneutil/navmesh.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/detourdebugdraw.cpp b/components/sceneutil/detourdebugdraw.cpp index 7ef329fc16..0b374846d1 100644 --- a/components/sceneutil/detourdebugdraw.cpp +++ b/components/sceneutil/detourdebugdraw.cpp @@ -56,7 +56,7 @@ namespace SceneUtil mMode = mode; mVertices = new osg::Vec3Array; mColors = new osg::Vec4Array; - mSize = size * mRecastInvertedScaleFactor; + mSize = size; } void DebugDraw::begin(duDebugDrawPrimitives prim, float size) @@ -93,7 +93,8 @@ namespace SceneUtil stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateSet->setMode(GL_DEPTH, (mDepthMask ? osg::StateAttribute::ON : osg::StateAttribute::OFF)); stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - stateSet->setAttributeAndModes(new osg::LineWidth(mSize)); + // TODO: mSize has to be used for the line width, but not for glLineWidth because of the spec limitations + stateSet->setAttributeAndModes(new osg::LineWidth()); stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); osg::ref_ptr geometry(new osg::Geometry); diff --git a/components/sceneutil/detourdebugdraw.hpp b/components/sceneutil/detourdebugdraw.hpp index 9b6a28acea..79975cacbc 100644 --- a/components/sceneutil/detourdebugdraw.hpp +++ b/components/sceneutil/detourdebugdraw.hpp @@ -15,7 +15,7 @@ namespace SceneUtil class DebugDraw : public duDebugDraw { public: - DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor); + explicit DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor); void depthMask(bool state) override; diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp index b0f356f089..b0ce3499e7 100644 --- a/components/sceneutil/navmesh.cpp +++ b/components/sceneutil/navmesh.cpp @@ -12,7 +12,7 @@ namespace SceneUtil { osg::ref_ptr createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings) { - const osg::ref_ptr group(new osg::Group); + osg::ref_ptr group(new osg::Group); DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 10), 1.0f / settings.mRecastScaleFactor); dtNavMeshQuery navMeshQuery; navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); From 3c41d0efc31af7fca2c4af048ad5386251f0c065 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 1 Nov 2021 20:05:34 +0100 Subject: [PATCH 1761/2859] Render each navmesh tile independently --- apps/openmw/mwrender/navmesh.cpp | 66 +++-- apps/openmw/mwrender/navmesh.hpp | 13 +- apps/openmw/mwrender/renderingmanager.cpp | 3 +- .../detournavigator/navmeshcacheitem.cpp | 27 ++- .../detournavigator/navmeshcacheitem.hpp | 19 +- components/sceneutil/navmesh.cpp | 225 +++++++++++++++++- components/sceneutil/navmesh.hpp | 4 +- 7 files changed, 320 insertions(+), 37 deletions(-) diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 7cf045d0bd..d4a42fa37e 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -37,45 +38,70 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const dtNavMesh& navMesh, std::size_t id, const DetourNavigator::Version& version, + void NavMesh::update(const DetourNavigator::NavMeshCacheItem& navMesh, std::size_t id, const DetourNavigator::Settings& settings) { - if (!mEnabled || (mGroup && mId == id && mVersion == version)) + using DetourNavigator::TilePosition; + using DetourNavigator::Version; + + if (!mEnabled || (!mTiles.empty() && mId == id && mVersion == navMesh.getVersion())) return; - mId = id; - mVersion = version; - if (mGroup) - mRootNode->removeChild(mGroup); - mGroup = SceneUtil::createNavMeshGroup(navMesh, settings); - if (mGroup) + if (mId != id) + { + reset(); + mId = id; + } + + mVersion = navMesh.getVersion(); + + std::vector updated; + navMesh.forEachUsedTile([&] (const TilePosition& position, const Version& version, const dtMeshTile& meshTile) + { + updated.push_back(position); + Tile& tile = mTiles[position]; + if (tile.mGroup != nullptr && tile.mVersion == version) + return; + if (tile.mGroup != nullptr) + mRootNode->removeChild(tile.mGroup); + tile.mGroup = SceneUtil::createNavMeshTileGroup(navMesh.getImpl(), meshTile, settings); + if (tile.mGroup == nullptr) + return; + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(tile.mGroup, "debug"); + tile.mGroup->setNodeMask(Mask_Debug); + mRootNode->addChild(tile.mGroup); + }); + std::sort(updated.begin(), updated.end()); + for (auto it = mTiles.begin(); it != mTiles.end();) { - MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(mGroup, "debug"); - mGroup->setNodeMask(Mask_Debug); - mRootNode->addChild(mGroup); + if (!std::binary_search(updated.begin(), updated.end(), it->first)) + { + mRootNode->removeChild(it->second.mGroup); + it = mTiles.erase(it); + } + else + ++it; } } void NavMesh::reset() { - if (mGroup) - { - mRootNode->removeChild(mGroup); - mGroup = nullptr; - } + for (auto& [position, tile] : mTiles) + mRootNode->removeChild(tile.mGroup); + mTiles.clear(); } void NavMesh::enable() { - if (mGroup) - mRootNode->addChild(mGroup); + for (const auto& [position, tile] : mTiles) + mRootNode->addChild(tile.mGroup); mEnabled = true; } void NavMesh::disable() { - if (mGroup) - mRootNode->removeChild(mGroup); + for (const auto& [position, tile] : mTiles) + mRootNode->removeChild(tile.mGroup); mEnabled = false; } } diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index c3aa31e05b..cdfa05d9e8 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -2,10 +2,12 @@ #define OPENMW_MWRENDER_NAVMESH_H #include +#include #include #include +#include class dtNavMesh; @@ -17,6 +19,7 @@ namespace osg namespace DetourNavigator { + class NavMeshCacheItem; struct Settings; } @@ -30,7 +33,7 @@ namespace MWRender bool toggle(); - void update(const dtNavMesh& navMesh, std::size_t id, const DetourNavigator::Version& version, + void update(const DetourNavigator::NavMeshCacheItem& navMesh, std::size_t id, const DetourNavigator::Settings& settings); void reset(); @@ -45,11 +48,17 @@ namespace MWRender } private: + struct Tile + { + DetourNavigator::Version mVersion; + osg::ref_ptr mGroup; + }; + osg::ref_ptr mRootNode; bool mEnabled; std::size_t mId; DetourNavigator::Version mVersion; - osg::ref_ptr mGroup; + std::map mTiles; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 58a1383c7c..e543709303 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1365,8 +1365,7 @@ namespace MWRender { try { - const auto locked = it->second->lockConst(); - mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getVersion(), mNavigator.getSettings()); + mNavMesh->update(*it->second->lockConst(), mNavMeshNumber, mNavigator.getSettings()); } catch (const std::exception& e) { diff --git a/components/detournavigator/navmeshcacheitem.cpp b/components/detournavigator/navmeshcacheitem.cpp index 889b764c3c..decf45de2c 100644 --- a/components/detournavigator/navmeshcacheitem.cpp +++ b/components/detournavigator/navmeshcacheitem.cpp @@ -13,12 +13,6 @@ namespace { using DetourNavigator::TilePosition; - const dtMeshTile* getTile(const dtNavMesh& navMesh, const TilePosition& position) - { - const int layer = 0; - return navMesh.getTileAt(position.x(), position.y(), layer); - } - bool removeTile(dtNavMesh& navMesh, const TilePosition& position) { const int layer = 0; @@ -41,10 +35,16 @@ namespace namespace DetourNavigator { + const dtMeshTile* getTile(const dtNavMesh& navMesh, const TilePosition& position) + { + const int layer = 0; + return navMesh.getTileAt(position.x(), position.y(), layer); + } + UpdateNavMeshStatus NavMeshCacheItem::updateTile(const TilePosition& position, NavMeshTilesCache::Value&& cached, NavMeshData&& navMeshData) { - const dtMeshTile* currentTile = ::getTile(*mImpl, position); + const dtMeshTile* currentTile = getTile(*mImpl, position); if (currentTile != nullptr && asNavMeshTileConstView(*currentTile) == asNavMeshTileConstView(navMeshData.mValue.get())) { @@ -54,7 +54,18 @@ namespace DetourNavigator const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize); if (dtStatusSucceed(addStatus)) { - mUsedTiles[position] = std::make_pair(std::move(cached), std::move(navMeshData)); + auto tile = mUsedTiles.find(position); + if (tile == mUsedTiles.end()) + { + mUsedTiles.emplace_hint(tile, position, + Tile {Version {mVersion.mRevision, 1}, std::move(cached), std::move(navMeshData)}); + } + else + { + ++tile->second.mVersion.mRevision; + tile->second.mCached = std::move(cached); + tile->second.mData = std::move(navMeshData); + } ++mVersion.mRevision; return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult(); } diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index 6fc9b17189..5d3b404080 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -124,6 +124,8 @@ namespace DetourNavigator } }; + const dtMeshTile* getTile(const dtNavMesh& navMesh, const TilePosition& position); + class NavMeshCacheItem { public: @@ -145,10 +147,25 @@ namespace DetourNavigator UpdateNavMeshStatus removeTile(const TilePosition& position); + template + void forEachUsedTile(Function&& function) const + { + for (const auto& [position, tile] : mUsedTiles) + if (const dtMeshTile* meshTile = getTile(*mImpl, position)) + function(position, tile.mVersion, *meshTile); + } + private: + struct Tile + { + Version mVersion; + NavMeshTilesCache::Value mCached; + NavMeshData mData; + }; + NavMeshPtr mImpl; Version mVersion; - std::map> mUsedTiles; + std::map mUsedTiles; }; using GuardedNavMeshCacheItem = Misc::ScopeGuarded; diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp index b0ce3499e7..01f1d52a47 100644 --- a/components/sceneutil/navmesh.cpp +++ b/components/sceneutil/navmesh.cpp @@ -8,16 +8,235 @@ #include #include +namespace +{ + // Copied from https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/DebugUtils/Source/DetourDebugDraw.cpp#L26-L38 + float distancePtLine2d(const float* pt, const float* p, const float* q) + { + float pqx = q[0] - p[0]; + float pqz = q[2] - p[2]; + float dx = pt[0] - p[0]; + float dz = pt[2] - p[2]; + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + if (d != 0) t /= d; + dx = p[0] + t*pqx - pt[0]; + dz = p[2] + t*pqz - pt[2]; + return dx*dx + dz*dz; + } + + // Copied from https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/DebugUtils/Source/DetourDebugDraw.cpp#L40-L118 + void drawPolyBoundaries(duDebugDraw* dd, const dtMeshTile* tile, const unsigned int col, + const float linew, bool inner) + { + static const float thr = 0.01f*0.01f; + + dd->begin(DU_DRAW_LINES, linew); + + for (int i = 0; i < tile->header->polyCount; ++i) + { + const dtPoly* p = &tile->polys[i]; + + if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) continue; + + const dtPolyDetail* pd = &tile->detailMeshes[i]; + + for (int j = 0, nj = (int)p->vertCount; j < nj; ++j) + { + unsigned int c = col; + if (inner) + { + if (p->neis[j] == 0) continue; + if (p->neis[j] & DT_EXT_LINK) + { + bool con = false; + for (unsigned int k = p->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) + { + if (tile->links[k].edge == j) + { + con = true; + break; + } + } + if (con) + c = duRGBA(255,255,255,48); + else + c = duRGBA(0,0,0,48); + } + else + c = duRGBA(0,48,64,32); + } + else + { + if (p->neis[j] != 0) continue; + } + + const float* v0 = &tile->verts[p->verts[j]*3]; + const float* v1 = &tile->verts[p->verts[(j+1) % nj]*3]; + + // Draw detail mesh edges which align with the actual poly edge. + // This is really slow. + for (int k = 0; k < pd->triCount; ++k) + { + const unsigned char* t = &tile->detailTris[(pd->triBase+k)*4]; + const float* tv[3]; + for (int m = 0; m < 3; ++m) + { + if (t[m] < p->vertCount) + tv[m] = &tile->verts[p->verts[t[m]]*3]; + else + tv[m] = &tile->detailVerts[(pd->vertBase+(t[m]-p->vertCount))*3]; + } + for (int m = 0, n = 2; m < 3; n=m++) + { + if ((dtGetDetailTriEdgeFlags(t[3], n) & DT_DETAIL_EDGE_BOUNDARY) == 0) + continue; + + if (distancePtLine2d(tv[n],v0,v1) < thr && + distancePtLine2d(tv[m],v0,v1) < thr) + { + dd->vertex(tv[n], c); + dd->vertex(tv[m], c); + } + } + } + } + } + dd->end(); + } + + // Copied from https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/DebugUtils/Source/DetourDebugDraw.cpp#L120-L235 + void drawMeshTile(duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery* query, + const dtMeshTile* tile, unsigned char flags) + { + dtPolyRef base = mesh.getPolyRefBase(tile); + + int tileNum = mesh.decodePolyIdTile(base); + const unsigned int tileColor = duIntToCol(tileNum, 128); + + dd->depthMask(false); + + dd->begin(DU_DRAW_TRIS); + for (int i = 0; i < tile->header->polyCount; ++i) + { + const dtPoly* p = &tile->polys[i]; + if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) // Skip off-mesh links. + continue; + + const dtPolyDetail* pd = &tile->detailMeshes[i]; + + unsigned int col; + if (query && query->isInClosedList(base | (dtPolyRef)i)) + col = duRGBA(255,196,0,64); + else + { + if (flags & DU_DRAWNAVMESH_COLOR_TILES) + col = tileColor; + else + col = duTransCol(dd->areaToCol(p->getArea()), 64); + } + + for (int j = 0; j < pd->triCount; ++j) + { + const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; + for (int k = 0; k < 3; ++k) + { + if (t[k] < p->vertCount) + dd->vertex(&tile->verts[p->verts[t[k]]*3], col); + else + dd->vertex(&tile->detailVerts[(pd->vertBase+t[k]-p->vertCount)*3], col); + } + } + } + dd->end(); + + // Draw inter poly boundaries + drawPolyBoundaries(dd, tile, duRGBA(0,48,64,32), 1.5f, true); + + // Draw outer poly boundaries + drawPolyBoundaries(dd, tile, duRGBA(0,48,64,220), 2.5f, false); + + if (flags & DU_DRAWNAVMESH_OFFMESHCONS) + { + dd->begin(DU_DRAW_LINES, 2.0f); + for (int i = 0; i < tile->header->polyCount; ++i) + { + const dtPoly* p = &tile->polys[i]; + if (p->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) // Skip regular polys. + continue; + + unsigned int col, col2; + if (query && query->isInClosedList(base | (dtPolyRef)i)) + col = duRGBA(255,196,0,220); + else + col = duDarkenCol(duTransCol(dd->areaToCol(p->getArea()), 220)); + + const dtOffMeshConnection* con = &tile->offMeshCons[i - tile->header->offMeshBase]; + const float* va = &tile->verts[p->verts[0]*3]; + const float* vb = &tile->verts[p->verts[1]*3]; + + // Check to see if start and end end-points have links. + bool startSet = false; + bool endSet = false; + for (unsigned int k = p->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) + { + if (tile->links[k].edge == 0) + startSet = true; + if (tile->links[k].edge == 1) + endSet = true; + } + + // End points and their on-mesh locations. + dd->vertex(va[0],va[1],va[2], col); + dd->vertex(con->pos[0],con->pos[1],con->pos[2], col); + col2 = startSet ? col : duRGBA(220,32,16,196); + duAppendCircle(dd, con->pos[0],con->pos[1]+0.1f,con->pos[2], con->rad, col2); + + dd->vertex(vb[0],vb[1],vb[2], col); + dd->vertex(con->pos[3],con->pos[4],con->pos[5], col); + col2 = endSet ? col : duRGBA(220,32,16,196); + duAppendCircle(dd, con->pos[3],con->pos[4]+0.1f,con->pos[5], con->rad, col2); + + // End point vertices. + dd->vertex(con->pos[0],con->pos[1],con->pos[2], duRGBA(0,48,64,196)); + dd->vertex(con->pos[0],con->pos[1]+0.2f,con->pos[2], duRGBA(0,48,64,196)); + + dd->vertex(con->pos[3],con->pos[4],con->pos[5], duRGBA(0,48,64,196)); + dd->vertex(con->pos[3],con->pos[4]+0.2f,con->pos[5], duRGBA(0,48,64,196)); + + // Connection arc. + duAppendArc(dd, con->pos[0],con->pos[1],con->pos[2], con->pos[3],con->pos[4],con->pos[5], 0.25f, + (con->flags & 1) ? 0.6f : 0, 0.6f, col); + } + dd->end(); + } + + const unsigned int vcol = duRGBA(0,0,0,196); + dd->begin(DU_DRAW_POINTS, 3.0f); + for (int i = 0; i < tile->header->vertCount; ++i) + { + const float* v = &tile->verts[i*3]; + dd->vertex(v[0], v[1], v[2], vcol); + } + dd->end(); + + dd->depthMask(true); + } +} + namespace SceneUtil { - osg::ref_ptr createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings) + osg::ref_ptr createNavMeshTileGroup(const dtNavMesh& navMesh, const dtMeshTile& meshTile, + const DetourNavigator::Settings& settings) { + if (meshTile.header == nullptr) + return nullptr; + osg::ref_ptr group(new osg::Group); DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 10), 1.0f / settings.mRecastScaleFactor); dtNavMeshQuery navMeshQuery; navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); - duDebugDrawNavMeshWithClosedList(&debugDraw, navMesh, navMeshQuery, - DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); + drawMeshTile(&debugDraw, navMesh, &navMeshQuery, &meshTile, DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); diff --git a/components/sceneutil/navmesh.hpp b/components/sceneutil/navmesh.hpp index b255b05756..acfb583eba 100644 --- a/components/sceneutil/navmesh.hpp +++ b/components/sceneutil/navmesh.hpp @@ -4,6 +4,7 @@ #include class dtNavMesh; +struct dtMeshTile; namespace osg { @@ -17,7 +18,8 @@ namespace DetourNavigator namespace SceneUtil { - osg::ref_ptr createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings); + osg::ref_ptr createNavMeshTileGroup(const dtNavMesh& navMesh, const dtMeshTile& meshTile, + const DetourNavigator::Settings& settings); } #endif From 0511a81baaf1fbe9969a88897d870ac28c00dfe0 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 1 Nov 2021 21:00:40 +0100 Subject: [PATCH 1762/2859] Use different alpha color for navmesh tiles with non zero user id --- components/sceneutil/navmesh.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp index 01f1d52a47..891f1350a1 100644 --- a/components/sceneutil/navmesh.cpp +++ b/components/sceneutil/navmesh.cpp @@ -105,14 +105,15 @@ namespace dd->end(); } - // Copied from https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/DebugUtils/Source/DetourDebugDraw.cpp#L120-L235 + // Based on https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/DebugUtils/Source/DetourDebugDraw.cpp#L120-L235 void drawMeshTile(duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery* query, const dtMeshTile* tile, unsigned char flags) { dtPolyRef base = mesh.getPolyRefBase(tile); int tileNum = mesh.decodePolyIdTile(base); - const unsigned int tileColor = duIntToCol(tileNum, 128); + const unsigned int tileNumColor = duIntToCol(tileNum, 128); + const unsigned alpha = tile->header->userId == 0 ? 64 : 128; dd->depthMask(false); @@ -127,13 +128,13 @@ namespace unsigned int col; if (query && query->isInClosedList(base | (dtPolyRef)i)) - col = duRGBA(255,196,0,64); + col = duRGBA(255, 196, 0, alpha); else { if (flags & DU_DRAWNAVMESH_COLOR_TILES) - col = tileColor; + col = duTransCol(tileNumColor, alpha); else - col = duTransCol(dd->areaToCol(p->getArea()), 64); + col = duTransCol(dd->areaToCol(p->getArea()), alpha); } for (int j = 0; j < pd->triCount; ++j) From d1a1b8c01ca6dedff239eaf0b6dd7270bcf28dab Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 1 Nov 2021 21:52:33 +0100 Subject: [PATCH 1763/2859] Use polygon offset to render navmesh and recast mesh --- components/sceneutil/navmesh.cpp | 11 ++++++++++- components/sceneutil/recastmesh.cpp | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp index 891f1350a1..3f91d41d51 100644 --- a/components/sceneutil/navmesh.cpp +++ b/components/sceneutil/navmesh.cpp @@ -1,5 +1,6 @@ #include "navmesh.hpp" #include "detourdebugdraw.hpp" +#include "depth.hpp" #include @@ -7,6 +8,7 @@ #include #include +#include namespace { @@ -241,7 +243,14 @@ namespace SceneUtil osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - group->getOrCreateStateSet()->setAttribute(material); + + const float polygonOffsetFactor = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; + const float polygonOffsetUnits = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; + osg::ref_ptr polygonOffset = new osg::PolygonOffset(polygonOffsetFactor, polygonOffsetUnits); + + osg::ref_ptr stateSet = group->getOrCreateStateSet(); + stateSet->setAttribute(material); + stateSet->setAttributeAndModes(polygonOffset); return group; } diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index 5732f86959..ed40bfedf7 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -1,5 +1,6 @@ #include "recastmesh.hpp" #include "detourdebugdraw.hpp" +#include "depth.hpp" #include #include @@ -9,6 +10,7 @@ #include #include +#include #include #include @@ -69,7 +71,14 @@ namespace SceneUtil osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - group->getOrCreateStateSet()->setAttribute(material); + + const float polygonOffsetFactor = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; + const float polygonOffsetUnits = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; + osg::ref_ptr polygonOffset = new osg::PolygonOffset(polygonOffsetFactor, polygonOffsetUnits); + + osg::ref_ptr stateSet = group->getOrCreateStateSet(); + stateSet->setAttribute(material); + stateSet->setAttributeAndModes(polygonOffset); return group; } From cffcb6a8974c1e4e180b35652f8d76a04b5d0d11 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 2 Nov 2021 02:43:09 +0100 Subject: [PATCH 1764/2859] Share state set between all navmesh tiles Do not change GL_DEPTH because it's always disabled anyway. --- apps/openmw/mwrender/navmesh.cpp | 7 +++++- apps/openmw/mwrender/navmesh.hpp | 3 +++ components/sceneutil/agentpath.cpp | 2 +- components/sceneutil/detourdebugdraw.cpp | 31 +++++++++++++----------- components/sceneutil/detourdebugdraw.hpp | 7 ++++-- components/sceneutil/navmesh.cpp | 31 +++++++++++++++--------- components/sceneutil/navmesh.hpp | 6 ++++- components/sceneutil/recastmesh.cpp | 2 +- 8 files changed, 57 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index d4a42fa37e..a3c26aeb59 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -5,8 +5,10 @@ #include #include #include +#include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -17,6 +19,8 @@ namespace MWRender { NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) : mRootNode(root) + , mGroupStateSet(SceneUtil::makeNavMeshTileStateSet()) + , mDebugDrawStateSet(SceneUtil::DebugDraw::makeStateSet()) , mEnabled(enabled) , mId(std::numeric_limits::max()) { @@ -64,7 +68,8 @@ namespace MWRender return; if (tile.mGroup != nullptr) mRootNode->removeChild(tile.mGroup); - tile.mGroup = SceneUtil::createNavMeshTileGroup(navMesh.getImpl(), meshTile, settings); + tile.mGroup = SceneUtil::createNavMeshTileGroup(navMesh.getImpl(), meshTile, settings, + mGroupStateSet, mDebugDrawStateSet); if (tile.mGroup == nullptr) return; MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(tile.mGroup, "debug"); diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index cdfa05d9e8..fd69a3e487 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -15,6 +15,7 @@ namespace osg { class Group; class Geometry; + class StateSet; } namespace DetourNavigator @@ -55,6 +56,8 @@ namespace MWRender }; osg::ref_ptr mRootNode; + osg::ref_ptr mGroupStateSet; + osg::ref_ptr mDebugDrawStateSet; bool mEnabled; std::size_t mId; DetourNavigator::Version mVersion; diff --git a/components/sceneutil/agentpath.cpp b/components/sceneutil/agentpath.cpp index 5f9b574e7d..5721110f77 100644 --- a/components/sceneutil/agentpath.cpp +++ b/components/sceneutil/agentpath.cpp @@ -43,7 +43,7 @@ namespace SceneUtil const osg::ref_ptr group(new osg::Group); - DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 0), 1); + DebugDraw debugDraw(*group, DebugDraw::makeStateSet(), osg::Vec3f(0, 0, 0), 1); const auto agentRadius = halfExtents.x(); const auto agentHeight = 2.0f * halfExtents.z(); diff --git a/components/sceneutil/detourdebugdraw.cpp b/components/sceneutil/detourdebugdraw.cpp index 0b374846d1..f1325340ea 100644 --- a/components/sceneutil/detourdebugdraw.cpp +++ b/components/sceneutil/detourdebugdraw.cpp @@ -32,19 +32,19 @@ namespace namespace SceneUtil { - DebugDraw::DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor) + DebugDraw::DebugDraw(osg::Group& group, const osg::ref_ptr& stateSet, + const osg::Vec3f& shift, float recastInvertedScaleFactor) : mGroup(group) + , mStateSet(stateSet) , mShift(shift) , mRecastInvertedScaleFactor(recastInvertedScaleFactor) - , mDepthMask(false) , mMode(osg::PrimitiveSet::POINTS) , mSize(1.0f) { } - void DebugDraw::depthMask(bool state) + void DebugDraw::depthMask(bool) { - mDepthMask = state; } void DebugDraw::texture(bool) @@ -88,17 +88,8 @@ namespace SceneUtil void DebugDraw::end() { - osg::ref_ptr stateSet(new osg::StateSet); - stateSet->setMode(GL_BLEND, osg::StateAttribute::ON); - stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateSet->setMode(GL_DEPTH, (mDepthMask ? osg::StateAttribute::ON : osg::StateAttribute::OFF)); - stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - // TODO: mSize has to be used for the line width, but not for glLineWidth because of the spec limitations - stateSet->setAttributeAndModes(new osg::LineWidth()); - stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - osg::ref_ptr geometry(new osg::Geometry); - geometry->setStateSet(stateSet); + geometry->setStateSet(mStateSet); geometry->setVertexArray(mVertices); geometry->setColorArray(mColors, osg::Array::BIND_PER_VERTEX); geometry->addPrimitiveSet(new osg::DrawArrays(mMode, 0, static_cast(mVertices->size()))); @@ -118,4 +109,16 @@ namespace SceneUtil { mColors->push_back(value); } + + osg::ref_ptr DebugDraw::makeStateSet() + { + osg::ref_ptr stateSet = new osg::StateSet; + stateSet->setMode(GL_BLEND, osg::StateAttribute::ON); + stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateSet->setMode(GL_DEPTH, osg::StateAttribute::OFF); + stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateSet->setAttributeAndModes(new osg::LineWidth()); + stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + return stateSet; + } } diff --git a/components/sceneutil/detourdebugdraw.hpp b/components/sceneutil/detourdebugdraw.hpp index 79975cacbc..1d00735ea0 100644 --- a/components/sceneutil/detourdebugdraw.hpp +++ b/components/sceneutil/detourdebugdraw.hpp @@ -15,7 +15,10 @@ namespace SceneUtil class DebugDraw : public duDebugDraw { public: - explicit DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor); + explicit DebugDraw(osg::Group& group, const osg::ref_ptr& stateSet, + const osg::Vec3f& shift, float recastInvertedScaleFactor); + + static osg::ref_ptr makeStateSet(); void depthMask(bool state) override; @@ -38,9 +41,9 @@ namespace SceneUtil private: osg::Group& mGroup; + osg::ref_ptr mStateSet; osg::Vec3f mShift; float mRecastInvertedScaleFactor; - bool mDepthMask; osg::PrimitiveSet::Mode mMode; float mSize; osg::ref_ptr mVertices; diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp index 3f91d41d51..d66f95381c 100644 --- a/components/sceneutil/navmesh.cpp +++ b/components/sceneutil/navmesh.cpp @@ -229,18 +229,8 @@ namespace namespace SceneUtil { - osg::ref_ptr createNavMeshTileGroup(const dtNavMesh& navMesh, const dtMeshTile& meshTile, - const DetourNavigator::Settings& settings) + osg::ref_ptr makeNavMeshTileStateSet() { - if (meshTile.header == nullptr) - return nullptr; - - osg::ref_ptr group(new osg::Group); - DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 10), 1.0f / settings.mRecastScaleFactor); - dtNavMeshQuery navMeshQuery; - navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); - drawMeshTile(&debugDraw, navMesh, &navMeshQuery, &meshTile, DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); - osg::ref_ptr material = new osg::Material; material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); @@ -248,9 +238,26 @@ namespace SceneUtil const float polygonOffsetUnits = SceneUtil::AutoDepth::isReversed() ? 1.0 : -1.0; osg::ref_ptr polygonOffset = new osg::PolygonOffset(polygonOffsetFactor, polygonOffsetUnits); - osg::ref_ptr stateSet = group->getOrCreateStateSet(); + osg::ref_ptr stateSet = new osg::StateSet; stateSet->setAttribute(material); stateSet->setAttributeAndModes(polygonOffset); + return stateSet; + } + + osg::ref_ptr createNavMeshTileGroup(const dtNavMesh& navMesh, const dtMeshTile& meshTile, + const DetourNavigator::Settings& settings, const osg::ref_ptr& groupStateSet, + const osg::ref_ptr& debugDrawStateSet) + { + if (meshTile.header == nullptr) + return nullptr; + + osg::ref_ptr group(new osg::Group); + group->setStateSet(groupStateSet); + constexpr float shift = 10.0f; + DebugDraw debugDraw(*group, debugDrawStateSet, osg::Vec3f(0, 0, shift), 1.0f / settings.mRecastScaleFactor); + dtNavMeshQuery navMeshQuery; + navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); + drawMeshTile(&debugDraw, navMesh, &navMeshQuery, &meshTile, DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); return group; } diff --git a/components/sceneutil/navmesh.hpp b/components/sceneutil/navmesh.hpp index acfb583eba..ca9b8bd570 100644 --- a/components/sceneutil/navmesh.hpp +++ b/components/sceneutil/navmesh.hpp @@ -9,6 +9,7 @@ struct dtMeshTile; namespace osg { class Group; + class StateSet; } namespace DetourNavigator @@ -18,8 +19,11 @@ namespace DetourNavigator namespace SceneUtil { + osg::ref_ptr makeNavMeshTileStateSet(); + osg::ref_ptr createNavMeshTileGroup(const dtNavMesh& navMesh, const dtMeshTile& meshTile, - const DetourNavigator::Settings& settings); + const DetourNavigator::Settings& settings, const osg::ref_ptr& groupStateSet, + const osg::ref_ptr& debugDrawStateSet); } #endif diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index ed40bfedf7..9614673a64 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -47,7 +47,7 @@ namespace SceneUtil using namespace DetourNavigator; const osg::ref_ptr group(new osg::Group); - DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 0), 1.0f); + DebugDraw debugDraw(*group, DebugDraw::makeStateSet(), osg::Vec3f(0, 0, 0), 1.0f); const DetourNavigator::Mesh& mesh = recastMesh.getMesh(); std::vector indices = mesh.getIndices(); std::vector vertices = mesh.getVertices(); From 84d6de3eba16c79ae814d8359c2d0b299f6649ba Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 21 Nov 2021 19:51:02 +0000 Subject: [PATCH 1765/2859] Parse paths with boost rules when it's quoted, but use the string verbatim when it's not --- apps/opencs/editor.cpp | 12 +-- apps/openmw/main.cpp | 12 +-- apps/openmw/options.cpp | 8 +- apps/openmw_test_suite/mwworld/test_store.cpp | 8 +- apps/openmw_test_suite/openmw/options.cpp | 75 +++++++++---------- components/files/configurationmanager.cpp | 31 +++++--- components/files/configurationmanager.hpp | 8 +- 7 files changed, 81 insertions(+), 73 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 6294e5eb7c..ac0a7a5ef7 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -88,11 +88,11 @@ std::pair > CS::Editor::readConfi boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::ReluctantPathContainer(), "data")->multitoken()->composing()) - ("data-local", boost::program_options::value()->default_value(Files::ReluctantPathContainer::value_type(), "")) + ("data", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing()) + ("data-local", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), "")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) ("encoding", boost::program_options::value()->default_value("win1252")) - ("resources", boost::program_options::value()->default_value(Files::ReluctantPath(), "resources")) + ("resources", boost::program_options::value()->default_value(Files::MaybeQuotedPath(), "resources")) ("fallback-archive", boost::program_options::value>()-> default_value(std::vector(), "fallback-archive")->multitoken()) ("fallback", boost::program_options::value()->default_value(FallbackMap(), "") @@ -112,7 +112,7 @@ std::pair > CS::Editor::readConfi mDocumentManager.setEncoding(ToUTF8::calculateEncoding(mEncodingName)); mFileDialog.setEncoding (QString::fromUtf8(mEncodingName.c_str())); - mDocumentManager.setResourceDir (mResources = variables["resources"].as()); + mDocumentManager.setResourceDir (mResources = variables["resources"].as()); if (variables["script-blacklist-use"].as()) mDocumentManager.setBlacklistedScripts ( @@ -122,10 +122,10 @@ std::pair > CS::Editor::readConfi Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = asPathContainer(variables["data"].as()); + dataDirs = asPathContainer(variables["data"].as()); } - Files::PathContainer::value_type local(variables["data-local"].as()); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) dataLocal.push_back(local); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 50a7c3d844..d502ecbc05 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -56,7 +56,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat { cfgMgr.readConfiguration(variables, desc, true); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); getRawStdout() << v.describe() << std::endl; return false; } @@ -65,7 +65,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc); Files::mergeComposingVariables(variables, composingVariables, desc); - Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); Log(Debug::Info) << v.describe(); engine.setGrabMouse(!variables["no-grab"].as()); @@ -78,15 +78,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // directory settings engine.enableFSStrict(variables["fs-strict"].as()); - Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); + Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); - Files::PathContainer::value_type local(variables["data-local"].as()); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) dataDirs.push_back(local); cfgMgr.processPaths(dataDirs); - engine.setResourceDir(variables["resources"].as()); + engine.setResourceDir(variables["resources"].as()); engine.setDataDirs(dataDirs); // fallback archives @@ -141,7 +141,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setWarningsMode (variables["script-warn"].as()); engine.setScriptBlacklist (variables["script-blacklist"].as()); engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); - engine.setSaveGameFile (variables["load-savegame"].as().string()); + engine.setSaveGameFile (variables["load-savegame"].as().string()); // other settings Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp index a1b2102f08..f7d20b4dd7 100644 --- a/apps/openmw/options.cpp +++ b/apps/openmw/options.cpp @@ -25,16 +25,16 @@ namespace OpenMW ("replace", bpo::value()->default_value(StringsVector(), "") ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") - ("data", bpo::value()->default_value(Files::ReluctantPathContainer(), "data") + ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") - ("data-local", bpo::value()->default_value(Files::ReluctantPathContainer::value_type(), ""), + ("data-local", bpo::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""), "set local data directory (highest priority)") ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") - ("resources", bpo::value()->default_value(Files::ReluctantPath(), "resources"), + ("resources", bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), "set resources directory") ("start", bpo::value()->default_value(""), @@ -77,7 +77,7 @@ namespace OpenMW ("script-blacklist-use", bpo::value()->implicit_value(true) ->default_value(true), "enable script blacklisting") - ("load-savegame", bpo::value()->default_value(Files::ReluctantPath(), ""), + ("load-savegame", bpo::value()->default_value(Files::MaybeQuotedPath(), ""), "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") ("skip-menu", bpo::value()->implicit_value(true) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index f2f9459df0..71b1fb0ff0 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -58,10 +58,10 @@ struct ContentFileTest : public ::testing::Test boost::program_options::options_description desc("Allowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::ReluctantPathContainer(), "data")->multitoken()->composing()) + ("data", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer(), "data")->multitoken()->composing()) ("content", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") - ("data-local", boost::program_options::value()->default_value(Files::ReluctantPathContainer::value_type(), "")); + ("data-local", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), "")); boost::program_options::notify(variables); @@ -69,10 +69,10 @@ struct ContentFileTest : public ::testing::Test Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = asPathContainer(variables["data"].as()); + dataDirs = asPathContainer(variables["data"].as()); } - Files::PathContainer::value_type local(variables["data-local"].as()); + Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) { dataLocal.push_back(local); } diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index bae324b134..9b8535ea36 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -40,7 +40,7 @@ namespace const std::array arguments {"openmw", "--load-savegame=save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_single_word_load_savegame_path) @@ -49,7 +49,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_multi_component_load_savegame_path) @@ -58,7 +58,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "/home/user/openmw/save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_windows_multi_component_load_savegame_path) @@ -67,7 +67,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"(C:\OpenMW\save.omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_spaces) @@ -76,8 +76,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "my save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "my"); -// EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_octothorpe) @@ -86,7 +85,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "my#save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "my#save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "my#save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_at_sign) @@ -95,7 +94,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "my@save.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "my@save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "my@save.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_quote) @@ -104,7 +103,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"(my"save.omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save.omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save.omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path) @@ -113,8 +112,8 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"("save".omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(save)"); -// EXPECT_EQ(variables["load-savegame"].as().string(), R"("save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(save)"); +// EXPECT_EQ(variables["load-savegame"].as().string(), R"("save".omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) @@ -123,7 +122,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"("save&".omwsave")"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_ampersand) @@ -132,7 +131,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"("save.omwsave&&")"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_ampersand) @@ -141,7 +140,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", "save&.omwsave"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); } TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_multiple_quotes) @@ -150,7 +149,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", R"(my"save".omwsave)"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(my"save".omwsave)"); } TEST(OpenMWOptionsFromArguments, should_compose_data) @@ -159,7 +158,7 @@ namespace const std::array arguments {"openmw", "--data", "1", "--data", "2"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromArguments, should_compose_data_from_single_flag) @@ -168,7 +167,7 @@ namespace const std::array arguments {"openmw", "--data", "1", "2"}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromArguments, should_throw_on_multiple_load_savegame) @@ -189,7 +188,7 @@ namespace const std::array arguments {"openmw", "--load-savegame", pathArgument.c_str()}; bpo::variables_map variables; parseArgs(arguments, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), path); + EXPECT_EQ(variables["load-savegame"].as().string(), path); } INSTANTIATE_TEST_SUITE_P( @@ -204,7 +203,7 @@ namespace std::istringstream stream("load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path) @@ -213,7 +212,7 @@ namespace std::istringstream stream(R"(load-savegame="save.omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_strip_outer_quotes_from_load_savegame_path) @@ -222,8 +221,8 @@ namespace std::istringstream stream(R"(load-savegame=""save".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), ""); -// EXPECT_EQ(variables["load-savegame"].as().string(), R"(""save".omwsave")"); + EXPECT_EQ(variables["load-savegame"].as().string(), ""); +// EXPECT_EQ(variables["load-savegame"].as().string(), R"(""save".omwsave")"); } TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path_with_space) @@ -232,7 +231,7 @@ namespace std::istringstream stream(R"(load-savegame="my save.omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "my save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_octothorpe) @@ -241,7 +240,7 @@ namespace std::istringstream stream("load-savegame=save#.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save#.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save#.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_at_sign) @@ -250,7 +249,7 @@ namespace std::istringstream stream("load-savegame=save@.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save@.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save@.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_quote) @@ -259,7 +258,7 @@ namespace std::istringstream stream(R"(load-savegame=save".omwsave)"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); } TEST(OpenMWOptionsFromConfig, should_support_confusing_savegame_path_with_lots_going_on) @@ -268,7 +267,7 @@ namespace std::istringstream stream(R"(load-savegame="one &"two"three".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(one "two)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(one "two)"); } TEST(OpenMWOptionsFromConfig, should_support_confusing_savegame_path_with_even_more_going_on) @@ -277,7 +276,7 @@ namespace std::istringstream stream(R"(load-savegame="one &"two"three ".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(one "two)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(one "two)"); } TEST(OpenMWOptionsFromConfig, should_ignore_commented_option) @@ -286,7 +285,7 @@ namespace std::istringstream stream("#load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), ""); + EXPECT_EQ(variables["load-savegame"].as().string(), ""); } TEST(OpenMWOptionsFromConfig, should_ignore_whitespace_prefixed_commented_option) @@ -295,7 +294,7 @@ namespace std::istringstream stream(" \t#load-savegame=save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), ""); + EXPECT_EQ(variables["load-savegame"].as().string(), ""); } TEST(OpenMWOptionsFromConfig, should_support_whitespace_around_option) @@ -304,7 +303,7 @@ namespace std::istringstream stream(" load-savegame = save.omwsave "); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_throw_on_multiple_load_savegame) @@ -321,7 +320,7 @@ namespace std::istringstream stream("load-savegame=/home/user/openmw/save.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "/home/user/openmw/save.omwsave"); } TEST(OpenMWOptionsFromConfig, should_support_windows_multi_component_load_savegame_path) @@ -330,7 +329,7 @@ namespace std::istringstream stream(R"(load-savegame=C:\OpenMW\save.omwsave)"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(C:\OpenMW\save.omwsave)"); } TEST(OpenMWOptionsFromConfig, should_compose_data) @@ -339,7 +338,7 @@ namespace std::istringstream stream("data=1\ndata=2"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsPath("1"), IsPath("2"))); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) @@ -348,7 +347,7 @@ namespace std::istringstream stream(R"(load-savegame="save&".omwsave")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); + EXPECT_EQ(variables["load-savegame"].as().string(), R"(save".omwsave)"); } TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_ampersand) @@ -357,7 +356,7 @@ namespace std::istringstream stream(R"(load-savegame="save.omwsave&&")"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save.omwsave&"); } TEST(OpenMWOptionsFromConfig, should_support_load_savegame_path_with_ampersand) @@ -366,7 +365,7 @@ namespace std::istringstream stream("load-savegame=save&.omwsave"); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); + EXPECT_EQ(variables["load-savegame"].as().string(), "save&.omwsave"); } struct OpenMWOptionsFromConfigStrings : TestWithParam {}; @@ -378,7 +377,7 @@ namespace std::istringstream stream("load-savegame=\"" + path + "\""); bpo::variables_map variables; Files::parseConfig(stream, variables, description); - EXPECT_EQ(variables["load-savegame"].as().string(), path); + EXPECT_EQ(variables["load-savegame"].as().string(), path); } INSTANTIATE_TEST_SUITE_P( diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index b04b211967..bb22e4750f 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -138,10 +138,10 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost boost::any& firstValue = firstPosition->second.value(); const boost::any& secondValue = second[name].value(); - if (firstValue.type() == typeid(Files::ReluctantPathContainer)) + if (firstValue.type() == typeid(Files::MaybeQuotedPathContainer)) { - auto& firstPathContainer = boost::any_cast(firstValue); - const auto& secondPathContainer = boost::any_cast(secondValue); + auto& firstPathContainer = boost::any_cast(firstValue); + const auto& secondPathContainer = boost::any_cast(secondValue); firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); } @@ -317,22 +317,31 @@ void parseConfig(std::istream& stream, boost::program_options::variables_map& va ); } -std::istream& operator>> (std::istream& istream, ReluctantPath& reluctantPath) +std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPath) { - // Read from stream using boost::filesystem::path rules, then discard anything remaining. + // If the stream starts with a double quote, read from stream using boost::filesystem::path rules, then discard anything remaining. // This prevents boost::program_options getting upset that we've not consumed the whole stream. - istream >> static_cast(reluctantPath); - if (istream && !istream.eof() && istream.peek() != EOF) + // If it doesn't start with a double quote, read the whole thing verbatim + if (istream.peek() == '"') { - std::string remainder(std::istreambuf_iterator(istream), {}); - Log(Debug::Warning) << "Trailing data in path setting. Used '" << reluctantPath.string() << "' but '" << remainder << "' remained"; + istream >> static_cast(MaybeQuotedPath); + if (istream && !istream.eof() && istream.peek() != EOF) + { + std::string remainder(std::istreambuf_iterator(istream), {}); + Log(Debug::Warning) << "Trailing data in path setting. Used '" << MaybeQuotedPath.string() << "' but '" << remainder << "' remained"; + } + } + else + { + std::string intermediate(std::istreambuf_iterator(istream), {}); + static_cast(MaybeQuotedPath) = intermediate; } return istream; } -PathContainer asPathContainer(const ReluctantPathContainer& reluctantPathContainer) +PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathContainer) { - return PathContainer(reluctantPathContainer.begin(), reluctantPathContainer.end()); + return PathContainer(MaybeQuotedPathContainer.begin(), MaybeQuotedPathContainer.end()); } } /* namespace Cfg */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 8f3e5f938e..0c4d2dc835 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -77,17 +77,17 @@ void parseArgs(int argc, const char* const argv[], boost::program_options::varia void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, boost::program_options::options_description& description); -class ReluctantPath : public boost::filesystem::path +class MaybeQuotedPath : public boost::filesystem::path { public: operator boost::filesystem::path() { return *this; } }; -std::istream& operator>> (std::istream& istream, ReluctantPath& reluctantPath); +std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPath); -typedef std::vector ReluctantPathContainer; +typedef std::vector MaybeQuotedPathContainer; -PathContainer asPathContainer(const ReluctantPathContainer& reluctantPathContainer); +PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathContainer); } /* namespace Cfg */ From a9877aea984243bc04cf26b80320a7d96c22124f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Nov 2021 18:19:14 +0000 Subject: [PATCH 1766/2859] Ensure Debug version of GMock library is added to imported target (cherry picked from commit dcf61dfe783fe808c76d102e6639ed0be77d5932) --- cmake/FindGMock.cmake | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake index 6b3caf543e..bc3410969f 100644 --- a/cmake/FindGMock.cmake +++ b/cmake/FindGMock.cmake @@ -164,8 +164,16 @@ find_dependency(Threads) set_target_properties(GMock::GMock PROPERTIES INTERFACE_LINK_LIBRARIES "Threads::Threads" - IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" - IMPORTED_LOCATION "${GMOCK_LIBRARY}") + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX") + +if(EXISTS "${GMOCK_LIBRARY}") + set_target_properties(GMock::GMock PROPERTIES + IMPORTED_LOCATION "${GMOCK_LIBRARY}") +endif() +if(EXISTS "${GMOCK_LIBRARY_DEBUG}") + set_target_properties(GMock::GMock PROPERTIES + IMPORTED_LOCATION_DEBUG "${GMOCK_LIBRARY_DEBUG}") +endif() if(GMOCK_INCLUDE_DIR) set_target_properties(GMock::GMock PROPERTIES From 5b8cba7323031bba5b862401b3cf4620f9fce933 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 22 Nov 2021 19:17:36 +0000 Subject: [PATCH 1767/2859] Clear Lua-created UI on game load and reloadlua command --- apps/openmw/mwlua/luabindings.hpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 2 ++ apps/openmw/mwlua/uibindings.cpp | 9 +++++++++ 3 files changed, 12 insertions(+) diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index aad3183734..b021354a75 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -58,6 +58,7 @@ namespace MWLua // Implemented in uibindings.cpp sol::table initUserInterfacePackage(const Context&); + void clearUserInterface(); // Implemented in inputbindings.cpp sol::table initInputPackage(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index f55ca20f02..2f3df00f7f 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -220,6 +220,7 @@ namespace MWLua mPlayer.getRefData().setLuaScripts(nullptr); mPlayer = MWWorld::Ptr(); } + clearUserInterface(); } void LuaManager::setupPlayer(const MWWorld::Ptr& ptr) @@ -425,6 +426,7 @@ namespace MWLua continue; ESM::LuaScripts data; scripts->save(data); + clearUserInterface(); scripts->load(data); } for (LocalScripts* scripts : mActiveLocalScripts) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 7008a0caea..9982b857c8 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -10,6 +10,7 @@ namespace MWLua { namespace { + std::set allElements; class UiAction final : public Action { @@ -152,6 +153,7 @@ namespace MWLua { if (element->mDestroy) return; + allElements.erase(element.get()); element->mDestroy = true; context.mLuaManager->addAction(std::make_unique(UiAction::DESTROY, element, context.mLua)); }; @@ -168,6 +170,7 @@ namespace MWLua api["create"] = [context](const sol::table& layout) { auto element = std::make_shared(layout); + allElements.emplace(element.get()); context.mLuaManager->addAction(std::make_unique(UiAction::CREATE, element, context.mLua)); return element; }; @@ -180,4 +183,10 @@ namespace MWLua return LuaUtil::makeReadOnly(api); } + void clearUserInterface() + { + for (auto element : allElements) + element->destroy(); + allElements.clear(); + } } From 4a976a8e239b5375ed3d5c587ec6e21b62254d4a Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 22 Nov 2021 19:39:47 +0000 Subject: [PATCH 1768/2859] Use a setting to enable Lua Action tracebacks --- apps/openmw/mwlua/actions.cpp | 23 ++++++++++--------- apps/openmw/mwlua/actions.hpp | 2 -- .../source/reference/modding/settings/lua.rst | 12 ++++++++++ files/settings-default.cfg | 3 +++ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 7389363fe6..49bb5cfcb4 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" @@ -12,12 +13,12 @@ namespace MWLua { - -#ifdef NDEBUG - Action::Action(LuaUtil::LuaState* state) {} -#else - Action::Action(LuaUtil::LuaState* state) : mCallerTraceback(state->debugTraceback()) {} -#endif + Action::Action(LuaUtil::LuaState* state) + { + static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); + if (luaDebug) + mCallerTraceback = state->debugTraceback(); + } void Action::safeApply(WorldView& w) const { @@ -28,11 +29,11 @@ namespace MWLua catch (const std::exception& e) { Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); -#ifdef NDEBUG - Log(Debug::Error) << "Traceback is available only in debug builds"; -#else - Log(Debug::Error) << "Caller " << mCallerTraceback; -#endif + + if (mCallerTraceback.empty()) + Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; + else + Log(Debug::Error) << "Caller " << mCallerTraceback; } } diff --git a/apps/openmw/mwlua/actions.hpp b/apps/openmw/mwlua/actions.hpp index 3b71eef4a9..e88b39e83c 100644 --- a/apps/openmw/mwlua/actions.hpp +++ b/apps/openmw/mwlua/actions.hpp @@ -29,9 +29,7 @@ namespace MWLua virtual std::string toString() const = 0; private: -#ifndef NDEBUG std::string mCallerTraceback; -#endif }; class TeleportAction final : public Action diff --git a/docs/source/reference/modding/settings/lua.rst b/docs/source/reference/modding/settings/lua.rst index 4d1a3ca808..65faf884ae 100644 --- a/docs/source/reference/modding/settings/lua.rst +++ b/docs/source/reference/modding/settings/lua.rst @@ -1,6 +1,18 @@ Lua Settings ############ +lua debug +--------- + +:Type: boolean +:Range: True/False +:Default: False + +Enables debug tracebacks for Lua actions. +It adds significant performance overhead, don't enable if you don't need it. + +This setting can only be configured by editing the settings configuration file. + lua num threads --------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c0083e4238..9715e3793e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1106,6 +1106,9 @@ stomp intensity = 1 [Lua] +# Enable performance-heavy debug features +lua debug = false + # Set the maximum number of threads used for Lua scripts. # If zero, Lua scripts are processed in the main thread. lua num threads = 1 From da0c5b54f0d0162ff062292f3fae38238c186249 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 21 Nov 2021 16:16:15 +0100 Subject: [PATCH 1769/2859] Load only supported content formats by EsmLoader --- apps/openmw_test_suite/esmloader/load.cpp | 23 +++++++++++++++++++++++ components/esmloader/load.cpp | 19 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/esmloader/load.cpp b/apps/openmw_test_suite/esmloader/load.cpp index 0f00847226..d6ed66f7a7 100644 --- a/apps/openmw_test_suite/esmloader/load.cpp +++ b/apps/openmw_test_suite/esmloader/load.cpp @@ -101,4 +101,27 @@ namespace EXPECT_EQ(esmData.mLands.size(), 0); EXPECT_EQ(esmData.mStatics.size(), 0); } + + TEST_F(EsmLoaderTest, loadEsmDataShouldSkipUnsupportedFormats) + { + Query query; + query.mLoadActivators = true; + query.mLoadCells = true; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = true; + query.mLoadLands = true; + query.mLoadStatics = true; + const std::vector contentFiles {{"script.omwscripts"}}; + std::vector readers(contentFiles.size()); + ToUTF8::Utf8Encoder* const encoder = nullptr; + const EsmData esmData = loadEsmData(query, contentFiles, mFileCollections, readers, encoder); + EXPECT_EQ(esmData.mActivators.size(), 0); + EXPECT_EQ(esmData.mCells.size(), 0); + EXPECT_EQ(esmData.mContainers.size(), 0); + EXPECT_EQ(esmData.mDoors.size(), 0); + EXPECT_EQ(esmData.mGameSettings.size(), 0); + EXPECT_EQ(esmData.mLands.size(), 0); + EXPECT_EQ(esmData.mStatics.size(), 0); + } } diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp index 9879f33274..42870989db 100644 --- a/components/esmloader/load.cpp +++ b/components/esmloader/load.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -206,10 +207,26 @@ namespace EsmLoader { ShallowContent result; + const std::set supportedFormats { + ".esm", + ".esp", + ".omwgame", + ".omwaddon", + ".project", + }; + for (std::size_t i = 0; i < contentFiles.size(); ++i) { const std::string &file = contentFiles[i]; - const Files::MultiDirCollection& collection = fileCollections.getCollection(boost::filesystem::path(file).extension().string()); + const std::string extension = boost::filesystem::path(file).extension().string(); + + if (supportedFormats.find(extension) == supportedFormats.end()) + { + Log(Debug::Warning) << "Skipping unsupported content file: " << file; + continue; + } + + const Files::MultiDirCollection& collection = fileCollections.getCollection(extension); ESM::ESMReader& reader = readers[i]; reader.setEncoder(encoder); From 1b1de86b4a984b888669eb9017362f67fbe99d7a Mon Sep 17 00:00:00 2001 From: kuyondo Date: Tue, 23 Nov 2021 04:07:10 +0800 Subject: [PATCH 1770/2859] Validate Quick Keys --- apps/openmw/mwgui/quickkeysmenu.cpp | 55 ++++++++++++++++------------- apps/openmw/mwgui/quickkeysmenu.hpp | 1 + 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index ad61f63b90..5bf5a4df63 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -79,44 +79,49 @@ namespace MWGui delete mMagicSelectionDialog; } - void QuickKeysMenu::onOpen() + // Check if quick keys are still valid + void QuickKeysMenu::validate(MWWorld::Ptr player) { - WindowBase::onOpen(); - - MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - // Check if quick keys are still valid - for (int i=0; i<10; ++i) + for (int i = 0; i < 10; ++i) { switch (mKey[i].type) { - case Type_Unassigned: - case Type_HandToHand: - case Type_Magic: - break; - case Type_Item: - case Type_MagicItem: - { - MWWorld::Ptr item = *mKey[i].button->getUserData(); - // Make sure the item is available and is not broken - if (!item || item.getRefData().getCount() < 1 || - (item.getClass().hasItemHealth(item) && + case Type_Unassigned: + case Type_HandToHand: + case Type_Magic: + break; + case Type_Item: + case Type_MagicItem: + { + MWWorld::Ptr item = *mKey[i].button->getUserData(); + // Make sure the item is available and is not broken + if (!item || item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) - { - // Try searching for a compatible replacement - item = store.findReplacement(mKey[i].id); + { + // Try searching for a compatible replacement + item = store.findReplacement(mKey[i].id); - if (item) - mKey[i].button->setUserData(MWWorld::Ptr(item)); + if (item) + mKey[i].button->setUserData(MWWorld::Ptr(item)); - break; - } + break; } } + } } } + void QuickKeysMenu::onOpen() + { + WindowBase::onOpen(); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + validate(player); + } + void QuickKeysMenu::unassign(keyData* key) { key->button->clearUserStrings(); @@ -334,6 +339,8 @@ namespace MWGui MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); + validate(player); + // Delay action executing, // if player is busy for now (casting a spell, attacking someone, etc.) bool isDelayNeeded = MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player) diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index b2742df796..bfb54e5d16 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -32,6 +32,7 @@ namespace MWGui void onAssignMagicItem (MWWorld::Ptr item); void onAssignMagic (const std::string& spellId); void onAssignMagicCancel (); + void validate(MWWorld::Ptr player); void onOpen() override; void activateQuickKey(int index); From 44d5c9618372daf7b3cb7903e46a73bc4fa8f2d7 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 22 Nov 2021 17:35:00 -0800 Subject: [PATCH 1771/2859] sunglare fix --- apps/openmw/mwrender/sky.cpp | 2 +- apps/openmw/mwrender/skyutil.cpp | 16 ++++++++++++---- apps/openmw/mwrender/skyutil.hpp | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index fcb7447cf3..c0a071c062 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -300,7 +300,7 @@ namespace MWRender mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager(), forceShaders); atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager)); mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index b98e489738..c0b6f81358 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -694,12 +694,14 @@ namespace MWRender mTransform->setNodeMask(visible ? mVisibleMask : 0); } - Sun::Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) + Sun::Sun(osg::Group* parentNode, Resource::SceneManager& sceneManager) : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) , mUpdater(new SunUpdater) { mTransform->addUpdateCallback(mUpdater); + Resource::ImageManager& imageManager = *sceneManager.getImageManager(); + osg::ref_ptr sunTex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds")); sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); @@ -714,9 +716,12 @@ namespace MWRender stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); stateset->setNestRenderBins(false); // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun - osg::ref_ptr alphaFunc = new osg::AlphaFunc; - alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); - stateset->setAttributeAndModes(alphaFunc); + if (!sceneManager.getForceShaders()) + { + osg::ref_ptr alphaFunc = new osg::AlphaFunc; + alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); + stateset->setAttributeAndModes(alphaFunc); + } stateset->setTextureAttributeAndModes(0, sunTex); stateset->setAttributeAndModes(createUnlitMaterial()); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query))); @@ -816,6 +821,7 @@ namespace MWRender // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. double far = SceneUtil::AutoDepth::isReversed() ? 0.0 : 1.0; + depth->setFunction(osg::Depth::LEQUAL); depth->setZNear(far); depth->setZFar(far); depth->setWriteMask(false); @@ -879,6 +885,8 @@ namespace MWRender camera->setClearMask(0); camera->setRenderOrder(osg::Camera::NESTED_RENDER); camera->setAllowEventFocus(false); + camera->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", static_cast(camera->getProjectionMatrix()))); + SceneUtil::setCameraClearDepth(camera); osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); camera->addChild(geom); diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index c2272143a0..a0d9ed72b4 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -239,7 +239,7 @@ namespace MWRender class Sun : public CelestialBody { public: - Sun(osg::Group* parentNode, Resource::ImageManager& imageManager); + Sun(osg::Group* parentNode, Resource::SceneManager& sceneManager); ~Sun(); From fd58e5ba778124469c91ddfec0669bb6cf649b3a Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 23 Nov 2021 04:39:17 +0300 Subject: [PATCH 1772/2859] Use the anim source to find mAccumRoot (bug #6417) --- CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 35 ++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2a67349b..5bf298a457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation Bug #6396: Inputting certain Unicode characters triggers an assertion Bug #6416: Morphs are applied to the wrong target + Bug #6417: OpenMW doesn't always use the right node to accumulate movement Bug #6429: Wyrmhaven: Can't add AI packages to player Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index ecfe65c575..bf0ed04055 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -624,9 +624,8 @@ namespace MWRender return; const NodeMap& nodeMap = getNodeMap(); - - for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); - it != animsrc->mKeyframes->mKeyframeControllers.end(); ++it) + const auto& controllerMap = animsrc->mKeyframes->mKeyframeControllers; + for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = controllerMap.begin(); it != controllerMap.end(); ++it) { std::string bonename = Misc::StringUtils::lowerCase(it->first); NodeMap::const_iterator found = nodeMap.find(bonename); @@ -652,14 +651,32 @@ namespace MWRender SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]); mObjectRoot->accept(assignVisitor); + // Determine the movement accumulation bone if necessary if (!mAccumRoot) { - NodeMap::const_iterator found = nodeMap.find("bip01"); - if (found == nodeMap.end()) - found = nodeMap.find("root bone"); - - if (found != nodeMap.end()) - mAccumRoot = found->second; + // Priority matters! bip01 is preferred. + static const std::array accumRootNames = + { + "bip01", + "root bone" + }; + NodeMap::const_iterator found = nodeMap.end(); + for (const std::string& name : accumRootNames) + { + found = nodeMap.find(name); + if (found == nodeMap.end()) + continue; + for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = controllerMap.begin(); it != controllerMap.end(); ++it) + { + if (Misc::StringUtils::lowerCase(it->first) == name) + { + mAccumRoot = found->second; + break; + } + } + if (mAccumRoot) + break; + } } } From 503f0e62e7482c4fa4770e4f7d906187c070c2d9 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Tue, 23 Nov 2021 14:55:50 +0800 Subject: [PATCH 1773/2859] Validate Argument is Index instead of Player --- apps/openmw/mwgui/quickkeysmenu.cpp | 28 ++++++++++++++-------------- apps/openmw/mwgui/quickkeysmenu.hpp | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 5bf5a4df63..1ada078ab6 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -79,14 +79,11 @@ namespace MWGui delete mMagicSelectionDialog; } - // Check if quick keys are still valid - void QuickKeysMenu::validate(MWWorld::Ptr player) + inline void QuickKeysMenu::validate(int index) { + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - - for (int i = 0; i < 10; ++i) - { - switch (mKey[i].type) + switch (mKey[index].type) { case Type_Unassigned: case Type_HandToHand: @@ -95,31 +92,34 @@ namespace MWGui case Type_Item: case Type_MagicItem: { - MWWorld::Ptr item = *mKey[i].button->getUserData(); + MWWorld::Ptr item = *mKey[index].button->getUserData(); // Make sure the item is available and is not broken + std::cout << item << std::endl; if (!item || item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement - item = store.findReplacement(mKey[i].id); + item = store.findReplacement(mKey[index].id); if (item) - mKey[i].button->setUserData(MWWorld::Ptr(item)); + mKey[index].button->setUserData(MWWorld::Ptr(item)); break; } } } - } } void QuickKeysMenu::onOpen() { WindowBase::onOpen(); - MWWorld::Ptr player = MWMechanics::getPlayer(); - validate(player); + // Quick key index + for (int index = 0; index < 10; ++index) + { + validate(index); + } } void QuickKeysMenu::unassign(keyData* key) @@ -334,12 +334,12 @@ namespace MWGui assert(index >= 1 && index <= 10); keyData *key = &mKey[index-1]; - + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); - validate(player); + validate(index-1); // Delay action executing, // if player is busy for now (casting a spell, attacking someone, etc.) diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index bfb54e5d16..4761c98ceb 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -32,7 +32,6 @@ namespace MWGui void onAssignMagicItem (MWWorld::Ptr item); void onAssignMagic (const std::string& spellId); void onAssignMagicCancel (); - void validate(MWWorld::Ptr player); void onOpen() override; void activateQuickKey(int index); @@ -77,7 +76,8 @@ namespace MWGui void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); - + // Check if quick key is still valid + inline void validate(int index); void unassign(keyData* key); }; From d55682f8331508411d277d04e374c7ea7c6abad7 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Tue, 23 Nov 2021 15:14:59 +0800 Subject: [PATCH 1774/2859] remove debug code --- apps/openmw/mwgui/quickkeysmenu.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 1ada078ab6..a581a52cc0 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -94,7 +94,6 @@ namespace MWGui { MWWorld::Ptr item = *mKey[index].button->getUserData(); // Make sure the item is available and is not broken - std::cout << item << std::endl; if (!item || item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) From f18b8adfea812ea0be6ade47b2bbfdae9860e6af Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 23 Nov 2021 17:00:53 +0100 Subject: [PATCH 1775/2859] Truncate instead of rounding to deal with negative values --- apps/openmw/mwmechanics/magiceffects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index b01c5446a7..7a1acb1003 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -11,7 +11,7 @@ namespace // Round value to prevent precision issues void truncate(float& value) { - value = std::roundf(value * 1024.f) / 1024.f; + value = static_cast(value * 1024.f) / 1024.f; } } From 60a345f5ccf59bfe6485f12f9e201cb6f87fbcb0 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Wed, 24 Nov 2021 01:44:14 +0800 Subject: [PATCH 1776/2859] Better indentation --- apps/openmw/mwgui/quickkeysmenu.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index a581a52cc0..e55b9b4878 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -83,8 +83,8 @@ namespace MWGui { MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - switch (mKey[index].type) - { + switch (mKey[index].type) + { case Type_Unassigned: case Type_HandToHand: case Type_Magic: @@ -107,7 +107,7 @@ namespace MWGui break; } } - } + } } void QuickKeysMenu::onOpen() From 5ffd077f9ec8fba5fba51ad5cb3a9e645ddfffc1 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Wed, 24 Nov 2021 01:51:03 +0800 Subject: [PATCH 1777/2859] Update changelog.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2a67349b..87b2d72770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,7 @@ Bug #6396: Inputting certain Unicode characters triggers an assertion Bug #6416: Morphs are applied to the wrong target Bug #6429: Wyrmhaven: Can't add AI packages to player + Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened. Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record From 30ff688c70b706de93454a1b294369a38664eb5b Mon Sep 17 00:00:00 2001 From: kuyondo Date: Wed, 24 Nov 2021 19:35:39 +0800 Subject: [PATCH 1778/2859] Create only one overencumbered messagebox --- apps/openmw/mwgui/messagebox.cpp | 9 +++++++++ apps/openmw/mwgui/messagebox.hpp | 1 + 2 files changed, 10 insertions(+) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index ed6633c983..f77b274f30 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -93,10 +93,19 @@ namespace MWGui void MessageBoxManager::createMessageBox (const std::string& message, bool stat) { + if (message == "#{sNotifyMessage59}") + for (MessageBox* messageBox : mMessageBoxes) + if (messageBox->mIsEncumberedMessage) + { + messageBox->mCurrentTime = 0; + return; + } + MessageBox *box = new MessageBox(*this, message); box->mCurrentTime = 0; std::string realMessage = MyGUI::LanguageManager::getInstance().replaceTags(message); box->mMaxTime = realMessage.length()*mMessageBoxSpeed; + box->mIsEncumberedMessage = (message == "#{sNotifyMessage59}"); if(stat) mStaticMessageBox = box; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 26d26bac56..b7c8e91b99 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -69,6 +69,7 @@ namespace MWGui float mCurrentTime; float mMaxTime; + bool mIsEncumberedMessage; protected: MessageBoxManager& mMessageBoxManager; From 11925b17ea7b28c9cc953527da2a56b886e17fe3 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Wed, 24 Nov 2021 19:39:22 +0800 Subject: [PATCH 1779/2859] Remove mOverencumberedMessageDelay --- apps/openmw/mwinput/actionmanager.cpp | 9 +-------- apps/openmw/mwinput/actionmanager.hpp | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index 59bdc37bb8..3931517fab 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -39,7 +39,6 @@ namespace MWInput , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) , mSneaking(false) , mAttemptJump(false) - , mOverencumberedMessageDelay(0.f) , mTimeIdle(0.f) { } @@ -88,22 +87,16 @@ namespace MWInput { player.setUpDown(1); triedToMove = true; - mOverencumberedMessageDelay = 0.f; } // if player tried to start moving, but can't (due to being overencumbered), display a notification. if (triedToMove) { MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - mOverencumberedMessageDelay -= dt; if (playerPtr.getClass().getEncumbrance(playerPtr) > playerPtr.getClass().getCapacity(playerPtr)) { player.setAutoMove (false); - if (mOverencumberedMessageDelay <= 0) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); - mOverencumberedMessageDelay = 1.0; - } + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); } } diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index 4141767bcc..4c51139d46 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -67,7 +67,6 @@ namespace MWInput bool mSneaking; bool mAttemptJump; - float mOverencumberedMessageDelay; float mTimeIdle; }; } From 6b43ce066294c058de4b1c2908f8f60f8d59fd04 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 24 Nov 2021 19:10:02 +0100 Subject: [PATCH 1780/2859] Fix deadlock in physics system 1. Reorder unlock and notify_all calls to avoid notifying when not all worker threads are waiting. 2. Make sure main thread does not attempt to exclusively lock mSimulationMutex while not all workers are done with previous frame. 3. Replace mNewFrame flag by counter to avoid modification from multiple threads. --- apps/openmw/mwphysics/mtphysics.cpp | 39 ++++++++++++++++++++++++----- apps/openmw/mwphysics/mtphysics.hpp | 7 +++++- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 5c49dee297..2f7e3b5995 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -267,7 +267,7 @@ namespace MWPhysics , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) - , mNewFrame(false) + , mFrameCounter(0) , mAdvanceSimulation(false) , mQuit(false) , mNextJob(0) @@ -302,13 +302,14 @@ namespace MWPhysics PhysicsTaskScheduler::~PhysicsTaskScheduler() { + waitForWorkers(); { MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); mQuit = true; mNumJobs = 0; mRemainingSteps = 0; + mHasJob.notify_all(); } - mHasJob.notify_all(); for (auto& thread : mThreads) thread.join(); } @@ -361,6 +362,8 @@ namespace MWPhysics void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { + waitForWorkers(); + // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. @@ -393,7 +396,7 @@ namespace MWPhysics mPhysicsDt = newDelta; mSimulations = std::move(simulations); mAdvanceSimulation = (mRemainingSteps != 0); - mNewFrame = true; + ++mFrameCounter; mNumJobs = mSimulations.size(); mNextLOS.store(0, std::memory_order_relaxed); mNextJob.store(0, std::memory_order_release); @@ -421,6 +424,7 @@ namespace MWPhysics void PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { + waitForWorkers(); MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); @@ -577,11 +581,15 @@ namespace MWPhysics void PhysicsTaskScheduler::worker() { + std::size_t lastFrame = 0; std::shared_lock lock(mSimulationMutex); while (!mQuit) { - if (!mNewFrame) - mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; }); + if (lastFrame == mFrameCounter) + { + mHasJob.wait(lock, [&] { return mQuit || lastFrame != mFrameCounter; }); + lastFrame = mFrameCounter; + } doSimulation(); } @@ -663,6 +671,7 @@ namespace MWPhysics void PhysicsTaskScheduler::releaseSharedStates() { + waitForWorkers(); std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex); mSimulations.clear(); mUpdateAabb.clear(); @@ -693,7 +702,6 @@ namespace MWPhysics void PhysicsTaskScheduler::afterPostSim() { - mNewFrame = false; { MaybeExclusiveLock lock(mLOSCacheMutex, mNumThreads); mLOSCache.erase( @@ -702,6 +710,10 @@ namespace MWPhysics mLOSCache.end()); } mTimeEnd = mTimer->tick(); + + std::unique_lock lock(mWorkersDoneMutex); + ++mWorkersFrameCounter; + mWorkersDone.notify_all(); } void PhysicsTaskScheduler::syncWithMainThread() @@ -710,4 +722,19 @@ namespace MWPhysics for (auto& sim : mSimulations) std::visit(vis, sim); } + + // Attempt to acquire unique lock on mSimulationMutex while not all worker + // threads are holding shared lock but will have to may lead to a deadlock because + // C++ standard does not guarantee priority for exclusive and shared locks + // for std::shared_mutex. For example microsoft STL implementation points out + // for the absence of such priority: + // https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks + void PhysicsTaskScheduler::waitForWorkers() + { + if (mNumThreads == 0) + return; + std::unique_lock lock(mWorkersDoneMutex); + if (mFrameCounter != mWorkersFrameCounter) + mWorkersDone.wait(lock); + } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 44330b2cc6..9ded1262d6 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -74,6 +74,7 @@ namespace MWPhysics void afterPostStep(); void afterPostSim(); void syncWithMainThread(); + void waitForWorkers(); std::unique_ptr mWorldFrameData; std::vector mSimulations; @@ -95,13 +96,17 @@ namespace MWPhysics int mNumJobs; int mRemainingSteps; int mLOSCacheExpiry; - bool mNewFrame; + std::size_t mFrameCounter; bool mAdvanceSimulation; bool mQuit; std::atomic mNextJob; std::atomic mNextLOS; std::vector mThreads; + std::size_t mWorkersFrameCounter = 0; + std::condition_variable mWorkersDone; + std::mutex mWorkersDoneMutex; + mutable std::shared_mutex mSimulationMutex; mutable std::shared_mutex mCollisionWorldMutex; mutable std::shared_mutex mLOSCacheMutex; From d6f06fbf9a0030edb51aaeaf92b2ae8c361fbc5b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 25 Nov 2021 22:00:52 +0100 Subject: [PATCH 1781/2859] Remove allies from combat when stopping combat --- CHANGELOG.md | 1 + apps/openmw/mwbase/mechanicsmanager.hpp | 3 +++ apps/openmw/mwmechanics/actors.cpp | 22 ++++++++++++++++--- apps/openmw/mwmechanics/actors.hpp | 2 ++ apps/openmw/mwmechanics/aisequence.cpp | 14 ++++++++++++ apps/openmw/mwmechanics/aisequence.hpp | 3 +++ .../mwmechanics/mechanicsmanagerimp.cpp | 5 +++++ .../mwmechanics/mechanicsmanagerimp.hpp | 2 ++ apps/openmw/mwscript/aiextensions.cpp | 3 +-- 9 files changed, 50 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 174eb9e4b4..8332066203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ------ Bug #1751: Birthsign abilities increase modified attribute values instead of base ones + Bug #1930: Followers are still fighting if a target stops combat with a leader Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3488: AI combat aiming is too slow Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 6bedbb5b4d..484940e3e6 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -112,6 +112,9 @@ namespace MWBase /// Makes \a ptr fight \a target. Also shouts a combat taunt. virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + /// Removes an actor and its allies from combat with the actor's targets. + virtual void stopCombat(const MWWorld::Ptr& ptr) = 0; + enum OffenseType { OT_Theft, // Taking items owned by an NPC or a faction you are not a member of diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b3ddefec3f..ce2465ec97 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -443,6 +443,22 @@ namespace MWMechanics } } + void Actors::stopCombat(const MWWorld::Ptr& ptr) + { + auto& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + std::vector targets; + if(ai.getCombatTargets(targets)) + { + std::set allySet; + getActorsSidingWith(ptr, allySet); + std::vector allies(allySet.begin(), allySet.end()); + for(const auto& ally : allies) + ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(targets); + for(const auto& target : targets) + target.getClass().getCreatureStats(target).getAiSequence().stopCombat(allies); + } + } + void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer) { // No combat for totally static creatures @@ -979,7 +995,7 @@ namespace MWMechanics // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) creatureStats.getAiSequence().stopPursuit(); - creatureStats.getAiSequence().stopCombat(); + stopCombat(ptr); // Reset factors to attack creatureStats.setAttacked(false); @@ -1967,7 +1983,7 @@ namespace MWMechanics if (stats.isDead()) continue; - // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package + // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Wander packages before the Follow/Escort package // Actors that are targeted by this actor's Follow or Escort packages also side with them for (const auto& package : stats.getAiSequence()) { @@ -1983,7 +1999,7 @@ namespace MWMechanics } break; } - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + else if (package->getTypeId() > AiPackageTypeId::Wander && package->getTypeId() <= AiPackageTypeId::Activate) // Don't count "fake" package types break; } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 7d44fd06cd..f922be6556 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -111,6 +111,8 @@ namespace MWMechanics ///< This function is normally called automatically during the update process, but it can /// also be called explicitly at any time to force an update. + /// Removes an actor from combat and makes all of their allies stop fighting the actor's targets + void stopCombat(const MWWorld::Ptr& ptr); /** Start combat between two actors @Notes: If againstPlayer = true then actor2 should be the Player. If one of the combatants is creature it should be actor1. diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b1827dd8ef..545ec7f140 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -158,6 +158,7 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const return false; } +// TODO: use std::list::remove_if for all these methods when we switch to C++20 void AiSequence::stopCombat() { for(auto it = mPackages.begin(); it != mPackages.end(); ) @@ -171,6 +172,19 @@ void AiSequence::stopCombat() } } +void AiSequence::stopCombat(const std::vector& targets) +{ + for(auto it = mPackages.begin(); it != mPackages.end(); ) + { + if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end()) + { + it = mPackages.erase(it); + } + else + ++it; + } +} + void AiSequence::stopPursuit() { for(auto it = mPackages.begin(); it != mPackages.end(); ) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 90bd999c91..77f6b2f7c0 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -102,6 +102,9 @@ namespace MWMechanics /// Removes all combat packages until first non-combat or stack empty. void stopCombat(); + /// Removes all combat packages with the given targets + void stopCombat(const std::vector& targets); + /// Has a package been completed during the last update? bool isPackageDone() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 362df54ab7..cfaad486a2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1607,6 +1607,11 @@ namespace MWMechanics MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); } + void MechanicsManager::stopCombat(const MWWorld::Ptr& actor) + { + mActors.stopCombat(actor); + } + void MechanicsManager::getObjectsInRange(const osg::Vec3f &position, float radius, std::vector &objects) { mActors.getObjectsInRange(position, radius, objects); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 06da2fde51..0f4c2e606a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -102,6 +102,8 @@ namespace MWMechanics /// Makes \a ptr fight \a target. Also shouts a combat taunt. void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; + void stopCombat(const MWWorld::Ptr& ptr) override; + /** * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 5ebc0bc529..b7b6de9463 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -515,8 +515,7 @@ namespace MWScript MWWorld::Ptr actor = R()(runtime); if (!actor.getClass().isActor()) return; - MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); - creatureStats.getAiSequence().stopCombat(); + MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); } }; From 1f2311538dc344cd418ef682cbe2806bf342b110 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Fri, 26 Nov 2021 05:20:58 +0800 Subject: [PATCH 1782/2859] vanilla style messagebox 2 --- apps/openmw/mwbase/windowmanager.hpp | 2 ++ apps/openmw/mwgui/messagebox.cpp | 11 +++++++++++ apps/openmw/mwgui/messagebox.hpp | 5 ++++- apps/openmw/mwgui/windowmanagerimp.cpp | 5 +++++ apps/openmw/mwgui/windowmanagerimp.hpp | 1 + apps/openmw/mwinput/actionmanager.cpp | 17 +++++++++++------ apps/openmw/mwinput/actionmanager.hpp | 1 - 7 files changed, 34 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 961a63ac79..6a05a71703 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -69,6 +69,7 @@ namespace MWGui class DialogueWindow; class WindowModal; class JailScreen; + class MessageBox; enum ShowInDialogueMode { ShowInDialogueMode_IfPossible, @@ -145,6 +146,7 @@ namespace MWBase virtual MWGui::CountDialog* getCountDialog() = 0; virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; + virtual std::vector getActiveMessageBoxes() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index ed6633c983..738d28e189 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -161,6 +161,12 @@ namespace MWGui return false; } + std::vector MessageBoxManager::getActiveMessageBoxes() + { + return mMessageBoxes; + } + + int MessageBoxManager::readPressedButton (bool reset) { int pressed = mLastButtonPressed; @@ -202,6 +208,11 @@ namespace MWGui mMainWidget->setPosition(pos); } + std::string MessageBox::getMessage() + { + return mMessage; + } + int MessageBox::getHeight () { return mMainWidget->getHeight()+mNextBoxPadding; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 26d26bac56..746d558e6a 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -49,6 +49,8 @@ namespace MWGui void setVisible(bool value); + std::vector getActiveMessageBoxes(); + private: std::vector mMessageBoxes; InteractiveMessageBox* mInterMessageBoxe; @@ -63,6 +65,7 @@ namespace MWGui public: MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message); void setMessage (const std::string& message); + std::string getMessage(); int getHeight (); void update (int height); void setVisible(bool value); @@ -72,7 +75,7 @@ namespace MWGui protected: MessageBoxManager& mMessageBoxManager; - const std::string& mMessage; + const std::string mMessage; MyGUI::EditBox* mMessageWidget; int mBottomPadding; int mNextBoxPadding; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index a935d7f900..28a19a63ef 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -771,6 +771,11 @@ namespace MWGui mMessageBoxManager->removeStaticMessageBox(); } + std::vector WindowManager::getActiveMessageBoxes() + { + return mMessageBoxManager->getActiveMessageBoxes(); + } + int WindowManager::readPressedButton () { return mMessageBoxManager->readPressedButton(); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 9ec79e0c82..a4bc266a86 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -187,6 +187,7 @@ namespace MWGui MWGui::CountDialog* getCountDialog() override; MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; + std::vector getActiveMessageBoxes() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index 59bdc37bb8..180f3aa2fb 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -22,6 +22,8 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwgui/messagebox.hpp" + #include "actions.hpp" #include "bindingsmanager.hpp" @@ -39,7 +41,6 @@ namespace MWInput , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) , mSneaking(false) , mAttemptJump(false) - , mOverencumberedMessageDelay(0.f) , mTimeIdle(0.f) { } @@ -88,22 +89,26 @@ namespace MWInput { player.setUpDown(1); triedToMove = true; - mOverencumberedMessageDelay = 0.f; } // if player tried to start moving, but can't (due to being overencumbered), display a notification. if (triedToMove) { MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - mOverencumberedMessageDelay -= dt; if (playerPtr.getClass().getEncumbrance(playerPtr) > playerPtr.getClass().getCapacity(playerPtr)) { player.setAutoMove (false); - if (mOverencumberedMessageDelay <= 0) + std::vector msgboxs = MWBase::Environment::get().getWindowManager()->getActiveMessageBoxes(); + std::vector::iterator it = std::find_if(msgboxs.begin(), msgboxs.end(), [](MWGui::MessageBox*& msg) { + return (msg->getMessage() == "#{sNotifyMessage59}"); + }); + + // if an overencumbered messagebox is already present, reset its expiry timer, otherwise create new one. + if (it != msgboxs.end()) + (*it)->mCurrentTime = 0; + else MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); - mOverencumberedMessageDelay = 1.0; - } } } diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index 4141767bcc..4c51139d46 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -67,7 +67,6 @@ namespace MWInput bool mSneaking; bool mAttemptJump; - float mOverencumberedMessageDelay; float mTimeIdle; }; } From df9f601ed74aef143b1487da34906e685e530a5b Mon Sep 17 00:00:00 2001 From: kuyondo Date: Sun, 28 Nov 2021 02:22:34 +0800 Subject: [PATCH 1783/2859] initialize constants --- apps/openmw/mwbase/windowmanager.hpp | 2 +- apps/openmw/mwgui/messagebox.cpp | 4 ++-- apps/openmw/mwgui/messagebox.hpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- apps/openmw/mwgui/windowmanagerimp.hpp | 2 +- apps/openmw/mwinput/actionmanager.cpp | 7 ++++--- apps/openmw/mwinput/actionmanager.hpp | 2 ++ 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 6a05a71703..e7d22bef92 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -146,7 +146,7 @@ namespace MWBase virtual MWGui::CountDialog* getCountDialog() = 0; virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; - virtual std::vector getActiveMessageBoxes() = 0; + virtual const std::vector getActiveMessageBoxes() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 738d28e189..fa9a8007b7 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -161,7 +161,7 @@ namespace MWGui return false; } - std::vector MessageBoxManager::getActiveMessageBoxes() + const std::vector MessageBoxManager::getActiveMessageBoxes() { return mMessageBoxes; } @@ -208,7 +208,7 @@ namespace MWGui mMainWidget->setPosition(pos); } - std::string MessageBox::getMessage() + const std::string MessageBox::getMessage() { return mMessage; } diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 746d558e6a..e01c54b9a5 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -49,7 +49,7 @@ namespace MWGui void setVisible(bool value); - std::vector getActiveMessageBoxes(); + const std::vector getActiveMessageBoxes(); private: std::vector mMessageBoxes; @@ -65,7 +65,7 @@ namespace MWGui public: MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message); void setMessage (const std::string& message); - std::string getMessage(); + const std::string getMessage(); int getHeight (); void update (int height); void setVisible(bool value); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 28a19a63ef..2eff1f6c84 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -771,7 +771,7 @@ namespace MWGui mMessageBoxManager->removeStaticMessageBox(); } - std::vector WindowManager::getActiveMessageBoxes() + const std::vector WindowManager::getActiveMessageBoxes() { return mMessageBoxManager->getActiveMessageBoxes(); } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index a4bc266a86..10577c8485 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -187,7 +187,7 @@ namespace MWGui MWGui::CountDialog* getCountDialog() override; MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; - std::vector getActiveMessageBoxes() override; + const std::vector getActiveMessageBoxes() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index 180f3aa2fb..84bdc7d12b 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -42,6 +42,7 @@ namespace MWInput , mSneaking(false) , mAttemptJump(false) , mTimeIdle(0.f) + , mOverencumberedMessage("#{sNotifyMessage59}") { } @@ -99,16 +100,16 @@ namespace MWInput { player.setAutoMove (false); std::vector msgboxs = MWBase::Environment::get().getWindowManager()->getActiveMessageBoxes(); - std::vector::iterator it = std::find_if(msgboxs.begin(), msgboxs.end(), [](MWGui::MessageBox*& msg) + const std::vector::iterator it = std::find_if(msgboxs.begin(), msgboxs.end(), [=](MWGui::MessageBox*& msgbox) { - return (msg->getMessage() == "#{sNotifyMessage59}"); + return (msgbox->getMessage() == mOverencumberedMessage); }); // if an overencumbered messagebox is already present, reset its expiry timer, otherwise create new one. if (it != msgboxs.end()) (*it)->mCurrentTime = 0; else - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); + MWBase::Environment::get().getWindowManager()->messageBox(mOverencumberedMessage); } } diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index 4c51139d46..b3deaed20e 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -67,6 +67,8 @@ namespace MWInput bool mSneaking; bool mAttemptJump; + const std::string mOverencumberedMessage; + float mTimeIdle; }; } From 6e43206d690c14f57242fdbe7ebaf201e44d33d8 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Sun, 28 Nov 2021 04:58:49 +0800 Subject: [PATCH 1784/2859] remove empty/redundant lines --- apps/openmw/mwgui/messagebox.cpp | 11 ----------- apps/openmw/mwgui/messagebox.hpp | 1 - 2 files changed, 12 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 1eee2bb857..ef9ad73437 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -93,19 +93,10 @@ namespace MWGui void MessageBoxManager::createMessageBox (const std::string& message, bool stat) { - if (message == "#{sNotifyMessage59}") - for (MessageBox* messageBox : mMessageBoxes) - if (messageBox->mIsEncumberedMessage) - { - messageBox->mCurrentTime = 0; - return; - } - MessageBox *box = new MessageBox(*this, message); box->mCurrentTime = 0; std::string realMessage = MyGUI::LanguageManager::getInstance().replaceTags(message); box->mMaxTime = realMessage.length()*mMessageBoxSpeed; - box->mIsEncumberedMessage = (message == "#{sNotifyMessage59}"); if(stat) mStaticMessageBox = box; @@ -154,7 +145,6 @@ namespace MWGui return mInterMessageBoxe != nullptr; } - bool MessageBoxManager::removeMessageBox (MessageBox *msgbox) { std::vector::iterator it; @@ -175,7 +165,6 @@ namespace MWGui return mMessageBoxes; } - int MessageBoxManager::readPressedButton (bool reset) { int pressed = mLastButtonPressed; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 6a4cda948e..e01c54b9a5 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -72,7 +72,6 @@ namespace MWGui float mCurrentTime; float mMaxTime; - bool mIsEncumberedMessage; protected: MessageBoxManager& mMessageBoxManager; From e9f8c3437292d16dea754f702f3d3a6feee28346 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 28 Nov 2021 11:31:34 +0000 Subject: [PATCH 1785/2859] Correctly set UI properties to defaults when passed nil --- components/CMakeLists.txt | 3 +- components/lua_ui/element.cpp | 13 +----- components/lua_ui/properties.hpp | 64 ++++++++++++++++++++++++++++++ components/lua_ui/text.cpp | 22 ++--------- components/lua_ui/text.hpp | 2 +- components/lua_ui/textedit.cpp | 15 ++----- components/lua_ui/textedit.hpp | 3 +- components/lua_ui/widget.cpp | 68 ++++++-------------------------- components/lua_ui/widget.hpp | 15 ++++--- components/lua_ui/window.cpp | 28 ++++++------- components/lua_ui/window.hpp | 5 ++- 11 files changed, 112 insertions(+), 126 deletions(-) create mode 100644 components/lua_ui/properties.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 56371a8c02..3ea44f1f11 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -162,7 +162,8 @@ add_component_dir (queries ) add_component_dir (lua_ui - widget widgetlist element content text textedit window + widget widgetlist element content + text textedit window ) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 258da5269f..1a1b38e4dc 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -24,18 +24,7 @@ namespace LuaUi void setProperties(LuaUi::WidgetExtension* ext, const sol::table& layout) { - auto props = layout.get>("props"); - if (props.has_value()) - { - props.value().for_each([ext](const sol::object& key, const sol::object& value) - { - if (key.is()) - ext->setProperty(key.as(), value); - else - Log(Debug::Warning) << "UI property key must be a string"; - }); - ext->updateCoord(); - } + ext->setProperties(layout.get("props")); } void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::table& layout) diff --git a/components/lua_ui/properties.hpp b/components/lua_ui/properties.hpp new file mode 100644 index 0000000000..7f23a5ce6c --- /dev/null +++ b/components/lua_ui/properties.hpp @@ -0,0 +1,64 @@ +#ifndef OPENMW_LUAUI_PROPERTIES +#define OPENMW_LUAUI_PROPERTIES + +#include +#include +#include + +#include + +namespace LuaUi +{ + template + sol::optional getProperty(sol::object from, std::string_view field) { + sol::object value = LuaUtil::getFieldOrNil(from, field); + if (value == sol::nil) + return sol::nullopt; + if (value.is()) + return value.as(); + std::string error("Property \""); + error += field; + error += "\" has an invalid value \""; + error += LuaUtil::toString(value); + error += "\""; + throw std::logic_error(error); + } + + template + T parseProperty(sol::object from, std::string_view field, const T& defaultValue) + { + sol::optional opt = getProperty(from, field); + if (opt.has_value()) + return opt.value(); + else + return defaultValue; + } + + template + MyGUI::types::TPoint parseProperty( + sol::object from, + std::string_view field, + const MyGUI::types::TPoint& defaultValue) + { + auto v = getProperty(from, field); + if (v.has_value()) + return MyGUI::types::TPoint(v.value().x(), v.value().y()); + else + return defaultValue; + } + + template + MyGUI::types::TSize parseProperty( + sol::object from, + std::string_view field, + const MyGUI::types::TSize& defaultValue) + { + auto v = getProperty(from, field); + if (v.has_value()) + return MyGUI::types::TSize(v.value().x(), v.value().y()); + else + return defaultValue; + } +} + +#endif // !OPENMW_LUAUI_PROPERTIES diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index 571bdaf403..4ae9865ac3 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -12,25 +12,11 @@ namespace LuaUi WidgetExtension::initialize(); } - bool LuaText::setPropertyRaw(std::string_view name, sol::object value) + void LuaText::setProperties(sol::object props) { - if (name == "caption") - { - if (!value.is()) - return false; - setCaption(value.as()); - } - else if (name == "autoSize") - { - if (!value.is()) - return false; - mAutoSized = value.as(); - } - else - { - return WidgetExtension::setPropertyRaw(name, value); - } - return true; + setCaption(parseProperty(props, "caption", std::string())); + mAutoSized = parseProperty(props, "autoSize", true); + WidgetExtension::setProperties(props); } MyGUI::IntSize LuaText::calculateSize() diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index 90a3fc563c..d87a9001a2 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -14,13 +14,13 @@ namespace LuaUi public: LuaText(); virtual void initialize() override; + virtual void setProperties(sol::object) override; private: bool mAutoSized; protected: virtual MyGUI::IntSize calculateSize() override; - bool setPropertyRaw(std::string_view name, sol::object value) override; }; } diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index 2324a770c2..9cdc716ce4 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -2,18 +2,9 @@ namespace LuaUi { - bool LuaTextEdit::setPropertyRaw(std::string_view name, sol::object value) + void LuaTextEdit::setProperties(sol::object props) { - if (name == "caption") - { - if (!value.is()) - return false; - setCaption(value.as()); - } - else - { - return WidgetExtension::setPropertyRaw(name, value); - } - return true; + setCaption(parseProperty(props, "caption", std::string())); + WidgetExtension::setProperties(props); } } diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index 3eb5b471c3..d14f54d659 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -11,8 +11,7 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaTextEdit) - protected: - bool setPropertyRaw(std::string_view name, sol::object value) override; + virtual void setProperties(sol::object) override; }; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 3ce07273f8..ef876e8a06 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -142,19 +142,12 @@ namespace LuaUi mCallbacks.clear(); } - void WidgetExtension::setProperty(std::string_view name, sol::object value) - { - if (!setPropertyRaw(name, value)) - Log(Debug::Error) << "Invalid value of property " << name - << ": " << LuaUtil::toString(value); - } - - MyGUI::IntCoord WidgetExtension::forcedOffset() + MyGUI::IntCoord WidgetExtension::forcedCoord() { return mForcedCoord; } - void WidgetExtension::setForcedOffset(const MyGUI::IntCoord& offset) + void WidgetExtension::setForcedCoord(const MyGUI::IntCoord& offset) { mForcedCoord = offset; } @@ -164,55 +157,16 @@ namespace LuaUi mWidget->setCoord(calculateCoord()); } - bool WidgetExtension::setPropertyRaw(std::string_view name, sol::object value) + void WidgetExtension::setProperties(sol::object props) { - if (name == "position") - { - if (!value.is()) - return false; - auto v = value.as(); - mAbsoluteCoord.left = v.x(); - mAbsoluteCoord.top = v.y(); - } - else if (name == "size") - { - if (!value.is()) - return false; - auto v = value.as(); - mAbsoluteCoord.width = v.x(); - mAbsoluteCoord.height = v.y(); - } - else if (name == "relativePosition") - { - if (!value.is()) - return false; - auto v = value.as(); - mRelativeCoord.left = v.x(); - mRelativeCoord.top = v.y(); - } - else if (name == "relativeSize") - { - if (!value.is()) - return false; - auto v = value.as(); - mRelativeCoord.width = v.x(); - mRelativeCoord.height = v.y(); - } - else if (name == "anchor") - { - if (!value.is()) - return false; - auto v = value.as(); - mAnchor.width = v.x(); - mAnchor.height = v.y(); - } - else if (name == "visible") - { - if (!value.is()) - return false; - mWidget->setVisible(value.as()); - } - return true; + mAbsoluteCoord = parseProperty(props, "position", MyGUI::IntPoint()); + mAbsoluteCoord = parseProperty(props, "size", MyGUI::IntSize()); + mRelativeCoord = parseProperty(props, "relativePosition", MyGUI::FloatPoint()); + mRelativeCoord = parseProperty(props, "relativeSize", MyGUI::FloatSize()); + mAnchor = parseProperty(props, "anchor", MyGUI::FloatSize()); + mWidget->setVisible(parseProperty(props, "visible", true)); + + updateCoord(); } void WidgetExtension::updateChildrenCoord(MyGUI::Widget* _widget) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 48b169572f..360c6483ba 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -5,10 +5,11 @@ #include #include -#include #include +#include "properties.hpp" + namespace LuaUi { /* @@ -36,17 +37,16 @@ namespace LuaUi void setCallback(const std::string&, const LuaUtil::Callback&); void clearCallbacks(); - void setProperty(std::string_view, sol::object value); + virtual void setProperties(sol::object); - MyGUI::IntCoord forcedOffset(); - void setForcedOffset(const MyGUI::IntCoord& offset); + MyGUI::IntCoord forcedCoord(); + void setForcedCoord(const MyGUI::IntCoord& offset); void updateCoord(); protected: sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; - virtual bool setPropertyRaw(std::string_view name, sol::object value); virtual void initialize(); virtual void deinitialize(); virtual MyGUI::IntSize calculateSize(); @@ -55,9 +55,14 @@ namespace LuaUi void triggerEvent(std::string_view name, const sol::object& argument) const; + // offsets the position and size, used only in C++ widget code MyGUI::IntCoord mForcedCoord; + // position and size in pixels MyGUI::IntCoord mAbsoluteCoord; + // position and size as a ratio of parent size MyGUI::FloatCoord mRelativeCoord; + // negative position offset as a ratio of this widget's size + // used in combination with relative coord to align the widget, e. g. center it MyGUI::FloatSize mAnchor; private: diff --git a/components/lua_ui/window.cpp b/components/lua_ui/window.cpp index b7bb426a63..5d34bf8212 100644 --- a/components/lua_ui/window.cpp +++ b/components/lua_ui/window.cpp @@ -8,6 +8,7 @@ namespace LuaUi : mCaption() , mPreviousMouse() , mChangeScale() + , mMoveResize() {} void LuaWindow::initialize() @@ -43,20 +44,13 @@ namespace LuaUi } } - bool LuaWindow::setPropertyRaw(std::string_view name, sol::object value) + void LuaWindow::setProperties(sol::object props) { - if (name == "caption") - { - if (!value.is()) - return false; - if (mCaption) - mCaption->setCaption(value.as()); - } - else - { - return WidgetExtension::setPropertyRaw(name, value); - } - return true; + if (mCaption) + mCaption->setCaption(parseProperty(props, "caption", std::string())); + mMoveResize = MyGUI::IntCoord(); + setForcedCoord(mMoveResize); + WidgetExtension::setProperties(props); } void LuaWindow::notifyMousePress(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) @@ -84,9 +78,11 @@ namespace LuaUi change.width *= (left - mPreviousMouse.left); change.height *= (top - mPreviousMouse.top); - setForcedOffset(forcedOffset() + change.size()); - MyGUI::IntPoint positionOffset = change.point() + getPosition() - calculateCoord().point(); - setForcedOffset(forcedOffset() + positionOffset); + mMoveResize = mMoveResize + change.size(); + setForcedCoord(mMoveResize); + // position can change based on size changes + mMoveResize = mMoveResize + change.point() + getPosition() - calculateCoord().point(); + setForcedCoord(mMoveResize); updateCoord(); mPreviousMouse.left = left; diff --git a/components/lua_ui/window.hpp b/components/lua_ui/window.hpp index c04b0a6055..f34779c8f7 100644 --- a/components/lua_ui/window.hpp +++ b/components/lua_ui/window.hpp @@ -15,6 +15,7 @@ namespace LuaUi public: LuaWindow(); + virtual void setProperties(sol::object) override; private: // \todo replace with LuaText when skins are properly implemented @@ -22,12 +23,12 @@ namespace LuaUi MyGUI::IntPoint mPreviousMouse; MyGUI::IntCoord mChangeScale; + MyGUI::IntCoord mMoveResize; + protected: virtual void initialize() override; virtual void deinitialize() override; - bool setPropertyRaw(std::string_view name, sol::object value) override; - void notifyMousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); void notifyMouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton); }; From 989f09930a95c1f3adf3be36040ec32d3c130e40 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 28 Nov 2021 18:58:40 +0100 Subject: [PATCH 1786/2859] Use the scaled mesh translation for collision shape position for living actors. It seems only (some) dead actors really needs to use the vertical half-extent. --- apps/openmw/mwphysics/actor.hpp | 6 +++--- apps/openmw/mwphysics/movementsolver.cpp | 4 ---- apps/openmw/mwphysics/physicssystem.cpp | 3 ++- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 01d8037f6b..cdf49eeb54 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -158,6 +158,9 @@ namespace MWPhysics bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const; + /// Returns the mesh translation, scaled and rotated as necessary + osg::Vec3f getScaledMeshTranslation() const; + private: MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world @@ -165,9 +168,6 @@ namespace MWPhysics void addCollisionMask(int collisionMask); int getCollisionMask() const; - /// Returns the mesh translation, scaled and rotated as necessary - osg::Vec3f getScaledMeshTranslation() const; - bool mCanWaterWalk; bool mWalkingOnWater; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fc2ea57b40..fff4606c7f 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -134,8 +134,6 @@ namespace MWPhysics // Adjust for collision mesh offset relative to actor's "location" // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own) - // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation - // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() actor.mPosition.z() += actor.mHalfExtentsZ; // vanilla-accurate float swimlevel = actor.mSwimLevel + actor.mHalfExtentsZ; @@ -449,8 +447,6 @@ namespace MWPhysics } } - // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) - // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, actor.mHalfExtentsZ); // use a 3d approximation of the movement vector to better judge player intent diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 98e3bcf737..c2a4402d27 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -934,7 +934,8 @@ namespace MWPhysics , mRotation() , mMovement(actor.velocity()) , mWaterlevel(waterlevel) - , mHalfExtentsZ(actor.getHalfExtents().z()) + // for compatibility with vanilla assets, mesh offset is the actor halfextent for dead actors + , mHalfExtentsZ(actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead() ? actor.getHalfExtents().z() : actor.getScaledMeshTranslation().z()) , mOldHeight(0) , mStuckFrames(0) , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) From bc4b54157b4b5e558e8fc25072b708a1b48ba0ce Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 28 Nov 2021 20:30:16 +0000 Subject: [PATCH 1787/2859] Remove commented-out test conditions --- apps/openmw_test_suite/openmw/options.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index 9b8535ea36..04c209e8a4 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -113,7 +113,6 @@ namespace bpo::variables_map variables; parseArgs(arguments, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), R"(save)"); -// EXPECT_EQ(variables["load-savegame"].as().string(), R"("save".omwsave)"); } TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) @@ -222,7 +221,6 @@ namespace bpo::variables_map variables; Files::parseConfig(stream, variables, description); EXPECT_EQ(variables["load-savegame"].as().string(), ""); -// EXPECT_EQ(variables["load-savegame"].as().string(), R"(""save".omwsave")"); } TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path_with_space) From 5e9d460032d523a90628c61717ef6fcb187e80fc Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 28 Nov 2021 20:33:17 +0000 Subject: [PATCH 1788/2859] Remove redundant conversion operator --- components/files/configurationmanager.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 0c4d2dc835..4b641c12fd 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -79,8 +79,6 @@ void parseConfig(std::istream& stream, boost::program_options::variables_map& va class MaybeQuotedPath : public boost::filesystem::path { -public: - operator boost::filesystem::path() { return *this; } }; std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPath); From 01a9eaf4a85dd1355d8c8fdf36705b5f83b392ea Mon Sep 17 00:00:00 2001 From: kuyondo Date: Mon, 29 Nov 2021 19:04:34 +0800 Subject: [PATCH 1789/2859] fix#6451 --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 362df54ab7..e16bfd4794 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -289,13 +289,8 @@ namespace MWMechanics MWWorld::Ptr ptr = getPlayer(); MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); - // Update the equipped weapon icon MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon == inv.end()) - winMgr->unsetSelectedWeapon(); - else - winMgr->setSelectedWeapon(*weapon); // Update the selected spell icon MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem(); @@ -310,6 +305,12 @@ namespace MWMechanics winMgr->unsetSelectedSpell(); } + // Update the equipped weapon icon + if (weapon == inv.end()) + winMgr->unsetSelectedWeapon(); + else + winMgr->setSelectedWeapon(*weapon); + if (mUpdatePlayer) { mUpdatePlayer = false; From 5f355a14cdda016535863a8e75332ba097628517 Mon Sep 17 00:00:00 2001 From: Thomas Lowe Date: Mon, 29 Nov 2021 07:18:49 -0500 Subject: [PATCH 1790/2859] Changed default setting for anti-alias alpha test to true. Added checkbox in advanced page for anti-alias alpha test, connected to AA combobox in the graphics page. --- apps/launcher/advancedpage.cpp | 12 ++++++++++++ apps/launcher/advancedpage.hpp | 1 + apps/launcher/graphicspage.cpp | 3 +++ apps/launcher/graphicspage.hpp | 4 ++++ apps/launcher/maindialog.cpp | 2 +- files/settings-default.cfg | 2 +- files/ui/advancedpage.ui | 10 ++++++++++ 7 files changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index af1fa255db..c7e228878c 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -118,6 +118,11 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); loadSettingBool(radialFogCheckBox, "radial fog", "Shaders"); loadSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); + loadSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); + if (Settings::Manager::getInt("antialiasing", "Video") == 0) { + antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked); + antialiasAlphaTestCheckBox->setEnabled(false); + } loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); @@ -268,6 +273,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); saveSettingBool(radialFogCheckBox, "radial fog", "Shaders"); saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); + saveSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); @@ -439,6 +445,12 @@ void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) loadCellsForAutocomplete(cellNames); } +void Launcher::AdvancedPage::slotAASettingChanged(int aaLevel) { + antialiasAlphaTestCheckBox->setEnabled(aaLevel > 0); + if (aaLevel == 0) + antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked); +} + void Launcher::AdvancedPage::slotAnimSourcesToggled(bool checked) { weaponSheathingCheckBox->setEnabled(checked); diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index 1d16fae706..e26f1690e9 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -24,6 +24,7 @@ namespace Launcher public slots: void slotLoadedCellsChanged(QStringList cellNames); + void slotAASettingChanged(int aaLevel); private slots: void on_skipMenuCheckBox_stateChanged(int state); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 8359834ddb..06cc98c741 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -47,7 +47,10 @@ Launcher::GraphicsPage::GraphicsPage(QWidget *parent) connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int))); connect(framerateLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotFramerateLimitToggled(bool))); connect(shadowDistanceCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotShadowDistLimitToggled(bool))); +} +void Launcher::GraphicsPage::connectAntiAliasingChanged(const QObject* receiver, const char* slot) { + connect(antiAliasingComboBox, SIGNAL(currentIndexChanged(int)), receiver, slot); } bool Launcher::GraphicsPage::setupSDL() diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index a6754ccb04..9275bf8e3d 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -20,12 +20,16 @@ namespace Launcher public: explicit GraphicsPage(QWidget *parent = nullptr); + void connectAntiAliasingChanged(const QObject *receiver, const char *slot); void saveSettings(); bool loadSettings(); public slots: void screenChanged(int screen); + signals: + void signalAntiAliasingChanged(int aaValue); + private slots: void slotFullScreenChanged(int state); void slotStandardToggled(bool checked); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 75d4464b68..d58be7afcf 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -146,7 +146,7 @@ void Launcher::MainDialog::createPages() connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); // Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection); - + mGraphicsPage->connectAntiAliasingChanged(mAdvancedPage, SLOT(slotAASettingChanged(int))); } Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c0083e4238..0e2c329ff2 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -492,7 +492,7 @@ minimum interior brightness = 0.08 # Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. # This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. # When MSAA is off, this setting will have no visible effect, but might have a performance cost. -antialias alpha test = false +antialias alpha test = true # Soften intersection of blended particle systems with opaque geometry soft particles = false diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 0e80f02700..0952e660d8 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -454,6 +454,16 @@ + + + + <html><head/><body><p>Enables alpha testing for smoother anti-aliasing (Requires anti-aliasing to be enabled)</p></body></html> + + + Use anti-alias alpha testing. + + + From 5927924ebc0c99649b9598820050692de6cb3d75 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Mon, 29 Nov 2021 17:29:35 +0000 Subject: [PATCH 1791/2859] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 174eb9e4b4..09f1279bad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,7 +82,8 @@ Bug #6416: Morphs are applied to the wrong target Bug #6417: OpenMW doesn't always use the right node to accumulate movement Bug #6429: Wyrmhaven: Can't add AI packages to player - Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened. + Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened + Bug #6451: Weapon summoned from Cast When Used item will have the name "None" Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record From b991263a92beb2c4bb78d7ce98ca698a49b4d7f6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 29 Nov 2021 20:16:49 +0000 Subject: [PATCH 1792/2859] Work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89062 --- components/files/configurationmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index bb22e4750f..c2cd44960f 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -327,13 +327,13 @@ std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPat istream >> static_cast(MaybeQuotedPath); if (istream && !istream.eof() && istream.peek() != EOF) { - std::string remainder(std::istreambuf_iterator(istream), {}); + std::string remainder{std::istreambuf_iterator(istream), {}}; Log(Debug::Warning) << "Trailing data in path setting. Used '" << MaybeQuotedPath.string() << "' but '" << remainder << "' remained"; } } else { - std::string intermediate(std::istreambuf_iterator(istream), {}); + std::string intermediate{std::istreambuf_iterator(istream), {}}; static_cast(MaybeQuotedPath) = intermediate; } return istream; From 47219b4def6632ba2efb843b22209b6f3224e1d5 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Nov 2021 15:15:22 +0100 Subject: [PATCH 1793/2859] Avoid base class call antipattern in classes derived from ContentLoader --- apps/openmw/mwworld/contentloader.hpp | 25 +++--------- apps/openmw/mwworld/esmloader.cpp | 27 ++++++------- apps/openmw/mwworld/esmloader.hpp | 10 ++--- apps/openmw/mwworld/worldimp.cpp | 57 ++++++++++++++------------- 4 files changed, 52 insertions(+), 67 deletions(-) diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b529ae9db8..55de77ad25 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -2,33 +2,20 @@ #define CONTENTLOADER_HPP #include -#include -#include -#include "components/loadinglistener/loadinglistener.hpp" +namespace Loading +{ + class Listener; +} namespace MWWorld { struct ContentLoader { - ContentLoader(Loading::Listener& listener) - : mListener(listener) - { - } - - virtual ~ContentLoader() - { - } - - virtual void load(const boost::filesystem::path& filepath, int& index) - { - Log(Debug::Info) << "Loading content file " << filepath.string(); - mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); - } + virtual ~ContentLoader() = default; - protected: - Loading::Listener& mListener; + virtual void load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) = 0; }; } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 1917c41428..8e702e7447 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -27,25 +27,22 @@ namespace MWWorld { EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& readers, - ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener) - : ContentLoader(listener) - , mEsm(readers) - , mStore(store) - , mEncoder(encoder) + ToUTF8::Utf8Encoder* encoder) + : mEsm(readers) + , mStore(store) + , mEncoder(encoder) { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) { - ContentLoader::load(filepath.filename(), index); - - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.open(filepath.string()); - lEsm.resolveParentFileIndices(mEsm); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.open(filepath.string()); + lEsm.resolveParentFileIndices(mEsm); + mEsm[index] = lEsm; + mStore.load(mEsm[index], listener); } void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats) diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 50631603de..533cf383ad 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -26,14 +26,14 @@ class ESMStore; struct EsmLoader : public ContentLoader { EsmLoader(MWWorld::ESMStore& store, std::vector& readers, - ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); + ToUTF8::Utf8Encoder* encoder); - void load(const boost::filesystem::path& filepath, int& index) override; + void load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) override; private: - std::vector& mEsm; - MWWorld::ESMStore& mStore; - ToUTF8::Utf8Encoder* mEncoder; + std::vector& mEsm; + MWWorld::ESMStore& mStore; + ToUTF8::Utf8Encoder* mEncoder; }; void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index db7bc0debd..f0ac894cfc 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include @@ -30,6 +32,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -79,43 +83,40 @@ namespace MWWorld { struct GameContentLoader : public ContentLoader { - GameContentLoader(Loading::Listener& listener) - : ContentLoader(listener) - { - } - - bool addLoader(const std::string& extension, ContentLoader* loader) + void addLoader(std::string&& extension, ContentLoader& loader) { - return mLoaders.insert(std::make_pair(extension, loader)).second; + mLoaders.emplace(std::move(extension), &loader); } - void load(const boost::filesystem::path& filepath, int& index) override + void load(const boost::filesystem::path& filepath, int& index, Loading::Listener* listener) override { - LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); + const auto it = mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string())); if (it != mLoaders.end()) { - it->second->load(filepath, index); + const std::string filename = filepath.filename().string(); + Log(Debug::Info) << "Loading content file " << filename; + if (listener != nullptr) + listener->setLabel(MyGUI::TextIterator::toTagsString(filename)); + it->second->load(filepath, index, listener); } else { - std::string msg("Cannot load file: "); - msg += filepath.string(); - throw std::runtime_error(msg.c_str()); + std::string msg("Cannot load file: "); + msg += filepath.string(); + throw std::runtime_error(msg.c_str()); } } private: - typedef std::map LoadersContainer; - LoadersContainer mLoaders; + std::map mLoaders; }; struct OMWScriptsLoader : public ContentLoader { ESMStore& mStore; - OMWScriptsLoader(Loading::Listener& listener, ESMStore& store) : ContentLoader(listener), mStore(store) {} - void load(const boost::filesystem::path& filepath, int& index) override + OMWScriptsLoader(ESMStore& store) : mStore(store) {} + void load(const boost::filesystem::path& filepath, int& /*index*/, Loading::Listener* /*listener*/) override { - ContentLoader::load(filepath.filename(), index); mStore.addOMWScripts(filepath.string()); } }; @@ -2936,18 +2937,18 @@ namespace MWWorld void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { - GameContentLoader gameContentLoader(*listener); - EsmLoader esmLoader(store, readers, encoder, *listener); + GameContentLoader gameContentLoader; + EsmLoader esmLoader(store, readers, encoder); validateMasterFiles(readers); - gameContentLoader.addLoader(".esm", &esmLoader); - gameContentLoader.addLoader(".esp", &esmLoader); - gameContentLoader.addLoader(".omwgame", &esmLoader); - gameContentLoader.addLoader(".omwaddon", &esmLoader); - gameContentLoader.addLoader(".project", &esmLoader); + gameContentLoader.addLoader(".esm", esmLoader); + gameContentLoader.addLoader(".esp", esmLoader); + gameContentLoader.addLoader(".omwgame", esmLoader); + gameContentLoader.addLoader(".omwaddon", esmLoader); + gameContentLoader.addLoader(".project", esmLoader); - OMWScriptsLoader omwScriptsLoader(*listener, store); - gameContentLoader.addLoader(".omwscripts", &omwScriptsLoader); + OMWScriptsLoader omwScriptsLoader(store); + gameContentLoader.addLoader(".omwscripts", omwScriptsLoader); int idx = 0; for (const std::string &file : content) @@ -2956,7 +2957,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - gameContentLoader.load(col.getPath(file), idx); + gameContentLoader.load(col.getPath(file), idx, listener); } else { From c4cd2f36c33ace443fb89fa3b98cf8a2a5f0daf6 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 23 Nov 2021 15:26:43 +0100 Subject: [PATCH 1794/2859] Move convertMagicEffects into separate file As completely unrelated to EsmLoader. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/esmloader.cpp | 203 -------------------------- apps/openmw/mwworld/esmloader.hpp | 5 - apps/openmw/mwworld/magiceffects.cpp | 210 +++++++++++++++++++++++++++ apps/openmw/mwworld/magiceffects.hpp | 17 +++ apps/openmw/mwworld/player.cpp | 1 + 7 files changed, 230 insertions(+), 209 deletions(-) create mode 100644 apps/openmw/mwworld/magiceffects.cpp create mode 100644 apps/openmw/mwworld/magiceffects.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7aa2928da5..2582b272cf 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -74,7 +74,7 @@ add_openmw_dir (mwworld actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref weather projectilemanager - cellpreloader datetimemanager groundcoverstore + cellpreloader datetimemanager groundcoverstore magiceffects ) add_openmw_dir (mwphysics diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 0448d0e28a..052cc35b45 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1,4 +1,5 @@ #include "cellstore.hpp" +#include "magiceffects.hpp" #include diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 8e702e7447..de16e386f2 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -2,26 +2,6 @@ #include "esmstore.hpp" #include -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwmechanics/magiceffects.hpp" - -namespace -{ - template - void getEnchantedItem(const std::string& id, std::string& enchantment, std::string& itemName) - { - const T* item = MWBase::Environment::get().getWorld()->getStore().get().search(id); - if(item) - { - enchantment = item->mEnchant; - itemName = item->mName; - } - } -} namespace MWWorld { @@ -45,187 +25,4 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index, Loadin mStore.load(mEsm[index], listener); } - void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats) - { - const auto& store = MWBase::Environment::get().getWorld()->getStore(); - // Convert corprus to format 10 - for (const auto& [id, oldStats] : creatureStats.mSpells.mCorprusSpells) - { - const ESM::Spell* spell = store.get().search(id); - if (!spell) - continue; - - ESM::CreatureStats::CorprusStats stats; - stats.mNextWorsening = oldStats.mNextWorsening; - for (int i=0; imEffects.mList) - { - if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) - stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; - } - creatureStats.mCorprusSpells[id] = stats; - } - // Convert to format 17 - for(const auto& [id, oldParams] : creatureStats.mSpells.mSpellParams) - { - const ESM::Spell* spell = store.get().search(id); - 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.mDisplayName = spell->mName; - params.mItem.unset(); - params.mCasterActorId = creatureStats.mActorId; - if(spell->mData.mType == ESM::Spell::ST_Ability) - params.mType = ESM::ActiveSpells::Type_Ability; - else - params.mType = ESM::ActiveSpells::Type_Permanent; - params.mWorsenings = -1; - int effectIndex = 0; - for(const auto& enam : spell->mEffects.mList) - { - if(oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) - { - ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; - effect.mDuration = -1; - effect.mTimeLeft = -1; - effect.mEffectIndex = effectIndex; - auto rand = oldParams.mEffectRands.find(effectIndex); - if(rand != oldParams.mEffectRands.end()) - { - float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; - effect.mMagnitude = magnitude; - effect.mMinMagnitude = magnitude; - effect.mMaxMagnitude = magnitude; - // 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; - } - else - { - effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; - effect.mFlags = ESM::ActiveEffect::Flag_None; - } - params.mEffects.emplace_back(effect); - } - effectIndex++; - } - creatureStats.mActiveSpells.mSpells.emplace_back(params); - } - std::multimap equippedItems; - for(std::size_t i = 0; i < inventory.mItems.size(); ++i) - { - const ESM::ObjectState& item = inventory.mItems[i]; - auto slot = inventory.mEquipmentSlots.find(i); - if(slot != inventory.mEquipmentSlots.end()) - equippedItems.emplace(item.mRef.mRefID, slot->second); - } - for(const auto& [id, oldMagnitudes] : inventory.mPermanentMagicEffectMagnitudes) - { - std::string eId; - std::string name; - switch(store.find(id)) - { - case ESM::REC_ARMO: - getEnchantedItem(id, eId, name); - break; - case ESM::REC_CLOT: - getEnchantedItem(id, eId, name); - break; - case ESM::REC_WEAP: - getEnchantedItem(id, eId, name); - break; - } - if(eId.empty()) - continue; - const ESM::Enchantment* enchantment = store.get().search(eId); - if(!enchantment) - continue; - ESM::ActiveSpells::ActiveSpellParams params; - params.mId = id; - params.mDisplayName = name; - params.mCasterActorId = creatureStats.mActorId; - params.mType = ESM::ActiveSpells::Type_Enchantment; - params.mWorsenings = -1; - for(std::size_t effectIndex = 0; effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) - { - const auto& enam = enchantment->mEffects.mList[effectIndex]; - auto [random, multiplier] = oldMagnitudes[effectIndex]; - float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; - magnitude *= multiplier; - if(magnitude <= 0) - continue; - ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mMagnitude = magnitude; - effect.mMinMagnitude = magnitude; - effect.mMaxMagnitude = magnitude; - effect.mArg = MWMechanics::EffectKey(enam).mArg; - effect.mDuration = -1; - effect.mTimeLeft = -1; - effect.mEffectIndex = static_cast(effectIndex); - // 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; - params.mEffects.emplace_back(effect); - } - auto [begin, end] = equippedItems.equal_range(id); - for(auto it = begin; it != end; ++it) - { - params.mItem = { static_cast(it->second), 0 }; - creatureStats.mActiveSpells.mSpells.emplace_back(params); - } - } - for(const auto& spell : creatureStats.mCorprusSpells) - { - auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), [&] (const auto& params) { return params.mId == spell.first; }); - if(it != creatureStats.mActiveSpells.mSpells.end()) - { - it->mNextWorsening = spell.second.mNextWorsening; - int worsenings = 0; - for(int i = 0; i < ESM::Attribute::Length; ++i) - worsenings = std::max(spell.second.mWorsenings[i], worsenings); - it->mWorsenings = worsenings; - } - } - for(const auto& [key, actorId] : creatureStats.mSummonedCreatureMap) - { - if(actorId == -1) - continue; - for(auto& params : creatureStats.mActiveSpells.mSpells) - { - if(params.mId == key.mSourceId) - { - bool found = false; - for(auto& effect : params.mEffects) - { - if(effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex) - { - effect.mArg = actorId; - found = true; - break; - } - } - if(found) - break; - } - } - } - // Reset modifiers that were previously recalculated each frame - for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) - creatureStats.mAttributes[i].mMod = 0.f; - for(std::size_t i = 0; i < 3; ++i) - creatureStats.mDynamic[i].mMod = 0.f; - for(std::size_t i = 0; i < 4; ++i) - creatureStats.mAiSettings[i].mMod = 0.f; - if(npcStats) - { - for(std::size_t i = 0; i < ESM::Skill::Length; ++i) - npcStats->mSkills[i].mMod = 0.f; - } - } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 533cf383ad..db50d44146 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -13,9 +13,6 @@ namespace ToUTF8 namespace ESM { class ESMReader; - struct CreatureStats; - struct InventoryState; - struct NpcStats; } namespace MWWorld @@ -36,8 +33,6 @@ struct EsmLoader : public ContentLoader ToUTF8::Utf8Encoder* mEncoder; }; -void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr); - } /* namespace MWWorld */ #endif // ESMLOADER_HPP diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp new file mode 100644 index 0000000000..7d7e2857fe --- /dev/null +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -0,0 +1,210 @@ +#include "magiceffects.hpp" +#include "esmstore.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/magiceffects.hpp" + +namespace +{ + template + void getEnchantedItem(const std::string& id, std::string& enchantment, std::string& itemName) + { + const T* item = MWBase::Environment::get().getWorld()->getStore().get().search(id); + if(item) + { + enchantment = item->mEnchant; + itemName = item->mName; + } + } +} + +namespace MWWorld +{ + void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats) + { + const auto& store = MWBase::Environment::get().getWorld()->getStore(); + // Convert corprus to format 10 + for (const auto& [id, oldStats] : creatureStats.mSpells.mCorprusSpells) + { + const ESM::Spell* spell = store.get().search(id); + if (!spell) + continue; + + ESM::CreatureStats::CorprusStats stats; + stats.mNextWorsening = oldStats.mNextWorsening; + for (int i=0; imEffects.mList) + { + if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; + } + creatureStats.mCorprusSpells[id] = stats; + } + // Convert to format 17 + for(const auto& [id, oldParams] : creatureStats.mSpells.mSpellParams) + { + const ESM::Spell* spell = store.get().search(id); + 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.mDisplayName = spell->mName; + params.mItem.unset(); + params.mCasterActorId = creatureStats.mActorId; + if(spell->mData.mType == ESM::Spell::ST_Ability) + params.mType = ESM::ActiveSpells::Type_Ability; + else + params.mType = ESM::ActiveSpells::Type_Permanent; + params.mWorsenings = -1; + int effectIndex = 0; + for(const auto& enam : spell->mEffects.mList) + { + if(oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) + { + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effect.mEffectIndex = effectIndex; + auto rand = oldParams.mEffectRands.find(effectIndex); + if(rand != oldParams.mEffectRands.end()) + { + float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; + effect.mMagnitude = magnitude; + effect.mMinMagnitude = magnitude; + effect.mMaxMagnitude = magnitude; + // 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; + } + else + { + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mMagnMin; + effect.mMaxMagnitude = enam.mMagnMax; + effect.mFlags = ESM::ActiveEffect::Flag_None; + } + params.mEffects.emplace_back(effect); + } + effectIndex++; + } + creatureStats.mActiveSpells.mSpells.emplace_back(params); + } + std::multimap equippedItems; + for(std::size_t i = 0; i < inventory.mItems.size(); ++i) + { + const ESM::ObjectState& item = inventory.mItems[i]; + auto slot = inventory.mEquipmentSlots.find(i); + if(slot != inventory.mEquipmentSlots.end()) + equippedItems.emplace(item.mRef.mRefID, slot->second); + } + for(const auto& [id, oldMagnitudes] : inventory.mPermanentMagicEffectMagnitudes) + { + std::string eId; + std::string name; + switch(store.find(id)) + { + case ESM::REC_ARMO: + getEnchantedItem(id, eId, name); + break; + case ESM::REC_CLOT: + getEnchantedItem(id, eId, name); + break; + case ESM::REC_WEAP: + getEnchantedItem(id, eId, name); + break; + } + if(eId.empty()) + continue; + const ESM::Enchantment* enchantment = store.get().search(eId); + if(!enchantment) + continue; + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = id; + params.mDisplayName = name; + params.mCasterActorId = creatureStats.mActorId; + params.mType = ESM::ActiveSpells::Type_Enchantment; + params.mWorsenings = -1; + for(std::size_t effectIndex = 0; effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) + { + const auto& enam = enchantment->mEffects.mList[effectIndex]; + auto [random, multiplier] = oldMagnitudes[effectIndex]; + float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; + magnitude *= multiplier; + if(magnitude <= 0) + continue; + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mMagnitude = magnitude; + effect.mMinMagnitude = magnitude; + effect.mMaxMagnitude = magnitude; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effect.mEffectIndex = static_cast(effectIndex); + // 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; + params.mEffects.emplace_back(effect); + } + auto [begin, end] = equippedItems.equal_range(id); + for(auto it = begin; it != end; ++it) + { + params.mItem = { static_cast(it->second), 0 }; + creatureStats.mActiveSpells.mSpells.emplace_back(params); + } + } + for(const auto& spell : creatureStats.mCorprusSpells) + { + auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), [&] (const auto& params) { return params.mId == spell.first; }); + if(it != creatureStats.mActiveSpells.mSpells.end()) + { + it->mNextWorsening = spell.second.mNextWorsening; + int worsenings = 0; + for(int i = 0; i < ESM::Attribute::Length; ++i) + worsenings = std::max(spell.second.mWorsenings[i], worsenings); + it->mWorsenings = worsenings; + } + } + for(const auto& [key, actorId] : creatureStats.mSummonedCreatureMap) + { + if(actorId == -1) + continue; + for(auto& params : creatureStats.mActiveSpells.mSpells) + { + if(params.mId == key.mSourceId) + { + bool found = false; + for(auto& effect : params.mEffects) + { + if(effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex) + { + effect.mArg = actorId; + found = true; + break; + } + } + if(found) + break; + } + } + } + // Reset modifiers that were previously recalculated each frame + for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) + creatureStats.mAttributes[i].mMod = 0.f; + for(std::size_t i = 0; i < 3; ++i) + creatureStats.mDynamic[i].mMod = 0.f; + for(std::size_t i = 0; i < 4; ++i) + creatureStats.mAiSettings[i].mMod = 0.f; + if(npcStats) + { + for(std::size_t i = 0; i < ESM::Skill::Length; ++i) + npcStats->mSkills[i].mMod = 0.f; + } + } +} diff --git a/apps/openmw/mwworld/magiceffects.hpp b/apps/openmw/mwworld/magiceffects.hpp new file mode 100644 index 0000000000..31d5ed2038 --- /dev/null +++ b/apps/openmw/mwworld/magiceffects.hpp @@ -0,0 +1,17 @@ +#ifndef OPENMW_MWWORLD_MAGICEFFECTS_H +#define OPENMW_MWWORLD_MAGICEFFECTS_H + +namespace ESM +{ + struct CreatureStats; + struct InventoryState; + struct NpcStats; +} + +namespace MWWorld +{ + void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, + ESM::NpcStats* npcStats = nullptr); +} + +#endif diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index caa0600f7c..4687a4eddd 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -12,6 +12,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/magiceffects.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" From 4f77c18af4b2df9abd13f5106591e75683089d99 Mon Sep 17 00:00:00 2001 From: Thomas Lowe Date: Mon, 29 Nov 2021 20:44:47 -0500 Subject: [PATCH 1795/2859] Removed unneeded signal. --- apps/launcher/graphicspage.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 9275bf8e3d..d86b570914 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -27,9 +27,6 @@ namespace Launcher public slots: void screenChanged(int screen); - signals: - void signalAntiAliasingChanged(int aaValue); - private slots: void slotFullScreenChanged(int state); void slotStandardToggled(bool checked); From bdb7b6079ee073baa465b2c1c75f42f42a9ebfc8 Mon Sep 17 00:00:00 2001 From: Thomas Lowe Date: Mon, 29 Nov 2021 20:49:31 -0500 Subject: [PATCH 1796/2859] Removed a period from the checkbox's display text to conform with other buttons. --- files/ui/advancedpage.ui | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 0952e660d8..f782c6caa0 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -14,7 +14,7 @@ - 0 + 1 @@ -281,8 +281,8 @@ 0 0 - 645 - 413 + 665 + 525 @@ -454,16 +454,16 @@ - - - - <html><head/><body><p>Enables alpha testing for smoother anti-aliasing (Requires anti-aliasing to be enabled)</p></body></html> - - - Use anti-alias alpha testing. - - - + + + + <html><head/><body><p>Enables alpha testing for smoother anti-aliasing (Requires anti-aliasing to be enabled)</p></body></html> + + + Use anti-alias alpha testing + + + From 95d7bdd0c9b469c41dfc51613a45a42538aaab88 Mon Sep 17 00:00:00 2001 From: Thomas Lowe Date: Tue, 30 Nov 2021 07:50:30 -0500 Subject: [PATCH 1797/2859] alpha test defaults to false --- files/settings-default.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 0e2c329ff2..c0083e4238 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -492,7 +492,7 @@ minimum interior brightness = 0.08 # Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. # This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. # When MSAA is off, this setting will have no visible effect, but might have a performance cost. -antialias alpha test = true +antialias alpha test = false # Soften intersection of blended particle systems with opaque geometry soft particles = false From c5b33185b543dd7247ac64315b0b866314e48fd8 Mon Sep 17 00:00:00 2001 From: OnlyForF1 <10170702-OnlyForF1@users.noreply.gitlab.com> Date: Tue, 30 Nov 2021 16:00:30 +0000 Subject: [PATCH 1798/2859] Recalculate the Projection Matrix every time the window is resized. --- apps/openmw/mwgui/windowmanagerimp.cpp | 17 ++++++++++--- apps/openmw/mwrender/postprocessor.cpp | 5 +--- apps/openmw/mwrender/postprocessor.hpp | 6 +---- apps/openmw/mwrender/renderingmanager.cpp | 18 ++++++++++--- components/settings/categories.hpp | 2 +- components/settings/settings.cpp | 31 ++++++++++++++++++----- components/settings/settings.hpp | 26 ++++++++++++------- 7 files changed, 72 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index a935d7f900..94a064a6c8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1098,12 +1098,21 @@ namespace MWGui void WindowManager::windowResized(int x, int y) { - // Note: this is a side effect of resolution change or window resize. - // There is no need to track these changes. Settings::Manager::setInt("resolution x", "Video", x); Settings::Manager::setInt("resolution y", "Video", y); - Settings::Manager::resetPendingChange("resolution x", "Video"); - Settings::Manager::resetPendingChange("resolution y", "Video"); + + // We only want to process changes to window-size related settings. + Settings::CategorySettingVector filter = {{"Video", "resolution x"}, + {"Video", "resolution y"}}; + + // If the HUD has not been initialised, the World singleton will not be available. + if (mHud) + { + MWBase::Environment::get().getWorld()->processChangedSettings( + Settings::Manager::getPendingChanges(filter)); + } + + Settings::Manager::resetPendingChanges(filter); mGuiPlatform->getRenderManagerPtr()->setViewSize(x, y); diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index b08cfe1f71..53fae4cd7d 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -14,7 +14,6 @@ #include #include "vismask.hpp" -#include "renderingmanager.hpp" namespace { @@ -132,11 +131,10 @@ namespace namespace MWRender { - PostProcessor::PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode) + PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode) : mViewer(viewer) , mRootNode(new osg::Group) , mDepthFormat(GL_DEPTH_COMPONENT24) - , mRendering(rendering) { bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); @@ -239,7 +237,6 @@ namespace MWRender mViewer->getCamera()->resize(width, height); mHUDCamera->resize(width, height); - mRendering.updateProjectionMatrix(); } void PostProcessor::createTexturesAndCamera(int width, int height) diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index cc5128d8f9..f2ef238737 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -14,12 +14,10 @@ namespace osgViewer namespace MWRender { - class RenderingManager; - class PostProcessor : public osg::Referenced { public: - PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode); + PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode); auto getMsaaFbo() { return mMsaaFbo; } auto getFbo() { return mFbo; } @@ -46,8 +44,6 @@ namespace MWRender osg::ref_ptr mOpaqueDepthTex; int mDepthFormat; - - RenderingManager& mRendering; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 30be74c839..b15aaf6f06 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -454,7 +454,7 @@ namespace MWRender mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover); rootNode->addUpdateCallback(mSharedUniformStateUpdater); - mPostProcessor = new PostProcessor(*this, viewer, mRootNode); + mPostProcessor = new PostProcessor(viewer, mRootNode); resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat()); resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex()); @@ -1208,19 +1208,26 @@ namespace MWRender void RenderingManager::processChangedSettings(const Settings::CategorySettingVector &changed) { + // Only perform a projection matrix update once if a relevant setting is changed. + bool updateProjection = false; + for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { if (it->first == "Camera" && it->second == "field of view") { mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); - updateProjectionMatrix(); + updateProjection = true; + } + else if (it->first == "Video" && (it->second == "resolution x" || it->second == "resolution y")) + { + updateProjection = true; } else if (it->first == "Camera" && it->second == "viewing distance") { mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); if(!Settings::Manager::getBool("use distant fog", "Fog")) mStateUpdater->setFogEnd(mViewDistance); - updateProjectionMatrix(); + updateProjection = true; } else if (it->first == "General" && (it->second == "texture filter" || it->second == "texture mipmap" || @@ -1263,6 +1270,11 @@ namespace MWRender } } } + + if (updateProjection) + { + updateProjectionMatrix(); + } } float RenderingManager::getNearClipDistance() const diff --git a/components/settings/categories.hpp b/components/settings/categories.hpp index d6cd042f61..6a2da2fa10 100644 --- a/components/settings/categories.hpp +++ b/components/settings/categories.hpp @@ -9,7 +9,7 @@ namespace Settings { using CategorySetting = std::pair; - using CategorySettingVector = std::set>; + using CategorySettingVector = std::set; using CategorySettingValueMap = std::map; } diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 09a3d1f516..cef627acd4 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -39,7 +39,7 @@ void Manager::saveUser(const std::string &file) std::string Manager::getString(const std::string &setting, const std::string &category) { - CategorySettingValueMap::key_type key = std::make_pair(category, setting); + CategorySettingValueMap::key_type key (category, setting); CategorySettingValueMap::iterator it = mUserSettings.find(key); if (it != mUserSettings.end()) return it->second; @@ -93,7 +93,7 @@ osg::Vec2f Manager::getVector2 (const std::string& setting, const std::string& c stream >> x >> y; if (stream.fail()) throw std::runtime_error(std::string("Can't parse 2d vector: " + value)); - return osg::Vec2f(x, y); + return {x, y}; } osg::Vec3f Manager::getVector3 (const std::string& setting, const std::string& category) @@ -104,14 +104,14 @@ osg::Vec3f Manager::getVector3 (const std::string& setting, const std::string& c stream >> x >> y >> z; if (stream.fail()) throw std::runtime_error(std::string("Can't parse 3d vector: " + value)); - return osg::Vec3f(x, y, z); + return {x, y, z}; } void Manager::setString(const std::string &setting, const std::string &category, const std::string &value) { - CategorySettingValueMap::key_type key = std::make_pair(category, setting); + CategorySettingValueMap::key_type key (category, setting); - CategorySettingValueMap::iterator found = mUserSettings.find(key); + auto found = mUserSettings.find(key); if (found != mUserSettings.end()) { if (found->second == value) @@ -165,18 +165,35 @@ void Manager::setVector3 (const std::string &setting, const std::string &categor void Manager::resetPendingChange(const std::string &setting, const std::string &category) { - CategorySettingValueMap::key_type key = std::make_pair(category, setting); + CategorySettingValueMap::key_type key (category, setting); mChangedSettings.erase(key); } -const CategorySettingVector Manager::getPendingChanges() +CategorySettingVector Manager::getPendingChanges() { return mChangedSettings; } +CategorySettingVector Manager::getPendingChanges(const CategorySettingVector& filter) +{ + CategorySettingVector intersection; + std::set_intersection(mChangedSettings.begin(), mChangedSettings.end(), + filter.begin(), filter.end(), + std::inserter(intersection, intersection.begin())); + return intersection; +} + void Manager::resetPendingChanges() { mChangedSettings.clear(); } +void Manager::resetPendingChanges(const CategorySettingVector& filter) +{ + for (const auto& key : filter) + { + mChangedSettings.erase(key); + } +} + } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index e3a29d4c34..21d5aff770 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -36,11 +36,19 @@ namespace Settings ///< save user settings to file static void resetPendingChange(const std::string &setting, const std::string &category); + ///< resets a single pending change static void resetPendingChanges(); + ///< resets the list of all pending changes - static const CategorySettingVector getPendingChanges(); - ///< returns the list of changed settings and then clears it + static void resetPendingChanges(const CategorySettingVector& filter); + ///< resets only the pending changes listed in the filter + + static CategorySettingVector getPendingChanges(); + ///< returns the list of changed settings + + static CategorySettingVector getPendingChanges(const CategorySettingVector& filter); + ///< returns the list of changed settings intersecting with the filter static int getInt (const std::string& setting, const std::string& category); static float getFloat (const std::string& setting, const std::string& category); @@ -50,15 +58,15 @@ namespace Settings static osg::Vec2f getVector2 (const std::string& setting, const std::string& category); static osg::Vec3f getVector3 (const std::string& setting, const std::string& category); - static void setInt (const std::string& setting, const std::string& category, const int value); - static void setFloat (const std::string& setting, const std::string& category, const float value); - static void setDouble (const std::string& setting, const std::string& category, const double value); + static void setInt (const std::string& setting, const std::string& category, int value); + static void setFloat (const std::string& setting, const std::string& category, float value); + static void setDouble (const std::string& setting, const std::string& category, double value); static void setString (const std::string& setting, const std::string& category, const std::string& value); - static void setBool (const std::string& setting, const std::string& category, const bool value); - static void setVector2 (const std::string& setting, const std::string& category, const osg::Vec2f value); - static void setVector3 (const std::string& setting, const std::string& category, const osg::Vec3f value); + static void setBool (const std::string& setting, const std::string& category, bool value); + static void setVector2 (const std::string& setting, const std::string& category, osg::Vec2f value); + static void setVector3 (const std::string& setting, const std::string& category, osg::Vec3f value); }; } -#endif // _COMPONENTS_SETTINGS_H +#endif // COMPONENTS_SETTINGS_H From f1ec8db393f5ff7bae77bf0ba16540763c5b56e9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 20 Nov 2021 00:15:18 +0100 Subject: [PATCH 1799/2859] Make Set- and ModStat behave as they should --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/stat.cpp | 15 +++++++- apps/openmw/mwmechanics/stat.hpp | 7 ++-- apps/openmw/mwscript/statsextensions.cpp | 47 ++++++++++-------------- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f5b83f158..0cff18a5f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Bug #1751: Birthsign abilities increase modified attribute values instead of base ones Bug #1930: Followers are still fighting if a target stops combat with a leader + Bug #2036: SetStat and ModStat instructions aren't implemented the same way as in Morrowind Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3488: AI combat aiming is too slow Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index c87de2ccbb..ee484f5afd 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -246,14 +246,25 @@ namespace MWMechanics return mModifier; } - void AttributeValue::setBase(float base) + void AttributeValue::setBase(float base, bool clearModifier) { mBase = base; + if(clearModifier) + { + mModifier = 0.f; + mDamage = 0.f; + } } void AttributeValue::setModifier(float mod) { - mModifier = mod; + if(mod < 0) + { + mModifier = 0.f; + mDamage -= mod; + } + else + mModifier = mod; } void AttributeValue::damage(float damage) diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index fb9dca9221..c80c5b1b70 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -133,13 +133,14 @@ namespace MWMechanics float getBase() const; float getModifier() const; - void setBase(float base); + void setBase(float base, bool clearModifier = false); void setModifier(float mod); // Maximum attribute damage is limited to the modified value. - // Note: I think MW applies damage directly to mModified, since you can also - // "restore" drained attributes. We need to rewrite the magic effect system to support this. + // Note: MW applies damage directly to mModified, however it does track how much + // a damaged attribute that has been fortified beyond its base can be restored. + // Getting rid of mDamage would require calculating its value by ignoring active effects when restoring void damage(float damage); void restore(float amount); diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index ccad186a0d..e57a9570f2 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -40,6 +40,22 @@ namespace return factionId; } + + void modStat(MWMechanics::AttributeValue& stat, float amount) + { + float base = stat.getBase(); + float modifier = stat.getModifier() - stat.getDamage(); + float modified = base + modifier; + if(modified <= 0.f && amount < 0.f) + amount = 0.f; + else if(amount < 0.f && modified + amount < 0.f) + amount = -modified; + else if((modifier <= 0.f || base >= 100.f) && amount > 0.f) + amount = std::clamp(100.f - modified, 0.f, amount); + stat.setBase(std::min(base + amount, 100.f), true); + modifier += base - stat.getBase() + amount; + stat.setModifier(modifier); + } } namespace MWScript @@ -122,7 +138,7 @@ namespace MWScript runtime.pop(); MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); - attribute.setBase (value); + attribute.setBase(value, true); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; @@ -146,19 +162,7 @@ namespace MWScript MWMechanics::AttributeValue attribute = ptr.getClass() .getCreatureStats(ptr) .getAttribute(mIndex); - - if (value == 0) - return; - - if (((attribute.getBase() <= 0) && (value < 0)) - || ((attribute.getBase() >= 100) && (value > 0))) - return; - - if (value < 0) - attribute.setBase(std::max(0.f, attribute.getBase() + value)); - else - attribute.setBase(std::min(100.f, attribute.getBase() + value)); - + modStat(attribute, value); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; @@ -372,7 +376,7 @@ namespace MWScript MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats (ptr); - stats.getSkill (mIndex).setBase (value); + stats.getSkill(mIndex).setBase(value, true); } }; @@ -395,18 +399,7 @@ namespace MWScript MWMechanics::SkillValue &skill = ptr.getClass() .getNpcStats(ptr) .getSkill(mIndex); - - if (value == 0) - return; - - if (((skill.getBase() <= 0.f) && (value < 0.f)) - || ((skill.getBase() >= 100.f) && (value > 0.f))) - return; - - if (value < 0) - skill.setBase(std::max(0.f, skill.getBase() + value)); - else - skill.setBase(std::min(100.f, skill.getBase() + value)); + modStat(skill, value); } }; From f50cbcad812845e2712671c2e9db99d8333fbedc Mon Sep 17 00:00:00 2001 From: Thomas Lowe Date: Tue, 30 Nov 2021 20:07:29 -0500 Subject: [PATCH 1800/2859] * AA combo box no longer disables the AA alpha test checkbox. * Updated AA alpha test description to be more accurate --- apps/launcher/advancedpage.cpp | 7 ------- apps/launcher/advancedpage.hpp | 1 - apps/launcher/graphicspage.cpp | 4 ---- apps/launcher/graphicspage.hpp | 1 - apps/launcher/maindialog.cpp | 1 - files/ui/advancedpage.ui | 2 +- 6 files changed, 1 insertion(+), 15 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index c7e228878c..d09704851a 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -121,7 +121,6 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); if (Settings::Manager::getInt("antialiasing", "Video") == 0) { antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked); - antialiasAlphaTestCheckBox->setEnabled(false); } loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool))); @@ -445,12 +444,6 @@ void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) loadCellsForAutocomplete(cellNames); } -void Launcher::AdvancedPage::slotAASettingChanged(int aaLevel) { - antialiasAlphaTestCheckBox->setEnabled(aaLevel > 0); - if (aaLevel == 0) - antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked); -} - void Launcher::AdvancedPage::slotAnimSourcesToggled(bool checked) { weaponSheathingCheckBox->setEnabled(checked); diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index e26f1690e9..1d16fae706 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -24,7 +24,6 @@ namespace Launcher public slots: void slotLoadedCellsChanged(QStringList cellNames); - void slotAASettingChanged(int aaLevel); private slots: void on_skipMenuCheckBox_stateChanged(int state); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 06cc98c741..e6d857a943 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -49,10 +49,6 @@ Launcher::GraphicsPage::GraphicsPage(QWidget *parent) connect(shadowDistanceCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotShadowDistLimitToggled(bool))); } -void Launcher::GraphicsPage::connectAntiAliasingChanged(const QObject* receiver, const char* slot) { - connect(antiAliasingComboBox, SIGNAL(currentIndexChanged(int)), receiver, slot); -} - bool Launcher::GraphicsPage::setupSDL() { bool sdlConnectSuccessful = initSDL(); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index d86b570914..a6754ccb04 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -20,7 +20,6 @@ namespace Launcher public: explicit GraphicsPage(QWidget *parent = nullptr); - void connectAntiAliasingChanged(const QObject *receiver, const char *slot); void saveSettings(); bool loadSettings(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index d58be7afcf..d335d7cded 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -146,7 +146,6 @@ void Launcher::MainDialog::createPages() connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); // Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection); - mGraphicsPage->connectAntiAliasingChanged(mAdvancedPage, SLOT(slotAASettingChanged(int))); } Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index f782c6caa0..4b7434dc35 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -457,7 +457,7 @@ - <html><head/><body><p>Enables alpha testing for smoother anti-aliasing (Requires anti-aliasing to be enabled)</p></body></html> + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance if MSAA is off.</p></body></html> Use anti-alias alpha testing From b02560bbaed89e3a7293f064439377b3a88c1153 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 1 Dec 2021 11:11:02 +0100 Subject: [PATCH 1801/2859] Add a precision parameter to format floating point number in stats mode --- scripts/osg_stats.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 037aafea00..d28558217e 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -35,6 +35,8 @@ 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('--precision', type=int, + 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, @@ -54,7 +56,7 @@ import termtables @click.option('--threshold_value', type=float, default=1.05/60, help='Threshold for hist_over.') @click.argument('path', type=click.Path(), nargs=-1) -def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, +def main(print_keys, 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): @@ -82,7 +84,7 @@ def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, if plot: draw_plots(sources=frames, plots=plot) if stats: - print_stats(sources=frames, keys=stats, stats_sum=stats_sum) + print_stats(sources=frames, keys=stats, stats_sum=stats_sum, precision=precision) if hist_threshold: draw_hist_threshold(sources=frames, keys=hist_threshold, begin_frame=begin_frame, threshold_name=threshold_name, threshold_value=threshold_value) @@ -242,13 +244,13 @@ def draw_plots(sources, plots): fig.canvas.set_window_title('plots') -def print_stats(sources, keys, stats_sum): +def print_stats(sources, keys, stats_sum, precision): stats = list() for name, frames in sources.items(): for key in keys: - stats.append(make_stats(source=name, key=key, values=filter_not_none(frames[key]))) + stats.append(make_stats(source=name, key=key, values=filter_not_none(frames[key]), precision=precision)) if stats_sum: - stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys))) + stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys), precision=precision)) metrics = list(stats[0].keys()) termtables.print( [list(v.values()) for v in stats], @@ -282,6 +284,10 @@ def filter_not_none(values): return [v for v in values if v is not None] +def fixed_float(value, precision): + return '{v:.{p}f}'.format(v=value, p=precision) if precision else value + + def sum_multiple(frames, keys): result = collections.Counter() for key in keys: @@ -292,17 +298,17 @@ def sum_multiple(frames, keys): return numpy.array([result[k] for k in sorted(result.keys())]) -def make_stats(source, key, values): +def make_stats(source, key, values, precision): return collections.OrderedDict( source=source, key=key, number=len(values), - min=min(values), - max=max(values), - mean=statistics.mean(values), - median=statistics.median(values), - stdev=statistics.stdev(values), - q95=numpy.quantile(values, 0.95), + min=fixed_float(min(values), precision), + max=fixed_float(max(values), precision), + mean=fixed_float(statistics.mean(values), precision), + median=fixed_float(statistics.median(values), precision), + stdev=fixed_float(statistics.stdev(values), precision), + q95=fixed_float(numpy.quantile(values, 0.95), precision), ) From 3754e59de0c9ac3cd3d518d6990ac7091b25da4f Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 1 Dec 2021 11:46:02 +0100 Subject: [PATCH 1802/2859] Add a regexp_match flag for keys used in stats and timeseries. When set, the argument given is considered a regexp against which the keys are matched. --- scripts/osg_stats.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index d28558217e..275e70dcff 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -12,11 +12,14 @@ import numpy import statistics import sys import termtables +import re @click.command() @click.option('--print_keys', is_flag=True, 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 and timeseries.') @click.option('--timeseries', type=str, multiple=True, help='Show a graph for given metric over time.') @click.option('--commulative_timeseries', type=str, multiple=True, @@ -56,7 +59,7 @@ import termtables @click.option('--threshold_value', type=float, default=1.05/60, help='Threshold for hist_over.') @click.argument('path', type=click.Path(), nargs=-1) -def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, precision, +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): @@ -70,10 +73,10 @@ def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, prec for v in keys: print(v) if timeseries: - draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum, + draw_timeseries(sources=frames, keys=matching_keys(keys, timeseries, regexp_match), add_sum=timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) if commulative_timeseries: - draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum, + draw_commulative_timeseries(sources=frames, keys=matching_keys(keys, commulative_timeseries, regexp_match), add_sum=commulative_timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) if hist: draw_hists(sources=frames, keys=hist) @@ -84,7 +87,7 @@ def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, prec if plot: draw_plots(sources=frames, plots=plot) if stats: - print_stats(sources=frames, keys=stats, stats_sum=stats_sum, precision=precision) + print_stats(sources=frames, keys=matching_keys(keys, stats, regexp_match), stats_sum=stats_sum, precision=precision) if hist_threshold: draw_hist_threshold(sources=frames, keys=hist_threshold, begin_frame=begin_frame, threshold_name=threshold_name, threshold_value=threshold_value) @@ -142,6 +145,12 @@ def collect_unique_keys(sources): return sorted(result) +def matching_keys(keys, patterns, regexp_match): + if regexp_match: + return { key for pattern in patterns for key in keys if re.search(pattern, key) } + return keys + + def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig, ax = matplotlib.pyplot.subplots() x = numpy.array(range(begin_frame, end_frame)) From 893d569529724dc2cf782a34d0f8c832c0c9b72b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 1 Dec 2021 18:21:21 +0100 Subject: [PATCH 1803/2859] Force the loot UI open if it was open while resurrecting the lootee --- apps/openmw/mwbase/windowmanager.hpp | 1 + apps/openmw/mwgui/container.cpp | 5 ++++- apps/openmw/mwgui/container.hpp | 3 ++- apps/openmw/mwgui/windowmanagerimp.cpp | 21 +++++++++++++++++---- apps/openmw/mwgui/windowmanagerimp.hpp | 4 ++++ apps/openmw/mwscript/statsextensions.cpp | 7 ++++++- 6 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 961a63ac79..958bb466ab 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -355,6 +355,7 @@ namespace MWBase virtual const std::string& getVersionDescription() const = 0; virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0; + virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0; }; } diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index de771051ef..fdfd192cc1 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -38,6 +38,7 @@ namespace MWGui , mSortModel(nullptr) , mModel(nullptr) , mSelectedItem(-1) + , mTreatNextOpenAsLoot(false) { getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); getWidget(mTakeButton, "TakeButton"); @@ -121,13 +122,15 @@ namespace MWGui void ContainerWindow::setPtr(const MWWorld::Ptr& container) { + bool lootAnyway = mTreatNextOpenAsLoot; + mTreatNextOpenAsLoot = false; mPtr = container; bool loot = mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead(); if (mPtr.getClass().hasInventoryStore(mPtr)) { - if (mPtr.getClass().isNpc() && !loot) + if (mPtr.getClass().isNpc() && !loot && !lootAnyway) { // we are stealing stuff mModel = new PickpocketItemModel(mPtr, new InventoryItemModel(container), diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 2a0dee44e2..66a20e7ef5 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -37,6 +37,7 @@ namespace MWGui void onDeleteCustomData(const MWWorld::Ptr& ptr) override; + void treatNextOpenAsLoot() { mTreatNextOpenAsLoot = true; }; private: DragAndDrop* mDragAndDrop; @@ -44,7 +45,7 @@ namespace MWGui SortFilterItemModel* mSortModel; ItemModel* mModel; int mSelectedItem; - + bool mTreatNextOpenAsLoot; MyGUI::Button* mDisposeCorpseButton; MyGUI::Button* mTakeButton; MyGUI::Button* mCloseButton; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index a935d7f900..3417fb479b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -165,6 +165,7 @@ namespace MWGui , mScreenFader(nullptr) , mDebugWindow(nullptr) , mJailScreen(nullptr) + , mContainerWindow(nullptr) , mTranslationDataStorage (translationDataStorage) , mCharGen(nullptr) , mInputBlocker(nullptr) @@ -360,10 +361,10 @@ namespace MWGui mGuiModeStates[GM_Dialogue] = GuiModeState(mDialogueWindow); mTradeWindow->eventTradeDone += MyGUI::newDelegate(mDialogueWindow, &DialogueWindow::onTradeComplete); - ContainerWindow* containerWindow = new ContainerWindow(mDragAndDrop); - mWindows.push_back(containerWindow); - trackWindow(containerWindow, "container"); - mGuiModeStates[GM_Container] = GuiModeState({containerWindow, mInventoryWindow}); + mContainerWindow = new ContainerWindow(mDragAndDrop); + mWindows.push_back(mContainerWindow); + trackWindow(mContainerWindow, "container"); + mGuiModeStates[GM_Container] = GuiModeState({mContainerWindow, mInventoryWindow}); mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender); mWindows.push_back(mHud); @@ -1166,6 +1167,16 @@ namespace MWGui } void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg) + { + pushGuiMode(mode, arg, false); + } + + void WindowManager::forceLootMode(const MWWorld::Ptr& ptr) + { + pushGuiMode(MWGui::GM_Container, ptr, true); + } + + void WindowManager::pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force) { if (mode==GM_Inventory && mAllowed==GW_None) return; @@ -1188,6 +1199,8 @@ namespace MWGui mGuiModeStates[mode].update(true); playSound(mGuiModeStates[mode].mOpenSound); } + if(force) + mContainerWindow->treatNextOpenAsLoot(); for (WindowBase* window : mGuiModeStates[mode].mWindows) window->setPtr(arg); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 9ec79e0c82..f68592cb5d 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -389,6 +389,7 @@ namespace MWGui const std::string& getVersionDescription() const override; void onDeleteCustomData(const MWWorld::Ptr& ptr) override; + void forceLootMode(const MWWorld::Ptr& ptr) override; private: unsigned int mOldUpdateMask; unsigned int mOldCullMask; @@ -447,6 +448,7 @@ namespace MWGui ScreenFader* mScreenFader; DebugWindow* mDebugWindow; JailScreen* mJailScreen; + ContainerWindow* mContainerWindow; std::vector mWindows; @@ -573,6 +575,8 @@ namespace MWGui void enableScene(bool enable); void handleScheduledMessageBoxes(); + + void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force); }; } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index ccad186a0d..c53588ed22 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1195,7 +1195,9 @@ namespace MWScript bool wasEnabled = ptr.getRefData().isEnabled(); MWBase::Environment::get().getWorld()->undeleteObject(ptr); MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); - MWBase::Environment::get().getWindowManager()->onDeleteCustomData(ptr); + auto windowManager = MWBase::Environment::get().getWindowManager(); + bool wasOpen = windowManager->containsMode(MWGui::GM_Container); + windowManager->onDeleteCustomData(ptr); // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). MWBase::Environment::get().getWorld()->disable(ptr); @@ -1203,6 +1205,9 @@ namespace MWScript ptr.getRefData().setCustomData(nullptr); if (wasEnabled) MWBase::Environment::get().getWorld()->enable(ptr); + // Reopen the loot GUI if it was closed because we resurrected the actor we were looting + if (wasOpen && !windowManager->containsMode(MWGui::GM_Container)) + windowManager->forceLootMode(ptr); } } }; From dca0fa0a415b5b994067a1633429b2852e7560c2 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 1 Dec 2021 21:39:53 +0000 Subject: [PATCH 1804/2859] Remove teleportation effects after they've been applied --- apps/openmw/mwmechanics/spelleffects.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 566fb9eded..d41cf82a9c 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -798,6 +798,11 @@ MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorl MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); return MagicApplicationResult::REMOVED; } + else if(effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention || effect.mEffectId == ESM::MagicEffect::DivineIntervention || effect.mEffectId == ESM::MagicEffect::Recall) + { + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) + return MagicApplicationResult::REMOVED; + } const auto* magicEffect = world->getStore().get().find(effect.mEffectId); if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) { From a680952434f292c758b7b640d35ddb33bb94410c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 1 Dec 2021 21:43:31 +0000 Subject: [PATCH 1805/2859] Combine daily and on-push build declarations --- .gitlab-ci.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 47511923ad..349bb5ffa3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -395,21 +395,16 @@ Windows_Ninja_Tests_RelWithDebInfo: - MSVC2019_64/*/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*/*.log -Daily_Windows_MSBuild_Engine_Release:on-schedule: - extends: - - .Windows_MSBuild_Base - variables: - <<: *engine-targets - config: "Release" - rules: - - if: $CI_PIPELINE_SOURCE == "schedule" - Windows_MSBuild_Engine_Release: extends: - .Windows_MSBuild_Base variables: <<: *engine-targets config: "Release" + rules: + # run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it + - if: $CI_PIPELINE_SOURCE == "push" + - if: $CI_PIPELINE_SOURCE == "schedule" Windows_MSBuild_Engine_Debug: extends: @@ -431,6 +426,10 @@ Windows_MSBuild_CS_Release: variables: <<: *cs-targets config: "Release" + rules: + # run this for both pushes and schedules so 'latest successful pipeline for branch' always includes it + - if: $CI_PIPELINE_SOURCE == "push" + - if: $CI_PIPELINE_SOURCE == "schedule" Windows_MSBuild_CS_Debug: extends: From ead0cb5ce07148bed0cded91c335e3a2534751e2 Mon Sep 17 00:00:00 2001 From: Ivan Beloborodov Date: Thu, 2 Dec 2021 08:04:29 +0000 Subject: [PATCH 1806/2859] #6419 Topic shouldn't be greyed out if they can produce another topic reference. --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 66 ++++++++++++++----- apps/openmw/mwdialogue/dialoguemanagerimp.hpp | 13 +++- 4 files changed, 62 insertions(+), 19 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 8ca9db90b9..4b6c054619 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -92,6 +92,7 @@ Programmers Haoda Wang (h313) hristoast Internecine + Ivan Beloborodov (myrix) Jackerty Jacob Essex (Yacoby) Jacob Turnbull (Tankinfrank) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f5b83f158..c3a84f5909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ Feature #6251: OpenMW-CS: Set instance movement based on camera zoom Feature #6288: Preserve the "blocked" record flag for referenceable objects. Feature #6380: Commas are treated as whitespace in vanilla + Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 5270c0143a..9800f1b39c 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -76,9 +76,10 @@ namespace MWDialogue mKnownTopics.insert( Misc::StringUtils::lowerCase(topic) ); } - void DialogueManager::parseText (const std::string& text) + std::vector DialogueManager::parseTopicIdsFromText (const std::string& text) { - updateActorKnownTopics(); + std::vector topicIdList; + std::vector hypertext = HyperTextParser::parseHyperText(text); for (std::vector::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok) @@ -95,6 +96,18 @@ namespace MWDialogue topicId = mTranslationDataStorage.topicStandardForm(topicId); } + topicIdList.push_back(topicId); + } + + return topicIdList; + } + + void DialogueManager::addTopicsFromText (const std::string& text) + { + updateActorKnownTopics(); + + for (const auto& topicId : parseTopicIdsFromText(text)) + { if (mActorKnownTopics.count( topicId )) mKnownTopics.insert( topicId ); } @@ -136,7 +149,6 @@ namespace MWDialogue mTalkedTo = creatureStats.hasTalkedToPlayer(); mActorKnownTopics.clear(); - mActorKnownTopicsFlag.clear(); //greeting const MWWorld::Store &dialogs = @@ -163,7 +175,7 @@ namespace MWDialogue executeScript (info->mResultScript, mActor); mLastTopic = it->mId; - parseText (info->mResponse); + addTopicsFromText (info->mResponse); return true; } @@ -277,7 +289,10 @@ namespace MWDialogue const ESM::Dialogue& dialogue = *dialogues.find (topic); - const ESM::DialInfo* info = filter.search(dialogue, true); + const ESM::DialInfo* info = + mChoice == -1 && mActorKnownTopics.count(topic) ? + mActorKnownTopics[topic].mInfo : filter.search(dialogue, true); + if (info) { std::string title; @@ -320,7 +335,7 @@ namespace MWDialogue executeScript (info->mResultScript, mActor); - parseText (info->mResponse); + addTopicsFromText (info->mResponse); } } @@ -339,7 +354,6 @@ namespace MWDialogue updateGlobals(); mActorKnownTopics.clear(); - mActorKnownTopicsFlag.clear(); const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get(); @@ -354,21 +368,41 @@ namespace MWDialogue if (answer != nullptr) { - int flag = 0; + int topicFlags = 0; if(!inJournal(topicId, answer->mId)) { // Does this dialogue contains some actor-specific answer? if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId())) - flag |= MWBase::DialogueManager::TopicType::Specific; + topicFlags |= MWBase::DialogueManager::TopicType::Specific; } else - flag |= MWBase::DialogueManager::TopicType::Exhausted; - mActorKnownTopics.insert (dialog.mId); - mActorKnownTopicsFlag[dialog.mId] = flag; + topicFlags |= MWBase::DialogueManager::TopicType::Exhausted; + mActorKnownTopics.insert (std::make_pair(dialog.mId, ActorKnownTopicInfo {topicFlags, answer})); } } } + + // If response to a topic leads to a new topic, the original topic is not exhausted. + + for (auto& [dialogId, topicInfo] : mActorKnownTopics) + { + // If the topic is not marked as exhausted, we don't need to do anything about it. + // If the topic will not be shown to the player, the flag actually does not matter. + + if (!(topicInfo.mFlags & MWBase::DialogueManager::TopicType::Exhausted) || + !mKnownTopics.count(dialogId)) + continue; + + for (const auto& topicId : parseTopicIdsFromText(topicInfo.mInfo->mResponse)) + { + if (mActorKnownTopics.count( topicId ) && !mKnownTopics.count( topicId )) + { + topicInfo.mFlags &= ~MWBase::DialogueManager::TopicType::Exhausted; + break; + } + } + } } std::list DialogueManager::getAvailableTopics() @@ -377,7 +411,7 @@ namespace MWDialogue std::list keywordList; - for (const std::string& topic : mActorKnownTopics) + for (const auto& [topic, topicInfo] : mActorKnownTopics) { //does the player know the topic? if (mKnownTopics.count(topic)) @@ -391,7 +425,7 @@ namespace MWDialogue int DialogueManager::getTopicFlag(const std::string& topicId) { - return mActorKnownTopicsFlag[topicId]; + return mActorKnownTopics[topicId].mFlags; } void DialogueManager::keywordSelected (const std::string& keyword, ResponseCallback* callback) @@ -444,7 +478,7 @@ namespace MWDialogue if (const ESM::DialInfo *info = filter.search (*dialogue, true)) { std::string text = info->mResponse; - parseText (text); + addTopicsFromText (text); mChoice = -1; mIsInChoice = false; @@ -579,7 +613,7 @@ namespace MWDialogue { const ESM::DialInfo* info = infos[0]; - parseText (info->mResponse); + addTopicsFromText (info->mResponse); const MWWorld::Store& gmsts = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index ab2625ff5a..57eb74d0a6 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "../mwworld/ptr.hpp" @@ -24,14 +25,19 @@ namespace MWDialogue { class DialogueManager : public MWBase::DialogueManager { + struct ActorKnownTopicInfo + { + int mFlags; + const ESM::DialInfo* mInfo; + }; + std::set mKnownTopics;// Those are the topics the player knows. // Modified faction reactions. > typedef std::map > ModFactionReactionMap; ModFactionReactionMap mChangedFactionReaction; - std::set mActorKnownTopics; - std::unordered_map mActorKnownTopicsFlag; + std::map mActorKnownTopics; Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; @@ -51,7 +57,8 @@ namespace MWDialogue int mCurrentDisposition; int mPermanentDispositionChange; - void parseText (const std::string& text); + std::vector parseTopicIdsFromText (const std::string& text); + void addTopicsFromText (const std::string& text); void updateActorKnownTopics(); void updateGlobals(); From 13400b2c5f4c222db3b6c471f32e20fce92ba622 Mon Sep 17 00:00:00 2001 From: andrew-app Date: Thu, 2 Dec 2021 17:01:40 +0000 Subject: [PATCH 1807/2859] Bug #5434: Pinned windows shouldn't cover breath progress bar --- AUTHORS.md | 3 ++- CHANGELOG.md | 1 + apps/openmw/mwgui/hud.cpp | 13 +++++---- apps/openmw/mwgui/hud.hpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 1 + files/mygui/openmw_hud.layout | 37 ++++++++++++++------------ files/mygui/openmw_layers.xml | 1 + 7 files changed, 34 insertions(+), 24 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 8ca9db90b9..d7f7198901 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -31,6 +31,7 @@ Programmers Allofich Andreas Stöckel Andrei Kortunov (akortunov) + Andrew Appuhamy (andrew-app) AnyOldName3 Ardekantur Armin Preiml @@ -230,7 +231,7 @@ Programmers Yuri Krupenin zelurker Noah Gooder - Andrew Appuhamy (andrew-app) + Documentation ------------- diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f5b83f158..ccb3d772fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Bug #5377: console does not appear after using menutest in inventory Bug #5379: Wandering NPCs falling through cantons Bug #5394: Windows snapping no longer works + Bug #5434: Pinned windows shouldn't cover breath progress bar Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5508: Engine binary links to Qt without using it diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index ab596137cd..f6136791e9 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -81,7 +81,7 @@ namespace MWGui , mMinimap(nullptr) , mCrosshair(nullptr) , mCellNameBox(nullptr) - , mDrowningFrame(nullptr) + , mDrowningBar(nullptr) , mDrowningFlash(nullptr) , mHealthManaStaminaBaseLeft(0) , mWeapBoxBaseLeft(0) @@ -119,6 +119,7 @@ namespace MWGui fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); //Drowning bar + getWidget(mDrowningBar, "DrowningBar"); getWidget(mDrowningFrame, "DrowningFrame"); getWidget(mDrowning, "Drowning"); getWidget(mDrowningFlash, "Flash"); @@ -224,7 +225,7 @@ namespace MWGui void HUD::setDrowningBarVisible(bool visible) { - mDrowningFrame->setVisible(visible); + mDrowningBar->setVisible(visible); } void HUD::onWorldClicked(MyGUI::Widget* _sender) @@ -368,9 +369,6 @@ namespace MWGui mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20)); } - if (mIsDrowning) - mDrowningFlashTheta += dt * osg::PI*2; - mSpellIcons->updateWidgets(mEffectBox, true); if (mEnemyActorId != -1 && mEnemyHealth->getVisible()) @@ -378,8 +376,13 @@ namespace MWGui updateEnemyHealthBar(); } + if (mDrowningBar->getVisible()) + mDrowningBar->setPosition(mMainWidget->getWidth()/2 - mDrowningFrame->getWidth()/2, mMainWidget->getTop()); + if (mIsDrowning) { + mDrowningFlashTheta += dt * osg::PI*2; + float intensity = (cos(mDrowningFlashTheta) + 2.0f) / 3.0f; mDrowningFlash->setAlpha(intensity); diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 8a89320d8c..ef591bec97 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -73,7 +73,7 @@ namespace MWGui MyGUI::ImageBox* mCrosshair; MyGUI::TextBox* mCellNameBox; MyGUI::TextBox* mWeaponSpellBox; - MyGUI::Widget *mDrowningFrame, *mDrowningFlash; + MyGUI::Widget *mDrowningBar, *mDrowningFrame, *mDrowningFlash; // bottom left elements int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 94a064a6c8..ca8557f2e1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -638,6 +638,7 @@ namespace MWGui mMap->setVisible(false); mStatsWindow->setVisible(false); mSpellWindow->setVisible(false); + mHud->setDrowningBarVisible(false); mInventoryWindow->setVisible(getMode() == GM_Container || getMode() == GM_Barter || getMode() == GM_Companion); } diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index b3e3cfb9df..a189075f44 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -31,22 +31,6 @@ - - - - - - - - - - - - - - - - @@ -128,4 +112,23 @@ - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index c288655027..4afa67f314 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -15,6 +15,7 @@ + From 58b888a38ee7a59fd6402b0978220c299bc34c22 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 2 Dec 2021 20:36:42 +0100 Subject: [PATCH 1808/2859] Preserve inventories when resurrecting actors while looting them --- apps/openmw/mwscript/statsextensions.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index c53588ed22..d7120af53a 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1194,20 +1194,25 @@ namespace MWScript { bool wasEnabled = ptr.getRefData().isEnabled(); MWBase::Environment::get().getWorld()->undeleteObject(ptr); - MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); auto windowManager = MWBase::Environment::get().getWindowManager(); bool wasOpen = windowManager->containsMode(MWGui::GM_Container); windowManager->onDeleteCustomData(ptr); - // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). MWBase::Environment::get().getWorld()->disable(ptr); - // resets runtime state such as inventory, stats and AI. does not reset position in the world - ptr.getRefData().setCustomData(nullptr); - if (wasEnabled) - MWBase::Environment::get().getWorld()->enable(ptr); - // Reopen the loot GUI if it was closed because we resurrected the actor we were looting if (wasOpen && !windowManager->containsMode(MWGui::GM_Container)) + { + // Reopen the loot GUI if it was closed because we resurrected the actor we were looting + MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); windowManager->forceLootMode(ptr); + } + else + { + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); + // resets runtime state such as inventory, stats and AI. does not reset position in the world + ptr.getRefData().setCustomData(nullptr); + } + if (wasEnabled) + MWBase::Environment::get().getWorld()->enable(ptr); } } }; From e65c9464522571139d96d4f954a7d0715a662d06 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 2 Dec 2021 21:52:29 +0000 Subject: [PATCH 1809/2859] Add a data field to Lua UI layouts --- components/lua_ui/element.cpp | 7 +++++++ components/lua_ui/widget.cpp | 5 ++++- components/lua_ui/widget.hpp | 5 ++++- docs/source/reference/lua-scripting/user_interface.rst | 8 ++++++++ docs/source/reference/lua-scripting/widgets/widget.rst | 2 +- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 1a1b38e4dc..63ae0e7d5e 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -46,6 +46,11 @@ namespace LuaUi } } + void setLayout(LuaUi::WidgetExtension* ext, const sol::table& layout) + { + ext->setLayout(layout); + } + LuaUi::WidgetExtension* createWidget(const sol::table& layout, LuaUi::WidgetExtension* parent) { std::string type = widgetType(layout); @@ -72,6 +77,7 @@ namespace LuaUi setEventCallbacks(ext, layout); setProperties(ext, layout); + setLayout(ext, layout); Content cont = content(layout); for (size_t i = 0; i < cont.size(); i++) @@ -90,6 +96,7 @@ namespace LuaUi { setEventCallbacks(ext, layout); setProperties(ext, layout); + setLayout(ext, layout); Content newContent = content(layout); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index ef876e8a06..5653659287 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -13,13 +13,16 @@ namespace LuaUi , mAbsoluteCoord() , mRelativeCoord() , mAnchor() + , mLua{ nullptr } + , mWidget{ nullptr } + , mLayout{ sol::nil } {} void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const { auto it = mCallbacks.find(name); if (it != mCallbacks.end()) - it->second(argument); + it->second(argument, mLayout); } void WidgetExtension::create(lua_State* lua, MyGUI::Widget* self) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 360c6483ba..99d430f71c 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -43,6 +43,8 @@ namespace LuaUi void setForcedCoord(const MyGUI::IntCoord& offset); void updateCoord(); + void setLayout(const sol::table& layout) { mLayout = layout; } + protected: sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; @@ -67,11 +69,12 @@ namespace LuaUi private: // use lua_State* instead of sol::state_view because MyGUI requires a default constructor - lua_State* mLua; + lua_State* mLua; MyGUI::Widget* mWidget; std::vector mContent; std::map> mCallbacks; + sol::table mLayout; void updateChildrenCoord(MyGUI::Widget*); diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 5b733a8fe9..88233dceb9 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -46,6 +46,14 @@ A container holding all the widget's children. It has a few important difference .. TODO: Talk about skins/templates here when they are ready +Events +------ + +| A table mapping event names to `openmw.async.callback` s. +| When an event triggers, the callback is called with two arguments: + an event-specific value, and that widget's layout table. +| See the Widget type pages for information on what events exist, and which first argument they pass. + Widget types ------------ diff --git a/docs/source/reference/lua-scripting/widgets/widget.rst b/docs/source/reference/lua-scripting/widgets/widget.rst index 51d6d17203..49058ee278 100644 --- a/docs/source/reference/lua-scripting/widgets/widget.rst +++ b/docs/source/reference/lua-scripting/widgets/widget.rst @@ -41,7 +41,7 @@ Events :widths: 20 20 60 * - name - - type + - first argument type - description * - keyPress - `KeyboardEvent <../openmw_input.html##(KeyboardEvent)>`_ From e10bbb9ad7b1439c6bbc11274763f2375973c3bf Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Nov 2021 19:48:18 +0100 Subject: [PATCH 1810/2859] Shift heightfield and water in navigator tests --- .../detournavigator/navigator.cpp | 861 +++++++++--------- .../detournavigator/operators.hpp | 2 +- components/detournavigator/makenavmesh.cpp | 1 + 3 files changed, 448 insertions(+), 416 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 0f4f1e3345..367572d449 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -35,8 +36,8 @@ namespace { Settings mSettings; std::unique_ptr mNavigator; - osg::Vec3f mPlayerPosition; - osg::Vec3f mAgentHalfExtents; + const osg::Vec3f mPlayerPosition; + const osg::Vec3f mAgentHalfExtents; osg::Vec3f mStart; osg::Vec3f mEnd; std::deque mPath; @@ -48,12 +49,13 @@ namespace const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); const osg::Vec3f mShift {0, 0, 0}; const float mEndTolerance = 0; + const btTransform mTransform {btMatrix3x3::getIdentity(), btVector3(256, 256, 0)}; DetourNavigatorNavigatorTest() - : mPlayerPosition(0, 0, 0) + : mPlayerPosition(256, 256, 0) , mAgentHalfExtents(29, 29, 66) - , mStart(-204, 204, 1) - , mEnd(204, -204, 1) + , mStart(52, 460, 1) + , mEnd(460, 52, 1) , mOut(mPath) , mStepSize(28.333332061767578125f) { @@ -135,6 +137,11 @@ namespace osg::ref_ptr mInstance; }; + osg::Vec3f getHeightfieldShift(const osg::Vec2i& cellPosition, int cellSize, float minHeight, float maxHeight) + { + return Misc::Convert::toOsg(BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.x(), cellSize, minHeight, maxHeight)); + } + TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), @@ -168,9 +175,11 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); @@ -178,28 +187,28 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204.0000152587890625, 204, 1.99998295307159423828125), - Vec3fEq(-183.96533203125, 183.9653167724609375, 1.99998819828033447265625), - Vec3fEq(-163.930633544921875, 163.9306182861328125, 1.99999344348907470703125), - Vec3fEq(-143.8959503173828125, 143.89593505859375, -2.720611572265625), - Vec3fEq(-123.86126708984375, 123.86124420166015625, -13.1089687347412109375), - Vec3fEq(-103.82657623291015625, 103.8265533447265625, -23.497333526611328125), - Vec3fEq(-83.7918853759765625, 83.7918548583984375, -33.885692596435546875), - Vec3fEq(-63.757190704345703125, 63.757171630859375, -44.274051666259765625), - Vec3fEq(-43.722503662109375, 43.72248077392578125, -54.66241455078125), - Vec3fEq(-23.687808990478515625, 23.6877918243408203125, -65.05077362060546875), - Vec3fEq(-3.6531188488006591796875, 3.6531002521514892578125, -75.43914031982421875), - Vec3fEq(16.3815746307373046875, -16.381591796875, -69.74927520751953125), - Vec3fEq(36.416263580322265625, -36.416286468505859375, -60.4739532470703125), - Vec3fEq(56.450958251953125, -56.450977325439453125, -51.1986236572265625), - Vec3fEq(76.48564910888671875, -76.4856719970703125, -41.92330169677734375), - Vec3fEq(96.5203399658203125, -96.52036285400390625, -31.46941375732421875), - Vec3fEq(116.55503082275390625, -116.5550537109375, -19.597003936767578125), - Vec3fEq(136.5897216796875, -136.5897369384765625, -7.724592685699462890625), - Vec3fEq(156.624420166015625, -156.624420166015625, 1.99999535083770751953125), - Vec3fEq(176.6591033935546875, -176.65911865234375, 1.99999010562896728515625), - Vec3fEq(196.69378662109375, -196.6938018798828125, 1.99998486042022705078125), - Vec3fEq(204, -204.0000152587890625, 1.99998295307159423828125) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), + Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), + Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), + Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), + Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), + Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), + Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), + Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), + Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), + Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), + Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), + Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), + Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), + Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), + Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), + Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), + Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), + Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), + Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), + Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } @@ -213,12 +222,14 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -226,31 +237,31 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.99998295307159423828125), - Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), - Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), - Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), - Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), - Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), - Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), - Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), - Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), - Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), - Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), - Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), - Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), - Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), - Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), - Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), - Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), - Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), - Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), - Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), - Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), - Vec3fEq(204, -204, 1.99998295307159423828125) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), + Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), + Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), + Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), + Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), + Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), + Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), + Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), + Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), + Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), + Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), + Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), + Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), + Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), + Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), + Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), + Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), + Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), + Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), + Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -260,29 +271,29 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.99998295307159423828125), - Vec3fEq(-189.9427337646484375, 179.3997802734375, -3.622931003570556640625), - Vec3fEq(-175.8854522705078125, 154.7995452880859375, -9.24583911895751953125), - Vec3fEq(-161.82818603515625, 130.1993255615234375, -14.86874866485595703125), - Vec3fEq(-147.770904541015625, 105.5991058349609375, -20.4916591644287109375), - Vec3fEq(-133.7136383056640625, 80.99887847900390625, -26.1145648956298828125), - Vec3fEq(-119.65636444091796875, 56.39865875244140625, -31.7374725341796875), - Vec3fEq(-105.59909820556640625, 31.798435211181640625, -26.133396148681640625), - Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), - Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), - Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), - Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), - Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), - Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), - Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.8243885040283203125), - Vec3fEq(53.413402557373046875, -120.6742401123046875, -31.3303241729736328125), - Vec3fEq(78.20446014404296875, -134.39215087890625, -25.8431549072265625), - Vec3fEq(102.99552154541015625, -148.110076904296875, -20.3559894561767578125), - Vec3fEq(127.7865753173828125, -161.827972412109375, -14.868824005126953125), - Vec3fEq(152.57763671875, -175.5458984375, -9.3816623687744140625), - Vec3fEq(177.3686981201171875, -189.2638092041015625, -3.894496917724609375), - Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.59266507625579833984375), - Vec3fEq(204, -204, 1.99998295307159423828125) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(69.5299530029296875, 434.754913330078125, -2.6775772571563720703125), + Vec3fEq(82.39324951171875, 409.50982666015625, -7.355137348175048828125), + Vec3fEq(95.25653839111328125, 384.2647705078125, -12.0326976776123046875), + Vec3fEq(108.11983489990234375, 359.019683837890625, -16.71025848388671875), + Vec3fEq(120.983123779296875, 333.774627685546875, -21.3878192901611328125), + Vec3fEq(133.8464202880859375, 308.529541015625, -26.0653781890869140625), + Vec3fEq(146.7097015380859375, 283.284454345703125, -30.7429370880126953125), + Vec3fEq(159.572998046875, 258.039398193359375, -35.420497894287109375), + Vec3fEq(172.4362945556640625, 232.7943115234375, -27.2731761932373046875), + Vec3fEq(185.2996063232421875, 207.54925537109375, -19.575878143310546875), + Vec3fEq(206.6449737548828125, 188.917236328125, -20.3511219024658203125), + Vec3fEq(227.9903564453125, 170.28521728515625, -22.9776935577392578125), + Vec3fEq(253.4362640380859375, 157.8239593505859375, -31.1692962646484375), + Vec3fEq(278.8822021484375, 145.3627166748046875, -30.253124237060546875), + Vec3fEq(304.328094482421875, 132.9014739990234375, -22.219127655029296875), + Vec3fEq(329.774017333984375, 120.44022369384765625, -13.2701435089111328125), + Vec3fEq(355.219940185546875, 107.97898101806640625, -5.330339908599853515625), + Vec3fEq(380.665863037109375, 95.51773834228515625, -3.5501649379730224609375), + Vec3fEq(406.111785888671875, 83.05649566650390625, -1.76998889446258544921875), + Vec3fEq(431.557708740234375, 70.5952606201171875, 0.01018683053553104400634765625), + Vec3fEq(457.003662109375, 58.134021759033203125, 1.79036080837249755859375), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } @@ -296,13 +307,15 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -310,34 +323,34 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.99998295307159423828125), - Vec3fEq(-189.9427337646484375, 179.3997802734375, -3.622931003570556640625), - Vec3fEq(-175.8854522705078125, 154.7995452880859375, -9.24583911895751953125), - Vec3fEq(-161.82818603515625, 130.1993255615234375, -14.86874866485595703125), - Vec3fEq(-147.770904541015625, 105.5991058349609375, -20.4916591644287109375), - Vec3fEq(-133.7136383056640625, 80.99887847900390625, -26.1145648956298828125), - Vec3fEq(-119.65636444091796875, 56.39865875244140625, -31.7374725341796875), - Vec3fEq(-105.59909820556640625, 31.798435211181640625, -26.133396148681640625), - Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), - Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), - Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), - Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), - Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), - Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), - Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.8243885040283203125), - Vec3fEq(53.413402557373046875, -120.6742401123046875, -31.3303241729736328125), - Vec3fEq(78.20446014404296875, -134.39215087890625, -25.8431549072265625), - Vec3fEq(102.99552154541015625, -148.110076904296875, -20.3559894561767578125), - Vec3fEq(127.7865753173828125, -161.827972412109375, -14.868824005126953125), - Vec3fEq(152.57763671875, -175.5458984375, -9.3816623687744140625), - Vec3fEq(177.3686981201171875, -189.2638092041015625, -3.894496917724609375), - Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.59266507625579833984375), - Vec3fEq(204, -204, 1.99998295307159423828125) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(69.5299530029296875, 434.754913330078125, -2.6775772571563720703125), + Vec3fEq(82.39324951171875, 409.50982666015625, -7.355137348175048828125), + Vec3fEq(95.25653839111328125, 384.2647705078125, -12.0326976776123046875), + Vec3fEq(108.11983489990234375, 359.019683837890625, -16.71025848388671875), + Vec3fEq(120.983123779296875, 333.774627685546875, -21.3878192901611328125), + Vec3fEq(133.8464202880859375, 308.529541015625, -26.0653781890869140625), + Vec3fEq(146.7097015380859375, 283.284454345703125, -30.7429370880126953125), + Vec3fEq(159.572998046875, 258.039398193359375, -35.420497894287109375), + Vec3fEq(172.4362945556640625, 232.7943115234375, -27.2731761932373046875), + Vec3fEq(185.2996063232421875, 207.54925537109375, -19.575878143310546875), + Vec3fEq(206.6449737548828125, 188.917236328125, -20.3511219024658203125), + Vec3fEq(227.9903564453125, 170.28521728515625, -22.9776935577392578125), + Vec3fEq(253.4362640380859375, 157.8239593505859375, -31.1692962646484375), + Vec3fEq(278.8822021484375, 145.3627166748046875, -30.253124237060546875), + Vec3fEq(304.328094482421875, 132.9014739990234375, -22.219127655029296875), + Vec3fEq(329.774017333984375, 120.44022369384765625, -13.2701435089111328125), + Vec3fEq(355.219940185546875, 107.97898101806640625, -5.330339908599853515625), + Vec3fEq(380.665863037109375, 95.51773834228515625, -3.5501649379730224609375), + Vec3fEq(406.111785888671875, 83.05649566650390625, -1.76998889446258544921875), + Vec3fEq(431.557708740234375, 70.5952606201171875, 0.01018683053553104400634765625), + Vec3fEq(457.003662109375, 58.134021759033203125, 1.79036080837249755859375), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); - mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); + mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -347,28 +360,28 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.99998295307159423828125), - Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), - Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), - Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), - Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), - Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), - Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), - Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), - Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), - Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), - Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), - Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), - Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), - Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), - Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), - Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), - Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), - Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), - Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), - Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), - Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), - Vec3fEq(204, -204, 1.99998295307159423828125) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), + Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), + Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), + Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), + Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), + Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), + Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), + Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), + Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), + Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), + Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), + Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), + Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), + Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), + Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), + Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), + Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), + Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), + Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), + Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } @@ -395,8 +408,8 @@ namespace heightfield2.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance()), btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance()), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance()), mTransform); + mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -404,28 +417,28 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.999981403350830078125), - Vec3fEq(-183.965301513671875, 183.965301513671875, -0.428465187549591064453125), - Vec3fEq(-163.9306182861328125, 163.9306182861328125, -2.8569104671478271484375), - Vec3fEq(-143.89593505859375, 143.89593505859375, -5.28535556793212890625), - Vec3fEq(-123.86124420166015625, 123.86124420166015625, -7.7138004302978515625), - Vec3fEq(-103.8265533447265625, 103.8265533447265625, -10.142246246337890625), - Vec3fEq(-83.7918548583984375, 83.7918548583984375, -12.3704509735107421875), - Vec3fEq(-63.75716400146484375, 63.75716400146484375, -14.354084014892578125), - Vec3fEq(-43.72247314453125, 43.72247314453125, -16.3377170562744140625), - Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -18.32135009765625), - Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -20.3049831390380859375), - Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -19.044734954833984375), - Vec3fEq(36.416290283203125, -36.416290283203125, -17.061100006103515625), - Vec3fEq(56.450984954833984375, -56.450984954833984375, -15.0774688720703125), - Vec3fEq(76.4856719970703125, -76.4856719970703125, -13.0938358306884765625), - Vec3fEq(96.52036285400390625, -96.52036285400390625, -11.02784252166748046875), - Vec3fEq(116.5550537109375, -116.5550537109375, -8.5993976593017578125), - Vec3fEq(136.5897369384765625, -136.5897369384765625, -6.170953273773193359375), - Vec3fEq(156.6244354248046875, -156.6244354248046875, -3.74250507354736328125), - Vec3fEq(176.6591339111328125, -176.6591339111328125, -1.314060688018798828125), - Vec3fEq(196.693817138671875, -196.693817138671875, 1.1143856048583984375), - Vec3fEq(204, -204, 1.9999811649322509765625) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(76.70135498046875, 439.965301513671875, -0.903246104717254638671875), + Vec3fEq(96.73604583740234375, 419.93060302734375, -3.8064472675323486328125), + Vec3fEq(116.770751953125, 399.89593505859375, -6.709649562835693359375), + Vec3fEq(136.8054351806640625, 379.861236572265625, -9.33333873748779296875), + Vec3fEq(156.840118408203125, 359.826568603515625, -9.33333873748779296875), + Vec3fEq(176.8748016357421875, 339.7918701171875, -9.33333873748779296875), + Vec3fEq(196.90948486328125, 319.757171630859375, -9.33333873748779296875), + Vec3fEq(216.944183349609375, 299.722503662109375, -9.33333873748779296875), + Vec3fEq(236.9788665771484375, 279.68780517578125, -9.33333873748779296875), + Vec3fEq(257.0135498046875, 259.65313720703125, -9.33333873748779296875), + Vec3fEq(277.048248291015625, 239.618438720703125, -9.33333873748779296875), + Vec3fEq(297.082916259765625, 219.583740234375, -9.33333873748779296875), + Vec3fEq(317.11761474609375, 199.549041748046875, -9.33333873748779296875), + Vec3fEq(337.15228271484375, 179.5143585205078125, -9.33333873748779296875), + Vec3fEq(357.186981201171875, 159.47967529296875, -9.33333873748779296875), + Vec3fEq(377.221649169921875, 139.4449920654296875, -9.33333873748779296875), + Vec3fEq(397.25634765625, 119.41030120849609375, -6.891522884368896484375), + Vec3fEq(417.291046142578125, 99.3756103515625, -4.053897380828857421875), + Vec3fEq(437.325714111328125, 79.340911865234375, -1.21627247333526611328125), + Vec3fEq(457.360443115234375, 59.3062286376953125, 1.621352672576904296875), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } @@ -439,6 +452,8 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(heightfieldData1); + const int cellSize1 = mHeightfieldTileSize * (surface1.mSize - 1); + const osg::Vec3f shift1 = getHeightfieldShift(mCellPosition, cellSize1, surface1.mMinHeight, surface1.mMaxHeight); const std::array heightfieldData2 {{ -25, -25, -25, -25, -25, @@ -448,10 +463,12 @@ namespace -25, -25, -25, -25, -25, }}; const HeightfieldSurface surface2 = makeSquareHeightfieldSurface(heightfieldData2); + const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1); + const osg::Vec3f shift2 = getHeightfieldShift(mCellPosition, cellSize2, surface2.mMinHeight, surface2.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - EXPECT_TRUE(mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface1.mSize - 1), mShift, surface1)); - EXPECT_FALSE(mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface2.mSize - 1), mShift, surface2)); + EXPECT_TRUE(mNavigator->addHeightfield(mCellPosition, cellSize1, shift1, surface1)); + EXPECT_FALSE(mNavigator->addHeightfield(mCellPosition, cellSize2, shift2, surface2)); } TEST_F(DetourNavigatorNavigatorTest, path_should_be_around_avoid_shape) @@ -483,7 +500,7 @@ namespace osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -491,29 +508,29 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.99997997283935546875), - Vec3fEq(-191.328948974609375, 178.65789794921875, -0.815807759761810302734375), - Vec3fEq(-178.65789794921875, 153.3157806396484375, -3.6315968036651611328125), - Vec3fEq(-165.986846923828125, 127.9736785888671875, -6.4473857879638671875), - Vec3fEq(-153.3157806396484375, 102.6315765380859375, -9.26317310333251953125), - Vec3fEq(-140.6447296142578125, 77.28946685791015625, -12.07896137237548828125), - Vec3fEq(-127.9736785888671875, 51.947368621826171875, -14.894748687744140625), - Vec3fEq(-115.3026275634765625, 26.6052646636962890625, -17.7105388641357421875), - Vec3fEq(-102.63158416748046875, 1.2631585597991943359375, -20.5263233184814453125), - Vec3fEq(-89.9605712890625, -24.0789661407470703125, -19.591716766357421875), - Vec3fEq(-68.54410552978515625, -42.629238128662109375, -19.847625732421875), - Vec3fEq(-47.127635955810546875, -61.17951202392578125, -20.1035366058349609375), - Vec3fEq(-25.711170196533203125, -79.72978973388671875, -20.359447479248046875), - Vec3fEq(-4.294706821441650390625, -98.280059814453125, -20.6153545379638671875), - Vec3fEq(17.121753692626953125, -116.83034515380859375, -17.3710460662841796875), - Vec3fEq(42.7990570068359375, -128.80755615234375, -14.7094440460205078125), - Vec3fEq(68.4763641357421875, -140.7847747802734375, -12.0478420257568359375), - Vec3fEq(94.15366363525390625, -152.761993408203125, -9.3862361907958984375), - Vec3fEq(119.83097076416015625, -164.7392120361328125, -6.724635601043701171875), - Vec3fEq(145.508270263671875, -176.7164306640625, -4.06303119659423828125), - Vec3fEq(171.185577392578125, -188.69366455078125, -1.40142619609832763671875), - Vec3fEq(196.862884521484375, -200.6708831787109375, 1.2601754665374755859375), - Vec3fEq(204, -204, 1.999979496002197265625) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(69.013885498046875, 434.49853515625, -0.74384129047393798828125), + Vec3fEq(81.36110687255859375, 408.997100830078125, -3.4876689910888671875), + Vec3fEq(93.7083282470703125, 383.495635986328125, -6.2314929962158203125), + Vec3fEq(106.0555419921875, 357.99420166015625, -8.97531890869140625), + Vec3fEq(118.40276336669921875, 332.49273681640625, -11.7191448211669921875), + Vec3fEq(130.7499847412109375, 306.991302490234375, -14.4629726409912109375), + Vec3fEq(143.0972137451171875, 281.4898681640625, -17.206798553466796875), + Vec3fEq(155.4444122314453125, 255.9884033203125, -19.9506206512451171875), + Vec3fEq(167.7916412353515625, 230.4869537353515625, -19.91887664794921875), + Vec3fEq(189.053619384765625, 211.75982666015625, -20.1138629913330078125), + Vec3fEq(210.3155975341796875, 193.032684326171875, -20.3088512420654296875), + Vec3fEq(231.577606201171875, 174.3055419921875, -20.503841400146484375), + Vec3fEq(252.839599609375, 155.5784149169921875, -19.9803981781005859375), + Vec3fEq(278.407989501953125, 143.3704071044921875, -17.2675113677978515625), + Vec3fEq(303.976348876953125, 131.16241455078125, -14.55462360382080078125), + Vec3fEq(329.54473876953125, 118.9544219970703125, -11.84173583984375), + Vec3fEq(355.11309814453125, 106.74642181396484375, -9.12884807586669921875), + Vec3fEq(380.681488037109375, 94.538421630859375, -6.4159603118896484375), + Vec3fEq(406.249847412109375, 82.33042144775390625, -3.7030735015869140625), + Vec3fEq(431.8182373046875, 70.1224365234375, -0.990187108516693115234375), + Vec3fEq(457.38665771484375, 57.9144439697265625, 1.72269880771636962890625), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } @@ -527,38 +544,40 @@ namespace 0, -50, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, osg::Vec3f(0, 0, 300)); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addWater(mCellPosition, cellSize, osg::Vec3f(shift.x(), shift.y(), 300)); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mStart.x() = 0; + mStart.x() = 256; mStart.z() = 300; - mEnd.x() = 0; + mEnd.x() = 256; mEnd.z() = 300; EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(0, 204, 185.33331298828125), - Vec3fEq(0, 175.6666717529296875, 185.33331298828125), - Vec3fEq(0, 147.3333282470703125, 185.33331298828125), - Vec3fEq(0, 119, 185.33331298828125), - Vec3fEq(0, 90.6666717529296875, 185.33331298828125), - Vec3fEq(0, 62.333339691162109375, 185.33331298828125), - Vec3fEq(0, 34.00000762939453125, 185.33331298828125), - Vec3fEq(0, 5.66667461395263671875, 185.33331298828125), - Vec3fEq(0, -22.6666584014892578125, 185.33331298828125), - Vec3fEq(0, -50.999988555908203125, 185.33331298828125), - Vec3fEq(0, -79.33332061767578125, 185.33331298828125), - Vec3fEq(0, -107.666656494140625, 185.33331298828125), - Vec3fEq(0, -135.9999847412109375, 185.33331298828125), - Vec3fEq(0, -164.33331298828125, 185.33331298828125), - Vec3fEq(0, -192.666656494140625, 185.33331298828125), - Vec3fEq(0, -204, 185.33331298828125) + Vec3fEq(256, 460, 185.33331298828125), + Vec3fEq(256, 431.666656494140625, 185.33331298828125), + Vec3fEq(256, 403.33331298828125, 185.33331298828125), + Vec3fEq(256, 375, 185.33331298828125), + Vec3fEq(256, 346.666656494140625, 185.33331298828125), + Vec3fEq(256, 318.33331298828125, 185.33331298828125), + Vec3fEq(256, 290, 185.33331298828125), + Vec3fEq(256, 261.666656494140625, 185.33331298828125), + Vec3fEq(256, 233.3333282470703125, 185.33331298828125), + Vec3fEq(256, 205, 185.33331298828125), + Vec3fEq(256, 176.6666717529296875, 185.33331298828125), + Vec3fEq(256, 148.3333282470703125, 185.33331298828125), + Vec3fEq(256, 120, 185.33331298828125), + Vec3fEq(256, 91.6666717529296875, 185.33331298828125), + Vec3fEq(255.999969482421875, 63.33333587646484375, 185.33331298828125), + Vec3fEq(255.999969482421875, 56.66666412353515625, 185.33331298828125) )) << mPath; } @@ -574,36 +593,38 @@ namespace 0, 0, 0, 0, 0, 0, 0, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addWater(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), osg::Vec3f(0, 0, -25)); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addWater(mCellPosition, cellSize, osg::Vec3f(shift.x(), shift.y(), -25)); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mStart.x() = 0; - mEnd.x() = 0; + mStart.x() = 256; + mEnd.x() = 256; EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(0, 204, -98.000030517578125), - Vec3fEq(0, 175.6666717529296875, -108.30306243896484375), - Vec3fEq(0, 147.3333282470703125, -118.6060791015625), - Vec3fEq(0, 119, -128.90911865234375), - Vec3fEq(0, 90.6666717529296875, -139.2121429443359375), - Vec3fEq(0, 62.333339691162109375, -143.3333587646484375), - Vec3fEq(0, 34.00000762939453125, -143.3333587646484375), - Vec3fEq(0, 5.66667461395263671875, -143.3333587646484375), - Vec3fEq(0, -22.6666584014892578125, -143.3333587646484375), - Vec3fEq(0, -50.999988555908203125, -143.3333587646484375), - Vec3fEq(0, -79.33332061767578125, -143.3333587646484375), - Vec3fEq(0, -107.666656494140625, -133.0303192138671875), - Vec3fEq(0, -135.9999847412109375, -122.72728729248046875), - Vec3fEq(0, -164.33331298828125, -112.4242706298828125), - Vec3fEq(0, -192.666656494140625, -102.12123870849609375), - Vec3fEq(0, -204, -98.00002288818359375) + Vec3fEq(256, 460, -129.4098663330078125), + Vec3fEq(256, 431.666656494140625, -129.6970062255859375), + Vec3fEq(256, 403.33331298828125, -129.6970062255859375), + Vec3fEq(256, 375, -129.4439239501953125), + Vec3fEq(256, 346.666656494140625, -129.02587890625), + Vec3fEq(256, 318.33331298828125, -128.6078338623046875), + Vec3fEq(256, 290, -128.1021728515625), + Vec3fEq(256, 261.666656494140625, -126.46875), + Vec3fEq(256, 233.3333282470703125, -119.4891357421875), + Vec3fEq(256, 205, -110.62021636962890625), + Vec3fEq(256, 176.6666717529296875, -101.7512969970703125), + Vec3fEq(256, 148.3333282470703125, -92.88237762451171875), + Vec3fEq(256, 120, -75.29378509521484375), + Vec3fEq(256, 91.6666717529296875, -55.201839447021484375), + Vec3fEq(256.000030517578125, 63.33333587646484375, -34.800380706787109375), + Vec3fEq(256.000030517578125, 56.66666412353515625, -30.00003814697265625) )) << mPath; } @@ -619,36 +640,38 @@ namespace 0, 0, 0, 0, 0, 0, 0, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - mNavigator->addWater(osg::Vec2i(0, 0), std::numeric_limits::max(), osg::Vec3f(0, 0, -25)); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addWater(mCellPosition, std::numeric_limits::max(), osg::Vec3f(shift.x(), shift.y(), -25)); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mStart.x() = 0; - mEnd.x() = 0; + mStart.x() = 256; + mEnd.x() = 256; EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(0, 204, -98.000030517578125), - Vec3fEq(0, 175.6666717529296875, -108.30306243896484375), - Vec3fEq(0, 147.3333282470703125, -118.6060791015625), - Vec3fEq(0, 119, -128.90911865234375), - Vec3fEq(0, 90.6666717529296875, -139.2121429443359375), - Vec3fEq(0, 62.333339691162109375, -143.3333587646484375), - Vec3fEq(0, 34.00000762939453125, -143.3333587646484375), - Vec3fEq(0, 5.66667461395263671875, -143.3333587646484375), - Vec3fEq(0, -22.6666584014892578125, -143.3333587646484375), - Vec3fEq(0, -50.999988555908203125, -143.3333587646484375), - Vec3fEq(0, -79.33332061767578125, -143.3333587646484375), - Vec3fEq(0, -107.666656494140625, -133.0303192138671875), - Vec3fEq(0, -135.9999847412109375, -122.72728729248046875), - Vec3fEq(0, -164.33331298828125, -112.4242706298828125), - Vec3fEq(0, -192.666656494140625, -102.12123870849609375), - Vec3fEq(0, -204, -98.00002288818359375) + Vec3fEq(256, 460, -129.4098663330078125), + Vec3fEq(256, 431.666656494140625, -129.6970062255859375), + Vec3fEq(256, 403.33331298828125, -129.6970062255859375), + Vec3fEq(256, 375, -129.4439239501953125), + Vec3fEq(256, 346.666656494140625, -129.02587890625), + Vec3fEq(256, 318.33331298828125, -128.6078338623046875), + Vec3fEq(256, 290, -128.1021728515625), + Vec3fEq(256, 261.666656494140625, -126.46875), + Vec3fEq(256, 233.3333282470703125, -119.4891357421875), + Vec3fEq(256, 205, -110.62021636962890625), + Vec3fEq(256, 176.6666717529296875, -101.7512969970703125), + Vec3fEq(256, 148.3333282470703125, -92.88237762451171875), + Vec3fEq(256, 120, -75.29378509521484375), + Vec3fEq(256, 91.6666717529296875, -55.201839447021484375), + Vec3fEq(256.000030517578125, 63.33333587646484375, -34.800380706787109375), + Vec3fEq(256.000030517578125, 56.66666412353515625, -30.00003814697265625) )) << mPath; } @@ -664,37 +687,38 @@ namespace 0, 0, 0, 0, 0, 0, 0, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, osg::Vec3f(0, 0, -25)); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addWater(mCellPosition, cellSize, osg::Vec3f(shift.x(), shift.y(), -25)); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mStart.x() = 0; - mEnd.x() = 0; + mStart.x() = 256; + mEnd.x() = 256; EXPECT_EQ(findPath(*mNavigator, mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(0, 204, -98.000030517578125), - Vec3fEq(10.26930999755859375, 177.59320068359375, -107.4711456298828125), - Vec3fEq(20.5386199951171875, 151.1864166259765625, -116.9422607421875), - Vec3fEq(30.8079280853271484375, 124.77960968017578125, -126.41339111328125), - Vec3fEq(41.077239990234375, 98.37281036376953125, -135.8845062255859375), - Vec3fEq(51.346546173095703125, 71.96601104736328125, -138.2003936767578125), - Vec3fEq(61.615856170654296875, 45.559215545654296875, -140.0838470458984375), - Vec3fEq(71.88516998291015625, 19.1524181365966796875, -141.9673004150390625), - Vec3fEq(82.15447235107421875, -7.254379749298095703125, -142.3074798583984375), - Vec3fEq(81.04636383056640625, -35.56603240966796875, -142.7104339599609375), - Vec3fEq(79.93825531005859375, -63.877685546875, -143.1133880615234375), - Vec3fEq(78.83014678955078125, -92.18933868408203125, -138.7660675048828125), - Vec3fEq(62.50392913818359375, -115.3460235595703125, -130.237823486328125), - Vec3fEq(46.17771148681640625, -138.502716064453125, -121.8172149658203125), - Vec3fEq(29.85149383544921875, -161.6594085693359375, -113.39659881591796875), - Vec3fEq(13.52527523040771484375, -184.81610107421875, -104.97599029541015625), - Vec3fEq(0, -204, -98.00002288818359375) + Vec3fEq(256, 460, -129.4098663330078125), + Vec3fEq(256, 431.666656494140625, -129.6970062255859375), + Vec3fEq(256, 403.33331298828125, -129.6970062255859375), + Vec3fEq(256, 375, -129.4439239501953125), + Vec3fEq(256, 346.666656494140625, -129.02587890625), + Vec3fEq(256, 318.33331298828125, -128.6078338623046875), + Vec3fEq(256, 290, -128.1021728515625), + Vec3fEq(256, 261.666656494140625, -126.46875), + Vec3fEq(256, 233.3333282470703125, -119.4891357421875), + Vec3fEq(256, 205, -110.62021636962890625), + Vec3fEq(256, 176.6666717529296875, -101.7512969970703125), + Vec3fEq(256, 148.3333282470703125, -92.88237762451171875), + Vec3fEq(256, 120, -75.29378509521484375), + Vec3fEq(256, 91.6666717529296875, -55.201839447021484375), + Vec3fEq(256.000030517578125, 63.33333587646484375, -34.800380706787109375), + Vec3fEq(256.000030517578125, 56.66666412353515625, -30.00003814697265625) )) << mPath; } @@ -711,7 +735,7 @@ namespace heightfield.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -719,7 +743,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -727,28 +751,28 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.99998295307159423828125), - Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), - Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), - Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), - Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), - Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), - Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), - Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), - Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), - Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), - Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), - Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), - Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), - Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), - Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), - Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), - Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), - Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), - Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), - Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), - Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), - Vec3fEq(204, -204, 1.99998295307159423828125) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), + Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), + Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), + Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), + Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), + Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), + Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), + Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), + Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), + Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), + Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), + Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), + Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), + Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), + Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), + Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), + Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), + Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), + Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), + Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } @@ -762,9 +786,11 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -772,7 +798,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -780,44 +806,47 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.99998295307159423828125), - Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), - Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), - Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), - Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), - Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), - Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), - Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), - Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), - Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), - Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), - Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), - Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), - Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), - Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), - Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), - Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), - Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), - Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), - Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), - Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), - Vec3fEq(204, -204, 1.99998295307159423828125) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(76.70135498046875, 439.965301513671875, -0.9659786224365234375), + Vec3fEq(96.73604583740234375, 419.93060302734375, -4.002437114715576171875), + Vec3fEq(116.770751953125, 399.89593505859375, -7.0388965606689453125), + Vec3fEq(136.8054351806640625, 379.861236572265625, -11.5593852996826171875), + Vec3fEq(156.840118408203125, 359.826568603515625, -20.7333812713623046875), + Vec3fEq(176.8748016357421875, 339.7918701171875, -34.014251708984375), + Vec3fEq(196.90948486328125, 319.757171630859375, -47.2951202392578125), + Vec3fEq(216.944183349609375, 299.722503662109375, -59.4111785888671875), + Vec3fEq(236.9788665771484375, 279.68780517578125, -65.76436614990234375), + Vec3fEq(257.0135498046875, 259.65313720703125, -68.12311553955078125), + Vec3fEq(277.048248291015625, 239.618438720703125, -66.5666656494140625), + Vec3fEq(297.082916259765625, 219.583740234375, -60.305889129638671875), + Vec3fEq(317.11761474609375, 199.549041748046875, -49.181324005126953125), + Vec3fEq(337.15228271484375, 179.5143585205078125, -35.742702484130859375), + Vec3fEq(357.186981201171875, 159.47967529296875, -22.304073333740234375), + Vec3fEq(377.221649169921875, 139.4449920654296875, -12.65070629119873046875), + Vec3fEq(397.25634765625, 119.41030120849609375, -7.41098117828369140625), + Vec3fEq(417.291046142578125, 99.3756103515625, -4.382833957672119140625), + Vec3fEq(437.325714111328125, 79.340911865234375, -1.354687213897705078125), + Vec3fEq(457.360443115234375, 59.3062286376953125, 1.624610424041748046875), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_then_find_random_point_around_circle_should_return_position) { - const std::array heightfieldData {{ - 0, 0, 0, 0, 0, - 0, -25, -25, -25, -25, - 0, -25, -100, -100, -100, - 0, -25, -100, -100, -100, - 0, -25, -100, -100, -100, + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, -25, + 0, -25, -1000, -1000, -100, -100, + 0, -25, -1000, -1000, -100, -100, + 0, -25, -100, -100, -100, -100, + 0, -25, -100, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -825,12 +854,12 @@ namespace const auto result = findRandomPointAroundCircle(*mNavigator, mAgentHalfExtents, mStart, 100.0, Flag_walk); - ASSERT_THAT(result, Optional(Vec3fEq(-198.909332275390625, 123.06096649169921875, 1.99998414516448974609375))) + ASSERT_THAT(result, Optional(Vec3fEq(69.6253509521484375, 531.29852294921875, -2.6667339801788330078125))) << (result ? *result : osg::Vec3f()); const auto distance = (*result - mStart).length(); - EXPECT_FLOAT_EQ(distance, 81.105133056640625) << distance; + EXPECT_FLOAT_EQ(distance, 73.536231994628906) << distance; } TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) @@ -846,17 +875,19 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); std::vector> boxes; std::generate_n(std::back_inserter(boxes), 100, [] { return std::make_unique(btVector3(20, 20, 100)); }); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); for (std::size_t i = 0; i < boxes.size(); ++i) { - const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10, i * 10, i * 10)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10, shift.y() + i * 10, i * 10)); mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); } @@ -864,7 +895,7 @@ namespace for (std::size_t i = 0; i < boxes.size(); ++i) { - const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10 + 1, i * 10 + 1, i * 10 + 1)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10 + 1, shift.y() + i * 10 + 1, i * 10 + 1)); mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); } @@ -875,29 +906,29 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 1.99998295307159423828125), - Vec3fEq(-189.9427337646484375, 179.3997802734375, 1.9999866485595703125), - Vec3fEq(-175.8854522705078125, 154.7995452880859375, 1.99999034404754638671875), - Vec3fEq(-161.82818603515625, 130.1993255615234375, -3.701923847198486328125), - Vec3fEq(-147.770904541015625, 105.5991058349609375, -15.67664432525634765625), - Vec3fEq(-133.7136383056640625, 80.99887847900390625, -27.6513614654541015625), - Vec3fEq(-119.65636444091796875, 56.39865875244140625, -20.1209163665771484375), - Vec3fEq(-105.59909820556640625, 31.798435211181640625, -25.0669879913330078125), - Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), - Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), - Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), - Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), - Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), - Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), - Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.1782474517822265625), - Vec3fEq(53.413402557373046875, -120.6742401123046875, -19.4096889495849609375), - Vec3fEq(78.20446014404296875, -134.39215087890625, -27.6632633209228515625), - Vec3fEq(102.99552154541015625, -148.110076904296875, -15.8613681793212890625), - Vec3fEq(127.7865753173828125, -161.827972412109375, -4.059485912322998046875), - Vec3fEq(152.57763671875, -175.5458984375, 1.9999904632568359375), - Vec3fEq(177.3686981201171875, -189.2638092041015625, 1.9999866485595703125), - Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.9999830722808837890625), - Vec3fEq(204, -204, 1.99998295307159423828125) + Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125), + Vec3fEq(69.5299530029296875, 434.754913330078125, -2.6775772571563720703125), + Vec3fEq(82.39324951171875, 409.50982666015625, -7.355137348175048828125), + Vec3fEq(95.25653839111328125, 384.2647705078125, -12.0326976776123046875), + Vec3fEq(108.11983489990234375, 359.019683837890625, -16.71025848388671875), + Vec3fEq(120.983123779296875, 333.774627685546875, -21.3878192901611328125), + Vec3fEq(133.8464202880859375, 308.529541015625, -26.0653781890869140625), + Vec3fEq(146.7097015380859375, 283.284454345703125, -30.7429370880126953125), + Vec3fEq(159.572998046875, 258.039398193359375, -35.420497894287109375), + Vec3fEq(172.4362945556640625, 232.7943115234375, -27.2731761932373046875), + Vec3fEq(185.2996063232421875, 207.54925537109375, -20.3612518310546875), + Vec3fEq(206.6449737548828125, 188.917236328125, -20.578319549560546875), + Vec3fEq(227.9903564453125, 170.28521728515625, -26.291717529296875), + Vec3fEq(253.4362640380859375, 157.8239593505859375, -34.784488677978515625), + Vec3fEq(278.8822021484375, 145.3627166748046875, -30.253124237060546875), + Vec3fEq(304.328094482421875, 132.9014739990234375, -25.72176361083984375), + Vec3fEq(329.774017333984375, 120.44022369384765625, -21.1904010772705078125), + Vec3fEq(355.219940185546875, 107.97898101806640625, -16.6590404510498046875), + Vec3fEq(380.665863037109375, 95.51773834228515625, -12.127681732177734375), + Vec3fEq(406.111785888671875, 83.05649566650390625, -7.5963191986083984375), + Vec3fEq(431.557708740234375, 70.5952606201171875, -3.0649592876434326171875), + Vec3fEq(457.003662109375, 58.134021759033203125, 1.4664003849029541015625), + Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; } @@ -949,15 +980,19 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - const auto result = raycast(*mNavigator, mAgentHalfExtents, mStart, mEnd, Flag_walk); + const osg::Vec3f start(57, 460, 1); + const osg::Vec3f end(460, 57, 1); + const auto result = raycast(*mNavigator, mAgentHalfExtents, start, end, Flag_walk); - ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.99998295307159423828125))) + ASSERT_THAT(result, Optional(Vec3fEq(end.x(), end.y(), 1.95257937908172607421875))) << (result ? *result : osg::Vec3f()); } @@ -971,27 +1006,28 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); - const btVector3 oscillatingBoxShapePosition(32, 32, 400); - CollisionShapeInstance boderBox(std::make_unique(btVector3(50, 50, 50))); + const btVector3 oscillatingBoxShapePosition(288, 288, 400); + CollisionShapeInstance borderBox(std::make_unique(btVector3(50, 50, 50))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations - mNavigator->addObject(ObjectId(&boderBox.shape()), ObjectShapes(boderBox.instance()), + mNavigator->addObject(ObjectId(&borderBox.shape()), ObjectShapes(borderBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); + const Version expectedVersion {1, 1}; + const auto navMeshes = mNavigator->getNavMeshes(); ASSERT_EQ(navMeshes.size(), 1); - { - const auto navMesh = navMeshes.begin()->second->lockConst(); - ASSERT_EQ(navMesh->getVersion(), (Version {1, 4})); - } + ASSERT_EQ(navMeshes.begin()->second->lockConst()->getVersion(), expectedVersion); for (int n = 0; n < 10; ++n) { @@ -1003,18 +1039,17 @@ namespace } ASSERT_EQ(navMeshes.size(), 1); - { - const auto navMesh = navMeshes.begin()->second->lockConst(); - ASSERT_EQ(navMesh->getVersion(), (Version {1, 4})); - } + ASSERT_EQ(navMeshes.begin()->second->lockConst()->getVersion(), expectedVersion); } TEST_F(DetourNavigatorNavigatorTest, should_provide_path_over_flat_heightfield) { const HeightfieldPlane plane {100}; + const int cellSize = mHeightfieldTileSize * 4; + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, plane.mHeight, plane.mHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * 4, mShift, plane); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, plane); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); @@ -1022,28 +1057,28 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, 101.99999237060546875), - Vec3fEq(-183.965301513671875, 183.965301513671875, 101.99999237060546875), - Vec3fEq(-163.9306182861328125, 163.9306182861328125, 101.99999237060546875), - Vec3fEq(-143.89593505859375, 143.89593505859375, 101.99999237060546875), - Vec3fEq(-123.86124420166015625, 123.86124420166015625, 101.99999237060546875), - Vec3fEq(-103.8265533447265625, 103.8265533447265625, 101.99999237060546875), - Vec3fEq(-83.7918548583984375, 83.7918548583984375, 101.99999237060546875), - Vec3fEq(-63.75716400146484375, 63.75716400146484375, 101.99999237060546875), - Vec3fEq(-43.72247314453125, 43.72247314453125, 101.99999237060546875), - Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, 101.99999237060546875), - Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, 101.99999237060546875), - Vec3fEq(16.3816013336181640625, -16.3816013336181640625, 101.99999237060546875), - Vec3fEq(36.416290283203125, -36.416290283203125, 101.99999237060546875), - Vec3fEq(56.450984954833984375, -56.450984954833984375, 101.99999237060546875), - Vec3fEq(76.4856719970703125, -76.4856719970703125, 101.99999237060546875), - Vec3fEq(96.52036285400390625, -96.52036285400390625, 101.99999237060546875), - Vec3fEq(116.5550537109375, -116.5550537109375, 101.99999237060546875), - Vec3fEq(136.5897369384765625, -136.5897369384765625, 101.99999237060546875), - Vec3fEq(156.6244354248046875, -156.6244354248046875, 101.99999237060546875), - Vec3fEq(176.6591339111328125, -176.6591339111328125, 101.99999237060546875), - Vec3fEq(196.693817138671875, -196.693817138671875, 101.99999237060546875), - Vec3fEq(204, -204, 101.99999237060546875) + Vec3fEq(56.66666412353515625, 460, 203.9999847412109375), + Vec3fEq(76.70135498046875, 439.965301513671875, 203.9999847412109375), + Vec3fEq(96.73604583740234375, 419.93060302734375, 203.9999847412109375), + Vec3fEq(116.770751953125, 399.89593505859375, 203.9999847412109375), + Vec3fEq(136.8054351806640625, 379.861236572265625, 203.9999847412109375), + Vec3fEq(156.840118408203125, 359.826568603515625, 203.9999847412109375), + Vec3fEq(176.8748016357421875, 339.7918701171875, 203.9999847412109375), + Vec3fEq(196.90948486328125, 319.757171630859375, 203.9999847412109375), + Vec3fEq(216.944183349609375, 299.722503662109375, 203.9999847412109375), + Vec3fEq(236.9788665771484375, 279.68780517578125, 203.9999847412109375), + Vec3fEq(257.0135498046875, 259.65313720703125, 203.9999847412109375), + Vec3fEq(277.048248291015625, 239.618438720703125, 203.9999847412109375), + Vec3fEq(297.082916259765625, 219.583740234375, 203.9999847412109375), + Vec3fEq(317.11761474609375, 199.549041748046875, 203.9999847412109375), + Vec3fEq(337.15228271484375, 179.5143585205078125, 203.9999847412109375), + Vec3fEq(357.186981201171875, 159.47967529296875, 203.9999847412109375), + Vec3fEq(377.221649169921875, 139.4449920654296875, 203.9999847412109375), + Vec3fEq(397.25634765625, 119.41030120849609375, 203.9999847412109375), + Vec3fEq(417.291046142578125, 99.3756103515625, 203.9999847412109375), + Vec3fEq(437.325714111328125, 79.340911865234375, 203.9999847412109375), + Vec3fEq(457.360443115234375, 59.3062286376953125, 203.9999847412109375), + Vec3fEq(460, 56.66666412353515625, 203.9999847412109375) )) << mPath; } @@ -1057,14 +1092,16 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), new btBoxShape(btVector3(200, 200, 1000))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1072,23 +1109,16 @@ namespace Status::PartialPath); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, -2.666739940643310546875), - Vec3fEq(-193.730682373046875, 177.59320068359375, -3.9535934925079345703125), - Vec3fEq(-183.4613800048828125, 151.1864166259765625, -5.240451335906982421875), - Vec3fEq(-173.1920623779296875, 124.77962493896484375, -6.527309417724609375), - Vec3fEq(-162.922760009765625, 98.37282562255859375, -7.814167022705078125), - Vec3fEq(-152.6534423828125, 71.96602630615234375, -9.898590087890625), - Vec3fEq(-142.384124755859375, 45.559230804443359375, -17.641445159912109375), - Vec3fEq(-132.1148223876953125, 19.152431488037109375, -25.3843059539794921875), - Vec3fEq(-121.8455047607421875, -7.254369258880615234375, -27.97742462158203125), - Vec3fEq(-111.57619476318359375, -33.66117095947265625, -16.974590301513671875), - Vec3fEq(-101.30689239501953125, -60.06797027587890625, -5.9717559814453125), - Vec3fEq(-91.0375823974609375, -86.47476959228515625, -2.6667339801788330078125), - Vec3fEq(-80.76827239990234375, -112.88156890869140625, -2.6667339801788330078125), - Vec3fEq(-70.49897003173828125, -139.2883758544921875, -2.6667339801788330078125), - Vec3fEq(-60.229663848876953125, -165.6951751708984375, -2.6667339801788330078125), - Vec3fEq(-49.96035003662109375, -192.1019744873046875, -2.6667339801788330078125), - Vec3fEq(-45.333343505859375, -204, -2.6667339801788330078125) + Vec3fEq(56.66666412353515625, 460, -2.5371043682098388671875), + Vec3fEq(76.4206390380859375, 439.688446044921875, -2.913421630859375), + Vec3fEq(96.17461395263671875, 419.37689208984375, -4.508244037628173828125), + Vec3fEq(115.9285888671875, 399.06536865234375, -6.103069305419921875), + Vec3fEq(135.68255615234375, 378.753814697265625, -7.69789028167724609375), + Vec3fEq(155.4365386962890625, 358.44232177734375, -20.9574832916259765625), + Vec3fEq(175.190521240234375, 338.130767822265625, -35.907501220703125), + Vec3fEq(194.944488525390625, 317.8192138671875, -50.8574981689453125), + Vec3fEq(214.6984710693359375, 297.507720947265625, -65.8075103759765625), + Vec3fEq(222.0000457763671875, 290.00006103515625, -71.3334197998046875) )) << mPath; } @@ -1102,14 +1132,16 @@ namespace 0, -25, -100, -100, -100, }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), new btBoxShape(btVector3(100, 100, 1000))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, mHeightfieldTileSize * (surface.mSize - 1), mShift, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); + mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1119,25 +1151,24 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-204, 204, -2.666739940643310546875), - Vec3fEq(-188.745635986328125, 180.1236114501953125, -4.578275203704833984375), - Vec3fEq(-173.49127197265625, 156.247222900390625, -6.489814281463623046875), - Vec3fEq(-158.2369232177734375, 132.370819091796875, -8.4013538360595703125), - Vec3fEq(-142.9825592041015625, 108.49443817138671875, -10.31289386749267578125), - Vec3fEq(-127.7281951904296875, 84.6180419921875, -17.4810466766357421875), - Vec3fEq(-112.47383880615234375, 60.7416534423828125, -27.6026020050048828125), - Vec3fEq(-97.21947479248046875, 36.865261077880859375, -37.724163055419921875), - Vec3fEq(-81.965118408203125, 12.98886966705322265625, -47.84572601318359375), - Vec3fEq(-66.71075439453125, -10.887523651123046875, -46.691577911376953125), - Vec3fEq(-51.45639801025390625, -34.763916015625, -32.085445404052734375), - Vec3fEq(-36.202037811279296875, -58.640308380126953125, -28.5217914581298828125), - Vec3fEq(-20.947673797607421875, -82.5167083740234375, -32.16143035888671875), - Vec3fEq(-5.693310260772705078125, -106.393096923828125, -35.8010711669921875), - Vec3fEq(9.56105327606201171875, -130.2694854736328125, -29.6399688720703125), - Vec3fEq(24.8154163360595703125, -154.1458740234375, -17.6428318023681640625), - Vec3fEq(40.0697784423828125, -178.0222625732421875, -10.46006107330322265625), - Vec3fEq(55.3241424560546875, -201.8986663818359375, -3.297139644622802734375), - Vec3fEq(56.66666412353515625, -204, -2.6667373180389404296875) + Vec3fEq(56.66666412353515625, 460, -2.5371043682098388671875), + Vec3fEq(71.5649566650390625, 435.899810791015625, -5.817593097686767578125), + Vec3fEq(86.46324920654296875, 411.79962158203125, -9.66499996185302734375), + Vec3fEq(101.36154937744140625, 387.699462890625, -13.512401580810546875), + Vec3fEq(116.2598419189453125, 363.599273681640625, -17.359806060791015625), + Vec3fEq(131.1581268310546875, 339.499114990234375, -21.2072086334228515625), + Vec3fEq(146.056427001953125, 315.39892578125, -25.0546112060546875), + Vec3fEq(160.9547271728515625, 291.298736572265625, -28.9020137786865234375), + Vec3fEq(175.8530120849609375, 267.198577880859375, -32.749416351318359375), + Vec3fEq(190.751312255859375, 243.098388671875, -33.819454193115234375), + Vec3fEq(205.64959716796875, 218.9982147216796875, -31.020172119140625), + Vec3fEq(220.5478973388671875, 194.898040771484375, -26.844608306884765625), + Vec3fEq(235.446197509765625, 170.7978668212890625, -26.785541534423828125), + Vec3fEq(250.3444671630859375, 146.6976776123046875, -26.7264766693115234375), + Vec3fEq(265.242767333984375, 122.59751129150390625, -20.59339141845703125), + Vec3fEq(280.141021728515625, 98.4973297119140625, -14.040531158447265625), + Vec3fEq(295.039306640625, 74.39715576171875, -7.48766994476318359375), + Vec3fEq(306, 56.66666412353515625, -2.6667339801788330078125) )) << mPath; } } diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp index 92740c65f1..fb6fcc5c39 100644 --- a/apps/openmw_test_suite/detournavigator/operators.hpp +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -48,7 +48,7 @@ namespace testing template <> inline testing::Message& Message::operator <<(const osg::Vec3f& value) { - return (*this) << "osg::Vec3f(" << std::setprecision(std::numeric_limits::max_exponent10) << value.x() + return (*this) << "Vec3fEq(" << std::setprecision(std::numeric_limits::max_exponent10) << value.x() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.y() << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.z() << ')'; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index c53fcdcc48..890e2c994b 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -357,6 +357,7 @@ namespace rcPolyMeshDetail& polyMeshDetail) { rcCompactHeightfield compact; + compact.dist = nullptr; buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact); erodeWalkableArea(context, config.walkableRadius, compact); From f4f4458d01716613ff78369a538959060f2af559 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Nov 2021 15:30:56 +0100 Subject: [PATCH 1811/2859] Calculate recast mesh bounds when building navmesh --- .../detournavigator/navigator.cpp | 20 ++--- components/detournavigator/makenavmesh.cpp | 84 ++++++++++++------- components/detournavigator/recastmesh.cpp | 22 ----- components/detournavigator/recastmesh.hpp | 6 -- 4 files changed, 66 insertions(+), 66 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 367572d449..f6b09c1d55 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1109,16 +1109,16 @@ namespace Status::PartialPath); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(56.66666412353515625, 460, -2.5371043682098388671875), - Vec3fEq(76.4206390380859375, 439.688446044921875, -2.913421630859375), - Vec3fEq(96.17461395263671875, 419.37689208984375, -4.508244037628173828125), - Vec3fEq(115.9285888671875, 399.06536865234375, -6.103069305419921875), - Vec3fEq(135.68255615234375, 378.753814697265625, -7.69789028167724609375), - Vec3fEq(155.4365386962890625, 358.44232177734375, -20.9574832916259765625), - Vec3fEq(175.190521240234375, 338.130767822265625, -35.907501220703125), - Vec3fEq(194.944488525390625, 317.8192138671875, -50.8574981689453125), - Vec3fEq(214.6984710693359375, 297.507720947265625, -65.8075103759765625), - Vec3fEq(222.0000457763671875, 290.00006103515625, -71.3334197998046875) + Vec3fEq(56.66664886474609375, 460, -2.5371043682098388671875), + Vec3fEq(76.42063140869140625, 439.6884765625, -2.9134314060211181640625), + Vec3fEq(96.17461395263671875, 419.376953125, -4.50826549530029296875), + Vec3fEq(115.9285888671875, 399.0654296875, -6.1030979156494140625), + Vec3fEq(135.6825714111328125, 378.753936767578125, -7.697928905487060546875), + Vec3fEq(155.436553955078125, 358.442413330078125, -20.9574985504150390625), + Vec3fEq(175.190521240234375, 338.130889892578125, -35.907512664794921875), + Vec3fEq(194.9445037841796875, 317.8193359375, -50.85752105712890625), + Vec3fEq(214.698486328125, 297.5078125, -65.807525634765625), + Vec3fEq(222.0001068115234375, 290.000091552734375, -71.333465576171875) )) << mPath; } diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 890e2c994b..cac4be0fdd 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -121,7 +121,7 @@ namespace return result; } - rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, + rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const TilePosition& tile, float minZ, float maxZ, const Settings& settings) { rcConfig config; @@ -140,15 +140,18 @@ namespace config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; config.borderSize = settings.mBorderSize; - config.width = settings.mTileSize + config.borderSize * 2; - config.height = settings.mTileSize + config.borderSize * 2; - rcVcopy(config.bmin, boundsMin.ptr()); - rcVcopy(config.bmax, boundsMax.ptr()); - config.bmin[0] -= getBorderSize(settings); - config.bmin[2] -= getBorderSize(settings); - config.bmax[0] += getBorderSize(settings); - config.bmax[2] += getBorderSize(settings); config.tileSize = settings.mTileSize; + const int size = config.tileSize + config.borderSize * 2; + config.width = size; + config.height = size; + const float halfBoundsSize = size * config.cs * 0.5f; + const osg::Vec2f shift = osg::Vec2f(tile.x() + 0.5f, tile.y() + 0.5f) * getTileSize(settings); + config.bmin[0] = shift.x() - halfBoundsSize; + config.bmin[1] = minZ; + config.bmin[2] = shift.y() - halfBoundsSize; + config.bmax[0] = shift.x() + halfBoundsSize; + config.bmax[1] = maxZ; + config.bmax[2] = shift.y() + halfBoundsSize; return config; } @@ -388,20 +391,56 @@ namespace ++power; return power; } + + std::pair getBoundsByZ(const RecastMesh& recastMesh, const osg::Vec3f& agentHalfExtents, const Settings& settings) + { + float minZ = 0; + float maxZ = 0; + + const std::vector& vertices = recastMesh.getMesh().getVertices(); + for (std::size_t i = 0, n = vertices.size(); i < n; i += 3) + { + minZ = std::min(minZ, vertices[i + 2]); + maxZ = std::max(maxZ, vertices[i + 2]); + } + + for (const Cell& water : recastMesh.getWater()) + { + const float swimLevel = getSwimLevel(settings, water.mShift.z(), agentHalfExtents.z()); + minZ = std::min(minZ, swimLevel); + maxZ = std::max(maxZ, swimLevel); + } + + for (const Heightfield& heightfield : recastMesh.getHeightfields()) + { + if (heightfield.mHeights.empty()) + continue; + const auto [minHeight, maxHeight] = std::minmax_element(heightfield.mHeights.begin(), heightfield.mHeights.end()); + minZ = std::min(minZ, *minHeight); + maxZ = std::max(maxZ, *maxHeight); + } + + for (const FlatHeightfield& heightfield : recastMesh.getFlatHeightfields()) + { + minZ = std::min(minZ, heightfield.mHeight); + maxZ = std::max(maxZ, heightfield.mHeight); + } + + return {minZ, maxZ}; + } } } // namespace DetourNavigator namespace DetourNavigator { std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, - const TilePosition& tile, const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings) + const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const Settings& settings) { - const TileBounds tileBounds = makeTileBounds(settings, tile); - const osg::Vec3f boundsMin(tileBounds.mMin.x(), bounds.mMin.y() - 1, tileBounds.mMin.y()); - const osg::Vec3f boundsMax(tileBounds.mMax.x(), bounds.mMax.y() + 1, tileBounds.mMax.y()); + const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings); rcContext context; - const auto config = makeConfig(agentHalfExtents, boundsMin, boundsMax, settings); + const auto config = makeConfig(agentHalfExtents, tilePosition, toNavMeshCoordinates(settings, minZ), + toNavMeshCoordinates(settings, maxZ), settings); rcHeightfield solid; createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); @@ -529,18 +568,8 @@ namespace DetourNavigator return navMeshCacheItem->lock()->removeTile(changedTile); } - auto recastMeshBounds = recastMesh->getBounds(); - recastMeshBounds.mMin = toNavMeshCoordinates(settings, recastMeshBounds.mMin); - recastMeshBounds.mMax = toNavMeshCoordinates(settings, recastMeshBounds.mMax); - - for (const auto& water : recastMesh->getWater()) - { - const float height = toNavMeshCoordinates(settings, getSwimLevel(settings, water.mShift.z(), agentHalfExtents.z())); - recastMeshBounds.mMin.y() = std::min(recastMeshBounds.mMin.y(), height); - recastMeshBounds.mMax.y() = std::max(recastMeshBounds.mMax.y(), height); - } - - if (isEmpty(recastMeshBounds)) + if (recastMesh->getMesh().getIndices().empty() && recastMesh->getWater().empty() + && recastMesh->getHeightfields().empty() && recastMesh->getFlatHeightfields().empty()) { Log(Debug::Debug) << "Ignore add tile: recastMesh is empty"; return navMeshCacheItem->lock()->removeTile(changedTile); @@ -559,8 +588,7 @@ namespace DetourNavigator if (!cachedNavMeshData) { - auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, recastMeshBounds, - agentHalfExtents, settings); + auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings); if (prepared == nullptr) { diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index e2dea0ad6e..258a700b0e 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -27,31 +27,9 @@ namespace DetourNavigator , mHeightfields(std::move(heightfields)) , mFlatHeightfields(std::move(flatHeightfields)) { - if (mMesh.getVerticesCount() > 0) - rcCalcBounds(mMesh.getVertices().data(), static_cast(mMesh.getVerticesCount()), - mBounds.mMin.ptr(), mBounds.mMax.ptr()); mWater.shrink_to_fit(); mHeightfields.shrink_to_fit(); for (Heightfield& v : mHeightfields) v.mHeights.shrink_to_fit(); - for (const Heightfield& v : mHeightfields) - { - const auto [min, max] = std::minmax_element(v.mHeights.begin(), v.mHeights.end()); - mBounds.mMin.x() = std::min(mBounds.mMin.x(), v.mBounds.mMin.x()); - mBounds.mMin.y() = std::min(mBounds.mMin.y(), v.mBounds.mMin.y()); - mBounds.mMin.z() = std::min(mBounds.mMin.z(), *min); - mBounds.mMax.x() = std::max(mBounds.mMax.x(), v.mBounds.mMax.x()); - mBounds.mMax.y() = std::max(mBounds.mMax.y(), v.mBounds.mMax.y()); - mBounds.mMax.z() = std::max(mBounds.mMax.z(), *max); - } - for (const FlatHeightfield& v : mFlatHeightfields) - { - mBounds.mMin.x() = std::min(mBounds.mMin.x(), v.mBounds.mMin.x()); - mBounds.mMin.y() = std::min(mBounds.mMin.y(), v.mBounds.mMin.y()); - mBounds.mMin.z() = std::min(mBounds.mMin.z(), v.mHeight); - mBounds.mMax.x() = std::max(mBounds.mMax.x(), v.mBounds.mMax.x()); - mBounds.mMax.y() = std::max(mBounds.mMax.y(), v.mBounds.mMax.y()); - mBounds.mMax.z() = std::max(mBounds.mMax.z(), v.mHeight); - } } } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index e2e7b9e188..928b282d19 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -118,11 +118,6 @@ namespace DetourNavigator return mFlatHeightfields; } - const Bounds& getBounds() const - { - return mBounds; - } - private: std::size_t mGeneration; std::size_t mRevision; @@ -130,7 +125,6 @@ namespace DetourNavigator std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; - Bounds mBounds; friend inline std::size_t getSize(const RecastMesh& value) noexcept { From 8571c317d8cc767255e15b57a668adaf963665d0 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Nov 2021 02:48:32 +0100 Subject: [PATCH 1812/2859] Add raw water data to navigator --- .../detournavigator/navmeshtilescache.cpp | 9 ++-- apps/openmw/mwworld/scene.cpp | 11 +---- .../detournavigator/navigator.cpp | 8 ++-- .../detournavigator/navmeshtilescache.cpp | 28 ++++++------ .../detournavigator/recastmeshbuilder.cpp | 28 ++++++++++-- .../tilecachedrecastmeshmanager.cpp | 24 +++++------ .../cachedrecastmeshmanager.cpp | 7 ++- .../cachedrecastmeshmanager.hpp | 4 +- components/detournavigator/makenavmesh.cpp | 23 +++++----- components/detournavigator/navigator.hpp | 2 +- components/detournavigator/navigatorimpl.cpp | 4 +- components/detournavigator/navigatorimpl.hpp | 2 +- components/detournavigator/navigatorstub.hpp | 2 +- components/detournavigator/navmeshmanager.cpp | 8 ++-- components/detournavigator/navmeshmanager.hpp | 2 +- .../detournavigator/navmeshtilescache.hpp | 2 +- components/detournavigator/recastmesh.cpp | 2 +- components/detournavigator/recastmesh.hpp | 43 +++++++++++++++++-- .../detournavigator/recastmeshbuilder.cpp | 4 +- .../detournavigator/recastmeshbuilder.hpp | 4 +- .../detournavigator/recastmeshmanager.cpp | 10 ++--- .../detournavigator/recastmeshmanager.hpp | 6 +-- .../tilecachedrecastmeshmanager.cpp | 12 +++--- .../tilecachedrecastmeshmanager.hpp | 4 +- 24 files changed, 150 insertions(+), 99 deletions(-) diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 6c530db41c..7acbcc3a4f 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -89,10 +89,11 @@ namespace template void generateWater(OutputIterator out, std::size_t count, Random& random) { - std::uniform_real_distribution distribution(0.0, 1.0); + std::uniform_real_distribution floatDistribution(0.0, 1.0); + std::uniform_int_distribution intDistribution(-10, 10); std::generate_n(out, count, [&] { - const osg::Vec3f shift(distribution(random), distribution(random), distribution(random)); - return Cell {1, shift}; + const osg::Vec2i cellPosition(intDistribution(random), intDistribution(random)); + return CellWater {cellPosition, Water {8196, floatDistribution(random)}}; }); } @@ -148,7 +149,7 @@ namespace const std::size_t generation = std::uniform_int_distribution(0, 100)(random); const std::size_t revision = std::uniform_int_distribution(0, 10000)(random); Mesh mesh = generateMesh(triangles, random); - std::vector water; + std::vector water; generateWater(std::back_inserter(water), 1, random); RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water), {generateHeightfield(random)}, {generateFlatHeightfield(random)}); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 769817810a..cc2a692e2b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -434,18 +434,11 @@ namespace MWWorld if (cell->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - { - const btTransform& transform =heightField->getCollisionObject()->getWorldTransform(); - mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - osg::Vec3f(static_cast(transform.getOrigin().x()), - static_cast(transform.getOrigin().y()), - waterLevel)); - } + mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, waterLevel); } else { - mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - osg::Vec3f(0, 0, waterLevel)); + mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), waterLevel); } } else diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index f6b09c1d55..a8b42f0db9 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -548,7 +548,7 @@ namespace const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addWater(mCellPosition, cellSize, osg::Vec3f(shift.x(), shift.y(), 300)); + mNavigator->addWater(mCellPosition, cellSize, 300); mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -597,7 +597,7 @@ namespace const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addWater(mCellPosition, cellSize, osg::Vec3f(shift.x(), shift.y(), -25)); + mNavigator->addWater(mCellPosition, cellSize, -25); mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -645,7 +645,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); - mNavigator->addWater(mCellPosition, std::numeric_limits::max(), osg::Vec3f(shift.x(), shift.y(), -25)); + mNavigator->addWater(mCellPosition, std::numeric_limits::max(), -25); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -691,7 +691,7 @@ namespace const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addWater(mCellPosition, cellSize, osg::Vec3f(shift.x(), shift.y(), -25)); + mNavigator->addWater(mCellPosition, cellSize, -25); mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index 309266142f..c56be440e3 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -144,14 +144,14 @@ namespace const std::size_t mGeneration = 0; const std::size_t mRevision = 0; const Mesh mMesh {makeMesh()}; - const std::vector mWater {}; + const std::vector mWater {}; const std::vector mHeightfields {}; const std::vector mFlatHeightfields {}; const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields}; std::unique_ptr mPreparedNavMeshData {makePeparedNavMeshData(3)}; const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh); - const std::size_t mRecastMeshWithWaterSize = mRecastMeshSize + sizeof(Cell); + const std::size_t mRecastMeshWithWaterSize = mRecastMeshSize + sizeof(CellWater); const std::size_t mPreparedNavMeshDataSize = sizeof(*mPreparedNavMeshData) + getSize(*mPreparedNavMeshData); }; @@ -234,7 +234,7 @@ namespace { const std::size_t maxSize = 1; NavMeshTilesCache cache(maxSize); - const std::vector water {1, Cell {1, osg::Vec3f()}}; + const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); @@ -246,7 +246,7 @@ namespace const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); - const std::vector water {1, Cell {1, osg::Vec3f()}}; + const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); const auto copy = clone(*anotherPreparedNavMeshData); @@ -264,7 +264,7 @@ namespace const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); - const std::vector water {1, Cell {1, osg::Vec3f()}}; + const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); @@ -280,12 +280,12 @@ namespace NavMeshTilesCache cache(maxSize); const auto copy = clone(*mPreparedNavMeshData); - const std::vector leastRecentlySetWater {1, Cell {1, osg::Vec3f()}}; + const std::vector leastRecentlySetWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mMesh, leastRecentlySetWater, mHeightfields, mFlatHeightfields}; auto leastRecentlySetData = makePeparedNavMeshData(3); - const std::vector mostRecentlySetWater {1, Cell {2, osg::Vec3f()}}; + const std::vector mostRecentlySetWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mMesh, mostRecentlySetWater, mHeightfields, mFlatHeightfields}; auto mostRecentlySetData = makePeparedNavMeshData(3); @@ -308,13 +308,13 @@ namespace const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); NavMeshTilesCache cache(maxSize); - const std::vector leastRecentlyUsedWater {1, Cell {1, osg::Vec3f()}}; + const std::vector leastRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, leastRecentlyUsedWater, mHeightfields, mFlatHeightfields}; auto leastRecentlyUsedData = makePeparedNavMeshData(3); const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData); - const std::vector mostRecentlyUsedWater {1, Cell {2, osg::Vec3f()}}; + const std::vector mostRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, mostRecentlyUsedWater, mHeightfields, mFlatHeightfields}; auto mostRecentlyUsedData = makePeparedNavMeshData(3); @@ -349,7 +349,7 @@ namespace const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); NavMeshTilesCache cache(maxSize); - const std::vector water {1, Cell {1, osg::Vec3f()}}; + const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; auto tooLargeData = makePeparedNavMeshData(10); @@ -364,12 +364,12 @@ namespace const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); NavMeshTilesCache cache(maxSize); - const std::vector anotherWater {1, Cell {1, osg::Vec3f()}}; + const std::vector anotherWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, anotherWater, mHeightfields, mFlatHeightfields}; auto anotherData = makePeparedNavMeshData(3); - const std::vector tooLargeWater {1, Cell {2, osg::Vec3f()}}; + const std::vector tooLargeWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, tooLargeWater, mHeightfields, mFlatHeightfields}; auto tooLargeData = makePeparedNavMeshData(10); @@ -390,7 +390,7 @@ namespace const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); - const std::vector water {1, Cell {1, osg::Vec3f()}}; + const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; auto anotherData = makePeparedNavMeshData(3); @@ -409,7 +409,7 @@ namespace const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; NavMeshTilesCache cache(maxSize); - const std::vector water {1, Cell {1, osg::Vec3f()}}; + const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; auto anotherData = makePeparedNavMeshData(3); diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index 73d86bd6ee..67a1206e46 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -28,6 +28,18 @@ namespace DetourNavigator return lhs.mSize == rhs.mSize && lhs.mShift == rhs.mShift; } + static inline bool operator ==(const Water& lhs, const Water& rhs) + { + const auto tie = [] (const Water& v) { return std::tie(v.mCellSize, v.mLevel); }; + return tie(lhs) == tie(rhs); + } + + static inline bool operator ==(const CellWater& lhs, const CellWater& rhs) + { + const auto tie = [] (const CellWater& v) { return std::tie(v.mCellPosition, v.mWater); }; + return tie(lhs) == tie(rhs); + } + static inline bool operator==(const Heightfield& lhs, const Heightfield& rhs) { return makeTuple(lhs) == makeTuple(rhs); @@ -38,6 +50,16 @@ namespace DetourNavigator return std::tie(lhs.mBounds, lhs.mHeight) == std::tie(rhs.mBounds, rhs.mHeight); } + static inline std::ostream& operator<<(std::ostream& s, const Water& v) + { + return s << "Water {" << v.mCellSize << ", " << v.mLevel << "}"; + } + + static inline std::ostream& operator<<(std::ostream& s, const CellWater& v) + { + return s << "CellWater {" << v.mCellPosition << ", " << v.mWater << "}"; + } + static inline std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v) { return s << "FlatHeightfield {" << v.mBounds << ", " << v.mHeight << "}"; @@ -435,10 +457,10 @@ namespace TEST_F(DetourNavigatorRecastMeshBuilderTest, add_water_then_get_water_should_return_it) { RecastMeshBuilder builder(mBounds); - builder.addWater(1000, osg::Vec3f(100, 200, 300)); + builder.addWater(osg::Vec2i(1, 2), Water {1000, 300.0f}); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); - EXPECT_EQ(recastMesh->getWater(), std::vector({ - Cell {1000, osg::Vec3f(100, 200, 300)} + EXPECT_EQ(recastMesh->getWater(), std::vector({ + CellWater {osg::Vec2i(1, 2), Water {1000, 300.0f}} })); } diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 6209ec9c2a..3667e946e0 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -264,7 +264,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; - EXPECT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); + EXPECT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) @@ -272,9 +272,9 @@ namespace TileCachedRecastMeshManager manager(mSettings); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; - ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); - for (int x = -6; x < 6; ++x) - for (int y = -6; y < 6; ++y) + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); + for (int x = -1; x < 12; ++x) + for (int y = -1; y < 12; ++y) ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); } @@ -286,7 +286,7 @@ namespace ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = std::numeric_limits::max(); - ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); @@ -303,10 +303,10 @@ namespace TileCachedRecastMeshManager manager(mSettings); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; - ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); const auto result = manager.removeWater(cellPosition); ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->mSize, cellSize); + EXPECT_EQ(result->mCellSize, cellSize); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) @@ -314,7 +314,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; - ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) @@ -329,7 +329,7 @@ namespace ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; - ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) @@ -344,10 +344,10 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); - ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); - for (int x = -6; x < 6; ++x) - for (int y = -6; y < 6; ++y) + for (int x = -1; x < 12; ++x) + for (int y = -1; y < 12; ++y) ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); } } diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 2e1ba45959..991f27cce1 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -32,16 +32,15 @@ namespace DetourNavigator return object; } - bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, - const osg::Vec3f& shift) + bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { - if (!mImpl.addWater(cellPosition, cellSize, shift)) + if (!mImpl.addWater(cellPosition, cellSize, level)) return false; mOutdatedCache = true; return true; } - std::optional CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) + std::optional CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { const auto water = mImpl.removeWater(cellPosition); if (water) diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 740e0b73f5..4d34a1b040 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -23,9 +23,9 @@ namespace DetourNavigator std::optional removeObject(const ObjectId id); - bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift); + bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); - std::optional removeWater(const osg::Vec2i& cellPosition); + std::optional removeWater(const osg::Vec2i& cellPosition); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, const HeightfieldShape& shape); diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index cac4be0fdd..f91a81e6d7 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -36,29 +36,28 @@ namespace float mHeight; }; - Rectangle getSwimRectangle(const Cell& water, const Settings& settings, - const osg::Vec3f& agentHalfExtents) + Rectangle getSwimRectangle(const CellWater& water, const Settings& settings, const osg::Vec3f& agentHalfExtents) { - if (water.mSize == std::numeric_limits::max()) + if (water.mWater.mCellSize == std::numeric_limits::max()) { return Rectangle { TileBounds { osg::Vec2f(-std::numeric_limits::max(), -std::numeric_limits::max()), osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max()) }, - toNavMeshCoordinates(settings, getSwimLevel(settings, water.mShift.z(), agentHalfExtents.z())) + toNavMeshCoordinates(settings, getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z())) }; } else { - const osg::Vec2f shift(water.mShift.x(), water.mShift.y()); - const float halfCellSize = water.mSize / 2.0f; + const osg::Vec2f shift = getWaterShift2d(water.mCellPosition, water.mWater.mCellSize); + const float halfCellSize = water.mWater.mCellSize / 2.0f; return Rectangle { TileBounds{ toNavMeshCoordinates(settings, shift + osg::Vec2f(-halfCellSize, -halfCellSize)), toNavMeshCoordinates(settings, shift + osg::Vec2f(halfCellSize, halfCellSize)) }, - toNavMeshCoordinates(settings, getSwimLevel(settings, water.mShift.z(), agentHalfExtents.z())) + toNavMeshCoordinates(settings, getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z())) }; } } @@ -241,12 +240,12 @@ namespace ); } - bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector& cells, + bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector& water, const Settings& settings, const rcConfig& config, rcHeightfield& solid) { - for (const Cell& cell : cells) + for (const CellWater& cellWater : water) { - const Rectangle rectangle = getSwimRectangle(cell, settings, agentHalfExtents); + const Rectangle rectangle = getSwimRectangle(cellWater, settings, agentHalfExtents); if (!rasterizeTriangles(context, rectangle, config, AreaType_water, solid)) return false; } @@ -404,9 +403,9 @@ namespace maxZ = std::max(maxZ, vertices[i + 2]); } - for (const Cell& water : recastMesh.getWater()) + for (const CellWater& water : recastMesh.getWater()) { - const float swimLevel = getSwimLevel(settings, water.mShift.z(), agentHalfExtents.z()); + const float swimLevel = getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z()); minZ = std::min(minZ, swimLevel); maxZ = std::max(maxZ, swimLevel); } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 5edc12ed84..9243aaf382 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -122,7 +122,7 @@ namespace DetourNavigator * at least single object is added to the scene, false if there is already water for given cell or there is no * any other objects. */ - virtual bool addWater(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift) = 0; + virtual bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level) = 0; /** * @brief removeWater to make it no more available at the scene. diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 44b42b22c2..333d018b28 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -97,9 +97,9 @@ namespace DetourNavigator return result; } - bool NavigatorImpl::addWater(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift) + bool NavigatorImpl::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { - return mNavMeshManager.addWater(cellPosition, cellSize, shift); + return mNavMeshManager.addWater(cellPosition, cellSize, level); } bool NavigatorImpl::removeWater(const osg::Vec2i& cellPosition) diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 8ed16b9a5b..8e3dd52982 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -31,7 +31,7 @@ namespace DetourNavigator bool removeObject(const ObjectId id) override; - bool addWater(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift) override; + bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level) override; bool removeWater(const osg::Vec2i& cellPosition) override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 758519769b..c66207bf18 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -44,7 +44,7 @@ namespace DetourNavigator return false; } - bool addWater(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const osg::Vec3f& /*shift*/) override + bool addWater(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, float /*level*/) override { return false; } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 362c6938c0..809d92d83c 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -73,10 +73,11 @@ namespace DetourNavigator return true; } - bool NavMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift) + bool NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { - if (!mRecastMeshManager.addWater(cellPosition, cellSize, shift)) + if (!mRecastMeshManager.addWater(cellPosition, cellSize, level)) return false; + const osg::Vec3f shift = getWaterShift3d(cellPosition, cellSize, level); addChangedTiles(cellSize, shift, ChangeType::add); return true; } @@ -86,7 +87,8 @@ namespace DetourNavigator const auto water = mRecastMeshManager.removeWater(cellPosition); if (!water) return false; - addChangedTiles(water->mSize, water->mShift, ChangeType::remove); + const osg::Vec3f shift = getWaterShift3d(cellPosition, water->mCellSize, water->mLevel); + addChangedTiles(water->mCellSize, shift, ChangeType::remove); return true; } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index b1496dc817..4a8731bb05 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -34,7 +34,7 @@ namespace DetourNavigator void addAgent(const osg::Vec3f& agentHalfExtents); - bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift); + bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); bool removeWater(const osg::Vec2i& cellPosition); diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 06d6c69e9b..fdafa0c6d6 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -23,7 +23,7 @@ namespace DetourNavigator struct RecastMeshData { Mesh mMesh; - std::vector mWater; + std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; }; diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index 258a700b0e..93a0e171a1 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -18,7 +18,7 @@ namespace DetourNavigator mAreaTypes = std::move(areaTypes); } - RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, + RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, std::vector heightfields, std::vector flatHeightfields) : mGeneration(generation) , mRevision(revision) diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 928b282d19..8a06131c93 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -53,6 +54,40 @@ namespace DetourNavigator osg::Vec3f mShift; }; + struct Water + { + int mCellSize; + float mLevel; + }; + + inline bool operator<(const Water& lhs, const Water& rhs) noexcept + { + const auto tie = [] (const Water& v) { return std::tie(v.mCellSize, v.mLevel); }; + return tie(lhs) < tie(rhs); + } + + struct CellWater + { + osg::Vec2i mCellPosition; + Water mWater; + }; + + inline bool operator<(const CellWater& lhs, const CellWater& rhs) noexcept + { + const auto tie = [] (const CellWater& v) { return std::tie(v.mCellPosition, v.mWater); }; + return tie(lhs) < tie(rhs); + } + + inline osg::Vec2f getWaterShift2d(const osg::Vec2i& cellPosition, int cellSize) + { + return osg::Vec2f((cellPosition.x() + 0.5f) * cellSize, (cellPosition.y() + 0.5f) * cellSize); + } + + inline osg::Vec3f getWaterShift3d(const osg::Vec2i& cellPosition, int cellSize, float level) + { + return osg::Vec3f(getWaterShift2d(cellPosition, cellSize), level); + } + struct Heightfield { TileBounds mBounds; @@ -88,7 +123,7 @@ namespace DetourNavigator class RecastMesh { public: - RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, + RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, std::vector heightfields, std::vector flatHeightfields); std::size_t getGeneration() const @@ -103,7 +138,7 @@ namespace DetourNavigator const Mesh& getMesh() const noexcept { return mMesh; } - const std::vector& getWater() const + const std::vector& getWater() const { return mWater; } @@ -122,13 +157,13 @@ namespace DetourNavigator std::size_t mGeneration; std::size_t mRevision; Mesh mMesh; - std::vector mWater; + std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; friend inline std::size_t getSize(const RecastMesh& value) noexcept { - return getSize(value.mMesh) + value.mWater.size() * sizeof(Cell) + return getSize(value.mMesh) + value.mWater.size() * sizeof(CellWater) + value.mHeightfields.size() * sizeof(Heightfield) + std::accumulate(value.mHeightfields.begin(), value.mHeightfields.end(), std::size_t {0}, [] (std::size_t r, const Heightfield& v) { return r + v.mHeights.size() * sizeof(float); }) diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 8a0d30616d..dfa1893310 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -200,9 +200,9 @@ namespace DetourNavigator } } - void RecastMeshBuilder::addWater(const int cellSize, const osg::Vec3f& shift) + void RecastMeshBuilder::addWater(const osg::Vec2i& cellPosition, const Water& water) { - mWater.push_back(Cell {cellSize, shift}); + mWater.push_back(CellWater {cellPosition, water}); } void RecastMeshBuilder::addHeightfield(int cellSize, const osg::Vec3f& shift, float height) diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 84e9716569..2d68a7e89f 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -48,7 +48,7 @@ namespace DetourNavigator void addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType); - void addWater(const int mCellSize, const osg::Vec3f& shift); + void addWater(const osg::Vec2i& cellPosition, const Water& water); void addHeightfield(int cellSize, const osg::Vec3f& shift, float height); @@ -60,7 +60,7 @@ namespace DetourNavigator private: const TileBounds mBounds; std::vector mTriangles; - std::vector mWater; + std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 6dcc2b2a42..48a64d70aa 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -73,23 +73,23 @@ namespace DetourNavigator return result; } - bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift) + bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { const std::lock_guard lock(mMutex); - if (!mWater.emplace(cellPosition, Cell {cellSize, shift}).second) + if (!mWater.emplace(cellPosition, Water {cellSize, level}).second) return false; ++mRevision; return true; } - std::optional RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) + std::optional RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { const std::lock_guard lock(mMutex); const auto water = mWater.find(cellPosition); if (water == mWater.end()) return std::nullopt; ++mRevision; - const Cell result = water->second; + Water result = water->second; mWater.erase(water); return result; } @@ -130,7 +130,7 @@ namespace DetourNavigator { const std::lock_guard lock(mMutex); for (const auto& [k, v] : mWater) - builder.addWater(v.mSize, v.mShift); + builder.addWater(k, v); for (const auto& [cellPosition, v] : mHeightfields) std::visit(AddHeightfield {v.mCell, builder}, v.mShape); objects.reserve(mObjects.size()); diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index d93745c71e..576314191f 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -41,9 +41,9 @@ namespace DetourNavigator std::optional removeObject(const ObjectId id); - bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift); + bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); - std::optional removeWater(const osg::Vec2i& cellPosition); + std::optional removeWater(const osg::Vec2i& cellPosition); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, const HeightfieldShape& shape); @@ -76,7 +76,7 @@ namespace DetourNavigator mutable std::mutex mMutex; std::size_t mRevision = 0; std::map mObjects; - std::map mWater; + std::map mWater; std::map mHeightfields; std::optional mLastNavMeshReportedChange; std::optional mLastNavMeshReport; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index f82ce9de83..347d4b7b30 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -54,8 +54,7 @@ namespace DetourNavigator return result; } - bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, - const osg::Vec3f& shift) + bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { auto& tilesPositions = mWaterTilesPositions[cellPosition]; @@ -66,7 +65,7 @@ namespace DetourNavigator const auto tiles = mTiles.lock(); for (auto& tile : *tiles) { - if (tile.second->addWater(cellPosition, cellSize, shift)) + if (tile.second->addWater(cellPosition, cellSize, level)) { tilesPositions.push_back(tile.first); result = true; @@ -75,6 +74,7 @@ namespace DetourNavigator } else { + const osg::Vec3f shift = getWaterShift3d(cellPosition, cellSize, level); getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) { const auto tiles = mTiles.lock(); @@ -85,7 +85,7 @@ namespace DetourNavigator tile = tiles->emplace(tilePosition, std::make_shared(tileBounds, mTilesGeneration)).first; } - if (tile->second->addWater(cellPosition, cellSize, shift)) + if (tile->second->addWater(cellPosition, cellSize, level)) { tilesPositions.push_back(tilePosition); result = true; @@ -99,12 +99,12 @@ namespace DetourNavigator return result; } - std::optional TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) + std::optional TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { const auto object = mWaterTilesPositions.find(cellPosition); if (object == mWaterTilesPositions.end()) return std::nullopt; - std::optional result; + std::optional result; for (const auto& tilePosition : object->second) { const auto tiles = mTiles.lock(); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 6b930c411f..606d08afdf 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -76,9 +76,9 @@ namespace DetourNavigator std::optional removeObject(const ObjectId id); - bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift); + bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); - std::optional removeWater(const osg::Vec2i& cellPosition); + std::optional removeWater(const osg::Vec2i& cellPosition); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, const HeightfieldShape& shape); From 7dcb219ecfa1adadec6a8d0d07fca491f18453c2 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Nov 2021 03:19:41 +0100 Subject: [PATCH 1813/2859] Add raw heightfield data to navigator --- .../detournavigator/navmeshtilescache.cpp | 31 ++--- apps/openmw/mwworld/scene.cpp | 2 +- .../detournavigator/navigator.cpp | 103 +++++++---------- .../detournavigator/recastmeshbuilder.cpp | 106 +++++++++++++----- .../cachedrecastmeshmanager.cpp | 12 +- .../cachedrecastmeshmanager.hpp | 5 +- .../detournavigator/gettilespositions.hpp | 4 +- .../detournavigator/heightfieldshape.hpp | 19 ++++ components/detournavigator/makenavmesh.cpp | 20 ++-- components/detournavigator/navigator.hpp | 3 +- components/detournavigator/navigatorimpl.cpp | 5 +- components/detournavigator/navigatorimpl.hpp | 3 +- components/detournavigator/navigatorstub.hpp | 3 +- components/detournavigator/navmeshmanager.cpp | 16 +-- components/detournavigator/navmeshmanager.hpp | 5 +- components/detournavigator/recastmesh.hpp | 28 ++--- .../detournavigator/recastmeshbuilder.cpp | 45 +++++--- .../detournavigator/recastmeshbuilder.hpp | 4 +- .../detournavigator/recastmeshmanager.cpp | 18 +-- .../detournavigator/recastmeshmanager.hpp | 19 ++-- components/detournavigator/tilebounds.hpp | 9 ++ .../tilecachedrecastmeshmanager.cpp | 11 +- .../tilecachedrecastmeshmanager.hpp | 5 +- 23 files changed, 266 insertions(+), 210 deletions(-) diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 7acbcc3a4f..9d41b62597 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -25,18 +25,10 @@ namespace }; template - TilePosition generateTilePosition(int max, Random& random) + osg::Vec2i generateVec2i(int max, Random& random) { std::uniform_int_distribution distribution(0, max); - return TilePosition(distribution(random), distribution(random)); - } - - template - TileBounds generateTileBounds(Random& random) - { - std::uniform_real_distribution distribution(0.0, 1.0); - const osg::Vec2f min(distribution(random), distribution(random)); - return TileBounds {min, min + osg::Vec2f(1.0, 1.0)}; + return osg::Vec2i(distribution(random), distribution(random)); } template @@ -89,11 +81,9 @@ namespace template void generateWater(OutputIterator out, std::size_t count, Random& random) { - std::uniform_real_distribution floatDistribution(0.0, 1.0); - std::uniform_int_distribution intDistribution(-10, 10); + std::uniform_real_distribution distribution(0.0, 1.0); std::generate_n(out, count, [&] { - const osg::Vec2i cellPosition(intDistribution(random), intDistribution(random)); - return CellWater {cellPosition, Water {8196, floatDistribution(random)}}; + return CellWater {generateVec2i(1000, random), Water {ESM::Land::REAL_SIZE, distribution(random)}}; }); } @@ -118,16 +108,18 @@ namespace { std::uniform_real_distribution distribution(0.0, 1.0); Heightfield result; - result.mBounds = generateTileBounds(random); + result.mCellPosition = generateVec2i(1000, random); + result.mCellSize = ESM::Land::REAL_SIZE; result.mMinHeight = distribution(random); result.mMaxHeight = result.mMinHeight + 1.0; - result.mShift = osg::Vec3f(distribution(random), distribution(random), distribution(random)); - result.mScale = distribution(random); result.mLength = static_cast(ESM::Land::LAND_SIZE); std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&] { return distribution(random); }); + result.mOriginalSize = ESM::Land::LAND_SIZE; + result.mMinX = 0; + result.mMinY = 0; return result; } @@ -136,7 +128,8 @@ namespace { std::uniform_real_distribution distribution(0.0, 1.0); FlatHeightfield result; - result.mBounds = generateTileBounds(random); + result.mCellPosition = generateVec2i(1000, random); + result.mCellSize = ESM::Land::REAL_SIZE; result.mHeight = distribution(random); return result; } @@ -145,7 +138,7 @@ namespace Key generateKey(std::size_t triangles, Random& random) { const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random); - const TilePosition tilePosition = generateTilePosition(10000, random); + const TilePosition tilePosition = generateVec2i(10000, random); const std::size_t generation = std::uniform_int_distribution(0, 100)(random); const std::size_t revision = std::uniform_int_distribution(0, 10000)(random); Mesh mesh = generateMesh(triangles, random); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index cc2a692e2b..15b3477cb8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -404,7 +404,7 @@ namespace MWWorld return heights; } } (); - mNavigator.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, shift, shape); + mNavigator.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, shape); } } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index a8b42f0db9..4e2b7758ff 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -47,7 +47,6 @@ namespace Loading::Listener mListener; const osg::Vec2i mCellPosition {0, 0}; const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); - const osg::Vec3f mShift {0, 0, 0}; const float mEndTolerance = 0; const btTransform mTransform {btMatrix3x3::getIdentity(), btVector3(256, 256, 0)}; @@ -137,9 +136,9 @@ namespace osg::ref_ptr mInstance; }; - osg::Vec3f getHeightfieldShift(const osg::Vec2i& cellPosition, int cellSize, float minHeight, float maxHeight) + btVector3 getHeightfieldShift(const osg::Vec2i& cellPosition, int cellSize, float minHeight, float maxHeight) { - return Misc::Convert::toOsg(BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.x(), cellSize, minHeight, maxHeight)); + return BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.x(), cellSize, minHeight, maxHeight); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) @@ -176,10 +175,9 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); @@ -223,13 +221,12 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -308,13 +305,12 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -453,7 +449,6 @@ namespace }}; const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(heightfieldData1); const int cellSize1 = mHeightfieldTileSize * (surface1.mSize - 1); - const osg::Vec3f shift1 = getHeightfieldShift(mCellPosition, cellSize1, surface1.mMinHeight, surface1.mMaxHeight); const std::array heightfieldData2 {{ -25, -25, -25, -25, -25, @@ -464,11 +459,10 @@ namespace }}; const HeightfieldSurface surface2 = makeSquareHeightfieldSurface(heightfieldData2); const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1); - const osg::Vec3f shift2 = getHeightfieldShift(mCellPosition, cellSize2, surface2.mMinHeight, surface2.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - EXPECT_TRUE(mNavigator->addHeightfield(mCellPosition, cellSize1, shift1, surface1)); - EXPECT_FALSE(mNavigator->addHeightfield(mCellPosition, cellSize2, shift2, surface2)); + EXPECT_TRUE(mNavigator->addHeightfield(mCellPosition, cellSize1, surface1)); + EXPECT_FALSE(mNavigator->addHeightfield(mCellPosition, cellSize2, surface2)); } TEST_F(DetourNavigatorNavigatorTest, path_should_be_around_avoid_shape) @@ -545,11 +539,10 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(mCellPosition, cellSize, 300); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -594,11 +587,10 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(mCellPosition, cellSize, -25); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -641,10 +633,9 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addWater(mCellPosition, std::numeric_limits::max(), -25); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -688,11 +679,10 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(mCellPosition, cellSize, -25); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -787,10 +777,9 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -798,7 +787,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -843,10 +832,9 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -876,14 +864,14 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); + const btVector3 shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); std::vector> boxes; std::generate_n(std::back_inserter(boxes), 100, [] { return std::make_unique(btVector3(20, 20, 100)); }); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); for (std::size_t i = 0; i < boxes.size(); ++i) { @@ -981,10 +969,9 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1007,14 +994,13 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); const btVector3 oscillatingBoxShapePosition(288, 288, 400); CollisionShapeInstance borderBox(std::make_unique(btVector3(50, 50, 50))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations @@ -1046,10 +1032,9 @@ namespace { const HeightfieldPlane plane {100}; const int cellSize = mHeightfieldTileSize * 4; - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, plane.mHeight, plane.mHeight); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, plane); + mNavigator->addHeightfield(mCellPosition, cellSize, plane); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); @@ -1057,28 +1042,28 @@ namespace Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(56.66666412353515625, 460, 203.9999847412109375), - Vec3fEq(76.70135498046875, 439.965301513671875, 203.9999847412109375), - Vec3fEq(96.73604583740234375, 419.93060302734375, 203.9999847412109375), - Vec3fEq(116.770751953125, 399.89593505859375, 203.9999847412109375), - Vec3fEq(136.8054351806640625, 379.861236572265625, 203.9999847412109375), - Vec3fEq(156.840118408203125, 359.826568603515625, 203.9999847412109375), - Vec3fEq(176.8748016357421875, 339.7918701171875, 203.9999847412109375), - Vec3fEq(196.90948486328125, 319.757171630859375, 203.9999847412109375), - Vec3fEq(216.944183349609375, 299.722503662109375, 203.9999847412109375), - Vec3fEq(236.9788665771484375, 279.68780517578125, 203.9999847412109375), - Vec3fEq(257.0135498046875, 259.65313720703125, 203.9999847412109375), - Vec3fEq(277.048248291015625, 239.618438720703125, 203.9999847412109375), - Vec3fEq(297.082916259765625, 219.583740234375, 203.9999847412109375), - Vec3fEq(317.11761474609375, 199.549041748046875, 203.9999847412109375), - Vec3fEq(337.15228271484375, 179.5143585205078125, 203.9999847412109375), - Vec3fEq(357.186981201171875, 159.47967529296875, 203.9999847412109375), - Vec3fEq(377.221649169921875, 139.4449920654296875, 203.9999847412109375), - Vec3fEq(397.25634765625, 119.41030120849609375, 203.9999847412109375), - Vec3fEq(417.291046142578125, 99.3756103515625, 203.9999847412109375), - Vec3fEq(437.325714111328125, 79.340911865234375, 203.9999847412109375), - Vec3fEq(457.360443115234375, 59.3062286376953125, 203.9999847412109375), - Vec3fEq(460, 56.66666412353515625, 203.9999847412109375) + Vec3fEq(56.66666412353515625, 460, 101.99999237060546875), + Vec3fEq(76.70135498046875, 439.965301513671875, 101.99999237060546875), + Vec3fEq(96.73604583740234375, 419.93060302734375, 101.99999237060546875), + Vec3fEq(116.770751953125, 399.89593505859375, 101.99999237060546875), + Vec3fEq(136.8054351806640625, 379.861236572265625, 101.99999237060546875), + Vec3fEq(156.840118408203125, 359.826568603515625, 101.99999237060546875), + Vec3fEq(176.8748016357421875, 339.7918701171875, 101.99999237060546875), + Vec3fEq(196.90948486328125, 319.757171630859375, 101.99999237060546875), + Vec3fEq(216.944183349609375, 299.722503662109375, 101.99999237060546875), + Vec3fEq(236.9788665771484375, 279.68780517578125, 101.99999237060546875), + Vec3fEq(257.0135498046875, 259.65313720703125, 101.99999237060546875), + Vec3fEq(277.048248291015625, 239.618438720703125, 101.99999237060546875), + Vec3fEq(297.082916259765625, 219.583740234375, 101.99999237060546875), + Vec3fEq(317.11761474609375, 199.549041748046875, 101.99999237060546875), + Vec3fEq(337.15228271484375, 179.5143585205078125, 101.99999237060546875), + Vec3fEq(357.186981201171875, 159.47967529296875, 101.99999237060546875), + Vec3fEq(377.221649169921875, 139.4449920654296875, 101.99999237060546875), + Vec3fEq(397.25634765625, 119.41030120849609375, 101.99999237060546875), + Vec3fEq(417.291046142578125, 99.3756103515625, 101.99999237060546875), + Vec3fEq(437.325714111328125, 79.340911865234375, 101.99999237060546875), + Vec3fEq(457.360443115234375, 59.3062286376953125, 101.99999237060546875), + Vec3fEq(460, 56.66666412353515625, 101.99999237060546875) )) << mPath; } @@ -1093,14 +1078,13 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), new btBoxShape(btVector3(200, 200, 1000))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1133,14 +1117,13 @@ namespace }}; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); - const osg::Vec3f shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), new btBoxShape(btVector3(100, 100, 1000))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addHeightfield(mCellPosition, cellSize, shift, surface); + mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index 67a1206e46..ad28bf4053 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -23,11 +23,6 @@ namespace DetourNavigator { - static inline bool operator ==(const Cell& lhs, const Cell& rhs) - { - return lhs.mSize == rhs.mSize && lhs.mShift == rhs.mShift; - } - static inline bool operator ==(const Water& lhs, const Water& rhs) { const auto tie = [] (const Water& v) { return std::tie(v.mCellSize, v.mLevel); }; @@ -47,7 +42,11 @@ namespace DetourNavigator static inline bool operator==(const FlatHeightfield& lhs, const FlatHeightfield& rhs) { - return std::tie(lhs.mBounds, lhs.mHeight) == std::tie(rhs.mBounds, rhs.mHeight); + const auto tie = [] (const FlatHeightfield& v) + { + return std::tie(v.mCellPosition, v.mCellSize, v.mHeight); + }; + return tie(lhs) == tie(rhs); } static inline std::ostream& operator<<(std::ostream& s, const Water& v) @@ -62,21 +61,21 @@ namespace DetourNavigator static inline std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v) { - return s << "FlatHeightfield {" << v.mBounds << ", " << v.mHeight << "}"; + return s << "FlatHeightfield {" << v.mCellPosition << ", " << v.mCellSize << ", " << v.mHeight << "}"; } static inline std::ostream& operator<<(std::ostream& s, const Heightfield& v) { - s << "Heightfield {.mBounds=" << v.mBounds - << ", .mLength=" << int(v.mLength) + s << "Heightfield {.mCellPosition=" << v.mCellPosition + << ", .mCellSize=" << v.mCellSize + << ", .mLength=" << static_cast(v.mLength) << ", .mMinHeight=" << v.mMinHeight << ", .mMaxHeight=" << v.mMaxHeight - << ", .mShift=" << v.mShift - << ", .mScale=" << v.mScale << ", .mHeights={"; for (float h : v.mHeights) s << h << ", "; - return s << "}}"; + s << "}"; + return s << ", .mOriginalSize=" << v.mOriginalSize << "}"; } } @@ -486,62 +485,111 @@ namespace TEST_F(DetourNavigatorRecastMeshBuilderTest, add_flat_heightfield_should_add_intersection) { - mBounds.mMin = osg::Vec2f(0, 0); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 1000; + const float height = 10; + mBounds.mMin = osg::Vec2f(100, 100); RecastMeshBuilder builder(mBounds); - builder.addHeightfield(1000, osg::Vec3f(1, 2, 3), 10); + builder.addHeightfield(cellPosition, cellSize, height); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getFlatHeightfields(), std::vector({ - FlatHeightfield {TileBounds {osg::Vec2f(0, 0), osg::Vec2f(501, 502)}, 13}, + FlatHeightfield {cellPosition, cellSize, height}, })); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_inside_tile) { - constexpr std::array heights {{ + constexpr std::size_t size = 3; + constexpr std::array heights {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, }}; + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 1000; + const float minHeight = 0; + const float maxHeight = 8; RecastMeshBuilder builder(mBounds); - builder.addHeightfield(1000, osg::Vec3f(1, 2, 3), heights.data(), 3, 0, 8); + builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); Heightfield expected; - expected.mBounds = TileBounds {osg::Vec2f(-499, -498), osg::Vec2f(501, 502)}; - expected.mLength = 3; - expected.mMinHeight = 0; - expected.mMaxHeight = 8; - expected.mShift = osg::Vec3f(-499, -498, 3); - expected.mScale = 500; + expected.mCellPosition = cellPosition; + expected.mCellSize = cellSize; + expected.mLength = size; + expected.mMinHeight = minHeight; + expected.mMaxHeight = maxHeight; + expected.mHeights = { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + }; + expected.mOriginalSize = 3; + expected.mMinX = 0; + expected.mMinY = 0; + EXPECT_EQ(recastMesh->getHeightfields(), std::vector({expected})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_to_shifted_cell_inside_tile) + { + constexpr std::size_t size = 3; + constexpr std::array heights {{ + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + }}; + const osg::Vec2i cellPosition(1, 2); + const int cellSize = 1000; + const float minHeight = 0; + const float maxHeight = 8; + RecastMeshBuilder builder(maxCellTileBounds(cellPosition, cellSize)); + builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); + Heightfield expected; + expected.mCellPosition = cellPosition; + expected.mCellSize = cellSize; + expected.mLength = size; + expected.mMinHeight = minHeight; + expected.mMaxHeight = maxHeight; expected.mHeights = { 0, 1, 2, 3, 4, 5, 6, 7, 8, }; + expected.mOriginalSize = 3; + expected.mMinX = 0; + expected.mMinY = 0; EXPECT_EQ(recastMesh->getHeightfields(), std::vector({expected})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_should_add_intersection) { - constexpr std::array heights {{ + constexpr std::size_t size = 3; + constexpr std::array heights {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, }}; - mBounds.mMin = osg::Vec2f(250, 250); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 1000; + const float minHeight = 0; + const float maxHeight = 8; + mBounds.mMin = osg::Vec2f(750, 750); RecastMeshBuilder builder(mBounds); - builder.addHeightfield(1000, osg::Vec3f(-1, -2, 3), heights.data(), 3, 0, 8); + builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); Heightfield expected; - expected.mBounds = TileBounds {osg::Vec2f(250, 250), osg::Vec2f(499, 498)}; + expected.mCellPosition = cellPosition; + expected.mCellSize = cellSize; expected.mLength = 2; expected.mMinHeight = 0; expected.mMaxHeight = 8; - expected.mShift = osg::Vec3f(-1, -2, 3); - expected.mScale = 500; expected.mHeights = { 4, 5, 7, 8, }; + expected.mOriginalSize = 3; + expected.mMinX = 1; + expected.mMinY = 1; EXPECT_EQ(recastMesh->getHeightfields(), std::vector({expected})); } } diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 991f27cce1..a3c7f4b172 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -49,20 +49,20 @@ namespace DetourNavigator } bool CachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, - const osg::Vec3f& shift, const HeightfieldShape& shape) + const HeightfieldShape& shape) { - if (!mImpl.addHeightfield(cellPosition, cellSize, shift, shape)) + if (!mImpl.addHeightfield(cellPosition, cellSize, shape)) return false; mOutdatedCache = true; return true; } - std::optional CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) + std::optional CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { - const auto cell = mImpl.removeHeightfield(cellPosition); - if (cell) + const auto heightfield = mImpl.removeHeightfield(cellPosition); + if (heightfield) mOutdatedCache = true; - return cell; + return heightfield; } std::shared_ptr CachedRecastMeshManager::getMesh() diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 4d34a1b040..eb18519891 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -27,10 +27,9 @@ namespace DetourNavigator std::optional removeWater(const osg::Vec2i& cellPosition); - bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, - const HeightfieldShape& shape); + bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); - std::optional removeHeightfield(const osg::Vec2i& cellPosition); + std::optional removeHeightfield(const osg::Vec2i& cellPosition); std::shared_ptr getMesh(); diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index e8ba8beba9..abfce06464 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -50,13 +50,13 @@ namespace DetourNavigator } template - void getTilesPositions(const int cellSize, const osg::Vec3f& shift, + void getTilesPositions(const int cellSize, const btVector3& shift, const Settings& settings, Callback&& callback) { using Misc::Convert::toOsg; const auto halfCellSize = cellSize / 2; - const btTransform transform(btMatrix3x3::getIdentity(), Misc::Convert::toBullet(shift)); + const btTransform transform(btMatrix3x3::getIdentity(), shift); auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); auto aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); diff --git a/components/detournavigator/heightfieldshape.hpp b/components/detournavigator/heightfieldshape.hpp index 48a273725b..06770e9b3d 100644 --- a/components/detournavigator/heightfieldshape.hpp +++ b/components/detournavigator/heightfieldshape.hpp @@ -1,6 +1,10 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_HEIGHFIELDSHAPE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_HEIGHFIELDSHAPE_H +#include + +#include + #include #include @@ -20,6 +24,21 @@ namespace DetourNavigator }; using HeightfieldShape = std::variant; + + inline btVector3 getHeightfieldShift(const HeightfieldPlane& v, const osg::Vec2i& cellPosition, int cellSize) + { + return BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, v.mHeight, v.mHeight); + } + + inline btVector3 getHeightfieldShift(const HeightfieldSurface& v, const osg::Vec2i& cellPosition, int cellSize) + { + return BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, v.mMinHeight, v.mMaxHeight); + } + + inline btVector3 getHeightfieldShift(const HeightfieldShape& v, const osg::Vec2i& cellPosition, int cellSize) + { + return std::visit([&] (const auto& w) { return getHeightfieldShift(w, cellPosition, cellSize); }, v); + } } #endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index f91a81e6d7..7d478b9c1b 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -252,14 +252,17 @@ namespace return true; } - bool rasterizeTriangles(rcContext& context, const std::vector& heightfields, + bool rasterizeTriangles(rcContext& context, const TileBounds& tileBounds, const std::vector& heightfields, const Settings& settings, const rcConfig& config, rcHeightfield& solid) { for (const FlatHeightfield& heightfield : heightfields) { - const Rectangle rectangle {heightfield.mBounds, toNavMeshCoordinates(settings, heightfield.mHeight)}; - if (!rasterizeTriangles(context, rectangle, config, AreaType_ground, solid)) - return false; + if (auto intersection = getIntersection(tileBounds, maxCellTileBounds(heightfield.mCellPosition, heightfield.mCellSize))) + { + const Rectangle rectangle {*intersection, toNavMeshCoordinates(settings, heightfield.mHeight)}; + if (!rasterizeTriangles(context, rectangle, config, AreaType_ground, solid)) + return false; + } } return true; } @@ -278,13 +281,14 @@ namespace return true; } - bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const rcConfig& config, const Settings& settings, rcHeightfield& solid) + bool rasterizeTriangles(rcContext& context, const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, + const RecastMesh& recastMesh, const rcConfig& config, const Settings& settings, rcHeightfield& solid) { return rasterizeTriangles(context, recastMesh.getMesh(), settings, config, solid) && rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, config, solid) && rasterizeTriangles(context, recastMesh.getHeightfields(), settings, config, solid) - && rasterizeTriangles(context, recastMesh.getFlatHeightfields(), settings, config, solid); + && rasterizeTriangles(context, makeRealTileBoundsWithBorder(settings, tilePosition), + recastMesh.getFlatHeightfields(), settings, config, solid); } void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb, @@ -444,7 +448,7 @@ namespace DetourNavigator rcHeightfield solid; createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); - if (!rasterizeTriangles(context, agentHalfExtents, recastMesh, config, settings, solid)) + if (!rasterizeTriangles(context, tilePosition, agentHalfExtents, recastMesh, config, settings, solid)) return nullptr; rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 9243aaf382..6070a19fa8 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -131,8 +131,7 @@ namespace DetourNavigator */ virtual bool removeWater(const osg::Vec2i& cellPosition) = 0; - virtual bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, - const HeightfieldShape& shape) = 0; + virtual bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) = 0; virtual bool removeHeightfield(const osg::Vec2i& cellPosition) = 0; diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 333d018b28..f209a24c2c 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -107,10 +107,9 @@ namespace DetourNavigator return mNavMeshManager.removeWater(cellPosition); } - bool NavigatorImpl::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, - const HeightfieldShape& shape) + bool NavigatorImpl::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { - return mNavMeshManager.addHeightfield(cellPosition, cellSize, shift, shape); + return mNavMeshManager.addHeightfield(cellPosition, cellSize, shape); } bool NavigatorImpl::removeHeightfield(const osg::Vec2i& cellPosition) diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 8e3dd52982..2c8474786f 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -35,8 +35,7 @@ namespace DetourNavigator bool removeWater(const osg::Vec2i& cellPosition) override; - bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, - const HeightfieldShape& shape) override; + bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) override; bool removeHeightfield(const osg::Vec2i& cellPosition) override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index c66207bf18..1a6096feef 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -54,8 +54,7 @@ namespace DetourNavigator return false; } - bool addHeightfield(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const osg::Vec3f& /*shift*/, - const HeightfieldShape& /*height*/) override + bool addHeightfield(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const HeightfieldShape& /*height*/) override { return false; } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 809d92d83c..fff7d8d7cc 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -8,6 +8,7 @@ #include "waitconditiontype.hpp" #include +#include #include @@ -77,7 +78,7 @@ namespace DetourNavigator { if (!mRecastMeshManager.addWater(cellPosition, cellSize, level)) return false; - const osg::Vec3f shift = getWaterShift3d(cellPosition, cellSize, level); + const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); addChangedTiles(cellSize, shift, ChangeType::add); return true; } @@ -87,16 +88,16 @@ namespace DetourNavigator const auto water = mRecastMeshManager.removeWater(cellPosition); if (!water) return false; - const osg::Vec3f shift = getWaterShift3d(cellPosition, water->mCellSize, water->mLevel); + const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, water->mCellSize, water->mLevel)); addChangedTiles(water->mCellSize, shift, ChangeType::remove); return true; } - bool NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, - const HeightfieldShape& shape) + bool NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { - if (!mRecastMeshManager.addHeightfield(cellPosition, cellSize, shift, shape)) + if (!mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape)) return false; + const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize); addChangedTiles(cellSize, shift, ChangeType::add); return true; } @@ -106,7 +107,8 @@ namespace DetourNavigator const auto heightfield = mRecastMeshManager.removeHeightfield(cellPosition); if (!heightfield) return false; - addChangedTiles(heightfield->mSize, heightfield->mShift, ChangeType::remove); + const btVector3 shift = getHeightfieldShift(heightfield->mShape, cellPosition, heightfield->mCellSize); + addChangedTiles(heightfield->mCellSize, shift, ChangeType::remove); return true; } @@ -253,7 +255,7 @@ namespace DetourNavigator [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } - void NavMeshManager::addChangedTiles(const int cellSize, const osg::Vec3f& shift, + void NavMeshManager::addChangedTiles(const int cellSize, const btVector3& shift, const ChangeType changeType) { if (cellSize == std::numeric_limits::max()) diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 4a8731bb05..b1926741c5 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -38,8 +38,7 @@ namespace DetourNavigator bool removeWater(const osg::Vec2i& cellPosition); - bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, - const HeightfieldShape& shape); + bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); bool removeHeightfield(const osg::Vec2i& cellPosition); @@ -74,7 +73,7 @@ namespace DetourNavigator void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType); - void addChangedTiles(const int cellSize, const osg::Vec3f& shift, const ChangeType changeType); + void addChangedTiles(const int cellSize, const btVector3& shift, const ChangeType changeType); void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 8a06131c93..3cfe9e1cab 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -48,12 +48,6 @@ namespace DetourNavigator } }; - struct Cell - { - int mSize; - osg::Vec3f mShift; - }; - struct Water { int mCellSize; @@ -90,18 +84,21 @@ namespace DetourNavigator struct Heightfield { - TileBounds mBounds; + osg::Vec2i mCellPosition; + int mCellSize; std::uint8_t mLength; float mMinHeight; float mMaxHeight; - osg::Vec3f mShift; - float mScale; std::vector mHeights; + std::size_t mOriginalSize; + std::uint8_t mMinX; + std::uint8_t mMinY; }; inline auto makeTuple(const Heightfield& v) noexcept { - return std::tie(v.mBounds, v.mLength, v.mMinHeight, v.mMaxHeight, v.mShift, v.mScale, v.mHeights); + return std::tie(v.mCellPosition, v.mCellSize, v.mLength, v.mMinHeight, v.mMaxHeight, + v.mHeights, v.mOriginalSize, v.mMinX, v.mMinY); } inline bool operator<(const Heightfield& lhs, const Heightfield& rhs) noexcept @@ -111,13 +108,15 @@ namespace DetourNavigator struct FlatHeightfield { - TileBounds mBounds; + osg::Vec2i mCellPosition; + int mCellSize; float mHeight; }; inline bool operator<(const FlatHeightfield& lhs, const FlatHeightfield& rhs) noexcept { - return std::tie(lhs.mBounds, lhs.mHeight) < std::tie(rhs.mBounds, rhs.mHeight); + const auto tie = [] (const FlatHeightfield& v) { return std::tie(v.mCellPosition, v.mCellSize, v.mHeight); }; + return tie(lhs) < tie(rhs); } class RecastMesh @@ -170,11 +169,6 @@ namespace DetourNavigator + value.mFlatHeightfields.size() * sizeof(FlatHeightfield); } }; - - inline bool operator<(const Cell& lhs, const Cell& rhs) noexcept - { - return std::tie(lhs.mSize, lhs.mShift) < std::tie(rhs.mSize, rhs.mShift); - } } #endif diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index dfa1893310..b54b927696 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -35,13 +36,9 @@ namespace DetourNavigator return result; } - TileBounds maxCellTileBounds(int size, const osg::Vec3f& shift) + float getHeightfieldScale(int cellSize, std::size_t dataSize) { - const float halfCellSize = static_cast(size) / 2; - return TileBounds { - osg::Vec2f(shift.x() - halfCellSize, shift.y() - halfCellSize), - osg::Vec2f(shift.x() + halfCellSize, shift.y() + halfCellSize) - }; + return static_cast(cellSize) / (dataSize - 1); } } @@ -108,7 +105,8 @@ namespace DetourNavigator static_cast(heightfield.mLength), heightfield.mHeights.data(), heightfield.mMinHeight, heightfield.mMaxHeight, upAxis, flipQuadEdges); #endif - shape.setLocalScaling(btVector3(heightfield.mScale, heightfield.mScale, 1)); + const float scale = getHeightfieldScale(heightfield.mCellSize, heightfield.mOriginalSize); + shape.setLocalScaling(btVector3(scale, scale, 1)); btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); @@ -118,8 +116,16 @@ namespace DetourNavigator triangles.emplace_back(makeRecastMeshTriangle(vertices, AreaType_ground)); }); shape.processAllTriangles(&callback, aabbMin, aabbMax); - const osg::Vec2f shift = (osg::Vec2f(aabbMax.x(), aabbMax.y()) - osg::Vec2f(aabbMin.x(), aabbMin.y())) * 0.5; - return makeMesh(std::move(triangles), heightfield.mShift + osg::Vec3f(shift.x(), shift.y(), 0)); + const osg::Vec2f aabbShift = (osg::Vec2f(aabbMax.x(), aabbMax.y()) - osg::Vec2f(aabbMin.x(), aabbMin.y())) * 0.5; + const osg::Vec2f tileShift = osg::Vec2f(heightfield.mMinX, heightfield.mMinY) * scale; + const osg::Vec2f localShift = aabbShift + tileShift; + const float cellSize = static_cast(heightfield.mCellSize); + const osg::Vec3f cellShift( + heightfield.mCellPosition.x() * cellSize, + heightfield.mCellPosition.y() * cellSize, + (heightfield.mMinHeight + heightfield.mMaxHeight) * 0.5f + ); + return makeMesh(std::move(triangles), cellShift + osg::Vec3f(localShift.x(), localShift.y(), 0)); } RecastMeshBuilder::RecastMeshBuilder(const TileBounds& bounds) noexcept @@ -205,19 +211,20 @@ namespace DetourNavigator mWater.push_back(CellWater {cellPosition, water}); } - void RecastMeshBuilder::addHeightfield(int cellSize, const osg::Vec3f& shift, float height) + void RecastMeshBuilder::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, float height) { - if (const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellSize, shift))) - mFlatHeightfields.emplace_back(FlatHeightfield {*intersection, height + shift.z()}); + if (const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellPosition, cellSize))) + mFlatHeightfields.emplace_back(FlatHeightfield {cellPosition, cellSize, height}); } - void RecastMeshBuilder::addHeightfield(int cellSize, const osg::Vec3f& shift, const float* heights, + void RecastMeshBuilder::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const float* heights, std::size_t size, float minHeight, float maxHeight) { - const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellSize, shift)); + const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellPosition, cellSize)); if (!intersection.has_value()) return; - const float stepSize = static_cast(cellSize) / (size - 1); + const osg::Vec3f shift = Misc::Convert::toOsg(BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, minHeight, maxHeight)); + const float stepSize = getHeightfieldScale(cellSize, size); const int halfCellSize = cellSize / 2; const auto local = [&] (float v, float shift) { return (v - shift + halfCellSize) / stepSize; }; const auto index = [&] (float v, int add) { return std::clamp(static_cast(v) + add, 0, size); }; @@ -236,13 +243,15 @@ namespace DetourNavigator for (std::size_t x = minX; x < endX; ++x) tileHeights.push_back(heights[x + y * size]); Heightfield heightfield; - heightfield.mBounds = *intersection; + heightfield.mCellPosition = cellPosition; + heightfield.mCellSize = cellSize; heightfield.mLength = static_cast(endY - minY); heightfield.mMinHeight = minHeight; heightfield.mMaxHeight = maxHeight; - heightfield.mShift = shift + osg::Vec3f(minX, minY, 0) * stepSize - osg::Vec3f(halfCellSize, halfCellSize, 0); - heightfield.mScale = stepSize; heightfield.mHeights = std::move(tileHeights); + heightfield.mOriginalSize = size; + heightfield.mMinX = static_cast(minX); + heightfield.mMinY = static_cast(minY); mHeightfields.push_back(std::move(heightfield)); } diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 2d68a7e89f..4bdcb788e3 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -50,9 +50,9 @@ namespace DetourNavigator void addWater(const osg::Vec2i& cellPosition, const Water& water); - void addHeightfield(int cellSize, const osg::Vec3f& shift, float height); + void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, float height); - void addHeightfield(int cellSize, const osg::Vec3f& shift, const float* heights, std::size_t size, + void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const float* heights, std::size_t size, float minHeight, float maxHeight); std::shared_ptr create(std::size_t generation, std::size_t revision) &&; diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 48a64d70aa..4772ec74a9 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -4,6 +4,7 @@ #include "heightfieldshape.hpp" #include +#include #include @@ -11,17 +12,18 @@ namespace { struct AddHeightfield { - const DetourNavigator::Cell& mCell; + osg::Vec2i mCellPosition; + int mCellSize; DetourNavigator::RecastMeshBuilder& mBuilder; void operator()(const DetourNavigator::HeightfieldSurface& v) { - mBuilder.addHeightfield(mCell.mSize, mCell.mShift, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight); + mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight); } void operator()(DetourNavigator::HeightfieldPlane v) { - mBuilder.addHeightfield(mCell.mSize, mCell.mShift, v.mHeight); + mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeight); } }; } @@ -94,24 +96,24 @@ namespace DetourNavigator return result; } - bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, + bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { const std::lock_guard lock(mMutex); - if (!mHeightfields.emplace(cellPosition, Heightfield {Cell {cellSize, shift}, shape}).second) + if (!mHeightfields.emplace(cellPosition, SizedHeightfieldShape {cellSize, shape}).second) return false; ++mRevision; return true; } - std::optional RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) + std::optional RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { const std::lock_guard lock(mMutex); const auto it = mHeightfields.find(cellPosition); if (it == mHeightfields.end()) return std::nullopt; ++mRevision; - const auto result = std::make_optional(it->second.mCell); + auto result = std::make_optional(it->second); mHeightfields.erase(it); return result; } @@ -132,7 +134,7 @@ namespace DetourNavigator for (const auto& [k, v] : mWater) builder.addWater(k, v); for (const auto& [cellPosition, v] : mHeightfields) - std::visit(AddHeightfield {v.mCell, builder}, v.mShape); + std::visit(AddHeightfield {cellPosition, v.mCellSize, builder}, v.mShape); objects.reserve(mObjects.size()); for (const auto& [k, object] : mObjects) { diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index 576314191f..e897c797fc 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -29,6 +29,12 @@ namespace DetourNavigator btTransform mTransform; }; + struct SizedHeightfieldShape + { + int mCellSize; + HeightfieldShape mShape; + }; + class RecastMeshManager { public: @@ -45,10 +51,9 @@ namespace DetourNavigator std::optional removeWater(const osg::Vec2i& cellPosition); - bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, - const HeightfieldShape& shape); + bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); - std::optional removeHeightfield(const osg::Vec2i& cellPosition); + std::optional removeHeightfield(const osg::Vec2i& cellPosition); std::shared_ptr getMesh() const; @@ -65,19 +70,13 @@ namespace DetourNavigator Version mNavMeshVersion; }; - struct Heightfield - { - Cell mCell; - HeightfieldShape mShape; - }; - const std::size_t mGeneration; const TileBounds mTileBounds; mutable std::mutex mMutex; std::size_t mRevision = 0; std::map mObjects; std::map mWater; - std::map mHeightfields; + std::map mHeightfields; std::optional mLastNavMeshReportedChange; std::optional mLastNavMeshReport; }; diff --git a/components/detournavigator/tilebounds.hpp b/components/detournavigator/tilebounds.hpp index 8557045342..693a382740 100644 --- a/components/detournavigator/tilebounds.hpp +++ b/components/detournavigator/tilebounds.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H #include +#include #include #include @@ -32,6 +33,14 @@ namespace DetourNavigator return std::nullopt; return TileBounds {osg::Vec2f(minX, minY), osg::Vec2f(maxX, maxY)}; } + + inline TileBounds maxCellTileBounds(const osg::Vec2i& position, int size) + { + return TileBounds { + osg::Vec2f(position.x(), position.y()) * size, + osg::Vec2f(position.x() + 1, position.y() + 1) * size + }; + } } #endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 347d4b7b30..20fbe30667 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -74,7 +74,7 @@ namespace DetourNavigator } else { - const osg::Vec3f shift = getWaterShift3d(cellPosition, cellSize, level); + const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) { const auto tiles = mTiles.lock(); @@ -126,8 +126,9 @@ namespace DetourNavigator } bool TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, - const osg::Vec3f& shift, const HeightfieldShape& shape) + const HeightfieldShape& shape) { + const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize); auto& tilesPositions = mHeightfieldTilesPositions[cellPosition]; bool result = false; @@ -142,7 +143,7 @@ namespace DetourNavigator tile = tiles->emplace(tilePosition, std::make_shared(tileBounds, mTilesGeneration)).first; } - if (tile->second->addHeightfield(cellPosition, cellSize, shift, shape)) + if (tile->second->addHeightfield(cellPosition, cellSize, shape)) { tilesPositions.push_back(tilePosition); result = true; @@ -155,12 +156,12 @@ namespace DetourNavigator return result; } - std::optional TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) + std::optional TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { const auto object = mHeightfieldTilesPositions.find(cellPosition); if (object == mHeightfieldTilesPositions.end()) return std::nullopt; - std::optional result; + std::optional result; for (const auto& tilePosition : object->second) { const auto tiles = mTiles.lock(); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 606d08afdf..bb08a4227e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -80,10 +80,9 @@ namespace DetourNavigator std::optional removeWater(const osg::Vec2i& cellPosition); - bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, - const HeightfieldShape& shape); + bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); - std::optional removeHeightfield(const osg::Vec2i& cellPosition); + std::optional removeHeightfield(const osg::Vec2i& cellPosition); std::shared_ptr getMesh(const TilePosition& tilePosition) const; From 3daa716090ef1fd18c14875ef69c4b131e1cb25c Mon Sep 17 00:00:00 2001 From: Thomas Lowe Date: Fri, 3 Dec 2021 06:45:54 -0500 Subject: [PATCH 1814/2859] Updated button description. --- files/ui/advancedpage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 4b7434dc35..3a2e7c49fc 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -457,7 +457,7 @@ - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance if MSAA is off.</p></body></html> + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> Use anti-alias alpha testing From 620748480b07df8f30435ec518a788dd9e50bc68 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 2 Dec 2021 22:02:42 +0000 Subject: [PATCH 1815/2859] Merge branch 'lua_missing_key_codes' into 'master' Lua binding for SDL_GetKeyName, two missing scan codes See merge request OpenMW/openmw!1450 (cherry picked from commit d86e7d4c9a28bc96af0a5638b26879fa49b8a847) 9a073baa Add Apostrophe and Period scan codes d66f3a35 Add getKeyName to Lua input API ed64add9 Replace mentions of KeyEvent with KEY --- apps/openmw/mwlua/inputbindings.cpp | 6 ++++++ files/lua_api/openmw/input.lua | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 6707d35d3b..8aecd1269c 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -71,6 +71,10 @@ 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["ACTION"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ {"GameMenu", MWInput::A_GameMenu}, {"Screenshot", MWInput::A_Screenshot}, @@ -252,6 +256,7 @@ namespace MWLua {"RightBracket", SDL_SCANCODE_RIGHTBRACKET}, {"RightShift", SDL_SCANCODE_RSHIFT}, + {"Apostrophe", SDL_SCANCODE_APOSTROPHE}, {"BackSlash", SDL_SCANCODE_BACKSLASH}, {"Backspace", SDL_SCANCODE_BACKSPACE}, {"CapsLock", SDL_SCANCODE_CAPSLOCK}, @@ -267,6 +272,7 @@ namespace MWLua {"NumLock", SDL_SCANCODE_NUMLOCKCLEAR}, {"PageDown", SDL_SCANCODE_PAGEDOWN}, {"PageUp", SDL_SCANCODE_PAGEUP}, + {"Period", SDL_SCANCODE_PERIOD}, {"Pause", SDL_SCANCODE_PAUSE}, {"PrintScreen", SDL_SCANCODE_PRINTSCREEN}, {"ScrollLock", SDL_SCANCODE_SCROLLLOCK}, diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index d4ff7f3475..361073e79d 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -20,7 +20,7 @@ ------------------------------------------------------------------------------- -- Is a keyboard button currently pressed. -- @function [parent=#input] isKeyPressed --- @param #number keyCode Key code (the same code that is used in @{openmw.input#KeyEvent}) +-- @param #number keyCode Key code (see @{openmw.input#KEY}) -- @return #boolean ------------------------------------------------------------------------------- @@ -83,6 +83,12 @@ -- @param #string key Control type (see @{openmw.input#CONTROL_SWITCH}) -- @param #boolean value +------------------------------------------------------------------------------- +-- Returns a human readable name for the given key code +-- @function [parent=#input] getKeyName +-- @param #number code A key code (see @{openmw.input#KEY}) +-- @return #string + ------------------------------------------------------------------------------- -- @type CONTROL_SWITCH -- @field [parent=#CONTROL_SWITCH] #string Controls Ability to move @@ -268,6 +274,7 @@ -- @field #number RightBracket -- @field #number RightSuper -- @field #number RightShift +-- @field #number Apostrophe -- @field #number BackSlash -- @field #number Backspace -- @field #number CapsLock @@ -284,6 +291,7 @@ -- @field #number PageDown -- @field #number PageUp -- @field #number Pause +-- @field #number Period -- @field #number PrintScreen -- @field #number ScrollLock -- @field #number Semicolon From f19b315651e9383f8759b719097faead398bdf99 Mon Sep 17 00:00:00 2001 From: psi29a Date: Fri, 3 Dec 2021 13:39:39 +0000 Subject: [PATCH 1816/2859] Update .resubmitted_merge_requests.txt --- .resubmitted_merge_requests.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.resubmitted_merge_requests.txt b/.resubmitted_merge_requests.txt index 01af064708..93dda75f1d 100644 --- a/.resubmitted_merge_requests.txt +++ b/.resubmitted_merge_requests.txt @@ -1,3 +1,4 @@ +1450 1314 1216 1172 From 47ff6a87a3c54b55722d114eba6a699da3fb329e Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Fri, 3 Dec 2021 16:05:18 +0000 Subject: [PATCH 1817/2859] Raise a warning when entering non-existent region --- CHANGELOG.md | 1 + apps/openmw/mwscript/skyextensions.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 778df6e680..0d85fbc865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Bug #6343: Magic projectile speed doesn't take race weight into account Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures Bug #6354: SFX abruptly cut off after crossing max distance; implement soft fading of sound effects + Bug #6358: Changeweather command does not report an error when entering non-existent region Bug #6363: Some scripts in Morrowland fail to work Bug #6376: Creatures should be able to use torches Bug #6386: Artifacts in water reflection due to imprecise screen-space coordinate computation diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index 4f4891b424..81984ad7b7 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -11,6 +11,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" + #include "interpretercontext.hpp" namespace MWScript @@ -91,7 +93,11 @@ namespace MWScript Interpreter::Type_Integer id = runtime[0].mInteger; runtime.pop(); - MWBase::Environment::get().getWorld()->changeWeather(region, id); + const ESM::Region* reg = MWBase::Environment::get().getWorld()->getStore().get().search(region); + if (reg) + MWBase::Environment::get().getWorld()->changeWeather(region, id); + else + runtime.getContext().report("Warning: Region \"" + region + "\" was not found"); } }; From d3df3efaf191a1554a32100c41fa18a22c689858 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 4 Dec 2021 11:32:39 +0000 Subject: [PATCH 1818/2859] Precompile sol.hpp in order to reduce compilation time. --- CMakeLists.txt | 6 ++++-- apps/openmw/CMakeLists.txt | 5 ++++- apps/openmw/android_main.cpp | 2 ++ apps/openmw_test_suite/CMakeLists.txt | 2 +- components/CMakeLists.txt | 5 +++++ 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 067b645a39..3f19b2a54f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,7 +414,8 @@ else(USE_LUAJIT) endif(USE_LUAJIT) # C++ library binding to Lua -set(SOL_INCLUDE_DIRS ${OpenMW_SOURCE_DIR}/extern/sol3.2.2 ${OpenMW_SOURCE_DIR}/extern/sol_config) +set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3.2.2) +set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config) include_directories( BEFORE SYSTEM @@ -426,7 +427,8 @@ include_directories( ${OPENGL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} ${LUA_INCLUDE_DIR} - ${SOL_INCLUDE_DIRS} + ${SOL_INCLUDE_DIR} + ${SOL_CONFIG_DIR} ) link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2582b272cf..551c68ce11 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -153,9 +153,12 @@ target_link_libraries(openmw "osg-ffmpeg-videoplayer" "oics" components - ${LUA_LIBRARIES} ) +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16) + target_precompile_headers(openmw PRIVATE ${SOL_INCLUDE_DIR}/sol/sol.hpp) +endif () + if (ANDROID) target_link_libraries(openmw EGL android log z) endif (ANDROID) diff --git a/apps/openmw/android_main.cpp b/apps/openmw/android_main.cpp index cc36388b0b..95365915df 100644 --- a/apps/openmw/android_main.cpp +++ b/apps/openmw/android_main.cpp @@ -1,4 +1,6 @@ +#ifndef stderr int stderr = 0; // Hack: fix linker error +#endif #include "SDL_main.h" #include diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index ed1d6c413b..7cfac20421 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -70,7 +70,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components ${LUA_LIBRARIES}) + target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components) # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bdb22f2dc5..43f0fb1ab5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -283,6 +283,7 @@ target_link_libraries(components ${SDL2_LIBRARIES} ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} + ${LUA_LIBRARIES} LZ4::LZ4 RecastNavigation::DebugUtils RecastNavigation::Detour @@ -371,3 +372,7 @@ endif(OSG_STATIC) if(USE_QT) set_property(TARGET components_qt PROPERTY AUTOMOC ON) endif(USE_QT) + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16) + target_precompile_headers(components PRIVATE ${SOL_INCLUDE_DIR}/sol/sol.hpp) +endif () From 15c7bddd57355c085c0f5094b39ba8143ce98795 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 4 Dec 2021 12:16:38 +0000 Subject: [PATCH 1819/2859] Move SDL-MyGUI input mappings to components, map both ways --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwinput/bindingsmanager.cpp | 7 +- apps/openmw/mwinput/controllermanager.cpp | 6 +- apps/openmw/mwinput/inputmanagerimp.cpp | 1 - apps/openmw/mwinput/keyboardmanager.cpp | 7 +- apps/openmw/mwinput/mousemanager.cpp | 24 +- apps/openmw/mwinput/sdlmappings.cpp | 218 --------------- components/CMakeLists.txt | 2 +- components/lua_ui/widget.cpp | 8 +- components/sdlutil/sdlmappings.cpp | 251 ++++++++++++++++++ .../sdlutil}/sdlmappings.hpp | 12 +- 11 files changed, 293 insertions(+), 245 deletions(-) delete mode 100644 apps/openmw/mwinput/sdlmappings.cpp create mode 100644 components/sdlutil/sdlmappings.cpp rename {apps/openmw/mwinput => components/sdlutil}/sdlmappings.hpp (50%) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2582b272cf..6b2dc91b72 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -27,7 +27,7 @@ add_openmw_dir (mwrender add_openmw_dir (mwinput actions actionmanager bindingsmanager controllermanager controlswitch - inputmanagerimp mousemanager keyboardmanager sdlmappings sensormanager + inputmanagerimp mousemanager keyboardmanager sensormanager ) add_openmw_dir (mwgui diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index f3e1ba2c8d..68be849dbd 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -13,7 +15,6 @@ #include "../mwworld/player.hpp" #include "actions.hpp" -#include "sdlmappings.hpp" namespace MWInput { @@ -546,9 +547,9 @@ namespace MWInput ICS::Control* c = mInputBinder->getChannel(action)->getAttachedControls().front().control; if (mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS::InputControlSystem::UNASSIGNED) - return sdlControllerAxisToString(mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); + return SDLUtil::sdlControllerAxisToString(mInputBinder->getJoystickAxisBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); else if (mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE) != ICS_MAX_DEVICE_BUTTONS) - return sdlControllerButtonToString(mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); + return SDLUtil::sdlControllerButtonToString(mInputBinder->getJoystickButtonBinding(c, sFakeDeviceId, ICS::Control::INCREASE)); else return "#{sNone}"; } diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 0ed58349d6..1e72a1c95b 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" @@ -19,7 +20,6 @@ #include "actionmanager.hpp" #include "bindingsmanager.hpp" #include "mousemanager.hpp" -#include "sdlmappings.hpp" namespace MWInput { @@ -215,7 +215,7 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(true); //esc, to leave initial movie screen - auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); + auto kc = SDLUtil::sdlKeyToMyGUI(SDLK_ESCAPE); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0)); if (!MWBase::Environment::get().getInputManager()->controlsDisabled()) @@ -259,7 +259,7 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(true); //esc, to leave initial movie screen - auto kc = sdlKeyToMyGUI(SDLK_ESCAPE); + auto kc = SDLUtil::sdlKeyToMyGUI(SDLK_ESCAPE); mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); mBindingsManager->controllerButtonReleased(deviceID, arg); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 5d737bd446..4ebe56bf94 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -18,7 +18,6 @@ #include "controlswitch.hpp" #include "keyboardmanager.hpp" #include "mousemanager.hpp" -#include "sdlmappings.hpp" #include "sensormanager.hpp" namespace MWInput diff --git a/apps/openmw/mwinput/keyboardmanager.cpp b/apps/openmw/mwinput/keyboardmanager.cpp index b121b665e7..d8fc548f25 100644 --- a/apps/openmw/mwinput/keyboardmanager.cpp +++ b/apps/openmw/mwinput/keyboardmanager.cpp @@ -4,6 +4,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/luamanager.hpp" @@ -13,7 +15,6 @@ #include "actions.hpp" #include "bindingsmanager.hpp" -#include "sdlmappings.hpp" namespace MWInput { @@ -35,7 +36,7 @@ namespace MWInput // HACK: to make default keybinding for the console work without printing an extra "^" upon closing // This assumes that SDL_TextInput events always come *after* the key event // (which is somewhat reasonable, and hopefully true for all SDL platforms) - auto kc = sdlKeyToMyGUI(arg.keysym.sym); + auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym); if (mBindingsManager->getKeyBinding(A_Console) == arg.keysym.scancode && MWBase::Environment::get().getWindowManager()->isConsoleMode()) SDL_StopTextInput(); @@ -71,7 +72,7 @@ namespace MWInput void KeyboardManager::keyReleased(const SDL_KeyboardEvent &arg) { MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); - auto kc = sdlKeyToMyGUI(arg.keysym.sym); + auto kc = SDLUtil::sdlKeyToMyGUI(arg.keysym.sym); if (!mBindingsManager->isDetectingBindingState()) mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 6fbe8cfc98..f646501607 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" @@ -17,7 +18,6 @@ #include "actions.hpp" #include "bindingsmanager.hpp" -#include "sdlmappings.hpp" namespace MWInput { @@ -125,7 +125,11 @@ namespace MWInput else { bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; + guiMode = MyGUI::InputManager::getInstance().injectMouseRelease( + static_cast(mGuiCursorX), + static_cast(mGuiCursorY), + SDLUtil::sdlMouseButtonToMyGui(id) + ) && guiMode; if (mBindingsManager->isDetectingBindingState()) return; // don't allow same mouseup to bind as initiated bind @@ -153,7 +157,11 @@ namespace MWInput if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events { guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; + guiMode = MyGUI::InputManager::getInstance().injectMousePress( + static_cast(mGuiCursorX), + static_cast(mGuiCursorY), + SDLUtil::sdlMouseButtonToMyGui(id) + ) && guiMode; if (MyGUI::InputManager::getInstance().getMouseFocusWidget () != nullptr) { MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); @@ -229,12 +237,18 @@ namespace MWInput bool MouseManager::injectMouseButtonPress(Uint8 button) { - return MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(button)); + return MyGUI::InputManager::getInstance().injectMousePress( + static_cast(mGuiCursorX), + static_cast(mGuiCursorY), + SDLUtil::sdlMouseButtonToMyGui(button)); } bool MouseManager::injectMouseButtonRelease(Uint8 button) { - return MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(button)); + return MyGUI::InputManager::getInstance().injectMouseRelease( + static_cast(mGuiCursorX), + static_cast(mGuiCursorY), + SDLUtil::sdlMouseButtonToMyGui(button)); } void MouseManager::injectMouseMove(float xMove, float yMove, float mouseWheelMove) diff --git a/apps/openmw/mwinput/sdlmappings.cpp b/apps/openmw/mwinput/sdlmappings.cpp deleted file mode 100644 index 0c3f5c5d85..0000000000 --- a/apps/openmw/mwinput/sdlmappings.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "sdlmappings.hpp" - -#include - -#include - -#include -#include - -namespace MWInput -{ - std::string sdlControllerButtonToString(int button) - { - switch(button) - { - case SDL_CONTROLLER_BUTTON_A: - return "A Button"; - case SDL_CONTROLLER_BUTTON_B: - return "B Button"; - case SDL_CONTROLLER_BUTTON_BACK: - return "Back Button"; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - return "DPad Down"; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: - return "DPad Left"; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - return "DPad Right"; - case SDL_CONTROLLER_BUTTON_DPAD_UP: - return "DPad Up"; - case SDL_CONTROLLER_BUTTON_GUIDE: - return "Guide Button"; - case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - return "Left Shoulder"; - case SDL_CONTROLLER_BUTTON_LEFTSTICK: - return "Left Stick Button"; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - return "Right Shoulder"; - case SDL_CONTROLLER_BUTTON_RIGHTSTICK: - return "Right Stick Button"; - case SDL_CONTROLLER_BUTTON_START: - return "Start Button"; - case SDL_CONTROLLER_BUTTON_X: - return "X Button"; - case SDL_CONTROLLER_BUTTON_Y: - return "Y Button"; - default: - return "Button " + std::to_string(button); - } - } - - std::string sdlControllerAxisToString(int axis) - { - switch(axis) - { - case SDL_CONTROLLER_AXIS_LEFTX: - return "Left Stick X"; - case SDL_CONTROLLER_AXIS_LEFTY: - return "Left Stick Y"; - case SDL_CONTROLLER_AXIS_RIGHTX: - return "Right Stick X"; - case SDL_CONTROLLER_AXIS_RIGHTY: - return "Right Stick Y"; - case SDL_CONTROLLER_AXIS_TRIGGERLEFT: - return "Left Trigger"; - case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - return "Right Trigger"; - default: - return "Axis " + std::to_string(axis); - } - } - - MyGUI::MouseButton sdlButtonToMyGUI(Uint8 button) - { - //The right button is the second button, according to MyGUI - if(button == SDL_BUTTON_RIGHT) - button = SDL_BUTTON_MIDDLE; - else if(button == SDL_BUTTON_MIDDLE) - button = SDL_BUTTON_RIGHT; - - //MyGUI's buttons are 0 indexed - return MyGUI::MouseButton::Enum(button - 1); - } - - void initKeyMap(std::map& keyMap) - { - keyMap[SDLK_UNKNOWN] = MyGUI::KeyCode::None; - keyMap[SDLK_ESCAPE] = MyGUI::KeyCode::Escape; - keyMap[SDLK_1] = MyGUI::KeyCode::One; - keyMap[SDLK_2] = MyGUI::KeyCode::Two; - keyMap[SDLK_3] = MyGUI::KeyCode::Three; - keyMap[SDLK_4] = MyGUI::KeyCode::Four; - keyMap[SDLK_5] = MyGUI::KeyCode::Five; - keyMap[SDLK_6] = MyGUI::KeyCode::Six; - keyMap[SDLK_7] = MyGUI::KeyCode::Seven; - keyMap[SDLK_8] = MyGUI::KeyCode::Eight; - keyMap[SDLK_9] = MyGUI::KeyCode::Nine; - keyMap[SDLK_0] = MyGUI::KeyCode::Zero; - keyMap[SDLK_MINUS] = MyGUI::KeyCode::Minus; - keyMap[SDLK_EQUALS] = MyGUI::KeyCode::Equals; - keyMap[SDLK_BACKSPACE] = MyGUI::KeyCode::Backspace; - keyMap[SDLK_TAB] = MyGUI::KeyCode::Tab; - keyMap[SDLK_q] = MyGUI::KeyCode::Q; - keyMap[SDLK_w] = MyGUI::KeyCode::W; - keyMap[SDLK_e] = MyGUI::KeyCode::E; - keyMap[SDLK_r] = MyGUI::KeyCode::R; - keyMap[SDLK_t] = MyGUI::KeyCode::T; - keyMap[SDLK_y] = MyGUI::KeyCode::Y; - keyMap[SDLK_u] = MyGUI::KeyCode::U; - keyMap[SDLK_i] = MyGUI::KeyCode::I; - keyMap[SDLK_o] = MyGUI::KeyCode::O; - keyMap[SDLK_p] = MyGUI::KeyCode::P; - keyMap[SDLK_RETURN] = MyGUI::KeyCode::Return; - keyMap[SDLK_a] = MyGUI::KeyCode::A; - keyMap[SDLK_s] = MyGUI::KeyCode::S; - keyMap[SDLK_d] = MyGUI::KeyCode::D; - keyMap[SDLK_f] = MyGUI::KeyCode::F; - keyMap[SDLK_g] = MyGUI::KeyCode::G; - keyMap[SDLK_h] = MyGUI::KeyCode::H; - keyMap[SDLK_j] = MyGUI::KeyCode::J; - keyMap[SDLK_k] = MyGUI::KeyCode::K; - keyMap[SDLK_l] = MyGUI::KeyCode::L; - keyMap[SDLK_SEMICOLON] = MyGUI::KeyCode::Semicolon; - keyMap[SDLK_QUOTE] = MyGUI::KeyCode::Apostrophe; - keyMap[SDLK_BACKQUOTE] = MyGUI::KeyCode::Grave; - keyMap[SDLK_LSHIFT] = MyGUI::KeyCode::LeftShift; - keyMap[SDLK_BACKSLASH] = MyGUI::KeyCode::Backslash; - keyMap[SDLK_z] = MyGUI::KeyCode::Z; - keyMap[SDLK_x] = MyGUI::KeyCode::X; - keyMap[SDLK_c] = MyGUI::KeyCode::C; - keyMap[SDLK_v] = MyGUI::KeyCode::V; - keyMap[SDLK_b] = MyGUI::KeyCode::B; - keyMap[SDLK_n] = MyGUI::KeyCode::N; - keyMap[SDLK_m] = MyGUI::KeyCode::M; - keyMap[SDLK_COMMA] = MyGUI::KeyCode::Comma; - keyMap[SDLK_PERIOD] = MyGUI::KeyCode::Period; - keyMap[SDLK_SLASH] = MyGUI::KeyCode::Slash; - keyMap[SDLK_RSHIFT] = MyGUI::KeyCode::RightShift; - keyMap[SDLK_KP_MULTIPLY] = MyGUI::KeyCode::Multiply; - keyMap[SDLK_LALT] = MyGUI::KeyCode::LeftAlt; - keyMap[SDLK_SPACE] = MyGUI::KeyCode::Space; - keyMap[SDLK_CAPSLOCK] = MyGUI::KeyCode::Capital; - keyMap[SDLK_F1] = MyGUI::KeyCode::F1; - keyMap[SDLK_F2] = MyGUI::KeyCode::F2; - keyMap[SDLK_F3] = MyGUI::KeyCode::F3; - keyMap[SDLK_F4] = MyGUI::KeyCode::F4; - keyMap[SDLK_F5] = MyGUI::KeyCode::F5; - keyMap[SDLK_F6] = MyGUI::KeyCode::F6; - keyMap[SDLK_F7] = MyGUI::KeyCode::F7; - keyMap[SDLK_F8] = MyGUI::KeyCode::F8; - keyMap[SDLK_F9] = MyGUI::KeyCode::F9; - keyMap[SDLK_F10] = MyGUI::KeyCode::F10; - keyMap[SDLK_NUMLOCKCLEAR] = MyGUI::KeyCode::NumLock; - keyMap[SDLK_SCROLLLOCK] = MyGUI::KeyCode::ScrollLock; - keyMap[SDLK_KP_7] = MyGUI::KeyCode::Numpad7; - keyMap[SDLK_KP_8] = MyGUI::KeyCode::Numpad8; - keyMap[SDLK_KP_9] = MyGUI::KeyCode::Numpad9; - keyMap[SDLK_KP_MINUS] = MyGUI::KeyCode::Subtract; - keyMap[SDLK_KP_4] = MyGUI::KeyCode::Numpad4; - keyMap[SDLK_KP_5] = MyGUI::KeyCode::Numpad5; - keyMap[SDLK_KP_6] = MyGUI::KeyCode::Numpad6; - keyMap[SDLK_KP_PLUS] = MyGUI::KeyCode::Add; - keyMap[SDLK_KP_1] = MyGUI::KeyCode::Numpad1; - keyMap[SDLK_KP_2] = MyGUI::KeyCode::Numpad2; - keyMap[SDLK_KP_3] = MyGUI::KeyCode::Numpad3; - keyMap[SDLK_KP_0] = MyGUI::KeyCode::Numpad0; - keyMap[SDLK_KP_PERIOD] = MyGUI::KeyCode::Decimal; - keyMap[SDLK_F11] = MyGUI::KeyCode::F11; - keyMap[SDLK_F12] = MyGUI::KeyCode::F12; - keyMap[SDLK_F13] = MyGUI::KeyCode::F13; - keyMap[SDLK_F14] = MyGUI::KeyCode::F14; - keyMap[SDLK_F15] = MyGUI::KeyCode::F15; - keyMap[SDLK_KP_EQUALS] = MyGUI::KeyCode::NumpadEquals; - keyMap[SDLK_COLON] = MyGUI::KeyCode::Colon; - keyMap[SDLK_KP_ENTER] = MyGUI::KeyCode::NumpadEnter; - keyMap[SDLK_KP_DIVIDE] = MyGUI::KeyCode::Divide; - keyMap[SDLK_SYSREQ] = MyGUI::KeyCode::SysRq; - keyMap[SDLK_RALT] = MyGUI::KeyCode::RightAlt; - keyMap[SDLK_HOME] = MyGUI::KeyCode::Home; - keyMap[SDLK_UP] = MyGUI::KeyCode::ArrowUp; - keyMap[SDLK_PAGEUP] = MyGUI::KeyCode::PageUp; - keyMap[SDLK_LEFT] = MyGUI::KeyCode::ArrowLeft; - keyMap[SDLK_RIGHT] = MyGUI::KeyCode::ArrowRight; - keyMap[SDLK_END] = MyGUI::KeyCode::End; - keyMap[SDLK_DOWN] = MyGUI::KeyCode::ArrowDown; - keyMap[SDLK_PAGEDOWN] = MyGUI::KeyCode::PageDown; - keyMap[SDLK_INSERT] = MyGUI::KeyCode::Insert; - keyMap[SDLK_DELETE] = MyGUI::KeyCode::Delete; - keyMap[SDLK_APPLICATION] = MyGUI::KeyCode::AppMenu; - -//The function of the Ctrl and Meta keys are switched on macOS compared to other platforms. -//For instance] = Cmd+C versus Ctrl+C to copy from the system clipboard -#if defined(__APPLE__) - keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftControl; - keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightControl; - keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftWindows; - keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightWindows; -#else - keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftWindows; - keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightWindows; - keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftControl; - keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightControl; -#endif - } - - MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code) - { - static std::map keyMap; - if (keyMap.empty()) - initKeyMap(keyMap); - - MyGUI::KeyCode kc = MyGUI::KeyCode::None; - auto foundKey = keyMap.find(code); - if (foundKey != keyMap.end()) - kc = foundKey->second; - - return kc; - } -} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bdb22f2dc5..25b8f06331 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -146,7 +146,7 @@ add_component_dir (fontloader ) add_component_dir (sdlutil - gl4es_init sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager + gl4es_init sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager sdlmappings ) add_component_dir (version diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 5653659287..6d7bb5063c 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -1,6 +1,7 @@ #include "widget.hpp" #include +#include #include "text.hpp" #include "textedit.hpp" @@ -87,9 +88,7 @@ namespace LuaUi sol::object WidgetExtension::keyEvent(MyGUI::KeyCode code) const { SDL_Keysym keySym; - // MyGUI key codes are not one to one with SDL key codes - // \todo refactor sdlmappings.cpp to map this back to SDL correctly - keySym.sym = static_cast(code.getValue()); + keySym.sym = SDLUtil::myGuiKeyToSdl(code); keySym.scancode = SDL_GetScancodeFromKey(keySym.sym); keySym.mod = SDL_GetModState(); return sol::make_object(mLua, keySym); @@ -103,8 +102,7 @@ namespace LuaUi sol::table table = makeTable(); table["position"] = position; table["offset"] = offset; - // \todo refactor sdlmappings.cpp to map this back to SDL properly - table["button"] = button.getValue() + 1; + table["button"] = SDLUtil::myGuiMouseButtonToSdl(button); return table; } diff --git a/components/sdlutil/sdlmappings.cpp b/components/sdlutil/sdlmappings.cpp new file mode 100644 index 0000000000..8306909ee5 --- /dev/null +++ b/components/sdlutil/sdlmappings.cpp @@ -0,0 +1,251 @@ +#include "sdlmappings.hpp" + +#include + +#include + +#include +#include + +namespace SDLUtil +{ + std::string sdlControllerButtonToString(int button) + { + switch(button) + { + case SDL_CONTROLLER_BUTTON_A: + return "A Button"; + case SDL_CONTROLLER_BUTTON_B: + return "B Button"; + case SDL_CONTROLLER_BUTTON_BACK: + return "Back Button"; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + return "DPad Down"; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + return "DPad Left"; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + return "DPad Right"; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + return "DPad Up"; + case SDL_CONTROLLER_BUTTON_GUIDE: + return "Guide Button"; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return "Left Shoulder"; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + return "Left Stick Button"; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return "Right Shoulder"; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + return "Right Stick Button"; + case SDL_CONTROLLER_BUTTON_START: + return "Start Button"; + case SDL_CONTROLLER_BUTTON_X: + return "X Button"; + case SDL_CONTROLLER_BUTTON_Y: + return "Y Button"; + default: + return "Button " + std::to_string(button); + } + } + + std::string sdlControllerAxisToString(int axis) + { + switch(axis) + { + case SDL_CONTROLLER_AXIS_LEFTX: + return "Left Stick X"; + case SDL_CONTROLLER_AXIS_LEFTY: + return "Left Stick Y"; + case SDL_CONTROLLER_AXIS_RIGHTX: + return "Right Stick X"; + case SDL_CONTROLLER_AXIS_RIGHTY: + return "Right Stick Y"; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + return "Left Trigger"; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + return "Right Trigger"; + default: + return "Axis " + std::to_string(axis); + } + } + + MyGUI::MouseButton sdlMouseButtonToMyGui(Uint8 button) + { + //The right button is the second button, according to MyGUI + if(button == SDL_BUTTON_RIGHT) + button = SDL_BUTTON_MIDDLE; + else if(button == SDL_BUTTON_MIDDLE) + button = SDL_BUTTON_RIGHT; + + //MyGUI's buttons are 0 indexed + return MyGUI::MouseButton::Enum(button - 1); + } + + Uint8 myGuiMouseButtonToSdl(MyGUI::MouseButton button) + { + Uint8 value = button.getValue() + 1; + if (value == SDL_BUTTON_RIGHT) + value = SDL_BUTTON_MIDDLE; + else if (value == SDL_BUTTON_MIDDLE) + value = SDL_BUTTON_RIGHT; + return value; + } + + namespace + { + std::map initKeyMap() + { + std::map keyMap; + keyMap[SDLK_UNKNOWN] = MyGUI::KeyCode::None; + keyMap[SDLK_ESCAPE] = MyGUI::KeyCode::Escape; + keyMap[SDLK_1] = MyGUI::KeyCode::One; + keyMap[SDLK_2] = MyGUI::KeyCode::Two; + keyMap[SDLK_3] = MyGUI::KeyCode::Three; + keyMap[SDLK_4] = MyGUI::KeyCode::Four; + keyMap[SDLK_5] = MyGUI::KeyCode::Five; + keyMap[SDLK_6] = MyGUI::KeyCode::Six; + keyMap[SDLK_7] = MyGUI::KeyCode::Seven; + keyMap[SDLK_8] = MyGUI::KeyCode::Eight; + keyMap[SDLK_9] = MyGUI::KeyCode::Nine; + keyMap[SDLK_0] = MyGUI::KeyCode::Zero; + keyMap[SDLK_MINUS] = MyGUI::KeyCode::Minus; + keyMap[SDLK_EQUALS] = MyGUI::KeyCode::Equals; + keyMap[SDLK_BACKSPACE] = MyGUI::KeyCode::Backspace; + keyMap[SDLK_TAB] = MyGUI::KeyCode::Tab; + keyMap[SDLK_q] = MyGUI::KeyCode::Q; + keyMap[SDLK_w] = MyGUI::KeyCode::W; + keyMap[SDLK_e] = MyGUI::KeyCode::E; + keyMap[SDLK_r] = MyGUI::KeyCode::R; + keyMap[SDLK_t] = MyGUI::KeyCode::T; + keyMap[SDLK_y] = MyGUI::KeyCode::Y; + keyMap[SDLK_u] = MyGUI::KeyCode::U; + keyMap[SDLK_i] = MyGUI::KeyCode::I; + keyMap[SDLK_o] = MyGUI::KeyCode::O; + keyMap[SDLK_p] = MyGUI::KeyCode::P; + keyMap[SDLK_RETURN] = MyGUI::KeyCode::Return; + keyMap[SDLK_a] = MyGUI::KeyCode::A; + keyMap[SDLK_s] = MyGUI::KeyCode::S; + keyMap[SDLK_d] = MyGUI::KeyCode::D; + keyMap[SDLK_f] = MyGUI::KeyCode::F; + keyMap[SDLK_g] = MyGUI::KeyCode::G; + keyMap[SDLK_h] = MyGUI::KeyCode::H; + keyMap[SDLK_j] = MyGUI::KeyCode::J; + keyMap[SDLK_k] = MyGUI::KeyCode::K; + keyMap[SDLK_l] = MyGUI::KeyCode::L; + keyMap[SDLK_SEMICOLON] = MyGUI::KeyCode::Semicolon; + keyMap[SDLK_QUOTE] = MyGUI::KeyCode::Apostrophe; + keyMap[SDLK_BACKQUOTE] = MyGUI::KeyCode::Grave; + keyMap[SDLK_LSHIFT] = MyGUI::KeyCode::LeftShift; + keyMap[SDLK_BACKSLASH] = MyGUI::KeyCode::Backslash; + keyMap[SDLK_z] = MyGUI::KeyCode::Z; + keyMap[SDLK_x] = MyGUI::KeyCode::X; + keyMap[SDLK_c] = MyGUI::KeyCode::C; + keyMap[SDLK_v] = MyGUI::KeyCode::V; + keyMap[SDLK_b] = MyGUI::KeyCode::B; + keyMap[SDLK_n] = MyGUI::KeyCode::N; + keyMap[SDLK_m] = MyGUI::KeyCode::M; + keyMap[SDLK_COMMA] = MyGUI::KeyCode::Comma; + keyMap[SDLK_PERIOD] = MyGUI::KeyCode::Period; + keyMap[SDLK_SLASH] = MyGUI::KeyCode::Slash; + keyMap[SDLK_RSHIFT] = MyGUI::KeyCode::RightShift; + keyMap[SDLK_KP_MULTIPLY] = MyGUI::KeyCode::Multiply; + keyMap[SDLK_LALT] = MyGUI::KeyCode::LeftAlt; + keyMap[SDLK_SPACE] = MyGUI::KeyCode::Space; + keyMap[SDLK_CAPSLOCK] = MyGUI::KeyCode::Capital; + keyMap[SDLK_F1] = MyGUI::KeyCode::F1; + keyMap[SDLK_F2] = MyGUI::KeyCode::F2; + keyMap[SDLK_F3] = MyGUI::KeyCode::F3; + keyMap[SDLK_F4] = MyGUI::KeyCode::F4; + keyMap[SDLK_F5] = MyGUI::KeyCode::F5; + keyMap[SDLK_F6] = MyGUI::KeyCode::F6; + keyMap[SDLK_F7] = MyGUI::KeyCode::F7; + keyMap[SDLK_F8] = MyGUI::KeyCode::F8; + keyMap[SDLK_F9] = MyGUI::KeyCode::F9; + keyMap[SDLK_F10] = MyGUI::KeyCode::F10; + keyMap[SDLK_NUMLOCKCLEAR] = MyGUI::KeyCode::NumLock; + keyMap[SDLK_SCROLLLOCK] = MyGUI::KeyCode::ScrollLock; + keyMap[SDLK_KP_7] = MyGUI::KeyCode::Numpad7; + keyMap[SDLK_KP_8] = MyGUI::KeyCode::Numpad8; + keyMap[SDLK_KP_9] = MyGUI::KeyCode::Numpad9; + keyMap[SDLK_KP_MINUS] = MyGUI::KeyCode::Subtract; + keyMap[SDLK_KP_4] = MyGUI::KeyCode::Numpad4; + keyMap[SDLK_KP_5] = MyGUI::KeyCode::Numpad5; + keyMap[SDLK_KP_6] = MyGUI::KeyCode::Numpad6; + keyMap[SDLK_KP_PLUS] = MyGUI::KeyCode::Add; + keyMap[SDLK_KP_1] = MyGUI::KeyCode::Numpad1; + keyMap[SDLK_KP_2] = MyGUI::KeyCode::Numpad2; + keyMap[SDLK_KP_3] = MyGUI::KeyCode::Numpad3; + keyMap[SDLK_KP_0] = MyGUI::KeyCode::Numpad0; + keyMap[SDLK_KP_PERIOD] = MyGUI::KeyCode::Decimal; + keyMap[SDLK_F11] = MyGUI::KeyCode::F11; + keyMap[SDLK_F12] = MyGUI::KeyCode::F12; + keyMap[SDLK_F13] = MyGUI::KeyCode::F13; + keyMap[SDLK_F14] = MyGUI::KeyCode::F14; + keyMap[SDLK_F15] = MyGUI::KeyCode::F15; + keyMap[SDLK_KP_EQUALS] = MyGUI::KeyCode::NumpadEquals; + keyMap[SDLK_COLON] = MyGUI::KeyCode::Colon; + keyMap[SDLK_KP_ENTER] = MyGUI::KeyCode::NumpadEnter; + keyMap[SDLK_KP_DIVIDE] = MyGUI::KeyCode::Divide; + keyMap[SDLK_SYSREQ] = MyGUI::KeyCode::SysRq; + keyMap[SDLK_RALT] = MyGUI::KeyCode::RightAlt; + keyMap[SDLK_HOME] = MyGUI::KeyCode::Home; + keyMap[SDLK_UP] = MyGUI::KeyCode::ArrowUp; + keyMap[SDLK_PAGEUP] = MyGUI::KeyCode::PageUp; + keyMap[SDLK_LEFT] = MyGUI::KeyCode::ArrowLeft; + keyMap[SDLK_RIGHT] = MyGUI::KeyCode::ArrowRight; + keyMap[SDLK_END] = MyGUI::KeyCode::End; + keyMap[SDLK_DOWN] = MyGUI::KeyCode::ArrowDown; + keyMap[SDLK_PAGEDOWN] = MyGUI::KeyCode::PageDown; + keyMap[SDLK_INSERT] = MyGUI::KeyCode::Insert; + keyMap[SDLK_DELETE] = MyGUI::KeyCode::Delete; + keyMap[SDLK_APPLICATION] = MyGUI::KeyCode::AppMenu; + + //The function of the Ctrl and Meta keys are switched on macOS compared to other platforms. + //For instance] = Cmd+C versus Ctrl+C to copy from the system clipboard + #if defined(__APPLE__) + keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftControl; + keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightControl; + keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftWindows; + keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightWindows; + #else + keyMap[SDLK_LGUI] = MyGUI::KeyCode::LeftWindows; + keyMap[SDLK_RGUI] = MyGUI::KeyCode::RightWindows; + keyMap[SDLK_LCTRL] = MyGUI::KeyCode::LeftControl; + keyMap[SDLK_RCTRL] = MyGUI::KeyCode::RightControl; + #endif + return keyMap; + } + + std::map reverseKeyMap(const std::map& map) + { + std::map result; + for (auto [sdl, mygui] : map) + result[mygui] = sdl; + return result; + } + } + + MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code) + { + static std::map keyMap = initKeyMap(); + + MyGUI::KeyCode kc = MyGUI::KeyCode::None; + auto foundKey = keyMap.find(code); + if (foundKey != keyMap.end()) + kc = foundKey->second; + + return kc; + } + + SDL_Keycode myGuiKeyToSdl(MyGUI::KeyCode button) + { + static auto keyMap = reverseKeyMap(initKeyMap()); + + SDL_Keycode kc = 0; + auto foundKey = keyMap.find(button); + if (foundKey != keyMap.end()) + kc = foundKey->second; + + return kc; + } +} diff --git a/apps/openmw/mwinput/sdlmappings.hpp b/components/sdlutil/sdlmappings.hpp similarity index 50% rename from apps/openmw/mwinput/sdlmappings.hpp rename to components/sdlutil/sdlmappings.hpp index 0cdd4694f5..3625075009 100644 --- a/apps/openmw/mwinput/sdlmappings.hpp +++ b/components/sdlutil/sdlmappings.hpp @@ -1,5 +1,5 @@ -#ifndef MWINPUT_SDLMAPPINGS_H -#define MWINPUT_SDLMAPPINGS_H +#ifndef SDLUTIL_SDLMAPPINGS +#define SDLUTIL_SDLMAPPINGS #include @@ -12,14 +12,16 @@ namespace MyGUI struct MouseButton; } -namespace MWInput +namespace SDLUtil { std::string sdlControllerButtonToString(int button); std::string sdlControllerAxisToString(int axis); - MyGUI::MouseButton sdlButtonToMyGUI(Uint8 button); + MyGUI::MouseButton sdlMouseButtonToMyGui(Uint8 button); + Uint8 myGuiMouseButtonToSdl(MyGUI::MouseButton button); MyGUI::KeyCode sdlKeyToMyGUI(SDL_Keycode code); + SDL_Keycode myGuiKeyToSdl(MyGUI::KeyCode button); } -#endif +#endif // !SDLUTIL_SDLMAPPINGS From dd15b30a27a1f759d00776a0173f246451a4077c Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 5 Dec 2021 12:54:49 +0300 Subject: [PATCH 1820/2859] Keep only one '\0' in NIF strings (bug #6473) --- CHANGELOG.md | 1 + components/nif/nifstream.hpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d85fbc865..65caa921a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Bug #6429: Wyrmhaven: Can't add AI packages to player Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened Bug #6451: Weapon summoned from Cast When Used item will have the name "None" + Bug #6473: Strings from NIF should be parsed only to first null terminator Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index b6bf01ce58..04243c2506 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -149,6 +149,9 @@ public: inp->read(str.data(), length); if (inp->bad()) throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); + size_t end = str.find('\0'); + if (end != std::string::npos) + str.erase(end); return str; } ///Read in a string of the length specified in the file From e2ca5288c2ff9a05a868c2924a12bb9e20b4a560 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 5 Dec 2021 13:19:36 +0000 Subject: [PATCH 1821/2859] Aggressively prune/expire unnecessary artifacts --- .gitlab-ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 349bb5ffa3..da77eccd31 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,6 +50,9 @@ Clang_Tidy: CXX: clang++ CI_CLANG_TIDY: 1 timeout: 8h + artifacts: + paths: [] + expire_in: 1 minute Coverity: extends: .Debian_Image @@ -73,6 +76,9 @@ Coverity: variables: CC: gcc CXX: g++ + artifacts: + paths: [] + expire_in: 1 minute Debian_GCC: extends: .Debian @@ -94,6 +100,9 @@ Debian_GCC_tests: variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 + artifacts: + paths: [] + expire_in: 1 minute Debian_GCC_tests_Debug: extends: Debian_GCC @@ -103,6 +112,9 @@ Debian_GCC_tests_Debug: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug + artifacts: + paths: [] + expire_in: 1 minute Debian_GCC_Static_Deps: extends: Debian_GCC @@ -125,6 +137,9 @@ Debian_GCC_Static_Deps_tests: variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 + artifacts: + paths: [] + expire_in: 1 minute Debian_Clang: extends: .Debian @@ -146,6 +161,9 @@ Debian_Clang_tests: variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 + artifacts: + paths: [] + expire_in: 1 minute Debian_Clang_tests_Debug: extends: Debian_Clang @@ -155,6 +173,9 @@ Debian_Clang_tests_Debug: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug + artifacts: + paths: [] + expire_in: 1 minute .MacOS: image: macos-11-xcode-12 @@ -329,6 +350,9 @@ Windows_Ninja_Tests_RelWithDebInfo: config: "RelWithDebInfo" # Gitlab can't successfully execute following binaries due to unknown reason # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" + artifacts: + paths: [] + expire_in: 1 minute .Windows_MSBuild_Base: tags: @@ -453,6 +477,9 @@ Windows_MSBuild_Tests_RelWithDebInfo: config: "RelWithDebInfo" # Gitlab can't successfully execute following binaries due to unknown reason # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" + artifacts: + paths: [] + expire_in: 1 minute Debian_AndroidNDK_arm64-v8a: tags: From 99bfe024ef2f7503be21089b7827d520639f497f Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 5 Dec 2021 18:36:35 +0100 Subject: [PATCH 1822/2859] Cap temp disposition change properly --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f1101d2566..e450e76483 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -718,7 +718,7 @@ namespace MWMechanics if (currentDisposition + tempChange < 0) { cappedDispositionChange = -currentDisposition; - tempChange = 0; + tempChange = cappedDispositionChange; } permChange = floor(cappedDispositionChange / fPerTempMult); From 9d694c2184b0483d55a141cfd19cfb454a8a8c53 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 5 Dec 2021 23:54:08 +0100 Subject: [PATCH 1823/2859] Show the coverity log in the CI --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index da77eccd31..6a87ea20b0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -73,6 +73,7 @@ Coverity: --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL --form file=@cov-int.tar.gz --form version="`git describe --tags`" --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" + - cat /builds/OpenMW/openmw/cov-int/build-log.txt variables: CC: gcc CXX: g++ From 30a351a1181f95be267025e66dddf542004684c1 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 5 Dec 2021 23:57:37 +0100 Subject: [PATCH 1824/2859] Remove the two last badges from the README They're already proudly displayed on gitlab: https://gitlab.com/OpenMW/openmw --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 638801b129..557cd614cd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ OpenMW ====== -[![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![pipeline status](https://gitlab.com/OpenMW/openmw/badges/master/pipeline.svg)](https://gitlab.com/OpenMW/openmw/commits/master) - OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. From 17560683c6e108e6aae972f901657d7b7e56ed0a Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 6 Dec 2021 11:07:53 +0100 Subject: [PATCH 1825/2859] Remove OSX 10.15 This has been broken for a while on gitlab's side. --- .gitlab-ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index da77eccd31..a6355b160e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -212,15 +212,6 @@ macOS11_Xcode12: variables: CCACHE_SIZE: 3G -macOS10.15_Xcode11: - extends: .MacOS - image: macos-10.15-xcode-11 - allow_failure: true - cache: - key: macOS10.15_Xcode11.v1 - variables: - CCACHE_SIZE: 3G - variables: &engine-targets targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard" package: "Engine" From c844e5d0452c77deae3854139350db8efb92a94a Mon Sep 17 00:00:00 2001 From: psi29a Date: Mon, 6 Dec 2021 13:46:56 +0000 Subject: [PATCH 1826/2859] Merge branch 'racer_recursion_limited' into 'master' Check if a leveled creature is in an unloaded cell before deciding it doesn't exist Closes #4376 See merge request OpenMW/openmw!1420 (cherry picked from commit 782371cb2e7f6653d72305090033557b53bbcf3a) 6d945da7 Check if a leveled creature is in an unloaded cell before deciding it doesn't exist --- CHANGELOG.md | 1 + apps/openmw/mwclass/creaturelevlist.cpp | 8 +++++++- apps/openmw/mwworld/cellstore.cpp | 14 ++++++++++++++ apps/openmw/mwworld/cellstore.hpp | 2 ++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d85fbc865..5f5b59c9c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Bug #3855: AI sometimes spams defensive spells Bug #3905: Great House Dagoth issues Bug #4203: Resurrecting an actor should close the loot GUI + Bug #4376: Moved actors don't respawn in their original cells Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: Editor: Incorrect command implementation diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index ddb5fd2b38..ee33242126 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -64,7 +64,13 @@ namespace MWClass if (customData.mSpawn) return; - MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + MWWorld::Ptr creature; + if(customData.mSpawnActorId != -1) + { + creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if(creature.isEmpty()) + creature = ptr.getCell()->getMovedActor(customData.mSpawnActorId); + } if (!creature.isEmpty()) { const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 052cc35b45..0c871e4f58 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1195,4 +1195,18 @@ namespace MWWorld || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) mRechargingItems.emplace_back(ptr.getBase(), static_cast(enchantment->mData.mCharge)); } + + Ptr MWWorld::CellStore::getMovedActor(int actorId) const + { + for(const auto& [cellRef, cell] : mMovedToAnotherCell) + { + if(cellRef->mClass->isActor() && cellRef->mData.getCustomData()) + { + Ptr actor(cellRef, cell); + if(actor.getClass().getCreatureStats(actor).getActorId() == actorId) + return actor; + } + } + return {}; + } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 6e927fbea6..d284a291a5 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -397,6 +397,8 @@ namespace MWWorld void respawn (); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. + Ptr getMovedActor(int actorId) const; + private: /// Run through references and store IDs From 0bbf9a5499ddd9700a56349a6d18a94d2c15f8bc Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 8 Dec 2021 07:53:10 +0000 Subject: [PATCH 1827/2859] Added !1420 --- .resubmitted_merge_requests.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.resubmitted_merge_requests.txt b/.resubmitted_merge_requests.txt index 93dda75f1d..5fd67c190c 100644 --- a/.resubmitted_merge_requests.txt +++ b/.resubmitted_merge_requests.txt @@ -1,4 +1,5 @@ 1450 +1420 1314 1216 1172 From b62b144ec056605ae4a77c637b0d797ba3766b29 Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 8 Dec 2021 09:06:10 +0000 Subject: [PATCH 1828/2859] Switch to Ubuntu Focal for now, until Debian gets latest MyGUI. --- .gitlab-ci.yml | 67 +++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 41830c0b56..538bfa11ec 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,17 +3,17 @@ stages: - build -.Debian_Image: +.Ubuntu_Image: tags: - docker - linux - image: debian:bullseye + image: ubuntu:focal rules: - if: $CI_PIPELINE_SOURCE == "push" -.Debian: - extends: .Debian_Image +.Ubuntu: + extends: .Ubuntu_Image cache: paths: - apt-cache/ @@ -35,11 +35,12 @@ stages: - build/install/ Clang_Tidy: - extends: .Debian_Image + extends: .Ubuntu_Image stage: build rules: - if: '$CI_PIPELINE_SOURCE == "schedule"' before_script: + - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic clang-tidy clang script: - CI/before_script.linux.sh @@ -55,11 +56,12 @@ Clang_Tidy: expire_in: 1 minute Coverity: - extends: .Debian_Image + extends: .Ubuntu_Image stage: build rules: - if: $CI_PIPELINE_SOURCE == "schedule" before_script: + - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz @@ -81,11 +83,12 @@ Coverity: paths: [] expire_in: 1 minute -Debian_GCC: - extends: .Debian +Ubuntu_GCC: + extends: .Ubuntu cache: - key: Debian_GCC.v2 + key: Ubuntu_GCC.v2 before_script: + - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic variables: CC: gcc @@ -94,10 +97,10 @@ Debian_GCC: # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h -Debian_GCC_tests: - extends: Debian_GCC +Ubuntu_GCC_tests: + extends: Ubuntu_GCC cache: - key: Debian_GCC_tests.v2 + key: Ubuntu_GCC_tests.v2 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 @@ -105,10 +108,10 @@ Debian_GCC_tests: paths: [] expire_in: 1 minute -Debian_GCC_tests_Debug: - extends: Debian_GCC +Ubuntu_GCC_tests_Debug: + extends: Ubuntu_GCC cache: - key: Debian_GCC_tests_Debug.v1 + key: Ubuntu_GCC_tests_Debug.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 @@ -117,24 +120,25 @@ Debian_GCC_tests_Debug: paths: [] expire_in: 1 minute -Debian_GCC_Static_Deps: - extends: Debian_GCC +Ubuntu_GCC_Static_Deps: + extends: Ubuntu_GCC cache: - key: Debian_GCC_Static_Deps + key: Ubuntu_GCC_Static_Deps paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: + - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static variables: CI_OPENMW_USE_STATIC_DEPS: 1 timeout: 3h -Debian_GCC_Static_Deps_tests: - extends: Debian_GCC_Static_Deps +Ubuntu_GCC_Static_Deps_tests: + extends: Ubuntu_GCC_Static_Deps cache: - key: Debian_GCC_Static_Deps_tests + key: Ubuntu_GCC_Static_Deps_tests variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 @@ -142,12 +146,13 @@ Debian_GCC_Static_Deps_tests: paths: [] expire_in: 1 minute -Debian_Clang: - extends: .Debian +Ubuntu_Clang: + extends: .Ubuntu before_script: + - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic cache: - key: Debian_Clang.v2 + key: Ubuntu_Clang.v2 variables: CC: clang CXX: clang++ @@ -155,10 +160,10 @@ Debian_Clang: # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h -Debian_Clang_tests: - extends: Debian_Clang +Ubuntu_Clang_tests: + extends: Ubuntu_Clang cache: - key: Debian_Clang_tests.v2 + key: Ubuntu_Clang_tests.v2 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 @@ -166,10 +171,10 @@ Debian_Clang_tests: paths: [] expire_in: 1 minute -Debian_Clang_tests_Debug: - extends: Debian_Clang +Ubuntu_Clang_tests_Debug: + extends: Ubuntu_Clang cache: - key: Debian_Clang_tests_Debug.v1 + key: Ubuntu_Clang_tests_Debug.v1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 @@ -473,7 +478,7 @@ Windows_MSBuild_Tests_RelWithDebInfo: paths: [] expire_in: 1 minute -Debian_AndroidNDK_arm64-v8a: +Ubuntu_AndroidNDK_arm64-v8a: tags: - linux image: psi29a/android-ndk:focal-ndk22 From a9bf53d4ed986212d19ee38f5a0b9022f4b908af Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 7 Dec 2021 11:31:41 +0100 Subject: [PATCH 1829/2859] modified builtin_scripts to be macOS aware; simplified things as the SHADER and MYGUI dir were just pointing to the OpenMW_BINARY_DIR anyway, so removing the code duplication --- CMakeLists.txt | 5 ++--- apps/opencs/CMakeLists.txt | 3 +-- apps/openmw/CMakeLists.txt | 3 +-- files/builtin_scripts/CMakeLists.txt | 24 ++++++++++++++++++------ files/mygui/CMakeLists.txt | 4 ++-- files/shaders/CMakeLists.txt | 4 ++-- files/vfs/CMakeLists.txt | 4 ++-- 7 files changed, 28 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f19b2a54f..d1da593883 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -445,9 +445,8 @@ if (APPLE) "${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY) endif (APPLE) -if (NOT APPLE) - set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR}) - set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR}) +if (NOT APPLE) # this is modified for macOS use later in "apps/open[mw|cs]/CMakeLists.txt" + set(OPENMW_BINARY_ROOT ${OpenMW_BINARY_DIR}) endif () add_subdirectory(files/) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 952bbbdbda..521cf495f7 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -183,8 +183,7 @@ if(APPLE) set(OPENCS_BUNDLE_NAME "OpenMW-CS") set(OPENCS_BUNDLE_RESOURCES_DIR "${OpenMW_BINARY_DIR}/${OPENCS_BUNDLE_NAME}.app/Contents/Resources") - set(OPENMW_MYGUI_FILES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) - set(OPENMW_SHADERS_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) + set(OPENMW_BINARY_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c8ce797587..fc5e6af325 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -179,8 +179,7 @@ endif() if(APPLE) set(BUNDLE_RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources") - set(OPENMW_MYGUI_FILES_ROOT ${BUNDLE_RESOURCES_DIR}) - set(OPENMW_SHADERS_ROOT ${BUNDLE_RESOURCES_DIR}) + set(OPENMW_BINARY_ROOT ${BUNDLE_RESOURCES_DIR}) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 5906680e1b..3e531c680e 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -1,8 +1,20 @@ -file(GLOB_RECURSE BUILTIN_SCRIPTS LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS "*") +if (NOT DEFINED OPENMW_BINARY_ROOT) + return() +endif() -foreach (f ${BUILTIN_SCRIPTS}) - if (NOT ("CMakeLists.txt" STREQUAL "${f}")) - copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OpenMW_BINARY_DIR}" "resources/vfs/${f}") - endif() -endforeach (f) +# Copy resource files into the build directory +set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(DDIRRELATIVE resources/vfs) +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "builtin.omwscripts") +set(DDIRRELATIVE resources/vfs/openmw_aux) +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "openmw_aux/util.lua") + +set(LUA_SCRIPTS_FILES + scripts/omw/camera.lua + scripts/omw/head_bobbing.lua + scripts/omw/third_person.lua +) + +set(DDIRRELATIVE resources/vfs/scripts/omw) +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "${LUA_SCRIPTS_FILES}") diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 49b833e382..cac1691aa3 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT DEFINED OPENMW_MYGUI_FILES_ROOT) +if (NOT DEFINED OPENMW_BINARY_ROOT) return() endif() @@ -97,4 +97,4 @@ set(MYGUI_FILES ) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_MYGUI_FILES_ROOT} ${DDIRRELATIVE} "${MYGUI_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "${MYGUI_FILES}") diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 6e19263a38..f00b52d024 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT DEFINED OPENMW_SHADERS_ROOT) +if (NOT DEFINED OPENMW_BINARY_ROOT) return() endif() @@ -42,4 +42,4 @@ set(SHADER_FILES softparticles.glsl ) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/vfs/CMakeLists.txt b/files/vfs/CMakeLists.txt index a97210d1df..0139138c04 100644 --- a/files/vfs/CMakeLists.txt +++ b/files/vfs/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT DEFINED OPENMW_MYGUI_FILES_ROOT) +if (NOT DEFINED OPENMW_BINARY_ROOT) return() endif() @@ -15,4 +15,4 @@ set(TEXTURE_FILES textures/omw_menu_scroll_center_v.dds ) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_MYGUI_FILES_ROOT} ${DDIRRELATIVE} "${TEXTURE_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "${TEXTURE_FILES}") From 93366269271f13f658a05e7175979387f71683f9 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 8 Dec 2021 14:33:49 +0100 Subject: [PATCH 1830/2859] use OPENMW_RESOURCES_ROOT instead --- CMakeLists.txt | 2 +- apps/opencs/CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 2 +- files/builtin_scripts/CMakeLists.txt | 8 ++++---- files/mygui/CMakeLists.txt | 4 ++-- files/shaders/CMakeLists.txt | 4 ++-- files/vfs/CMakeLists.txt | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1da593883..644a7419a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -446,7 +446,7 @@ if (APPLE) endif (APPLE) if (NOT APPLE) # this is modified for macOS use later in "apps/open[mw|cs]/CMakeLists.txt" - set(OPENMW_BINARY_ROOT ${OpenMW_BINARY_DIR}) + set(OPENMW_RESOURCES_ROOT ${OpenMW_BINARY_DIR}) endif () add_subdirectory(files/) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 521cf495f7..b3d405d1a3 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -183,7 +183,7 @@ if(APPLE) set(OPENCS_BUNDLE_NAME "OpenMW-CS") set(OPENCS_BUNDLE_RESOURCES_DIR "${OpenMW_BINARY_DIR}/${OPENCS_BUNDLE_NAME}.app/Contents/Resources") - set(OPENMW_BINARY_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) + set(OPENMW_RESOURCES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index fc5e6af325..94babb6ab1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -179,7 +179,7 @@ endif() if(APPLE) set(BUNDLE_RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources") - set(OPENMW_BINARY_ROOT ${BUNDLE_RESOURCES_DIR}) + set(OPENMW_RESOURCES_ROOT ${BUNDLE_RESOURCES_DIR}) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 3e531c680e..6f290cc1f7 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -1,14 +1,14 @@ -if (NOT DEFINED OPENMW_BINARY_ROOT) +if (NOT DEFINED OPENMW_RESOURCES_ROOT) return() endif() # Copy resource files into the build directory set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIRRELATIVE resources/vfs) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "builtin.omwscripts") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "builtin.omwscripts") set(DDIRRELATIVE resources/vfs/openmw_aux) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "openmw_aux/util.lua") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "openmw_aux/util.lua") set(LUA_SCRIPTS_FILES scripts/omw/camera.lua @@ -17,4 +17,4 @@ set(LUA_SCRIPTS_FILES ) set(DDIRRELATIVE resources/vfs/scripts/omw) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "${LUA_SCRIPTS_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${LUA_SCRIPTS_FILES}") diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index cac1691aa3..bdf7558cf7 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT DEFINED OPENMW_BINARY_ROOT) +if (NOT DEFINED OPENMW_RESOURCES_ROOT) return() endif() @@ -97,4 +97,4 @@ set(MYGUI_FILES ) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "${MYGUI_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${MYGUI_FILES}") diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index f00b52d024..73929486cd 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT DEFINED OPENMW_BINARY_ROOT) +if (NOT DEFINED OPENMW_RESOURCES_ROOT) return() endif() @@ -42,4 +42,4 @@ set(SHADER_FILES softparticles.glsl ) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/vfs/CMakeLists.txt b/files/vfs/CMakeLists.txt index 0139138c04..15dcb80ec1 100644 --- a/files/vfs/CMakeLists.txt +++ b/files/vfs/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT DEFINED OPENMW_BINARY_ROOT) +if (NOT DEFINED OPENMW_RESOURCES_ROOT) return() endif() @@ -15,4 +15,4 @@ set(TEXTURE_FILES textures/omw_menu_scroll_center_v.dds ) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_BINARY_ROOT} ${DDIRRELATIVE} "${TEXTURE_FILES}") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${TEXTURE_FILES}") From 4b67fe5351f9037e2e2f49bad4ff85b25d4412ad Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 8 Dec 2021 17:18:08 +0100 Subject: [PATCH 1831/2859] Actually remove effects before proclaiming they've been removed --- apps/openmw/mwmechanics/spelleffects.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index d41cf82a9c..3b352a710b 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -796,12 +796,16 @@ MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorl { if(target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); + onMagicEffectRemoved(target, spellParams, effect); return MagicApplicationResult::REMOVED; } else if(effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention || effect.mEffectId == ESM::MagicEffect::DivineIntervention || effect.mEffectId == ESM::MagicEffect::Recall) { if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) + { + onMagicEffectRemoved(target, spellParams, effect); return MagicApplicationResult::REMOVED; + } } const auto* magicEffect = world->getStore().get().find(effect.mEffectId); if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) From 5129ab39fcd7bde0961a4b5fb51822aa336efef4 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 9 Dec 2021 00:06:28 +0100 Subject: [PATCH 1832/2859] Check extension in lower case --- components/esmloader/load.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp index cc3e011d3d..842cc8003a 100644 --- a/components/esmloader/load.cpp +++ b/components/esmloader/load.cpp @@ -218,7 +218,7 @@ namespace EsmLoader for (std::size_t i = 0; i < contentFiles.size(); ++i) { const std::string &file = contentFiles[i]; - const std::string extension = boost::filesystem::path(file).extension().string(); + const std::string extension = Misc::StringUtils::lowerCase(boost::filesystem::path(file).extension().string()); if (supportedFormats.find(extension) == supportedFormats.end()) { From fd025f69911ebe1f372d862f619d7318b7d15fcc Mon Sep 17 00:00:00 2001 From: Thomas Lowe Date: Wed, 8 Dec 2021 18:55:31 -0500 Subject: [PATCH 1833/2859] Added my name to authors list! --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 8ca9db90b9..8ed88a8c22 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -215,6 +215,7 @@ Programmers tlmullis tri4ng1e Thoronador + Tom Lowe (Vulpen) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) unelsson From 221e425fe664a6981b3b37ee8a303d0b9c1ac354 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 9 Dec 2021 14:59:17 +0000 Subject: [PATCH 1834/2859] Only precompile headers with MSVC --- apps/openmw/CMakeLists.txt | 2 +- components/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 94babb6ab1..b54ecb416c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -155,7 +155,7 @@ target_link_libraries(openmw components ) -if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16) +if (MSVC AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.16) target_precompile_headers(openmw PRIVATE ${SOL_INCLUDE_DIR}/sol/sol.hpp) endif () diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index cea215fe42..e980eb5532 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -373,6 +373,6 @@ if(USE_QT) set_property(TARGET components_qt PROPERTY AUTOMOC ON) endif(USE_QT) -if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16) +if (MSVC AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.16) target_precompile_headers(components PRIVATE ${SOL_INCLUDE_DIR}/sol/sol.hpp) endif () From 810ad9d3fbc1be23f879f3cf79d99ba35dafd8fa Mon Sep 17 00:00:00 2001 From: jvoisin Date: Thu, 9 Dec 2021 20:05:33 +0100 Subject: [PATCH 1835/2859] Silence apt-get install --- CI/install_debian_deps.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index a8843207d9..370f7f23ed 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -64,5 +64,5 @@ done export APT_CACHE_DIR="${PWD}/apt-cache" set -x mkdir -pv "$APT_CACHE_DIR" -apt-get update -yq -apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" +apt-get update -yqq +apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" From f007025cedcfd1b24a3cb8d2c14d063c58035a6c Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 9 Dec 2021 21:05:55 +0100 Subject: [PATCH 1836/2859] Add job ID to discord notification To easily track down where it comes from. --- scripts/find_missing_merge_requests.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/find_missing_merge_requests.py b/scripts/find_missing_merge_requests.py index be54750098..1cca15c03b 100755 --- a/scripts/find_missing_merge_requests.py +++ b/scripts/find_missing_merge_requests.py @@ -14,6 +14,8 @@ import urllib.parse help='Path to text file with Gitlab token.') @click.option('--project_id', type=int, default=7107382, help='Gitlab project id.') +@click.option('--job_id', type=int, default=os.getenv('CI_JOB_ID'), + help='Gitlab job id.') @click.option('--host', type=str, default='gitlab.com', help='Gitlab host.') @click.option('--workers', type=int, default=10, @@ -28,7 +30,7 @@ import urllib.parse help='Number of merge requests per page.') @click.option('--ignored_mrs_path', type=str, help='Path to a list of ignored MRs.') -def main(token_path, project_id, host, workers, target_branch, begin_page, end_page, per_page, ignored_mrs_path): +def main(token_path, project_id, job_id, host, workers, target_branch, begin_page, end_page, per_page, ignored_mrs_path): headers = make_headers(token_path) base_url = f'https://{host}/api/v4/projects/{project_id}/' discord_webhook_url = os.getenv('DISCORD_WEBHOOK_URL') @@ -67,7 +69,8 @@ def main(token_path, project_id, host, workers, target_branch, begin_page, end_p if discord_webhook_url is not None and missing_mrs: project_web_url = parse_gitlab_response(requests.get(url=base_url, headers=headers))['web_url'] + '/' discord_message = format_discord_message(missing=missing, filtered=filtered, target_branch=target_branch, - project_web_url=project_web_url, missing_mrs=missing_mrs) + project_web_url=project_web_url, missing_mrs=missing_mrs, + job_id=job_id) print('Sending Discord notification...') print(discord_message) discord_webhook.DiscordWebhook(url=discord_webhook_url, content=discord_message, rate_limit_retry=True).execute() @@ -75,10 +78,11 @@ def main(token_path, project_id, host, workers, target_branch, begin_page, end_p exit(-1) -def format_discord_message(missing, filtered, target_branch, project_web_url, missing_mrs): +def format_discord_message(missing, filtered, target_branch, project_web_url, missing_mrs, job_id): target_branch = format_link(target_branch, urllib.parse.urljoin(project_web_url, f'-/tree/{target_branch}')) + job = f' by job ' + format_link(job_id, urllib.parse.urljoin(project_web_url, f'-/jobs/{job_id}')) if job_id else '' return ( - f'Found {missing} missing MRs out of {filtered} from {target_branch} target branch:\n' + f'Found {missing} missing MRs out of {filtered} from {target_branch} target branch{job}:\n' + '\n'.join(format_missing_mr_message(v, project_web_url) for v in missing_mrs) ) From 9257c27fe124e1edb5fdf08dc71cb48799e3de3a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 10 Dec 2021 18:51:37 +0100 Subject: [PATCH 1837/2859] Move target checking code so it applies to all sources --- apps/openmw/mwmechanics/spellcasting.cpp | 3 -- apps/openmw/mwmechanics/spelleffects.cpp | 60 +++++++++++++++++++----- apps/openmw/mwmechanics/spellutil.cpp | 44 ----------------- apps/openmw/mwmechanics/spellutil.hpp | 3 -- 4 files changed, 47 insertions(+), 63 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 2edc437752..c99eee43d5 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -128,9 +128,6 @@ namespace MWMechanics } canCastAnEffect = true; - if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer)) - continue; - // caster needs to be an actor for linked effects (e.g. Absorb) if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked && (caster.isEmpty() || !caster.getClass().isActor())) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 3b352a710b..aef6b26f19 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -774,6 +774,45 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co } } +bool shouldRemoveEffect(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect) +{ + const auto world = MWBase::Environment::get().getWorld(); + switch(effect.mEffectId) + { + case ESM::MagicEffect::Levitate: + { + if(!world->isLevitationEnabled()) + { + if(target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + return true; + } + break; + } + case ESM::MagicEffect::Recall: + case ESM::MagicEffect::DivineIntervention: + case ESM::MagicEffect::AlmsiviIntervention: + { + return effect.mFlags & ESM::ActiveEffect::Flag_Applied; + } + case ESM::MagicEffect::WaterWalking: + { + if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target)) + return true; + if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) + break; + if (!world->isWaterWalkingCastableOnTarget(target)) + { + if(target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}"); + return true; + } + break; + } + } + return false; +} + MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) { const auto world = MWBase::Environment::get().getWorld(); @@ -792,21 +831,11 @@ MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorl MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); return MagicApplicationResult::APPLIED; } - else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled()) + else if(shouldRemoveEffect(target, effect)) { - if(target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); onMagicEffectRemoved(target, spellParams, effect); return MagicApplicationResult::REMOVED; } - else if(effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention || effect.mEffectId == ESM::MagicEffect::DivineIntervention || effect.mEffectId == ESM::MagicEffect::Recall) - { - if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) - { - onMagicEffectRemoved(target, spellParams, effect); - return MagicApplicationResult::REMOVED; - } - } const auto* magicEffect = world->getStore().get().find(effect.mEffectId); if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) { @@ -883,8 +912,13 @@ MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorl float oldMagnitude = 0.f; if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) oldMagnitude = effect.mMagnitude; - else if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) - playEffects(target, *magicEffect); + else + { + if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + playEffects(target, *magicEffect); + if(effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0 && caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); + } float magnitude = roll(effect); //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here effect.mMagnitude = magnitude; diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 70167abcd3..3c4f094be4 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -199,48 +199,4 @@ namespace MWMechanics const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); return spell && spellIncreasesSkill(spell); } - - bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer) - { - switch (effectId) - { - case ESM::MagicEffect::Levitate: - { - if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); - return false; - } - break; - } - case ESM::MagicEffect::Soultrap: - { - if (!target.getClass().isNpc() // no messagebox for NPCs - && (target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0)) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); - return true; // must still apply to get visual effect and have target regard it as attack - } - break; - } - case ESM::MagicEffect::WaterWalking: - { - if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target)) - return false; - - MWBase::World *world = MWBase::Environment::get().getWorld(); - - if (!world->isWaterWalkingCastableOnTarget(target)) - { - if (castByPlayer && caster == target) - MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}"); - return false; - } - break; - } - } - return true; - } } diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index 81f39b6dda..bcc531087c 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -48,9 +48,6 @@ namespace MWMechanics /// Get whether or not the given spell contributes to skill progress. bool spellIncreasesSkill(const ESM::Spell* spell); bool spellIncreasesSkill(const std::string& spellId); - - /// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure. - bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); } #endif From 5a6b39f8e054bf9280cf4300f6271d700b77bf64 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 4 Nov 2021 01:57:27 +0100 Subject: [PATCH 1838/2859] Store mesh source data in recast mesh --- .../detournavigator/navmeshtilescache.cpp | 2 +- apps/openmw/mwphysics/ptrholder.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 25 +++++++---- apps/openmw/mwworld/worldimp.cpp | 4 +- .../detournavigator/navigator.cpp | 37 +++++++++-------- .../detournavigator/navmeshtilescache.cpp | 41 ++++++++++--------- .../detournavigator/recastmeshbuilder.cpp | 35 +++++++++------- .../detournavigator/recastmeshobject.cpp | 8 ++-- .../tilecachedrecastmeshmanager.cpp | 41 ++++++++++--------- components/CMakeLists.txt | 1 + components/detournavigator/areatype.hpp | 15 +++++++ components/detournavigator/navigator.hpp | 13 ++++-- components/detournavigator/navigatorimpl.cpp | 8 ++-- .../detournavigator/objecttransform.hpp | 27 ++++++++++++ components/detournavigator/recastmesh.cpp | 4 +- components/detournavigator/recastmesh.hpp | 15 ++++++- .../detournavigator/recastmeshbuilder.cpp | 10 ++++- .../detournavigator/recastmeshbuilder.hpp | 8 +++- .../detournavigator/recastmeshmanager.cpp | 10 +++-- .../detournavigator/recastmeshobject.cpp | 3 +- .../detournavigator/recastmeshobject.hpp | 22 +++++++--- components/esm/defs.hpp | 8 ++++ components/misc/convert.hpp | 5 +++ 23 files changed, 236 insertions(+), 108 deletions(-) create mode 100644 components/detournavigator/objecttransform.hpp diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 9d41b62597..c27fdc4289 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -145,7 +145,7 @@ namespace std::vector water; generateWater(std::back_inserter(water), 1, random); RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water), - {generateHeightfield(random)}, {generateFlatHeightfield(random)}); + {generateHeightfield(random)}, {generateFlatHeightfield(random)}, {}); return Key {agentHalfExtents, tilePosition, std::move(recastMesh)}; } diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index fcd6ce203a..e194f8e934 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -20,7 +20,7 @@ namespace MWPhysics mPtr = updated; } - MWWorld::Ptr getPtr() + MWWorld::Ptr getPtr() const { return mPtr; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 15b3477cb8..c3db62b5a9 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -64,14 +64,23 @@ namespace * osg::Quat(zr, osg::Vec3(0, 0, -1)); } - osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) + osg::Quat makeInverseNodeRotation(const MWWorld::Ptr& ptr) { const auto pos = ptr.getRefData().getPosition(); + return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : makeInversedOrderObjectOsgQuat(pos); + } - const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) - : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos)); + osg::Quat makeDirectNodeRotation(const MWWorld::Ptr& ptr) + { + const auto pos = ptr.getRefData().getPosition(); + return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos); + } - return rot; + osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) + { + if (order == RotationOrder::inverse) + return makeInverseNodeRotation(ptr); + return makeDirectNodeRotation(ptr); } void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, const osg::Quat &rotation) @@ -101,7 +110,7 @@ namespace } std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + const auto rotation = makeDirectNodeRotation(ptr); const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) @@ -128,6 +137,8 @@ namespace { if (const auto object = physics.getObject(ptr)) { + const DetourNavigator::ObjectTransform objectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()}; + if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport()) { btVector3 aabbMin; @@ -159,7 +170,7 @@ namespace navigator.addObject( DetourNavigator::ObjectId(object), - DetourNavigator::DoorShapes(object->getShapeInstance(), connectionStart, connectionEnd), + DetourNavigator::DoorShapes(object->getShapeInstance(), objectTransform, connectionStart, connectionEnd), transform ); } @@ -167,7 +178,7 @@ namespace { navigator.addObject( DetourNavigator::ObjectId(object), - DetourNavigator::ObjectShapes(object->getShapeInstance()), + DetourNavigator::ObjectShapes(object->getShapeInstance(), objectTransform), object->getTransform() ); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f0ac894cfc..d08eef2904 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1533,7 +1533,9 @@ namespace MWWorld void World::updateNavigatorObject(const MWPhysics::Object& object) { - const DetourNavigator::ObjectShapes shapes(object.getShapeInstance()); + const MWWorld::Ptr ptr = object.getPtr(); + const DetourNavigator::ObjectShapes shapes(object.getShapeInstance(), + DetourNavigator::ObjectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()}); mShouldUpdateNavigator = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform()) || mShouldUpdateNavigator; } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 4e2b7758ff..07dcbb2002 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -49,6 +49,7 @@ namespace const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); const float mEndTolerance = 0; const btTransform mTransform {btMatrix3x3::getIdentity(), btVector3(256, 256, 0)}; + const ObjectTransform mObjectTransform {ESM::Position {{256, 256, 0}, {0, 0, 0}}, 0.0f}; DetourNavigatorNavigatorTest() : mPlayerPosition(256, 256, 0) @@ -258,7 +259,7 @@ namespace Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -311,7 +312,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -346,7 +347,7 @@ namespace compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); - mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -404,8 +405,8 @@ namespace heightfield2.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance()), mTransform); - mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance()), mTransform); + mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance(), mObjectTransform), mTransform); + mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -494,7 +495,7 @@ namespace osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance), mTransform); + mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance, mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -725,7 +726,7 @@ namespace heightfield.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), mTransform); + mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -733,7 +734,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), mTransform); + mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -876,7 +877,7 @@ namespace for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10, shift.y() + i * 10, i * 10)); - mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); + mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform); } std::this_thread::sleep_for(std::chrono::microseconds(1)); @@ -884,7 +885,7 @@ namespace for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10 + 1, shift.y() + i * 10 + 1, i * 10 + 1)); - mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); + mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); @@ -930,7 +931,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); - mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); + mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -939,7 +940,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1)); - mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); + mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -947,7 +948,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2)); - mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); + mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1001,10 +1002,10 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, surface); - mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), + mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations - mNavigator->addObject(ObjectId(&borderBox.shape()), ObjectShapes(borderBox.instance()), + mNavigator->addObject(ObjectId(&borderBox.shape()), ObjectShapes(borderBox.instance(), mObjectTransform), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1019,7 +1020,7 @@ namespace { const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10), oscillatingBoxShapePosition); - mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), transform); + mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), transform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); } @@ -1085,7 +1086,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1124,7 +1125,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index c56be440e3..2a7c5ac5a1 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -147,7 +147,8 @@ namespace const std::vector mWater {}; const std::vector mHeightfields {}; const std::vector mFlatHeightfields {}; - const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields}; + const std::vector mSources {}; + const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields, mSources}; std::unique_ptr mPreparedNavMeshData {makePeparedNavMeshData(3)}; const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh); @@ -235,7 +236,7 @@ namespace const std::size_t maxSize = 1; NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh unexistentRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh)); @@ -247,7 +248,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); const auto copy = clone(*anotherPreparedNavMeshData); @@ -265,7 +266,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, @@ -281,13 +282,13 @@ namespace const auto copy = clone(*mPreparedNavMeshData); const std::vector leastRecentlySetWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mMesh, leastRecentlySetWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh leastRecentlySetRecastMesh(mGeneration, mRevision, mMesh, leastRecentlySetWater, + mHeightfields, mFlatHeightfields, mSources); auto leastRecentlySetData = makePeparedNavMeshData(3); const std::vector mostRecentlySetWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); - const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mMesh, mostRecentlySetWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh mostRecentlySetRecastMesh(mGeneration, mRevision, mMesh, mostRecentlySetWater, + mHeightfields, mFlatHeightfields, mSources); auto mostRecentlySetData = makePeparedNavMeshData(3); ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, @@ -309,14 +310,14 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector leastRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, leastRecentlyUsedWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh leastRecentlyUsedRecastMesh(mGeneration, mRevision, mMesh, leastRecentlyUsedWater, + mHeightfields, mFlatHeightfields, mSources); auto leastRecentlyUsedData = makePeparedNavMeshData(3); const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData); const std::vector mostRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); - const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, mostRecentlyUsedWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh mostRecentlyUsedRecastMesh(mGeneration, mRevision, mMesh, mostRecentlyUsedWater, + mHeightfields, mFlatHeightfields, mSources); auto mostRecentlyUsedData = makePeparedNavMeshData(3); const auto mostRecentlyUsedCopy = clone(*mostRecentlyUsedData); @@ -350,8 +351,8 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, water, - mHeightfields, mFlatHeightfields}; + const RecastMesh tooLargeRecastMesh(mGeneration, mRevision, mMesh, water, + mHeightfields, mFlatHeightfields, mSources); auto tooLargeData = makePeparedNavMeshData(10); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); @@ -365,13 +366,13 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector anotherWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, anotherWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, anotherWater, + mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); const std::vector tooLargeWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); - const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, tooLargeWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh tooLargeRecastMesh(mGeneration, mRevision, mMesh, tooLargeWater, + mHeightfields, mFlatHeightfields, mSources); auto tooLargeData = makePeparedNavMeshData(10); const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, @@ -391,7 +392,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); @@ -410,7 +411,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index ad28bf4053..39e57234a3 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -89,6 +89,8 @@ namespace TileBounds mBounds; const std::size_t mGeneration = 0; const std::size_t mRevision = 0; + const osg::ref_ptr mSource {nullptr}; + const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; DetourNavigatorRecastMeshBuilderTest() { @@ -115,7 +117,8 @@ namespace btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + builder.addObject(static_cast(shape), btTransform::getIdentity(), + AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, 0, @@ -135,7 +138,7 @@ namespace builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -152,7 +155,8 @@ namespace const std::array heightfieldData {{0, 0, 0, 0}}; btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); RecastMeshBuilder builder(mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + builder.addObject(static_cast(shape), btTransform::getIdentity(), + AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -0.5, -0.5, 0, @@ -168,7 +172,8 @@ namespace { btBoxShape shape(btVector3(1, 1, 2)); RecastMeshBuilder builder(mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + builder.addObject(static_cast(shape), btTransform::getIdentity(), + AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, -2, @@ -214,7 +219,7 @@ namespace builder.addObject( static_cast(shape), btTransform::getIdentity(), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -261,7 +266,7 @@ namespace builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -285,7 +290,7 @@ namespace builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -307,7 +312,7 @@ namespace builder.addObject( static_cast(shape), btTransform::getIdentity(), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -334,7 +339,7 @@ namespace builder.addObject( static_cast(shape), btTransform::getIdentity(), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -359,7 +364,7 @@ namespace static_cast(shape), btTransform(btQuaternion(btVector3(1, 0, 0), static_cast(-osg::PI_4))), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector({ @@ -384,7 +389,7 @@ namespace static_cast(shape), btTransform(btQuaternion(btVector3(0, 1, 0), static_cast(osg::PI_4))), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector({ @@ -409,7 +414,7 @@ namespace static_cast(shape), btTransform(btQuaternion(btVector3(0, 0, 1), static_cast(osg::PI_4))), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector({ @@ -433,12 +438,12 @@ namespace builder.addObject( static_cast(shape1), btTransform::getIdentity(), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); builder.addObject( static_cast(shape2), btTransform::getIdentity(), - AreaType_null + AreaType_null, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -471,7 +476,7 @@ namespace btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, 0, diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index 7751d5220c..ff0d3e519c 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -1,6 +1,7 @@ #include "operators.hpp" #include +#include #include #include @@ -15,10 +16,11 @@ namespace struct DetourNavigatorRecastMeshObjectTest : Test { btBoxShape mBoxShapeImpl {btVector3(1, 2, 3)}; - CollisionShape mBoxShape {nullptr, mBoxShapeImpl}; + const ObjectTransform mObjectTransform {ESM::Position {{1, 2, 3}, {1, 2, 3}}, 0.5f}; + CollisionShape mBoxShape {nullptr, mBoxShapeImpl, mObjectTransform}; btCompoundShape mCompoundShapeImpl {true}; - CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl}; - btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; + CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl, mObjectTransform}; + btTransform mTransform {Misc::Convert::makeBulletTransform(mObjectTransform.mPosition)}; DetourNavigatorRecastMeshObjectTest() { diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 3667e946e0..eac7f4abbd 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -17,6 +17,9 @@ namespace { Settings mSettings; std::vector mChangedTiles; + const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; + const osg::ref_ptr mShape = new Resource::BulletShape; + const osg::ref_ptr mInstance = new Resource::BulletShapeInstance(mShape); DetourNavigatorTileCachedRecastMeshManagerTest() { @@ -56,7 +59,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } @@ -64,7 +67,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } @@ -73,7 +76,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) @@ -85,7 +88,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); @@ -100,7 +103,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); @@ -111,7 +114,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); @@ -123,7 +126,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); } @@ -133,7 +136,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); @@ -153,7 +156,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); @@ -168,7 +171,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); @@ -181,7 +184,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); @@ -201,7 +204,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const auto initialRevision = manager.getRevision(); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getRevision(), initialRevision + 1); } @@ -210,7 +213,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeAddRevision = manager.getRevision(); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); @@ -222,7 +225,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); @@ -233,7 +236,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); @@ -244,7 +247,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeRemoveRevision = manager.getRevision(); manager.removeObject(ObjectId(&boxShape)); @@ -282,7 +285,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = std::numeric_limits::max(); @@ -325,7 +328,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; @@ -342,7 +345,7 @@ namespace const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index dd1ac36d91..b145f61332 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -203,6 +203,7 @@ add_component_dir(detournavigator preparednavmeshdata navmeshcacheitem navigatorutils + generatenavmeshtile ) add_component_dir(loadinglistener diff --git a/components/detournavigator/areatype.hpp b/components/detournavigator/areatype.hpp index 9d99421af0..125b7ed7b9 100644 --- a/components/detournavigator/areatype.hpp +++ b/components/detournavigator/areatype.hpp @@ -3,6 +3,8 @@ #include +#include + namespace DetourNavigator { enum AreaType : unsigned char @@ -21,6 +23,19 @@ namespace DetourNavigator float mPathgrid = 1.0f; float mGround = 1.0f; }; + + inline std::ostream& operator<<(std::ostream& stream, AreaType value) + { + switch (value) + { + case AreaType_null: return stream << "null"; + case AreaType_water: return stream << "water"; + case AreaType_door: return stream << "door"; + case AreaType_pathgrid: return stream << "pathgrid"; + case AreaType_ground: return stream << "ground"; + } + return stream << "unknown area type (" << static_cast>(value) << ")"; + } } #endif diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 6070a19fa8..9ffe062fe5 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -6,6 +6,7 @@ #include "recastmeshtiles.hpp" #include "waitconditiontype.hpp" #include "heightfieldshape.hpp" +#include "objecttransform.hpp" #include @@ -27,10 +28,14 @@ namespace DetourNavigator struct ObjectShapes { osg::ref_ptr mShapeInstance; + ObjectTransform mTransform; - ObjectShapes(const osg::ref_ptr& shapeInstance) + ObjectShapes(const osg::ref_ptr& shapeInstance, const ObjectTransform& transform) : mShapeInstance(shapeInstance) - {} + , mTransform(transform) + { + assert(mShapeInstance != nullptr); + } }; struct DoorShapes : ObjectShapes @@ -39,8 +44,8 @@ namespace DetourNavigator osg::Vec3f mConnectionEnd; DoorShapes(const osg::ref_ptr& shapeInstance, - const osg::Vec3f& connectionStart,const osg::Vec3f& connectionEnd) - : ObjectShapes(shapeInstance) + const ObjectTransform& transform, const osg::Vec3f& connectionStart, const osg::Vec3f& connectionEnd) + : ObjectShapes(shapeInstance, transform) , mConnectionStart(connectionStart) , mConnectionEnd(connectionEnd) {} diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index f209a24c2c..9092624ca3 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -34,12 +34,12 @@ namespace DetourNavigator bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape}; + const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); - CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; + const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); if (mNavMeshManager.addObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); @@ -64,12 +64,12 @@ namespace DetourNavigator bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - const CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape}; + const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); bool result = mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); - const CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; + const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); if (mNavMeshManager.updateObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); diff --git a/components/detournavigator/objecttransform.hpp b/components/detournavigator/objecttransform.hpp new file mode 100644 index 0000000000..2da9a25348 --- /dev/null +++ b/components/detournavigator/objecttransform.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H + +#include + +#include + +namespace DetourNavigator +{ + struct ObjectTransform + { + ESM::Position mPosition; + float mScale; + + friend inline auto tie(const ObjectTransform& v) + { + return std::tie(v.mPosition, v.mScale); + } + + friend inline bool operator<(const ObjectTransform& l, const ObjectTransform& r) + { + return tie(l) < tie(r); + } + }; +} + +#endif diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index 93a0e171a1..16220d74f1 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -19,13 +19,15 @@ namespace DetourNavigator } RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, - std::vector heightfields, std::vector flatHeightfields) + std::vector heightfields, std::vector flatHeightfields, + std::vector meshSources) : mGeneration(generation) , mRevision(revision) , mMesh(std::move(mesh)) , mWater(std::move(water)) , mHeightfields(std::move(heightfields)) , mFlatHeightfields(std::move(flatHeightfields)) + , mMeshSources(std::move(meshSources)) { mWater.shrink_to_fit(); mHeightfields.shrink_to_fit(); diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 3cfe9e1cab..df9d6414d5 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -4,8 +4,10 @@ #include "areatype.hpp" #include "bounds.hpp" #include "tilebounds.hpp" +#include "objecttransform.hpp" #include +#include #include #include @@ -119,11 +121,19 @@ namespace DetourNavigator return tie(lhs) < tie(rhs); } + struct MeshSource + { + osg::ref_ptr mShape; + ObjectTransform mObjectTransform; + AreaType mAreaType; + }; + class RecastMesh { public: RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, - std::vector heightfields, std::vector flatHeightfields); + std::vector heightfields, std::vector flatHeightfields, + std::vector sources); std::size_t getGeneration() const { @@ -152,6 +162,8 @@ namespace DetourNavigator return mFlatHeightfields; } + const std::vector& getMeshSources() const noexcept { return mMeshSources; } + private: std::size_t mGeneration; std::size_t mRevision; @@ -159,6 +171,7 @@ namespace DetourNavigator std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; + std::vector mMeshSources; friend inline std::size_t getSize(const RecastMesh& value) noexcept { diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index b54b927696..0f7552aa77 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -133,6 +133,13 @@ namespace DetourNavigator { } + void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform, + const AreaType areaType, osg::ref_ptr source, const ObjectTransform& objectTransform) + { + addObject(shape, transform, areaType); + mSources.push_back(MeshSource {std::move(source), objectTransform, areaType}); + } + void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { @@ -261,7 +268,8 @@ namespace DetourNavigator std::sort(mWater.begin(), mWater.end()); Mesh mesh = makeMesh(std::move(mTriangles)); return std::make_shared(generation, revision, std::move(mesh), std::move(mWater), - std::move(mHeightfields), std::move(mFlatHeightfields)); + std::move(mHeightfields), std::move(mFlatHeightfields), + std::move(mSources)); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 4bdcb788e3..d0848c2a45 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -4,6 +4,8 @@ #include "recastmesh.hpp" #include "tilebounds.hpp" +#include + #include #include @@ -38,7 +40,8 @@ namespace DetourNavigator public: explicit RecastMeshBuilder(const TileBounds& bounds) noexcept; - void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); + void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType, + osg::ref_ptr source, const ObjectTransform& objectTransform); void addObject(const btCompoundShape& shape, const btTransform& transform, const AreaType areaType); @@ -63,6 +66,9 @@ namespace DetourNavigator std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; + std::vector mSources; + + inline void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 4772ec74a9..a7b24766fc 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -122,7 +122,8 @@ namespace DetourNavigator { RecastMeshBuilder builder(mTileBounds); using Object = std::tuple< - osg::ref_ptr, + osg::ref_ptr, + ObjectTransform, std::reference_wrapper, btTransform, AreaType @@ -139,12 +140,13 @@ namespace DetourNavigator for (const auto& [k, object] : mObjects) { const RecastMeshObject& impl = object.getImpl(); - objects.emplace_back(impl.getHolder(), impl.getShape(), impl.getTransform(), impl.getAreaType()); + objects.emplace_back(impl.getInstance(), impl.getObjectTransform(), impl.getShape(), + impl.getTransform(), impl.getAreaType()); } revision = mRevision; } - for (const auto& [holder, shape, transform, areaType] : objects) - builder.addObject(shape, transform, areaType); + for (const auto& [instance, objectTransform, shape, transform, areaType] : objects) + builder.addObject(shape, transform, areaType, instance->getSource(), objectTransform); return std::move(builder).create(mGeneration, revision); } diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 31aa13a208..343aeeb39e 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -75,7 +75,8 @@ namespace DetourNavigator RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType) - : mHolder(shape.getHolder()) + : mInstance(shape.getInstance()) + , mObjectTransform(shape.getObjectTransform()) , mImpl(shape.getShape(), transform, areaType) { } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index e833ee37e3..760774353c 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -2,6 +2,9 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H #include "areatype.hpp" +#include "objecttransform.hpp" + +#include #include @@ -19,17 +22,21 @@ namespace DetourNavigator class CollisionShape { public: - CollisionShape(osg::ref_ptr holder, const btCollisionShape& shape) - : mHolder(std::move(holder)) + CollisionShape(osg::ref_ptr instance, const btCollisionShape& shape, + const ObjectTransform& transform) + : mInstance(std::move(instance)) , mShape(shape) + , mObjectTransform(transform) {} - const osg::ref_ptr& getHolder() const { return mHolder; } + const osg::ref_ptr& getInstance() const { return mInstance; } const btCollisionShape& getShape() const { return mShape; } + const ObjectTransform& getObjectTransform() const { return mObjectTransform; } private: - osg::ref_ptr mHolder; + osg::ref_ptr mInstance; std::reference_wrapper mShape; + ObjectTransform mObjectTransform; }; class ChildRecastMeshObject @@ -60,7 +67,7 @@ namespace DetourNavigator bool update(const btTransform& transform, const AreaType areaType) { return mImpl.update(transform, areaType); } - const osg::ref_ptr& getHolder() const { return mHolder; } + const osg::ref_ptr& getInstance() const { return mInstance; } const btCollisionShape& getShape() const { return mImpl.getShape(); } @@ -68,8 +75,11 @@ namespace DetourNavigator AreaType getAreaType() const { return mImpl.getAreaType(); } + const ObjectTransform& getObjectTransform() const { return mObjectTransform; } + private: - osg::ref_ptr mHolder; + osg::ref_ptr mInstance; + ObjectTransform mObjectTransform; ChildRecastMeshObject mImpl; }; } diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 254e66ec3a..e793c6fea7 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -3,6 +3,8 @@ #include +#include + #include namespace ESM @@ -59,6 +61,12 @@ struct Position { 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); + } }; #pragma pack(pop) diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index 6f4a55cfcc..45f3504dc1 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -67,6 +67,11 @@ namespace Convert { return makeBulletQuaternion(position.rot); } + + inline btTransform makeBulletTransform(const ESM::Position& position) + { + return btTransform(makeBulletQuaternion(position), toBullet(position.asVec3())); + } } } From 33bb18850df8c7a085a50a598fa65baaf0a4e236 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Nov 2021 23:48:48 +0100 Subject: [PATCH 1839/2859] Move operator<< to debug.hpp --- .../detournavigator/recastmeshbuilder.cpp | 29 ------------------ components/detournavigator/debug.hpp | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index 39e57234a3..36e0287461 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -48,35 +48,6 @@ namespace DetourNavigator }; return tie(lhs) == tie(rhs); } - - static inline std::ostream& operator<<(std::ostream& s, const Water& v) - { - return s << "Water {" << v.mCellSize << ", " << v.mLevel << "}"; - } - - static inline std::ostream& operator<<(std::ostream& s, const CellWater& v) - { - return s << "CellWater {" << v.mCellPosition << ", " << v.mWater << "}"; - } - - static inline std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v) - { - return s << "FlatHeightfield {" << v.mCellPosition << ", " << v.mCellSize << ", " << v.mHeight << "}"; - } - - static inline std::ostream& operator<<(std::ostream& s, const Heightfield& v) - { - s << "Heightfield {.mCellPosition=" << v.mCellPosition - << ", .mCellSize=" << v.mCellSize - << ", .mLength=" << static_cast(v.mLength) - << ", .mMinHeight=" << v.mMinHeight - << ", .mMaxHeight=" << v.mMaxHeight - << ", .mHeights={"; - for (float h : v.mHeights) - s << h << ", "; - s << "}"; - return s << ", .mOriginalSize=" << v.mOriginalSize << "}"; - } } namespace diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index a868ac2a2e..7c86603348 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -3,6 +3,7 @@ #include "tilebounds.hpp" #include "status.hpp" +#include "recastmesh.hpp" #include @@ -39,6 +40,35 @@ namespace DetourNavigator return stream << "DetourNavigator::Error::" << static_cast(value); } + inline std::ostream& operator<<(std::ostream& s, const Water& v) + { + return s << "Water {" << v.mCellSize << ", " << v.mLevel << "}"; + } + + inline std::ostream& operator<<(std::ostream& s, const CellWater& v) + { + return s << "CellWater {" << v.mCellPosition << ", " << v.mWater << "}"; + } + + inline std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v) + { + return s << "FlatHeightfield {" << v.mCellPosition << ", " << v.mCellSize << ", " << v.mHeight << "}"; + } + + inline std::ostream& operator<<(std::ostream& s, const Heightfield& v) + { + s << "Heightfield {.mCellPosition=" << v.mCellPosition + << ", .mCellSize=" << v.mCellSize + << ", .mLength=" << static_cast(v.mLength) + << ", .mMinHeight=" << v.mMinHeight + << ", .mMaxHeight=" << v.mMaxHeight + << ", .mHeights={"; + for (float h : v.mHeights) + s << h << ", "; + s << "}"; + return s << ", .mOriginalSize=" << v.mOriginalSize << "}"; + } + class RecastMesh; struct Settings; From 01c712d5f1d7934ad57e22e26f7fa2fcbcbb803a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Nov 2021 13:46:43 +0100 Subject: [PATCH 1840/2859] Split navigator settings into subtypes Mostly to distinguish settings that affect properties of the generated navmesh. --- apps/openmw/mwrender/actorspaths.cpp | 3 +- apps/openmw/mwrender/recastmesh.cpp | 4 +- apps/openmw/mwworld/worldimp.cpp | 2 +- .../detournavigator/gettilespositions.cpp | 2 +- .../detournavigator/navigator.cpp | 38 +-- .../detournavigator/settingsutils.cpp | 4 +- .../tilecachedrecastmeshmanager.cpp | 2 +- .../detournavigator/asyncnavmeshupdater.cpp | 2 +- components/detournavigator/debug.cpp | 3 +- components/detournavigator/debug.hpp | 5 +- .../findrandompointaroundcircle.cpp | 2 +- .../findrandompointaroundcircle.hpp | 4 +- components/detournavigator/findsmoothpath.hpp | 12 +- .../detournavigator/gettilespositions.hpp | 6 +- components/detournavigator/makenavmesh.cpp | 252 ++++++++---------- components/detournavigator/makenavmesh.hpp | 14 +- components/detournavigator/navigatorimpl.cpp | 12 +- components/detournavigator/navigatorutils.cpp | 12 +- components/detournavigator/navigatorutils.hpp | 6 +- components/detournavigator/navmeshmanager.cpp | 14 +- .../offmeshconnectionsmanager.cpp | 2 +- .../offmeshconnectionsmanager.hpp | 4 +- components/detournavigator/raycast.cpp | 2 +- components/detournavigator/raycast.hpp | 4 +- components/detournavigator/settings.cpp | 90 ++++--- components/detournavigator/settings.hpp | 38 ++- components/detournavigator/settingsutils.hpp | 52 ++-- .../tilecachedrecastmeshmanager.cpp | 2 +- .../tilecachedrecastmeshmanager.hpp | 4 +- components/sceneutil/agentpath.cpp | 2 +- components/sceneutil/agentpath.hpp | 4 +- components/sceneutil/navmesh.cpp | 4 +- components/sceneutil/recastmesh.cpp | 2 +- components/sceneutil/recastmesh.hpp | 4 +- components/settings/settings.cpp | 9 + components/settings/settings.hpp | 1 + .../reference/modding/settings/navigator.rst | 8 +- files/settings-default.cfg | 4 +- 38 files changed, 329 insertions(+), 306 deletions(-) diff --git a/apps/openmw/mwrender/actorspaths.cpp b/apps/openmw/mwrender/actorspaths.cpp index 941f37df75..c8c5f56d8f 100644 --- a/apps/openmw/mwrender/actorspaths.cpp +++ b/apps/openmw/mwrender/actorspaths.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -47,7 +48,7 @@ namespace MWRender if (group != mGroups.end()) mRootNode->removeChild(group->second); - const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings); + const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings.mRecast); if (newGroup) { MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(newGroup, "debug"); diff --git a/apps/openmw/mwrender/recastmesh.cpp b/apps/openmw/mwrender/recastmesh.cpp index f108536242..5f202720b2 100644 --- a/apps/openmw/mwrender/recastmesh.cpp +++ b/apps/openmw/mwrender/recastmesh.cpp @@ -54,7 +54,7 @@ namespace MWRender if (it->second.mGeneration != tile->second->getGeneration() || it->second.mRevision != tile->second->getRevision()) { - const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings); + const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings.mRecast); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mRootNode->removeChild(it->second.mValue); @@ -71,7 +71,7 @@ namespace MWRender { if (mGroups.count(tile.first)) continue; - const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings); + const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings.mRecast); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group}); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d08eef2904..5a937d1a78 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -186,7 +186,7 @@ namespace MWWorld if (Settings::Manager::getBool("enable", "Navigator")) { auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); - navigatorSettings.mSwimHeightScale = mSwimHeightScale; + navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; mNavigator = DetourNavigator::makeNavigator(navigatorSettings); } else diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp index 1ad5c063d0..ced33a99f0 100644 --- a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -21,7 +21,7 @@ namespace struct DetourNavigatorGetTilesPositionsTest : Test { - Settings mSettings; + RecastSettings mSettings; std::vector mTilesPositions; CollectTilesPositions mCollect {mTilesPositions}; diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 07dcbb2002..b630c261ef 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -63,28 +63,28 @@ namespace mSettings.mEnableWriteNavMeshToFile = false; mSettings.mEnableRecastMeshFileNameRevision = false; mSettings.mEnableNavMeshFileNameRevision = false; - mSettings.mBorderSize = 16; - mSettings.mCellHeight = 0.2f; - mSettings.mCellSize = 0.2f; - mSettings.mDetailSampleDist = 6; - mSettings.mDetailSampleMaxError = 1; - mSettings.mMaxClimb = 34; - mSettings.mMaxSimplificationError = 1.3f; - mSettings.mMaxSlope = 49; - mSettings.mRecastScaleFactor = 0.017647058823529415f; - mSettings.mSwimHeightScale = 0.89999997615814208984375f; - mSettings.mMaxEdgeLen = 12; - mSettings.mMaxNavMeshQueryNodes = 2048; - mSettings.mMaxVertsPerPoly = 6; - mSettings.mRegionMergeSize = 20; - mSettings.mRegionMinSize = 8; - mSettings.mTileSize = 64; + mSettings.mRecast.mBorderSize = 16; + mSettings.mRecast.mCellHeight = 0.2f; + mSettings.mRecast.mCellSize = 0.2f; + mSettings.mRecast.mDetailSampleDist = 6; + mSettings.mRecast.mDetailSampleMaxError = 1; + mSettings.mRecast.mMaxClimb = 34; + mSettings.mRecast.mMaxSimplificationError = 1.3f; + mSettings.mRecast.mMaxSlope = 49; + mSettings.mRecast.mRecastScaleFactor = 0.017647058823529415f; + mSettings.mRecast.mSwimHeightScale = 0.89999997615814208984375f; + mSettings.mRecast.mMaxEdgeLen = 12; + mSettings.mDetour.mMaxNavMeshQueryNodes = 2048; + mSettings.mRecast.mMaxVertsPerPoly = 6; + mSettings.mRecast.mRegionMergeArea = 400; + mSettings.mRecast.mRegionMinArea = 64; + mSettings.mRecast.mTileSize = 64; mSettings.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); mSettings.mAsyncNavMeshUpdaterThreads = 1; mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024; - mSettings.mMaxPolygonPathSize = 1024; - mSettings.mMaxSmoothPathSize = 1024; - mSettings.mMaxPolys = 4096; + mSettings.mDetour.mMaxPolygonPathSize = 1024; + mSettings.mDetour.mMaxSmoothPathSize = 1024; + mSettings.mDetour.mMaxPolys = 4096; mSettings.mMaxTilesNumber = 512; mSettings.mMinUpdateInterval = std::chrono::milliseconds(50); mNavigator.reset(new NavigatorImpl(mSettings)); diff --git a/apps/openmw_test_suite/detournavigator/settingsutils.cpp b/apps/openmw_test_suite/detournavigator/settingsutils.cpp index ffed64ab81..f06f3b3e32 100644 --- a/apps/openmw_test_suite/detournavigator/settingsutils.cpp +++ b/apps/openmw_test_suite/detournavigator/settingsutils.cpp @@ -11,7 +11,7 @@ namespace struct DetourNavigatorGetTilePositionTest : Test { - Settings mSettings; + RecastSettings mSettings; DetourNavigatorGetTilePositionTest() { @@ -47,7 +47,7 @@ namespace struct DetourNavigatorMakeTileBoundsTest : Test { - Settings mSettings; + RecastSettings mSettings; DetourNavigatorMakeTileBoundsTest() { diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index eac7f4abbd..9426a3968f 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -15,7 +15,7 @@ namespace struct DetourNavigatorTileCachedRecastMeshManagerTest : Test { - Settings mSettings; + RecastSettings mSettings; std::vector mChangedTiles; const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; const osg::ref_ptr mShape = new Resource::BulletShape; diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 583fd1162a..c2c79a840e 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -404,7 +404,7 @@ namespace DetourNavigator } if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) - + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings); + + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings.get().mRecast); if (mSettings.get().mEnableWriteNavMeshToFile) if (const auto shared = job.mNavMeshCacheItem.lock()) writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 07832d748f..8394c6696d 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -11,7 +11,8 @@ namespace DetourNavigator { - void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const Settings& settings) + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, + const std::string& revision, const RecastSettings& settings) { const auto path = pathPrefix + "recastmesh" + revision + ".obj"; boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out); diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index 7c86603348..ce1e2d2023 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -70,9 +70,10 @@ namespace DetourNavigator } class RecastMesh; - struct Settings; + struct RecastSettings; - void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const Settings& settings); + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, + const std::string& revision, const RecastSettings& settings); void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision); } diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index a3407a61c3..1e1e2401c5 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -10,7 +10,7 @@ namespace DetourNavigator { std::optional findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const Settings& settings) + const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const DetourSettings& settings) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) diff --git a/components/detournavigator/findrandompointaroundcircle.hpp b/components/detournavigator/findrandompointaroundcircle.hpp index d0dc2bbbc0..89a3c0964c 100644 --- a/components/detournavigator/findrandompointaroundcircle.hpp +++ b/components/detournavigator/findrandompointaroundcircle.hpp @@ -10,10 +10,10 @@ class dtNavMesh; namespace DetourNavigator { - struct Settings; + struct DetourSettings; std::optional findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const Settings& settings); + const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const DetourSettings& settings); } #endif diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 2e63340578..fa35470c42 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -60,7 +60,7 @@ namespace DetourNavigator class OutputTransformIterator { public: - OutputTransformIterator(OutputIterator& impl, const Settings& settings) + explicit OutputTransformIterator(OutputIterator& impl, const RecastSettings& settings) : mImpl(impl), mSettings(settings) { } @@ -91,7 +91,7 @@ namespace DetourNavigator private: std::reference_wrapper mImpl; - std::reference_wrapper mSettings; + std::reference_wrapper mSettings; }; inline bool initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) @@ -261,7 +261,7 @@ namespace DetourNavigator const Settings& settings, float endTolerance, OutputIterator& out) { dtNavMeshQuery navMeshQuery; - if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) + if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mDetour.mMaxNavMeshQueryNodes)) return Status::InitNavMeshQueryFailed; dtQueryFilter queryFilter; @@ -283,7 +283,7 @@ namespace DetourNavigator if (endRef == 0) return Status::EndPolygonNotFound; - std::vector polygonPath(settings.mMaxPolygonPathSize); + std::vector polygonPath(settings.mDetour.mMaxPolygonPathSize); const auto polygonPathSize = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, polygonPath.data(), polygonPath.size()); @@ -294,9 +294,9 @@ namespace DetourNavigator return Status::Success; const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef; - auto outTransform = OutputTransformIterator(out, settings); + auto outTransform = OutputTransformIterator(out, settings.mRecast); const Status smoothStatus = makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, - polygonPath, *polygonPathSize, settings.mMaxSmoothPathSize, outTransform); + polygonPath, *polygonPathSize, settings.mDetour.mMaxSmoothPathSize, outTransform); if (smoothStatus != Status::Success) return smoothStatus; diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index abfce06464..707db0b512 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -15,7 +15,7 @@ namespace DetourNavigator { template void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, - const Settings& settings, Callback&& callback) + const RecastSettings& settings, Callback&& callback) { auto min = toNavMeshCoordinates(settings, aabbMin); auto max = toNavMeshCoordinates(settings, aabbMax); @@ -40,7 +40,7 @@ namespace DetourNavigator template void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, - const Settings& settings, Callback&& callback) + const RecastSettings& settings, Callback&& callback) { btVector3 aabbMin; btVector3 aabbMax; @@ -51,7 +51,7 @@ namespace DetourNavigator template void getTilesPositions(const int cellSize, const btVector3& shift, - const Settings& settings, Callback&& callback) + const RecastSettings& settings, Callback&& callback) { using Misc::Convert::toOsg; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 7d478b9c1b..217e2b565e 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -36,32 +36,6 @@ namespace float mHeight; }; - Rectangle getSwimRectangle(const CellWater& water, const Settings& settings, const osg::Vec3f& agentHalfExtents) - { - if (water.mWater.mCellSize == std::numeric_limits::max()) - { - return Rectangle { - TileBounds { - osg::Vec2f(-std::numeric_limits::max(), -std::numeric_limits::max()), - osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max()) - }, - toNavMeshCoordinates(settings, getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z())) - }; - } - else - { - const osg::Vec2f shift = getWaterShift2d(water.mCellPosition, water.mWater.mCellSize); - const float halfCellSize = water.mWater.mCellSize / 2.0f; - return Rectangle { - TileBounds{ - toNavMeshCoordinates(settings, shift + osg::Vec2f(-halfCellSize, -halfCellSize)), - toNavMeshCoordinates(settings, shift + osg::Vec2f(halfCellSize, halfCellSize)) - }, - toNavMeshCoordinates(settings, getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z())) - }; - } - } - std::vector getOffMeshVerts(const std::vector& connections) { std::vector result; @@ -120,52 +94,46 @@ namespace return result; } - rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const TilePosition& tile, float minZ, float maxZ, - const Settings& settings) - { - rcConfig config; - - config.cs = settings.mCellSize; - config.ch = settings.mCellHeight; - config.walkableSlopeAngle = settings.mMaxSlope; - config.walkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / config.ch)); - config.walkableClimb = static_cast(std::floor(getMaxClimb(settings) / config.ch)); - config.walkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / config.cs)); - config.maxEdgeLen = static_cast(std::round(settings.mMaxEdgeLen / config.cs)); - config.maxSimplificationError = settings.mMaxSimplificationError; - config.minRegionArea = settings.mRegionMinSize * settings.mRegionMinSize; - config.mergeRegionArea = settings.mRegionMergeSize * settings.mRegionMergeSize; - config.maxVertsPerPoly = settings.mMaxVertsPerPoly; - config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; - config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; - config.borderSize = settings.mBorderSize; - config.tileSize = settings.mTileSize; - const int size = config.tileSize + config.borderSize * 2; - config.width = size; - config.height = size; - const float halfBoundsSize = size * config.cs * 0.5f; - const osg::Vec2f shift = osg::Vec2f(tile.x() + 0.5f, tile.y() + 0.5f) * getTileSize(settings); - config.bmin[0] = shift.x() - halfBoundsSize; - config.bmin[1] = minZ; - config.bmin[2] = shift.y() - halfBoundsSize; - config.bmax[0] = shift.x() + halfBoundsSize; - config.bmax[1] = maxZ; - config.bmax[2] = shift.y() + halfBoundsSize; - - return config; + float getHeight(const RecastSettings& settings,const osg::Vec3f& agentHalfExtents) + { + return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor; + } + + float getMaxClimb(const RecastSettings& settings) + { + return settings.mMaxClimb * settings.mRecastScaleFactor; + } + + float getRadius(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents) + { + return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor; + } + + float getSwimLevel(const RecastSettings& settings, const float waterLevel, const float agentHalfExtentsZ) + { + return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;; } - void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin, - const float* bmax, const float cs, const float ch) + void initHeightfield(rcContext& context, const TilePosition& tilePosition, float minZ, float maxZ, + const RecastSettings& settings, rcHeightfield& solid) { - const auto result = rcCreateHeightfield(&context, solid, width, height, bmin, bmax, cs, ch); + const int size = settings.mTileSize + settings.mBorderSize * 2; + const int width = size; + const int height = size; + const float halfBoundsSize = size * settings.mCellSize * 0.5f; + const osg::Vec2f shift = osg::Vec2f(tilePosition.x() + 0.5f, tilePosition.y() + 0.5f) * getTileSize(settings); + const osg::Vec3f bmin(shift.x() - halfBoundsSize, minZ, shift.y() - halfBoundsSize); + const osg::Vec3f bmax(shift.x() + halfBoundsSize, maxZ, shift.y() + halfBoundsSize); + + const auto result = rcCreateHeightfield(&context, solid, width, height, bmin.ptr(), bmax.ptr(), + settings.mCellSize, settings.mCellHeight); if (!result) throw NavigatorException("Failed to create heightfield for navmesh"); } - bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const Settings& settings, const rcConfig& config, - rcHeightfield& solid) + bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const RecastSettings& settings, + const RecastParams& params, rcHeightfield& solid) { std::vector areas(mesh.getAreaTypes().begin(), mesh.getAreaTypes().end()); std::vector vertices = mesh.getVertices(); @@ -179,7 +147,7 @@ namespace rcClearUnwalkableTriangles( &context, - config.walkableSlopeAngle, + settings.mMaxSlope, vertices.data(), static_cast(mesh.getVerticesCount()), mesh.getIndices().data(), @@ -195,30 +163,18 @@ namespace areas.data(), static_cast(areas.size()), solid, - config.walkableClimb + params.mWalkableClimb ); } - bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, const rcConfig& config, - AreaType areaType, rcHeightfield& solid) + bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, AreaType areaType, + const RecastParams& params, rcHeightfield& solid) { - const osg::Vec2f tileBoundsMin( - std::clamp(rectangle.mBounds.mMin.x(), config.bmin[0], config.bmax[0]), - std::clamp(rectangle.mBounds.mMin.y(), config.bmin[2], config.bmax[2]) - ); - const osg::Vec2f tileBoundsMax( - std::clamp(rectangle.mBounds.mMax.x(), config.bmin[0], config.bmax[0]), - std::clamp(rectangle.mBounds.mMax.y(), config.bmin[2], config.bmax[2]) - ); - - if (tileBoundsMax == tileBoundsMin) - return true; - const std::array vertices { - tileBoundsMin.x(), rectangle.mHeight, tileBoundsMin.y(), - tileBoundsMin.x(), rectangle.mHeight, tileBoundsMax.y(), - tileBoundsMax.x(), rectangle.mHeight, tileBoundsMax.y(), - tileBoundsMax.x(), rectangle.mHeight, tileBoundsMin.y(), + rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(), + rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(), + rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(), + rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(), }; const std::array indices { @@ -236,31 +192,42 @@ namespace areas.data(), static_cast(areas.size()), solid, - config.walkableClimb + params.mWalkableClimb ); } bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector& water, - const Settings& settings, const rcConfig& config, rcHeightfield& solid) + const RecastSettings& settings, const RecastParams& params, const TileBounds& realTileBounds, rcHeightfield& solid) { for (const CellWater& cellWater : water) { - const Rectangle rectangle = getSwimRectangle(cellWater, settings, agentHalfExtents); - if (!rasterizeTriangles(context, rectangle, config, AreaType_water, solid)) - return false; + const TileBounds cellTileBounds = maxCellTileBounds(cellWater.mCellPosition, cellWater.mWater.mCellSize); + if (auto intersection = getIntersection(realTileBounds, cellTileBounds)) + { + const Rectangle rectangle { + toNavMeshCoordinates(settings, *intersection), + toNavMeshCoordinates(settings, getSwimLevel(settings, cellWater.mWater.mLevel, agentHalfExtents.z())) + }; + if (!rasterizeTriangles(context, rectangle, AreaType_water, params, solid)) + return false; + } } return true; } - bool rasterizeTriangles(rcContext& context, const TileBounds& tileBounds, const std::vector& heightfields, - const Settings& settings, const rcConfig& config, rcHeightfield& solid) + bool rasterizeTriangles(rcContext& context, const TileBounds& realTileBounds, const std::vector& heightfields, + const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { for (const FlatHeightfield& heightfield : heightfields) { - if (auto intersection = getIntersection(tileBounds, maxCellTileBounds(heightfield.mCellPosition, heightfield.mCellSize))) + const TileBounds cellTileBounds = maxCellTileBounds(heightfield.mCellPosition, heightfield.mCellSize); + if (auto intersection = getIntersection(realTileBounds, cellTileBounds)) { - const Rectangle rectangle {*intersection, toNavMeshCoordinates(settings, heightfield.mHeight)}; - if (!rasterizeTriangles(context, rectangle, config, AreaType_ground, solid)) + const Rectangle rectangle { + toNavMeshCoordinates(settings, *intersection), + toNavMeshCoordinates(settings, heightfield.mHeight) + }; + if (!rasterizeTriangles(context, rectangle, AreaType_ground, params, solid)) return false; } } @@ -268,27 +235,25 @@ namespace } bool rasterizeTriangles(rcContext& context, const std::vector& heightfields, - const Settings& settings, const rcConfig& config, rcHeightfield& solid) + const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { - using BulletHelpers::makeProcessTriangleCallback; - for (const Heightfield& heightfield : heightfields) { const Mesh mesh = makeMesh(heightfield); - if (!rasterizeTriangles(context, mesh, settings, config, solid)) + if (!rasterizeTriangles(context, mesh, settings, params, solid)) return false; } return true; } bool rasterizeTriangles(rcContext& context, const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, - const RecastMesh& recastMesh, const rcConfig& config, const Settings& settings, rcHeightfield& solid) + const RecastMesh& recastMesh, const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { - return rasterizeTriangles(context, recastMesh.getMesh(), settings, config, solid) - && rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, config, solid) - && rasterizeTriangles(context, recastMesh.getHeightfields(), settings, config, solid) - && rasterizeTriangles(context, makeRealTileBoundsWithBorder(settings, tilePosition), - recastMesh.getFlatHeightfields(), settings, config, solid); + const TileBounds realTileBounds = makeRealTileBoundsWithBorder(settings, tilePosition); + return rasterizeTriangles(context, recastMesh.getMesh(), settings, params, solid) + && rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, params, realTileBounds, solid) + && rasterizeTriangles(context, recastMesh.getHeightfields(), settings, params, solid) + && rasterizeTriangles(context, realTileBounds, recastMesh.getFlatHeightfields(), settings, params, solid); } void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb, @@ -359,27 +324,25 @@ namespace polyMesh.flags[i] = getFlag(static_cast(polyMesh.areas[i])); } - bool fillPolyMesh(rcContext& context, const rcConfig& config, rcHeightfield& solid, rcPolyMesh& polyMesh, - rcPolyMeshDetail& polyMeshDetail) + bool fillPolyMesh(rcContext& context, const RecastSettings& settings, const RecastParams& params, + rcHeightfield& solid, rcPolyMesh& polyMesh, rcPolyMeshDetail& polyMeshDetail) { rcCompactHeightfield compact; - compact.dist = nullptr; - buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact); + buildCompactHeightfield(context, params.mWalkableHeight, params.mWalkableClimb, solid, compact); - erodeWalkableArea(context, config.walkableRadius, compact); + erodeWalkableArea(context, params.mWalkableRadius, compact); buildDistanceField(context, compact); - buildRegions(context, compact, config.borderSize, config.minRegionArea, config.mergeRegionArea); + buildRegions(context, compact, settings.mBorderSize, settings.mRegionMinArea, settings.mRegionMergeArea); rcContourSet contourSet; - buildContours(context, compact, config.maxSimplificationError, config.maxEdgeLen, contourSet); + buildContours(context, compact, settings.mMaxSimplificationError, params.mMaxEdgeLen, contourSet); if (contourSet.nconts == 0) return false; - buildPolyMesh(context, contourSet, config.maxVertsPerPoly, polyMesh); + buildPolyMesh(context, contourSet, settings.mMaxVertsPerPoly, polyMesh); - buildPolyMeshDetail(context, polyMesh, compact, config.detailSampleDist, config.detailSampleMaxError, - polyMeshDetail); + buildPolyMeshDetail(context, polyMesh, compact, params.mSampleDist, params.mSampleMaxError, polyMeshDetail); setPolyMeshFlags(polyMesh); @@ -395,7 +358,7 @@ namespace return power; } - std::pair getBoundsByZ(const RecastMesh& recastMesh, const osg::Vec3f& agentHalfExtents, const Settings& settings) + std::pair getBoundsByZ(const RecastMesh& recastMesh, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings) { float minZ = 0; float maxZ = 0; @@ -436,39 +399,54 @@ namespace namespace DetourNavigator { + RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents) + { + RecastParams result; + + result.mWalkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / settings.mCellHeight)); + result.mWalkableClimb = static_cast(std::floor(getMaxClimb(settings) / settings.mCellHeight)); + result.mWalkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / settings.mCellSize)); + result.mMaxEdgeLen = static_cast(std::round(static_cast(settings.mMaxEdgeLen) / settings.mCellSize)); + result.mSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : settings.mCellSize * settings.mDetailSampleDist; + result.mSampleMaxError = settings.mCellHeight * settings.mDetailSampleMaxError; + + return result; + } + std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, - const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const Settings& settings) + const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings) { const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings); rcContext context; - const auto config = makeConfig(agentHalfExtents, tilePosition, toNavMeshCoordinates(settings, minZ), - toNavMeshCoordinates(settings, maxZ), settings); rcHeightfield solid; - createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); + initHeightfield(context, tilePosition, toNavMeshCoordinates(settings, minZ), + toNavMeshCoordinates(settings, maxZ), settings, solid); + + const RecastParams params = makeRecastParams(settings, agentHalfExtents); - if (!rasterizeTriangles(context, tilePosition, agentHalfExtents, recastMesh, config, settings, solid)) + if (!rasterizeTriangles(context, tilePosition, agentHalfExtents, recastMesh, settings, params, solid)) return nullptr; - rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); - rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid); - rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid); + rcFilterLowHangingWalkableObstacles(&context, params.mWalkableClimb, solid); + rcFilterLedgeSpans(&context, params.mWalkableHeight, params.mWalkableClimb, solid); + rcFilterWalkableLowHeightSpans(&context, params.mWalkableHeight, solid); std::unique_ptr result = std::make_unique(); - if (!fillPolyMesh(context, config, solid, result->mPolyMesh, result->mPolyMeshDetail)) + if (!fillPolyMesh(context, settings, params, solid, result->mPolyMesh, result->mPolyMeshDetail)) return nullptr; - result->mCellSize = config.cs; - result->mCellHeight = config.ch; + result->mCellSize = settings.mCellSize; + result->mCellHeight = settings.mCellHeight; return result; } NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, const std::vector& offMeshConnections, const osg::Vec3f& agentHalfExtents, - const TilePosition& tile, const Settings& settings) + const TilePosition& tile, const RecastSettings& settings) { const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const std::vector offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); @@ -524,7 +502,7 @@ namespace DetourNavigator // Max tiles and max polys affect how the tile IDs are caculated. // There are 22 bits available for identifying a tile and a polygon. const int polysAndTilesBits = 22; - const auto polysBits = getMinValuableBitsNumber(settings.mMaxPolys); + const auto polysBits = getMinValuableBitsNumber(settings.mDetour.mMaxPolys); if (polysBits >= polysAndTilesBits) throw InvalidArgument("Too many polygons per tile"); @@ -533,8 +511,8 @@ namespace DetourNavigator dtNavMeshParams params; std::fill_n(params.orig, 3, 0.0f); - params.tileWidth = settings.mTileSize * settings.mCellSize; - params.tileHeight = settings.mTileSize * settings.mCellSize; + params.tileWidth = settings.mRecast.mTileSize * settings.mRecast.mCellSize; + params.tileHeight = settings.mRecast.mTileSize * settings.mRecast.mCellSize; params.maxTiles = 1 << tilesBits; params.maxPolys = 1 << polysBits; @@ -558,9 +536,9 @@ namespace DetourNavigator { Log(Debug::Debug) << std::fixed << std::setprecision(2) << "Update NavMesh with multiple tiles:" << - " agentHeight=" << getHeight(settings, agentHalfExtents) << - " agentMaxClimb=" << getMaxClimb(settings) << - " agentRadius=" << getRadius(settings, agentHalfExtents) << + " agentHeight=" << getHeight(settings.mRecast, agentHalfExtents) << + " agentMaxClimb=" << getMaxClimb(settings.mRecast) << + " agentRadius=" << getRadius(settings.mRecast, agentHalfExtents) << " changedTile=(" << changedTile << ")" << " playerTile=(" << playerTile << ")" << " changedTileDistance=" << getDistance(changedTile, playerTile); @@ -591,7 +569,7 @@ namespace DetourNavigator if (!cachedNavMeshData) { - auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings); + auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings.mRecast); if (prepared == nullptr) { @@ -601,7 +579,7 @@ namespace DetourNavigator if (updateType == UpdateType::Temporary) return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(), - makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings)); + makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings.mRecast)); cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, std::move(prepared)); @@ -609,12 +587,12 @@ namespace DetourNavigator { Log(Debug::Debug) << "Navigator cache overflow"; return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(), - makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings)); + makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings.mRecast)); } } const auto updateStatus = navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData), - makeNavMeshTileData(cachedNavMeshData.get(), offMeshConnections, agentHalfExtents, changedTile, settings)); + makeNavMeshTileData(cachedNavMeshData.get(), offMeshConnections, agentHalfExtents, changedTile, settings.mRecast)); return UpdateNavMeshStatusBuilder(updateStatus).cached(cached).getResult(); } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 5b4169374b..989565d4bf 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -22,6 +22,16 @@ namespace DetourNavigator struct PreparedNavMeshData; struct NavMeshData; + struct RecastParams + { + float mSampleDist = 0; + float mSampleMaxError = 0; + int mMaxEdgeLen = 0; + int mWalkableClimb = 0; + int mWalkableHeight = 0; + int mWalkableRadius = 0; + }; + inline float getLength(const osg::Vec2i& value) { return std::sqrt(float(osg::square(value.x()) + osg::square(value.y()))); @@ -38,12 +48,14 @@ namespace DetourNavigator return expectedTilesCount <= maxTiles; } + RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents); + std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, const TilePosition& tile, const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings); NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, const std::vector& offMeshConnections, const osg::Vec3f& agentHalfExtents, - const TilePosition& tile, const Settings& settings); + const TilePosition& tile, const RecastSettings& settings); NavMeshPtr makeEmptyNavMesh(const Settings& settings); diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 9092624ca3..541e1f791c 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -53,8 +53,8 @@ namespace DetourNavigator { if (addObject(id, static_cast(shapes), transform)) { - const osg::Vec3f start = toNavMeshCoordinates(mSettings, shapes.mConnectionStart); - const osg::Vec3f end = toNavMeshCoordinates(mSettings, shapes.mConnectionEnd); + const osg::Vec3f start = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionStart); + const osg::Vec3f end = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionEnd); mNavMeshManager.addOffMeshConnection(id, start, end, AreaType_door); mNavMeshManager.addOffMeshConnection(id, end, start, AreaType_door); return true; @@ -126,8 +126,8 @@ namespace DetourNavigator const auto dst = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid.mPoints[edge.mV1])); mNavMeshManager.addOffMeshConnection( ObjectId(&pathgrid), - toNavMeshCoordinates(mSettings, src), - toNavMeshCoordinates(mSettings, dst), + toNavMeshCoordinates(mSettings.mRecast, src), + toNavMeshCoordinates(mSettings.mRecast, dst), AreaType_pathgrid ); } @@ -149,7 +149,7 @@ namespace DetourNavigator void NavigatorImpl::updatePlayerPosition(const osg::Vec3f& playerPosition) { - const TilePosition tilePosition = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); + const TilePosition tilePosition = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition) return; update(playerPosition); @@ -225,6 +225,6 @@ namespace DetourNavigator float NavigatorImpl::getMaxNavmeshAreaRealRadius() const { const auto& settings = getSettings(); - return getRealTileSize(settings) * getMaxNavmeshAreaRadius(settings); + return getRealTileSize(settings.mRecast) * getMaxNavmeshAreaRadius(settings); } } diff --git a/components/detournavigator/navigatorutils.cpp b/components/detournavigator/navigatorutils.cpp index 82a108db6f..bc94e3f991 100644 --- a/components/detournavigator/navigatorutils.cpp +++ b/components/detournavigator/navigatorutils.cpp @@ -13,11 +13,11 @@ namespace DetourNavigator return std::nullopt; const auto settings = navigator.getSettings(); const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(), - toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, maxRadius), includeFlags, settings); + toNavMeshCoordinates(settings.mRecast, agentHalfExtents), toNavMeshCoordinates(settings.mRecast, start), + toNavMeshCoordinates(settings.mRecast, maxRadius), includeFlags, settings.mDetour); if (!result) return std::nullopt; - return std::optional(fromNavMeshCoordinates(settings, *result)); + return std::optional(fromNavMeshCoordinates(settings.mRecast, *result)); } std::optional raycast(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, @@ -28,10 +28,10 @@ namespace DetourNavigator return std::nullopt; const auto settings = navigator.getSettings(); const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), - toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, end), includeFlags, settings); + toNavMeshCoordinates(settings.mRecast, agentHalfExtents), toNavMeshCoordinates(settings.mRecast, start), + toNavMeshCoordinates(settings.mRecast, end), includeFlags, settings.mDetour); if (!result) return std::nullopt; - return fromNavMeshCoordinates(settings, *result); + return fromNavMeshCoordinates(settings.mRecast, *result); } } diff --git a/components/detournavigator/navigatorutils.hpp b/components/detournavigator/navigatorutils.hpp index 4ccc238f97..8f3b6161f9 100644 --- a/components/detournavigator/navigatorutils.hpp +++ b/components/detournavigator/navigatorutils.hpp @@ -37,9 +37,9 @@ namespace DetourNavigator if (navMesh == nullptr) return Status::NavMeshNotFound; const auto settings = navigator.getSettings(); - return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), - toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, endTolerance, out); + return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings.mRecast, agentHalfExtents), + toNavMeshCoordinates(settings.mRecast, stepSize), toNavMeshCoordinates(settings.mRecast, start), + toNavMeshCoordinates(settings.mRecast, end), includeFlags, areaCosts, settings, endTolerance, out); } /** diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index fff7d8d7cc..452fba93ad 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -43,8 +43,8 @@ namespace DetourNavigator { NavMeshManager::NavMeshManager(const Settings& settings) : mSettings(settings) - , mRecastMeshManager(settings) - , mOffMeshConnectionsManager(settings) + , mRecastMeshManager(mSettings.mRecast) + , mOffMeshConnectionsManager(mSettings.mRecast) , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) {} @@ -140,8 +140,8 @@ namespace DetourNavigator { mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end, areaType}); - const auto startTilePosition = getTilePosition(mSettings, start); - const auto endTilePosition = getTilePosition(mSettings, end); + const auto startTilePosition = getTilePosition(mSettings.mRecast, start); + const auto endTilePosition = getTilePosition(mSettings.mRecast, end); addChangedTile(startTilePosition, ChangeType::add); @@ -158,7 +158,7 @@ namespace DetourNavigator void NavMeshManager::update(const osg::Vec3f& playerPosition, const osg::Vec3f& agentHalfExtents) { - const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); + const auto playerTile = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents]; auto lastPlayerTile = mPlayerTile.find(agentHalfExtents); if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() @@ -251,7 +251,7 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(shape, transform, mSettings, + getTilesPositions(shape, transform, mSettings.mRecast, [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } @@ -261,7 +261,7 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) return; - getTilesPositions(cellSize, shift, mSettings, + getTilesPositions(cellSize, shift, mSettings.mRecast, [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/offmeshconnectionsmanager.cpp b/components/detournavigator/offmeshconnectionsmanager.cpp index a673ae3e68..2306e50da9 100644 --- a/components/detournavigator/offmeshconnectionsmanager.cpp +++ b/components/detournavigator/offmeshconnectionsmanager.cpp @@ -11,7 +11,7 @@ namespace DetourNavigator { - OffMeshConnectionsManager::OffMeshConnectionsManager(const Settings& settings) + OffMeshConnectionsManager::OffMeshConnectionsManager(const RecastSettings& settings) : mSettings(settings) {} diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 20a6427cd5..a0a1231bc4 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -18,7 +18,7 @@ namespace DetourNavigator class OffMeshConnectionsManager { public: - OffMeshConnectionsManager(const Settings& settings); + explicit OffMeshConnectionsManager(const RecastSettings& settings); void add(const ObjectId id, const OffMeshConnection& value); @@ -33,7 +33,7 @@ namespace DetourNavigator std::map> mByTilePosition; }; - const Settings& mSettings; + const RecastSettings& mSettings; Misc::ScopeGuarded mValues; }; } diff --git a/components/detournavigator/raycast.cpp b/components/detournavigator/raycast.cpp index 271da22496..be3217ba40 100644 --- a/components/detournavigator/raycast.cpp +++ b/components/detournavigator/raycast.cpp @@ -10,7 +10,7 @@ namespace DetourNavigator { std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings) + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const DetourSettings& settings) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) diff --git a/components/detournavigator/raycast.hpp b/components/detournavigator/raycast.hpp index ddf61b49f4..60cdf0a157 100644 --- a/components/detournavigator/raycast.hpp +++ b/components/detournavigator/raycast.hpp @@ -10,10 +10,10 @@ class dtNavMesh; namespace DetourNavigator { - struct Settings; + struct DetourSettings; std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings); + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const DetourSettings& settings); } #endif diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index e428f3695a..5ad45d699b 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -3,43 +3,65 @@ #include #include +#include + namespace DetourNavigator { + RecastSettings makeRecastSettingsFromSettingsManager() + { + constexpr float epsilon = std::numeric_limits::epsilon(); + + RecastSettings result; + + result.mBorderSize = std::max(0, ::Settings::Manager::getInt("border size", "Navigator")); + result.mCellHeight = std::max(epsilon, ::Settings::Manager::getFloat("cell height", "Navigator")); + result.mCellSize = std::max(epsilon, ::Settings::Manager::getFloat("cell size", "Navigator")); + result.mDetailSampleDist = std::max(0.0f, ::Settings::Manager::getFloat("detail sample dist", "Navigator")); + result.mDetailSampleMaxError = std::max(0.0f, ::Settings::Manager::getFloat("detail sample max error", "Navigator")); + result.mMaxClimb = Constants::sStepSizeUp; + result.mMaxSimplificationError = std::max(0.0f, ::Settings::Manager::getFloat("max simplification error", "Navigator")); + result.mMaxSlope = Constants::sMaxSlope; + result.mRecastScaleFactor = std::max(epsilon, ::Settings::Manager::getFloat("recast scale factor", "Navigator")); + result.mSwimHeightScale = 0; + result.mMaxEdgeLen = std::max(0, ::Settings::Manager::getInt("max edge len", "Navigator")); + result.mMaxVertsPerPoly = std::max(3, ::Settings::Manager::getInt("max verts per poly", "Navigator")); + result.mRegionMergeArea = std::max(0, ::Settings::Manager::getInt("region merge area", "Navigator")); + result.mRegionMinArea = std::max(0, ::Settings::Manager::getInt("region min area", "Navigator")); + result.mTileSize = std::max(1, ::Settings::Manager::getInt("tile size", "Navigator")); + + return result; + } + + DetourSettings makeDetourSettingsFromSettingsManager() + { + DetourSettings result; + + result.mMaxNavMeshQueryNodes = std::clamp(::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"), 1, 65535); + result.mMaxPolys = std::clamp(::Settings::Manager::getInt("max polygons per tile", "Navigator"), 1, (1 << 22) - 1); + result.mMaxPolygonPathSize = static_cast(std::max(0, ::Settings::Manager::getInt("max polygon path size", "Navigator"))); + result.mMaxSmoothPathSize = static_cast(std::max(0, ::Settings::Manager::getInt("max smooth path size", "Navigator"))); + + return result; + } + Settings makeSettingsFromSettingsManager() { - Settings navigatorSettings; - - navigatorSettings.mBorderSize = ::Settings::Manager::getInt("border size", "Navigator"); - navigatorSettings.mCellHeight = ::Settings::Manager::getFloat("cell height", "Navigator"); - navigatorSettings.mCellSize = ::Settings::Manager::getFloat("cell size", "Navigator"); - navigatorSettings.mDetailSampleDist = ::Settings::Manager::getFloat("detail sample dist", "Navigator"); - navigatorSettings.mDetailSampleMaxError = ::Settings::Manager::getFloat("detail sample max error", "Navigator"); - navigatorSettings.mMaxClimb = Constants::sStepSizeUp; - navigatorSettings.mMaxSimplificationError = ::Settings::Manager::getFloat("max simplification error", "Navigator"); - navigatorSettings.mMaxSlope = Constants::sMaxSlope; - navigatorSettings.mRecastScaleFactor = ::Settings::Manager::getFloat("recast scale factor", "Navigator"); - navigatorSettings.mSwimHeightScale = 0; - navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator"); - navigatorSettings.mMaxNavMeshQueryNodes = ::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); - navigatorSettings.mMaxPolys = ::Settings::Manager::getInt("max polygons per tile", "Navigator"); - navigatorSettings.mMaxTilesNumber = ::Settings::Manager::getInt("max tiles number", "Navigator"); - navigatorSettings.mMaxVertsPerPoly = ::Settings::Manager::getInt("max verts per poly", "Navigator"); - navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator"); - navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator"); - navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator"); - navigatorSettings.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator"); - navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(::Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); - navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); - navigatorSettings.mMaxPolygonPathSize = static_cast(::Settings::Manager::getInt("max polygon path size", "Navigator")); - navigatorSettings.mMaxSmoothPathSize = static_cast(::Settings::Manager::getInt("max smooth path size", "Navigator")); - navigatorSettings.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); - navigatorSettings.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); - navigatorSettings.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator"); - navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); - navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); - navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); - navigatorSettings.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); - - return navigatorSettings; + Settings result; + + result.mRecast = makeRecastSettingsFromSettingsManager(); + result.mDetour = makeDetourSettingsFromSettingsManager(); + result.mMaxTilesNumber = std::max(0, ::Settings::Manager::getInt("max tiles number", "Navigator")); + result.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator"); + result.mAsyncNavMeshUpdaterThreads = static_cast(std::max(0, ::Settings::Manager::getInt("async nav mesh updater threads", "Navigator"))); + result.mMaxNavMeshTilesCacheSize = static_cast(std::max(std::int64_t {0}, ::Settings::Manager::getInt64("max nav mesh tiles cache size", "Navigator"))); + result.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); + result.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); + result.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator"); + result.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); + result.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); + result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); + result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); + + return result; } } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 0ea35d9b49..fbd44dfcfc 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -6,12 +6,8 @@ namespace DetourNavigator { - struct Settings + struct RecastSettings { - bool mEnableWriteRecastMeshToFile = false; - bool mEnableWriteNavMeshToFile = false; - bool mEnableRecastMeshFileNameRevision = false; - bool mEnableNavMeshFileNameRevision = false; float mCellHeight = 0; float mCellSize = 0; float mDetailSampleDist = 0; @@ -23,23 +19,41 @@ namespace DetourNavigator float mSwimHeightScale = 0; int mBorderSize = 0; int mMaxEdgeLen = 0; - int mMaxNavMeshQueryNodes = 0; - int mMaxPolys = 0; - int mMaxTilesNumber = 0; int mMaxVertsPerPoly = 0; - int mRegionMergeSize = 0; - int mRegionMinSize = 0; + int mRegionMergeArea = 0; + int mRegionMinArea = 0; int mTileSize = 0; + }; + + struct DetourSettings + { + int mMaxPolys = 0; + int mMaxNavMeshQueryNodes = 0; + std::size_t mMaxPolygonPathSize = 0; + std::size_t mMaxSmoothPathSize = 0; + }; + + struct Settings + { + bool mEnableWriteRecastMeshToFile = false; + bool mEnableWriteNavMeshToFile = false; + bool mEnableRecastMeshFileNameRevision = false; + bool mEnableNavMeshFileNameRevision = false; + RecastSettings mRecast; + DetourSettings mDetour; int mWaitUntilMinDistanceToPlayer = 0; + int mMaxTilesNumber = 0; std::size_t mAsyncNavMeshUpdaterThreads = 0; std::size_t mMaxNavMeshTilesCacheSize = 0; - std::size_t mMaxPolygonPathSize = 0; - std::size_t mMaxSmoothPathSize = 0; std::string mRecastMeshPathPrefix; std::string mNavMeshPathPrefix; std::chrono::milliseconds mMinUpdateInterval; }; + RecastSettings makeRecastSettingsFromSettingsManager(); + + DetourSettings makeDetourSettingsFromSettingsManager(); + Settings makeSettingsFromSettingsManager(); } diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 6f15faaa3e..285920e5a0 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -4,12 +4,8 @@ #include "settings.hpp" #include "tilebounds.hpp" #include "tileposition.hpp" -#include "tilebounds.hpp" - -#include #include -#include #include #include @@ -17,38 +13,31 @@ namespace DetourNavigator { - inline float getHeight(const Settings& settings,const osg::Vec3f& agentHalfExtents) - { - return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor; - } - - inline float getMaxClimb(const Settings& settings) - { - return settings.mMaxClimb * settings.mRecastScaleFactor; - } - - inline float getRadius(const Settings& settings, const osg::Vec3f& agentHalfExtents) - { - return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor; - } - - inline float toNavMeshCoordinates(const Settings& settings, float value) + inline float toNavMeshCoordinates(const RecastSettings& settings, float value) { return value * settings.mRecastScaleFactor; } - inline osg::Vec2f toNavMeshCoordinates(const Settings& settings, osg::Vec2f position) + inline osg::Vec2f toNavMeshCoordinates(const RecastSettings& settings, osg::Vec2f position) { return position * settings.mRecastScaleFactor; } - inline osg::Vec3f toNavMeshCoordinates(const Settings& settings, osg::Vec3f position) + inline osg::Vec3f toNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { std::swap(position.y(), position.z()); return position * settings.mRecastScaleFactor; } - inline osg::Vec3f fromNavMeshCoordinates(const Settings& settings, osg::Vec3f position) + inline TileBounds toNavMeshCoordinates(const RecastSettings& settings, const TileBounds& value) + { + return TileBounds { + toNavMeshCoordinates(settings, value.mMin), + toNavMeshCoordinates(settings, value.mMax) + }; + } + + inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; position *= factor; @@ -56,12 +45,12 @@ namespace DetourNavigator return position; } - inline float getTileSize(const Settings& settings) + inline float getTileSize(const RecastSettings& settings) { return static_cast(settings.mTileSize) * settings.mCellSize; } - inline TilePosition getTilePosition(const Settings& settings, const osg::Vec3f& position) + inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec3f& position) { return TilePosition( static_cast(std::floor(position.x() / getTileSize(settings))), @@ -69,7 +58,7 @@ namespace DetourNavigator ); } - inline TileBounds makeTileBounds(const Settings& settings, const TilePosition& tilePosition) + inline TileBounds makeTileBounds(const RecastSettings& settings, const TilePosition& tilePosition) { return TileBounds { osg::Vec2f(tilePosition.x(), tilePosition.y()) * getTileSize(settings), @@ -77,17 +66,12 @@ namespace DetourNavigator }; } - inline float getBorderSize(const Settings& settings) + inline float getBorderSize(const RecastSettings& settings) { return static_cast(settings.mBorderSize) * settings.mCellSize; } - inline float getSwimLevel(const Settings& settings, const float waterLevel, const float agentHalfExtentsZ) - { - return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;; - } - - inline float getRealTileSize(const Settings& settings) + inline float getRealTileSize(const RecastSettings& settings) { return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor; } @@ -97,7 +81,7 @@ namespace DetourNavigator return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; } - inline TileBounds makeRealTileBoundsWithBorder(const Settings& settings, const TilePosition& tilePosition) + inline TileBounds makeRealTileBoundsWithBorder(const RecastSettings& settings, const TilePosition& tilePosition) { TileBounds result = makeTileBounds(settings, tilePosition); const float border = getBorderSize(settings); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 20fbe30667..ebd64180aa 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -10,7 +10,7 @@ namespace DetourNavigator { - TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) + TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) : mSettings(settings) {} diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index bb08a4227e..96183a51be 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -20,7 +20,7 @@ namespace DetourNavigator class TileCachedRecastMeshManager { public: - TileCachedRecastMeshManager(const Settings& settings); + explicit TileCachedRecastMeshManager(const RecastSettings& settings); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); @@ -102,7 +102,7 @@ namespace DetourNavigator private: using TilesMap = std::map>; - const Settings& mSettings; + const RecastSettings& mSettings; Misc::ScopeGuarded mTiles; std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; diff --git a/components/sceneutil/agentpath.cpp b/components/sceneutil/agentpath.cpp index 5721110f77..db026a3332 100644 --- a/components/sceneutil/agentpath.cpp +++ b/components/sceneutil/agentpath.cpp @@ -37,7 +37,7 @@ namespace SceneUtil { osg::ref_ptr createAgentPathGroup(const std::deque& path, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, - const DetourNavigator::Settings& settings) + const DetourNavigator::RecastSettings& settings) { using namespace DetourNavigator; diff --git a/components/sceneutil/agentpath.hpp b/components/sceneutil/agentpath.hpp index a8965d852e..1194fa512a 100644 --- a/components/sceneutil/agentpath.hpp +++ b/components/sceneutil/agentpath.hpp @@ -13,14 +13,14 @@ namespace osg namespace DetourNavigator { - struct Settings; + struct RecastSettings; } namespace SceneUtil { osg::ref_ptr createAgentPathGroup(const std::deque& path, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, - const DetourNavigator::Settings& settings); + const DetourNavigator::RecastSettings& settings); } #endif diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp index d66f95381c..eac3d17156 100644 --- a/components/sceneutil/navmesh.cpp +++ b/components/sceneutil/navmesh.cpp @@ -254,9 +254,9 @@ namespace SceneUtil osg::ref_ptr group(new osg::Group); group->setStateSet(groupStateSet); constexpr float shift = 10.0f; - DebugDraw debugDraw(*group, debugDrawStateSet, osg::Vec3f(0, 0, shift), 1.0f / settings.mRecastScaleFactor); + DebugDraw debugDraw(*group, debugDrawStateSet, osg::Vec3f(0, 0, shift), 1.0f / settings.mRecast.mRecastScaleFactor); dtNavMeshQuery navMeshQuery; - navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); + navMeshQuery.init(&navMesh, settings.mDetour.mMaxNavMeshQueryNodes); drawMeshTile(&debugDraw, navMesh, &navMeshQuery, &meshTile, DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); return group; diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index 9614673a64..d320624682 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -42,7 +42,7 @@ namespace namespace SceneUtil { osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, - const DetourNavigator::Settings& settings) + const DetourNavigator::RecastSettings& settings) { using namespace DetourNavigator; diff --git a/components/sceneutil/recastmesh.hpp b/components/sceneutil/recastmesh.hpp index ee5d9865e5..674b5b1d2a 100644 --- a/components/sceneutil/recastmesh.hpp +++ b/components/sceneutil/recastmesh.hpp @@ -11,13 +11,13 @@ namespace osg namespace DetourNavigator { class RecastMesh; - struct Settings; + struct RecastSettings; } namespace SceneUtil { osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, - const DetourNavigator::Settings& settings); + const DetourNavigator::RecastSettings& settings); } #endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index cef627acd4..7fa625e4ab 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -79,6 +79,15 @@ int Manager::getInt (const std::string& setting, const std::string& category) return number; } +std::int64_t Manager::getInt64 (const std::string& setting, const std::string& category) +{ + const std::string& value = getString(setting, category); + std::stringstream stream(value); + std::size_t number = 0; + stream >> number; + return number; +} + bool Manager::getBool (const std::string& setting, const std::string& category) { const std::string& string = getString(setting, category); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 21d5aff770..a4b1cf3a54 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -51,6 +51,7 @@ namespace Settings ///< returns the list of changed settings intersecting with the filter static int getInt (const std::string& setting, const std::string& category); + static std::int64_t getInt64 (const std::string& setting, const std::string& category); static float getFloat (const std::string& setting, const std::string& category); static double getDouble (const std::string& setting, const std::string& category); static std::string getString (const std::string& setting, const std::string& category); diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index aea817530e..f8ab4522d6 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -365,20 +365,20 @@ max verts per poly The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process. -region merge size +region merge area ----------------- :Type: integer :Range: >= 0 -:Default: 20 +:Default: 400 Any regions with a span count smaller than this value will, if possible, be merged with larger regions. -region min size +region min area --------------- :Type: integer :Range: >= 0 -:Default: 8 +:Default: 64 The minimum number of cells allowed to form isolated island areas. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9715e3793e..084aad4fe3 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -876,10 +876,10 @@ max polygons per tile = 4096 max verts per poly = 6 # Any regions with a span count smaller than this value will, if possible, be merged with larger regions. (value >= 0) -region merge size = 20 +region merge area = 400 # The minimum number of cells allowed to form isolated island areas. (value >= 0) -region min size = 8 +region min area = 64 # Number of background threads to update nav mesh (value >= 1) async nav mesh updater threads = 1 From 5325495f464462826f9b062ee42f4a001e2cc6b4 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Oct 2021 17:01:50 +0200 Subject: [PATCH 1841/2859] Move serialization detournavigator/ -> components/ --- apps/openmw_test_suite/CMakeLists.txt | 9 +++++---- .../serialization/binaryreader.cpp | 6 +++--- .../serialization/binarywriter.cpp | 6 +++--- .../{detournavigator => }/serialization/format.hpp | 8 ++++---- .../serialization/integration.cpp | 10 +++++----- .../serialization/sizeaccumulator.cpp | 6 +++--- .../serialization/binaryreader.hpp | 6 +++--- .../serialization/binarywriter.hpp | 6 +++--- .../{detournavigator => }/serialization/format.hpp | 9 ++++++--- .../serialization/sizeaccumulator.hpp | 6 +++--- 10 files changed, 38 insertions(+), 34 deletions(-) rename apps/openmw_test_suite/{detournavigator => }/serialization/binaryreader.cpp (93%) rename apps/openmw_test_suite/{detournavigator => }/serialization/binarywriter.cpp (91%) rename apps/openmw_test_suite/{detournavigator => }/serialization/format.hpp (89%) rename apps/openmw_test_suite/{detournavigator => }/serialization/integration.cpp (85%) rename apps/openmw_test_suite/{detournavigator => }/serialization/sizeaccumulator.cpp (85%) rename components/{detournavigator => }/serialization/binaryreader.hpp (89%) rename components/{detournavigator => }/serialization/binarywriter.hpp (89%) rename components/{detournavigator => }/serialization/format.hpp (90%) rename components/{detournavigator => }/serialization/sizeaccumulator.hpp (81%) diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 7cfac20421..57e05b8511 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -41,10 +41,11 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp detournavigator/tilecachedrecastmeshmanager.cpp - detournavigator/serialization/binaryreader.cpp - detournavigator/serialization/binarywriter.cpp - detournavigator/serialization/sizeaccumulator.cpp - detournavigator/serialization/integration.cpp + + serialization/binaryreader.cpp + serialization/binarywriter.cpp + serialization/sizeaccumulator.cpp + serialization/integration.cpp settings/parser.cpp diff --git a/apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp b/apps/openmw_test_suite/serialization/binaryreader.cpp similarity index 93% rename from apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp rename to apps/openmw_test_suite/serialization/binaryreader.cpp index d071326cf5..cb4f5c57bd 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp +++ b/apps/openmw_test_suite/serialization/binaryreader.cpp @@ -1,6 +1,6 @@ #include "format.hpp" -#include +#include #include #include @@ -12,8 +12,8 @@ namespace { using namespace testing; - using namespace DetourNavigator::Serialization; - using namespace DetourNavigator::SerializationTesting; + using namespace Serialization; + using namespace SerializationTesting; TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeValue) { diff --git a/apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp b/apps/openmw_test_suite/serialization/binarywriter.cpp similarity index 91% rename from apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp rename to apps/openmw_test_suite/serialization/binarywriter.cpp index fccc2be3da..cb0f29ba85 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp +++ b/apps/openmw_test_suite/serialization/binarywriter.cpp @@ -1,6 +1,6 @@ #include "format.hpp" -#include +#include #include #include @@ -12,8 +12,8 @@ namespace { using namespace testing; - using namespace DetourNavigator::Serialization; - using namespace DetourNavigator::SerializationTesting; + using namespace Serialization; + using namespace SerializationTesting; TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeValue) { diff --git a/apps/openmw_test_suite/detournavigator/serialization/format.hpp b/apps/openmw_test_suite/serialization/format.hpp similarity index 89% rename from apps/openmw_test_suite/detournavigator/serialization/format.hpp rename to apps/openmw_test_suite/serialization/format.hpp index 7c5e26a0be..8f61838fde 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/format.hpp +++ b/apps/openmw_test_suite/serialization/format.hpp @@ -1,12 +1,12 @@ -#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H -#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H +#ifndef OPENMW_TEST_SUITE_SERIALIZATION_FORMAT_H +#define OPENMW_TEST_SUITE_SERIALIZATION_FORMAT_H -#include +#include #include #include -namespace DetourNavigator::SerializationTesting +namespace SerializationTesting { struct Pod { diff --git a/apps/openmw_test_suite/detournavigator/serialization/integration.cpp b/apps/openmw_test_suite/serialization/integration.cpp similarity index 85% rename from apps/openmw_test_suite/detournavigator/serialization/integration.cpp rename to apps/openmw_test_suite/serialization/integration.cpp index e7e8eacc20..cb8c711c67 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/integration.cpp +++ b/apps/openmw_test_suite/serialization/integration.cpp @@ -1,8 +1,8 @@ #include "format.hpp" -#include -#include -#include +#include +#include +#include #include #include @@ -12,8 +12,8 @@ namespace { using namespace testing; - using namespace DetourNavigator::Serialization; - using namespace DetourNavigator::SerializationTesting; + using namespace Serialization; + using namespace SerializationTesting; struct DetourNavigatorSerializationIntegrationTest : Test { diff --git a/apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp b/apps/openmw_test_suite/serialization/sizeaccumulator.cpp similarity index 85% rename from apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp rename to apps/openmw_test_suite/serialization/sizeaccumulator.cpp index 39b7ea8646..dce148468a 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp +++ b/apps/openmw_test_suite/serialization/sizeaccumulator.cpp @@ -1,6 +1,6 @@ #include "format.hpp" -#include +#include #include @@ -12,8 +12,8 @@ namespace { using namespace testing; - using namespace DetourNavigator::Serialization; - using namespace DetourNavigator::SerializationTesting; + using namespace Serialization; + using namespace SerializationTesting; TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticType) { diff --git a/components/detournavigator/serialization/binaryreader.hpp b/components/serialization/binaryreader.hpp similarity index 89% rename from components/detournavigator/serialization/binaryreader.hpp rename to components/serialization/binaryreader.hpp index 0d75c3ac99..65b1e2f9d8 100644 --- a/components/detournavigator/serialization/binaryreader.hpp +++ b/components/serialization/binaryreader.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYREADER_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYREADER_H +#ifndef OPENMW_COMPONENTS_SERIALIZATION_BINARYREADER_H +#define OPENMW_COMPONENTS_SERIALIZATION_BINARYREADER_H #include #include @@ -7,7 +7,7 @@ #include #include -namespace DetourNavigator::Serialization +namespace Serialization { class BinaryReader { diff --git a/components/detournavigator/serialization/binarywriter.hpp b/components/serialization/binarywriter.hpp similarity index 89% rename from components/detournavigator/serialization/binarywriter.hpp rename to components/serialization/binarywriter.hpp index 5e710d85d5..de549c7e02 100644 --- a/components/detournavigator/serialization/binarywriter.hpp +++ b/components/serialization/binarywriter.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYWRITER_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYWRITER_H +#ifndef OPENMW_COMPONENTS_SERIALIZATION_BINARYWRITER_H +#define OPENMW_COMPONENTS_SERIALIZATION_BINARYWRITER_H #include #include @@ -7,7 +7,7 @@ #include #include -namespace DetourNavigator::Serialization +namespace Serialization { struct BinaryWriter { diff --git a/components/detournavigator/serialization/format.hpp b/components/serialization/format.hpp similarity index 90% rename from components/detournavigator/serialization/format.hpp rename to components/serialization/format.hpp index d07ab9da6f..5d287e80cf 100644 --- a/components/detournavigator/serialization/format.hpp +++ b/components/serialization/format.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H +#ifndef OPENMW_COMPONENTS_SERIALIZATION_FORMAT_H +#define OPENMW_COMPONENTS_SERIALIZATION_FORMAT_H #include #include @@ -8,7 +8,7 @@ #include #include -namespace DetourNavigator::Serialization +namespace Serialization { enum class Mode { @@ -22,6 +22,9 @@ namespace DetourNavigator::Serialization template struct IsContiguousContainer> : std::true_type {}; + template + struct IsContiguousContainer> : std::true_type {}; + template constexpr bool isContiguousContainer = IsContiguousContainer>::value; diff --git a/components/detournavigator/serialization/sizeaccumulator.hpp b/components/serialization/sizeaccumulator.hpp similarity index 81% rename from components/detournavigator/serialization/sizeaccumulator.hpp rename to components/serialization/sizeaccumulator.hpp index 28bdb5c1cb..2386fe7bea 100644 --- a/components/detournavigator/serialization/sizeaccumulator.hpp +++ b/components/serialization/sizeaccumulator.hpp @@ -1,10 +1,10 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_SIZEACCUMULATOR_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_SIZEACCUMULATOR_H +#ifndef OPENMW_COMPONENTS_SERIALIZATION_SIZEACCUMULATOR_H +#define OPENMW_COMPONENTS_SERIALIZATION_SIZEACCUMULATOR_H #include #include -namespace DetourNavigator::Serialization +namespace Serialization { class SizeAccumulator { From 23ad1b2b9fef5fd86bbb84ff97ea47168ddf75c0 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 8 Nov 2021 19:25:42 +0100 Subject: [PATCH 1842/2859] Move enum related serialization logic from format to visitors --- components/serialization/binaryreader.hpp | 18 +++++++++++------- components/serialization/binarywriter.hpp | 14 +++++++++----- components/serialization/format.hpp | 16 +--------------- components/serialization/sizeaccumulator.hpp | 4 ++-- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/components/serialization/binaryreader.hpp b/components/serialization/binaryreader.hpp index 65b1e2f9d8..c1a9031676 100644 --- a/components/serialization/binaryreader.hpp +++ b/components/serialization/binaryreader.hpp @@ -23,12 +23,14 @@ namespace Serialization template void operator()(Format&& format, T& value) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_enum_v) + (*this)(std::forward(format), static_cast&>(value)); + else if constexpr (std::is_arithmetic_v) { - if (mEnd - mPos < static_cast(sizeof(value))) + if (mEnd - mPos < static_cast(sizeof(T))) throw std::runtime_error("Not enough data"); - std::memcpy(&value, mPos, sizeof(value)); - mPos += sizeof(value); + std::memcpy(&value, mPos, sizeof(T)); + mPos += sizeof(T); } else { @@ -39,11 +41,13 @@ namespace Serialization template auto operator()(Format&& format, T* data, std::size_t count) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_enum_v) + (*this)(std::forward(format), reinterpret_cast*>(data), count); + else if constexpr (std::is_arithmetic_v) { - if (mEnd - mPos < static_cast(count * sizeof(T))) - throw std::runtime_error("Not enough data"); const std::size_t size = sizeof(T) * count; + if (mEnd - mPos < static_cast(size)) + throw std::runtime_error("Not enough data"); std::memcpy(data, mPos, size); mPos += size; } diff --git a/components/serialization/binarywriter.hpp b/components/serialization/binarywriter.hpp index de549c7e02..5250bcfdf2 100644 --- a/components/serialization/binarywriter.hpp +++ b/components/serialization/binarywriter.hpp @@ -23,12 +23,14 @@ namespace Serialization template void operator()(Format&& format, const T& value) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_enum_v) + (*this)(std::forward(format), static_cast>(value)); + else if constexpr (std::is_arithmetic_v) { - if (mEnd - mDest < static_cast(sizeof(value))) + if (mEnd - mDest < static_cast(sizeof(T))) throw std::runtime_error("Not enough space"); - std::memcpy(mDest, &value, sizeof(value)); - mDest += sizeof(value); + std::memcpy(mDest, &value, sizeof(T)); + mDest += sizeof(T); } else { @@ -39,7 +41,9 @@ namespace Serialization template auto operator()(Format&& format, const T* data, std::size_t count) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_enum_v) + (*this)(std::forward(format), reinterpret_cast*>(data), count); + else if constexpr (std::is_arithmetic_v) { const std::size_t size = sizeof(T) * count; if (mEnd - mDest < static_cast(size)) diff --git a/components/serialization/format.hpp b/components/serialization/format.hpp index 5d287e80cf..595afd0dad 100644 --- a/components/serialization/format.hpp +++ b/components/serialization/format.hpp @@ -34,24 +34,10 @@ namespace Serialization template void operator()(Visitor&& visitor, T* data, std::size_t size) const { - if constexpr (std::is_arithmetic_v) - { + if constexpr (std::is_arithmetic_v || std::is_enum_v) visitor(self(), data, size); - } - else if constexpr (std::is_enum_v) - { - if constexpr (mode == Mode::Write) - visitor(self(), reinterpret_cast*>(data), size); - else - { - static_assert(mode == Mode::Read); - visitor(self(), reinterpret_cast*>(data), size); - } - } else - { std::for_each(data, data + size, [&] (auto& v) { visitor(self(), v); }); - } } template diff --git a/components/serialization/sizeaccumulator.hpp b/components/serialization/sizeaccumulator.hpp index 2386fe7bea..4dcc004f73 100644 --- a/components/serialization/sizeaccumulator.hpp +++ b/components/serialization/sizeaccumulator.hpp @@ -18,7 +18,7 @@ namespace Serialization template void operator()(Format&& format, const T& value) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_arithmetic_v || std::is_enum_v) mValue += sizeof(T); else format(*this, value); @@ -27,7 +27,7 @@ namespace Serialization template auto operator()(Format&& format, const T* data, std::size_t count) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_arithmetic_v || std::is_enum_v) mValue += count * sizeof(T); else format(*this, data, count); From b5c689976e6271ac5f915ce060ef2945488cc5c0 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 8 Nov 2021 19:28:04 +0100 Subject: [PATCH 1843/2859] Serialize arithmetic and enum types in little endian encoding --- components/serialization/binaryreader.hpp | 6 ++++++ components/serialization/binarywriter.hpp | 23 +++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/components/serialization/binaryreader.hpp b/components/serialization/binaryreader.hpp index c1a9031676..66e09f6ffb 100644 --- a/components/serialization/binaryreader.hpp +++ b/components/serialization/binaryreader.hpp @@ -1,6 +1,9 @@ #ifndef OPENMW_COMPONENTS_SERIALIZATION_BINARYREADER_H #define OPENMW_COMPONENTS_SERIALIZATION_BINARYREADER_H +#include + +#include #include #include #include @@ -31,6 +34,7 @@ namespace Serialization throw std::runtime_error("Not enough data"); std::memcpy(&value, mPos, sizeof(T)); mPos += sizeof(T); + value = Misc::toLittleEndian(value); } else { @@ -50,6 +54,8 @@ namespace Serialization throw std::runtime_error("Not enough data"); std::memcpy(data, mPos, size); mPos += size; + if constexpr (!Misc::IS_LITTLE_ENDIAN) + std::for_each(data, data + count, [&] (T& v) { v = Misc::fromLittleEndian(v); }); } else { diff --git a/components/serialization/binarywriter.hpp b/components/serialization/binarywriter.hpp index 5250bcfdf2..199f208da9 100644 --- a/components/serialization/binarywriter.hpp +++ b/components/serialization/binarywriter.hpp @@ -1,6 +1,9 @@ #ifndef OPENMW_COMPONENTS_SERIALIZATION_BINARYWRITER_H #define OPENMW_COMPONENTS_SERIALIZATION_BINARYWRITER_H +#include + +#include #include #include #include @@ -29,8 +32,7 @@ namespace Serialization { if (mEnd - mDest < static_cast(sizeof(T))) throw std::runtime_error("Not enough space"); - std::memcpy(mDest, &value, sizeof(T)); - mDest += sizeof(T); + writeValue(value); } else { @@ -48,8 +50,13 @@ namespace Serialization const std::size_t size = sizeof(T) * count; if (mEnd - mDest < static_cast(size)) throw std::runtime_error("Not enough space"); - std::memcpy(mDest, data, size); - mDest += size; + if constexpr (Misc::IS_LITTLE_ENDIAN) + { + std::memcpy(mDest, data, size); + mDest += size; + } + else + std::for_each(data, data + count, [&] (const T& v) { writeValue(v); }); } else { @@ -60,6 +67,14 @@ namespace Serialization private: std::byte* mDest; const std::byte* const mEnd; + + template + void writeValue(const T& value) noexcept + { + T coverted = Misc::toLittleEndian(value); + std::memcpy(mDest, &coverted, sizeof(T)); + mDest += sizeof(T); + } }; } From 953a4c5550db6030617f84f4d4b3c5d261f62f5c Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 29 Jun 2021 03:49:21 +0200 Subject: [PATCH 1844/2859] Add a binary to generate navmesh from content files Load content files based on the engine config files. Generate navmesh per cell for all cells and store into SQLite database. --- .gitlab-ci.yml | 2 +- CI/before_script.android.sh | 1 + CI/before_script.linux.sh | 1 + CMakeLists.txt | 12 + apps/navmeshtool/CMakeLists.txt | 22 ++ apps/navmeshtool/main.cpp | 209 +++++++++++ apps/navmeshtool/navmesh.cpp | 212 +++++++++++ apps/navmeshtool/navmesh.hpp | 23 ++ apps/navmeshtool/worldspacedata.cpp | 329 ++++++++++++++++++ apps/navmeshtool/worldspacedata.hpp | 97 ++++++ apps/openmw_test_suite/CMakeLists.txt | 2 + .../detournavigator/generate.hpp | 51 +++ .../detournavigator/navmeshdb.cpp | 112 ++++++ .../detournavigator/navmeshtilescache.cpp | 54 +-- components/CMakeLists.txt | 3 + .../cachedrecastmeshmanager.cpp | 5 + .../cachedrecastmeshmanager.hpp | 2 + .../detournavigator/dbrefgeometryobject.hpp | 46 +++ .../detournavigator/generatenavmeshtile.cpp | 95 +++++ .../detournavigator/generatenavmeshtile.hpp | 71 ++++ components/detournavigator/makenavmesh.cpp | 45 ++- components/detournavigator/makenavmesh.hpp | 23 +- components/detournavigator/navmeshdb.cpp | 296 ++++++++++++++++ components/detournavigator/navmeshdb.hpp | 153 ++++++++ components/detournavigator/navmeshdbutils.cpp | 40 +++ components/detournavigator/navmeshdbutils.hpp | 13 + components/detournavigator/objectid.hpp | 5 + .../offmeshconnectionsmanager.cpp | 4 +- .../offmeshconnectionsmanager.hpp | 2 +- .../detournavigator/recastmeshprovider.hpp | 33 ++ components/detournavigator/serialization.cpp | 214 ++++++++++++ components/detournavigator/serialization.hpp | 27 ++ components/detournavigator/settings.cpp | 1 + components/detournavigator/settings.hpp | 1 + .../tilecachedrecastmeshmanager.cpp | 7 + .../tilecachedrecastmeshmanager.hpp | 2 + components/sqlite3/request.hpp | 8 + components/sqlite3/types.hpp | 15 + .../reference/modding/settings/game.rst | 1 + .../reference/modding/settings/navigator.rst | 11 + files/settings-default.cfg | 4 + 41 files changed, 2195 insertions(+), 59 deletions(-) create mode 100644 apps/navmeshtool/CMakeLists.txt create mode 100644 apps/navmeshtool/main.cpp create mode 100644 apps/navmeshtool/navmesh.cpp create mode 100644 apps/navmeshtool/navmesh.hpp create mode 100644 apps/navmeshtool/worldspacedata.cpp create mode 100644 apps/navmeshtool/worldspacedata.hpp create mode 100644 apps/openmw_test_suite/detournavigator/generate.hpp create mode 100644 apps/openmw_test_suite/detournavigator/navmeshdb.cpp create mode 100644 components/detournavigator/dbrefgeometryobject.hpp create mode 100644 components/detournavigator/generatenavmeshtile.cpp create mode 100644 components/detournavigator/generatenavmeshtile.hpp create mode 100644 components/detournavigator/navmeshdb.cpp create mode 100644 components/detournavigator/navmeshdb.hpp create mode 100644 components/detournavigator/navmeshdbutils.cpp create mode 100644 components/detournavigator/navmeshdbutils.hpp create mode 100644 components/detournavigator/recastmeshprovider.hpp create mode 100644 components/detournavigator/serialization.cpp create mode 100644 components/detournavigator/serialization.hpp create mode 100644 components/sqlite3/types.hpp diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 538bfa11ec..b977d86a97 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -219,7 +219,7 @@ macOS11_Xcode12: CCACHE_SIZE: 3G variables: &engine-targets - targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard" + targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard,openmw-navmeshtool" package: "Engine" variables: &cs-targets diff --git a/CI/before_script.android.sh b/CI/before_script.android.sh index bdf7d4a244..43422f68c1 100755 --- a/CI/before_script.android.sh +++ b/CI/before_script.android.sh @@ -22,6 +22,7 @@ cmake \ -DBUILD_ESSIMPORTER=0 \ -DBUILD_OPENCS=0 \ -DBUILD_WIZARD=0 \ +-DBUILD_NAVMESHTOOL=OFF \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_SQLITE3=OFF \ .. diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index b9fed204e3..19d8338721 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -71,6 +71,7 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then -DBUILD_ESSIMPORTER=OFF \ -DBUILD_OPENCS=OFF \ -DBUILD_WIZARD=OFF \ + -DBUILD_NAVMESHTOOL=OFF \ -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ -DGTEST_ROOT="${GOOGLETEST_DIR}" \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 644a7419a8..090e6c56e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ option(BUILD_DOCS "Build documentation." OFF ) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) +option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. @@ -603,6 +604,10 @@ if (BUILD_BENCHMARKS) add_subdirectory(apps/benchmarks) endif() +if (BUILD_NAVMESHTOOL) + add_subdirectory(apps/navmeshtool) +endif() + if (WIN32) if (MSVC) if (OPENMW_MP_BUILD) @@ -702,6 +707,10 @@ if (WIN32) if (BUILD_BENCHMARKS) set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() + + if (BUILD_NAVMESHTOOL) + set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() endif(MSVC) # TODO: At some point release builds should not use the console but rather write to a log file @@ -944,6 +953,9 @@ elseif(NOT APPLE) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" ) ENDIF(BUILD_WIZARD) + if(BUILD_NAVMESHTOOL) + install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" ) + endif() # Install licenses INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" ) diff --git a/apps/navmeshtool/CMakeLists.txt b/apps/navmeshtool/CMakeLists.txt new file mode 100644 index 0000000000..7df049af97 --- /dev/null +++ b/apps/navmeshtool/CMakeLists.txt @@ -0,0 +1,22 @@ +set(NAVMESHTOOL + worldspacedata.cpp + navmesh.cpp + main.cpp +) +source_group(apps\\navmeshtool FILES ${NAVMESHTOOL}) + +openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL}) + +target_link_libraries(openmw-navmeshtool + ${Boost_PROGRAM_OPTIONS_LIBRARY} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions(--coverage) + target_link_libraries(openmw-navmeshtool gcov) +endif() + +if (WIN32) + install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".") +endif() diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp new file mode 100644 index 0000000000..3e867fcbc9 --- /dev/null +++ b/apps/navmeshtool/main.cpp @@ -0,0 +1,209 @@ +#include "worldspacedata.hpp" +#include "navmesh.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + namespace + { + namespace bpo = boost::program_options; + + using StringsVector = std::vector; + + bpo::options_description makeOptionsDescription() + { + using Fallback::FallbackMap; + + bpo::options_description result; + + result.add_options() + ("help", "print help message") + + ("version", "print version information and quit") + + ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") + ->multitoken()->composing(), "set data directories (later directories have higher priority)") + + ("data-local", bpo::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""), + "set local data directory (highest priority)") + + ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") + ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") + + ("resources", bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), + "set resources directory") + + ("content", bpo::value()->default_value(StringsVector(), "") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts") + + ("fs-strict", bpo::value()->implicit_value(true) + ->default_value(false), "strict file system handling (no case folding)") + + ("encoding", bpo::value()-> + default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default") + + ("fallback", bpo::value()->default_value(Fallback::FallbackMap(), "") + ->multitoken()->composing(), "fallback values") + + ("threads", bpo::value()->default_value(std::max(std::thread::hardware_concurrency() - 1, 1)), + "number of threads for parallel processing") + + ("process-interior-cells", bpo::value()->implicit_value(true) + ->default_value(false), "build navmesh for interior cells") + ; + + return result; + } + + void loadSettings(const Files::ConfigurationManager& config, Settings::Manager& settings) + { + const std::string localDefault = (config.getLocalPath() / "defaults.bin").string(); + const std::string globalDefault = (config.getGlobalPath() / "defaults.bin").string(); + + if (boost::filesystem::exists(localDefault)) + settings.loadDefault(localDefault); + else if (boost::filesystem::exists(globalDefault)) + settings.loadDefault(globalDefault); + else + throw std::runtime_error("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); + + const std::string settingsPath = (config.getUserConfigPath() / "settings.cfg").string(); + if (boost::filesystem::exists(settingsPath)) + settings.loadUser(settingsPath); + } + + int runNavMeshTool(int argc, char *argv[]) + { + bpo::options_description desc = makeOptionsDescription(); + + bpo::parsed_options options = bpo::command_line_parser(argc, argv) + .options(desc).allow_unregistered().run(); + bpo::variables_map variables; + + bpo::store(options, variables); + bpo::notify(variables); + + if (variables.find("help") != variables.end()) + { + getRawStdout() << desc << std::endl; + return 0; + } + + Files::ConfigurationManager config; + + bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc); + config.readConfiguration(variables, desc); + Files::mergeComposingVariables(variables, composingVariables, desc); + + const std::string encoding(variables["encoding"].as()); + Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding)); + + Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); + + auto local = variables["data-local"].as(); + if (!local.empty()) + dataDirs.push_back(std::move(local)); + + config.processPaths(dataDirs); + + const auto fsStrict = variables["fs-strict"].as(); + const auto resDir = variables["resources"].as(); + Version::Version v = Version::getOpenmwVersion(resDir.string()); + Log(Debug::Info) << v.describe(); + dataDirs.insert(dataDirs.begin(), resDir / "vfs"); + const auto fileCollections = Files::Collections(dataDirs, !fsStrict); + const auto archives = variables["fallback-archive"].as(); + const auto contentFiles = variables["content"].as(); + const std::size_t threadsNumber = variables["threads"].as(); + + if (threadsNumber < 1) + { + std::cerr << "Invalid threads number: " << threadsNumber << ", expected >= 1"; + return -1; + } + + const bool processInteriorCells = variables["process-interior-cells"].as(); + + Fallback::Map::init(variables["fallback"].as().mMap); + + VFS::Manager vfs(fsStrict); + + VFS::registerArchives(&vfs, fileCollections, archives, true); + + Settings::Manager settings; + loadSettings(config, settings); + + const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game"); + + DetourNavigator::NavMeshDb db((config.getUserDataPath() / "navmesh.db").string()); + + std::vector readers(contentFiles.size()); + EsmLoader::Query query; + query.mLoadActivators = true; + query.mLoadCells = true; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = true; + query.mLoadLands = true; + query.mLoadStatics = true; + const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); + + Resource::ImageManager imageManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); + Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); + DetourNavigator::RecastGlobalAllocator::init(); + DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); + navigatorSettings.mRecast.mSwimHeightScale = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat(); + + WorldspaceData cellsData = gatherWorldspaceData(navigatorSettings, readers, vfs, bulletShapeManager, + esmData, processInteriorCells); + + generateAllNavMeshTiles(agentHalfExtents, navigatorSettings, threadsNumber, cellsData, std::move(db)); + + Log(Debug::Info) << "Done"; + + return 0; + } + } +} + +int main(int argc, char *argv[]) +{ + return wrapApplication(NavMeshTool::runNavMeshTool, argc, argv, "NavMeshTool"); +} diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp new file mode 100644 index 0000000000..6b8150f1c0 --- /dev/null +++ b/apps/navmeshtool/navmesh.cpp @@ -0,0 +1,212 @@ +#include "navmesh.hpp" + +#include "worldspacedata.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + namespace + { + using DetourNavigator::GenerateNavMeshTile; + using DetourNavigator::NavMeshDb; + using DetourNavigator::NavMeshTileInfo; + using DetourNavigator::PreparedNavMeshData; + using DetourNavigator::RecastMeshProvider; + using DetourNavigator::MeshSource; + using DetourNavigator::Settings; + using DetourNavigator::ShapeId; + using DetourNavigator::TileId; + using DetourNavigator::TilePosition; + using DetourNavigator::TileVersion; + using Sqlite3::Transaction; + + void logGeneratedTiles(std::size_t provided, std::size_t expected) + { + Log(Debug::Info) << provided << "/" << expected << " (" + << (static_cast(provided) / static_cast(expected) * 100) + << "%) navmesh tiles are generated"; + } + + struct LogGeneratedTiles + { + void operator()(std::size_t provided, std::size_t expected) const + { + logGeneratedTiles(provided, expected); + } + }; + + class NavMeshTileConsumer final : public DetourNavigator::NavMeshTileConsumer + { + public: + std::atomic_size_t mExpected {0}; + + explicit NavMeshTileConsumer(NavMeshDb db) + : mDb(std::move(db)) + , mTransaction(mDb.startTransaction()) + , mNextTileId(mDb.getMaxTileId() + 1) + , mNextShapeId(mDb.getMaxShapeId() + 1) + {} + + std::size_t getProvided() const { return mProvided.load(); } + + std::size_t getInserted() const { return mInserted.load(); } + + std::size_t getUpdated() const { return mUpdated.load(); } + + std::int64_t resolveMeshSource(const MeshSource& source) override + { + const std::lock_guard lock(mMutex); + return DetourNavigator::resolveMeshSource(mDb, source, mNextShapeId); + } + + std::optional find(const std::string& worldspace, const TilePosition &tilePosition, + const std::vector &input) override + { + std::optional result; + std::lock_guard lock(mMutex); + if (const auto tile = mDb.findTile(worldspace, tilePosition, input)) + { + NavMeshTileInfo info; + info.mTileId = tile->mTileId; + info.mVersion = tile->mVersion; + result.emplace(info); + } + return result; + } + + void ignore() override { report(); } + + void insert(const std::string& worldspace, const TilePosition& tilePosition, std::int64_t version, + const std::vector& input, PreparedNavMeshData& data) override + { + data.mUserId = static_cast(mNextTileId); + { + std::lock_guard lock(mMutex); + mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data)); + ++mNextTileId.t; + } + ++mInserted; + report(); + } + + void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) override + { + data.mUserId = static_cast(tileId); + { + std::lock_guard lock(mMutex); + mDb.updateTile(TileId {tileId}, TileVersion {version}, serialize(data)); + } + ++mUpdated; + report(); + } + + void wait() + { + constexpr std::size_t tilesPerTransaction = 3000; + std::unique_lock lock(mMutex); + while (mProvided < mExpected) + { + mHasTile.wait(lock); + if (mProvided % tilesPerTransaction == 0) + { + mTransaction.commit(); + mTransaction = mDb.startTransaction(); + } + } + logGeneratedTiles(mProvided, mExpected); + } + + void commit() { mTransaction.commit(); } + + private: + std::atomic_size_t mProvided {0}; + std::atomic_size_t mInserted {0}; + std::atomic_size_t mUpdated {0}; + std::mutex mMutex; + NavMeshDb mDb; + Transaction mTransaction; + TileId mNextTileId; + std::condition_variable mHasTile; + Misc::ProgressReporter mReporter; + ShapeId mNextShapeId; + + void report() + { + mReporter(mProvided + 1, mExpected); + ++mProvided; + mHasTile.notify_one(); + } + }; + } + + void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings, + const std::size_t threadsNumber, WorldspaceData& data, NavMeshDb&& db) + { + Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers..."; + + SceneUtil::WorkQueue workQueue(threadsNumber); + auto navMeshTileConsumer = std::make_shared(std::move(db)); + std::size_t tiles = 0; + + for (const std::unique_ptr& input : data.mNavMeshInputs) + { + DetourNavigator::getTilesPositions( + Misc::Convert::toOsg(input->mAabb.m_min), Misc::Convert::toOsg(input->mAabb.m_max), settings.mRecast, + [&] (const TilePosition& tilePosition) + { + workQueue.addWorkItem(new GenerateNavMeshTile( + input->mWorldspace, + tilePosition, + RecastMeshProvider(input->mTileCachedRecastMeshManager), + agentHalfExtents, + settings, + navMeshTileConsumer + )); + + ++tiles; + }); + + navMeshTileConsumer->mExpected = tiles; + } + + navMeshTileConsumer->wait(); + navMeshTileConsumer->commit(); + + Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, " + << navMeshTileConsumer->getInserted() << " are inserted and " + << navMeshTileConsumer->getUpdated() << " updated"; + } +} diff --git a/apps/navmeshtool/navmesh.hpp b/apps/navmeshtool/navmesh.hpp new file mode 100644 index 0000000000..725f0cd6a4 --- /dev/null +++ b/apps/navmeshtool/navmesh.hpp @@ -0,0 +1,23 @@ +#ifndef OPENMW_NAVMESHTOOL_NAVMESH_H +#define OPENMW_NAVMESHTOOL_NAVMESH_H + +#include + +#include +#include + +namespace DetourNavigator +{ + class NavMeshDb; + struct Settings; +} + +namespace NavMeshTool +{ + struct WorldspaceData; + + void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const DetourNavigator::Settings& settings, + const std::size_t threadsNumber, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db); +} + +#endif diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp new file mode 100644 index 0000000000..7c5aa06c83 --- /dev/null +++ b/apps/navmeshtool/worldspacedata.cpp @@ -0,0 +1,329 @@ +#include "worldspacedata.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + namespace + { + using DetourNavigator::CollisionShape; + using DetourNavigator::HeightfieldPlane; + using DetourNavigator::HeightfieldShape; + using DetourNavigator::HeightfieldSurface; + using DetourNavigator::ObjectId; + using DetourNavigator::ObjectTransform; + + struct CellRef + { + ESM::RecNameInts mType; + ESM::RefNum mRefNum; + std::string mRefId; + float mScale; + ESM::Position mPos; + + CellRef(ESM::RecNameInts type, ESM::RefNum refNum, std::string&& refId, float scale, const ESM::Position& pos) + : mType(type), mRefNum(refNum), mRefId(std::move(refId)), mScale(scale), mPos(pos) {} + }; + + ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, std::string_view refId) + { + const auto it = std::lower_bound(esmData.mRefIdTypes.begin(), esmData.mRefIdTypes.end(), + refId, EsmLoader::LessById {}); + if (it == esmData.mRefIdTypes.end() || it->mId != refId) + return {}; + return it->mType; + } + + std::vector loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, + std::vector& readers) + { + std::vector> cellRefs; + + for (std::size_t i = 0; i < cell.mContextList.size(); i++) + { + ESM::ESMReader& reader = readers[static_cast(cell.mContextList[i].index)]; + cell.restore(reader, static_cast(i)); + ESM::CellRef cellRef; + bool deleted = false; + while (ESM::Cell::getNextRef(reader, cellRef, deleted)) + { + Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID); + const ESM::RecNameInts type = getType(esmData, cellRef.mRefID); + if (type == ESM::RecNameInts {}) + continue; + cellRefs.emplace_back(deleted, type, cellRef.mRefNum, std::move(cellRef.mRefID), + cellRef.mScale, cellRef.mPos); + } + } + + Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs"; + + const auto getKey = [] (const EsmLoader::Record& v) -> const ESM::RefNum& { return v.mValue.mRefNum; }; + std::vector result = prepareRecords(cellRefs, getKey); + + Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs"; + + return result; + } + + template + void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, + Resource::BulletShapeManager& bulletShapeManager, std::vector& readers, + F&& f) + { + std::vector cellRefs = loadCellRefs(cell, esmData, readers); + + Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs"; + + for (CellRef& cellRef : cellRefs) + { + std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType)); + if (model.empty()) + continue; + + if (cellRef.mType != ESM::REC_STAT) + model = Misc::ResourceHelpers::correctActorModelPath(model, &vfs); + + osg::ref_ptr shape = [&] + { + try + { + return bulletShapeManager.getShape("meshes/" + model); + } + catch (const std::exception& e) + { + Log(Debug::Warning) << "Failed to load cell ref \"" << cellRef.mRefId << "\" model \"" << model << "\": " << e.what(); + return osg::ref_ptr(); + } + } (); + + if (shape == nullptr || shape->mCollisionShape == nullptr) + continue; + + osg::ref_ptr shapeInstance(new Resource::BulletShapeInstance(std::move(shape))); + + switch (cellRef.mType) + { + case ESM::REC_ACTI: + case ESM::REC_CONT: + case ESM::REC_DOOR: + case ESM::REC_STAT: + f(BulletObject(std::move(shapeInstance), cellRef.mPos, cellRef.mScale)); + break; + default: + break; + } + } + } + + struct GetXY + { + osg::Vec2i operator()(const ESM::Land& value) const { return osg::Vec2i(value.mX, value.mY); } + }; + + struct LessByXY + { + bool operator ()(const ESM::Land& lhs, const ESM::Land& rhs) const + { + return GetXY {}(lhs) < GetXY {}(rhs); + } + + bool operator ()(const ESM::Land& lhs, const osg::Vec2i& rhs) const + { + return GetXY {}(lhs) < rhs; + } + + bool operator ()(const osg::Vec2i& lhs, const ESM::Land& rhs) const + { + return lhs < GetXY {}(rhs); + } + }; + + btAABB getAabb(const osg::Vec2i& cellPosition, btScalar minHeight, btScalar maxHeight) + { + btAABB aabb; + aabb.m_min = btVector3( + static_cast(cellPosition.x() * ESM::Land::REAL_SIZE), + static_cast(cellPosition.y() * ESM::Land::REAL_SIZE), + minHeight + ); + aabb.m_min = btVector3( + static_cast((cellPosition.x() + 1) * ESM::Land::REAL_SIZE), + static_cast((cellPosition.y() + 1) * ESM::Land::REAL_SIZE), + maxHeight + ); + return aabb; + } + + void mergeOrAssign(const btAABB& aabb, btAABB& target, bool& initialized) + { + if (initialized) + return target.merge(aabb); + + target.m_min = aabb.m_min; + target.m_max = aabb.m_max; + initialized = true; + } + + std::tuple makeHeightfieldShape(const std::optional& land, + const osg::Vec2i& cellPosition, std::vector>& heightfields, + std::vector>& landDatas) + { + if (!land.has_value() || osg::Vec2i(land->mX, land->mY) != cellPosition + || (land->mDataTypes & ESM::Land::DATA_VHGT) == 0) + return {HeightfieldPlane {ESM::Land::DEFAULT_HEIGHT}, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT}; + + ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique()); + land->loadData(ESM::Land::DATA_VHGT, &landData); + heightfields.emplace_back(std::vector(std::begin(landData.mHeights), std::end(landData.mHeights))); + HeightfieldSurface surface; + surface.mHeights = heightfields.back().data(); + surface.mMinHeight = landData.mMinHeight; + surface.mMaxHeight = landData.mMaxHeight; + surface.mSize = static_cast(ESM::Land::LAND_SIZE); + return {surface, landData.mMinHeight, landData.mMaxHeight}; + } + } + + WorldspaceNavMeshInput::WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings) + : mWorldspace(std::move(worldspace)) + , mTileCachedRecastMeshManager(settings) + { + mAabb.m_min = btVector3(0, 0, 0); + mAabb.m_max = btVector3(0, 0, 0); + } + + WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector& readers, + const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, + bool processInteriorCells) + { + Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells..."; + + std::map> navMeshInputs; + WorldspaceData data; + + std::size_t objectsCounter = 0; + + for (std::size_t i = 0; i < esmData.mCells.size(); ++i) + { + const ESM::Cell& cell = esmData.mCells[i]; + const bool exterior = cell.isExterior(); + + if (!exterior && !processInteriorCells) + { + Log(Debug::Info) << "Skipped " << (exterior ? "exterior" : "interior") + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; + continue; + } + + Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior") + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; + + const osg::Vec2i cellPosition(cell.mData.mX, cell.mData.mY); + const std::size_t cellObjectsBegin = data.mObjects.size(); + + WorldspaceNavMeshInput& navMeshInput = [&] () -> WorldspaceNavMeshInput& + { + auto it = navMeshInputs.find(cell.mCellId.mWorldspace); + if (it == navMeshInputs.end()) + { + it = navMeshInputs.emplace(cell.mCellId.mWorldspace, + std::make_unique(cell.mCellId.mWorldspace, settings.mRecast)).first; + } + return *it->second; + } (); + + if (exterior) + { + const auto it = std::lower_bound(esmData.mLands.begin(), esmData.mLands.end(), cellPosition, LessByXY {}); + const auto [heightfieldShape, minHeight, maxHeight] = makeHeightfieldShape( + it == esmData.mLands.end() ? std::optional() : *it, + cellPosition, data.mHeightfields, data.mLandData + ); + + mergeOrAssign(getAabb(cellPosition, minHeight, maxHeight), + navMeshInput.mAabb, navMeshInput.mAabbInitialized); + + navMeshInput.mTileCachedRecastMeshManager.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, heightfieldShape); + + navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1); + } + else + { + if ((cell.mData.mFlags & ESM::Cell::HasWater) != 0) + navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, std::numeric_limits::max(), cell.mWater); + } + + forEachObject(cell, esmData, vfs, bulletShapeManager, readers, + [&] (BulletObject object) + { + const btTransform& transform = object.getCollisionObject().getWorldTransform(); + const btAABB aabb = BulletHelpers::getAabb(*object.getCollisionObject().getCollisionShape(), transform); + mergeOrAssign(aabb, navMeshInput.mAabb, navMeshInput.mAabbInitialized); + if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) + navMeshInput.mAabb.merge(BulletHelpers::getAabb(*avoid, transform)); + + const ObjectId objectId(++objectsCounter); + const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform()); + + navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, DetourNavigator::AreaType_ground); + + if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) + { + const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform()); + navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, DetourNavigator::AreaType_null); + } + + data.mObjects.emplace_back(std::move(object)); + }); + + Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior") + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") " << cell.getDescription() + << " with " << (data.mObjects.size() - cellObjectsBegin) << " objects"; + } + + data.mNavMeshInputs.reserve(navMeshInputs.size()); + std::transform(navMeshInputs.begin(), navMeshInputs.end(), std::back_inserter(data.mNavMeshInputs), + [] (auto& v) { return std::move(v.second); }); + + Log(Debug::Info) << "Processed " << esmData.mCells.size() << " cells, added " + << data.mObjects.size() << " objects and " << data.mHeightfields.size() << " height fields"; + + return data; + } +} diff --git a/apps/navmeshtool/worldspacedata.hpp b/apps/navmeshtool/worldspacedata.hpp new file mode 100644 index 0000000000..3dccd5a8bc --- /dev/null +++ b/apps/navmeshtool/worldspacedata.hpp @@ -0,0 +1,97 @@ +#ifndef OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H +#define OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace ESM +{ + class ESMReader; +} + +namespace VFS +{ + class Manager; +} + +namespace Resource +{ + class BulletShapeManager; +} + +namespace EsmLoader +{ + struct EsmData; +} + +namespace DetourNavigator +{ + struct Settings; +} + +namespace NavMeshTool +{ + using DetourNavigator::TileCachedRecastMeshManager; + using DetourNavigator::ObjectTransform; + + struct WorldspaceNavMeshInput + { + std::string mWorldspace; + TileCachedRecastMeshManager mTileCachedRecastMeshManager; + btAABB mAabb; + bool mAabbInitialized = false; + + explicit WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings); + }; + + class BulletObject + { + public: + BulletObject(osg::ref_ptr&& shapeInstance, const ESM::Position& position, + float localScaling) + : mShapeInstance(std::move(shapeInstance)) + , mObjectTransform {position, localScaling} + , mCollisionObject(BulletHelpers::makeCollisionObject( + mShapeInstance->mCollisionShape.get(), + Misc::Convert::toBullet(position.asVec3()), + Misc::Convert::toBullet(Misc::Convert::makeOsgQuat(position)) + )) + { + mShapeInstance->setLocalScaling(btVector3(localScaling, localScaling, localScaling)); + } + + const osg::ref_ptr& getShapeInstance() const noexcept { return mShapeInstance; } + const DetourNavigator::ObjectTransform& getObjectTransform() const noexcept { return mObjectTransform; } + btCollisionObject& getCollisionObject() const noexcept { return *mCollisionObject; } + + private: + osg::ref_ptr mShapeInstance; + DetourNavigator::ObjectTransform mObjectTransform; + std::unique_ptr mCollisionObject; + }; + + struct WorldspaceData + { + std::vector> mNavMeshInputs; + std::vector mObjects; + std::vector> mLandData; + std::vector> mHeightfields; + }; + + WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector& readers, + const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, + bool processInteriorCells); +} + +#endif diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 57e05b8511..6a93eaa397 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -41,6 +41,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp detournavigator/tilecachedrecastmeshmanager.cpp + detournavigator/navmeshdb.cpp + detournavigator/serialization.cpp serialization/binaryreader.cpp serialization/binarywriter.cpp diff --git a/apps/openmw_test_suite/detournavigator/generate.hpp b/apps/openmw_test_suite/detournavigator/generate.hpp new file mode 100644 index 0000000000..52d04495a7 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/generate.hpp @@ -0,0 +1,51 @@ +#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H +#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H + +#include +#include +#include +#include + +namespace DetourNavigator +{ + namespace Tests + { + template + inline auto generateValue(T& value, Random& random) + -> std::enable_if_t= 2> + { + using Distribution = std::conditional_t< + std::is_floating_point_v, + std::uniform_real_distribution, + std::uniform_int_distribution + >; + Distribution distribution(std::numeric_limits::min(), std::numeric_limits::max()); + value = distribution(random); + } + + template + inline auto generateValue(T& value, Random& random) + -> std::enable_if_t + { + unsigned short v; + generateValue(v, random); + value = static_cast(v % 256); + } + + template + inline void generateValue(unsigned char& value, Random& random) + { + unsigned short v; + generateValue(v, random); + value = static_cast(v % 256); + } + + template + inline void generateRange(I begin, I end, Random& random) + { + std::for_each(begin, end, [&] (auto& v) { generateValue(v, random); }); + } + } +} + +#endif diff --git a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp new file mode 100644 index 0000000000..feadc2f59d --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp @@ -0,0 +1,112 @@ +#include "generate.hpp" + +#include +#include + +#include + +#include +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + using namespace DetourNavigator::Tests; + + struct Tile + { + std::string mWorldspace; + TilePosition mTilePosition; + std::vector mInput; + std::vector mData; + }; + + struct DetourNavigatorNavMeshDbTest : Test + { + NavMeshDb mDb {":memory:"}; + std::minstd_rand mRandom; + + std::vector generateData() + { + std::vector data(32); + generateRange(data.begin(), data.end(), mRandom); + return data; + } + + Tile insertTile(TileId tileId, TileVersion version) + { + std::string worldspace = "sys::default"; + const TilePosition tilePosition {3, 4}; + std::vector input = generateData(); + std::vector data = generateData(); + EXPECT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); + return {std::move(worldspace), tilePosition, std::move(input), std::move(data)}; + } + }; + + TEST_F(DetourNavigatorNavMeshDbTest, get_max_tile_id_for_empty_db_should_return_zero) + { + EXPECT_EQ(mDb.getMaxTileId(), TileId {0}); + } + + TEST_F(DetourNavigatorNavMeshDbTest, inserted_tile_should_be_found_by_key) + { + const TileId tileId {146}; + const TileVersion version {1}; + const auto [worldspace, tilePosition, input, data] = insertTile(tileId, version); + const auto result = mDb.findTile(worldspace, tilePosition, input); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->mTileId, tileId); + EXPECT_EQ(result->mVersion, version); + } + + TEST_F(DetourNavigatorNavMeshDbTest, inserted_tile_should_change_max_tile_id) + { + insertTile(TileId {53}, TileVersion {1}); + EXPECT_EQ(mDb.getMaxTileId(), TileId {53}); + } + + TEST_F(DetourNavigatorNavMeshDbTest, updated_tile_should_change_data) + { + const TileId tileId {13}; + const TileVersion version {1}; + auto [worldspace, tilePosition, input, data] = insertTile(tileId, version); + generateRange(data.begin(), data.end(), mRandom); + ASSERT_EQ(mDb.updateTile(tileId, version, data), 1); + const auto row = mDb.getTileData(worldspace, tilePosition, input); + ASSERT_TRUE(row.has_value()); + EXPECT_EQ(row->mTileId, tileId); + EXPECT_EQ(row->mVersion, version); + ASSERT_FALSE(row->mData.empty()); + EXPECT_EQ(row->mData, data); + } + + TEST_F(DetourNavigatorNavMeshDbTest, on_inserted_duplicate_should_throw_exception) + { + const TileId tileId {53}; + const TileVersion version {1}; + const std::string worldspace = "sys::default"; + const TilePosition tilePosition {3, 4}; + const std::vector input = generateData(); + const std::vector data = generateData(); + ASSERT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); + EXPECT_THROW(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), std::runtime_error); + } + + TEST_F(DetourNavigatorNavMeshDbTest, inserted_duplicate_leaves_db_in_correct_state) + { + const TileId tileId {53}; + const TileVersion version {1}; + const std::string worldspace = "sys::default"; + const TilePosition tilePosition {3, 4}; + const std::vector input = generateData(); + const std::vector data = generateData(); + ASSERT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); + EXPECT_THROW(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), std::runtime_error); + EXPECT_NO_THROW(insertTile(TileId {54}, version)); + } +} diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index 2a7c5ac5a1..b47ae98233 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -1,4 +1,5 @@ #include "operators.hpp" +#include "generate.hpp" #include #include @@ -21,6 +22,7 @@ namespace { using namespace testing; using namespace DetourNavigator; + using namespace DetourNavigator::Tests; void* permRecastAlloc(int size) { @@ -30,14 +32,15 @@ namespace return result; } - template - void generate(T*& values, int size) + template + void generateRecastArray(T*& values, int size, Random& random) { values = static_cast(permRecastAlloc(size * sizeof(T))); - std::generate_n(values, static_cast(size), [] { return static_cast(std::rand()); }); + generateRange(values, values + static_cast(size), random); } - void generate(rcPolyMesh& value, int size) + template + void generate(rcPolyMesh& value, int size, Random& random) { value.nverts = size; value.maxpolys = size; @@ -45,40 +48,43 @@ namespace value.npolys = size; rcVcopy(value.bmin, osg::Vec3f(-1, -2, -3).ptr()); rcVcopy(value.bmax, osg::Vec3f(3, 2, 1).ptr()); - value.cs = 1.0f / (std::rand() % 999 + 1); - value.ch = 1.0f / (std::rand() % 999 + 1); - value.borderSize = std::rand(); - value.maxEdgeError = 1.0f / (std::rand() % 999 + 1); - generate(value.verts, getVertsLength(value)); - generate(value.polys, getPolysLength(value)); - generate(value.regs, getRegsLength(value)); - generate(value.flags, getFlagsLength(value)); - generate(value.areas, getAreasLength(value)); + generateValue(value.cs, random); + generateValue(value.ch, random); + generateValue(value.borderSize, random); + generateValue(value.maxEdgeError, random); + generateRecastArray(value.verts, getVertsLength(value), random); + generateRecastArray(value.polys, getPolysLength(value), random); + generateRecastArray(value.regs, getRegsLength(value), random); + generateRecastArray(value.flags, getFlagsLength(value), random); + generateRecastArray(value.areas, getAreasLength(value), random); } - void generate(rcPolyMeshDetail& value, int size) + template + void generate(rcPolyMeshDetail& value, int size, Random& random) { value.nmeshes = size; value.nverts = size; value.ntris = size; - generate(value.meshes, getMeshesLength(value)); - generate(value.verts, getVertsLength(value)); - generate(value.tris, getTrisLength(value)); + generateRecastArray(value.meshes, getMeshesLength(value), random); + generateRecastArray(value.verts, getVertsLength(value), random); + generateRecastArray(value.tris, getTrisLength(value), random); } - void generate(PreparedNavMeshData& value, int size) + template + void generate(PreparedNavMeshData& value, int size, Random& random) { - value.mUserId = std::rand(); - value.mCellHeight = 1.0f / (std::rand() % 999 + 1); - value.mCellSize = 1.0f / (std::rand() % 999 + 1); - generate(value.mPolyMesh, size); - generate(value.mPolyMeshDetail, size); + generateValue(value.mUserId, random); + generateValue(value.mCellHeight, random); + generateValue(value.mCellSize, random); + generate(value.mPolyMesh, size, random); + generate(value.mPolyMeshDetail, size, random); } std::unique_ptr makePeparedNavMeshData(int size) { + std::minstd_rand random; auto result = std::make_unique(); - generate(*result, size); + generate(*result, size, random); return result; } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b145f61332..be3efd1b9f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -204,6 +204,9 @@ add_component_dir(detournavigator navmeshcacheitem navigatorutils generatenavmeshtile + navmeshdb + serialization + navmeshdbutils ) add_component_dir(loadinglistener diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index a3c7f4b172..e350c5591f 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -84,6 +84,11 @@ namespace DetourNavigator return *mCached.lockConst(); } + std::shared_ptr CachedRecastMeshManager::getNewMesh() const + { + return mImpl.getMesh(); + } + bool CachedRecastMeshManager::isEmpty() const { return mImpl.isEmpty(); diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index eb18519891..b92d39efa4 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -35,6 +35,8 @@ namespace DetourNavigator std::shared_ptr getCachedMesh() const; + std::shared_ptr getNewMesh() const; + bool isEmpty() const; void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion); diff --git a/components/detournavigator/dbrefgeometryobject.hpp b/components/detournavigator/dbrefgeometryobject.hpp new file mode 100644 index 0000000000..2be44ca175 --- /dev/null +++ b/components/detournavigator/dbrefgeometryobject.hpp @@ -0,0 +1,46 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DBREFGEOMETRYOBJECT_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_DBREFGEOMETRYOBJECT_H + +#include "objecttransform.hpp" +#include "recastmesh.hpp" + +#include +#include +#include +#include + +namespace DetourNavigator +{ + struct DbRefGeometryObject + { + std::int64_t mShapeId; + ObjectTransform mObjectTransform; + + friend inline auto tie(const DbRefGeometryObject& v) + { + return std::tie(v.mShapeId, v.mObjectTransform); + } + + friend inline bool operator<(const DbRefGeometryObject& l, const DbRefGeometryObject& r) + { + return tie(l) < tie(r); + } + }; + + template + inline std::vector makeDbRefGeometryObjects(const std::vector& meshSources, + ResolveMeshSource&& resolveMeshSource) + { + std::vector result; + result.reserve(meshSources.size()); + std::transform(meshSources.begin(), meshSources.end(), std::back_inserter(result), + [&] (const MeshSource& meshSource) + { + return DbRefGeometryObject {resolveMeshSource(meshSource), meshSource.mObjectTransform}; + }); + std::sort(result.begin(), result.end()); + return result; + } +} + +#endif diff --git a/components/detournavigator/generatenavmeshtile.cpp b/components/detournavigator/generatenavmeshtile.cpp new file mode 100644 index 0000000000..9dc8e70383 --- /dev/null +++ b/components/detournavigator/generatenavmeshtile.cpp @@ -0,0 +1,95 @@ +#include "generatenavmeshtile.hpp" + +#include "dbrefgeometryobject.hpp" +#include "makenavmesh.hpp" +#include "offmeshconnectionsmanager.hpp" +#include "preparednavmeshdata.hpp" +#include "serialization.hpp" +#include "settings.hpp" +#include "tilecachedrecastmeshmanager.hpp" + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace DetourNavigator +{ + namespace + { + struct Ignore + { + std::shared_ptr mConsumer; + + ~Ignore() noexcept + { + if (mConsumer != nullptr) + mConsumer->ignore(); + } + }; + } + + GenerateNavMeshTile::GenerateNavMeshTile(std::string worldspace, const TilePosition& tilePosition, + RecastMeshProvider recastMeshProvider, const osg::Vec3f& agentHalfExtents, + const DetourNavigator::Settings& settings, std::weak_ptr consumer) + : mWorldspace(std::move(worldspace)) + , mTilePosition(tilePosition) + , mRecastMeshProvider(recastMeshProvider) + , mAgentHalfExtents(agentHalfExtents) + , mSettings(settings) + , mConsumer(std::move(consumer)) {} + + void GenerateNavMeshTile::doWork() + { + impl(); + } + + void GenerateNavMeshTile::impl() noexcept + { + const auto consumer = mConsumer.lock(); + + if (consumer == nullptr) + return; + + try + { + Ignore ignore {consumer}; + + const std::shared_ptr recastMesh = mRecastMeshProvider.getMesh(mTilePosition); + + if (recastMesh == nullptr || isEmpty(*recastMesh)) + return; + + const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return consumer->resolveMeshSource(v); }); + std::vector input = serialize(mSettings.mRecast, *recastMesh, objects); + const std::optional info = consumer->find(mWorldspace, mTilePosition, input); + + if (info.has_value() && info->mVersion == mSettings.mNavMeshVersion) + return; + + const auto data = prepareNavMeshTileData(*recastMesh, mTilePosition, mAgentHalfExtents, mSettings.mRecast); + + if (data == nullptr) + return; + + if (info.has_value()) + consumer->update(info->mTileId, mSettings.mNavMeshVersion, *data); + else + consumer->insert(mWorldspace, mTilePosition, mSettings.mNavMeshVersion, input, *data); + + ignore.mConsumer = nullptr; + } + catch (const std::exception& e) + { + Log(Debug::Warning) << "Failed to generate navmesh for worldspace \"" << mWorldspace + << "\" tile " << mTilePosition << ": " << e.what(); + } + } +} diff --git a/components/detournavigator/generatenavmeshtile.hpp b/components/detournavigator/generatenavmeshtile.hpp new file mode 100644 index 0000000000..511b8dfb8f --- /dev/null +++ b/components/detournavigator/generatenavmeshtile.hpp @@ -0,0 +1,71 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H + +#include "recastmeshprovider.hpp" +#include "tileposition.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace DetourNavigator +{ + class OffMeshConnectionsManager; + class RecastMesh; + struct NavMeshTileConsumer; + struct OffMeshConnection; + struct PreparedNavMeshData; + struct Settings; + + struct NavMeshTileInfo + { + std::int64_t mTileId; + std::int64_t mVersion; + }; + + struct NavMeshTileConsumer + { + virtual ~NavMeshTileConsumer() = default; + + virtual std::int64_t resolveMeshSource(const MeshSource& source) = 0; + + virtual std::optional find(const std::string& worldspace, const TilePosition& tilePosition, + const std::vector& input) = 0; + + virtual void ignore() = 0; + + virtual void insert(const std::string& worldspace, const TilePosition& tilePosition, + std::int64_t version, const std::vector& input, PreparedNavMeshData& data) = 0; + + virtual void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0; + }; + + class GenerateNavMeshTile final : public SceneUtil::WorkItem + { + public: + GenerateNavMeshTile(std::string worldspace, const TilePosition& tilePosition, + RecastMeshProvider recastMeshProvider, const osg::Vec3f& agentHalfExtents, const Settings& settings, + std::weak_ptr consumer); + + void doWork() final; + + private: + const std::string mWorldspace; + const TilePosition mTilePosition; + const RecastMeshProvider mRecastMeshProvider; + const osg::Vec3f mAgentHalfExtents; + const Settings& mSettings; + std::weak_ptr mConsumer; + + inline void impl() noexcept; + }; +} + +#endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 217e2b565e..4bfffb22ab 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -114,6 +114,30 @@ namespace return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;; } + struct RecastParams + { + float mSampleDist = 0; + float mSampleMaxError = 0; + int mMaxEdgeLen = 0; + int mWalkableClimb = 0; + int mWalkableHeight = 0; + int mWalkableRadius = 0; + }; + + RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents) + { + RecastParams result; + + result.mWalkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / settings.mCellHeight)); + result.mWalkableClimb = static_cast(std::floor(getMaxClimb(settings) / settings.mCellHeight)); + result.mWalkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / settings.mCellSize)); + result.mMaxEdgeLen = static_cast(std::round(static_cast(settings.mMaxEdgeLen) / settings.mCellSize)); + result.mSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : settings.mCellSize * settings.mDetailSampleDist; + result.mSampleMaxError = settings.mCellHeight * settings.mDetailSampleMaxError; + + return result; + } + void initHeightfield(rcContext& context, const TilePosition& tilePosition, float minZ, float maxZ, const RecastSettings& settings, rcHeightfield& solid) { @@ -399,27 +423,13 @@ namespace namespace DetourNavigator { - RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents) - { - RecastParams result; - - result.mWalkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / settings.mCellHeight)); - result.mWalkableClimb = static_cast(std::floor(getMaxClimb(settings) / settings.mCellHeight)); - result.mWalkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / settings.mCellSize)); - result.mMaxEdgeLen = static_cast(std::round(static_cast(settings.mMaxEdgeLen) / settings.mCellSize)); - result.mSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : settings.mCellSize * settings.mDetailSampleDist; - result.mSampleMaxError = settings.mCellHeight * settings.mDetailSampleMaxError; - - return result; - } - std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings) { - const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings); - rcContext context; + const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings); + rcHeightfield solid; initHeightfield(context, tilePosition, toNavMeshCoordinates(settings, minZ), toNavMeshCoordinates(settings, maxZ), settings, solid); @@ -549,8 +559,7 @@ namespace DetourNavigator return navMeshCacheItem->lock()->removeTile(changedTile); } - if (recastMesh->getMesh().getIndices().empty() && recastMesh->getWater().empty() - && recastMesh->getHeightfields().empty() && recastMesh->getFlatHeightfields().empty()) + if (isEmpty(*recastMesh)) { Log(Debug::Debug) << "Ignore add tile: recastMesh is empty"; return navMeshCacheItem->lock()->removeTile(changedTile); diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 989565d4bf..fccf70baad 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -14,6 +14,7 @@ #include class dtNavMesh; +struct rcConfig; namespace DetourNavigator { @@ -22,16 +23,6 @@ namespace DetourNavigator struct PreparedNavMeshData; struct NavMeshData; - struct RecastParams - { - float mSampleDist = 0; - float mSampleMaxError = 0; - int mMaxEdgeLen = 0; - int mWalkableClimb = 0; - int mWalkableHeight = 0; - int mWalkableRadius = 0; - }; - inline float getLength(const osg::Vec2i& value) { return std::sqrt(float(osg::square(value.x()) + osg::square(value.y()))); @@ -48,10 +39,16 @@ namespace DetourNavigator return expectedTilesCount <= maxTiles; } - RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents); + inline bool isEmpty(const RecastMesh& recastMesh) + { + return recastMesh.getMesh().getIndices().empty() + && recastMesh.getWater().empty() + && recastMesh.getHeightfields().empty() + && recastMesh.getFlatHeightfields().empty(); + } - std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, const TilePosition& tile, - const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings); + std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, + const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings); NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, const std::vector& offMeshConnections, const osg::Vec3f& agentHalfExtents, diff --git a/components/detournavigator/navmeshdb.cpp b/components/detournavigator/navmeshdb.cpp new file mode 100644 index 0000000000..ebff250ee0 --- /dev/null +++ b/components/detournavigator/navmeshdb.cpp @@ -0,0 +1,296 @@ +#include "navmeshdb.hpp" + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace DetourNavigator +{ + namespace + { + constexpr const char schema[] = R"( + BEGIN TRANSACTION; + + CREATE TABLE IF NOT EXISTS tiles ( + tile_id INTEGER PRIMARY KEY, + revision INTEGER NOT NULL DEFAULT 1, + worldspace TEXT NOT NULL, + tile_position_x INTEGER NOT NULL, + tile_position_y INTEGER NOT NULL, + version INTEGER NOT NULL, + input BLOB, + data BLOB + ); + + CREATE UNIQUE INDEX IF NOT EXISTS index_unique_tiles_by_worldspace_and_tile_position_and_input + ON tiles (worldspace, tile_position_x, tile_position_y, input); + + CREATE TABLE IF NOT EXISTS shapes ( + shape_id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + type INTEGER NOT NULL, + hash BLOB NOT NULL + ); + + CREATE UNIQUE INDEX IF NOT EXISTS index_unique_shapes_by_name_and_type_and_hash + ON shapes (name, type, hash); + + COMMIT; + )"; + + constexpr std::string_view getMaxTileIdQuery = R"( + SELECT max(tile_id) FROM tiles + )"; + + constexpr std::string_view findTileQuery = R"( + SELECT tile_id, version + FROM tiles + WHERE worldspace = :worldspace + AND tile_position_x = :tile_position_x + AND tile_position_y = :tile_position_y + AND input = :input + )"; + + constexpr std::string_view getTileDataQuery = R"( + SELECT tile_id, version, data + FROM tiles + WHERE worldspace = :worldspace + AND tile_position_x = :tile_position_x + AND tile_position_y = :tile_position_y + AND input = :input + )"; + + constexpr std::string_view insertTileQuery = R"( + INSERT INTO tiles ( tile_id, worldspace, version, tile_position_x, tile_position_y, input, data) + VALUES (:tile_id, :worldspace, :version, :tile_position_x, :tile_position_y, :input, :data) + )"; + + constexpr std::string_view updateTileQuery = R"( + UPDATE tiles + SET version = :version, + data = :data, + revision = revision + 1 + WHERE tile_id = :tile_id + )"; + + constexpr std::string_view getMaxShapeIdQuery = R"( + SELECT max(shape_id) FROM shapes + )"; + + constexpr std::string_view findShapeIdQuery = R"( + SELECT shape_id + FROM shapes + WHERE name = :name + AND type = :type + AND hash = :hash + )"; + + constexpr std::string_view insertShapeQuery = R"( + INSERT INTO shapes ( shape_id, name, type, hash) + VALUES (:shape_id, :name, :type, :hash) + )"; + } + + std::ostream& operator<<(std::ostream& stream, ShapeType value) + { + switch (value) + { + case ShapeType::Collision: return stream << "collision"; + case ShapeType::Avoid: return stream << "avoid"; + } + return stream << "unknown shape type (" << static_cast>(value) << ")"; + } + + NavMeshDb::NavMeshDb(std::string_view path) + : mDb(Sqlite3::makeDb(path, schema)) + , mGetMaxTileId(*mDb, DbQueries::GetMaxTileId {}) + , mFindTile(*mDb, DbQueries::FindTile {}) + , mGetTileData(*mDb, DbQueries::GetTileData {}) + , mInsertTile(*mDb, DbQueries::InsertTile {}) + , mUpdateTile(*mDb, DbQueries::UpdateTile {}) + , mGetMaxShapeId(*mDb, DbQueries::GetMaxShapeId {}) + , mFindShapeId(*mDb, DbQueries::FindShapeId {}) + , mInsertShape(*mDb, DbQueries::InsertShape {}) + { + } + + Sqlite3::Transaction NavMeshDb::startTransaction() + { + return Sqlite3::Transaction(*mDb); + } + + TileId NavMeshDb::getMaxTileId() + { + TileId tileId {0}; + request(*mDb, mGetMaxTileId, &tileId, 1); + return tileId; + } + + std::optional NavMeshDb::findTile(const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input) + { + Tile result; + auto row = std::tie(result.mTileId, result.mVersion); + const std::vector compressedInput = Misc::compress(input); + if (&row == request(*mDb, mFindTile, &row, 1, worldspace, tilePosition, compressedInput)) + return {}; + return result; + } + + std::optional NavMeshDb::getTileData(const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input) + { + TileData result; + auto row = std::tie(result.mTileId, result.mVersion, result.mData); + const std::vector compressedInput = Misc::compress(input); + if (&row == request(*mDb, mGetTileData, &row, 1, worldspace, tilePosition, compressedInput)) + return {}; + result.mData = Misc::decompress(result.mData); + return result; + } + + int NavMeshDb::insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition, + TileVersion version, const std::vector& input, const std::vector& data) + { + const std::vector compressedInput = Misc::compress(input); + const std::vector compressedData = Misc::compress(data); + return execute(*mDb, mInsertTile, tileId, worldspace, tilePosition, version, compressedInput, compressedData); + } + + int NavMeshDb::updateTile(TileId tileId, TileVersion version, const std::vector& data) + { + const std::vector compressedData = Misc::compress(data); + return execute(*mDb, mUpdateTile, tileId, version, compressedData); + } + + ShapeId NavMeshDb::getMaxShapeId() + { + ShapeId shapeId {0}; + request(*mDb, mGetMaxShapeId, &shapeId, 1); + return shapeId; + } + + std::optional NavMeshDb::findShapeId(const std::string& name, ShapeType type, + const Sqlite3::ConstBlob& hash) + { + ShapeId shapeId; + if (&shapeId == request(*mDb, mFindShapeId, &shapeId, 1, name, type, hash)) + return {}; + return shapeId; + } + + int NavMeshDb::insertShape(ShapeId shapeId, const std::string& name, ShapeType type, + const Sqlite3::ConstBlob& hash) + { + return execute(*mDb, mInsertShape, shapeId, name, type, hash); + } + + namespace DbQueries + { + std::string_view GetMaxTileId::text() noexcept + { + return getMaxTileIdQuery; + } + + std::string_view FindTile::text() noexcept + { + return findTileQuery; + } + + void FindTile::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input) + { + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); + Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); + Sqlite3::bindParameter(db, statement, ":input", input); + } + + std::string_view GetTileData::text() noexcept + { + return getTileDataQuery; + } + + void GetTileData::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input) + { + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); + Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); + Sqlite3::bindParameter(db, statement, ":input", input); + } + + std::string_view InsertTile::text() noexcept + { + return insertTileQuery; + } + + void InsertTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, const std::string& worldspace, + const TilePosition& tilePosition, TileVersion version, const std::vector& input, + const std::vector& data) + { + Sqlite3::bindParameter(db, statement, ":tile_id", tileId); + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); + Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); + Sqlite3::bindParameter(db, statement, ":version", version); + Sqlite3::bindParameter(db, statement, ":input", input); + Sqlite3::bindParameter(db, statement, ":data", data); + } + + std::string_view UpdateTile::text() noexcept + { + return updateTileQuery; + } + + void UpdateTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version, + const std::vector& data) + { + Sqlite3::bindParameter(db, statement, ":tile_id", tileId); + Sqlite3::bindParameter(db, statement, ":version", version); + Sqlite3::bindParameter(db, statement, ":data", data); + } + + std::string_view GetMaxShapeId::text() noexcept + { + return getMaxShapeIdQuery; + } + + std::string_view FindShapeId::text() noexcept + { + return findShapeIdQuery; + } + + void FindShapeId::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& name, + ShapeType type, const Sqlite3::ConstBlob& hash) + { + Sqlite3::bindParameter(db, statement, ":name", name); + Sqlite3::bindParameter(db, statement, ":type", static_cast(type)); + Sqlite3::bindParameter(db, statement, ":hash", hash); + } + + std::string_view InsertShape::text() noexcept + { + return insertShapeQuery; + } + + void InsertShape::bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, const std::string& name, + ShapeType type, const Sqlite3::ConstBlob& hash) + { + Sqlite3::bindParameter(db, statement, ":shape_id", shapeId); + Sqlite3::bindParameter(db, statement, ":name", name); + Sqlite3::bindParameter(db, statement, ":type", static_cast(type)); + Sqlite3::bindParameter(db, statement, ":hash", hash); + } + } +} diff --git a/components/detournavigator/navmeshdb.hpp b/components/detournavigator/navmeshdb.hpp new file mode 100644 index 0000000000..636f1de000 --- /dev/null +++ b/components/detournavigator/navmeshdb.hpp @@ -0,0 +1,153 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H + +#include "tileposition.hpp" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct sqlite3; +struct sqlite3_stmt; + +namespace DetourNavigator +{ + BOOST_STRONG_TYPEDEF(std::int64_t, TileId) + BOOST_STRONG_TYPEDEF(std::int64_t, TileRevision) + BOOST_STRONG_TYPEDEF(std::int64_t, TileVersion) + BOOST_STRONG_TYPEDEF(std::int64_t, ShapeId) + + struct Tile + { + TileId mTileId; + TileVersion mVersion; + }; + + struct TileData + { + TileId mTileId; + TileVersion mVersion; + std::vector mData; + }; + + enum class ShapeType + { + Collision = 1, + Avoid = 2, + }; + + std::ostream& operator<<(std::ostream& stream, ShapeType value); + + namespace DbQueries + { + struct GetMaxTileId + { + static std::string_view text() noexcept; + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + struct FindTile + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input); + }; + + struct GetTileData + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input); + }; + + struct InsertTile + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, const std::string& worldspace, + const TilePosition& tilePosition, TileVersion version, const std::vector& input, + const std::vector& data); + }; + + struct UpdateTile + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version, + const std::vector& data); + }; + + struct GetMaxShapeId + { + static std::string_view text() noexcept; + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + struct FindShapeId + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& name, + ShapeType type, const Sqlite3::ConstBlob& hash); + }; + + struct InsertShape + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, const std::string& name, + ShapeType type, const Sqlite3::ConstBlob& hash); + }; + } + + class NavMeshDb + { + public: + explicit NavMeshDb(std::string_view path); + + Sqlite3::Transaction startTransaction(); + + TileId getMaxTileId(); + + std::optional findTile(const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input); + + std::optional getTileData(const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input); + + int insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition, + TileVersion version, const std::vector& input, const std::vector& data); + + int updateTile(TileId tileId, TileVersion version, const std::vector& data); + + ShapeId getMaxShapeId(); + + std::optional findShapeId(const std::string& name, ShapeType type, const Sqlite3::ConstBlob& hash); + + int insertShape(ShapeId shapeId, const std::string& name, ShapeType type, const Sqlite3::ConstBlob& hash); + + private: + Sqlite3::Db mDb; + Sqlite3::Statement mGetMaxTileId; + Sqlite3::Statement mFindTile; + Sqlite3::Statement mGetTileData; + Sqlite3::Statement mInsertTile; + Sqlite3::Statement mUpdateTile; + Sqlite3::Statement mGetMaxShapeId; + Sqlite3::Statement mFindShapeId; + Sqlite3::Statement mInsertShape; + }; +} + +#endif diff --git a/components/detournavigator/navmeshdbutils.cpp b/components/detournavigator/navmeshdbutils.cpp new file mode 100644 index 0000000000..dce6ef3a72 --- /dev/null +++ b/components/detournavigator/navmeshdbutils.cpp @@ -0,0 +1,40 @@ +#include "navmeshdbutils.hpp" +#include "navmeshdb.hpp" +#include "recastmesh.hpp" + +#include + +#include + +namespace DetourNavigator +{ + namespace + { + ShapeId getShapeId(NavMeshDb& db, const std::string& name, ShapeType type, const std::string& hash, ShapeId& nextShapeId) + { + const Sqlite3::ConstBlob hashData {hash.data(), static_cast(hash.size())}; + if (const auto existingShapeId = db.findShapeId(name, type, hashData)) + return *existingShapeId; + const ShapeId newShapeId = nextShapeId; + db.insertShape(newShapeId, name, type, hashData); + Log(Debug::Verbose) << "Added " << name << " " << type << " shape to navmeshdb with id " << newShapeId; + ++nextShapeId.t; + return newShapeId; + } + } + + ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId) + { + switch (source.mAreaType) + { + case AreaType_null: + return getShapeId(db, source.mShape->mFileName, ShapeType::Avoid, source.mShape->mFileHash, nextShapeId); + case AreaType_ground: + return getShapeId(db, source.mShape->mFileName, ShapeType::Collision, source.mShape->mFileHash, nextShapeId); + default: + Log(Debug::Warning) << "Trying to resolve recast mesh source with unsupported area type: " << source.mAreaType; + assert(false); + return ShapeId(0); + } + } +} diff --git a/components/detournavigator/navmeshdbutils.hpp b/components/detournavigator/navmeshdbutils.hpp new file mode 100644 index 0000000000..02b3bdbb00 --- /dev/null +++ b/components/detournavigator/navmeshdbutils.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDBUTILS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDBUTILS_H + +#include "navmeshdb.hpp" + +namespace DetourNavigator +{ + struct MeshSource; + + ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId); +} + +#endif diff --git a/components/detournavigator/objectid.hpp b/components/detournavigator/objectid.hpp index 9c4b5b2710..22fc792c6f 100644 --- a/components/detournavigator/objectid.hpp +++ b/components/detournavigator/objectid.hpp @@ -15,6 +15,11 @@ namespace DetourNavigator { } + explicit ObjectId(std::size_t value) noexcept + : mValue(value) + { + } + std::size_t value() const noexcept { return mValue; diff --git a/components/detournavigator/offmeshconnectionsmanager.cpp b/components/detournavigator/offmeshconnectionsmanager.cpp index 2306e50da9..a11da21218 100644 --- a/components/detournavigator/offmeshconnectionsmanager.cpp +++ b/components/detournavigator/offmeshconnectionsmanager.cpp @@ -65,11 +65,11 @@ namespace DetourNavigator return removed; } - std::vector OffMeshConnectionsManager::get(const TilePosition& tilePosition) + std::vector OffMeshConnectionsManager::get(const TilePosition& tilePosition) const { std::vector result; - const auto values = mValues.lock(); + const auto values = mValues.lockConst(); const auto itByTilePosition = values->mByTilePosition.find(tilePosition); diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index a0a1231bc4..455b03276a 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -24,7 +24,7 @@ namespace DetourNavigator std::set remove(const ObjectId id); - std::vector get(const TilePosition& tilePosition); + std::vector get(const TilePosition& tilePosition) const; private: struct Values diff --git a/components/detournavigator/recastmeshprovider.hpp b/components/detournavigator/recastmeshprovider.hpp new file mode 100644 index 0000000000..5c56d15a5e --- /dev/null +++ b/components/detournavigator/recastmeshprovider.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHPROVIDER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHPROVIDER_H + +#include "tileposition.hpp" +#include "recastmesh.hpp" +#include "tilecachedrecastmeshmanager.hpp" +#include "version.hpp" + +#include +#include + +namespace DetourNavigator +{ + class RecastMesh; + + class RecastMeshProvider + { + public: + RecastMeshProvider(TileCachedRecastMeshManager& impl) + : mImpl(impl) + {} + + std::shared_ptr getMesh(const TilePosition& tilePosition) const + { + return mImpl.get().getNewMesh(tilePosition); + } + + private: + std::reference_wrapper mImpl; + }; +} + +#endif diff --git a/components/detournavigator/serialization.cpp b/components/detournavigator/serialization.cpp new file mode 100644 index 0000000000..8eb60ddb97 --- /dev/null +++ b/components/detournavigator/serialization.cpp @@ -0,0 +1,214 @@ +#include "serialization.hpp" + +#include "dbrefgeometryobject.hpp" +#include "preparednavmeshdata.hpp" +#include "recastmesh.hpp" +#include "settings.hpp" + +#include +#include +#include + +#include +#include + +namespace DetourNavigator +{ +namespace +{ + template + struct Format : Serialization::Format> + { + using Serialization::Format>::operator(); + + template + void operator()(Visitor&& visitor, const osg::Vec2i& value) const + { + visitor(*this, value.ptr(), 2); + } + + template + void operator()(Visitor&& visitor, const osg::Vec2f& value) const + { + visitor(*this, value.ptr(), 2); + } + + template + void operator()(Visitor&& visitor, const osg::Vec3f& value) const + { + visitor(*this, value.ptr(), 3); + } + + template + void operator()(Visitor&& visitor, const Water& value) const + { + visitor(*this, value.mCellSize); + visitor(*this, value.mLevel); + } + + template + void operator()(Visitor&& visitor, const CellWater& value) const + { + visitor(*this, value.mCellPosition); + visitor(*this, value.mWater); + } + + template + void operator()(Visitor&& visitor, const RecastSettings& value) const + { + visitor(*this, value.mCellHeight); + visitor(*this, value.mCellSize); + visitor(*this, value.mDetailSampleDist); + visitor(*this, value.mDetailSampleMaxError); + visitor(*this, value.mMaxClimb); + visitor(*this, value.mMaxSimplificationError); + visitor(*this, value.mMaxSlope); + visitor(*this, value.mRecastScaleFactor); + visitor(*this, value.mSwimHeightScale); + visitor(*this, value.mBorderSize); + visitor(*this, value.mMaxEdgeLen); + visitor(*this, value.mMaxVertsPerPoly); + visitor(*this, value.mRegionMergeArea); + visitor(*this, value.mRegionMinArea); + visitor(*this, value.mTileSize); + } + + template + void operator()(Visitor&& visitor, const TileBounds& value) const + { + visitor(*this, value.mMin); + visitor(*this, value.mMax); + } + + template + void operator()(Visitor&& visitor, const Heightfield& value) const + { + visitor(*this, value.mCellPosition); + visitor(*this, value.mCellSize); + visitor(*this, value.mLength); + visitor(*this, value.mMinHeight); + visitor(*this, value.mMaxHeight); + visitor(*this, value.mHeights); + visitor(*this, value.mOriginalSize); + visitor(*this, value.mMinX); + visitor(*this, value.mMinY); + } + + template + void operator()(Visitor&& visitor, const FlatHeightfield& value) const + { + visitor(*this, value.mCellPosition); + visitor(*this, value.mCellSize); + visitor(*this, value.mHeight); + } + + template + void operator()(Visitor&& visitor, const RecastMesh& value) const + { + visitor(*this, value.getWater()); + visitor(*this, value.getHeightfields()); + visitor(*this, value.getFlatHeightfields()); + } + + template + void operator()(Visitor&& visitor, const ESM::Position& value) const + { + visitor(*this, value.pos); + visitor(*this, value.rot); + } + + template + void operator()(Visitor&& visitor, const ObjectTransform& value) const + { + visitor(*this, value.mPosition); + visitor(*this, value.mScale); + } + + template + void operator()(Visitor&& visitor, const DbRefGeometryObject& value) const + { + visitor(*this, value.mShapeId); + visitor(*this, value.mObjectTransform); + } + + template + void operator()(Visitor&& visitor, const RecastSettings& settings, const RecastMesh& recastMesh, + const std::vector& dbRefGeometryObjects) const + { + visitor(*this, DetourNavigator::recastMeshMagic); + visitor(*this, DetourNavigator::recastMeshVersion); + visitor(*this, settings); + visitor(*this, recastMesh); + visitor(*this, dbRefGeometryObjects); + } + + template + auto operator()(Visitor&& visitor, const rcPolyMesh& value) const + { + visitor(*this, value.nverts); + visitor(*this, value.npolys); + visitor(*this, value.maxpolys); + visitor(*this, value.nvp); + visitor(*this, value.bmin); + visitor(*this, value.bmax); + visitor(*this, value.cs); + visitor(*this, value.ch); + visitor(*this, value.borderSize); + visitor(*this, value.maxEdgeError); + visitor(*this, value.verts, getVertsLength(value)); + visitor(*this, value.polys, getPolysLength(value)); + visitor(*this, value.regs, getRegsLength(value)); + visitor(*this, value.flags, getFlagsLength(value)); + visitor(*this, value.areas, getAreasLength(value)); + } + + template + auto operator()(Visitor&& visitor, const rcPolyMeshDetail& value) const + { + visitor(*this, value.nmeshes); + visitor(*this, value.meshes, getMeshesLength(value)); + visitor(*this, value.nverts); + visitor(*this, value.verts, getVertsLength(value)); + visitor(*this, value.ntris); + visitor(*this, value.tris, getTrisLength(value)); + } + + template + auto operator()(Visitor&& visitor, const PreparedNavMeshData& value) const + { + visitor(*this, DetourNavigator::preparedNavMeshDataMagic); + visitor(*this, DetourNavigator::preparedNavMeshDataVersion); + visitor(*this, value.mUserId); + visitor(*this, value.mCellSize); + visitor(*this, value.mCellHeight); + visitor(*this, value.mPolyMesh); + visitor(*this, value.mPolyMeshDetail); + } + }; +} +} // namespace DetourNavigator + +namespace DetourNavigator +{ + std::vector serialize(const RecastSettings& settings, const RecastMesh& recastMesh, + const std::vector& dbRefGeometryObjects) + { + constexpr Format format; + Serialization::SizeAccumulator sizeAccumulator; + format(sizeAccumulator, settings, recastMesh, dbRefGeometryObjects); + std::vector result(sizeAccumulator.value()); + format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), + settings, recastMesh, dbRefGeometryObjects); + return result; + } + + std::vector serialize(const PreparedNavMeshData& value) + { + constexpr Format format; + Serialization::SizeAccumulator sizeAccumulator; + format(sizeAccumulator, value); + std::vector result(sizeAccumulator.value()); + format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), value); + return result; + } +} diff --git a/components/detournavigator/serialization.hpp b/components/detournavigator/serialization.hpp new file mode 100644 index 0000000000..8e8096a3dc --- /dev/null +++ b/components/detournavigator/serialization.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H + +#include +#include +#include + +namespace DetourNavigator +{ + class RecastMesh; + struct DbRefGeometryObject; + struct PreparedNavMeshData; + struct RecastSettings; + + constexpr char recastMeshMagic[] = {'r', 'c', 's', 't'}; + constexpr std::uint32_t recastMeshVersion = 1; + + constexpr char preparedNavMeshDataMagic[] = {'p', 'n', 'a', 'v'}; + constexpr std::uint32_t preparedNavMeshDataVersion = 1; + + std::vector serialize(const RecastSettings& settings, const RecastMesh& value, + const std::vector& dbRefGeometryObjects); + + std::vector serialize(const PreparedNavMeshData& value); +} + +#endif diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 5ad45d699b..cda9ab8d2f 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -61,6 +61,7 @@ namespace DetourNavigator result.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); + result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator"); return result; } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index fbd44dfcfc..3253b60893 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -48,6 +48,7 @@ namespace DetourNavigator std::string mRecastMeshPathPrefix; std::string mNavMeshPathPrefix; std::chrono::milliseconds mMinUpdateInterval; + std::int64_t mNavMeshVersion = 0; }; RecastSettings makeRecastSettingsFromSettingsManager(); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index ebd64180aa..88fd8ab3f5 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -196,6 +196,13 @@ namespace DetourNavigator return nullptr; } + std::shared_ptr TileCachedRecastMeshManager::getNewMesh(const TilePosition& tilePosition) const + { + if (const auto manager = getManager(tilePosition)) + return manager->getNewMesh(); + return nullptr; + } + std::size_t TileCachedRecastMeshManager::getRevision() const { return mRevision; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 96183a51be..68dc565fa5 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -88,6 +88,8 @@ namespace DetourNavigator std::shared_ptr getCachedMesh(const TilePosition& tilePosition) const; + std::shared_ptr getNewMesh(const TilePosition& tilePosition) const; + template void forEachTile(Function&& function) const { diff --git a/components/sqlite3/request.hpp b/components/sqlite3/request.hpp index 339c7f7521..0a74bf1cb3 100644 --- a/components/sqlite3/request.hpp +++ b/components/sqlite3/request.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_SQLITE3_REQUEST_H #include "statement.hpp" +#include "types.hpp" #include @@ -53,6 +54,13 @@ namespace Sqlite3 + ": " + std::string(sqlite3_errmsg(&db))); } + inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, const ConstBlob& value) + { + if (sqlite3_bind_blob(&stmt, index, value.mData, value.mSize, SQLITE_STATIC) != SQLITE_OK) + throw std::runtime_error("Failed to bind blob to parameter " + std::to_string(index) + + ": " + std::string(sqlite3_errmsg(&db))); + } + template inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, const char* name, const T& value) { diff --git a/components/sqlite3/types.hpp b/components/sqlite3/types.hpp new file mode 100644 index 0000000000..325e9e6608 --- /dev/null +++ b/components/sqlite3/types.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_SQLITE3_TYPES_H +#define OPENMW_COMPONENTS_SQLITE3_TYPES_H + +#include + +namespace Sqlite3 +{ + struct ConstBlob + { + const char* mData; + int mSize; + }; +} + +#endif diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 605be3f6af..6b19885557 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -464,3 +464,4 @@ default actor pathfind half extents :Default: 29.27999496459961 28.479997634887695 66.5 Actor half extents used for exterior cells to generate navmesh. +Changing the value will invalidate navmesh disk cache. diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index f8ab4522d6..9132364d48 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -206,6 +206,17 @@ Absent pieces usually mean a bug in recast mesh tiles building. Allows to do in-game debug. Potentially decreases performance. +nav mesh version +---------------- + +:Type: integer +:Range: > 0 +:Default: 1 + +Version of navigation mesh generation algorithm. +Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. +Changing the value will invalidate navmesh disk cache. + Expert settings *************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 084aad4fe3..e80d6d1106 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -930,6 +930,10 @@ min update interval ms = 250 # Distance is measured in the number of tiles and can be only an integer value. wait until min distance to player = 5 +# Version of navigation mesh generation algorithm. +# Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. +nav mesh version = 1 + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. From c9b8ba7b46c2170ef873d7c2f1d8172b3e86fb69 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 9 Jul 2021 22:51:42 +0200 Subject: [PATCH 1845/2859] Read navmesh tile data from database When tile is not found in memory cache try to find it in the database. --- apps/navmeshtool/worldspacedata.cpp | 1 + .../mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwworld/scene.cpp | 24 +++-- apps/openmw/mwworld/worldimp.cpp | 7 +- .../detournavigator/navigator.cpp | 7 +- .../detournavigator/navmeshtilescache.cpp | 8 -- .../tilecachedrecastmeshmanager.cpp | 100 +++++++++++------- components/CMakeLists.txt | 1 + .../detournavigator/asyncnavmeshupdater.cpp | 21 ++-- .../detournavigator/asyncnavmeshupdater.hpp | 11 +- .../detournavigator/generatenavmeshtile.cpp | 2 +- components/detournavigator/makenavmesh.cpp | 30 +++++- components/detournavigator/makenavmesh.hpp | 8 +- components/detournavigator/navigator.cpp | 9 +- components/detournavigator/navigator.hpp | 10 +- components/detournavigator/navigatorimpl.cpp | 9 +- components/detournavigator/navigatorimpl.hpp | 5 +- components/detournavigator/navigatorstub.hpp | 2 + components/detournavigator/navmeshmanager.cpp | 23 ++-- components/detournavigator/navmeshmanager.hpp | 5 +- .../detournavigator/preparednavmeshdata.cpp | 9 +- components/detournavigator/recast.cpp | 49 +++++++++ components/detournavigator/recast.hpp | 16 +++ .../detournavigator/recastmeshprovider.hpp | 4 +- components/detournavigator/serialization.cpp | 74 +++++++++++-- components/detournavigator/serialization.hpp | 2 + components/detournavigator/settings.cpp | 1 + components/detournavigator/settings.hpp | 1 + .../tilecachedrecastmeshmanager.cpp | 96 ++++++++++------- .../tilecachedrecastmeshmanager.hpp | 30 +++--- .../reference/modding/settings/navigator.rst | 11 ++ files/settings-default.cfg | 3 + 32 files changed, 424 insertions(+), 157 deletions(-) create mode 100644 components/detournavigator/recast.cpp diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 7c5aa06c83..189d24f34a 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -264,6 +264,7 @@ namespace NavMeshTool { it = navMeshInputs.emplace(cell.mCellId.mWorldspace, std::make_unique(cell.mCellId.mWorldspace, settings.mRecast)).first; + it->second->mTileCachedRecastMeshManager.setWorldspace(cell.mCellId.mWorldspace); } return *it->second; } (); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index e450e76483..9974cb44cd 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -788,7 +788,7 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); world->getNavigator()->setUpdatesEnabled(mAI); if (mAI) - world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); + world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); return mAI; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index c3db62b5a9..054e26e855 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -350,8 +350,7 @@ namespace MWWorld if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) mNavigator.removePathgrid(*pathgrid); - const auto player = world->getPlayerPtr(); - mNavigator.update(player.getRefData().getPosition().asVec3()); + mNavigator.update(world->getPlayerPtr().getRefData().getPosition().asVec3()); MWBase::Environment::get().getMechanicsManager()->drop (cell); @@ -378,6 +377,8 @@ namespace MWWorld const int cellX = cell->getCell()->getGridX(); const int cellY = cell->getCell()->getGridY(); + mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace); + if (cell->getCell()->isExterior()) { osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); @@ -507,10 +508,13 @@ namespace MWWorld void Scene::playerMoved(const osg::Vec3f &pos) { + if (mCurrentCell == nullptr) + return; + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3()); - if (!mCurrentCell || !mCurrentCell->isExterior()) + if (!mCurrentCell->isExterior()) return; osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); @@ -886,8 +890,11 @@ namespace MWWorld addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - mNavigator.update(player.getRefData().getPosition().asVec3()); + if (mCurrentCell != nullptr) + { + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + mNavigator.update(player.getRefData().getPosition().asVec3()); + } } catch (std::exception& e) { @@ -903,8 +910,11 @@ namespace MWWorld if (const auto object = mPhysics->getObject(ptr)) { mNavigator.removeObject(DetourNavigator::ObjectId(object)); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - mNavigator.update(player.getRefData().getPosition().asVec3()); + if (mCurrentCell != nullptr) + { + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + mNavigator.update(player.getRefData().getPosition().asVec3()); + } } else if (mPhysics->getActor(ptr)) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5a937d1a78..a44646ef37 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -187,7 +187,7 @@ namespace MWWorld { auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; - mNavigator = DetourNavigator::makeNavigator(navigatorSettings); + mNavigator = DetourNavigator::makeNavigator(navigatorSettings, userDataPath); } else { @@ -1524,9 +1524,10 @@ namespace MWWorld if (const auto object = mPhysics->getObject(door.first)) updateNavigatorObject(*object); - if (mShouldUpdateNavigator) + auto player = getPlayerPtr(); + if (mShouldUpdateNavigator && player.getCell() != nullptr) { - mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3()); + mNavigator->update(player.getRefData().getPosition().asVec3()); mShouldUpdateNavigator = false; } } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index b630c261ef..0254d1b813 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ namespace Settings mSettings; std::unique_ptr mNavigator; const osg::Vec3f mPlayerPosition; + const std::string mWorldspace; const osg::Vec3f mAgentHalfExtents; osg::Vec3f mStart; osg::Vec3f mEnd; @@ -53,6 +55,7 @@ namespace DetourNavigatorNavigatorTest() : mPlayerPosition(256, 256, 0) + , mWorldspace("sys::default") , mAgentHalfExtents(29, 29, 66) , mStart(52, 460, 1) , mEnd(460, 52, 1) @@ -87,7 +90,7 @@ namespace mSettings.mDetour.mMaxPolys = 4096; mSettings.mMaxTilesNumber = 512; mSettings.mMinUpdateInterval = std::chrono::milliseconds(50); - mNavigator.reset(new NavigatorImpl(mSettings)); + mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:"))); } }; @@ -854,7 +857,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) { mSettings.mAsyncNavMeshUpdaterThreads = 2; - mNavigator.reset(new NavigatorImpl(mSettings)); + mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:"))); const std::array heightfieldData {{ 0, 0, 0, 0, 0, diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index b47ae98233..ec5576a634 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -24,14 +24,6 @@ namespace using namespace DetourNavigator; using namespace DetourNavigator::Tests; - void* permRecastAlloc(int size) - { - void* result = rcAlloc(static_cast(size), RC_ALLOC_PERM); - if (result == nullptr) - throw std::bad_alloc(); - return result; - } - template void generateRecastArray(T*& values, int size, Random& random) { diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 9426a3968f..c44ebc5155 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -38,7 +38,7 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) { TileCachedRecastMeshManager manager(mSettings); - EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero) @@ -75,12 +75,13 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) - ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); + ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) @@ -113,90 +114,98 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); + const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); - EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); + const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); - EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); - EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.removeObject(ObjectId(&boxShape)); - EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); - EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value) @@ -235,6 +244,7 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); @@ -273,17 +283,19 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); for (int x = -1; x < 12; ++x) for (int y = -1; y < 12; ++y) - ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); + ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); @@ -292,7 +304,7 @@ namespace ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); + ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt) @@ -315,18 +327,20 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_EQ(manager.getMesh(TilePosition(x, y)), nullptr); + ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); @@ -336,12 +350,13 @@ namespace ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); + ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; const btBoxShape boxShape(btVector3(20, 20, 100)); @@ -351,6 +366,19 @@ namespace ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); for (int x = -1; x < 12; ++x) for (int y = -1; y < 12; ++y) - ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); + ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_new_worldspace_should_remove_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(nullptr, boxShape, mObjectTransform); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + manager.setWorldspace("other"); + for (int x = -1; x < 1; ++x) + for (int y = -1; y < 1; ++y) + ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr); } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index be3efd1b9f..f969ac1265 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -207,6 +207,7 @@ add_component_dir(detournavigator navmeshdb serialization navmeshdbutils + recast ) add_component_dir(loadinglistener diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index c2c79a840e..a808030478 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -76,10 +76,11 @@ namespace namespace DetourNavigator { Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr navMeshCacheItem, - const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::chrono::steady_clock::time_point processTime) : mAgentHalfExtents(agentHalfExtents) , mNavMeshCacheItem(std::move(navMeshCacheItem)) + , mWorldspace(worldspace) , mChangedTile(changedTile) , mProcessTime(processTime) , mChangeType(changeType) @@ -89,10 +90,11 @@ namespace DetourNavigator } AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, - OffMeshConnectionsManager& offMeshConnectionsManager) + OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(recastMeshManager) , mOffMeshConnectionsManager(offMeshConnectionsManager) + , mDb(std::move(db)) , mShouldStop() , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) { @@ -111,8 +113,8 @@ namespace DetourNavigator thread.join(); } - void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, - const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, + void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem, + const TilePosition& playerTile, std::string_view worldspace, const std::map& changedTiles) { bool playerTileChanged = false; @@ -147,8 +149,8 @@ namespace DetourNavigator ? mLastUpdates[std::tie(agentHalfExtents, changedTile)] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); - const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, changedTile, - changeType, getManhattanDistance(changedTile, playerTile), processTime); + const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace, + changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime); if (playerTileChanged) mWaiting.push_back(it); @@ -302,12 +304,13 @@ namespace DetourNavigator if (!navMeshCacheItem) return true; - const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); + const auto recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile); const auto playerTile = *mPlayerTile.lockConst(); const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); - const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, - offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, getUpdateType(job.mChangeType)); + const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mWorldspace, job.mChangedTile, + playerTile, offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, + getUpdateType(job.mChangeType), mDb, mNextShapeId); if (recastMesh != nullptr) { diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 2d915ad434..44d5be58d5 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -7,6 +7,7 @@ #include "tileposition.hpp" #include "navmeshtilescache.hpp" #include "waitconditiontype.hpp" +#include "navmeshdb.hpp" #include @@ -57,6 +58,7 @@ namespace DetourNavigator { const osg::Vec3f mAgentHalfExtents; const std::weak_ptr mNavMeshCacheItem; + const std::string mWorldspace; const TilePosition mChangedTile; const std::chrono::steady_clock::time_point mProcessTime; unsigned mTryNumber = 0; @@ -65,7 +67,7 @@ namespace DetourNavigator const int mDistanceToOrigin; Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr navMeshCacheItem, - const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::chrono::steady_clock::time_point processTime); }; @@ -75,11 +77,12 @@ namespace DetourNavigator { public: AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, - OffMeshConnectionsManager& offMeshConnectionsManager); + OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db); ~AsyncNavMeshUpdater(); void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, - const TilePosition& playerTile, const std::map& changedTiles); + const TilePosition& playerTile, std::string_view worldspace, + const std::map& changedTiles); void wait(Loading::Listener& listener, WaitConditionType waitConditionType); @@ -89,6 +92,8 @@ namespace DetourNavigator std::reference_wrapper mSettings; std::reference_wrapper mRecastMeshManager; std::reference_wrapper mOffMeshConnectionsManager; + Misc::ScopeGuarded> mDb; + ShapeId mNextShapeId {1}; std::atomic_bool mShouldStop; mutable std::mutex mMutex; std::condition_variable mHasJob; diff --git a/components/detournavigator/generatenavmeshtile.cpp b/components/detournavigator/generatenavmeshtile.cpp index 9dc8e70383..ad8978cd4b 100644 --- a/components/detournavigator/generatenavmeshtile.cpp +++ b/components/detournavigator/generatenavmeshtile.cpp @@ -61,7 +61,7 @@ namespace DetourNavigator { Ignore ignore {consumer}; - const std::shared_ptr recastMesh = mRecastMeshProvider.getMesh(mTilePosition); + const std::shared_ptr recastMesh = mRecastMeshProvider.getMesh(mWorldspace, mTilePosition); if (recastMesh == nullptr || isEmpty(*recastMesh)) return; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 4bfffb22ab..8b764b91b4 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -10,9 +10,15 @@ #include "preparednavmeshdata.hpp" #include "navmeshdata.hpp" #include "recastmeshbuilder.hpp" +#include "navmeshdb.hpp" +#include "serialization.hpp" +#include "dbrefgeometryobject.hpp" +#include "navmeshdbutils.hpp" #include #include +#include +#include #include #include @@ -540,9 +546,10 @@ namespace DetourNavigator } UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const TilePosition& changedTile, const TilePosition& playerTile, + const std::string& worldspace, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, - const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType) + const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType, + Misc::ScopeGuarded>& db, ShapeId& nextShapeId) { Log(Debug::Debug) << std::fixed << std::setprecision(2) << "Update NavMesh with multiple tiles:" << @@ -578,7 +585,24 @@ namespace DetourNavigator if (!cachedNavMeshData) { - auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings.mRecast); + std::optional stored; + if (const auto dbLocked = db.lock(); *dbLocked != nullptr) + { + const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(**dbLocked, v, nextShapeId); }); + stored = (*dbLocked)->getTileData(worldspace, changedTile, serialize(settings.mRecast, *recastMesh, objects)); + } + + std::unique_ptr prepared; + if (stored.has_value() && stored->mVersion == settings.mNavMeshVersion) + { + prepared = std::make_unique(); + if (!deserialize(stored->mData, *prepared)) + prepared = nullptr; + } + + if (prepared == nullptr) + prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings.mRecast); if (prepared == nullptr) { diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index fccf70baad..40ead79ad6 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -7,6 +7,9 @@ #include "sharednavmesh.hpp" #include "navmeshtilescache.hpp" #include "offmeshconnection.hpp" +#include "navmeshdb.hpp" + +#include #include @@ -63,9 +66,10 @@ namespace DetourNavigator }; UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const TilePosition& changedTile, const TilePosition& playerTile, + const std::string& worldspace, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, - const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType); + const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType, + Misc::ScopeGuarded>& db, ShapeId& nextShapeId); } #endif diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 1877c3dd93..cf3c4ba5b3 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -5,10 +5,15 @@ namespace DetourNavigator { - std::unique_ptr makeNavigator(const Settings& settings) + std::unique_ptr makeNavigator(const Settings& settings, const std::string& userDataPath) { DetourNavigator::RecastGlobalAllocator::init(); - return std::make_unique(settings); + + std::unique_ptr db; + if (settings.mEnableNavMeshDiskCache) + db = std::make_unique(userDataPath + "/navmesh.db"); + + return std::make_unique(settings, std::move(db)); } std::unique_ptr makeNavigatorStub() diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 9ffe062fe5..14731fcc5b 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -10,6 +10,8 @@ #include +#include + namespace ESM { struct Cell; @@ -75,6 +77,12 @@ namespace DetourNavigator */ virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; + /** + * @brief setWorldspace should be called before adding object from new worldspace + * @param worldspace + */ + virtual void setWorldspace(std::string_view worldspace) = 0; + /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes * @param id is used to distinguish different objects @@ -188,7 +196,7 @@ namespace DetourNavigator virtual float getMaxNavmeshAreaRealRadius() const = 0; }; - std::unique_ptr makeNavigator(const Settings& settings); + std::unique_ptr makeNavigator(const Settings& settings, const std::string& userDataPath); std::unique_ptr makeNavigatorStub(); } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 541e1f791c..85d86e6b2b 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -8,9 +8,9 @@ namespace DetourNavigator { - NavigatorImpl::NavigatorImpl(const Settings& settings) + NavigatorImpl::NavigatorImpl(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) - , mNavMeshManager(mSettings) + , mNavMeshManager(mSettings, std::move(db)) , mUpdatesEnabled(true) { } @@ -32,6 +32,11 @@ namespace DetourNavigator --it->second; } + void NavigatorImpl::setWorldspace(std::string_view worldspace) + { + mNavMeshManager.setWorldspace(worldspace); + } + bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 2c8474786f..116817395c 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -5,6 +5,7 @@ #include "navmeshmanager.hpp" #include +#include namespace DetourNavigator { @@ -15,12 +16,14 @@ namespace DetourNavigator * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. * @param settings allows to customize navigator work. Constructor is only place to set navigator settings. */ - explicit NavigatorImpl(const Settings& settings); + explicit NavigatorImpl(const Settings& settings, std::unique_ptr&& db); void addAgent(const osg::Vec3f& agentHalfExtents) override; void removeAgent(const osg::Vec3f& agentHalfExtents) override; + void setWorldspace(std::string_view worldspace) override; + bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 1a6096feef..6a320c1a08 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -19,6 +19,8 @@ namespace DetourNavigator void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} + void setWorldspace(std::string_view /*worldspace*/) override {} + bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override { return false; diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 452fba93ad..9c8cb5389c 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -41,13 +41,23 @@ namespace namespace DetourNavigator { - NavMeshManager::NavMeshManager(const Settings& settings) + NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) - , mRecastMeshManager(mSettings.mRecast) - , mOffMeshConnectionsManager(mSettings.mRecast) - , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) + , mRecastMeshManager(settings.mRecast) + , mOffMeshConnectionsManager(settings.mRecast) + , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)) {} + void NavMeshManager::setWorldspace(std::string_view worldspace) + { + if (worldspace == mWorldspace) + return; + mRecastMeshManager.setWorldspace(worldspace); + for (auto& [agent, cache] : mCache) + cache = std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter); + mWorldspace = worldspace; + } + bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { @@ -208,7 +218,7 @@ namespace DetourNavigator recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); }); } - mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); + mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost); if (changedTiles != mChangedTiles.end()) changedTiles->second.clear(); Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents << @@ -241,9 +251,10 @@ namespace DetourNavigator std::vector tiles; mRecastMeshManager.forEachTile( [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); + const std::string worldspace = mRecastMeshManager.getWorldspace(); RecastMeshTiles result; for (const TilePosition& tile : tiles) - if (auto mesh = mRecastMeshManager.getCachedMesh(tile)) + if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile)) result.emplace(tile, std::move(mesh)); return result; } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index b1926741c5..3fd2d28d74 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -22,7 +22,9 @@ namespace DetourNavigator class NavMeshManager { public: - NavMeshManager(const Settings& settings); + explicit NavMeshManager(const Settings& settings, std::unique_ptr&& db); + + void setWorldspace(std::string_view worldspace); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); @@ -62,6 +64,7 @@ namespace DetourNavigator private: const Settings& mSettings; + std::string mWorldspace; TileCachedRecastMeshManager mRecastMeshManager; OffMeshConnectionsManager mOffMeshConnectionsManager; AsyncNavMeshUpdater mAsyncNavMeshUpdater; diff --git a/components/detournavigator/preparednavmeshdata.cpp b/components/detournavigator/preparednavmeshdata.cpp index c86e18482c..77c70eade3 100644 --- a/components/detournavigator/preparednavmeshdata.cpp +++ b/components/detournavigator/preparednavmeshdata.cpp @@ -1,8 +1,8 @@ #include "preparednavmeshdata.hpp" #include "preparednavmeshdatatuple.hpp" +#include "recast.hpp" #include -#include namespace { @@ -15,13 +15,6 @@ namespace value.nverts = 0; value.ntris = 0; } - - void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept - { - rcFree(value.meshes); - rcFree(value.verts); - rcFree(value.tris); - } } namespace DetourNavigator diff --git a/components/detournavigator/recast.cpp b/components/detournavigator/recast.cpp new file mode 100644 index 0000000000..f3c7768430 --- /dev/null +++ b/components/detournavigator/recast.cpp @@ -0,0 +1,49 @@ +#include "recast.hpp" + +#include +#include + +#include +#include + +namespace DetourNavigator +{ + void* permRecastAlloc(std::size_t size) + { + void* const result = rcAlloc(size, RC_ALLOC_PERM); + if (result == nullptr) + throw std::bad_alloc(); + return result; + } + + void permRecastAlloc(rcPolyMesh& value) + { + permRecastAlloc(value.verts, getVertsLength(value)); + permRecastAlloc(value.polys, getPolysLength(value)); + permRecastAlloc(value.regs, getRegsLength(value)); + permRecastAlloc(value.flags, getFlagsLength(value)); + permRecastAlloc(value.areas, getAreasLength(value)); + } + + void permRecastAlloc(rcPolyMeshDetail& value) + { + try + { + permRecastAlloc(value.meshes, getMeshesLength(value)); + permRecastAlloc(value.verts, getVertsLength(value)); + permRecastAlloc(value.tris, getTrisLength(value)); + } + catch (...) + { + freePolyMeshDetail(value); + throw; + } + } + + void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept + { + rcFree(value.meshes); + rcFree(value.verts); + rcFree(value.tris); + } +} diff --git a/components/detournavigator/recast.hpp b/components/detournavigator/recast.hpp index 4e9ab329b7..8b9042b661 100644 --- a/components/detournavigator/recast.hpp +++ b/components/detournavigator/recast.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace DetourNavigator { @@ -46,6 +47,21 @@ namespace DetourNavigator { return 4 * static_cast(value.ntris); } + + void* permRecastAlloc(std::size_t size); + + template + inline void permRecastAlloc(T*& values, std::size_t size) + { + static_assert(std::is_arithmetic_v); + values = new (permRecastAlloc(size * sizeof(T))) T[size]; + } + + void permRecastAlloc(rcPolyMesh& value); + + void permRecastAlloc(rcPolyMeshDetail& value); + + void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept; } #endif diff --git a/components/detournavigator/recastmeshprovider.hpp b/components/detournavigator/recastmeshprovider.hpp index 5c56d15a5e..b01b7c4ea1 100644 --- a/components/detournavigator/recastmeshprovider.hpp +++ b/components/detournavigator/recastmeshprovider.hpp @@ -20,9 +20,9 @@ namespace DetourNavigator : mImpl(impl) {} - std::shared_ptr getMesh(const TilePosition& tilePosition) const + std::shared_ptr getMesh(std::string_view worldspace, const TilePosition& tilePosition) const { - return mImpl.get().getNewMesh(tilePosition); + return mImpl.get().getNewMesh(worldspace, tilePosition); } private: diff --git a/components/detournavigator/serialization.cpp b/components/detournavigator/serialization.cpp index 8eb60ddb97..50b014acb1 100644 --- a/components/detournavigator/serialization.cpp +++ b/components/detournavigator/serialization.cpp @@ -2,14 +2,18 @@ #include "dbrefgeometryobject.hpp" #include "preparednavmeshdata.hpp" +#include "recast.hpp" #include "recastmesh.hpp" #include "settings.hpp" +#include #include #include #include #include +#include +#include #include namespace DetourNavigator @@ -142,8 +146,9 @@ namespace visitor(*this, dbRefGeometryObjects); } - template - auto operator()(Visitor&& visitor, const rcPolyMesh& value) const + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, rcPolyMesh>> { visitor(*this, value.nverts); visitor(*this, value.npolys); @@ -155,6 +160,19 @@ namespace visitor(*this, value.ch); visitor(*this, value.borderSize); visitor(*this, value.maxEdgeError); + if constexpr (mode == Serialization::Mode::Read) + { + if (value.verts == nullptr) + permRecastAlloc(value.verts, getVertsLength(value)); + if (value.polys == nullptr) + permRecastAlloc(value.polys, getPolysLength(value)); + if (value.regs == nullptr) + permRecastAlloc(value.regs, getRegsLength(value)); + if (value.flags == nullptr) + permRecastAlloc(value.flags, getFlagsLength(value)); + if (value.areas == nullptr) + permRecastAlloc(value.areas, getAreasLength(value)); + } visitor(*this, value.verts, getVertsLength(value)); visitor(*this, value.polys, getPolysLength(value)); visitor(*this, value.regs, getRegsLength(value)); @@ -162,22 +180,48 @@ namespace visitor(*this, value.areas, getAreasLength(value)); } - template - auto operator()(Visitor&& visitor, const rcPolyMeshDetail& value) const + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, rcPolyMeshDetail>> { visitor(*this, value.nmeshes); + if constexpr (mode == Serialization::Mode::Read) + if (value.meshes == nullptr) + permRecastAlloc(value.meshes, getMeshesLength(value)); visitor(*this, value.meshes, getMeshesLength(value)); visitor(*this, value.nverts); + if constexpr (mode == Serialization::Mode::Read) + if (value.verts == nullptr) + permRecastAlloc(value.verts, getVertsLength(value)); visitor(*this, value.verts, getVertsLength(value)); visitor(*this, value.ntris); + if constexpr (mode == Serialization::Mode::Read) + if (value.tris == nullptr) + permRecastAlloc(value.tris, getTrisLength(value)); visitor(*this, value.tris, getTrisLength(value)); } - template - auto operator()(Visitor&& visitor, const PreparedNavMeshData& value) const + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, PreparedNavMeshData>> { - visitor(*this, DetourNavigator::preparedNavMeshDataMagic); - visitor(*this, DetourNavigator::preparedNavMeshDataVersion); + if constexpr (mode == Serialization::Mode::Write) + { + visitor(*this, DetourNavigator::preparedNavMeshDataMagic); + visitor(*this, DetourNavigator::preparedNavMeshDataVersion); + } + else + { + static_assert(mode == Serialization::Mode::Read); + char magic[std::size(DetourNavigator::preparedNavMeshDataMagic)]; + visitor(*this, magic); + if (std::memcmp(magic, DetourNavigator::preparedNavMeshDataMagic, sizeof(magic)) != 0) + throw std::runtime_error("Bad PreparedNavMeshData magic"); + std::uint32_t version = 0; + visitor(*this, version); + if (version != DetourNavigator::preparedNavMeshDataVersion) + throw std::runtime_error("Bad PreparedNavMeshData version"); + } visitor(*this, value.mUserId); visitor(*this, value.mCellSize); visitor(*this, value.mCellHeight); @@ -211,4 +255,18 @@ namespace DetourNavigator format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), value); return result; } + + bool deserialize(const std::vector& data, PreparedNavMeshData& value) + { + try + { + constexpr Format format; + format(Serialization::BinaryReader(data.data(), data.data() + data.size()), value); + return true; + } + catch (const std::exception&) + { + return false; + } + } } diff --git a/components/detournavigator/serialization.hpp b/components/detournavigator/serialization.hpp index 8e8096a3dc..194e50a994 100644 --- a/components/detournavigator/serialization.hpp +++ b/components/detournavigator/serialization.hpp @@ -22,6 +22,8 @@ namespace DetourNavigator const std::vector& dbRefGeometryObjects); std::vector serialize(const PreparedNavMeshData& value); + + bool deserialize(const std::vector& data, PreparedNavMeshData& value); } #endif diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index cda9ab8d2f..68baeb51d8 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -62,6 +62,7 @@ namespace DetourNavigator result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator"); + result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator"); return result; } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 3253b60893..ab8a95c649 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -39,6 +39,7 @@ namespace DetourNavigator bool mEnableWriteNavMeshToFile = false; bool mEnableRecastMeshFileNameRevision = false; bool mEnableNavMeshFileNameRevision = false; + bool mEnableNavMeshDiskCache = false; RecastSettings mRecast; DetourSettings mDetour; int mWaitUntilMinDistanceToPlayer = 0; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 88fd8ab3f5..bf3df92d6e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -14,15 +14,30 @@ namespace DetourNavigator : mSettings(settings) {} + std::string TileCachedRecastMeshManager::getWorldspace() const + { + const std::lock_guard lock(mMutex); + return mWorldspace; + } + + void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace) + { + const std::lock_guard lock(mMutex); + if (mWorldspace == worldspace) + return; + mTiles.clear(); + mWorldspace = worldspace; + } + bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { std::vector tilesPositions; { - auto tiles = mTiles.lock(); + const std::lock_guard lock(mMutex); getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) { - if (addTile(id, shape, transform, areaType, tilePosition, tiles.get())) + if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) tilesPositions.push_back(tilePosition); }); } @@ -41,10 +56,10 @@ namespace DetourNavigator return std::nullopt; std::optional result; { - auto tiles = mTiles.lock(); + const std::lock_guard lock(mMutex); for (const auto& tilePosition : object->second) { - const auto removed = removeTile(id, tilePosition, tiles.get()); + const auto removed = removeTile(id, tilePosition, mTiles); if (removed && !result) result = removed; } @@ -62,8 +77,8 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) { - const auto tiles = mTiles.lock(); - for (auto& tile : *tiles) + const std::lock_guard lock(mMutex); + for (auto& tile : mTiles) { if (tile.second->addWater(cellPosition, cellSize, level)) { @@ -77,13 +92,13 @@ namespace DetourNavigator const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) { - const auto tiles = mTiles.lock(); - auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + const std::lock_guard lock(mMutex); + auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = tiles->emplace(tilePosition, - std::make_shared(tileBounds, mTilesGeneration)).first; + tile = mTiles.emplace_hint(tile, tilePosition, + std::make_shared(tileBounds, mTilesGeneration)); } if (tile->second->addWater(cellPosition, cellSize, level)) { @@ -107,14 +122,14 @@ namespace DetourNavigator std::optional result; for (const auto& tilePosition : object->second) { - const auto tiles = mTiles.lock(); - const auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + const std::lock_guard lock(mMutex); + const auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) continue; const auto tileResult = tile->second->removeWater(cellPosition); if (tile->second->isEmpty()) { - tiles->erase(tile); + mTiles.erase(tile); ++mTilesGeneration; } if (tileResult && !result) @@ -135,13 +150,13 @@ namespace DetourNavigator getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) { - const auto tiles = mTiles.lock(); - auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + const std::lock_guard lock(mMutex); + auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = tiles->emplace(tilePosition, - std::make_shared(tileBounds, mTilesGeneration)).first; + tile = mTiles.emplace_hint(tile, tilePosition, + std::make_shared(tileBounds, mTilesGeneration)); } if (tile->second->addHeightfield(cellPosition, cellSize, shape)) { @@ -164,14 +179,14 @@ namespace DetourNavigator std::optional result; for (const auto& tilePosition : object->second) { - const auto tiles = mTiles.lock(); - const auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + const std::lock_guard lock(mMutex); + const auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) continue; const auto tileResult = tile->second->removeHeightfield(cellPosition); if (tile->second->isEmpty()) { - tiles->erase(tile); + mTiles.erase(tile); ++mTilesGeneration; } if (tileResult && !result) @@ -182,23 +197,23 @@ namespace DetourNavigator return result; } - std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getMesh(std::string_view worldspace, const TilePosition& tilePosition) const { - if (const auto manager = getManager(tilePosition)) + if (const auto manager = getManager(worldspace, tilePosition)) return manager->getMesh(); return nullptr; } - std::shared_ptr TileCachedRecastMeshManager::getCachedMesh(const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const { - if (const auto manager = getManager(tilePosition)) + if (const auto manager = getManager(worldspace, tilePosition)) return manager->getCachedMesh(); return nullptr; } - std::shared_ptr TileCachedRecastMeshManager::getNewMesh(const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const { - if (const auto manager = getManager(tilePosition)) + if (const auto manager = getManager(worldspace, tilePosition)) return manager->getNewMesh(); return nullptr; } @@ -210,9 +225,9 @@ namespace DetourNavigator void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const { - const auto tiles = mTiles.lockConst(); - const auto it = tiles->find(tilePosition); - if (it == tiles->end()) + const std::lock_guard lock(mMutex); + const auto it = mTiles.find(tilePosition); + if (it == mTiles.end()) return; it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); } @@ -225,8 +240,8 @@ namespace DetourNavigator if (tile == tiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = tiles.emplace(tilePosition, - std::make_shared(tileBounds, mTilesGeneration)).first; + tile = tiles.emplace_hint(tile, tilePosition, + std::make_shared(tileBounds, mTilesGeneration)); } return tile->second->addObject(id, shape, transform, areaType); } @@ -253,11 +268,14 @@ namespace DetourNavigator return tileResult; } - std::shared_ptr TileCachedRecastMeshManager::getManager(const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getManager(std::string_view worldspace, + const TilePosition& tilePosition) const { - const auto tiles = mTiles.lockConst(); - const auto it = tiles->find(tilePosition); - if (it == tiles->end()) + const std::lock_guard lock(mMutex); + if (mWorldspace != worldspace) + return nullptr; + const auto it = mTiles.find(tilePosition); + if (it == mTiles.end()) return nullptr; return it->second; } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 68dc565fa5..23171f0925 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -8,8 +8,6 @@ #include "version.hpp" #include "heightfieldshape.hpp" -#include - #include #include #include @@ -22,6 +20,10 @@ namespace DetourNavigator public: explicit TileCachedRecastMeshManager(const RecastSettings& settings); + std::string getWorldspace() const; + + void setWorldspace(std::string_view worldspace); + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); @@ -36,19 +38,19 @@ namespace DetourNavigator bool changed = false; std::vector newTiles; { - auto tiles = mTiles.lock(); + const std::lock_guard lock(mMutex); const auto onTilePosition = [&] (const TilePosition& tilePosition) { if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) { newTiles.push_back(tilePosition); - if (updateTile(id, transform, areaType, tilePosition, tiles.get())) + if (updateTile(id, transform, areaType, tilePosition, mTiles)) { onChangedTile(tilePosition); changed = true; } } - else if (addTile(id, shape, transform, areaType, tilePosition, tiles.get())) + else if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) { newTiles.push_back(tilePosition); onChangedTile(tilePosition); @@ -59,7 +61,7 @@ namespace DetourNavigator std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { - if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, tiles.get())) + if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, mTiles)) { onChangedTile(tile); changed = true; @@ -84,16 +86,17 @@ namespace DetourNavigator std::optional removeHeightfield(const osg::Vec2i& cellPosition); - std::shared_ptr getMesh(const TilePosition& tilePosition) const; + std::shared_ptr getMesh(std::string_view worldspace, const TilePosition& tilePosition) const; - std::shared_ptr getCachedMesh(const TilePosition& tilePosition) const; + std::shared_ptr getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const; - std::shared_ptr getNewMesh(const TilePosition& tilePosition) const; + std::shared_ptr getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const; template void forEachTile(Function&& function) const { - for (auto& [tilePosition, recastMeshManager] : *mTiles.lockConst()) + const std::lock_guard lock(mMutex); + for (auto& [tilePosition, recastMeshManager] : mTiles) function(tilePosition, *recastMeshManager); } @@ -105,7 +108,9 @@ namespace DetourNavigator using TilesMap = std::map>; const RecastSettings& mSettings; - Misc::ScopeGuarded mTiles; + mutable std::mutex mMutex; + std::string mWorldspace; + TilesMap mTiles; std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::map> mHeightfieldTilesPositions; @@ -121,7 +126,8 @@ namespace DetourNavigator std::optional removeTile(const ObjectId id, const TilePosition& tilePosition, TilesMap& tiles); - inline std::shared_ptr getManager(const TilePosition& tilePosition) const; + inline std::shared_ptr getManager(std::string_view worldspace, + const TilePosition& tilePosition) const; }; } diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 9132364d48..15059356ff 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -54,6 +54,17 @@ Allows to complete cell loading only when minimal navigation mesh area is genera nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation speed on systems bound by CPU. Zero means no waiting. +enable nav mesh disk cache +-------------------------- + +:Type: boolean +:Range: True/False +:Default: True + +If true navmesh cache stored on disk will be used in addition to memory cache. +If navmesh tile is not present in memory cache, it will be looked up in the disk cache. +If it's not found there it will be generated. + Advanced settings ***************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index e80d6d1106..1752b4f5a0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -934,6 +934,9 @@ wait until min distance to player = 5 # Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. nav mesh version = 1 +# Use navigation mesh cache stored on disk (true, false) +enable nav mesh disk cache = true + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. From 9e0451c714c052fb2234be20bac00b4d8c2645a0 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Aug 2021 23:35:45 +0200 Subject: [PATCH 1846/2859] Support navmesh generation from launcher --- apps/launcher/datafilespage.cpp | 68 +++++++++++++++++++++- apps/launcher/datafilespage.hpp | 14 ++++- components/process/processinvoker.cpp | 14 ++++- components/process/processinvoker.hpp | 6 +- files/ui/datafilespage.ui | 84 ++++++++++++++++++++++++++- 5 files changed, 179 insertions(+), 7 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index eb0950dae0..097dc21dd2 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1,4 +1,5 @@ #include "datafilespage.hpp" +#include "maindialog.hpp" #include @@ -8,6 +9,7 @@ #include #include #include +#include #include #include @@ -24,11 +26,14 @@ const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; -Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent) +Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, MainDialog *parent) : QWidget(parent) + , mMainDialog(parent) , mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) + , mNavMeshToolInvoker(new Process::ProcessInvoker(this)) { ui.setupUi (this); setObjectName ("DataFilesPage"); @@ -57,8 +62,6 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: void Launcher::DataFilesPage::buildView() { - ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); - QToolButton * refreshButton = mSelector->refreshButton(); //tool buttons @@ -89,6 +92,13 @@ void Launcher::DataFilesPage::buildView() this, SLOT (slotProfileChangedByUser(QString, QString))); connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked())); + + connect(ui.updateNavMeshButton, SIGNAL(clicked()), this, SLOT(startNavMeshTool())); + connect(ui.cancelNavMeshButton, SIGNAL(clicked()), this, SLOT(killNavMeshTool())); + + connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(updateNavMeshProgress())); + connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardError()), this, SLOT(updateNavMeshProgress())); + connect(mNavMeshToolInvoker->getProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(navMeshToolFinished(int, QProcess::ExitStatus))); } bool Launcher::DataFilesPage::loadSettings() @@ -411,3 +421,55 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles) std::sort(cellNamesList.begin(), cellNamesList.end()); emit signalLoadedCellsChanged(cellNamesList); } + +void Launcher::DataFilesPage::startNavMeshTool() +{ + mMainDialog->writeSettings(); + + ui.navMeshLogPlainTextEdit->clear(); + ui.navMeshProgressBar->setValue(0); + ui.navMeshProgressBar->setMaximum(1); + + if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"))) + return; + + ui.cancelNavMeshButton->setEnabled(true); + ui.navMeshProgressBar->setEnabled(true); +} + +void Launcher::DataFilesPage::killNavMeshTool() +{ + mNavMeshToolInvoker->killProcess(); +} + +void Launcher::DataFilesPage::updateNavMeshProgress() +{ + QProcess& process = *mNavMeshToolInvoker->getProcess(); + QString text; + while (process.canReadLine()) + { + const QByteArray line = process.readLine(); + const auto end = std::find_if(line.rbegin(), line.rend(), [] (auto v) { return v != '\n' && v != '\r'; }); + text = QString::fromUtf8(line.mid(0, line.size() - (end - line.rbegin()))); + ui.navMeshLogPlainTextEdit->appendPlainText(text); + } + const QRegularExpression pattern(R"([\( ](\d+)/(\d+)[\) ])"); + QRegularExpressionMatch match = pattern.match(text); + if (!match.hasMatch()) + return; + int maximum = match.captured(2).toInt(); + if (text.contains("cell")) + maximum *= 100; + ui.navMeshProgressBar->setMaximum(std::max(ui.navMeshProgressBar->maximum(), maximum)); + ui.navMeshProgressBar->setValue(match.captured(1).toInt()); +} + +void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + updateNavMeshProgress(); + ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAll())); + if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit) + ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum()); + ui.cancelNavMeshButton->setEnabled(false); + ui.navMeshProgressBar->setEnabled(false); +} diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 5a7a6dc6e6..a039237590 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -2,6 +2,9 @@ #define DATAFILESPAGE_H #include "ui_datafilespage.h" + +#include + #include @@ -19,6 +22,7 @@ namespace Config { class GameSettings; namespace Launcher { + class MainDialog; class TextInputDialog; class ProfilesComboBox; @@ -31,7 +35,7 @@ namespace Launcher public: explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, - Config::LauncherSettings &launcherSettings, QWidget *parent = nullptr); + Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr); QAbstractItemModel* profilesModel() const; @@ -69,12 +73,18 @@ namespace Launcher void on_cloneProfileAction_triggered(); void on_deleteProfileAction_triggered(); + void startNavMeshTool(); + void killNavMeshTool(); + void updateNavMeshProgress(); + void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus); + public: /// Content List that is always present const static char *mDefaultContentListName; private: + MainDialog *mMainDialog; TextInputDialog *mNewProfileDialog; TextInputDialog *mCloneProfileDialog; @@ -87,6 +97,8 @@ namespace Launcher QStringList previousSelectedFiles; QString mDataLocal; + Process::ProcessInvoker* mNavMeshToolInvoker; + void buildView(); void setProfile (int index, bool savePrevious); void setProfile (const QString &previous, const QString ¤t, bool savePrevious); diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp index 78cf70038b..7c45c865a1 100644 --- a/components/process/processinvoker.cpp +++ b/components/process/processinvoker.cpp @@ -7,7 +7,8 @@ #include #include -Process::ProcessInvoker::ProcessInvoker() +Process::ProcessInvoker::ProcessInvoker(QObject* parent) + : QObject(parent) { mProcess = new QProcess(this); @@ -56,6 +57,7 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis // mProcess = new QProcess(this); mName = name; mArguments = arguments; + mIgnoreErrors = false; QString path(name); #ifdef Q_OS_WIN @@ -151,6 +153,8 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis void Process::ProcessInvoker::processError(QProcess::ProcessError error) { + if (mIgnoreErrors) + return; QMessageBox msgBox; msgBox.setWindowTitle(tr("Error running executable")); msgBox.setIcon(QMessageBox::Critical); @@ -166,6 +170,8 @@ void Process::ProcessInvoker::processError(QProcess::ProcessError error) void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) { + if (mIgnoreErrors) + return; QString error(mProcess->readAllStandardError()); error.append(tr("\nArguments:\n")); error.append(mArguments.join(" ")); @@ -181,3 +187,9 @@ void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus msgBox.exec(); } } + +void Process::ProcessInvoker::killProcess() +{ + mIgnoreErrors = true; + mProcess->kill(); +} diff --git a/components/process/processinvoker.hpp b/components/process/processinvoker.hpp index 8fff6658ca..f4b402cb12 100644 --- a/components/process/processinvoker.hpp +++ b/components/process/processinvoker.hpp @@ -13,7 +13,7 @@ namespace Process public: - ProcessInvoker(); + ProcessInvoker(QObject* parent = nullptr); ~ProcessInvoker(); // void setProcessName(const QString &name); @@ -27,12 +27,16 @@ namespace Process inline bool startProcess(const QString &name, bool detached = false) { return startProcess(name, QStringList(), detached); } bool startProcess(const QString &name, const QStringList &arguments, bool detached = false); + void killProcess(); + private: QProcess *mProcess; QString mName; QStringList mArguments; + bool mIgnoreErrors = false; + private slots: void processError(QProcess::ProcessError error); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index ccac5050ed..ff330391d2 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -2,12 +2,94 @@ DataFilesPage + + + 0 + 0 + 571 + 384 + + Qt::DefaultContextMenu - + + + 0 + + + + Data Files + + + + + + + + + + Navigation mesh cache + + + + + + + + Qt::TabFocus + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + + + Update + + + + + + + false + + + 0 + + + + + + + false + + + Cancel navigation mesh generation. Already processed data will be saved. + + + Cancel + + + + + + + + + true + + + QPlainTextEdit::NoWrap + + + true + + + + + + From 96eb8d7be9077b93461673c332ab536fc29bb7b0 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 Aug 2021 00:05:09 +0200 Subject: [PATCH 1847/2859] Write generated navmesh to navmeshdb Perform all request to db in a single thread to avoid blocking navmesh generator threads due to slow write operations. Write to db navmesh for all changes except update as it done for memory cache. Batch multiple db operations into a single transaction to speed up writing by not executing fsync after each insert/update query. All reads are performed in the same transaction so they see uncommited data. --- apps/openmw_test_suite/CMakeLists.txt | 1 + .../detournavigator/asyncnavmeshupdater.cpp | 201 ++++++ .../detournavigator/navigator.cpp | 34 +- .../detournavigator/navmeshtilescache.cpp | 44 +- .../detournavigator/settings.hpp | 50 ++ .../detournavigator/asyncnavmeshupdater.cpp | 596 ++++++++++++++---- .../detournavigator/asyncnavmeshupdater.hpp | 135 +++- components/detournavigator/makenavmesh.cpp | 85 --- components/detournavigator/makenavmesh.hpp | 12 - .../detournavigator/navmeshcacheitem.cpp | 23 +- .../detournavigator/navmeshcacheitem.hpp | 6 + components/detournavigator/navmeshmanager.cpp | 4 +- .../detournavigator/navmeshtilescache.cpp | 9 +- .../detournavigator/navmeshtilescache.hpp | 4 +- .../detournavigator/preparednavmeshdata.cpp | 11 + .../detournavigator/preparednavmeshdata.hpp | 2 +- components/detournavigator/recast.cpp | 31 + components/detournavigator/recast.hpp | 5 + components/detournavigator/settings.cpp | 1 + components/detournavigator/settings.hpp | 1 + components/resource/stats.cpp | 2 + .../reference/modding/settings/navigator.rst | 9 + files/settings-default.cfg | 3 + 23 files changed, 966 insertions(+), 303 deletions(-) create mode 100644 apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp create mode 100644 apps/openmw_test_suite/detournavigator/settings.hpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 6a93eaa397..19b31f78be 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -43,6 +43,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/tilecachedrecastmeshmanager.cpp detournavigator/navmeshdb.cpp detournavigator/serialization.cpp + detournavigator/asyncnavmeshupdater.cpp serialization/binaryreader.cpp serialization/binarywriter.cpp diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp new file mode 100644 index 0000000000..d245838858 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -0,0 +1,201 @@ +#include "settings.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + using namespace DetourNavigator::Tests; + + void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager) + { + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0}); + } + + struct DetourNavigatorAsyncNavMeshUpdaterTest : Test + { + Settings mSettings = makeSettings(); + TileCachedRecastMeshManager mRecastMeshManager {mSettings.mRecast}; + OffMeshConnectionsManager mOffMeshConnectionsManager {mSettings.mRecast}; + const osg::Vec3f mAgentHalfExtents {29, 29, 66}; + const TilePosition mPlayerTile {0, 0}; + const std::string mWorldspace = "sys::default"; + Loading::Listener mListener; + }; + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_all_jobs_done_when_empty_wait_should_terminate) + { + AsyncNavMeshUpdater updater {mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr}; + updater.wait(mListener, WaitConditionType::allJobsDone); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_required_tiles_present_when_empty_wait_should_terminate) + { + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + updater.wait(mListener, WaitConditionType::requiredTilesPresent); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_generate_navmesh_tile) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_post_should_lead_to_cache_hit) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + ASSERT_EQ(stats.mCache.mGetCount, 1); + ASSERT_EQ(stats.mCache.mHitCount, 0); + } + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + EXPECT_EQ(stats.mCache.mGetCount, 2); + EXPECT_EQ(stats.mCache.mHitCount, 1); + } + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_for_update_change_type_should_not_update_cache) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTiles {{TilePosition {0, 0}, ChangeType::update}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + ASSERT_EQ(stats.mCache.mGetCount, 1); + ASSERT_EQ(stats.mCache.mHitCount, 0); + } + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + EXPECT_EQ(stats.mCache.mGetCount, 2); + EXPECT_EQ(stats.mCache.mHitCount, 0); + } + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_write_generated_tile_to_db) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + auto db = std::make_unique(":memory:"); + NavMeshDb* const dbPtr = db.get(); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const TilePosition tilePosition {0, 0}; + const std::map changedTiles {{tilePosition, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); + ASSERT_NE(recastMesh, nullptr); + ShapeId nextShapeId {1}; + const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); }); + const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, objects)); + ASSERT_TRUE(tile.has_value()); + EXPECT_EQ(tile->mTileId, 1); + EXPECT_EQ(tile->mVersion, mSettings.mNavMeshVersion); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + auto db = std::make_unique(":memory:"); + NavMeshDb* const dbPtr = db.get(); + mSettings.mWriteToNavMeshDb = false; + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const TilePosition tilePosition {0, 0}; + const std::map changedTiles {{tilePosition, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); + ASSERT_NE(recastMesh, nullptr); + ShapeId nextShapeId {1}; + const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); }); + const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, objects)); + ASSERT_FALSE(tile.has_value()); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_read_from_db_on_cache_miss) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + mSettings.mMaxNavMeshTilesCacheSize = 0; + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::make_unique(":memory:")); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + ASSERT_EQ(stats.mCache.mGetCount, 1); + ASSERT_EQ(stats.mCache.mHitCount, 0); + ASSERT_TRUE(stats.mDb.has_value()); + ASSERT_EQ(stats.mDb->mGetTileCount, 1); + ASSERT_EQ(stats.mDbGetTileHits, 0); + } + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + EXPECT_EQ(stats.mCache.mGetCount, 2); + EXPECT_EQ(stats.mCache.mHitCount, 0); + ASSERT_TRUE(stats.mDb.has_value()); + EXPECT_EQ(stats.mDb->mGetTileCount, 2); + EXPECT_EQ(stats.mDbGetTileHits, 1); + } + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, on_changing_player_tile_post_should_remove_tiles_out_of_range) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTilesAdd {{TilePosition {0, 0}, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTilesAdd); + updater.wait(mListener, WaitConditionType::allJobsDone); + ASSERT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0); + const std::map changedTilesRemove {{TilePosition {0, 0}, ChangeType::remove}}; + const TilePosition playerTile(100, 100); + updater.post(mAgentHalfExtents, navMeshCacheItem, playerTile, mWorldspace, changedTilesRemove); + updater.wait(mListener, WaitConditionType::allJobsDone); + EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0); + } +} diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 0254d1b813..e7cd0a7db0 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1,4 +1,5 @@ #include "operators.hpp" +#include "settings.hpp" #include #include @@ -32,10 +33,11 @@ namespace { using namespace testing; using namespace DetourNavigator; + using namespace DetourNavigator::Tests; struct DetourNavigatorNavigatorTest : Test { - Settings mSettings; + Settings mSettings = makeSettings(); std::unique_ptr mNavigator; const osg::Vec3f mPlayerPosition; const std::string mWorldspace; @@ -62,34 +64,6 @@ namespace , mOut(mPath) , mStepSize(28.333332061767578125f) { - mSettings.mEnableWriteRecastMeshToFile = false; - mSettings.mEnableWriteNavMeshToFile = false; - mSettings.mEnableRecastMeshFileNameRevision = false; - mSettings.mEnableNavMeshFileNameRevision = false; - mSettings.mRecast.mBorderSize = 16; - mSettings.mRecast.mCellHeight = 0.2f; - mSettings.mRecast.mCellSize = 0.2f; - mSettings.mRecast.mDetailSampleDist = 6; - mSettings.mRecast.mDetailSampleMaxError = 1; - mSettings.mRecast.mMaxClimb = 34; - mSettings.mRecast.mMaxSimplificationError = 1.3f; - mSettings.mRecast.mMaxSlope = 49; - mSettings.mRecast.mRecastScaleFactor = 0.017647058823529415f; - mSettings.mRecast.mSwimHeightScale = 0.89999997615814208984375f; - mSettings.mRecast.mMaxEdgeLen = 12; - mSettings.mDetour.mMaxNavMeshQueryNodes = 2048; - mSettings.mRecast.mMaxVertsPerPoly = 6; - mSettings.mRecast.mRegionMergeArea = 400; - mSettings.mRecast.mRegionMinArea = 64; - mSettings.mRecast.mTileSize = 64; - mSettings.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); - mSettings.mAsyncNavMeshUpdaterThreads = 1; - mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024; - mSettings.mDetour.mMaxPolygonPathSize = 1024; - mSettings.mDetour.mMaxSmoothPathSize = 1024; - mSettings.mDetour.mMaxPolys = 4096; - mSettings.mMaxTilesNumber = 512; - mSettings.mMinUpdateInterval = std::chrono::milliseconds(50); mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:"))); } }; @@ -1013,7 +987,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - const Version expectedVersion {1, 1}; + const Version expectedVersion {1, 4}; const auto navMeshes = mNavigator->getNavMeshes(); ASSERT_EQ(navMeshes.size(), 1); diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index ec5576a634..cbd68e0fe1 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -80,51 +80,9 @@ namespace return result; } - template - void clone(const T* src, T*& dst, std::size_t size) - { - dst = static_cast(permRecastAlloc(static_cast(size) * sizeof(T))); - std::memcpy(dst, src, size * sizeof(T)); - } - - void clone(const rcPolyMesh& src, rcPolyMesh& dst) - { - dst.nverts = src.nverts; - dst.npolys = src.npolys; - dst.maxpolys = src.maxpolys; - dst.nvp = src.nvp; - rcVcopy(dst.bmin, src.bmin); - rcVcopy(dst.bmax, src.bmax); - dst.cs = src.cs; - dst.ch = src.ch; - dst.borderSize = src.borderSize; - dst.maxEdgeError = src.maxEdgeError; - clone(src.verts, dst.verts, getVertsLength(dst)); - clone(src.polys, dst.polys, getPolysLength(dst)); - clone(src.regs, dst.regs, getRegsLength(dst)); - clone(src.flags, dst.flags, getFlagsLength(dst)); - clone(src.areas, dst.areas, getAreasLength(dst)); - } - - void clone(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst) - { - dst.nmeshes = src.nmeshes; - dst.nverts = src.nverts; - dst.ntris = src.ntris; - clone(src.meshes, dst.meshes, getMeshesLength(dst)); - clone(src.verts, dst.verts, getVertsLength(dst)); - clone(src.tris, dst.tris, getTrisLength(dst)); - } - std::unique_ptr clone(const PreparedNavMeshData& value) { - auto result = std::make_unique(); - result->mUserId = value.mUserId; - result->mCellHeight = value.mCellHeight; - result->mCellSize = value.mCellSize; - clone(value.mPolyMesh, result->mPolyMesh); - clone(value.mPolyMeshDetail, result->mPolyMeshDetail); - return result; + return std::make_unique(value); } Mesh makeMesh() diff --git a/apps/openmw_test_suite/detournavigator/settings.hpp b/apps/openmw_test_suite/detournavigator/settings.hpp new file mode 100644 index 0000000000..dc37dc7550 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/settings.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H +#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H + +#include + +#include +#include + +namespace DetourNavigator +{ + namespace Tests + { + inline Settings makeSettings() + { + Settings result; + result.mEnableWriteRecastMeshToFile = false; + result.mEnableWriteNavMeshToFile = false; + result.mEnableRecastMeshFileNameRevision = false; + result.mEnableNavMeshFileNameRevision = false; + result.mRecast.mBorderSize = 16; + result.mRecast.mCellHeight = 0.2f; + result.mRecast.mCellSize = 0.2f; + result.mRecast.mDetailSampleDist = 6; + result.mRecast.mDetailSampleMaxError = 1; + result.mRecast.mMaxClimb = 34; + result.mRecast.mMaxSimplificationError = 1.3f; + result.mRecast.mMaxSlope = 49; + result.mRecast.mRecastScaleFactor = 0.017647058823529415f; + result.mRecast.mSwimHeightScale = 0.89999997615814208984375f; + result.mRecast.mMaxEdgeLen = 12; + result.mDetour.mMaxNavMeshQueryNodes = 2048; + result.mRecast.mMaxVertsPerPoly = 6; + result.mRecast.mRegionMergeArea = 400; + result.mRecast.mRegionMinArea = 64; + result.mRecast.mTileSize = 64; + result.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); + result.mAsyncNavMeshUpdaterThreads = 1; + result.mMaxNavMeshTilesCacheSize = 1024 * 1024; + result.mDetour.mMaxPolygonPathSize = 1024; + result.mDetour.mMaxSmoothPathSize = 1024; + result.mDetour.mMaxPolys = 4096; + result.mMaxTilesNumber = 512; + result.mMinUpdateInterval = std::chrono::milliseconds(50); + result.mWriteToNavMeshDb = true; + return result; + } + } +} + +#endif diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index a808030478..47d984030f 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -3,6 +3,9 @@ #include "makenavmesh.hpp" #include "settings.hpp" #include "version.hpp" +#include "serialization.hpp" +#include "navmeshdbutils.hpp" +#include "dbrefgeometryobject.hpp" #include #include @@ -15,70 +18,102 @@ #include #include #include +#include -namespace +namespace DetourNavigator { - using DetourNavigator::ChangeType; - using DetourNavigator::TilePosition; - using DetourNavigator::UpdateType; - using DetourNavigator::ChangeType; - using DetourNavigator::Job; - using DetourNavigator::JobIt; - - int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs) + namespace { - return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); - } + int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs) + { + return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); + } - int getMinDistanceTo(const TilePosition& position, int maxDistance, - const std::set>& pushedTiles, - const std::set>& presentTiles) - { - int result = maxDistance; - for (const auto& [halfExtents, tile] : pushedTiles) - if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end()) - result = std::min(result, getManhattanDistance(position, tile)); - return result; - } + int getMinDistanceTo(const TilePosition& position, int maxDistance, + const std::set>& pushedTiles, + const std::set>& presentTiles) + { + int result = maxDistance; + for (const auto& [halfExtents, tile] : pushedTiles) + if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end()) + result = std::min(result, getManhattanDistance(position, tile)); + return result; + } - UpdateType getUpdateType(ChangeType changeType) noexcept - { - if (changeType == ChangeType::update) - return UpdateType::Temporary; - return UpdateType::Persistent; - } + auto getPriority(const Job& job) noexcept + { + return std::make_tuple(-static_cast>(job.mState), job.mProcessTime, + job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); + } - auto getPriority(const Job& job) noexcept - { - return std::make_tuple(job.mProcessTime, job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); - } + struct LessByJobPriority + { + bool operator()(JobIt lhs, JobIt rhs) const noexcept + { + return getPriority(*lhs) < getPriority(*rhs); + } + }; - struct LessByJobPriority - { - bool operator()(JobIt lhs, JobIt rhs) const noexcept + void insertPrioritizedJob(JobIt job, std::deque& queue) { - return getPriority(*lhs) < getPriority(*rhs); + const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {}); + queue.insert(it, job); } - }; - void insertPrioritizedJob(JobIt job, std::deque& queue) - { - const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {}); - queue.insert(it, job); - } + auto getDbPriority(const Job& job) noexcept + { + return std::make_tuple(static_cast>(job.mState), + job.mChangeType, job.mDistanceToPlayer, job.mDistanceToOrigin); + } - auto getAgentAndTile(const Job& job) noexcept - { - return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile); + struct LessByJobDbPriority + { + bool operator()(JobIt lhs, JobIt rhs) const noexcept + { + return getDbPriority(*lhs) < getDbPriority(*rhs); + } + }; + + void insertPrioritizedDbJob(JobIt job, std::deque& queue) + { + const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobDbPriority {}); + queue.insert(it, job); + } + + auto getAgentAndTile(const Job& job) noexcept + { + return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile); + } + + std::unique_ptr makeDbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, const Settings& settings) + { + if (db == nullptr) + return nullptr; + return std::make_unique(updater, std::move(db), TileVersion(settings.mNavMeshVersion), settings.mRecast); + } + + void updateJobs(std::deque& jobs, TilePosition playerTile, int maxTiles) + { + for (JobIt job : jobs) + { + job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); + if (!shouldAddTile(job->mChangedTile, playerTile, maxTiles)) + job->mChangeType = ChangeType::remove; + } + } + + std::size_t getNextJobId() + { + static std::atomic_size_t nextJobId {1}; + return nextJobId.fetch_add(1); + } } -} -namespace DetourNavigator -{ Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr navMeshCacheItem, std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::chrono::steady_clock::time_point processTime) - : mAgentHalfExtents(agentHalfExtents) + : mId(getNextJobId()) + , mAgentHalfExtents(agentHalfExtents) , mNavMeshCacheItem(std::move(navMeshCacheItem)) , mWorldspace(worldspace) , mChangedTile(changedTile) @@ -94,9 +129,9 @@ namespace DetourNavigator : mSettings(settings) , mRecastMeshManager(recastMeshManager) , mOffMeshConnectionsManager(offMeshConnectionsManager) - , mDb(std::move(db)) , mShouldStop() , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) + , mDbWorker(makeDbWorker(*this, std::move(db), mSettings)) { for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) mThreads.emplace_back([&] { process(); }); @@ -105,6 +140,8 @@ namespace DetourNavigator AsyncNavMeshUpdater::~AsyncNavMeshUpdater() { mShouldStop = true; + if (mDbWorker != nullptr) + mDbWorker->stop(); std::unique_lock lock(mMutex); mWaiting.clear(); mHasJob.notify_all(); @@ -128,18 +165,12 @@ namespace DetourNavigator return; const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); + const int maxTiles = std::min(mSettings.get().mMaxTilesNumber, params.maxTiles); - const std::lock_guard lock(mMutex); + std::unique_lock lock(mMutex); if (playerTileChanged) - { - for (JobIt job : mWaiting) - { - job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); - if (!shouldAddTile(job->mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles))) - job->mChangeType = ChangeType::remove; - } - } + updateJobs(mWaiting, playerTile, maxTiles); for (const auto& [changedTile, changeType] : changedTiles) { @@ -152,6 +183,9 @@ namespace DetourNavigator const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace, changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime); + Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentHalfExtents << ")" + << " changedTile=(" << it->mChangedTile << ")"; + if (playerTileChanged) mWaiting.push_back(it); else @@ -166,6 +200,11 @@ namespace DetourNavigator if (!mWaiting.empty()) mHasJob.notify_all(); + + lock.unlock(); + + if (playerTileChanged && mDbWorker != nullptr) + mDbWorker->updateJobs(playerTile, maxTiles); } void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType) @@ -243,25 +282,40 @@ namespace DetourNavigator mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); } - void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const + AsyncNavMeshUpdater::Stats AsyncNavMeshUpdater::getStats() const { - std::size_t jobs = 0; - std::size_t waiting = 0; - std::size_t pushed = 0; - + Stats result; { const std::lock_guard lock(mMutex); - jobs = mJobs.size(); - waiting = mWaiting.size(); - pushed = mPushed.size(); + result.mJobs = mJobs.size(); + result.mWaiting = mWaiting.size(); + result.mPushed = mPushed.size(); } + result.mProcessing = mProcessingTiles.lockConst()->size(); + if (mDbWorker != nullptr) + result.mDb = mDbWorker->getStats(); + result.mCache = mNavMeshTilesCache.getStats(); + result.mDbGetTileHits = mDbGetTileHits.load(std::memory_order_relaxed); + return result; + } - stats.setAttribute(frameNumber, "NavMesh Jobs", jobs); - stats.setAttribute(frameNumber, "NavMesh Waiting", waiting); - stats.setAttribute(frameNumber, "NavMesh Pushed", pushed); - stats.setAttribute(frameNumber, "NavMesh Processing", mProcessingTiles.lockConst()->size()); + void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out) + { + out.setAttribute(frameNumber, "NavMesh Jobs", static_cast(stats.mJobs)); + out.setAttribute(frameNumber, "NavMesh Waiting", static_cast(stats.mWaiting)); + out.setAttribute(frameNumber, "NavMesh Pushed", static_cast(stats.mPushed)); + out.setAttribute(frameNumber, "NavMesh Processing", static_cast(stats.mProcessing)); - mNavMeshTilesCache.reportStats(frameNumber, stats); + if (stats.mDb.has_value()) + { + out.setAttribute(frameNumber, "NavMesh DbJobs", static_cast(stats.mDb->mJobs)); + + if (stats.mDb->mGetTileCount > 0) + out.setAttribute(frameNumber, "NavMesh DbCacheHitRate", static_cast(stats.mDbGetTileHits) + / static_cast(stats.mDb->mGetTileCount) * 100.0); + } + + reportStats(stats.mCache, frameNumber, out); } void AsyncNavMeshUpdater::process() noexcept @@ -274,12 +328,26 @@ namespace DetourNavigator { if (JobIt job = getNextJob(); job != mJobs.end()) { - const auto processed = processJob(*job); - unlockTile(job->mAgentHalfExtents, job->mChangedTile); - if (processed) - removeJob(job); - else - repost(job); + const JobStatus status = processJob(*job); + Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status; + switch (status) + { + case JobStatus::Done: + unlockTile(job->mAgentHalfExtents, job->mChangedTile); + if (job->mGeneratedNavMeshData != nullptr) + mDbWorker->enqueueJob(job); + else + removeJob(job); + break; + case JobStatus::Fail: + repost(job); + break; + case JobStatus::MemoryCacheMiss: + { + mDbWorker->enqueueJob(job); + break; + } + } } else cleanupLastUpdates(); @@ -292,34 +360,156 @@ namespace DetourNavigator Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id(); } - bool AsyncNavMeshUpdater::processJob(const Job& job) + JobStatus AsyncNavMeshUpdater::processJob(Job& job) { - Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")" - " by thread=" << std::this_thread::get_id(); - - const auto start = std::chrono::steady_clock::now(); + Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); if (!navMeshCacheItem) - return true; + return JobStatus::Done; - const auto recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile); const auto playerTile = *mPlayerTile.lockConst(); + const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams(); + + if (!shouldAddTile(job.mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles))) + { + Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; + navMeshCacheItem->lock()->removeTile(job.mChangedTile); + return JobStatus::Done; + } + + switch (job.mState) + { + case JobState::Initial: + return processInitialJob(job, *navMeshCacheItem); + case JobState::WithDbResult: + return processJobWithDbResult(job, *navMeshCacheItem); + } + + return JobStatus::Done; + } + + JobStatus AsyncNavMeshUpdater::processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem) + { + Log(Debug::Debug) << "Processing initial job " << job.mId; + + std::shared_ptr recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile); + + if (recastMesh == nullptr) + { + Log(Debug::Debug) << "Null recast mesh for job " << job.mId; + navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); + return JobStatus::Done; + } + + if (isEmpty(*recastMesh)) + { + Log(Debug::Debug) << "Empty bounds for job " << job.mId; + navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); + return JobStatus::Done; + } + + NavMeshTilesCache::Value cachedNavMeshData = mNavMeshTilesCache.get(job.mAgentHalfExtents, job.mChangedTile, *recastMesh); + std::unique_ptr preparedNavMeshData; + const PreparedNavMeshData* preparedNavMeshDataPtr = nullptr; + + if (cachedNavMeshData) + { + preparedNavMeshDataPtr = &cachedNavMeshData.get(); + } + else + { + if (job.mChangeType != ChangeType::update && mDbWorker != nullptr) + { + job.mRecastMesh = std::move(recastMesh); + return JobStatus::MemoryCacheMiss; + } + + preparedNavMeshData = prepareNavMeshTileData(*recastMesh, job.mChangedTile, job.mAgentHalfExtents, mSettings.get().mRecast); + + if (preparedNavMeshData == nullptr) + { + Log(Debug::Debug) << "Null navmesh data for job " << job.mId; + navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); + return JobStatus::Done; + } + + if (job.mChangeType == ChangeType::update) + { + preparedNavMeshDataPtr = preparedNavMeshData.get(); + } + else + { + cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentHalfExtents, job.mChangedTile, + *recastMesh, std::move(preparedNavMeshData)); + preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get(); + } + } + const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); - const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mWorldspace, job.mChangedTile, - playerTile, offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, - getUpdateType(job.mChangeType), mDb, mNextShapeId); + const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData), + makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast)); + + return handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *recastMesh); + } + + JobStatus AsyncNavMeshUpdater::processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem) + { + Log(Debug::Debug) << "Processing job with db result " << job.mId; + + std::unique_ptr preparedNavMeshData; + bool generatedNavMeshData = false; + + if (job.mCachedTileData.has_value() && job.mCachedTileData->mVersion == mSettings.get().mNavMeshVersion) + { + preparedNavMeshData = std::make_unique(); + if (deserialize(job.mCachedTileData->mData, *preparedNavMeshData)) + ++mDbGetTileHits; + else + preparedNavMeshData = nullptr; + } + + if (preparedNavMeshData == nullptr) + { + preparedNavMeshData = prepareNavMeshTileData(*job.mRecastMesh, job.mChangedTile, job.mAgentHalfExtents, mSettings.get().mRecast); + generatedNavMeshData = true; + } - if (recastMesh != nullptr) + if (preparedNavMeshData == nullptr) { - const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion(); - mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile, - Version {recastMesh->getGeneration(), recastMesh->getRevision()}, - navMeshVersion); + Log(Debug::Debug) << "Null navmesh data for job " << job.mId; + navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); + return JobStatus::Done; } + auto cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentHalfExtents, job.mChangedTile, *job.mRecastMesh, + std::move(preparedNavMeshData)); + + const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); + + const PreparedNavMeshData* preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get(); + const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData), + makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast)); + + const JobStatus result = handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *job.mRecastMesh); + + if (result == JobStatus::Done && job.mChangeType != ChangeType::update + && mDbWorker != nullptr && mSettings.get().mWriteToNavMeshDb && generatedNavMeshData) + job.mGeneratedNavMeshData = std::make_unique(*preparedNavMeshDataPtr); + + return result; + } + + JobStatus AsyncNavMeshUpdater::handleUpdateNavMeshStatus(UpdateNavMeshStatus status, + const Job& job, const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh) + { + const Version navMeshVersion = navMeshCacheItem.lockConst()->getVersion(); + mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile, + Version {recastMesh.getGeneration(), recastMesh.getRevision()}, + navMeshVersion); + if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost) { const std::scoped_lock lock(mMutex); @@ -331,23 +521,9 @@ namespace DetourNavigator mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile)); } - const auto finish = std::chrono::steady_clock::now(); - - writeDebugFiles(job, recastMesh.get()); - - using FloatMs = std::chrono::duration; + writeDebugFiles(job, &recastMesh); - const Version version = navMeshCacheItem->lockConst()->getVersion(); - Log(Debug::Debug) << std::fixed << std::setprecision(2) << - "Cache updated for agent=(" << job.mAgentHalfExtents << ")" << - " tile=" << job.mChangedTile << - " status=" << status << - " generation=" << version.mGeneration << - " revision=" << version.mRevision << - " time=" << std::chrono::duration_cast(finish - start).count() << "ms" << - " thread=" << std::this_thread::get_id(); - - return isSuccess(status); + return isSuccess(status) ? JobStatus::Done : JobStatus::Fail; } JobIt AsyncNavMeshUpdater::getNextJob() @@ -376,8 +552,12 @@ namespace DetourNavigator mWaiting.pop_front(); + if (job->mRecastMesh != nullptr) + return job; + if (!lockTile(job->mAgentHalfExtents, job->mChangedTile)) { + Log(Debug::Debug) << "Failed to lock tile by " << job->mId; ++job->mTryNumber; insertPrioritizedJob(job, mWaiting); return mJobs.end(); @@ -415,6 +595,8 @@ namespace DetourNavigator void AsyncNavMeshUpdater::repost(JobIt job) { + unlockTile(job->mAgentHalfExtents, job->mChangedTile); + if (mShouldStop || job->mTryNumber > 2) return; @@ -433,17 +615,15 @@ namespace DetourNavigator bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) { - if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) - return true; + Log(Debug::Debug) << "Locking tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")"; return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second; } void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) { - if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) - return; auto locked = mProcessingTiles.lock(); locked->erase(std::tie(agentHalfExtents, changedTile)); + Log(Debug::Debug) << "Unlocked tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")"; if (locked->empty()) mProcessed.notify_all(); } @@ -469,9 +649,201 @@ namespace DetourNavigator } } + void AsyncNavMeshUpdater::enqueueJob(JobIt job) + { + Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id(); + const std::lock_guard lock(mMutex); + insertPrioritizedJob(job, mWaiting); + mHasJob.notify_all(); + } + void AsyncNavMeshUpdater::removeJob(JobIt job) { + Log(Debug::Debug) << "Removing job " << job->mId << " by thread=" << std::this_thread::get_id(); const std::lock_guard lock(mMutex); mJobs.erase(job); } + + void DbJobQueue::push(JobIt job) + { + const std::lock_guard lock(mMutex); + insertPrioritizedDbJob(job, mJobs); + mHasJob.notify_all(); + } + + std::optional DbJobQueue::pop() + { + std::unique_lock lock(mMutex); + mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); }); + if (mJobs.empty()) + return std::nullopt; + const JobIt job = mJobs.front(); + mJobs.pop_front(); + return job; + } + + void DbJobQueue::update(TilePosition playerTile, int maxTiles) + { + const std::lock_guard lock(mMutex); + updateJobs(mJobs, playerTile, maxTiles); + std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority {}); + } + + void DbJobQueue::stop() + { + const std::lock_guard lock(mMutex); + mJobs.clear(); + mShouldStop = true; + mHasJob.notify_all(); + } + + std::size_t DbJobQueue::size() const + { + const std::lock_guard lock(mMutex); + return mJobs.size(); + } + + DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, + TileVersion version, const RecastSettings& recastSettings) + : mUpdater(updater) + , mRecastSettings(recastSettings) + , mDb(std::move(db)) + , mVersion(version) + , mNextTileId(mDb->getMaxTileId() + 1) + , mNextShapeId(mDb->getMaxShapeId() + 1) + , mThread([this] { run(); }) + { + } + + DbWorker::~DbWorker() + { + stop(); + mThread.join(); + } + + void DbWorker::enqueueJob(JobIt job) + { + Log(Debug::Debug) << "Enqueueing db job " << job->mId << " by thread=" << std::this_thread::get_id(); + mQueue.push(job); + } + + DbWorker::Stats DbWorker::getStats() const + { + Stats result; + result.mJobs = mQueue.size(); + result.mGetTileCount = mGetTileCount.load(std::memory_order::memory_order_relaxed); + return result; + } + + void DbWorker::stop() + { + mShouldStop = true; + mQueue.stop(); + } + + void DbWorker::run() noexcept + { + constexpr std::size_t writesPerTransaction = 100; + auto transaction = mDb->startTransaction(); + while (!mShouldStop) + { + try + { + if (const auto job = mQueue.pop()) + processJob(*job); + if (mWrites > writesPerTransaction) + { + mWrites = 0; + transaction.commit(); + transaction = mDb->startTransaction(); + } + } + catch (const std::exception& e) + { + Log(Debug::Error) << "DbWorker exception: " << e.what(); + } + } + transaction.commit(); + } + + void DbWorker::processJob(JobIt job) + { + const auto process = [&] (auto f) + { + try + { + f(job); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what(); + } + }; + + if (job->mGeneratedNavMeshData != nullptr) + { + process([&] (JobIt job) { processWritingJob(job); }); + mUpdater.removeJob(job); + return; + } + + process([&] (JobIt job) { processReadingJob(job); }); + job->mState = JobState::WithDbResult; + mUpdater.enqueueJob(job); + } + + void DbWorker::processReadingJob(JobIt job) + { + Log(Debug::Debug) << "Processing db read job " << job->mId; + + if (job->mInput.empty()) + { + Log(Debug::Debug) << "Serializing input for job " << job->mId; + const ShapeId shapeId = mNextShapeId; + const std::vector objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); + if (shapeId != mNextShapeId) + ++mWrites; + job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects); + } + + job->mCachedTileData = mDb->getTileData(job->mWorldspace, job->mChangedTile, job->mInput); + ++mGetTileCount; + } + + void DbWorker::processWritingJob(JobIt job) + { + ++mWrites; + + Log(Debug::Debug) << "Processing db write job " << job->mId; + + if (job->mInput.empty()) + { + Log(Debug::Debug) << "Serializing input for job " << job->mId; + const std::vector objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); + job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects); + } + + if (const auto& cachedTileData = job->mCachedTileData) + { + Log(Debug::Debug) << "Update db tile by job " << job->mId; + job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId; + mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData)); + return; + } + + const auto cached = mDb->findTile(job->mWorldspace, job->mChangedTile, job->mInput); + if (cached.has_value() && cached->mVersion == mVersion) + { + Log(Debug::Debug) << "Ignore existing db tile by job " << job->mId; + return; + } + + job->mGeneratedNavMeshData->mUserId = mNextTileId; + Log(Debug::Debug) << "Insert db tile by job " << job->mId; + mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile, + mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData)); + ++mNextTileId.t; + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 44d5be58d5..541d86fe9c 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -21,6 +21,7 @@ #include #include #include +#include class dtNavMesh; @@ -54,8 +55,15 @@ namespace DetourNavigator return stream << "ChangeType::" << static_cast(value); } + enum class JobState + { + Initial, + WithDbResult, + }; + struct Job { + const std::size_t mId; const osg::Vec3f mAgentHalfExtents; const std::weak_ptr mNavMeshCacheItem; const std::string mWorldspace; @@ -65,6 +73,11 @@ namespace DetourNavigator ChangeType mChangeType; int mDistanceToPlayer; const int mDistanceToOrigin; + JobState mState = JobState::Initial; + std::vector mInput; + std::shared_ptr mRecastMesh; + std::optional mCachedTileData; + std::unique_ptr mGeneratedNavMeshData; Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr navMeshCacheItem, std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, @@ -73,27 +86,124 @@ namespace DetourNavigator using JobIt = std::list::iterator; + enum class JobStatus + { + Done, + Fail, + MemoryCacheMiss, + }; + + inline std::ostream& operator<<(std::ostream& stream, JobStatus value) + { + switch (value) + { + case JobStatus::Done: return stream << "JobStatus::Done"; + case JobStatus::Fail: return stream << "JobStatus::Fail"; + case JobStatus::MemoryCacheMiss: return stream << "JobStatus::MemoryCacheMiss"; + } + return stream << "JobStatus::" << static_cast>(value); + } + + class DbJobQueue + { + public: + void push(JobIt job); + + std::optional pop(); + + void update(TilePosition playerTile, int maxTiles); + + void stop(); + + std::size_t size() const; + + private: + mutable std::mutex mMutex; + std::condition_variable mHasJob; + std::deque mJobs; + bool mShouldStop = false; + }; + + class AsyncNavMeshUpdater; + + class DbWorker + { + public: + struct Stats + { + std::size_t mJobs = 0; + std::size_t mGetTileCount = 0; + }; + + DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, + TileVersion version, const RecastSettings& recastSettings); + + ~DbWorker(); + + Stats getStats() const; + + void enqueueJob(JobIt job); + + void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); } + + void stop(); + + private: + AsyncNavMeshUpdater& mUpdater; + const RecastSettings& mRecastSettings; + const std::unique_ptr mDb; + const TileVersion mVersion; + TileId mNextTileId; + ShapeId mNextShapeId; + DbJobQueue mQueue; + std::atomic_bool mShouldStop {false}; + std::atomic_size_t mGetTileCount {0}; + std::size_t mWrites = 0; + std::thread mThread; + + inline void run() noexcept; + + inline void processJob(JobIt job); + + inline void processReadingJob(JobIt job); + + inline void processWritingJob(JobIt job); + }; + class AsyncNavMeshUpdater { public: + struct Stats + { + std::size_t mJobs = 0; + std::size_t mWaiting = 0; + std::size_t mPushed = 0; + std::size_t mProcessing = 0; + std::size_t mDbGetTileHits = 0; + std::optional mDb; + NavMeshTilesCache::Stats mCache; + }; + AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db); ~AsyncNavMeshUpdater(); - void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, + void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, std::string_view worldspace, const std::map& changedTiles); void wait(Loading::Listener& listener, WaitConditionType waitConditionType); - void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + Stats getStats() const; + + void enqueueJob(JobIt job); + + void removeJob(JobIt job); private: std::reference_wrapper mSettings; std::reference_wrapper mRecastMeshManager; std::reference_wrapper mOffMeshConnectionsManager; - Misc::ScopeGuarded> mDb; - ShapeId mNextShapeId {1}; std::atomic_bool mShouldStop; mutable std::mutex mMutex; std::condition_variable mHasJob; @@ -108,14 +218,21 @@ namespace DetourNavigator std::map, std::chrono::steady_clock::time_point> mLastUpdates; std::set> mPresentTiles; std::vector mThreads; + std::unique_ptr mDbWorker; + std::atomic_size_t mDbGetTileHits {0}; void process() noexcept; - bool processJob(const Job& job); + JobStatus processJob(Job& job); - JobIt getNextJob(); + inline JobStatus processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem); + + inline JobStatus processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem); + + inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job, + const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh); - JobIt getJob(std::deque& jobs, bool changeLastUpdate); + JobIt getNextJob(); void postThreadJob(JobIt job, std::deque& queue); @@ -134,9 +251,9 @@ namespace DetourNavigator int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); void waitUntilAllJobsDone(); - - inline void removeJob(JobIt job); }; + + void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out); } #endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 8b764b91b4..070a227454 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -544,89 +544,4 @@ namespace DetourNavigator return navMesh; } - - UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const std::string& worldspace, const TilePosition& changedTile, const TilePosition& playerTile, - const std::vector& offMeshConnections, const Settings& settings, - const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType, - Misc::ScopeGuarded>& db, ShapeId& nextShapeId) - { - Log(Debug::Debug) << std::fixed << std::setprecision(2) << - "Update NavMesh with multiple tiles:" << - " agentHeight=" << getHeight(settings.mRecast, agentHalfExtents) << - " agentMaxClimb=" << getMaxClimb(settings.mRecast) << - " agentRadius=" << getRadius(settings.mRecast, agentHalfExtents) << - " changedTile=(" << changedTile << ")" << - " playerTile=(" << playerTile << ")" << - " changedTileDistance=" << getDistance(changedTile, playerTile); - - if (!recastMesh) - { - Log(Debug::Debug) << "Ignore add tile: recastMesh is null"; - return navMeshCacheItem->lock()->removeTile(changedTile); - } - - if (isEmpty(*recastMesh)) - { - Log(Debug::Debug) << "Ignore add tile: recastMesh is empty"; - return navMeshCacheItem->lock()->removeTile(changedTile); - } - - const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); - - if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles))) - { - Log(Debug::Debug) << "Ignore add tile: too far from player"; - return navMeshCacheItem->lock()->removeTile(changedTile); - } - - auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh); - bool cached = static_cast(cachedNavMeshData); - - if (!cachedNavMeshData) - { - std::optional stored; - if (const auto dbLocked = db.lock(); *dbLocked != nullptr) - { - const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), - [&] (const MeshSource& v) { return resolveMeshSource(**dbLocked, v, nextShapeId); }); - stored = (*dbLocked)->getTileData(worldspace, changedTile, serialize(settings.mRecast, *recastMesh, objects)); - } - - std::unique_ptr prepared; - if (stored.has_value() && stored->mVersion == settings.mNavMeshVersion) - { - prepared = std::make_unique(); - if (!deserialize(stored->mData, *prepared)) - prepared = nullptr; - } - - if (prepared == nullptr) - prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings.mRecast); - - if (prepared == nullptr) - { - Log(Debug::Debug) << "Ignore add tile: NavMeshData is null"; - return navMeshCacheItem->lock()->removeTile(changedTile); - } - - if (updateType == UpdateType::Temporary) - return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(), - makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings.mRecast)); - - cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, std::move(prepared)); - - if (!cachedNavMeshData) - { - Log(Debug::Debug) << "Navigator cache overflow"; - return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(), - makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings.mRecast)); - } - } - - const auto updateStatus = navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData), - makeNavMeshTileData(cachedNavMeshData.get(), offMeshConnections, agentHalfExtents, changedTile, settings.mRecast)); - - return UpdateNavMeshStatusBuilder(updateStatus).cached(cached).getResult(); - } } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 40ead79ad6..14919ab134 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -58,18 +58,6 @@ namespace DetourNavigator const TilePosition& tile, const RecastSettings& settings); NavMeshPtr makeEmptyNavMesh(const Settings& settings); - - enum class UpdateType - { - Persistent, - Temporary - }; - - UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const std::string& worldspace, const TilePosition& changedTile, const TilePosition& playerTile, - const std::vector& offMeshConnections, const Settings& settings, - const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType, - Misc::ScopeGuarded>& db, ShapeId& nextShapeId); } #endif diff --git a/components/detournavigator/navmeshcacheitem.cpp b/components/detournavigator/navmeshcacheitem.cpp index decf45de2c..ffcb0a7359 100644 --- a/components/detournavigator/navmeshcacheitem.cpp +++ b/components/detournavigator/navmeshcacheitem.cpp @@ -50,7 +50,8 @@ namespace DetourNavigator { return UpdateNavMeshStatus::ignored; } - const auto removed = ::removeTile(*mImpl, position); + bool removed = ::removeTile(*mImpl, position); + removed = mEmptyTiles.erase(position) > 0 || removed; const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize); if (dtStatusSucceed(addStatus)) { @@ -82,7 +83,8 @@ namespace DetourNavigator UpdateNavMeshStatus NavMeshCacheItem::removeTile(const TilePosition& position) { - const auto removed = ::removeTile(*mImpl, position); + bool removed = ::removeTile(*mImpl, position); + removed = mEmptyTiles.erase(position) > 0 || removed; if (removed) { mUsedTiles.erase(position); @@ -90,4 +92,21 @@ namespace DetourNavigator } return UpdateNavMeshStatusBuilder().removed(removed).getResult(); } + + UpdateNavMeshStatus NavMeshCacheItem::markAsEmpty(const TilePosition& position) + { + bool removed = ::removeTile(*mImpl, position); + removed = mEmptyTiles.insert(position).second || removed; + if (removed) + { + mUsedTiles.erase(position); + ++mVersion.mRevision; + } + return UpdateNavMeshStatusBuilder().removed(removed).getResult(); + } + + bool NavMeshCacheItem::isEmptyTile(const TilePosition& position) const + { + return mEmptyTiles.find(position) != mEmptyTiles.end(); + } } diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index 5d3b404080..ae4a2de66b 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -12,6 +12,7 @@ #include #include +#include struct dtMeshTile; @@ -147,6 +148,10 @@ namespace DetourNavigator UpdateNavMeshStatus removeTile(const TilePosition& position); + UpdateNavMeshStatus markAsEmpty(const TilePosition& position); + + bool isEmptyTile(const TilePosition& position) const; + template void forEachUsedTile(Function&& function) const { @@ -166,6 +171,7 @@ namespace DetourNavigator NavMeshPtr mImpl; Version mVersion; std::map mUsedTiles; + std::set mEmptyTiles; }; using GuardedNavMeshCacheItem = Misc::ScopeGuarded; diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 9c8cb5389c..399af8a6a9 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -211,7 +211,7 @@ namespace DetourNavigator const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); if (shouldAdd && !presentInNavMesh) - tilesToPost.insert(std::make_pair(tile, ChangeType::add)); + tilesToPost.insert(std::make_pair(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add)); else if (!shouldAdd && presentInNavMesh) tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); else @@ -243,7 +243,7 @@ namespace DetourNavigator void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const { - mAsyncNavMeshUpdater.reportStats(frameNumber, stats); + DetourNavigator::reportStats(mAsyncNavMeshUpdater.getStats(), frameNumber, stats); } RecastMeshTiles NavMeshManager::getRecastMeshTiles() const diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 3d595f13a8..bbda8a3179 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -79,12 +79,11 @@ namespace DetourNavigator return result; } - void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& out) const + void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out) { - const Stats stats = getStats(); - out.setAttribute(frameNumber, "NavMesh CacheSize", stats.mNavMeshCacheSize); - out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles); - out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles); + out.setAttribute(frameNumber, "NavMesh CacheSize", static_cast(stats.mNavMeshCacheSize)); + out.setAttribute(frameNumber, "NavMesh UsedTiles", static_cast(stats.mUsedNavMeshTiles)); + out.setAttribute(frameNumber, "NavMesh CachedTiles", static_cast(stats.mCachedNavMeshTiles)); if (stats.mGetCount > 0) out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(stats.mHitCount) / stats.mGetCount * 100.0); } diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index fdafa0c6d6..e7e0b6c7a8 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -144,8 +144,6 @@ namespace DetourNavigator Stats getStats() const; - void reportStats(unsigned int frameNumber, osg::Stats& stats) const; - private: mutable std::mutex mMutex; std::size_t mMaxNavMeshDataSize; @@ -163,6 +161,8 @@ namespace DetourNavigator void releaseItem(ItemIterator iterator); }; + + void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out); } #endif diff --git a/components/detournavigator/preparednavmeshdata.cpp b/components/detournavigator/preparednavmeshdata.cpp index 77c70eade3..a737ae19a5 100644 --- a/components/detournavigator/preparednavmeshdata.cpp +++ b/components/detournavigator/preparednavmeshdata.cpp @@ -4,6 +4,8 @@ #include +#include + namespace { void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept @@ -24,6 +26,15 @@ namespace DetourNavigator initPolyMeshDetail(mPolyMeshDetail); } + PreparedNavMeshData::PreparedNavMeshData(const PreparedNavMeshData& other) + : mUserId(other.mUserId) + , mCellSize(other.mCellSize) + , mCellHeight(other.mCellHeight) + { + copyPolyMesh(other.mPolyMesh, mPolyMesh); + copyPolyMeshDetail(other.mPolyMeshDetail, mPolyMeshDetail); + } + PreparedNavMeshData::~PreparedNavMeshData() noexcept { freePolyMeshDetail(mPolyMeshDetail); diff --git a/components/detournavigator/preparednavmeshdata.hpp b/components/detournavigator/preparednavmeshdata.hpp index 3566cfc71b..b3de7a447f 100644 --- a/components/detournavigator/preparednavmeshdata.hpp +++ b/components/detournavigator/preparednavmeshdata.hpp @@ -18,7 +18,7 @@ namespace DetourNavigator rcPolyMeshDetail mPolyMeshDetail; PreparedNavMeshData() noexcept; - PreparedNavMeshData(const PreparedNavMeshData&) = delete; + PreparedNavMeshData(const PreparedNavMeshData& other); ~PreparedNavMeshData() noexcept; diff --git a/components/detournavigator/recast.cpp b/components/detournavigator/recast.cpp index f3c7768430..c1d14c0aa8 100644 --- a/components/detournavigator/recast.cpp +++ b/components/detournavigator/recast.cpp @@ -46,4 +46,35 @@ namespace DetourNavigator rcFree(value.verts); rcFree(value.tris); } + + void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst) + { + dst.nverts = src.nverts; + dst.npolys = src.npolys; + dst.maxpolys = src.maxpolys; + dst.nvp = src.nvp; + rcVcopy(dst.bmin, src.bmin); + rcVcopy(dst.bmax, src.bmax); + dst.cs = src.cs; + dst.ch = src.ch; + dst.borderSize = src.borderSize; + dst.maxEdgeError = src.maxEdgeError; + permRecastAlloc(dst); + std::memcpy(dst.verts, src.verts, getVertsLength(src) * sizeof(*dst.verts)); + std::memcpy(dst.polys, src.polys, getPolysLength(src) * sizeof(*dst.polys)); + std::memcpy(dst.regs, src.regs, getRegsLength(src) * sizeof(*dst.regs)); + std::memcpy(dst.flags, src.flags, getFlagsLength(src) * sizeof(*dst.flags)); + std::memcpy(dst.areas, src.areas, getAreasLength(src) * sizeof(*dst.areas)); + } + + void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst) + { + dst.nmeshes = src.nmeshes; + dst.nverts = src.nverts; + dst.ntris = src.ntris; + permRecastAlloc(dst); + std::memcpy(dst.meshes, src.meshes, getMeshesLength(src) * sizeof(*dst.meshes)); + std::memcpy(dst.verts, src.verts, getVertsLength(src) * sizeof(*dst.verts)); + std::memcpy(dst.tris, src.tris, getTrisLength(src) * sizeof(*dst.tris)); + } } diff --git a/components/detournavigator/recast.hpp b/components/detournavigator/recast.hpp index 8b9042b661..1811d35772 100644 --- a/components/detournavigator/recast.hpp +++ b/components/detournavigator/recast.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H #include +#include #include #include @@ -62,6 +63,10 @@ namespace DetourNavigator void permRecastAlloc(rcPolyMeshDetail& value); void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept; + + void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst); + + void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst); } #endif diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 68baeb51d8..cc2c685992 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -63,6 +63,7 @@ namespace DetourNavigator result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator"); result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator"); + result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator"); return result; } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index ab8a95c649..e6be8017d5 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -40,6 +40,7 @@ namespace DetourNavigator bool mEnableRecastMeshFileNameRevision = false; bool mEnableNavMeshFileNameRevision = false; bool mEnableNavMeshDiskCache = false; + bool mWriteToNavMeshDb = false; RecastSettings mRecast; DetourSettings mDetour; int mWaitUntilMinDistanceToPlayer = 0; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index b3705f69cc..d97ddd1d6f 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -393,6 +393,8 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "NavMesh Waiting", "NavMesh Pushed", "NavMesh Processing", + "NavMesh DbJobs", + "NavMesh DbCacheHitRate", "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 15059356ff..6d00c770bc 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -65,6 +65,15 @@ If true navmesh cache stored on disk will be used in addition to memory cache. If navmesh tile is not present in memory cache, it will be looked up in the disk cache. If it's not found there it will be generated. +write to navmeshdb +------------------ + +:Type: boolean +:Range: True/False +:Default: False + +If true generated navmesh tiles will be stored into disk cache while game is running. + Advanced settings ***************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1752b4f5a0..57faaba11d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -937,6 +937,9 @@ nav mesh version = 1 # Use navigation mesh cache stored on disk (true, false) enable nav mesh disk cache = true +# Cache navigation mesh tiles to disk (true, false) +write to navmeshdb = false + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. From 24ec06f0d2450b4086825b8a7bb34c48c6caa86d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 11 Dec 2021 01:14:53 +0100 Subject: [PATCH 1848/2859] Add changelog record for navmesh disk cache --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 984b545ef1..9bc340bef0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ Feature #6128: Soft Particles Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly + Feature #6189: Navigation mesh disk cache Feature #6199: Support FBO Rendering Feature #6248: Embedded error marker mesh Feature #6249: Alpha testing support for Collada From a64057fb3660c98a7ca94553bcaa16f7b45f1ed1 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 11 Dec 2021 04:42:47 +0300 Subject: [PATCH 1849/2859] Some NIF cleanup Clean up keyframe controller construction Make LOD and switch node generation static Clarify decal map implementation --- components/nifosg/controller.cpp | 57 +++++++++++++++++--------------- components/nifosg/controller.hpp | 9 +---- components/nifosg/nifloader.cpp | 33 +++++------------- 3 files changed, 40 insertions(+), 59 deletions(-) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 54300d34e8..07134532e9 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -82,34 +82,37 @@ KeyframeController::KeyframeController(const KeyframeController ©, const osg { } -KeyframeController::KeyframeController(const Nif::NiKeyframeData *data) - : mRotations(data->mRotations) - , mXRotations(data->mXRotations, 0.f) - , mYRotations(data->mYRotations, 0.f) - , mZRotations(data->mZRotations, 0.f) - , mTranslations(data->mTranslations, osg::Vec3f()) - , mScales(data->mScales, 1.f) -{ -} - -KeyframeController::KeyframeController(const Nif::NiTransformInterpolator* interpolator) - : mRotations(interpolator->data->mRotations, interpolator->defaultRot) - , mXRotations(interpolator->data->mXRotations, 0.f) - , mYRotations(interpolator->data->mYRotations, 0.f) - , mZRotations(interpolator->data->mZRotations, 0.f) - , mTranslations(interpolator->data->mTranslations, interpolator->defaultPos) - , mScales(interpolator->data->mScales, interpolator->defaultScale) -{ -} - -KeyframeController::KeyframeController(const float scale, const osg::Vec3f& pos, const osg::Quat& rot) - : mRotations(Nif::QuaternionKeyMapPtr(), rot) - , mXRotations(Nif::FloatKeyMapPtr(), 0.f) - , mYRotations(Nif::FloatKeyMapPtr(), 0.f) - , mZRotations(Nif::FloatKeyMapPtr(), 0.f) - , mTranslations(Nif::Vector3KeyMapPtr(), pos) - , mScales(Nif::FloatKeyMapPtr(), scale) +KeyframeController::KeyframeController(const Nif::NiKeyframeController *keyctrl) { + if (!keyctrl->interpolator.empty()) + { + const Nif::NiTransformInterpolator* interp = keyctrl->interpolator.getPtr(); + if (!interp->data.empty()) + { + mRotations = QuaternionInterpolator(interp->data->mRotations, interp->defaultRot); + mXRotations = FloatInterpolator(interp->data->mXRotations); + mYRotations = FloatInterpolator(interp->data->mYRotations); + mZRotations = FloatInterpolator(interp->data->mZRotations); + mTranslations = Vec3Interpolator(interp->data->mTranslations, interp->defaultPos); + mScales = FloatInterpolator(interp->data->mScales, interp->defaultScale); + } + else + { + mRotations = QuaternionInterpolator(Nif::QuaternionKeyMapPtr(), interp->defaultRot); + mTranslations = Vec3Interpolator(Nif::Vector3KeyMapPtr(), interp->defaultPos); + mScales = FloatInterpolator(Nif::FloatKeyMapPtr(), interp->defaultScale); + } + } + else if (!keyctrl->data.empty()) + { + const Nif::NiKeyframeData* keydata = keyctrl->data.getPtr(); + mRotations = QuaternionInterpolator(keydata->mRotations); + mXRotations = FloatInterpolator(keydata->mXRotations); + mYRotations = FloatInterpolator(keydata->mYRotations); + mZRotations = FloatInterpolator(keydata->mZRotations); + mTranslations = Vec3Interpolator(keydata->mTranslations); + mScales = FloatInterpolator(keydata->mScales, 1.f); + } } osg::Quat KeyframeController::getXYZRotation(float time) const diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 5d88dda1f1..a5f887ebe6 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -234,16 +234,9 @@ namespace NifOsg class KeyframeController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: - // This is used if there's no interpolator but there is data (Morrowind meshes). - KeyframeController(const Nif::NiKeyframeData *data); - // This is used if the interpolator has data. - KeyframeController(const Nif::NiTransformInterpolator* interpolator); - // This is used if there are default values available (e.g. from a data-less interpolator). - // If there's neither keyframe data nor an interpolator a KeyframeController must not be created. - KeyframeController(const float scale, const osg::Vec3f& pos, const osg::Quat& rot); - KeyframeController(); KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop); + KeyframeController(const Nif::NiKeyframeController *keyctrl); META_Object(NifOsg, KeyframeController) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 99aaaa3323..3148b93cdc 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -270,8 +270,8 @@ namespace NifOsg if (key->data.empty() && key->interpolator.empty()) continue; - osg::ref_ptr callback(handleKeyframeController(key)); - callback->setFunction(std::shared_ptr(new NifOsg::ControllerFunction(key))); + osg::ref_ptr callback = new NifOsg::KeyframeController(key); + setupController(key, callback, /*animflags*/0); if (!target.mKeyframeControllers.emplace(strdata->string, callback).second) Log(Debug::Verbose) << "Controller " << strdata->string << " present more than once in " << nif->getFilename() << ", ignoring later version"; @@ -359,7 +359,7 @@ namespace NifOsg handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags); } - void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags) + static void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags) { bool autoPlay = animflags & Nif::NiNode::AnimFlag_AutoPlay; if (autoPlay) @@ -368,7 +368,7 @@ namespace NifOsg toSetup->setFunction(std::shared_ptr(new ControllerFunction(ctrl))); } - osg::ref_ptr handleLodNode(const Nif::NiLODNode* niLodNode) + static osg::ref_ptr handleLodNode(const Nif::NiLODNode* niLodNode) { osg::ref_ptr lod (new osg::LOD); lod->setName(niLodNode->name); @@ -383,7 +383,7 @@ namespace NifOsg return lod; } - osg::ref_ptr handleSwitchNode(const Nif::NiSwitchNode* niSwitchNode) + static osg::ref_ptr handleSwitchNode(const Nif::NiSwitchNode* niSwitchNode) { osg::ref_ptr switchNode (new osg::Switch); switchNode->setName(niSwitchNode->name); @@ -718,24 +718,6 @@ namespace NifOsg } } - static osg::ref_ptr handleKeyframeController(const Nif::NiKeyframeController* keyctrl) - { - osg::ref_ptr ctrl; - if (!keyctrl->interpolator.empty()) - { - const Nif::NiTransformInterpolator* interp = keyctrl->interpolator.getPtr(); - if (!interp->data.empty()) - ctrl = new NifOsg::KeyframeController(interp); - else - ctrl = new NifOsg::KeyframeController(interp->defaultScale, interp->defaultPos, interp->defaultRot); - } - else if (!keyctrl->data.empty()) - { - ctrl = new NifOsg::KeyframeController(keyctrl->data.getPtr()); - } - return ctrl; - } - void handleNodeControllers(const Nif::Node* nifNode, osg::Node* node, int animflags, bool& isAnimated) { for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) @@ -747,7 +729,7 @@ namespace NifOsg const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); if (key->data.empty() && key->interpolator.empty()) continue; - osg::ref_ptr callback(handleKeyframeController(key)); + osg::ref_ptr callback = new KeyframeController(key); setupController(key, callback, animflags); node->addUpdateCallback(callback); isAnimated = true; @@ -1615,6 +1597,9 @@ namespace NifOsg } else if (i == Nif::NiTexturingProperty::DecalTexture) { + // This is only an inaccurate imitation of the original implementation, + // see https://github.com/niftools/nifskope/issues/184 + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; // Interpolate to the decal texture's colour... texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); From ff9f010d99f1330401d7bdda2241672f9fda9dda Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 11 Dec 2021 15:30:57 +0100 Subject: [PATCH 1850/2859] Explicitely add the initial actor to the set of its allies --- apps/openmw/mwmechanics/actors.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ce2465ec97..d5d434e128 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -451,6 +451,7 @@ namespace MWMechanics { std::set allySet; getActorsSidingWith(ptr, allySet); + allySet.insert(ptr); std::vector allies(allySet.begin(), allySet.end()); for(const auto& ally : allies) ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(targets); From a71bda2bf9b9601aaf05085420a236a5c74a0534 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 14 Dec 2021 09:18:43 +0000 Subject: [PATCH 1851/2859] Merge branch 'a_bit_faster' into 'master' Speed up the pipeline a teensy bit See merge request OpenMW/openmw!1471 (cherry picked from commit 0dff3042d0af6256aba74dc905dd44c723529845) 2b026cad Speed up the pipeline a teensy bit --- .gitlab-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b977d86a97..1f31278be1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,14 @@ stages: - build +# https://blog.nimbleways.com/let-s-make-faster-gitlab-ci-cd-pipelines/ +variables: + FF_USE_NEW_SHELL_ESCAPE: "true" + FF_USE_FASTZIP: "true" + # These can be specified per job or per pipeline + ARTIFACT_COMPRESSION_LEVEL: "fast" + CACHE_COMPRESSION_LEVEL: "fast" + .Ubuntu_Image: tags: - docker From 029eb1ade6338bb55589b2e2aeb95a2b7141eb69 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 14 Dec 2021 09:21:41 +0000 Subject: [PATCH 1852/2859] added !1471 --- .resubmitted_merge_requests.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.resubmitted_merge_requests.txt b/.resubmitted_merge_requests.txt index 5fd67c190c..1585a60ec1 100644 --- a/.resubmitted_merge_requests.txt +++ b/.resubmitted_merge_requests.txt @@ -1,3 +1,4 @@ +1471 1450 1420 1314 From 2fce89e64fdc170ab086283240862c289109a61d Mon Sep 17 00:00:00 2001 From: kuyondo Date: Tue, 14 Dec 2021 22:40:05 +0800 Subject: [PATCH 1853/2859] changes --- apps/openmw/mwgui/messagebox.cpp | 5 ----- apps/openmw/mwgui/messagebox.hpp | 4 ++-- apps/openmw/mwinput/actionmanager.cpp | 7 +++---- apps/openmw/mwinput/actionmanager.hpp | 2 -- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index ef9ad73437..890406cd12 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -206,11 +206,6 @@ namespace MWGui mMainWidget->setPosition(pos); } - const std::string MessageBox::getMessage() - { - return mMessage; - } - int MessageBox::getHeight () { return mMainWidget->getHeight()+mNextBoxPadding; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index e01c54b9a5..d46d31d938 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -65,7 +65,7 @@ namespace MWGui public: MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message); void setMessage (const std::string& message); - const std::string getMessage(); + const std::string& getMessage() { return mMessage; }; int getHeight (); void update (int height); void setVisible(bool value); @@ -75,7 +75,7 @@ namespace MWGui protected: MessageBoxManager& mMessageBoxManager; - const std::string mMessage; + std::string mMessage; MyGUI::EditBox* mMessageWidget; int mBottomPadding; int mNextBoxPadding; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index 84bdc7d12b..63b0a197a7 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -42,7 +42,6 @@ namespace MWInput , mSneaking(false) , mAttemptJump(false) , mTimeIdle(0.f) - , mOverencumberedMessage("#{sNotifyMessage59}") { } @@ -100,16 +99,16 @@ namespace MWInput { player.setAutoMove (false); std::vector msgboxs = MWBase::Environment::get().getWindowManager()->getActiveMessageBoxes(); - const std::vector::iterator it = std::find_if(msgboxs.begin(), msgboxs.end(), [=](MWGui::MessageBox*& msgbox) + const std::vector::iterator it = std::find_if(msgboxs.begin(), msgboxs.end(), [](MWGui::MessageBox*& msgbox) { - return (msgbox->getMessage() == mOverencumberedMessage); + return (msgbox->getMessage() == "#{sNotifyMessage59}"); }); // if an overencumbered messagebox is already present, reset its expiry timer, otherwise create new one. if (it != msgboxs.end()) (*it)->mCurrentTime = 0; else - MWBase::Environment::get().getWindowManager()->messageBox(mOverencumberedMessage); + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); } } diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index b3deaed20e..4c51139d46 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -67,8 +67,6 @@ namespace MWInput bool mSneaking; bool mAttemptJump; - const std::string mOverencumberedMessage; - float mTimeIdle; }; } From c865114b9b74fde4a38ed47ef4cb0b0b1e0f589d Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 14 Dec 2021 17:38:06 +0000 Subject: [PATCH 1854/2859] Lua UI Layers --- apps/openmw/mwlua/uibindings.cpp | 64 ++++++++++++++++++- components/CMakeLists.txt | 2 +- components/lua/luastate.hpp | 11 ++++ components/lua_ui/element.cpp | 22 ++++++- components/lua_ui/layers.hpp | 55 ++++++++++++++++ .../lua-scripting/user_interface.rst | 17 ++++- files/lua_api/openmw/ui.lua | 25 ++++++++ 7 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 components/lua_ui/layers.hpp diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 9982b857c8..987304d4c3 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -1,6 +1,7 @@ -#include #include #include +#include +#include #include "context.hpp" #include "actions.hpp" @@ -81,6 +82,41 @@ namespace MWLua inline size_t toLuaIndex(size_t i) { return i + 1; } } + class LayerAction final : public Action + { + public: + LayerAction(std::string_view name, std::string_view afterName, + LuaUi::Layers::Options options, LuaUtil::LuaState* state) + : Action(state) + , mName(name) + , mAfterName(afterName) + , mOptions(options) + {} + + void apply(WorldView&) const override + { + size_t index = LuaUi::Layers::indexOf(mAfterName); + if (index == LuaUi::Layers::size()) + throw std::logic_error(std::string("Layer not found")); + LuaUi::Layers::insert(index, mName, mOptions); + } + + std::string toString() const override + { + std::string result("Insert UI layer \""); + result += mName; + result += "\" after \""; + result += mAfterName; + result += "\""; + return result; + } + + private: + std::string mName; + std::string mAfterName; + LuaUi::Layers::Options mOptions; + }; + sol::table initUserInterfacePackage(const Context& context) { auto uiContent = context.mLua->sol().new_usertype("UiContent"); @@ -175,6 +211,32 @@ namespace MWLua return element; }; + sol::table layers = context.mLua->newTable(); + layers[sol::meta_function::length] = []() + { + return LuaUi::Layers::size(); + }; + layers[sol::meta_function::index] = [](size_t index) + { + index = fromLuaIndex(index); + return LuaUi::Layers::at(index); + }; + layers["indexOf"] = [](std::string_view name) -> sol::optional + { + size_t index = LuaUi::Layers::indexOf(name); + if (index == LuaUi::Layers::size()) + return sol::nullopt; + else + return toLuaIndex(index); + }; + layers["insertAfter"] = [context](std::string_view afterName, std::string_view name, const sol::object& opt) + { + LuaUi::Layers::Options options; + options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); + context.mLuaManager->addAction(std::make_unique(name, afterName, options, context.mLua)); + }; + api["layers"] = LuaUtil::makeReadOnly(layers); + sol::table typeTable = context.mLua->newTable(); for (const auto& it : LuaUi::widgetTypeToName()) typeTable.set(it.second, it.first); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f969ac1265..3b38489d7a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -162,7 +162,7 @@ add_component_dir (queries ) add_component_dir (lua_ui - widget widgetlist element content + widget widgetlist element layers content text textedit window ) diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index 71cdb4f75d..32c180c987 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -119,6 +119,17 @@ namespace LuaUtil // String representation of a Lua object. Should be used for debugging/logging purposes only. std::string toString(const sol::object&); + template + T getValueOrDefault(const sol::object& obj, const T& defaultValue) + { + if (obj == sol::nil) + return defaultValue; + if (obj.is()) + return obj.as(); + else + throw std::logic_error(std::string("Value \"") + toString(obj) + std::string("\" has unexpected type")); + } + // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. // Needed to forbid any changes in common resources that can be accessed from different sandboxes. sol::table makeReadOnly(sol::table); diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 63ae0e7d5e..5147068a7e 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -55,7 +55,6 @@ namespace LuaUi { std::string type = widgetType(layout); std::string skin = layout.get_or("skin", std::string()); - std::string layer = layout.get_or("layer", std::string("Windows")); std::string name = layout.get_or("name", std::string()); static auto widgetTypeMap = widgetTypeToName(); @@ -65,7 +64,7 @@ namespace LuaUi MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( type, skin, MyGUI::IntCoord(), MyGUI::Align::Default, - layer, name); + std::string(), name); LuaUi::WidgetExtension* ext = dynamic_cast(widget); if (!ext) @@ -124,17 +123,36 @@ namespace LuaUi ext->addChild(createWidget(newContent.at(i), ext)); } + void setLayer(const sol::table& layout, LuaUi::WidgetExtension* ext) + { + MyGUI::ILayer* layerNode = ext->widget()->getLayer(); + std::string currentLayer = layerNode ? layerNode->getName() : std::string(); + std::string newLayer = layout.get_or("layer", std::string()); + if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer)) + throw std::logic_error(std::string("Layer ") += newLayer += " doesn't exist"); + else if (newLayer != currentLayer) + { + MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget()); + } + } + void Element::create() { assert(!mRoot); if (!mRoot) + { mRoot = createWidget(mLayout, nullptr); + setLayer(mLayout, mRoot); + } } void Element::update() { if (mRoot && mUpdate) + { updateWidget(mLayout, mRoot); + setLayer(mLayout, mRoot); + } mUpdate = false; } diff --git a/components/lua_ui/layers.hpp b/components/lua_ui/layers.hpp new file mode 100644 index 0000000000..6fe7fc9c16 --- /dev/null +++ b/components/lua_ui/layers.hpp @@ -0,0 +1,55 @@ +#ifndef OPENMW_LUAUI_LAYERS +#define OPENMW_LUAUI_LAYERS + +#include +#include + +#include +#include + +namespace LuaUi +{ + namespace Layers + { + struct Options { + bool mInteractive; + }; + + size_t size() + { + return MyGUI::LayerManager::getInstance().getLayerCount(); + } + + std::string at(size_t index) + { + if (index >= size()) + throw std::logic_error("Invalid layer index"); + return MyGUI::LayerManager::getInstance().getLayer(index)->getName(); + } + + size_t indexOf(std::string_view name) + { + for (size_t i = 0; i < size(); i++) + if (at(i) == name) + return i; + return size(); + } + + void insert(size_t index, std::string_view name, Options options) + { + if (index > size()) + throw std::logic_error("Invalid layer index"); + if (indexOf(name) < size()) + Log(Debug::Error) << "Layer \"" << name << "\" already exists"; + else + { + auto layer = MyGUI::LayerManager::getInstance() + .createLayerAt(std::string(name), "OverlappedLayer", index); + auto overlappedLayer = dynamic_cast(layer); + overlappedLayer->setPick(options.mInteractive); + } + } + } +} + +#endif // OPENMW_LUAUI_LAYERS diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 88233dceb9..73ca57d1a7 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -22,9 +22,22 @@ Every widget is defined by a layout, which is a Lua table with the following fie 4. `content`: a Content (`openmw.ui.content`), which contains layouts for the children of this widget. 5. | `name`: an arbitrary string, the only limitatiion is it being unique within a `Content`. | Helpful for navigatilng through the layouts. -6. `layer`: only applies for the root widget. (Windows, HUD, etc) +6. `layer`: only applies for the root widget. -.. TODO: Write a more detailed documentation for layers when they are finished +Layers +------ +Layers control how widgets overlap - layers with higher indexes cover render over layers with lower indexes. +Widgets within the same layer which were added later overlap the ones created earlier. +A layer can also be set as non-interactive, which prevents all mouse interactions with the widgets in that layer. + +.. TODO: Move this list when layers are de-hardcoded + +Pre-defined OpenMW layers: + +1. `HUD` interactive +2. `Windows` interactive +3. `Notification` non-interactive +4. `MessageBox` interactive Elements -------- diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 593cbf8043..c60a4e1abd 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -8,6 +8,10 @@ --- -- @field [parent=#ui] #WIDGET_TYPE WIDGET_TYPE +--- +-- Tools for working with layers +-- @field [parent=#ui] #Layers layers + --- -- @type WIDGET_TYPE -- @field [parent=#WIDGET_TYPE] Widget Base widget type @@ -40,6 +44,27 @@ -- @field #table events Optional table of event callbacks -- @field #Content content Optional @{openmw.ui#Content} of children layouts +--- +-- Layers +-- @type Layers +-- @usage +-- ui.layers.insertAfter('HUD', 'NewLayer', { interactive = true }) +-- local fourthLayerName = ui.layers[4] +-- local windowsIndex = ui.layers.indexOf('Windows') + +--- +-- Index of the layer with the givent name. Returns nil if the layer doesn't exist +-- @function [parent=#Layers] indexOf +-- @param #string name Name of the layer +-- @return #number, #nil index + +--- +-- Creates a layer and inserts it after another layer (shifts indexes of some other layers). +-- @function [parent=#Layers] insertAfter +-- @param #string afterName Name of the layer after which the new layer will be inserted +-- @param #string name Name of the new layer +-- @param #table options Table with a boolean `interactive` field (default is true). Layers with interactive = false will ignore all mouse interactions. + --- -- Content. An array-like container, which allows to reference elements by their name -- @type Content From a204b4da9648d41264f4526dab0f09f13347f7aa Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 14 Dec 2021 21:06:40 +0100 Subject: [PATCH 1855/2859] Change the legalities of opening unlocked objects --- CHANGELOG.md | 1 + .../mwmechanics/mechanicsmanagerimp.cpp | 70 +++++++++++-------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc340bef0..b61cf944b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened Bug #6451: Weapon summoned from Cast When Used item will have the name "None" Bug #6473: Strings from NIF should be parsed only to first null terminator + Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9974cb44cd..fa5aaa82ef 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -66,6 +66,35 @@ namespace } } + bool isOwned(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) + { + const MWWorld::CellRef& cellref = target.getCellRef(); + + const std::string& owner = cellref.getOwner(); + bool isOwned = !owner.empty() && owner != "player"; + + const std::string& faction = cellref.getFaction(); + bool isFactionOwned = false; + if (!faction.empty() && ptr.getClass().isNpc()) + { + const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); + auto found = factions.find(Misc::StringUtils::lowerCase(faction)); + if (found == factions.end() || found->second < cellref.getFactionRank()) + isFactionOwned = true; + } + + const std::string& globalVariable = cellref.getGlobalVariable(); + if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(globalVariable)) + { + isOwned = false; + isFactionOwned = false; + } + + if (!cellref.getOwner().empty()) + victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true, false); + + return isOwned || isFactionOwned; + } } namespace MWMechanics @@ -881,35 +910,11 @@ namespace MWMechanics return true; } - const std::string& owner = cellref.getOwner(); - bool isOwned = !owner.empty() && owner != "player"; - - const std::string& faction = cellref.getFaction(); - bool isFactionOwned = false; - if (!faction.empty() && ptr.getClass().isNpc()) - { - const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); - std::map::const_iterator found = factions.find(Misc::StringUtils::lowerCase(faction)); - if (found == factions.end() - || found->second < cellref.getFactionRank()) - isFactionOwned = true; - } - - const std::string& globalVariable = cellref.getGlobalVariable(); - if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(Misc::StringUtils::lowerCase(globalVariable)) == 1) - { - isOwned = false; - isFactionOwned = false; - } - - if (!cellref.getOwner().empty()) - victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true, false); - - // A special case for evidence chest - we should not allow to take items even if it is technically permitted - if (Misc::StringUtils::ciEqual(cellref.getRefId(), "stolen_goods")) + if (isOwned(ptr, target, victim)) return false; - return (!isOwned && !isFactionOwned); + // A special case for evidence chest - we should not allow to take items even if it is technically permitted + return !Misc::StringUtils::ciEqual(cellref.getRefId(), "stolen_goods"); } bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed) @@ -941,9 +946,14 @@ namespace MWMechanics void MechanicsManager::unlockAttempted(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item) { MWWorld::Ptr victim; - if (isAllowedToUse(ptr, item, victim)) - return; - commitCrime(ptr, victim, OT_Trespassing, item.getCellRef().getFaction()); + if (isOwned(ptr, item, victim)) + { + // Note that attempting to unlock something that has ever been locked (even ESM::UnbreakableLock) is a crime even if it's already unlocked. + // Likewise, it's illegal to unlock something that has a trap but isn't otherwise locked. + const auto& cellref = item.getCellRef(); + if(cellref.getLockLevel() || !cellref.getTrap().empty()) + commitCrime(ptr, victim, OT_Trespassing, item.getCellRef().getFaction()); + } } std::vector > MechanicsManager::getStolenItemOwners(const std::string& itemid) From b834527813eb2d4bede3197f19e20b75c42ad5d8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 14 Dec 2021 21:36:11 +0100 Subject: [PATCH 1856/2859] Add missing include --- components/vfs/filesystemarchive.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 35f44f46cd..7766a74f49 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -1,5 +1,7 @@ #include "filesystemarchive.hpp" +#include + #include #include From 7549496162dd665b6fde48262dcf968c8afccda6 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 14 Dec 2021 00:36:26 +0100 Subject: [PATCH 1857/2859] A few small fixes + expose makeReadOnly to Lua + an option to apply makeReadOnly during deserialize --- apps/openmw/mwlua/cellbindings.cpp | 8 +++++ apps/openmw/mwlua/inputbindings.cpp | 2 +- apps/openmw/mwlua/userdataserializer.cpp | 2 +- .../lua/test_serialization.cpp | 27 ++++++++++++----- .../lua/test_utilpackage.cpp | 3 ++ components/lua/luastate.cpp | 3 +- components/lua/serialization.cpp | 30 +++++++++++-------- components/lua/serialization.hpp | 6 ++-- components/lua/utilpackage.cpp | 1 + files/lua_api/openmw/util.lua | 6 ++++ 10 files changed, 61 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index a23fb47c32..4ca9018cad 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -4,6 +4,14 @@ #include "../mwworld/cellstore.hpp" +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; +} + namespace MWLua { diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 8aecd1269c..9ca2d94770 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -54,7 +54,7 @@ namespace MWLua { return input->isControllerButtonPressed(static_cast(button)); }; - api["isMouseButtonPressed"] = [input](int button) -> bool + api["isMouseButtonPressed"] = [](int button) -> bool { return SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(button); }; diff --git a/apps/openmw/mwlua/userdataserializer.cpp b/apps/openmw/mwlua/userdataserializer.cpp index 6946cd5532..b675774c53 100644 --- a/apps/openmw/mwlua/userdataserializer.cpp +++ b/apps/openmw/mwlua/userdataserializer.cpp @@ -33,7 +33,7 @@ namespace MWLua // Deserializes userdata of type "typeName" from binaryData. Should push the result on stack using sol::stack::push. // Returns false if this type is not supported by this serializer. - bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state& lua) const override + bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override { if (typeName == "o") { diff --git a/apps/openmw_test_suite/lua/test_serialization.cpp b/apps/openmw_test_suite/lua/test_serialization.cpp index 1983daa158..9300e4571b 100644 --- a/apps/openmw_test_suite/lua/test_serialization.cpp +++ b/apps/openmw_test_suite/lua/test_serialization.cpp @@ -121,14 +121,25 @@ namespace std::string serialized = LuaUtil::serialize(table); EXPECT_EQ(serialized.size(), 123); sol::table res_table = LuaUtil::deserialize(lua, serialized); + sol::table res_readonly_table = LuaUtil::deserialize(lua, serialized, nullptr, true); - EXPECT_EQ(res_table.get("aa"), 1); - EXPECT_EQ(res_table.get("ab"), true); - EXPECT_EQ(res_table.get("nested").get("aa"), 2); - EXPECT_EQ(res_table.get("nested").get("bb"), "something"); - EXPECT_FLOAT_EQ(res_table.get("nested").get(5), -0.5); - EXPECT_EQ(res_table.get(1), osg::Vec2f(1, 2)); - EXPECT_EQ(res_table.get(2), osg::Vec2f(2, 1)); + for (auto t : {res_table, res_readonly_table}) + { + EXPECT_EQ(t.get("aa"), 1); + EXPECT_EQ(t.get("ab"), true); + EXPECT_EQ(t.get("nested").get("aa"), 2); + EXPECT_EQ(t.get("nested").get("bb"), "something"); + EXPECT_FLOAT_EQ(t.get("nested").get(5), -0.5); + EXPECT_EQ(t.get(1), osg::Vec2f(1, 2)); + EXPECT_EQ(t.get(2), osg::Vec2f(2, 1)); + } + + lua["t"] = res_table; + lua["ro_t"] = res_readonly_table; + EXPECT_NO_THROW(lua.safe_script("t.x = 5")); + EXPECT_NO_THROW(lua.safe_script("t.nested.x = 5")); + EXPECT_ERROR(lua.safe_script("ro_t.x = 5"), "userdata value"); + EXPECT_ERROR(lua.safe_script("ro_t.nested.x = 5"), "userdata value"); } struct TestStruct1 { double a, b; }; @@ -157,7 +168,7 @@ namespace return false; } - bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state& lua) const override + bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override { if (typeName == "ts1") { diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index 21d2a344d4..14b7021532 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -120,6 +120,9 @@ namespace EXPECT_FLOAT_EQ(get(lua, "util.clamp(0.1, 0, 1.5)"), 0.1); EXPECT_FLOAT_EQ(get(lua, "util.clamp(-0.1, 0, 1.5)"), 0); EXPECT_FLOAT_EQ(get(lua, "util.clamp(2.1, 0, 1.5)"), 1.5); + lua.safe_script("t = util.makeReadOnly({x = 1})"); + EXPECT_FLOAT_EQ(get(lua, "t.x"), 1); + EXPECT_ERROR(lua.safe_script("t.y = 2"), "userdata value"); } } diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index e78f7bed06..61637d7b07 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -76,9 +76,8 @@ namespace LuaUtil lua_State* lua = table.lua_state(); table[sol::meta_function::index] = table; - sol::stack::push(lua, std::move(table)); lua_newuserdata(lua, 0); - lua_pushvalue(lua, -2); + sol::stack::push(lua, std::move(table)); lua_setmetatable(lua, -2); return sol::stack::pop(lua); } diff --git a/components/lua/serialization.cpp b/components/lua/serialization.cpp index 2e13cfe29f..f9b8951e4e 100644 --- a/components/lua/serialization.cpp +++ b/components/lua/serialization.cpp @@ -5,6 +5,8 @@ #include +#include "luastate.hpp" + namespace LuaUtil { @@ -147,7 +149,8 @@ namespace LuaUtil throw std::runtime_error("Unknown Lua type."); } - static void deserializeImpl(sol::state& lua, std::string_view& binaryData, const UserdataSerializer* customSerializer) + static void deserializeImpl(lua_State* lua, std::string_view& binaryData, + const UserdataSerializer* customSerializer, bool readOnly) { if (binaryData.empty()) throw std::runtime_error("Unexpected end of serialized data."); @@ -176,22 +179,22 @@ namespace LuaUtil if (type & SHORT_STRING_FLAG) { size_t size = type & 0x1f; - sol::stack::push(lua.lua_state(), binaryData.substr(0, size)); + sol::stack::push(lua, binaryData.substr(0, size)); binaryData = binaryData.substr(size); return; } switch (static_cast(type)) { case SerializedType::NUMBER: - sol::stack::push(lua.lua_state(), getValue(binaryData)); + sol::stack::push(lua, getValue(binaryData)); return; case SerializedType::BOOLEAN: - sol::stack::push(lua.lua_state(), getValue(binaryData) != 0); + sol::stack::push(lua, getValue(binaryData) != 0); return; case SerializedType::LONG_STRING: { uint32_t size = getValue(binaryData); - sol::stack::push(lua.lua_state(), binaryData.substr(0, size)); + sol::stack::push(lua, binaryData.substr(0, size)); binaryData = binaryData.substr(size); return; } @@ -200,13 +203,15 @@ namespace LuaUtil lua_createtable(lua, 0, 0); while (!binaryData.empty() && binaryData[0] != char(SerializedType::TABLE_END)) { - deserializeImpl(lua, binaryData, customSerializer); - deserializeImpl(lua, binaryData, customSerializer); + deserializeImpl(lua, binaryData, customSerializer, readOnly); + deserializeImpl(lua, binaryData, customSerializer, readOnly); lua_settable(lua, -3); } if (binaryData.empty()) throw std::runtime_error("Unexpected end of serialized data."); binaryData = binaryData.substr(1); + if (readOnly) + sol::stack::push(lua, makeReadOnly(sol::stack::pop(lua))); return; } case SerializedType::TABLE_END: @@ -215,7 +220,7 @@ namespace LuaUtil { float x = getValue(binaryData); float y = getValue(binaryData); - sol::stack::push(lua.lua_state(), osg::Vec2f(x, y)); + sol::stack::push(lua, osg::Vec2f(x, y)); return; } case SerializedType::VEC3: @@ -223,7 +228,7 @@ namespace LuaUtil float x = getValue(binaryData); float y = getValue(binaryData); float z = getValue(binaryData); - sol::stack::push(lua.lua_state(), osg::Vec3f(x, y, z)); + sol::stack::push(lua, osg::Vec3f(x, y, z)); return; } } @@ -240,7 +245,8 @@ namespace LuaUtil return res; } - sol::object deserialize(sol::state& lua, std::string_view binaryData, const UserdataSerializer* customSerializer) + sol::object deserialize(lua_State* lua, std::string_view binaryData, + const UserdataSerializer* customSerializer, bool readOnly) { if (binaryData.empty()) return sol::nil; @@ -248,10 +254,10 @@ namespace LuaUtil throw std::runtime_error("Incorrect version of Lua serialization format: " + std::to_string(static_cast(binaryData[0]))); binaryData = binaryData.substr(1); - deserializeImpl(lua, binaryData, customSerializer); + deserializeImpl(lua, binaryData, customSerializer, readOnly); if (!binaryData.empty()) throw std::runtime_error("Unexpected data after serialized object"); - return sol::stack::pop(lua.lua_state()); + return sol::stack::pop(lua); } } diff --git a/components/lua/serialization.hpp b/components/lua/serialization.hpp index fddae2cfb4..d685bb2ad6 100644 --- a/components/lua/serialization.hpp +++ b/components/lua/serialization.hpp @@ -1,7 +1,6 @@ #ifndef COMPONENTS_LUA_SERIALIZATION_H #define COMPONENTS_LUA_SERIALIZATION_H -#include // missing from sol/sol.hpp #include namespace LuaUtil @@ -21,14 +20,15 @@ namespace LuaUtil // Deserializes userdata of type "typeName" from binaryData. Should push the result on stack using sol::stack::push. // Returns false if this type is not supported by this serializer. - virtual bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state&) const = 0; + virtual bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State*) const = 0; protected: static void append(BinaryData&, std::string_view typeName, const void* data, size_t dataSize); }; BinaryData serialize(const sol::object&, const UserdataSerializer* customSerializer = nullptr); - sol::object deserialize(sol::state& lua, std::string_view binaryData, const UserdataSerializer* customSerializer = nullptr); + sol::object deserialize(lua_State* lua, std::string_view binaryData, + const UserdataSerializer* customSerializer = nullptr, bool readOnly = false); } diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index b68fc7afa4..abb680a6bc 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -175,6 +175,7 @@ namespace LuaUtil util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); }; // NOTE: `util["clamp"] = std::clamp` causes error 'AddressSanitizer: stack-use-after-scope' util["normalizeAngle"] = &Misc::normalizeAngle; + util["makeReadOnly"] = &makeReadOnly; return util; } diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index fdb140ad53..22986f09a6 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -19,6 +19,12 @@ -- @param #number angle Angle in radians -- @return #number Angle in range `[-pi, pi]` +------------------------------------------------------------------------------- +-- Makes a table read only. +-- @function [parent=#util] makeReadOnly +-- @param #table table Any table. +-- @return #table The same table wrapped with read only userdata. + ------------------------------------------------------------------------------- -- Immutable 2D vector From db72380ba926655354fc89f6e9cbe643d1c7babf Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 14 Dec 2021 00:51:18 +0100 Subject: [PATCH 1858/2859] Allow Lua scripts to handle input actions when UI is opened. Also fixes #6456. --- apps/openmw/engine.cpp | 12 ++++------- apps/openmw/mwlua/luabindings.cpp | 3 ++- apps/openmw/mwlua/luamanagerimp.cpp | 22 +++++++++++--------- apps/openmw/mwlua/luamanagerimp.hpp | 4 ++-- apps/openmw/mwlua/worldview.cpp | 3 +++ apps/openmw/mwlua/worldview.hpp | 4 ++++ files/builtin_scripts/scripts/omw/camera.lua | 2 ++ files/lua_api/openmw/core.lua | 7 ++++++- 8 files changed, 35 insertions(+), 22 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a44a02ef18..5c9e915703 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -296,7 +296,7 @@ bool OMW::Engine::frame(float frametime) // Should be called after input manager update and before any change to the game world. // It applies to the game world queued changes from the previous frame. - mLuaManager->synchronizedUpdate(mEnvironment.getWindowManager()->isGuiMode(), frametime); + mLuaManager->synchronizedUpdate(); // update game state { @@ -873,10 +873,8 @@ public: mThread = std::thread([this]{ threadBody(); }); }; - void allowUpdate(double dt) + void allowUpdate() { - mDt = dt; - mIsGuiMode = mEngine->mEnvironment.getWindowManager()->isGuiMode(); if (!mThread) return; { @@ -918,7 +916,7 @@ private: const unsigned int frameNumber = viewer->getFrameStamp()->getFrameNumber(); ScopedProfile profile(frameStart, frameNumber, *osg::Timer::instance(), *viewer->getViewerStats()); - mEngine->mLuaManager->update(mIsGuiMode, mDt); + mEngine->mLuaManager->update(); } void threadBody() @@ -943,8 +941,6 @@ private: std::condition_variable mCV; bool mUpdateRequest = false; bool mJoinRequest = false; - double mDt = 0; - bool mIsGuiMode = false; std::optional mThread; }; @@ -1057,7 +1053,7 @@ void OMW::Engine::go() mEnvironment.getWorld()->updateWindowManager(); - luaWorker.allowUpdate(dt); // if there is a separate Lua thread, it starts the update now + luaWorker.allowUpdate(); // if there is a separate Lua thread, it starts the update now mViewer->renderingTraversals(); diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 80af77139c..57b1b17a3b 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 10; + api["API_REVISION"] = 11; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); @@ -37,6 +37,7 @@ namespace MWLua }; api["getGameTimeInSeconds"] = [world=context.mWorldView]() { return world->getGameTimeInSeconds(); }; api["getGameTimeInHours"] = [world=context.mWorldView]() { return world->getGameTimeInHours(); }; + api["isWorldPaused"] = [world=context.mWorldView]() { return world->isPaused(); }; api["OBJECT_TYPE"] = definitionList(*lua, { "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 2f3df00f7f..f134ef86dd 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -78,11 +78,12 @@ namespace MWLua mInitialized = true; } - void LuaManager::update(bool paused, float dt) + void LuaManager::update() { if (mPlayer.isEmpty()) return; // The game is not started yet. + float frameDuration = MWBase::Environment::get().getFrameDuration(); ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -101,9 +102,9 @@ namespace MWLua mGlobalEvents = std::vector(); mLocalEvents = std::vector(); - if (!paused) + if (!mWorldView.isPaused()) { // Update time and process timers - double seconds = mWorldView.getGameTimeInSeconds() + dt; + double seconds = mWorldView.getGameTimeInSeconds() + frameDuration; mWorldView.setGameTimeInSeconds(seconds); double hours = mWorldView.getGameTimeInHours(); @@ -146,10 +147,10 @@ namespace MWLua } mLocalEngineEvents.clear(); - if (!paused) + if (!mWorldView.isPaused()) { for (LocalScripts* scripts : mActiveLocalScripts) - scripts->update(dt); + scripts->update(frameDuration); } // Engine handlers in global scripts @@ -168,24 +169,25 @@ namespace MWLua mGlobalScripts.actorActive(GObject(id, objectRegistry)); mActorAddedEvents.clear(); - if (!paused) - mGlobalScripts.update(dt); + if (!mWorldView.isPaused()) + mGlobalScripts.update(frameDuration); } - void LuaManager::synchronizedUpdate(bool paused, float dt) + void LuaManager::synchronizedUpdate() { if (mPlayer.isEmpty()) return; // The game is not started yet. // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); - if (playerScripts && !paused) + if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) { for (const auto& event : mInputEvents) playerScripts->processInputEvent(event); - playerScripts->inputUpdate(dt); } mInputEvents.clear(); + if (playerScripts && !mWorldView.isPaused()) + playerScripts->inputUpdate(MWBase::Environment::get().getFrameDuration()); MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); for (const std::string& message : mUIMessages) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 56d20c2720..88273de7f0 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -29,10 +29,10 @@ namespace MWLua // Called by engine.cpp every frame. For performance reasons it works in a separate // thread (in parallel with osg Cull). Can not use scene graph. - void update(bool paused, float dt); + void update(); // Called by engine.cpp from the main thread. Can use scene graph. - void synchronizedUpdate(bool paused, float dt); + void synchronizedUpdate(); // Available everywhere through the MWBase::LuaManager interface. // LuaManager queues these events and propagates to scripts on the next `update` call. diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index beaa2d4770..219035745a 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -4,6 +4,8 @@ #include #include +#include "../mwbase/windowmanager.hpp" + #include "../mwclass/container.hpp" #include "../mwworld/class.hpp" @@ -20,6 +22,7 @@ namespace MWLua mContainersInScene.updateList(); mDoorsInScene.updateList(); mItemsInScene.updateList(); + mPaused = MWBase::Environment::get().getWindowManager()->isGuiMode(); } void WorldView::clear() diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index ea27e0ff84..a8befd4685 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -19,6 +19,9 @@ namespace MWLua void update(); // Should be called every frame. void clear(); // Should be called every time before starting or loading a new game. + // Whether the world is paused (i.e. game time is not changing and actors don't move). + bool isPaused() const { return mPaused; } + // Returns the number of seconds passed from the beginning of the game. double getGameTimeInSeconds() const { return mGameSeconds; } void setGameTimeInSeconds(double t) { mGameSeconds = t; } @@ -74,6 +77,7 @@ namespace MWLua ObjectGroup mItemsInScene; double mGameSeconds = 0; + bool mPaused = false; }; } diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 1a5d1ca730..4025f7eee4 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -1,4 +1,5 @@ local camera = require('openmw.camera') +local core = require('openmw.core') local input = require('openmw.input') local settings = require('openmw.settings') local util = require('openmw.util') @@ -225,6 +226,7 @@ return { onUpdate = onUpdate, onInputUpdate = onInputUpdate, onInputAction = function(action) + if core.isWorldPaused() then return end if action == input.ACTION.ZoomIn then zoom(10) elseif action == input.ACTION.ZoomOut then diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 35513bbb79..fe309a92db 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -22,7 +22,7 @@ ------------------------------------------------------------------------------- -- Game time in seconds. --- The number of seconds in the game world, passed from starting a new game. +-- The number of seconds passed in the game world since starting a new game. -- @function [parent=#core] getGameTimeInSeconds -- @return #number @@ -32,6 +32,11 @@ -- @function [parent=#core] getGameTimeInHours -- @return #number +------------------------------------------------------------------------------- +-- Whether the world is paused (onUpdate doesn't work when the world is paused). +-- @function [parent=#core] isWorldPaused +-- @return #boolean + ------------------------------------------------------------------------------- -- @type OBJECT_TYPE From 7d3c5f529a339030cafa1d5e0894ad8986194ee2 Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 15 Dec 2021 13:19:00 +0000 Subject: [PATCH 1859/2859] Update .gitlab-ci.yml file to allow failure of static deps build --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1f31278be1..34a40756fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -130,6 +130,7 @@ Ubuntu_GCC_tests_Debug: Ubuntu_GCC_Static_Deps: extends: Ubuntu_GCC + allow_failure: true cache: key: Ubuntu_GCC_Static_Deps paths: From e967e0544fc885c6458ad3b90f298037ae09c877 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 15 Dec 2021 21:13:22 +0100 Subject: [PATCH 1860/2859] Upgrade to SDL 2.0.18 --- CI/before_script.msvc.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 1341289335..32169fc04b 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -566,9 +566,9 @@ if [ -z $SKIP_DOWNLOAD ]; then fi # SDL2 - download "SDL 2.0.12" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.12.zip" \ - "SDL2-2.0.12.zip" + download "SDL 2.0.18" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.18.zip" \ + "SDL2-2.0.18.zip" # LZ4 download "LZ4 1.9.2" \ @@ -898,17 +898,17 @@ fi cd $DEPS echo # SDL2 -printf "SDL 2.0.12... " +printf "SDL 2.0.18... " { - if [ -d SDL2-2.0.12 ]; then + if [ -d SDL2-2.0.18 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf SDL2-2.0.12 - eval 7z x -y SDL2-2.0.12.zip $STRIP + rm -rf SDL2-2.0.18 + eval 7z x -y SDL2-2.0.18.zip $STRIP fi - export SDL2DIR="$(real_pwd)/SDL2-2.0.12" + export SDL2DIR="$(real_pwd)/SDL2-2.0.18" for config in ${CONFIGURATIONS[@]}; do - add_runtime_dlls $config "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll" + add_runtime_dlls $config "$(pwd)/SDL2-2.0.18/lib/x${ARCHSUFFIX}/SDL2.dll" done echo Done. } From d66907ba6777a858aea95d73c470a3e0d820a16d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 15 Dec 2021 22:17:38 +0000 Subject: [PATCH 1861/2859] Log OpenGL Vendor, Renderer and Version on startup --- apps/openmw/engine.cpp | 21 ++++++++++++++++++++- components/sceneutil/util.cpp | 16 ++++++++++++++++ components/sceneutil/util.hpp | 12 ++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5c9e915703..1a73ae3531 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -43,6 +43,7 @@ #include #include +#include #include "mwinput/inputmanagerimp.hpp" @@ -239,6 +240,20 @@ namespace { void operator()(std::string) const {} }; + + class IdentifyOpenGLOperation : public osg::GraphicsOperation + { + public: + IdentifyOpenGLOperation() : GraphicsOperation("IdentifyOpenGLOperation", false) + {} + + void operator()(osg::GraphicsContext* graphicsContext) override + { + Log(Debug::Info) << "OpenGL Vendor: " << glGetString(GL_VENDOR); + Log(Debug::Info) << "OpenGL Renderer: " << glGetString(GL_RENDERER); + Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION); + } + }; } void OMW::Engine::executeLocalScripts() @@ -643,8 +658,12 @@ void OMW::Engine::createWindow(Settings::Manager& settings) camera->setGraphicsContext(graphicsWindow); camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); + osg::ref_ptr realizeOperations = new SceneUtil::OperationSequence(false); + mViewer->setRealizeOperation(realizeOperations); + realizeOperations->add(new IdentifyOpenGLOperation()); + if (Debug::shouldDebugOpenGL()) - mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation()); + realizeOperations->add(new Debug::EnableGLDebugOperation()); mViewer->realize(); diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 7065fae933..33d8f1c8a6 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -317,4 +317,20 @@ bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg:: return addMSAAIntermediateTarget; } +OperationSequence::OperationSequence(bool keep) + : Operation("OperationSequence", keep) + , mOperationQueue(new osg::OperationQueue()) +{ +} + +void OperationSequence::operator()(osg::Object* object) +{ + mOperationQueue->runOperations(object); +} + +void OperationSequence::add(osg::Operation* operation) +{ + mOperationQueue->add(operation); +} + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index ddc2db845d..89d3a12e97 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -63,6 +63,18 @@ namespace SceneUtil // Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false); + + class OperationSequence : public osg::Operation + { + public: + OperationSequence(bool keep); + + void operator()(osg::Object* object) override; + + void add(osg::Operation* operation); + protected: + osg::ref_ptr mOperationQueue; + }; } #endif From 651b79d34e43b32d8392c1bb2ea690a46b8ba163 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Thu, 16 Dec 2021 12:53:28 +0800 Subject: [PATCH 1862/2859] Update changelog.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 174eb9e4b4..e5428e9112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6177: Followers of player follower stop following after waiting for a day Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla + Bug #6191: Encumbrance messagebox timer works incorrectly Bug #6197: Infinite Casting Loop Bug #6253: Multiple instances of Reflect stack additively Bug #6255: Reflect is different from vanilla From 94b2bde48adf3b3e4e29209057c3d70409f93b6d Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 16 Dec 2021 16:52:39 +0100 Subject: [PATCH 1863/2859] Raise required MyGUI version to 3.4.1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 090e6c56e3..67a8f7c679 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -399,7 +399,7 @@ set(Boost_NO_BOOST_CMAKE ON) find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) if(OPENMW_USE_SYSTEM_MYGUI) - find_package(MyGUI 3.2.2 REQUIRED) + find_package(MyGUI 3.4.1 REQUIRED) endif() find_package(SDL2 2.0.9 REQUIRED) find_package(OpenAL REQUIRED) From 2bc81e47a6af08bef6008282621f7e36ec4da877 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 16 Dec 2021 19:22:07 +0100 Subject: [PATCH 1864/2859] Remove all MyGUI version checks --- apps/openmw/mwgui/bookpage.cpp | 6 ----- apps/openmw/mwgui/keyboardnavigation.cpp | 27 ------------------- .../myguiplatform/myguirendermanager.cpp | 2 -- .../myguiplatform/myguirendermanager.hpp | 8 ------ components/myguiplatform/myguitexture.cpp | 2 -- components/myguiplatform/myguitexture.hpp | 23 +++++----------- 6 files changed, 7 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index b2d2c39fd6..acaee7de28 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -907,12 +907,6 @@ protected: return {}; MyGUI::IntPoint pos (left, top); -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - // work around inconsistency in MyGUI where the mouse press coordinates aren't - // transformed by the current Layer (even though mouse *move* events are). - if(!move) - pos = mNode->getLayer()->getPosition(left, top); -#endif pos.left -= mCroppedParent->getAbsoluteLeft (); pos.top -= mCroppedParent->getAbsoluteTop (); pos.top += mViewTop; diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index b4437873c3..249e5d0240 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -93,19 +93,6 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget) mCurrentFocus = nullptr; } -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) -void styleFocusedButton(MyGUI::Widget* w) -{ - if (w) - { - if (MyGUI::Button* b = w->castType(false)) - { - b->_setWidgetState("highlighted"); - } - } -} -#endif - bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) { while (widget && widget->getParent()) @@ -128,9 +115,6 @@ void KeyboardNavigation::onFrame() if (focus == mCurrentFocus) { -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - styleFocusedButton(mCurrentFocus); -#endif return; } @@ -143,19 +127,8 @@ void KeyboardNavigation::onFrame() if (focus != mCurrentFocus) { -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - if (mCurrentFocus) - { - if (MyGUI::Button* b = mCurrentFocus->castType(false)) - b->_setWidgetState("normal"); - } -#endif mCurrentFocus = focus; } - -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) - styleFocusedButton(mCurrentFocus); -#endif } void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus) diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 43b176c795..7fb82bbce5 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -586,7 +586,6 @@ bool RenderManager::checkTexture(MyGUI::ITexture* _texture) return true; } -#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) void RenderManager::registerShader( const std::string& _shaderName, const std::string& _vertexProgramFile, @@ -594,6 +593,5 @@ void RenderManager::registerShader( { MYGUI_PLATFORM_LOG(Warning, "osgMyGUI::RenderManager::registerShader is not implemented"); } -#endif } diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index 8ef9691e4f..bfb3da605a 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -116,17 +116,9 @@ public: bool checkTexture(MyGUI::ITexture* _texture); - // setViewSize() is a part of MyGUI::RenderManager interface since 3.4.0 release -#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3, 4, 0) - void setViewSize(int width, int height); -#else void setViewSize(int width, int height) override; -#endif - // registerShader() is a part of MyGUI::RenderManager interface since 3.4.1 release -#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) void registerShader(const std::string& _shaderName, const std::string& _vertexProgramFile, const std::string& _fragmentProgramFile) override; -#endif /*internal:*/ diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index d0e4e9a86e..d120cb52f3 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -165,8 +165,6 @@ namespace osgMyGUI return nullptr; } -#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) void OSGTexture::setShader(const std::string& _shaderName) { Log(Debug::Warning) << "OSGTexture::setShader is not implemented"; } -#endif } diff --git a/components/myguiplatform/myguitexture.hpp b/components/myguiplatform/myguitexture.hpp index e8b49eab04..4f7ff8f116 100644 --- a/components/myguiplatform/myguitexture.hpp +++ b/components/myguiplatform/myguitexture.hpp @@ -5,12 +5,6 @@ #include -#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) - #define OPENMW_MYGUI_CONST_GETTER_3_4_1 const -#else - #define OPENMW_MYGUI_CONST_GETTER_3_4_1 -#endif - namespace osg { class Image; @@ -57,21 +51,18 @@ namespace osgMyGUI void* lock(MyGUI::TextureUsage access) override; void unlock() override; - bool isLocked() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mLockedImage.valid(); } + bool isLocked() const override { return mLockedImage.valid(); } - int getWidth() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mWidth; } - int getHeight() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mHeight; } + int getWidth() const override { return mWidth; } + int getHeight() const override { return mHeight; } - MyGUI::PixelFormat getFormat() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mFormat; } - MyGUI::TextureUsage getUsage() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mUsage; } - size_t getNumElemBytes() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mNumElemBytes; } + MyGUI::PixelFormat getFormat() const override { return mFormat; } + MyGUI::TextureUsage getUsage() const override { return mUsage; } + size_t getNumElemBytes() const override { return mNumElemBytes; } MyGUI::IRenderTarget *getRenderTarget() override; - // setShader() is a part of MyGUI::RenderManager interface since 3.4.1 release -#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) - void setShader(const std::string& _shaderName) override; -#endif + void setShader(const std::string& _shaderName) override; /*internal:*/ osg::Texture2D *getTexture() const { return mTexture.get(); } From 3f52ede9cbae3153bc0827c3b4e478135c71fa4f Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 16 Dec 2021 19:48:10 +0100 Subject: [PATCH 1865/2859] Remove unnecessary myguicompat --- components/myguiplatform/myguicompat.h | 12 ------------ components/myguiplatform/myguidatamanager.cpp | 8 ++++---- components/myguiplatform/myguidatamanager.hpp | 10 ++++------ components/myguiplatform/myguirendermanager.cpp | 5 ++--- components/myguiplatform/myguirendermanager.hpp | 6 ++---- components/myguiplatform/scalinglayer.cpp | 4 +--- 6 files changed, 13 insertions(+), 32 deletions(-) delete mode 100644 components/myguiplatform/myguicompat.h diff --git a/components/myguiplatform/myguicompat.h b/components/myguiplatform/myguicompat.h deleted file mode 100644 index 04ca11a79f..0000000000 --- a/components/myguiplatform/myguicompat.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H -#define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H - -#include - -#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0) - #define OPENMW_MYGUI_CONST_GETTER_3_4_1 const -#else - #define OPENMW_MYGUI_CONST_GETTER_3_4_1 -#endif - -#endif // OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H diff --git a/components/myguiplatform/myguidatamanager.cpp b/components/myguiplatform/myguidatamanager.cpp index 0310e996b9..fc5d2bf953 100644 --- a/components/myguiplatform/myguidatamanager.cpp +++ b/components/myguiplatform/myguidatamanager.cpp @@ -15,7 +15,7 @@ void DataManager::setResourcePath(const std::string &path) mResourcePath = path; } -MyGUI::IDataStream *DataManager::getData(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 +MyGUI::IDataStream *DataManager::getData(const std::string &name) const { std::string fullpath = getDataPath(name); std::unique_ptr stream; @@ -34,13 +34,13 @@ void DataManager::freeData(MyGUI::IDataStream *data) delete data; } -bool DataManager::isDataExist(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 +bool DataManager::isDataExist(const std::string &name) const { std::string fullpath = mResourcePath + "/" + name; return boost::filesystem::exists(fullpath); } -const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) OPENMW_MYGUI_CONST_GETTER_3_4_1 +const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) const { // TODO: pattern matching (unused?) static MyGUI::VectorString strings; @@ -49,7 +49,7 @@ const MyGUI::VectorString &DataManager::getDataListNames(const std::string &patt return strings; } -const std::string &DataManager::getDataPath(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1 +const std::string &DataManager::getDataPath(const std::string &name) const { static std::string result; result.clear(); diff --git a/components/myguiplatform/myguidatamanager.hpp b/components/myguiplatform/myguidatamanager.hpp index ca2f94899c..da24763d7b 100644 --- a/components/myguiplatform/myguidatamanager.hpp +++ b/components/myguiplatform/myguidatamanager.hpp @@ -3,8 +3,6 @@ #include -#include "myguicompat.h" - namespace osgMyGUI { @@ -19,7 +17,7 @@ public: /** Get data stream from specified resource name. @param _name Resource name (usually file name). */ - MyGUI::IDataStream* getData(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; + MyGUI::IDataStream* getData(const std::string& _name) const override; /** Free data stream. @param _data Data stream. @@ -29,18 +27,18 @@ public: /** Is data with specified name exist. @param _name Resource name. */ - bool isDataExist(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; + bool isDataExist(const std::string& _name) const override; /** Get all data names with names that matches pattern. @param _pattern Pattern to match (for example "*.layout"). */ - const MyGUI::VectorString& getDataListNames(const std::string& _pattern) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; + const MyGUI::VectorString& getDataListNames(const std::string& _pattern) const override; /** Get full path to data. @param _name Resource name. @return Return full path to specified data. */ - const std::string& getDataPath(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override; + const std::string& getDataPath(const std::string& _name) const override; private: std::string mResourcePath; diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 7fb82bbce5..b8ca06a181 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -15,7 +15,6 @@ #include #include -#include "myguicompat.h" #include "myguitexture.hpp" #define MYGUI_PLATFORM_LOG_SECTION "Platform" @@ -276,7 +275,7 @@ public: osg::VertexBufferObject* getVertexBuffer(); void setVertexCount(size_t count) override; - size_t getVertexCount() OPENMW_MYGUI_CONST_GETTER_3_4_1 override; + size_t getVertexCount() const override; MyGUI::Vertex *lock() override; void unlock() override; @@ -303,7 +302,7 @@ void OSGVertexBuffer::setVertexCount(size_t count) mNeedVertexCount = count; } -size_t OSGVertexBuffer::getVertexCount() OPENMW_MYGUI_CONST_GETTER_3_4_1 +size_t OSGVertexBuffer::getVertexCount() const { return mNeedVertexCount; } diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index bfb3da605a..0d1ad4fb41 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -5,8 +5,6 @@ #include -#include "myguicompat.h" - namespace Resource { class ImageManager; @@ -79,7 +77,7 @@ public: const MyGUI::IntSize& getViewSize() const override { return mViewSize; } /** @see RenderManager::getVertexFormat */ - MyGUI::VertexColourType getVertexFormat() OPENMW_MYGUI_CONST_GETTER_3_4_1 override + MyGUI::VertexColourType getVertexFormat() const override { return mVertexFormat; } /** @see RenderManager::isFormatSupported */ @@ -112,7 +110,7 @@ public: void setInjectState(osg::StateSet* stateSet); /** @see IRenderTarget::getInfo */ - const MyGUI::RenderTargetInfo& getInfo() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mInfo; } + const MyGUI::RenderTargetInfo& getInfo() const override { return mInfo; } bool checkTexture(MyGUI::ITexture* _texture); diff --git a/components/myguiplatform/scalinglayer.cpp b/components/myguiplatform/scalinglayer.cpp index 51c148253f..75a149c810 100644 --- a/components/myguiplatform/scalinglayer.cpp +++ b/components/myguiplatform/scalinglayer.cpp @@ -3,8 +3,6 @@ #include #include -#include "myguicompat.h" - namespace osgMyGUI { @@ -39,7 +37,7 @@ namespace osgMyGUI mTarget->doRender(_buffer, _texture, _count); } - const MyGUI::RenderTargetInfo& getInfo() OPENMW_MYGUI_CONST_GETTER_3_4_1 override + const MyGUI::RenderTargetInfo& getInfo() const override { mInfo = mTarget->getInfo(); mInfo.hOffset = mHOffset; From 8fedca57802f5953f0ab229c48849ae54c9d851c Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 16 Dec 2021 21:48:47 +0100 Subject: [PATCH 1866/2859] Fix navmesh cache progress bar jumps When initial approximation of maximum progress based on numer of cells is too high comparing to real number of navmesh tiles. --- apps/launcher/datafilespage.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 097dc21dd2..f5efa8bbcc 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -457,11 +457,18 @@ void Launcher::DataFilesPage::updateNavMeshProgress() QRegularExpressionMatch match = pattern.match(text); if (!match.hasMatch()) return; - int maximum = match.captured(2).toInt(); + int value = match.captured(1).toInt(); + const int maximum = match.captured(2).toInt(); if (text.contains("cell")) - maximum *= 100; - ui.navMeshProgressBar->setMaximum(std::max(ui.navMeshProgressBar->maximum(), maximum)); - ui.navMeshProgressBar->setValue(match.captured(1).toInt()); + ui.navMeshProgressBar->setMaximum(maximum * 100); + else if (maximum > ui.navMeshProgressBar->maximum()) + ui.navMeshProgressBar->setMaximum(maximum); + else + value += static_cast(std::round( + (ui.navMeshProgressBar->maximum() - maximum) + * (static_cast(value) / static_cast(maximum)) + )); + ui.navMeshProgressBar->setValue(value); } void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus) From aaf6c82e33ff03567dd9657f8d4c075e061ea7b2 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 16 Dec 2021 22:40:02 +0100 Subject: [PATCH 1867/2859] Do not write shapes to navmeshdb when writing is disabled --- .../detournavigator/asyncnavmeshupdater.cpp | 43 ++++++++++++++++++- .../detournavigator/asyncnavmeshupdater.cpp | 29 +++++++++---- .../detournavigator/asyncnavmeshupdater.hpp | 3 +- .../detournavigator/dbrefgeometryobject.hpp | 27 +++++++++--- components/detournavigator/navmeshdbutils.cpp | 25 ++++++++++- components/detournavigator/navmeshdbutils.hpp | 4 ++ components/misc/typetraits.hpp | 19 ++++++++ 7 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 components/misc/typetraits.hpp diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index d245838858..b23446cea7 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -26,6 +26,23 @@ namespace recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0}); } + void addObject(const btBoxShape& shape, TileCachedRecastMeshManager& recastMeshManager) + { + const ObjectId id(&shape); + osg::ref_ptr bulletShape(new Resource::BulletShape); + bulletShape->mFileName = "test.nif"; + bulletShape->mFileHash = "test_hash"; + ObjectTransform objectTransform; + std::fill(std::begin(objectTransform.mPosition.pos), std::end(objectTransform.mPosition.pos), 0.1f); + std::fill(std::begin(objectTransform.mPosition.rot), std::end(objectTransform.mPosition.rot), 0.2f); + objectTransform.mScale = 3.14f; + const CollisionShape collisionShape( + osg::ref_ptr(new Resource::BulletShapeInstance(bulletShape)), + shape, objectTransform + ); + recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground); + } + struct DetourNavigatorAsyncNavMeshUpdaterTest : Test { Settings mSettings = makeSettings(); @@ -34,6 +51,7 @@ namespace const osg::Vec3f mAgentHalfExtents {29, 29, 66}; const TilePosition mPlayerTile {0, 0}; const std::string mWorldspace = "sys::default"; + const btBoxShape mBox {btVector3(100, 100, 20)}; Loading::Listener mListener; }; @@ -111,6 +129,7 @@ namespace { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); + addObject(mBox, mRecastMeshManager); auto db = std::make_unique(":memory:"); NavMeshDb* const dbPtr = db.get(); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); @@ -130,10 +149,11 @@ namespace EXPECT_EQ(tile->mVersion, mSettings.mNavMeshVersion); } - TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write) + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write_tiles) { mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); + addObject(mBox, mRecastMeshManager); auto db = std::make_unique(":memory:"); NavMeshDb* const dbPtr = db.get(); mSettings.mWriteToNavMeshDb = false; @@ -152,6 +172,27 @@ namespace ASSERT_FALSE(tile.has_value()); } + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write_shapes) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + addObject(mBox, mRecastMeshManager); + auto db = std::make_unique(":memory:"); + NavMeshDb* const dbPtr = db.get(); + mSettings.mWriteToNavMeshDb = false; + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const TilePosition tilePosition {0, 0}; + const std::map changedTiles {{tilePosition, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); + ASSERT_NE(recastMesh, nullptr); + const auto objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v); }); + EXPECT_FALSE(objects.has_value()); + } + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_read_from_db_on_cache_miss) { mRecastMeshManager.setWorldspace(mWorldspace); diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 47d984030f..657befd9d7 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -89,7 +89,8 @@ namespace DetourNavigator { if (db == nullptr) return nullptr; - return std::make_unique(updater, std::move(db), TileVersion(settings.mNavMeshVersion), settings.mRecast); + return std::make_unique(updater, std::move(db), TileVersion(settings.mNavMeshVersion), + settings.mRecast, settings.mWriteToNavMeshDb); } void updateJobs(std::deque& jobs, TilePosition playerTile, int maxTiles) @@ -704,11 +705,12 @@ namespace DetourNavigator } DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, - TileVersion version, const RecastSettings& recastSettings) + TileVersion version, const RecastSettings& recastSettings, bool writeToDb) : mUpdater(updater) , mRecastSettings(recastSettings) , mDb(std::move(db)) , mVersion(version) + , mWriteToDb(writeToDb) , mNextTileId(mDb->getMaxTileId() + 1) , mNextShapeId(mDb->getMaxShapeId() + 1) , mThread([this] { run(); }) @@ -799,12 +801,23 @@ namespace DetourNavigator if (job->mInput.empty()) { Log(Debug::Debug) << "Serializing input for job " << job->mId; - const ShapeId shapeId = mNextShapeId; - const std::vector objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), - [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); - if (shapeId != mNextShapeId) - ++mWrites; - job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects); + if (mWriteToDb) + { + const ShapeId shapeId = mNextShapeId; + const auto objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); + if (shapeId != mNextShapeId) + ++mWrites; + job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects); + } + else + { + const auto objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v); }); + if (!objects.has_value()) + return; + job->mInput = serialize(mRecastSettings, *job->mRecastMesh, *objects); + } } job->mCachedTileData = mDb->getTileData(job->mWorldspace, job->mChangedTile, job->mInput); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 541d86fe9c..31778f8c26 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -136,7 +136,7 @@ namespace DetourNavigator }; DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, - TileVersion version, const RecastSettings& recastSettings); + TileVersion version, const RecastSettings& recastSettings, bool writeToDb); ~DbWorker(); @@ -153,6 +153,7 @@ namespace DetourNavigator const RecastSettings& mRecastSettings; const std::unique_ptr mDb; const TileVersion mVersion; + const bool mWriteToDb; TileId mNextTileId; ShapeId mNextShapeId; DbJobQueue mQueue; diff --git a/components/detournavigator/dbrefgeometryobject.hpp b/components/detournavigator/dbrefgeometryobject.hpp index 2be44ca175..acf2a58b19 100644 --- a/components/detournavigator/dbrefgeometryobject.hpp +++ b/components/detournavigator/dbrefgeometryobject.hpp @@ -4,10 +4,14 @@ #include "objecttransform.hpp" #include "recastmesh.hpp" +#include + #include #include #include #include +#include +#include namespace DetourNavigator { @@ -28,16 +32,27 @@ namespace DetourNavigator }; template - inline std::vector makeDbRefGeometryObjects(const std::vector& meshSources, - ResolveMeshSource&& resolveMeshSource) + inline auto makeDbRefGeometryObjects(const std::vector& meshSources, ResolveMeshSource&& resolveMeshSource) + -> std::conditional_t< + Misc::isOptional>, + std::optional>, + std::vector + > { std::vector result; result.reserve(meshSources.size()); - std::transform(meshSources.begin(), meshSources.end(), std::back_inserter(result), - [&] (const MeshSource& meshSource) + for (const MeshSource& meshSource : meshSources) + { + const auto shapeId = resolveMeshSource(meshSource); + if constexpr (Misc::isOptional>) { - return DbRefGeometryObject {resolveMeshSource(meshSource), meshSource.mObjectTransform}; - }); + if (!shapeId.has_value()) + return std::nullopt; + result.push_back(DbRefGeometryObject {*shapeId, meshSource.mObjectTransform}); + } + else + result.push_back(DbRefGeometryObject {shapeId, meshSource.mObjectTransform}); + } std::sort(result.begin(), result.end()); return result; } diff --git a/components/detournavigator/navmeshdbutils.cpp b/components/detournavigator/navmeshdbutils.cpp index dce6ef3a72..86f81bfc51 100644 --- a/components/detournavigator/navmeshdbutils.cpp +++ b/components/detournavigator/navmeshdbutils.cpp @@ -5,12 +5,21 @@ #include #include +#include namespace DetourNavigator { namespace { - ShapeId getShapeId(NavMeshDb& db, const std::string& name, ShapeType type, const std::string& hash, ShapeId& nextShapeId) + std::optional findShapeId(NavMeshDb& db, const std::string& name, ShapeType type, + const std::string& hash) + { + const Sqlite3::ConstBlob hashData {hash.data(), static_cast(hash.size())}; + return db.findShapeId(name, type, hashData); + } + + ShapeId getShapeId(NavMeshDb& db, const std::string& name, ShapeType type, + const std::string& hash, ShapeId& nextShapeId) { const Sqlite3::ConstBlob hashData {hash.data(), static_cast(hash.size())}; if (const auto existingShapeId = db.findShapeId(name, type, hashData)) @@ -37,4 +46,18 @@ namespace DetourNavigator return ShapeId(0); } } + + std::optional resolveMeshSource(NavMeshDb& db, const MeshSource& source) + { + switch (source.mAreaType) + { + case AreaType_null: + return findShapeId(db, source.mShape->mFileName, ShapeType::Avoid, source.mShape->mFileHash); + case AreaType_ground: + return findShapeId(db, source.mShape->mFileName, ShapeType::Collision, source.mShape->mFileHash); + default: + Log(Debug::Warning) << "Trying to resolve recast mesh source with unsupported area type: " << source.mAreaType; + return std::nullopt; + } + } } diff --git a/components/detournavigator/navmeshdbutils.hpp b/components/detournavigator/navmeshdbutils.hpp index 02b3bdbb00..aafde3307c 100644 --- a/components/detournavigator/navmeshdbutils.hpp +++ b/components/detournavigator/navmeshdbutils.hpp @@ -3,11 +3,15 @@ #include "navmeshdb.hpp" +#include + namespace DetourNavigator { struct MeshSource; ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId); + + std::optional resolveMeshSource(NavMeshDb& db, const MeshSource& source); } #endif diff --git a/components/misc/typetraits.hpp b/components/misc/typetraits.hpp new file mode 100644 index 0000000000..4c6a7e731b --- /dev/null +++ b/components/misc/typetraits.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_COMPONENTS_MISC_TYPETRAITS_H +#define OPENMW_COMPONENTS_MISC_TYPETRAITS_H + +#include +#include + +namespace Misc +{ + template + struct IsOptional : std::false_type {}; + + template + struct IsOptional> : std::true_type {}; + + template + inline constexpr bool isOptional = IsOptional::value; +} + +#endif From b0c2317d3456860816ca2a384ce3f92fd1e4a23c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 17 Dec 2021 00:06:11 +0100 Subject: [PATCH 1868/2859] Fix build on ubuntu 18.04 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /<>/openmw-0.47.0+git202112160802~ubuntu18.04.1/apps/navmeshtool/worldspacedata.cpp:213:109: error: class template argument deduction failed: heightfields.emplace_back(std::vector(std::begin(landData.mHeights), std::end(landData.mHeights))); ^ /<>/openmw-0.47.0+git202112160802~ubuntu18.04.1/apps/navmeshtool/worldspacedata.cpp:213:109: error: no matching function for call to ‘vector(float*, float*)’ In file included from /usr/include/c++/7/vector:64:0, from /<>/openmw-0.47.0+git202112160802~ubuntu18.04.1/components/esm/loadpgrd.hpp:5, from /<>/openmw-0.47.0+git202112160802~ubuntu18.04.1/components/misc/convert.hpp:5, from /<>/openmw-0.47.0+git202112160802~ubuntu18.04.1/components/bullethelpers/collisionobject.hpp:4, from /<>/openmw-0.47.0+git202112160802~ubuntu18.04.1/apps/navmeshtool/worldspacedata.hpp:4, from /<>/openmw-0.47.0+git202112160802~ubuntu18.04.1/apps/navmeshtool/worldspacedata.cpp:1: /usr/include/c++/7/bits/stl_vector.h:411:2: note: candidate: template vector(_InputIterator, _InputIterator, const _Alloc&)-> std::vector<_Tp, _Alloc> vector(_InputIterator __first, _InputIterator __last, ^~~~~~ --- apps/navmeshtool/worldspacedata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 189d24f34a..b88b967cf5 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -210,7 +210,7 @@ namespace NavMeshTool ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique()); land->loadData(ESM::Land::DATA_VHGT, &landData); - heightfields.emplace_back(std::vector(std::begin(landData.mHeights), std::end(landData.mHeights))); + heightfields.push_back(std::vector(std::begin(landData.mHeights), std::end(landData.mHeights))); HeightfieldSurface surface; surface.mHeights = heightfields.back().data(); surface.mMinHeight = landData.mMinHeight; From 85a52606cf83affde56ce30dbb8ade1e25e29747 Mon Sep 17 00:00:00 2001 From: psi29a Date: Fri, 17 Dec 2021 09:33:13 +0000 Subject: [PATCH 1869/2859] Add support for macOS12 with XCode13 in our CI/CD --- .gitlab-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 34a40756fe..d7f8520de4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -227,6 +227,14 @@ macOS11_Xcode12: variables: CCACHE_SIZE: 3G +macOS12_Xcode13: + extends: .MacOS + image: macos-12-xcode-13 + cache: + key: macOS12_Xcode13.v1 + variables: + CCACHE_SIZE: 3G + variables: &engine-targets targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard,openmw-navmeshtool" package: "Engine" From 43b2114444d7e9577d16ba463b7ea7cb150502c2 Mon Sep 17 00:00:00 2001 From: Wassim DHIF Date: Fri, 8 Oct 2021 18:36:16 +0200 Subject: [PATCH 1870/2859] Add Ubuntu build Docker image --- docker/Dockerfile.ubuntu | 23 +++++++++++++++++++++++ docker/README.md | 17 +++++++++++++++++ docker/build.sh | 13 +++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 docker/Dockerfile.ubuntu create mode 100644 docker/README.md create mode 100755 docker/build.sh diff --git a/docker/Dockerfile.ubuntu b/docker/Dockerfile.ubuntu new file mode 100644 index 0000000000..51aaa6465e --- /dev/null +++ b/docker/Dockerfile.ubuntu @@ -0,0 +1,23 @@ +FROM ubuntu +LABEL maintainer="Wassim DHIF " + +ENV NPROC=1 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends software-properties-common apt-utils \ + && add-apt-repository ppa:openmw/openmw \ + && apt-get update \ + && apt-get install -y --no-install-recommends openmw openmw-launcher \ + && apt-get install -y --no-install-recommends git build-essential cmake \ + libopenal-dev libopenscenegraph-dev libbullet-dev libsdl2-dev \ + libmygui-dev libunshield-dev liblz4-dev libtinyxml-dev libqt5opengl5-dev \ + libboost-filesystem-dev libboost-program-options-dev libboost-iostreams-dev \ + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev \ + librecastnavigation-dev libluajit-5.1-dev + +COPY build.sh /build.sh + +RUN mkdir /openmw +WORKDIR /openmw + +ENTRYPOINT ["/build.sh"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..4c4131c235 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,17 @@ +# Build OpenMW using Docker + +## Build Docker image + +Replace `LINUX_VERSION` with the Linux distribution you wish to use. +``` +docker build -f Dockerfile.LINUX_VERSION -t openmw.LINUX_VERSION . +``` + +## Build OpenMW using Docker + +Labeling systems like SELinux require that proper labels are placed on volume content mounted into a container. +Without a label, the security system might prevent the processes running inside the container from using the content. +The Z option tells Docker to label the content with a private unshared label. +``` +docker run -v /path/to/openmw:/openmw:Z -e NPROC=2 -it openmw.LINUX_VERSION +``` diff --git a/docker/build.sh b/docker/build.sh new file mode 100755 index 0000000000..0f79161379 --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -xe + +# Creating build directory... +mkdir -p build +cd build + +# Running CMake... +cmake ../ + +# Building with $NPROC CPU... +make -j $NPROC From a2964f2244cb43613e3bd81d322dc5b824cef93b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 18 Dec 2021 15:16:37 +0100 Subject: [PATCH 1871/2859] Don't consider underwater sneaking to be sneaking --- 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 0cbe5c284a..6c33f86c76 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1944,7 +1944,7 @@ void CharacterController::update(float duration) bool flying = world->isFlying(mPtr); bool solid = world->isActorCollisionEnabled(mPtr); // Can't run and sneak while flying (see speed formula in Npc/Creature::getSpeed) - bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying; + bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying && !inwater; bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; CreatureStats &stats = cls.getCreatureStats(mPtr); Movement& movementSettings = cls.getMovementSettings(mPtr); From 0688f55171628a91080e0b23f95678ec600a5b61 Mon Sep 17 00:00:00 2001 From: myrix Date: Sat, 18 Dec 2021 20:07:41 +0300 Subject: [PATCH 1872/2859] optimized keyword parsing --- apps/openmw/mwdialogue/hypertextparser.cpp | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwdialogue/hypertextparser.cpp b/apps/openmw/mwdialogue/hypertextparser.cpp index caafa5f324..389488cbe1 100644 --- a/apps/openmw/mwdialogue/hypertextparser.cpp +++ b/apps/openmw/mwdialogue/hypertextparser.cpp @@ -47,19 +47,25 @@ namespace MWDialogue void tokenizeKeywords(const std::string & text, std::vector & tokens) { - const MWWorld::Store & dialogs = - MWBase::Environment::get().getWorld()->getStore().get(); + static bool keywordSearchInitialized = false; + static KeywordSearch keywordSearch; - std::vector keywordList; - keywordList.reserve(dialogs.getSize()); - for (const auto& it : dialogs) - keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); - sort(keywordList.begin(), keywordList.end()); + if (!keywordSearchInitialized) + { + const MWWorld::Store & dialogs = + MWBase::Environment::get().getWorld()->getStore().get(); + + std::vector keywordList; + keywordList.reserve(dialogs.getSize()); + for (const auto& it : dialogs) + keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); + sort(keywordList.begin(), keywordList.end()); - KeywordSearch keywordSearch; + for (const auto& it : keywordList) + keywordSearch.seed(it, 0 /*unused*/); - for (const auto& it : keywordList) - keywordSearch.seed(it, 0 /*unused*/); + keywordSearchInitialized = true; + } std::vector::Match> matches; keywordSearch.highlightKeywords(text.begin(), text.end(), matches); From c746a8abb70a21b73eb9bf0fec934f43337a41ca Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 18 Dec 2021 21:34:15 +0000 Subject: [PATCH 1873/2859] Attempt to catch freezes on Windows --- .../crashcatcher/windows_crashcatcher.cpp | 3 +- .../crashcatcher/windows_crashmonitor.cpp | 56 +++++++++++++++++++ .../crashcatcher/windows_crashmonitor.hpp | 4 ++ components/crashcatcher/windows_crashshm.hpp | 1 + 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp index 39ac86d7b8..eea6ac40ad 100644 --- a/components/crashcatcher/windows_crashcatcher.cpp +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -144,6 +144,7 @@ namespace Crash mShm->mEvent = CrashSHM::Event::Startup; mShm->mStartup.mShmMutex = duplicateHandle(mShmMutex); mShm->mStartup.mAppProcessHandle = duplicateHandle(GetCurrentProcess()); + mShm->mStartup.mAppMainThreadId = GetThreadId(GetCurrentThread()); mShm->mStartup.mSignalApp = duplicateHandle(mSignalAppEvent); mShm->mStartup.mSignalMonitor = duplicateHandle(mSignalMonitorEvent); @@ -196,7 +197,7 @@ namespace Crash // must remain until monitor has finished waitMonitor(); - std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !"; + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); } diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 8976deb2ea..26ebc0bad3 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include "windows_crashcatcher.hpp" #include "windows_crashmonitor.hpp" #include "windows_crashshm.hpp" @@ -28,6 +30,7 @@ namespace Crash mShmMutex = mShm->mStartup.mShmMutex; mAppProcessHandle = mShm->mStartup.mAppProcessHandle; + mAppMainThreadId = mShm->mStartup.mAppMainThreadId; mSignalAppEvent = mShm->mStartup.mSignalApp; mSignalMonitorEvent = mShm->mStartup.mSignalMonitor; } @@ -80,6 +83,44 @@ namespace Crash return code == STILL_ACTIVE; } + bool CrashMonitor::isAppFrozen() + { + if (!mAppWindowHandle) + { + EnumWindows([](HWND handle, LPARAM param) -> BOOL { + CrashMonitor& crashMonitor = *(CrashMonitor*)param; + DWORD processId; + if (GetWindowThreadProcessId(handle, &processId) == crashMonitor.mAppMainThreadId && processId == GetProcessId(crashMonitor.mAppProcessHandle)) + { + if (GetWindow(handle, GW_OWNER) == 0) + { + crashMonitor.mAppWindowHandle = handle; + return false; + } + } + return true; + }, (LPARAM)this); + if (mAppWindowHandle) + { + // TODO: use https://devblogs.microsoft.com/oldnewthing/20111026-00/?p=9263 to monitor for the window being destroyed + } + else + return false; + } + if (IsHungAppWindow) + return IsHungAppWindow(mAppWindowHandle); + else + { + BOOL debuggerPresent; + + if (CheckRemoteDebuggerPresent(mAppProcessHandle, &debuggerPresent) && debuggerPresent) + return false; + if (SendMessageTimeoutA(mAppWindowHandle, WM_NULL, 0, 0, 0, 5000, nullptr) == 0) + return GetLastError() == ERROR_TIMEOUT; + } + return false; + } + void CrashMonitor::run() { try @@ -88,8 +129,16 @@ namespace Crash signalApp(); bool running = true; + bool frozen = false; while (isAppAlive() && running) { + if (isAppFrozen()) + { + frozen = true; + handleCrash(); + running = false; + break; + } if (waitApp()) { shmLock(); @@ -113,6 +162,13 @@ namespace Crash } } + if (frozen) + { + TerminateProcess(mAppProcessHandle, -1); + std::string message = "OpenMW appears to have frozen.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !"; + SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); + } + } catch (...) { diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index 678d38435c..01acd34bdd 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -21,6 +21,8 @@ public: private: HANDLE mAppProcessHandle = nullptr; + DWORD mAppMainThreadId = 0; + HWND mAppWindowHandle = nullptr; // triggered when the monitor process wants to wake the parent process (received via SHM) HANDLE mSignalAppEvent = nullptr; @@ -37,6 +39,8 @@ private: bool isAppAlive() const; + bool isAppFrozen(); + void shmLock(); void shmUnlock(); diff --git a/components/crashcatcher/windows_crashshm.hpp b/components/crashcatcher/windows_crashshm.hpp index 47929a45fe..a474600f94 100644 --- a/components/crashcatcher/windows_crashshm.hpp +++ b/components/crashcatcher/windows_crashshm.hpp @@ -26,6 +26,7 @@ namespace Crash struct Startup { HANDLE mAppProcessHandle; + DWORD mAppMainThreadId; HANDLE mSignalApp; HANDLE mSignalMonitor; HANDLE mShmMutex; From 3a9cfbfa5340b80dd8949a0334ddea72a58eb39a Mon Sep 17 00:00:00 2001 From: myrix Date: Sun, 19 Dec 2021 14:00:49 +0300 Subject: [PATCH 1874/2859] HyperTextParser as a class with proper keyword search caching --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 3 +- apps/openmw/mwdialogue/dialoguemanagerimp.hpp | 4 + apps/openmw/mwdialogue/hypertextparser.cpp | 126 +++++++++--------- apps/openmw/mwdialogue/hypertextparser.hpp | 15 ++- apps/openmw/mwworld/store.cpp | 26 +++- apps/openmw/mwworld/store.hpp | 4 + 6 files changed, 105 insertions(+), 73 deletions(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 9800f1b39c..0698624f32 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -43,7 +43,6 @@ #include "../mwmechanics/actorutil.hpp" #include "filter.hpp" -#include "hypertextparser.hpp" namespace MWDialogue { @@ -80,7 +79,7 @@ namespace MWDialogue { std::vector topicIdList; - std::vector hypertext = HyperTextParser::parseHyperText(text); + std::vector hypertext = mHyperTextParser.parseHyperText(text); for (std::vector::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok) { diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 57eb74d0a6..1fc80db666 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -16,6 +16,8 @@ #include "../mwscript/compilercontext.hpp" +#include "hypertextparser.hpp" + namespace ESM { struct Dialogue; @@ -57,6 +59,8 @@ namespace MWDialogue int mCurrentDisposition; int mPermanentDispositionChange; + HyperTextParser mHyperTextParser; + std::vector parseTopicIdsFromText (const std::string& text); void addTopicsFromText (const std::string& text); diff --git a/apps/openmw/mwdialogue/hypertextparser.cpp b/apps/openmw/mwdialogue/hypertextparser.cpp index 389488cbe1..eae36d26d1 100644 --- a/apps/openmw/mwdialogue/hypertextparser.cpp +++ b/apps/openmw/mwdialogue/hypertextparser.cpp @@ -6,95 +6,89 @@ #include "../mwworld/store.hpp" #include "../mwworld/esmstore.hpp" -#include "keywordsearch.hpp" - #include "hypertextparser.hpp" namespace MWDialogue { - namespace HyperTextParser + std::vector HyperTextParser::parseHyperText(const std::string & text) { - std::vector parseHyperText(const std::string & text) + std::vector result; + size_t pos_end = std::string::npos, iteration_pos = 0; + for(;;) { - std::vector result; - size_t pos_end = std::string::npos, iteration_pos = 0; - for(;;) + size_t pos_begin = text.find('@', iteration_pos); + if (pos_begin != std::string::npos) + pos_end = text.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) { - size_t pos_begin = text.find('@', iteration_pos); - if (pos_begin != std::string::npos) - pos_end = text.find('#', pos_begin); - - if (pos_begin != std::string::npos && pos_end != std::string::npos) - { - if (pos_begin != iteration_pos) - tokenizeKeywords(text.substr(iteration_pos, pos_begin - iteration_pos), result); - - std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1); - result.emplace_back(link, Token::ExplicitLink); - - iteration_pos = pos_end + 1; - } - else - { - if (iteration_pos != text.size()) - tokenizeKeywords(text.substr(iteration_pos), result); - break; - } - } + if (pos_begin != iteration_pos) + tokenizeKeywords(text.substr(iteration_pos, pos_begin - iteration_pos), result); - return result; + std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1); + result.emplace_back(link, Token::ExplicitLink); + + iteration_pos = pos_end + 1; + } + else + { + if (iteration_pos != text.size()) + tokenizeKeywords(text.substr(iteration_pos), result); + break; + } } - void tokenizeKeywords(const std::string & text, std::vector & tokens) - { - static bool keywordSearchInitialized = false; - static KeywordSearch keywordSearch; + return result; + } - if (!keywordSearchInitialized) - { - const MWWorld::Store & dialogs = - MWBase::Environment::get().getWorld()->getStore().get(); + void HyperTextParser::tokenizeKeywords(const std::string & text, std::vector & tokens) + { + const MWWorld::Store & dialogs = + MWBase::Environment::get().getWorld()->getStore().get(); - std::vector keywordList; - keywordList.reserve(dialogs.getSize()); - for (const auto& it : dialogs) - keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); - sort(keywordList.begin(), keywordList.end()); + if (dialogs.getModPoint() != mKeywordModPoint) + { + mKeywordSearch.clear(); - for (const auto& it : keywordList) - keywordSearch.seed(it, 0 /*unused*/); + std::vector keywordList; + keywordList.reserve(dialogs.getSize()); + for (const auto& it : dialogs) + keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); + sort(keywordList.begin(), keywordList.end()); - keywordSearchInitialized = true; - } + for (const auto& it : keywordList) + mKeywordSearch.seed(it, 0 /*unused*/); - std::vector::Match> matches; - keywordSearch.highlightKeywords(text.begin(), text.end(), matches); + mKeywordModPoint = dialogs.getModPoint(); + } - for (std::vector::Match>::const_iterator it = matches.begin(); it != matches.end(); ++it) - { - tokens.emplace_back(std::string(it->mBeg, it->mEnd), Token::ImplicitKeyword); - } + std::vector::Match> matches; + mKeywordSearch.highlightKeywords(text.begin(), text.end(), matches); + + for (std::vector::Match>::const_iterator it = matches.begin(); it != matches.end(); ++it) + { + tokens.emplace_back(std::string(it->mBeg, it->mEnd), Token::ImplicitKeyword); } + } + + size_t HyperTextParser::removePseudoAsterisks(std::string & phrase) + { + size_t pseudoAsterisksCount = 0; - size_t removePseudoAsterisks(std::string & phrase) + if( !phrase.empty() ) { - size_t pseudoAsterisksCount = 0; + std::string::reverse_iterator rit = phrase.rbegin(); - if( !phrase.empty() ) + const char specialPseudoAsteriskCharacter = 127; + while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) { - std::string::reverse_iterator rit = phrase.rbegin(); - - const char specialPseudoAsteriskCharacter = 127; - while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) - { - pseudoAsterisksCount++; - ++rit; - } + pseudoAsterisksCount++; + ++rit; } + } - phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount); + phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount); - return pseudoAsterisksCount; - } + return pseudoAsterisksCount; } } diff --git a/apps/openmw/mwdialogue/hypertextparser.hpp b/apps/openmw/mwdialogue/hypertextparser.hpp index 4ae0474c42..1e903d5618 100644 --- a/apps/openmw/mwdialogue/hypertextparser.hpp +++ b/apps/openmw/mwdialogue/hypertextparser.hpp @@ -4,10 +4,17 @@ #include #include +#include "keywordsearch.hpp" + namespace MWDialogue { - namespace HyperTextParser + class HyperTextParser { + uint64_t mKeywordModPoint; + KeywordSearch mKeywordSearch; + + public: + struct Token { enum Type @@ -24,12 +31,14 @@ namespace MWDialogue Type mType; }; + HyperTextParser() : mKeywordModPoint(0) {} + // In translations (at least Russian) the links are marked with @#, so // it should be a function to parse it std::vector parseHyperText(const std::string & text); void tokenizeKeywords(const std::string & text, std::vector & tokens); - size_t removePseudoAsterisks(std::string & phrase); - } + static size_t removePseudoAsterisks(std::string & phrase); + }; } #endif diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 4d720a11a7..5c696e5556 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -77,12 +77,13 @@ namespace MWWorld template Store::Store() + : mModPoint(1) { } template Store::Store(const Store& orig) - : mStatic(orig.mStatic) + : mStatic(orig.mStatic), mModPoint(orig.mModPoint + 1) { } @@ -93,6 +94,8 @@ namespace MWWorld assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); mDynamic.clear(); + + mModPoint++; } template @@ -162,6 +165,8 @@ namespace MWWorld if (inserted.second) mShared.push_back(&inserted.first->second); + mModPoint++; + return RecordId(record.mId, isDeleted); } template @@ -213,6 +218,9 @@ namespace MWWorld T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); + + mModPoint++; + return ptr; } template @@ -222,6 +230,9 @@ namespace MWWorld T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); + + mModPoint++; + return ptr; } template @@ -242,6 +253,8 @@ namespace MWWorld ++sharedIter; } mStatic.erase(it); + + mModPoint++; } return true; @@ -259,6 +272,9 @@ namespace MWWorld for (auto it = mDynamic.begin(); it != mDynamic.end(); ++it) { mShared.push_back(&it->second); } + + mModPoint++; + return true; } template @@ -997,6 +1013,8 @@ namespace MWWorld // TODO: verify and document this inconsistent behaviour // TODO: if we require this behaviour, maybe we should move it to the place that requires it std::sort(mShared.begin(), mShared.end(), [](const ESM::Dialogue* l, const ESM::Dialogue* r) -> bool { return l->mId < r->mId; }); + + mModPoint++; } template <> @@ -1018,6 +1036,8 @@ namespace MWWorld found->second.loadData(esm, isDeleted); dialogue.mId = found->second.mId; } + + mModPoint++; return RecordId(dialogue.mId, isDeleted); } @@ -1025,7 +1045,9 @@ namespace MWWorld template<> bool Store::eraseStatic(const std::string &id) { - mStatic.erase(id); + if (mStatic.erase(id)) + mModPoint++; + return true; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 22f36da690..b4ac1056a8 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -159,6 +159,8 @@ namespace MWWorld typedef std::unordered_map Dynamic; Dynamic mDynamic; + uint64_t mModPoint; + friend class ESMStore; public: @@ -203,6 +205,8 @@ namespace MWWorld RecordId load(ESM::ESMReader &esm) override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override; RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) override; + + uint64_t getModPoint() const { return mModPoint; } }; template <> From 02ef6c858dcc92e5a32d985180b6dd4143dc3d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Sun, 19 Dec 2021 21:27:00 +0000 Subject: [PATCH 1875/2859] Tutorial on how to get a static model from Blender to OpenMW. The existing article on Blender-OpenMW pipeline via COLLADA will be split into more in-depth articles for static and animated models respectively. --- ...pipeline-blender-collada-static-models.rst | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst new file mode 100644 index 0000000000..72edc6fe1f --- /dev/null +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst @@ -0,0 +1,129 @@ +######################## +Static Model via COLLADA +######################## + +This tutorial shows how to get a static model from Blender to OpenMW +using the COLLADA format. It does not cover using Blender itself, as there are +many better resources for that. The focus is solely on the pipeline and its +specific requirements. Static models are those that don’t have any animations +included in the exported file. + +Requirements +************ + +To use the Blender to OpenMW pipeline via COLLADA, you will need the following. + +* `OpenMW 0.47 `_ or later +* `Blender 2.81 `_ or later. Latest confirmed, working version is Blender 3.0 +* `Better COLLADA Exporter `_ tuned for OpenMW +* A model you would like to export. In our case, it's a barrel. + +The Barrel +******** +The barrel shown in this tutorial, and its revelant files, are available from +the `Example Suite repository `_. +This should be useful for further study of how to set up a static prop in case +the tutorial is not clear on any particular thing. + +.. image:: barrel-prop-in-blender.webp + :align: center + +* ``data/meshes/the_barrel.dae`` – exported model +* ``data/textures/the_barrel.dds`` – diffuse texture +* ``data/textures/the_barrel_n.dds`` – normal map +* ``data/textures/the_barrel_n.dds`` – specular map +* ``source_assets/the_barrel.blend`` – source file configured as this tutorial specifies + +Location, Rotation, Scale +************************* + +First, let's take a look at how the fundamental properties of a scene +in Blender translate to a COLLADA model suitable for use in OpenMW. These apply +the same to static and animated models. + +Location +======== + +Objects keep their visual location and origin they had in the original scene. + +Rotation +======== + +* Blender’s +Z axis is up axis in OpenMW +* Blender’s +Y axis is front axis in OpenMW +* Blender’s X axis is left-right axis in OpenMW + +Scale +===== + +Scale ratio between Blender and OpenMW is 70 to 1. This means 70 units in +Blender translate to 1 m in OpenMW. + +However, a scale factor like this is impractical to work with. A better +approach is to work with a scale of 1 Blender unit = 1 m and apply the 70 scale +factor at export. The exporter will automatically scale all object, mesh, +armature and animation data. + + +Materials +********* + +OpenMW uses the classic, specular material setup and currently doesn't +support the more mainstream `PBR `_ +way. In Blender, the mesh needs a default material with a diffuse texture +connected to the ``Base Color`` socket. This is enough for the material to be +included in the exported COLLADA file. + +.. image:: barrel-prop-in-blender-material.webp + :align: center + +Additional texture types, such as specular or normal maps, are used +when enabled in the OpenMW Launcher with the ``Auto use object normal maps`` +and ``Auto use object specular maps`` options. The textures need to follow the +name of the diffuse texture with an additional suffix, and be in the same +folder. OpenMW will then automatically recognize and use these textures. In the +case of the barrel, the textures are named: + +* ``the_barrel.dds`` - diffuse texture +* ``the_barrel_n.dds`` - normal map +* ``the_barrel_spec.dds`` - specular map + +Collision Shapes +**************** + +In Blender, a custom collision shape is set up with an empty named +``Collision`` or ``collision``. Any mesh that is a child of this empty will be +used for physics collision and will not be visible in-game. There can be +multiple child meshes under ``collision`` and they will all contribute to the +collision shapes. The meshes themselves can have an arbitrary name, it's only +the name of the empty that is important. The ``tcb`` command in OpenMW's in-game +console will make the collision shapes visible and you will be able to inspect +them. + +.. image:: barrel-prop-in-blender-collision.webp + :align: center + +If not custom collision shape is present, OpenMW will use the regular +mesh itself, which is not optimal and needs to be avoided. + +Exporter Settings +***************** + +For static models, use the following exporter settings. Before export, select +all objects you wish to include in the exported file and have the "Selected +Objects" option enabled. Without this, the exporter could fail. + + +.. image:: dae-exporter-static.webp + :align: center + +Getting the Model in-game +************************* + +Once the model is exported, it needs to be placed in the correct folder where +OpenMW will read it. In OpenMW-CS it can then be assigned to an object and added +to a world cell. + + +.. image:: barrel-prop-in-openmwcs.webp + :align: center From f057713bcbdd6e6444dea216b4ea53ac851a1961 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 20 Dec 2021 18:31:39 +0100 Subject: [PATCH 1876/2859] Fix coverity submission. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d7f8520de4..e7cd3e4cba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -81,8 +81,8 @@ Coverity: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL - --form file=@cov-int.tar.gz --form version="`git describe --tags`" - --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" + --form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" + --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" - cat /builds/OpenMW/openmw/cov-int/build-log.txt variables: CC: gcc From c0d097237964e5ba213d16b5a30a9f0fbc2e6138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Mon, 20 Dec 2021 20:20:56 +0000 Subject: [PATCH 1877/2859] Some more tweaks and hook up the .rst file so it will show everything properly once on readthedocs. --- .../reference/modding/custom-models/index.rst | 1 + .../pipeline-blender-collada-static-models.rst | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/source/reference/modding/custom-models/index.rst b/docs/source/reference/modding/custom-models/index.rst index b204dffd99..8b6c5d87f7 100644 --- a/docs/source/reference/modding/custom-models/index.rst +++ b/docs/source/reference/modding/custom-models/index.rst @@ -19,5 +19,6 @@ Below is a quick overview of supported formats, followed by separate articles wi :maxdepth: 1 pipeline-blender-collada + pipeline-blender-collada-static-models pipeline-blender-osgnative pipeline-blender-nif diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst index 72edc6fe1f..a2073f0e56 100644 --- a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst @@ -15,7 +15,7 @@ To use the Blender to OpenMW pipeline via COLLADA, you will need the following. * `OpenMW 0.47 `_ or later * `Blender 2.81 `_ or later. Latest confirmed, working version is Blender 3.0 -* `Better COLLADA Exporter `_ tuned for OpenMW +* `OpenMW COLLADA Exporter `_ * A model you would like to export. In our case, it's a barrel. The Barrel @@ -25,7 +25,7 @@ the `Example Suite repository Date: Mon, 20 Dec 2021 22:13:11 +0000 Subject: [PATCH 1878/2859] Fix signed/unsigned mismatch --- components/crashcatcher/windows_crashmonitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 26ebc0bad3..5774f5ee30 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -164,7 +164,7 @@ namespace Crash if (frozen) { - TerminateProcess(mAppProcessHandle, -1); + TerminateProcess(mAppProcessHandle, 0xDEAD); std::string message = "OpenMW appears to have frozen.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); } From 97396da74c51def4ca84f86107d418cec43deb69 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 20 Dec 2021 22:23:44 +0000 Subject: [PATCH 1879/2859] Get rid of break It might look confusing with the breaks in the switch below --- components/crashcatcher/windows_crashmonitor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 5774f5ee30..2c942e00b7 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -137,9 +137,8 @@ namespace Crash frozen = true; handleCrash(); running = false; - break; } - if (waitApp()) + if (!frozen && waitApp()) { shmLock(); From d15c2922a96f7c414229508767765acfb6c42cf6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 20 Dec 2021 22:24:47 +0000 Subject: [PATCH 1880/2859] Stop monitoring closed windows If it gets repalced, the new one will be watched instead --- .../crashcatcher/windows_crashmonitor.cpp | 20 ++++++++++++++++++- .../crashcatcher/windows_crashmonitor.hpp | 2 ++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 2c942e00b7..4627380356 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -18,6 +18,7 @@ namespace Crash { + std::unordered_map CrashMonitor::smEventHookOwners{}; CrashMonitor::CrashMonitor(HANDLE shmHandle) : mShmHandle(shmHandle) @@ -85,6 +86,10 @@ namespace Crash bool CrashMonitor::isAppFrozen() { + MSG message; + // Allow the event hook callback to run + PeekMessage(&message, nullptr, 0, 0, PM_NOREMOVE); + if (!mAppWindowHandle) { EnumWindows([](HWND handle, LPARAM param) -> BOOL { @@ -102,7 +107,20 @@ namespace Crash }, (LPARAM)this); if (mAppWindowHandle) { - // TODO: use https://devblogs.microsoft.com/oldnewthing/20111026-00/?p=9263 to monitor for the window being destroyed + DWORD processId; + GetWindowThreadProcessId(mAppWindowHandle, &processId); + HWINEVENTHOOK eventHookHandle = SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY, nullptr, + [](HWINEVENTHOOK hWinEventHook, DWORD event, HWND windowHandle, LONG objectId, LONG childId, DWORD eventThread, DWORD eventTime) + { + CrashMonitor& crashMonitor = *smEventHookOwners[hWinEventHook]; + if (event == EVENT_OBJECT_DESTROY && windowHandle == crashMonitor.mAppWindowHandle && objectId == OBJID_WINDOW && childId == INDEXID_CONTAINER) + { + crashMonitor.mAppWindowHandle = nullptr; + smEventHookOwners.erase(hWinEventHook); + UnhookWinEvent(hWinEventHook); + } + }, processId, mAppMainThreadId, WINEVENT_OUTOFCONTEXT); + smEventHookOwners[eventHookHandle] = this; } else return false; diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index 01acd34bdd..031e5b0612 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -33,6 +33,8 @@ private: HANDLE mShmHandle = nullptr; HANDLE mShmMutex = nullptr; + static std::unordered_map smEventHookOwners; + void signalApp() const; bool waitApp() const; From ed3286994ca59abc669b229c62e63f238f1e73d1 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Dec 2021 19:50:58 +0100 Subject: [PATCH 1881/2859] Fix ASAN error: heap-use-after-free ================================================================= ==20931==ERROR: AddressSanitizer: heap-use-after-free on address 0x607000206030 at pc 0x7fc8b0f3a72b bp 0x7ffcee176860 sp 0x7ffcee176008 READ of size 13 at 0x607000206030 thread T0 #0 0x7fc8b0f3a72a in __interceptor_strlen /build/gcc/src/gcc/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:389 #1 0x562e069a0af7 in QString::fromUtf8(char const*, int) /usr/include/qt/QtCore/qstring.h:706 #2 0x562e069a0af7 in Launcher::AdvancedPage::AdvancedPage(Config::GameSettings&, QWidget*) /home/elsid/dev/openmw/apps/launcher/advancedpage.cpp:29 #3 0x562e06959613 in Launcher::MainDialog::createPages() /home/elsid/dev/openmw/apps/launcher/maindialog.cpp:127 #4 0x562e069691d2 in Launcher::MainDialog::setup() /home/elsid/dev/openmw/apps/launcher/maindialog.cpp:228 #5 0x562e06969d88 in Launcher::MainDialog::showFirstRunDialog() /home/elsid/dev/openmw/apps/launcher/maindialog.cpp:188 #6 0x562e06957025 in main /home/elsid/dev/openmw/apps/launcher/main.cpp:35 #7 0x7fc8ad0d9b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24) #8 0x562e0690fced in _start (/home/elsid/dev/openmw/build/gcc/asan/openmw-launcher+0x56ced) 0x607000206030 is located 16 bytes inside of 64-byte region [0x607000206020,0x607000206060) freed by thread T0 here: #0 0x7fc8b0fb3f19 in __interceptor_free /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:127 #1 0x7fc8b0de3388 (/usr/lib/libopenal.so.1+0x40388) previously allocated by thread T0 here: #0 0x7fc8b0fb4fd6 in __interceptor_posix_memalign /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:226 #1 0x7fc8b0e379cb (/usr/lib/libopenal.so.1+0x949cb) SUMMARY: AddressSanitizer: heap-use-after-free /build/gcc/src/gcc/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:389 in __interceptor_strlen Shadow bytes around the buggy address: 0x0c0e80038bb0: 00 00 00 00 00 00 00 00 00 fa fa fa fa fa 00 00 0x0c0e80038bc0: 00 00 00 00 00 00 00 fa fa fa fa fa 00 00 00 00 0x0c0e80038bd0: 00 00 00 00 00 fa fa fa fa fa 00 00 00 00 00 00 0x0c0e80038be0: 00 00 02 fa fa fa fa fa 00 00 00 00 00 00 00 00 0x0c0e80038bf0: 02 fa fa fa fa fa fd fd fd fd fd fd fd fd fa fa =>0x0c0e80038c00: fa fa fa fa fd fd[fd]fd fd fd fd fd fa fa fa fa 0x0c0e80038c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0e80038c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0e80038c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0e80038c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0e80038c50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==20931==ABORTING --- apps/launcher/advancedpage.cpp | 9 +++++---- apps/launcher/utils/openalutil.cpp | 10 +++++----- apps/launcher/utils/openalutil.hpp | 7 ++++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index d09704851a..b9d35b3c95 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -1,6 +1,7 @@ #include "advancedpage.hpp" #include +#include #include #include @@ -20,13 +21,13 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, QWidget setObjectName ("AdvancedPage"); setupUi(this); - for(const char * name : Launcher::enumerateOpenALDevices()) + for(const std::string& name : Launcher::enumerateOpenALDevices()) { - audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); + audioDeviceSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name)); } - for(const char * name : Launcher::enumerateOpenALDevicesHrtf()) + for(const std::string& name : Launcher::enumerateOpenALDevicesHrtf()) { - hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name)); + hrtfProfileSelectorComboBox->addItem(QString::fromStdString(name), QString::fromStdString(name)); } loadSettings(); diff --git a/apps/launcher/utils/openalutil.cpp b/apps/launcher/utils/openalutil.cpp index 53fd704203..469872d158 100644 --- a/apps/launcher/utils/openalutil.cpp +++ b/apps/launcher/utils/openalutil.cpp @@ -9,9 +9,9 @@ #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif -std::vector Launcher::enumerateOpenALDevices() +std::vector Launcher::enumerateOpenALDevices() { - std::vector devlist; + std::vector devlist; const ALCchar *devnames; if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) @@ -22,7 +22,7 @@ std::vector Launcher::enumerateOpenALDevices() { devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); } - + while(devnames && *devnames) { devlist.emplace_back(devnames); @@ -31,9 +31,9 @@ std::vector Launcher::enumerateOpenALDevices() return devlist; } -std::vector Launcher::enumerateOpenALDevicesHrtf() +std::vector Launcher::enumerateOpenALDevicesHrtf() { - std::vector ret; + std::vector ret; ALCdevice *device = alcOpenDevice(nullptr); if(device) diff --git a/apps/launcher/utils/openalutil.hpp b/apps/launcher/utils/openalutil.hpp index 4a84fbae7d..b084dce7ce 100644 --- a/apps/launcher/utils/openalutil.hpp +++ b/apps/launcher/utils/openalutil.hpp @@ -1,7 +1,8 @@ #include +#include namespace Launcher { - std::vector enumerateOpenALDevices(); - std::vector enumerateOpenALDevicesHrtf(); -} \ No newline at end of file + std::vector enumerateOpenALDevices(); + std::vector enumerateOpenALDevicesHrtf(); +} From 55b066d2bdb42b19251aedfe34e25823a9e31a5d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 21 Dec 2021 10:50:28 +0100 Subject: [PATCH 1882/2859] Preserve the original caster if classic reflected absorb spells behaviour is on --- apps/openmw/mwmechanics/activespells.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index d435bd6c44..3fbb347098 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -9,6 +9,8 @@ #include +#include + #include "creaturestats.hpp" #include "spellcasting.hpp" #include "spelleffects.hpp" @@ -236,7 +238,13 @@ namespace MWMechanics if(result == MagicApplicationResult::REFLECTED) { if(!reflected) - reflected = {*spellIt, ptr}; + { + static const bool keepOriginalCaster = Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"); + if(keepOriginalCaster) + reflected = {*spellIt, caster}; + else + reflected = {*spellIt, ptr}; + } auto& reflectedEffect = reflected->mEffects.emplace_back(*it); reflectedEffect.mFlags = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; it = spellIt->mEffects.erase(it); From 766cb52523498b4b3878f33941f8908f52b481fc Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 21 Dec 2021 14:53:31 +0000 Subject: [PATCH 1883/2859] Factorise `add-apt-repository -y ppa:openmw/openmw` --- .gitlab-ci.yml | 5 ----- CI/install_debian_deps.sh | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e7cd3e4cba..ea2d860f07 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,7 +48,6 @@ Clang_Tidy: rules: - if: '$CI_PIPELINE_SOURCE == "schedule"' before_script: - - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic clang-tidy clang script: - CI/before_script.linux.sh @@ -69,7 +68,6 @@ Coverity: rules: - if: $CI_PIPELINE_SOURCE == "schedule" before_script: - - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz @@ -96,7 +94,6 @@ Ubuntu_GCC: cache: key: Ubuntu_GCC.v2 before_script: - - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic variables: CC: gcc @@ -138,7 +135,6 @@ Ubuntu_GCC_Static_Deps: - ccache/ - build/extern/fetched/ before_script: - - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static variables: CI_OPENMW_USE_STATIC_DEPS: 1 @@ -158,7 +154,6 @@ Ubuntu_GCC_Static_Deps_tests: Ubuntu_Clang: extends: .Ubuntu before_script: - - apt-get update; apt-get -y install software-properties-common; add-apt-repository -y ppa:openmw/openmw - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic cache: key: Ubuntu_Clang.v2 diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 370f7f23ed..6364bb2cd1 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -65,4 +65,6 @@ export APT_CACHE_DIR="${PWD}/apt-cache" set -x mkdir -pv "$APT_CACHE_DIR" apt-get update -yqq +apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common +add-apt-repository -y ppa:openmw/openmw apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" From f05cd901cf02694f9561fc01af2bd73fadbaedad Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 21 Dec 2021 23:19:13 +0000 Subject: [PATCH 1884/2859] Show messagebox while OpenMW appears to be frozen If it thaws, the messagebox disappears again. The user can press the Abort button to kill OpenMW and generate a crash dump. --- .../crashcatcher/windows_crashmonitor.cpp | 60 +++++++++++++++++-- .../crashcatcher/windows_crashmonitor.hpp | 7 +++ 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 4627380356..336210391a 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -148,15 +148,23 @@ namespace Crash bool running = true; bool frozen = false; - while (isAppAlive() && running) + while (isAppAlive() && running && !mFreezeAbort) { if (isAppFrozen()) { - frozen = true; - handleCrash(); - running = false; + if (!frozen) + { + showFreezeMessageBox(); + frozen = true; + } + } + else if (frozen) + { + hideFreezeMessageBox(); + frozen = false; } - if (!frozen && waitApp()) + + if (!mFreezeAbort && waitApp()) { shmLock(); @@ -180,6 +188,9 @@ namespace Crash } if (frozen) + hideFreezeMessageBox(); + + if (mFreezeAbort) { TerminateProcess(mAppProcessHandle, 0xDEAD); std::string message = "OpenMW appears to have frozen.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !"; @@ -258,4 +269,43 @@ namespace Crash } } + void CrashMonitor::showFreezeMessageBox() + { + std::thread messageBoxThread([&]() { + SDL_MessageBoxButtonData button = { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort" }; + SDL_MessageBoxData messageBoxData = { + SDL_MESSAGEBOX_ERROR, + nullptr, + "OpenMW appears to have frozen", + "OpenMW appears to have frozen. Press Abort to terminate it and generate a crash dump.\nIf OpenMW hasn't actually frozen, this message box will disappear a within a few seconds of it becoming responsive.", + 1, + &button, + nullptr + }; + + int buttonId; + if (SDL_ShowMessageBox(&messageBoxData, &buttonId) == 0 && buttonId == 0) + mFreezeAbort = true; + }); + + mFreezeMessageBoxThreadId = GetThreadId(messageBoxThread.native_handle()); + messageBoxThread.detach(); + } + + void CrashMonitor::hideFreezeMessageBox() + { + if (!mFreezeMessageBoxThreadId) + return; + + EnumWindows([](HWND handle, LPARAM param) -> BOOL { + CrashMonitor& crashMonitor = *(CrashMonitor*)param; + DWORD processId; + if (GetWindowThreadProcessId(handle, &processId) == crashMonitor.mFreezeMessageBoxThreadId && processId == GetCurrentProcessId()) + PostMessage(handle, WM_CLOSE, 0, 0); + return true; + }, (LPARAM)this); + + mFreezeMessageBoxThreadId = 0; + } + } // namespace Crash diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index 031e5b0612..b520e309b0 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -33,6 +33,9 @@ private: HANDLE mShmHandle = nullptr; HANDLE mShmMutex = nullptr; + DWORD mFreezeMessageBoxThreadId = 0; + std::atomic_bool mFreezeAbort; + static std::unordered_map smEventHookOwners; void signalApp() const; @@ -48,6 +51,10 @@ private: void shmUnlock(); void handleCrash(); + + void showFreezeMessageBox(); + + void hideFreezeMessageBox(); }; } // namespace Crash From ca453910797c07b8dff777f58a25d9c744da7f04 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 21 Dec 2021 21:38:23 +0100 Subject: [PATCH 1885/2859] Only run static jobs when cmake-related things are modified --- .gitlab-ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea2d860f07..987e05da44 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -127,6 +127,13 @@ Ubuntu_GCC_tests_Debug: Ubuntu_GCC_Static_Deps: extends: Ubuntu_GCC + rules: + - if: $CI_PIPELINE_SOURCE == "push" + changes: + - "**/CMakeLists.txt" + - "cmake/**/*" + - "CI/**/*" + - ".gitlab-ci.yml" allow_failure: true cache: key: Ubuntu_GCC_Static_Deps From c9fb4ee2ede75f375d34c236eeadcbe87244b5dc Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 22 Dec 2021 22:43:23 +0000 Subject: [PATCH 1886/2859] Silence `apt-get install` even more --- CI/install_debian_deps.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 6364bb2cd1..945c5f09bf 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -65,6 +65,6 @@ export APT_CACHE_DIR="${PWD}/apt-cache" set -x mkdir -pv "$APT_CACHE_DIR" apt-get update -yqq -apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common +apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common >/dev/null add-apt-repository -y ppa:openmw/openmw -apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" +apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null From b06248a31f52c0c7b808d79dd2b14df5d6bf37e3 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 18 Dec 2021 17:04:55 +0100 Subject: [PATCH 1887/2859] Fix #6502 --- components/resource/scenemanager.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 5f2d78d2ed..72349dadb1 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -1,6 +1,7 @@ #include "scenemanager.hpp" #include +#include #include #include @@ -10,6 +11,7 @@ #include +#include #include #include @@ -472,9 +474,14 @@ namespace Resource osgDB::ReaderWriter::ReadResult readImage(const std::string& filename, const osgDB::Options* options) override { + std::filesystem::path filePath(filename); + if (filePath.is_absolute()) + // It is a hack. Needed because either OSG or libcollada-dom tries to make an absolute path from + // our relative VFS path by adding current working directory. + filePath = std::filesystem::relative(filename, osgDB::getCurrentWorkingDirectory()); try { - return osgDB::ReaderWriter::ReadResult(mImageManager->getImage(filename), osgDB::ReaderWriter::ReadResult::FILE_LOADED); + return osgDB::ReaderWriter::ReadResult(mImageManager->getImage(filePath), osgDB::ReaderWriter::ReadResult::FILE_LOADED); } catch (std::exception& e) { From 27cc7a5172265b861e0b5b050f95df549534a0bc Mon Sep 17 00:00:00 2001 From: myrix Date: Fri, 24 Dec 2021 00:54:00 +0300 Subject: [PATCH 1888/2859] caching dialog keyword search in Store --- apps/openmw/mwbase/world.hpp | 6 + apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 3 +- apps/openmw/mwdialogue/dialoguemanagerimp.hpp | 4 - apps/openmw/mwdialogue/hypertextparser.cpp | 115 ++++++++---------- apps/openmw/mwdialogue/hypertextparser.hpp | 15 +-- apps/openmw/mwdialogue/keywordsearch.hpp | 10 +- apps/openmw/mwworld/esmstore.hpp | 3 + apps/openmw/mwworld/store.cpp | 87 +++++++++---- apps/openmw/mwworld/store.hpp | 42 ++++++- apps/openmw/mwworld/worldimp.hpp | 3 + 10 files changed, 175 insertions(+), 113 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index d1747a2e39..d0ff851274 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -87,6 +87,11 @@ namespace MWWorld typedef std::vector > PtrMovementList; } +namespace MWDialogue +{ + template class KeywordSearch; +} + namespace MWBase { /// \brief Interface for the World (implemented in MWWorld) @@ -150,6 +155,7 @@ namespace MWBase virtual MWWorld::ConstPtr getPlayerConstPtr() const = 0; virtual const MWWorld::ESMStore& getStore() const = 0; + virtual const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() = 0; virtual std::vector& getEsmReader() = 0; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 0698624f32..9800f1b39c 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -43,6 +43,7 @@ #include "../mwmechanics/actorutil.hpp" #include "filter.hpp" +#include "hypertextparser.hpp" namespace MWDialogue { @@ -79,7 +80,7 @@ namespace MWDialogue { std::vector topicIdList; - std::vector hypertext = mHyperTextParser.parseHyperText(text); + std::vector hypertext = HyperTextParser::parseHyperText(text); for (std::vector::iterator tok = hypertext.begin(); tok != hypertext.end(); ++tok) { diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 1fc80db666..57eb74d0a6 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -16,8 +16,6 @@ #include "../mwscript/compilercontext.hpp" -#include "hypertextparser.hpp" - namespace ESM { struct Dialogue; @@ -59,8 +57,6 @@ namespace MWDialogue int mCurrentDisposition; int mPermanentDispositionChange; - HyperTextParser mHyperTextParser; - std::vector parseTopicIdsFromText (const std::string& text); void addTopicsFromText (const std::string& text); diff --git a/apps/openmw/mwdialogue/hypertextparser.cpp b/apps/openmw/mwdialogue/hypertextparser.cpp index eae36d26d1..c1fb6c9c43 100644 --- a/apps/openmw/mwdialogue/hypertextparser.cpp +++ b/apps/openmw/mwdialogue/hypertextparser.cpp @@ -6,89 +6,78 @@ #include "../mwworld/store.hpp" #include "../mwworld/esmstore.hpp" +#include "keywordsearch.hpp" + #include "hypertextparser.hpp" namespace MWDialogue { - std::vector HyperTextParser::parseHyperText(const std::string & text) + namespace HyperTextParser { - std::vector result; - size_t pos_end = std::string::npos, iteration_pos = 0; - for(;;) + std::vector parseHyperText(const std::string & text) { - size_t pos_begin = text.find('@', iteration_pos); - if (pos_begin != std::string::npos) - pos_end = text.find('#', pos_begin); - - if (pos_begin != std::string::npos && pos_end != std::string::npos) - { - if (pos_begin != iteration_pos) - tokenizeKeywords(text.substr(iteration_pos, pos_begin - iteration_pos), result); - - std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1); - result.emplace_back(link, Token::ExplicitLink); - - iteration_pos = pos_end + 1; - } - else + std::vector result; + size_t pos_end = std::string::npos, iteration_pos = 0; + for(;;) { - if (iteration_pos != text.size()) - tokenizeKeywords(text.substr(iteration_pos), result); - break; + size_t pos_begin = text.find('@', iteration_pos); + if (pos_begin != std::string::npos) + pos_end = text.find('#', pos_begin); + + if (pos_begin != std::string::npos && pos_end != std::string::npos) + { + if (pos_begin != iteration_pos) + tokenizeKeywords(text.substr(iteration_pos, pos_begin - iteration_pos), result); + + std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1); + result.emplace_back(link, Token::ExplicitLink); + + iteration_pos = pos_end + 1; + } + else + { + if (iteration_pos != text.size()) + tokenizeKeywords(text.substr(iteration_pos), result); + break; + } } - } - - return result; - } - void HyperTextParser::tokenizeKeywords(const std::string & text, std::vector & tokens) - { - const MWWorld::Store & dialogs = - MWBase::Environment::get().getWorld()->getStore().get(); + return result; + } - if (dialogs.getModPoint() != mKeywordModPoint) + void tokenizeKeywords(const std::string & text, std::vector & tokens) { - mKeywordSearch.clear(); - - std::vector keywordList; - keywordList.reserve(dialogs.getSize()); - for (const auto& it : dialogs) - keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); - sort(keywordList.begin(), keywordList.end()); + const auto& keywordSearch = + MWBase::Environment::get().getWorld()->getDialogIdKeywordSearch(); - for (const auto& it : keywordList) - mKeywordSearch.seed(it, 0 /*unused*/); + std::vector::Match> matches; + keywordSearch.highlightKeywords(text.begin(), text.end(), matches); - mKeywordModPoint = dialogs.getModPoint(); + for (std::vector::Match>::const_iterator it = matches.begin(); it != matches.end(); ++it) + { + tokens.emplace_back(std::string(it->mBeg, it->mEnd), Token::ImplicitKeyword); + } } - std::vector::Match> matches; - mKeywordSearch.highlightKeywords(text.begin(), text.end(), matches); - - for (std::vector::Match>::const_iterator it = matches.begin(); it != matches.end(); ++it) + size_t removePseudoAsterisks(std::string & phrase) { - tokens.emplace_back(std::string(it->mBeg, it->mEnd), Token::ImplicitKeyword); - } - } + size_t pseudoAsterisksCount = 0; - size_t HyperTextParser::removePseudoAsterisks(std::string & phrase) - { - size_t pseudoAsterisksCount = 0; - - if( !phrase.empty() ) - { - std::string::reverse_iterator rit = phrase.rbegin(); - - const char specialPseudoAsteriskCharacter = 127; - while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) + if( !phrase.empty() ) { - pseudoAsterisksCount++; - ++rit; + std::string::reverse_iterator rit = phrase.rbegin(); + + const char specialPseudoAsteriskCharacter = 127; + while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter ) + { + pseudoAsterisksCount++; + ++rit; + } } - } - phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount); + phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount); - return pseudoAsterisksCount; + return pseudoAsterisksCount; + } } } diff --git a/apps/openmw/mwdialogue/hypertextparser.hpp b/apps/openmw/mwdialogue/hypertextparser.hpp index 1e903d5618..4ae0474c42 100644 --- a/apps/openmw/mwdialogue/hypertextparser.hpp +++ b/apps/openmw/mwdialogue/hypertextparser.hpp @@ -4,17 +4,10 @@ #include #include -#include "keywordsearch.hpp" - namespace MWDialogue { - class HyperTextParser + namespace HyperTextParser { - uint64_t mKeywordModPoint; - KeywordSearch mKeywordSearch; - - public: - struct Token { enum Type @@ -31,14 +24,12 @@ namespace MWDialogue Type mType; }; - HyperTextParser() : mKeywordModPoint(0) {} - // In translations (at least Russian) the links are marked with @#, so // it should be a function to parse it std::vector parseHyperText(const std::string & text); void tokenizeKeywords(const std::string & text, std::vector & tokens); - static size_t removePseudoAsterisks(std::string & phrase); - }; + size_t removePseudoAsterisks(std::string & phrase); + } } #endif diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 39599457ef..3f932084fe 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -74,13 +74,13 @@ public: return left.mBeg < right.mBeg; } - void highlightKeywords (Point beg, Point end, std::vector& out) + void highlightKeywords (Point beg, Point end, std::vector& out) const { std::vector matches; for (Point i = beg; i != end; ++i) { // check first character - typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); + typename Entry::childen_t::const_iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); // no match, on to next character if (candidate == mRoot.mChildren.end ()) @@ -91,11 +91,11 @@ public: // 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< typename std::pair > candidates; + std::vector< typename std::pair > candidates; while ((j + 1) != end) { - typename Entry::childen_t::iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); + typename Entry::childen_t::const_iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); if (next == candidate->second.mChildren.end ()) { @@ -116,7 +116,7 @@ public: // shorter candidates will be added to the vector first. however, we want to check against longer candidates first std::reverse(candidates.begin(), candidates.end()); - for (typename std::vector< std::pair >::iterator it = candidates.begin(); + for (typename std::vector< std::pair >::iterator it = candidates.begin(); it != candidates.end(); ++it) { candidate = it->second; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 8582a1daca..855dd062e9 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -284,6 +284,9 @@ namespace MWWorld /// Actors with the same ID share spells, abilities, etc. /// @return The shared spell list to use for this actor and whether or not it has already been initialized. std::pair, bool> getSpellList(const std::string& id) const; + + const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() { + return mDialogs.getDialogIdKeywordSearch(); } }; template <> diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 5c696e5556..a06bf843cb 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -77,13 +77,12 @@ namespace MWWorld template Store::Store() - : mModPoint(1) { } template Store::Store(const Store& orig) - : mStatic(orig.mStatic), mModPoint(orig.mModPoint + 1) + : mStatic(orig.mStatic) { } @@ -94,8 +93,6 @@ namespace MWWorld assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); mDynamic.clear(); - - mModPoint++; } template @@ -165,8 +162,6 @@ namespace MWWorld if (inserted.second) mShared.push_back(&inserted.first->second); - mModPoint++; - return RecordId(record.mId, isDeleted); } template @@ -218,9 +213,6 @@ namespace MWWorld T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); - - mModPoint++; - return ptr; } template @@ -230,9 +222,6 @@ namespace MWWorld T *ptr = &result.first->second; if (result.second) mShared.push_back(ptr); - - mModPoint++; - return ptr; } template @@ -253,8 +242,6 @@ namespace MWWorld ++sharedIter; } mStatic.erase(it); - - mModPoint++; } return true; @@ -272,9 +259,6 @@ namespace MWWorld for (auto it = mDynamic.begin(); it != mDynamic.end(); ++it) { mShared.push_back(&it->second); } - - mModPoint++; - return true; } template @@ -997,8 +981,11 @@ namespace MWWorld // Dialogue //========================================================================= + Store::Store() + : mKeywordSearchModFlag(true) + { + } - template<> void Store::setUp() { // DialInfos marked as deleted are kept during the loading phase, so that the linked list @@ -1014,10 +1001,45 @@ namespace MWWorld // TODO: if we require this behaviour, maybe we should move it to the place that requires it std::sort(mShared.begin(), mShared.end(), [](const ESM::Dialogue* l, const ESM::Dialogue* r) -> bool { return l->mId < r->mId; }); - mModPoint++; + mKeywordSearchModFlag = true; + } + + const ESM::Dialogue *Store::search(const std::string &id) const + { + typename Static::const_iterator it = mStatic.find(id); + if (it != mStatic.end()) + return &(it->second); + + return nullptr; + } + + const ESM::Dialogue *Store::find(const std::string &id) const + { + const ESM::Dialogue *ptr = search(id); + if (ptr == nullptr) + { + std::stringstream msg; + msg << ESM::Dialogue::getRecordType() << " '" << id << "' not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + typename Store::iterator Store::begin() const + { + return mShared.begin(); + } + + typename Store::iterator Store::end() const + { + return mShared.end(); + } + + size_t Store::getSize() const + { + return mShared.size(); } - template <> inline RecordId Store::load(ESM::ESMReader &esm) { // The original letter case of a dialogue ID is saved, because it's printed ESM::Dialogue dialogue; @@ -1037,20 +1059,39 @@ namespace MWWorld dialogue.mId = found->second.mId; } - mModPoint++; + mKeywordSearchModFlag = true; return RecordId(dialogue.mId, isDeleted); } - template<> bool Store::eraseStatic(const std::string &id) { if (mStatic.erase(id)) - mModPoint++; + mKeywordSearchModFlag = true; return true; } + const MWDialogue::KeywordSearch& Store::getDialogIdKeywordSearch() + { + if (mKeywordSearchModFlag) + { + mKeywordSearch.clear(); + + std::vector keywordList; + keywordList.reserve(getSize()); + for (const auto& it : *this) + keywordList.push_back(Misc::StringUtils::lowerCase(it.mId)); + sort(keywordList.begin(), keywordList.end()); + + for (const auto& it : keywordList) + mKeywordSearch.seed(it, 0 /*unused*/); + + mKeywordSearchModFlag = false; + } + + return mKeywordSearch; + } } template class MWWorld::Store; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index b4ac1056a8..58ac1e6780 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -11,6 +11,8 @@ #include #include +#include "../mwdialogue/keywordsearch.hpp" + namespace ESM { struct Land; @@ -154,13 +156,10 @@ namespace MWWorld /// @par mShared usually preserves the record order as it came from the content files (this /// is relevant for the spell autocalc code and selection order /// for heads/hairs in the character creation) - /// @warning ESM::Dialogue Store currently implements a sorted order for unknown reasons. std::vector mShared; typedef std::unordered_map Dynamic; Dynamic mDynamic; - uint64_t mModPoint; - friend class ESMStore; public: @@ -205,8 +204,6 @@ namespace MWWorld RecordId load(ESM::ESMReader &esm) override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override; RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) override; - - uint64_t getModPoint() const { return mModPoint; } }; template <> @@ -444,6 +441,41 @@ namespace MWWorld iterator end() const; }; + template <> + class Store : public StoreBase + { + typedef std::unordered_map Static; + Static mStatic; + /// @par mShared usually preserves the record order as it came from the content files (this + /// is relevant for the spell autocalc code and selection order + /// for heads/hairs in the character creation) + /// @warning ESM::Dialogue Store currently implements a sorted order for unknown reasons. + std::vector mShared; + + bool mKeywordSearchModFlag; + MWDialogue::KeywordSearch mKeywordSearch; + + public: + Store(); + + typedef SharedIterator iterator; + + void setUp() override; + + const ESM::Dialogue *search(const std::string &id) const; + const ESM::Dialogue *find(const std::string &id) const; + + iterator begin() const; + iterator end() const; + + size_t getSize() const override; + + bool eraseStatic(const std::string &id) override; + + RecordId load(ESM::ESMReader &esm) override; + + const MWDialogue::KeywordSearch& getDialogIdKeywordSearch(); + }; } //end namespace diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 84d97ce37c..bea7710a53 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -236,6 +236,9 @@ namespace MWWorld const MWWorld::ESMStore& getStore() const override; + const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() override { + return mStore.getDialogIdKeywordSearch(); } + std::vector& getEsmReader() override; LocalScripts& getLocalScripts() override; From 9c7835e27fe812becf82627d2bf6bb0132045317 Mon Sep 17 00:00:00 2001 From: myrix Date: Fri, 24 Dec 2021 02:04:26 +0300 Subject: [PATCH 1889/2859] explicit instantiation error fix --- apps/openmw/mwworld/store.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index a06bf843cb..1dc3ebf622 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1107,7 +1107,7 @@ template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; From 26dfce11148a421edd49918a1bc6ed382bb809a7 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Thu, 23 Dec 2021 23:38:57 +0300 Subject: [PATCH 1890/2859] Rehash key group and morph loading (bug #6517) --- CHANGELOG.md | 1 + components/nif/data.cpp | 8 ++--- components/nif/nifkey.hpp | 71 ++++++++++++++------------------------- 3 files changed, 30 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38cd1efca5..e4094f0ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ Bug #6451: Weapon summoned from Cast When Used item will have the name "None" Bug #6473: Strings from NIF should be parsed only to first null terminator Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime + Bug #6517: Rotations for KeyframeData in NIFs should be optional Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record diff --git a/components/nif/data.cpp b/components/nif/data.cpp index cac924733d..1b6d302d68 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -428,7 +428,7 @@ void NiMorphData::read(NIFStream *nif) for(int i = 0;i < morphCount;i++) { mMorphs[i].mKeyFrames = std::make_shared(); - mMorphs[i].mKeyFrames->read(nif, true, /*morph*/true); + mMorphs[i].mKeyFrames->read(nif, /*morph*/true); nif->getVector3s(mMorphs[i].mVertices, vertCount); } } @@ -445,9 +445,9 @@ void NiKeyframeData::read(NIFStream *nif) mXRotations = std::make_shared(); mYRotations = std::make_shared(); mZRotations = std::make_shared(); - mXRotations->read(nif, true); - mYRotations->read(nif, true); - mZRotations->read(nif, true); + mXRotations->read(nif); + mYRotations->read(nif); + mZRotations->read(nif); } mTranslations = std::make_shared(); mTranslations->read(nif); diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index 91869ff849..f1b4f94d00 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -48,93 +48,72 @@ struct KeyMapT { using ValueType = T; using KeyType = KeyT; - unsigned int mInterpolationType = InterpolationType_Linear; + unsigned int mInterpolationType = InterpolationType_Unknown; MapType mKeys; //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) - void read(NIFStream *nif, bool force = false, bool morph = false) + void read(NIFStream *nif, bool morph = false) { assert(nif); - mInterpolationType = InterpolationType_Unknown; - if (morph && nif->getVersion() >= NIFStream::generateVersion(10,1,0,106)) nif->getString(); // Frame name size_t count = nif->getUInt(); - if (count == 0 && !force && !morph) - return; - - if (morph && nif->getVersion() > NIFStream::generateVersion(10,1,0,0)) - { - if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,104) && - nif->getVersion() <= NIFStream::generateVersion(20,1,0,2) && nif->getBethVersion() < 10) - nif->getFloat(); // Legacy weight - return; - } - mKeys.clear(); - - mInterpolationType = nif->getUInt(); + if (count != 0 || morph) + mInterpolationType = nif->getUInt(); KeyType key = {}; - NIFStream &nifReference = *nif; - if (mInterpolationType == InterpolationType_Linear - || mInterpolationType == InterpolationType_Constant) + if (mInterpolationType == InterpolationType_Linear || mInterpolationType == InterpolationType_Constant) { - for(size_t i = 0;i < count;i++) + for (size_t i = 0;i < count;i++) { float time = nif->getFloat(); - readValue(nifReference, key); + readValue(*nif, key); mKeys[time] = key; } } else if (mInterpolationType == InterpolationType_Quadratic) { - for(size_t i = 0;i < count;i++) + for (size_t i = 0;i < count;i++) { float time = nif->getFloat(); - readQuadratic(nifReference, key); + readQuadratic(*nif, key); mKeys[time] = key; } } else if (mInterpolationType == InterpolationType_TBC) { - for(size_t i = 0;i < count;i++) + for (size_t i = 0;i < count;i++) { float time = nif->getFloat(); - readTBC(nifReference, key); + readTBC(*nif, key); mKeys[time] = key; } } - //XYZ keys aren't actually read here. - //data.hpp sees that the last type read was InterpolationType_XYZ and: - // Eats a floating point number, then - // Re-runs the read function 3 more times. - // When it does that it's reading in a bunch of InterpolationType_Linear keys, not InterpolationType_XYZ. - else if(mInterpolationType == InterpolationType_XYZ) - { - //Don't try to read XYZ keys into the wrong part - if ( count != 1 ) - { - std::stringstream error; - error << "XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: " - << count; - nif->file->fail(error.str()); - } - } - else if (mInterpolationType == InterpolationType_Unknown) + else if (mInterpolationType == InterpolationType_XYZ) { - if (count != 0) - nif->file->fail("Interpolation type 0 doesn't work with keys"); + //XYZ keys aren't actually read here. + //data.cpp sees that the last type read was InterpolationType_XYZ and: + // Eats a floating point number, then + // Re-runs the read function 3 more times. + // When it does that it's reading in a bunch of InterpolationType_Linear keys, not InterpolationType_XYZ. } - else + else if (count != 0 || morph) { std::stringstream error; error << "Unhandled interpolation type: " << mInterpolationType; nif->file->fail(error.str()); } + + if (morph && nif->getVersion() > NIFStream::generateVersion(10,1,0,0)) + { + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,104) && + nif->getVersion() <= NIFStream::generateVersion(20,1,0,2) && nif->getBethVersion() < 10) + nif->getFloat(); // Legacy weight + } } private: From 88fc038cebcc25106ef24026c0b29346e0298f3c Mon Sep 17 00:00:00 2001 From: myrix Date: Fri, 24 Dec 2021 12:04:26 +0300 Subject: [PATCH 1891/2859] with mutable, without World and ESMStore interface change --- apps/openmw/mwbase/world.hpp | 6 ------ apps/openmw/mwdialogue/hypertextparser.cpp | 2 +- apps/openmw/mwworld/esmstore.hpp | 3 --- apps/openmw/mwworld/store.cpp | 2 +- apps/openmw/mwworld/store.hpp | 6 +++--- apps/openmw/mwworld/worldimp.hpp | 3 --- 6 files changed, 5 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index d0ff851274..d1747a2e39 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -87,11 +87,6 @@ namespace MWWorld typedef std::vector > PtrMovementList; } -namespace MWDialogue -{ - template class KeywordSearch; -} - namespace MWBase { /// \brief Interface for the World (implemented in MWWorld) @@ -155,7 +150,6 @@ namespace MWBase virtual MWWorld::ConstPtr getPlayerConstPtr() const = 0; virtual const MWWorld::ESMStore& getStore() const = 0; - virtual const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() = 0; virtual std::vector& getEsmReader() = 0; diff --git a/apps/openmw/mwdialogue/hypertextparser.cpp b/apps/openmw/mwdialogue/hypertextparser.cpp index c1fb6c9c43..89a42bf2ea 100644 --- a/apps/openmw/mwdialogue/hypertextparser.cpp +++ b/apps/openmw/mwdialogue/hypertextparser.cpp @@ -48,7 +48,7 @@ namespace MWDialogue void tokenizeKeywords(const std::string & text, std::vector & tokens) { const auto& keywordSearch = - MWBase::Environment::get().getWorld()->getDialogIdKeywordSearch(); + MWBase::Environment::get().getWorld()->getStore().get().getDialogIdKeywordSearch(); std::vector::Match> matches; keywordSearch.highlightKeywords(text.begin(), text.end(), matches); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 855dd062e9..8582a1daca 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -284,9 +284,6 @@ namespace MWWorld /// Actors with the same ID share spells, abilities, etc. /// @return The shared spell list to use for this actor and whether or not it has already been initialized. std::pair, bool> getSpellList(const std::string& id) const; - - const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() { - return mDialogs.getDialogIdKeywordSearch(); } }; template <> diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 1dc3ebf622..c767bd669a 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1072,7 +1072,7 @@ namespace MWWorld return true; } - const MWDialogue::KeywordSearch& Store::getDialogIdKeywordSearch() + const MWDialogue::KeywordSearch& Store::getDialogIdKeywordSearch() const { if (mKeywordSearchModFlag) { diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 58ac1e6780..1ec51ad5fd 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -452,8 +452,8 @@ namespace MWWorld /// @warning ESM::Dialogue Store currently implements a sorted order for unknown reasons. std::vector mShared; - bool mKeywordSearchModFlag; - MWDialogue::KeywordSearch mKeywordSearch; + mutable bool mKeywordSearchModFlag; + mutable MWDialogue::KeywordSearch mKeywordSearch; public: Store(); @@ -474,7 +474,7 @@ namespace MWWorld RecordId load(ESM::ESMReader &esm) override; - const MWDialogue::KeywordSearch& getDialogIdKeywordSearch(); + const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() const; }; } //end namespace diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index bea7710a53..84d97ce37c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -236,9 +236,6 @@ namespace MWWorld const MWWorld::ESMStore& getStore() const override; - const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() override { - return mStore.getDialogIdKeywordSearch(); } - std::vector& getEsmReader() override; LocalScripts& getLocalScripts() override; From 58697d98b22a686b9b8e16c2b1fbc0c4fa7dd374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Fri, 24 Dec 2021 18:16:34 +0000 Subject: [PATCH 1892/2859] Fix links in the collada documentation. Exporter now points to OpenMW's official repo and the example suite link now points to the actual place it needs to. --- .../pipeline-blender-collada-static-models.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst index a2073f0e56..5cb336a305 100644 --- a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst @@ -14,14 +14,14 @@ Requirements To use the Blender to OpenMW pipeline via COLLADA, you will need the following. * `OpenMW 0.47 `_ or later -* `Blender 2.81 `_ or later. Latest confirmed, working version is Blender 3.0 -* `OpenMW COLLADA Exporter `_ +* `Blender 2.83 `_ or later. Latest confirmed, working version is Blender 3.0 +* `OpenMW COLLADA Exporter `_ * A model you would like to export. In our case, it's a barrel. The Barrel ******** The barrel shown in this tutorial, and its revelant files, are available from -the `Example Suite repository `_. +the `Example Suite repository `_. This should be useful for further study of how to set up a static prop in case the tutorial is not clear on any particular thing. From c1f59b1221cdad439909bfedbc742a7228eee485 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 24 Dec 2021 23:17:50 +0100 Subject: [PATCH 1893/2859] Automatically drop workaround when the format is next updated --- apps/essimporter/converter.cpp | 5 ++--- apps/openmw/mwclass/creature.cpp | 6 +----- apps/openmw/mwclass/npc.cpp | 5 +---- apps/openmw/mwmechanics/creaturestats.cpp | 6 +++--- apps/openmw/mwmechanics/creaturestats.hpp | 2 -- components/esm/creaturestats.cpp | 19 +++++++++++++++++-- components/esm/creaturestats.hpp | 1 + 7 files changed, 25 insertions(+), 19 deletions(-) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 874b936baf..6e79e27f18 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -2,7 +2,6 @@ #include #include -#include // INT_MIN #include @@ -371,7 +370,7 @@ namespace ESSImport if (cellref.mHasACDT) convertACDT(cellref.mACDT, objstate.mCreatureStats); else - objstate.mCreatureStats.mGoldPool = INT_MIN; // HACK: indicates no ACDT + objstate.mCreatureStats.mMissingACDT = true; if (cellref.mHasACSC) convertACSC(cellref.mACSC, objstate.mCreatureStats); convertNpcData(cellref, objstate.mNpcStats); @@ -414,7 +413,7 @@ namespace ESSImport if (cellref.mHasACDT) convertACDT(cellref.mACDT, objstate.mCreatureStats); else - objstate.mCreatureStats.mGoldPool = INT_MIN; // HACK: indicates no ACDT + objstate.mCreatureStats.mMissingACDT = true; if (cellref.mHasACSC) convertACSC(cellref.mACSC, objstate.mCreatureStats); convertCREC(crecIt->second, objstate); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 0b3ffa924f..03b7cfb069 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,7 +1,5 @@ #include "creature.hpp" -#include // INT_MIN - #include #include #include @@ -758,9 +756,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - // FIXME: the use of mGoldPool can be replaced with another flag the next time - // the save file format is changed - if (creatureState.mCreatureStats.mGoldPool == INT_MIN) + if (creatureState.mCreatureStats.mMissingACDT) ensureCustomData(ptr); else { diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5d50ba558f..c46e3c0534 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,7 +1,6 @@ #include "npc.hpp" #include -#include // INT_MIN #include #include @@ -1302,9 +1301,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - // FIXME: the use of mGoldPool can be replaced with another flag the next time - // the save file format is changed - if (npcState.mCreatureStats.mGoldPool == INT_MIN) + if (npcState.mCreatureStats.mMissingACDT) ensureCustomData(ptr); else // Create a CustomData, but don't fill it from ESM records (not needed) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index d832c47c87..c99f87e833 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -1,7 +1,6 @@ #include "creaturestats.hpp" #include -#include #include #include @@ -558,12 +557,13 @@ namespace MWMechanics state.mHasAiSettings = true; for (int i=0; i<4; ++i) mAiSettings[i].writeState (state.mAiSettings[i]); + + state.mMissingACDT = false; } void CreatureStats::readState (const ESM::CreatureStats& state) { - // HACK: using mGoldPool as an indicator for lack of ACDT during .ess import - if (state.mGoldPool != INT_MIN) + if (!state.mMissingACDT) { for (int i=0; i void ESM::CreatureStats::load (ESMReader &esm) { @@ -163,6 +166,13 @@ void ESM::CreatureStats::load (ESMReader &esm) mCorprusSpells[id] = stats; } + if(esm.getFormat() <= 18) + mMissingACDT = mGoldPool == std::numeric_limits::min(); + else + { + mMissingACDT = false; + esm.getHNOT(mMissingACDT, "NOAC"); + } } void ESM::CreatureStats::save (ESMWriter &esm) const @@ -173,8 +183,10 @@ void ESM::CreatureStats::save (ESMWriter &esm) const for (int i=0; i<3; ++i) mDynamic[i].save (esm); - if (mGoldPool) - esm.writeHNT ("GOLD", mGoldPool); + if (ESM::SavedGame::sCurrentFormat <= 18 && mMissingACDT) + esm.writeHNT("GOLD", std::numeric_limits::min()); + else if(mGoldPool) + esm.writeHNT("GOLD", mGoldPool); if (mTradeTime.mDay != 0 || mTradeTime.mHour != 0) esm.writeHNT ("TIME", mTradeTime); @@ -246,6 +258,8 @@ void ESM::CreatureStats::save (ESMWriter &esm) const for (int i=0; i<4; ++i) mAiSettings[i].save(esm); } + if(ESM::SavedGame::sCurrentFormat > 18 && mMissingACDT) + esm.writeHNT("NOAC", mMissingACDT); } void ESM::CreatureStats::blank() @@ -274,4 +288,5 @@ void ESM::CreatureStats::blank() mDeathAnimation = -1; mLevel = 1; mCorprusSpells.clear(); + mMissingACDT = false; } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 651b126d0e..7b261e3dd3 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -85,6 +85,7 @@ namespace ESM signed char mDeathAnimation; ESM::TimeStamp mTimeOfDeath; int mLevel; + bool mMissingACDT; std::map mCorprusSpells; SpellState mSpells; From 0e29a760d8080f2f63a582a283e3d7d3b2e201c1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 26 Dec 2021 02:09:14 +0000 Subject: [PATCH 1894/2859] Tidy up includes --- components/crashcatcher/windows_crashcatcher.cpp | 3 ++- components/crashcatcher/windows_crashmonitor.cpp | 3 ++- components/crashcatcher/windows_crashmonitor.hpp | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp index eea6ac40ad..e9eb60e845 100644 --- a/components/crashcatcher/windows_crashcatcher.cpp +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -1,10 +1,11 @@ +#include "windows_crashcatcher.hpp" + #include #include #include #include #include -#include "windows_crashcatcher.hpp" #include "windows_crashmonitor.hpp" #include "windows_crashshm.hpp" #include diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 336210391a..50d3fc08a1 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -1,3 +1,5 @@ +#include "windows_crashmonitor.hpp" + #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include @@ -12,7 +14,6 @@ #include #include "windows_crashcatcher.hpp" -#include "windows_crashmonitor.hpp" #include "windows_crashshm.hpp" #include diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index b520e309b0..871346d292 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -1,7 +1,9 @@ #ifndef WINDOWS_CRASHMONITOR_HPP #define WINDOWS_CRASHMONITOR_HPP -#include +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include namespace Crash { From fa05b0b96cbdaae575c5b5f65696965139d63042 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 26 Dec 2021 02:10:37 +0000 Subject: [PATCH 1895/2859] Include Should fix compilation on CI --- components/crashcatcher/windows_crashmonitor.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index 871346d292..4028362836 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -5,6 +5,8 @@ #define WIN32_LEAN_AND_MEAN #include +#include + namespace Crash { From a2a45ccdb3cff1c2dde007f47dd731a9f7ce182d Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 26 Dec 2021 13:26:35 +0000 Subject: [PATCH 1896/2859] have coverity use clang instead of gcc --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 987e05da44..4a3f27bc4a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -68,7 +68,7 @@ Coverity: rules: - if: $CI_PIPELINE_SOURCE == "schedule" before_script: - - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity + - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic coverity - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz script: @@ -83,8 +83,8 @@ Coverity: --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" - cat /builds/OpenMW/openmw/cov-int/build-log.txt variables: - CC: gcc - CXX: g++ + CC: clang + CXX: clang++ artifacts: paths: [] expire_in: 1 minute From c61967a316da3a1e38a3824e2c6ea285e1f47c8a Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 26 Dec 2021 14:51:40 +0100 Subject: [PATCH 1897/2859] Use `-O0` when compiling with coverity --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4a3f27bc4a..06148d03d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -85,6 +85,7 @@ Coverity: variables: CC: clang CXX: clang++ + CXXFLAGS: -O0 artifacts: paths: [] expire_in: 1 minute From 9b2978f14390ad3a6fbd112d8ceee34d10042a2b Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 26 Dec 2021 17:56:19 +0300 Subject: [PATCH 1898/2859] Rehash morph loading rehashing --- components/nif/nifkey.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index f1b4f94d00..7736ab7ad9 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -101,7 +101,7 @@ struct KeyMapT { // Re-runs the read function 3 more times. // When it does that it's reading in a bunch of InterpolationType_Linear keys, not InterpolationType_XYZ. } - else if (count != 0 || morph) + else if (count != 0) { std::stringstream error; error << "Unhandled interpolation type: " << mInterpolationType; From ac747f02f32b71d65e0211f375c04ed0e0af70e8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 26 Dec 2021 15:27:25 +0000 Subject: [PATCH 1899/2859] Don't teleport NPCs to unknown cells --- .../mwscript/transformationextensions.cpp | 19 +++++++---- apps/openmw/mwworld/worldimp.cpp | 8 +++++ components/compiler/lineparser.cpp | 33 +++---------------- 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 292965fd94..8a159a5685 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -355,7 +355,8 @@ namespace MWScript if (ptr.getContainerStore()) return; - if (ptr == MWMechanics::getPlayer()) + bool isPlayer = ptr == MWMechanics::getPlayer(); + if (isPlayer) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } @@ -378,17 +379,21 @@ namespace MWScript } catch(std::exception&) { - // cell not found, move to exterior instead (vanilla PositionCell compatibility) + // cell not found, move to exterior instead if moving the player (vanilla PositionCell compatibility) const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); - int cx,cy; - MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); - store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); if(!cell) { - std::string error = "Warning: PositionCell: unknown interior cell (" + cellID + "), moving to exterior instead"; + std::string error = "Warning: PositionCell: unknown interior cell (" + cellID + ")"; + if(isPlayer) + error += ", moving to exterior instead"; runtime.getContext().report (error); Log(Debug::Warning) << error; + if(!isPlayer) + return; } + int cx,cy; + MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); + store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); } if(store) { @@ -400,7 +405,7 @@ namespace MWScript // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. - if(ptr != MWMechanics::getPlayer()) + if(!isPlayer) zRot = zRot/60.0f; rot.z() = osg::DegreesToRadians(zRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,rot); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a44646ef37..325e048958 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -550,6 +550,14 @@ namespace MWWorld const ESM::Cell *cell = mStore.get().searchExtByName (cellName); if (cell) return cell; + // treat "Wilderness" like an empty string + static const std::string defaultName = mStore.get().find("sDefaultCellname")->mValue.getString(); + if (Misc::StringUtils::ciEqual(cellName, defaultName)) + { + cell = mStore.get().searchExtByName(""); + if (cell) + return cell; + } // didn't work -> now check for regions for (const ESM::Region ®ion : mStore.get()) diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 2e398618a3..3ad8c5bbe2 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -12,7 +12,6 @@ #include "generator.hpp" #include "extensions.hpp" #include "declarationparser.hpp" -#include "exception.hpp" namespace Compiler { @@ -259,33 +258,11 @@ namespace Compiler mExplicit.clear(); } - try - { - // workaround for broken positioncell instructions. - /// \todo add option to disable this - std::unique_ptr errorDowngrade (nullptr); - if (Misc::StringUtils::lowerCase (loc.mLiteral)=="positioncell") - errorDowngrade = std::make_unique (getErrorHandler()); - - std::vector code; - int optionals = mExprParser.parseArguments (argumentType, scanner, code, keyword); - mCode.insert (mCode.end(), code.begin(), code.end()); - extensions->generateInstructionCode (keyword, mCode, mLiterals, - mExplicit, optionals); - } - catch (const SourceException&) - { - // Ignore argument exceptions for positioncell. - /// \todo add option to disable this - if (Misc::StringUtils::lowerCase (loc.mLiteral)=="positioncell") - { - SkipParser skip (getErrorHandler(), getContext()); - scanner.scan (skip); - return false; - } - - throw; - } + std::vector code; + int optionals = mExprParser.parseArguments (argumentType, scanner, code, keyword); + mCode.insert (mCode.end(), code.begin(), code.end()); + extensions->generateInstructionCode (keyword, mCode, mLiterals, + mExplicit, optionals); mState = EndState; return true; From 7fe5351f55454dd39c0fc02f21fa1fc141cb99ee Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 26 Dec 2021 18:04:20 +0000 Subject: [PATCH 1900/2859] Fix compilation error on windows: cannot convert from 'std::filesystem::path'... --- components/resource/scenemanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 72349dadb1..4f0658e033 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -481,7 +481,8 @@ namespace Resource filePath = std::filesystem::relative(filename, osgDB::getCurrentWorkingDirectory()); try { - return osgDB::ReaderWriter::ReadResult(mImageManager->getImage(filePath), osgDB::ReaderWriter::ReadResult::FILE_LOADED); + return osgDB::ReaderWriter::ReadResult(mImageManager->getImage(filePath.string()), + osgDB::ReaderWriter::ReadResult::FILE_LOADED); } catch (std::exception& e) { From 81c9ef947ff8c5309d3b4366f6a4beaac34eb81d Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 26 Dec 2021 19:50:19 +0000 Subject: [PATCH 1901/2859] Resovles #6519 Do not display effects duration for ingredient --- CHANGELOG.md | 1 + apps/openmw/mwgui/tooltips.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4094f0ec2..601bbeaeba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,7 @@ Bug #6473: Strings from NIF should be parsed only to first null terminator Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime Bug #6517: Rotations for KeyframeData in NIFs should be optional + Bug #6519: Effects tooltips for ingredients work incorrectly Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 84d8b65592..0a2fd4c448 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -493,6 +493,7 @@ namespace MWGui std::vector effectItems; int flag = info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0; flag |= info.isIngredient ? Widgets::MWEffectList::EF_NoMagnitude : 0; + flag |= info.isIngredient ? Widgets::MWEffectList::EF_Constant : 0; effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, flag); totalSize.height += coord.top-6; totalSize.width = std::max(totalSize.width, coord.width); From debdcf29539d0ed9edecdb87aa07bf3e6979fa31 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 27 Dec 2021 00:47:33 +0100 Subject: [PATCH 1902/2859] Don't touch base stats when turning into a werewolf --- CHANGELOG.md | 1 + .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwworld/player.cpp | 63 +++++++++++-------- apps/openmw/mwworld/player.hpp | 4 +- components/esm/player.cpp | 48 +++++++++++--- components/esm/player.hpp | 5 +- components/esm/savedgame.cpp | 2 +- 7 files changed, 83 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d239717d0a..6127c24154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Bug #6324: Special Slave Companions: Can't buy the slave companions Bug #6326: Detect Enchantment/Key should detect items in unresolved containers Bug #6327: Blocking roots the character in place + Bug #6333: Werewolf stat changes should be implemented as damage/fortifications Bug #6343: Magic projectile speed doesn't take race weight into account Bug #6347: PlaceItem/PlaceItemCell/PlaceAt should work with levelled creatures Bug #6354: SFX abruptly cut off after crossing max distance; implement soft fading of sound effects diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fa5aaa82ef..e0d2da497b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1865,8 +1865,8 @@ namespace MWMechanics { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::NpcStats &stats = actor.getClass().getNpcStats(actor); - - stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->mValue.getInteger()); + auto& skill = stats.getSkill(ESM::Skill::Acrobatics); + skill.setModifier(gmst.find("fWerewolfAcrobatics")->mValue.getFloat() - skill.getModified()); } void MechanicsManager::cleanupSummonedCreature(const MWWorld::Ptr &caster, int creatureActorId) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 4687a4eddd..270889a23e 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -58,9 +58,9 @@ namespace MWWorld MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); for (int i=0; i health = creatureStats.getDynamic(0); - creatureStats.setHealth(int(health.getBase() / gmst.find("fWereWolfHealth")->mValue.getFloat())); + creatureStats.setHealth(health.getBase() / gmst.find("fWereWolfHealth")->mValue.getFloat()); for (int i=0; i health = creatureStats.getDynamic(0); - creatureStats.setHealth(int(health.getBase() * gmst.find("fWereWolfHealth")->mValue.getFloat())); + creatureStats.setHealth(health.getBase() * gmst.find("fWereWolfHealth")->mValue.getFloat()); for(size_t i = 0;i < ESM::Attribute::Length;++i) { // Oh, Bethesda. It's "Intelligence". @@ -90,7 +99,7 @@ namespace MWWorld ESM::Attribute::sAttributeNames[i]); MWMechanics::AttributeValue value = npcStats.getAttribute(i); - value.setBase(int(gmst.find(name)->mValue.getFloat())); + value.setModifier(gmst.find(name)->mValue.getFloat() - value.getModified()); npcStats.setAttribute(i, value); } @@ -104,9 +113,8 @@ namespace MWWorld std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : ESM::Skill::sSkillNames[i]); - MWMechanics::SkillValue value = npcStats.getSkill(i); - value.setBase(int(gmst.find(name)->mValue.getFloat())); - npcStats.setSkill(i, value); + MWMechanics::SkillValue& value = npcStats.getSkill(i); + value.setModifier(gmst.find(name)->mValue.getFloat() - value.getModified()); } } @@ -316,14 +324,12 @@ namespace MWWorld for (int i=0; iapplyWerewolfAcrobatics(getPlayer()); + } } getPlayer().getClass().getCreatureStats(getPlayer()).getAiSequence().clear(); diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index a7e42d95e1..1a9744e8a3 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -53,8 +53,8 @@ namespace MWWorld PreviousItems mPreviousItems; // Saved stats prior to becoming a werewolf - MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; - MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; + float mSaveSkills[ESM::Skill::Length]; + float mSaveAttributes[ESM::Attribute::Length]; bool mAttackingOrSpell; bool mJumping; diff --git a/components/esm/player.cpp b/components/esm/player.cpp index e2e9219e22..028a042809 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -44,13 +44,43 @@ void ESM::Player::load (ESMReader &esm) checkPrevItems = false; } - bool intFallback = esm.getFormat() < 11; - if (esm.hasMoreSubs()) + if(esm.getFormat() < 19) { - for (int i=0; i attribute; + attribute.load(esm, intFallback); + if (clearModified) + attribute.mMod = 0.f; + mSaveAttributes[i] = attribute.mBase + attribute.mMod - attribute.mDamage; + if (mObject.mNpcStats.mIsWerewolf) + mObject.mCreatureStats.mAttributes[i] = attribute; + } + for (int i=0; i skill; + skill.load(esm, intFallback); + if (clearModified) + skill.mMod = 0.f; + mSaveSkills[i] = skill.mBase + skill.mMod - skill.mDamage; + if (mObject.mNpcStats.mIsWerewolf) + { + if(i == ESM::Skill::Acrobatics) + mSetWerewolfAcrobatics = mObject.mNpcStats.mSkills[i].mBase != skill.mBase; + mObject.mNpcStats.mSkills[i] = skill; + } + } + } + } + else + { + mSetWerewolfAcrobatics = false; + esm.getHNT(mSaveAttributes, "WWAT"); + esm.getHNT(mSaveSkills, "WWSK"); } } @@ -79,8 +109,6 @@ void ESM::Player::save (ESMWriter &esm) const esm.writeHNString ("PREV", it->second); } - for (int i=0; i mSaveAttributes[ESM::Attribute::Length]; - StatState mSaveSkills[ESM::Skill::Length]; + float mSaveAttributes[ESM::Attribute::Length]; + float mSaveSkills[ESM::Skill::Length]; typedef std::map PreviousItems; // previous equipped items, needed for bound spells PreviousItems mPreviousItems; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 4ce0876bb8..6cecf26489 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 18; +int ESM::SavedGame::sCurrentFormat = 19; void ESM::SavedGame::load (ESMReader &esm) { From ba281a0da059a736d28576acda2cb6298ed664e2 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 27 Dec 2021 16:08:00 +0000 Subject: [PATCH 1903/2859] Fix coverity issues --- apps/navmeshtool/navmesh.cpp | 2 +- apps/navmeshtool/worldspacedata.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 6b8150f1c0..4187197947 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -73,7 +73,7 @@ namespace NavMeshTool public: std::atomic_size_t mExpected {0}; - explicit NavMeshTileConsumer(NavMeshDb db) + explicit NavMeshTileConsumer(NavMeshDb&& db) : mDb(std::move(db)) , mTransaction(mDb.startTransaction()) , mNextTileId(mDb.getMaxTileId() + 1) diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index b88b967cf5..d05a8fe3f0 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -246,7 +246,7 @@ namespace NavMeshTool if (!exterior && !processInteriorCells) { - Log(Debug::Info) << "Skipped " << (exterior ? "exterior" : "interior") + Log(Debug::Info) << "Skipped interior" << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; continue; } From dc946d0a4738808f58cf63c26cfc33b7ff809829 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 27 Dec 2021 22:30:25 +0100 Subject: [PATCH 1904/2859] Fix Dagoth Ur's Fire Shield not being visible --- apps/openmw/mwmechanics/activespells.cpp | 3 +-- apps/openmw/mwmechanics/spelleffects.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 3fbb347098..0c781e81bd 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -218,8 +218,7 @@ namespace MWMechanics return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId(); }) != mSpells.end()) continue; - ActiveSpellParams params(*slot, enchantment, slotIndex, ptr); - mSpells.emplace_back(params); + const ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{*slot, enchantment, slotIndex, ptr}); for(const auto& effect : params.mEffects) MWMechanics::playEffects(ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index aef6b26f19..8121f7584d 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -914,8 +914,8 @@ MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorl oldMagnitude = effect.mMagnitude; else { - if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) - playEffects(target, *magicEffect); + if(spellParams.getType() != ESM::ActiveSpells::Type_Enchantment) + playEffects(target, *magicEffect, spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary); if(effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0 && caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); } From adefd0c9cad72875f400f2d98e5040e464261b91 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 28 Dec 2021 11:55:28 +0000 Subject: [PATCH 1905/2859] Get rid of travis-ci --- .travis.yml | 80 ----------------------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1322dfca1b..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,80 +0,0 @@ -language: cpp -branches: - only: - - master - - /openmw-.*$/ -cache: ccache -addons: - apt: - sources: - - sourceline: 'ppa:openmw/openmw' - packages: [ - # Dev - build-essential, cmake, clang-tools-9, ccache, - # Boost - libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, - # FFmpeg - libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev, - # Audio, Video and Misc. deps - libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev, - # The other ones from OpenMW ppa - libbullet-dev, libopenscenegraph-dev, libmygui-dev - ] -matrix: - include: - - name: OpenMW (all) on MacOS 10.15 with Xcode 11.6 - os: osx - osx_image: xcode11.6 - - name: OpenMW (all) on Ubuntu Focal with GCC - os: linux - dist: focal - - name: OpenMW (tests only) on Ubuntu Focal with GCC - os: linux - dist: focal - env: - - BUILD_TESTS_ONLY: 1 - - name: OpenMW (openmw) on Ubuntu Focal with Clang's Static Analysis - os: linux - dist: focal - env: - - MATRIX_EVAL="CC=clang-9 && CXX=clang++-9" - - ANALYZE="scan-build-9 --force-analyze-debug-code --use-cc clang-9 --use-c++ clang++-9" - compiler: clang - -before_install: - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi - - ./CI/before_install.${TRAVIS_OS_NAME}.sh -before_script: - - ccache -z - - ./CI/before_script.${TRAVIS_OS_NAME}.sh -script: - - cd ./build - - ${ANALYZE} make -j3; - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi - - if [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - - cd "${TRAVIS_BUILD_DIR}" - - ccache -s -deploy: - provider: script - script: ./CI/deploy.osx.sh - skip_cleanup: true - on: - branch: master - condition: "$TRAVIS_EVENT_TYPE = cron && $TRAVIS_OS_NAME = osx" - repo: OpenMW/openmw -notifications: - email: - if: repository_slug = OpenMW/openmw AND branch = master - recipients: - - corrmage+travis-ci@gmail.com - on_success: change - on_failure: always - irc: - if: repository_slug = OpenMW/openmw AND branch = master - channels: - - "irc.libera.chat#openmw" - on_success: change - on_failure: always - use_notice: true From 6f870a464be4a3b77706efcf0951510ca05c015e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 29 Dec 2021 12:34:12 +0000 Subject: [PATCH 1906/2859] Replace magic numbers with enums --- CHANGELOG.md | 1 + components/esm/loadmgef.cpp | 28 ++++++++++++---------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d239717d0a..02d86f8802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime Bug #6517: Rotations for KeyframeData in NIFs should be optional Bug #6519: Effects tooltips for ingredients work incorrectly + Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index badbbb2133..7aff249aeb 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -281,14 +281,12 @@ short MagicEffect::getResistanceEffect(short effect) effects[DisintegrateArmor] = Sanctuary; effects[DisintegrateWeapon] = Sanctuary; - for (int i=0; i<5; ++i) - effects[DrainAttribute+i] = ResistMagicka; - for (int i=0; i<5; ++i) - effects[DamageAttribute+i] = ResistMagicka; - for (int i=0; i<5; ++i) - effects[AbsorbAttribute+i] = ResistMagicka; - for (int i=0; i<10; ++i) - effects[WeaknessToFire+i] = ResistMagicka; + for (int i = DrainAttribute; i <= DamageSkill; ++i) + effects[i] = ResistMagicka; + for (int i = AbsorbAttribute; i <= AbsorbSkill; ++i) + effects[i] = ResistMagicka; + for (int i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i) + effects[i] = ResistMagicka; effects[Burden] = ResistMagicka; effects[Charm] = ResistMagicka; @@ -326,14 +324,12 @@ short MagicEffect::getWeaknessEffect(short effect) static std::map effects; if (effects.empty()) { - for (int i=0; i<5; ++i) - effects[DrainAttribute+i] = WeaknessToMagicka; - for (int i=0; i<5; ++i) - effects[DamageAttribute+i] = WeaknessToMagicka; - for (int i=0; i<5; ++i) - effects[AbsorbAttribute+i] = WeaknessToMagicka; - for (int i=0; i<10; ++i) - effects[WeaknessToFire+i] = WeaknessToMagicka; + for (int i = DrainAttribute; i <= DamageSkill; ++i) + effects[i] = WeaknessToMagicka; + for (int i = AbsorbAttribute; i <= AbsorbSkill; ++i) + effects[i] = WeaknessToMagicka; + for (int i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i) + effects[i] = WeaknessToMagicka; effects[Burden] = WeaknessToMagicka; effects[Charm] = WeaknessToMagicka; From 63bf4bf868ec0a415631b8285a8ef6e689e80c7b Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Wed, 29 Dec 2021 15:11:48 +0100 Subject: [PATCH 1907/2859] Solve 2 bugs in projectile movement simulation: - properly initialize mSimulationPosition in the constructor. Unlucky thread scheduling can cause processHits to be called before the first simulation run, causing the projectile to vanish to whatever value the variable happens to contains. - don't continue moving the projectile after a hit. The position would continue to be updated to some senseless value. --- apps/openmw/mwphysics/mtphysics.cpp | 3 ++- apps/openmw/mwphysics/projectile.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2f7e3b5995..5fcda2dfa0 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -185,7 +185,8 @@ namespace } void operator()(MWPhysics::ProjectileSimulation& sim) const { - MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld); + if (sim.first->isActive()) + MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld); } }; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 9f8962d5e6..6176738043 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -33,6 +33,7 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f mPosition = position; mPreviousPosition = position; + mSimulationPosition = position; setCaster(caster); const int collisionMask = CollisionType_World | CollisionType_HeightMap | From 14a330609fad0164ed4036832836d34d04b26c38 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 8 Dec 2021 20:44:10 +0000 Subject: [PATCH 1908/2859] ShaderVisitor improvements * Adds comments explaining the less-than-obvious aspects. * Adds comments explaining what to do when adding new stuff. * Some fixes caused by those comments not historically existing. * Add a TODO comment to something which may catch fire in the future. --- components/shader/shadervisitor.cpp | 130 +++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 21 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 2063546034..107665369c 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -29,6 +29,15 @@ namespace Shader { + /** + * Miniature version of osg::StateSet used to track state added by the shader visitor which should be ignored when + * it's applied a second time, and removed when shaders are removed. + * Actual StateAttributes aren't kept as they're recoverable from the StateSet this is attached to - we just want + * the TypeMemberPair as that uniquely identifies which of those StateAttributes it was we're tracking. + * Not all StateSet features have been added yet - we implement an equivalently-named method to each of the StateSet + * methods called in createProgram, and implement new ones as they're needed. + * When expanding tracking to cover new things, ensure they're accounted for in ensureFFP. + */ class AddedState : public osg::Object { public: @@ -45,7 +54,6 @@ namespace Shader void addUniform(const std::string& name) { mUniforms.emplace(name); } void setMode(osg::StateAttribute::GLMode mode) { mModes.emplace(mode); } void setAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { mAttributes.emplace(typeMemberPair); } - void setTextureMode(int unit, osg::StateAttribute::GLMode mode) { mTextureModes[unit].emplace(mode); } void setAttribute(const osg::StateAttribute* attribute) { @@ -63,6 +71,25 @@ namespace Shader template void setAttributeAndModes(osg::ref_ptr attribute) { setAttributeAndModes(attribute.get()); } + void setTextureMode(unsigned int unit, osg::StateAttribute::GLMode mode) { mTextureModes[unit].emplace(mode); } + void setTextureAttribute(int unit, osg::StateAttribute::TypeMemberPair typeMemberPair) { mTextureAttributes[unit].emplace(typeMemberPair); } + + void setTextureAttribute(unsigned int unit, const osg::StateAttribute* attribute) + { + mTextureAttributes[unit].emplace(attribute->getTypeMemberPair()); + } + template + void setTextureAttribute(unsigned int unit, osg::ref_ptr attribute) { setTextureAttribute(unit, attribute.get()); } + + void setTextureAttributeAndModes(unsigned int unit, const osg::StateAttribute* attribute) + { + setTextureAttribute(unit, attribute); + InterrogateModesHelper helper(this, unit); + attribute->getModeUsage(helper); + } + template + void setTextureAttributeAndModes(unsigned int unit, osg::ref_ptr attribute) { setTextureAttributeAndModes(unit, attribute.get()); } + bool hasUniform(const std::string& name) { return mUniforms.count(name); } bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } bool hasAttribute(const osg::StateAttribute::TypeMemberPair &typeMemberPair) { return mAttributes.count(typeMemberPair); } @@ -77,10 +104,11 @@ namespace Shader } const std::set& getAttributes() { return mAttributes; } + const std::unordered_map>& getTextureAttributes() { return mTextureAttributes; } bool empty() { - return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureModes.empty(); + return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureModes.empty() && mTextureAttributes.empty(); } META_Object(Shader, AddedState) @@ -89,20 +117,26 @@ namespace Shader class InterrogateModesHelper : public osg::StateAttribute::ModeUsage { public: - InterrogateModesHelper(AddedState* tracker) : mTracker(tracker) {} + InterrogateModesHelper(AddedState* tracker, unsigned int textureUnit = 0) + : mTracker(tracker) + , mTextureUnit(textureUnit) + {} void usesMode(osg::StateAttribute::GLMode mode) override { mTracker->setMode(mode); } - void usesTextureMode(osg::StateAttribute::GLMode mode) override {} + void usesTextureMode(osg::StateAttribute::GLMode mode) override { mTracker->setTextureMode(mTextureUnit, mode); } private: AddedState* mTracker; + unsigned int mTextureUnit; }; using ModeSet = std::unordered_set; + using AttributeSet = std::set; std::unordered_set mUniforms; ModeSet mModes; - std::set mAttributes; - std::unordered_map mTextureModes; + AttributeSet mAttributes; + std::unordered_map mTextureModes; + std::unordered_map mTextureAttributes; }; ShaderVisitor::ShaderRequirements::ShaderRequirements() @@ -229,6 +263,7 @@ namespace Shader if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) mRequirements.back().mShaderRequired = true; + // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); if (!texAttributes.empty()) @@ -242,6 +277,8 @@ namespace Shader const osg::StateAttribute *attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { + // If textures ever get removed in createProgram, expand this to check we're operating on main texture attribute list + // rather than the removed list if (addedState && addedState->hasTextureMode(unit, GL_TEXTURE_2D)) continue; @@ -462,6 +499,22 @@ namespace Shader return; } + /** + * The shader visitor is supposed to be idempotent and undoable. + * That means we need to back up state we've removed (so it can be restored and/or considered by further + * applications of the visitor) and track which state we added (so it can be removed and/or ignored by further + * applications of the visitor). + * Before editing writableStateSet in a way that explicitly removes state or might overwrite existing state, it + * should be copied to removedState, another StateSet, unless it's there already or was added by a previous + * application of the visitor (is in previousAddedState). + * If it's a new class of state that's not already handled by ReinstateRemovedStateVisitor::apply, make sure to + * add handling there. + * Similarly, any time new state is added to writableStateSet, the equivalent method should be called on + * addedState. + * If that method doesn't exist yet, implement it - we don't use a full StateSet as we only need to check + * existence, not equality, and don't need to actually get the value as we can get it from writableStateSet + * instead. + */ osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) @@ -486,7 +539,10 @@ namespace Shader } if (defineMap["diffuseMap"] == "0") + { writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); + addedState->addUniform("useDiffuseMapForShadowAlpha"); + } defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; @@ -495,7 +551,6 @@ namespace Shader defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); - // back up removed state in case recreateShaders gets rid of the shader later osg::ref_ptr removedState; if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); @@ -570,7 +625,7 @@ namespace Shader osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - addedState->setAttribute(depth); + addedState->setAttributeAndModes(depth); writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); addedState->addUniform("particleSize"); @@ -579,23 +634,11 @@ namespace Shader addedState->addUniform("opaqueDepthTex"); writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); - addedState->setTextureMode(2, GL_TEXTURE_2D); + addedState->setTextureAttributeAndModes(2, mOpaqueDepthTex); } defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; - if (!addedState->empty()) - { - // user data is normally shallow copied so shared with the original stateset - osg::ref_ptr writableUserData; - if (mAllowedToModifyStateSets) - writableUserData = writableStateSet->getOrCreateUserDataContainer(); - else - writableUserData = getWritableUserDataContainer(*writableStateSet); - - updateAddedState(*writableUserData, addedState); - } - std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -615,6 +658,18 @@ namespace Shader addedState->addUniform(texIt->second); } } + + if (!addedState->empty()) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getOrCreateUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + updateAddedState(*writableUserData, addedState); + } } void ShaderVisitor::ensureFFP(osg::Node& node) @@ -627,6 +682,18 @@ namespace Shader else writableStateSet = getWritableStateSet(node); + /** + * We might have been using shaders temporarily with the node (e.g. if a GlowUpdater applied a temporary + * environment map for a temporary enchantment). + * We therefore need to remove any state doing so added, and restore any that it removed. + * This is kept track of in createProgram in the StateSet's userdata. + * If new classes of state get added, handling it here is required - not all StateSet features are implemented + * in AddedState yet as so far they've not been necessary. + * Removed state requires no particular special handling as it's dealt with by merging StateSets. + * We don't need to worry about state in writableStateSet having the OVERRIDE flag as if it's in both, it's also + * in addedState, and gets removed first. + */ + // user data is normally shallow copied so shared with the original stateset - we'll need to copy before edits osg::ref_ptr writableUserData; @@ -661,6 +728,23 @@ namespace Shader // We don't have access to the function to do that, and can't call removeAttribute with an iterator for (const auto& [type, member] : addedState->getAttributes()) writableStateSet->removeAttribute(type, member); + + for (unsigned int unit = 0; unit < writableStateSet->getTextureModeList().size(); ++unit) + { + for (auto itr = writableStateSet->getTextureModeList()[unit].begin(); itr != writableStateSet->getTextureModeList()[unit].end();) + { + if (addedState->hasTextureMode(unit, itr->first)) + writableStateSet->getTextureModeList()[unit].erase(itr++); + else + ++itr; + } + } + + for (const auto& [unit, attributeList] : addedState->getTextureAttributes()) + { + for (const auto& [type, member] : attributeList) + writableStateSet->removeTextureAttribute(unit, type); + } } @@ -833,6 +917,10 @@ namespace Shader void ReinstateRemovedStateVisitor::apply(osg::Node& node) { + // TODO: this may eventually need to remove added state. + // If so, we can migrate from explicitly copying removed state to just calling osg::StateSet::merge. + // Not everything is transferred from removedState yet - implement more when createProgram starts marking more + // as removed. if (node.getStateSet()) { osg::ref_ptr removedState = getRemovedState(*node.getStateSet()); From f91a5499d353f0d8ffc018268380afcf74c626af Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 26 Dec 2021 19:12:04 +0100 Subject: [PATCH 1909/2859] Add extern/i18n.lua --- extern/i18n.lua/CMakeLists.txt | 17 ++ extern/i18n.lua/LICENSE | 22 +++ extern/i18n.lua/README.md | 164 ++++++++++++++++ extern/i18n.lua/i18n/init.lua | 188 ++++++++++++++++++ extern/i18n.lua/i18n/interpolate.lua | 60 ++++++ extern/i18n.lua/i18n/plural.lua | 280 +++++++++++++++++++++++++++ extern/i18n.lua/i18n/variants.lua | 49 +++++ extern/i18n.lua/i18n/version.lua | 1 + files/CMakeLists.txt | 1 + 9 files changed, 782 insertions(+) create mode 100644 extern/i18n.lua/CMakeLists.txt create mode 100644 extern/i18n.lua/LICENSE create mode 100644 extern/i18n.lua/README.md create mode 100644 extern/i18n.lua/i18n/init.lua create mode 100644 extern/i18n.lua/i18n/interpolate.lua create mode 100644 extern/i18n.lua/i18n/plural.lua create mode 100644 extern/i18n.lua/i18n/variants.lua create mode 100644 extern/i18n.lua/i18n/version.lua diff --git a/extern/i18n.lua/CMakeLists.txt b/extern/i18n.lua/CMakeLists.txt new file mode 100644 index 0000000000..aec4447470 --- /dev/null +++ b/extern/i18n.lua/CMakeLists.txt @@ -0,0 +1,17 @@ +if (NOT DEFINED OPENMW_RESOURCES_ROOT) + message( FATAL_ERROR "OPENMW_RESOURCES_ROOT is not set" ) +endif() + +# Copy resource files into the build directory +set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(DDIRRELATIVE resources/lua_libs/i18n) + +set(I18N_LUA_FILES + i18n/init.lua + i18n/interpolate.lua + i18n/plural.lua + i18n/variants.lua + i18n/version.lua +) + +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${I18N_LUA_FILES}") diff --git a/extern/i18n.lua/LICENSE b/extern/i18n.lua/LICENSE new file mode 100644 index 0000000000..ddf484685b --- /dev/null +++ b/extern/i18n.lua/LICENSE @@ -0,0 +1,22 @@ +MIT License Terms +================= + +Copyright (c) 2012 Enrique García Cota. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extern/i18n.lua/README.md b/extern/i18n.lua/README.md new file mode 100644 index 0000000000..8b3271c321 --- /dev/null +++ b/extern/i18n.lua/README.md @@ -0,0 +1,164 @@ +i18n.lua +======== + +[![Build Status](https://travis-ci.org/kikito/i18n.lua.png?branch=master)](https://travis-ci.org/kikito/i18n.lua) + +A very complete i18n lib for Lua + +Description +=========== + +``` lua +i18n = require 'i18n' + +-- loading stuff +i18n.set('en.welcome', 'welcome to this program') +i18n.load({ + en = { + good_bye = "good-bye!", + age_msg = "your age is %{age}.", + phone_msg = { + one = "you have one new message.", + other = "you have %{count} new messages." + } + } +}) +i18n.loadFile('path/to/your/project/i18n/de.lua') -- load German language file +i18n.loadFile('path/to/your/project/i18n/fr.lua') -- load French language file +… -- section 'using language files' below describes structure of files + +-- setting the translation context +i18n.setLocale('en') -- English is the default locale anyway + +-- getting translations +i18n.translate('welcome') -- Welcome to this program +i18n('welcome') -- Welcome to this program +i18n('age_msg', {age = 18}) -- Your age is 18. +i18n('phone_msg', {count = 1}) -- You have one new message. +i18n('phone_msg', {count = 2}) -- You have 2 new messages. +i18n('good_bye') -- Good-bye! + +``` + +Interpolation +============= + +You can interpolate variables in 3 different ways: + +``` lua +-- the most usual one +i18n.set('variables', 'Interpolating variables: %{name} %{age}') +i18n('variables', {name='john', 'age'=10}) -- Interpolating variables: john 10 + +i18n.set('lua', 'Traditional Lua way: %d %s') +i18n('lua', {1, 'message'}) -- Traditional Lua way: 1 message + +i18n.set('combined', 'Combined: %.q %.d %.o') +i18n('combined', {name='john', 'age'=10}) -- Combined: john 10 12k +``` + + + +Pluralization +============= + +This lib implements the [unicode.org plural rules](http://cldr.unicode.org/index/cldr-spec/plural-rules). Just set the locale you want to use and it will deduce the appropiate pluralization rules: + +``` lua +i18n = require 'i18n' + +i18n.load({ + en = { + msg = { + one = "one message", + other = "%{count} messages" + } + }, + ru = { + msg = { + one = "1 сообщение", + few = "%{count} сообщения", + many = "%{count} сообщений", + other = "%{count} сообщения" + } + } +}) + +i18n('msg', {count = 1}) -- one message +i18n.setLocale('ru') +i18n('msg', {count = 5}) -- 5 сообщений +``` + +The appropiate rule is chosen by finding the 'root' of the locale used: for example if the current locale is 'fr-CA', the 'fr' rules will be applied. + +If the provided functions are not enough (i.e. invented languages) it's possible to specify a custom pluralization function in the second parameter of setLocale. This function must return 'one', 'few', 'other', etc given a number. + +Fallbacks +========= + +When a value is not found, the lib has several fallback mechanisms: + +* First, it will look in the current locale's parents. For example, if the locale was set to 'en-US' and the key 'msg' was not found there, it will be looked over in 'en'. +* Second, if the value is not found in the locale ancestry, a 'fallback locale' (by default: 'en') can be used. If the fallback locale has any parents, they will be looked over too. +* Third, if all the locales have failed, but there is a param called 'default' on the provided data, it will be used. +* Otherwise the translation will return nil. + +The parents of a locale are found by splitting the locale by its hyphens. Other separation characters (spaces, underscores, etc) are not supported. + +Using language files +==================== + +It might be a good idea to store each translation in a different file. This is supported via the 'i18n.loadFile' directive: + +``` lua +… +i18n.loadFile('path/to/your/project/i18n/de.lua') -- German translation +i18n.loadFile('path/to/your/project/i18n/en.lua') -- English translation +i18n.loadFile('path/to/your/project/i18n/fr.lua') -- French translation +… +``` + +The German language file 'de.lua' should read: + +``` lua +return { + de = { + good_bye = "Auf Wiedersehen!", + age_msg = "Ihr Alter beträgt %{age}.", + phone_msg = { + one = "Sie haben eine neue Nachricht.", + other = "Sie haben %{count} neue Nachrichten." + } + } +} +``` + +If desired, you can also store all translations in one single file (eg. 'translations.lua'): + +``` lua +return { + de = { + good_bye = "Auf Wiedersehen!", + age_msg = "Ihr Alter beträgt %{age}.", + phone_msg = { + one = "Sie haben eine neue Nachricht.", + other = "Sie haben %{count} neue Nachrichten." + } + }, + fr = { + good_bye = "Au revoir !", + age_msg = "Vous avez %{age} ans.", + phone_msg = { + one = "Vous avez une noveau message.", + other = "Vous avez %{count} noveaux messages." + } + }, + … +} +``` + +Specs +===== +This project uses [busted](https://github.com/Olivine-Labs/busted) for its specs. If you want to run the specs, you will have to install it first. Then just execute the following from the root inspect folder: + + busted diff --git a/extern/i18n.lua/i18n/init.lua b/extern/i18n.lua/i18n/init.lua new file mode 100644 index 0000000000..6bcccd0572 --- /dev/null +++ b/extern/i18n.lua/i18n/init.lua @@ -0,0 +1,188 @@ +local i18n = {} + +local store +local locale +local pluralizeFunction +local defaultLocale = 'en' +local fallbackLocale = defaultLocale + +local currentFilePath = (...):gsub("%.init$","") + +local plural = require(currentFilePath .. '.plural') +local interpolate = require(currentFilePath .. '.interpolate') +local variants = require(currentFilePath .. '.variants') +local version = require(currentFilePath .. '.version') + +i18n.plural, i18n.interpolate, i18n.variants, i18n.version, i18n._VERSION = + plural, interpolate, variants, version, version + +-- private stuff + +local function dotSplit(str) + local fields, length = {},0 + str:gsub("[^%.]+", function(c) + length = length + 1 + fields[length] = c + end) + return fields, length +end + +local function isPluralTable(t) + return type(t) == 'table' and type(t.other) == 'string' +end + +local function isPresent(str) + return type(str) == 'string' and #str > 0 +end + +local function assertPresent(functionName, paramName, value) + if isPresent(value) then return end + + local msg = "i18n.%s requires a non-empty string on its %s. Got %s (a %s value)." + error(msg:format(functionName, paramName, tostring(value), type(value))) +end + +local function assertPresentOrPlural(functionName, paramName, value) + if isPresent(value) or isPluralTable(value) then return end + + local msg = "i18n.%s requires a non-empty string or plural-form table on its %s. Got %s (a %s value)." + error(msg:format(functionName, paramName, tostring(value), type(value))) +end + +local function assertPresentOrTable(functionName, paramName, value) + if isPresent(value) or type(value) == 'table' then return end + + local msg = "i18n.%s requires a non-empty string or table on its %s. Got %s (a %s value)." + error(msg:format(functionName, paramName, tostring(value), type(value))) +end + +local function assertFunctionOrNil(functionName, paramName, value) + if value == nil or type(value) == 'function' then return end + + local msg = "i18n.%s requires a function (or nil) on param %s. Got %s (a %s value)." + error(msg:format(functionName, paramName, tostring(value), type(value))) +end + +local function defaultPluralizeFunction(count) + return plural.get(variants.root(i18n.getLocale()), count) +end + +local function pluralize(t, data) + assertPresentOrPlural('interpolatePluralTable', 't', t) + data = data or {} + local count = data.count or 1 + local plural_form = pluralizeFunction(count) + return t[plural_form] +end + +local function treatNode(node, data) + if type(node) == 'string' then + return interpolate(node, data) + elseif isPluralTable(node) then + return interpolate(pluralize(node, data), data) + end + return node +end + +local function recursiveLoad(currentContext, data) + local composedKey + for k,v in pairs(data) do + composedKey = (currentContext and (currentContext .. '.') or "") .. tostring(k) + assertPresent('load', composedKey, k) + assertPresentOrTable('load', composedKey, v) + if type(v) == 'string' then + i18n.set(composedKey, v) + else + recursiveLoad(composedKey, v) + end + end +end + +local function localizedTranslate(key, loc, data) + local path, length = dotSplit(loc .. "." .. key) + local node = store + + for i=1, length do + node = node[path[i]] + if not node then return nil end + end + + return treatNode(node, data) +end + +-- public interface + +function i18n.set(key, value) + assertPresent('set', 'key', key) + assertPresentOrPlural('set', 'value', value) + + local path, length = dotSplit(key) + local node = store + + for i=1, length-1 do + key = path[i] + node[key] = node[key] or {} + node = node[key] + end + + local lastKey = path[length] + node[lastKey] = value +end + +function i18n.translate(key, data) + assertPresent('translate', 'key', key) + + data = data or {} + local usedLocale = data.locale or locale + + local fallbacks = variants.fallbacks(usedLocale, fallbackLocale) + for i=1, #fallbacks do + local value = localizedTranslate(key, fallbacks[i], data) + if value then return value end + end + + return data.default +end + +function i18n.setLocale(newLocale, newPluralizeFunction) + assertPresent('setLocale', 'newLocale', newLocale) + assertFunctionOrNil('setLocale', 'newPluralizeFunction', newPluralizeFunction) + locale = newLocale + pluralizeFunction = newPluralizeFunction or defaultPluralizeFunction +end + +function i18n.setFallbackLocale(newFallbackLocale) + assertPresent('setFallbackLocale', 'newFallbackLocale', newFallbackLocale) + fallbackLocale = newFallbackLocale +end + +function i18n.getFallbackLocale() + return fallbackLocale +end + +function i18n.getLocale() + return locale +end + +function i18n.reset() + store = {} + plural.reset() + i18n.setLocale(defaultLocale) + i18n.setFallbackLocale(defaultLocale) +end + +function i18n.load(data) + recursiveLoad(nil, data) +end + +function i18n.loadFile(path) + local chunk = assert(loadfile(path)) + local data = chunk() + i18n.load(data) +end + +setmetatable(i18n, {__call = function(_, ...) return i18n.translate(...) end}) + +i18n.reset() + +return i18n diff --git a/extern/i18n.lua/i18n/interpolate.lua b/extern/i18n.lua/i18n/interpolate.lua new file mode 100644 index 0000000000..c6bb242f05 --- /dev/null +++ b/extern/i18n.lua/i18n/interpolate.lua @@ -0,0 +1,60 @@ +local unpack = unpack or table.unpack -- lua 5.2 compat + +local FORMAT_CHARS = { c=1, d=1, E=1, e=1, f=1, g=1, G=1, i=1, o=1, u=1, X=1, x=1, s=1, q=1, ['%']=1 } + +-- matches a string of type %{age} +local function interpolateValue(string, variables) + return string:gsub("(.?)%%{%s*(.-)%s*}", + function (previous, key) + if previous == "%" then + return + else + return previous .. tostring(variables[key]) + end + end) +end + +-- matches a string of type %.d +local function interpolateField(string, variables) + return string:gsub("(.?)%%<%s*(.-)%s*>%.([cdEefgGiouXxsq])", + function (previous, key, format) + if previous == "%" then + return + else + return previous .. string.format("%" .. format, variables[key] or "nil") + end + end) +end + +local function escapePercentages(string) + return string:gsub("(%%)(.?)", function(_, char) + if FORMAT_CHARS[char] then + return "%" .. char + else + return "%%" .. char + end + end) +end + +local function unescapePercentages(string) + return string:gsub("(%%%%)(.?)", function(_, char) + if FORMAT_CHARS[char] then + return "%" .. char + else + return "%%" .. char + end + end) +end + +local function interpolate(pattern, variables) + variables = variables or {} + local result = pattern + result = interpolateValue(result, variables) + result = interpolateField(result, variables) + result = escapePercentages(result) + result = string.format(result, unpack(variables)) + result = unescapePercentages(result) + return result +end + +return interpolate diff --git a/extern/i18n.lua/i18n/plural.lua b/extern/i18n.lua/i18n/plural.lua new file mode 100644 index 0000000000..bb99804ee8 --- /dev/null +++ b/extern/i18n.lua/i18n/plural.lua @@ -0,0 +1,280 @@ +local plural = {} +local defaultFunction = nil +-- helper functions + +local function assertPresentString(functionName, paramName, value) + if type(value) ~= 'string' or #value == 0 then + local msg = "Expected param %s of function %s to be a string, but got %s (a value of type %s) instead" + error(msg:format(paramName, functionName, tostring(value), type(value))) + end +end + +local function assertNumber(functionName, paramName, value) + if type(value) ~= 'number' then + local msg = "Expected param %s of function %s to be a number, but got %s (a value of type %s) instead" + error(msg:format(paramName, functionName, tostring(value), type(value))) + end +end + +-- transforms "foo bar baz" into {'foo','bar','baz'} +local function words(str) + local result, length = {}, 0 + str:gsub("%S+", function(word) + length = length + 1 + result[length] = word + end) + return result +end + +local function isInteger(n) + return n == math.floor(n) +end + +local function between(value, min, max) + return value >= min and value <= max +end + +local function inside(v, list) + for i=1, #list do + if v == list[i] then return true end + end + return false +end + + +-- pluralization functions + +local pluralization = {} + +local f1 = function(n) + return n == 1 and "one" or "other" +end +pluralization[f1] = words([[ + af asa bem bez bg bn brx ca cgg chr da de dv ee el + en eo es et eu fi fo fur fy gl gsw gu ha haw he is + it jmc kaj kcg kk kl ksb ku lb lg mas ml mn mr nah + nb nd ne nl nn no nr ny nyn om or pa pap ps pt rm + rof rwk saq seh sn so sq ss ssy st sv sw syr ta te + teo tig tk tn ts ur ve vun wae xh xog zu +]]) + +local f2 = function(n) + return (n == 0 or n == 1) and "one" or "other" +end +pluralization[f2] = words("ak am bh fil guw hi ln mg nso ti tl wa") + +local f3 = function(n) + if not isInteger(n) then return 'other' end + return (n == 0 and "zero") or + (n == 1 and "one") or + (n == 2 and "two") or + (between(n % 100, 3, 10) and "few") or + (between(n % 100, 11, 99) and "many") or + "other" +end +pluralization[f3] = {'ar'} + +local f4 = function() + return "other" +end +pluralization[f4] = words([[ + az bm bo dz fa hu id ig ii ja jv ka kde kea km kn + ko lo ms my root sah ses sg th to tr vi wo yo zh +]]) + +local f5 = function(n) + if not isInteger(n) then return 'other' end + local n_10, n_100 = n % 10, n % 100 + return (n_10 == 1 and n_100 ~= 11 and 'one') or + (between(n_10, 2, 4) and not between(n_100, 12, 14) and 'few') or + ((n_10 == 0 or between(n_10, 5, 9) or between(n_100, 11, 14)) and 'many') or + 'other' +end +pluralization[f5] = words('be bs hr ru sh sr uk') + +local f6 = function(n) + if not isInteger(n) then return 'other' end + local n_10, n_100 = n % 10, n % 100 + return (n_10 == 1 and not inside(n_100, {11,71,91}) and 'one') or + (n_10 == 2 and not inside(n_100, {12,72,92}) and 'two') or + (inside(n_10, {3,4,9}) and + not between(n_100, 10, 19) and + not between(n_100, 70, 79) and + not between(n_100, 90, 99) + and 'few') or + (n ~= 0 and n % 1000000 == 0 and 'many') or + 'other' +end +pluralization[f6] = {'br'} + +local f7 = function(n) + return (n == 1 and 'one') or + ((n == 2 or n == 3 or n == 4) and 'few') or + 'other' +end +pluralization[f7] = {'cz', 'sk'} + +local f8 = function(n) + return (n == 0 and 'zero') or + (n == 1 and 'one') or + (n == 2 and 'two') or + (n == 3 and 'few') or + (n == 6 and 'many') or + 'other' +end +pluralization[f8] = {'cy'} + +local f9 = function(n) + return (n >= 0 and n < 2 and 'one') or + 'other' +end +pluralization[f9] = {'ff', 'fr', 'kab'} + +local f10 = function(n) + return (n == 1 and 'one') or + (n == 2 and 'two') or + ((n == 3 or n == 4 or n == 5 or n == 6) and 'few') or + ((n == 7 or n == 8 or n == 9 or n == 10) and 'many') or + 'other' +end +pluralization[f10] = {'ga'} + +local f11 = function(n) + return ((n == 1 or n == 11) and 'one') or + ((n == 2 or n == 12) and 'two') or + (isInteger(n) and (between(n, 3, 10) or between(n, 13, 19)) and 'few') or + 'other' +end +pluralization[f11] = {'gd'} + +local f12 = function(n) + local n_10 = n % 10 + return ((n_10 == 1 or n_10 == 2 or n % 20 == 0) and 'one') or + 'other' +end +pluralization[f12] = {'gv'} + +local f13 = function(n) + return (n == 1 and 'one') or + (n == 2 and 'two') or + 'other' +end +pluralization[f13] = words('iu kw naq se sma smi smj smn sms') + +local f14 = function(n) + return (n == 0 and 'zero') or + (n == 1 and 'one') or + 'other' +end +pluralization[f14] = {'ksh'} + +local f15 = function(n) + return (n == 0 and 'zero') or + (n > 0 and n < 2 and 'one') or + 'other' +end +pluralization[f15] = {'lag'} + +local f16 = function(n) + if not isInteger(n) then return 'other' end + if between(n % 100, 11, 19) then return 'other' end + local n_10 = n % 10 + return (n_10 == 1 and 'one') or + (between(n_10, 2, 9) and 'few') or + 'other' +end +pluralization[f16] = {'lt'} + +local f17 = function(n) + return (n == 0 and 'zero') or + ((n % 10 == 1 and n % 100 ~= 11) and 'one') or + 'other' +end +pluralization[f17] = {'lv'} + +local f18 = function(n) + return((n % 10 == 1 and n ~= 11) and 'one') or + 'other' +end +pluralization[f18] = {'mk'} + +local f19 = function(n) + return (n == 1 and 'one') or + ((n == 0 or + (n ~= 1 and isInteger(n) and between(n % 100, 1, 19))) + and 'few') or + 'other' +end +pluralization[f19] = {'mo', 'ro'} + +local f20 = function(n) + if n == 1 then return 'one' end + if not isInteger(n) then return 'other' end + local n_100 = n % 100 + return ((n == 0 or between(n_100, 2, 10)) and 'few') or + (between(n_100, 11, 19) and 'many') or + 'other' +end +pluralization[f20] = {'mt'} + +local f21 = function(n) + if n == 1 then return 'one' end + if not isInteger(n) then return 'other' end + local n_10, n_100 = n % 10, n % 100 + + return ((between(n_10, 2, 4) and not between(n_100, 12, 14)) and 'few') or + ((n_10 == 0 or n_10 == 1 or between(n_10, 5, 9) or between(n_100, 12, 14)) and 'many') or + 'other' +end +pluralization[f21] = {'pl'} + +local f22 = function(n) + return (n == 0 or n == 1) and 'one' or + 'other' +end +pluralization[f22] = {'shi'} + +local f23 = function(n) + local n_100 = n % 100 + return (n_100 == 1 and 'one') or + (n_100 == 2 and 'two') or + ((n_100 == 3 or n_100 == 4) and 'few') or + 'other' +end +pluralization[f23] = {'sl'} + +local f24 = function(n) + return (isInteger(n) and (n == 0 or n == 1 or between(n, 11, 99)) and 'one') + or 'other' +end +pluralization[f24] = {'tzm'} + +local pluralizationFunctions = {} +for f,locales in pairs(pluralization) do + for _,locale in ipairs(locales) do + pluralizationFunctions[locale] = f + end +end + +-- public interface + +function plural.get(locale, n) + assertPresentString('i18n.plural.get', 'locale', locale) + assertNumber('i18n.plural.get', 'n', n) + + local f = pluralizationFunctions[locale] or defaultFunction + + return f(math.abs(n)) +end + +function plural.setDefaultFunction(f) + defaultFunction = f +end + +function plural.reset() + defaultFunction = pluralizationFunctions['en'] +end + +plural.reset() + +return plural diff --git a/extern/i18n.lua/i18n/variants.lua b/extern/i18n.lua/i18n/variants.lua new file mode 100644 index 0000000000..0cfad42f6c --- /dev/null +++ b/extern/i18n.lua/i18n/variants.lua @@ -0,0 +1,49 @@ +local variants = {} + +local function reverse(arr, length) + local result = {} + for i=1, length do result[i] = arr[length-i+1] end + return result, length +end + +local function concat(arr1, len1, arr2, len2) + for i = 1, len2 do + arr1[len1 + i] = arr2[i] + end + return arr1, len1 + len2 +end + +function variants.ancestry(locale) + local result, length, accum = {},0,nil + locale:gsub("[^%-]+", function(c) + length = length + 1 + accum = accum and (accum .. '-' .. c) or c + result[length] = accum + end) + return reverse(result, length) +end + +function variants.isParent(parent, child) + return not not child:match("^".. parent .. "%-") +end + +function variants.root(locale) + return locale:match("[^%-]+") +end + +function variants.fallbacks(locale, fallbackLocale) + if locale == fallbackLocale or + variants.isParent(fallbackLocale, locale) then + return variants.ancestry(locale) + end + if variants.isParent(locale, fallbackLocale) then + return variants.ancestry(fallbackLocale) + end + + local ancestry1, length1 = variants.ancestry(locale) + local ancestry2, length2 = variants.ancestry(fallbackLocale) + + return concat(ancestry1, length1, ancestry2, length2) +end + +return variants diff --git a/extern/i18n.lua/i18n/version.lua b/extern/i18n.lua/i18n/version.lua new file mode 100644 index 0000000000..eb788884ac --- /dev/null +++ b/extern/i18n.lua/i18n/version.lua @@ -0,0 +1 @@ +return '0.9.2' diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index cea33f0f40..607ddeca49 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(shaders) add_subdirectory(vfs) add_subdirectory(builtin_scripts) add_subdirectory(lua_api) +add_subdirectory(../extern/i18n.lua ${CMAKE_CURRENT_BINARY_DIR}/files) From d5cba38f4bb7f19a61347e0883c561cfaaeb46b2 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Thu, 30 Dec 2021 17:08:29 -0800 Subject: [PATCH 1910/2859] shader-based object texture blending --- files/shaders/objects_fragment.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 99ed44919b..6e8d929569 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -146,7 +146,7 @@ void main() #if @decalMap vec4 decalTex = texture2D(decalMap, decalMapUV); - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, decalTex.xyz, decalTex.a); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, decalTex.xyz, decalTex.a * diffuseColor.a); #endif #if @envMap From 0f246e73653fb1790c0a7c3cdf61e2197bbbade4 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 26 Dec 2021 21:49:20 +0100 Subject: [PATCH 1911/2859] Use a separate instance of Lua i18n for every context --- apps/openmw/engine.cpp | 2 +- apps/openmw/mwlua/context.hpp | 2 + apps/openmw/mwlua/luabindings.cpp | 4 +- apps/openmw/mwlua/luamanagerimp.cpp | 11 +- apps/openmw/mwlua/luamanagerimp.hpp | 4 +- apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/lua/test_i18n.cpp | 110 ++++++++++++++++++ apps/openmw_test_suite/lua/test_lua.cpp | 4 +- .../lua/test_utilpackage.cpp | 6 - apps/openmw_test_suite/lua/testing_util.hpp | 7 ++ components/CMakeLists.txt | 4 +- components/lua/i18n.cpp | 108 +++++++++++++++++ components/lua/i18n.hpp | 41 +++++++ components/lua/luastate.cpp | 81 +++++++++++-- components/lua/luastate.hpp | 9 +- .../source/reference/modding/settings/lua.rst | 11 ++ files/lua_api/openmw/core.lua | 33 ++++++ files/settings-default.cfg | 4 + 18 files changed, 416 insertions(+), 26 deletions(-) create mode 100644 apps/openmw_test_suite/lua/test_i18n.cpp create mode 100644 components/lua/i18n.cpp create mode 100644 components/lua/i18n.hpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 1a73ae3531..afba76edce 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -737,7 +737,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mViewer->addEventHandler(mScreenCaptureHandler); - mLuaManager = new MWLua::LuaManager(mVFS.get()); + mLuaManager = new MWLua::LuaManager(mVFS.get(), (mResDir / "lua_libs").string()); mEnvironment.setLuaManager(mLuaManager); // Create input and UI first to set up a bootstrapping environment for diff --git a/apps/openmw/mwlua/context.hpp b/apps/openmw/mwlua/context.hpp index b3e3703a46..7ff584d8cc 100644 --- a/apps/openmw/mwlua/context.hpp +++ b/apps/openmw/mwlua/context.hpp @@ -7,6 +7,7 @@ namespace LuaUtil { class LuaState; class UserdataSerializer; + class I18nManager; } namespace MWLua @@ -20,6 +21,7 @@ namespace MWLua LuaManager* mLuaManager; LuaUtil::LuaState* mLua; LuaUtil::UserdataSerializer* mSerializer; + LuaUtil::I18nManager* mI18n; WorldView* mWorldView; LocalEventQueue* mLocalEventQueue; GlobalEventQueue* mGlobalEventQueue; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 57b1b17a3b..c525fd8a23 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -1,6 +1,7 @@ #include "luabindings.hpp" #include +#include #include #include "../mwbase/environment.hpp" @@ -25,7 +26,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 11; + api["API_REVISION"] = 12; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); @@ -64,6 +65,7 @@ namespace MWLua {"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft}, {"Ammunition", MWWorld::InventoryStore::Slot_Ammunition} })); + api["i18n"] = [i18n=context.mI18n](const std::string& context) { return i18n->getContext(context); }; return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index f134ef86dd..be5764c32f 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include #include "../mwbase/windowmanager.hpp" @@ -20,9 +22,10 @@ namespace MWLua { - LuaManager::LuaManager(const VFS::Manager* vfs) : mLua(vfs, &mConfiguration) + LuaManager::LuaManager(const VFS::Manager* vfs, const std::string& libsDir) : mLua(vfs, &mConfiguration), mI18n(vfs, &mLua) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); + mLua.addInternalLibSearchPath(libsDir); mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry()); mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry()); @@ -46,6 +49,7 @@ namespace MWLua context.mIsGlobal = true; context.mLuaManager = this; context.mLua = &mLua; + context.mI18n = &mI18n; context.mWorldView = &mWorldView; context.mLocalEventQueue = &mLocalEvents; context.mGlobalEventQueue = &mGlobalEvents; @@ -55,6 +59,11 @@ namespace MWLua localContext.mIsGlobal = false; localContext.mSerializer = mLocalSerializer.get(); + mI18n.init(); + std::vector preferredLanguages; + Misc::StringUtils::split(Settings::Manager::getString("i18n preferred languages", "Lua"), preferredLanguages, ", "); + mI18n.setPreferredLanguages(preferredLanguages); + initObjectBindingsForGlobalScripts(context); initCellBindingsForGlobalScripts(context); initObjectBindingsForLocalScripts(localContext); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 88273de7f0..d050cb9413 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -5,6 +5,7 @@ #include #include +#include #include "../mwbase/luamanager.hpp" @@ -22,7 +23,7 @@ namespace MWLua class LuaManager : public MWBase::LuaManager { public: - LuaManager(const VFS::Manager* vfs); + LuaManager(const VFS::Manager* vfs, const std::string& libsDir); // Called by engine.cpp when the environment is fully initialized. void init(); @@ -91,6 +92,7 @@ namespace MWLua bool mGlobalScriptsStarted = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; + LuaUtil::I18nManager mI18n; sol::table mNearbyPackage; sol::table mUserInterfacePackage; sol::table mCameraPackage; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 19b31f78be..9465d59b47 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -23,6 +23,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) lua/test_serialization.cpp lua/test_querypackage.cpp lua/test_configuration.cpp + lua/test_i18n.cpp lua/test_ui_content.cpp diff --git a/apps/openmw_test_suite/lua/test_i18n.cpp b/apps/openmw_test_suite/lua/test_i18n.cpp new file mode 100644 index 0000000000..427482be64 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_i18n.cpp @@ -0,0 +1,110 @@ +#include "gmock/gmock.h" +#include + +#include + +#include +#include + +#include "testing_util.hpp" + +namespace +{ + using namespace testing; + + TestFile invalidScript("not a script"); + TestFile incorrectScript("return { incorrectSection = {}, engineHandlers = { incorrectHandler = function() end } }"); + TestFile emptyScript(""); + + TestFile test1En(R"X( +return { + good_morning = "Good morning.", + you_have_arrows = { + one = "You have one arrow.", + other = "You have %{count} arrows.", + }, +} +)X"); + + TestFile test1De(R"X( +return { + good_morning = "Guten Morgen.", + you_have_arrows = { + one = "Du hast ein Pfeil.", + other = "Du hast %{count} Pfeile.", + }, + ["Hello %{name}!"] = "Hallo %{name}!", +} +)X"); + +TestFile test2En(R"X( +return { + good_morning = "Morning!", + you_have_arrows = "Arrows count: %{count}", +} +)X"); + + TestFile invalidTest2De(R"X( +require('math') +return {} +)X"); + + struct LuaI18nTest : Test + { + std::unique_ptr mVFS = createTestVFS({ + {"i18n/Test1/en.lua", &test1En}, + {"i18n/Test1/de.lua", &test1De}, + {"i18n/Test2/en.lua", &test2En}, + {"i18n/Test2/de.lua", &invalidTest2De}, + }); + + LuaUtil::ScriptsConfiguration mCfg; + std::string mLibsPath = (Files::TargetPathType("openmw_test_suite").getLocalPath() / "resources" / "lua_libs").string(); + }; + + TEST_F(LuaI18nTest, I18n) + { + internal::CaptureStdout(); + LuaUtil::LuaState lua{mVFS.get(), &mCfg}; + sol::state& l = lua.sol(); + LuaUtil::I18nManager i18n(mVFS.get(), &lua); + lua.addInternalLibSearchPath(mLibsPath); + i18n.init(); + i18n.setPreferredLanguages({"de", "en"}); + EXPECT_THAT(internal::GetCapturedStdout(), "I18n preferred languages: de en\n"); + + internal::CaptureStdout(); + l["t1"] = i18n.getContext("Test1"); + EXPECT_THAT(internal::GetCapturedStdout(), "Language file \"i18n/Test1/de.lua\" is enabled\n"); + + internal::CaptureStdout(); + l["t2"] = i18n.getContext("Test2"); + { + std::string output = internal::GetCapturedStdout(); + EXPECT_THAT(output, HasSubstr("Can not load i18n/Test2/de.lua")); + EXPECT_THAT(output, HasSubstr("Language file \"i18n/Test2/en.lua\" is enabled")); + } + + EXPECT_EQ(get(l, "t1('good_morning')"), "Guten Morgen."); + EXPECT_EQ(get(l, "t1('you_have_arrows', {count=1})"), "Du hast ein Pfeil."); + EXPECT_EQ(get(l, "t1('you_have_arrows', {count=5})"), "Du hast 5 Pfeile."); + EXPECT_EQ(get(l, "t1('Hello %{name}!', {name='World'})"), "Hallo World!"); + EXPECT_EQ(get(l, "t2('good_morning')"), "Morning!"); + EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); + + internal::CaptureStdout(); + i18n.setPreferredLanguages({"en", "de"}); + EXPECT_THAT(internal::GetCapturedStdout(), + "I18n preferred languages: en de\n" + "Language file \"i18n/Test1/en.lua\" is enabled\n" + "Language file \"i18n/Test2/en.lua\" is enabled\n"); + + EXPECT_EQ(get(l, "t1('good_morning')"), "Good morning."); + EXPECT_EQ(get(l, "t1('you_have_arrows', {count=1})"), "You have one arrow."); + EXPECT_EQ(get(l, "t1('you_have_arrows', {count=5})"), "You have 5 arrows."); + EXPECT_EQ(get(l, "t1('Hello %{name}!', {name='World'})"), "Hello World!"); + EXPECT_EQ(get(l, "t2('good_morning')"), "Morning!"); + EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); + } + +} diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 4b3ecdcb2b..fe3cf14d25 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -106,7 +106,7 @@ return { } EXPECT_EQ(LuaUtil::call(script["useCounter"]).get(), 45); - EXPECT_ERROR(LuaUtil::call(script["incorrectRequire"]), "Resource 'counter.lua' not found"); + EXPECT_ERROR(LuaUtil::call(script["incorrectRequire"]), "module not found: counter"); } TEST_F(LuaStateTest, ReadOnly) @@ -161,7 +161,7 @@ return { sol::table script2 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api2}}); - EXPECT_ERROR(LuaUtil::call(script1["sqr"], 3), "Resource 'sqrlib.lua' not found"); + EXPECT_ERROR(LuaUtil::call(script1["sqr"], 3), "module not found: sqrlib"); EXPECT_EQ(LuaUtil::call(script2["sqr"], 3).get(), 9); EXPECT_EQ(LuaUtil::call(script1["apiName"]).get(), "api1"); diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index 14b7021532..953d5f50d3 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -10,12 +10,6 @@ namespace { using namespace testing; - template - T get(sol::state& lua, std::string luaCode) - { - return lua.safe_script("return " + luaCode).get(); - } - std::string getAsString(sol::state& lua, std::string luaCode) { return LuaUtil::toString(lua.safe_script("return " + luaCode)); diff --git a/apps/openmw_test_suite/lua/testing_util.hpp b/apps/openmw_test_suite/lua/testing_util.hpp index 2f6810350f..ba4a418bfb 100644 --- a/apps/openmw_test_suite/lua/testing_util.hpp +++ b/apps/openmw_test_suite/lua/testing_util.hpp @@ -2,6 +2,7 @@ #define LUA_TESTING_UTIL_H #include +#include #include #include @@ -9,6 +10,12 @@ namespace { + template + T get(sol::state& lua, const std::string& luaCode) + { + return lua.safe_script("return " + luaCode).get(); + } + class TestFile : public VFS::File { public: diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3b38489d7a..6fedf25b4c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -29,7 +29,7 @@ endif (GIT_CHECKOUT) # source files add_component_dir (lua - luastate scriptscontainer utilpackage serialization configuration + luastate scriptscontainer utilpackage serialization configuration i18n ) add_component_dir (settings @@ -160,7 +160,7 @@ add_component_dir (fallback add_component_dir (queries query luabindings ) - + add_component_dir (lua_ui widget widgetlist element layers content text textedit window diff --git a/components/lua/i18n.cpp b/components/lua/i18n.cpp new file mode 100644 index 0000000000..9fd2724f75 --- /dev/null +++ b/components/lua/i18n.cpp @@ -0,0 +1,108 @@ +#include "i18n.hpp" + +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace LuaUtil +{ + + void I18nManager::init() + { + mPreferredLanguages.push_back("en"); + sol::usertype ctx = mLua->sol().new_usertype("I18nContext"); + ctx[sol::meta_function::call] = &Context::translate; + try + { + mI18nLoader = mLua->loadInternalLib("i18n"); + sol::set_environment(mLua->newInternalLibEnvironment(), mI18nLoader); + } + catch (std::exception& e) + { + Log(Debug::Error) << "LuaUtil::I18nManager initialization failed: " << e.what(); + } + } + + void I18nManager::setPreferredLanguages(const std::vector& langs) + { + { + Log msg(Debug::Info); + msg << "I18n preferred languages:"; + for (const std::string& l : langs) + msg << " " << l; + } + mPreferredLanguages = langs; + for (auto& [_, context] : mContexts) + context.updateLang(this); + } + + void I18nManager::Context::readLangData(I18nManager* manager, const std::string& lang) + { + std::string path = "i18n/"; + path.append(mName); + path.append("/"); + path.append(lang); + path.append(".lua"); + if (!manager->mVFS->exists(path)) + return; + try + { + sol::protected_function dataFn = manager->mLua->loadFromVFS(path); + sol::environment emptyEnv(manager->mLua->sol(), sol::create); + sol::set_environment(emptyEnv, dataFn); + sol::table data = manager->mLua->newTable(); + data[lang] = call(dataFn); + call(mI18n["load"], data); + mLoadedLangs[lang] = true; + } + catch (std::exception& e) + { + Log(Debug::Error) << "Can not load " << path << ": " << e.what(); + } + } + + sol::object I18nManager::Context::translate(std::string_view key, const sol::object& data) + { + sol::object res = call(mI18n["translate"], key, data); + if (res != sol::nil) + return res; + + // If not found in a language file - register the key itself as a message. + std::string composedKey = call(mI18n["getLocale"]).get(); + composedKey.push_back('.'); + composedKey.append(key); + call(mI18n["set"], composedKey, key); + return call(mI18n["translate"], key, data); + } + + void I18nManager::Context::updateLang(I18nManager* manager) + { + for (const std::string& lang : manager->mPreferredLanguages) + { + if (mLoadedLangs[lang] == sol::nil) + readLangData(manager, lang); + if (mLoadedLangs[lang] != sol::nil) + { + Log(Debug::Verbose) << "Language file \"i18n/" << mName << "/" << lang << ".lua\" is enabled"; + call(mI18n["setLocale"], lang); + return; + } + } + Log(Debug::Warning) << "No language files for the preferred languages found in \"i18n/" << mName << "\""; + } + + sol::object I18nManager::getContext(const std::string& contextName) + { + if (mI18nLoader == sol::nil) + throw std::runtime_error("LuaUtil::I18nManager is not initialized"); + Context ctx{contextName, mLua->newTable(), call(mI18nLoader, "i18n.init")}; + ctx.updateLang(this); + mContexts.emplace(contextName, ctx); + return sol::make_object(mLua->sol(), ctx); + } + +} diff --git a/components/lua/i18n.hpp b/components/lua/i18n.hpp new file mode 100644 index 0000000000..4bc7c624f1 --- /dev/null +++ b/components/lua/i18n.hpp @@ -0,0 +1,41 @@ +#ifndef COMPONENTS_LUA_I18N_H +#define COMPONENTS_LUA_I18N_H + +#include "luastate.hpp" + +namespace LuaUtil +{ + + class I18nManager + { + public: + I18nManager(const VFS::Manager* vfs, LuaState* lua) : mVFS(vfs), mLua(lua) {} + void init(); + + void setPreferredLanguages(const std::vector& langs); + const std::vector& getPreferredLanguages() const { return mPreferredLanguages; } + + sol::object getContext(const std::string& contextName); + + private: + struct Context + { + std::string mName; + sol::table mLoadedLangs; + sol::table mI18n; + + void updateLang(I18nManager* manager); + void readLangData(I18nManager* manager, const std::string& lang); + sol::object translate(std::string_view key, const sol::object& data); + }; + + const VFS::Manager* mVFS; + LuaState* mLua; + sol::object mI18nLoader = sol::nil; + std::vector mPreferredLanguages; + std::map mContexts; + }; + +} + +#endif // COMPONENTS_LUA_I18N_H \ No newline at end of file diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 61637d7b07..cee48b4545 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -4,17 +4,44 @@ #include #endif // NO_LUAJIT +#include + #include namespace LuaUtil { - static std::string packageNameToPath(std::string_view packageName) + static std::string packageNameToVfsPath(std::string_view packageName, const VFS::Manager* vfs) { - std::string res(packageName); - std::replace(res.begin(), res.end(), '.', '/'); - res.append(".lua"); - return res; + std::string path(packageName); + std::replace(path.begin(), path.end(), '.', '/'); + std::string pathWithInit = path + "/init.lua"; + path.append(".lua"); + if (vfs->exists(path)) + return path; + else if (vfs->exists(pathWithInit)) + return pathWithInit; + else + throw std::runtime_error("module not found: " + std::string(packageName)); + } + + static std::string packageNameToPath(std::string_view packageName, const std::vector& searchDirs) + { + std::string path(packageName); + std::replace(path.begin(), path.end(), '.', '/'); + std::string pathWithInit = path + "/init.lua"; + path.append(".lua"); + for (const std::string& dir : searchDirs) + { + std::filesystem::path base(dir); + std::filesystem::path p1 = base / path; + if (std::filesystem::exists(p1)) + return p1.string(); + std::filesystem::path p2 = base / pathWithInit; + if (std::filesystem::exists(p2)) + return p2.string(); + } + throw std::runtime_error("module not found: " + std::string(packageName)); } static const std::string safeFunctions[] = { @@ -28,7 +55,7 @@ namespace LuaUtil sol::lib::string, sol::lib::table, sol::lib::debug); mLua["math"]["randomseed"](static_cast(std::time(nullptr))); - mLua["math"]["randomseed"] = sol::nil; + mLua["math"]["randomseed"] = []{}; mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; mLua.script(R"(printToLog = function(name, ...) @@ -105,7 +132,7 @@ namespace LuaUtil const std::string& path, const std::string& namePrefix, const std::map& packages, const sol::object& hiddenData) { - sol::protected_function script = loadScript(path); + sol::protected_function script = loadScriptAndCache(path); sol::environment env(mLua, sol::create, mSandboxEnv); std::string envName = namePrefix + "[" + path + "]:"; @@ -122,9 +149,9 @@ namespace LuaUtil sol::object package = packages[packageName]; if (package == sol::nil) { - sol::protected_function packageLoader = loadScript(packageNameToPath(packageName)); + sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS)); sol::set_environment(env, packageLoader); - package = throwIfError(packageLoader()); + package = call(packageLoader, packageName); if (!package.is()) throw std::runtime_error("Lua package must return a table."); packages[packageName] = package; @@ -138,6 +165,24 @@ namespace LuaUtil return call(script); } + sol::environment LuaState::newInternalLibEnvironment() + { + sol::environment env(mLua, sol::create, mSandboxEnv); + sol::table loaded(mLua, sol::create); + for (const std::string& s : safePackages) + loaded[s] = mSandboxEnv[s]; + env["require"] = [this, loaded, env](const std::string& module) mutable + { + if (loaded[module] != sol::nil) + return loaded[module]; + sol::protected_function initializer = loadInternalLib(module); + sol::set_environment(env, initializer); + loaded[module] = call(initializer, module); + return loaded[module]; + }; + return env; + } + sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) { if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) @@ -146,17 +191,31 @@ namespace LuaUtil return std::move(res); } - sol::function LuaState::loadScript(const std::string& path) + sol::function LuaState::loadScriptAndCache(const std::string& path) { auto iter = mCompiledScripts.find(path); if (iter != mCompiledScripts.end()) return mLua.load(iter->second.as_string_view(), path, sol::load_mode::binary); + sol::function res = loadFromVFS(path); + mCompiledScripts[path] = res.dump(); + return res; + } + sol::function LuaState::loadFromVFS(const std::string& path) + { std::string fileContent(std::istreambuf_iterator(*mVFS->get(path)), {}); sol::load_result res = mLua.load(fileContent, path, sol::load_mode::text); if (!res.valid()) throw std::runtime_error("Lua error: " + res.get()); - mCompiledScripts[path] = res.get().dump(); + return res; + } + + sol::function LuaState::loadInternalLib(std::string_view libName) + { + std::string path = packageNameToPath(libName, mLibSearchPaths); + sol::load_result res = mLua.load_file(path, sol::load_mode::text); + if (!res.valid()) + throw std::runtime_error("Lua error: " + res.get()); return res; } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index 32c180c987..f9be5e9e99 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -76,12 +76,18 @@ namespace LuaUtil const ScriptsConfiguration& getConfiguration() const { return *mConf; } + // Load internal Lua library. All libraries are loaded in one sandbox and shouldn't be exposed to scripts directly. + void addInternalLibSearchPath(const std::string& path) { mLibSearchPaths.push_back(path); } + sol::function loadInternalLib(std::string_view libName); + sol::function loadFromVFS(const std::string& path); + sol::environment newInternalLibEnvironment(); + private: static sol::protected_function_result throwIfError(sol::protected_function_result&&); template friend sol::protected_function_result call(const sol::protected_function& fn, Args&&... args); - sol::function loadScript(const std::string& path); + sol::function loadScriptAndCache(const std::string& path); sol::state mLua; const ScriptsConfiguration* mConf; @@ -89,6 +95,7 @@ namespace LuaUtil std::map mCompiledScripts; std::map mCommonPackages; const VFS::Manager* mVFS; + std::vector mLibSearchPaths; }; // Should be used for every call of every Lua function. diff --git a/docs/source/reference/modding/settings/lua.rst b/docs/source/reference/modding/settings/lua.rst index 65faf884ae..919d530d18 100644 --- a/docs/source/reference/modding/settings/lua.rst +++ b/docs/source/reference/modding/settings/lua.rst @@ -27,3 +27,14 @@ Values >1 are not yet supported. This setting can only be configured by editing the settings configuration file. +i18n preferred languages +------------------------ + +:Type: string +:Default: en + +List of the preferred languages separated by comma. +For example "de,en" means German as the first prority and English as a fallback. + +This setting can only be configured by editing the settings configuration file. + diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index fe309a92db..016267d39d 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -37,6 +37,39 @@ -- @function [parent=#core] isWorldPaused -- @return #boolean +------------------------------------------------------------------------------- +-- Return i18n formatting function for the given context. +-- It is based on `i18n.lua` library. +-- Language files should be stored in VFS as `i18n//.lua`. +-- See https://github.com/kikito/i18n.lua for format details. +-- @function [parent=#core] i18n +-- @param #string context I18n context; recommended to use the name of the mod. +-- @return #function +-- @usage +-- -- DataFiles/i18n/MyMod/en.lua +-- return { +-- good_morning = 'Good morning.', +-- you_have_arrows = { +-- one = 'You have one arrow.', +-- other = 'You have %{count} arrows.', +-- }, +-- } +-- @usage +-- -- DataFiles/i18n/MyMod/de.lua +-- return { +-- good_morning = "Guten Morgen.", +-- you_have_arrows = { +-- one = "Du hast ein Pfeil.", +-- other = "Du hast %{count} Pfeile.", +-- }, +-- ["Hello %{name}!"] = "Hallo %{name}!", +-- } +-- @usage +-- local myMsg = core.i18n('MyMod') +-- print( myMsg('good_morning') ) +-- print( myMsg('you_have_arrows', {count=5}) ) +-- print( myMsg('Hello %{name}!', {name='World'}) ) + ------------------------------------------------------------------------------- -- @type OBJECT_TYPE diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 57faaba11d..acd011f233 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1123,3 +1123,7 @@ lua debug = false # If zero, Lua scripts are processed in the main thread. lua num threads = 1 +# List of the preferred languages separated by comma. +# For example "de,en" means German as the first prority and English as a fallback. +i18n preferred languages = en + From e4ee6ab0c167443927d0d7054b17ecf08f605e78 Mon Sep 17 00:00:00 2001 From: psi29a Date: Mon, 3 Jan 2022 11:53:07 +0000 Subject: [PATCH 1912/2859] Update extern/i18n.lua/CMakeLists.txt to be like the rest... (make i18n work on macOS) --- extern/i18n.lua/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/i18n.lua/CMakeLists.txt b/extern/i18n.lua/CMakeLists.txt index aec4447470..1f7a71a2c2 100644 --- a/extern/i18n.lua/CMakeLists.txt +++ b/extern/i18n.lua/CMakeLists.txt @@ -1,5 +1,5 @@ if (NOT DEFINED OPENMW_RESOURCES_ROOT) - message( FATAL_ERROR "OPENMW_RESOURCES_ROOT is not set" ) + return() endif() # Copy resource files into the build directory From 97ab7cf457bf7cf5054d468eed54ce883f01ef0b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 3 Jan 2022 19:17:56 +0100 Subject: [PATCH 1913/2859] Fix demoralize and rally being swapped --- apps/openmw/mwmechanics/spelleffects.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index aef6b26f19..558a7ebea8 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -46,7 +46,7 @@ namespace { auto& creatureStats = target.getClass().getCreatureStats(target); auto stat = creatureStats.getAiSetting(setting); - stat.setModifier(static_cast(stat.getModifier() - magnitude)); + stat.setModifier(static_cast(stat.getModifier() + magnitude)); creatureStats.setAiSetting(setting, stat); } } @@ -491,11 +491,11 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co break; case ESM::MagicEffect::FrenzyCreature: case ESM::MagicEffect::FrenzyHumanoid: - modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid); + modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid); break; case ESM::MagicEffect::CalmCreature: case ESM::MagicEffect::CalmHumanoid: - modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid); + modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid); if(!invalid && effect.mMagnitude > 0) { auto& creatureStats = target.getClass().getCreatureStats(target); @@ -998,11 +998,11 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara break; case ESM::MagicEffect::FrenzyCreature: case ESM::MagicEffect::FrenzyHumanoid: - modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid); + modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid); break; case ESM::MagicEffect::CalmCreature: case ESM::MagicEffect::CalmHumanoid: - modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid); + modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid); break; case ESM::MagicEffect::DemoralizeCreature: case ESM::MagicEffect::DemoralizeHumanoid: From aab0473c2870e1fabe8cf6bf8e7372d54297b0b7 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 3 Jan 2022 21:05:14 +0100 Subject: [PATCH 1914/2859] Only prevent recasting by the actor who cast the spell --- apps/openmw/mwmechanics/spellpriority.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index acc9fc2a3f..b79af49b05 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -75,6 +75,16 @@ namespace } return duration; } + + bool isSpellActive(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const std::string& id) + { + 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 && Misc::StringUtils::ciEqual(spell.getId(), id); + }) != active.end(); + } } namespace MWMechanics @@ -105,8 +115,6 @@ namespace MWMechanics float rateSpell(const ESM::Spell *spell, const MWWorld::Ptr &actor, const MWWorld::Ptr& enemy) { - const CreatureStats& stats = actor.getClass().getCreatureStats(actor); - float successChance = MWMechanics::getSpellSuccessChance(spell, actor); if (successChance == 0.f) return 0.f; @@ -125,9 +133,9 @@ namespace MWMechanics // Spells don't stack, so early out if the spell is still active on the target int types = getRangeTypes(spell->mEffects); - if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId)) + if ((types & Self) && isSpellActive(actor, actor, spell->mId)) return 0.f; - if ( ((types & Touch) || (types & Target)) && enemy.getClass().getCreatureStats(enemy).getActiveSpells().isSpellActive(spell->mId)) + if ( ((types & Touch) || (types & Target)) && isSpellActive(actor, enemy, spell->mId)) return 0.f; return rateEffects(spell->mEffects, actor, enemy) * (successChance / 100.f); From 1816784784855e5defb66bd6cd41e3586c57ece6 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 4 Jan 2022 09:14:16 +0000 Subject: [PATCH 1915/2859] Update CI/before_script.osx.sh to allow full use of c++17 on macOS --- CI/before_script.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 6d0fe8c99e..8b2c9c6f01 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -16,7 +16,7 @@ cmake \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \ -D CMAKE_C_FLAGS_RELEASE="-g -O0" \ -D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \ --D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \ +-D CMAKE_OSX_DEPLOYMENT_TARGET="10.15" \ -D CMAKE_BUILD_TYPE=RELEASE \ -D OPENMW_OSX_DEPLOYMENT=TRUE \ -D OPENMW_USE_SYSTEM_SQLITE3=OFF \ From e743f896ef9dfc532d02f6cc7d652d434ac9d476 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 4 Jan 2022 10:48:25 +0000 Subject: [PATCH 1916/2859] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06148d03d8..105057b860 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -74,7 +74,7 @@ Coverity: script: - CI/before_script.linux.sh # Remove the specific targets and build everything once we can do it under 3h - - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter openmw-navmeshtool openmw-cs after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME From bdfad27e05a05a8960edc6a61d0393bc85f2f2f8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 4 Jan 2022 16:50:04 +0100 Subject: [PATCH 1917/2859] Remove constant conditions --- components/esm/creaturestats.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index ee4b0ad092..0f404ba58b 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -1,7 +1,6 @@ #include "creaturestats.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" -#include "savedgame.hpp" #include @@ -183,9 +182,7 @@ void ESM::CreatureStats::save (ESMWriter &esm) const for (int i=0; i<3; ++i) mDynamic[i].save (esm); - if (ESM::SavedGame::sCurrentFormat <= 18 && mMissingACDT) - esm.writeHNT("GOLD", std::numeric_limits::min()); - else if(mGoldPool) + if (mGoldPool) esm.writeHNT("GOLD", mGoldPool); if (mTradeTime.mDay != 0 || mTradeTime.mHour != 0) @@ -258,7 +255,7 @@ void ESM::CreatureStats::save (ESMWriter &esm) const for (int i=0; i<4; ++i) mAiSettings[i].save(esm); } - if(ESM::SavedGame::sCurrentFormat > 18 && mMissingACDT) + if (mMissingACDT) esm.writeHNT("NOAC", mMissingACDT); } From 5a7a3a0d2d330af4665b49945a17178ebc8e644c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 4 Jan 2022 17:11:55 +0100 Subject: [PATCH 1918/2859] Silence a signed/unsigned mismatch warning --- components/esm/defs.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index e793c6fea7..f87a67405b 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -96,7 +96,7 @@ struct FourCC static constexpr unsigned int value = (((((d << 8) | c) << 8) | b) << 8) | a; }; -enum RecNameInts +enum RecNameInts : unsigned int { // format 0 / legacy REC_ACTI = FourCC<'A','C','T','I'>::value, From 40faf5425017644d8c5457e5a89739bf507311f7 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 4 Jan 2022 19:32:49 +0000 Subject: [PATCH 1919/2859] Remove -bugprone-narrowing-conversions from clang-tidy --- CI/before_script.linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 19d8338721..8278ffcd0e 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -46,7 +46,7 @@ fi if [[ $CI_CLANG_TIDY ]]; then CMAKE_CONF_OPTS+=( - -DCMAKE_CXX_CLANG_TIDY='clang-tidy;-checks=-*,boost-*,clang-analyzer-*,concurrency-*,performance-*,-header-filter=.*,bugprone-*,misc-definitions-in-headers,misc-misplaced-const,misc-redundant-expression' + -DCMAKE_CXX_CLANG_TIDY='clang-tidy;-checks=-*,boost-*,clang-analyzer-*,concurrency-*,performance-*,-header-filter=.*,bugprone-*,misc-definitions-in-headers,misc-misplaced-const,misc-redundant-expression,-bugprone-narrowing-conversions' ) fi From 421e6629ad98f282dff032d82ad6cf682e26eba4 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 4 Jan 2022 20:42:09 +0100 Subject: [PATCH 1920/2859] Fix unnecessary-copy-initialization > warning: the variable 'key' is copy-constructed from a const reference but is only used as const reference; consider making it a const reference [performance-unnecessary-copy-initialization] Found by clang-tidy. --- apps/openmw/mwgui/tooltips.cpp | 4 ++-- apps/openmw/mwmechanics/magiceffects.cpp | 2 +- apps/wizard/inisettings.cpp | 2 +- components/detournavigator/navigatorutils.cpp | 4 ++-- components/esm/esmreader.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 0a2fd4c448..3a2bd65361 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -650,7 +650,7 @@ namespace MWGui std::string ToolTips::getSoulString(const MWWorld::CellRef& cellref) { - std::string soul = cellref.getSoul(); + const std::string& soul = cellref.getSoul(); if (soul.empty()) return std::string(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -666,7 +666,7 @@ namespace MWGui { std::string ret; ret += getMiscString(cellref.getOwner(), "Owner"); - const std::string factionId = cellref.getFaction(); + const std::string& factionId = cellref.getFaction(); if (!factionId.empty()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 7a1acb1003..39d340e6ba 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -137,7 +137,7 @@ namespace MWMechanics { if (this==&effects) { - MagicEffects temp (effects); + const MagicEffects& temp (effects); *this += temp; return *this; } diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index e9cec12f5f..198d1df6e7 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -122,7 +122,7 @@ bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream) QString section(fullKey.at(0)); section.prepend(QLatin1Char('[')); section.append(QLatin1Char(']')); - QString key(fullKey.at(1)); + const QString& key(fullKey.at(1)); int index = buffer.lastIndexOf(section); if (index == -1) { diff --git a/components/detournavigator/navigatorutils.cpp b/components/detournavigator/navigatorutils.cpp index bc94e3f991..bfb0946b9e 100644 --- a/components/detournavigator/navigatorutils.cpp +++ b/components/detournavigator/navigatorutils.cpp @@ -11,7 +11,7 @@ namespace DetourNavigator const auto navMesh = navigator.getNavMesh(agentHalfExtents); if (!navMesh) return std::nullopt; - const auto settings = navigator.getSettings(); + const auto& settings = navigator.getSettings(); const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings.mRecast, agentHalfExtents), toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, maxRadius), includeFlags, settings.mDetour); @@ -26,7 +26,7 @@ namespace DetourNavigator const auto navMesh = navigator.getNavMesh(agentHalfExtents); if (navMesh == nullptr) return std::nullopt; - const auto settings = navigator.getSettings(); + const auto& settings = navigator.getSettings(); const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings.mRecast, agentHalfExtents), toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags, settings.mDetour); diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index c84a1798ff..416e8a9be2 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -70,7 +70,7 @@ void ESMReader::resolveParentFileIndices(const std::vector& allPlugin const ESMReader& reader = allPlugins.at(i); if (reader.getFileSize() == 0) continue; // Content file in non-ESM format - const std::string candidate = reader.getName(); + const std::string& candidate = reader.getName(); std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { index = i; From 2c5269536131d5134d825e0cfc77081a510fe83d Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Tue, 4 Jan 2022 12:23:37 -0800 Subject: [PATCH 1921/2859] fix regression where LEQUAL was used instead of LESS for depth function --- apps/openmw/mwrender/skyutil.cpp | 2 +- components/terrain/material.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index c0b6f81358..ead9d9af70 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -816,7 +816,7 @@ namespace MWRender osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { - osg::ref_ptr depth = new SceneUtil::AutoDepth; + osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LEQUAL); // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index ae432d262e..6ae1a970d3 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -106,7 +106,7 @@ namespace osg::ref_ptr mValue; LequalDepth() - : mValue(new SceneUtil::AutoDepth) + : mValue(new SceneUtil::AutoDepth(osg::Depth::LEQUAL)) { } }; From 27af7767352ede07f33f5e7e74aae8f9a2a64d3a Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 4 Jan 2022 22:01:52 +0000 Subject: [PATCH 1922/2859] Fix unnecessary float-to-double promotion --- apps/openmw/mwmechanics/creaturestats.cpp | 2 +- apps/openmw/mwmechanics/trading.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index c99f87e833..570547e0d9 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -44,7 +44,7 @@ namespace MWMechanics float max = getFatigue().getModified(); float current = getFatigue().getCurrent(); - float normalised = floor(max) == 0 ? 1 : std::max (0.0f, current / max); + float normalised = std::floor(max) == 0 ? 1 : std::max (0.0f, current / max); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index 750fd803d4..d9ec66bab2 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -71,10 +71,10 @@ namespace MWMechanics int initialMerchantOffer = std::abs(merchantOffer); if ( !buying && (finalPrice > initialMerchantOffer) ) { - skillGain = floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); + skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); } else if ( buying && (finalPrice < initialMerchantOffer) ) { - skillGain = floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); + skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); } player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); From 909aa43ba12b1d06cf822783215ac84ee6a3730d Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Tue, 4 Jan 2022 14:10:39 -0800 Subject: [PATCH 1923/2859] update sky particle node correctly --- apps/openmw/mwrender/sky.cpp | 7 ++++--- apps/openmw/mwrender/sky.hpp | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index c0a071c062..7b124b018c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -248,6 +248,7 @@ namespace MWRender , mEnabled(true) , mSunEnabled(true) , mPrecipitationAlpha(0.f) + , mDirtyParticlesEffect(false) { osg::ref_ptr skyroot = new CameraRelativeTransform; skyroot->setName("Sky Root"); @@ -537,8 +538,7 @@ namespace MWRender if (!enabled && mParticleNode && mParticleEffect) { mCurrentParticleEffect.clear(); - mParticleNode->removeChild(mParticleEffect); - mParticleEffect = nullptr; + mDirtyParticlesEffect = true; } mEnabled = enabled; @@ -610,8 +610,9 @@ namespace MWRender if (mIsStorm) mStormDirection = weather.mStormDirection; - if (mCurrentParticleEffect != weather.mParticleEffect) + if (mDirtyParticlesEffect || (mCurrentParticleEffect != weather.mParticleEffect)) { + mDirtyParticlesEffect = false; mCurrentParticleEffect = weather.mParticleEffect; // cleanup old particles diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 1a30633886..e2ceae45f4 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -186,6 +186,7 @@ namespace MWRender bool mSunEnabled; float mPrecipitationAlpha; + bool mDirtyParticlesEffect; osg::Vec4f mMoonScriptColor; }; From 4eea734551ff83588a1b756c51b6f6e9f19d52cc Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 29 Dec 2021 13:01:51 +0100 Subject: [PATCH 1924/2859] Resolves #6088 by rolling our own PacketList that replaces the deprecated AVPacketList; initial work done by akortunov in https://github.com/akortunov/openmw/commit/60aec0460604b32c5e84ecb0cff922d5f6db9855; adapted and cleaned up some old cruft along the way. --- extern/osg-ffmpeg-videoplayer/videostate.cpp | 82 ++++++++++---------- extern/osg-ffmpeg-videoplayer/videostate.hpp | 14 +++- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 7fcad33697..8b7e8b0771 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -4,10 +4,12 @@ #include #include #include +#include #include #include #include +#include #if defined(_MSC_VER) #pragma warning (push) @@ -62,20 +64,20 @@ namespace av_frame_free(&frame); } }; - - template - struct AVFree - { - void operator()(T* frame) const - { - av_free(&frame); - } - }; } namespace Video { +struct PacketListFree +{ + void operator()(Video::PacketList* list) const + { + av_packet_free(&list->pkt); + av_free(&list); + } +}; + VideoState::VideoState() : mAudioFactory(nullptr) , format_ctx(nullptr) @@ -114,25 +116,29 @@ void VideoState::setAudioFactory(MovieAudioFactory *factory) void PacketQueue::put(AVPacket *pkt) { - std::unique_ptr> pkt1(static_cast(av_malloc(sizeof(AVPacketList)))); + std::unique_ptr pkt1(static_cast(av_malloc(sizeof(PacketList)))); if(!pkt1) throw std::bad_alloc(); + if(pkt == &flush_pkt) - pkt1->pkt = *pkt; + pkt1->pkt = pkt; else - av_packet_move_ref(&pkt1->pkt, pkt); + { + pkt1->pkt = av_packet_alloc(); + av_packet_move_ref(pkt1->pkt, pkt); + } pkt1->next = nullptr; std::lock_guard lock(this->mutex); - AVPacketList* ptr = pkt1.release(); + PacketList* ptr = pkt1.release(); if(!last_pkt) this->first_pkt = ptr; else this->last_pkt->next = ptr; this->last_pkt = ptr; this->nb_packets++; - this->size += ptr->pkt.size; + this->size += ptr->pkt->size; this->cond.notify_one(); } @@ -141,17 +147,17 @@ int PacketQueue::get(AVPacket *pkt, VideoState *is) std::unique_lock lock(this->mutex); while(!is->mQuit) { - AVPacketList *pkt1 = this->first_pkt; + PacketList *pkt1 = this->first_pkt; if(pkt1) { this->first_pkt = pkt1->next; if(!this->first_pkt) this->last_pkt = nullptr; this->nb_packets--; - this->size -= pkt1->pkt.size; + this->size -= pkt1->pkt->size; av_packet_unref(pkt); - av_packet_move_ref(pkt, &pkt1->pkt); + av_packet_move_ref(pkt, pkt1->pkt); av_free(pkt1); return 1; @@ -173,14 +179,14 @@ void PacketQueue::flush() void PacketQueue::clear() { - AVPacketList *pkt, *pkt1; + PacketList *pkt, *pkt1; std::lock_guard lock(this->mutex); for(pkt = this->first_pkt; pkt != nullptr; pkt = pkt1) { pkt1 = pkt->next; - if (pkt->pkt.data != flush_pkt.data) - av_packet_unref(&pkt->pkt); + if (pkt->pkt->data != flush_pkt.data) + av_packet_unref(pkt->pkt); av_freep(&pkt); } this->last_pkt = nullptr; @@ -415,7 +421,7 @@ double VideoState::synchronize_video(const AVFrame &src_frame, double pts) class VideoThread { public: - VideoThread(VideoState* self) + explicit VideoThread(VideoState* self) : mVideoState(self) , mThread([this] { @@ -439,9 +445,8 @@ public: void run() { VideoState* self = mVideoState; - AVPacket packetData; - av_init_packet(&packetData); - std::unique_ptr packet(&packetData); + AVPacket* packetData = av_packet_alloc(); + std::unique_ptr packet(packetData); std::unique_ptr pFrame{av_frame_alloc()}; while(self->videoq.get(packet.get(), self) >= 0) @@ -491,7 +496,7 @@ private: class ParseThread { public: - ParseThread(VideoState* self) + explicit ParseThread(VideoState* self) : mVideoState(self) , mThread([this] { run(); }) { @@ -507,9 +512,8 @@ public: VideoState* self = mVideoState; AVFormatContext *pFormatCtx = self->format_ctx; - AVPacket packetData; - av_init_packet(&packetData); - std::unique_ptr packet(&packetData); + AVPacket* packetData = av_packet_alloc(); + std::unique_ptr packet(packetData); try { @@ -632,13 +636,11 @@ bool VideoState::update() int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) { - const AVCodec *codec; - if(stream_index < 0 || stream_index >= static_cast(pFormatCtx->nb_streams)) return -1; // Get a pointer to the codec context for the video stream - codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codecpar->codec_id); + const AVCodec *codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codecpar->codec_id); if(!codec) { fprintf(stderr, "Unsupported codec!\n"); @@ -702,7 +704,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) return -1; } - this->video_thread.reset(new VideoThread(this)); + this->video_thread = std::make_unique(this); break; default: @@ -721,7 +723,7 @@ void VideoState::init(std::shared_ptr inputstream, const std::stri this->av_sync_type = AV_SYNC_DEFAULT; this->mQuit = false; - this->stream = inputstream; + this->stream = std::move(inputstream); if(!this->stream.get()) throw std::runtime_error("Failed to open video resource"); @@ -789,7 +791,7 @@ void VideoState::init(std::shared_ptr inputstream, const std::stri } - this->parse_thread.reset(new ParseThread(this)); + this->parse_thread = std::make_unique(this); } void VideoState::deinit() @@ -801,11 +803,11 @@ void VideoState::deinit() mAudioDecoder.reset(); - if (this->parse_thread.get()) + if (this->parse_thread) { this->parse_thread.reset(); } - if (this->video_thread.get()) + if (this->video_thread) { this->video_thread.reset(); } @@ -851,8 +853,8 @@ void VideoState::deinit() } // Dellocate RGBA frame queue. - for (std::size_t i = 0; i < VIDEO_PICTURE_ARRAY_SIZE; ++i) - this->pictq[i].rgbaFrame = nullptr; + for (auto & i : this->pictq) + i.rgbaFrame = nullptr; } @@ -870,7 +872,7 @@ double VideoState::get_master_clock() return this->get_external_clock(); } -double VideoState::get_video_clock() +double VideoState::get_video_clock() const { return this->frame_last_pts; } @@ -896,7 +898,7 @@ void VideoState::seekTo(double time) mSeekRequested = true; } -double VideoState::getDuration() +double VideoState::getDuration() const { return this->format_ctx->duration / 1000000.0; } diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index 32a772f299..654cd1a814 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -46,7 +46,6 @@ extern "C" extern "C" { struct SwsContext; - struct AVPacketList; struct AVPacket; struct AVFormatContext; struct AVStream; @@ -78,6 +77,13 @@ struct ExternalClock void set(uint64_t time); }; +class PacketList +{ +public: + AVPacket* pkt = nullptr; + PacketList *next = nullptr; +}; + struct PacketQueue { PacketQueue() : first_pkt(nullptr), last_pkt(nullptr), flushing(false), nb_packets(0), size(0) @@ -85,7 +91,7 @@ struct PacketQueue { ~PacketQueue() { clear(); } - AVPacketList *first_pkt, *last_pkt; + PacketList *first_pkt, *last_pkt; std::atomic flushing; std::atomic nb_packets; std::atomic size; @@ -129,7 +135,7 @@ struct VideoState { void setPaused(bool isPaused); void seekTo(double time); - double getDuration(); + double getDuration() const; int stream_open(int stream_index, AVFormatContext *pFormatCtx); @@ -145,7 +151,7 @@ struct VideoState { double synchronize_video(const AVFrame &src_frame, double pts); double get_audio_clock(); - double get_video_clock(); + double get_video_clock() const; double get_external_clock(); double get_master_clock(); From bae87139982f06af966f817125f6a88ab3f7a601 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 5 Jan 2022 09:26:02 +0100 Subject: [PATCH 1925/2859] make use of std::array `pictq.size()` instead of VIDEO_PICTURE_ARRAY_SIZE; remove redundant smart-pointer .get calls; fix typos --- extern/osg-ffmpeg-videoplayer/videostate.cpp | 22 ++++++++++---------- extern/osg-ffmpeg-videoplayer/videostate.hpp | 7 +++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 8b7e8b0771..0f3a48faa5 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -97,7 +97,7 @@ VideoState::VideoState() { mFlushPktData = flush_pkt.data; -// This is not needed anymore above FFMpeg version 4.0 +// This is not needed any more above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 av_register_all(); #endif @@ -310,7 +310,7 @@ void VideoState::video_refresh() VideoPicture* vp = &this->pictq[this->pictq_rindex]; this->video_display(vp); - this->pictq_rindex = (pictq_rindex+1) % VIDEO_PICTURE_ARRAY_SIZE; + this->pictq_rindex = (pictq_rindex+1) % pictq.size(); this->frame_last_pts = vp->pts; this->pictq_size--; this->pictq_cond.notify_one(); @@ -327,12 +327,12 @@ void VideoState::video_refresh() for (; ipictq_size-1; ++i) { if (this->pictq[pictq_rindex].pts + threshold <= this->get_master_clock()) - this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_ARRAY_SIZE; // not enough time to show this picture + this->pictq_rindex = (this->pictq_rindex+1) % pictq.size(); // not enough time to show this picture else break; } - assert (this->pictq_rindex < VIDEO_PICTURE_ARRAY_SIZE); + assert (this->pictq_rindex < pictq.size()); VideoPicture* vp = &this->pictq[this->pictq_rindex]; this->video_display(vp); @@ -342,7 +342,7 @@ void VideoState::video_refresh() this->pictq_size -= i; // update queue for next picture this->pictq_size--; - this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_ARRAY_SIZE; + this->pictq_rindex = (this->pictq_rindex+1) % pictq.size(); this->pictq_cond.notify_one(); } } @@ -392,7 +392,7 @@ int VideoState::queue_picture(const AVFrame &pFrame, double pts) 0, this->video_ctx->height, vp->rgbaFrame->data, vp->rgbaFrame->linesize); // now we inform our display thread that we have a pic ready - this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_ARRAY_SIZE; + this->pictq_windex = (this->pictq_windex+1) % pictq.size(); this->pictq_size++; return 0; @@ -656,7 +656,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) this->audio_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(this->audio_ctx, pFormatCtx->streams[stream_index]->codecpar); -// This is not needed anymore above FFMpeg version 4.0 +// This is not needed any more above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 av_codec_set_pkt_timebase(this->audio_ctx, pFormatCtx->streams[stream_index]->time_base); #endif @@ -693,7 +693,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) this->video_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(this->video_ctx, pFormatCtx->streams[stream_index]->codecpar); -// This is not needed anymore above FFMpeg version 4.0 +// This is not needed any more above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 av_codec_set_pkt_timebase(this->video_ctx, pFormatCtx->streams[stream_index]->time_base); #endif @@ -724,7 +724,7 @@ void VideoState::init(std::shared_ptr inputstream, const std::stri this->mQuit = false; this->stream = std::move(inputstream); - if(!this->stream.get()) + if(!this->stream) throw std::runtime_error("Failed to open video resource"); AVIOContext *ioCtx = avio_alloc_context(nullptr, 0, 0, this, istream_read, istream_write, istream_seek); @@ -852,7 +852,7 @@ void VideoState::deinit() mTexture = nullptr; } - // Dellocate RGBA frame queue. + // Deallocate RGBA frame queue. for (auto & i : this->pictq) i.rgbaFrame = nullptr; @@ -879,7 +879,7 @@ double VideoState::get_video_clock() const double VideoState::get_audio_clock() { - if (!mAudioDecoder.get()) + if (!mAudioDecoder) return 0.0; return mAudioDecoder->getAudioClock(); } diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index 654cd1a814..a53acd4183 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -1,8 +1,9 @@ #ifndef VIDEOPLAYER_VIDEOSTATE_H #define VIDEOPLAYER_VIDEOSTATE_H -#include +#include #include +#include #include #include #include @@ -40,8 +41,6 @@ extern "C" #include "videodefs.hpp" #define VIDEO_PICTURE_QUEUE_SIZE 50 -// allocate one extra to make sure we do not overwrite the osg::Image currently set on the texture -#define VIDEO_PICTURE_ARRAY_SIZE (VIDEO_PICTURE_QUEUE_SIZE+1) extern "C" { @@ -184,7 +183,7 @@ struct VideoState { PacketQueue videoq; SwsContext* sws_context; int sws_context_w, sws_context_h; - VideoPicture pictq[VIDEO_PICTURE_ARRAY_SIZE]; + std::array pictq; // allocate one extra to make sure we do not overwrite the osg::Image currently set on the texture int pictq_size, pictq_rindex, pictq_windex; std::mutex pictq_mutex; std::condition_variable pictq_cond; From a7166aa05ca9b81588a01a37d785c8762da09cb7 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 5 Jan 2022 10:43:30 +0000 Subject: [PATCH 1926/2859] Use updated resolutions when setting projection matrix --- apps/openmw/mwrender/renderingmanager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 73ee40435e..5ebd95ee44 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1138,7 +1138,10 @@ namespace MWRender void RenderingManager::updateProjectionMatrix() { - double aspect = mViewer->getCamera()->getViewport()->aspectRatio(); + double width = Settings::Manager::getInt("resolution x", "Video"); + double height = Settings::Manager::getInt("resolution y", "Video"); + + double aspect = (height == 0.0) ? 1.0 : width / height; float fov = mFieldOfView; if (mFieldOfViewOverridden) fov = mFieldOfViewOverride; @@ -1155,7 +1158,7 @@ namespace MWRender mSharedUniformStateUpdater->setNear(mNearClip); mSharedUniformStateUpdater->setFar(mViewDistance); - mSharedUniformStateUpdater->setScreenRes(mViewer->getCamera()->getViewport()->width(), mViewer->getCamera()->getViewport()->height()); + mSharedUniformStateUpdater->setScreenRes(width, height); // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. From 1a6be081499dd6006c5aa8cb4dc18ec36ed32772 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 5 Jan 2022 10:44:44 +0000 Subject: [PATCH 1927/2859] Fix "warning: loop variable is copied but only used as const reference;... --- components/resource/scenemanager.cpp | 4 ++-- components/sceneutil/mwshadowtechnique.cpp | 2 +- components/sceneutil/osgacontroller.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 4f0658e033..41f65ba0ab 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -273,7 +273,7 @@ namespace Resource correct format for OpenMW: alphatest mode value MaterialName e.g alphatest GEQUAL 0.8 MyAlphaTestedMaterial */ std::vector descriptions = node.getDescriptions(); - for (auto description : descriptions) + for (const auto & description : descriptions) { mDescriptions.emplace_back(description); } @@ -281,7 +281,7 @@ namespace Resource // Iterate each description, and see if the current node uses the specified material for alpha testing if (node.getStateSet()) { - for (auto description : mDescriptions) + for (const auto & description : mDescriptions) { std::vector descriptionParts; std::istringstream descriptionStringStream(description); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index daf6bb80ab..d56708e46c 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -1404,7 +1404,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) std::string validRegionUniformName = "validRegionMatrix" + std::to_string(sm_i); osg::ref_ptr validRegionUniform; - for (auto uniform : _uniforms[cv.getTraversalNumber() % 2]) + for (const auto & uniform : _uniforms[cv.getTraversalNumber() % 2]) { if (uniform->getName() == validRegionUniformName) validRegionUniform = uniform; diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 520b2d177b..4d0f7a460a 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -33,7 +33,7 @@ namespace SceneUtil if (channelTargetName != umt->getName()) continue; // check if we can link a StackedTransformElement to the current Channel - for (auto stackedTransform : umt->getStackedTransforms()) + for (const auto & stackedTransform : umt->getStackedTransforms()) { osgAnimation::StackedTransformElement* element = stackedTransform.get(); if (element && !element->getName().empty() && channelName == element->getName()) From f0db57661190b8bc94ad9fc337f5d4e25869c340 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 5 Jan 2022 12:19:22 +0100 Subject: [PATCH 1928/2859] removed redundant get on smart pointer; made pictq_[r|w]index unsigned longs to be type compatible with std::array pictq.size(); fixes assert issue --- extern/osg-ffmpeg-videoplayer/videostate.cpp | 2 +- extern/osg-ffmpeg-videoplayer/videostate.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 0f3a48faa5..ff1d9c6517 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -676,7 +676,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) } mAudioDecoder = mAudioFactory->createDecoder(this); - if (!mAudioDecoder.get()) + if (!mAudioDecoder) { std::cerr << "Failed to create audio decoder, can not play audio stream" << std::endl; avcodec_free_context(&this->audio_ctx); diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index a53acd4183..d1592bd910 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -184,7 +184,8 @@ struct VideoState { SwsContext* sws_context; int sws_context_w, sws_context_h; std::array pictq; // allocate one extra to make sure we do not overwrite the osg::Image currently set on the texture - int pictq_size, pictq_rindex, pictq_windex; + int pictq_size; + unsigned long pictq_rindex, pictq_windex; std::mutex pictq_mutex; std::condition_variable pictq_cond; From cd6edb961a064249f3ac3fab27bbbde8cb4e4121 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 5 Jan 2022 17:22:22 +0100 Subject: [PATCH 1929/2859] Remove dead code --- apps/openmw/mwmechanics/magiceffects.cpp | 22 ---------------------- apps/openmw/mwmechanics/magiceffects.hpp | 2 -- 2 files changed, 24 deletions(-) diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 39d340e6ba..e9e0be397e 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -133,28 +133,6 @@ namespace MWMechanics } } - MagicEffects& MagicEffects::operator+= (const MagicEffects& effects) - { - if (this==&effects) - { - const MagicEffects& temp (effects); - *this += temp; - return *this; - } - - for (Collection::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) - { - Collection::iterator result = mCollection.find (iter->first); - - if (result!=mCollection.end()) - result->second += iter->second; - else - mCollection.insert (*iter); - } - - return *this; - } - EffectParam MagicEffects::get (const EffectKey& key) const { Collection::const_iterator iter = mCollection.find (key); diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 50f4dab050..e8175f6a78 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -97,8 +97,6 @@ namespace MWMechanics /// Copy Modifier values from \a effects, but keep original mBase values. void setModifiers(const MagicEffects& effects); - MagicEffects& operator+= (const MagicEffects& effects); - EffectParam get (const EffectKey& key) const; ///< This function can safely be used for keys that are not present. From 5eca122f0424b5194715c0b086fd37dd7298f8d5 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 5 Jan 2022 19:50:52 +0100 Subject: [PATCH 1930/2859] Avoid creating multiple i18n contexts with the same name. --- components/lua/i18n.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/lua/i18n.cpp b/components/lua/i18n.cpp index 9fd2724f75..e4d8e3ba78 100644 --- a/components/lua/i18n.cpp +++ b/components/lua/i18n.cpp @@ -99,6 +99,9 @@ namespace LuaUtil { if (mI18nLoader == sol::nil) throw std::runtime_error("LuaUtil::I18nManager is not initialized"); + auto it = mContexts.find(contextName); + if (it != mContexts.end()) + return sol::make_object(mLua->sol(), it->second); Context ctx{contextName, mLua->newTable(), call(mI18nLoader, "i18n.init")}; ctx.updateLang(this); mContexts.emplace(contextName, ctx); From 6a19a66ae5fa15c416fc73cfd2a71b7e9a4f9ca8 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 5 Jan 2022 20:08:03 +0100 Subject: [PATCH 1931/2859] Fix #6535 --- components/lua/luastate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index cee48b4545..6ff698c9c2 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -170,7 +170,7 @@ namespace LuaUtil sol::environment env(mLua, sol::create, mSandboxEnv); sol::table loaded(mLua, sol::create); for (const std::string& s : safePackages) - loaded[s] = mSandboxEnv[s]; + loaded[s] = static_cast(mSandboxEnv[s]); env["require"] = [this, loaded, env](const std::string& module) mutable { if (loaded[module] != sol::nil) From 6672014a518d01bc53a65aed770ba97726e77619 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 6 Jan 2022 15:02:24 +0100 Subject: [PATCH 1932/2859] make this great again --- extern/osg-ffmpeg-videoplayer/videostate.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index ff1d9c6517..9b07ece14d 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -310,7 +310,7 @@ void VideoState::video_refresh() VideoPicture* vp = &this->pictq[this->pictq_rindex]; this->video_display(vp); - this->pictq_rindex = (pictq_rindex+1) % pictq.size(); + this->pictq_rindex = (this->pictq_rindex+1) % this->pictq.size(); this->frame_last_pts = vp->pts; this->pictq_size--; this->pictq_cond.notify_one(); @@ -326,13 +326,13 @@ void VideoState::video_refresh() int i=0; for (; ipictq_size-1; ++i) { - if (this->pictq[pictq_rindex].pts + threshold <= this->get_master_clock()) - this->pictq_rindex = (this->pictq_rindex+1) % pictq.size(); // not enough time to show this picture + if (this->pictq[this->pictq_rindex].pts + threshold <= this->get_master_clock()) + this->pictq_rindex = (this->pictq_rindex+1) % this->pictq.size(); // not enough time to show this picture else break; } - assert (this->pictq_rindex < pictq.size()); + assert (this->pictq_rindex < this->pictq.size()); VideoPicture* vp = &this->pictq[this->pictq_rindex]; this->video_display(vp); @@ -342,7 +342,7 @@ void VideoState::video_refresh() this->pictq_size -= i; // update queue for next picture this->pictq_size--; - this->pictq_rindex = (this->pictq_rindex+1) % pictq.size(); + this->pictq_rindex = (this->pictq_rindex+1) % this->pictq.size(); this->pictq_cond.notify_one(); } } @@ -392,7 +392,7 @@ int VideoState::queue_picture(const AVFrame &pFrame, double pts) 0, this->video_ctx->height, vp->rgbaFrame->data, vp->rgbaFrame->linesize); // now we inform our display thread that we have a pic ready - this->pictq_windex = (this->pictq_windex+1) % pictq.size(); + this->pictq_windex = (this->pictq_windex+1) % this->pictq.size(); this->pictq_size++; return 0; From cdae1c4c978501b5b8c55eb3fecff64263ffbafd Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Thu, 6 Jan 2022 22:01:28 +0000 Subject: [PATCH 1933/2859] Heavily mitigate jittery objects far from origin --- apps/openmw/mwphysics/ptrholder.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index e194f8e934..7366049cba 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include "../mwworld/ptr.hpp" @@ -56,12 +58,12 @@ namespace MWPhysics mPosition = position; } - osg::Vec3f getPosition() const + osg::Vec3d getPosition() const { return mPosition; } - osg::Vec3f getPreviousPosition() const + osg::Vec3d getPreviousPosition() const { return mPreviousPosition; } @@ -71,8 +73,8 @@ namespace MWPhysics std::unique_ptr mCollisionObject; osg::Vec3f mVelocity; osg::Vec3f mSimulationPosition; - osg::Vec3f mPosition; - osg::Vec3f mPreviousPosition; + osg::Vec3d mPosition; + osg::Vec3d mPreviousPosition; }; } From 63a8bc5f9b3fa2280e539abe6c8ebd1a7982f875 Mon Sep 17 00:00:00 2001 From: Josquin Frei Date: Fri, 7 Jan 2022 14:35:11 +0000 Subject: [PATCH 1934/2859] Fix wizard dialogs --- apps/wizard/mainwizard.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index d92f5b029c..064f0813d8 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -88,8 +88,9 @@ void Wizard::MainWizard::setupLog() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(mLogError.arg(file.fileName())); + connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); - return qApp->quit(); + return; } addLogText(QString("Started OpenMW Wizard on %1").arg(QDateTime::currentDateTime().toString())); @@ -110,8 +111,9 @@ void Wizard::MainWizard::addLogText(const QString &text) msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(mLogError.arg(file.fileName())); + connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); - return qApp->quit(); + return; } if (!file.isSequential()) @@ -148,8 +150,9 @@ void Wizard::MainWizard::setupGameSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(message.arg(file.fileName())); + connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); - return qApp->quit(); + return; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); @@ -177,8 +180,9 @@ void Wizard::MainWizard::setupGameSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(message.arg(file.fileName())); - - return qApp->quit(); + connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); + msgBox.exec(); + return; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); @@ -210,8 +214,9 @@ void Wizard::MainWizard::setupLauncherSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(message.arg(file.fileName())); + connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); - return qApp->quit(); + return; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); @@ -394,8 +399,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setText(tr("

Could not create %1

\

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

").arg(userPath)); + connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); - return qApp->quit(); + return; } } @@ -411,8 +417,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setText(tr("

Could not open %1 for writing

\

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

").arg(file.fileName())); + connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); - return qApp->quit(); + return; } QTextStream stream(&file); @@ -433,8 +440,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setText(tr("

Could not open %1 for writing

\

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

").arg(file.fileName())); + connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); - return qApp->quit(); + return; } stream.setDevice(&file); From b5a7ad6d62dba84b4eed88fc9a9a76f06312a5a6 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 8 Jan 2022 01:58:52 +0300 Subject: [PATCH 1935/2859] Fix multilayer parallax parameter reading --- components/nif/property.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 70acbb82ae..ee47e8ccbe 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -180,7 +180,7 @@ void BSLightingShaderProperty::read(NIFStream *nif) case BSLightingShaderType::ShaderType_MultiLayerParallax: nif->skip(4); // Inner layer thickness nif->skip(4); // Refraction scale - nif->skip(4); // Inner layer texture scale + nif->skip(8); // Inner layer texture scale nif->skip(4); // Environment map strength break; case BSLightingShaderType::ShaderType_SparkleSnow: From 5d7db94a233a9ad30277d936530427bf6bd36979 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sat, 8 Jan 2022 22:08:05 +0100 Subject: [PATCH 1936/2859] Keep coverity logs as artifacts instead of deploying them --- .gitlab-ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 105057b860..8275df7572 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -81,14 +81,13 @@ Coverity: --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL --form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" - - cat /builds/OpenMW/openmw/cov-int/build-log.txt variables: CC: clang CXX: clang++ CXXFLAGS: -O0 artifacts: - paths: [] - expire_in: 1 minute + paths: + - /builds/OpenMW/openmw/cov-int/build-log.txt Ubuntu_GCC: extends: .Ubuntu From e32d3d11ce53e97c27a02047632a99f30bf1a470 Mon Sep 17 00:00:00 2001 From: cody glassman Date: Sat, 8 Jan 2022 16:12:24 -0800 Subject: [PATCH 1937/2859] ensure readbuffer is bound before using glReadPixels --- apps/openmw/mwrender/screenshotmanager.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 1973ca4025..5c3d925c9b 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -91,15 +91,18 @@ namespace MWRender int width = screenW - leftPadding*2; int height = screenH - topPadding*2; - // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer when in use. - // glReadPixel() cannot read from multisampled targets. + // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer. Also ensure that the readbuffer is set correctly with rendeirng to FBO. + // glReadPixel() cannot read from multisampled targets PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); - if (postProcessor && postProcessor->getFbo() && postProcessor->getMsaaFbo()) + if (postProcessor && postProcessor->getFbo()) { osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); if (ext) + { ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, postProcessor->getFbo()->getHandle(renderInfo.getContextID())); + renderInfo.getState()->glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); + } } mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); From 8216410e7d50ffd2bf6d0a62810880eb9da83c3b Mon Sep 17 00:00:00 2001 From: cody glassman Date: Sat, 8 Jan 2022 16:19:42 -0800 Subject: [PATCH 1938/2859] add white ambient to spell VFX as in Morrowind --- CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 419727c87d..3ee15e0ea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed + Bug #4949: Incorrect particle lighting Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index bf0ed04055..732ffd33aa 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -372,6 +373,19 @@ namespace private: int mEffectId; }; + + osg::ref_ptr getVFXLightModelInstance() + { + static osg::ref_ptr lightModel = nullptr; + + if (!lightModel) + { + lightModel = new osg::LightModel; + lightModel->setAmbientIntensity({1,1,1,1}); + } + + return lightModel; + } } namespace MWRender @@ -1547,7 +1561,9 @@ namespace MWRender parentNode->addChild(trans); osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, trans); - node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + // 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); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); From cd1bea080f4e5f73c8fc273ffae14208d3068b5d Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Sun, 9 Jan 2022 09:13:38 +0000 Subject: [PATCH 1939/2859] Update changelog and remove an unused setting from settings-default.cfg --- CHANGELOG.md | 2 ++ files/settings-default.cfg | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 419727c87d..63fcb91cef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ Bug #6517: Rotations for KeyframeData in NIFs should be optional Bug #6519: Effects tooltips for ingredients work incorrectly Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary + Bug #6544: Far from world origin objects jitter when camera is still Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record @@ -123,6 +124,7 @@ Feature #6288: Preserve the "blocked" record flag for referenceable objects. Feature #6380: Commas are treated as whitespace in vanilla Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference + Feature #6534: Shader-based object texture blending Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp diff --git a/files/settings-default.cfg b/files/settings-default.cfg index acd011f233..06e080fdfd 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -152,9 +152,6 @@ object paging min size merge factor = 0.3 # Controls how inexpensive an object needs to be to utilize 'min size merge factor'. object paging min size cost multiplier = 25 -# Assign a random color to merged batches. -object paging debug batches = false - [Fog] # If true, use extended fog parameters for distant terrain not controlled by From d9672f7d468338cb2e96e775b8494dc85131a546 Mon Sep 17 00:00:00 2001 From: Josquin Frei Date: Mon, 10 Jan 2022 12:42:03 +0000 Subject: [PATCH 1940/2859] Add serialization for TransformM and TransformQ --- AUTHORS.md | 1 + .../lua/test_serialization.cpp | 30 ++++++++++++++ components/lua/serialization.cpp | 40 +++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 67be756f33..2ddaa03cf6 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -115,6 +115,7 @@ Programmers John Blomberg (fstp) Jordan Ayers Jordan Milne + Josquin Frei Josua Grawitter Jules Blok (Armada651) julianko diff --git a/apps/openmw_test_suite/lua/test_serialization.cpp b/apps/openmw_test_suite/lua/test_serialization.cpp index 9300e4571b..57a1070c83 100644 --- a/apps/openmw_test_suite/lua/test_serialization.cpp +++ b/apps/openmw_test_suite/lua/test_serialization.cpp @@ -1,10 +1,13 @@ #include "gmock/gmock.h" #include +#include +#include #include #include #include +#include #include @@ -104,6 +107,33 @@ namespace } } + TEST(LuaSerializationTest, Transform) { + sol::state lua; + osg::Matrixf matrix(1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16); + LuaUtil::TransformM transM = LuaUtil::asTransform(matrix); + osg::Quat quat(1, 2, 3, 4); + LuaUtil::TransformQ transQ = LuaUtil::asTransform(quat); + + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, transM)); + EXPECT_EQ(serialized.size(), 130); // version, type, 16x double + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as().mM, transM.mM); + } + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, transQ)); + EXPECT_EQ(serialized.size(), 34); // version, type, 4x double + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as().mQ, transQ.mQ); + } + + } + TEST(LuaSerializationTest, Table) { sol::state lua; diff --git a/components/lua/serialization.cpp b/components/lua/serialization.cpp index f9b8951e4e..8b18294449 100644 --- a/components/lua/serialization.cpp +++ b/components/lua/serialization.cpp @@ -1,11 +1,15 @@ #include "serialization.hpp" +#include +#include #include #include +#include #include #include "luastate.hpp" +#include "utilpackage.hpp" namespace LuaUtil { @@ -22,6 +26,8 @@ namespace LuaUtil VEC2 = 0x10, VEC3 = 0x11, + TRANSFORM_M = 0x12, + TRANSFORM_Q = 0x13, // All values should be lesser than 0x20 (SHORT_STRING_FLAG). }; @@ -106,6 +112,23 @@ namespace LuaUtil appendValue(out, v.z()); return; } + if (data.is()) + { + appendType(out, SerializedType::TRANSFORM_M); + osg::Matrixf matrix = data.as().mM; + for (size_t i = 0; i < 4; i++) + for (size_t j = 0; j < 4; j++) + appendValue(out, matrix(i,j)); + return; + } + if (data.is()) + { + appendType(out, SerializedType::TRANSFORM_Q); + osg::Quat quat = data.as().mQ; + for(size_t i = 0; i < 4; i++) + appendValue(out, quat[i]); + return; + } if (customSerializer && customSerializer->serialize(out, data)) return; else @@ -231,6 +254,23 @@ namespace LuaUtil sol::stack::push(lua, osg::Vec3f(x, y, z)); return; } + case SerializedType::TRANSFORM_M: + { + osg::Matrixf mat; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + mat(i, j) = getValue(binaryData); + sol::stack::push(lua, asTransform(mat)); + return; + } + case SerializedType::TRANSFORM_Q: + { + osg::Quat q; + for (int i = 0; i < 4; i++) + q[i] = getValue(binaryData); + sol::stack::push(lua, asTransform(q)); + return; + } } throw std::runtime_error("Unknown type in serialized data: " + std::to_string(type)); } From 877f5c445ed296315505e826bceace9b2b92a1d8 Mon Sep 17 00:00:00 2001 From: psi29a Date: Mon, 10 Jan 2022 19:38:55 +0000 Subject: [PATCH 1941/2859] Add librecast-dev to deps that needed for Debian/Ubuntu --- CI/install_debian_deps.sh | 3 +-- docker/Dockerfile.ubuntu | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 945c5f09bf..131ccae305 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -22,9 +22,8 @@ declare -rA GROUPED_DEPS=( libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev - ca-certificates + librecast-dev libsqlite3-dev ca-certificates " - # TODO: add librecastnavigation-dev when debian is ready # These dependencies can alternatively be built and linked statically. [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev" diff --git a/docker/Dockerfile.ubuntu b/docker/Dockerfile.ubuntu index 51aaa6465e..b5fadff3e3 100644 --- a/docker/Dockerfile.ubuntu +++ b/docker/Dockerfile.ubuntu @@ -13,7 +13,7 @@ RUN apt-get update \ libmygui-dev libunshield-dev liblz4-dev libtinyxml-dev libqt5opengl5-dev \ libboost-filesystem-dev libboost-program-options-dev libboost-iostreams-dev \ libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev \ - librecastnavigation-dev libluajit-5.1-dev + librecast-dev libsqlite3-dev libluajit-5.1-dev COPY build.sh /build.sh From 2d1b1002397f7e7394b18d9e35a658e2f5bcb4ee Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 1 Dec 2021 21:28:05 +0100 Subject: [PATCH 1942/2859] Change terminology of gameSecond/gameHour to simulationTime/gameTime --- apps/openmw/mwlua/asyncbindings.cpp | 22 ++-- apps/openmw/mwlua/luabindings.cpp | 28 ++++- apps/openmw/mwlua/luamanagerimp.cpp | 10 +- apps/openmw/mwlua/worldview.cpp | 8 +- apps/openmw/mwlua/worldview.hpp | 17 +-- .../lua/test_scriptscontainer.cpp | 26 ++--- components/esm/luascripts.cpp | 4 +- components/esm/luascripts.hpp | 8 +- components/lua/luastate.cpp | 7 +- components/lua/scriptscontainer.cpp | 40 +++---- components/lua/scriptscontainer.hpp | 19 ++-- docs/source/reference/lua-scripting/api.rst | 3 + .../lua-scripting/openmw_aux_time.rst | 5 + .../reference/lua-scripting/overview.rst | 9 +- files/builtin_scripts/openmw_aux/time.lua | 104 ++++++++++++++++++ files/builtin_scripts/openmw_aux/util.lua | 67 ----------- files/lua_api/openmw/async.lua | 16 +-- files/lua_api/openmw/core.lua | 19 +++- files/lua_api/openmw/world.lua | 30 +++++ files/lua_api/os.doclua | 64 +++++++++++ 20 files changed, 342 insertions(+), 164 deletions(-) create mode 100644 docs/source/reference/lua-scripting/openmw_aux_time.rst create mode 100644 files/builtin_scripts/openmw_aux/time.lua create mode 100644 files/lua_api/os.doclua diff --git a/apps/openmw/mwlua/asyncbindings.cpp b/apps/openmw/mwlua/asyncbindings.cpp index 9bddf75ee4..0ffb2aad8f 100644 --- a/apps/openmw/mwlua/asyncbindings.cpp +++ b/apps/openmw/mwlua/asyncbindings.cpp @@ -19,36 +19,36 @@ namespace MWLua sol::function getAsyncPackageInitializer(const Context& context) { - using TimeUnit = LuaUtil::ScriptsContainer::TimeUnit; + using TimerType = LuaUtil::ScriptsContainer::TimerType; sol::usertype api = context.mLua->sol().new_usertype("AsyncPackage"); api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback) { asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback)); return TimerCallback{asyncId, std::string(name)}; }; - api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay, - const TimerCallback& callback, sol::object callbackArg) + api["newSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay, + const TimerCallback& callback, sol::object callbackArg) { callback.mAsyncId.mContainer->setupSerializableTimer( - TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, + TimerType::SIMULATION_TIME, world->getSimulationTime() + delay, callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg)); }; - api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay, - const TimerCallback& callback, sol::object callbackArg) + api["newGameTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay, + const TimerCallback& callback, sol::object callbackArg) { callback.mAsyncId.mContainer->setupSerializableTimer( - TimeUnit::HOURS, world->getGameTimeInHours() + delay, + TimerType::GAME_TIME, world->getGameTime() + delay, callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg)); }; - api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) + api["newUnsavableSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) { asyncId.mContainer->setupUnsavableTimer( - TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScriptId, std::move(callback)); + TimerType::SIMULATION_TIME, world->getSimulationTime() + delay, asyncId.mScriptId, std::move(callback)); }; - api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) + api["newUnsavableGameTimer"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback) { asyncId.mContainer->setupUnsavableTimer( - TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScriptId, std::move(callback)); + TimerType::GAME_TIME, world->getGameTime() + delay, asyncId.mScriptId, std::move(callback)); }; api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) { diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index c525fd8a23..5a9e33faef 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -22,11 +22,32 @@ namespace MWLua return LuaUtil::makeReadOnly(res); } + static void addTimeBindings(sol::table& api, const Context& context, bool global) + { + api["getSimulationTime"] = [world=context.mWorldView]() { return world->getSimulationTime(); }; + api["getSimulationTimeScale"] = [world=context.mWorldView]() { return world->getSimulationTimeScale(); }; + api["getGameTime"] = [world=context.mWorldView]() { return world->getGameTime(); }; + api["getGameTimeScale"] = [world=context.mWorldView]() { return world->getGameTimeScale(); }; + api["isWorldPaused"] = [world=context.mWorldView]() { return world->isPaused(); }; + + if (!global) + return; + + api["setGameTimeScale"] = [world=context.mWorldView](double scale) { world->setGameTimeScale(scale); }; + + // TODO: Ability to make game time slower or faster than real time (needed for example for mechanics like VATS) + // api["setSimulationTimeScale"] = [](double scale) {}; + + // TODO: Ability to pause/resume world from Lua (needed for UI dehardcoding) + // api["pause"] = []() {}; + // api["resume"] = []() {}; + } + sol::table initCorePackage(const Context& context) { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 12; + api["API_REVISION"] = 13; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); @@ -36,9 +57,7 @@ namespace MWLua { context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; - api["getGameTimeInSeconds"] = [world=context.mWorldView]() { return world->getGameTimeInSeconds(); }; - api["getGameTimeInHours"] = [world=context.mWorldView]() { return world->getGameTimeInHours(); }; - api["isWorldPaused"] = [world=context.mWorldView]() { return world->isPaused(); }; + addTimeBindings(api, context, false); api["OBJECT_TYPE"] = definitionList(*lua, { "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", @@ -73,6 +92,7 @@ namespace MWLua { sol::table api(context.mLua->sol(), sol::create); WorldView* worldView = context.mWorldView; + addTimeBindings(api, context, true); api["getCellByName"] = [worldView=context.mWorldView](const std::string& name) -> sol::optional { MWWorld::CellStore* cell = worldView->findNamedCell(name); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index be5764c32f..658ee7c809 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -113,13 +113,13 @@ namespace MWLua if (!mWorldView.isPaused()) { // Update time and process timers - double seconds = mWorldView.getGameTimeInSeconds() + frameDuration; - mWorldView.setGameTimeInSeconds(seconds); - double hours = mWorldView.getGameTimeInHours(); + double simulationTime = mWorldView.getSimulationTime() + frameDuration; + mWorldView.setSimulationTime(simulationTime); + double gameTime = mWorldView.getGameTime(); - mGlobalScripts.processTimers(seconds, hours); + mGlobalScripts.processTimers(simulationTime, gameTime); for (LocalScripts* scripts : mActiveLocalScripts) - scripts->processTimers(seconds, hours); + scripts->processTimers(simulationTime, gameTime); } // Receive events diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index 219035745a..35b1db7a93 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -70,16 +70,16 @@ namespace MWLua removeFromGroup(*group, ptr); } - double WorldView::getGameTimeInHours() const + double WorldView::getGameTime() const { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::TimeStamp timeStamp = world->getTimeStamp(); - return static_cast(timeStamp.getDay()) * 24 + timeStamp.getHour(); + return (static_cast(timeStamp.getDay()) * 24 + timeStamp.getHour()) * 3600.0; } void WorldView::load(ESM::ESMReader& esm) { - esm.getHNT(mGameSeconds, "LUAW"); + esm.getHNT(mSimulationTime, "LUAW"); ObjectId lastAssignedId; lastAssignedId.load(esm, true); mObjectRegistry.setLastAssignedId(lastAssignedId); @@ -87,7 +87,7 @@ namespace MWLua void WorldView::save(ESM::ESMWriter& esm) const { - esm.writeHNT("LUAW", mGameSeconds); + esm.writeHNT("LUAW", mSimulationTime); mObjectRegistry.getLastAssignedId().save(esm, true); } diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index a8befd4685..e5b5fe4f67 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -22,13 +22,16 @@ namespace MWLua // Whether the world is paused (i.e. game time is not changing and actors don't move). bool isPaused() const { return mPaused; } - // Returns the number of seconds passed from the beginning of the game. - double getGameTimeInSeconds() const { return mGameSeconds; } - void setGameTimeInSeconds(double t) { mGameSeconds = t; } + // The number of seconds passed from the beginning of the game. + double getSimulationTime() const { return mSimulationTime; } + void setSimulationTime(double t) { mSimulationTime = t; } + double getSimulationTimeScale() const { return 1.0; } - // Returns the number of game hours passed from the beginning of the game. - // Note that the number of seconds in a game hour is not fixed. - double getGameTimeInHours() const; + // The game time (in game seconds) passed from the beginning of the game. + // Note that game time generally goes faster than the simulation time. + double getGameTime() const; + double getGameTimeScale() const { return MWBase::Environment::get().getWorld()->getTimeScaleFactor(); } + void setGameTimeScale(double s) { MWBase::Environment::get().getWorld()->setGlobalFloat("timescale", s); } ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; } ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } @@ -76,7 +79,7 @@ namespace MWLua ObjectGroup mDoorsInScene; ObjectGroup mItemsInScene; - double mGameSeconds = 0; + double mSimulationTime = 0; bool mPaused = false; }; diff --git a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp index 8be02a2f13..779debb4e6 100644 --- a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp +++ b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp @@ -364,7 +364,7 @@ return { TEST_F(LuaScriptsContainerTest, Timers) { - using TimeUnit = LuaUtil::ScriptsContainer::TimeUnit; + using TimerType = LuaUtil::ScriptsContainer::TimerType; LuaUtil::ScriptsContainer scripts(&mLua, "Test"); int test1Id = *mCfg.findId("test1.lua"); int test2Id = *mCfg.findId("test2.lua"); @@ -387,18 +387,18 @@ return { scripts.processTimers(1, 2); - scripts.setupSerializableTimer(TimeUnit::SECONDS, 10, test1Id, "B", sol::make_object(mLua.sol(), 3)); - scripts.setupSerializableTimer(TimeUnit::HOURS, 10, test2Id, "B", sol::make_object(mLua.sol(), 4)); - scripts.setupSerializableTimer(TimeUnit::SECONDS, 5, test1Id, "A", sol::make_object(mLua.sol(), 1)); - scripts.setupSerializableTimer(TimeUnit::HOURS, 5, test2Id, "A", sol::make_object(mLua.sol(), 2)); - scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, test1Id, "A", sol::make_object(mLua.sol(), 10)); - scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, test1Id, "B", sol::make_object(mLua.sol(), 20)); - - scripts.setupUnsavableTimer(TimeUnit::SECONDS, 10, test2Id, fn2); - scripts.setupUnsavableTimer(TimeUnit::HOURS, 10, test1Id, fn2); - scripts.setupUnsavableTimer(TimeUnit::SECONDS, 5, test2Id, fn1); - scripts.setupUnsavableTimer(TimeUnit::HOURS, 5, test1Id, fn1); - scripts.setupUnsavableTimer(TimeUnit::SECONDS, 15, test2Id, fn1); + scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 10, test1Id, "B", sol::make_object(mLua.sol(), 3)); + scripts.setupSerializableTimer(TimerType::GAME_TIME, 10, test2Id, "B", sol::make_object(mLua.sol(), 4)); + scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 5, test1Id, "A", sol::make_object(mLua.sol(), 1)); + scripts.setupSerializableTimer(TimerType::GAME_TIME, 5, test2Id, "A", sol::make_object(mLua.sol(), 2)); + scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 15, test1Id, "A", sol::make_object(mLua.sol(), 10)); + scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 15, test1Id, "B", sol::make_object(mLua.sol(), 20)); + + scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 10, test2Id, fn2); + scripts.setupUnsavableTimer(TimerType::GAME_TIME, 10, test1Id, fn2); + scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 5, test2Id, fn1); + scripts.setupUnsavableTimer(TimerType::GAME_TIME, 5, test1Id, fn1); + scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 15, test2Id, fn1); EXPECT_EQ(counter1, 0); EXPECT_EQ(counter3, 0); diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index c831cbbbfc..53beb02d82 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -72,7 +72,7 @@ void ESM::LuaScripts::load(ESMReader& esm) { esm.getSubHeader(); LuaTimer timer; - esm.getT(timer.mUnit); + esm.getT(timer.mType); esm.getT(timer.mTime); timer.mCallbackName = esm.getHNString("LUAC"); timer.mCallbackArgument = loadLuaBinaryData(esm); @@ -91,7 +91,7 @@ void ESM::LuaScripts::save(ESMWriter& esm) const for (const LuaTimer& timer : script.mTimers) { esm.startSubRecord("LUAT"); - esm.writeT(timer.mUnit); + esm.writeT(timer.mType); esm.writeT(timer.mTime); esm.endRecord("LUAT"); esm.writeHNString("LUAC", timer.mCallbackName); diff --git a/components/esm/luascripts.hpp b/components/esm/luascripts.hpp index e6f7113c16..985d756fce 100644 --- a/components/esm/luascripts.hpp +++ b/components/esm/luascripts.hpp @@ -51,13 +51,13 @@ namespace ESM struct LuaTimer { - enum class TimeUnit : bool + enum class Type : bool { - SECONDS = 0, - HOURS = 1, + SIMULATION_TIME = 0, + GAME_TIME = 1, }; - TimeUnit mUnit; + Type mType; double mTime; std::string mCallbackName; std::string mCallbackArgument; // Serialized Lua table. It is a binary data. Can contain '\0'. diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 6ff698c9c2..bfe9cb513c 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -52,7 +52,7 @@ namespace LuaUtil LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf) : mConf(conf), mVFS(vfs) { mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, - sol::lib::string, sol::lib::table, sol::lib::debug); + sol::lib::string, sol::lib::table, sol::lib::os, sol::lib::debug); mLua["math"]["randomseed"](static_cast(std::time(nullptr))); mLua["math"]["randomseed"] = []{}; @@ -85,6 +85,11 @@ namespace LuaUtil if (mLua[s] == sol::nil) throw std::logic_error("Lua package not found: " + s); mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]); } + mCommonPackages["os"] = mSandboxEnv["os"] = makeReadOnly(tableFromPairs({ + {"date", mLua["os"]["date"]}, + {"difftime", mLua["os"]["difftime"]}, + {"time", mLua["os"]["time"]} + })); } LuaState::~LuaState() diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index eb9da7a60b..780d10cebd 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -309,21 +309,21 @@ namespace LuaUtil void ScriptsContainer::save(ESM::LuaScripts& data) { std::map> timers; - auto saveTimerFn = [&](const Timer& timer, TimeUnit timeUnit) + auto saveTimerFn = [&](const Timer& timer, TimerType timerType) { if (!timer.mSerializable) return; ESM::LuaTimer savedTimer; savedTimer.mTime = timer.mTime; - savedTimer.mUnit = timeUnit; + savedTimer.mType = timerType; savedTimer.mCallbackName = std::get(timer.mCallback); savedTimer.mCallbackArgument = timer.mSerializedArg; timers[timer.mScriptId].push_back(std::move(savedTimer)); }; - for (const Timer& timer : mSecondsTimersQueue) - saveTimerFn(timer, TimeUnit::SECONDS); - for (const Timer& timer : mHoursTimersQueue) - saveTimerFn(timer, TimeUnit::HOURS); + for (const Timer& timer : mSimulationTimersQueue) + saveTimerFn(timer, TimerType::SIMULATION_TIME); + for (const Timer& timer : mGameTimersQueue) + saveTimerFn(timer, TimerType::GAME_TIME); data.mScripts.clear(); for (auto& [scriptId, script] : mScripts) { @@ -408,17 +408,17 @@ namespace LuaUtil // updates refnums, so timer.mSerializedArg may be not equal to savedTimer.mCallbackArgument. timer.mSerializedArg = serialize(timer.mArg, mSerializer); - if (savedTimer.mUnit == TimeUnit::HOURS) - mHoursTimersQueue.push_back(std::move(timer)); + if (savedTimer.mType == TimerType::GAME_TIME) + mGameTimersQueue.push_back(std::move(timer)); else - mSecondsTimersQueue.push_back(std::move(timer)); + mSimulationTimersQueue.push_back(std::move(timer)); } catch (std::exception& e) { printError(scriptId, "can not load timer", e); } } } - std::make_heap(mSecondsTimersQueue.begin(), mSecondsTimersQueue.end()); - std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end()); + std::make_heap(mSimulationTimersQueue.begin(), mSimulationTimersQueue.end()); + std::make_heap(mGameTimersQueue.begin(), mGameTimersQueue.end()); } ScriptsContainer::~ScriptsContainer() @@ -437,8 +437,8 @@ namespace LuaUtil for (auto& [_, handlers] : mEngineHandlers) handlers->mList.clear(); mEventHandlers.clear(); - mSecondsTimersQueue.clear(); - mHoursTimersQueue.clear(); + mSimulationTimersQueue.clear(); + mGameTimersQueue.clear(); mPublicInterfaces.clear(); // Assigned by LuaUtil::makeReadOnly, but `clear` removes it, so we need to assign it again. @@ -464,7 +464,7 @@ namespace LuaUtil std::push_heap(timerQueue.begin(), timerQueue.end()); } - void ScriptsContainer::setupSerializableTimer(TimeUnit timeUnit, double time, int scriptId, + void ScriptsContainer::setupSerializableTimer(TimerType type, double time, int scriptId, std::string_view callbackName, sol::object callbackArg) { Timer t; @@ -474,10 +474,10 @@ namespace LuaUtil t.mTime = time; t.mArg = callbackArg; t.mSerializedArg = serialize(t.mArg, mSerializer); - insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t)); + insertTimer(type == TimerType::GAME_TIME ? mGameTimersQueue : mSimulationTimersQueue, std::move(t)); } - void ScriptsContainer::setupUnsavableTimer(TimeUnit timeUnit, double time, int scriptId, sol::function callback) + void ScriptsContainer::setupUnsavableTimer(TimerType type, double time, int scriptId, sol::function callback) { Timer t; t.mScriptId = scriptId; @@ -488,7 +488,7 @@ namespace LuaUtil getScript(t.mScriptId).mTemporaryCallbacks.emplace(mTemporaryCallbackCounter, std::move(callback)); mTemporaryCallbackCounter++; - insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t)); + insertTimer(type == TimerType::GAME_TIME ? mGameTimersQueue : mSimulationTimersQueue, std::move(t)); } void ScriptsContainer::callTimer(const Timer& t) @@ -524,10 +524,10 @@ namespace LuaUtil } } - void ScriptsContainer::processTimers(double gameSeconds, double gameHours) + void ScriptsContainer::processTimers(double simulationTime, double gameTime) { - updateTimerQueue(mSecondsTimersQueue, gameSeconds); - updateTimerQueue(mHoursTimersQueue, gameHours); + updateTimerQueue(mSimulationTimersQueue, simulationTime); + updateTimerQueue(mGameTimersQueue, gameTime); } } diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 1863d04669..fcbd2ba0b7 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -73,7 +73,7 @@ namespace LuaUtil ScriptsContainer* mContainer; int mIndex; // index in LuaUtil::ScriptsConfiguration }; - using TimeUnit = ESM::LuaTimer::TimeUnit; + using TimerType = ESM::LuaTimer::Type; // `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print` output. // `autoStartMode` specifies the list of scripts that should be autostarted in this container; the list itself is @@ -99,8 +99,7 @@ namespace LuaUtil bool hasScript(int scriptId) const { return mScripts.count(scriptId) != 0; } void removeScript(int scriptId); - // Processes timers. gameSeconds and gameHours are time (in seconds and in game hours) passed from the game start. - void processTimers(double gameSeconds, double gameHours); + void processTimers(double simulationTime, double gameTime); // Calls `onUpdate` (if present) for every script in the container. // Handlers are called in the same order as scripts were added. @@ -134,17 +133,17 @@ namespace LuaUtil void registerTimerCallback(int scriptId, std::string_view callbackName, sol::function callback); // Sets up a timer, that can be automatically saved and loaded. - // timeUnit - game seconds (TimeUnit::Seconds) or game hours (TimeUnit::Hours). + // type - the type of timer, either SIMULATION_TIME or GAME_TIME. // time - the absolute game time (in seconds or in hours) when the timer should be executed. // scriptPath - script path in VFS is used as script id. The script with the given path should already present in the container. // callbackName - callback (should be registered in advance) for this timer. // callbackArg - parameter for the callback (should be serializable). - void setupSerializableTimer(TimeUnit timeUnit, double time, int scriptId, + void setupSerializableTimer(TimerType type, double time, int scriptId, std::string_view callbackName, sol::object callbackArg); - // Creates a timer. `callback` is an arbitrary Lua function. This type of timers is called "unsavable" - // because it can not be stored in saves. I.e. loading a saved game will not fully restore the state. - void setupUnsavableTimer(TimeUnit timeUnit, double time, int scriptId, sol::function callback); + // Creates a timer. `callback` is an arbitrary Lua function. These timers are called "unsavable" + // because they can not be stored in saves. I.e. loading a saved game will not fully restore the state. + void setupUnsavableTimer(TimerType type, double time, int scriptId, sol::function callback); protected: struct Handler @@ -237,8 +236,8 @@ namespace LuaUtil std::map mEngineHandlers; std::map> mEventHandlers; - std::vector mSecondsTimersQueue; - std::vector mHoursTimersQueue; + std::vector mSimulationTimersQueue; + std::vector mGameTimersQueue; int64_t mTemporaryCallbackCounter = 0; }; diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index dd9d151482..635fcfb95e 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_ui openmw_camera openmw_aux_util + openmw_aux_time interface_camera @@ -75,6 +76,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid +=========================================================+====================+===============================================================+ |:ref:`openmw_aux.util ` | everywhere | | Miscellaneous utils | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw_aux.time ` | everywhere | | Timers and game time utils | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ **Interfaces of built-in scripts** diff --git a/docs/source/reference/lua-scripting/openmw_aux_time.rst b/docs/source/reference/lua-scripting/openmw_aux_time.rst new file mode 100644 index 0000000000..120d888a01 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_aux_time.rst @@ -0,0 +1,5 @@ +Package openmw_aux.time +======================= + +.. raw:: html + :file: generated_html/openmw_aux_time.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 938611635a..103a93c559 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -15,10 +15,11 @@ Here are starting points for learning Lua: Each script works in a separate sandbox and doesn't have any access to the underlying operating system. Only a limited list of allowed standard libraries can be used: `coroutine `__, -`math `__, +`math `__ (except `math.randomseed` -- it is called by the engine on startup and not available from scripts), `string `__, -`table `__. -These libraries are loaded automatically and are always available (except the function `math.randomseed` -- it is called by the engine on startup and not available from scripts). +`table `__, +`os `__ (only `os.date`, `os.difftime`, `os.time`). +These libraries are loaded automatically and are always available. Allowed `basic functions `__: ``assert``, ``error``, ``ipairs``, ``next``, ``pairs``, ``pcall``, ``print``, ``select``, ``tonumber``, ``tostring``, ``type``, ``unpack``, ``xpcall``, ``rawequal``, ``rawget``, ``rawset``, ``getmetatable``, ``setmetatable``. @@ -366,6 +367,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid +=========================================================+====================+===============================================================+ |:ref:`openmw_aux.util ` | everywhere | | Miscellaneous utils | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw_aux.time ` | everywhere | | Timers and game time utils | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ They can be loaded with ``require`` the same as API packages. For example: diff --git a/files/builtin_scripts/openmw_aux/time.lua b/files/builtin_scripts/openmw_aux/time.lua new file mode 100644 index 0000000000..a6aac0fa1a --- /dev/null +++ b/files/builtin_scripts/openmw_aux/time.lua @@ -0,0 +1,104 @@ +--- +-- `openmw_aux.time` defines utility functions for timers. +-- Implementation can be found in `resources/vfs/openmw_aux/time.lua`. +-- @module time +-- @usage local time = require('openmw_aux.time') + +local time = { + second = 1, + minute = 60, + hour = 3600, + day = 3600 * 24, + GameTime = 'GameTime', + SimulationTime = 'SimulationTime', +} + +--- +-- Alias of async:registerTimerCallback ; register a function as a timer callback. +-- @function [parent=#time] registerTimerCallback +-- @param #string name +-- @param #function func +-- @return openmw.async#TimerCallback +function time.registerTimerCallback(name, fn) + local async = require('openmw.async') + return async:registerTimerCallback(name, fn) +end + +--- +-- Alias of async:newSimulationTimer ; call callback(arg) in `delay` game seconds. +-- Callback must be registered in advance. +-- @function [parent=#time] newGameTimer +-- @param #number delay +-- @param openmw.async#TimerCallback callback A callback returned by `registerTimerCallback` +-- @param arg An argument for `callback`; can be `nil`. +function time.newGameTimer(delay, callback, callbackArg) + local async = require('openmw.async') + return async:newGameTimer(delay, callback, callbackArg) +end + +--- +-- Alias of async:newSimulationTimer ; call callback(arg) in `delay` simulation seconds. +-- Callback must be registered in advance. +-- @function [parent=#time] newSimulationTimer +-- @param #number delay +-- @param openmw.async#TimerCallback callback A callback returned by `registerTimerCallback` +-- @param arg An argument for `callback`; can be `nil`. +function time.newSimulationTimer(delay, callback, callbackArg) + local async = require('openmw.async') + return async:newSimulationTimer(delay, callback, callbackArg) +end + +--- +-- Run given function repeatedly. +-- Note that loading a save stops the evaluation. If it should work always, call it during initialization of the script (i.e. not in a handler) +-- @function [parent=#time] runRepeatedly +-- @param #function fn the function that should be called +-- @param #number period interval +-- @param #table options additional options `initialDelay` and `type`. +-- `initialDelay` - delay before the first call. If missed then the delay is a random number in range [0, N]. Randomization is used for performance reasons -- to prevent all scripts from doing time consuming operations at the same time. +-- `type` - either `time.SimulationTime` (by default, timer uses simulation time) or `time.GameTime` (timer uses game time). +-- @return #function a function without arguments that can be used to stop the periodical evaluation. +-- @usage +-- local stopFn = time.runRepeatedly(function() print('Test') end, +-- 5 * time.second) -- print 'Test' every 5 seconds +-- stopFn() -- stop printing 'Test' +-- time.runRepeatedly( -- print 'Test' every 5 minutes with initial 30 second delay +-- function() print('Test2') end, 5 * time.minute, +-- { initialDelay = 30 * time.second }) +-- @usage +-- local timeBeforeMidnight = time.day - time.gameTime() % time.day +-- time.runRepeatedly(doSomething, time.day, { +-- initialDelay = timeBeforeMidnight, +-- type = time.GameTime, +-- }) -- call `doSomething` at the end of every game day. +function time.runRepeatedly(fn, period, options) + if period <= 0 then + error('Period must be positive. If you want it to be as small '.. + 'as possible, use the engine handler `onUpdate` instead', 2) + end + local async = require('openmw.async') + local core = require('openmw.core') + local initialDelay = (options and options.initialDelay) or math.random() * period + local getTimeFn, newTimerFn + if (options and options.type) == time.GameTime then + getTimeFn = core.getGameTime + newTimerFn = async.newUnsavableGameTimer + else + getTimeFn = core.getSimulationTime + newTimerFn = async.newUnsavableSimulationTimer + end + local baseTime = getTimeFn() + initialDelay + local breakFlag = false + local wrappedFn + wrappedFn = function() + if breakFlag then return end + fn() + local nextDelay = 1.5 * period - math.fmod(getTimeFn() - baseTime + period / 2, period) + newTimerFn(async, nextDelay, wrappedFn) + end + newTimerFn(async, initialDelay, wrappedFn) + return function() breakFlag = true end +end + +return time + diff --git a/files/builtin_scripts/openmw_aux/util.lua b/files/builtin_scripts/openmw_aux/util.lua index ba52ced2bd..8d4f7c1fd7 100644 --- a/files/builtin_scripts/openmw_aux/util.lua +++ b/files/builtin_scripts/openmw_aux/util.lua @@ -28,72 +28,5 @@ function aux_util.findNearestTo(point, objectList) return res, resDist end -------------------------------------------------------------------------------- --- Runs given function every N game seconds (seconds when the game is not paused). --- Note that loading a save stops the evaluation. If it should work always, call it in 2 places -- --- when a script starts and in the engine handler `onLoad`. --- @function [parent=#util] runEveryNSeconds --- @param #number N interval in seconds --- @param #function fn the function that should be called every N seconds --- @param #number initialDelay optional argument -- delay in seconds before the first call. If missed then the delay is a random number in range [0, N]. Randomization is used for performance reasons -- to prevent all scripts from doing time consuming operations at the same time. --- @return #function a function without arguments that can be used to stop the periodical evaluation. --- @usage --- local stopFn = aux_util.runEveryNSeconds(5, function() print('Test') end) -- print 'Test' every 5 seconds --- stopFn() -- stop printing 'Test' --- aux_util.runEveryNSeconds(5, function() print('Test2') end, 1) -- print 'Test' every 5 seconds starting from the next second -function aux_util.runEveryNSeconds(N, fn, initialDelay) - if N <= 0 then - error('Interval must be positive. If you want it to be as small '.. - 'as possible, use the engine handler `onUpdate` instead', 2) - end - local async = require('openmw.async') - local core = require('openmw.core') - local breakFlag = false - local initialDelay = initialDelay or math.random() * N - local baseTime = core.getGameTimeInSeconds() + initialDelay - local wrappedFn - wrappedFn = function() - if breakFlag then return end - fn() - local nextDelay = 1.5 * N - math.fmod(core.getGameTimeInSeconds() - baseTime + N / 2, N) - async:newUnsavableTimerInSeconds(nextDelay, wrappedFn) - end - async:newUnsavableTimerInSeconds(initialDelay, wrappedFn) - return function() breakFlag = true end -end - -------------------------------------------------------------------------------- --- Runs given function every N game hours. --- Note that loading a save stops the evaluation. If it should work always, call it in 2 places -- --- when a script starts and in the engine handler `onLoad`. --- @function [parent=#util] runEveryNHours --- @param #number N interval in game hours --- @param #function fn the function that should be called every N game hours --- @param #number initialDelay optional argument -- delay in game hours before the first call. If missed then the delay is a random number in range [0, N]. Randomization is used for performance reasons -- to prevent all scripts from doing time consuming operations at the same time. --- @return #function a function without arguments that can be used to stop the periodical evaluation. --- @usage --- local timeBeforeMidnight = 24 - math.fmod(core.getGameTimeInHours(), 24) --- aux_util.runEveryNHours(24, doSomething, timeBeforeMidnight) -- call `doSomething` at the end of every game day. -function aux_util.runEveryNHours(N, fn, initialDelay) - if N <= 0 then - error('Interval must be positive. If you want it to be as small '.. - 'as possible, use the engine handler `onUpdate` instead', 2) - end - local async = require('openmw.async') - local core = require('openmw.core') - local breakFlag = false - local initialDelay = initialDelay or math.random() * N - local baseTime = core.getGameTimeInHours() + initialDelay - local wrappedFn - wrappedFn = function() - if breakFlag then return end - fn() - local nextDelay = 1.5 * N - math.fmod(core.getGameTimeInHours() - baseTime + N / 2, N) - async:newUnsavableTimerInHours(nextDelay, wrappedFn) - end - async:newUnsavableTimerInHours(initialDelay, wrappedFn) - return function() breakFlag = true end -end - return aux_util diff --git a/files/lua_api/openmw/async.lua b/files/lua_api/openmw/async.lua index cc3a233f8a..61d5763a0b 100644 --- a/files/lua_api/openmw/async.lua +++ b/files/lua_api/openmw/async.lua @@ -15,35 +15,35 @@ -- @return #TimerCallback ------------------------------------------------------------------------------- --- Calls callback(arg) in `delay` seconds. +-- Calls callback(arg) in `delay` simulation seconds. -- Callback must be registered in advance. --- @function [parent=#async] newTimerInSeconds +-- @function [parent=#async] newSimulationTimer -- @param self -- @param #number delay -- @param #TimerCallback callback A callback returned by `registerTimerCallback` -- @param arg An argument for `callback`; can be `nil`. ------------------------------------------------------------------------------- --- Calls callback(arg) in `delay` game hours. +-- Calls callback(arg) in `delay` game seconds. -- Callback must be registered in advance. --- @function [parent=#async] newTimerInHours +-- @function [parent=#async] newGameTimer -- @param self -- @param #number delay -- @param #TimerCallback callback A callback returned by `registerTimerCallback` -- @param arg An argument for `callback`; can be `nil`. ------------------------------------------------------------------------------- --- Calls `func()` in `delay` seconds. +-- Calls `func()` in `delay` simulation seconds. -- The timer will be lost if the game is saved and loaded. --- @function [parent=#async] newUnsavableTimerInSeconds +-- @function [parent=#async] newUnsavableSimulationTimer -- @param self -- @param #number delay -- @param #function func ------------------------------------------------------------------------------- --- Calls `func()` in `delay` game hours. +-- Calls `func()` in `delay` game seconds. -- The timer will be lost if the game is saved and loaded. --- @function [parent=#async] newUnsavableTimerInHours +-- @function [parent=#async] newUnsavableGameTimer -- @param self -- @param #number delay -- @param #function func diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 016267d39d..d18be52406 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -20,16 +20,25 @@ -- @param #string eventName -- @param eventData +------------------------------------------------------------------------------- +-- Simulation time in seconds. +-- The number of simulation seconds passed in the game world since starting a new game. +-- @function [parent=#core] getSimulationTime +-- @return #number + +------------------------------------------------------------------------------- +-- The scale of simulation time relative to real time. +-- @function [parent=#core] getSimulationTimeScale +-- @return #number + ------------------------------------------------------------------------------- -- Game time in seconds. --- The number of seconds passed in the game world since starting a new game. --- @function [parent=#core] getGameTimeInSeconds +-- @function [parent=#core] getGameTime -- @return #number ------------------------------------------------------------------------------- --- Current time of the game world in hours. --- Note that the number of game seconds in a game hour is not guaranteed to be fixed. --- @function [parent=#core] getGameTimeInHours +-- The scale of game time relative to simulation time. +-- @function [parent=#core] getGameTimeScale -- @return #number ------------------------------------------------------------------------------- diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 90ea23b4f5..690f0bd566 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -29,6 +29,36 @@ -- @param #number gridY -- @return openmw.core#Cell +------------------------------------------------------------------------------- +-- Simulation time in seconds. +-- The number of simulation seconds passed in the game world since starting a new game. +-- @function [parent=#core] getSimulationTime +-- @return #number + +------------------------------------------------------------------------------- +-- The scale of simulation time relative to real time. +-- @function [parent=#core] getSimulationTimeScale +-- @return #number + +------------------------------------------------------------------------------- +-- Game time in seconds. +-- @function [parent=#core] getGameTime +-- @return #number + +------------------------------------------------------------------------------- +-- The scale of game time relative to simulation time. +-- @function [parent=#core] getGameTimeScale +-- @return #number + +------------------------------------------------------------------------------- +-- Set the ratio of game time speed to simulation time speed. +-- @function [parent=#world] setGameTimeScale +-- @param #number ratio + +------------------------------------------------------------------------------- +-- Whether the world is paused (onUpdate doesn't work when the world is paused). +-- @function [parent=#world] isWorldPaused +-- @return #boolean return nil diff --git a/files/lua_api/os.doclua b/files/lua_api/os.doclua new file mode 100644 index 0000000000..127ccd049a --- /dev/null +++ b/files/lua_api/os.doclua @@ -0,0 +1,64 @@ +------------------------------------------------------------------------------- +-- Operating System Facilities. +-- This library is implemented through table os. +-- @module os + + +------------------------------------------------------------------------------- +-- Returns a string or a table containing date and time, formatted according +-- to the given string `format`. +-- +-- If the `time` argument is present, this is the time to be formatted +-- (see the `os.time` function for a description of this value). Otherwise, +-- `date` formats the current time. +-- +-- If `format` starts with '`!`', then the date is formatted in Coordinated +-- Universal Time. After this optional character, if `format` is the string +-- "`*t`", then `date` returns a table with the following fields: +-- +-- * `year` (four digits) +-- * `month` (1--12) +-- * `day` (1--31) +-- * `hour` (0--23) +-- * `min` (0--59) +-- * `sec` (0--61) +-- * `wday` (weekday, Sunday is 1) +-- * `yday` (day of the year) +-- * `isdst` (daylight saving flag, a boolean). +-- +-- If `format` is not "`*t`", then `date` returns the date as a string, +-- formatted according to the same rules as the C function `strftime`. +-- When called without arguments, `date` returns a reasonable date and time +-- representation that depends on the host system and on the current locale +-- (that is, `os.date()` is equivalent to `os.date("%c")`). +-- @function [parent=#os] date +-- @param #string format format of date. (optional) +-- @param #number time time to format. (default value is current time) +-- @return #string a formatted string representation of `time`. + +------------------------------------------------------------------------------- +-- Returns the number of seconds from time `t1` to time `t2`. In POSIX, +-- Windows, and some other systems, this value is exactly `t2`*-*`t1`. +-- @function [parent=#os] difftime +-- @param #number t2 +-- @param #number t1 +-- @return #number the number of seconds from time `t1` to time `t2`. + +------------------------------------------------------------------------------- +-- Returns the current time when called without arguments, or a time +-- representing the date and time specified by the given table. This table +-- must have fields `year`, `month`, and `day`, and may have fields `hour`, +-- `min`, `sec`, and `isdst` (for a description of these fields, see the +-- `os.date` function). +-- +-- The returned value is a number, whose meaning depends on your system. In +-- POSIX, Windows, and some other systems, this number counts the number +-- of seconds since some given start time (the "epoch"). In other systems, +-- the meaning is not specified, and the number returned by `time` can be +-- used only as an argument to `date` and `difftime`. +-- @function [parent=#os] time +-- @param #table table a table which describes a date. +-- @return #number a number meaning a date. + +return nil + From 9fd7630ca056ea122184d395482210b52832d610 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 10 Jan 2022 21:04:07 +0100 Subject: [PATCH 1943/2859] Add calendar.lua --- docs/source/reference/lua-scripting/api.rst | 3 + .../lua-scripting/openmw_aux_calendar.rst | 5 + .../reference/lua-scripting/overview.rst | 6 +- files/builtin_scripts/CMakeLists.txt | 8 +- files/builtin_scripts/i18n/Calendar/en.lua | 42 +++++ files/builtin_scripts/openmw_aux/calendar.lua | 159 ++++++++++++++++++ files/builtin_scripts/scripts/omw/camera.lua | 6 +- 7 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 docs/source/reference/lua-scripting/openmw_aux_calendar.rst create mode 100644 files/builtin_scripts/i18n/Calendar/en.lua create mode 100644 files/builtin_scripts/openmw_aux/calendar.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 635fcfb95e..d6e85389b8 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -18,6 +18,7 @@ Lua API reference openmw_input openmw_ui openmw_camera + openmw_aux_calendar openmw_aux_util openmw_aux_time interface_camera @@ -74,6 +75,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ | Built-in library | Can be used | Description | +=========================================================+====================+===============================================================+ +|:ref:`openmw_aux.calendar ` | everywhere | | Game time calendar | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw_aux.util ` | everywhere | | Miscellaneous utils | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw_aux.time ` | everywhere | | Timers and game time utils | diff --git a/docs/source/reference/lua-scripting/openmw_aux_calendar.rst b/docs/source/reference/lua-scripting/openmw_aux_calendar.rst new file mode 100644 index 0000000000..ea60b62852 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_aux_calendar.rst @@ -0,0 +1,5 @@ +Package openmw_aux.calendar +=========================== + +.. raw:: html + :file: generated_html/openmw_aux_calendar.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 103a93c559..eab3fc962a 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -365,6 +365,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ | Built-in library | Can be used | Description | +=========================================================+====================+===============================================================+ +|:ref:`openmw_aux.calendar ` | everywhere | | Game time calendar | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw_aux.util ` | everywhere | | Miscellaneous utils | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw_aux.time ` | everywhere | | Timers and game time utils | @@ -374,8 +376,8 @@ They can be loaded with ``require`` the same as API packages. For example: .. code-block:: Lua - local aux_util = require('openmw_aux.util') - aux_util.runEveryNSeconds(15, doSomething) -- run `doSomething()` every 15 seconds + local time = require('openmw_aux.time') + time.runRepeatedly(doSomething, 15 * time.second) -- run `doSomething()` every 15 seconds Script interfaces diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 6f290cc1f7..1ef67a2e15 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -7,8 +7,14 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIRRELATIVE resources/vfs) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "builtin.omwscripts") +set(LUA_AUX_FILES + openmw_aux/util.lua + openmw_aux/time.lua + openmw_aux/calendar.lua +) + set(DDIRRELATIVE resources/vfs/openmw_aux) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "openmw_aux/util.lua") +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${LUA_AUX_FILES}") set(LUA_SCRIPTS_FILES scripts/omw/camera.lua diff --git a/files/builtin_scripts/i18n/Calendar/en.lua b/files/builtin_scripts/i18n/Calendar/en.lua new file mode 100644 index 0000000000..a4e8183a92 --- /dev/null +++ b/files/builtin_scripts/i18n/Calendar/en.lua @@ -0,0 +1,42 @@ +-- source: https://en.uesp.net/wiki/Lore:Calendar + +return { + month1 = "Morning Star", + month2 = "Sun's Dawn", + month3 = "First Seed", + month4 = "Rain's Hand", + month5 = "Second Seed", + month6 = "Midyear", + month7 = "Sun's Height", + month8 = "Last Seed", + month9 = "Hearthfire", + month10 = "Frostfall", + month11 = "Sun's Dusk", + month12 = "Evening Star", + + -- The variant of month names in the context "day X of month Y". + -- In English it is the same, but some languages require a different form. + monthInGenitive1 = "Morning Star", + monthInGenitive2 = "Sun's Dawn", + monthInGenitive3 = "First Seed", + monthInGenitive4 = "Rain's Hand", + monthInGenitive5 = "Second Seed", + monthInGenitive6 = "Midyear", + monthInGenitive7 = "Sun's Height", + monthInGenitive8 = "Last Seed", + monthInGenitive9 = "Hearthfire", + monthInGenitive10 = "Frostfall", + monthInGenitive11 = "Sun's Dusk", + monthInGenitive12 = "Evening Star", + + dateFormat = "day %{day} of %{monthInGenitive} %{year}", + + weekday1 = "Sundas", + weekday2 = "Morndas", + weekday3 = "Tirdas", + weekday4 = "Middas", + weekday5 = "Turdas", + weekday6 = "Fredas", + weekday7 = "Loredas", +} + diff --git a/files/builtin_scripts/openmw_aux/calendar.lua b/files/builtin_scripts/openmw_aux/calendar.lua new file mode 100644 index 0000000000..58e7298f1d --- /dev/null +++ b/files/builtin_scripts/openmw_aux/calendar.lua @@ -0,0 +1,159 @@ +--- +-- `openmw_aux.calendar` defines utility functions for formatting game time. +-- Implementation can be found in `resources/vfs/openmw_aux/calendar.lua`. +-- @module calendar +-- @usage local calendar = require('openmw_aux.calendar') + +local core = require('openmw.core') +local time = require('openmw_aux.time') +local i18n = core.i18n('Calendar') + +local monthsDuration = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +local daysInWeek = 7 +local daysInYear = 0 +for _, d in ipairs(monthsDuration) do daysInYear = daysInYear + d end + +local startingYear = 427 +local startingYearDay = 227 +local startingWeekDay = 1 + +local function gameTime(t) + if not t then + return core.getGameTime() + else + local days = (t.year or 0) * daysInYear + (t.day or 0) + for i = 1, (t.month or 1)-1 do + days = days + monthsDuration[i] + end + return days * time.day + (t.hour or 0) * time.hour + + (t.min or 0) * time.minute + (t.sec or 0) * time.second + end +end + +local function defaultDateFormat(t) + return i18n('dateFormat', { + day = t.day, + month = i18n('month' .. t.month), + monthInGenitive = i18n('monthInGenitive' .. t.month), + year = t.year, + }) +end + +local function formatGameTime(formatStr, timestamp) + timestamp = timestamp or core.getGameTime() + + local t = {} + local day = math.floor(timestamp / time.day) + t.year = math.floor(day / daysInYear) + startingYear + t.yday = (day + startingYearDay - 1) % daysInYear + 1 + t.wday = (day + startingWeekDay - 1) % daysInWeek + 1 + timestamp = timestamp % time.day + t.hour = math.floor(timestamp / time.hour) + timestamp = timestamp % time.hour + t.min = math.floor(timestamp / time.minute) + t.sec = math.floor(timestamp) % time.minute + + t.day = t.yday + t.month = 1 + while t.day > monthsDuration[t.month] do + t.day = t.day - monthsDuration[t.month] + t.month = t.month + 1 + end + + if formatStr == '*t' then return t end + + local replFn = function(tag) + if tag == '%a' or tag == '%A' then return i18n('weekday' .. t.wday) end + if tag == '%b' or tag == '%B' then return i18n('monthInGenitive' .. t.month) end + if tag == '%c' then + return string.format('%02d:%02d %s', t.hour, t.min, defaultDateFormat(t)) + end + if tag == '%d' then return string.format('%02d', t.day) end + if tag == '%e' then return string.format('%2d', t.day) end + if tag == '%H' then return string.format('%02d', t.hour) end + if tag == '%I' then return string.format('%02d', (t.hour - 1) % 12 + 1) end + if tag == '%M' then return string.format('%02d', t.min) end + if tag == '%m' then return string.format('%02d', t.month) end + if tag == '%p' then + if t.hour > 0 and t.hour <= 12 then + return 'am' + else + return 'pm' + end + end + if tag == '%S' then return string.format('%02d', t.sec) end + if tag == '%w' then return t.wday - 1 end + if tag == '%x' then return defaultDateFormat(t) end + if tag == '%X' then return string.format('%02d:%02d', t.hour, t.min) end + if tag == '%Y' then return t.year end + if tag == '%y' then return string.format('%02d', t.year % 100) end + if tag == '%%' then return '%' end + error('Unknown tag "'..tag..'"') + end + + res, _ = string.gsub(formatStr or '%c', '%%.', replFn) + return res +end + +return { + --- An equivalent of `os.time` for game time. + -- See [https://www.lua.org/pil/22.1.html](https://www.lua.org/pil/22.1.html) + -- @function [parent=#calendar] gameTime + -- @param #table table a table which describes a date (optional). + -- @return #number a timestamp. + gameTime = gameTime, + + --- An equivalent of `os.date` for game time. + -- See [https://www.lua.org/pil/22.1.html](https://www.lua.org/pil/22.1.html). + -- It is a slow function. Please try not to use it in every frame. + -- @function [parent=#calendar] formatGameTime + -- @param #string format format of date (optional) + -- @param #number time time to format (default value is current time) + -- @return #string a formatted string representation of `time`. + formatGameTime = formatGameTime, + + --- The number of months in a year + -- @field [parent=#calendar] #number monthCount + monthCount = #monthsDuration, + + --- The number of days in a year + -- @field [parent=#calendar] #number daysInYear + daysInYear = daysInYear, + + --- The number of days in a week + -- @field [parent=#calendar] #number daysInWeek + daysInWeek = daysInWeek, + + --- The number of days in a month + -- @function [parent=#calendar] daysInMonth + -- @param monthIndex + -- @return #number + daysInMonth = function(m) + return monthsDuration[(m-1) % #monthsDuration + 1] + end, + + --- The name of a month + -- @function [parent=#calendar] monthName + -- @param monthIndex + -- @return #string + monthName = function(m) + return i18n('month' .. ((m-1) % #monthsDuration + 1)) + end, + + --- The name of a month in genitive (for English is the same as `monthName`, but in some languages the form can differ). + -- @function [parent=#calendar] monthNameInGenitive + -- @param monthIndex + -- @return #string + monthNameInGenitive = function(m) + return i18n('monthInGenitive' .. ((m-1) % #monthsDuration + 1)) + end, + + --- The name of a weekday + -- @function [parent=#calendar] weekdayName + -- @param dayIndex + -- @return #string + weekdayName = function(d) + return i18n('weekday' .. ((d-1) % daysInWeek + 1)) + end, +} + diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 4025f7eee4..cd4d0dccfb 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -175,10 +175,12 @@ return { --- @module Camera -- @usage require('openmw.interfaces').Camera interface = { - --- @field [parent=#Camera] #number version Interface version + --- Interface version + -- @field [parent=#Camera] #number version version = 0, - --- @function [parent=#Camera] getPrimaryMode Returns primary mode (MODE.FirstPerson or MODE.ThirdPerson). + --- Return primary mode (MODE.FirstPerson or MODE.ThirdPerson). + -- @function [parent=#Camera] getPrimaryMode getPrimaryMode = function() return primaryMode end, --- @function [parent=#Camera] getBaseThirdPersonDistance getBaseThirdPersonDistance = function() return third_person.baseDistance end, From 8ec0a52605ac0dad211aeb0900cf6bd685fb8d39 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Tue, 11 Jan 2022 09:34:19 +0000 Subject: [PATCH 1944/2859] Toggable day night switch (#5928) --- CHANGELOG.md | 1 + apps/launcher/advancedpage.cpp | 4 +++ apps/opencs/model/prefs/state.cpp | 1 + apps/opencs/view/render/lighting.cpp | 32 +++++++++++++++++-- apps/opencs/view/render/scenewidget.cpp | 5 +++ apps/opencs/view/world/previewsubview.cpp | 2 ++ apps/openmw/mwrender/animation.cpp | 2 +- .../reference/modding/settings/game.rst | 9 ++++++ files/settings-default.cfg | 3 ++ files/ui/advancedpage.ui | 19 +++++++++++ 10 files changed, 75 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be525b001..4165c7d579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #5863: GetEffect should return true after the player has teleported Bug #5913: Failed assertion during Ritual of Trees quest + Bug #5928: Glow in the Dahrk functionality used without mod installed Bug #5937: Lights always need to be rotated by 90 degrees Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6051: NaN water height in ESM file is not handled gracefully diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index b9d35b3c95..fc1d84a61f 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -143,6 +143,8 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); viewingDistanceComboBox->setValue(convertToCells(Settings::Manager::getInt("viewing distance", "Camera"))); objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain")); + + loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); } // Audio @@ -298,6 +300,8 @@ void Launcher::AdvancedPage::saveSettings() double objectPagingMinSize = objectPagingMinSizeComboBox->value(); if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain")) Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); + + saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); } // Audio diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 58a0f296e2..06dc1f4a2e 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -223,6 +223,7 @@ void CSMPrefs::State::declare() declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)). setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if " "the gradient option is disabled."); + declareBool("scene-day-night-switch-nodes", "Use Day/Night Switch Nodes", true); declareCategory ("Tooltips"); declareBool ("scene", "Show Tooltips in 3D scenes", true); diff --git a/apps/opencs/view/render/lighting.cpp b/apps/opencs/view/render/lighting.cpp index 82ad43e6ad..c7bee79289 100644 --- a/apps/opencs/view/render/lighting.cpp +++ b/apps/opencs/view/render/lighting.cpp @@ -3,9 +3,12 @@ #include #include #include +#include #include +#include "../../model/prefs/state.hpp" + class DayNightSwitchVisitor : public osg::NodeVisitor { public: @@ -16,8 +19,33 @@ public: void apply(osg::Switch &switchNode) override { - if (switchNode.getName() == Constants::NightDayLabel) - switchNode.setSingleChildOn(mIndex); + constexpr int NoIndex = -1; + + int initialIndex = NoIndex; + if (!switchNode.getUserValue("initialIndex", initialIndex)) + { + for (size_t i = 0; i < switchNode.getValueList().size(); ++i) + { + if (switchNode.getValueList()[i]) + { + initialIndex = i; + break; + } + } + + if (initialIndex != NoIndex) + switchNode.setUserValue("initialIndex", initialIndex); + } + + if (CSMPrefs::get()["Rendering"]["scene-day-night-switch-nodes"].isTrue()) + { + if (switchNode.getName() == Constants::NightDayLabel) + switchNode.setSingleChildOn(mIndex); + } + else if (initialIndex != NoIndex) + { + switchNode.setSingleChildOn(initialIndex); + } traverse(switchNode); } diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index dbed1ba97c..b0c2140da9 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -550,6 +550,11 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) { updateCameraParameters(); } + else if (*setting == "Rendering/scene-day-night-switch-nodes") + { + if (mLighting) + setLighting(mLighting); + } } void RenderWidget::updateCameraParameters(double overrideAspect) diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index f3312bb208..cb231b4782 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -25,6 +25,8 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo else mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), true, this); + mScene->setExterior(true); + CSVWidget::SceneToolbar *toolbar = new CSVWidget::SceneToolbar (48+6, this); CSVWidget::SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 732ffd33aa..b1c7bc89c4 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1831,7 +1831,7 @@ namespace MWRender visitor.remove(); } - if (SceneUtil::hasUserDescription(mObjectRoot, Constants::NightDayLabel)) + if (Settings::Manager::getBool("day night switches", "Game") && SceneUtil::hasUserDescription(mObjectRoot, Constants::NightDayLabel)) { AddSwitchCallbacksVisitor visitor; mObjectRoot->accept(visitor); diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 6b19885557..f407c6a126 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -465,3 +465,12 @@ default actor pathfind half extents Actor half extents used for exterior cells to generate navmesh. Changing the value will invalidate navmesh disk cache. + +day night switches +------------------ + +:Type: boolean +:Range: True/False +:Default: True + +Some mods add models which change visuals based on time of day. When this setting is enabled, supporting models will automatically make use of Day/night state. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 06e080fdfd..f1f3206661 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -373,6 +373,9 @@ allow actors to follow over water surface = true # Default size of actor for navmesh generation default actor pathfind half extents = 29.27999496459961 28.479997634887695 66.5 +# Enables use of day/night switch nodes +day night switches = true + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 3a2e7c49fc..8e976df42d 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -557,6 +557,25 @@
+ + + + Models + + + + + + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + + + Day night switch nodes + + + + + + From a182fdeea184a4dd987df57367a426160687f0fd Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 14 Dec 2021 00:39:01 +0100 Subject: [PATCH 1945/2859] Permanent storage for Lua data --- apps/openmw/engine.cpp | 2 + apps/openmw/mwlua/luabindings.cpp | 39 +++- apps/openmw/mwlua/luabindings.hpp | 6 +- apps/openmw/mwlua/luamanagerimp.cpp | 29 ++- apps/openmw/mwlua/luamanagerimp.hpp | 11 +- apps/openmw/mwlua/settingsbindings.cpp | 7 +- apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/lua/test_storage.cpp | 103 +++++++++ components/CMakeLists.txt | 4 +- components/lua/storage.cpp | 198 ++++++++++++++++++ components/lua/storage.hpp | 81 +++++++ docs/source/reference/lua-scripting/api.rst | 6 +- .../lua-scripting/openmw_settings.rst | 5 - .../lua-scripting/openmw_storage.rst | 5 + .../reference/lua-scripting/overview.rst | 4 +- files/lua_api/openmw/core.lua | 6 + files/lua_api/openmw/settings.lua | 14 -- files/lua_api/openmw/storage.lua | 96 +++++++++ 18 files changed, 583 insertions(+), 34 deletions(-) create mode 100644 apps/openmw_test_suite/lua/test_storage.cpp create mode 100644 components/lua/storage.cpp create mode 100644 components/lua/storage.hpp delete mode 100644 docs/source/reference/lua-scripting/openmw_settings.rst create mode 100644 docs/source/reference/lua-scripting/openmw_storage.rst delete mode 100644 files/lua_api/openmw/settings.lua create mode 100644 files/lua_api/openmw/storage.lua diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index afba76edce..4ad140465b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -881,6 +881,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } mLuaManager->init(); + mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath().string()); } class OMW::Engine::LuaWorker @@ -1103,6 +1104,7 @@ void OMW::Engine::go() // Save user settings settings.saveUser(settingspath); + mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string()); Log(Debug::Info) << "Quitting peacefully."; } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 5a9e33faef..cf60e00728 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -6,7 +6,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/store.hpp" #include "eventqueue.hpp" #include "worldview.hpp" @@ -47,7 +49,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 13; + api["API_REVISION"] = 14; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); @@ -85,6 +87,17 @@ namespace MWLua {"Ammunition", MWWorld::InventoryStore::Slot_Ammunition} })); api["i18n"] = [i18n=context.mI18n](const std::string& context) { return i18n->getContext(context); }; + const MWWorld::Store* gmst = &MWBase::Environment::get().getWorld()->getStore().get(); + api["getGMST"] = [lua=context.mLua, gmst](const std::string& setting) -> sol::object + { + const ESM::Variant& value = gmst->find(setting)->mValue; + if (value.getType() == ESM::VT_String) + return sol::make_object(lua->sol(), value.getString()); + else if (value.getType() == ESM::VT_Int) + return sol::make_object(lua->sol(), value.getInteger()); + else + return sol::make_object(lua->sol(), value.getFloat()); + }; return LuaUtil::makeReadOnly(api); } @@ -163,5 +176,29 @@ namespace MWLua return LuaUtil::makeReadOnly(res); } + sol::table initGlobalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage) + { + sol::table res(context.mLua->sol(), sol::create); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getMutableSection(section); }; + res["allGlobalSections"] = [globalStorage]() { return globalStorage->getAllSections(); }; + return LuaUtil::makeReadOnly(res); + } + + sol::table initLocalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage) + { + sol::table res(context.mLua->sol(), sol::create); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; + return LuaUtil::makeReadOnly(res); + } + + sol::table initPlayerStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage, LuaUtil::LuaStorage* playerStorage) + { + sol::table res(context.mLua->sol(), sol::create); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; + res["playerSection"] = [playerStorage](std::string_view section) { return playerStorage->getMutableSection(section); }; + res["allPlayerSections"] = [playerStorage]() { return playerStorage->getAllSections(); }; + return LuaUtil::makeReadOnly(res); + } + } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index b021354a75..cfaf50c4f6 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "context.hpp" #include "eventqueue.hpp" @@ -25,6 +26,10 @@ namespace MWLua sol::table initFieldGroup(const Context&, const QueryFieldGroup&); + sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); + sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); + sol::table initPlayerStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage, LuaUtil::LuaStorage* playerStorage); + // Implemented in nearbybindings.cpp sol::table initNearbyPackage(const Context&); @@ -65,7 +70,6 @@ namespace MWLua // Implemented in settingsbindings.cpp sol::table initGlobalSettingsPackage(const Context&); - sol::table initLocalSettingsPackage(const Context&); sol::table initPlayerSettingsPackage(const Context&); // openmw.self package is implemented in localscripts.cpp diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 658ee7c809..39b944807a 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -1,5 +1,7 @@ #include "luamanagerimp.hpp" +#include + #include #include @@ -69,6 +71,7 @@ namespace MWLua initObjectBindingsForLocalScripts(localContext); initCellBindingsForLocalScripts(localContext); LocalScripts::initializeSelfPackage(localContext); + LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context)); mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); @@ -76,17 +79,37 @@ namespace MWLua mLua.addCommonPackage("openmw.query", initQueryPackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); mGlobalScripts.addPackage("openmw.settings", initGlobalSettingsPackage(context)); + mGlobalScripts.addPackage("openmw.storage", initGlobalStoragePackage(context, &mGlobalStorage)); mCameraPackage = initCameraPackage(localContext); mUserInterfacePackage = initUserInterfacePackage(localContext); mInputPackage = initInputPackage(localContext); mNearbyPackage = initNearbyPackage(localContext); - mLocalSettingsPackage = initLocalSettingsPackage(localContext); + mLocalSettingsPackage = initGlobalSettingsPackage(localContext); mPlayerSettingsPackage = initPlayerSettingsPackage(localContext); + mLocalStoragePackage = initLocalStoragePackage(localContext, &mGlobalStorage); + mPlayerStoragePackage = initPlayerStoragePackage(localContext, &mGlobalStorage, &mPlayerStorage); initConfiguration(); mInitialized = true; } + void LuaManager::loadPermanentStorage(const std::string& userConfigPath) + { + auto globalPath = std::filesystem::path(userConfigPath) / "global_storage.bin"; + auto playerPath = std::filesystem::path(userConfigPath) / "player_storage.bin"; + if (std::filesystem::exists(globalPath)) + mGlobalStorage.load(globalPath.string()); + if (std::filesystem::exists(playerPath)) + mPlayerStorage.load(playerPath.string()); + } + + void LuaManager::savePermanentStorage(const std::string& userConfigPath) + { + std::filesystem::path confDir(userConfigPath); + mGlobalStorage.save((confDir / "global_storage.bin").string()); + mPlayerStorage.save((confDir / "player_storage.bin").string()); + } + void LuaManager::update() { if (mPlayer.isEmpty()) @@ -232,6 +255,8 @@ namespace MWLua mPlayer = MWWorld::Ptr(); } clearUserInterface(); + mGlobalStorage.clearTemporary(); + mPlayerStorage.clearTemporary(); } void LuaManager::setupPlayer(const MWWorld::Ptr& ptr) @@ -346,11 +371,13 @@ namespace MWLua scripts->addPackage("openmw.camera", mCameraPackage); scripts->addPackage("openmw.input", mInputPackage); scripts->addPackage("openmw.settings", mPlayerSettingsPackage); + scripts->addPackage("openmw.storage", mPlayerStoragePackage); } else { scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()), flag); scripts->addPackage("openmw.settings", mLocalSettingsPackage); + scripts->addPackage("openmw.storage", mLocalStoragePackage); } scripts->addPackage("openmw.nearby", mNearbyPackage); scripts->setSerializer(mLocalSerializer.get()); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index d050cb9413..753cc6ca49 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -4,8 +4,9 @@ #include #include -#include #include +#include +#include #include "../mwbase/luamanager.hpp" @@ -28,6 +29,9 @@ namespace MWLua // Called by engine.cpp when the environment is fully initialized. void init(); + void loadPermanentStorage(const std::string& userConfigPath); + void savePermanentStorage(const std::string& userConfigPath); + // Called by engine.cpp every frame. For performance reasons it works in a separate // thread (in parallel with osg Cull). Can not use scene graph. void update(); @@ -99,6 +103,8 @@ namespace MWLua sol::table mInputPackage; sol::table mLocalSettingsPackage; sol::table mPlayerSettingsPackage; + sol::table mLocalStoragePackage; + sol::table mPlayerStoragePackage; GlobalScripts mGlobalScripts{&mLua}; std::set mActiveLocalScripts; @@ -139,6 +145,9 @@ namespace MWLua std::vector> mActionQueue; std::unique_ptr mTeleportPlayerAction; std::vector mUIMessages; + + LuaUtil::LuaStorage mGlobalStorage{mLua.sol()}; + LuaUtil::LuaStorage mPlayerStorage{mLua.sol()}; }; } diff --git a/apps/openmw/mwlua/settingsbindings.cpp b/apps/openmw/mwlua/settingsbindings.cpp index 12dd69f73a..afd852b1a0 100644 --- a/apps/openmw/mwlua/settingsbindings.cpp +++ b/apps/openmw/mwlua/settingsbindings.cpp @@ -8,7 +8,7 @@ namespace MWLua { - static sol::table initSettingsPackage(const Context& context, bool /*global*/, bool player) + static sol::table initSettingsPackage(const Context& context, bool player) { LuaUtil::LuaState* lua = context.mLua; sol::table config(lua->sol(), sol::create); @@ -65,8 +65,7 @@ namespace MWLua return LuaUtil::makeReadOnly(config); } - sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, true, false); } - sol::table initLocalSettingsPackage(const Context& context) { return initSettingsPackage(context, false, false); } - sol::table initPlayerSettingsPackage(const Context& context) { return initSettingsPackage(context, false, true); } + sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, false); } + sol::table initPlayerSettingsPackage(const Context& context) { return initSettingsPackage(context, true); } } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 9465d59b47..da68a21998 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -24,6 +24,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) lua/test_querypackage.cpp lua/test_configuration.cpp lua/test_i18n.cpp + lua/test_storage.cpp lua/test_ui_content.cpp diff --git a/apps/openmw_test_suite/lua/test_storage.cpp b/apps/openmw_test_suite/lua/test_storage.cpp new file mode 100644 index 0000000000..fafba008a1 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_storage.cpp @@ -0,0 +1,103 @@ +#include +#include +#include + +#include + +namespace +{ + using namespace testing; + + template + T get(sol::state& lua, std::string luaCode) + { + return lua.safe_script("return " + luaCode).get(); + } + + TEST(LuaUtilStorageTest, Basic) + { + sol::state mLua; + LuaUtil::LuaStorage::initLuaBindings(mLua); + LuaUtil::LuaStorage storage(mLua); + mLua["mutable"] = storage.getMutableSection("test"); + mLua["ro"] = storage.getReadOnlySection("test"); + + mLua.safe_script("mutable:set('x', 5)"); + EXPECT_EQ(get(mLua, "mutable:get('x')"), 5); + EXPECT_EQ(get(mLua, "ro:get('x')"), 5); + EXPECT_FALSE(get(mLua, "mutable:wasChanged()")); + EXPECT_TRUE(get(mLua, "ro:wasChanged()")); + EXPECT_FALSE(get(mLua, "ro:wasChanged()")); + + EXPECT_THROW(mLua.safe_script("ro:set('y', 3)"), std::exception); + + mLua.safe_script("t1 = mutable:asTable()"); + mLua.safe_script("t2 = ro:asTable()"); + EXPECT_EQ(get(mLua, "t1.x"), 5); + EXPECT_EQ(get(mLua, "t2.x"), 5); + + mLua.safe_script("mutable:reset()"); + EXPECT_TRUE(get(mLua, "ro:get('x') == nil")); + + mLua.safe_script("mutable:reset({x=4, y=7})"); + EXPECT_EQ(get(mLua, "ro:get('x')"), 4); + EXPECT_EQ(get(mLua, "ro:get('y')"), 7); + EXPECT_FALSE(get(mLua, "mutable:wasChanged()")); + EXPECT_TRUE(get(mLua, "ro:wasChanged()")); + EXPECT_FALSE(get(mLua, "ro:wasChanged()")); + } + + TEST(LuaUtilStorageTest, Table) + { + sol::state mLua; + LuaUtil::LuaStorage::initLuaBindings(mLua); + LuaUtil::LuaStorage storage(mLua); + mLua["mutable"] = storage.getMutableSection("test"); + mLua["ro"] = storage.getReadOnlySection("test"); + + mLua.safe_script("mutable:set('x', { y = 'abc', z = 7 })"); + EXPECT_EQ(get(mLua, "mutable:get('x').z"), 7); + EXPECT_THROW(mLua.safe_script("mutable:get('x').z = 3"), std::exception); + EXPECT_NO_THROW(mLua.safe_script("mutable:getCopy('x').z = 3")); + EXPECT_EQ(get(mLua, "mutable:get('x').z"), 7); + EXPECT_EQ(get(mLua, "ro:get('x').z"), 7); + EXPECT_EQ(get(mLua, "ro:get('x').y"), "abc"); + } + + TEST(LuaUtilStorageTest, Saving) + { + sol::state mLua; + LuaUtil::LuaStorage::initLuaBindings(mLua); + LuaUtil::LuaStorage storage(mLua); + + mLua["permanent"] = storage.getMutableSection("permanent"); + mLua["temporary"] = storage.getMutableSection("temporary"); + mLua.safe_script("temporary:removeOnExit()"); + mLua.safe_script("permanent:set('x', 1)"); + mLua.safe_script("temporary:set('y', 2)"); + + std::string tmpFile = (std::filesystem::temp_directory_path() / "test_storage.bin").string(); + storage.save(tmpFile); + EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); + EXPECT_EQ(get(mLua, "temporary:get('y')"), 2); + + storage.clearTemporary(); + mLua["permanent"] = storage.getMutableSection("permanent"); + mLua["temporary"] = storage.getMutableSection("temporary"); + EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); + EXPECT_TRUE(get(mLua, "temporary:get('y') == nil")); + + mLua.safe_script("permanent:set('x', 3)"); + mLua.safe_script("permanent:set('z', 4)"); + + LuaUtil::LuaStorage storage2(mLua); + mLua["permanent"] = storage2.getMutableSection("permanent"); + mLua["temporary"] = storage2.getMutableSection("temporary"); + + storage2.load(tmpFile); + EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); + EXPECT_TRUE(get(mLua, "permanent:get('z') == nil")); + EXPECT_TRUE(get(mLua, "temporary:get('y') == nil")); + } + +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6fedf25b4c..f7382db1c7 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -29,7 +29,7 @@ endif (GIT_CHECKOUT) # source files add_component_dir (lua - luastate scriptscontainer utilpackage serialization configuration i18n + luastate scriptscontainer utilpackage serialization configuration i18n storage ) add_component_dir (settings @@ -160,7 +160,7 @@ add_component_dir (fallback add_component_dir (queries query luabindings ) - + add_component_dir (lua_ui widget widgetlist element layers content text textedit window diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp new file mode 100644 index 0000000000..def38fdd67 --- /dev/null +++ b/components/lua/storage.cpp @@ -0,0 +1,198 @@ +#include "storage.hpp" + +#include +#include + +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; +} + +namespace LuaUtil +{ + LuaStorage::Value LuaStorage::Section::sEmpty; + + sol::object LuaStorage::Value::getCopy(lua_State* L) const + { + return deserialize(L, mSerializedValue); + } + + sol::object LuaStorage::Value::getReadOnly(lua_State* L) const + { + if (mReadOnlyValue == sol::nil && !mSerializedValue.empty()) + mReadOnlyValue = deserialize(L, mSerializedValue, nullptr, true); + return mReadOnlyValue; + } + + const LuaStorage::Value& LuaStorage::Section::get(std::string_view key) const + { + auto it = mValues.find(key); + if (it != mValues.end()) + return it->second; + else + return sEmpty; + } + + void LuaStorage::Section::set(std::string_view key, const sol::object& value) + { + mValues[std::string(key)] = Value(value); + mChangeCounter++; + if (mStorage->mListener) + (*mStorage->mListener)(mSectionName, key, value); + } + + bool LuaStorage::Section::wasChanged(int64_t& lastCheck) + { + bool res = lastCheck < mChangeCounter; + lastCheck = mChangeCounter; + return res; + } + + sol::table LuaStorage::Section::asTable() + { + sol::table res(mStorage->mLua, sol::create); + for (const auto& [k, v] : mValues) + res[k] = v.getCopy(mStorage->mLua); + return res; + } + + void LuaStorage::initLuaBindings(lua_State* L) + { + sol::state_view lua(L); + sol::usertype roView = lua.new_usertype("ReadOnlySection"); + sol::usertype mutableView = lua.new_usertype("MutableSection"); + roView["get"] = [](sol::this_state s, SectionReadOnlyView& section, std::string_view key) + { + return section.mSection->get(key).getReadOnly(s); + }; + roView["getCopy"] = [](sol::this_state s, SectionReadOnlyView& section, std::string_view key) + { + return section.mSection->get(key).getCopy(s); + }; + roView["wasChanged"] = [](SectionReadOnlyView& section) { return section.mSection->wasChanged(section.mLastCheck); }; + roView["asTable"] = [](SectionReadOnlyView& section) { return section.mSection->asTable(); }; + mutableView["get"] = [](sol::this_state s, SectionMutableView& section, std::string_view key) + { + return section.mSection->get(key).getReadOnly(s); + }; + mutableView["getCopy"] = [](sol::this_state s, SectionMutableView& section, std::string_view key) + { + return section.mSection->get(key).getCopy(s); + }; + mutableView["wasChanged"] = [](SectionMutableView& section) { return section.mSection->wasChanged(section.mLastCheck); }; + mutableView["asTable"] = [](SectionMutableView& section) { return section.mSection->asTable(); }; + mutableView["reset"] = [](SectionMutableView& section, sol::optional newValues) + { + section.mSection->mValues.clear(); + if (newValues) + { + for (const auto& [k, v] : *newValues) + { + try + { + section.mSection->set(k.as(), v); + } + catch (std::exception& e) + { + Log(Debug::Error) << "LuaUtil::LuaStorage::Section::reset(table): " << e.what(); + } + } + } + section.mSection->mChangeCounter++; + section.mLastCheck = section.mSection->mChangeCounter; + }; + mutableView["removeOnExit"] = [](SectionMutableView& section) { section.mSection->mPermanent = false; }; + mutableView["set"] = [](SectionMutableView& section, std::string_view key, const sol::object& value) + { + if (section.mLastCheck == section.mSection->mChangeCounter) + section.mLastCheck++; + section.mSection->set(key, value); + }; + } + + void LuaStorage::clearTemporary() + { + auto it = mData.begin(); + while (it != mData.end()) + { + if (!it->second->mPermanent) + it = mData.erase(it); + else + ++it; + } + } + + void LuaStorage::load(const std::string& path) + { + mData.clear(); + try + { + Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << std::filesystem::file_size(path) << " bytes)"; + std::ifstream fin(path, std::fstream::binary); + std::string serializedData((std::istreambuf_iterator(fin)), std::istreambuf_iterator()); + sol::table data = deserialize(mLua, serializedData); + for (const auto& [sectionName, sectionTable] : data) + { + Section* section = getSection(sectionName.as()); + for (const auto& [key, value] : sol::table(sectionTable)) + section->set(key.as(), value); + } + } + catch (std::exception& e) + { + Log(Debug::Error) << "Can not read \"" << path << "\": " << e.what(); + } + } + + void LuaStorage::save(const std::string& path) const + { + sol::table data(mLua, sol::create); + for (const auto& [sectionName, section] : mData) + { + if (section->mPermanent) + data[sectionName] = section->asTable(); + } + std::string serializedData = serialize(data); + Log(Debug::Info) << "Saving Lua storage \"" << path << "\" (" << serializedData.size() << " bytes)"; + std::ofstream fout(path, std::fstream::binary); + fout.write(serializedData.data(), serializedData.size()); + fout.close(); + } + + LuaStorage::Section* LuaStorage::getSection(std::string_view sectionName) + { + auto it = mData.find(sectionName); + if (it != mData.end()) + return it->second.get(); + auto section = std::make_unique
(this, std::string(sectionName)); + sectionName = section->mSectionName; + auto [newIt, _] = mData.emplace(sectionName, std::move(section)); + return newIt->second.get(); + } + + sol::object LuaStorage::getReadOnlySection(std::string_view sectionName) + { + Section* section = getSection(sectionName); + return sol::make_object(mLua, SectionReadOnlyView{section, section->mChangeCounter}); + } + + sol::object LuaStorage::getMutableSection(std::string_view sectionName) + { + Section* section = getSection(sectionName); + return sol::make_object(mLua, SectionMutableView{section, section->mChangeCounter}); + } + + sol::table LuaStorage::getAllSections() + { + sol::table res(mLua, sol::create); + for (const auto& [sectionName, _] : mData) + res[sectionName] = getMutableSection(sectionName); + return res; + } + +} diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp new file mode 100644 index 0000000000..e847604aeb --- /dev/null +++ b/components/lua/storage.hpp @@ -0,0 +1,81 @@ +#ifndef COMPONENTS_LUA_STORAGE_H +#define COMPONENTS_LUA_STORAGE_H + +#include +#include + +#include "serialization.hpp" + +namespace LuaUtil +{ + + class LuaStorage + { + public: + static void initLuaBindings(lua_State*); + + explicit LuaStorage(lua_State* lua) : mLua(lua) {} + + void clearTemporary(); + void load(const std::string& path); + void save(const std::string& path) const; + + sol::object getReadOnlySection(std::string_view sectionName); + sol::object getMutableSection(std::string_view sectionName); + sol::table getAllSections(); + + void set(std::string_view section, std::string_view key, const sol::object& value) { getSection(section)->set(key, value); } + + using ListenerFn = std::function; + void setListener(ListenerFn fn) { mListener = std::move(fn); } + + private: + class Value + { + public: + Value() {} + Value(const sol::object& value) : mSerializedValue(serialize(value)) {} + sol::object getCopy(lua_State* L) const; + sol::object getReadOnly(lua_State* L) const; + + private: + std::string mSerializedValue; + mutable sol::object mReadOnlyValue = sol::nil; + }; + + struct Section + { + explicit Section(LuaStorage* storage, std::string name) : mStorage(storage), mSectionName(std::move(name)) {} + const Value& get(std::string_view key) const; + void set(std::string_view key, const sol::object& value); + bool wasChanged(int64_t& lastCheck); + sol::table asTable(); + + LuaStorage* mStorage; + std::string mSectionName; + std::map> mValues; + bool mPermanent = true; + int64_t mChangeCounter = 0; + static Value sEmpty; + }; + struct SectionMutableView + { + Section* mSection = nullptr; + int64_t mLastCheck = 0; + }; + struct SectionReadOnlyView + { + Section* mSection = nullptr; + int64_t mLastCheck = 0; + }; + + Section* getSection(std::string_view sectionName); + + lua_State* mLua; + std::map> mData; + std::optional mListener; + }; + +} + +#endif // COMPONENTS_LUA_STORAGE_H diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index d6e85389b8..ef831e734a 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -8,7 +8,7 @@ Lua API reference engine_handlers user_interface openmw_util - openmw_settings + openmw_storage openmw_core openmw_async openmw_query @@ -45,8 +45,8 @@ Player scripts are local scripts that are attached to a player. |:ref:`openmw.util ` | everywhere | | Defines utility functions and classes like 3D vectors, | | | | | that don't depend on the game world. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.settings ` | everywhere | | Access to GMST records in content files (implemented) and | -| | | | to mod settings (not implemented). | +|:ref:`openmw.storage ` | everywhere | | Storage API. In particular can be used to store data | +| | | | between game sessions. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.core ` | everywhere | | Functions that are common for both global and local scripts | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ diff --git a/docs/source/reference/lua-scripting/openmw_settings.rst b/docs/source/reference/lua-scripting/openmw_settings.rst deleted file mode 100644 index f3d26882bb..0000000000 --- a/docs/source/reference/lua-scripting/openmw_settings.rst +++ /dev/null @@ -1,5 +0,0 @@ -Package openmw.settings -======================= - -.. raw:: html - :file: generated_html/openmw_settings.html diff --git a/docs/source/reference/lua-scripting/openmw_storage.rst b/docs/source/reference/lua-scripting/openmw_storage.rst new file mode 100644 index 0000000000..5abf664e1a --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_storage.rst @@ -0,0 +1,5 @@ +Package openmw.storage +====================== + +.. raw:: html + :file: generated_html/openmw_storage.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index eab3fc962a..9de283b029 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -334,8 +334,8 @@ Player scripts are local scripts that are attached to a player. |:ref:`openmw.util ` | everywhere | | Defines utility functions and classes like 3D vectors, | | | | | that don't depend on the game world. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.settings ` | everywhere | | Access to GMST records in content files (implemented) and | -| | | | to mod settings (not implemented). | +|:ref:`openmw.storage ` | everywhere | | Storage API. In particular can be used to store data | +| | | | between game sessions. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.core ` | everywhere | | Functions that are common for both global and local scripts | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index d18be52406..8415690067 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -46,6 +46,12 @@ -- @function [parent=#core] isWorldPaused -- @return #boolean +------------------------------------------------------------------------------- +-- Get a GMST setting from content files. +-- @function [parent=#core] getGMST +-- @param #string setting Setting name +-- @return #any + ------------------------------------------------------------------------------- -- Return i18n formatting function for the given context. -- It is based on `i18n.lua` library. diff --git a/files/lua_api/openmw/settings.lua b/files/lua_api/openmw/settings.lua deleted file mode 100644 index 6c35caa8af..0000000000 --- a/files/lua_api/openmw/settings.lua +++ /dev/null @@ -1,14 +0,0 @@ -------------------------------------------------------------------------------- --- `openmw.settings` provides read-only access to GMST records in content files. --- @module settings --- @usage --- local settings = require('openmw.settings') - -------------------------------------------------------------------------------- --- Get a GMST setting from content files. --- @function [parent=#settings] getGMST --- @param #string setting - - -return nil - diff --git a/files/lua_api/openmw/storage.lua b/files/lua_api/openmw/storage.lua new file mode 100644 index 0000000000..5499eefb9c --- /dev/null +++ b/files/lua_api/openmw/storage.lua @@ -0,0 +1,96 @@ +--- +-- `openmw.storage` contains functions to work with permanent Lua storage. +-- @module storage +-- @usage +-- local storage = require('openmw.storage') +-- local myModData = storage.globalSection('MyModExample') +-- myModData:set("someVariable", 1.0) +-- myModData:set("anotherVariable", { exampleStr='abc', exampleBool=true }) +-- local function update() +-- if myModCfg:checkChanged() then +-- print('Data was changes by another script:') +-- print('MyModExample.someVariable =', myModData:get('someVariable')) +-- print('MyModExample.anotherVariable.exampleStr =', +-- myModData:get('anotherVariable').exampleStr) +-- end +-- end + +--- +-- Get a section of the global storage; can be used by any script, but only global scripts can change values. +-- Creates the section if it doesn't exist. +-- @function [parent=#storage] globalSection +-- @param #string sectionName +-- @return #StorageSection + +--- +-- Get a section of the player storage; can be used by player scripts only. +-- Creates the section if it doesn't exist. +-- @function [parent=#storage] playerSection +-- @param #string sectionName +-- @return #StorageSection + +--- +-- Get all global sections as a table; can be used by global scripts only. +-- Note that adding/removing items to the returned table doesn't create or remove sections. +-- @function [parent=#storage] allGlobalSections +-- @return #table + +--- +-- Get all global sections as a table; can be used by player scripts only. +-- Note that adding/removing items to the returned table doesn't create or remove sections. +-- @function [parent=#storage] allPlayerSections +-- @return #table + +--- +-- A map `key -> value` that represents a storage section. +-- @type StorageSection + +--- +-- Get value by a string key; if value is a table makes it readonly. +-- @function [parent=#StorageSection] get +-- @param self +-- @param #string key + +--- +-- Get value by a string key; if value is a table returns a copy. +-- @function [parent=#StorageSection] getCopy +-- @param self +-- @param #string key + +--- +-- Return `True` if any value in this section was changed by another script since the last `wasChanged`. +-- @function [parent=#StorageSection] wasChanged +-- @param self +-- @return #boolean + +--- +-- Copy all values and return them as a table. +-- @function [parent=#StorageSection] asTable +-- @param self +-- @return #table + +--- +-- Remove all existing values and assign values from given (the arg is optional) table. +-- Note: `section:reset()` removes all values, but not the section itself. Use `section:removeOnExit()` to remove the section completely. +-- @function [parent=#StorageSection] reset +-- @param self +-- @param #table values (optional) New values + +--- +-- Make the whole section temporary: will be removed on exit or when load a save. +-- No section can be removed immediately because other scripts may use it at the moment. +-- 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. +-- @function [parent=#StorageSection] removeOnExit +-- @param self + +--- +-- Set value by a string key; can not be used for global storage from a local script. +-- @function [parent=#StorageSection] set +-- @param self +-- @param #string key +-- @param #any value + +return nil + From ba30b37bb4b539cacf81e0f8ef43a1555c7613ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Thu, 13 Jan 2022 22:46:00 +0200 Subject: [PATCH 1946/2859] Add missing include to unordered_map --- components/crashcatcher/windows_crashmonitor.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index 4028362836..eaf908bf66 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace Crash { From 97d56e198fdef194d68ef597cc0d850f32637ba0 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 15 Jan 2022 11:25:30 +0100 Subject: [PATCH 1947/2859] Use double precision for vectors serialization in Lua --- .../lua/test_serialization.cpp | 6 +++--- components/lua/serialization.cpp | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_serialization.cpp b/apps/openmw_test_suite/lua/test_serialization.cpp index 57a1070c83..1d664b06a4 100644 --- a/apps/openmw_test_suite/lua/test_serialization.cpp +++ b/apps/openmw_test_suite/lua/test_serialization.cpp @@ -93,14 +93,14 @@ namespace { std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec2)); - EXPECT_EQ(serialized.size(), 10); // version, type, 2x float + EXPECT_EQ(serialized.size(), 18); // version, type, 2x double sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), vec2); } { std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec3)); - EXPECT_EQ(serialized.size(), 14); // version, type, 3x float + EXPECT_EQ(serialized.size(), 26); // version, type, 3x double sol::object value = LuaUtil::deserialize(lua, serialized); ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), vec3); @@ -149,7 +149,7 @@ namespace table[2] = osg::Vec2f(2, 1); std::string serialized = LuaUtil::serialize(table); - EXPECT_EQ(serialized.size(), 123); + EXPECT_EQ(serialized.size(), 139); sol::table res_table = LuaUtil::deserialize(lua, serialized); sol::table res_readonly_table = LuaUtil::deserialize(lua, serialized, nullptr, true); diff --git a/components/lua/serialization.cpp b/components/lua/serialization.cpp index 8b18294449..e8f66c698c 100644 --- a/components/lua/serialization.cpp +++ b/components/lua/serialization.cpp @@ -99,17 +99,17 @@ namespace LuaUtil { appendType(out, SerializedType::VEC2); osg::Vec2f v = data.as(); - appendValue(out, v.x()); - appendValue(out, v.y()); + appendValue(out, v.x()); + appendValue(out, v.y()); return; } if (data.is()) { appendType(out, SerializedType::VEC3); osg::Vec3f v = data.as(); - appendValue(out, v.x()); - appendValue(out, v.y()); - appendValue(out, v.z()); + appendValue(out, v.x()); + appendValue(out, v.y()); + appendValue(out, v.z()); return; } if (data.is()) @@ -241,16 +241,16 @@ namespace LuaUtil throw std::runtime_error("Unexpected end of table during deserialization."); case SerializedType::VEC2: { - float x = getValue(binaryData); - float y = getValue(binaryData); + float x = getValue(binaryData); + float y = getValue(binaryData); sol::stack::push(lua, osg::Vec2f(x, y)); return; } case SerializedType::VEC3: { - float x = getValue(binaryData); - float y = getValue(binaryData); - float z = getValue(binaryData); + float x = getValue(binaryData); + float y = getValue(binaryData); + float z = getValue(binaryData); sol::stack::push(lua, osg::Vec3f(x, y, z)); return; } From 1b7e923b2c44bd76e83aff6df15a8b27fddfeddf Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 15 Jan 2022 12:01:30 +0000 Subject: [PATCH 1948/2859] Lua fixes --- apps/openmw/mwlua/luamanagerimp.cpp | 10 ++++++++-- apps/openmw/mwlua/luamanagerimp.hpp | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 658ee7c809..f49aa81493 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -244,7 +244,10 @@ namespace MWLua mPlayer = ptr; LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); if (!localScripts) + { localScripts = createLocalScripts(ptr, ESM::LuaScriptCfg::sPlayer); + localScripts->addAutoStartedScripts(); + } mActiveLocalScripts.insert(localScripts); mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}}); mPlayerChanged = true; @@ -274,7 +277,10 @@ namespace MWLua { ESM::LuaScriptCfg::Flags flag = getLuaScriptFlag(ptr); if (!mConfiguration.getListByFlag(flag).empty()) - localScripts = createLocalScripts(ptr, flag); // TODO: put to a queue and apply on next `update()` + { + localScripts = createLocalScripts(ptr, flag); + localScripts->addAutoStartedScripts(); // TODO: put to a queue and apply on next `update()` + } } if (localScripts) { @@ -327,6 +333,7 @@ namespace MWLua if (!localScripts) { localScripts = createLocalScripts(ptr, getLuaScriptFlag(ptr)); + localScripts->addAutoStartedScripts(); if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell())) mActiveLocalScripts.insert(localScripts); } @@ -354,7 +361,6 @@ namespace MWLua } scripts->addPackage("openmw.nearby", mNearbyPackage); scripts->setSerializer(mLocalSerializer.get()); - scripts->addAutoStartedScripts(); MWWorld::RefData& refData = ptr.getRefData(); refData.setLuaScripts(std::move(scripts)); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index d050cb9413..5928a4a511 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -24,6 +24,7 @@ namespace MWLua { public: LuaManager(const VFS::Manager* vfs, const std::string& libsDir); + ~LuaManager() { clear(); } // Called by engine.cpp when the environment is fully initialized. void init(); From 5fcb2cabc8c85b01bae855c74742638b84325ad0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 15 Jan 2022 13:04:15 +0100 Subject: [PATCH 1949/2859] Make scaleObject a no-op when not changing scale --- apps/openmw/mwworld/worldimp.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 325e048958..f6e00d3e21 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1283,15 +1283,14 @@ namespace MWWorld void World::scaleObject (const Ptr& ptr, float scale) { + if (scale == ptr.getCellRef().getScale()) + return; if (mPhysics->getActor(ptr)) mNavigator->removeAgent(getPathfindingHalfExtents(ptr)); - if (scale != ptr.getCellRef().getScale()) - { - ptr.getCellRef().setScale(scale); - mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); - mWorldScene->removeFromPagedRefs(ptr); - } + ptr.getCellRef().setScale(scale); + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + mWorldScene->removeFromPagedRefs(ptr); if(ptr.getRefData().getBaseNode() != nullptr) mWorldScene->updateObjectScale(ptr); From 467791299737d6dd5e7262687229c65d609d90a8 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 15 Jan 2022 13:59:22 +0100 Subject: [PATCH 1950/2859] Remove incorrect destructor that was added in !1557. --- apps/openmw/mwlua/luamanagerimp.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 5928a4a511..d050cb9413 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -24,7 +24,6 @@ namespace MWLua { public: LuaManager(const VFS::Manager* vfs, const std::string& libsDir); - ~LuaManager() { clear(); } // Called by engine.cpp when the environment is fully initialized. void init(); From a5cdc889a2d128172b853019f416c5487831fe0c Mon Sep 17 00:00:00 2001 From: Artem Nykolenko Date: Sun, 16 Jan 2022 10:15:47 +0000 Subject: [PATCH 1951/2859] #6303 Made player stop attacking and sheathe weapon when going to jail --- CHANGELOG.md | 1 + apps/openmw/mwworld/worldimp.cpp | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4165c7d579..9c45d15e6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod Bug #6302: Teleporting disabled actor breaks its disabled state + Bug #6303: After "go to jail" weapon can stuck in the ready to attack state Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken Bug #6321: Arrow enchantments should always be applied to the target Bug #6322: Total sold/cost should reset to 0 when there are no items offered diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 325e048958..9cf60f7dc5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3621,14 +3621,13 @@ namespace MWWorld void World::goToJail() { + const MWWorld::Ptr player = getPlayerPtr(); if (!mGoToJail) { // Reset bounty and forget the crime now, but don't change cell yet (the player should be able to read the dialog text first) mGoToJail = true; mPlayerInJail = true; - MWWorld::Ptr player = getPlayerPtr(); - int bounty = player.getClass().getNpcStats(player).getBounty(); player.getClass().getNpcStats(player).setBounty(0); mPlayer->recordCrimeId(); @@ -3641,6 +3640,12 @@ namespace MWWorld } else { + if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(player)) + { + mPlayer->setAttackingOrSpell(false); + } + + mPlayer->setDrawState(MWMechanics::DrawState_Nothing); mGoToJail = false; MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); From 20cbf941fbe054bc24ff24feb3ef01f6b13c1842 Mon Sep 17 00:00:00 2001 From: wareya Date: Sun, 16 Jan 2022 17:58:04 -0500 Subject: [PATCH 1952/2859] re-introduce short circuiting, but only under certain circumstances --- apps/openmw/mwphysics/movementsolver.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 1c06c9f00f..2f2abca487 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -322,6 +322,18 @@ namespace MWPhysics newPosition = (newPosition + tracer.mEndPos)/2.0; } + // short circuit if we went backwards, but only if it was mostly horizontal and we're on the ground + if (seenGround && newVelocity * origVelocity <= 0.0f) + { + auto perpendicular = newVelocity ^ origVelocity; + if (perpendicular.length2() > 0.0f) + { + perpendicular.normalize(); + if (std::abs(perpendicular.z()) > 0.7071f) + break; + } + } + // Do not allow sliding up steep slopes if there is gravity. // The purpose of this is to prevent air control from letting you slide up tall, unwalkable slopes. // For that purpose, it is not necessary to do it when trying to slide along acute seams/crevices (i.e. usedSeamLogic) From 886dee57ee3bea182d5ac18555513ae121fd2b08 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 17 Jan 2022 08:05:19 +0000 Subject: [PATCH 1953/2859] Always use /bigobj, clean up scattered uses into one --- CMakeLists.txt | 45 ++++++++++++++++----------- apps/openmw/CMakeLists.txt | 7 ----- apps/openmw_test_suite/CMakeLists.txt | 8 ----- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67a8f7c679..ae840fb8b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,16 @@ option(OPENMW_OSX_DEPLOYMENT OFF) if (MSVC) option(OPENMW_MP_BUILD "Build OpenMW with /MP flag" OFF) + if (OPENMW_MP_BUILD) + add_compile_options(/MP) + endif() + + # \bigobj is required: + # 1) for OPENMW_UNITY_BUILD; + # 2) to compile lua bindings in components, openmw and tests, because sol3 is heavily templated. + # there should be no relevant downsides to having it on: + # https://docs.microsoft.com/en-us/cpp/build/reference/bigobj-increase-number-of-sections-in-dot-obj-file + add_compile_options(/bigobj) endif() # Set up common paths @@ -610,10 +620,6 @@ endif() if (WIN32) if (MSVC) - if (OPENMW_MP_BUILD) - set( MT_BUILD "/MP") - endif() - foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(SolutionDir)$(Configuration)" ) @@ -662,54 +668,55 @@ if (WIN32) set(WARNINGS "${WARNINGS} /wd${d}") endforeach(d) - set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") - set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}") + set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${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} ${MT_BUILD}") + set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_ESMTOOL) - set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_ESSIMPORTER) - set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_LAUNCHER) - set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_MWINIIMPORTER) - set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_OPENCS) - set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_OPENMW) - # \bigobj is required: - # 1) for OPENMW_UNITY_BUILD; - # 2) to compile lua bindings, because sol3 is heavily templated. - set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") + set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_WIZARD) - set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}") + endif() + + if (BUILD_UNITTESTS) + set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_BENCHMARKS) - set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() if (BUILD_NAVMESHTOOL) - set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() endif(MSVC) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b54ecb416c..ea1d48885c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -207,13 +207,6 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw gcov) endif() -if (MSVC) - # Debug version needs increased number of sections beyond 2^16 - if (CMAKE_CL_64) - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") - endif (CMAKE_CL_64) -endif (MSVC) - if (WIN32) INSTALL(TARGETS openmw RUNTIME DESTINATION ".") endif (WIN32) diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index da68a21998..2ee34186d8 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -87,14 +87,6 @@ if (GTEST_FOUND AND GMOCK_FOUND) target_link_libraries(openmw_test_suite gcov) endif() - if (MSVC) - if (CMAKE_CL_64) - # Debug version of openmw_unit_tests needs increased number of sections beyond 2^16 - # just like openmw and openmw-cs - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") - endif (CMAKE_CL_64) - endif (MSVC) - file(DOWNLOAD https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame ${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame From d1d8f058acf1e8494e114829a9b5e398a0a92efb Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 17 Jan 2022 22:35:06 +0000 Subject: [PATCH 1954/2859] Lua bindings for Colours --- .../lua/test_serialization.cpp | 24 ++++ .../lua/test_utilpackage.cpp | 46 +++++++ components/CMakeLists.txt | 2 +- components/lua/serialization.cpp | 41 +++++++ components/lua/utilpackage.cpp | 108 +++++++++++------ components/lua/utilpackage.hpp | 2 + components/misc/color.cpp | 59 +++++++++ components/misc/color.hpp | 34 ++++++ files/lua_api/openmw/util.lua | 113 +++++++++++++++++- 9 files changed, 390 insertions(+), 39 deletions(-) create mode 100644 components/misc/color.cpp create mode 100644 components/misc/color.hpp diff --git a/apps/openmw_test_suite/lua/test_serialization.cpp b/apps/openmw_test_suite/lua/test_serialization.cpp index 1d664b06a4..5a6d5b7e51 100644 --- a/apps/openmw_test_suite/lua/test_serialization.cpp +++ b/apps/openmw_test_suite/lua/test_serialization.cpp @@ -5,11 +5,13 @@ #include #include #include +#include #include #include #include +#include #include "testing_util.hpp" @@ -90,6 +92,7 @@ namespace sol::state lua; osg::Vec2f vec2(1, 2); osg::Vec3f vec3(1, 2, 3); + osg::Vec4f vec4(1, 2, 3, 4); { std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec2)); @@ -105,6 +108,27 @@ namespace ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), vec3); } + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec4)); + EXPECT_EQ(serialized.size(), 34); // version, type, 4x double + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), vec4); + } + } + + TEST(LuaSerializationTest, Color) + { + sol::state lua; + Misc::Color color(1, 1, 1, 1); + + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, color)); + EXPECT_EQ(serialized.size(), 18); // version, type, 4x float + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), color); + } } TEST(LuaSerializationTest, Transform) { diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index 953d5f50d3..ba19cca383 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -70,6 +70,52 @@ namespace EXPECT_FLOAT_EQ(get(lua, "len"), 0); } + TEST(LuaUtilPackageTest, Vector4) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua.safe_script("v = util.vector4(5, 12, 13, 15)"); + EXPECT_FLOAT_EQ(get(lua, "v.x"), 5); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 12); + EXPECT_FLOAT_EQ(get(lua, "v.z"), 13); + EXPECT_FLOAT_EQ(get(lua, "v.w"), 15); + EXPECT_EQ(get(lua, "tostring(v)"), "(5, 12, 13, 15)"); + EXPECT_FLOAT_EQ(get(lua, "util.vector4(4, 0, 0, 3):length()"), 5); + EXPECT_FLOAT_EQ(get(lua, "util.vector4(4, 0, 0, 3):length2()"), 25); + EXPECT_FALSE(get(lua, "util.vector4(1, 2, 3, 4) == util.vector4(1, 3, 2, 4)")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) + util.vector4(2, 5, 1, 2) == util.vector4(3, 7, 4, 6)")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) - util.vector4(2, 5, 1, 7) == -util.vector4(1, 3, -2, 3)")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) == util.vector4(2, 4, 6, 8) / 2")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) * 2 == util.vector4(2, 4, 6, 8)")); + EXPECT_FLOAT_EQ(get(lua, "util.vector4(3, 2, 1, 4) * v"), 5 * 3 + 12 * 2 + 13 * 1 + 15 * 4); + EXPECT_FLOAT_EQ(get(lua, "util.vector4(3, 2, 1, 4):dot(v)"), 5 * 3 + 12 * 2 + 13 * 1 + 15 * 4); + lua.safe_script("v2, len = util.vector4(3, 0, 0, 4):normalize()"); + EXPECT_FLOAT_EQ(get(lua, "len"), 5); + EXPECT_TRUE(get(lua, "v2 == util.vector4(3/5, 0, 0, 4/5)")); + lua.safe_script("_, len = util.vector4(0, 0, 0, 0):normalize()"); + EXPECT_FLOAT_EQ(get(lua, "len"), 0); + } + + TEST(LuaUtilPackageTest, Color) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua.safe_script("brown = util.color.rgba(0.75, 0.25, 0, 1)"); + EXPECT_EQ(get(lua, "tostring(brown)"), "(0.75, 0.25, 0, 1)"); + lua.safe_script("blue = util.color.rgb(0, 1, 0, 1)"); + EXPECT_EQ(get(lua, "tostring(blue)"), "(0, 1, 0, 1)"); + lua.safe_script("red = util.color.hex('ff0000')"); + EXPECT_EQ(get(lua, "tostring(red)"), "(1, 0, 0, 1)"); + lua.safe_script("green = util.color.hex('00FF00')"); + EXPECT_EQ(get(lua, "tostring(green)"), "(0, 1, 0, 1)"); + lua.safe_script("darkRed = util.color.hex('a01112')"); + EXPECT_EQ(get(lua, "darkRed:asHex()"), "a01112"); + EXPECT_TRUE(get(lua, "green:asRgba() == util.vector4(0, 1, 0, 1)")); + EXPECT_TRUE(get(lua, "red:asRgb() == util.vector3(1, 0, 0)")); + } + TEST(LuaUtilPackageTest, Transform) { sol::state lua; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f7382db1c7..59f1e331e4 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -93,7 +93,7 @@ add_component_dir (esmterrain add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread - compression osguservalues errorMarker + compression osguservalues errorMarker color ) add_component_dir (debug diff --git a/components/lua/serialization.cpp b/components/lua/serialization.cpp index e8f66c698c..d0bbbc9dc5 100644 --- a/components/lua/serialization.cpp +++ b/components/lua/serialization.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "luastate.hpp" @@ -28,6 +29,8 @@ namespace LuaUtil VEC3 = 0x11, TRANSFORM_M = 0x12, TRANSFORM_Q = 0x13, + VEC4 = 0x14, + COLOR = 0x15, // All values should be lesser than 0x20 (SHORT_STRING_FLAG). }; @@ -129,6 +132,26 @@ namespace LuaUtil appendValue(out, quat[i]); return; } + if (data.is()) + { + appendType(out, SerializedType::VEC4); + osg::Vec4f v = data.as(); + appendValue(out, v.x()); + appendValue(out, v.y()); + appendValue(out, v.z()); + appendValue(out, v.w()); + return; + } + if (data.is()) + { + appendType(out, SerializedType::COLOR); + Misc::Color v = data.as (); + appendValue(out, v.r()); + appendValue(out, v.g()); + appendValue(out, v.b()); + appendValue(out, v.a()); + return; + } if (customSerializer && customSerializer->serialize(out, data)) return; else @@ -271,6 +294,24 @@ namespace LuaUtil sol::stack::push(lua, asTransform(q)); return; } + case SerializedType::VEC4: + { + float x = getValue(binaryData); + float y = getValue(binaryData); + float z = getValue(binaryData); + float w = getValue(binaryData); + sol::stack::push(lua, osg::Vec4f(x, y, z, w)); + return; + } + case SerializedType::COLOR: + { + float r = getValue(binaryData); + float g = getValue(binaryData); + float b = getValue(binaryData); + float a = getValue(binaryData); + sol::stack::push(lua, Misc::Color(r, g, b, a)); + return; + } } throw std::runtime_error("Unknown type in serialized data: " + std::to_string(type)); } diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index abb680a6bc..c51b00a7d5 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -2,8 +2,10 @@ #include #include +#include #include +#include #include "luastate.hpp" @@ -15,6 +17,12 @@ namespace sol template <> struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; + template <> struct is_automagical : std::false_type {}; @@ -24,6 +32,31 @@ namespace sol namespace LuaUtil { + namespace { + template + void addVectorMethods(sol::usertype& vectorType) + { + vectorType[sol::meta_function::unary_minus] = [](const T& a) { return -a; }; + vectorType[sol::meta_function::addition] = [](const T& a, const T& b) { return a + b; }; + vectorType[sol::meta_function::subtraction] = [](const T& a, const T& b) { return a - b; }; + vectorType[sol::meta_function::equal_to] = [](const T& a, const T& b) { return a == b; }; + vectorType[sol::meta_function::multiplication] = sol::overload( + [](const T& a, float c) { return a * c; }, + [](const T& a, const T& b) { return a * b; }); + vectorType[sol::meta_function::division] = [](const T& a, float c) { return a / c; }; + vectorType["dot"] = [](const T& a, const T b) { return a * b; }; + vectorType["length"] = &T::length; + vectorType["length2"] = &T::length2; + vectorType["normalize"] = [](const T& v) + { + float len = v.length(); + if (len == 0) + return std::make_tuple(T(), 0.f); + else + return std::make_tuple(v * (1.f / len), len); + }; + } + } sol::table initUtilPackage(sol::state& lua) { @@ -34,29 +67,13 @@ namespace LuaUtil sol::usertype vec2Type = lua.new_usertype("Vec2"); vec2Type["x"] = sol::readonly_property([](const Vec2& v) -> float { return v.x(); } ); vec2Type["y"] = sol::readonly_property([](const Vec2& v) -> float { return v.y(); } ); - vec2Type[sol::meta_function::to_string] = [](const Vec2& v) { + vec2Type[sol::meta_function::to_string] = [](const Vec2& v) + { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ")"; return ss.str(); }; - vec2Type[sol::meta_function::unary_minus] = [](const Vec2& a) { return -a; }; - vec2Type[sol::meta_function::addition] = [](const Vec2& a, const Vec2& b) { return a + b; }; - vec2Type[sol::meta_function::subtraction] = [](const Vec2& a, const Vec2& b) { return a - b; }; - vec2Type[sol::meta_function::equal_to] = [](const Vec2& a, const Vec2& b) { return a == b; }; - vec2Type[sol::meta_function::multiplication] = sol::overload( - [](const Vec2& a, float c) { return a * c; }, - [](const Vec2& a, const Vec2& b) { return a * b; }); - vec2Type[sol::meta_function::division] = [](const Vec2& a, float c) { return a / c; }; - vec2Type["dot"] = [](const Vec2& a, const Vec2& b) { return a * b; }; - vec2Type["length"] = &Vec2::length; - vec2Type["length2"] = &Vec2::length2; - vec2Type["normalize"] = [](const Vec2& v) { - float len = v.length(); - if (len == 0) - return std::make_tuple(Vec2(), 0.f); - else - return std::make_tuple(v * (1.f / len), len); - }; + addVectorMethods(vec2Type); vec2Type["rotate"] = &Misc::rotateVec2f; // Lua bindings for Vec3 @@ -65,31 +82,48 @@ namespace LuaUtil vec3Type["x"] = sol::readonly_property([](const Vec3& v) -> float { return v.x(); } ); vec3Type["y"] = sol::readonly_property([](const Vec3& v) -> float { return v.y(); } ); vec3Type["z"] = sol::readonly_property([](const Vec3& v) -> float { return v.z(); } ); - vec3Type[sol::meta_function::to_string] = [](const Vec3& v) { + vec3Type[sol::meta_function::to_string] = [](const Vec3& v) + { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; return ss.str(); }; - vec3Type[sol::meta_function::unary_minus] = [](const Vec3& a) { return -a; }; - vec3Type[sol::meta_function::addition] = [](const Vec3& a, const Vec3& b) { return a + b; }; - vec3Type[sol::meta_function::subtraction] = [](const Vec3& a, const Vec3& b) { return a - b; }; - vec3Type[sol::meta_function::equal_to] = [](const Vec3& a, const Vec3& b) { return a == b; }; - vec3Type[sol::meta_function::multiplication] = sol::overload( - [](const Vec3& a, float c) { return a * c; }, - [](const Vec3& a, const Vec3& b) { return a * b; }); - vec3Type[sol::meta_function::division] = [](const Vec3& a, float c) { return a / c; }; + addVectorMethods(vec3Type); vec3Type[sol::meta_function::involution] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; - vec3Type["dot"] = [](const Vec3& a, const Vec3& b) { return a * b; }; vec3Type["cross"] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; - vec3Type["length"] = &Vec3::length; - vec3Type["length2"] = &Vec3::length2; - vec3Type["normalize"] = [](const Vec3& v) { - float len = v.length(); - if (len == 0) - return std::make_tuple(Vec3(), 0.f); - else - return std::make_tuple(v * (1.f / len), len); + + // Lua bindings for Vec4 + util["vector4"] = [](float x, float y, float z, float w) + { return Vec4(x, y, z, w); }; + sol::usertype vec4Type = lua.new_usertype("Vec4"); + vec4Type["x"] = sol::readonly_property([](const Vec4& v) -> float { return v.x(); }); + vec4Type["y"] = sol::readonly_property([](const Vec4& v) -> float { return v.y(); }); + vec4Type["z"] = sol::readonly_property([](const Vec4& v) -> float { return v.z(); }); + vec4Type["w"] = sol::readonly_property([](const Vec4& v) -> float { return v.w(); }); + vec4Type[sol::meta_function::to_string] = [](const Vec4& v) + { + std::stringstream ss; + ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ", " << v.w() << ")"; + return ss.str(); }; + addVectorMethods(vec4Type); + + // Lua bindings for Color + sol::usertype colorType = lua.new_usertype("Color"); + colorType["r"] = [](const Misc::Color& c) { return c.r(); }; + colorType["g"] = [](const Misc::Color& c) { return c.g(); }; + colorType["b"] = [](const Misc::Color& c) { return c.b(); }; + colorType["a"] = [](const Misc::Color& c) { return c.a(); }; + colorType[sol::meta_function::to_string] = [](const Misc::Color& c) { return c.toString(); }; + colorType["asRgba"] = [](const Misc::Color& c) { return Vec4(c.r(), c.g(), c.b(), c.a()); }; + colorType["asRgb"] = [](const Misc::Color& c) { return Vec3(c.r(), c.g(), c.b()); }; + colorType["asHex"] = [](const Misc::Color& c) { return c.toHex(); }; + + sol::table color(lua, sol::create); + color["rgba"] = [](float r, float g, float b, float a) { return Misc::Color(r, g, b, a); }; + color["rgb"] = [](float r, float g, float b) { return Misc::Color(r, g, b, 1); }; + color["hex"] = [](std::string_view hex) { return Misc::Color::fromHex(hex); }; + util["color"] = LuaUtil::makeReadOnly(color); // Lua bindings for Transform sol::usertype transMType = lua.new_usertype("TransformM"); diff --git a/components/lua/utilpackage.hpp b/components/lua/utilpackage.hpp index d26bfdb027..a647b682af 100644 --- a/components/lua/utilpackage.hpp +++ b/components/lua/utilpackage.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -11,6 +12,7 @@ namespace LuaUtil { using Vec2 = osg::Vec2f; using Vec3 = osg::Vec3f; + using Vec4 = osg::Vec4f; // For performance reasons "Transform" is implemented as 2 types with the same interface. // Transform supports only composition, inversion, and applying to a 3d vector. diff --git a/components/misc/color.cpp b/components/misc/color.cpp new file mode 100644 index 0000000000..edf3435428 --- /dev/null +++ b/components/misc/color.cpp @@ -0,0 +1,59 @@ +#include "color.hpp" + +#include +#include +#include +#include + +namespace Misc +{ + Color::Color(float r, float g, float b, float a) + : mR(std::clamp(r, 0.f, 1.f)) + , mG(std::clamp(g, 0.f, 1.f)) + , mB(std::clamp(b, 0.f, 1.f)) + , mA(std::clamp(a, 0.f, 1.f)) + {} + + std::string Color::toString() const + { + std::ostringstream ss; + ss << "(" << r() << ", " << g() << ", " << b() << ", " << a() << ')'; + return ss.str(); + } + + Color Color::fromHex(std::string_view hex) + { + if (hex.size() != 6) + throw std::logic_error(std::string("Invalid hex color: ") += hex); + std::array rgb; + for (size_t i = 0; i < rgb.size(); i++) + { + auto sub = hex.substr(i * 2, 2); + int v; + auto [_, ec] = std::from_chars(sub.data(), sub.data() + sub.size(), v, 16); + if (ec != std::errc()) + throw std::logic_error(std::string("Invalid hex color: ") += hex); + rgb[i] = v / 255.0f; + } + return Color(rgb[0], rgb[1], rgb[2], 1); + } + + std::string Color::toHex() const + { + std::string result(6, '0'); + std::array rgb = { mR, mG, mB }; + for (size_t i = 0; i < rgb.size(); i++) + { + int b = static_cast(rgb[i] * 255.0f); + auto [_, ec] = std::to_chars(result.data() + i * 2, result.data() + (i + 1) * 2, b, 16); + if (ec != std::errc()) + throw std::logic_error("Error when converting number to base 16"); + } + return result; + } + + bool operator==(const Color& l, const Color& r) + { + return l.mR == r.mR && l.mG == r.mG && l.mB == r.mB && l.mA == r.mA; + } +} diff --git a/components/misc/color.hpp b/components/misc/color.hpp new file mode 100644 index 0000000000..932d261fad --- /dev/null +++ b/components/misc/color.hpp @@ -0,0 +1,34 @@ +#ifndef COMPONENTS_MISC_COLOR +#define COMPONENTS_MISC_COLOR + +#include + +namespace Misc +{ + class Color + { + public: + Color(float r, float g, float b, float a); + + float r() const { return mR; } + float g() const { return mG; } + float b() const { return mB; } + float a() const { return mA; } + + std::string toString() const; + + static Color fromHex(std::string_view hex); + + std::string toHex() const; + + friend bool operator==(const Color& l, const Color& r); + + private: + float mR; + float mG; + float mB; + float mA; + }; +} + +#endif // !COMPONENTS_MISC_COLOR diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index 22986f09a6..ea7d037daa 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -133,7 +133,7 @@ ------------------------------------------------------------------------------- -- Normalizes vector. -- Returns two values: normalized vector and the length of the original vector. --- It doesn't change the original vector. +-- It doesn't change the original vector. -- @function [parent=#Vector3] normalize -- @param self -- @return #Vector3, #number @@ -152,6 +152,117 @@ -- @param #Vector3 v -- @return #Vector3 + +------------------------------------------------------------------------------- +-- Immutable 4D vector. +-- @type Vector4 +-- @field #number x +-- @field #number y +-- @field #number z +-- @field #number w +-- @usage +-- v = util.vector4(3, 4, 5, 6) +-- v.x, v.y, v.z, v.w -- 3.0, 4.0, 5.0, 6.0 +-- str(v) -- "(3.0, 4.0, 5.0, 6.0)" +-- v:length() -- length +-- v:length2() -- square of the length +-- v:normalize() -- normalized vector +-- v1:dot(v2) -- dot product (returns a number) +-- v1 * v2 -- dot product (returns a number) +-- v1 + v2 -- vector addition +-- v1 - v2 -- vector subtraction +-- v1 * x -- multiplication by a number +-- v1 / x -- division by a number + +------------------------------------------------------------------------------- +-- Creates a new 4D vector. Vectors are immutable and can not be changed after creation. +-- @function [parent=#util] vector4 +-- @param #number x. +-- @param #number y. +-- @param #number z. +-- @param #number w. +-- @return #Vector4. + +------------------------------------------------------------------------------- +-- Length of the vector +-- @function [parent=#Vector4] length +-- @param self +-- @return #number + +------------------------------------------------------------------------------- +-- Square of the length of the vector +-- @function [parent=#Vector4] length2 +-- @param self +-- @return #number + +------------------------------------------------------------------------------- +-- Normalizes vector. +-- Returns two values: normalized vector and the length of the original vector. +-- It doesn't change the original vector. +-- @function [parent=#Vector4] normalize +-- @param self +-- @return #Vector4, #number + +------------------------------------------------------------------------------- +-- Dot product. +-- @function [parent=#Vector4] dot +-- @param self +-- @param #Vector4 v +-- @return #number + +------------------------------------------------------------------------------- +-- Color in RGBA format. All of the component values are in the range [0, 1]. +-- @type Color +-- @field #number r Red component +-- @field #number g Green component +-- @field #number b Blue component +-- @field #number a Alpha (transparency) component + +------------------------------------------------------------------------------- +-- Returns a Vector4 with RGBA components of the Color. +-- @function [parent=#Color] asRgba +-- @param self +-- @return #Vector4 + +------------------------------------------------------------------------------- +-- Returns a Vector3 with RGB components of the Color. +-- @function [parent=#Color] asRgb +-- @param self +-- @return #Vector3 + +------------------------------------------------------------------------------- +-- Converts the color into a HEX string. +-- @function [parent=#Color] asHex +-- @param self +-- @return #string + +------------------------------------------------------------------------------- +-- @type COLOR +-- @field [parent=#util] #COLOR color Methods for creating #Color values from different formats. + +------------------------------------------------------------------------------- +-- Creates a Color from RGBA format +-- @function [parent=#COLOR] rgba +-- @param #number r +-- @param #number g +-- @param #number b +-- @param #number a +-- @return #Color + +------------------------------------------------------------------------------- +-- Creates a Color from RGB format. Equivalent to calling util.rgba with a = 1. +-- @function [parent=#COLOR] rgb +-- @param #number r +-- @param #number g +-- @param #number b +-- @return #Color + +------------------------------------------------------------------------------- +-- Parses a hex color string into a Color. +-- @function [parent=#COLOR] hex +-- @param #string hex A hex color string in RRGGBB format (e. g. "ff0000"). +-- @return #Color + ------------------------------------------------------------------------------- -- @type Transform From cc528d2e0800e7ad258073f6be70224e0f7926c0 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 18 Jan 2022 08:12:56 +0000 Subject: [PATCH 1955/2859] Dispose Lua UI elements correctly --- apps/openmw/mwgui/windowmanagerimp.cpp | 4 ++- apps/openmw/mwlua/luamanagerimp.cpp | 7 ++++-- apps/openmw/mwlua/uibindings.cpp | 15 ++--------- components/CMakeLists.txt | 2 +- components/lua_ui/element.cpp | 25 ++++++++++++++++--- components/lua_ui/element.hpp | 14 +++++------ components/lua_ui/text.cpp | 5 ---- components/lua_ui/text.hpp | 1 - .../lua_ui/{widgetlist.cpp => util.cpp} | 10 +++++++- .../lua_ui/{widgetlist.hpp => util.hpp} | 2 ++ components/lua_ui/widget.cpp | 15 ++++------- components/lua_ui/widget.hpp | 8 +++--- 12 files changed, 59 insertions(+), 49 deletions(-) rename components/lua_ui/{widgetlist.cpp => util.cpp} (81%) rename components/lua_ui/{widgetlist.hpp => util.hpp} (89%) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 8e7951667b..37807fdf2a 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -53,7 +53,7 @@ #include #include -#include +#include #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" @@ -510,6 +510,8 @@ namespace MWGui { try { + LuaUi::clearUserInterface(); + mStatsWatcher.reset(); MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index fc6dc86285..8211c37abf 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -12,6 +12,8 @@ #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" @@ -237,6 +239,7 @@ namespace MWLua void LuaManager::clear() { + LuaUi::clearUserInterface(); mActiveLocalScripts.clear(); mLocalEvents.clear(); mGlobalEvents.clear(); @@ -254,7 +257,6 @@ namespace MWLua mPlayer.getRefData().setLuaScripts(nullptr); mPlayer = MWWorld::Ptr(); } - clearUserInterface(); mGlobalStorage.clearTemporary(); mPlayerStorage.clearTemporary(); } @@ -454,6 +456,8 @@ namespace MWLua void LuaManager::reloadAllScripts() { Log(Debug::Info) << "Reload Lua"; + + LuaUi::clearUserInterface(); mLua.dropScriptCache(); initConfiguration(); @@ -470,7 +474,6 @@ namespace MWLua continue; ESM::LuaScripts data; scripts->save(data); - clearUserInterface(); scripts->load(data); } for (LocalScripts* scripts : mActiveLocalScripts) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 987304d4c3..61e7c471c1 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -11,8 +11,6 @@ namespace MWLua { namespace { - std::set allElements; - class UiAction final : public Action { public: @@ -189,7 +187,6 @@ namespace MWLua { if (element->mDestroy) return; - allElements.erase(element.get()); element->mDestroy = true; context.mLuaManager->addAction(std::make_unique(UiAction::DESTROY, element, context.mLua)); }; @@ -205,8 +202,7 @@ namespace MWLua }; api["create"] = [context](const sol::table& layout) { - auto element = std::make_shared(layout); - allElements.emplace(element.get()); + auto element = LuaUi::Element::make(layout); context.mLuaManager->addAction(std::make_unique(UiAction::CREATE, element, context.mLua)); return element; }; @@ -244,11 +240,4 @@ namespace MWLua return LuaUtil::makeReadOnly(api); } - - void clearUserInterface() - { - for (auto element : allElements) - element->destroy(); - allElements.clear(); - } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f7382db1c7..c12bc98f32 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -162,7 +162,7 @@ add_component_dir (queries ) add_component_dir (lua_ui - widget widgetlist element layers content + widget element util layers content text textedit window ) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 5147068a7e..dcb21c32a0 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -3,11 +3,10 @@ #include #include "content.hpp" -#include "widgetlist.hpp" +#include "util.hpp" namespace LuaUi { - std::string widgetType(const sol::table& layout) { return layout.get_or("type", std::string("LuaWidget")); @@ -70,7 +69,7 @@ namespace LuaUi if (!ext) throw std::runtime_error("Invalid widget!"); - ext->create(layout.lua_state(), widget); + ext->initialize(layout.lua_state(), widget); if (parent != nullptr) widget->attachToWidget(parent->widget()); @@ -87,7 +86,7 @@ namespace LuaUi void destroyWidget(LuaUi::WidgetExtension* ext) { - ext->destroy(); + ext->deinitialize(); MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); } @@ -136,6 +135,23 @@ namespace LuaUi } } + std::map> Element::sAllElements; + + Element::Element(sol::table layout) + : mRoot{ nullptr } + , mLayout{ std::move(layout) } + , mUpdate{ false } + , mDestroy{ false } + {} + + + std::shared_ptr Element::make(sol::table layout) + { + std::shared_ptr ptr(new Element(std::move(layout))); + sAllElements[ptr.get()] = ptr; + return ptr; + } + void Element::create() { assert(!mRoot); @@ -161,5 +177,6 @@ namespace LuaUi if (mRoot) destroyWidget(mRoot); mRoot = nullptr; + sAllElements.erase(this); } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index 10e10d8960..daaf340660 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -7,13 +7,7 @@ namespace LuaUi { struct Element { - Element(sol::table layout) - : mRoot{ nullptr } - , mLayout{ layout } - , mUpdate{ false } - , mDestroy{ false } - { - } + static std::shared_ptr make(sol::table layout); LuaUi::WidgetExtension* mRoot; sol::table mLayout; @@ -25,6 +19,12 @@ namespace LuaUi void update(); void destroy(); + + friend void clearUserInterface(); + + private: + Element(sol::table layout); + static std::map> sAllElements; }; } diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index 4ae9865ac3..f93f8eecf0 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -7,11 +7,6 @@ namespace LuaUi : mAutoSized(true) {} - void LuaText::initialize() - { - WidgetExtension::initialize(); - } - void LuaText::setProperties(sol::object props) { setCaption(parseProperty(props, "caption", std::string())); diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index d87a9001a2..533dc4f453 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -13,7 +13,6 @@ namespace LuaUi public: LuaText(); - virtual void initialize() override; virtual void setProperties(sol::object) override; private: diff --git a/components/lua_ui/widgetlist.cpp b/components/lua_ui/util.cpp similarity index 81% rename from components/lua_ui/widgetlist.cpp rename to components/lua_ui/util.cpp index c2a9bef990..8cadbc9cc2 100644 --- a/components/lua_ui/widgetlist.cpp +++ b/components/lua_ui/util.cpp @@ -1,4 +1,4 @@ -#include "widgetlist.hpp" +#include "util.hpp" #include @@ -7,6 +7,8 @@ #include "textedit.hpp" #include "window.hpp" +#include "element.hpp" + namespace LuaUi { @@ -28,4 +30,10 @@ namespace LuaUi }; return types; } + + void clearUserInterface() + { + while (!Element::sAllElements.empty()) + Element::sAllElements.begin()->second->destroy(); + } } diff --git a/components/lua_ui/widgetlist.hpp b/components/lua_ui/util.hpp similarity index 89% rename from components/lua_ui/widgetlist.hpp rename to components/lua_ui/util.hpp index ff033fb6ca..3851e6c947 100644 --- a/components/lua_ui/widgetlist.hpp +++ b/components/lua_ui/util.hpp @@ -9,6 +9,8 @@ namespace LuaUi void registerAllWidgets(); const std::unordered_map& widgetTypeToName(); + + void clearUserInterface(); } #endif // OPENMW_LUAUI_WIDGETLIST diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 6d7bb5063c..50c6a193e6 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -26,7 +26,7 @@ namespace LuaUi it->second(argument, mLayout); } - void WidgetExtension::create(lua_State* lua, MyGUI::Widget* self) + void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) { mLua = lua; mWidget = self; @@ -54,17 +54,9 @@ namespace LuaUi mWidget->eventKeyLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); } - void WidgetExtension::destroy() - { - clearCallbacks(); - deinitialize(); - - for (WidgetExtension* child : mContent) - child->destroy(); - } - void WidgetExtension::deinitialize() { + clearCallbacks(); mWidget->eventKeyButtonPressed.clear(); mWidget->eventKeyButtonReleased.clear(); mWidget->eventMouseButtonClick.clear(); @@ -78,6 +70,9 @@ namespace LuaUi mWidget->eventMouseLostFocus.clear(); mWidget->eventKeySetFocus.clear(); mWidget->eventKeyLostFocus.clear(); + + for (WidgetExtension* child : mContent) + child->deinitialize(); } sol::table WidgetExtension::makeTable() const diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 99d430f71c..f8ba105be5 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -22,9 +22,9 @@ namespace LuaUi public: WidgetExtension(); // must be called after creating the underlying MyGUI::Widget - void create(lua_State* lua, MyGUI::Widget* self); + void initialize(lua_State* lua, MyGUI::Widget* self); // must be called after before destroying the underlying MyGUI::Widget - void destroy(); + virtual void deinitialize(); void addChild(WidgetExtension* ext); WidgetExtension* childAt(size_t index) const; @@ -46,11 +46,11 @@ namespace LuaUi void setLayout(const sol::table& layout) { mLayout = layout; } protected: + virtual void initialize(); sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; - virtual void initialize(); - virtual void deinitialize(); + virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); From ca6262c033ebb98d72514cdea20c9f76a3821107 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 18 Jan 2022 18:22:46 +0000 Subject: [PATCH 1956/2859] switch Static Deps and Tests from GCC to clang and set to -O0 to speed up builds --- .gitlab-ci.yml | 20 +++++++++++++------- CI/before_script.linux.sh | 4 ++-- extern/CMakeLists.txt | 6 +++--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8275df7572..fef2f39234 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -125,8 +125,8 @@ Ubuntu_GCC_tests_Debug: paths: [] expire_in: 1 minute -Ubuntu_GCC_Static_Deps: - extends: Ubuntu_GCC +Ubuntu_Static_Deps: + extends: Ubuntu_Clang rules: - if: $CI_PIPELINE_SOURCE == "push" changes: @@ -136,24 +136,30 @@ Ubuntu_GCC_Static_Deps: - ".gitlab-ci.yml" allow_failure: true cache: - key: Ubuntu_GCC_Static_Deps + key: Ubuntu_Static_Deps.V1 paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: - - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static + - CI/install_debian_deps.sh clang openmw-deps openmw-deps-static variables: CI_OPENMW_USE_STATIC_DEPS: 1 + CC: clang + CXX: clang++ + CXXFLAGS: -O0 timeout: 3h -Ubuntu_GCC_Static_Deps_tests: - extends: Ubuntu_GCC_Static_Deps +Ubuntu_Static_Deps_tests: + extends: Ubuntu_Static_Deps cache: - key: Ubuntu_GCC_Static_Deps_tests + key: Ubuntu_Static_Deps_tests.V1 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 + CC: clang + CXX: clang++ + CXXFLAGS: -O0 artifacts: paths: [] expire_in: 1 minute diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 8278ffcd0e..2b1d156c77 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -17,7 +17,7 @@ fi CXX_FLAGS='-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy' if [[ "${CXX}" == 'clang++' ]]; then - CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments" + CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments -Wno-error=misleading-indentation" fi declare -a CMAKE_CONF_OPTS=( @@ -30,7 +30,7 @@ declare -a CMAKE_CONF_OPTS=( -DBUILD_SHARED_LIBS=OFF -DUSE_SYSTEM_TINYXML=ON -DCMAKE_INSTALL_PREFIX=install - -DCMAKE_C_FLAGS='-Werror' + -DCMAKE_C_FLAGS="-Werror -Wno-error=misleading-indentation" -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" -DOPENMW_CXX_FLAGS="-Werror=implicit-fallthrough" ) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 0bfdc4c233..27351ce675 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -147,11 +147,11 @@ if(NOT OPENMW_USE_SYSTEM_OSG) endif() endif() - # branch OpenSceneGraph-3.6 on 23 Jan 2021. + # OSGoS branch 3.6 include(FetchContent) FetchContent_Declare(osg - URL https://github.com/OpenMW/osg/archive/e65f47c4ab3a0b53cc19f517961671e5f840a08d.zip - URL_HASH MD5=0c967fe48d80744f6956f6b0b67ef7c6 + URL https://github.com/OpenMW/osg/archive/01cc2b585c8456a4ff843066b7e1a8715558289f.zip + URL_HASH MD5=f1496c5ce32f733581e84568cf2712af SOURCE_DIR fetched/osg ) FetchContent_MakeAvailableExcludeFromAll(osg) From 55f95f1ea31818c574628a93ba66a1c9e461408a Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 18 Jan 2022 20:07:02 +0100 Subject: [PATCH 1957/2859] Enable controller gyro and capture the values --- apps/openmw/mwinput/controllermanager.cpp | 30 +++++++++++++++++++++++ apps/openmw/mwinput/controllermanager.hpp | 6 +++++ 2 files changed, 36 insertions(+) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 1e72a1c95b..5b37686c7d 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include @@ -31,6 +33,7 @@ namespace MWInput : mBindingsManager(bindingsManager) , mActionManager(actionManager) , mMouseManager(mouseManager) + , mGyroAvailable(false) , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mSneakToggleShortcutTimer(0.f) @@ -287,6 +290,7 @@ namespace MWInput void ControllerManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) { mBindingsManager->controllerAdded(deviceID, arg); + enableGyroSensor(); } void ControllerManager::controllerRemoved(const SDL_ControllerDeviceEvent &arg) @@ -399,4 +403,30 @@ namespace MWInput return false; } + void ControllerManager::enableGyroSensor() + { + mGyroAvailable = false; + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (!cntrl) + return; + if (!SDL_GameControllerHasSensor(cntrl, SDL_SENSOR_GYRO)) + return; + if (SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE) < 0) + return; + mGyroAvailable = true; + } + + bool ControllerManager::isGyroAvailable() const + { + return mGyroAvailable; + } + + std::array ControllerManager::getGyroValues() const + { + float gyro[3] = { 0.f }; + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (cntrl && mGyroAvailable) + SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); + return std::array({gyro[0], gyro[1], gyro[2]}); + } } diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index 948b48d538..8df6ee5c9f 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -44,16 +44,22 @@ namespace MWInput float getAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1] bool isButtonPressed(SDL_GameControllerButton button) const; + bool isGyroAvailable() const; + std::array getGyroValues() const; + private: // Return true if GUI consumes input. bool gamepadToGuiControl(const SDL_ControllerButtonEvent &arg); bool gamepadToGuiControl(const SDL_ControllerAxisEvent &arg); + void enableGyroSensor(); + BindingsManager* mBindingsManager; ActionManager* mActionManager; MouseManager* mMouseManager; bool mJoystickEnabled; + bool mGyroAvailable; float mGamepadCursorSpeed; float mSneakToggleShortcutTimer; bool mGamepadGuiCursorEnabled; From 9fa0faf94453381039fde917877dcb245e30bdc7 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 18 Jan 2022 21:10:35 +0100 Subject: [PATCH 1958/2859] Refactor sensor manager to match controller manager gyro API --- apps/openmw/mwinput/sensormanager.cpp | 45 ++++++++++++++++----------- apps/openmw/mwinput/sensormanager.hpp | 8 +++-- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index 3e8e70aefe..601924fa16 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -13,8 +13,7 @@ namespace MWInput SensorManager::SensorManager() : mInvertX(Settings::Manager::getBool("invert x axis", "Input")) , mInvertY(Settings::Manager::getBool("invert y axis", "Input")) - , mGyroXSpeed(0.f) - , mGyroYSpeed(0.f) + , mGyroValues() , mGyroUpdateTimer(0.f) , mGyroHSensitivity(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) , mGyroVSensitivity(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) @@ -119,7 +118,6 @@ namespace MWInput { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; - mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } @@ -141,7 +139,6 @@ namespace MWInput { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; - mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } } @@ -177,18 +174,18 @@ namespace MWInput } } - float SensorManager::getGyroAxisSpeed(GyroscopeAxis axis, const SDL_SensorEvent &arg) const + float SensorManager::getGyroAxisSpeed(GyroscopeAxis axis) const { switch (axis) { case GyroscopeAxis::X: case GyroscopeAxis::Y: case GyroscopeAxis::Z: - return std::abs(arg.data[0]) >= mGyroInputThreshold ? arg.data[axis-1] : 0.f; + return std::abs(mGyroValues[0]) >= mGyroInputThreshold ? mGyroValues[axis - 1] : 0.f; case GyroscopeAxis::Minus_X: case GyroscopeAxis::Minus_Y: case GyroscopeAxis::Minus_Z: - return std::abs(arg.data[0]) >= mGyroInputThreshold ? -arg.data[std::abs(axis)-1] : 0.f; + return std::abs(mGyroValues[0]) >= mGyroInputThreshold ? -mGyroValues[std::abs(axis) - 1] : 0.f; default: return 0.f; } @@ -217,8 +214,9 @@ namespace MWInput break; case SDL_SENSOR_GYRO: { - mGyroXSpeed = getGyroAxisSpeed(mGyroHAxis, arg); - mGyroYSpeed = getGyroAxisSpeed(mGyroVAxis, arg); + mGyroValues[0] = arg.data[0]; + mGyroValues[1] = arg.data[1]; + mGyroValues[2] = arg.data[2]; mGyroUpdateTimer = 0.f; break; @@ -230,28 +228,29 @@ namespace MWInput void SensorManager::update(float dt) { - if (mGyroXSpeed == 0.f && mGyroYSpeed == 0.f) - return; - + mGyroUpdateTimer += dt; if (mGyroUpdateTimer > 0.5f) { // More than half of second passed since the last gyroscope update. // A device more likely was disconnected or switched to the sleep mode. // Reset current rotation speed and wait for update. - mGyroXSpeed = 0.f; - mGyroYSpeed = 0.f; + mGyroValues = { 0, 0, 0 }; mGyroUpdateTimer = 0.f; return; } - mGyroUpdateTimer += dt; - if (!mGuiCursorEnabled) { + float gyroH = getGyroAxisSpeed(mGyroHAxis); + float gyroV = getGyroAxisSpeed(mGyroVAxis); + + if (gyroH == 0 && gyroV == 0) + return; + float rot[3]; - rot[0] = -mGyroYSpeed * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1); + rot[0] = -gyroV * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1); rot[1] = 0.0f; - rot[2] = -mGyroXSpeed * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1); + rot[2] = -gyroH * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1); // Only actually turn player when we're not in vanity mode bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); @@ -267,4 +266,14 @@ namespace MWInput MWBase::Environment::get().getInputManager()->resetIdleTime(); } } + + bool SensorManager::isGyroAvailable() const + { + return mGyroscope != nullptr; + } + + std::array SensorManager::getGyroValues() const + { + return mGyroValues; + } } diff --git a/apps/openmw/mwinput/sensormanager.hpp b/apps/openmw/mwinput/sensormanager.hpp index 75472d43b4..e096a89260 100644 --- a/apps/openmw/mwinput/sensormanager.hpp +++ b/apps/openmw/mwinput/sensormanager.hpp @@ -35,6 +35,9 @@ namespace MWInput void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } + bool isGyroAvailable() const; + std::array getGyroValues() const; + private: enum GyroscopeAxis { @@ -50,13 +53,12 @@ namespace MWInput void updateSensors(); void correctGyroscopeAxes(); GyroscopeAxis mapGyroscopeAxis(const std::string& axis); - float getGyroAxisSpeed(GyroscopeAxis axis, const SDL_SensorEvent &arg) const; + float getGyroAxisSpeed(GyroscopeAxis axis) const; bool mInvertX; bool mInvertY; - float mGyroXSpeed; - float mGyroYSpeed; + std::array mGyroValues; float mGyroUpdateTimer; float mGyroHSensitivity; From a496f16cdb6939df27949da07c1fa8b1cd40f320 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 18 Jan 2022 22:47:49 +0100 Subject: [PATCH 1959/2859] Implement gyro camera for controllers --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwinput/gyroaxis.cpp | 22 ++++++ apps/openmw/mwinput/gyroaxis.hpp | 22 ++++++ apps/openmw/mwinput/gyromanager.cpp | 95 +++++++++++++++++++++++++ apps/openmw/mwinput/gyromanager.hpp | 38 ++++++++++ apps/openmw/mwinput/inputmanagerimp.cpp | 18 +++++ apps/openmw/mwinput/inputmanagerimp.hpp | 2 + apps/openmw/mwinput/sensormanager.cpp | 89 +---------------------- apps/openmw/mwinput/sensormanager.hpp | 20 +----- 9 files changed, 203 insertions(+), 105 deletions(-) create mode 100644 apps/openmw/mwinput/gyroaxis.cpp create mode 100644 apps/openmw/mwinput/gyroaxis.hpp create mode 100644 apps/openmw/mwinput/gyromanager.cpp create mode 100644 apps/openmw/mwinput/gyromanager.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ea1d48885c..79cb6f30bd 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -27,7 +27,7 @@ add_openmw_dir (mwrender add_openmw_dir (mwinput actions actionmanager bindingsmanager controllermanager controlswitch - inputmanagerimp mousemanager keyboardmanager sensormanager + inputmanagerimp mousemanager keyboardmanager gyroaxis sensormanager gyromanager ) add_openmw_dir (mwgui diff --git a/apps/openmw/mwinput/gyroaxis.cpp b/apps/openmw/mwinput/gyroaxis.cpp new file mode 100644 index 0000000000..3eb71fb30b --- /dev/null +++ b/apps/openmw/mwinput/gyroaxis.cpp @@ -0,0 +1,22 @@ +#include "gyroaxis.hpp" + +namespace MWInput +{ + GyroscopeAxis gyroscopeAxisFromString(std::string_view s) + { + if (s == "x") + return GyroscopeAxis::X; + else if (s == "y") + return GyroscopeAxis::Y; + else if (s == "z") + return GyroscopeAxis::Z; + else if (s == "-x") + return GyroscopeAxis::Minus_X; + else if (s == "-y") + return GyroscopeAxis::Minus_Y; + else if (s == "-z") + return GyroscopeAxis::Minus_Z; + + return GyroscopeAxis::Unknown; + } +} diff --git a/apps/openmw/mwinput/gyroaxis.hpp b/apps/openmw/mwinput/gyroaxis.hpp new file mode 100644 index 0000000000..542217351d --- /dev/null +++ b/apps/openmw/mwinput/gyroaxis.hpp @@ -0,0 +1,22 @@ +#ifndef MWINPUT_GYROAXIS +#define MWINPUT_GYROAXIS + +#include + +namespace MWInput +{ + enum GyroscopeAxis + { + Unknown = 0, + X = 1, + Y = 2, + Z = 3, + Minus_X = -1, + Minus_Y = -2, + Minus_Z = -3 + }; + + GyroscopeAxis gyroscopeAxisFromString(std::string_view s); +} + +#endif // !MWINPUT_GYROAXIS diff --git a/apps/openmw/mwinput/gyromanager.cpp b/apps/openmw/mwinput/gyromanager.cpp new file mode 100644 index 0000000000..5a2b532186 --- /dev/null +++ b/apps/openmw/mwinput/gyromanager.cpp @@ -0,0 +1,95 @@ +#include "gyromanager.hpp" + +#include "../mwbase/inputmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" + +namespace MWInput +{ + GyroManager::GyroManager() + : mEnabled(Settings::Manager::getBool("enable gyroscope", "Input")) + , mGuiCursorEnabled(true) + , mSensitivityH(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) + , mSensitivityV(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) + , mInvertH(Settings::Manager::getBool("invert x axis", "Input")) + , mInvertV(Settings::Manager::getBool("invert y axis", "Input")) + , mInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) + , mAxisH(gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input"))) + , mAxisV(gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input"))) + {}; + + void GyroManager::update(float dt, std::array values) const + { + if (!mGuiCursorEnabled) + { + float gyroH = getAxisValue(mAxisH, values); + float gyroV = getAxisValue(mAxisV, values); + + if (gyroH == 0 && gyroV == 0) + return; + + float rot[3]; + rot[0] = -gyroV * dt * mSensitivityV * 4 * (mInvertV ? -1 : 1); + rot[1] = 0.0f; + rot[2] = -gyroH * dt * mSensitivityH * 4 * (mInvertH ? -1 : 1); + + // Only actually turn player when we're not in vanity mode + 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 (!playerLooking) + MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); + + MWBase::Environment::get().getInputManager()->resetIdleTime(); + } + } + + void GyroManager::processChangedSettings(const Settings::CategorySettingVector& changed) + { + for (const auto& setting : changed) + { + if (setting.first != "Input") + continue; + + if (setting.second == "enable gyroscope") + mEnabled = Settings::Manager::getBool("enable gyroscope", "Input"); + else if (setting.second == "gyro horizontal sensitivity") + mSensitivityH = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); + else if (setting.second == "gyro vertical sensitivity") + mSensitivityV = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); + else if (setting.second == "invert x axis") + mInvertH = Settings::Manager::getBool("invert x axis", "Input"); + else if (setting.second == "invert y axis") + mInvertV = Settings::Manager::getBool("invert y axis", "Input"); + else if (setting.second == "gyro input threshold") + mInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); + else if (setting.second == "gyro horizontal axis") + mAxisH = gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input")); + else if (setting.second == "gyro vertical axis") + mAxisV = gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input")); + } + } + + namespace + { + int signum(int x) + { + return 0 < x - x < 0; + } + } + + float GyroManager::getAxisValue(GyroscopeAxis axis, std::array values) const + { + if (axis == GyroscopeAxis::Unknown) + return 0; + float value = values[std::abs(axis) - 1] * signum(axis); + //if (std::abs(value) <= mInputThreshold) + // value = 0; + return value; + } +} diff --git a/apps/openmw/mwinput/gyromanager.hpp b/apps/openmw/mwinput/gyromanager.hpp new file mode 100644 index 0000000000..2c4a13905f --- /dev/null +++ b/apps/openmw/mwinput/gyromanager.hpp @@ -0,0 +1,38 @@ +#ifndef MWINPUT_GYROMANAGER +#define MWINPUT_GYROMANAGER + +#include + +#include "gyroaxis.hpp" + +namespace MWInput +{ + class GyroManager + { + public: + GyroManager(); + + bool isEnabled() const { return mEnabled; } + + void update(float dt, std::array values) const; + + void processChangedSettings(const Settings::CategorySettingVector& changed); + + void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } + + private: + bool mEnabled; + bool mGuiCursorEnabled; + float mSensitivityH; + float mSensitivityV; + bool mInvertH; + bool mInvertV; + float mInputThreshold; + GyroscopeAxis mAxisH; + GyroscopeAxis mAxisV; + + float getAxisValue(GyroscopeAxis axis, std::array values) const; + }; +} + +#endif // !MWINPUT_GYROMANAGER diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 4ebe56bf94..8beead5803 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -19,6 +19,7 @@ #include "keyboardmanager.hpp" #include "mousemanager.hpp" #include "sensormanager.hpp" +#include "gyromanager.hpp" namespace MWInput { @@ -51,6 +52,8 @@ namespace MWInput mSensorManager = new SensorManager(); mInputWrapper->setSensorEventCallback(mSensorManager); + + mGyroManager = new GyroManager(); } void InputManager::clear() @@ -72,6 +75,8 @@ namespace MWInput delete mBindingsManager; delete mInputWrapper; + + delete mGyroManager; } void InputManager::setAttemptJump(bool jumping) @@ -100,6 +105,17 @@ namespace MWInput mMouseManager->update(dt); mSensorManager->update(dt); mActionManager->update(dt, controllerMove); + + if (mGyroManager->isEnabled()) + { + bool controllerAvailable = mControllerManager->isGyroAvailable(); + bool sensorAvailable = mSensorManager->isGyroAvailable(); + if (controllerAvailable || sensorAvailable) + { + mGyroManager->update(dt, + controllerAvailable ? mControllerManager->getGyroValues() : mSensorManager->getGyroValues()); + } + } } void InputManager::setDragDrop(bool dragDrop) @@ -117,6 +133,7 @@ namespace MWInput mControllerManager->setGuiCursorEnabled(guiMode); mMouseManager->setGuiCursorEnabled(guiMode); mSensorManager->setGuiCursorEnabled(guiMode); + mGyroManager->setGuiCursorEnabled(guiMode); mMouseManager->setMouseLookEnabled(!guiMode); if (guiMode) MWBase::Environment::get().getWindowManager()->showCrosshair(false); @@ -130,6 +147,7 @@ namespace MWInput { mMouseManager->processChangedSettings(changed); mSensorManager->processChangedSettings(changed); + mGyroManager->processChangedSettings(changed); } bool InputManager::getControlSwitch(std::string_view sw) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 41478d5dcb..3f9f1b3be1 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -39,6 +39,7 @@ namespace MWInput class KeyboardManager; class MouseManager; class SensorManager; + class GyroManager; /** * @brief Class that provides a high-level API for game input @@ -128,6 +129,7 @@ namespace MWInput KeyboardManager* mKeyboardManager; MouseManager* mMouseManager; SensorManager* mSensorManager; + GyroManager* mGyroManager; }; } #endif diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index 601924fa16..fed25b88ce 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -11,15 +11,10 @@ namespace MWInput { SensorManager::SensorManager() - : mInvertX(Settings::Manager::getBool("invert x axis", "Input")) - , mInvertY(Settings::Manager::getBool("invert y axis", "Input")) - , mGyroValues() + : mGyroValues() , mGyroUpdateTimer(0.f) - , mGyroHSensitivity(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) - , mGyroVSensitivity(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) , mGyroHAxis(GyroscopeAxis::Minus_X) , mGyroVAxis(GyroscopeAxis::Y) - , mGyroInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) , mGyroscope(nullptr) , mGuiCursorEnabled(true) { @@ -41,24 +36,6 @@ namespace MWInput } } - SensorManager::GyroscopeAxis SensorManager::mapGyroscopeAxis(const std::string& axis) - { - if (axis == "x") - return GyroscopeAxis::X; - else if (axis == "y") - return GyroscopeAxis::Y; - else if (axis == "z") - return GyroscopeAxis::Z; - else if (axis == "-x") - return GyroscopeAxis::Minus_X; - else if (axis == "-y") - return GyroscopeAxis::Minus_Y; - else if (axis == "-z") - return GyroscopeAxis::Minus_Z; - - return GyroscopeAxis::Unknown; - } - void SensorManager::correctGyroscopeAxes() { if (!Settings::Manager::getBool("enable gyroscope", "Input")) @@ -67,8 +44,8 @@ namespace MWInput // Treat setting from config as axes for landscape mode. // If the device does not support orientation change, do nothing. // Note: in is unclear how to correct axes for devices with non-standart Z axis direction. - mGyroHAxis = mapGyroscopeAxis(Settings::Manager::getString("gyro horizontal axis", "Input")); - mGyroVAxis = mapGyroscopeAxis(Settings::Manager::getString("gyro vertical axis", "Input")); + mGyroHAxis = gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input")); + mGyroVAxis = gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input")); SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); switch (currentOrientation) @@ -148,18 +125,6 @@ namespace MWInput { for (const auto& setting : changed) { - if (setting.first == "Input" && setting.second == "invert x axis") - mInvertX = Settings::Manager::getBool("invert x axis", "Input"); - - if (setting.first == "Input" && setting.second == "invert y axis") - mInvertY = Settings::Manager::getBool("invert y axis", "Input"); - - if (setting.first == "Input" && setting.second == "gyro horizontal sensitivity") - mGyroHSensitivity = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); - - if (setting.first == "Input" && setting.second == "gyro vertical sensitivity") - mGyroVSensitivity = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); - if (setting.first == "Input" && setting.second == "enable gyroscope") init(); @@ -168,26 +133,6 @@ namespace MWInput if (setting.first == "Input" && setting.second == "gyro vertical axis") correctGyroscopeAxes(); - - if (setting.first == "Input" && setting.second == "gyro input threshold") - mGyroInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); - } - } - - float SensorManager::getGyroAxisSpeed(GyroscopeAxis axis) const - { - switch (axis) - { - case GyroscopeAxis::X: - case GyroscopeAxis::Y: - case GyroscopeAxis::Z: - return std::abs(mGyroValues[0]) >= mGyroInputThreshold ? mGyroValues[axis - 1] : 0.f; - case GyroscopeAxis::Minus_X: - case GyroscopeAxis::Minus_Y: - case GyroscopeAxis::Minus_Z: - return std::abs(mGyroValues[0]) >= mGyroInputThreshold ? -mGyroValues[std::abs(axis) - 1] : 0.f; - default: - return 0.f; } } @@ -236,34 +181,6 @@ namespace MWInput // Reset current rotation speed and wait for update. mGyroValues = { 0, 0, 0 }; mGyroUpdateTimer = 0.f; - return; - } - - if (!mGuiCursorEnabled) - { - float gyroH = getGyroAxisSpeed(mGyroHAxis); - float gyroV = getGyroAxisSpeed(mGyroVAxis); - - if (gyroH == 0 && gyroV == 0) - return; - - float rot[3]; - rot[0] = -gyroV * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1); - rot[1] = 0.0f; - rot[2] = -gyroH * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1); - - // Only actually turn player when we're not in vanity mode - 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 (!playerLooking) - MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); - - MWBase::Environment::get().getInputManager()->resetIdleTime(); } } diff --git a/apps/openmw/mwinput/sensormanager.hpp b/apps/openmw/mwinput/sensormanager.hpp index e096a89260..92d0a036e8 100644 --- a/apps/openmw/mwinput/sensormanager.hpp +++ b/apps/openmw/mwinput/sensormanager.hpp @@ -6,6 +6,8 @@ #include #include +#include "gyroaxis.hpp" + namespace SDLUtil { class InputWrapper; @@ -39,33 +41,15 @@ namespace MWInput std::array getGyroValues() const; private: - enum GyroscopeAxis - { - Unknown = 0, - X = 1, - Y = 2, - Z = 3, - Minus_X = -1, - Minus_Y = -2, - Minus_Z = -3 - }; void updateSensors(); void correctGyroscopeAxes(); - GyroscopeAxis mapGyroscopeAxis(const std::string& axis); - float getGyroAxisSpeed(GyroscopeAxis axis) const; - - bool mInvertX; - bool mInvertY; std::array mGyroValues; float mGyroUpdateTimer; - float mGyroHSensitivity; - float mGyroVSensitivity; GyroscopeAxis mGyroHAxis; GyroscopeAxis mGyroVAxis; - float mGyroInputThreshold; SDL_Sensor* mGyroscope; From bdd13f36b2d0f6f9391f063f8adfe97f897f5877 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 18 Jan 2022 23:29:45 +0000 Subject: [PATCH 1960/2859] do some Appveyor Qt magic to get it all sorted --- CI/before_script.msvc.sh | 8 +++++++- appveyor.yml | 26 +++++++++----------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 32169fc04b..aaf459611e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -878,7 +878,13 @@ fi done echo Done. else - QT_SDK="C:/Qt/5.13/msvc2017${SUFFIX}" + # default to msvc2019 which pre-loads Qt 5.15.2 + qt_version="5.15.2" + if [ "msvc${MSVC_REAL_YEAR}" == "msvc2017" ]; then + qt_version="5.13" + fi + QT_SDK="C:/Qt/${qt_version}/msvc${MSVC_REAL_YEAR}${SUFFIX}" + add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" for CONFIGURATION in ${CONFIGURATIONS[@]}; do diff --git a/appveyor.yml b/appveyor.yml index e2c13ed948..95f070a662 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,6 @@ environment: APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 platform: -# - Win32 - x64 configuration: @@ -22,26 +21,20 @@ configuration: - Release # - RelWithDebInfo -# For the Qt, Boost, CMake, etc installs -#os: Visual Studio 2017 - # We want the git revision for versioning, # so shallow clones don't work. clone_depth: 1 cache: - - C:\projects\openmw\deps\Bullet-2.87-msvc2015-win32.7z - - C:\projects\openmw\deps\Bullet-2.87-msvc2015-win64.7z - - C:\projects\openmw\deps\MyGUI-3.2.2-msvc2015-win32.7z - - C:\projects\openmw\deps\MyGUI-3.2.2-msvc2015-win64.7z - - C:\projects\openmw\deps\OSG-3.4.1-scrawl-msvc2015-win32.7z - - C:\projects\openmw\deps\OSG-3.4.1-scrawl-msvc2015-win64.7z - - C:\projects\openmw\deps\ffmpeg-3.2.4-dev-win32.zip - - C:\projects\openmw\deps\ffmpeg-3.2.4-dev-win64.zip - - C:\projects\openmw\deps\ffmpeg-3.2.4-win32.zip - - C:\projects\openmw\deps\ffmpeg-3.2.4-win64.zip - - C:\projects\openmw\deps\OpenAL-Soft-1.19.1.zip - - C:\projects\openmw\deps\SDL2-2.0.7.zip + - C:\projects\openmw\deps\Bullet-2.89-msvc2017-win64-double.7z + - C:\projects\openmw\deps\MyGUI-3.4.1-msvc2017-win64.7z + - C:\projects\openmw\deps\MyGUI-3.4.1-msvc2019-win64.7z + - C:\projects\openmw\deps\OSGoS-3.6.5-b02abe2-msvc2017-win64.7z + - C:\projects\openmw\deps\OSGoS-3.6.5-b02abe2-msvc2019-win64.7z + - C:\projects\openmw\deps\ffmpeg-4.2.2-dev-win64.zip + - C:\projects\openmw\deps\ffmpeg-4.2.2-win64.zip + - C:\projects\openmw\deps\OpenAL-Soft-1.20.1.zip + - C:\projects\openmw\deps\SDL2-2.0.18.zip clone_folder: C:\projects\openmw @@ -53,7 +46,6 @@ before_build: - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install build_script: - - cmd: if %PLATFORM%==Win32 set build=MSVC%msvc%_32 - cmd: if %PLATFORM%==x64 set build=MSVC%msvc%_64 - cmd: msbuild %build%\OpenMW.sln /t:Build /p:Configuration=%configuration% /m:2 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - cmd: cmake --install %build% --config %configuration% From a2002bc983fb2ed3c767f2bc81db4e0da069ca82 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 19 Jan 2022 01:07:17 +0100 Subject: [PATCH 1961/2859] Support frequency of ProgressReporter calls lower than interval --- components/misc/progressreporter.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/misc/progressreporter.hpp b/components/misc/progressreporter.hpp index 733e36191e..30d9b0ae2e 100644 --- a/components/misc/progressreporter.hpp +++ b/components/misc/progressreporter.hpp @@ -29,10 +29,10 @@ namespace Misc { const std::lock_guard lock(mMutex); const auto now = std::chrono::steady_clock::now(); - const auto left = mNextReport - now; - if (left.count() > 0 || provided == expected) + if (mNextReport > now || provided == expected) return false; - mNextReport += mInterval + left; + if (mInterval.count() > 0) + mNextReport = mNextReport + mInterval * ((now - mNextReport + mInterval).count() / mInterval.count()); return true; } (); if (shouldReport) From fbb72a19517f3898558367c11b3d8959b75403a8 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 19 Jan 2022 11:31:49 +0000 Subject: [PATCH 1962/2859] Make back launcher "Game Mechanics" tab to be default --- files/ui/advancedpage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 8e976df42d..18f406d5b3 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -14,7 +14,7 @@ - 1 + 0 From 9cafc31c0d8cafb1c75299b41535fe49fc5028a2 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 19 Jan 2022 11:32:38 +0000 Subject: [PATCH 1963/2859] Restore ripples with soft particles --- apps/openmw/mwrender/ripplesimulation.cpp | 2 +- components/resource/scenemanager.cpp | 4 +++- components/resource/scenemanager.hpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 282362ea56..1045594c20 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -109,7 +109,7 @@ RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* createWaterRippleStateSet(resourceSystem, mParticleNode); - resourceSystem->getSceneManager()->recreateShaders(mParticleNode); + resourceSystem->getSceneManager()->recreateShaders(mParticleNode, "objects", false, nullptr, true); mParent->addChild(mParticleNode); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 41f65ba0ab..8ddc3fd102 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -340,13 +340,15 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate, bool disableSoftParticles) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix)); shaderVisitor->setAllowedToModifyStateSets(false); shaderVisitor->setProgramTemplate(programTemplate); if (forceShadersForNode) shaderVisitor->setForceShaders(true); + if (disableSoftParticles) + shaderVisitor->setOpaqueDepthTex(nullptr); node->accept(*shaderVisitor); } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 58ae8fdb8b..b3a42bac45 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -77,7 +77,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr, bool disableSoftParticles = false); /// Applying shaders to a node may replace some fixed-function state. /// This restores it. From a79bdf07d27b659fd4ff0cb23eed0561e9597e62 Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 19 Jan 2022 15:48:35 +0000 Subject: [PATCH 1964/2859] do some bash magic to rearrange CXX_FLAGS --- CI/before_script.linux.sh | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 2b1d156c77..2a10ae2b78 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -14,12 +14,7 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then BUILD_BENCHMARKS=ON fi -CXX_FLAGS='-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy' - -if [[ "${CXX}" == 'clang++' ]]; then - CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments -Wno-error=misleading-indentation" -fi - +# setup our basic cmake build options declare -a CMAKE_CONF_OPTS=( -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" @@ -29,10 +24,9 @@ declare -a CMAKE_CONF_OPTS=( -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=OFF -DUSE_SYSTEM_TINYXML=ON + -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON -DCMAKE_INSTALL_PREFIX=install - -DCMAKE_C_FLAGS="-Werror -Wno-error=misleading-indentation" - -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" - -DOPENMW_CXX_FLAGS="-Werror=implicit-fallthrough" + -DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project ) if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then @@ -41,12 +35,13 @@ if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then -DOPENMW_USE_SYSTEM_OSG=OFF -DOPENMW_USE_SYSTEM_BULLET=OFF -DOPENMW_USE_SYSTEM_SQLITE3=OFF + -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=OFF ) fi if [[ $CI_CLANG_TIDY ]]; then CMAKE_CONF_OPTS+=( - -DCMAKE_CXX_CLANG_TIDY='clang-tidy;-checks=-*,boost-*,clang-analyzer-*,concurrency-*,performance-*,-header-filter=.*,bugprone-*,misc-definitions-in-headers,misc-misplaced-const,misc-redundant-expression,-bugprone-narrowing-conversions' + -DCMAKE_CXX_CLANG_TIDY="clang-tidy;-checks=-*,boost-*,clang-analyzer-*,concurrency-*,performance-*,-header-filter=.*,bugprone-*,misc-definitions-in-headers,misc-misplaced-const,misc-redundant-expression,-bugprone-narrowing-conversions" ) fi @@ -61,6 +56,16 @@ mkdir -p build cd build if [[ "${BUILD_TESTS_ONLY}" ]]; then + + # flags specific to our test suite + CXX_FLAGS="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" + if [[ "${CXX}" == 'clang++' ]]; then + CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments" + fi + CMAKE_CONF_OPTS+=( + -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" + ) + ${ANALYZE} cmake \ "${CMAKE_CONF_OPTS[@]}" \ -DBUILD_OPENMW=OFF \ From 4021d23cff2cd6b0dba7693c953d1b9c4f78e38c Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 19 Jan 2022 17:01:43 +0100 Subject: [PATCH 1965/2859] Refactor sensor manager axis correction --- apps/openmw/mwinput/controllermanager.cpp | 2 +- apps/openmw/mwinput/gyromanager.cpp | 28 ++++---------- apps/openmw/mwinput/gyromanager.hpp | 2 - apps/openmw/mwinput/sensormanager.cpp | 45 ++++++++--------------- apps/openmw/mwinput/sensormanager.hpp | 11 +++--- files/settings-default.cfg | 2 +- 6 files changed, 31 insertions(+), 59 deletions(-) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 5b37686c7d..8753344373 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -33,8 +33,8 @@ namespace MWInput : mBindingsManager(bindingsManager) , mActionManager(actionManager) , mMouseManager(mouseManager) - , mGyroAvailable(false) , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) + , mGyroAvailable(false) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mSneakToggleShortcutTimer(0.f) , mGamepadGuiCursorEnabled(true) diff --git a/apps/openmw/mwinput/gyromanager.cpp b/apps/openmw/mwinput/gyromanager.cpp index 5a2b532186..f590abf45c 100644 --- a/apps/openmw/mwinput/gyromanager.cpp +++ b/apps/openmw/mwinput/gyromanager.cpp @@ -12,8 +12,6 @@ namespace MWInput , mGuiCursorEnabled(true) , mSensitivityH(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) , mSensitivityV(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) - , mInvertH(Settings::Manager::getBool("invert x axis", "Input")) - , mInvertV(Settings::Manager::getBool("invert y axis", "Input")) , mInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) , mAxisH(gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input"))) , mAxisV(gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input"))) @@ -26,13 +24,13 @@ namespace MWInput float gyroH = getAxisValue(mAxisH, values); float gyroV = getAxisValue(mAxisV, values); - if (gyroH == 0 && gyroV == 0) + if (gyroH == 0.f && gyroV == 0.f) return; float rot[3]; - rot[0] = -gyroV * dt * mSensitivityV * 4 * (mInvertV ? -1 : 1); + rot[0] = -gyroV * dt * mSensitivityV; rot[1] = 0.0f; - rot[2] = -gyroH * dt * mSensitivityH * 4 * (mInvertH ? -1 : 1); + rot[2] = -gyroH * dt * mSensitivityH; // Only actually turn player when we're not in vanity mode bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); @@ -62,10 +60,6 @@ namespace MWInput mSensitivityH = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); else if (setting.second == "gyro vertical sensitivity") mSensitivityV = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); - else if (setting.second == "invert x axis") - mInvertH = Settings::Manager::getBool("invert x axis", "Input"); - else if (setting.second == "invert y axis") - mInvertV = Settings::Manager::getBool("invert y axis", "Input"); else if (setting.second == "gyro input threshold") mInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); else if (setting.second == "gyro horizontal axis") @@ -75,21 +69,15 @@ namespace MWInput } } - namespace - { - int signum(int x) - { - return 0 < x - x < 0; - } - } - float GyroManager::getAxisValue(GyroscopeAxis axis, std::array values) const { if (axis == GyroscopeAxis::Unknown) return 0; - float value = values[std::abs(axis) - 1] * signum(axis); - //if (std::abs(value) <= mInputThreshold) - // value = 0; + float value = values[std::abs(axis) - 1]; + if (axis < 0) + value *= -1; + if (std::abs(value) <= mInputThreshold) + value = 0; return value; } } diff --git a/apps/openmw/mwinput/gyromanager.hpp b/apps/openmw/mwinput/gyromanager.hpp index 2c4a13905f..ede8eb5c81 100644 --- a/apps/openmw/mwinput/gyromanager.hpp +++ b/apps/openmw/mwinput/gyromanager.hpp @@ -25,8 +25,6 @@ namespace MWInput bool mGuiCursorEnabled; float mSensitivityH; float mSensitivityV; - bool mInvertH; - bool mInvertV; float mInputThreshold; GyroscopeAxis mAxisH; GyroscopeAxis mAxisV; diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index fed25b88ce..cab78c46f2 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -11,10 +11,9 @@ namespace MWInput { SensorManager::SensorManager() - : mGyroValues() + : mRotation() + , mGyroValues() , mGyroUpdateTimer(0.f) - , mGyroHAxis(GyroscopeAxis::Minus_X) - , mGyroVAxis(GyroscopeAxis::Y) , mGyroscope(nullptr) , mGuiCursorEnabled(true) { @@ -44,40 +43,36 @@ namespace MWInput // Treat setting from config as axes for landscape mode. // If the device does not support orientation change, do nothing. // Note: in is unclear how to correct axes for devices with non-standart Z axis direction. - mGyroHAxis = gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input")); - mGyroVAxis = gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input")); + + mRotation = osg::Matrixf::identity(); + + float angle = 0; SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: - return; + break; case SDL_ORIENTATION_LANDSCAPE: break; case SDL_ORIENTATION_LANDSCAPE_FLIPPED: { - mGyroHAxis = GyroscopeAxis(-mGyroHAxis); - mGyroVAxis = GyroscopeAxis(-mGyroVAxis); - + angle = osg::PIf; break; } case SDL_ORIENTATION_PORTRAIT: { - GyroscopeAxis oldVAxis = mGyroVAxis; - mGyroVAxis = mGyroHAxis; - mGyroHAxis = GyroscopeAxis(-oldVAxis); - + angle = -0.5 * osg::PIf; break; } case SDL_ORIENTATION_PORTRAIT_FLIPPED: { - GyroscopeAxis oldVAxis = mGyroVAxis; - mGyroVAxis = GyroscopeAxis(-mGyroHAxis); - mGyroHAxis = oldVAxis; - + angle = 0.5 * osg::PIf; break; } } + + mRotation.makeRotate(angle, osg::Vec3f(0, 0, 1)); } void SensorManager::updateSensors() @@ -127,12 +122,6 @@ namespace MWInput { if (setting.first == "Input" && setting.second == "enable gyroscope") init(); - - if (setting.first == "Input" && setting.second == "gyro horizontal axis") - correctGyroscopeAxes(); - - if (setting.first == "Input" && setting.second == "gyro vertical axis") - correctGyroscopeAxes(); } } @@ -159,11 +148,9 @@ namespace MWInput break; case SDL_SENSOR_GYRO: { - mGyroValues[0] = arg.data[0]; - mGyroValues[1] = arg.data[1]; - mGyroValues[2] = arg.data[2]; + osg::Vec3f gyro(arg.data[0], arg.data[1], arg.data[2]); + mGyroValues = mRotation * gyro; mGyroUpdateTimer = 0.f; - break; } default: @@ -179,7 +166,7 @@ namespace MWInput // More than half of second passed since the last gyroscope update. // A device more likely was disconnected or switched to the sleep mode. // Reset current rotation speed and wait for update. - mGyroValues = { 0, 0, 0 }; + mGyroValues = osg::Vec3f(); mGyroUpdateTimer = 0.f; } } @@ -191,6 +178,6 @@ namespace MWInput std::array SensorManager::getGyroValues() const { - return mGyroValues; + return { mGyroValues.x(), mGyroValues.y(), mGyroValues.z() }; } } diff --git a/apps/openmw/mwinput/sensormanager.hpp b/apps/openmw/mwinput/sensormanager.hpp index 92d0a036e8..6353c47764 100644 --- a/apps/openmw/mwinput/sensormanager.hpp +++ b/apps/openmw/mwinput/sensormanager.hpp @@ -3,11 +3,12 @@ #include +#include +#include + #include #include -#include "gyroaxis.hpp" - namespace SDLUtil { class InputWrapper; @@ -45,12 +46,10 @@ namespace MWInput void updateSensors(); void correctGyroscopeAxes(); - std::array mGyroValues; + osg::Matrixf mRotation; + osg::Vec3f mGyroValues; float mGyroUpdateTimer; - GyroscopeAxis mGyroHAxis; - GyroscopeAxis mGyroVAxis; - SDL_Sensor* mGyroscope; bool mGuiCursorEnabled; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f1f3206661..cec1dc3432 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -540,7 +540,7 @@ gyro horizontal axis = -x gyro vertical axis = y # The minimum gyroscope movement that is able to rotate the camera. -gyro input threshold = 0.01 +gyro input threshold = 0 # Horizontal camera axis sensitivity to gyroscope movement. gyro horizontal sensitivity = 1.0 From 15e9c6615c304b5d66365be0005cdcb3d4a55aa0 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 19 Jan 2022 21:32:46 +0100 Subject: [PATCH 1966/2859] Disable controller gyro with older SDL --- apps/openmw/mwinput/controllermanager.cpp | 26 +++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 8753344373..6c6549face 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -406,14 +406,16 @@ namespace MWInput void ControllerManager::enableGyroSensor() { mGyroAvailable = false; - SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); - if (!cntrl) - return; - if (!SDL_GameControllerHasSensor(cntrl, SDL_SENSOR_GYRO)) - return; - if (SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE) < 0) - return; - mGyroAvailable = true; + #if SDL_VERSION_ATLEAST(2, 0, 14) + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (!cntrl) + return; + if (!SDL_GameControllerHasSensor(cntrl, SDL_SENSOR_GYRO)) + return; + if (SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE) < 0) + return; + mGyroAvailable = true; + #endif } bool ControllerManager::isGyroAvailable() const @@ -424,9 +426,11 @@ namespace MWInput std::array ControllerManager::getGyroValues() const { float gyro[3] = { 0.f }; - SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); - if (cntrl && mGyroAvailable) - SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); + #if SDL_VERSION_ATLEAST(2, 0, 14) + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (cntrl && mGyroAvailable) + SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); + #endif return std::array({gyro[0], gyro[1], gyro[2]}); } } From 57ac592973e81edc08e613e96b32f9d77eb2a02e Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 19 Jan 2022 22:35:32 +0100 Subject: [PATCH 1967/2859] Fix warning --- apps/openmw/mwinput/gyromanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/gyromanager.cpp b/apps/openmw/mwinput/gyromanager.cpp index f590abf45c..e06f9ac08d 100644 --- a/apps/openmw/mwinput/gyromanager.cpp +++ b/apps/openmw/mwinput/gyromanager.cpp @@ -15,7 +15,7 @@ namespace MWInput , mInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) , mAxisH(gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input"))) , mAxisV(gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input"))) - {}; + {} void GyroManager::update(float dt, std::array values) const { From 1df078551505280dc219afad687010c5e460062e Mon Sep 17 00:00:00 2001 From: psi29a Date: Thu, 20 Jan 2022 11:08:23 +0000 Subject: [PATCH 1968/2859] Update README.md to add discord link and be more clear about OpenMW like in our gitlab repo. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 557cd614cd..bf81c58e7e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ OpenMW ====== -OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. +OpenMW is an open-source open-world RPG game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. @@ -9,6 +9,8 @@ OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. * License: GPLv3 (see [LICENSE](https://gitlab.com/OpenMW/openmw/-/raw/master/LICENSE) for more information) * Website: https://www.openmw.org * IRC: #openmw on irc.libera.chat +* Discord: https://discord.gg/bWuqq2e + Font Licenses: * DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://gitlab.com/OpenMW/openmw/-/raw/master/files/mygui/DejaVuFontLicense.txt) for more information) From 183ca3079e8f9f1f7a99f025cc6eb4543d73b3ba Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 20 Jan 2022 15:42:01 +0100 Subject: [PATCH 1969/2859] Merge gyroaxis into gyro manager --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwinput/gyroaxis.cpp | 22 ---------------------- apps/openmw/mwinput/gyroaxis.hpp | 22 ---------------------- apps/openmw/mwinput/gyromanager.cpp | 18 ++++++++++++++++++ apps/openmw/mwinput/gyromanager.hpp | 15 +++++++++++++-- 5 files changed, 32 insertions(+), 47 deletions(-) delete mode 100644 apps/openmw/mwinput/gyroaxis.cpp delete mode 100644 apps/openmw/mwinput/gyroaxis.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 79cb6f30bd..3c8a0a4d9a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -27,7 +27,7 @@ add_openmw_dir (mwrender add_openmw_dir (mwinput actions actionmanager bindingsmanager controllermanager controlswitch - inputmanagerimp mousemanager keyboardmanager gyroaxis sensormanager gyromanager + inputmanagerimp mousemanager keyboardmanager sensormanager gyromanager ) add_openmw_dir (mwgui diff --git a/apps/openmw/mwinput/gyroaxis.cpp b/apps/openmw/mwinput/gyroaxis.cpp deleted file mode 100644 index 3eb71fb30b..0000000000 --- a/apps/openmw/mwinput/gyroaxis.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "gyroaxis.hpp" - -namespace MWInput -{ - GyroscopeAxis gyroscopeAxisFromString(std::string_view s) - { - if (s == "x") - return GyroscopeAxis::X; - else if (s == "y") - return GyroscopeAxis::Y; - else if (s == "z") - return GyroscopeAxis::Z; - else if (s == "-x") - return GyroscopeAxis::Minus_X; - else if (s == "-y") - return GyroscopeAxis::Minus_Y; - else if (s == "-z") - return GyroscopeAxis::Minus_Z; - - return GyroscopeAxis::Unknown; - } -} diff --git a/apps/openmw/mwinput/gyroaxis.hpp b/apps/openmw/mwinput/gyroaxis.hpp deleted file mode 100644 index 542217351d..0000000000 --- a/apps/openmw/mwinput/gyroaxis.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef MWINPUT_GYROAXIS -#define MWINPUT_GYROAXIS - -#include - -namespace MWInput -{ - enum GyroscopeAxis - { - Unknown = 0, - X = 1, - Y = 2, - Z = 3, - Minus_X = -1, - Minus_Y = -2, - Minus_Z = -3 - }; - - GyroscopeAxis gyroscopeAxisFromString(std::string_view s); -} - -#endif // !MWINPUT_GYROAXIS diff --git a/apps/openmw/mwinput/gyromanager.cpp b/apps/openmw/mwinput/gyromanager.cpp index e06f9ac08d..b0c6f121c5 100644 --- a/apps/openmw/mwinput/gyromanager.cpp +++ b/apps/openmw/mwinput/gyromanager.cpp @@ -7,6 +7,24 @@ namespace MWInput { + GyroManager::GyroscopeAxis GyroManager::gyroscopeAxisFromString(std::string_view s) + { + if (s == "x") + return GyroscopeAxis::X; + else if (s == "y") + return GyroscopeAxis::Y; + else if (s == "z") + return GyroscopeAxis::Z; + else if (s == "-x") + return GyroscopeAxis::Minus_X; + else if (s == "-y") + return GyroscopeAxis::Minus_Y; + else if (s == "-z") + return GyroscopeAxis::Minus_Z; + + return GyroscopeAxis::Unknown; + } + GyroManager::GyroManager() : mEnabled(Settings::Manager::getBool("enable gyroscope", "Input")) , mGuiCursorEnabled(true) diff --git a/apps/openmw/mwinput/gyromanager.hpp b/apps/openmw/mwinput/gyromanager.hpp index ede8eb5c81..bcd3b88f49 100644 --- a/apps/openmw/mwinput/gyromanager.hpp +++ b/apps/openmw/mwinput/gyromanager.hpp @@ -3,8 +3,6 @@ #include -#include "gyroaxis.hpp" - namespace MWInput { class GyroManager @@ -21,6 +19,19 @@ namespace MWInput void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } private: + enum GyroscopeAxis + { + Unknown = 0, + X = 1, + Y = 2, + Z = 3, + Minus_X = -1, + Minus_Y = -2, + Minus_Z = -3 + }; + + static GyroscopeAxis gyroscopeAxisFromString(std::string_view s); + bool mEnabled; bool mGuiCursorEnabled; float mSensitivityH; From 580edf18b9cd9ec8911ae8ccfb2c03efa2eb0a06 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 21 Jan 2022 00:28:56 +0000 Subject: [PATCH 1970/2859] Use weak_ptr for Actor and Projectile simulations (#6515) --- apps/openmw/mwphysics/mtphysics.cpp | 25 ++++++++++++++++++++----- apps/openmw/mwphysics/physicssystem.hpp | 4 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2f7e3b5995..2bbd751f63 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -118,7 +118,10 @@ namespace const btCollisionWorld* mCollisionWorld; void operator()(MWPhysics::ActorSimulation& sim) const { - auto& [actor, frameData] = sim; + auto& [actorPtr, frameData] = sim; + const auto actor = actorPtr.lock(); + if (actor == nullptr) + return; actor->applyOffsetChange(); frameData.mPosition = actor->getPosition(); if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld)) @@ -157,7 +160,10 @@ namespace btCollisionWorld* mCollisionWorld; void operator()(MWPhysics::ActorSimulation& sim) const { - auto& [actor, frameData] = sim; + auto& [actorPtr, frameData] = sim; + const auto actor = actorPtr.lock(); + if (actor == nullptr) + return; if (actor->setPosition(frameData.mPosition)) { frameData.mPosition = actor->getPosition(); // account for potential position change made by script @@ -167,7 +173,10 @@ namespace } void operator()(MWPhysics::ProjectileSimulation& sim) const { - auto& [proj, frameData] = sim; + auto& [projPtr, frameData] = sim; + const auto proj = projPtr.lock(); + if (proj == nullptr) + return; proj->setPosition(frameData.mPosition); proj->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(proj->getCollisionObject()); @@ -197,7 +206,10 @@ namespace const MWPhysics::PhysicsTaskScheduler* scheduler; void operator()(MWPhysics::ActorSimulation& sim) const { - auto& [actor, frameData] = sim; + auto& [actorPtr, frameData] = sim; + const auto actor = actorPtr.lock(); + if (actor == nullptr) + return; auto ptr = actor->getPtr(); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); @@ -229,7 +241,10 @@ namespace } void operator()(MWPhysics::ProjectileSimulation& sim) const { - auto& [proj, frameData] = sim; + auto& [projPtr, frameData] = sim; + const auto proj = projPtr.lock(); + if (proj == nullptr) + return; proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt)); } }; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index c31bbfbf65..d2b535c427 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -117,8 +117,8 @@ namespace MWPhysics osg::Vec3f mStormDirection; }; - using ActorSimulation = std::pair, ActorFrameData>; - using ProjectileSimulation = std::pair, ProjectileFrameData>; + using ActorSimulation = std::pair, ActorFrameData>; + using ProjectileSimulation = std::pair, ProjectileFrameData>; using Simulation = std::variant; class PhysicsSystem : public RayCastingInterface From 90691814ee93953cb62dfc5292a4e3faf2a92e36 Mon Sep 17 00:00:00 2001 From: psi29a Date: Fri, 21 Jan 2022 21:06:01 +0000 Subject: [PATCH 1971/2859] Update .gitlab-ci.yml --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fef2f39234..be6f57053c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -134,7 +134,6 @@ Ubuntu_Static_Deps: - "cmake/**/*" - "CI/**/*" - ".gitlab-ci.yml" - allow_failure: true cache: key: Ubuntu_Static_Deps.V1 paths: From 3f14011087a48195a3f2289fde118e6602ba89fb Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 22 Jan 2022 00:15:56 +0100 Subject: [PATCH 1972/2859] Support multiple parents for NIF nodes Choose a parent base on which node is used to iterate over children nodes. This leads to duplicate handing of child nodes. A node will be handled so many times how many parents it has. For example: p1 p2 \ / c Will be handled as: p1 p2 | | c c If c has children they will be handled X times c is handled. --- .../nifloader/testbulletnifloader.cpp | 112 +++++++++++++----- components/nif/node.hpp | 6 +- components/nif/parent.hpp | 15 +++ components/nifbullet/bulletnifloader.cpp | 37 +++--- components/nifbullet/bulletnifloader.hpp | 11 +- components/nifosg/nifloader.cpp | 51 ++++---- 6 files changed, 156 insertions(+), 76 deletions(-) create mode 100644 components/nif/parent.hpp diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 8fbf5c1b5b..f84fbe2508 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -277,7 +277,7 @@ namespace value.flags = 0; init(value.trafo); value.hasBounds = false; - value.parent = nullptr; + value.parents.push_back(nullptr); value.isBone = false; } @@ -512,7 +512,7 @@ namespace mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); - mNode.parent = &mNiNode; + mNode.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -538,7 +538,7 @@ namespace mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); - mNode.parent = &mNiNode; + mNode.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -569,13 +569,13 @@ namespace mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); - mNode.parent = &mNiNode; + mNode.parents.push_back(&mNiNode); mNode2.hasBounds = true; mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); - mNode2.parent = &mNiNode; + mNode2.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -605,14 +605,14 @@ namespace mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); - mNode.parent = &mNiNode; + mNode.parents.push_back(&mNiNode); mNode2.hasBounds = true; mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); - mNode2.parent = &mNiNode; + mNode2.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -691,7 +691,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) { - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -710,9 +710,9 @@ namespace TEST_F(TestBulletNifLoader, for_nested_tri_shape_child_should_return_shape_with_triangle_mesh_shape) { mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); - mNiNode2.parent = &mNiNode; + mNiNode2.parents.push_back(&mNiNode); mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); - mNiTriShape.parent = &mNiNode2; + mNiTriShape.parents.push_back(&mNiNode2); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); @@ -729,8 +729,8 @@ namespace TEST_F(TestBulletNifLoader, for_two_tri_shape_children_should_return_shape_with_triangle_mesh_shape_with_all_meshes) { - mNiTriShape.parent = &mNiNode; - mNiTriShape2.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); + mNiTriShape2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2) @@ -753,7 +753,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_shape_with_triangle_mesh_shape) { mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -796,7 +796,7 @@ namespace { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.trafo.scale = 4; @@ -822,11 +822,11 @@ namespace { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; - mNiTriShape2.parent = &mNiNode; + mNiTriShape2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), @@ -864,7 +864,7 @@ namespace mController.flags |= Nif::NiNode::ControllerFlag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiTriShape.controller = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.trafo.scale = 4; @@ -893,10 +893,10 @@ namespace mController.flags |= Nif::NiNode::ControllerFlag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; - mNiTriShape2.parent = &mNiNode; + mNiTriShape2.parents.push_back(&mNiNode); mNiTriShape2.controller = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), @@ -931,7 +931,7 @@ namespace TEST_F(TestBulletNifLoader, should_add_static_mesh_to_existing_compound_mesh) { - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); @@ -959,7 +959,7 @@ namespace TEST_F(TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) { - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; @@ -979,7 +979,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -996,7 +996,7 @@ namespace { auto data = static_cast(mNiTriShape.data.getPtr()); data->triangles.clear(); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1014,7 +1014,7 @@ namespace mNiStringExtraData.string = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1033,7 +1033,7 @@ namespace mNiStringExtraData2.string = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1051,7 +1051,7 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1069,10 +1069,10 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parent = &mNiNode2; + mNiTriShape.parents.push_back(&mNiNode2); mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode2.recType = Nif::RC_RootCollisionNode; - mNiNode2.parent = &mNiNode; + mNiNode2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); mNiNode.recType = Nif::RC_NiNode; @@ -1163,7 +1163,7 @@ namespace TEST_F(TestBulletNifLoader, for_avoid_collision_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; mNiTriStripsData.strips.front() = {0, 1}; @@ -1181,7 +1181,7 @@ namespace TEST_F(TestBulletNifLoader, for_animated_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { mNiTriStripsData.strips.front() = {0, 1}; - mNiTriStrips.parent = &mNiNode; + mNiTriStrips.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriStrips)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1197,7 +1197,7 @@ namespace TEST_F(TestBulletNifLoader, should_not_add_static_mesh_with_no_triangles_to_compound_shape) { mNiTriStripsData.strips.front() = {0, 1}; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); @@ -1218,4 +1218,54 @@ namespace EXPECT_EQ(*result, expected); } + + TEST_F(TestBulletNifLoader, should_handle_node_with_multiple_parents) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 4; + mNiTriShape.parents = {&mNiNode, &mNiNode2}; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.trafo.scale = 2; + mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode2.trafo.scale = 3; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiNode2)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles1(new btTriangleMesh(false)); + triangles1->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh1(new Resource::TriangleMeshShape(triangles1.release(), true)); + mesh1->setLocalScaling(btVector3(8, 8, 8)); + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); + mesh2->setLocalScaling(btVector3(12, 12, 12)); + std::unique_ptr shape(new btCompoundShape); + const btTransform transform1 { + btMatrix3x3( + 1, 0, 0, + 0, 0.8004512795493964327775415767973754555, 0.59939782204119995689950428641168400645, + 0, -0.59939782204119995689950428641168400645, 0.8004512795493964327775415767973754555 + ), + btVector3(2, 4, 6) + }; + const btTransform transform2 { + btMatrix3x3( + 1, 0, 0, + 0, 0.79515431915808965079861536651151254773, 0.60640713116208888600056070572463795543, + 0, -0.60640713116208888600056070572463795543, 0.79515431915808965079861536651151254773 + ), + btVector3(3, 6, 9) + }; + shape->addChildShape(transform1, mesh1.release()); + shape->addChildShape(transform2, mesh2.release()); + Resource::BulletShape expected; + expected.mCollisionShape.reset(shape.release()); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 7e5bcdb3be..31eb63144c 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -166,7 +166,7 @@ struct Node : public Named if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) collision.read(nif); - parent = nullptr; + parents.clear(); isBone = false; } @@ -180,7 +180,7 @@ struct Node : public Named // Parent node, or nullptr for the root node. As far as I'm aware, only // NiNodes (or types derived from NiNodes) can be parents. - NiNode *parent; + std::vector parents; bool isBone{false}; @@ -238,7 +238,7 @@ struct NiNode : Node { // Why would a unique list of children contain empty refs? if(!children[i].empty()) - children[i]->parent = this; + children[i]->parents.push_back(this); } } }; diff --git a/components/nif/parent.hpp b/components/nif/parent.hpp new file mode 100644 index 0000000000..afa540c8ec --- /dev/null +++ b/components/nif/parent.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_NIF_PARENT_HPP +#define OPENMW_COMPONENTS_NIF_PARENT_HPP + +namespace Nif +{ + struct NiNode; + + struct Parent + { + const NiNode& mNiNode; + const Parent* mParent; + }; +} + +#endif diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 4be07525a6..25079ad9e0 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -16,15 +16,17 @@ #include #include #include +#include namespace { -osg::Matrixf getWorldTransform(const Nif::Node& node) +osg::Matrixf getWorldTransform(const Nif::Node& node, const Nif::Parent* nodeParent) { - if(node.parent != nullptr) - return node.trafo.toMatrix() * getWorldTransform(*node.parent); - return node.trafo.toMatrix(); + osg::Matrixf result = node.trafo.toMatrix(); + for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) + result *= parent->mNiNode.trafo.toMatrix(); + return result; } bool pathFileNameStartsWithX(const std::string& path) @@ -214,7 +216,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) for (const Nif::Node* node : roots) { bool autogenerated = hasAutoGeneratedCollision(*node); - handleNode(filename, *node, 0, autogenerated, isAnimated, autogenerated); + handleNode(filename, *node, nullptr, 0, autogenerated, isAnimated, autogenerated); } if (mCompoundShape) @@ -305,8 +307,8 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node& rootNode) return true; } -void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, int flags, - bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) +void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, + int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) { // TODO: allow on-the fly collision switching via toggling this flag if (node.recType == Nif::RC_NiCollisionSwitch && !(node.flags & Nif::NiNode::Flag_ActiveCollision)) @@ -361,7 +363,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n || node.recType == Nif::RC_NiTriStrips || node.recType == Nif::RC_BSLODTriShape)) { - handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid); + handleNiTriShape(node, parent, flags, getWorldTransform(node, parent), isAnimated, avoid); } } @@ -369,30 +371,31 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n if (const Nif::NiNode *ninode = dynamic_cast(&node)) { const Nif::NodeList &list = ninode->children; + const Nif::Parent currentParent {*ninode, parent}; for(size_t i = 0;i < list.length();i++) { if (list[i].empty()) continue; - assert(list[i].get().parent == &node); - handleNode(fileName, list[i].get(), flags, isCollisionNode, isAnimated, autogenerated, avoid); + assert(std::find(list[i]->parents.begin(), list[i]->parents.end(), ninode) != list[i]->parents.end()); + handleNode(fileName, list[i].get(), ¤tParent, flags, isCollisionNode, isAnimated, autogenerated, avoid); } } } -void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, int flags, const osg::Matrixf &transform, - bool isAnimated, bool avoid) +void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, const Nif::Parent* parent, int flags, + const osg::Matrixf &transform, bool isAnimated, bool avoid) { // If the object was marked "NCO" earlier, it shouldn't collide with // anything. So don't do anything. if ((flags & 0x800)) return; - handleNiTriShape(static_cast(nifNode), transform, isAnimated, avoid); + handleNiTriShape(static_cast(nifNode), parent, transform, isAnimated, avoid); } -void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const osg::Matrixf &transform, - bool isAnimated, bool avoid) +void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, + const osg::Matrixf &transform, bool isAnimated, bool avoid) { if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) return; @@ -413,8 +416,8 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const childMesh.release(); float scale = niGeometry.trafo.scale; - for (const Nif::Node* parent = niGeometry.parent; parent != nullptr; parent = parent->parent) - scale *= parent->trafo.scale; + for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) + scale *= parent->mNiNode.trafo.scale; osg::Quat q = transform.getRotate(); osg::Vec3f v = transform.getTrans(); childShape->setLocalScaling(btVector3(scale, scale, scale)); diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index e0fec338c6..01a17a5aa1 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -28,6 +28,7 @@ namespace Nif struct NiTriShape; struct NiTriStrips; struct NiGeometry; + struct Parent; } namespace NifBullet @@ -55,14 +56,16 @@ public: private: bool findBoundingBox(const Nif::Node& node, const std::string& filename); - void handleNode(const std::string& fileName, const Nif::Node& node, int flags, bool isCollisionNode, - bool isAnimated=false, bool autogenerated=false, bool avoid=false); + void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, int flags, + bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false); bool hasAutoGeneratedCollision(const Nif::Node& rootNode); - void handleNiTriShape(const Nif::Node& nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); + void handleNiTriShape(const Nif::Node& nifNode, const Nif::Parent* parent, int flags, const osg::Matrixf& transform, + bool isAnimated, bool avoid); - void handleNiTriShape(const Nif::NiGeometry& nifNode, const osg::Matrixf& transform, bool isAnimated, bool avoid); + void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, const osg::Matrixf& transform, + bool isAnimated, bool avoid); std::unique_ptr mCompoundShape; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 43af68fc90..67cb16b795 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -17,6 +17,7 @@ #include #include #include +#include // particle #include @@ -74,10 +75,10 @@ namespace } // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it. - void collectDrawableProperties(const Nif::Node* nifNode, std::vector& out) + void collectDrawableProperties(const Nif::Node* nifNode, const Nif::Parent* parent, std::vector& out) { - if (nifNode->parent) - collectDrawableProperties(nifNode->parent, out); + if (parent != nullptr) + collectDrawableProperties(&parent->mNiNode, parent->mParent, out); const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i setDataVariance(osg::Object::STATIC); for (const Nif::Node* root : roots) { - auto node = handleNode(root, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + auto node = handleNode(root, nullptr, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); created->addChild(node); } if (mHasNightDayLabel) @@ -339,7 +340,7 @@ namespace NifOsg { // Get the lowest numbered recIndex of the NiTexturingProperty root node. // This is what is overridden when a spell effect "particle texture" is used. - if (nifNode->parent == nullptr && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) + if (nifNode->parents.empty() && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) { mFirstRootTextureIndex = props[i].getPtr()->recIndex; mFoundFirstRootTexturingProperty = true; @@ -483,7 +484,7 @@ namespace NifOsg // The Root node can be created as a Group if no transformation is required. // This takes advantage of the fact root nodes can't have additional controllers // loaded from an external .kf file (original engine just throws "can't find node" errors if you try). - if (!nifNode->parent && nifNode->controller.empty() && nifNode->trafo.isIdentity()) + if (nifNode->parents.empty() && nifNode->controller.empty() && nifNode->trafo.isIdentity()) node = new osg::Group; dataVariance = nifNode->isBone ? osg::Object::DYNAMIC : osg::Object::STATIC; @@ -505,8 +506,10 @@ namespace NifOsg return node; } - osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, osg::Node* rootNode=nullptr) + osg::ref_ptr handleNode(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + Resource::ImageManager* imageManager, std::vector boundTextures, int animflags, + bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, + osg::Node* rootNode=nullptr) { if (rootNode != nullptr && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) return nullptr; @@ -615,9 +618,9 @@ namespace NifOsg Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; if (skin.empty()) - handleGeometry(nifNode, node, composite, boundTextures, animflags); + handleGeometry(nifNode, parent, node, composite, boundTextures, animflags); else - handleSkinnedGeometry(nifNode, node, composite, boundTextures, animflags); + handleSkinnedGeometry(nifNode, parent, node, composite, boundTextures, animflags); if (!nifNode->controller.empty()) handleMeshControllers(nifNode, node, composite, boundTextures, animflags); @@ -625,7 +628,7 @@ namespace NifOsg } if (nifNode->recType == Nif::RC_NiParticles) - handleParticleSystem(nifNode, node, composite, animflags); + handleParticleSystem(nifNode, parent, node, composite, animflags); if (composite->getNumControllers() > 0) { @@ -681,10 +684,11 @@ namespace NifOsg } const Nif::NodeList &children = ninode->children; + const Nif::Parent currentParent {*ninode, parent}; for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - handleNode(children[i].getPtr(), currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); + handleNode(children[i].getPtr(), ¤tParent, currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); } } @@ -1026,7 +1030,8 @@ namespace NifOsg mEmitterQueue.clear(); } - void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) + void handleParticleSystem(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Group *parentNode, + SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); @@ -1095,7 +1100,7 @@ namespace NifOsg handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); std::vector drawableProps; - collectDrawableProperties(nifNode, drawableProps); + collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); // particle system updater (after the emitters and affectors in the scene graph) @@ -1146,7 +1151,9 @@ namespace NifOsg } } - void handleNiGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleNiGeometry(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Geometry *geometry, + osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, + const std::vector& boundTextures, int animflags) { const Nif::NiGeometry* niGeometry = static_cast(nifNode); if (niGeometry->data.empty()) @@ -1198,15 +1205,17 @@ namespace NifOsg // - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them // above the actual renderable would be tedious. std::vector drawableProps; - collectDrawableProperties(nifNode, drawableProps); + collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->colors.empty(), animflags); } - void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleGeometry(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, + int animflags) { assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geom (new osg::Geometry); - handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); + handleNiGeometry(nifNode, parent, geom, parentNode, composite, boundTextures, animflags); // If the record had no valid geometry data in it, early-out if (geom->empty()) return; @@ -1250,12 +1259,12 @@ namespace NifOsg return morphGeom; } - void handleSkinnedGeometry(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, - const std::vector& boundTextures, int animflags) + void handleSkinnedGeometry(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Group *parentNode, + SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geometry (new osg::Geometry); - handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); + handleNiGeometry(nifNode, parent, geometry, parentNode, composite, boundTextures, animflags); if (geometry->empty()) return; osg::ref_ptr rig(new SceneUtil::RigGeometry); From a85f2b0b2ac1e2ec172a6d094ff48a639784e5b8 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 22 Jan 2022 13:21:23 +0100 Subject: [PATCH 1973/2859] Remove unnecessary mGuiCursorEnabled; from sensor manager --- apps/openmw/mwinput/inputmanagerimp.cpp | 1 - apps/openmw/mwinput/sensormanager.cpp | 1 - apps/openmw/mwinput/sensormanager.hpp | 4 ---- 3 files changed, 6 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 8beead5803..40de2b45fb 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -132,7 +132,6 @@ namespace MWInput { mControllerManager->setGuiCursorEnabled(guiMode); mMouseManager->setGuiCursorEnabled(guiMode); - mSensorManager->setGuiCursorEnabled(guiMode); mGyroManager->setGuiCursorEnabled(guiMode); mMouseManager->setMouseLookEnabled(!guiMode); if (guiMode) diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index cab78c46f2..f3cee579e5 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -15,7 +15,6 @@ namespace MWInput , mGyroValues() , mGyroUpdateTimer(0.f) , mGyroscope(nullptr) - , mGuiCursorEnabled(true) { init(); } diff --git a/apps/openmw/mwinput/sensormanager.hpp b/apps/openmw/mwinput/sensormanager.hpp index 6353c47764..8f72b99de9 100644 --- a/apps/openmw/mwinput/sensormanager.hpp +++ b/apps/openmw/mwinput/sensormanager.hpp @@ -36,8 +36,6 @@ namespace MWInput void displayOrientationChanged() override; void processChangedSettings(const Settings::CategorySettingVector& changed); - void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } - bool isGyroAvailable() const; std::array getGyroValues() const; @@ -51,8 +49,6 @@ namespace MWInput float mGyroUpdateTimer; SDL_Sensor* mGyroscope; - - bool mGuiCursorEnabled; }; } #endif From 26bdba88a2a9a48a4779b11cda6555f5f6f30d6f Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 22 Jan 2022 17:17:20 +0100 Subject: [PATCH 1974/2859] Store std::weak_ptr into mUpdateAabb. This avoid extending the lifetime of collision objects beyond what they should. --- apps/openmw/mwphysics/mtphysics.cpp | 11 ++++++++--- apps/openmw/mwphysics/mtphysics.hpp | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2bbd751f63..29a59314b9 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -518,7 +518,7 @@ namespace MWPhysics mCollisionWorld->removeCollisionObject(collisionObject); } - void PhysicsTaskScheduler::updateSingleAabb(std::shared_ptr ptr, bool immediate) + void PhysicsTaskScheduler::updateSingleAabb(const std::shared_ptr& ptr, bool immediate) { if (immediate || mNumThreads == 0) { @@ -527,7 +527,7 @@ namespace MWPhysics else { MaybeExclusiveLock lock(mUpdateAabbMutex, mNumThreads); - mUpdateAabb.insert(std::move(ptr)); + mUpdateAabb.insert(ptr); } } @@ -570,7 +570,12 @@ namespace MWPhysics { MaybeExclusiveLock lock(mUpdateAabbMutex, mNumThreads); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), - [this](const std::shared_ptr& ptr) { updatePtrAabb(ptr); }); + [this](const std::weak_ptr& ptr) + { + auto p = ptr.lock(); + if (p != nullptr) + updatePtrAabb(p); + }); mUpdateAabb.clear(); } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 9ded1262d6..685d4eefab 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -54,7 +54,7 @@ namespace MWPhysics void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask); void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); void removeCollisionObject(btCollisionObject* collisionObject); - void updateSingleAabb(std::shared_ptr ptr, bool immediate=false); + void updateSingleAabb(const std::shared_ptr& ptr, bool immediate=false); bool getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2); void debugDraw(); void* getUserPointer(const btCollisionObject* object) const; @@ -85,7 +85,7 @@ namespace MWPhysics btCollisionWorld* mCollisionWorld; MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; - std::set> mUpdateAabb; + std::set, std::owner_less>> mUpdateAabb; // TODO: use std::experimental::flex_barrier or std::barrier once it becomes a thing std::unique_ptr mPreStepBarrier; From 24989e7bc1fb66577ec340591afa8d2e8f91d708 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 23 Jan 2022 11:30:22 +0100 Subject: [PATCH 1975/2859] Validate that object exists before onActorActive Lua handler --- apps/openmw/mwlua/luamanagerimp.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 8211c37abf..459a85d9c9 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -200,7 +200,13 @@ namespace MWLua } for (ObjectId id : mActorAddedEvents) - mGlobalScripts.actorActive(GObject(id, objectRegistry)); + { + GObject obj(id, objectRegistry); + if (obj.isValid()) + mGlobalScripts.actorActive(obj); + else + Log(Debug::Verbose) << "Can not call onActorActive engine handler: object" << idToString(id) << " is already removed"; + } mActorAddedEvents.clear(); if (!mWorldView.isPaused()) From 4b59ff20609b4a52d0a8203f337aa539ec1fc687 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 23 Jan 2022 15:40:14 +0100 Subject: [PATCH 1976/2859] Include headers instead of source files --- apps/openmw_test_suite/esmloader/esmdata.cpp | 10 +++++++++- apps/openmw_test_suite/esmloader/load.cpp | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/openmw_test_suite/esmloader/esmdata.cpp b/apps/openmw_test_suite/esmloader/esmdata.cpp index da78aaa3a2..7e5791561e 100644 --- a/apps/openmw_test_suite/esmloader/esmdata.cpp +++ b/apps/openmw_test_suite/esmloader/esmdata.cpp @@ -1,4 +1,12 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/apps/openmw_test_suite/esmloader/load.cpp b/apps/openmw_test_suite/esmloader/load.cpp index d6ed66f7a7..048657e65d 100644 --- a/apps/openmw_test_suite/esmloader/load.cpp +++ b/apps/openmw_test_suite/esmloader/load.cpp @@ -1,4 +1,12 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include From d1fb8545217a4e4044e6825eace60a485618954c Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 22 Jan 2022 15:58:41 +0100 Subject: [PATCH 1977/2859] move most of the files from esm to esm3, keep common code in esm; this is make space for a future with esm4 esm typo esm typo --- .../detournavigator/navmeshtilescache.cpp | 2 +- apps/esmtool/esmtool.cpp | 4 +- apps/esmtool/labels.cpp | 24 +++--- apps/essimporter/convertacdt.hpp | 8 +- apps/essimporter/convertcntc.hpp | 2 +- apps/essimporter/convertcrec.hpp | 2 +- apps/essimporter/converter.cpp | 4 +- apps/essimporter/converter.hpp | 34 ++++---- apps/essimporter/convertinventory.hpp | 2 +- apps/essimporter/convertnpcc.hpp | 2 +- apps/essimporter/convertplayer.hpp | 4 +- apps/essimporter/convertscpt.hpp | 2 +- apps/essimporter/convertscri.hpp | 2 +- apps/essimporter/importacdt.cpp | 4 +- apps/essimporter/importacdt.hpp | 2 +- apps/essimporter/importcellref.cpp | 2 +- apps/essimporter/importcellref.hpp | 2 +- apps/essimporter/importcntc.cpp | 2 +- apps/essimporter/importcrec.cpp | 2 +- apps/essimporter/importcrec.hpp | 2 +- apps/essimporter/importdial.cpp | 2 +- apps/essimporter/importer.cpp | 22 ++--- apps/essimporter/importercontext.hpp | 14 ++-- apps/essimporter/importgame.cpp | 2 +- apps/essimporter/importinfo.cpp | 2 +- apps/essimporter/importinventory.cpp | 2 +- apps/essimporter/importinventory.hpp | 2 +- apps/essimporter/importjour.cpp | 2 +- apps/essimporter/importklst.cpp | 2 +- apps/essimporter/importnpcc.cpp | 2 +- apps/essimporter/importnpcc.hpp | 4 +- apps/essimporter/importplayer.cpp | 2 +- apps/essimporter/importplayer.hpp | 2 +- apps/essimporter/importproj.cpp | 2 +- apps/essimporter/importques.cpp | 2 +- apps/essimporter/importscpt.cpp | 2 +- apps/essimporter/importscpt.hpp | 2 +- apps/essimporter/importscri.cpp | 2 +- apps/essimporter/importscri.hpp | 2 +- apps/essimporter/importsplm.cpp | 2 +- apps/launcher/utils/cellnameloader.cpp | 2 +- apps/launcher/utils/cellnameloader.hpp | 2 +- apps/mwiniimporter/importer.cpp | 2 +- apps/navmeshtool/main.cpp | 4 +- apps/navmeshtool/navmesh.cpp | 2 +- apps/navmeshtool/worldspacedata.cpp | 8 +- apps/navmeshtool/worldspacedata.hpp | 2 +- apps/opencs/model/doc/runner.hpp | 2 +- apps/opencs/model/doc/savingstages.cpp | 2 +- apps/opencs/model/doc/savingstate.hpp | 2 +- apps/opencs/model/tools/birthsigncheck.hpp | 2 +- apps/opencs/model/tools/bodypartcheck.hpp | 4 +- apps/opencs/model/tools/classcheck.cpp | 4 +- apps/opencs/model/tools/classcheck.hpp | 2 +- apps/opencs/model/tools/enchantmentcheck.hpp | 2 +- apps/opencs/model/tools/factioncheck.cpp | 2 +- apps/opencs/model/tools/factioncheck.hpp | 2 +- apps/opencs/model/tools/gmstcheck.hpp | 2 +- apps/opencs/model/tools/journalcheck.hpp | 2 +- apps/opencs/model/tools/magiceffectcheck.hpp | 4 +- apps/opencs/model/tools/racecheck.hpp | 2 +- apps/opencs/model/tools/regioncheck.hpp | 2 +- apps/opencs/model/tools/skillcheck.hpp | 2 +- apps/opencs/model/tools/soundcheck.hpp | 2 +- apps/opencs/model/tools/spellcheck.cpp | 2 +- apps/opencs/model/tools/spellcheck.hpp | 2 +- apps/opencs/model/tools/startscriptcheck.hpp | 4 +- apps/opencs/model/tools/topicinfocheck.hpp | 14 ++-- apps/opencs/model/world/actoradapter.cpp | 10 +-- apps/opencs/model/world/actoradapter.hpp | 4 +- apps/opencs/model/world/cell.hpp | 2 +- apps/opencs/model/world/cellcoordinates.cpp | 2 +- apps/opencs/model/world/columnimp.hpp | 6 +- apps/opencs/model/world/columns.cpp | 12 +-- apps/opencs/model/world/data.cpp | 6 +- apps/opencs/model/world/data.hpp | 38 ++++----- apps/opencs/model/world/idcollection.hpp | 2 +- apps/opencs/model/world/idtable.cpp | 2 +- apps/opencs/model/world/info.hpp | 2 +- apps/opencs/model/world/infocollection.cpp | 4 +- apps/opencs/model/world/infoselectwrapper.hpp | 2 +- apps/opencs/model/world/land.hpp | 2 +- apps/opencs/model/world/landtexture.cpp | 2 +- apps/opencs/model/world/landtexture.hpp | 2 +- apps/opencs/model/world/metadata.cpp | 6 +- .../model/world/nestedcoladapterimp.cpp | 4 +- .../model/world/nestedcoladapterimp.hpp | 10 +-- apps/opencs/model/world/pathgrid.hpp | 2 +- apps/opencs/model/world/ref.hpp | 2 +- apps/opencs/model/world/refcollection.cpp | 2 +- apps/opencs/model/world/refidadapterimp.cpp | 4 +- apps/opencs/model/world/refidadapterimp.hpp | 10 +-- apps/opencs/model/world/refidcollection.cpp | 2 +- apps/opencs/model/world/refiddata.hpp | 40 ++++----- apps/opencs/view/render/actor.cpp | 2 +- apps/opencs/view/render/actor.hpp | 2 +- apps/opencs/view/render/brushdraw.hpp | 2 +- apps/opencs/view/render/cell.cpp | 4 +- apps/opencs/view/render/cellborder.cpp | 2 +- apps/opencs/view/render/cellwater.cpp | 2 +- apps/opencs/view/render/commands.cpp | 2 +- apps/opencs/view/render/terrainselection.cpp | 2 +- apps/opencs/view/render/terrainselection.hpp | 2 +- apps/opencs/view/render/terrainshapemode.cpp | 2 +- apps/opencs/view/world/dialoguecreator.cpp | 2 +- apps/opencs/view/world/globalcreator.cpp | 2 +- apps/opencs/view/world/vartypedelegate.hpp | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwclass/activator.cpp | 2 +- apps/openmw/mwclass/actor.cpp | 2 +- apps/openmw/mwclass/actor.hpp | 2 +- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/armor.cpp | 6 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/container.cpp | 4 +- apps/openmw/mwclass/creature.cpp | 4 +- apps/openmw/mwclass/creaturelevlist.cpp | 4 +- apps/openmw/mwclass/door.cpp | 4 +- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/itemlevlist.cpp | 2 +- apps/openmw/mwclass/light.cpp | 4 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 6 +- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/static.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 8 +- apps/openmw/mwdialogue/dialoguemanagerimp.hpp | 2 +- apps/openmw/mwdialogue/hypertextparser.cpp | 2 +- apps/openmw/mwdialogue/journalentry.cpp | 2 +- apps/openmw/mwdialogue/journalimp.cpp | 8 +- apps/openmw/mwdialogue/quest.cpp | 2 +- apps/openmw/mwdialogue/selectwrapper.hpp | 2 +- apps/openmw/mwgui/bookwindow.cpp | 2 +- apps/openmw/mwgui/charactercreation.hpp | 2 +- apps/openmw/mwgui/class.hpp | 2 +- apps/openmw/mwgui/itemchargeview.cpp | 2 +- apps/openmw/mwgui/mapwindow.cpp | 4 +- apps/openmw/mwgui/mapwindow.hpp | 4 +- apps/openmw/mwgui/merchantrepair.cpp | 2 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 4 +- apps/openmw/mwgui/review.hpp | 2 +- apps/openmw/mwgui/scrollwindow.cpp | 2 +- apps/openmw/mwgui/sortfilteritemmodel.cpp | 26 +++--- apps/openmw/mwgui/spellcreationdialog.hpp | 4 +- apps/openmw/mwgui/spellicons.cpp | 2 +- apps/openmw/mwgui/spellmodel.hpp | 2 +- apps/openmw/mwgui/statswatcher.hpp | 2 +- apps/openmw/mwgui/widgets.hpp | 4 +- apps/openmw/mwgui/windowmanagerimp.cpp | 4 +- apps/openmw/mwinput/controlswitch.cpp | 6 +- apps/openmw/mwinput/inputmanagerimp.cpp | 4 +- apps/openmw/mwlua/cellbindings.cpp | 2 +- apps/openmw/mwlua/eventqueue.cpp | 4 +- apps/openmw/mwlua/luamanagerimp.cpp | 4 +- apps/openmw/mwlua/object.cpp | 2 +- apps/openmw/mwlua/object.hpp | 2 +- apps/openmw/mwlua/worldview.cpp | 6 +- apps/openmw/mwmechanics/activespells.cpp | 2 +- apps/openmw/mwmechanics/activespells.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 4 +- apps/openmw/mwmechanics/actorutil.hpp | 6 +- apps/openmw/mwmechanics/aiactivate.cpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aicombataction.cpp | 4 +- apps/openmw/mwmechanics/aiescort.cpp | 4 +- apps/openmw/mwmechanics/aifollow.cpp | 4 +- apps/openmw/mwmechanics/aipackage.cpp | 4 +- apps/openmw/mwmechanics/aipursue.cpp | 2 +- apps/openmw/mwmechanics/aisequence.cpp | 2 +- apps/openmw/mwmechanics/aisequence.hpp | 2 +- apps/openmw/mwmechanics/aitravel.cpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/alchemy.cpp | 8 +- apps/openmw/mwmechanics/alchemy.hpp | 2 +- apps/openmw/mwmechanics/creaturestats.cpp | 6 +- apps/openmw/mwmechanics/creaturestats.hpp | 2 +- apps/openmw/mwmechanics/enchanting.hpp | 4 +- apps/openmw/mwmechanics/magiceffects.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwmechanics/npcstats.cpp | 8 +- apps/openmw/mwmechanics/objects.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- apps/openmw/mwmechanics/pathgrid.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.hpp | 4 +- apps/openmw/mwmechanics/spelleffects.cpp | 2 +- apps/openmw/mwmechanics/spelllist.cpp | 2 +- apps/openmw/mwmechanics/spelllist.hpp | 2 +- apps/openmw/mwmechanics/spellpriority.cpp | 6 +- apps/openmw/mwmechanics/spells.cpp | 4 +- apps/openmw/mwmechanics/spellutil.hpp | 2 +- apps/openmw/mwmechanics/stat.cpp | 2 +- apps/openmw/mwmechanics/summoning.hpp | 2 +- apps/openmw/mwmechanics/weaponpriority.cpp | 2 +- apps/openmw/mwphysics/movementsolver.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 2 +- apps/openmw/mwrender/actoranimation.cpp | 4 +- apps/openmw/mwrender/characterpreview.hpp | 2 +- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/fogmanager.cpp | 2 +- apps/openmw/mwrender/globalmap.cpp | 2 +- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/groundcover.hpp | 2 +- apps/openmw/mwrender/localmap.cpp | 4 +- apps/openmw/mwrender/objectpaging.cpp | 2 +- apps/openmw/mwrender/objectpaging.hpp | 2 +- apps/openmw/mwrender/pathgrid.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/water.cpp | 2 +- apps/openmw/mwscript/compilercontext.cpp | 2 +- apps/openmw/mwscript/containerextensions.cpp | 2 +- apps/openmw/mwscript/globalscripts.cpp | 4 +- apps/openmw/mwscript/locals.cpp | 6 +- apps/openmw/mwscript/miscextensions.cpp | 4 +- apps/openmw/mwscript/scriptmanagerimp.cpp | 2 +- apps/openmw/mwscript/statsextensions.cpp | 2 +- .../mwscript/transformationextensions.cpp | 2 +- apps/openmw/mwsound/watersoundupdater.cpp | 2 +- apps/openmw/mwstate/character.cpp | 2 +- apps/openmw/mwstate/character.hpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 8 +- apps/openmw/mwworld/actioneat.cpp | 2 +- apps/openmw/mwworld/cellpreloader.cpp | 2 +- apps/openmw/mwworld/cellref.cpp | 2 +- apps/openmw/mwworld/cellref.hpp | 2 +- apps/openmw/mwworld/cells.cpp | 8 +- apps/openmw/mwworld/cellstore.cpp | 24 +++--- apps/openmw/mwworld/cellstore.hpp | 40 ++++----- apps/openmw/mwworld/containerstore.cpp | 2 +- apps/openmw/mwworld/containerstore.hpp | 24 +++--- apps/openmw/mwworld/esmloader.cpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 4 +- apps/openmw/mwworld/globals.cpp | 4 +- apps/openmw/mwworld/globals.hpp | 2 +- apps/openmw/mwworld/groundcoverstore.hpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 4 +- apps/openmw/mwworld/livecellref.cpp | 2 +- apps/openmw/mwworld/magiceffects.cpp | 2 +- apps/openmw/mwworld/player.cpp | 8 +- apps/openmw/mwworld/player.hpp | 4 +- apps/openmw/mwworld/projectilemanager.cpp | 4 +- apps/openmw/mwworld/projectilemanager.hpp | 2 +- apps/openmw/mwworld/refdata.cpp | 2 +- apps/openmw/mwworld/refdata.hpp | 2 +- apps/openmw/mwworld/store.cpp | 6 +- apps/openmw/mwworld/weather.cpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 8 +- .../detournavigator/navigator.cpp | 2 +- .../detournavigator/navmeshdb.cpp | 2 +- .../detournavigator/recastmeshbuilder.cpp | 2 +- apps/openmw_test_suite/esm/variant.cpp | 8 +- apps/openmw_test_suite/mwworld/test_store.cpp | 4 +- components/CMakeLists.txt | 15 ++-- components/config/gamesettings.cpp | 2 +- .../contentselector/model/contentmodel.cpp | 2 +- components/contentselector/model/esmfile.cpp | 2 +- components/detournavigator/navigatorimpl.cpp | 2 +- components/esm/luascripts.cpp | 4 +- components/esm/records.hpp | 84 +++++++++---------- components/{esm => esm3}/activespells.cpp | 0 components/{esm => esm3}/activespells.hpp | 2 +- components/{esm => esm3}/aipackage.cpp | 0 components/{esm => esm3}/aipackage.hpp | 2 +- components/{esm => esm3}/aisequence.cpp | 0 components/{esm => esm3}/aisequence.hpp | 4 +- components/{esm => esm3}/animationstate.cpp | 0 components/{esm => esm3}/animationstate.hpp | 0 components/{esm => esm3}/cellid.cpp | 0 components/{esm => esm3}/cellid.hpp | 0 components/{esm => esm3}/cellref.cpp | 0 components/{esm => esm3}/cellref.hpp | 2 +- components/{esm => esm3}/cellstate.cpp | 0 components/{esm => esm3}/cellstate.hpp | 2 +- components/{esm => esm3}/containerstate.cpp | 0 components/{esm => esm3}/containerstate.hpp | 0 components/{esm => esm3}/controlsstate.cpp | 0 components/{esm => esm3}/controlsstate.hpp | 0 .../{esm => esm3}/creaturelevliststate.cpp | 0 .../{esm => esm3}/creaturelevliststate.hpp | 0 components/{esm => esm3}/creaturestate.cpp | 0 components/{esm => esm3}/creaturestate.hpp | 0 components/{esm => esm3}/creaturestats.cpp | 0 components/{esm => esm3}/creaturestats.hpp | 4 +- .../{esm => esm3}/custommarkerstate.cpp | 0 .../{esm => esm3}/custommarkerstate.hpp | 0 components/{esm => esm3}/debugprofile.cpp | 2 +- components/{esm => esm3}/debugprofile.hpp | 0 components/{esm => esm3}/dialoguestate.cpp | 0 components/{esm => esm3}/dialoguestate.hpp | 0 components/{esm => esm3}/doorstate.cpp | 0 components/{esm => esm3}/doorstate.hpp | 0 components/{esm => esm3}/effectlist.cpp | 0 components/{esm => esm3}/effectlist.hpp | 0 components/{esm => esm3}/esmreader.cpp | 0 components/{esm => esm3}/esmreader.hpp | 2 +- components/{esm => esm3}/esmwriter.cpp | 0 components/{esm => esm3}/esmwriter.hpp | 2 +- components/{esm => esm3}/filter.cpp | 2 +- components/{esm => esm3}/filter.hpp | 0 components/{esm => esm3}/fogstate.cpp | 0 components/{esm => esm3}/fogstate.hpp | 0 components/{esm => esm3}/globalmap.cpp | 2 +- components/{esm => esm3}/globalmap.hpp | 0 components/{esm => esm3}/globalscript.cpp | 0 components/{esm => esm3}/globalscript.hpp | 0 components/{esm => esm3}/inventorystate.cpp | 0 components/{esm => esm3}/inventorystate.hpp | 0 components/{esm => esm3}/journalentry.cpp | 0 components/{esm => esm3}/journalentry.hpp | 0 components/{esm => esm3}/loadacti.cpp | 2 +- components/{esm => esm3}/loadacti.hpp | 0 components/{esm => esm3}/loadalch.cpp | 2 +- components/{esm => esm3}/loadalch.hpp | 0 components/{esm => esm3}/loadappa.cpp | 2 +- components/{esm => esm3}/loadappa.hpp | 0 components/{esm => esm3}/loadarmo.cpp | 2 +- components/{esm => esm3}/loadarmo.hpp | 0 components/{esm => esm3}/loadbody.cpp | 2 +- components/{esm => esm3}/loadbody.hpp | 0 components/{esm => esm3}/loadbook.cpp | 2 +- components/{esm => esm3}/loadbook.hpp | 0 components/{esm => esm3}/loadbsgn.cpp | 2 +- components/{esm => esm3}/loadbsgn.hpp | 0 components/{esm => esm3}/loadcell.cpp | 2 +- components/{esm => esm3}/loadcell.hpp | 4 +- components/{esm => esm3}/loadclas.cpp | 2 +- components/{esm => esm3}/loadclas.hpp | 0 components/{esm => esm3}/loadclot.cpp | 2 +- components/{esm => esm3}/loadclot.hpp | 0 components/{esm => esm3}/loadcont.cpp | 2 +- components/{esm => esm3}/loadcont.hpp | 2 +- components/{esm => esm3}/loadcrea.cpp | 2 +- components/{esm => esm3}/loadcrea.hpp | 0 components/{esm => esm3}/loaddial.cpp | 2 +- components/{esm => esm3}/loaddial.hpp | 0 components/{esm => esm3}/loaddoor.cpp | 2 +- components/{esm => esm3}/loaddoor.hpp | 0 components/{esm => esm3}/loadench.cpp | 2 +- components/{esm => esm3}/loadench.hpp | 0 components/{esm => esm3}/loadfact.cpp | 2 +- components/{esm => esm3}/loadfact.hpp | 0 components/{esm => esm3}/loadglob.cpp | 2 +- components/{esm => esm3}/loadglob.hpp | 0 components/{esm => esm3}/loadgmst.cpp | 2 +- components/{esm => esm3}/loadgmst.hpp | 0 components/{esm => esm3}/loadinfo.cpp | 2 +- components/{esm => esm3}/loadinfo.hpp | 2 +- components/{esm => esm3}/loadingr.cpp | 2 +- components/{esm => esm3}/loadingr.hpp | 0 components/{esm => esm3}/loadland.cpp | 2 +- components/{esm => esm3}/loadland.hpp | 2 +- components/{esm => esm3}/loadlevlist.cpp | 2 +- components/{esm => esm3}/loadlevlist.hpp | 0 components/{esm => esm3}/loadligh.cpp | 2 +- components/{esm => esm3}/loadligh.hpp | 0 components/{esm => esm3}/loadlock.cpp | 2 +- components/{esm => esm3}/loadlock.hpp | 0 components/{esm => esm3}/loadltex.cpp | 2 +- components/{esm => esm3}/loadltex.hpp | 0 components/{esm => esm3}/loadmgef.cpp | 2 +- components/{esm => esm3}/loadmgef.hpp | 0 components/{esm => esm3}/loadmisc.cpp | 2 +- components/{esm => esm3}/loadmisc.hpp | 0 components/{esm => esm3}/loadnpc.cpp | 2 +- components/{esm => esm3}/loadnpc.hpp | 2 +- components/{esm => esm3}/loadpgrd.cpp | 2 +- components/{esm => esm3}/loadpgrd.hpp | 0 components/{esm => esm3}/loadprob.cpp | 2 +- components/{esm => esm3}/loadprob.hpp | 0 components/{esm => esm3}/loadrace.cpp | 2 +- components/{esm => esm3}/loadrace.hpp | 0 components/{esm => esm3}/loadregn.cpp | 2 +- components/{esm => esm3}/loadregn.hpp | 2 +- components/{esm => esm3}/loadrepa.cpp | 2 +- components/{esm => esm3}/loadrepa.hpp | 0 components/{esm => esm3}/loadscpt.cpp | 2 +- components/{esm => esm3}/loadscpt.hpp | 2 +- components/{esm => esm3}/loadskil.cpp | 2 +- components/{esm => esm3}/loadskil.hpp | 2 +- components/{esm => esm3}/loadsndg.cpp | 2 +- components/{esm => esm3}/loadsndg.hpp | 0 components/{esm => esm3}/loadsoun.cpp | 2 +- components/{esm => esm3}/loadsoun.hpp | 0 components/{esm => esm3}/loadspel.cpp | 2 +- components/{esm => esm3}/loadspel.hpp | 0 components/{esm => esm3}/loadsscr.cpp | 2 +- components/{esm => esm3}/loadsscr.hpp | 0 components/{esm => esm3}/loadstat.cpp | 2 +- components/{esm => esm3}/loadstat.hpp | 0 components/{esm => esm3}/loadtes3.cpp | 4 +- components/{esm => esm3}/loadtes3.hpp | 2 +- components/{esm => esm3}/loadweap.cpp | 2 +- components/{esm => esm3}/loadweap.hpp | 0 components/{esm => esm3}/locals.cpp | 0 components/{esm => esm3}/locals.hpp | 0 components/{esm => esm3}/magiceffects.cpp | 0 components/{esm => esm3}/magiceffects.hpp | 0 components/{esm => esm3}/mappings.cpp | 0 components/{esm => esm3}/mappings.hpp | 4 +- components/{esm => esm3}/npcstate.cpp | 0 components/{esm => esm3}/npcstate.hpp | 0 components/{esm => esm3}/npcstats.cpp | 0 components/{esm => esm3}/npcstats.hpp | 0 components/{esm => esm3}/objectstate.cpp | 0 components/{esm => esm3}/objectstate.hpp | 2 +- components/{esm => esm3}/player.cpp | 0 components/{esm => esm3}/player.hpp | 4 +- components/{esm => esm3}/projectilestate.cpp | 0 components/{esm => esm3}/projectilestate.hpp | 2 +- components/{esm => esm3}/queststate.cpp | 0 components/{esm => esm3}/queststate.hpp | 0 components/{esm => esm3}/quickkeys.cpp | 0 components/{esm => esm3}/quickkeys.hpp | 0 components/{esm => esm3}/savedgame.cpp | 0 components/{esm => esm3}/savedgame.hpp | 2 +- components/{esm => esm3}/spelllist.cpp | 0 components/{esm => esm3}/spelllist.hpp | 0 components/{esm => esm3}/spellstate.cpp | 0 components/{esm => esm3}/spellstate.hpp | 2 +- components/{esm => esm3}/statstate.cpp | 0 components/{esm => esm3}/statstate.hpp | 0 components/{esm => esm3}/stolenitems.cpp | 4 +- components/{esm => esm3}/stolenitems.hpp | 0 components/{esm => esm3}/transport.cpp | 4 +- components/{esm => esm3}/transport.hpp | 2 +- components/{esm => esm3}/variant.cpp | 2 +- components/{esm => esm3}/variant.hpp | 0 components/{esm => esm3}/variantimp.cpp | 0 components/{esm => esm3}/variantimp.hpp | 0 components/{esm => esm3}/weatherstate.cpp | 0 components/{esm => esm3}/weatherstate.hpp | 0 components/esmloader/esmdata.cpp | 14 ++-- components/esmloader/load.cpp | 18 ++-- components/esmloader/load.hpp | 2 +- components/esmloader/record.hpp | 2 +- components/esmterrain/storage.hpp | 4 +- components/lua/configuration.hpp | 2 +- components/misc/convert.hpp | 2 +- components/misc/coordinateconverter.hpp | 6 +- components/sceneutil/lightutil.cpp | 2 +- components/sceneutil/pathgridutil.cpp | 2 +- components/terrain/cellborder.cpp | 2 +- 448 files changed, 687 insertions(+), 684 deletions(-) rename components/{esm => esm3}/activespells.cpp (100%) rename components/{esm => esm3}/activespells.hpp (97%) rename components/{esm => esm3}/aipackage.cpp (100%) rename components/{esm => esm3}/aipackage.hpp (98%) rename components/{esm => esm3}/aisequence.cpp (100%) rename components/{esm => esm3}/aisequence.hpp (98%) rename components/{esm => esm3}/animationstate.cpp (100%) rename components/{esm => esm3}/animationstate.hpp (100%) rename components/{esm => esm3}/cellid.cpp (100%) rename components/{esm => esm3}/cellid.hpp (100%) rename components/{esm => esm3}/cellref.cpp (100%) rename components/{esm => esm3}/cellref.hpp (99%) rename components/{esm => esm3}/cellstate.cpp (100%) rename components/{esm => esm3}/cellstate.hpp (93%) rename components/{esm => esm3}/containerstate.cpp (100%) rename components/{esm => esm3}/containerstate.hpp (100%) rename components/{esm => esm3}/controlsstate.cpp (100%) rename components/{esm => esm3}/controlsstate.hpp (100%) rename components/{esm => esm3}/creaturelevliststate.cpp (100%) rename components/{esm => esm3}/creaturelevliststate.hpp (100%) rename components/{esm => esm3}/creaturestate.cpp (100%) rename components/{esm => esm3}/creaturestate.hpp (100%) rename components/{esm => esm3}/creaturestats.cpp (100%) rename components/{esm => esm3}/creaturestats.hpp (97%) rename components/{esm => esm3}/custommarkerstate.cpp (100%) rename components/{esm => esm3}/custommarkerstate.hpp (100%) rename components/{esm => esm3}/debugprofile.cpp (97%) rename components/{esm => esm3}/debugprofile.hpp (100%) rename components/{esm => esm3}/dialoguestate.cpp (100%) rename components/{esm => esm3}/dialoguestate.hpp (100%) rename components/{esm => esm3}/doorstate.cpp (100%) rename components/{esm => esm3}/doorstate.hpp (100%) rename components/{esm => esm3}/effectlist.cpp (100%) rename components/{esm => esm3}/effectlist.hpp (100%) rename components/{esm => esm3}/esmreader.cpp (100%) rename components/{esm => esm3}/esmreader.hpp (99%) rename components/{esm => esm3}/esmwriter.cpp (100%) rename components/{esm => esm3}/esmwriter.hpp (99%) rename components/{esm => esm3}/filter.cpp (97%) rename components/{esm => esm3}/filter.hpp (100%) rename components/{esm => esm3}/fogstate.cpp (100%) rename components/{esm => esm3}/fogstate.hpp (100%) rename components/{esm => esm3}/globalmap.cpp (96%) rename components/{esm => esm3}/globalmap.hpp (100%) rename components/{esm => esm3}/globalscript.cpp (100%) rename components/{esm => esm3}/globalscript.hpp (100%) rename components/{esm => esm3}/inventorystate.cpp (100%) rename components/{esm => esm3}/inventorystate.hpp (100%) rename components/{esm => esm3}/journalentry.cpp (100%) rename components/{esm => esm3}/journalentry.hpp (100%) rename components/{esm => esm3}/loadacti.cpp (98%) rename components/{esm => esm3}/loadacti.hpp (100%) rename components/{esm => esm3}/loadalch.cpp (98%) rename components/{esm => esm3}/loadalch.hpp (100%) rename components/{esm => esm3}/loadappa.cpp (98%) rename components/{esm => esm3}/loadappa.hpp (100%) rename components/{esm => esm3}/loadarmo.cpp (99%) rename components/{esm => esm3}/loadarmo.hpp (100%) rename components/{esm => esm3}/loadbody.cpp (98%) rename components/{esm => esm3}/loadbody.hpp (100%) rename components/{esm => esm3}/loadbook.cpp (98%) rename components/{esm => esm3}/loadbook.hpp (100%) rename components/{esm => esm3}/loadbsgn.cpp (98%) rename components/{esm => esm3}/loadbsgn.hpp (100%) rename components/{esm => esm3}/loadcell.cpp (99%) rename components/{esm => esm3}/loadcell.hpp (98%) rename components/{esm => esm3}/loadclas.cpp (98%) rename components/{esm => esm3}/loadclas.hpp (100%) rename components/{esm => esm3}/loadclot.cpp (98%) rename components/{esm => esm3}/loadclot.hpp (100%) rename components/{esm => esm3}/loadcont.cpp (99%) rename components/{esm => esm3}/loadcont.hpp (96%) rename components/{esm => esm3}/loadcrea.cpp (99%) rename components/{esm => esm3}/loadcrea.hpp (100%) rename components/{esm => esm3}/loaddial.cpp (99%) rename components/{esm => esm3}/loaddial.hpp (100%) rename components/{esm => esm3}/loaddoor.cpp (98%) rename components/{esm => esm3}/loaddoor.hpp (100%) rename components/{esm => esm3}/loadench.cpp (98%) rename components/{esm => esm3}/loadench.hpp (100%) rename components/{esm => esm3}/loadfact.cpp (99%) rename components/{esm => esm3}/loadfact.hpp (100%) rename components/{esm => esm3}/loadglob.cpp (96%) rename components/{esm => esm3}/loadglob.hpp (100%) rename components/{esm => esm3}/loadgmst.cpp (96%) rename components/{esm => esm3}/loadgmst.hpp (100%) rename components/{esm => esm3}/loadinfo.cpp (99%) rename components/{esm => esm3}/loadinfo.hpp (98%) rename components/{esm => esm3}/loadingr.cpp (98%) rename components/{esm => esm3}/loadingr.hpp (100%) rename components/{esm => esm3}/loadland.cpp (99%) rename components/{esm => esm3}/loadland.hpp (99%) rename components/{esm => esm3}/loadlevlist.cpp (99%) rename components/{esm => esm3}/loadlevlist.hpp (100%) rename components/{esm => esm3}/loadligh.cpp (98%) rename components/{esm => esm3}/loadligh.hpp (100%) rename components/{esm => esm3}/loadlock.cpp (98%) rename components/{esm => esm3}/loadlock.hpp (100%) rename components/{esm => esm3}/loadltex.cpp (97%) rename components/{esm => esm3}/loadltex.hpp (100%) rename components/{esm => esm3}/loadmgef.cpp (99%) rename components/{esm => esm3}/loadmgef.hpp (100%) rename components/{esm => esm3}/loadmisc.cpp (98%) rename components/{esm => esm3}/loadmisc.hpp (100%) rename components/{esm => esm3}/loadnpc.cpp (99%) rename components/{esm => esm3}/loadnpc.hpp (98%) rename components/{esm => esm3}/loadpgrd.cpp (99%) rename components/{esm => esm3}/loadpgrd.hpp (100%) rename components/{esm => esm3}/loadprob.cpp (98%) rename components/{esm => esm3}/loadprob.hpp (100%) rename components/{esm => esm3}/loadrace.cpp (98%) rename components/{esm => esm3}/loadrace.hpp (100%) rename components/{esm => esm3}/loadregn.cpp (99%) rename components/{esm => esm3}/loadregn.hpp (97%) rename components/{esm => esm3}/loadrepa.cpp (98%) rename components/{esm => esm3}/loadrepa.hpp (100%) rename components/{esm => esm3}/loadscpt.cpp (99%) rename components/{esm => esm3}/loadscpt.hpp (97%) rename components/{esm => esm3}/loadskil.cpp (99%) rename components/{esm => esm3}/loadskil.hpp (98%) rename components/{esm => esm3}/loadsndg.cpp (98%) rename components/{esm => esm3}/loadsndg.hpp (100%) rename components/{esm => esm3}/loadsoun.cpp (98%) rename components/{esm => esm3}/loadsoun.hpp (100%) rename components/{esm => esm3}/loadspel.cpp (98%) rename components/{esm => esm3}/loadspel.hpp (100%) rename components/{esm => esm3}/loadsscr.cpp (97%) rename components/{esm => esm3}/loadsscr.hpp (100%) rename components/{esm => esm3}/loadstat.cpp (97%) rename components/{esm => esm3}/loadstat.hpp (100%) rename components/{esm => esm3}/loadtes3.cpp (96%) rename components/{esm => esm3}/loadtes3.hpp (97%) rename components/{esm => esm3}/loadweap.cpp (98%) rename components/{esm => esm3}/loadweap.hpp (100%) rename components/{esm => esm3}/locals.cpp (100%) rename components/{esm => esm3}/locals.hpp (100%) rename components/{esm => esm3}/magiceffects.cpp (100%) rename components/{esm => esm3}/magiceffects.hpp (100%) rename components/{esm => esm3}/mappings.cpp (100%) rename components/{esm => esm3}/mappings.hpp (78%) rename components/{esm => esm3}/npcstate.cpp (100%) rename components/{esm => esm3}/npcstate.hpp (100%) rename components/{esm => esm3}/npcstats.cpp (100%) rename components/{esm => esm3}/npcstats.hpp (100%) rename components/{esm => esm3}/objectstate.cpp (100%) rename components/{esm => esm3}/objectstate.hpp (97%) rename components/{esm => esm3}/player.cpp (100%) rename components/{esm => esm3}/player.hpp (93%) rename components/{esm => esm3}/projectilestate.cpp (100%) rename components/{esm => esm3}/projectilestate.hpp (96%) rename components/{esm => esm3}/queststate.cpp (100%) rename components/{esm => esm3}/queststate.hpp (100%) rename components/{esm => esm3}/quickkeys.cpp (100%) rename components/{esm => esm3}/quickkeys.hpp (100%) rename components/{esm => esm3}/savedgame.cpp (100%) rename components/{esm => esm3}/savedgame.hpp (96%) rename components/{esm => esm3}/spelllist.cpp (100%) rename components/{esm => esm3}/spelllist.hpp (100%) rename components/{esm => esm3}/spellstate.cpp (100%) rename components/{esm => esm3}/spellstate.hpp (97%) rename components/{esm => esm3}/statstate.cpp (100%) rename components/{esm => esm3}/statstate.hpp (100%) rename components/{esm => esm3}/stolenitems.cpp (94%) rename components/{esm => esm3}/stolenitems.hpp (100%) rename components/{esm => esm3}/transport.cpp (92%) rename components/{esm => esm3}/transport.hpp (94%) rename components/{esm => esm3}/variant.cpp (99%) rename components/{esm => esm3}/variant.hpp (100%) rename components/{esm => esm3}/variantimp.cpp (100%) rename components/{esm => esm3}/variantimp.hpp (100%) rename components/{esm => esm3}/weatherstate.cpp (100%) rename components/{esm => esm3}/weatherstate.hpp (100%) diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index c27fdc4289..e8a4be1e46 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a0ee654da9..60509d7c87 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -10,8 +10,8 @@ #include -#include -#include +#include +#include #include #include "record.hpp" diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 24e3605eb2..d6cae207c9 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -1,17 +1,17 @@ #include "labels.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/apps/essimporter/convertacdt.hpp b/apps/essimporter/convertacdt.hpp index 4059dd1af8..00e90ababf 100644 --- a/apps/essimporter/convertacdt.hpp +++ b/apps/essimporter/convertacdt.hpp @@ -1,10 +1,10 @@ #ifndef OPENMW_ESSIMPORT_CONVERTACDT_H #define OPENMW_ESSIMPORT_CONVERTACDT_H -#include -#include -#include -#include +#include +#include +#include +#include #include "importacdt.hpp" diff --git a/apps/essimporter/convertcntc.hpp b/apps/essimporter/convertcntc.hpp index c299d87a1e..2dc51949b1 100644 --- a/apps/essimporter/convertcntc.hpp +++ b/apps/essimporter/convertcntc.hpp @@ -3,7 +3,7 @@ #include "importcntc.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/convertcrec.hpp b/apps/essimporter/convertcrec.hpp index 7d317f03e8..fa2e7e807f 100644 --- a/apps/essimporter/convertcrec.hpp +++ b/apps/essimporter/convertcrec.hpp @@ -3,7 +3,7 @@ #include "importcrec.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 6e79e27f18..ad28a9295f 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -5,8 +5,8 @@ #include -#include -#include +#include +#include #include diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 81b9711bbf..8b71775759 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -6,23 +6,23 @@ #include #include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "importcrec.hpp" #include "importcntc.hpp" diff --git a/apps/essimporter/convertinventory.hpp b/apps/essimporter/convertinventory.hpp index 8abe85a44a..95d134d4fc 100644 --- a/apps/essimporter/convertinventory.hpp +++ b/apps/essimporter/convertinventory.hpp @@ -3,7 +3,7 @@ #include "importinventory.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/convertnpcc.hpp b/apps/essimporter/convertnpcc.hpp index eb12d8f3bc..d0a395e33e 100644 --- a/apps/essimporter/convertnpcc.hpp +++ b/apps/essimporter/convertnpcc.hpp @@ -3,7 +3,7 @@ #include "importnpcc.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/convertplayer.hpp b/apps/essimporter/convertplayer.hpp index 1d2fdc87a6..73c98309f8 100644 --- a/apps/essimporter/convertplayer.hpp +++ b/apps/essimporter/convertplayer.hpp @@ -3,8 +3,8 @@ #include "importplayer.hpp" -#include -#include +#include +#include namespace ESSImport { diff --git a/apps/essimporter/convertscpt.hpp b/apps/essimporter/convertscpt.hpp index 3390bd6070..f4a4e34fe4 100644 --- a/apps/essimporter/convertscpt.hpp +++ b/apps/essimporter/convertscpt.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESSIMPORT_CONVERTSCPT_H #define OPENMW_ESSIMPORT_CONVERTSCPT_H -#include +#include #include "importscpt.hpp" diff --git a/apps/essimporter/convertscri.hpp b/apps/essimporter/convertscri.hpp index 2d89456662..3908dbacf2 100644 --- a/apps/essimporter/convertscri.hpp +++ b/apps/essimporter/convertscri.hpp @@ -3,7 +3,7 @@ #include "importscri.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index 0ddd2eb64c..69760a6bdb 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -1,8 +1,8 @@ #include "importacdt.hpp" -#include +#include -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index 354eca32d8..6ecb72e334 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "importscri.hpp" diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index 442a7781c7..e6c62cc812 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -1,6 +1,6 @@ #include "importcellref.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importcellref.hpp b/apps/essimporter/importcellref.hpp index 4d7f07a6cc..d69a0a829d 100644 --- a/apps/essimporter/importcellref.hpp +++ b/apps/essimporter/importcellref.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "importacdt.hpp" diff --git a/apps/essimporter/importcntc.cpp b/apps/essimporter/importcntc.cpp index a492aef5aa..a4b54ca7b4 100644 --- a/apps/essimporter/importcntc.cpp +++ b/apps/essimporter/importcntc.cpp @@ -1,6 +1,6 @@ #include "importcntc.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importcrec.cpp b/apps/essimporter/importcrec.cpp index 64879f2afc..6cef13333b 100644 --- a/apps/essimporter/importcrec.cpp +++ b/apps/essimporter/importcrec.cpp @@ -1,6 +1,6 @@ #include "importcrec.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importcrec.hpp b/apps/essimporter/importcrec.hpp index 5110fbc689..77933eafe8 100644 --- a/apps/essimporter/importcrec.hpp +++ b/apps/essimporter/importcrec.hpp @@ -2,7 +2,7 @@ #define OPENMW_ESSIMPORT_CREC_H #include "importinventory.hpp" -#include +#include namespace ESM { diff --git a/apps/essimporter/importdial.cpp b/apps/essimporter/importdial.cpp index 5797a708a1..1467e43365 100644 --- a/apps/essimporter/importdial.cpp +++ b/apps/essimporter/importdial.cpp @@ -1,6 +1,6 @@ #include "importdial.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 84f13b8c91..996a24b011 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -8,20 +8,20 @@ #include #include -#include -#include +#include +#include #include -#include -#include +#include +#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 179e00f087..149059219c 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -3,13 +3,13 @@ #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "importnpcc.hpp" #include "importcrec.hpp" diff --git a/apps/essimporter/importgame.cpp b/apps/essimporter/importgame.cpp index 1012541b49..df36afe784 100644 --- a/apps/essimporter/importgame.cpp +++ b/apps/essimporter/importgame.cpp @@ -1,6 +1,6 @@ #include "importgame.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importinfo.cpp b/apps/essimporter/importinfo.cpp index 1131553709..49a0c745f6 100644 --- a/apps/essimporter/importinfo.cpp +++ b/apps/essimporter/importinfo.cpp @@ -1,6 +1,6 @@ #include "importinfo.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index e91c39452c..c4d11763f5 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -2,7 +2,7 @@ #include -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importinventory.hpp b/apps/essimporter/importinventory.hpp index a1324a6960..6f757ac9a0 100644 --- a/apps/essimporter/importinventory.hpp +++ b/apps/essimporter/importinventory.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include "importscri.hpp" diff --git a/apps/essimporter/importjour.cpp b/apps/essimporter/importjour.cpp index e5d24e113c..1c46b3159c 100644 --- a/apps/essimporter/importjour.cpp +++ b/apps/essimporter/importjour.cpp @@ -1,6 +1,6 @@ #include "importjour.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importklst.cpp b/apps/essimporter/importklst.cpp index daa1ab0774..5d9f22a31c 100644 --- a/apps/essimporter/importklst.cpp +++ b/apps/essimporter/importklst.cpp @@ -1,6 +1,6 @@ #include "importklst.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importnpcc.cpp b/apps/essimporter/importnpcc.cpp index 3cbd749ce8..4d8da66f0f 100644 --- a/apps/essimporter/importnpcc.cpp +++ b/apps/essimporter/importnpcc.cpp @@ -1,6 +1,6 @@ #include "importnpcc.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp index a23ab1e50b..d525c00743 100644 --- a/apps/essimporter/importnpcc.hpp +++ b/apps/essimporter/importnpcc.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_ESSIMPORT_NPCC_H #define OPENMW_ESSIMPORT_NPCC_H -#include +#include -#include +#include #include "importinventory.hpp" diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index 8c275a2868..52e4a9b7d0 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -1,6 +1,6 @@ #include "importplayer.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp index 924522383b..6c8f211c57 100644 --- a/apps/essimporter/importplayer.hpp +++ b/apps/essimporter/importplayer.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include "importacdt.hpp" diff --git a/apps/essimporter/importproj.cpp b/apps/essimporter/importproj.cpp index b2dcf4e7da..aada41a778 100644 --- a/apps/essimporter/importproj.cpp +++ b/apps/essimporter/importproj.cpp @@ -1,6 +1,6 @@ #include "importproj.h" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importques.cpp b/apps/essimporter/importques.cpp index 78b779e439..b57083b0b3 100644 --- a/apps/essimporter/importques.cpp +++ b/apps/essimporter/importques.cpp @@ -1,6 +1,6 @@ #include "importques.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importscpt.cpp b/apps/essimporter/importscpt.cpp index 652383cdaa..4df46ddc8d 100644 --- a/apps/essimporter/importscpt.cpp +++ b/apps/essimporter/importscpt.cpp @@ -1,6 +1,6 @@ #include "importscpt.hpp" -#include +#include diff --git a/apps/essimporter/importscpt.hpp b/apps/essimporter/importscpt.hpp index 6bfd2603a2..15f4fde598 100644 --- a/apps/essimporter/importscpt.hpp +++ b/apps/essimporter/importscpt.hpp @@ -3,7 +3,7 @@ #include "importscri.hpp" -#include +#include namespace ESM { diff --git a/apps/essimporter/importscri.cpp b/apps/essimporter/importscri.cpp index de0b35c86c..91971dde3b 100644 --- a/apps/essimporter/importscri.cpp +++ b/apps/essimporter/importscri.cpp @@ -1,6 +1,6 @@ #include "importscri.hpp" -#include +#include namespace ESSImport { diff --git a/apps/essimporter/importscri.hpp b/apps/essimporter/importscri.hpp index fe68e50515..73d8942f81 100644 --- a/apps/essimporter/importscri.hpp +++ b/apps/essimporter/importscri.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESSIMPORT_IMPORTSCRI_H #define OPENMW_ESSIMPORT_IMPORTSCRI_H -#include +#include #include diff --git a/apps/essimporter/importsplm.cpp b/apps/essimporter/importsplm.cpp index 9fdba4ddb5..f635a8fdbb 100644 --- a/apps/essimporter/importsplm.cpp +++ b/apps/essimporter/importsplm.cpp @@ -1,6 +1,6 @@ #include "importsplm.h" -#include +#include namespace ESSImport { diff --git a/apps/launcher/utils/cellnameloader.cpp b/apps/launcher/utils/cellnameloader.cpp index e8ba54651c..4cb8b545aa 100644 --- a/apps/launcher/utils/cellnameloader.cpp +++ b/apps/launcher/utils/cellnameloader.cpp @@ -1,6 +1,6 @@ #include "cellnameloader.hpp" -#include +#include #include QSet CellNameLoader::getCellNames(QStringList &contentPaths) diff --git a/apps/launcher/utils/cellnameloader.hpp b/apps/launcher/utils/cellnameloader.hpp index 899ff75adb..6143b78bd9 100644 --- a/apps/launcher/utils/cellnameloader.hpp +++ b/apps/launcher/utils/cellnameloader.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace ESM {class ESMReader; struct Cell;} namespace ContentSelectorView {class ContentSelector;} diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 35a1c4ec8b..68f266ed6e 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 3e867fcbc9..aee040bb9d 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -4,8 +4,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 4187197947..d59d274c21 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index d05a8fe3f0..227f73ad33 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -7,10 +7,10 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/apps/navmeshtool/worldspacedata.hpp b/apps/navmeshtool/worldspacedata.hpp index 3dccd5a8bc..2efb0954e0 100644 --- a/apps/navmeshtool/worldspacedata.hpp +++ b/apps/navmeshtool/worldspacedata.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include diff --git a/apps/opencs/model/doc/runner.hpp b/apps/opencs/model/doc/runner.hpp index 517122492a..0cfbaab3af 100644 --- a/apps/opencs/model/doc/runner.hpp +++ b/apps/opencs/model/doc/runner.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include class QTemporaryFile; diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index a410d34b2a..5efa474f76 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include "../world/infocollection.hpp" #include "../world/cellcoordinates.hpp" diff --git a/apps/opencs/model/doc/savingstate.hpp b/apps/opencs/model/doc/savingstate.hpp index e6c8c545a7..727352a872 100644 --- a/apps/opencs/model/doc/savingstate.hpp +++ b/apps/opencs/model/doc/savingstate.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include diff --git a/apps/opencs/model/tools/birthsigncheck.hpp b/apps/opencs/model/tools/birthsigncheck.hpp index 498894f882..1d88673adc 100644 --- a/apps/opencs/model/tools/birthsigncheck.hpp +++ b/apps/opencs/model/tools/birthsigncheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_BIRTHSIGNCHECK_H #define CSM_TOOLS_BIRTHSIGNCHECK_H -#include +#include #include "../world/idcollection.hpp" #include "../world/resources.hpp" diff --git a/apps/opencs/model/tools/bodypartcheck.hpp b/apps/opencs/model/tools/bodypartcheck.hpp index 2c379bd078..2eba75c495 100644 --- a/apps/opencs/model/tools/bodypartcheck.hpp +++ b/apps/opencs/model/tools/bodypartcheck.hpp @@ -1,8 +1,8 @@ #ifndef CSM_TOOLS_BODYPARTCHECK_H #define CSM_TOOLS_BODYPARTCHECK_H -#include -#include +#include +#include #include "../world/resources.hpp" #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index a82121597c..aa38ed46ce 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -2,8 +2,8 @@ #include -#include -#include +#include +#include #include "../prefs/state.hpp" diff --git a/apps/opencs/model/tools/classcheck.hpp b/apps/opencs/model/tools/classcheck.hpp index a78c2eb975..9d66336d43 100644 --- a/apps/opencs/model/tools/classcheck.hpp +++ b/apps/opencs/model/tools/classcheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_CLASSCHECK_H #define CSM_TOOLS_CLASSCHECK_H -#include +#include #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/enchantmentcheck.hpp b/apps/opencs/model/tools/enchantmentcheck.hpp index e9c8b9eece..8ee71ad7cb 100644 --- a/apps/opencs/model/tools/enchantmentcheck.hpp +++ b/apps/opencs/model/tools/enchantmentcheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_ENCHANTMENTCHECK_H #define CSM_TOOLS_ENCHANTMENTCHECK_H -#include +#include #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 8a198e9535..dbecea9589 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../prefs/state.hpp" diff --git a/apps/opencs/model/tools/factioncheck.hpp b/apps/opencs/model/tools/factioncheck.hpp index d281c1b416..a6a6815976 100644 --- a/apps/opencs/model/tools/factioncheck.hpp +++ b/apps/opencs/model/tools/factioncheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_FACTIONCHECK_H #define CSM_TOOLS_FACTIONCHECK_H -#include +#include #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/gmstcheck.hpp b/apps/opencs/model/tools/gmstcheck.hpp index 2c12a8607a..c57f6a088b 100644 --- a/apps/opencs/model/tools/gmstcheck.hpp +++ b/apps/opencs/model/tools/gmstcheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_GMSTCHECK_H #define CSM_TOOLS_GMSTCHECK_H -#include +#include #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/journalcheck.hpp b/apps/opencs/model/tools/journalcheck.hpp index b63127b522..65ce8b85df 100644 --- a/apps/opencs/model/tools/journalcheck.hpp +++ b/apps/opencs/model/tools/journalcheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_JOURNALCHECK_H #define CSM_TOOLS_JOURNALCHECK_H -#include +#include #include "../world/idcollection.hpp" #include "../world/infocollection.hpp" diff --git a/apps/opencs/model/tools/magiceffectcheck.hpp b/apps/opencs/model/tools/magiceffectcheck.hpp index 4b2c24cc7c..e264683d04 100644 --- a/apps/opencs/model/tools/magiceffectcheck.hpp +++ b/apps/opencs/model/tools/magiceffectcheck.hpp @@ -1,8 +1,8 @@ #ifndef CSM_TOOLS_MAGICEFFECTCHECK_HPP #define CSM_TOOLS_MAGICEFFECTCHECK_HPP -#include -#include +#include +#include #include "../world/idcollection.hpp" #include "../world/refidcollection.hpp" diff --git a/apps/opencs/model/tools/racecheck.hpp b/apps/opencs/model/tools/racecheck.hpp index 7c70f13b00..fe08f4bb67 100644 --- a/apps/opencs/model/tools/racecheck.hpp +++ b/apps/opencs/model/tools/racecheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_RACECHECK_H #define CSM_TOOLS_RACECHECK_H -#include +#include #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/regioncheck.hpp b/apps/opencs/model/tools/regioncheck.hpp index e7ddb0bcab..71893c6c5d 100644 --- a/apps/opencs/model/tools/regioncheck.hpp +++ b/apps/opencs/model/tools/regioncheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_REGIONCHECK_H #define CSM_TOOLS_REGIONCHECK_H -#include +#include #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/skillcheck.hpp b/apps/opencs/model/tools/skillcheck.hpp index b1af887f6c..f13a5b7a29 100644 --- a/apps/opencs/model/tools/skillcheck.hpp +++ b/apps/opencs/model/tools/skillcheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_SKILLCHECK_H #define CSM_TOOLS_SKILLCHECK_H -#include +#include #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/soundcheck.hpp b/apps/opencs/model/tools/soundcheck.hpp index 80eb9e7f29..fc3255c538 100644 --- a/apps/opencs/model/tools/soundcheck.hpp +++ b/apps/opencs/model/tools/soundcheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_SOUNDCHECK_H #define CSM_TOOLS_SOUNDCHECK_H -#include +#include #include "../world/resources.hpp" #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index dc9ce65c0a..6cec24b705 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include "../prefs/state.hpp" diff --git a/apps/opencs/model/tools/spellcheck.hpp b/apps/opencs/model/tools/spellcheck.hpp index bfc9628107..1a8d3d0237 100644 --- a/apps/opencs/model/tools/spellcheck.hpp +++ b/apps/opencs/model/tools/spellcheck.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_SPELLCHECK_H #define CSM_TOOLS_SPELLCHECK_H -#include +#include #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/tools/startscriptcheck.hpp b/apps/opencs/model/tools/startscriptcheck.hpp index a45d3c9437..4113090793 100644 --- a/apps/opencs/model/tools/startscriptcheck.hpp +++ b/apps/opencs/model/tools/startscriptcheck.hpp @@ -1,8 +1,8 @@ #ifndef CSM_TOOLS_STARTSCRIPTCHECK_H #define CSM_TOOLS_STARTSCRIPTCHECK_H -#include -#include +#include +#include #include "../doc/stage.hpp" diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp index b9dbdc1536..de3fa82ae3 100644 --- a/apps/opencs/model/tools/topicinfocheck.hpp +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -3,13 +3,13 @@ #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "../world/cell.hpp" #include "../world/idcollection.hpp" diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 7e7f926384..bc8f519dcd 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -1,10 +1,10 @@ #include "actoradapter.hpp" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include "data.hpp" diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 826e3b9179..2d8375bb2f 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -10,8 +10,8 @@ #include #include -#include -#include +#include +#include #include #include "refidcollection.hpp" diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index 160610874c..256a07d301 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index af8c26d70a..9fde26a962 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index e0148d6cc6..493f62694e 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -9,9 +9,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "columnbase.hpp" #include "columns.hpp" diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 8e53b3b270..3c5ea52586 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -405,7 +405,7 @@ namespace "Combat", "Magic", "Stealth", 0 }; - // see ESM::Attribute::AttributeID in + // see ESM::Attribute::AttributeID in static const char *sAttributes[] = { "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", @@ -498,7 +498,7 @@ namespace "Alteration", "Conjuration", "Destruction", "Illusion", "Mysticism", "Restoration", 0 }; - // impact from magic effects, see ESM::Skill::SkillEnum in + // impact from magic effects, see ESM::Skill::SkillEnum in static const char *sSkills[] = { "Block", "Armorer", "MediumArmor", "HeavyArmor", "BluntWeapon", @@ -509,13 +509,13 @@ namespace "Speechcraft", "HandToHand", 0 }; - // range of magic effects, see ESM::RangeType in + // range of magic effects, see ESM::RangeType in static const char *sEffectRange[] = { "Self", "Touch", "Target", 0 }; - // magic effect names, see ESM::MagicEffect::Effects in + // magic effect names, see ESM::MagicEffect::Effects in static const char *sEffectId[] = { "WaterBreathing", "SwiftSwim", "WaterWalking", "Shield", "FireShield", @@ -549,7 +549,7 @@ namespace "SummonBonewolf", "SummonCreature04", "SummonCreature05", 0 }; - // see ESM::PartReferenceType in + // see ESM::PartReferenceType in static const char *sPartRefType[] = { "Head", "Hair", "Neck", "Cuirass", "Groin", @@ -560,7 +560,7 @@ namespace "Weapon", "Tail", 0 }; - // see the enums in + // see the enums in static const char *sAiPackageType[] = { "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ec4c1f6a13..2018b3e8fd 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -5,10 +5,10 @@ #include -#include +#include #include -#include -#include +#include +#include #include #include diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index b1c20b8629..4a5d8f51a6 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -9,25 +9,25 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index bbc49f18c1..9261d287f5 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -1,7 +1,7 @@ #ifndef CSM_WOLRD_IDCOLLECTION_H #define CSM_WOLRD_IDCOLLECTION_H -#include +#include #include "collection.hpp" #include "land.hpp" diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 215f42133d..2e2ea41761 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include "collectionbase.hpp" diff --git a/apps/opencs/model/world/info.hpp b/apps/opencs/model/world/info.hpp index 1bcb2dc2d0..9405c002c7 100644 --- a/apps/opencs/model/world/info.hpp +++ b/apps/opencs/model/world/info.hpp @@ -1,7 +1,7 @@ #ifndef CSM_WOLRD_INFO_H #define CSM_WOLRD_INFO_H -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index d58a8327f2..a7cdc28d28 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -4,8 +4,8 @@ #include #include -#include -#include +#include +#include #include diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index ce26a46dc7..7c7a839fa6 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -1,7 +1,7 @@ #ifndef CSM_WORLD_INFOSELECTWRAPPER_H #define CSM_WORLD_INFOSELECTWRAPPER_H -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index e604f13119..99da5cfac0 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 43deb64a47..c8ac8369ed 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index a7376438c1..601d4b79c9 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/metadata.cpp b/apps/opencs/model/world/metadata.cpp index b2fa3487cd..acee441883 100644 --- a/apps/opencs/model/world/metadata.cpp +++ b/apps/opencs/model/world/metadata.cpp @@ -1,8 +1,8 @@ #include "metadata.hpp" -#include -#include -#include +#include +#include +#include void CSMWorld::MetaData::blank() { diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index e8b4102d7c..08a23f4410 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -1,7 +1,7 @@ #include "nestedcoladapterimp.hpp" -#include -#include +#include +#include #include "idcollection.hpp" #include "pathgrid.hpp" diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 54780d290e..a5daefc3cd 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -3,12 +3,12 @@ #include -#include -#include -#include // for converting magic effect id to string & back -#include // for converting skill names +#include +#include +#include // for converting magic effect id to string & back +#include // for converting skill names #include // for converting attributes -#include +#include #include "nestedcolumnadapter.hpp" #include "nestedtablewrapper.hpp" diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index ce74d419e4..712b3969d6 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index 23b4ad1b5c..1eefe79f67 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index c6f5148290..76a3b7311a 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,6 +1,6 @@ #include "refcollection.hpp" -#include +#include #include "ref.hpp" #include "cell.hpp" diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 0dfc9945f7..d2c2a46db7 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -4,8 +4,8 @@ #include #include -#include -#include +#include +#include #include "nestedtablewrapper.hpp" diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 153d1bde91..55668d16d8 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -5,11 +5,11 @@ #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "columnbase.hpp" #include "record.hpp" diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 928c7284ad..2e0c7e2028 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "refidadapter.hpp" #include "refidadapterimp.hpp" diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index b9dee80638..ff2232dffe 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -7,26 +7,26 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index 94b82c96cd..d33c6f3f28 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include #include diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 8172e6fff7..414cd438d7 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -8,7 +8,7 @@ #include -#include +#include #include #include "../../model/world/actoradapter.hpp" diff --git a/apps/opencs/view/render/brushdraw.hpp b/apps/opencs/view/render/brushdraw.hpp index 0551631cd9..f95a0c5a7c 100644 --- a/apps/opencs/view/render/brushdraw.hpp +++ b/apps/opencs/view/render/brushdraw.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../widget/brushshapes.hpp" namespace CSVRender diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 2502dc1fd0..04e52bcd1f 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -8,8 +8,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/apps/opencs/view/render/cellborder.cpp b/apps/opencs/view/render/cellborder.cpp index d8ff638010..b93b5d1fcf 100644 --- a/apps/opencs/view/render/cellborder.cpp +++ b/apps/opencs/view/render/cellborder.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "mask.hpp" diff --git a/apps/opencs/view/render/cellwater.cpp b/apps/opencs/view/render/cellwater.cpp index f8857c3afc..9e4dbd9f14 100644 --- a/apps/opencs/view/render/cellwater.cpp +++ b/apps/opencs/view/render/cellwater.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include #include diff --git a/apps/opencs/view/render/commands.cpp b/apps/opencs/view/render/commands.cpp index 699bf5d016..2820d7578d 100644 --- a/apps/opencs/view/render/commands.cpp +++ b/apps/opencs/view/render/commands.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include "editmode.hpp" #include "terrainselection.hpp" diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 1fc183ba47..814b011de1 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include "../../model/world/cellcoordinates.hpp" #include "../../model/world/columnimp.hpp" diff --git a/apps/opencs/view/render/terrainselection.hpp b/apps/opencs/view/render/terrainselection.hpp index 1d0da7bb59..4b4758e75e 100644 --- a/apps/opencs/view/render/terrainselection.hpp +++ b/apps/opencs/view/render/terrainselection.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "../../model/world/cellcoordinates.hpp" namespace osg diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 936e8d6778..9052cc5e6b 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include "../widget/brushshapes.hpp" diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp index 7c6fb2e81f..82ebee8467 100644 --- a/apps/opencs/view/world/dialoguecreator.cpp +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -1,6 +1,6 @@ #include "dialoguecreator.hpp" -#include +#include #include "../../model/doc/document.hpp" diff --git a/apps/opencs/view/world/globalcreator.cpp b/apps/opencs/view/world/globalcreator.cpp index c7b140e156..20a5c75cf6 100644 --- a/apps/opencs/view/world/globalcreator.cpp +++ b/apps/opencs/view/world/globalcreator.cpp @@ -1,6 +1,6 @@ #include "globalcreator.hpp" -#include +#include #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" diff --git a/apps/opencs/view/world/vartypedelegate.hpp b/apps/opencs/view/world/vartypedelegate.hpp index 44705e80ec..5b0daec904 100644 --- a/apps/opencs/view/world/vartypedelegate.hpp +++ b/apps/opencs/view/world/vartypedelegate.hpp @@ -1,7 +1,7 @@ #ifndef CSV_WORLD_VARTYPEDELEGATE_H #define CSV_WORLD_VARTYPEDELEGATE_H -#include +#include #include "enumdelegate.hpp" diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index d1747a2e39..1d9e4fae2d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 6c53ba72f3..a60c44265c 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,6 +1,6 @@ #include "activator.hpp" -#include +#include #include #include diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index ad43bd6e5f..9b5e10e1c7 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -1,6 +1,6 @@ #include "actor.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 596bdf26ec..82055e2500 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -3,7 +3,7 @@ #include "../mwworld/class.hpp" -#include +#include namespace ESM { diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 6e9d8b3779..76bee280bc 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -1,6 +1,6 @@ #include "apparatus.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index bc2d788b5b..5dfc5e12f1 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -1,8 +1,8 @@ #include "armor.hpp" -#include -#include -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index eef8e02808..3fcd7368d6 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -1,6 +1,6 @@ #include "book.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index ce8c79d02e..a5f5d828c9 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -1,6 +1,6 @@ #include "clothing.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 0f45c25744..b043b4671d 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -1,7 +1,7 @@ #include "container.hpp" -#include -#include +#include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 03b7cfb069..ba13f5f6d9 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -2,8 +2,8 @@ #include #include -#include -#include +#include +#include #include #include "../mwmechanics/creaturestats.hpp" diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index ee33242126..66e35e5bf8 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -1,7 +1,7 @@ #include "creaturelevlist.hpp" -#include -#include +#include +#include #include "../mwmechanics/levelledlist.hpp" diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 01ff2aa440..81a45f3a30 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -1,7 +1,7 @@ #include "door.hpp" -#include -#include +#include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index f9288a88ce..e09d0de6c0 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWCLASS_DOOR_H #define GAME_MWCLASS_DOOR_H -#include +#include #include "../mwworld/class.hpp" diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index f582812934..424996e417 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -1,6 +1,6 @@ #include "ingredient.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index 4ca45152a1..920c6a5d22 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -1,6 +1,6 @@ #include "itemlevlist.hpp" -#include +#include namespace MWClass { diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index dbd4e8a184..26f41d30f7 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -1,7 +1,7 @@ #include "light.hpp" -#include -#include +#include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index ccb5bbbd58..b97093ae2c 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -1,6 +1,6 @@ #include "lockpick.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 30e68d2377..4fbe8ea04e 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -1,6 +1,6 @@ #include "misc.hpp" -#include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c46e3c0534..4cf191de9c 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -6,9 +6,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index e0f8cf8397..9e7aeffbe5 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -1,6 +1,6 @@ #include "potion.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 8291fb8f3c..f705e51435 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -1,6 +1,6 @@ #include "probe.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 42581a8b6b..64fcd08d78 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -1,6 +1,6 @@ #include "repair.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index fc350c8351..0b77a5630d 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -1,6 +1,6 @@ #include "static.hpp" -#include +#include #include #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index e7337c83b7..a0ec4bd0e5 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -1,6 +1,6 @@ #include "weapon.hpp" -#include +#include #include #include diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 9800f1b39c..4612e2bdfd 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -5,10 +5,10 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 57eb74d0a6..7a3e44349e 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwdialogue/hypertextparser.cpp b/apps/openmw/mwdialogue/hypertextparser.cpp index 89a42bf2ea..732cdb1f8f 100644 --- a/apps/openmw/mwdialogue/hypertextparser.cpp +++ b/apps/openmw/mwdialogue/hypertextparser.cpp @@ -1,4 +1,4 @@ -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 32b2b101d9..7af71d034e 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index b219516183..9f4c8c3689 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -2,10 +2,10 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 5f20a8abb2..16e229ca7f 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,6 +1,6 @@ #include "quest.hpp" -#include +#include #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp index ef787d8eec..dff484562d 100644 --- a/apps/openmw/mwdialogue/selectwrapper.hpp +++ b/apps/openmw/mwdialogue/selectwrapper.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWDIALOGUE_SELECTWRAPPER_H #define GAME_MWDIALOGUE_SELECTWRAPPER_H -#include +#include namespace MWDialogue { diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 86089051d6..8dbb90ca6d 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index beb8715fcd..a6c2d10c2f 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -1,7 +1,7 @@ #ifndef CHARACTER_CREATION_HPP #define CHARACTER_CREATION_HPP -#include +#include #include #include diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index bb34a05530..4c5c23535f 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -2,7 +2,7 @@ #define MWGUI_CLASS_H #include -#include +#include #include "widgets.hpp" #include "windowbase.hpp" diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index 44fa94f3a2..5f9788bc45 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 388bbc7d48..2aa1b51950 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -11,8 +11,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 61f48d5279..3c3d278476 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -8,9 +8,9 @@ #include "windowpinnablebase.hpp" -#include +#include -#include +#include #include namespace MWRender diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index e737cb2b29..1542312efe 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -1,6 +1,6 @@ #include "merchantrepair.hpp" -#include +#include #include #include diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 5daea8f3f8..5ba1b4aafa 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -1,7 +1,7 @@ #include "pickpocketitemmodel.hpp" #include -#include +#include #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index e55b9b4878..99876e0c85 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -6,8 +6,8 @@ #include #include -#include -#include +#include +#include #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index cb847536d3..cf3d693e97 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -2,7 +2,7 @@ #define MWGUI_REVIEW_H #include -#include +#include #include "windowbase.hpp" #include "widgets.hpp" diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index f2c967da4e..df703dcb61 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 9d6ed49d3d..c804eafb72 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -3,19 +3,19 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index 73352ac238..1dd16c33cd 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -1,8 +1,8 @@ #ifndef MWGUI_SPELLCREATION_H #define MWGUI_SPELLCREATION_H -#include -#include +#include +#include #include "windowbase.hpp" #include "referenceinterface.hpp" diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 0673446fe7..acc5131a22 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -5,7 +5,7 @@ #include -#include +#include #include #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/spellmodel.hpp b/apps/openmw/mwgui/spellmodel.hpp index 2404610bf1..af8000c278 100644 --- a/apps/openmw/mwgui/spellmodel.hpp +++ b/apps/openmw/mwgui/spellmodel.hpp @@ -2,7 +2,7 @@ #define OPENMW_GUI_SPELLMODEL_H #include "../mwworld/ptr.hpp" -#include +#include namespace MWGui { diff --git a/apps/openmw/mwgui/statswatcher.hpp b/apps/openmw/mwgui/statswatcher.hpp index 353779d877..6262a50565 100644 --- a/apps/openmw/mwgui/statswatcher.hpp +++ b/apps/openmw/mwgui/statswatcher.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../mwmechanics/stat.hpp" diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 3c55287159..2f27cc029c 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -3,8 +3,8 @@ #include "../mwmechanics/stat.hpp" -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 37807fdf2a..c2f7785da3 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -28,8 +28,8 @@ #include #include -#include -#include +#include +#include #include diff --git a/apps/openmw/mwinput/controlswitch.cpp b/apps/openmw/mwinput/controlswitch.cpp index 6c22e133bc..da8df3ac6b 100644 --- a/apps/openmw/mwinput/controlswitch.cpp +++ b/apps/openmw/mwinput/controlswitch.cpp @@ -1,8 +1,8 @@ #include "controlswitch.hpp" -#include -#include -#include +#include +#include +#include #include diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 4ebe56bf94..31170cafa2 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -3,8 +3,8 @@ #include #include -#include -#include +#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 4ca9018cad..dbcbbab644 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -1,6 +1,6 @@ #include "luabindings.hpp" -#include +#include #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwlua/eventqueue.cpp b/apps/openmw/mwlua/eventqueue.cpp index 1c136551c4..86086f29db 100644 --- a/apps/openmw/mwlua/eventqueue.cpp +++ b/apps/openmw/mwlua/eventqueue.cpp @@ -2,8 +2,8 @@ #include -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 8211c37abf..944a75837e 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -4,8 +4,8 @@ #include -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 69206e8c37..5d413a4aea 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -142,7 +142,7 @@ namespace MWLua } else { - // TODO: If Ptr is empty then try to load the object from esp/esm. + // TODO: If Ptr is empty then try to load the object from esp/esm3. } return ptr; } diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 5b1b5df74e..e87721e381 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include #include diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index 35b1db7a93..be712386c0 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -1,8 +1,8 @@ #include "worldview.hpp" -#include -#include -#include +#include +#include +#include #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 0c781e81bd..d0cdb4b8bb 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 524bdc0475..5fb732ea8a 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "../mwworld/timestamp.hpp" #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d5d434e128..7c94c4e604 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2,8 +2,8 @@ #include -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index a66d8866bf..91770ddfec 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -3,9 +3,9 @@ #include -#include -#include -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 06aef0cdb0..59ad0c1ada 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -1,6 +1,6 @@ #include "aiactivate.hpp" -#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 9eb8a00763..46f04b6f97 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index e6176c869c..3be68f8394 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -1,7 +1,7 @@ #include "aicombataction.hpp" -#include -#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 0184c6e66f..97bb101f06 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -1,7 +1,7 @@ #include "aiescort.hpp" -#include -#include +#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 43a0f25c62..b6e1b124c6 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,7 +1,7 @@ #include "aifollow.hpp" -#include -#include +#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index b1f4355a39..e8e03daad6 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -1,7 +1,7 @@ #include "aipackage.hpp" -#include -#include +#include +#include #include #include #include diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 2d896ddbdc..3343b3cec0 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -1,6 +1,6 @@ #include "aipursue.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 545ec7f140..2cb18f1e78 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include "aipackage.hpp" #include "aistate.hpp" diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 77f6b2f7c0..853509ed80 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -7,7 +7,7 @@ #include "aistate.hpp" #include "aipackagetypeid.hpp" -#include +#include namespace MWWorld { diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 0b4a1411eb..d420771bd4 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 664ae105f3..c2f0beb179 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 61fec3b543..f513554c12 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -9,10 +9,10 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index d23f978ead..8c91a81177 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 570547e0d9..03c2d3f41f 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -2,9 +2,9 @@ #include -#include -#include -#include +#include +#include +#include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 0543a31ba2..002991e004 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -14,7 +14,7 @@ #include "drawstate.hpp" #include -#include +#include namespace ESM { diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 256c5dad48..5e1a6fa239 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -3,8 +3,8 @@ #include -#include -#include +#include +#include #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index e9e0be397e..d4b89c56d0 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -3,8 +3,8 @@ #include #include -#include -#include +#include +#include namespace { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index e0d2da497b..0816445271 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -4,8 +4,8 @@ #include -#include -#include +#include +#include #include diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 1d1dfacce8..ef49381eff 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -2,10 +2,10 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 9125eb527b..2b5157b4ec 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -1,7 +1,7 @@ #include "objects.hpp" #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index f0a5040334..17b355682a 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include namespace MWWorld { diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 050504617e..dfe958e745 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace ESM { diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 4d9cc3a7de..8dfd2b3f0c 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -1,8 +1,8 @@ #ifndef MWMECHANICS_SPELLCASTING_H #define MWMECHANICS_SPELLCASTING_H -#include -#include +#include +#include #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 7e5f89b39b..95ed3ca06e 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include diff --git a/apps/openmw/mwmechanics/spelllist.cpp b/apps/openmw/mwmechanics/spelllist.cpp index 9328d533e3..f90bb22f05 100644 --- a/apps/openmw/mwmechanics/spelllist.cpp +++ b/apps/openmw/mwmechanics/spelllist.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "spells.hpp" diff --git a/apps/openmw/mwmechanics/spelllist.hpp b/apps/openmw/mwmechanics/spelllist.hpp index f5759fd7ee..b43a0bf14f 100644 --- a/apps/openmw/mwmechanics/spelllist.hpp +++ b/apps/openmw/mwmechanics/spelllist.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include namespace ESM { diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index b79af49b05..0f44ee7d07 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -1,9 +1,9 @@ #include "spellpriority.hpp" #include "weaponpriority.hpp" -#include -#include -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 6520ae3ab3..133fc5e931 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,8 +1,8 @@ #include "spells.hpp" #include -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index bcc531087c..571e02d166 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -1,7 +1,7 @@ #ifndef MWMECHANICS_SPELLUTIL_H #define MWMECHANICS_SPELLUTIL_H -#include +#include namespace ESM { diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index ee484f5afd..b5285330df 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -1,6 +1,6 @@ #include "stat.hpp" -#include +#include namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index 3186eef986..091ee98185 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -5,7 +5,7 @@ #include "../mwworld/ptr.hpp" -#include +#include #include "magiceffects.hpp" diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 1a17cc87e6..024d837fe7 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -1,6 +1,6 @@ #include "weaponpriority.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 2f2abca487..fd8a7f2b05 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 98e3bcf737..195af630f5 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 25a9904e26..88e1d10d81 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -5,8 +5,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 808ff0801d..0d7c1959c3 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -6,7 +6,7 @@ #include -#include +#include #include diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 50dfb68008..627b200b71 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include #include diff --git a/apps/openmw/mwrender/fogmanager.cpp b/apps/openmw/mwrender/fogmanager.cpp index b151882922..b68b846851 100644 --- a/apps/openmw/mwrender/fogmanager.cpp +++ b/apps/openmw/mwrender/fogmanager.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include #include diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index ba8749d81c..5da79ec037 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index a39e31bb4d..a25ff004c0 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include #include diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index 26ed8530aa..d6d3ac52a7 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace MWWorld { diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index d9982d35c3..202420fee6 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -12,8 +12,8 @@ #include #include -#include -#include +#include +#include #include #include #include diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 756769bc7d..4748491dd4 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include #include diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index c24cdf4f8d..940760ff6c 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index 42d9150811..ec7d2c15e0 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 1925494ccd..7416ec988a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -43,7 +43,7 @@ #include #include -#include +#include #include diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index fd5cbe0f79..47e60245ba 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -38,7 +38,7 @@ #include -#include +#include #include diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 983365e06a..72537d606b 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -2,7 +2,7 @@ #include "../mwworld/esmstore.hpp" -#include +#include #include diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 501404e958..b20e73957b 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -13,7 +13,7 @@ #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 9de08fbce3..680fd145a0 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -2,8 +2,8 @@ #include #include -#include -#include +#include +#include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index b4c8d102c5..4c395f83d4 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -1,9 +1,9 @@ #include "locals.hpp" #include "globalscripts.hpp" -#include -#include -#include +#include +#include +#include #include #include diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 5d9f037357..f2c603089c 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -18,8 +18,8 @@ #include -#include -#include +#include +#include #include diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 2027c9e606..7efb5148fa 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -7,7 +7,7 @@ #include -#include +#include #include diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 186d1edf26..1697a26e86 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 8a159a5685..ec43364e38 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include diff --git a/apps/openmw/mwsound/watersoundupdater.cpp b/apps/openmw/mwsound/watersoundupdater.cpp index b1646c404f..4ef9bfffc4 100644 --- a/apps/openmw/mwsound/watersoundupdater.cpp +++ b/apps/openmw/mwsound/watersoundupdater.cpp @@ -4,7 +4,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/ptr.hpp" -#include +#include #include diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 52696de104..2d82602534 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -5,7 +5,7 @@ #include -#include +#include #include #include diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index e12de9ca64..2ecd888a7b 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace MWState { diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index b9825a0f90..db7ca19ff8 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -2,10 +2,10 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index ef435cca92..a1f568383b 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -1,6 +1,6 @@ #include "actioneat.hpp" -#include +#include #include "../mwworld/containerstore.hpp" diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index fc09a6e9a8..cdf1986383 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include "../mwrender/landmanager.hpp" diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 2f4702b1eb..2e1d64c441 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace MWWorld { diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index b5e80930ed..507544f1c9 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWWORLD_CELLREF_H #define OPENMW_MWWORLD_CELLREF_H -#include +#include namespace ESM { diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index d020eace45..9f5e4ffbbc 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -1,11 +1,11 @@ #include "cells.hpp" #include -#include -#include +#include +#include #include -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 0c871e4f58..92836be72a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -5,18 +5,18 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/luamanager.hpp" diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index d284a291a5..551924857b 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -11,26 +11,26 @@ #include "livecellref.hpp" #include "cellreflist.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "timestamp.hpp" #include "ptr.hpp" diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 112f56abfc..0850f9c40f 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index f18a595466..cf9bc533e0 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -6,18 +6,18 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index de16e386f2..8369523dca 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -1,7 +1,7 @@ #include "esmloader.hpp" #include "esmstore.hpp" -#include +#include namespace MWWorld { diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 1284df694a..695c5636c0 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -5,8 +5,8 @@ #include #include -#include -#include +#include +#include #include #include #include diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 8a481334e8..cb1d26cb71 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -2,8 +2,8 @@ #include -#include -#include +#include +#include #include #include "esmstore.hpp" diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index ae5e412c70..5b414971f6 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -7,7 +7,7 @@ #include -#include +#include namespace ESM { diff --git a/apps/openmw/mwworld/groundcoverstore.hpp b/apps/openmw/mwworld/groundcoverstore.hpp index 197be2a998..6c48a63c58 100644 --- a/apps/openmw/mwworld/groundcoverstore.hpp +++ b/apps/openmw/mwworld/groundcoverstore.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 8cb0b9b012..27a21e3362 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -4,8 +4,8 @@ #include #include -#include -#include +#include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 62c9f3a2f0..ad9cdabb38 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -1,7 +1,7 @@ #include "livecellref.hpp" #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 7d7e2857fe..9c257d9093 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -1,7 +1,7 @@ #include "magiceffects.hpp" #include "esmstore.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 270889a23e..8d0724e39e 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -4,11 +4,11 @@ #include -#include -#include -#include +#include +#include +#include #include -#include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 1a9744e8a3..2770042969 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -9,9 +9,9 @@ #include "../mwmechanics/drawstate.hpp" #include "../mwmechanics/stat.hpp" -#include +#include #include -#include +#include namespace ESM { diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3bff1854a6..5e60154a89 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -8,8 +8,8 @@ #include -#include -#include +#include +#include #include #include diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index f889250e1d..63a0dacc71 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include "../mwbase/soundmanager.hpp" diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index e8c5ba35e4..10c65bd17c 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -1,6 +1,6 @@ #include "refdata.hpp" -#include +#include #include "customdata.hpp" #include "cellstore.hpp" diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index c98eb0f2af..0bc6df52a5 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -2,7 +2,7 @@ #define GAME_MWWORLD_REFDATA_H #include -#include +#include #include "../mwscript/locals.hpp" #include "../mwworld/customdata.hpp" diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index c767bd669a..d35746dfff 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -2,8 +2,8 @@ #include -#include -#include +#include +#include #include #include @@ -433,7 +433,7 @@ namespace MWWorld return search(cell.mName); } - // this method *must* be called right after esm.loadCell() + // this method *must* be called right after esm3.loadCell() void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) { ESM::CellRef ref; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 965c690238..e2b75edc40 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -2,9 +2,9 @@ #include -#include -#include -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 501378d7cc..1aebe94b87 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -11,10 +11,10 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index e7cd0a7db0..09a2be58ee 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include diff --git a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp index feadc2f59d..ba008f50ff 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp @@ -1,7 +1,7 @@ #include "generate.hpp" #include -#include +#include #include diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index 36e0287461..b86fe7616b 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include diff --git a/apps/openmw_test_suite/esm/variant.cpp b/apps/openmw_test_suite/esm/variant.cpp index 10d35e486c..6991a8b4a8 100644 --- a/apps/openmw_test_suite/esm/variant.cpp +++ b/apps/openmw_test_suite/esm/variant.cpp @@ -1,7 +1,7 @@ -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 7ddab538c4..10003cfdfd 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -3,8 +3,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 89b27b0d94..1f70091247 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -76,15 +76,18 @@ add_component_dir (to_utf8 to_utf8 ) -add_component_dir (esm - attr defs esmcommon esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell +add_component_dir(esm attr defs esmcommon records util luascripts) + +add_component_dir (esm3 + esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat - loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter - savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate - npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile - aisequence magiceffects util custommarkerstate stolenitems transport animationstate controlsstate mappings luascripts + loadweap aipackage effectlist spelllist variant variantimp loadtes3 cellref filter + savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap + inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats + weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile + aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings ) add_component_dir (esmterrain diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 8717a6839b..2b4bce5faf 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -465,7 +465,7 @@ bool Config::GameSettings::hasMaster() QStringList content = mSettings.values(QString(Config::GameSettings::sContentKey)); for (int i = 0; i < content.count(); ++i) { - if (content.at(i).endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) || content.at(i).endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) + if (content.at(i).endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) || content.at(i).endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) { result = true; break; diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 199799025a..57dfe0f87e 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon, bool showOMWScripts) : QAbstractTableModel(parent), diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 46a7c96008..c6223128a4 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -66,7 +66,7 @@ QByteArray ContentSelectorModel::EsmFile::encodedData() const bool ContentSelectorModel::EsmFile::isGameFile() const { return (mGameFiles.size() == 0) && - (mFileName.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive) || + (mFileName.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive) || mFileName.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive)); } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 85d86e6b2b..0a898665a0 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -3,7 +3,7 @@ #include "settingsutils.hpp" #include -#include +#include #include namespace DetourNavigator diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index 53beb02d82..17c5e2843a 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -1,7 +1,7 @@ #include "luascripts.hpp" -#include "esmreader.hpp" -#include "esmwriter.hpp" +#include "components/esm3/esmreader.hpp" +#include "components/esm3/esmwriter.hpp" // List of all records, that are related to Lua. // diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 5c183b6f6d..50d2f90263 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -2,48 +2,48 @@ #define OPENMW_ESM_RECORDS_H #include "defs.hpp" -#include "loadacti.hpp" -#include "loadalch.hpp" -#include "loadappa.hpp" -#include "loadarmo.hpp" -#include "loadbody.hpp" -#include "loadbook.hpp" -#include "loadbsgn.hpp" -#include "loadcell.hpp" -#include "loadclas.hpp" -#include "loadclot.hpp" -#include "loadcont.hpp" -#include "loadcrea.hpp" -#include "loadinfo.hpp" -#include "loaddial.hpp" -#include "loaddoor.hpp" -#include "loadench.hpp" -#include "loadfact.hpp" -#include "loadglob.hpp" -#include "loadgmst.hpp" -#include "loadingr.hpp" -#include "loadland.hpp" -#include "loadlevlist.hpp" -#include "loadligh.hpp" -#include "loadlock.hpp" -#include "loadrepa.hpp" -#include "loadprob.hpp" -#include "loadltex.hpp" -#include "loadmgef.hpp" -#include "loadmisc.hpp" -#include "loadnpc.hpp" -#include "loadpgrd.hpp" -#include "loadrace.hpp" -#include "loadregn.hpp" -#include "loadscpt.hpp" -#include "loadskil.hpp" -#include "loadsndg.hpp" -#include "loadsoun.hpp" -#include "loadspel.hpp" -#include "loadsscr.hpp" -#include "loadstat.hpp" -#include "loadweap.hpp" +#include "components/esm3/loadacti.hpp" +#include "components/esm3/loadalch.hpp" +#include "components/esm3/loadappa.hpp" +#include "components/esm3/loadarmo.hpp" +#include "components/esm3/loadbody.hpp" +#include "components/esm3/loadbook.hpp" +#include "components/esm3/loadbsgn.hpp" +#include "components/esm3/loadcell.hpp" +#include "components/esm3/loadclas.hpp" +#include "components/esm3/loadclot.hpp" +#include "components/esm3/loadcont.hpp" +#include "components/esm3/loadcrea.hpp" +#include "components/esm3/loadinfo.hpp" +#include "components/esm3/loaddial.hpp" +#include "components/esm3/loaddoor.hpp" +#include "components/esm3/loadench.hpp" +#include "components/esm3/loadfact.hpp" +#include "components/esm3/loadglob.hpp" +#include "components/esm3/loadgmst.hpp" +#include "components/esm3/loadingr.hpp" +#include "components/esm3/loadland.hpp" +#include "components/esm3/loadlevlist.hpp" +#include "components/esm3/loadligh.hpp" +#include "components/esm3/loadlock.hpp" +#include "components/esm3/loadrepa.hpp" +#include "components/esm3/loadprob.hpp" +#include "components/esm3/loadltex.hpp" +#include "components/esm3/loadmgef.hpp" +#include "components/esm3/loadmisc.hpp" +#include "components/esm3/loadnpc.hpp" +#include "components/esm3/loadpgrd.hpp" +#include "components/esm3/loadrace.hpp" +#include "components/esm3/loadregn.hpp" +#include "components/esm3/loadscpt.hpp" +#include "components/esm3/loadskil.hpp" +#include "components/esm3/loadsndg.hpp" +#include "components/esm3/loadsoun.hpp" +#include "components/esm3/loadspel.hpp" +#include "components/esm3/loadsscr.hpp" +#include "components/esm3/loadstat.hpp" +#include "components/esm3/loadweap.hpp" // Special records which are not loaded from ESM -#include "attr.hpp" +#include "components/esm/attr.hpp" #endif diff --git a/components/esm/activespells.cpp b/components/esm3/activespells.cpp similarity index 100% rename from components/esm/activespells.cpp rename to components/esm3/activespells.cpp diff --git a/components/esm/activespells.hpp b/components/esm3/activespells.hpp similarity index 97% rename from components/esm/activespells.hpp rename to components/esm3/activespells.hpp index a79366f9c2..91b3f495f5 100644 --- a/components/esm/activespells.hpp +++ b/components/esm3/activespells.hpp @@ -2,7 +2,7 @@ #define OPENMW_ESM_ACTIVESPELLS_H #include "cellref.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" #include "effectlist.hpp" #include diff --git a/components/esm/aipackage.cpp b/components/esm3/aipackage.cpp similarity index 100% rename from components/esm/aipackage.cpp rename to components/esm3/aipackage.cpp diff --git a/components/esm/aipackage.hpp b/components/esm3/aipackage.hpp similarity index 98% rename from components/esm/aipackage.hpp rename to components/esm3/aipackage.hpp index 6993867da1..90e1d1cf9e 100644 --- a/components/esm/aipackage.hpp +++ b/components/esm3/aipackage.hpp @@ -4,7 +4,7 @@ #include #include -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" namespace ESM { diff --git a/components/esm/aisequence.cpp b/components/esm3/aisequence.cpp similarity index 100% rename from components/esm/aisequence.cpp rename to components/esm3/aisequence.cpp diff --git a/components/esm/aisequence.hpp b/components/esm3/aisequence.hpp similarity index 98% rename from components/esm/aisequence.hpp rename to components/esm3/aisequence.hpp index 00c1316d9c..b5a003678b 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -4,9 +4,9 @@ #include #include -#include "defs.hpp" +#include "components/esm/defs.hpp" -#include "util.hpp" +#include "components/esm/util.hpp" namespace ESM { diff --git a/components/esm/animationstate.cpp b/components/esm3/animationstate.cpp similarity index 100% rename from components/esm/animationstate.cpp rename to components/esm3/animationstate.cpp diff --git a/components/esm/animationstate.hpp b/components/esm3/animationstate.hpp similarity index 100% rename from components/esm/animationstate.hpp rename to components/esm3/animationstate.hpp diff --git a/components/esm/cellid.cpp b/components/esm3/cellid.cpp similarity index 100% rename from components/esm/cellid.cpp rename to components/esm3/cellid.cpp diff --git a/components/esm/cellid.hpp b/components/esm3/cellid.hpp similarity index 100% rename from components/esm/cellid.hpp rename to components/esm3/cellid.hpp diff --git a/components/esm/cellref.cpp b/components/esm3/cellref.cpp similarity index 100% rename from components/esm/cellref.cpp rename to components/esm3/cellref.cpp diff --git a/components/esm/cellref.hpp b/components/esm3/cellref.hpp similarity index 99% rename from components/esm/cellref.hpp rename to components/esm3/cellref.hpp index 0013329ccc..55e4b700fc 100644 --- a/components/esm/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -4,7 +4,7 @@ #include #include -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/cellstate.cpp b/components/esm3/cellstate.cpp similarity index 100% rename from components/esm/cellstate.cpp rename to components/esm3/cellstate.cpp diff --git a/components/esm/cellstate.hpp b/components/esm3/cellstate.hpp similarity index 93% rename from components/esm/cellstate.hpp rename to components/esm3/cellstate.hpp index 55c1e51550..9c0427f76b 100644 --- a/components/esm/cellstate.hpp +++ b/components/esm3/cellstate.hpp @@ -3,7 +3,7 @@ #include "cellid.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/containerstate.cpp b/components/esm3/containerstate.cpp similarity index 100% rename from components/esm/containerstate.cpp rename to components/esm3/containerstate.cpp diff --git a/components/esm/containerstate.hpp b/components/esm3/containerstate.hpp similarity index 100% rename from components/esm/containerstate.hpp rename to components/esm3/containerstate.hpp diff --git a/components/esm/controlsstate.cpp b/components/esm3/controlsstate.cpp similarity index 100% rename from components/esm/controlsstate.cpp rename to components/esm3/controlsstate.cpp diff --git a/components/esm/controlsstate.hpp b/components/esm3/controlsstate.hpp similarity index 100% rename from components/esm/controlsstate.hpp rename to components/esm3/controlsstate.hpp diff --git a/components/esm/creaturelevliststate.cpp b/components/esm3/creaturelevliststate.cpp similarity index 100% rename from components/esm/creaturelevliststate.cpp rename to components/esm3/creaturelevliststate.cpp diff --git a/components/esm/creaturelevliststate.hpp b/components/esm3/creaturelevliststate.hpp similarity index 100% rename from components/esm/creaturelevliststate.hpp rename to components/esm3/creaturelevliststate.hpp diff --git a/components/esm/creaturestate.cpp b/components/esm3/creaturestate.cpp similarity index 100% rename from components/esm/creaturestate.cpp rename to components/esm3/creaturestate.cpp diff --git a/components/esm/creaturestate.hpp b/components/esm3/creaturestate.hpp similarity index 100% rename from components/esm/creaturestate.hpp rename to components/esm3/creaturestate.hpp diff --git a/components/esm/creaturestats.cpp b/components/esm3/creaturestats.cpp similarity index 100% rename from components/esm/creaturestats.cpp rename to components/esm3/creaturestats.cpp diff --git a/components/esm/creaturestats.hpp b/components/esm3/creaturestats.hpp similarity index 97% rename from components/esm/creaturestats.hpp rename to components/esm3/creaturestats.hpp index 7b261e3dd3..d47a283630 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm3/creaturestats.hpp @@ -7,9 +7,9 @@ #include "statstate.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" -#include "attr.hpp" +#include "components/esm/attr.hpp" #include "spellstate.hpp" #include "activespells.hpp" #include "magiceffects.hpp" diff --git a/components/esm/custommarkerstate.cpp b/components/esm3/custommarkerstate.cpp similarity index 100% rename from components/esm/custommarkerstate.cpp rename to components/esm3/custommarkerstate.cpp diff --git a/components/esm/custommarkerstate.hpp b/components/esm3/custommarkerstate.hpp similarity index 100% rename from components/esm/custommarkerstate.hpp rename to components/esm3/custommarkerstate.hpp diff --git a/components/esm/debugprofile.cpp b/components/esm3/debugprofile.cpp similarity index 97% rename from components/esm/debugprofile.cpp rename to components/esm3/debugprofile.cpp index 090d2bfe6d..f862d4a289 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm3/debugprofile.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; diff --git a/components/esm/debugprofile.hpp b/components/esm3/debugprofile.hpp similarity index 100% rename from components/esm/debugprofile.hpp rename to components/esm3/debugprofile.hpp diff --git a/components/esm/dialoguestate.cpp b/components/esm3/dialoguestate.cpp similarity index 100% rename from components/esm/dialoguestate.cpp rename to components/esm3/dialoguestate.cpp diff --git a/components/esm/dialoguestate.hpp b/components/esm3/dialoguestate.hpp similarity index 100% rename from components/esm/dialoguestate.hpp rename to components/esm3/dialoguestate.hpp diff --git a/components/esm/doorstate.cpp b/components/esm3/doorstate.cpp similarity index 100% rename from components/esm/doorstate.cpp rename to components/esm3/doorstate.cpp diff --git a/components/esm/doorstate.hpp b/components/esm3/doorstate.hpp similarity index 100% rename from components/esm/doorstate.hpp rename to components/esm3/doorstate.hpp diff --git a/components/esm/effectlist.cpp b/components/esm3/effectlist.cpp similarity index 100% rename from components/esm/effectlist.cpp rename to components/esm3/effectlist.cpp diff --git a/components/esm/effectlist.hpp b/components/esm3/effectlist.hpp similarity index 100% rename from components/esm/effectlist.hpp rename to components/esm3/effectlist.hpp diff --git a/components/esm/esmreader.cpp b/components/esm3/esmreader.cpp similarity index 100% rename from components/esm/esmreader.cpp rename to components/esm3/esmreader.cpp diff --git a/components/esm/esmreader.hpp b/components/esm3/esmreader.hpp similarity index 99% rename from components/esm/esmreader.hpp rename to components/esm3/esmreader.hpp index d7eb6ff0a1..3b384dc603 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -12,7 +12,7 @@ #include -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" #include "loadtes3.hpp" namespace ESM { diff --git a/components/esm/esmwriter.cpp b/components/esm3/esmwriter.cpp similarity index 100% rename from components/esm/esmwriter.cpp rename to components/esm3/esmwriter.cpp diff --git a/components/esm/esmwriter.hpp b/components/esm3/esmwriter.hpp similarity index 99% rename from components/esm/esmwriter.hpp rename to components/esm3/esmwriter.hpp index ba5800f67c..ed2ccbb377 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -4,7 +4,7 @@ #include #include -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" #include "loadtes3.hpp" namespace ToUTF8 diff --git a/components/esm/filter.cpp b/components/esm3/filter.cpp similarity index 97% rename from components/esm/filter.cpp rename to components/esm3/filter.cpp index 76a518c63d..03fa4ba278 100644 --- a/components/esm/filter.cpp +++ b/components/esm3/filter.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" unsigned int ESM::Filter::sRecordId = REC_FILT; diff --git a/components/esm/filter.hpp b/components/esm3/filter.hpp similarity index 100% rename from components/esm/filter.hpp rename to components/esm3/filter.hpp diff --git a/components/esm/fogstate.cpp b/components/esm3/fogstate.cpp similarity index 100% rename from components/esm/fogstate.cpp rename to components/esm3/fogstate.cpp diff --git a/components/esm/fogstate.hpp b/components/esm3/fogstate.hpp similarity index 100% rename from components/esm/fogstate.hpp rename to components/esm3/fogstate.hpp diff --git a/components/esm/globalmap.cpp b/components/esm3/globalmap.cpp similarity index 96% rename from components/esm/globalmap.cpp rename to components/esm3/globalmap.cpp index 190329c61e..3005349d26 100644 --- a/components/esm/globalmap.cpp +++ b/components/esm3/globalmap.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" unsigned int ESM::GlobalMap::sRecordId = ESM::REC_GMAP; diff --git a/components/esm/globalmap.hpp b/components/esm3/globalmap.hpp similarity index 100% rename from components/esm/globalmap.hpp rename to components/esm3/globalmap.hpp diff --git a/components/esm/globalscript.cpp b/components/esm3/globalscript.cpp similarity index 100% rename from components/esm/globalscript.cpp rename to components/esm3/globalscript.cpp diff --git a/components/esm/globalscript.hpp b/components/esm3/globalscript.hpp similarity index 100% rename from components/esm/globalscript.hpp rename to components/esm3/globalscript.hpp diff --git a/components/esm/inventorystate.cpp b/components/esm3/inventorystate.cpp similarity index 100% rename from components/esm/inventorystate.cpp rename to components/esm3/inventorystate.cpp diff --git a/components/esm/inventorystate.hpp b/components/esm3/inventorystate.hpp similarity index 100% rename from components/esm/inventorystate.hpp rename to components/esm3/inventorystate.hpp diff --git a/components/esm/journalentry.cpp b/components/esm3/journalentry.cpp similarity index 100% rename from components/esm/journalentry.cpp rename to components/esm3/journalentry.cpp diff --git a/components/esm/journalentry.hpp b/components/esm3/journalentry.hpp similarity index 100% rename from components/esm/journalentry.hpp rename to components/esm3/journalentry.hpp diff --git a/components/esm/loadacti.cpp b/components/esm3/loadacti.cpp similarity index 98% rename from components/esm/loadacti.cpp rename to components/esm3/loadacti.cpp index fcb0954918..c4aa9cb6b8 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm3/loadacti.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadacti.hpp b/components/esm3/loadacti.hpp similarity index 100% rename from components/esm/loadacti.hpp rename to components/esm3/loadacti.hpp diff --git a/components/esm/loadalch.cpp b/components/esm3/loadalch.cpp similarity index 98% rename from components/esm/loadalch.cpp rename to components/esm3/loadalch.cpp index ad30570f74..618e116cce 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadalch.hpp b/components/esm3/loadalch.hpp similarity index 100% rename from components/esm/loadalch.hpp rename to components/esm3/loadalch.hpp diff --git a/components/esm/loadappa.cpp b/components/esm3/loadappa.cpp similarity index 98% rename from components/esm/loadappa.cpp rename to components/esm3/loadappa.cpp index 1113870197..13ba0fbb42 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm3/loadappa.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadappa.hpp b/components/esm3/loadappa.hpp similarity index 100% rename from components/esm/loadappa.hpp rename to components/esm3/loadappa.hpp diff --git a/components/esm/loadarmo.cpp b/components/esm3/loadarmo.cpp similarity index 99% rename from components/esm/loadarmo.cpp rename to components/esm3/loadarmo.cpp index cab0d52a88..9c5164f8a6 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm3/loadarmo.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadarmo.hpp b/components/esm3/loadarmo.hpp similarity index 100% rename from components/esm/loadarmo.hpp rename to components/esm3/loadarmo.hpp diff --git a/components/esm/loadbody.cpp b/components/esm3/loadbody.cpp similarity index 98% rename from components/esm/loadbody.cpp rename to components/esm3/loadbody.cpp index c7f6bce40a..1098941c25 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm3/loadbody.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadbody.hpp b/components/esm3/loadbody.hpp similarity index 100% rename from components/esm/loadbody.hpp rename to components/esm3/loadbody.hpp diff --git a/components/esm/loadbook.cpp b/components/esm3/loadbook.cpp similarity index 98% rename from components/esm/loadbook.cpp rename to components/esm3/loadbook.cpp index 07b9a6b50f..c03046ed9f 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm3/loadbook.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadbook.hpp b/components/esm3/loadbook.hpp similarity index 100% rename from components/esm/loadbook.hpp rename to components/esm3/loadbook.hpp diff --git a/components/esm/loadbsgn.cpp b/components/esm3/loadbsgn.cpp similarity index 98% rename from components/esm/loadbsgn.cpp rename to components/esm3/loadbsgn.cpp index d767eb66e0..d02fc93914 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm3/loadbsgn.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadbsgn.hpp b/components/esm3/loadbsgn.hpp similarity index 100% rename from components/esm/loadbsgn.hpp rename to components/esm3/loadbsgn.hpp diff --git a/components/esm/loadcell.cpp b/components/esm3/loadcell.cpp similarity index 99% rename from components/esm/loadcell.cpp rename to components/esm3/loadcell.cpp index b2c95ad25f..256be62898 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -11,7 +11,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" #include "cellid.hpp" namespace diff --git a/components/esm/loadcell.hpp b/components/esm3/loadcell.hpp similarity index 98% rename from components/esm/loadcell.hpp rename to components/esm3/loadcell.hpp index c2a694b744..61f9fb54a3 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -5,8 +5,8 @@ #include #include -#include "esmcommon.hpp" -#include "defs.hpp" +#include "components/esm/esmcommon.hpp" +#include "components/esm/defs.hpp" #include "cellref.hpp" #include "cellid.hpp" diff --git a/components/esm/loadclas.cpp b/components/esm3/loadclas.cpp similarity index 98% rename from components/esm/loadclas.cpp rename to components/esm3/loadclas.cpp index c70f7dd0d3..0bd966e752 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadclas.hpp b/components/esm3/loadclas.hpp similarity index 100% rename from components/esm/loadclas.hpp rename to components/esm3/loadclas.hpp diff --git a/components/esm/loadclot.cpp b/components/esm3/loadclot.cpp similarity index 98% rename from components/esm/loadclot.cpp rename to components/esm3/loadclot.cpp index 8f2aff40fc..05a895b0f4 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm3/loadclot.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadclot.hpp b/components/esm3/loadclot.hpp similarity index 100% rename from components/esm/loadclot.hpp rename to components/esm3/loadclot.hpp diff --git a/components/esm/loadcont.cpp b/components/esm3/loadcont.cpp similarity index 99% rename from components/esm/loadcont.cpp rename to components/esm3/loadcont.cpp index b7757646a1..19bb649125 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm3/loadcont.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadcont.hpp b/components/esm3/loadcont.hpp similarity index 96% rename from components/esm/loadcont.hpp rename to components/esm3/loadcont.hpp index eac791e3f3..a236f0a3e5 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm3/loadcont.hpp @@ -4,7 +4,7 @@ #include #include -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" namespace ESM { diff --git a/components/esm/loadcrea.cpp b/components/esm3/loadcrea.cpp similarity index 99% rename from components/esm/loadcrea.cpp rename to components/esm3/loadcrea.cpp index 590a68bc35..1cec61b3ab 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadcrea.hpp b/components/esm3/loadcrea.hpp similarity index 100% rename from components/esm/loadcrea.hpp rename to components/esm3/loadcrea.hpp diff --git a/components/esm/loaddial.cpp b/components/esm3/loaddial.cpp similarity index 99% rename from components/esm/loaddial.cpp rename to components/esm3/loaddial.cpp index 535ea23380..67696be821 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm3/loaddial.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loaddial.hpp b/components/esm3/loaddial.hpp similarity index 100% rename from components/esm/loaddial.hpp rename to components/esm3/loaddial.hpp diff --git a/components/esm/loaddoor.cpp b/components/esm3/loaddoor.cpp similarity index 98% rename from components/esm/loaddoor.cpp rename to components/esm3/loaddoor.cpp index d5bf51b4da..48fa0faaf3 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm3/loaddoor.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loaddoor.hpp b/components/esm3/loaddoor.hpp similarity index 100% rename from components/esm/loaddoor.hpp rename to components/esm3/loaddoor.hpp diff --git a/components/esm/loadench.cpp b/components/esm3/loadench.cpp similarity index 98% rename from components/esm/loadench.cpp rename to components/esm3/loadench.cpp index db0727099c..fae0aa211b 100644 --- a/components/esm/loadench.cpp +++ b/components/esm3/loadench.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadench.hpp b/components/esm3/loadench.hpp similarity index 100% rename from components/esm/loadench.hpp rename to components/esm3/loadench.hpp diff --git a/components/esm/loadfact.cpp b/components/esm3/loadfact.cpp similarity index 99% rename from components/esm/loadfact.cpp rename to components/esm3/loadfact.cpp index 61d0e1dcf4..554c30e40e 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm3/loadfact.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadfact.hpp b/components/esm3/loadfact.hpp similarity index 100% rename from components/esm/loadfact.hpp rename to components/esm3/loadfact.hpp diff --git a/components/esm/loadglob.cpp b/components/esm3/loadglob.cpp similarity index 96% rename from components/esm/loadglob.cpp rename to components/esm3/loadglob.cpp index d2226d1738..fd4ee05665 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm3/loadglob.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadglob.hpp b/components/esm3/loadglob.hpp similarity index 100% rename from components/esm/loadglob.hpp rename to components/esm3/loadglob.hpp diff --git a/components/esm/loadgmst.cpp b/components/esm3/loadgmst.cpp similarity index 96% rename from components/esm/loadgmst.cpp rename to components/esm3/loadgmst.cpp index 6d4ac1b202..1bcf6a1fa4 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm3/loadgmst.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadgmst.hpp b/components/esm3/loadgmst.hpp similarity index 100% rename from components/esm/loadgmst.hpp rename to components/esm3/loadgmst.hpp diff --git a/components/esm/loadinfo.cpp b/components/esm3/loadinfo.cpp similarity index 99% rename from components/esm/loadinfo.cpp rename to components/esm3/loadinfo.cpp index 6c54b0b9ab..5cc1f9a090 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadinfo.hpp b/components/esm3/loadinfo.hpp similarity index 98% rename from components/esm/loadinfo.hpp rename to components/esm3/loadinfo.hpp index d68301c91b..7f135087ee 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm3/loadinfo.hpp @@ -4,7 +4,7 @@ #include #include -#include "defs.hpp" +#include "components/esm/defs.hpp" #include "variant.hpp" namespace ESM diff --git a/components/esm/loadingr.cpp b/components/esm3/loadingr.cpp similarity index 98% rename from components/esm/loadingr.cpp rename to components/esm3/loadingr.cpp index 1aba000267..652e381881 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadingr.hpp b/components/esm3/loadingr.hpp similarity index 100% rename from components/esm/loadingr.hpp rename to components/esm3/loadingr.hpp diff --git a/components/esm/loadland.cpp b/components/esm3/loadland.cpp similarity index 99% rename from components/esm/loadland.cpp rename to components/esm3/loadland.cpp index e97ad6b759..24cac7f015 100644 --- a/components/esm/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -5,7 +5,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadland.hpp b/components/esm3/loadland.hpp similarity index 99% rename from components/esm/loadland.hpp rename to components/esm3/loadland.hpp index 610dd28fb8..1244a955cf 100644 --- a/components/esm/loadland.hpp +++ b/components/esm3/loadland.hpp @@ -5,7 +5,7 @@ #include -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" namespace ESM { diff --git a/components/esm/loadlevlist.cpp b/components/esm3/loadlevlist.cpp similarity index 99% rename from components/esm/loadlevlist.cpp rename to components/esm3/loadlevlist.cpp index acf97f4259..1de278761b 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadlevlist.hpp b/components/esm3/loadlevlist.hpp similarity index 100% rename from components/esm/loadlevlist.hpp rename to components/esm3/loadlevlist.hpp diff --git a/components/esm/loadligh.cpp b/components/esm3/loadligh.cpp similarity index 98% rename from components/esm/loadligh.cpp rename to components/esm3/loadligh.cpp index 32c0b16243..4e8c41fbb9 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm3/loadligh.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadligh.hpp b/components/esm3/loadligh.hpp similarity index 100% rename from components/esm/loadligh.hpp rename to components/esm3/loadligh.hpp diff --git a/components/esm/loadlock.cpp b/components/esm3/loadlock.cpp similarity index 98% rename from components/esm/loadlock.cpp rename to components/esm3/loadlock.cpp index bea5c86773..10ac680ab5 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm3/loadlock.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadlock.hpp b/components/esm3/loadlock.hpp similarity index 100% rename from components/esm/loadlock.hpp rename to components/esm3/loadlock.hpp diff --git a/components/esm/loadltex.cpp b/components/esm3/loadltex.cpp similarity index 97% rename from components/esm/loadltex.cpp rename to components/esm3/loadltex.cpp index f2a1e17d49..3d01c4fe17 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm3/loadltex.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadltex.hpp b/components/esm3/loadltex.hpp similarity index 100% rename from components/esm/loadltex.hpp rename to components/esm3/loadltex.hpp diff --git a/components/esm/loadmgef.cpp b/components/esm3/loadmgef.cpp similarity index 99% rename from components/esm/loadmgef.cpp rename to components/esm3/loadmgef.cpp index 7aff249aeb..e86907564c 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace { diff --git a/components/esm/loadmgef.hpp b/components/esm3/loadmgef.hpp similarity index 100% rename from components/esm/loadmgef.hpp rename to components/esm3/loadmgef.hpp diff --git a/components/esm/loadmisc.cpp b/components/esm3/loadmisc.cpp similarity index 98% rename from components/esm/loadmisc.cpp rename to components/esm3/loadmisc.cpp index a60012e74b..0405382cfd 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm3/loadmisc.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadmisc.hpp b/components/esm3/loadmisc.hpp similarity index 100% rename from components/esm/loadmisc.hpp rename to components/esm3/loadmisc.hpp diff --git a/components/esm/loadnpc.cpp b/components/esm3/loadnpc.cpp similarity index 99% rename from components/esm/loadnpc.cpp rename to components/esm3/loadnpc.cpp index b86ea6f8bc..07ee560b54 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadnpc.hpp b/components/esm3/loadnpc.hpp similarity index 98% rename from components/esm/loadnpc.hpp rename to components/esm3/loadnpc.hpp index f0354cb603..24752beb11 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -4,7 +4,7 @@ #include #include -#include "defs.hpp" +#include "components/esm/defs.hpp" #include "loadcont.hpp" #include "aipackage.hpp" #include "spelllist.hpp" diff --git a/components/esm/loadpgrd.cpp b/components/esm3/loadpgrd.cpp similarity index 99% rename from components/esm/loadpgrd.cpp rename to components/esm3/loadpgrd.cpp index b10e3c453d..da90018619 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadpgrd.hpp b/components/esm3/loadpgrd.hpp similarity index 100% rename from components/esm/loadpgrd.hpp rename to components/esm3/loadpgrd.hpp diff --git a/components/esm/loadprob.cpp b/components/esm3/loadprob.cpp similarity index 98% rename from components/esm/loadprob.cpp rename to components/esm3/loadprob.cpp index edc6b89cd1..644abb6a39 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm3/loadprob.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadprob.hpp b/components/esm3/loadprob.hpp similarity index 100% rename from components/esm/loadprob.hpp rename to components/esm3/loadprob.hpp diff --git a/components/esm/loadrace.cpp b/components/esm3/loadrace.cpp similarity index 98% rename from components/esm/loadrace.cpp rename to components/esm3/loadrace.cpp index 35ad4421f1..092d064cd8 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm3/loadrace.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadrace.hpp b/components/esm3/loadrace.hpp similarity index 100% rename from components/esm/loadrace.hpp rename to components/esm3/loadrace.hpp diff --git a/components/esm/loadregn.cpp b/components/esm3/loadregn.cpp similarity index 99% rename from components/esm/loadregn.cpp rename to components/esm3/loadregn.cpp index 9cfdaaabc0..9e8921e939 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm3/loadregn.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadregn.hpp b/components/esm3/loadregn.hpp similarity index 97% rename from components/esm/loadregn.hpp rename to components/esm3/loadregn.hpp index 64991c9b3a..7944f69cfe 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm3/loadregn.hpp @@ -4,7 +4,7 @@ #include #include -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" namespace ESM { diff --git a/components/esm/loadrepa.cpp b/components/esm3/loadrepa.cpp similarity index 98% rename from components/esm/loadrepa.cpp rename to components/esm3/loadrepa.cpp index 3fe62e740a..f7349256f0 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm3/loadrepa.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadrepa.hpp b/components/esm3/loadrepa.hpp similarity index 100% rename from components/esm/loadrepa.hpp rename to components/esm3/loadrepa.hpp diff --git a/components/esm/loadscpt.cpp b/components/esm3/loadscpt.cpp similarity index 99% rename from components/esm/loadscpt.cpp rename to components/esm3/loadscpt.cpp index 76cb3c0149..dfbcc1c7f1 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadscpt.hpp b/components/esm3/loadscpt.hpp similarity index 97% rename from components/esm/loadscpt.hpp rename to components/esm3/loadscpt.hpp index f737698184..d40a571ec6 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm3/loadscpt.hpp @@ -4,7 +4,7 @@ #include #include -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" namespace ESM { diff --git a/components/esm/loadskil.cpp b/components/esm3/loadskil.cpp similarity index 99% rename from components/esm/loadskil.cpp rename to components/esm3/loadskil.cpp index 9236302dea..5843b3ddb5 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm3/loadskil.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadskil.hpp b/components/esm3/loadskil.hpp similarity index 98% rename from components/esm/loadskil.hpp rename to components/esm3/loadskil.hpp index 404ef06692..cd4cad6a71 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm3/loadskil.hpp @@ -4,7 +4,7 @@ #include #include -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadsndg.cpp b/components/esm3/loadsndg.cpp similarity index 98% rename from components/esm/loadsndg.cpp rename to components/esm3/loadsndg.cpp index c1170b36c7..17423a061b 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm3/loadsndg.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadsndg.hpp b/components/esm3/loadsndg.hpp similarity index 100% rename from components/esm/loadsndg.hpp rename to components/esm3/loadsndg.hpp diff --git a/components/esm/loadsoun.cpp b/components/esm3/loadsoun.cpp similarity index 98% rename from components/esm/loadsoun.cpp rename to components/esm3/loadsoun.cpp index 9d58e19e99..109d732da5 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm3/loadsoun.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadsoun.hpp b/components/esm3/loadsoun.hpp similarity index 100% rename from components/esm/loadsoun.hpp rename to components/esm3/loadsoun.hpp diff --git a/components/esm/loadspel.cpp b/components/esm3/loadspel.cpp similarity index 98% rename from components/esm/loadspel.cpp rename to components/esm3/loadspel.cpp index cc26024426..d113cdd194 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm3/loadspel.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadspel.hpp b/components/esm3/loadspel.hpp similarity index 100% rename from components/esm/loadspel.hpp rename to components/esm3/loadspel.hpp diff --git a/components/esm/loadsscr.cpp b/components/esm3/loadsscr.cpp similarity index 97% rename from components/esm/loadsscr.cpp rename to components/esm3/loadsscr.cpp index 723fb3bf10..59d6eab58e 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm3/loadsscr.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadsscr.hpp b/components/esm3/loadsscr.hpp similarity index 100% rename from components/esm/loadsscr.hpp rename to components/esm3/loadsscr.hpp diff --git a/components/esm/loadstat.cpp b/components/esm3/loadstat.cpp similarity index 97% rename from components/esm/loadstat.cpp rename to components/esm3/loadstat.cpp index e0c9eb2c7e..a4941f0895 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm3/loadstat.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadstat.hpp b/components/esm3/loadstat.hpp similarity index 100% rename from components/esm/loadstat.hpp rename to components/esm3/loadstat.hpp diff --git a/components/esm/loadtes3.cpp b/components/esm3/loadtes3.cpp similarity index 96% rename from components/esm/loadtes3.cpp rename to components/esm3/loadtes3.cpp index f5cbcd62f2..e39d65298c 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm3/loadtes3.cpp @@ -1,9 +1,9 @@ #include "loadtes3.hpp" -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" void ESM::Header::blank() { diff --git a/components/esm/loadtes3.hpp b/components/esm3/loadtes3.hpp similarity index 97% rename from components/esm/loadtes3.hpp rename to components/esm3/loadtes3.hpp index 014e2a1362..8f5ed68413 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm3/loadtes3.hpp @@ -3,7 +3,7 @@ #include -#include "esmcommon.hpp" +#include "components/esm/esmcommon.hpp" namespace ESM { diff --git a/components/esm/loadweap.cpp b/components/esm3/loadweap.cpp similarity index 98% rename from components/esm/loadweap.cpp rename to components/esm3/loadweap.cpp index 08c5a3b641..e109096e09 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm3/loadweap.cpp @@ -2,7 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/loadweap.hpp b/components/esm3/loadweap.hpp similarity index 100% rename from components/esm/loadweap.hpp rename to components/esm3/loadweap.hpp diff --git a/components/esm/locals.cpp b/components/esm3/locals.cpp similarity index 100% rename from components/esm/locals.cpp rename to components/esm3/locals.cpp diff --git a/components/esm/locals.hpp b/components/esm3/locals.hpp similarity index 100% rename from components/esm/locals.hpp rename to components/esm3/locals.hpp diff --git a/components/esm/magiceffects.cpp b/components/esm3/magiceffects.cpp similarity index 100% rename from components/esm/magiceffects.cpp rename to components/esm3/magiceffects.cpp diff --git a/components/esm/magiceffects.hpp b/components/esm3/magiceffects.hpp similarity index 100% rename from components/esm/magiceffects.hpp rename to components/esm3/magiceffects.hpp diff --git a/components/esm/mappings.cpp b/components/esm3/mappings.cpp similarity index 100% rename from components/esm/mappings.cpp rename to components/esm3/mappings.cpp diff --git a/components/esm/mappings.hpp b/components/esm3/mappings.hpp similarity index 78% rename from components/esm/mappings.hpp rename to components/esm3/mappings.hpp index f930fef152..e2fa1b62c3 100644 --- a/components/esm/mappings.hpp +++ b/components/esm3/mappings.hpp @@ -3,8 +3,8 @@ #include -#include -#include +#include +#include namespace ESM { diff --git a/components/esm/npcstate.cpp b/components/esm3/npcstate.cpp similarity index 100% rename from components/esm/npcstate.cpp rename to components/esm3/npcstate.cpp diff --git a/components/esm/npcstate.hpp b/components/esm3/npcstate.hpp similarity index 100% rename from components/esm/npcstate.hpp rename to components/esm3/npcstate.hpp diff --git a/components/esm/npcstats.cpp b/components/esm3/npcstats.cpp similarity index 100% rename from components/esm/npcstats.cpp rename to components/esm3/npcstats.cpp diff --git a/components/esm/npcstats.hpp b/components/esm3/npcstats.hpp similarity index 100% rename from components/esm/npcstats.hpp rename to components/esm3/npcstats.hpp diff --git a/components/esm/objectstate.cpp b/components/esm3/objectstate.cpp similarity index 100% rename from components/esm/objectstate.cpp rename to components/esm3/objectstate.cpp diff --git a/components/esm/objectstate.hpp b/components/esm3/objectstate.hpp similarity index 97% rename from components/esm/objectstate.hpp rename to components/esm3/objectstate.hpp index b30f44b5e1..eb09e2e854 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -6,7 +6,7 @@ #include "cellref.hpp" #include "locals.hpp" -#include "luascripts.hpp" +#include "components/esm/luascripts.hpp" #include "animationstate.hpp" namespace ESM diff --git a/components/esm/player.cpp b/components/esm3/player.cpp similarity index 100% rename from components/esm/player.cpp rename to components/esm3/player.cpp diff --git a/components/esm/player.hpp b/components/esm3/player.hpp similarity index 93% rename from components/esm/player.hpp rename to components/esm3/player.hpp index bea29cf74a..d69571705d 100644 --- a/components/esm/player.hpp +++ b/components/esm3/player.hpp @@ -5,10 +5,10 @@ #include "npcstate.hpp" #include "cellid.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" #include "loadskil.hpp" -#include "attr.hpp" +#include "components/esm/attr.hpp" namespace ESM { diff --git a/components/esm/projectilestate.cpp b/components/esm3/projectilestate.cpp similarity index 100% rename from components/esm/projectilestate.cpp rename to components/esm3/projectilestate.cpp diff --git a/components/esm/projectilestate.hpp b/components/esm3/projectilestate.hpp similarity index 96% rename from components/esm/projectilestate.hpp rename to components/esm3/projectilestate.hpp index 84292813ce..2b2f0d137b 100644 --- a/components/esm/projectilestate.hpp +++ b/components/esm3/projectilestate.hpp @@ -8,7 +8,7 @@ #include "effectlist.hpp" -#include "util.hpp" +#include "components/esm/util.hpp" namespace ESM { diff --git a/components/esm/queststate.cpp b/components/esm3/queststate.cpp similarity index 100% rename from components/esm/queststate.cpp rename to components/esm3/queststate.cpp diff --git a/components/esm/queststate.hpp b/components/esm3/queststate.hpp similarity index 100% rename from components/esm/queststate.hpp rename to components/esm3/queststate.hpp diff --git a/components/esm/quickkeys.cpp b/components/esm3/quickkeys.cpp similarity index 100% rename from components/esm/quickkeys.cpp rename to components/esm3/quickkeys.cpp diff --git a/components/esm/quickkeys.hpp b/components/esm3/quickkeys.hpp similarity index 100% rename from components/esm/quickkeys.hpp rename to components/esm3/quickkeys.hpp diff --git a/components/esm/savedgame.cpp b/components/esm3/savedgame.cpp similarity index 100% rename from components/esm/savedgame.cpp rename to components/esm3/savedgame.cpp diff --git a/components/esm/savedgame.hpp b/components/esm3/savedgame.hpp similarity index 96% rename from components/esm/savedgame.hpp rename to components/esm3/savedgame.hpp index 26efae824e..46180bb704 100644 --- a/components/esm/savedgame.hpp +++ b/components/esm3/savedgame.hpp @@ -4,7 +4,7 @@ #include #include -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/spelllist.cpp b/components/esm3/spelllist.cpp similarity index 100% rename from components/esm/spelllist.cpp rename to components/esm3/spelllist.cpp diff --git a/components/esm/spelllist.hpp b/components/esm3/spelllist.hpp similarity index 100% rename from components/esm/spelllist.hpp rename to components/esm3/spelllist.hpp diff --git a/components/esm/spellstate.cpp b/components/esm3/spellstate.cpp similarity index 100% rename from components/esm/spellstate.cpp rename to components/esm3/spellstate.cpp diff --git a/components/esm/spellstate.hpp b/components/esm3/spellstate.hpp similarity index 97% rename from components/esm/spellstate.hpp rename to components/esm3/spellstate.hpp index e7067dae8c..bb909c506c 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm3/spellstate.hpp @@ -6,7 +6,7 @@ #include #include -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/statstate.cpp b/components/esm3/statstate.cpp similarity index 100% rename from components/esm/statstate.cpp rename to components/esm3/statstate.cpp diff --git a/components/esm/statstate.hpp b/components/esm3/statstate.hpp similarity index 100% rename from components/esm/statstate.hpp rename to components/esm3/statstate.hpp diff --git a/components/esm/stolenitems.cpp b/components/esm3/stolenitems.cpp similarity index 94% rename from components/esm/stolenitems.cpp rename to components/esm3/stolenitems.cpp index c51b0b99b0..e196fc80c2 100644 --- a/components/esm/stolenitems.cpp +++ b/components/esm3/stolenitems.cpp @@ -1,7 +1,7 @@ #include "stolenitems.hpp" -#include -#include +#include +#include namespace ESM { diff --git a/components/esm/stolenitems.hpp b/components/esm3/stolenitems.hpp similarity index 100% rename from components/esm/stolenitems.hpp rename to components/esm3/stolenitems.hpp diff --git a/components/esm/transport.cpp b/components/esm3/transport.cpp similarity index 92% rename from components/esm/transport.cpp rename to components/esm3/transport.cpp index 9d40debf73..958ef6949a 100644 --- a/components/esm/transport.cpp +++ b/components/esm3/transport.cpp @@ -2,8 +2,8 @@ #include -#include -#include +#include +#include namespace ESM { diff --git a/components/esm/transport.hpp b/components/esm3/transport.hpp similarity index 94% rename from components/esm/transport.hpp rename to components/esm3/transport.hpp index 10d4013f72..f1e5f2103b 100644 --- a/components/esm/transport.hpp +++ b/components/esm3/transport.hpp @@ -4,7 +4,7 @@ #include #include -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace ESM { diff --git a/components/esm/variant.cpp b/components/esm3/variant.cpp similarity index 99% rename from components/esm/variant.cpp rename to components/esm3/variant.cpp index 830e13f3e4..a0f6ca7b41 100644 --- a/components/esm/variant.cpp +++ b/components/esm3/variant.cpp @@ -6,7 +6,7 @@ #include "esmreader.hpp" #include "variantimp.hpp" -#include "defs.hpp" +#include "components/esm/defs.hpp" namespace { diff --git a/components/esm/variant.hpp b/components/esm3/variant.hpp similarity index 100% rename from components/esm/variant.hpp rename to components/esm3/variant.hpp diff --git a/components/esm/variantimp.cpp b/components/esm3/variantimp.cpp similarity index 100% rename from components/esm/variantimp.cpp rename to components/esm3/variantimp.cpp diff --git a/components/esm/variantimp.hpp b/components/esm3/variantimp.hpp similarity index 100% rename from components/esm/variantimp.hpp rename to components/esm3/variantimp.hpp diff --git a/components/esm/weatherstate.cpp b/components/esm3/weatherstate.cpp similarity index 100% rename from components/esm/weatherstate.cpp rename to components/esm3/weatherstate.cpp diff --git a/components/esm/weatherstate.hpp b/components/esm3/weatherstate.hpp similarity index 100% rename from components/esm/weatherstate.hpp rename to components/esm3/weatherstate.hpp diff --git a/components/esmloader/esmdata.cpp b/components/esmloader/esmdata.cpp index bf2a8675d2..fd7d0f622c 100644 --- a/components/esmloader/esmdata.cpp +++ b/components/esmloader/esmdata.cpp @@ -3,13 +3,13 @@ #include "record.hpp" #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp index 842cc8003a..14478a6c85 100644 --- a/components/esmloader/load.cpp +++ b/components/esmloader/load.cpp @@ -4,16 +4,16 @@ #include "record.hpp" #include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/components/esmloader/load.hpp b/components/esmloader/load.hpp index 39d6f48b81..eae592260a 100644 --- a/components/esmloader/load.hpp +++ b/components/esmloader/load.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_ESMLOADER_LOAD_H #define OPENMW_COMPONENTS_ESMLOADER_LOAD_H -#include +#include #include #include diff --git a/components/esmloader/record.hpp b/components/esmloader/record.hpp index c076ee72c6..b88991cca1 100644 --- a/components/esmloader/record.hpp +++ b/components/esmloader/record.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_ESMLOADER_RECORD_H #define OPENMW_COMPONENTS_ESMLOADER_RECORD_H -#include +#include #include #include diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 107255a7af..a2ac625c6c 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -6,8 +6,8 @@ #include -#include -#include +#include +#include namespace VFS { diff --git a/components/lua/configuration.hpp b/components/lua/configuration.hpp index 32eddf399c..5baa4f24cf 100644 --- a/components/lua/configuration.hpp +++ b/components/lua/configuration.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include "components/esm/luascripts.hpp" namespace LuaUtil { diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index 45f3504dc1..81879cbf89 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -2,7 +2,7 @@ #define OPENMW_COMPONENTS_MISC_CONVERT_H #include -#include +#include #include #include diff --git a/components/misc/coordinateconverter.hpp b/components/misc/coordinateconverter.hpp index 1906414150..42e772c16d 100644 --- a/components/misc/coordinateconverter.hpp +++ b/components/misc/coordinateconverter.hpp @@ -2,9 +2,9 @@ #define OPENMW_COMPONENTS_MISC_COORDINATECONVERTER_H #include -#include -#include -#include +#include +#include +#include namespace Misc { diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 2a5a945558..07b41ac814 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include "lightmanager.hpp" diff --git a/components/sceneutil/pathgridutil.cpp b/components/sceneutil/pathgridutil.cpp index 9ebdef61b0..6bfc724d7b 100644 --- a/components/sceneutil/pathgridutil.cpp +++ b/components/sceneutil/pathgridutil.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace SceneUtil { diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 927530660e..05d212ef4f 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -5,7 +5,7 @@ #include #include "world.hpp" -#include "../esm/loadland.hpp" +#include "../esm3/loadland.hpp" #include #include From a126e29a19565d731488528aca97c712344ac739 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 22 Jan 2022 16:20:45 +0100 Subject: [PATCH 1978/2859] relative to absolute path --- components/terrain/cellborder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 05d212ef4f..342df759a5 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -5,8 +5,8 @@ #include #include "world.hpp" -#include "../esm3/loadland.hpp" +#include #include #include From cdbab2325f3ed96c3f14ad1bcd325306b7532d47 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 22 Jan 2022 23:52:08 +0100 Subject: [PATCH 1979/2859] rename esmterrain to esm3terrain --- apps/opencs/view/render/terrainstorage.cpp | 5 ----- apps/opencs/view/render/terrainstorage.hpp | 4 ++-- apps/openmw/mwrender/landmanager.hpp | 2 +- apps/openmw/mwrender/terrainstorage.hpp | 2 +- components/CMakeLists.txt | 2 +- components/{esmterrain => esm3terrain}/storage.cpp | 0 components/{esmterrain => esm3terrain}/storage.hpp | 0 7 files changed, 5 insertions(+), 10 deletions(-) rename components/{esmterrain => esm3terrain}/storage.cpp (100%) rename components/{esmterrain => esm3terrain}/storage.hpp (100%) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 035c073567..c015a6d620 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -1,10 +1,5 @@ #include "terrainstorage.hpp" -#include "../../model/world/land.hpp" -#include "../../model/world/landtexture.hpp" - -#include - namespace CSVRender { TerrainStorage::TerrainStorage(const CSMWorld::Data &data) diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 762eb80036..d9320d72f3 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "../../model/world/data.hpp" @@ -25,7 +25,7 @@ namespace CSVRender private: const CSMWorld::Data& mData; - std::array mAlteredHeight; + std::array mAlteredHeight{}; osg::ref_ptr getLand (int cellX, int cellY) override; const ESM::LandTexture* getLandTexture(int index, short plugin) override; diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index f3cc860854..e4b068eb76 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace ESM { diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 90bf42b841..edea556157 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1f70091247..9ab2c4b34f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -90,7 +90,7 @@ add_component_dir (esm3 aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings ) -add_component_dir (esmterrain +add_component_dir (esm3terrain storage ) diff --git a/components/esmterrain/storage.cpp b/components/esm3terrain/storage.cpp similarity index 100% rename from components/esmterrain/storage.cpp rename to components/esm3terrain/storage.cpp diff --git a/components/esmterrain/storage.hpp b/components/esm3terrain/storage.hpp similarity index 100% rename from components/esmterrain/storage.hpp rename to components/esm3terrain/storage.hpp From 74e7cfc02315f3de16cfc971aff49c6105d6da25 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 22 Jan 2022 22:44:02 +0100 Subject: [PATCH 1980/2859] remove unused includes: part1 remove unused imports: part2 revert one tidy we will keep for c++20 --- apps/esmtool/esmtool.cpp | 1 - apps/essimporter/importer.cpp | 1 - apps/essimporter/importscpt.cpp | 1 - apps/essimporter/importscri.cpp | 1 + apps/essimporter/main.cpp | 1 - apps/launcher/datafilespage.cpp | 1 - apps/launcher/datafilespage.hpp | 2 -- apps/launcher/maindialog.hpp | 1 - apps/mwiniimporter/importer.cpp | 3 +-- apps/navmeshtool/main.cpp | 1 - apps/navmeshtool/navmesh.cpp | 12 ------------ apps/navmeshtool/worldspacedata.cpp | 4 ---- apps/niftest/niftest.cpp | 1 - apps/opencs/model/doc/savingstages.cpp | 2 -- apps/opencs/model/tools/classcheck.cpp | 1 - apps/opencs/model/tools/factioncheck.cpp | 1 - apps/opencs/model/tools/mergestate.hpp | 2 +- apps/opencs/model/tools/spellcheck.cpp | 4 ---- apps/opencs/model/world/data.cpp | 1 - apps/opencs/model/world/idtable.cpp | 1 - apps/opencs/model/world/refidadapterimp.cpp | 2 -- apps/opencs/view/doc/filedialog.cpp | 5 ----- apps/opencs/view/render/cell.cpp | 8 -------- apps/opencs/view/render/commands.cpp | 6 ------ apps/opencs/view/render/terrainselection.cpp | 2 -- apps/opencs/view/render/terrainshapemode.cpp | 11 ----------- apps/opencs/view/render/terrainstorage.cpp | 3 +++ apps/opencs/view/render/terrainstorage.hpp | 2 +- apps/opencs/view/world/dialoguecreator.cpp | 4 ---- apps/opencs/view/world/globalcreator.cpp | 2 -- apps/opencs/view/world/table.cpp | 3 --- apps/openmw/engine.cpp | 3 --- apps/openmw/mwbase/dialoguemanager.hpp | 2 +- apps/openmw/mwbase/inputmanager.hpp | 2 +- apps/openmw/mwbase/journal.hpp | 2 +- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwbase/windowmanager.hpp | 2 +- apps/openmw/mwclass/container.cpp | 3 --- apps/openmw/mwgui/alchemywindow.cpp | 1 - apps/openmw/mwgui/bookpage.cpp | 2 -- apps/openmw/mwgui/bookpage.hpp | 2 +- apps/openmw/mwgui/formatting.cpp | 5 +---- apps/openmw/mwgui/hud.cpp | 1 - apps/openmw/mwgui/inventorywindow.cpp | 3 --- apps/openmw/mwgui/journalviewmodel.hpp | 2 +- apps/openmw/mwgui/mapwindow.hpp | 2 +- apps/openmw/mwgui/spellbuyingwindow.cpp | 3 --- apps/openmw/mwgui/spellcreationdialog.cpp | 6 ------ apps/openmw/mwgui/travelwindow.cpp | 5 +---- apps/openmw/mwgui/widgets.cpp | 6 +----- apps/openmw/mwmechanics/aifollow.hpp | 5 +---- apps/openmw/mwrender/localmap.cpp | 3 +-- apps/openmw/mwrender/renderingmanager.cpp | 3 --- apps/openmw/mwscript/globalscripts.hpp | 2 +- apps/openmw/mwsound/ffmpeg_decoder.hpp | 2 +- apps/openmw/mwsound/loudness.cpp | 2 +- apps/openmw/mwsound/openal_output.cpp | 3 +-- apps/openmw/mwworld/cellref.cpp | 2 +- apps/openmw/mwworld/class.cpp | 2 -- apps/openmw/mwworld/esmstore.cpp | 1 - apps/openmw/mwworld/globals.hpp | 2 +- apps/openmw/mwworld/player.cpp | 1 - apps/openmw/mwworld/scene.cpp | 3 --- apps/openmw/mwworld/weather.hpp | 2 +- apps/openmw/options.cpp | 1 - apps/wizard/mainwizard.cpp | 1 - apps/wizard/unshield/unshieldworker.cpp | 1 - components/misc/convert.hpp | 5 +---- components/nifbullet/bulletnifloader.cpp | 1 - components/terrain/quadtreeworld.cpp | 1 - 70 files changed, 28 insertions(+), 159 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 60509d7c87..aaa5e16be5 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 996a24b011..710c2e9ab2 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -2,7 +2,6 @@ #include -#include #include #include diff --git a/apps/essimporter/importscpt.cpp b/apps/essimporter/importscpt.cpp index 4df46ddc8d..5760d8b438 100644 --- a/apps/essimporter/importscpt.cpp +++ b/apps/essimporter/importscpt.cpp @@ -3,7 +3,6 @@ #include - namespace ESSImport { diff --git a/apps/essimporter/importscri.cpp b/apps/essimporter/importscri.cpp index 91971dde3b..40a5516147 100644 --- a/apps/essimporter/importscri.cpp +++ b/apps/essimporter/importscri.cpp @@ -2,6 +2,7 @@ #include + namespace ESSImport { diff --git a/apps/essimporter/main.cpp b/apps/essimporter/main.cpp index 9b969e35af..e81368c607 100644 --- a/apps/essimporter/main.cpp +++ b/apps/essimporter/main.cpp @@ -11,7 +11,6 @@ namespace bpo = boost::program_options; namespace bfs = boost::filesystem; - int main(int argc, char** argv) { try diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index f5efa8bbcc..c1fecf86db 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -21,7 +21,6 @@ #include #include "utils/textinputdialog.hpp" -#include "utils/profilescombobox.hpp" const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index a039237590..36b69d4f8e 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -6,8 +6,6 @@ #include #include - - #include #include diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 398ba882c4..ee81185dd5 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -5,7 +5,6 @@ #ifndef Q_MOC_RUN #include - #include #include diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 68f266ed6e..35d9701803 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -1,13 +1,12 @@ #include "importer.hpp" #include -#include #include #include #include #include -#include + namespace bfs = boost::filesystem; diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index aee040bb9d..8e46170e63 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index d59d274c21..94cac36f51 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -2,37 +2,25 @@ #include "worldspacedata.hpp" -#include #include #include #include #include #include -#include -#include #include #include #include #include -#include #include -#include -#include #include #include #include -#include - #include -#include #include #include #include -#include -#include -#include #include #include diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 227f73ad33..742e6d775d 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -1,7 +1,6 @@ #include "worldspacedata.hpp" #include -#include #include #include #include @@ -14,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -24,7 +22,6 @@ #include #include -#include #include #include @@ -32,7 +29,6 @@ #include #include #include -#include #include #include #include diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index c42df4a423..f857dbde37 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -1,7 +1,6 @@ ///Program to test .nif files both on the FileSystem and in BSA archives. #include -#include #include #include diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 5efa474f76..08bd6c81e8 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -2,8 +2,6 @@ #include -#include - #include #include "../world/infocollection.hpp" diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index aa38ed46ce..aa64805e5d 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -7,7 +7,6 @@ #include "../prefs/state.hpp" -#include "../world/universalid.hpp" CSMTools::ClassCheckStage::ClassCheckStage (const CSMWorld::IdCollection& classes) : mClasses (classes) diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index dbecea9589..2be59c2f9d 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -6,7 +6,6 @@ #include "../prefs/state.hpp" -#include "../world/universalid.hpp" CSMTools::FactionCheckStage::FactionCheckStage (const CSMWorld::IdCollection& factions) : mFactions (factions) diff --git a/apps/opencs/model/tools/mergestate.hpp b/apps/opencs/model/tools/mergestate.hpp index 96e6752e20..a100d7a2e0 100644 --- a/apps/opencs/model/tools/mergestate.hpp +++ b/apps/opencs/model/tools/mergestate.hpp @@ -1,7 +1,7 @@ #ifndef CSM_TOOLS_MERGESTATE_H #define CSM_TOOLS_MERGESTATE_H -#include +#include #include #include diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index 6cec24b705..eb2302aa70 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -1,13 +1,9 @@ #include "spellcheck.hpp" -#include -#include - #include #include "../prefs/state.hpp" -#include "../world/universalid.hpp" CSMTools::SpellCheckStage::SpellCheckStage (const CSMWorld::IdCollection& spells) : mSpells (spells) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 2018b3e8fd..12274b912f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 2e2ea41761..75213aab84 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -1,7 +1,6 @@ #include "idtable.hpp" #include -#include #include #include #include diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index d2c2a46db7..a30dcadd0b 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1,8 +1,6 @@ #include "refidadapterimp.hpp" -#include #include -#include #include #include diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 946dac047e..51745be0a7 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -1,15 +1,10 @@ #include "filedialog.hpp" -#include #include #include #include -#include #include -#include -#include #include -#include #include "components/contentselector/model/esmfile.hpp" #include "components/contentselector/view/contentselector.hpp" diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 04e52bcd1f..b6aa5c2fd7 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,23 +1,15 @@ #include "cell.hpp" -#include - #include #include -#include #include #include #include #include -#include #include #include "../../model/world/idtable.hpp" -#include "../../model/world/columns.hpp" -#include "../../model/world/data.hpp" -#include "../../model/world/refcollection.hpp" -#include "../../model/world/cellcoordinates.hpp" #include "cellwater.hpp" #include "cellborder.hpp" diff --git a/apps/opencs/view/render/commands.cpp b/apps/opencs/view/render/commands.cpp index 2820d7578d..515948489e 100644 --- a/apps/opencs/view/render/commands.cpp +++ b/apps/opencs/view/render/commands.cpp @@ -1,14 +1,8 @@ #include "commands.hpp" -#include - #include -#include -#include "editmode.hpp" -#include "terrainselection.hpp" #include "terrainshapemode.hpp" -#include "terraintexturemode.hpp" #include "worldspacewidget.hpp" CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent) diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 814b011de1..dc1886d57c 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -4,11 +4,9 @@ #include #include -#include #include -#include "../../model/world/cellcoordinates.hpp" #include "../../model/world/columnimp.hpp" #include "../../model/world/idtable.hpp" diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 9052cc5e6b..baae1807fd 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -2,15 +2,12 @@ #include #include -#include #include #include #include #include #include -#include -#include #include #include @@ -18,20 +15,12 @@ #include #include -#include "../widget/brushshapes.hpp" -#include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolshapebrush.hpp" -#include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/data.hpp" -#include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" -#include "../../model/world/land.hpp" #include "../../model/world/tablemimedata.hpp" -#include "../../model/world/universalid.hpp" #include "brushdraw.hpp" #include "commands.hpp" diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index c015a6d620..a3f1a91b62 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -1,5 +1,8 @@ #include "terrainstorage.hpp" +#include + + namespace CSVRender { TerrainStorage::TerrainStorage(const CSMWorld::Data &data) diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index d9320d72f3..dbb39a44ad 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -25,7 +25,7 @@ namespace CSVRender private: const CSMWorld::Data& mData; - std::array mAlteredHeight{}; + std::array mAlteredHeight; osg::ref_ptr getLand (int cellX, int cellY) override; const ESM::LandTexture* getLandTexture(int index, short plugin) override; diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp index 82ebee8467..9317aa95cd 100644 --- a/apps/opencs/view/world/dialoguecreator.cpp +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -2,11 +2,7 @@ #include -#include "../../model/doc/document.hpp" - -#include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" -#include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" void CSVWorld::DialogueCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const diff --git a/apps/opencs/view/world/globalcreator.cpp b/apps/opencs/view/world/globalcreator.cpp index 20a5c75cf6..6c5b75fb56 100644 --- a/apps/opencs/view/world/globalcreator.cpp +++ b/apps/opencs/view/world/globalcreator.cpp @@ -2,9 +2,7 @@ #include -#include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" -#include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" namespace CSVWorld diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 643396a057..69242fbca3 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -16,12 +16,9 @@ #include "../../model/world/commands.hpp" #include "../../model/world/infotableproxymodel.hpp" -#include "../../model/world/idtableproxymodel.hpp" #include "../../model/world/idtablebase.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexturetableproxymodel.hpp" -#include "../../model/world/record.hpp" -#include "../../model/world/columns.hpp" #include "../../model/world/commanddispatcher.hpp" #include "../../model/prefs/state.hpp" diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4ad140465b..9e2e1aedad 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,8 +1,6 @@ #include "engine.hpp" -#include #include -#include #include #include @@ -11,7 +9,6 @@ #include #include -#include #include #include diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index dc1f6f9667..4fbd958381 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include namespace Loading { diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index d475d93cd1..e22d7f00bc 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include namespace Loading { diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index cd87928903..ed07392992 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "../mwdialogue/journalentry.hpp" #include "../mwdialogue/topic.hpp" diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 484940e3e6..fb9197782b 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include "../mwmechanics/actorutil.hpp" // For MWMechanics::GreetingState diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 0d1ace3f0b..3044e13e9b 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWBASE_WINDOWMANAGER_H #define GAME_MWBASE_WINDOWMANAGER_H -#include +#include #include #include #include diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index b043b4671d..09d33af414 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -9,11 +9,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/nullaction.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/customdata.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/actionharvest.hpp" diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index fd4a21cf10..618017a7cd 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -16,7 +16,6 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" #include #include diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index acaee7de28..56ab2be8c2 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -10,8 +10,6 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" namespace MWGui { diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 512a439929..ab6ef555b0 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 156dc5b0d6..f416fbe07c 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -15,9 +15,7 @@ #include "../mwscript/interpretercontext.hpp" -namespace MWGui -{ - namespace Formatting +namespace MWGui::Formatting { /* BookTextParser */ BookTextParser::BookTextParser(const std::string & text) @@ -489,4 +487,3 @@ namespace MWGui return mPaginator.getCurrentTop(); } } -} diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index f6136791e9..4ed54c38d6 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -16,7 +16,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 0e76c2429f..80e922fb66 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -27,8 +27,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/actorutil.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "itemview.hpp" @@ -38,7 +36,6 @@ #include "countdialog.hpp" #include "tradewindow.hpp" #include "draganddrop.hpp" -#include "widgets.hpp" #include "tooltips.hpp" namespace diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 3a93721303..eda51b9300 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 3c3d278476..9f6ea1339d 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -1,7 +1,7 @@ #ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H -#include +#include #include #include diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 49870a9ddf..716fd7a0b5 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -11,10 +11,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/actorutil.hpp" namespace MWGui { diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 8c0ea865a8..398635b751 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -8,16 +8,10 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwmechanics/spells.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/spellutil.hpp" #include "tooltips.hpp" diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index a42028ee87..4ce3729033 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -11,15 +11,12 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/actorutil.hpp" - #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionteleport.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" + namespace MWGui { TravelWindow::TravelWindow() : diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index fb8521f067..6f4c5d9551 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -1,6 +1,5 @@ #include "widgets.hpp" -#include #include #include @@ -15,9 +14,7 @@ #include "controllers.hpp" -namespace MWGui -{ - namespace Widgets +namespace MWGui::Widgets { /* MWSkill */ @@ -535,4 +532,3 @@ namespace MWGui assignWidget(mBarTextWidget, "BarText"); } } -} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index b83e464fa1..1b7ab7a632 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -9,13 +9,10 @@ #include "../mwworld/ptr.hpp" -namespace ESM -{ -namespace AiSequence +namespace ESM::AiSequence { struct AiFollow; } -} namespace MWMechanics { diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 202420fee6..9a645312d5 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -1,6 +1,6 @@ #include "localmap.hpp" -#include +#include #include #include @@ -31,7 +31,6 @@ #include "../mwworld/cellstore.hpp" #include "vismask.hpp" -#include "util.hpp" namespace { diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7416ec988a..6c9ff173ae 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -24,7 +23,6 @@ #include #include -#include #include #include @@ -51,7 +49,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" #include "../mwgui/loadingscreen.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" #include "sky.hpp" diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index cd70f4e667..f0eaf88739 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "locals.hpp" diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 0a67a47580..c57740ff55 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -1,7 +1,7 @@ #ifndef GAME_SOUND_FFMPEG_DECODER_H #define GAME_SOUND_FFMPEG_DECODER_H -#include +#include #if defined(_MSC_VER) #pragma warning (push) diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index a36615ee4a..ac44d1b40e 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -1,6 +1,6 @@ #include "loudness.hpp" -#include +#include #include #include diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index b3cc81b803..649c67961d 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -6,10 +6,9 @@ #include #include #include -#include #include -#include +#include #include #include diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 2e1d64c441..69b6a94da8 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -1,6 +1,6 @@ #include "cellref.hpp" -#include +#include #include #include diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 24cd09ad37..ac78114932 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -10,7 +10,6 @@ #include "../mwworld/esmstore.hpp" #include "ptr.hpp" -#include "refdata.hpp" #include "nullaction.hpp" #include "failedaction.hpp" #include "actiontake.hpp" @@ -18,7 +17,6 @@ #include "../mwgui/tooltips.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 695c5636c0..6308c9a547 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index 5b414971f6..85cb7c39ce 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 8d0724e39e..3902a409e5 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -25,7 +25,6 @@ #include "cellstore.hpp" #include "class.hpp" -#include "esmloader.hpp" #include "ptr.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 054e26e855..7a9e742895 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -2,11 +2,9 @@ #include #include -#include #include #include -#include #include #include @@ -14,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 21b2fae9f8..b29ad9e994 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWWORLD_WEATHER_H #define GAME_MWWORLD_WEATHER_H -#include +#include #include #include diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp index bde10755ea..c78dc395d9 100644 --- a/apps/openmw/options.cpp +++ b/apps/openmw/options.cpp @@ -4,7 +4,6 @@ #include #include -#include namespace { diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 064f0813d8..7987d9b08d 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index f84658bf35..14a34e8779 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index 81879cbf89..f31f1cc08e 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -10,9 +10,7 @@ #include #include -namespace Misc -{ -namespace Convert +namespace Misc::Convert { inline osg::Vec3f makeOsgVec3f(const float* values) { @@ -73,6 +71,5 @@ namespace Convert return btTransform(makeBulletQuaternion(position), toBullet(position.asVec3())); } } -} #endif \ No newline at end of file diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 4be07525a6..ebd6f69f03 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -6,7 +6,6 @@ #include #include -#include #include diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 6dbed34331..eba69ad42d 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include From 203ee492c882349be194b13fde7e03e81b36ab4d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 23 Jan 2022 20:55:17 +0100 Subject: [PATCH 1981/2859] Remove `getRefIdRef` because it is the same as `getRefId`. --- apps/openmw/mwlua/actions.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- apps/openmw/mwlua/object.cpp | 8 ++++---- apps/openmw/mwworld/cellref.hpp | 3 --- apps/openmw/mwworld/cells.cpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 4 ++-- apps/openmw/mwworld/containerstore.cpp | 10 +++++----- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 49bb5cfcb4..dda237fb4e 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -95,7 +95,7 @@ namespace MWLua else { const std::string& recordId = std::get(item); - if (old_it != store.end() && old_it->getCellRef().getRefIdRef() == recordId) + if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) return true; // already equipped itemPtr = store.search(recordId); if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 8211c37abf..240a1b8627 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -374,7 +374,7 @@ namespace MWLua std::shared_ptr scripts; if (flag == ESM::LuaScriptCfg::sPlayer) { - assert(ptr.getCellRef().getRefIdRef() == "player"); + assert(ptr.getCellRef().getRefId() == "player"); scripts = std::make_shared(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry())); scripts->addPackage("openmw.ui", mUserInterfacePackage); scripts->addPackage("openmw.camera", mCameraPackage); diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 69206e8c37..a9f09ad95d 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -44,7 +44,7 @@ namespace MWLua bool isMarker(const MWWorld::Ptr& ptr) { - std::string_view id = ptr.getCellRef().getRefIdRef(); + std::string_view id = ptr.getCellRef().getRefId(); return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; } @@ -55,7 +55,7 @@ namespace MWLua // and can be accidentally changed. We use `ptr.getTypeDescription()` only as a fallback // for types that are not present in `luaObjectTypeInfo` (for such types result stability // is not necessary because they are not listed in OpenMW Lua documentation). - if (ptr.getCellRef().getRefIdRef() == "player") + if (ptr.getCellRef().getRefId() == "player") return "Player"; if (isMarker(ptr)) return "Marker"; @@ -64,7 +64,7 @@ namespace MWLua ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr) { - if (ptr.getCellRef().getRefIdRef() == "player") + if (ptr.getCellRef().getRefId() == "player") return ESM::LuaScriptCfg::sPlayer; if (isMarker(ptr)) return 0; @@ -82,7 +82,7 @@ namespace MWLua res.append(" ("); res.append(getLuaObjectTypeName(ptr)); res.append(", "); - res.append(ptr.getCellRef().getRefIdRef()); + res.append(ptr.getCellRef().getRefId()); res.append(")"); return res; } diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index b5e80930ed..455f2d077d 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -38,9 +38,6 @@ namespace MWWorld // Id of object being referenced const std::string& getRefId() const { return mCellRef.mRefID; } - // Reference to ID of the object being referenced - const std::string& getRefIdRef() const { return mCellRef.mRefID; } // TODO replace with getRefId - // For doors - true if this door teleports to somewhere else, false // if it should open through animation. bool getTeleport() const { return mCellRef.mTeleport; } diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index d020eace45..5f8706a740 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -36,7 +36,7 @@ namespace } bool cont = cell.second.forEach([&] (MWWorld::Ptr ptr) { - if (ptr.getCellRef().getRefIdRef() == id) + if (ptr.getCellRef().getRefId() == id) { return visitor(ptr); } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 0c871e4f58..3fbfb4d440 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -190,7 +190,7 @@ namespace { for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) - if (iter->mRef.getRefNum()==state.mRef.mRefNum && iter->mRef.getRefIdRef() == state.mRef.mRefID) + if (iter->mRef.getRefNum()==state.mRef.mRefNum && iter->mRef.getRefId() == state.mRef.mRefID) { // overwrite existing reference float oldscale = iter->mRef.getScale(); @@ -426,7 +426,7 @@ namespace MWWorld const std::string *mIdToFind; bool operator()(const PtrType& ptr) { - if (ptr.getCellRef().getRefIdRef() == *mIdToFind) + if (ptr.getCellRef().getRefId() == *mIdToFind) { mFound = ptr; return false; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 112f56abfc..18a91707be 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -183,7 +183,7 @@ int MWWorld::ContainerStore::count(const std::string &id) const { int total=0; for (const auto&& iter : *this) - if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefIdRef(), id)) + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) total += iter.getRefData().getCount(); return total; } @@ -248,7 +248,7 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const MWWorld::Class& cls1 = ptr1.getClass(); const MWWorld::Class& cls2 = ptr2.getClass(); - if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefIdRef(), ptr2.getCellRef().getRefIdRef())) + if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefId(), ptr2.getCellRef().getRefId())) return false; // If it has an enchantment, don't stack when some of the charge is already used @@ -363,7 +363,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefIdRef(), MWWorld::ContainerStore::sGoldId)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) { iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); flagAsModified(); @@ -464,7 +464,7 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefIdRef(), itemId)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) toRemove -= remove(*iter, toRemove, actor, equipReplacement, resolveFirst); flagAsModified(); @@ -739,7 +739,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) for (auto&& iter : *this) { int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1; - if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefIdRef(), id)) + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) { // Prefer the stack with the lowest remaining uses // Try to get item with zero durability only if there are no other items found From 067d71f7eb591bc6ac77f11844360e9a02799c40 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 23 Jan 2022 22:33:00 +0100 Subject: [PATCH 1982/2859] Fix heap use after free in components/lua/storage.cpp --- apps/openmw_test_suite/lua/test_storage.cpp | 2 +- components/lua/storage.cpp | 19 +++++++++++-------- components/lua/storage.hpp | 8 ++++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_storage.cpp b/apps/openmw_test_suite/lua/test_storage.cpp index fafba008a1..a1c6af6e0a 100644 --- a/apps/openmw_test_suite/lua/test_storage.cpp +++ b/apps/openmw_test_suite/lua/test_storage.cpp @@ -91,10 +91,10 @@ namespace mLua.safe_script("permanent:set('z', 4)"); LuaUtil::LuaStorage storage2(mLua); + storage2.load(tmpFile); mLua["permanent"] = storage2.getMutableSection("permanent"); mLua["temporary"] = storage2.getMutableSection("temporary"); - storage2.load(tmpFile); EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); EXPECT_TRUE(get(mLua, "permanent:get('z') == nil")); EXPECT_TRUE(get(mLua, "temporary:get('y') == nil")); diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index def38fdd67..91a339cb03 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -121,7 +121,10 @@ namespace LuaUtil while (it != mData.end()) { if (!it->second->mPermanent) + { + it->second->mValues.clear(); it = mData.erase(it); + } else ++it; } @@ -129,7 +132,7 @@ namespace LuaUtil void LuaStorage::load(const std::string& path) { - mData.clear(); + assert(mData.empty()); // Shouldn't be used before loading try { Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << std::filesystem::file_size(path) << " bytes)"; @@ -138,7 +141,7 @@ namespace LuaUtil sol::table data = deserialize(mLua, serializedData); for (const auto& [sectionName, sectionTable] : data) { - Section* section = getSection(sectionName.as()); + const std::shared_ptr
& section = getSection(sectionName.as()); for (const auto& [key, value] : sol::table(sectionTable)) section->set(key.as(), value); } @@ -164,26 +167,26 @@ namespace LuaUtil fout.close(); } - LuaStorage::Section* LuaStorage::getSection(std::string_view sectionName) + const std::shared_ptr& LuaStorage::getSection(std::string_view sectionName) { auto it = mData.find(sectionName); if (it != mData.end()) - return it->second.get(); - auto section = std::make_unique
(this, std::string(sectionName)); + return it->second; + auto section = std::make_shared
(this, std::string(sectionName)); sectionName = section->mSectionName; auto [newIt, _] = mData.emplace(sectionName, std::move(section)); - return newIt->second.get(); + return newIt->second; } sol::object LuaStorage::getReadOnlySection(std::string_view sectionName) { - Section* section = getSection(sectionName); + const std::shared_ptr
& section = getSection(sectionName); return sol::make_object(mLua, SectionReadOnlyView{section, section->mChangeCounter}); } sol::object LuaStorage::getMutableSection(std::string_view sectionName) { - Section* section = getSection(sectionName); + const std::shared_ptr
& section = getSection(sectionName); return sol::make_object(mLua, SectionMutableView{section, section->mChangeCounter}); } diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index e847604aeb..c23e417f0f 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -60,19 +60,19 @@ namespace LuaUtil }; struct SectionMutableView { - Section* mSection = nullptr; + std::shared_ptr
mSection = nullptr; int64_t mLastCheck = 0; }; struct SectionReadOnlyView { - Section* mSection = nullptr; + std::shared_ptr
mSection = nullptr; int64_t mLastCheck = 0; }; - Section* getSection(std::string_view sectionName); + const std::shared_ptr
& getSection(std::string_view sectionName); lua_State* mLua; - std::map> mData; + std::map> mData; std::optional mListener; }; From 40a228026104b9d1e3c4fb58c0123d1cc24d6cdd Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 24 Jan 2022 16:26:24 +0100 Subject: [PATCH 1983/2859] Update documentation regarding gyroscope --- docs/source/reference/modding/settings/input.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/source/reference/modding/settings/input.rst b/docs/source/reference/modding/settings/input.rst index d04c267a76..7aedf1bc21 100644 --- a/docs/source/reference/modding/settings/input.rst +++ b/docs/source/reference/modding/settings/input.rst @@ -158,6 +158,11 @@ Enable the support of camera rotation based on the information supplied from the This setting can only be configured by editing the settings configuration file. +Built-in (e. g. in a phone or tablet) and controller gyroscopes are supported. If both are present, controller gyroscope takes priority. + +Note: controller gyroscopes are only supported when OpenMW is built with SDL 2.0.14 or higher, +and were tested only on Windows. + gyro horizontal axis -------------------- @@ -190,8 +195,8 @@ gyro input threshold -------------------- :Type: floating point -:Range: > 0 -:Default: 0.01 +:Range: >=0 +:Default: 0.0 This setting determines the minimum value of the rotation that will be accepted. It allows to avoid crosshair oscillation due to gyroscope "noise". @@ -209,6 +214,8 @@ This setting controls the overall gyroscope horizontal sensitivity. The smaller this sensitivity is, the less visible effect the device rotation will have on the horizontal camera rotation, and vice versa. +Value of X means that rotating the device by 1 degree will cause the player to rotate by X degrees. + This setting can only be configured by editing the settings configuration file. gyro vertical sensitivity @@ -222,4 +229,6 @@ This setting controls the overall gyroscope vertical sensitivity. The smaller this sensitivity is, the less visible effect the device rotation will have on the vertical camera rotation, and vice versa. +Value of X means that rotating the device by 1 degree will cause the player to rotate by X degrees. + This setting can only be configured by editing the settings configuration file. From 7ec7c57879b8d5384c69879b55074abc075f1534 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 23 Jan 2022 19:24:36 +0100 Subject: [PATCH 1984/2859] Remove unnecessary loops from path This prevents actors going back when a new shortest path includes a point behind them where they were right before. Such situation can happen when path includes off mesh connection. Resulting cost of such path can be lower than the real one because off mesh connections are straight lines and walking surface usually is not a plane but a surface. Skip to path point where distance from current position to the line between previous and this point is less than point tolerance. Which means actor is standing very close to the edge between those points. Additionally check by navmesh raycasting to make sure there is actually a valid path. --- apps/openmw/mwmechanics/pathfinding.cpp | 23 +++++++++++++++++++---- components/misc/math.hpp | 16 ++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 components/misc/math.hpp diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index e727d8585d..1cc4526193 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -315,12 +316,13 @@ namespace MWMechanics while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance) mPath.pop_front(); + const IsValidShortcut isValidShortcut { + MWBase::Environment::get().getWorld()->getNavigator(), + halfExtents, flags + }; + if (shortenIfAlmostStraight) { - const IsValidShortcut isValidShortcut { - MWBase::Environment::get().getWorld()->getNavigator(), - halfExtents, flags - }; while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance) && isValidShortcut(mPath[0], mPath[2])) mPath.erase(mPath.begin() + 1); @@ -329,6 +331,19 @@ namespace MWMechanics mPath.pop_front(); } + if (mPath.size() > 1) + { + std::size_t begin = 0; + for (std::size_t i = 1; i < mPath.size(); ++i) + { + const float sqrDistance = Misc::getVectorToLine(position, mPath[i - 1], mPath[i]).length2(); + if (sqrDistance < pointTolerance * pointTolerance && isValidShortcut(position, mPath[i])) + begin = i; + } + for (std::size_t i = 0; i < begin; ++i) + mPath.pop_front(); + } + if (mPath.size() == 1) { float distSqr; diff --git a/components/misc/math.hpp b/components/misc/math.hpp new file mode 100644 index 0000000000..27e29675f2 --- /dev/null +++ b/components/misc/math.hpp @@ -0,0 +1,16 @@ +#ifndef OPENMW_COMPONENTS_MISC_MATH_H +#define OPENMW_COMPONENTS_MISC_MATH_H + +#include + +namespace Misc +{ + inline osg::Vec3f getVectorToLine(const osg::Vec3f& position, const osg::Vec3f& a, const osg::Vec3f& b) + { + osg::Vec3f direction = b - a; + direction.normalize(); + return (position - a) ^ direction; + } +} + +#endif From 12df2deb7034772e09da2a7b7a1c0d85c28587ce Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 24 Jan 2022 22:46:33 +0100 Subject: [PATCH 1985/2859] Remove usage of deprecated std::iterator /home/elsid/dev/openmw/apps/openmw/mwlua/../mwbase/../mwworld/containerstore.hpp:264:23: warning: 'iterator' is deprecated [-Wdeprecated-declarations] : public std::iterator ^ /home/elsid/dev/openmw/apps/openmw/mwlua/../mwworld/inventorystore.hpp:79:36: note: in instantiation of template class 'MWWorld::ContainerStoreIteratorBase' requested here ContainerStoreIterator mSelectedEnchantItem; ^ /usr/bin/../include/c++/v1/__iterator/iterator.h:27:29: note: 'iterator' has been explicitly marked deprecated here struct _LIBCPP_TEMPLATE_VIS _LIBCPP_DEPRECATED_IN_CXX17 iterator ^ /usr/bin/../include/c++/v1/__config:1016:39: note: expanded from macro '_LIBCPP_DEPRECATED_IN_CXX17' ^ /usr/bin/../include/c++/v1/__config:993:48: note: expanded from macro '_LIBCPP_DEPRECATED' ^ --- apps/openmw/mwworld/containerstore.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index f18a595466..1ef53c3e99 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -258,10 +258,8 @@ namespace MWWorld friend class MWClass::Container; }; - template class ContainerStoreIteratorBase - : public std::iterator { template struct IsConvertible @@ -362,6 +360,12 @@ namespace MWWorld /// \return reached the end? public: + using iterator_category = std::forward_iterator_tag; + using value_type = PtrType; + using difference_type = std::ptrdiff_t; + using pointer = PtrType*; + using reference = PtrType&; + template ContainerStoreIteratorBase (const ContainerStoreIteratorBase& other) { From ecc654a36939d8627f21b228c6ddbdf7b09464b8 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 25 Jan 2022 14:06:53 +0000 Subject: [PATCH 1986/2859] Limit and filter navmesh input (#5858) --- apps/navmeshtool/navmesh.cpp | 39 ++++++---- apps/openmw/mwworld/scene.cpp | 7 +- .../detournavigator/gettilespositions.cpp | 15 ++-- .../tilecachedrecastmeshmanager.cpp | 8 ++ components/CMakeLists.txt | 1 + .../detournavigator/gettilespositions.cpp | 66 ++++++++++++++++ .../detournavigator/gettilespositions.hpp | 77 ++++++------------- components/detournavigator/navigatorimpl.cpp | 1 + components/detournavigator/navmeshmanager.cpp | 18 ++++- .../detournavigator/recastmeshbuilder.cpp | 12 +++ components/detournavigator/settingsutils.hpp | 5 ++ .../tilecachedrecastmeshmanager.cpp | 22 +++++- .../tilecachedrecastmeshmanager.hpp | 7 +- 13 files changed, 196 insertions(+), 82 deletions(-) create mode 100644 components/detournavigator/gettilespositions.cpp diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 4187197947..100ddaf22a 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -35,6 +35,7 @@ #include #include #include +#include namespace NavMeshTool { @@ -180,26 +181,36 @@ namespace NavMeshTool SceneUtil::WorkQueue workQueue(threadsNumber); auto navMeshTileConsumer = std::make_shared(std::move(db)); std::size_t tiles = 0; + std::mt19937_64 random; for (const std::unique_ptr& input : data.mNavMeshInputs) { + std::vector worldspaceTiles; + DetourNavigator::getTilesPositions( - Misc::Convert::toOsg(input->mAabb.m_min), Misc::Convert::toOsg(input->mAabb.m_max), settings.mRecast, - [&] (const TilePosition& tilePosition) - { - workQueue.addWorkItem(new GenerateNavMeshTile( - input->mWorldspace, - tilePosition, - RecastMeshProvider(input->mTileCachedRecastMeshManager), - agentHalfExtents, - settings, - navMeshTileConsumer - )); - - ++tiles; - }); + DetourNavigator::makeTilesPositionsRange( + Misc::Convert::toOsg(input->mAabb.m_min), + Misc::Convert::toOsg(input->mAabb.m_max), + settings.mRecast + ), + [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); } + ); + + tiles += worldspaceTiles.size(); navMeshTileConsumer->mExpected = tiles; + + std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random); + + for (const TilePosition& tilePosition : worldspaceTiles) + workQueue.addWorkItem(new GenerateNavMeshTile( + input->mWorldspace, + tilePosition, + RecastMeshProvider(input->mTileCachedRecastMeshManager), + agentHalfExtents, + settings, + navMeshTileConsumer + )); } navMeshTileConsumer->wait(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 054e26e855..fc02f205ff 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -511,8 +511,7 @@ namespace MWWorld if (mCurrentCell == nullptr) return; - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3()); + mNavigator.updatePlayerPosition(pos); if (!mCurrentCell->isExterior()) return; @@ -823,6 +822,8 @@ namespace MWWorld loadingListener->setProgressRange(cell->count()); + mNavigator.updatePlayerPosition(position.asVec3()); + // Load cell. mPagedRefs.clear(); loadCell(cell, loadingListener, changeEvent); @@ -856,6 +857,8 @@ namespace MWWorld if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); + mNavigator.updatePlayerPosition(position.asVec3()); + changeCellGrid(position.asVec3(), x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp index ced33a99f0..8121c19205 100644 --- a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -36,35 +37,35 @@ namespace TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile) { - getTilesPositions(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles) { - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles) { - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates) { - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates) { - getTilesPositions(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), @@ -78,7 +79,7 @@ namespace { mSettings.mBorderSize = 1; - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), @@ -97,7 +98,7 @@ namespace { mSettings.mRecastScaleFactor = 0.5; - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index c44ebc5155..631e4105ba 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -90,6 +90,10 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); + TileBounds bounds; + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); @@ -137,6 +141,10 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); + TileBounds bounds; + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 89b27b0d94..ae06f3f2b0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -208,6 +208,7 @@ add_component_dir(detournavigator serialization navmeshdbutils recast + gettilespositions ) add_component_dir(loadinglistener diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp new file mode 100644 index 0000000000..e74a22e5ba --- /dev/null +++ b/components/detournavigator/gettilespositions.cpp @@ -0,0 +1,66 @@ +#include "gettilespositions.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" +#include "tileposition.hpp" +#include "tilebounds.hpp" + +#include + +#include + +namespace DetourNavigator +{ + TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, + const RecastSettings& settings) + { + osg::Vec3f min = toNavMeshCoordinates(settings, aabbMin); + osg::Vec3f max = toNavMeshCoordinates(settings, aabbMax); + + const float border = getBorderSize(settings); + min -= osg::Vec3f(border, border, border); + max += osg::Vec3f(border, border, border); + + TilePosition minTile = getTilePosition(settings, min); + TilePosition maxTile = getTilePosition(settings, max); + + if (minTile.x() > maxTile.x()) + std::swap(minTile.x(), maxTile.x()); + + if (minTile.y() > maxTile.y()) + std::swap(minTile.y(), maxTile.y()); + + return {minTile, maxTile}; + } + + TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, + const TileBounds& bounds, const RecastSettings& settings) + { + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(transform, aabbMin, aabbMax); + aabbMin.setX(std::max(aabbMin.x(), bounds.mMin.x())); + aabbMin.setY(std::max(aabbMin.y(), bounds.mMin.y())); + aabbMax.setX(std::min(aabbMax.x(), bounds.mMax.x())); + aabbMax.setY(std::min(aabbMax.y(), bounds.mMax.y())); + return makeTilesPositionsRange(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings); + } + + TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, + const RecastSettings& settings) + { + using Misc::Convert::toOsg; + + const int halfCellSize = cellSize / 2; + const btTransform transform(btMatrix3x3::getIdentity(), shift); + btVector3 aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); + btVector3 aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); + + aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); + aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); + + aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); + aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); + + return makeTilesPositionsRange(toOsg(aabbMin), toOsg(aabbMax), settings); + } +} diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 707db0b512..9d3df7ef65 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -1,72 +1,43 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H -#include "settings.hpp" -#include "settingsutils.hpp" +#include "tilebounds.hpp" #include "tileposition.hpp" -#include +class btVector3; +class btTransform; +class btCollisionShape; -#include - -#include +namespace osg +{ + class Vec3f; +} namespace DetourNavigator { - template - void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, - const RecastSettings& settings, Callback&& callback) - { - auto min = toNavMeshCoordinates(settings, aabbMin); - auto max = toNavMeshCoordinates(settings, aabbMax); + struct RecastSettings; - const auto border = getBorderSize(settings); - min -= osg::Vec3f(border, border, border); - max += osg::Vec3f(border, border, border); + struct TilesPositionsRange + { + TilePosition mMin; + TilePosition mMax; + }; - auto minTile = getTilePosition(settings, min); - auto maxTile = getTilePosition(settings, max); + TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, + const osg::Vec3f& aabbMax, const RecastSettings& settings); - if (minTile.x() > maxTile.x()) - std::swap(minTile.x(), maxTile.x()); + TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, + const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings); - if (minTile.y() > maxTile.y()) - std::swap(minTile.y(), maxTile.y()); - - for (int tileX = minTile.x(); tileX <= maxTile.x(); ++tileX) - for (int tileY = minTile.y(); tileY <= maxTile.y(); ++tileY) - callback(TilePosition {tileX, tileY}); - } + TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, + const RecastSettings& settings); template - void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, - const RecastSettings& settings, Callback&& callback) + void getTilesPositions(const TilesPositionsRange& range, Callback&& callback) { - btVector3 aabbMin; - btVector3 aabbMax; - shape.getAabb(transform, aabbMin, aabbMax); - - getTilesPositions(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings, std::forward(callback)); - } - - template - void getTilesPositions(const int cellSize, const btVector3& shift, - const RecastSettings& settings, Callback&& callback) - { - using Misc::Convert::toOsg; - - const auto halfCellSize = cellSize / 2; - const btTransform transform(btMatrix3x3::getIdentity(), shift); - auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); - auto aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); - - aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); - aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); - - aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); - aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); - - getTilesPositions(toOsg(aabbMin), toOsg(aabbMax), settings, std::forward(callback)); + for (int tileX = range.mMin.x(); tileX <= range.mMax.x(); ++tileX) + for (int tileY = range.mMin.y(); tileY <= range.mMax.y(); ++tileY) + callback(TilePosition {tileX, tileY}); } } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 85d86e6b2b..82a156bb97 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace DetourNavigator { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 399af8a6a9..e8da5a0d61 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -9,6 +9,7 @@ #include #include +#include #include @@ -41,6 +42,18 @@ namespace namespace DetourNavigator { + namespace + { + TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles) + { + const float radius = fromNavMeshCoordinates(settings, std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1) * getTileSize(settings)); + TileBounds result; + result.mMin = center - osg::Vec2f(radius, radius); + result.mMax = center + osg::Vec2f(radius, radius); + return result; + } + } + NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(settings.mRecast) @@ -204,6 +217,7 @@ namespace DetourNavigator } } const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); + mRecastMeshManager.setBounds(makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), maxTiles)); mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) { if (tilesToPost.count(tile)) @@ -262,7 +276,7 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(shape, transform, mSettings.mRecast, + getTilesPositions(makeTilesPositionsRange(shape, transform, mRecastMeshManager.getBounds(), mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } @@ -272,7 +286,7 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) return; - getTilesPositions(cellSize, shift, mSettings.mRecast, + getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 0f7552aa77..08e7002cf8 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace DetourNavigator { @@ -40,6 +41,16 @@ namespace DetourNavigator { return static_cast(cellSize) / (dataSize - 1); } + + bool isNan(const RecastMeshTriangle& triangle) + { + for (std::size_t i = 0; i < 3; ++i) + if (std::isnan(triangle.mVertices[i].x()) + || std::isnan(triangle.mVertices[i].y()) + || std::isnan(triangle.mVertices[i].z())) + return true; + return false; + } } Mesh makeMesh(std::vector&& triangles, const osg::Vec3f& shift) @@ -264,6 +275,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) && { + mTriangles.erase(std::remove_if(mTriangles.begin(), mTriangles.end(), isNan), mTriangles.end()); std::sort(mTriangles.begin(), mTriangles.end()); std::sort(mWater.begin(), mWater.end()); Mesh mesh = makeMesh(std::move(mTriangles)); diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 285920e5a0..e38da2d0e1 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -37,6 +37,11 @@ namespace DetourNavigator }; } + inline float fromNavMeshCoordinates(const RecastSettings& settings, float value) + { + return value / settings.mRecastScaleFactor; + } + inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index bf3df92d6e..b3750ad171 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -4,6 +4,7 @@ #include "settingsutils.hpp" #include +#include #include #include @@ -14,6 +15,18 @@ namespace DetourNavigator : mSettings(settings) {} + TileBounds TileCachedRecastMeshManager::getBounds() const + { + const std::lock_guard lock(mMutex); + return mBounds; + } + + void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds) + { + const std::lock_guard lock(mMutex); + mBounds = bounds; + } + std::string TileCachedRecastMeshManager::getWorldspace() const { const std::lock_guard lock(mMutex); @@ -35,7 +48,8 @@ namespace DetourNavigator std::vector tilesPositions; { const std::lock_guard lock(mMutex); - getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) + getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mBounds, mSettings), + [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) tilesPositions.push_back(tilePosition); @@ -90,7 +104,8 @@ namespace DetourNavigator else { const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); - getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) + getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), + [&] (const TilePosition& tilePosition) { const std::lock_guard lock(mMutex); auto tile = mTiles.find(tilePosition); @@ -148,7 +163,8 @@ namespace DetourNavigator bool result = false; - getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) + getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), + [&] (const TilePosition& tilePosition) { const std::lock_guard lock(mMutex); auto tile = mTiles.find(tilePosition); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 23171f0925..d99dc3e27e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -20,6 +20,10 @@ namespace DetourNavigator public: explicit TileCachedRecastMeshManager(const RecastSettings& settings); + TileBounds getBounds() const; + + void setBounds(const TileBounds& bounds); + std::string getWorldspace() const; void setWorldspace(std::string_view worldspace); @@ -57,7 +61,7 @@ namespace DetourNavigator changed = true; } }; - getTilesPositions(shape.getShape(), transform, mSettings, onTilePosition); + getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mBounds, mSettings), onTilePosition); std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { @@ -109,6 +113,7 @@ namespace DetourNavigator const RecastSettings& mSettings; mutable std::mutex mMutex; + TileBounds mBounds; std::string mWorldspace; TilesMap mTiles; std::unordered_map> mObjectsTilesPositions; From 670cc9794726fbfcca7cd06ccd8ecb771a8193f3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 25 Jan 2022 15:24:49 +0100 Subject: [PATCH 1987/2859] Add #5858 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c45d15e6d..df20ef97e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Bug #5788: Texture editing parses the selected indexes wrongly Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention Bug #5842: GetDisposition adds temporary disposition change from different actors + Bug #5858: Animated model freezes the game Bug #5863: GetEffect should return true after the player has teleported Bug #5913: Failed assertion during Ritual of Trees quest Bug #5928: Glow in the Dahrk functionality used without mod installed From 12ce82980ce7db03fbc9eb9bd35b895416eb6b05 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 25 Jan 2022 16:33:53 +0100 Subject: [PATCH 1988/2859] Revert "Limit and filter navmesh input (#5858)" This reverts commit ecc654a36939d8627f21b228c6ddbdf7b09464b8. --- apps/navmeshtool/navmesh.cpp | 39 ++++------ apps/openmw/mwworld/scene.cpp | 7 +- .../detournavigator/gettilespositions.cpp | 15 ++-- .../tilecachedrecastmeshmanager.cpp | 8 -- components/CMakeLists.txt | 1 - .../detournavigator/gettilespositions.cpp | 66 ---------------- .../detournavigator/gettilespositions.hpp | 77 +++++++++++++------ components/detournavigator/navigatorimpl.cpp | 1 - components/detournavigator/navmeshmanager.cpp | 18 +---- .../detournavigator/recastmeshbuilder.cpp | 12 --- components/detournavigator/settingsutils.hpp | 5 -- .../tilecachedrecastmeshmanager.cpp | 22 +----- .../tilecachedrecastmeshmanager.hpp | 7 +- 13 files changed, 82 insertions(+), 196 deletions(-) delete mode 100644 components/detournavigator/gettilespositions.cpp diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 100ddaf22a..4187197947 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -35,7 +35,6 @@ #include #include #include -#include namespace NavMeshTool { @@ -181,36 +180,26 @@ namespace NavMeshTool SceneUtil::WorkQueue workQueue(threadsNumber); auto navMeshTileConsumer = std::make_shared(std::move(db)); std::size_t tiles = 0; - std::mt19937_64 random; for (const std::unique_ptr& input : data.mNavMeshInputs) { - std::vector worldspaceTiles; - DetourNavigator::getTilesPositions( - DetourNavigator::makeTilesPositionsRange( - Misc::Convert::toOsg(input->mAabb.m_min), - Misc::Convert::toOsg(input->mAabb.m_max), - settings.mRecast - ), - [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); } - ); - - tiles += worldspaceTiles.size(); + Misc::Convert::toOsg(input->mAabb.m_min), Misc::Convert::toOsg(input->mAabb.m_max), settings.mRecast, + [&] (const TilePosition& tilePosition) + { + workQueue.addWorkItem(new GenerateNavMeshTile( + input->mWorldspace, + tilePosition, + RecastMeshProvider(input->mTileCachedRecastMeshManager), + agentHalfExtents, + settings, + navMeshTileConsumer + )); + + ++tiles; + }); navMeshTileConsumer->mExpected = tiles; - - std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random); - - for (const TilePosition& tilePosition : worldspaceTiles) - workQueue.addWorkItem(new GenerateNavMeshTile( - input->mWorldspace, - tilePosition, - RecastMeshProvider(input->mTileCachedRecastMeshManager), - agentHalfExtents, - settings, - navMeshTileConsumer - )); } navMeshTileConsumer->wait(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fc02f205ff..054e26e855 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -511,7 +511,8 @@ namespace MWWorld if (mCurrentCell == nullptr) return; - mNavigator.updatePlayerPosition(pos); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3()); if (!mCurrentCell->isExterior()) return; @@ -822,8 +823,6 @@ namespace MWWorld loadingListener->setProgressRange(cell->count()); - mNavigator.updatePlayerPosition(position.asVec3()); - // Load cell. mPagedRefs.clear(); loadCell(cell, loadingListener, changeEvent); @@ -857,8 +856,6 @@ namespace MWWorld if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); - mNavigator.updatePlayerPosition(position.asVec3()); - changeCellGrid(position.asVec3(), x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp index 8121c19205..ced33a99f0 100644 --- a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -37,35 +36,35 @@ namespace TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect); + getTilesPositions(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings), mCollect); + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings), mCollect); + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings), mCollect); + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect); + getTilesPositions(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), @@ -79,7 +78,7 @@ namespace { mSettings.mBorderSize = 1; - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings), mCollect); + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), @@ -98,7 +97,7 @@ namespace { mSettings.mRecastScaleFactor = 0.5; - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings), mCollect); + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings, mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 631e4105ba..c44ebc5155 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -90,10 +90,6 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - TileBounds bounds; - bounds.mMin = osg::Vec2f(-1000, -1000); - bounds.mMax = osg::Vec2f(1000, 1000); - manager.setBounds(bounds); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); @@ -141,10 +137,6 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); - TileBounds bounds; - bounds.mMin = osg::Vec2f(-1000, -1000); - bounds.mMax = osg::Vec2f(1000, 1000); - manager.setBounds(bounds); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index ae06f3f2b0..89b27b0d94 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -208,7 +208,6 @@ add_component_dir(detournavigator serialization navmeshdbutils recast - gettilespositions ) add_component_dir(loadinglistener diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp deleted file mode 100644 index e74a22e5ba..0000000000 --- a/components/detournavigator/gettilespositions.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "gettilespositions.hpp" -#include "settings.hpp" -#include "settingsutils.hpp" -#include "tileposition.hpp" -#include "tilebounds.hpp" - -#include - -#include - -namespace DetourNavigator -{ - TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, - const RecastSettings& settings) - { - osg::Vec3f min = toNavMeshCoordinates(settings, aabbMin); - osg::Vec3f max = toNavMeshCoordinates(settings, aabbMax); - - const float border = getBorderSize(settings); - min -= osg::Vec3f(border, border, border); - max += osg::Vec3f(border, border, border); - - TilePosition minTile = getTilePosition(settings, min); - TilePosition maxTile = getTilePosition(settings, max); - - if (minTile.x() > maxTile.x()) - std::swap(minTile.x(), maxTile.x()); - - if (minTile.y() > maxTile.y()) - std::swap(minTile.y(), maxTile.y()); - - return {minTile, maxTile}; - } - - TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, - const TileBounds& bounds, const RecastSettings& settings) - { - btVector3 aabbMin; - btVector3 aabbMax; - shape.getAabb(transform, aabbMin, aabbMax); - aabbMin.setX(std::max(aabbMin.x(), bounds.mMin.x())); - aabbMin.setY(std::max(aabbMin.y(), bounds.mMin.y())); - aabbMax.setX(std::min(aabbMax.x(), bounds.mMax.x())); - aabbMax.setY(std::min(aabbMax.y(), bounds.mMax.y())); - return makeTilesPositionsRange(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings); - } - - TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, - const RecastSettings& settings) - { - using Misc::Convert::toOsg; - - const int halfCellSize = cellSize / 2; - const btTransform transform(btMatrix3x3::getIdentity(), shift); - btVector3 aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); - btVector3 aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); - - aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); - aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); - - aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); - aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); - - return makeTilesPositionsRange(toOsg(aabbMin), toOsg(aabbMax), settings); - } -} diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 9d3df7ef65..707db0b512 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -1,43 +1,72 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H -#include "tilebounds.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" #include "tileposition.hpp" -class btVector3; -class btTransform; -class btCollisionShape; +#include -namespace osg -{ - class Vec3f; -} +#include + +#include namespace DetourNavigator { - struct RecastSettings; - - struct TilesPositionsRange + template + void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, + const RecastSettings& settings, Callback&& callback) { - TilePosition mMin; - TilePosition mMax; - }; + auto min = toNavMeshCoordinates(settings, aabbMin); + auto max = toNavMeshCoordinates(settings, aabbMax); - TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, - const osg::Vec3f& aabbMax, const RecastSettings& settings); + const auto border = getBorderSize(settings); + min -= osg::Vec3f(border, border, border); + max += osg::Vec3f(border, border, border); - TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, - const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings); + auto minTile = getTilePosition(settings, min); + auto maxTile = getTilePosition(settings, max); - TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, - const RecastSettings& settings); + if (minTile.x() > maxTile.x()) + std::swap(minTile.x(), maxTile.x()); + + if (minTile.y() > maxTile.y()) + std::swap(minTile.y(), maxTile.y()); + + for (int tileX = minTile.x(); tileX <= maxTile.x(); ++tileX) + for (int tileY = minTile.y(); tileY <= maxTile.y(); ++tileY) + callback(TilePosition {tileX, tileY}); + } template - void getTilesPositions(const TilesPositionsRange& range, Callback&& callback) + void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, + const RecastSettings& settings, Callback&& callback) { - for (int tileX = range.mMin.x(); tileX <= range.mMax.x(); ++tileX) - for (int tileY = range.mMin.y(); tileY <= range.mMax.y(); ++tileY) - callback(TilePosition {tileX, tileY}); + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(transform, aabbMin, aabbMax); + + getTilesPositions(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings, std::forward(callback)); + } + + template + void getTilesPositions(const int cellSize, const btVector3& shift, + const RecastSettings& settings, Callback&& callback) + { + using Misc::Convert::toOsg; + + const auto halfCellSize = cellSize / 2; + const btTransform transform(btMatrix3x3::getIdentity(), shift); + auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); + auto aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); + + aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); + aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); + + aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); + aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); + + getTilesPositions(toOsg(aabbMin), toOsg(aabbMax), settings, std::forward(callback)); } } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 82a156bb97..85d86e6b2b 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -5,7 +5,6 @@ #include #include #include -#include namespace DetourNavigator { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index e8da5a0d61..399af8a6a9 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -9,7 +9,6 @@ #include #include -#include #include @@ -42,18 +41,6 @@ namespace namespace DetourNavigator { - namespace - { - TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles) - { - const float radius = fromNavMeshCoordinates(settings, std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1) * getTileSize(settings)); - TileBounds result; - result.mMin = center - osg::Vec2f(radius, radius); - result.mMax = center + osg::Vec2f(radius, radius); - return result; - } - } - NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(settings.mRecast) @@ -217,7 +204,6 @@ namespace DetourNavigator } } const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); - mRecastMeshManager.setBounds(makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), maxTiles)); mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) { if (tilesToPost.count(tile)) @@ -276,7 +262,7 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(makeTilesPositionsRange(shape, transform, mRecastMeshManager.getBounds(), mSettings.mRecast), + getTilesPositions(shape, transform, mSettings.mRecast, [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } @@ -286,7 +272,7 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) return; - getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings.mRecast), + getTilesPositions(cellSize, shift, mSettings.mRecast, [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 08e7002cf8..0f7552aa77 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -20,7 +20,6 @@ #include #include #include -#include namespace DetourNavigator { @@ -41,16 +40,6 @@ namespace DetourNavigator { return static_cast(cellSize) / (dataSize - 1); } - - bool isNan(const RecastMeshTriangle& triangle) - { - for (std::size_t i = 0; i < 3; ++i) - if (std::isnan(triangle.mVertices[i].x()) - || std::isnan(triangle.mVertices[i].y()) - || std::isnan(triangle.mVertices[i].z())) - return true; - return false; - } } Mesh makeMesh(std::vector&& triangles, const osg::Vec3f& shift) @@ -275,7 +264,6 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) && { - mTriangles.erase(std::remove_if(mTriangles.begin(), mTriangles.end(), isNan), mTriangles.end()); std::sort(mTriangles.begin(), mTriangles.end()); std::sort(mWater.begin(), mWater.end()); Mesh mesh = makeMesh(std::move(mTriangles)); diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index e38da2d0e1..285920e5a0 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -37,11 +37,6 @@ namespace DetourNavigator }; } - inline float fromNavMeshCoordinates(const RecastSettings& settings, float value) - { - return value / settings.mRecastScaleFactor; - } - inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index b3750ad171..bf3df92d6e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -4,7 +4,6 @@ #include "settingsutils.hpp" #include -#include #include #include @@ -15,18 +14,6 @@ namespace DetourNavigator : mSettings(settings) {} - TileBounds TileCachedRecastMeshManager::getBounds() const - { - const std::lock_guard lock(mMutex); - return mBounds; - } - - void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds) - { - const std::lock_guard lock(mMutex); - mBounds = bounds; - } - std::string TileCachedRecastMeshManager::getWorldspace() const { const std::lock_guard lock(mMutex); @@ -48,8 +35,7 @@ namespace DetourNavigator std::vector tilesPositions; { const std::lock_guard lock(mMutex); - getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mBounds, mSettings), - [&] (const TilePosition& tilePosition) + getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) tilesPositions.push_back(tilePosition); @@ -104,8 +90,7 @@ namespace DetourNavigator else { const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); - getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), - [&] (const TilePosition& tilePosition) + getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) { const std::lock_guard lock(mMutex); auto tile = mTiles.find(tilePosition); @@ -163,8 +148,7 @@ namespace DetourNavigator bool result = false; - getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), - [&] (const TilePosition& tilePosition) + getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) { const std::lock_guard lock(mMutex); auto tile = mTiles.find(tilePosition); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index d99dc3e27e..23171f0925 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -20,10 +20,6 @@ namespace DetourNavigator public: explicit TileCachedRecastMeshManager(const RecastSettings& settings); - TileBounds getBounds() const; - - void setBounds(const TileBounds& bounds); - std::string getWorldspace() const; void setWorldspace(std::string_view worldspace); @@ -61,7 +57,7 @@ namespace DetourNavigator changed = true; } }; - getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mBounds, mSettings), onTilePosition); + getTilesPositions(shape.getShape(), transform, mSettings, onTilePosition); std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { @@ -113,7 +109,6 @@ namespace DetourNavigator const RecastSettings& mSettings; mutable std::mutex mMutex; - TileBounds mBounds; std::string mWorldspace; TilesMap mTiles; std::unordered_map> mObjectsTilesPositions; From d1d29a24524d062bd36a944b59ca86005e0ed271 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Dec 2021 00:05:50 +0100 Subject: [PATCH 1989/2859] Shuffle tile positions before adding to queue for processing --- apps/navmeshtool/navmesh.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 4187197947..36ed448e72 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -35,6 +35,7 @@ #include #include #include +#include namespace NavMeshTool { @@ -180,26 +181,31 @@ namespace NavMeshTool SceneUtil::WorkQueue workQueue(threadsNumber); auto navMeshTileConsumer = std::make_shared(std::move(db)); std::size_t tiles = 0; + std::mt19937_64 random; for (const std::unique_ptr& input : data.mNavMeshInputs) { + std::vector worldspaceTiles; + DetourNavigator::getTilesPositions( Misc::Convert::toOsg(input->mAabb.m_min), Misc::Convert::toOsg(input->mAabb.m_max), settings.mRecast, - [&] (const TilePosition& tilePosition) - { - workQueue.addWorkItem(new GenerateNavMeshTile( - input->mWorldspace, - tilePosition, - RecastMeshProvider(input->mTileCachedRecastMeshManager), - agentHalfExtents, - settings, - navMeshTileConsumer - )); - - ++tiles; - }); + [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); }); + + tiles += worldspaceTiles.size(); navMeshTileConsumer->mExpected = tiles; + + std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random); + + for (const TilePosition& tilePosition : worldspaceTiles) + workQueue.addWorkItem(new GenerateNavMeshTile( + input->mWorldspace, + tilePosition, + RecastMeshProvider(input->mTileCachedRecastMeshManager), + agentHalfExtents, + settings, + navMeshTileConsumer + )); } navMeshTileConsumer->wait(); From bba7beb0c58f259aaffd8ccd59e0ae03c1907855 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Dec 2021 00:37:32 +0100 Subject: [PATCH 1990/2859] Split tiles position range creation and iteration over --- apps/navmeshtool/navmesh.cpp | 9 ++- .../detournavigator/gettilespositions.cpp | 15 ++-- components/CMakeLists.txt | 1 + .../detournavigator/gettilespositions.cpp | 62 +++++++++++++++ .../detournavigator/gettilespositions.hpp | 76 ++++++------------- components/detournavigator/navigatorimpl.cpp | 1 + components/detournavigator/navmeshmanager.cpp | 5 +- .../tilecachedrecastmeshmanager.cpp | 10 ++- .../tilecachedrecastmeshmanager.hpp | 2 +- 9 files changed, 113 insertions(+), 68 deletions(-) create mode 100644 components/detournavigator/gettilespositions.cpp diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 36ed448e72..100ddaf22a 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -188,8 +188,13 @@ namespace NavMeshTool std::vector worldspaceTiles; DetourNavigator::getTilesPositions( - Misc::Convert::toOsg(input->mAabb.m_min), Misc::Convert::toOsg(input->mAabb.m_max), settings.mRecast, - [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); }); + DetourNavigator::makeTilesPositionsRange( + Misc::Convert::toOsg(input->mAabb.m_min), + Misc::Convert::toOsg(input->mAabb.m_max), + settings.mRecast + ), + [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); } + ); tiles += worldspaceTiles.size(); diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp index ced33a99f0..8121c19205 100644 --- a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -36,35 +37,35 @@ namespace TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile) { - getTilesPositions(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles) { - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles) { - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates) { - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates) { - getTilesPositions(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), @@ -78,7 +79,7 @@ namespace { mSettings.mBorderSize = 1; - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), @@ -97,7 +98,7 @@ namespace { mSettings.mRecastScaleFactor = 0.5; - getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings, mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 89b27b0d94..ae06f3f2b0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -208,6 +208,7 @@ add_component_dir(detournavigator serialization navmeshdbutils recast + gettilespositions ) add_component_dir(loadinglistener diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp new file mode 100644 index 0000000000..d427eb3e12 --- /dev/null +++ b/components/detournavigator/gettilespositions.cpp @@ -0,0 +1,62 @@ +#include "gettilespositions.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" +#include "tileposition.hpp" + +#include + +#include + +namespace DetourNavigator +{ + TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, + const RecastSettings& settings) + { + osg::Vec3f min = toNavMeshCoordinates(settings, aabbMin); + osg::Vec3f max = toNavMeshCoordinates(settings, aabbMax); + + const float border = getBorderSize(settings); + min -= osg::Vec3f(border, border, border); + max += osg::Vec3f(border, border, border); + + TilePosition minTile = getTilePosition(settings, min); + TilePosition maxTile = getTilePosition(settings, max); + + if (minTile.x() > maxTile.x()) + std::swap(minTile.x(), maxTile.x()); + + if (minTile.y() > maxTile.y()) + std::swap(minTile.y(), maxTile.y()); + + return {minTile, maxTile}; + } + + TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, + const RecastSettings& settings) + { + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(transform, aabbMin, aabbMax); + + return makeTilesPositionsRange(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings); + } + + TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, + const RecastSettings& settings) + { + using Misc::Convert::toOsg; + + const int halfCellSize = cellSize / 2; + const btTransform transform(btMatrix3x3::getIdentity(), shift); + btVector3 aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); + btVector3 aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); + + aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); + aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); + + aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); + aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); + + return makeTilesPositionsRange(toOsg(aabbMin), toOsg(aabbMax), settings); + } +} diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 707db0b512..946f3e64f2 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -1,72 +1,42 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H -#include "settings.hpp" -#include "settingsutils.hpp" #include "tileposition.hpp" -#include +class btVector3; +class btTransform; +class btCollisionShape; -#include - -#include +namespace osg +{ + class Vec3f; +} namespace DetourNavigator { - template - void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, - const RecastSettings& settings, Callback&& callback) - { - auto min = toNavMeshCoordinates(settings, aabbMin); - auto max = toNavMeshCoordinates(settings, aabbMax); + struct RecastSettings; - const auto border = getBorderSize(settings); - min -= osg::Vec3f(border, border, border); - max += osg::Vec3f(border, border, border); + struct TilesPositionsRange + { + TilePosition mMin; + TilePosition mMax; + }; - auto minTile = getTilePosition(settings, min); - auto maxTile = getTilePosition(settings, max); + TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, + const osg::Vec3f& aabbMax, const RecastSettings& settings); - if (minTile.x() > maxTile.x()) - std::swap(minTile.x(), maxTile.x()); + TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, + const btTransform& transform, const RecastSettings& settings); - if (minTile.y() > maxTile.y()) - std::swap(minTile.y(), maxTile.y()); - - for (int tileX = minTile.x(); tileX <= maxTile.x(); ++tileX) - for (int tileY = minTile.y(); tileY <= maxTile.y(); ++tileY) - callback(TilePosition {tileX, tileY}); - } + TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, + const RecastSettings& settings); template - void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, - const RecastSettings& settings, Callback&& callback) + void getTilesPositions(const TilesPositionsRange& range, Callback&& callback) { - btVector3 aabbMin; - btVector3 aabbMax; - shape.getAabb(transform, aabbMin, aabbMax); - - getTilesPositions(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings, std::forward(callback)); - } - - template - void getTilesPositions(const int cellSize, const btVector3& shift, - const RecastSettings& settings, Callback&& callback) - { - using Misc::Convert::toOsg; - - const auto halfCellSize = cellSize / 2; - const btTransform transform(btMatrix3x3::getIdentity(), shift); - auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); - auto aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); - - aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); - aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); - - aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); - aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); - - getTilesPositions(toOsg(aabbMin), toOsg(aabbMax), settings, std::forward(callback)); + for (int tileX = range.mMin.x(); tileX <= range.mMax.x(); ++tileX) + for (int tileY = range.mMin.y(); tileY <= range.mMax.y(); ++tileY) + callback(TilePosition {tileX, tileY}); } } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 85d86e6b2b..82a156bb97 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace DetourNavigator { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 399af8a6a9..9fba4ad611 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -9,6 +9,7 @@ #include #include +#include #include @@ -262,7 +263,7 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(shape, transform, mSettings.mRecast, + getTilesPositions(makeTilesPositionsRange(shape, transform, mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } @@ -272,7 +273,7 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) return; - getTilesPositions(cellSize, shift, mSettings.mRecast, + getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index bf3df92d6e..17ba7afc39 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -4,6 +4,7 @@ #include "settingsutils.hpp" #include +#include #include #include @@ -35,7 +36,8 @@ namespace DetourNavigator std::vector tilesPositions; { const std::lock_guard lock(mMutex); - getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) + getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mSettings), + [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) tilesPositions.push_back(tilePosition); @@ -90,7 +92,8 @@ namespace DetourNavigator else { const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); - getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) + getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), + [&] (const TilePosition& tilePosition) { const std::lock_guard lock(mMutex); auto tile = mTiles.find(tilePosition); @@ -148,7 +151,8 @@ namespace DetourNavigator bool result = false; - getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) + getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), + [&] (const TilePosition& tilePosition) { const std::lock_guard lock(mMutex); auto tile = mTiles.find(tilePosition); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 23171f0925..88face24ce 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -57,7 +57,7 @@ namespace DetourNavigator changed = true; } }; - getTilesPositions(shape.getShape(), transform, mSettings, onTilePosition); + getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mSettings), onTilePosition); std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { From a9ae263acd7031c6d5f16a91233cc250890c3118 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 23 Jan 2022 16:44:46 +0100 Subject: [PATCH 1991/2859] Reuse existing player position --- apps/openmw/mwworld/scene.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 054e26e855..5b74fe63f4 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -511,8 +511,7 @@ namespace MWWorld if (mCurrentCell == nullptr) return; - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3()); + mNavigator.updatePlayerPosition(pos); if (!mCurrentCell->isExterior()) return; From b0ef20c30348e52c38464777db9a33ecf693f877 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 23 Jan 2022 14:47:20 +0100 Subject: [PATCH 1992/2859] Cull navmesh objects by scene bounds If object is too big iteration over all tiles covering it can take too much time. Limit bounds to a square around a player position to cover only tiles that will be present in navmesh based on max tiles number option. --- apps/openmw/mwworld/scene.cpp | 4 ++++ .../tilecachedrecastmeshmanager.cpp | 8 ++++++++ .../detournavigator/gettilespositions.cpp | 8 ++++++-- .../detournavigator/gettilespositions.hpp | 3 ++- components/detournavigator/navmeshmanager.cpp | 15 ++++++++++++++- components/detournavigator/settingsutils.hpp | 5 +++++ .../tilecachedrecastmeshmanager.cpp | 17 ++++++++++++++++- .../tilecachedrecastmeshmanager.hpp | 7 ++++++- 8 files changed, 61 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 5b74fe63f4..fc02f205ff 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -822,6 +822,8 @@ namespace MWWorld loadingListener->setProgressRange(cell->count()); + mNavigator.updatePlayerPosition(position.asVec3()); + // Load cell. mPagedRefs.clear(); loadCell(cell, loadingListener, changeEvent); @@ -855,6 +857,8 @@ namespace MWWorld if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); + mNavigator.updatePlayerPosition(position.asVec3()); + changeCellGrid(position.asVec3(), x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index c44ebc5155..631e4105ba 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -90,6 +90,10 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); + TileBounds bounds; + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); @@ -137,6 +141,10 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); + TileBounds bounds; + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp index d427eb3e12..e74a22e5ba 100644 --- a/components/detournavigator/gettilespositions.cpp +++ b/components/detournavigator/gettilespositions.cpp @@ -2,6 +2,7 @@ #include "settings.hpp" #include "settingsutils.hpp" #include "tileposition.hpp" +#include "tilebounds.hpp" #include @@ -32,12 +33,15 @@ namespace DetourNavigator } TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, - const RecastSettings& settings) + const TileBounds& bounds, const RecastSettings& settings) { btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(transform, aabbMin, aabbMax); - + aabbMin.setX(std::max(aabbMin.x(), bounds.mMin.x())); + aabbMin.setY(std::max(aabbMin.y(), bounds.mMin.y())); + aabbMax.setX(std::min(aabbMax.x(), bounds.mMax.x())); + aabbMax.setY(std::min(aabbMax.y(), bounds.mMax.y())); return makeTilesPositionsRange(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings); } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 946f3e64f2..9d3df7ef65 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H +#include "tilebounds.hpp" #include "tileposition.hpp" class btVector3; @@ -26,7 +27,7 @@ namespace DetourNavigator const osg::Vec3f& aabbMax, const RecastSettings& settings); TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, - const btTransform& transform, const RecastSettings& settings); + const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings); TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings); diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 9fba4ad611..e8da5a0d61 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -42,6 +42,18 @@ namespace namespace DetourNavigator { + namespace + { + TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles) + { + const float radius = fromNavMeshCoordinates(settings, std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1) * getTileSize(settings)); + TileBounds result; + result.mMin = center - osg::Vec2f(radius, radius); + result.mMax = center + osg::Vec2f(radius, radius); + return result; + } + } + NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(settings.mRecast) @@ -205,6 +217,7 @@ namespace DetourNavigator } } const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); + mRecastMeshManager.setBounds(makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), maxTiles)); mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) { if (tilesToPost.count(tile)) @@ -263,7 +276,7 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(makeTilesPositionsRange(shape, transform, mSettings.mRecast), + getTilesPositions(makeTilesPositionsRange(shape, transform, mRecastMeshManager.getBounds(), mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 285920e5a0..e38da2d0e1 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -37,6 +37,11 @@ namespace DetourNavigator }; } + inline float fromNavMeshCoordinates(const RecastSettings& settings, float value) + { + return value / settings.mRecastScaleFactor; + } + inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 17ba7afc39..ced5a28656 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -8,13 +8,28 @@ #include #include +#include namespace DetourNavigator { TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) : mSettings(settings) + , mBounds {osg::Vec2f(-std::numeric_limits::max(), -std::numeric_limits::max()), + osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max())} {} + TileBounds TileCachedRecastMeshManager::getBounds() const + { + const std::lock_guard lock(mMutex); + return mBounds; + } + + void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds) + { + const std::lock_guard lock(mMutex); + mBounds = bounds; + } + std::string TileCachedRecastMeshManager::getWorldspace() const { const std::lock_guard lock(mMutex); @@ -36,7 +51,7 @@ namespace DetourNavigator std::vector tilesPositions; { const std::lock_guard lock(mMutex); - getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mSettings), + getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mBounds, mSettings), [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 88face24ce..d99dc3e27e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -20,6 +20,10 @@ namespace DetourNavigator public: explicit TileCachedRecastMeshManager(const RecastSettings& settings); + TileBounds getBounds() const; + + void setBounds(const TileBounds& bounds); + std::string getWorldspace() const; void setWorldspace(std::string_view worldspace); @@ -57,7 +61,7 @@ namespace DetourNavigator changed = true; } }; - getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mSettings), onTilePosition); + getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mBounds, mSettings), onTilePosition); std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { @@ -109,6 +113,7 @@ namespace DetourNavigator const RecastSettings& mSettings; mutable std::mutex mMutex; + TileBounds mBounds; std::string mWorldspace; TilesMap mTiles; std::unordered_map> mObjectsTilesPositions; From 9069e97dce21ca0adf795b74b220247bde32471e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 23 Jan 2022 15:12:27 +0100 Subject: [PATCH 1993/2859] Filter out triangles with NaN coordinates Sorting a vector with such values gives invalid result because comparison with NaN is always false. --- components/detournavigator/recastmeshbuilder.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 0f7552aa77..08e7002cf8 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace DetourNavigator { @@ -40,6 +41,16 @@ namespace DetourNavigator { return static_cast(cellSize) / (dataSize - 1); } + + bool isNan(const RecastMeshTriangle& triangle) + { + for (std::size_t i = 0; i < 3; ++i) + if (std::isnan(triangle.mVertices[i].x()) + || std::isnan(triangle.mVertices[i].y()) + || std::isnan(triangle.mVertices[i].z())) + return true; + return false; + } } Mesh makeMesh(std::vector&& triangles, const osg::Vec3f& shift) @@ -264,6 +275,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) && { + mTriangles.erase(std::remove_if(mTriangles.begin(), mTriangles.end(), isNan), mTriangles.end()); std::sort(mTriangles.begin(), mTriangles.end()); std::sort(mWater.begin(), mWater.end()); Mesh mesh = makeMesh(std::move(mTriangles)); From e4cb1a13702906058b0867fa65f367aa6d0b0e3d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 23 Jan 2022 23:53:25 +0000 Subject: [PATCH 1994/2859] Run unit tests in CI with -fsanitize=address --- CI/before_script.linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 2a10ae2b78..801d7f0a3e 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -58,7 +58,7 @@ cd build if [[ "${BUILD_TESTS_ONLY}" ]]; then # flags specific to our test suite - CXX_FLAGS="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" + CXX_FLAGS="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy -fsanitize=address" if [[ "${CXX}" == 'clang++' ]]; then CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments" fi From 34723c8ea8e451c3a205ec3f4b93f2164ccb5a8d Mon Sep 17 00:00:00 2001 From: Matt <3397065-ZehMatt@users.noreply.gitlab.com> Date: Thu, 27 Jan 2022 19:18:57 +0000 Subject: [PATCH 1995/2859] Cleanup interpreter code a bit --- CHANGELOG.md | 1 + apps/openmw/mwscript/aiextensions.cpp | 123 +++++---- apps/openmw/mwscript/animationextensions.cpp | 12 +- apps/openmw/mwscript/cellextensions.cpp | 20 +- apps/openmw/mwscript/containerextensions.cpp | 32 +-- apps/openmw/mwscript/controlextensions.cpp | 82 +++--- apps/openmw/mwscript/dialogueextensions.cpp | 44 +-- apps/openmw/mwscript/guiextensions.cpp | 66 ++--- apps/openmw/mwscript/miscextensions.cpp | 238 ++++++++--------- apps/openmw/mwscript/skyextensions.cpp | 16 +- apps/openmw/mwscript/soundextensions.cpp | 64 ++--- apps/openmw/mwscript/statsextensions.cpp | 250 +++++++++--------- .../mwscript/transformationextensions.cpp | 82 +++--- apps/openmw/mwscript/userextensions.cpp | 12 +- .../mwscript/test_scripts.cpp | 12 +- components/interpreter/installopcodes.cpp | 134 +++++----- components/interpreter/interpreter.cpp | 132 +++------ components/interpreter/interpreter.hpp | 62 +++-- components/interpreter/localopcodes.hpp | 42 +-- 19 files changed, 647 insertions(+), 777 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c45d15e6d..c8a17898f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -130,6 +130,7 @@ Feature #6534: Shader-based object texture blending Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp + Task #6553: Simplify interpreter instruction registration 0.47.0 ------ diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index b7b6de9463..f862508ee4 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -553,69 +553,68 @@ namespace MWScript } }; - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment3 (Compiler::Ai::opcodeAIActivate, new OpAiActivate); - interpreter.installSegment3 (Compiler::Ai::opcodeAIActivateExplicit, new OpAiActivate); - interpreter.installSegment3 (Compiler::Ai::opcodeAiTravel, new OpAiTravel); - interpreter.installSegment3 (Compiler::Ai::opcodeAiTravelExplicit, new OpAiTravel); - interpreter.installSegment3 (Compiler::Ai::opcodeAiEscort, new OpAiEscort); - interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortExplicit, new OpAiEscort); - interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortCell, new OpAiEscortCell); - interpreter.installSegment3 (Compiler::Ai::opcodeAiEscortCellExplicit, new OpAiEscortCell); - interpreter.installSegment3 (Compiler::Ai::opcodeAiWander, new OpAiWander); - interpreter.installSegment3 (Compiler::Ai::opcodeAiWanderExplicit, new OpAiWander); - interpreter.installSegment3 (Compiler::Ai::opcodeAiFollow, new OpAiFollow); - interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowExplicit, new OpAiFollow); - interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowCell, new OpAiFollowCell); - interpreter.installSegment3 (Compiler::Ai::opcodeAiFollowCellExplicit, new OpAiFollowCell); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAiPackageDone, new OpGetAiPackageDone); - - interpreter.installSegment5 (Compiler::Ai::opcodeGetAiPackageDoneExplicit, - new OpGetAiPackageDone); - interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackage, new OpGetCurrentAIPackage); - interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); - interpreter.installSegment5 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); - interpreter.installSegment5 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); - interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); - interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); - interpreter.installSegment5 (Compiler::Ai::opcodeGetTarget, new OpGetTarget); - interpreter.installSegment5 (Compiler::Ai::opcodeGetTargetExplicit, new OpGetTarget); - interpreter.installSegment5 (Compiler::Ai::opcodeStartCombat, new OpStartCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeStartCombatExplicit, new OpStartCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeStopCombat, new OpStopCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeStopCombatExplicit, new OpStopCombat); - interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); - - interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFightExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFlee, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFleeExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarm, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarmExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - - interpreter.installSegment5 (Compiler::Ai::opcodeModHello, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeModHelloExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFight, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFightExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFlee, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFleeExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeModAlarm, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - interpreter.installSegment5 (Compiler::Ai::opcodeModAlarmExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - - interpreter.installSegment5 (Compiler::Ai::opcodeGetHello, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetHelloExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFight, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFightExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFlee, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - - interpreter.installSegment5 (Compiler::Ai::opcodeFace, new OpFace); - interpreter.installSegment5 (Compiler::Ai::opcodeFaceExplicit, new OpFace); + interpreter.installSegment3>(Compiler::Ai::opcodeAIActivate); + interpreter.installSegment3>(Compiler::Ai::opcodeAIActivateExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiTravel); + interpreter.installSegment3>(Compiler::Ai::opcodeAiTravelExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiEscort); + interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortCell); + interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortCellExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiWander); + interpreter.installSegment3>(Compiler::Ai::opcodeAiWanderExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiFollow); + interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowExplicit); + interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowCell); + interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowCellExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetAiPackageDone); + + interpreter.installSegment5>(Compiler::Ai::opcodeGetAiPackageDoneExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetCurrentAiPackage); + interpreter.installSegment5>(Compiler::Ai::opcodeGetCurrentAiPackageExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetDetected); + interpreter.installSegment5>(Compiler::Ai::opcodeGetDetectedExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetLineOfSight); + interpreter.installSegment5>(Compiler::Ai::opcodeGetLineOfSightExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeGetTarget); + interpreter.installSegment5>(Compiler::Ai::opcodeGetTargetExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeStartCombat); + interpreter.installSegment5>(Compiler::Ai::opcodeStartCombatExplicit); + interpreter.installSegment5>(Compiler::Ai::opcodeStopCombat); + interpreter.installSegment5>(Compiler::Ai::opcodeStopCombatExplicit); + interpreter.installSegment5(Compiler::Ai::opcodeToggleAI); + + interpreter.installSegment5>(Compiler::Ai::opcodeSetHello, MWMechanics::CreatureStats::AiSetting::AI_Hello); + interpreter.installSegment5>(Compiler::Ai::opcodeSetHelloExplicit, MWMechanics::CreatureStats::AiSetting::AI_Hello); + interpreter.installSegment5>(Compiler::Ai::opcodeSetFight, MWMechanics::CreatureStats::AiSetting::AI_Fight); + interpreter.installSegment5>(Compiler::Ai::opcodeSetFightExplicit, MWMechanics::CreatureStats::AiSetting::AI_Fight); + interpreter.installSegment5>(Compiler::Ai::opcodeSetFlee, MWMechanics::CreatureStats::AiSetting::AI_Flee); + interpreter.installSegment5>(Compiler::Ai::opcodeSetFleeExplicit, MWMechanics::CreatureStats::AiSetting::AI_Flee); + interpreter.installSegment5>(Compiler::Ai::opcodeSetAlarm, MWMechanics::CreatureStats::AiSetting::AI_Alarm); + interpreter.installSegment5>(Compiler::Ai::opcodeSetAlarmExplicit, MWMechanics::CreatureStats::AiSetting::AI_Alarm); + + interpreter.installSegment5>(Compiler::Ai::opcodeModHello, MWMechanics::CreatureStats::AiSetting::AI_Hello); + interpreter.installSegment5>(Compiler::Ai::opcodeModHelloExplicit, MWMechanics::CreatureStats::AiSetting::AI_Hello); + interpreter.installSegment5>(Compiler::Ai::opcodeModFight, MWMechanics::CreatureStats::AiSetting::AI_Fight); + interpreter.installSegment5>(Compiler::Ai::opcodeModFightExplicit, MWMechanics::CreatureStats::AiSetting::AI_Fight); + interpreter.installSegment5>(Compiler::Ai::opcodeModFlee, MWMechanics::CreatureStats::AiSetting::AI_Flee); + interpreter.installSegment5>(Compiler::Ai::opcodeModFleeExplicit, MWMechanics::CreatureStats::AiSetting::AI_Flee); + interpreter.installSegment5>(Compiler::Ai::opcodeModAlarm, MWMechanics::CreatureStats::AiSetting::AI_Alarm); + interpreter.installSegment5>(Compiler::Ai::opcodeModAlarmExplicit, MWMechanics::CreatureStats::AiSetting::AI_Alarm); + + interpreter.installSegment5>(Compiler::Ai::opcodeGetHello, MWMechanics::CreatureStats::AiSetting::AI_Hello); + interpreter.installSegment5>(Compiler::Ai::opcodeGetHelloExplicit, MWMechanics::CreatureStats::AiSetting::AI_Hello); + interpreter.installSegment5>(Compiler::Ai::opcodeGetFight, MWMechanics::CreatureStats::AiSetting::AI_Fight); + interpreter.installSegment5>(Compiler::Ai::opcodeGetFightExplicit, MWMechanics::CreatureStats::AiSetting::AI_Fight); + interpreter.installSegment5>(Compiler::Ai::opcodeGetFlee, MWMechanics::CreatureStats::AiSetting::AI_Flee); + interpreter.installSegment5>(Compiler::Ai::opcodeGetFleeExplicit, MWMechanics::CreatureStats::AiSetting::AI_Flee); + interpreter.installSegment5>(Compiler::Ai::opcodeGetAlarm, MWMechanics::CreatureStats::AiSetting::AI_Alarm); + interpreter.installSegment5>(Compiler::Ai::opcodeGetAlarmExplicit, MWMechanics::CreatureStats::AiSetting::AI_Alarm); + + interpreter.installSegment5>(Compiler::Ai::opcodeFace); + interpreter.installSegment5>(Compiler::Ai::opcodeFaceExplicit); } } } diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 8bb6cc6ada..9320c86a88 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -101,12 +101,12 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Animation::opcodeSkipAnim, new OpSkipAnim); - interpreter.installSegment5 (Compiler::Animation::opcodeSkipAnimExplicit, new OpSkipAnim); - interpreter.installSegment3 (Compiler::Animation::opcodePlayAnim, new OpPlayAnim); - interpreter.installSegment3 (Compiler::Animation::opcodePlayAnimExplicit, new OpPlayAnim); - interpreter.installSegment3 (Compiler::Animation::opcodeLoopAnim, new OpLoopAnim); - interpreter.installSegment3 (Compiler::Animation::opcodeLoopAnimExplicit, new OpLoopAnim); + interpreter.installSegment5>(Compiler::Animation::opcodeSkipAnim); + interpreter.installSegment5>(Compiler::Animation::opcodeSkipAnimExplicit); + interpreter.installSegment3>(Compiler::Animation::opcodePlayAnim); + interpreter.installSegment3>(Compiler::Animation::opcodePlayAnimExplicit); + interpreter.installSegment3>(Compiler::Animation::opcodeLoopAnim); + interpreter.installSegment3>(Compiler::Animation::opcodeLoopAnimExplicit); } } } diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index b0977984fb..d5ac222244 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -250,16 +250,16 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Cell::opcodeCellChanged, new OpCellChanged); - interpreter.installSegment5 (Compiler::Cell::opcodeTestCells, new OpTestCells); - interpreter.installSegment5 (Compiler::Cell::opcodeTestInteriorCells, new OpTestInteriorCells); - interpreter.installSegment5 (Compiler::Cell::opcodeCOC, new OpCOC); - interpreter.installSegment5 (Compiler::Cell::opcodeCOE, new OpCOE); - interpreter.installSegment5 (Compiler::Cell::opcodeGetInterior, new OpGetInterior); - interpreter.installSegment5 (Compiler::Cell::opcodeGetPCCell, new OpGetPCCell); - interpreter.installSegment5 (Compiler::Cell::opcodeGetWaterLevel, new OpGetWaterLevel); - interpreter.installSegment5 (Compiler::Cell::opcodeSetWaterLevel, new OpSetWaterLevel); - interpreter.installSegment5 (Compiler::Cell::opcodeModWaterLevel, new OpModWaterLevel); + interpreter.installSegment5(Compiler::Cell::opcodeCellChanged); + interpreter.installSegment5(Compiler::Cell::opcodeTestCells); + interpreter.installSegment5(Compiler::Cell::opcodeTestInteriorCells); + interpreter.installSegment5(Compiler::Cell::opcodeCOC); + interpreter.installSegment5(Compiler::Cell::opcodeCOE); + interpreter.installSegment5(Compiler::Cell::opcodeGetInterior); + interpreter.installSegment5(Compiler::Cell::opcodeGetPCCell); + interpreter.installSegment5(Compiler::Cell::opcodeGetWaterLevel); + interpreter.installSegment5(Compiler::Cell::opcodeSetWaterLevel); + interpreter.installSegment5(Compiler::Cell::opcodeModWaterLevel); } } } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 501404e958..ec86ba4869 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -488,22 +488,22 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Container::opcodeAddItem, new OpAddItem); - interpreter.installSegment5 (Compiler::Container::opcodeAddItemExplicit, new OpAddItem); - interpreter.installSegment5 (Compiler::Container::opcodeGetItemCount, new OpGetItemCount); - interpreter.installSegment5 (Compiler::Container::opcodeGetItemCountExplicit, new OpGetItemCount); - interpreter.installSegment5 (Compiler::Container::opcodeRemoveItem, new OpRemoveItem); - interpreter.installSegment5 (Compiler::Container::opcodeRemoveItemExplicit, new OpRemoveItem); - interpreter.installSegment5 (Compiler::Container::opcodeEquip, new OpEquip); - interpreter.installSegment5 (Compiler::Container::opcodeEquipExplicit, new OpEquip); - interpreter.installSegment5 (Compiler::Container::opcodeGetArmorType, new OpGetArmorType); - interpreter.installSegment5 (Compiler::Container::opcodeGetArmorTypeExplicit, new OpGetArmorType); - interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquipped, new OpHasItemEquipped); - interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquippedExplicit, new OpHasItemEquipped); - interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGem, new OpHasSoulGem); - interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGemExplicit, new OpHasSoulGem); - interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponType, new OpGetWeaponType); - interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponTypeExplicit, new OpGetWeaponType); + interpreter.installSegment5>(Compiler::Container::opcodeAddItem); + interpreter.installSegment5>(Compiler::Container::opcodeAddItemExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeGetItemCount); + interpreter.installSegment5>(Compiler::Container::opcodeGetItemCountExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeRemoveItem); + interpreter.installSegment5>(Compiler::Container::opcodeRemoveItemExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeEquip); + interpreter.installSegment5>(Compiler::Container::opcodeEquipExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeGetArmorType); + interpreter.installSegment5>(Compiler::Container::opcodeGetArmorTypeExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeHasItemEquipped); + interpreter.installSegment5>(Compiler::Container::opcodeHasItemEquippedExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeHasSoulGem); + interpreter.installSegment5>(Compiler::Container::opcodeHasSoulGemExplicit); + interpreter.installSegment5>(Compiler::Container::opcodeGetWeaponType); + interpreter.installSegment5>(Compiler::Container::opcodeGetWeaponTypeExplicit); } } } diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 2716630d72..74d2067394 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -188,67 +188,51 @@ namespace MWScript }; - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - for (int i=0; i(Compiler::Control::opcodeEnable + i, Compiler::Control::controls[i], true); + interpreter.installSegment5(Compiler::Control::opcodeDisable + i, Compiler::Control::controls[i], false); + interpreter.installSegment5(Compiler::Control::opcodeGetDisabled + i, Compiler::Control::controls[i]); } - interpreter.installSegment5 (Compiler::Control::opcodeToggleCollision, new OpToggleCollision); + interpreter.installSegment5(Compiler::Control::opcodeToggleCollision); //Force Run - interpreter.installSegment5 (Compiler::Control::opcodeClearForceRun, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceRunExplicit, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeForceRun, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeForceRunExplicit, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); + interpreter.installSegment5>(Compiler::Control::opcodeClearForceRun, MWMechanics::CreatureStats::Flag_ForceRun); + interpreter.installSegment5>(Compiler::Control::opcodeClearForceRunExplicit, MWMechanics::CreatureStats::Flag_ForceRun); + interpreter.installSegment5>(Compiler::Control::opcodeForceRun, MWMechanics::CreatureStats::Flag_ForceRun); + interpreter.installSegment5>(Compiler::Control::opcodeForceRunExplicit, MWMechanics::CreatureStats::Flag_ForceRun); //Force Jump - interpreter.installSegment5 (Compiler::Control::opcodeClearForceJump, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceJumpExplicit, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); - interpreter.installSegment5 (Compiler::Control::opcodeForceJump, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); - interpreter.installSegment5 (Compiler::Control::opcodeForceJumpExplicit, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + interpreter.installSegment5>(Compiler::Control::opcodeClearForceJump, MWMechanics::CreatureStats::Flag_ForceJump); + interpreter.installSegment5>(Compiler::Control::opcodeClearForceJumpExplicit, MWMechanics::CreatureStats::Flag_ForceJump); + interpreter.installSegment5>(Compiler::Control::opcodeForceJump, MWMechanics::CreatureStats::Flag_ForceJump); + interpreter.installSegment5>(Compiler::Control::opcodeForceJumpExplicit, MWMechanics::CreatureStats::Flag_ForceJump); //Force MoveJump - interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJump, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJumpExplicit, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); - interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJump, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); - interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJumpExplicit, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + interpreter.installSegment5>(Compiler::Control::opcodeClearForceMoveJump, MWMechanics::CreatureStats::Flag_ForceMoveJump); + interpreter.installSegment5>(Compiler::Control::opcodeClearForceMoveJumpExplicit, MWMechanics::CreatureStats::Flag_ForceMoveJump); + interpreter.installSegment5>(Compiler::Control::opcodeForceMoveJump, MWMechanics::CreatureStats::Flag_ForceMoveJump); + interpreter.installSegment5>(Compiler::Control::opcodeForceMoveJumpExplicit, MWMechanics::CreatureStats::Flag_ForceMoveJump); //Force Sneak - interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneakExplicit, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeForceSneak, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeForceSneakExplicit, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - - interpreter.installSegment5 (Compiler::Control::opcodeGetPcRunning, new OpGetPcRunning); - interpreter.installSegment5 (Compiler::Control::opcodeGetPcSneaking, new OpGetPcSneaking); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceRun, new OpGetForceRun); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceRunExplicit, new OpGetForceRun); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceJump, new OpGetForceJump); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceJumpExplicit, new OpGetForceJump); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJump, new OpGetForceMoveJump); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJumpExplicit, new OpGetForceMoveJump); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneak, new OpGetForceSneak); - interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneakExplicit, new OpGetForceSneak); + interpreter.installSegment5>(Compiler::Control::opcodeClearForceSneak, MWMechanics::CreatureStats::Flag_ForceSneak); + interpreter.installSegment5>(Compiler::Control::opcodeClearForceSneakExplicit, MWMechanics::CreatureStats::Flag_ForceSneak); + interpreter.installSegment5>(Compiler::Control::opcodeForceSneak, MWMechanics::CreatureStats::Flag_ForceSneak); + interpreter.installSegment5>(Compiler::Control::opcodeForceSneakExplicit, MWMechanics::CreatureStats::Flag_ForceSneak); + + interpreter.installSegment5(Compiler::Control::opcodeGetPcRunning); + interpreter.installSegment5(Compiler::Control::opcodeGetPcSneaking); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceRun); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceRunExplicit); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceJump); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceJumpExplicit); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceMoveJump); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceMoveJumpExplicit); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceSneak); + interpreter.installSegment5>(Compiler::Control::opcodeGetForceSneakExplicit); } } } diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index b99a043bf5..a147bce6ea 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -284,28 +284,28 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Dialogue::opcodeJournal, new OpJournal); - interpreter.installSegment5 (Compiler::Dialogue::opcodeJournalExplicit, new OpJournal); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSetJournalIndex, new OpSetJournalIndex); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGetJournalIndex, new OpGetJournalIndex); - interpreter.installSegment5 (Compiler::Dialogue::opcodeAddTopic, new OpAddTopic); - interpreter.installSegment3 (Compiler::Dialogue::opcodeChoice,new OpChoice); - interpreter.installSegment5 (Compiler::Dialogue::opcodeForceGreeting, new OpForceGreeting); - interpreter.installSegment5 (Compiler::Dialogue::opcodeForceGreetingExplicit, new OpForceGreeting); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGoodbye, new OpGoodbye); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputation, new OpGetReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSetReputation, new OpSetReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeModReputation, new OpModReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSetReputationExplicit, new OpSetReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeModReputationExplicit, new OpModReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputationExplicit, new OpGetReputation); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFaction, new OpSameFaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeSetFactionReaction, new OpSetFactionReaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); - interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActor, new OpClearInfoActor); - interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActorExplicit, new OpClearInfoActor); + interpreter.installSegment5>(Compiler::Dialogue::opcodeJournal); + interpreter.installSegment5>(Compiler::Dialogue::opcodeJournalExplicit); + interpreter.installSegment5(Compiler::Dialogue::opcodeSetJournalIndex); + interpreter.installSegment5(Compiler::Dialogue::opcodeGetJournalIndex); + interpreter.installSegment5(Compiler::Dialogue::opcodeAddTopic); + interpreter.installSegment3(Compiler::Dialogue::opcodeChoice); + interpreter.installSegment5>(Compiler::Dialogue::opcodeForceGreeting); + interpreter.installSegment5>(Compiler::Dialogue::opcodeForceGreetingExplicit); + interpreter.installSegment5(Compiler::Dialogue::opcodeGoodbye); + interpreter.installSegment5>(Compiler::Dialogue::opcodeGetReputation); + interpreter.installSegment5>(Compiler::Dialogue::opcodeSetReputation); + interpreter.installSegment5>(Compiler::Dialogue::opcodeModReputation); + interpreter.installSegment5>(Compiler::Dialogue::opcodeSetReputationExplicit); + interpreter.installSegment5>(Compiler::Dialogue::opcodeModReputationExplicit); + interpreter.installSegment5>(Compiler::Dialogue::opcodeGetReputationExplicit); + interpreter.installSegment5>(Compiler::Dialogue::opcodeSameFaction); + interpreter.installSegment5>(Compiler::Dialogue::opcodeSameFactionExplicit); + interpreter.installSegment5(Compiler::Dialogue::opcodeModFactionReaction); + interpreter.installSegment5(Compiler::Dialogue::opcodeSetFactionReaction); + interpreter.installSegment5(Compiler::Dialogue::opcodeGetFactionReaction); + interpreter.installSegment5>(Compiler::Dialogue::opcodeClearInfoActor); + interpreter.installSegment5>(Compiler::Dialogue::opcodeClearInfoActorExplicit); } } diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index f0bc90e044..e44c94e686 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -218,45 +218,33 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Gui::opcodeEnableBirthMenu, - new OpShowDialogue (MWGui::GM_Birth)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableClassMenu, - new OpShowDialogue (MWGui::GM_Class)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableNameMenu, - new OpShowDialogue (MWGui::GM_Name)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableRaceMenu, - new OpShowDialogue (MWGui::GM_Race)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsReviewMenu, - new OpShowDialogue (MWGui::GM_Review)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableLevelupMenu, - new OpShowDialogue (MWGui::GM_Levelup)); - - interpreter.installSegment5 (Compiler::Gui::opcodeEnableInventoryMenu, - new OpEnableWindow (MWGui::GW_Inventory)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableMagicMenu, - new OpEnableWindow (MWGui::GW_Magic)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableMapMenu, - new OpEnableWindow (MWGui::GW_Map)); - interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsMenu, - new OpEnableWindow (MWGui::GW_Stats)); - - interpreter.installSegment5 (Compiler::Gui::opcodeEnableRest, - new OpEnableRest ()); - - interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenu, - new OpShowRestMenu); - interpreter.installSegment5 (Compiler::Gui::opcodeShowRestMenuExplicit, new OpShowRestMenu); - - interpreter.installSegment5 (Compiler::Gui::opcodeGetButtonPressed, new OpGetButtonPressed); - - interpreter.installSegment5 (Compiler::Gui::opcodeToggleFogOfWar, new OpToggleFogOfWar); - - interpreter.installSegment5 (Compiler::Gui::opcodeToggleFullHelp, new OpToggleFullHelp); - - interpreter.installSegment5 (Compiler::Gui::opcodeShowMap, new OpShowMap); - interpreter.installSegment5 (Compiler::Gui::opcodeFillMap, new OpFillMap); - interpreter.installSegment3 (Compiler::Gui::opcodeMenuTest, new OpMenuTest); - interpreter.installSegment5 (Compiler::Gui::opcodeToggleMenus, new OpToggleMenus); + interpreter.installSegment5(Compiler::Gui::opcodeEnableBirthMenu, MWGui::GM_Birth); + interpreter.installSegment5(Compiler::Gui::opcodeEnableClassMenu, MWGui::GM_Class); + interpreter.installSegment5(Compiler::Gui::opcodeEnableNameMenu, MWGui::GM_Name); + interpreter.installSegment5(Compiler::Gui::opcodeEnableRaceMenu, MWGui::GM_Race); + interpreter.installSegment5(Compiler::Gui::opcodeEnableStatsReviewMenu, MWGui::GM_Review); + interpreter.installSegment5(Compiler::Gui::opcodeEnableLevelupMenu, MWGui::GM_Levelup); + + interpreter.installSegment5(Compiler::Gui::opcodeEnableInventoryMenu, MWGui::GW_Inventory); + interpreter.installSegment5(Compiler::Gui::opcodeEnableMagicMenu, MWGui::GW_Magic); + interpreter.installSegment5(Compiler::Gui::opcodeEnableMapMenu, MWGui::GW_Map); + interpreter.installSegment5(Compiler::Gui::opcodeEnableStatsMenu, MWGui::GW_Stats); + + interpreter.installSegment5(Compiler::Gui::opcodeEnableRest); + + interpreter.installSegment5>(Compiler::Gui::opcodeShowRestMenu); + interpreter.installSegment5>(Compiler::Gui::opcodeShowRestMenuExplicit); + + interpreter.installSegment5(Compiler::Gui::opcodeGetButtonPressed); + + interpreter.installSegment5(Compiler::Gui::opcodeToggleFogOfWar); + + interpreter.installSegment5(Compiler::Gui::opcodeToggleFullHelp); + + interpreter.installSegment5(Compiler::Gui::opcodeShowMap); + interpreter.installSegment5(Compiler::Gui::opcodeFillMap); + interpreter.installSegment3(Compiler::Gui::opcodeMenuTest); + interpreter.installSegment5(Compiler::Gui::opcodeToggleMenus); } } } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 5d9f037357..fe8caf4567 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1616,125 +1616,125 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Misc::opcodeMenuMode, new OpMenuMode); - interpreter.installSegment5 (Compiler::Misc::opcodeRandom, new OpRandom); - interpreter.installSegment5 (Compiler::Misc::opcodeScriptRunning, new OpScriptRunning); - interpreter.installSegment5 (Compiler::Misc::opcodeStartScript, new OpStartScript); - interpreter.installSegment5 (Compiler::Misc::opcodeStartScriptExplicit, new OpStartScript); - interpreter.installSegment5 (Compiler::Misc::opcodeStopScript, new OpStopScript); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSecondsPassed, new OpGetSecondsPassed); - interpreter.installSegment5 (Compiler::Misc::opcodeEnable, new OpEnable); - interpreter.installSegment5 (Compiler::Misc::opcodeEnableExplicit, new OpEnable); - interpreter.installSegment5 (Compiler::Misc::opcodeDisable, new OpDisable); - interpreter.installSegment5 (Compiler::Misc::opcodeDisableExplicit, new OpDisable); - interpreter.installSegment5 (Compiler::Misc::opcodeGetDisabled, new OpGetDisabled); - interpreter.installSegment5 (Compiler::Misc::opcodeGetDisabledExplicit, new OpGetDisabled); - interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); - interpreter.installSegment5 (Compiler::Misc::opcodeOnActivate, new OpOnActivate); - interpreter.installSegment5 (Compiler::Misc::opcodeOnActivateExplicit, new OpOnActivate); - interpreter.installSegment5 (Compiler::Misc::opcodeActivate, new OpActivate); - interpreter.installSegment5 (Compiler::Misc::opcodeActivateExplicit, new OpActivate); - interpreter.installSegment3 (Compiler::Misc::opcodeLock, new OpLock); - interpreter.installSegment3 (Compiler::Misc::opcodeLockExplicit, new OpLock); - interpreter.installSegment5 (Compiler::Misc::opcodeUnlock, new OpUnlock); - interpreter.installSegment5 (Compiler::Misc::opcodeUnlockExplicit, new OpUnlock); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleCollisionDebug, new OpToggleCollisionDebug); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleCollisionBoxes, new OpToggleCollisionBoxes); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleWireframe, new OpToggleWireframe); - interpreter.installSegment5 (Compiler::Misc::opcodeFadeIn, new OpFadeIn); - interpreter.installSegment5 (Compiler::Misc::opcodeFadeOut, new OpFadeOut); - interpreter.installSegment5 (Compiler::Misc::opcodeFadeTo, new OpFadeTo); - interpreter.installSegment5 (Compiler::Misc::opcodeTogglePathgrid, new OpTogglePathgrid); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleWater, new OpToggleWater); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleWorld, new OpToggleWorld); - interpreter.installSegment5 (Compiler::Misc::opcodeDontSaveObject, new OpDontSaveObject); - interpreter.installSegment5 (Compiler::Misc::opcodePcForce1stPerson, new OpPcForce1stPerson); - interpreter.installSegment5 (Compiler::Misc::opcodePcForce3rdPerson, new OpPcForce3rdPerson); - interpreter.installSegment5 (Compiler::Misc::opcodePcGet3rdPerson, new OpPcGet3rdPerson); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleVanityMode, new OpToggleVanityMode); - interpreter.installSegment5 (Compiler::Misc::opcodeGetPcSleep, new OpGetPcSleep); - interpreter.installSegment5 (Compiler::Misc::opcodeGetPcJumping, new OpGetPcJumping); - interpreter.installSegment5 (Compiler::Misc::opcodeWakeUpPc, new OpWakeUpPc); - interpreter.installSegment5 (Compiler::Misc::opcodePlayBink, new OpPlayBink); - interpreter.installSegment5 (Compiler::Misc::opcodePayFine, new OpPayFine); - interpreter.installSegment5 (Compiler::Misc::opcodePayFineThief, new OpPayFineThief); - interpreter.installSegment5 (Compiler::Misc::opcodeGoToJail, new OpGoToJail); - interpreter.installSegment5 (Compiler::Misc::opcodeGetLocked, new OpGetLocked); - interpreter.installSegment5 (Compiler::Misc::opcodeGetLockedExplicit, new OpGetLocked); - interpreter.installSegment5 (Compiler::Misc::opcodeGetEffect, new OpGetEffect); - interpreter.installSegment5 (Compiler::Misc::opcodeGetEffectExplicit, new OpGetEffect); - interpreter.installSegment5 (Compiler::Misc::opcodeAddSoulGem, new OpAddSoulGem); - interpreter.installSegment5 (Compiler::Misc::opcodeAddSoulGemExplicit, new OpAddSoulGem); - interpreter.installSegment3 (Compiler::Misc::opcodeRemoveSoulGem, new OpRemoveSoulGem); - interpreter.installSegment3 (Compiler::Misc::opcodeRemoveSoulGemExplicit, new OpRemoveSoulGem); - interpreter.installSegment5 (Compiler::Misc::opcodeDrop, new OpDrop); - interpreter.installSegment5 (Compiler::Misc::opcodeDropExplicit, new OpDrop); - interpreter.installSegment5 (Compiler::Misc::opcodeDropSoulGem, new OpDropSoulGem); - interpreter.installSegment5 (Compiler::Misc::opcodeDropSoulGemExplicit, new OpDropSoulGem); - interpreter.installSegment5 (Compiler::Misc::opcodeGetAttacked, new OpGetAttacked); - interpreter.installSegment5 (Compiler::Misc::opcodeGetAttackedExplicit, new OpGetAttacked); - interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawn, new OpGetWeaponDrawn); - interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawnExplicit, new OpGetWeaponDrawn); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadied, new OpGetSpellReadied); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadiedExplicit, new OpGetSpellReadied); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffects, new OpGetSpellEffects); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffectsExplicit, new OpGetSpellEffects); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCurrentTime, new OpGetCurrentTime); - interpreter.installSegment5 (Compiler::Misc::opcodeSetDelete, new OpSetDelete); - interpreter.installSegment5 (Compiler::Misc::opcodeSetDeleteExplicit, new OpSetDelete); - interpreter.installSegment5 (Compiler::Misc::opcodeGetSquareRoot, new OpGetSquareRoot); - interpreter.installSegment5 (Compiler::Misc::opcodeFall, new OpFall); - interpreter.installSegment5 (Compiler::Misc::opcodeFallExplicit, new OpFall); - interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPc, new OpGetStandingPc); - interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPcExplicit, new OpGetStandingPc); - interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActor, new OpGetStandingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActorExplicit, new OpGetStandingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPc, new OpGetCollidingPc); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPcExplicit, new OpGetCollidingPc); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActor, new OpGetCollidingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActorExplicit, new OpGetCollidingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActor, new OpHurtStandingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActorExplicit, new OpHurtStandingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActor, new OpHurtCollidingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActorExplicit, new OpHurtCollidingActor); - interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed); - interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMe, new OpHitAttemptOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMeExplicit, new OpHitAttemptOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeDisableTeleporting, new OpEnableTeleporting); - interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); - interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); - interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars); - interpreter.installSegment5 (Compiler::Misc::opcodeShow, new OpShow); - interpreter.installSegment5 (Compiler::Misc::opcodeShowExplicit, new OpShow); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleScripts, new OpToggleScripts); - interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); - interpreter.installSegment5 (Compiler::Misc::opcodeEnableLevitation, new OpEnableLevitation); - interpreter.installSegment5 (Compiler::Misc::opcodeCast, new OpCast); - interpreter.installSegment5 (Compiler::Misc::opcodeCastExplicit, new OpCast); - interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpell, new OpExplodeSpell); - interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell); - interpreter.installSegment5 (Compiler::Misc::opcodeGetPcInJail, new OpGetPcInJail); - interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling); - interpreter.installSegment3 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); - interpreter.installSegment3 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); - interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevCreature, new OpAddToLevCreature); - interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevCreature, new OpRemoveFromLevCreature); - interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevItem, new OpAddToLevItem); - interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevItem, new OpRemoveFromLevItem); - interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph); - interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths); - interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender); - interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMe, new OpRepairedOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMeExplicit, new OpRepairedOnMe); - interpreter.installSegment5 (Compiler::Misc::opcodeToggleRecastMesh, new OpToggleRecastMesh); - interpreter.installSegment5 (Compiler::Misc::opcodeHelp, new OpHelp); - interpreter.installSegment5 (Compiler::Misc::opcodeReloadLua, new OpReloadLua); + interpreter.installSegment5(Compiler::Misc::opcodeMenuMode); + interpreter.installSegment5(Compiler::Misc::opcodeRandom); + interpreter.installSegment5(Compiler::Misc::opcodeScriptRunning); + interpreter.installSegment5>(Compiler::Misc::opcodeStartScript); + interpreter.installSegment5>(Compiler::Misc::opcodeStartScriptExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeStopScript); + interpreter.installSegment5(Compiler::Misc::opcodeGetSecondsPassed); + interpreter.installSegment5>(Compiler::Misc::opcodeEnable); + interpreter.installSegment5>(Compiler::Misc::opcodeEnableExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeDisable); + interpreter.installSegment5>(Compiler::Misc::opcodeDisableExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetDisabled); + interpreter.installSegment5>(Compiler::Misc::opcodeGetDisabledExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeXBox); + interpreter.installSegment5>(Compiler::Misc::opcodeOnActivate); + interpreter.installSegment5>(Compiler::Misc::opcodeOnActivateExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeActivate); + interpreter.installSegment5>(Compiler::Misc::opcodeActivateExplicit); + interpreter.installSegment3>(Compiler::Misc::opcodeLock); + interpreter.installSegment3>(Compiler::Misc::opcodeLockExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeUnlock); + interpreter.installSegment5>(Compiler::Misc::opcodeUnlockExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeToggleCollisionDebug); + interpreter.installSegment5(Compiler::Misc::opcodeToggleCollisionBoxes); + interpreter.installSegment5(Compiler::Misc::opcodeToggleWireframe); + interpreter.installSegment5(Compiler::Misc::opcodeFadeIn); + interpreter.installSegment5(Compiler::Misc::opcodeFadeOut); + interpreter.installSegment5(Compiler::Misc::opcodeFadeTo); + interpreter.installSegment5(Compiler::Misc::opcodeTogglePathgrid); + interpreter.installSegment5(Compiler::Misc::opcodeToggleWater); + interpreter.installSegment5(Compiler::Misc::opcodeToggleWorld); + interpreter.installSegment5(Compiler::Misc::opcodeDontSaveObject); + interpreter.installSegment5(Compiler::Misc::opcodePcForce1stPerson); + interpreter.installSegment5(Compiler::Misc::opcodePcForce3rdPerson); + interpreter.installSegment5(Compiler::Misc::opcodePcGet3rdPerson); + interpreter.installSegment5(Compiler::Misc::opcodeToggleVanityMode); + interpreter.installSegment5(Compiler::Misc::opcodeGetPcSleep); + interpreter.installSegment5(Compiler::Misc::opcodeGetPcJumping); + interpreter.installSegment5(Compiler::Misc::opcodeWakeUpPc); + interpreter.installSegment5(Compiler::Misc::opcodePlayBink); + interpreter.installSegment5(Compiler::Misc::opcodePayFine); + interpreter.installSegment5(Compiler::Misc::opcodePayFineThief); + interpreter.installSegment5(Compiler::Misc::opcodeGoToJail); + interpreter.installSegment5>(Compiler::Misc::opcodeGetLocked); + interpreter.installSegment5>(Compiler::Misc::opcodeGetLockedExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetEffect); + interpreter.installSegment5>(Compiler::Misc::opcodeGetEffectExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeAddSoulGem); + interpreter.installSegment5>(Compiler::Misc::opcodeAddSoulGemExplicit); + interpreter.installSegment3>(Compiler::Misc::opcodeRemoveSoulGem); + interpreter.installSegment3>(Compiler::Misc::opcodeRemoveSoulGemExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeDrop); + interpreter.installSegment5>(Compiler::Misc::opcodeDropExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeDropSoulGem); + interpreter.installSegment5>(Compiler::Misc::opcodeDropSoulGemExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetAttacked); + interpreter.installSegment5>(Compiler::Misc::opcodeGetAttackedExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetWeaponDrawn); + interpreter.installSegment5>(Compiler::Misc::opcodeGetWeaponDrawnExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellReadied); + interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellReadiedExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellEffects); + interpreter.installSegment5>(Compiler::Misc::opcodeGetSpellEffectsExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeGetCurrentTime); + interpreter.installSegment5>(Compiler::Misc::opcodeSetDelete); + interpreter.installSegment5>(Compiler::Misc::opcodeSetDeleteExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeGetSquareRoot); + interpreter.installSegment5>(Compiler::Misc::opcodeFall); + interpreter.installSegment5>(Compiler::Misc::opcodeFallExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingPc); + interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingPcExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingActor); + interpreter.installSegment5>(Compiler::Misc::opcodeGetStandingActorExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingPc); + interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingPcExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingActor); + interpreter.installSegment5>(Compiler::Misc::opcodeGetCollidingActorExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeHurtStandingActor); + interpreter.installSegment5>(Compiler::Misc::opcodeHurtStandingActorExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeHurtCollidingActor); + interpreter.installSegment5>(Compiler::Misc::opcodeHurtCollidingActorExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeGetWindSpeed); + interpreter.installSegment5>(Compiler::Misc::opcodeHitOnMe); + interpreter.installSegment5>(Compiler::Misc::opcodeHitOnMeExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeHitAttemptOnMe); + interpreter.installSegment5>(Compiler::Misc::opcodeHitAttemptOnMeExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeDisableTeleporting); + interpreter.installSegment5>(Compiler::Misc::opcodeEnableTeleporting); + interpreter.installSegment5>(Compiler::Misc::opcodeShowVars); + interpreter.installSegment5>(Compiler::Misc::opcodeShowVarsExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeShow); + interpreter.installSegment5>(Compiler::Misc::opcodeShowExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeToggleGodMode); + interpreter.installSegment5(Compiler::Misc::opcodeToggleScripts); + interpreter.installSegment5>(Compiler::Misc::opcodeDisableLevitation); + interpreter.installSegment5>(Compiler::Misc::opcodeEnableLevitation); + interpreter.installSegment5>(Compiler::Misc::opcodeCast); + interpreter.installSegment5>(Compiler::Misc::opcodeCastExplicit); + interpreter.installSegment5>(Compiler::Misc::opcodeExplodeSpell); + interpreter.installSegment5>(Compiler::Misc::opcodeExplodeSpellExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeGetPcInJail); + interpreter.installSegment5(Compiler::Misc::opcodeGetPcTraveling); + interpreter.installSegment3>(Compiler::Misc::opcodeBetaComment); + interpreter.installSegment3>(Compiler::Misc::opcodeBetaCommentExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeAddToLevCreature); + interpreter.installSegment5(Compiler::Misc::opcodeRemoveFromLevCreature); + interpreter.installSegment5(Compiler::Misc::opcodeAddToLevItem); + interpreter.installSegment5(Compiler::Misc::opcodeRemoveFromLevItem); + interpreter.installSegment3>(Compiler::Misc::opcodeShowSceneGraph); + interpreter.installSegment3>(Compiler::Misc::opcodeShowSceneGraphExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeToggleBorders); + interpreter.installSegment5(Compiler::Misc::opcodeToggleNavMesh); + interpreter.installSegment5(Compiler::Misc::opcodeToggleActorsPaths); + interpreter.installSegment5(Compiler::Misc::opcodeSetNavMeshNumberToRender); + interpreter.installSegment5>(Compiler::Misc::opcodeRepairedOnMe); + interpreter.installSegment5>(Compiler::Misc::opcodeRepairedOnMeExplicit); + interpreter.installSegment5(Compiler::Misc::opcodeToggleRecastMesh); + interpreter.installSegment5(Compiler::Misc::opcodeHelp); + interpreter.installSegment5(Compiler::Misc::opcodeReloadLua); } } } diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index 81984ad7b7..1d00b8c052 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -126,14 +126,14 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Sky::opcodeToggleSky, new OpToggleSky); - interpreter.installSegment5 (Compiler::Sky::opcodeTurnMoonWhite, new OpTurnMoonWhite); - interpreter.installSegment5 (Compiler::Sky::opcodeTurnMoonRed, new OpTurnMoonRed); - interpreter.installSegment5 (Compiler::Sky::opcodeGetMasserPhase, new OpGetMasserPhase); - interpreter.installSegment5 (Compiler::Sky::opcodeGetSecundaPhase, new OpGetSecundaPhase); - interpreter.installSegment5 (Compiler::Sky::opcodeGetCurrentWeather, new OpGetCurrentWeather); - interpreter.installSegment5 (Compiler::Sky::opcodeChangeWeather, new OpChangeWeather); - interpreter.installSegment3 (Compiler::Sky::opcodeModRegion, new OpModRegion); + interpreter.installSegment5(Compiler::Sky::opcodeToggleSky); + interpreter.installSegment5(Compiler::Sky::opcodeTurnMoonWhite); + interpreter.installSegment5(Compiler::Sky::opcodeTurnMoonRed); + interpreter.installSegment5(Compiler::Sky::opcodeGetMasserPhase); + interpreter.installSegment5(Compiler::Sky::opcodeGetSecundaPhase); + interpreter.installSegment5(Compiler::Sky::opcodeGetCurrentWeather); + interpreter.installSegment5(Compiler::Sky::opcodeChangeWeather); + interpreter.installSegment3(Compiler::Sky::opcodeModRegion); } } } diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 6eab758f19..b8d1470ea8 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -104,15 +104,11 @@ namespace MWScript } }; - template + template class OpPlaySound3D : public Interpreter::Opcode0 { - bool mLoop; - public: - OpPlaySound3D (bool loop) : mLoop (loop) {} - void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); @@ -122,20 +118,16 @@ namespace MWScript MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0, MWSound::Type::Sfx, - mLoop ? MWSound::PlayMode::LoopRemoveAtDistance + TLoop ? MWSound::PlayMode::LoopRemoveAtDistance : MWSound::PlayMode::Normal); } }; - template + template class OpPlaySoundVP3D : public Interpreter::Opcode0 { - bool mLoop; - public: - OpPlaySoundVP3D (bool loop) : mLoop (loop) {} - void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); @@ -151,7 +143,7 @@ namespace MWScript MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, volume, pitch, MWSound::Type::Sfx, - mLoop ? MWSound::PlayMode::LoopRemoveAtDistance + TLoop ? MWSound::PlayMode::LoopRemoveAtDistance : MWSound::PlayMode::Normal); } @@ -205,34 +197,28 @@ namespace MWScript }; - void installOpcodes (Interpreter::Interpreter& interpreter) + void installOpcodes(Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::Sound::opcodeSay, new OpSay); - interpreter.installSegment5 (Compiler::Sound::opcodeSayDone, new OpSayDone); - interpreter.installSegment5 (Compiler::Sound::opcodeStreamMusic, new OpStreamMusic); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound, new OpPlaySound); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySoundVP, new OpPlaySoundVP); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3D, new OpPlaySound3D (false)); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DVP, new OpPlaySoundVP3D (false)); - interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3D, new OpPlaySound3D (true)); - interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DVP, - new OpPlaySoundVP3D (true)); - interpreter.installSegment5 (Compiler::Sound::opcodeStopSound, new OpStopSound); - interpreter.installSegment5 (Compiler::Sound::opcodeGetSoundPlaying, new OpGetSoundPlaying); - - interpreter.installSegment5 (Compiler::Sound::opcodeSayExplicit, new OpSay); - interpreter.installSegment5 (Compiler::Sound::opcodeSayDoneExplicit, new OpSayDone); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DExplicit, - new OpPlaySound3D (false)); - interpreter.installSegment5 (Compiler::Sound::opcodePlaySound3DVPExplicit, - new OpPlaySoundVP3D (false)); - interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DExplicit, - new OpPlaySound3D (true)); - interpreter.installSegment5 (Compiler::Sound::opcodePlayLoopSound3DVPExplicit, - new OpPlaySoundVP3D (true)); - interpreter.installSegment5 (Compiler::Sound::opcodeStopSoundExplicit, new OpStopSound); - interpreter.installSegment5 (Compiler::Sound::opcodeGetSoundPlayingExplicit, - new OpGetSoundPlaying); + interpreter.installSegment5>(Compiler::Sound::opcodeSay); + interpreter.installSegment5>(Compiler::Sound::opcodeSayDone); + interpreter.installSegment5(Compiler::Sound::opcodeStreamMusic); + interpreter.installSegment5(Compiler::Sound::opcodePlaySound); + interpreter.installSegment5(Compiler::Sound::opcodePlaySoundVP); + interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3D); + interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3DVP); + interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3D); + interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3DVP); + interpreter.installSegment5>(Compiler::Sound::opcodeStopSound); + interpreter.installSegment5>(Compiler::Sound::opcodeGetSoundPlaying); + + interpreter.installSegment5>(Compiler::Sound::opcodeSayExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodeSayDoneExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3DExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodePlaySound3DVPExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3DExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodePlayLoopSound3DVPExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodeStopSoundExplicit); + interpreter.installSegment5>(Compiler::Sound::opcodeGetSoundPlayingExplicit); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 186d1edf26..048029c72a 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1325,146 +1325,132 @@ namespace MWScript { for (int i=0; i (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetAttributeExplicit+i, - new OpGetAttribute (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeGetAttribute + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeGetAttributeExplicit + i, i); - interpreter.installSegment5 (Compiler::Stats::opcodeSetAttribute+i, new OpSetAttribute (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeSetAttributeExplicit+i, - new OpSetAttribute (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeSetAttribute + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeSetAttributeExplicit + i, i); - interpreter.installSegment5 (Compiler::Stats::opcodeModAttribute+i, new OpModAttribute (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeModAttributeExplicit+i, - new OpModAttribute (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeModAttribute + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeModAttributeExplicit + i, i); } for (int i=0; i (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicExplicit+i, - new OpGetDynamic (i)); - - interpreter.installSegment5 (Compiler::Stats::opcodeSetDynamic+i, new OpSetDynamic (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeSetDynamicExplicit+i, - new OpSetDynamic (i)); - - interpreter.installSegment5 (Compiler::Stats::opcodeModDynamic+i, new OpModDynamic (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeModDynamicExplicit+i, - new OpModDynamic (i)); - - interpreter.installSegment5 (Compiler::Stats::opcodeModCurrentDynamic+i, - new OpModCurrentDynamic (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeModCurrentDynamicExplicit+i, - new OpModCurrentDynamic (i)); - - interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicGetRatio+i, - new OpGetDynamicGetRatio (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetDynamicGetRatioExplicit+i, - new OpGetDynamicGetRatio (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamic + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamicExplicit + i, i); + + interpreter.installSegment5>(Compiler::Stats::opcodeSetDynamic + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeSetDynamicExplicit + i, i); + + interpreter.installSegment5>(Compiler::Stats::opcodeModDynamic + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeModDynamicExplicit + i, i); + + interpreter.installSegment5>(Compiler::Stats::opcodeModCurrentDynamic + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeModCurrentDynamicExplicit + i, i); + + interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamicGetRatio + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamicGetRatioExplicit + i, i); } for (int i=0; i (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetSkillExplicit+i, new OpGetSkill (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeGetSkill + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeGetSkillExplicit + i, i); - interpreter.installSegment5 (Compiler::Stats::opcodeSetSkill+i, new OpSetSkill (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeSetSkillExplicit+i, new OpSetSkill (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeSetSkill + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeSetSkillExplicit + i, i); - interpreter.installSegment5 (Compiler::Stats::opcodeModSkill+i, new OpModSkill (i)); - interpreter.installSegment5 (Compiler::Stats::opcodeModSkillExplicit+i, new OpModSkill (i)); + interpreter.installSegment5>(Compiler::Stats::opcodeModSkill + i, i); + interpreter.installSegment5>(Compiler::Stats::opcodeModSkillExplicit + i, i); } - interpreter.installSegment5 (Compiler::Stats::opcodeGetPCCrimeLevel, new OpGetPCCrimeLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeSetPCCrimeLevel, new OpSetPCCrimeLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeModPCCrimeLevel, new OpModPCCrimeLevel); - - interpreter.installSegment5 (Compiler::Stats::opcodeAddSpell, new OpAddSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeAddSpellExplicit, new OpAddSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpell, new OpRemoveSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellExplicit, - new OpRemoveSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffects, new OpRemoveSpellEffects); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffectsExplicit, - new OpRemoveSpellEffects); - interpreter.installSegment5 (Compiler::Stats::opcodeResurrect, new OpResurrect); - interpreter.installSegment5 (Compiler::Stats::opcodeResurrectExplicit, - new OpResurrect); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffects, new OpRemoveEffects); - interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffectsExplicit, - new OpRemoveEffects); - - interpreter.installSegment5 (Compiler::Stats::opcodeGetSpell, new OpGetSpell); - interpreter.installSegment5 (Compiler::Stats::opcodeGetSpellExplicit, new OpGetSpell); - - interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRank,new OpPCRaiseRank); - interpreter.installSegment3(Compiler::Stats::opcodePCLowerRank,new OpPCLowerRank); - interpreter.installSegment3(Compiler::Stats::opcodePCJoinFaction,new OpPCJoinFaction); - interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRankExplicit,new OpPCRaiseRank); - interpreter.installSegment3(Compiler::Stats::opcodePCLowerRankExplicit,new OpPCLowerRank); - interpreter.installSegment3(Compiler::Stats::opcodePCJoinFactionExplicit,new OpPCJoinFaction); - interpreter.installSegment3(Compiler::Stats::opcodeGetPCRank,new OpGetPCRank); - interpreter.installSegment3(Compiler::Stats::opcodeGetPCRankExplicit,new OpGetPCRank); - - interpreter.installSegment5(Compiler::Stats::opcodeModDisposition,new OpModDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeModDispositionExplicit,new OpModDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeSetDisposition,new OpSetDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeSetDispositionExplicit,new OpSetDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeGetDisposition,new OpGetDisposition); - interpreter.installSegment5(Compiler::Stats::opcodeGetDispositionExplicit,new OpGetDisposition); - - interpreter.installSegment5 (Compiler::Stats::opcodeGetLevel, new OpGetLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeGetLevelExplicit, new OpGetLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeSetLevel, new OpSetLevel); - interpreter.installSegment5 (Compiler::Stats::opcodeSetLevelExplicit, new OpSetLevel); - - interpreter.installSegment5 (Compiler::Stats::opcodeGetDeadCount, new OpGetDeadCount); - - interpreter.installSegment3 (Compiler::Stats::opcodeGetPCFacRep, new OpGetPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeGetPCFacRepExplicit, new OpGetPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeSetPCFacRep, new OpSetPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeSetPCFacRepExplicit, new OpSetPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeModPCFacRep, new OpModPCFacRep); - interpreter.installSegment3 (Compiler::Stats::opcodeModPCFacRepExplicit, new OpModPCFacRep); - - interpreter.installSegment5 (Compiler::Stats::opcodeGetCommonDisease, new OpGetCommonDisease); - interpreter.installSegment5 (Compiler::Stats::opcodeGetCommonDiseaseExplicit, new OpGetCommonDisease); - interpreter.installSegment5 (Compiler::Stats::opcodeGetBlightDisease, new OpGetBlightDisease); - interpreter.installSegment5 (Compiler::Stats::opcodeGetBlightDiseaseExplicit, new OpGetBlightDisease); - - interpreter.installSegment5 (Compiler::Stats::opcodeGetRace, new OpGetRace); - interpreter.installSegment5 (Compiler::Stats::opcodeGetRaceExplicit, new OpGetRace); - interpreter.installSegment5 (Compiler::Stats::opcodeGetWerewolfKills, new OpGetWerewolfKills); - - interpreter.installSegment3 (Compiler::Stats::opcodePcExpelled, new OpPcExpelled); - interpreter.installSegment3 (Compiler::Stats::opcodePcExpelledExplicit, new OpPcExpelled); - interpreter.installSegment3 (Compiler::Stats::opcodePcExpell, new OpPcExpell); - interpreter.installSegment3 (Compiler::Stats::opcodePcExpellExplicit, new OpPcExpell); - interpreter.installSegment3 (Compiler::Stats::opcodePcClearExpelled, new OpPcClearExpelled); - interpreter.installSegment3 (Compiler::Stats::opcodePcClearExpelledExplicit, new OpPcClearExpelled); - interpreter.installSegment5 (Compiler::Stats::opcodeRaiseRank, new OpRaiseRank); - interpreter.installSegment5 (Compiler::Stats::opcodeRaiseRankExplicit, new OpRaiseRank); - interpreter.installSegment5 (Compiler::Stats::opcodeLowerRank, new OpLowerRank); - interpreter.installSegment5 (Compiler::Stats::opcodeLowerRankExplicit, new OpLowerRank); - - interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath); - interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath); - interpreter.installSegment5 (Compiler::Stats::opcodeOnMurder, new OpOnMurder); - interpreter.installSegment5 (Compiler::Stats::opcodeOnMurderExplicit, new OpOnMurder); - interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout); - interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout); - - interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolf, new OpIsWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolfExplicit, new OpIsWerewolf); - - interpreter.installSegment5 (Compiler::Stats::opcodeBecomeWerewolf, new OpSetWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeBecomeWerewolfExplicit, new OpSetWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolf, new OpSetWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf); - interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics); - interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics); - interpreter.installSegment5 (Compiler::Stats::opcodeGetStat, new OpGetStat); - interpreter.installSegment5 (Compiler::Stats::opcodeGetStatExplicit, new OpGetStat); + interpreter.installSegment5(Compiler::Stats::opcodeGetPCCrimeLevel); + interpreter.installSegment5(Compiler::Stats::opcodeSetPCCrimeLevel); + interpreter.installSegment5(Compiler::Stats::opcodeModPCCrimeLevel); + + interpreter.installSegment5>(Compiler::Stats::opcodeAddSpell); + interpreter.installSegment5>(Compiler::Stats::opcodeAddSpellExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpell); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellEffects); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellEffectsExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeResurrect); + interpreter.installSegment5>(Compiler::Stats::opcodeResurrectExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveEffects); + interpreter.installSegment5>(Compiler::Stats::opcodeRemoveEffectsExplicit); + + interpreter.installSegment5>(Compiler::Stats::opcodeGetSpell); + interpreter.installSegment5>(Compiler::Stats::opcodeGetSpellExplicit); + + interpreter.installSegment3>(Compiler::Stats::opcodePCRaiseRank); + interpreter.installSegment3>(Compiler::Stats::opcodePCLowerRank); + interpreter.installSegment3>(Compiler::Stats::opcodePCJoinFaction); + interpreter.installSegment3>(Compiler::Stats::opcodePCRaiseRankExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodePCLowerRankExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodePCJoinFactionExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodeGetPCRank); + interpreter.installSegment3>(Compiler::Stats::opcodeGetPCRankExplicit); + + interpreter.installSegment5>(Compiler::Stats::opcodeModDisposition); + interpreter.installSegment5>(Compiler::Stats::opcodeModDispositionExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeSetDisposition); + interpreter.installSegment5>(Compiler::Stats::opcodeSetDispositionExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeGetDisposition); + interpreter.installSegment5>(Compiler::Stats::opcodeGetDispositionExplicit); + + interpreter.installSegment5>(Compiler::Stats::opcodeGetLevel); + interpreter.installSegment5>(Compiler::Stats::opcodeGetLevelExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeSetLevel); + interpreter.installSegment5>(Compiler::Stats::opcodeSetLevelExplicit); + + interpreter.installSegment5(Compiler::Stats::opcodeGetDeadCount); + + interpreter.installSegment3>(Compiler::Stats::opcodeGetPCFacRep); + interpreter.installSegment3>(Compiler::Stats::opcodeGetPCFacRepExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodeSetPCFacRep); + interpreter.installSegment3>(Compiler::Stats::opcodeSetPCFacRepExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodeModPCFacRep); + interpreter.installSegment3>(Compiler::Stats::opcodeModPCFacRepExplicit); + + interpreter.installSegment5>(Compiler::Stats::opcodeGetCommonDisease); + interpreter.installSegment5>(Compiler::Stats::opcodeGetCommonDiseaseExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeGetBlightDisease); + interpreter.installSegment5>(Compiler::Stats::opcodeGetBlightDiseaseExplicit); + + interpreter.installSegment5>(Compiler::Stats::opcodeGetRace); + interpreter.installSegment5>(Compiler::Stats::opcodeGetRaceExplicit); + interpreter.installSegment5(Compiler::Stats::opcodeGetWerewolfKills); + + interpreter.installSegment3>(Compiler::Stats::opcodePcExpelled); + interpreter.installSegment3>(Compiler::Stats::opcodePcExpelledExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodePcExpell); + interpreter.installSegment3>(Compiler::Stats::opcodePcExpellExplicit); + interpreter.installSegment3>(Compiler::Stats::opcodePcClearExpelled); + interpreter.installSegment3>(Compiler::Stats::opcodePcClearExpelledExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeRaiseRank); + interpreter.installSegment5>(Compiler::Stats::opcodeRaiseRankExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeLowerRank); + interpreter.installSegment5>(Compiler::Stats::opcodeLowerRankExplicit); + + interpreter.installSegment5>(Compiler::Stats::opcodeOnDeath); + interpreter.installSegment5>(Compiler::Stats::opcodeOnDeathExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeOnMurder); + interpreter.installSegment5>(Compiler::Stats::opcodeOnMurderExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeOnKnockout); + interpreter.installSegment5>(Compiler::Stats::opcodeOnKnockoutExplicit); + + interpreter.installSegment5>(Compiler::Stats::opcodeIsWerewolf); + interpreter.installSegment5>(Compiler::Stats::opcodeIsWerewolfExplicit); + + interpreter.installSegment5>(Compiler::Stats::opcodeBecomeWerewolf); + interpreter.installSegment5>(Compiler::Stats::opcodeBecomeWerewolfExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeUndoWerewolf); + interpreter.installSegment5>(Compiler::Stats::opcodeUndoWerewolfExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeSetWerewolfAcrobatics); + interpreter.installSegment5>(Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit); + interpreter.installSegment5>(Compiler::Stats::opcodeGetStat); + interpreter.installSegment5>(Compiler::Stats::opcodeGetStatExplicit); static const MagicEffect sMagicEffects[] = { { ESM::MagicEffect::ResistMagicka, ESM::MagicEffect::WeaknessToMagicka }, @@ -1498,14 +1484,14 @@ namespace MWScript int positive = sMagicEffects[i].mPositiveEffect; int negative = sMagicEffects[i].mNegativeEffect; - interpreter.installSegment5 (Compiler::Stats::opcodeGetMagicEffect+i, new OpGetMagicEffect (positive, negative)); - interpreter.installSegment5 (Compiler::Stats::opcodeGetMagicEffectExplicit+i, new OpGetMagicEffect (positive, negative)); + interpreter.installSegment5>(Compiler::Stats::opcodeGetMagicEffect + i, positive, negative); + interpreter.installSegment5>(Compiler::Stats::opcodeGetMagicEffectExplicit + i, positive, negative); - interpreter.installSegment5 (Compiler::Stats::opcodeSetMagicEffect+i, new OpSetMagicEffect (positive, negative)); - interpreter.installSegment5 (Compiler::Stats::opcodeSetMagicEffectExplicit+i, new OpSetMagicEffect (positive, negative)); + interpreter.installSegment5>(Compiler::Stats::opcodeSetMagicEffect + i, positive, negative); + interpreter.installSegment5>(Compiler::Stats::opcodeSetMagicEffectExplicit + i, positive, negative); - interpreter.installSegment5 (Compiler::Stats::opcodeModMagicEffect+i, new OpModMagicEffect (positive, negative)); - interpreter.installSegment5 (Compiler::Stats::opcodeModMagicEffectExplicit+i, new OpModMagicEffect (positive, negative)); + interpreter.installSegment5>(Compiler::Stats::opcodeModMagicEffect + i, positive, negative); + interpreter.installSegment5>(Compiler::Stats::opcodeModMagicEffectExplicit + i, positive, negative); } } } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 8a159a5685..57399cf02b 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -799,47 +799,47 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5(Compiler::Transformation::opcodeGetDistance, new OpGetDistance); - interpreter.installSegment5(Compiler::Transformation::opcodeGetDistanceExplicit, new OpGetDistance); - interpreter.installSegment5(Compiler::Transformation::opcodeSetScale,new OpSetScale); - interpreter.installSegment5(Compiler::Transformation::opcodeSetScaleExplicit,new OpSetScale); - interpreter.installSegment5(Compiler::Transformation::opcodeSetAngle,new OpSetAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeSetAngleExplicit,new OpSetAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeGetScale,new OpGetScale); - interpreter.installSegment5(Compiler::Transformation::opcodeGetScaleExplicit,new OpGetScale); - interpreter.installSegment5(Compiler::Transformation::opcodeGetAngle,new OpGetAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeGetAngleExplicit,new OpGetAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeGetPos,new OpGetPos); - interpreter.installSegment5(Compiler::Transformation::opcodeGetPosExplicit,new OpGetPos); - interpreter.installSegment5(Compiler::Transformation::opcodeSetPos,new OpSetPos); - interpreter.installSegment5(Compiler::Transformation::opcodeSetPosExplicit,new OpSetPos); - interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingPos,new OpGetStartingPos); - interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingPosExplicit,new OpGetStartingPos); - interpreter.installSegment5(Compiler::Transformation::opcodePosition,new OpPosition); - interpreter.installSegment5(Compiler::Transformation::opcodePositionExplicit,new OpPosition); - interpreter.installSegment5(Compiler::Transformation::opcodePositionCell,new OpPositionCell); - interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt); - interpreter.installSegment5(Compiler::Transformation::opcodeModScale,new OpModScale); - interpreter.installSegment5(Compiler::Transformation::opcodeModScaleExplicit,new OpModScale); - interpreter.installSegment5(Compiler::Transformation::opcodeRotate,new OpRotate); - interpreter.installSegment5(Compiler::Transformation::opcodeRotateExplicit,new OpRotate); - interpreter.installSegment5(Compiler::Transformation::opcodeRotateWorld,new OpRotateWorld); - interpreter.installSegment5(Compiler::Transformation::opcodeRotateWorldExplicit,new OpRotateWorld); - interpreter.installSegment5(Compiler::Transformation::opcodeSetAtStart,new OpSetAtStart); - interpreter.installSegment5(Compiler::Transformation::opcodeSetAtStartExplicit,new OpSetAtStart); - interpreter.installSegment5(Compiler::Transformation::opcodeMove,new OpMove); - interpreter.installSegment5(Compiler::Transformation::opcodeMoveExplicit,new OpMove); - interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorld,new OpMoveWorld); - interpreter.installSegment5(Compiler::Transformation::opcodeMoveWorldExplicit,new OpMoveWorld); - interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle); - interpreter.installSegment5(Compiler::Transformation::opcodeResetActors, new OpResetActors); - interpreter.installSegment5(Compiler::Transformation::opcodeFixme, new OpFixme); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetDistance); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetDistanceExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetScale); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetScaleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetAngle); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetAngleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetScale); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetScaleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetAngle); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetAngleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetPos); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetPosExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetPos); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetPosExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingPos); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingPosExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodePosition); + interpreter.installSegment5>(Compiler::Transformation::opcodePositionExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodePositionCell); + interpreter.installSegment5>(Compiler::Transformation::opcodePositionCellExplicit); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem); + interpreter.installSegment5>(Compiler::Transformation::opcodePlaceAtPc); + interpreter.installSegment5>(Compiler::Transformation::opcodePlaceAtMe); + interpreter.installSegment5>(Compiler::Transformation::opcodePlaceAtMeExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeModScale); + interpreter.installSegment5>(Compiler::Transformation::opcodeModScaleExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeRotate); + interpreter.installSegment5>(Compiler::Transformation::opcodeRotateExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeRotateWorld); + interpreter.installSegment5>(Compiler::Transformation::opcodeRotateWorldExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetAtStart); + interpreter.installSegment5>(Compiler::Transformation::opcodeSetAtStartExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeMove); + interpreter.installSegment5>(Compiler::Transformation::opcodeMoveExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeMoveWorld); + interpreter.installSegment5>(Compiler::Transformation::opcodeMoveWorldExplicit); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingAngle); + interpreter.installSegment5>(Compiler::Transformation::opcodeGetStartingAngleExplicit); + interpreter.installSegment5(Compiler::Transformation::opcodeResetActors); + interpreter.installSegment5(Compiler::Transformation::opcodeFixme); } } } diff --git a/apps/openmw/mwscript/userextensions.cpp b/apps/openmw/mwscript/userextensions.cpp index 3f443304d7..f929425a9c 100644 --- a/apps/openmw/mwscript/userextensions.cpp +++ b/apps/openmw/mwscript/userextensions.cpp @@ -66,12 +66,12 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { - interpreter.installSegment5 (Compiler::User::opcodeUser1, new OpUser1); - interpreter.installSegment5 (Compiler::User::opcodeUser2, new OpUser2); - interpreter.installSegment5 (Compiler::User::opcodeUser3, new OpUser3); - interpreter.installSegment5 (Compiler::User::opcodeUser3Explicit, new OpUser3); - interpreter.installSegment5 (Compiler::User::opcodeUser4, new OpUser4); - interpreter.installSegment5 (Compiler::User::opcodeUser4Explicit, new OpUser4); + interpreter.installSegment5(Compiler::User::opcodeUser1); + interpreter.installSegment5(Compiler::User::opcodeUser2); + interpreter.installSegment5>(Compiler::User::opcodeUser3); + interpreter.installSegment5>(Compiler::User::opcodeUser3Explicit); + interpreter.installSegment5>(Compiler::User::opcodeUser4); + interpreter.installSegment5>(Compiler::User::opcodeUser4Explicit); } } } diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index c04860afdf..dea2a0dc8a 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -49,10 +49,12 @@ namespace mInterpreter.run(&script.mByteCode[0], static_cast(script.mByteCode.size()), context); } - void installOpcode(int code, Interpreter::Opcode0* opcode) + template + void installOpcode(int code, TArgs&& ...args) { - mInterpreter.installSegment5(code, opcode); + mInterpreter.installSegment5(code, std::forward(args)...); } + protected: void SetUp() override { @@ -472,7 +474,7 @@ messagebox,"this is a %g",a EXPECT_EQ(topic, "OpenMW Unit Test"); } }; - installOpcode(Compiler::Dialogue::opcodeAddTopic, new AddTopic(failed)); + installOpcode(Compiler::Dialogue::opcodeAddTopic, failed); TestInterpreterContext context; run(*script, context); } @@ -715,7 +717,7 @@ messagebox,"this is a %g",a mTopics.erase(mTopics.begin()); } }; - installOpcode(Compiler::Dialogue::opcodeAddTopic, new AddTopic(topics)); + installOpcode(Compiler::Dialogue::opcodeAddTopic, topics); TestInterpreterContext context; run(*script, context); EXPECT_TRUE(topics.empty()); @@ -826,7 +828,7 @@ messagebox,"this is a %g",a } }; bool ran = false; - installOpcode(Compiler::Transformation::opcodePositionCell, new PositionCell(ran)); + installOpcode(Compiler::Transformation::opcodePositionCell, ran); TestInterpreterContext context; context.setLocalShort(0, 0); run(*script, context); diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index b5cb229e84..d5e9bb0cc5 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -11,89 +11,77 @@ namespace Interpreter { - void installOpcodes (Interpreter& interpreter) + void installOpcodes(Interpreter& interpreter) { // generic - interpreter.installSegment0 (0, new OpPushInt); - interpreter.installSegment5 (3, new OpIntToFloat); - interpreter.installSegment5 (6, new OpFloatToInt); - interpreter.installSegment5 (7, new OpNegateInt); - interpreter.installSegment5 (8, new OpNegateFloat); - interpreter.installSegment5 (17, new OpIntToFloat1); - interpreter.installSegment5 (18, new OpFloatToInt1); + interpreter.installSegment0(0); + interpreter.installSegment5(3); + interpreter.installSegment5(6); + interpreter.installSegment5(7); + interpreter.installSegment5(8); + interpreter.installSegment5(17); + interpreter.installSegment5(18); // local variables, global variables & literals - interpreter.installSegment5 (0, new OpStoreLocalShort); - interpreter.installSegment5 (1, new OpStoreLocalLong); - interpreter.installSegment5 (2, new OpStoreLocalFloat); - interpreter.installSegment5 (4, new OpFetchIntLiteral); - interpreter.installSegment5 (5, new OpFetchFloatLiteral); - interpreter.installSegment5 (21, new OpFetchLocalShort); - interpreter.installSegment5 (22, new OpFetchLocalLong); - interpreter.installSegment5 (23, new OpFetchLocalFloat); - interpreter.installSegment5 (39, new OpStoreGlobalShort); - interpreter.installSegment5 (40, new OpStoreGlobalLong); - interpreter.installSegment5 (41, new OpStoreGlobalFloat); - interpreter.installSegment5 (42, new OpFetchGlobalShort); - interpreter.installSegment5 (43, new OpFetchGlobalLong); - interpreter.installSegment5 (44, new OpFetchGlobalFloat); - interpreter.installSegment5 (59, new OpStoreMemberShort (false)); - interpreter.installSegment5 (60, new OpStoreMemberLong (false)); - interpreter.installSegment5 (61, new OpStoreMemberFloat (false)); - interpreter.installSegment5 (62, new OpFetchMemberShort (false)); - interpreter.installSegment5 (63, new OpFetchMemberLong (false)); - interpreter.installSegment5 (64, new OpFetchMemberFloat (false)); - interpreter.installSegment5 (65, new OpStoreMemberShort (true)); - interpreter.installSegment5 (66, new OpStoreMemberLong (true)); - interpreter.installSegment5 (67, new OpStoreMemberFloat (true)); - interpreter.installSegment5 (68, new OpFetchMemberShort (true)); - interpreter.installSegment5 (69, new OpFetchMemberLong (true)); - interpreter.installSegment5 (70, new OpFetchMemberFloat (true)); + interpreter.installSegment5(0); + interpreter.installSegment5(1); + interpreter.installSegment5(2); + interpreter.installSegment5(4); + interpreter.installSegment5(5); + interpreter.installSegment5(21); + interpreter.installSegment5(22); + interpreter.installSegment5(23); + interpreter.installSegment5(39); + interpreter.installSegment5(40); + interpreter.installSegment5(41); + interpreter.installSegment5(42); + interpreter.installSegment5(43); + interpreter.installSegment5(44); + interpreter.installSegment5>(59); + interpreter.installSegment5>(60); + interpreter.installSegment5>(61); + interpreter.installSegment5>(62); + interpreter.installSegment5>(63); + interpreter.installSegment5>(64); + interpreter.installSegment5>(65); + interpreter.installSegment5>(66); + interpreter.installSegment5>(67); + interpreter.installSegment5>(68); + interpreter.installSegment5>(69); + interpreter.installSegment5>(70); // math - interpreter.installSegment5 (9, new OpAddInt); - interpreter.installSegment5 (10, new OpAddInt); - interpreter.installSegment5 (11, new OpSubInt); - interpreter.installSegment5 (12, new OpSubInt); - interpreter.installSegment5 (13, new OpMulInt); - interpreter.installSegment5 (14, new OpMulInt); - interpreter.installSegment5 (15, new OpDivInt); - interpreter.installSegment5 (16, new OpDivInt); - interpreter.installSegment5 (26, - new OpCompare >); - interpreter.installSegment5 (27, - new OpCompare >); - interpreter.installSegment5 (28, - new OpCompare >); - interpreter.installSegment5 (29, - new OpCompare >); - interpreter.installSegment5 (30, - new OpCompare >); - interpreter.installSegment5 (31, - new OpCompare >); + interpreter.installSegment5>(9); + interpreter.installSegment5>(10); + interpreter.installSegment5>(11); + interpreter.installSegment5>(12); + interpreter.installSegment5>(13); + interpreter.installSegment5>(14); + interpreter.installSegment5>(15); + interpreter.installSegment5>(16); + interpreter.installSegment5 >>(26); + interpreter.installSegment5 >>(27); + interpreter.installSegment5 >>(28); + interpreter.installSegment5 >>(29); + interpreter.installSegment5 >>(30); + interpreter.installSegment5 >>(31); - interpreter.installSegment5 (32, - new OpCompare >); - interpreter.installSegment5 (33, - new OpCompare >); - interpreter.installSegment5 (34, - new OpCompare >); - interpreter.installSegment5 (35, - new OpCompare >); - interpreter.installSegment5 (36, - new OpCompare >); - interpreter.installSegment5 (37, - new OpCompare >); + interpreter.installSegment5 >>(32); + interpreter.installSegment5 >>(33); + interpreter.installSegment5 >>(34); + interpreter.installSegment5 >>(35); + interpreter.installSegment5 >>(36); + interpreter.installSegment5 >>(37); // control structures - interpreter.installSegment5 (20, new OpReturn); - interpreter.installSegment5 (24, new OpSkipZero); - interpreter.installSegment5 (25, new OpSkipNonZero); - interpreter.installSegment0 (1, new OpJumpForward); - interpreter.installSegment0 (2, new OpJumpBackward); + interpreter.installSegment5(20); + interpreter.installSegment5(24); + interpreter.installSegment5(25); + interpreter.installSegment0(1); + interpreter.installSegment0(2); // misc - interpreter.installSegment3 (0, new OpMessageBox); - interpreter.installSegment5 (58, new OpReport); + interpreter.installSegment3(0); + interpreter.installSegment5(58); } } diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index c04352a90f..69f3111440 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -7,92 +7,75 @@ namespace Interpreter { + [[noreturn]] static void abortUnknownCode(int segment, int opcode) + { + const std::string error = "unknown opcode " + std::to_string(opcode) + " in segment " + std::to_string(segment); + throw std::runtime_error(error); + } + + [[noreturn]] static void abortUnknownSegment(Type_Code code) + { + const std::string error = "opcode outside of the allocated segment range: " + std::to_string(code); + throw std::runtime_error(error); + } + + template + auto& getDispatcher(const T& segment, unsigned int seg, int opcode) + { + auto it = segment.find(opcode); + if (it == segment.end()) + { + abortUnknownCode(seg, opcode); + } + return it->second; + } + void Interpreter::execute (Type_Code code) { - unsigned int segSpec = code>>30; + unsigned int segSpec = code >> 30; switch (segSpec) { case 0: { - int opcode = code>>24; - unsigned int arg0 = code & 0xffffff; + const int opcode = code >> 24; + const unsigned int arg0 = code & 0xffffff; - std::map::iterator iter = mSegment0.find (opcode); - - if (iter==mSegment0.end()) - abortUnknownCode (0, opcode); - - iter->second->execute (mRuntime, arg0); - - return; + return getDispatcher(mSegment0, 0, opcode)->execute(mRuntime, arg0); } case 2: { - int opcode = (code>>20) & 0x3ff; - unsigned int arg0 = code & 0xfffff; - - std::map::iterator iter = mSegment2.find (opcode); + const int opcode = (code >> 20) & 0x3ff; + const unsigned int arg0 = code & 0xfffff; - if (iter==mSegment2.end()) - abortUnknownCode (2, opcode); - - iter->second->execute (mRuntime, arg0); - - return; + return getDispatcher(mSegment2, 2, opcode)->execute(mRuntime, arg0); } } - segSpec = code>>26; + segSpec = code >> 26; switch (segSpec) { case 0x30: { - int opcode = (code>>8) & 0x3ffff; - unsigned int arg0 = code & 0xff; - - std::map::iterator iter = mSegment3.find (opcode); + const int opcode = (code >> 8) & 0x3ffff; + const unsigned int arg0 = code & 0xff; - if (iter==mSegment3.end()) - abortUnknownCode (3, opcode); - - iter->second->execute (mRuntime, arg0); - - return; + return getDispatcher(mSegment3, 3, opcode)->execute(mRuntime, arg0); } case 0x32: { - int opcode = code & 0x3ffffff; - - std::map::iterator iter = mSegment5.find (opcode); + const int opcode = code & 0x3ffffff; - if (iter==mSegment5.end()) - abortUnknownCode (5, opcode); - - iter->second->execute (mRuntime); - - return; + return getDispatcher(mSegment5, 5, opcode)->execute(mRuntime); } } abortUnknownSegment (code); } - [[noreturn]] void Interpreter::abortUnknownCode (int segment, int opcode) - { - const std::string error = "unknown opcode " + std::to_string(opcode) + " in segment " + std::to_string(segment); - throw std::runtime_error (error); - } - - [[noreturn]] void Interpreter::abortUnknownSegment (Type_Code code) - { - const std::string error = "opcode outside of the allocated segment range: " + std::to_string(code); - throw std::runtime_error (error); - } - void Interpreter::begin() { if (mRunning) @@ -123,49 +106,6 @@ namespace Interpreter Interpreter::Interpreter() : mRunning (false) {} - Interpreter::~Interpreter() - { - for (std::map::iterator iter (mSegment0.begin()); - iter!=mSegment0.end(); ++iter) - delete iter->second; - - for (std::map::iterator iter (mSegment2.begin()); - iter!=mSegment2.end(); ++iter) - delete iter->second; - - for (std::map::iterator iter (mSegment3.begin()); - iter!=mSegment3.end(); ++iter) - delete iter->second; - - for (std::map::iterator iter (mSegment5.begin()); - iter!=mSegment5.end(); ++iter) - delete iter->second; - } - - void Interpreter::installSegment0 (int code, Opcode1 *opcode) - { - assert(mSegment0.find(code) == mSegment0.end()); - mSegment0.insert (std::make_pair (code, opcode)); - } - - void Interpreter::installSegment2 (int code, Opcode1 *opcode) - { - assert(mSegment2.find(code) == mSegment2.end()); - mSegment2.insert (std::make_pair (code, opcode)); - } - - void Interpreter::installSegment3 (int code, Opcode1 *opcode) - { - assert(mSegment3.find(code) == mSegment3.end()); - mSegment3.insert (std::make_pair (code, opcode)); - } - - void Interpreter::installSegment5 (int code, Opcode0 *opcode) - { - assert(mSegment5.find(code) == mSegment5.end()); - mSegment5.insert (std::make_pair (code, opcode)); - } - void Interpreter::run (const Type_Code *code, int codeSize, Context& context) { assert (codeSize>=4); diff --git a/components/interpreter/interpreter.hpp b/components/interpreter/interpreter.hpp index 3aa5e36d58..2e08e6614b 100644 --- a/components/interpreter/interpreter.hpp +++ b/components/interpreter/interpreter.hpp @@ -3,24 +3,25 @@ #include #include +#include +#include +#include #include "runtime.hpp" #include "types.hpp" +#include "opcodes.hpp" namespace Interpreter { - class Opcode0; - class Opcode1; - class Interpreter { std::stack mCallstack; bool mRunning; Runtime mRuntime; - std::map mSegment0; - std::map mSegment2; - std::map mSegment3; - std::map mSegment5; + std::map> mSegment0; + std::map> mSegment2; + std::map> mSegment3; + std::map> mSegment5; // not implemented Interpreter (const Interpreter&); @@ -28,31 +29,44 @@ namespace Interpreter void execute (Type_Code code); - [[noreturn]] void abortUnknownCode (int segment, int opcode); - - [[noreturn]] void abortUnknownSegment (Type_Code code); - void begin(); void end(); + template + void installSegment(TSeg& seg, int code, TOp&& op) + { + assert(seg.find(code) == seg.end()); + seg.emplace(code, std::move(op)); + } + public: Interpreter(); - ~Interpreter(); - - void installSegment0 (int code, Opcode1 *opcode); - ///< ownership of \a opcode is transferred to *this. - - void installSegment2 (int code, Opcode1 *opcode); - ///< ownership of \a opcode is transferred to *this. - - void installSegment3 (int code, Opcode1 *opcode); - ///< ownership of \a opcode is transferred to *this. - - void installSegment5 (int code, Opcode0 *opcode); - ///< ownership of \a opcode is transferred to *this. + template + void installSegment0(int code, TArgs&& ...args) + { + installSegment(mSegment0, code, std::make_unique(std::forward(args)...)); + } + + template + void installSegment2(int code, TArgs&& ...args) + { + installSegment(mSegment2, code, std::make_unique(std::forward(args)...)); + } + + template + void installSegment3(int code, TArgs&& ...args) + { + installSegment(mSegment3, code, std::make_unique(std::forward(args)...)); + } + + template + void installSegment5(int code, TArgs&& ...args) + { + installSegment(mSegment5, code, std::make_unique(std::forward(args)...)); + } void run (const Type_Code *code, int codeSize, Context& context); }; diff --git a/components/interpreter/localopcodes.hpp b/components/interpreter/localopcodes.hpp index 0227327b3a..72a730ae99 100644 --- a/components/interpreter/localopcodes.hpp +++ b/components/interpreter/localopcodes.hpp @@ -206,14 +206,11 @@ namespace Interpreter } }; + template class OpStoreMemberShort : public Opcode0 { - bool mGlobal; - public: - OpStoreMemberShort (bool global) : mGlobal (global) {} - void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; @@ -222,7 +219,7 @@ namespace Interpreter index = runtime[2].mInteger; std::string variable = runtime.getStringLiteral (index); - runtime.getContext().setMemberShort (id, variable, data, mGlobal); + runtime.getContext().setMemberShort (id, variable, data, TGlobal); runtime.pop(); runtime.pop(); @@ -230,14 +227,11 @@ namespace Interpreter } }; + template class OpStoreMemberLong : public Opcode0 { - bool mGlobal; - public: - OpStoreMemberLong (bool global) : mGlobal (global) {} - void execute (Runtime& runtime) override { Type_Integer data = runtime[0].mInteger; @@ -246,7 +240,7 @@ namespace Interpreter index = runtime[2].mInteger; std::string variable = runtime.getStringLiteral (index); - runtime.getContext().setMemberLong (id, variable, data, mGlobal); + runtime.getContext().setMemberLong (id, variable, data, TGlobal); runtime.pop(); runtime.pop(); @@ -254,14 +248,11 @@ namespace Interpreter } }; + template class OpStoreMemberFloat : public Opcode0 { - bool mGlobal; - public: - OpStoreMemberFloat (bool global) : mGlobal (global) {} - void execute (Runtime& runtime) override { Type_Float data = runtime[0].mFloat; @@ -270,7 +261,7 @@ namespace Interpreter index = runtime[2].mInteger; std::string variable = runtime.getStringLiteral (index); - runtime.getContext().setMemberFloat (id, variable, data, mGlobal); + runtime.getContext().setMemberFloat (id, variable, data, TGlobal); runtime.pop(); runtime.pop(); @@ -278,14 +269,11 @@ namespace Interpreter } }; + template class OpFetchMemberShort : public Opcode0 { - bool mGlobal; - public: - OpFetchMemberShort (bool global) : mGlobal (global) {} - void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; @@ -294,19 +282,16 @@ namespace Interpreter std::string variable = runtime.getStringLiteral (index); runtime.pop(); - int value = runtime.getContext().getMemberShort (id, variable, mGlobal); + int value = runtime.getContext().getMemberShort (id, variable, TGlobal); runtime[0].mInteger = value; } }; + template class OpFetchMemberLong : public Opcode0 { - bool mGlobal; - public: - OpFetchMemberLong (bool global) : mGlobal (global) {} - void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; @@ -315,19 +300,16 @@ namespace Interpreter std::string variable = runtime.getStringLiteral (index); runtime.pop(); - int value = runtime.getContext().getMemberLong (id, variable, mGlobal); + int value = runtime.getContext().getMemberLong (id, variable, TGlobal); runtime[0].mInteger = value; } }; + template class OpFetchMemberFloat : public Opcode0 { - bool mGlobal; - public: - OpFetchMemberFloat (bool global) : mGlobal (global) {} - void execute (Runtime& runtime) override { Type_Integer index = runtime[0].mInteger; @@ -336,7 +318,7 @@ namespace Interpreter std::string variable = runtime.getStringLiteral (index); runtime.pop(); - float value = runtime.getContext().getMemberFloat (id, variable, mGlobal); + float value = runtime.getContext().getMemberFloat (id, variable, TGlobal); runtime[0].mFloat = value; } }; From 022fdc49a5b45c9e620b6261d492ebc22583b848 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 27 Jan 2022 21:13:38 +0100 Subject: [PATCH 1996/2859] update tests to use esm3 rename --- apps/openmw_test_suite/esmloader/esmdata.cpp | 16 ++++++++-------- apps/openmw_test_suite/esmloader/load.cpp | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/openmw_test_suite/esmloader/esmdata.cpp b/apps/openmw_test_suite/esmloader/esmdata.cpp index 7e5791561e..a697aae531 100644 --- a/apps/openmw_test_suite/esmloader/esmdata.cpp +++ b/apps/openmw_test_suite/esmloader/esmdata.cpp @@ -1,11 +1,11 @@ -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/apps/openmw_test_suite/esmloader/load.cpp b/apps/openmw_test_suite/esmloader/load.cpp index 048657e65d..d1647d138e 100644 --- a/apps/openmw_test_suite/esmloader/load.cpp +++ b/apps/openmw_test_suite/esmloader/load.cpp @@ -1,10 +1,10 @@ -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include From 88a83bab1ffa06cda668f8018e4b5c2c276a9a5d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 22 Jan 2022 17:27:10 +0100 Subject: [PATCH 1997/2859] Remove predefined data paths `data="?global?data"`, `data=./data` (#6564) --- CHANGELOG.md | 1 + CMakeLists.txt | 3 --- files/openmw.cfg | 1 - files/openmw.cfg.local | 2 -- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b80da169c6..efc831db0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,7 @@ Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp Task #6553: Simplify interpreter instruction registration + Task #6564: Remove predefined data paths `data="?global?data"`, `data=./data` 0.47.0 ------ diff --git a/CMakeLists.txt b/CMakeLists.txt index ae840fb8b3..75cc8ae10f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,7 +181,6 @@ endif() # Set up common paths if (APPLE) - set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "../Resources/resources" CACHE PATH "location of OpenMW resources files") elseif(UNIX) # Paths @@ -199,10 +198,8 @@ elseif(UNIX) ENDIF() SET(SYSCONFDIR "${GLOBAL_CONFIG_PATH}/openmw" CACHE PATH "Set config dir") - set(MORROWIND_DATA_FILES "${DATADIR}/data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "${DATADIR}/resources" CACHE PATH "location of OpenMW resources files") else() - set(MORROWIND_DATA_FILES "data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "resources" CACHE PATH "location of OpenMW resources files") endif(APPLE) diff --git a/files/openmw.cfg b/files/openmw.cfg index d98097c3eb..f524911489 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -3,7 +3,6 @@ # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) content=builtin.omwscripts -data=${MORROWIND_DATA_FILES} data-local="?userdata?data" resources=${OPENMW_RESOURCE_FILES} script-blacklist=Museum diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index 704ac68068..bed9b9b10a 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -3,8 +3,6 @@ # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) content=builtin.omwscripts -data="?global?data" -data=./data data-local="?userdata?data" resources=./resources script-blacklist=Museum From 4521d3987cbaeba6be7d16ab93e5b24b9538f1e4 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 28 Jan 2022 01:15:15 +0300 Subject: [PATCH 1998/2859] Fix out of bounds UV set handling --- components/nifosg/nifloader.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 67cb16b795..36f474a223 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1136,18 +1136,18 @@ namespace NifOsg const auto& uvlist = data->uvlist; int textureStage = 0; - for (const unsigned int uvSet : boundTextures) + for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it, ++textureStage) { + unsigned int uvSet = *it; if (uvSet >= uvlist.size()) { Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " << mFilename; - if (!uvlist.empty()) - geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[0].size(), uvlist[0].data()), osg::Array::BIND_PER_VERTEX); - continue; + if (uvlist.empty()) + continue; + uvSet = 0; } geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), osg::Array::BIND_PER_VERTEX); - textureStage++; } } From 3877d227f454add588d04c70c88cfe243395527a Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 28 Jan 2022 01:00:52 +0100 Subject: [PATCH 1999/2859] Avoid unnecessary copies --- components/esm3/stolenitems.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/esm3/stolenitems.cpp b/components/esm3/stolenitems.cpp index e196fc80c2..a43ae61a60 100644 --- a/components/esm3/stolenitems.cpp +++ b/components/esm3/stolenitems.cpp @@ -32,15 +32,14 @@ namespace ESM std::map, int> ownerMap; while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) { - std::string subname = esm.retSubName().toString(); + const bool isFaction = (esm.retSubName() == "FNAM"); std::string owner = esm.getHString(); - bool isFaction = (subname == "FNAM"); int count; esm.getHNT(count, "COUN"); - ownerMap.insert(std::make_pair(std::make_pair(owner, isFaction), count)); + ownerMap.emplace(std::make_pair(std::move(owner), isFaction), count); } - mStolenItems[itemid] = ownerMap; + mStolenItems.insert_or_assign(std::move(itemid), std::move(ownerMap)); } } From bf692a4bfae3c8d90aefcd09b55e3fcf45dda543 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 28 Jan 2022 03:27:10 +0100 Subject: [PATCH 2000/2859] Add more tests for fixed string --- .../esm/test_fixed_string.cpp | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index bd598cc932..b59f19e7bb 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -105,3 +105,46 @@ TEST(EsmFixedString, is_pod) ASSERT_TRUE(std::is_pod::value); ASSERT_TRUE(std::is_pod::value); } + +TEST(EsmFixedString, assign_should_zero_untouched_bytes_for_4) +{ + ESM::NAME value; + value = static_cast(0xFFFFFFFFu); + value.assign(std::string(1, 'a')); + EXPECT_EQ(value, static_cast('a')) << value.toInt(); +} + +TEST(EsmFixedString, assign_should_only_truncate_for_4) +{ + ESM::NAME value; + value.assign(std::string(5, 'a')); + EXPECT_EQ(value, std::string(4, 'a')); +} + +TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero) +{ + ESM::FIXED_STRING<17> value; + value.assign(std::string(20, 'a')); + EXPECT_EQ(value, std::string(16, 'a')); +} + +TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero_for_32) +{ + ESM::NAME32 value; + value.assign(std::string(33, 'a')); + EXPECT_EQ(value, std::string(31, 'a')); +} + +TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero_for_64) +{ + ESM::NAME64 value; + value.assign(std::string(65, 'a')); + EXPECT_EQ(value, std::string(63, 'a')); +} + +TEST(EsmFixedString, assignment_operator_is_supported_for_uint32) +{ + ESM::NAME value; + value = static_cast(0xFEDCBA98u); + EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); +} From 960dd1f7088f6aaede6c19d5bea9c012b22ed578 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 28 Jan 2022 09:31:45 +0000 Subject: [PATCH 2001/2859] Lua UI templates --- apps/openmw/mwlua/luabindings.hpp | 1 - components/CMakeLists.txt | 4 +- components/lua_ui/element.cpp | 182 ++++++++++-------- components/lua_ui/image.cpp | 51 +++++ components/lua_ui/image.hpp | 37 ++++ components/lua_ui/properties.hpp | 90 ++++++--- components/lua_ui/text.cpp | 19 +- components/lua_ui/text.hpp | 3 +- components/lua_ui/textedit.cpp | 7 +- components/lua_ui/textedit.hpp | 2 +- components/lua_ui/util.cpp | 4 + components/lua_ui/widget.cpp | 153 ++++++++++----- components/lua_ui/widget.hpp | 53 +++-- components/lua_ui/window.cpp | 55 +++--- components/lua_ui/window.hpp | 13 +- .../lua-scripting/user_interface.rst | 21 +- .../lua-scripting/widgets/widget.rst | 17 ++ files/mygui/core.skin | 4 + 18 files changed, 490 insertions(+), 226 deletions(-) create mode 100644 components/lua_ui/image.cpp create mode 100644 components/lua_ui/image.hpp diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index cfaf50c4f6..377d6d1e4f 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -63,7 +63,6 @@ namespace MWLua // Implemented in uibindings.cpp sol::table initUserInterfacePackage(const Context&); - void clearUserInterface(); // Implemented in inputbindings.cpp sol::table initInputPackage(const Context&); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 89b27b0d94..131581f7f7 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -162,8 +162,8 @@ add_component_dir (queries ) add_component_dir (lua_ui - widget element util layers content - text textedit window + properties widget element util layers content + text textedit window image ) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index dcb21c32a0..c9e0fc309e 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -7,128 +7,144 @@ namespace LuaUi { - std::string widgetType(const sol::table& layout) + namespace LayoutKeys { - return layout.get_or("type", std::string("LuaWidget")); + constexpr std::string_view type = "type"; + constexpr std::string_view name = "name"; + constexpr std::string_view layer = "layer"; + constexpr std::string_view templateLayout = "template"; + constexpr std::string_view props = "props"; + constexpr std::string_view events = "events"; + constexpr std::string_view content = "content"; + constexpr std::string_view external = "external"; } - Content content(const sol::table& layout) + std::string widgetType(const sol::table& layout) { - auto optional = layout.get>("content"); - if (optional.has_value()) - return optional.value(); - else - return Content(); + return layout.get_or(LayoutKeys::type, std::string("LuaWidget")); } - void setProperties(LuaUi::WidgetExtension* ext, const sol::table& layout) + void destroyWidget(LuaUi::WidgetExtension* ext) { - ext->setProperties(layout.get("props")); + ext->deinitialize(); + MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); } - void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::table& layout) + WidgetExtension* createWidget(const sol::table& layout); + void updateWidget(WidgetExtension* ext, const sol::table& layout); + + std::vector updateContent( + const std::vector& children, const sol::object& contentObj) { - ext->clearCallbacks(); - auto events = layout.get>("events"); - if (events.has_value()) + std::vector result; + if (contentObj == sol::nil) { - events.value().for_each([ext](const sol::object& name, const sol::object& callback) - { - if (name.is() && callback.is()) - ext->setCallback(name.as(), callback.as()); - else if (!name.is()) - Log(Debug::Warning) << "UI event key must be a string"; - else if (!callback.is()) - Log(Debug::Warning) << "UI event handler for key \"" << name.as() - << "\" must be an openmw.async.callback"; - }); + for (WidgetExtension* w : children) + destroyWidget(w); + return result; } + if (!contentObj.is()) + throw std::logic_error("Layout content field must be a openmw.ui.content"); + Content content = contentObj.as(); + result.resize(content.size()); + size_t minSize = std::min(children.size(), content.size()); + for (size_t i = 0; i < minSize; i++) + { + WidgetExtension* ext = children[i]; + sol::table newLayout = content.at(i); + if (ext->widget()->getTypeName() == widgetType(newLayout) + && ext->getLayout() == newLayout) + { + updateWidget(ext, newLayout); + } + else + { + destroyWidget(ext); + ext = createWidget(newLayout); + } + result[i] = ext; + } + for (size_t i = minSize; i < children.size(); i++) + destroyWidget(children[i]); + for (size_t i = minSize; i < content.size(); i++) + result[i] = createWidget(content.at(i)); + return result; } - void setLayout(LuaUi::WidgetExtension* ext, const sol::table& layout) + void setTemplate(WidgetExtension* ext, const sol::object& templateLayout) { - ext->setLayout(layout); + // \todo remove when none of the widgets require this workaround + sol::object skin = LuaUtil::getFieldOrNil(templateLayout, "skin"); + if (skin.is()) + ext->widget()->changeWidgetSkin(skin.as()); + + sol::object props = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::props); + ext->setTemplateProperties(props); + sol::object content = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::content); + ext->setTemplateChildren(updateContent(ext->templateChildren(), content)); + } + + void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj) + { + ext->clearCallbacks(); + if (eventsObj == sol::nil) + return; + if (!eventsObj.is()) + throw std::logic_error("The \"events\" layout field must be a table of callbacks"); + auto events = eventsObj.as(); + events.for_each([ext](const sol::object& name, const sol::object& callback) + { + if (name.is() && callback.is()) + ext->setCallback(name.as(), callback.as()); + else if (!name.is()) + Log(Debug::Warning) << "UI event key must be a string"; + else if (!callback.is()) + Log(Debug::Warning) << "UI event handler for key \"" << name.as() + << "\" must be an openmw.async.callback"; + }); } - LuaUi::WidgetExtension* createWidget(const sol::table& layout, LuaUi::WidgetExtension* parent) + WidgetExtension* createWidget(const sol::table& layout) { std::string type = widgetType(layout); - std::string skin = layout.get_or("skin", std::string()); - std::string name = layout.get_or("name", std::string()); + std::string name = layout.get_or(LayoutKeys::name, std::string()); static auto widgetTypeMap = widgetTypeToName(); if (widgetTypeMap.find(type) == widgetTypeMap.end()) throw std::logic_error(std::string("Invalid widget type ") += type); MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( - type, skin, + type, "", MyGUI::IntCoord(), MyGUI::Align::Default, std::string(), name); - LuaUi::WidgetExtension* ext = dynamic_cast(widget); + WidgetExtension* ext = dynamic_cast(widget); if (!ext) throw std::runtime_error("Invalid widget!"); - ext->initialize(layout.lua_state(), widget); - if (parent != nullptr) - widget->attachToWidget(parent->widget()); - - setEventCallbacks(ext, layout); - setProperties(ext, layout); - setLayout(ext, layout); - - Content cont = content(layout); - for (size_t i = 0; i < cont.size(); i++) - ext->addChild(createWidget(cont.at(i), ext)); + updateWidget(ext, layout); return ext; } - void destroyWidget(LuaUi::WidgetExtension* ext) - { - ext->deinitialize(); - MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); - } - - void updateWidget(const sol::table& layout, LuaUi::WidgetExtension* ext) + void updateWidget(WidgetExtension* ext, const sol::table& layout) { - setEventCallbacks(ext, layout); - setProperties(ext, layout); - setLayout(ext, layout); - - Content newContent = content(layout); - - size_t oldSize = ext->childCount(); - size_t newSize = newContent.size(); - size_t minSize = std::min(oldSize, newSize); - for (size_t i = 0; i < minSize; i++) - { - LuaUi::WidgetExtension* oldWidget = ext->childAt(i); - sol::table newChild = newContent.at(i); - - if (oldWidget->widget()->getTypeName() != widgetType(newChild)) - { - destroyWidget(oldWidget); - ext->assignChild(i, createWidget(newChild, ext)); - } - else - updateWidget(newChild, oldWidget); - } - - for (size_t i = minSize; i < oldSize; i++) - destroyWidget(ext->eraseChild(i)); + ext->setLayout(layout); + ext->setExternal(layout.get(LayoutKeys::external)); + setTemplate(ext, layout.get(LayoutKeys::templateLayout)); + ext->setProperties(layout.get(LayoutKeys::props)); + setEventCallbacks(ext, layout.get(LayoutKeys::events)); - for (size_t i = minSize; i < newSize; i++) - ext->addChild(createWidget(newContent.at(i), ext)); + ext->setChildren(updateContent(ext->children(), layout.get(LayoutKeys::content))); } - void setLayer(const sol::table& layout, LuaUi::WidgetExtension* ext) + void setLayer(WidgetExtension* ext, const sol::table& layout) { MyGUI::ILayer* layerNode = ext->widget()->getLayer(); std::string currentLayer = layerNode ? layerNode->getName() : std::string(); - std::string newLayer = layout.get_or("layer", std::string()); + std::string newLayer = layout.get_or(LayoutKeys::layer, std::string()); if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer)) - throw std::logic_error(std::string("Layer ") += newLayer += " doesn't exist"); + throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist"); else if (newLayer != currentLayer) { MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget()); @@ -157,8 +173,8 @@ namespace LuaUi assert(!mRoot); if (!mRoot) { - mRoot = createWidget(mLayout, nullptr); - setLayer(mLayout, mRoot); + mRoot = createWidget(mLayout); + setLayer(mRoot, mLayout); } } @@ -166,8 +182,8 @@ namespace LuaUi { if (mRoot && mUpdate) { - updateWidget(mLayout, mRoot); - setLayer(mLayout, mRoot); + updateWidget(mRoot, mLayout); + setLayer(mRoot, mLayout); } mUpdate = false; } diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp new file mode 100644 index 0000000000..c8fdafbb66 --- /dev/null +++ b/components/lua_ui/image.cpp @@ -0,0 +1,51 @@ +#include "image.hpp" + +#include + +namespace LuaUi +{ + void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) + { + mCurrentCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); + mAlign = MyGUI::Align::Stretch; + MyGUI::TileRect::_setAlign(_oldsize); + mTileSize = mSetTileSize; + + // zero tilesize stands for not tiling + if (mTileSize.width == 0) + mTileSize.width = mCoord.width; + if (mTileSize.height == 0) + mTileSize.height = mCoord.height; + + // mCoord could be zero, prevent division by 0 + // use arbitrary large numbers to prevent performance issues + if (mTileSize.width == 0) + mTileSize.width = 1e7; + if (mTileSize.height == 0) + mTileSize.height = 1e7; + } + + LuaImage::LuaImage() + { + changeWidgetSkin("LuaImage"); + mTileRect = dynamic_cast(getSubWidgetMain()); + } + + void LuaImage::updateProperties() + { + setImageTexture(propertyValue("path", std::string())); + + bool tileH = propertyValue("tileH", false); + bool tileV = propertyValue("tileV", false); + MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(_getTextureName()); + MyGUI::IntSize textureSize; + if (texture != nullptr) + textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight()); + mTileRect->updateSize(MyGUI::IntSize( + tileH ? textureSize.width : 0, + tileV ? textureSize.height : 0 + )); + + WidgetExtension::updateProperties(); + } +} diff --git a/components/lua_ui/image.hpp b/components/lua_ui/image.hpp new file mode 100644 index 0000000000..4073d1bd2a --- /dev/null +++ b/components/lua_ui/image.hpp @@ -0,0 +1,37 @@ +#ifndef OPENMW_LUAUI_IMAGE +#define OPENMW_LUAUI_IMAGE + +#include +#include + +#include "widget.hpp" + +namespace LuaUi +{ + class LuaTileRect : public MyGUI::TileRect + { + MYGUI_RTTI_DERIVED(LuaTileRect) + + public: + void _setAlign(const MyGUI::IntSize& _oldsize) override; + + void updateSize(MyGUI::IntSize tileSize) { mSetTileSize = tileSize; } + + protected: + MyGUI::IntSize mSetTileSize; + }; + + class LuaImage : public MyGUI::ImageBox, public WidgetExtension + { + MYGUI_RTTI_DERIVED(LuaImage) + + public: + LuaImage(); + + protected: + virtual void updateProperties() override; + LuaTileRect* mTileRect; + }; +} + +#endif // OPENMW_LUAUI_IMAGE diff --git a/components/lua_ui/properties.hpp b/components/lua_ui/properties.hpp index 7f23a5ce6c..7c47291b84 100644 --- a/components/lua_ui/properties.hpp +++ b/components/lua_ui/properties.hpp @@ -9,56 +9,82 @@ namespace LuaUi { - template - sol::optional getProperty(sol::object from, std::string_view field) { - sol::object value = LuaUtil::getFieldOrNil(from, field); - if (value == sol::nil) - return sol::nullopt; - if (value.is()) - return value.as(); - std::string error("Property \""); - error += field; - error += "\" has an invalid value \""; - error += LuaUtil::toString(value); - error += "\""; - throw std::logic_error(error); + template + constexpr bool isMyGuiVector() { + return + std::is_same() || + std::is_same() || + std::is_same() || + std::is_same(); } - template - T parseProperty(sol::object from, std::string_view field, const T& defaultValue) + template + sol::optional parseValue( + sol::object table, + std::string_view field, + std::string_view errorPrefix) { - sol::optional opt = getProperty(from, field); - if (opt.has_value()) - return opt.value(); + sol::object opt = LuaUtil::getFieldOrNil(table, field); + if (opt != sol::nil && !opt.is()) + { + std::string error(errorPrefix); + error += " \""; + error += field; + error += "\" has an invalid value \""; + error += LuaUtil::toString(opt); + error += "\""; + throw std::logic_error(error); + } + if (!opt.is()) + return sol::nullopt; + + LuaT luaT = opt.as(); + if constexpr (isMyGuiVector()) + return T(luaT.x(), luaT.y()); else - return defaultValue; + return luaT; } template - MyGUI::types::TPoint parseProperty( - sol::object from, + sol::optional parseValue( + sol::object table, std::string_view field, - const MyGUI::types::TPoint& defaultValue) + std::string_view errorPrefix) { - auto v = getProperty(from, field); - if (v.has_value()) - return MyGUI::types::TPoint(v.value().x(), v.value().y()); + if constexpr (isMyGuiVector()) + return parseValue(table, field, errorPrefix); else - return defaultValue; + return parseValue(table, field, errorPrefix); } template - MyGUI::types::TSize parseProperty( - sol::object from, + T parseProperty( + sol::object props, + sol::object templateProps, std::string_view field, - const MyGUI::types::TSize& defaultValue) + const T& defaultValue) { - auto v = getProperty(from, field); - if (v.has_value()) - return MyGUI::types::TSize(v.value().x(), v.value().y()); + auto propOptional = parseValue(props, field, "Property"); + auto templateOptional = parseValue(templateProps, field, "Template property"); + + if (propOptional.has_value()) + return propOptional.value(); + else if (templateOptional.has_value()) + return templateOptional.value(); else return defaultValue; } + + template + T parseExternal( + sol::object external, + std::string_view field, + const T& defaultValue) + { + auto optional = parseValue(external, field, "External value"); + + return optional.value_or(defaultValue); + } } #endif // !OPENMW_LUAUI_PROPERTIES diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index f93f8eecf0..47e2b574cc 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -5,13 +5,22 @@ namespace LuaUi { LuaText::LuaText() : mAutoSized(true) - {} + { + changeWidgetSkin("NormalText"); + } + + void LuaText::updateProperties() + { + setCaption(propertyValue("caption", std::string())); + mAutoSized = propertyValue("autoSize", true); + WidgetExtension::updateProperties(); + } - void LuaText::setProperties(sol::object props) + void LuaText::setCaption(const MyGUI::UString& caption) { - setCaption(parseProperty(props, "caption", std::string())); - mAutoSized = parseProperty(props, "autoSize", true); - WidgetExtension::setProperties(props); + MyGUI::TextBox::setCaption(caption); + if (mAutoSized) + updateCoord(); } MyGUI::IntSize LuaText::calculateSize() diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index 533dc4f453..c435f6687a 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -13,7 +13,8 @@ namespace LuaUi public: LuaText(); - virtual void setProperties(sol::object) override; + virtual void updateProperties() override; + void setCaption(const MyGUI::UString& caption) override; private: bool mAutoSized; diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index 9cdc716ce4..5fbdf3e6d8 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -2,9 +2,10 @@ namespace LuaUi { - void LuaTextEdit::setProperties(sol::object props) + void LuaTextEdit::updateProperties() { - setCaption(parseProperty(props, "caption", std::string())); - WidgetExtension::setProperties(props); + setCaption(propertyValue("caption", std::string())); + + WidgetExtension::updateProperties(); } } diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index d14f54d659..dfc649994a 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -11,7 +11,7 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaTextEdit) - virtual void setProperties(sol::object) override; + virtual void updateProperties() override; }; } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 8cadbc9cc2..1e13c97a80 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -6,6 +6,7 @@ #include "text.hpp" #include "textedit.hpp" #include "window.hpp" +#include "image.hpp" #include "element.hpp" @@ -18,6 +19,8 @@ namespace LuaUi MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("BasisSkin"); } const std::unordered_map& widgetTypeToName() @@ -27,6 +30,7 @@ namespace LuaUi { "LuaText", "Text" }, { "LuaTextEdit", "TextEdit" }, { "LuaWindow", "Window" }, + { "LuaImage", "Image" }, }; return types; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 50c6a193e6..027adc44ca 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -16,22 +16,15 @@ namespace LuaUi , mAnchor() , mLua{ nullptr } , mWidget{ nullptr } + , mSlot(this) , mLayout{ sol::nil } {} - void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const - { - auto it = mCallbacks.find(name); - if (it != mCallbacks.end()) - it->second(argument, mLayout); - } - void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) { mLua = lua; mWidget = self; - - mWidget->eventChangeCoord += MyGUI::newDelegate(this, &WidgetExtension::updateChildrenCoord); + updateTemplate(); initialize(); } @@ -71,8 +64,56 @@ namespace LuaUi mWidget->eventKeySetFocus.clear(); mWidget->eventKeyLostFocus.clear(); - for (WidgetExtension* child : mContent) - child->deinitialize(); + for (WidgetExtension* w : mChildren) + w->deinitialize(); + for (WidgetExtension* w : mTemplateChildren) + w->deinitialize(); + } + + void WidgetExtension::attach(WidgetExtension* ext) + { + ext->widget()->attachToWidget(mSlot->widget()); + ext->updateCoord(); + } + + WidgetExtension* WidgetExtension::findFirst(std::string_view flagName) + { + if (externalValue(flagName, false)) + return this; + for (WidgetExtension* w : mChildren) + { + WidgetExtension* result = w->findFirst(flagName); + if (result != nullptr) + return result; + } + return nullptr; + } + + void WidgetExtension::findAll(std::string_view flagName, std::vector& result) + { + if (externalValue(flagName, false)) + result.push_back(this); + for (WidgetExtension* w : mChildren) + w->findAll(flagName, result); + } + + WidgetExtension* WidgetExtension::findFirstInTemplates(std::string_view flagName) + { + for (WidgetExtension* w : mTemplateChildren) + { + WidgetExtension* result = w->findFirst(flagName); + if (result != nullptr) + return result; + } + return nullptr; + } + + std::vector WidgetExtension::findAllInTemplates(std::string_view flagName) + { + std::vector result; + for (WidgetExtension* w : mTemplateChildren) + w->findAll(flagName, result); + return result; } sol::table WidgetExtension::makeTable() const @@ -91,9 +132,9 @@ namespace LuaUi sol::object WidgetExtension::mouseEvent(int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const { - auto position = osg::Vec2f(left, top); - auto absolutePosition = mWidget->getAbsolutePosition(); - auto offset = position - osg::Vec2f(absolutePosition.left, absolutePosition.top); + osg::Vec2f position(left, top); + MyGUI::IntPoint absolutePosition = mWidget->getAbsolutePosition(); + osg::Vec2f offset = position - osg::Vec2f(absolutePosition.left, absolutePosition.top); sol::table table = makeTable(); table["position"] = position; table["offset"] = offset; @@ -101,31 +142,36 @@ namespace LuaUi return table; } - void WidgetExtension::addChild(WidgetExtension* ext) - { - mContent.push_back(ext); - } - - WidgetExtension* WidgetExtension::childAt(size_t index) const + void WidgetExtension::setChildren(const std::vector& children) { - return mContent.at(index); + mChildren.resize(children.size()); + for (size_t i = 0; i < children.size(); ++i) + { + mChildren[i] = children[i]; + attach(mChildren[i]); + } } - void WidgetExtension::assignChild(size_t index, WidgetExtension* ext) + void WidgetExtension::setTemplateChildren(const std::vector& children) { - if (mContent.size() <= index) - throw std::logic_error("Invalid widget child index"); - mContent[index] = ext; + mTemplateChildren.resize(children.size()); + for (size_t i = 0; i < children.size(); ++i) + { + mTemplateChildren[i] = children[i]; + mTemplateChildren[i]->widget()->attachToWidget(mWidget); + } + updateTemplate(); } - WidgetExtension* WidgetExtension::eraseChild(size_t index) + void WidgetExtension::updateTemplate() { - if (mContent.size() <= index) - throw std::logic_error("Invalid widget child index"); - auto it = mContent.begin() + index; - WidgetExtension* ext = *it; - mContent.erase(it); - return ext; + WidgetExtension* oldSlot = mSlot; + mSlot = findFirstInTemplates("slot"); + if (mSlot == nullptr) + mSlot = this; + if (mSlot != oldSlot) + for (WidgetExtension* w : mChildren) + attach(w); } void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) @@ -150,25 +196,39 @@ namespace LuaUi void WidgetExtension::updateCoord() { - mWidget->setCoord(calculateCoord()); + MyGUI::IntCoord oldCoord = mWidget->getCoord(); + MyGUI::IntCoord newCoord = calculateCoord(); + + if (oldCoord != newCoord) + mWidget->setCoord(newCoord); + if (oldCoord.size() != newCoord.size()) + updateChildrenCoord(); } void WidgetExtension::setProperties(sol::object props) { - mAbsoluteCoord = parseProperty(props, "position", MyGUI::IntPoint()); - mAbsoluteCoord = parseProperty(props, "size", MyGUI::IntSize()); - mRelativeCoord = parseProperty(props, "relativePosition", MyGUI::FloatPoint()); - mRelativeCoord = parseProperty(props, "relativeSize", MyGUI::FloatSize()); - mAnchor = parseProperty(props, "anchor", MyGUI::FloatSize()); - mWidget->setVisible(parseProperty(props, "visible", true)); - + mProperties = props; + updateProperties(); updateCoord(); } - void WidgetExtension::updateChildrenCoord(MyGUI::Widget* _widget) + void WidgetExtension::updateProperties() + { + mAbsoluteCoord = propertyValue("position", MyGUI::IntPoint()); + mAbsoluteCoord = propertyValue("size", MyGUI::IntSize()); + mRelativeCoord = propertyValue("relativePosition", MyGUI::FloatPoint()); + mRelativeCoord = propertyValue("relativeSize", MyGUI::FloatSize()); + mAnchor = propertyValue("anchor", MyGUI::FloatSize()); + mWidget->setVisible(propertyValue("visible", true)); + mWidget->setPointer(propertyValue("pointer", std::string("arrow"))); + } + + void WidgetExtension::updateChildrenCoord() { - for (auto& child : mContent) - child->updateCoord(); + for (WidgetExtension* w : mTemplateChildren) + w->updateCoord(); + for (WidgetExtension* w : mChildren) + w->updateCoord(); } MyGUI::IntSize WidgetExtension::calculateSize() @@ -199,6 +259,13 @@ namespace LuaUi return newCoord; } + void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const + { + auto it = mCallbacks.find(name); + if (it != mCallbacks.end()) + it->second(argument, mLayout); + } + void WidgetExtension::keyPress(MyGUI::Widget*, MyGUI::KeyCode code, MyGUI::Char ch) { if (code == MyGUI::KeyCode::None) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index f8ba105be5..4085963137 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -26,35 +26,57 @@ namespace LuaUi // must be called after before destroying the underlying MyGUI::Widget virtual void deinitialize(); - void addChild(WidgetExtension* ext); - WidgetExtension* childAt(size_t index) const; - void assignChild(size_t index, WidgetExtension* ext); - WidgetExtension* eraseChild(size_t index); - size_t childCount() const { return mContent.size(); } - MyGUI::Widget* widget() const { return mWidget; } + const std::vector& children() { return mChildren; } + void setChildren(const std::vector&); + + const std::vector& templateChildren() { return mTemplateChildren; } + void setTemplateChildren(const std::vector&); + void setCallback(const std::string&, const LuaUtil::Callback&); void clearCallbacks(); - virtual void setProperties(sol::object); + void setProperties(sol::object); + void setTemplateProperties(sol::object props) { mTemplateProperties = props; } + + void setExternal(sol::object external) { mExternal = external; } MyGUI::IntCoord forcedCoord(); void setForcedCoord(const MyGUI::IntCoord& offset); void updateCoord(); + const sol::table& getLayout() { return mLayout; } void setLayout(const sol::table& layout) { mLayout = layout; } + template + T externalValue(std::string_view name, const T& defaultValue) + { + return parseExternal(mExternal, name, defaultValue); + } + protected: virtual void initialize(); sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; - + virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); + template + T propertyValue(std::string_view name, const T& defaultValue) + { + return parseProperty(mProperties, mTemplateProperties, name, defaultValue); + } + + WidgetExtension* findFirstInTemplates(std::string_view flagName); + std::vector findAllInTemplates(std::string_view flagName); + + virtual void updateTemplate(); + virtual void updateProperties(); + void triggerEvent(std::string_view name, const sol::object& argument) const; // offsets the position and size, used only in C++ widget code @@ -71,12 +93,21 @@ namespace LuaUi // use lua_State* instead of sol::state_view because MyGUI requires a default constructor lua_State* mLua; MyGUI::Widget* mWidget; - - std::vector mContent; + std::vector mChildren; + std::vector mTemplateChildren; + WidgetExtension* mSlot; std::map> mCallbacks; sol::table mLayout; + sol::object mProperties; + sol::object mTemplateProperties; + sol::object mExternal; + + void attach(WidgetExtension* ext); + + WidgetExtension* findFirst(std::string_view name); + void findAll(std::string_view flagName, std::vector& result); - void updateChildrenCoord(MyGUI::Widget*); + void updateChildrenCoord(); void keyPress(MyGUI::Widget*, MyGUI::KeyCode, MyGUI::Char); void keyRelease(MyGUI::Widget*, MyGUI::KeyCode); diff --git a/components/lua_ui/window.cpp b/components/lua_ui/window.cpp index 5d34bf8212..6e6b80bf22 100644 --- a/components/lua_ui/window.cpp +++ b/components/lua_ui/window.cpp @@ -11,46 +11,40 @@ namespace LuaUi , mMoveResize() {} - void LuaWindow::initialize() + void LuaWindow::updateTemplate() { - WidgetExtension::initialize(); - - assignWidget(mCaption, "Caption"); - if (mCaption) + for (auto& [w, _] : mActionWidgets) { - mCaption->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); - mCaption->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); - } - for (auto w : getSkinWidgetsByName("Action")) - { - w->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); - w->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); + w->eventMouseButtonPressed.clear(); + w->eventMouseDrag.m_event.clear(); } - } + mActionWidgets.clear(); - void LuaWindow::deinitialize() - { - WidgetExtension::deinitialize(); + WidgetExtension* captionWidget = findFirstInTemplates("caption"); + mCaption = dynamic_cast(captionWidget); if (mCaption) + mActionWidgets.emplace(mCaption->widget(), mCaption); + for (WidgetExtension* ext : findAllInTemplates("action")) + mActionWidgets.emplace(ext->widget(), ext); + + for (auto& [w, _] : mActionWidgets) { - mCaption->eventMouseButtonPressed.clear(); - mCaption->eventMouseDrag.m_event.clear(); - } - for (auto w : getSkinWidgetsByName("Action")) - { - w->eventMouseButtonPressed.clear(); - w->eventMouseDrag.m_event.clear(); + w->eventMouseButtonPressed += MyGUI::newDelegate(this, &LuaWindow::notifyMousePress); + w->eventMouseDrag += MyGUI::newDelegate(this, &LuaWindow::notifyMouseDrag); } + + WidgetExtension::updateTemplate(); } - void LuaWindow::setProperties(sol::object props) + void LuaWindow::updateProperties() { if (mCaption) - mCaption->setCaption(parseProperty(props, "caption", std::string())); + mCaption->setCaption(propertyValue("caption", std::string())); mMoveResize = MyGUI::IntCoord(); setForcedCoord(mMoveResize); - WidgetExtension::setProperties(props); + + WidgetExtension::updateProperties(); } void LuaWindow::notifyMousePress(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) @@ -61,10 +55,11 @@ namespace LuaUi mPreviousMouse.left = left; mPreviousMouse.top = top; - if (sender->isUserString("Scale")) - mChangeScale = MyGUI::IntCoord::parse(sender->getUserString("Scale")); - else - mChangeScale = MyGUI::IntCoord(1, 1, 0, 0); + WidgetExtension* ext = mActionWidgets[sender]; + + mChangeScale = MyGUI::IntCoord( + ext->externalValue("move", MyGUI::IntPoint(1, 1)), + ext->externalValue("resize", MyGUI::IntSize(0, 0))); } void LuaWindow::notifyMouseDrag(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) diff --git a/components/lua_ui/window.hpp b/components/lua_ui/window.hpp index f34779c8f7..be3b3e2c23 100644 --- a/components/lua_ui/window.hpp +++ b/components/lua_ui/window.hpp @@ -3,9 +3,8 @@ #include -#include - #include "widget.hpp" +#include "text.hpp" namespace LuaUi { @@ -15,20 +14,18 @@ namespace LuaUi public: LuaWindow(); - virtual void setProperties(sol::object) override; + virtual void updateTemplate() override; + virtual void updateProperties() override; private: - // \todo replace with LuaText when skins are properly implemented - MyGUI::TextBox* mCaption; + LuaText* mCaption; + std::map mActionWidgets; MyGUI::IntPoint mPreviousMouse; MyGUI::IntCoord mChangeScale; MyGUI::IntCoord mMoveResize; protected: - virtual void initialize() override; - virtual void deinitialize() override; - void notifyMousePress(MyGUI::Widget*, int, int, MyGUI::MouseButton); void notifyMouseDrag(MyGUI::Widget*, int, int, MyGUI::MouseButton); }; diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 73ca57d1a7..13420d318c 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -22,7 +22,9 @@ Every widget is defined by a layout, which is a Lua table with the following fie 4. `content`: a Content (`openmw.ui.content`), which contains layouts for the children of this widget. 5. | `name`: an arbitrary string, the only limitatiion is it being unique within a `Content`. | Helpful for navigatilng through the layouts. -6. `layer`: only applies for the root widget. +6. `layer`: only applies for the root widget. (Windows, HUD, etc) +7. `template`: a Lua table which pre-defines a layout for this widget. See Templates below for more details. +8. `external`: similar to properties, but they affect how other widgets interact with this one. See the widget pages for details. Layers ------ @@ -57,7 +59,14 @@ A container holding all the widget's children. It has a few important difference | While there is nothing preventing you from changing the `name` of a table inside a content, it is not supported, and will lead to undefined behaviour. | If you have to change the name, assign a new table to the index instead. -.. TODO: Talk about skins/templates here when they are ready +Templates +--------- + +Templates are Lua tables with the following (optional) fields: + +1. `props`: Same as in layouts, defines the behaviour of this widget. Can be overwritten by `props` values in the layout. +2. | `content`: Extra children to add to the widget. For example, the frame and caption for Window widgets. + | Contains normal layouts Events ------ @@ -97,7 +106,7 @@ Example local layout = { layers = 'Windows', type = ui.TYPE.Window, - skin = 'MW_Window', -- TODO: replace all skins here when they are properly implemented + template = { skin = 'MW_Window' }, -- TODO: replace all skins here when they are re-implemented in Lua props = { size = v2(200, 250), -- put the window in the middle of the screen @@ -107,7 +116,7 @@ Example content = ui.content { { type = ui.TYPE.Text, - skin = 'SandText', + template = { skin = 'SandText' }, props = { caption = 'Input password', relativePosition = v2(0.5, 0), @@ -117,7 +126,7 @@ Example { name = 'input', type = ui.TYPE.TextEdit, - skin = "MW_TextEdit", + template = { skin = "MW_TextEdit" }, props = { caption = '', relativePosition = v2(0.5, 0.5), @@ -129,7 +138,7 @@ Example { name = 'submit', type = ui.TYPE.Text, -- TODO: replace with button when implemented - skin = "MW_Button", + template = { skin = "MW_Button" }, props = { caption = 'Submit', -- position at the bottom diff --git a/docs/source/reference/lua-scripting/widgets/widget.rst b/docs/source/reference/lua-scripting/widgets/widget.rst index 49058ee278..36cc0917d9 100644 --- a/docs/source/reference/lua-scripting/widgets/widget.rst +++ b/docs/source/reference/lua-scripting/widgets/widget.rst @@ -33,6 +33,8 @@ Properties - boolean (true) - Defines if the widget is visible +.. TODO: document the mouse pointer property, when API for reading / adding pointer types is available + Events ------ @@ -75,3 +77,18 @@ Events * - textInput - string - Text input with this widget in focus + +External +-------- +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - slot + - bool (false) + - | Only applies for template content (ignored in layout content). + | If true, all the widgets defined in layout content will be rendered as children of this widget. + | Only one widget per template can have slot = true (others will be ignored). \ No newline at end of file diff --git a/files/mygui/core.skin b/files/mygui/core.skin index ee9135554e..5cf02a99e5 100644 --- a/files/mygui/core.skin +++ b/files/mygui/core.skin @@ -15,4 +15,8 @@ + + + + From 1832ed6a48204f414dc70347a2f5383d484ba599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 28 Jan 2022 16:26:43 +0200 Subject: [PATCH 2002/2859] Use pop_back to shrink the stack --- components/interpreter/runtime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp index 1cb8f558c9..8170526f35 100644 --- a/components/interpreter/runtime.cpp +++ b/components/interpreter/runtime.cpp @@ -100,7 +100,7 @@ namespace Interpreter if (mStack.empty()) throw std::runtime_error ("stack underflow"); - mStack.resize (mStack.size()-1); + mStack.pop_back(); } Data& Runtime::operator[] (int Index) From 45db56b382bf1a4abfafca2bb0f48efad5f4340f Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 27 Jan 2022 23:53:09 +0100 Subject: [PATCH 2003/2859] Rework fixed string * Avoid inheritance. * Define equality operators out of the class definition. * Replace toString with toStringView where it doesn't make sense to create a string. --- apps/essimporter/importsplm.h | 2 +- apps/opencs/model/world/refidadapterimp.hpp | 10 +- apps/openmw/mwmechanics/aiactivate.cpp | 2 +- apps/openmw/mwmechanics/aiactivate.hpp | 3 +- apps/openmw/mwmechanics/aiescort.cpp | 8 +- apps/openmw/mwmechanics/aiescort.hpp | 5 +- apps/openmw/mwmechanics/aifollow.cpp | 8 +- apps/openmw/mwmechanics/aifollow.hpp | 5 +- apps/openmw/mwmechanics/aisequence.cpp | 6 +- apps/openmw/mwstate/statemanagerimp.cpp | 2 +- .../esm/test_fixed_string.cpp | 2 +- components/esm/esmcommon.hpp | 167 ++++++++---------- components/esm3/esmreader.cpp | 12 +- components/esm3/loadscpt.cpp | 4 +- 14 files changed, 117 insertions(+), 119 deletions(-) diff --git a/apps/essimporter/importsplm.h b/apps/essimporter/importsplm.h index 8fd5c2bb52..1fc118a8f5 100644 --- a/apps/essimporter/importsplm.h +++ b/apps/essimporter/importsplm.h @@ -41,7 +41,7 @@ struct SPLM { int mUnknown; unsigned char mUnknown2; - ESM::FIXED_STRING<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration + ESM::FixedString<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration }; struct CNAM // 36 bytes diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 55668d16d8..41de82a0ef 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1913,14 +1913,20 @@ namespace CSMWorld break; // always save case 13: // NAME32 if (content.mType == ESM::AI_Activate) - content.mActivate.mName.assign(value.toString().toUtf8().constData()); + { + const QByteArray name = value.toString().toUtf8(); + content.mActivate.mName.assign(std::string_view(name.constData(), name.size())); + } else return; // return without saving break; // always save case 14: // NAME32 if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mId.assign(value.toString().toUtf8().constData()); + { + const QByteArray id = value.toString().toUtf8(); + content.mTarget.mId.assign(std::string_view(id.constData(), id.size())); + } else return; // return without saving diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 59ad0c1ada..42cff3642e 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -13,7 +13,7 @@ namespace MWMechanics { - AiActivate::AiActivate(const std::string &objectId, bool repeat) + AiActivate::AiActivate(std::string_view objectId, bool repeat) : TypedAiPackage(repeat), mObjectId(objectId) { } diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index ad4d4e6064..eae5ec5b6b 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -4,6 +4,7 @@ #include "typedaipackage.hpp" #include +#include #include "pathfinding.hpp" @@ -24,7 +25,7 @@ namespace MWMechanics public: /// Constructor /** \param objectId Reference to object to activate **/ - explicit AiActivate(const std::string &objectId, bool repeat); + explicit AiActivate(std::string_view objectId, bool repeat); explicit AiActivate(const ESM::AiSequence::AiActivate* activate); diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 97bb101f06..a1c0bd0471 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -20,20 +20,20 @@ namespace MWMechanics { - AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z, bool repeat) + AiEscort::AiEscort(std::string_view actorId, int duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { - mTargetActorRefId = actorId; + mTargetActorRefId = std::string(actorId); } - AiEscort::AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z, bool repeat) + AiEscort::AiEscort(std::string_view actorId, std::string_view cellId, int duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration), mRemainingDuration(static_cast(duration)) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { - mTargetActorRefId = actorId; + mTargetActorRefId = std::string(actorId); } AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index c49e227e09..0f601a29ed 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -4,6 +4,7 @@ #include "typedaipackage.hpp" #include +#include namespace ESM { @@ -22,11 +23,11 @@ namespace MWMechanics /// Implementation of AiEscort /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time \implement AiEscort **/ - AiEscort(const std::string &actorId, int duration, float x, float y, float z, bool repeat); + AiEscort(std::string_view actorId, int duration, float x, float y, float z, bool repeat); /// Implementation of AiEscortCell /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time \implement AiEscortCell **/ - AiEscort(const std::string &actorId, const std::string &cellId, int duration, float x, float y, float z, bool repeat); + AiEscort(std::string_view actorId, std::string_view cellId, int duration, float x, float y, float z, bool repeat); AiEscort(const ESM::AiSequence::AiEscort* escort); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index b6e1b124c6..cdb8ac8c1d 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -28,18 +28,18 @@ namespace MWMechanics { int AiFollow::mFollowIndexCounter = 0; -AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z, bool repeat) +AiFollow::AiFollow(std::string_view actorId, float duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat), mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(""), mActive(false), mFollowIndex(mFollowIndexCounter++) { - mTargetActorRefId = actorId; + mTargetActorRefId = std::string(actorId); } -AiFollow::AiFollow(const std::string &actorId, const std::string &cellId, float duration, float x, float y, float z, bool repeat) +AiFollow::AiFollow(std::string_view actorId, std::string_view cellId, float duration, float x, float y, float z, bool repeat) : TypedAiPackage(repeat), mAlwaysFollow(false), mDuration(duration), mRemainingDuration(duration), mX(x), mY(y), mZ(z) , mCellId(cellId), mActive(false), mFollowIndex(mFollowIndexCounter++) { - mTargetActorRefId = actorId; + mTargetActorRefId = std::string(actorId); } AiFollow::AiFollow(const MWWorld::Ptr& actor, bool commanded) diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 1b7ab7a632..c57d6da483 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -4,6 +4,7 @@ #include "typedaipackage.hpp" #include +#include #include @@ -38,9 +39,9 @@ namespace MWMechanics { public: /// Follow Actor for duration or until you arrive at a world position - AiFollow(const std::string &actorId, float duration, float x, float y, float z, bool repeat); + AiFollow(std::string_view actorId, float duration, float x, float y, float z, bool repeat); /// Follow Actor for duration or until you arrive at a position in a cell - AiFollow(const std::string &actorId, const std::string &CellId, float duration, float x, float y, float z, bool repeat); + AiFollow(std::string_view actorId, std::string_view cellId, float duration, float x, float y, float z, bool repeat); /// Follow Actor indefinitively AiFollow(const MWWorld::Ptr& actor, bool commanded=false); diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2cb18f1e78..b9efcb1cde 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -428,7 +428,7 @@ void AiSequence::fill(const ESM::AIPackageList &list) else if (esmPackage.mType == ESM::AI_Escort) { ESM::AITarget data = esmPackage.mTarget; - package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); + package = std::make_unique(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Travel) { @@ -438,12 +438,12 @@ void AiSequence::fill(const ESM::AIPackageList &list) else if (esmPackage.mType == ESM::AI_Activate) { ESM::AIActivate data = esmPackage.mActivate; - package = std::make_unique(data.mName.toString(), data.mShouldRepeat != 0); + package = std::make_unique(data.mName.toStringView(), data.mShouldRepeat != 0); } else //if (esmPackage.mType == ESM::AI_Follow) { ESM::AITarget data = esmPackage.mTarget; - package = std::make_unique(data.mId.toString(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); + package = std::make_unique(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } mPackages.push_back(std::move(package)); } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index db7ca19ff8..3b459ec2ce 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -494,7 +494,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str default: // ignore invalid records - Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toString(); + Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toStringView(); reader.skipRecord(); } int progressPercent = static_cast(float(reader.getFileOffset())/total*100); diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index b59f19e7bb..26fb1590d5 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -123,7 +123,7 @@ TEST(EsmFixedString, assign_should_only_truncate_for_4) TEST(EsmFixedString, assign_should_truncate_and_set_last_element_to_zero) { - ESM::FIXED_STRING<17> value; + ESM::FixedString<17> value; value.assign(std::string(20, 'a')); EXPECT_EQ(value, std::string(16, 'a')); } diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 7caaae2afb..921eeed744 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -1,12 +1,11 @@ #ifndef OPENMW_ESM_COMMON_H #define OPENMW_ESM_COMMON_H -#include #include #include #include - -#include +#include +#include namespace ESM { @@ -22,120 +21,110 @@ enum RecordFlag FLAG_Blocked = 0x00002000 }; -// CRTP for FIXED_STRING class, a structure used for holding fixed-length strings -template< template class DERIVED, size_t SIZE> -class FIXED_STRING_BASE +template +struct FixedString { - /* The following methods must be implemented in derived classes: - * char const* ro_data() const; // return pointer to ro buffer - * char* rw_data(); // return pointer to rw buffer - */ -public: - enum { size = SIZE }; - - template - bool operator==(char const (&str)[OTHER_SIZE]) const - { - size_t other_len = strnlen(str, OTHER_SIZE); - if (other_len != this->length()) - return false; - return std::strncmp(self()->ro_data(), str, size) == 0; - } + static_assert(capacity > 0); - //this operator will not be used for char[N], only for char* - template::value>::type> - bool operator==(const T* const& str) const - { - char const* const data = self()->ro_data(); - for(size_t i = 0; i < size; ++i) - { - if(data[i] != str[i]) return false; - else if(data[i] == '\0') return true; - } - return str[size] == '\0'; - } - bool operator!=(const char* const str) const { return !( (*this) == str ); } + static constexpr std::size_t sCapacity = capacity; + + char mData[capacity]; - bool operator==(const std::string& str) const + std::string_view toStringView() const noexcept { - return (*this) == str.c_str(); + return std::string_view(mData, strnlen(mData, capacity)); } - bool operator!=(const std::string& str) const { return !( (*this) == str ); } - static size_t data_size() { return size; } - size_t length() const { return strnlen(self()->ro_data(), size); } - std::string toString() const { return std::string(self()->ro_data(), this->length()); } - - void assign(const std::string& value) + std::string toString() const { - std::strncpy(self()->rw_data(), value.c_str(), size-1); - self()->rw_data()[size-1] = '\0'; + return std::string(toStringView()); } - void clear() { this->assign(""); } -private: - DERIVED const* self() const + std::uint32_t toInt() const noexcept { - return static_cast const*>(this); + static_assert(capacity == sizeof(std::uint32_t)); + std::uint32_t value; + std::memcpy(&value, mData, capacity); + return value; } - // write the non-const version in terms of the const version - // Effective C++ 3rd ed., Item 3 (p. 24-25) - DERIVED* self() + void clear() noexcept { - return const_cast*>(static_cast(this)->self()); + std::memset(mData, 0, capacity); } -}; -// Generic implementation -template -struct FIXED_STRING : public FIXED_STRING_BASE -{ - char data[SIZE]; - - char const* ro_data() const { return data; } - char* rw_data() { return data; } -}; + void assign(std::string_view value) noexcept + { + if (value.empty()) + { + clear(); + return; + } -// In the case of SIZE=4, it can be more efficient to match the string -// as a 32 bit number, therefore the struct is implemented as a union with an int. -template <> -struct FIXED_STRING<4> : public FIXED_STRING_BASE -{ - char data[4]; + if (value.size() < capacity) + { + if constexpr (capacity == sizeof(std::uint32_t)) + std::memset(mData, 0, capacity); + std::memcpy(mData, value.data(), value.size()); + if constexpr (capacity != sizeof(std::uint32_t)) + mData[value.size()] = '\0'; + return; + } - using FIXED_STRING_BASE::operator==; - using FIXED_STRING_BASE::operator!=; + std::memcpy(mData, value.data(), capacity); - bool operator==(uint32_t v) const { return v == toInt(); } - bool operator!=(uint32_t v) const { return v != toInt(); } + if constexpr (capacity != sizeof(std::uint32_t)) + mData[capacity - 1] = '\0'; + } - FIXED_STRING<4>& operator=(std::uint32_t value) + FixedString& operator=(std::uint32_t value) noexcept { - std::memcpy(data, &value, sizeof(data)); + static_assert(capacity == sizeof(value)); + std::memcpy(&mData, &value, capacity); return *this; } +}; - void assign(const std::string& value) +template >> +inline bool operator==(const FixedString& lhs, const T* const& rhs) noexcept +{ + for (std::size_t i = 0; i < capacity; ++i) { - std::memset(data, 0, sizeof(data)); - std::memcpy(data, value.data(), std::min(value.size(), sizeof(data))); + if (lhs.mData[i] != rhs[i]) + return false; + if (lhs.mData[i] == '\0') + return true; } + return rhs[capacity] == '\0'; +} - char const* ro_data() const { return data; } - char* rw_data() { return data; } +template +inline bool operator==(const FixedString& lhs, const std::string& rhs) noexcept +{ + return lhs == rhs.c_str(); +} - std::uint32_t toInt() const - { - std::uint32_t value; - std::memcpy(&value, data, sizeof(data)); - return value; - } -}; +template +inline bool operator==(const FixedString& lhs, const char (&rhs)[rhsSize]) noexcept +{ + return strnlen(rhs, rhsSize) == strnlen(lhs.mData, capacity) + && std::strncmp(lhs.mData, rhs, capacity) == 0; +} + +inline bool operator==(const FixedString<4>& lhs, std::uint32_t rhs) noexcept +{ + return lhs.toInt() == rhs; +} + +template +inline bool operator!=(const FixedString& lhs, const Rhs& rhs) noexcept +{ + return !(lhs == rhs); +} -typedef FIXED_STRING<4> NAME; -typedef FIXED_STRING<32> NAME32; -typedef FIXED_STRING<64> NAME64; +using NAME = FixedString<4>; +using NAME32 = FixedString<32>; +using NAME64 = FixedString<64>; /* This struct defines a file 'context' which can be saved and later restored by an ESMReader instance. It will save the position within diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 416e8a9be2..a10eba2378 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -208,9 +208,9 @@ void ESMReader::getSubName() } // reading the subrecord data anyway. - const int subNameSize = static_cast(mCtx.subName.data_size()); - getExact(mCtx.subName.rw_data(), subNameSize); - mCtx.leftRec -= static_cast(subNameSize); + const std::size_t subNameSize = decltype(mCtx.subName)::sCapacity; + getExact(mCtx.subName.mData, static_cast(subNameSize)); + mCtx.leftRec -= static_cast(subNameSize); } void ESMReader::skipHSub() @@ -257,7 +257,7 @@ NAME ESMReader::getRecName() if (!hasMoreRecs()) fail("No more records, getRecName() failed"); getName(mCtx.recName); - mCtx.leftFile -= mCtx.recName.data_size(); + mCtx.leftFile -= decltype(mCtx.recName)::sCapacity; // Make sure we don't carry over any old cached subrecord // names. This can happen in some cases when we skip parts of a @@ -331,8 +331,8 @@ std::string ESMReader::getString(int size) ss << "ESM Error: " << msg; ss << "\n File: " << mCtx.filename; - ss << "\n Record: " << mCtx.recName.toString(); - ss << "\n Subrecord: " << mCtx.subName.toString(); + ss << "\n Record: " << mCtx.recName.toStringView(); + ss << "\n Subrecord: " << mCtx.subName.toStringView(); if (mEsm.get()) ss << "\n Offset: 0x" << std::hex << mEsm->tellg(); throw std::runtime_error(ss.str()); diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index dfbcc1c7f1..a7e59f837e 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -47,7 +47,7 @@ namespace ESM std::stringstream ss; ss << "Malformed string table"; ss << "\n File: " << esm.getName(); - ss << "\n Record: " << esm.getContext().recName.toString(); + ss << "\n Record: " << esm.getContext().recName.toStringView(); ss << "\n Subrecord: " << "SCVR"; ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); Log(Debug::Verbose) << ss.str(); @@ -68,7 +68,7 @@ namespace ESM std::stringstream ss; ss << "String table overflow"; ss << "\n File: " << esm.getName(); - ss << "\n Record: " << esm.getContext().recName.toString(); + ss << "\n Record: " << esm.getContext().recName.toStringView(); ss << "\n Subrecord: " << "SCVR"; ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); Log(Debug::Verbose) << ss.str(); From 68ef96410cb6731131a82b6b0583ac30e6ffd9fc Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 29 Jan 2022 02:32:58 +0100 Subject: [PATCH 2004/2859] Make ESM::Position not packed (should fix #6566) --- components/esm/defs.hpp | 4 ---- components/esm3/objectstate.cpp | 18 +++++++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index f87a67405b..a7d2f44dd7 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -41,9 +41,6 @@ enum RangeType RT_Target = 2 }; -#pragma pack(push) -#pragma pack(1) - // Position and rotation struct Position { @@ -68,7 +65,6 @@ struct Position return tuple(l) < tuple(r); } }; -#pragma pack(pop) bool inline operator== (const Position& left, const Position& right) noexcept { diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 76967e497c..dd400c0024 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -28,8 +28,15 @@ void ESM::ObjectState::load (ESMReader &esm) mCount = 1; esm.getHNOT (mCount, "COUN"); - mPosition = mRef.mPos; - esm.getHNOT (mPosition, "POS_", 24); + if(esm.isNextSub("POS_")) + { + std::array pos; + esm.getHT(pos); + memcpy(mPosition.pos, pos.data(), sizeof(float) * 3); + memcpy(mPosition.rot, pos.data() + 3, sizeof(float) * 3); + } + else + mPosition = mRef.mPos; if (esm.isNextSub("LROT")) esm.skipHSub(); // local rotation, no longer used @@ -67,7 +74,12 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const esm.writeHNT ("COUN", mCount); if (!inInventory && mPosition != mRef.mPos) - esm.writeHNT ("POS_", mPosition, 24); + { + std::array pos; + memcpy(pos.data(), mPosition.pos, sizeof(float) * 3); + memcpy(pos.data() + 3, mPosition.rot, sizeof(float) * 3); + esm.writeHNT ("POS_", pos.data(), 24); + } if (mFlags != 0) esm.writeHNT ("FLAG", mFlags); From ecbcdd0e5466b084487ae391cb8d1f28ade7a13e Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 29 Jan 2022 06:29:55 +0100 Subject: [PATCH 2005/2859] Add missing initialization --- components/files/configfileparser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/files/configfileparser.cpp b/components/files/configfileparser.cpp index 7fcc033157..3d8e06ebe7 100644 --- a/components/files/configfileparser.cpp +++ b/components/files/configfileparser.cpp @@ -91,7 +91,7 @@ namespace Files // Invariant: no element is prefix of other element. std::set allowed_prefixes; std::string m_prefix; - bool m_allow_unregistered; + bool m_allow_unregistered = false; }; common_config_file_iterator::common_config_file_iterator( From 71fe57f7a52a0dd1ffdbada000536731f5a709da Mon Sep 17 00:00:00 2001 From: Timo Gurr Date: Sat, 29 Jan 2022 12:57:04 +0000 Subject: [PATCH 2006/2859] cmake: Use GNUInstallDirs to install data directories --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 75cc8ae10f..1e0c7f7d4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.1.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +include(GNUInstallDirs) + # for link time optimization, remove if cmake version is >= 3.9 if(POLICY CMP0069) # LTO cmake_policy(SET CMP0069 NEW) @@ -186,7 +188,7 @@ elseif(UNIX) # Paths SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "Where to install libraries") - SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(DATAROOTDIR "${CMAKE_INSTALL_DATAROOTDIR}" CACHE PATH "Sets the root of data directories to a non-default location") SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/games/" CACHE PATH "Set data path prefix") SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") From 8b7ae9afd8ca7b253cb04dea0449d8b23117054e Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 29 Jan 2022 05:04:32 +0100 Subject: [PATCH 2007/2859] Fix use after free and possible deadlock on exit Lock Simulation weak_ptr in all visitors to avoid use after free. And swap order for weak_ptr locking with locking collision world mutex to avoid deadlock when underlying object tries to lock the same mutex in the destructor. Add SimulationImpl type to avoid use of FrameData without locking weak_ptr. --- apps/openmw/mwphysics/mtphysics.cpp | 99 ++++++++++++++++--------- apps/openmw/mwphysics/physicssystem.hpp | 24 +++++- 2 files changed, 84 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 2bbd751f63..7de197a32a 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -1,3 +1,5 @@ +#include + #include #include @@ -111,17 +113,49 @@ namespace return ptr.getPosition() * interpolationFactor + ptr.getPreviousPosition() * (1.f - interpolationFactor); } + using LockedActorSimulation = std::pair< + std::shared_ptr, + std::reference_wrapper + >; + using LockedProjectileSimulation = std::pair< + std::shared_ptr, + std::reference_wrapper + >; + namespace Visitors { + template class Lock> + struct WithLockedPtr + { + const Impl& mImpl; + std::shared_mutex& mCollisionWorldMutex; + const int mNumJobs; + + template + void operator()(MWPhysics::SimulationImpl& sim) const + { + auto locked = sim.lock(); + if (!locked.has_value()) + return; + auto&& [ptr, frameData] = *std::move(locked); + // Locked shared_ptr has to be destructed after releasing mCollisionWorldMutex to avoid + // possible deadlock. Ptr destructor also acquires mCollisionWorldMutex. + const std::pair arg(std::move(ptr), frameData); + const Lock lock(mCollisionWorldMutex, mNumJobs); + mImpl(arg); + } + }; + struct InitPosition { const btCollisionWorld* mCollisionWorld; void operator()(MWPhysics::ActorSimulation& sim) const { - auto& [actorPtr, frameData] = sim; - const auto actor = actorPtr.lock(); - if (actor == nullptr) + auto locked = sim.lock(); + if (!locked.has_value()) return; + auto& [actor, frameDataRef] = *locked; + auto& frameData = frameDataRef.get(); actor->applyOffsetChange(); frameData.mPosition = actor->getPosition(); if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld)) @@ -138,7 +172,7 @@ namespace frameData.mStuckFrames = actor->getStuckFrames(); frameData.mLastStuckPosition = actor->getLastStuckPosition(); } - void operator()(MWPhysics::ProjectileSimulation& sim) const + void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const { } }; @@ -146,11 +180,11 @@ namespace struct PreStep { btCollisionWorld* mCollisionWorld; - void operator()(MWPhysics::ActorSimulation& sim) const + void operator()(const LockedActorSimulation& sim) const { MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld); } - void operator()(MWPhysics::ProjectileSimulation& sim) const + void operator()(const LockedProjectileSimulation& /*sim*/) const { } }; @@ -158,12 +192,10 @@ namespace struct UpdatePosition { btCollisionWorld* mCollisionWorld; - void operator()(MWPhysics::ActorSimulation& sim) const + void operator()(const LockedActorSimulation& sim) const { - auto& [actorPtr, frameData] = sim; - const auto actor = actorPtr.lock(); - if (actor == nullptr) - return; + auto& [actor, frameDataRef] = sim; + auto& frameData = frameDataRef.get(); if (actor->setPosition(frameData.mPosition)) { frameData.mPosition = actor->getPosition(); // account for potential position change made by script @@ -171,12 +203,10 @@ namespace mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } } - void operator()(MWPhysics::ProjectileSimulation& sim) const + void operator()(const LockedProjectileSimulation& sim) const { - auto& [projPtr, frameData] = sim; - const auto proj = projPtr.lock(); - if (proj == nullptr) - return; + auto& [proj, frameDataRef] = sim; + auto& frameData = frameDataRef.get(); proj->setPosition(frameData.mPosition); proj->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(proj->getCollisionObject()); @@ -188,11 +218,11 @@ namespace const float mPhysicsDt; const btCollisionWorld* mCollisionWorld; const MWPhysics::WorldFrameData& mWorldFrameData; - void operator()(MWPhysics::ActorSimulation& sim) const + void operator()(const LockedActorSimulation& sim) const { MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData); } - void operator()(MWPhysics::ProjectileSimulation& sim) const + void operator()(const LockedProjectileSimulation& sim) const { MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld); } @@ -206,10 +236,11 @@ namespace const MWPhysics::PhysicsTaskScheduler* scheduler; void operator()(MWPhysics::ActorSimulation& sim) const { - auto& [actorPtr, frameData] = sim; - const auto actor = actorPtr.lock(); - if (actor == nullptr) + auto locked = sim.lock(); + if (!locked.has_value()) return; + auto& [actor, frameDataRef] = *locked; + auto& frameData = frameDataRef.get(); auto ptr = actor->getPtr(); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); @@ -241,10 +272,10 @@ namespace } void operator()(MWPhysics::ProjectileSimulation& sim) const { - auto& [projPtr, frameData] = sim; - const auto proj = projPtr.lock(); - if (proj == nullptr) + auto locked = sim.lock(); + if (!locked.has_value()) return; + auto& [proj, frameData] = *locked; proj->setSimulationPosition(::interpolateMovements(*proj, mTimeAccum, mPhysicsDt)); } }; @@ -612,12 +643,10 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActorsPositions() { - const Visitors::UpdatePosition vis{mCollisionWorld}; - for (auto& sim : mSimulations) - { - MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); + const Visitors::UpdatePosition impl{mCollisionWorld}; + const Visitors::WithLockedPtr vis{impl, mCollisionWorldMutex, mNumThreads}; + for (Simulation& sim : mSimulations) std::visit(vis, sim); - } } bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) @@ -641,12 +670,10 @@ namespace MWPhysics { mPreStepBarrier->wait([this] { afterPreStep(); }); int job = 0; - const Visitors::Move vis{mPhysicsDt, mCollisionWorld, *mWorldFrameData}; + const Visitors::Move impl{mPhysicsDt, mCollisionWorld, *mWorldFrameData}; + const Visitors::WithLockedPtr vis{impl, mCollisionWorldMutex, mNumThreads}; while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) - { - MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads); std::visit(vis, mSimulations[job]); - } mPostStepBarrier->wait([this] { afterPostStep(); }); } @@ -697,12 +724,10 @@ namespace MWPhysics updateAabbs(); if (!mRemainingSteps) return; - const Visitors::PreStep vis{mCollisionWorld}; + const Visitors::PreStep impl{mCollisionWorld}; + const Visitors::WithLockedPtr vis{impl, mCollisionWorldMutex, mNumThreads}; for (auto& sim : mSimulations) - { - MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); std::visit(vis, sim); - } } void PhysicsTaskScheduler::afterPostStep() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index d2b535c427..1606ac084c 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -117,8 +119,26 @@ namespace MWPhysics osg::Vec3f mStormDirection; }; - using ActorSimulation = std::pair, ActorFrameData>; - using ProjectileSimulation = std::pair, ProjectileFrameData>; + template + class SimulationImpl + { + public: + explicit SimulationImpl(const std::weak_ptr& ptr, FrameData&& data) : mPtr(ptr), mData(data) {} + + std::optional, std::reference_wrapper>> lock() + { + if (auto locked = mPtr.lock()) + return {{std::move(locked), std::ref(mData)}}; + return std::nullopt; + } + + private: + std::weak_ptr mPtr; + FrameData mData; + }; + + using ActorSimulation = SimulationImpl; + using ProjectileSimulation = SimulationImpl; using Simulation = std::variant; class PhysicsSystem : public RayCastingInterface From 79dc600dae5cee3f1f4d20ddc4939f453a45c187 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 27 Jan 2022 19:56:27 +0100 Subject: [PATCH 2008/2859] Uncap attribute and skill damage for drain and absorb effects --- apps/openmw/mwmechanics/spelleffects.cpp | 4 ++++ apps/openmw/mwmechanics/stat.cpp | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 95ed3ca06e..d0584791a5 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -74,6 +74,8 @@ namespace { auto& creatureStats = target.getClass().getCreatureStats(target); auto attr = creatureStats.getAttribute(effect.mArg); + if(effect.mEffectId == ESM::MagicEffect::DamageAttribute) + magnitude = std::min(attr.getModified(), magnitude); attr.damage(magnitude); creatureStats.setAttribute(effect.mArg, attr); } @@ -98,6 +100,8 @@ namespace { auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.mArg); + if(effect.mEffectId == ESM::MagicEffect::DamageSkill) + magnitude = std::min(skill.getModified(), magnitude); skill.damage(magnitude); } diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index b5285330df..263c867aea 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -269,12 +269,7 @@ namespace MWMechanics void AttributeValue::damage(float damage) { - float threshold = mBase + mModifier; - - if (mDamage + damage > threshold) - mDamage = threshold; - else - mDamage += damage; + mDamage += damage; } void AttributeValue::restore(float amount) { From 07eb6db030116ee14335fdbb3743b4e471b1fa6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 29 Jan 2022 21:09:27 +0200 Subject: [PATCH 2009/2859] Use unique_ptr for ESM records --- apps/esmtool/esmtool.cpp | 16 +++---- apps/esmtool/record.cpp | 97 ++++++++++++++++++++-------------------- apps/esmtool/record.hpp | 3 +- 3 files changed, 56 insertions(+), 60 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index aaa5e16be5..4fe0b248af 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -27,7 +28,7 @@ struct ESMData unsigned int version; std::vector masters; - std::deque mRecords; + std::deque> mRecords; // Value: (Reference, Deleted flag) std::map > > mCellRefs; std::map mRecordStats; @@ -363,7 +364,7 @@ int load(Arguments& info) uint32_t flags; esm.getRecHeader(flags); - EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); + auto record = EsmTool::RecordBase::create(n); if (record == nullptr) { if (skipped.count(n.toInt()) == 0) @@ -408,11 +409,7 @@ int load(Arguments& info) if (save) { - info.data.mRecords.push_back(record); - } - else - { - delete record; + info.data.mRecords.push_back(std::move(record)); } ++info.data.mRecordStats[n.toInt()]; } @@ -420,9 +417,6 @@ int load(Arguments& info) } catch(std::exception &e) { std::cout << "\nERROR:\n\n " << e.what() << std::endl; - for (const EsmTool::RecordBase* record : info.data.mRecords) - delete record; - info.data.mRecords.clear(); return 1; } @@ -485,7 +479,7 @@ int clone(Arguments& info) esm.save(save); int saved = 0; - for (EsmTool::RecordBase* record : info.data.mRecords) + for (auto& record : info.data.mRecords) { if (i <= 0) break; diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 171f64eabe..f9c3dea5d4 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -171,226 +171,227 @@ void printTransport(const std::vector& transport) namespace EsmTool { -RecordBase * -RecordBase::create(const ESM::NAME type) +std::unique_ptr RecordBase::create(const ESM::NAME type) { - RecordBase *record = nullptr; + std::unique_ptr record; - switch (type.toInt()) { + switch (type.toInt()) + { case ESM::REC_ACTI: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_ALCH: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_APPA: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_ARMO: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_BODY: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_BOOK: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_BSGN: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_CELL: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_CLAS: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_CLOT: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_CONT: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_CREA: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_DIAL: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_DOOR: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_ENCH: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_FACT: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_GLOB: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_GMST: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_INFO: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_INGR: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_LAND: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_LEVI: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_LEVC: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_LIGH: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_LOCK: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_LTEX: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_MISC: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_MGEF: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_NPC_: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_PGRD: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_PROB: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_RACE: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_REGN: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_REPA: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_SCPT: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_SKIL: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_SNDG: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_SOUN: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_SPEL: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_STAT: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_WEAP: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } case ESM::REC_SSCR: { - record = new EsmTool::Record; + record = std::make_unique>(); break; } default: - record = nullptr; + break; } - if (record) { + if (record) + { record->mType = type; } return record; diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index bbb3dd0988..ef90dd1310 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESMTOOL_RECORD_H #include +#include #include @@ -54,7 +55,7 @@ namespace EsmTool virtual void save(ESM::ESMWriter &esm) = 0; virtual void print() = 0; - static RecordBase *create(ESM::NAME type); + static std::unique_ptr create(ESM::NAME type); // just make it a bit shorter template From 70623d0b239b066aa7344b7675962f2ff1b0afce Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 29 Jan 2022 15:14:06 +0100 Subject: [PATCH 2010/2859] Move FetchContent for benchmark to extern --- CMakeLists.txt | 2 ++ apps/benchmarks/CMakeLists.txt | 22 ---------------------- extern/CMakeLists.txt | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e0c7f7d4b..2b12c2b651 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,8 @@ option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_reca option(OPENMW_USE_SYSTEM_SQLITE3 "Use system provided SQLite3 library" ON) +option(OPENMW_USE_SYSTEM_BENCHMARK "Use system Google Benchmark library." OFF) + option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt index ad4a21d5e4..c96531a666 100644 --- a/apps/benchmarks/CMakeLists.txt +++ b/apps/benchmarks/CMakeLists.txt @@ -1,27 +1,5 @@ -option(OPENMW_USE_SYSTEM_BENCHMARK "Use system Google Benchmark library." OFF) - if(OPENMW_USE_SYSTEM_BENCHMARK) find_package(benchmark REQUIRED) -else() - cmake_minimum_required(VERSION 3.11) - - set(BENCHMARK_ENABLE_TESTING OFF) - set(BENCHMARK_ENABLE_INSTALL OFF) - set(BENCHMARK_ENABLE_GTEST_TESTS OFF) - - set(SAVED_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - - string(REPLACE "-Wsuggest-override" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - string(REPLACE "-Wundef" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - include(FetchContent) - FetchContent_Declare(benchmark - URL https://github.com/google/benchmark/archive/refs/tags/v1.5.2.zip - URL_HASH MD5=49395b757a7c4656d70f1328d93efd00 - SOURCE_DIR fetched/benchmark - ) - FetchContent_MakeAvailableExcludeFromAll(benchmark) - - set(CMAKE_CXX_FLAGS "${SAVED_CMAKE_CXX_FLAGS}") endif() openmw_add_executable(openmw_detournavigator_navmeshtilescache_benchmark detournavigator/navmeshtilescache.cpp) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 27351ce675..4dd4c2b416 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -210,3 +210,19 @@ if (NOT OPENMW_USE_SYSTEM_SQLITE3) endif() add_subdirectory(smhasher) + +if (NOT OPENMW_USE_SYSTEM_BENCHMARK) + cmake_minimum_required(VERSION 3.11) + + set(BENCHMARK_ENABLE_TESTING OFF) + set(BENCHMARK_ENABLE_INSTALL OFF) + set(BENCHMARK_ENABLE_GTEST_TESTS OFF) + + include(FetchContent) + FetchContent_Declare(benchmark + URL https://github.com/google/benchmark/archive/refs/tags/v1.5.2.zip + URL_HASH MD5=49395b757a7c4656d70f1328d93efd00 + SOURCE_DIR fetched/benchmark + ) + FetchContent_MakeAvailableExcludeFromAll(benchmark) +endif() From b0f192d8789edc3c7c7029622efa4a2b80a4c75e Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 29 Jan 2022 15:48:24 +0100 Subject: [PATCH 2011/2859] Fix warning: maybe-uninitialized ../../components/misc/color.cpp: In static member function 'static Misc::Color Misc::Color::fromHex(std::string_view)': ../../components/misc/color.cpp:36:24: error: 'v' may be used uninitialized in this function [-Werror=maybe-uninitialized] 36 | rgb[i] = v / 255.0f; | ~~^~~~~~~~ --- components/misc/color.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/misc/color.cpp b/components/misc/color.cpp index edf3435428..56520c5d74 100644 --- a/components/misc/color.cpp +++ b/components/misc/color.cpp @@ -29,7 +29,7 @@ namespace Misc for (size_t i = 0; i < rgb.size(); i++) { auto sub = hex.substr(i * 2, 2); - int v; + int v = 0; auto [_, ec] = std::from_chars(sub.data(), sub.data() + sub.size(), v, 16); if (ec != std::errc()) throw std::logic_error(std::string("Invalid hex color: ") += hex); From 28ce8fd0f347d6e6dd13289561b979420393f8cc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 29 Jan 2022 03:16:58 +0100 Subject: [PATCH 2012/2859] Add separate jobs to run tests with ASAN, TSAN, UBSAN To not slow down benchmarks with all optimizations. --- .gitlab-ci.yml | 48 +++++++++++++++++++++++++++++++++++++-- CI/before_script.linux.sh | 14 +++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index be6f57053c..3b34b0c07f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -105,7 +105,7 @@ Ubuntu_GCC: Ubuntu_GCC_tests: extends: Ubuntu_GCC cache: - key: Ubuntu_GCC_tests.v2 + key: Ubuntu_GCC_tests.v3 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 @@ -125,6 +125,50 @@ Ubuntu_GCC_tests_Debug: paths: [] expire_in: 1 minute +Ubuntu_GCC_tests_asan: + extends: Ubuntu_GCC + cache: + key: Ubuntu_GCC_asan.v1 + variables: + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak + CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak + ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 + artifacts: + paths: [] + expire_in: 1 minute + +Ubuntu_GCC_tests_ubsan: + extends: Ubuntu_GCC + cache: + key: Ubuntu_GCC_ubsan.v1 + variables: + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O0 -fsanitize=undefined + UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 + artifacts: + paths: [] + expire_in: 1 minute + +Ubuntu_GCC_tests_tsan: + extends: Ubuntu_GCC + cache: + key: Ubuntu_GCC_tsan.v1 + variables: + CCACHE_SIZE: 1G + BUILD_TESTS_ONLY: 1 + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=thread -fPIE + CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread + TSAN_OPTIONS: second_deadlock_stack=1:halt_on_error=1 + artifacts: + paths: [] + expire_in: 1 minute + Ubuntu_Static_Deps: extends: Ubuntu_Clang rules: @@ -179,7 +223,7 @@ Ubuntu_Clang: Ubuntu_Clang_tests: extends: Ubuntu_Clang cache: - key: Ubuntu_Clang_tests.v2 + key: Ubuntu_Clang_tests.v3 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 801d7f0a3e..386b25bbe6 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -52,13 +52,25 @@ if [[ "${CMAKE_BUILD_TYPE}" ]]; then ) fi +if [[ "${CMAKE_CXX_FLAGS_DEBUG}" ]]; then + CMAKE_CONF_OPTS+=( + -DCMAKE_CXX_FLAGS_DEBUG="${CMAKE_CXX_FLAGS_DEBUG}" + ) +fi + +if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then + CMAKE_CONF_OPTS+=( + -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" + ) +fi + mkdir -p build cd build if [[ "${BUILD_TESTS_ONLY}" ]]; then # flags specific to our test suite - CXX_FLAGS="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy -fsanitize=address" + CXX_FLAGS="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" if [[ "${CXX}" == 'clang++' ]]; then CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments" fi From bebeff88811183c2cd905c3cd03d7e5043e39e75 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 30 Jan 2022 02:19:13 +0100 Subject: [PATCH 2013/2859] Fix build with libc++ /home/elsid/dev/openmw/components/esm3/objectstate.cpp:33:30: error: implicit instantiation of undefined template 'std::array' std::array pos; ^ /usr/bin/../include/c++/v1/__tuple:219:64: note: template is declared here template struct _LIBCPP_TEMPLATE_VIS array; ^ /home/elsid/dev/openmw/components/esm3/objectstate.cpp:78:30: error: implicit instantiation of undefined template 'std::array' std::array pos; ^ /usr/bin/../include/c++/v1/__tuple:219:64: note: template is declared here template struct _LIBCPP_TEMPLATE_VIS array; ^ --- components/esm3/objectstate.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index dd400c0024..11fa81b7d8 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "esmreader.hpp" #include "esmwriter.hpp" From de2383ec982a78d7d20d8e23f6f1f4c3f4a6eda9 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 30 Jan 2022 11:59:30 +0000 Subject: [PATCH 2014/2859] Fix save corruption introduced by https://gitlab.com/OpenMW/openmw/-/merge_requests/1600 --- components/esm3/objectstate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index dd400c0024..149507d728 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -78,7 +78,7 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const std::array pos; memcpy(pos.data(), mPosition.pos, sizeof(float) * 3); memcpy(pos.data() + 3, mPosition.rot, sizeof(float) * 3); - esm.writeHNT ("POS_", pos.data(), 24); + esm.writeHNT ("POS_", pos, 24); } if (mFlags != 0) From b17c9a22ff7c837058de7c8fcb4bb3fab1a4de7d Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 30 Jan 2022 14:04:12 +0100 Subject: [PATCH 2015/2859] Disallow to call ESMWriter::writeT with pointer type This will make ESMWriter to use a pointer to this pointer to access the data that is unlikely an intent. For example: 68ef96410cb6731131a82b6b0583ac30e6ffd9fc. --- components/esm3/esmwriter.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index ed2ccbb377..3073daf15e 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "components/esm/esmcommon.hpp" #include "loadtes3.hpp" @@ -111,6 +112,7 @@ class ESMWriter template void writeT(const T& data) { + static_assert(!std::is_pointer_v); write((char*)&data, sizeof(T)); } @@ -123,6 +125,7 @@ class ESMWriter template void writeT(const T& data, size_t size) { + static_assert(!std::is_pointer_v); write((char*)&data, size); } From ec4adcc5a25f400c020ef09a4867e27cbcfe4b77 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 30 Jan 2022 20:27:37 +0000 Subject: [PATCH 2016/2859] Add better explanation of the `onLoad` engine handler. --- docs/source/reference/lua-scripting/engine_handlers.rst | 3 +++ docs/source/reference/lua-scripting/overview.rst | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 4f5f99965a..46e648d620 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -19,6 +19,9 @@ Engine handler is a function defined by a script, that can be called by the engi | onLoad(savedData, initData) | | Called on loading with the data previosly returned by | | | | onSave. During loading the object is always inactive. initData is | | | | the same as in onInit. | +| | | Note that onLoad means loading a script rather than loading a game.| +| | | If a script did not exist when a game was saved onLoad will not be | +| | | called, but onInit will. | +----------------------------------+----------------------------------------------------------------------+ | onInterfaceOverride(base) | | Called if the current script has an interface and overrides an | | | | interface (``base``) of another script. | diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 9de283b029..1bfdf33c42 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -246,11 +246,14 @@ See :ref:`Engine handlers reference`. onSave and onLoad ================= -When a game is saved or loaded, the engine calls the engine handlers `onSave` or `onLoad` for every script. +When a game is saved or loaded, the engine calls the engine handlers `onSave` or `onLoad` to save or load each script. The value that `onSave` returns will be passed to `onLoad` when the game is loaded. It is the only way to save the internal state of a script. All other script variables will be lost after closing the game. The saved state must be :ref:`serializable `. +Note that `onLoad` means loading a script rather than loading a game. +If a script did not exist when a game was saved then `onLoad` will not be called, but `onInit` will. + `onSave` and `onLoad` can be called even for objects in inactive state, so it shouldn't use `openmw.nearby`. An example: From 7ea5aa250b73a7c4c09de375a57a7f45630cd92f Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 30 Jan 2022 21:43:23 +0100 Subject: [PATCH 2017/2859] Revert "Cull navmesh objects by scene bounds" This reverts commit b0ef20c30348e52c38464777db9a33ecf693f877. --- apps/openmw/mwworld/scene.cpp | 4 ---- .../tilecachedrecastmeshmanager.cpp | 8 -------- .../detournavigator/gettilespositions.cpp | 8 ++------ .../detournavigator/gettilespositions.hpp | 3 +-- components/detournavigator/navmeshmanager.cpp | 15 +-------------- components/detournavigator/settingsutils.hpp | 5 ----- .../tilecachedrecastmeshmanager.cpp | 17 +---------------- .../tilecachedrecastmeshmanager.hpp | 7 +------ 8 files changed, 6 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index de9e2706c4..0d2db8b99a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -819,8 +819,6 @@ namespace MWWorld loadingListener->setProgressRange(cell->count()); - mNavigator.updatePlayerPosition(position.asVec3()); - // Load cell. mPagedRefs.clear(); loadCell(cell, loadingListener, changeEvent); @@ -854,8 +852,6 @@ namespace MWWorld if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); - mNavigator.updatePlayerPosition(position.asVec3()); - changeCellGrid(position.asVec3(), x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 631e4105ba..c44ebc5155 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -90,10 +90,6 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - TileBounds bounds; - bounds.mMin = osg::Vec2f(-1000, -1000); - bounds.mMax = osg::Vec2f(1000, 1000); - manager.setBounds(bounds); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); @@ -141,10 +137,6 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); - TileBounds bounds; - bounds.mMin = osg::Vec2f(-1000, -1000); - bounds.mMax = osg::Vec2f(1000, 1000); - manager.setBounds(bounds); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp index e74a22e5ba..d427eb3e12 100644 --- a/components/detournavigator/gettilespositions.cpp +++ b/components/detournavigator/gettilespositions.cpp @@ -2,7 +2,6 @@ #include "settings.hpp" #include "settingsutils.hpp" #include "tileposition.hpp" -#include "tilebounds.hpp" #include @@ -33,15 +32,12 @@ namespace DetourNavigator } TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, - const TileBounds& bounds, const RecastSettings& settings) + const RecastSettings& settings) { btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(transform, aabbMin, aabbMax); - aabbMin.setX(std::max(aabbMin.x(), bounds.mMin.x())); - aabbMin.setY(std::max(aabbMin.y(), bounds.mMin.y())); - aabbMax.setX(std::min(aabbMax.x(), bounds.mMax.x())); - aabbMax.setY(std::min(aabbMax.y(), bounds.mMax.y())); + return makeTilesPositionsRange(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings); } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 9d3df7ef65..946f3e64f2 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H -#include "tilebounds.hpp" #include "tileposition.hpp" class btVector3; @@ -27,7 +26,7 @@ namespace DetourNavigator const osg::Vec3f& aabbMax, const RecastSettings& settings); TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, - const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings); + const btTransform& transform, const RecastSettings& settings); TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings); diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index e8da5a0d61..9fba4ad611 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -42,18 +42,6 @@ namespace namespace DetourNavigator { - namespace - { - TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles) - { - const float radius = fromNavMeshCoordinates(settings, std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1) * getTileSize(settings)); - TileBounds result; - result.mMin = center - osg::Vec2f(radius, radius); - result.mMax = center + osg::Vec2f(radius, radius); - return result; - } - } - NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(settings.mRecast) @@ -217,7 +205,6 @@ namespace DetourNavigator } } const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); - mRecastMeshManager.setBounds(makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), maxTiles)); mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) { if (tilesToPost.count(tile)) @@ -276,7 +263,7 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(makeTilesPositionsRange(shape, transform, mRecastMeshManager.getBounds(), mSettings.mRecast), + getTilesPositions(makeTilesPositionsRange(shape, transform, mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index e38da2d0e1..285920e5a0 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -37,11 +37,6 @@ namespace DetourNavigator }; } - inline float fromNavMeshCoordinates(const RecastSettings& settings, float value) - { - return value / settings.mRecastScaleFactor; - } - inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index ced5a28656..17ba7afc39 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -8,28 +8,13 @@ #include #include -#include namespace DetourNavigator { TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) : mSettings(settings) - , mBounds {osg::Vec2f(-std::numeric_limits::max(), -std::numeric_limits::max()), - osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max())} {} - TileBounds TileCachedRecastMeshManager::getBounds() const - { - const std::lock_guard lock(mMutex); - return mBounds; - } - - void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds) - { - const std::lock_guard lock(mMutex); - mBounds = bounds; - } - std::string TileCachedRecastMeshManager::getWorldspace() const { const std::lock_guard lock(mMutex); @@ -51,7 +36,7 @@ namespace DetourNavigator std::vector tilesPositions; { const std::lock_guard lock(mMutex); - getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mBounds, mSettings), + getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mSettings), [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index d99dc3e27e..88face24ce 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -20,10 +20,6 @@ namespace DetourNavigator public: explicit TileCachedRecastMeshManager(const RecastSettings& settings); - TileBounds getBounds() const; - - void setBounds(const TileBounds& bounds); - std::string getWorldspace() const; void setWorldspace(std::string_view worldspace); @@ -61,7 +57,7 @@ namespace DetourNavigator changed = true; } }; - getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mBounds, mSettings), onTilePosition); + getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mSettings), onTilePosition); std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { @@ -113,7 +109,6 @@ namespace DetourNavigator const RecastSettings& mSettings; mutable std::mutex mMutex; - TileBounds mBounds; std::string mWorldspace; TilesMap mTiles; std::unordered_map> mObjectsTilesPositions; From ae821738c95042bb7c7687b113e0534e72fb9d7a Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 30 Jan 2022 22:09:37 +0100 Subject: [PATCH 2018/2859] Revert "Add #5858 to the changelog" This reverts commit 670cc9794726fbfcca7cd06ccd8ecb771a8193f3. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efc831db0c..d2baee6552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,6 @@ Bug #5788: Texture editing parses the selected indexes wrongly Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention Bug #5842: GetDisposition adds temporary disposition change from different actors - Bug #5858: Animated model freezes the game Bug #5863: GetEffect should return true after the player has teleported Bug #5913: Failed assertion during Ritual of Trees quest Bug #5928: Glow in the Dahrk functionality used without mod installed From 7e346fbbdd5be341d2e783c969da01c41f3f7bfa Mon Sep 17 00:00:00 2001 From: Timo Gurr Date: Sun, 30 Jan 2022 21:39:28 +0000 Subject: [PATCH 2019/2859] cmake: for now do not use CMAKE_INSTALL_DATAROOTDIR for GLOBAL_DATA_PATH --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b12c2b651..20c00fb757 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,7 +191,7 @@ elseif(UNIX) SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "Where to install libraries") SET(DATAROOTDIR "${CMAKE_INSTALL_DATAROOTDIR}" CACHE PATH "Sets the root of data directories to a non-default location") - SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/games/" CACHE PATH "Set data path prefix") + SET(GLOBAL_DATA_PATH "${CMAKE_INSTALL_PREFIX}/share/games/" CACHE PATH "Set data path prefix") SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") From 8512f7cb4c4832d8e81337eb0ac87a1217a48228 Mon Sep 17 00:00:00 2001 From: Timo Gurr Date: Sun, 30 Jan 2022 23:05:35 +0100 Subject: [PATCH 2020/2859] cmake: only download benchmarks if we build with it Since 70623d0b239b066aa7344b7675962f2ff1b0afce CMake tries to download benchmarks even if we pass -DBUILD_BENCHMARKS:BOOL=FALSE. --- extern/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 4dd4c2b416..b4bfe2e3bd 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -211,7 +211,7 @@ endif() add_subdirectory(smhasher) -if (NOT OPENMW_USE_SYSTEM_BENCHMARK) +if (BUILD_BENCHMARKS AND NOT OPENMW_USE_SYSTEM_BENCHMARK) cmake_minimum_required(VERSION 3.11) set(BENCHMARK_ENABLE_TESTING OFF) From 010dc90d42b9467127ab323b387784df1f2e5596 Mon Sep 17 00:00:00 2001 From: psi29a Date: Mon, 31 Jan 2022 07:57:08 +0000 Subject: [PATCH 2021/2859] Have Android CI also use latest ccache to fix issues with cmake. --- .gitlab-ci.yml | 14 ++++++-------- CI/install_debian_deps.sh | 10 +++------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3b34b0c07f..aaf7f233f0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -68,7 +68,7 @@ Coverity: rules: - if: $CI_PIPELINE_SOURCE == "schedule" before_script: - - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic coverity + - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz script: @@ -181,9 +181,9 @@ Ubuntu_Static_Deps: cache: key: Ubuntu_Static_Deps.V1 paths: - - apt-cache/ - - ccache/ - - build/extern/fetched/ + - apt-cache/ + - ccache/ + - build/extern/fetched/ before_script: - CI/install_debian_deps.sh clang openmw-deps openmw-deps-static variables: @@ -555,15 +555,13 @@ Ubuntu_AndroidNDK_arm64-v8a: variables: CCACHE_SIZE: 3G cache: - key: Ubuntu__Focal_AndroidNDK_r22b_arm64-v8a.v1 + key: Ubuntu__Focal_AndroidNDK_r22b_arm64-v8a.v2 paths: - apt-cache/ - ccache/ - build/extern/fetched/ before_script: - - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - - apt-get update -yq - - apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential + - CI/install_debian_deps.sh gcc stage: build script: - export CCACHE_BASEDIR="`pwd`" diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 131ccae305..74b0746ac0 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -9,13 +9,11 @@ print_help() { } declare -rA GROUPED_DEPS=( - [gcc]="binutils gcc g++ libc-dev" - [clang]="binutils clang" + [gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config" + [clang]="binutils clang make cmake ccache curl unzip git pkg-config" # Common dependencies for building OpenMW. [openmw-deps]=" - make cmake ccache git pkg-config - libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev @@ -27,7 +25,6 @@ declare -rA GROUPED_DEPS=( # These dependencies can alternatively be built and linked statically. [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev libsqlite3-dev" - [coverity]="curl" [clang-tidy]="clang-tidy" # Pre-requisites for building MyGUI and OSG for static linking. @@ -40,8 +37,7 @@ declare -rA GROUPED_DEPS=( # * JPEG: libjpeg-dev # * PNG: libpng-dev [openmw-deps-static]=" - make cmake - ccache curl unzip libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev + libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev " ) From ba3ae448d4f69cb802b28dac31e28bd092af7fc0 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 30 Jan 2022 11:07:39 +0100 Subject: [PATCH 2022/2859] Initial import of esm4 by cc9cii --- components/CMakeLists.txt | 12 +- components/esm/common.cpp | 15 + components/esm/common.hpp | 57 ++ components/esm/reader.cpp | 114 +++ components/esm/reader.hpp | 60 ++ components/esm4/acti.hpp | 70 ++ components/esm4/actor.hpp | 163 +++++ components/esm4/common.cpp | 100 +++ components/esm4/common.hpp | 939 +++++++++++++++++++++++++ components/esm4/dialogue.hpp | 46 ++ components/esm4/effect.hpp | 56 ++ components/esm4/formid.cpp | 78 ++ components/esm4/formid.hpp | 42 ++ components/esm4/inventory.hpp | 55 ++ components/esm4/lighting.hpp | 79 +++ components/esm4/loadachr.cpp | 124 ++++ components/esm4/loadachr.hpp | 70 ++ components/esm4/loadacre.cpp | 106 +++ components/esm4/loadacre.hpp | 67 ++ components/esm4/loadacti.cpp | 103 +++ components/esm4/loadalch.cpp | 121 ++++ components/esm4/loadalch.hpp | 88 +++ components/esm4/loadaloc.cpp | 172 +++++ components/esm4/loadaloc.hpp | 88 +++ components/esm4/loadammo.cpp | 133 ++++ components/esm4/loadammo.hpp | 84 +++ components/esm4/loadanio.cpp | 80 +++ components/esm4/loadanio.hpp | 63 ++ components/esm4/loadappa.cpp | 106 +++ components/esm4/loadappa.hpp | 75 ++ components/esm4/loadarma.cpp | 151 ++++ components/esm4/loadarma.hpp | 70 ++ components/esm4/loadarmo.cpp | 218 ++++++ components/esm4/loadarmo.hpp | 196 ++++++ components/esm4/loadaspc.cpp | 94 +++ components/esm4/loadaspc.hpp | 66 ++ components/esm4/loadbook.cpp | 121 ++++ components/esm4/loadbook.hpp | 114 +++ components/esm4/loadbptd.cpp | 113 +++ components/esm4/loadbptd.hpp | 128 ++++ components/esm4/loadcell.cpp | 269 +++++++ components/esm4/loadcell.hpp | 109 +++ components/esm4/loadclas.cpp | 84 +++ components/esm4/loadclas.hpp | 66 ++ components/esm4/loadclfm.cpp | 93 +++ components/esm4/loadclfm.hpp | 70 ++ components/esm4/loadclot.cpp | 106 +++ components/esm4/loadclot.hpp | 83 +++ components/esm4/loadcont.cpp | 107 +++ components/esm4/loadcont.hpp | 71 ++ components/esm4/loadcrea.cpp | 232 ++++++ components/esm4/loadcrea.hpp | 151 ++++ components/esm4/loaddial.cpp | 125 ++++ components/esm4/loaddial.hpp | 70 ++ components/esm4/loaddobj.cpp | 128 ++++ components/esm4/loaddobj.hpp | 100 +++ components/esm4/loaddoor.cpp | 93 +++ components/esm4/loaddoor.hpp | 76 ++ components/esm4/loadeyes.cpp | 74 ++ components/esm4/loadeyes.hpp | 68 ++ components/esm4/loadflor.cpp | 89 +++ components/esm4/loadflor.hpp | 78 ++ components/esm4/loadflst.cpp | 81 +++ components/esm4/loadflst.hpp | 60 ++ components/esm4/loadfurn.cpp | 98 +++ components/esm4/loadfurn.hpp | 64 ++ components/esm4/loadglob.cpp | 82 +++ components/esm4/loadglob.hpp | 60 ++ components/esm4/loadgras.cpp | 78 ++ components/esm4/loadgras.hpp | 98 +++ components/esm4/loadgrup.hpp | 158 +++++ components/esm4/loadhair.cpp | 84 +++ components/esm4/loadhair.hpp | 71 ++ components/esm4/loadhdpt.cpp | 104 +++ components/esm4/loadhdpt.hpp | 66 ++ components/esm4/loadidle.cpp | 84 +++ components/esm4/loadidle.hpp | 62 ++ components/esm4/loadidlm.cpp | 103 +++ components/esm4/loadidlm.hpp | 63 ++ components/esm4/loadimod.cpp | 89 +++ components/esm4/loadimod.hpp | 59 ++ components/esm4/loadinfo.cpp | 222 ++++++ components/esm4/loadinfo.hpp | 90 +++ components/esm4/loadingr.cpp | 133 ++++ components/esm4/loadingr.hpp | 83 +++ components/esm4/loadkeym.cpp | 93 +++ components/esm4/loadkeym.hpp | 77 ++ components/esm4/loadland.cpp | 255 +++++++ components/esm4/loadland.hpp | 136 ++++ components/esm4/loadlgtm.cpp | 105 +++ components/esm4/loadlgtm.hpp | 63 ++ components/esm4/loadligh.cpp | 120 ++++ components/esm4/loadligh.hpp | 101 +++ components/esm4/loadltex.cpp | 107 +++ components/esm4/loadltex.hpp | 75 ++ components/esm4/loadlvlc.cpp | 130 ++++ components/esm4/loadlvlc.hpp | 71 ++ components/esm4/loadlvli.cpp | 142 ++++ components/esm4/loadlvli.hpp | 72 ++ components/esm4/loadlvln.cpp | 114 +++ components/esm4/loadlvln.hpp | 69 ++ components/esm4/loadmato.cpp | 76 ++ components/esm4/loadmato.hpp | 58 ++ components/esm4/loadmisc.cpp | 95 +++ components/esm4/loadmisc.hpp | 77 ++ components/esm4/loadmset.cpp | 115 +++ components/esm4/loadmset.hpp | 99 +++ components/esm4/loadmstt.cpp | 87 +++ components/esm4/loadmstt.hpp | 60 ++ components/esm4/loadmusc.cpp | 86 +++ components/esm4/loadmusc.hpp | 60 ++ components/esm4/loadnavi.cpp | 373 ++++++++++ components/esm4/loadnavi.hpp | 117 +++ components/esm4/loadnavm.cpp | 270 +++++++ components/esm4/loadnavm.hpp | 112 +++ components/esm4/loadnote.cpp | 86 +++ components/esm4/loadnote.hpp | 60 ++ components/esm4/loadnpc.cpp | 333 +++++++++ components/esm4/loadnpc.hpp | 230 ++++++ components/esm4/loadotft.cpp | 84 +++ components/esm4/loadotft.hpp | 60 ++ components/esm4/loadpack.cpp | 200 ++++++ components/esm4/loadpack.hpp | 110 +++ components/esm4/loadpgrd.cpp | 177 +++++ components/esm4/loadpgrd.hpp | 96 +++ components/esm4/loadpgre.cpp | 105 +++ components/esm4/loadpgre.hpp | 59 ++ components/esm4/loadpwat.cpp | 82 +++ components/esm4/loadpwat.hpp | 59 ++ components/esm4/loadqust.cpp | 179 +++++ components/esm4/loadqust.hpp | 84 +++ components/esm4/loadrace.cpp | 715 +++++++++++++++++++ components/esm4/loadrace.hpp | 174 +++++ components/esm4/loadrefr.cpp | 349 +++++++++ components/esm4/loadrefr.hpp | 119 ++++ components/esm4/loadregn.cpp | 164 +++++ components/esm4/loadregn.hpp | 98 +++ components/esm4/loadroad.cpp | 123 ++++ components/esm4/loadroad.hpp | 89 +++ components/esm4/loadsbsp.cpp | 78 ++ components/esm4/loadsbsp.hpp | 64 ++ components/esm4/loadscol.cpp | 84 +++ components/esm4/loadscol.hpp | 59 ++ components/esm4/loadscpt.cpp | 173 +++++ components/esm4/loadscpt.hpp | 59 ++ components/esm4/loadscrl.cpp | 99 +++ components/esm4/loadscrl.hpp | 68 ++ components/esm4/loadsgst.cpp | 118 ++++ components/esm4/loadsgst.hpp | 75 ++ components/esm4/loadslgm.cpp | 91 +++ components/esm4/loadslgm.hpp | 76 ++ components/esm4/loadsndr.cpp | 94 +++ components/esm4/loadsndr.hpp | 86 +++ components/esm4/loadsoun.cpp | 95 +++ components/esm4/loadsoun.hpp | 101 +++ components/esm4/loadstat.cpp | 101 +++ components/esm4/loadstat.hpp | 62 ++ components/esm4/loadtact.cpp | 89 +++ components/esm4/loadtact.hpp | 76 ++ components/esm4/loadterm.cpp | 101 +++ components/esm4/loadterm.hpp | 66 ++ components/esm4/loadtes4.cpp | 113 +++ components/esm4/loadtes4.hpp | 69 ++ components/esm4/loadtree.cpp | 85 +++ components/esm4/loadtree.hpp | 62 ++ components/esm4/loadtxst.cpp | 92 +++ components/esm4/loadtxst.hpp | 66 ++ components/esm4/loadweap.cpp | 188 +++++ components/esm4/loadweap.hpp | 96 +++ components/esm4/loadwrld.cpp | 205 ++++++ components/esm4/loadwrld.hpp | 137 ++++ components/esm4/reader.cpp | 639 +++++++++++++++++ components/esm4/reader.hpp | 298 ++++++++ components/esm4/records.hpp | 74 ++ components/esm4/reference.hpp | 68 ++ components/esm4/script.hpp | 384 ++++++++++ components/to_utf8/to_utf8.cpp | 40 +- components/to_utf8/to_utf8.hpp | 3 + components/translation/translation.cpp | 11 +- 179 files changed, 20848 insertions(+), 18 deletions(-) create mode 100644 components/esm/common.cpp create mode 100644 components/esm/common.hpp create mode 100644 components/esm/reader.cpp create mode 100644 components/esm/reader.hpp create mode 100644 components/esm4/acti.hpp create mode 100644 components/esm4/actor.hpp create mode 100644 components/esm4/common.cpp create mode 100644 components/esm4/common.hpp create mode 100644 components/esm4/dialogue.hpp create mode 100644 components/esm4/effect.hpp create mode 100644 components/esm4/formid.cpp create mode 100644 components/esm4/formid.hpp create mode 100644 components/esm4/inventory.hpp create mode 100644 components/esm4/lighting.hpp create mode 100644 components/esm4/loadachr.cpp create mode 100644 components/esm4/loadachr.hpp create mode 100644 components/esm4/loadacre.cpp create mode 100644 components/esm4/loadacre.hpp create mode 100644 components/esm4/loadacti.cpp create mode 100644 components/esm4/loadalch.cpp create mode 100644 components/esm4/loadalch.hpp create mode 100644 components/esm4/loadaloc.cpp create mode 100644 components/esm4/loadaloc.hpp create mode 100644 components/esm4/loadammo.cpp create mode 100644 components/esm4/loadammo.hpp create mode 100644 components/esm4/loadanio.cpp create mode 100644 components/esm4/loadanio.hpp create mode 100644 components/esm4/loadappa.cpp create mode 100644 components/esm4/loadappa.hpp create mode 100644 components/esm4/loadarma.cpp create mode 100644 components/esm4/loadarma.hpp create mode 100644 components/esm4/loadarmo.cpp create mode 100644 components/esm4/loadarmo.hpp create mode 100644 components/esm4/loadaspc.cpp create mode 100644 components/esm4/loadaspc.hpp create mode 100644 components/esm4/loadbook.cpp create mode 100644 components/esm4/loadbook.hpp create mode 100644 components/esm4/loadbptd.cpp create mode 100644 components/esm4/loadbptd.hpp create mode 100644 components/esm4/loadcell.cpp create mode 100644 components/esm4/loadcell.hpp create mode 100644 components/esm4/loadclas.cpp create mode 100644 components/esm4/loadclas.hpp create mode 100644 components/esm4/loadclfm.cpp create mode 100644 components/esm4/loadclfm.hpp create mode 100644 components/esm4/loadclot.cpp create mode 100644 components/esm4/loadclot.hpp create mode 100644 components/esm4/loadcont.cpp create mode 100644 components/esm4/loadcont.hpp create mode 100644 components/esm4/loadcrea.cpp create mode 100644 components/esm4/loadcrea.hpp create mode 100644 components/esm4/loaddial.cpp create mode 100644 components/esm4/loaddial.hpp create mode 100644 components/esm4/loaddobj.cpp create mode 100644 components/esm4/loaddobj.hpp create mode 100644 components/esm4/loaddoor.cpp create mode 100644 components/esm4/loaddoor.hpp create mode 100644 components/esm4/loadeyes.cpp create mode 100644 components/esm4/loadeyes.hpp create mode 100644 components/esm4/loadflor.cpp create mode 100644 components/esm4/loadflor.hpp create mode 100644 components/esm4/loadflst.cpp create mode 100644 components/esm4/loadflst.hpp create mode 100644 components/esm4/loadfurn.cpp create mode 100644 components/esm4/loadfurn.hpp create mode 100644 components/esm4/loadglob.cpp create mode 100644 components/esm4/loadglob.hpp create mode 100644 components/esm4/loadgras.cpp create mode 100644 components/esm4/loadgras.hpp create mode 100644 components/esm4/loadgrup.hpp create mode 100644 components/esm4/loadhair.cpp create mode 100644 components/esm4/loadhair.hpp create mode 100644 components/esm4/loadhdpt.cpp create mode 100644 components/esm4/loadhdpt.hpp create mode 100644 components/esm4/loadidle.cpp create mode 100644 components/esm4/loadidle.hpp create mode 100644 components/esm4/loadidlm.cpp create mode 100644 components/esm4/loadidlm.hpp create mode 100644 components/esm4/loadimod.cpp create mode 100644 components/esm4/loadimod.hpp create mode 100644 components/esm4/loadinfo.cpp create mode 100644 components/esm4/loadinfo.hpp create mode 100644 components/esm4/loadingr.cpp create mode 100644 components/esm4/loadingr.hpp create mode 100644 components/esm4/loadkeym.cpp create mode 100644 components/esm4/loadkeym.hpp create mode 100644 components/esm4/loadland.cpp create mode 100644 components/esm4/loadland.hpp create mode 100644 components/esm4/loadlgtm.cpp create mode 100644 components/esm4/loadlgtm.hpp create mode 100644 components/esm4/loadligh.cpp create mode 100644 components/esm4/loadligh.hpp create mode 100644 components/esm4/loadltex.cpp create mode 100644 components/esm4/loadltex.hpp create mode 100644 components/esm4/loadlvlc.cpp create mode 100644 components/esm4/loadlvlc.hpp create mode 100644 components/esm4/loadlvli.cpp create mode 100644 components/esm4/loadlvli.hpp create mode 100644 components/esm4/loadlvln.cpp create mode 100644 components/esm4/loadlvln.hpp create mode 100644 components/esm4/loadmato.cpp create mode 100644 components/esm4/loadmato.hpp create mode 100644 components/esm4/loadmisc.cpp create mode 100644 components/esm4/loadmisc.hpp create mode 100644 components/esm4/loadmset.cpp create mode 100644 components/esm4/loadmset.hpp create mode 100644 components/esm4/loadmstt.cpp create mode 100644 components/esm4/loadmstt.hpp create mode 100644 components/esm4/loadmusc.cpp create mode 100644 components/esm4/loadmusc.hpp create mode 100644 components/esm4/loadnavi.cpp create mode 100644 components/esm4/loadnavi.hpp create mode 100644 components/esm4/loadnavm.cpp create mode 100644 components/esm4/loadnavm.hpp create mode 100644 components/esm4/loadnote.cpp create mode 100644 components/esm4/loadnote.hpp create mode 100644 components/esm4/loadnpc.cpp create mode 100644 components/esm4/loadnpc.hpp create mode 100644 components/esm4/loadotft.cpp create mode 100644 components/esm4/loadotft.hpp create mode 100644 components/esm4/loadpack.cpp create mode 100644 components/esm4/loadpack.hpp create mode 100644 components/esm4/loadpgrd.cpp create mode 100644 components/esm4/loadpgrd.hpp create mode 100644 components/esm4/loadpgre.cpp create mode 100644 components/esm4/loadpgre.hpp create mode 100644 components/esm4/loadpwat.cpp create mode 100644 components/esm4/loadpwat.hpp create mode 100644 components/esm4/loadqust.cpp create mode 100644 components/esm4/loadqust.hpp create mode 100644 components/esm4/loadrace.cpp create mode 100644 components/esm4/loadrace.hpp create mode 100644 components/esm4/loadrefr.cpp create mode 100644 components/esm4/loadrefr.hpp create mode 100644 components/esm4/loadregn.cpp create mode 100644 components/esm4/loadregn.hpp create mode 100644 components/esm4/loadroad.cpp create mode 100644 components/esm4/loadroad.hpp create mode 100644 components/esm4/loadsbsp.cpp create mode 100644 components/esm4/loadsbsp.hpp create mode 100644 components/esm4/loadscol.cpp create mode 100644 components/esm4/loadscol.hpp create mode 100644 components/esm4/loadscpt.cpp create mode 100644 components/esm4/loadscpt.hpp create mode 100644 components/esm4/loadscrl.cpp create mode 100644 components/esm4/loadscrl.hpp create mode 100644 components/esm4/loadsgst.cpp create mode 100644 components/esm4/loadsgst.hpp create mode 100644 components/esm4/loadslgm.cpp create mode 100644 components/esm4/loadslgm.hpp create mode 100644 components/esm4/loadsndr.cpp create mode 100644 components/esm4/loadsndr.hpp create mode 100644 components/esm4/loadsoun.cpp create mode 100644 components/esm4/loadsoun.hpp create mode 100644 components/esm4/loadstat.cpp create mode 100644 components/esm4/loadstat.hpp create mode 100644 components/esm4/loadtact.cpp create mode 100644 components/esm4/loadtact.hpp create mode 100644 components/esm4/loadterm.cpp create mode 100644 components/esm4/loadterm.hpp create mode 100644 components/esm4/loadtes4.cpp create mode 100644 components/esm4/loadtes4.hpp create mode 100644 components/esm4/loadtree.cpp create mode 100644 components/esm4/loadtree.hpp create mode 100644 components/esm4/loadtxst.cpp create mode 100644 components/esm4/loadtxst.hpp create mode 100644 components/esm4/loadweap.cpp create mode 100644 components/esm4/loadweap.hpp create mode 100644 components/esm4/loadwrld.cpp create mode 100644 components/esm4/loadwrld.hpp create mode 100644 components/esm4/reader.cpp create mode 100644 components/esm4/reader.hpp create mode 100644 components/esm4/records.hpp create mode 100644 components/esm4/reference.hpp create mode 100644 components/esm4/script.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2d986660bc..3a7b1a43b8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -76,7 +76,7 @@ add_component_dir (to_utf8 to_utf8 ) -add_component_dir(esm attr defs esmcommon records util luascripts) +add_component_dir(esm attr common defs esmcommon reader records util luascripts) add_component_dir (esm3 esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell @@ -94,6 +94,16 @@ add_component_dir (esm3terrain storage ) +add_component_dir (esm4 + loadachr loadacre loadacti loadalch loadaloc loadammo loadanio loadappa loadarma loadarmo loadaspc loadbook + loadbptd loadcell loadclas loadclfm loadclot common loadcont loadcrea loaddial loaddobj loaddoor loadeyes + loadflor loadflst formid loadfurn loadglob loadgras loadhair loadhdpt loadidle loadidlm loadimod loadinfo + loadingr loadkeym loadland loadlgtm loadligh loadltex loadlvlc loadlvli loadlvln loadmato loadmisc loadmset + loadmstt loadmusc loadnavi loadnavm loadnote loadnpc loadotft loadpack loadpgrd loadpgre loadpwat loadqust + loadrace loadrefr loadregn loadroad loadsbsp loadscol loadscpt loadscrl loadsgst loadslgm loadsndr + loadsoun loadstat loadtact loadterm loadtes4 loadtree loadtxst loadweap loadwrld reader +) + add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread compression osguservalues errorMarker color diff --git a/components/esm/common.cpp b/components/esm/common.cpp new file mode 100644 index 0000000000..d04033edef --- /dev/null +++ b/components/esm/common.cpp @@ -0,0 +1,15 @@ +#include "sstream" + +namespace ESM +{ + std::string printName(const std::uint32_t typeId) + { + unsigned char typeName[4]; + typeName[0] = typeId & 0xff; + typeName[1] = (typeId >> 8) & 0xff; + typeName[2] = (typeId >> 16) & 0xff; + typeName[3] = (typeId >> 24) & 0xff; + + return std::string((char*)typeName, 4); + } +} diff --git a/components/esm/common.hpp b/components/esm/common.hpp new file mode 100644 index 0000000000..3a8894e321 --- /dev/null +++ b/components/esm/common.hpp @@ -0,0 +1,57 @@ +#ifndef COMPONENT_ESM_COMMON_H +#define COMPONENT_ESM_COMMON_H + +#include +#include + +namespace ESM +{ +#pragma pack(push, 1) + union ESMVersion + { + float f; + std::uint32_t ui; + }; + + union TypeId + { + std::uint32_t value; + char name[4]; // record type in ascii + }; +#pragma pack(pop) + + enum ESMVersions + { + VER_120 = 0x3f99999a, // TES3 + VER_130 = 0x3fa66666, // TES3 + VER_080 = 0x3f4ccccd, // TES4 + VER_100 = 0x3f800000, // TES4 + VER_132 = 0x3fa8f5c3, // FONV Courier's Stash, DeadMoney + VER_133 = 0x3faa3d71, // FONV HonestHearts + VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues + VER_094 = 0x3f70a3d7, // TES5/FO3 + VER_170 = 0x3fd9999a // TES5 + }; + + // Defines another files (esm or esp) that this file depends upon. + struct MasterData + { + std::string name; + std::uint64_t size; + }; + + enum VarType + { + VT_Unknown = 0, + VT_None, + VT_Short, // stored as a float, kinda + VT_Int, + VT_Long, // stored as a float + VT_Float, + VT_String + }; + + std::string printName(const std::uint32_t typeId); +} + +#endif // COMPONENT_ESM_COMMON_H diff --git a/components/esm/reader.cpp b/components/esm/reader.cpp new file mode 100644 index 0000000000..1f22b95b47 --- /dev/null +++ b/components/esm/reader.cpp @@ -0,0 +1,114 @@ +#include "reader.hpp" + +//#ifdef NDEBUG +//#undef NDEBUG +//#endif + +#include +#include + +#include + +#include "components/esm3/esmreader.hpp" +#include "components/esm4/reader.hpp" + +namespace ESM +{ + Reader* Reader::getReader(const std::string &filename) + { + Files::IStreamPtr esmStream(Files::openConstrainedFileStream (filename.c_str ())); + + std::uint32_t modVer = 0; // get the first 4 bytes of the record header only + esmStream->read((char*)&modVer, sizeof(modVer)); + if (esmStream->gcount() == sizeof(modVer)) + { + esmStream->seekg(0); + + if (modVer == ESM4::REC_TES4) + { + return new ESM4::Reader(esmStream, filename); + } + else + { + //return new ESM3::ESMReader(esmStream, filename); + } + } + + throw std::runtime_error("Unknown file format"); + } + + bool Reader::getStringImpl(std::string& str, std::size_t size, + Files::IStreamPtr filestream, ToUTF8::Utf8Encoder* encoder, bool hasNull) + { + std::size_t newSize = size; + + if (encoder) + { + if (!hasNull) + newSize += 1; // Utf8Encoder::getLength() expects a null terminator + + std::string tmp; + tmp.resize(newSize); + filestream->read(&tmp[0], size); + if ((std::size_t)filestream->gcount() == size) + { + if (hasNull) + { + assert (tmp[newSize - 1] == '\0' + && "ESM4::Reader::getString string is not terminated with a null"); + } + else + { + // NOTE: script text of some mods have terminating null; + // unfortunately we just have to deal with it + //if (tmp[newSize - 2] != '\0') + tmp[newSize - 1] = '\0'; // for Utf8Encoder::getLength() + } + + encoder->toUtf8(tmp, str, newSize - 1); + // NOTE: the encoder converts “keep” as “keep†which increases the length, + // which results in below resize() truncating the string + if (str.size() == newSize) // ascii + str.resize(newSize - 1); // don't want the null terminator + else + { + // this is a horrible hack but fortunately there's only a few like this + std::string tmp2(str.data()); + str.swap(tmp2); + } + + return true; + } + } + else + { + if (hasNull) + newSize -= 1; // don't read the null terminator yet + + str.resize(newSize); // assumed C++11 + filestream->read(&str[0], newSize); + if ((std::size_t)filestream->gcount() == newSize) + { + if (hasNull) + { + char ch; + filestream->read(&ch, 1); // read the null terminator + assert (ch == '\0' + && "ESM4::Reader::getString string is not terminated with a null"); + } +#if 0 + else + { + // NOTE: normal ESMs don't but omwsave has locals or spells with null terminator + assert (str[newSize - 1] != '\0' + && "ESM4::Reader::getString string is unexpectedly terminated with a null"); + } +#endif + return true; + } + } + + str.clear(); + return false; // FIXME: throw instead? + } +} diff --git a/components/esm/reader.hpp b/components/esm/reader.hpp new file mode 100644 index 0000000000..a5e3dd516b --- /dev/null +++ b/components/esm/reader.hpp @@ -0,0 +1,60 @@ +#ifndef COMPONENT_ESM_READER_H +#define COMPONENT_ESM_READER_H + +#include + +#include +#include + +#include "common.hpp" // MasterData + +namespace ToUTF8 +{ + class Utf8Encoder; +} + +namespace ESM +{ + class Reader + { + std::vector* mGlobalReaderList; + + public: + virtual ~Reader() {} + + static Reader* getReader(const std::string& filename); + + void setGlobalReaderList(std::vector *list) {mGlobalReaderList = list;} + std::vector *getGlobalReaderList() {return mGlobalReaderList;} + + virtual inline bool isEsm4() const = 0; + + virtual inline bool hasMoreRecs() const = 0; + + virtual inline void setEncoder(ToUTF8::Utf8Encoder* encoder) = 0; + + // used to check for dependencies e.g. CS::Editor::run() + virtual inline const std::vector& getGameFiles() const = 0; + + // used by ContentSelector::ContentModel::addFiles() + virtual inline const std::string getAuthor() const = 0; + virtual inline const std::string getDesc() const = 0; + virtual inline int getFormat() const = 0; + + virtual inline std::string getFileName() const = 0; + + // used by CSMWorld::Data::startLoading() and getTotalRecords() for loading progress bar + virtual inline int getRecordCount() const = 0; + + virtual void setModIndex(std::uint32_t index) = 0; + + // used by CSMWorld::Data::getTotalRecords() + virtual void close() = 0; + + protected: + bool getStringImpl(std::string& str, std::size_t size, + Files::IStreamPtr filestream, ToUTF8::Utf8Encoder* encoder, bool hasNull = false); + }; +} + +#endif // COMPONENT_ESM_READER_H diff --git a/components/esm4/acti.hpp b/components/esm4/acti.hpp new file mode 100644 index 0000000000..4ec3fe1e5e --- /dev/null +++ b/components/esm4/acti.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACTI_H +#define ESM4_ACTI_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Activator + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + FormId mScriptId; + FormId mLoopingSound; // SOUN + FormId mActivationSound; // SOUN + + float mBoundRadius; + + FormId mRadioTemplate; // SOUN + FormId mRadioStation; // TACT + + std::string mActivationPrompt; + + Activator(); + virtual ~Activator(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACTI_H diff --git a/components/esm4/actor.hpp b/components/esm4/actor.hpp new file mode 100644 index 0000000000..ae56a49564 --- /dev/null +++ b/components/esm4/actor.hpp @@ -0,0 +1,163 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACTOR_H +#define ESM4_ACTOR_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ +#pragma pack(push, 1) + struct AIData // NPC_, CREA + { + std::uint8_t aggression; + std::uint8_t confidence; + std::uint8_t energyLevel; + std::uint8_t responsibility; + std::uint32_t aiFlags; + std::uint8_t trainSkill; + std::uint8_t trainLevel; + std::uint16_t unknown; + }; + + struct AttributeValues + { + std::uint8_t strength; + std::uint8_t intelligence; + std::uint8_t willpower; + std::uint8_t agility; + std::uint8_t speed; + std::uint8_t endurance; + std::uint8_t personality; + std::uint8_t luck; + }; + + struct ACBS_TES4 + { + std::uint32_t flags; + std::uint16_t baseSpell; + std::uint16_t fatigue; + std::uint16_t barterGold; + std::int16_t levelOrOffset; + std::uint16_t calcMin; + std::uint16_t calcMax; + std::uint32_t padding1; + std::uint32_t padding2; + }; + + struct ACBS_FO3 + { + std::uint32_t flags; + std::uint16_t fatigue; + std::uint16_t barterGold; + std::int16_t levelOrMult; + std::uint16_t calcMinlevel; + std::uint16_t calcMaxlevel; + std::uint16_t speedMultiplier; + float karma; + std::int16_t dispositionBase; + std::uint16_t templateFlags; + }; + + struct ACBS_TES5 + { + std::uint32_t flags; + std::uint16_t magickaOffset; + std::uint16_t staminaOffset; + std::uint16_t levelOrMult; // TODO: check if int16_t + std::uint16_t calcMinlevel; + std::uint16_t calcMaxlevel; + std::uint16_t speedMultiplier; + std::uint16_t dispositionBase; // TODO: check if int16_t + std::uint16_t templateFlags; + std::uint16_t healthOffset; + std::uint16_t bleedoutOverride; + }; + + union ActorBaseConfig + { + ACBS_TES4 tes4; + ACBS_FO3 fo3; + ACBS_TES5 tes5; + }; + + struct ActorFaction + { + FormId faction; + std::int8_t rank; + std::uint8_t unknown1; + std::uint8_t unknown2; + std::uint8_t unknown3; + }; +#pragma pack(pop) + + struct BodyTemplate // TES5 + { + // 0x00000001 - Head + // 0x00000002 - Hair + // 0x00000004 - Body + // 0x00000008 - Hands + // 0x00000010 - Forearms + // 0x00000020 - Amulet + // 0x00000040 - Ring + // 0x00000080 - Feet + // 0x00000100 - Calves + // 0x00000200 - Shield + // 0x00000400 - Tail + // 0x00000800 - Long Hair + // 0x00001000 - Circlet + // 0x00002000 - Ears + // 0x00004000 - Body AddOn 3 + // 0x00008000 - Body AddOn 4 + // 0x00010000 - Body AddOn 5 + // 0x00020000 - Body AddOn 6 + // 0x00040000 - Body AddOn 7 + // 0x00080000 - Body AddOn 8 + // 0x00100000 - Decapitate Head + // 0x00200000 - Decapitate + // 0x00400000 - Body AddOn 9 + // 0x00800000 - Body AddOn 10 + // 0x01000000 - Body AddOn 11 + // 0x02000000 - Body AddOn 12 + // 0x04000000 - Body AddOn 13 + // 0x08000000 - Body AddOn 14 + // 0x10000000 - Body AddOn 15 + // 0x20000000 - Body AddOn 16 + // 0x40000000 - Body AddOn 17 + // 0x80000000 - FX01 + std::uint32_t bodyPart; + std::uint8_t flags; + std::uint8_t unknown1; // probably padding + std::uint8_t unknown2; // probably padding + std::uint8_t unknown3; // probably padding + std::uint32_t type; // 0 = light, 1 = heavy, 2 = none (cloth?) + }; +} + +#endif // ESM4_ACTOR_H diff --git a/components/esm4/common.cpp b/components/esm4/common.cpp new file mode 100644 index 0000000000..752660fd85 --- /dev/null +++ b/components/esm4/common.cpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2015-2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "common.hpp" + +#include +#include +#include + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + const char *sGroupType[] = + { + "Record Type", "World Child", "Interior Cell", "Interior Sub Cell", "Exterior Cell", + "Exterior Sub Cell", "Cell Child", "Topic Child", "Cell Persistent Child", + "Cell Temporary Child", "Cell Visible Dist Child", "Unknown" + }; + + std::string printLabel(const GroupLabel& label, const std::uint32_t type) + { + std::ostringstream ss; + ss << std::string(sGroupType[std::min(type, (uint32_t)11)]); // avoid out of range + + switch (type) + { + case ESM4::Grp_RecordType: + { + ss << ": " << std::string((char*)label.recordType, 4); + break; + } + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + { + //short x, y; + //y = label & 0xff; + //x = (label >> 16) & 0xff; + ss << ": grid (x, y) " << std::dec << label.grid[1] << ", " << label.grid[0]; + + break; + } + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + { + ss << ": block 0x" << std::hex << label.value; + break; + } + case ESM4::Grp_WorldChild: + case ESM4::Grp_CellChild: + case ESM4::Grp_TopicChild: + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + { + ss << ": FormId 0x" << formIdToString(label.value); + break; + } + default: + break; + } + + return ss.str(); + } + + void gridToString(std::int16_t x, std::int16_t y, std::string& str) + { + char buf[6+6+2+1]; // longest signed 16 bit number is 6 characters (-32768) + int res = snprintf(buf, 6+6+2+1, "#%d %d", x, y); + if (res > 0 && res < 6+6+2+1) + str.assign(buf); + else + throw std::runtime_error("possible buffer overflow while converting grid"); + } +} diff --git a/components/esm4/common.hpp b/components/esm4/common.hpp new file mode 100644 index 0000000000..2f769c28c9 --- /dev/null +++ b/components/esm4/common.hpp @@ -0,0 +1,939 @@ +/* + Copyright (C) 2015-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + MKTAG macro was adapated from ScummVM. + +*/ +#ifndef ESM4_COMMON_H +#define ESM4_COMMON_H + +#include +#include + +#include "formid.hpp" + +// From ScummVM's endianness.h but for little endian +#ifndef MKTAG +#define MKTAG(a0,a1,a2,a3) ((std::uint32_t)((a0) | ((a1) << 8) | ((a2) << 16) | ((a3) << 24))) +#endif + +namespace ESM4 +{ + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format + enum RecordTypes + { + REC_AACT = MKTAG('A','A','C','T'), // Action + REC_ACHR = MKTAG('A','C','H','R'), // Actor Reference + REC_ACTI = MKTAG('A','C','T','I'), // Activator + REC_ADDN = MKTAG('A','D','D','N'), // Addon Node + REC_ALCH = MKTAG('A','L','C','H'), // Potion + REC_AMMO = MKTAG('A','M','M','O'), // Ammo + REC_ANIO = MKTAG('A','N','I','O'), // Animated Object + REC_APPA = MKTAG('A','P','P','A'), // Apparatus (probably unused) + REC_ARMA = MKTAG('A','R','M','A'), // Armature (Model) + REC_ARMO = MKTAG('A','R','M','O'), // Armor + REC_ARTO = MKTAG('A','R','T','O'), // Art Object + REC_ASPC = MKTAG('A','S','P','C'), // Acoustic Space + REC_ASTP = MKTAG('A','S','T','P'), // Association Type + REC_AVIF = MKTAG('A','V','I','F'), // Actor Values/Perk Tree Graphics + REC_BOOK = MKTAG('B','O','O','K'), // Book + REC_BPTD = MKTAG('B','P','T','D'), // Body Part Data + REC_CAMS = MKTAG('C','A','M','S'), // Camera Shot + REC_CELL = MKTAG('C','E','L','L'), // Cell + REC_CLAS = MKTAG('C','L','A','S'), // Class + REC_CLFM = MKTAG('C','L','F','M'), // Color + REC_CLMT = MKTAG('C','L','M','T'), // Climate + REC_CLOT = MKTAG('C','L','O','T'), // Clothing + REC_COBJ = MKTAG('C','O','B','J'), // Constructible Object (recipes) + REC_COLL = MKTAG('C','O','L','L'), // Collision Layer + REC_CONT = MKTAG('C','O','N','T'), // Container + REC_CPTH = MKTAG('C','P','T','H'), // Camera Path + REC_CREA = MKTAG('C','R','E','A'), // Creature + REC_CSTY = MKTAG('C','S','T','Y'), // Combat Style + REC_DEBR = MKTAG('D','E','B','R'), // Debris + REC_DIAL = MKTAG('D','I','A','L'), // Dialog Topic + REC_DLBR = MKTAG('D','L','B','R'), // Dialog Branch + REC_DLVW = MKTAG('D','L','V','W'), // Dialog View + REC_DOBJ = MKTAG('D','O','B','J'), // Default Object Manager + REC_DOOR = MKTAG('D','O','O','R'), // Door + REC_DUAL = MKTAG('D','U','A','L'), // Dual Cast Data (possibly unused) + //REC_ECZN = MKTAG('E','C','Z','N'), // Encounter Zone + REC_EFSH = MKTAG('E','F','S','H'), // Effect Shader + REC_ENCH = MKTAG('E','N','C','H'), // Enchantment + REC_EQUP = MKTAG('E','Q','U','P'), // Equip Slot (flag-type values) + REC_EXPL = MKTAG('E','X','P','L'), // Explosion + REC_EYES = MKTAG('E','Y','E','S'), // Eyes + REC_FACT = MKTAG('F','A','C','T'), // Faction + REC_FLOR = MKTAG('F','L','O','R'), // Flora + REC_FLST = MKTAG('F','L','S','T'), // Form List (non-levelled list) + REC_FSTP = MKTAG('F','S','T','P'), // Footstep + REC_FSTS = MKTAG('F','S','T','S'), // Footstep Set + REC_FURN = MKTAG('F','U','R','N'), // Furniture + REC_GLOB = MKTAG('G','L','O','B'), // Global Variable + REC_GMST = MKTAG('G','M','S','T'), // Game Setting + REC_GRAS = MKTAG('G','R','A','S'), // Grass + REC_GRUP = MKTAG('G','R','U','P'), // Form Group + REC_HAIR = MKTAG('H','A','I','R'), // Hair + //REC_HAZD = MKTAG('H','A','Z','D'), // Hazard + REC_HDPT = MKTAG('H','D','P','T'), // Head Part + REC_IDLE = MKTAG('I','D','L','E'), // Idle Animation + REC_IDLM = MKTAG('I','D','L','M'), // Idle Marker + REC_IMAD = MKTAG('I','M','A','D'), // Image Space Modifier + REC_IMGS = MKTAG('I','M','G','S'), // Image Space + REC_INFO = MKTAG('I','N','F','O'), // Dialog Topic Info + REC_INGR = MKTAG('I','N','G','R'), // Ingredient + REC_IPCT = MKTAG('I','P','C','T'), // Impact Data + REC_IPDS = MKTAG('I','P','D','S'), // Impact Data Set + REC_KEYM = MKTAG('K','E','Y','M'), // Key + REC_KYWD = MKTAG('K','Y','W','D'), // Keyword + REC_LAND = MKTAG('L','A','N','D'), // Land + REC_LCRT = MKTAG('L','C','R','T'), // Location Reference Type + REC_LCTN = MKTAG('L','C','T','N'), // Location + REC_LGTM = MKTAG('L','G','T','M'), // Lighting Template + REC_LIGH = MKTAG('L','I','G','H'), // Light + REC_LSCR = MKTAG('L','S','C','R'), // Load Screen + REC_LTEX = MKTAG('L','T','E','X'), // Land Texture + REC_LVLC = MKTAG('L','V','L','C'), // Leveled Creature + REC_LVLI = MKTAG('L','V','L','I'), // Leveled Item + REC_LVLN = MKTAG('L','V','L','N'), // Leveled Actor + REC_LVSP = MKTAG('L','V','S','P'), // Leveled Spell + REC_MATO = MKTAG('M','A','T','O'), // Material Object + REC_MATT = MKTAG('M','A','T','T'), // Material Type + REC_MESG = MKTAG('M','E','S','G'), // Message + REC_MGEF = MKTAG('M','G','E','F'), // Magic Effect + REC_MISC = MKTAG('M','I','S','C'), // Misc. Object + REC_MOVT = MKTAG('M','O','V','T'), // Movement Type + REC_MSTT = MKTAG('M','S','T','T'), // Movable Static + REC_MUSC = MKTAG('M','U','S','C'), // Music Type + REC_MUST = MKTAG('M','U','S','T'), // Music Track + REC_NAVI = MKTAG('N','A','V','I'), // Navigation (master data) + REC_NAVM = MKTAG('N','A','V','M'), // Nav Mesh + REC_NOTE = MKTAG('N','O','T','E'), // Note + REC_NPC_ = MKTAG('N','P','C','_'), // Actor (NPC, Creature) + REC_OTFT = MKTAG('O','T','F','T'), // Outfit + REC_PACK = MKTAG('P','A','C','K'), // AI Package + REC_PERK = MKTAG('P','E','R','K'), // Perk + REC_PGRE = MKTAG('P','G','R','E'), // Placed grenade + REC_PHZD = MKTAG('P','H','Z','D'), // Placed hazard + REC_PROJ = MKTAG('P','R','O','J'), // Projectile + REC_QUST = MKTAG('Q','U','S','T'), // Quest + REC_RACE = MKTAG('R','A','C','E'), // Race / Creature type + REC_REFR = MKTAG('R','E','F','R'), // Object Reference + REC_REGN = MKTAG('R','E','G','N'), // Region (Audio/Weather) + REC_RELA = MKTAG('R','E','L','A'), // Relationship + REC_REVB = MKTAG('R','E','V','B'), // Reverb Parameters + REC_RFCT = MKTAG('R','F','C','T'), // Visual Effect + REC_SBSP = MKTAG('S','B','S','P'), // Subspace (TES4 only?) + REC_SCEN = MKTAG('S','C','E','N'), // Scene + REC_SCPT = MKTAG('S','C','P','T'), // Script + REC_SCRL = MKTAG('S','C','R','L'), // Scroll + REC_SGST = MKTAG('S','G','S','T'), // Sigil Stone + REC_SHOU = MKTAG('S','H','O','U'), // Shout + REC_SLGM = MKTAG('S','L','G','M'), // Soul Gem + REC_SMBN = MKTAG('S','M','B','N'), // Story Manager Branch Node + REC_SMEN = MKTAG('S','M','E','N'), // Story Manager Event Node + REC_SMQN = MKTAG('S','M','Q','N'), // Story Manager Quest Node + REC_SNCT = MKTAG('S','N','C','T'), // Sound Category + REC_SNDR = MKTAG('S','N','D','R'), // Sound Reference + REC_SOPM = MKTAG('S','O','P','M'), // Sound Output Model + REC_SOUN = MKTAG('S','O','U','N'), // Sound + REC_SPEL = MKTAG('S','P','E','L'), // Spell + REC_SPGD = MKTAG('S','P','G','D'), // Shader Particle Geometry + REC_STAT = MKTAG('S','T','A','T'), // Static + REC_TACT = MKTAG('T','A','C','T'), // Talking Activator + REC_TERM = MKTAG('T','E','R','M'), // Terminal + REC_TES4 = MKTAG('T','E','S','4'), // Plugin info + REC_TREE = MKTAG('T','R','E','E'), // Tree + REC_TXST = MKTAG('T','X','S','T'), // Texture Set + REC_VTYP = MKTAG('V','T','Y','P'), // Voice Type + REC_WATR = MKTAG('W','A','T','R'), // Water Type + REC_WEAP = MKTAG('W','E','A','P'), // Weapon + REC_WOOP = MKTAG('W','O','O','P'), // Word Of Power + REC_WRLD = MKTAG('W','R','L','D'), // World Space + REC_WTHR = MKTAG('W','T','H','R'), // Weather + REC_ACRE = MKTAG('A','C','R','E'), // Placed Creature (TES4 only?) + REC_PGRD = MKTAG('P','G','R','D'), // Pathgrid (TES4 only?) + REC_ROAD = MKTAG('R','O','A','D'), // Road (TES4 only?) + REC_IMOD = MKTAG('I','M','O','D'), // Item Mod + REC_PWAT = MKTAG('P','W','A','T'), // Placeable Water + REC_SCOL = MKTAG('S','C','O','L'), // Static Collection + REC_CCRD = MKTAG('C','C','R','D'), // Caravan Card + REC_CMNY = MKTAG('C','M','N','Y'), // Caravan Money + REC_ALOC = MKTAG('A','L','O','C'), // Audio Location Controller + REC_MSET = MKTAG('M','S','E','T') // Media Set + }; + + enum SubRecordTypes + { + SUB_HEDR = MKTAG('H','E','D','R'), + SUB_CNAM = MKTAG('C','N','A','M'), + SUB_SNAM = MKTAG('S','N','A','M'), // TES4 only? + SUB_MAST = MKTAG('M','A','S','T'), + SUB_DATA = MKTAG('D','A','T','A'), + SUB_ONAM = MKTAG('O','N','A','M'), + SUB_INTV = MKTAG('I','N','T','V'), + SUB_INCC = MKTAG('I','N','C','C'), + SUB_OFST = MKTAG('O','F','S','T'), // TES4 only? + SUB_DELE = MKTAG('D','E','L','E'), // TES4 only? + + SUB_DNAM = MKTAG('D','N','A','M'), + SUB_EDID = MKTAG('E','D','I','D'), + SUB_FULL = MKTAG('F','U','L','L'), + SUB_LTMP = MKTAG('L','T','M','P'), + SUB_MHDT = MKTAG('M','H','D','T'), + SUB_MNAM = MKTAG('M','N','A','M'), + SUB_MODL = MKTAG('M','O','D','L'), + SUB_NAM0 = MKTAG('N','A','M','0'), + SUB_NAM2 = MKTAG('N','A','M','2'), + SUB_NAM3 = MKTAG('N','A','M','3'), + SUB_NAM4 = MKTAG('N','A','M','4'), + SUB_NAM9 = MKTAG('N','A','M','9'), + SUB_NAMA = MKTAG('N','A','M','A'), + SUB_PNAM = MKTAG('P','N','A','M'), + SUB_RNAM = MKTAG('R','N','A','M'), + SUB_TNAM = MKTAG('T','N','A','M'), + SUB_UNAM = MKTAG('U','N','A','M'), + SUB_WCTR = MKTAG('W','C','T','R'), + SUB_WNAM = MKTAG('W','N','A','M'), + SUB_XEZN = MKTAG('X','E','Z','N'), + SUB_XLCN = MKTAG('X','L','C','N'), + SUB_XXXX = MKTAG('X','X','X','X'), + SUB_ZNAM = MKTAG('Z','N','A','M'), + SUB_MODT = MKTAG('M','O','D','T'), + SUB_ICON = MKTAG('I','C','O','N'), // TES4 only? + + SUB_NVER = MKTAG('N','V','E','R'), + SUB_NVMI = MKTAG('N','V','M','I'), + SUB_NVPP = MKTAG('N','V','P','P'), + SUB_NVSI = MKTAG('N','V','S','I'), + + SUB_NVNM = MKTAG('N','V','N','M'), + SUB_NNAM = MKTAG('N','N','A','M'), + + SUB_XCLC = MKTAG('X','C','L','C'), + SUB_XCLL = MKTAG('X','C','L','L'), + SUB_TVDT = MKTAG('T','V','D','T'), + SUB_XCGD = MKTAG('X','C','G','D'), + SUB_LNAM = MKTAG('L','N','A','M'), + SUB_XCLW = MKTAG('X','C','L','W'), + SUB_XNAM = MKTAG('X','N','A','M'), + SUB_XCLR = MKTAG('X','C','L','R'), + SUB_XWCS = MKTAG('X','W','C','S'), + SUB_XWCN = MKTAG('X','W','C','N'), + SUB_XWCU = MKTAG('X','W','C','U'), + SUB_XCWT = MKTAG('X','C','W','T'), + SUB_XOWN = MKTAG('X','O','W','N'), + SUB_XILL = MKTAG('X','I','L','L'), + SUB_XWEM = MKTAG('X','W','E','M'), + SUB_XCCM = MKTAG('X','C','C','M'), + SUB_XCAS = MKTAG('X','C','A','S'), + SUB_XCMO = MKTAG('X','C','M','O'), + SUB_XCIM = MKTAG('X','C','I','M'), + SUB_XCMT = MKTAG('X','C','M','T'), // TES4 only? + SUB_XRNK = MKTAG('X','R','N','K'), // TES4 only? + SUB_XGLB = MKTAG('X','G','L','B'), // TES4 only? + + SUB_VNML = MKTAG('V','N','M','L'), + SUB_VHGT = MKTAG('V','H','G','T'), + SUB_VCLR = MKTAG('V','C','L','R'), + SUA_BTXT = MKTAG('B','T','X','T'), + SUB_ATXT = MKTAG('A','T','X','T'), + SUB_VTXT = MKTAG('V','T','X','T'), + SUB_VTEX = MKTAG('V','T','E','X'), + + SUB_HNAM = MKTAG('H','N','A','M'), + SUB_GNAM = MKTAG('G','N','A','M'), + + SUB_RCLR = MKTAG('R','C','L','R'), + SUB_RPLI = MKTAG('R','P','L','I'), + SUB_RPLD = MKTAG('R','P','L','D'), + SUB_RDAT = MKTAG('R','D','A','T'), + SUB_RDMD = MKTAG('R','D','M','D'), // TES4 only? + SUB_RDSD = MKTAG('R','D','S','D'), // TES4 only? + SUB_RDGS = MKTAG('R','D','G','S'), // TES4 only? + SUB_RDMO = MKTAG('R','D','M','O'), + SUB_RDSA = MKTAG('R','D','S','A'), + SUB_RDWT = MKTAG('R','D','W','T'), + SUB_RDOT = MKTAG('R','D','O','T'), + SUB_RDMP = MKTAG('R','D','M','P'), + + SUB_MODB = MKTAG('M','O','D','B'), + SUB_OBND = MKTAG('O','B','N','D'), + SUB_MODS = MKTAG('M','O','D','S'), + + SUB_NAME = MKTAG('N','A','M','E'), + SUB_XMRK = MKTAG('X','M','R','K'), + SUB_FNAM = MKTAG('F','N','A','M'), + SUB_XSCL = MKTAG('X','S','C','L'), + SUB_XTEL = MKTAG('X','T','E','L'), + SUB_XTRG = MKTAG('X','T','R','G'), + SUB_XSED = MKTAG('X','S','E','D'), + SUB_XLOD = MKTAG('X','L','O','D'), + SUB_XPCI = MKTAG('X','P','C','I'), + SUB_XLOC = MKTAG('X','L','O','C'), + SUB_XESP = MKTAG('X','E','S','P'), + SUB_XLCM = MKTAG('X','L','C','M'), + SUB_XRTM = MKTAG('X','R','T','M'), + SUB_XACT = MKTAG('X','A','C','T'), + SUB_XCNT = MKTAG('X','C','N','T'), + SUB_VMAD = MKTAG('V','M','A','D'), + SUB_XPRM = MKTAG('X','P','R','M'), + SUB_XMBO = MKTAG('X','M','B','O'), + SUB_XPOD = MKTAG('X','P','O','D'), + SUB_XRMR = MKTAG('X','R','M','R'), + SUB_INAM = MKTAG('I','N','A','M'), + SUB_SCHR = MKTAG('S','C','H','R'), + SUB_XLRM = MKTAG('X','L','R','M'), + SUB_XRGD = MKTAG('X','R','G','D'), + SUB_XRDS = MKTAG('X','R','D','S'), + SUB_XEMI = MKTAG('X','E','M','I'), + SUB_XLIG = MKTAG('X','L','I','G'), + SUB_XALP = MKTAG('X','A','L','P'), + SUB_XNDP = MKTAG('X','N','D','P'), + SUB_XAPD = MKTAG('X','A','P','D'), + SUB_XAPR = MKTAG('X','A','P','R'), + SUB_XLIB = MKTAG('X','L','I','B'), + SUB_XLKR = MKTAG('X','L','K','R'), + SUB_XLRT = MKTAG('X','L','R','T'), + SUB_XCVL = MKTAG('X','C','V','L'), + SUB_XCVR = MKTAG('X','C','V','R'), + SUB_XCZA = MKTAG('X','C','Z','A'), + SUB_XCZC = MKTAG('X','C','Z','C'), + SUB_XFVC = MKTAG('X','F','V','C'), + SUB_XHTW = MKTAG('X','H','T','W'), + SUB_XIS2 = MKTAG('X','I','S','2'), + SUB_XMBR = MKTAG('X','M','B','R'), + SUB_XCCP = MKTAG('X','C','C','P'), + SUB_XPWR = MKTAG('X','P','W','R'), + SUB_XTRI = MKTAG('X','T','R','I'), + SUB_XATR = MKTAG('X','A','T','R'), + SUB_XPRD = MKTAG('X','P','R','D'), + SUB_XPPA = MKTAG('X','P','P','A'), + SUB_PDTO = MKTAG('P','D','T','O'), + SUB_XLRL = MKTAG('X','L','R','L'), + + SUB_QNAM = MKTAG('Q','N','A','M'), + SUB_COCT = MKTAG('C','O','C','T'), + SUB_COED = MKTAG('C','O','E','D'), + SUB_CNTO = MKTAG('C','N','T','O'), + SUB_SCRI = MKTAG('S','C','R','I'), + + SUB_BNAM = MKTAG('B','N','A','M'), + + SUB_BMDT = MKTAG('B','M','D','T'), + SUB_MOD2 = MKTAG('M','O','D','2'), + SUB_MOD3 = MKTAG('M','O','D','3'), + SUB_MOD4 = MKTAG('M','O','D','4'), + SUB_MO2B = MKTAG('M','O','2','B'), + SUB_MO3B = MKTAG('M','O','3','B'), + SUB_MO4B = MKTAG('M','O','4','B'), + SUB_MO2T = MKTAG('M','O','2','T'), + SUB_MO3T = MKTAG('M','O','3','T'), + SUB_MO4T = MKTAG('M','O','4','T'), + SUB_ANAM = MKTAG('A','N','A','M'), + SUB_ENAM = MKTAG('E','N','A','M'), + SUB_ICO2 = MKTAG('I','C','O','2'), + + SUB_ACBS = MKTAG('A','C','B','S'), + SUB_SPLO = MKTAG('S','P','L','O'), + SUB_AIDT = MKTAG('A','I','D','T'), + SUB_PKID = MKTAG('P','K','I','D'), + SUB_HCLR = MKTAG('H','C','L','R'), + SUB_FGGS = MKTAG('F','G','G','S'), + SUB_FGGA = MKTAG('F','G','G','A'), + SUB_FGTS = MKTAG('F','G','T','S'), + SUB_KFFZ = MKTAG('K','F','F','Z'), + + SUB_PFIG = MKTAG('P','F','I','G'), + SUB_PFPC = MKTAG('P','F','P','C'), + + SUB_XHRS = MKTAG('X','H','R','S'), + SUB_XMRC = MKTAG('X','M','R','C'), + + SUB_SNDD = MKTAG('S','N','D','D'), + SUB_SNDX = MKTAG('S','N','D','X'), + + SUB_DESC = MKTAG('D','E','S','C'), + + SUB_ENIT = MKTAG('E','N','I','T'), + SUB_EFID = MKTAG('E','F','I','D'), + SUB_EFIT = MKTAG('E','F','I','T'), + SUB_SCIT = MKTAG('S','C','I','T'), + + SUB_SOUL = MKTAG('S','O','U','L'), + SUB_SLCP = MKTAG('S','L','C','P'), + + SUB_CSCR = MKTAG('C','S','C','R'), + SUB_CSDI = MKTAG('C','S','D','I'), + SUB_CSDC = MKTAG('C','S','D','C'), + SUB_NIFZ = MKTAG('N','I','F','Z'), + SUB_CSDT = MKTAG('C','S','D','T'), + SUB_NAM1 = MKTAG('N','A','M','1'), + SUB_NIFT = MKTAG('N','I','F','T'), + + SUB_LVLD = MKTAG('L','V','L','D'), + SUB_LVLF = MKTAG('L','V','L','F'), + SUB_LVLO = MKTAG('L','V','L','O'), + + SUB_BODT = MKTAG('B','O','D','T'), + SUB_YNAM = MKTAG('Y','N','A','M'), + SUB_DEST = MKTAG('D','E','S','T'), + SUB_DMDL = MKTAG('D','M','D','L'), + SUB_DMDS = MKTAG('D','M','D','S'), + SUB_DMDT = MKTAG('D','M','D','T'), + SUB_DSTD = MKTAG('D','S','T','D'), + SUB_DSTF = MKTAG('D','S','T','F'), + SUB_KNAM = MKTAG('K','N','A','M'), + SUB_KSIZ = MKTAG('K','S','I','Z'), + SUB_KWDA = MKTAG('K','W','D','A'), + SUB_VNAM = MKTAG('V','N','A','M'), + SUB_SDSC = MKTAG('S','D','S','C'), + SUB_MO2S = MKTAG('M','O','2','S'), + SUB_MO4S = MKTAG('M','O','4','S'), + SUB_BOD2 = MKTAG('B','O','D','2'), + SUB_BAMT = MKTAG('B','A','M','T'), + SUB_BIDS = MKTAG('B','I','D','S'), + SUB_ETYP = MKTAG('E','T','Y','P'), + SUB_BMCT = MKTAG('B','M','C','T'), + SUB_MICO = MKTAG('M','I','C','O'), + SUB_MIC2 = MKTAG('M','I','C','2'), + SUB_EAMT = MKTAG('E','A','M','T'), + SUB_EITM = MKTAG('E','I','T','M'), + + SUB_SCTX = MKTAG('S','C','T','X'), + SUB_XLTW = MKTAG('X','L','T','W'), + SUB_XMBP = MKTAG('X','M','B','P'), + SUB_XOCP = MKTAG('X','O','C','P'), + SUB_XRGB = MKTAG('X','R','G','B'), + SUB_XSPC = MKTAG('X','S','P','C'), + SUB_XTNM = MKTAG('X','T','N','M'), + SUB_ATKR = MKTAG('A','T','K','R'), + SUB_CRIF = MKTAG('C','R','I','F'), + SUB_DOFT = MKTAG('D','O','F','T'), + SUB_DPLT = MKTAG('D','P','L','T'), + SUB_ECOR = MKTAG('E','C','O','R'), + SUB_ATKD = MKTAG('A','T','K','D'), + SUB_ATKE = MKTAG('A','T','K','E'), + SUB_FTST = MKTAG('F','T','S','T'), + SUB_HCLF = MKTAG('H','C','L','F'), + SUB_NAM5 = MKTAG('N','A','M','5'), + SUB_NAM6 = MKTAG('N','A','M','6'), + SUB_NAM7 = MKTAG('N','A','M','7'), + SUB_NAM8 = MKTAG('N','A','M','8'), + SUB_PRKR = MKTAG('P','R','K','R'), + SUB_PRKZ = MKTAG('P','R','K','Z'), + SUB_SOFT = MKTAG('S','O','F','T'), + SUB_SPCT = MKTAG('S','P','C','T'), + SUB_TINC = MKTAG('T','I','N','C'), + SUB_TIAS = MKTAG('T','I','A','S'), + SUB_TINI = MKTAG('T','I','N','I'), + SUB_TINV = MKTAG('T','I','N','V'), + SUB_TPLT = MKTAG('T','P','L','T'), + SUB_VTCK = MKTAG('V','T','C','K'), + SUB_SHRT = MKTAG('S','H','R','T'), + SUB_SPOR = MKTAG('S','P','O','R'), + SUB_XHOR = MKTAG('X','H','O','R'), + SUB_CTDA = MKTAG('C','T','D','A'), + SUB_CRDT = MKTAG('C','R','D','T'), + SUB_FNMK = MKTAG('F','N','M','K'), + SUB_FNPR = MKTAG('F','N','P','R'), + SUB_WBDT = MKTAG('W','B','D','T'), + SUB_QUAL = MKTAG('Q','U','A','L'), + SUB_INDX = MKTAG('I','N','D','X'), + SUB_ATTR = MKTAG('A','T','T','R'), + SUB_MTNM = MKTAG('M','T','N','M'), + SUB_UNES = MKTAG('U','N','E','S'), + SUB_TIND = MKTAG('T','I','N','D'), + SUB_TINL = MKTAG('T','I','N','L'), + SUB_TINP = MKTAG('T','I','N','P'), + SUB_TINT = MKTAG('T','I','N','T'), + SUB_TIRS = MKTAG('T','I','R','S'), + SUB_PHWT = MKTAG('P','H','W','T'), + SUB_AHCF = MKTAG('A','H','C','F'), + SUB_AHCM = MKTAG('A','H','C','M'), + SUB_HEAD = MKTAG('H','E','A','D'), + SUB_MPAI = MKTAG('M','P','A','I'), + SUB_MPAV = MKTAG('M','P','A','V'), + SUB_DFTF = MKTAG('D','F','T','F'), + SUB_DFTM = MKTAG('D','F','T','M'), + SUB_FLMV = MKTAG('F','L','M','V'), + SUB_FTSF = MKTAG('F','T','S','F'), + SUB_FTSM = MKTAG('F','T','S','M'), + SUB_MTYP = MKTAG('M','T','Y','P'), + SUB_PHTN = MKTAG('P','H','T','N'), + SUB_RNMV = MKTAG('R','N','M','V'), + SUB_RPRF = MKTAG('R','P','R','F'), + SUB_RPRM = MKTAG('R','P','R','M'), + SUB_SNMV = MKTAG('S','N','M','V'), + SUB_SPED = MKTAG('S','P','E','D'), + SUB_SWMV = MKTAG('S','W','M','V'), + SUB_WKMV = MKTAG('W','K','M','V'), + SUB_LLCT = MKTAG('L','L','C','T'), + SUB_IDLF = MKTAG('I','D','L','F'), + SUB_IDLA = MKTAG('I','D','L','A'), + SUB_IDLC = MKTAG('I','D','L','C'), + SUB_IDLT = MKTAG('I','D','L','T'), + SUB_DODT = MKTAG('D','O','D','T'), + SUB_TX00 = MKTAG('T','X','0','0'), + SUB_TX01 = MKTAG('T','X','0','1'), + SUB_TX02 = MKTAG('T','X','0','2'), + SUB_TX03 = MKTAG('T','X','0','3'), + SUB_TX04 = MKTAG('T','X','0','4'), + SUB_TX05 = MKTAG('T','X','0','5'), + SUB_TX06 = MKTAG('T','X','0','6'), + SUB_TX07 = MKTAG('T','X','0','7'), + SUB_BPND = MKTAG('B','P','N','D'), + SUB_BPTN = MKTAG('B','P','T','N'), + SUB_BPNN = MKTAG('B','P','N','N'), + SUB_BPNT = MKTAG('B','P','N','T'), + SUB_BPNI = MKTAG('B','P','N','I'), + SUB_RAGA = MKTAG('R','A','G','A'), + + SUB_QSTI = MKTAG('Q','S','T','I'), + SUB_QSTR = MKTAG('Q','S','T','R'), + SUB_QSDT = MKTAG('Q','S','D','T'), + SUB_SCDA = MKTAG('S','C','D','A'), + SUB_SCRO = MKTAG('S','C','R','O'), + SUB_QSTA = MKTAG('Q','S','T','A'), + SUB_CTDT = MKTAG('C','T','D','T'), + SUB_SCHD = MKTAG('S','C','H','D'), + SUB_TCLF = MKTAG('T','C','L','F'), + SUB_TCLT = MKTAG('T','C','L','T'), + SUB_TRDT = MKTAG('T','R','D','T'), + SUB_TPIC = MKTAG('T','P','I','C'), + + SUB_PKDT = MKTAG('P','K','D','T'), + SUB_PSDT = MKTAG('P','S','D','T'), + SUB_PLDT = MKTAG('P','L','D','T'), + SUB_PTDT = MKTAG('P','T','D','T'), + SUB_PGRP = MKTAG('P','G','R','P'), + SUB_PGRR = MKTAG('P','G','R','R'), + SUB_PGRI = MKTAG('P','G','R','I'), + SUB_PGRL = MKTAG('P','G','R','L'), + SUB_PGAG = MKTAG('P','G','A','G'), + SUB_FLTV = MKTAG('F','L','T','V'), + + SUB_XHLT = MKTAG('X','H','L','T'), // Unofficial Oblivion Patch + SUB_XCHG = MKTAG('X','C','H','G'), // thievery.exp + + SUB_ITXT = MKTAG('I','T','X','T'), + SUB_MO5T = MKTAG('M','O','5','T'), + SUB_MOD5 = MKTAG('M','O','D','5'), + SUB_MDOB = MKTAG('M','D','O','B'), + SUB_SPIT = MKTAG('S','P','I','T'), + SUB_PTDA = MKTAG('P','T','D','A'), // TES5 + SUB_PFOR = MKTAG('P','F','O','R'), // TES5 + SUB_PFO2 = MKTAG('P','F','O','2'), // TES5 + SUB_PRCB = MKTAG('P','R','C','B'), // TES5 + SUB_PKCU = MKTAG('P','K','C','U'), // TES5 + SUB_PKC2 = MKTAG('P','K','C','2'), // TES5 + SUB_CITC = MKTAG('C','I','T','C'), // TES5 + SUB_CIS1 = MKTAG('C','I','S','1'), // TES5 + SUB_CIS2 = MKTAG('C','I','S','2'), // TES5 + SUB_TIFC = MKTAG('T','I','F','C'), // TES5 + SUB_ALCA = MKTAG('A','L','C','A'), // TES5 + SUB_ALCL = MKTAG('A','L','C','L'), // TES5 + SUB_ALCO = MKTAG('A','L','C','O'), // TES5 + SUB_ALDN = MKTAG('A','L','D','N'), // TES5 + SUB_ALEA = MKTAG('A','L','E','A'), // TES5 + SUB_ALED = MKTAG('A','L','E','D'), // TES5 + SUB_ALEQ = MKTAG('A','L','E','Q'), // TES5 + SUB_ALFA = MKTAG('A','L','F','A'), // TES5 + SUB_ALFC = MKTAG('A','L','F','C'), // TES5 + SUB_ALFD = MKTAG('A','L','F','D'), // TES5 + SUB_ALFE = MKTAG('A','L','F','E'), // TES5 + SUB_ALFI = MKTAG('A','L','F','I'), // TES5 + SUB_ALFL = MKTAG('A','L','F','L'), // TES5 + SUB_ALFR = MKTAG('A','L','F','R'), // TES5 + SUB_ALID = MKTAG('A','L','I','D'), // TES5 + SUB_ALLS = MKTAG('A','L','L','S'), // TES5 + SUB_ALNA = MKTAG('A','L','N','A'), // TES5 + SUB_ALNT = MKTAG('A','L','N','T'), // TES5 + SUB_ALPC = MKTAG('A','L','P','C'), // TES5 + SUB_ALRT = MKTAG('A','L','R','T'), // TES5 + SUB_ALSP = MKTAG('A','L','S','P'), // TES5 + SUB_ALST = MKTAG('A','L','S','T'), // TES5 + SUB_ALUA = MKTAG('A','L','U','A'), // TES5 + SUB_FLTR = MKTAG('F','L','T','R'), // TES5 + SUB_QTGL = MKTAG('Q','T','G','L'), // TES5 + SUB_TWAT = MKTAG('T','W','A','T'), // TES5 + SUB_XIBS = MKTAG('X','I','B','S'), // FO3 + SUB_REPL = MKTAG('R','E','P','L'), // FO3 + SUB_BIPL = MKTAG('B','I','P','L'), // FO3 + SUB_MODD = MKTAG('M','O','D','D'), // FO3 + SUB_MOSD = MKTAG('M','O','S','D'), // FO3 + SUB_MO3S = MKTAG('M','O','3','S'), // FO3 + SUB_XCET = MKTAG('X','C','E','T'), // FO3 + SUB_LVLG = MKTAG('L','V','L','G'), // FO3 + SUB_NVCI = MKTAG('N','V','C','I'), // FO3 + SUB_NVVX = MKTAG('N','V','V','X'), // FO3 + SUB_NVTR = MKTAG('N','V','T','R'), // FO3 + SUB_NVCA = MKTAG('N','V','C','A'), // FO3 + SUB_NVDP = MKTAG('N','V','D','P'), // FO3 + SUB_NVGD = MKTAG('N','V','G','D'), // FO3 + SUB_NVEX = MKTAG('N','V','E','X'), // FO3 + SUB_XHLP = MKTAG('X','H','L','P'), // FO3 + SUB_XRDO = MKTAG('X','R','D','O'), // FO3 + SUB_XAMT = MKTAG('X','A','M','T'), // FO3 + SUB_XAMC = MKTAG('X','A','M','C'), // FO3 + SUB_XRAD = MKTAG('X','R','A','D'), // FO3 + SUB_XORD = MKTAG('X','O','R','D'), // FO3 + SUB_XCLP = MKTAG('X','C','L','P'), // FO3 + SUB_NEXT = MKTAG('N','E','X','T'), // FO3 + SUB_QOBJ = MKTAG('Q','O','B','J'), // FO3 + SUB_POBA = MKTAG('P','O','B','A'), // FO3 + SUB_POCA = MKTAG('P','O','C','A'), // FO3 + SUB_POEA = MKTAG('P','O','E','A'), // FO3 + SUB_PKDD = MKTAG('P','K','D','D'), // FO3 + SUB_PKD2 = MKTAG('P','K','D','2'), // FO3 + SUB_PKPT = MKTAG('P','K','P','T'), // FO3 + SUB_PKED = MKTAG('P','K','E','D'), // FO3 + SUB_PKE2 = MKTAG('P','K','E','2'), // FO3 + SUB_PKAM = MKTAG('P','K','A','M'), // FO3 + SUB_PUID = MKTAG('P','U','I','D'), // FO3 + SUB_PKW3 = MKTAG('P','K','W','3'), // FO3 + SUB_PTD2 = MKTAG('P','T','D','2'), // FO3 + SUB_PLD2 = MKTAG('P','L','D','2'), // FO3 + SUB_PKFD = MKTAG('P','K','F','D'), // FO3 + SUB_IDLB = MKTAG('I','D','L','B'), // FO3 + SUB_XDCR = MKTAG('X','D','C','R'), // FO3 + SUB_DALC = MKTAG('D','A','L','C'), // FO3 + SUB_IMPS = MKTAG('I','M','P','S'), // FO3 Anchorage + SUB_IMPF = MKTAG('I','M','P','F'), // FO3 Anchorage + + SUB_XATO = MKTAG('X','A','T','O'), // FONV + SUB_INFC = MKTAG('I','N','F','C'), // FONV + SUB_INFX = MKTAG('I','N','F','X'), // FONV + SUB_TDUM = MKTAG('T','D','U','M'), // FONV + SUB_TCFU = MKTAG('T','C','F','U'), // FONV + SUB_DAT2 = MKTAG('D','A','T','2'), // FONV + SUB_RCIL = MKTAG('R','C','I','L'), // FONV + SUB_MMRK = MKTAG('M','M','R','K'), // FONV + SUB_SCRV = MKTAG('S','C','R','V'), // FONV + SUB_SCVR = MKTAG('S','C','V','R'), // FONV + SUB_SLSD = MKTAG('S','L','S','D'), // FONV + SUB_XSRF = MKTAG('X','S','R','F'), // FONV + SUB_XSRD = MKTAG('X','S','R','D'), // FONV + SUB_WMI1 = MKTAG('W','M','I','1'), // FONV + SUB_RDID = MKTAG('R','D','I','D'), // FONV + SUB_RDSB = MKTAG('R','D','S','B'), // FONV + SUB_RDSI = MKTAG('R','D','S','I'), // FONV + SUB_BRUS = MKTAG('B','R','U','S'), // FONV + SUB_VATS = MKTAG('V','A','T','S'), // FONV + SUB_VANM = MKTAG('V','A','N','M'), // FONV + SUB_MWD1 = MKTAG('M','W','D','1'), // FONV + SUB_MWD2 = MKTAG('M','W','D','2'), // FONV + SUB_MWD3 = MKTAG('M','W','D','3'), // FONV + SUB_MWD4 = MKTAG('M','W','D','4'), // FONV + SUB_MWD5 = MKTAG('M','W','D','5'), // FONV + SUB_MWD6 = MKTAG('M','W','D','6'), // FONV + SUB_MWD7 = MKTAG('M','W','D','7'), // FONV + SUB_WMI2 = MKTAG('W','M','I','2'), // FONV + SUB_WMI3 = MKTAG('W','M','I','3'), // FONV + SUB_WMS1 = MKTAG('W','M','S','1'), // FONV + SUB_WMS2 = MKTAG('W','M','S','2'), // FONV + SUB_WNM1 = MKTAG('W','N','M','1'), // FONV + SUB_WNM2 = MKTAG('W','N','M','2'), // FONV + SUB_WNM3 = MKTAG('W','N','M','3'), // FONV + SUB_WNM4 = MKTAG('W','N','M','4'), // FONV + SUB_WNM5 = MKTAG('W','N','M','5'), // FONV + SUB_WNM6 = MKTAG('W','N','M','6'), // FONV + SUB_WNM7 = MKTAG('W','N','M','7'), // FONV + SUB_JNAM = MKTAG('J','N','A','M'), // FONV + SUB_EFSD = MKTAG('E','F','S','D'), // FONV DeadMoney + }; + + enum MagicEffectID + { + // Alteration + EFI_BRDN = MKTAG('B','R','D','N'), + EFI_FTHR = MKTAG('F','T','H','R'), + EFI_FISH = MKTAG('F','I','S','H'), + EFI_FRSH = MKTAG('F','R','S','H'), + EFI_OPEN = MKTAG('O','P','N','N'), + EFI_SHLD = MKTAG('S','H','L','D'), + EFI_LISH = MKTAG('L','I','S','H'), + EFI_WABR = MKTAG('W','A','B','R'), + EFI_WAWA = MKTAG('W','A','W','A'), + + // Conjuration + EFI_BABO = MKTAG('B','A','B','O'), // Bound Boots + EFI_BACU = MKTAG('B','A','C','U'), // Bound Cuirass + EFI_BAGA = MKTAG('B','A','G','A'), // Bound Gauntlets + EFI_BAGR = MKTAG('B','A','G','R'), // Bound Greaves + EFI_BAHE = MKTAG('B','A','H','E'), // Bound Helmet + EFI_BASH = MKTAG('B','A','S','H'), // Bound Shield + EFI_BWAX = MKTAG('B','W','A','X'), // Bound Axe + EFI_BWBO = MKTAG('B','W','B','O'), // Bound Bow + EFI_BWDA = MKTAG('B','W','D','A'), // Bound Dagger + EFI_BWMA = MKTAG('B','W','M','A'), // Bound Mace + EFI_BWSW = MKTAG('B','W','S','W'), // Bound Sword + EFI_Z001 = MKTAG('Z','0','0','1'), // Summon Rufio's Ghost + EFI_Z002 = MKTAG('Z','0','0','2'), // Summon Ancestor Guardian + EFI_Z003 = MKTAG('Z','0','0','3'), // Summon Spiderling + EFI_Z005 = MKTAG('Z','0','0','5'), // Summon Bear + EFI_ZCLA = MKTAG('Z','C','L','A'), // Summon Clannfear + EFI_ZDAE = MKTAG('Z','D','A','E'), // Summon Daedroth + EFI_ZDRE = MKTAG('Z','D','R','E'), // Summon Dremora + EFI_ZDRL = MKTAG('Z','D','R','L'), // Summon Dremora Lord + EFI_ZFIA = MKTAG('Z','F','I','A'), // Summon Flame Atronach + EFI_ZFRA = MKTAG('Z','F','R','A'), // Summon Frost Atronach + EFI_ZGHO = MKTAG('Z','G','H','O'), // Summon Ghost + EFI_ZHDZ = MKTAG('Z','H','D','Z'), // Summon Headless Zombie + EFI_ZLIC = MKTAG('Z','L','I','C'), // Summon Lich + EFI_ZSCA = MKTAG('Z','S','C','A'), // Summon Scamp + EFI_ZSKE = MKTAG('Z','S','K','E'), // Summon Skeleton + EFI_ZSKA = MKTAG('Z','S','K','A'), // Summon Skeleton Guardian + EFI_ZSKH = MKTAG('Z','S','K','H'), // Summon Skeleton Hero + EFI_ZSKC = MKTAG('Z','S','K','C'), // Summon Skeleton Champion + EFI_ZSPD = MKTAG('Z','S','P','D'), // Summon Spider Daedra + EFI_ZSTA = MKTAG('Z','S','T','A'), // Summon Storm Atronach + EFI_ZWRA = MKTAG('Z','W','R','A'), // Summon Faded Wraith + EFI_ZWRL = MKTAG('Z','W','R','L'), // Summon Gloom Wraith + EFI_ZXIV = MKTAG('Z','X','I','V'), // Summon Xivilai + EFI_ZZOM = MKTAG('Z','Z','O','M'), // Summon Zombie + EFI_TURN = MKTAG('T','U','R','N'), // Turn Undead + + // Destruction + EFI_DGAT = MKTAG('D','G','A','T'), // Damage Attribute + EFI_DGFA = MKTAG('D','G','F','A'), // Damage Fatigue + EFI_DGHE = MKTAG('D','G','H','E'), // Damage Health + EFI_DGSP = MKTAG('D','G','S','P'), // Damage Magicka + EFI_DIAR = MKTAG('D','I','A','R'), // Disintegrate Armor + EFI_DIWE = MKTAG('D','I','W','E'), // Disintegrate Weapon + EFI_DRAT = MKTAG('D','R','A','T'), // Drain Attribute + EFI_DRFA = MKTAG('D','R','F','A'), // Drain Fatigue + EFI_DRHE = MKTAG('D','R','H','E'), // Drain Health + EFI_DRSP = MKTAG('D','R','S','P'), // Drain Magicka + EFI_DRSK = MKTAG('D','R','S','K'), // Drain Skill + EFI_FIDG = MKTAG('F','I','D','G'), // Fire Damage + EFI_FRDG = MKTAG('F','R','D','G'), // Frost Damage + EFI_SHDG = MKTAG('S','H','D','G'), // Shock Damage + EFI_WKDI = MKTAG('W','K','D','I'), // Weakness to Disease + EFI_WKFI = MKTAG('W','K','F','I'), // Weakness to Fire + EFI_WKFR = MKTAG('W','K','F','R'), // Weakness to Frost + EFI_WKMA = MKTAG('W','K','M','A'), // Weakness to Magic + EFI_WKNW = MKTAG('W','K','N','W'), // Weakness to Normal Weapons + EFI_WKPO = MKTAG('W','K','P','O'), // Weakness to Poison + EFI_WKSH = MKTAG('W','K','S','H'), // Weakness to Shock + + // Illusion + EFI_CALM = MKTAG('C','A','L','M'), // Calm + EFI_CHML = MKTAG('C','H','M','L'), // Chameleon + EFI_CHRM = MKTAG('C','H','R','M'), // Charm + EFI_COCR = MKTAG('C','O','C','R'), // Command Creature + EFI_COHU = MKTAG('C','O','H','U'), // Command Humanoid + EFI_DEMO = MKTAG('D','E','M','O'), // Demoralize + EFI_FRNZ = MKTAG('F','R','N','Z'), // Frenzy + EFI_INVI = MKTAG('I','N','V','I'), // Invisibility + EFI_LGHT = MKTAG('L','G','H','T'), // Light + EFI_NEYE = MKTAG('N','E','Y','E'), // Night-Eye + EFI_PARA = MKTAG('P','A','R','A'), // Paralyze + EFI_RALY = MKTAG('R','A','L','Y'), // Rally + EFI_SLNC = MKTAG('S','L','N','C'), // Silence + + // Mysticism + EFI_DTCT = MKTAG('D','T','C','T'), // Detect Life + EFI_DSPL = MKTAG('D','S','P','L'), // Dispel + EFI_REDG = MKTAG('R','E','D','G'), // Reflect Damage + EFI_RFLC = MKTAG('R','F','L','C'), // Reflect Spell + EFI_STRP = MKTAG('S','T','R','P'), // Soul Trap + EFI_SABS = MKTAG('S','A','B','S'), // Spell Absorption + EFI_TELE = MKTAG('T','E','L','E'), // Telekinesis + + // Restoration + EFI_ABAT = MKTAG('A','B','A','T'), // Absorb Attribute + EFI_ABFA = MKTAG('A','B','F','A'), // Absorb Fatigue + EFI_ABHe = MKTAG('A','B','H','e'), // Absorb Health + EFI_ABSP = MKTAG('A','B','S','P'), // Absorb Magicka + EFI_ABSK = MKTAG('A','B','S','K'), // Absorb Skill + EFI_1400 = MKTAG('1','4','0','0'), // Cure Disease + EFI_CUPA = MKTAG('C','U','P','A'), // Cure Paralysis + EFI_CUPO = MKTAG('C','U','P','O'), // Cure Poison + EFI_FOAT = MKTAG('F','O','A','T'), // Fortify Attribute + EFI_FOFA = MKTAG('F','O','F','A'), // Fortify Fatigue + EFI_FOHE = MKTAG('F','O','H','E'), // Fortify Health + EFI_FOSP = MKTAG('F','O','S','P'), // Fortify Magicka + EFI_FOSK = MKTAG('F','O','S','K'), // Fortify Skill + EFI_RSDI = MKTAG('R','S','D','I'), // Resist Disease + EFI_RSFI = MKTAG('R','S','F','I'), // Resist Fire + EFI_RSFR = MKTAG('R','S','F','R'), // Resist Frost + EFI_RSMA = MKTAG('R','S','M','A'), // Resist Magic + EFI_RSNW = MKTAG('R','S','N','W'), // Resist Normal Weapons + EFI_RSPA = MKTAG('R','S','P','A'), // Resist Paralysis + EFI_RSPO = MKTAG('R','S','P','O'), // Resist Poison + EFI_RSSH = MKTAG('R','S','S','H'), // Resist Shock + EFI_REAT = MKTAG('R','E','A','T'), // Restore Attribute + EFI_REFA = MKTAG('R','E','F','A'), // Restore Fatigue + EFI_REHE = MKTAG('R','E','H','E'), // Restore Health + EFI_RESP = MKTAG('R','E','S','P'), // Restore Magicka + + // Effects + EFI_LOCK = MKTAG('L','O','C','K'), // Lock Lock + EFI_SEFF = MKTAG('S','E','F','F'), // Script Effect + EFI_Z020 = MKTAG('Z','0','2','0'), // Summon 20 Extra + EFI_MYHL = MKTAG('M','Y','H','L'), // Summon Mythic Dawn Helmet + EFI_MYTH = MKTAG('M','Y','T','H'), // Summon Mythic Dawn Armor + EFI_REAN = MKTAG('R','E','A','N'), // Reanimate + EFI_DISE = MKTAG('D','I','S','E'), // Disease Info + EFI_POSN = MKTAG('P','O','S','N'), // Poison Info + EFI_DUMY = MKTAG('D','U','M','Y'), // Mehrunes Dagon Custom Effect + EFI_STMA = MKTAG('S','T','M','A'), // Stunted Magicka + EFI_SUDG = MKTAG('S','U','D','G'), // Sun Damage + EFI_VAMP = MKTAG('V','A','M','P'), // Vampirism + EFI_DARK = MKTAG('D','A','R','K'), // Darkness + EFI_RSWD = MKTAG('R','S','W','D') // Resist Water Damage + }; + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Groups + enum GroupType + { + Grp_RecordType = 0, + Grp_WorldChild = 1, + Grp_InteriorCell = 2, + Grp_InteriorSubCell = 3, + Grp_ExteriorCell = 4, + Grp_ExteriorSubCell = 5, + Grp_CellChild = 6, + Grp_TopicChild = 7, + Grp_CellPersistentChild = 8, + Grp_CellTemporaryChild = 9, + Grp_CellVisibleDistChild = 10 + }; + + // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Records + enum RecordFlag + { + Rec_ESM = 0x00000001, // (TES4 record only) Master (ESM) file. + Rec_Deleted = 0x00000020, // Deleted + Rec_Constant = 0x00000040, // Constant + Rec_HiddenLMap = 0x00000040, // (REFR) Hidden From Local Map (Needs Confirmation: Related to shields) + Rec_Localized = 0x00000080, // (TES4 record only) Is localized. This will make Skyrim load the + // .STRINGS, .DLSTRINGS, and .ILSTRINGS files associated with the mod. + // If this flag is not set, lstrings are treated as zstrings. + Rec_FireOff = 0x00000080, // (PHZD) Turn off fire + Rec_UpdateAnim = 0x00000100, // Must Update Anims + Rec_NoAccess = 0x00000100, // (REFR) Inaccessible + Rec_Hidden = 0x00000200, // (REFR) Hidden from local map + Rec_StartDead = 0x00000200, // (ACHR) Starts dead /(REFR) MotionBlurCastsShadows + Rec_Persistent = 0x00000400, // Quest item / Persistent reference + Rec_DispMenu = 0x00000400, // (LSCR) Displays in Main Menu + Rec_Disabled = 0x00000800, // Initially disabled + Rec_Ignored = 0x00001000, // Ignored + Rec_VisDistant = 0x00008000, // Visible when distant + Rec_RandAnim = 0x00010000, // (ACTI) Random Animation Start + Rec_Danger = 0x00020000, // (ACTI) Dangerous / Off limits (Interior cell) + // Dangerous Can't be set withough Ignore Object Interaction + Rec_Compressed = 0x00040000, // Data is compressed + Rec_CanNotWait = 0x00080000, // Can't wait + Rec_IgnoreObj = 0x00100000, // (ACTI) Ignore Object Interaction + // Ignore Object Interaction Sets Dangerous Automatically + Rec_Marker = 0x00800000, // Is Marker + Rec_Obstacle = 0x02000000, // (ACTI) Obstacle / (REFR) No AI Acquire + Rec_NavMFilter = 0x04000000, // NavMesh Gen - Filter + Rec_NavMBBox = 0x08000000, // NavMesh Gen - Bounding Box + Rec_ExitToTalk = 0x10000000, // (FURN) Must Exit to Talk + Rec_Refected = 0x10000000, // (REFR) Reflected By Auto Water + Rec_ChildUse = 0x20000000, // (FURN/IDLM) Child Can Use + Rec_NoHavok = 0x20000000, // (REFR) Don't Havok Settle + Rec_NavMGround = 0x40000000, // NavMesh Gen - Ground + Rec_NoRespawn = 0x40000000, // (REFR) NoRespawn + Rec_MultiBound = 0x80000000 // (REFR) MultiBound + }; + +#pragma pack(push, 1) + // NOTE: the label field of a group is not reliable (http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format) + union GroupLabel + { + std::uint32_t value; // formId, blockNo or raw int representation of type + char recordType[4]; // record type in ascii + std::int16_t grid[2]; // grid y, x (note the reverse order) + }; + + struct GroupTypeHeader + { + std::uint32_t typeId; + std::uint32_t groupSize; // includes the 24 bytes (20 for TES4) of header (i.e. this struct) + GroupLabel label; // format based on type + std::int32_t type; + std::uint16_t stamp; // & 0xff for day, & 0xff00 for months since Dec 2002 (i.e. 1 = Jan 2003) + std::uint16_t unknown; + std::uint16_t version; // not in TES4 + std::uint16_t unknown2; // not in TES4 + }; + + struct RecordTypeHeader + { + std::uint32_t typeId; + std::uint32_t dataSize; // does *not* include 24 bytes (20 for TES4) of header + std::uint32_t flags; + FormId id; + std::uint32_t revision; + std::uint16_t version; // not in TES4 + std::uint16_t unknown; // not in TES4 + }; + + union RecordHeader + { + struct GroupTypeHeader group; + struct RecordTypeHeader record; + }; + + struct SubRecordHeader + { + std::uint32_t typeId; + std::uint16_t dataSize; + }; + + // Grid, CellGrid and Vertex are shared by NVMI(NAVI) and NVNM(NAVM) + + struct Grid + { + std::int16_t x; + std::int16_t y; + }; + + union CellGrid + { + FormId cellId; + Grid grid; + }; + + struct Vertex + { + float x; + float y; + float z; + }; +#pragma pack(pop) + + // For pretty printing GroupHeader labels + std::string printLabel(const GroupLabel& label, const std::uint32_t type); + + void gridToString(std::int16_t x, std::int16_t y, std::string& str); +} + +#endif // ESM4_COMMON_H diff --git a/components/esm4/dialogue.hpp b/components/esm4/dialogue.hpp new file mode 100644 index 0000000000..e77d192901 --- /dev/null +++ b/components/esm4/dialogue.hpp @@ -0,0 +1,46 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_DIALOGUE_H +#define ESM4_DIALOGUE_H + +namespace ESM4 +{ + enum DialType + { + DTYP_Topic = 0, + DTYP_Conversation = 1, + DTYP_Combat = 2, + DTYP_Persuation = 3, + DTYP_Detection = 4, + DTYP_Service = 5, + DTYP_Miscellaneous = 6, + // below FO3/FONV + DTYP_Radio = 7 + }; +} + +#endif // ESM4_DIALOGUE_H diff --git a/components/esm4/effect.hpp b/components/esm4/effect.hpp new file mode 100644 index 0000000000..8580a478dc --- /dev/null +++ b/components/esm4/effect.hpp @@ -0,0 +1,56 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_EFFECT_H +#define ESM4_EFFECT_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ +#pragma pack(push, 1) + union EFI_Label + { + std::uint32_t value; + char effect[4]; + }; + + struct ScriptEffect + { + FormId formId; // Script effect (Magic effect must be SEFF) + std::int32_t school; // Magic school. See Magic schools for more information. + EFI_Label visualEffect; // Visual effect name or 0x00000000 if None + std::uint8_t flags; // 0x01 = Hostile + std::uint8_t unknown1; + std::uint8_t unknown2; + std::uint8_t unknown3; + }; +#pragma pack(pop) +} + +#endif // ESM4_EFFECT_H diff --git a/components/esm4/formid.cpp b/components/esm4/formid.cpp new file mode 100644 index 0000000000..f5493fd0ca --- /dev/null +++ b/components/esm4/formid.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#include "formid.hpp" + +#include +#include +#include +#include // strtol +#include // LONG_MIN, LONG_MAX for gcc + +#include + +namespace ESM4 +{ + void formIdToString(FormId formId, std::string& str) + { + char buf[8+1]; + int res = snprintf(buf, 8+1, "%08X", formId); + if (res > 0 && res < 8+1) + str.assign(buf); + else + throw std::runtime_error("Possible buffer overflow while converting formId"); + } + + std::string formIdToString(FormId formId) + { + std::string str; + formIdToString(formId, str); + return str; + } + + bool isFormId(const std::string& str, FormId *id) + { + if (str.size() != 8) + return false; + + char *tmp; + errno = 0; + unsigned long val = strtol(str.c_str(), &tmp, 16); + + if (tmp == str.c_str() || *tmp != '\0' + || ((val == (unsigned long)LONG_MIN || val == (unsigned long)LONG_MAX) && errno == ERANGE)) + return false; + + if (id != nullptr) + *id = static_cast(val); + + return true; + } + + FormId stringToFormId(const std::string& str) + { + if (str.size() != 8) + throw std::out_of_range("StringToFormId: incorrect string size"); + + return static_cast(std::stoul(str, nullptr, 16)); + } +} diff --git a/components/esm4/formid.hpp b/components/esm4/formid.hpp new file mode 100644 index 0000000000..6bb2c475e3 --- /dev/null +++ b/components/esm4/formid.hpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2016 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#ifndef ESM4_FORMID_H +#define ESM4_FORMID_H + +#include +#include + +namespace ESM4 +{ + typedef std::uint32_t FormId; + + void formIdToString(FormId formId, std::string& str); + + std::string formIdToString(FormId formId); + + bool isFormId(const std::string& str, FormId *id = nullptr); + + FormId stringToFormId(const std::string& str); +} + +#endif // ESM4_FORMID_H diff --git a/components/esm4/inventory.hpp b/components/esm4/inventory.hpp new file mode 100644 index 0000000000..31181ce33c --- /dev/null +++ b/components/esm4/inventory.hpp @@ -0,0 +1,55 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_INVENTORY_H +#define ESM4_INVENTORY_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ +#pragma pack(push, 1) + // LVLC, LVLI + struct LVLO + { + std::int16_t level; + std::uint16_t unknown; // sometimes missing + FormId item; + std::int16_t count; + std::uint16_t unknown2; // sometimes missing + }; + + struct InventoryItem // NPC_, CREA, CONT + { + FormId item; + std::uint32_t count; + }; +#pragma pack(pop) +} + +#endif // ESM4_INVENTORY_H diff --git a/components/esm4/lighting.hpp b/components/esm4/lighting.hpp new file mode 100644 index 0000000000..42e76eceee --- /dev/null +++ b/components/esm4/lighting.hpp @@ -0,0 +1,79 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LIGHTING_H +#define ESM4_LIGHTING_H + +#include + +namespace ESM4 +{ +#pragma pack(push, 1) + // guesses only for TES4 + struct Lighting + { // | Aichan Prison values + std::uint32_t ambient; // | 16 17 19 00 (RGBA) + std::uint32_t directional; // | 00 00 00 00 (RGBA) + std::uint32_t fogColor; // | 1D 1B 16 00 (RGBA) + float fogNear; // Fog Near | 00 00 00 00 = 0.f + float fogFar; // Fog Far | 00 80 3B 45 = 3000.f + std::int32_t rotationXY; // rotation xy | 00 00 00 00 = 0 + std::int32_t rotationZ; // rotation z | 00 00 00 00 = 0 + float fogDirFade; // Fog dir fade | 00 00 80 3F = 1.f + float fogClipDist; // Fog clip dist | 00 80 3B 45 = 3000.f + float fogPower; + }; + + struct Lighting_TES5 + { + std::uint32_t ambient; + std::uint32_t directional; + std::uint32_t fogColor; + float fogNear; + float fogFar; + std::int32_t rotationXY; + std::int32_t rotationZ; + float fogDirFade; + float fogClipDist; + float fogPower; + std::uint32_t unknown1; + std::uint32_t unknown2; + std::uint32_t unknown3; + std::uint32_t unknown4; + std::uint32_t unknown5; + std::uint32_t unknown6; + std::uint32_t unknown7; + std::uint32_t unknown8; + std::uint32_t fogColorFar; + float fogMax; + float LightFadeStart; + float LightFadeEnd; + std::uint32_t padding; + }; +#pragma pack(pop) +} + +#endif // ESM4_LIGHTING_H diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp new file mode 100644 index 0000000000..fe03fcb59e --- /dev/null +++ b/components/esm4/loadachr.cpp @@ -0,0 +1,124 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadachr.hpp" + +#include +//#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ActorCharacter::ActorCharacter() : mFormId(0), mFlags(0), mBaseObj(0), + mScale(1.f), mOwner(0), mGlobal(0), mInitiallyDisabled(false) +{ + mEditorId.clear(); + mFullName.clear(); + + mEsp.parent = 0; + mEsp.flags = 0; +} + +ESM4::ActorCharacter::~ActorCharacter() +{ +} + +void ESM4::ActorCharacter::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currCell(); // NOTE: only for persistent achr? (aren't they all persistent?) + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break; + case ESM4::SUB_DATA: reader.get(mPlacement); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XOWN: reader.get(mOwner); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + break; + } + case ESM4::SUB_XRGD: // ragdoll + case ESM4::SUB_XRGB: // ragdoll biped + { + //std::cout << "ACHR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XHRS: // horse formId + case ESM4::SUB_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 ESM4::SUB_XPRD: + case ESM4::SUB_XPPA: + case ESM4::SUB_INAM: + case ESM4::SUB_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 + { + //std::cout << "ACHR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACHR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ActorCharacter::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ActorCharacter::blank() +//{ +//} diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp new file mode 100644 index 0000000000..a8656ea926 --- /dev/null +++ b/components/esm4/loadachr.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACHR_H +#define ESM4_ACHR_H + +#include + +#include "reference.hpp" // FormId, Placement, EnableParent + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ActorCharacter + { + FormId mParent; // cell formId, from the loading sequence + // NOTE: for exterior cells it will be the dummy cell FormId + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mBaseObj; + + Placement mPlacement; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + + bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) + + EnableParent mEsp; + + ActorCharacter(); + virtual ~ActorCharacter(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACHR_H diff --git a/components/esm4/loadacre.cpp b/components/esm4/loadacre.cpp new file mode 100644 index 0000000000..3510986d3c --- /dev/null +++ b/components/esm4/loadacre.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadacre.hpp" + +#include +//#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ActorCreature::ActorCreature() : mFormId(0), mFlags(0), mBaseObj(0), mScale(1.f), + mOwner(0), mGlobal(0), mFactionRank(0), mInitiallyDisabled(false) +{ + mEditorId.clear(); + + mEsp.parent = 0; + mEsp.flags = 0; +} + +ESM4::ActorCreature::~ActorCreature() +{ +} + +void ESM4::ActorCreature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break; + case ESM4::SUB_DATA: reader.get(mPlacement); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + break; + } + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.get(mGlobal); break; // FIXME: formId? + case ESM4::SUB_XRNK: reader.get(mFactionRank); break; + case ESM4::SUB_XRGD: // ragdoll + case ESM4::SUB_XRGB: // ragdoll biped + { + // seems to occur only for dead bodies, e.g. DeadMuffy, DeadDogVicious + //std::cout << "ACRE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XLKR: // FO3 + case ESM4::SUB_XLCM: // FO3 + case ESM4::SUB_XEZN: // FO3 + case ESM4::SUB_XMRC: // FO3 + case ESM4::SUB_XAPD: // FO3 + case ESM4::SUB_XAPR: // FO3 + case ESM4::SUB_XRDS: // FO3 + case ESM4::SUB_XPRD: // FO3 + case ESM4::SUB_XATO: // FONV + { + //std::cout << "ACRE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACRE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ActorCreature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ActorCreature::blank() +//{ +//} diff --git a/components/esm4/loadacre.hpp b/components/esm4/loadacre.hpp new file mode 100644 index 0000000000..26cab34a4e --- /dev/null +++ b/components/esm4/loadacre.hpp @@ -0,0 +1,67 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ACRE_H +#define ESM4_ACRE_H + +#include + +#include "reference.hpp" // FormId, Placement, EnableParent + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ActorCreature + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + FormId mBaseObj; + + Placement mPlacement; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + std::uint32_t mFactionRank; + + bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) + + EnableParent mEsp; + + ActorCreature(); + virtual ~ActorCreature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ACRE_H diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp new file mode 100644 index 0000000000..abbe901014 --- /dev/null +++ b/components/esm4/loadacti.cpp @@ -0,0 +1,103 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "acti.hpp" + +#include +#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Activator::Activator() : mFormId(0), mFlags(0), mScriptId(0), mLoopingSound(0), mActivationSound(0), + mBoundRadius(0.f), mRadioTemplate(0), mRadioStation(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mActivationPrompt.clear(); +} + +ESM4::Activator::~Activator() +{ +} + +void ESM4::Activator::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_SNAM: reader.getFormId(mLoopingSound); break; + case ESM4::SUB_VNAM: reader.getFormId(mActivationSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_INAM: reader.getFormId(mRadioTemplate); break; // FONV + case ESM4::SUB_RNAM: reader.getFormId(mRadioStation); break; + case ESM4::SUB_XATO: reader.getZString(mActivationPrompt); break; // FONV + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_DEST: + case ESM4::SUB_DMDL: + case ESM4::SUB_DMDS: + case ESM4::SUB_DMDT: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + 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: + { + //std::cout << "ACTI " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ACTI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Activator::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Activator::blank() +//{ +//} diff --git a/components/esm4/loadalch.cpp b/components/esm4/loadalch.cpp new file mode 100644 index 0000000000..b720b740ca --- /dev/null +++ b/components/esm4/loadalch.cpp @@ -0,0 +1,121 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadalch.hpp" + +#include +#include +//#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Potion::Potion() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mScriptId(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + mMiniIcon.clear(); + + mData.weight = 0.f; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::Potion::~Potion() +{ +} + +void ESM4::Potion::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_ENIT: + { + if (subHdr.dataSize == 8) // TES4 + { + reader.get(&mItem, 8); + mItem.withdrawl = 0; + mItem.sound = 0; + break; + } + + reader.get(mItem); + reader.adjustFormId(mItem.withdrawl); + reader.adjustFormId(mItem.sound); + break; + } + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + case ESM4::SUB_CTDA: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_ETYP: // FO3 + { + //std::cout << "ALCH " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ALCH::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Potion::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Potion::blank() +//{ +//} diff --git a/components/esm4/loadalch.hpp b/components/esm4/loadalch.hpp new file mode 100644 index 0000000000..e6680dc93b --- /dev/null +++ b/components/esm4/loadalch.hpp @@ -0,0 +1,88 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ALCH_H +#define ESM4_ALCH_H + +#include +#include + +#include "effect.hpp" // FormId, ScriptEffect + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Potion + { +#pragma pack(push, 1) + struct Data + { + float weight; + }; + + struct EnchantedItem + { + std::int32_t value; + std::uint32_t flags; + FormId withdrawl; + float chanceAddition; + FormId sound; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + std::string mMiniIcon; // inventory + + FormId mPickUpSound; + FormId mDropSound; + + FormId mScriptId; + ScriptEffect mEffect; + + float mBoundRadius; + + Data mData; + EnchantedItem mItem; + + Potion(); + virtual ~Potion(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ALCH_H diff --git a/components/esm4/loadaloc.cpp b/components/esm4/loadaloc.cpp new file mode 100644 index 0000000000..a84e56592d --- /dev/null +++ b/components/esm4/loadaloc.cpp @@ -0,0 +1,172 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadaloc.hpp" + +#include +#include +//#include // FIXME: for debugging only +//#include // FIXME: for debugging only + +//#include // FIXME + +//#include "formid.hpp" // FIXME: + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MediaLocationController::MediaLocationController() : mFormId(0), mFlags(0), + mConditionalFaction(0), mLocationDelay(0.f), mRetriggerDelay(0.f), mDayStart(0), mNightStart(0) +{ + mEditorId.clear(); + mFullName.clear(); + + std::memset(&mMediaFlags, 0, sizeof(MLC_Flags)); +} + +ESM4::MediaLocationController::~MediaLocationController() +{ +} + +void ESM4::MediaLocationController::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_GNAM: + { + FormId id; + reader.getFormId(id); + mBattleSets.push_back(id); + + break; + } + case ESM4::SUB_LNAM: + { + FormId id; + reader.getFormId(id); + mLocationSets.push_back(id); + + break; + } + case ESM4::SUB_YNAM: + { + FormId id; + reader.getFormId(id); + mEnemySets.push_back(id); + + break; + } + case ESM4::SUB_HNAM: + { + FormId id; + reader.getFormId(id); + mNeutralSets.push_back(id); + + break; + } + case ESM4::SUB_XNAM: + { + FormId id; + reader.getFormId(id); + mFriendSets.push_back(id); + + break; + } + case ESM4::SUB_ZNAM: + { + FormId id; + reader.getFormId(id); + mAllySets.push_back(id); + + break; + } + case ESM4::SUB_RNAM: reader.getFormId(mConditionalFaction); break; + case ESM4::SUB_NAM1: + { + reader.get(mMediaFlags); + std::uint8_t flags = mMediaFlags.loopingOptions; + mMediaFlags.loopingOptions = (flags & 0xF0) >> 4; + mMediaFlags.factionNotFound = flags & 0x0F; // WARN: overwriting data + break; + } + case ESM4::SUB_NAM4: reader.get(mLocationDelay); break; + case ESM4::SUB_NAM7: reader.get(mRetriggerDelay); break; + case ESM4::SUB_NAM5: reader.get(mDayStart); break; + case ESM4::SUB_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 + { +#if 0 + boost::scoped_array mDataBuf(new unsigned char[subHdr.dataSize]); + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; + for (std::size_t i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) // looks like printable ascii char + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) // wrap around + ss << "\n"; + else if (i < subHdr.dataSize-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + //std::cout << "ALOC " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); +#endif + break; + } + default: + //std::cout << "ALOC " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::ALOC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MediaLocationController::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MediaLocationController::blank() +//{ +//} diff --git a/components/esm4/loadaloc.hpp b/components/esm4/loadaloc.hpp new file mode 100644 index 0000000000..1c2d17d11e --- /dev/null +++ b/components/esm4/loadaloc.hpp @@ -0,0 +1,88 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ALOC_H +#define ESM4_ALOC_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + +#pragma pack(push, 1) + struct MLC_Flags + { + // use day/night transition: 0 = loop, 1 = random, 2 = retrigger, 3 = none + // use defaults (6:00/23:54): 4 = loop, 5 = random, 6 = retrigger, 7 = none + std::uint8_t loopingOptions; + // 0 = neutral, 1 = enemy, 2 = ally, 3 = friend, 4 = location, 5 = none + std::uint8_t factionNotFound; // WARN: overwriting whatever is in this + std::uint16_t unknown; // padding? + }; +#pragma pack(pop) + + struct MediaLocationController + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + + std::vector mBattleSets; + std::vector mLocationSets; + std::vector mEnemySets; + std::vector mNeutralSets; + std::vector mFriendSets; + std::vector mAllySets; + + MLC_Flags mMediaFlags; + + FormId mConditionalFaction; + + float mLocationDelay; + float mRetriggerDelay; + + std::uint32_t mDayStart; + std::uint32_t mNightStart; + + MediaLocationController(); + virtual ~MediaLocationController(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ALOC_H diff --git a/components/esm4/loadammo.cpp b/components/esm4/loadammo.cpp new file mode 100644 index 0000000000..7ccf8e1582 --- /dev/null +++ b/components/esm4/loadammo.cpp @@ -0,0 +1,133 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadammo.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Ammunition::Ammunition() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + mMiniIcon.clear(); +} + +ESM4::Ammunition::~Ammunition() +{ +} + +void ESM4::Ammunition::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 16) // FO3 has 13 bytes even though VER_094 + { + FormId projectile; + reader.get(projectile); // FIXME: add to mData + reader.get(mData.flags); + reader.get(mData.weight); + float damageInFloat; + reader.get(damageInFloat); // FIXME: add to mData + } + else if (isFONV || subHdr.dataSize == 13) + { + reader.get(mData.speed); + std::uint8_t flags; + reader.get(flags); + mData.flags = flags; + static std::uint8_t dummy; + reader.get(dummy); + reader.get(dummy); + reader.get(dummy); + reader.get(mData.value); + reader.get(mData.clipRounds); + } + else // TES4 + { + reader.get(mData.speed); + reader.get(mData.flags); + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.damage); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_ONAM: // FO3 + case ESM4::SUB_DAT2: // FONV + case ESM4::SUB_QNAM: // FONV + case ESM4::SUB_RCIL: // FONV + case ESM4::SUB_SCRI: // FONV + { + //std::cout << "AMMO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::AMMO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Ammunition::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Ammunition::blank() +//{ +//} diff --git a/components/esm4/loadammo.hpp b/components/esm4/loadammo.hpp new file mode 100644 index 0000000000..8ff9cd9390 --- /dev/null +++ b/components/esm4/loadammo.hpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_AMMO_H +#define ESM4_AMMO_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Ammunition + { + struct Data // FIXME: TES5 projectile, damage (float) + { + float speed; + std::uint32_t flags; + std::uint32_t value; // gold + float weight; + std::uint16_t damage; + std::uint8_t clipRounds; // only in FO3/FONV + + Data() : speed(0.f), flags(0), value(0), weight(0.f), damage(0), clipRounds(0) {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + std::string mIcon; // inventory + std::string mMiniIcon; // inventory + + FormId mPickUpSound; + FormId mDropSound; + + float mBoundRadius; + + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Ammunition(); + virtual ~Ammunition(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_AMMO_H diff --git a/components/esm4/loadanio.cpp b/components/esm4/loadanio.cpp new file mode 100644 index 0000000000..b6b97a1a47 --- /dev/null +++ b/components/esm4/loadanio.cpp @@ -0,0 +1,80 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadanio.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::AnimObject::AnimObject() : mFormId(0), mFlags(0), mBoundRadius(0.f), mIdleAnim(0) +{ + mEditorId.clear(); + mModel.clear(); + mUnloadEvent.clear(); +} + +ESM4::AnimObject::~AnimObject() +{ +} + +void ESM4::AnimObject::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_BNAM: reader.getZString(mUnloadEvent); break; + case ESM4::SUB_DATA: reader.getFormId(mIdleAnim); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: // TES5 only + case ESM4::SUB_MODS: // TES5 only + { + //std::cout << "ANIO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ANIO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::AnimObject::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::AnimObject::blank() +//{ +//} diff --git a/components/esm4/loadanio.hpp b/components/esm4/loadanio.hpp new file mode 100644 index 0000000000..4259abe7b3 --- /dev/null +++ b/components/esm4/loadanio.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ANIO_H +#define ESM4_ANIO_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct AnimObject + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + FormId mIdleAnim; // only in TES4 + std::string mUnloadEvent; // only in TES5 + + AnimObject(); + virtual ~AnimObject(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ANIO_H diff --git a/components/esm4/loadappa.cpp b/components/esm4/loadappa.cpp new file mode 100644 index 0000000000..7ad7635927 --- /dev/null +++ b/components/esm4/loadappa.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadappa.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Apparatus::Apparatus() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + + mData.type = 0; + mData.value = 0; + mData.weight = 0.f; + mData.quality = 0.f; +} + +ESM4::Apparatus::~Apparatus() +{ +} + +void ESM4::Apparatus::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + { + reader.get(mData.value); + reader.get(mData.weight); + } + else + { + reader.get(mData.type); + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.quality); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_QUAL: + { + //std::cout << "APPA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::APPA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Apparatus::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Apparatus::blank() +//{ +//} diff --git a/components/esm4/loadappa.hpp b/components/esm4/loadappa.hpp new file mode 100644 index 0000000000..1723c969ee --- /dev/null +++ b/components/esm4/loadappa.hpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_APPA_H +#define ESM4_APPA_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Apparatus + { + struct Data + { + std::uint8_t type; // 0 = Mortar and Pestle, 1 = Alembic, 2 = Calcinator, 3 = Retort + std::uint32_t value; // gold + float weight; + float quality; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + std::string mIcon; // inventory + + float mBoundRadius; + + FormId mScriptId; + + Data mData; + + Apparatus(); + virtual ~Apparatus(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_APPA_H diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp new file mode 100644 index 0000000000..8b29000bb3 --- /dev/null +++ b/components/esm4/loadarma.cpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadarma.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ArmorAddon::ArmorAddon() : mFormId(0), mFlags(0), mTextureMale(0), mTextureFemale(0), + mRacePrimary(0) +{ + mEditorId.clear(); + + mModelMale.clear(); + mModelFemale.clear(); +} + +ESM4::ArmorAddon::~ArmorAddon() +{ +} + +void ESM4::ArmorAddon::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MOD2: reader.getZString(mModelMale); break; + case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; + case ESM4::SUB_MOD4: + case ESM4::SUB_MOD5: + { + std::string model; + reader.getZString(model); + + //std::cout << mEditorId << " " << ESM::printName(subHdr.typeId) << " " << model << std::endl; + + break; + } + case ESM4::SUB_NAM0: reader.getFormId(mTextureMale); break; + case ESM4::SUB_NAM1: reader.getFormId(mTextureFemale); break; + case ESM4::SUB_RNAM: reader.getFormId(mRacePrimary); break; + case ESM4::SUB_MODL: + { + if ((esmVer == ESM::VER_094 || esmVer == ESM::VER_170) && subHdr.dataSize == 4) // TES5 + { + FormId formId; + reader.getFormId(formId); + mRaces.push_back(formId); + } + else + reader.skipSubRecordData(); // FIXME: this should be mModelMale for FO3/FONV + + break; + } + case ESM4::SUB_BODT: // body template + { + reader.get(mBodyTemplate.bodyPart); + reader.get(mBodyTemplate.flags); + reader.get(mBodyTemplate.unknown1); // probably padding + reader.get(mBodyTemplate.unknown2); // probably padding + reader.get(mBodyTemplate.unknown3); // probably padding + reader.get(mBodyTemplate.type); + + break; + } + case ESM4::SUB_BOD2: // TES5 + { + reader.get(mBodyTemplate.bodyPart); + mBodyTemplate.flags = 0; + mBodyTemplate.unknown1 = 0; // probably padding + mBodyTemplate.unknown2 = 0; // probably padding + mBodyTemplate.unknown3 = 0; // probably padding + reader.get(mBodyTemplate.type); + + break; + } + case ESM4::SUB_DNAM: + case ESM4::SUB_MO2T: // FIXME: should group with MOD2 + case ESM4::SUB_MO2S: // 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_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_MO5T: + 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 + { + //std::cout << "ARMA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ARMA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ArmorAddon::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ArmorAddon::blank() +//{ +//} diff --git a/components/esm4/loadarma.hpp b/components/esm4/loadarma.hpp new file mode 100644 index 0000000000..59c4550b7f --- /dev/null +++ b/components/esm4/loadarma.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ARMA_H +#define ESM4_ARMA_H + +#include +#include +#include + +#include "formid.hpp" +#include "actor.hpp" // BodyTemplate + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ArmorAddon + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::string mModelMale; + std::string mModelFemale; + + FormId mTextureMale; + FormId mTextureFemale; + + FormId mRacePrimary; + std::vector mRaces; // TES5 only + + BodyTemplate mBodyTemplate; // TES5 + + ArmorAddon(); + virtual ~ArmorAddon(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ARMA_H diff --git a/components/esm4/loadarmo.cpp b/components/esm4/loadarmo.cpp new file mode 100644 index 0000000000..bc0765c869 --- /dev/null +++ b/components/esm4/loadarmo.cpp @@ -0,0 +1,218 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadarmo.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Armor::Armor() : mFormId(0), mFlags(0), mIsTES4(false), mIsFO3(false), mIsFONV(false), + mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), + mArmorFlags(0), mGeneralFlags(0), mScriptId(0), mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModelMale.clear(); + mModelMaleWorld.clear(); + mModelFemale.clear(); + mModelFemaleWorld.clear(); + mText.clear(); + mIconMale.clear(); + mMiniIconMale.clear(); + mIconFemale.clear(); + mMiniIconFemale.clear(); + + mData.armor = 0; + mData.value = 0; + mData.health = 0; + mData.weight = 0.f; +} + +ESM4::Armor::~Armor() +{ +} + +void ESM4::Armor::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 8) // FO3 has 12 bytes even though VER_094 + { + reader.get(mData.value); + reader.get(mData.weight); + mIsFO3 = true; + } + else if (mIsFONV || subHdr.dataSize == 12) + { + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + } + else + { + reader.get(mData); // TES4 + mIsTES4 = true; + } + + break; + } + case ESM4::SUB_MODL: // seems only for Dawnguard/Dragonborn? + { + //if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + if (subHdr.dataSize == 4) // FO3 has zstring even though VER_094 + { + FormId formId; + reader.getFormId(formId); + + mAddOns.push_back(formId); + } + else + { + if (!reader.getZString(mModelMale)) + throw std::runtime_error ("ARMO MODL data read error"); + } + + break; + } + case ESM4::SUB_MOD2: reader.getZString(mModelMaleWorld);break; + case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; + case ESM4::SUB_MOD4: reader.getZString(mModelFemaleWorld); break; + case ESM4::SUB_ICON: reader.getZString(mIconMale); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIconMale); break; + case ESM4::SUB_ICO2: reader.getZString(mIconFemale); break; + case ESM4::SUB_MIC2: reader.getZString(mMiniIconFemale); break; + case ESM4::SUB_BMDT: + { + if (subHdr.dataSize == 8) // FO3 + { + reader.get(mArmorFlags); + reader.get(mGeneralFlags); + mGeneralFlags &= 0x000000ff; + mGeneralFlags |= TYPE_FO3; + } + else // TES4 + { + reader.get(mArmorFlags); + mGeneralFlags = (mArmorFlags & 0x00ff0000) >> 16; + mGeneralFlags |= TYPE_TES4; + } + break; + } + case ESM4::SUB_BODT: + { + reader.get(mArmorFlags); + uint32_t flags = 0; + if (subHdr.dataSize == 12) + reader.get(flags); + reader.get(mGeneralFlags); // skill + mGeneralFlags &= 0x0000000f; // 0 (light), 1 (heavy) or 2 (none) + if (subHdr.dataSize == 12) + mGeneralFlags |= (flags & 0x0000000f) << 3; + mGeneralFlags |= TYPE_TES5; + break; + } + case ESM4::SUB_BOD2: + { + reader.get(mArmorFlags); + reader.get(mGeneralFlags); + mGeneralFlags &= 0x0000000f; // 0 (light), 1 (heavy) or 2 (none) + mGeneralFlags |= TYPE_TES5; + break; + } + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_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 + { + //std::cout << "ARMO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ARMO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //if ((mArmorFlags&0xffff) == 0x02) // only hair + //std::cout << "only hair " << mEditorId << std::endl; +} + +//void ESM4::Armor::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Armor::blank() +//{ +//} diff --git a/components/esm4/loadarmo.hpp b/components/esm4/loadarmo.hpp new file mode 100644 index 0000000000..f17521f507 --- /dev/null +++ b/components/esm4/loadarmo.hpp @@ -0,0 +1,196 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ARMO_H +#define ESM4_ARMO_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Armor + { + // WARN: TES4 Armorflags still has the general flags high bits + enum ArmorFlags + { + TES4_Head = 0x00000001, + TES4_Hair = 0x00000002, + TES4_UpperBody = 0x00000004, + TES4_LowerBody = 0x00000008, + TES4_Hands = 0x00000010, + TES4_Feet = 0x00000020, + TES4_RightRing = 0x00000040, + TES4_LeftRing = 0x00000080, + TES4_Amulet = 0x00000100, + TES4_Weapon = 0x00000200, + TES4_BackWeapon = 0x00000400, + TES4_SideWeapon = 0x00000800, + TES4_Quiver = 0x00001000, + TES4_Shield = 0x00002000, + TES4_Torch = 0x00004000, + TES4_Tail = 0x00008000, + // + FO3_Head = 0x00000001, + FO3_Hair = 0x00000002, + FO3_UpperBody = 0x00000004, + FO3_LeftHand = 0x00000008, + FO3_RightHand = 0x00000010, + FO3_Weapon = 0x00000020, + FO3_PipBoy = 0x00000040, + FO3_Backpack = 0x00000080, + FO3_Necklace = 0x00000100, + FO3_Headband = 0x00000200, + FO3_Hat = 0x00000400, + FO3_EyeGlasses = 0x00000800, + FO3_NoseRing = 0x00001000, + FO3_Earrings = 0x00002000, + FO3_Mask = 0x00004000, + FO3_Choker = 0x00008000, + FO3_MouthObject = 0x00010000, + FO3_BodyAddOn1 = 0x00020000, + FO3_BodyAddOn2 = 0x00040000, + FO3_BodyAddOn3 = 0x00080000, + // + TES5_Head = 0x00000001, + TES5_Hair = 0x00000002, + TES5_Body = 0x00000004, + TES5_Hands = 0x00000008, + TES5_Forearms = 0x00000010, + TES5_Amulet = 0x00000020, + TES5_Ring = 0x00000040, + TES5_Feet = 0x00000080, + TES5_Calves = 0x00000100, + TES5_Shield = 0x00000200, + TES5_Tail = 0x00000400, + TES5_LongHair = 0x00000800, + TES5_Circlet = 0x00001000, + TES5_Ears = 0x00002000, + TES5_BodyAddOn3 = 0x00004000, + TES5_BodyAddOn4 = 0x00008000, + TES5_BodyAddOn5 = 0x00010000, + TES5_BodyAddOn6 = 0x00020000, + TES5_BodyAddOn7 = 0x00040000, + TES5_BodyAddOn8 = 0x00080000, + TES5_DecapHead = 0x00100000, + TES5_Decapitate = 0x00200000, + TES5_BodyAddOn9 = 0x00400000, + TES5_BodyAddOn10 = 0x00800000, + TES5_BodyAddOn11 = 0x01000000, + TES5_BodyAddOn12 = 0x02000000, + TES5_BodyAddOn13 = 0x04000000, + TES5_BodyAddOn14 = 0x08000000, + TES5_BodyAddOn15 = 0x10000000, + TES5_BodyAddOn16 = 0x20000000, + TES5_BodyAddOn17 = 0x40000000, + TES5_FX01 = 0x80000000 + }; + + enum GeneralFlags + { + TYPE_TES4 = 0x1000, + TYPE_FO3 = 0x2000, + TYPE_TES5 = 0x3000, + TYPE_FONV = 0x4000, + // + TES4_HideRings = 0x0001, + TES4_HideAmulet = 0x0002, + TES4_NonPlayable = 0x0040, + TES4_HeavyArmor = 0x0080, + // + FO3_PowerArmor = 0x0020, + FO3_NonPlayable = 0x0040, + FO3_HeavyArmor = 0x0080, + // + TES5_LightArmor = 0x0000, + TES5_HeavyArmor = 0x0001, + TES5_None = 0x0002, + TES5_ModVoice = 0x0004, // note bit shift + TES5_NonPlayable = 0x0040 // note bit shift + }; + +#pragma pack(push, 1) + struct Data + { + std::uint16_t armor; // only in TES4? + std::uint32_t value; + std::uint32_t health; // not in TES5? + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + bool mIsTES4; // TODO: check that these match the general flags + bool mIsFO3; + bool mIsFONV; + + std::string mEditorId; + std::string mFullName; + std::string mModelMale; + std::string mModelMaleWorld; + std::string mModelFemale; + std::string mModelFemaleWorld; + std::string mText; + std::string mIconMale; + std::string mMiniIconMale; + std::string mIconFemale; + std::string mMiniIconFemale; + + FormId mPickUpSound; + FormId mDropSound; + + std::string mModel; // FIXME: for OpenCS + + float mBoundRadius; + + std::uint32_t mArmorFlags; + std::uint32_t mGeneralFlags; + FormId mScriptId; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + std::vector mAddOns; // TES5 ARMA + Data mData; + + Armor(); + virtual ~Armor(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ARMO_H diff --git a/components/esm4/loadaspc.cpp b/components/esm4/loadaspc.cpp new file mode 100644 index 0000000000..a4cb6f3939 --- /dev/null +++ b/components/esm4/loadaspc.cpp @@ -0,0 +1,94 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadaspc.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::AcousticSpace::AcousticSpace() : mFormId(0), mFlags(0), mEnvironmentType(0), mSoundRegion(0), + mIsInterior(0) +{ + mEditorId.clear(); +} + +ESM4::AcousticSpace::~AcousticSpace() +{ +} + +void ESM4::AcousticSpace::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_ANAM: reader.get(mEnvironmentType); break; + case ESM4::SUB_SNAM: + { + FormId id; + reader.getFormId(id); + mAmbientLoopSounds.push_back(id); + break; + } + case ESM4::SUB_RDAT: reader.getFormId(mSoundRegion); break; + case ESM4::SUB_INAM: reader.get(mIsInterior); break; + case ESM4::SUB_WNAM: // usually 0 for FONV (maybe # of close Actors for Walla to trigger) + { + std::uint32_t dummy; + reader.get(dummy); + //std::cout << "WNAM " << mEditorId << " " << dummy << std::endl; + break; + } + case ESM4::SUB_BNAM: // TES5 reverb formid + case ESM4::SUB_OBND: + { + //std::cout << "ASPC " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::ASPC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::AcousticSpace::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::AcousticSpace::blank() +//{ +//} diff --git a/components/esm4/loadaspc.hpp b/components/esm4/loadaspc.hpp new file mode 100644 index 0000000000..3ab1a8484d --- /dev/null +++ b/components/esm4/loadaspc.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ASPC_H +#define ESM4_ASPC_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct AcousticSpace + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::uint32_t mEnvironmentType; + + // 0 Dawn (5:00 start), 1 Afternoon (8:00), 2 Dusk (18:00), 3 Night (20:00) + std::vector mAmbientLoopSounds; + FormId mSoundRegion; + + std::uint32_t mIsInterior; // if true only use mAmbientLoopSounds[0] + + AcousticSpace(); + virtual ~AcousticSpace(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ASPC_H diff --git a/components/esm4/loadbook.cpp b/components/esm4/loadbook.cpp new file mode 100644 index 0000000000..0ffc69ef0e --- /dev/null +++ b/components/esm4/loadbook.cpp @@ -0,0 +1,121 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadbook.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Book::Book() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), + mEnchantmentPoints(0), mEnchantment(0), mPickUpSound(0), mDropSound(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + + mData.flags = 0; + mData.type = 0; + mData.bookSkill = 0; + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Book::~Book() +{ +} + +void ESM4::Book::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + //std::uint32_t esmVer = reader.esmVersion(); // currently unused + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_DATA: + { + reader.get(mData.flags); + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 16) // FO3 has 10 bytes even though VER_094 + { + static std::uint8_t dummy; + reader.get(mData.type); + reader.get(dummy); + reader.get(dummy); + reader.get(mData.teaches); + } + else + { + reader.get(mData.bookSkill); + } + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; // TODO: does this exist? + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_CNAM: + case ESM4::SUB_INAM: + case ESM4::SUB_VMAD: + { + //std::cout << "BOOK " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::BOOK::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Book::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Book::blank() +//{ +//} diff --git a/components/esm4/loadbook.hpp b/components/esm4/loadbook.hpp new file mode 100644 index 0000000000..c5c1a3529f --- /dev/null +++ b/components/esm4/loadbook.hpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_BOOK_H +#define ESM4_BOOK_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Book + { + enum Flags + { + Flag_Scroll = 0x0001, + Flag_NoTake = 0x0002 + }; + + enum BookSkill // for TES4 only + { + BookSkill_None = -1, + BookSkill_Armorer = 0, + BookSkill_Athletics = 1, + BookSkill_Blade = 2, + BookSkill_Block = 3, + BookSkill_Blunt = 4, + BookSkill_HandToHand = 5, + BookSkill_HeavyArmor = 6, + BookSkill_Alchemy = 7, + BookSkill_Alteration = 8, + BookSkill_Conjuration = 9, + BookSkill_Destruction = 10, + BookSkill_Illusion = 11, + BookSkill_Mysticism = 12, + BookSkill_Restoration = 13, + BookSkill_Acrobatics = 14, + BookSkill_LightArmor = 15, + BookSkill_Marksman = 16, + BookSkill_Mercantile = 17, + BookSkill_Security = 18, + BookSkill_Sneak = 19, + BookSkill_Speechcraft = 20 + }; + + struct Data + { + std::uint8_t flags; + std::uint8_t type; // TES5 only + std::uint32_t teaches; // TES5 only + std::int8_t bookSkill; // not in TES5 + std::uint32_t value; + float weight; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + std::string mText; + FormId mScriptId; + std::string mIcon; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + FormId mPickUpSound; + FormId mDropSound; + + Book(); + virtual ~Book(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_BOOK_H diff --git a/components/esm4/loadbptd.cpp b/components/esm4/loadbptd.cpp new file mode 100644 index 0000000000..6c6c1bc26c --- /dev/null +++ b/components/esm4/loadbptd.cpp @@ -0,0 +1,113 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadbptd.hpp" + +#include +#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +void ESM4::BodyPartData::BodyPart::clear() +{ + mPartName.clear(); + mNodeName.clear(); + mVATSTarget.clear(); + mIKStartNode.clear(); + std::memset(&mData, 0, sizeof(BPND)); + mLimbReplacementModel.clear(); + mGoreEffectsTarget.clear(); +} + +ESM4::BodyPartData::BodyPartData() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::BodyPartData::~BodyPartData() +{ +} + +void ESM4::BodyPartData::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + BodyPart bodyPart; + bodyPart.clear(); + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_BPTN: reader.getLocalizedString(bodyPart.mPartName); break; + case ESM4::SUB_BPNN: reader.getZString(bodyPart.mNodeName); break; + case ESM4::SUB_BPNT: reader.getZString(bodyPart.mVATSTarget); break; + case ESM4::SUB_BPNI: reader.getZString(bodyPart.mIKStartNode); break; + case ESM4::SUB_BPND: reader.get(bodyPart.mData); break; + case ESM4::SUB_NAM1: reader.getZString(bodyPart.mLimbReplacementModel); break; + case ESM4::SUB_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_MODS: + case ESM4::SUB_MODT: + { + //std::cout << "BPTD " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::BPTD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + + //if (mEditorId == "DefaultBodyPartData") + //std::cout << "BPTD: " << mEditorId << std::endl; // FIXME: testing only +} + +//void ESM4::BodyPartData::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::BodyPartData::blank() +//{ +//} diff --git a/components/esm4/loadbptd.hpp b/components/esm4/loadbptd.hpp new file mode 100644 index 0000000000..0a29cd22cb --- /dev/null +++ b/components/esm4/loadbptd.hpp @@ -0,0 +1,128 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_BPTD_H +#define ESM4_BPTD_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct BodyPartData + { +#pragma pack(push, 1) + struct BPND + { + float damageMult; + + // Severable + // IK Data + // IK Data - Biped Data + // Explodable + // IK Data - Is Head + // IK Data - Headtracking + // To Hit Chance - Absolute + std::uint8_t flags; + + // Torso + // Head + // Eye + // LookAt + // Fly Grab + // Saddle + std::uint8_t partType; + + std::uint8_t healthPercent; + std::int8_t actorValue; //(Actor Values) + std::uint8_t toHitChance; + + std::uint8_t explExplosionChance; // % + std::uint16_t explDebrisCount; + FormId explDebris; + FormId explExplosion; + float trackingMaxAngle; + float explDebrisScale; + + std::int32_t sevDebrisCount; + FormId sevDebris; + FormId sevExplosion; + float sevDebrisScale; + + //Struct - Gore Effects Positioning + float transX; + float transY; + float transZ; + float rotX; + float rotY; + float rotZ; + + FormId sevImpactDataSet; + FormId explImpactDataSet; + uint8_t sevDecalCount; + uint8_t explDecalCount; + uint16_t Unknown; + float limbReplacementScale; + }; +#pragma pack(pop) + + struct BodyPart + { + std::string mPartName; + std::string mNodeName; + std::string mVATSTarget; + std::string mIKStartNode; + BPND mData; + std::string mLimbReplacementModel; + std::string mGoreEffectsTarget; + + void clear(); + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + std::vector mBodyParts; + + BodyPartData(); + virtual ~BodyPartData(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_BPTD_H diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp new file mode 100644 index 0000000000..eb1f864b92 --- /dev/null +++ b/components/esm4/loadcell.cpp @@ -0,0 +1,269 @@ +/* + Copyright (C) 2015-2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadcell.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include +#include // FLT_MAX for gcc + +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Cell::Cell() : mParent(0), mFormId(0), mFlags(0), mCellFlags(0), mX(0), mY(0), mOwner(0), + mGlobal(0), mClimate(0), mWater(0), mWaterHeight(0.f), + mLightingTemplate(0), mLightingTemplateFlags(0), mMusic(0), mAcousticSpace(0), + mMusicType(0), mPreloaded(false) +{ + mEditorId.clear(); + mFullName.clear(); + + mLighting.ambient = 0; + mLighting.directional = 0; + mLighting.fogColor = 0; + mLighting.fogNear = 0.f; + mLighting.fogFar = 0.f; + mLighting.rotationXY = 0; + mLighting.rotationZ = 0; + mLighting.fogDirFade = 0.f; + mLighting.fogClipDist = 0.f; + mLighting.fogPower = FLT_MAX; // hack way to detect TES4 + + mRegions.clear(); +} + +ESM4::Cell::~Cell() +{ +} + +void ESM4::Cell::init(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currWorld(); + + reader.clearCellGrid(); // clear until XCLC FIXME: somehow do this automatically? + + // Sometimes cell 0,0 does not have an XCLC sub record (e.g. ToddLand 000009BF) + // To workaround this issue put a default value if group is "exterior sub cell" and its + // grid from label is "0 0". Note the reversed X/Y order (no matter since they're both 0 + // anyway). + if (reader.grp().type == ESM4::Grp_ExteriorSubCell + && reader.grp().label.grid[1] == 0 && reader.grp().label.grid[0] == 0) + { + ESM4::CellGrid currCellGrid; + currCellGrid.grid.x = 0; + currCellGrid.grid.y = 0; + reader.setCurrCellGrid(currCellGrid); // side effect: sets mCellGridValid true + } +} + +// TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) +// +// But, for external cells we may be scanning the whole record since we don't know if there is +// going to be an EDID subrecord. And the vast majority of cells are these kinds. +// +// So perhaps some testing needs to be done to see if scanning and skipping takes +// longer/shorter/same as loading the subrecords. +bool ESM4::Cell::preload(ESM4::Reader& reader) +{ + if (!mPreloaded) + load(reader); + + mPreloaded = true; + return true; +} + +void ESM4::Cell::load(ESM4::Reader& reader) +{ + if (mPreloaded) + return; + + // WARN: we need to call setCurrCell (and maybe setCurrCellGrid?) again before loading + // cell child groups if we are loading them after restoring the context + // (may be easier to update the context before saving?) + init(reader); + reader.setCurrCell(mFormId); // save for LAND (and other children) to access later + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: + { + if (!reader.getZString(mEditorId)) + throw std::runtime_error ("CELL EDID data read error"); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "CELL Editor ID: " << mEditorId << std::endl; +#endif + break; + } + case ESM4::SUB_XCLC: + { + //(X, Y) grid location of the cell followed by flags. Always in + //exterior cells and never in interior cells. + // + // int32 - X + // int32 - Y + // uint32 - flags (high bits look random) + // + // 0x1 - Force Hide Land Quad 1 + // 0x2 - Force Hide Land Quad 2 + // 0x4 - Force Hide Land Quad 3 + // 0x8 - Force Hide Land Quad 4 + uint32_t flags; + reader.get(mX); + reader.get(mY); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "CELL group " << ESM4::printLabel(reader.grp().label, reader.grp().type) << std::endl; + std::cout << padding << "CELL formId " << std::hex << reader.hdr().record.id << std::endl; + std::cout << padding << "CELL X " << std::dec << mX << ", Y " << mY << std::endl; +#endif + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + if (subHdr.dataSize == 12) + reader.get(flags); // not in Obvlivion, nor FO3/FONV + + // Remember cell grid for later (loading LAND, NAVM which should be CELL temporary children) + // Note that grids only apply for external cells. For interior cells use the cell's formid. + ESM4::CellGrid currCell; + currCell.grid.x = (int16_t)mX; + currCell.grid.y = (int16_t)mY; + reader.setCurrCellGrid(currCell); + + break; + } + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + if (subHdr.dataSize == 2) + reader.get(mCellFlags); + else + { + assert(subHdr.dataSize == 1 && "CELL unexpected DATA flag size"); + reader.get(&mCellFlags, sizeof(std::uint8_t)); + } + else + { + reader.get((std::uint8_t&)mCellFlags); // 8 bits in Obvlivion + } +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "flags: " << std::hex << mCellFlags << std::endl; +#endif + break; + } + case ESM4::SUB_XCLR: // for exterior cells + { + mRegions.resize(subHdr.dataSize/sizeof(FormId)); + for (std::vector::iterator it = mRegions.begin(); it != mRegions.end(); ++it) + { + reader.getFormId(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "region: " << std::hex << *it << std::endl; +#endif + } + break; + } + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.getFormId(mGlobal); break; // Oblivion only? + case ESM4::SUB_XCCM: reader.getFormId(mClimate); break; + case ESM4::SUB_XCWT: reader.getFormId(mWater); break; + case ESM4::SUB_XCLW: reader.get(mWaterHeight); break; + case ESM4::SUB_XCLL: + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + { + if (subHdr.dataSize == 40) // FO3/FONV + reader.get(mLighting); + else if (subHdr.dataSize == 92) // TES5 + { + reader.get(mLighting); + reader.skipSubRecordData(52); // FIXME + } + else + reader.skipSubRecordData(); + } + else + reader.get(&mLighting, 36); // TES4 + + break; + } + case ESM4::SUB_XCMT: reader.get(mMusicType); break; // Oblivion only? + case ESM4::SUB_LTMP: reader.getFormId(mLightingTemplate); break; + case ESM4::SUB_LNAM: reader.get(mLightingTemplateFlags); break; // seems to always follow LTMP + case ESM4::SUB_XCMO: reader.getFormId(mMusic); break; + case ESM4::SUB_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: // Oblivion only? + case ESM4::SUB_XCET: // FO3 + case ESM4::SUB_IMPF: // FO3 Zeta + { + //std::cout << "CELL " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CELL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Cell::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Cell::blank() +{ +} diff --git a/components/esm4/loadcell.hpp b/components/esm4/loadcell.hpp new file mode 100644 index 0000000000..70dc94e73a --- /dev/null +++ b/components/esm4/loadcell.hpp @@ -0,0 +1,109 @@ +/* + Copyright (C) 2015-2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CELL_H +#define ESM4_CELL_H + +#include +#include +#include + +#include "formid.hpp" +#include "lighting.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + struct ReaderContext; + struct CellGroup; + typedef std::uint32_t FormId; + + enum CellFlags // TES4 TES5 + { // ----------------------- ------------------------------------ + CELL_Interior = 0x0001, // Can't travel from here Interior + CELL_HasWater = 0x0002, // Has water (Int) Has Water (Int) + CELL_NoTravel = 0x0004, // not Can't Travel From Here(Int only) + CELL_HideLand = 0x0008, // Force hide land (Ext) No LOD Water + // Oblivion interior (Int) + CELL_Public = 0x0020, // Public place Public Area + CELL_HandChgd = 0x0040, // Hand changed Hand Changed + CELL_QuasiExt = 0x0080, // Behave like exterior Show Sky + CELL_SkyLight = 0x0100 // Use Sky Lighting + }; + + // Unlike TES3, multiple cells can have the same exterior co-ordinates. + // The cells need to be organised under world spaces. + struct Cell + { + FormId mParent; // world formId (for grouping cells), from the loading sequence + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::uint16_t mCellFlags; // TES5 can also be 8 bits + + std::int32_t mX; + std::int32_t mY; + + FormId mOwner; + FormId mGlobal; + FormId mClimate; + FormId mWater; + float mWaterHeight; + + std::vector mRegions; + Lighting mLighting; + + FormId mLightingTemplate; // FO3/FONV + std::uint32_t mLightingTemplateFlags; // FO3/FONV + + FormId mMusic; // FO3/FONV + FormId mAcousticSpace; // FO3/FONV + // TES4: 0 = default, 1 = public, 2 = dungeon + // FO3/FONV have more types (not sure how they are used, however) + std::uint8_t mMusicType; + + CellGroup *mCellGroup; + + Cell(); + virtual ~Cell(); + + void init(ESM4::Reader& reader); // common setup for both preload() and load() + + bool mPreloaded; + bool preload(ESM4::Reader& reader); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_CELL_H diff --git a/components/esm4/loadclas.cpp b/components/esm4/loadclas.cpp new file mode 100644 index 0000000000..2948976709 --- /dev/null +++ b/components/esm4/loadclas.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadclas.hpp" + +//#ifdef NDEBUG // FIXME: debugging only +//#undef NDEBUG +//#endif + +//#include +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Class::Class() +{ + mEditorId.clear(); + mFullName.clear(); + mDesc.clear(); + mIcon.clear(); +} + +ESM4::Class::~Class() +{ +} + +void ESM4::Class::load(ESM4::Reader& reader) +{ + //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mDesc); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: + { + //std::cout << "CLAS " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CLAS::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Class::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Class::blank() +//{ +//} diff --git a/components/esm4/loadclas.hpp b/components/esm4/loadclas.hpp new file mode 100644 index 0000000000..83e8372f4a --- /dev/null +++ b/components/esm4/loadclas.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CLAS_H +#define ESM4_CLAS_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Class + { + struct Data + { + std::uint32_t attr; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mDesc; + std::string mIcon; + Data mData; + + Class(); + ~Class(); + + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& reader) const; + + //void blank(); + }; +} + +#endif // ESM4_CLAS_H diff --git a/components/esm4/loadclfm.cpp b/components/esm4/loadclfm.cpp new file mode 100644 index 0000000000..b926cb93fd --- /dev/null +++ b/components/esm4/loadclfm.cpp @@ -0,0 +1,93 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadclfm.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Colour::Colour() : mFormId(0), mFlags(0), mPlayable(0) +{ + mEditorId.clear(); + mFullName.clear(); + + mColour.red = 0; + mColour.green = 0; + mColour.blue = 0; + mColour.custom = 0; +} + +ESM4::Colour::~Colour() +{ +} + +void ESM4::Colour::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_CNAM: + { + reader.get(mColour.red); + reader.get(mColour.green); + reader.get(mColour.blue); + reader.get(mColour.custom); + + break; + } + case ESM4::SUB_FNAM: + { + reader.get(mPlayable); + + break; + } + default: + //std::cout << "CLFM " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::CLFM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Colour::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Colour::blank() +//{ +//} diff --git a/components/esm4/loadclfm.hpp b/components/esm4/loadclfm.hpp new file mode 100644 index 0000000000..2a18e42cd6 --- /dev/null +++ b/components/esm4/loadclfm.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CLFM_H +#define ESM4_CLFM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + // FIXME: duplicate with Npc + struct ColourRGB + { + std::uint8_t red; + std::uint8_t green; + std::uint8_t blue; + std::uint8_t custom; // alpha? + }; + + struct Colour + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + + ColourRGB mColour; + std::uint32_t mPlayable; + + Colour(); + virtual ~Colour(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CLFM_H diff --git a/components/esm4/loadclot.cpp b/components/esm4/loadclot.cpp new file mode 100644 index 0000000000..da6473c393 --- /dev/null +++ b/components/esm4/loadclot.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadclot.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Clothing::Clothing() : mFormId(0), mFlags(0), mBoundRadius(0.f), mClothingFlags(0), + mScriptId(0), mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModelMale.clear(); + mModelMaleWorld.clear(); + mModelFemale.clear(); + mModelFemaleWorld.clear(); + mIconMale.clear(); + mIconFemale.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Clothing::~Clothing() +{ +} + +void ESM4::Clothing::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_BMDT: reader.get(mClothingFlags); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODL: reader.getZString(mModelMale); break; + case ESM4::SUB_MOD2: reader.getZString(mModelMaleWorld); break; + case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break; + case ESM4::SUB_MOD4: reader.getZString(mModelFemaleWorld); break; + case ESM4::SUB_ICON: reader.getZString(mIconMale); break; + case ESM4::SUB_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: + { + //std::cout << "CLOT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CLOT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //if ((mClothingFlags&0xffff) == 0x02) // only hair + //std::cout << "only hair " << mEditorId << std::endl; +} + +//void ESM4::Clothing::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Clothing::blank() +//{ +//} diff --git a/components/esm4/loadclot.hpp b/components/esm4/loadclot.hpp new file mode 100644 index 0000000000..06e019137b --- /dev/null +++ b/components/esm4/loadclot.hpp @@ -0,0 +1,83 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CLOT_H +#define ESM4_CLOT_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Clothing + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModelMale; + std::string mModelMaleWorld; + std::string mModelFemale; + std::string mModelFemaleWorld; + std::string mIconMale; // texture + std::string mIconFemale; // texture + + std::string mModel; // FIXME: for OpenCS + + float mBoundRadius; + + std::uint32_t mClothingFlags; // see Armor::ArmorFlags for the values + FormId mScriptId; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Clothing(); + virtual ~Clothing(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CLOT_H diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp new file mode 100644 index 0000000000..06366c88cc --- /dev/null +++ b/components/esm4/loadcont.cpp @@ -0,0 +1,107 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadcont.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Container::Container() : mFormId(0), mFlags(0), mBoundRadius(0.f), mDataFlags(0), mWeight(0.f), + mOpenSound(0), mCloseSound(0), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Container::~Container() +{ +} + +void ESM4::Container::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + reader.get(mDataFlags); + reader.get(mWeight); + break; + } + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SNAM: reader.getFormId(mOpenSound); break; + case ESM4::SUB_QNAM: reader.getFormId(mCloseSound); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: // TES5 only + 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_DEST: // FONV + case ESM4::SUB_DSTD: // FONV + case ESM4::SUB_DSTF: // FONV + case ESM4::SUB_DMDL: // FONV + case ESM4::SUB_DMDT: // FONV + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "CONT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CONT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Container::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Container::blank() +//{ +//} diff --git a/components/esm4/loadcont.hpp b/components/esm4/loadcont.hpp new file mode 100644 index 0000000000..e059d9a361 --- /dev/null +++ b/components/esm4/loadcont.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CONT_H +#define ESM4_CONT_H + +#include +#include +#include + +#include "formid.hpp" +#include "inventory.hpp" // InventoryItem + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Container + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + unsigned char mDataFlags; + float mWeight; + + FormId mOpenSound; + FormId mCloseSound; + FormId mScriptId; // TES4 only + + std::vector mInventory; + + Container(); + virtual ~Container(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CONT_H diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp new file mode 100644 index 0000000000..5fdd414256 --- /dev/null +++ b/components/esm4/loadcrea.cpp @@ -0,0 +1,232 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadcrea.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include +#include +#include +#include +#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Creature::Creature() : mFormId(0), mFlags(0), mDeathItem(0), mScriptId(0), mCombatStyle(0), + mSoundBase(0), mSound(0), mSoundChance(0), mBaseScale(0.f), + mTurningSpeed(0.f), mFootWeight(0.f), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mBloodSpray.clear(); + mBloodDecal.clear(); + + mAIData.aggression = 0; + mAIData.confidence = 0; + mAIData.energyLevel = 0; + mAIData.responsibility = 0; + mAIData.aiFlags = 0; + mAIData.trainSkill = 0; + mAIData.trainLevel = 0; + + std::memset(&mData, 0, sizeof(Data)); +} + +ESM4::Creature::~Creature() +{ +} + +void ESM4::Creature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_SPLO: + { + FormId id; + reader.getFormId(id); + mSpell.push_back(id); + break; + } + case ESM4::SUB_PKID: + { + FormId id; + reader.getFormId(id); + mAIPackages.push_back(id); + break; + } + case ESM4::SUB_SNAM: + { + reader.get(mFaction); + reader.adjustFormId(mFaction.faction); + break; + } + case ESM4::SUB_INAM: reader.getFormId(mDeathItem); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_AIDT: + { + if (subHdr.dataSize == 20) // FO3 + reader.skipSubRecordData(); + else + reader.get(mAIData); // 12 bytes + break; + } + case ESM4::SUB_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: + { + if (subHdr.dataSize == 17) // FO3 + reader.skipSubRecordData(); + else + reader.get(mData); + break; + } + case ESM4::SUB_ZNAM: reader.getFormId(mCombatStyle); break; + case ESM4::SUB_CSCR: reader.getFormId(mSoundBase); break; + case ESM4::SUB_CSDI: reader.getFormId(mSound); break; + case ESM4::SUB_CSDC: reader.get(mSoundChance); break; + case ESM4::SUB_BNAM: reader.get(mBaseScale); break; + case ESM4::SUB_TNAM: reader.get(mTurningSpeed); break; + case ESM4::SUB_WNAM: reader.get(mFootWeight); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_NAM0: reader.getZString(mBloodSpray); break; + case ESM4::SUB_NAM1: reader.getZString(mBloodDecal); break; + case ESM4::SUB_NIFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("CREA NIFZ data read error"); + + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mNif.push_back(file); + + break; + } + case ESM4::SUB_NIFT: + { + if (subHdr.dataSize != 4) // FIXME: FO3 + { + reader.skipSubRecordData(); + break; + } + + assert(subHdr.dataSize == 4 && "CREA NIFT datasize error"); + std::uint32_t nift; + reader.get(nift); + if (nift) + std::cout << "CREA NIFT " << mFormId << ", non-zero " << nift << std::endl; + break; + } + case ESM4::SUB_KFFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("CREA KFFZ data read error"); + + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mKf.push_back(file); + + break; + } + case ESM4::SUB_TPLT: reader.get(mBaseTemplate); break; // FO3 + case ESM4::SUB_PNAM: // FO3/FONV/TES5 + { + FormId bodyPart; + reader.get(bodyPart); + mBodyParts.push_back(bodyPart); + + 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 + { + //std::cout << "CREA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::CREA::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Creature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Creature::blank() +//{ +//} diff --git a/components/esm4/loadcrea.hpp b/components/esm4/loadcrea.hpp new file mode 100644 index 0000000000..c2889d1713 --- /dev/null +++ b/components/esm4/loadcrea.hpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_CREA_H +#define ESM4_CREA_H + +#include +#include +#include + +#include "actor.hpp" +#include "inventory.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Creature + { + enum ACBS_TES4 + { + TES4_Essential = 0x000002, + TES4_WeapAndShield = 0x000004, + TES4_Respawn = 0x000008, + TES4_PCLevelOffset = 0x000080, + TES4_NoLowLevelProc = 0x000200, + TES4_NoHead = 0x008000, // different meaning to npc_ + TES4_NoRightArm = 0x010000, + TES4_NoLeftArm = 0x020000, + TES4_NoCombatWater = 0x040000, + TES4_NoShadow = 0x080000, + TES4_NoCorpseCheck = 0x100000 // opposite of npc_ + }; + + enum ACBS_FO3 + { + FO3_Biped = 0x00000001, + FO3_Essential = 0x00000002, + FO3_Weap_Shield = 0x00000004, + FO3_Respawn = 0x00000008, + FO3_CanSwim = 0x00000010, + FO3_CanFly = 0x00000020, + FO3_CanWalk = 0x00000040, + FO3_PCLevelMult = 0x00000080, + FO3_NoLowLevelProc = 0x00000200, + FO3_NoBloodSpray = 0x00000800, + FO3_NoBloodDecal = 0x00001000, + FO3_NoHead = 0x00008000, + FO3_NoRightArm = 0x00010000, + FO3_NoLeftArm = 0x00020000, + FO3_NoWaterCombat = 0x00040000, + FO3_NoShadow = 0x00080000, + FO3_NoVATSMelee = 0x00100000, + FO3_AllowPCDialog = 0x00200000, + FO3_NoOpenDoors = 0x00400000, + FO3_Immobile = 0x00800000, + FO3_TiltFrontBack = 0x01000000, + FO3_TiltLeftRight = 0x02000000, + FO3_NoKnockdown = 0x04000000, + FO3_NotPushable = 0x08000000, + FO3_AllowPickpoket = 0x10000000, + FO3_IsGhost = 0x20000000, + FO3_NoRotateHead = 0x40000000, + FO3_Invulnerable = 0x80000000 + }; + +#pragma pack(push, 1) + struct Data + { + std::uint8_t unknown; + std::uint8_t combat; + std::uint8_t magic; + std::uint8_t stealth; + std::uint16_t soul; + std::uint16_t health; + std::uint16_t unknown2; + std::uint16_t damage; + AttributeValues attribs; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + FormId mDeathItem; + std::vector mSpell; + FormId mScriptId; + + AIData mAIData; + std::vector mAIPackages; + ActorBaseConfig mBaseConfig; + ActorFaction mFaction; + Data mData; + FormId mCombatStyle; + FormId mSoundBase; + FormId mSound; + std::uint8_t mSoundChance; + float mBaseScale; + float mTurningSpeed; + float mFootWeight; + std::string mBloodSpray; + std::string mBloodDecal; + + float mBoundRadius; + std::vector mNif; // NIF filenames, get directory from mModel + std::vector mKf; + + std::vector mInventory; + + FormId mBaseTemplate; // FO3/FONV + std::vector mBodyParts; // FO3/FONV + + Creature(); + virtual ~Creature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_CREA_H diff --git a/components/esm4/loaddial.cpp b/components/esm4/loaddial.cpp new file mode 100644 index 0000000000..716da441d5 --- /dev/null +++ b/components/esm4/loaddial.cpp @@ -0,0 +1,125 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loaddial.hpp" + +#include +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Dialogue::Dialogue() : mFormId(0), mFlags(0), mDoAllBeforeRepeat(false), + mDialType(0), mDialFlags(0), mPriority(0.f) +{ + mEditorId.clear(); + mTopicName.clear(); + + mTextDumb.clear(); // FIXME: temp name +} + +ESM4::Dialogue::~Dialogue() +{ +} + +void ESM4::Dialogue::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mTopicName); break; + case ESM4::SUB_QSTI: + { + FormId questId; + reader.getFormId(questId); + mQuests.push_back(questId); + + break; + } + case ESM4::SUB_QSTR: // Seems never used in TES4 + { + FormId questRem; + reader.getFormId(questRem); + mQuestsRemoved.push_back(questRem); + + break; + } + case ESM4::SUB_DATA: + { + if (subHdr.dataSize == 4) // TES5 + { + std::uint8_t dummy; + reader.get(dummy); + if (dummy != 0) + mDoAllBeforeRepeat = true; + } + + reader.get(mDialType); // TES4/FO3/FONV/TES5 + + if (subHdr.dataSize >= 2) // FO3/FONV/TES5 + reader.get(mDialFlags); + + if (subHdr.dataSize >= 3) // TES5 + reader.skipSubRecordData(1); // unknown + + break; + } + case ESM4::SUB_PNAM: reader.get(mPriority); break; // FO3/FONV + case ESM4::SUB_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 + { + //std::cout << "DIAL " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::DIAL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Dialogue::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Dialogue::blank() +//{ +//} diff --git a/components/esm4/loaddial.hpp b/components/esm4/loaddial.hpp new file mode 100644 index 0000000000..ef44c841fc --- /dev/null +++ b/components/esm4/loaddial.hpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_DIAL_H +#define ESM4_DIAL_H + +#include +#include +#include + +#include "formid.hpp" +#include "dialogue.hpp" // DialType + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Dialogue + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::vector mQuests; + std::vector mQuestsRemoved; // FONV only? + std::string mTopicName; + + std::string mTextDumb; // FIXME: temp name + + bool mDoAllBeforeRepeat; // TES5 only + std::uint8_t mDialType; // DialType + std::uint8_t mDialFlags; // FO3/FONV: 0x1 rumours, 0x2 top-level + + float mPriority; + + Dialogue(); + virtual ~Dialogue(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_DIAL_H diff --git a/components/esm4/loaddobj.cpp b/components/esm4/loaddobj.cpp new file mode 100644 index 0000000000..307674a5db --- /dev/null +++ b/components/esm4/loaddobj.cpp @@ -0,0 +1,128 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loaddobj.hpp" + +#include +#include +//#include // FIXME: for debugging only + +//#include "formid.hpp" + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::DefaultObj::DefaultObj() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + std::memset(&mData, 0, sizeof(Defaults)); +} + +ESM4::DefaultObj::~DefaultObj() +{ +} + +void ESM4::DefaultObj::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; // "DefaultObjectManager" + case ESM4::SUB_DATA: + { + reader.getFormId(mData.stimpack); + reader.getFormId(mData.superStimpack); + reader.getFormId(mData.radX); + reader.getFormId(mData.radAway); + reader.getFormId(mData.morphine); + reader.getFormId(mData.perkParalysis); + reader.getFormId(mData.playerFaction); + reader.getFormId(mData.mysteriousStrangerNPC); + reader.getFormId(mData.mysteriousStrangerFaction); + reader.getFormId(mData.defaultMusic); + reader.getFormId(mData.battleMusic); + reader.getFormId(mData.deathMusic); + reader.getFormId(mData.successMusic); + reader.getFormId(mData.levelUpMusic); + reader.getFormId(mData.playerVoiceMale); + reader.getFormId(mData.playerVoiceMaleChild); + reader.getFormId(mData.playerVoiceFemale); + reader.getFormId(mData.playerVoiceFemaleChild); + reader.getFormId(mData.eatPackageDefaultFood); + reader.getFormId(mData.everyActorAbility); + reader.getFormId(mData.drugWearsOffImageSpace); + // below FONV only + if (subHdr.dataSize == 136) // FONV 136/4 = 34 formid + { + reader.getFormId(mData.doctorsBag); + reader.getFormId(mData.missFortuneNPC); + reader.getFormId(mData.missFortuneFaction); + reader.getFormId(mData.meltdownExplosion); + reader.getFormId(mData.unarmedForwardPA); + reader.getFormId(mData.unarmedBackwardPA); + reader.getFormId(mData.unarmedLeftPA); + reader.getFormId(mData.unarmedRightPA); + reader.getFormId(mData.unarmedCrouchPA); + reader.getFormId(mData.unarmedCounterPA); + reader.getFormId(mData.spotterEffect); + reader.getFormId(mData.itemDetectedEfect); + reader.getFormId(mData.cateyeMobileEffectNYI); + } + + break; + } + case ESM4::SUB_DNAM: + { + //std::cout << "DOBJ " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + //std::cout << "DOBJ " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::DOBJ::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::DefaultObj::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::DefaultObj::blank() +//{ +//} diff --git a/components/esm4/loaddobj.hpp b/components/esm4/loaddobj.hpp new file mode 100644 index 0000000000..6d708fcbbc --- /dev/null +++ b/components/esm4/loaddobj.hpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_DOBJ_H +#define ESM4_DOBJ_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Defaults + { + FormId stimpack; + FormId superStimpack; + FormId radX; + FormId radAway; + FormId morphine; + FormId perkParalysis; + FormId playerFaction; + FormId mysteriousStrangerNPC; + FormId mysteriousStrangerFaction; + FormId defaultMusic; + FormId battleMusic; + FormId deathMusic; + FormId successMusic; + FormId levelUpMusic; + FormId playerVoiceMale; + FormId playerVoiceMaleChild; + FormId playerVoiceFemale; + FormId playerVoiceFemaleChild; + FormId eatPackageDefaultFood; + FormId everyActorAbility; + FormId drugWearsOffImageSpace; + // below FONV only + FormId doctorsBag; + FormId missFortuneNPC; + FormId missFortuneFaction; + FormId meltdownExplosion; + FormId unarmedForwardPA; + FormId unarmedBackwardPA; + FormId unarmedLeftPA; + FormId unarmedRightPA; + FormId unarmedCrouchPA; + FormId unarmedCounterPA; + FormId spotterEffect; + FormId itemDetectedEfect; + FormId cateyeMobileEffectNYI; + }; + + struct DefaultObj + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + Defaults mData; + + DefaultObj(); + virtual ~DefaultObj(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_DOBJ_H diff --git a/components/esm4/loaddoor.cpp b/components/esm4/loaddoor.cpp new file mode 100644 index 0000000000..45ac1f0b6c --- /dev/null +++ b/components/esm4/loaddoor.cpp @@ -0,0 +1,93 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loaddoor.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Door::Door() : mFormId(0), mFlags(0), mBoundRadius(0.f), mDoorFlags(0), mScriptId(0), + mOpenSound(0), mCloseSound(0), mLoopSound(0), mRandomTeleport(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Door::~Door() +{ +} + +void ESM4::Door::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_SNAM: reader.getFormId(mOpenSound); break; + case ESM4::SUB_ANAM: reader.getFormId(mCloseSound); break; + case ESM4::SUB_BNAM: reader.getFormId(mLoopSound); break; + case ESM4::SUB_FNAM: reader.get(mDoorFlags); break; + case ESM4::SUB_TNAM: reader.getFormId(mRandomTeleport); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + 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 + { + //std::cout << "DOOR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::DOOR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Door::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Door::blank() +//{ +//} diff --git a/components/esm4/loaddoor.hpp b/components/esm4/loaddoor.hpp new file mode 100644 index 0000000000..0a332339d5 --- /dev/null +++ b/components/esm4/loaddoor.hpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_DOOR_H +#define ESM4_DOOR_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Door + { + enum Flags + { + Flag_OblivionGate = 0x01, + Flag_AutomaticDoor = 0x02, + Flag_Hidden = 0x04, + Flag_MinimalUse = 0x08 + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + std::uint8_t mDoorFlags; + FormId mScriptId; + FormId mOpenSound; // SNDR for TES5, SOUN for others + FormId mCloseSound; // SNDR for TES5, SOUN for others + FormId mLoopSound; + FormId mRandomTeleport; + + Door(); + virtual ~Door(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_DOOR_H diff --git a/components/esm4/loadeyes.cpp b/components/esm4/loadeyes.cpp new file mode 100644 index 0000000000..059bee6c37 --- /dev/null +++ b/components/esm4/loadeyes.cpp @@ -0,0 +1,74 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadeyes.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Eyes::Eyes() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mIcon.clear(); + + mData.flags = 0; +} + +ESM4::Eyes::~Eyes() +{ +} + +void ESM4::Eyes::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + default: + throw std::runtime_error("ESM4::EYES::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Eyes::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Eyes::blank() +//{ +//} diff --git a/components/esm4/loadeyes.hpp b/components/esm4/loadeyes.hpp new file mode 100644 index 0000000000..814e2a9647 --- /dev/null +++ b/components/esm4/loadeyes.hpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_EYES_H +#define ESM4_EYES_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Eyes + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = playable? + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mIcon; // texture + + Data mData; + + Eyes(); + virtual ~Eyes(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_EYES_H diff --git a/components/esm4/loadflor.cpp b/components/esm4/loadflor.cpp new file mode 100644 index 0000000000..fab6c3f0ed --- /dev/null +++ b/components/esm4/loadflor.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadflor.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Flora::Flora() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mIngredient(0), + mSound(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Flora::~Flora() +{ +} + +void ESM4::Flora::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_PFIG: reader.getFormId(mIngredient); break; + case ESM4::SUB_PFPC: reader.get(mPercentHarvest); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: + case ESM4::SUB_FNAM: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_RNAM: + case ESM4::SUB_VMAD: + { + //std::cout << "FLOR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::FLOR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Flora::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Flora::blank() +//{ +//} diff --git a/components/esm4/loadflor.hpp b/components/esm4/loadflor.hpp new file mode 100644 index 0000000000..2f9e22d548 --- /dev/null +++ b/components/esm4/loadflor.hpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_FLOR_H +#define ESM4_FLOR_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Flora + { +#pragma pack(push, 1) + struct Production + { + std::uint8_t spring; + std::uint8_t summer; + std::uint8_t autumn; + std::uint8_t winter; + + Production() : spring(0), summer(0), autumn(0), winter(0) {} + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + FormId mScriptId; + FormId mIngredient; + FormId mSound; + Production mPercentHarvest; + + Flora(); + virtual ~Flora(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_FLOR_H diff --git a/components/esm4/loadflst.cpp b/components/esm4/loadflst.cpp new file mode 100644 index 0000000000..32b23ff284 --- /dev/null +++ b/components/esm4/loadflst.cpp @@ -0,0 +1,81 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadflst.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::FormIdList::FormIdList() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::FormIdList::~FormIdList() +{ +} + +void ESM4::FormIdList::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_LNAM: + { + FormId formId; + reader.getFormId(formId); + + mObjects.push_back(formId); + + break; + } + default: + //std::cout << "FLST " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::FLST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //std::cout << "flst " << mEditorId << " " << mObjects.size() << std::endl; // FIXME +} + +//void ESM4::FormIdList::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::FormIdList::blank() +//{ +//} diff --git a/components/esm4/loadflst.hpp b/components/esm4/loadflst.hpp new file mode 100644 index 0000000000..6fd381aa44 --- /dev/null +++ b/components/esm4/loadflst.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_FLST_H +#define ESM4_FLST_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct FormIdList + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::vector mObjects; + + FormIdList(); + virtual ~FormIdList(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_FLST_H diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp new file mode 100644 index 0000000000..6fba8bc3ce --- /dev/null +++ b/components/esm4/loadfurn.cpp @@ -0,0 +1,98 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadfurn.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Furniture::Furniture() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), + mActiveMarkerFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::Furniture::~Furniture() +{ +} + +void ESM4::Furniture::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MNAM: reader.get(mActiveMarkerFlags); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_DEST: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + 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_MODS: + case ESM4::SUB_NAM0: + case ESM4::SUB_OBND: + case ESM4::SUB_PNAM: + case ESM4::SUB_VMAD: + case ESM4::SUB_WBDT: + case ESM4::SUB_XMRK: + { + //std::cout << "FURN " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::FURN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Furniture::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Furniture::blank() +//{ +//} diff --git a/components/esm4/loadfurn.hpp b/components/esm4/loadfurn.hpp new file mode 100644 index 0000000000..89da61ebf9 --- /dev/null +++ b/components/esm4/loadfurn.hpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_FURN_H +#define ESM4_FURN_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Furniture + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + float mBoundRadius; + + FormId mScriptId; + std::uint32_t mActiveMarkerFlags; + + Furniture(); + virtual ~Furniture(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_FURN_H diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp new file mode 100644 index 0000000000..6a2fa8b00a --- /dev/null +++ b/components/esm4/loadglob.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadglob.hpp" + +#include +#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::GlobalVariable::GlobalVariable() : mFormId(0), mFlags(0), mType(0), mValue(0.f) +{ + mEditorId.clear(); +} + +ESM4::GlobalVariable::~GlobalVariable() +{ +} + +void ESM4::GlobalVariable::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FNAM: reader.get(mType); break; + case ESM4::SUB_FLTV: reader.get(mValue); break; + case ESM4::SUB_FULL: + case ESM4::SUB_MODL: + case ESM4::SUB_MODB: + case ESM4::SUB_ICON: + case ESM4::SUB_DATA: + case ESM4::SUB_OBND: // TES5 + case ESM4::SUB_VMAD: // TES5 + { + //std::cout << "GLOB " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::GLOB::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::GlobalVariable::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::GlobalVariable::blank() +//{ +//} diff --git a/components/esm4/loadglob.hpp b/components/esm4/loadglob.hpp new file mode 100644 index 0000000000..15cf4a07ac --- /dev/null +++ b/components/esm4/loadglob.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_GLOB_H +#define ESM4_GLOB_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct GlobalVariable + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::uint8_t mType; + float mValue; + + GlobalVariable(); + virtual ~GlobalVariable(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_GLOB_H diff --git a/components/esm4/loadgras.cpp b/components/esm4/loadgras.cpp new file mode 100644 index 0000000000..3818654fc1 --- /dev/null +++ b/components/esm4/loadgras.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadgras.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Grass::Grass() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Grass::~Grass() +{ +} + +void ESM4::Grass::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + { + //std::cout << "GRAS " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::GRAS::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Grass::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Grass::blank() +//{ +//} diff --git a/components/esm4/loadgras.hpp b/components/esm4/loadgras.hpp new file mode 100644 index 0000000000..d7754d03d9 --- /dev/null +++ b/components/esm4/loadgras.hpp @@ -0,0 +1,98 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_GRAS_H +#define ESM4_GRAS_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Grass + { +#pragma pack(push, 1) + // unused fields are probably packing + struct Data + { + std::uint8_t density; + std::uint8_t minSlope; + std::uint8_t maxSlope; + std::uint8_t unused; + std::uint16_t distanceFromWater; + std::uint16_t unused2; + /* + 1 Above - At Least + 2 Above - At Most + 3 Below - At Least + 4 Below - At Most + 5 Either - At Least + 6 Either - At Most + 7 Either - At Most Above + 8 Either - At Most Below + */ + std::uint32_t waterDistApplication; + float positionRange; + float heightRange; + float colorRange; + float wavePeriod; + /* + 0x01 Vertex Lighting + 0x02 Uniform Scaling + 0x04 Fit to Slope + */ + std::uint8_t flags; + std::uint8_t unused3; + std::uint16_t unused4; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + Data mData; + + Grass(); + virtual ~Grass(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_GRAS_H diff --git a/components/esm4/loadgrup.hpp b/components/esm4/loadgrup.hpp new file mode 100644 index 0000000000..8eb53a0e58 --- /dev/null +++ b/components/esm4/loadgrup.hpp @@ -0,0 +1,158 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_GRUP_H +#define ESM4_GRUP_H + +#include +#include + +#include "common.hpp" // GroupLabel + +namespace ESM4 +{ + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format#Hierarchical_Top_Groups + // + // Type | Info | + // ------+--------------------------------------+------------------- + // 2 | Interior Cell Block | + // 3 | Interior Cell Sub-Block | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // | | + // 0 | Top (Type) | + // R | WRLD | + // 1 | World Children | + // R | ROAD | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // 4 | Exterior World Block | + // 5 | Exterior World Sub-block | + // R | CELL | + // 6 | Cell Childen | + // 8 | Persistent children | + // R | REFR, ACHR, ACRE | + // 10 | Visible distant children | + // R | REFR, ACHR, ACRE | + // 9 | Temp Children | + // R | LAND | + // R | PGRD | + // R | REFR, ACHR, ACRE | + // + struct WorldGroup + { + FormId mWorld; // WRLD record for this group + + // occurs only after World Child (type 1) + // since GRUP label may not be reliable, need to keep the formid of the current WRLD in + // the reader's context + FormId mRoad; + + std::vector mCells; // FIXME should this be CellGroup* instead? + + WorldGroup() : mWorld(0), mRoad(0) {} + }; + + // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format/CELL + // + // The block and subblock groups for an interior cell are determined by the last two decimal + // digits of the lower 3 bytes of the cell form ID (the modindex is not included in the + // calculation). For example, for form ID 0x000CF2=3314, the block is 4 and the subblock is 1. + // + // The block and subblock groups for an exterior cell are determined by the X-Y coordinates of + // the cell. Each block contains 16 subblocks (4x4) and each subblock contains 64 cells (8x8). + // So each block contains 1024 cells (32x32). + // + // NOTE: There may be many CELL records in one subblock + struct CellGroup + { + FormId mCell; // CELL record for this cell group + int mCellModIndex; // from which file to get the CELL record (e.g. may have been updated) + + // For retrieving parent group size (for lazy loading or skipping) and sub-block number / grid + // NOTE: There can be more than one file that adds/modifies records to this cell group + // + // Use Case 1: To quickly get only the visble when distant records: + // + // - Find the FormId of the CELL (maybe WRLD/X/Y grid lookup or from XTEL of a REFR) + // - search a map of CELL FormId to CellGroup + // - load CELL and its child groups (or load the visible distant only, or whatever) + // + // Use Case 2: Scan the files but don't load CELL or cell group + // + // - Load referenceables and other records up front, updating them as required + // - Don't load CELL, LAND, PGRD or ROAD (keep FormId's and file index, and file + // context then skip the rest of the group) + // + std::vector mHeaders; // FIXME: is this needed? + + // FIXME: should these be pairs? i.e. so that we know from which file + // the formid came (it may have been updated by a mod) + // but does it matter? the record itself keeps track of whether it is base, + // added or modified anyway + // FIXME: should these be maps? e.g. std::map + // or vector for storage with a corresponding map of index? + + // cache (modindex adjusted) formId's of children + // FIXME: also need file index + file context of all those that has type 8 GRUP + GroupTypeHeader mHdrPersist; + std::vector mPersistent; // REFR, ACHR, ACRE + std::vector mdelPersistent; + + // FIXME: also need file index + file context of all those that has type 10 GRUP + GroupTypeHeader mHdrVisDist; + std::vector mVisibleDist; // REFR, ACHR, ACRE + std::vector mdelVisibleDist; + + // FIXME: also need file index + file context of all those that has type 9 GRUP + GroupTypeHeader mHdrTemp; + FormId mLand; // if present, assume only one LAND per exterior CELL + FormId mPgrd; // if present, seems to be the first record after LAND in Temp Cell Child GRUP + std::vector mTemporary; // REFR, ACHR, ACRE + std::vector mdelTemporary; + + // need to keep modindex and context for lazy loading (of all the files that contribute + // to this group) + + CellGroup() : mCell(0), mLand(0), mPgrd(0) {} + }; +} + +#endif // ESM4_GRUP_H diff --git a/components/esm4/loadhair.cpp b/components/esm4/loadhair.cpp new file mode 100644 index 0000000000..661c333cbd --- /dev/null +++ b/components/esm4/loadhair.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadhair.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Hair::Hair() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.flags = 0; +} + +ESM4::Hair::~Hair() +{ +} + +void ESM4::Hair::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + { + //std::cout << "HAIR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::HAIR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Hair::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Hair::blank() +//{ +//} diff --git a/components/esm4/loadhair.hpp b/components/esm4/loadhair.hpp new file mode 100644 index 0000000000..c9a4fc2756 --- /dev/null +++ b/components/esm4/loadhair.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_HAIR +#define ESM4_HAIR + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Hair + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = not playable, 0x02 = not male, 0x04 = not female, ?? = fixed + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; // mesh + std::string mIcon; // texture + + float mBoundRadius; + + Data mData; + + Hair(); + virtual ~Hair(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_HAIR diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp new file mode 100644 index 0000000000..f188890246 --- /dev/null +++ b/components/esm4/loadhdpt.cpp @@ -0,0 +1,104 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadhdpt.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::HeadPart::HeadPart() : mFormId(0), mFlags(0), mData(0), mAdditionalPart(0), mBaseTexture(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mTriFile.resize(3); +} + +ESM4::HeadPart::~HeadPart() +{ +} + +void ESM4::HeadPart::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t type; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_HNAM: reader.getFormId(mAdditionalPart); break; + case ESM4::SUB_NAM0: // TES5 + { + reader.get(type); + + break; + } + case ESM4::SUB_NAM1: // TES5 + { + std::string file; + reader.getZString(file); + + // FIXME: check type >= 0 && type < 3 + mTriFile[type] = std::move(file); + + break; + } + case ESM4::SUB_TNAM: reader.getFormId(mBaseTexture); break; + case ESM4::SUB_PNAM: + case ESM4::SUB_MODS: + case ESM4::SUB_MODT: + case ESM4::SUB_RNAM: + { + //std::cout << "HDPT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::HDPT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::HeadPart::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::HeadPart::blank() +//{ +//} diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp new file mode 100644 index 0000000000..d93411bb7e --- /dev/null +++ b/components/esm4/loadhdpt.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_HDPT_H +#define ESM4_HDPT_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct HeadPart + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + + std::uint8_t mData; + + FormId mAdditionalPart; + + std::vector mTriFile; + FormId mBaseTexture; + + HeadPart(); + virtual ~HeadPart(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_HDPT_H diff --git a/components/esm4/loadidle.cpp b/components/esm4/loadidle.cpp new file mode 100644 index 0000000000..3d45b76e82 --- /dev/null +++ b/components/esm4/loadidle.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadidle.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::IdleAnimation::IdleAnimation() : mFormId(0), mFlags(0), mParent(0), mPrevious(0) +{ + mEditorId.clear(); + mCollision.clear(); + mEvent.clear(); +} + +ESM4::IdleAnimation::~IdleAnimation() +{ +} + +void ESM4::IdleAnimation::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_DNAM: reader.getZString(mCollision); break; + case ESM4::SUB_ENAM: reader.getZString(mEvent); break; + case ESM4::SUB_ANAM: + { + reader.get(mParent); + reader.get(mPrevious); + break; + } + case ESM4::SUB_CTDA: // formId + case ESM4::SUB_DATA: // formId + { + //std::cout << "IDLE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::IDLE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::IdleAnimation::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::IdleAnimation::blank() +//{ +//} diff --git a/components/esm4/loadidle.hpp b/components/esm4/loadidle.hpp new file mode 100644 index 0000000000..cadd056122 --- /dev/null +++ b/components/esm4/loadidle.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_IDLE_H +#define ESM4_IDLE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct IdleAnimation + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mCollision; + std::string mEvent; + + FormId mParent; // IDLE or AACT + FormId mPrevious; + + IdleAnimation(); + virtual ~IdleAnimation(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_IDLE_H diff --git a/components/esm4/loadidlm.cpp b/components/esm4/loadidlm.cpp new file mode 100644 index 0000000000..ba823df300 --- /dev/null +++ b/components/esm4/loadidlm.cpp @@ -0,0 +1,103 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadidlm.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::IdleMarker::IdleMarker() : mFormId(0), mFlags(0), mIdleFlags(0), mIdleCount(0), mIdleTimer(0.f), mIdleAnim(0) +{ + mEditorId.clear(); +} + +ESM4::IdleMarker::~IdleMarker() +{ +} + +void ESM4::IdleMarker::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_IDLF: reader.get(mIdleFlags); break; + case ESM4::SUB_IDLC: + { + if (subHdr.dataSize != 1) // FO3 can have 4? + { + reader.skipSubRecordData(); + break; + } + + reader.get(mIdleCount); + break; + } + case ESM4::SUB_IDLT: reader.get(mIdleTimer); break; + case ESM4::SUB_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 + { + reader.skipSubRecordData(); + break; + } + + mIdleAnim.resize(mIdleCount); + for (unsigned int i = 0; i < static_cast(mIdleCount); ++i) + reader.get(mIdleAnim.at(i)); + break; + } + case ESM4::SUB_OBND: // object bounds + { + //std::cout << "IDLM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::IDLM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::IdleMarker::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::IdleMarker::blank() +//{ +//} diff --git a/components/esm4/loadidlm.hpp b/components/esm4/loadidlm.hpp new file mode 100644 index 0000000000..78d03a7024 --- /dev/null +++ b/components/esm4/loadidlm.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_IDLM_H +#define ESM4_IDLM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct IdleMarker + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + std::uint8_t mIdleFlags; + std::uint8_t mIdleCount; + float mIdleTimer; + std::vector mIdleAnim; + + IdleMarker(); + virtual ~IdleMarker(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_IDLM_H diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp new file mode 100644 index 0000000000..9950c59b7b --- /dev/null +++ b/components/esm4/loadimod.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadimod.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::ItemMod::ItemMod() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::ItemMod::~ItemMod() +{ +} + +void ESM4::ItemMod::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_OBND: + case ESM4::SUB_FULL: + case ESM4::SUB_MODL: + case ESM4::SUB_ICON: + case ESM4::SUB_MICO: + case ESM4::SUB_SCRI: + case ESM4::SUB_DESC: + case ESM4::SUB_YNAM: + case ESM4::SUB_ZNAM: + case ESM4::SUB_DATA: + { + //std::cout << "IMOD " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "IMOD " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::IMOD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::ItemMod::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::ItemMod::blank() +//{ +//} diff --git a/components/esm4/loadimod.hpp b/components/esm4/loadimod.hpp new file mode 100644 index 0000000000..a72fbe472d --- /dev/null +++ b/components/esm4/loadimod.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_IMOD_H +#define ESM4_IMOD_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct ItemMod + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + ItemMod(); + virtual ~ItemMod(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_IMOD_H diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp new file mode 100644 index 0000000000..a68774112b --- /dev/null +++ b/components/esm4/loadinfo.cpp @@ -0,0 +1,222 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadinfo.hpp" + +#include +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::DialogInfo::DialogInfo() : mFormId(0), mFlags(0), mQuest(0), mSound(0), + mDialType(0), mNextSpeaker(0), mInfoFlags(0), mParam3(0) +{ + std::memset(&mResponseData, 0, sizeof(TargetResponseData)); + mResponse.clear(); + mNotes.clear(); + mEdits.clear(); + + std::memset(&mTargetCondition, 0, sizeof(TargetCondition)); + + std::memset(&mScript.scriptHeader, 0, sizeof(ScriptHeader)); + mScript.scriptSource.clear(); + mScript.globReference = 0; +} + +ESM4::DialogInfo::~DialogInfo() +{ +} + +void ESM4::DialogInfo::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code + + static ScriptLocalVariableData localVar; + bool ignore = false; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_QSTI: reader.getFormId(mQuest); break; // FormId quest id + case ESM4::SUB_SNDD: reader.getFormId(mSound); break; // FO3 (not used in FONV?) + case ESM4::SUB_TRDT: + { + if (subHdr.dataSize == 16) // TES4 + reader.get(&mResponseData, 16); + else if (subHdr.dataSize == 20) // FO3 + reader.get(&mResponseData, 20); + else // FO3/FONV + { + reader.get(mResponseData); + if (mResponseData.sound) + reader.adjustFormId(mResponseData.sound); + } + + break; + } + case ESM4::SUB_NAM1: reader.getZString(mResponse); break; // response text + case ESM4::SUB_NAM2: reader.getZString(mNotes); break; // actor notes + case ESM4::SUB_NAM3: reader.getZString(mEdits); break; // not in TES4 + case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + { + if (subHdr.dataSize == 24) // TES4 + reader.get(&mTargetCondition, 24); + else if (subHdr.dataSize == 20) // FO3 + reader.get(&mTargetCondition, 20); + else if (subHdr.dataSize == 28) + { + reader.get(mTargetCondition); // FO3/FONV + if (mTargetCondition.reference) + reader.adjustFormId(mTargetCondition.reference); + } + else // TES5 + { + reader.get(&mTargetCondition, 20); + if (subHdr.dataSize == 36) + reader.getFormId(mParam3); + reader.get(mTargetCondition.runOn); + reader.get(mTargetCondition.reference); + if (mTargetCondition.reference) + reader.adjustFormId(mTargetCondition.reference); + reader.skipSubRecordData(4); // unknown + } + + break; + } + case ESM4::SUB_SCHR: + { + if (!ignore) + reader.get(mScript.scriptHeader); + else + reader.skipSubRecordData(); // TODO: does the second one ever used? + + break; + } + case ESM4::SUB_SCDA: reader.skipSubRecordData(); break; // compiled script data + case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); break; + case ESM4::SUB_SCRO: reader.getFormId(mScript.globReference); break; + case ESM4::SUB_SLSD: + { + localVar.clear(); + reader.get(localVar.index); + reader.get(localVar.unknown1); + reader.get(localVar.unknown2); + reader.get(localVar.unknown3); + reader.get(localVar.type); + reader.get(localVar.unknown4); + // WARN: assumes SCVR will follow immediately + + break; + } + case ESM4::SUB_SCVR: // assumed always pair with SLSD + { + reader.getZString(localVar.variableName); + + mScript.localVarData.push_back(localVar); + + break; + } + case ESM4::SUB_SCRV: + { + std::uint32_t index; + reader.get(index); + + mScript.localRefVarIndex.push_back(index); + + break; + } + case ESM4::SUB_NEXT: // FO3/FONV marker for next script header + { + ignore = true; + + break; + } + case ESM4::SUB_DATA: // always 3 for TES4 ? + { + if (subHdr.dataSize == 4) // FO3/FONV + { + reader.get(mDialType); + reader.get(mNextSpeaker); + reader.get(mInfoFlags); + } + else + 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_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 + { + //std::cout << "INFO " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "INFO " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::INFO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::DialogInfo::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::DialogInfo::blank() +//{ +//} diff --git a/components/esm4/loadinfo.hpp b/components/esm4/loadinfo.hpp new file mode 100644 index 0000000000..be7090cd32 --- /dev/null +++ b/components/esm4/loadinfo.hpp @@ -0,0 +1,90 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_INFO_H +#define ESM4_INFO_H + +#include +#include + +#include "formid.hpp" +#include "script.hpp" // TargetCondition +#include "dialogue.hpp" // DialType + +namespace ESM4 +{ + class Reader; + class Writer; + + enum InfoFlag + { + INFO_Goodbye = 0x0001, + INFO_Random = 0x0002, + INFO_SayOnce = 0x0004, + INFO_RunImmediately = 0x0008, + INFO_InfoRefusal = 0x0010, + INFO_RandomEnd = 0x0020, + INFO_RunForRumors = 0x0040, + INFO_SpeechChallenge = 0x0080, + INFO_SayOnceADay = 0x0100, + INFO_AlwaysDarken = 0x0200 + }; + + struct DialogInfo + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; // FIXME: no such record for INFO, but keep here to avoid extra work for now + + FormId mQuest; + FormId mSound; // unused? + + TargetResponseData mResponseData; + std::string mResponse; + std::string mNotes; + std::string mEdits; + + std::uint8_t mDialType; // DialType + std::uint8_t mNextSpeaker; + std::uint16_t mInfoFlags; // see above enum + + TargetCondition mTargetCondition; + FormId mParam3; // TES5 only + + ScriptDefinition mScript; // FIXME: ignoring the second one after the NEXT sub-record + + DialogInfo(); + virtual ~DialogInfo(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_INFO_H diff --git a/components/esm4/loadingr.cpp b/components/esm4/loadingr.cpp new file mode 100644 index 0000000000..03994dbfb4 --- /dev/null +++ b/components/esm4/loadingr.cpp @@ -0,0 +1,133 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadingr.hpp" + +#include +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Ingredient::Ingredient() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; + mEnchantment.value = 0; + mEnchantment.flags = 0; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::Ingredient::~Ingredient() +{ +} + +void ESM4::Ingredient::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (mFullName.empty()) + { + reader.getLocalizedString(mFullName); break; + } + else // in TES4 subsequent FULL records are script effect names + { + // FIXME: should be part of a struct? + std::string scriptEffectName; + if (!reader.getZString(scriptEffectName)) + throw std::runtime_error ("INGR FULL data read error"); + + mScriptEffect.push_back(scriptEffectName); + + break; + } + } + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 8) // FO3 is size 4 even though VER_094 + reader.get(mData); + else + reader.get(mData.weight); + + break; + } + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ENIT: reader.get(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_MODS: // Dragonborn only? + 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 + { + //std::cout << "INGR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::INGR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Ingredient::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Ingredient::blank() +//{ +//} diff --git a/components/esm4/loadingr.hpp b/components/esm4/loadingr.hpp new file mode 100644 index 0000000000..44312eb24b --- /dev/null +++ b/components/esm4/loadingr.hpp @@ -0,0 +1,83 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_INGR_H +#define ESM4_INGR_H + +#include +#include + +#include "effect.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Ingredient + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; + float weight; + }; + + struct ENIT + { + std::uint32_t value; + std::uint32_t flags; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + std::vector mScriptEffect; // FIXME: prob. should be in a struct + FormId mScriptId; + ScriptEffect mEffect; + ENIT mEnchantment; + + Data mData; + + Ingredient(); + virtual ~Ingredient(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_INGR_H diff --git a/components/esm4/loadkeym.cpp b/components/esm4/loadkeym.cpp new file mode 100644 index 0000000000..a5aba621af --- /dev/null +++ b/components/esm4/loadkeym.cpp @@ -0,0 +1,93 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadkeym.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Key::Key() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + mMiniIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Key::~Key() +{ +} + +void ESM4::Key::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + { + //std::cout << "KEYM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::KEYM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Key::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Key::blank() +//{ +//} diff --git a/components/esm4/loadkeym.hpp b/components/esm4/loadkeym.hpp new file mode 100644 index 0000000000..d59b3826d5 --- /dev/null +++ b/components/esm4/loadkeym.hpp @@ -0,0 +1,77 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_KEYM_H +#define ESM4_KEYM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Key + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + std::string mMiniIcon; // inventory + + FormId mPickUpSound; + FormId mDropSound; + + float mBoundRadius; + FormId mScriptId; + + Data mData; + + Key(); + virtual ~Key(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_KEYM_H diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp new file mode 100644 index 0000000000..2bb260da33 --- /dev/null +++ b/components/esm4/loadland.cpp @@ -0,0 +1,255 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadland.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include + +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Land::Land() : mFormId(0), mFlags(0), mLandFlags(0), mDataTypes(0) +{ + for (int i = 0; i < 4; ++i) + { + mTextures[i].base.formId = 0; + mTextures[i].base.quadrant = 0; + mTextures[i].base.unknown1 = 0; + mTextures[i].base.unknown2 = 0; + } +} + +ESM4::Land::~Land() +{ +} + +// overlap north +// +// 32 +// 31 +// 30 +// overlap . +// west . +// . +// 2 +// 1 +// 0 +// 0 1 2 ... 30 31 32 +// +// overlap south +// +void ESM4::Land::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + TxtLayer layer; + std::int8_t currentAddQuad = -1; // for VTXT following ATXT + + //std::map uniqueTextures; // FIXME: for temp testing only + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_DATA: + { + reader.get(mLandFlags); + break; + } + case ESM4::SUB_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 + { +#if 0 + reader.get(mHeightMap.heightOffset); + reader.get(mHeightMap.gradientData); + reader.get(mHeightMap.unknown1); + reader.get(mHeightMap.unknown2); +#endif + reader.get(mHeightMap); + mDataTypes |= LAND_VHGT; + break; + } + case ESM4::SUB_VCLR: // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 + { + reader.get(mVertColr); + mDataTypes |= LAND_VCLR; + break; + } + case ESM4::SUA_BTXT: + { + BTXT base; + if (reader.getExact(base)) + { + assert(base.quadrant < 4 && base.quadrant >= 0 && "base texture quadrant index error"); + + reader.adjustFormId(base.formId); + mTextures[base.quadrant].base = std::move(base); +#if 0 + std::cout << "Base Texture formid: 0x" + << std::hex << mTextures[base.quadrant].base.formId + << ", quad " << std::dec << (int)base.quadrant << std::endl; +#endif + } + break; + } + case ESM4::SUB_ATXT: + { + if (currentAddQuad != -1) + { + // FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now + std::cout << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << std::endl; + mTextures[currentAddQuad].layers.push_back(layer); + } + reader.get(layer.texture); + reader.adjustFormId(layer.texture.formId); + assert(layer.texture.quadrant < 4 && layer.texture.quadrant >= 0 + && "additional texture quadrant index error"); +#if 0 + FormId txt = layer.texture.formId; + std::map::iterator lb = uniqueTextures.lower_bound(txt); + if (lb != uniqueTextures.end() && !(uniqueTextures.key_comp()(txt, lb->first))) + { + lb->second += 1; + } + else + uniqueTextures.insert(lb, std::make_pair(txt, 1)); +#endif +#if 0 + std::cout << "Additional Texture formId: 0x" + << std::hex << layer.texture.formId + << ", quad " << std::dec << (int)layer.texture.quadrant << std::endl; + std::cout << "Additional Texture layer: " + << std::dec << (int)layer.texture.layerIndex << std::endl; +#endif + currentAddQuad = layer.texture.quadrant; + break; + } + case ESM4::SUB_VTXT: + { + assert(currentAddQuad != -1 && "VTXT without ATXT found"); + + int count = (int)reader.subRecordHeader().dataSize / sizeof(ESM4::Land::VTXT); + assert((reader.subRecordHeader().dataSize % sizeof(ESM4::Land::VTXT)) == 0 + && "ESM4::LAND VTXT data size error"); + + if (count) + { + layer.data.resize(count); + std::vector::iterator it = layer.data.begin(); + for (;it != layer.data.end(); ++it) + { + reader.get(*it); + // FIXME: debug only + //std::cout << "pos: " << std::dec << (int)(*it).position << std::endl; + } + } + mTextures[currentAddQuad].layers.push_back(layer); + + // Assumed that the layers are added in the correct sequence + // FIXME: Knights.esp doesn't seem to observe this - investigate more + //assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()-1 + //&& "additional texture layer index error"); + + currentAddQuad = -1; + layer.data.clear(); + // FIXME: debug only + //std::cout << "VTXT: count " << std::dec << count << std::endl; + break; + } + case ESM4::SUB_VTEX: // only in Oblivion? + { + int count = (int)reader.subRecordHeader().dataSize / sizeof(FormId); + assert((reader.subRecordHeader().dataSize % sizeof(FormId)) == 0 + && "ESM4::LAND VTEX data size error"); + + if (count) + { + mIds.resize(count); + for (std::vector::iterator it = mIds.begin(); it != mIds.end(); ++it) + { + reader.getFormId(*it); + // FIXME: debug only + //std::cout << "VTEX: " << std::hex << *it << std::endl; + } + } + break; + } + default: + throw std::runtime_error("ESM4::LAND::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + + if (currentAddQuad != -1) + { + // FIXME: not sure if it happens here as well + std::cout << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << " quad " << (int)layer.texture.quadrant << std::endl; + mTextures[currentAddQuad].layers.push_back(layer); + } + + bool missing = false; + for (int i = 0; i < 4; ++i) + { + if (mTextures[i].base.formId == 0) + { + //std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl; + //std::cout << "layers " << mTextures[i].layers.size() << std::endl; + // NOTE: can't set the default here since FO3/FONV may have different defaults + //mTextures[i].base.formId = 0x000008C0; // TerrainHDDirt01.dds + missing = true; + } + //else + //{ + // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " base, quad " << i << std::endl; + // std::cout << "layers " << mTextures[i].layers.size() << std::endl; + //} + } + // at least one of the quadrants do not have a base texture, return without setting the flag + if (!missing) + mDataTypes |= LAND_VTEX; +} + +//void ESM4::Land::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Land::blank() +//{ +//} diff --git a/components/esm4/loadland.hpp b/components/esm4/loadland.hpp new file mode 100644 index 0000000000..6d53a99ff2 --- /dev/null +++ b/components/esm4/loadland.hpp @@ -0,0 +1,136 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LAND_H +#define ESM4_LAND_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + + struct Land + { + enum + { + LAND_VNML = 1, + LAND_VHGT = 2, + LAND_WNAM = 4, // only in TES3? + LAND_VCLR = 8, + LAND_VTEX = 16 + }; + + // number of vertices per side + static const int VERTS_PER_SIDE = 33; + + // cell terrain size in world coords + static const int REAL_SIZE = 4096; + + // total number of vertices + static const int LAND_NUM_VERTS = VERTS_PER_SIDE * VERTS_PER_SIDE; + + static const int HEIGHT_SCALE = 8; + + // number of textures per side of a land quadrant + // (for TES4 - based on vanilla observations) + static const int QUAD_TEXTURE_PER_SIDE = 6; + +#pragma pack(push,1) + struct VHGT + { + float heightOffset; + std::int8_t gradientData[VERTS_PER_SIDE * VERTS_PER_SIDE]; + std::uint16_t unknown1; + unsigned char unknown2; + }; + + struct BTXT + { + FormId formId; + std::uint8_t quadrant; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right + std::uint8_t unknown1; + std::uint16_t unknown2; + }; + + struct ATXT + { + FormId formId; + std::uint8_t quadrant; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right + std::uint8_t unknown; + std::uint16_t layerIndex; // texture layer, 0..7 + }; + + struct VTXT + { + std::uint16_t position; // 0..288 (17x17 grid) + std::uint8_t unknown1; + std::uint8_t unknown2; + float opacity; + }; +#pragma pack(pop) + + struct TxtLayer + { + ATXT texture; + std::vector data; // alpha data + }; + + struct Texture + { + BTXT base; + std::vector layers; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::uint32_t mLandFlags; // from DATA subrecord + + // FIXME: lazy loading not yet implemented + int mDataTypes; // which data types are loaded + + signed char mVertNorm[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VNML subrecord + signed char mVertColr[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VCLR subrecord + VHGT mHeightMap; + Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right + std::vector mIds; // land texture (LTEX) formids + + Land(); + virtual ~Land(); + + virtual void load(Reader& reader); + //virtual void save(Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LAND_H diff --git a/components/esm4/loadlgtm.cpp b/components/esm4/loadlgtm.cpp new file mode 100644 index 0000000000..79867d4106 --- /dev/null +++ b/components/esm4/loadlgtm.cpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadlgtm.hpp" + +#include +#include // FLT_MAX for gcc +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LightingTemplate::LightingTemplate() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + mLighting.ambient = 0; + mLighting.directional = 0; + mLighting.fogColor = 0; + mLighting.fogNear = 0.f; + mLighting.fogFar = 0.f; + mLighting.rotationXY = 0; + mLighting.rotationZ = 0; + mLighting.fogDirFade = 0.f; + mLighting.fogClipDist = 0.f; + mLighting.fogPower = FLT_MAX; // hack way to detect TES4 +} + +ESM4::LightingTemplate::~LightingTemplate() +{ +} + +void ESM4::LightingTemplate::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_DATA: + { + if (subHdr.dataSize == 36) // TES4 + reader.get(&mLighting, 36); + if (subHdr.dataSize == 40) // FO3/FONV + reader.get(mLighting); + else if (subHdr.dataSize == 92) // TES5 + { + reader.get(mLighting); + reader.skipSubRecordData(52); // FIXME + } + else + reader.skipSubRecordData(); // throw? + + break; + } + case ESM4::SUB_DALC: // TES5 + { + //std::cout << "LGTM " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LGTM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LightingTemplate::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LightingTemplate::blank() +//{ +//} diff --git a/components/esm4/loadlgtm.hpp b/components/esm4/loadlgtm.hpp new file mode 100644 index 0000000000..9724d8b16e --- /dev/null +++ b/components/esm4/loadlgtm.hpp @@ -0,0 +1,63 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_LGTM_H +#define ESM4_LGTM_H + +#include +#include + +#include "formid.hpp" +#include "lighting.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct LightingTemplate + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + Lighting mLighting; + + LightingTemplate(); + virtual ~LightingTemplate(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LGTM_H diff --git a/components/esm4/loadligh.cpp b/components/esm4/loadligh.cpp new file mode 100644 index 0000000000..bdd125c6bb --- /dev/null +++ b/components/esm4/loadligh.cpp @@ -0,0 +1,120 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadligh.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Light::Light() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mSound(0), + mFade(0.f) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); +} + +ESM4::Light::~Light() +{ +} + +void ESM4::Light::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + // FIXME: TES4 might be uint32 as well, need to check + if (isFONV || (esmVer == ESM::VER_094 && subHdr.dataSize == 32)/*FO3*/) + { + reader.get(mData.time); // uint32 + } + else + reader.get(mData.duration); // float + + reader.get(mData.radius); + reader.get(mData.colour); + reader.get(mData.flags); + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 48) + { + reader.get(mData.falloff); + reader.get(mData.FOV); + reader.get(mData.nearClip); + reader.get(mData.frequency); + reader.get(mData.intensityAmplitude); + reader.get(mData.movementAmplitude); + } + else if (subHdr.dataSize == 32) // TES4 + { + reader.get(mData.falloff); + reader.get(mData.FOV); + } + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_FNAM: reader.get(mFade); break; + case ESM4::SUB_MODT: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: // Dragonborn only? + { + //std::cout << "LIGH " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LIGH::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Light::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Light::blank() +//{ +//} diff --git a/components/esm4/loadligh.hpp b/components/esm4/loadligh.hpp new file mode 100644 index 0000000000..4a0120d71a --- /dev/null +++ b/components/esm4/loadligh.hpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LIGH_H +#define ESM4_LIGH_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Light + { + struct Data + { + std::uint32_t time; // FO/FONV only + float duration; + std::uint32_t radius; + std::uint32_t colour; // RGBA + // flags: + // 0x00000001 = Dynamic + // 0x00000002 = Can be Carried + // 0x00000004 = Negative + // 0x00000008 = Flicker + // 0x00000020 = Off By Default + // 0x00000040 = Flicker Slow + // 0x00000080 = Pulse + // 0x00000100 = Pulse Slow + // 0x00000200 = Spot Light + // 0x00000400 = Spot Shadow + std::int32_t flags; + float falloff; + float FOV; + float nearClip; // TES5 only + float frequency; // TES5 only + float intensityAmplitude; // TES5 only + float movementAmplitude; // TES5 only + std::uint32_t value; // gold + float weight; + Data() : duration(-1), radius(0), colour(0), flags(0), falloff(1.f), FOV(90), + nearClip(0.f), frequency(0.f), intensityAmplitude(0.f), movementAmplitude(0.f), + value(0), weight(0.f) // FIXME: FOV in degrees or radians? + {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; + + float mBoundRadius; + + FormId mScriptId; + FormId mSound; + + float mFade; + + Data mData; + + Light(); + virtual ~Light(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LIGH_H diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp new file mode 100644 index 0000000000..577d5dc8e1 --- /dev/null +++ b/components/esm4/loadltex.cpp @@ -0,0 +1,107 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadltex.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include +//#include // FIXME: debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LandTexture::LandTexture() : mFormId(0), mFlags(0), mHavokFriction(0), mHavokRestitution(0), + mTextureSpecular(0), mGrass(0), mHavokMaterial(0), mTexture(0), + mMaterial(0) +{ + mEditorId.clear(); + mTextureFile.clear(); +} + +ESM4::LandTexture::~LandTexture() +{ +} + +void ESM4::LandTexture::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_HNAM: + { + if (isFONV) + { + reader.skipSubRecordData(); // FIXME: skip FONV for now + break; + } + + if ((reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + && subHdr.dataSize == 2) // FO3 is VER_094 but dataSize 3 + { + //assert(subHdr.dataSize == 2 && "LTEX unexpected HNAM size"); + reader.get(mHavokFriction); + reader.get(mHavokRestitution); + } + else + { + assert(subHdr.dataSize == 3 && "LTEX unexpected HNAM size"); + reader.get(mHavokMaterial); + reader.get(mHavokFriction); + reader.get(mHavokRestitution); + } + break; + } + case ESM4::SUB_ICON: reader.getZString(mTextureFile); break; // Oblivion only? + case ESM4::SUB_SNAM: reader.get(mTextureSpecular); break; + case ESM4::SUB_GNAM: reader.getFormId(mGrass); break; + case ESM4::SUB_TNAM: reader.getFormId(mTexture); break; // TES5 only + case ESM4::SUB_MNAM: reader.getFormId(mMaterial); break; // TES5 only + default: + throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LandTexture::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LandTexture::blank() +//{ +//} diff --git a/components/esm4/loadltex.hpp b/components/esm4/loadltex.hpp new file mode 100644 index 0000000000..934e7cc708 --- /dev/null +++ b/components/esm4/loadltex.hpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LTEX_H +#define ESM4_LTEX_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LandTexture + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::uint8_t mHavokFriction; + std::uint8_t mHavokRestitution; + + std::uint8_t mTextureSpecular; // default 30 + FormId mGrass; + + // ------ TES4 only ----- + + std::string mTextureFile; + std::uint8_t mHavokMaterial; + + // ------ TES5 only ----- + + FormId mTexture; + FormId mMaterial; + + // ---------------------- + + LandTexture(); + virtual ~LandTexture(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LTEX_H diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp new file mode 100644 index 0000000000..0088446277 --- /dev/null +++ b/components/esm4/loadlvlc.cpp @@ -0,0 +1,130 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadlvlc.hpp" + +#include +//#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LevelledCreature::LevelledCreature() : mFormId(0), mFlags(0), mScriptId(0), mTemplate(0), + mChanceNone(0), mLvlCreaFlags(0) +{ + mEditorId.clear(); +} + +ESM4::LevelledCreature::~LevelledCreature() +{ +} + +void ESM4::LevelledCreature::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_TNAM: reader.getFormId(mTemplate); break; + case ESM4::SUB_LVLD: reader.get(mChanceNone); break; + case ESM4::SUB_LVLF: reader.get(mLvlCreaFlags); break; + case ESM4::SUB_LVLO: + { + static LVLO lvlo; + if (subHdr.dataSize != 12) + { + if (subHdr.dataSize == 8) + { + reader.get(lvlo.level); + reader.get(lvlo.item); + reader.get(lvlo.count); + //std::cout << "LVLC " << mEditorId << " LVLO lev " << lvlo.level << ", item " << lvlo.item + //<< ", count " << lvlo.count << std::endl; + // FIXME: seems to happen only once, don't add to mLvlObject + // LVLC TesKvatchCreature LVLO lev 1, item 1393819648, count 2 + // 0x0001, 0x5314 0000, 0x0002 + break; + } + else + throw std::runtime_error("ESM4::LVLC::load - " + mEditorId + " LVLO size error"); + } + else + reader.get(lvlo); + + reader.adjustFormId(lvlo.item); + mLvlObject.push_back(lvlo); + break; + } + case ESM4::SUB_OBND: // FO3 + { + //std::cout << "LVLC " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LVLC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +bool ESM4::LevelledCreature::calcAllLvlLessThanPlayer() const +{ + if (mHasLvlCreaFlags) + return (mLvlCreaFlags & 0x01) != 0; + else + return (mChanceNone & 0x80) != 0; // FIXME: 0x80 is just a guess +} + +bool ESM4::LevelledCreature::calcEachItemInCount() const +{ + if (mHasLvlCreaFlags) + return (mLvlCreaFlags & 0x02) != 0; + else + return true; // FIXME: just a guess +} + +std::int8_t ESM4::LevelledCreature::chanceNone() const +{ + if (mHasLvlCreaFlags) + return mChanceNone; + else + return (mChanceNone & 0x7f); // FIXME: 0x80 is just a guess +} + +//void ESM4::LevelledCreature::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LevelledCreature::blank() +//{ +//} diff --git a/components/esm4/loadlvlc.hpp b/components/esm4/loadlvlc.hpp new file mode 100644 index 0000000000..ccd08944b5 --- /dev/null +++ b/components/esm4/loadlvlc.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LVLC_H +#define ESM4_LVLC_H + +#include +#include + +#include "formid.hpp" +#include "inventory.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LevelledCreature + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + FormId mScriptId; + FormId mTemplate; + std::int8_t mChanceNone; + + bool mHasLvlCreaFlags; + std::uint8_t mLvlCreaFlags; + + std::vector mLvlObject; + + bool calcAllLvlLessThanPlayer() const; + bool calcEachItemInCount() const; + std::int8_t chanceNone() const; + + LevelledCreature(); + virtual ~LevelledCreature(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LVLC_H diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp new file mode 100644 index 0000000000..1237a0add4 --- /dev/null +++ b/components/esm4/loadlvli.cpp @@ -0,0 +1,142 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadlvli.hpp" + +#include +#include // FIXME: for debugging + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LevelledItem::LevelledItem() : mFormId(0), mFlags(0), mChanceNone(0), mHasLvlItemFlags(false), + mLvlItemFlags(0), mData(0) +{ + mEditorId.clear(); +} + +ESM4::LevelledItem::~LevelledItem() +{ +} + +void ESM4::LevelledItem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_LVLD: reader.get(mChanceNone); break; + case ESM4::SUB_LVLF: reader.get(mLvlItemFlags); mHasLvlItemFlags = true; break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_LVLO: + { + static LVLO lvlo; + if (subHdr.dataSize != 12) + { + if (subHdr.dataSize == 8) + { + reader.get(lvlo.level); + reader.get(lvlo.item); + reader.get(lvlo.count); +// std::cout << "LVLI " << mEditorId << " LVLO lev " << lvlo.level << ", item " << lvlo.item +// << ", count " << lvlo.count << std::endl; + break; + } + else + throw std::runtime_error("ESM4::LVLI::load - " + mEditorId + " LVLO size error"); + } + else + reader.get(lvlo); + + reader.adjustFormId(lvlo.item); + 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 + { + + //std::cout << "LVLI " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LVLI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + + // FIXME: testing + //if (mHasLvlItemFlags && mChanceNone >= 90) + //std::cout << "LVLI " << mEditorId << " chance none " << int(mChanceNone) << std::endl; +} + +bool ESM4::LevelledItem::calcAllLvlLessThanPlayer() const +{ + if (mHasLvlItemFlags) + return (mLvlItemFlags & 0x01) != 0; + else + return (mChanceNone & 0x80) != 0; // FIXME: 0x80 is just a guess +} + +bool ESM4::LevelledItem::calcEachItemInCount() const +{ + if (mHasLvlItemFlags) + return (mLvlItemFlags & 0x02) != 0; + else + return mData != 0; +} + +std::int8_t ESM4::LevelledItem::chanceNone() const +{ + if (mHasLvlItemFlags) + return mChanceNone; + else + return (mChanceNone & 0x7f); // FIXME: 0x80 is just a guess +} + +bool ESM4::LevelledItem::useAll() const +{ + if (mHasLvlItemFlags) + return (mLvlItemFlags & 0x04) != 0; + else + return false; +} + +//void ESM4::LevelledItem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LevelledItem::blank() +//{ +//} diff --git a/components/esm4/loadlvli.hpp b/components/esm4/loadlvli.hpp new file mode 100644 index 0000000000..0964370e63 --- /dev/null +++ b/components/esm4/loadlvli.hpp @@ -0,0 +1,72 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LVLI_H +#define ESM4_LVLI_H + +#include +#include + +#include "formid.hpp" +#include "inventory.hpp" // LVLO + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LevelledItem + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::int8_t mChanceNone; + + bool mHasLvlItemFlags; + std::uint8_t mLvlItemFlags; + + std::uint8_t mData; + + std::vector mLvlObject; + + LevelledItem(); + virtual ~LevelledItem(); + + bool calcAllLvlLessThanPlayer() const; + bool calcEachItemInCount() const; + bool useAll() const; + std::int8_t chanceNone() const; + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LVLI_H diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp new file mode 100644 index 0000000000..fdd860b9d9 --- /dev/null +++ b/components/esm4/loadlvln.cpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadlvln.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::LevelledNpc::LevelledNpc() : mFormId(0), mFlags(0), mChanceNone(0), mLvlActorFlags(0), mListCount(0) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::LevelledNpc::~LevelledNpc() +{ +} + +void ESM4::LevelledNpc::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + //std::uint32_t esmVer = reader.esmVersion(); // currently unused + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_LLCT: reader.get(mListCount); break; + case ESM4::SUB_LVLD: reader.get(mChanceNone); break; + case ESM4::SUB_LVLF: reader.get(mLvlActorFlags); break; + case ESM4::SUB_LVLO: + { + static LVLO lvlo; + if (subHdr.dataSize != 12) + { + if (subHdr.dataSize == 8) + { + reader.get(lvlo.level); + reader.get(lvlo.item); + reader.get(lvlo.count); + break; + } + else + throw std::runtime_error("ESM4::LVLN::load - " + mEditorId + " LVLO size error"); + } +// else if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) +// { +// std::uint32_t level; +// reader.get(level); +// lvlo.level = static_cast(level); +// reader.get(lvlo.item); +// std::uint32_t count; +// reader.get(count); +// lvlo.count = static_cast(count); +// } + else + reader.get(lvlo); + + reader.adjustFormId(lvlo.item); + mLvlObject.push_back(lvlo); + break; + } + case ESM4::SUB_COED: // owner + case ESM4::SUB_OBND: // object bounds + case ESM4::SUB_MODT: // model texture data + { + //std::cout << "LVLN " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::LVLN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::LevelledNpc::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::LevelledNpc::blank() +//{ +//} diff --git a/components/esm4/loadlvln.hpp b/components/esm4/loadlvln.hpp new file mode 100644 index 0000000000..057ed9581d --- /dev/null +++ b/components/esm4/loadlvln.hpp @@ -0,0 +1,69 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_LVLN_H +#define ESM4_LVLN_H + +#include +#include + +#include "formid.hpp" +#include "inventory.hpp" // LVLO + +namespace ESM4 +{ + class Reader; + class Writer; + + struct LevelledNpc + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + std::int8_t mChanceNone; + std::uint8_t mLvlActorFlags; + + std::uint8_t mListCount; + std::vector mLvlObject; + + LevelledNpc(); + virtual ~LevelledNpc(); + + inline bool calcAllLvlLessThanPlayer() const { return (mLvlActorFlags & 0x01) != 0; } + inline bool calcEachItemInCount() const { return (mLvlActorFlags & 0x02) != 0; } + inline std::int8_t chanceNone() const { return mChanceNone; } + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_LVLN_H diff --git a/components/esm4/loadmato.cpp b/components/esm4/loadmato.cpp new file mode 100644 index 0000000000..fdbdc66ebc --- /dev/null +++ b/components/esm4/loadmato.cpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadmato.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Material::Material() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Material::~Material() +{ +} + +void ESM4::Material::load(ESM4::Reader& reader) +{ + //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DNAM: + case ESM4::SUB_DATA: + { + //std::cout << "MATO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MATO::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Material::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Material::blank() +//{ +//} diff --git a/components/esm4/loadmato.hpp b/components/esm4/loadmato.hpp new file mode 100644 index 0000000000..9b0d6c3947 --- /dev/null +++ b/components/esm4/loadmato.hpp @@ -0,0 +1,58 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MATO_H +#define ESM4_MATO_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Material + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + Material(); + virtual ~Material(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MATO_H diff --git a/components/esm4/loadmisc.cpp b/components/esm4/loadmisc.cpp new file mode 100644 index 0000000000..ee3c10d0b6 --- /dev/null +++ b/components/esm4/loadmisc.cpp @@ -0,0 +1,95 @@ +/* + Copyright (C) 2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadmisc.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MiscItem::MiscItem() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + mMiniIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::MiscItem::~MiscItem() +{ +} + +void ESM4::MiscItem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + case ESM4::SUB_KSIZ: + case ESM4::SUB_KWDA: + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_VMAD: + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "MISC " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MISC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MiscItem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MiscItem::blank() +//{ +//} diff --git a/components/esm4/loadmisc.hpp b/components/esm4/loadmisc.hpp new file mode 100644 index 0000000000..94f09b1a23 --- /dev/null +++ b/components/esm4/loadmisc.hpp @@ -0,0 +1,77 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MISC_H +#define ESM4_MISC_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct MiscItem + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + std::string mMiniIcon; // inventory + + FormId mPickUpSound; + FormId mDropSound; + + float mBoundRadius; + FormId mScriptId; + + Data mData; + + MiscItem(); + virtual ~MiscItem(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MISC_H diff --git a/components/esm4/loadmset.cpp b/components/esm4/loadmset.cpp new file mode 100644 index 0000000000..b7859f9fa5 --- /dev/null +++ b/components/esm4/loadmset.cpp @@ -0,0 +1,115 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadmset.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MediaSet::MediaSet() : mFormId(0), mFlags(0), mSetType(-1), mEnabled(0), + mBoundaryDayOuter(0.f), mBoundaryDayMiddle(0.f), mBoundaryDayInner(0.f), + mBoundaryNightOuter(0.f), mBoundaryNightMiddle(0.f), mBoundaryNightInner(0.f), + mLevel8(0.f), mLevel9(0.f), mLevel0(0.f), mLevelA(0.f), mLevelB(0.f), mLevelC(0.f), + mTime1(0.f), mTime2(0.f), mTime3(0.f), mTime4(0.f), + mSoundIntro(0), mSoundOutro(0) +{ + mEditorId.clear(); + mFullName.clear(); + + mSet2.clear(); + mSet3.clear(); + mSet4.clear(); + mSet5.clear(); + mSet6.clear(); + mSet7.clear(); +} + +ESM4::MediaSet::~MediaSet() +{ +} + +void ESM4::MediaSet::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mFullName); break; + case ESM4::SUB_NAM1: reader.get(mSetType); break; + case ESM4::SUB_PNAM: reader.get(mEnabled); break; + case ESM4::SUB_NAM2: reader.getZString(mSet2); break; + case ESM4::SUB_NAM3: reader.getZString(mSet3); break; + case ESM4::SUB_NAM4: reader.getZString(mSet4); break; + case ESM4::SUB_NAM5: reader.getZString(mSet5); break; + case ESM4::SUB_NAM6: reader.getZString(mSet6); break; + case ESM4::SUB_NAM7: reader.getZString(mSet7); break; + case ESM4::SUB_HNAM: reader.getFormId(mSoundIntro); break; + case ESM4::SUB_INAM: reader.getFormId(mSoundOutro); break; + case ESM4::SUB_NAM8: reader.get(mLevel8); break; + case ESM4::SUB_NAM9: reader.get(mLevel9); break; + case ESM4::SUB_NAM0: reader.get(mLevel0); break; + case ESM4::SUB_ANAM: reader.get(mLevelA); break; + case ESM4::SUB_BNAM: reader.get(mLevelB); break; + case ESM4::SUB_CNAM: reader.get(mLevelC); break; + case ESM4::SUB_JNAM: reader.get(mBoundaryDayOuter); break; + case ESM4::SUB_KNAM: reader.get(mBoundaryDayMiddle); break; + case ESM4::SUB_LNAM: reader.get(mBoundaryDayInner); break; + case ESM4::SUB_MNAM: reader.get(mBoundaryNightOuter); break; + case ESM4::SUB_NNAM: reader.get(mBoundaryNightMiddle); break; + case ESM4::SUB_ONAM: reader.get(mBoundaryNightInner); break; + case ESM4::SUB_DNAM: reader.get(mTime1); break; + case ESM4::SUB_ENAM: reader.get(mTime2); break; + case ESM4::SUB_FNAM: reader.get(mTime3); break; + case ESM4::SUB_GNAM: reader.get(mTime4); break; + case ESM4::SUB_DATA: + { + //std::cout << "MSET " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MSET::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MediaSet::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MediaSet::blank() +//{ +//} diff --git a/components/esm4/loadmset.hpp b/components/esm4/loadmset.hpp new file mode 100644 index 0000000000..b13c4d6f64 --- /dev/null +++ b/components/esm4/loadmset.hpp @@ -0,0 +1,99 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MSET_H +#define ESM4_MSET_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct MediaSet + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + + // -1 none, 0 battle, 1 location, 2 dungeon, 3 incidental + // Battle - intro (HNAM), loop (NAM2), outro (INAM) + // Location - day outer (NAM2), day middle (NAM3), day inner (NAM4), + // night outer (NAM5), night middle (NAM6), night inner (NAM7) + // Dungeon - intro (HNAM), battle (NAM2), explore (NAM3), suspence (NAM4), outro (INAM) + // Incidental - daytime (HNAM), nighttime (INAM) + std::int32_t mSetType; + // 0x01 day outer, 0x02 day middle, 0x04 day inner + // 0x08 night outer, 0x10 night middle, 0x20 night inner + std::uint8_t mEnabled; // for location + + float mBoundaryDayOuter; // % + float mBoundaryDayMiddle; // % + float mBoundaryDayInner; // % + float mBoundaryNightOuter; // % + float mBoundaryNightMiddle; // % + float mBoundaryNightInner; // % + + // start at 2 to reduce confusion + std::string mSet2; // NAM2 + std::string mSet3; // NAM3 + std::string mSet4; // NAM4 + std::string mSet5; // NAM5 + std::string mSet6; // NAM6 + std::string mSet7; // NAM7 + + float mLevel8; // dB + float mLevel9; // dB + float mLevel0; // dB + float mLevelA; // dB + float mLevelB; // dB + float mLevelC; // dB + + float mTime1; + float mTime2; + float mTime3; + float mTime4; + + FormId mSoundIntro; // HNAM + FormId mSoundOutro; // INAM + + MediaSet(); + virtual ~MediaSet(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MSET_H diff --git a/components/esm4/loadmstt.cpp b/components/esm4/loadmstt.cpp new file mode 100644 index 0000000000..61d6147b2f --- /dev/null +++ b/components/esm4/loadmstt.cpp @@ -0,0 +1,87 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadmstt.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::MovableStatic::MovableStatic() : mFormId(0), mFlags(0), mData(0), mLoopingSound(0) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::MovableStatic::~MovableStatic() +{ +} + +void ESM4::MovableStatic::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SNAM: reader.get(mLoopingSound); break; + case ESM4::SUB_DEST: // destruction data + case ESM4::SUB_OBND: // object bounds + case ESM4::SUB_MODT: // model texture data + case ESM4::SUB_DMDL: + case ESM4::SUB_DMDT: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + case ESM4::SUB_MODS: + case ESM4::SUB_FULL: + case ESM4::SUB_MODB: + { + //std::cout << "MSTT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MSTT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::MovableStatic::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::MovableStatic::blank() +//{ +//} diff --git a/components/esm4/loadmstt.hpp b/components/esm4/loadmstt.hpp new file mode 100644 index 0000000000..77dc453d35 --- /dev/null +++ b/components/esm4/loadmstt.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_MSTT_H +#define ESM4_MSTT_H + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct MovableStatic + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + std::int8_t mData; + FormId mLoopingSound; + + MovableStatic(); + virtual ~MovableStatic(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MSTT_H diff --git a/components/esm4/loadmusc.cpp b/components/esm4/loadmusc.cpp new file mode 100644 index 0000000000..903bbb5a1f --- /dev/null +++ b/components/esm4/loadmusc.cpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadmusc.hpp" + +#include +//#include // FIXME: for debugging only + +//#include "formid.hpp" + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Music::Music() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mMusicFile.clear(); +} + +ESM4::Music::~Music() +{ +} + +void ESM4::Music::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FNAM: reader.getZString(mMusicFile); + //std::cout << "music: " << /*formIdToString(mFormId)*/mEditorId << " " << mMusicFile << std::endl; + 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 + { + //std::cout << "MUSC " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::MUSC::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Music::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Music::blank() +//{ +//} diff --git a/components/esm4/loadmusc.hpp b/components/esm4/loadmusc.hpp new file mode 100644 index 0000000000..ab3889a6c0 --- /dev/null +++ b/components/esm4/loadmusc.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_MUSC_H +#define ESM4_MUSC_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Music + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mMusicFile; + + Music(); + virtual ~Music(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_MUSC_H diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp new file mode 100644 index 0000000000..0655edb1e8 --- /dev/null +++ b/components/esm4/loadnavi.cpp @@ -0,0 +1,373 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadnavi.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include + +#include // FIXME: debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Navigation::Navigation() +{ + mEditorId.clear(); +} + +ESM4::Navigation::~Navigation() +{ +} + +void ESM4::Navigation::IslandInfo::load(ESM4::Reader& reader) +{ + reader.get(minX); + reader.get(minY); + reader.get(minZ); + reader.get(maxX); + reader.get(maxY); + reader.get(maxZ); + + std::uint32_t count; + reader.get(count); // countTriangle; + if (count) + { + triangles.resize(count); + //std::cout << "NVMI island triangles " << std::dec << count << std::endl; // FIXME + for (std::vector::iterator it = triangles.begin(); it != triangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countVertex; + if (count) + { + verticies.resize(count); + for (std::vector::iterator it = verticies.begin(); it != verticies.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NVMI vert " << std::dec << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; +#endif + } + } +} + +void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader) +{ + std::uint32_t count; + + reader.get(formId); + reader.get(flags); + reader.get(x); + reader.get(y); + reader.get(z); + +// FIXME: for debugging only +#if 0 + std::string padding = ""; + if (flags == ESM4::FLG_Modified) + padding.insert(0, 2, '-'); + else if (flags == ESM4::FLG_Unmodified) + padding.insert(0, 4, '.'); + + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NVMI formId: 0x" << std::hex << formId << std::endl; + std::cout << padding << "NVMI flags: " << std::hex << flags << std::endl; + std::cout << padding << "NVMI center: " << std::dec << x << ", " << y << ", " << z << std::endl; +#endif + + reader.get(flagPrefMerges); + + reader.get(count); // countMerged; + if (count) + { + //std::cout << "NVMI countMerged " << std::dec << count << std::endl; + formIdMerged.resize(count); + for (std::vector::iterator it = formIdMerged.begin(); it != formIdMerged.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countPrefMerged; + if (count) + { + //std::cout << "NVMI countPrefMerged " << std::dec << count << std::endl; + formIdPrefMerged.resize(count); + for (std::vector::iterator it = formIdPrefMerged.begin(); it != formIdPrefMerged.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // countLinkedDoors; + if (count) + { + //std::cout << "NVMI countLinkedDoors " << std::dec << count << std::endl; + linkedDoors.resize(count); + for (std::vector::iterator it = linkedDoors.begin(); it != linkedDoors.end(); ++it) + { + reader.get(*it); + } + } + + unsigned char island; + reader.get(island); + if (island) + { + Navigation::IslandInfo island2; + island2.load(reader); + islandInfo.push_back(island2); // Maybe don't use a vector for just one entry? + } + else if (flags == FLG_Island) // FIXME: debug only + std::cerr << "nvmi no island but has 0x20 flag" << std::endl; + + reader.get(locationMarker); + + reader.get(worldSpaceId); + //FLG_Tamriel = 0x0000003c, // grid info follows, possibly Tamriel? + //FLG_Morrowind = 0x01380000, // grid info follows, probably Skywind + if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000) + { + reader.get(cellGrid.grid.y); // NOTE: reverse order + reader.get(cellGrid.grid.x); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == ESM4::FLG_Morrowind) + std::cout << padding << "NVMI MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; + else + std::cout << padding << "NVMI SR: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; +#endif + } + else + { + reader.get(cellGrid.cellId); + +#if 0 + if (worldSpaceId == 0) // interior + std::cout << "NVMI Interior: cellId " << std::hex << cellGrid.cellId << std::endl; + else + std::cout << "NVMI FormID: cellId " << std::hex << cellGrid.cellId << std::endl; +#endif + } +} + +// NVPP data seems to be organised this way (total is 0x64 = 100) +// +// (0) total | 0x1 | formid (index 0) | count | formid's +// (1) | count | formid's +// (2) | count | formid's +// (3) | count | formid's +// (4) | count | formid's +// (5) | count | formid's +// (6) | count | formid's +// (7) | count | formid's +// (8) | count | formid's +// (9) | count | formid's +// (10) | 0x1 | formid (index 1) | count | formid's +// (11) | count | formid's +// (12) | count | formid's +// (13) | count | formid's +// (14) | count | formid's +// (15) | count | formid's +// ... +// +// (88) | count | formid's +// (89) | count | formid's +// +// Here the pattern changes (final count is 0xa = 10) +// +// (90) | 0x1 | formid (index 9) | count | formid | index +// (91) | formid | index +// (92) | formid | index +// (93) | formid | index +// (94) | formid | index +// (95) | formid | index +// (96) | formid | index +// (97) | formid | index +// (98) | formid | index +// (99) | formid | index +// +// Note that the index values are not sequential, i.e. the first index value +// (i.e. row 90) for Update.esm is 2. +// +// Also note that there's no list of formid's following the final node (index 9) +// +// The same 10 formids seem to be used for the indices, but not necessarily +// with the same index value (but only Update.esm differs?) +// +// formid cellid X Y Editor ID other formids in same X,Y S U D D +// -------- ------ --- --- --------------------------- ---------------------------- - - - - +// 00079bbf 9639 5 -4 WhiterunExterior17 00079bc3 0 6 0 0 +// 0010377b 8ed5 6 24 DawnstarWesternMineExterior 1 1 1 1 +// 000a3f44 9577 -22 2 RoriksteadEdge 2 9 2 2 +// 00100f4b 8ea2 26 25 WinterholdExterior01 00100f4a, 00100f49 3 3 3 3 +// 00103120 bc8e 42 -22 (near Riften) 4 2 4 4 +// 00105e9a 929d -18 24 SolitudeExterior03 5 0 5 5 +// 001030cb 7178 -40 1 SalviusFarmExterior01 (east of Markarth) 6 8 6 6 +// 00098776 980b 4 -19 HelgenExterior 000cce3d 7 5 7 7 +// 000e88cc 93de -9 14 (near Morthal) 0010519e, 0010519d, 000e88d2 8 7 8 8 +// 000b87df b51d 33 5 WindhelmAttackStart05 9 4 9 9 +// +void ESM4::Navigation::load(ESM4::Reader& reader) +{ + //mFormId = reader.hdr().record.id; + //mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: // seems to be unused? + { + if (!reader.getZString(mEditorId)) + throw std::runtime_error ("NAVI EDID data read error"); + break; + } + case ESM4::SUB_NVPP: + { + std::uint32_t total; + std::uint32_t count; + reader.get(total); + if (!total) + { + reader.get(count); // throw away + break; + } + + total -= 10; // HACK + std::uint32_t node; + for (std::uint32_t i = 0; i < total; ++i) + { + std::vector preferredPaths; + reader.get(count); + if (count == 1) + { + reader.get(node); + reader.get(count); + } + if (count) + { + preferredPaths.resize(count); + for (std::vector::iterator it = preferredPaths.begin(); + it != preferredPaths.end(); ++it) + { + reader.get(*it); + } + } + mPreferredPaths.push_back(std::make_pair(node, preferredPaths)); +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", count " << count << ", i " << std::dec << i << std::endl; +#endif + } + reader.get(count); + assert(count == 1 && "expected separator"); + + reader.get(node); // HACK + std::vector preferredPaths; + mPreferredPaths.push_back(std::make_pair(node, preferredPaths)); // empty +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", count " << 0 << std::endl; +#endif + + reader.get(count); // HACK + assert(count == 10 && "expected 0xa"); + std::uint32_t index; + for (std::uint32_t i = 0; i < count; ++i) + { + reader.get(node); + reader.get(index); +#if 0 + std::cout << "node " << std::hex << node // FIXME: debugging only + << ", index " << index << ", i " << std::dec << total+i << std::endl; +#endif + //std::pair::iterator, bool> res = + mPathIndexMap.insert(std::make_pair(node, index)); + // FIXME: this throws if more than one file is being loaded + //if (!res.second) + //throw std::runtime_error ("node already exists in the preferred path index map"); + } + break; + } + case ESM4::SUB_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 + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + { + reader.skipSubRecordData(); // FIXME: FO3/FONV have different form of NavMeshInfo + break; + } + + //std::cout << "\nNVMI start" << std::endl; + NavMeshInfo nvmi; + nvmi.load(reader); + mNavMeshInfo.push_back (nvmi); + break; + } + case ESM4::SUB_NVSI: // from Dawnguard onwards + case ESM4::SUB_NVCI: // FO3 + { + reader.skipSubRecordData(); // FIXME: + break; + } + default: + { + throw std::runtime_error("ESM4::NAVI::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + } +} + +//void ESM4::Navigation::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Navigation::blank() +//{ +//} diff --git a/components/esm4/loadnavi.hpp b/components/esm4/loadnavi.hpp new file mode 100644 index 0000000000..0db9650c4e --- /dev/null +++ b/components/esm4/loadnavi.hpp @@ -0,0 +1,117 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NAVI_H +#define ESM4_NAVI_H + +#include +#include +#include + +#include "common.hpp" // CellGrid, Vertex + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Navigation + { +#pragma pack(push,1) + struct DoorRef + { + std::uint32_t unknown; + FormId formId; + }; + + struct Triangle + { + std::uint16_t vertexIndex0; + std::uint16_t vertexIndex1; + std::uint16_t vertexIndex2; + }; +#pragma pack(pop) + + struct IslandInfo + { + float minX; + float minY; + float minZ; + float maxX; + float maxY; + float maxZ; + std::vector triangles; + std::vector verticies; + + void load(ESM4::Reader& reader); + }; + + enum Flags // NVMI island flags (not certain) + { + FLG_Island = 0x00000020, + FLG_Modified = 0x00000000, // not island + FLG_Unmodified = 0x00000040 // not island + }; + + struct NavMeshInfo + { + FormId formId; + std::uint32_t flags; + // center point of the navmesh + float x; + float y; + float z; + std::uint32_t flagPrefMerges; + std::vector formIdMerged; + std::vector formIdPrefMerged; + std::vector linkedDoors; + std::vector islandInfo; + std::uint32_t locationMarker; + FormId worldSpaceId; + CellGrid cellGrid; + + void load(ESM4::Reader& reader); + }; + + std::string mEditorId; + + std::vector mNavMeshInfo; + + std::vector > > mPreferredPaths; + + std::map mPathIndexMap; + + Navigation(); + virtual ~Navigation(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_NAVI_H diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp new file mode 100644 index 0000000000..1f3eebe434 --- /dev/null +++ b/components/esm4/loadnavm.cpp @@ -0,0 +1,270 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadnavm.hpp" + +#include +#include +#include + +#include // FIXME: debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::NavMesh::NavMesh() : mFormId(0), mFlags(0) +{ +} + +ESM4::NavMesh::~NavMesh() +{ +} + +void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader) +{ + //std::cout << "start: divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; + //"this 0x" << this << std::endl; // FIXME + + std::uint32_t count; + + reader.get(unknownNVER); + reader.get(unknownLCTN); + reader.get(worldSpaceId); + //FLG_Tamriel = 0x0000003c, // grid info follows, possibly Tamriel? + //FLG_Morrowind = 0x01380000, // grid info follows, probably Skywind + if (worldSpaceId == 0x0000003c || worldSpaceId == 0x01380000) + { + // ^ + // Y | X Y Index + // | 0,0 0 + // 1 |23 0,1 1 + // 0 |01 1,0 2 + // +--- 1,1 3 + // 01 -> + // X + // + // e.g. Dagonfel X:13,14,15,16 Y:43,44,45,46 (Morrowind X:7 Y:22) + // + // Skywind: -4,-3 -2,-1 0,1 2,3 4,5 6,7 + // Morrowind: -2 -1 0 1 2 3 + // + // Formula seems to be floor(Skywind coord / 2) + // + reader.get(cellGrid.grid.y); // NOTE: reverse order + reader.get(cellGrid.grid.x); +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == ESM4::FLG_Morrowind) + std::cout << padding << "NVNM MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; + else + std::cout << padding << "NVNM SR: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; +#endif + } + else + { + reader.get(cellGrid.cellId); + +#if 0 + std::string padding = ""; // FIXME + padding.insert(0, reader.stackSize()*2, ' '); + if (worldSpaceId == 0) // interior + std::cout << padding << "NVNM Interior: cellId " << std::hex << cellGrid.cellId << std::endl; + else + std::cout << padding << "NVNM FormID: cellId " << std::hex << cellGrid.cellId << std::endl; +#endif + } + + reader.get(count); // numVerticies + if (count) + { + verticies.resize(count); + for (std::vector::iterator it = verticies.begin(); it != verticies.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + //if (reader.hdr().record.id == 0x2004ecc) // FIXME + std::cout << "nvnm vert " << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; +#endif + } + } + + reader.get(count); // numTriangles; + if (count) + { + triangles.resize(count); + for (std::vector::iterator it = triangles.begin(); it != triangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // numExtConn; + if (count) + { + extConns.resize(count); + for (std::vector::iterator it = extConns.begin(); it != extConns.end(); ++it) + { + reader.get(*it); +// FIXME: debugging only +#if 0 + std::cout << "nvnm ext 0x" << std::hex << (*it).navMesh << std::endl; +#endif + } + } + + reader.get(count); // numDoorTriangles; + if (count) + { + doorTriangles.resize(count); + for (std::vector::iterator it = doorTriangles.begin(); it != doorTriangles.end(); ++it) + { + reader.get(*it); + } + } + + reader.get(count); // numCoverTriangles; + if (count) + { + coverTriangles.resize(count); + for (std::vector::iterator it = coverTriangles.begin(); it != coverTriangles.end(); ++it) + { + reader.get(*it); + } + } + + // abs((maxX - minX) / divisor) = Max X Distance + reader.get(divisor); // FIXME: latest over-writes old + + reader.get(maxXDist); // FIXME: update with formula + reader.get(maxYDist); + reader.get(minX); // FIXME: use std::min + reader.get(minY); + reader.get(minZ); + reader.get(maxX); + reader.get(maxY); + reader.get(maxZ); + + // FIXME: should check remaining size here + // there are divisor^2 segments, each segment is a vector of triangle indices + for (unsigned int i = 0; i < divisor*divisor; ++i) + { + reader.get(count); // NOTE: count may be zero + + std::vector indices; + indices.resize(count); + for (std::vector::iterator it = indices.begin(); it != indices.end(); ++it) + { + reader.get(*it); + } + triSegments.push_back(indices); + } + assert(triSegments.size() == divisor*divisor && "tiangle segments size is not the square of divisor"); +#if 0 + if (triSegments.size() != divisor*divisor) + std::cout << "divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; + "this 0x" << this << std::endl; +#endif +} + +void ESM4::NavMesh::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + mFlags = reader.hdr().record.flags; + + //std::cout << "NavMesh 0x" << std::hex << this << std::endl; // FIXME + std::uint32_t subSize = 0; // for XXXX sub record + +// FIXME: debugging only +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "NAVM flags 0x" << std::hex << reader.hdr().record.flags << std::endl; + std::cout << padding << "NAVM id 0x" << std::hex << reader.hdr().record.id << std::endl; +#endif + while (reader.getSubRecordHeader()) + { + switch (reader.subRecordHeader().typeId) + { + case ESM4::SUB_NVNM: + { + NVNMstruct nvnm; + nvnm.load(reader); + mData.push_back(nvnm); // FIXME try swap + break; + } + case ESM4::SUB_ONAM: + case ESM4::SUB_PNAM: + case ESM4::SUB_NNAM: + { + if (subSize) + { + reader.skipSubRecordData(subSize); // special post XXXX + reader.updateRecordRead(subSize); // WARNING: manual update + subSize = 0; + } + else + //const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + //std::cout << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + + break; + } + case ESM4::SUB_XXXX: + { + reader.get(subSize); + break; + } + 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 + { + reader.skipSubRecordData(); // FIXME: + break; + } + default: + throw std::runtime_error("ESM4::NAVM::load - Unknown subrecord " + + ESM::printName(reader.subRecordHeader().typeId)); + } + } + //std::cout << "num nvnm " << std::dec << mData.size() << std::endl; // FIXME +} + +//void ESM4::NavMesh::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::NavMesh::blank() +{ +} diff --git a/components/esm4/loadnavm.hpp b/components/esm4/loadnavm.hpp new file mode 100644 index 0000000000..f5fb9b5e20 --- /dev/null +++ b/components/esm4/loadnavm.hpp @@ -0,0 +1,112 @@ +/* + Copyright (C) 2015, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NAVM_H +#define ESM4_NAVM_H + +#include +#include + +#include "common.hpp" // CellGrid, Vertex + +namespace ESM4 +{ + class Reader; + class Writer; + + struct NavMesh + { +#pragma pack(push,1) + struct Triangle + { + std::uint16_t vertexIndex0; + std::uint16_t vertexIndex1; + std::uint16_t vertexIndex2; + std::uint16_t edge0; + std::uint16_t edge1; + std::uint16_t edge2; + std::uint16_t coverMarker; + std::uint16_t coverFlags; + }; + + struct ExtConnection + { + std::uint32_t unknown; + FormId navMesh; + std::uint16_t triangleIndex; + }; + + struct DoorTriangle + { + std::uint16_t triangleIndex; + std::uint32_t unknown; + FormId doorRef; + }; +#pragma pack(pop) + + struct NVNMstruct + { + std::uint32_t unknownNVER; + std::uint32_t unknownLCTN; + FormId worldSpaceId; + CellGrid cellGrid; + std::vector verticies; + std::vector triangles; + std::vector extConns; + std::vector doorTriangles; + std::vector coverTriangles; + std::uint32_t divisor; + float maxXDist; + float maxYDist; + float minX; + float minY; + float minZ; + float maxX; + float maxY; + float maxZ; + // there are divisor^2 segments, each segment is a vector of triangle indices + std::vector > triSegments; + + void load(ESM4::Reader& esm); + }; + + std::vector mData; // Up to 4 skywind cells in one Morrowind cell + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + NavMesh(); + virtual ~NavMesh(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; + +} + +#endif // ESM4_NAVM_H diff --git a/components/esm4/loadnote.cpp b/components/esm4/loadnote.cpp new file mode 100644 index 0000000000..ee897258b7 --- /dev/null +++ b/components/esm4/loadnote.cpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadnote.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Note::Note() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); +} + +ESM4::Note::~Note() +{ +} + +void ESM4::Note::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: + case ESM4::SUB_MODB: + case ESM4::SUB_ONAM: + case ESM4::SUB_SNAM: + case ESM4::SUB_TNAM: + case ESM4::SUB_XNAM: + case ESM4::SUB_OBND: + { + //std::cout << "NOTE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::NOTE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Note::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Note::blank() +//{ +//} diff --git a/components/esm4/loadnote.hpp b/components/esm4/loadnote.hpp new file mode 100644 index 0000000000..1ba9bfec24 --- /dev/null +++ b/components/esm4/loadnote.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NOTE_H +#define ESM4_NOTE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Note + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; + + Note(); + virtual ~Note(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_NOTE_H diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp new file mode 100644 index 0000000000..e32ee6e90d --- /dev/null +++ b/components/esm4/loadnpc.cpp @@ -0,0 +1,333 @@ +/* + Copyright (C) 2016-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadnpc.hpp" + +#include +#include +#include // getline +#include // NOTE: for testing only +#include // NOTE: for testing only +#include // NOTE: for testing only + +//#include +//#include +#include + +#include "formid.hpp" // NOTE: for testing only +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Npc::Npc() : mFormId(0), mFlags(0), mIsTES4(false), mIsFONV(false), mRace(0), mClass(0), mHair(0), + mEyes(0), mHairLength(0.f), mHairColourId(0), mDeathItem(0), + mScriptId(0), mCombatStyle(0), mSoundBase(0), mSound(0), mSoundChance(0), + mFootWeight(0.f), mBoundRadius(0.f), mBaseTemplate(0), mWornArmor(0), + mDefaultOutfit(0), mSleepOutfit(0), mDefaultPkg(0), mFgRace(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + + mHairColour.red = 0; + mHairColour.green = 0; + mHairColour.blue = 0; + mHairColour.custom = 0; + + std::memset(&mAIData, 0, sizeof(AIData)); + std::memset(&mData, 0, sizeof(Data)); + std::memset(&mBaseConfig, 0, sizeof(ActorBaseConfig)); + std::memset(&mFaction, 0, sizeof(ActorFaction)); +} + +ESM4::Npc::~Npc() +{ +} + +void ESM4::Npc::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + mIsTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + //mIsTES5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; // WARN: FO3 is also VER_094 + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; // not for TES5, see Race + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_CNTO: + { + static InventoryItem inv; // FIXME: use unique_ptr here? + reader.get(inv); + reader.adjustFormId(inv.item); + mInventory.push_back(inv); + break; + } + case ESM4::SUB_SPLO: + { + FormId id; + reader.getFormId(id); + mSpell.push_back(id); + break; + } + case ESM4::SUB_PKID: + { + FormId id; + reader.getFormId(id); + mAIPackages.push_back(id); + break; + } + case ESM4::SUB_SNAM: + { + reader.get(mFaction); + reader.adjustFormId(mFaction.faction); + break; + } + case ESM4::SUB_RNAM: reader.getFormId(mRace); break; + case ESM4::SUB_CNAM: reader.getFormId(mClass); break; + case ESM4::SUB_HNAM: reader.getFormId(mHair); break; // not for TES5 + case ESM4::SUB_ENAM: reader.getFormId(mEyes); break; + // + case ESM4::SUB_INAM: reader.getFormId(mDeathItem); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + // + case ESM4::SUB_AIDT: + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) + { + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + + reader.get(mAIData); // TES4 + break; + } + case ESM4::SUB_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: + { + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) + { + if (subHdr.dataSize != 0) // FIXME FO3 + reader.skipSubRecordData(); + break; // zero length + } + + reader.get(&mData, 33); // FIXME: check packing + break; + } + case ESM4::SUB_ZNAM: reader.getFormId(mCombatStyle); break; + case ESM4::SUB_CSCR: reader.getFormId(mSoundBase); break; + case ESM4::SUB_CSDI: reader.getFormId(mSound); break; + case ESM4::SUB_CSDC: reader.get(mSoundChance); break; + case ESM4::SUB_WNAM: + { + if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + reader.get(mWornArmor); + else + reader.get(mFootWeight); + break; + } + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_KFFZ: + { + std::string str; + if (!reader.getZString(str)) + throw std::runtime_error ("NPC_ KFFZ data read error"); + + // Seems to be only below 3, and only happens 3 times while loading TES4: + // Forward_SheogorathWithCane.kf + // TurnLeft_SheogorathWithCane.kf + // TurnRight_SheogorathWithCane.kf + std::stringstream ss(str); + std::string file; + while (std::getline(ss, file, '\0')) // split the strings + mKf.push_back(file); + + break; + } + case ESM4::SUB_LNAM: reader.get(mHairLength); break; + case ESM4::SUB_HCLR: + { + reader.get(mHairColour.red); + reader.get(mHairColour.green); + reader.get(mHairColour.blue); + reader.get(mHairColour.custom); + + break; + } + case ESM4::SUB_TPLT: reader.get(mBaseTemplate); break; + case ESM4::SUB_FGGS: + { + mSymShapeModeCoefficients.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymShapeModeCoefficients.at(i)); + + break; + } + case ESM4::SUB_FGGA: + { + mAsymShapeModeCoefficients.resize(30); + for (std::size_t i = 0; i < 30; ++i) + reader.get(mAsymShapeModeCoefficients.at(i)); + + break; + } + case ESM4::SUB_FGTS: + { + mSymTextureModeCoefficients.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymTextureModeCoefficients.at(i)); + + break; + } + case ESM4::SUB_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 + { + FormId headPart; + reader.getFormId(headPart); + mHeadParts.push_back(headPart); + + break; + } + case ESM4::SUB_HCLF: // TES5 hair colour + { + reader.getFormId(mHairColourId); + + break; + } + case ESM4::SUB_COCT: // TES5 + { + std::uint32_t count; + reader.get(count); + + break; + } + case ESM4::SUB_DOFT: reader.getFormId(mDefaultOutfit); break; + case ESM4::SUB_SOFT: reader.getFormId(mSleepOutfit); break; + case ESM4::SUB_DPLT: reader.getFormId(mDefaultPkg); break; // AI package list + case ESM4::SUB_DEST: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: + { +#if 1 + boost::scoped_array dataBuf(new unsigned char[subHdr.dataSize]); + reader.get(&dataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; + for (std::size_t i = 0; i < subHdr.dataSize; ++i) + { + if (dataBuf[i] > 64 && dataBuf[i] < 91) // looks like printable ascii char + ss << (char)(dataBuf[i]) << " "; + else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(dataBuf[i]); + if ((i & 0x000f) == 0xf) // wrap around + ss << "\n"; + else if (i < (size_t)(subHdr.dataSize-1)) // quiesce gcc + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + //std::cout << "NPC_ " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); +#endif + break; + } + 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 + { + //std::cout << "NPC_ " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::NPC_::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Npc::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Npc::blank() +//{ +//} diff --git a/components/esm4/loadnpc.hpp b/components/esm4/loadnpc.hpp new file mode 100644 index 0000000000..d30ca5f14f --- /dev/null +++ b/components/esm4/loadnpc.hpp @@ -0,0 +1,230 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_NPC__H +#define ESM4_NPC__H + +#include +#include +#include + +#include "actor.hpp" +#include "inventory.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Npc + { + enum ACBS_TES4 + { + TES4_Female = 0x000001, + TES4_Essential = 0x000002, + TES4_Respawn = 0x000008, + TES4_AutoCalcStats = 0x000010, + TES4_PCLevelOffset = 0x000080, + TES4_NoLowLevelProc = 0x000200, + TES4_NoRumors = 0x002000, + TES4_Summonable = 0x004000, + TES4_NoPersuasion = 0x008000, // different meaning to crea + TES4_CanCorpseCheck = 0x100000 // opposite of crea + }; + + enum ACBS_FO3 + { + FO3_Female = 0x00000001, + FO3_Essential = 0x00000002, + FO3_PresetFace = 0x00000004, // Is CharGen Face Preset + FO3_Respawn = 0x00000008, + FO3_AutoCalcStats = 0x00000010, + FO3_PCLevelMult = 0x00000080, + FO3_UseTemplate = 0x00000100, + FO3_NoLowLevelProc = 0x00000200, + FO3_NoBloodSpray = 0x00000800, + FO3_NoBloodDecal = 0x00001000, + FO3_NoVATSMelee = 0x00100000, + FO3_AnyRace = 0x00400000, + FO3_AutoCalcServ = 0x00800000, + FO3_NoKnockdown = 0x04000000, + FO3_NotPushable = 0x08000000, + FO3_NoRotateHead = 0x40000000 + }; + + enum ACBS_TES5 + { + TES5_Female = 0x00000001, + TES5_Essential = 0x00000002, + TES5_PresetFace = 0x00000004, // Is CharGen Face Preset + TES5_Respawn = 0x00000008, + TES5_AutoCalcStats = 0x00000010, + TES5_Unique = 0x00000020, + TES5_NoStealth = 0x00000040, // Doesn't affect stealth meter + TES5_PCLevelMult = 0x00000080, + //TES5_Unknown = 0x00000100, // Audio template? + TES5_Protected = 0x00000800, + TES5_Summonable = 0x00004000, + TES5_NoBleeding = 0x00010000, + TES5_Owned = 0x00040000, // owned/follow? (Horses, Atronachs, NOT Shadowmere) + TES5_GenderAnim = 0x00080000, // Opposite Gender Anims + TES5_SimpleActor = 0x00100000, + TES5_LoopedScript = 0x00200000, // AAvenicci, Arcadia, Silvia, Afflicted, TortureVictims + TES5_LoopedAudio = 0x10000000, // AAvenicci, Arcadia, Silvia, DA02 Cultists, Afflicted, TortureVictims + TES5_IsGhost = 0x20000000, // Ghost/non-interactable (Ghosts, Nocturnal) + TES5_Invulnerable = 0x80000000 + }; + + enum Template_Flags + { + TES5_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, + // voice type, death item; Sounds tab; Animation tab; Character Gen tabs + TES5_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, + // speed, bleedout, class + TES5_UseFactions = 0x0004, // both factions and assigned crime faction + TES5_UseSpellList = 0x0008, // both spells and perks + TES5_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and + // gift filter + TES5_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; + // rest of tab controlled by Def Pack List + TES5_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, + // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter + TES5_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item + // -- but not death item + TES5_UseScript = 0x0200, + TES5_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) + TES5_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, + // events, and data) + TES5_UseKeywords = 0x1000 + }; + +#pragma pack(push, 1) + struct SkillValues + { + std::uint8_t armorer; + std::uint8_t athletics; + std::uint8_t blade; + std::uint8_t block; + std::uint8_t blunt; + std::uint8_t handToHand; + std::uint8_t heavyArmor; + std::uint8_t alchemy; + std::uint8_t alteration; + std::uint8_t conjuration; + std::uint8_t destruction; + std::uint8_t illusion; + std::uint8_t mysticism; + std::uint8_t restoration; + std::uint8_t acrobatics; + std::uint8_t lightArmor; + std::uint8_t marksman; + std::uint8_t mercantile; + std::uint8_t security; + std::uint8_t sneak; + std::uint8_t speechcraft; + }; + + struct HairColour + { + std::uint8_t red; + std::uint8_t green; + std::uint8_t blue; + std::uint8_t custom; // alpha? + }; + + struct Data + { + SkillValues skills; + std::uint32_t health; + AttributeValues attribs; + }; + +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + bool mIsTES4; + bool mIsFONV; + + std::string mEditorId; + std::string mFullName; + std::string mModel; // skeleton model (can be a marker in FO3/FONV) + + FormId mRace; + FormId mClass; + FormId mHair; // not for TES5, see mHeadParts + FormId mEyes; + + std::vector mHeadParts; // FO3/FONV/TES5 + + float mHairLength; + HairColour mHairColour; // TES4/FO3/FONV + FormId mHairColourId; // TES5 + + FormId mDeathItem; + std::vector mSpell; + FormId mScriptId; + + AIData mAIData; + std::vector mAIPackages; // seems to be in priority order, 0 = highest priority + ActorBaseConfig mBaseConfig; // union + ActorFaction mFaction; + Data mData; + FormId mCombatStyle; + FormId mSoundBase; + FormId mSound; + std::uint8_t mSoundChance; + float mFootWeight; + + float mBoundRadius; + std::vector mKf; // filenames only, get directory path from mModel + + std::vector mInventory; + + FormId mBaseTemplate; // FO3/FONV/TES5 + FormId mWornArmor; // TES5 only? + + FormId mDefaultOutfit; // TES5 OTFT + FormId mSleepOutfit; // TES5 OTFT + FormId mDefaultPkg; + + std::vector mSymShapeModeCoefficients; // size 0 or 50 + std::vector mAsymShapeModeCoefficients; // size 0 or 30 + std::vector mSymTextureModeCoefficients; // size 0 or 50 + std::int16_t mFgRace; + + Npc(); + virtual ~Npc(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_NPC__H diff --git a/components/esm4/loadotft.cpp b/components/esm4/loadotft.cpp new file mode 100644 index 0000000000..dec2a05666 --- /dev/null +++ b/components/esm4/loadotft.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadotft.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Outfit::Outfit() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::Outfit::~Outfit() +{ +} + +void ESM4::Outfit::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_INAM: + { + std::size_t numObj = subHdr.dataSize / sizeof(FormId); + for (std::size_t i = 0; i < numObj; ++i) + { + FormId formId; + reader.getFormId(formId); + + mInventory.push_back(formId); + } + + break; + } + default: + //std::cout << "OTFT " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + throw std::runtime_error("ESM4::OTFT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Outfit::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Outfit::blank() +//{ +//} diff --git a/components/esm4/loadotft.hpp b/components/esm4/loadotft.hpp new file mode 100644 index 0000000000..3b1db34d0e --- /dev/null +++ b/components/esm4/loadotft.hpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_OTFT_H +#define ESM4_OTFT_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Outfit + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::vector mInventory; + + Outfit(); + virtual ~Outfit(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_OTFT_H diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp new file mode 100644 index 0000000000..241147df00 --- /dev/null +++ b/components/esm4/loadpack.cpp @@ -0,0 +1,200 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadpack.hpp" + +#include +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::AIPackage::AIPackage() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + std::memset(&mData, 0, sizeof(PKDT)); + std::memset(&mSchedule, 0, sizeof(PSDT)); + std::memset(&mLocation, 0, sizeof(PLDT)); + mLocation.type = 0xff; // default to indicate no location data + std::memset(&mTarget, 0, sizeof(PTDT)); + mTarget.type = 0xff; // default to indicate no target data + + mConditions.clear(); +} + +ESM4::AIPackage::~AIPackage() +{ +} + +void ESM4::AIPackage::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_PKDT: + { + if (subHdr.dataSize != sizeof(PKDT) && subHdr.dataSize == 4) + { + //std::cout << "skip fallout" << mEditorId << std::endl; // FIXME + reader.get(mData.flags); + mData.type = 0; // FIXME + } + else if (subHdr.dataSize != sizeof(mData)) + reader.skipSubRecordData(); // FIXME: FO3 + else + reader.get(mData); + + break; + } + case ESM4::SUB_PSDT: //reader.get(mSchedule); break; + { + if (subHdr.dataSize != sizeof(mSchedule)) + reader.skipSubRecordData(); // FIXME: + else + reader.get(mSchedule); // TES4 + + break; + } + case ESM4::SUB_PLDT: + { + if (subHdr.dataSize != sizeof(mLocation)) + reader.skipSubRecordData(); // FIXME: + else + { + reader.get(mLocation); // TES4 + if (mLocation.type != 5) + reader.adjustFormId(mLocation.location); + } + + break; + } + case ESM4::SUB_PTDT: + { + if (subHdr.dataSize != sizeof(mTarget)) + reader.skipSubRecordData(); // FIXME: FO3 + else + { + reader.get(mTarget); // TES4 + if (mLocation.type != 2) + reader.adjustFormId(mTarget.target); + } + + break; + } + case ESM4::SUB_CTDA: + { + if (subHdr.dataSize != sizeof(CTDA)) + { + reader.skipSubRecordData(); // FIXME: FO3 + break; + } + + static CTDA condition; + reader.get(condition); + // FIXME: how to "unadjust" if not FormId? + //adjustFormId(condition.param1); + //adjustFormId(condition.param2); + mConditions.push_back(condition); + + 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 + { + //std::cout << "PACK " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::PACK::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::AIPackage::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::AIPackage::blank() +//{ +//} diff --git a/components/esm4/loadpack.hpp b/components/esm4/loadpack.hpp new file mode 100644 index 0000000000..93a9a8290c --- /dev/null +++ b/components/esm4/loadpack.hpp @@ -0,0 +1,110 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_PACK_H +#define ESM4_PACK_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct AIPackage + { +#pragma pack(push, 1) + struct PKDT // data + { + std::uint32_t flags; + std::int32_t type; + }; + + struct PSDT // schedule + { + std::uint8_t month; // Any = 0xff + std::uint8_t dayOfWeek; // Any = 0xff + std::uint8_t date; // Any = 0 + std::uint8_t time; // Any = 0xff + std::uint32_t duration; + }; + + struct PLDT // location + { + std::int32_t type; // 0 = near ref, 1 = in cell, 2 = current loc, 3 = editor loc, 4 = obj id, 5 = obj type + FormId location; // uint32_t if type = 5 + std::int32_t radius; + }; + + struct PTDT // target + { + std::int32_t type; // 0 = specific ref, 1 = obj id, 2 = obj type + FormId target; // uint32_t if type = 2 + std::int32_t distance; + }; + + // NOTE: param1/param2 can be FormId or number, but assume FormId so that adjustFormId + // can be called + struct CTDA + { + std::uint8_t condition; + std::uint8_t unknown1; // probably padding + std::uint8_t unknown2; // probably padding + std::uint8_t unknown3; // probably padding + float compValue; + std::int32_t fnIndex; + FormId param1; + FormId param2; + std::uint32_t unknown4; // probably padding + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + PKDT mData; + PSDT mSchedule; + PLDT mLocation; + PTDT mTarget; + std::vector mConditions; + + AIPackage(); + virtual ~AIPackage(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_PACK_H diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp new file mode 100644 index 0000000000..a0bfc9b119 --- /dev/null +++ b/components/esm4/loadpgrd.cpp @@ -0,0 +1,177 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadpgrd.hpp" + +#include +//#include // FIXME: for debugging only +//#include // FIXME: for debugging only + +//#include // FIXME for debugging only + +#include "formid.hpp" // FIXME: for mEditorId workaround +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Pathgrid::Pathgrid() : mFormId(0), mFlags(0), mData(0) +{ + mEditorId.clear(); + + mNodes.clear(); + mLinks.clear(); + mForeign.clear(); + mObjects.clear(); +} + +ESM4::Pathgrid::~Pathgrid() +{ +} + +void ESM4::Pathgrid::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_PGRP: + { + std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); + if (numNodes != std::size_t(mData)) // keep gcc quiet + throw std::runtime_error("ESM4::PGRD::load numNodes mismatch"); + + mNodes.resize(numNodes); + for (std::size_t i = 0; i < numNodes; ++i) + { + reader.get(mNodes.at(i)); + + if (int(mNodes.at(i).z) % 2 == 0) + mNodes.at(i).priority = 0; + else + mNodes.at(i).priority = 1; + } + + break; + } + case ESM4::SUB_PGRR: + { + static PGRR link; + + for (std::size_t i = 0; i < std::size_t(mData); ++i) // keep gcc quiet + { + for (std::size_t j = 0; j < mNodes[i].numLinks; ++j) + { + link.startNode = std::int16_t(i); + + reader.get(link.endNode); + if (link.endNode == -1) + continue; + + // ICMarketDistrictTheBestDefenseBasement doesn't have a PGRR sub-record + // CELL formId 00049E2A + // PGRD formId 000304B7 + //if (mFormId == 0x0001C2C8) + //std::cout << link.startNode << "," << link.endNode << std::endl; + mLinks.push_back(link); + } + } + + break; + } + case ESM4::SUB_PGRI: + { + std::size_t numForeign = subHdr.dataSize / sizeof(PGRI); + mForeign.resize(numForeign); + for (std::size_t i = 0; i < numForeign; ++i) + { + reader.get(mForeign.at(i)); + mForeign.at(i).localNode;// &= 0xffff; // some have junk high bits (maybe flags?) + } + + break; + } + case ESM4::SUB_PGRL: + { + static PGRL objLink; + reader.get(objLink.object); + // object linkedNode + std::size_t numNodes = (subHdr.dataSize - sizeof(int32_t)) / sizeof(int32_t); + + objLink.linkedNodes.resize(numNodes); + for (std::size_t i = 0; i < numNodes; ++i) + reader.get(objLink.linkedNodes.at(i)); + + mObjects.push_back(objLink); + + break; + } + case ESM4::SUB_PGAG: + { +#if 0 + boost::scoped_array mDataBuf(new unsigned char[subHdr.dataSize]); + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; + for (std::size_t i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) // looks like printable ascii char + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) // wrap around + ss << "\n"; + else if (i < subHdr.dataSize-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + //std::cout << "PGRD " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); +#endif + break; + } + default: + throw std::runtime_error("ESM4::PGRD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Pathgrid::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Pathgrid::blank() +//{ +//} diff --git a/components/esm4/loadpgrd.hpp b/components/esm4/loadpgrd.hpp new file mode 100644 index 0000000000..9f522af5ca --- /dev/null +++ b/components/esm4/loadpgrd.hpp @@ -0,0 +1,96 @@ +/* + Copyright (C) 2020 - 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_PGRD_H +#define ESM4_PGRD_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Pathgrid + { +#pragma pack(push, 1) + struct PGRP + { + float x; + float y; + float z; + std::uint8_t numLinks; + std::uint8_t priority; // probably padding, repurposing + std::uint16_t unknown; // probably padding + }; + + struct PGRR + { + std::int16_t startNode; + std::int16_t endNode; + }; + + struct PGRI + { + std::int32_t localNode; + float x; // foreign + float y; // foreign + float z; // foreign + }; +#pragma pack(pop) + + struct PGRL + { + FormId object; + std::vector linkedNodes; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; // FIXME: no such record for PGRD, but keep here to avoid extra work for now + + std::int16_t mData; // number of nodes + std::vector mNodes; + std::vector mLinks; + std::vector mForeign; + std::vector mObjects; + + Pathgrid(); + virtual ~Pathgrid(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_PGRD_H diff --git a/components/esm4/loadpgre.cpp b/components/esm4/loadpgre.cpp new file mode 100644 index 0000000000..9c3e745ab9 --- /dev/null +++ b/components/esm4/loadpgre.cpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadpgre.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::PlacedGrenade::PlacedGrenade() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::PlacedGrenade::~PlacedGrenade() +{ +} + +void ESM4::PlacedGrenade::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_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_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: + { + //std::cout << "PGRE " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "PGRE " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::PGRE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::PlacedGrenade::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::PlacedGrenade::blank() +//{ +//} diff --git a/components/esm4/loadpgre.hpp b/components/esm4/loadpgre.hpp new file mode 100644 index 0000000000..bb36acfc37 --- /dev/null +++ b/components/esm4/loadpgre.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_PGRE_H +#define ESM4_PGRE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct PlacedGrenade + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + PlacedGrenade(); + virtual ~PlacedGrenade(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_PGRE_H diff --git a/components/esm4/loadpwat.cpp b/components/esm4/loadpwat.cpp new file mode 100644 index 0000000000..a88adcf4c2 --- /dev/null +++ b/components/esm4/loadpwat.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadpwat.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::PlaceableWater::PlaceableWater() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::PlaceableWater::~PlaceableWater() +{ +} + +void ESM4::PlaceableWater::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_OBND: + case ESM4::SUB_MODL: + case ESM4::SUB_DNAM: + { + //std::cout << "PWAT " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "PWAT " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::PWAT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::PlaceableWater::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::PlaceableWater::blank() +//{ +//} diff --git a/components/esm4/loadpwat.hpp b/components/esm4/loadpwat.hpp new file mode 100644 index 0000000000..e1de91ab96 --- /dev/null +++ b/components/esm4/loadpwat.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_PWAT_H +#define ESM4_PWAT_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct PlaceableWater + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + PlaceableWater(); + virtual ~PlaceableWater(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_PWAT_H diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp new file mode 100644 index 0000000000..67e250ae76 --- /dev/null +++ b/components/esm4/loadqust.cpp @@ -0,0 +1,179 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadqust.hpp" + +#include +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Quest::Quest() : mFormId(0), mFlags(0), mQuestScript(0) +{ + mEditorId.clear(); + mQuestName.clear(); + mFileName.clear(); + + std::memset(&mScript.scriptHeader, 0, sizeof(ScriptHeader)); + mScript.scriptSource.clear(); + mScript.globReference = 0; +} + +ESM4::Quest::~Quest() +{ +} + +void ESM4::Quest::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getZString(mQuestName); break; + case ESM4::SUB_ICON: reader.getZString(mFileName); break; // TES4 (none in FO3/FONV) + case ESM4::SUB_DATA: + { + if (subHdr.dataSize == 2) // TES4 + { + reader.get(&mData, 2); + mData.questDelay = 0.f; // unused in TES4 but keep it clean + + //if ((mData.flags & Flag_StartGameEnabled) != 0) + //std::cout << "start quest " << mEditorId << std::endl; + } + else + reader.get(mData); // FO3 + + break; + } + case ESM4::SUB_SCRI: reader.get(mQuestScript); break; + case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + { + TargetCondition cond; + + if (subHdr.dataSize == 24) // TES4 + { + reader.get(&cond, 24); + cond.reference = 0; // unused in TES4 but keep it clean + } + else if (subHdr.dataSize == 28) + { + reader.get(cond); // FO3/FONV + if (cond.reference) + reader.adjustFormId(cond.reference); + } + else + { + // one record with size 20: EDID GenericSupMutBehemoth + reader.skipSubRecordData(); // FIXME + } + // FIXME: support TES5 + + mTargetConditions.push_back(cond); + + break; + } + case ESM4::SUB_SCHR: reader.get(mScript.scriptHeader); break; + case ESM4::SUB_SCDA: reader.skipSubRecordData(); break; // compiled script data + case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); break; + case ESM4::SUB_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_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 + { + //std::cout << "QUST " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::QUST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //if (mEditorId == "DAConversations") + //std::cout << mEditorId << std::endl; +} + +//void ESM4::Quest::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Quest::blank() +//{ +//} diff --git a/components/esm4/loadqust.hpp b/components/esm4/loadqust.hpp new file mode 100644 index 0000000000..905a5cf0fa --- /dev/null +++ b/components/esm4/loadqust.hpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_QUST_H +#define ESM4_QUST_H + +#include + +#include "formid.hpp" +#include "script.hpp" // TargetCondition, ScriptDefinition + +namespace ESM4 +{ + class Reader; + class Writer; + +#pragma pack(push, 1) + struct QuestData + { + std::uint8_t flags; // Quest_Flags + std::uint8_t priority; + std::uint16_t padding; // FO3 + float questDelay; // FO3 + }; +#pragma pack(pop) + + struct Quest + { + // NOTE: these values are for TES4 + enum Quest_Flags + { + Flag_StartGameEnabled = 0x01, + Flag_AllowRepeatConvTopic = 0x04, + Flag_AllowRepeatStages = 0x08 + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mQuestName; + std::string mFileName; // texture file + FormId mQuestScript; + + QuestData mData; + + std::vector mTargetConditions; + + ScriptDefinition mScript; + + Quest(); + virtual ~Quest(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_QUST_H diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp new file mode 100644 index 0000000000..79f635bd48 --- /dev/null +++ b/components/esm4/loadrace.cpp @@ -0,0 +1,715 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadrace.hpp" + +#include +#include +#include // FIXME: for debugging only +#include // FIXME: for debugging only + +#include "formid.hpp" +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Race::Race() : mFormId(0), mFlags(0), mIsTES5(false) + , mHeightMale(1.f), mHeightFemale(1.f), mWeightMale(1.f), mWeightFemale(1.f) + , mRaceFlags(0), mFaceGenMainClamp(0.f), mFaceGenFaceClamp(0.f), mNumKeywords(0) + , mSkin(0) +{ + mEditorId.clear(); + mFullName.clear(); + mDesc.clear(); + mModelMale.clear(); + mModelFemale.clear(); + + std::memset(&mAttribMale, 0, sizeof(AttributeValues)); + std::memset(&mAttribFemale, 0, sizeof(AttributeValues)); + + mVNAM.resize(2); + mDefaultHair.resize(2); + + mBodyTemplate.bodyPart = 0; + mBodyTemplate.flags = 0; + mBodyTemplate.type = 0; +} + +ESM4::Race::~Race() +{ +} + +void ESM4::Race::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + bool isTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + bool isFO3 = false; + + bool isMale = false; + int curr_part = -1; // 0 = head, 1 = body, 2 = egt, 3 = hkx + std::uint32_t currentIndex = 0xffffffff; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + //std::cout << "RACE " << ESM::printName(subHdr.typeId) << std::endl; + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: + { + reader.getZString(mEditorId); + // TES4 + // Sheogorath 0x0005308E + // GoldenSaint 0x0001208F + // DarkSeducer 0x0001208E + // VampireRace 0x00000019 + // Dremora 0x00038010 + // Argonian 0x00023FE9 + // Nord 0x000224FD + // Breton 0x000224FC + // WoodElf 0x000223C8 + // Khajiit 0x000223C7 + // DarkElf 0x000191C1 + // Orc 0x000191C0 + // HighElf 0x00019204 + // Redguard 0x00000D43 + // Imperial 0x00000907 + break; + } + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: + { + if (subHdr.dataSize == 1) // FO3? + { + reader.skipSubRecordData(); + break; + } + + reader.getLocalizedString(mDesc); break; + } + case ESM4::SUB_SPLO: // bonus spell formid (TES5 may have SPCT and multiple SPLO) + { + FormId magic; + reader.getFormId(magic); + mBonusSpells.push_back(magic); +// std::cout << "RACE " << printName(subHdr.typeId) << " " << formIdToString(magic) << std::endl; + + break; + } + case ESM4::SUB_DATA: // ?? different length for TES5 + { +// DATA:size 128 +// 0f 0f ff 00 ff 00 ff 00 ff 00 ff 00 ff 00 00 00 +// 9a 99 99 3f 00 00 80 3f 00 00 80 3f 00 00 80 3f +// 48 89 10 00 00 00 40 41 00 00 00 00 00 00 48 43 +// 00 00 48 43 00 00 80 3f 9a 99 19 3f 00 00 00 40 +// 01 00 00 00 ff ff ff ff ff ff ff ff 00 00 00 00 +// ff ff ff ff 00 00 00 00 00 00 20 41 00 00 a0 40 +// 00 00 a0 40 00 00 80 42 ff ff ff ff 00 00 00 00 +// 00 00 00 00 9a 99 99 3e 00 00 a0 40 02 00 00 00 +#if 0 + unsigned char mDataBuf[256/*bufSize*/]; + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + ss << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n"; + for (unsigned int i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < 256/*bufSize*/-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + if (subHdr.dataSize == 36) // TES4/FO3/FONV + { + if (!isTES4 && !isFONV && !mIsTES5) + isFO3 = true; + + std::uint8_t skill; + std::uint8_t bonus; + for (unsigned int i = 0; i < 8; ++i) + { + reader.get(skill); + reader.get(bonus); + mSkillBonus[static_cast(skill)] = bonus; + } + reader.get(mHeightMale); + reader.get(mHeightFemale); + reader.get(mWeightMale); + reader.get(mWeightFemale); + reader.get(mRaceFlags); + } + else if (subHdr.dataSize >= 128 && subHdr.dataSize <= 164) // TES5 + { + mIsTES5 = true; + + std::uint8_t skill; + std::uint8_t bonus; + for (unsigned int i = 0; i < 7; ++i) + { + reader.get(skill); + reader.get(bonus); + mSkillBonus[static_cast(skill)] = bonus; + } + std::uint16_t unknown; + reader.get(unknown); + + reader.get(mHeightMale); + reader.get(mHeightFemale); + reader.get(mWeightMale); + reader.get(mWeightFemale); + reader.get(mRaceFlags); + + // FIXME + float dummy; + reader.get(dummy); // starting health + reader.get(dummy); // starting magicka + reader.get(dummy); // starting stamina + reader.get(dummy); // base carry weight + reader.get(dummy); // base mass + reader.get(dummy); // accleration rate + reader.get(dummy); // decleration rate + + uint32_t dummy2; + reader.get(dummy2); // size + reader.get(dummy2); // head biped object + reader.get(dummy2); // hair biped object + reader.get(dummy); // injured health % (0.f..1.f) + reader.get(dummy2); // shield biped object + reader.get(dummy); // health regen + reader.get(dummy); // magicka regen + reader.get(dummy); // stamina regen + reader.get(dummy); // unarmed damage + reader.get(dummy); // unarmed reach + reader.get(dummy2); // body biped object + reader.get(dummy); // aim angle tolerance + reader.get(dummy2); // unknown + reader.get(dummy); // angular accleration rate + reader.get(dummy); // angular tolerance + reader.get(dummy2); // flags + + if (subHdr.dataSize > 128) + { + reader.get(dummy2); // unknown 1 + reader.get(dummy2); // unknown 2 + reader.get(dummy2); // unknown 3 + reader.get(dummy2); // unknown 4 + reader.get(dummy2); // unknown 5 + reader.get(dummy2); // unknown 6 + reader.get(dummy2); // unknown 7 + reader.get(dummy2); // unknown 8 + reader.get(dummy2); // unknown 9 + } + } + else + { + reader.skipSubRecordData(); + std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + } +#endif + break; + } + case ESM4::SUB_DNAM: + { + reader.get(mDefaultHair[0]); // male + reader.get(mDefaultHair[1]); // female + + break; + } + case ESM4::SUB_CNAM: // Only in TES4? + // CNAM SNAM VNAM + // Sheogorath 0x0 0000 98 2b 10011000 00101011 + // Golden Saint 0x3 0011 26 46 00100110 01000110 + // Dark Seducer 0xC 1100 df 55 11011111 01010101 + // Vampire Race 0x0 0000 77 44 01110111 10001000 + // Dremora 0x7 0111 bf 32 10111111 00110010 + // Argonian 0x0 0000 dc 3c 11011100 00111100 + // Nord 0x5 0101 b6 03 10110110 00000011 + // Breton 0x5 0101 48 1d 01001000 00011101 00000000 00000907 (Imperial) + // Wood Elf 0xD 1101 2e 4a 00101110 01001010 00019204 00019204 (HighElf) + // khajiit 0x5 0101 54 5b 01010100 01011011 00023FE9 00023FE9 (Argonian) + // Dark Elf 0x0 0000 72 54 01110010 01010100 00019204 00019204 (HighElf) + // Orc 0xC 1100 74 09 01110100 00001001 000224FD 000224FD (Nord) + // High Elf 0xF 1111 e6 21 11100110 00100001 + // Redguard 0xD 1101 a9 61 10101001 01100001 + // Imperial 0xD 1101 8e 35 10001110 00110101 + { + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_PNAM: reader.get(mFaceGenMainClamp); break; // 0x40A00000 = 5.f + case ESM4::SUB_UNAM: reader.get(mFaceGenFaceClamp); break; // 0x40400000 = 3.f + case ESM4::SUB_ATTR: // Only in TES4? + { + if (subHdr.dataSize == 2) // FO3? + { + reader.skipSubRecordData(); + break; + } + reader.get(mAttribMale.strength); + reader.get(mAttribMale.intelligence); + reader.get(mAttribMale.willpower); + reader.get(mAttribMale.agility); + reader.get(mAttribMale.speed); + reader.get(mAttribMale.endurance); + reader.get(mAttribMale.personality); + reader.get(mAttribMale.luck); + reader.get(mAttribFemale.strength); + reader.get(mAttribFemale.intelligence); + reader.get(mAttribFemale.willpower); + reader.get(mAttribFemale.agility); + reader.get(mAttribFemale.speed); + reader.get(mAttribFemale.endurance); + reader.get(mAttribFemale.personality); + reader.get(mAttribFemale.luck); + + break; + } + // [0..9]-> ICON + // NAM0 -> INDX -> MODL --+ + // ^ -> MODB | + // | | + // +-------------+ + // + case ESM4::SUB_NAM0: // start marker head data /* 1 */ + { + curr_part = 0; // head part + + if (isFO3 || isFONV) + { + mHeadParts.resize(8); + mHeadPartsFemale.resize(8); + } + else if (isTES4) + mHeadParts.resize(9); // assumed based on Construction Set + else + { + mHeadPartIdsMale.resize(5); + mHeadPartIdsFemale.resize(5); + } + + currentIndex = 0xffffffff; + break; + } + case ESM4::SUB_INDX: + { + reader.get(currentIndex); + // FIXME: below check is rather useless + //if (headpart) + //{ + // if (currentIndex > 8) + // throw std::runtime_error("ESM4::RACE::load - too many head part " + currentIndex); + //} + //else // bodypart + //{ + // if (currentIndex > 4) + // throw std::runtime_error("ESM4::RACE::load - too many body part " + currentIndex); + //} + + break; + } + case ESM4::SUB_MODL: + { + if (curr_part == 0) // head part + { + if (isMale || isTES4) + reader.getZString(mHeadParts[currentIndex].mesh); + else + reader.getZString(mHeadPartsFemale[currentIndex].mesh); // TODO: check TES4 + + // TES5 keeps head part formid in mHeadPartIdsMale and mHeadPartIdsFemale + } + else if (curr_part == 1) // body part + { + if (isMale) + reader.getZString(mBodyPartsMale[currentIndex].mesh); + else + reader.getZString(mBodyPartsFemale[currentIndex].mesh); + + // TES5 seems to have no body parts at all, instead keep EGT models + } + else if (curr_part == 2) // egt + { + //std::cout << mEditorId << " egt " << currentIndex << std::endl; // FIXME + reader.skipSubRecordData(); // FIXME TES5 egt + } + else + { + //std::cout << mEditorId << " hkx " << currentIndex << std::endl; // FIXME + reader.skipSubRecordData(); // FIXME TES5 hkx + } + + break; + } + case ESM4::SUB_MODB: reader.skipSubRecordData(); break; // always 0x0000? + case ESM4::SUB_ICON: + { + if (curr_part == 0) // head part + { + if (isMale || isTES4) + reader.getZString(mHeadParts[currentIndex].texture); + else + reader.getZString(mHeadPartsFemale[currentIndex].texture); // TODO: check TES4 + } + else if (curr_part == 1) // body part + { + if (isMale) + reader.getZString(mBodyPartsMale[currentIndex].texture); + else + reader.getZString(mBodyPartsFemale[currentIndex].texture); + } + else + reader.skipSubRecordData(); // FIXME TES5 + + break; + } + // + case ESM4::SUB_NAM1: // start marker body data /* 4 */ + { + + if (isFO3 || isFONV) + { + curr_part = 1; // body part + + mBodyPartsMale.resize(4); + mBodyPartsFemale.resize(4); + } + else if (isTES4) + { + curr_part = 1; // body part + + mBodyPartsMale.resize(5); // 0 = upper body, 1 = legs, 2 = hands, 3 = feet, 4 = tail + mBodyPartsFemale.resize(5); // 0 = upper body, 1 = legs, 2 = hands, 3 = feet, 4 = tail + } + else // TES5 + curr_part = 2; // for TES5 NAM1 indicates the start of EGT model + + if (isTES4) + currentIndex = 4; // FIXME: argonian tail mesh without preceeding INDX + else + currentIndex = 0xffffffff; + + break; + } + case ESM4::SUB_MNAM: isMale = true; break; /* 2, 5, 7 */ + case ESM4::SUB_FNAM: isMale = false; break; /* 3, 6, 8 */ + // + case ESM4::SUB_HNAM: + { + std::size_t numHairChoices = subHdr.dataSize / sizeof(FormId); + mHairChoices.resize(numHairChoices); + for (unsigned int i = 0; i < numHairChoices; ++i) + reader.get(mHairChoices.at(i)); + + break; + } + case ESM4::SUB_ENAM: + { + std::size_t numEyeChoices = subHdr.dataSize / sizeof(FormId); + mEyeChoices.resize(numEyeChoices); + for (unsigned int i = 0; i < numEyeChoices; ++i) + reader.get(mEyeChoices.at(i)); + + break; + } + case ESM4::SUB_FGGS: + { + if (isMale || isTES4) + { + mSymShapeModeCoefficients.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymShapeModeCoefficients.at(i)); + } + else + { + mSymShapeModeCoeffFemale.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymShapeModeCoeffFemale.at(i)); + } + + break; + } + case ESM4::SUB_FGGA: + { + if (isMale || isTES4) + { + mAsymShapeModeCoefficients.resize(30); + for (std::size_t i = 0; i < 30; ++i) + reader.get(mAsymShapeModeCoefficients.at(i)); + } + else + { + mAsymShapeModeCoeffFemale.resize(30); + for (std::size_t i = 0; i < 30; ++i) + reader.get(mAsymShapeModeCoeffFemale.at(i)); + } + + break; + } + case ESM4::SUB_FGTS: + { + if (isMale || isTES4) + { + mSymTextureModeCoefficients.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymTextureModeCoefficients.at(i)); + } + else + { + mSymTextureModeCoeffFemale.resize(50); + for (std::size_t i = 0; i < 50; ++i) + reader.get(mSymTextureModeCoeffFemale.at(i)); + } + + break; + } + // + case ESM4::SUB_SNAM: //skipping...2 // only in TES4? + { + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XNAM: + { + FormId race; + std::int32_t adjustment; + reader.get(race); + reader.get(adjustment); + mDisposition[race] = adjustment; + + break; + } + case ESM4::SUB_VNAM: + { + if (subHdr.dataSize == 8) // TES4 + { + reader.get(mVNAM[0]); // For TES4 seems to be 2 race formids + reader.get(mVNAM[1]); + } + else if (subHdr.dataSize == 4) // TES5 + { + // equipment type flags meant to be uint32 ??? GLOB reference? shows up in + // SCRO in sript records and CTDA in INFO records + uint32_t dummy; + reader.get(dummy); + } + else + { + reader.skipSubRecordData(); + std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + } + + break; + } + // + case ESM4::SUB_ANAM: // TES5 + { + if (isMale) + reader.getZString(mModelMale); + else + reader.getZString(mModelFemale); + break; + } + case ESM4::SUB_KSIZ: reader.get(mNumKeywords); break; + case ESM4::SUB_KWDA: + { + std::uint32_t formid; + for (unsigned int i = 0; i < mNumKeywords; ++i) + reader.getFormId(formid); + break; + } + // + case ESM4::SUB_WNAM: // ARMO FormId + { + reader.getFormId(mSkin); + //std::cout << mEditorId << " skin " << formIdToString(mSkin) << std::endl; // FIXME + break; + } + case ESM4::SUB_BODT: // body template + { + reader.get(mBodyTemplate.bodyPart); + reader.get(mBodyTemplate.flags); + reader.get(mBodyTemplate.unknown1); // probably padding + reader.get(mBodyTemplate.unknown2); // probably padding + reader.get(mBodyTemplate.unknown3); // probably padding + reader.get(mBodyTemplate.type); + + break; + } + case ESM4::SUB_BOD2: // TES5 + { + reader.get(mBodyTemplate.bodyPart); + mBodyTemplate.flags = 0; + mBodyTemplate.unknown1 = 0; // probably padding + mBodyTemplate.unknown2 = 0; // probably padding + mBodyTemplate.unknown3 = 0; // probably padding + reader.get(mBodyTemplate.type); + + break; + } + case ESM4::SUB_HEAD: // TES5 + { + FormId formId; + reader.getFormId(formId); + + // FIXME: no order? head, mouth, eyes, brow, hair + if (isMale) + mHeadPartIdsMale[currentIndex] = formId; + else + mHeadPartIdsFemale[currentIndex] = formId; + + //std::cout << mEditorId << (isMale ? " male head " : " female head ") + //<< formIdToString(formId) << " " << currentIndex << std::endl; // FIXME + + break; + } + case ESM4::SUB_NAM3: // start of hkx model + { + curr_part = 3; // for TES5 NAM3 indicates the start of hkx model + + break; + } + // Not sure for what this is used - maybe to indicate which slots are in use? e.g.: + // + // ManakinRace HEAD + // ManakinRace Hair + // ManakinRace BODY + // ManakinRace Hands + // ManakinRace Forearms + // ManakinRace Amulet + // ManakinRace Ring + // ManakinRace Feet + // ManakinRace Calves + // ManakinRace SHIELD + // ManakinRace + // ManakinRace LongHair + // ManakinRace Circlet + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace DecapitateHead + // ManakinRace Decapitate + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace + // ManakinRace FX0 + case ESM4::SUB_NAME: // TES5 biped object names (x32) + { + std::string name; + reader.getZString(name); + //std::cout << mEditorId << " " << name << std::endl; + + 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_YNAM: // FO3 + case ESM4::SUB_NAM2: // FO3 + case ESM4::SUB_VTCK: // FO3 + case ESM4::SUB_MODT: // FO3 + case ESM4::SUB_MODD: // FO3 + case ESM4::SUB_ONAM: // FO3 + { + + //std::cout << "RACE " << ESM::printName(subHdr.typeId) << " skipping..." << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::RACE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Race::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Race::blank() +//{ +//} diff --git a/components/esm4/loadrace.hpp b/components/esm4/loadrace.hpp new file mode 100644 index 0000000000..0cede13b0b --- /dev/null +++ b/components/esm4/loadrace.hpp @@ -0,0 +1,174 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_RACE +#define ESM4_RACE + +#include +#include +#include + +#include "formid.hpp" +#include "actor.hpp" // AttributeValues, BodyTemplate + +namespace ESM4 +{ + class Reader; + class Writer; + typedef std::uint32_t FormId; + + struct Race + { +#pragma pack(push, 1) + struct Data + { + std::uint8_t flags; // 0x01 = not playable, 0x02 = not male, 0x04 = not female, ?? = fixed + }; +#pragma pack(pop) + + enum SkillIndex + { + Skill_Armorer = 0x0C, + Skill_Athletics = 0x0D, + Skill_Blade = 0x0E, + Skill_Block = 0x0F, + Skill_Blunt = 0x10, + Skill_HandToHand = 0x11, + Skill_HeavyArmor = 0x12, + Skill_Alchemy = 0x13, + Skill_Alteration = 0x14, + Skill_Conjuration = 0x15, + Skill_Destruction = 0x16, + Skill_Illusion = 0x17, + Skill_Mysticism = 0x18, + Skill_Restoration = 0x19, + Skill_Acrobatics = 0x1A, + Skill_LightArmor = 0x1B, + Skill_Marksman = 0x1C, + Skill_Mercantile = 0x1D, + Skill_Security = 0x1E, + Skill_Sneak = 0x1F, + Skill_Speechcraft = 0x20, + Skill_None = 0xFF, + Skill_Unknown = 0x00 + }; + + enum HeadPartIndex // TES4 + { + Head = 0, + EarMale = 1, + EarFemale = 2, + Mouth = 3, + TeethLower = 4, + TeethUpper = 5, + Tongue = 6, + EyeLeft = 7, // no texture + EyeRight = 8, // no texture + NumHeadParts = 9 + }; + + enum BodyPartIndex // TES4 + { + UpperBody = 0, + LowerBody = 1, + Hands = 2, + Feet = 3, + Tail = 4, + NumBodyParts = 5 + }; + + struct BodyPart + { + std::string mesh; // can be empty for arms, hands, etc + std::string texture; // can be empty e.g. eye left, eye right + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + bool mIsTES5; + + std::string mEditorId; + std::string mFullName; + std::string mDesc; + std::string mModelMale; // TES5 skeleton (in TES4 skeleton is found in npc_) + std::string mModelFemale; // TES5 skeleton (in TES4 skeleton is found in npc_) + + AttributeValues mAttribMale; + AttributeValues mAttribFemale; + std::map mSkillBonus; + + // DATA + float mHeightMale; + float mHeightFemale; + float mWeightMale; + float mWeightFemale; + std::uint32_t mRaceFlags; // 0x0001 = playable? + + std::vector mHeadParts; // see HeadPartIndex + std::vector mHeadPartsFemale; // see HeadPartIndex + + std::vector mBodyPartsMale; // see BodyPartIndex + std::vector mBodyPartsFemale; // see BodyPartIndex + + std::vector mEyeChoices; // texture only + std::vector mHairChoices; // not for TES5 + + float mFaceGenMainClamp; + float mFaceGenFaceClamp; + std::vector mSymShapeModeCoefficients; // should be 50 + std::vector mSymShapeModeCoeffFemale; // should be 50 + std::vector mAsymShapeModeCoefficients; // should be 30 + std::vector mAsymShapeModeCoeffFemale; // should be 30 + std::vector mSymTextureModeCoefficients; // should be 50 + std::vector mSymTextureModeCoeffFemale; // should be 50 + + std::map mDisposition; // race adjustments + std::vector mBonusSpells; // race ability/power + std::vector mVNAM; // don't know what these are; 1 or 2 RACE FormIds + std::vector mDefaultHair; // male/female (HAIR FormId for TES4) + + std::uint32_t mNumKeywords; + + FormId mSkin; // TES5 + BodyTemplate mBodyTemplate; // TES5 + + // FIXME: there's no fixed order? + // head, mouth, eyes, brow, hair + std::vector mHeadPartIdsMale; // TES5 + std::vector mHeadPartIdsFemale; // TES5 + + Race(); + virtual ~Race(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_RACE diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp new file mode 100644 index 0000000000..4921fd16ca --- /dev/null +++ b/components/esm4/loadrefr.cpp @@ -0,0 +1,349 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadrefr.hpp" + +#include +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Reference::Reference() : mParent(0), mFormId(0), mFlags(0), + mBaseObj(0), mScale(1.f), mOwner(0), mGlobal(0), mFactionRank(-1), + mInitiallyDisabled(false), mIsMapMarker(false), mMapMarker(0), mCount(1), + mAudioLocation(0), mIsLocked(false), mLockLevel(0), mKey(0), mTargetRef(0) +{ + mEditorId.clear(); + mFullName.clear(); + + //mPlacement. + + mEsp.parent = 0; + mEsp.flags = 0; + + mRadio.rangeRadius = 0.f; + mRadio.broadcastRange = 0; + mRadio.staticPercentage = 0.f; + mRadio.posReference = 0; + + mDoor.destDoor = 0; + //mDoor.destPos. + mDoor.flags = 0; +} + +ESM4::Reference::~Reference() +{ +} + +void ESM4::Reference::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currCell(); // NOTE: only for persistent refs? + + // TODO: Let the engine apply this? Saved games? + //mInitiallyDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + FormId mid; + FormId sid; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_NAME: + { + reader.getFormId(mBaseObj); +#if 0 + if (mFlags & ESM4::Rec_Disabled) + std::cout << "REFR disable at start " << formIdToString(mFormId) << + " baseobj " << formIdToString(mBaseObj) << + " " << (mEditorId.empty() ? "" : mEditorId) << std::endl; // FIXME +#endif + //if (mBaseObj == 0x20) // e.g. FO3 mFormId == 0x0007E90F + //if (mBaseObj == 0x17) + //std::cout << mEditorId << std::endl; + break; + } + case ESM4::SUB_DATA: reader.get(mPlacement); break; + case ESM4::SUB_XSCL: reader.get(mScale); break; + case ESM4::SUB_XOWN: reader.getFormId(mOwner); break; + case ESM4::SUB_XGLB: reader.getFormId(mGlobal); break; + case ESM4::SUB_XRNK: reader.get(mFactionRank); break; + case ESM4::SUB_XESP: + { + reader.get(mEsp); + reader.adjustFormId(mEsp.parent); + //std::cout << "REFR parent: " << formIdToString(mEsp.parent) << " ref " << formIdToString(mFormId) + //<< ", 0x" << std::hex << (mEsp.flags & 0xff) << std::endl;// FIXME + break; + } + case ESM4::SUB_XTEL: + { + reader.getFormId(mDoor.destDoor); + reader.get(mDoor.destPos); + if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || isFONV) + reader.get(mDoor.flags); // not in Obvlivion + //std::cout << "REFR dest door: " << formIdToString(mDoor.destDoor) << std::endl;// FIXME + break; + } + case ESM4::SUB_XSED: + { + // 1 or 4 bytes + if (subHdr.dataSize == 1) + { + uint8_t data; + reader.get(data); + //std::cout << "REFR XSED " << std::hex << (int)data << std::endl; + break; + } + else if (subHdr.dataSize == 4) + { + uint32_t data; + reader.get(data); + //std::cout << "REFR XSED " << std::hex << (int)data << std::endl; + break; + } + + //std::cout << "REFR XSED dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XLOD: + { + // 12 bytes + if (subHdr.dataSize == 12) + { + float data, data2, data3; + reader.get(data); + reader.get(data2); + reader.get(data3); + //bool hasVisibleWhenDistantFlag = (mFlags & 0x00008000) != 0; // currently unused + // some are trees, e.g. 000E03B6, mBaseObj 00022F32, persistent, visible when distant + // some are doors, e.g. 000270F7, mBaseObj 000CD338, persistent, initially disabled + // (this particular one is an Oblivion Gate) + //std::cout << "REFR XLOD " << std::hex << (int)data << " " << (int)data2 << " " << (int)data3 << std::endl; + break; + } + //std::cout << "REFR XLOD dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XACT: + { + if (subHdr.dataSize == 4) + { + uint32_t data; + reader.get(data); + //std::cout << "REFR XACT " << std::hex << (int)data << std::endl; + break; + } + + //std::cout << "REFR XACT dataSize: " << subHdr.dataSize << std::endl;// FIXME + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XRTM: // formId + { + // seems like another ref, e.g. 00064583 has base object 00000034 which is "XMarkerHeading" + // e.g. some are doors (prob. quest related) + // MS94OblivionGateRef XRTM : 00097C88 + // MQ11SkingradGate XRTM : 00064583 + // MQ11ChorrolGate XRTM : 00188770 + // MQ11LeyawiinGate XRTM : 0018AD7C + // MQ11AnvilGate XRTM : 0018D452 + // MQ11BravilGate XRTM : 0018AE1B + // e.g. some are XMarkerHeading + // XRTM : 000A4DD7 in OblivionRDCavesMiddleA05 (maybe spawn points?) + FormId marker; + reader.getFormId(marker); + //std::cout << "REFR " << mEditorId << " XRTM : " << formIdToString(marker) << std::endl;// FIXME + break; + } + case ESM4::SUB_TNAM: //reader.get(mMapMarker); break; + { + if (subHdr.dataSize != sizeof(mMapMarker)) + //reader.skipSubRecordData(); // FIXME: FO3 + reader.getFormId(mid); + else + reader.get(mMapMarker); // TES4 + + break; + } + case ESM4::SUB_XMRK: mIsMapMarker = true; break; // all have mBaseObj 0x00000010 "MapMarker" + case ESM4::SUB_FNAM: + { + //std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + case ESM4::SUB_XTRG: // formId + { + reader.getFormId(mTargetRef); + //std::cout << "REFR XRTG : " << formIdToString(id) << std::endl;// FIXME + break; + } + case ESM4::SUB_CNAM: reader.getFormId(mAudioLocation); break; // FONV + case ESM4::SUB_XRDO: // FO3 + { + reader.get(mRadio.rangeRadius); + reader.get(mRadio.broadcastRange); + reader.get(mRadio.staticPercentage); + reader.getFormId(mRadio.posReference); + + break; + } + case ESM4::SUB_SCRO: // FO3 + { + reader.getFormId(sid); + //if (mFormId == 0x0016b74B) + //std::cout << "REFR SCRO : " << formIdToString(sid) << std::endl;// FIXME + break; + } + case ESM4::SUB_XLOC: + { + mIsLocked = true; + std::int8_t dummy; // FIXME: very poor code + + reader.get(mLockLevel); + reader.get(dummy); + reader.get(dummy); + reader.get(dummy); + reader.getFormId(mKey); + reader.get(dummy); // flag? + reader.get(dummy); + reader.get(dummy); + reader.get(dummy); + if (subHdr.dataSize == 16) + reader.skipSubRecordData(4); // Oblivion (sometimes), flag? + else if (subHdr.dataSize == 20) // Skyrim, FO3 + reader.skipSubRecordData(8); // flag? + + 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 ESM4::SUB_XPCI: // formId + case ESM4::SUB_XLCM: + case ESM4::SUB_XCNT: + 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_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: // Dawnguard only? + 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 + { + //if (mFormId == 0x0007e90f) // XPRM XPOD + //if (mBaseObj == 0x17) //XPRM XOCP occlusion plane data XMBO bound half extents + //std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::REFR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } + //if (mFormId == 0x0016B74B) // base is TACT vCasinoUltraLuxeRadio in cell ULCasino + //std::cout << "REFR SCRO " << formIdToString(sid) << std::endl; +} + +//void ESM4::Reference::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Reference::blank() +{ +} diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp new file mode 100644 index 0000000000..d9c96cbf9f --- /dev/null +++ b/components/esm4/loadrefr.hpp @@ -0,0 +1,119 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_REFR_H +#define ESM4_REFR_H + +#include + +#include "reference.hpp" // FormId, Placement, EnableParent + +namespace ESM4 +{ + class Reader; + class Writer; + + enum MapMarkerType + { + Map_None = 0x00, // ? + Map_Camp = 0x01, + Map_Cave = 0x02, + Map_City = 0x03, + Map_ElvenRuin = 0x04, + Map_FortRuin = 0x05, + Map_Mine = 0x06, + Map_Landmark = 0x07, + Map_Tavern = 0x08, + Map_Settlement = 0x09, + Map_DaedricShrine = 0x0A, + Map_OblivionGate = 0x0B, + Map_Unknown = 0x0C // ? (door icon) + }; + + struct TeleportDest + { + FormId destDoor; + Placement destPos; + std::uint32_t flags; // 0x01 no alarm (only in TES5) + }; + + struct RadioStationData + { + float rangeRadius; + // 0 radius, 1 everywhere, 2 worldspace and linked int, 3 linked int, 4 current cell only + std::uint32_t broadcastRange; + float staticPercentage; + FormId posReference; // only used if broadcastRange == 0 + }; + + struct Reference + { + FormId mParent; // cell FormId (currently persistent refs only), from the loading sequence + // NOTE: for exterior cells it will be the dummy cell FormId + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mBaseObj; + + Placement mPlacement; + float mScale; // default 1.f + FormId mOwner; + FormId mGlobal; + std::int32_t mFactionRank; + + bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) + bool mIsMapMarker; + std::uint16_t mMapMarker; + + EnableParent mEsp; + + std::uint32_t mCount; // only if > 1 (default 1) + + FormId mAudioLocation; + + RadioStationData mRadio; + + TeleportDest mDoor; + bool mIsLocked; + std::int8_t mLockLevel; + FormId mKey; + + FormId mTargetRef; + + Reference(); + virtual ~Reference(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_REFR_H diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp new file mode 100644 index 0000000000..6f871e8471 --- /dev/null +++ b/components/esm4/loadregn.cpp @@ -0,0 +1,164 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadregn.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include + +//#include // FIXME: debug only +//#include "formid.hpp" + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Region::Region() : mFormId(0), mFlags(0), mWorldId(0), mEdgeFalloff(0) +{ + mEditorId.clear(); + mShader.clear(); + mMapName.clear(); +} + +ESM4::Region::~Region() +{ +} + +void ESM4::Region::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_RCLR: reader.get(mColour); break; + case ESM4::SUB_WNAM: reader.getFormId(mWorldId); break; + case ESM4::SUB_ICON: reader.getZString(mShader); break; + case ESM4::SUB_RPLI: reader.get(mEdgeFalloff); break; + case ESM4::SUB_RPLD: + { + mRPLD.resize(subHdr.dataSize/sizeof(std::uint32_t)); + for (std::vector::iterator it = mRPLD.begin(); it != mRPLD.end(); ++it) + { + reader.get(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "RPLD: 0x" << std::hex << *it << std::endl; +#endif + } + + break; + } + case ESM4::SUB_RDAT: reader.get(mData); break; + case ESM4::SUB_RDMP: + { + assert(mData.type == RDAT_Map && "REGN unexpected data type"); + reader.getLocalizedString(mMapName); break; + } + // FO3 only 2: DemoMegatonSound and DC01 (both 0 RDMD) + // FONV none + case ESM4::SUB_RDMD: // music type; 0 default, 1 public, 2 dungeon + { +#if 0 + int dummy; + reader.get(dummy); + std::cout << "REGN " << mEditorId << " " << dummy << std::endl; +#else + reader.skipSubRecordData(); +#endif + break; + } + case ESM4::SUB_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 + { + //assert(mData.type == RDAT_Map && "REGN unexpected data type"); + if (mData.type != RDAT_Sound) + throw std::runtime_error("ESM4::REGN::load - unexpected data type " + + ESM::printName(subHdr.typeId)); + + std::size_t numSounds = subHdr.dataSize / sizeof(RegionSound); + mSounds.resize(numSounds); + for (std::size_t i = 0; i < numSounds; ++i) + reader.get(mSounds.at(i)); + + 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 + { + //RDAT skipping... following is a map + //RDMP skipping... map name + // + //RDAT skipping... following is weather + //RDWT skipping... weather data + // + //RDAT skipping... following is sound + //RDMD skipping... unknown, maybe music data + // + //RDSD skipping... unknown, maybe sound data + // + //RDAT skipping... following is grass + //RDGS skipping... unknown, maybe grass + + //std::cout << "REGN " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + default: + throw std::runtime_error("ESM4::REGN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Region::save(ESM4::Writer& writer) const +//{ +//} + +void ESM4::Region::blank() +{ +} diff --git a/components/esm4/loadregn.hpp b/components/esm4/loadregn.hpp new file mode 100644 index 0000000000..36ed8eb9d5 --- /dev/null +++ b/components/esm4/loadregn.hpp @@ -0,0 +1,98 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_REGN_H +#define ESM4_REGN_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Region + { + enum RegionDataType + { + RDAT_None = 0x00, + RDAT_Objects = 0x02, + RDAT_Weather = 0x03, + RDAT_Map = 0x04, + RDAT_Landscape = 0x05, + RDAT_Grass = 0x06, + RDAT_Sound = 0x07, + RDAT_Imposter = 0x08 + }; + +#pragma pack(push, 1) + struct RegionData + { + std::uint32_t type; + std::uint8_t flag; + std::uint8_t priority; + std::uint16_t unknown; + }; + + struct RegionSound + { + FormId sound; + std::uint32_t flags; // 0 pleasant, 1 cloudy, 2 rainy, 3 snowy + std::uint32_t chance; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::uint32_t mColour; // RGBA + FormId mWorldId; // worldspace formid + + std::string mShader; //?? ICON + std::string mMapName; + + std::uint32_t mEdgeFalloff; + std::vector mRPLD; // unknown, point data? + + RegionData mData; + std::vector mSounds; + + Region(); + virtual ~Region(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + void blank(); + }; +} + +#endif // ESM4_REGN_H diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp new file mode 100644 index 0000000000..cb7742eb7c --- /dev/null +++ b/components/esm4/loadroad.cpp @@ -0,0 +1,123 @@ +/* + Copyright (C) 2020 - 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadroad.hpp" + +#include +//#include // FIXME: for debugging only + +#include "formid.hpp" // FIXME: for workaround +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Road::Road() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + mNodes.clear(); + mLinks.clear(); +} + +ESM4::Road::~Road() +{ +} + +void ESM4::Road::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + mParent = reader.currWorld(); + + mEditorId = formIdToString(mFormId); // FIXME: quick workaround to use existing code + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_PGRP: + { + std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); + + mNodes.resize(numNodes); + for (std::size_t i = 0; i < numNodes; ++i) + { + reader.get(mNodes.at(i)); + } + + break; + } + case ESM4::SUB_PGRR: + { + static PGRR link; + static RDRP linkPt; + + for (std::size_t i = 0; i < mNodes.size(); ++i) + { + for (std::size_t j = 0; j < mNodes[i].numLinks; ++j) + { + link.startNode = std::int16_t(i); + + reader.get(linkPt); + + // FIXME: instead of looping each time, maybe use a map? + bool found = false; + for (std::size_t k = 0; k < mNodes.size(); ++k) + { + if (linkPt.x != mNodes[k].x || linkPt.y != mNodes[k].y || linkPt.z != mNodes[k].z) + continue; + else + { + link.endNode = std::int16_t(k); + mLinks.push_back(link); + + found = true; + break; + } + } + + if (!found) + throw std::runtime_error("ESM4::ROAD::PGRR - Unknown link point " + + std::to_string(j) + "at node " + std::to_string(i) + "."); + } + } + + break; + } + default: + throw std::runtime_error("ESM4::ROAD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Road::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Road::blank() +//{ +//} diff --git a/components/esm4/loadroad.hpp b/components/esm4/loadroad.hpp new file mode 100644 index 0000000000..dad92e858c --- /dev/null +++ b/components/esm4/loadroad.hpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2020 - 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_ROAD_H +#define ESM4_ROAD_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Road + { +#pragma pack(push, 1) + // FIXME: duplicated from PGRD + struct PGRP + { + float x; + float y; + float z; + std::uint8_t numLinks; + std::uint8_t unknown1; + std::uint16_t unknown2; + }; + + // FIXME: duplicated from PGRD + struct PGRR + { + std::int16_t startNode; + std::int16_t endNode; + }; + + struct RDRP + { + float x; + float y; + float z; + }; +#pragma pack(pop) + FormId mParent; // world FormId, from the loading sequence + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::vector mNodes; + std::vector mLinks; + + Road(); + virtual ~Road(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_ROAD_H diff --git a/components/esm4/loadsbsp.cpp b/components/esm4/loadsbsp.cpp new file mode 100644 index 0000000000..c9af5b856e --- /dev/null +++ b/components/esm4/loadsbsp.cpp @@ -0,0 +1,78 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadsbsp.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SubSpace::SubSpace() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + + mDimension.x = 0.f; + mDimension.y = 0.f; + mDimension.z = 0.f; +} + +ESM4::SubSpace::~SubSpace() +{ +} + +void ESM4::SubSpace::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_DNAM: + { + reader.get(mDimension.x); + reader.get(mDimension.y); + reader.get(mDimension.z); + break; + } + default: + throw std::runtime_error("ESM4::SBSP::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SubSpace::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SubSpace::blank() +//{ +//} diff --git a/components/esm4/loadsbsp.hpp b/components/esm4/loadsbsp.hpp new file mode 100644 index 0000000000..c0ca7c5ddf --- /dev/null +++ b/components/esm4/loadsbsp.hpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SBSP_H +#define ESM4_SBSP_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + + struct SubSpace + { + struct Dimension + { + float x; + float y; + float z; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + Dimension mDimension; + + SubSpace(); + virtual ~SubSpace(); + + virtual void load(Reader& reader); + //virtual void save(Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SBSP_H diff --git a/components/esm4/loadscol.cpp b/components/esm4/loadscol.cpp new file mode 100644 index 0000000000..1990af5f4b --- /dev/null +++ b/components/esm4/loadscol.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#include "loadscol.hpp" + +#include +#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::StaticCollection::StaticCollection() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::StaticCollection::~StaticCollection() +{ +} + +void ESM4::StaticCollection::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_OBND: + case ESM4::SUB_MODL: + case ESM4::SUB_MODT: + case ESM4::SUB_ONAM: + case ESM4::SUB_DATA: + { + //std::cout << "SCOL " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + std::cout << "SCOL " << ESM::printName(subHdr.typeId) << " skipping..." + << subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + //throw std::runtime_error("ESM4::SCOL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::StaticCollection::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::StaticCollection::blank() +//{ +//} diff --git a/components/esm4/loadscol.hpp b/components/esm4/loadscol.hpp new file mode 100644 index 0000000000..c32cdd3ca9 --- /dev/null +++ b/components/esm4/loadscol.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_SCOL_H +#define ESM4_SCOL_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct StaticCollection + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + StaticCollection(); + virtual ~StaticCollection(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SCOL_H diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp new file mode 100644 index 0000000000..cf4bdeb58a --- /dev/null +++ b/components/esm4/loadscpt.cpp @@ -0,0 +1,173 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadscpt.hpp" + +#include +#include // FIXME: debugging only +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Script::Script() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); +} + +ESM4::Script::~Script() +{ +} + +void ESM4::Script::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + static ScriptLocalVariableData localVar; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: + { + reader.getZString(mEditorId); + break; + } + case ESM4::SUB_SCHR: + { + // For debugging only +#if 0 + unsigned char mDataBuf[256/*bufSize*/]; + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + for (unsigned int i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < 256/*bufSize*/-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + reader.get(mScript.scriptHeader); +#endif + break; + } + case ESM4::SUB_SCTX: reader.getString(mScript.scriptSource); + //if (mEditorId == "CTrapLogs01SCRIPT") + //std::cout << mScript.scriptSource << std::endl; + break; + case ESM4::SUB_SCDA: // compiled script data + { + // For debugging only +#if 0 + if (subHdr.dataSize >= 4096) + { + std::cout << "Skipping " << mEditorId << std::endl; + reader.skipSubRecordData(); + break; + } + + std::cout << mEditorId << std::endl; + + unsigned char mDataBuf[4096/*bufSize*/]; + reader.get(&mDataBuf[0], subHdr.dataSize); + + std::ostringstream ss; + for (unsigned int i = 0; i < subHdr.dataSize; ++i) + { + //if (mDataBuf[i] > 64 && mDataBuf[i] < 91) + //ss << (char)(mDataBuf[i]) << " "; + //else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < 4096/*bufSize*/-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +#else + reader.skipSubRecordData(); +#endif + break; + } + case ESM4::SUB_SCRO: reader.getFormId(mScript.globReference); break; + case ESM4::SUB_SLSD: + { + localVar.clear(); + reader.get(localVar.index); + reader.get(localVar.unknown1); + reader.get(localVar.unknown2); + reader.get(localVar.unknown3); + reader.get(localVar.type); + reader.get(localVar.unknown4); + // WARN: assumes SCVR will follow immediately + + break; + } + case ESM4::SUB_SCVR: // assumed always pair with SLSD + { + reader.getZString(localVar.variableName); + + mScript.localVarData.push_back(localVar); + + break; + } + case ESM4::SUB_SCRV: + { + std::uint32_t index; + reader.get(index); + + mScript.localRefVarIndex.push_back(index); + + break; + } + default: + //std::cout << "SCPT " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + //reader.skipSubRecordData(); + //break; + throw std::runtime_error("ESM4::SCPT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Script::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Script::blank() +//{ +//} diff --git a/components/esm4/loadscpt.hpp b/components/esm4/loadscpt.hpp new file mode 100644 index 0000000000..747f49444d --- /dev/null +++ b/components/esm4/loadscpt.hpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SCPT_H +#define ESM4_SCPT_H + +#include + +#include "formid.hpp" +#include "script.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Script + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + ScriptDefinition mScript; + + Script(); + virtual ~Script(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SCPT_H diff --git a/components/esm4/loadscrl.cpp b/components/esm4/loadscrl.cpp new file mode 100644 index 0000000000..c09147ce5e --- /dev/null +++ b/components/esm4/loadscrl.cpp @@ -0,0 +1,99 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadscrl.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Scroll::Scroll() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::Scroll::~Scroll() +{ +} + +void ESM4::Scroll::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_DATA: + { + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); 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: + { + //std::cout << "SCRL " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SCRL::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Scroll::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Scroll::blank() +//{ +//} diff --git a/components/esm4/loadscrl.hpp b/components/esm4/loadscrl.hpp new file mode 100644 index 0000000000..a88781a6f8 --- /dev/null +++ b/components/esm4/loadscrl.hpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SCRL_H +#define ESM4_SCRL_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Scroll + { + struct Data + { + std::uint32_t value; + float weight; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + + Data mData; + + Scroll(); + virtual ~Scroll(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SCRL_H diff --git a/components/esm4/loadsgst.cpp b/components/esm4/loadsgst.cpp new file mode 100644 index 0000000000..b46c6bad29 --- /dev/null +++ b/components/esm4/loadsgst.cpp @@ -0,0 +1,118 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadsgst.hpp" + +#include +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SigilStone::SigilStone() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.uses = 0; + mData.value = 0; + mData.weight = 0.f; + + std::memset(&mEffect, 0, sizeof(ScriptEffect)); +} + +ESM4::SigilStone::~SigilStone() +{ +} + +void ESM4::SigilStone::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: + { + if (mFullName.empty()) + { + if (!reader.getZString(mFullName)) + throw std::runtime_error ("SGST FULL data read error"); + } + else + { + // FIXME: should be part of a struct? + std::string scriptEffectName; + if (!reader.getZString(scriptEffectName)) + throw std::runtime_error ("SGST FULL data read error"); + mScriptEffect.push_back(scriptEffectName); + } + break; + } + case ESM4::SUB_DATA: + { + reader.get(mData.uses); + reader.get(mData.value); + reader.get(mData.weight); + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_SCIT: + { + reader.get(mEffect); + reader.adjustFormId(mEffect.formId); + break; + } + case ESM4::SUB_MODT: + case ESM4::SUB_EFID: + case ESM4::SUB_EFIT: + { + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SGST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SigilStone::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SigilStone::blank() +//{ +//} diff --git a/components/esm4/loadsgst.hpp b/components/esm4/loadsgst.hpp new file mode 100644 index 0000000000..b0bbf9d800 --- /dev/null +++ b/components/esm4/loadsgst.hpp @@ -0,0 +1,75 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SGST_H +#define ESM4_SGST_H + +#include +#include + +#include "effect.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct SigilStone + { + struct Data + { + std::uint8_t uses; + std::uint32_t value; // gold + float weight; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + std::vector mScriptEffect; // FIXME: prob. should be in a struct + FormId mScriptId; + ScriptEffect mEffect; + + Data mData; + + SigilStone(); + virtual ~SigilStone(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SGST_H diff --git a/components/esm4/loadslgm.cpp b/components/esm4/loadslgm.cpp new file mode 100644 index 0000000000..482c817802 --- /dev/null +++ b/components/esm4/loadslgm.cpp @@ -0,0 +1,91 @@ +/* + Copyright (C) 2016, 2018, 2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadslgm.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SoulGem::SoulGem() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mSoul(0), mSoulCapacity(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mIcon.clear(); + + mData.value = 0; + mData.weight = 0.f; +} + +ESM4::SoulGem::~SoulGem() +{ +} + +void ESM4::SoulGem::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_DATA: reader.get(mData); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_SOUL: reader.get(mSoul); break; + case ESM4::SUB_SLCP: reader.get(mSoulCapacity); break; + case ESM4::SUB_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: + { + //std::cout << "SLGM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SLGM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SoulGem::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SoulGem::blank() +//{ +//} diff --git a/components/esm4/loadslgm.hpp b/components/esm4/loadslgm.hpp new file mode 100644 index 0000000000..8724da13c1 --- /dev/null +++ b/components/esm4/loadslgm.hpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SLGM_H +#define ESM4_SLGM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct SoulGem + { +#pragma pack(push, 1) + struct Data + { + std::uint32_t value; // gold + float weight; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mIcon; // inventory + + float mBoundRadius; + + FormId mScriptId; + std::uint8_t mSoul; // 0 = None, 1 = Petty, 2 = Lesser, 3 = Common, 4 = Greater, 5 = Grand + std::uint8_t mSoulCapacity; // 0 = None, 1 = Petty, 2 = Lesser, 3 = Common, 4 = Greater, 5 = Grand + + Data mData; + + SoulGem(); + virtual ~SoulGem(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SLGM_H diff --git a/components/esm4/loadsndr.cpp b/components/esm4/loadsndr.cpp new file mode 100644 index 0000000000..25109e36b6 --- /dev/null +++ b/components/esm4/loadsndr.cpp @@ -0,0 +1,94 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadsndr.hpp" + +#include +//#include // FIXME: for debugging only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::SoundReference::SoundReference() : mFormId(0), mFlags(0), mSoundCategory(0), mSoundId(0), mOutputModel(0) +{ + mEditorId.clear(); + mSoundFile.clear(); +} + +ESM4::SoundReference::~SoundReference() +{ +} + +void ESM4::SoundReference::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_CTDA: + { + reader.get(&mTargetCondition, 20); + reader.get(mTargetCondition.runOn); + reader.get(mTargetCondition.reference); + if (mTargetCondition.reference) + reader.adjustFormId(mTargetCondition.reference); + reader.skipSubRecordData(4); // unknown + + break; + } + case ESM4::SUB_GNAM: reader.getFormId(mSoundCategory); break; + case ESM4::SUB_SNAM: reader.getFormId(mSoundId); break; + case ESM4::SUB_ONAM: reader.getFormId(mOutputModel); break; + case ESM4::SUB_ANAM: reader.getZString(mSoundFile); break; + case ESM4::SUB_LNAM: reader.get(mLoopInfo); break; + case ESM4::SUB_BNAM: reader.get(mData); break; + case ESM4::SUB_CNAM: // CRC32 hash + case ESM4::SUB_FNAM: // unknown + { + //std::cout << "SNDR " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SNDR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::SoundReference::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::SoundReference::blank() +//{ +//} diff --git a/components/esm4/loadsndr.hpp b/components/esm4/loadsndr.hpp new file mode 100644 index 0000000000..17f0491136 --- /dev/null +++ b/components/esm4/loadsndr.hpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SNDR_H +#define ESM4_SNDR_H + +#include +#include + +#include "formid.hpp" +#include "script.hpp" // TargetCondition + +namespace ESM4 +{ + class Reader; + class Writer; + +#pragma pack(push, 1) + struct LoopInfo + { + std::uint16_t flags; + std::uint8_t unknown; + std::uint8_t rumble; + }; + + struct SoundInfo + { + std::int8_t frequencyAdjustment; // %, signed + std::uint8_t frequencyVariance; // % + std::uint8_t priority; // default 128 + std::uint8_t dBVriance; + std::uint16_t staticAttenuation; // divide by 100 to get value in dB + }; +#pragma pack(pop) + + struct SoundReference + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + FormId mSoundCategory; // SNCT + FormId mSoundId; // another SNDR + FormId mOutputModel; // SOPM + + std::string mSoundFile; + LoopInfo mLoopInfo; + SoundInfo mData; + + TargetCondition mTargetCondition; + + SoundReference(); + virtual ~SoundReference(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SNDR_H diff --git a/components/esm4/loadsoun.cpp b/components/esm4/loadsoun.cpp new file mode 100644 index 0000000000..6441d8a799 --- /dev/null +++ b/components/esm4/loadsoun.cpp @@ -0,0 +1,95 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadsoun.hpp" + +#include +//#include // FIXME + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Sound::Sound() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mSoundFile.clear(); +} + +ESM4::Sound::~Sound() +{ +} + +void ESM4::Sound::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FNAM: reader.getZString(mSoundFile); break; + case ESM4::SUB_SNDX: reader.get(mData); break; + case ESM4::SUB_SNDD: + { + if (subHdr.dataSize == 8) + reader.get(&mData, 8); + else + { + reader.get(mData); + 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 + { + //std::cout << "SOUN " << ESM::printName(subHdr.typeId) << " skipping..." + //<< subHdr.dataSize << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::SOUN::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Sound::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Sound::blank() +//{ +//} diff --git a/components/esm4/loadsoun.hpp b/components/esm4/loadsoun.hpp new file mode 100644 index 0000000000..845f38c380 --- /dev/null +++ b/components/esm4/loadsoun.hpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_SOUN_H +#define ESM4_SOUN_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Sound + { + enum Flags + { + Flag_RandomFreqShift = 0x0001, + Flag_PlayAtRandom = 0x0002, + Flag_EnvIgnored = 0x0004, + Flag_RandomLocation = 0x0008, + Flag_Loop = 0x0010, + Flag_MenuSound = 0x0020, + Flag_2D = 0x0040, + Flag_360LFE = 0x0080 + }; + +#pragma pack(push, 1) + struct SNDX + { + std::uint8_t minAttenuation; // distance? + std::uint8_t maxAttenuation; // distance? + std::int8_t freqAdjustment; // %, signed + std::uint8_t unknown; // probably padding + std::uint16_t flags; + std::uint16_t unknown2; // probably padding + std::uint16_t staticAttenuation; // divide by 100 to get value in dB + std::uint8_t stopTime; // multiply by 1440/256 to get value in minutes + std::uint8_t startTime; // multiply by 1440/256 to get value in minutes + }; + + struct SoundData + { + std::int16_t attenuationPoint1; + std::int16_t attenuationPoint2; + std::int16_t attenuationPoint3; + std::int16_t attenuationPoint4; + std::int16_t attenuationPoint5; + std::int16_t reverbAttenuationControl; + std::int32_t priority; + std::int32_t x; + std::int32_t y; + }; +#pragma pack(pop) + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::string mSoundFile; + SNDX mData; + SoundData mExtra; + + Sound(); + virtual ~Sound(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_SOUN_H diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp new file mode 100644 index 0000000000..de487147fe --- /dev/null +++ b/components/esm4/loadstat.cpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2015-2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadstat.hpp" + +#include +#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Static::Static() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); +} + +ESM4::Static::~Static() +{ +} + +void ESM4::Static::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + { + // version is only availabe in TES5 (seems to be 27 or 28?) + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + //std::cout << "STAT MODT ver: " << std::hex << reader.hdr().record.version << std::endl; + + // for TES4 these are just a sequence of bytes + mMODT.resize(subHdr.dataSize/sizeof(std::uint8_t)); + for (std::vector::iterator it = mMODT.begin(); it != mMODT.end(); ++it) + { + reader.get(*it); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "MODT: " << std::hex << *it << std::endl; +#endif + } + break; + } + case ESM4::SUB_MODS: + case ESM4::SUB_OBND: + case ESM4::SUB_DNAM: + case ESM4::SUB_MNAM: + case ESM4::SUB_BRUS: // FONV + case ESM4::SUB_RNAM: // FONV + { + //std::cout << "STAT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::STAT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Static::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Static::blank() +//{ +//} diff --git a/components/esm4/loadstat.hpp b/components/esm4/loadstat.hpp new file mode 100644 index 0000000000..966721ef37 --- /dev/null +++ b/components/esm4/loadstat.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2015-2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_STAT_H +#define ESM4_STAT_H + +#include +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Static + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + std::vector mMODT; // FIXME texture hash + + Static(); + virtual ~Static(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_STAT_H diff --git a/components/esm4/loadtact.cpp b/components/esm4/loadtact.cpp new file mode 100644 index 0000000000..c082048086 --- /dev/null +++ b/components/esm4/loadtact.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadtact.hpp" + +#include +#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::TalkingActivator::TalkingActivator() : mFormId(0), mFlags(0), mScriptId(0), mVoiceType(0), mLoopSound(0), + mRadioTemplate(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); +} + +ESM4::TalkingActivator::~TalkingActivator() +{ +} + +void ESM4::TalkingActivator::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_VNAM: reader.getFormId(mVoiceType); break; + case ESM4::SUB_SNAM: reader.getFormId(mLoopSound); break; + case ESM4::SUB_INAM: reader.getFormId(mRadioTemplate); break; // FONV + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_DEST: // FO3 destruction + case ESM4::SUB_DSTD: // FO3 destruction + case ESM4::SUB_DSTF: // FO3 destruction + case ESM4::SUB_FNAM: + case ESM4::SUB_PNAM: + case ESM4::SUB_MODT: // texture file hash? + case ESM4::SUB_OBND: + { + //std::cout << "TACT " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TACT::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::TalkingActivator::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::TalkingActivator::blank() +//{ +//} diff --git a/components/esm4/loadtact.hpp b/components/esm4/loadtact.hpp new file mode 100644 index 0000000000..a1d15524be --- /dev/null +++ b/components/esm4/loadtact.hpp @@ -0,0 +1,76 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TACT_H +#define ESM4_TACT_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + enum TalkingActivatorFlags + { + TACT_OnLocalMap = 0x00000200, + TACT_QuestItem = 0x00000400, + TACT_NoVoiceFilter = 0x00002000, + TACT_RandomAnimStart = 0x00010000, + TACT_RadioStation = 0x00020000, + TACT_NonProxy = 0x10000000, // only valid if Radio Station + TACT_ContBroadcast = 0x40000000 // only valid if Radio Station + }; + + struct TalkingActivator + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see above for details + + std::string mEditorId; + std::string mFullName; + + std::string mModel; + + FormId mScriptId; + FormId mVoiceType; // VTYP + FormId mLoopSound; // SOUN + FormId mRadioTemplate; // SOUN + + TalkingActivator(); + virtual ~TalkingActivator(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TACT_H diff --git a/components/esm4/loadterm.cpp b/components/esm4/loadterm.cpp new file mode 100644 index 0000000000..68a436d87c --- /dev/null +++ b/components/esm4/loadterm.cpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2019-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadterm.hpp" + +#include +//#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Terminal::Terminal() : mFormId(0), mFlags(0), mScriptId(0), mPasswordNote(0), mSound(0) +{ + mEditorId.clear(); + mFullName.clear(); + mText.clear(); + + mModel.clear(); + mResultText.clear(); +} + +ESM4::Terminal::~Terminal() +{ +} + +void ESM4::Terminal::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_PNAM: reader.getFormId(mPasswordNote); break; + case ESM4::SUB_SNAM: reader.getFormId(mSound); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_RNAM: reader.getZString(mResultText); break; + case ESM4::SUB_DNAM: // difficulty + case ESM4::SUB_ANAM: // flags + case ESM4::SUB_CTDA: + case ESM4::SUB_INAM: + case ESM4::SUB_ITXT: + case ESM4::SUB_MODT: // texture hash? + 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_MODS: // FONV + { + //std::cout << "TERM " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TERM::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Terminal::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Terminal::blank() +//{ +//} diff --git a/components/esm4/loadterm.hpp b/components/esm4/loadterm.hpp new file mode 100644 index 0000000000..9fb0ee60a8 --- /dev/null +++ b/components/esm4/loadterm.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TERM_H +#define ESM4_TERM_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Terminal + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mText; + + std::string mModel; + std::string mResultText; + + FormId mScriptId; + FormId mPasswordNote; + FormId mSound; + + Terminal(); + virtual ~Terminal(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TERM_H diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp new file mode 100644 index 0000000000..42c4acde4e --- /dev/null +++ b/components/esm4/loadtes4.cpp @@ -0,0 +1,113 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadtes4.hpp" + +#ifdef NDEBUG // FIXME: debuggigng only +#undef NDEBUG +#endif + +#include +#include + +#include // FIXME: debugging only + +#include "common.hpp" +#include "formid.hpp" +#include "reader.hpp" +//#include "writer.hpp" + +void ESM4::Header::load(ESM4::Reader& reader) +{ + mFlags = reader.hdr().record.flags; // 0x01 = Rec_ESM, 0x80 = Rec_Localized + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_HEDR: + { + if (!reader.getExact(mData.version) || !reader.getExact(mData.records) || !reader.getExact(mData.nextObjectId)) + throw std::runtime_error("TES4 HEDR data read error"); + + assert((size_t)subHdr.dataSize == sizeof(mData.version)+sizeof(mData.records)+sizeof(mData.nextObjectId) + && "TES4 HEDR data size mismatch"); + break; + } + case ESM4::SUB_CNAM: reader.getZString(mAuthor); break; + case ESM4::SUB_SNAM: reader.getZString(mDesc); break; + case ESM4::SUB_MAST: // multiple + { + ESM::MasterData m; + if (!reader.getZString(m.name)) + throw std::runtime_error("TES4 MAST data read error"); + + // NOTE: some mods do not have DATA following MAST so can't read DATA here + + mMaster.push_back (m); + break; + } + case ESM4::SUB_DATA: + { + // WARNING: assumes DATA always follows MAST + if (!reader.getExact(mMaster.back().size)) + throw std::runtime_error("TES4 DATA data read error"); + break; + } + case ESM4::SUB_ONAM: + { + mOverrides.resize(subHdr.dataSize/sizeof(FormId)); + for (unsigned int & mOverride : mOverrides) + { + if (!reader.getExact(mOverride)) + throw std::runtime_error("TES4 ONAM data read error"); +#if 0 + std::string padding = ""; + padding.insert(0, reader.stackSize()*2, ' '); + std::cout << padding << "ESM4::Header::ONAM overrides: " << formIdToString(mOverride) << std::endl; +#endif + } + break; + } + case ESM4::SUB_INTV: + case ESM4::SUB_INCC: + case ESM4::SUB_OFST: // Oblivion only? + case ESM4::SUB_DELE: // Oblivion only? + { + //std::cout << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::Header::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Header::save(ESM4::Writer& writer) +//{ +//} diff --git a/components/esm4/loadtes4.hpp b/components/esm4/loadtes4.hpp new file mode 100644 index 0000000000..6d84fe360b --- /dev/null +++ b/components/esm4/loadtes4.hpp @@ -0,0 +1,69 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TES4_H +#define ESM4_TES4_H + +#include + +#include "formid.hpp" +#include "../esm/common.hpp" // ESMVersion, MasterData + +namespace ESM4 +{ + class Reader; + class Writer; + +#pragma pack(push, 1) + struct Data + { + ESM::ESMVersion version; // File format version. + std::int32_t records; // Number of records + std::uint32_t nextObjectId; + }; +#pragma pack(pop) + + struct Header + { + std::uint32_t mFlags; // 0x01 esm, 0x80 localised strings + + Data mData; + std::string mAuthor; // Author's name + std::string mDesc; // File description + std::vector mMaster; + + std::vector mOverrides; // Skyrim only, cell children (ACHR, LAND, NAVM, PGRE, PHZD, REFR) + + // position in the vector = mod index of master files above + // value = adjusted mod index based on all the files loaded so far + //std::vector mModIndices; + + void load (Reader& reader); + //void save (Writer& writer); + }; +} + +#endif // ESM4_TES4_H diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp new file mode 100644 index 0000000000..7029222758 --- /dev/null +++ b/components/esm4/loadtree.cpp @@ -0,0 +1,85 @@ +/* + Copyright (C) 2016, 2018 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadtree.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Tree::Tree() : mFormId(0), mFlags(0), mBoundRadius(0.f) +{ + mEditorId.clear(); + mModel.clear(); + mLeafTexture.clear(); +} + +ESM4::Tree::~Tree() +{ +} + +void ESM4::Tree::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mLeafTexture); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_MODT: + 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: + { + //std::cout << "TREE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TREE::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Tree::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Tree::blank() +//{ +//} diff --git a/components/esm4/loadtree.hpp b/components/esm4/loadtree.hpp new file mode 100644 index 0000000000..2069f93d95 --- /dev/null +++ b/components/esm4/loadtree.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2016, 2018, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TREE_H +#define ESM4_TREE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Tree + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mModel; + + float mBoundRadius; + + std::string mLeafTexture; + + Tree(); + virtual ~Tree(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TREE_H diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp new file mode 100644 index 0000000000..ce281d4848 --- /dev/null +++ b/components/esm4/loadtxst.cpp @@ -0,0 +1,92 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadtxst.hpp" + +#include +#include // FIXME: testing only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::TextureSet::TextureSet() : mFormId(0), mFlags(0) +{ + mEditorId.clear(); + mDiffuse.clear(); + mNormalMap.clear(); + mEnvMask.clear(); + mToneMap.clear(); + mDetailMap.clear(); + mEnvMap.clear(); + mUnknown.clear(); + mSpecular.clear(); +} + +ESM4::TextureSet::~TextureSet() +{ +} + +void ESM4::TextureSet::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_TX00: reader.getZString(mDiffuse); break; + case ESM4::SUB_TX01: reader.getZString(mNormalMap); break; + case ESM4::SUB_TX02: reader.getZString(mEnvMask); break; + case ESM4::SUB_TX03: reader.getZString(mToneMap); break; + case ESM4::SUB_TX04: reader.getZString(mDetailMap); break; + case ESM4::SUB_TX05: reader.getZString(mEnvMap); break; + case ESM4::SUB_TX06: reader.getZString(mUnknown); break; + case ESM4::SUB_TX07: reader.getZString(mSpecular); break; + case ESM4::SUB_DNAM: + case ESM4::SUB_DODT: + case ESM4::SUB_OBND: // object bounds + { + //std::cout << "TXST " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::TXST::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::TextureSet::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::TextureSet::blank() +//{ +//} diff --git a/components/esm4/loadtxst.hpp b/components/esm4/loadtxst.hpp new file mode 100644 index 0000000000..cc81923e1f --- /dev/null +++ b/components/esm4/loadtxst.hpp @@ -0,0 +1,66 @@ +/* + Copyright (C) 2019, 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_TXST_H +#define ESM4_TXST_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct TextureSet + { + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + + std::string mDiffuse; // includes alpha info + std::string mNormalMap; // includes specular info (alpha channel) + std::string mEnvMask; + std::string mToneMap; + std::string mDetailMap; + std::string mEnvMap; + std::string mUnknown; + std::string mSpecular; + + TextureSet(); + virtual ~TextureSet(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_TXST_H diff --git a/components/esm4/loadweap.cpp b/components/esm4/loadweap.cpp new file mode 100644 index 0000000000..b56e01171c --- /dev/null +++ b/components/esm4/loadweap.cpp @@ -0,0 +1,188 @@ +/* + Copyright (C) 2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadweap.hpp" + +#include + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::Weapon::Weapon() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0), + mEnchantmentPoints(0), mEnchantment(0) +{ + mEditorId.clear(); + mFullName.clear(); + mModel.clear(); + mText.clear(); + mIcon.clear(); + mMiniIcon.clear(); +} + +ESM4::Weapon::~Weapon() +{ +} + +void ESM4::Weapon::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + std::uint32_t esmVer = reader.esmVersion(); + bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_DATA: + { + //if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) + if (subHdr.dataSize == 10) // FO3 has 15 bytes even though VER_094 + { + reader.get(mData.value); + reader.get(mData.weight); + reader.get(mData.damage); + } + else if (isFONV || subHdr.dataSize == 15) + { + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + reader.get(mData.damage); + reader.get(mData.clipSize); + } + else + { + reader.get(mData.type); + reader.get(mData.speed); + reader.get(mData.reach); + reader.get(mData.flags); + reader.get(mData.value); + reader.get(mData.health); + reader.get(mData.weight); + reader.get(mData.damage); + } + break; + } + case ESM4::SUB_MODL: reader.getZString(mModel); break; + case ESM4::SUB_ICON: reader.getZString(mIcon); break; + case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3 + case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break; + case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break; + case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break; + case ESM4::SUB_MODB: reader.get(mBoundRadius); break; + case ESM4::SUB_DESC: reader.getLocalizedString(mText); break; + case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break; + case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break; + case ESM4::SUB_MODT: + 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_MODS: + 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: // FO3 + case ESM4::SUB_MO4T: // FO3 + case ESM4::SUB_MO4S: // FO3 + 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_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_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 + { + //std::cout << "WEAP " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); + break; + } + default: + throw std::runtime_error("ESM4::WEAP::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + } +} + +//void ESM4::Weapon::save(ESM4::Writer& writer) const +//{ +//} + +//void ESM4::Weapon::blank() +//{ +//} diff --git a/components/esm4/loadweap.hpp b/components/esm4/loadweap.hpp new file mode 100644 index 0000000000..dae2da1e7e --- /dev/null +++ b/components/esm4/loadweap.hpp @@ -0,0 +1,96 @@ +/* + Copyright (C) 2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_WEAP_H +#define ESM4_WEAP_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct Weapon + { + struct Data + { + // type + // 0 = Blade One Hand + // 1 = Blade Two Hand + // 2 = Blunt One Hand + // 3 = Blunt Two Hand + // 4 = Staff + // 5 = Bow + std::uint32_t type; + float speed; + float reach; + std::uint32_t flags; + std::uint32_t value; // gold + std::uint32_t health; + float weight; + std::uint16_t damage; + std::uint8_t clipSize; // FO3/FONV only + + Data() : type(0), speed(0.f), reach(0.f), flags(0), value(0), + health(0), weight(0.f), damage(0), clipSize(0) {} + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + std::string mIcon; + std::string mMiniIcon; + + FormId mPickUpSound; + FormId mDropSound; + + float mBoundRadius; + + FormId mScriptId; + std::uint16_t mEnchantmentPoints; + FormId mEnchantment; + + Data mData; + + Weapon(); + virtual ~Weapon(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + + //void blank(); + }; +} + +#endif // ESM4_WEAP_H diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp new file mode 100644 index 0000000000..ec01556338 --- /dev/null +++ b/components/esm4/loadwrld.cpp @@ -0,0 +1,205 @@ +/* + Copyright (C) 2015-2016, 2018-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#include "loadwrld.hpp" + +#include +//#include // FIXME: debug only + +#include "reader.hpp" +//#include "writer.hpp" + +ESM4::World::World() : mFormId(0), mFlags(0), mParent(0), mWorldFlags(0), mClimate(0), mWater(0), + mLandLevel(0.f), mWaterLevel(0.f), // -2700.f and -14000.f for TES5 + mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mSound(0), mMusic(0), mParentUseFlags(0) +{ + mEditorId.clear(); + mFullName.clear(); + mMapFile.clear(); + + mMap.width = 0; + mMap.height = 0; + mMap.NWcellX = 0; + mMap.NWcellY = 0; + mMap.SEcellX = 0; + mMap.SEcellY = 0; + mMap.minHeight = 0.f; + mMap.maxHeight = 0.f; + mMap.initialPitch = 0.f; +} + +ESM4::World::~World() +{ +} + +void ESM4::World::load(ESM4::Reader& reader) +{ + mFormId = reader.hdr().record.id; + reader.adjustFormId(mFormId); + mFlags = reader.hdr().record.flags; + + // It should be possible to save the current world formId automatically while reading in + // the record header rather than doing it manually here but possibly less efficient (may + // need to check each record?). + // + // Alternatively it may be possible to figure it out by examining the group headers, but + // apparently the label field is not reliable so the parent world formid may have been + // corrupted by the use of ignore flag (TODO: should check to verify). + reader.setCurrWorld(mFormId); // save for CELL later + + std::uint32_t subSize = 0; // for XXXX sub record + + std::uint32_t esmVer = reader.esmVersion(); + //bool isTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100); + //bool isFONV = (esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134); + bool isTES5 = (esmVer == ESM::VER_094 || esmVer == ESM::VER_170); // WARN: FO3 is also VER_094 + bool usingDefaultLevels = true; + + while (reader.getSubRecordHeader()) + { + const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); + switch (subHdr.typeId) + { + case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; + case ESM4::SUB_WCTR: reader.get(mCenterCell); break; // Center cell, TES5 only + case ESM4::SUB_WNAM: reader.getFormId(mParent); break; + case ESM4::SUB_SNAM: reader.get(mSound); break; // sound, Oblivion only? + case ESM4::SUB_ICON: reader.getZString(mMapFile); break; // map filename, Oblivion only? + case ESM4::SUB_CNAM: reader.get(mClimate); break; + case ESM4::SUB_NAM2: reader.getFormId(mWater); break; + case ESM4::SUB_NAM0: + { + reader.get(mMinX); + reader.get(mMinY); + break; + } + case ESM4::SUB_NAM9: + { + reader.get(mMaxX); + reader.get(mMaxY); + break; + } + case ESM4::SUB_DATA: reader.get(mWorldFlags); break; + case ESM4::SUB_MNAM: + { + reader.get(mMap.width); + reader.get(mMap.height); + reader.get(mMap.NWcellX); + reader.get(mMap.NWcellY); + reader.get(mMap.SEcellX); + reader.get(mMap.SEcellY); + + if (subHdr.dataSize == 28) // Skyrim? + { + reader.get(mMap.minHeight); + reader.get(mMap.maxHeight); + reader.get(mMap.initialPitch); + } + + break; + } + case ESM4::SUB_DNAM: // defaults + { + reader.get(mLandLevel); // -2700.f for TES5 + reader.get(mWaterLevel); // -14000.f for TES5 + usingDefaultLevels = false; + + break; + } + // Only a few worlds in FO3 have music (I'm guessing 00090908 "explore" is the default?) + // 00090906 public WRLD: 00000A74 MegatonWorld + // 00090CE7 base WRLD: 0001A25D DCWorld18 (Arlington National Cemeteray) + // 00090CE7 base WRLD: 0001A266 DCWorld09 (The Mall) + // 00090CE7 base WRLD: 0001A267 DCWorld08 (Pennsylvania Avenue) + // 000BAD30 tranquilitylane WRLD: 000244A7 TranquilityLane + // 00090CE7 base WRLD: 000271C0 MonumentWorld (The Washington Monument) + // 00090907 dungeon WRLD: 0004C4D1 MamaDolcesWorld (Mama Dolce's Loading Yard) + // + // FONV has only 3 (note the different format, also can't find the files?): + // 00119D2E freeside\freeside_01.mp3 0010BEEA FreesideWorld (Freeside) + // 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: reader.getFormId(mMusic); break; + case ESM4::SUB_PNAM: reader.get(mParentUseFlags); break; + 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_MODL: + case ESM4::SUB_NAMA: + case ESM4::SUB_ONAM: + case ESM4::SUB_TNAM: + case ESM4::SUB_UNAM: + case ESM4::SUB_XWEM: + case ESM4::SUB_MODT: // from Dragonborn onwards? + 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 + { + //std::cout << "WRLD " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl; + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + break; + } + case ESM4::SUB_OFST: + { + if (subSize) + { + reader.skipSubRecordData(subSize); // special post XXXX + reader.updateRecordRead(subSize); // WARNING: manually update + subSize = 0; + } + else + reader.skipSubRecordData(); // FIXME: process the subrecord rather than skip + + break; + } + case ESM4::SUB_XXXX: + { + reader.get(subSize); + break; + } + default: + throw std::runtime_error("ESM4::WRLD::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + } + + if (isTES5 && usingDefaultLevels) + { + mLandLevel = -2700.f; + mWaterLevel = -14000.f; + } + } +} + +//void ESM4::World::save(ESM4::Writer& writer) const +//{ +//} diff --git a/components/esm4/loadwrld.hpp b/components/esm4/loadwrld.hpp new file mode 100644 index 0000000000..0105dc12f4 --- /dev/null +++ b/components/esm4/loadwrld.hpp @@ -0,0 +1,137 @@ +/* + Copyright (C) 2015-2016, 2018-2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_WRLD_H +#define ESM4_WRLD_H + +#include +#include +#include + +#include "common.hpp" + +namespace ESM4 +{ + class Reader; + class Writer; + + struct World + { + enum WorldFlags // TES4 TES5 + { // -------------------- ----------------- + WLD_Small = 0x01, // Small World Small World + WLD_NoFastTravel = 0x02, // Can't Fast Travel Can't Fast Travel + WLD_Oblivion = 0x04, // Oblivion worldspace + WLD_NoLODWater = 0x08, // No LOD Water + WLD_NoLandscpe = 0x10, // No LOD Water No Landscape + WLD_NoSky = 0x20, // No Sky + wLD_FixedDimension = 0x40, // Fixed Dimensions + WLD_NoGrass = 0x80 // No Grass + }; + + struct REFRcoord + { + FormId formId; + std::int16_t unknown1; + std::int16_t unknown2; + }; + + struct RNAMstruct + { + std::int16_t unknown1; + std::int16_t unknown2; + std::vector refrs; + }; + + //Map size struct 16 or 28 byte structure + struct Map + { + std::uint32_t width; // usable width of the map + std::uint32_t height; // usable height of the map + std::int16_t NWcellX; + std::int16_t NWcellY; + std::int16_t SEcellX; + std::int16_t SEcellY; + float minHeight; // Camera Data (default 50000), new as of Skyrim 1.8, purpose is not yet known. + float maxHeight; // Camera Data (default 80000) + float initialPitch; + }; + + FormId mFormId; // from the header + std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + + std::string mEditorId; + std::string mFullName; + FormId mParent; // parent worldspace formid + std::uint8_t mWorldFlags; + FormId mClimate; + FormId mWater; + float mLandLevel; + float mWaterLevel; + + Map mMap; + + std::int32_t mMinX; + std::int32_t mMinY; + std::int32_t mMaxX; + std::int32_t mMaxY; + + // ------ TES4 only ----- + + std::int32_t mSound; // 0 = no record, 1 = Public, 2 = Dungeon + std::string mMapFile; + + // ------ TES5 only ----- + + Grid mCenterCell; + RNAMstruct mData; + + // ---------------------- + FormId mMusic; + + // 0x01 use Land data + // 0x02 use LOD data + // 0x04 use Map data + // 0x08 use Water data + // 0x10 use Climate data + // 0x20 use Image Space data (Climate for TES5) + // 0x40 use SkyCell (TES5) + // 0x80 needs water adjustment (this isn't for parent I think? FONV only set for wastelandnv) + std::uint16_t mParentUseFlags; // FO3/FONV + + // cache formId's of children (e.g. CELL, ROAD) + std::vector mCells; + std::vector mRoads; + + World(); + virtual ~World(); + + virtual void load(ESM4::Reader& reader); + //virtual void save(ESM4::Writer& writer) const; + }; +} + +#endif // ESM4_WRLD_H diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp new file mode 100644 index 0000000000..f4fe8def68 --- /dev/null +++ b/components/esm4/reader.cpp @@ -0,0 +1,639 @@ +/* + Copyright (C) 2015-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#include "reader.hpp" + +#ifdef NDEBUG // FIXME: debugging only +#undef NDEBUG +#endif + +#undef DEBUG_GROUPSTACK + +#include +#include +#include +#include // for debugging +#include // for debugging +#include // for debugging + +#if defined(_MSC_VER) + #pragma warning (push) + #pragma warning (disable : 4706) + #include + #pragma warning (pop) +#else + #include +#endif +#include +#include +#include +#include +#include + +#include + +#include "formid.hpp" + +namespace ESM4 +{ + +ReaderContext::ReaderContext() : modIndex(0), recHeaderSize(sizeof(RecordHeader)), + filePos(0), recordRead(0), currWorld(0), currCell(0), cellGridValid(false) +{ + currCellGrid.cellId = 0; + currCellGrid.grid.x = 0; + currCellGrid.grid.y = 0; +} + +Reader::Reader(Files::IStreamPtr esmStream, const std::string& filename) + : mEncoder(nullptr), mFileSize(0), mStream(esmStream) +{ + // used by ESMReader only? + mCtx.filename = filename; + + mCtx.fileRead = 0; + mStream->seekg(0, mStream->end); + mFileSize = mStream->tellg(); + mStream->seekg(20); // go to the start but skip the "TES4" record header + + mSavedStream.reset(); + + // determine header size + std::uint32_t subRecName = 0; + mStream->read((char*)&subRecName, sizeof(subRecName)); + if (subRecName == 0x52444548) // "HEDR" + mCtx.recHeaderSize = sizeof(RecordHeader) - 4; // TES4 header size is 4 bytes smaller than TES5 header + else + mCtx.recHeaderSize = sizeof(RecordHeader); + + // restart from the beginning (i.e. "TES4" record header) + mStream->seekg(0, mStream->beg); +#if 0 + unsigned int esmVer = mHeader.mData.version.ui; + bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + //bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; + //bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; + + // TES4 header size is 4 bytes smaller than TES5 header + mCtx.recHeaderSize = isTes4 ? sizeof(ESM4::RecordHeader) - 4 : sizeof(ESM4::RecordHeader); +#endif + getRecordHeader(); + if (mCtx.recordHeader.record.typeId == REC_TES4) + { + mHeader.load(*this); + mCtx.fileRead += mCtx.recordHeader.record.dataSize; + + buildLStringIndex(); // for localised strings in Skyrim + } + else + fail("Unknown file format"); +} + +Reader::~Reader() +{ + close(); +} + +// Since the record data may have been compressed, it is not always possible to use seek() to +// go to a position of a sub record. +// +// The record header needs to be saved in the context or the header needs to be re-loaded after +// restoring the context. The latter option was chosen. +ReaderContext Reader::getContext() +{ + mCtx.filePos = mStream->tellg(); + mCtx.filePos -= mCtx.recHeaderSize; // update file position + return mCtx; +} + +// NOTE: Assumes that the caller has reopened the file if necessary +bool Reader::restoreContext(const ReaderContext& ctx) +{ + if (mSavedStream) // TODO: doesn't seem to ever happen + { + mStream = mSavedStream; + mSavedStream.reset(); + } + + mCtx.groupStack.clear(); // probably not necessary since it will be overwritten + mCtx = ctx; + mStream->seekg(ctx.filePos); // update file position + + return getRecordHeader(); +} + +void Reader::close() +{ + mStream.reset(); + //clearCtx(); + //mHeader.blank(); +} + +void Reader::openRaw(Files::IStreamPtr esmStream, const std::string& filename) +{ + close(); + + mStream = esmStream; + mCtx.filename = filename; + mCtx.fileRead = 0; + mStream->seekg(0, mStream->end); + mFileSize = mStream->tellg(); + mStream->seekg(0, mStream->beg); + +} + +void Reader::open(Files::IStreamPtr esmStream, const std::string &filename) +{ + openRaw(esmStream, filename); + + // should at least have the size of ESM3 record header (20 or 24 bytes for ESM4) + assert (mFileSize >= 16); + std::uint32_t modVer = 0; + if (getExact(modVer)) // get the first 4 bytes of the record header only + { + // FIXME: need to setup header/context + if (modVer == REC_TES4) + { + } + else + { + } + } + + throw std::runtime_error("Unknown file format"); // can't yet use fail() as mCtx is not setup +} + +void Reader::setRecHeaderSize(const std::size_t size) +{ + mCtx.recHeaderSize = size; +} + +// FIXME: only "English" strings supported for now +void Reader::buildLStringIndex() +{ + if ((mHeader.mFlags & Rec_ESM) == 0 || (mHeader.mFlags & Rec_Localized) == 0) + return; + + boost::filesystem::path p(mCtx.filename); + std::string filename = p.stem().filename().string(); + + buildLStringIndex("Strings/" + filename + "_English.STRINGS", Type_Strings); + buildLStringIndex("Strings/" + filename + "_English.ILSTRINGS", Type_ILStrings); + buildLStringIndex("Strings/" + filename + "_English.DLSTRINGS", Type_DLStrings); +} + +void Reader::buildLStringIndex(const std::string& stringFile, LocalizedStringType stringType) +{ + std::uint32_t numEntries; + std::uint32_t dataSize; + std::uint32_t stringId; + LStringOffset sp; + sp.type = stringType; + + // TODO: possibly check if the resource exists? + Files::IStreamPtr filestream = Files::IStreamPtr(Files::openConstrainedFileStream(stringFile.c_str())); + + filestream->seekg(0, std::ios::end); + std::size_t fileSize = filestream->tellg(); + filestream->seekg(0, std::ios::beg); + + switch (stringType) + { + case Type_Strings: mStrings = filestream; break; + case Type_ILStrings: mILStrings = filestream; break; + case Type_DLStrings: mDLStrings = filestream; break; + default: + throw std::runtime_error("ESM4::Reader::unknown localised string type"); + } + + filestream->read((char*)&numEntries, sizeof(numEntries)); + filestream->read((char*)&dataSize, sizeof(dataSize)); + std::size_t dataStart = fileSize - dataSize; + for (unsigned int i = 0; i < numEntries; ++i) + { + filestream->read((char*)&stringId, sizeof(stringId)); + filestream->read((char*)&sp.offset, sizeof(sp.offset)); + sp.offset += (std::uint32_t)dataStart; + mLStringIndex[stringId] = sp; + } + //assert (dataStart - filestream->tell() == 0 && "String file start of data section mismatch"); +} + +void Reader::getLocalizedString(std::string& str) +{ + if (!hasLocalizedStrings()) + return (void)getZString(str); + + std::uint32_t stringId; // FormId + get(stringId); + if (stringId) // TES5 FoxRace, BOOK + getLocalizedStringImpl(stringId, str); +} + +// FIXME: very messy and probably slow/inefficient +void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str) +{ + const std::map::const_iterator it = mLStringIndex.find(stringId); + + if (it != mLStringIndex.end()) + { + Files::IStreamPtr filestream; + + switch (it->second.type) + { + case Type_Strings: // no string size provided + { + filestream = mStrings; + filestream->seekg(it->second.offset); + + char ch; + std::vector data; + do { + filestream->read(&ch, sizeof(ch)); + data.push_back(ch); + } while (ch != 0); + + str = std::string(data.data()); + return; + } + case Type_ILStrings: filestream = mILStrings; break; + case Type_DLStrings: filestream = mDLStrings; break; + default: + throw std::runtime_error("ESM4::Reader::getLocalizedString unknown string type"); + } + + // get ILStrings or DLStrings (they provide string size) + filestream->seekg(it->second.offset); + std::uint32_t size = 0; + filestream->read((char*)&size, sizeof(size)); + getStringImpl(str, size, filestream, mEncoder, true); // expect null terminated string + } + else + throw std::runtime_error("ESM4::Reader::getLocalizedString localized string not found"); +} + +bool Reader::getRecordHeader() +{ + // FIXME: this seems very hacky but we may have skipped subrecords from within an inflated data block + if (/*mStream->eof() && */mSavedStream) + { + mStream = mSavedStream; + mSavedStream.reset(); + } + + mStream->read((char*)&mCtx.recordHeader, mCtx.recHeaderSize); + std::size_t bytesRead = (std::size_t)mStream->gcount(); + + // keep track of data left to read from the file + mCtx.fileRead += mCtx.recHeaderSize; + + mCtx.recordRead = 0; // for keeping track of sub records + + // After reading the record header we can cache a WRLD or CELL formId for convenient access later. + // FIXME: currently currWorld and currCell are set manually when loading the WRLD and CELL records + + // HACK: mCtx.groupStack.back() is updated before the record data are read/skipped + // N.B. the data must be fully read/skipped for this to work + if (mCtx.recordHeader.record.typeId != REC_GRUP && !mCtx.groupStack.empty()) + { + mCtx.groupStack.back().second += (std::uint32_t)mCtx.recHeaderSize + mCtx.recordHeader.record.dataSize; + + // keep track of data left to read from the file + mCtx.fileRead += mCtx.recordHeader.record.dataSize; + } + + return bytesRead == mCtx.recHeaderSize; +} + +void Reader::getRecordData(bool dump) +{ + std::uint32_t uncompressedSize = 0; + + if ((mCtx.recordHeader.record.flags & Rec_Compressed) != 0) + { + mStream->read(reinterpret_cast(&uncompressedSize), sizeof(std::uint32_t)); + + std::size_t recordSize = mCtx.recordHeader.record.dataSize - sizeof(std::uint32_t); + Bsa::MemoryInputStream compressedRecord(recordSize); + mStream->read(compressedRecord.getRawData(), recordSize); + std::istream *fileStream = (std::istream*)&compressedRecord; + mSavedStream = mStream; + + mCtx.recordHeader.record.dataSize = uncompressedSize - sizeof(uncompressedSize); + + std::shared_ptr memoryStreamPtr + = std::make_shared(uncompressedSize); + + boost::iostreams::filtering_streambuf inputStreamBuf; + inputStreamBuf.push(boost::iostreams::zlib_decompressor()); + inputStreamBuf.push(*fileStream); + + boost::iostreams::basic_array_sink sr(memoryStreamPtr->getRawData(), uncompressedSize); + boost::iostreams::copy(inputStreamBuf, sr); + + // For debugging only +//#if 0 +if (dump) +{ + std::ostringstream ss; + char* data = memoryStreamPtr->getRawData(); + for (unsigned int i = 0; i < uncompressedSize; ++i) + { + if (data[i] > 64 && data[i] < 91) + ss << (char)(data[i]) << " "; + else + ss << std::setfill('0') << std::setw(2) << std::hex << (int)(data[i]); + if ((i & 0x000f) == 0xf) + ss << "\n"; + else if (i < uncompressedSize-1) + ss << " "; + } + std::cout << ss.str() << std::endl; +} +//#endif + mStream = std::shared_ptr(memoryStreamPtr, (std::istream*)memoryStreamPtr.get()); + } +} + +void Reader::skipRecordData() +{ + assert (mCtx.recordRead <= mCtx.recordHeader.record.dataSize && "Skipping after reading more than available"); + mStream->ignore(mCtx.recordHeader.record.dataSize - mCtx.recordRead); + mCtx.recordRead = mCtx.recordHeader.record.dataSize; // for getSubRecordHeader() +} + +bool Reader::getSubRecordHeader() +{ + bool result = false; + // NOTE: some SubRecords have 0 dataSize (e.g. SUB_RDSD in one of REC_REGN records in Oblivion.esm). + // Also SUB_XXXX has zero dataSize and the following 4 bytes represent the actual dataSize + // - hence it require manual updtes to mCtx.recordRead via updateRecordRead() + // See ESM4::NavMesh and ESM4::World. + if (mCtx.recordHeader.record.dataSize - mCtx.recordRead >= sizeof(mCtx.subRecordHeader)) + { + result = getExact(mCtx.subRecordHeader); + // HACK: below assumes sub-record data will be read or skipped in full; + // this hack aims to avoid updating mCtx.recordRead each time anything is read + mCtx.recordRead += (sizeof(mCtx.subRecordHeader) + mCtx.subRecordHeader.dataSize); + } + else if (mCtx.recordRead > mCtx.recordHeader.record.dataSize) + { + // try to correct any overshoot, seek to the end of the expected data + // this will only work if mCtx.subRecordHeader.dataSize was fully read or skipped + // (i.e. it will only correct mCtx.subRecordHeader.dataSize being incorrect) + // TODO: not tested + std::uint32_t overshoot = (std::uint32_t)mCtx.recordRead - mCtx.recordHeader.record.dataSize; + + std::size_t pos = mStream->tellg(); + mStream->seekg(pos - overshoot); + + return false; + } + + return result; +} + +void Reader::skipSubRecordData() +{ + mStream->ignore(mCtx.subRecordHeader.dataSize); +} + +void Reader::skipSubRecordData(std::uint32_t size) +{ + mStream->ignore(size); +} + +void Reader::enterGroup() +{ +#ifdef DEBUG_GROUPSTACK + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Starting record group " + << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << std::endl; +#endif + // empty group if the group size is same as the header size + if (mCtx.recordHeader.group.groupSize == (std::uint32_t)mCtx.recHeaderSize) + { +#ifdef DEBUG_GROUPSTACK + std::cout << padding << "Ignoring record group " // FIXME: debugging only + << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) + << " (empty)" << std::endl; +#endif + if (!mCtx.groupStack.empty()) // top group may be empty (e.g. HAIR in Skyrim) + { + // don't put on the stack, exitGroupCheck() may not get called before recursing into this method + mCtx.groupStack.back().second += mCtx.recordHeader.group.groupSize; + exitGroupCheck(); + } + + return; // don't push an empty group, just return + } + + // push group + mCtx.groupStack.push_back(std::make_pair(mCtx.recordHeader.group, (std::uint32_t)mCtx.recHeaderSize)); +} + +void Reader::exitGroupCheck() +{ + if (mCtx.groupStack.empty()) + return; + + // pop finished groups (note reading too much is allowed here) + std::uint32_t lastGroupSize = mCtx.groupStack.back().first.groupSize; + while (mCtx.groupStack.back().second >= lastGroupSize) + { +#ifdef DEBUG_GROUPSTACK + GroupTypeHeader grp = mCtx.groupStack.back().first; // FIXME: grp is for debugging only +#endif + // try to correct any overshoot + // TODO: not tested + std::uint32_t overshoot = mCtx.groupStack.back().second - lastGroupSize; + if (overshoot > 0) + { + std::size_t pos = mStream->tellg(); + mStream->seekg(pos - overshoot); + } + + mCtx.groupStack.pop_back(); +#ifdef DEBUG_GROUPSTACK + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Finished record group " << printLabel(grp.label, grp.type) << std::endl; +#endif + // if the previous group was the final one no need to do below + if (mCtx.groupStack.empty()) + return; + + mCtx.groupStack.back().second += lastGroupSize; + lastGroupSize = mCtx.groupStack.back().first.groupSize; + + assert (lastGroupSize >= mCtx.groupStack.back().second && "Read more records than available"); +//#if 0 + if (mCtx.groupStack.back().second > lastGroupSize) // FIXME: debugging only + std::cerr << printLabel(mCtx.groupStack.back().first.label, + mCtx.groupStack.back().first.type) + << " read more records than available" << std::endl; +//#endif + } +} + +// WARNING: this method should be used after first calling enterGroup() +// else the method may try to dereference an element that does not exist +const GroupTypeHeader& Reader::grp(std::size_t pos) const +{ + assert (pos <= mCtx.groupStack.size()-1 && "ESM4::Reader::grp - exceeded stack depth"); + + return (*(mCtx.groupStack.end()-pos-1)).first; +} + +void Reader::skipGroupData() +{ + assert (!mCtx.groupStack.empty() && "Skipping group with an empty stack"); + + // subtract what was already read/skipped + std::uint32_t skipSize = mCtx.groupStack.back().first.groupSize - mCtx.groupStack.back().second; + + mStream->ignore(skipSize); + + // keep track of data left to read from the file + mCtx.fileRead += skipSize; + + mCtx.groupStack.back().second = mCtx.groupStack.back().first.groupSize; +} + +void Reader::skipGroup() +{ +#ifdef DEBUG_GROUPSTACK + std::string padding = ""; // FIXME: debugging only + padding.insert(0, mCtx.groupStack.size()*2, ' '); + std::cout << padding << "Skipping record group " + << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << std::endl; +#endif + // subtract the size of header already read before skipping + std::uint32_t skipSize = mCtx.recordHeader.group.groupSize - (std::uint32_t)mCtx.recHeaderSize; + mStream->ignore(skipSize); + + // keep track of data left to read from the file + mCtx.fileRead += skipSize; + + // NOTE: mCtx.groupStack.back().second already has mCtx.recHeaderSize from enterGroup() + if (!mCtx.groupStack.empty()) + mCtx.groupStack.back().second += mCtx.recordHeader.group.groupSize; +} + +const CellGrid& Reader::currCellGrid() const +{ + // Maybe should throw an exception instead? + assert (mCtx.cellGridValid && "Attempt to use an invalid cell grid"); + + return mCtx.currCellGrid; +} + +// NOTE: the parameter 'files' must have the file names in the loaded order +void Reader::updateModIndices(const std::vector& files) +{ + if (files.size() >= 0xff) + throw std::runtime_error("ESM4::Reader::updateModIndices too many files"); // 0xff is reserved + + // NOTE: this map is rebuilt each time this method is called (i.e. each time a file is loaded) + // Perhaps there is an opportunity to optimize this by saving the result somewhere. + // But then, the number of files is at most around 250 so perhaps keeping it simple might be better. + + // build a lookup map + std::unordered_map fileIndex; + + for (size_t i = 0; i < files.size(); ++i) // ATTENTION: assumes current file is not included + fileIndex[boost::to_lower_copy(files[i])] = i; + + mCtx.parentFileIndices.resize(mHeader.mMaster.size()); + for (unsigned int i = 0; i < mHeader.mMaster.size(); ++i) + { + // locate the position of the dependency in already loaded files + std::unordered_map::const_iterator it + = fileIndex.find(boost::to_lower_copy(mHeader.mMaster[i].name)); + + if (it != fileIndex.end()) + mCtx.parentFileIndices[i] = (std::uint32_t)((it->second << 24) & 0xff000000); + else + throw std::runtime_error("ESM4::Reader::updateModIndices required dependency file not loaded"); +#if 0 + std::cout << "Master Mod: " << mCtx.header.mMaster[i].name << ", " // FIXME: debugging only + << formIdToString(mCtx.parentFileIndices[i]) << std::endl; +#endif + } + + if (!mCtx.parentFileIndices.empty() && mCtx.parentFileIndices[0] != 0) + throw std::runtime_error("ESM4::Reader::updateModIndices base modIndex is not zero"); +} + +// ModIndex adjusted formId according to master file dependencies +// (see http://www.uesp.net/wiki/Tes4Mod:FormID_Fixup) +// NOTE: need to update modindex to parentFileIndices.size() before saving +// +// FIXME: probably should add a parameter to check for mCtx.header::mOverrides +// (ACHR, LAND, NAVM, PGRE, PHZD, REFR), but not sure what exactly overrides mean +// i.e. use the modindx of its master? +// FIXME: Apparently ModIndex '00' in an ESP means the object is defined in one of its masters. +// This means we may need to search multiple times to get the correct id. +// (see https://www.uesp.net/wiki/Tes4Mod:Formid#ModIndex_Zero) +void Reader::adjustFormId(FormId& id) +{ + if (mCtx.parentFileIndices.empty()) + return; + + std::size_t index = (id >> 24) & 0xff; + + if (index < mCtx.parentFileIndices.size()) + id = mCtx.parentFileIndices[index] | (id & 0x00ffffff); + else + id = mCtx.modIndex | (id & 0x00ffffff); +} + +bool Reader::getFormId(FormId& id) +{ + if (!getExact(id)) + return false; + + adjustFormId(id); + return true; +} + +void Reader::adjustGRUPFormId() +{ + adjustFormId(mCtx.recordHeader.group.label.value); +} + +[[noreturn]] void Reader::fail(const std::string& msg) +{ + std::stringstream ss; + + ss << "ESM Error: " << msg; + ss << "\n File: " << mCtx.filename; + ss << "\n Record: " << ESM::printName(mCtx.recordHeader.record.typeId); + ss << "\n Subrecord: " << ESM::printName(mCtx.subRecordHeader.typeId); + if (mStream.get()) + ss << "\n Offset: 0x" << std::hex << mStream->tellg(); + + throw std::runtime_error(ss.str()); +} + +} diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp new file mode 100644 index 0000000000..b3f1070495 --- /dev/null +++ b/components/esm4/reader.hpp @@ -0,0 +1,298 @@ +/* + Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + +*/ +#ifndef ESM4_READER_H +#define ESM4_READER_H + +#include +#include +#include + +#include "common.hpp" +#include "loadtes4.hpp" +#include "../esm/reader.hpp" + +namespace ESM4 { + // bytes read from group, updated by + // getRecordHeader() in advance + // | + // v + typedef std::vector > GroupStack; + + struct ReaderContext { + std::string filename; // in case we need to reopen to restore the context + std::uint32_t modIndex; // the sequential position of this file in the load order: + // 0x00 reserved, 0xFF in-game (see notes below) + + // position in the vector = mod index of master files above + // value = adjusted mod index based on all the files loaded so far + std::vector parentFileIndices; + + std::size_t recHeaderSize; // normally should be already set correctly, but just in + // case the file was re-opened. default = TES5 size, + // can be reduced for TES4 by setRecHeaderSize() + + std::size_t filePos; // assume that the record header will be re-read once + // the context is restored + + // for keeping track of things + std::size_t fileRead; // number of bytes read, incl. the current record + + GroupStack groupStack; // keep track of bytes left to find when a group is done + RecordHeader recordHeader; // header of the current record or group being processed + SubRecordHeader subRecordHeader; // header of the current sub record being processed + std::uint32_t recordRead; // bytes read from the sub records, incl. the current one + + FormId currWorld; // formId of current world - for grouping CELL records + FormId currCell; // formId of current cell + // FIXME: try to get rid of these two members, seem like massive hacks + CellGrid currCellGrid; // TODO: should keep a map of cell formids + bool cellGridValid; + + ReaderContext(); + }; + + class Reader : public ESM::Reader + { + Header mHeader; // ESM4 header + + ReaderContext mCtx; + + ToUTF8::Utf8Encoder* mEncoder; + + std::size_t mFileSize; + + Files::IStreamPtr mStream; + Files::IStreamPtr mSavedStream; // mStream is saved here while using deflated memory stream + + Files::IStreamPtr mStrings; + Files::IStreamPtr mILStrings; + Files::IStreamPtr mDLStrings; + + enum LocalizedStringType + { + Type_Strings = 0, + Type_ILStrings = 1, + Type_DLStrings = 2 + }; + + struct LStringOffset + { + LocalizedStringType type; + std::uint32_t offset; + }; + + std::map mLStringIndex; + + void buildLStringIndex(const std::string& stringFile, LocalizedStringType stringType); + + inline bool hasLocalizedStrings() const { return (mHeader.mFlags & Rec_Localized) != 0; } + + void getLocalizedStringImpl(const FormId stringId, std::string& str); + + // Close the file, resets all information. + // After calling close() the structure may be reused to load a new file. + //void close(); + + // Raw opening. Opens the file and sets everything up but doesn't parse the header. + void openRaw(Files::IStreamPtr esmStream, const std::string& filename); + + // Load ES file from a new stream, parses the header. + // Closes the currently open file first, if any. + void open(Files::IStreamPtr esmStream, const std::string& filename); + + Reader() = default; + + public: + + Reader(Files::IStreamPtr esmStream, const std::string& filename); + ~Reader(); + + // FIXME: should be private but ESMTool uses it + void openRaw(const std::string& filename) { + openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); + } + + void open(const std::string& filename) { + open(Files::openConstrainedFileStream (filename.c_str ()), filename); + } + + void close() final; + + inline bool isEsm4() const final { return true; } + + inline void setEncoder(ToUTF8::Utf8Encoder* encoder) final { mEncoder = encoder; }; + + const std::vector& getGameFiles() const final { return mHeader.mMaster; } + + inline int getRecordCount() const final { return mHeader.mData.records; } + inline const std::string getAuthor() const final { return mHeader.mAuthor; } + inline int getFormat() const final { return 0; }; // prob. not relevant for ESM4 + inline const std::string getDesc() const final { return mHeader.mDesc; } + + inline std::string getFileName() const final { return mCtx.filename; }; // not used + + inline bool hasMoreRecs() const final { return (mFileSize - mCtx.fileRead) > 0; } + + // Methods added for updating loading progress bars + inline std::size_t getFileSize() const { return mFileSize; } + inline std::size_t getFileOffset() const { return mStream->tellg(); } + + // Methods added for saving/restoring context + ReaderContext getContext(); // WARN: must be called immediately after reading the record header + + bool restoreContext(const ReaderContext& ctx); // returns the result of re-reading the header + + template + inline void get(T& t) { mStream->read((char*)&t, sizeof(T)); } + + template + bool getExact(T& t) { + mStream->read((char*)&t, sizeof(T)); + return mStream->gcount() == sizeof(T); // FIXME: try/catch block needed? + } + + // for arrays + inline bool get(void* p, std::size_t size) { + mStream->read((char*)p, size); + return mStream->gcount() == (std::streamsize)size; // FIXME: try/catch block needed? + } + + // NOTE: must be called before calling getRecordHeader() + void setRecHeaderSize(const std::size_t size); + + inline unsigned int esmVersion() const { return mHeader.mData.version.ui; } + inline unsigned int numRecords() const { return mHeader.mData.records; } + + void buildLStringIndex(); + void getLocalizedString(std::string& str); + + // Read 24 bytes of header. The caller can then decide whether to process or skip the data. + bool getRecordHeader(); + + inline const RecordHeader& hdr() const { return mCtx.recordHeader; } + + const GroupTypeHeader& grp(std::size_t pos = 0) const; + + // The object setting up this reader needs to supply the file's load order index + // so that the formId's in this file can be adjusted with the file (i.e. mod) index. + void setModIndex(std::uint32_t index) final { mCtx.modIndex = (index << 24) & 0xff000000; } + void updateModIndices(const std::vector& files); + + // Maybe should throw an exception if called when not valid? + const CellGrid& currCellGrid() const; + + inline bool hasCellGrid() const { return mCtx.cellGridValid; } + + // This is set while loading a CELL record (XCLC sub record) and invalidated + // each time loading a CELL (see clearCellGrid()) + inline void setCurrCellGrid(const CellGrid& currCell) { + mCtx.cellGridValid = true; + mCtx.currCellGrid = currCell; + } + + // FIXME: This is called each time a new CELL record is read. Rather than calling this + // methos explicitly, mCellGridValid should be set automatically somehow. + // + // Cell 2c143 is loaded immedicatly after 1bdb1 and can mistakely appear to have grid 0, 1. + inline void clearCellGrid() { mCtx.cellGridValid = false; } + + // Should be set at the beginning of a CELL load + inline void setCurrCell(FormId formId) { mCtx.currCell = formId; } + + inline FormId currCell() const { return mCtx.currCell; } + + // Should be set at the beginning of a WRLD load + inline void setCurrWorld(FormId formId) { mCtx.currWorld = formId; } + + inline FormId currWorld() const { return mCtx.currWorld; } + + // Get the data part of a record + // Note: assumes the header was read correctly and nothing else was read + void getRecordData(bool dump = false); + + // Skip the data part of a record + // Note: assumes the header was read correctly (partial skip is allowed) + void skipRecordData(); + + // Skip the remaining part of the group + // Note: assumes the header was read correctly and group was pushed onto the stack + void skipGroupData(); + + // Skip the group without pushing onto the stack + // Note: assumes the header was read correctly and group was not pushed onto the stack + // (expected to be used during development only while some groups are not yet supported) + void skipGroup(); + + // Read 6 bytes of header. The caller can then decide whether to process or skip the data. + bool getSubRecordHeader(); + + // Manally update (i.e. increase) the bytes read after SUB_XXXX + inline void updateRecordRead(std::uint32_t subSize) { mCtx.recordRead += subSize; } + + inline const SubRecordHeader& subRecordHeader() const { return mCtx.subRecordHeader; } + + // Skip the data part of a subrecord + // Note: assumes the header was read correctly and nothing else was read + void skipSubRecordData(); + + // Special for a subrecord following a XXXX subrecord + void skipSubRecordData(std::uint32_t size); + + // Get a subrecord of a particular type and data type + template + bool getSubRecord(const ESM4::SubRecordTypes type, T& t) + { + ESM4::SubRecordHeader hdr; + if (!getExact(hdr) || (hdr.typeId != type) || (hdr.dataSize != sizeof(T))) + return false; + + return get(t); + } + + // ModIndex adjusted formId according to master file dependencies + void adjustFormId(FormId& id); + + bool getFormId(FormId& id); + + void adjustGRUPFormId(); + + // Note: uses the string size from the subrecord header rather than checking null termination + bool getZString(std::string& str) { + return getStringImpl(str, mCtx.subRecordHeader.dataSize, mStream, mEncoder, true); + } + bool getString(std::string& str) { + return getStringImpl(str, mCtx.subRecordHeader.dataSize, mStream, mEncoder); + } + + void enterGroup(); + void exitGroupCheck(); + + // for debugging only + size_t stackSize() const { return mCtx.groupStack.size(); } + + // Used for error handling + [[noreturn]] void fail(const std::string& msg); + }; +} + +#endif // ESM4_READER_H diff --git a/components/esm4/records.hpp b/components/esm4/records.hpp new file mode 100644 index 0000000000..d212091adb --- /dev/null +++ b/components/esm4/records.hpp @@ -0,0 +1,74 @@ +#ifndef ESM4_RECORDS_H +#define ESM4_RECORDS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // ESM4_RECORDS_H diff --git a/components/esm4/reference.hpp b/components/esm4/reference.hpp new file mode 100644 index 0000000000..5ac94b8519 --- /dev/null +++ b/components/esm4/reference.hpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2020 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + +*/ +#ifndef ESM4_REFERENCE_H +#define ESM4_REFERENCE_H + +#include +#include + +#include "formid.hpp" + +namespace ESM4 +{ +#pragma pack(push, 1) + struct Vector3 + { + float x; + float y; + float z; + }; + + // REFR, ACHR, ACRE + struct Placement + { + Vector3 pos; + Vector3 rot; // angles are in radian, rz applied first and rx applied last + }; + + // REFR, ACHR, ACRE + struct EnableParent + { + FormId parent; + std::uint32_t flags; //0x0001 = Set Enable State Opposite Parent, 0x0002 = Pop In + }; +#pragma pack(pop) + + struct LODReference + { + FormId baseObj; + Placement placement; + float scale; + }; +} + +#endif // ESM4_REFERENCE_H diff --git a/components/esm4/script.hpp b/components/esm4/script.hpp new file mode 100644 index 0000000000..8ba8c0025c --- /dev/null +++ b/components/esm4/script.hpp @@ -0,0 +1,384 @@ +/* + Copyright (C) 2020-2021 cc9cii + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + cc9cii cc9c@iinet.net.au + + Much of the information on the data structures are based on the information + from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by + trial & error. See http://en.uesp.net/wiki for details. + + Also see https://tes5edit.github.io/fopdoc/ for FO3/FONV specific details. + +*/ +#ifndef ESM4_SCRIPT_H +#define ESM4_SCRIPT_H + +#include +#include +#include + +namespace ESM4 +{ + enum EmotionType + { + EMO_Neutral = 0, + EMO_Anger = 1, + EMO_Disgust = 2, + EMO_Fear = 3, + EMO_Sad = 4, + EMO_Happy = 5, + EMO_Surprise = 6, + EMO_Pained = 7 // FO3/FONV + }; + + enum ConditionTypeAndFlag + { + // flag + CTF_Combine = 0x01, + CTF_RunOnTarget = 0x02, + CTF_UseGlobal = 0x04, + // condition + CTF_EqualTo = 0x00, + CTF_NotEqualTo = 0x20, + CTF_GreaterThan = 0x40, + CTF_GrThOrEqTo = 0x60, + CTF_LessThan = 0x80, + CTF_LeThOrEqTo = 0xA0 + }; + + enum FunctionIndices + { + FUN_GetDistance = 1, + FUN_GetLocked = 5, + FUN_GetPos = 6, + FUN_GetAngle = 8, + FUN_GetStartingPos = 10, + FUN_GetStartingAngle = 11, + FUN_GetSecondsPassed = 12, + FUN_GetActorValue = 14, + FUN_GetCurrentTime = 18, + FUN_GetScale = 24, + FUN_IsMoving = 25, + FUN_IsTurning = 26, + FUN_GetLineOfSight = 27, + FUN_GetIsInSameCell = 32, + FUN_GetDisabled = 35, + FUN_GetMenuMode = 36, + FUN_GetDisease = 39, + FUN_GetVampire = 40, + FUN_GetClothingValue = 41, + FUN_SameFaction = 42, + FUN_SameRace = 43, + FUN_SameSex = 44, + FUN_GetDetected = 45, + FUN_GetDead = 46, + FUN_GetItemCount = 47, + FUN_GetGold = 48, + FUN_GetSleeping = 49, + FUN_GetTalkedToPC = 50, + FUN_GetScriptVariable = 53, + FUN_GetQuestRunning = 56, + FUN_GetStage = 58, + FUN_GetStageDone = 59, + FUN_GetFactionRankDifference = 60, + FUN_GetAlarmed = 61, + FUN_IsRaining = 62, + FUN_GetAttacked = 63, + FUN_GetIsCreature = 64, + FUN_GetLockLevel = 65, + FUN_GetShouldAttack = 66, + FUN_GetInCell = 67, + FUN_GetIsClass = 68, + FUN_GetIsRace = 69, + FUN_GetIsSex = 70, + FUN_GetInFaction = 71, + FUN_GetIsID = 72, + FUN_GetFactionRank = 73, + FUN_GetGlobalValue = 74, + FUN_IsSnowing = 75, + FUN_GetDisposition = 76, + FUN_GetRandomPercent = 77, + FUN_GetQuestVariable = 79, + FUN_GetLevel = 80, + FUN_GetArmorRating = 81, + FUN_GetDeadCount = 84, + FUN_GetIsAlerted = 91, + FUN_GetPlayerControlsDisabled = 98, + FUN_GetHeadingAngle = 99, + FUN_IsWeaponOut = 101, + FUN_IsTorchOut = 102, + FUN_IsShieldOut = 103, + FUN_IsFacingUp = 106, + FUN_GetKnockedState = 107, + FUN_GetWeaponAnimType = 108, + FUN_IsWeaponSkillType = 109, + FUN_GetCurrentAIPackage = 110, + FUN_IsWaiting = 111, + FUN_IsIdlePlaying = 112, + FUN_GetMinorCrimeCount = 116, + FUN_GetMajorCrimeCount = 117, + FUN_GetActorAggroRadiusViolated = 118, + FUN_GetCrime = 122, + FUN_IsGreetingPlayer = 123, + FUN_IsGuard = 125, + FUN_HasBeenEaten = 127, + FUN_GetFatiguePercentage = 128, + FUN_GetPCIsClass = 129, + FUN_GetPCIsRace = 130, + FUN_GetPCIsSex = 131, + FUN_GetPCInFaction = 132, + FUN_SameFactionAsPC = 133, + FUN_SameRaceAsPC = 134, + FUN_SameSexAsPC = 135, + FUN_GetIsReference = 136, + FUN_IsTalking = 141, + FUN_GetWalkSpeed = 142, + FUN_GetCurrentAIProcedure = 143, + FUN_GetTrespassWarningLevel = 144, + FUN_IsTrespassing = 145, + FUN_IsInMyOwnedCell = 146, + FUN_GetWindSpeed = 147, + FUN_GetCurrentWeatherPercent = 148, + FUN_GetIsCurrentWeather = 149, + FUN_IsContinuingPackagePCNear = 150, + FUN_CanHaveFlames = 153, + FUN_HasFlames = 154, + FUN_GetOpenState = 157, + FUN_GetSitting = 159, + FUN_GetFurnitureMarkerID = 160, + FUN_GetIsCurrentPackage = 161, + FUN_IsCurrentFurnitureRef = 162, + FUN_IsCurrentFurnitureObj = 163, + FUN_GetDayofWeek = 170, + FUN_GetTalkedToPCParam = 172, + FUN_IsPCSleeping = 175, + FUN_IsPCAMurderer = 176, + FUN_GetDetectionLevel = 180, + FUN_GetEquipped = 182, + FUN_IsSwimming = 185, + FUN_GetAmountSoldStolen = 190, + FUN_GetIgnoreCrime = 192, + FUN_GetPCExpelled = 193, + FUN_GetPCFactionMurder = 195, + FUN_GetPCEnemyofFaction = 197, + FUN_GetPCFactionAttack = 199, + FUN_GetDestroyed = 203, + FUN_HasMagicEffect = 214, + FUN_GetDefaultOpen = 215, + FUN_GetAnimAction = 219, + FUN_IsSpellTarget = 223, + FUN_GetVATSMode = 224, + FUN_GetPersuasionNumber = 225, + FUN_GetSandman = 226, + FUN_GetCannibal = 227, + FUN_GetIsClassDefault = 228, + FUN_GetClassDefaultMatch = 229, + FUN_GetInCellParam = 230, + FUN_GetVatsTargetHeight = 235, + FUN_GetIsGhost = 237, + FUN_GetUnconscious = 242, + FUN_GetRestrained = 244, + FUN_GetIsUsedItem = 246, + FUN_GetIsUsedItemType = 247, + FUN_GetIsPlayableRace = 254, + FUN_GetOffersServicesNow = 255, + FUN_GetUsedItemLevel = 258, + FUN_GetUsedItemActivate = 259, + FUN_GetBarterGold = 264, + FUN_IsTimePassing = 265, + FUN_IsPleasant = 266, + FUN_IsCloudy = 267, + FUN_GetArmorRatingUpperBody = 274, + FUN_GetBaseActorValue = 277, + FUN_IsOwner = 278, + FUN_IsCellOwner = 280, + FUN_IsHorseStolen = 282, + FUN_IsLeftUp = 285, + FUN_IsSneaking = 286, + FUN_IsRunning = 287, + FUN_GetFriendHit = 288, + FUN_IsInCombat = 289, + FUN_IsInInterior = 300, + FUN_IsWaterObject = 304, + FUN_IsActorUsingATorch = 306, + FUN_IsXBox = 309, + FUN_GetInWorldspace = 310, + FUN_GetPCMiscStat = 312, + FUN_IsActorEvil = 313, + FUN_IsActorAVictim = 314, + FUN_GetTotalPersuasionNumber = 315, + FUN_GetIdleDoneOnce = 318, + FUN_GetNoRumors = 320, + FUN_WhichServiceMenu = 323, + FUN_IsRidingHorse = 327, + FUN_IsInDangerousWater = 332, + FUN_GetIgnoreFriendlyHits = 338, + FUN_IsPlayersLastRiddenHorse = 339, + FUN_IsActor = 353, + FUN_IsEssential = 354, + FUN_IsPlayerMovingIntoNewSpace = 358, + FUN_GetTimeDead = 361, + FUN_GetPlayerHasLastRiddenHorse = 362, + FUN_IsChild = 365, + FUN_GetLastPlayerAction = 367, + FUN_IsPlayerActionActive = 368, + FUN_IsTalkingActivatorActor = 370, + FUN_IsInList = 372, + FUN_GetHasNote = 382, + FUN_GetHitLocation = 391, + FUN_IsPC1stPerson = 392, + FUN_GetCauseofDeath = 397, + FUN_IsLimbGone = 398, + FUN_IsWeaponInList = 399, + FUN_HasFriendDisposition = 403, + FUN_GetVATSValue = 408, + FUN_IsKiller = 409, + FUN_IsKillerObject = 410, + FUN_GetFactionCombatReaction = 411, + FUN_Exists = 415, + FUN_GetGroupMemberCount = 416, + FUN_GetGroupTargetCount = 417, + FUN_GetObjectiveCompleted = 420, + FUN_GetObjectiveDisplayed = 421, + FUN_GetIsVoiceType = 427, + FUN_GetPlantedExplosive = 428, + FUN_IsActorTalkingThroughActivator = 430, + FUN_GetHealthPercentage = 431, + FUN_GetIsObjectType = 433, + FUN_GetDialogueEmotion = 435, + FUN_GetDialogueEmotionValue = 436, + FUN_GetIsCreatureType = 438, + FUN_GetInZone = 446, + FUN_HasPerk = 449, + FUN_GetFactionRelation = 450, + FUN_IsLastIdlePlayed = 451, + FUN_GetPlayerTeammate = 454, + FUN_GetPlayerTeammateCount = 455, + FUN_GetActorCrimePlayerEnemy = 459, + FUN_GetActorFactionPlayerEnemy = 460, + FUN_IsPlayerTagSkill = 462, + FUN_IsPlayerGrabbedRef = 464, + FUN_GetDestructionStage = 471, + FUN_GetIsAlignment = 474, + FUN_GetThreatRatio = 478, + FUN_GetIsUsedItemEquipType = 480, + FUN_GetConcussed = 489, + FUN_GetMapMarkerVisible = 492, + FUN_GetPermanentActorValue = 495, + FUN_GetKillingBlowLimb = 496, + FUN_GetWeaponHealthPerc = 500, + FUN_GetRadiationLevel = 503, + FUN_GetLastHitCritical = 510, + FUN_IsCombatTarget = 515, + FUN_GetVATSRightAreaFree = 518, + FUN_GetVATSLeftAreaFree = 519, + FUN_GetVATSBackAreaFree = 520, + FUN_GetVATSFrontAreaFree = 521, + FUN_GetIsLockBroken = 522, + FUN_IsPS3 = 523, + FUN_IsWin32 = 524, + FUN_GetVATSRightTargetVisible = 525, + FUN_GetVATSLeftTargetVisible = 526, + FUN_GetVATSBackTargetVisible = 527, + FUN_GetVATSFrontTargetVisible = 528, + FUN_IsInCriticalStage = 531, + FUN_GetXPForNextLevel = 533, + FUN_GetQuestCompleted = 546, + FUN_IsGoreDisabled = 550, + FUN_GetSpellUsageNum = 555, + FUN_GetActorsInHigh = 557, + FUN_HasLoaded3D = 558, + FUN_GetReputation = 573, + FUN_GetReputationPct = 574, + FUN_GetReputationThreshold = 575, + FUN_IsHardcore = 586, + FUN_GetForceHitReaction = 601, + FUN_ChallengeLocked = 607, + FUN_GetCasinoWinningStage = 610, + FUN_PlayerInRegion = 612, + FUN_GetChallengeCompleted = 614, + FUN_IsAlwaysHardcore = 619 + }; + +#pragma pack(push, 1) + struct TargetResponseData + { + std::uint32_t emoType; // EmotionType + std::int32_t emoValue; + std::uint32_t unknown1; + std::uint32_t responseNo; // 1 byte + padding + // below FO3/FONV + FormId sound; // when 20 bytes usually 0 but there are exceptions (FO3 INFO FormId = 0x0002241f) + std::uint32_t flags; // 1 byte + padding (0x01 = use emotion anim) + }; + + struct TargetCondition + { + std::uint32_t condition; // ConditionTypeAndFlag + padding + float comparison; // WARN: can be GLOB FormId if flag set + std::uint32_t functionIndex; + std::uint32_t param1; // FIXME: if formid needs modindex adjustment or not? + std::uint32_t param2; + std::uint32_t runOn; // 0 subject, 1 target, 2 reference, 3 combat target, 4 linked reference + // below FO3/FONV/TES5 + FormId reference; + }; + + struct ScriptHeader + { + std::uint32_t unused; + std::uint32_t refCount; + std::uint32_t compiledSize; + std::uint32_t variableCount; + std::uint16_t type; // 0 object, 1 quest, 0x100 effect + std::uint16_t flag; // 0x01 enabled + }; +#pragma pack(pop) + + struct ScriptLocalVariableData + { + // SLSD + std::uint32_t index; + std::uint32_t unknown1; + std::uint32_t unknown2; + std::uint32_t unknown3; + std::uint32_t type; + std::uint32_t unknown4; + // SCVR + std::string variableName; + + void clear() { + index = 0; + type = 0; + variableName.clear(); + } + }; + + struct ScriptDefinition + { + ScriptHeader scriptHeader; + // SDCA compiled source + std::string scriptSource; + std::vector localVarData; + std::vector localRefVarIndex; + FormId globReference; + }; +} + +#endif // ESM4_SCRIPT_H diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index f7dc33fcbf..04edfda09d 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -84,6 +84,15 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) // is also ok.) assert(input[size] == 0); + std::string inputString(input, size); + std::string output; + toUtf8(inputString, output, size); + + return output; +} + +void Utf8Encoder::toUtf8(std::string& input, std::string& output, size_t size) +{ // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding // shares its first 128 values (0-127) with ASCII. There are no plans @@ -93,29 +102,36 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) // Compute output length, and check for pure ascii input at the same // time. bool ascii; - size_t outlen = getLength(input, ascii); + size_t outlen = getLength(input.c_str(), ascii); // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input, outlen); + { + std::swap(input, output); + return; + } // Make sure the output is large enough - resize(outlen); - char *out = &mOutput[0]; + if (output.size() <= outlen) + // Add some extra padding to reduce the chance of having to resize + // again later. + output.resize(3*outlen); + + // And make sure the string is zero terminated + output[outlen] = 0; + char *in = &input[0]; + char *out = &output[0]; // Translate - while (*input) - copyFromArray(*(input++), out); + while (*in) + copyFromArray(*(in++), out); // Make sure that we wrote the correct number of bytes - assert((out-&mOutput[0]) == (int)outlen); + assert((out-&output[0]) == (int)outlen); // And make extra sure the output is null terminated - assert(mOutput.size() > outlen); - assert(mOutput[outlen] == 0); - - // Return a string - return std::string(&mOutput[0], outlen); + assert(output.size() > outlen); + assert(output[outlen] == 0); } std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index d8c9f09d5d..23dc09c06e 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -34,6 +34,9 @@ namespace ToUTF8 return getUtf8(str.c_str(), str.size()); } + // Convert input to UTF8 to the given output string + void toUtf8(std::string& input, std::string& output, size_t size); + std::string getLegacyEnc(const char *input, size_t size); inline std::string getLegacyEnc(const std::string &str) { diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index ef0f432075..37068fd70d 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -53,13 +53,14 @@ namespace Translation if (!line.empty()) { - line = mEncoder->getUtf8(line); + std::string utf8Line; + mEncoder->toUtf8(line, utf8Line, line.size()); - size_t tab_pos = line.find('\t'); - if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) + size_t tab_pos = utf8Line.find('\t'); + if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < utf8Line.size() - 1) { - std::string key = line.substr(0, tab_pos); - std::string value = line.substr(tab_pos + 1); + std::string key = utf8Line.substr(0, tab_pos); + std::string value = utf8Line.substr(tab_pos + 1); if (!key.empty() && !value.empty()) container.insert(std::make_pair(key, value)); From 8113620dce5ac188347d688e7fb5894cbeb5c14b Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 30 Jan 2022 20:26:35 +0100 Subject: [PATCH 2023/2859] handle a few wearnings raised as errors --- components/esm4/loadpgrd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index a0bfc9b119..c9c3048605 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -115,7 +115,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) for (std::size_t i = 0; i < numForeign; ++i) { reader.get(mForeign.at(i)); - mForeign.at(i).localNode;// &= 0xffff; // some have junk high bits (maybe flags?) + // mForeign.at(i).localNode;// &= 0xffff; // some have junk high bits (maybe flags?) } break; From 899199c8ed0eb9c13658e1fd44b8e2f3d00c7697 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 31 Jan 2022 22:40:27 +0100 Subject: [PATCH 2024/2859] Add range to "aux_util.findNearestTo". --- files/builtin_scripts/openmw_aux/util.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/files/builtin_scripts/openmw_aux/util.lua b/files/builtin_scripts/openmw_aux/util.lua index 8d4f7c1fd7..c442178b5e 100644 --- a/files/builtin_scripts/openmw_aux/util.lua +++ b/files/builtin_scripts/openmw_aux/util.lua @@ -13,19 +13,24 @@ local aux_util = {} -- @function [parent=#util] findNearestTo -- @param openmw.util#Vector3 point -- @param openmw.core#ObjectList objectList +-- @param #number minDist (optional) ignore objects that are closer than minDist +-- @param #number maxDist (optional) ignore objects that are farther than maxDist -- @return openmw.core#GameObject, #number the nearest object and the distance -function aux_util.findNearestTo(point, objectList) +function aux_util.findNearestTo(point, objectList, minDist, maxDist) local res = nil local resDist = nil + local minDist = minDist or 0 for i = 1, #objectList do local obj = objectList[i] local dist = (obj.position - point):length() - if i == 1 or dist < resDist then + if dist >= minDist and (not res or dist < resDist) then res = obj resDist = dist end end - return res, resDist + if res and (not maxDist or resDist <= maxDist) then + return res, resDist + end end return aux_util From c31dedb89cd9391b4700f6f1a7f26eee724a7969 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 1 Feb 2022 18:47:20 +0000 Subject: [PATCH 2025/2859] Implement Yaw, Pitch and Use (attack / cast spell) in Lua self.controls --- apps/openmw/mwbase/luamanager.hpp | 4 +++- apps/openmw/mwlua/localscripts.cpp | 4 +++- apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 13 ++++++---- apps/openmw/mwmechanics/aicombat.cpp | 12 +++++----- apps/openmw/mwmechanics/aicombat.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 29 ++++++++++++----------- apps/openmw/mwmechanics/character.hpp | 6 +++-- apps/openmw/mwmechanics/creaturestats.cpp | 1 + apps/openmw/mwmechanics/creaturestats.hpp | 5 ++-- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwworld/player.cpp | 9 +------ apps/openmw/mwworld/player.hpp | 2 -- files/lua_api/openmw/self.lua | 4 +++- 14 files changed, 50 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index cc479a2937..8c0c5d61de 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -56,7 +56,9 @@ namespace MWBase bool mRun = false; float mMovement = 0; float mSideMovement = 0; - float mTurn = 0; + float mPitchChange = 0; + float mYawChange = 0; + int mUse = 0; }; virtual ActorControls* getActorControls(const MWWorld::Ptr&) const = 0; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 98cf0a0b1a..918970dc54 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -27,9 +27,11 @@ namespace MWLua [](ActorControls& c, const TYPE& v) { c.FIELD = v; c.mChanged = true; }) controls["movement"] = CONTROL(float, mMovement); controls["sideMovement"] = CONTROL(float, mSideMovement); - controls["turn"] = CONTROL(float, mTurn); + controls["pitchChange"] = CONTROL(float, mPitchChange); + controls["yawChange"] = CONTROL(float, mYawChange); controls["run"] = CONTROL(bool, mRun); controls["jump"] = CONTROL(bool, mJump); + controls["use"] = CONTROL(int, mUse); #undef CONTROL sol::usertype selfAPI = diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index cf60e00728..309ab51c08 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -49,7 +49,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 14; + api["API_REVISION"] = 15; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7c94c4e604..c567ea464c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1401,9 +1401,6 @@ namespace MWMechanics // AI processing is only done within given distance to the player. bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange; - if (isPlayer) - ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell()); - // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player. if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead() || !iter->first.getClass().getCreatureStats(iter->first).getAiSequence().isInCombat() @@ -1524,25 +1521,31 @@ namespace MWMechanics CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); float speedFactor = isPlayer ? 1.f : mov.mSpeedFactor; osg::Vec2f movement = osg::Vec2f(mov.mPosition[0], mov.mPosition[1]) * speedFactor; + float rotationX = mov.mRotation[0]; float rotationZ = mov.mRotation[2]; bool jump = mov.mPosition[2] == 1; bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run); + bool attackingOrSpell = stats.getAttackingOrSpell(); if (luaControls->mChanged) { mov.mPosition[0] = luaControls->mSideMovement; mov.mPosition[1] = luaControls->mMovement; mov.mPosition[2] = luaControls->mJump ? 1 : 0; + mov.mRotation[0] = luaControls->mPitchChange; mov.mRotation[1] = 0; - mov.mRotation[2] = luaControls->mTurn; + mov.mRotation[2] = luaControls->mYawChange; mov.mSpeedFactor = osg::Vec2(luaControls->mMovement, luaControls->mSideMovement).length(); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->mRun); + stats.setAttackingOrSpell(luaControls->mUse == 1); luaControls->mChanged = false; } luaControls->mSideMovement = movement.x(); luaControls->mMovement = movement.y(); - luaControls->mTurn = rotationZ; + luaControls->mPitchChange = rotationX; + luaControls->mYawChange = rotationZ; luaControls->mJump = jump; luaControls->mRun = runFlag; + luaControls->mUse = attackingOrSpell ? luaControls->mUse | 1 : luaControls->mUse & ~1; } } } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 46f04b6f97..83ff795545 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -141,7 +141,7 @@ namespace MWMechanics if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); if (storage.mRotateMove) return false; - storage.updateAttack(characterController); + storage.updateAttack(actor, characterController); } else { @@ -168,7 +168,7 @@ namespace MWMechanics if (!canFight(actor, target)) { storage.stopAttack(); - characterController.setAttackingOrSpell(false); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); storage.mActionCooldown = 0.f; // Continue combat if target is player or player follower/escorter and an attack has been attempted const std::list& playerFollowersAndEscorters = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer()); @@ -299,7 +299,7 @@ namespace MWMechanics { storage.mUseCustomDestination = false; storage.stopAttack(); - characterController.setAttackingOrSpell(false); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); currentAction.reset(new ActionFlee()); actionCooldown = currentAction->getActionCooldown(); storage.startFleeing(); @@ -575,7 +575,7 @@ namespace MWMechanics if (mAttackCooldown <= 0) { mAttack = true; // attack starts just now - characterController.setAttackingOrSpell(true); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(true); if (!distantCombat) characterController.setAIAttackType(chooseBestAttack(weapon)); @@ -603,13 +603,13 @@ namespace MWMechanics } } - void AiCombatStorage::updateAttack(CharacterController& characterController) + void AiCombatStorage::updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController) { if (mAttack && (characterController.getAttackStrength() >= mStrength || characterController.readyToPrepareAttack())) { mAttack = false; } - characterController.setAttackingOrSpell(mAttack); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); } void AiCombatStorage::stopAttack() diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 34e726412c..c783994b97 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -88,7 +88,7 @@ namespace MWMechanics void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, const ESM::Weapon* weapon, bool distantCombat); - void updateAttack(CharacterController& characterController); + void updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController); void stopAttack(); void startFleeing(); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6c33f86c76..0f8e2ebd97 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -854,7 +854,6 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSecondsOfSwimming(0) , mSecondsOfRunning(0) , mTurnAnimationThreshold(0) - , mAttackingOrSpell(false) , mCastingManualSpell(false) , mTimeUntilWake(0.f) , mIsMovingBackward(false) @@ -1138,7 +1137,7 @@ bool CharacterController::updateCreatureState() mAnimation->disable(mCurrentWeapon); } - if(mAttackingOrSpell) + if(getAttackingOrSpell()) { if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) { @@ -1202,7 +1201,7 @@ bool CharacterController::updateCreatureState() } } - mAttackingOrSpell = false; + setAttackingOrSpell(false); } bool animPlaying = mAnimation->getInfo(mCurrentWeapon); @@ -1277,11 +1276,10 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { forcestateupdate = true; mUpperBodyState = UpperCharState_WeapEquiped; - mAttackingOrSpell = false; + setAttackingOrSpell(false); mAnimation->disable(mCurrentWeapon); mAnimation->showWeapons(true); - if (mPtr == getPlayer()) - MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); + stats.setAttackingOrSpell(false); } if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) @@ -1456,7 +1454,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) float complete; bool animPlaying; ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; - if(mAttackingOrSpell) + if(getAttackingOrSpell()) { MWWorld::Ptr player = getPlayer(); @@ -1478,11 +1476,9 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation - mAttackingOrSpell = false; + setAttackingOrSpell(false); if (mPtr == player) { - MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); - // For the player, set the spell we want to cast // This has to be done at the start of the casting animation, // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) @@ -1791,7 +1787,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) // Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be random. float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack"); - if (mAttackingOrSpell || minAttackTime == maxAttackTime) + if (getAttackingOrSpell() || minAttackTime == maxAttackTime) { start = mAttackType+" min attack"; stop = mAttackType+" max attack"; @@ -2647,7 +2643,7 @@ void CharacterController::forceStateUpdate() // Make sure we canceled the current attack or spellcasting, // because we disabled attack animations anyway. mCastingManualSpell = false; - mAttackingOrSpell = false; + setAttackingOrSpell(false); if (mUpperBodyState != UpperCharState_Nothing) mUpperBodyState = UpperCharState_WeapEquiped; @@ -2845,12 +2841,12 @@ bool CharacterController::isRunning() const void CharacterController::setAttackingOrSpell(bool attackingOrSpell) { - mAttackingOrSpell = attackingOrSpell; + mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell); } void CharacterController::castSpell(const std::string& spellId, bool manualSpell) { - mAttackingOrSpell = true; + setAttackingOrSpell(true); mCastingManualSpell = manualSpell; ActionSpell action = ActionSpell(spellId); action.prepare(mPtr); @@ -2894,6 +2890,11 @@ float CharacterController::getAttackStrength() const return mAttackStrength; } +bool CharacterController::getAttackingOrSpell() +{ + return mPtr.getClass().getCreatureStats(mPtr).getAttackingOrSpell(); +} + void CharacterController::setActive(int active) { mAnimation->setActive(active); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index adeaa739af..1647980541 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -188,7 +188,6 @@ class CharacterController : public MWRender::Animation::TextKeyListener std::string mAttackType; // slash, chop or thrust - bool mAttackingOrSpell; bool mCastingManualSpell; float mTimeUntilWake; @@ -235,6 +234,10 @@ class CharacterController : public MWRender::Animation::TextKeyListener std::string getWeaponAnimation(int weaponType) const; + bool getAttackingOrSpell(); + void setAttackingOrSpell(bool attackingOrSpell); + + public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); @@ -285,7 +288,6 @@ public: bool isAttackingOrSpell() const; void setVisibility(float visibility); - void setAttackingOrSpell(bool attackingOrSpell); void castSpell(const std::string& spellId, bool manualSpell=false); void setAIAttackType(const std::string& attackType); static void setAttackTypeRandomly(std::string& attackType); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 03c2d3f41f..9611f5b34a 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -24,6 +24,7 @@ namespace MWMechanics mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1), mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) + , mAttackingOrSpell(false) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 002991e004..2bcb452292 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -92,6 +92,7 @@ namespace MWMechanics protected: int mLevel; + bool mAttackingOrSpell; public: CreatureStats(); @@ -124,7 +125,7 @@ namespace MWMechanics const MagicEffects & getMagicEffects() const; - bool getAttackingOrSpell() const; + bool getAttackingOrSpell() const { return mAttackingOrSpell; } int getLevel() const; @@ -149,7 +150,7 @@ namespace MWMechanics /// Set Modifier for each magic effect according to \a effects. Does not touch Base values. void modifyMagicEffects(const MagicEffects &effects); - void setAttackingOrSpell(bool attackingOrSpell); + void setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; } void setLevel(int level); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index da361aec25..308762045c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -957,7 +957,7 @@ void NpcAnimation::showWeapons(bool showWeapon) removeIndividualPart(ESM::PRT_Weapon); // If we remove/hide weapon from player, we should reset attack animation as well if (mPtr == MWMechanics::getPlayer()) - MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); + mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false); } updateHolsteredWeapon(!mShowWeapons); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 3902a409e5..7062366697 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -39,7 +39,6 @@ namespace MWWorld mTeleported(false), mCurrentCrimeId(-1), mPaidCrimeId(-1), - mAttackingOrSpell(false), mJumping(false) { ESM::CellRef cellRef; @@ -266,12 +265,7 @@ namespace MWWorld void Player::setAttackingOrSpell(bool attackingOrSpell) { - mAttackingOrSpell = attackingOrSpell; - } - - bool Player::getAttackingOrSpell() const - { - return mAttackingOrSpell; + getPlayer().getClass().getCreatureStats(getPlayer()).setAttackingOrSpell(attackingOrSpell); } void Player::setJumping(bool jumping) @@ -314,7 +308,6 @@ namespace MWWorld mAutoMove = false; mForwardBackward = 0; mTeleported = false; - mAttackingOrSpell = false; mJumping = false; mCurrentCrimeId = -1; mPaidCrimeId = -1; diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 2770042969..4add58541e 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -56,7 +56,6 @@ namespace MWWorld float mSaveSkills[ESM::Skill::Length]; float mSaveAttributes[ESM::Attribute::Length]; - bool mAttackingOrSpell; bool mJumping; public: @@ -112,7 +111,6 @@ namespace MWWorld void setTeleported(bool teleported); void setAttackingOrSpell(bool attackingOrSpell); - bool getAttackingOrSpell() const; void setJumping(bool jumping); bool getJumping() const; diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 98f1071cf6..1228a92a87 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -30,9 +30,11 @@ -- @type ActorControls -- @field [parent=#ActorControls] #number movement +1 - move forward, -1 - move backward -- @field [parent=#ActorControls] #number sideMovement +1 - move right, -1 - move left --- @field [parent=#ActorControls] #number turn Turn right (radians); if negative - turn left +-- @field [parent=#ActorControls] #number yawChange Turn right (radians); if negative - turn left +-- @field [parent=#ActorControls] #number pitchChange Look down (radians); if negative - look up -- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump +-- @field [parent=#ActorControls] #number use if 1 - activates the readied weapon/spell. For weapons, keeping at 1 will charge the attack until set to 0. ------------------------------------------------------------------------------- -- Enables or disables standart AI (enabled by default). From fba82eb1a7fca59420f69577224116d093ae4567 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 25 Jan 2022 21:53:00 +0100 Subject: [PATCH 2026/2859] Script settings tab --- apps/openmw/mwgui/settingswindow.cpp | 85 +++++++++++++++++++++-- apps/openmw/mwgui/settingswindow.hpp | 10 +++ apps/openmw/mwlua/uibindings.cpp | 17 ++++- components/CMakeLists.txt | 2 +- components/lua_ui/scriptsettings.cpp | 37 ++++++++++ components/lua_ui/scriptsettings.hpp | 25 +++++++ components/lua_ui/util.cpp | 2 + files/mygui/openmw_settings_window.layout | 13 ++++ 8 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 components/lua_ui/scriptsettings.cpp create mode 100644 components/lua_ui/scriptsettings.hpp diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 69d09f7260..8c3740d15d 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -1,18 +1,20 @@ #include "settingswindow.hpp" +#include +#include +#include +#include + #include #include #include #include #include #include +#include #include -#include -#include -#include - #include #include #include @@ -21,6 +23,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -33,7 +36,6 @@ namespace { - std::string textureMipmappingToStr(const std::string& val) { if (val == "linear") return "Trilinear"; @@ -236,6 +238,10 @@ namespace MWGui getWidget(mLightingMethodButton, "LightingMethodButton"); getWidget(mLightsResetButton, "LightsResetButton"); getWidget(mMaxLights, "MaxLights"); + getWidget(mScriptFilter, "ScriptFilter"); + getWidget(mScriptList, "ScriptList"); + getWidget(mScriptView, "ScriptView"); + getWidget(mScriptDisabled, "ScriptDisabled"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -321,9 +327,12 @@ namespace MWGui mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); + + mScriptFilter->eventEditTextChange += MyGUI::newDelegate(this, &SettingsWindow::onScriptFilterChange); + mScriptList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SettingsWindow::onScriptListSelection); } - void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/) + void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t index) { resetScrollbars(); } @@ -699,6 +708,67 @@ namespace MWGui mControlsBox->setVisibleVScroll(true); } + void SettingsWindow::resizeScriptSettings() + { + static int minListWidth = 150; + static float relativeListWidth = 0.2f; + MyGUI::IntSize parentSize = mScriptFilter->getParent()->getClientCoord().size(); + int listWidth = std::max(minListWidth, static_cast(parentSize.width * relativeListWidth)); + int filterHeight = mScriptFilter->getSize().height; + int listBorder = (mScriptList->getSize().height - mScriptList->getClientCoord().height) / 2; + int listHeight = parentSize.height - listBorder - mScriptList->getPosition().top; + mScriptFilter->setSize({ listWidth, filterHeight }); + mScriptList->setSize({ listWidth, listHeight }); + mScriptView->setPosition({ listWidth, 0 }); + mScriptView->setSize({ parentSize.width - listWidth, parentSize.height }); + mScriptDisabled->setPosition({0, 0}); + mScriptDisabled->setSize(parentSize); + } + + void SettingsWindow::renderScriptSettings() + { + while (mScriptView->getChildCount() > 0) + mScriptView->getChildAt(0)->detachFromWidget(); + mScriptList->removeAllItems(); + + std::string filter(".*"); + filter += mScriptFilter->getCaption(); + filter += ".*"; + auto flags = std::regex_constants::icase; + std::regex filterRegex(filter, flags); + + auto scriptSettings = LuaUi::scriptSettings(); + for (size_t i = 0; i < scriptSettings.size(); ++i) + { + LuaUi::ScriptSettings script = scriptSettings[i]; + if (std::regex_match(script.mName, filterRegex) || std::regex_match(script.mSearchHints, filterRegex)) + mScriptList->addItem(script.mName, i); + } + + // Hide script settings tab when the game world isn't loaded and scripts couldn't add their settings + bool disabled = scriptSettings.empty(); + mScriptDisabled->setVisible(disabled); + mScriptFilter->setVisible(!disabled); + mScriptList->setVisible(!disabled); + mScriptView->setVisible(!disabled); + } + + void SettingsWindow::onScriptFilterChange(MyGUI::Widget*) + { + renderScriptSettings(); + } + + void SettingsWindow::onScriptListSelection(MyGUI::Widget*, size_t index) + { + while (mScriptView->getChildCount() > 0) + mScriptView->getChildAt(0)->detachFromWidget(); + if (index >= mScriptList->getItemCount()) + return; + size_t scriptIndex = *mScriptList->getItemDataAt(index); + LuaUi::ScriptSettings script = LuaUi::scriptSettings()[scriptIndex]; + LuaUi::attachToWidget(script, mScriptView); + } + void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) { int actionId = *_sender->getUserData(); @@ -744,12 +814,15 @@ namespace MWGui updateControlsBox(); updateLightSettings(); resetScrollbars(); + renderScriptSettings(); + resizeScriptSettings(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); } void SettingsWindow::onWindowResize(MyGUI::Window *_sender) { layoutControlsBox(); + resizeScriptSettings(); } void SettingsWindow::computeMinimumWindowSize() diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index d58e94e84a..f002a67306 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -44,6 +44,11 @@ namespace MWGui MyGUI::Button* mControllerSwitch; bool mKeyboardMode; //if true, setting up the keyboard. Otherwise, it's controller + MyGUI::EditBox* mScriptFilter; + MyGUI::ListBox* mScriptList; + MyGUI::Widget* mScriptView; + MyGUI::EditBox* mScriptDisabled; + void onTabChanged(MyGUI::TabControl* _sender, size_t index); void onOkButtonClicked(MyGUI::Widget* _sender); void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); @@ -71,12 +76,17 @@ namespace MWGui void onWindowResize(MyGUI::Window* _sender); + void onScriptFilterChange(MyGUI::Widget*); + void onScriptListSelection(MyGUI::Widget*, size_t index); + void apply(); void configureWidgets(MyGUI::Widget* widget, bool init); void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); void layoutControlsBox(); + void resizeScriptSettings(); + void renderScriptSettings(); void computeMinimumWindowSize(); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 61e7c471c1..5fe54a4d35 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "context.hpp" #include "actions.hpp" @@ -43,7 +44,7 @@ namespace MWLua break; } } - catch (std::exception& e) + catch (std::exception&) { // prevent any actions on a potentially corrupted widget mElement->mRoot = nullptr; @@ -238,6 +239,20 @@ namespace MWLua typeTable.set(it.second, it.first); api["TYPE"] = LuaUtil::makeReadOnly(typeTable); + api["registerSettings"] = [](sol::table options) + { + LuaUi::ScriptSettings script; + script.mName = options.get_or("name", std::string()); + if (script.mName.empty()) + throw std::logic_error("No name provided for script settings"); + script.mSearchHints = options.get_or("searchHints", std::string()); + auto element = options.get_or>("element", nullptr); + if (!element) + throw std::logic_error("No UI element provided for script settings"); + script.mElement = element.get(); + LuaUi::registerSettings(script); + }; + return LuaUtil::makeReadOnly(api); } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2d986660bc..3526386e69 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -165,7 +165,7 @@ add_component_dir (queries ) add_component_dir (lua_ui - properties widget element util layers content + properties widget element util layers content scriptsettings text textedit window image ) diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp new file mode 100644 index 0000000000..071ded1b5d --- /dev/null +++ b/components/lua_ui/scriptsettings.cpp @@ -0,0 +1,37 @@ +#include "scriptsettings.hpp" + +#include + +#include "element.hpp" + +namespace LuaUi +{ + namespace + { + std::vector allSettings; + } + + const std::vector& scriptSettings() + { + return allSettings; + } + + void registerSettings(const ScriptSettings& script) + { + allSettings.push_back(script); + } + + void clearSettings() + { + allSettings.clear(); + } + + void attachToWidget(const ScriptSettings& script, MyGUI::Widget* widget) + { + WidgetExtension* root = script.mElement->mRoot; + if (!root) + return; + root->widget()->attachToWidget(widget); + root->updateCoord(); + } +} diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp new file mode 100644 index 0000000000..eadcec430f --- /dev/null +++ b/components/lua_ui/scriptsettings.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_LUAUI_SCRIPTSETTINGS +#define OPENMW_LUAUI_SCRIPTSETTINGS + +#include +#include +#include + +#include + +namespace LuaUi +{ + class Element; + struct ScriptSettings + { + std::string mName; + std::string mSearchHints; + Element* mElement; // TODO: figure out if this can lead to use after free + }; + const std::vector& scriptSettings(); + void registerSettings(const ScriptSettings& script); + void clearSettings(); + void attachToWidget(const ScriptSettings& script, MyGUI::Widget* widget); +} + +#endif // !OPENMW_LUAUI_SCRIPTSETTINGS diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 1e13c97a80..3987765617 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -9,6 +9,7 @@ #include "image.hpp" #include "element.hpp" +#include "scriptsettings.hpp" namespace LuaUi { @@ -37,6 +38,7 @@ namespace LuaUi void clearUserInterface() { + clearSettings(); while (!Element::sAllElements.empty()) Element::sAllElements.begin()->second->destroy(); } diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8e81764f60..6a276ddd22 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -656,6 +656,19 @@ --> + + + + + + + + + + + + + From 64df4f54c649c1f9a381db0c99b61d665e766afa Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 27 Jan 2022 23:12:30 +0100 Subject: [PATCH 2027/2859] Add scrolling to the script settings view --- apps/openmw/mwgui/settingswindow.cpp | 25 +++++++++++++++-------- apps/openmw/mwgui/settingswindow.hpp | 3 ++- files/mygui/openmw_scroll.skin.xml | 7 +++++++ files/mygui/openmw_settings_window.layout | 14 +++++++++---- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 8c3740d15d..567e603e53 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -240,6 +240,7 @@ namespace MWGui getWidget(mMaxLights, "MaxLights"); getWidget(mScriptFilter, "ScriptFilter"); getWidget(mScriptList, "ScriptList"); + getWidget(mScriptBox, "ScriptBox"); getWidget(mScriptView, "ScriptView"); getWidget(mScriptDisabled, "ScriptDisabled"); @@ -332,7 +333,7 @@ namespace MWGui mScriptList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SettingsWindow::onScriptListSelection); } - void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t index) + void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/) { resetScrollbars(); } @@ -710,26 +711,28 @@ namespace MWGui void SettingsWindow::resizeScriptSettings() { - static int minListWidth = 150; - static float relativeListWidth = 0.2f; + static const int minListWidth = 150; + static const float relativeListWidth = 0.2f; + static const int padding = 2; + static const int outerPadding = padding * 2; MyGUI::IntSize parentSize = mScriptFilter->getParent()->getClientCoord().size(); int listWidth = std::max(minListWidth, static_cast(parentSize.width * relativeListWidth)); int filterHeight = mScriptFilter->getSize().height; - int listBorder = (mScriptList->getSize().height - mScriptList->getClientCoord().height) / 2; - int listHeight = parentSize.height - listBorder - mScriptList->getPosition().top; + int listHeight = parentSize.height - mScriptList->getPosition().top - outerPadding; mScriptFilter->setSize({ listWidth, filterHeight }); mScriptList->setSize({ listWidth, listHeight }); - mScriptView->setPosition({ listWidth, 0 }); - mScriptView->setSize({ parentSize.width - listWidth, parentSize.height }); + mScriptBox->setPosition({ listWidth + padding, 0 }); + mScriptBox->setSize({ parentSize.width - listWidth - padding, parentSize.height - outerPadding }); mScriptDisabled->setPosition({0, 0}); mScriptDisabled->setSize(parentSize); } - + void SettingsWindow::renderScriptSettings() { while (mScriptView->getChildCount() > 0) mScriptView->getChildAt(0)->detachFromWidget(); mScriptList->removeAllItems(); + mScriptView->setCanvasSize({0, 0}); std::string filter(".*"); filter += mScriptFilter->getCaption(); @@ -750,7 +753,7 @@ namespace MWGui mScriptDisabled->setVisible(disabled); mScriptFilter->setVisible(!disabled); mScriptList->setVisible(!disabled); - mScriptView->setVisible(!disabled); + mScriptBox->setVisible(!disabled); } void SettingsWindow::onScriptFilterChange(MyGUI::Widget*) @@ -767,6 +770,10 @@ namespace MWGui size_t scriptIndex = *mScriptList->getItemDataAt(index); LuaUi::ScriptSettings script = LuaUi::scriptSettings()[scriptIndex]; LuaUi::attachToWidget(script, mScriptView); + MyGUI::IntSize canvasSize; + if (mScriptView->getChildCount() > 0) + canvasSize = mScriptView->getChildAt(0)->getSize(); + mScriptView->setCanvasSize(canvasSize); } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index f002a67306..c7ba1e8ece 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -46,7 +46,8 @@ namespace MWGui MyGUI::EditBox* mScriptFilter; MyGUI::ListBox* mScriptList; - MyGUI::Widget* mScriptView; + MyGUI::Widget* mScriptBox; + MyGUI::ScrollView* mScriptView; MyGUI::EditBox* mScriptDisabled; void onTabChanged(MyGUI::TabControl* _sender, size_t index); diff --git a/files/mygui/openmw_scroll.skin.xml b/files/mygui/openmw_scroll.skin.xml index f946a61ac9..a2c2cd9150 100644 --- a/files/mygui/openmw_scroll.skin.xml +++ b/files/mygui/openmw_scroll.skin.xml @@ -14,4 +14,11 @@ + + + + + + + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 6a276ddd22..cf55550fbe 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -658,10 +658,16 @@ - - - - + + + + + + + + + + From a972a54ea9f2e000d6054c66eaadf599a56ad5a9 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 28 Jan 2022 21:35:05 +0100 Subject: [PATCH 2028/2859] Allow changing element root widget type, prevent use after free in script settings --- apps/openmw/mwgui/settingswindow.cpp | 20 +++++----- apps/openmw/mwgui/settingswindow.hpp | 1 + components/lua_ui/element.cpp | 60 ++++++++++++++++++++++++---- components/lua_ui/element.hpp | 7 +++- components/lua_ui/scriptsettings.cpp | 9 ++--- components/lua_ui/scriptsettings.hpp | 2 +- 6 files changed, 73 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 567e603e53..3ea60b92bb 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -207,9 +207,9 @@ namespace MWGui } } - SettingsWindow::SettingsWindow() : - WindowBase("openmw_settings_window.layout"), - mKeyboardMode(true) + SettingsWindow::SettingsWindow() : WindowBase("openmw_settings_window.layout") + , mKeyboardMode(true) + , mCurrentPage(-1) { bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); const std::string widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; @@ -729,8 +729,8 @@ namespace MWGui void SettingsWindow::renderScriptSettings() { - while (mScriptView->getChildCount() > 0) - mScriptView->getChildAt(0)->detachFromWidget(); + LuaUi::attachToWidget(mCurrentPage); + mCurrentPage = -1; mScriptList->removeAllItems(); mScriptView->setCanvasSize({0, 0}); @@ -763,13 +763,13 @@ namespace MWGui void SettingsWindow::onScriptListSelection(MyGUI::Widget*, size_t index) { - while (mScriptView->getChildCount() > 0) - mScriptView->getChildAt(0)->detachFromWidget(); + if (mCurrentPage >= 0) + LuaUi::attachToWidget(mCurrentPage); + mCurrentPage = -1; if (index >= mScriptList->getItemCount()) return; - size_t scriptIndex = *mScriptList->getItemDataAt(index); - LuaUi::ScriptSettings script = LuaUi::scriptSettings()[scriptIndex]; - LuaUi::attachToWidget(script, mScriptView); + mCurrentPage = *mScriptList->getItemDataAt(index); + LuaUi::attachToWidget(mCurrentPage, mScriptView); MyGUI::IntSize canvasSize; if (mScriptView->getChildCount() > 0) canvasSize = mScriptView->getChildAt(0)->getSize(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index c7ba1e8ece..2db8f2e19c 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -49,6 +49,7 @@ namespace MWGui MyGUI::Widget* mScriptBox; MyGUI::ScrollView* mScriptView; MyGUI::EditBox* mScriptDisabled; + int mCurrentPage; void onTabChanged(MyGUI::TabControl* _sender, size_t index); void onOkButtonClicked(MyGUI::Widget* _sender); diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index c9e0fc309e..5baddb98d8 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -138,7 +138,7 @@ namespace LuaUi ext->setChildren(updateContent(ext->children(), layout.get(LayoutKeys::content))); } - void setLayer(WidgetExtension* ext, const sol::table& layout) + std::string setLayer(WidgetExtension* ext, const sol::table& layout) { MyGUI::ILayer* layerNode = ext->widget()->getLayer(); std::string currentLayer = layerNode ? layerNode->getName() : std::string(); @@ -149,15 +149,18 @@ namespace LuaUi { MyGUI::LayerManager::getInstance().attachToLayerNode(newLayer, ext->widget()); } + return newLayer; } std::map> Element::sAllElements; Element::Element(sol::table layout) - : mRoot{ nullptr } - , mLayout{ std::move(layout) } - , mUpdate{ false } - , mDestroy{ false } + : mRoot(nullptr) + , mAttachedTo(nullptr) + , mLayout(std::move(layout)) + , mLayer() + , mUpdate(false) + , mDestroy(false) {} @@ -174,7 +177,8 @@ namespace LuaUi if (!mRoot) { mRoot = createWidget(mLayout); - setLayer(mRoot, mLayout); + mLayer = setLayer(mRoot, mLayout); + updateAttachment(); } } @@ -182,8 +186,17 @@ namespace LuaUi { if (mRoot && mUpdate) { - updateWidget(mRoot, mLayout); - setLayer(mRoot, mLayout); + if (mRoot->widget()->getTypeName() != widgetType(mLayout)) + { + destroyWidget(mRoot); + mRoot = createWidget(mLayout); + } + else + { + updateWidget(mRoot, mLayout); + } + mLayer = setLayer(mRoot, mLayout); + updateAttachment(); } mUpdate = false; } @@ -195,4 +208,35 @@ namespace LuaUi mRoot = nullptr; sAllElements.erase(this); } + + void Element::attachToWidget(MyGUI::Widget* w) + { + if (mAttachedTo && w) + throw std::logic_error("A UI element can't be attached to two widgets at once"); + mAttachedTo = w; + updateAttachment(); + } + + void Element::updateAttachment() + { + if (!mRoot) + return; + if (mAttachedTo) + { + if (!mLayer.empty()) + Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; + if (mRoot->widget()->getParent() != mAttachedTo) + { + mRoot->widget()->attachToWidget(mAttachedTo); + mRoot->updateCoord(); + } + } + else + { + if (mRoot->widget()->getParent() != nullptr) + { + mRoot->widget()->detachFromWidget(); + } + } + } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index daaf340660..95d0aa2ebc 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -9,8 +9,10 @@ namespace LuaUi { static std::shared_ptr make(sol::table layout); - LuaUi::WidgetExtension* mRoot; + WidgetExtension* mRoot; + MyGUI::Widget* mAttachedTo; sol::table mLayout; + std::string mLayer; bool mUpdate; bool mDestroy; @@ -22,9 +24,12 @@ namespace LuaUi friend void clearUserInterface(); + void attachToWidget(MyGUI::Widget* w = nullptr); + private: Element(sol::table layout); static std::map> sAllElements; + void updateAttachment(); }; } diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index 071ded1b5d..c8a7d849c9 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -26,12 +26,9 @@ namespace LuaUi allSettings.clear(); } - void attachToWidget(const ScriptSettings& script, MyGUI::Widget* widget) + void attachToWidget(size_t index, MyGUI::Widget* widget) { - WidgetExtension* root = script.mElement->mRoot; - if (!root) - return; - root->widget()->attachToWidget(widget); - root->updateCoord(); + if (0 <= index && index < allSettings.size()) + allSettings[index].mElement->attachToWidget(widget); } } diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index eadcec430f..b13bfc578c 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -19,7 +19,7 @@ namespace LuaUi const std::vector& scriptSettings(); void registerSettings(const ScriptSettings& script); void clearSettings(); - void attachToWidget(const ScriptSettings& script, MyGUI::Widget* widget); + void attachToWidget(size_t index, MyGUI::Widget* widget = nullptr); } #endif // !OPENMW_LUAUI_SCRIPTSETTINGS From e78b8402fac084f40ae82a86060820b8ea0c17a3 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 28 Jan 2022 21:45:13 +0100 Subject: [PATCH 2029/2859] Fix warnings --- apps/openmw/mwgui/settingswindow.cpp | 7 ++++--- apps/openmw/mwgui/settingswindow.hpp | 4 ++-- components/lua_ui/scriptsettings.cpp | 2 +- components/lua_ui/scriptsettings.hpp | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 3ea60b92bb..f5d35bc13f 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -729,7 +729,8 @@ namespace MWGui void SettingsWindow::renderScriptSettings() { - LuaUi::attachToWidget(mCurrentPage); + if (mCurrentPage >= 0) + LuaUi::attachToWidget(mCurrentPage); mCurrentPage = -1; mScriptList->removeAllItems(); mScriptView->setCanvasSize({0, 0}); @@ -756,12 +757,12 @@ namespace MWGui mScriptBox->setVisible(!disabled); } - void SettingsWindow::onScriptFilterChange(MyGUI::Widget*) + void SettingsWindow::onScriptFilterChange(MyGUI::EditBox*) { renderScriptSettings(); } - void SettingsWindow::onScriptListSelection(MyGUI::Widget*, size_t index) + void SettingsWindow::onScriptListSelection(MyGUI::ListBox*, size_t index) { if (mCurrentPage >= 0) LuaUi::attachToWidget(mCurrentPage); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 2db8f2e19c..ff86c96f60 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -78,8 +78,8 @@ namespace MWGui void onWindowResize(MyGUI::Window* _sender); - void onScriptFilterChange(MyGUI::Widget*); - void onScriptListSelection(MyGUI::Widget*, size_t index); + void onScriptFilterChange(MyGUI::EditBox*); + void onScriptListSelection(MyGUI::ListBox*, size_t index); void apply(); diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index c8a7d849c9..1bc93f4e84 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -28,7 +28,7 @@ namespace LuaUi void attachToWidget(size_t index, MyGUI::Widget* widget) { - if (0 <= index && index < allSettings.size()) + if (index < allSettings.size()) allSettings[index].mElement->attachToWidget(widget); } } diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index b13bfc578c..7075a3b189 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -9,7 +9,7 @@ namespace LuaUi { - class Element; + struct Element; struct ScriptSettings { std::string mName; From a005f25c4b0d3e4beef00df534f42295b920a325 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 29 Jan 2022 13:22:08 +0100 Subject: [PATCH 2030/2859] Use page terminology for script settings --- apps/openmw/mwgui/settingswindow.cpp | 8 ++++---- apps/openmw/mwlua/uibindings.cpp | 18 +++++++++--------- components/lua_ui/scriptsettings.cpp | 16 ++++++++-------- components/lua_ui/scriptsettings.hpp | 6 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index f5d35bc13f..646fe0d19b 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -741,12 +741,12 @@ namespace MWGui auto flags = std::regex_constants::icase; std::regex filterRegex(filter, flags); - auto scriptSettings = LuaUi::scriptSettings(); + auto scriptSettings = LuaUi::scriptSettingsPages(); for (size_t i = 0; i < scriptSettings.size(); ++i) { - LuaUi::ScriptSettings script = scriptSettings[i]; - if (std::regex_match(script.mName, filterRegex) || std::regex_match(script.mSearchHints, filterRegex)) - mScriptList->addItem(script.mName, i); + LuaUi::ScriptSettingsPage page = scriptSettings[i]; + if (std::regex_match(page.mName, filterRegex) || std::regex_match(page.mSearchHints, filterRegex)) + mScriptList->addItem(page.mName, i); } // Hide script settings tab when the game world isn't loaded and scripts couldn't add their settings diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 5fe54a4d35..7737f36ec4 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -239,18 +239,18 @@ namespace MWLua typeTable.set(it.second, it.first); api["TYPE"] = LuaUtil::makeReadOnly(typeTable); - api["registerSettings"] = [](sol::table options) + api["registerSettingsPage"] = [](sol::table options) { - LuaUi::ScriptSettings script; - script.mName = options.get_or("name", std::string()); - if (script.mName.empty()) - throw std::logic_error("No name provided for script settings"); - script.mSearchHints = options.get_or("searchHints", std::string()); + LuaUi::ScriptSettingsPage page; + page.mName = options.get_or("name", std::string()); + if (page.mName.empty()) + throw std::logic_error("No name provided for the settings page"); + page.mSearchHints = options.get_or("searchHints", std::string()); auto element = options.get_or>("element", nullptr); if (!element) - throw std::logic_error("No UI element provided for script settings"); - script.mElement = element.get(); - LuaUi::registerSettings(script); + throw std::logic_error("No UI element provided for the settings page"); + page.mElement = element.get(); + LuaUi::registerSettingsPage(page); }; return LuaUtil::makeReadOnly(api); diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index 1bc93f4e84..1a016381cd 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -8,27 +8,27 @@ namespace LuaUi { namespace { - std::vector allSettings; + std::vector allPages; } - const std::vector& scriptSettings() + const std::vector& scriptSettingsPages() { - return allSettings; + return allPages; } - void registerSettings(const ScriptSettings& script) + void registerSettingsPage(const ScriptSettingsPage& page) { - allSettings.push_back(script); + allPages.push_back(page); } void clearSettings() { - allSettings.clear(); + allPages.clear(); } void attachToWidget(size_t index, MyGUI::Widget* widget) { - if (index < allSettings.size()) - allSettings[index].mElement->attachToWidget(widget); + if (index < allPages.size()) + allPages[index].mElement->attachToWidget(widget); } } diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index 7075a3b189..852e0df1ec 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -10,14 +10,14 @@ namespace LuaUi { struct Element; - struct ScriptSettings + struct ScriptSettingsPage { std::string mName; std::string mSearchHints; Element* mElement; // TODO: figure out if this can lead to use after free }; - const std::vector& scriptSettings(); - void registerSettings(const ScriptSettings& script); + const std::vector& scriptSettingsPages(); + void registerSettingsPage(const ScriptSettingsPage& page); void clearSettings(); void attachToWidget(size_t index, MyGUI::Widget* widget = nullptr); } From 5f7ab4988072a4a2a4e6f2ba34885566e1b9b406 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 29 Jan 2022 16:59:43 +0100 Subject: [PATCH 2031/2859] Implement script setting pages' descriptions --- apps/openmw/mwgui/settingswindow.cpp | 35 ++++++++++++++++++----- apps/openmw/mwgui/settingswindow.hpp | 6 ++++ apps/openmw/mwlua/uibindings.cpp | 2 +- components/lua_ui/scriptsettings.hpp | 2 +- files/mygui/openmw_settings_window.layout | 8 ++++++ 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 646fe0d19b..dfd0e6d9df 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -243,6 +243,8 @@ namespace MWGui getWidget(mScriptBox, "ScriptBox"); getWidget(mScriptView, "ScriptView"); getWidget(mScriptDisabled, "ScriptDisabled"); + getWidget(mScriptDescription, "ScriptDescription"); + mScriptChild = nullptr; #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -331,6 +333,7 @@ namespace MWGui mScriptFilter->eventEditTextChange += MyGUI::newDelegate(this, &SettingsWindow::onScriptFilterChange); mScriptList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SettingsWindow::onScriptListSelection); + mScriptList->eventListMouseItemFocus += MyGUI::newDelegate(this, &SettingsWindow::onScriptListFocus); } void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/) @@ -745,7 +748,7 @@ namespace MWGui for (size_t i = 0; i < scriptSettings.size(); ++i) { LuaUi::ScriptSettingsPage page = scriptSettings[i]; - if (std::regex_match(page.mName, filterRegex) || std::regex_match(page.mSearchHints, filterRegex)) + if (std::regex_match(page.mName, filterRegex) || std::regex_match(page.mDescription, filterRegex)) mScriptList->addItem(page.mName, i); } @@ -767,14 +770,32 @@ namespace MWGui if (mCurrentPage >= 0) LuaUi::attachToWidget(mCurrentPage); mCurrentPage = -1; + if (index < mScriptList->getItemCount()) + { + mCurrentPage = *mScriptList->getItemDataAt(index); + LuaUi::attachToWidget(mCurrentPage, mScriptView); + } + mScriptChild = mScriptView->getChildCount() > 0 ? mScriptView->getChildAt(0) : nullptr; + MyGUI::IntSize canvasSize = mScriptChild ? mScriptChild->getSize() : MyGUI::IntSize(); + if (mScriptChild) + mScriptChild->setVisible(mScriptView->isVisible()); + mScriptView->setCanvasSize(canvasSize); + } + + void SettingsWindow::onScriptListFocus(MyGUI::ListBox*, size_t index) + { if (index >= mScriptList->getItemCount()) + { + mScriptDescription->setVisible(false); + mScriptView->setVisible(true); + if (mScriptChild) + mScriptChild->setVisible(true); return; - mCurrentPage = *mScriptList->getItemDataAt(index); - LuaUi::attachToWidget(mCurrentPage, mScriptView); - MyGUI::IntSize canvasSize; - if (mScriptView->getChildCount() > 0) - canvasSize = mScriptView->getChildAt(0)->getSize(); - mScriptView->setCanvasSize(canvasSize); + } + size_t page = *mScriptList->getItemDataAt(index); + mScriptDescription->setCaption(LuaUi::scriptSettingsPages()[page].mDescription); + mScriptDescription->setVisible(true); + mScriptView->setVisible(false); } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index ff86c96f60..22308f02b6 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -49,8 +49,13 @@ namespace MWGui MyGUI::Widget* mScriptBox; MyGUI::ScrollView* mScriptView; MyGUI::EditBox* mScriptDisabled; + MyGUI::EditBox* mScriptDescription; int mCurrentPage; + // only necessary to work around a MyGUI bug + // adding a child to an invisible widget doesn't make the child invisible + MyGUI::Widget* mScriptChild; + void onTabChanged(MyGUI::TabControl* _sender, size_t index); void onOkButtonClicked(MyGUI::Widget* _sender); void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); @@ -80,6 +85,7 @@ namespace MWGui void onScriptFilterChange(MyGUI::EditBox*); void onScriptListSelection(MyGUI::ListBox*, size_t index); + void onScriptListFocus(MyGUI::ListBox*, size_t index); void apply(); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 7737f36ec4..7b11e1770c 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -245,7 +245,7 @@ namespace MWLua page.mName = options.get_or("name", std::string()); if (page.mName.empty()) throw std::logic_error("No name provided for the settings page"); - page.mSearchHints = options.get_or("searchHints", std::string()); + page.mDescription = options.get_or("description", std::string()); auto element = options.get_or>("element", nullptr); if (!element) throw std::logic_error("No UI element provided for the settings page"); diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index 852e0df1ec..28525bb1da 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -13,7 +13,7 @@ namespace LuaUi struct ScriptSettingsPage { std::string mName; - std::string mSearchHints; + std::string mDescription; Element* mElement; // TODO: figure out if this can lead to use after free }; const std::vector& scriptSettingsPages(); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index cf55550fbe..da64735c79 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -665,6 +665,14 @@ + + + + + + + + From 1455aa3e026f1a71ff511a387e0f92a895b0907a Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 29 Jan 2022 18:33:14 +0100 Subject: [PATCH 2032/2859] Allow changing script settings pages after registering them --- apps/openmw/mwgui/settingswindow.cpp | 9 ++++---- apps/openmw/mwlua/uibindings.cpp | 11 +-------- components/lua_ui/scriptsettings.cpp | 34 +++++++++++++++++++++++----- components/lua_ui/scriptsettings.hpp | 5 ++-- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index dfd0e6d9df..d41d9cc4f0 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -744,16 +744,15 @@ namespace MWGui auto flags = std::regex_constants::icase; std::regex filterRegex(filter, flags); - auto scriptSettings = LuaUi::scriptSettingsPages(); - for (size_t i = 0; i < scriptSettings.size(); ++i) + for (size_t i = 0; i < LuaUi::scriptSettingsPageCount(); ++i) { - LuaUi::ScriptSettingsPage page = scriptSettings[i]; + LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i); if (std::regex_match(page.mName, filterRegex) || std::regex_match(page.mDescription, filterRegex)) mScriptList->addItem(page.mName, i); } // Hide script settings tab when the game world isn't loaded and scripts couldn't add their settings - bool disabled = scriptSettings.empty(); + bool disabled = LuaUi::scriptSettingsPageCount() == 0; mScriptDisabled->setVisible(disabled); mScriptFilter->setVisible(!disabled); mScriptList->setVisible(!disabled); @@ -793,7 +792,7 @@ namespace MWGui return; } size_t page = *mScriptList->getItemDataAt(index); - mScriptDescription->setCaption(LuaUi::scriptSettingsPages()[page].mDescription); + mScriptDescription->setCaption(LuaUi::scriptSettingsPageAt(page).mDescription); mScriptDescription->setVisible(true); mScriptView->setVisible(false); } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 7b11e1770c..bd19dde114 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -241,16 +241,7 @@ namespace MWLua api["registerSettingsPage"] = [](sol::table options) { - LuaUi::ScriptSettingsPage page; - page.mName = options.get_or("name", std::string()); - if (page.mName.empty()) - throw std::logic_error("No name provided for the settings page"); - page.mDescription = options.get_or("description", std::string()); - auto element = options.get_or>("element", nullptr); - if (!element) - throw std::logic_error("No UI element provided for the settings page"); - page.mElement = element.get(); - LuaUi::registerSettingsPage(page); + LuaUi::registerSettingsPage(options); }; return LuaUtil::makeReadOnly(api); diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index 1a016381cd..2695b9f25d 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -8,17 +8,35 @@ namespace LuaUi { namespace { - std::vector allPages; + std::vector allPages; + ScriptSettingsPage parse(const sol::table& options) + { + auto name = options.get_or("name", std::string()); + auto description = options.get_or("description", std::string()); + auto element = options.get_or>("element", nullptr); + if (name.empty()) + Log(Debug::Warning) << "A script settings page has an empty name"; + if (!element.get()) + Log(Debug::Warning) << "A script settings page has no UI element assigned"; + return { + name, description, element.get() + }; + } } - const std::vector& scriptSettingsPages() + size_t scriptSettingsPageCount() { - return allPages; + return allPages.size(); } - void registerSettingsPage(const ScriptSettingsPage& page) + ScriptSettingsPage scriptSettingsPageAt(size_t index) { - allPages.push_back(page); + return parse(allPages[index]); + } + + void registerSettingsPage(const sol::table& options) + { + allPages.push_back(options); } void clearSettings() @@ -29,6 +47,10 @@ namespace LuaUi void attachToWidget(size_t index, MyGUI::Widget* widget) { if (index < allPages.size()) - allPages[index].mElement->attachToWidget(widget); + { + ScriptSettingsPage page = parse(allPages[index]); + if (page.mElement) + page.mElement->attachToWidget(widget); + } } } diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index 28525bb1da..3b51aff901 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -16,8 +16,9 @@ namespace LuaUi std::string mDescription; Element* mElement; // TODO: figure out if this can lead to use after free }; - const std::vector& scriptSettingsPages(); - void registerSettingsPage(const ScriptSettingsPage& page); + size_t scriptSettingsPageCount(); + ScriptSettingsPage scriptSettingsPageAt(size_t index); + void registerSettingsPage(const sol::table& options); void clearSettings(); void attachToWidget(size_t index, MyGUI::Widget* widget = nullptr); } From db9e734a6ad6ca112914cf87f4fd934f322d4118 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 29 Jan 2022 18:46:07 +0100 Subject: [PATCH 2033/2859] Fix warning --- apps/openmw/mwgui/settingswindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index d41d9cc4f0..2abea56064 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -777,7 +777,7 @@ namespace MWGui mScriptChild = mScriptView->getChildCount() > 0 ? mScriptView->getChildAt(0) : nullptr; MyGUI::IntSize canvasSize = mScriptChild ? mScriptChild->getSize() : MyGUI::IntSize(); if (mScriptChild) - mScriptChild->setVisible(mScriptView->isVisible()); + mScriptChild->setVisible(mScriptView->getVisible()); mScriptView->setCanvasSize(canvasSize); } From 086a7d9bc5fdb8bbff60348257d916df107e31ea Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 29 Jan 2022 23:06:43 +0100 Subject: [PATCH 2034/2859] Wrap Lua settings widgets into an Adapter widget --- apps/openmw/mwgui/settingswindow.cpp | 28 ++++------ apps/openmw/mwgui/settingswindow.hpp | 7 ++- components/CMakeLists.txt | 2 +- components/lua_ui/adapter.cpp | 65 +++++++++++++++++++++++ components/lua_ui/adapter.hpp | 29 ++++++++++ components/lua_ui/element.cpp | 38 ++++++++----- components/lua_ui/element.hpp | 5 +- components/lua_ui/scriptsettings.cpp | 11 ++-- components/lua_ui/scriptsettings.hpp | 6 +-- components/lua_ui/util.cpp | 2 + components/lua_ui/widget.cpp | 9 ++++ components/lua_ui/widget.hpp | 9 ++++ files/mygui/openmw_settings_window.layout | 1 + 13 files changed, 168 insertions(+), 44 deletions(-) create mode 100644 components/lua_ui/adapter.cpp create mode 100644 components/lua_ui/adapter.hpp diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 2abea56064..f556bbdd5b 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -242,9 +242,9 @@ namespace MWGui getWidget(mScriptList, "ScriptList"); getWidget(mScriptBox, "ScriptBox"); getWidget(mScriptView, "ScriptView"); + getWidget(mScriptAdapter, "ScriptAdapter"); getWidget(mScriptDisabled, "ScriptDisabled"); getWidget(mScriptDescription, "ScriptDescription"); - mScriptChild = nullptr; #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -732,8 +732,7 @@ namespace MWGui void SettingsWindow::renderScriptSettings() { - if (mCurrentPage >= 0) - LuaUi::attachToWidget(mCurrentPage); + mScriptAdapter->detach(); mCurrentPage = -1; mScriptList->removeAllItems(); mScriptView->setCanvasSize({0, 0}); @@ -766,18 +765,14 @@ namespace MWGui void SettingsWindow::onScriptListSelection(MyGUI::ListBox*, size_t index) { - if (mCurrentPage >= 0) - LuaUi::attachToWidget(mCurrentPage); + mScriptAdapter->detach(); mCurrentPage = -1; if (index < mScriptList->getItemCount()) { mCurrentPage = *mScriptList->getItemDataAt(index); - LuaUi::attachToWidget(mCurrentPage, mScriptView); + LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); } - mScriptChild = mScriptView->getChildCount() > 0 ? mScriptView->getChildAt(0) : nullptr; - MyGUI::IntSize canvasSize = mScriptChild ? mScriptChild->getSize() : MyGUI::IntSize(); - if (mScriptChild) - mScriptChild->setVisible(mScriptView->getVisible()); + MyGUI::IntSize canvasSize = mScriptAdapter->getSize(); mScriptView->setCanvasSize(canvasSize); } @@ -787,14 +782,13 @@ namespace MWGui { mScriptDescription->setVisible(false); mScriptView->setVisible(true); - if (mScriptChild) - mScriptChild->setVisible(true); - return; } - size_t page = *mScriptList->getItemDataAt(index); - mScriptDescription->setCaption(LuaUi::scriptSettingsPageAt(page).mDescription); - mScriptDescription->setVisible(true); - mScriptView->setVisible(false); + else { + size_t page = *mScriptList->getItemDataAt(index); + mScriptDescription->setCaption(LuaUi::scriptSettingsPageAt(page).mDescription); + mScriptDescription->setVisible(true); + mScriptView->setVisible(false); + } } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 22308f02b6..217dcf3051 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_SETTINGS_H #define MWGUI_SETTINGS_H +#include + #include "windowbase.hpp" namespace MWGui @@ -48,14 +50,11 @@ namespace MWGui MyGUI::ListBox* mScriptList; MyGUI::Widget* mScriptBox; MyGUI::ScrollView* mScriptView; + LuaUi::LuaAdapter* mScriptAdapter; MyGUI::EditBox* mScriptDisabled; MyGUI::EditBox* mScriptDescription; int mCurrentPage; - // only necessary to work around a MyGUI bug - // adding a child to an invisible widget doesn't make the child invisible - MyGUI::Widget* mScriptChild; - void onTabChanged(MyGUI::TabControl* _sender, size_t index); void onOkButtonClicked(MyGUI::Widget* _sender); void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3526386e69..58e6d52ead 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -166,7 +166,7 @@ add_component_dir (queries add_component_dir (lua_ui properties widget element util layers content scriptsettings - text textedit window image + adapter text textedit window image ) diff --git a/components/lua_ui/adapter.cpp b/components/lua_ui/adapter.cpp new file mode 100644 index 0000000000..9a109d5d1f --- /dev/null +++ b/components/lua_ui/adapter.cpp @@ -0,0 +1,65 @@ +#include "adapter.hpp" + +#include + +#include "element.hpp" + +namespace LuaUi +{ + namespace + { + sol::state luaState; + } + + LuaAdapter::LuaAdapter() + : mElement(nullptr) + , mContent(nullptr) + { + MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( + "LuaWidget", "", + MyGUI::IntCoord(), MyGUI::Align::Default, + std::string(), ""); + + mContent = dynamic_cast(widget); + if (!mContent) + throw std::runtime_error("Invalid widget!"); + mContent->initialize(luaState, widget); + mContent->onSizeChange([this](MyGUI::IntSize size) + { + setSize(size); + }); + mContent->widget()->attachToWidget(this); + } + + void LuaAdapter::attach(const std::shared_ptr& element) + { + detachElement(); + mElement = element; + attachElement(); + setSize(mContent->widget()->getSize()); + + // workaround for MyGUI bug + // parent visibility doesn't affect added children + setVisible(!getVisible()); + setVisible(!getVisible()); + } + + void LuaAdapter::detach() + { + detachElement(); + setSize({ 0, 0 }); + } + + void LuaAdapter::attachElement() + { + if (mElement.get()) + mElement->attachToWidget(mContent); + } + + void LuaAdapter::detachElement() + { + if (mElement.get()) + mElement->detachFromWidget(); + mElement = nullptr; + } +} diff --git a/components/lua_ui/adapter.hpp b/components/lua_ui/adapter.hpp new file mode 100644 index 0000000000..2ff206464d --- /dev/null +++ b/components/lua_ui/adapter.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_LUAUI_ADAPTER +#define OPENMW_LUAUI_ADAPTER + +#include + +namespace LuaUi +{ + class WidgetExtension; + struct Element; + class LuaAdapter : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(LuaAdapter) + + public: + LuaAdapter(); + + void attach(const std::shared_ptr& element); + void detach(); + bool empty() { return mElement.get() == nullptr; } + + private: + WidgetExtension* mContent; + std::shared_ptr mElement; + void attachElement(); + void detachElement(); + }; +} + +#endif // !OPENMW_LUAUI_ADAPTER diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 5baddb98d8..9baec82269 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -4,6 +4,7 @@ #include "content.hpp" #include "util.hpp" +#include "widget.hpp" namespace LuaUi { @@ -209,14 +210,26 @@ namespace LuaUi sAllElements.erase(this); } - void Element::attachToWidget(MyGUI::Widget* w) + void Element::attachToWidget(WidgetExtension* w) { - if (mAttachedTo && w) + if (mAttachedTo) throw std::logic_error("A UI element can't be attached to two widgets at once"); mAttachedTo = w; updateAttachment(); } + void Element::detachFromWidget() + { + if (mRoot) + { + mRoot->onSizeChange({}); + mRoot->widget()->detachFromWidget(); + } + if (mAttachedTo) + mAttachedTo->setChildren({}); + mAttachedTo = nullptr; + } + void Element::updateAttachment() { if (!mRoot) @@ -225,18 +238,17 @@ namespace LuaUi { if (!mLayer.empty()) Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; - if (mRoot->widget()->getParent() != mAttachedTo) - { - mRoot->widget()->attachToWidget(mAttachedTo); - mRoot->updateCoord(); - } - } - else - { - if (mRoot->widget()->getParent() != nullptr) + mAttachedTo->setChildren({ mRoot }); + auto callback = [this](MyGUI::IntSize size) { - mRoot->widget()->detachFromWidget(); - } + if (!mAttachedTo) + return; + mAttachedTo->setForcedSize(mRoot->widget()->getSize()); + mAttachedTo->updateCoord(); + }; + mRoot->onSizeChange(callback); + mRoot->updateCoord(); + callback(mRoot->widget()->getSize()); } } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index 95d0aa2ebc..13f4ea052d 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -10,7 +10,7 @@ namespace LuaUi static std::shared_ptr make(sol::table layout); WidgetExtension* mRoot; - MyGUI::Widget* mAttachedTo; + WidgetExtension* mAttachedTo; sol::table mLayout; std::string mLayer; bool mUpdate; @@ -24,7 +24,8 @@ namespace LuaUi friend void clearUserInterface(); - void attachToWidget(MyGUI::Widget* w = nullptr); + void attachToWidget(WidgetExtension* w); + void detachFromWidget(); private: Element(sol::table layout); diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index 2695b9f25d..bf7788597a 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -1,8 +1,10 @@ #include "scriptsettings.hpp" #include +#include #include "element.hpp" +#include "adapter.hpp" namespace LuaUi { @@ -19,7 +21,7 @@ namespace LuaUi if (!element.get()) Log(Debug::Warning) << "A script settings page has no UI element assigned"; return { - name, description, element.get() + name, description, element }; } } @@ -44,13 +46,14 @@ namespace LuaUi allPages.clear(); } - void attachToWidget(size_t index, MyGUI::Widget* widget) + void attachPageAt(size_t index, LuaAdapter* adapter) { if (index < allPages.size()) { ScriptSettingsPage page = parse(allPages[index]); - if (page.mElement) - page.mElement->attachToWidget(widget); + adapter->detach(); + if (page.mElement.get()) + adapter->attach(page.mElement); } } } diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index 3b51aff901..050722c249 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_LUAUI_SCRIPTSETTINGS #define OPENMW_LUAUI_SCRIPTSETTINGS -#include #include #include @@ -9,18 +8,19 @@ namespace LuaUi { + class LuaAdapter; struct Element; struct ScriptSettingsPage { std::string mName; std::string mDescription; - Element* mElement; // TODO: figure out if this can lead to use after free + std::shared_ptr mElement; }; size_t scriptSettingsPageCount(); ScriptSettingsPage scriptSettingsPageAt(size_t index); void registerSettingsPage(const sol::table& options); void clearSettings(); - void attachToWidget(size_t index, MyGUI::Widget* widget = nullptr); + void attachPageAt(size_t index, LuaAdapter* adapter); } #endif // !OPENMW_LUAUI_SCRIPTSETTINGS diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 3987765617..24a1442a53 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -2,6 +2,7 @@ #include +#include "adapter.hpp" #include "widget.hpp" #include "text.hpp" #include "textedit.hpp" @@ -16,6 +17,7 @@ namespace LuaUi void registerAllWidgets() { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 027adc44ca..4224598ca1 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -194,6 +194,11 @@ namespace LuaUi mForcedCoord = offset; } + void WidgetExtension::setForcedSize(const MyGUI::IntSize& size) + { + mForcedCoord = size; + } + void WidgetExtension::updateCoord() { MyGUI::IntCoord oldCoord = mWidget->getCoord(); @@ -202,7 +207,11 @@ namespace LuaUi if (oldCoord != newCoord) mWidget->setCoord(newCoord); if (oldCoord.size() != newCoord.size()) + { updateChildrenCoord(); + if (mOnSizeChange.has_value()) + mOnSizeChange.value()(newCoord.size()); + } } void WidgetExtension::setProperties(sol::object props) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 4085963137..1eff2a113b 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -2,6 +2,7 @@ #define OPENMW_LUAUI_WIDGET #include +#include #include #include @@ -44,6 +45,7 @@ namespace LuaUi MyGUI::IntCoord forcedCoord(); void setForcedCoord(const MyGUI::IntCoord& offset); + void setForcedSize(const MyGUI::IntSize& size); void updateCoord(); const sol::table& getLayout() { return mLayout; } @@ -55,6 +57,11 @@ namespace LuaUi return parseExternal(mExternal, name, defaultValue); } + void onSizeChange(const std::optional>& callback) + { + mOnSizeChange = callback; + } + protected: virtual void initialize(); sol::table makeTable() const; @@ -119,6 +126,8 @@ namespace LuaUi void mouseRelease(MyGUI::Widget*, int, int, MyGUI::MouseButton); void focusGain(MyGUI::Widget*, MyGUI::Widget*); void focusLoss(MyGUI::Widget*, MyGUI::Widget*); + + std::optional> mOnSizeChange; }; class LuaWidget : public MyGUI::Widget, public WidgetExtension diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index da64735c79..d6ac4f43fa 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -664,6 +664,7 @@ + From f07f05ddd37389413128a0f12a0342360eee17d7 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 29 Jan 2022 23:40:31 +0100 Subject: [PATCH 2035/2859] Add Container widget type, use it to make Adapter code less hacky --- components/CMakeLists.txt | 2 +- components/lua_ui/adapter.cpp | 17 ++++++--------- components/lua_ui/adapter.hpp | 4 ++-- components/lua_ui/container.cpp | 35 ++++++++++++++++++++++++++++++ components/lua_ui/container.hpp | 21 ++++++++++++++++++ components/lua_ui/element.cpp | 12 ----------- components/lua_ui/util.cpp | 3 +++ components/lua_ui/widget.cpp | 38 +++++++++++++++++++++++++-------- components/lua_ui/widget.hpp | 10 +++++++++ 9 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 components/lua_ui/container.cpp create mode 100644 components/lua_ui/container.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 58e6d52ead..3ca08c4b7b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -166,7 +166,7 @@ add_component_dir (queries add_component_dir (lua_ui properties widget element util layers content scriptsettings - adapter text textedit window image + adapter text textedit window image container ) diff --git a/components/lua_ui/adapter.cpp b/components/lua_ui/adapter.cpp index 9a109d5d1f..9d37e2cb1a 100644 --- a/components/lua_ui/adapter.cpp +++ b/components/lua_ui/adapter.cpp @@ -3,6 +3,7 @@ #include #include "element.hpp" +#include "container.hpp" namespace LuaUi { @@ -15,18 +16,12 @@ namespace LuaUi : mElement(nullptr) , mContent(nullptr) { - MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( - "LuaWidget", "", - MyGUI::IntCoord(), MyGUI::Align::Default, - std::string(), ""); - - mContent = dynamic_cast(widget); - if (!mContent) - throw std::runtime_error("Invalid widget!"); - mContent->initialize(luaState, widget); - mContent->onSizeChange([this](MyGUI::IntSize size) + mContent = MyGUI::Gui::getInstancePtr()->createWidget( + "", MyGUI::IntCoord(), MyGUI::Align::Default, "", ""); + mContent->initialize(luaState, mContent); + mContent->onCoordChange([this](WidgetExtension* ext, MyGUI::IntCoord coord) { - setSize(size); + setSize(coord.size()); }); mContent->widget()->attachToWidget(this); } diff --git a/components/lua_ui/adapter.hpp b/components/lua_ui/adapter.hpp index 2ff206464d..e18f04c1f9 100644 --- a/components/lua_ui/adapter.hpp +++ b/components/lua_ui/adapter.hpp @@ -5,7 +5,7 @@ namespace LuaUi { - class WidgetExtension; + class LuaContainer; struct Element; class LuaAdapter : public MyGUI::Widget { @@ -19,7 +19,7 @@ namespace LuaUi bool empty() { return mElement.get() == nullptr; } private: - WidgetExtension* mContent; + LuaContainer* mContent; std::shared_ptr mElement; void attachElement(); void detachElement(); diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp new file mode 100644 index 0000000000..867378744b --- /dev/null +++ b/components/lua_ui/container.cpp @@ -0,0 +1,35 @@ +#include "container.hpp" + +#include + +namespace LuaUi +{ + void LuaContainer::updateChildren() + { + WidgetExtension::updateChildren(); + for (auto w : children()) + { + w->onCoordChange([this](WidgetExtension* child, MyGUI::IntCoord coord) + { updateSizeToFit(); }); + } + updateSizeToFit(); + } + + MyGUI::IntSize LuaContainer::childScalingSize() + { + return MyGUI::IntSize(); + } + + void LuaContainer::updateSizeToFit() + { + MyGUI::IntSize size; + for (auto w : children()) + { + MyGUI::IntCoord coord = w->widget()->getCoord(); + size.width = std::max(size.width, coord.left + coord.width); + size.height = std::max(size.height, coord.top + coord.height); + } + setForcedSize(size); + updateCoord(); + } +} diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp new file mode 100644 index 0000000000..a005b7ae53 --- /dev/null +++ b/components/lua_ui/container.hpp @@ -0,0 +1,21 @@ +#ifndef OPENMW_LUAUI_CONTAINER +#define OPENMW_LUAUI_CONTAINER + +#include "widget.hpp" + +namespace LuaUi +{ + class LuaContainer : public WidgetExtension, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(LuaContainer) + + protected: + virtual void updateChildren() override; + virtual MyGUI::IntSize childScalingSize() override; + + private: + void updateSizeToFit(); + }; +} + +#endif // !OPENMW_LUAUI_CONTAINER diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 9baec82269..59a3b13d55 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -221,10 +221,7 @@ namespace LuaUi void Element::detachFromWidget() { if (mRoot) - { - mRoot->onSizeChange({}); mRoot->widget()->detachFromWidget(); - } if (mAttachedTo) mAttachedTo->setChildren({}); mAttachedTo = nullptr; @@ -239,16 +236,7 @@ namespace LuaUi if (!mLayer.empty()) Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; mAttachedTo->setChildren({ mRoot }); - auto callback = [this](MyGUI::IntSize size) - { - if (!mAttachedTo) - return; - mAttachedTo->setForcedSize(mRoot->widget()->getSize()); - mAttachedTo->updateCoord(); - }; - mRoot->onSizeChange(callback); mRoot->updateCoord(); - callback(mRoot->widget()->getSize()); } } } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 24a1442a53..7e81fc579c 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -8,6 +8,7 @@ #include "textedit.hpp" #include "window.hpp" #include "image.hpp" +#include "container.hpp" #include "element.hpp" #include "scriptsettings.hpp" @@ -23,6 +24,7 @@ namespace LuaUi MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("BasisSkin"); } @@ -34,6 +36,7 @@ namespace LuaUi { "LuaTextEdit", "TextEdit" }, { "LuaWindow", "Window" }, { "LuaImage", "Image" }, + { "LuaContainer", "Container" }, }; return types; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 4224598ca1..b021c688fa 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -14,10 +14,11 @@ namespace LuaUi , mAbsoluteCoord() , mRelativeCoord() , mAnchor() - , mLua{ nullptr } - , mWidget{ nullptr } + , mLua(nullptr) + , mWidget(nullptr) , mSlot(this) - , mLayout{ sol::nil } + , mParent(nullptr) + , mLayout(sol::nil) {} void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) @@ -64,6 +65,8 @@ namespace LuaUi mWidget->eventKeySetFocus.clear(); mWidget->eventKeyLostFocus.clear(); + mOnSizeChange.reset(); + for (WidgetExtension* w : mChildren) w->deinitialize(); for (WidgetExtension* w : mTemplateChildren) @@ -72,6 +75,7 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { + ext->mParent = this; ext->widget()->attachToWidget(mSlot->widget()); ext->updateCoord(); } @@ -150,6 +154,7 @@ namespace LuaUi mChildren[i] = children[i]; attach(mChildren[i]); } + updateChildren(); } void WidgetExtension::setTemplateChildren(const std::vector& children) @@ -212,6 +217,8 @@ namespace LuaUi if (mOnSizeChange.has_value()) mOnSizeChange.value()(newCoord.size()); } + if (oldCoord != newCoord && mOnCoordChange.has_value()) + mOnCoordChange.value()(this, newCoord); } void WidgetExtension::setProperties(sol::object props) @@ -240,23 +247,31 @@ namespace LuaUi w->updateCoord(); } + MyGUI::IntSize WidgetExtension::parentSize() + { + if (mParent) + return mParent->childScalingSize(); + else + return widget()->getParentSize(); + } + MyGUI::IntSize WidgetExtension::calculateSize() { - const MyGUI::IntSize& parentSize = mWidget->getParentSize(); + MyGUI::IntSize pSize = parentSize(); MyGUI::IntSize newSize; newSize = mAbsoluteCoord.size() + mForcedCoord.size(); - newSize.width += mRelativeCoord.width * parentSize.width; - newSize.height += mRelativeCoord.height * parentSize.height; + newSize.width += mRelativeCoord.width * pSize.width; + newSize.height += mRelativeCoord.height * pSize.height; return newSize; } MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) { - const MyGUI::IntSize& parentSize = mWidget->getParentSize(); + MyGUI::IntSize pSize = parentSize(); MyGUI::IntPoint newPosition; newPosition = mAbsoluteCoord.point() + mForcedCoord.point(); - newPosition.left += mRelativeCoord.left * parentSize.width - mAnchor.width * size.width; - newPosition.top += mRelativeCoord.top * parentSize.height - mAnchor.height * size.height; + newPosition.left += mRelativeCoord.left * pSize.width - mAnchor.width * size.width; + newPosition.top += mRelativeCoord.top * pSize.height - mAnchor.height * size.height; return newPosition; } @@ -268,6 +283,11 @@ namespace LuaUi return newCoord; } + MyGUI::IntSize WidgetExtension::childScalingSize() + { + return widget()->getSize(); + } + void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const { auto it = mCallbacks.find(name); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 1eff2a113b..a5adb33130 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -57,6 +57,11 @@ namespace LuaUi return parseExternal(mExternal, name, defaultValue); } + void onCoordChange(const std::optional>& callback) + { + mOnCoordChange = callback; + } + void onSizeChange(const std::optional>& callback) { mOnSizeChange = callback; @@ -68,9 +73,11 @@ 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 calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); + virtual MyGUI::IntSize childScalingSize(); template T propertyValue(std::string_view name, const T& defaultValue) @@ -83,6 +90,7 @@ namespace LuaUi virtual void updateTemplate(); virtual void updateProperties(); + virtual void updateChildren() {}; void triggerEvent(std::string_view name, const sol::object& argument) const; @@ -108,6 +116,7 @@ namespace LuaUi sol::object mProperties; sol::object mTemplateProperties; sol::object mExternal; + WidgetExtension* mParent; void attach(WidgetExtension* ext); @@ -127,6 +136,7 @@ namespace LuaUi void focusGain(MyGUI::Widget*, MyGUI::Widget*); void focusLoss(MyGUI::Widget*, MyGUI::Widget*); + std::optional> mOnCoordChange; std::optional> mOnSizeChange; }; From 67641dcdb7106cf7ea528ae305aef4a61186c142 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 29 Jan 2022 23:43:08 +0100 Subject: [PATCH 2036/2859] Fix compile errors --- apps/openmw/mwlua/uibindings.cpp | 2 +- components/CMakeLists.txt | 3 ++- components/lua_ui/adapter.cpp | 14 +++++++------- components/lua_ui/adapter.hpp | 6 ++++-- components/lua_ui/registerscriptsettings.hpp | 13 +++++++++++++ components/lua_ui/scriptsettings.cpp | 1 + components/lua_ui/scriptsettings.hpp | 3 +-- components/lua_ui/util.cpp | 2 +- components/lua_ui/widget.cpp | 2 +- 9 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 components/lua_ui/registerscriptsettings.hpp diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index bd19dde114..073ecc7822 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include "context.hpp" #include "actions.hpp" diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3ca08c4b7b..9a1ffc847c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -165,7 +165,8 @@ add_component_dir (queries ) add_component_dir (lua_ui - properties widget element util layers content scriptsettings + registerscriptsettings scriptsettings + properties widget element util layers content adapter text textedit window image container ) diff --git a/components/lua_ui/adapter.cpp b/components/lua_ui/adapter.cpp index 9d37e2cb1a..aa04f4c374 100644 --- a/components/lua_ui/adapter.cpp +++ b/components/lua_ui/adapter.cpp @@ -14,16 +14,16 @@ namespace LuaUi LuaAdapter::LuaAdapter() : mElement(nullptr) - , mContent(nullptr) + , mContainer(nullptr) { - mContent = MyGUI::Gui::getInstancePtr()->createWidget( + mContainer = MyGUI::Gui::getInstancePtr()->createWidget( "", MyGUI::IntCoord(), MyGUI::Align::Default, "", ""); - mContent->initialize(luaState, mContent); - mContent->onCoordChange([this](WidgetExtension* ext, MyGUI::IntCoord coord) + mContainer->initialize(luaState, mContainer); + mContainer->onCoordChange([this](WidgetExtension* ext, MyGUI::IntCoord coord) { setSize(coord.size()); }); - mContent->widget()->attachToWidget(this); + mContainer->widget()->attachToWidget(this); } void LuaAdapter::attach(const std::shared_ptr& element) @@ -31,7 +31,7 @@ namespace LuaUi detachElement(); mElement = element; attachElement(); - setSize(mContent->widget()->getSize()); + setSize(mContainer->widget()->getSize()); // workaround for MyGUI bug // parent visibility doesn't affect added children @@ -48,7 +48,7 @@ namespace LuaUi void LuaAdapter::attachElement() { if (mElement.get()) - mElement->attachToWidget(mContent); + mElement->attachToWidget(mContainer); } void LuaAdapter::detachElement() diff --git a/components/lua_ui/adapter.hpp b/components/lua_ui/adapter.hpp index e18f04c1f9..2cb08b7367 100644 --- a/components/lua_ui/adapter.hpp +++ b/components/lua_ui/adapter.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_LUAUI_ADAPTER #define OPENMW_LUAUI_ADAPTER +#include + #include namespace LuaUi @@ -16,11 +18,11 @@ namespace LuaUi void attach(const std::shared_ptr& element); void detach(); - bool empty() { return mElement.get() == nullptr; } private: - LuaContainer* mContent; std::shared_ptr mElement; + LuaContainer* mContainer; + void attachElement(); void detachElement(); }; diff --git a/components/lua_ui/registerscriptsettings.hpp b/components/lua_ui/registerscriptsettings.hpp new file mode 100644 index 0000000000..fb794468da --- /dev/null +++ b/components/lua_ui/registerscriptsettings.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_LUAUI_REGISTERSCRIPTSETTINGS +#define OPENMW_LUAUI_REGISTERSCRIPTSETTINGS + +#include + +namespace LuaUi +{ + // implemented in scriptsettings.cpp + void registerSettingsPage(const sol::table& options); + void clearSettings(); +} + +#endif // !OPENMW_LUAUI_REGISTERSCRIPTSETTINGS diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index bf7788597a..a1671186a4 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -3,6 +3,7 @@ #include #include +#include "registerscriptsettings.hpp" #include "element.hpp" #include "adapter.hpp" diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index 050722c249..245c3e0449 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -18,8 +19,6 @@ namespace LuaUi }; size_t scriptSettingsPageCount(); ScriptSettingsPage scriptSettingsPageAt(size_t index); - void registerSettingsPage(const sol::table& options); - void clearSettings(); void attachPageAt(size_t index, LuaAdapter* adapter); } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 7e81fc579c..8a7fd4b65b 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -11,7 +11,7 @@ #include "container.hpp" #include "element.hpp" -#include "scriptsettings.hpp" +#include "registerscriptsettings.hpp" namespace LuaUi { diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index b021c688fa..f9f6dc41ff 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -17,8 +17,8 @@ namespace LuaUi , mLua(nullptr) , mWidget(nullptr) , mSlot(this) - , mParent(nullptr) , mLayout(sol::nil) + , mParent(nullptr) {} void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) From 01d65a14f125fd652a33b8eb859a5b4a43beb5a4 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 30 Jan 2022 14:25:20 +0100 Subject: [PATCH 2037/2859] Implement more advanced search, sort script setting pages by alphabet and filter match quality --- apps/openmw/mwgui/settingswindow.cpp | 60 ++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index f556bbdd5b..a8c430f2a2 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -729,6 +729,42 @@ namespace MWGui mScriptDisabled->setPosition({0, 0}); mScriptDisabled->setSize(parentSize); } + + namespace + { + std::string escapeRegex(const std::string& str) + { + static std::regex specialChars(R"r([\^\.\[\$\(\)\|\*\+\?\{])r", std::regex_constants::extended); + return std::regex_replace(str, specialChars, R"(\$&)"); + } + + std::regex wordSearch(const std::string& query) + { + static std::regex wordsRegex(R"([^[:space:]]+)", std::regex_constants::extended); + auto wordsBegin = std::sregex_iterator(query.begin(), query.end(), wordsRegex); + auto wordsEnd = std::sregex_iterator(); + std::string searchRegex("("); + for (auto it = wordsBegin; it != wordsEnd; ++it) + { + if (it != wordsBegin) + searchRegex += '|'; + searchRegex += escapeRegex(query.substr(it->position(), it->length())); + } + searchRegex += ')'; + // query had only whitespace characters + if (searchRegex == "()") + searchRegex = "^(.*)$"; + static auto flags = std::regex_constants::icase | std::regex_constants::extended; + return std::regex(searchRegex, flags); + } + + int weightedSearch(const std::regex& regex, const std::string& text) + { + std::smatch matches; + std::regex_search(text, matches, regex); + return matches.size(); + } + } void SettingsWindow::renderScriptSettings() { @@ -737,18 +773,26 @@ namespace MWGui mScriptList->removeAllItems(); mScriptView->setCanvasSize({0, 0}); - std::string filter(".*"); - filter += mScriptFilter->getCaption(); - filter += ".*"; - auto flags = std::regex_constants::icase; - std::regex filterRegex(filter, flags); - + std::regex searchRegex = wordSearch(mScriptFilter->getCaption()); + std::vector> weightedPages; + weightedPages.reserve(LuaUi::scriptSettingsPageCount()); for (size_t i = 0; i < LuaUi::scriptSettingsPageCount(); ++i) { LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i); - if (std::regex_match(page.mName, filterRegex) || std::regex_match(page.mDescription, filterRegex)) - mScriptList->addItem(page.mName, i); + int nameSearch = 2 * weightedSearch(searchRegex, page.mName); + int descriptionSearch = weightedSearch(searchRegex, page.mDescription); + int search = nameSearch + descriptionSearch; + if (search > 0) + weightedPages.push_back({ i, page, search }); } + std::sort(weightedPages.begin(), weightedPages.end(), [](const auto& a, const auto& b) + { + const auto& [iA, pageA, weightA] = a; + const auto& [iB, pageB, weightB] = b; + return weightA == weightB ? pageA.mName < pageB.mName : weightA > weightB; + }); + for (const auto & [i, page, weight] : weightedPages) + mScriptList->addItem(page.mName, i); // Hide script settings tab when the game world isn't loaded and scripts couldn't add their settings bool disabled = LuaUi::scriptSettingsPageCount() == 0; From 7f796d148e042ba362de0dcab413ef1800566969 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 30 Jan 2022 15:50:04 +0100 Subject: [PATCH 2038/2859] Clean up --- components/lua_ui/util.cpp | 1 - components/lua_ui/widget.cpp | 6 +----- components/lua_ui/widget.hpp | 6 ------ 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 8a7fd4b65b..cd2cb2c077 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -36,7 +36,6 @@ namespace LuaUi { "LuaTextEdit", "TextEdit" }, { "LuaWindow", "Window" }, { "LuaImage", "Image" }, - { "LuaContainer", "Container" }, }; return types; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index f9f6dc41ff..70ed6b64fe 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -65,7 +65,7 @@ namespace LuaUi mWidget->eventKeySetFocus.clear(); mWidget->eventKeyLostFocus.clear(); - mOnSizeChange.reset(); + mOnCoordChange.reset(); for (WidgetExtension* w : mChildren) w->deinitialize(); @@ -212,11 +212,7 @@ namespace LuaUi if (oldCoord != newCoord) mWidget->setCoord(newCoord); if (oldCoord.size() != newCoord.size()) - { updateChildrenCoord(); - if (mOnSizeChange.has_value()) - mOnSizeChange.value()(newCoord.size()); - } if (oldCoord != newCoord && mOnCoordChange.has_value()) mOnCoordChange.value()(this, newCoord); } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index a5adb33130..63af5b3cae 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -62,11 +62,6 @@ namespace LuaUi mOnCoordChange = callback; } - void onSizeChange(const std::optional>& callback) - { - mOnSizeChange = callback; - } - protected: virtual void initialize(); sol::table makeTable() const; @@ -137,7 +132,6 @@ namespace LuaUi void focusLoss(MyGUI::Widget*, MyGUI::Widget*); std::optional> mOnCoordChange; - std::optional> mOnSizeChange; }; class LuaWidget : public MyGUI::Widget, public WidgetExtension From ef1e72dc17900a882c7dd8993ab80b08c4dc73b9 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 1 Feb 2022 19:20:47 +0100 Subject: [PATCH 2039/2859] Revert from settings description to searchHints --- apps/openmw/mwgui/settingswindow.cpp | 19 +------------------ apps/openmw/mwgui/settingswindow.hpp | 2 -- components/lua_ui/scriptsettings.cpp | 4 ++-- components/lua_ui/scriptsettings.hpp | 2 +- files/mygui/openmw_settings_window.layout | 8 -------- 5 files changed, 4 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index a8c430f2a2..14587513df 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -244,7 +244,6 @@ namespace MWGui getWidget(mScriptView, "ScriptView"); getWidget(mScriptAdapter, "ScriptAdapter"); getWidget(mScriptDisabled, "ScriptDisabled"); - getWidget(mScriptDescription, "ScriptDescription"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -333,7 +332,6 @@ namespace MWGui mScriptFilter->eventEditTextChange += MyGUI::newDelegate(this, &SettingsWindow::onScriptFilterChange); mScriptList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SettingsWindow::onScriptListSelection); - mScriptList->eventListMouseItemFocus += MyGUI::newDelegate(this, &SettingsWindow::onScriptListFocus); } void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/) @@ -780,7 +778,7 @@ namespace MWGui { LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i); int nameSearch = 2 * weightedSearch(searchRegex, page.mName); - int descriptionSearch = weightedSearch(searchRegex, page.mDescription); + int descriptionSearch = weightedSearch(searchRegex, page.mSearchHints); int search = nameSearch + descriptionSearch; if (search > 0) weightedPages.push_back({ i, page, search }); @@ -820,21 +818,6 @@ namespace MWGui mScriptView->setCanvasSize(canvasSize); } - void SettingsWindow::onScriptListFocus(MyGUI::ListBox*, size_t index) - { - if (index >= mScriptList->getItemCount()) - { - mScriptDescription->setVisible(false); - mScriptView->setVisible(true); - } - else { - size_t page = *mScriptList->getItemDataAt(index); - mScriptDescription->setCaption(LuaUi::scriptSettingsPageAt(page).mDescription); - mScriptDescription->setVisible(true); - mScriptView->setVisible(false); - } - } - void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) { int actionId = *_sender->getUserData(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 217dcf3051..1a888257a8 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -52,7 +52,6 @@ namespace MWGui MyGUI::ScrollView* mScriptView; LuaUi::LuaAdapter* mScriptAdapter; MyGUI::EditBox* mScriptDisabled; - MyGUI::EditBox* mScriptDescription; int mCurrentPage; void onTabChanged(MyGUI::TabControl* _sender, size_t index); @@ -84,7 +83,6 @@ namespace MWGui void onScriptFilterChange(MyGUI::EditBox*); void onScriptListSelection(MyGUI::ListBox*, size_t index); - void onScriptListFocus(MyGUI::ListBox*, size_t index); void apply(); diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index a1671186a4..f2ab84bde0 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -15,14 +15,14 @@ namespace LuaUi ScriptSettingsPage parse(const sol::table& options) { auto name = options.get_or("name", std::string()); - auto description = options.get_or("description", std::string()); + auto searchHints = options.get_or("searchHints", std::string()); auto element = options.get_or>("element", nullptr); if (name.empty()) Log(Debug::Warning) << "A script settings page has an empty name"; if (!element.get()) Log(Debug::Warning) << "A script settings page has no UI element assigned"; return { - name, description, element + name, searchHints, element }; } } diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index 245c3e0449..c468f9eb0b 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -14,7 +14,7 @@ namespace LuaUi struct ScriptSettingsPage { std::string mName; - std::string mDescription; + std::string mSearchHints; std::shared_ptr mElement; }; size_t scriptSettingsPageCount(); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index d6ac4f43fa..13f5ada24b 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -666,14 +666,6 @@ - - - - - - - - From 2185fd29c5ecd149fabeea83f84a82b6beb54d6a Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 1 Feb 2022 19:26:05 +0100 Subject: [PATCH 2040/2859] Document ui.registerSettingsPage --- files/lua_api/openmw/ui.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index c60a4e1abd..997c39780e 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -36,6 +36,11 @@ -- @param #Layout layout -- @return #Element +--- +-- Adds a settings page to main menu setting's Scripts tab. +-- @function [parent=#ui] regregisterSettingsPageister +-- @param #SettingsPage page + --- -- Layout -- @type Layout @@ -137,4 +142,11 @@ -- @field openmw.util#Vector2 offset Position of the mouse cursor relative to the widget -- @field #number button Mouse button which triggered the event (could be nil) +--- +-- Settings page parameters, passed as an argument to uyi.registerSettingsPage +-- @type SettingsPage +-- @field #string name Name of the page, displayed in the list, used for search +-- @field #string searchHints Additional keywords used in search, not displayed anywhere +-- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size (set `size` field in `props`, see Widget documentation, `relativeSize` is ignored) + return nil From 8752f78fa45ebd1212cd2874ed0e105739369c23 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 11 Jan 2022 21:03:10 +0100 Subject: [PATCH 2041/2859] Remove weaponless, non-biped distinction --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 106 +------------------------- apps/openmw/mwmechanics/character.hpp | 1 - 3 files changed, 4 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2baee6552..710d0bff12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed Bug #4949: Incorrect particle lighting + Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0f8e2ebd97..820bfad07b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -707,9 +707,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (mPtr.getClass().isActor()) refreshHitRecoilAnims(idle); - std::string weap; - if (mPtr.getClass().hasInventoryStore(mPtr)) - weap = getWeaponType(mWeaponType)->mShortGroup; + std::string weap = getWeaponType(mWeaponType)->mShortGroup; refreshJumpAnims(weap, jump, idle, force); refreshMovementAnims(weap, movement, idle, force); @@ -1119,97 +1117,6 @@ void CharacterController::updateIdleStormState(bool inwater) } } -bool CharacterController::updateCreatureState() -{ - const MWWorld::Class &cls = mPtr.getClass(); - CreatureStats &stats = cls.getCreatureStats(mPtr); - - int weapType = ESM::Weapon::None; - if(stats.getDrawState() == DrawState_Weapon) - weapType = ESM::Weapon::HandToHand; - else if (stats.getDrawState() == DrawState_Spell) - weapType = ESM::Weapon::Spell; - - if (weapType != mWeaponType) - { - mWeaponType = weapType; - if (mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->disable(mCurrentWeapon); - } - - if(getAttackingOrSpell()) - { - if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) - { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - - std::string startKey = "start"; - std::string stopKey = "stop"; - if (weapType == ESM::Weapon::Spell) - { - const std::string spellid = stats.getSpells().getSelectedSpell(); - bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); - - if (!spellid.empty() && canCast) - { - MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); - cast.playSpellCastingEffects(spellid, false); - - if (!mAnimation->hasAnimation("spellcast")) - { - MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; - } - else - { - const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); - - switch(effectentry.mRange) - { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; - } - - startKey = mAttackType + " " + startKey; - stopKey = mAttackType + " " + stopKey; - mCurrentWeapon = "spellcast"; - } - } - else - mCurrentWeapon = ""; - } - - if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation - { - mCurrentWeapon = chooseRandomAttackAnimation(); - } - - if (!mCurrentWeapon.empty()) - { - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::BlendMask_All, true, - 1, startKey, stopKey, - 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; - - mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - - if (weapType == ESM::Weapon::HandToHand) - playSwishSound(0.0f); - } - } - - setAttackingOrSpell(false); - } - - bool animPlaying = mAnimation->getInfo(mCurrentWeapon); - if (!animPlaying) - mUpperBodyState = UpperCharState_Nothing; - return false; -} - bool CharacterController::updateCarriedLeftVisible(const int weaptype) const { // Shields/torches shouldn't be visible during any operation involving two hands @@ -2346,11 +2253,7 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. - if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) - forcestateupdate = updateWeaponState(idlestate) || forcestateupdate; - else - forcestateupdate = updateCreatureState() || forcestateupdate; + forcestateupdate = updateWeaponState(idlestate) || forcestateupdate; refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); updateIdleStormState(inwater); @@ -2879,10 +2782,7 @@ bool CharacterController::readyToStartAttack() const if (mHitState != CharState_None && mHitState != CharState_Block) return false; - if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) - return mUpperBodyState == UpperCharState_WeapEquiped; - else - return mUpperBodyState == UpperCharState_Nothing; + return mUpperBodyState == UpperCharState_WeapEquiped; } float CharacterController::getAttackStrength() const diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 1647980541..8c410bba25 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -206,7 +206,6 @@ class CharacterController : public MWRender::Animation::TextKeyListener void clearAnimQueue(bool clearPersistAnims = false); bool updateWeaponState(CharacterState& idle); - bool updateCreatureState(); void updateIdleStormState(bool inwater); std::string chooseRandomAttackAnimation() const; From f68c0c41a9875cfa7413b3e89ff33d4bb28d8d9f Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 1 Feb 2022 22:03:01 +0100 Subject: [PATCH 2042/2859] Prioritize setting pages with hits in the name when searching --- apps/openmw/mwgui/settingswindow.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 14587513df..80a034fd2a 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -756,7 +756,8 @@ namespace MWGui return std::regex(searchRegex, flags); } - int weightedSearch(const std::regex& regex, const std::string& text) + // use double to guarantee no overflow when casting from size_t + double weightedSearch(const std::regex& regex, const std::string& text) { std::smatch matches; std::regex_search(text, matches, regex); @@ -772,24 +773,23 @@ namespace MWGui mScriptView->setCanvasSize({0, 0}); std::regex searchRegex = wordSearch(mScriptFilter->getCaption()); - std::vector> weightedPages; + std::vector> weightedPages; weightedPages.reserve(LuaUi::scriptSettingsPageCount()); for (size_t i = 0; i < LuaUi::scriptSettingsPageCount(); ++i) { LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i); - int nameSearch = 2 * weightedSearch(searchRegex, page.mName); - int descriptionSearch = weightedSearch(searchRegex, page.mSearchHints); - int search = nameSearch + descriptionSearch; - if (search > 0) - weightedPages.push_back({ i, page, search }); + double nameSearch = 2 * weightedSearch(searchRegex, page.mName); + double hintSearch = weightedSearch(searchRegex, page.mSearchHints); + if ((nameSearch + hintSearch) > 0) + weightedPages.push_back({ i, page, -nameSearch, -hintSearch }); } std::sort(weightedPages.begin(), weightedPages.end(), [](const auto& a, const auto& b) { - const auto& [iA, pageA, weightA] = a; - const auto& [iB, pageB, weightB] = b; - return weightA == weightB ? pageA.mName < pageB.mName : weightA > weightB; + const auto& [iA, pageA, nameA, hintA] = a; + const auto& [iB, pageB, nameB, hintB] = b; + return std::tie(nameA, hintA, pageA.mName) < std::tie(nameB, hintB, pageB.mName); }); - for (const auto & [i, page, weight] : weightedPages) + for (const auto & [i, page, name, hint] : weightedPages) mScriptList->addItem(page.mName, i); // Hide script settings tab when the game world isn't loaded and scripts couldn't add their settings @@ -814,8 +814,7 @@ namespace MWGui mCurrentPage = *mScriptList->getItemDataAt(index); LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); } - MyGUI::IntSize canvasSize = mScriptAdapter->getSize(); - mScriptView->setCanvasSize(canvasSize); + mScriptView->setCanvasSize(mScriptAdapter->getSize()); } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) From 47c37e5849a91969ec191a28383559cdeeb6159d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 1 Feb 2022 23:42:56 +0000 Subject: [PATCH 2043/2859] Lua command `object:activateBy(actor)` and handler `onActivate` --- AUTHORS.md | 2 +- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwlua/actions.cpp | 31 ++++++++++++++++--- apps/openmw/mwlua/actions.hpp | 14 +++++++++ apps/openmw/mwlua/localscripts.cpp | 6 +++- apps/openmw/mwlua/localscripts.hpp | 7 ++++- apps/openmw/mwlua/luamanagerimp.cpp | 5 +++ apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/objectbindings.cpp | 8 +++++ apps/openmw/mwscript/interpretercontext.cpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 3 +- .../lua-scripting/engine_handlers.rst | 6 ++++ files/lua_api/openmw/core.lua | 8 +++++ 13 files changed, 86 insertions(+), 8 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 2ddaa03cf6..ecfc03dc1e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -218,6 +218,7 @@ Programmers tlmullis tri4ng1e Thoronador + Tobias Tribble (zackogenic) Tom Lowe (Vulpen) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) @@ -234,7 +235,6 @@ Programmers Yuri Krupenin zelurker Noah Gooder - Documentation ------------- diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 8c0c5d61de..7fa8b6df58 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -36,6 +36,7 @@ namespace MWBase virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; virtual void appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) = 0; + virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; // TODO: notify LuaManager about other events // virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0; diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index dda237fb4e..8135d14299 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -6,10 +6,13 @@ #include #include -#include "../mwworld/cellstore.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwworld/player.hpp" +#include + +#include +#include +#include +#include +#include namespace MWLua { @@ -145,4 +148,24 @@ namespace MWLua tryEquipToSlot(anySlot, item); } + void ActivateAction::apply(WorldView& worldView) const + { + MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true); + if (object.isEmpty()) + throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); + MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true); + if (actor.isEmpty()) + throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); + + MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); + std::shared_ptr action = object.getClass().activate(object, actor); + action->execute(actor); + } + + std::string ActivateAction::toString() const + { + return std::string("ActivateAction object=") + idToString(mObject) + + std::string(" actor=") + idToString(mActor); + } + } diff --git a/apps/openmw/mwlua/actions.hpp b/apps/openmw/mwlua/actions.hpp index e88b39e83c..97da953437 100644 --- a/apps/openmw/mwlua/actions.hpp +++ b/apps/openmw/mwlua/actions.hpp @@ -65,6 +65,20 @@ namespace MWLua Equipment mEquipment; }; + class ActivateAction final : public Action + { + public: + ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) + : Action(state), mObject(object), mActor(actor) {} + + void apply(WorldView&) const override; + std::string toString() const override; + + private: + ObjectId mObject; + ObjectId mActor; + }; + } #endif // MWLUA_ACTIONS_H diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 918970dc54..2b492c5c17 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -88,7 +88,7 @@ namespace MWLua : LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj) { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); - registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers}); + registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers}); } void LocalScripts::receiveEngineEvent(const EngineEvent& event) @@ -106,6 +106,10 @@ namespace MWLua mData.mIsActive = false; callEngineHandlers(mOnInactiveHandlers); } + else if constexpr (std::is_same_v) + { + callEngineHandlers(mOnActivatedHandlers, arg.mActivatingActor); + } else { static_assert(std::is_same_v); diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 68da0b8b03..10b9cd53af 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -33,11 +33,15 @@ namespace MWLua struct OnActive {}; struct OnInactive {}; + struct OnActivated + { + LObject mActivatingActor; + }; struct OnConsume { std::string mRecordId; }; - using EngineEvent = std::variant; + using EngineEvent = std::variant; void receiveEngineEvent(const EngineEvent&); @@ -48,6 +52,7 @@ namespace MWLua EngineHandlerList mOnActiveHandlers{"onActive"}; EngineHandlerList mOnInactiveHandlers{"onInactive"}; EngineHandlerList mOnConsumeHandlers{"onConsume"}; + EngineHandlerList mOnActivatedHandlers{"onActivated"}; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 58784a4629..e9146b8af4 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -352,6 +352,11 @@ namespace MWLua mLocalEngineEvents.push_back({getId(toPtr), LocalScripts::OnConsume{std::string(recordId)}}); } + void LuaManager::objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) + { + mLocalEngineEvents.push_back({getId(object), LocalScripts::OnActivated{LObject(getId(actor), mWorldView.getObjectRegistry())}}); + } + MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 753cc6ca49..b292eb3c1b 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -49,6 +49,7 @@ namespace MWLua void deregisterObject(const MWWorld::Ptr& ptr) override; void inputEvent(const InputEvent& event) override { mInputEvents.push_back(event); } void appliedToObject(const MWWorld::Ptr& toPtr, std::string_view recordId, const MWWorld::Ptr& fromPtr) override; + void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override; MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 9857170b32..9543e306c1 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -137,6 +137,14 @@ namespace MWLua const MWWorld::Class& cls = o.ptr().getClass(); return cls.getWalkSpeed(o.ptr()); }; + objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor) + { + uint32_t esmRecordType = actor.ptr().getType(); + if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_) + throw std::runtime_error("The argument of `activateBy` must be an actor who activates the object. Got: " + + ptrToString(actor.ptr())); + context.mLuaManager->addAction(std::make_unique(context.mLua, o.id(), actor.id())); + }; if constexpr (std::is_same_v) { // Only for global scripts diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 6d7dcdda1d..0b9833e097 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -12,6 +12,7 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" @@ -417,6 +418,7 @@ namespace MWScript void InterpreterContext::executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) { + MWBase::Environment::get().getLuaManager()->objectActivated(ptr, actor); std::shared_ptr action = (ptr.getClass().activate(ptr, actor)); action->execute (actor); if (action->getTarget() != MWWorld::Ptr() && action->getTarget() != ptr) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1aebe94b87..706ce4859f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3827,7 +3827,8 @@ namespace MWWorld if (object.getRefData().activate()) { - std::shared_ptr action = (object.getClass().activate(object, actor)); + MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); + std::shared_ptr action = object.getClass().activate(object, actor); action->execute (actor); } } diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 46e648d620..d247803c08 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -43,6 +43,12 @@ Engine handler is a function defined by a script, that can be called by the engi | | | can not access anything nearby, but it is possible to send | | | | an event to global scripts. | +----------------------------------+----------------------------------------------------------------------+ +| onActivated(actor) | | Called on an object when an actor activates it. Note that picking | +| | | up an item is also an activation and works this way: (1) a copy of | +| | | the item is placed to the actor's inventory, (2) count of | +| | | the original item is set to zero, (3) and only then onActivated is | +| | | called on the original item, so self.count is already zero. | ++----------------------------------+----------------------------------------------------------------------+ | onConsume(recordId) | | Called if `recordId` (e.g. a potion) is consumed. | +----------------------------------+----------------------------------------------------------------------+ | **Only for local scripts attached to a player** | diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 8415690067..95732af837 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -217,6 +217,14 @@ -- @param #string eventName -- @param eventData +------------------------------------------------------------------------------- +-- Activate the object. +-- @function [parent=#GameObject] activateBy +-- @param self +-- @param #GameObject actor The actor who activates the object +-- @usage local self = require('openmw.self') +-- object:activateBy(self) + ------------------------------------------------------------------------------- -- Returns `true` if the item is equipped on the object. -- @function [parent=#GameObject] isEquipped From aaea2bc0f6436ed5bdd82d37440383334967d556 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 1 Feb 2022 00:58:49 +0300 Subject: [PATCH 2044/2859] Implement transformBoundingSphere for both sphere types (bug #6579) --- CHANGELOG.md | 1 + components/sceneutil/util.cpp | 31 ------------------------------- components/sceneutil/util.hpp | 32 +++++++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2baee6552..56d0910d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ Bug #6519: Effects tooltips for ingredients work incorrectly Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary Bug #6544: Far from world origin objects jitter when camera is still + Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 33d8f1c8a6..b70a0e2481 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -188,37 +188,6 @@ private: osg::ref_ptr mMsaaFbo; }; -void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere) -{ - osg::BoundingSphere::vec_type xdash = bsphere._center; - xdash.x() += bsphere._radius; - xdash = xdash*matrix; - - osg::BoundingSphere::vec_type ydash = bsphere._center; - ydash.y() += bsphere._radius; - ydash = ydash*matrix; - - osg::BoundingSphere::vec_type zdash = bsphere._center; - zdash.z() += bsphere._radius; - zdash = zdash*matrix; - - bsphere._center = bsphere._center*matrix; - - xdash -= bsphere._center; - osg::BoundingSphere::value_type sqrlen_xdash = xdash.length2(); - - ydash -= bsphere._center; - osg::BoundingSphere::value_type sqrlen_ydash = ydash.length2(); - - zdash -= bsphere._center; - osg::BoundingSphere::value_type sqrlen_zdash = zdash.length2(); - - bsphere._radius = sqrlen_xdash; - if (bsphere._radius> 0) & 0xFF) / 255.0f, diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 89d3a12e97..18bf8c7728 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -49,7 +49,37 @@ namespace SceneUtil // Transform a bounding sphere by a matrix // based off private code in osg::Transform // TODO: patch osg to make public - void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere); + template + inline void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphereImpl& bsphere) + { + VT xdash = bsphere._center; + xdash.x() += bsphere._radius; + xdash = xdash*matrix; + + VT ydash = bsphere._center; + ydash.y() += bsphere._radius; + ydash = ydash*matrix; + + VT zdash = bsphere._center; + zdash.z() += bsphere._radius; + zdash = zdash*matrix; + + bsphere._center = bsphere._center*matrix; + + xdash -= bsphere._center; + typename VT::value_type sqrlen_xdash = xdash.length2(); + + ydash -= bsphere._center; + typename VT::value_type sqrlen_ydash = ydash.length2(); + + zdash -= bsphere._center; + typename VT::value_type sqrlen_zdash = zdash.length2(); + + bsphere._radius = sqrlen_xdash; + if (bsphere._radius Date: Wed, 2 Feb 2022 16:57:59 +0300 Subject: [PATCH 2045/2859] Fix double precision bound issues in std::max/std::clamp --- apps/openmw/mwrender/localmap.cpp | 6 ++++-- components/sceneutil/lightmanager.cpp | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 9a645312d5..66d41a47e4 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -460,8 +460,10 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) yOffset++; mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize; } - mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX); - mBounds.yMax() = std::max(mBounds.yMax(), fog->mBounds.mMaxY); + if (fog->mBounds.mMaxX > mBounds.xMax()) + mBounds.xMax() = fog->mBounds.mMaxX; + if (fog->mBounds.mMaxY > mBounds.yMax()) + mBounds.yMax() = fog->mBounds.mMaxY; if(xOffset != 0 || yOffset != 0) Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 29907c542d..45738bcad8 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1109,7 +1109,8 @@ namespace SceneUtil if (transform.mLightSource->getLastAppliedFrame() != frameNum && mPointLightFadeEnd != 0.f) { const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; - float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); + const float viewDelta = viewBound.center().length() - mPointLightFadeStart; + float fade = 1 - std::clamp(viewDelta / fadeDelta, 0.f, 1.f); if (fade == 0.f) continue; From 946b8b804cca2a004a80b5391102c436b914bef7 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 2 Feb 2022 15:27:48 +0100 Subject: [PATCH 2046/2859] MR feedback --- apps/openmw/mwgui/settingswindow.cpp | 51 +++++++++++++++------------- apps/openmw/mwlua/uibindings.cpp | 5 +-- files/lua_api/openmw/ui.lua | 4 +-- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 80a034fd2a..e79132f0ab 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -712,10 +712,10 @@ namespace MWGui void SettingsWindow::resizeScriptSettings() { - static const int minListWidth = 150; - static const float relativeListWidth = 0.2f; - static const int padding = 2; - static const int outerPadding = padding * 2; + constexpr int minListWidth = 150; + constexpr float relativeListWidth = 0.2f; + constexpr int padding = 2; + constexpr int outerPadding = padding * 2; MyGUI::IntSize parentSize = mScriptFilter->getParent()->getClientCoord().size(); int listWidth = std::max(minListWidth, static_cast(parentSize.width * relativeListWidth)); int filterHeight = mScriptFilter->getSize().height; @@ -732,13 +732,13 @@ namespace MWGui { std::string escapeRegex(const std::string& str) { - static std::regex specialChars(R"r([\^\.\[\$\(\)\|\*\+\?\{])r", std::regex_constants::extended); + static const std::regex specialChars(R"r([\^\.\[\$\(\)\|\*\+\?\{])r", std::regex_constants::extended); return std::regex_replace(str, specialChars, R"(\$&)"); } std::regex wordSearch(const std::string& query) { - static std::regex wordsRegex(R"([^[:space:]]+)", std::regex_constants::extended); + static const std::regex wordsRegex(R"([^[:space:]]+)", std::regex_constants::extended); auto wordsBegin = std::sregex_iterator(query.begin(), query.end(), wordsRegex); auto wordsEnd = std::sregex_iterator(); std::string searchRegex("("); @@ -752,16 +752,15 @@ namespace MWGui // query had only whitespace characters if (searchRegex == "()") searchRegex = "^(.*)$"; - static auto flags = std::regex_constants::icase | std::regex_constants::extended; - return std::regex(searchRegex, flags); + return std::regex(searchRegex, std::regex_constants::extended | std::regex_constants::icase); } - // use double to guarantee no overflow when casting from size_t double weightedSearch(const std::regex& regex, const std::string& text) { std::smatch matches; std::regex_search(text, matches, regex); - return matches.size(); + // need a signed value, so cast to double (not an integer type to guarantee no overflow) + return static_cast(matches.size()); } } @@ -772,25 +771,31 @@ namespace MWGui mScriptList->removeAllItems(); mScriptView->setCanvasSize({0, 0}); + struct WeightedPage { + size_t mIndex; + std::string mName; + double mNameWeight; + double mHintWeight; + + constexpr auto tie() const { return std::tie(mNameWeight, mHintWeight, mName); } + + constexpr bool operator<(const WeightedPage& rhs) const { return tie() < rhs.tie(); } + }; + std::regex searchRegex = wordSearch(mScriptFilter->getCaption()); - std::vector> weightedPages; + std::vector weightedPages; weightedPages.reserve(LuaUi::scriptSettingsPageCount()); for (size_t i = 0; i < LuaUi::scriptSettingsPageCount(); ++i) { LuaUi::ScriptSettingsPage page = LuaUi::scriptSettingsPageAt(i); - double nameSearch = 2 * weightedSearch(searchRegex, page.mName); - double hintSearch = weightedSearch(searchRegex, page.mSearchHints); - if ((nameSearch + hintSearch) > 0) - weightedPages.push_back({ i, page, -nameSearch, -hintSearch }); + double nameWeight = weightedSearch(searchRegex, page.mName); + double hintWeight = weightedSearch(searchRegex, page.mSearchHints); + if ((nameWeight + hintWeight) > 0) + weightedPages.push_back({ i, page.mName, -nameWeight, -hintWeight }); } - std::sort(weightedPages.begin(), weightedPages.end(), [](const auto& a, const auto& b) - { - const auto& [iA, pageA, nameA, hintA] = a; - const auto& [iB, pageB, nameB, hintB] = b; - return std::tie(nameA, hintA, pageA.mName) < std::tie(nameB, hintB, pageB.mName); - }); - for (const auto & [i, page, name, hint] : weightedPages) - mScriptList->addItem(page.mName, i); + std::sort(weightedPages.begin(), weightedPages.end()); + for (const WeightedPage& weightedPage : weightedPages) + mScriptList->addItem(weightedPage.mName, weightedPage.mIndex); // Hide script settings tab when the game world isn't loaded and scripts couldn't add their settings bool disabled = LuaUi::scriptSettingsPageCount() == 0; diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 073ecc7822..6f1d34072a 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -239,10 +239,7 @@ namespace MWLua typeTable.set(it.second, it.first); api["TYPE"] = LuaUtil::makeReadOnly(typeTable); - api["registerSettingsPage"] = [](sol::table options) - { - LuaUi::registerSettingsPage(options); - }; + api["registerSettingsPage"] = &LuaUi::registerSettingsPage; return LuaUtil::makeReadOnly(api); } diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 997c39780e..8049df860a 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -38,7 +38,7 @@ --- -- Adds a settings page to main menu setting's Scripts tab. --- @function [parent=#ui] regregisterSettingsPageister +-- @function [parent=#ui] registerSettingsPage -- @param #SettingsPage page --- @@ -143,7 +143,7 @@ -- @field #number button Mouse button which triggered the event (could be nil) --- --- Settings page parameters, passed as an argument to uyi.registerSettingsPage +-- Settings page parameters, passed as an argument to ui.registerSettingsPage -- @type SettingsPage -- @field #string name Name of the page, displayed in the list, used for search -- @field #string searchHints Additional keywords used in search, not displayed anywhere From 2945f6238e922a0a110c8226cee4f83c6a24f83c Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 2 Feb 2022 20:30:28 +0000 Subject: [PATCH 2047/2859] Fix util.color docs --- files/lua_api/openmw/util.lua | 90 ++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index ea7d037daa..dca1683d45 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -1,11 +1,11 @@ -------------------------------------------------------------------------------- +--- -- `openmw.util` defines utility functions and classes like 3D vectors, that don't depend on the game world. -- @module util -- @usage local util = require('openmw.util') -------------------------------------------------------------------------------- +--- -- Limits given value to the interval [`from`, `to`]. -- @function [parent=#util] clamp -- @param #number value @@ -13,20 +13,20 @@ -- @param #number to -- @return #number min(max(value, from), to) -------------------------------------------------------------------------------- +--- -- Adds `2pi*k` and puts the angle in range `[-pi, pi]`. -- @function [parent=#util] normalizeAngle -- @param #number angle Angle in radians -- @return #number Angle in range `[-pi, pi]` -------------------------------------------------------------------------------- +--- -- Makes a table read only. -- @function [parent=#util] makeReadOnly -- @param #table table Any table. -- @return #table The same table wrapped with read only userdata. -------------------------------------------------------------------------------- +--- -- Immutable 2D vector -- @type Vector2 -- @field #number x @@ -46,26 +46,26 @@ -- v1 * x -- multiplication by a number -- v1 / x -- division by a number -------------------------------------------------------------------------------- +--- -- Creates a new 2D vector. Vectors are immutable and can not be changed after creation. -- @function [parent=#util] vector2 -- @param #number x. -- @param #number y. -- @return #Vector2. -------------------------------------------------------------------------------- +--- -- Length of the vector. -- @function [parent=#Vector2] length -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Square of the length of the vector. -- @function [parent=#Vector2] length2 -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Normalizes vector. -- Returns two values: normalized vector and the length of the original vector. -- It doesn't change the original vector. @@ -73,14 +73,14 @@ -- @param self -- @return #Vector2, #number -------------------------------------------------------------------------------- +--- -- Rotates 2D vector clockwise. -- @function [parent=#Vector2] rotate -- @param self -- @param #number angle Angle in radians -- @return #Vector2 Rotated vector. -------------------------------------------------------------------------------- +--- -- Dot product. -- @function [parent=#Vector2] dot -- @param self @@ -88,7 +88,7 @@ -- @return #number -------------------------------------------------------------------------------- +--- -- Immutable 3D vector -- @type Vector3 -- @field #number x @@ -110,7 +110,7 @@ -- v1 * x -- multiplication by a number -- v1 / x -- division by a number -------------------------------------------------------------------------------- +--- -- Creates a new 3D vector. Vectors are immutable and can not be changed after creation. -- @function [parent=#util] vector3 -- @param #number x. @@ -118,19 +118,19 @@ -- @param #number z. -- @return #Vector3. -------------------------------------------------------------------------------- +--- -- Length of the vector -- @function [parent=#Vector3] length -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Square of the length of the vector -- @function [parent=#Vector3] length2 -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Normalizes vector. -- Returns two values: normalized vector and the length of the original vector. -- It doesn't change the original vector. @@ -138,14 +138,14 @@ -- @param self -- @return #Vector3, #number -------------------------------------------------------------------------------- +--- -- Dot product. -- @function [parent=#Vector3] dot -- @param self -- @param #Vector3 v -- @return #number -------------------------------------------------------------------------------- +--- -- Cross product. -- @function [parent=#Vector3] cross -- @param self @@ -153,7 +153,7 @@ -- @return #Vector3 -------------------------------------------------------------------------------- +--- -- Immutable 4D vector. -- @type Vector4 -- @field #number x @@ -174,7 +174,7 @@ -- v1 * x -- multiplication by a number -- v1 / x -- division by a number -------------------------------------------------------------------------------- +--- -- Creates a new 4D vector. Vectors are immutable and can not be changed after creation. -- @function [parent=#util] vector4 -- @param #number x. @@ -183,19 +183,19 @@ -- @param #number w. -- @return #Vector4. -------------------------------------------------------------------------------- +--- -- Length of the vector -- @function [parent=#Vector4] length -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Square of the length of the vector -- @function [parent=#Vector4] length2 -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Normalizes vector. -- Returns two values: normalized vector and the length of the original vector. -- It doesn't change the original vector. @@ -203,14 +203,14 @@ -- @param self -- @return #Vector4, #number -------------------------------------------------------------------------------- +--- -- Dot product. -- @function [parent=#Vector4] dot -- @param self -- @param #Vector4 v -- @return #number -------------------------------------------------------------------------------- +--- -- Color in RGBA format. All of the component values are in the range [0, 1]. -- @type Color -- @field #number r Red component @@ -218,29 +218,33 @@ -- @field #number b Blue component -- @field #number a Alpha (transparency) component -------------------------------------------------------------------------------- +--- -- Returns a Vector4 with RGBA components of the Color. -- @function [parent=#Color] asRgba -- @param self -- @return #Vector4 -------------------------------------------------------------------------------- +--- -- Returns a Vector3 with RGB components of the Color. -- @function [parent=#Color] asRgb -- @param self -- @return #Vector3 -------------------------------------------------------------------------------- +--- -- Converts the color into a HEX string. -- @function [parent=#Color] asHex -- @param self -- @return #string -------------------------------------------------------------------------------- +--- +-- Methods for creating #Color values from different formats. -- @type COLOR --- @field [parent=#util] #COLOR color Methods for creating #Color values from different formats. -------------------------------------------------------------------------------- +--- +-- Methods for creating #Color values from different formats. +-- @field [parent=#util] #COLOR color + +--- -- Creates a Color from RGBA format -- @function [parent=#COLOR] rgba -- @param #number r @@ -249,7 +253,7 @@ -- @param #number a -- @return #Color -------------------------------------------------------------------------------- +--- -- Creates a Color from RGB format. Equivalent to calling util.rgba with a = 1. -- @function [parent=#COLOR] rgb -- @param #number r @@ -257,26 +261,26 @@ -- @param #number b -- @return #Color -------------------------------------------------------------------------------- +--- -- Parses a hex color string into a Color. -- @function [parent=#COLOR] hex -- @param #string hex A hex color string in RRGGBB format (e. g. "ff0000"). -- @return #Color -------------------------------------------------------------------------------- +--- -- @type Transform -------------------------------------------------------------------------------- +--- -- Returns the inverse transform. -- @function [parent=#Transform] inverse -- @param self -- @return #Transform. -------------------------------------------------------------------------------- +--- -- @type TRANSFORM -- @field [parent=#TRANSFORM] #Transform identity Empty transform. -------------------------------------------------------------------------------- +--- -- Movement by given vector. -- @function [parent=#TRANSFORM] move -- @param #Vector3 offset. @@ -286,7 +290,7 @@ -- util.transform.move(x, y, z) -- util.transform.move(util.vector3(x, y, z)) -------------------------------------------------------------------------------- +--- -- Scale transform. -- @function [parent=#TRANSFORM] scale -- @param #number scaleX. @@ -299,32 +303,32 @@ -- util.transform.scale(util.vector3(x, y, z)) -------------------------------------------------------------------------------- +--- -- Rotation around a vector (counterclockwise if the vector points to us). -- @function [parent=#TRANSFORM] rotate -- @param #number angle -- @param #Vector3 axis. -- @return #Transform. -------------------------------------------------------------------------------- +--- -- X-axis rotation (equivalent to `rotate(angle, vector3(-1, 0, 0))`). -- @function [parent=#TRANSFORM] rotateX -- @param #number angle -- @return #Transform. -------------------------------------------------------------------------------- +--- -- Y-axis rotation (equivalent to `rotate(angle, vector3(0, -1, 0))`). -- @function [parent=#TRANSFORM] rotateY -- @param #number angle -- @return #Transform. -------------------------------------------------------------------------------- +--- -- Z-axis rotation (equivalent to `rotate(angle, vector3(0, 0, -1))`). -- @function [parent=#TRANSFORM] rotateZ -- @param #number angle -- @return #Transform. -------------------------------------------------------------------------------- +--- -- 3D transforms (scale/move/rotate) that can be applied to 3D vectors. -- Several transforms can be combined and applied to a vector using multiplication. -- Combined transforms apply in reverse order (from right to left). From dd5ba5c57b88a1000a1cd8f781e84d2a3b33f8f5 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 13 Jan 2022 00:20:16 +0100 Subject: [PATCH 2048/2859] Redirect log only after parsing configuration --- apps/opencs/editor.cpp | 2 + apps/opencs/main.cpp | 2 +- apps/openmw/main.cpp | 4 +- components/debug/debugging.cpp | 80 ++++++++++++++++++---------------- components/debug/debugging.hpp | 7 ++- 5 files changed, 54 insertions(+), 41 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 862d1c545f..7d5230f894 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -105,6 +106,7 @@ std::pair > CS::Editor::readConfi boost::program_options::notify(variables); mCfgMgr.readConfiguration(variables, desc, false); + setupLogging(mCfgMgr.getLogPath().string(), "OpenMW-CS"); Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index c7d57a256e..1e6e718983 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -79,5 +79,5 @@ int runApplication(int argc, char *argv[]) int main(int argc, char *argv[]) { - return wrapApplication(&runApplication, argc, argv, "OpenMW-CS"); + return wrapApplication(&runApplication, argc, argv, "OpenMW-CS", false); } diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index d5bed4ba25..6b92fa78f5 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -65,6 +65,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc); Files::mergeComposingVariables(variables, composingVariables, desc); + setupLogging(cfgMgr.getLogPath().string(), "OpenMW"); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as().string()); Log(Debug::Info) << v.describe(); @@ -230,7 +232,7 @@ extern "C" int SDL_main(int argc, char**argv) int main(int argc, char**argv) #endif { - return wrapApplication(&runApplication, argc, argv, "OpenMW"); + return wrapApplication(&runApplication, argc, argv, "OpenMW", false); } // Platform specific for Windows when there is no console built into the executable. diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 0e372942f2..c84bb39d3d 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -137,61 +137,65 @@ namespace Debug } static std::unique_ptr rawStdout = nullptr; +static std::unique_ptr rawStderr = nullptr; +static boost::filesystem::ofstream logfile; + +#if defined(_WIN32) && defined(_DEBUG) +static boost::iostreams::stream_buffer sb; +#else +static boost::iostreams::stream_buffer coutsb; +static boost::iostreams::stream_buffer cerrsb; +#endif std::ostream& getRawStdout() { return rawStdout ? *rawStdout : std::cout; } -int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName) +// Redirect cout and cerr to the log file +void setupLogging(const std::string& logDir, const std::string& appName, std::ios_base::openmode mode) { -#if defined _WIN32 - (void)Debug::attachParentConsole(); -#endif - rawStdout = std::make_unique(std::cout.rdbuf()); - - // Some objects used to redirect cout and cerr - // Scope must be here, so this still works inside the catch block for logging exceptions - std::streambuf* cout_rdbuf = std::cout.rdbuf (); - std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); - #if defined(_WIN32) && defined(_DEBUG) - boost::iostreams::stream_buffer sb; + // Redirect cout and cerr to VS debug output when running in debug mode + sb.open(Debug::DebugOutput()); + std::cout.rdbuf(&sb); + std::cerr.rdbuf(&sb); #else - boost::iostreams::stream_buffer coutsb; - boost::iostreams::stream_buffer cerrsb; - std::ostream oldcout(cout_rdbuf); - std::ostream oldcerr(cerr_rdbuf); + const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; + logfile.open(boost::filesystem::path(logDir) / logName, mode); + + coutsb.open(Debug::Tee(logfile, *rawStdout)); + cerrsb.open(Debug::Tee(logfile, *rawStderr)); + + std::cout.rdbuf(&coutsb); + std::cerr.rdbuf(&cerrsb); #endif +} - const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; - boost::filesystem::ofstream logfile; +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], + const std::string& appName, bool autoSetupLogging) +{ +#if defined _WIN32 + (void)Debug::attachParentConsole(); +#endif + rawStdout = std::make_unique(std::cout.rdbuf()); + rawStderr = std::make_unique(std::cerr.rdbuf()); int ret = 0; try { Files::ConfigurationManager cfgMgr; -#if defined(_WIN32) && defined(_DEBUG) - // Redirect cout and cerr to VS debug output when running in debug mode - sb.open(Debug::DebugOutput()); - std::cout.rdbuf (&sb); - std::cerr.rdbuf (&sb); -#else - // Redirect cout and cerr to the log file - // If we are collecting a stack trace, append to existing log file - std::ios_base::openmode mode = std::ios::out; - if(argc == 2 && strcmp(argv[1], crash_switch) == 0) - mode |= std::ios::app; - - logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName), mode); + if (autoSetupLogging) + { + std::ios_base::openmode mode = std::ios::out; - coutsb.open (Debug::Tee(logfile, oldcout)); - cerrsb.open (Debug::Tee(logfile, oldcerr)); + // If we are collecting a stack trace, append to existing log file + if (argc == 2 && strcmp(argv[1], crash_switch) == 0) + mode |= std::ios::app; - std::cout.rdbuf (&coutsb); - std::cerr.rdbuf (&cerrsb); -#endif + setupLogging(cfgMgr.getLogPath().string(), appName, mode); + } #if defined(_WIN32) const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp"; @@ -217,8 +221,8 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c } // Restore cout and cerr - std::cout.rdbuf(cout_rdbuf); - std::cerr.rdbuf(cerr_rdbuf); + std::cout.rdbuf(rawStdout->rdbuf()); + std::cerr.rdbuf(rawStderr->rdbuf()); Debug::CurrentDebugLevel = Debug::NoLevel; return ret; diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp index d8849cd896..a2c5ae9e6c 100644 --- a/components/debug/debugging.hpp +++ b/components/debug/debugging.hpp @@ -133,11 +133,16 @@ namespace Debug std::map mColors; }; #endif + + } // Can be used to print messages without timestamps std::ostream& getRawStdout(); -int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName); +void setupLogging(const std::string& logDir, const std::string& appName, std::ios_base::openmode = std::ios::out); + +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], + const std::string& appName, bool autoSetupLogging = true); #endif From 5ca56a4f8ad12fa0f67fa8cb468540b0034717b9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 13 Jan 2022 23:10:09 +0100 Subject: [PATCH 2049/2859] New option "config" for specifying additional config directories. --- apps/essimporter/main.cpp | 1 + apps/navmeshtool/main.cpp | 1 + apps/opencs/editor.cpp | 1 + apps/openmw/main.cpp | 3 +- apps/openmw_test_suite/mwworld/test_store.cpp | 1 + components/files/configurationmanager.cpp | 127 +++++++++++++----- components/files/configurationmanager.hpp | 18 ++- files/openmw.cfg | 1 + files/openmw.cfg.local | 1 + 9 files changed, 112 insertions(+), 42 deletions(-) diff --git a/apps/essimporter/main.cpp b/apps/essimporter/main.cpp index e81368c607..bc5d34bc03 100644 --- a/apps/essimporter/main.cpp +++ b/apps/essimporter/main.cpp @@ -25,6 +25,7 @@ int main(int argc, char** argv) ("encoding", boost::program_options::value()->default_value("win1252"), "encoding of the save file") ; p_desc.add("mwsave", 1).add("output", 1); + Files::ConfigurationManager::addCommonOptions(desc); bpo::variables_map variables; diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 8e46170e63..ce3cb10b20 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -84,6 +84,7 @@ namespace NavMeshTool ("process-interior-cells", bpo::value()->implicit_value(true) ->default_value(false), "build navmesh for interior cells") ; + Files::ConfigurationManager::addCommonOptions(result); return result; } diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 7d5230f894..3db201874a 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -102,6 +102,7 @@ std::pair > CS::Editor::readConfi ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ("script-blacklist-use", boost::program_options::value()->implicit_value(true) ->default_value(true), "enable script blacklisting"); + Files::ConfigurationManager::addCommonOptions(desc); boost::program_options::notify(variables); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 6b92fa78f5..456fe23b60 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -40,6 +40,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat typedef std::vector StringsVector; bpo::options_description desc = OpenMW::makeOptionsDescription(); + Files::ConfigurationManager::addCommonOptions(desc); bpo::variables_map variables; @@ -61,9 +62,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc); cfgMgr.readConfiguration(variables, desc); - Files::mergeComposingVariables(variables, composingVariables, desc); setupLogging(cfgMgr.getLogPath().string(), "OpenMW"); diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 10003cfdfd..bf1a40f7d9 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -58,6 +58,7 @@ struct ContentFileTest : public ::testing::Test ("content", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") ("data-local", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), "")); + Files::ConfigurationManager::addCommonOptions(desc); boost::program_options::notify(variables); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index c2cd44960f..739c7f7b43 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -20,6 +20,7 @@ static const char* const applicationName = "openmw"; #endif const char* const localToken = "?local?"; +const char* const userConfigToken = "?userconfig?"; const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; @@ -50,6 +51,7 @@ ConfigurationManager::~ConfigurationManager() void ConfigurationManager::setupTokensMapping() { mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); + mTokensMapping.insert(std::make_pair(userConfigToken, &FixedPath<>::getUserConfigPath)); mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } @@ -57,31 +59,92 @@ void ConfigurationManager::setupTokensMapping() void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet) { + using ParsedConfigFile = bpo::basic_parsed_options; bool silent = mSilent; mSilent = quiet; - - // User config has the highest priority. - auto composingVariables = separateComposingVariables(variables, description); - loadConfig(mFixedPath.getUserConfigPath(), variables, description); - mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); - // read either local or global config depending on type of installation - composingVariables = separateComposingVariables(variables, description); - bool loaded = loadConfig(mFixedPath.getLocalPath(), variables, description); - mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); - if (!loaded) + std::optional config = loadConfig(mFixedPath.getLocalPath(), description); + if (config) + mActiveConfigPaths.push_back(mFixedPath.getLocalPath()); + else + { + mActiveConfigPaths.push_back(mFixedPath.getGlobalConfigPath()); + config = loadConfig(mFixedPath.getGlobalConfigPath(), description); + } + if (!config) { - composingVariables = separateComposingVariables(variables, description); - loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + if (!quiet) + Log(Debug::Error) << "Neither local config nor global config are available."; + mSilent = silent; + return; + } + + std::stack extraConfigDirs; + addExtraConfigDirs(extraConfigDirs, variables); + addExtraConfigDirs(extraConfigDirs, *config); + + std::vector parsedOptions{*std::move(config)}; + std::set alreadyParsedPaths; // needed to prevent infinite loop in case of a circular link + alreadyParsedPaths.insert(boost::filesystem::path(mActiveConfigPaths.front())); + + while (!extraConfigDirs.empty()) + { + boost::filesystem::path path = extraConfigDirs.top(); + extraConfigDirs.pop(); + if (alreadyParsedPaths.count(path) > 0) + { + if (!quiet) + Log(Debug::Warning) << "Repeated config dir: " << path; + continue; + } + alreadyParsedPaths.insert(path); + mActiveConfigPaths.push_back(path); + config = loadConfig(path, description); + if (!config) + continue; + addExtraConfigDirs(extraConfigDirs, *config); + parsedOptions.push_back(*std::move(config)); + } + + for (auto it = parsedOptions.rbegin(); it != parsedOptions.rend(); ++it) + { + auto composingVariables = separateComposingVariables(variables, description); + boost::program_options::store(std::move(*it), variables); mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); } + mLogPath = mActiveConfigPaths.back(); mSilent = silent; } +void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, + const bpo::basic_parsed_options& options) const +{ + boost::program_options::variables_map variables; + boost::program_options::store(options, variables); + boost::program_options::notify(variables); + addExtraConfigDirs(dirs, variables); +} + +void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, + const boost::program_options::variables_map& variables) const +{ + auto configIt = variables.find("config"); + if (configIt == variables.end()) + return; + Files::PathContainer newDirs = asPathContainer(configIt->second.as()); + processPaths(newDirs); + for (auto it = newDirs.rbegin(); it != newDirs.rend(); ++it) + dirs.push(*it); +} + +void ConfigurationManager::addCommonOptions(boost::program_options::options_description& description) +{ + description.add_options() + ("config", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "config") + ->multitoken()->composing(), "additional config directories"); +} + boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, boost::program_options::options_description& description) { @@ -107,7 +170,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost if (description.find_nothrow("replace", false)) { auto replace = second["replace"]; - if (!replace.defaulted() && !replace.empty()) + if (!replace.empty()) { std::vector replaceVector = replace.as>(); replacedVariables.insert(replaceVector.begin(), replaceVector.end()); @@ -132,7 +195,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost continue; } - if (second[name].defaulted() || second[name].empty()) + if (second[name].empty()) continue; boost::any& firstValue = firstPosition->second.value(); @@ -142,7 +205,6 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost { auto& firstPathContainer = boost::any_cast(firstValue); const auto& secondPathContainer = boost::any_cast(secondValue); - firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); } else if (firstValue.type() == typeid(std::vector)) @@ -165,10 +227,10 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost Log(Debug::Error) << "Unexpected composing variable type. Curse boost and their blasted arcane templates."; } } - + boost::program_options::notify(first); } -void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) +void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) const { std::string path; for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) @@ -181,7 +243,7 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre std::string::size_type pos = path.find('?', 1); if (pos != std::string::npos && pos != 0) { - TokensMappingContainer::iterator tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); + auto tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); if (tokenIt != mTokensMapping.end()) { boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); @@ -224,8 +286,8 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre std::bind(&boost::filesystem::path::empty, std::placeholders::_1)), dataDirs.end()); } -bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, - boost::program_options::variables_map& variables, +std::optional> ConfigurationManager::loadConfig( + const boost::filesystem::path& path, boost::program_options::options_description& description) { boost::filesystem::path cfgFile(path); @@ -238,20 +300,11 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, boost::filesystem::ifstream configFileStream(cfgFile); if (configFileStream.is_open()) - { - parseConfig(configFileStream, variables, description); - - return true; - } - else - { - if (!mSilent) - Log(Debug::Error) << "Loading failed."; - - return false; - } + return Files::parse_config_file(configFileStream, description, true); + else if (!mSilent) + Log(Debug::Error) << "Loading failed."; } - return false; + return std::nullopt; } const boost::filesystem::path& ConfigurationManager::getGlobalPath() const @@ -344,4 +397,4 @@ PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathCon return PathContainer(MaybeQuotedPathContainer.begin(), MaybeQuotedPathContainer.end()); } -} /* namespace Cfg */ +} /* namespace Files */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 4b641c12fd..07a9300305 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -2,6 +2,8 @@ #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #include +#include +#include #include @@ -25,7 +27,7 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet=false); - void processPaths(Files::PathContainer& dataDirs, bool create = false); + void processPaths(Files::PathContainer& dataDirs, bool create = false) const; ///< \param create Try creating the directory, if it does not exist. /**< Fixed paths */ @@ -37,24 +39,34 @@ struct ConfigurationManager const boost::filesystem::path& getUserDataPath() const; const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; + const std::vector& getActiveConfigPaths() const { return mActiveConfigPaths; } const boost::filesystem::path& getCachePath() const; const boost::filesystem::path& getLogPath() const; const boost::filesystem::path& getScreenshotPath() const; + static void addCommonOptions(boost::program_options::options_description& description); + private: typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; typedef std::map TokensMappingContainer; - bool loadConfig(const boost::filesystem::path& path, - boost::program_options::variables_map& variables, + std::optional> loadConfig( + const boost::filesystem::path& path, boost::program_options::options_description& description); + void addExtraConfigDirs(std::stack& dirs, + const boost::program_options::variables_map& variables) const; + void addExtraConfigDirs(std::stack& dirs, + const boost::program_options::basic_parsed_options& options) const; + void setupTokensMapping(); + std::vector mActiveConfigPaths; + FixedPathType mFixedPath; boost::filesystem::path mLogPath; diff --git a/files/openmw.cfg b/files/openmw.cfg index f524911489..7bd70c3e5e 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +config="?userconfig?" resources=${OPENMW_RESOURCE_FILES} script-blacklist=Museum script-blacklist=MockChangeScript diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index bed9b9b10a..79ab9e6156 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +config="?userconfig?" resources=./resources script-blacklist=Museum script-blacklist=MockChangeScript From 1bcc4a8bcc6cfbf3570a276c5647bcd294773a7c Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 14 Jan 2022 00:54:54 +0100 Subject: [PATCH 2050/2859] Read settings.cfg from all active config dirs --- CHANGELOG.md | 1 + apps/openmw/engine.cpp | 28 +++++++++++++++++----------- components/settings/parser.cpp | 7 +++++-- components/settings/parser.hpp | 3 ++- components/settings/settings.cpp | 4 ++-- components/settings/settings.hpp | 2 +- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56d0910d22..a181a35098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions + Feature #2491: Ability to make OpenMW "portable" Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9e2e1aedad..50053b8b76 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -520,20 +520,26 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) std::string OMW::Engine::loadSettings (Settings::Manager & settings) { - // Create the settings manager and load default settings file - const std::string localdefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); - const std::string globaldefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); + const std::vector& paths = mCfgMgr.getActiveConfigPaths(); + if (paths.empty()) + throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); - // prefer local - if (boost::filesystem::exists(localdefault)) - settings.loadDefault(localdefault); - else if (boost::filesystem::exists(globaldefault)) - settings.loadDefault(globaldefault); - else + // Create the settings manager and load default settings file. + const std::string defaultsBin = (paths.front() / "defaults.bin").string(); + if (!boost::filesystem::exists(defaultsBin)) throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); + settings.loadDefault(defaultsBin); + + // Load "settings.cfg" from every config dir except the last one as additional default settings. + for (int i = 0; i < static_cast(paths.size()) - 1; ++i) + { + const std::string additionalDefaults = (paths[i] / "settings.cfg").string(); + if (boost::filesystem::exists(additionalDefaults)) + settings.loadDefault(additionalDefaults, true); + } - // load user settings if they exist - std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + // Load "settings.cfg" from the last config as user settings if they exist. This path will be used to save modified settings. + std::string settingspath = (paths.back() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index f2419dfdd6..dd1b8c1a20 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -9,7 +9,8 @@ #include -void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64Encoded) +void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, + bool base64Encoded, bool overrideExisting) { mFile = file; boost::filesystem::ifstream fstream; @@ -73,7 +74,9 @@ void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, Cat std::string value = line.substr(valueBegin); Misc::StringUtils::trim(value); - if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) + if (overrideExisting) + settings[std::make_pair(currentCategory, setting)] = value; + else if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); } } diff --git a/components/settings/parser.hpp b/components/settings/parser.hpp index 45b1a18f72..c934fbea07 100644 --- a/components/settings/parser.hpp +++ b/components/settings/parser.hpp @@ -10,7 +10,8 @@ namespace Settings class SettingsFileParser { public: - void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64encoded = false); + void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, + bool base64encoded = false, bool overrideExisting = false); void saveSettingsFile(const std::string& file, const CategorySettingValueMap& settings); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 7fa625e4ab..09e74b348b 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -19,10 +19,10 @@ void Manager::clear() mChangedSettings.clear(); } -void Manager::loadDefault(const std::string &file) +void Manager::loadDefault(const std::string &file, bool overrideExisting) { SettingsFileParser parser; - parser.loadSettingsFile(file, mDefaultSettings, true); + parser.loadSettingsFile(file, mDefaultSettings, true, overrideExisting); } void Manager::loadUser(const std::string &file) diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index a4b1cf3a54..84e7066fbd 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -26,7 +26,7 @@ namespace Settings void clear(); ///< clears all settings and default settings - void loadDefault (const std::string& file); + void loadDefault (const std::string& file, bool overrideExisting = false); ///< load file as the default settings (can be overridden by user settings) void loadUser (const std::string& file); From a453e5c19884e716a66abb10175fd351dc243d36 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 16 Jan 2022 01:59:20 +0100 Subject: [PATCH 2051/2859] Reuse the same code to load settings in apps/openmw, apps/launcher, apps/opencs --- apps/launcher/maindialog.cpp | 62 +++++++------------------------ apps/navmeshtool/main.cpp | 19 +--------- apps/opencs/editor.cpp | 11 +++++- apps/opencs/editor.hpp | 3 ++ apps/opencs/model/prefs/state.cpp | 17 +-------- apps/openmw/engine.cpp | 31 +--------------- components/settings/settings.cpp | 32 ++++++++++++---- components/settings/settings.hpp | 12 +++--- 8 files changed, 61 insertions(+), 126 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index d335d7cded..167f6b9c26 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -414,57 +414,23 @@ bool Launcher::MainDialog::setupGameData() bool Launcher::MainDialog::setupGraphicsSettings() { - // This method is almost a copy of OMW::Engine::loadSettings(). They should definitely - // remain consistent, and possibly be merged into a shared component. At the very least - // the filenames should be in the CfgMgr component. - - // Ensure to clear previous settings in case we had already loaded settings. - mEngineSettings.clear(); - - // Create the settings manager and load default settings file - const std::string localDefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); - const std::string globalDefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); - std::string defaultPath; - - // Prefer the defaults.bin in the current directory. - if (boost::filesystem::exists(localDefault)) - defaultPath = localDefault; - else if (boost::filesystem::exists(globalDefault)) - defaultPath = globalDefault; - // Something's very wrong if we can't find the file at all. - else { - cfgError(tr("Error reading OpenMW configuration file"), - tr("
Could not find defaults.bin

\ - The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.")); - return false; - } - - // Load the default settings, report any parsing errors. - try { - mEngineSettings.loadDefault(defaultPath); - } - catch (std::exception& e) { - std::string msg = std::string("
Error reading defaults.bin

") + e.what(); - cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); - return false; - } - - // Load user settings if they exist - const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); - // User settings are not required to exist, so if they don't we're done. - if (!boost::filesystem::exists(userPath)) return true; - - try { - mEngineSettings.loadUser(userPath); + mEngineSettings.clear(); // Ensure to clear previous settings in case we had already loaded settings. + try + { + boost::program_options::variables_map variables; + boost::program_options::options_description desc; + mCfgMgr.addCommonOptions(desc); + mCfgMgr.readConfiguration(variables, desc, true); + mEngineSettings.load(mCfgMgr); + return true; } - catch (std::exception& e) { - std::string msg = std::string("
Error reading settings.cfg

") + e.what(); - cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); + catch (std::exception& e) + { + cfgError(tr("Error reading OpenMW configuration files"), + tr("
The problem may be due to an incomplete installation of OpenMW.
\ + Reinstalling OpenMW may resolve the problem.
") + e.what()); return false; } - - return true; } void Launcher::MainDialog::loadSettings() diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index ce3cb10b20..f89e80e542 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -89,23 +89,6 @@ namespace NavMeshTool return result; } - void loadSettings(const Files::ConfigurationManager& config, Settings::Manager& settings) - { - const std::string localDefault = (config.getLocalPath() / "defaults.bin").string(); - const std::string globalDefault = (config.getGlobalPath() / "defaults.bin").string(); - - if (boost::filesystem::exists(localDefault)) - settings.loadDefault(localDefault); - else if (boost::filesystem::exists(globalDefault)) - settings.loadDefault(globalDefault); - else - throw std::runtime_error("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); - - const std::string settingsPath = (config.getUserConfigPath() / "settings.cfg").string(); - if (boost::filesystem::exists(settingsPath)) - settings.loadUser(settingsPath); - } - int runNavMeshTool(int argc, char *argv[]) { bpo::options_description desc = makeOptionsDescription(); @@ -166,7 +149,7 @@ namespace NavMeshTool VFS::registerArchives(&vfs, fileCollections, archives, true); Settings::Manager settings; - loadSettings(config, settings); + settings.load(config); const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game"); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 3db201874a..1d5934fe5d 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -21,7 +21,7 @@ using namespace Fallback; CS::Editor::Editor (int argc, char **argv) -: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), +: mConfigVariables(readConfiguration()), mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), mPid(""), mLock(), mMerge (mDocumentManager), mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr) { @@ -83,7 +83,7 @@ CS::Editor::~Editor () remove(mPid.string().c_str())); // ignore any error } -std::pair > CS::Editor::readConfig(bool quiet) +boost::program_options::variables_map CS::Editor::readConfiguration() { boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); @@ -109,6 +109,13 @@ std::pair > CS::Editor::readConfi mCfgMgr.readConfiguration(variables, desc, false); setupLogging(mCfgMgr.getLogPath().string(), "OpenMW-CS"); + return variables; +} + +std::pair > CS::Editor::readConfig(bool quiet) +{ + boost::program_options::variables_map& variables = mConfigVariables; + Fallback::Map::init(variables["fallback"].as().mMap); mEncodingName = variables["encoding"].as(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 1c93427613..7b258c8049 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -40,6 +40,7 @@ namespace CS Q_OBJECT Files::ConfigurationManager mCfgMgr; + boost::program_options::variables_map mConfigVariables; CSMPrefs::State mSettingsState; CSMDoc::DocumentManager mDocumentManager; CSVDoc::StartupDialogue mStartup; @@ -58,6 +59,8 @@ namespace CS Files::PathContainer mDataDirs; std::string mEncodingName; + boost::program_options::variables_map readConfiguration(); + ///< Calls mCfgMgr.readConfiguration; should be used before initialization of mSettingsState as it depends on the configuration. std::pair > readConfig(bool quiet=false); ///< \return data paths diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 06dc1f4a2e..ee0cbbed5a 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -16,22 +16,7 @@ CSMPrefs::State *CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::load() { - // default settings file - boost::filesystem::path local = mConfigurationManager.getLocalPath() / mDefaultConfigFile; - boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mDefaultConfigFile; - - if (boost::filesystem::exists (local)) - mSettings.loadDefault (local.string()); - else if (boost::filesystem::exists (global)) - mSettings.loadDefault (global.string()); - else - throw std::runtime_error ("No default settings file found! Make sure the file \"" + mDefaultConfigFile + "\" was properly installed."); - - // user settings file - boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; - - if (boost::filesystem::exists (user)) - mSettings.loadUser (user.string()); + mSettings.load(mConfigurationManager); } void CSMPrefs::State::declare() diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 50053b8b76..cd32cb3b0d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -518,34 +518,6 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) mNewGame = newGame; } -std::string OMW::Engine::loadSettings (Settings::Manager & settings) -{ - const std::vector& paths = mCfgMgr.getActiveConfigPaths(); - if (paths.empty()) - throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); - - // Create the settings manager and load default settings file. - const std::string defaultsBin = (paths.front() / "defaults.bin").string(); - if (!boost::filesystem::exists(defaultsBin)) - throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); - settings.loadDefault(defaultsBin); - - // Load "settings.cfg" from every config dir except the last one as additional default settings. - for (int i = 0; i < static_cast(paths.size()) - 1; ++i) - { - const std::string additionalDefaults = (paths[i] / "settings.cfg").string(); - if (boost::filesystem::exists(additionalDefaults)) - settings.loadDefault(additionalDefaults, true); - } - - // Load "settings.cfg" from the last config as user settings if they exist. This path will be used to save modified settings. - std::string settingspath = (paths.back() / "settings.cfg").string(); - if (boost::filesystem::exists(settingspath)) - settings.loadUser(settingspath); - - return settingspath; -} - void OMW::Engine::createWindow(Settings::Manager& settings) { int screen = settings.getInt("screen", "Video"); @@ -981,8 +953,7 @@ void OMW::Engine::go() // Load settings Settings::Manager settings; - std::string settingspath; - settingspath = loadSettings (settings); + std::string settingspath = settings.load(mCfgMgr); MWClass::registerClasses(); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 09e74b348b..b8ff700492 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -3,6 +3,7 @@ #include +#include #include namespace Settings @@ -19,16 +20,33 @@ void Manager::clear() mChangedSettings.clear(); } -void Manager::loadDefault(const std::string &file, bool overrideExisting) +std::string Manager::load(const Files::ConfigurationManager& cfgMgr) { SettingsFileParser parser; - parser.loadSettingsFile(file, mDefaultSettings, true, overrideExisting); -} + const std::vector& paths = cfgMgr.getActiveConfigPaths(); + if (paths.empty()) + throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); + + // Create the settings manager and load default settings file. + const std::string defaultsBin = (paths.front() / "defaults.bin").string(); + if (!boost::filesystem::exists(defaultsBin)) + throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); + parser.loadSettingsFile(defaultsBin, mDefaultSettings, true, false); + + // Load "settings.cfg" from every config dir except the last one as additional default settings. + for (int i = 0; i < static_cast(paths.size()) - 1; ++i) + { + const std::string additionalDefaults = (paths[i] / "settings.cfg").string(); + if (boost::filesystem::exists(additionalDefaults)) + parser.loadSettingsFile(additionalDefaults, mDefaultSettings, false, true); + } -void Manager::loadUser(const std::string &file) -{ - SettingsFileParser parser; - parser.loadSettingsFile(file, mUserSettings); + // Load "settings.cfg" from the last config as user settings if they exist. This path will be used to save modified settings. + std::string settingspath = (paths.back() / "settings.cfg").string(); + if (boost::filesystem::exists(settingspath)) + parser.loadSettingsFile(settingspath, mUserSettings, false, false); + + return settingspath; } void Manager::saveUser(const std::string &file) diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 84e7066fbd..04831ef171 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -9,6 +9,11 @@ #include #include +namespace Files +{ + struct ConfigurationManager; +} + namespace Settings { /// @@ -26,11 +31,8 @@ namespace Settings void clear(); ///< clears all settings and default settings - void loadDefault (const std::string& file, bool overrideExisting = false); - ///< load file as the default settings (can be overridden by user settings) - - void loadUser (const std::string& file); - ///< load file as user settings + std::string load(const Files::ConfigurationManager& cfgMgr); + ///< load settings from all active config dirs. Returns the path of the last loaded file. void saveUser (const std::string& file); ///< save user settings to file From 5ff2fc55ac6976750c9f096ea84085a03782ac4e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 16 Jan 2022 02:10:13 +0100 Subject: [PATCH 2052/2859] Make ConfigurationManager::getUserConfigPath to return the actual config path where the settings, logs, and Lua storage are stored. --- components/files/configurationmanager.cpp | 5 ++++- components/files/configurationmanager.hpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 739c7f7b43..b814da18ff 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -314,7 +314,10 @@ const boost::filesystem::path& ConfigurationManager::getGlobalPath() const const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const { - return mFixedPath.getUserConfigPath(); + if (mActiveConfigPaths.empty()) + return mFixedPath.getUserConfigPath(); + else + return mActiveConfigPaths.back(); } const boost::filesystem::path& ConfigurationManager::getUserDataPath() const diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 07a9300305..fcbb0d5ff5 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -32,10 +32,10 @@ struct ConfigurationManager /**< Fixed paths */ const boost::filesystem::path& getGlobalPath() const; - const boost::filesystem::path& getUserConfigPath() const; const boost::filesystem::path& getLocalPath() const; const boost::filesystem::path& getGlobalDataPath() const; + const boost::filesystem::path& getUserConfigPath() const; const boost::filesystem::path& getUserDataPath() const; const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; From 9c1ff16b6202b00d3df706e0fb4965ee0730827f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 20 Jan 2022 18:20:28 +0100 Subject: [PATCH 2053/2859] Add config variable `user-data="?userdata?"`. Allows to override paths to saves and screenshots. --- components/files/configurationmanager.cpp | 124 +++++++++++++--------- components/files/configurationmanager.hpp | 2 + files/openmw.cfg | 1 + files/openmw.cfg.local | 1 + 4 files changed, 76 insertions(+), 52 deletions(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index b814da18ff..4101c4ef54 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -30,18 +30,10 @@ ConfigurationManager::ConfigurationManager(bool silent) { setupTokensMapping(); - boost::filesystem::create_directories(mFixedPath.getUserConfigPath()); - boost::filesystem::create_directories(mFixedPath.getUserDataPath()); - + // Initialize with fixed paths, will be overridden in `readConfiguration`. mLogPath = mFixedPath.getUserConfigPath(); - + mUserDataPath = mFixedPath.getUserDataPath(); mScreenshotPath = mFixedPath.getUserDataPath() / "screenshots"; - - // probably not necessary but validate the creation of the screenshots directory and fallback to the original behavior if it fails - boost::system::error_code dirErr; - if (!boost::filesystem::create_directories(mScreenshotPath, dirErr) && !boost::filesystem::is_directory(mScreenshotPath)) { - mScreenshotPath = mFixedPath.getUserDataPath(); - } } ConfigurationManager::~ConfigurationManager() @@ -114,6 +106,30 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m } mLogPath = mActiveConfigPaths.back(); + mUserDataPath = variables["user-data"].as(); + if (mUserDataPath.empty()) + { + if (!quiet) + Log(Debug::Warning) << "Error: `user-data` is not specified"; + mUserDataPath = mFixedPath.getUserDataPath(); + } + processPath(mUserDataPath, true); + mScreenshotPath = mUserDataPath / "screenshots"; + + boost::filesystem::create_directories(getUserConfigPath()); + boost::filesystem::create_directories(mScreenshotPath); + + // probably not necessary but validate the creation of the screenshots directory and fallback to the original behavior if it fails + if (!boost::filesystem::is_directory(mScreenshotPath)) + mScreenshotPath = mUserDataPath; + + if (!quiet) + { + Log(Debug::Info) << "Logs dir: " << getUserConfigPath().string(); + Log(Debug::Info) << "User data dir: " << mUserDataPath.string(); + Log(Debug::Info) << "Screenshots dir: " << mScreenshotPath.string(); + } + mSilent = silent; } @@ -141,8 +157,9 @@ void ConfigurationManager::addExtraConfigDirs(std::stack()->default_value(Files::MaybeQuotedPathContainer(), "config") - ->multitoken()->composing(), "additional config directories"); + ("config", bpo::value()->multitoken()->composing(), "additional config directories") + ("user-data", bpo::value(), + "set user data directory (used for saves, screenshots, etc)"); } boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, @@ -230,54 +247,57 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost boost::program_options::notify(first); } -void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) const +void ConfigurationManager::processPath(boost::filesystem::path& path, bool create) const { - std::string path; - for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) - { - path = it->string(); + std::string str = path.string(); + + // Do nothing if the path doesn't start with a token + if (str.empty() || str[0] != '?') + return; - // Check if path contains a token - if (!path.empty() && *path.begin() == '?') + std::string::size_type pos = str.find('?', 1); + if (pos != std::string::npos && pos != 0) + { + auto tokenIt = mTokensMapping.find(str.substr(0, pos + 1)); + if (tokenIt != mTokensMapping.end()) { - std::string::size_type pos = path.find('?', 1); - if (pos != std::string::npos && pos != 0) + boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); + if (pos < str.length() - 1) { - auto tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); - if (tokenIt != mTokensMapping.end()) - { - boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); - if (pos < path.length() - 1) - { - // There is something after the token, so we should - // append it to the path - tempPath /= path.substr(pos + 1, path.length() - pos); - } - - *it = tempPath; - } - else - { - // Clean invalid / unknown token, it will be removed outside the loop - (*it).clear(); - } + // There is something after the token, so we should + // append it to the path + tempPath /= str.substr(pos + 1, str.length() - pos); } + + path = tempPath; + } + else + { + if (!mSilent) + Log(Debug::Warning) << "Path starts with unknown token: " << path; + path.clear(); } + } - if (!boost::filesystem::is_directory(*it)) + if (!boost::filesystem::is_directory(path) && create) + { + try { - if (create) - { - try - { - boost::filesystem::create_directories (*it); - } - catch (...) {} - - if (boost::filesystem::is_directory(*it)) - continue; - } + boost::filesystem::create_directories(path); + } + catch (...) {} + } +} +void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) const +{ + for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) + { + processPath(*it, create); + if (!boost::filesystem::is_directory(*it)) + { + if (!mSilent) + Log(Debug::Warning) << "No such dir: " << *it; (*it).clear(); } } @@ -322,7 +342,7 @@ const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const const boost::filesystem::path& ConfigurationManager::getUserDataPath() const { - return mFixedPath.getUserDataPath(); + return mUserDataPath; } const boost::filesystem::path& ConfigurationManager::getLocalPath() const diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index fcbb0d5ff5..49844cca41 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -27,6 +27,7 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet=false); + void processPath(boost::filesystem::path& path, bool create = false) const; void processPaths(Files::PathContainer& dataDirs, bool create = false) const; ///< \param create Try creating the directory, if it does not exist. @@ -70,6 +71,7 @@ struct ConfigurationManager FixedPathType mFixedPath; boost::filesystem::path mLogPath; + boost::filesystem::path mUserDataPath; boost::filesystem::path mScreenshotPath; TokensMappingContainer mTokensMapping; diff --git a/files/openmw.cfg b/files/openmw.cfg index 7bd70c3e5e..d1ecd6f8a3 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +user-data="?userdata?" config="?userconfig?" resources=${OPENMW_RESOURCE_FILES} script-blacklist=Museum diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index 79ab9e6156..f928113002 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +user-data="?userdata?" config="?userconfig?" resources=./resources script-blacklist=Museum From 5b23ba3faf36c0aebe5de58a3a5decb2b600d779 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 2 Feb 2022 00:20:32 +0100 Subject: [PATCH 2054/2859] Fix `defaulted` in ConfigurationManager::mergeComposingVariables --- components/files/configurationmanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 4101c4ef54..4ffd9013b7 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -187,7 +187,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost if (description.find_nothrow("replace", false)) { auto replace = second["replace"]; - if (!replace.empty()) + if (!replace.defaulted() && !replace.empty()) { std::vector replaceVector = replace.as>(); replacedVariables.insert(replaceVector.begin(), replaceVector.end()); @@ -206,13 +206,13 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost continue; } - if (replacedVariables.count(name)) + if (replacedVariables.count(name) || firstPosition->second.defaulted() || firstPosition->second.empty()) { firstPosition->second = second[name]; continue; } - if (second[name].empty()) + if (second[name].defaulted() || second[name].empty()) continue; boost::any& firstValue = firstPosition->second.value(); From 6084dbfc3a64feaee7305b2f710c5855f8f34678 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 3 Feb 2022 00:07:30 +0100 Subject: [PATCH 2055/2859] Add a warning if replace==config is used. --- components/files/configurationmanager.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 4ffd9013b7..87f4cd4943 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -123,6 +123,18 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m if (!boost::filesystem::is_directory(mScreenshotPath)) mScreenshotPath = mUserDataPath; + if (!quiet && !variables["replace"].empty()) + { + for (const std::string& var : variables["replace"].as>()) + { + if (var == "config") + { + Log(Debug::Warning) << "replace=config is not allowed and was ignored"; + break; + } + } + } + if (!quiet) { Log(Debug::Info) << "Logs dir: " << getUserConfigPath().string(); From 16cfdfec19145ac2b890e5a30ad4075092f06da8 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 3 Feb 2022 00:04:28 +0100 Subject: [PATCH 2056/2859] Avoid unnecessary computation And fix UBSAN error: /home/elsid/dev/openmw/apps/openmw/mwrender/renderingmanager.cpp:659:81: runtime error: division by zero #0 0x556eac16f4dc in MWRender::RenderingManager::configureAmbient(ESM::Cell const*) /home/elsid/dev/openmw/apps/openmw/mwrender/renderingmanager.cpp:659 #1 0x556eadfd3d60 in MWWorld::Scene::loadCell(MWWorld::CellStore*, Loading::Listener*, bool, osg::Vec3f const&) /home/elsid/dev/openmw/apps/openmw/mwworld/scene.cpp:467 #2 0x556eadfe3047 in MWWorld::Scene::changeToInteriorCell(std::__cxx11::basic_string, std::allocator > const&, ESM::Position const&, bool, bool) /home/elsid/dev/openmw/apps/openmw/mwworld/scene.cpp:830 #3 0x556eadeb8fb3 in MWWorld::World::changeToInteriorCell(std::__cxx11::basic_string, std::allocator > const&, ESM::Position const&, bool, bool) /home/elsid/dev/openmw/apps/openmw/mwworld/worldimp.cpp:978 #4 0x556eadeba5f1 in MWWorld::World::changeToCell(ESM::CellId const&, ESM::Position const&, bool, bool) /home/elsid/dev/openmw/apps/openmw/mwworld/worldimp.cpp:1008 #5 0x556eaeb852dd in MWState::StateManager::loadGame(MWState::Character const*, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:533 #6 0x556eaeb81674 in MWState::StateManager::loadGame(std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/apps/openmw/mwstate/statemanagerimp.cpp:366 #7 0x556eaebd2aae in OMW::Engine::go() /home/elsid/dev/openmw/apps/openmw/engine.cpp:1025 #8 0x556eaeba810a in runApplication(int, char**) /home/elsid/dev/openmw/apps/openmw/main.cpp:221 #9 0x556eaf865e9a in wrapApplication(int (*)(int, char**), int, char**, std::__cxx11::basic_string, std::allocator > const&) /home/elsid/dev/openmw/components/debug/debugging.cpp:205 #10 0x556eaeba8368 in main /home/elsid/dev/openmw/apps/openmw/main.cpp:233 #11 0x7f89773b3b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24) #12 0x556eac13c09d in _start (/home/elsid/dev/openmw/build/gcc/ubsan/openmw+0x669c09d) --- apps/openmw/mwrender/renderingmanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6c9ff173ae..90fe0877ec 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -656,11 +656,10 @@ namespace MWRender if (relativeLuminance < mMinimumAmbientLuminance) { // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can - float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f) ambient = osg::Vec4(mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a()); else - ambient *= targetBrightnessIncreaseFactor; + ambient *= mMinimumAmbientLuminance / relativeLuminance; } } From 6e4f6288664888a6d70da26a248c155c1f73c463 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 1 Feb 2022 00:32:26 +0100 Subject: [PATCH 2057/2859] Replace `inventory:get()` with `inventory:getAll()` --- apps/openmw/mwlua/luabindings.cpp | 13 +- apps/openmw/mwlua/object.cpp | 32 ++--- apps/openmw/mwlua/object.hpp | 25 ++++ apps/openmw/mwlua/objectbindings.cpp | 61 ++++----- files/lua_api/openmw/core.lua | 182 +++++++++------------------ 5 files changed, 141 insertions(+), 172 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 309ab51c08..084de7d5e1 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -16,10 +16,10 @@ namespace MWLua { - static sol::table definitionList(LuaUtil::LuaState& lua, std::initializer_list values) + static sol::table definitionList(LuaUtil::LuaState& lua, std::initializer_list values) { sol::table res(lua.sol(), sol::create); - for (const std::string& v : values) + for (const std::string_view& v : values) res[v] = v; return LuaUtil::makeReadOnly(res); } @@ -49,7 +49,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 15; + api["API_REVISION"] = 16; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); @@ -62,8 +62,11 @@ namespace MWLua addTimeBindings(api, context, false); api["OBJECT_TYPE"] = definitionList(*lua, { - "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", - "Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon" + ObjectTypeName::Activator, ObjectTypeName::Armor, ObjectTypeName::Book, ObjectTypeName::Clothing, + ObjectTypeName::Creature, ObjectTypeName::Door, ObjectTypeName::Ingredient, ObjectTypeName::Light, + ObjectTypeName::MiscItem, ObjectTypeName::NPC, ObjectTypeName::Player, ObjectTypeName::Potion, + ObjectTypeName::Static, ObjectTypeName::Weapon, ObjectTypeName::Activator, ObjectTypeName::Lockpick, + ObjectTypeName::Probe, ObjectTypeName::Repair }); api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ {"Helmet", MWWorld::InventoryStore::Slot_Helmet}, diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 93a4709efb..816ec6712b 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -17,20 +17,24 @@ namespace MWLua }; const static std::unordered_map luaObjectTypeInfo = { - {ESM::REC_ACTI, {"Activator", ESM::LuaScriptCfg::sActivator}}, - {ESM::REC_ARMO, {"Armor", ESM::LuaScriptCfg::sArmor}}, - {ESM::REC_BOOK, {"Book", ESM::LuaScriptCfg::sBook}}, - {ESM::REC_CLOT, {"Clothing", ESM::LuaScriptCfg::sClothing}}, - {ESM::REC_CONT, {"Container", ESM::LuaScriptCfg::sContainer}}, - {ESM::REC_CREA, {"Creature", ESM::LuaScriptCfg::sCreature}}, - {ESM::REC_DOOR, {"Door", ESM::LuaScriptCfg::sDoor}}, - {ESM::REC_INGR, {"Ingredient", ESM::LuaScriptCfg::sIngredient}}, - {ESM::REC_LIGH, {"Light", ESM::LuaScriptCfg::sLight}}, - {ESM::REC_MISC, {"Miscellaneous", ESM::LuaScriptCfg::sMiscItem}}, - {ESM::REC_NPC_, {"NPC", ESM::LuaScriptCfg::sNPC}}, - {ESM::REC_ALCH, {"Potion", ESM::LuaScriptCfg::sPotion}}, - {ESM::REC_STAT, {"Static"}}, - {ESM::REC_WEAP, {"Weapon", ESM::LuaScriptCfg::sWeapon}}, + {ESM::REC_ACTI, {ObjectTypeName::Activator, ESM::LuaScriptCfg::sActivator}}, + {ESM::REC_ARMO, {ObjectTypeName::Armor, ESM::LuaScriptCfg::sArmor}}, + {ESM::REC_BOOK, {ObjectTypeName::Book, ESM::LuaScriptCfg::sBook}}, + {ESM::REC_CLOT, {ObjectTypeName::Clothing, ESM::LuaScriptCfg::sClothing}}, + {ESM::REC_CONT, {ObjectTypeName::Container, ESM::LuaScriptCfg::sContainer}}, + {ESM::REC_CREA, {ObjectTypeName::Creature, ESM::LuaScriptCfg::sCreature}}, + {ESM::REC_DOOR, {ObjectTypeName::Door, ESM::LuaScriptCfg::sDoor}}, + {ESM::REC_INGR, {ObjectTypeName::Ingredient, ESM::LuaScriptCfg::sIngredient}}, + {ESM::REC_LIGH, {ObjectTypeName::Light, ESM::LuaScriptCfg::sLight}}, + {ESM::REC_MISC, {ObjectTypeName::MiscItem, ESM::LuaScriptCfg::sMiscItem}}, + {ESM::REC_NPC_, {ObjectTypeName::NPC, ESM::LuaScriptCfg::sNPC}}, + {ESM::REC_ALCH, {ObjectTypeName::Potion, ESM::LuaScriptCfg::sPotion}}, + {ESM::REC_STAT, {ObjectTypeName::Static}}, + {ESM::REC_WEAP, {ObjectTypeName::Weapon, ESM::LuaScriptCfg::sWeapon}}, + {ESM::REC_APPA, {ObjectTypeName::Apparatus}}, + {ESM::REC_LOCK, {ObjectTypeName::Lockpick}}, + {ESM::REC_PROB, {ObjectTypeName::Probe}}, + {ESM::REC_REPA, {ObjectTypeName::Repair}}, }; std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback) diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index e87721e381..0b51da17c3 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -14,6 +14,31 @@ namespace MWLua { + namespace ObjectTypeName + { + // Names of object types in Lua. + // These names are part of OpenMW Lua API. + constexpr std::string_view Activator = "Activator"; + constexpr std::string_view Armor = "Armor"; + constexpr std::string_view Book = "Book"; + constexpr std::string_view Clothing = "Clothing"; + constexpr std::string_view Container = "Container"; + constexpr std::string_view Creature = "Creature"; + constexpr std::string_view Door = "Door"; + constexpr std::string_view Ingredient = "Ingredient"; + constexpr std::string_view Light = "Light"; + constexpr std::string_view MiscItem = "Miscellaneous"; + constexpr std::string_view NPC = "NPC"; + constexpr std::string_view Player = "Player"; + constexpr std::string_view Potion = "Potion"; + constexpr std::string_view Static = "Static"; + constexpr std::string_view Weapon = "Weapon"; + constexpr std::string_view Apparatus = "Apparatus"; + constexpr std::string_view Lockpick = "Lockpick"; + constexpr std::string_view Probe = "Probe"; + constexpr std::string_view Repair = "Repair"; + } + // ObjectId is a unique identifier of a game object. // It can change only if the order of content files was change. using ObjectId = ESM::RefNum; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 9543e306c1..ef0482f9fe 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -309,8 +309,38 @@ namespace MWLua inventoryT[sol::meta_function::to_string] = [](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; }; - auto getWithMask = [context](const InventoryT& inventory, int mask) + inventoryT["getAll"] = [worldView=context.mWorldView](const InventoryT& inventory, sol::optional type) { + int mask; + if (!type.has_value()) + mask = MWWorld::ContainerStore::Type_All; + else if (*type == ObjectTypeName::Potion) + mask = MWWorld::ContainerStore::Type_Potion; + else if (*type == ObjectTypeName::Armor) + mask = MWWorld::ContainerStore::Type_Armor; + else if (*type == ObjectTypeName::Book) + mask = MWWorld::ContainerStore::Type_Book; + else if (*type == ObjectTypeName::Clothing) + mask = MWWorld::ContainerStore::Type_Clothing; + else if (*type == ObjectTypeName::Ingredient) + mask = MWWorld::ContainerStore::Type_Ingredient; + else if (*type == ObjectTypeName::Light) + mask = MWWorld::ContainerStore::Type_Light; + else if (*type == ObjectTypeName::MiscItem) + mask = MWWorld::ContainerStore::Type_Miscellaneous; + else if (*type == ObjectTypeName::Weapon) + mask = MWWorld::ContainerStore::Type_Weapon; + else if (*type == ObjectTypeName::Apparatus) + mask = MWWorld::ContainerStore::Type_Apparatus; + else if (*type == ObjectTypeName::Lockpick) + mask = MWWorld::ContainerStore::Type_Lockpick; + else if (*type == ObjectTypeName::Probe) + mask = MWWorld::ContainerStore::Type_Probe; + else if (*type == ObjectTypeName::Repair) + mask = MWWorld::ContainerStore::Type_Repair; + else + throw std::runtime_error(std::string("inventory:getAll doesn't support type " + std::string(*type))); + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); ObjectIdList list = std::make_shared>(); @@ -318,39 +348,12 @@ namespace MWLua while (it.getType() != -1) { const MWWorld::Ptr& item = *(it++); - context.mWorldView->getObjectRegistry()->registerPtr(item); + worldView->getObjectRegistry()->registerPtr(item); list->push_back(getId(item)); } return ObjectList{list}; }; - inventoryT["getAll"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_All); }; - inventoryT["getPotions"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Potion); }; - inventoryT["getApparatuses"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Apparatus); }; - inventoryT["getArmor"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Armor); }; - inventoryT["getBooks"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Book); }; - inventoryT["getClothing"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Clothing); }; - inventoryT["getIngredients"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Ingredient); }; - inventoryT["getLights"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Light); }; - inventoryT["getLockpicks"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Lockpick); }; - inventoryT["getMiscellaneous"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Miscellaneous); }; - inventoryT["getProbes"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Probe); }; - inventoryT["getRepairKits"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Repair); }; - inventoryT["getWeapons"] = - [getWithMask](const InventoryT& inventory) { return getWithMask(inventory, MWWorld::ContainerStore::Type_Weapon); }; - inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId) { const MWWorld::Ptr& ptr = inventory.mObj.ptr(); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 95732af837..07309edca8 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -88,21 +88,24 @@ ------------------------------------------------------------------------------- -- @type OBJECT_TYPE --- @field [parent=#OBJECT_TYPE] #string Activator "Activator" --- @field [parent=#OBJECT_TYPE] #string Armor "Armor" --- @field [parent=#OBJECT_TYPE] #string Book "Book" --- @field [parent=#OBJECT_TYPE] #string Clothing "Clothing" --- @field [parent=#OBJECT_TYPE] #string Container "Container" --- @field [parent=#OBJECT_TYPE] #string Creature "Creature" --- @field [parent=#OBJECT_TYPE] #string Door "Door" --- @field [parent=#OBJECT_TYPE] #string Ingredient "Ingredient" --- @field [parent=#OBJECT_TYPE] #string Light "Light" --- @field [parent=#OBJECT_TYPE] #string Miscellaneous "Miscellaneous" --- @field [parent=#OBJECT_TYPE] #string NPC "NPC" --- @field [parent=#OBJECT_TYPE] #string Player "Player" --- @field [parent=#OBJECT_TYPE] #string Potion "Potion" --- @field [parent=#OBJECT_TYPE] #string Static "Static" --- @field [parent=#OBJECT_TYPE] #string Weapon "Weapon" +-- @field #string Activator "Activator" +-- @field #string Armor "Armor" +-- @field #string Book "Book" +-- @field #string Clothing "Clothing" +-- @field #string Container "Container" +-- @field #string Creature "Creature" +-- @field #string Door "Door" +-- @field #string Ingredient "Ingredient" +-- @field #string Light "Light" +-- @field #string Miscellaneous "Miscellaneous" +-- @field #string NPC "NPC" +-- @field #string Player "Player" +-- @field #string Potion "Potion" +-- @field #string Static "Static" +-- @field #string Apparatus "Apparatus" +-- @field #string Lockpick "Lockpick" +-- @field #string Probe "Probe" +-- @field #string Repair "Repair" ------------------------------------------------------------------------------- -- Possible `object.type` values. @@ -111,25 +114,25 @@ ------------------------------------------------------------------------------- -- @type EQUIPMENT_SLOT --- @field [parent=#EQUIPMENT_SLOT] #number Helmet --- @field [parent=#EQUIPMENT_SLOT] #number Cuirass --- @field [parent=#EQUIPMENT_SLOT] #number Greaves --- @field [parent=#EQUIPMENT_SLOT] #number LeftPauldron --- @field [parent=#EQUIPMENT_SLOT] #number RightPauldron --- @field [parent=#EQUIPMENT_SLOT] #number LeftGauntlet --- @field [parent=#EQUIPMENT_SLOT] #number RightGauntlet --- @field [parent=#EQUIPMENT_SLOT] #number Boots --- @field [parent=#EQUIPMENT_SLOT] #number Shirt --- @field [parent=#EQUIPMENT_SLOT] #number Pants --- @field [parent=#EQUIPMENT_SLOT] #number Skirt --- @field [parent=#EQUIPMENT_SLOT] #number Robe --- @field [parent=#EQUIPMENT_SLOT] #number LeftRing --- @field [parent=#EQUIPMENT_SLOT] #number RightRing --- @field [parent=#EQUIPMENT_SLOT] #number Amulet --- @field [parent=#EQUIPMENT_SLOT] #number Belt --- @field [parent=#EQUIPMENT_SLOT] #number CarriedRight --- @field [parent=#EQUIPMENT_SLOT] #number CarriedLeft --- @field [parent=#EQUIPMENT_SLOT] #number Ammunition +-- @field #number Helmet +-- @field #number Cuirass +-- @field #number Greaves +-- @field #number LeftPauldron +-- @field #number RightPauldron +-- @field #number LeftGauntlet +-- @field #number RightGauntlet +-- @field #number Boots +-- @field #number Shirt +-- @field #number Pants +-- @field #number Skirt +-- @field #number Robe +-- @field #number LeftRing +-- @field #number RightRing +-- @field #number Amulet +-- @field #number Belt +-- @field #number CarriedRight +-- @field #number CarriedLeft +-- @field #number Ammunition ------------------------------------------------------------------------------- -- Available equipment slots. Used in `object:getEquipment` and `object:setEquipment`. @@ -140,17 +143,17 @@ -- Any object that exists in the game world and has a specific location. -- Player, actors, items, and statics are game objects. -- @type GameObject --- @field [parent=#GameObject] openmw.util#Vector3 position Object position. --- @field [parent=#GameObject] openmw.util#Vector3 rotation Object rotation (ZXY order). --- @field [parent=#GameObject] #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil. --- @field [parent=#GameObject] #string type Type of the object (see @{openmw.core#OBJECT_TYPE}). --- @field [parent=#GameObject] #number count Count (makes sense if holded in a container). --- @field [parent=#GameObject] #string recordId Record ID. --- @field [parent=#GameObject] #Inventory inventory Inventory of an Player/NPC or content of an container. --- @field [parent=#GameObject] #boolean isTeleport `True` if it is a teleport door (only if `object.type` == "Door"). --- @field [parent=#GameObject] openmw.util#Vector3 destPosition Destination (only if a teleport door). --- @field [parent=#GameObject] openmw.util#Vector3 destRotation Destination rotation (only if a teleport door). --- @field [parent=#GameObject] #string destCell Destination cell (only if a teleport door). +-- @field openmw.util#Vector3 position Object position. +-- @field openmw.util#Vector3 rotation Object rotation (ZXY order). +-- @field #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil. +-- @field #string type Type of the object (see @{openmw.core#OBJECT_TYPE}). +-- @field #number count Count (makes sense if holded in a container). +-- @field #string recordId Record ID. +-- @field #Inventory inventory Inventory of an Player/NPC or content of an container. +-- @field #boolean isTeleport `True` if it is a teleport door (only if `object.type` == "Door"). +-- @field openmw.util#Vector3 destPosition Destination (only if a teleport door). +-- @field openmw.util#Vector3 destRotation Destination rotation (only if a teleport door). +-- @field #string destCell Destination cell (only if a teleport door). ------------------------------------------------------------------------------- -- Is the object still exists/available. @@ -306,12 +309,12 @@ ------------------------------------------------------------------------------- -- A cell of the game world. -- @type Cell --- @field [parent=#Cell] #string name Name of the cell (can be empty string). --- @field [parent=#Cell] #string region Region of the cell. --- @field [parent=#Cell] #boolean isExterior Is it exterior or interior. --- @field [parent=#Cell] #number gridX Index of the cell by X (only for exteriors). --- @field [parent=#Cell] #number gridY Index of the cell by Y (only for exteriors). --- @field [parent=#Cell] #boolean hasWater True if the cell contains water. +-- @field #string name Name of the cell (can be empty string). +-- @field #string region Region of the cell. +-- @field #boolean isExterior Is it exterior or interior. +-- @field #number gridX Index of the cell by X (only for exteriors). +-- @field #number gridY Index of the cell by Y (only for exteriors). +-- @field #boolean hasWater True if the cell contains water. ------------------------------------------------------------------------------- -- Returns true either if the cell contains the object or if the cell is an exterior and the object is also in an exterior. @@ -347,82 +350,13 @@ -- @return #number ------------------------------------------------------------------------------- --- Get all content of the inventory. +-- Get all items of given type from the inventory. -- @function [parent=#Inventory] getAll -- @param self +-- @param type (optional) items type (see @{openmw.core#OBJECT_TYPE}) -- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all potions. --- @function [parent=#Inventory] getPotions --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all apparatuses. --- @function [parent=#Inventory] getApparatuses --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all armor. --- @function [parent=#Inventory] getArmor --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all books. --- @function [parent=#Inventory] getBooks --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all clothing. --- @function [parent=#Inventory] getClothing --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all ingredients. --- @function [parent=#Inventory] getIngredients --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all lights. --- @function [parent=#Inventory] getLights --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all lockpicks. --- @function [parent=#Inventory] getLockpicks --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all miscellaneous items. --- @function [parent=#Inventory] getMiscellaneous --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all probes. --- @function [parent=#Inventory] getProbes --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all repair kits. --- @function [parent=#Inventory] getRepairKits --- @param self --- @return #ObjectList - -------------------------------------------------------------------------------- --- Get all weapon. --- @function [parent=#Inventory] getWeapons --- @param self --- @return #ObjectList +-- @usage local all = inventory:getAll() +-- local weapons = inventory:getAll(core.OBJECT_TYPE.Weapon) return nil From 542717394a7f18553843bcaf0ee4b976b5baf2e8 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Feb 2022 21:48:06 +0100 Subject: [PATCH 2058/2859] Remove objects, water and heightfields when no longer required --- .../detournavigator/navigator.cpp | 12 ++++++++++ .../tilecachedrecastmeshmanager.cpp | 4 ++-- .../tilecachedrecastmeshmanager.cpp | 22 ++++++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 09a2be58ee..50bf1fe735 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1132,4 +1132,16 @@ namespace Vec3fEq(306, 56.66666412353515625, -2.6667339801788330078125) )) << mPath; } + + TEST_F(DetourNavigatorNavigatorTest, only_one_water_per_cell_is_allowed) + { + const int cellSize1 = 100; + const float level1 = 1; + const int cellSize2 = 200; + const float level2 = 2; + + mNavigator->addAgent(mAgentHalfExtents); + EXPECT_TRUE(mNavigator->addWater(mCellPosition, cellSize1, level1)); + EXPECT_FALSE(mNavigator->addWater(mCellPosition, cellSize2, level2)); + } } diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index c44ebc5155..c637d35424 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -63,7 +63,7 @@ namespace EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } - TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_throw_exception) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); @@ -225,7 +225,7 @@ namespace const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeAddRevision = manager.getRevision(); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); EXPECT_EQ(manager.getRevision(), beforeAddRevision); } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 17ba7afc39..d1f586f85c 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -33,6 +33,9 @@ namespace DetourNavigator bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { + const auto it = mObjectsTilesPositions.find(id); + if (it != mObjectsTilesPositions.end()) + return false; std::vector tilesPositions; { const std::lock_guard lock(mMutex); @@ -46,7 +49,7 @@ namespace DetourNavigator if (tilesPositions.empty()) return false; std::sort(tilesPositions.begin(), tilesPositions.end()); - mObjectsTilesPositions.insert_or_assign(id, std::move(tilesPositions)); + mObjectsTilesPositions.emplace_hint(it, id, std::move(tilesPositions)); ++mRevision; return true; } @@ -66,6 +69,7 @@ namespace DetourNavigator result = removed; } } + mObjectsTilesPositions.erase(object); if (result) ++mRevision; return result; @@ -73,7 +77,12 @@ namespace DetourNavigator bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) { - auto& tilesPositions = mWaterTilesPositions[cellPosition]; + const auto it = mWaterTilesPositions.find(cellPosition); + if (it != mWaterTilesPositions.end()) + return false; + + std::vector& tilesPositions = mWaterTilesPositions.emplace_hint( + it, cellPosition, std::vector())->second; bool result = false; @@ -138,6 +147,7 @@ namespace DetourNavigator if (tileResult && !result) result = tileResult; } + mWaterTilesPositions.erase(object); if (result) ++mRevision; return result; @@ -146,8 +156,13 @@ namespace DetourNavigator bool TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) { + const auto it = mHeightfieldTilesPositions.find(cellPosition); + if (it != mHeightfieldTilesPositions.end()) + return false; + + std::vector& tilesPositions = mHeightfieldTilesPositions.emplace_hint( + it, cellPosition, std::vector())->second; const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize); - auto& tilesPositions = mHeightfieldTilesPositions[cellPosition]; bool result = false; @@ -196,6 +211,7 @@ namespace DetourNavigator if (tileResult && !result) result = tileResult; } + mHeightfieldTilesPositions.erase(object); if (result) ++mRevision; return result; From 783411fa1f6e958c473d14ad69f818464c56ea90 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 31 Jan 2022 22:33:42 +0100 Subject: [PATCH 2059/2859] Use new player position when updating navigator on cell loading --- apps/openmw/mwworld/scene.cpp | 14 ++++++++------ apps/openmw/mwworld/scene.hpp | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0d2db8b99a..3b2f9cd608 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -360,7 +360,7 @@ namespace MWWorld mActiveCells.erase(cell); } - void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn) + void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position) { using DetourNavigator::HeightfieldShape; @@ -461,7 +461,7 @@ namespace MWWorld mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); } - mNavigator.update(player.getRefData().getPosition().asVec3()); + mNavigator.update(position); if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) mRendering.configureAmbient(cell->getCell()); @@ -608,7 +608,7 @@ namespace MWWorld if (!isCellInCollection(x, y, mActiveCells)) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadCell (cell, loadingListener, changeEvent); + loadCell(cell, loadingListener, changeEvent, pos); } } @@ -641,7 +641,7 @@ namespace MWWorld loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadCell(cell, nullptr, false); + loadCell(cell, nullptr, false, osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits); auto iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -686,7 +686,9 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadCell(cell, nullptr, false); + ESM::Position position; + MWBase::Environment::get().getWorld()->findInteriorPosition(it->mName, position); + loadCell(cell, nullptr, false, position.asVec3()); auto iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -821,7 +823,7 @@ namespace MWWorld // Load cell. mPagedRefs.clear(); - loadCell(cell, loadingListener, changeEvent); + loadCell(cell, loadingListener, changeEvent, position.asVec3()); changePlayerCell(cell, position, adjustPlayerPos); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 623bddbc82..c0b6f5ae46 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -116,7 +116,7 @@ namespace MWWorld osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; void unloadCell(CellStore* cell); - void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn); + void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position); public: From 1a52a2a02909707a84e6afac6ea54a1926bc6f4f Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Feb 2022 00:24:56 +0100 Subject: [PATCH 2060/2859] Clamp tile position --- components/detournavigator/settingsutils.hpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 285920e5a0..a4c68e3cd5 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -50,12 +50,24 @@ namespace DetourNavigator return static_cast(settings.mTileSize) * settings.mCellSize; } + inline int getTilePosition(const RecastSettings& settings, float position) + { + const float v = std::floor(position / getTileSize(settings)); + if (v < static_cast(std::numeric_limits::min())) + return std::numeric_limits::min(); + if (v > static_cast(std::numeric_limits::max() - 1)) + return std::numeric_limits::max() - 1; + return static_cast(v); + } + + inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec2f& position) + { + return TilePosition(getTilePosition(settings, position.x()), getTilePosition(settings, position.y())); + } + inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec3f& position) { - return TilePosition( - static_cast(std::floor(position.x() / getTileSize(settings))), - static_cast(std::floor(position.z() / getTileSize(settings))) - ); + return getTilePosition(settings, osg::Vec2f(position.x(), position.z())); } inline TileBounds makeTileBounds(const RecastSettings& settings, const TilePosition& tilePosition) From 1b2954f2db6545c68e7f4f2eaaecdbfbb1b6239a Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Feb 2022 00:22:53 +0100 Subject: [PATCH 2061/2859] Remove unused z coordinate --- apps/navmeshtool/navmesh.cpp | 4 ++-- .../detournavigator/gettilespositions.cpp | 14 ++++++------ .../detournavigator/gettilespositions.cpp | 22 ++++++++----------- .../detournavigator/gettilespositions.hpp | 6 ++--- components/detournavigator/tilebounds.hpp | 13 +++++++++++ components/misc/convert.hpp | 7 +++++- 6 files changed, 40 insertions(+), 26 deletions(-) diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 39f2b2f37c..ca614d0cf6 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -177,8 +177,8 @@ namespace NavMeshTool DetourNavigator::getTilesPositions( DetourNavigator::makeTilesPositionsRange( - Misc::Convert::toOsg(input->mAabb.m_min), - Misc::Convert::toOsg(input->mAabb.m_max), + Misc::Convert::toOsgXY(input->mAabb.m_min), + Misc::Convert::toOsgXY(input->mAabb.m_max), settings.mRecast ), [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); } diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp index 8121c19205..70c6ef9a09 100644 --- a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -37,35 +37,35 @@ namespace TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec2f(2, 2), osg::Vec2f(31, 31), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings), mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(32, 31), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings), mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31, 32), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings), mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31, 31), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates) { - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings), mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec2f(-31, -31), osg::Vec2f(31, 31), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), @@ -79,7 +79,7 @@ namespace { mSettings.mBorderSize = 1; - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings), mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(31.5, 31.5), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre( TilePosition(-1, -1), @@ -98,7 +98,7 @@ namespace { mSettings.mRecastScaleFactor = 0.5; - getTilesPositions(makeTilesPositionsRange(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings), mCollect); + getTilesPositions(makeTilesPositionsRange(osg::Vec2f(0, 0), osg::Vec2f(32, 32), mSettings), mCollect); EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp index d427eb3e12..4bf5159a88 100644 --- a/components/detournavigator/gettilespositions.cpp +++ b/components/detournavigator/gettilespositions.cpp @@ -2,6 +2,7 @@ #include "settings.hpp" #include "settingsutils.hpp" #include "tileposition.hpp" +#include "tilebounds.hpp" #include @@ -9,15 +10,15 @@ namespace DetourNavigator { - TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, + TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin, const osg::Vec2f& aabbMax, const RecastSettings& settings) { - osg::Vec3f min = toNavMeshCoordinates(settings, aabbMin); - osg::Vec3f max = toNavMeshCoordinates(settings, aabbMax); + osg::Vec2f min = toNavMeshCoordinates(settings, aabbMin); + osg::Vec2f max = toNavMeshCoordinates(settings, aabbMax); const float border = getBorderSize(settings); - min -= osg::Vec3f(border, border, border); - max += osg::Vec3f(border, border, border); + min -= osg::Vec2f(border, border); + max += osg::Vec2f(border, border); TilePosition minTile = getTilePosition(settings, min); TilePosition maxTile = getTilePosition(settings, max); @@ -34,18 +35,13 @@ namespace DetourNavigator TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, const RecastSettings& settings) { - btVector3 aabbMin; - btVector3 aabbMax; - shape.getAabb(transform, aabbMin, aabbMax); - - return makeTilesPositionsRange(Misc::Convert::toOsg(aabbMin), Misc::Convert::toOsg(aabbMax), settings); + const TileBounds bounds = makeObjectTileBounds(shape, transform); + return makeTilesPositionsRange(bounds.mMin, bounds.mMax, settings); } TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings) { - using Misc::Convert::toOsg; - const int halfCellSize = cellSize / 2; const btTransform transform(btMatrix3x3::getIdentity(), shift); btVector3 aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); @@ -57,6 +53,6 @@ namespace DetourNavigator aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); - return makeTilesPositionsRange(toOsg(aabbMin), toOsg(aabbMax), settings); + return makeTilesPositionsRange(Misc::Convert::toOsgXY(aabbMin), Misc::Convert::toOsgXY(aabbMax), settings); } } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 946f3e64f2..8a8b2b7c32 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -9,7 +9,7 @@ class btCollisionShape; namespace osg { - class Vec3f; + class Vec2f; } namespace DetourNavigator @@ -22,8 +22,8 @@ namespace DetourNavigator TilePosition mMax; }; - TilesPositionsRange makeTilesPositionsRange(const osg::Vec3f& aabbMin, - const osg::Vec3f& aabbMax, const RecastSettings& settings); + TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin, + const osg::Vec2f& aabbMax, const RecastSettings& settings); TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, const RecastSettings& settings); diff --git a/components/detournavigator/tilebounds.hpp b/components/detournavigator/tilebounds.hpp index 693a382740..79f6370ff2 100644 --- a/components/detournavigator/tilebounds.hpp +++ b/components/detournavigator/tilebounds.hpp @@ -1,9 +1,14 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H +#include + #include #include +#include +#include + #include #include #include @@ -41,6 +46,14 @@ namespace DetourNavigator osg::Vec2f(position.x() + 1, position.y() + 1) * size }; } + + inline TileBounds makeObjectTileBounds(const btCollisionShape& shape, const btTransform& transform) + { + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(transform, aabbMin, aabbMax); + return TileBounds {Misc::Convert::toOsgXY(aabbMin), Misc::Convert::toOsgXY(aabbMax)}; + } } #endif diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index f31f1cc08e..0b84c32059 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -70,6 +70,11 @@ namespace Misc::Convert { return btTransform(makeBulletQuaternion(position), toBullet(position.asVec3())); } + + inline osg::Vec2f toOsgXY(const btVector3& value) + { + return osg::Vec2f(static_cast(value.x()), static_cast(value.y())); + } } -#endif \ No newline at end of file +#endif From a5b078e9a714ef0c078bf74ab041b02ded8c07e2 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Feb 2022 00:35:03 +0100 Subject: [PATCH 2062/2859] Allow to represent empty range with TilesPositionsRange --- components/detournavigator/gettilespositions.cpp | 2 +- components/detournavigator/gettilespositions.hpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp index 4bf5159a88..7a179aff24 100644 --- a/components/detournavigator/gettilespositions.cpp +++ b/components/detournavigator/gettilespositions.cpp @@ -29,7 +29,7 @@ namespace DetourNavigator if (minTile.y() > maxTile.y()) std::swap(minTile.y(), maxTile.y()); - return {minTile, maxTile}; + return {minTile, maxTile + osg::Vec2i(1, 1)}; } TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 8a8b2b7c32..db88708314 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -18,8 +18,8 @@ namespace DetourNavigator struct TilesPositionsRange { - TilePosition mMin; - TilePosition mMax; + TilePosition mBegin; + TilePosition mEnd; }; TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin, @@ -32,10 +32,10 @@ namespace DetourNavigator const RecastSettings& settings); template - void getTilesPositions(const TilesPositionsRange& range, Callback&& callback) + inline void getTilesPositions(const TilesPositionsRange& range, Callback&& callback) { - for (int tileX = range.mMin.x(); tileX <= range.mMax.x(); ++tileX) - for (int tileY = range.mMin.y(); tileY <= range.mMax.y(); ++tileY) + for (int tileX = range.mBegin.x(); tileX < range.mEnd.x(); ++tileX) + for (int tileY = range.mBegin.y(); tileY < range.mEnd.y(); ++tileY) callback(TilePosition {tileX, tileY}); } } From 563f3f87dd2dd6f174eb00c72853204ae432ca0b Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Feb 2022 00:42:03 +0100 Subject: [PATCH 2063/2859] Reduce critical sections size --- components/detournavigator/tilecachedrecastmeshmanager.cpp | 3 ++- components/detournavigator/tilecachedrecastmeshmanager.hpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index d1f586f85c..314e7641e3 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -38,8 +38,9 @@ namespace DetourNavigator return false; std::vector tilesPositions; { + const TilesPositionsRange range = makeTilesPositionsRange(shape.getShape(), transform, mSettings); const std::lock_guard lock(mMutex); - getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mSettings), + getTilesPositions(range, [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 88face24ce..efd70477d3 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -38,7 +38,6 @@ namespace DetourNavigator bool changed = false; std::vector newTiles; { - const std::lock_guard lock(mMutex); const auto onTilePosition = [&] (const TilePosition& tilePosition) { if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) @@ -57,7 +56,9 @@ namespace DetourNavigator changed = true; } }; - getTilesPositions(makeTilesPositionsRange(shape.getShape(), transform, mSettings), onTilePosition); + const TilesPositionsRange range = makeTilesPositionsRange(shape.getShape(), transform, mSettings); + const std::lock_guard lock(mMutex); + getTilesPositions(range, onTilePosition); std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { From 05b54cbfb86ec12e35278e0a69c108fabd4fb4b2 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Feb 2022 01:21:47 +0100 Subject: [PATCH 2064/2859] Cull navmesh objects by scene bounds If object is too big iteration over all tiles covering it can take too much time. Limit bounds to a square around a player position to cover only tiles that will be present in navmesh based on max tiles number option. Each object is associated with a set of tiles its present in. Culling can reduce this set but it has to be update when bounds change position. Do this in TileCachedRecastMeshManager::setBounds updating the set and adding/removing objects to the corresponding CachedRecastMeshManagers. --- apps/navmeshtool/worldspacedata.cpp | 6 +- apps/openmw/mwworld/scene.cpp | 4 + .../detournavigator/asyncnavmeshupdater.cpp | 2 +- .../detournavigator/operators.hpp | 8 -- .../tilecachedrecastmeshmanager.cpp | 127 ++++++++++++------ .../detournavigator/asyncnavmeshupdater.hpp | 24 +--- components/detournavigator/changetype.hpp | 33 +++++ .../detournavigator/gettilespositions.cpp | 21 +++ .../detournavigator/gettilespositions.hpp | 17 +++ components/detournavigator/navigator.hpp | 6 + components/detournavigator/navigatorimpl.cpp | 6 + components/detournavigator/navigatorimpl.hpp | 2 + components/detournavigator/navigatorstub.hpp | 2 + components/detournavigator/navmeshmanager.cpp | 43 +++++- components/detournavigator/navmeshmanager.hpp | 2 + components/detournavigator/settingsutils.hpp | 5 + components/detournavigator/tilebounds.hpp | 17 ++- .../tilecachedrecastmeshmanager.cpp | 107 +++++++++++---- .../tilecachedrecastmeshmanager.hpp | 73 +++++++--- 19 files changed, 378 insertions(+), 127 deletions(-) create mode 100644 components/detournavigator/changetype.hpp diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 742e6d775d..81bbd857f5 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -298,12 +298,14 @@ namespace NavMeshTool const ObjectId objectId(++objectsCounter); const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform()); - navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, DetourNavigator::AreaType_ground); + navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, + DetourNavigator::AreaType_ground, [] (const auto&) {}); if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) { const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform()); - navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, DetourNavigator::AreaType_null); + navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, + DetourNavigator::AreaType_null, [] (const auto&) {}); } data.mObjects.emplace_back(std::move(object)); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3b2f9cd608..1cb80389ce 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -534,6 +534,8 @@ namespace MWWorld unloadCell (cell); } + mNavigator.updateBounds(pos); + mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); mRendering.setActiveGrid(newGrid); @@ -821,6 +823,8 @@ namespace MWWorld loadingListener->setProgressRange(cell->count()); + mNavigator.updateBounds(position.asVec3()); + // Load cell. mPagedRefs.clear(); loadCell(cell, loadingListener, changeEvent, position.asVec3()); diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index b23446cea7..71c013d9f4 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -40,7 +40,7 @@ namespace osg::ref_ptr(new Resource::BulletShapeInstance(bulletShape)), shape, objectTransform ); - recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground); + recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground, [] (auto) {}); } struct DetourNavigatorAsyncNavMeshUpdaterTest : Test diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp index fb6fcc5c39..60e1400764 100644 --- a/apps/openmw_test_suite/detournavigator/operators.hpp +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -12,14 +12,6 @@ #include -namespace DetourNavigator -{ - static inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs) - { - return lhs.mMin == rhs.mMin && lhs.mMax == rhs.mMax; - } -} - namespace { template diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index c637d35424..4803f452b3 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -16,7 +16,8 @@ namespace struct DetourNavigatorTileCachedRecastMeshManagerTest : Test { RecastSettings mSettings; - std::vector mChangedTiles; + std::vector mAddedTiles; + std::vector> mChangedTiles; const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; const osg::ref_ptr mShape = new Resource::BulletShape; const osg::ref_ptr mInstance = new Resource::BulletShapeInstance(mShape); @@ -29,9 +30,14 @@ namespace mSettings.mTileSize = 64; } - void onChangedTile(const TilePosition& tilePosition) + void onAddedTile(const TilePosition& tilePosition) { - mChangedTiles.push_back(tilePosition); + mAddedTiles.push_back(tilePosition); + } + + void onChangedTile(const TilePosition& tilePosition, ChangeType changeType) + { + mChangedTiles.emplace_back(tilePosition, changeType); } }; @@ -60,16 +66,16 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); } - TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_throw_exception) + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) @@ -78,26 +84,47 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_return_added_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); + TileBounds bounds; + bounds.mMin = osg::Vec2f(182, 182); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, + [&] (const auto& v) { onAddedTile(v); }); + EXPECT_THAT(mAddedTiles, ElementsAre(TilePosition(0, 0))); + } + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + TileBounds bounds; + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, - [&] (const auto& v) { onChangedTile(v); })); - EXPECT_THAT( - mChangedTiles, - ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0), - TilePosition(1, -1), TilePosition(1, 0)) - ); + [&] (const auto& ... v) { onChangedTile(v ...); })); + EXPECT_THAT(mChangedTiles, ElementsAre( + std::pair(TilePosition(-1, -1), ChangeType::add), + std::pair(TilePosition(-1, 0), ChangeType::add), + std::pair(TilePosition(0, -1), ChangeType::update), + std::pair(TilePosition(0, 0), ChangeType::update), + std::pair(TilePosition(1, -1), ChangeType::remove), + std::pair(TilePosition(1, 0), ChangeType::remove) + )); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_return_empty) @@ -105,10 +132,10 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, - [&] (const auto& v) { onChangedTile(v); })); - EXPECT_EQ(mChangedTiles, std::vector()); + [&] (const auto& ... v) { onChangedTile(v ...); })); + EXPECT_THAT(mChangedTiles, IsEmpty()); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) @@ -117,7 +144,7 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); @@ -130,26 +157,30 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); + TileBounds bounds; + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); @@ -165,11 +196,11 @@ namespace const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); } @@ -180,7 +211,7 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); @@ -195,13 +226,13 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); @@ -214,7 +245,7 @@ namespace const auto initialRevision = manager.getRevision(); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), initialRevision + 1); } @@ -223,9 +254,9 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeAddRevision = manager.getRevision(); - EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); EXPECT_EQ(manager.getRevision(), beforeAddRevision); } @@ -235,9 +266,9 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); } @@ -247,9 +278,9 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); } @@ -258,7 +289,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeRemoveRevision = manager.getRevision(); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); @@ -298,7 +329,7 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); const osg::Vec2i cellPosition(0, 0); const int cellSize = std::numeric_limits::max(); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); @@ -343,7 +374,7 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); @@ -361,7 +392,7 @@ namespace const int cellSize = 8192; const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); for (int x = -1; x < 12; ++x) @@ -375,10 +406,28 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); manager.setWorldspace("other"); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr); } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_bounds_should_return_changed_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); + TileBounds bounds; + bounds.mMin = osg::Vec2f(182, 0); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(0, -182); + EXPECT_THAT(manager.setBounds(bounds), ElementsAre( + std::pair(TilePosition(-1, -1), ChangeType::add), + std::pair(TilePosition(0, 0), ChangeType::remove) + )); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 31778f8c26..fa8cba03af 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -8,6 +8,7 @@ #include "navmeshtilescache.hpp" #include "waitconditiontype.hpp" #include "navmeshdb.hpp" +#include "changetype.hpp" #include @@ -32,29 +33,6 @@ namespace Loading namespace DetourNavigator { - enum class ChangeType - { - remove = 0, - mixed = 1, - add = 2, - update = 3, - }; - - inline std::ostream& operator <<(std::ostream& stream, ChangeType value) - { - switch (value) { - case ChangeType::remove: - return stream << "ChangeType::remove"; - case ChangeType::mixed: - return stream << "ChangeType::mixed"; - case ChangeType::add: - return stream << "ChangeType::add"; - case ChangeType::update: - return stream << "ChangeType::update"; - } - return stream << "ChangeType::" << static_cast(value); - } - enum class JobState { Initial, diff --git a/components/detournavigator/changetype.hpp b/components/detournavigator/changetype.hpp new file mode 100644 index 0000000000..1ede6aec13 --- /dev/null +++ b/components/detournavigator/changetype.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHANGETYPE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHANGETYPE_H + +#include + +namespace DetourNavigator +{ + enum class ChangeType + { + remove = 0, + mixed = 1, + add = 2, + update = 3, + }; + + inline std::ostream& operator <<(std::ostream& stream, ChangeType value) + { + switch (value) + { + case ChangeType::remove: + return stream << "ChangeType::remove"; + case ChangeType::mixed: + return stream << "ChangeType::mixed"; + case ChangeType::add: + return stream << "ChangeType::add"; + case ChangeType::update: + return stream << "ChangeType::update"; + } + return stream << "ChangeType::" << static_cast(value); + } +} + +#endif diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp index 7a179aff24..2d42ec25bb 100644 --- a/components/detournavigator/gettilespositions.cpp +++ b/components/detournavigator/gettilespositions.cpp @@ -39,6 +39,14 @@ namespace DetourNavigator return makeTilesPositionsRange(bounds.mMin, bounds.mMax, settings); } + TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, + const TileBounds& bounds, const RecastSettings& settings) + { + if (const auto intersection = getIntersection(bounds, makeObjectTileBounds(shape, transform))) + return makeTilesPositionsRange(intersection->mMin, intersection->mMax, settings); + return {}; + } + TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings) { @@ -55,4 +63,17 @@ namespace DetourNavigator return makeTilesPositionsRange(Misc::Convert::toOsgXY(aabbMin), Misc::Convert::toOsgXY(aabbMax), settings); } + + TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept + { + const int beginX = std::max(a.mBegin.x(), b.mBegin.x()); + const int endX = std::min(a.mEnd.x(), b.mEnd.x()); + if (beginX > endX) + return {}; + const int beginY = std::max(a.mBegin.y(), b.mBegin.y()); + const int endY = std::min(a.mEnd.y(), b.mEnd.y()); + if (beginY > endY) + return {}; + return TilesPositionsRange {TilePosition(beginX, beginY), TilePosition(endX, endY)}; + } } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index db88708314..79188868dc 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H +#include "tilebounds.hpp" #include "tileposition.hpp" class btVector3; @@ -28,6 +29,9 @@ namespace DetourNavigator TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, const RecastSettings& settings); + TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, + const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings); + TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings); @@ -38,6 +42,19 @@ namespace DetourNavigator for (int tileY = range.mBegin.y(); tileY < range.mEnd.y(); ++tileY) callback(TilePosition {tileX, tileY}); } + + inline bool isInTilesPositionsRange(int begin, int end, int coordinate) + { + return begin <= coordinate && coordinate < end; + } + + inline bool isInTilesPositionsRange(const TilesPositionsRange& range, const TilePosition& position) + { + return isInTilesPositionsRange(range.mBegin.x(), range.mEnd.x(), position.x()) + && isInTilesPositionsRange(range.mBegin.y(), range.mEnd.y(), position.y()); + } + + TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept; } #endif diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 14731fcc5b..65f7c43fc1 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -83,6 +83,12 @@ namespace DetourNavigator */ virtual void setWorldspace(std::string_view worldspace) = 0; + /** + * @brief updateBounds should be called before adding object from loading cell + * @param playerPosition corresponds to the bounds center + */ + virtual void updateBounds(const osg::Vec3f& playerPosition) = 0; + /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes * @param id is used to distinguish different objects diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 4f6ec79328..42cd8bd9d1 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -38,6 +38,11 @@ namespace DetourNavigator mNavMeshManager.setWorldspace(worldspace); } + void NavigatorImpl::updateBounds(const osg::Vec3f& playerPosition) + { + mNavMeshManager.updateBounds(playerPosition); + } + bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); @@ -158,6 +163,7 @@ namespace DetourNavigator const TilePosition tilePosition = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition) return; + mNavMeshManager.updateBounds(playerPosition); update(playerPosition); mLastPlayerPosition = tilePosition; } diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 116817395c..58c4c6c05d 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -24,6 +24,8 @@ namespace DetourNavigator void setWorldspace(std::string_view worldspace) override; + void updateBounds(const osg::Vec3f& playerPosition) override; + bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 6a320c1a08..466ff5c912 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -72,6 +72,8 @@ namespace DetourNavigator void update(const osg::Vec3f& /*playerPosition*/) override {} + void updateBounds(const osg::Vec3f& /*playerPosition*/) override {} + void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {}; void setUpdatesEnabled(bool /*enabled*/) override {} diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 9fba4ad611..bbaf010807 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -42,6 +42,18 @@ namespace namespace DetourNavigator { + namespace + { + TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles) + { + const float radius = fromNavMeshCoordinates(settings, std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1) * getTileSize(settings)); + TileBounds result; + result.mMin = center - osg::Vec2f(radius, radius); + result.mMax = center + osg::Vec2f(radius, radius); + return result; + } + } + NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(settings.mRecast) @@ -59,21 +71,37 @@ namespace DetourNavigator mWorldspace = worldspace; } + void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition) + { + const TileBounds bounds = makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), + mSettings.mMaxTilesNumber); + const auto changedTiles = mRecastMeshManager.setBounds(bounds); + for (const auto& [agent, cache] : mCache) + { + auto& tiles = mChangedTiles[agent]; + for (const auto& [tilePosition, changeType] : changedTiles) + { + auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + tiles.emplace_hint(tile, tilePosition, changeType); + else + tile->second = addChangeType(tile->second, changeType); + } + } + } + bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { - const btCollisionShape& collisionShape = shape.getShape(); - if (!mRecastMeshManager.addObject(id, shape, transform, areaType)) - return false; - addChangedTiles(collisionShape, transform, ChangeType::add); - return true; + return mRecastMeshManager.addObject(id, shape, transform, areaType, + [&] (const TilePosition& tile) { addChangedTile(tile, ChangeType::add); }); } bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { return mRecastMeshManager.updateObject(id, shape, transform, areaType, - [&] (const TilePosition& tile) { addChangedTile(tile, ChangeType::update); }); + [&] (const TilePosition& tile, ChangeType changeType) { addChangedTile(tile, changeType); }); } bool NavMeshManager::removeObject(const ObjectId id) @@ -263,7 +291,8 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(makeTilesPositionsRange(shape, transform, mSettings.mRecast), + const auto bounds = mRecastMeshManager.getBounds(); + getTilesPositions(makeTilesPositionsRange(shape, transform, bounds, mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 3fd2d28d74..01937d22bd 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -26,6 +26,8 @@ namespace DetourNavigator void setWorldspace(std::string_view worldspace); + void updateBounds(const osg::Vec3f& playerPosition); + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index a4c68e3cd5..c1e611aaf8 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -37,6 +37,11 @@ namespace DetourNavigator }; } + inline float fromNavMeshCoordinates(const RecastSettings& settings, float value) + { + return value / settings.mRecastScaleFactor; + } + inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; diff --git a/components/detournavigator/tilebounds.hpp b/components/detournavigator/tilebounds.hpp index 79f6370ff2..2dc32292e8 100644 --- a/components/detournavigator/tilebounds.hpp +++ b/components/detournavigator/tilebounds.hpp @@ -21,9 +21,24 @@ namespace DetourNavigator osg::Vec2f mMax; }; + inline auto tie(const TileBounds& value) noexcept + { + return std::tie(value.mMin, value.mMax); + } + inline bool operator<(const TileBounds& lhs, const TileBounds& rhs) noexcept { - return std::tie(lhs.mMin, lhs.mMax) < std::tie(rhs.mMin, rhs.mMax); + return tie(lhs) < tie(rhs); + } + + inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs) noexcept + { + return tie(lhs) == tie(rhs); + } + + inline bool operator !=(const TileBounds& lhs, const TileBounds& rhs) noexcept + { + return !(lhs == rhs); } inline std::optional getIntersection(const TileBounds& a, const TileBounds& b) noexcept diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 314e7641e3..c7bd3bc867 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -2,19 +2,93 @@ #include "makenavmesh.hpp" #include "gettilespositions.hpp" #include "settingsutils.hpp" +#include "changetype.hpp" #include #include #include #include +#include namespace DetourNavigator { + namespace + { + const TileBounds infiniteTileBounds { + osg::Vec2f(-std::numeric_limits::max(), -std::numeric_limits::max()), + osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max()) + }; + } + TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) : mSettings(settings) + , mBounds(infiniteTileBounds) + , mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings)) {} + TileBounds TileCachedRecastMeshManager::getBounds() const + { + return mBounds; + } + + std::vector> TileCachedRecastMeshManager::setBounds(const TileBounds& bounds) + { + std::vector> changedTiles; + + if (mBounds == bounds) + return changedTiles; + + const auto newRange = makeTilesPositionsRange(bounds.mMin, bounds.mMax, mSettings); + + if (mBounds != infiniteTileBounds) + { + const std::lock_guard lock(mMutex); + for (auto& object : mObjects) + { + const ObjectId id = object.first; + ObjectData& data = object.second; + const TilesPositionsRange objectRange = makeTilesPositionsRange(data.mShape.getShape(), data.mTransform, mSettings); + + const auto onOldTilePosition = [&] (const TilePosition& position) + { + if (isInTilesPositionsRange(newRange, position)) + return; + const auto it = data.mTiles.find(position); + if (it == data.mTiles.end()) + return; + data.mTiles.erase(it); + if (removeTile(id, position, mTiles)) + changedTiles.emplace_back(position, ChangeType::remove); + }; + getTilesPositions(getIntersection(mRange, objectRange), onOldTilePosition); + + const auto onNewTilePosition = [&] (const TilePosition& position) + { + if (data.mTiles.find(position) != data.mTiles.end()) + return; + if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, mTiles)) + { + data.mTiles.insert(position); + changedTiles.emplace_back(position, ChangeType::add); + } + }; + getTilesPositions(getIntersection(newRange, objectRange), onNewTilePosition); + } + + std::sort(changedTiles.begin(), changedTiles.end()); + changedTiles.erase(std::unique(changedTiles.begin(), changedTiles.end()), changedTiles.end()); + } + + if (!changedTiles.empty()) + ++mRevision; + + mBounds = bounds; + mRange = newRange; + + return changedTiles; + } + std::string TileCachedRecastMeshManager::getWorldspace() const { const std::lock_guard lock(mMutex); @@ -30,47 +104,22 @@ namespace DetourNavigator mWorldspace = worldspace; } - bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, - const btTransform& transform, const AreaType areaType) - { - const auto it = mObjectsTilesPositions.find(id); - if (it != mObjectsTilesPositions.end()) - return false; - std::vector tilesPositions; - { - const TilesPositionsRange range = makeTilesPositionsRange(shape.getShape(), transform, mSettings); - const std::lock_guard lock(mMutex); - getTilesPositions(range, - [&] (const TilePosition& tilePosition) - { - if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) - tilesPositions.push_back(tilePosition); - }); - } - if (tilesPositions.empty()) - return false; - std::sort(tilesPositions.begin(), tilesPositions.end()); - mObjectsTilesPositions.emplace_hint(it, id, std::move(tilesPositions)); - ++mRevision; - return true; - } - std::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) { - const auto object = mObjectsTilesPositions.find(id); - if (object == mObjectsTilesPositions.end()) + const auto object = mObjects.find(id); + if (object == mObjects.end()) return std::nullopt; std::optional result; { const std::lock_guard lock(mMutex); - for (const auto& tilePosition : object->second) + for (const auto& tilePosition : object->second.mTiles) { const auto removed = removeTile(id, tilePosition, mTiles); if (removed && !result) result = removed; } } - mObjectsTilesPositions.erase(object); + mObjects.erase(object); if (result) ++mRevision; return result; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index efd70477d3..906355524e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -7,11 +7,13 @@ #include "gettilespositions.hpp" #include "version.hpp" #include "heightfieldshape.hpp" +#include "changetype.hpp" #include #include #include #include +#include namespace DetourNavigator { @@ -20,58 +22,85 @@ namespace DetourNavigator public: explicit TileCachedRecastMeshManager(const RecastSettings& settings); + TileBounds getBounds() const; + + std::vector> setBounds(const TileBounds& bounds); + std::string getWorldspace() const; void setWorldspace(std::string_view worldspace); + template bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType); + const AreaType areaType, OnChangedTile&& onChangedTile) + { + auto it = mObjects.find(id); + if (it != mObjects.end()) + return false; + const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); + const TilesPositionsRange range = getIntersection(mRange, objectRange); + std::set tilesPositions; + if (range.mBegin != range.mEnd) + { + const std::lock_guard lock(mMutex); + getTilesPositions(range, + [&] (const TilePosition& tilePosition) + { + if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) + tilesPositions.insert(tilePosition); + }); + } + it = mObjects.emplace_hint(it, id, ObjectData {shape, transform, areaType, std::move(tilesPositions)}); + std::for_each(it->second.mTiles.begin(), it->second.mTiles.end(), std::forward(onChangedTile)); + ++mRevision; + return true; + } template bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, OnChangedTile&& onChangedTile) { - const auto object = mObjectsTilesPositions.find(id); - if (object == mObjectsTilesPositions.end()) + const auto object = mObjects.find(id); + if (object == mObjects.end()) return false; - auto& currentTiles = object->second; + auto& data = object->second; bool changed = false; - std::vector newTiles; + std::set newTiles; { const auto onTilePosition = [&] (const TilePosition& tilePosition) { - if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) + if (data.mTiles.find(tilePosition) != data.mTiles.end()) { - newTiles.push_back(tilePosition); + newTiles.insert(tilePosition); if (updateTile(id, transform, areaType, tilePosition, mTiles)) { - onChangedTile(tilePosition); + onChangedTile(tilePosition, ChangeType::update); changed = true; } } else if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) { - newTiles.push_back(tilePosition); - onChangedTile(tilePosition); + newTiles.insert(tilePosition); + onChangedTile(tilePosition, ChangeType::add); changed = true; } }; - const TilesPositionsRange range = makeTilesPositionsRange(shape.getShape(), transform, mSettings); + const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); + const TilesPositionsRange range = getIntersection(mRange, objectRange); const std::lock_guard lock(mMutex); getTilesPositions(range, onTilePosition); - std::sort(newTiles.begin(), newTiles.end()); - for (const auto& tile : currentTiles) + for (const auto& tile : data.mTiles) { - if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, mTiles)) + if (newTiles.find(tile) == newTiles.end() && removeTile(id, tile, mTiles)) { - onChangedTile(tile); + onChangedTile(tile, ChangeType::remove); changed = true; } } } if (changed) { - currentTiles = std::move(newTiles); + data.mTiles = std::move(newTiles); ++mRevision; } return changed; @@ -108,11 +137,21 @@ namespace DetourNavigator private: using TilesMap = std::map>; + struct ObjectData + { + const CollisionShape mShape; + const btTransform mTransform; + const AreaType mAreaType; + std::set mTiles; + }; + const RecastSettings& mSettings; mutable std::mutex mMutex; + TileBounds mBounds; + TilesPositionsRange mRange; std::string mWorldspace; TilesMap mTiles; - std::unordered_map> mObjectsTilesPositions; + std::unordered_map mObjects; std::map> mWaterTilesPositions; std::map> mHeightfieldTilesPositions; std::size_t mRevision = 0; From 832ab103cb864eda905f4c0aa52fe90145dca864 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 2 Feb 2022 01:09:34 +0100 Subject: [PATCH 2065/2859] Filter out unchanged animated objects for navigator update --- apps/openmw/mwphysics/physicssystem.cpp | 9 +++++++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 7 ++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ef7fffa034..f5f22529ff 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -490,7 +490,7 @@ namespace MWPhysics mObjects.emplace(ptr.mRef, obj); if (obj->isAnimated()) - mAnimatedObjects.insert(obj.get()); + mAnimatedObjects.emplace(obj.get(), false); } void PhysicsSystem::remove(const MWWorld::Ptr &ptr) @@ -739,13 +739,18 @@ namespace MWPhysics void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { - for (Object* animatedObject : mAnimatedObjects) + for (auto& [animatedObject, changed] : mAnimatedObjects) { if (animatedObject->animateCollisionShapes()) { auto obj = mObjects.find(animatedObject->getPtr().mRef); assert(obj != mObjects.end()); mTaskScheduler->updateSingleAabb(obj->second); + changed = true; + } + else + { + changed = false; } } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 1606ac084c..b165f10761 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -300,7 +300,7 @@ namespace MWPhysics using ObjectMap = std::unordered_map>; ObjectMap mObjects; - std::set mAnimatedObjects; // stores pointers to elements in mObjects + std::map mAnimatedObjects; // stores pointers to elements in mObjects ActorMap mActors; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 706ce4859f..61abb7fcc2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1525,7 +1525,12 @@ namespace MWWorld void World::updateNavigator() { - mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { updateNavigatorObject(*object); }); + mPhysics->forEachAnimatedObject([&] (const auto& pair) + { + const auto [object, changed] = pair; + if (changed) + updateNavigatorObject(*object); + }); for (const auto& door : mDoorStates) if (const auto object = mPhysics->getObject(door.first)) From 3caeda7299508b8a80da254658bb3f73b11c66fd Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 2 Feb 2022 01:11:13 +0100 Subject: [PATCH 2066/2859] Consider animated object unchanged if no transform updates done --- apps/openmw/mwphysics/object.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 08fcc7e47d..76661b86a0 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -112,6 +112,7 @@ namespace MWPhysics assert (mShapeInstance->mCollisionShape->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->mCollisionShape.get()); + bool result = false; for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes) { auto nodePathFound = mRecIndexToNodePath.find(recIndex); @@ -145,8 +146,11 @@ namespace MWPhysics // Note: we can not apply scaling here for now since we treat scaled shapes // as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now if (!(transform == compound->getChildTransform(shapeIndex))) + { compound->updateChildTransform(shapeIndex, transform); + result = true; + } } - return true; + return result; } } From 0b644a897e123370ea489cf43664a079fe532733 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 3 Feb 2022 22:24:26 +0100 Subject: [PATCH 2067/2859] Explicitly bind TileCachedRecastMeshManager with mutex --- .../tilecachedrecastmeshmanager.cpp | 71 +++++++++---------- .../tilecachedrecastmeshmanager.hpp | 32 +++++---- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index c7bd3bc867..e7e46e96df 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -43,7 +43,7 @@ namespace DetourNavigator if (mBounds != infiniteTileBounds) { - const std::lock_guard lock(mMutex); + const auto locked = mWorldspaceTiles.lock(); for (auto& object : mObjects) { const ObjectId id = object.first; @@ -58,7 +58,7 @@ namespace DetourNavigator if (it == data.mTiles.end()) return; data.mTiles.erase(it); - if (removeTile(id, position, mTiles)) + if (removeTile(id, position, locked->mTiles)) changedTiles.emplace_back(position, ChangeType::remove); }; getTilesPositions(getIntersection(mRange, objectRange), onOldTilePosition); @@ -67,7 +67,7 @@ namespace DetourNavigator { if (data.mTiles.find(position) != data.mTiles.end()) return; - if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, mTiles)) + if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, locked->mTiles)) { data.mTiles.insert(position); changedTiles.emplace_back(position, ChangeType::add); @@ -91,17 +91,16 @@ namespace DetourNavigator std::string TileCachedRecastMeshManager::getWorldspace() const { - const std::lock_guard lock(mMutex); - return mWorldspace; + return mWorldspaceTiles.lockConst()->mWorldspace; } void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace) { - const std::lock_guard lock(mMutex); - if (mWorldspace == worldspace) + const auto locked = mWorldspaceTiles.lock(); + if (locked->mWorldspace == worldspace) return; - mTiles.clear(); - mWorldspace = worldspace; + locked->mTiles.clear(); + locked->mWorldspace = worldspace; } std::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) @@ -111,10 +110,10 @@ namespace DetourNavigator return std::nullopt; std::optional result; { - const std::lock_guard lock(mMutex); + const auto locked = mWorldspaceTiles.lock(); for (const auto& tilePosition : object->second.mTiles) { - const auto removed = removeTile(id, tilePosition, mTiles); + const auto removed = removeTile(id, tilePosition, locked->mTiles); if (removed && !result) result = removed; } @@ -138,8 +137,8 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) { - const std::lock_guard lock(mMutex); - for (auto& tile : mTiles) + const auto locked = mWorldspaceTiles.lock(); + for (auto& tile : locked->mTiles) { if (tile.second->addWater(cellPosition, cellSize, level)) { @@ -154,12 +153,12 @@ namespace DetourNavigator getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), [&] (const TilePosition& tilePosition) { - const std::lock_guard lock(mMutex); - auto tile = mTiles.find(tilePosition); - if (tile == mTiles.end()) + const auto locked = mWorldspaceTiles.lock(); + auto tile = locked->mTiles.find(tilePosition); + if (tile == locked->mTiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = mTiles.emplace_hint(tile, tilePosition, + tile = locked->mTiles.emplace_hint(tile, tilePosition, std::make_shared(tileBounds, mTilesGeneration)); } if (tile->second->addWater(cellPosition, cellSize, level)) @@ -184,14 +183,14 @@ namespace DetourNavigator std::optional result; for (const auto& tilePosition : object->second) { - const std::lock_guard lock(mMutex); - const auto tile = mTiles.find(tilePosition); - if (tile == mTiles.end()) + const auto locked = mWorldspaceTiles.lock(); + const auto tile = locked->mTiles.find(tilePosition); + if (tile == locked->mTiles.end()) continue; const auto tileResult = tile->second->removeWater(cellPosition); if (tile->second->isEmpty()) { - mTiles.erase(tile); + locked->mTiles.erase(tile); ++mTilesGeneration; } if (tileResult && !result) @@ -219,12 +218,12 @@ namespace DetourNavigator getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), [&] (const TilePosition& tilePosition) { - const std::lock_guard lock(mMutex); - auto tile = mTiles.find(tilePosition); - if (tile == mTiles.end()) + const auto locked = mWorldspaceTiles.lock(); + auto tile = locked->mTiles.find(tilePosition); + if (tile == locked->mTiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = mTiles.emplace_hint(tile, tilePosition, + tile = locked->mTiles.emplace_hint(tile, tilePosition, std::make_shared(tileBounds, mTilesGeneration)); } if (tile->second->addHeightfield(cellPosition, cellSize, shape)) @@ -248,14 +247,14 @@ namespace DetourNavigator std::optional result; for (const auto& tilePosition : object->second) { - const std::lock_guard lock(mMutex); - const auto tile = mTiles.find(tilePosition); - if (tile == mTiles.end()) + const auto locked = mWorldspaceTiles.lock(); + const auto tile = locked->mTiles.find(tilePosition); + if (tile == locked->mTiles.end()) continue; const auto tileResult = tile->second->removeHeightfield(cellPosition); if (tile->second->isEmpty()) { - mTiles.erase(tile); + locked->mTiles.erase(tile); ++mTilesGeneration; } if (tileResult && !result) @@ -295,9 +294,9 @@ namespace DetourNavigator void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const { - const std::lock_guard lock(mMutex); - const auto it = mTiles.find(tilePosition); - if (it == mTiles.end()) + const auto locked = mWorldspaceTiles.lockConst(); + const auto it = locked->mTiles.find(tilePosition); + if (it == locked->mTiles.end()) return; it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); } @@ -341,11 +340,11 @@ namespace DetourNavigator std::shared_ptr TileCachedRecastMeshManager::getManager(std::string_view worldspace, const TilePosition& tilePosition) const { - const std::lock_guard lock(mMutex); - if (mWorldspace != worldspace) + const auto locked = mWorldspaceTiles.lockConst(); + if (locked->mWorldspace != worldspace) return nullptr; - const auto it = mTiles.find(tilePosition); - if (it == mTiles.end()) + const auto it = locked->mTiles.find(tilePosition); + if (it == locked->mTiles.end()) return nullptr; return it->second; } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 906355524e..5e168efc16 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -9,6 +9,8 @@ #include "heightfieldshape.hpp" #include "changetype.hpp" +#include + #include #include #include @@ -42,11 +44,11 @@ namespace DetourNavigator std::set tilesPositions; if (range.mBegin != range.mEnd) { - const std::lock_guard lock(mMutex); + const auto locked = mWorldspaceTiles.lock(); getTilesPositions(range, [&] (const TilePosition& tilePosition) { - if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) + if (addTile(id, shape, transform, areaType, tilePosition, locked->mTiles)) tilesPositions.insert(tilePosition); }); } @@ -67,31 +69,31 @@ namespace DetourNavigator bool changed = false; std::set newTiles; { + const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); + const TilesPositionsRange range = getIntersection(mRange, objectRange); + const auto locked = mWorldspaceTiles.lock(); const auto onTilePosition = [&] (const TilePosition& tilePosition) { if (data.mTiles.find(tilePosition) != data.mTiles.end()) { newTiles.insert(tilePosition); - if (updateTile(id, transform, areaType, tilePosition, mTiles)) + if (updateTile(id, transform, areaType, tilePosition, locked->mTiles)) { onChangedTile(tilePosition, ChangeType::update); changed = true; } } - else if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) + else if (addTile(id, shape, transform, areaType, tilePosition, locked->mTiles)) { newTiles.insert(tilePosition); onChangedTile(tilePosition, ChangeType::add); changed = true; } }; - const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); - const TilesPositionsRange range = getIntersection(mRange, objectRange); - const std::lock_guard lock(mMutex); getTilesPositions(range, onTilePosition); for (const auto& tile : data.mTiles) { - if (newTiles.find(tile) == newTiles.end() && removeTile(id, tile, mTiles)) + if (newTiles.find(tile) == newTiles.end() && removeTile(id, tile, locked->mTiles)) { onChangedTile(tile, ChangeType::remove); changed = true; @@ -125,8 +127,8 @@ namespace DetourNavigator template void forEachTile(Function&& function) const { - const std::lock_guard lock(mMutex); - for (auto& [tilePosition, recastMeshManager] : mTiles) + const auto& locked = mWorldspaceTiles.lockConst(); + for (const auto& [tilePosition, recastMeshManager] : locked->mTiles) function(tilePosition, *recastMeshManager); } @@ -145,12 +147,16 @@ namespace DetourNavigator std::set mTiles; }; + struct WorldspaceTiles + { + std::string mWorldspace; + TilesMap mTiles; + }; + const RecastSettings& mSettings; - mutable std::mutex mMutex; TileBounds mBounds; TilesPositionsRange mRange; - std::string mWorldspace; - TilesMap mTiles; + Misc::ScopeGuarded mWorldspaceTiles; std::unordered_map mObjects; std::map> mWaterTilesPositions; std::map> mHeightfieldTilesPositions; From e7f3524924062eb12c4884db1517a681ee93da56 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 21 Nov 2021 15:28:40 +0100 Subject: [PATCH 2068/2859] Add a tool to load and print information about all bullet objects in all cells --- .gitlab-ci.yml | 2 +- CI/before_script.android.sh | 1 + CMakeLists.txt | 12 ++ apps/bulletobjecttool/CMakeLists.txt | 20 ++ apps/bulletobjecttool/main.cpp | 203 ++++++++++++++++++++ components/CMakeLists.txt | 2 +- components/resource/foreachbulletobject.cpp | 161 ++++++++++++++++ components/resource/foreachbulletobject.hpp | 48 +++++ 8 files changed, 447 insertions(+), 2 deletions(-) create mode 100644 apps/bulletobjecttool/CMakeLists.txt create mode 100644 apps/bulletobjecttool/main.cpp create mode 100644 components/resource/foreachbulletobject.cpp create mode 100644 components/resource/foreachbulletobject.hpp diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aaf7f233f0..6bd2226ef7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -287,7 +287,7 @@ macOS12_Xcode13: CCACHE_SIZE: 3G variables: &engine-targets - targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard,openmw-navmeshtool" + targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard,openmw-navmeshtool,openmw-bulletobjecttool" package: "Engine" variables: &cs-targets diff --git a/CI/before_script.android.sh b/CI/before_script.android.sh index 43422f68c1..80a3f11e57 100755 --- a/CI/before_script.android.sh +++ b/CI/before_script.android.sh @@ -23,6 +23,7 @@ cmake \ -DBUILD_OPENCS=0 \ -DBUILD_WIZARD=0 \ -DBUILD_NAVMESHTOOL=OFF \ +-DBUILD_BULLETOBJECTTOOL=OFF \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_SQLITE3=OFF \ .. diff --git a/CMakeLists.txt b/CMakeLists.txt index 20c00fb757..bc4fa56e25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) +option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. @@ -619,6 +620,10 @@ if (BUILD_NAVMESHTOOL) add_subdirectory(apps/navmeshtool) endif() +if (BUILD_BULLETOBJECTTOOL) + add_subdirectory( apps/bulletobjecttool ) +endif() + if (WIN32) if (MSVC) foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) @@ -719,6 +724,10 @@ if (WIN32) if (BUILD_NAVMESHTOOL) set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") endif() + + if (BUILD_BULLETOBJECTTOOL) + set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() endif(MSVC) # TODO: At some point release builds should not use the console but rather write to a log file @@ -964,6 +973,9 @@ elseif(NOT APPLE) if(BUILD_NAVMESHTOOL) install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" ) endif() + IF(BUILD_BULLETOBJECTTOOL) + INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-bulletobjecttool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_BULLETOBJECTTOOL) # Install licenses INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" ) diff --git a/apps/bulletobjecttool/CMakeLists.txt b/apps/bulletobjecttool/CMakeLists.txt new file mode 100644 index 0000000000..6beb411e20 --- /dev/null +++ b/apps/bulletobjecttool/CMakeLists.txt @@ -0,0 +1,20 @@ +set(BULLETMESHTOOL + main.cpp +) +source_group(apps\\bulletobjecttool FILES ${BULLETMESHTOOL}) + +openmw_add_executable(openmw-bulletobjecttool ${BULLETMESHTOOL}) + +target_link_libraries(openmw-bulletobjecttool + ${Boost_PROGRAM_OPTIONS_LIBRARY} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions(--coverage) + target_link_libraries(openmw-bulletobjecttool gcov) +endif() + +if (WIN32) + install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".") +endif() diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp new file mode 100644 index 0000000000..c80883fe78 --- /dev/null +++ b/apps/bulletobjecttool/main.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace +{ + namespace bpo = boost::program_options; + + using StringsVector = std::vector; + + bpo::options_description makeOptionsDescription() + { + using Fallback::FallbackMap; + + bpo::options_description result; + + result.add_options() + ("help", "print help message") + + ("version", "print version information and quit") + + ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") + ->multitoken()->composing(), "set data directories (later directories have higher priority)") + + ("data-local", bpo::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""), + "set local data directory (highest priority)") + + ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") + ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") + + ("resources", bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), + "set resources directory") + + ("content", bpo::value()->default_value(StringsVector(), "") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts") + + ("fs-strict", bpo::value()->implicit_value(true) + ->default_value(false), "strict file system handling (no case folding)") + + ("encoding", bpo::value()-> + default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default") + + ("fallback", bpo::value()->default_value(FallbackMap(), "") + ->multitoken()->composing(), "fallback values") + ; + + Files::ConfigurationManager::addCommonOptions(result); + + return result; + } + + struct WriteArray + { + const float (&mValue)[3]; + + friend std::ostream& operator <<(std::ostream& stream, const WriteArray& value) + { + for (std::size_t i = 0; i < 2; ++i) + stream << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue[i] << ", "; + return stream << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue[2]; + } + }; + + std::string toHex(std::string_view value) + { + std::string buffer(value.size() * 2, '0'); + char* out = buffer.data(); + for (const char v : value) + { + const std::ptrdiff_t space = static_cast(static_cast(v) <= 0xf); + const auto [ptr, ec] = std::to_chars(out + space, out + space + 2, static_cast(v), 16); + if (ec != std::errc()) + throw std::system_error(std::make_error_code(ec)); + out += 2; + } + return buffer; + } + + int runBulletObjectTool(int argc, char *argv[]) + { + bpo::options_description desc = makeOptionsDescription(); + + bpo::parsed_options options = bpo::command_line_parser(argc, argv) + .options(desc).allow_unregistered().run(); + bpo::variables_map variables; + + bpo::store(options, variables); + bpo::notify(variables); + + if (variables.find("help") != variables.end()) + { + getRawStdout() << desc << std::endl; + return 0; + } + + Files::ConfigurationManager config; + + bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc); + config.readConfiguration(variables, desc); + Files::mergeComposingVariables(variables, composingVariables, desc); + + const std::string encoding(variables["encoding"].as()); + Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding)); + + Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); + + auto local = variables["data-local"].as(); + if (!local.empty()) + dataDirs.push_back(std::move(local)); + + config.processPaths(dataDirs); + + const auto fsStrict = variables["fs-strict"].as(); + const auto resDir = variables["resources"].as(); + Version::Version v = Version::getOpenmwVersion(resDir.string()); + Log(Debug::Info) << v.describe(); + dataDirs.insert(dataDirs.begin(), resDir / "vfs"); + const auto fileCollections = Files::Collections(dataDirs, !fsStrict); + const auto archives = variables["fallback-archive"].as(); + const auto contentFiles = variables["content"].as(); + + Fallback::Map::init(variables["fallback"].as().mMap); + + VFS::Manager vfs(fsStrict); + + VFS::registerArchives(&vfs, fileCollections, archives, true); + + Settings::Manager settings; + settings.load(config); + + std::vector readers(contentFiles.size()); + EsmLoader::Query query; + query.mLoadActivators = true; + query.mLoadCells = true; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = true; + query.mLoadLands = true; + query.mLoadStatics = true; + const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); + + Resource::ImageManager imageManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); + Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); + + Resource::forEachBulletObject(readers, vfs, bulletShapeManager, esmData, + [] (const ESM::Cell& cell, const Resource::BulletObject& object) + { + Log(Debug::Verbose) << "Found bullet object in " << (cell.isExterior() ? "exterior" : "interior") + << " cell \"" << cell.getDescription() << "\":" + << " fileName=\"" << object.mShape->mFileName << '"' + << " fileHash=" << toHex(object.mShape->mFileHash) + << " collisionShape=" << std::boolalpha << (object.mShape->mCollisionShape == nullptr) + << " avoidCollisionShape=" << std::boolalpha << (object.mShape->mAvoidCollisionShape == nullptr) + << " position=(" << WriteArray {object.mPosition.pos} << ')' + << " rotation=(" << WriteArray {object.mPosition.rot} << ')' + << " scale=" << std::setprecision(std::numeric_limits::max_exponent10) << object.mScale; + }); + + Log(Debug::Info) << "Done"; + + return 0; + } +} + +int main(int argc, char *argv[]) +{ + return wrapApplication(runBulletObjectTool, argc, argv, "BulletObjectTool"); +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 9a1ffc847c..790cd6b646 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -46,7 +46,7 @@ add_component_dir (vfs add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem - resourcemanager stats animation + resourcemanager stats animation foreachbulletobject ) add_component_dir (shader diff --git a/components/resource/foreachbulletobject.cpp b/components/resource/foreachbulletobject.cpp new file mode 100644 index 0000000000..aaaa6fa5f9 --- /dev/null +++ b/components/resource/foreachbulletobject.cpp @@ -0,0 +1,161 @@ +#include "foreachbulletobject.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Resource +{ + namespace + { + struct CellRef + { + ESM::RecNameInts mType; + ESM::RefNum mRefNum; + std::string mRefId; + float mScale; + ESM::Position mPos; + + CellRef(ESM::RecNameInts type, ESM::RefNum refNum, std::string&& refId, float scale, const ESM::Position& pos) + : mType(type), mRefNum(refNum), mRefId(std::move(refId)), mScale(scale), mPos(pos) {} + }; + + ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, std::string_view refId) + { + const auto it = std::lower_bound(esmData.mRefIdTypes.begin(), esmData.mRefIdTypes.end(), + refId, EsmLoader::LessById {}); + if (it == esmData.mRefIdTypes.end() || it->mId != refId) + return {}; + return it->mType; + } + + std::vector loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, + std::vector& readers) + { + std::vector> cellRefs; + + for (std::size_t i = 0; i < cell.mContextList.size(); i++) + { + ESM::ESMReader& reader = readers[static_cast(cell.mContextList[i].index)]; + cell.restore(reader, static_cast(i)); + ESM::CellRef cellRef; + bool deleted = false; + while (ESM::Cell::getNextRef(reader, cellRef, deleted)) + { + Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID); + const ESM::RecNameInts type = getType(esmData, cellRef.mRefID); + if (type == ESM::RecNameInts {}) + continue; + cellRefs.emplace_back(deleted, type, cellRef.mRefNum, std::move(cellRef.mRefID), + cellRef.mScale, cellRef.mPos); + } + } + + Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs"; + + const auto getKey = [] (const EsmLoader::Record& v) -> const ESM::RefNum& { return v.mValue.mRefNum; }; + std::vector result = prepareRecords(cellRefs, getKey); + + Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs"; + + return result; + } + + template + void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, + Resource::BulletShapeManager& bulletShapeManager, std::vector& readers, + F&& f) + { + std::vector cellRefs = loadCellRefs(cell, esmData, readers); + + Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs"; + + for (CellRef& cellRef : cellRefs) + { + std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType)); + if (model.empty()) + continue; + + if (cellRef.mType != ESM::REC_STAT) + model = Misc::ResourceHelpers::correctActorModelPath(model, &vfs); + + osg::ref_ptr shape = [&] + { + try + { + return bulletShapeManager.getShape("meshes/" + model); + } + catch (const std::exception& e) + { + Log(Debug::Warning) << "Failed to load cell ref \"" << cellRef.mRefId << "\" model \"" << model << "\": " << e.what(); + return osg::ref_ptr(); + } + } (); + + if (shape == nullptr) + continue; + + switch (cellRef.mType) + { + case ESM::REC_ACTI: + case ESM::REC_CONT: + case ESM::REC_DOOR: + case ESM::REC_STAT: + f(BulletObject {std::move(shape), cellRef.mPos, cellRef.mScale}); + break; + default: + break; + } + } + } + } + + void forEachBulletObject(std::vector& readers, const VFS::Manager& vfs, + Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, + std::function callback) + { + Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells..."; + + for (std::size_t i = 0; i < esmData.mCells.size(); ++i) + { + const ESM::Cell& cell = esmData.mCells[i]; + const bool exterior = cell.isExterior(); + + Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior") + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; + + std::size_t objects = 0; + + forEachObject(cell, esmData, vfs, bulletShapeManager, readers, + [&] (const BulletObject& object) + { + callback(cell, object); + ++objects; + }); + + Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior") + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") " << cell.getDescription() + << " with " << objects << " objects"; + } + } +} diff --git a/components/resource/foreachbulletobject.hpp b/components/resource/foreachbulletobject.hpp new file mode 100644 index 0000000000..5b1495e4cc --- /dev/null +++ b/components/resource/foreachbulletobject.hpp @@ -0,0 +1,48 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H +#define OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H + +#include +#include +#include + +#include + +#include +#include + +namespace ESM +{ + class ESMReader; + struct Cell; +} + +namespace VFS +{ + class Manager; +} + +namespace Resource +{ + class BulletShapeManager; +} + +namespace EsmLoader +{ + struct EsmData; +} + +namespace Resource +{ + struct BulletObject + { + osg::ref_ptr mShape; + ESM::Position mPosition; + float mScale; + }; + + void forEachBulletObject(std::vector& readers, const VFS::Manager& vfs, + Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, + std::function callback); +} + +#endif From a383d9dfdf9d2b89d95e264131ae441f4ac0520e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:21:39 +0200 Subject: [PATCH 2069/2859] Make ownership explicit in Environment --- apps/openmw/engine.cpp | 40 +++++------ apps/openmw/mwbase/environment.cpp | 106 ++++++++++++----------------- apps/openmw/mwbase/environment.hpp | 50 +++++++------- 3 files changed, 88 insertions(+), 108 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9e2e1aedad..2a6b832c14 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -694,18 +694,18 @@ void OMW::Engine::setWindowIcon() void OMW::Engine::prepareEngine (Settings::Manager & settings) { mEnvironment.setStateManager ( - new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles)); + std::make_unique (mCfgMgr.getUserDataPath() / "saves", mContentFiles)); createWindow(settings); osg::ref_ptr rootNode (new osg::Group); mViewer->setSceneData(rootNode); - mVFS.reset(new VFS::Manager(mFSStrict)); + mVFS = std::make_unique(mFSStrict); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); - mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); + mResourceSystem = std::make_unique(mVFS.get()); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), @@ -734,8 +734,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mViewer->addEventHandler(mScreenCaptureHandler); - mLuaManager = new MWLua::LuaManager(mVFS.get(), (mResDir / "lua_libs").string()); - mEnvironment.setLuaManager(mLuaManager); + auto luaMgr = std::make_unique(mVFS.get(), (mResDir / "lua_libs").string()); + mLuaManager = luaMgr.get(); + mEnvironment.setLuaManager(std::move(luaMgr)); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so @@ -806,33 +807,35 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) guiRoot->setName("GUI Root"); guiRoot->setNodeMask(MWRender::Mask_GUI); rootNode->addChild(guiRoot); - MWGui::WindowManager* window = new MWGui::WindowManager(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), + + auto windowMgr = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string(), shadersSupported); - mEnvironment.setWindowManager (window); + auto* windowMgrInternal = windowMgr.get(); + mEnvironment.setWindowManager (std::move(windowMgr)); - MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); - mEnvironment.setInputManager (input); + auto inputMgr = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); + mEnvironment.setInputManager (std::move(inputMgr)); // Create sound system - mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound)); + mEnvironment.setSoundManager (std::make_unique(mVFS.get(), mUseSound)); if (!mSkipMenu) { const std::string& logo = Fallback::Map::getString("Movies_Company_Logo"); if (!logo.empty()) - window->playVideo(logo, true); + mEnvironment.getWindowManager()->playVideo(logo, true); } // Create the world - mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mEnvironment.setWorld(std::make_unique(mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); - window->setStore(mEnvironment.getWorld()->getStore()); - window->initUI(); + windowMgrInternal->setStore(mEnvironment.getWorld()->getStore()); + windowMgrInternal->initUI(); //Load translation data mTranslationDataStorage.setEncoder(mEncoder); @@ -845,16 +848,15 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions (&mExtensions); - mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode, + mEnvironment.setScriptManager (std::make_unique(mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode, mScriptBlacklistUse ? mScriptBlacklist : std::vector())); // Create game mechanics system - MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager; - mEnvironment.setMechanicsManager (mechanics); + mEnvironment.setMechanicsManager (std::make_unique()); // Create dialog system - mEnvironment.setJournal (new MWDialogue::Journal); - mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage)); + mEnvironment.setJournal (std::make_unique()); + mEnvironment.setDialogueManager (std::make_unique(mExtensions, mTranslationDataStorage)); mEnvironment.setResourceSystem(mResourceSystem.get()); // scripts diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 71940f67d2..1b212c73ab 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -18,68 +18,64 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() -: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr), - mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), - mStateManager (nullptr), mLuaManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f) { - assert (!sThis); + assert(!sThis); sThis = this; } MWBase::Environment::~Environment() { - cleanup(); sThis = nullptr; } -void MWBase::Environment::setWorld (World *world) +void MWBase::Environment::setWorld (std::unique_ptr&& world) { - mWorld = world; + mWorld = std::move(world); } -void MWBase::Environment::setSoundManager (SoundManager *soundManager) +void MWBase::Environment::setSoundManager (std::unique_ptr&& soundManager) { - mSoundManager = soundManager; + mSoundManager = std::move(soundManager); } -void MWBase::Environment::setScriptManager (ScriptManager *scriptManager) +void MWBase::Environment::setScriptManager (std::unique_ptr&& scriptManager) { - mScriptManager = scriptManager; + mScriptManager = std::move(scriptManager); } -void MWBase::Environment::setWindowManager (WindowManager *windowManager) +void MWBase::Environment::setWindowManager (std::unique_ptr&& windowManager) { - mWindowManager = windowManager; + mWindowManager = std::move(windowManager); } -void MWBase::Environment::setMechanicsManager (MechanicsManager *mechanicsManager) +void MWBase::Environment::setMechanicsManager (std::unique_ptr&& mechanicsManager) { - mMechanicsManager = mechanicsManager; + mMechanicsManager = std::move(mechanicsManager); } -void MWBase::Environment::setDialogueManager (DialogueManager *dialogueManager) +void MWBase::Environment::setDialogueManager (std::unique_ptr&& dialogueManager) { - mDialogueManager = dialogueManager; + mDialogueManager = std::move(dialogueManager); } -void MWBase::Environment::setJournal (Journal *journal) +void MWBase::Environment::setJournal (std::unique_ptr&& journal) { - mJournal = journal; + mJournal = std::move(journal); } -void MWBase::Environment::setInputManager (InputManager *inputManager) +void MWBase::Environment::setInputManager (std::unique_ptr&& inputManager) { - mInputManager = inputManager; + mInputManager = std::move(inputManager); } -void MWBase::Environment::setStateManager (StateManager *stateManager) +void MWBase::Environment::setStateManager (std::unique_ptr&& stateManager) { - mStateManager = stateManager; + mStateManager = std::move(stateManager); } -void MWBase::Environment::setLuaManager (LuaManager *luaManager) +void MWBase::Environment::setLuaManager (std::unique_ptr&& luaManager) { - mLuaManager = luaManager; + mLuaManager = std::move(luaManager); } void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem) @@ -105,61 +101,61 @@ float MWBase::Environment::getFrameRateLimit() const MWBase::World *MWBase::Environment::getWorld() const { assert (mWorld); - return mWorld; + return mWorld.get(); } MWBase::SoundManager *MWBase::Environment::getSoundManager() const { assert (mSoundManager); - return mSoundManager; + return mSoundManager.get(); } MWBase::ScriptManager *MWBase::Environment::getScriptManager() const { assert (mScriptManager); - return mScriptManager; + return mScriptManager.get(); } MWBase::WindowManager *MWBase::Environment::getWindowManager() const { assert (mWindowManager); - return mWindowManager; + return mWindowManager.get(); } MWBase::MechanicsManager *MWBase::Environment::getMechanicsManager() const { assert (mMechanicsManager); - return mMechanicsManager; + return mMechanicsManager.get(); } MWBase::DialogueManager *MWBase::Environment::getDialogueManager() const { assert (mDialogueManager); - return mDialogueManager; + return mDialogueManager.get(); } MWBase::Journal *MWBase::Environment::getJournal() const { assert (mJournal); - return mJournal; + return mJournal.get(); } MWBase::InputManager *MWBase::Environment::getInputManager() const { assert (mInputManager); - return mInputManager; + return mInputManager.get(); } MWBase::StateManager *MWBase::Environment::getStateManager() const { assert (mStateManager); - return mStateManager; + return mStateManager.get(); } MWBase::LuaManager *MWBase::Environment::getLuaManager() const { assert (mLuaManager); - return mLuaManager; + return mLuaManager.get(); } Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const @@ -174,35 +170,17 @@ float MWBase::Environment::getFrameDuration() const void MWBase::Environment::cleanup() { - delete mMechanicsManager; - mMechanicsManager = nullptr; - - delete mDialogueManager; - mDialogueManager = nullptr; - - delete mJournal; - mJournal = nullptr; - - delete mScriptManager; - mScriptManager = nullptr; - - delete mWindowManager; - mWindowManager = nullptr; - - delete mWorld; - mWorld = nullptr; - - delete mSoundManager; - mSoundManager = nullptr; - - delete mInputManager; - mInputManager = nullptr; - - delete mStateManager; - mStateManager = nullptr; - - delete mLuaManager; - mLuaManager = nullptr; + mMechanicsManager.reset(); + mDialogueManager.reset(); + mJournal.reset(); + mScriptManager.reset(); + mWindowManager.reset(); + mWorld.reset(); + mSoundManager.reset(); + mInputManager.reset(); + mStateManager.reset(); + mLuaManager.reset(); + mResourceSystem = nullptr; } const MWBase::Environment& MWBase::Environment::get() diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index b2600e7dd5..00af5d2869 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -1,6 +1,8 @@ #ifndef GAME_BASE_ENVIRONMENT_H #define GAME_BASE_ENVIRONMENT_H +#include + namespace osg { class Stats; @@ -28,25 +30,23 @@ namespace MWBase /// /// This class allows each mw-subsystem to access any others subsystem's top-level manager class. /// - /// \attention Environment takes ownership of the manager class instances it is handed over in - /// the set* functions. class Environment { static Environment *sThis; - World *mWorld; - SoundManager *mSoundManager; - ScriptManager *mScriptManager; - WindowManager *mWindowManager; - MechanicsManager *mMechanicsManager; - DialogueManager *mDialogueManager; - Journal *mJournal; - InputManager *mInputManager; - StateManager *mStateManager; - LuaManager *mLuaManager; - Resource::ResourceSystem *mResourceSystem; - float mFrameDuration; - float mFrameRateLimit; + std::unique_ptr mWorld; + std::unique_ptr mSoundManager; + std::unique_ptr mScriptManager; + std::unique_ptr mWindowManager; + std::unique_ptr mMechanicsManager; + std::unique_ptr mDialogueManager; + std::unique_ptr mJournal; + std::unique_ptr mInputManager; + std::unique_ptr mStateManager; + std::unique_ptr mLuaManager; + Resource::ResourceSystem* mResourceSystem{}; + float mFrameDuration{}; + float mFrameRateLimit{}; Environment (const Environment&); ///< not implemented @@ -60,25 +60,25 @@ namespace MWBase ~Environment(); - void setWorld (World *world); + void setWorld (std::unique_ptr&& world); - void setSoundManager (SoundManager *soundManager); + void setSoundManager (std::unique_ptr&& soundManager); - void setScriptManager (MWBase::ScriptManager *scriptManager); + void setScriptManager (std::unique_ptr&& scriptManager); - void setWindowManager (WindowManager *windowManager); + void setWindowManager (std::unique_ptr&& windowManager); - void setMechanicsManager (MechanicsManager *mechanicsManager); + void setMechanicsManager (std::unique_ptr&& mechanicsManager); - void setDialogueManager (DialogueManager *dialogueManager); + void setDialogueManager (std::unique_ptr&& dialogueManager); - void setJournal (Journal *journal); + void setJournal (std::unique_ptr&& journal); - void setInputManager (InputManager *inputManager); + void setInputManager (std::unique_ptr&& inputManager); - void setStateManager (StateManager *stateManager); + void setStateManager (std::unique_ptr&& stateManager); - void setLuaManager (LuaManager *luaManager); + void setLuaManager (std::unique_ptr&& luaManager); void setResourceSystem (Resource::ResourceSystem *resourceSystem); From 5ebcd37da109629041502cf630e76946b0b4c153 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 5 Feb 2022 19:07:44 +0100 Subject: [PATCH 2070/2859] Rename method and restore swish sounds --- apps/openmw/mwmechanics/character.cpp | 16 ++++++++++------ apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 820bfad07b..815f3b30cb 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1125,7 +1125,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const return mAnimation->updateCarriedLeftVisible(weaptype); } -bool CharacterController::updateWeaponState(CharacterState& idle) +bool CharacterController::updateState(CharacterState& idle) { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); @@ -1363,20 +1363,24 @@ bool CharacterController::updateWeaponState(CharacterState& idle) ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; if(getAttackingOrSpell()) { - MWWorld::Ptr player = getPlayer(); - bool resetIdle = ammunition; if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackStrength = 0; - // Randomize attacks for non-bipedal creatures with Weapon flag + // Randomize attacks for non-bipedal creatures if (mPtr.getClass().getType() == ESM::Creature::sRecordId && !mPtr.getClass().isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { mCurrentWeapon = chooseRandomAttackAnimation(); + if (!mPtr.getClass().hasInventoryStore(mPtr)) + { + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); + if (mWeaponType == ESM::Weapon::HandToHand) + playSwishSound(0.0f); + } } if(mWeaponType == ESM::Weapon::Spell) @@ -1384,7 +1388,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation setAttackingOrSpell(false); - if (mPtr == player) + if (mPtr == getPlayer()) { // For the player, set the spell we want to cast // This has to be done at the start of the casting animation, @@ -2253,7 +2257,7 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - forcestateupdate = updateWeaponState(idlestate) || forcestateupdate; + forcestateupdate = updateState(idlestate) || forcestateupdate; refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); updateIdleStormState(inwater); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 8c410bba25..1a0fd42fba 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -205,7 +205,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void clearAnimQueue(bool clearPersistAnims = false); - bool updateWeaponState(CharacterState& idle); + bool updateState(CharacterState& idle); void updateIdleStormState(bool inwater); std::string chooseRandomAttackAnimation() const; From 020e0b2ea5c5da26322119df8196c2a1ca5ab1c9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 5 Feb 2022 22:50:04 +0100 Subject: [PATCH 2071/2859] Don't allow non-bipedal actors to play hand-to-hand animations --- apps/openmw/mwmechanics/character.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 815f3b30cb..3ea09569ba 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -435,6 +435,8 @@ std::string CharacterController::getWeaponAnimation(int weaponType) const else if (isRealWeapon) weaponGroup = oneHandFallback; } + else if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + return "attack1"; return weaponGroup; } @@ -1293,7 +1295,7 @@ bool CharacterController::updateState(CharacterState& idle) } mWeaponType = weaptype; - mCurrentWeapon = getWeaponAnimation(mWeaponType); + mCurrentWeapon = weapgroup; if(!upSoundId.empty() && !isStillWeapon) { @@ -1376,11 +1378,7 @@ bool CharacterController::updateState(CharacterState& idle) { mCurrentWeapon = chooseRandomAttackAnimation(); if (!mPtr.getClass().hasInventoryStore(mPtr)) - { mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - if (mWeaponType == ESM::Weapon::HandToHand) - playSwishSound(0.0f); - } } if(mWeaponType == ESM::Weapon::Spell) @@ -1561,7 +1559,11 @@ bool CharacterController::updateState(CharacterState& idle) weapSpeed, startKey, stopKey, 0.0f, 0); if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) + { mUpperBodyState = UpperCharState_StartToMinAttack; + if (mWeaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + playSwishSound(0.0f); + } } } From 4657060d2caef27f3a7d67fb8995c969dc8a7440 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 6 Feb 2022 11:08:47 +0100 Subject: [PATCH 2072/2859] Extend swish and strength changes to all random attacks --- apps/openmw/mwmechanics/character.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 3ea09569ba..90a175af51 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1377,8 +1377,6 @@ bool CharacterController::updateState(CharacterState& idle) (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { mCurrentWeapon = chooseRandomAttackAnimation(); - if (!mPtr.getClass().hasInventoryStore(mPtr)) - mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); } if(mWeaponType == ESM::Weapon::Spell) @@ -1561,8 +1559,11 @@ bool CharacterController::updateState(CharacterState& idle) if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) { mUpperBodyState = UpperCharState_StartToMinAttack; - if (mWeaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + if (isRandomAttackAnimation(mCurrentWeapon)) + { + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); playSwishSound(0.0f); + } } } } From 6c56436809f608853367f1851f3bf9b5790d6122 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 23 Jan 2022 20:49:42 +0100 Subject: [PATCH 2073/2859] Control AI packages from Lua --- apps/openmw/mwlua/localscripts.cpp | 102 +++++++++++- apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 6 +- apps/openmw/mwmechanics/aisequence.cpp | 12 +- apps/openmw/mwmechanics/aisequence.hpp | 10 +- docs/source/generate_luadoc.sh | 1 + .../reference/lua-scripting/aipackages.rst | 145 ++++++++++++++++++ docs/source/reference/lua-scripting/api.rst | 7 + .../source/reference/lua-scripting/events.rst | 13 ++ .../reference/lua-scripting/interface_ai.rst | 6 + .../reference/lua-scripting/overview.rst | 8 +- files/builtin_scripts/builtin.omwscripts | 1 + files/builtin_scripts/scripts/omw/ai.lua | 116 ++++++++++++++ files/lua_api/openmw/self.lua | 19 +-- 14 files changed, 401 insertions(+), 47 deletions(-) create mode 100644 docs/source/reference/lua-scripting/aipackages.rst create mode 100644 docs/source/reference/lua-scripting/events.rst create mode 100644 docs/source/reference/lua-scripting/interface_ai.rst create mode 100644 files/builtin_scripts/scripts/omw/ai.lua diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 2b492c5c17..552ce77541 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -1,9 +1,17 @@ #include "localscripts.hpp" +#include + #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/aisequence.hpp" #include "../mwmechanics/aicombat.hpp" +#include "../mwmechanics/aiescort.hpp" +#include "../mwmechanics/aifollow.hpp" +#include "../mwmechanics/aipursue.hpp" +#include "../mwmechanics/aitravel.hpp" +#include "../mwmechanics/aiwander.hpp" +#include "../mwmechanics/aipackage.hpp" #include "luamanagerimp.hpp" @@ -60,28 +68,106 @@ namespace MWLua } context.mLuaManager->addAction(std::make_unique(context.mLua, obj.id(), std::move(eqp))); }; - selfAPI["getCombatTarget"] = [worldView=context.mWorldView](SelfObject& self) -> sol::optional + + using AiPackage = MWMechanics::AiPackage; + sol::usertype aiPackage = context.mLua->sol().new_usertype("AiPackage"); + aiPackage["type"] = sol::readonly_property([](const AiPackage& p) -> std::string_view + { + switch (p.getTypeId()) + { + case MWMechanics::AiPackageTypeId::Wander: return "Wander"; + case MWMechanics::AiPackageTypeId::Travel: return "Travel"; + case MWMechanics::AiPackageTypeId::Escort: return "Escort"; + case MWMechanics::AiPackageTypeId::Follow: return "Follow"; + case MWMechanics::AiPackageTypeId::Activate: return "Activate"; + case MWMechanics::AiPackageTypeId::Combat: return "Combat"; + case MWMechanics::AiPackageTypeId::Pursue: return "Pursue"; + case MWMechanics::AiPackageTypeId::AvoidDoor: return "AvoidDoor"; + case MWMechanics::AiPackageTypeId::Face: return "Face"; + case MWMechanics::AiPackageTypeId::Breathe: return "Breathe"; + case MWMechanics::AiPackageTypeId::Cast: return "Cast"; + default: return "Unknown"; + } + }); + aiPackage["target"] = sol::readonly_property([worldView=context.mWorldView](const AiPackage& p) -> sol::optional + { + MWWorld::Ptr target = p.getTarget(); + if (target.isEmpty()) + return sol::nullopt; + else + return LObject(getId(target), worldView->getObjectRegistry()); + }); + aiPackage["sideWithTarget"] = sol::readonly_property([](const AiPackage& p) { return p.sideWithTarget(); }); + aiPackage["destination"] = sol::readonly_property([](const AiPackage& p) { return p.getDestination(); }); + + selfAPI["_getActiveAiPackage"] = [](SelfObject& self) -> sol::optional> { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - MWWorld::Ptr target; - if (ai.getCombatTarget(target)) - return LObject(getId(target), worldView->getObjectRegistry()); + if (ai.isEmpty()) + return sol::nullopt; else - return {}; + return *ai.begin(); }; - selfAPI["stopCombat"] = [](SelfObject& self) + selfAPI["_iterateAndFilterAiSequence"] = [](SelfObject& self, sol::function callback) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - ai.stopCombat(); + std::list>& list = ai.getUnderlyingList(); + for (auto it = list.begin(); it != list.end();) + { + bool keep = LuaUtil::call(callback, *it).get(); + if (keep) + ++it; + else + it = list.erase(it); + } }; - selfAPI["startCombat"] = [](SelfObject& self, const LObject& target) + selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target) { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); ai.stack(MWMechanics::AiCombat(target.ptr()), ptr); }; + selfAPI["_startAiPursue"] = [](SelfObject& self, const LObject& target) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiPursue(target.ptr()), ptr); + }; + selfAPI["_startAiFollow"] = [](SelfObject& self, const LObject& target) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiFollow(target.ptr()), ptr); + }; + selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell, + float duration, const osg::Vec3f& dest) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + // TODO: change AiEscort implementation to accept ptr instead of a non-unique refId. + const std::string& refId = target.ptr().getCellRef().getRefId(); + int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); + const ESM::Cell* esmCell = cell.mStore->getCell(); + if (esmCell->isExterior()) + ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr); + else + ai.stack(MWMechanics::AiEscort(refId, esmCell->mName, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr); + }; + selfAPI["_startAiWander"] = [](SelfObject& self, int distance, float duration) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + int gameHoursDuration = static_cast(std::ceil(duration / 3600.0)); + ai.stack(MWMechanics::AiWander(distance, gameHoursDuration, 0, {}, false), ptr); + }; + selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target) + { + const MWWorld::Ptr& ptr = self.ptr(); + MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), false), ptr); + }; } LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 084de7d5e1..06ba6d5c5a 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -49,7 +49,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 16; + api["API_REVISION"] = 17; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c567ea464c..2f447e12d6 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2013,7 +2013,7 @@ namespace MWMechanics std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) { std::list list; - forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::shared_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) list.push_back(iter.first); @@ -2064,7 +2064,7 @@ namespace MWMechanics std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) { std::list list; - forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::shared_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) { @@ -2081,7 +2081,7 @@ namespace MWMechanics std::map Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor) { std::map map; - forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::shared_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) { diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b9efcb1cde..86bc714964 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -87,17 +87,7 @@ bool AiSequence::getCombatTargets(std::vector &targetActors) const return !targetActors.empty(); } -std::list>::const_iterator AiSequence::begin() const -{ - return mPackages.begin(); -} - -std::list>::const_iterator AiSequence::end() const -{ - return mPackages.end(); -} - -void AiSequence::erase(std::list>::const_iterator package) +void AiSequence::erase(std::list>::const_iterator package) { // Not sure if manually terminated packages should trigger mDone, probably not? for(auto it = mPackages.begin(); it != mPackages.end(); ++it) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 853509ed80..6cbfcf045d 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -38,7 +38,7 @@ namespace MWMechanics class AiSequence { ///AiPackages to run though - std::list> mPackages; + std::list> mPackages; ///Finished with top AIPackage, set for one frame bool mDone; @@ -63,10 +63,12 @@ namespace MWMechanics virtual ~AiSequence(); /// Iterator may be invalidated by any function calls other than begin() or end(). - std::list>::const_iterator begin() const; - std::list>::const_iterator end() const; + std::list>::const_iterator begin() const { return mPackages.begin(); } + std::list>::const_iterator end() const { return mPackages.end(); } - void erase(std::list>::const_iterator package); + void erase(std::list>::const_iterator package); + + std::list>& getUnderlyingList() { return mPackages; } /// Returns currently executing AiPackage type /** \see enum class AiPackageTypeId **/ diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index 067a1ad4cf..99e387cf0f 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -65,5 +65,6 @@ $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw/*lua cd $FILES_DIR/builtin_scripts $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua +$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/ai.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera.lua diff --git a/docs/source/reference/lua-scripting/aipackages.rst b/docs/source/reference/lua-scripting/aipackages.rst new file mode 100644 index 0000000000..4570be712e --- /dev/null +++ b/docs/source/reference/lua-scripting/aipackages.rst @@ -0,0 +1,145 @@ +Built-in AI packages +==================== + +Combat +------ + +Attack another actor. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - target + - `GameObject `_ [required] + - the actor to attack + +**Examples** + +.. code-block:: Lua + + -- from local script add package to self + local AI = require('openmw.interfaces').AI + AI.startPackage({type='Combat', target=anotherActor}) + + -- via event to any actor + actor:sendEvent('StartAIPackage', {type='Combat', target=anotherActor}) + +Pursue +------ + +Pursue another actor. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - target + - `GameObject `_ [required] + - the actor to pursue + +Follow +------ + +Follow another actor. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - target + - `GameObject `_ [required] + - the actor to follow + +Escort +------ + +Escort another actor to the given location. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - target + - `GameObject `_ [required] + - the actor to follow + * - destPosition + - `3d vector `_ [required] + - the destination point + * - destCell + - Cell [optional] + - the destination cell + * - duration + - number [optional] + - duration in game time (will be rounded up to the next hour) + +**Example** + +.. code-block:: Lua + + actor:sendEvent('StartAIPackage', { + type = 'Escort', + target = object.self, + destPosition = util.vector3(x, y, z), + duration = 3 * time.hour, + }) + +Wander +------ + +Wander nearby current position. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - distance + - float [default=0] + - the actor to follow + * - duration + - number [optional] + - duration in game time (will be rounded up to the next hour) + +Travel +------ + +Go to given location. + +**Arguments** + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type + - description + * - destPosition + - `3d vector `_ [required] + - the point to travel to + diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index ef831e734a..41fd52253f 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -7,6 +7,8 @@ Lua API reference engine_handlers user_interface + aipackages + events openmw_util openmw_storage openmw_core @@ -21,6 +23,7 @@ Lua API reference openmw_aux_calendar openmw_aux_util openmw_aux_time + interface_ai interface_camera @@ -28,6 +31,8 @@ Lua API reference - :ref:`User interface reference ` - `Game object reference `_ - `Cell reference `_ +- :ref:`Built-in AI packages` +- :ref:`Built-in events` **API packages** @@ -87,6 +92,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ | Interface | Can be used | Description | +=========================================================+====================+===============================================================+ +|:ref:`AI ` | by local scripts | | Control basic AI of NPCs and creatures. | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`Camera ` | by player scripts | | Allows to alter behavior of the built-in camera script | | | | | without overriding the script completely. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst new file mode 100644 index 0000000000..3d443b2811 --- /dev/null +++ b/docs/source/reference/lua-scripting/events.rst @@ -0,0 +1,13 @@ +Built-in events +=============== + +Any script can send to any actor (except player, for player will be ignored) events ``StartAIPackage`` and ``RemoveAIPackages``. +The effect is equivalent to calling ``interfaces.AI.startPackage`` or ``interfaces.AI.removePackages`` in a local script on this actor. + +Examples: + +.. code-block:: Lua + + actor:sendEvent('StartAIPackage', {type='Combat', target=self.object}) + actor:sendEvent('RemoveAIPackages', 'Pursue') + diff --git a/docs/source/reference/lua-scripting/interface_ai.rst b/docs/source/reference/lua-scripting/interface_ai.rst new file mode 100644 index 0000000000..ec79b50d5d --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_ai.rst @@ -0,0 +1,6 @@ +Interface AI +============ + +.. raw:: html + :file: generated_html/scripts_omw_ai.html + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 1bfdf33c42..02c76d094c 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -452,6 +452,8 @@ The order in which the scripts are started is important. So if one mod should ov +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ | Interface | Can be used | Description | +=========================================================+====================+===============================================================+ +|:ref:`AI ` | by local scripts | | Control basic AI of NPCs and creatures. | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`Camera ` | by player scripts | | Allows to alter behavior of the built-in camera script | | | | | without overriding the script completely. | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ @@ -484,7 +486,7 @@ At some moment it will send the 'DamagedByDarkPower' event to all nearby actors: local self = require('openmw.self') local nearby = require('openmw.nearby') - local function onActivate() + local function onActivated() for i, actor in nearby.actors:ipairs() do local dist = (self.position - actor.position):length() if dist < 500 then @@ -494,7 +496,7 @@ At some moment it will send the 'DamagedByDarkPower' event to all nearby actors: end end - return { engineHandlers = { ... } } + return { engineHandlers = { onActivated = onActivated } } And every actor should have a local script that processes this event: @@ -526,6 +528,8 @@ The protection mod attaches an additional local script to every actor. The scrip In order to be able to intercept the event, the protection script should be placed in the load order below the original script. +See :ref:`the list of events ` that are used by built-in scripts. + Timers ====== diff --git a/files/builtin_scripts/builtin.omwscripts b/files/builtin_scripts/builtin.omwscripts index 30fccad9fa..2ffe7007f2 100644 --- a/files/builtin_scripts/builtin.omwscripts +++ b/files/builtin_scripts/builtin.omwscripts @@ -1 +1,2 @@ PLAYER: scripts/omw/camera.lua +NPC,CREATURE: scripts/omw/ai.lua diff --git a/files/builtin_scripts/scripts/omw/ai.lua b/files/builtin_scripts/scripts/omw/ai.lua new file mode 100644 index 0000000000..5d76a7d9be --- /dev/null +++ b/files/builtin_scripts/scripts/omw/ai.lua @@ -0,0 +1,116 @@ +local self = require('openmw.self') +local interfaces = require('openmw.interfaces') + +local function startPackage(args) + if args.type == 'Combat' then + if not args.target then error("target required") end + self:_startAiCombat(args.target) + elseif args.type == 'Pursue' then + if not args.target then error("target required") end + self:_startAiPursue(args.target) + elseif args.type == 'Follow' then + if not args.target then error("target required") end + self:_startAiFollow(args.target) + elseif args.type == 'Escort' then + if not args.target then error("target required") end + if not args.destPosition then error("destPosition required") end + self:_startAiEscort(args.target, args.destCell or self.cell, args.duration or 0, args.destPosition) + elseif args.type == 'Wander' then + self:_startAiWander(args.distance or 0, args.duration or 0) + elseif args.type == 'Travel' then + if not args.destPosition then error("destPosition required") end + self:_startAiTravel(args.destPosition) + else + error('Unsupported AI Package: '..args.type) + end +end + +local function filterPackages(filter) + self:_iterateAndFilterAiSequence(filter) +end + +return { + interfaceName = 'AI', + --- Basic AI interface + -- @module AI + -- @usage require('openmw.interfaces').AI + interface = { + --- Interface version + -- @field [parent=#AI] #number version + version = 0, + + --- AI Package + -- @type Package + -- @field #string type Type of the AI package. + -- @field openmw.core#GameObject target Target (usually an actor) of the AI package (can be nil). + -- @field #boolean sideWithTarget Whether to help the target in combat (true or false). + -- @field openmw.util#Vector3 position Destination point of the AI package (can be nil). + + --- Return the currently active AI package (or `nil` if there are no AI packages). + -- @function [parent=#AI] getActivePackage + -- @return #Package + getActivePackage = function() return self:_getActiveAiPackage() end, + + --- Start new AI package. + -- @function [parent=#AI] startPackage + -- @param #table options See the "Built-in AI packages" page. + startPackage = startPackage, + + --- Iterate over all packages starting from the active one and remove those where `filterCallback` returns false. + -- @function [parent=#AI] filterPackages + -- @param #function filterCallback + filterPackages = filterPackages, + + --- Iterate over all packages and run `callback` for each starting from the active one. + -- The same as `filterPackage`, but without removal. + -- @function [parent=#AI] forEachPackage + -- @param #function callback + forEachPackage = function(callback) + local filter = function(p) + callback(p) + return true + end + filterPackages(filter) + end, + + --- Remove packages of given type (remove all packages if the type is not specified). + -- @function [parent=#AI] removePackages + -- @param #string packageType (optional) The type of packages to remove. + removePackages = function(packageType) + filterPackages(function(p) return packageType and p.type ~= packageType end) + end, + + --- Return the target of the active package if the package has given type + -- @function [parent=#AI] getActiveTarget + -- @param #string packageType The expected type of the active package + -- @return openmw.core#GameObject The target (can be nil if the package has no target or has another type) + getActiveTarget = function(packageType) + local p = self:_getActiveAiPackage() + if p and p.type == packageType then + return p.target + else + return nil + end + end, + + --- Get list of targets of all packages of the given type. + -- @function [parent=#AI] getTargets + -- @param #string packageType + -- @return #list + getTargets = function(packageType) + local res = {} + filterPackages(function(p) + if p.type == packageType and p.target then + res[#res + 1] = p.target + end + return true + end) + return res + end, + }, + eventHandlers = { + StartAIPackage = function(options) interfaces.AI.startPackage(options) end, + RemoveAIPackages = function(packageType) interfaces.AI.removePackages(packageType) end, + }, +} + diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 1228a92a87..d6f01412eb 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -37,27 +37,10 @@ -- @field [parent=#ActorControls] #number use if 1 - activates the readied weapon/spell. For weapons, keeping at 1 will charge the attack until set to 0. ------------------------------------------------------------------------------- --- Enables or disables standart AI (enabled by default). +-- Enables or disables standard AI (enabled by default). -- @function [parent=#self] enableAI -- @param self -- @param #boolean v -------------------------------------------------------------------------------- --- Returns current target or nil if not in combat --- @function [parent=#self] getCombatTarget --- @param self --- @return openmw.core#GameObject - -------------------------------------------------------------------------------- --- Remove all combat packages from the actor. --- @function [parent=#self] stopCombat --- @param self - -------------------------------------------------------------------------------- --- Attack `target`. --- @function [parent=#self] startCombat --- @param self --- @param openmw.core#GameObject target - return nil From 60aa57c8d8fafee95974a0ba8654d31bef872a87 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Sun, 6 Feb 2022 18:18:20 +0000 Subject: [PATCH 2074/2859] Update water.rst --- docs/source/reference/modding/settings/water.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index fb3b755c6f..5057870353 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -82,7 +82,7 @@ rain ripple detail :Type: integer :Range: 0, 1, 2 -:Default: 2 +:Default: 1 Controls how detailed the raindrop ripples on water are. From ebaee9d08c1051b3fce00176a153e837ec51e0b2 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Sun, 6 Feb 2022 18:20:31 +0000 Subject: [PATCH 2075/2859] Update settings-default.cfg --- files/settings-default.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index cec1dc3432..3d461d2dce 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -654,7 +654,7 @@ reflection detail = 2 # Whether to use fully detailed raindrop ripples. (0, 1, 2). # 0 = rings only; 1 = sparse, high detail; 2 = dense, high detail -rain ripple detail = 2 +rain ripple detail = 1 # Overrides the value in '[Camera] small feature culling pixel size' specifically for water reflection/refraction textures. small feature culling pixel size = 20.0 From ac5cd6c80a6d5bc214bce4b1a4db16f22e852add Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 6 Feb 2022 21:07:14 +0100 Subject: [PATCH 2076/2859] Add default value for `user-data` in openmw.cfg --- apps/openmw/options.cpp | 2 +- components/files/configurationmanager.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp index c78dc395d9..dbf910b70f 100644 --- a/apps/openmw/options.cpp +++ b/apps/openmw/options.cpp @@ -27,7 +27,7 @@ namespace OpenMW ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") - ("data-local", bpo::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""), + ("data-local", bpo::value()->default_value(Files::MaybeQuotedPath(), ""), "set local data directory (highest priority)") ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 87f4cd4943..0c5ba40882 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -168,9 +168,12 @@ void ConfigurationManager::addExtraConfigDirs(std::stack(defaultUserData) = boost::filesystem::path("?userdata?"); + description.add_options() ("config", bpo::value()->multitoken()->composing(), "additional config directories") - ("user-data", bpo::value(), + ("user-data", bpo::value()->default_value(defaultUserData, ""), "set user data directory (used for saves, screenshots, etc)"); } From 581c3f48829983cedea3f072a3194ce2be9270b3 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 6 Feb 2022 20:22:38 +0000 Subject: [PATCH 2077/2859] Update and document Lua Text and TextEdit widget types, fix some issues with Lua UI --- apps/openmw/mwlua/uibindings.cpp | 7 + components/CMakeLists.txt | 2 +- components/lua_ui/alignment.cpp | 18 +++ components/lua_ui/alignment.hpp | 18 +++ components/lua_ui/container.hpp | 4 +- components/lua_ui/element.cpp | 2 + components/lua_ui/image.cpp | 4 +- components/lua_ui/image.hpp | 2 +- components/lua_ui/properties.hpp | 11 ++ components/lua_ui/text.cpp | 28 +++- components/lua_ui/text.hpp | 9 +- components/lua_ui/textedit.cpp | 34 ++++- components/lua_ui/textedit.hpp | 8 +- components/lua_ui/widget.cpp | 37 +++-- components/lua_ui/widget.hpp | 7 +- components/lua_ui/window.cpp | 7 +- components/lua_ui/window.hpp | 4 +- .../lua-scripting/user_interface.rst | 137 ++++++------------ .../reference/lua-scripting/widgets/text.rst | 44 ++++++ .../lua-scripting/widgets/textedit.rst | 51 +++++++ files/lua_api/openmw/ui.lua | 26 +++- files/mygui/CMakeLists.txt | 1 + files/mygui/core.skin | 4 - files/mygui/core.xml | 1 + files/mygui/openmw_lua.xml | 15 ++ 25 files changed, 338 insertions(+), 143 deletions(-) create mode 100644 components/lua_ui/alignment.cpp create mode 100644 components/lua_ui/alignment.hpp create mode 100644 docs/source/reference/lua-scripting/widgets/text.rst create mode 100644 docs/source/reference/lua-scripting/widgets/textedit.rst create mode 100644 files/mygui/openmw_lua.xml diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 6f1d34072a..4333ae5072 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "context.hpp" #include "actions.hpp" @@ -239,6 +240,12 @@ namespace MWLua typeTable.set(it.second, it.first); api["TYPE"] = LuaUtil::makeReadOnly(typeTable); + api["ALIGNMENT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + { "Start", LuaUi::Alignment::Start }, + { "Center", LuaUi::Alignment::Center }, + { "End", LuaUi::Alignment::End } + })); + api["registerSettingsPage"] = &LuaUi::registerSettingsPage; return LuaUtil::makeReadOnly(api); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 9a1ffc847c..b6d40931be 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -166,7 +166,7 @@ add_component_dir (queries add_component_dir (lua_ui registerscriptsettings scriptsettings - properties widget element util layers content + properties widget element util layers content alignment adapter text textedit window image container ) diff --git a/components/lua_ui/alignment.cpp b/components/lua_ui/alignment.cpp new file mode 100644 index 0000000000..dfd09b752d --- /dev/null +++ b/components/lua_ui/alignment.cpp @@ -0,0 +1,18 @@ +#include "alignment.hpp" + +namespace LuaUi +{ + MyGUI::Align alignmentToMyGui(Alignment horizontal, Alignment vertical) + { + MyGUI::Align align(MyGUI::Align::Center); + if (horizontal == Alignment::Start) + align |= MyGUI::Align::Left; + if (horizontal == Alignment::End) + align |= MyGUI::Align::Right; + if (horizontal == Alignment::Start) + align |= MyGUI::Align::Top; + if (horizontal == Alignment::End) + align |= MyGUI::Align::Bottom; + return align; + } +} diff --git a/components/lua_ui/alignment.hpp b/components/lua_ui/alignment.hpp new file mode 100644 index 0000000000..8503ffba03 --- /dev/null +++ b/components/lua_ui/alignment.hpp @@ -0,0 +1,18 @@ +#ifndef OPENMW_LUAUI_ALIGNMENT +#define OPENMW_LUAUI_ALIGNMENT + +#include + +namespace LuaUi +{ + enum class Alignment + { + Start = 0, + Center = 1, + End = 2 + }; + + MyGUI::Align alignmentToMyGui(Alignment horizontal, Alignment vertical); +} + +#endif // !OPENMW_LUAUI_PROPERTIES diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp index a005b7ae53..664fb08ea2 100644 --- a/components/lua_ui/container.hpp +++ b/components/lua_ui/container.hpp @@ -10,8 +10,8 @@ namespace LuaUi MYGUI_RTTI_DERIVED(LuaContainer) protected: - virtual void updateChildren() override; - virtual MyGUI::IntSize childScalingSize() override; + void updateChildren() override; + MyGUI::IntSize childScalingSize() override; private: void updateSizeToFit(); diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 59a3b13d55..d2074ecfc2 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -130,6 +130,8 @@ namespace LuaUi void updateWidget(WidgetExtension* ext, const sol::table& layout) { + ext->resetSlot(); // otherwise if template gets changed, all non-template children will get destroyed + ext->setLayout(layout); ext->setExternal(layout.get(LayoutKeys::external)); setTemplate(ext, layout.get(LayoutKeys::templateLayout)); diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index c8fdafbb66..c72bac21e9 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -19,9 +19,9 @@ namespace LuaUi // mCoord could be zero, prevent division by 0 // use arbitrary large numbers to prevent performance issues - if (mTileSize.width == 0) + if (mTileSize.width <= 0) mTileSize.width = 1e7; - if (mTileSize.height == 0) + if (mTileSize.height <= 0) mTileSize.height = 1e7; } diff --git a/components/lua_ui/image.hpp b/components/lua_ui/image.hpp index 4073d1bd2a..e841e55fdb 100644 --- a/components/lua_ui/image.hpp +++ b/components/lua_ui/image.hpp @@ -29,7 +29,7 @@ namespace LuaUi LuaImage(); protected: - virtual void updateProperties() override; + void updateProperties() override; LuaTileRect* mTileRect; }; } diff --git a/components/lua_ui/properties.hpp b/components/lua_ui/properties.hpp index 7c47291b84..ade25156e3 100644 --- a/components/lua_ui/properties.hpp +++ b/components/lua_ui/properties.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace LuaUi { @@ -18,6 +19,12 @@ namespace LuaUi std::is_same(); } + template + constexpr bool isMyGuiColor() + { + return std::is_same(); + } + template sol::optional parseValue( sol::object table, @@ -41,6 +48,8 @@ namespace LuaUi LuaT luaT = opt.as(); if constexpr (isMyGuiVector()) return T(luaT.x(), luaT.y()); + else if constexpr (isMyGuiColor()) + return T(luaT.r(), luaT.g(), luaT.b(), luaT.a()); else return luaT; } @@ -53,6 +62,8 @@ namespace LuaUi { if constexpr (isMyGuiVector()) return parseValue(table, field, errorPrefix); + else if constexpr (isMyGuiColor()) + return parseValue(table, field, errorPrefix); else return parseValue(table, field, errorPrefix); } diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index 47e2b574cc..3a56ad68af 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -1,18 +1,40 @@ - #include "text.hpp" +#include "alignment.hpp" + namespace LuaUi { LuaText::LuaText() : mAutoSized(true) + {} + + void LuaText::initialize() { - changeWidgetSkin("NormalText"); + changeWidgetSkin("SandText"); + setEditStatic(true); + setVisibleHScroll(false); + setVisibleVScroll(false); + + WidgetExtension::initialize(); } void LuaText::updateProperties() { - setCaption(propertyValue("caption", std::string())); mAutoSized = propertyValue("autoSize", true); + + setCaption(propertyValue("text", std::string())); + setFontHeight(propertyValue("textSize", 10)); + setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); + setEditMultiLine(propertyValue("multiline", false)); + setEditWordWrap(propertyValue("wordWrap", false)); + + Alignment horizontal(propertyValue("textAlignH", Alignment::Start)); + Alignment vertical(propertyValue("textAlignV", Alignment::Start)); + setTextAlign(alignmentToMyGui(horizontal, vertical)); + + setTextShadow(propertyValue("textShadow", false)); + setTextShadowColour(propertyValue("textShadowColor", MyGUI::Colour(0, 0, 0, 1))); + WidgetExtension::updateProperties(); } diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index c435f6687a..e42b6f9935 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -1,26 +1,27 @@ #ifndef OPENMW_LUAUI_TEXT #define OPENMW_LUAUI_TEXT -#include +#include #include "widget.hpp" namespace LuaUi { - class LuaText : public MyGUI::TextBox, public WidgetExtension + class LuaText : public MyGUI::EditBox, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaText) public: LuaText(); - virtual void updateProperties() override; + void initialize() override; + void updateProperties() override; void setCaption(const MyGUI::UString& caption) override; private: bool mAutoSized; protected: - virtual MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() override; }; } diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index 5fbdf3e6d8..ac44872f21 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -1,11 +1,43 @@ #include "textedit.hpp" +#include "alignment.hpp" + namespace LuaUi { + void LuaTextEdit::initialize() + { + changeWidgetSkin("LuaTextEdit"); + + eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); + + WidgetExtension::initialize(); + } + + void LuaTextEdit::deinitialize() + { + eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); + WidgetExtension::deinitialize(); + } + void LuaTextEdit::updateProperties() { - setCaption(propertyValue("caption", std::string())); + setCaption(propertyValue("text", std::string())); + setFontHeight(propertyValue("textSize", 10)); + setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); + setEditMultiLine(propertyValue("multiline", false)); + setEditWordWrap(propertyValue("wordWrap", false)); + + Alignment horizontal(propertyValue("textAlignH", Alignment::Start)); + Alignment vertical(propertyValue("textAlignV", Alignment::Start)); + setTextAlign(alignmentToMyGui(horizontal, vertical)); + + setEditStatic(propertyValue("readOnly", false)); WidgetExtension::updateProperties(); } + + void LuaTextEdit::textChange(MyGUI::EditBox*) + { + triggerEvent("textChanged", sol::make_object(lua(), getCaption().asUTF8())); + } } diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index dfc649994a..208240283c 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -11,7 +11,13 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaTextEdit) - virtual void updateProperties() override; + protected: + void initialize() override; + void deinitialize() override; + void updateProperties() override; + + private: + void textChange(MyGUI::EditBox*); }; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 70ed6b64fe..4364a0c362 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -10,14 +10,13 @@ namespace LuaUi { WidgetExtension::WidgetExtension() - : mForcedCoord() - , mAbsoluteCoord() - , mRelativeCoord() - , mAnchor() - , mLua(nullptr) + : mLua(nullptr) , mWidget(nullptr) , mSlot(this) , mLayout(sol::nil) + , mProperties(sol::nil) + , mTemplateProperties(sol::nil) + , mExternal(sol::nil) , mParent(nullptr) {} @@ -80,16 +79,22 @@ namespace LuaUi ext->updateCoord(); } - WidgetExtension* WidgetExtension::findFirst(std::string_view flagName) + void WidgetExtension::attachTemplate(WidgetExtension* ext) + { + ext->widget()->attachToWidget(widget()); + ext->updateCoord(); + } + + WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) { - if (externalValue(flagName, false)) - return this; for (WidgetExtension* w : mChildren) { - WidgetExtension* result = w->findFirst(flagName); + WidgetExtension* result = w->findDeep(flagName); if (result != nullptr) return result; } + if (externalValue(flagName, false)) + return this; return nullptr; } @@ -101,11 +106,11 @@ namespace LuaUi w->findAll(flagName, result); } - WidgetExtension* WidgetExtension::findFirstInTemplates(std::string_view flagName) + WidgetExtension* WidgetExtension::findDeepInTemplates(std::string_view flagName) { for (WidgetExtension* w : mTemplateChildren) { - WidgetExtension* result = w->findFirst(flagName); + WidgetExtension* result = w->findDeep(flagName); if (result != nullptr) return result; } @@ -163,7 +168,7 @@ namespace LuaUi for (size_t i = 0; i < children.size(); ++i) { mTemplateChildren[i] = children[i]; - mTemplateChildren[i]->widget()->attachToWidget(mWidget); + attachTemplate(mTemplateChildren[i]); } updateTemplate(); } @@ -171,9 +176,11 @@ namespace LuaUi void WidgetExtension::updateTemplate() { WidgetExtension* oldSlot = mSlot; - mSlot = findFirstInTemplates("slot"); - if (mSlot == nullptr) + WidgetExtension* slot = findDeepInTemplates("slot"); + if (slot == nullptr) mSlot = this; + else + mSlot = slot->mSlot; if (mSlot != oldSlot) for (WidgetExtension* w : mChildren) attach(w); @@ -281,7 +288,7 @@ namespace LuaUi MyGUI::IntSize WidgetExtension::childScalingSize() { - return widget()->getSize(); + return mSlot->widget()->getSize(); } void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 63af5b3cae..9037443676 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -50,6 +50,7 @@ namespace LuaUi const sol::table& getLayout() { return mLayout; } void setLayout(const sol::table& layout) { mLayout = layout; } + void resetSlot() { mSlot = this; } template T externalValue(std::string_view name, const T& defaultValue) @@ -80,13 +81,14 @@ namespace LuaUi return parseProperty(mProperties, mTemplateProperties, name, defaultValue); } - WidgetExtension* findFirstInTemplates(std::string_view flagName); + WidgetExtension* findDeepInTemplates(std::string_view flagName); std::vector findAllInTemplates(std::string_view flagName); virtual void updateTemplate(); virtual void updateProperties(); virtual void updateChildren() {}; + lua_State* lua() { return mLua; } void triggerEvent(std::string_view name, const sol::object& argument) const; // offsets the position and size, used only in C++ widget code @@ -114,8 +116,9 @@ namespace LuaUi WidgetExtension* mParent; void attach(WidgetExtension* ext); + void attachTemplate(WidgetExtension* ext); - WidgetExtension* findFirst(std::string_view name); + WidgetExtension* findDeep(std::string_view name); void findAll(std::string_view flagName, std::vector& result); void updateChildrenCoord(); diff --git a/components/lua_ui/window.cpp b/components/lua_ui/window.cpp index 6e6b80bf22..41281208d4 100644 --- a/components/lua_ui/window.cpp +++ b/components/lua_ui/window.cpp @@ -5,10 +5,7 @@ namespace LuaUi { LuaWindow::LuaWindow() - : mCaption() - , mPreviousMouse() - , mChangeScale() - , mMoveResize() + : mCaption(nullptr) {} void LuaWindow::updateTemplate() @@ -20,7 +17,7 @@ namespace LuaUi } mActionWidgets.clear(); - WidgetExtension* captionWidget = findFirstInTemplates("caption"); + WidgetExtension* captionWidget = findDeepInTemplates("caption"); mCaption = dynamic_cast(captionWidget); if (mCaption) diff --git a/components/lua_ui/window.hpp b/components/lua_ui/window.hpp index be3b3e2c23..8c466e73d1 100644 --- a/components/lua_ui/window.hpp +++ b/components/lua_ui/window.hpp @@ -14,8 +14,8 @@ namespace LuaUi public: LuaWindow(); - virtual void updateTemplate() override; - virtual void updateProperties() override; + void updateTemplate() override; + void updateProperties() override; private: LuaText* mCaption; diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 13420d318c..bb4a3a3d53 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -1,11 +1,6 @@ User interface reference ======================== -.. toctree:: - :hidden: - - widgets/widget - Layouts ------- @@ -79,103 +74,57 @@ Events Widget types ------------ -.. list-table:: - :widths: 30 70 +.. toctree:: + :maxdepth: 1 - * - :ref:`Widget` - - Base widget type, all the other widget inherit its properties and events. - * - `Text` - - Displays text. - * - EditText - - Accepts text input from the user. - * - Window - - Can be moved and resized by the user. + Widget: Base widget type, all the other widgets inherit its properties and events. + Text: Displays text. + TextEdit: Accepts text input from the user. Example ------- -*scripts/requirePassword.lua* +*scripts/clock.lua* .. code-block:: Lua - local core = require('openmw.core') - local async = require('openmw.async') - local ui = require('openmw.ui') - local v2 = require('openmw.util').vector2 - - local layout = { - layers = 'Windows', - type = ui.TYPE.Window, - template = { skin = 'MW_Window' }, -- TODO: replace all skins here when they are re-implemented in Lua - props = { - size = v2(200, 250), - -- put the window in the middle of the screen - relativePosition = v2(0.5, 0.5), - anchor = v2(0.5, 0.5), - }, - content = ui.content { - { - type = ui.TYPE.Text, - template = { skin = 'SandText' }, - props = { - caption = 'Input password', - relativePosition = v2(0.5, 0), - anchor = v2(0.5, 0), - }, - }, - { - name = 'input', - type = ui.TYPE.TextEdit, - template = { skin = "MW_TextEdit" }, - props = { - caption = '', - relativePosition = v2(0.5, 0.5), - anchor = v2(0.5, 0.5), - size = v2(125, 50), - }, - events = {} - }, - { - name = 'submit', - type = ui.TYPE.Text, -- TODO: replace with button when implemented - template = { skin = "MW_Button" }, - props = { - caption = 'Submit', - -- position at the bottom - relativePosition = v2(0.5, 1.0), - anchor = v2(0.5, 1.0), - autoSize = false, - size = v2(75, 50), - }, - events = {}, - }, - }, - } - - local element = nil - - local input = layout.content.input - -- TODO: replace with a better event when TextEdit is finished - input.events.textInput = async:callback(function(text) - input.props.caption = input.props.caption .. text - end) - - local submit = layout.content.submit - submit.events.mouseClick = async:callback(function() - if input.props.caption == 'very secret password' then - if element then - element:destroy() - end - else - print('wrong password', input.props.caption) - core.quit() - end - end) - - element = ui.create(layout) - -*requirePassword.omwscripts* + local ui = require('openmw.ui') + local util = require('openmw.util') + local calendar = require('openmw_aux.calendar') + local time = require('openmw_aux.time') + + local element = ui.create { + -- important not to forget the layer + -- by default widgets are not attached to any layer and are not visible + layer = 'HUD', + type = ui.TYPE.Text, + props = { + -- position in the top right corner + relativePosition = util.vector2(1, 0), + -- position is for the top left corner of the widget by default + -- change it to align exactly to the top right corner of the screen + anchor = util.vector2(1, 0), + text = calendar.formatGameTime('%H:%M'), + textSize = 24, + -- default black text color isn't always visible + textColor = util.color.rgb(0, 1, 0), + }, + } + + local function updateTime() + -- formatGameTime uses current time by default + -- otherwise we could get it by calling `core.getGameTime()` + element.layout.props.text = calendar.formatGameTime('%H:%M') + -- the layout changes won't affect the widget unless we request an update + element:update() + end + + -- we are showing game time in hours and minutes + -- so no need to update more often than ones a game minute + time.runRepeatedly(updateTime, 1 * time.minute, { type = time.GameTime }) + +*clock.omwscripts* :: - PLAYER: scripts/requirePassword.lua + PLAYER: scripts/clock.lua diff --git a/docs/source/reference/lua-scripting/widgets/text.rst b/docs/source/reference/lua-scripting/widgets/text.rst new file mode 100644 index 0000000000..074100577a --- /dev/null +++ b/docs/source/reference/lua-scripting/widgets/text.rst @@ -0,0 +1,44 @@ +Text Widget +=========== + +Properties +---------- + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - autoSize + - boolean (true) + - | Adjusts this widget's size to fit the text exactly. + | Ignores `size` and `relativeSize`. + * - text + - string ('') + - The text to display. + * - textSize + - number (10) + - The size of the text. + * - textColor + - util.color (0, 0, 0, 1) + - The color of the text. + * - multiline + - boolean (false) + - Whether to render text on multiple lines. + * - wordWrap + - boolean (false) + - Whether to break text into lines to fit the widget's width. + * - textAlignH + - ui.ALIGNMENT (Start) + - Horizontal alignment of the text. + * - textAlignV + - ui.ALIGNMENT (Start) + - Vertical alignment of the text. + * - textShadow + - boolean (false) + - Whether to render a shadow behind the text. + * - textShadowColor + - util.color (0, 0, 0, 1) + - The color of the text shadow. diff --git a/docs/source/reference/lua-scripting/widgets/textedit.rst b/docs/source/reference/lua-scripting/widgets/textedit.rst new file mode 100644 index 0000000000..16e640d1b0 --- /dev/null +++ b/docs/source/reference/lua-scripting/widgets/textedit.rst @@ -0,0 +1,51 @@ +TextEdit Widget +=============== + +Properties +---------- + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - text + - string ('') + - The text to display. + * - textSize + - number (10) + - The size of the text. + * - textColor + - util.color (0, 0, 0, 1) + - The color of the text. + * - multiline + - boolean (false) + - Whether to render text on multiple lines. + * - wordWrap + - boolean (false) + - Whether to break text into lines to fit the widget's width. + * - textAlignH + - ui.ALIGNMENT (Start) + - Horizontal alignment of the text. + * - textAlignV + - ui.ALIGNMENT (Start) + - Vertical alignment of the text. + * - readOnly + - boolean (false) + - Whether the text can be edited. + +Events +------ + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - first argument type + - description + * - textChanged + - string + - Displayed text changed (e. g. by user input) diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 8049df860a..d4f305b96e 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -6,18 +6,32 @@ -- local ui = require('openmw.ui') --- --- @field [parent=#ui] #WIDGET_TYPE WIDGET_TYPE +-- Widget types +-- @field [parent=#ui] #TYPE TYPE + +--- +-- Alignment values (left to right, top to bottom) +-- @field [parent=#ui] #ALIGNMENT ALIGNMENT --- -- Tools for working with layers -- @field [parent=#ui] #Layers layers --- --- @type WIDGET_TYPE --- @field [parent=#WIDGET_TYPE] Widget Base widget type --- @field [parent=#WIDGET_TYPE] Text Display text --- @field [parent=#WIDGET_TYPE] TextEdit Accepts user text input --- @field [parent=#WIDGET_TYPE] Window Can be moved and resized by the user +-- All available widget types +-- @type TYPE +-- @field Widget Base widget type +-- @field Text Display text +-- @field TextEdit Accepts user text input +-- @field Window Can be moved and resized by the user + +--- +-- Alignment values (details depend on the specific property). +-- For horizontal alignment the order is left to right, for vertical alignment the order is top to bottom. +-- @type ALIGNMENT +-- @field Start +-- @field Center +-- @field End --- -- Shows given message at the bottom of the screen. diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index bdf7558cf7..1edfbca806 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -43,6 +43,7 @@ set(MYGUI_FILES openmw_journal.layout openmw_journal.skin.xml openmw_layers.xml + openmw_lua.xml openmw_list.skin.xml openmw_mainmenu.layout openmw_mainmenu.skin.xml diff --git a/files/mygui/core.skin b/files/mygui/core.skin index 5cf02a99e5..ee9135554e 100644 --- a/files/mygui/core.skin +++ b/files/mygui/core.skin @@ -15,8 +15,4 @@ - - - - diff --git a/files/mygui/core.xml b/files/mygui/core.xml index 23697890dc..54e2aecd27 100644 --- a/files/mygui/core.xml +++ b/files/mygui/core.xml @@ -5,6 +5,7 @@ + diff --git a/files/mygui/openmw_lua.xml b/files/mygui/openmw_lua.xml new file mode 100644 index 0000000000..cb0af2b4a9 --- /dev/null +++ b/files/mygui/openmw_lua.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file From 8e330653c7caa1e3de5740919e6f9a0e6320fed4 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 6 Feb 2022 22:16:37 +0100 Subject: [PATCH 2078/2859] Fix #6590 --- apps/openmw/mwlua/luamanagerimp.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index e9146b8af4..0dcd72d359 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -114,6 +114,7 @@ namespace MWLua void LuaManager::update() { + static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); if (mPlayer.isEmpty()) return; // The game is not started yet. @@ -172,7 +173,8 @@ namespace MWLua LObject obj(e.mDest, objectRegistry); if (!obj.isValid()) { - Log(Debug::Verbose) << "Can not call engine handlers: object" << idToString(e.mDest) << " is not found"; + if (luaDebug) + Log(Debug::Verbose) << "Can not call engine handlers: object" << idToString(e.mDest) << " is not found"; continue; } LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); @@ -204,7 +206,7 @@ namespace MWLua GObject obj(id, objectRegistry); if (obj.isValid()) mGlobalScripts.actorActive(obj); - else + else if (luaDebug) Log(Debug::Verbose) << "Can not call onActorActive engine handler: object" << idToString(id) << " is already removed"; } mActorAddedEvents.clear(); From 5f74df75c6efbdf4f5ea679759c30b09d640faa9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 6 Feb 2022 23:09:06 +0100 Subject: [PATCH 2079/2859] Add default value for `config`. --- components/files/configurationmanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 0c5ba40882..80426586fe 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -172,7 +172,8 @@ void ConfigurationManager::addCommonOptions(boost::program_options::options_desc static_cast(defaultUserData) = boost::filesystem::path("?userdata?"); description.add_options() - ("config", bpo::value()->multitoken()->composing(), "additional config directories") + ("config", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "") + ->multitoken()->composing(), "additional config directories") ("user-data", bpo::value()->default_value(defaultUserData, ""), "set user data directory (used for saves, screenshots, etc)"); } From a294adcdaf03b5df3aae526ae3d78ae2883b0a1d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 6 Feb 2022 22:10:20 +0000 Subject: [PATCH 2080/2859] Proper support of `pairs` and `ipairs` in Lua; fix bug in `makeReadOnly`. --- apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/objectbindings.cpp | 2 +- apps/openmw_test_suite/lua/test_lua.cpp | 2 + components/lua/luastate.cpp | 61 ++++++++++++------- components/lua/scriptscontainer.cpp | 3 - .../reference/lua-scripting/overview.rst | 15 ++++- files/lua_api/openmw/core.lua | 5 -- 7 files changed, 57 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 084de7d5e1..06ba6d5c5a 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -49,7 +49,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 16; + api["API_REVISION"] = 17; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index ef0482f9fe..3ae9035271 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -72,7 +72,7 @@ namespace MWLua else throw std::runtime_error("Index out of range"); }; - listT["ipairs"] = [registry](const ListT& list) + listT[sol::meta_function::ipairs] = [registry](const ListT& list) { auto iter = [registry](const ListT& l, int64_t i) -> sol::optional> { diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index fe3cf14d25..539390f2af 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -39,6 +39,7 @@ return { -- should throw an error incorrectRequire = function() require('counter') end, modifySystemLib = function() math.sin = 5 end, + modifySystemLib2 = function() math.__index.sin = 5 end, rawsetSystemLib = function() rawset(math, 'sin', 5) end, callLoadstring = function() loadstring('print(1)') end, setSqr = function() require('sqrlib').sqr = math.sin end, @@ -119,6 +120,7 @@ return { // but read-only object can not be modified even with rawset EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)"); EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value"); + EXPECT_ERROR(LuaUtil::call(script["modifySystemLib2"]), "a nil value"); EXPECT_EQ(LuaUtil::getMutableFromReadOnly(LuaUtil::makeReadOnly(script)), script); } diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index bfe9cb513c..bda39d270d 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -58,20 +58,41 @@ namespace LuaUtil mLua["math"]["randomseed"] = []{}; mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; - mLua.script(R"(printToLog = function(name, ...) - local msg = name - for _, v in ipairs({...}) do - msg = msg .. '\t' .. tostring(v) + mLua["cmetatable"] = [](const sol::table& v) -> sol::object { return v[sol::metatable_key]; }; + mLua.script(R"( + local _pairs = pairs + local _ipairs = ipairs + local _tostring = tostring + local _write = writeToLog + local printToLog = function(name, ...) + local msg = name + for _, v in _ipairs({...}) do + msg = msg .. '\t' .. _tostring(v) + end + return _write(msg) end - return writeToLog(msg) - end)"); - mLua.script("printGen = function(name) return function(...) return printToLog(name, ...) end end"); + printGen = function(name) return function(...) return printToLog(name, ...) end end + + local _cmeta = cmetatable + function pairsForReadOnly(v) return _pairs(_cmeta(v).__index) end + function ipairsForReadOnly(v) return _ipairs(_cmeta(v).__index) end + )"); // Some fixes for compatibility between different Lua versions if (mLua["unpack"] == sol::nil) mLua["unpack"] = mLua["table"]["unpack"]; else if (mLua["table"]["unpack"] == sol::nil) mLua["table"]["unpack"] = mLua["unpack"]; + if (LUA_VERSION_NUM <= 501) + { + mLua.script(R"( + local _pairs = pairs + local _ipairs = ipairs + local _cmeta = cmetatable + pairs = function(v) return ((_cmeta(v) or v).__pairs or _pairs)(v) end + ipairs = function(v) return ((_cmeta(v) or v).__ipairs or _ipairs)(v) end + )"); + } mSandboxEnv = sol::table(mLua, sol::create); mSandboxEnv["_VERSION"] = mLua["_VERSION"]; @@ -106,24 +127,22 @@ namespace LuaUtil if (table.is()) return table; // it is already userdata, no sense to wrap it again - lua_State* lua = table.lua_state(); - table[sol::meta_function::index] = table; - lua_newuserdata(lua, 0); - sol::stack::push(lua, std::move(table)); - lua_setmetatable(lua, -2); - return sol::stack::pop(lua); + lua_State* luaState = table.lua_state(); + sol::state_view lua(luaState); + sol::table meta(lua, sol::create); + meta["__index"] = table; + meta["__pairs"] = lua["pairsForReadOnly"]; + meta["__ipairs"] = lua["ipairsForReadOnly"]; + + lua_newuserdata(luaState, 0); + sol::stack::push(luaState, meta); + lua_setmetatable(luaState, -2); + return sol::stack::pop(luaState); } sol::table getMutableFromReadOnly(const sol::userdata& ro) { - lua_State* lua = ro.lua_state(); - sol::stack::push(lua, ro); - int ok = lua_getmetatable(lua, -1); - assert(ok); - (void)ok; - sol::table res = sol::stack::pop(lua); - lua_pop(lua, 1); - return res; + return ro[sol::metatable_key].get()["__index"]; } void LuaState::addCommonPackage(std::string packageName, sol::object package) diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 780d10cebd..11ca28de08 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -439,10 +439,7 @@ namespace LuaUtil mEventHandlers.clear(); mSimulationTimersQueue.clear(); mGameTimersQueue.clear(); - mPublicInterfaces.clear(); - // Assigned by LuaUtil::makeReadOnly, but `clear` removes it, so we need to assign it again. - mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces; } ScriptsContainer::Script& ScriptsContainer::getScript(int scriptId) diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 1bfdf33c42..e38e8948ec 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -4,7 +4,7 @@ Overview of Lua scripting Language and sandboxing ======================= -OpenMW supports scripts written in Lua 5.1. +OpenMW supports scripts written in Lua 5.1 with some extensions (see below) from Lua 5.2. There are no plans to switch to any newer version of the language, because newer versions are not supported by LuaJIT. Here are starting points for learning Lua: @@ -24,11 +24,22 @@ These libraries are loaded automatically and are always available. Allowed `basic functions `__: ``assert``, ``error``, ``ipairs``, ``next``, ``pairs``, ``pcall``, ``print``, ``select``, ``tonumber``, ``tostring``, ``type``, ``unpack``, ``xpcall``, ``rawequal``, ``rawget``, ``rawset``, ``getmetatable``, ``setmetatable``. +Supported Lua 5.2 features: + +- ``goto`` and ``::labels::``; +- hex escapes ``\x3F`` and ``\*`` escape in strings; +- ``math.log(x [,base])``; +- ``string.rep(s, n [,sep])``; +- in ``string.format()``: ``%q`` is reversible, ``%s`` uses ``__tostring``, ``%a`` and ``%A`` are added; +- String matching pattern ``%g``; +- ``__pairs`` and ``__ipairs`` metamethods; +- Function ``table.unpack`` (alias to Lua 5.1 ``unpack``). + Loading libraries with ``require('library_name')`` is allowed, but limited. It works this way: 1. If `library_name` is one of the standard libraries, then return the library. 2. If `library_name` is one of the built-in `API packages`_, then return the package. -3. Otherwise search for a Lua source file with such name in :ref:`data folders `. For example ``require('my_lua_library.something')`` will try to open the file ``my_lua_library/something.lua``. +3. Otherwise search for a Lua source file with such name in :ref:`data folders `. For example ``require('my_lua_library.something')`` will try to open one of the files ``my_lua_library/something.lua`` or ``my_lua_library/something/init.lua``. Loading DLLs and precompiled Lua files is intentionally prohibited for compatibility and security reasons. diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 07309edca8..e7e657a853 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -293,11 +293,6 @@ -- @type ObjectList -- @extends #list<#GameObject> -------------------------------------------------------------------------------- --- Create iterator. --- @function [parent=#ObjectList] ipairs --- @param self - ------------------------------------------------------------------------------- -- Filter list with a Query. -- @function [parent=#ObjectList] select From bbc9c53423a414b702cb4f951db5b7f5439691f2 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 7 Feb 2022 11:51:59 -0800 Subject: [PATCH 2081/2859] support morrowind stenciling --- CHANGELOG.md | 1 + apps/openmw/mwrender/npcanimation.cpp | 6 ++-- apps/openmw/mwrender/postprocessor.cpp | 18 +++++----- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ components/nifosg/nifloader.cpp | 44 ++++++++++++++++++++--- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a181a35098..64af236875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,6 +129,7 @@ Feature #6288: Preserve the "blocked" record flag for referenceable objects. Feature #6380: Commas are treated as whitespace in vanilla Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference + Feature #6443: NiStencilProperty is not fully supported Feature #6534: Shader-based object texture blending Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 308762045c..47aeaf53af 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -333,12 +333,12 @@ public: osg::FrameBufferAttachment(postProcessor->getFirstPersonRBProxy()).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); - glClear(GL_DEPTH_BUFFER_BIT); + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // color accumulation pass bin->drawImplementation(renderInfo, previous); auto primaryFBO = postProcessor->getMsaaFbo() ? postProcessor->getMsaaFbo() : postProcessor->getFbo(); - primaryFBO->getAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); + primaryFBO->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); state->pushStateSet(mStateSet); state->apply(); @@ -349,7 +349,7 @@ public: else { // fallback to standard depth clear when we are not rendering our main scene via an intermediate FBO - glClear(GL_DEPTH_BUFFER_BIT); + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); bin->drawImplementation(renderInfo, previous); } } diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 6db01444ad..afdec4f1b5 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -1,5 +1,7 @@ #include "postprocessor.hpp" +#include + #include #include #include @@ -155,7 +157,7 @@ namespace MWRender PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode) : mViewer(viewer) , mRootNode(new osg::Group) - , mDepthFormat(GL_DEPTH_COMPONENT24) + , mDepthFormat(GL_DEPTH24_STENCIL8_EXT) { bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); @@ -183,9 +185,9 @@ namespace MWRender if (SceneUtil::AutoDepth::isReversed()) { if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) - mDepthFormat = GL_DEPTH_COMPONENT32F; + mDepthFormat = GL_DEPTH32F_STENCIL8; else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) - mDepthFormat = GL_DEPTH_COMPONENT32F_NV; + mDepthFormat = GL_DEPTH32F_STENCIL8_NV; else { // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. @@ -213,7 +215,7 @@ namespace MWRender mViewer->getCamera()->addCullCallback(new CullCallback); mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); - mViewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, mDepthTex); + mViewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mDepthTex); mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); mViewer->getCamera()->setUserData(this); @@ -236,7 +238,7 @@ namespace MWRender mFbo = new osg::FrameBufferObject; mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex)); - mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(mDepthTex)); + mFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTex)); // When MSAA is enabled we must first render to a render buffer, then // blit the result to the FBO which is either passed to the main frame @@ -247,7 +249,7 @@ namespace MWRender osg::ref_ptr colorRB = new osg::RenderBuffer(width, height, mSceneTex->getInternalFormat(), samples); osg::ref_ptr depthRB = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); - mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthRB)); + mMsaaFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(depthRB)); } if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY")) @@ -264,8 +266,8 @@ namespace MWRender { mDepthTex = new osg::Texture2D; mDepthTex->setTextureSize(width, height); - mDepthTex->setSourceFormat(GL_DEPTH_COMPONENT); - mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT : GL_UNSIGNED_INT); + mDepthTex->setSourceFormat(GL_DEPTH_STENCIL_EXT); + mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_UNSIGNED_INT_24_8_EXT); mDepthTex->setInternalFormat(mDepthFormat); mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 90fe0877ec..4ec44f0141 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -538,6 +538,8 @@ namespace MWRender SceneUtil::setCameraClearDepth(mViewer->getCamera()); updateProjectionMatrix(); + + mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } RenderingManager::~RenderingManager() diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 36f474a223..db1dbc4d2c 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -218,6 +218,7 @@ namespace NifOsg bool mHasNightDayLabel = false; bool mHasHerbalismLabel = false; + bool mHasStencilProperty = false; // This is used to queue emitters that weren't attached to their node yet. std::vector>> mEmitterQueue; @@ -309,6 +310,18 @@ namespace NifOsg if (mHasHerbalismLabel) created->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); + // Morrowind is particular about draw order with stenciling, there is no real way around not using traversal order drawing. + static osg::ref_ptr traversalOrderBin; + + if (!traversalOrderBin) + { + traversalOrderBin = new osgUtil::RenderBin(osgUtil::RenderBin::TRAVERSAL_ORDER); + osgUtil::RenderBin::addRenderBinPrototype("TraversalOrder", traversalOrderBin); + } + + if (mHasStencilProperty) + created->getOrCreateStateSet()->setRenderBinDetails(2, "TraversalOrder"); + // Attach particle emitters to their nodes which should all be loaded by now. handleQueuedParticleEmitters(created, nif); @@ -334,6 +347,21 @@ namespace NifOsg void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { const Nif::PropertyList& props = nifNode->props; + + bool hasStencilProperty = false; + + for (size_t i = 0; i recType == Nif::RC_NiStencilProperty) + { + hasStencilProperty = true; + break; + } + } + for (size_t i = 0; i recIndex == mFirstRootTextureIndex) applyTo->setUserValue("overrideFx", 1); } - handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags); + handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags, hasStencilProperty); } } auto geometry = dynamic_cast(nifNode); // NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property if (geometry && !geometry->shaderprop.empty()) - handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags); + handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, hasStencilProperty); } static void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags) @@ -1347,7 +1375,7 @@ namespace NifOsg case 4: return osg::Stencil::GREATER; case 5: return osg::Stencil::NOTEQUAL; case 6: return osg::Stencil::GEQUAL; - case 7: return osg::Stencil::NEVER; // NifSkope says this is GL_ALWAYS, but in MW it's GL_NEVER + case 7: return osg::Stencil::ALWAYS; default: Log(Debug::Info) << "Unexpected stencil function: " << func << " in " << mFilename; return osg::Stencil::NEVER; @@ -1772,7 +1800,7 @@ namespace NifOsg } void handleProperty(const Nif::Property *property, - osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags, bool hasStencilProperty) { switch (property->recType) { @@ -1800,6 +1828,7 @@ namespace NifOsg if (stencilprop->data.enabled != 0) { + mHasStencilProperty = true; osg::ref_ptr stencil = new osg::Stencil; stencil->setFunction(getStencilFunction(stencilprop->data.compareFunc), stencilprop->data.stencilRef, stencilprop->data.stencilMask); stencil->setStencilFailOperation(getStencilOperation(stencilprop->data.failAction)); @@ -1831,7 +1860,9 @@ namespace NifOsg osg::ref_ptr depth = new osg::Depth; // Depth write flag depth->setWriteMask((zprop->flags>>1)&1); - // Morrowind ignores depth test function + // Morrowind ignores depth test function, unless a NiStencilProperty is present, in which case it uses a fixed depth function of GL_ALWAYS. + if (hasStencilProperty) + depth->setFunction(osg::Depth::ALWAYS); depth = shareAttribute(depth); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); break; @@ -2049,7 +2080,10 @@ namespace NifOsg bool noSort = (alphaprop->flags>>13)&1; if (!noSort) + { node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + node->getOrCreateStateSet()->setNestRenderBins(false); + } else node->getOrCreateStateSet()->setRenderBinToInherit(); } From 8c2c322d9237b4ce15a75e9ddec99e6079c2fe0a Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 7 Feb 2022 14:35:15 -0800 Subject: [PATCH 2082/2859] add stencil to water RTTs, reword some comments --- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/screenshotmanager.cpp | 2 +- components/nifosg/nifloader.cpp | 12 ++---------- components/sceneutil/rtt.cpp | 14 +++++++------- components/sceneutil/rtt.hpp | 2 +- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 66d41a47e4..a3c8d6b2ce 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -187,7 +187,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 5c3d925c9b..90e6eec124 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -348,7 +348,7 @@ namespace MWRender rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson)); - rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); renderCameraToImage(rttCamera.get(),image,w,h); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index db1dbc4d2c..87ffeb232c 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -310,17 +310,9 @@ namespace NifOsg if (mHasHerbalismLabel) created->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); - // Morrowind is particular about draw order with stenciling, there is no real way around not using traversal order drawing. - static osg::ref_ptr traversalOrderBin; - - if (!traversalOrderBin) - { - traversalOrderBin = new osgUtil::RenderBin(osgUtil::RenderBin::TRAVERSAL_ORDER); - osgUtil::RenderBin::addRenderBinPrototype("TraversalOrder", traversalOrderBin); - } - + // When dealing with stencil buffer, draw order is especially sensitive. Make sure such objects are drawn with traversal order. if (mHasStencilProperty) - created->getOrCreateStateSet()->setRenderBinDetails(2, "TraversalOrder"); + created->getOrCreateStateSet()->setRenderBinDetails(2, "TraversalOrderBin"); // Attach particle emitters to their nodes which should all be loaded by now. handleQueuedParticleEmitters(created, nif); diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index 0765a2e835..9fe9a44516 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -50,7 +50,7 @@ namespace SceneUtil osg::Texture* RTTNode::getDepthTexture(osgUtil::CullVisitor* cv) { - return getViewDependentData(cv)->mCamera->getBufferAttachmentMap()[osg::Camera::DEPTH_BUFFER]._texture; + return getViewDependentData(cv)->mCamera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._texture; } RTTNode::ViewDependentData* RTTNode::getViewDependentData(osgUtil::CullVisitor* cv) @@ -67,7 +67,7 @@ namespace SceneUtil mViewDependentDataMap[cv]->mCamera = camera; camera->setRenderOrder(osg::Camera::PRE_RENDER, mRenderOrderNum); - camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); camera->setViewport(0, 0, mTextureWidth, mTextureHeight); SceneUtil::setCameraClearDepth(camera); @@ -88,18 +88,18 @@ namespace SceneUtil SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, colorBuffer); } - if (camera->getBufferAttachmentMap().count(osg::Camera::DEPTH_BUFFER) == 0) + if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) { auto depthBuffer = new osg::Texture2D; depthBuffer->setTextureSize(mTextureWidth, mTextureHeight); - depthBuffer->setSourceFormat(GL_DEPTH_COMPONENT); - depthBuffer->setInternalFormat(GL_DEPTH_COMPONENT24); + depthBuffer->setSourceFormat(GL_DEPTH_STENCIL_EXT); + depthBuffer->setInternalFormat(GL_DEPTH24_STENCIL8_EXT); depthBuffer->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); depthBuffer->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - depthBuffer->setSourceType(GL_UNSIGNED_INT); + depthBuffer->setSourceType(GL_UNSIGNED_INT_24_8_EXT); depthBuffer->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); depthBuffer->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::DEPTH_BUFFER, depthBuffer); + camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, depthBuffer); } } diff --git a/components/sceneutil/rtt.hpp b/components/sceneutil/rtt.hpp index 7adfa098eb..c587006c96 100644 --- a/components/sceneutil/rtt.hpp +++ b/components/sceneutil/rtt.hpp @@ -27,7 +27,7 @@ namespace SceneUtil /// @par Camera settings should be effectuated by overriding the setDefaults() and apply() methods, following a pattern similar to SceneUtil::StateSetUpdater /// @par When using the RTT texture in your statesets, it is recommended to use SceneUtil::StateSetUpdater as a cull callback to handle this as the appropriate /// textures can be retrieved during SceneUtil::StateSetUpdater::Apply() - /// @par For any of COLOR_BUFFER or DEPTH_BUFFER not added during setDefaults(), RTTNode will attach a default buffer. The default color buffer has an internal format of GL_RGB. + /// @par For any of COLOR_BUFFER or PACKED_DEPTH_STENCIL_BUFFER not added during setDefaults(), RTTNode will attach a default buffer. The default color buffer has an internal format of GL_RGB. /// The default depth buffer has internal format GL_DEPTH_COMPONENT24, source format GL_DEPTH_COMPONENT, and source type GL_UNSIGNED_INT. Default wrap is CLAMP_TO_EDGE and filter LINEAR. class RTTNode : public osg::Node { From 88f02913d53b922e038e69cf97f140cab9daf4ac Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 7 Feb 2022 19:46:42 -0800 Subject: [PATCH 2083/2859] use vertex emitters on array particle controllers when appropriate --- CHANGELOG.md | 1 + components/nif/node.hpp | 3 + components/nifosg/nifloader.cpp | 24 ++++--- components/nifosg/particle.cpp | 118 ++++++++++++++++++++++++++------ components/nifosg/particle.hpp | 15 +++- 5 files changed, 129 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a181a35098..16509efca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -130,6 +130,7 @@ Feature #6380: Commas are treated as whitespace in vanilla Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference Feature #6534: Shader-based object texture blending + Feature #6592: Missing support for NiTriShape particle emitters Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp Task #6553: Simplify interpreter instruction registration diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 31eb63144c..fb57e07739 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -211,6 +211,9 @@ struct NiNode : Node enum ControllerFlags { ControllerFlag_Active = 0x8 }; + enum BSPArrayController { + BSPArrayController_AtVertex = 0x10 + }; void read(NIFStream *nif) override { diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 36f474a223..d003940a93 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -51,8 +51,7 @@ namespace void getAllNiNodes(const Nif::Node* node, std::vector& outIndices) { - const Nif::NiNode* ninode = dynamic_cast(node); - if (ninode) + if (const Nif::NiNode* ninode = dynamic_cast(node)) { outIndices.push_back(ninode->recIndex); for (unsigned int i=0; ichildren.length(); ++i) @@ -976,7 +975,8 @@ namespace NifOsg osg::ref_ptr handleParticleEmitter(const Nif::NiParticleSystemController* partctrl) { std::vector targets; - if (partctrl->recType == Nif::RC_NiBSPArrayController) + const bool atVertex = (partctrl->flags & Nif::NiNode::BSPArrayController_AtVertex); + if (partctrl->recType == Nif::RC_NiBSPArrayController && !atVertex) { getAllNiNodes(partctrl->emitter.getPtr(), targets); } @@ -1000,12 +1000,20 @@ namespace NifOsg partctrl->lifetime, partctrl->lifetimeRandom); emitter->setShooter(shooter); - osgParticle::BoxPlacer* placer = new osgParticle::BoxPlacer; - placer->setXRange(-partctrl->offsetRandom.x() / 2.f, partctrl->offsetRandom.x() / 2.f); - placer->setYRange(-partctrl->offsetRandom.y() / 2.f, partctrl->offsetRandom.y() / 2.f); - placer->setZRange(-partctrl->offsetRandom.z() / 2.f, partctrl->offsetRandom.z() / 2.f); + if (atVertex && (partctrl->recType == Nif::RC_NiBSPArrayController)) + { + emitter->setUseGeometryEmitter(true); + emitter->setGeometryEmitterTarget(partctrl->emitter->recIndex); + } + else + { + osgParticle::BoxPlacer* placer = new osgParticle::BoxPlacer; + placer->setXRange(-partctrl->offsetRandom.x() / 2.f, partctrl->offsetRandom.x() / 2.f); + placer->setYRange(-partctrl->offsetRandom.y() / 2.f, partctrl->offsetRandom.y() / 2.f); + placer->setZRange(-partctrl->offsetRandom.z() / 2.f, partctrl->offsetRandom.z() / 2.f); + emitter->setPlacer(placer); + } - emitter->setPlacer(placer); return emitter; } diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 7821b9c2b8..493143a7a3 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -11,6 +11,52 @@ #include #include #include +#include +#include + +namespace +{ + class FindFirstGeometry : public osg::NodeVisitor + { + public: + FindFirstGeometry() + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mGeometry(nullptr) + { + } + + void apply(osg::Node& node) override + { + if (mGeometry) + return; + + traverse(node); + } + + void apply(osg::Drawable& drawable) override + { + if (auto morph = dynamic_cast(&drawable)) + { + mGeometry = morph->getSourceGeometry(); + return; + } + else if (auto rig = dynamic_cast(&drawable)) + { + mGeometry = rig->getSourceGeometry(); + return; + } + + traverse(drawable); + } + + void apply(osg::Geometry& geometry) override + { + mGeometry = &geometry; + } + + osg::Geometry* mGeometry; + }; +} namespace NifOsg { @@ -264,6 +310,8 @@ void GravityAffector::operate(osgParticle::Particle *particle, double dt) Emitter::Emitter() : osgParticle::Emitter() + , mUseGeometryEmitter(false) + , mGeometryEmitterTarget(std::nullopt) { } @@ -274,29 +322,19 @@ Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op) , mShooter(copy.mShooter) // need a deep copy because the remainder is stored in the object , mCounter(static_cast(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL))) + , mUseGeometryEmitter(copy.mUseGeometryEmitter) + , mGeometryEmitterTarget(copy.mGeometryEmitterTarget) + , mCachedGeometryEmitter(copy.mCachedGeometryEmitter) { } Emitter::Emitter(const std::vector &targets) : mTargets(targets) + , mUseGeometryEmitter(false) + , mGeometryEmitterTarget(std::nullopt) { } -void Emitter::setShooter(osgParticle::Shooter *shooter) -{ - mShooter = shooter; -} - -void Emitter::setPlacer(osgParticle::Placer *placer) -{ - mPlacer = placer; -} - -void Emitter::setCounter(osgParticle::Counter *counter) -{ - mCounter = counter; -} - void Emitter::emitParticles(double dt) { int n = mCounter->numParticlesToCreate(dt); @@ -316,21 +354,53 @@ void Emitter::emitParticles(double dt) const osg::Matrix& ltw = getLocalToWorldMatrix(); osg::Matrix emitterToPs = ltw * worldToPs; - if (!mTargets.empty()) + osg::ref_ptr geometryVertices = nullptr; + + if (mUseGeometryEmitter || !mTargets.empty()) { - int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1); - int randomRecIndex = mTargets[randomIndex]; + int recIndex; + + if (mUseGeometryEmitter) + { + if (!mGeometryEmitterTarget.has_value()) + return; + + recIndex = mGeometryEmitterTarget.value(); + } + else + { + int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1); + recIndex = mTargets[randomIndex]; + } // we could use a map here for faster lookup - FindGroupByRecIndex visitor(randomRecIndex); + FindGroupByRecIndex visitor(recIndex); getParent(0)->accept(visitor); if (!visitor.mFound) { - Log(Debug::Info) << "Can't find emitter node" << randomRecIndex; + Log(Debug::Info) << "Can't find emitter node" << recIndex; return; } + if (mUseGeometryEmitter) + { + if (!mCachedGeometryEmitter.lock(geometryVertices)) + { + FindFirstGeometry geometryVisitor; + visitor.mFound->accept(geometryVisitor); + + if (geometryVisitor.mGeometry) + { + if (auto* vertices = dynamic_cast(geometryVisitor.mGeometry->getVertexArray())) + { + mCachedGeometryEmitter = osg::observer_ptr(vertices); + geometryVertices = vertices; + } + } + } + } + osg::NodePath path = visitor.mFoundPath; path.erase(path.begin()); emitterToPs = osg::computeLocalToWorld(path) * emitterToPs; @@ -338,12 +408,18 @@ void Emitter::emitParticles(double dt) emitterToPs.orthoNormalize(emitterToPs); + if (mUseGeometryEmitter && (!geometryVertices.valid() || geometryVertices->empty())) + return; + for (int i=0; icreateParticle(nullptr); if (P) { - mPlacer->place(P); + if (mUseGeometryEmitter) + P->setPosition((*geometryVertices)[Misc::Rng::rollDice(geometryVertices->getNumElements())]); + else if (mPlacer) + mPlacer->place(P); mShooter->shoot(P); diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 8b724545f4..9e68cdf347 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_NIFOSG_PARTICLE_H #define OPENMW_COMPONENTS_NIFOSG_PARTICLE_H +#include + #include #include #include @@ -233,9 +235,12 @@ namespace NifOsg void emitParticles(double dt) override; - void setShooter(osgParticle::Shooter* shooter); - void setPlacer(osgParticle::Placer* placer); - void setCounter(osgParticle::Counter* counter); + void setShooter(osgParticle::Shooter* shooter) { mShooter = shooter; } + void setPlacer(osgParticle::Placer* placer) { mPlacer = placer; } + void setCounter(osgParticle::Counter* counter) { mCounter = counter;} + + void setUseGeometryEmitter(bool useGeometryEmitter) { mUseGeometryEmitter = useGeometryEmitter; } + void setGeometryEmitterTarget(std::optional recIndex) { mGeometryEmitterTarget = recIndex; } private: // NIF Record indices @@ -244,6 +249,10 @@ namespace NifOsg osg::ref_ptr mPlacer; osg::ref_ptr mShooter; osg::ref_ptr mCounter; + + bool mUseGeometryEmitter; + std::optional mGeometryEmitterTarget; + osg::observer_ptr mCachedGeometryEmitter; }; } From a05e029aa040d8af4b27eacd0a250a46b7d34be8 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Tue, 8 Feb 2022 13:49:05 -0800 Subject: [PATCH 2084/2859] search for stencil enabled flag correctly --- components/nifosg/nifloader.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 87ffeb232c..11f8fb4661 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -349,8 +349,12 @@ namespace NifOsg if (props[i].getPtr()->recType == Nif::RC_NiStencilProperty) { - hasStencilProperty = true; - break; + const Nif::NiStencilProperty* stencilprop = static_cast(props[i].getPtr()); + if (stencilprop->data.enabled != 0) + { + hasStencilProperty = true; + break; + } } } From 27d2daabc15d114229d1b19e53b7fdf1cc658f92 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Tue, 8 Feb 2022 14:12:17 -0800 Subject: [PATCH 2085/2859] move depth define to header, missing on mac --- components/sceneutil/depth.cpp | 4 ---- components/sceneutil/depth.hpp | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp index 5d53b97726..ee95f8c7ec 100644 --- a/components/sceneutil/depth.cpp +++ b/components/sceneutil/depth.cpp @@ -6,10 +6,6 @@ #include -#ifndef GL_DEPTH32F_STENCIL8_NV -#define GL_DEPTH32F_STENCIL8_NV 0x8DAC -#endif - namespace SceneUtil { void setCameraClearDepth(osg::Camera* camera) diff --git a/components/sceneutil/depth.hpp b/components/sceneutil/depth.hpp index 11a863ef7a..1f4ba55297 100644 --- a/components/sceneutil/depth.hpp +++ b/components/sceneutil/depth.hpp @@ -5,6 +5,10 @@ #include "util.hpp" +#ifndef GL_DEPTH32F_STENCIL8_NV +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#endif + namespace SceneUtil { // Sets camera clear depth to 0 if reversed depth buffer is in use, 1 otherwise. From ee2235c5c1d83a1abb0e319b817a8f26d21132cf Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 8 Feb 2022 23:43:44 +0000 Subject: [PATCH 2086/2859] Copy scripts/omw/ai.lua to vfs (was added in !1604, but I forgot to modify CMakeLists.txt) --- files/builtin_scripts/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 1ef67a2e15..90c3c2a503 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -17,6 +17,7 @@ set(DDIRRELATIVE resources/vfs/openmw_aux) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${LUA_AUX_FILES}") set(LUA_SCRIPTS_FILES + scripts/omw/ai.lua scripts/omw/camera.lua scripts/omw/head_bobbing.lua scripts/omw/third_person.lua From 85053941b358a9398c80b053b9219c95e6d23a61 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 9 Feb 2022 13:55:32 +0300 Subject: [PATCH 2087/2859] Properly postprocess NiSortAdjustNode --- components/nif/node.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index fb57e07739..837a4a0e30 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -460,6 +460,7 @@ struct NiSortAdjustNode : NiNode } void post(NIFFile *nif) override { + NiNode::post(nif); mSubSorter.post(nif); } }; From 5aef14eccdc944478f4efe30bb858eebe47bbefd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 20:28:27 +0100 Subject: [PATCH 2088/2859] Prevent division by 0 --- apps/openmw/mwdialogue/filter.cpp | 11 ++++++----- apps/openmw/mwgui/hud.cpp | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index a47334a2dd..7ae9f14a7a 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -257,9 +257,9 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); - - float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() / - player.getClass().getCreatureStats (player).getHealth().getModified(); + float ratio = player.getClass().getCreatureStats(player).getHealth().getModified(); + if(ratio > 0) + ratio = player.getClass().getCreatureStats(player).getHealth().getCurrent() / ratio; return select.selectCompare (static_cast(ratio*100)); } @@ -276,8 +276,9 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_HealthPercent: { - float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() / - mActor.getClass().getCreatureStats (mActor).getHealth().getModified(); + float ratio = mActor.getClass().getCreatureStats(mActor).getHealth().getModified(); + if(ratio > 0) + ratio = mActor.getClass().getCreatureStats(mActor).getHealth().getCurrent() / ratio; return select.selectCompare (static_cast(ratio*100)); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 4ed54c38d6..5af85b34ae 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -608,7 +608,8 @@ namespace MWGui mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) - mEnemyHealth->setProgressPosition(static_cast(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100)); + float health = stats.getHealth().getModified(); + mEnemyHealth->setProgressPosition(health == 0.f ? 0 : static_cast(stats.getHealth().getCurrent() / health * 100)); static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) From 4e52c96cf58e7b0c8b59db490e12c850bed2f7ca Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 20:31:27 +0100 Subject: [PATCH 2089/2859] Make Set/Mod[DynamicStat] work with negative values as in vanilla --- apps/openmw/mwgui/statswindow.cpp | 2 +- apps/openmw/mwscript/statsextensions.cpp | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 0830af0744..49ee7698ee 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -179,7 +179,7 @@ namespace MWGui void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { int current = static_cast(value.getCurrent()); - int modified = static_cast(value.getModified()); + int modified = static_cast(value.getModified(false)); // Fatigue can be negative if (id != "FBar") diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 78195b3693..f127b2bc4b 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -218,8 +218,8 @@ namespace MWScript MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); - stat.setModified (value, 0); - stat.setCurrent(value); + stat.setBase(value); + stat.setCurrent(value, true); ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } @@ -259,19 +259,18 @@ namespace MWScript } } - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); + MWMechanics::DynamicStat stat = stats.getDynamic(mIndex); - MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) - .getDynamic (mIndex)); + float current = stat.getCurrent(); + float base = diff + stat.getBase(); + if(mIndex != 2) + base = std::max(base, 0.f); + stat.setBase(base); + stat.setCurrent(diff + current, true); - stat.setModified (diff + stat.getModified(), 0); - stat.setCurrentModified (diff + stat.getCurrentModified()); - - stat.setCurrent (diff + current); - - ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); + stats.setDynamic (mIndex, stat); } }; From dc495a685ae50cebfa96637e8cdcf8370aa9c4b6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 20:32:59 +0100 Subject: [PATCH 2090/2859] Remove a member variable that doesn't get saved and remove fortify maximum health code --- apps/openmw/mwgui/race.cpp | 2 +- apps/openmw/mwgui/widgets.hpp | 4 - apps/openmw/mwmechanics/creaturestats.cpp | 15 +- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwmechanics/spelleffects.cpp | 3 +- apps/openmw/mwmechanics/stat.cpp | 168 +----------------- apps/openmw/mwmechanics/stat.hpp | 56 ++---- apps/openmw/mwworld/magiceffects.cpp | 6 +- 8 files changed, 36 insertions(+), 222 deletions(-) diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index d30eb65eb3..efa4cea981 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -399,7 +399,7 @@ namespace MWGui skillWidget = mSkillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default, std::string("Skill") + MyGUI::utility::toString(i)); skillWidget->setSkillNumber(skillId); - skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast(race->mData.mBonus[i].mBonus))); + skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast(race->mData.mBonus[i].mBonus), 0.f)); ToolTips::createSkillToolTip(skillWidget, skillId); diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 2f27cc029c..e430872487 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -181,8 +181,6 @@ namespace MWGui public: MWSpell(); - typedef MWMechanics::Stat SpellValue; - void setSpellId(const std::string &id); /** @@ -215,8 +213,6 @@ namespace MWGui public: MWEffectList(); - typedef MWMechanics::Stat EnchantmentValue; - enum EffectFlags { EF_NoTarget = 0x01, // potions have no target (target is always the player) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 9611f5b34a..a53caf1451 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -26,8 +26,6 @@ namespace MWMechanics mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0) , mAttackingOrSpell(false) { - for (int i=0; i<4; ++i) - mAiSettings[i] = 0; } const AiSequence& CreatureStats::getAiSequence() const @@ -158,9 +156,8 @@ namespace MWMechanics float agility = getAttribute(ESM::Attribute::Agility).getModified(); float endurance = getAttribute(ESM::Attribute::Endurance).getModified(); DynamicStat fatigue = getFatigue(); - float diff = (strength+willpower+agility+endurance) - fatigue.getBase(); float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0; - fatigue.setModified(fatigue.getModified() + diff, 0); + fatigue.setBase(std::max(0.f, strength + willpower + agility + endurance)); fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio, false, true); setFatigue(fatigue); } @@ -196,8 +193,6 @@ namespace MWMechanics mDead = true; - mDynamic[index].setModifier(0); - mDynamic[index].setCurrentModifier(0); mDynamic[index].setCurrent(0); } } @@ -281,10 +276,7 @@ namespace MWMechanics { if (mDead) { - if (mDynamic[0].getModified() < 1) - mDynamic[0].setModified(1, 0); - - mDynamic[0].setCurrent(mDynamic[0].getModified()); + mDynamic[0].setCurrent(mDynamic[0].getBase()); mDead = false; mDeathAnimationFinished = false; } @@ -415,9 +407,8 @@ namespace MWMechanics double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; DynamicStat magicka = getMagicka(); - float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0; - magicka.setModified(magicka.getModified() + diff, 0); + magicka.setBase(magickaFactor * intelligence); magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); setMagicka(magicka); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0816445271..5c41e8142d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -255,7 +255,7 @@ namespace MWMechanics for (int i=0; i<3; ++i) { DynamicStat stat = creatureStats.getDynamic (i); - stat.setCurrent (stat.getModified()); + stat.setCurrent(stat.getModified()); creatureStats.setDynamic (i, stat); } @@ -1450,7 +1450,7 @@ namespace MWMechanics std::string script = target.getClass().getScript(target); if (!script.empty() && target.getRefData().getLocals().hasVar(script, "onpchitme") && attacker == player) { - int fight = std::max(0, target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified()); + int fight = target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified(); peaceful = (fight == 0); } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index d0584791a5..42c27c7931 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -64,8 +64,7 @@ namespace auto& creatureStats = target.getClass().getCreatureStats(target); auto stat = creatureStats.getDynamic(index); float current = stat.getCurrent(); - stat.setModified(stat.getModified() + magnitude, 0); - stat.setCurrentModified(stat.getCurrentModified() + magnitude); + stat.setBase(std::max(0.f, stat.getBase() + magnitude)); stat.setCurrent(current + magnitude); creatureStats.setDynamic(index, stat); } diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 263c867aea..9d62eb7873 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -5,174 +5,41 @@ namespace MWMechanics { template - Stat::Stat() : mBase (0), mModified (0), mCurrentModified (0) {} + Stat::Stat() : mBase (0), mModifier (0) {} template - Stat::Stat(T base) : mBase (base), mModified (base), mCurrentModified (base) {} - template - Stat::Stat(T base, T modified) : mBase (base), mModified (modified), mCurrentModified (modified) {} - - template - const T& Stat::getBase() const - { - return mBase; - } + Stat::Stat(T base, T modified) : mBase (base), mModifier (modified) {} template T Stat::getModified(bool capped) const { - if(!capped) - return mModified; - return std::max(static_cast(0), mModified); - } - - template - T Stat::getCurrentModified() const - { - return mCurrentModified; - } - - template - T Stat::getModifier() const - { - return mModified-mBase; - } - - template - T Stat::getCurrentModifier() const - { - return mCurrentModified - mModified; - } - - template - void Stat::set (const T& value) - { - T diff = value - mBase; - mBase = mModified = value; - mCurrentModified += diff; - } - - template - void Stat::setBase (const T& value) - { - T diff = value - mBase; - mBase = value; - mModified += diff; - mCurrentModified += diff; - } - - template - void Stat::setModified (T value, const T& min, const T& max) - { - T diff = value - mModified; - - if (mBase+diffmax) - { - value = max + (mModified - mBase); - diff = value - mModified; - } - - mModified = value; - mBase += diff; - mCurrentModified += diff; - } - - template - void Stat::setCurrentModified(T value) - { - mCurrentModified = value; - } - - template - void Stat::setModifier (const T& modifier) - { - mModified = mBase + modifier; - } - - template - void Stat::setCurrentModifier(const T& modifier) - { - mCurrentModified = mModified + modifier; + if(capped) + return std::max({}, mModifier + mBase); + return mModifier + mBase; } template void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; - state.mMod = mCurrentModified; + state.mMod = mModifier; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; - mModified = state.mBase; - mCurrentModified = state.mMod; + mModifier = state.mMod; } template - DynamicStat::DynamicStat() : mStatic (0), mCurrent (0) {} + DynamicStat::DynamicStat() : mStatic(0, 0), mCurrent(0) {} template - DynamicStat::DynamicStat(T base) : mStatic (base), mCurrent (base) {} + DynamicStat::DynamicStat(T base) : mStatic(base, 0), mCurrent(base) {} template DynamicStat::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {} template DynamicStat::DynamicStat(const Stat &stat, T current) : mStatic(stat), mCurrent (current) {} - - template - const T& DynamicStat::getBase() const - { - return mStatic.getBase(); - } - template - T DynamicStat::getModified() const - { - return mStatic.getModified(); - } - template - T DynamicStat::getCurrentModified() const - { - return mStatic.getCurrentModified(); - } - - template - const T& DynamicStat::getCurrent() const - { - return mCurrent; - } - - template - void DynamicStat::set (const T& value) - { - mStatic.set (value); - mCurrent = value; - } - template - void DynamicStat::setBase (const T& value) - { - mStatic.setBase (value); - - if (mCurrent>getModified()) - mCurrent = getModified(); - } - template - void DynamicStat::setModified (T value, const T& min, const T& max) - { - mStatic.setModified (value, min, max); - - if (mCurrent>getModified()) - mCurrent = getModified(); - } - template - void DynamicStat::setCurrentModified(T value) - { - mStatic.setCurrentModified(value); - } template void DynamicStat::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified) { @@ -197,23 +64,6 @@ namespace MWMechanics mCurrent = 0; } } - template - void DynamicStat::setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero) - { - T diff = modifier - mStatic.getModifier(); - mStatic.setModifier (modifier); - setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); - } - - template - void DynamicStat::setCurrentModifier(const T& modifier, bool allowCurrentDecreaseBelowZero) - { - T diff = modifier - mStatic.getCurrentModifier(); - mStatic.setCurrentModifier(modifier); - - // The (modifier > 0) check here allows increase over modified only if the modifier is positive (a fortify effect is active). - setCurrent (getCurrent() + diff, allowCurrentDecreaseBelowZero, (modifier > 0)); - } template void DynamicStat::writeState (ESM::StatState& state) const diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index c80c5b1b70..e08d0af71e 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -16,38 +16,22 @@ namespace MWMechanics class Stat { T mBase; - T mModified; - T mCurrentModified; + T mModifier; public: typedef T Type; Stat(); - Stat(T base); Stat(T base, T modified); - const T& getBase() const; + const T& getBase() const { return mBase; }; T getModified(bool capped = true) const; - T getCurrentModified() const; - T getModifier() const; - T getCurrentModifier() const; + T getModifier() const { return mModifier; }; - /// Set base and modified to \a value. - void set (const T& value); + void setBase(const T& value) { mBase = value; }; - /// Set base and adjust modified accordingly. - void setBase (const T& value); - - /// Set modified value and adjust base accordingly. - void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); - - /// Set "current modified," used for drain and fortify. Unlike the regular modifier - /// this just adds and subtracts from the current value without changing the maximum. - void setCurrentModified(T value); - - void setModifier (const T& modifier); - void setCurrentModifier (const T& modifier); + void setModifier(const T& modifier) { mModifier = modifier; }; void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); @@ -57,7 +41,7 @@ namespace MWMechanics inline bool operator== (const Stat& left, const Stat& right) { return left.getBase()==right.getBase() && - left.getModified()==right.getModified(); + left.getModifier()==right.getModifier(); } template @@ -80,27 +64,17 @@ namespace MWMechanics DynamicStat(T base, T modified, T current); DynamicStat(const Stat &stat, T current); - const T& getBase() const; - T getModified() const; - T getCurrentModified() const; - const T& getCurrent() const; - - /// Set base, modified and current to \a value. - void set (const T& value); + const T& getBase() const { return mStatic.getBase(); }; + T getModified(bool capped = true) const { return mStatic.getModified(capped); }; + const T& getCurrent() const { return mCurrent; }; - /// Set base and adjust modified accordingly. - void setBase (const T& value); - - /// Set modified value and adjust base accordingly. - void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); - - /// Set "current modified," used for drain and fortify. Unlike the regular modifier - /// this just adds and subtracts from the current value without changing the maximum. - void setCurrentModified(T value); + /// Set base and adjust current accordingly. + void setBase(const T& value) { mStatic.setBase(value); }; void setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false); - void setModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero=false); - void setCurrentModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero = false); + + T getModifier() const { return mStatic.getModifier(); } + void setModifier(T value) { mStatic.setModifier(value); } void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); @@ -110,7 +84,7 @@ namespace MWMechanics inline bool operator== (const DynamicStat& left, const DynamicStat& right) { return left.getBase()==right.getBase() && - left.getModified()==right.getModified() && + left.getModifier()==right.getModifier() && left.getCurrent()==right.getCurrent(); } diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 9c257d9093..1138a55238 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -198,7 +198,11 @@ namespace MWWorld for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) creatureStats.mAttributes[i].mMod = 0.f; for(std::size_t i = 0; i < 3; ++i) - creatureStats.mDynamic[i].mMod = 0.f; + { + auto& dynamic = creatureStats.mDynamic[i]; + dynamic.mCurrent -= dynamic.mMod - dynamic.mBase; + dynamic.mMod = 0.f; + } for(std::size_t i = 0; i < 4; ++i) creatureStats.mAiSettings[i].mMod = 0.f; if(npcStats) From ede9d27437da965dbffcfa6c5931516a67bbc54e Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 10 Feb 2022 19:43:27 +0000 Subject: [PATCH 2091/2859] Element-wise multiplication and division of Lua vectors --- .../lua/test_utilpackage.cpp | 12 ++++++ components/lua/utilpackage.cpp | 41 ++++++++++-------- files/lua_api/openmw/util.lua | 43 +++++++++++++++++++ 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index ba19cca383..5e27b36916 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -39,6 +39,10 @@ namespace EXPECT_TRUE(get(lua, "v2 == util.vector2(3/5, 4/5)")); lua.safe_script("_, len = util.vector2(0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); + lua.safe_script("ediv0 = util.vector2(1, 0):ediv(util.vector2(0, 0))"); + EXPECT_TRUE(get(lua, "ediv0.x == math.huge and ediv0.y ~= ediv0.y")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2):emul(util.vector2(3, 4)) == util.vector2(3, 8)")); + EXPECT_TRUE(get(lua, "util.vector2(4, 6):ediv(util.vector2(2, 3)) == util.vector2(2, 2)")); } TEST(LuaUtilPackageTest, Vector3) @@ -68,6 +72,10 @@ namespace EXPECT_TRUE(get(lua, "v2 == util.vector3(3/5, 4/5, 0)")); lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); + lua.safe_script("ediv0 = util.vector3(1, 1, 1):ediv(util.vector3(0, 0, 0))"); + EXPECT_TRUE(get(lua, "ediv0.z == math.huge")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3):emul(util.vector3(3, 4, 5)) == util.vector3(3, 8, 15)")); + EXPECT_TRUE(get(lua, "util.vector3(4, 6, 8):ediv(util.vector3(2, 3, 4)) == util.vector3(2, 2, 2)")); } TEST(LuaUtilPackageTest, Vector4) @@ -95,6 +103,10 @@ namespace EXPECT_TRUE(get(lua, "v2 == util.vector4(3/5, 0, 0, 4/5)")); lua.safe_script("_, len = util.vector4(0, 0, 0, 0):normalize()"); EXPECT_FLOAT_EQ(get(lua, "len"), 0); + lua.safe_script("ediv0 = util.vector4(1, 1, 1, -1):ediv(util.vector4(0, 0, 0, 0))"); + EXPECT_TRUE(get(lua, "ediv0.w == -math.huge")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4):emul(util.vector4(3, 4, 5, 6)) == util.vector4(3, 8, 15, 24)")); + EXPECT_TRUE(get(lua, "util.vector4(4, 6, 8, 9):ediv(util.vector4(2, 3, 4, 3)) == util.vector4(2, 2, 2, 3)")); } TEST(LuaUtilPackageTest, Color) diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index c51b00a7d5..d31a8259be 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -55,6 +55,29 @@ namespace LuaUtil else return std::make_tuple(v * (1.f / len), len); }; + vectorType["emul"] = [](const T& a, const T& b) + { + T result; + for (int i = 0; i < T::num_components; ++i) + result[i] = a[i] * b[i]; + return result; + }; + vectorType["ediv"] = [](const T& a, const T& b) + { + T result; + for (int i = 0; i < T::num_components; ++i) + result[i] = a[i] / b[i]; + return result; + }; + vectorType[sol::meta_function::to_string] = [](const T& v) + { + std::stringstream ss; + ss << "(" << v[0]; + for (int i = 1; i < T::num_components; ++i) + ss << ", " << v[i]; + ss << ")"; + return ss.str(); + }; } } @@ -67,12 +90,6 @@ namespace LuaUtil sol::usertype vec2Type = lua.new_usertype("Vec2"); vec2Type["x"] = sol::readonly_property([](const Vec2& v) -> float { return v.x(); } ); vec2Type["y"] = sol::readonly_property([](const Vec2& v) -> float { return v.y(); } ); - vec2Type[sol::meta_function::to_string] = [](const Vec2& v) - { - std::stringstream ss; - ss << "(" << v.x() << ", " << v.y() << ")"; - return ss.str(); - }; addVectorMethods(vec2Type); vec2Type["rotate"] = &Misc::rotateVec2f; @@ -82,12 +99,6 @@ namespace LuaUtil vec3Type["x"] = sol::readonly_property([](const Vec3& v) -> float { return v.x(); } ); vec3Type["y"] = sol::readonly_property([](const Vec3& v) -> float { return v.y(); } ); vec3Type["z"] = sol::readonly_property([](const Vec3& v) -> float { return v.z(); } ); - vec3Type[sol::meta_function::to_string] = [](const Vec3& v) - { - std::stringstream ss; - ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; - return ss.str(); - }; addVectorMethods(vec3Type); vec3Type[sol::meta_function::involution] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; vec3Type["cross"] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; @@ -100,12 +111,6 @@ namespace LuaUtil vec4Type["y"] = sol::readonly_property([](const Vec4& v) -> float { return v.y(); }); vec4Type["z"] = sol::readonly_property([](const Vec4& v) -> float { return v.z(); }); vec4Type["w"] = sol::readonly_property([](const Vec4& v) -> float { return v.w(); }); - vec4Type[sol::meta_function::to_string] = [](const Vec4& v) - { - std::stringstream ss; - ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ", " << v.w() << ")"; - return ss.str(); - }; addVectorMethods(vec4Type); // Lua bindings for Color diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index dca1683d45..4ea0ee4b0d 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -87,6 +87,20 @@ -- @param #Vector2 v -- @return #number +--- +-- Element-wise multiplication +-- @function [parent=#Vector2] emul +-- @param self +-- @param #Vector2 v +-- @return #Vector2 + +--- +-- Element-wise division +-- @function [parent=#Vector2] ediv +-- @param self +-- @param #Vector2 v +-- @return #Vector2 + --- -- Immutable 3D vector @@ -152,6 +166,20 @@ -- @param #Vector3 v -- @return #Vector3 +--- +-- Element-wise multiplication +-- @function [parent=#Vector3] emul +-- @param self +-- @param #Vector3 v +-- @return #Vector3 + +--- +-- Element-wise division +-- @function [parent=#Vector3] ediv +-- @param self +-- @param #Vector3 v +-- @return #Vector3 + --- -- Immutable 4D vector. @@ -210,6 +238,21 @@ -- @param #Vector4 v -- @return #number +--- +-- Element-wise multiplication +-- @function [parent=#Vector4] emul +-- @param self +-- @param #Vector4 v +-- @return #Vector4 + +--- +-- Element-wise division +-- @function [parent=#Vector4] ediv +-- @param self +-- @param #Vector4 v +-- @return #Vector4 + + --- -- Color in RGBA format. All of the component values are in the range [0, 1]. -- @type Color From 6b203892fc55cf5bb1c0eac15cffd3b6009c4eb8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 20:46:20 +0100 Subject: [PATCH 2092/2859] Fix mod not increasing fortified values --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwscript/statsextensions.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5c41e8142d..d2ebef45e7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -255,7 +255,7 @@ namespace MWMechanics for (int i=0; i<3; ++i) { DynamicStat stat = creatureStats.getDynamic (i); - stat.setCurrent(stat.getModified()); + stat.setCurrent (stat.getModified()); creatureStats.setDynamic (i, stat); } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index f127b2bc4b..f2de338fb3 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -219,7 +219,7 @@ namespace MWScript .getDynamic (mIndex)); stat.setBase(value); - stat.setCurrent(value, true); + stat.setCurrent(stat.getModified(false), true, true); ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } @@ -268,7 +268,7 @@ namespace MWScript if(mIndex != 2) base = std::max(base, 0.f); stat.setBase(base); - stat.setCurrent(diff + current, true); + stat.setCurrent(diff + current, true, true); stats.setDynamic (mIndex, stat); } From 712107de2da596aad598bf97eb6d18dc68708100 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Thu, 10 Feb 2022 11:59:49 -0800 Subject: [PATCH 2093/2859] nisortadjust support --- CHANGELOG.md | 1 + components/nif/niffile.cpp | 2 +- components/nif/record.hpp | 3 +- components/nifosg/nifloader.cpp | 94 +++++++++++++++++++++++++++++---- 4 files changed, 89 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b26d758847..ccdfec178a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,7 @@ Feature #6443: NiStencilProperty is not fully supported Feature #6534: Shader-based object texture blending Feature #6592: Missing support for NiTriShape particle emitters + Feature #6600: Support NiSortAdjustNode Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp Task #6553: Simplify interpreter instruction registration diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 1a1bbd7217..37fb82dd04 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -153,7 +153,7 @@ static std::map makeFactory() factory["bhkRigidBody"] = {&construct , RC_bhkRigidBody }; factory["bhkRigidBodyT"] = {&construct , RC_bhkRigidBodyT }; factory["BSLightingShaderProperty"] = {&construct , RC_BSLightingShaderProperty }; - factory["NiSortAdjustNode"] = {&construct , RC_NiNode }; + factory["NiSortAdjustNode"] = {&construct , RC_NiSortAdjustNode }; factory["NiClusterAccumulator"] = {&construct , RC_NiClusterAccumulator }; factory["NiAlphaAccumulator"] = {&construct , RC_NiAlphaAccumulator }; return factory; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 937ce1d1e9..2ce1ff0743 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -142,7 +142,8 @@ enum RecordType RC_bhkRigidBodyT, RC_BSLightingShaderProperty, RC_NiClusterAccumulator, - RC_NiAlphaAccumulator + RC_NiAlphaAccumulator, + RC_NiSortAdjustNode }; /// Base class for all records diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 206bbf2092..b61681e04e 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -219,6 +219,9 @@ namespace NifOsg bool mHasHerbalismLabel = false; bool mHasStencilProperty = false; + const Nif::NiSortAdjustNode* mPushedSorter = nullptr; + const Nif::NiSortAdjustNode* mLastAppliedNoInheritSorter = nullptr; + // This is used to queue emitters that weren't attached to their node yet. std::vector>> mEmitterQueue; @@ -309,10 +312,6 @@ namespace NifOsg if (mHasHerbalismLabel) created->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); - // When dealing with stencil buffer, draw order is especially sensitive. Make sure such objects are drawn with traversal order. - if (mHasStencilProperty) - created->getOrCreateStateSet()->setRenderBinDetails(2, "TraversalOrderBin"); - // Attach particle emitters to their nodes which should all be loaded by now. handleQueuedParticleEmitters(created, nif); @@ -597,6 +596,22 @@ namespace NifOsg if (nifNode->recType == Nif::RC_NiBSAnimationNode || nifNode->recType == Nif::RC_NiBSParticleNode) animflags = nifNode->flags; + if (nifNode->recType == Nif::RC_NiSortAdjustNode) + { + auto sortNode = static_cast(nifNode); + + if (sortNode->mSubSorter.empty()) + { + Log(Debug::Warning) << "Empty accumulator found in '" << nifNode->recName << "' node " << nifNode->recIndex; + } + else + { + if (mPushedSorter && !mPushedSorter->mSubSorter.empty() && mPushedSorter->mMode != Nif::NiSortAdjustNode::SortingMode_Inherit) + mLastAppliedNoInheritSorter = mPushedSorter; + mPushedSorter = sortNode; + } + } + // Hide collision shapes, but don't skip the subgraph // We still need to animate the hidden bones so the physics system can access them if (nifNode->recType == Nif::RC_RootCollisionNode) @@ -2008,6 +2023,13 @@ namespace NifOsg mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); bool hasMatCtrl = false; + bool hasSortAlpha = false; + osg::StateSet* blendFuncStateSet = nullptr; + + auto setBin_Transparent = [] (osg::StateSet* ss) { ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); }; + auto setBin_BackToFront = [] (osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); }; + auto setBin_Traversal = [] (osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); }; + auto setBin_Inherit = [] (osg::StateSet* ss) { ss->setRenderBinToInherit(); }; int lightmode = 1; float emissiveMult = 1.f; @@ -2085,17 +2107,23 @@ namespace NifOsg bool noSort = (alphaprop->flags>>13)&1; if (!noSort) { - node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - node->getOrCreateStateSet()->setNestRenderBins(false); + hasSortAlpha = true; + if (!mPushedSorter) + setBin_Transparent(node->getStateSet()); } else - node->getOrCreateStateSet()->setRenderBinToInherit(); + { + if (!mPushedSorter) + setBin_Inherit(node->getStateSet()); + } } else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); - stateset->setRenderBinToInherit(); + blendFuncStateSet = stateset; + if (!mPushedSorter) + blendFuncStateSet->setRenderBinToInherit(); } if((alphaprop->flags>>9)&1) @@ -2158,7 +2186,10 @@ namespace NifOsg mat->setColorMode(osg::Material::OFF); } - if (!hasMatCtrl && mat->getColorMode() == osg::Material::OFF + if (!mPushedSorter && !hasSortAlpha && mHasStencilProperty) + setBin_Traversal(node->getOrCreateStateSet()); + + if (!mPushedSorter && !hasMatCtrl && mat->getColorMode() == osg::Material::OFF && mat->getEmission(osg::Material::FRONT_AND_BACK) == osg::Vec4f(0,0,0,1) && mat->getDiffuse(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1) && mat->getAmbient(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1) @@ -2177,6 +2208,51 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); if (specStrength != 1.f) stateset->addUniform(new osg::Uniform("specStrength", specStrength)); + + if (!mPushedSorter) + return; + + auto assignBin = [&] (int mode, int type) { + if (mode == Nif::NiSortAdjustNode::SortingMode_Off) + { + setBin_Traversal(stateset); + return; + } + + if (type == Nif::RC_NiAlphaAccumulator) + { + if (hasSortAlpha) + setBin_BackToFront(stateset); + else + setBin_Traversal(stateset); + } + else if (type == Nif::RC_NiClusterAccumulator) + setBin_BackToFront(stateset); + else + Log(Debug::Error) << "Unrecognized NiAccumulator in " << mFilename; + }; + + switch (mPushedSorter->mMode) + { + case Nif::NiSortAdjustNode::SortingMode_Inherit: + { + if (mLastAppliedNoInheritSorter) + assignBin(mLastAppliedNoInheritSorter->mMode, mLastAppliedNoInheritSorter->mSubSorter->recType); + else + assignBin(mPushedSorter->mMode, Nif::RC_NiAlphaAccumulator); + break; + } + case Nif::NiSortAdjustNode::SortingMode_Off: + { + setBin_Traversal(stateset); + break; + } + case Nif::NiSortAdjustNode::SortingMode_Subsort: + { + assignBin(mPushedSorter->mMode, mPushedSorter->mSubSorter->recType); + break; + } + } } }; From 054d8babc48cfe30e344ca65df85c411a0569569 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 10 Feb 2022 22:10:46 +0100 Subject: [PATCH 2094/2859] Add getRatio method --- apps/openmw/mwdialogue/filter.cpp | 12 ++---------- apps/openmw/mwgui/hud.cpp | 3 +-- apps/openmw/mwmechanics/aicombataction.cpp | 3 +-- apps/openmw/mwmechanics/stat.cpp | 13 +++++++++++++ apps/openmw/mwmechanics/stat.hpp | 1 + apps/openmw/mwscript/statsextensions.cpp | 12 ++---------- 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 7ae9f14a7a..e90ba79481 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -257,11 +257,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); - float ratio = player.getClass().getCreatureStats(player).getHealth().getModified(); - if(ratio > 0) - ratio = player.getClass().getCreatureStats(player).getHealth().getCurrent() / ratio; - - return select.selectCompare (static_cast(ratio*100)); + return select.selectCompare(static_cast(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100)); } case SelectWrapper::Function_PcDynamicStat: @@ -276,11 +272,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_HealthPercent: { - float ratio = mActor.getClass().getCreatureStats(mActor).getHealth().getModified(); - if(ratio > 0) - ratio = mActor.getClass().getCreatureStats(mActor).getHealth().getCurrent() / ratio; - - return select.selectCompare (static_cast(ratio*100)); + return select.selectCompare(static_cast(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100)); } default: diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 5af85b34ae..e591163731 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -608,8 +608,7 @@ namespace MWGui mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) - float health = stats.getHealth().getModified(); - mEnemyHealth->setProgressPosition(health == 0.f ? 0 : static_cast(stats.getHealth().getCurrent() / health * 100)); + mEnemyHealth->setProgressPosition(static_cast(stats.getHealth().getRatio() * 100)); static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 3be68f8394..43b43eb9f9 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -476,8 +476,7 @@ namespace MWMechanics static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat(); static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat(); - float healthPercentage = (stats.getHealth().getModified() == 0.0f) - ? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified(); + float healthPercentage = stats.getHealth().getRatio(false); float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult; static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger(); diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 9d62eb7873..eacfca98ae 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -65,6 +65,19 @@ namespace MWMechanics } } + template + T DynamicStat::getRatio(bool nanIsZero) const + { + T modified = getModified(); + if(modified == T{}) + { + if(nanIsZero) + return modified; + return {1}; + } + return getCurrent() / modified; + } + template void DynamicStat::writeState (ESM::StatState& state) const { diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index e08d0af71e..1e9bb100d0 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -67,6 +67,7 @@ namespace MWMechanics const T& getBase() const { return mStatic.getBase(); }; T getModified(bool capped = true) const { return mStatic.getModified(capped); }; const T& getCurrent() const { return mCurrent; }; + T getRatio(bool nanIsZero = true) const; /// Set base and adjust current accordingly. void setBase(const T& value) { mStatic.setBase(value); }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index f2de338fb3..a62d40065d 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -324,17 +324,9 @@ namespace MWScript void execute (Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - - Interpreter::Type_Float value = 0; - - Interpreter::Type_Float max = stats.getDynamic(mIndex).getModified(); - - if (max>0) - value = stats.getDynamic(mIndex).getCurrent() / max; - - runtime.push (value); + runtime.push(stats.getDynamic(mIndex).getRatio()); } }; From 6cd12823e88bd9e6cc503146bd0227557b4aac48 Mon Sep 17 00:00:00 2001 From: Niek Wilting Date: Fri, 11 Feb 2022 19:26:13 +0100 Subject: [PATCH 2095/2859] Fix always-use-best-attack rounding --- apps/openmw/mwmechanics/character.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0f8e2ebd97..a1d686baaa 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -55,9 +55,9 @@ namespace std::string getBestAttack (const ESM::Weapon* weapon) { - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + int slash = weapon->mData.mSlash[0] + weapon->mData.mSlash[1]; + int chop = weapon->mData.mChop[0] + weapon->mData.mChop[1]; + int thrust = weapon->mData.mThrust[0] + weapon->mData.mThrust[1]; if (slash == chop && slash == thrust) return "slash"; else if (thrust >= chop && thrust >= slash) From 32e710b11325ab4da8c078a8dae8f01e5da24cc7 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 11 Feb 2022 18:57:59 +0000 Subject: [PATCH 2096/2859] Add missing `i18n/Calendar/en.lua` in CMakeLists.txt --- files/builtin_scripts/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 90c3c2a503..1f140a4af1 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -25,3 +25,6 @@ set(LUA_SCRIPTS_FILES set(DDIRRELATIVE resources/vfs/scripts/omw) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${LUA_SCRIPTS_FILES}") + +set(DDIRRELATIVE resources/vfs/i18n/Calendar) +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "i18n/Calendar/en.lua") From d097c16206077933234aac566957ef513f1459ae Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 20:40:38 +0100 Subject: [PATCH 2097/2859] Use unique_ptr to manage nif record lifetime --- components/nif/niffile.cpp | 20 +++++++------------- components/nif/niffile.hpp | 6 ++---- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 1a1bbd7217..e976a9d96a 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -17,17 +17,11 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name) parse(stream); } -NIFFile::~NIFFile() -{ - for (Record* record : records) - delete record; -} - -template static Record* construct() { return new NodeType; } +template static std::unique_ptr construct() { return std::make_unique(); } struct RecordFactoryEntry { - using create_t = Record* (*)(); + using create_t = std::unique_ptr (*)(); create_t mCreate; RecordType mType; @@ -290,7 +284,7 @@ void NIFFile::parse(Files::IStreamPtr stream) const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10,0,0,0) && ver < NIFStream::generateVersion(10,2,0,0); for (std::size_t i = 0; i < recNum; i++) { - Record *r = nullptr; + std::unique_ptr r; std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString(); if(rec.empty()) @@ -315,7 +309,7 @@ void NIFFile::parse(Files::IStreamPtr stream) if (entry != factories.end()) { - r = entry->second.mCreate (); + r = entry->second.mCreate(); r->recType = entry->second.mType; } else @@ -328,8 +322,8 @@ void NIFFile::parse(Files::IStreamPtr stream) assert(r->recType != RC_MISSING); r->recName = rec; r->recIndex = i; - records[i] = r; r->read(&nif); + records[i] = std::move(r); } const std::size_t rootNum = nif.getUInt(); @@ -341,7 +335,7 @@ void NIFFile::parse(Files::IStreamPtr stream) int idx = nif.getInt(); if (idx >= 0 && static_cast(idx) < records.size()) { - roots[i] = records[idx]; + roots[i] = records[idx].get(); } else { @@ -351,7 +345,7 @@ void NIFFile::parse(Files::IStreamPtr stream) } // Once parsing is done, do post-processing. - for (Record* record : records) + for (const auto& record : records) record->post(this); } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 6884f51d58..d9f46795c1 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -55,7 +55,7 @@ class NIFFile final : public File std::string hash; /// Record list - std::vector records; + std::vector> records; /// Root list. This is a select portion of the pointers from records std::vector roots; @@ -107,13 +107,11 @@ public: /// Open a NIF stream. The name is used for error messages. NIFFile(Files::IStreamPtr stream, const std::string &name); - ~NIFFile(); /// Get a given record Record *getRecord(size_t index) const override { - Record *res = records.at(index); - return res; + return records.at(index).get(); } /// Number of records size_t numRecords() const override { return records.size(); } From 082810f9244b6666ee5c2f5e3e27ef314328ed83 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 21:17:11 +0100 Subject: [PATCH 2098/2859] Store record type as a part of construct function type --- components/nif/niffile.cpp | 272 ++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 138 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index e976a9d96a..f3355c4e77 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -17,144 +17,143 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name) parse(stream); } -template static std::unique_ptr construct() { return std::make_unique(); } - -struct RecordFactoryEntry { - - using create_t = std::unique_ptr (*)(); - - create_t mCreate; - RecordType mType; +template +static std::unique_ptr construct() +{ + auto result = std::make_unique(); + result->recType = recordType; + return result; +} -}; +using CreateRecord = std::unique_ptr (*)(); ///These are all the record types we know how to read. -static std::map makeFactory() +static std::map makeFactory() { - std::map factory; - factory["NiNode"] = {&construct , RC_NiNode }; - factory["NiSwitchNode"] = {&construct , RC_NiSwitchNode }; - factory["NiLODNode"] = {&construct , RC_NiLODNode }; - factory["AvoidNode"] = {&construct , RC_AvoidNode }; - factory["NiCollisionSwitch"] = {&construct , RC_NiCollisionSwitch }; - factory["NiBSParticleNode"] = {&construct , RC_NiBSParticleNode }; - factory["NiBSAnimationNode"] = {&construct , RC_NiBSAnimationNode }; - factory["NiBillboardNode"] = {&construct , RC_NiBillboardNode }; - factory["NiTriShape"] = {&construct , RC_NiTriShape }; - factory["NiTriStrips"] = {&construct , RC_NiTriStrips }; - factory["NiLines"] = {&construct , RC_NiLines }; - factory["NiParticles"] = {&construct , RC_NiParticles }; - factory["NiRotatingParticles"] = {&construct , RC_NiParticles }; - factory["NiAutoNormalParticles"] = {&construct , RC_NiParticles }; - factory["NiCamera"] = {&construct , RC_NiCamera }; - factory["RootCollisionNode"] = {&construct , RC_RootCollisionNode }; - factory["NiTexturingProperty"] = {&construct , RC_NiTexturingProperty }; - factory["NiFogProperty"] = {&construct , RC_NiFogProperty }; - factory["NiMaterialProperty"] = {&construct , RC_NiMaterialProperty }; - factory["NiZBufferProperty"] = {&construct , RC_NiZBufferProperty }; - factory["NiAlphaProperty"] = {&construct , RC_NiAlphaProperty }; - factory["NiVertexColorProperty"] = {&construct , RC_NiVertexColorProperty }; - factory["NiShadeProperty"] = {&construct , RC_NiShadeProperty }; - factory["NiDitherProperty"] = {&construct , RC_NiDitherProperty }; - factory["NiWireframeProperty"] = {&construct , RC_NiWireframeProperty }; - factory["NiSpecularProperty"] = {&construct , RC_NiSpecularProperty }; - factory["NiStencilProperty"] = {&construct , RC_NiStencilProperty }; - factory["NiVisController"] = {&construct , RC_NiVisController }; - factory["NiGeomMorpherController"] = {&construct , RC_NiGeomMorpherController }; - factory["NiKeyframeController"] = {&construct , RC_NiKeyframeController }; - factory["NiAlphaController"] = {&construct , RC_NiAlphaController }; - factory["NiRollController"] = {&construct , RC_NiRollController }; - factory["NiUVController"] = {&construct , RC_NiUVController }; - factory["NiPathController"] = {&construct , RC_NiPathController }; - factory["NiMaterialColorController"] = {&construct , RC_NiMaterialColorController }; - factory["NiBSPArrayController"] = {&construct , RC_NiBSPArrayController }; - factory["NiParticleSystemController"] = {&construct , RC_NiParticleSystemController }; - factory["NiFlipController"] = {&construct , RC_NiFlipController }; - factory["NiAmbientLight"] = {&construct , RC_NiLight }; - factory["NiDirectionalLight"] = {&construct , RC_NiLight }; - factory["NiPointLight"] = {&construct , RC_NiLight }; - factory["NiSpotLight"] = {&construct , RC_NiLight }; - factory["NiTextureEffect"] = {&construct , RC_NiTextureEffect }; - factory["NiExtraData"] = {&construct , RC_NiExtraData }; - factory["NiVertWeightsExtraData"] = {&construct , RC_NiVertWeightsExtraData }; - factory["NiTextKeyExtraData"] = {&construct , RC_NiTextKeyExtraData }; - factory["NiStringExtraData"] = {&construct , RC_NiStringExtraData }; - factory["NiGravity"] = {&construct , RC_NiGravity }; - factory["NiPlanarCollider"] = {&construct , RC_NiPlanarCollider }; - factory["NiSphericalCollider"] = {&construct , RC_NiSphericalCollider }; - factory["NiParticleGrowFade"] = {&construct , RC_NiParticleGrowFade }; - factory["NiParticleColorModifier"] = {&construct , RC_NiParticleColorModifier }; - factory["NiParticleRotation"] = {&construct , RC_NiParticleRotation }; - factory["NiFloatData"] = {&construct , RC_NiFloatData }; - factory["NiTriShapeData"] = {&construct , RC_NiTriShapeData }; - factory["NiTriStripsData"] = {&construct , RC_NiTriStripsData }; - factory["NiLinesData"] = {&construct , RC_NiLinesData }; - factory["NiVisData"] = {&construct , RC_NiVisData }; - factory["NiColorData"] = {&construct , RC_NiColorData }; - factory["NiPixelData"] = {&construct , RC_NiPixelData }; - factory["NiMorphData"] = {&construct , RC_NiMorphData }; - factory["NiKeyframeData"] = {&construct , RC_NiKeyframeData }; - factory["NiSkinData"] = {&construct , RC_NiSkinData }; - factory["NiUVData"] = {&construct , RC_NiUVData }; - factory["NiPosData"] = {&construct , RC_NiPosData }; - factory["NiParticlesData"] = {&construct , RC_NiParticlesData }; - factory["NiRotatingParticlesData"] = {&construct , RC_NiParticlesData }; - factory["NiAutoNormalParticlesData"] = {&construct , RC_NiParticlesData }; - factory["NiSequenceStreamHelper"] = {&construct , RC_NiSequenceStreamHelper }; - factory["NiSourceTexture"] = {&construct , RC_NiSourceTexture }; - factory["NiSkinInstance"] = {&construct , RC_NiSkinInstance }; - factory["NiLookAtController"] = {&construct , RC_NiLookAtController }; - factory["NiPalette"] = {&construct , RC_NiPalette }; - factory["NiIntegerExtraData"] = {&construct , RC_NiIntegerExtraData }; - factory["NiIntegersExtraData"] = {&construct , RC_NiIntegersExtraData }; - factory["NiBinaryExtraData"] = {&construct , RC_NiBinaryExtraData }; - factory["NiBooleanExtraData"] = {&construct , RC_NiBooleanExtraData }; - factory["NiVectorExtraData"] = {&construct , RC_NiVectorExtraData }; - factory["NiColorExtraData"] = {&construct , RC_NiColorExtraData }; - factory["NiFloatExtraData"] = {&construct , RC_NiFloatExtraData }; - factory["NiFloatsExtraData"] = {&construct , RC_NiFloatsExtraData }; - factory["NiStringPalette"] = {&construct , RC_NiStringPalette }; - factory["NiBoolData"] = {&construct , RC_NiBoolData }; - factory["NiSkinPartition"] = {&construct , RC_NiSkinPartition }; - factory["BSXFlags"] = {&construct , RC_BSXFlags }; - factory["BSBound"] = {&construct , RC_BSBound }; - factory["NiTransformData"] = {&construct , RC_NiKeyframeData }; - factory["BSFadeNode"] = {&construct , RC_NiNode }; - factory["bhkBlendController"] = {&construct , RC_bhkBlendController }; - factory["NiFloatInterpolator"] = {&construct , RC_NiFloatInterpolator }; - factory["NiBoolInterpolator"] = {&construct , RC_NiBoolInterpolator }; - factory["NiPoint3Interpolator"] = {&construct , RC_NiPoint3Interpolator }; - factory["NiTransformController"] = {&construct , RC_NiKeyframeController }; - factory["NiTransformInterpolator"] = {&construct , RC_NiTransformInterpolator }; - factory["NiColorInterpolator"] = {&construct , RC_NiColorInterpolator }; - factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; - factory["BSLODTriShape"] = {&construct , RC_BSLODTriShape }; - factory["BSShaderProperty"] = {&construct , RC_BSShaderProperty }; - factory["BSShaderPPLightingProperty"] = {&construct , RC_BSShaderPPLightingProperty }; - factory["BSShaderNoLightingProperty"] = {&construct , RC_BSShaderNoLightingProperty }; - factory["BSFurnitureMarker"] = {&construct , RC_BSFurnitureMarker }; - factory["NiCollisionObject"] = {&construct , RC_NiCollisionObject }; - factory["bhkCollisionObject"] = {&construct , RC_bhkCollisionObject }; - factory["BSDismemberSkinInstance"] = {&construct , RC_BSDismemberSkinInstance }; - factory["NiControllerManager"] = {&construct , RC_NiControllerManager }; - factory["bhkMoppBvTreeShape"] = {&construct , RC_bhkMoppBvTreeShape }; - factory["bhkNiTriStripsShape"] = {&construct , RC_bhkNiTriStripsShape }; - factory["bhkPackedNiTriStripsShape"] = {&construct , RC_bhkPackedNiTriStripsShape }; - factory["hkPackedNiTriStripsData"] = {&construct , RC_hkPackedNiTriStripsData }; - factory["bhkConvexVerticesShape"] = {&construct , RC_bhkConvexVerticesShape }; - factory["bhkBoxShape"] = {&construct , RC_bhkBoxShape }; - factory["bhkListShape"] = {&construct , RC_bhkListShape }; - factory["bhkRigidBody"] = {&construct , RC_bhkRigidBody }; - factory["bhkRigidBodyT"] = {&construct , RC_bhkRigidBodyT }; - factory["BSLightingShaderProperty"] = {&construct , RC_BSLightingShaderProperty }; - factory["NiSortAdjustNode"] = {&construct , RC_NiNode }; - factory["NiClusterAccumulator"] = {&construct , RC_NiClusterAccumulator }; - factory["NiAlphaAccumulator"] = {&construct , RC_NiAlphaAccumulator }; - return factory; + return { + {"NiNode" , &construct }, + {"NiSwitchNode" , &construct }, + {"NiLODNode" , &construct }, + {"AvoidNode" , &construct }, + {"NiCollisionSwitch" , &construct }, + {"NiBSParticleNode" , &construct }, + {"NiBSAnimationNode" , &construct }, + {"NiBillboardNode" , &construct }, + {"NiTriShape" , &construct }, + {"NiTriStrips" , &construct }, + {"NiLines" , &construct }, + {"NiParticles" , &construct }, + {"NiRotatingParticles" , &construct }, + {"NiAutoNormalParticles" , &construct }, + {"NiCamera" , &construct }, + {"RootCollisionNode" , &construct }, + {"NiTexturingProperty" , &construct }, + {"NiFogProperty" , &construct }, + {"NiMaterialProperty" , &construct }, + {"NiZBufferProperty" , &construct }, + {"NiAlphaProperty" , &construct }, + {"NiVertexColorProperty" , &construct }, + {"NiShadeProperty" , &construct }, + {"NiDitherProperty" , &construct }, + {"NiWireframeProperty" , &construct }, + {"NiSpecularProperty" , &construct }, + {"NiStencilProperty" , &construct }, + {"NiVisController" , &construct }, + {"NiGeomMorpherController" , &construct }, + {"NiKeyframeController" , &construct }, + {"NiAlphaController" , &construct }, + {"NiRollController" , &construct }, + {"NiUVController" , &construct }, + {"NiPathController" , &construct }, + {"NiMaterialColorController" , &construct }, + {"NiBSPArrayController" , &construct }, + {"NiParticleSystemController" , &construct }, + {"NiFlipController" , &construct }, + {"NiAmbientLight" , &construct }, + {"NiDirectionalLight" , &construct }, + {"NiPointLight" , &construct }, + {"NiSpotLight" , &construct }, + {"NiTextureEffect" , &construct }, + {"NiExtraData" , &construct }, + {"NiVertWeightsExtraData" , &construct }, + {"NiTextKeyExtraData" , &construct }, + {"NiStringExtraData" , &construct }, + {"NiGravity" , &construct }, + {"NiPlanarCollider" , &construct }, + {"NiSphericalCollider" , &construct }, + {"NiParticleGrowFade" , &construct }, + {"NiParticleColorModifier" , &construct }, + {"NiParticleRotation" , &construct }, + {"NiFloatData" , &construct }, + {"NiTriShapeData" , &construct }, + {"NiTriStripsData" , &construct }, + {"NiLinesData" , &construct }, + {"NiVisData" , &construct }, + {"NiColorData" , &construct }, + {"NiPixelData" , &construct }, + {"NiMorphData" , &construct }, + {"NiKeyframeData" , &construct }, + {"NiSkinData" , &construct }, + {"NiUVData" , &construct }, + {"NiPosData" , &construct }, + {"NiParticlesData" , &construct }, + {"NiRotatingParticlesData" , &construct }, + {"NiAutoNormalParticlesData" , &construct }, + {"NiSequenceStreamHelper" , &construct }, + {"NiSourceTexture" , &construct }, + {"NiSkinInstance" , &construct }, + {"NiLookAtController" , &construct }, + {"NiPalette" , &construct }, + {"NiIntegerExtraData" , &construct }, + {"NiIntegersExtraData" , &construct }, + {"NiBinaryExtraData" , &construct }, + {"NiBooleanExtraData" , &construct }, + {"NiVectorExtraData" , &construct }, + {"NiColorExtraData" , &construct }, + {"NiFloatExtraData" , &construct }, + {"NiFloatsExtraData" , &construct }, + {"NiStringPalette" , &construct }, + {"NiBoolData" , &construct }, + {"NiSkinPartition" , &construct }, + {"BSXFlags" , &construct }, + {"BSBound" , &construct }, + {"NiTransformData" , &construct }, + {"BSFadeNode" , &construct }, + {"bhkBlendController" , &construct }, + {"NiFloatInterpolator" , &construct }, + {"NiBoolInterpolator" , &construct }, + {"NiPoint3Interpolator" , &construct }, + {"NiTransformController" , &construct }, + {"NiTransformInterpolator" , &construct }, + {"NiColorInterpolator" , &construct }, + {"BSShaderTextureSet" , &construct }, + {"BSLODTriShape" , &construct }, + {"BSShaderProperty" , &construct }, + {"BSShaderPPLightingProperty" , &construct }, + {"BSShaderNoLightingProperty" , &construct }, + {"BSFurnitureMarker" , &construct }, + {"NiCollisionObject" , &construct }, + {"bhkCollisionObject" , &construct }, + {"BSDismemberSkinInstance" , &construct }, + {"NiControllerManager" , &construct }, + {"bhkMoppBvTreeShape" , &construct }, + {"bhkNiTriStripsShape" , &construct }, + {"bhkPackedNiTriStripsShape" , &construct }, + {"hkPackedNiTriStripsData" , &construct }, + {"bhkConvexVerticesShape" , &construct }, + {"bhkBoxShape" , &construct }, + {"bhkListShape" , &construct }, + {"bhkRigidBody" , &construct }, + {"bhkRigidBodyT" , &construct }, + {"BSLightingShaderProperty" , &construct }, + {"NiSortAdjustNode" , &construct }, + {"NiClusterAccumulator" , &construct }, + {"NiAlphaAccumulator" , &construct }, + }; } ///Make the factory map used for parsing the file -static const std::map factories = makeFactory(); +static const std::map factories = makeFactory(); std::string NIFFile::printVersion(unsigned int version) { @@ -305,16 +304,13 @@ void NIFFile::parse(Files::IStreamPtr stream) } } - std::map::const_iterator entry = factories.find(rec); + const auto entry = factories.find(rec); - if (entry != factories.end()) - { - r = entry->second.mCreate(); - r->recType = entry->second.mType; - } - else + if (entry == factories.end()) fail("Unknown record type " + rec); + r = entry->second(); + if (!supported) Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" << filename << ")"; From fbd95516f4a7ffadc2fb1baee5f56c3fcea8229f Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 21:28:24 +0100 Subject: [PATCH 2099/2859] Repalce raw for-loops by corresponding algorithms --- components/nif/niffile.cpp | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index f3355c4e77..bf604f5adf 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace Nif { @@ -181,18 +182,11 @@ void NIFFile::parse(Files::IStreamPtr stream) "NetImmerse File Format", "Gamebryo File Format" }; - bool supported = false; - for (const std::string& verString : verStrings) - { - supported = (head.compare(0, verString.size(), verString) == 0); - if (supported) - break; - } - if (!supported) + const bool supportedHeader = std::any_of(verStrings.begin(), verStrings.end(), + [&] (const std::string& verString) { return head.compare(0, verString.size(), verString) == 0; }); + if (!supportedHeader) fail("Invalid NIF header: " + head); - supported = false; - // Get BCD version ver = nif.getUInt(); // 4.0.0.0 is an older, practically identical version of the format. @@ -202,13 +196,8 @@ void NIFFile::parse(Files::IStreamPtr stream) NIFStream::generateVersion(4,0,0,0), VER_MW }; - for (uint32_t supportedVer : supportedVers) - { - supported = (ver == supportedVer); - if (supported) - break; - } - if (!supported) + const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), ver) != supportedVers.end(); + if (!supportedVersion) { if (sLoadUnsupportedFiles) warn("Unsupported NIF version: " + printVersion(ver) + ". Proceed with caution!"); @@ -311,7 +300,7 @@ void NIFFile::parse(Files::IStreamPtr stream) r = entry->second(); - if (!supported) + if (!supportedVersion) Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" << filename << ")"; assert(r != nullptr); From 283b68025cf4cd532c86f08861b78af9d9ba9267 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 21:36:08 +0100 Subject: [PATCH 2100/2859] Avoid possible race condition on NIFFile::sLoadUnsupportedFiles Its value is written from the main thread but other threads read it. --- components/nif/niffile.cpp | 2 +- components/nif/niffile.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index bf604f5adf..8b17680cf3 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -344,7 +344,7 @@ bool NIFFile::getUseSkinning() const return mUseSkinning; } -bool NIFFile::sLoadUnsupportedFiles = false; +std::atomic_bool NIFFile::sLoadUnsupportedFiles = false; void NIFFile::setLoadUnsupportedFiles(bool load) { diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index d9f46795c1..def2b8870d 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -65,7 +66,7 @@ class NIFFile final : public File bool mUseSkinning = false; - static bool sLoadUnsupportedFiles; + static std::atomic_bool sLoadUnsupportedFiles; /// Parse the file void parse(Files::IStreamPtr stream); From e1fe5010134550088a937b81d8eafbad720eb0da Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 21:52:00 +0100 Subject: [PATCH 2101/2859] Use proper type for Record::recType --- components/nif/record.hpp | 2 +- components/nifosg/nifloader.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 937ce1d1e9..f18a639aae 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -149,7 +149,7 @@ enum RecordType struct Record { // Record type and type name - int recType{RC_MISSING}; + RecordType recType{RC_MISSING}; std::string recName; unsigned int recIndex{~0u}; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 206bbf2092..a543b7a67f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2122,6 +2122,8 @@ namespace NifOsg specStrength = shaderprop->mSpecStrength; break; } + default: + break; } } From 2a87cf1720b80197a78fac069aefc1f3ec694506 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 11 Feb 2022 22:08:13 +0100 Subject: [PATCH 2102/2859] Replace unordered_map by switch statement Add handling for missing Nif::BSLightingShaderType::ShaderType_SkinTint. Use string_view instead of string to avoid lifetime issues for returning value. osg::Object::setUserValue will anyway copy string. --- components/nifosg/nifloader.cpp | 106 +++++++++++++++----------------- 1 file changed, 49 insertions(+), 57 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a543b7a67f..73bd2068fb 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1,6 +1,7 @@ #include "nifloader.hpp" #include +#include #include #include @@ -1743,64 +1744,55 @@ namespace NifOsg } } - const std::string& getBSShaderPrefix(unsigned int type) const + std::string_view getBSShaderPrefix(unsigned int type) const { - static const std::unordered_map mapping = - { - {Nif::BSShaderType::ShaderType_TallGrass, std::string()}, - {Nif::BSShaderType::ShaderType_Default, "nv_default"}, - {Nif::BSShaderType::ShaderType_Sky, std::string()}, - {Nif::BSShaderType::ShaderType_Skin, std::string()}, - {Nif::BSShaderType::ShaderType_Water, std::string()}, - {Nif::BSShaderType::ShaderType_Lighting30, std::string()}, - {Nif::BSShaderType::ShaderType_Tile, std::string()}, - {Nif::BSShaderType::ShaderType_NoLighting, "nv_nolighting"}, - }; - auto prefix = mapping.find(static_cast(type)); - if (prefix == mapping.end()) - Log(Debug::Warning) << "Unknown BSShaderType " << type << " in " << mFilename; - else if (prefix->second.empty()) - Log(Debug::Warning) << "Unhandled BSShaderType " << type << " in " << mFilename; - else - return prefix->second; - - return mapping.at(Nif::BSShaderType::ShaderType_Default); + switch (static_cast(type)) + { + case Nif::BSShaderType::ShaderType_Default: return "nv_default"; + case Nif::BSShaderType::ShaderType_NoLighting: return "nv_nolighting"; + case Nif::BSShaderType::ShaderType_TallGrass: + case Nif::BSShaderType::ShaderType_Sky: + case Nif::BSShaderType::ShaderType_Skin: + case Nif::BSShaderType::ShaderType_Water: + case Nif::BSShaderType::ShaderType_Lighting30: + case Nif::BSShaderType::ShaderType_Tile: + Log(Debug::Warning) << "Unhandled BSShaderType " << type << " in " << mFilename; + return std::string_view(); + } + Log(Debug::Warning) << "Unknown BSShaderType " << type << " in " << mFilename; + return std::string_view(); } - const std::string& getBSLightingShaderPrefix(unsigned int type) const + std::string_view getBSLightingShaderPrefix(unsigned int type) const { - static const std::unordered_map mapping = - { - {Nif::BSLightingShaderType::ShaderType_Default, "nv_default"}, - {Nif::BSLightingShaderType::ShaderType_EnvMap, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Glow, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Parallax, std::string()}, - {Nif::BSLightingShaderType::ShaderType_FaceTint, std::string()}, - {Nif::BSLightingShaderType::ShaderType_HairTint, std::string()}, - {Nif::BSLightingShaderType::ShaderType_ParallaxOcc, std::string()}, - {Nif::BSLightingShaderType::ShaderType_MultitexLand, std::string()}, - {Nif::BSLightingShaderType::ShaderType_LODLand, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Snow, std::string()}, - {Nif::BSLightingShaderType::ShaderType_MultiLayerParallax, std::string()}, - {Nif::BSLightingShaderType::ShaderType_TreeAnim, std::string()}, - {Nif::BSLightingShaderType::ShaderType_LODObjects, std::string()}, - {Nif::BSLightingShaderType::ShaderType_SparkleSnow, std::string()}, - {Nif::BSLightingShaderType::ShaderType_LODObjectsHD, std::string()}, - {Nif::BSLightingShaderType::ShaderType_EyeEnvmap, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Cloud, std::string()}, - {Nif::BSLightingShaderType::ShaderType_LODNoise, std::string()}, - {Nif::BSLightingShaderType::ShaderType_MultitexLandLODBlend, std::string()}, - {Nif::BSLightingShaderType::ShaderType_Dismemberment, std::string()} - }; - auto prefix = mapping.find(static_cast(type)); - if (prefix == mapping.end()) - Log(Debug::Warning) << "Unknown BSLightingShaderType " << type << " in " << mFilename; - else if (prefix->second.empty()) - Log(Debug::Warning) << "Unhandled BSLightingShaderType " << type << " in " << mFilename; - else - return prefix->second; - - return mapping.at(Nif::BSLightingShaderType::ShaderType_Default); + switch (static_cast(type)) + { + case Nif::BSLightingShaderType::ShaderType_Default: return "nv_default"; + case Nif::BSLightingShaderType::ShaderType_EnvMap: + case Nif::BSLightingShaderType::ShaderType_Glow: + case Nif::BSLightingShaderType::ShaderType_Parallax: + case Nif::BSLightingShaderType::ShaderType_FaceTint: + case Nif::BSLightingShaderType::ShaderType_SkinTint: + case Nif::BSLightingShaderType::ShaderType_HairTint: + case Nif::BSLightingShaderType::ShaderType_ParallaxOcc: + case Nif::BSLightingShaderType::ShaderType_MultitexLand: + case Nif::BSLightingShaderType::ShaderType_LODLand: + case Nif::BSLightingShaderType::ShaderType_Snow: + case Nif::BSLightingShaderType::ShaderType_MultiLayerParallax: + case Nif::BSLightingShaderType::ShaderType_TreeAnim: + case Nif::BSLightingShaderType::ShaderType_LODObjects: + case Nif::BSLightingShaderType::ShaderType_SparkleSnow: + case Nif::BSLightingShaderType::ShaderType_LODObjectsHD: + case Nif::BSLightingShaderType::ShaderType_EyeEnvmap: + case Nif::BSLightingShaderType::ShaderType_Cloud: + case Nif::BSLightingShaderType::ShaderType_LODNoise: + case Nif::BSLightingShaderType::ShaderType_MultitexLandLODBlend: + case Nif::BSLightingShaderType::ShaderType_Dismemberment: + Log(Debug::Warning) << "Unhandled BSLightingShaderType " << type << " in " << mFilename; + return std::string_view(); + } + Log(Debug::Warning) << "Unknown BSLightingShaderType " << type << " in " << mFilename; + return std::string_view(); } void handleProperty(const Nif::Property *property, @@ -1895,7 +1887,7 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", getBSShaderPrefix(texprop->type)); + node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->textureSet.empty()) @@ -1910,7 +1902,7 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", getBSShaderPrefix(texprop->type)); + node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->filename.empty()) @@ -1952,7 +1944,7 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", getBSLightingShaderPrefix(texprop->type)); + node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->type))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->mTextureSet.empty()) From fdfde836fe58e127521ee5306e4e9c4f6f0c3259 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 12 Feb 2022 01:03:12 +0100 Subject: [PATCH 2103/2859] Expect recent saves to store the modified value --- apps/openmw/mwmechanics/stat.cpp | 4 ++-- apps/openmw/mwworld/magiceffects.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index eacfca98ae..585808645a 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -21,13 +21,13 @@ namespace MWMechanics void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; - state.mMod = mModifier; + state.mMod = mModifier + mBase; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; - mModifier = state.mMod; + mModifier = state.mMod - mBase; } diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 1138a55238..f52a61af52 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -201,10 +201,10 @@ namespace MWWorld { auto& dynamic = creatureStats.mDynamic[i]; dynamic.mCurrent -= dynamic.mMod - dynamic.mBase; - dynamic.mMod = 0.f; + dynamic.mMod = dynamic.mBase; } for(std::size_t i = 0; i < 4; ++i) - creatureStats.mAiSettings[i].mMod = 0.f; + creatureStats.mAiSettings[i].mMod = creatureStats.mAiSettings[i].mBase; if(npcStats) { for(std::size_t i = 0; i < ESM::Skill::Length; ++i) From 68768517666a90c4a02e8a25b2a2b04ec1128b5d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 12 Feb 2022 11:59:39 +0100 Subject: [PATCH 2104/2859] Allow `require` to return not a table in Lua --- components/lua/luastate.cpp | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index bda39d270d..13e1421116 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -162,26 +162,27 @@ namespace LuaUtil std::string envName = namePrefix + "[" + path + "]:"; env["print"] = mLua["printGen"](envName); + auto maybeRunLoader = [&hiddenData](const sol::object& package) -> sol::object + { + if (package.is()) + return call(package.as(), hiddenData); + else + return package; + }; sol::table loaded(mLua, sol::create); for (const auto& [key, value] : mCommonPackages) - loaded[key] = value; + loaded[key] = maybeRunLoader(value); for (const auto& [key, value] : packages) - loaded[key] = value; - env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) + loaded[key] = maybeRunLoader(value); + env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) mutable { - sol::table packages = loaded; - sol::object package = packages[packageName]; - if (package == sol::nil) - { - sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS)); - sol::set_environment(env, packageLoader); - package = call(packageLoader, packageName); - if (!package.is()) - throw std::runtime_error("Lua package must return a table."); - packages[packageName] = package; - } - else if (package.is()) - package = packages[packageName] = call(package.as(), hiddenData); + sol::object package = loaded[packageName]; + if (package != sol::nil) + return package; + sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS)); + sol::set_environment(env, packageLoader); + package = call(packageLoader, packageName); + loaded[packageName] = package; return package; }; From c382910c1fe6ef0d5bb301708e93a700b66a78ef Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 12 Feb 2022 13:25:27 +0100 Subject: [PATCH 2105/2859] Restart all quests with the same name when a quest is restarted --- CHANGELOG.md | 1 + apps/openmw/mwdialogue/journalimp.cpp | 13 +++++++++- apps/openmw/mwdialogue/quest.cpp | 37 +++++++++++---------------- apps/openmw/mwdialogue/quest.hpp | 5 ++-- apps/openmw/mwdialogue/topic.cpp | 5 ++-- apps/openmw/mwdialogue/topic.hpp | 2 +- 6 files changed, 35 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b26d758847..2cc4fee33b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -273,6 +273,7 @@ Bug #6142: Groundcover plugins change cells flags Bug #6276: Deleted groundcover instances are not deleted in game Bug #6294: Game crashes with empty pathgrid + Bug #6606: Quests with multiple IDs cannot always be restarted Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 9f4c8c3689..41b30a95ce 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -93,7 +95,16 @@ namespace MWDialogue StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor); Quest& quest = getQuest (id); - quest.addEntry (entry); // we are doing slicing on purpose here + if(quest.addEntry(entry)) // we are doing slicing on purpose here + { + // Restart all "other" quests with the same name as well + std::string name = quest.getName(); + for(auto& it : mQuests) + { + if(it.second.isFinished() && Misc::StringUtils::ciEqual(it.second.getName(), name)) + it.second.setFinished(false); + } + } // there is no need to show empty entries in journal if (!entry.getText().empty()) diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 16e229ca7f..ce05676965 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,5 +1,7 @@ #include "quest.hpp" +#include + #include #include "../mwworld/esmstore.hpp" @@ -50,42 +52,33 @@ namespace MWDialogue return mFinished; } - void Quest::addEntry (const JournalEntry& entry) + void Quest::setFinished(bool finished) { - int index = -1; + mFinished = finished; + } + bool Quest::addEntry (const JournalEntry& entry) + { const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (entry.mTopic); - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) - if (iter->mId == entry.mInfoId) - { - index = iter->mData.mJournalIndex; - break; - } + auto info = std::find_if(dialogue->mInfo.begin(), dialogue->mInfo.end(), [&](const auto& info) { return info.mId == entry.mInfoId; }); - if (index==-1) + if (info == dialogue->mInfo.end() || info->mData.mJournalIndex == -1) throw std::runtime_error ("unknown journal entry for topic " + mTopic); - for (auto &info : dialogue->mInfo) - { - if (info.mData.mJournalIndex == index - && (info.mQuestStatus == ESM::DialInfo::QS_Finished || info.mQuestStatus == ESM::DialInfo::QS_Restart)) - { - mFinished = (info.mQuestStatus == ESM::DialInfo::QS_Finished); - break; - } - } + if (info->mQuestStatus == ESM::DialInfo::QS_Finished || info->mQuestStatus == ESM::DialInfo::QS_Restart) + mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished; - if (index > mIndex) - mIndex = index; + if (info->mData.mJournalIndex > mIndex) + mIndex = info->mData.mJournalIndex; for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) if (iter->mInfoId==entry.mInfoId) - return; + return info->mQuestStatus == ESM::DialInfo::QS_Restart; mEntries.push_back (entry); // we want slicing here + return info->mQuestStatus == ESM::DialInfo::QS_Restart; } void Quest::write (ESM::QuestState& state) const diff --git a/apps/openmw/mwdialogue/quest.hpp b/apps/openmw/mwdialogue/quest.hpp index 712f94fae4..53b4d02f65 100644 --- a/apps/openmw/mwdialogue/quest.hpp +++ b/apps/openmw/mwdialogue/quest.hpp @@ -33,9 +33,10 @@ namespace MWDialogue ///< Calling this function with a non-existent index will throw an exception. bool isFinished() const; + void setFinished(bool finished); - void addEntry (const JournalEntry& entry) override; - ///< Add entry and adjust index accordingly. + bool addEntry (const JournalEntry& entry) override; + ///< Add entry and adjust index accordingly. Returns true if the quest should be restarted. /// /// \note Redundant entries are ignored, but the index is still adjusted. diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index eb7fbdc1de..9d56028184 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -18,7 +18,7 @@ namespace MWDialogue Topic::~Topic() {} - void Topic::addEntry (const JournalEntry& entry) + bool Topic::addEntry (const JournalEntry& entry) { if (entry.mTopic!=mTopic) throw std::runtime_error ("topic does not match: " + mTopic); @@ -27,10 +27,11 @@ namespace MWDialogue for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it) { if (it->mInfoId == entry.mInfoId) - return; + return false; } mEntries.push_back (entry); // we want slicing here + return false; } void Topic::insertEntry (const ESM::JournalEntry& entry) diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index 72486ef8af..12df484aa7 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -35,7 +35,7 @@ namespace MWDialogue virtual ~Topic(); - virtual void addEntry (const JournalEntry& entry); + virtual bool addEntry (const JournalEntry& entry); ///< Add entry /// /// \note Redundant entries are ignored. From c9c7fb7e49aa65b65005c6e9280b3e8cadf52b25 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 12 Feb 2022 11:52:52 +0100 Subject: [PATCH 2106/2859] Remove redundant functions from Utf8Encoder interface --- components/esm3/esmreader.cpp | 2 +- components/to_utf8/to_utf8.cpp | 30 ++++++++++++++++++------------ components/to_utf8/to_utf8.hpp | 15 ++++----------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index a10eba2378..667132c60f 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -320,7 +320,7 @@ std::string ESMReader::getString(int size) // Convert to UTF8 and return if (mEncoder) - return mEncoder->getUtf8(ptr, size); + return mEncoder->getUtf8(std::string_view(ptr, size)); return std::string (ptr, size); } diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index f7dc33fcbf..3c4421c605 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -77,12 +77,15 @@ Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): } } -std::string Utf8Encoder::getUtf8(const char* input, size_t size) +std::string Utf8Encoder::getUtf8(std::string_view input) { + if (input.empty()) + return input; + // Double check that the input string stops at some point (it might // contain zero terminators before this, inside its own data, which // is also ok.) - assert(input[size] == 0); + assert(input[input.size()] == 0); // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding @@ -93,19 +96,19 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) // Compute output length, and check for pure ascii input at the same // time. bool ascii; - size_t outlen = getLength(input, ascii); + size_t outlen = getLength(input.data(), ascii); // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input, outlen); + return std::string(input.data(), outlen); // Make sure the output is large enough resize(outlen); char *out = &mOutput[0]; // Translate - while (*input) - copyFromArray(*(input++), out); + for (const char* ptr = input.data(); *ptr;) + copyFromArray(*(ptr++), out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); @@ -118,12 +121,15 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) return std::string(&mOutput[0], outlen); } -std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) +std::string Utf8Encoder::getLegacyEnc(std::string_view input) { + if (input.empty()) + return input; + // Double check that the input string stops at some point (it might // contain zero terminators before this, inside its own data, which // is also ok.) - assert(input[size] == 0); + assert(input[input.size()] == 0); // TODO: The rest of this function is designed for single-character // input encodings only. It also assumes that the input the input @@ -134,19 +140,19 @@ std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) // Compute output length, and check for pure ascii input at the same // time. bool ascii; - size_t outlen = getLength2(input, ascii); + size_t outlen = getLength2(input.data(), ascii); // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input, outlen); + return std::string(input.data(), outlen); // Make sure the output is large enough resize(outlen); char *out = &mOutput[0]; // Translate - while(*input) - copyFromArray2(input, out); + for (const char* ptr = input.data(); *ptr;) + copyFromArray2(ptr, out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index d8c9f09d5d..07960ae3f9 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ToUTF8 { @@ -28,17 +29,9 @@ namespace ToUTF8 Utf8Encoder(FromType sourceEncoding); // Convert to UTF8 from the previously given code page. - std::string getUtf8(const char *input, size_t size); - inline std::string getUtf8(const std::string &str) - { - return getUtf8(str.c_str(), str.size()); - } - - std::string getLegacyEnc(const char *input, size_t size); - inline std::string getLegacyEnc(const std::string &str) - { - return getLegacyEnc(str.c_str(), str.size()); - } + std::string getUtf8(std::string_view input); + + std::string getLegacyEnc(std::string_view input); private: void resize(size_t size); From 4502569660d0f1a8bc0aebb89fdf3cd2c7a80a38 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 12 Feb 2022 18:31:52 +0300 Subject: [PATCH 2107/2859] Clarify stretch menu background documentation (#5880) --- docs/source/reference/modding/settings/GUI.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index 00f99e47bc..91d2e67d6c 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -70,7 +70,7 @@ stretch menu background Stretch or shrink the main menu screen, loading splash screens, introductory movie, and cut scenes to fill the specified video resolution, distorting their aspect ratio. The Bethesda provided assets have a 4:3 aspect ratio, but other assets are permitted to have other aspect ratios. -If this setting is false, the assets will be centered in their correct aspect ratio, +If this setting is false, the assets will be centered in the mentioned 4:3 aspect ratio, with black bars filling the remainder of the screen. This setting can be configured in the Interface section of Advanced tab of the launcher. From c75e938c46a660253a6dd76c3545f82ce40f8eb9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 12 Feb 2022 12:00:35 +0100 Subject: [PATCH 2108/2859] Return string_view from Utf8Encoder functions To avoid redundant std::string constructions. --- components/esm3/esmreader.cpp | 2 +- components/esm3/esmwriter.cpp | 4 ++-- components/fontloader/fontloader.cpp | 21 ++++++++++++++------- components/to_utf8/to_utf8.cpp | 14 ++++++-------- components/to_utf8/to_utf8.hpp | 12 ++++++++---- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 667132c60f..165263d6e4 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -320,7 +320,7 @@ std::string ESMReader::getString(int size) // Convert to UTF8 and return if (mEncoder) - return mEncoder->getUtf8(std::string_view(ptr, size)); + return std::string(mEncoder->getUtf8(std::string_view(ptr, size))); return std::string (ptr, size); } diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index f65340f703..d0137c5131 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -193,9 +193,9 @@ namespace ESM else { // Convert to UTF8 and return - std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; + const std::string_view string = mEncoder != nullptr ? mEncoder->getLegacyEnc(data) : data; - write(string.c_str(), string.size()); + write(string.data(), string.size()); } } diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 76f554bec7..36174d0b7e 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -1,6 +1,8 @@ #include "fontloader.hpp" #include +#include +#include #include @@ -26,7 +28,7 @@ namespace { - unsigned long utf8ToUnicode(const std::string& utf8) + unsigned long utf8ToUnicode(std::string_view utf8) { size_t i = 0; unsigned long unicode; @@ -116,16 +118,21 @@ namespace } } - // getUtf8, aka the worst function ever written. - // This includes various hacks for dealing with Morrowind's .fnt files that are *mostly* + // getUnicode includes various hacks for dealing with Morrowind's .fnt files that are *mostly* // in the expected win12XX encoding, but also have randomly swapped characters sometimes. // Looks like the Morrowind developers found standard encodings too boring and threw in some twists for fun. - std::string getUtf8 (unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) + unsigned long getUnicode(unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) { if (encoding == ToUTF8::WINDOWS_1250) // Hack for polish font - return encoder.getUtf8(std::string(1, mapUtf8Char(c))); + { + const std::array str {static_cast(mapUtf8Char(c)), '\0'}; + return utf8ToUnicode(encoder.getUtf8(std::string_view(str.data(), 1))); + } else - return encoder.getUtf8(std::string(1, c)); + { + const std::array str {static_cast(c), '\0'}; + return utf8ToUnicode(encoder.getUtf8(std::string_view(str.data(), 1))); + } } [[noreturn]] void fail (Files::IStreamPtr file, const std::string& fileName, const std::string& message) @@ -355,7 +362,7 @@ namespace Gui float h = data[i].bottom_left.y*height - y1; ToUTF8::Utf8Encoder encoder(mEncoding); - unsigned long unicodeVal = utf8ToUnicode(getUtf8(i, encoder, mEncoding)); + unsigned long unicodeVal = getUnicode(i, encoder, mEncoding); MyGUI::xml::ElementPtr code = codes->createChild("Code"); code->addAttribute("index", unicodeVal); diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 3c4421c605..7fd0e3cd88 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -77,7 +77,7 @@ Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): } } -std::string Utf8Encoder::getUtf8(std::string_view input) +std::string_view Utf8Encoder::getUtf8(std::string_view input) { if (input.empty()) return input; @@ -100,7 +100,7 @@ std::string Utf8Encoder::getUtf8(std::string_view input) // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input.data(), outlen); + return std::string_view(input.data(), outlen); // Make sure the output is large enough resize(outlen); @@ -117,11 +117,10 @@ std::string Utf8Encoder::getUtf8(std::string_view input) assert(mOutput.size() > outlen); assert(mOutput[outlen] == 0); - // Return a string - return std::string(&mOutput[0], outlen); + return std::string_view(mOutput.data(), outlen); } -std::string Utf8Encoder::getLegacyEnc(std::string_view input) +std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) { if (input.empty()) return input; @@ -144,7 +143,7 @@ std::string Utf8Encoder::getLegacyEnc(std::string_view input) // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input.data(), outlen); + return std::string_view(input.data(), outlen); // Make sure the output is large enough resize(outlen); @@ -161,8 +160,7 @@ std::string Utf8Encoder::getLegacyEnc(std::string_view input) assert(mOutput.size() > outlen); assert(mOutput[outlen] == 0); - // Return a string - return std::string(&mOutput[0], outlen); + return std::string_view(mOutput.data(), outlen); } // Make sure the output vector is large enough for 'size' bytes, diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 07960ae3f9..0e9db01e1d 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -28,10 +28,14 @@ namespace ToUTF8 public: Utf8Encoder(FromType sourceEncoding); - // Convert to UTF8 from the previously given code page. - std::string getUtf8(std::string_view input); - - std::string getLegacyEnc(std::string_view input); + /// Convert to UTF8 from the previously given code page. + /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not + /// ASCII-only string. Otherwise returns a view to the input. + std::string_view getUtf8(std::string_view input); + + /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not + /// ASCII-only string. Otherwise returns a view to the input. + std::string_view getLegacyEnc(std::string_view input); private: void resize(size_t size); From 367bdcf0cc5678951b35953d4b8d2722020ab362 Mon Sep 17 00:00:00 2001 From: Matt <3397065-ZehMatt@users.noreply.gitlab.com> Date: Sat, 12 Feb 2022 23:50:41 +0000 Subject: [PATCH 2109/2859] #6091: Optimize isInCombat --- apps/openmw/mwlua/localscripts.cpp | 13 +- apps/openmw/mwmechanics/actors.cpp | 21 +- apps/openmw/mwmechanics/aisequence.cpp | 258 ++++++++++-------- apps/openmw/mwmechanics/aisequence.hpp | 56 +++- .../mwmechanics/mechanicsmanagerimp.cpp | 8 +- apps/openmw/mwmechanics/spelleffects.cpp | 4 +- 6 files changed, 209 insertions(+), 151 deletions(-) diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 552ce77541..c95eae43a7 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -113,15 +113,12 @@ namespace MWLua { const MWWorld::Ptr& ptr = self.ptr(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - std::list>& list = ai.getUnderlyingList(); - for (auto it = list.begin(); it != list.end();) + + ai.erasePackagesIf([&](auto& entry) { - bool keep = LuaUtil::call(callback, *it).get(); - if (keep) - ++it; - else - it = list.erase(it); - } + bool keep = LuaUtil::call(callback, entry).template get(); + return !keep; + }); }; selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2f447e12d6..b6275070e0 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -73,23 +73,20 @@ bool isCommanded(const MWWorld::Ptr& actor) // Check for command effects having ended and remove package if necessary void adjustCommandedActor (const MWWorld::Ptr& actor) { - MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + if (!isCommanded(actor)) + return; - bool hasCommandPackage = false; + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - auto it = stats.getAiSequence().begin(); - for (; it != stats.getAiSequence().end(); ++it) + stats.getAiSequence().erasePackageIf([](auto& entry) { - if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow && - static_cast(it->get())->isCommanded()) + if (entry->getTypeId() == MWMechanics::AiPackageTypeId::Follow && + static_cast(entry.get())->isCommanded()) { - hasCommandPackage = true; - break; + return true; } - } - - if (!isCommanded(actor) && hasCommandPackage) - stats.getAiSequence().erase(it); + return false; + }); } void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 86bc714964..a1f2b5c3e3 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -1,6 +1,7 @@ #include "aisequence.hpp" #include +#include #include #include @@ -29,6 +30,9 @@ void AiSequence::copy (const AiSequence& sequence) // We need to keep an AiWander storage, if present - it has a state machine. // Not sure about another temporary storages sequence.mAiState.copy(mAiState); + + mNumCombatPackages = sequence.mNumCombatPackages; + mNumPursuitPackages = sequence.mNumPursuitPackages; } AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {} @@ -58,6 +62,28 @@ AiSequence::~AiSequence() clear(); } +void AiSequence::onPackageAdded(const AiPackage& package) +{ + if (package.getTypeId() == AiPackageTypeId::Combat) + mNumCombatPackages++; + else if (package.getTypeId() == AiPackageTypeId::Pursue) + mNumPursuitPackages++; + + assert(mNumCombatPackages >= 0); + assert(mNumPursuitPackages >= 0); +} + +void AiSequence::onPackageRemoved(const AiPackage& package) +{ + if (package.getTypeId() == AiPackageTypeId::Combat) + mNumCombatPackages--; + else if (package.getTypeId() == AiPackageTypeId::Pursue) + mNumPursuitPackages--; + + assert(mNumCombatPackages >= 0); + assert(mNumPursuitPackages >= 0); +} + AiPackageTypeId AiSequence::getTypeId() const { if (mPackages.empty()) @@ -87,32 +113,30 @@ bool AiSequence::getCombatTargets(std::vector &targetActors) const return !targetActors.empty(); } -void AiSequence::erase(std::list>::const_iterator package) +AiPackages::iterator AiSequence::erase(AiPackages::iterator package) { // Not sure if manually terminated packages should trigger mDone, probably not? - for(auto it = mPackages.begin(); it != mPackages.end(); ++it) - { - if (package == it) - { - mPackages.erase(it); - return; - } - } - throw std::runtime_error("can't find package to erase"); + auto& ptr = *package; + onPackageRemoved(*ptr); + + return mPackages.erase(package); } bool AiSequence::isInCombat() const { - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) - { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) - return true; - } - return false; + return mNumCombatPackages > 0; +} + +bool AiSequence::isInPursuit() const +{ + return mNumPursuitPackages > 0; } bool AiSequence::isEngagedWithActor() const { + if (!isInCombat()) + return false; + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) @@ -127,16 +151,18 @@ bool AiSequence::isEngagedWithActor() const bool AiSequence::hasPackage(AiPackageTypeId typeId) const { - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + auto it = std::find_if(mPackages.begin(), mPackages.end(), [typeId](const auto& package) { - if ((*it)->getTypeId() == typeId) - return true; - } - return false; + return package->getTypeId() == typeId; + }); + return it != mPackages.end(); } bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const { + if (!isInCombat()) + return false; + for (auto it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackageTypeId::Combat) @@ -148,27 +174,31 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const return false; } -// TODO: use std::list::remove_if for all these methods when we switch to C++20 -void AiSequence::stopCombat() +void AiSequence::removePackagesById(AiPackageTypeId id) { - for(auto it = mPackages.begin(); it != mPackages.end(); ) + for (auto it = mPackages.begin(); it != mPackages.end(); ) { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) + if ((*it)->getTypeId() == id) { - it = mPackages.erase(it); + it = erase(it); } else ++it; } } +void AiSequence::stopCombat() +{ + removePackagesById(AiPackageTypeId::Combat); +} + void AiSequence::stopCombat(const std::vector& targets) { for(auto it = mPackages.begin(); it != mPackages.end(); ) { if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end()) { - it = mPackages.erase(it); + it = erase(it); } else ++it; @@ -177,15 +207,7 @@ void AiSequence::stopCombat(const std::vector& targets) void AiSequence::stopPursuit() { - for(auto it = mPackages.begin(); it != mPackages.end(); ) - { - if ((*it)->getTypeId() == AiPackageTypeId::Pursue) - { - it = mPackages.erase(it); - } - else - ++it; - } + removePackagesById(AiPackageTypeId::Pursue); } bool AiSequence::isPackageDone() const @@ -204,112 +226,117 @@ namespace void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange) { - if(actor != getPlayer()) + if (actor == getPlayer()) { - if (mPackages.empty()) - { - mLastAiPackage = AiPackageTypeId::None; - return; - } + // Players don't use this. + return; + } - auto packageIt = mPackages.begin(); - MWMechanics::AiPackage* package = packageIt->get(); - if (!package->alwaysActive() && outOfRange) - return; + if (mPackages.empty()) + { + mLastAiPackage = AiPackageTypeId::None; + return; + } - auto packageTypeId = package->getTypeId(); - // workaround ai packages not being handled as in the vanilla engine - if (isActualAiPackage(packageTypeId)) - mLastAiPackage = packageTypeId; - // if active package is combat one, choose nearest target - if (packageTypeId == AiPackageTypeId::Combat) - { - auto itActualCombat = mPackages.end(); + auto packageIt = mPackages.begin(); + MWMechanics::AiPackage* package = packageIt->get(); + if (!package->alwaysActive() && outOfRange) + return; + + auto packageTypeId = package->getTypeId(); + // workaround ai packages not being handled as in the vanilla engine + if (isActualAiPackage(packageTypeId)) + mLastAiPackage = packageTypeId; + // if active package is combat one, choose nearest target + if (packageTypeId == AiPackageTypeId::Combat) + { + auto itActualCombat = mPackages.end(); - float nearestDist = std::numeric_limits::max(); - osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); + float nearestDist = std::numeric_limits::max(); + osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); - float bestRating = 0.f; + float bestRating = 0.f; - for (auto it = mPackages.begin(); it != mPackages.end();) - { - if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; + for (auto it = mPackages.begin(); it != mPackages.end();) + { + if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; - MWWorld::Ptr target = (*it)->getTarget(); + MWWorld::Ptr target = (*it)->getTarget(); - // target disappeared (e.g. summoned creatures) - if (target.isEmpty()) - { - it = mPackages.erase(it); - } - else - { - float rating = MWMechanics::getBestActionRating(actor, target); + // target disappeared (e.g. summoned creatures) + if (target.isEmpty()) + { + it = erase(it); + } + else + { + float rating = MWMechanics::getBestActionRating(actor, target); - const ESM::Position &targetPos = target.getRefData().getPosition(); + const ESM::Position &targetPos = target.getRefData().getPosition(); - float distTo = (targetPos.asVec3() - vActorPos).length2(); + float distTo = (targetPos.asVec3() - vActorPos).length2(); - // Small threshold for changing target - if (it == mPackages.begin()) - distTo = std::max(0.f, distTo - 2500.f); + // Small threshold for changing target + if (it == mPackages.begin()) + distTo = std::max(0.f, distTo - 2500.f); - // if a target has higher priority than current target or has same priority but closer - if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating)) - { - nearestDist = distTo; - itActualCombat = it; - bestRating = rating; - } - ++it; + // if a target has higher priority than current target or has same priority but closer + if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating)) + { + nearestDist = distTo; + itActualCombat = it; + bestRating = rating; } + ++it; } + } - assert(!mPackages.empty()); + assert(!mPackages.empty()); - if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) - { - assert(itActualCombat != mPackages.end()); - // move combat package with nearest target to the front - mPackages.splice(mPackages.begin(), mPackages, itActualCombat); - } - - packageIt = mPackages.begin(); - package = packageIt->get(); - packageTypeId = package->getTypeId(); + if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + { + assert(itActualCombat != mPackages.end()); + // move combat package with nearest target to the front + std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat)); } - try + packageIt = mPackages.begin(); + package = packageIt->get(); + packageTypeId = package->getTypeId(); + } + + try + { + if (package->execute(actor, characterController, mAiState, duration)) { - if (package->execute(actor, characterController, mAiState, duration)) - { - // Put repeating noncombat AI packages on the end of the stack so they can be used again - if (isActualAiPackage(packageTypeId) && package->getRepeat()) - { - package->reset(); - mPackages.push_back(package->clone()); - } - // To account for the rare case where AiPackage::execute() queued another AI package - // (e.g. AiPursue executing a dialogue script that uses startCombat) - mPackages.erase(packageIt); - if (isActualAiPackage(packageTypeId)) - mDone = true; - } - else + // Put repeating noncombat AI packages on the end of the stack so they can be used again + if (isActualAiPackage(packageTypeId) && package->getRepeat()) { - mDone = false; + package->reset(); + mPackages.push_back(package->clone()); } + // To account for the rare case where AiPackage::execute() queued another AI package + // (e.g. AiPursue executing a dialogue script that uses startCombat) + erase(packageIt); + if (isActualAiPackage(packageTypeId)) + mDone = true; } - catch (std::exception& e) + else { - Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); + mDone = false; } } + catch (std::exception& e) + { + Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); + } } void AiSequence::clear() { mPackages.clear(); + mNumCombatPackages = 0; + mNumPursuitPackages = 0; } void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther) @@ -353,7 +380,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo { if((*it)->canCancel()) { - it = mPackages.erase(it); + it = erase(it); } else ++it; @@ -373,11 +400,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo if((*it)->getPriority() <= package.getPriority()) { + onPackageAdded(package); mPackages.insert(it, package.clone()); return; } } + onPackageAdded(package); mPackages.push_back(package.clone()); // Make sure that temporary storage is empty @@ -435,6 +464,8 @@ void AiSequence::fill(const ESM::AIPackageList &list) ESM::AITarget data = esmPackage.mTarget; package = std::make_unique(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } + + onPackageAdded(*package); mPackages.push_back(std::move(package)); } } @@ -504,6 +535,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) if (!package.get()) continue; + onPackageAdded(*package); mPackages.push_back(std::move(package)); } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 6cbfcf045d..0d23207b63 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -1,8 +1,9 @@ #ifndef GAME_MWMECHANICS_AISEQUENCE_H #define GAME_MWMECHANICS_AISEQUENCE_H -#include #include +#include +#include #include "aistate.hpp" #include "aipackagetypeid.hpp" @@ -22,8 +23,6 @@ namespace ESM } } - - namespace MWMechanics { class AiPackage; @@ -33,15 +32,20 @@ namespace MWMechanics struct AiTemporaryBase; typedef DerivedClassStorage AiState; + using AiPackages = std::vector>; + /// \brief Sequence of AI-packages for a single actor /** The top-most AI package is run each frame. When completed, it is removed from the stack. **/ class AiSequence { ///AiPackages to run though - std::list> mPackages; + AiPackages mPackages; ///Finished with top AIPackage, set for one frame - bool mDone; + bool mDone{}; + + int mNumCombatPackages{}; + int mNumPursuitPackages{}; ///Copy AiSequence void copy (const AiSequence& sequence); @@ -50,6 +54,11 @@ namespace MWMechanics AiPackageTypeId mLastAiPackage; AiState mAiState; + void onPackageAdded(const AiPackage& package); + void onPackageRemoved(const AiPackage& package); + + AiPackages::iterator erase(AiPackages::iterator package); + public: ///Default constructor AiSequence(); @@ -63,12 +72,31 @@ namespace MWMechanics virtual ~AiSequence(); /// Iterator may be invalidated by any function calls other than begin() or end(). - std::list>::const_iterator begin() const { return mPackages.begin(); } - std::list>::const_iterator end() const { return mPackages.end(); } - - void erase(std::list>::const_iterator package); - - std::list>& getUnderlyingList() { return mPackages; } + AiPackages::const_iterator begin() const { return mPackages.begin(); } + AiPackages::const_iterator end() const { return mPackages.end(); } + + /// Removes all packages controlled by the predicate. + template + void erasePackagesIf(const F&& pred) + { + mPackages.erase(std::remove_if(mPackages.begin(), mPackages.end(), [&](auto& entry) + { + const bool doRemove = pred(entry); + if (doRemove) + onPackageRemoved(*entry); + return doRemove; + }), mPackages.end()); + } + + /// Removes a single package controlled by the predicate. + template + void erasePackageIf(const F&& pred) + { + auto it = std::find_if(mPackages.begin(), mPackages.end(), pred); + if (it == mPackages.end()) + return; + erase(it); + } /// Returns currently executing AiPackage type /** \see enum class AiPackageTypeId **/ @@ -89,6 +117,12 @@ namespace MWMechanics /// Is there any combat package? bool isInCombat () const; + /// Is there any pursuit package. + bool isInPursuit() const; + + /// Removes all packages using the specified id. + void removePackagesById(AiPackageTypeId id); + /// Are we in combat with any other actor, who's also engaging us? bool isEngagedWithActor () const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0816445271..dc349a1e6b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1318,7 +1318,7 @@ namespace MWMechanics // once the bounty has been paid. actor.getClass().getNpcStats(actor).setCrimeId(id); - if (!actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit()) { actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); } @@ -1396,7 +1396,7 @@ namespace MWMechanics { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. - if (!victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit()) startCombat(victim, player); // Set the crime ID, which we will use to calm down participants @@ -1442,7 +1442,7 @@ namespace MWMechanics { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. - if (!target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) + if (!target.getClass().getCreatureStats(target).getAiSequence().isInPursuit()) { // If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player, // he will attack the player only if we will force him (e.g. via StartCombat console command) @@ -1467,7 +1467,7 @@ namespace MWMechanics const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) && !isAggressive(target, attacker) && !seq.isEngagedWithActor() - && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue); + && !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit(); } void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index d0584791a5..3ae6de23da 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -980,12 +980,10 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) { auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); - auto it = std::find_if(seq.begin(), seq.end(), [&](const auto& package) + seq.erasePackageIf([&](const auto& package) { return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast(package.get())->isCommanded(); }); - if(it != seq.end()) - seq.erase(it); } break; case ESM::MagicEffect::ExtraSpell: From 2d4d28fb8e9765cf2a5724a4ed60f26d45c0d095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Sun, 13 Feb 2022 15:30:21 +0000 Subject: [PATCH 2110/2859] Documentation how to get an Animated creature in OpenMW --- .../reference/modding/custom-models/index.rst | 2 +- ...pipeline-blender-collada-animated-creature | 221 ++++++++++++++++++ 2 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature diff --git a/docs/source/reference/modding/custom-models/index.rst b/docs/source/reference/modding/custom-models/index.rst index 8b6c5d87f7..b2d92032c5 100644 --- a/docs/source/reference/modding/custom-models/index.rst +++ b/docs/source/reference/modding/custom-models/index.rst @@ -18,7 +18,7 @@ Below is a quick overview of supported formats, followed by separate articles wi :caption: Table of Contents :maxdepth: 1 - pipeline-blender-collada pipeline-blender-collada-static-models + pipeline-blender-collada-animated-creature pipeline-blender-osgnative pipeline-blender-nif diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature b/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature new file mode 100644 index 0000000000..b759bf9a02 --- /dev/null +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature @@ -0,0 +1,221 @@ +############################# +Animated Creature via COLLADA +############################# + +This tutorial shows how to get an animated creature from Blender to OpenMW using +the COLLADA format. It does not cover using Blender itself, as there are many +better resources for that. The focus is solely on the animation pipeline and its +specific requirements. Most of them are related to how the model, rig, and +animations are set up in Blender. + +.. note:: + This tutorial builds upon the :doc:`pipeline-blender-collada-static-models` tutorial. All fundamentals of exporting static objects apply to animated ones as well. + +Requirements +************ + +To use the Blender to OpenMW pipeline via COLLADA, you will need the following. + +* `OpenMW 0.48 `_ or later +* `Blender 2.83 `_ or later. Latest confirmed, working version is Blender 3.0 +* `OpenMW COLLADA Exporter `_ +* An animated model you would like to export. In our case the flamboyant Land Racer! + +The Land Racer +************** + +The creature, and its revelant files, are available from the `Example Suite repository `_. +This should be useful for further study of how to set things up in case this +tutorial is not clear on any particular thing. + +* ``data/meshes/land_racer.dae`` – exported model +* ``data/meshes/land_racer.txt`` – animation textkeys +* ``data/textures/land_racer.dds`` – diffuse texture +* ``data/textures/land_racer_n.dds`` - normal map +* ``source_assets/land_racer.blend`` – source file configured as this tutorial specifies + +Model +***** + +The model needs to be a child of the rig and have an Armature modifier asigned. +Bone weights are limited to a maximum of 4 bones per vertex. The model needs to +have default location, rotation, and scale. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-in-blender.jpg + :align: center + + +Collision Shape +*************** + +Collision is set up with an empty named ``Collision`` or ``collision`` with a +single child mesh. OpenMW will use the bounding box of this mesh for physics +collision shape so a simple, cuboid mesh is enough. If no collision shape is +defined, the bounding box of the animated model will be used instead. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-collision-shape.jpg + :align: center + + +Armature / Rig +************** + +.. note:: + Only a single rig object should be included in the exported file. Exporting multiple rigs is not reliably supported and can lead to errors. + +Root +---- + +The rig needs to be structured in a specific way. There should be a single top +bone in the rig’s hierarchy, the root bone named ``Bip01``. The name is +required so OpenMW recognizes and uses it for root movement. For this same +reason, the bone should be by default aligned with the world. The root bone +needs to have its ``Deform`` flag **enabled**. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-root-bone.jpg + :align: center + + +Deform Bones +------------ + +Below the root bone, the bones are divided into two branches. One branch +contains the deform bones which get included in the final exported file. These +are otherwise not animated directly but inherit motion from other bones via +constraints. They have their ``Deform`` flag **enabled**. For creatures, the +deform bones can be named as you desire and don’t need to follow the naming +convention used for NPC and player models. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-rig-hierarchy.jpg + :align: center + +Control Bones +------------- + +The other branch holds control and helper bones that enable comfortable +animation in Blender, but are neither required nor included in the exported +file. They have their ``Deform`` flag **disabled**. How these bones are +structured is a big separate topic on its own that this tutorial does not cover, +but you can study the provided source file. + + +Animations +********** + +A creature in OpenMW is expected to have a set of animations to display its +various actions. These animations are recognized and used by their name. + +.. list-table:: + :widths: 25 25 50 + :header-rows: 1 + + * - Animation name + - Possible variations + - Purpose + * - attack1 + - attack2, attack3 + - The creature performs an attack + * - death1 + - death2 - death5 + - The creature dies while upright + * - hit1 + - hit2 - hit5 + - The creature is hit in combat + * - idle + - idle2 - idle9 + - Flavour animations when the creature does nothing in particular + * - knockout + - / + - When creature's fatigue goes below 0 and it staggers to the ground + * - deathknockout + - / + - The creature dies while knocked out + * - knockdown + - / + - When the creatures receives a heavy hit in combat or lands from a considerable height + * - deathknockdown + - / + - The creature dies while knocked down + * - runforward + - / + - Moving forward fast + * - walkforward + - / + - Moving forward at regular speed + +Animating in Blender is done at 30 fps. Specific to how OpenMW works, each +exported animation needs to take a unique range on the timeline. To achieve +this, actions are placed as strips in the NLA editor with an obligatory one +frame gap between them. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-nla-strips.jpg + :align: center + +NLA strips affect the exported result based on their scale, name, frame range, +repetition, or any other factor affecting the end animation result. It's +*What you see is what you get* principle. + +Root movement is required for animations such as ``walkforward`` and +``runforward`` and is likely to work for other animations if needed. +Root movement works only when the root bone is named ``Bip01``. + +Textkeys +******** + +The exported COLLADA file requires a corresponding textkeys file for OpenMW to +properly read the animations. Textkeys is a ``.txt`` file containing animation +definitions and events. At a minimum it needs to include at least animation +``start`` and ``stop`` values in a format as shown in this example. + +.. code:: + + idle: start 0.033333 + idle: stop 2.033333 + walkforward: start 2.066667 + walkforward: stop 3.666667 + runforward: start 3.7 + runforward: stop 4.433333 + attack1: start 4.466667 + attack1: stop 5.433333 + ... + +The textkeys file is placed in the same folder as the model and matches the model's name. + +* ``meshes/land_racer.dae`` +* ``meshes/land_racer.txt`` + +While it's possible to write it by hand, OpenMW's Collada Exporter offers a +convenient option to export a textkeys file based on Blender's timeline markers +(not to be confused with pose markers which are contained per action). What you +need to do is create properly named timeline markers for each animation and +enable the ``Export Textkeys`` option in the exporter. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-textkey-markers.jpg + :align: center + +In the example of ``walkforward`` the timeline markers should be named +``walkforward: start`` and ``walkforward: stop``. + + +Exporter Settings +***************** + +For animated models, use the following exporter settings. Before export, select +all objects you wish to include in the exported file and have the ``Selected +Objects`` option enabled. Without this, the exporter could fail. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-exporter-settings.jpg + :align: center + + +Getting the Model In-game +************************* + +Once the Land Racer is exported, both of its ``.dae`` and ``.txt`` files need to +be placed in the correct folder where OpenMW will read it. Afterwards in +OpenMW-CS, it should be visible in the Assets->Meshes table and can be assigned +to the ``Model/Animation`` field of a creature. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/landracer-in-openmwcs.jpg + :align: center + From 7884a0102632694b524a19ebe21d3e1438742907 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 13 Feb 2022 16:38:49 +0100 Subject: [PATCH 2111/2859] Add tests for Utf8Encoder --- apps/openmw_test_suite/CMakeLists.txt | 6 +- .../toutf8/data}/french-utf8.txt | 0 .../toutf8/data}/french-win1252.txt | 0 .../toutf8/data}/russian-utf8.txt | 0 .../toutf8/data}/russian-win1251.txt | 0 apps/openmw_test_suite/toutf8/toutf8.cpp | 139 ++++++++++++++++++ components/to_utf8/tests/.gitignore | 1 - .../to_utf8/tests/output/to_utf8_test.out | 4 - components/to_utf8/tests/test.sh | 18 --- components/to_utf8/tests/to_utf8_test.cpp | 59 -------- components/to_utf8/to_utf8.cpp | 139 +++++++++--------- components/to_utf8/to_utf8.hpp | 10 +- 12 files changed, 219 insertions(+), 157 deletions(-) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/french-utf8.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/french-win1252.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/russian-utf8.txt (100%) rename {components/to_utf8/tests/test_data => apps/openmw_test_suite/toutf8/data}/russian-win1251.txt (100%) create mode 100644 apps/openmw_test_suite/toutf8/toutf8.cpp delete mode 100644 components/to_utf8/tests/.gitignore delete mode 100644 components/to_utf8/tests/output/to_utf8_test.out delete mode 100755 components/to_utf8/tests/test.sh delete mode 100644 components/to_utf8/tests/to_utf8_test.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 2ee34186d8..16d820c4f9 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -70,6 +70,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) esmloader/esmdata.cpp files/hash.cpp + + toutf8/toutf8.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) @@ -93,6 +95,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) EXPECTED_MD5 bf3691034a38611534c74c3b89a7d2c3 ) - target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data") + target_compile_definitions(openmw_test_suite + PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data" + OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") endif() diff --git a/components/to_utf8/tests/test_data/french-utf8.txt b/apps/openmw_test_suite/toutf8/data/french-utf8.txt similarity index 100% rename from components/to_utf8/tests/test_data/french-utf8.txt rename to apps/openmw_test_suite/toutf8/data/french-utf8.txt diff --git a/components/to_utf8/tests/test_data/french-win1252.txt b/apps/openmw_test_suite/toutf8/data/french-win1252.txt similarity index 100% rename from components/to_utf8/tests/test_data/french-win1252.txt rename to apps/openmw_test_suite/toutf8/data/french-win1252.txt diff --git a/components/to_utf8/tests/test_data/russian-utf8.txt b/apps/openmw_test_suite/toutf8/data/russian-utf8.txt similarity index 100% rename from components/to_utf8/tests/test_data/russian-utf8.txt rename to apps/openmw_test_suite/toutf8/data/russian-utf8.txt diff --git a/components/to_utf8/tests/test_data/russian-win1251.txt b/apps/openmw_test_suite/toutf8/data/russian-win1251.txt similarity index 100% rename from components/to_utf8/tests/test_data/russian-win1251.txt rename to apps/openmw_test_suite/toutf8/data/russian-win1251.txt diff --git a/apps/openmw_test_suite/toutf8/toutf8.cpp b/apps/openmw_test_suite/toutf8/toutf8.cpp new file mode 100644 index 0000000000..bad4f34fd5 --- /dev/null +++ b/apps/openmw_test_suite/toutf8/toutf8.cpp @@ -0,0 +1,139 @@ +#include + +#include + +#include + +#ifndef OPENMW_TEST_SUITE_SOURCE_DIR +#define OPENMW_TEST_SUITE_SOURCE_DIR "" +#endif + +namespace +{ + using namespace testing; + using namespace ToUTF8; + + struct Params + { + FromType mLegacyEncoding; + std::string mLegacyEncodingFileName; + std::string mUtf8FileName; + }; + + std::string readContent(const std::string& fileName) + { + std::ifstream file; + file.exceptions(std::ios::failbit | std::ios::badbit); + file.open(std::string(OPENMW_TEST_SUITE_SOURCE_DIR) + "/toutf8/data/" + fileName); + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); + } + + struct Utf8EncoderTest : TestWithParam {}; + + TEST(Utf8EncoderTest, getUtf8ShouldReturnEmptyAsIs) + { + Utf8Encoder encoder(FromType::CP437); + EXPECT_EQ(encoder.getUtf8(std::string_view()), std::string_view()); + } + + TEST(Utf8EncoderTest, getUtf8ShouldReturnAsciiOnlyAsIs) + { + std::string input; + for (int c = 1; c <= std::numeric_limits::max(); ++c) + input.push_back(c); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getUtf8(input); + EXPECT_EQ(result.data(), input.data()); + EXPECT_EQ(result.size(), input.size()); + } + + TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilZero) + { + const std::string input("a\0b"); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getUtf8(input); + EXPECT_EQ(result, "a"); + } + + TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForAscii) + { + const std::string input("abc"); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2)); + EXPECT_EQ(result, "ab"); + } + + TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForNonAscii) + { + const std::string input("a\x92" "b"); + Utf8Encoder encoder(FromType::WINDOWS_1252); + const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2)); + EXPECT_EQ(result, "a\xE2\x80\x99"); + } + + TEST_P(Utf8EncoderTest, getUtf8ShouldConvertFromLegacyEncodingToUtf8) + { + const std::string input(readContent(GetParam().mLegacyEncodingFileName)); + const std::string expected(readContent(GetParam().mUtf8FileName)); + Utf8Encoder encoder(GetParam().mLegacyEncoding); + const std::string_view result = encoder.getUtf8(input); + EXPECT_EQ(result, expected); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldReturnEmptyAsIs) + { + Utf8Encoder encoder(FromType::CP437); + EXPECT_EQ(encoder.getLegacyEnc(std::string_view()), std::string_view()); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldReturnAsciiOnlyAsIs) + { + std::string input; + for (int c = 1; c <= std::numeric_limits::max(); ++c) + input.push_back(c); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getLegacyEnc(input); + EXPECT_EQ(result.data(), input.data()); + EXPECT_EQ(result.size(), input.size()); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilZero) + { + const std::string input("a\0b"); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getLegacyEnc(input); + EXPECT_EQ(result, "a"); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilEndOfInputForAscii) + { + const std::string input("abc"); + Utf8Encoder encoder(FromType::CP437); + const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 2)); + EXPECT_EQ(result, "ab"); + } + + TEST(Utf8EncoderTest, getLegacyEncShouldStripIncompleteCharacters) + { + const std::string input("a\xc3\xa2\xe2\x80\x99"); + Utf8Encoder encoder(FromType::WINDOWS_1252); + const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 5)); + EXPECT_EQ(result, "a\xe2"); + } + + TEST_P(Utf8EncoderTest, getLegacyEncShouldConvertFromUtf8ToLegacyEncoding) + { + const std::string input(readContent(GetParam().mUtf8FileName)); + const std::string expected(readContent(GetParam().mLegacyEncodingFileName)); + Utf8Encoder encoder(GetParam().mLegacyEncoding); + const std::string_view result = encoder.getLegacyEnc(input); + EXPECT_EQ(result, expected); + } + + INSTANTIATE_TEST_SUITE_P(Files, Utf8EncoderTest, Values( + Params {ToUTF8::WINDOWS_1251, "russian-win1251.txt", "russian-utf8.txt"}, + Params {ToUTF8::WINDOWS_1252, "french-win1252.txt", "french-utf8.txt"} + )); +} diff --git a/components/to_utf8/tests/.gitignore b/components/to_utf8/tests/.gitignore deleted file mode 100644 index 8144904045..0000000000 --- a/components/to_utf8/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_test diff --git a/components/to_utf8/tests/output/to_utf8_test.out b/components/to_utf8/tests/output/to_utf8_test.out deleted file mode 100644 index dcb32359ab..0000000000 --- a/components/to_utf8/tests/output/to_utf8_test.out +++ /dev/null @@ -1,4 +0,0 @@ -original: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? -converted: Без вопросов отдаете ему рулет, зная, что позже вы сможете привести с собой своих друзей и тогда он получит по заслугам? -original: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. -converted: Vous lui donnez le gâteau sans protester avant d’aller chercher tous vos amis et de revenir vous venger. diff --git a/components/to_utf8/tests/test.sh b/components/to_utf8/tests/test.sh deleted file mode 100755 index 2d07708adc..0000000000 --- a/components/to_utf8/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done diff --git a/components/to_utf8/tests/to_utf8_test.cpp b/components/to_utf8/tests/to_utf8_test.cpp deleted file mode 100644 index 3fcddd1581..0000000000 --- a/components/to_utf8/tests/to_utf8_test.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include -#include -#include - -#include "../to_utf8.hpp" - -std::string getFirstLine(const std::string &filename); -void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, - const std::string &utf8File); - -/// Test character encoding conversion to and from UTF-8 -void testEncoder(ToUTF8::FromType encoding, const std::string &legacyEncFile, - const std::string &utf8File) -{ - // get some test data - std::string legacyEncLine = getFirstLine(legacyEncFile); - std::string utf8Line = getFirstLine(utf8File); - - // create an encoder for specified character encoding - ToUTF8::Utf8Encoder encoder (encoding); - - // convert text to UTF-8 - std::string convertedUtf8Line = encoder.getUtf8(legacyEncLine); - - std::cout << "original: " << utf8Line << std::endl; - std::cout << "converted: " << convertedUtf8Line << std::endl; - - // check correctness - assert(convertedUtf8Line == utf8Line); - - // convert UTF-8 text to legacy encoding - std::string convertedLegacyEncLine = encoder.getLegacyEnc(utf8Line); - // check correctness - assert(convertedLegacyEncLine == legacyEncLine); -} - -std::string getFirstLine(const std::string &filename) -{ - std::string line; - std::ifstream text (filename.c_str()); - - if (!text.is_open()) - { - throw std::runtime_error("Unable to open file " + filename); - } - - std::getline(text, line); - text.close(); - - return line; -} - -int main() -{ - testEncoder(ToUTF8::WINDOWS_1251, "test_data/russian-win1251.txt", "test_data/russian-utf8.txt"); - testEncoder(ToUTF8::WINDOWS_1252, "test_data/french-win1252.txt", "test_data/french-utf8.txt"); - return 0; -} diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 7fd0e3cd88..1f0a81ad10 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -44,6 +45,14 @@ using namespace ToUTF8; +namespace +{ + std::string_view::iterator skipAscii(std::string_view input) + { + return std::find_if(input.begin(), input.end(), [] (unsigned char v) { return v == 0 || v >= 128; }); + } +} + Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): mOutput(50*1024) { @@ -82,11 +91,6 @@ std::string_view Utf8Encoder::getUtf8(std::string_view input) if (input.empty()) return input; - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - assert(input[input.size()] == 0); - // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding // shares its first 128 values (0-127) with ASCII. There are no plans @@ -95,8 +99,7 @@ std::string_view Utf8Encoder::getUtf8(std::string_view input) // Compute output length, and check for pure ascii input at the same // time. - bool ascii; - size_t outlen = getLength(input.data(), ascii); + const auto [outlen, ascii] = getLength(input); // If we're pure ascii, then don't bother converting anything. if(ascii) @@ -107,8 +110,8 @@ std::string_view Utf8Encoder::getUtf8(std::string_view input) char *out = &mOutput[0]; // Translate - for (const char* ptr = input.data(); *ptr;) - copyFromArray(*(ptr++), out); + for (auto it = input.begin(); it != input.end() && *it != 0; ++it) + copyFromArray(*it, out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); @@ -125,11 +128,6 @@ std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) if (input.empty()) return input; - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - assert(input[input.size()] == 0); - // TODO: The rest of this function is designed for single-character // input encodings only. It also assumes that the input the input // encoding shares its first 128 values (0-127) with ASCII. These @@ -138,8 +136,7 @@ std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) // Compute output length, and check for pure ascii input at the same // time. - bool ascii; - size_t outlen = getLength2(input.data(), ascii); + const auto [outlen, ascii] = getLengthLegacyEnc(input); // If we're pure ascii, then don't bother converting anything. if(ascii) @@ -150,8 +147,8 @@ std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) char *out = &mOutput[0]; // Translate - for (const char* ptr = input.data(); *ptr;) - copyFromArray2(ptr, out); + for (auto it = input.begin(); it != input.end() && *it != 0;) + copyFromArrayLegacyEnc(it, input.end(), out); // Make sure that we wrote the correct number of bytes assert((out-&mOutput[0]) == (int)outlen); @@ -186,34 +183,30 @@ void Utf8Encoder::resize(size_t size) is the case, then the ascii parameter is set to true, and the caller can optimize for this case. */ -size_t Utf8Encoder::getLength(const char* input, bool &ascii) const +std::pair Utf8Encoder::getLength(std::string_view input) const { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost // always the entire string.) - while (inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. - if (inp) + if (it == input.end() || *it == 0) + return {it - input.begin(), true}; + + std::size_t len = it - input.begin(); + + do { - ascii = false; - while (inp) - { - // Find the translated length of this character in the - // lookup table. - len += translationArray[inp*6]; - inp = *(++ptr); - } + // Find the translated length of this character in the + // lookup table. + len += translationArray[static_cast(*it) * 6]; + ++it; } - return len; + while (it != input.end() && *it != 0); + + return {len, false}; } // Translate one character 'ch' using the translation array 'arr', and @@ -233,51 +226,52 @@ void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const out += len; } -size_t Utf8Encoder::getLength2(const char* input, bool &ascii) const +std::pair Utf8Encoder::getLengthLegacyEnc(std::string_view input) const { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost // always the entire string.) - while (inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. - if (inp) + if (it == input.end() || *it == 0) + return {it - input.begin(), true}; + + std::size_t len = it - input.begin(); + std::size_t symbolLen = 0; + + do { - ascii = false; - while(inp) + symbolLen += 1; + // Find the translated length of this character in the + // lookup table. + switch (static_cast(*it)) { - len += 1; - // Find the translated length of this character in the - // lookup table. - switch(inp) - { - case 0xe2: len -= 2; break; - case 0xc2: - case 0xcb: - case 0xc4: - case 0xc6: - case 0xc3: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xc5: len -= 1; break; - } - - inp = *(++ptr); + case 0xe2: symbolLen -= 2; break; + case 0xc2: + case 0xcb: + case 0xc4: + case 0xc6: + case 0xc3: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xc5: symbolLen -= 1; break; + default: + len += symbolLen; + symbolLen = 0; + break; } + + ++it; } - return len; + while (it != input.end() && *it != 0); + + return {len, false}; } -void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const +void Utf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const { unsigned char ch = *(chp++); // Optimize for ASCII values @@ -308,10 +302,17 @@ void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const return; } + if (chp == end) + return; + unsigned char ch2 = *(chp++); unsigned char ch3 = '\0'; if (len == 3) + { + if (chp == end) + return; ch3 = *(chp++); + } for (int i = 128; i < 256; i++) { diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 0e9db01e1d..794c9148e5 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -38,11 +38,11 @@ namespace ToUTF8 std::string_view getLegacyEnc(std::string_view input); private: - void resize(size_t size); - size_t getLength(const char* input, bool &ascii) const; - void copyFromArray(unsigned char chp, char* &out) const; - size_t getLength2(const char* input, bool &ascii) const; - void copyFromArray2(const char*& chp, char* &out) const; + inline void resize(std::size_t size); + inline std::pair getLength(std::string_view input) const; + inline void copyFromArray(unsigned char chp, char* &out) const; + inline std::pair getLengthLegacyEnc(std::string_view input) const; + inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; std::vector mOutput; const signed char* translationArray; From b43eb29465014eec2a7a8e195925a025392fc346 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 13 Feb 2022 20:13:14 +0100 Subject: [PATCH 2112/2859] Log duration of writing save game file --- apps/openmw/mwstate/statemanagerimp.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 3b459ec2ce..634dcdab53 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -188,6 +188,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot try { + const auto start = std::chrono::steady_clock::now(); + if (!character) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); @@ -302,6 +304,11 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot Settings::Manager::setString ("character", "Saves", slot->mPath.parent_path().filename().string()); + + const auto finish = std::chrono::steady_clock::now(); + + Log(Debug::Info) << '\'' << description << "' is saved in " + << std::chrono::duration_cast>(finish - start).count() << "ms"; } catch (const std::exception& e) { From a4d7b7251122d1b31194ffb69053ef5343c71c7c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 13 Feb 2022 20:45:05 +0100 Subject: [PATCH 2113/2859] Write png image of the global map for save asynchronously Write global map to the save file last to give more time for async job to finish. --- apps/openmw/mwbase/windowmanager.hpp | 2 + apps/openmw/mwgui/mapwindow.cpp | 8 +++- apps/openmw/mwgui/mapwindow.hpp | 4 +- apps/openmw/mwgui/windowmanagerimp.cpp | 5 +++ apps/openmw/mwgui/windowmanagerimp.hpp | 2 + apps/openmw/mwrender/globalmap.cpp | 59 +++++++++++++++++++------ apps/openmw/mwrender/globalmap.hpp | 5 +++ apps/openmw/mwstate/statemanagerimp.cpp | 8 ++-- 8 files changed, 74 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 3044e13e9b..eeb2d6a56b 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -358,6 +358,8 @@ namespace MWBase virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0; virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0; + + virtual void asyncPrepareSaveMap() = 0; }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 2aa1b51950..b5f281d333 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -759,7 +759,7 @@ namespace MWGui , mGlobal(Settings::Manager::getBool("global", "Map")) , mEventBoxGlobal(nullptr) , mEventBoxLocal(nullptr) - , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue)) + , mGlobalMapRender(std::make_unique(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() { static bool registered = false; @@ -1028,7 +1028,6 @@ namespace MWGui MapWindow::~MapWindow() { - delete mGlobalMapRender; } void MapWindow::setCellName(const std::string& cellName) @@ -1357,6 +1356,11 @@ namespace MWGui marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } + void MapWindow::asyncPrepareSaveMap() + { + mGlobalMapRender->asyncWritePng(); + } + // ------------------------------------------------------------------- EditNoteDialog::EditNoteDialog() diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 9f6ea1339d..c50a92dac3 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -260,6 +260,8 @@ namespace MWGui void write (ESM::ESMWriter& writer, Loading::Listener& progress); void readRecord (ESM::ESMReader& reader, uint32_t type); + void asyncPrepareSaveMap(); + private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); @@ -304,7 +306,7 @@ namespace MWGui MyGUI::Button* mEventBoxLocal; float mGlobalMapZoom = 1.0f; - MWRender::GlobalMap* mGlobalMapRender; + std::unique_ptr mGlobalMapRender; struct MapMarkerType { diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index c2f7785da3..04e48fb280 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2263,4 +2263,9 @@ namespace MWGui for(auto* window : mWindows) window->onDeleteCustomData(ptr); } + + void WindowManager::asyncPrepareSaveMap() + { + mMap->asyncPrepareSaveMap(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index ef19329cdf..11d10ab45e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -392,6 +392,8 @@ namespace MWGui void onDeleteCustomData(const MWWorld::Ptr& ptr) override; void forceLootMode(const MWWorld::Ptr& ptr) override; + void asyncPrepareSaveMap() override; + private: unsigned int mOldUpdateMask; unsigned int mOldCullMask; diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 5da79ec037..3d0a066451 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -93,6 +93,26 @@ namespace MWRender::GlobalMap* mParent; }; + std::vector writePng(const osg::Image& overlayImage) + { + std::ostringstream ostream; + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!readerwriter) + { + Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found"; + return std::vector(); + } + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(overlayImage, ostream); + if (!result.success()) + { + Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status(); + return std::vector(); + } + + std::string data = ostream.str(); + return std::vector(data.begin(), data.end()); + } } namespace MWRender @@ -221,6 +241,20 @@ namespace MWRender osg::ref_ptr mOverlayTexture; }; + struct GlobalMap::WritePng final : public SceneUtil::WorkItem + { + osg::ref_ptr mOverlayImage; + std::vector mImageData; + + explicit WritePng(osg::ref_ptr overlayImage) + : mOverlayImage(std::move(overlayImage)) {} + + void doWork() override + { + mImageData = writePng(*mOverlayImage); + } + }; + GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue) : mRoot(root) , mWorkQueue(workQueue) @@ -400,23 +434,15 @@ namespace MWRender map.mBounds.mMinY = mMinY; map.mBounds.mMaxY = mMaxY; - std::ostringstream ostream; - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); - if (!readerwriter) - { - Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found"; - return; - } - - osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream); - if (!result.success()) + if (mWritePng != nullptr) { - Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status(); + mWritePng->waitTillDone(); + map.mImageData = std::move(mWritePng->mImageData); + mWritePng = nullptr; return; } - std::string data = ostream.str(); - map.mImageData = std::vector(data.begin(), data.end()); + map.mImageData = writePng(*mOverlayImage); } struct Box @@ -606,4 +632,11 @@ namespace MWRender cam->removeChildren(0, cam->getNumChildren()); mRoot->removeChild(cam); } + + void GlobalMap::asyncWritePng() + { + // Use deep copy to avoid any sychronization + mWritePng = new WritePng(new osg::Image(*mOverlayImage, osg::CopyOp::DEEP_COPY_ALL)); + mWorkQueue->addWorkItem(mWritePng, /*front=*/true); + } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index fd8a8d1016..28531f14df 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -72,7 +72,11 @@ namespace MWRender void ensureLoaded(); + void asyncWritePng(); + private: + struct WritePng; + /** * Request rendering a 2d quad onto mOverlayTexture. * x, y, width and height are the destination coordinates (top-left coordinate origin) @@ -121,6 +125,7 @@ namespace MWRender osg::ref_ptr mWorkQueue; osg::ref_ptr mWorkItem; + osg::ref_ptr mWritePng; int mWidth; int mHeight; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 634dcdab53..c8cd53a331 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -190,6 +190,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot { const auto start = std::chrono::steady_clock::now(); + MWBase::Environment::get().getWindowManager()->asyncPrepareSaveMap(); + if (!character) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); @@ -257,9 +259,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot +MWBase::Environment::get().getWorld()->countSavedGameRecords() +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() - +MWBase::Environment::get().getWindowManager()->countSavedGameRecords() +MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords() - +MWBase::Environment::get().getInputManager()->countSavedGameRecords(); + +MWBase::Environment::get().getInputManager()->countSavedGameRecords() + +MWBase::Environment::get().getWindowManager()->countSavedGameRecords(); writer.setRecordCount (recordCount); writer.save (stream); @@ -282,9 +284,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::Environment::get().getLuaManager()->write(writer, listener); MWBase::Environment::get().getWorld()->write (writer, listener); MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); - MWBase::Environment::get().getWindowManager()->write(writer, listener); MWBase::Environment::get().getMechanicsManager()->write(writer, listener); MWBase::Environment::get().getInputManager()->write(writer, listener); + MWBase::Environment::get().getWindowManager()->write(writer, listener); // Ensure we have written the number of records that was estimated if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record From d787317df91de048008e7c5ad767bec7a0355f25 Mon Sep 17 00:00:00 2001 From: David Nagy Date: Mon, 14 Feb 2022 11:14:41 +0000 Subject: [PATCH 2114/2859] Update overview.rst (#6598) --- AUTHORS.md | 1 + .../reference/lua-scripting/overview.rst | 35 ++++++++----------- files/builtin_scripts/openmw_aux/time.lua | 2 +- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index ecfc03dc1e..3edba5f25c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -249,6 +249,7 @@ Documentation Joakim Berg (lysol90) Ryan Tucker (Ravenwing) sir_herrbatka + David Nagy (zuzaman) Packagers --------- diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index f270def2fa..e6024a27a1 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -546,10 +546,10 @@ Timers ====== Timers are in the :ref:`openmw.async ` package. -They can be set either in game seconds or in game hours. +They can be set either in simulation time or in game time. -- `Game seconds`: the number of seconds in the game world (i.e. seconds when the game is not paused), passed from starting a new game. -- `Game hours`: current time of the game world in hours. The number of seconds in a game hour is not guaranteed to be fixed. +- `Simulation time`: the number of seconds in the game world (i.e. seconds when the game is not paused), passed from starting a new game. +- `Game time`: current time of the game world in seconds. Note that game time generally goes faster than the simulation time. When the game is paused, all timers are paused as well. @@ -578,7 +578,7 @@ An example: end) local function teleportWithDelay(delay, actor, cellName, pos) - async:newTimerInSeconds(delay, teleportWithDelayCallback, { + async:newSimulationTimer(delay, teleportWithDelayCallback, { actor = actor, destCellName = cellName, destPos = pos, @@ -603,7 +603,7 @@ An example: engineHandlers = { onKeyPress = function(key) if key.symbol == 'x' then - async:newUnsavableTimerInSeconds( + async:newUnsavableSimulationTimer( 10, function() ui.showMessage('You have pressed "X" 10 seconds ago') @@ -613,28 +613,21 @@ An example: } } -Also in `openmw_aux`_ are the helper functions ``runEveryNSeconds`` and ``runEveryNHours``, they are implemented on top of unsavable timers: +Also in `openmw_aux`_ is the helper function ``runRepeatedly``, it is implemented on top of unsavable timers: .. code-block:: Lua - local async = require('openmw.async') local core = require('openmw.core') + local time = require('openmw_aux.time') - -- call `doSomething()` at the end of every game day. - -- `timeBeforeMidnight` is a delay before the first call. `24` is an interval. + -- call `doSomething()` at the end of every game day. + -- the second argument (`time.day`) is the interval. -- the periodical evaluation can be stopped at any moment by calling `stopFn()` - local timeBeforeMidnight = 24 - math.fmod(core.getGameTimeInHours(), 24) - local stopFn = aux_util.runEveryNHours(24, doSomething, timeBeforeMidnight) - - return { - engineHandlers = { - onLoad = function() - -- the timer is unsavable, so we need to restart it in `onLoad`. - timeBeforeMidnight = 24 - math.fmod(core.getGameTimeInHours(), 24) - stopFn = aux_util.runEveryNHours(24, doSomething, timeBeforeMidnight) - end, - } - } + local timeBeforeMidnight = time.day - core.getGameTime() % time.day + local stopFn = time.runRepeatedly(doSomething, time.day, { + initialDelay = timeBeforeMidnight, + type = time.GameTime, + }) Queries diff --git a/files/builtin_scripts/openmw_aux/time.lua b/files/builtin_scripts/openmw_aux/time.lua index a6aac0fa1a..1c543c53b8 100644 --- a/files/builtin_scripts/openmw_aux/time.lua +++ b/files/builtin_scripts/openmw_aux/time.lua @@ -66,7 +66,7 @@ end -- function() print('Test2') end, 5 * time.minute, -- { initialDelay = 30 * time.second }) -- @usage --- local timeBeforeMidnight = time.day - time.gameTime() % time.day +-- local timeBeforeMidnight = time.day - core.getGameTime() % time.day -- time.runRepeatedly(doSomething, time.day, { -- initialDelay = timeBeforeMidnight, -- type = time.GameTime, From 649c2f828659f559a821209a9f30c4f85cab2dcf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 14 Feb 2022 18:38:37 +0100 Subject: [PATCH 2115/2859] Fix stats not working right for saves started before version 17 --- apps/openmw/mwmechanics/stat.cpp | 4 ++-- apps/openmw/mwworld/cellstore.cpp | 5 +++++ apps/openmw/mwworld/magiceffects.cpp | 13 +++++++++++-- apps/openmw/mwworld/magiceffects.hpp | 2 ++ apps/openmw/mwworld/player.cpp | 2 ++ components/esm3/savedgame.cpp | 2 +- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 585808645a..eacfca98ae 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -21,13 +21,13 @@ namespace MWMechanics void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; - state.mMod = mModifier + mBase; + state.mMod = mModifier; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; - mModifier = state.mMod - mBase; + mModifier = state.mMod; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b0c2ea3a23..75136eb915 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -185,6 +185,11 @@ namespace else if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); } + else if(state.mVersion < 20) + { + if constexpr (std::is_same_v || std::is_same_v) + MWWorld::convertStats(state.mCreatureStats); + } if (state.mRef.mRefNum.hasContentFile()) { diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index f52a61af52..44c4061832 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -201,14 +201,23 @@ namespace MWWorld { auto& dynamic = creatureStats.mDynamic[i]; dynamic.mCurrent -= dynamic.mMod - dynamic.mBase; - dynamic.mMod = dynamic.mBase; + dynamic.mMod = 0.f; } for(std::size_t i = 0; i < 4; ++i) - creatureStats.mAiSettings[i].mMod = creatureStats.mAiSettings[i].mBase; + creatureStats.mAiSettings[i].mMod = 0.f; if(npcStats) { for(std::size_t i = 0; i < ESM::Skill::Length; ++i) npcStats->mSkills[i].mMod = 0.f; } } + + // Versions 17-19 wrote different modifiers to the savegame depending on whether the save had upgraded from a pre-17 version or not + void convertStats(ESM::CreatureStats& creatureStats) + { + for(std::size_t i = 0; i < 3; ++i) + creatureStats.mDynamic[i].mMod = 0.f; + for(std::size_t i = 0; i < 4; ++i) + creatureStats.mAiSettings[i].mMod = 0.f; + } } diff --git a/apps/openmw/mwworld/magiceffects.hpp b/apps/openmw/mwworld/magiceffects.hpp index 31d5ed2038..fdcc578e53 100644 --- a/apps/openmw/mwworld/magiceffects.hpp +++ b/apps/openmw/mwworld/magiceffects.hpp @@ -12,6 +12,8 @@ namespace MWWorld { void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr); + + void convertStats(ESM::CreatureStats& creatureStats); } #endif diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 7062366697..45e87a7942 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -383,6 +383,8 @@ namespace MWWorld } if (reader.getFormat() < 17) convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats); + else if(reader.getFormat() < 20) + convertStats(player.mObject.mCreatureStats); if (!player.mObject.mEnabled) { diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 6cecf26489..18498abe2c 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 19; +int ESM::SavedGame::sCurrentFormat = 20; void ESM::SavedGame::load (ESMReader &esm) { From 071ab3f6508890080ae38a7b13a141f9292276a2 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 14 Feb 2022 19:56:50 +0100 Subject: [PATCH 2116/2859] Fix out of bounds access for std::string_view --- components/fontloader/fontloader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 36174d0b7e..d0125398f1 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -30,6 +30,8 @@ namespace { unsigned long utf8ToUnicode(std::string_view utf8) { + if (utf8.empty()) + return 0; size_t i = 0; unsigned long unicode; size_t numbytes; From fbbf8710673057801b7063d4b7d6d36baf1c4e11 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 14 Feb 2022 22:36:07 +0100 Subject: [PATCH 2117/2859] Avoid extra copy for Utf8Encoder::getUtf8 result --- apps/mwiniimporter/importer.cpp | 36 ++++++++++++++------------ components/translation/translation.cpp | 12 ++++----- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 35d9701803..f7523b11cf 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -663,49 +663,51 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::p std::string line; while (std::getline(file, line)) { - line = encoder.getUtf8(line); + std::string_view utf8 = encoder.getUtf8(line); // unify Unix-style and Windows file ending - if (!(line.empty()) && (line[line.length()-1]) == '\r') { - line = line.substr(0, line.length()-1); + if (!(utf8.empty()) && (utf8[utf8.length()-1]) == '\r') { + utf8 = utf8.substr(0, utf8.length()-1); } - if(line.empty()) { + if(utf8.empty()) { continue; } - if(line[0] == '[') { - int pos = static_cast(line.find(']')); + if(utf8[0] == '[') { + int pos = static_cast(utf8.find(']')); if(pos < 2) { - std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl; + std::cout << "Warning: ini file wrongly formatted (" << utf8 << "). Line ignored." << std::endl; continue; } - section = line.substr(1, line.find(']')-1); + section = utf8.substr(1, utf8.find(']')-1); continue; } - int comment_pos = static_cast(line.find(';')); + int comment_pos = static_cast(utf8.find(';')); if(comment_pos > 0) { - line = line.substr(0,comment_pos); + utf8 = utf8.substr(0,comment_pos); } - int pos = static_cast(line.find('=')); + int pos = static_cast(utf8.find('=')); if(pos < 1) { continue; } - std::string key(section + ":" + line.substr(0,pos)); - std::string value(line.substr(pos+1)); + std::string key(section + ":" + std::string(utf8.substr(0, pos))); + const std::string_view value(utf8.substr(pos+1)); if(value.empty()) { std::cout << "Warning: ignored empty value for key '" << key << "'." << std::endl; continue; } - if(map.find(key) == map.end()) { - map.insert( std::make_pair (key, std::vector() ) ); - } - map[key].push_back(value); + auto it = map.find(key); + + if (it == map.end()) + it = map.emplace_hint(it, std::move(key), std::vector()); + + it->second.push_back(std::string(value)); } return map; diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index ef0f432075..f2cdd803e7 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -53,16 +53,16 @@ namespace Translation if (!line.empty()) { - line = mEncoder->getUtf8(line); + const std::string_view utf8 = mEncoder->getUtf8(line); - size_t tab_pos = line.find('\t'); - if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) + size_t tab_pos = utf8.find('\t'); + if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < utf8.size() - 1) { - std::string key = line.substr(0, tab_pos); - std::string value = line.substr(tab_pos + 1); + const std::string_view key = utf8.substr(0, tab_pos); + const std::string_view value = utf8.substr(tab_pos + 1); if (!key.empty() && !value.empty()) - container.insert(std::make_pair(key, value)); + container.emplace(key, value); } } } From c044bef6a77edeea44d35ac908841cbe49610b75 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 14 Feb 2022 22:26:01 +0100 Subject: [PATCH 2118/2859] Add StatelessUtf8Encoder to support caller provided buffer for output --- apps/opencs/editor.cpp | 2 +- .../contentselector/model/contentmodel.cpp | 3 +- components/to_utf8/to_utf8.cpp | 131 ++++++++++-------- components/to_utf8/to_utf8.hpp | 41 ++++-- 4 files changed, 105 insertions(+), 72 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 1d5934fe5d..07154f6d55 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -370,7 +370,7 @@ int CS::Editor::run() else { ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder = ToUTF8::calculateEncoding(mEncodingName); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName)); fileReader.setEncoder(&encoder); fileReader.open(mFileToLoad.string()); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 57dfe0f87e..f7cedc83a4 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -445,8 +445,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) try { ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder = - ToUTF8::calculateEncoding(mEncoding.toStdString()); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); fileReader.setEncoder(&encoder); fileReader.open(std::string(dir.absoluteFilePath(path2).toUtf8().constData())); diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 1f0a81ad10..a193e6375d 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -51,42 +51,52 @@ namespace { return std::find_if(input.begin(), input.end(), [] (unsigned char v) { return v == 0 || v >= 128; }); } -} -Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): - mOutput(50*1024) -{ - switch (sourceEncoding) + std::basic_string_view getTranslationArray(FromType sourceEncoding) { - case ToUTF8::WINDOWS_1252: + switch (sourceEncoding) { - translationArray = ToUTF8::windows_1252; - break; - } - case ToUTF8::WINDOWS_1250: - { - translationArray = ToUTF8::windows_1250; - break; - } - case ToUTF8::WINDOWS_1251: - { - translationArray = ToUTF8::windows_1251; - break; - } - case ToUTF8::CP437: - { - translationArray = ToUTF8::cp437; - break; + case ToUTF8::WINDOWS_1252: + return ToUTF8::windows_1252; + case ToUTF8::WINDOWS_1250: + return ToUTF8::windows_1250; + case ToUTF8::WINDOWS_1251: + return ToUTF8::windows_1251; + case ToUTF8::CP437: + return ToUTF8::cp437; } + throw std::logic_error("Invalid source encoding: " + std::to_string(sourceEncoding)); + } - default: + // Make sure the output vector is large enough for 'size' bytes, + // including a terminating zero after it. + void resize(std::size_t size, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) + { + if (buffer.size() >= size) + return; + + switch (bufferAllocationPolicy) { - assert(0); + case BufferAllocationPolicy::FitToRequiredSize: + buffer.resize(size); + break; + case BufferAllocationPolicy::UseGrowFactor: + // Add some extra padding to reduce the chance of having to resize + // again later. + buffer.resize(3 * size); + // And make sure the string is zero terminated + buffer[size] = 0; + break; } } } -std::string_view Utf8Encoder::getUtf8(std::string_view input) +StatelessUtf8Encoder::StatelessUtf8Encoder(FromType sourceEncoding) + : mTranslationArray(getTranslationArray(sourceEncoding)) +{ +} + +std::string_view StatelessUtf8Encoder::getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { if (input.empty()) return input; @@ -106,24 +116,24 @@ std::string_view Utf8Encoder::getUtf8(std::string_view input) return std::string_view(input.data(), outlen); // Make sure the output is large enough - resize(outlen); - char *out = &mOutput[0]; + resize(outlen, bufferAllocationPolicy, buffer); + char *out = buffer.data(); // Translate for (auto it = input.begin(); it != input.end() && *it != 0; ++it) copyFromArray(*it, out); // Make sure that we wrote the correct number of bytes - assert((out-&mOutput[0]) == (int)outlen); + assert((out - buffer.data()) == (int)outlen); // And make extra sure the output is null terminated - assert(mOutput.size() > outlen); - assert(mOutput[outlen] == 0); + assert(buffer.size() >= outlen); + assert(buffer[outlen] == 0); - return std::string_view(mOutput.data(), outlen); + return std::string_view(buffer.data(), outlen); } -std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) +std::string_view StatelessUtf8Encoder::getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { if (input.empty()) return input; @@ -143,34 +153,21 @@ std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) return std::string_view(input.data(), outlen); // Make sure the output is large enough - resize(outlen); - char *out = &mOutput[0]; + resize(outlen, bufferAllocationPolicy, buffer); + char *out = buffer.data(); // Translate for (auto it = input.begin(); it != input.end() && *it != 0;) copyFromArrayLegacyEnc(it, input.end(), out); // Make sure that we wrote the correct number of bytes - assert((out-&mOutput[0]) == (int)outlen); + assert((out - buffer.data()) == static_cast(outlen)); // And make extra sure the output is null terminated - assert(mOutput.size() > outlen); - assert(mOutput[outlen] == 0); + assert(buffer.size() >= outlen); + assert(buffer[outlen] == 0); - return std::string_view(mOutput.data(), outlen); -} - -// Make sure the output vector is large enough for 'size' bytes, -// including a terminating zero after it. -void Utf8Encoder::resize(size_t size) -{ - if (mOutput.size() <= size) - // Add some extra padding to reduce the chance of having to resize - // again later. - mOutput.resize(3*size); - - // And make sure the string is zero terminated - mOutput[size] = 0; + return std::string_view(buffer.data(), outlen); } /** Get the total length length needed to decode the given string with @@ -183,7 +180,7 @@ void Utf8Encoder::resize(size_t size) is the case, then the ascii parameter is set to true, and the caller can optimize for this case. */ -std::pair Utf8Encoder::getLength(std::string_view input) const +std::pair StatelessUtf8Encoder::getLength(std::string_view input) const { // Do away with the ascii part of the string first (this is almost // always the entire string.) @@ -201,7 +198,7 @@ std::pair Utf8Encoder::getLength(std::string_view input) cons { // Find the translated length of this character in the // lookup table. - len += translationArray[static_cast(*it) * 6]; + len += mTranslationArray[static_cast(*it) * 6]; ++it; } while (it != input.end() && *it != 0); @@ -211,7 +208,7 @@ std::pair Utf8Encoder::getLength(std::string_view input) cons // Translate one character 'ch' using the translation array 'arr', and // advance the output pointer accordingly. -void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const +void StatelessUtf8Encoder::copyFromArray(unsigned char ch, char* &out) const { // Optimize for ASCII values if (ch < 128) @@ -220,13 +217,13 @@ void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const return; } - const signed char *in = translationArray + ch*6; + const signed char *in = &mTranslationArray[ch * 6]; int len = *(in++); memcpy(out, in, len); out += len; } -std::pair Utf8Encoder::getLengthLegacyEnc(std::string_view input) const +std::pair StatelessUtf8Encoder::getLengthLegacyEnc(std::string_view input) const { // Do away with the ascii part of the string first (this is almost // always the entire string.) @@ -271,7 +268,7 @@ std::pair Utf8Encoder::getLengthLegacyEnc(std::string_view in return {len, false}; } -void Utf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const +void StatelessUtf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const { unsigned char ch = *(chp++); // Optimize for ASCII values @@ -316,7 +313,7 @@ void Utf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::s for (int i = 128; i < 256; i++) { - unsigned char b1 = translationArray[i*6 + 1], b2 = translationArray[i*6 + 2], b3 = translationArray[i*6 + 3]; + unsigned char b1 = mTranslationArray[i*6 + 1], b2 = mTranslationArray[i*6 + 2], b3 = mTranslationArray[i*6 + 3]; if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) { *(out++) = (char)i; @@ -329,6 +326,22 @@ void Utf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::s *(out++) = ch; // Could not find glyph, just put whatever } +Utf8Encoder::Utf8Encoder(FromType sourceEncoding) + : mBuffer(50 * 1024, '\0') + , mImpl(sourceEncoding) +{ +} + +std::string_view Utf8Encoder::getUtf8(std::string_view input) +{ + return mImpl.getUtf8(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); +} + +std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) +{ + return mImpl.getLegacyEnc(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); +} + ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) { if (encodingName == "win1250") diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 794c9148e5..037e3ea3bf 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -18,34 +18,55 @@ namespace ToUTF8 CP437 // Used for fonts (*.fnt) if data files encoding is 1252. Otherwise, uses the same encoding as the data files. }; + enum class BufferAllocationPolicy + { + FitToRequiredSize, + UseGrowFactor, + }; + FromType calculateEncoding(const std::string& encodingName); std::string encodingUsingMessage(const std::string& encodingName); - // class + class StatelessUtf8Encoder + { + public: + explicit StatelessUtf8Encoder(FromType sourceEncoding); + + /// Convert to UTF8 from the previously given code page. + /// Returns a view to passed buffer that will be resized to fit output if it's too small. + std::string_view getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; + + /// Convert from UTF-8 to sourceEncoding. + /// Returns a view to passed buffer that will be resized to fit output if it's too small. + std::string_view getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; + + private: + inline std::pair getLength(std::string_view input) const; + inline void copyFromArray(unsigned char chp, char* &out) const; + inline std::pair getLengthLegacyEnc(std::string_view input) const; + inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; + + const std::basic_string_view mTranslationArray; + }; class Utf8Encoder { public: - Utf8Encoder(FromType sourceEncoding); + explicit Utf8Encoder(FromType sourceEncoding); /// Convert to UTF8 from the previously given code page. /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not /// ASCII-only string. Otherwise returns a view to the input. std::string_view getUtf8(std::string_view input); + /// Convert from UTF-8 to sourceEncoding. /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not /// ASCII-only string. Otherwise returns a view to the input. std::string_view getLegacyEnc(std::string_view input); private: - inline void resize(std::size_t size); - inline std::pair getLength(std::string_view input) const; - inline void copyFromArray(unsigned char chp, char* &out) const; - inline std::pair getLengthLegacyEnc(std::string_view input) const; - inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; - - std::vector mOutput; - const signed char* translationArray; + std::string mBuffer; + StatelessUtf8Encoder mImpl; }; } From 3305b400dcbeff24c39247c3317f1a88be1d296b Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 28 Jan 2022 18:40:17 +0100 Subject: [PATCH 2119/2859] Use ESM::NAME instead of const char* and std::string as argument type --- apps/esmtool/esmtool.cpp | 6 +-- .../esm/test_fixed_string.cpp | 31 ++++++++------ components/esm/esmcommon.hpp | 42 +++++++++++++++++++ components/esm3/activespells.cpp | 4 +- components/esm3/aipackage.cpp | 2 +- components/esm3/cellref.cpp | 8 ++-- components/esm3/cellref.hpp | 5 ++- components/esm3/esmreader.cpp | 16 +++---- components/esm3/esmreader.hpp | 22 +++++----- components/esm3/esmwriter.cpp | 31 +++++--------- components/esm3/esmwriter.hpp | 32 +++++++------- components/esm3/loadlevlist.hpp | 16 ++++--- components/esm3/weatherstate.cpp | 22 +++++----- 13 files changed, 137 insertions(+), 100 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 4fe0b248af..a7946c77a6 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -484,9 +484,9 @@ int clone(Arguments& info) if (i <= 0) break; - const ESM::NAME& typeName = record->getType(); + const ESM::NAME typeName = record->getType(); - esm.startRecord(typeName.toString(), record->getFlags()); + esm.startRecord(typeName, record->getFlags()); record->save(esm); if (typeName.toInt() == ESM::REC_CELL) { @@ -498,7 +498,7 @@ int clone(Arguments& info) } } - esm.endRecord(typeName.toString()); + esm.endRecord(typeName); saved++; int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount)*100); diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index 26fb1590d5..1189f667e5 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -1,12 +1,12 @@ #include #include "components/esm/esmcommon.hpp" +#include "components/esm/defs.hpp" TEST(EsmFixedString, operator__eq_ne) { { SCOPED_TRACE("asdc == asdc"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); char s[4] = {'a', 's', 'd', 'c'}; std::string ss(s, 4); @@ -16,8 +16,7 @@ TEST(EsmFixedString, operator__eq_ne) } { SCOPED_TRACE("asdc == asdcx"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); char s[5] = {'a', 's', 'd', 'c', 'x'}; std::string ss(s, 5); @@ -27,8 +26,7 @@ TEST(EsmFixedString, operator__eq_ne) } { SCOPED_TRACE("asdc == asdc[NULL]"); - ESM::NAME name; - name.assign("asdc"); + const ESM::NAME name("asdc"); char s[5] = {'a', 's', 'd', 'c', '\0'}; std::string ss(s, 5); @@ -41,8 +39,7 @@ TEST(EsmFixedString, operator__eq_ne_const) { { SCOPED_TRACE("asdc == asdc (const)"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); const char s[4] = { 'a', 's', 'd', 'c' }; std::string ss(s, 4); @@ -52,8 +49,7 @@ TEST(EsmFixedString, operator__eq_ne_const) } { SCOPED_TRACE("asdc == asdcx (const)"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); const char s[5] = { 'a', 's', 'd', 'c', 'x' }; std::string ss(s, 5); @@ -63,8 +59,7 @@ TEST(EsmFixedString, operator__eq_ne_const) } { SCOPED_TRACE("asdc == asdc[NULL] (const)"); - ESM::NAME name; - name.assign("asdc"); + constexpr ESM::NAME name("asdc"); const char s[5] = { 'a', 's', 'd', 'c', '\0' }; std::string ss(s, 5); @@ -148,3 +143,15 @@ TEST(EsmFixedString, assignment_operator_is_supported_for_uint32) value = static_cast(0xFEDCBA98u); EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); } + +TEST(EsmFixedString, construction_from_uint32_is_supported) +{ + constexpr ESM::NAME value(0xFEDCBA98u); + EXPECT_EQ(value, static_cast(0xFEDCBA98u)) << value.toInt(); +} + +TEST(EsmFixedString, construction_from_RecNameInts_is_supported) +{ + constexpr ESM::NAME value(ESM::RecNameInts::REC_ACTI); + EXPECT_EQ(value, static_cast(ESM::RecNameInts::REC_ACTI)) << value.toInt(); +} diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 921eeed744..7ca059020f 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace ESM { @@ -30,6 +32,41 @@ struct FixedString char mData[capacity]; + FixedString() = default; + + template + constexpr FixedString(const char (&value)[size]) noexcept + : mData() + { + if constexpr (capacity == sizeof(std::uint32_t)) + { + static_assert(capacity == size || capacity + 1 == size); + if constexpr (capacity + 1 == size) + assert(value[capacity] == '\0'); + for (std::size_t i = 0; i < capacity; ++i) + mData[i] = value[i]; + } + else + { + const std::size_t length = std::min(capacity, size); + for (std::size_t i = 0; i < length; ++i) + mData[i] = value[i]; + mData[std::min(capacity - 1, length)] = '\0'; + } + } + + constexpr explicit FixedString(std::uint32_t value) noexcept + : mData() + { + static_assert(capacity == sizeof(std::uint32_t)); + for (std::size_t i = 0; i < capacity; ++i) + mData[i] = static_cast((value >> (i * std::numeric_limits::digits)) & std::numeric_limits::max()); + } + + template + constexpr explicit FixedString(T value) noexcept + : FixedString(static_cast(value)) {} + std::string_view toStringView() const noexcept { return std::string_view(mData, strnlen(mData, capacity)); @@ -116,6 +153,11 @@ inline bool operator==(const FixedString<4>& lhs, std::uint32_t rhs) noexcept return lhs.toInt() == rhs; } +inline bool operator==(const FixedString<4>& lhs, const FixedString<4>& rhs) noexcept +{ + return lhs.toInt() == rhs.toInt(); +} + template inline bool operator!=(const FixedString& lhs, const Rhs& rhs) noexcept { diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 22f862b6e4..61be6a7838 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -5,7 +5,7 @@ namespace { - void save(ESM::ESMWriter& esm, const std::vector& spells, const std::string& tag) + void save(ESM::ESMWriter& esm, const std::vector& spells, ESM::NAME tag) { for (const auto& params : spells) { @@ -38,7 +38,7 @@ namespace } } - void load(ESM::ESMReader& esm, std::vector& spells, const char* tag) + void load(ESM::ESMReader& esm, std::vector& spells, ESM::NAME tag) { int format = esm.getFormat(); diff --git a/components/esm3/aipackage.cpp b/components/esm3/aipackage.cpp index fa20d271c0..5a95e58ca8 100644 --- a/components/esm3/aipackage.cpp +++ b/components/esm3/aipackage.cpp @@ -65,7 +65,7 @@ namespace ESM case AI_Escort: case AI_Follow: { - const char *name = (it->mType == AI_Escort) ? "AI_E" : "AI_F"; + const ESM::NAME name = (it->mType == AI_Escort) ? ESM::NAME("AI_E") : ESM::NAME("AI_F"); esm.writeHNT(name, it->mTarget, sizeof(it->mTarget)); esm.writeHNOCString("CNDT", it->mCellName); break; diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 002a885d92..8487b3c0f0 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -5,15 +5,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) +void ESM::RefNum::load(ESMReader& esm, bool wide, ESM::NAME tag) { if (wide) - esm.getHNT (*this, tag.c_str(), 8); + esm.getHNT(*this, tag, 8); else - esm.getHNT (mIndex, tag.c_str()); + esm.getHNT(mIndex, tag); } -void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const +void ESM::RefNum::save(ESMWriter &esm, bool wide, ESM::NAME tag) const { if (wide) esm.writeHNT (tag, *this, 8); diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 55e4b700fc..cff635f455 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -5,6 +5,7 @@ #include #include "components/esm/defs.hpp" +#include "components/esm/esmcommon.hpp" namespace ESM { @@ -18,9 +19,9 @@ namespace ESM unsigned int mIndex; int mContentFile; - void load (ESMReader& esm, bool wide = false, const std::string& tag = "FRMR"); + void load(ESMReader& esm, bool wide = false, ESM::NAME tag = "FRMR"); - void save (ESMWriter &esm, bool wide = false, const std::string& tag = "FRMR") const; + void save(ESMWriter &esm, bool wide = false, ESM::NAME tag = "FRMR") const; inline bool hasContentFile() const { return mContentFile >= 0; } diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 165263d6e4..47974e45a8 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -113,14 +113,14 @@ void ESMReader::open(const std::string &file) open (Files::openConstrainedFileStream (file.c_str ()), file); } -std::string ESMReader::getHNOString(const char* name) +std::string ESMReader::getHNOString(NAME name) { if (isNextSub(name)) return getHString(); return ""; } -std::string ESMReader::getHNString(const char* name) +std::string ESMReader::getHNString(NAME name) { getSubNameIs(name); return getHString(); @@ -156,21 +156,21 @@ void ESMReader::getHExact(void*p, int size) } // Read the given number of bytes from a named subrecord -void ESMReader::getHNExact(void*p, int size, const char* name) +void ESMReader::getHNExact(void*p, int size, NAME name) { getSubNameIs(name); getHExact(p, size); } // Get the next subrecord name and check if it matches the parameter -void ESMReader::getSubNameIs(const char* name) +void ESMReader::getSubNameIs(NAME name) { getSubName(); if (mCtx.subName != name) - fail("Expected subrecord " + std::string(name) + " but got " + mCtx.subName.toString()); + fail("Expected subrecord " + name.toString() + " but got " + mCtx.subName.toString()); } -bool ESMReader::isNextSub(const char* name) +bool ESMReader::isNextSub(NAME name) { if (!hasMoreSubs()) return false; @@ -185,7 +185,7 @@ bool ESMReader::isNextSub(const char* name) return !mCtx.subCached; } -bool ESMReader::peekNextSub(const char *name) +bool ESMReader::peekNextSub(NAME name) { if (!hasMoreSubs()) return false; @@ -226,7 +226,7 @@ void ESMReader::skipHSubSize(int size) reportSubSizeMismatch(mCtx.leftSub, size); } -void ESMReader::skipHSubUntil(const char *name) +void ESMReader::skipHSubUntil(NAME name) { while (hasMoreSubs() && !isNextSub(name)) { diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 3b384dc603..e370946ee9 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -98,7 +98,7 @@ public: // Read data of a given type, stored in a subrecord of a given name template - void getHNT(X &x, const char* name) + void getHNT(X &x, NAME name) { getSubNameIs(name); getHT(x); @@ -106,7 +106,7 @@ public: // Optional version of getHNT template - void getHNOT(X &x, const char* name) + void getHNOT(X &x, NAME name) { if(isNextSub(name)) getHT(x); @@ -115,7 +115,7 @@ public: // Version with extra size checking, to make sure the compiler // doesn't mess up our struct padding. template - void getHNT(X &x, const char* name, int size) + void getHNT(X &x, NAME name, int size) { assert(sizeof(X) == size); getSubNameIs(name); @@ -123,7 +123,7 @@ public: } template - void getHNOT(X &x, const char* name, int size) + void getHNOT(X &x, NAME name, int size) { assert(sizeof(X) == size); if(isNextSub(name)) @@ -150,10 +150,10 @@ public: } // Read a string by the given name if it is the next record. - std::string getHNOString(const char* name); + std::string getHNOString(NAME name); // Read a string with the given sub-record name - std::string getHNString(const char* name); + std::string getHNString(NAME name); // Read a string, including the sub-record header (but not the name) std::string getHString(); @@ -162,7 +162,7 @@ public: void getHExact(void*p, int size); // Read the given number of bytes from a named subrecord - void getHNExact(void*p, int size, const char* name); + void getHNExact(void*p, int size, NAME name); /************************************************************************* * @@ -171,16 +171,16 @@ public: *************************************************************************/ // Get the next subrecord name and check if it matches the parameter - void getSubNameIs(const char* name); + void getSubNameIs(NAME name); /** Checks if the next sub record name matches the parameter. If it does, it is read into 'subName' just as if getSubName() was called. If not, the read name will still be available for future calls to getSubName(), isNextSub() and getSubNameIs(). */ - bool isNextSub(const char* name); + bool isNextSub(NAME name); - bool peekNextSub(const char* name); + bool peekNextSub(NAME name); // Store the current subrecord name for the next call of getSubName() void cacheSubName() {mCtx.subCached = true; }; @@ -197,7 +197,7 @@ public: void skipHSubSize(int size); // Skip all subrecords until the given subrecord or no more subrecords remaining - void skipHSubUntil(const char* name); + void skipHSubUntil(NAME name); /* Sub-record header. This updates leftRec beyond the current sub-record as well. leftSub contains size of current sub-record. diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index d0137c5131..77ba5fc6aa 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -86,7 +86,7 @@ namespace ESM throw std::runtime_error ("Unclosed record remaining"); } - void ESMWriter::startRecord(const std::string& name, uint32_t flags) + void ESMWriter::startRecord(ESM::NAME name, uint32_t flags) { mRecordCount++; @@ -105,15 +105,10 @@ namespace ESM void ESMWriter::startRecord (uint32_t name, uint32_t flags) { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic - type += reinterpret_cast (&name)[i]; - - startRecord (type, flags); + startRecord(ESM::NAME(name), flags); } - void ESMWriter::startSubRecord(const std::string& name) + void ESMWriter::startSubRecord(ESM::NAME name) { // Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. assert (mRecords.size() <= 1); @@ -129,7 +124,7 @@ namespace ESM assert(mRecords.back().size == 0); } - void ESMWriter::endRecord(const std::string& name) + void ESMWriter::endRecord(ESM::NAME name) { RecordData rec = mRecords.back(); assert(rec.name == name); @@ -147,22 +142,17 @@ namespace ESM void ESMWriter::endRecord (uint32_t name) { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic - type += reinterpret_cast (&name)[i]; - - endRecord (type); + endRecord(ESM::NAME(name)); } - void ESMWriter::writeHNString(const std::string& name, const std::string& data) + void ESMWriter::writeHNString(ESM::NAME name, const std::string& data) { startSubRecord(name); writeHString(data); endRecord(name); } - void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) + void ESMWriter::writeHNString(ESM::NAME name, const std::string& data, size_t size) { assert(data.size() <= size); startSubRecord(name); @@ -177,7 +167,7 @@ namespace ESM endRecord(name); } - void ESMWriter::writeFixedSizeString(const std::string &data, int size) + void ESMWriter::writeFixedSizeString(const std::string& data, int size) { std::string string; if (!data.empty()) @@ -206,10 +196,9 @@ namespace ESM write("\0", 1); } - void ESMWriter::writeName(const std::string& name) + void ESMWriter::writeName(ESM::NAME name) { - assert((name.size() == 4 && name[3] != '\0')); - write(name.c_str(), name.size()); + write(name.mData, ESM::NAME::sCapacity); } void ESMWriter::write(const char* data, size_t size) diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 3073daf15e..8ee507f39d 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -19,7 +19,7 @@ class ESMWriter { struct RecordData { - std::string name; + ESM::NAME name; std::streampos position; uint32_t size; }; @@ -56,27 +56,27 @@ class ESMWriter void close(); ///< \note Does not close the stream. - void writeHNString(const std::string& name, const std::string& data); - void writeHNString(const std::string& name, const std::string& data, size_t size); - void writeHNCString(const std::string& name, const std::string& data) + void writeHNString(ESM::NAME name, const std::string& data); + void writeHNString(ESM::NAME name, const std::string& data, size_t size); + void writeHNCString(ESM::NAME name, const std::string& data) { startSubRecord(name); writeHCString(data); endRecord(name); } - void writeHNOString(const std::string& name, const std::string& data) + void writeHNOString(ESM::NAME name, const std::string& data) { if (!data.empty()) writeHNString(name, data); } - void writeHNOCString(const std::string& name, const std::string& data) + void writeHNOCString(ESM::NAME name, const std::string& data) { if (!data.empty()) writeHNCString(name, data); } template - void writeHNT(const std::string& name, const T& data) + void writeHNT(ESM::NAME name, const T& data) { startSubRecord(name); writeT(data); @@ -84,7 +84,7 @@ class ESMWriter } template - void writeHNT(const std::string& name, const T (&data)[size]) + void writeHNT(ESM::NAME name, const T (&data)[size]) { startSubRecord(name); writeT(data); @@ -94,15 +94,15 @@ class ESMWriter // Prevent using writeHNT with strings. This already happened by accident and results in // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. - void writeHNT(const std::string& name, const std::string& data) = delete; + void writeHNT(ESM::NAME name, const std::string& data) = delete; - void writeT(const std::string& data) = delete; + void writeT(ESM::NAME data) = delete; template - void writeHNT(const std::string& name, const T (&data)[size], int) = delete; + void writeHNT(ESM::NAME name, const T (&data)[size], int) = delete; template - void writeHNT(const std::string& name, const T& data, int size) + void writeHNT(ESM::NAME name, const T& data, int size) { startSubRecord(name); writeT(data, size); @@ -129,16 +129,16 @@ class ESMWriter write((char*)&data, size); } - void startRecord(const std::string& name, uint32_t flags = 0); + void startRecord(ESM::NAME name, uint32_t flags = 0); void startRecord(uint32_t name, uint32_t flags = 0); /// @note Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. - void startSubRecord(const std::string& name); - void endRecord(const std::string& name); + void startSubRecord(ESM::NAME name); + void endRecord(ESM::NAME name); void endRecord(uint32_t name); void writeFixedSizeString(const std::string& data, int size); void writeHString(const std::string& data); void writeHCString(const std::string& data); - void writeName(const std::string& data); + void writeName(ESM::NAME data); void write(const char* data, size_t size); private: diff --git a/components/esm3/loadlevlist.hpp b/components/esm3/loadlevlist.hpp index 0ebd7a64cb..aea85b20e0 100644 --- a/components/esm3/loadlevlist.hpp +++ b/components/esm3/loadlevlist.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace ESM { @@ -27,7 +29,7 @@ struct LevelledListBase // Record name used to read references. Must be set before load() is // called. - const char *mRecName; + ESM::NAME mRecName; struct LevelItem { @@ -37,6 +39,8 @@ struct LevelledListBase std::vector mList; + explicit LevelledListBase(ESM::NAME recName) : mRecName(recName) {} + void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; @@ -58,10 +62,7 @@ struct CreatureLevList: LevelledListBase // player. }; - CreatureLevList() - { - mRecName = "CNAM"; - } + CreatureLevList() : LevelledListBase("CNAM") {} }; struct ItemLevList: LevelledListBase @@ -84,10 +85,7 @@ struct ItemLevList: LevelledListBase // player. }; - ItemLevList() - { - mRecName = "INAM"; - } + ItemLevList() : LevelledListBase("INAM") {} }; } diff --git a/components/esm3/weatherstate.cpp b/components/esm3/weatherstate.cpp index cd1a82b0b7..c791aac145 100644 --- a/components/esm3/weatherstate.cpp +++ b/components/esm3/weatherstate.cpp @@ -5,17 +5,17 @@ namespace { - const char* currentRegionRecord = "CREG"; - const char* timePassedRecord = "TMPS"; - const char* fastForwardRecord = "FAST"; - const char* weatherUpdateTimeRecord = "WUPD"; - const char* transitionFactorRecord = "TRFC"; - const char* currentWeatherRecord = "CWTH"; - const char* nextWeatherRecord = "NWTH"; - const char* queuedWeatherRecord = "QWTH"; - const char* regionNameRecord = "RGNN"; - const char* regionWeatherRecord = "RGNW"; - const char* regionChanceRecord = "RGNC"; + constexpr ESM::NAME currentRegionRecord = "CREG"; + constexpr ESM::NAME timePassedRecord = "TMPS"; + constexpr ESM::NAME fastForwardRecord = "FAST"; + constexpr ESM::NAME weatherUpdateTimeRecord = "WUPD"; + constexpr ESM::NAME transitionFactorRecord = "TRFC"; + constexpr ESM::NAME currentWeatherRecord = "CWTH"; + constexpr ESM::NAME nextWeatherRecord = "NWTH"; + constexpr ESM::NAME queuedWeatherRecord = "QWTH"; + constexpr ESM::NAME regionNameRecord = "RGNN"; + constexpr ESM::NAME regionWeatherRecord = "RGNW"; + constexpr ESM::NAME regionChanceRecord = "RGNC"; } namespace ESM From f9da792386eb4692dcc3a87e0665ecb2d2da7cc6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 15 Feb 2022 17:25:07 +0100 Subject: [PATCH 2120/2859] Force a scale update when changing view modes --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 6 +++--- apps/openmw/mwworld/worldimp.hpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1d9e4fae2d..0b55484278 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -293,7 +293,7 @@ namespace MWBase virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec) = 0; ///< @return an updated Ptr - virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; + virtual void scaleObject (const MWWorld::Ptr& ptr, float scale, bool force = false) = 0; virtual void rotateObject(const MWWorld::Ptr& ptr, const osg::Vec3f& rot, RotationFlags flags = RotationFlag_inverseOrder) = 0; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 47aeaf53af..3fdfaed72d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -296,7 +296,7 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) return; mViewMode = viewMode; - MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change + MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change mAmmunition.reset(); rebuild(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 61abb7fcc2..a5ab0968ee 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1281,9 +1281,9 @@ namespace MWWorld return moveObject(ptr, newpos); } - void World::scaleObject (const Ptr& ptr, float scale) + void World::scaleObject (const Ptr& ptr, float scale, bool force) { - if (scale == ptr.getCellRef().getScale()) + if (!force && scale == ptr.getCellRef().getScale()) return; if (mPhysics->getActor(ptr)) mNavigator->removeAgent(getPathfindingHalfExtents(ptr)); @@ -2482,7 +2482,7 @@ namespace MWWorld player.getClass().getInventoryStore(player).setInvListener(anim, player); player.getClass().getInventoryStore(player).setContListener(anim); - scaleObject(player, player.getCellRef().getScale()); // apply race height + scaleObject(player, player.getCellRef().getScale(), true); // apply race height rotateObject(player, osg::Vec3f(), MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust); MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr()); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 84d97ce37c..61154afb59 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -377,7 +377,7 @@ namespace MWWorld MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec) override; ///< @return an updated Ptr - void scaleObject (const Ptr& ptr, float scale) override; + void scaleObject (const Ptr& ptr, float scale, bool force = false) override; /// World rotates object, uses radians /// @note Rotations via this method use a different rotation order than the initial rotations in the CS. This From 875d9dcead1490406e42276fd7e52f53b342fff9 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 15 Feb 2022 22:54:23 +0100 Subject: [PATCH 2121/2859] Fix buffer resizing by StatelessUtf8Encoder --- apps/openmw_test_suite/toutf8/toutf8.cpp | 20 ++++++++++++++++++++ components/to_utf8/to_utf8.cpp | 8 +++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/toutf8/toutf8.cpp b/apps/openmw_test_suite/toutf8/toutf8.cpp index bad4f34fd5..d1cf6b2851 100644 --- a/apps/openmw_test_suite/toutf8/toutf8.cpp +++ b/apps/openmw_test_suite/toutf8/toutf8.cpp @@ -136,4 +136,24 @@ namespace Params {ToUTF8::WINDOWS_1251, "russian-win1251.txt", "russian-utf8.txt"}, Params {ToUTF8::WINDOWS_1252, "french-win1252.txt", "french-utf8.txt"} )); + + TEST(StatelessUtf8EncoderTest, shouldCleanupBuffer) + { + std::string buffer; + StatelessUtf8Encoder encoder(FromType::WINDOWS_1252); + encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::UseGrowFactor, buffer); + const std::string shortString("short\x92"); + ASSERT_GT(buffer.size(), shortString.size()); + const std::string_view shortUtf8 = encoder.getUtf8(shortString, BufferAllocationPolicy::UseGrowFactor, buffer); + ASSERT_GE(buffer.size(), shortUtf8.size()); + EXPECT_EQ(buffer[shortUtf8.size()], '\0') << buffer; + } + + TEST(StatelessUtf8EncoderTest, withFitToRequiredSizeShouldResizeBuffer) + { + std::string buffer; + StatelessUtf8Encoder encoder(FromType::WINDOWS_1252); + const std::string_view utf8 = encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::FitToRequiredSize, buffer); + EXPECT_EQ(buffer.size(), utf8.size()); + } } diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index a193e6375d..f4b52b644b 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -72,7 +72,13 @@ namespace // including a terminating zero after it. void resize(std::size_t size, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) { - if (buffer.size() >= size) + if (buffer.size() > size) + { + buffer[size] = 0; + return; + } + + if (buffer.size() == size) return; switch (bufferAllocationPolicy) From 2e38f0b641c44ef8ff3139d2b93fd288c487871e Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 16 Feb 2022 02:58:21 +0100 Subject: [PATCH 2122/2859] Fix btAABB initialization --- apps/navmeshtool/worldspacedata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 81bbd857f5..50e529c6e5 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -178,7 +178,7 @@ namespace NavMeshTool static_cast(cellPosition.y() * ESM::Land::REAL_SIZE), minHeight ); - aabb.m_min = btVector3( + aabb.m_max = btVector3( static_cast((cellPosition.x() + 1) * ESM::Land::REAL_SIZE), static_cast((cellPosition.y() + 1) * ESM::Land::REAL_SIZE), maxHeight From 4a06351c3b0745b7f38955e0ba56f612eaa22462 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 16 Feb 2022 10:48:49 +0100 Subject: [PATCH 2123/2859] update to_utf8 and translation to make use of new stateless utf8 --- components/to_utf8/to_utf8.cpp | 280 +++++++++++++------------ components/to_utf8/to_utf8.hpp | 63 +++--- components/translation/translation.cpp | 11 +- 3 files changed, 185 insertions(+), 169 deletions(-) diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 04edfda09d..a193e6375d 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -44,55 +45,62 @@ using namespace ToUTF8; -Utf8Encoder::Utf8Encoder(const FromType sourceEncoding): - mOutput(50*1024) +namespace { - switch (sourceEncoding) + std::string_view::iterator skipAscii(std::string_view input) { - case ToUTF8::WINDOWS_1252: - { - translationArray = ToUTF8::windows_1252; - break; - } - case ToUTF8::WINDOWS_1250: - { - translationArray = ToUTF8::windows_1250; - break; - } - case ToUTF8::WINDOWS_1251: - { - translationArray = ToUTF8::windows_1251; - break; - } - case ToUTF8::CP437: + return std::find_if(input.begin(), input.end(), [] (unsigned char v) { return v == 0 || v >= 128; }); + } + + std::basic_string_view getTranslationArray(FromType sourceEncoding) + { + switch (sourceEncoding) { - translationArray = ToUTF8::cp437; - break; + case ToUTF8::WINDOWS_1252: + return ToUTF8::windows_1252; + case ToUTF8::WINDOWS_1250: + return ToUTF8::windows_1250; + case ToUTF8::WINDOWS_1251: + return ToUTF8::windows_1251; + case ToUTF8::CP437: + return ToUTF8::cp437; } + throw std::logic_error("Invalid source encoding: " + std::to_string(sourceEncoding)); + } + + // Make sure the output vector is large enough for 'size' bytes, + // including a terminating zero after it. + void resize(std::size_t size, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) + { + if (buffer.size() >= size) + return; - default: + switch (bufferAllocationPolicy) { - assert(0); + case BufferAllocationPolicy::FitToRequiredSize: + buffer.resize(size); + break; + case BufferAllocationPolicy::UseGrowFactor: + // Add some extra padding to reduce the chance of having to resize + // again later. + buffer.resize(3 * size); + // And make sure the string is zero terminated + buffer[size] = 0; + break; } } } -std::string Utf8Encoder::getUtf8(const char* input, size_t size) +StatelessUtf8Encoder::StatelessUtf8Encoder(FromType sourceEncoding) + : mTranslationArray(getTranslationArray(sourceEncoding)) { - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - assert(input[size] == 0); - - std::string inputString(input, size); - std::string output; - toUtf8(inputString, output, size); - - return output; } -void Utf8Encoder::toUtf8(std::string& input, std::string& output, size_t size) +std::string_view StatelessUtf8Encoder::getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { + if (input.empty()) + return input; + // Note: The rest of this function is designed for single-character // input encodings only. It also assumes that the input encoding // shares its first 128 values (0-127) with ASCII. There are no plans @@ -101,45 +109,34 @@ void Utf8Encoder::toUtf8(std::string& input, std::string& output, size_t size) // Compute output length, and check for pure ascii input at the same // time. - bool ascii; - size_t outlen = getLength(input.c_str(), ascii); + const auto [outlen, ascii] = getLength(input); // If we're pure ascii, then don't bother converting anything. if(ascii) - { - std::swap(input, output); - return; - } + return std::string_view(input.data(), outlen); // Make sure the output is large enough - if (output.size() <= outlen) - // Add some extra padding to reduce the chance of having to resize - // again later. - output.resize(3*outlen); - - // And make sure the string is zero terminated - output[outlen] = 0; - char *in = &input[0]; - char *out = &output[0]; + resize(outlen, bufferAllocationPolicy, buffer); + char *out = buffer.data(); // Translate - while (*in) - copyFromArray(*(in++), out); + for (auto it = input.begin(); it != input.end() && *it != 0; ++it) + copyFromArray(*it, out); // Make sure that we wrote the correct number of bytes - assert((out-&output[0]) == (int)outlen); + assert((out - buffer.data()) == (int)outlen); // And make extra sure the output is null terminated - assert(output.size() > outlen); - assert(output[outlen] == 0); + assert(buffer.size() >= outlen); + assert(buffer[outlen] == 0); + + return std::string_view(buffer.data(), outlen); } -std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) +std::string_view StatelessUtf8Encoder::getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const { - // Double check that the input string stops at some point (it might - // contain zero terminators before this, inside its own data, which - // is also ok.) - assert(input[size] == 0); + if (input.empty()) + return input; // TODO: The rest of this function is designed for single-character // input encodings only. It also assumes that the input the input @@ -149,43 +146,28 @@ std::string Utf8Encoder::getLegacyEnc(const char *input, size_t size) // Compute output length, and check for pure ascii input at the same // time. - bool ascii; - size_t outlen = getLength2(input, ascii); + const auto [outlen, ascii] = getLengthLegacyEnc(input); // If we're pure ascii, then don't bother converting anything. if(ascii) - return std::string(input, outlen); + return std::string_view(input.data(), outlen); // Make sure the output is large enough - resize(outlen); - char *out = &mOutput[0]; + resize(outlen, bufferAllocationPolicy, buffer); + char *out = buffer.data(); // Translate - while(*input) - copyFromArray2(input, out); + for (auto it = input.begin(); it != input.end() && *it != 0;) + copyFromArrayLegacyEnc(it, input.end(), out); // Make sure that we wrote the correct number of bytes - assert((out-&mOutput[0]) == (int)outlen); + assert((out - buffer.data()) == static_cast(outlen)); // And make extra sure the output is null terminated - assert(mOutput.size() > outlen); - assert(mOutput[outlen] == 0); - - // Return a string - return std::string(&mOutput[0], outlen); -} - -// Make sure the output vector is large enough for 'size' bytes, -// including a terminating zero after it. -void Utf8Encoder::resize(size_t size) -{ - if (mOutput.size() <= size) - // Add some extra padding to reduce the chance of having to resize - // again later. - mOutput.resize(3*size); + assert(buffer.size() >= outlen); + assert(buffer[outlen] == 0); - // And make sure the string is zero terminated - mOutput[size] = 0; + return std::string_view(buffer.data(), outlen); } /** Get the total length length needed to decode the given string with @@ -198,39 +180,35 @@ void Utf8Encoder::resize(size_t size) is the case, then the ascii parameter is set to true, and the caller can optimize for this case. */ -size_t Utf8Encoder::getLength(const char* input, bool &ascii) const +std::pair StatelessUtf8Encoder::getLength(std::string_view input) const { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost // always the entire string.) - while (inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. - if (inp) + if (it == input.end() || *it == 0) + return {it - input.begin(), true}; + + std::size_t len = it - input.begin(); + + do { - ascii = false; - while (inp) - { - // Find the translated length of this character in the - // lookup table. - len += translationArray[inp*6]; - inp = *(++ptr); - } + // Find the translated length of this character in the + // lookup table. + len += mTranslationArray[static_cast(*it) * 6]; + ++it; } - return len; + while (it != input.end() && *it != 0); + + return {len, false}; } // Translate one character 'ch' using the translation array 'arr', and // advance the output pointer accordingly. -void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const +void StatelessUtf8Encoder::copyFromArray(unsigned char ch, char* &out) const { // Optimize for ASCII values if (ch < 128) @@ -239,57 +217,58 @@ void Utf8Encoder::copyFromArray(unsigned char ch, char* &out) const return; } - const signed char *in = translationArray + ch*6; + const signed char *in = &mTranslationArray[ch * 6]; int len = *(in++); memcpy(out, in, len); out += len; } -size_t Utf8Encoder::getLength2(const char* input, bool &ascii) const +std::pair StatelessUtf8Encoder::getLengthLegacyEnc(std::string_view input) const { - ascii = true; - size_t len = 0; - const char* ptr = input; - unsigned char inp = *ptr; - // Do away with the ascii part of the string first (this is almost // always the entire string.) - while (inp && inp < 128) - inp = *(++ptr); - len += (ptr-input); + auto it = skipAscii(input); // If we're not at the null terminator at this point, then there // were some non-ascii characters to deal with. Go to slow-mode for // the rest of the string. - if (inp) + if (it == input.end() || *it == 0) + return {it - input.begin(), true}; + + std::size_t len = it - input.begin(); + std::size_t symbolLen = 0; + + do { - ascii = false; - while(inp) + symbolLen += 1; + // Find the translated length of this character in the + // lookup table. + switch (static_cast(*it)) { - len += 1; - // Find the translated length of this character in the - // lookup table. - switch(inp) - { - case 0xe2: len -= 2; break; - case 0xc2: - case 0xcb: - case 0xc4: - case 0xc6: - case 0xc3: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xc5: len -= 1; break; - } - - inp = *(++ptr); + case 0xe2: symbolLen -= 2; break; + case 0xc2: + case 0xcb: + case 0xc4: + case 0xc6: + case 0xc3: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xc5: symbolLen -= 1; break; + default: + len += symbolLen; + symbolLen = 0; + break; } + + ++it; } - return len; + while (it != input.end() && *it != 0); + + return {len, false}; } -void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const +void StatelessUtf8Encoder::copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const { unsigned char ch = *(chp++); // Optimize for ASCII values @@ -320,14 +299,21 @@ void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const return; } + if (chp == end) + return; + unsigned char ch2 = *(chp++); unsigned char ch3 = '\0'; if (len == 3) + { + if (chp == end) + return; ch3 = *(chp++); + } for (int i = 128; i < 256; i++) { - unsigned char b1 = translationArray[i*6 + 1], b2 = translationArray[i*6 + 2], b3 = translationArray[i*6 + 3]; + unsigned char b1 = mTranslationArray[i*6 + 1], b2 = mTranslationArray[i*6 + 2], b3 = mTranslationArray[i*6 + 3]; if (b1 == ch && b2 == ch2 && (len != 3 || b3 == ch3)) { *(out++) = (char)i; @@ -340,6 +326,22 @@ void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) const *(out++) = ch; // Could not find glyph, just put whatever } +Utf8Encoder::Utf8Encoder(FromType sourceEncoding) + : mBuffer(50 * 1024, '\0') + , mImpl(sourceEncoding) +{ +} + +std::string_view Utf8Encoder::getUtf8(std::string_view input) +{ + return mImpl.getUtf8(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); +} + +std::string_view Utf8Encoder::getLegacyEnc(std::string_view input) +{ + return mImpl.getLegacyEnc(input, BufferAllocationPolicy::UseGrowFactor, mBuffer); +} + ToUTF8::FromType ToUTF8::calculateEncoding(const std::string& encodingName) { if (encodingName == "win1250") diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 23dc09c06e..037e3ea3bf 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ToUTF8 { @@ -17,41 +18,55 @@ namespace ToUTF8 CP437 // Used for fonts (*.fnt) if data files encoding is 1252. Otherwise, uses the same encoding as the data files. }; + enum class BufferAllocationPolicy + { + FitToRequiredSize, + UseGrowFactor, + }; + FromType calculateEncoding(const std::string& encodingName); std::string encodingUsingMessage(const std::string& encodingName); - // class + class StatelessUtf8Encoder + { + public: + explicit StatelessUtf8Encoder(FromType sourceEncoding); + + /// Convert to UTF8 from the previously given code page. + /// Returns a view to passed buffer that will be resized to fit output if it's too small. + std::string_view getUtf8(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; + + /// Convert from UTF-8 to sourceEncoding. + /// Returns a view to passed buffer that will be resized to fit output if it's too small. + std::string_view getLegacyEnc(std::string_view input, BufferAllocationPolicy bufferAllocationPolicy, std::string& buffer) const; + + private: + inline std::pair getLength(std::string_view input) const; + inline void copyFromArray(unsigned char chp, char* &out) const; + inline std::pair getLengthLegacyEnc(std::string_view input) const; + inline void copyFromArrayLegacyEnc(std::string_view::iterator& chp, std::string_view::iterator end, char* &out) const; + + const std::basic_string_view mTranslationArray; + }; class Utf8Encoder { public: - Utf8Encoder(FromType sourceEncoding); - - // Convert to UTF8 from the previously given code page. - std::string getUtf8(const char *input, size_t size); - inline std::string getUtf8(const std::string &str) - { - return getUtf8(str.c_str(), str.size()); - } + explicit Utf8Encoder(FromType sourceEncoding); - // Convert input to UTF8 to the given output string - void toUtf8(std::string& input, std::string& output, size_t size); + /// Convert to UTF8 from the previously given code page. + /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not + /// ASCII-only string. Otherwise returns a view to the input. + std::string_view getUtf8(std::string_view input); - std::string getLegacyEnc(const char *input, size_t size); - inline std::string getLegacyEnc(const std::string &str) - { - return getLegacyEnc(str.c_str(), str.size()); - } + /// Convert from UTF-8 to sourceEncoding. + /// Returns a view to internal buffer invalidate by next getUtf8 or getLegacyEnc call if input is not + /// ASCII-only string. Otherwise returns a view to the input. + std::string_view getLegacyEnc(std::string_view input); private: - void resize(size_t size); - size_t getLength(const char* input, bool &ascii) const; - void copyFromArray(unsigned char chp, char* &out) const; - size_t getLength2(const char* input, bool &ascii) const; - void copyFromArray2(const char*& chp, char* &out) const; - - std::vector mOutput; - const signed char* translationArray; + std::string mBuffer; + StatelessUtf8Encoder mImpl; }; } diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index 37068fd70d..ef0f432075 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -53,14 +53,13 @@ namespace Translation if (!line.empty()) { - std::string utf8Line; - mEncoder->toUtf8(line, utf8Line, line.size()); + line = mEncoder->getUtf8(line); - size_t tab_pos = utf8Line.find('\t'); - if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < utf8Line.size() - 1) + size_t tab_pos = line.find('\t'); + if (tab_pos != std::string::npos && tab_pos > 0 && tab_pos < line.size() - 1) { - std::string key = utf8Line.substr(0, tab_pos); - std::string value = utf8Line.substr(tab_pos + 1); + std::string key = line.substr(0, tab_pos); + std::string value = line.substr(tab_pos + 1); if (!key.empty() && !value.empty()) container.insert(std::make_pair(key, value)); From 139ae9325ada954222cc691144926b519ae7abb1 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 14 Feb 2022 23:28:44 +0100 Subject: [PATCH 2124/2859] Fix compile errors by using StatelessUtf8Encoder --- components/esm/reader.cpp | 38 +++++--------------------------------- components/esm/reader.hpp | 4 ++-- components/esm4/reader.hpp | 4 ++-- 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/components/esm/reader.cpp b/components/esm/reader.cpp index 1f22b95b47..7fcc2cce3a 100644 --- a/components/esm/reader.cpp +++ b/components/esm/reader.cpp @@ -38,45 +38,17 @@ namespace ESM } bool Reader::getStringImpl(std::string& str, std::size_t size, - Files::IStreamPtr filestream, ToUTF8::Utf8Encoder* encoder, bool hasNull) + Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull) { std::size_t newSize = size; if (encoder) { - if (!hasNull) - newSize += 1; // Utf8Encoder::getLength() expects a null terminator - - std::string tmp; - tmp.resize(newSize); - filestream->read(&tmp[0], size); - if ((std::size_t)filestream->gcount() == size) + std::string input(size, '\0'); + filestream->read(input.data(), size); + if (filestream->gcount() == static_cast(size)) { - if (hasNull) - { - assert (tmp[newSize - 1] == '\0' - && "ESM4::Reader::getString string is not terminated with a null"); - } - else - { - // NOTE: script text of some mods have terminating null; - // unfortunately we just have to deal with it - //if (tmp[newSize - 2] != '\0') - tmp[newSize - 1] = '\0'; // for Utf8Encoder::getLength() - } - - encoder->toUtf8(tmp, str, newSize - 1); - // NOTE: the encoder converts “keep” as “keep†which increases the length, - // which results in below resize() truncating the string - if (str.size() == newSize) // ascii - str.resize(newSize - 1); // don't want the null terminator - else - { - // this is a horrible hack but fortunately there's only a few like this - std::string tmp2(str.data()); - str.swap(tmp2); - } - + encoder->getUtf8(input, ToUTF8::BufferAllocationPolicy::FitToRequiredSize, str); return true; } } diff --git a/components/esm/reader.hpp b/components/esm/reader.hpp index a5e3dd516b..5356d9bd01 100644 --- a/components/esm/reader.hpp +++ b/components/esm/reader.hpp @@ -31,7 +31,7 @@ namespace ESM virtual inline bool hasMoreRecs() const = 0; - virtual inline void setEncoder(ToUTF8::Utf8Encoder* encoder) = 0; + virtual inline void setEncoder(ToUTF8::StatelessUtf8Encoder* encoder) = 0; // used to check for dependencies e.g. CS::Editor::run() virtual inline const std::vector& getGameFiles() const = 0; @@ -53,7 +53,7 @@ namespace ESM protected: bool getStringImpl(std::string& str, std::size_t size, - Files::IStreamPtr filestream, ToUTF8::Utf8Encoder* encoder, bool hasNull = false); + Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false); }; } diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index b3f1070495..c1bf808dd5 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -77,7 +77,7 @@ namespace ESM4 { ReaderContext mCtx; - ToUTF8::Utf8Encoder* mEncoder; + ToUTF8::StatelessUtf8Encoder* mEncoder; std::size_t mFileSize; @@ -140,7 +140,7 @@ namespace ESM4 { inline bool isEsm4() const final { return true; } - inline void setEncoder(ToUTF8::Utf8Encoder* encoder) final { mEncoder = encoder; }; + inline void setEncoder(ToUTF8::StatelessUtf8Encoder* encoder) final { mEncoder = encoder; }; const std::vector& getGameFiles() const final { return mHeader.mMaster; } From b96c41df075d90ea3aba940ebe6bd297925d39ac Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 16 Feb 2022 17:08:39 +0100 Subject: [PATCH 2125/2859] Initialize string_view with explicit size Otherwise size is detected by null terminating character. --- components/to_utf8/to_utf8.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index f4b52b644b..7e7d3101fc 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -57,13 +57,13 @@ namespace switch (sourceEncoding) { case ToUTF8::WINDOWS_1252: - return ToUTF8::windows_1252; + return {ToUTF8::windows_1252, std::size(ToUTF8::windows_1252)}; case ToUTF8::WINDOWS_1250: - return ToUTF8::windows_1250; + return {ToUTF8::windows_1250, std::size(ToUTF8::windows_1250)}; case ToUTF8::WINDOWS_1251: - return ToUTF8::windows_1251; + return {ToUTF8::windows_1251, std::size(ToUTF8::windows_1251)}; case ToUTF8::CP437: - return ToUTF8::cp437; + return {ToUTF8::cp437, std::size(ToUTF8::cp437)}; } throw std::logic_error("Invalid source encoding: " + std::to_string(sourceEncoding)); } From 83be3826ff398e98f9926db77e67dc61cdbb6336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:19:10 +0200 Subject: [PATCH 2126/2859] Fix #6618: Crash due to iterator invalidation --- apps/openmw/mwmechanics/aisequence.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index a1f2b5c3e3..0bece95088 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -309,12 +309,18 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if (package->execute(actor, characterController, mAiState, duration)) { - // Put repeating noncombat AI packages on the end of the stack so they can be used again + const auto packageIdx = std::distance(mPackages.begin(), packageIt); + + // Put repeating non-combat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && package->getRepeat()) { package->reset(); mPackages.push_back(package->clone()); } + + // Iterator may have been invalidated by push back ensure its correct. + packageIt = mPackages.begin() + packageIdx; + // To account for the rare case where AiPackage::execute() queued another AI package // (e.g. AiPursue executing a dialogue script that uses startCombat) erase(packageIt); From e60e0b55ebd9688adc6902b88d2950b4fc573e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 16 Feb 2022 18:19:55 +0200 Subject: [PATCH 2127/2859] Fix potential another crash --- apps/openmw/mwmechanics/aisequence.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 0bece95088..8506e1daf0 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -291,7 +291,8 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac } } - assert(!mPackages.empty()); + if (mPackages.empty()) + return; if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) { From 0ce29a61314f088202274e631b0ec6720a57a781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 16 Feb 2022 20:21:10 +0200 Subject: [PATCH 2128/2859] Simplify logic in AiSequence::execute --- apps/openmw/mwmechanics/aisequence.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 8506e1daf0..0cb1e97603 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -238,8 +238,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac return; } - auto packageIt = mPackages.begin(); - MWMechanics::AiPackage* package = packageIt->get(); + auto* package = mPackages.front().get(); if (!package->alwaysActive() && outOfRange) return; @@ -301,8 +300,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat)); } - packageIt = mPackages.begin(); - package = packageIt->get(); + package = mPackages.front().get(); packageTypeId = package->getTypeId(); } @@ -310,8 +308,6 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if (package->execute(actor, characterController, mAiState, duration)) { - const auto packageIdx = std::distance(mPackages.begin(), packageIt); - // Put repeating non-combat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && package->getRepeat()) { @@ -319,12 +315,9 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac mPackages.push_back(package->clone()); } - // Iterator may have been invalidated by push back ensure its correct. - packageIt = mPackages.begin() + packageIdx; - // To account for the rare case where AiPackage::execute() queued another AI package // (e.g. AiPursue executing a dialogue script that uses startCombat) - erase(packageIt); + erase(mPackages.begin()); if (isActualAiPackage(packageTypeId)) mDone = true; } From 7bd4971e0c17ab8567aa4b4296c1106ba67ce611 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 16 Feb 2022 21:58:22 +0100 Subject: [PATCH 2129/2859] Disallow non-bipedal hand-to-hand refreshes --- apps/openmw/mwmechanics/character.cpp | 6 ++++-- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5f1c3c2d92..25a5ac6aa0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -709,7 +709,9 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (mPtr.getClass().isActor()) refreshHitRecoilAnims(idle); - std::string weap = getWeaponType(mWeaponType)->mShortGroup; + std::string weap; + if (mWeaponType != ESM::Weapon::HandToHand || mPtr.getClass().isBipedal(mPtr)) + weap = getWeaponType(mWeaponType)->mShortGroup; refreshJumpAnims(weap, jump, idle, force); refreshMovementAnims(weap, movement, idle, force); @@ -1127,7 +1129,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const return mAnimation->updateCarriedLeftVisible(weaptype); } -bool CharacterController::updateState(CharacterState& idle) +bool CharacterController::updateState(CharacterState idle) { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 1a0fd42fba..e7f445054e 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -205,7 +205,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void clearAnimQueue(bool clearPersistAnims = false); - bool updateState(CharacterState& idle); + bool updateState(CharacterState idle); void updateIdleStormState(bool inwater); std::string chooseRandomAttackAnimation() const; From de7f9f643994bc7bf1801c1678969895ae3f4866 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 16 Feb 2022 17:24:56 +0100 Subject: [PATCH 2130/2859] Replace raw for loop by algorithm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To fix compilation error with -D_GLIBCXX_DEBUG: /home/elsid/dev/openmw/apps/openmw/mwdialogue/dialoguemanagerimp.cpp: In member function ‘virtual void MWDialogue::DialogueManager::write(ESM::ESMWriter&, Loading::Listener&) const’: /home/elsid/dev/openmw/apps/openmw/mwdialogue/dialoguemanagerimp.cpp:679:78: error: no matching function for call to ‘__gnu_debug::_Safe_iterator >, std::__debug::set >, std::bidirectional_iterator_tag>::_Safe_iterator(std::__debug::set, Misc::StringUtils::CiComp>::const_iterator)’ 679 | for (std::set::const_iterator iter (mKnownTopics.begin()); | ^ --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 4612e2bdfd..feb3c22035 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -676,11 +676,8 @@ namespace MWDialogue { ESM::DialogueState state; - for (std::set::const_iterator iter (mKnownTopics.begin()); - iter!=mKnownTopics.end(); ++iter) - { - state.mKnownTopics.push_back (*iter); - } + state.mKnownTopics.reserve(mKnownTopics.size()); + std::copy(mKnownTopics.begin(), mKnownTopics.end(), std::back_inserter(state.mKnownTopics)); state.mChangedFactionReaction = mChangedFactionReaction; From d96e2037e39f335c0c40a89fa9dd05a6f34da92b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 17 Feb 2022 01:27:25 +0100 Subject: [PATCH 2131/2859] Build tests by gcc with enabled stdlibc++ assertions in debug mode This can catch some problems in the code like out of bounds access for string_view element via operator[] when it refers a buffer larger than the view. --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aaf7f233f0..4c5223c212 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -116,11 +116,12 @@ Ubuntu_GCC_tests: Ubuntu_GCC_tests_Debug: extends: Ubuntu_GCC cache: - key: Ubuntu_GCC_tests_Debug.v1 + key: Ubuntu_GCC_tests_Debug.v2 variables: CCACHE_SIZE: 1G BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -g -O0 -D_GLIBCXX_ASSERTIONS artifacts: paths: [] expire_in: 1 minute From dd473d06dffc3edc66e9311257a15da65ea4d048 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sat, 8 Jan 2022 05:53:10 +0300 Subject: [PATCH 2132/2859] Implement gloss-mapping (feature #6541) --- CHANGELOG.md | 1 + components/nifosg/nifloader.cpp | 17 ++++++++++------- components/shader/shadervisitor.cpp | 10 +++++++++- files/shaders/objects_fragment.glsl | 15 +++++++++++++-- files/shaders/objects_vertex.glsl | 8 ++++++++ 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ebd3b07e3..dd2ad0fd57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,7 @@ Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference Feature #6443: NiStencilProperty is not fully supported Feature #6534: Shader-based object texture blending + Feature #6541: Gloss-mapping Feature #6592: Missing support for NiTriShape particle emitters Feature #6600: Support NiSortAdjustNode Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8e0a5c08ac..3007bc6cf2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1552,14 +1552,8 @@ namespace NifOsg case Nif::NiTexturingProperty::BumpTexture: case Nif::NiTexturingProperty::DetailTexture: case Nif::NiTexturingProperty::DecalTexture: - break; case Nif::NiTexturingProperty::GlossTexture: - { - // Not used by the vanilla engine. MCP (Morrowind Code Patch) adds an option to use Gloss maps: - // "- Gloss map fix. Morrowind removed gloss map entries from model files after loading them. This stops Morrowind from removing them." - // Log(Debug::Info) << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used."; - continue; - } + break; default: { Log(Debug::Info) << "Unhandled texture stage " << i << " on shape \"" << nodeName << "\" in " << mFilename; @@ -1651,6 +1645,12 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("bumpMapMatrix", bumpMapMatrix)); stateset->addUniform(new osg::Uniform("envMapLumaBias", texprop->envMapLumaBias)); } + else if (i == Nif::NiTexturingProperty::GlossTexture) + { + // A gloss map is an environment map mask. + // Gloss maps are only implemented in the object shaders as well. + stateset->setTextureMode(texUnit, GL_TEXTURE_2D, osg::StateAttribute::OFF); + } else if (i == Nif::NiTexturingProperty::DecalTexture) { // This is only an inaccurate imitation of the original implementation, @@ -1694,6 +1694,9 @@ namespace NifOsg case Nif::NiTexturingProperty::DecalTexture: texture2d->setName("decalMap"); break; + case Nif::NiTexturingProperty::GlossTexture: + texture2d->setName("glossMap"); + break; default: break; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 107665369c..d8bbeeadc2 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -244,7 +244,7 @@ namespace Shader addedState->setName("addedState"); } - const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap", "glossMap" }; bool isTextureNameRecognized(const std::string& name) { for (unsigned int i=0; isetTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + } } else Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 9b60e7c6a4..85950c7468 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -58,6 +58,11 @@ uniform vec2 envMapLumaBias; uniform mat2 bumpMapMatrix; #endif +#if @glossMap +uniform sampler2D glossMap; +varying vec2 glossMapUV; +#endif + uniform bool simpleWater; varying float euclideanDepth; @@ -168,8 +173,14 @@ void main() envLuma = clamp(bumpTex.b * envMapLumaBias.x + envMapLumaBias.y, 0.0, 1.0); #endif + vec3 envEffect = texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; + +#if @glossMap + envEffect *= texture2D(glossMap, glossMapUV).xyz; +#endif + #if @preLightEnv - gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; + gl_FragData[0].xyz += envEffect; #endif #endif @@ -189,7 +200,7 @@ void main() gl_FragData[0].xyz *= lighting; #if @envMap && !@preLightEnv - gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; + gl_FragData[0].xyz += envEffect; #endif #if @emissiveMap diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 969fe5903e..77c7fef391 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -47,6 +47,10 @@ varying vec2 bumpMapUV; varying vec2 specularMapUV; #endif +#if @glossMap +varying vec2 glossMapUV; +#endif + varying float euclideanDepth; varying float linearDepth; @@ -120,6 +124,10 @@ void main(void) specularMapUV = (gl_TextureMatrix[@specularMapUV] * gl_MultiTexCoord@specularMapUV).xy; #endif +#if @glossMap + glossMapUV = (gl_TextureMatrix[@glossMapUV] * gl_MultiTexCoord@glossMapUV).xy; +#endif + passColor = gl_Color; passViewPos = viewPos.xyz; passNormal = gl_Normal.xyz; From 9584cb7ac2083ab01e8c05cb7cde00cd5a77b55f Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 5 Feb 2022 16:24:57 +0100 Subject: [PATCH 2133/2859] Use fixed size types for serialization --- apps/openmw_test_suite/serialization/format.hpp | 5 +++-- components/serialization/format.hpp | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/serialization/format.hpp b/apps/openmw_test_suite/serialization/format.hpp index 8f61838fde..603d2790e0 100644 --- a/apps/openmw_test_suite/serialization/format.hpp +++ b/apps/openmw_test_suite/serialization/format.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace SerializationTesting { @@ -20,7 +21,7 @@ namespace SerializationTesting } }; - enum Enum + enum Enum : std::int32_t { A, B, @@ -30,7 +31,7 @@ namespace SerializationTesting struct Composite { short mFloatArray[3] = {0}; - std::vector mIntVector; + std::vector mIntVector; std::vector mEnumVector; std::vector mPodVector; std::size_t mPodDataSize = 0; diff --git a/components/serialization/format.hpp b/components/serialization/format.hpp index 595afd0dad..956345149c 100644 --- a/components/serialization/format.hpp +++ b/components/serialization/format.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Serialization { @@ -51,13 +52,13 @@ namespace Serialization -> std::enable_if_t> { if constexpr (mode == Mode::Write) - visitor(self(), value.size()); + visitor(self(), static_cast(value.size())); else { static_assert(mode == Mode::Read); - std::size_t size = 0; + std::uint64_t size = 0; visitor(self(), size); - value.resize(size); + value.resize(static_cast(size)); } self()(std::forward(visitor), value.data(), value.size()); } From 52b3a87dae5df9b4fcacd2dc2e61a730b7352abd Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 5 Feb 2022 16:32:40 +0100 Subject: [PATCH 2134/2859] Make constexpr variable defined in header inline --- components/serialization/format.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/serialization/format.hpp b/components/serialization/format.hpp index 956345149c..29fc0ec42d 100644 --- a/components/serialization/format.hpp +++ b/components/serialization/format.hpp @@ -27,7 +27,7 @@ namespace Serialization struct IsContiguousContainer> : std::true_type {}; template - constexpr bool isContiguousContainer = IsContiguousContainer>::value; + inline constexpr bool isContiguousContainer = IsContiguousContainer>::value; template struct Format From 67741402b5ef92ca8aad11325bea7878dbf3f7b9 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 18 Feb 2022 21:45:50 +0100 Subject: [PATCH 2135/2859] Replace reference to const std::string by std::string_view for navmeshdb related arguments --- apps/navmeshtool/navmesh.cpp | 5 +++-- apps/navmeshtool/navmesh.hpp | 1 - .../detournavigator/generatenavmeshtile.hpp | 4 ++-- components/detournavigator/navmeshdb.cpp | 21 +++++++++---------- components/detournavigator/navmeshdb.hpp | 21 +++++++++---------- components/detournavigator/navmeshdbutils.cpp | 5 +++-- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index ca614d0cf6..cf18d7edc3 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace NavMeshTool { @@ -81,7 +82,7 @@ namespace NavMeshTool return DetourNavigator::resolveMeshSource(mDb, source, mNextShapeId); } - std::optional find(const std::string& worldspace, const TilePosition &tilePosition, + std::optional find(std::string_view worldspace, const TilePosition &tilePosition, const std::vector &input) override { std::optional result; @@ -98,7 +99,7 @@ namespace NavMeshTool void ignore() override { report(); } - void insert(const std::string& worldspace, const TilePosition& tilePosition, std::int64_t version, + void insert(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t version, const std::vector& input, PreparedNavMeshData& data) override { data.mUserId = static_cast(mNextTileId); diff --git a/apps/navmeshtool/navmesh.hpp b/apps/navmeshtool/navmesh.hpp index 725f0cd6a4..ff837eebe7 100644 --- a/apps/navmeshtool/navmesh.hpp +++ b/apps/navmeshtool/navmesh.hpp @@ -4,7 +4,6 @@ #include #include -#include namespace DetourNavigator { diff --git a/components/detournavigator/generatenavmeshtile.hpp b/components/detournavigator/generatenavmeshtile.hpp index 511b8dfb8f..69afe426bc 100644 --- a/components/detournavigator/generatenavmeshtile.hpp +++ b/components/detournavigator/generatenavmeshtile.hpp @@ -36,12 +36,12 @@ namespace DetourNavigator virtual std::int64_t resolveMeshSource(const MeshSource& source) = 0; - virtual std::optional find(const std::string& worldspace, const TilePosition& tilePosition, + virtual std::optional find(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) = 0; virtual void ignore() = 0; - virtual void insert(const std::string& worldspace, const TilePosition& tilePosition, + virtual void insert(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t version, const std::vector& input, PreparedNavMeshData& data) = 0; virtual void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0; diff --git a/components/detournavigator/navmeshdb.cpp b/components/detournavigator/navmeshdb.cpp index ebff250ee0..425cf7d434 100644 --- a/components/detournavigator/navmeshdb.cpp +++ b/components/detournavigator/navmeshdb.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -136,7 +135,7 @@ namespace DetourNavigator return tileId; } - std::optional NavMeshDb::findTile(const std::string& worldspace, + std::optional NavMeshDb::findTile(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) { Tile result; @@ -147,7 +146,7 @@ namespace DetourNavigator return result; } - std::optional NavMeshDb::getTileData(const std::string& worldspace, + std::optional NavMeshDb::getTileData(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) { TileData result; @@ -159,7 +158,7 @@ namespace DetourNavigator return result; } - int NavMeshDb::insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition, + int NavMeshDb::insertTile(TileId tileId, std::string_view worldspace, const TilePosition& tilePosition, TileVersion version, const std::vector& input, const std::vector& data) { const std::vector compressedInput = Misc::compress(input); @@ -180,7 +179,7 @@ namespace DetourNavigator return shapeId; } - std::optional NavMeshDb::findShapeId(const std::string& name, ShapeType type, + std::optional NavMeshDb::findShapeId(std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash) { ShapeId shapeId; @@ -189,7 +188,7 @@ namespace DetourNavigator return shapeId; } - int NavMeshDb::insertShape(ShapeId shapeId, const std::string& name, ShapeType type, + int NavMeshDb::insertShape(ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash) { return execute(*mDb, mInsertShape, shapeId, name, type, hash); @@ -207,7 +206,7 @@ namespace DetourNavigator return findTileQuery; } - void FindTile::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + void FindTile::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) { Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); @@ -221,7 +220,7 @@ namespace DetourNavigator return getTileDataQuery; } - void GetTileData::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + void GetTileData::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) { Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); @@ -235,7 +234,7 @@ namespace DetourNavigator return insertTileQuery; } - void InsertTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, const std::string& worldspace, + void InsertTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, std::string_view worldspace, const TilePosition& tilePosition, TileVersion version, const std::vector& input, const std::vector& data) { @@ -271,7 +270,7 @@ namespace DetourNavigator return findShapeIdQuery; } - void FindShapeId::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& name, + void FindShapeId::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash) { Sqlite3::bindParameter(db, statement, ":name", name); @@ -284,7 +283,7 @@ namespace DetourNavigator return insertShapeQuery; } - void InsertShape::bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, const std::string& name, + void InsertShape::bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash) { Sqlite3::bindParameter(db, statement, ":shape_id", shapeId); diff --git a/components/detournavigator/navmeshdb.hpp b/components/detournavigator/navmeshdb.hpp index 636f1de000..09604e5706 100644 --- a/components/detournavigator/navmeshdb.hpp +++ b/components/detournavigator/navmeshdb.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -64,21 +63,21 @@ namespace DetourNavigator struct FindTile { static std::string_view text() noexcept; - static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input); }; struct GetTileData { static std::string_view text() noexcept; - static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input); }; struct InsertTile { static std::string_view text() noexcept; - static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, const std::string& worldspace, + static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, std::string_view worldspace, const TilePosition& tilePosition, TileVersion version, const std::vector& input, const std::vector& data); }; @@ -99,14 +98,14 @@ namespace DetourNavigator struct FindShapeId { static std::string_view text() noexcept; - static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& name, + static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); }; struct InsertShape { static std::string_view text() noexcept; - static void bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, const std::string& name, + static void bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); }; } @@ -120,22 +119,22 @@ namespace DetourNavigator TileId getMaxTileId(); - std::optional findTile(const std::string& worldspace, + std::optional findTile(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input); - std::optional getTileData(const std::string& worldspace, + std::optional getTileData(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input); - int insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition, + int insertTile(TileId tileId, std::string_view worldspace, const TilePosition& tilePosition, TileVersion version, const std::vector& input, const std::vector& data); int updateTile(TileId tileId, TileVersion version, const std::vector& data); ShapeId getMaxShapeId(); - std::optional findShapeId(const std::string& name, ShapeType type, const Sqlite3::ConstBlob& hash); + std::optional findShapeId(std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); - int insertShape(ShapeId shapeId, const std::string& name, ShapeType type, const Sqlite3::ConstBlob& hash); + int insertShape(ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); private: Sqlite3::Db mDb; diff --git a/components/detournavigator/navmeshdbutils.cpp b/components/detournavigator/navmeshdbutils.cpp index 86f81bfc51..71873972b9 100644 --- a/components/detournavigator/navmeshdbutils.cpp +++ b/components/detournavigator/navmeshdbutils.cpp @@ -6,19 +6,20 @@ #include #include +#include namespace DetourNavigator { namespace { - std::optional findShapeId(NavMeshDb& db, const std::string& name, ShapeType type, + std::optional findShapeId(NavMeshDb& db, std::string_view name, ShapeType type, const std::string& hash) { const Sqlite3::ConstBlob hashData {hash.data(), static_cast(hash.size())}; return db.findShapeId(name, type, hashData); } - ShapeId getShapeId(NavMeshDb& db, const std::string& name, ShapeType type, + ShapeId getShapeId(NavMeshDb& db, std::string_view name, ShapeType type, const std::string& hash, ShapeId& nextShapeId) { const Sqlite3::ConstBlob hashData {hash.data(), static_cast(hash.size())}; From ab1a6e034ee822d9ca6b664e5b72a3a45bde97e0 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 18 Feb 2022 21:35:09 +0100 Subject: [PATCH 2136/2859] Add navmeshtool flag to remove unused tiles from navmesh disk cache * Remove tiles outside processing range. Useful when new content profile map has different bounds. * Remove ignored tiles. For a case when content profile maps have intersection but there is no more data for navmesh. * Remove older tiles at the same worldspace position. If navmesh tile data has changed with new content, the old ones unlikely to be used. * Vacuum the database when there are modifications. SQLite leaves empty pages in the file on database modification. Vacuum cleans up unused pages reducing the file size. --- apps/navmeshtool/main.cpp | 7 +- apps/navmeshtool/navmesh.cpp | 102 +++++++++++++---- apps/navmeshtool/navmesh.hpp | 3 +- .../detournavigator/navmeshdb.cpp | 57 ++++++++++ .../detournavigator/generatenavmeshtile.cpp | 12 +- .../detournavigator/generatenavmeshtile.hpp | 8 +- .../detournavigator/gettilespositions.hpp | 7 +- components/detournavigator/navmeshdb.cpp | 103 ++++++++++++++++++ components/detournavigator/navmeshdb.hpp | 41 +++++++ .../detournavigator/tilespositionsrange.hpp | 15 +++ 10 files changed, 322 insertions(+), 33 deletions(-) create mode 100644 components/detournavigator/tilespositionsrange.hpp diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index f89e80e542..894ec6b3b1 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -83,6 +83,9 @@ namespace NavMeshTool ("process-interior-cells", bpo::value()->implicit_value(true) ->default_value(false), "build navmesh for interior cells") + + ("remove-unused-tiles", bpo::value()->implicit_value(true) + ->default_value(false), "remove tiles from cache that will not be used with current content profile") ; Files::ConfigurationManager::addCommonOptions(result); @@ -141,6 +144,7 @@ namespace NavMeshTool } const bool processInteriorCells = variables["process-interior-cells"].as(); + const bool removeUnusedTiles = variables["remove-unused-tiles"].as(); Fallback::Map::init(variables["fallback"].as().mMap); @@ -177,7 +181,8 @@ namespace NavMeshTool WorldspaceData cellsData = gatherWorldspaceData(navigatorSettings, readers, vfs, bulletShapeManager, esmData, processInteriorCells); - generateAllNavMeshTiles(agentHalfExtents, navigatorSettings, threadsNumber, cellsData, std::move(db)); + generateAllNavMeshTiles(agentHalfExtents, navigatorSettings, threadsNumber, removeUnusedTiles, + cellsData, std::move(db)); Log(Debug::Info) << "Done"; diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index cf18d7edc3..3161192cf9 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -41,6 +41,7 @@ namespace NavMeshTool using DetourNavigator::TileId; using DetourNavigator::TilePosition; using DetourNavigator::TileVersion; + using DetourNavigator::TilesPositionsRange; using Sqlite3::Transaction; void logGeneratedTiles(std::size_t provided, std::size_t expected) @@ -63,8 +64,9 @@ namespace NavMeshTool public: std::atomic_size_t mExpected {0}; - explicit NavMeshTileConsumer(NavMeshDb&& db) + explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles) : mDb(std::move(db)) + , mRemoveUnusedTiles(removeUnusedTiles) , mTransaction(mDb.startTransaction()) , mNextTileId(mDb.getMaxTileId() + 1) , mNextShapeId(mDb.getMaxShapeId() + 1) @@ -76,6 +78,12 @@ namespace NavMeshTool std::size_t getUpdated() const { return mUpdated.load(); } + std::size_t getDeleted() const + { + const std::lock_guard lock(mMutex); + return mDeleted; + } + std::int64_t resolveMeshSource(const MeshSource& source) override { const std::lock_guard lock(mMutex); @@ -97,11 +105,34 @@ namespace NavMeshTool return result; } - void ignore() override { report(); } + void ignore(std::string_view worldspace, const TilePosition& tilePosition) override + { + if (mRemoveUnusedTiles) + { + std::lock_guard lock(mMutex); + mDeleted += static_cast(mDb.deleteTilesAt(worldspace, tilePosition)); + } + report(); + } + + void identity(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId) override + { + if (mRemoveUnusedTiles) + { + std::lock_guard lock(mMutex); + mDeleted += static_cast(mDb.deleteTilesAtExcept(worldspace, tilePosition, TileId {tileId})); + } + report(); + } - void insert(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t version, - const std::vector& input, PreparedNavMeshData& data) override + void insert(std::string_view worldspace, const TilePosition& tilePosition, + std::int64_t version, const std::vector& input, PreparedNavMeshData& data) override { + if (mRemoveUnusedTiles) + { + std::lock_guard lock(mMutex); + mDeleted += static_cast(mDb.deleteTilesAt(worldspace, tilePosition)); + } data.mUserId = static_cast(mNextTileId); { std::lock_guard lock(mMutex); @@ -112,11 +143,14 @@ namespace NavMeshTool report(); } - void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) override + void update(std::string_view worldspace, const TilePosition& tilePosition, + std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) override { data.mUserId = static_cast(tileId); { std::lock_guard lock(mMutex); + if (mRemoveUnusedTiles) + mDeleted += static_cast(mDb.deleteTilesAtExcept(worldspace, tilePosition, TileId {tileId})); mDb.updateTile(TileId {tileId}, TileVersion {version}, serialize(data)); } ++mUpdated; @@ -141,49 +175,66 @@ namespace NavMeshTool void commit() { mTransaction.commit(); } + void vacuum() { mDb.vacuum(); } + + void removeTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range) + { + const std::lock_guard lock(mMutex); + mTransaction.commit(); + Log(Debug::Info) << "Removing tiles outside processed range for worldspace \"" << worldspace << "\"..."; + mDeleted += static_cast(mDb.deleteTilesOutsideRange(worldspace, range)); + mTransaction = mDb.startTransaction(); + } + private: std::atomic_size_t mProvided {0}; std::atomic_size_t mInserted {0}; std::atomic_size_t mUpdated {0}; - std::mutex mMutex; + std::size_t mDeleted = 0; + mutable std::mutex mMutex; NavMeshDb mDb; + const bool mRemoveUnusedTiles; Transaction mTransaction; TileId mNextTileId; std::condition_variable mHasTile; Misc::ProgressReporter mReporter; ShapeId mNextShapeId; + std::mutex mReportMutex; void report() { - mReporter(mProvided + 1, mExpected); - ++mProvided; + const std::size_t provided = mProvided.fetch_add(1, std::memory_order_relaxed) + 1; + mReporter(provided, mExpected); mHasTile.notify_one(); } }; } void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings, - const std::size_t threadsNumber, WorldspaceData& data, NavMeshDb&& db) + std::size_t threadsNumber, bool removeUnusedTiles, WorldspaceData& data, NavMeshDb&& db) { Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers..."; SceneUtil::WorkQueue workQueue(threadsNumber); - auto navMeshTileConsumer = std::make_shared(std::move(db)); + auto navMeshTileConsumer = std::make_shared(std::move(db), removeUnusedTiles); std::size_t tiles = 0; std::mt19937_64 random; for (const std::unique_ptr& input : data.mNavMeshInputs) { + const auto range = DetourNavigator::makeTilesPositionsRange( + Misc::Convert::toOsgXY(input->mAabb.m_min), + Misc::Convert::toOsgXY(input->mAabb.m_max), + settings.mRecast + ); + + if (removeUnusedTiles) + navMeshTileConsumer->removeTilesOutsideRange(input->mWorldspace, range); + std::vector worldspaceTiles; - DetourNavigator::getTilesPositions( - DetourNavigator::makeTilesPositionsRange( - Misc::Convert::toOsgXY(input->mAabb.m_min), - Misc::Convert::toOsgXY(input->mAabb.m_max), - settings.mRecast - ), - [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); } - ); + DetourNavigator::getTilesPositions(range, + [&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); }); tiles += worldspaceTiles.size(); @@ -205,8 +256,19 @@ namespace NavMeshTool navMeshTileConsumer->wait(); navMeshTileConsumer->commit(); + const auto inserted = navMeshTileConsumer->getInserted(); + const auto updated = navMeshTileConsumer->getUpdated(); + const auto deleted = navMeshTileConsumer->getDeleted(); + Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, " - << navMeshTileConsumer->getInserted() << " are inserted and " - << navMeshTileConsumer->getUpdated() << " updated"; + << inserted << " are inserted, " + << updated << " updated and " + << deleted << " deleted"; + + if (inserted + updated + deleted > 0) + { + Log(Debug::Info) << "Vacuuming the database..."; + navMeshTileConsumer->vacuum(); + } } } diff --git a/apps/navmeshtool/navmesh.hpp b/apps/navmeshtool/navmesh.hpp index ff837eebe7..3d0e9e4665 100644 --- a/apps/navmeshtool/navmesh.hpp +++ b/apps/navmeshtool/navmesh.hpp @@ -16,7 +16,8 @@ namespace NavMeshTool struct WorldspaceData; void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const DetourNavigator::Settings& settings, - const std::size_t threadsNumber, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db); + std::size_t threadsNumber, bool removeUnusedTiles, WorldspaceData& cellsData, + DetourNavigator::NavMeshDb&& db); } #endif diff --git a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp index ba008f50ff..a17f5132c5 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp @@ -109,4 +109,61 @@ namespace EXPECT_THROW(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), std::runtime_error); EXPECT_NO_THROW(insertTile(TileId {54}, version)); } + + TEST_F(DetourNavigatorNavMeshDbTest, delete_tiles_at_should_remove_all_tiles_with_given_worldspace_and_position) + { + const TileVersion version {1}; + const std::string worldspace = "sys::default"; + const TilePosition tilePosition {3, 4}; + const std::vector input1 = generateData(); + const std::vector input2 = generateData(); + const std::vector data = generateData(); + ASSERT_EQ(mDb.insertTile(TileId {53}, worldspace, tilePosition, version, input1, data), 1); + ASSERT_EQ(mDb.insertTile(TileId {54}, worldspace, tilePosition, version, input2, data), 1); + ASSERT_EQ(mDb.deleteTilesAt(worldspace, tilePosition), 2); + EXPECT_FALSE(mDb.findTile(worldspace, tilePosition, input1).has_value()); + EXPECT_FALSE(mDb.findTile(worldspace, tilePosition, input2).has_value()); + } + + TEST_F(DetourNavigatorNavMeshDbTest, delete_tiles_at_except_should_leave_tile_with_given_id) + { + const TileId leftTileId {53}; + const TileId removedTileId {54}; + const TileVersion version {1}; + const std::string worldspace = "sys::default"; + const TilePosition tilePosition {3, 4}; + const std::vector leftInput = generateData(); + const std::vector removedInput = generateData(); + const std::vector data = generateData(); + ASSERT_EQ(mDb.insertTile(leftTileId, worldspace, tilePosition, version, leftInput, data), 1); + ASSERT_EQ(mDb.insertTile(removedTileId, worldspace, tilePosition, version, removedInput, data), 1); + ASSERT_EQ(mDb.deleteTilesAtExcept(worldspace, tilePosition, leftTileId), 1); + const auto left = mDb.findTile(worldspace, tilePosition, leftInput); + ASSERT_TRUE(left.has_value()); + EXPECT_EQ(left->mTileId, leftTileId); + EXPECT_FALSE(mDb.findTile(worldspace, tilePosition, removedInput).has_value()); + } + + TEST_F(DetourNavigatorNavMeshDbTest, delete_tiles_outside_range_should_leave_tiles_inside_given_rectangle) + { + TileId tileId {1}; + const TileVersion version {1}; + const std::string worldspace = "sys::default"; + const std::vector input = generateData(); + const std::vector data = generateData(); + for (int x = -2; x <= 2; ++x) + { + for (int y = -2; y <= 2; ++y) + { + ASSERT_EQ(mDb.insertTile(tileId, worldspace, TilePosition {x, y}, version, input, data), 1); + ++tileId.t; + } + } + const TilesPositionsRange range {TilePosition {-1, -1}, TilePosition {2, 2}}; + ASSERT_EQ(mDb.deleteTilesOutsideRange(worldspace, range), 16); + for (int x = -2; x <= 2; ++x) + for (int y = -2; y <= 2; ++y) + ASSERT_EQ(mDb.findTile(worldspace, TilePosition {x, y}, input).has_value(), + -1 <= x && x <= 1 && -1 <= y && y <= 1) << "x=" << x << " y=" << y; + } } diff --git a/components/detournavigator/generatenavmeshtile.cpp b/components/detournavigator/generatenavmeshtile.cpp index ad8978cd4b..360c05931f 100644 --- a/components/detournavigator/generatenavmeshtile.cpp +++ b/components/detournavigator/generatenavmeshtile.cpp @@ -25,12 +25,14 @@ namespace DetourNavigator { struct Ignore { + std::string_view mWorldspace; + const TilePosition& mTilePosition; std::shared_ptr mConsumer; ~Ignore() noexcept { if (mConsumer != nullptr) - mConsumer->ignore(); + mConsumer->ignore(mWorldspace, mTilePosition); } }; } @@ -59,7 +61,7 @@ namespace DetourNavigator try { - Ignore ignore {consumer}; + Ignore ignore {mWorldspace, mTilePosition, consumer}; const std::shared_ptr recastMesh = mRecastMeshProvider.getMesh(mWorldspace, mTilePosition); @@ -72,7 +74,11 @@ namespace DetourNavigator const std::optional info = consumer->find(mWorldspace, mTilePosition, input); if (info.has_value() && info->mVersion == mSettings.mNavMeshVersion) + { + consumer->identity(mWorldspace, mTilePosition, info->mTileId); + ignore.mConsumer = nullptr; return; + } const auto data = prepareNavMeshTileData(*recastMesh, mTilePosition, mAgentHalfExtents, mSettings.mRecast); @@ -80,7 +86,7 @@ namespace DetourNavigator return; if (info.has_value()) - consumer->update(info->mTileId, mSettings.mNavMeshVersion, *data); + consumer->update(mWorldspace, mTilePosition, info->mTileId, mSettings.mNavMeshVersion, *data); else consumer->insert(mWorldspace, mTilePosition, mSettings.mNavMeshVersion, input, *data); diff --git a/components/detournavigator/generatenavmeshtile.hpp b/components/detournavigator/generatenavmeshtile.hpp index 69afe426bc..e6d9e26c1d 100644 --- a/components/detournavigator/generatenavmeshtile.hpp +++ b/components/detournavigator/generatenavmeshtile.hpp @@ -39,12 +39,16 @@ namespace DetourNavigator virtual std::optional find(std::string_view worldspace, const TilePosition& tilePosition, const std::vector& input) = 0; - virtual void ignore() = 0; + virtual void ignore(std::string_view worldspace, const TilePosition& tilePosition) = 0; + + virtual void identity(std::string_view worldspace, const TilePosition& tilePosition, + std::int64_t tileId) = 0; virtual void insert(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t version, const std::vector& input, PreparedNavMeshData& data) = 0; - virtual void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0; + virtual void update(std::string_view worldspace, const TilePosition& tilePosition, + std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0; }; class GenerateNavMeshTile final : public SceneUtil::WorkItem diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 79188868dc..33c1131176 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -3,6 +3,7 @@ #include "tilebounds.hpp" #include "tileposition.hpp" +#include "tilespositionsrange.hpp" class btVector3; class btTransform; @@ -17,12 +18,6 @@ namespace DetourNavigator { struct RecastSettings; - struct TilesPositionsRange - { - TilePosition mBegin; - TilePosition mEnd; - }; - TilesPositionsRange makeTilesPositionsRange(const osg::Vec2f& aabbMin, const osg::Vec2f& aabbMax, const RecastSettings& settings); diff --git a/components/detournavigator/navmeshdb.cpp b/components/detournavigator/navmeshdb.cpp index 425cf7d434..621c97f390 100644 --- a/components/detournavigator/navmeshdb.cpp +++ b/components/detournavigator/navmeshdb.cpp @@ -34,6 +34,9 @@ namespace DetourNavigator CREATE UNIQUE INDEX IF NOT EXISTS index_unique_tiles_by_worldspace_and_tile_position_and_input ON tiles (worldspace, tile_position_x, tile_position_y, input); + CREATE INDEX IF NOT EXISTS index_tiles_by_worldspace_and_tile_position + ON tiles (worldspace, tile_position_x, tile_position_y); + CREATE TABLE IF NOT EXISTS shapes ( shape_id INTEGER PRIMARY KEY, name TEXT NOT NULL, @@ -82,6 +85,31 @@ namespace DetourNavigator WHERE tile_id = :tile_id )"; + constexpr std::string_view deleteTilesAtQuery = R"( + DELETE FROM tiles + WHERE worldspace = :worldspace + AND tile_position_x = :tile_position_x + AND tile_position_y = :tile_position_y + )"; + + constexpr std::string_view deleteTilesAtExceptQuery = R"( + DELETE FROM tiles + WHERE worldspace = :worldspace + AND tile_position_x = :tile_position_x + AND tile_position_y = :tile_position_y + AND tile_id != :exclude_tile_id + )"; + + constexpr std::string_view deleteTilesOutsideRangeQuery = R"( + DELETE FROM tiles + WHERE worldspace = :worldspace + AND ( tile_position_x < :begin_tile_position_x + OR tile_position_y < :begin_tile_position_y + OR tile_position_x >= :end_tile_position_x + OR tile_position_y >= :end_tile_position_y + ) + )"; + constexpr std::string_view getMaxShapeIdQuery = R"( SELECT max(shape_id) FROM shapes )"; @@ -98,6 +126,10 @@ namespace DetourNavigator INSERT INTO shapes ( shape_id, name, type, hash) VALUES (:shape_id, :name, :type, :hash) )"; + + constexpr std::string_view vacuumQuery = R"( + VACUUM; + )"; } std::ostream& operator<<(std::ostream& stream, ShapeType value) @@ -117,9 +149,13 @@ namespace DetourNavigator , mGetTileData(*mDb, DbQueries::GetTileData {}) , mInsertTile(*mDb, DbQueries::InsertTile {}) , mUpdateTile(*mDb, DbQueries::UpdateTile {}) + , mDeleteTilesAt(*mDb, DbQueries::DeleteTilesAt {}) + , mDeleteTilesAtExcept(*mDb, DbQueries::DeleteTilesAtExcept {}) + , mDeleteTilesOutsideRange(*mDb, DbQueries::DeleteTilesOutsideRange {}) , mGetMaxShapeId(*mDb, DbQueries::GetMaxShapeId {}) , mFindShapeId(*mDb, DbQueries::FindShapeId {}) , mInsertShape(*mDb, DbQueries::InsertShape {}) + , mVacuum(*mDb, DbQueries::Vacuum {}) { } @@ -172,6 +208,21 @@ namespace DetourNavigator return execute(*mDb, mUpdateTile, tileId, version, compressedData); } + int NavMeshDb::deleteTilesAt(std::string_view worldspace, const TilePosition& tilePosition) + { + return execute(*mDb, mDeleteTilesAt, worldspace, tilePosition); + } + + int NavMeshDb::deleteTilesAtExcept(std::string_view worldspace, const TilePosition& tilePosition, TileId excludeTileId) + { + return execute(*mDb, mDeleteTilesAtExcept, worldspace, tilePosition, excludeTileId); + } + + int NavMeshDb::deleteTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range) + { + return execute(*mDb, mDeleteTilesOutsideRange, worldspace, range); + } + ShapeId NavMeshDb::getMaxShapeId() { ShapeId shapeId {0}; @@ -194,6 +245,11 @@ namespace DetourNavigator return execute(*mDb, mInsertShape, shapeId, name, type, hash); } + void NavMeshDb::vacuum() + { + execute(*mDb, mVacuum); + } + namespace DbQueries { std::string_view GetMaxTileId::text() noexcept @@ -260,6 +316,48 @@ namespace DetourNavigator Sqlite3::bindParameter(db, statement, ":data", data); } + std::string_view DeleteTilesAt::text() noexcept + { + return deleteTilesAtQuery; + } + + void DeleteTilesAt::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, + const TilePosition& tilePosition) + { + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); + Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); + } + + std::string_view DeleteTilesAtExcept::text() noexcept + { + return deleteTilesAtExceptQuery; + } + + void DeleteTilesAtExcept::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, + const TilePosition& tilePosition, TileId excludeTileId) + { + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); + Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); + Sqlite3::bindParameter(db, statement, ":exclude_tile_id", excludeTileId); + } + + std::string_view DeleteTilesOutsideRange::text() noexcept + { + return deleteTilesOutsideRangeQuery; + } + + void DeleteTilesOutsideRange::bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, + const TilesPositionsRange& range) + { + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":begin_tile_position_x", range.mBegin.x()); + Sqlite3::bindParameter(db, statement, ":begin_tile_position_y", range.mBegin.y()); + Sqlite3::bindParameter(db, statement, ":end_tile_position_x", range.mEnd.x()); + Sqlite3::bindParameter(db, statement, ":end_tile_position_y", range.mEnd.y()); + } + std::string_view GetMaxShapeId::text() noexcept { return getMaxShapeIdQuery; @@ -291,5 +389,10 @@ namespace DetourNavigator Sqlite3::bindParameter(db, statement, ":type", static_cast(type)); Sqlite3::bindParameter(db, statement, ":hash", hash); } + + std::string_view Vacuum::text() noexcept + { + return vacuumQuery; + } } } diff --git a/components/detournavigator/navmeshdb.hpp b/components/detournavigator/navmeshdb.hpp index 09604e5706..f10a3a3288 100644 --- a/components/detournavigator/navmeshdb.hpp +++ b/components/detournavigator/navmeshdb.hpp @@ -3,6 +3,8 @@ #include "tileposition.hpp" +#include + #include #include #include @@ -89,6 +91,27 @@ namespace DetourNavigator const std::vector& data); }; + struct DeleteTilesAt + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, + const TilePosition& tilePosition); + }; + + struct DeleteTilesAtExcept + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, + const TilePosition& tilePosition, TileId excludeTileId); + }; + + struct DeleteTilesOutsideRange + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view worldspace, + const TilesPositionsRange& range); + }; + struct GetMaxShapeId { static std::string_view text() noexcept; @@ -108,6 +131,12 @@ namespace DetourNavigator static void bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); }; + + struct Vacuum + { + static std::string_view text() noexcept; + static void bind(sqlite3&, sqlite3_stmt&) {} + }; } class NavMeshDb @@ -130,12 +159,20 @@ namespace DetourNavigator int updateTile(TileId tileId, TileVersion version, const std::vector& data); + int deleteTilesAt(std::string_view worldspace, const TilePosition& tilePosition); + + int deleteTilesAtExcept(std::string_view worldspace, const TilePosition& tilePosition, TileId excludeTileId); + + int deleteTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range); + ShapeId getMaxShapeId(); std::optional findShapeId(std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); int insertShape(ShapeId shapeId, std::string_view name, ShapeType type, const Sqlite3::ConstBlob& hash); + void vacuum(); + private: Sqlite3::Db mDb; Sqlite3::Statement mGetMaxTileId; @@ -143,9 +180,13 @@ namespace DetourNavigator Sqlite3::Statement mGetTileData; Sqlite3::Statement mInsertTile; Sqlite3::Statement mUpdateTile; + Sqlite3::Statement mDeleteTilesAt; + Sqlite3::Statement mDeleteTilesAtExcept; + Sqlite3::Statement mDeleteTilesOutsideRange; Sqlite3::Statement mGetMaxShapeId; Sqlite3::Statement mFindShapeId; Sqlite3::Statement mInsertShape; + Sqlite3::Statement mVacuum; }; } diff --git a/components/detournavigator/tilespositionsrange.hpp b/components/detournavigator/tilespositionsrange.hpp new file mode 100644 index 0000000000..d5f2622ba1 --- /dev/null +++ b/components/detournavigator/tilespositionsrange.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILESPOSITIONSRANGE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILESPOSITIONSRANGE_H + +#include "tileposition.hpp" + +namespace DetourNavigator +{ + struct TilesPositionsRange + { + TilePosition mBegin; + TilePosition mEnd; + }; +} + +#endif From a7e76c6f3dab07621a22feb2c8f641e4b60e96b6 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 27 Jan 2022 21:17:47 +0100 Subject: [PATCH 2137/2859] Remove redundant include --- apps/openmw_test_suite/sqlite3/request.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw_test_suite/sqlite3/request.cpp b/apps/openmw_test_suite/sqlite3/request.cpp index e0094827d1..3bcf658c0d 100644 --- a/apps/openmw_test_suite/sqlite3/request.cpp +++ b/apps/openmw_test_suite/sqlite3/request.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include From 76ba5025e38fa3185247290c7ac540b936832d84 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 18 Feb 2022 20:48:15 +0300 Subject: [PATCH 2138/2859] Clarify root node transformation discard --- components/nif/node.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 837a4a0e30..34ac12e490 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -225,9 +225,10 @@ struct NiNode : Node // Discard transformations for the root node, otherwise some meshes // occasionally get wrong orientation. Only for NiNode-s for now, but // can be expanded if needed. + // FIXME: if node 0 is *not* the only root node, this must not happen. if (0 == recIndex && !Misc::StringUtils::ciEqual(name, "bip01")) { - static_cast(this)->trafo = Nif::Transformation::getIdentity(); + trafo = Nif::Transformation::getIdentity(); } } From 59130366380b234707be1974cf44b7f7ec6b5618 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 27 Jan 2022 21:17:56 +0100 Subject: [PATCH 2139/2859] Use SQLITE_OPEN_NOMUTEX --- components/sqlite3/db.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/sqlite3/db.cpp b/components/sqlite3/db.cpp index 54a156057d..b1e3afb1ae 100644 --- a/components/sqlite3/db.cpp +++ b/components/sqlite3/db.cpp @@ -16,7 +16,10 @@ namespace Sqlite3 Db makeDb(std::string_view path, const char* schema) { sqlite3* handle = nullptr; - const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + // All uses of NavMeshDb are protected by a mutex (navmeshtool) or serialized in a single thread (DbWorker) + // so additional synchronization between threads is not required and SQLITE_OPEN_NOMUTEX can be used. + // This is unsafe to use NavMeshDb without external synchronization because of internal state. + const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX; if (const int ec = sqlite3_open_v2(std::string(path).c_str(), &handle, flags, nullptr); ec != SQLITE_OK) { const std::string message(sqlite3_errmsg(handle)); From 8b4362ece7853077847f780e24caca31b53aa614 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 27 Jan 2022 21:18:01 +0100 Subject: [PATCH 2140/2859] Disable navmesh disk cache when db is failed to open --- components/detournavigator/navigator.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index cf3c4ba5b3..5b9341523c 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -3,6 +3,8 @@ #include "navigatorstub.hpp" #include "recastglobalallocator.hpp" +#include + namespace DetourNavigator { std::unique_ptr makeNavigator(const Settings& settings, const std::string& userDataPath) @@ -11,7 +13,16 @@ namespace DetourNavigator std::unique_ptr db; if (settings.mEnableNavMeshDiskCache) - db = std::make_unique(userDataPath + "/navmesh.db"); + { + try + { + db = std::make_unique(userDataPath + "/navmesh.db"); + } + catch (const std::exception& e) + { + Log(Debug::Error) << e.what() << ", navigation mesh disk cache will be disabled"; + } + } return std::make_unique(settings, std::move(db)); } From 54df5031b7c0184a135b250bae863919f839c73e Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 18 Feb 2022 20:47:34 +0300 Subject: [PATCH 2141/2859] Add XYZ rotation axis order support --- components/nif/data.cpp | 3 +-- components/nif/data.hpp | 15 +++++++++++++++ components/nifosg/controller.cpp | 32 ++++++++++++++++++++++++++++---- components/nifosg/controller.hpp | 2 ++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 1b6d302d68..36a123fdfa 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -439,9 +439,8 @@ void NiKeyframeData::read(NIFStream *nif) mRotations->read(nif); if(mRotations->mInterpolationType == InterpolationType_XYZ) { - //Chomp unused float if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) - nif->getFloat(); + mAxisOrder = static_cast(nif->getInt()); mXRotations = std::make_shared(); mYRotations = std::make_shared(); mZRotations = std::make_shared(); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 70171ac47c..919e442665 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -240,6 +240,21 @@ struct NiKeyframeData : public Record Vector3KeyMapPtr mTranslations; FloatKeyMapPtr mScales; + enum class AxisOrder + { + Order_XYZ = 0, + Order_XZY = 1, + Order_YZX = 2, + Order_YXZ = 3, + Order_ZXY = 4, + Order_ZYX = 5, + Order_XYX = 6, + Order_YZY = 7, + Order_ZXZ = 8 + }; + + AxisOrder mAxisOrder{AxisOrder::Order_XYZ}; + void read(NIFStream *nif) override; }; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 07134532e9..91bd94c74f 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -79,6 +79,7 @@ KeyframeController::KeyframeController(const KeyframeController ©, const osg , mZRotations(copy.mZRotations) , mTranslations(copy.mTranslations) , mScales(copy.mScales) + , mAxisOrder(copy.mAxisOrder) { } @@ -95,6 +96,7 @@ KeyframeController::KeyframeController(const Nif::NiKeyframeController *keyctrl) mZRotations = FloatInterpolator(interp->data->mZRotations); mTranslations = Vec3Interpolator(interp->data->mTranslations, interp->defaultPos); mScales = FloatInterpolator(interp->data->mScales, interp->defaultScale); + mAxisOrder = interp->data->mAxisOrder; } else { @@ -112,6 +114,7 @@ KeyframeController::KeyframeController(const Nif::NiKeyframeController *keyctrl) mZRotations = FloatInterpolator(keydata->mZRotations); mTranslations = Vec3Interpolator(keydata->mTranslations); mScales = FloatInterpolator(keydata->mScales, 1.f); + mAxisOrder = keydata->mAxisOrder; } } @@ -124,10 +127,31 @@ osg::Quat KeyframeController::getXYZRotation(float time) const yrot = mYRotations.interpKey(time); if (!mZRotations.empty()) zrot = mZRotations.interpKey(time); - osg::Quat xr(xrot, osg::Vec3f(1,0,0)); - osg::Quat yr(yrot, osg::Vec3f(0,1,0)); - osg::Quat zr(zrot, osg::Vec3f(0,0,1)); - return (xr*yr*zr); + osg::Quat xr(xrot, osg::X_AXIS); + osg::Quat yr(yrot, osg::Y_AXIS); + osg::Quat zr(zrot, osg::Z_AXIS); + switch (mAxisOrder) + { + case Nif::NiKeyframeData::AxisOrder::Order_XYZ: + return xr * yr * zr; + case Nif::NiKeyframeData::AxisOrder::Order_XZY: + return xr * zr * yr; + case Nif::NiKeyframeData::AxisOrder::Order_YZX: + return yr * zr * xr; + case Nif::NiKeyframeData::AxisOrder::Order_YXZ: + return yr * xr * zr; + case Nif::NiKeyframeData::AxisOrder::Order_ZXY: + return zr * xr * yr; + case Nif::NiKeyframeData::AxisOrder::Order_ZYX: + return zr * yr * xr; + case Nif::NiKeyframeData::AxisOrder::Order_XYX: + return xr * yr * xr; + case Nif::NiKeyframeData::AxisOrder::Order_YZY: + return yr * zr * yr; + case Nif::NiKeyframeData::AxisOrder::Order_ZXZ: + return zr * xr * zr; + } + return xr * yr * zr; } osg::Vec3f KeyframeController::getTranslation(float time) const diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index a5f887ebe6..acd96afd1e 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -255,6 +255,8 @@ namespace NifOsg Vec3Interpolator mTranslations; FloatInterpolator mScales; + Nif::NiKeyframeData::AxisOrder mAxisOrder{Nif::NiKeyframeData::AxisOrder::Order_XYZ}; + osg::Quat getXYZRotation(float time) const; }; From cccfb14785a27053c1375f0d36c6f56660213d1a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 19 Feb 2022 02:07:41 +0100 Subject: [PATCH 2142/2859] Remove user-defined constructor from ESM4::Quest To avoid explicit initialization. It should happen in the load functions anyway. --- components/esm4/loadqust.cpp | 11 ----------- components/esm4/loadqust.hpp | 1 - 2 files changed, 12 deletions(-) diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp index 67e250ae76..5588f90580 100644 --- a/components/esm4/loadqust.cpp +++ b/components/esm4/loadqust.cpp @@ -33,17 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Quest::Quest() : mFormId(0), mFlags(0), mQuestScript(0) -{ - mEditorId.clear(); - mQuestName.clear(); - mFileName.clear(); - - std::memset(&mScript.scriptHeader, 0, sizeof(ScriptHeader)); - mScript.scriptSource.clear(); - mScript.globReference = 0; -} - ESM4::Quest::~Quest() { } diff --git a/components/esm4/loadqust.hpp b/components/esm4/loadqust.hpp index 905a5cf0fa..853805900c 100644 --- a/components/esm4/loadqust.hpp +++ b/components/esm4/loadqust.hpp @@ -71,7 +71,6 @@ namespace ESM4 ScriptDefinition mScript; - Quest(); virtual ~Quest(); virtual void load(ESM4::Reader& reader); From 96463cbb02c419c7581c34c080afb367e530bb78 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 19 Feb 2022 02:49:06 +0100 Subject: [PATCH 2143/2859] Remove malformed and unused esm4/records.hpp file --- components/esm4/records.hpp | 74 ------------------------------------- 1 file changed, 74 deletions(-) delete mode 100644 components/esm4/records.hpp diff --git a/components/esm4/records.hpp b/components/esm4/records.hpp deleted file mode 100644 index d212091adb..0000000000 --- a/components/esm4/records.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef ESM4_RECORDS_H -#define ESM4_RECORDS_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#endif // ESM4_RECORDS_H From 57c1f2e231c22ee5e5e6c0b8210ac744a6dd6058 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 19 Feb 2022 02:49:22 +0100 Subject: [PATCH 2144/2859] Make sure everything compiles in ESM4 --- apps/openmw_test_suite/CMakeLists.txt | 2 + apps/openmw_test_suite/esm4/includes.cpp | 89 ++++++++++++++++++++++ components/CMakeLists.txt | 97 ++++++++++++++++++++++-- 3 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 apps/openmw_test_suite/esm4/includes.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 16d820c4f9..73e7af1d9e 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -72,6 +72,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) files/hash.cpp toutf8/toutf8.cpp + + esm4/includes.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/esm4/includes.cpp b/apps/openmw_test_suite/esm4/includes.cpp new file mode 100644 index 0000000000..8272571099 --- /dev/null +++ b/apps/openmw_test_suite/esm4/includes.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a0cc7ce2ce..8ab71b4cdf 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -95,13 +95,96 @@ add_component_dir (esm3terrain ) add_component_dir (esm4 - loadachr loadacre loadacti loadalch loadaloc loadammo loadanio loadappa loadarma loadarmo loadaspc loadbook - loadbptd loadcell loadclas loadclfm loadclot common loadcont loadcrea loaddial loaddobj loaddoor loadeyes - loadflor loadflst formid loadfurn loadglob loadgras loadhair loadhdpt loadidle loadidlm loadimod loadinfo - loadingr loadkeym loadland loadlgtm loadligh loadltex loadlvlc loadlvli loadlvln loadmato loadmisc loadmset - loadmstt loadmusc loadnavi loadnavm loadnote loadnpc loadotft loadpack loadpgrd loadpgre loadpwat loadqust - loadrace loadrefr loadregn loadroad loadsbsp loadscol loadscpt loadscrl loadsgst loadslgm loadsndr - loadsoun loadstat loadtact loadterm loadtes4 loadtree loadtxst loadweap loadwrld reader + acti + actor + common + dialogue + effect + formid + inventory + lighting + loadachr + loadacre + loadacti + loadalch + loadaloc + loadammo + loadanio + loadappa + loadarma + loadarmo + loadaspc + loadbook + loadbptd + loadcell + loadclas + loadclfm + loadclot + loadcont + loadcrea + loaddial + loaddobj + loaddoor + loadeyes + loadflor + loadflst + loadfurn + loadglob + loadgras + loadgrup + loadhair + loadhdpt + loadidle + loadidlm + loadimod + loadinfo + loadingr + loadkeym + loadland + loadlgtm + loadligh + loadltex + loadlvlc + loadlvli + loadlvln + loadmato + loadmisc + loadmset + loadmstt + loadmusc + loadnavi + loadnavm + loadnote + loadnpc + loadotft + loadpack + loadpgrd + loadpgre + loadpwat + loadqust + loadrace + loadrefr + loadregn + loadroad + loadsbsp + loadscol + loadscpt + loadscrl + loadsgst + loadslgm + loadsndr + loadsoun + loadstat + loadtact + loadterm + loadtes4 + loadtree + loadtxst + loadweap + loadwrld + reader + reference + script ) add_component_dir (misc From b997e28e57413a68cdda4271c559a69000cf9152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 19 Feb 2022 14:56:51 +0200 Subject: [PATCH 2145/2859] Fix #6633: AiSequence packages being removed incorrectly --- apps/openmw/mwmechanics/aisequence.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 0cb1e97603..bf01936f90 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -315,9 +315,16 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac mPackages.push_back(package->clone()); } - // To account for the rare case where AiPackage::execute() queued another AI package - // (e.g. AiPursue executing a dialogue script that uses startCombat) - erase(mPackages.begin()); + // The active package is typically the first entry, this is however not always the case + // e.g. AiPursue executing a dialogue script that uses startCombat adds a combat package to the front + // due to the priority. + auto activePackageIt = std::find_if(mPackages.begin(), mPackages.end(), [&](auto& entry) + { + return entry.get() == package; + }); + + erase(activePackageIt); + if (isActualAiPackage(packageTypeId)) mDone = true; } From 6fd251e8f7f246df7bf8446ae7b0f380bab643ed Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 19 Feb 2022 21:41:40 +0100 Subject: [PATCH 2146/2859] Avoid starting async png writing when overlay image is null --- apps/openmw/mwrender/globalmap.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 3d0a066451..acd566ff18 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -635,6 +635,8 @@ namespace MWRender void GlobalMap::asyncWritePng() { + if (mOverlayImage == nullptr) + return; // Use deep copy to avoid any sychronization mWritePng = new WritePng(new osg::Image(*mOverlayImage, osg::CopyOp::DEEP_COPY_ALL)); mWorkQueue->addWorkItem(mWritePng, /*front=*/true); From 79c3b2e1a3d9796de8c2d41aadc945605d27767b Mon Sep 17 00:00:00 2001 From: Lamoot Date: Sat, 19 Feb 2022 22:20:26 +0100 Subject: [PATCH 2147/2859] Cleanup and fix articles connected with the collada asset workflow. --- ...ine-blender-collada-animated-creature.rst} | 6 +- ...pipeline-blender-collada-static-models.rst | 3 +- .../pipeline-blender-collada.rst | 127 ------------------ .../reference/modding/settings/models.rst | 46 +++++-- 4 files changed, 40 insertions(+), 142 deletions(-) rename docs/source/reference/modding/custom-models/{pipeline-blender-collada-animated-creature => pipeline-blender-collada-animated-creature.rst} (99%) delete mode 100644 docs/source/reference/modding/custom-models/pipeline-blender-collada.rst diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature b/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature.rst similarity index 99% rename from docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature rename to docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature.rst index b759bf9a02..096af20932 100644 --- a/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-animated-creature.rst @@ -64,7 +64,7 @@ Armature / Rig Only a single rig object should be included in the exported file. Exporting multiple rigs is not reliably supported and can lead to errors. Root ----- +==== The rig needs to be structured in a specific way. There should be a single top bone in the rig’s hierarchy, the root bone named ``Bip01``. The name is @@ -77,7 +77,7 @@ needs to have its ``Deform`` flag **enabled**. Deform Bones ------------- +============ Below the root bone, the bones are divided into two branches. One branch contains the deform bones which get included in the final exported file. These @@ -90,7 +90,7 @@ convention used for NPC and player models. :align: center Control Bones -------------- +============= The other branch holds control and helper bones that enable comfortable animation in Blender, but are neither required nor included in the exported diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst index 5cb336a305..860993273a 100644 --- a/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst +++ b/docs/source/reference/modding/custom-models/pipeline-blender-collada-static-models.rst @@ -19,7 +19,8 @@ To use the Blender to OpenMW pipeline via COLLADA, you will need the following. * A model you would like to export. In our case, it's a barrel. The Barrel -******** +********** + The barrel shown in this tutorial, and its revelant files, are available from the `Example Suite repository `_. This should be useful for further study of how to set up a static prop in case diff --git a/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst b/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst deleted file mode 100644 index 47096a83e7..0000000000 --- a/docs/source/reference/modding/custom-models/pipeline-blender-collada.rst +++ /dev/null @@ -1,127 +0,0 @@ -############################## -Blender to OpenMW with COLLADA -############################## - -Requirements -************ -To use the Blender to OpenMW pipeline via COLLADA, you will need the following. - -* `OpenMW 0.47 `_ or later -* `Blender 2.81 `_ or later. Latest confirmed, working version is Blender 2.91 -* `Better COLLADA Exporter `_ tuned for OpenMW -* A model you would like to export - - -Static Models -************* -Static models are those that don't have any animations included in the exported file. First, let's take a look at how the fundamental properties of a scene in Blender translate to a COLLADA model suitable for use in OpenMW. These apply the same to static and animated models. - -Location -======== - -Objects keep their visual location and origin they had in the original scene. - -Rotation -======== - -* Blender’s +Z axis is up axis in OpenMW -* Blender’s +Y axis is front axis in OpenMW -* Blender’s X axis is left-right axis in OpenMW - -Scale -===== - -Scale ratio between Blender and OpenMW is 70 to 1. This means 70 units in Blender translate to 1 m in OpenMW. - -However, a scale factor like this is impractical to work with. A better approach is to work with a scale of 1 Blender unit = 1 m and apply the 70 scale factor at export. The exporter will automatically scale all object, mesh, armature and animation data. - -Materials -========= - -OpenMW uses the classic, specular material setup and currently doesn't use the more mainstream `PBR `_ way. In Blender, the mesh needs a default material with a diffuse texture connected to the ``Base Color`` socket. This is enough for the material to be included in the exported COLLADA file. - -Additional texture types, such as specular or normal maps, will be automatically recognized and used by OpenMW. They need an identical base name as the diffuse texture, a suffix, and be in the same folder. How to enable this and what suffix is used for what texture type is covered in more detail in :doc:`../../modding/texture-modding/texture-basics`. - -Collision Shapes -================ - -In Blender, create an empty and name it ``collision``. Any mesh that is a child of this empty will be used for physics collision and will not be visible. There can be multiple child meshes under ``collision`` and they will all contribute to the collision shapes. The meshes themselves can have an arbitrary name, it's only the name of the empty that is important. The ``tcb`` command in OpenMW's in-game console will make the collision shapes visible and you will be able to inspect them. - -Exporter Settings -================= - -For static models, use the following exporter settings. Before export, select all objects you wish to include in the exported file and have the "Selected Objects" option enabled. Without this, the exporter could fail. - - -.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/dae_exporter_static.jpg - :align: center - - -Animated Models -*************** - -Animated models are those where a hierarchy of bones, known as armature, deforms the mesh and makes things move. Besides the topics covered above, the following requirements apply. - -Armature -======== - -* A single armature per COLLADA file is advised to avoid any potential problems. -* There needs to be a single top-most bone in the armature’s hierarchy, where both the deformation and control bones fall under it. -* Not all bones need to be exported. By disabing the bone’s “Deform” property and using the corresponding option in the exporter, it is possible to export only the bones needed for animation. - - -Animations -========== - -Every action in Blender is exported as its own animation clip in COLLADA. Actions you don't wish to export need to have "-noexp" added to their name, with the corresponding option enabled in the exporter. - -Due to current limitations of the format and exporter, the keyframes of individual actions must not overlap with other actions. The keyframes need to be manually offset to a unique range on the timeline as shown in this example. - -.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/dae_animations_on_timeline.jpg - :align: center - -Textkeys --------- - -The exported COLLADA file requires a corresponding textkeys file for OpenMW to properly read the animations. Textkeys is a .txt file containing animation definitions. Textkeys file is placed in the same folder as the model and uses a name matching the model. - - - ``OpenMWDude.dae`` - - ``OpenMWDude.txt`` - -Textkeys use a simple format as shown in the example. Name, start and stop values can be taken from the corresponding COLLADA file for each ````. - -.. code:: - - idle: start 0.03333333333333333 - idle: stop 2.033333333333333 - runforward: start 2.0666666666666664 - runforward: stop 3.0666666666666664 - runback: start 3.1 - runback: stop 4.1 - ... - - -Root Motion -=========== - -OpenMW can read the movement of the root (top-most in hierarchy) bone and use it to move objects in the game world. For this to work, the root bone must be animated to move through space. The root bone must, in its default pose, be alligned with the world. - -.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/dae_rig_root.jpg - :align: center - - -Exporter Settings -================= - -For animated models, use the following exporter settings. Before export, select all objects you wish to include in the exported file and have the "Selected Objects" option enabled. Without this, the exporter could fail. - -.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/custom-models/_static/dae_exporter_animated.jpg - :align: center - - - - - - - - diff --git a/docs/source/reference/modding/settings/models.rst b/docs/source/reference/modding/settings/models.rst index c0192067a7..b870268567 100644 --- a/docs/source/reference/modding/settings/models.rst +++ b/docs/source/reference/modding/settings/models.rst @@ -37,10 +37,15 @@ xbaseanim :Range: :Default: meshes/xbase_anim.nif -Path to the file used for 3rd person base animation model that looks also for the corresponding kf-file. +Path to the file used for 3rd person base animation model that looks also for +the corresponding kf-file. .. note:: - If you are using the COLLADA format, you don't need to separate the files as they are separated between .nif and .kf files. It works if you plug the same COLLADA file into all animation-related entries, just make sure there is a corresponding textkeys file. You can read more about the textkeys in :doc:`../../modding/custom-models/pipeline-blender-collada`. + If you are using the COLLADA format, you don't need to separate the files as + they are separated between .nif and .kf files. It works if you plug the same + COLLADA file into all animation-related entries, just make sure there is a + corresponding textkeys file. You can read more about the textkeys in + :doc:`../../modding/custom-models/pipeline-blender-collada-animated-creature`. baseanim -------- @@ -58,7 +63,8 @@ xbaseanim1st :Range: :Default: meshes/xbase_anim.1st.nif -Path to the file used for 1st person base animation model that looks also for corresponding kf-file. +Path to the file used for 1st person base animation model that looks also for +corresponding kf-file. baseanimkna ----------- @@ -175,7 +181,9 @@ skyatmosphere :Range: :Default: meshes/sky_atmosphere.nif -Path to the file used for the sky atmosphere mesh, which is one of the three meshes needed to render the sky. It's used to make the top half of the sky blue and renders in front of the background color. +Path to the file used for the sky atmosphere mesh, which is one of the three +meshes needed to render the sky. It's used to make the top half of the sky blue +and renders in front of the background color. skyclouds --------- @@ -184,7 +192,9 @@ skyclouds :Range: :Default: meshes/sky_clouds_01.nif. -Path to the file used for the sky clouds mesh, which is one of the three meshes needed to render the sky. It displays a scrolling texture of clouds in front of the atmosphere mesh and background color +Path to the file used for the sky clouds mesh, which is one of the three meshes +needed to render the sky. It displays a scrolling texture of clouds in front of +the atmosphere mesh and background color skynight01 ---------- @@ -193,7 +203,10 @@ skynight01 :Range: :Default: meshes/sky_night_01.nif -Path to the file used for the sky stars mesh, which is one of the three meshes needed to render the sky. During night, it displays a texture with stars in front of the atmosphere and behind the clouds. If skynight02 is present, skynight01 will not be used. +Path to the file used for the sky stars mesh, which is one of the three meshes +needed to render the sky. During night, it displays a texture with stars in +front of the atmosphere and behind the clouds. If skynight02 is present, +skynight01 will not be used. skynight02 ---------- @@ -202,7 +215,10 @@ skynight02 :Range: :Default: meshes/sky_night_02.nif -Path to the file used for the sky stars mesh, which is one of the three meshes needed to render the sky. During night, it displays a texture with stars in front of the atmosphere and behind the clouds. If it's present it will be used instead of skynight01. +Path to the file used for the sky stars mesh, which is one of the three meshes +needed to render the sky. During night, it displays a texture with stars in +front of the atmosphere and behind the clouds. If it's present it will be used +instead of skynight01. weatherashcloud --------------- @@ -211,7 +227,9 @@ weatherashcloud :Range: :Default: meshes/ashcloud.nif -Path to the file used for the ash clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect. +Path to the file used for the ash clouds weather effect in Morrowind. OpenMW +doesn't use this file, instead it renders a similar looking particle effect. +Changing this won't have any effect. weatherblightcloud ------------------ @@ -220,7 +238,9 @@ weatherblightcloud :Range: :Default: meshes/blightcloud.nif -Path to the file used for the blight clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect. +Path to the file used for the blight clouds weather effect in Morrowind. OpenMW +doesn't use this file, instead it renders a similar looking particle effect. +Changing this won't have any effect. weathersnow ----------- @@ -229,7 +249,9 @@ weathersnow :Range: :Default: meshes/snow.nif -Path to the file used for the snow falling weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect. +Path to the file used for the snow falling weather effect in Morrowind. OpenMW +doesn't use this file, instead it renders a similar looking particle effect. +Changing this won't have any effect. weatherblizzard --------------- @@ -238,4 +260,6 @@ weatherblizzard :Range: :Default: meshes/blizzard.nif -Path to the file used for the blizzard clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect. +Path to the file used for the blizzard clouds weather effect in Morrowind. +OpenMW doesn't use this file, instead it renders a similar looking particle +effect. Changing this won't have any effect. From cc6dce544386f459c19d414ef3c43cbabcddd6c0 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 21 Feb 2022 19:49:00 +0000 Subject: [PATCH 2148/2859] Support controller touchpads (Resolves https://gitlab.com/OpenMW/openmw/-/issues/6639) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/luamanager.hpp | 15 +- apps/openmw/mwinput/controllermanager.cpp | 18 ++ apps/openmw/mwinput/controllermanager.hpp | 4 + apps/openmw/mwlua/inputbindings.cpp | 12 ++ apps/openmw/mwlua/playerscripts.hpp | 23 ++- components/sdlutil/events.hpp | 32 +++- components/sdlutil/sdlinputwrapper.cpp | 14 ++ .../lua-scripting/engine_handlers.rst | 162 ++++++++++-------- files/lua_api/openmw/input.lua | 8 + 10 files changed, 207 insertions(+), 83 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3c8a0a4d9a..71c138eee9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query - luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings + luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings ) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 7fa8b6df58..5ed751ffed 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace MWWorld { class Ptr; @@ -43,8 +45,17 @@ namespace MWBase struct InputEvent { - enum {KeyPressed, KeyReleased, ControllerPressed, ControllerReleased, Action} mType; - std::variant mValue; + enum { + KeyPressed, + KeyReleased, + ControllerPressed, + ControllerReleased, + Action, + TouchPressed, + TouchReleased, + TouchMoved, + } mType; + std::variant mValue; }; virtual void inputEvent(const InputEvent& event) = 0; diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 6c6549face..f4d307e7d0 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -433,4 +433,22 @@ namespace MWInput #endif return std::array({gyro[0], gyro[1], gyro[2]}); } + + void ControllerManager::touchpadMoved(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::TouchMoved, arg }); + } + + void ControllerManager::touchpadPressed(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::TouchPressed, arg }); + } + + void ControllerManager::touchpadReleased(int deviceId, const SDLUtil::TouchEvent& arg) + { + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::TouchReleased, arg }); + } } diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index 8df6ee5c9f..2472128c26 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -31,6 +31,10 @@ namespace MWInput void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) override; void controllerRemoved(const SDL_ControllerDeviceEvent &arg) override; + void touchpadMoved(int deviceId, const SDLUtil::TouchEvent& arg) override; + void touchpadPressed(int deviceId, const SDLUtil::TouchEvent& arg) override; + void touchpadReleased(int deviceId, const SDLUtil::TouchEvent& arg) override; + void processChangedSettings(const Settings::CategorySettingVector& changed); void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; } diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 9ca2d94770..a8487557ba 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" @@ -32,6 +34,16 @@ namespace MWLua keyEvent["withAlt"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_ALT; }); keyEvent["withSuper"] = sol::readonly_property([](const SDL_Keysym& e) -> bool { return e.mod & KMOD_GUI; }); + auto touchpadEvent = context.mLua->sol().new_usertype("TouchpadEvent"); + touchpadEvent["device"] = sol::readonly_property( + [](const SDLUtil::TouchEvent& e) -> int { return e.mDevice; }); + touchpadEvent["finger"] = sol::readonly_property( + [](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; }); + touchpadEvent["position"] = sol::readonly_property( + [](const SDLUtil::TouchEvent& e) -> osg::Vec2f { return osg::Vec2f(e.mX, e.mY);}); + touchpadEvent["pressure"] = sol::readonly_property( + [](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); sol::table api(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index e8cdd120ac..15ab451792 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -3,6 +3,8 @@ #include +#include + #include "../mwbase/luamanager.hpp" #include "localscripts.hpp" @@ -15,9 +17,12 @@ namespace MWLua public: PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer) { - registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers, - &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, - &mActionHandlers, &mInputUpdateHandlers}); + registerEngineHandlers({ + &mKeyPressHandlers, &mKeyReleaseHandlers, + &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, + &mActionHandlers, &mInputUpdateHandlers, + &mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved + }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) @@ -40,6 +45,15 @@ namespace MWLua case InputEvent::Action: callEngineHandlers(mActionHandlers, std::get(event.mValue)); break; + case InputEvent::TouchPressed: + callEngineHandlers(mTouchpadPressed, std::get(event.mValue)); + break; + case InputEvent::TouchReleased: + callEngineHandlers(mTouchpadReleased, std::get(event.mValue)); + break; + case InputEvent::TouchMoved: + callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); + break; } } @@ -52,6 +66,9 @@ namespace MWLua EngineHandlerList mControllerButtonReleaseHandlers{"onControllerButtonRelease"}; EngineHandlerList mActionHandlers{"onInputAction"}; EngineHandlerList mInputUpdateHandlers{"onInputUpdate"}; + EngineHandlerList mTouchpadPressed{ "onTouchPress" }; + EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; + EngineHandlerList mTouchpadMoved{ "onTouchMove" }; }; } diff --git a/components/sdlutil/events.hpp b/components/sdlutil/events.hpp index a0dd11acec..ce71f04457 100644 --- a/components/sdlutil/events.hpp +++ b/components/sdlutil/events.hpp @@ -1,6 +1,7 @@ #ifndef _SFO_EVENTS_H #define _SFO_EVENTS_H +#include #include #include @@ -18,6 +19,24 @@ struct MouseMotionEvent : SDL_MouseMotionEvent { Sint32 z; }; +struct TouchEvent { + int mDevice; + int mFinger; + float mX; + float mY; + float mPressure; + + #if SDL_VERSION_ATLEAST(2, 0, 14) + explicit TouchEvent(const SDL_ControllerTouchpadEvent& arg) + : mDevice(arg.touchpad) + , mFinger(arg.finger) + , mX(arg.x) + , mY(arg.y) + , mPressure(arg.pressure) + {} + #endif +}; + /////////////// // Listeners // @@ -50,25 +69,24 @@ public: virtual void keyReleased(const SDL_KeyboardEvent &arg) = 0; }; + class ControllerListener { public: virtual ~ControllerListener() {} - /** @remarks Joystick button down event */ - virtual void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; - /** @remarks Joystick button up event */ + virtual void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; virtual void buttonReleased(int deviceID, const SDL_ControllerButtonEvent &evt) = 0; - /** @remarks Joystick axis moved event */ virtual void axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg) = 0; - /** @remarks Joystick Added **/ virtual void controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) = 0; - - /** @remarks Joystick Removed **/ virtual void controllerRemoved(const SDL_ControllerDeviceEvent &arg) = 0; + virtual void touchpadMoved(int deviceId, const TouchEvent& arg) = 0; + virtual void touchpadPressed(int deviceId, const TouchEvent& arg) = 0; + virtual void touchpadReleased(int deviceId, const TouchEvent& arg) = 0; + }; class WindowListener diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 42276cc2a1..3bd74f569a 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -146,6 +146,20 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v if(mConListener) mConListener->axisMoved(1, evt.caxis); break; + #if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLERSENSORUPDATE: + // controller sensor data is received on demand + break; + case SDL_CONTROLLERTOUCHPADDOWN: + mConListener->touchpadPressed(1, TouchEvent(evt.ctouchpad)); + break; + case SDL_CONTROLLERTOUCHPADMOTION: + mConListener->touchpadMoved(1, TouchEvent(evt.ctouchpad)); + break; + case SDL_CONTROLLERTOUCHPADUP: + mConListener->touchpadReleased(1, TouchEvent(evt.ctouchpad)); + break; + #endif case SDL_WINDOWEVENT: handleWindowEvent(evt); break; diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index d247803c08..adc07d25fe 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -3,74 +3,96 @@ Engine handlers reference Engine handler is a function defined by a script, that can be called by the engine. -+---------------------------------------------------------------------------------------------------------+ -| **Can be defined by any script** | -+----------------------------------+----------------------------------------------------------------------+ -| onInit(initData) | | Called once when the script is created (not loaded). `InitData can`| -| | | `be assigned to a script in openmw-cs (not yet implemented)`. | -| | | ``onInterfaceOverride`` can be called before ``onInit``. | -+----------------------------------+----------------------------------------------------------------------+ -| onUpdate(dt) | | Called every frame if the game is not paused. `dt` is the time | -| | | from the last update in seconds. | -+----------------------------------+----------------------------------------------------------------------+ -| onSave() -> savedData | | Called when the game is saving. May be called in inactive | -| | | state, so it shouldn't use `openmw.nearby`. | -+----------------------------------+----------------------------------------------------------------------+ -| onLoad(savedData, initData) | | Called on loading with the data previosly returned by | -| | | onSave. During loading the object is always inactive. initData is | -| | | the same as in onInit. | -| | | Note that onLoad means loading a script rather than loading a game.| -| | | If a script did not exist when a game was saved onLoad will not be | -| | | called, but onInit will. | -+----------------------------------+----------------------------------------------------------------------+ -| onInterfaceOverride(base) | | Called if the current script has an interface and overrides an | -| | | interface (``base``) of another script. | -+----------------------------------+----------------------------------------------------------------------+ -| **Only for global scripts** | -+----------------------------------+----------------------------------------------------------------------+ -| onNewGame() | New game is started | -+----------------------------------+----------------------------------------------------------------------+ -| onPlayerAdded(player) | Player added to the game world. The argument is a `Game object`. | -+----------------------------------+----------------------------------------------------------------------+ -| onActorActive(actor) | Actor (NPC or Creature) becomes active. | -+----------------------------------+----------------------------------------------------------------------+ -| **Only for local scripts** | -+----------------------------------+----------------------------------------------------------------------+ -| onActive() | | Called when the object becomes active (either a player | -| | | came to this cell again, or a save was loaded). | -+----------------------------------+----------------------------------------------------------------------+ -| onInactive() | | Object became inactive. Since it is inactive the handler | -| | | can not access anything nearby, but it is possible to send | -| | | an event to global scripts. | -+----------------------------------+----------------------------------------------------------------------+ -| onActivated(actor) | | Called on an object when an actor activates it. Note that picking | -| | | up an item is also an activation and works this way: (1) a copy of | -| | | the item is placed to the actor's inventory, (2) count of | -| | | the original item is set to zero, (3) and only then onActivated is | -| | | called on the original item, so self.count is already zero. | -+----------------------------------+----------------------------------------------------------------------+ -| onConsume(recordId) | | Called if `recordId` (e.g. a potion) is consumed. | -+----------------------------------+----------------------------------------------------------------------+ -| **Only for local scripts attached to a player** | -+----------------------------------+----------------------------------------------------------------------+ -| onInputUpdate(dt) | | Called every frame (if the game is not paused) right after | -| | | processing user input. Use it only for latency-critical stuff. | -+----------------------------------+----------------------------------------------------------------------+ -| onKeyPress(key) | | `Key `_ is pressed. | -| | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` | -+----------------------------------+----------------------------------------------------------------------+ -| onKeyRelease(key) | | `Key `_ is released. | -| | | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` | -+----------------------------------+----------------------------------------------------------------------+ -| onControllerButtonPress(id) | | A `button `_ on a game | -| | controller is pressed. Usage example: | -| | | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` | -+----------------------------------+----------------------------------------------------------------------+ -| onControllerButtonRelease(id) | | A `button `_ on a game | -| | controller is released. Usage example: | -| | | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` | -+----------------------------------+----------------------------------------------------------------------+ -| onInputAction(id) | | `Game control `_ is pressed. | -| | | Usage example: ``if id == input.ACTION.ToggleWeapon then ...`` | -+----------------------------------+----------------------------------------------------------------------+ + +**Can be defined by any script** + +.. list-table:: + :widths: 20 80 + + * - onInit(initData) + - | Called once when the script is created (not loaded). `InitData can be` + | `assigned to a script in openmw-cs (not yet implemented).` + | ``onInterfaceOverride`` can be called before ``onInit``. + * - onUpdate(dt) + - | Called every frame if the game is not paused. `dt` is the time + | from the last update in seconds. + * - onSave() -> savedData + - | Called when the game is saving. May be called in inactive state, + | so it shouldn't use `openmw.nearby`. + * - onLoad(savedData, initData) + - | Called on loading with the data previosly returned by + | ``onSave``. During loading the object is always inactive. ``initData`` is + | the same as in ``onInit``. + | Note that ``onLoad`` means loading a script rather than loading a game. + | If a script did not exist when a game was saved onLoad will not be + | called, but ``onInit`` will. + * - onInterfaceOverride(base) + - | Called if the current script has an interface and overrides an interface + | (``base``) of another script. + +**Only for global scripts** + +.. list-table:: + :widths: 20 80 + + * - onNewGame() + - New game is started + * - onPlayerAdded(player) + - Player added to the game world. The argument is a `Game object`. + * - onActorActive(actor) + - Actor (NPC or Creature) becomes active. + +**Only for local scripts** + +.. list-table:: + :widths: 20 80 + + * - onActive() + - | Called when the object becomes active + | (either a player came to this cell again, or a save was loaded). + * - onInactive() + - | Object became inactive. Since it is inactive the handler + | can not access anything nearby, but it is possible to send + | an event to global scripts. + * - onActivated(actor) + - | Called on an object when an actor activates it. Note that picking + | up an item is also an activation and works this way: (1) a copy of + | the item is placed to the actor's inventory, (2) count of + | the original item is set to zero, (3) and only then onActivated is + | called on the original item, so self.count is already zero. + * - onConsume(recordId) + - Called if `recordId` (e.g. a potion) is consumed. + +**Only for local scripts attached to a player** + +.. list-table:: + :widths: 20 80 + + * - onInputUpdate(dt) + - | Called every frame (if the game is not paused) right after processing + | user input. Use it only for latency-critical stuff. + * - onKeyPress(key) + - | `Key `_ is pressed. + | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` + * - onKeyRelease(key) + - | `Key `_ is released. + | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` + * - onControllerButtonPress(id) + - | A `button `_ on a game controller is pressed. + | Usage example: ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` + * - onControllerButtonRelease(id) + - | A `button `_ on a game controller is released. + | Usage example: ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` + * - onInputAction(id) + - | `Game control `_ is pressed. + | Usage example: ``if id == input.ACTION.ToggleWeapon then ...`` + * - onTouchPress(touchEvent) + - | A finger pressed on a touch device. + | `Touch event `_. + * - onTouchRelease(touchEvent) + - | A finger released a touch device. + | `Touch event `_. + * - onTouchMove(touchEvent) + - | A finger moved on a touch device. + | `Touch event `_. \ No newline at end of file diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 361073e79d..5ebaa46e1f 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -313,5 +313,13 @@ -- @field [parent=#KeyboardEvent] #boolean withAlt Is `Alt` key pressed. -- @field [parent=#KeyboardEvent] #boolean withSuper Is `Super`/`Win` key pressed. +--- +-- The argument of onTouchPress/onTouchRelease/onTouchMove engine handlers. +-- @type TouchEvent +-- @field [parent=#TouchEvent] #number device Device id (there might be multiple touch devices connected). Note: the specific device ids are not guaranteed. Always use previous user input (onTouch... handlers) to get a valid device id (e. g. in your script's settings page). +-- @field [parent=#TouchEvent] #number finger Finger id (the device might support multitouch). +-- @field [parent=#TouchEvent] openmw.util#Vector2 position Relative position on the touch device (0 to 1 from top left corner), +-- @field [parent=#TouchEvent] #number pressure Pressure of the finger. + return nil From a1abc84f5911677dd8a34beb92d16b3344c42c82 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 21 Feb 2022 20:43:27 +0000 Subject: [PATCH 2149/2859] Implement pairs and ipairs for ui.content and ui.layers. Document all iterable types in a uniform way. --- apps/openmw/mwlua/uibindings.cpp | 30 +++++++++++ components/lua/luastate.cpp | 33 ++++++------ docs/source/generate_luadoc.sh | 1 - docs/source/reference/lua-scripting/api.rst | 1 + .../reference/lua-scripting/iterables.rst | 50 +++++++++++++++++++ .../reference/lua-scripting/overview.rst | 2 +- files/lua_api/openmw/core.lua | 4 +- files/lua_api/openmw/ui.lua | 9 +++- 8 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 docs/source/reference/lua-scripting/iterables.rst diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 4333ae5072..248f82f28a 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -166,6 +166,21 @@ namespace MWLua else return sol::nullopt; }; + { + auto pairs = [](LuaUi::Content& content) + { + auto next = [](LuaUi::Content& content, size_t i) -> sol::optional> + { + if (i < content.size()) + return std::make_tuple(i + 1, content.at(i)); + else + return sol::nullopt; + }; + return std::make_tuple(next, content, 0); + }; + uiContent[sol::meta_function::ipairs] = pairs; + uiContent[sol::meta_function::pairs] = pairs; + } auto element = context.mLua->sol().new_usertype("Element"); element["layout"] = sol::property( @@ -233,6 +248,21 @@ namespace MWLua options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); context.mLuaManager->addAction(std::make_unique(name, afterName, options, context.mLua)); }; + { + auto pairs = [layers](const sol::object&) + { + auto next = [](const sol::table& l, size_t i) -> sol::optional> + { + if (i < LuaUi::Layers::size()) + return std::make_tuple(i + 1, LuaUi::Layers::at(i)); + else + return sol::nullopt; + }; + return std::make_tuple(next, layers, 0); + }; + layers[sol::meta_function::pairs] = pairs; + layers[sol::meta_function::ipairs] = pairs; + } api["layers"] = LuaUtil::makeReadOnly(layers); sol::table typeTable = context.mLua->newTable(); diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 13e1421116..dbd792224f 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -59,6 +59,23 @@ namespace LuaUtil mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; mLua["cmetatable"] = [](const sol::table& v) -> sol::object { return v[sol::metatable_key]; }; + + // Some fixes for compatibility between different Lua versions + if (mLua["unpack"] == sol::nil) + mLua["unpack"] = mLua["table"]["unpack"]; + else if (mLua["table"]["unpack"] == sol::nil) + mLua["table"]["unpack"] = mLua["unpack"]; + if (LUA_VERSION_NUM <= 501) + { + mLua.script(R"( + local _pairs = pairs + local _ipairs = ipairs + local _cmeta = cmetatable + pairs = function(v) return ((_cmeta(v) or v).__pairs or _pairs)(v) end + ipairs = function(v) return ((_cmeta(v) or v).__ipairs or _ipairs)(v) end + )"); + } + mLua.script(R"( local _pairs = pairs local _ipairs = ipairs @@ -78,22 +95,6 @@ namespace LuaUtil function ipairsForReadOnly(v) return _ipairs(_cmeta(v).__index) end )"); - // Some fixes for compatibility between different Lua versions - if (mLua["unpack"] == sol::nil) - mLua["unpack"] = mLua["table"]["unpack"]; - else if (mLua["table"]["unpack"] == sol::nil) - mLua["table"]["unpack"] = mLua["unpack"]; - if (LUA_VERSION_NUM <= 501) - { - mLua.script(R"( - local _pairs = pairs - local _ipairs = ipairs - local _cmeta = cmetatable - pairs = function(v) return ((_cmeta(v) or v).__pairs or _pairs)(v) end - ipairs = function(v) return ((_cmeta(v) or v).__ipairs or _ipairs)(v) end - )"); - } - mSandboxEnv = sol::table(mLua, sol::create); mSandboxEnv["_VERSION"] = mLua["_VERSION"]; for (const std::string& s : safeFunctions) diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index 99e387cf0f..b3ec10ba97 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -67,4 +67,3 @@ cd $FILES_DIR/builtin_scripts $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/ai.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera.lua - diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 41fd52253f..0681c63dea 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -25,6 +25,7 @@ Lua API reference openmw_aux_time interface_ai interface_camera + iterables - :ref:`Engine handlers reference` diff --git a/docs/source/reference/lua-scripting/iterables.rst b/docs/source/reference/lua-scripting/iterables.rst new file mode 100644 index 0000000000..208b7f1c92 --- /dev/null +++ b/docs/source/reference/lua-scripting/iterables.rst @@ -0,0 +1,50 @@ +Iterable types +============== + +List Iterable +------------- + +An iterable with defined size and order. + +.. code-block:: Lua + + -- can iterate over the list with pairs + for i, v in pairs(list) do + -- ... + end + +.. code-block:: Lua + + -- can iterate over the list with ipairs + for i, v in ipairs(list) do + -- ... + end + +.. code-block:: Lua + + -- can get total size with the size # operator + local length = #list + +.. code-block:: Lua + + -- can index the list with numbers + for i = 1, length do + list[i] + end + +Map Iterable +------------ + +An iterable with undefined order. + +.. code-block:: Lua + + -- can iterate over the map with pairs + for k, v in pairs(map) do + -- ... + end + +.. code-block:: Lua + + -- can index the map by key + map[key] diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index e6024a27a1..e07abee899 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -498,7 +498,7 @@ At some moment it will send the 'DamagedByDarkPower' event to all nearby actors: local nearby = require('openmw.nearby') local function onActivated() - for i, actor in nearby.actors:ipairs() do + for i, actor in ipairs(nearby.actors) do local dist = (self.position - actor.position):length() if dist < 500 then local damage = (1 - dist / 500) * 200 diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index e7e657a853..84e2b4faae 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -289,9 +289,9 @@ ------------------------------------------------------------------------------- --- List of GameObjects. +-- List of GameObjects. Implements [iterables#List](iterables.html#List) of #GameObject -- @type ObjectList --- @extends #list<#GameObject> +-- @list <#GameObject> ------------------------------------------------------------------------------- -- Filter list with a Query. diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index d4f305b96e..478b9235a5 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -64,12 +64,16 @@ -- @field #Content content Optional @{openmw.ui#Content} of children layouts --- --- Layers +-- Layers. Implements [iterables#List](iterables.html#List) of #string. -- @type Layers +-- @list <#string> -- @usage -- ui.layers.insertAfter('HUD', 'NewLayer', { interactive = true }) -- local fourthLayerName = ui.layers[4] -- local windowsIndex = ui.layers.indexOf('Windows') +-- for i, name in ipairs(ui.layers) do +-- print('layer', i, name) +-- end --- -- Index of the layer with the givent name. Returns nil if the layer doesn't exist @@ -85,7 +89,8 @@ -- @param #table options Table with a boolean `interactive` field (default is true). Layers with interactive = false will ignore all mouse interactions. --- --- Content. An array-like container, which allows to reference elements by their name +-- Content. An array-like container, which allows to reference elements by their name. +-- Implements [iterables#List](iterables.html#List) of #Layout and [iterables#Map](iterables.html#Map) of #string to #Layout. -- @type Content -- @list <#Layout> -- @usage From 56b9e29093679abbf089087a4486693306b09ff0 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 19 Feb 2022 01:06:04 +0100 Subject: [PATCH 2150/2859] Provide unit tests reports and coverage from CI Use https://docs.gitlab.com/ee/ci/unit_test_reports.html#googletest for unit tests reports. Use https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html#cc-example for coverage reports. --- .gitlab-ci.yml | 63 ++++++++++++++++++++++++++++++++------- CI/before_script.linux.sh | 6 ++++ CI/install_debian_deps.sh | 2 ++ 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4c5223c212..8799ac660a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,8 +35,8 @@ variables: - cd build - cmake --build . -- -j $(nproc) - cmake --install . - - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite; fi - - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi + - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite --gtest_output="xml:tests.xml"; fi + - if [[ "${BUILD_TESTS_ONLY}" && ! "${BUILD_WITH_CODE_COVERAGE}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi - ccache -s artifacts: paths: @@ -111,7 +111,10 @@ Ubuntu_GCC_tests: BUILD_TESTS_ONLY: 1 artifacts: paths: [] - expire_in: 1 minute + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/tests.xml Ubuntu_GCC_tests_Debug: extends: Ubuntu_GCC @@ -124,7 +127,10 @@ Ubuntu_GCC_tests_Debug: CMAKE_CXX_FLAGS_DEBUG: -g -O0 -D_GLIBCXX_ASSERTIONS artifacts: paths: [] - expire_in: 1 minute + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/tests.xml Ubuntu_GCC_tests_asan: extends: Ubuntu_GCC @@ -139,7 +145,10 @@ Ubuntu_GCC_tests_asan: ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 artifacts: paths: [] - expire_in: 1 minute + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/tests.xml Ubuntu_GCC_tests_ubsan: extends: Ubuntu_GCC @@ -153,7 +162,10 @@ Ubuntu_GCC_tests_ubsan: UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 artifacts: paths: [] - expire_in: 1 minute + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/tests.xml Ubuntu_GCC_tests_tsan: extends: Ubuntu_GCC @@ -168,7 +180,29 @@ Ubuntu_GCC_tests_tsan: TSAN_OPTIONS: second_deadlock_stack=1:halt_on_error=1 artifacts: paths: [] - expire_in: 1 minute + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/tests.xml + +Ubuntu_GCC_tests_coverage: + extends: Ubuntu_GCC_tests_Debug + cache: + key: Ubuntu_GCC_tests_coverage.v1 + variables: + BUILD_WITH_CODE_COVERAGE: 1 + before_script: + - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic openmw-coverage + after_script: + - gcovr --xml-pretty --exclude-unreachable-branches --print-summary --root ${CI_PROJECT_DIR} -j $(nproc) -o coverage.xml + coverage: /^\s*lines:\s*\d+.\d+\%/ + artifacts: + paths: [] + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + cobertura: coverage.xml + junit: build/tests.xml Ubuntu_Static_Deps: extends: Ubuntu_Clang @@ -206,7 +240,10 @@ Ubuntu_Static_Deps_tests: CXXFLAGS: -O0 artifacts: paths: [] - expire_in: 1 minute + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/tests.xml Ubuntu_Clang: extends: .Ubuntu @@ -230,7 +267,10 @@ Ubuntu_Clang_tests: BUILD_TESTS_ONLY: 1 artifacts: paths: [] - expire_in: 1 minute + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/tests.xml Ubuntu_Clang_tests_Debug: extends: Ubuntu_Clang @@ -242,7 +282,10 @@ Ubuntu_Clang_tests_Debug: CMAKE_BUILD_TYPE: Debug artifacts: paths: [] - expire_in: 1 minute + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + when: always + reports: + junit: build/tests.xml .MacOS: image: macos-11-xcode-12 diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 386b25bbe6..41d271fc70 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -64,6 +64,12 @@ if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then ) fi +if [[ "${BUILD_WITH_CODE_COVERAGE}" ]]; then + CMAKE_CONF_OPTS+=( + -DBUILD_WITH_CODE_COVERAGE="${BUILD_WITH_CODE_COVERAGE}" + ) +fi + mkdir -p build cd build diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 74b0746ac0..93f701d011 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -40,6 +40,8 @@ declare -rA GROUPED_DEPS=( libcollada-dom-dev libfreetype6-dev libjpeg-dev libpng-dev libsdl2-dev libboost-system-dev libboost-filesystem-dev libgl-dev " + + [openmw-coverage]="gcovr" ) if [[ $# -eq 0 ]]; then From de3092f014b75dcf800113944703b7a3fbfc4be5 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Tue, 22 Feb 2022 04:04:08 +0000 Subject: [PATCH 2151/2859] Revert "Merge branch 'offset_the_deads' into 'master'" This reverts commit 7dd02076f5a05528d0df308bf68e6bd733bd1b43 --- apps/openmw/mwphysics/actor.hpp | 6 +++--- apps/openmw/mwphysics/movementsolver.cpp | 4 ++++ apps/openmw/mwphysics/physicssystem.cpp | 3 +-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index cdf49eeb54..01d8037f6b 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -158,9 +158,6 @@ namespace MWPhysics bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const; - /// Returns the mesh translation, scaled and rotated as necessary - osg::Vec3f getScaledMeshTranslation() const; - private: MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world @@ -168,6 +165,9 @@ namespace MWPhysics void addCollisionMask(int collisionMask); int getCollisionMask() const; + /// Returns the mesh translation, scaled and rotated as necessary + osg::Vec3f getScaledMeshTranslation() const; + bool mCanWaterWalk; bool mWalkingOnWater; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 8987c8d8a3..fd8a7f2b05 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -134,6 +134,8 @@ namespace MWPhysics // Adjust for collision mesh offset relative to actor's "location" // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own) + // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation + // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() actor.mPosition.z() += actor.mHalfExtentsZ; // vanilla-accurate float swimlevel = actor.mSwimLevel + actor.mHalfExtentsZ; @@ -463,6 +465,8 @@ namespace MWPhysics } } + // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) + // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, actor.mHalfExtentsZ); // use a 3d approximation of the movement vector to better judge player intent diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f5f22529ff..bbd3447b6a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -939,8 +939,7 @@ namespace MWPhysics , mRotation() , mMovement(actor.velocity()) , mWaterlevel(waterlevel) - // for compatibility with vanilla assets, mesh offset is the actor halfextent for dead actors - , mHalfExtentsZ(actor.getPtr().getClass().getCreatureStats(actor.getPtr()).isDead() ? actor.getHalfExtents().z() : actor.getScaledMeshTranslation().z()) + , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) , mStuckFrames(0) , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) From f516e34688b08b4d18874884f2a9ffdea65d1c4d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 19 Feb 2022 01:54:34 +0100 Subject: [PATCH 2152/2859] Remove used-defined constructors from ESM4 types To avoid explicit initialization. It should happen in the load functions anyway. * Move all non-zero initializations to corresponding class definition. * Replace std::vector by std::array when it has constant size. --- components/esm4/acti.hpp | 1 - components/esm4/lighting.hpp | 3 ++- components/esm4/loadachr.cpp | 10 ---------- components/esm4/loadachr.hpp | 3 +-- components/esm4/loadacre.cpp | 9 --------- components/esm4/loadacre.hpp | 3 +-- components/esm4/loadacti.cpp | 10 ---------- components/esm4/loadalch.cpp | 13 ------------- components/esm4/loadaloc.cpp | 9 --------- components/esm4/loadammo.cpp | 10 ---------- components/esm4/loadammo.hpp | 1 - components/esm4/loadanio.cpp | 7 ------- components/esm4/loadanio.hpp | 1 - components/esm4/loadappa.cpp | 14 -------------- components/esm4/loadappa.hpp | 1 - components/esm4/loadarma.cpp | 9 --------- components/esm4/loadarma.hpp | 1 - components/esm4/loadarmo.cpp | 22 ---------------------- components/esm4/loadarmo.hpp | 1 - components/esm4/loadaspc.cpp | 6 ------ components/esm4/loadaspc.hpp | 1 - components/esm4/loadbook.cpp | 16 ---------------- components/esm4/loadbook.hpp | 1 - components/esm4/loadbptd.cpp | 7 ------- components/esm4/loadbptd.hpp | 1 - components/esm4/loadcell.cpp | 22 ---------------------- components/esm4/loadcell.hpp | 1 - components/esm4/loadclas.cpp | 8 -------- components/esm4/loadclas.hpp | 1 - components/esm4/loadclfm.cpp | 11 ----------- components/esm4/loadclfm.hpp | 1 - components/esm4/loadclot.cpp | 16 ---------------- components/esm4/loadclot.hpp | 1 - components/esm4/loadcont.cpp | 8 -------- components/esm4/loadcont.hpp | 1 - components/esm4/loadcrea.cpp | 22 ---------------------- components/esm4/loadcrea.hpp | 1 - components/esm4/loaddial.cpp | 9 --------- components/esm4/loaddial.hpp | 1 - components/esm4/loaddobj.cpp | 7 ------- components/esm4/loaddobj.hpp | 1 - components/esm4/loaddoor.cpp | 8 -------- components/esm4/loaddoor.hpp | 1 - components/esm4/loadeyes.cpp | 9 --------- components/esm4/loadeyes.hpp | 1 - components/esm4/loadflor.cpp | 8 -------- components/esm4/loadflor.hpp | 1 - components/esm4/loadflst.cpp | 5 ----- components/esm4/loadflst.hpp | 1 - components/esm4/loadfurn.cpp | 8 -------- components/esm4/loadfurn.hpp | 1 - components/esm4/loadglob.cpp | 5 ----- components/esm4/loadglob.hpp | 1 - components/esm4/loadgras.cpp | 6 ------ components/esm4/loadgras.hpp | 1 - components/esm4/loadhair.cpp | 10 ---------- components/esm4/loadhair.hpp | 1 - components/esm4/loadhdpt.cpp | 9 --------- components/esm4/loadhdpt.hpp | 4 ++-- components/esm4/loadidle.cpp | 7 ------- components/esm4/loadidle.hpp | 1 - components/esm4/loadidlm.cpp | 5 ----- components/esm4/loadidlm.hpp | 1 - components/esm4/loadimod.cpp | 5 ----- components/esm4/loadimod.hpp | 1 - components/esm4/loadinfo.cpp | 15 --------------- components/esm4/loadinfo.hpp | 1 - components/esm4/loadingr.cpp | 15 --------------- components/esm4/loadingr.hpp | 1 - components/esm4/loadkeym.cpp | 12 ------------ components/esm4/loadkeym.hpp | 1 - components/esm4/loadland.cpp | 11 ----------- components/esm4/loadland.hpp | 1 - components/esm4/loadlgtm.cpp | 16 ---------------- components/esm4/loadlgtm.hpp | 1 - components/esm4/loadligh.cpp | 9 --------- components/esm4/loadligh.hpp | 11 +++-------- components/esm4/loadltex.cpp | 8 -------- components/esm4/loadltex.hpp | 1 - components/esm4/loadlvlc.cpp | 6 ------ components/esm4/loadlvlc.hpp | 1 - components/esm4/loadlvli.cpp | 6 ------ components/esm4/loadlvli.hpp | 1 - components/esm4/loadlvln.cpp | 6 ------ components/esm4/loadlvln.hpp | 1 - components/esm4/loadmato.cpp | 6 ------ components/esm4/loadmato.hpp | 1 - components/esm4/loadmisc.cpp | 12 ------------ components/esm4/loadmisc.hpp | 1 - components/esm4/loadmset.cpp | 18 ------------------ components/esm4/loadmset.hpp | 3 +-- components/esm4/loadmstt.cpp | 6 ------ components/esm4/loadmusc.cpp | 6 ------ components/esm4/loadmusc.hpp | 1 - components/esm4/loadnavi.cpp | 5 ----- components/esm4/loadnavi.hpp | 1 - components/esm4/loadnavm.cpp | 4 ---- components/esm4/loadnavm.hpp | 1 - components/esm4/loadnote.cpp | 8 -------- components/esm4/loadnote.hpp | 1 - components/esm4/loadnpc.cpp | 21 --------------------- components/esm4/loadnpc.hpp | 1 - components/esm4/loadotft.cpp | 5 ----- components/esm4/loadotft.hpp | 1 - components/esm4/loadpack.cpp | 14 -------------- components/esm4/loadpack.hpp | 4 ++-- components/esm4/loadpgrd.cpp | 10 ---------- components/esm4/loadpgre.cpp | 5 ----- components/esm4/loadpwat.cpp | 5 ----- components/esm4/loadrace.cpp | 22 ---------------------- components/esm4/loadrace.hpp | 14 +++++++------- components/esm4/loadrefr.cpp | 23 ----------------------- components/esm4/loadrefr.hpp | 7 +++---- components/esm4/loadregn.cpp | 7 ------- components/esm4/loadregn.hpp | 1 - components/esm4/loadroad.cpp | 8 -------- components/esm4/loadroad.hpp | 1 - components/esm4/loadsbsp.cpp | 9 --------- components/esm4/loadsbsp.hpp | 1 - components/esm4/loadscol.cpp | 5 ----- components/esm4/loadscol.hpp | 1 - components/esm4/loadscpt.cpp | 5 ----- components/esm4/loadscpt.hpp | 1 - components/esm4/loadscrl.cpp | 11 ----------- components/esm4/loadscrl.hpp | 1 - components/esm4/loadsgst.cpp | 14 -------------- components/esm4/loadsgst.hpp | 1 - components/esm4/loadslgm.cpp | 11 ----------- components/esm4/loadslgm.hpp | 1 - components/esm4/loadsndr.cpp | 6 ------ components/esm4/loadsndr.hpp | 1 - components/esm4/loadsoun.cpp | 6 ------ components/esm4/loadsoun.hpp | 1 - components/esm4/loadstat.cpp | 6 ------ components/esm4/loadstat.hpp | 1 - components/esm4/loadtact.cpp | 8 -------- components/esm4/loadtact.hpp | 1 - components/esm4/loadterm.cpp | 10 ---------- components/esm4/loadterm.hpp | 1 - components/esm4/loadtree.cpp | 7 ------- components/esm4/loadtree.hpp | 1 - components/esm4/loadtxst.cpp | 13 ------------- components/esm4/loadtxst.hpp | 1 - components/esm4/loadweap.cpp | 11 ----------- components/esm4/loadweap.hpp | 1 - components/esm4/loadwrld.cpp | 19 ------------------- components/esm4/loadwrld.hpp | 1 - 147 files changed, 22 insertions(+), 852 deletions(-) diff --git a/components/esm4/acti.hpp b/components/esm4/acti.hpp index 4ec3fe1e5e..f86a4aa370 100644 --- a/components/esm4/acti.hpp +++ b/components/esm4/acti.hpp @@ -57,7 +57,6 @@ namespace ESM4 std::string mActivationPrompt; - Activator(); virtual ~Activator(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/lighting.hpp b/components/esm4/lighting.hpp index 42e76eceee..a139ae630e 100644 --- a/components/esm4/lighting.hpp +++ b/components/esm4/lighting.hpp @@ -28,6 +28,7 @@ #define ESM4_LIGHTING_H #include +#include namespace ESM4 { @@ -44,7 +45,7 @@ namespace ESM4 std::int32_t rotationZ; // rotation z | 00 00 00 00 = 0 float fogDirFade; // Fog dir fade | 00 00 80 3F = 1.f float fogClipDist; // Fog clip dist | 00 80 3B 45 = 3000.f - float fogPower; + float fogPower = std::numeric_limits::max(); }; struct Lighting_TES5 diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp index fe03fcb59e..e006588475 100644 --- a/components/esm4/loadachr.cpp +++ b/components/esm4/loadachr.cpp @@ -32,16 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::ActorCharacter::ActorCharacter() : mFormId(0), mFlags(0), mBaseObj(0), - mScale(1.f), mOwner(0), mGlobal(0), mInitiallyDisabled(false) -{ - mEditorId.clear(); - mFullName.clear(); - - mEsp.parent = 0; - mEsp.flags = 0; -} - ESM4::ActorCharacter::~ActorCharacter() { } diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp index a8656ea926..7f78aa32d1 100644 --- a/components/esm4/loadachr.hpp +++ b/components/esm4/loadachr.hpp @@ -49,7 +49,7 @@ namespace ESM4 FormId mBaseObj; Placement mPlacement; - float mScale; // default 1.f + float mScale = 1.0f; FormId mOwner; FormId mGlobal; @@ -57,7 +57,6 @@ namespace ESM4 EnableParent mEsp; - ActorCharacter(); virtual ~ActorCharacter(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadacre.cpp b/components/esm4/loadacre.cpp index 3510986d3c..9eb0cc7e5f 100644 --- a/components/esm4/loadacre.cpp +++ b/components/esm4/loadacre.cpp @@ -32,15 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::ActorCreature::ActorCreature() : mFormId(0), mFlags(0), mBaseObj(0), mScale(1.f), - mOwner(0), mGlobal(0), mFactionRank(0), mInitiallyDisabled(false) -{ - mEditorId.clear(); - - mEsp.parent = 0; - mEsp.flags = 0; -} - ESM4::ActorCreature::~ActorCreature() { } diff --git a/components/esm4/loadacre.hpp b/components/esm4/loadacre.hpp index 26cab34a4e..0b436d762b 100644 --- a/components/esm4/loadacre.hpp +++ b/components/esm4/loadacre.hpp @@ -45,7 +45,7 @@ namespace ESM4 FormId mBaseObj; Placement mPlacement; - float mScale; // default 1.f + float mScale = 1.0f; FormId mOwner; FormId mGlobal; std::uint32_t mFactionRank; @@ -54,7 +54,6 @@ namespace ESM4 EnableParent mEsp; - ActorCreature(); virtual ~ActorCreature(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp index abbe901014..cdfd6972fc 100644 --- a/components/esm4/loadacti.cpp +++ b/components/esm4/loadacti.cpp @@ -32,16 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Activator::Activator() : mFormId(0), mFlags(0), mScriptId(0), mLoopingSound(0), mActivationSound(0), - mBoundRadius(0.f), mRadioTemplate(0), mRadioStation(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - - mActivationPrompt.clear(); -} - ESM4::Activator::~Activator() { } diff --git a/components/esm4/loadalch.cpp b/components/esm4/loadalch.cpp index b720b740ca..2c346d71b9 100644 --- a/components/esm4/loadalch.cpp +++ b/components/esm4/loadalch.cpp @@ -33,19 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Potion::Potion() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mScriptId(0), mBoundRadius(0.f) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); - mMiniIcon.clear(); - - mData.weight = 0.f; - - std::memset(&mEffect, 0, sizeof(ScriptEffect)); -} - ESM4::Potion::~Potion() { } diff --git a/components/esm4/loadaloc.cpp b/components/esm4/loadaloc.cpp index a84e56592d..9907e5908b 100644 --- a/components/esm4/loadaloc.cpp +++ b/components/esm4/loadaloc.cpp @@ -38,15 +38,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::MediaLocationController::MediaLocationController() : mFormId(0), mFlags(0), - mConditionalFaction(0), mLocationDelay(0.f), mRetriggerDelay(0.f), mDayStart(0), mNightStart(0) -{ - mEditorId.clear(); - mFullName.clear(); - - std::memset(&mMediaFlags, 0, sizeof(MLC_Flags)); -} - ESM4::MediaLocationController::~MediaLocationController() { } diff --git a/components/esm4/loadammo.cpp b/components/esm4/loadammo.cpp index 7ccf8e1582..3c30a7a73b 100644 --- a/components/esm4/loadammo.cpp +++ b/components/esm4/loadammo.cpp @@ -31,16 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Ammunition::Ammunition() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mText.clear(); - mIcon.clear(); - mMiniIcon.clear(); -} - ESM4::Ammunition::~Ammunition() { } diff --git a/components/esm4/loadammo.hpp b/components/esm4/loadammo.hpp index 8ff9cd9390..c31d38c10c 100644 --- a/components/esm4/loadammo.hpp +++ b/components/esm4/loadammo.hpp @@ -71,7 +71,6 @@ namespace ESM4 Data mData; - Ammunition(); virtual ~Ammunition(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadanio.cpp b/components/esm4/loadanio.cpp index b6b97a1a47..15847fbbda 100644 --- a/components/esm4/loadanio.cpp +++ b/components/esm4/loadanio.cpp @@ -31,13 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::AnimObject::AnimObject() : mFormId(0), mFlags(0), mBoundRadius(0.f), mIdleAnim(0) -{ - mEditorId.clear(); - mModel.clear(); - mUnloadEvent.clear(); -} - ESM4::AnimObject::~AnimObject() { } diff --git a/components/esm4/loadanio.hpp b/components/esm4/loadanio.hpp index 4259abe7b3..f7e85f613b 100644 --- a/components/esm4/loadanio.hpp +++ b/components/esm4/loadanio.hpp @@ -50,7 +50,6 @@ namespace ESM4 FormId mIdleAnim; // only in TES4 std::string mUnloadEvent; // only in TES5 - AnimObject(); virtual ~AnimObject(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadappa.cpp b/components/esm4/loadappa.cpp index 7ad7635927..c0561322b0 100644 --- a/components/esm4/loadappa.cpp +++ b/components/esm4/loadappa.cpp @@ -31,20 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Apparatus::Apparatus() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mText.clear(); - mIcon.clear(); - - mData.type = 0; - mData.value = 0; - mData.weight = 0.f; - mData.quality = 0.f; -} - ESM4::Apparatus::~Apparatus() { } diff --git a/components/esm4/loadappa.hpp b/components/esm4/loadappa.hpp index 1723c969ee..f3b2966518 100644 --- a/components/esm4/loadappa.hpp +++ b/components/esm4/loadappa.hpp @@ -62,7 +62,6 @@ namespace ESM4 Data mData; - Apparatus(); virtual ~Apparatus(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp index 8b29000bb3..6306845689 100644 --- a/components/esm4/loadarma.cpp +++ b/components/esm4/loadarma.cpp @@ -32,15 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::ArmorAddon::ArmorAddon() : mFormId(0), mFlags(0), mTextureMale(0), mTextureFemale(0), - mRacePrimary(0) -{ - mEditorId.clear(); - - mModelMale.clear(); - mModelFemale.clear(); -} - ESM4::ArmorAddon::~ArmorAddon() { } diff --git a/components/esm4/loadarma.hpp b/components/esm4/loadarma.hpp index 59c4550b7f..f7abb404a1 100644 --- a/components/esm4/loadarma.hpp +++ b/components/esm4/loadarma.hpp @@ -57,7 +57,6 @@ namespace ESM4 BodyTemplate mBodyTemplate; // TES5 - ArmorAddon(); virtual ~ArmorAddon(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadarmo.cpp b/components/esm4/loadarmo.cpp index bc0765c869..c6237f326d 100644 --- a/components/esm4/loadarmo.cpp +++ b/components/esm4/loadarmo.cpp @@ -32,28 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Armor::Armor() : mFormId(0), mFlags(0), mIsTES4(false), mIsFO3(false), mIsFONV(false), - mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), - mArmorFlags(0), mGeneralFlags(0), mScriptId(0), mEnchantmentPoints(0), mEnchantment(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModelMale.clear(); - mModelMaleWorld.clear(); - mModelFemale.clear(); - mModelFemaleWorld.clear(); - mText.clear(); - mIconMale.clear(); - mMiniIconMale.clear(); - mIconFemale.clear(); - mMiniIconFemale.clear(); - - mData.armor = 0; - mData.value = 0; - mData.health = 0; - mData.weight = 0.f; -} - ESM4::Armor::~Armor() { } diff --git a/components/esm4/loadarmo.hpp b/components/esm4/loadarmo.hpp index f17521f507..b44ba1a973 100644 --- a/components/esm4/loadarmo.hpp +++ b/components/esm4/loadarmo.hpp @@ -183,7 +183,6 @@ namespace ESM4 std::vector mAddOns; // TES5 ARMA Data mData; - Armor(); virtual ~Armor(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadaspc.cpp b/components/esm4/loadaspc.cpp index a4cb6f3939..8fb9d70c71 100644 --- a/components/esm4/loadaspc.cpp +++ b/components/esm4/loadaspc.cpp @@ -32,12 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::AcousticSpace::AcousticSpace() : mFormId(0), mFlags(0), mEnvironmentType(0), mSoundRegion(0), - mIsInterior(0) -{ - mEditorId.clear(); -} - ESM4::AcousticSpace::~AcousticSpace() { } diff --git a/components/esm4/loadaspc.hpp b/components/esm4/loadaspc.hpp index 3ab1a8484d..179297a604 100644 --- a/components/esm4/loadaspc.hpp +++ b/components/esm4/loadaspc.hpp @@ -53,7 +53,6 @@ namespace ESM4 std::uint32_t mIsInterior; // if true only use mAmbientLoopSounds[0] - AcousticSpace(); virtual ~AcousticSpace(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadbook.cpp b/components/esm4/loadbook.cpp index 0ffc69ef0e..939888fc43 100644 --- a/components/esm4/loadbook.cpp +++ b/components/esm4/loadbook.cpp @@ -31,22 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Book::Book() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), - mEnchantmentPoints(0), mEnchantment(0), mPickUpSound(0), mDropSound(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mText.clear(); - mIcon.clear(); - - mData.flags = 0; - mData.type = 0; - mData.bookSkill = 0; - mData.value = 0; - mData.weight = 0.f; -} - ESM4::Book::~Book() { } diff --git a/components/esm4/loadbook.hpp b/components/esm4/loadbook.hpp index c5c1a3529f..9cb5358bdb 100644 --- a/components/esm4/loadbook.hpp +++ b/components/esm4/loadbook.hpp @@ -101,7 +101,6 @@ namespace ESM4 FormId mPickUpSound; FormId mDropSound; - Book(); virtual ~Book(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadbptd.cpp b/components/esm4/loadbptd.cpp index 6c6c1bc26c..a65d2dd482 100644 --- a/components/esm4/loadbptd.cpp +++ b/components/esm4/loadbptd.cpp @@ -43,13 +43,6 @@ void ESM4::BodyPartData::BodyPart::clear() mGoreEffectsTarget.clear(); } -ESM4::BodyPartData::BodyPartData() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); -} - ESM4::BodyPartData::~BodyPartData() { } diff --git a/components/esm4/loadbptd.hpp b/components/esm4/loadbptd.hpp index 0a29cd22cb..0b971ca083 100644 --- a/components/esm4/loadbptd.hpp +++ b/components/esm4/loadbptd.hpp @@ -115,7 +115,6 @@ namespace ESM4 std::vector mBodyParts; - BodyPartData(); virtual ~BodyPartData(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index eb1f864b92..0b90a7cdbf 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -39,28 +39,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Cell::Cell() : mParent(0), mFormId(0), mFlags(0), mCellFlags(0), mX(0), mY(0), mOwner(0), - mGlobal(0), mClimate(0), mWater(0), mWaterHeight(0.f), - mLightingTemplate(0), mLightingTemplateFlags(0), mMusic(0), mAcousticSpace(0), - mMusicType(0), mPreloaded(false) -{ - mEditorId.clear(); - mFullName.clear(); - - mLighting.ambient = 0; - mLighting.directional = 0; - mLighting.fogColor = 0; - mLighting.fogNear = 0.f; - mLighting.fogFar = 0.f; - mLighting.rotationXY = 0; - mLighting.rotationZ = 0; - mLighting.fogDirFade = 0.f; - mLighting.fogClipDist = 0.f; - mLighting.fogPower = FLT_MAX; // hack way to detect TES4 - - mRegions.clear(); -} - ESM4::Cell::~Cell() { } diff --git a/components/esm4/loadcell.hpp b/components/esm4/loadcell.hpp index 70dc94e73a..c2274fcbe9 100644 --- a/components/esm4/loadcell.hpp +++ b/components/esm4/loadcell.hpp @@ -91,7 +91,6 @@ namespace ESM4 CellGroup *mCellGroup; - Cell(); virtual ~Cell(); void init(ESM4::Reader& reader); // common setup for both preload() and load() diff --git a/components/esm4/loadclas.cpp b/components/esm4/loadclas.cpp index 2948976709..80c9fc2b0f 100644 --- a/components/esm4/loadclas.cpp +++ b/components/esm4/loadclas.cpp @@ -36,14 +36,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Class::Class() -{ - mEditorId.clear(); - mFullName.clear(); - mDesc.clear(); - mIcon.clear(); -} - ESM4::Class::~Class() { } diff --git a/components/esm4/loadclas.hpp b/components/esm4/loadclas.hpp index 83e8372f4a..c68a1c1ae5 100644 --- a/components/esm4/loadclas.hpp +++ b/components/esm4/loadclas.hpp @@ -53,7 +53,6 @@ namespace ESM4 std::string mIcon; Data mData; - Class(); ~Class(); void load(ESM4::Reader& reader); diff --git a/components/esm4/loadclfm.cpp b/components/esm4/loadclfm.cpp index b926cb93fd..7ad9f56615 100644 --- a/components/esm4/loadclfm.cpp +++ b/components/esm4/loadclfm.cpp @@ -32,17 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Colour::Colour() : mFormId(0), mFlags(0), mPlayable(0) -{ - mEditorId.clear(); - mFullName.clear(); - - mColour.red = 0; - mColour.green = 0; - mColour.blue = 0; - mColour.custom = 0; -} - ESM4::Colour::~Colour() { } diff --git a/components/esm4/loadclfm.hpp b/components/esm4/loadclfm.hpp index 2a18e42cd6..35d4a28686 100644 --- a/components/esm4/loadclfm.hpp +++ b/components/esm4/loadclfm.hpp @@ -57,7 +57,6 @@ namespace ESM4 ColourRGB mColour; std::uint32_t mPlayable; - Colour(); virtual ~Colour(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadclot.cpp b/components/esm4/loadclot.cpp index da6473c393..8f43cf2ac6 100644 --- a/components/esm4/loadclot.cpp +++ b/components/esm4/loadclot.cpp @@ -32,22 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Clothing::Clothing() : mFormId(0), mFlags(0), mBoundRadius(0.f), mClothingFlags(0), - mScriptId(0), mEnchantmentPoints(0), mEnchantment(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModelMale.clear(); - mModelMaleWorld.clear(); - mModelFemale.clear(); - mModelFemaleWorld.clear(); - mIconMale.clear(); - mIconFemale.clear(); - - mData.value = 0; - mData.weight = 0.f; -} - ESM4::Clothing::~Clothing() { } diff --git a/components/esm4/loadclot.hpp b/components/esm4/loadclot.hpp index 06e019137b..8870c58a45 100644 --- a/components/esm4/loadclot.hpp +++ b/components/esm4/loadclot.hpp @@ -70,7 +70,6 @@ namespace ESM4 Data mData; - Clothing(); virtual ~Clothing(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp index 06366c88cc..6ddb6c17f9 100644 --- a/components/esm4/loadcont.cpp +++ b/components/esm4/loadcont.cpp @@ -31,14 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Container::Container() : mFormId(0), mFlags(0), mBoundRadius(0.f), mDataFlags(0), mWeight(0.f), - mOpenSound(0), mCloseSound(0), mScriptId(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); -} - ESM4::Container::~Container() { } diff --git a/components/esm4/loadcont.hpp b/components/esm4/loadcont.hpp index e059d9a361..7218da9729 100644 --- a/components/esm4/loadcont.hpp +++ b/components/esm4/loadcont.hpp @@ -58,7 +58,6 @@ namespace ESM4 std::vector mInventory; - Container(); virtual ~Container(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp index 5fdd414256..a78aadd82d 100644 --- a/components/esm4/loadcrea.cpp +++ b/components/esm4/loadcrea.cpp @@ -40,28 +40,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Creature::Creature() : mFormId(0), mFlags(0), mDeathItem(0), mScriptId(0), mCombatStyle(0), - mSoundBase(0), mSound(0), mSoundChance(0), mBaseScale(0.f), - mTurningSpeed(0.f), mFootWeight(0.f), mBoundRadius(0.f) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - - mBloodSpray.clear(); - mBloodDecal.clear(); - - mAIData.aggression = 0; - mAIData.confidence = 0; - mAIData.energyLevel = 0; - mAIData.responsibility = 0; - mAIData.aiFlags = 0; - mAIData.trainSkill = 0; - mAIData.trainLevel = 0; - - std::memset(&mData, 0, sizeof(Data)); -} - ESM4::Creature::~Creature() { } diff --git a/components/esm4/loadcrea.hpp b/components/esm4/loadcrea.hpp index c2889d1713..8062a670d0 100644 --- a/components/esm4/loadcrea.hpp +++ b/components/esm4/loadcrea.hpp @@ -138,7 +138,6 @@ namespace ESM4 FormId mBaseTemplate; // FO3/FONV std::vector mBodyParts; // FO3/FONV - Creature(); virtual ~Creature(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loaddial.cpp b/components/esm4/loaddial.cpp index 716da441d5..28176e7c52 100644 --- a/components/esm4/loaddial.cpp +++ b/components/esm4/loaddial.cpp @@ -33,15 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Dialogue::Dialogue() : mFormId(0), mFlags(0), mDoAllBeforeRepeat(false), - mDialType(0), mDialFlags(0), mPriority(0.f) -{ - mEditorId.clear(); - mTopicName.clear(); - - mTextDumb.clear(); // FIXME: temp name -} - ESM4::Dialogue::~Dialogue() { } diff --git a/components/esm4/loaddial.hpp b/components/esm4/loaddial.hpp index ef44c841fc..6463da4f19 100644 --- a/components/esm4/loaddial.hpp +++ b/components/esm4/loaddial.hpp @@ -57,7 +57,6 @@ namespace ESM4 float mPriority; - Dialogue(); virtual ~Dialogue(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loaddobj.cpp b/components/esm4/loaddobj.cpp index 307674a5db..b81e29c9fc 100644 --- a/components/esm4/loaddobj.cpp +++ b/components/esm4/loaddobj.cpp @@ -37,13 +37,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::DefaultObj::DefaultObj() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - - std::memset(&mData, 0, sizeof(Defaults)); -} - ESM4::DefaultObj::~DefaultObj() { } diff --git a/components/esm4/loaddobj.hpp b/components/esm4/loaddobj.hpp index 6d708fcbbc..b9557bbb05 100644 --- a/components/esm4/loaddobj.hpp +++ b/components/esm4/loaddobj.hpp @@ -87,7 +87,6 @@ namespace ESM4 Defaults mData; - DefaultObj(); virtual ~DefaultObj(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loaddoor.cpp b/components/esm4/loaddoor.cpp index 45ac1f0b6c..7af92fa870 100644 --- a/components/esm4/loaddoor.cpp +++ b/components/esm4/loaddoor.cpp @@ -31,14 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Door::Door() : mFormId(0), mFlags(0), mBoundRadius(0.f), mDoorFlags(0), mScriptId(0), - mOpenSound(0), mCloseSound(0), mLoopSound(0), mRandomTeleport(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); -} - ESM4::Door::~Door() { } diff --git a/components/esm4/loaddoor.hpp b/components/esm4/loaddoor.hpp index 0a332339d5..71e7ae70ac 100644 --- a/components/esm4/loaddoor.hpp +++ b/components/esm4/loaddoor.hpp @@ -63,7 +63,6 @@ namespace ESM4 FormId mLoopSound; FormId mRandomTeleport; - Door(); virtual ~Door(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadeyes.cpp b/components/esm4/loadeyes.cpp index 059bee6c37..d4f4fc4bbb 100644 --- a/components/esm4/loadeyes.cpp +++ b/components/esm4/loadeyes.cpp @@ -31,15 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Eyes::Eyes() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - mFullName.clear(); - mIcon.clear(); - - mData.flags = 0; -} - ESM4::Eyes::~Eyes() { } diff --git a/components/esm4/loadeyes.hpp b/components/esm4/loadeyes.hpp index 814e2a9647..dfc5d5e983 100644 --- a/components/esm4/loadeyes.hpp +++ b/components/esm4/loadeyes.hpp @@ -55,7 +55,6 @@ namespace ESM4 Data mData; - Eyes(); virtual ~Eyes(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadflor.cpp b/components/esm4/loadflor.cpp index fab6c3f0ed..fcc7fa05c1 100644 --- a/components/esm4/loadflor.cpp +++ b/components/esm4/loadflor.cpp @@ -31,14 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Flora::Flora() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mIngredient(0), - mSound(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); -} - ESM4::Flora::~Flora() { } diff --git a/components/esm4/loadflor.hpp b/components/esm4/loadflor.hpp index 2f9e22d548..f4961adf9c 100644 --- a/components/esm4/loadflor.hpp +++ b/components/esm4/loadflor.hpp @@ -65,7 +65,6 @@ namespace ESM4 FormId mSound; Production mPercentHarvest; - Flora(); virtual ~Flora(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadflst.cpp b/components/esm4/loadflst.cpp index 32b23ff284..f73f883148 100644 --- a/components/esm4/loadflst.cpp +++ b/components/esm4/loadflst.cpp @@ -32,11 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::FormIdList::FormIdList() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); -} - ESM4::FormIdList::~FormIdList() { } diff --git a/components/esm4/loadflst.hpp b/components/esm4/loadflst.hpp index 6fd381aa44..c7e098b9ba 100644 --- a/components/esm4/loadflst.hpp +++ b/components/esm4/loadflst.hpp @@ -47,7 +47,6 @@ namespace ESM4 std::vector mObjects; - FormIdList(); virtual ~FormIdList(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp index 6fba8bc3ce..046f105451 100644 --- a/components/esm4/loadfurn.cpp +++ b/components/esm4/loadfurn.cpp @@ -31,14 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Furniture::Furniture() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), - mActiveMarkerFlags(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); -} - ESM4::Furniture::~Furniture() { } diff --git a/components/esm4/loadfurn.hpp b/components/esm4/loadfurn.hpp index 89da61ebf9..c04f78f462 100644 --- a/components/esm4/loadfurn.hpp +++ b/components/esm4/loadfurn.hpp @@ -51,7 +51,6 @@ namespace ESM4 FormId mScriptId; std::uint32_t mActiveMarkerFlags; - Furniture(); virtual ~Furniture(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp index 6a2fa8b00a..3435726d76 100644 --- a/components/esm4/loadglob.cpp +++ b/components/esm4/loadglob.cpp @@ -32,11 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::GlobalVariable::GlobalVariable() : mFormId(0), mFlags(0), mType(0), mValue(0.f) -{ - mEditorId.clear(); -} - ESM4::GlobalVariable::~GlobalVariable() { } diff --git a/components/esm4/loadglob.hpp b/components/esm4/loadglob.hpp index 15cf4a07ac..59e2c1fbd0 100644 --- a/components/esm4/loadglob.hpp +++ b/components/esm4/loadglob.hpp @@ -47,7 +47,6 @@ namespace ESM4 std::uint8_t mType; float mValue; - GlobalVariable(); virtual ~GlobalVariable(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadgras.cpp b/components/esm4/loadgras.cpp index 3818654fc1..81be002bc9 100644 --- a/components/esm4/loadgras.cpp +++ b/components/esm4/loadgras.cpp @@ -31,12 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Grass::Grass() : mFormId(0), mFlags(0), mBoundRadius(0.f) -{ - mEditorId.clear(); - mModel.clear(); -} - ESM4::Grass::~Grass() { } diff --git a/components/esm4/loadgras.hpp b/components/esm4/loadgras.hpp index d7754d03d9..f142779637 100644 --- a/components/esm4/loadgras.hpp +++ b/components/esm4/loadgras.hpp @@ -85,7 +85,6 @@ namespace ESM4 Data mData; - Grass(); virtual ~Grass(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadhair.cpp b/components/esm4/loadhair.cpp index 661c333cbd..f558a46521 100644 --- a/components/esm4/loadhair.cpp +++ b/components/esm4/loadhair.cpp @@ -32,16 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Hair::Hair() : mFormId(0), mFlags(0), mBoundRadius(0.f) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); - - mData.flags = 0; -} - ESM4::Hair::~Hair() { } diff --git a/components/esm4/loadhair.hpp b/components/esm4/loadhair.hpp index c9a4fc2756..22ae6ef605 100644 --- a/components/esm4/loadhair.hpp +++ b/components/esm4/loadhair.hpp @@ -58,7 +58,6 @@ namespace ESM4 Data mData; - Hair(); virtual ~Hair(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index f188890246..ce579f5371 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -32,15 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::HeadPart::HeadPart() : mFormId(0), mFlags(0), mData(0), mAdditionalPart(0), mBaseTexture(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - - mTriFile.resize(3); -} - ESM4::HeadPart::~HeadPart() { } diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp index d93411bb7e..9b67b0fce8 100644 --- a/components/esm4/loadhdpt.hpp +++ b/components/esm4/loadhdpt.hpp @@ -28,7 +28,7 @@ #define ESM4_HDPT_H #include -#include +#include #include "formid.hpp" @@ -50,7 +50,7 @@ namespace ESM4 FormId mAdditionalPart; - std::vector mTriFile; + std::array mTriFile; FormId mBaseTexture; HeadPart(); diff --git a/components/esm4/loadidle.cpp b/components/esm4/loadidle.cpp index 3d45b76e82..16bcfcf9dc 100644 --- a/components/esm4/loadidle.cpp +++ b/components/esm4/loadidle.cpp @@ -31,13 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::IdleAnimation::IdleAnimation() : mFormId(0), mFlags(0), mParent(0), mPrevious(0) -{ - mEditorId.clear(); - mCollision.clear(); - mEvent.clear(); -} - ESM4::IdleAnimation::~IdleAnimation() { } diff --git a/components/esm4/loadidle.hpp b/components/esm4/loadidle.hpp index cadd056122..d41d715906 100644 --- a/components/esm4/loadidle.hpp +++ b/components/esm4/loadidle.hpp @@ -49,7 +49,6 @@ namespace ESM4 FormId mParent; // IDLE or AACT FormId mPrevious; - IdleAnimation(); virtual ~IdleAnimation(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadidlm.cpp b/components/esm4/loadidlm.cpp index ba823df300..0aada6a3a3 100644 --- a/components/esm4/loadidlm.cpp +++ b/components/esm4/loadidlm.cpp @@ -32,11 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::IdleMarker::IdleMarker() : mFormId(0), mFlags(0), mIdleFlags(0), mIdleCount(0), mIdleTimer(0.f), mIdleAnim(0) -{ - mEditorId.clear(); -} - ESM4::IdleMarker::~IdleMarker() { } diff --git a/components/esm4/loadidlm.hpp b/components/esm4/loadidlm.hpp index 78d03a7024..717aade9c3 100644 --- a/components/esm4/loadidlm.hpp +++ b/components/esm4/loadidlm.hpp @@ -50,7 +50,6 @@ namespace ESM4 float mIdleTimer; std::vector mIdleAnim; - IdleMarker(); virtual ~IdleMarker(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp index 9950c59b7b..7be99fbae0 100644 --- a/components/esm4/loadimod.cpp +++ b/components/esm4/loadimod.cpp @@ -34,11 +34,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::ItemMod::ItemMod() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); -} - ESM4::ItemMod::~ItemMod() { } diff --git a/components/esm4/loadimod.hpp b/components/esm4/loadimod.hpp index a72fbe472d..2fee4f3156 100644 --- a/components/esm4/loadimod.hpp +++ b/components/esm4/loadimod.hpp @@ -46,7 +46,6 @@ namespace ESM4 std::string mEditorId; - ItemMod(); virtual ~ItemMod(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index a68774112b..d72051e8a7 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -33,21 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::DialogInfo::DialogInfo() : mFormId(0), mFlags(0), mQuest(0), mSound(0), - mDialType(0), mNextSpeaker(0), mInfoFlags(0), mParam3(0) -{ - std::memset(&mResponseData, 0, sizeof(TargetResponseData)); - mResponse.clear(); - mNotes.clear(); - mEdits.clear(); - - std::memset(&mTargetCondition, 0, sizeof(TargetCondition)); - - std::memset(&mScript.scriptHeader, 0, sizeof(ScriptHeader)); - mScript.scriptSource.clear(); - mScript.globReference = 0; -} - ESM4::DialogInfo::~DialogInfo() { } diff --git a/components/esm4/loadinfo.hpp b/components/esm4/loadinfo.hpp index be7090cd32..be7e77efc4 100644 --- a/components/esm4/loadinfo.hpp +++ b/components/esm4/loadinfo.hpp @@ -77,7 +77,6 @@ namespace ESM4 ScriptDefinition mScript; // FIXME: ignoring the second one after the NEXT sub-record - DialogInfo(); virtual ~DialogInfo(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadingr.cpp b/components/esm4/loadingr.cpp index 03994dbfb4..38f38463d0 100644 --- a/components/esm4/loadingr.cpp +++ b/components/esm4/loadingr.cpp @@ -32,21 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Ingredient::Ingredient() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); - - mData.value = 0; - mData.weight = 0.f; - mEnchantment.value = 0; - mEnchantment.flags = 0; - - std::memset(&mEffect, 0, sizeof(ScriptEffect)); -} - ESM4::Ingredient::~Ingredient() { } diff --git a/components/esm4/loadingr.hpp b/components/esm4/loadingr.hpp index 44312eb24b..c011ff54cf 100644 --- a/components/esm4/loadingr.hpp +++ b/components/esm4/loadingr.hpp @@ -70,7 +70,6 @@ namespace ESM4 Data mData; - Ingredient(); virtual ~Ingredient(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadkeym.cpp b/components/esm4/loadkeym.cpp index a5aba621af..85d348465f 100644 --- a/components/esm4/loadkeym.cpp +++ b/components/esm4/loadkeym.cpp @@ -31,18 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Key::Key() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); - mMiniIcon.clear(); - - mData.value = 0; - mData.weight = 0.f; -} - ESM4::Key::~Key() { } diff --git a/components/esm4/loadkeym.hpp b/components/esm4/loadkeym.hpp index d59b3826d5..6a31395618 100644 --- a/components/esm4/loadkeym.hpp +++ b/components/esm4/loadkeym.hpp @@ -64,7 +64,6 @@ namespace ESM4 Data mData; - Key(); virtual ~Key(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 2bb260da33..aadc2f3aaf 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -38,17 +38,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Land::Land() : mFormId(0), mFlags(0), mLandFlags(0), mDataTypes(0) -{ - for (int i = 0; i < 4; ++i) - { - mTextures[i].base.formId = 0; - mTextures[i].base.quadrant = 0; - mTextures[i].base.unknown1 = 0; - mTextures[i].base.unknown2 = 0; - } -} - ESM4::Land::~Land() { } diff --git a/components/esm4/loadland.hpp b/components/esm4/loadland.hpp index 6d53a99ff2..80a2490855 100644 --- a/components/esm4/loadland.hpp +++ b/components/esm4/loadland.hpp @@ -123,7 +123,6 @@ namespace ESM4 Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right std::vector mIds; // land texture (LTEX) formids - Land(); virtual ~Land(); virtual void load(Reader& reader); diff --git a/components/esm4/loadlgtm.cpp b/components/esm4/loadlgtm.cpp index 79867d4106..74486c3615 100644 --- a/components/esm4/loadlgtm.cpp +++ b/components/esm4/loadlgtm.cpp @@ -35,22 +35,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LightingTemplate::LightingTemplate() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - - mLighting.ambient = 0; - mLighting.directional = 0; - mLighting.fogColor = 0; - mLighting.fogNear = 0.f; - mLighting.fogFar = 0.f; - mLighting.rotationXY = 0; - mLighting.rotationZ = 0; - mLighting.fogDirFade = 0.f; - mLighting.fogClipDist = 0.f; - mLighting.fogPower = FLT_MAX; // hack way to detect TES4 -} - ESM4::LightingTemplate::~LightingTemplate() { } diff --git a/components/esm4/loadlgtm.hpp b/components/esm4/loadlgtm.hpp index 9724d8b16e..9e54bf53d9 100644 --- a/components/esm4/loadlgtm.hpp +++ b/components/esm4/loadlgtm.hpp @@ -50,7 +50,6 @@ namespace ESM4 Lighting mLighting; - LightingTemplate(); virtual ~LightingTemplate(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadligh.cpp b/components/esm4/loadligh.cpp index bdd125c6bb..c4f0a94d51 100644 --- a/components/esm4/loadligh.cpp +++ b/components/esm4/loadligh.cpp @@ -31,15 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Light::Light() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mSound(0), - mFade(0.f) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); -} - ESM4::Light::~Light() { } diff --git a/components/esm4/loadligh.hpp b/components/esm4/loadligh.hpp index 4a0120d71a..b1c642eb27 100644 --- a/components/esm4/loadligh.hpp +++ b/components/esm4/loadligh.hpp @@ -42,7 +42,7 @@ namespace ESM4 struct Data { std::uint32_t time; // FO/FONV only - float duration; + float duration = -1; std::uint32_t radius; std::uint32_t colour; // RGBA // flags: @@ -57,18 +57,14 @@ namespace ESM4 // 0x00000200 = Spot Light // 0x00000400 = Spot Shadow std::int32_t flags; - float falloff; - float FOV; + float falloff = 1.f; + float FOV = 90; // FIXME: FOV in degrees or radians? float nearClip; // TES5 only float frequency; // TES5 only float intensityAmplitude; // TES5 only float movementAmplitude; // TES5 only std::uint32_t value; // gold float weight; - Data() : duration(-1), radius(0), colour(0), flags(0), falloff(1.f), FOV(90), - nearClip(0.f), frequency(0.f), intensityAmplitude(0.f), movementAmplitude(0.f), - value(0), weight(0.f) // FIXME: FOV in degrees or radians? - {} }; FormId mFormId; // from the header @@ -88,7 +84,6 @@ namespace ESM4 Data mData; - Light(); virtual ~Light(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp index 577d5dc8e1..7b442ef8df 100644 --- a/components/esm4/loadltex.cpp +++ b/components/esm4/loadltex.cpp @@ -37,14 +37,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LandTexture::LandTexture() : mFormId(0), mFlags(0), mHavokFriction(0), mHavokRestitution(0), - mTextureSpecular(0), mGrass(0), mHavokMaterial(0), mTexture(0), - mMaterial(0) -{ - mEditorId.clear(); - mTextureFile.clear(); -} - ESM4::LandTexture::~LandTexture() { } diff --git a/components/esm4/loadltex.hpp b/components/esm4/loadltex.hpp index 934e7cc708..6347df2827 100644 --- a/components/esm4/loadltex.hpp +++ b/components/esm4/loadltex.hpp @@ -62,7 +62,6 @@ namespace ESM4 // ---------------------- - LandTexture(); virtual ~LandTexture(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp index 0088446277..cef4c0aff9 100644 --- a/components/esm4/loadlvlc.cpp +++ b/components/esm4/loadlvlc.cpp @@ -32,12 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LevelledCreature::LevelledCreature() : mFormId(0), mFlags(0), mScriptId(0), mTemplate(0), - mChanceNone(0), mLvlCreaFlags(0) -{ - mEditorId.clear(); -} - ESM4::LevelledCreature::~LevelledCreature() { } diff --git a/components/esm4/loadlvlc.hpp b/components/esm4/loadlvlc.hpp index ccd08944b5..61f2d90e24 100644 --- a/components/esm4/loadlvlc.hpp +++ b/components/esm4/loadlvlc.hpp @@ -58,7 +58,6 @@ namespace ESM4 bool calcEachItemInCount() const; std::int8_t chanceNone() const; - LevelledCreature(); virtual ~LevelledCreature(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp index 1237a0add4..44395e92ea 100644 --- a/components/esm4/loadlvli.cpp +++ b/components/esm4/loadlvli.cpp @@ -32,12 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LevelledItem::LevelledItem() : mFormId(0), mFlags(0), mChanceNone(0), mHasLvlItemFlags(false), - mLvlItemFlags(0), mData(0) -{ - mEditorId.clear(); -} - ESM4::LevelledItem::~LevelledItem() { } diff --git a/components/esm4/loadlvli.hpp b/components/esm4/loadlvli.hpp index 0964370e63..479d556d5c 100644 --- a/components/esm4/loadlvli.hpp +++ b/components/esm4/loadlvli.hpp @@ -54,7 +54,6 @@ namespace ESM4 std::vector mLvlObject; - LevelledItem(); virtual ~LevelledItem(); bool calcAllLvlLessThanPlayer() const; diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp index fdd860b9d9..f4782a9c40 100644 --- a/components/esm4/loadlvln.cpp +++ b/components/esm4/loadlvln.cpp @@ -32,12 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LevelledNpc::LevelledNpc() : mFormId(0), mFlags(0), mChanceNone(0), mLvlActorFlags(0), mListCount(0) -{ - mEditorId.clear(); - mModel.clear(); -} - ESM4::LevelledNpc::~LevelledNpc() { } diff --git a/components/esm4/loadlvln.hpp b/components/esm4/loadlvln.hpp index 057ed9581d..7c82a44ded 100644 --- a/components/esm4/loadlvln.hpp +++ b/components/esm4/loadlvln.hpp @@ -52,7 +52,6 @@ namespace ESM4 std::uint8_t mListCount; std::vector mLvlObject; - LevelledNpc(); virtual ~LevelledNpc(); inline bool calcAllLvlLessThanPlayer() const { return (mLvlActorFlags & 0x01) != 0; } diff --git a/components/esm4/loadmato.cpp b/components/esm4/loadmato.cpp index fdbdc66ebc..839a6211df 100644 --- a/components/esm4/loadmato.cpp +++ b/components/esm4/loadmato.cpp @@ -31,12 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Material::Material() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - mModel.clear(); -} - ESM4::Material::~Material() { } diff --git a/components/esm4/loadmato.hpp b/components/esm4/loadmato.hpp index 9b0d6c3947..8f60824bd8 100644 --- a/components/esm4/loadmato.hpp +++ b/components/esm4/loadmato.hpp @@ -45,7 +45,6 @@ namespace ESM4 std::string mEditorId; std::string mModel; - Material(); virtual ~Material(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadmisc.cpp b/components/esm4/loadmisc.cpp index ee3c10d0b6..8231df28f5 100644 --- a/components/esm4/loadmisc.cpp +++ b/components/esm4/loadmisc.cpp @@ -31,18 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::MiscItem::MiscItem() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); - mMiniIcon.clear(); - - mData.value = 0; - mData.weight = 0.f; -} - ESM4::MiscItem::~MiscItem() { } diff --git a/components/esm4/loadmisc.hpp b/components/esm4/loadmisc.hpp index 94f09b1a23..308a6ec326 100644 --- a/components/esm4/loadmisc.hpp +++ b/components/esm4/loadmisc.hpp @@ -64,7 +64,6 @@ namespace ESM4 Data mData; - MiscItem(); virtual ~MiscItem(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadmset.cpp b/components/esm4/loadmset.cpp index b7859f9fa5..67ee5666d1 100644 --- a/components/esm4/loadmset.cpp +++ b/components/esm4/loadmset.cpp @@ -32,24 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::MediaSet::MediaSet() : mFormId(0), mFlags(0), mSetType(-1), mEnabled(0), - mBoundaryDayOuter(0.f), mBoundaryDayMiddle(0.f), mBoundaryDayInner(0.f), - mBoundaryNightOuter(0.f), mBoundaryNightMiddle(0.f), mBoundaryNightInner(0.f), - mLevel8(0.f), mLevel9(0.f), mLevel0(0.f), mLevelA(0.f), mLevelB(0.f), mLevelC(0.f), - mTime1(0.f), mTime2(0.f), mTime3(0.f), mTime4(0.f), - mSoundIntro(0), mSoundOutro(0) -{ - mEditorId.clear(); - mFullName.clear(); - - mSet2.clear(); - mSet3.clear(); - mSet4.clear(); - mSet5.clear(); - mSet6.clear(); - mSet7.clear(); -} - ESM4::MediaSet::~MediaSet() { } diff --git a/components/esm4/loadmset.hpp b/components/esm4/loadmset.hpp index b13c4d6f64..c66273bc6c 100644 --- a/components/esm4/loadmset.hpp +++ b/components/esm4/loadmset.hpp @@ -51,7 +51,7 @@ namespace ESM4 // night outer (NAM5), night middle (NAM6), night inner (NAM7) // Dungeon - intro (HNAM), battle (NAM2), explore (NAM3), suspence (NAM4), outro (INAM) // Incidental - daytime (HNAM), nighttime (INAM) - std::int32_t mSetType; + std::int32_t mSetType = -1; // 0x01 day outer, 0x02 day middle, 0x04 day inner // 0x08 night outer, 0x10 night middle, 0x20 night inner std::uint8_t mEnabled; // for location @@ -86,7 +86,6 @@ namespace ESM4 FormId mSoundIntro; // HNAM FormId mSoundOutro; // INAM - MediaSet(); virtual ~MediaSet(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadmstt.cpp b/components/esm4/loadmstt.cpp index 61d6147b2f..35c69c8a31 100644 --- a/components/esm4/loadmstt.cpp +++ b/components/esm4/loadmstt.cpp @@ -32,12 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::MovableStatic::MovableStatic() : mFormId(0), mFlags(0), mData(0), mLoopingSound(0) -{ - mEditorId.clear(); - mModel.clear(); -} - ESM4::MovableStatic::~MovableStatic() { } diff --git a/components/esm4/loadmusc.cpp b/components/esm4/loadmusc.cpp index 903bbb5a1f..a328fbce1e 100644 --- a/components/esm4/loadmusc.cpp +++ b/components/esm4/loadmusc.cpp @@ -36,12 +36,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Music::Music() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - mMusicFile.clear(); -} - ESM4::Music::~Music() { } diff --git a/components/esm4/loadmusc.hpp b/components/esm4/loadmusc.hpp index ab3889a6c0..2ec10abc8c 100644 --- a/components/esm4/loadmusc.hpp +++ b/components/esm4/loadmusc.hpp @@ -47,7 +47,6 @@ namespace ESM4 std::string mEditorId; std::string mMusicFile; - Music(); virtual ~Music(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp index 0655edb1e8..818a8e98aa 100644 --- a/components/esm4/loadnavi.cpp +++ b/components/esm4/loadnavi.cpp @@ -38,11 +38,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Navigation::Navigation() -{ - mEditorId.clear(); -} - ESM4::Navigation::~Navigation() { } diff --git a/components/esm4/loadnavi.hpp b/components/esm4/loadnavi.hpp index 0db9650c4e..ff0d033f3b 100644 --- a/components/esm4/loadnavi.hpp +++ b/components/esm4/loadnavi.hpp @@ -104,7 +104,6 @@ namespace ESM4 std::map mPathIndexMap; - Navigation(); virtual ~Navigation(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp index 1f3eebe434..1e84a9ffee 100644 --- a/components/esm4/loadnavm.cpp +++ b/components/esm4/loadnavm.cpp @@ -35,10 +35,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::NavMesh::NavMesh() : mFormId(0), mFlags(0) -{ -} - ESM4::NavMesh::~NavMesh() { } diff --git a/components/esm4/loadnavm.hpp b/components/esm4/loadnavm.hpp index f5fb9b5e20..7910b1373d 100644 --- a/components/esm4/loadnavm.hpp +++ b/components/esm4/loadnavm.hpp @@ -98,7 +98,6 @@ namespace ESM4 FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details - NavMesh(); virtual ~NavMesh(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadnote.cpp b/components/esm4/loadnote.cpp index ee897258b7..90e39811af 100644 --- a/components/esm4/loadnote.cpp +++ b/components/esm4/loadnote.cpp @@ -32,14 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Note::Note() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); -} - ESM4::Note::~Note() { } diff --git a/components/esm4/loadnote.hpp b/components/esm4/loadnote.hpp index 1ba9bfec24..506674081f 100644 --- a/components/esm4/loadnote.hpp +++ b/components/esm4/loadnote.hpp @@ -47,7 +47,6 @@ namespace ESM4 std::string mModel; std::string mIcon; - Note(); virtual ~Note(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index e32ee6e90d..51d1d0b181 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -41,27 +41,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Npc::Npc() : mFormId(0), mFlags(0), mIsTES4(false), mIsFONV(false), mRace(0), mClass(0), mHair(0), - mEyes(0), mHairLength(0.f), mHairColourId(0), mDeathItem(0), - mScriptId(0), mCombatStyle(0), mSoundBase(0), mSound(0), mSoundChance(0), - mFootWeight(0.f), mBoundRadius(0.f), mBaseTemplate(0), mWornArmor(0), - mDefaultOutfit(0), mSleepOutfit(0), mDefaultPkg(0), mFgRace(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - - mHairColour.red = 0; - mHairColour.green = 0; - mHairColour.blue = 0; - mHairColour.custom = 0; - - std::memset(&mAIData, 0, sizeof(AIData)); - std::memset(&mData, 0, sizeof(Data)); - std::memset(&mBaseConfig, 0, sizeof(ActorBaseConfig)); - std::memset(&mFaction, 0, sizeof(ActorFaction)); -} - ESM4::Npc::~Npc() { } diff --git a/components/esm4/loadnpc.hpp b/components/esm4/loadnpc.hpp index d30ca5f14f..1bd38e189c 100644 --- a/components/esm4/loadnpc.hpp +++ b/components/esm4/loadnpc.hpp @@ -217,7 +217,6 @@ namespace ESM4 std::vector mSymTextureModeCoefficients; // size 0 or 50 std::int16_t mFgRace; - Npc(); virtual ~Npc(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadotft.cpp b/components/esm4/loadotft.cpp index dec2a05666..d1b1bb23a7 100644 --- a/components/esm4/loadotft.cpp +++ b/components/esm4/loadotft.cpp @@ -32,11 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Outfit::Outfit() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); -} - ESM4::Outfit::~Outfit() { } diff --git a/components/esm4/loadotft.hpp b/components/esm4/loadotft.hpp index 3b1db34d0e..b982a3c468 100644 --- a/components/esm4/loadotft.hpp +++ b/components/esm4/loadotft.hpp @@ -47,7 +47,6 @@ namespace ESM4 std::vector mInventory; - Outfit(); virtual ~Outfit(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp index 241147df00..5ab9555cd6 100644 --- a/components/esm4/loadpack.cpp +++ b/components/esm4/loadpack.cpp @@ -33,20 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::AIPackage::AIPackage() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - - std::memset(&mData, 0, sizeof(PKDT)); - std::memset(&mSchedule, 0, sizeof(PSDT)); - std::memset(&mLocation, 0, sizeof(PLDT)); - mLocation.type = 0xff; // default to indicate no location data - std::memset(&mTarget, 0, sizeof(PTDT)); - mTarget.type = 0xff; // default to indicate no target data - - mConditions.clear(); -} - ESM4::AIPackage::~AIPackage() { } diff --git a/components/esm4/loadpack.hpp b/components/esm4/loadpack.hpp index 93a9a8290c..b7fe895941 100644 --- a/components/esm4/loadpack.hpp +++ b/components/esm4/loadpack.hpp @@ -58,14 +58,14 @@ namespace ESM4 struct PLDT // location { - std::int32_t type; // 0 = near ref, 1 = in cell, 2 = current loc, 3 = editor loc, 4 = obj id, 5 = obj type + std::int32_t type = 0xff; // 0 = near ref, 1 = in cell, 2 = current loc, 3 = editor loc, 4 = obj id, 5 = obj type, 0xff = no location data FormId location; // uint32_t if type = 5 std::int32_t radius; }; struct PTDT // target { - std::int32_t type; // 0 = specific ref, 1 = obj id, 2 = obj type + std::int32_t type = 0xff; // 0 = specific ref, 1 = obj id, 2 = obj type, 0xff = no target data FormId target; // uint32_t if type = 2 std::int32_t distance; }; diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index c9c3048605..4a95c8af0a 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -36,16 +36,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Pathgrid::Pathgrid() : mFormId(0), mFlags(0), mData(0) -{ - mEditorId.clear(); - - mNodes.clear(); - mLinks.clear(); - mForeign.clear(); - mObjects.clear(); -} - ESM4::Pathgrid::~Pathgrid() { } diff --git a/components/esm4/loadpgre.cpp b/components/esm4/loadpgre.cpp index 9c3e745ab9..665b07e523 100644 --- a/components/esm4/loadpgre.cpp +++ b/components/esm4/loadpgre.cpp @@ -34,11 +34,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::PlacedGrenade::PlacedGrenade() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); -} - ESM4::PlacedGrenade::~PlacedGrenade() { } diff --git a/components/esm4/loadpwat.cpp b/components/esm4/loadpwat.cpp index a88adcf4c2..52852b0e8a 100644 --- a/components/esm4/loadpwat.cpp +++ b/components/esm4/loadpwat.cpp @@ -34,11 +34,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::PlaceableWater::PlaceableWater() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); -} - ESM4::PlaceableWater::~PlaceableWater() { } diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp index 79f635bd48..3fa9661122 100644 --- a/components/esm4/loadrace.cpp +++ b/components/esm4/loadrace.cpp @@ -35,28 +35,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Race::Race() : mFormId(0), mFlags(0), mIsTES5(false) - , mHeightMale(1.f), mHeightFemale(1.f), mWeightMale(1.f), mWeightFemale(1.f) - , mRaceFlags(0), mFaceGenMainClamp(0.f), mFaceGenFaceClamp(0.f), mNumKeywords(0) - , mSkin(0) -{ - mEditorId.clear(); - mFullName.clear(); - mDesc.clear(); - mModelMale.clear(); - mModelFemale.clear(); - - std::memset(&mAttribMale, 0, sizeof(AttributeValues)); - std::memset(&mAttribFemale, 0, sizeof(AttributeValues)); - - mVNAM.resize(2); - mDefaultHair.resize(2); - - mBodyTemplate.bodyPart = 0; - mBodyTemplate.flags = 0; - mBodyTemplate.type = 0; -} - ESM4::Race::~Race() { } diff --git a/components/esm4/loadrace.hpp b/components/esm4/loadrace.hpp index 0cede13b0b..c5912b719b 100644 --- a/components/esm4/loadrace.hpp +++ b/components/esm4/loadrace.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "formid.hpp" #include "actor.hpp" // AttributeValues, BodyTemplate @@ -122,10 +123,10 @@ namespace ESM4 std::map mSkillBonus; // DATA - float mHeightMale; - float mHeightFemale; - float mWeightMale; - float mWeightFemale; + float mHeightMale = 1.0f; + float mHeightFemale = 1.0f; + float mWeightMale = 1.0f; + float mWeightFemale = 1.0f; std::uint32_t mRaceFlags; // 0x0001 = playable? std::vector mHeadParts; // see HeadPartIndex @@ -148,8 +149,8 @@ namespace ESM4 std::map mDisposition; // race adjustments std::vector mBonusSpells; // race ability/power - std::vector mVNAM; // don't know what these are; 1 or 2 RACE FormIds - std::vector mDefaultHair; // male/female (HAIR FormId for TES4) + std::array mVNAM; // don't know what these are; 1 or 2 RACE FormIds + std::array mDefaultHair; // male/female (HAIR FormId for TES4) std::uint32_t mNumKeywords; @@ -161,7 +162,6 @@ namespace ESM4 std::vector mHeadPartIdsMale; // TES5 std::vector mHeadPartIdsFemale; // TES5 - Race(); virtual ~Race(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index 4921fd16ca..24cd3d5cde 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -32,29 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Reference::Reference() : mParent(0), mFormId(0), mFlags(0), - mBaseObj(0), mScale(1.f), mOwner(0), mGlobal(0), mFactionRank(-1), - mInitiallyDisabled(false), mIsMapMarker(false), mMapMarker(0), mCount(1), - mAudioLocation(0), mIsLocked(false), mLockLevel(0), mKey(0), mTargetRef(0) -{ - mEditorId.clear(); - mFullName.clear(); - - //mPlacement. - - mEsp.parent = 0; - mEsp.flags = 0; - - mRadio.rangeRadius = 0.f; - mRadio.broadcastRange = 0; - mRadio.staticPercentage = 0.f; - mRadio.posReference = 0; - - mDoor.destDoor = 0; - //mDoor.destPos. - mDoor.flags = 0; -} - ESM4::Reference::~Reference() { } diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index d9c96cbf9f..b631b334ee 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -82,10 +82,10 @@ namespace ESM4 FormId mBaseObj; Placement mPlacement; - float mScale; // default 1.f + float mScale = 1.0f; FormId mOwner; FormId mGlobal; - std::int32_t mFactionRank; + std::int32_t mFactionRank = -1; bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) bool mIsMapMarker; @@ -93,7 +93,7 @@ namespace ESM4 EnableParent mEsp; - std::uint32_t mCount; // only if > 1 (default 1) + std::uint32_t mCount = 1; // only if > 1 FormId mAudioLocation; @@ -106,7 +106,6 @@ namespace ESM4 FormId mTargetRef; - Reference(); virtual ~Reference(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp index 6f871e8471..7acda43350 100644 --- a/components/esm4/loadregn.cpp +++ b/components/esm4/loadregn.cpp @@ -39,13 +39,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Region::Region() : mFormId(0), mFlags(0), mWorldId(0), mEdgeFalloff(0) -{ - mEditorId.clear(); - mShader.clear(); - mMapName.clear(); -} - ESM4::Region::~Region() { } diff --git a/components/esm4/loadregn.hpp b/components/esm4/loadregn.hpp index 36ed8eb9d5..86b5ca5799 100644 --- a/components/esm4/loadregn.hpp +++ b/components/esm4/loadregn.hpp @@ -85,7 +85,6 @@ namespace ESM4 RegionData mData; std::vector mSounds; - Region(); virtual ~Region(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp index cb7742eb7c..fca11db779 100644 --- a/components/esm4/loadroad.cpp +++ b/components/esm4/loadroad.cpp @@ -33,14 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Road::Road() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - - mNodes.clear(); - mLinks.clear(); -} - ESM4::Road::~Road() { } diff --git a/components/esm4/loadroad.hpp b/components/esm4/loadroad.hpp index dad92e858c..85745d000a 100644 --- a/components/esm4/loadroad.hpp +++ b/components/esm4/loadroad.hpp @@ -76,7 +76,6 @@ namespace ESM4 std::vector mNodes; std::vector mLinks; - Road(); virtual ~Road(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadsbsp.cpp b/components/esm4/loadsbsp.cpp index c9af5b856e..9daef0f89c 100644 --- a/components/esm4/loadsbsp.cpp +++ b/components/esm4/loadsbsp.cpp @@ -31,15 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::SubSpace::SubSpace() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - - mDimension.x = 0.f; - mDimension.y = 0.f; - mDimension.z = 0.f; -} - ESM4::SubSpace::~SubSpace() { } diff --git a/components/esm4/loadsbsp.hpp b/components/esm4/loadsbsp.hpp index c0ca7c5ddf..601b2b0cb7 100644 --- a/components/esm4/loadsbsp.hpp +++ b/components/esm4/loadsbsp.hpp @@ -51,7 +51,6 @@ namespace ESM4 std::string mEditorId; Dimension mDimension; - SubSpace(); virtual ~SubSpace(); virtual void load(Reader& reader); diff --git a/components/esm4/loadscol.cpp b/components/esm4/loadscol.cpp index 1990af5f4b..23a3cad1a3 100644 --- a/components/esm4/loadscol.cpp +++ b/components/esm4/loadscol.cpp @@ -34,11 +34,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::StaticCollection::StaticCollection() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); -} - ESM4::StaticCollection::~StaticCollection() { } diff --git a/components/esm4/loadscol.hpp b/components/esm4/loadscol.hpp index c32cdd3ca9..dc1bf296e1 100644 --- a/components/esm4/loadscol.hpp +++ b/components/esm4/loadscol.hpp @@ -46,7 +46,6 @@ namespace ESM4 std::string mEditorId; - StaticCollection(); virtual ~StaticCollection(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index cf4bdeb58a..c95a212e75 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -33,11 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Script::Script() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); -} - ESM4::Script::~Script() { } diff --git a/components/esm4/loadscpt.hpp b/components/esm4/loadscpt.hpp index 747f49444d..459c552876 100644 --- a/components/esm4/loadscpt.hpp +++ b/components/esm4/loadscpt.hpp @@ -46,7 +46,6 @@ namespace ESM4 ScriptDefinition mScript; - Script(); virtual ~Script(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadscrl.cpp b/components/esm4/loadscrl.cpp index c09147ce5e..c09c38ae90 100644 --- a/components/esm4/loadscrl.cpp +++ b/components/esm4/loadscrl.cpp @@ -32,17 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Scroll::Scroll() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mText.clear(); - - mData.value = 0; - mData.weight = 0.f; -} - ESM4::Scroll::~Scroll() { } diff --git a/components/esm4/loadscrl.hpp b/components/esm4/loadscrl.hpp index a88781a6f8..d4feded5d8 100644 --- a/components/esm4/loadscrl.hpp +++ b/components/esm4/loadscrl.hpp @@ -55,7 +55,6 @@ namespace ESM4 Data mData; - Scroll(); virtual ~Scroll(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadsgst.cpp b/components/esm4/loadsgst.cpp index b46c6bad29..9453cdc27d 100644 --- a/components/esm4/loadsgst.cpp +++ b/components/esm4/loadsgst.cpp @@ -32,20 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::SigilStone::SigilStone() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); - - mData.uses = 0; - mData.value = 0; - mData.weight = 0.f; - - std::memset(&mEffect, 0, sizeof(ScriptEffect)); -} - ESM4::SigilStone::~SigilStone() { } diff --git a/components/esm4/loadsgst.hpp b/components/esm4/loadsgst.hpp index b0bbf9d800..3a2d32850d 100644 --- a/components/esm4/loadsgst.hpp +++ b/components/esm4/loadsgst.hpp @@ -62,7 +62,6 @@ namespace ESM4 Data mData; - SigilStone(); virtual ~SigilStone(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadslgm.cpp b/components/esm4/loadslgm.cpp index 482c817802..97bbab9569 100644 --- a/components/esm4/loadslgm.cpp +++ b/components/esm4/loadslgm.cpp @@ -31,17 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::SoulGem::SoulGem() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0), mSoul(0), mSoulCapacity(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mIcon.clear(); - - mData.value = 0; - mData.weight = 0.f; -} - ESM4::SoulGem::~SoulGem() { } diff --git a/components/esm4/loadslgm.hpp b/components/esm4/loadslgm.hpp index 8724da13c1..e101fbf8a5 100644 --- a/components/esm4/loadslgm.hpp +++ b/components/esm4/loadslgm.hpp @@ -63,7 +63,6 @@ namespace ESM4 Data mData; - SoulGem(); virtual ~SoulGem(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadsndr.cpp b/components/esm4/loadsndr.cpp index 25109e36b6..4c0bf30b24 100644 --- a/components/esm4/loadsndr.cpp +++ b/components/esm4/loadsndr.cpp @@ -32,12 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::SoundReference::SoundReference() : mFormId(0), mFlags(0), mSoundCategory(0), mSoundId(0), mOutputModel(0) -{ - mEditorId.clear(); - mSoundFile.clear(); -} - ESM4::SoundReference::~SoundReference() { } diff --git a/components/esm4/loadsndr.hpp b/components/esm4/loadsndr.hpp index 17f0491136..fba1912264 100644 --- a/components/esm4/loadsndr.hpp +++ b/components/esm4/loadsndr.hpp @@ -73,7 +73,6 @@ namespace ESM4 TargetCondition mTargetCondition; - SoundReference(); virtual ~SoundReference(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadsoun.cpp b/components/esm4/loadsoun.cpp index 6441d8a799..bcb776e100 100644 --- a/components/esm4/loadsoun.cpp +++ b/components/esm4/loadsoun.cpp @@ -32,12 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Sound::Sound() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - mSoundFile.clear(); -} - ESM4::Sound::~Sound() { } diff --git a/components/esm4/loadsoun.hpp b/components/esm4/loadsoun.hpp index 845f38c380..b766aa5534 100644 --- a/components/esm4/loadsoun.hpp +++ b/components/esm4/loadsoun.hpp @@ -88,7 +88,6 @@ namespace ESM4 SNDX mData; SoundData mExtra; - Sound(); virtual ~Sound(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp index de487147fe..d08812b4ee 100644 --- a/components/esm4/loadstat.cpp +++ b/components/esm4/loadstat.cpp @@ -32,12 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Static::Static() : mFormId(0), mFlags(0), mBoundRadius(0.f) -{ - mEditorId.clear(); - mModel.clear(); -} - ESM4::Static::~Static() { } diff --git a/components/esm4/loadstat.hpp b/components/esm4/loadstat.hpp index 966721ef37..9c689462ef 100644 --- a/components/esm4/loadstat.hpp +++ b/components/esm4/loadstat.hpp @@ -49,7 +49,6 @@ namespace ESM4 float mBoundRadius; std::vector mMODT; // FIXME texture hash - Static(); virtual ~Static(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadtact.cpp b/components/esm4/loadtact.cpp index c082048086..f82e70c483 100644 --- a/components/esm4/loadtact.cpp +++ b/components/esm4/loadtact.cpp @@ -32,14 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::TalkingActivator::TalkingActivator() : mFormId(0), mFlags(0), mScriptId(0), mVoiceType(0), mLoopSound(0), - mRadioTemplate(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); -} - ESM4::TalkingActivator::~TalkingActivator() { } diff --git a/components/esm4/loadtact.hpp b/components/esm4/loadtact.hpp index a1d15524be..f3fd91a433 100644 --- a/components/esm4/loadtact.hpp +++ b/components/esm4/loadtact.hpp @@ -63,7 +63,6 @@ namespace ESM4 FormId mLoopSound; // SOUN FormId mRadioTemplate; // SOUN - TalkingActivator(); virtual ~TalkingActivator(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadterm.cpp b/components/esm4/loadterm.cpp index 68a436d87c..e9eedc3651 100644 --- a/components/esm4/loadterm.cpp +++ b/components/esm4/loadterm.cpp @@ -32,16 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Terminal::Terminal() : mFormId(0), mFlags(0), mScriptId(0), mPasswordNote(0), mSound(0) -{ - mEditorId.clear(); - mFullName.clear(); - mText.clear(); - - mModel.clear(); - mResultText.clear(); -} - ESM4::Terminal::~Terminal() { } diff --git a/components/esm4/loadterm.hpp b/components/esm4/loadterm.hpp index 9fb0ee60a8..1b1486d7a2 100644 --- a/components/esm4/loadterm.hpp +++ b/components/esm4/loadterm.hpp @@ -53,7 +53,6 @@ namespace ESM4 FormId mPasswordNote; FormId mSound; - Terminal(); virtual ~Terminal(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp index 7029222758..c70863a51b 100644 --- a/components/esm4/loadtree.cpp +++ b/components/esm4/loadtree.cpp @@ -31,13 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Tree::Tree() : mFormId(0), mFlags(0), mBoundRadius(0.f) -{ - mEditorId.clear(); - mModel.clear(); - mLeafTexture.clear(); -} - ESM4::Tree::~Tree() { } diff --git a/components/esm4/loadtree.hpp b/components/esm4/loadtree.hpp index 2069f93d95..2c88812b46 100644 --- a/components/esm4/loadtree.hpp +++ b/components/esm4/loadtree.hpp @@ -49,7 +49,6 @@ namespace ESM4 std::string mLeafTexture; - Tree(); virtual ~Tree(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp index ce281d4848..51f58c9816 100644 --- a/components/esm4/loadtxst.cpp +++ b/components/esm4/loadtxst.cpp @@ -32,19 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::TextureSet::TextureSet() : mFormId(0), mFlags(0) -{ - mEditorId.clear(); - mDiffuse.clear(); - mNormalMap.clear(); - mEnvMask.clear(); - mToneMap.clear(); - mDetailMap.clear(); - mEnvMap.clear(); - mUnknown.clear(); - mSpecular.clear(); -} - ESM4::TextureSet::~TextureSet() { } diff --git a/components/esm4/loadtxst.hpp b/components/esm4/loadtxst.hpp index cc81923e1f..71251c2dcb 100644 --- a/components/esm4/loadtxst.hpp +++ b/components/esm4/loadtxst.hpp @@ -53,7 +53,6 @@ namespace ESM4 std::string mUnknown; std::string mSpecular; - TextureSet(); virtual ~TextureSet(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadweap.cpp b/components/esm4/loadweap.cpp index b56e01171c..463496187e 100644 --- a/components/esm4/loadweap.cpp +++ b/components/esm4/loadweap.cpp @@ -31,17 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Weapon::Weapon() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f), mScriptId(0), - mEnchantmentPoints(0), mEnchantment(0) -{ - mEditorId.clear(); - mFullName.clear(); - mModel.clear(); - mText.clear(); - mIcon.clear(); - mMiniIcon.clear(); -} - ESM4::Weapon::~Weapon() { } diff --git a/components/esm4/loadweap.hpp b/components/esm4/loadweap.hpp index dae2da1e7e..7128e26ec9 100644 --- a/components/esm4/loadweap.hpp +++ b/components/esm4/loadweap.hpp @@ -83,7 +83,6 @@ namespace ESM4 Data mData; - Weapon(); virtual ~Weapon(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp index ec01556338..ca8e5fd2c5 100644 --- a/components/esm4/loadwrld.cpp +++ b/components/esm4/loadwrld.cpp @@ -32,25 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::World::World() : mFormId(0), mFlags(0), mParent(0), mWorldFlags(0), mClimate(0), mWater(0), - mLandLevel(0.f), mWaterLevel(0.f), // -2700.f and -14000.f for TES5 - mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mSound(0), mMusic(0), mParentUseFlags(0) -{ - mEditorId.clear(); - mFullName.clear(); - mMapFile.clear(); - - mMap.width = 0; - mMap.height = 0; - mMap.NWcellX = 0; - mMap.NWcellY = 0; - mMap.SEcellX = 0; - mMap.SEcellY = 0; - mMap.minHeight = 0.f; - mMap.maxHeight = 0.f; - mMap.initialPitch = 0.f; -} - ESM4::World::~World() { } diff --git a/components/esm4/loadwrld.hpp b/components/esm4/loadwrld.hpp index 0105dc12f4..5937670daf 100644 --- a/components/esm4/loadwrld.hpp +++ b/components/esm4/loadwrld.hpp @@ -126,7 +126,6 @@ namespace ESM4 std::vector mCells; std::vector mRoads; - World(); virtual ~World(); virtual void load(ESM4::Reader& reader); From 1a15ad216d57d127d2b287c48bbb44e4f6e6596f Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 20 Feb 2022 05:53:57 +0300 Subject: [PATCH 2153/2859] =?UTF-8?q?Increase=20the=20base=20angular=20vel?= =?UTF-8?q?ocity=20to=20900=C2=B0/sec=20(#5192)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/steering.hpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ebd3b07e3..a51c785f48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system + Bug #5192: Actor turn rate is too slow Bug #5207: Loose summons can be present in scene Bug #5377: console does not appear after using menutest in inventory Bug #5379: Wandering NPCs falling through cantons diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index f305a6961c..99fa1387db 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -16,9 +16,11 @@ namespace MWMechanics // Max rotating speed, radian/sec inline float getAngularVelocity(const float actorSpeed) { - const float baseAngluarVelocity = 10; + constexpr float degreesPerFrame = 15.f; + constexpr int framesPerSecond = 60; + const float baseAngularVelocity = osg::DegreesToRadians(degreesPerFrame * framesPerSecond); const float baseSpeed = 200; - return baseAngluarVelocity * std::max(actorSpeed / baseSpeed, 1.0f); + return baseAngularVelocity * std::max(actorSpeed / baseSpeed, 1.0f); } /// configure rotation settings for an actor to reach this target angle (eventually) From 595c2e0a8e23391a56a2352f70e6ad739f9ce5b1 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 23 Feb 2022 00:39:30 +0100 Subject: [PATCH 2154/2859] Use unique_ptr to manage AiPackage lifetime --- apps/openmw/mwmechanics/aiactivate.cpp | 4 +-- apps/openmw/mwmechanics/aicombat.cpp | 4 +-- apps/openmw/mwmechanics/aiescort.cpp | 4 +-- apps/openmw/mwmechanics/aifollow.cpp | 4 +-- apps/openmw/mwmechanics/aipursue.cpp | 4 +-- apps/openmw/mwmechanics/aisequence.cpp | 20 ++++++------ apps/openmw/mwmechanics/aitravel.cpp | 4 +-- apps/openmw/mwmechanics/aiwander.cpp | 4 +-- components/esm3/aisequence.cpp | 44 +++++++++++--------------- components/esm3/aisequence.hpp | 4 +-- 10 files changed, 45 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 42cff3642e..c2bcab2297 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -52,8 +52,8 @@ namespace MWMechanics ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Activate; - package.mPackage = activate.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(activate); + sequence.mPackages.push_back(std::move(package)); } AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 83ff795545..231597f9e0 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -460,8 +460,8 @@ namespace MWMechanics ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Combat; - package.mPackage = combat.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(combat); + sequence.mPackages.push_back(std::move(package)); } void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index a1c0bd0471..2e7d55712f 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -109,8 +109,8 @@ namespace MWMechanics ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Escort; - package.mPackage = escort.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(escort); + sequence.mPackages.push_back(std::move(package)); } void AiEscort::fastForward(const MWWorld::Ptr& actor, AiState &state) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index cdb8ac8c1d..9444303821 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -217,8 +217,8 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; - package.mPackage = follow.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(follow); + sequence.mPackages.push_back(std::move(package)); } int AiFollow::getFollowIndex() const diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 3343b3cec0..252bff58f8 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -81,8 +81,8 @@ void AiPursue::writeState(ESM::AiSequence::AiSequence &sequence) const ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Pursue; - package.mPackage = pursue.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(pursue); + sequence.mPackages.push_back(std::move(package)); } } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index bf01936f90..c59b3e39fe 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -498,41 +498,41 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) { case ESM::AiSequence::Ai_Wander: { - package.reset(new AiWander(static_cast(container.mPackage))); + package.reset(new AiWander(&static_cast(*container.mPackage))); break; } case ESM::AiSequence::Ai_Travel: { - const auto source = static_cast(container.mPackage); - if (source->mHidden) - package.reset(new AiInternalTravel(source)); + const ESM::AiSequence::AiTravel& source = static_cast(*container.mPackage); + if (source.mHidden) + package.reset(new AiInternalTravel(&source)); else - package.reset(new AiTravel(source)); + package.reset(new AiTravel(&source)); break; } case ESM::AiSequence::Ai_Escort: { - package.reset(new AiEscort(static_cast(container.mPackage))); + package.reset(new AiEscort(&static_cast(*container.mPackage))); break; } case ESM::AiSequence::Ai_Follow: { - package.reset(new AiFollow(static_cast(container.mPackage))); + package.reset(new AiFollow(&static_cast(*container.mPackage))); break; } case ESM::AiSequence::Ai_Activate: { - package.reset(new AiActivate(static_cast(container.mPackage))); + package.reset(new AiActivate(&static_cast(*container.mPackage))); break; } case ESM::AiSequence::Ai_Combat: { - package.reset(new AiCombat(static_cast(container.mPackage))); + package.reset(new AiCombat(&static_cast(*container.mPackage))); break; } case ESM::AiSequence::Ai_Pursue: { - package.reset(new AiPursue(static_cast(container.mPackage))); + package.reset(new AiPursue(&static_cast(*container.mPackage))); break; } default: diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index d420771bd4..4a2df475f4 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -129,8 +129,8 @@ namespace MWMechanics ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Travel; - package.mPackage = travel.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(travel); + sequence.mPackages.push_back(std::move(package)); } AiInternalTravel::AiInternalTravel(float x, float y, float z) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index c2f0beb179..c63a4c0270 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -888,8 +888,8 @@ namespace MWMechanics ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Wander; - package.mPackage = wander.release(); - sequence.mPackages.push_back(package); + package.mPackage = std::move(wander); + sequence.mPackages.push_back(std::move(package)); } AiWander::AiWander (const ESM::AiSequence::AiWander* wander) diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index b99cac3ade..a9670a598d 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -152,12 +152,6 @@ namespace AiSequence esm.writeHNT ("TARG", mTargetActorId); } - AiSequence::~AiSequence() - { - for (std::vector::iterator it = mPackages.begin(); it != mPackages.end(); ++it) - delete it->mPackage; - } - void AiSequence::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) @@ -166,25 +160,25 @@ namespace AiSequence switch (it->mType) { case Ai_Wander: - static_cast(it->mPackage)->save(esm); + static_cast(*it->mPackage).save(esm); break; case Ai_Travel: - static_cast(it->mPackage)->save(esm); + static_cast(*it->mPackage).save(esm); break; case Ai_Escort: - static_cast(it->mPackage)->save(esm); + static_cast(*it->mPackage).save(esm); break; case Ai_Follow: - static_cast(it->mPackage)->save(esm); + static_cast(*it->mPackage).save(esm); break; case Ai_Activate: - static_cast(it->mPackage)->save(esm); + static_cast(*it->mPackage).save(esm); break; case Ai_Combat: - static_cast(it->mPackage)->save(esm); + static_cast(*it->mPackage).save(esm); break; case Ai_Pursue: - static_cast(it->mPackage)->save(esm); + static_cast(*it->mPackage).save(esm); break; default: @@ -212,7 +206,7 @@ namespace AiSequence { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); - mPackages.back().mPackage = ptr.release(); + mPackages.back().mPackage = std::move(ptr); ++count; break; } @@ -220,7 +214,7 @@ namespace AiSequence { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); - mPackages.back().mPackage = ptr.release(); + mPackages.back().mPackage = std::move(ptr); ++count; break; } @@ -228,7 +222,7 @@ namespace AiSequence { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); - mPackages.back().mPackage = ptr.release(); + mPackages.back().mPackage = std::move(ptr); ++count; break; } @@ -236,7 +230,7 @@ namespace AiSequence { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); - mPackages.back().mPackage = ptr.release(); + mPackages.back().mPackage = std::move(ptr); ++count; break; } @@ -244,7 +238,7 @@ namespace AiSequence { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); - mPackages.back().mPackage = ptr.release(); + mPackages.back().mPackage = std::move(ptr); ++count; break; } @@ -252,14 +246,14 @@ namespace AiSequence { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); - mPackages.back().mPackage = ptr.release(); + mPackages.back().mPackage = std::move(ptr); break; } case Ai_Pursue: { std::unique_ptr ptr = std::make_unique(); ptr->load(esm); - mPackages.back().mPackage = ptr.release(); + mPackages.back().mPackage = std::move(ptr); break; } default: @@ -274,15 +268,15 @@ namespace AiSequence for(auto& pkg : mPackages) { if(pkg.mType == Ai_Wander) - static_cast(pkg.mPackage)->mData.mShouldRepeat = true; + static_cast(*pkg.mPackage).mData.mShouldRepeat = true; else if(pkg.mType == Ai_Travel) - static_cast(pkg.mPackage)->mRepeat = true; + static_cast(*pkg.mPackage).mRepeat = true; else if(pkg.mType == Ai_Escort) - static_cast(pkg.mPackage)->mRepeat = true; + static_cast(*pkg.mPackage).mRepeat = true; else if(pkg.mType == Ai_Follow) - static_cast(pkg.mPackage)->mRepeat = true; + static_cast(*pkg.mPackage).mRepeat = true; else if(pkg.mType == Ai_Activate) - static_cast(pkg.mPackage)->mRepeat = true; + static_cast(*pkg.mPackage).mRepeat = true; } } } diff --git a/components/esm3/aisequence.hpp b/components/esm3/aisequence.hpp index b5a003678b..3dcecc1cb0 100644 --- a/components/esm3/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "components/esm/defs.hpp" @@ -149,7 +150,7 @@ namespace ESM { int mType; - AiPackage* mPackage; + std::unique_ptr mPackage; }; struct AiSequence @@ -158,7 +159,6 @@ namespace ESM { mLastAiPackage = -1; } - ~AiSequence(); std::vector mPackages; int mLastAiPackage; From b6bddbfae22b63f9ce4b008c11cc88df8b8f0c24 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 23 Feb 2022 01:56:55 +0100 Subject: [PATCH 2155/2859] Support regexp_match for hist_threshold and hist --- scripts/osg_stats.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 275e70dcff..d74df94a5c 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -19,7 +19,8 @@ import re @click.option('--print_keys', is_flag=True, 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 and timeseries.') + help='Use all metric that match given key. ' + 'Can be used with stats, timeseries, commulative_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, @@ -72,14 +73,18 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if print_keys: for v in keys: print(v) + def matching_keys(patterns): + if regexp_match: + return [key for pattern in patterns for key in keys if re.search(pattern, key)] + return keys if timeseries: - draw_timeseries(sources=frames, keys=matching_keys(keys, timeseries, regexp_match), add_sum=timeseries_sum, + 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(keys, commulative_timeseries, regexp_match), add_sum=commulative_timeseries_sum, + draw_commulative_timeseries(sources=frames, keys=matching_keys(commulative_timeseries), add_sum=commulative_timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) if hist: - draw_hists(sources=frames, keys=hist) + draw_hists(sources=frames, keys=matching_keys(hist)) if hist_ratio: draw_hist_ratio(sources=frames, pairs=hist_ratio) if stdev_hist: @@ -87,9 +92,9 @@ 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(keys, stats, regexp_match), stats_sum=stats_sum, precision=precision) + print_stats(sources=frames, keys=matching_keys(stats), stats_sum=stats_sum, precision=precision) if hist_threshold: - draw_hist_threshold(sources=frames, keys=hist_threshold, begin_frame=begin_frame, + draw_hist_threshold(sources=frames, keys=matching_keys(hist_threshold), begin_frame=begin_frame, threshold_name=threshold_name, threshold_value=threshold_value) matplotlib.pyplot.show() @@ -145,12 +150,6 @@ def collect_unique_keys(sources): return sorted(result) -def matching_keys(keys, patterns, regexp_match): - if regexp_match: - return { key for pattern in patterns for key in keys if re.search(pattern, key) } - return keys - - def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig, ax = matplotlib.pyplot.subplots() x = numpy.array(range(begin_frame, end_frame)) From 5f71afa274a896a568b206593906dbefad48df86 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 23 Feb 2022 01:57:37 +0100 Subject: [PATCH 2156/2859] Fix matlab deprecation warning scripts/osg_stats.py:163: MatplotlibDeprecationWarning: The set_window_title function was deprecated in Matplotlib 3.4 and will be removed two minor releases later. Use manager.set_window_title or GUI-specific methods instead. fig.canvas.set_window_title('timeseries') --- scripts/osg_stats.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index d74df94a5c..cc4314cd04 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -160,7 +160,7 @@ def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label=f'sum:{name}') ax.grid(True) ax.legend() - fig.canvas.set_window_title('timeseries') + fig.canvas.manager.set_window_title('timeseries') def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): @@ -173,7 +173,7 @@ def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): ax.plot(x, numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}') ax.grid(True) ax.legend() - fig.canvas.set_window_title('commulative_timeseries') + fig.canvas.manager.set_window_title('commulative_timeseries') def draw_hists(sources, keys): @@ -189,7 +189,7 @@ def draw_hists(sources, keys): ax.set_xticks(bins) ax.grid(True) ax.legend() - fig.canvas.set_window_title('hists') + fig.canvas.manager.set_window_title('hists') def draw_hist_ratio(sources, pairs): @@ -205,7 +205,7 @@ def draw_hist_ratio(sources, pairs): ax.set_xticks(bins) ax.grid(True) ax.legend() - fig.canvas.set_window_title('hists_ratio') + fig.canvas.manager.set_window_title('hists_ratio') def draw_stdev_hists(sources, stdev_hists): @@ -224,7 +224,7 @@ def draw_stdev_hists(sources, stdev_hists): ax.set_xticks(bins) ax.grid(True) ax.legend() - fig.canvas.set_window_title('stdev_hists') + fig.canvas.manager.set_window_title('stdev_hists') def draw_plots(sources, plots): @@ -249,7 +249,7 @@ def draw_plots(sources, plots): ) ax.grid(True) ax.legend() - fig.canvas.set_window_title('plots') + fig.canvas.manager.set_window_title('plots') def print_stats(sources, keys, stats_sum, precision): @@ -285,7 +285,7 @@ def draw_hist_threshold(sources, keys, begin_frame, threshold_name, threshold_va ax.xaxis.set_major_formatter(matplotlib.pyplot.FixedFormatter(numbers)) ax.grid(True) ax.legend() - fig.canvas.set_window_title(f'hist_threshold:{name}') + fig.canvas.manager.set_window_title(f'hist_threshold:{name}') def filter_not_none(values): From fdd9e6a79386d498a8ae2bca45567feb30e75a6e Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 23 Feb 2022 10:10:50 +0300 Subject: [PATCH 2157/2859] Always reduce weapon condition by the raw damage (bug #6559) --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwmechanics/combat.cpp | 18 +++++++++++------- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ebd3b07e3..0e98247686 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ Bug #6519: Effects tooltips for ingredients work incorrectly Bug #6523: Disintegrate Weapon is resisted by Resist Magicka instead of Sanctuary Bug #6544: Far from world origin objects jitter when camera is still + Bug #6559: Weapon condition inconsistency between melee and ranged critical / sneak / KO attacks Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index ba13f5f6d9..49b3627952 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -305,8 +305,8 @@ namespace MWClass { damage = attack[0] + ((attack[1]-attack[0])*attackStrength); MWMechanics::adjustWeaponDamage(damage, weapon, ptr); - MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); + MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); } // Apply "On hit" enchanted weapons diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4cf191de9c..bfae090545 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -607,9 +607,9 @@ namespace MWClass damage = attack[0] + ((attack[1]-attack[0])*attackStrength); } MWMechanics::adjustWeaponDamage(damage, weapon, ptr); + MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); MWMechanics::applyWerewolfDamageMult(victim, weapon, damage); - MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); healthdmg = true; } else diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 2962a25ede..0eccd6688c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -200,17 +200,17 @@ namespace MWMechanics bool validVictim = !victim.isEmpty() && victim.getClass().isActor(); + int weaponSkill = ESM::Skill::Marksman; + if (!weapon.isEmpty()) + weaponSkill = weapon.getClass().getEquipmentSkill(weapon); + float damage = 0.f; if (validVictim) { if (attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); - int weaponSkill = ESM::Skill::Marksman; - if (!weapon.isEmpty()) - weaponSkill = weapon.getClass().getEquipmentSkill(weapon); - - int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); + int skillValue = attacker.getClass().getSkill(attacker, weaponSkill); if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) { @@ -228,6 +228,12 @@ namespace MWMechanics damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); adjustWeaponDamage(damage, weapon, attacker); + } + + reduceWeaponCondition(damage, validVictim, weapon, attacker); + + if (validVictim) + { if (weapon == projectile || Settings::Manager::getBool("only appropriate ammunition bypasses resistance", "Game") || isNormalWeapon(weapon)) resistNormalWeapon(victim, attacker, projectile, damage); applyWerewolfDamageMult(victim, projectile, damage); @@ -247,8 +253,6 @@ namespace MWMechanics } } - reduceWeaponCondition(damage, validVictim, weapon, attacker); - // Apply "On hit" effect of the projectile bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true); From 3c66a927d83575522ecd1dfd8828e3ea3db8eff5 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 23 Feb 2022 20:06:29 +0100 Subject: [PATCH 2158/2859] Fix coverity issue about uninitialized members of LevelledListBase * Remove explicit constructor. * Use static constexpr where possible. * Use CRTP to get RecName. --- components/esm3/loadlevlist.cpp | 12 ++++-------- components/esm3/loadlevlist.hpp | 34 +++++++++++++++++---------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 1de278761b..2aa209fadb 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -6,7 +6,7 @@ namespace ESM { - void LevelledListBase::load(ESMReader &esm, bool &isDeleted) + void LevelledListBase::load(ESMReader& esm, ESM::NAME recName, bool& isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); @@ -43,7 +43,7 @@ namespace ESM for (size_t i = 0; i < mList.size(); i++) { LevelItem &li = mList[i]; - li.mId = esm.getHNString(mRecName); + li.mId = esm.getHNString(recName); esm.getHNT(li.mLevel, "INTV"); } @@ -75,7 +75,7 @@ namespace ESM esm.fail("Missing NAME subrecord"); } - void LevelledListBase::save(ESMWriter &esm, bool isDeleted) const + void LevelledListBase::save(ESMWriter& esm, ESM::NAME recName, bool isDeleted) const { esm.writeHNCString("NAME", mId); @@ -91,7 +91,7 @@ namespace ESM for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { - esm.writeHNCString(mRecName, it->mId); + esm.writeHNCString(recName, it->mId); esm.writeHNT("INTV", it->mLevel); } } @@ -102,8 +102,4 @@ namespace ESM mChanceNone = 0; mList.clear(); } - - unsigned int CreatureLevList::sRecordId = REC_LEVC; - - unsigned int ItemLevList::sRecordId = REC_LEVI; } diff --git a/components/esm3/loadlevlist.hpp b/components/esm3/loadlevlist.hpp index aea85b20e0..3e75ad7c70 100644 --- a/components/esm3/loadlevlist.hpp +++ b/components/esm3/loadlevlist.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace ESM { @@ -27,10 +28,6 @@ struct LevelledListBase unsigned int mRecordFlags; std::string mId; - // Record name used to read references. Must be set before load() is - // called. - ESM::NAME mRecName; - struct LevelItem { std::string mId; @@ -39,18 +36,25 @@ struct LevelledListBase std::vector mList; - explicit LevelledListBase(ESM::NAME recName) : mRecName(recName) {} - - void load(ESMReader &esm, bool &isDeleted); - void save(ESMWriter &esm, bool isDeleted = false) const; + void load(ESMReader& esm, ESM::NAME recName, bool& isDeleted); + void save(ESMWriter& esm, ESM::NAME recName, bool isDeleted) const; void blank(); ///< Set record to default state (does not touch the ID). }; -struct CreatureLevList: LevelledListBase +template +struct CustomLevelledListBase : LevelledListBase { - static unsigned int sRecordId; + void load(ESMReader &esm, bool& isDeleted) { LevelledListBase::load(esm, Base::sRecName, isDeleted); } + void save(ESMWriter &esm, bool isDeleted = false) const { LevelledListBase::save(esm, Base::sRecName, isDeleted); } +}; + +struct CreatureLevList : CustomLevelledListBase +{ + /// Record name used to read references. + static constexpr ESM::NAME sRecName {"CNAM"}; + static constexpr RecNameInts sRecordId = RecNameInts::REC_LEVC; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "CreatureLevList"; } @@ -61,13 +65,13 @@ struct CreatureLevList: LevelledListBase // level, not just the closest below // player. }; - - CreatureLevList() : LevelledListBase("CNAM") {} }; -struct ItemLevList: LevelledListBase +struct ItemLevList : CustomLevelledListBase { - static unsigned int sRecordId; + /// Record name used to read references. + static constexpr ESM::NAME sRecName {"INAM"}; + static constexpr RecNameInts sRecordId = RecNameInts::REC_LEVI; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "ItemLevList"; } @@ -84,8 +88,6 @@ struct ItemLevList: LevelledListBase // level, not just the closest below // player. }; - - ItemLevList() : LevelledListBase("INAM") {} }; } From c0f095a4626b7269be32c5a54064633bcb819278 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 23 Feb 2022 22:02:28 +0100 Subject: [PATCH 2159/2859] Fix typos --- docs/source/reference/lua-scripting/overview.rst | 2 +- docs/source/reference/lua-scripting/user_interface.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index e07abee899..8fa255bac0 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -150,7 +150,7 @@ The order of lines determines the script load order (i.e. script priorities). Possible flags are: -- ``GLOBAL`` - a global script; always active, can not by stopped; +- ``GLOBAL`` - a global script; always active, can not be stopped; - ``CUSTOM`` - dynamic local script that can be started or stopped by a global script; - ``PLAYER`` - an auto started player script; - ``ACTIVATOR`` - a local script that will be automatically attached to any activator; diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index bb4a3a3d53..e00aa8bf63 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -23,7 +23,7 @@ Every widget is defined by a layout, which is a Lua table with the following fie Layers ------ -Layers control how widgets overlap - layers with higher indexes cover render over layers with lower indexes. +Layers control how widgets overlap - layers with higher indexes render over layers with lower indexes. Widgets within the same layer which were added later overlap the ones created earlier. A layer can also be set as non-interactive, which prevents all mouse interactions with the widgets in that layer. @@ -120,7 +120,7 @@ Example end -- we are showing game time in hours and minutes - -- so no need to update more often than ones a game minute + -- so no need to update more often than once a game minute time.runRepeatedly(updateTime, 1 * time.minute, { type = time.GameTime }) *clock.omwscripts* From 4a0b7846993b439b7925f619b6c31e7d5c195539 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 23 Feb 2022 22:08:50 +0100 Subject: [PATCH 2160/2859] Use reasonable Luadoc comment headers --- files/lua_api/openmw/async.lua | 14 +++--- files/lua_api/openmw/camera.lua | 60 +++++++++++------------ files/lua_api/openmw/core.lua | 84 ++++++++++++++++----------------- files/lua_api/openmw/input.lua | 54 ++++++++++----------- files/lua_api/openmw/nearby.lua | 22 ++++----- files/lua_api/openmw/query.lua | 42 ++++++++--------- files/lua_api/openmw/self.lua | 12 ++--- files/lua_api/openmw/world.lua | 22 ++++----- 8 files changed, 155 insertions(+), 155 deletions(-) diff --git a/files/lua_api/openmw/async.lua b/files/lua_api/openmw/async.lua index 61d5763a0b..64080ed96a 100644 --- a/files/lua_api/openmw/async.lua +++ b/files/lua_api/openmw/async.lua @@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +--- -- `openmw.async` contains timers and coroutine utils. All functions require -- the package itself as a first argument. -- @module async @@ -6,7 +6,7 @@ -------------------------------------------------------------------------------- +--- -- Register a function as a timer callback. -- @function [parent=#async] registerTimerCallback -- @param self @@ -14,7 +14,7 @@ -- @param #function func -- @return #TimerCallback -------------------------------------------------------------------------------- +--- -- Calls callback(arg) in `delay` simulation seconds. -- Callback must be registered in advance. -- @function [parent=#async] newSimulationTimer @@ -23,7 +23,7 @@ -- @param #TimerCallback callback A callback returned by `registerTimerCallback` -- @param arg An argument for `callback`; can be `nil`. -------------------------------------------------------------------------------- +--- -- Calls callback(arg) in `delay` game seconds. -- Callback must be registered in advance. -- @function [parent=#async] newGameTimer @@ -32,7 +32,7 @@ -- @param #TimerCallback callback A callback returned by `registerTimerCallback` -- @param arg An argument for `callback`; can be `nil`. -------------------------------------------------------------------------------- +--- -- Calls `func()` in `delay` simulation seconds. -- The timer will be lost if the game is saved and loaded. -- @function [parent=#async] newUnsavableSimulationTimer @@ -40,7 +40,7 @@ -- @param #number delay -- @param #function func -------------------------------------------------------------------------------- +--- -- Calls `func()` in `delay` game seconds. -- The timer will be lost if the game is saved and loaded. -- @function [parent=#async] newUnsavableGameTimer @@ -48,7 +48,7 @@ -- @param #number delay -- @param #function func -------------------------------------------------------------------------------- +--- -- Wraps Lua function with `Callback` object that can be used in async API calls. -- @function [parent=#async] callback -- @param self diff --git a/files/lua_api/openmw/camera.lua b/files/lua_api/openmw/camera.lua index 0d72214414..1d09bdf7ad 100644 --- a/files/lua_api/openmw/camera.lua +++ b/files/lua_api/openmw/camera.lua @@ -1,11 +1,11 @@ -------------------------------------------------------------------------------- +--- -- `openmw.camera` controls camera. -- Can be used only by player scripts. -- @module camera -- @usage local camera = require('openmw.camera') -------------------------------------------------------------------------------- +--- -- @type MODE Camera modes. -- @field #number Static Camera doesn't track player; player inputs doesn't affect camera; use `setStaticPosition` to move the camera. -- @field #number FirstPerson First person mode. @@ -13,126 +13,126 @@ -- @field #number Vanity Similar to Preview; camera slowly moves around the player. -- @field #number Preview Third person mode, but player character doesn't turn to the view direction. -------------------------------------------------------------------------------- +--- -- Camera modes. -- @field [parent=#camera] #MODE MODE -------------------------------------------------------------------------------- +--- -- Return the current @{openmw.camera#MODE}. -- @function [parent=#camera] getMode -- @return #MODE -------------------------------------------------------------------------------- +--- -- Return the mode the camera will switch to after the end of the current animation. Can be nil. -- @function [parent=#camera] getQueuedMode -- @return #MODE -------------------------------------------------------------------------------- +--- -- Change @{openmw.camera#MODE}; if the second (optional, true by default) argument is set to false, the switching can be delayed (see `getQueuedMode`). -- @function [parent=#camera] setMode -- @param #MODE mode -- @param #boolean force -------------------------------------------------------------------------------- +--- -- If set to true then after switching from Preview to ThirdPerson the player character turns to the camera view direction. Otherwise the camera turns to the character view direction. -- @function [parent=#camera] allowCharacterDeferredRotation -- @param #boolean boolValue -------------------------------------------------------------------------------- +--- -- Show/hide crosshair. -- @function [parent=#camera] showCrosshair -- @param #boolean boolValue -------------------------------------------------------------------------------- +--- -- Current position of the tracked object (the characters head if there is no animation). -- @function [parent=#camera] getTrackedPosition -- @return openmw.util#Vector3 -------------------------------------------------------------------------------- +--- -- Current position of the camera. -- @function [parent=#camera] getPosition -- @return openmw.util#Vector3 -------------------------------------------------------------------------------- +--- -- Camera pitch angle (radians) without taking extraPitch into account. -- Full pitch is `getPitch()+getExtraPitch()`. -- @function [parent=#camera] getPitch -- @return #number -------------------------------------------------------------------------------- +--- -- Force the pitch angle to the given value (radians); player input on this axis is ignored in this frame. -- @function [parent=#camera] setPitch -- @param #number value -------------------------------------------------------------------------------- +--- -- Camera yaw angle (radians) without taking extraYaw into account. -- Full yaw is `getYaw()+getExtraYaw()`. -- @function [parent=#camera] getYaw -- @return #number -------------------------------------------------------------------------------- +--- -- Force the yaw angle to the given value (radians); player input on this axis is ignored in this frame. -- @function [parent=#camera] setYaw -- @param #number value -------------------------------------------------------------------------------- +--- -- Get camera roll angle (radians). -- @function [parent=#camera] getRoll -- @return #number -------------------------------------------------------------------------------- +--- -- Set camera roll angle (radians). -- @function [parent=#camera] setRoll -- @param #number value -------------------------------------------------------------------------------- +--- -- Additional summand for the pitch angle that is not affected by player input. -- Full pitch is `getPitch()+getExtraPitch()`. -- @function [parent=#camera] getExtraPitch -- @return #number -------------------------------------------------------------------------------- +--- -- Additional summand for the pitch angle; useful for camera shaking effects. -- Setting extra pitch doesn't block player input. -- Full pitch is `getPitch()+getExtraPitch()`. -- @function [parent=#camera] setExtraPitch -- @param #number value -------------------------------------------------------------------------------- +--- -- Additional summand for the yaw angle that is not affected by player input. -- Full yaw is `getYaw()+getExtraYaw()`. -- @function [parent=#camera] getExtraYaw -- @return #number -------------------------------------------------------------------------------- +--- -- Additional summand for the yaw angle; useful for camera shaking effects. -- Setting extra pitch doesn't block player input. -- Full yaw is `getYaw()+getExtraYaw()`. -- @function [parent=#camera] setExtraYaw -- @param #number value -------------------------------------------------------------------------------- +--- -- Set camera position; can be used only if camera is in Static mode. -- @function [parent=#camera] setStaticPosition -- @param openmw.util#Vector3 pos -------------------------------------------------------------------------------- +--- -- The offset between the characters head and the camera in first person mode (3d vector). -- @function [parent=#camera] getFirstPersonOffset -- @return openmw.util#Vector3 -------------------------------------------------------------------------------- +--- -- Set the offset between the characters head and the camera in first person mode (3d vector). -- @function [parent=#camera] setFirstPersonOffset -- @param openmw.util#Vector3 offset -------------------------------------------------------------------------------- +--- -- Preferred offset between tracked position (see `getTrackedPosition`) and the camera focal point (the center of the screen) in third person mode. -- See `setFocalPreferredOffset`. -- @function [parent=#camera] getFocalPreferredOffset -- @return openmw.util#Vector2 -------------------------------------------------------------------------------- +--- -- Set preferred offset between tracked position (see `getTrackedPosition`) and the camera focal point (the center of the screen) in third person mode. -- The offset is a 2d vector (X, Y) where X is horizontal (to the right from the character) and Y component is vertical (upward). -- The real offset can differ from the preferred one during smooth transition of if blocked by an obstacle. @@ -140,29 +140,29 @@ -- @function [parent=#camera] setFocalPreferredOffset -- @param openmw.util#Vector2 offset -------------------------------------------------------------------------------- +--- -- The actual distance between the camera and the character in third person mode; can differ from the preferred one if there is an obstacle. -- @function [parent=#camera] getThirdPersonDistance -- @return #number -------------------------------------------------------------------------------- +--- -- Set preferred distance between the camera and the character in third person mode. -- @function [parent=#camera] setPreferredThirdPersonDistance -- @param #number distance -------------------------------------------------------------------------------- +--- -- The current speed coefficient of focal point (the center of the screen in third person mode) smooth transition. -- @function [parent=#camera] getFocalTransitionSpeed -- @return #number -------------------------------------------------------------------------------- +--- -- Set the speed coefficient of focal point (the center of the screen in third person mode) smooth transition. -- Smooth transition happens by default every time when the preferred offset was changed. Use `instantTransition()` to skip the current transition. -- @function [parent=#camera] setFocalTransitionSpeed -- Set the speed coefficient -- @param #number speed -------------------------------------------------------------------------------- +--- -- Make instant the current transition of camera focal point and the current deferred rotation (see `allowCharacterDeferredRotation`). -- @function [parent=#camera] instantTransition diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 84e2b4faae..d57b6a405e 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +--- -- `openmw.core` defines functions and types that are available in both local -- and global scripts. -- @module core @@ -6,53 +6,53 @@ -------------------------------------------------------------------------------- +--- -- The revision of OpenMW Lua API. It is an integer that is incremented every time the API is changed. -- @field [parent=#core] #number API_REVISION -------------------------------------------------------------------------------- +--- -- Terminates the game and quits to the OS. Should be used only for testing purposes. -- @function [parent=#core] quit -------------------------------------------------------------------------------- +--- -- Send an event to global scripts. -- @function [parent=#core] sendGlobalEvent -- @param #string eventName -- @param eventData -------------------------------------------------------------------------------- +--- -- Simulation time in seconds. -- The number of simulation seconds passed in the game world since starting a new game. -- @function [parent=#core] getSimulationTime -- @return #number -------------------------------------------------------------------------------- +--- -- The scale of simulation time relative to real time. -- @function [parent=#core] getSimulationTimeScale -- @return #number -------------------------------------------------------------------------------- +--- -- Game time in seconds. -- @function [parent=#core] getGameTime -- @return #number -------------------------------------------------------------------------------- +--- -- The scale of game time relative to simulation time. -- @function [parent=#core] getGameTimeScale -- @return #number -------------------------------------------------------------------------------- +--- -- Whether the world is paused (onUpdate doesn't work when the world is paused). -- @function [parent=#core] isWorldPaused -- @return #boolean -------------------------------------------------------------------------------- +--- -- Get a GMST setting from content files. -- @function [parent=#core] getGMST -- @param #string setting Setting name -- @return #any -------------------------------------------------------------------------------- +--- -- Return i18n formatting function for the given context. -- It is based on `i18n.lua` library. -- Language files should be stored in VFS as `i18n//.lua`. @@ -86,7 +86,7 @@ -- print( myMsg('Hello %{name}!', {name='World'}) ) -------------------------------------------------------------------------------- +--- -- @type OBJECT_TYPE -- @field #string Activator "Activator" -- @field #string Armor "Armor" @@ -107,12 +107,12 @@ -- @field #string Probe "Probe" -- @field #string Repair "Repair" -------------------------------------------------------------------------------- +--- -- Possible `object.type` values. -- @field [parent=#core] #OBJECT_TYPE OBJECT_TYPE -------------------------------------------------------------------------------- +--- -- @type EQUIPMENT_SLOT -- @field #number Helmet -- @field #number Cuirass @@ -134,12 +134,12 @@ -- @field #number CarriedLeft -- @field #number Ammunition -------------------------------------------------------------------------------- +--- -- Available equipment slots. Used in `object:getEquipment` and `object:setEquipment`. -- @field [parent=#core] #EQUIPMENT_SLOT EQUIPMENT_SLOT -------------------------------------------------------------------------------- +--- -- Any object that exists in the game world and has a specific location. -- Player, actors, items, and statics are game objects. -- @type GameObject @@ -155,7 +155,7 @@ -- @field openmw.util#Vector3 destRotation Destination rotation (only if a teleport door). -- @field #string destCell Destination cell (only if a teleport door). -------------------------------------------------------------------------------- +--- -- Is the object still exists/available. -- Returns true if the object exists and loaded, and false otherwise. If false, then every -- access to the object will raise an error. @@ -163,7 +163,7 @@ -- @param self -- @return #boolean -------------------------------------------------------------------------------- +--- -- Returns true if the object is an actor and is able to move. For dead, paralized, -- or knocked down actors in returns false. -- access to the object will raise an error. @@ -171,56 +171,56 @@ -- @param self -- @return #boolean -------------------------------------------------------------------------------- +--- -- Speed of running. Returns 0 if not an actor, but for dead actors it still returns a positive value. -- @function [parent=#GameObject] getRunSpeed -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Speed of walking. Returns 0 if not an actor, but for dead actors it still returns a positive value. -- @function [parent=#GameObject] getWalkSpeed -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Current speed. Can be called only from a local script. -- @function [parent=#GameObject] getCurrentSpeed -- @param self -- @return #number -------------------------------------------------------------------------------- +--- -- Is the actor standing on ground. Can be called only from a local script. -- @function [parent=#GameObject] isOnGround -- @param self -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is the actor in water. Can be called only from a local script. -- @function [parent=#GameObject] isSwimming -- @param self -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is the actor in weapon stance. Can be called only from a local script. -- @function [parent=#GameObject] isInWeaponStance -- @param self -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is the actor in magic stance. Can be called only from a local script. -- @function [parent=#GameObject] isInMagicStance -- @param self -- @return #boolean -------------------------------------------------------------------------------- +--- -- Send local event to the object. -- @function [parent=#GameObject] sendEvent -- @param self -- @param #string eventName -- @param eventData -------------------------------------------------------------------------------- +--- -- Activate the object. -- @function [parent=#GameObject] activateBy -- @param self @@ -228,14 +228,14 @@ -- @usage local self = require('openmw.self') -- object:activateBy(self) -------------------------------------------------------------------------------- +--- -- Returns `true` if the item is equipped on the object. -- @function [parent=#GameObject] isEquipped -- @param self -- @param #GameObject item -- @return #boolean -------------------------------------------------------------------------------- +--- -- Get equipment. -- Returns a table `slot` -> `GameObject` of currently equipped items. -- See @{openmw.core#EQUIPMENT_SLOT}. Returns empty table if the object doesn't have @@ -244,7 +244,7 @@ -- @param self -- @return #map<#number,#GameObject> -------------------------------------------------------------------------------- +--- -- Set equipment. -- Keys in the table are equipment slots (see @{openmw.core#EQUIPMENT_SLOT}). Each -- value can be either a `GameObject` or recordId. Raises an error if @@ -254,7 +254,7 @@ -- @param self -- @param equipment -------------------------------------------------------------------------------- +--- -- Add new local script to the object. -- Can be called only from a global script. Script should be specified in a content -- file (omwgame/omwaddon/omwscripts) with a CUSTOM flag. @@ -262,7 +262,7 @@ -- @param self -- @param #string scriptPath Path to the script in OpenMW virtual filesystem. -------------------------------------------------------------------------------- +--- -- Whether a script with given path is attached to this object. -- Can be called only from a global script. -- @function [parent=#GameObject] hasScript @@ -270,14 +270,14 @@ -- @param #string scriptPath Path to the script in OpenMW virtual filesystem. -- @return #boolean -------------------------------------------------------------------------------- +--- -- Removes script that was attached by `addScript` -- Can be called only from a global script. -- @function [parent=#GameObject] removeScript -- @param self -- @param #string scriptPath Path to the script in OpenMW virtual filesystem. -------------------------------------------------------------------------------- +--- -- Moves object to given cell and position. -- The effect is not immediate: the position will be updated only in the next -- frame. Can be called only from a global script. @@ -288,12 +288,12 @@ -- @param openmw.util#Vector3 rotation New rotation. Optional argument. If missed, then the current rotation is used. -------------------------------------------------------------------------------- +--- -- List of GameObjects. Implements [iterables#List](iterables.html#List) of #GameObject -- @type ObjectList -- @list <#GameObject> -------------------------------------------------------------------------------- +--- -- Filter list with a Query. -- @function [parent=#ObjectList] select -- @param self @@ -301,7 +301,7 @@ -- @return #ObjectList -------------------------------------------------------------------------------- +--- -- A cell of the game world. -- @type Cell -- @field #string name Name of the cell (can be empty string). @@ -311,7 +311,7 @@ -- @field #number gridY Index of the cell by Y (only for exteriors). -- @field #boolean hasWater True if the cell contains water. -------------------------------------------------------------------------------- +--- -- Returns true either if the cell contains the object or if the cell is an exterior and the object is also in an exterior. -- @function [parent=#Cell] isInSameSpace -- @param self @@ -324,7 +324,7 @@ -- -- the distance can't be calculated because the coordinates are in different spaces -- end -------------------------------------------------------------------------------- +--- -- Select objects from the cell with a Query (only in global scripts). -- Returns an empty list if the cell is not loaded. -- @function [parent=#Cell] selectObjects @@ -333,18 +333,18 @@ -- @return #ObjectList -------------------------------------------------------------------------------- +--- -- Inventory of a player/NPC or a content of a container. -- @type Inventory -------------------------------------------------------------------------------- +--- -- The number of items with given recordId. -- @function [parent=#Inventory] countOf -- @param self -- @param #string recordId -- @return #number -------------------------------------------------------------------------------- +--- -- Get all items of given type from the inventory. -- @function [parent=#Inventory] getAll -- @param self diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 5ebaa46e1f..12ea5c94b3 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -1,95 +1,95 @@ -------------------------------------------------------------------------------- +--- -- `openmw.input` can be used only in scripts attached to a player. -- @module input -- @usage local input = require('openmw.input') -------------------------------------------------------------------------------- +--- -- Is player idle. -- @function [parent=#input] isIdle -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is a specific control currently pressed. -- Input bindings can be changed ingame using Options/Controls menu. -- @function [parent=#input] isActionPressed -- @param #number actionId One of @{openmw.input#ACTION} -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is a keyboard button currently pressed. -- @function [parent=#input] isKeyPressed -- @param #number keyCode Key code (see @{openmw.input#KEY}) -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is a controller button currently pressed. -- @function [parent=#input] isControllerButtonPressed -- @param #number buttonId Button index (see @{openmw.input#CONTROLLER_BUTTON}) -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is `Shift` key pressed. -- @function [parent=#input] isShiftPressed -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is `Ctrl` key pressed. -- @function [parent=#input] isCtrlPressed -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is `Alt` key pressed. -- @function [parent=#input] isAltPressed -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is `Super`/`Win` key pressed. -- @function [parent=#input] isSuperPressed -- @return #boolean -------------------------------------------------------------------------------- +--- -- Is a mouse button currently pressed. -- @function [parent=#input] isMouseButtonPressed -- @param #number buttonId Button index (1 - left, 2 - middle, 3 - right, 4 - X1, 5 - X2) -- @return #boolean -------------------------------------------------------------------------------- +--- -- Horizontal mouse movement during the last frame. -- @function [parent=#input] getMouseMoveX -- @return #number -------------------------------------------------------------------------------- +--- -- Vertical mouse movement during the last frame. -- @function [parent=#input] getMouseMoveY -- @return #number -------------------------------------------------------------------------------- +--- -- Get value of an axis of a game controller. -- @function [parent=#input] getAxisValue -- @param #number axisId Index of a controller axis, one of @{openmw.input#CONTROLLER_AXIS}. -- @return #number Value in range [-1, 1]. -------------------------------------------------------------------------------- +--- -- Get state of a control switch. I.e. is player able to move/fight/jump/etc. -- @function [parent=#input] getControlSwitch -- @param #string key Control type (see @{openmw.input#CONTROL_SWITCH}) -- @return #boolean -------------------------------------------------------------------------------- +--- -- Set state of a control switch. I.e. forbid or allow player to move/fight/jump/etc. -- @function [parent=#input] setControlSwitch -- @param #string key Control type (see @{openmw.input#CONTROL_SWITCH}) -- @param #boolean value -------------------------------------------------------------------------------- +--- -- Returns a human readable name for the given key code -- @function [parent=#input] getKeyName -- @param #number code A key code (see @{openmw.input#KEY}) -- @return #string -------------------------------------------------------------------------------- +--- -- @type CONTROL_SWITCH -- @field [parent=#CONTROL_SWITCH] #string Controls Ability to move -- @field [parent=#CONTROL_SWITCH] #string Fighting Ability to attack @@ -99,11 +99,11 @@ -- @field [parent=#CONTROL_SWITCH] #string ViewMode Ability to toggle 1st/3rd person view -- @field [parent=#CONTROL_SWITCH] #string VanityMode Vanity view if player doesn't touch controls for a long time -------------------------------------------------------------------------------- +--- -- Values that can be used with getControlSwitch/setControlSwitch. -- @field [parent=#input] #CONTROL_SWITCH CONTROL_SWITCH -------------------------------------------------------------------------------- +--- -- @type ACTION -- @field [parent=#ACTION] #number GameMenu -- @field [parent=#ACTION] #number Screenshot @@ -150,11 +150,11 @@ -- @field [parent=#ACTION] #number ZoomIn -- @field [parent=#ACTION] #number ZoomOut -------------------------------------------------------------------------------- +--- -- Values that can be used with isActionPressed. -- @field [parent=#input] #ACTION ACTION -------------------------------------------------------------------------------- +--- -- @type CONTROLLER_BUTTON -- @field [parent=#CONTROLLER_BUTTON] #number A -- @field [parent=#CONTROLLER_BUTTON] #number B @@ -172,11 +172,11 @@ -- @field [parent=#CONTROLLER_BUTTON] #number DPadLeft -- @field [parent=#CONTROLLER_BUTTON] #number DPadRight -------------------------------------------------------------------------------- +--- -- Values that can be passed to onControllerButtonPress/onControllerButtonRelease engine handlers. -- @field [parent=#input] #CONTROLLER_BUTTON CONTROLLER_BUTTON -------------------------------------------------------------------------------- +--- -- Ids of game controller axises. Used as an argument in getAxisValue. -- @type CONTROLLER_AXIS -- @field [parent=#CONTROLLER_AXIS] #number LeftX Left stick horizontal axis (from -1 to 1) @@ -190,11 +190,11 @@ -- @field [parent=#CONTROLLER_AXIS] #number MoveForwardBackward Movement forward/backward (LeftY by default, can be mapped to another axis in Options/Controls menu) -- @field [parent=#CONTROLLER_AXIS] #number MoveLeftRight Side movement (LeftX by default, can be mapped to another axis in Options/Controls menu) -------------------------------------------------------------------------------- +--- -- Values that can be used with getAxisValue. -- @field [parent=#input] #CONTROLLER_AXIS CONTROLLER_AXIS -------------------------------------------------------------------------------- +--- -- @type KEY -- @field #number _0 -- @field #number _1 @@ -299,11 +299,11 @@ -- @field #number Space -- @field #number Tab -------------------------------------------------------------------------------- +--- -- Key codes. -- @field [parent=#input] #KEY KEY -------------------------------------------------------------------------------- +--- -- The argument of `onKeyPress`/`onKeyRelease` engine handlers. -- @type KeyboardEvent -- @field [parent=#KeyboardEvent] #string symbol The pressed symbol (1-symbol string if can be represented or an empty string otherwise). diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index f1aeb07b24..c3403ded0c 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +--- -- `openmw.nearby` provides read-only access to the nearest area of the game world. -- Can be used only from local scripts. -- @module nearby @@ -6,33 +6,33 @@ -------------------------------------------------------------------------------- +--- -- List of nearby activators. -- @field [parent=#nearby] openmw.core#ObjectList activators -------------------------------------------------------------------------------- +--- -- List of nearby actors. -- @field [parent=#nearby] openmw.core#ObjectList actors -------------------------------------------------------------------------------- +--- -- List of nearby containers. -- @field [parent=#nearby] openmw.core#ObjectList containers -------------------------------------------------------------------------------- +--- -- List of nearby doors. -- @field [parent=#nearby] openmw.core#ObjectList doors -------------------------------------------------------------------------------- +--- -- Everything that can be picked up in the nearby. -- @field [parent=#nearby] openmw.core#ObjectList items -------------------------------------------------------------------------------- +--- -- Evaluates a Query. -- @function [parent=#nearby] selectObjects -- @param openmw.query#Query query -- @return openmw.core#ObjectList -------------------------------------------------------------------------------- +--- -- @type COLLISION_TYPE -- @field [parent=#COLLISION_TYPE] #number World -- @field [parent=#COLLISION_TYPE] #number Door @@ -42,12 +42,12 @@ -- @field [parent=#COLLISION_TYPE] #number Water -- @field [parent=#COLLISION_TYPE] #number Default Used by deafult: World+Door+Actor+HeightMap -------------------------------------------------------------------------------- +--- -- Collision types that are used in `castRay`. -- Several types can be combined with '+'. -- @field [parent=#nearby] #COLLISION_TYPE COLLISION_TYPE -------------------------------------------------------------------------------- +--- -- Result of raycasing -- @type RayCastingResult -- @field [parent=#RayCastingResult] #boolean hit Is there a collision? (true/false) @@ -55,7 +55,7 @@ -- @field [parent=#RayCastingResult] openmw.util#Vector3 hitNormal Normal to the surface in the collision point (nil if no collision) -- @field [parent=#RayCastingResult] openmw.core#GameObject hitObject The object the ray has collided with (can be nil) -------------------------------------------------------------------------------- +--- -- Cast ray from one point to another and return the first collision. -- @function [parent=#nearby] castRay -- @param openmw.util#Vector3 from Start point of the ray. diff --git a/files/lua_api/openmw/query.lua b/files/lua_api/openmw/query.lua index 9684fb372c..b5cea061ff 100644 --- a/files/lua_api/openmw/query.lua +++ b/files/lua_api/openmw/query.lua @@ -1,22 +1,22 @@ -------------------------------------------------------------------------------- +--- -- `openmw.query` constructs queries that can be used in `world.selectObjects` and `nearby.selectObjects`. -- @module query -- @usage local query = require('openmw.query') -------------------------------------------------------------------------------- +--- -- Query. A Query itself can no return objects. It only holds search conditions. -- @type Query -------------------------------------------------------------------------------- +--- -- Add condition. -- @function [parent=#Query] where -- @param self -- @param condition -- @return #Query -------------------------------------------------------------------------------- +--- -- Limit result size. -- @function [parent=#Query] limit -- @param self @@ -24,91 +24,91 @@ -- @return #Query -------------------------------------------------------------------------------- +--- -- A field that can be used in a condition -- @type Field -------------------------------------------------------------------------------- +--- -- Equal -- @function [parent=#Field] eq -- @param self -- @param value -------------------------------------------------------------------------------- +--- -- Not equal -- @function [parent=#Field] neq -- @param self -- @param value -------------------------------------------------------------------------------- +--- -- Lesser -- @function [parent=#Field] lt -- @param self -- @param value -------------------------------------------------------------------------------- +--- -- Lesser or equal -- @function [parent=#Field] lte -- @param self -- @param value -------------------------------------------------------------------------------- +--- -- Greater -- @function [parent=#Field] gt -- @param self -- @param value -------------------------------------------------------------------------------- +--- -- Greater or equal -- @function [parent=#Field] gte -- @param self -- @param value -------------------------------------------------------------------------------- +--- -- @type OBJECT -- @field [parent=#OBJECT] #Field type -- @field [parent=#OBJECT] #Field recordId -- @field [parent=#OBJECT] #Field count -- @field [parent=#OBJECT] #CellFields cell -------------------------------------------------------------------------------- +--- -- Fields that can be used with any object. -- @field [parent=#query] #OBJECT OBJECT -------------------------------------------------------------------------------- +--- -- @type DOOR -- @field [parent=#DOOR] #Field isTeleport -- @field [parent=#DOOR] #CellFields destCell -------------------------------------------------------------------------------- +--- -- Fields that can be used only when search for doors. -- @field [parent=#query] #DOOR DOOR -------------------------------------------------------------------------------- +--- -- @type CellFields -- @field [parent=#CellFields] #Field name -- @field [parent=#CellFields] #Field region -- @field [parent=#CellFields] #Field isExterior -------------------------------------------------------------------------------- +--- -- Base Query to select activators. -- @field [parent=#query] #Query activators -------------------------------------------------------------------------------- +--- -- Base Query to select actors. -- @field [parent=#query] #Query actors -------------------------------------------------------------------------------- +--- -- Base Query to select containers. -- @field [parent=#query] #Query containers -------------------------------------------------------------------------------- +--- -- Base Query to select doors. -- @field [parent=#query] #Query doors -------------------------------------------------------------------------------- +--- -- Base Query to select items. -- @field [parent=#query] #Query items diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index d6f01412eb..06be20ee64 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +--- -- `openmw.self` provides full access to the object the script is attached to. -- Can be used only from local scripts. All fields and function of `GameObject` are also available for `openmw.self`. -- @module self @@ -10,22 +10,22 @@ -------------------------------------------------------------------------------- +--- -- Returns true if the script isActive (the object it is attached to is in an active cell). -- If it is not active, then `openmw.nearby` can not be used. -- @function [parent=#self] isActive -- @param self -- @return #boolean -------------------------------------------------------------------------------- +--- -- The object the script is attached to (readonly) -- @field [parent=#self] openmw.core#GameObject object -------------------------------------------------------------------------------- +--- -- Movement controls (only for actors) -- @field [parent=#self] #ActorControls controls -------------------------------------------------------------------------------- +--- -- Allows to view and/or modify controls of an actor. All fields are mutable. -- @type ActorControls -- @field [parent=#ActorControls] #number movement +1 - move forward, -1 - move backward @@ -36,7 +36,7 @@ -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump -- @field [parent=#ActorControls] #number use if 1 - activates the readied weapon/spell. For weapons, keeping at 1 will charge the attack until set to 0. -------------------------------------------------------------------------------- +--- -- Enables or disables standard AI (enabled by default). -- @function [parent=#self] enableAI -- @param self diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 690f0bd566..127c41c064 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +--- -- `openmw.world` is an interface to the game world for global scripts. -- Can not be used from local scripts. -- @module world @@ -6,56 +6,56 @@ -------------------------------------------------------------------------------- +--- -- List of currently active actors. -- @field [parent=#world] openmw.core#ObjectList activeActors -------------------------------------------------------------------------------- +--- -- Evaluates a Query. -- @function [parent=#world] selectObjects -- @param openmw.query#Query query -- @return openmw.core#ObjectList -------------------------------------------------------------------------------- +--- -- Loads a named cell -- @function [parent=#world] getCellByName -- @param #string cellName -- @return openmw.core#Cell -------------------------------------------------------------------------------- +--- -- Loads an exterior cell by grid indices -- @function [parent=#world] getExteriorCell -- @param #number gridX -- @param #number gridY -- @return openmw.core#Cell -------------------------------------------------------------------------------- +--- -- Simulation time in seconds. -- The number of simulation seconds passed in the game world since starting a new game. -- @function [parent=#core] getSimulationTime -- @return #number -------------------------------------------------------------------------------- +--- -- The scale of simulation time relative to real time. -- @function [parent=#core] getSimulationTimeScale -- @return #number -------------------------------------------------------------------------------- +--- -- Game time in seconds. -- @function [parent=#core] getGameTime -- @return #number -------------------------------------------------------------------------------- +--- -- The scale of game time relative to simulation time. -- @function [parent=#core] getGameTimeScale -- @return #number -------------------------------------------------------------------------------- +--- -- Set the ratio of game time speed to simulation time speed. -- @function [parent=#world] setGameTimeScale -- @param #number ratio -------------------------------------------------------------------------------- +--- -- Whether the world is paused (onUpdate doesn't work when the world is paused). -- @function [parent=#world] isWorldPaused -- @return #boolean From 62aac3cc772eec4e54fa4745f7dd3bd2aacf94c2 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 23 Feb 2022 22:09:08 +0100 Subject: [PATCH 2161/2859] Fix Lua Camera module declaration --- files/builtin_scripts/scripts/omw/camera.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index cd4d0dccfb..4b5911930e 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -172,7 +172,8 @@ end return { interfaceName = 'Camera', - --- @module Camera + --- + -- @module Camera -- @usage require('openmw.interfaces').Camera interface = { --- Interface version From 42d6032c8bbae732ac603529dfa671856c38772c Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 24 Feb 2022 00:24:41 +0100 Subject: [PATCH 2162/2859] Support compilation with c++20 --- apps/navmeshtool/navmesh.cpp | 2 +- apps/openmw/mwphysics/actor.cpp | 2 +- .../detournavigator/navmeshdb.cpp | 2 +- apps/openmw_test_suite/openmw/options.cpp | 17 +++++++-- .../detournavigator/asyncnavmeshupdater.cpp | 4 +- components/detournavigator/navmeshdb.hpp | 11 +++--- components/detournavigator/navmeshdbutils.cpp | 2 +- components/misc/strongtypedef.hpp | 38 +++++++++++++++++++ components/sceneutil/mwshadowtechnique.cpp | 2 +- 9 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 components/misc/strongtypedef.hpp diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 3161192cf9..8b85029949 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -137,7 +137,7 @@ namespace NavMeshTool { std::lock_guard lock(mMutex); mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data)); - ++mNextTileId.t; + ++mNextTileId; } ++mInserted; report(); diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 568da999bc..a1f5a48388 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -163,7 +163,7 @@ bool Actor::setPosition(const osg::Vec3f& position) { std::scoped_lock lock(mPositionMutex); applyOffsetChange(); - bool hasChanged = (mPosition != position && !mSkipSimulation) || mWorldPositionChanged; + bool hasChanged = (mPosition.operator!=(position) && !mSkipSimulation) || mWorldPositionChanged; if (!mSkipSimulation) { mPreviousPosition = mPosition; diff --git a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp index a17f5132c5..f7d9327e65 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp @@ -156,7 +156,7 @@ namespace for (int y = -2; y <= 2; ++y) { ASSERT_EQ(mDb.insertTile(tileId, worldspace, TilePosition {x, y}, version, input, data), 1); - ++tileId.t; + ++tileId; } } const TilesPositionsRange range {TilePosition {-1, -1}, TilePosition {2, 2}}; diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index 04c209e8a4..b65da5d157 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -17,9 +17,18 @@ namespace namespace bpo = boost::program_options; - std::vector generateSupportedCharacters(std::vector&& base = {}) + template + std::string makeString(const T (&range)[size]) { - std::vector result = std::move(base); + static_assert(size > 0); + return std::string(std::begin(range), std::end(range) - 1); + } + + template + std::vector generateSupportedCharacters(Args&& ... args) + { + std::vector result; + (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)); @@ -193,7 +202,7 @@ namespace INSTANTIATE_TEST_SUITE_P( SupportedCharacters, OpenMWOptionsFromArgumentsStrings, - ValuesIn(generateSupportedCharacters({u8"👍", u8"Ъ", u8"Ǽ", "\n"})) + ValuesIn(generateSupportedCharacters(u8"👍", u8"Ъ", u8"Ǽ", "\n")) ); TEST(OpenMWOptionsFromConfig, should_support_single_word_load_savegame_path) @@ -381,6 +390,6 @@ namespace INSTANTIATE_TEST_SUITE_P( SupportedCharacters, OpenMWOptionsFromConfigStrings, - ValuesIn(generateSupportedCharacters({u8"👍", u8"Ъ", u8"Ǽ"})) + ValuesIn(generateSupportedCharacters(u8"👍", u8"Ъ", u8"Ǽ")) ); } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 657befd9d7..4af229da8a 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -733,7 +733,7 @@ namespace DetourNavigator { Stats result; result.mJobs = mQueue.size(); - result.mGetTileCount = mGetTileCount.load(std::memory_order::memory_order_relaxed); + result.mGetTileCount = mGetTileCount.load(std::memory_order_relaxed); return result; } @@ -857,6 +857,6 @@ namespace DetourNavigator Log(Debug::Debug) << "Insert db tile by job " << job->mId; mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile, mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData)); - ++mNextTileId.t; + ++mNextTileId; } } diff --git a/components/detournavigator/navmeshdb.hpp b/components/detournavigator/navmeshdb.hpp index f10a3a3288..35aaeaf677 100644 --- a/components/detournavigator/navmeshdb.hpp +++ b/components/detournavigator/navmeshdb.hpp @@ -4,14 +4,13 @@ #include "tileposition.hpp" #include +#include #include #include #include #include -#include - #include #include #include @@ -28,10 +27,10 @@ struct sqlite3_stmt; namespace DetourNavigator { - BOOST_STRONG_TYPEDEF(std::int64_t, TileId) - BOOST_STRONG_TYPEDEF(std::int64_t, TileRevision) - BOOST_STRONG_TYPEDEF(std::int64_t, TileVersion) - BOOST_STRONG_TYPEDEF(std::int64_t, ShapeId) + using TileId = Misc::StrongTypedef; + using TileRevision = Misc::StrongTypedef; + using TileVersion = Misc::StrongTypedef; + using ShapeId = Misc::StrongTypedef; struct Tile { diff --git a/components/detournavigator/navmeshdbutils.cpp b/components/detournavigator/navmeshdbutils.cpp index 71873972b9..d2379c4e53 100644 --- a/components/detournavigator/navmeshdbutils.cpp +++ b/components/detournavigator/navmeshdbutils.cpp @@ -28,7 +28,7 @@ namespace DetourNavigator const ShapeId newShapeId = nextShapeId; db.insertShape(newShapeId, name, type, hashData); Log(Debug::Verbose) << "Added " << name << " " << type << " shape to navmeshdb with id " << newShapeId; - ++nextShapeId.t; + ++nextShapeId; return newShapeId; } } diff --git a/components/misc/strongtypedef.hpp b/components/misc/strongtypedef.hpp new file mode 100644 index 0000000000..2a9e4c3a8b --- /dev/null +++ b/components/misc/strongtypedef.hpp @@ -0,0 +1,38 @@ +#ifndef OPENMW_COMPONENTS_MISC_STRONGTYPEDEF_H +#define OPENMW_COMPONENTS_MISC_STRONGTYPEDEF_H + +#include + +namespace Misc +{ + template + struct StrongTypedef + { + T mValue; + + StrongTypedef() = default; + + explicit StrongTypedef(const T& value) : mValue(value) {} + + explicit StrongTypedef(T&& value) : mValue(std::move(value)) {} + + operator const T&() const { return mValue; } + + operator T&() { return mValue; } + + StrongTypedef& operator++() + { + ++mValue; + return *this; + } + + StrongTypedef operator++(int) + { + StrongTypedef copy(*this); + operator++(); + return copy; + } + }; +} + +#endif diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index d56708e46c..1bdc0e5693 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -493,7 +493,7 @@ void MWShadowTechnique::LightData::setLightData(osg::RefMatrix* lm, const osg::L lightDir.set(-lightPos.x(), -lightPos.y(), -lightPos.z()); lightDir.normalize(); OSG_INFO<<" Directional light, lightPos="< Date: Fri, 25 Feb 2022 04:02:21 +0200 Subject: [PATCH 2163/2859] Use std::vector instead of std::list --- apps/openmw/mwbase/mechanicsmanager.hpp | 11 ++++---- apps/openmw/mwmechanics/actors.cpp | 28 +++++++++---------- apps/openmw/mwmechanics/actors.hpp | 10 +++---- apps/openmw/mwmechanics/aicombat.cpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 10 +++---- .../mwmechanics/mechanicsmanagerimp.hpp | 10 +++---- 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index fb9197782b..5b41ff7fda 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -199,16 +198,16 @@ namespace MWBase ///Returns the list of actors which are siding with the given actor in fights /**ie AiFollow or AiEscort is active and the target is the actor **/ - virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor) = 0; - virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; - virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; + virtual std::vector getActorsSidingWith(const MWWorld::Ptr& actor) = 0; + virtual std::vector getActorsFollowing(const MWWorld::Ptr& actor) = 0; + virtual std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; virtual std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0; ///Returns a list of actors who are fighting the given actor within the fAlarmDistance /** ie AiCombat is active and the target is the actor **/ - virtual std::list getActorsFighting(const MWWorld::Ptr& actor) = 0; + virtual std::vector getActorsFighting(const MWWorld::Ptr& actor) = 0; - virtual std::list getEnemiesNearby(const MWWorld::Ptr& actor) = 0; + virtual std::vector getEnemiesNearby(const MWWorld::Ptr& actor) = 0; /// Recursive versions of above methods virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b6275070e0..80ae4c6e54 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1969,9 +1969,9 @@ namespace MWMechanics return false; } - std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) + std::vector Actors::getActorsSidingWith(const MWWorld::Ptr& actor) { - std::list list; + std::vector list; for(PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) { const MWWorld::Ptr &iteratedActor = iter->first; @@ -2007,9 +2007,9 @@ namespace MWMechanics return list; } - std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) + std::vector Actors::getActorsFollowing(const MWWorld::Ptr& actor) { - std::list list; + std::vector list; forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::shared_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) @@ -2022,14 +2022,14 @@ namespace MWMechanics } void Actors::getActorsFollowing(const MWWorld::Ptr &actor, std::set& out) { - std::list followers = getActorsFollowing(actor); + auto followers = getActorsFollowing(actor); for(const MWWorld::Ptr &follower : followers) if (out.insert(follower).second) getActorsFollowing(follower, out); } void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out) { - std::list followers = getActorsSidingWith(actor); + auto followers = getActorsSidingWith(actor); for(const MWWorld::Ptr &follower : followers) if (out.insert(follower).second) getActorsSidingWith(follower, out); @@ -2042,7 +2042,7 @@ namespace MWMechanics out.insert(search->second.begin(), search->second.end()); else { - std::list followers = getActorsSidingWith(actor); + auto followers = getActorsSidingWith(actor); for (const MWWorld::Ptr &follower : followers) if (out.insert(follower).second) getActorsSidingWith(follower, out, cachedAllies); @@ -2058,9 +2058,9 @@ namespace MWMechanics } } - std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) + std::vector Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) { - std::list list; + std::vector list; forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::shared_ptr& package) { if (package->followTargetThroughDoors() && package->getTarget() == actor) @@ -2093,8 +2093,8 @@ namespace MWMechanics return map; } - std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { - std::list list; + std::vector Actors::getActorsFighting(const MWWorld::Ptr& actor) { + std::vector list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, mActorsProcessingRange, neighbors); @@ -2108,14 +2108,14 @@ namespace MWMechanics continue; if (stats.getAiSequence().isInCombat(actor)) - list.push_front(neighbor); + list.push_back(neighbor); } return list; } - std::list Actors::getEnemiesNearby(const MWWorld::Ptr& actor) + std::vector Actors::getEnemiesNearby(const MWWorld::Ptr& actor) { - std::list list; + std::vector list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, mActorsProcessingRange, neighbors); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index f922be6556..f1985377e8 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -164,8 +164,8 @@ namespace MWMechanics ///Returns the list of actors which are siding with the given actor in fights /**ie AiFollow or AiEscort is active and the target is the actor **/ - std::list getActorsSidingWith(const MWWorld::Ptr& actor); - std::list getActorsFollowing(const MWWorld::Ptr& actor); + std::vector getActorsSidingWith(const MWWorld::Ptr& actor); + std::vector getActorsFollowing(const MWWorld::Ptr& actor); /// Recursive version of getActorsFollowing void getActorsFollowing(const MWWorld::Ptr &actor, std::set& out); @@ -175,15 +175,15 @@ namespace MWMechanics void getActorsSidingWith(const MWWorld::Ptr &actor, std::set& out, std::map >& cachedAllies); /// Get the list of AiFollow::mFollowIndex for all actors following this target - std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); + std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor); std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor); ///Returns the list of actors which are fighting the given actor /**ie AiCombat is active and the target is the actor **/ - std::list getActorsFighting(const MWWorld::Ptr& actor); + std::vector getActorsFighting(const MWWorld::Ptr& actor); /// Unlike getActorsFighting, also returns actors that *would* fight the given actor if they saw him. - std::list getEnemiesNearby(const MWWorld::Ptr& actor); + std::vector getEnemiesNearby(const MWWorld::Ptr& actor); void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 83ff795545..ecf53bf2d8 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -171,7 +171,7 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); storage.mActionCooldown = 0.f; // Continue combat if target is player or player follower/escorter and an attack has been attempted - const std::list& playerFollowersAndEscorters = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer()); + const auto& playerFollowersAndEscorters = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer()); bool targetSidesWithPlayer = (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target) != playerFollowersAndEscorters.end()); if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer) && ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId() == target.getClass().getCreatureStats(target).getActorId()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 6a1f9f2b19..658c5c85c6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1639,17 +1639,17 @@ namespace MWMechanics return mActors.isAnyObjectInRange(position, radius); } - std::list MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) + std::vector MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) { return mActors.getActorsSidingWith(actor); } - std::list MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) + std::vector MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) { return mActors.getActorsFollowing(actor); } - std::list MechanicsManager::getActorsFollowingIndices(const MWWorld::Ptr& actor) + std::vector MechanicsManager::getActorsFollowingIndices(const MWWorld::Ptr& actor) { return mActors.getActorsFollowingIndices(actor); } @@ -1659,11 +1659,11 @@ namespace MWMechanics return mActors.getActorsFollowingByIndex(actor); } - std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { + std::vector MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } - std::list MechanicsManager::getEnemiesNearby(const MWWorld::Ptr& actor) { + std::vector MechanicsManager::getEnemiesNearby(const MWWorld::Ptr& actor) { return mActors.getEnemiesNearby(actor); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 0f4c2e606a..6128b8bf52 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -149,13 +149,13 @@ namespace MWMechanics /// Check if there are actors in selected range bool isAnyActorInRange(const osg::Vec3f &position, float radius) override; - std::list getActorsSidingWith(const MWWorld::Ptr& actor) override; - std::list getActorsFollowing(const MWWorld::Ptr& actor) override; - std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) override; + std::vector getActorsSidingWith(const MWWorld::Ptr& actor) override; + std::vector getActorsFollowing(const MWWorld::Ptr& actor) override; + std::vector getActorsFollowingIndices(const MWWorld::Ptr& actor) override; std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) override; - std::list getActorsFighting(const MWWorld::Ptr& actor) override; - std::list getEnemiesNearby(const MWWorld::Ptr& actor) override; + std::vector getActorsFighting(const MWWorld::Ptr& actor) override; + std::vector getEnemiesNearby(const MWWorld::Ptr& actor) override; /// Recursive version of getActorsFollowing void getActorsFollowing(const MWWorld::Ptr& actor, std::set& out) override; From 7f3058d501481bdc6d6e642ef14a635d43f99927 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Fri, 25 Feb 2022 17:40:27 +0100 Subject: [PATCH 2164/2859] Use toStringView in esmtool instead of toString --- apps/esmtool/esmtool.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a7946c77a6..a968dcb464 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -293,7 +293,7 @@ void printRaw(ESM::ESMReader &esm) while(esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); - std::cout << "Record: " << n.toString() << '\n'; + std::cout << "Record: " << n.toStringView() << '\n'; esm.getRecHeader(); while(esm.hasMoreSubs()) { @@ -302,7 +302,7 @@ void printRaw(ESM::ESMReader &esm) esm.skipHSub(); n = esm.retSubName(); std::ios::fmtflags f(std::cout.flags()); - std::cout << " " << n.toString() << " - " << esm.getSubSize() + std::cout << " " << n.toStringView() << " - " << esm.getSubSize() << " bytes @ 0x" << std::hex << offs << '\n'; std::cout.flags(f); } @@ -367,9 +367,9 @@ int load(Arguments& info) auto record = EsmTool::RecordBase::create(n); if (record == nullptr) { - if (skipped.count(n.toInt()) == 0) + if (!quiet && skipped.count(n.toInt()) == 0) { - std::cout << "Skipping " << n.toString() << " records.\n"; + std::cout << "Skipping " << n.toStringView() << " records.\n"; skipped.emplace(n.toInt()); } @@ -389,7 +389,7 @@ int load(Arguments& info) if (!info.types.empty()) { std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), n.toString()); + match = std::find(info.types.begin(), info.types.end(), n.toStringView()); if (match == info.types.end()) interested = false; } @@ -398,7 +398,7 @@ int load(Arguments& info) if(!quiet && interested) { - std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n"; + std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n"; record->print(); } @@ -454,7 +454,7 @@ int clone(Arguments& info) ESM::NAME name; name = stat.first; int amount = stat.second; - std::cout << std::setw(digitCount) << amount << " " << name.toString() << " "; + std::cout << std::setw(digitCount) << amount << " " << name.toStringView() << " "; if (++i % 3 == 0) std::cout << '\n'; } From e092ee2624376c5235594cdabb1b0f3c2df7af86 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 7 Feb 2022 23:37:08 +0100 Subject: [PATCH 2165/2859] Document the Lua Image widget, add UI texture resources --- apps/openmw/mwlua/luamanagerimp.cpp | 2 + apps/openmw/mwlua/luamanagerimp.hpp | 6 ++ apps/openmw/mwlua/uibindings.cpp | 88 +++++++++++-------- components/CMakeLists.txt | 2 +- components/lua_ui/image.cpp | 23 ++++- components/lua_ui/resources.cpp | 23 +++++ components/lua_ui/resources.hpp | 55 ++++++++++++ .../lua-scripting/user_interface.rst | 1 + .../reference/lua-scripting/widgets/image.rst | 22 +++++ files/lua_api/openmw/ui.lua | 30 +++++-- 10 files changed, 209 insertions(+), 43 deletions(-) create mode 100644 components/lua_ui/resources.cpp create mode 100644 components/lua_ui/resources.hpp create mode 100644 docs/source/reference/lua-scripting/widgets/image.rst diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 0dcd72d359..3319e975cc 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -37,6 +37,8 @@ namespace MWLua mLocalLoader = createUserdataSerializer(true, mWorldView.getObjectRegistry(), &mContentFileMapping); mGlobalScripts.setSerializer(mGlobalSerializer.get()); + + mUiResourceManager = std::make_unique(vfs); } void LuaManager::initConfiguration() diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index b292eb3c1b..fecb9478e0 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -8,6 +8,8 @@ #include #include +#include + #include "../mwbase/luamanager.hpp" #include "actions.hpp" @@ -89,10 +91,14 @@ namespace MWLua return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); }; } + LuaUi::ResourceManager* uiResourceManager() { return mUiResourceManager.get(); } + private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); + std::unique_ptr mUiResourceManager; + bool mInitialized = false; bool mGlobalScriptsStarted = false; LuaUtil::ScriptsConfiguration mConfiguration; diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 248f82f28a..0572365b57 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "context.hpp" #include "actions.hpp" @@ -77,45 +78,45 @@ namespace MWLua std::shared_ptr mElement; }; - // Lua arrays index from 1 - inline size_t fromLuaIndex(size_t i) { return i - 1; } - inline size_t toLuaIndex(size_t i) { return i + 1; } - } + class LayerAction final : public Action + { + public: + LayerAction(std::string_view name, std::string_view afterName, + LuaUi::Layers::Options options, LuaUtil::LuaState* state) + : Action(state) + , mName(name) + , mAfterName(afterName) + , mOptions(options) + {} - class LayerAction final : public Action - { - public: - LayerAction(std::string_view name, std::string_view afterName, - LuaUi::Layers::Options options, LuaUtil::LuaState* state) - : Action(state) - , mName(name) - , mAfterName(afterName) - , mOptions(options) - {} + void apply(WorldView&) const override + { + size_t index = LuaUi::Layers::indexOf(mAfterName); + if (index == LuaUi::Layers::size()) + throw std::logic_error(std::string("Layer not found")); + LuaUi::Layers::insert(index, mName, mOptions); + } - void apply(WorldView&) const override - { - size_t index = LuaUi::Layers::indexOf(mAfterName); - if (index == LuaUi::Layers::size()) - throw std::logic_error(std::string("Layer not found")); - LuaUi::Layers::insert(index, mName, mOptions); - } + std::string toString() const override + { + std::string result("Insert UI layer \""); + result += mName; + result += "\" after \""; + result += mAfterName; + result += "\""; + return result; + } - std::string toString() const override - { - std::string result("Insert UI layer \""); - result += mName; - result += "\" after \""; - result += mAfterName; - result += "\""; - return result; - } + private: + std::string mName; + std::string mAfterName; + LuaUi::Layers::Options mOptions; + }; - private: - std::string mName; - std::string mAfterName; - LuaUi::Layers::Options mOptions; - }; + // Lua arrays index from 1 + inline size_t fromLuaIndex(size_t i) { return i - 1; } + inline size_t toLuaIndex(size_t i) { return i + 1; } + } sol::table initUserInterfacePackage(const Context& context) { @@ -278,6 +279,23 @@ namespace MWLua api["registerSettingsPage"] = &LuaUi::registerSettingsPage; + api["texture"] = [luaManager=context.mLuaManager](const sol::table& options) + { + LuaUi::TextureData data; + sol::object path = LuaUtil::getFieldOrNil(options, "path"); + if (path.is() and !path.as().empty()) + data.mPath = path.as(); + else + throw sol::error("Invalid texture path"); + sol::object offset = LuaUtil::getFieldOrNil(options, "offset"); + if (offset.is()) + data.mOffset = offset.as(); + sol::object size = LuaUtil::getFieldOrNil(options, "size"); + if (size.is()) + data.mSize = size.as(); + return luaManager->uiResourceManager()->registerTexture(data); + }; + return LuaUtil::makeReadOnly(api); } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 929df9b20d..e183d54a95 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -259,7 +259,7 @@ add_component_dir (queries add_component_dir (lua_ui registerscriptsettings scriptsettings - properties widget element util layers content alignment + properties widget element util layers content alignment resources adapter text textedit window image container ) diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index c72bac21e9..9dab34868e 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -2,6 +2,8 @@ #include +#include "resources.hpp" + namespace LuaUi { void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) @@ -33,19 +35,38 @@ namespace LuaUi void LuaImage::updateProperties() { - setImageTexture(propertyValue("path", std::string())); + TextureResource* resource = propertyValue("resource", nullptr); + MyGUI::IntCoord atlasCoord; + if (resource) + { + auto& data = resource->data(); + atlasCoord = MyGUI::IntCoord( + static_cast(data.mOffset.x()), + static_cast(data.mOffset.y()), + static_cast(data.mSize.x()), + static_cast(data.mSize.y())); + setImageTexture(data.mPath); + } bool tileH = propertyValue("tileH", false); bool tileV = propertyValue("tileV", false); + MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(_getTextureName()); MyGUI::IntSize textureSize; if (texture != nullptr) textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight()); + mTileRect->updateSize(MyGUI::IntSize( tileH ? textureSize.width : 0, tileV ? textureSize.height : 0 )); + if (atlasCoord.width == 0) + atlasCoord.width = textureSize.width; + if (atlasCoord.height == 0) + atlasCoord.height = textureSize.height; + setImageCoord(atlasCoord); + WidgetExtension::updateProperties(); } } diff --git a/components/lua_ui/resources.cpp b/components/lua_ui/resources.cpp new file mode 100644 index 0000000000..421375f9cb --- /dev/null +++ b/components/lua_ui/resources.cpp @@ -0,0 +1,23 @@ +#include "resources.hpp" + +#include + +namespace LuaUi +{ + std::shared_ptr ResourceManager::registerTexture(TextureData data) + { + std::string normalizedPath = vfs()->normalizeFilename(data.mPath); + if (!vfs()->exists(normalizedPath)) + { + std::string error("Texture with path \""); + error += data.mPath; + error += "\" doesn't exist"; + throw std::logic_error(error); + } + data.mPath = normalizedPath; + + TextureResources& list = mTextures[normalizedPath]; + list.push_back(std::make_shared(data)); + return list.back(); + } +} diff --git a/components/lua_ui/resources.hpp b/components/lua_ui/resources.hpp new file mode 100644 index 0000000000..6bad0b0337 --- /dev/null +++ b/components/lua_ui/resources.hpp @@ -0,0 +1,55 @@ +#ifndef OPENMW_LUAUI_RESOURCES +#define OPENMW_LUAUI_RESOURCES + +#include +#include +#include +#include + +#include + +namespace VFS +{ + class Manager; +} + +namespace LuaUi +{ + struct TextureData + { + std::string mPath; + osg::Vec2f mOffset; + osg::Vec2f mSize; + }; + + class TextureResource + { + public: + TextureResource(TextureData data) + : mData(data) + {} + + const TextureData& data() { return mData; } + private: + TextureData mData; + }; + + class ResourceManager { + public: + ResourceManager(const VFS::Manager* vfs) + : mVfs(vfs) + {} + + std::shared_ptr registerTexture(TextureData data); + + protected: + const VFS::Manager* vfs() const { return mVfs; } + + private: + const VFS::Manager* mVfs; + using TextureResources = std::vector>; + std::unordered_map mTextures; + }; +} + +#endif // OPENMW_LUAUI_LAYERS diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index e00aa8bf63..71ad61b410 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -80,6 +80,7 @@ Widget types Widget: Base widget type, all the other widgets inherit its properties and events. Text: Displays text. TextEdit: Accepts text input from the user. + Image: Renders a texture. Example ------- diff --git a/docs/source/reference/lua-scripting/widgets/image.rst b/docs/source/reference/lua-scripting/widgets/image.rst new file mode 100644 index 0000000000..cae8753d52 --- /dev/null +++ b/docs/source/reference/lua-scripting/widgets/image.rst @@ -0,0 +1,22 @@ +Image Widget +============ + +Properties +---------- + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - resource + - ui.texture + - The texture resource to display + * - tileH + - boolean (false) + - Whether to tile the texture horizontally + * - tileV + - boolean (false) + - Whether to tile the texture vertically diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 478b9235a5..c9df230900 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -53,7 +53,15 @@ --- -- Adds a settings page to main menu setting's Scripts tab. -- @function [parent=#ui] registerSettingsPage --- @param #SettingsPage page +-- @param #SettingsPageOptions page + +--- +-- Table with settings page options, passed as an argument to ui.registerSettingsPage +-- @type SettingsPageOptions +-- @field #string name Name of the page, displayed in the list, used for search +-- @field #string searchHints Additional keywords used in search, not displayed anywhere +-- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size (set `size` field in `props`, see Widget documentation, `relativeSize` is ignored) + --- -- Layout @@ -162,10 +170,20 @@ -- @field #number button Mouse button which triggered the event (could be nil) --- --- Settings page parameters, passed as an argument to ui.registerSettingsPage --- @type SettingsPage --- @field #string name Name of the page, displayed in the list, used for search --- @field #string searchHints Additional keywords used in search, not displayed anywhere --- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size (set `size` field in `props`, see Widget documentation, `relativeSize` is ignored) +-- Register a new texture resource +-- @function [parent=#ui] texture #TextureResource +-- @param #TextureResourceOptions options + +--- +-- A texture ready to be used by UI widgets +-- @type TextureResource + +--- +-- Table with argument passed to ui.texture +-- @type TextureResourceOptions +-- @field #string path Path to the texture file. Required +-- @field openmw.util#Vector2 offset Offset of this resource in the texture. (0, 0) by default +-- @field openmw.util#Vector2 size Size of the resource in the texture. (0, 0) by default. +-- 0 means the whole texture size is used. return nil From fc50724f5c2306bc060bb500839b04263ebd98eb Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 8 Feb 2022 13:38:42 +0100 Subject: [PATCH 2166/2859] Render text and images correctly in templates with slots --- apps/openmw/mwlua/uibindings.cpp | 2 +- components/lua_ui/element.cpp | 3 +-- components/lua_ui/image.cpp | 2 +- components/lua_ui/image.hpp | 4 +--- components/lua_ui/text.cpp | 2 +- components/lua_ui/textedit.cpp | 2 +- components/lua_ui/widget.cpp | 21 +++++++++++++++++---- components/lua_ui/widget.hpp | 4 +++- files/mygui/openmw_lua.xml | 10 ++++------ 9 files changed, 30 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 0572365b57..031bb67af0 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -283,7 +283,7 @@ namespace MWLua { LuaUi::TextureData data; sol::object path = LuaUtil::getFieldOrNil(options, "path"); - if (path.is() and !path.as().empty()) + if (path.is() && !path.as().empty()) data.mPath = path.as(); else throw sol::error("Invalid texture path"); diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index d2074ecfc2..47dd0a3d38 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -130,8 +130,7 @@ namespace LuaUi void updateWidget(WidgetExtension* ext, const sol::table& layout) { - ext->resetSlot(); // otherwise if template gets changed, all non-template children will get destroyed - + ext->reset(); ext->setLayout(layout); ext->setExternal(layout.get(LayoutKeys::external)); setTemplate(ext, layout.get(LayoutKeys::templateLayout)); diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 9dab34868e..4a25416835 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -27,7 +27,7 @@ namespace LuaUi mTileSize.height = 1e7; } - LuaImage::LuaImage() + void LuaImage::initialize() { changeWidgetSkin("LuaImage"); mTileRect = dynamic_cast(getSubWidgetMain()); diff --git a/components/lua_ui/image.hpp b/components/lua_ui/image.hpp index e841e55fdb..f7df102440 100644 --- a/components/lua_ui/image.hpp +++ b/components/lua_ui/image.hpp @@ -25,10 +25,8 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaImage) - public: - LuaImage(); - protected: + void initialize() override; void updateProperties() override; LuaTileRect* mTileRect; }; diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index 3a56ad68af..9ec31834f4 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -10,7 +10,7 @@ namespace LuaUi void LuaText::initialize() { - changeWidgetSkin("SandText"); + changeWidgetSkin("LuaText"); setEditStatic(true); setVisibleHScroll(false); setVisibleVScroll(false); diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index ac44872f21..5cb47ec34a 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -6,7 +6,7 @@ namespace LuaUi { void LuaTextEdit::initialize() { - changeWidgetSkin("LuaTextEdit"); + changeWidgetSkin("LuaText"); eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 4364a0c362..96a6d096bd 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -24,9 +24,8 @@ namespace LuaUi { mLua = lua; mWidget = self; - updateTemplate(); - initialize(); + updateTemplate(); } void WidgetExtension::initialize() @@ -72,6 +71,13 @@ namespace LuaUi w->deinitialize(); } + void WidgetExtension::reset() + { + // detach all children from the slot widget, in case it gets destroyed + for (auto& w: mChildren) + w->widget()->detachFromWidget(); + } + void WidgetExtension::attach(WidgetExtension* ext) { ext->mParent = this; @@ -182,8 +188,15 @@ namespace LuaUi else mSlot = slot->mSlot; if (mSlot != oldSlot) - for (WidgetExtension* w : mChildren) - attach(w); + { + MyGUI::IntSize slotSize = mSlot->widget()->getSize(); + MyGUI::IntPoint slotPosition = mSlot->widget()->getAbsolutePosition() - widget()->getAbsolutePosition(); + MyGUI::IntCoord slotCoord(slotPosition, slotSize); + if (mWidget->getSubWidgetMain()) + mWidget->getSubWidgetMain()->setCoord(slotCoord); + if (mWidget->getSubWidgetText()) + mWidget->getSubWidgetText()->setCoord(slotCoord); + } } void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 9037443676..827993d47d 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -28,6 +28,9 @@ namespace LuaUi virtual void deinitialize(); MyGUI::Widget* widget() const { return mWidget; } + WidgetExtension* slot() const { return mSlot; } + + void reset(); const std::vector& children() { return mChildren; } void setChildren(const std::vector&); @@ -50,7 +53,6 @@ namespace LuaUi const sol::table& getLayout() { return mLayout; } void setLayout(const sol::table& layout) { mLayout = layout; } - void resetSlot() { mSlot = this; } template T externalValue(std::string_view name, const T& defaultValue) diff --git a/files/mygui/openmw_lua.xml b/files/mygui/openmw_lua.xml index cb0af2b4a9..588fc53014 100644 --- a/files/mygui/openmw_lua.xml +++ b/files/mygui/openmw_lua.xml @@ -4,12 +4,10 @@ - - - - - - + + + + \ No newline at end of file From e7ed709e5ebd862207303ec26966be5617405bae Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 16 Feb 2022 21:48:21 +0100 Subject: [PATCH 2167/2859] Fix Lua TextEdit skin --- components/lua_ui/textedit.cpp | 2 +- files/mygui/openmw_lua.xml | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index 5cb47ec34a..ac44872f21 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -6,7 +6,7 @@ namespace LuaUi { void LuaTextEdit::initialize() { - changeWidgetSkin("LuaText"); + changeWidgetSkin("LuaTextEdit"); eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); diff --git a/files/mygui/openmw_lua.xml b/files/mygui/openmw_lua.xml index 588fc53014..6e5e232a2a 100644 --- a/files/mygui/openmw_lua.xml +++ b/files/mygui/openmw_lua.xml @@ -8,6 +8,16 @@ - + + + + + + + + + + + \ No newline at end of file From 94cc090f7340a0a085bf459985957e90bc7003b9 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 21 Feb 2022 15:31:54 +0100 Subject: [PATCH 2168/2859] PR feedack for documentation --- docs/source/reference/lua-scripting/widgets/image.rst | 4 ++-- files/lua_api/openmw/ui.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/reference/lua-scripting/widgets/image.rst b/docs/source/reference/lua-scripting/widgets/image.rst index cae8753d52..bd68687cfe 100644 --- a/docs/source/reference/lua-scripting/widgets/image.rst +++ b/docs/source/reference/lua-scripting/widgets/image.rst @@ -16,7 +16,7 @@ Properties - The texture resource to display * - tileH - boolean (false) - - Whether to tile the texture horizontally + - Tile the texture horizontally * - tileV - boolean (false) - - Whether to tile the texture vertically + - Tile the texture vertically diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index c9df230900..d8f7d865e1 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -60,7 +60,7 @@ -- @type SettingsPageOptions -- @field #string name Name of the page, displayed in the list, used for search -- @field #string searchHints Additional keywords used in search, not displayed anywhere --- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size (set `size` field in `props`, see Widget documentation, `relativeSize` is ignored) +-- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size. Set the `size` field in `props`, `relativeSize` is ignored. --- @@ -183,7 +183,7 @@ -- @type TextureResourceOptions -- @field #string path Path to the texture file. Required -- @field openmw.util#Vector2 offset Offset of this resource in the texture. (0, 0) by default --- @field openmw.util#Vector2 size Size of the resource in the texture. (0, 0) by default. +-- @field openmw.util#Vector2 size Size of the resource in the texture. (0, 0) by default. -- 0 means the whole texture size is used. return nil From e7474490a11b4dfadccea818c81c1ac434cf807a Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 21 Feb 2022 15:38:54 +0100 Subject: [PATCH 2169/2859] Document using ui.texture to create atlases --- files/lua_api/openmw/ui.lua | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index d8f7d865e1..6461588a69 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -170,20 +170,33 @@ -- @field #number button Mouse button which triggered the event (could be nil) --- --- Register a new texture resource +-- Register a new texture resource. Can be used to manually atlas UI textures. -- @function [parent=#ui] texture #TextureResource -- @param #TextureResourceOptions options +-- @usage +-- local ui = require('openmw.ui') +-- local vector2 = require('openmw.util').vector2 +-- local myAtlas = 'textures/my_atlas.dds' -- a 128x128 atlas +-- local texture1 = ui.texture { -- texture in the top left corner of the atlas +-- path = myAtlas, +-- offset = vector2(0, 0), +-- size = vector2(64, 64), +-- } +-- local texture2 = ui.texture { -- texture in the top right corner of the atlas +-- path = myAtlas, +-- offset = vector2(64, 0), +-- size = vector2(64, 64), +-- } --- -- A texture ready to be used by UI widgets -- @type TextureResource --- --- Table with argument passed to ui.texture +-- Table with arguments passed to ui.texture. -- @type TextureResourceOptions -- @field #string path Path to the texture file. Required -- @field openmw.util#Vector2 offset Offset of this resource in the texture. (0, 0) by default --- @field openmw.util#Vector2 size Size of the resource in the texture. (0, 0) by default. --- 0 means the whole texture size is used. +-- @field openmw.util#Vector2 size Size of the resource in the texture. (0, 0) by default. 0 means the whole texture size is used. return nil From a7bb87d8a150352415328703ee79306a43d647f9 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 21 Feb 2022 15:57:40 +0100 Subject: [PATCH 2170/2859] Use StringUtils::format --- components/lua_ui/resources.cpp | 5 ++--- components/lua_ui/resources.hpp | 9 +++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/lua_ui/resources.cpp b/components/lua_ui/resources.cpp index 421375f9cb..92318a8a46 100644 --- a/components/lua_ui/resources.cpp +++ b/components/lua_ui/resources.cpp @@ -1,6 +1,7 @@ #include "resources.hpp" #include +#include namespace LuaUi { @@ -9,9 +10,7 @@ namespace LuaUi std::string normalizedPath = vfs()->normalizeFilename(data.mPath); if (!vfs()->exists(normalizedPath)) { - std::string error("Texture with path \""); - error += data.mPath; - error += "\" doesn't exist"; + auto error = Misc::StringUtils::format("Texture with path \"%s\" doesn't exist", data.mPath); throw std::logic_error(error); } data.mPath = normalizedPath; diff --git a/components/lua_ui/resources.hpp b/components/lua_ui/resources.hpp index 6bad0b0337..da9d566273 100644 --- a/components/lua_ui/resources.hpp +++ b/components/lua_ui/resources.hpp @@ -27,14 +27,16 @@ namespace LuaUi public: TextureResource(TextureData data) : mData(data) - {} + {} const TextureData& data() { return mData; } + private: TextureData mData; }; - class ResourceManager { + class ResourceManager + { public: ResourceManager(const VFS::Manager* vfs) : mVfs(vfs) @@ -42,10 +44,9 @@ namespace LuaUi std::shared_ptr registerTexture(TextureData data); - protected: + private: const VFS::Manager* vfs() const { return mVfs; } - private: const VFS::Manager* mVfs; using TextureResources = std::vector>; std::unordered_map mTextures; From fe13fa850ecffd20b21bf5ab943aaa6d8ce4d93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 26 Feb 2022 14:30:06 +0100 Subject: [PATCH 2171/2859] Fix bug on memorystream --- components/bsa/memorystream.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bsa/memorystream.hpp b/components/bsa/memorystream.hpp index 5aae448299..0b7e0da9e2 100644 --- a/components/bsa/memorystream.hpp +++ b/components/bsa/memorystream.hpp @@ -40,7 +40,7 @@ namespace Bsa Memory buffer is freed once the class instance is destroyed. */ -class MemoryInputStream : private std::vector, public virtual Files::MemBuf, public std::istream { +class MemoryInputStream : private std::vector, public Files::MemBuf, public std::istream { public: explicit MemoryInputStream(size_t bufferSize) : std::vector(bufferSize) From 21a363d96ffef0ba0d66b9a7c2086a4ad746e43b Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 26 Feb 2022 13:54:42 +0000 Subject: [PATCH 2172/2859] Refactor UI layers --- apps/openmw/mwgui/debugwindow.cpp | 6 ------ apps/openmw/mwgui/hud.cpp | 2 -- apps/openmw/mwgui/layout.cpp | 6 +++--- apps/openmw/mwgui/loadingscreen.cpp | 21 +++++++------------ apps/openmw/mwgui/loadingscreen.hpp | 2 +- apps/openmw/mwgui/screenfader.cpp | 3 --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- files/mygui/openmw_chargen_birth.layout | 2 +- files/mygui/openmw_chargen_class.layout | 2 +- .../openmw_chargen_class_description.layout | 2 +- .../mygui/openmw_chargen_create_class.layout | 2 +- ...penmw_chargen_generate_class_result.layout | 2 +- files/mygui/openmw_chargen_race.layout | 2 +- files/mygui/openmw_chargen_review.layout | 2 +- .../openmw_chargen_select_attribute.layout | 2 +- .../mygui/openmw_chargen_select_skill.layout | 2 +- files/mygui/openmw_confirmation_dialog.layout | 2 +- files/mygui/openmw_console.layout | 2 +- files/mygui/openmw_count_window.layout | 2 +- files/mygui/openmw_edit_effect.layout | 2 +- files/mygui/openmw_edit_note.layout | 2 +- files/mygui/openmw_infobox.layout | 2 +- .../openmw_interactive_messagebox.layout | 2 +- .../mygui/openmw_itemselection_dialog.layout | 2 +- files/mygui/openmw_layers.xml | 15 ++++++------- files/mygui/openmw_loading_screen.layout | 4 +++- .../mygui/openmw_magicselection_dialog.layout | 2 +- files/mygui/openmw_persuasion_dialog.layout | 2 +- files/mygui/openmw_quickkeys_menu.layout | 2 +- files/mygui/openmw_savegame_dialog.layout | 2 +- files/mygui/openmw_screen_fader.layout | 2 +- files/mygui/openmw_screen_fader_hit.layout | 2 +- files/mygui/openmw_settings_window.layout | 2 +- files/mygui/openmw_text_input.layout | 2 +- files/mygui/openmw_tooltips.layout | 2 +- files/mygui/openmw_windows.skin.xml | 6 +----- 36 files changed, 50 insertions(+), 69 deletions(-) diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index a29910f000..728e16b21a 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -92,11 +91,6 @@ namespace MWGui MyGUI::TabItem* item = mTabControl->addItem("Physics Profiler"); mBulletProfilerEdit = item->createWidgetReal ("LogEdit", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Stretch); - - MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - mMainWidget->setSize(viewSize); - - } void DebugWindow::onFrame(float dt) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index e591163731..1aa0496ae9 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -99,8 +99,6 @@ namespace MWGui , mIsDrowning(false) , mDrowningFlashTheta(0.f) { - mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - // Energy bars getWidget(mHealthFrame, "HealthFrame"); getWidget(mHealth, "Health"); diff --git a/apps/openmw/mwgui/layout.cpp b/apps/openmw/mwgui/layout.cpp index 9b9b9537f9..06577f780c 100644 --- a/apps/openmw/mwgui/layout.cpp +++ b/apps/openmw/mwgui/layout.cpp @@ -24,10 +24,10 @@ namespace MWGui for (MyGUI::Widget* widget : mListWindowRoot) { if (widget->getName() == main_name) - { mMainWidget = widget; - break; - } + + // Force the alignment to update immedeatly + widget->_setAlign(widget->getSize(), widget->getParentSize()); } MYGUI_ASSERT(mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found."); } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index ef8eea0104..1ea53a6ec7 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -45,19 +44,14 @@ namespace MWGui , mProgress(0) , mShowWallpaper(true) { - mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); getWidget(mLoadingBox, "LoadingBox"); + getWidget(mSceneImage, "Scene"); + getWidget(mSplashImage, "Splash"); mProgressBar->setScrollViewPage(1); - mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); - mSceneImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Scene"); - findSplashScreens(); } @@ -100,7 +94,7 @@ namespace MWGui void LoadingScreen::setVisible(bool visible) { WindowBase::setVisible(visible); - mBackgroundImage->setVisible(visible); + mSplashImage->setVisible(visible); mSceneImage->setVisible(visible); } @@ -207,7 +201,6 @@ namespace MWGui mViewer->getSceneData()->setComputeBoundingSphereCallback(nullptr); mViewer->getSceneData()->dirtyBound(); - //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; setVisible(false); if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) @@ -229,8 +222,8 @@ namespace MWGui // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3 // we can't do this by default, because the Morrowind splash screens are 1024x1024, but should be displayed as 4:3 bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); - mBackgroundImage->setVisible(true); - mBackgroundImage->setBackgroundImage(randomSplash, true, stretch); + mSplashImage->setVisible(true); + mSplashImage->setBackgroundImage(randomSplash, true, stretch); } mSceneImage->setBackgroundImage(""); mSceneImage->setVisible(false); @@ -320,8 +313,8 @@ namespace MWGui #endif mCopyFramebufferToTextureCallback->reset(); - mBackgroundImage->setBackgroundImage(""); - mBackgroundImage->setVisible(false); + mSplashImage->setBackgroundImage(""); + mSplashImage->setVisible(false); mSceneImage->setRenderItemTexture(mGuiTexture.get()); mSceneImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index c643965342..cfa97ed762 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -79,7 +79,7 @@ namespace MWGui MyGUI::TextBox* mLoadingText; MyGUI::ScrollBar* mProgressBar; - BackgroundImage* mBackgroundImage; + BackgroundImage* mSplashImage; BackgroundImage* mSceneImage; std::vector mSplashScreens; diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index 619852a22b..aa47d0821c 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -1,6 +1,5 @@ #include "screenfader.hpp" -#include #include #include @@ -91,8 +90,6 @@ namespace MWGui { MyGUI::Gui::getInstance().eventFrameStart += MyGUI::newDelegate(this, &ScreenFader::onFrameStart); - mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - MyGUI::ImageBox* imageBox = mMainWidget->castType(false); if (imageBox) { diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 04e48fb280..0be8f2c751 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -256,7 +256,7 @@ namespace MWGui MyGUI::PointerManager::getInstance().setVisible(false); mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Default, "InputBlocker"); + MyGUI::Align::Default, "Video"); mVideoBackground->setImageTexture("black"); mVideoBackground->setVisible(false); mVideoBackground->setNeedMouseFocus(true); diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout index 18d4123407..206e559e45 100644 --- a/files/mygui/openmw_chargen_birth.layout +++ b/files/mygui/openmw_chargen_birth.layout @@ -1,6 +1,6 @@ - + diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout index 1402d9b5cf..cd01b836ca 100644 --- a/files/mygui/openmw_chargen_class.layout +++ b/files/mygui/openmw_chargen_class.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_chargen_class_description.layout b/files/mygui/openmw_chargen_class_description.layout index 906ca23529..250df32d30 100644 --- a/files/mygui/openmw_chargen_class_description.layout +++ b/files/mygui/openmw_chargen_class_description.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout index fe491eb6d3..17a5c8684b 100644 --- a/files/mygui/openmw_chargen_create_class.layout +++ b/files/mygui/openmw_chargen_create_class.layout @@ -1,6 +1,6 @@ - + diff --git a/files/mygui/openmw_chargen_generate_class_result.layout b/files/mygui/openmw_chargen_generate_class_result.layout index 48a203b18f..42da935bf5 100644 --- a/files/mygui/openmw_chargen_generate_class_result.layout +++ b/files/mygui/openmw_chargen_generate_class_result.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_chargen_race.layout b/files/mygui/openmw_chargen_race.layout index 71f6dc476e..4f95dac5cb 100644 --- a/files/mygui/openmw_chargen_race.layout +++ b/files/mygui/openmw_chargen_race.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_chargen_review.layout b/files/mygui/openmw_chargen_review.layout index 927296fadc..2443115eb6 100644 --- a/files/mygui/openmw_chargen_review.layout +++ b/files/mygui/openmw_chargen_review.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_chargen_select_attribute.layout b/files/mygui/openmw_chargen_select_attribute.layout index 57bd4ebc65..115c8642d6 100644 --- a/files/mygui/openmw_chargen_select_attribute.layout +++ b/files/mygui/openmw_chargen_select_attribute.layout @@ -1,6 +1,6 @@ - + diff --git a/files/mygui/openmw_chargen_select_skill.layout b/files/mygui/openmw_chargen_select_skill.layout index 3737ea9043..009c2e96f4 100644 --- a/files/mygui/openmw_chargen_select_skill.layout +++ b/files/mygui/openmw_chargen_select_skill.layout @@ -1,6 +1,6 @@ - + diff --git a/files/mygui/openmw_confirmation_dialog.layout b/files/mygui/openmw_confirmation_dialog.layout index 246c8aa8ff..beaa4bf339 100644 --- a/files/mygui/openmw_confirmation_dialog.layout +++ b/files/mygui/openmw_confirmation_dialog.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index 7dba079eed..3584002e92 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -1,6 +1,6 @@  - + diff --git a/files/mygui/openmw_count_window.layout b/files/mygui/openmw_count_window.layout index d507ad31ad..c9e160cbb2 100644 --- a/files/mygui/openmw_count_window.layout +++ b/files/mygui/openmw_count_window.layout @@ -1,6 +1,6 @@  - + diff --git a/files/mygui/openmw_edit_effect.layout b/files/mygui/openmw_edit_effect.layout index cd6453c817..545cb66e47 100644 --- a/files/mygui/openmw_edit_effect.layout +++ b/files/mygui/openmw_edit_effect.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_edit_note.layout b/files/mygui/openmw_edit_note.layout index edf53bde75..765315aa3f 100644 --- a/files/mygui/openmw_edit_note.layout +++ b/files/mygui/openmw_edit_note.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_infobox.layout b/files/mygui/openmw_infobox.layout index 252a237cdb..21816a1303 100644 --- a/files/mygui/openmw_infobox.layout +++ b/files/mygui/openmw_infobox.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_interactive_messagebox.layout b/files/mygui/openmw_interactive_messagebox.layout index 410426656f..a9a50a3ad7 100644 --- a/files/mygui/openmw_interactive_messagebox.layout +++ b/files/mygui/openmw_interactive_messagebox.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_itemselection_dialog.layout b/files/mygui/openmw_itemselection_dialog.layout index b2337b6690..636a864e1f 100644 --- a/files/mygui/openmw_itemselection_dialog.layout +++ b/files/mygui/openmw_itemselection_dialog.layout @@ -1,6 +1,6 @@ - + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 4afa67f314..9c99526ec8 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -1,23 +1,24 @@ - - - + + - - - + + + + - + + diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index 69df200221..ead825e279 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -1,7 +1,6 @@ - @@ -16,4 +15,7 @@ + + + diff --git a/files/mygui/openmw_magicselection_dialog.layout b/files/mygui/openmw_magicselection_dialog.layout index 027250d7ac..870592a536 100644 --- a/files/mygui/openmw_magicselection_dialog.layout +++ b/files/mygui/openmw_magicselection_dialog.layout @@ -1,6 +1,6 @@ - + diff --git a/files/mygui/openmw_persuasion_dialog.layout b/files/mygui/openmw_persuasion_dialog.layout index c66f9d0adc..3b10588ec8 100644 --- a/files/mygui/openmw_persuasion_dialog.layout +++ b/files/mygui/openmw_persuasion_dialog.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_quickkeys_menu.layout b/files/mygui/openmw_quickkeys_menu.layout index 27bbbc190a..829c64d0c6 100644 --- a/files/mygui/openmw_quickkeys_menu.layout +++ b/files/mygui/openmw_quickkeys_menu.layout @@ -1,6 +1,6 @@ - + diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout index 236eaaa61d..22fa69aa5b 100644 --- a/files/mygui/openmw_savegame_dialog.layout +++ b/files/mygui/openmw_savegame_dialog.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_screen_fader.layout b/files/mygui/openmw_screen_fader.layout index e9009d32a6..59f65aba14 100644 --- a/files/mygui/openmw_screen_fader.layout +++ b/files/mygui/openmw_screen_fader.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_screen_fader_hit.layout b/files/mygui/openmw_screen_fader_hit.layout index 7e60f0ff5a..91bc036c8a 100644 --- a/files/mygui/openmw_screen_fader_hit.layout +++ b/files/mygui/openmw_screen_fader_hit.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 13f5ada24b..d0e2edfbf6 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -1,6 +1,6 @@  - + diff --git a/files/mygui/openmw_text_input.layout b/files/mygui/openmw_text_input.layout index 64ec61b025..85376060f4 100644 --- a/files/mygui/openmw_text_input.layout +++ b/files/mygui/openmw_text_input.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index ce927038c2..fd87b5c0e0 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index d6d747c4f9..14f6930b3c 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -453,11 +453,7 @@ - + From 67879bac55167b0fbba639ef02e0117c871a8a8c Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 26 Feb 2022 12:10:55 +0100 Subject: [PATCH 2173/2859] MR feedack --- apps/openmw/mwlua/luamanagerimp.cpp | 11 +++++++---- apps/openmw/mwlua/luamanagerimp.hpp | 5 ++--- apps/openmw/mwlua/uibindings.cpp | 12 ++++++------ components/lua_ui/image.cpp | 11 +++++------ components/lua_ui/resources.cpp | 9 +++++++-- components/lua_ui/resources.hpp | 17 +++-------------- 6 files changed, 30 insertions(+), 35 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 3319e975cc..716612f745 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -26,7 +26,10 @@ namespace MWLua { - LuaManager::LuaManager(const VFS::Manager* vfs, const std::string& libsDir) : mLua(vfs, &mConfiguration), mI18n(vfs, &mLua) + LuaManager::LuaManager(const VFS::Manager* vfs, const std::string& libsDir) + : mLua(vfs, &mConfiguration) + , mUiResourceManager(vfs) + , mI18n(vfs, &mLua) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); mLua.addInternalLibSearchPath(libsDir); @@ -37,8 +40,6 @@ namespace MWLua mLocalLoader = createUserdataSerializer(true, mWorldView.getObjectRegistry(), &mContentFileMapping); mGlobalScripts.setSerializer(mGlobalSerializer.get()); - - mUiResourceManager = std::make_unique(vfs); } void LuaManager::initConfiguration() @@ -250,6 +251,7 @@ namespace MWLua void LuaManager::clear() { LuaUi::clearUserInterface(); + mUiResourceManager.clear(); mActiveLocalScripts.clear(); mLocalEvents.clear(); mGlobalEvents.clear(); @@ -472,7 +474,8 @@ namespace MWLua { Log(Debug::Info) << "Reload Lua"; - LuaUi::clearUserInterface(); + LuaUi::clearUserInterface(); + mUiResourceManager.clear(); mLua.dropScriptCache(); initConfiguration(); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index fecb9478e0..fed6dc9dc7 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -91,18 +91,17 @@ namespace MWLua return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); }; } - LuaUi::ResourceManager* uiResourceManager() { return mUiResourceManager.get(); } + LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); - std::unique_ptr mUiResourceManager; - bool mInitialized = false; bool mGlobalScriptsStarted = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; + LuaUi::ResourceManager mUiResourceManager; LuaUtil::I18nManager mI18n; sol::table mNearbyPackage; sol::table mUserInterfacePackage; diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 031bb67af0..b384994654 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -78,10 +78,10 @@ namespace MWLua std::shared_ptr mElement; }; - class LayerAction final : public Action + class InsertLayerAction final : public Action { public: - LayerAction(std::string_view name, std::string_view afterName, + InsertLayerAction(std::string_view name, std::string_view afterName, LuaUi::Layers::Options options, LuaUtil::LuaState* state) : Action(state) , mName(name) @@ -247,7 +247,7 @@ namespace MWLua { LuaUi::Layers::Options options; options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); - context.mLuaManager->addAction(std::make_unique(name, afterName, options, context.mLua)); + context.mLuaManager->addAction(std::make_unique(name, afterName, options, context.mLua)); }; { auto pairs = [layers](const sol::object&) @@ -283,10 +283,10 @@ namespace MWLua { LuaUi::TextureData data; sol::object path = LuaUtil::getFieldOrNil(options, "path"); - if (path.is() && !path.as().empty()) + if (path.is()) data.mPath = path.as(); - else - throw sol::error("Invalid texture path"); + if (data.mPath.empty()) + throw std::logic_error("Invalid texture path"); sol::object offset = LuaUtil::getFieldOrNil(options, "offset"); if (offset.is()) data.mOffset = offset.as(); diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 4a25416835..69bccac1ed 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -39,13 +39,12 @@ namespace LuaUi MyGUI::IntCoord atlasCoord; if (resource) { - auto& data = resource->data(); atlasCoord = MyGUI::IntCoord( - static_cast(data.mOffset.x()), - static_cast(data.mOffset.y()), - static_cast(data.mSize.x()), - static_cast(data.mSize.y())); - setImageTexture(data.mPath); + static_cast(resource->mOffset.x()), + static_cast(resource->mOffset.y()), + static_cast(resource->mSize.x()), + static_cast(resource->mSize.y())); + setImageTexture(resource->mPath); } bool tileH = propertyValue("tileH", false); diff --git a/components/lua_ui/resources.cpp b/components/lua_ui/resources.cpp index 92318a8a46..605c9ba58b 100644 --- a/components/lua_ui/resources.cpp +++ b/components/lua_ui/resources.cpp @@ -7,8 +7,8 @@ namespace LuaUi { std::shared_ptr ResourceManager::registerTexture(TextureData data) { - std::string normalizedPath = vfs()->normalizeFilename(data.mPath); - if (!vfs()->exists(normalizedPath)) + std::string normalizedPath = mVfs->normalizeFilename(data.mPath); + if (!mVfs->exists(normalizedPath)) { auto error = Misc::StringUtils::format("Texture with path \"%s\" doesn't exist", data.mPath); throw std::logic_error(error); @@ -19,4 +19,9 @@ namespace LuaUi list.push_back(std::make_shared(data)); return list.back(); } + + void ResourceManager::clear() + { + mTextures.clear(); + } } diff --git a/components/lua_ui/resources.hpp b/components/lua_ui/resources.hpp index da9d566273..cabcd63bf4 100644 --- a/components/lua_ui/resources.hpp +++ b/components/lua_ui/resources.hpp @@ -22,18 +22,8 @@ namespace LuaUi osg::Vec2f mSize; }; - class TextureResource - { - public: - TextureResource(TextureData data) - : mData(data) - {} - - const TextureData& data() { return mData; } - - private: - TextureData mData; - }; + // will have more/different data when automated atlasing is supported + using TextureResource = TextureData; class ResourceManager { @@ -43,10 +33,9 @@ namespace LuaUi {} std::shared_ptr registerTexture(TextureData data); + void clear(); private: - const VFS::Manager* vfs() const { return mVfs; } - const VFS::Manager* mVfs; using TextureResources = std::vector>; std::unordered_map mTextures; From 6845d681f23d64662d659d3c05257e135ed85e3d Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 27 Feb 2022 11:59:55 +0000 Subject: [PATCH 2174/2859] Update hardcoded layers (hotfix for https://gitlab.com/OpenMW/openmw/-/merge_requests/1681) --- apps/openmw/mwgui/layout.cpp | 2 +- apps/openmw/mwgui/mainmenu.cpp | 6 +++--- files/mygui/openmw_interactive_messagebox_notransp.layout | 2 +- files/mygui/openmw_layers.xml | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/layout.cpp b/apps/openmw/mwgui/layout.cpp index 06577f780c..9ac39f9a00 100644 --- a/apps/openmw/mwgui/layout.cpp +++ b/apps/openmw/mwgui/layout.cpp @@ -26,7 +26,7 @@ namespace MWGui if (widget->getName() == main_name) mMainWidget = widget; - // Force the alignment to update immedeatly + // Force the alignment to update immediately widget->_setAlign(widget->getSize(), widget->getParentSize()); } MYGUI_ASSERT(mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found."); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 4e695710ff..8fff838add 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -167,11 +167,11 @@ namespace MWGui { // Use black background to correct aspect ratio mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Default, "Menu"); + MyGUI::Align::Default, "MainMenuBackground"); mVideoBackground->setImageTexture("black"); mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); + MyGUI::Align::Stretch, "MainMenuBackground"); mVideo->setVFS(mVFS); mVideo->playVideo("video\\menu_background.bik"); @@ -191,7 +191,7 @@ namespace MWGui if (!mBackground) { mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); + MyGUI::Align::Stretch, "MainMenuBackground"); mBackground->setBackgroundImage("textures\\menu_morrowind.dds", true, stretch); } mBackground->setVisible(true); diff --git a/files/mygui/openmw_interactive_messagebox_notransp.layout b/files/mygui/openmw_interactive_messagebox_notransp.layout index f5a462977b..3a31ee2501 100644 --- a/files/mygui/openmw_interactive_messagebox_notransp.layout +++ b/files/mygui/openmw_interactive_messagebox_notransp.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 9c99526ec8..3e267075ef 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -13,6 +13,7 @@ + From ff7ad93bace989af7cea0dbc1a7dd9e6e0e8a970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 26 Feb 2022 15:18:36 +0100 Subject: [PATCH 2175/2859] Avoid missusing of CompressedBSAFile with private inheritance --- apps/bsatool/bsatool.cpp | 117 ++++++++++++++------------- components/bsa/bsa_file.hpp | 2 +- components/bsa/compressedbsafile.hpp | 8 +- components/vfs/bsaarchive.cpp | 49 +++++++---- components/vfs/bsaarchive.hpp | 10 ++- 5 files changed, 107 insertions(+), 79 deletions(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 4057d627c4..24c98fd139 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -151,62 +151,11 @@ bool parseOptions (int argc, char** argv, Arguments &info) return true; } -int list(std::unique_ptr& bsa, Arguments& info); -int extract(std::unique_ptr& bsa, Arguments& info); -int extractAll(std::unique_ptr& bsa, Arguments& info); -int add(std::unique_ptr& bsa, Arguments& info); - -int main(int argc, char** argv) -{ - try - { - Arguments info; - if(!parseOptions (argc, argv, info)) - return 1; - - // Open file - std::unique_ptr bsa; - - Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(info.filename); - - if (bsaVersion == Bsa::BSAVER_COMPRESSED) - bsa = std::make_unique(Bsa::CompressedBSAFile()); - else - bsa = std::make_unique(Bsa::BSAFile()); - - if (info.mode == "create") - { - bsa->open(info.filename); - return 0; - } - - bsa->open(info.filename); - - if (info.mode == "list") - return list(bsa, info); - else if (info.mode == "extract") - return extract(bsa, info); - else if (info.mode == "extractall") - return extractAll(bsa, info); - else if (info.mode == "add") - return add(bsa, info); - else - { - std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; - return 1; - } - } - catch (std::exception& e) - { - std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl; - return 2; - } -} - -int list(std::unique_ptr& bsa, Arguments& info) +template +int list(std::unique_ptr& bsa, Arguments& info) { // List all files - const Bsa::BSAFile::FileList &files = bsa->getList(); + const auto &files = bsa->getList(); for (const auto& file : files) { if(info.longformat) @@ -225,7 +174,8 @@ int list(std::unique_ptr& bsa, Arguments& info) return 0; } -int extract(std::unique_ptr& bsa, Arguments& info) +template +int extract(std::unique_ptr& bsa, Arguments& info) { std::string archivePath = info.extractfile; Misc::StringUtils::replaceAll(archivePath, "/", "\\"); @@ -281,7 +231,8 @@ int extract(std::unique_ptr& bsa, Arguments& info) return 0; } -int extractAll(std::unique_ptr& bsa, Arguments& info) +template +int extractAll(std::unique_ptr& bsa, Arguments& info) { for (const auto &file : bsa->getList()) { @@ -315,10 +266,62 @@ int extractAll(std::unique_ptr& bsa, Arguments& info) return 0; } -int add(std::unique_ptr& bsa, Arguments& info) +template +int add(std::unique_ptr& bsa, Arguments& info) { boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in); bsa->addFile(info.addfile, stream); return 0; } + +template +int call(Arguments& info) +{ + std::unique_ptr bsa = std::make_unique(); + if (info.mode == "create") + { + bsa->open(info.filename); + return 0; + } + + bsa->open(info.filename); + + if (info.mode == "list") + return list(bsa, info); + else if (info.mode == "extract") + return extract(bsa, info); + else if (info.mode == "extractall") + return extractAll(bsa, info); + else if (info.mode == "add") + return add(bsa, info); + else + { + std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; + return 1; + } +} + +int main(int argc, char** argv) +{ + try + { + Arguments info; + if (!parseOptions(argc, argv, info)) + return 1; + + // Open file + + Bsa::BsaVersion bsaVersion = Bsa::CompressedBSAFile::detectVersion(info.filename); + + if (bsaVersion == Bsa::BSAVER_COMPRESSED) + return call(info); + else + return call(info); + } + catch (std::exception& e) + { + std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl; + return 2; + } +} diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index f6c5d03139..83e9ae954c 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -128,7 +128,7 @@ public: return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize); } - virtual void addFile(const std::string& filename, std::istream& file); + void addFile(const std::string& filename, std::istream& file); /// Get a list of all files /// @note Thread safe. diff --git a/components/bsa/compressedbsafile.hpp b/components/bsa/compressedbsafile.hpp index f19815dcf3..2c0b3c0aac 100644 --- a/components/bsa/compressedbsafile.hpp +++ b/components/bsa/compressedbsafile.hpp @@ -39,7 +39,7 @@ namespace Bsa BSAVER_COMPRESSED = 0x415342 //B, S, A }; - class CompressedBSAFile : public BSAFile + class CompressedBSAFile : private BSAFile { private: //special marker for invalid records, @@ -85,6 +85,10 @@ namespace Bsa static std::uint64_t generateHash(std::string stem, std::string extension) ; Files::IStreamPtr getFile(const FileRecord& fileRecord); public: + using BSAFile::open; + using BSAFile::getList; + using BSAFile::getFilename; + CompressedBSAFile(); virtual ~CompressedBSAFile(); @@ -96,7 +100,7 @@ namespace Bsa Files::IStreamPtr getFile(const char* filePath); Files::IStreamPtr getFile(const FileStruct* fileStruct); - void addFile(const std::string& filename, std::istream& file) override; + void addFile(const std::string& filename, std::istream& file); }; } diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp index b0caa5c305..d4d1a5a38b 100644 --- a/components/vfs/bsaarchive.cpp +++ b/components/vfs/bsaarchive.cpp @@ -8,7 +8,7 @@ namespace VFS BsaArchive::BsaArchive(const std::string &filename) { - mFile = std::make_unique(Bsa::BSAFile()); + mFile = std::make_unique(); mFile->open(filename); const Bsa::BSAFile::FileList &filelist = mFile->getList(); @@ -53,17 +53,30 @@ std::string BsaArchive::getDescription() const return std::string{"BSA: "} + mFile->getFilename(); } +// ------------------------------------------------------------------------------ + +BsaArchiveFile::BsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::BSAFile* bsa) + : mInfo(info) + , mFile(bsa) +{ + +} + +Files::IStreamPtr BsaArchiveFile::open() +{ + return mFile->getFile(mInfo); +} + CompressedBsaArchive::CompressedBsaArchive(const std::string &filename) - : BsaArchive() + : Archive() { - mFile = std::make_unique(Bsa::CompressedBSAFile()); - mFile->open(filename); + mCompressedFile = std::make_unique(); + mCompressedFile->open(filename); - const Bsa::BSAFile::FileList &filelist = mFile->getList(); + const Bsa::BSAFile::FileList &filelist = mCompressedFile->getList(); for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it) { - mResources.emplace_back(&*it, mFile.get()); - mCompressedResources.emplace_back(&*it, static_cast(mFile.get())); + mCompressedResources.emplace_back(&*it, mCompressedFile.get()); } } @@ -78,22 +91,26 @@ void CompressedBsaArchive::listResources(std::map &out, cha } } -// ------------------------------------------------------------------------------ - -BsaArchiveFile::BsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::BSAFile* bsa) - : mInfo(info) - , mFile(bsa) +bool CompressedBsaArchive::contains(const std::string& file, char (*normalize_function)(char)) const { - + for (const auto& it : mCompressedResources) + { + std::string ent = it.mInfo->name(); + std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); + if(file == ent) + return true; + } + return false; } -Files::IStreamPtr BsaArchiveFile::open() +std::string CompressedBsaArchive::getDescription() const { - return mFile->getFile(mInfo); + return std::string{"BSA: "} + mCompressedFile->getFilename(); } + CompressedBsaArchiveFile::CompressedBsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::CompressedBSAFile* bsa) - : BsaArchiveFile(info, bsa) + : mInfo(info) , mCompressedFile(bsa) { diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 1015f8d220..1bf9cf4381 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -19,12 +19,14 @@ namespace VFS Bsa::BSAFile* mFile; }; - class CompressedBsaArchiveFile : public BsaArchiveFile + class CompressedBsaArchiveFile : public File { public: CompressedBsaArchiveFile(const Bsa::BSAFile::FileStruct* info, Bsa::CompressedBSAFile* bsa); Files::IStreamPtr open() override; + + const Bsa::BSAFile::FileStruct* mInfo; Bsa::CompressedBSAFile* mCompressedFile; }; @@ -44,12 +46,14 @@ namespace VFS std::vector mResources; }; - class CompressedBsaArchive : public BsaArchive + class CompressedBsaArchive : public Archive { public: CompressedBsaArchive(const std::string& filename); - void listResources(std::map& out, char (*normalize_function) (char)) override; virtual ~CompressedBsaArchive() {} + void listResources(std::map& out, char (*normalize_function) (char)) override; + bool contains(const std::string& file, char (*normalize_function) (char)) const override; + std::string getDescription() const override; private: std::unique_ptr mCompressedFile; From 25feea9b160c674912e7b57b28fd4779a1d4d226 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 28 Feb 2022 16:42:22 +0100 Subject: [PATCH 2176/2859] Prevent write to empty vector element --- CHANGELOG.md | 1 + apps/openmw/mwgui/spellcreationdialog.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd3932e5c..085eaedaf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -278,6 +278,7 @@ Bug #6276: Deleted groundcover instances are not deleted in game Bug #6294: Game crashes with empty pathgrid Bug #6606: Quests with multiple IDs cannot always be restarted + Bug #6655: Constant effect absorb attribute causes the game to break Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 398635b751..d3da26b8bf 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -109,10 +109,6 @@ namespace MWGui { bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; - bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; - - if (!allowSelf && !allowTouch && !allowTarget) - return; // TODO: Show an error message popup? setMagicEffect(effect); mEditing = false; @@ -617,6 +613,13 @@ namespace MWGui const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; + bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; + + if (!allowSelf && !allowTouch && !allowTarget) + return; // TODO: Show an error message popup? + if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) { delete mSelectSkillDialog; From 4b742dd461842db623c0d4edc04e4c29c45facf3 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 24 Feb 2022 16:01:40 +0100 Subject: [PATCH 2177/2859] explicit capture for lamda --- apps/opencs/view/world/tableheadermouseeventhandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/tableheadermouseeventhandler.cpp b/apps/opencs/view/world/tableheadermouseeventhandler.cpp index 866c6149db..a9d34ce7ab 100644 --- a/apps/opencs/view/world/tableheadermouseeventhandler.cpp +++ b/apps/opencs/view/world/tableheadermouseeventhandler.cpp @@ -14,7 +14,7 @@ TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable * par { header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); connect( - &header, &QHeaderView::customContextMenuRequested, [=](const QPoint & position) { showContextMenu(position); }); + &header, &QHeaderView::customContextMenuRequested, [this](const QPoint & position) { showContextMenu(position); }); header.viewport()->installEventFilter(this); } @@ -52,7 +52,7 @@ QMenu & TableHeaderMouseEventHandler::createContextMenu() action->setChecked(!table.isColumnHidden(i)); menu->addAction(action); - connect(action, &QAction::triggered, [=]() { + connect(action, &QAction::triggered, [this, &action, &i]() { table.setColumnHidden(i, !action->isChecked()); action->setChecked(!action->isChecked()); action->toggle(); From ad11cc8d8a62991654e4c7865dfd75c6dd46acaf Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 25 Feb 2022 15:47:11 +0100 Subject: [PATCH 2178/2859] reinstall fontconfig as needed; const an imbigious == operator --- CI/before_install.osx.sh | 2 +- apps/openmw/mwrender/globalmap.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index ec4ece6343..b3463aa9b8 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -9,7 +9,7 @@ brew update --quiet [ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies qt@6 || true # Some of these tools can come from places other than brew, so check before installing -[ -z "${TRAVIS}" ] && brew install fontconfig +[ -z "${TRAVIS}" ] && brew reinstall fontconfig command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake command -v qmake >/dev/null 2>&1 || brew install qt@5 diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index acd566ff18..dca26a220f 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -453,7 +453,7 @@ namespace MWRender : mLeft(left), mTop(top), mRight(right), mBottom(bottom) { } - bool operator == (const Box& other) + bool operator == (const Box& other) const { return mLeft == other.mLeft && mTop == other.mTop && mRight == other.mRight && mBottom == other.mBottom; } From 1927b1c6d9b0e60ff0ad202900155c0e2e3b35ae Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 25 Feb 2022 16:27:43 +0100 Subject: [PATCH 2179/2859] use static cast to handle: deprecated between enumerations of different types --- apps/openmw/mwlua/inputbindings.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index a8487557ba..1be6e086fa 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -40,7 +40,7 @@ namespace MWLua touchpadEvent["finger"] = sol::readonly_property( [](const SDLUtil::TouchEvent& e) -> int { return e.mFinger; }); touchpadEvent["position"] = sol::readonly_property( - [](const SDLUtil::TouchEvent& e) -> osg::Vec2f { return osg::Vec2f(e.mX, e.mY);}); + [](const SDLUtil::TouchEvent& e) -> osg::Vec2f { return {e.mX, e.mY};}); touchpadEvent["pressure"] = sol::readonly_property( [](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); @@ -177,10 +177,10 @@ namespace MWLua {"TriggerLeft", SDL_CONTROLLER_AXIS_TRIGGERLEFT}, {"TriggerRight", SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, - {"LookUpDown", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookUpDown}, - {"LookLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_LookLeftRight}, - {"MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveForwardBackward}, - {"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight} + {"LookUpDown", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_LookUpDown)}, + {"LookLeftRight", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_LookLeftRight)}, + {"MoveForwardBackward", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_MoveForwardBackward)}, + {"MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + static_cast(MWInput::A_MoveLeftRight)} })); api["KEY"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ From 6601274992fdb8671532fe97d420e171d8a536f0 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 25 Feb 2022 18:09:12 +0100 Subject: [PATCH 2180/2859] constify weakcache overloaded operators --- components/misc/weakcache.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/misc/weakcache.hpp b/components/misc/weakcache.hpp index 022a722dba..3bbb0812cd 100644 --- a/components/misc/weakcache.hpp +++ b/components/misc/weakcache.hpp @@ -22,8 +22,8 @@ namespace Misc public: iterator(WeakCache* cache, typename Map::iterator current, typename Map::iterator end); iterator& operator++(); - bool operator==(const iterator& other); - bool operator!=(const iterator& other); + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; StrongPtr operator*(); private: WeakCache* mCache; @@ -74,13 +74,13 @@ namespace Misc } template - bool WeakCache::iterator::operator==(const iterator& other) + bool WeakCache::iterator::operator==(const iterator& other) const { return mCurrent == other.mCurrent; } template - bool WeakCache::iterator::operator!=(const iterator& other) + bool WeakCache::iterator::operator!=(const iterator& other) const { return !(*this == other); } From 32fd6f297a0e7efbdbd246355398ab1b94280fcb Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 25 Feb 2022 21:53:45 +0100 Subject: [PATCH 2181/2859] static_cast bitwise operation between different enumeration types --- apps/opencs/view/widget/colorpickerpopup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/widget/colorpickerpopup.cpp b/apps/opencs/view/widget/colorpickerpopup.cpp index 206a667276..3d69523924 100644 --- a/apps/opencs/view/widget/colorpickerpopup.cpp +++ b/apps/opencs/view/widget/colorpickerpopup.cpp @@ -11,7 +11,7 @@ CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent) : QFrame(parent) { setWindowFlags(Qt::Popup); - setFrameStyle(QFrame::Box | QFrame::Plain); + setFrameStyle(QFrame::Box | static_cast(QFrame::Plain)); hide(); mColorPicker = new QColorDialog(this); From f6757ce1241b079a6f810c4ff650fb122a0d0967 Mon Sep 17 00:00:00 2001 From: psi29a Date: Wed, 2 Mar 2022 09:06:58 +0000 Subject: [PATCH 2182/2859] ccache for Windows --- .gitlab-ci.yml | 16 ++++++++++++++-- CI/before_script.msvc.sh | 9 +++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0225d08461..5579fcc76b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -352,6 +352,7 @@ variables: &tests-targets - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y + - choco install ccache -y - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y - choco install vswhere -y - choco install ninja -y @@ -374,10 +375,14 @@ variables: &tests-targets - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t + - $env:CCACHE_BASEDIR = Get-Location + - $env:CCACHE_DIR = "$(Get-Location)\ccache" + - New-Item -Type Directory -Force -Path $env:CCACHE_DIR + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C - cd MSVC2019_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config --target ($targets.Split(',')) + - 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 - Get-ChildItem -Recurse *.ilk | Remove-Item @@ -393,6 +398,7 @@ variables: &tests-targets cache: key: ninja-v2 paths: + - ccache - deps - MSVC2019_64_Ninja/deps/Qt artifacts: @@ -473,6 +479,7 @@ Windows_Ninja_Tests_RelWithDebInfo: - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y + - choco install ccache -y - choco install cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' -y - choco install vswhere -y - choco install python -y @@ -494,9 +501,13 @@ Windows_Ninja_Tests_RelWithDebInfo: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t + - $env:CCACHE_BASEDIR = Get-Location + - $env:CCACHE_DIR = "$(Get-Location)\ccache" + - New-Item -Type Directory -Force -Path $env:CCACHE_DIR + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C - cd MSVC2019_64 - cmake --build . --config $config --target ($targets.Split(',')) + - 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 - Get-ChildItem -Recurse *.ilk | Remove-Item @@ -512,6 +523,7 @@ Windows_Ninja_Tests_RelWithDebInfo: cache: key: msbuild-v2 paths: + - ccache - deps - MSVC2019_64/deps/Qt artifacts: diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index aaf459611e..563a65595b 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -62,6 +62,7 @@ VERBOSE="" STRIP="" SKIP_DOWNLOAD="" SKIP_EXTRACT="" +USE_CCACHE="" KEEP="" UNITY_BUILD="" VS_VERSION="" @@ -100,6 +101,8 @@ while [ $# -gt 0 ]; do e ) SKIP_EXTRACT=true ;; + C ) + USE_CCACHE=true ;; k ) KEEP=true ;; @@ -145,6 +148,8 @@ Options: Set the configuration, can also be set with environment variable CONFIGURATION. For mutli-config generators, this is ignored, and all configurations are set up. For single-config generators, several configurations can be set up at once by specifying -c multiple times. + -C + Use ccache. -d Skip checking the downloads. -e @@ -503,6 +508,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" +fi + echo echo "===================================" echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}" From 129d68e2999893e91ebd577dd03b76ca666980f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Thu, 3 Mar 2022 20:24:52 +0200 Subject: [PATCH 2183/2859] Fix #6660: Crash during intro video when closing the game --- apps/openmw/mwrender/landmanager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index 560c1ba720..6af1d9782c 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -25,7 +25,10 @@ osg::ref_ptr LandManager::getLand(int x, int y) return static_cast(obj.get()); else { - const ESM::Land* land = MWBase::Environment::get().getWorld()->getStore().get().search(x,y); + const auto* world = MWBase::Environment::get().getWorld(); + if (!world) + return nullptr; + const ESM::Land* land = world->getStore().get().search(x,y); if (!land) return nullptr; osg::ref_ptr landObj (new ESMTerrain::LandObject(land, mLoadFlags)); From 0b528d3bfb5e9ff4c7c113093b83df1ba16e8d7d Mon Sep 17 00:00:00 2001 From: Matt <3397065-ZehMatt@users.noreply.gitlab.com> Date: Thu, 3 Mar 2022 20:39:53 +0000 Subject: [PATCH 2184/2859] Small improvement for headtracking --- apps/openmw/mwmechanics/actors.cpp | 32 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b6275070e0..267e674520 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1457,26 +1457,32 @@ namespace MWMechanics MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); bool firstPersonPlayer = isPlayer && world->isFirstPerson(); - bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue); - MWWorld::Ptr activePackageTarget; - + // 1. Unconsious actor can not track target // 2. Actors in combat and pursue mode do not bother to headtrack anyone except their target // 3. Player character does not use headtracking in the 1st-person view if (!stats.getKnockedDown() && !firstPersonPlayer) { + bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().isInPursuit(); if (inCombatOrPursue) - activePackageTarget = stats.getAiSequence().getActivePackage().getTarget(); - - for (PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { - if (it->first == iter->first) - continue; - - if (inCombatOrPursue && it->first != activePackageTarget) - continue; - - updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue); + auto activePackageTarget = stats.getAiSequence().getActivePackage().getTarget(); + if (!activePackageTarget.isEmpty()) + { + // Track the specified target of package. + updateHeadTracking(iter->first, activePackageTarget, headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue); + } + } + else + { + // Find something nearby. + for (auto& [ptr, _] : mActors) + { + if (ptr == iter->first) + continue; + + updateHeadTracking(iter->first, ptr, headTrackTarget, sqrHeadTrackDistance, inCombatOrPursue); + } } } From 96e48e549269c442b84aa49e1833a9093cdae7a5 Mon Sep 17 00:00:00 2001 From: Martin Otto Date: Fri, 4 Mar 2022 09:44:52 +0000 Subject: [PATCH 2185/2859] Make settings loader differentiate between engine and editor (fixes #6658). --- apps/opencs/model/prefs/state.cpp | 2 +- components/settings/settings.cpp | 29 ++++++++++++++++++++++------- components/settings/settings.hpp | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index ee0cbbed5a..0981ae4401 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -16,7 +16,7 @@ CSMPrefs::State *CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::load() { - mSettings.load(mConfigurationManager); + mSettings.load(mConfigurationManager, true); } void CSMPrefs::State::declare() diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index b8ff700492..4c17394836 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -20,29 +20,44 @@ void Manager::clear() mChangedSettings.clear(); } -std::string Manager::load(const Files::ConfigurationManager& cfgMgr) +std::string Manager::load(const Files::ConfigurationManager& cfgMgr, bool loadEditorSettings) { SettingsFileParser parser; const std::vector& paths = cfgMgr.getActiveConfigPaths(); if (paths.empty()) throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); + // Create file name strings for either the engine or the editor. + std::string defaultSettingsFile = ""; + std::string userSettingsFile = ""; + + if (!loadEditorSettings) + { + defaultSettingsFile = "defaults.bin"; + userSettingsFile = "settings.cfg"; + } + else + { + defaultSettingsFile = "defaults-cs.bin"; + userSettingsFile = "openmw-cs.cfg"; + } + // Create the settings manager and load default settings file. - const std::string defaultsBin = (paths.front() / "defaults.bin").string(); + const std::string defaultsBin = (paths.front() / defaultSettingsFile).string(); if (!boost::filesystem::exists(defaultsBin)) - throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); + throw std::runtime_error ("No default settings file found! Make sure the file \"" + defaultSettingsFile + "\" was properly installed."); parser.loadSettingsFile(defaultsBin, mDefaultSettings, true, false); - // Load "settings.cfg" from every config dir except the last one as additional default settings. + // Load "settings.cfg" or "openmw-cs.cfg" from every config dir except the last one as additional default settings. for (int i = 0; i < static_cast(paths.size()) - 1; ++i) { - const std::string additionalDefaults = (paths[i] / "settings.cfg").string(); + const std::string additionalDefaults = (paths[i] / userSettingsFile).string(); if (boost::filesystem::exists(additionalDefaults)) parser.loadSettingsFile(additionalDefaults, mDefaultSettings, false, true); } - // Load "settings.cfg" from the last config as user settings if they exist. This path will be used to save modified settings. - std::string settingspath = (paths.back() / "settings.cfg").string(); + // Load "settings.cfg" or "openmw-cs.cfg" from the last config dir as user settings. This path will be used to save modified settings. + std::string settingspath = (paths.back() / userSettingsFile).string(); if (boost::filesystem::exists(settingspath)) parser.loadSettingsFile(settingspath, mUserSettings, false, false); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 04831ef171..c23d8d878e 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -31,7 +31,7 @@ namespace Settings void clear(); ///< clears all settings and default settings - std::string load(const Files::ConfigurationManager& cfgMgr); + std::string load(const Files::ConfigurationManager& cfgMgr, bool loadEditorSettings = false); ///< load settings from all active config dirs. Returns the path of the last loaded file. void saveUser (const std::string& file); From db5770b44c077bad1b35b85e1782a92e11f855e5 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 4 Mar 2022 17:31:52 +0100 Subject: [PATCH 2186/2859] Fix bugs introduced by layer refactor --- files/mygui/openmw_chargen_select_specialization.layout | 2 +- files/mygui/openmw_layers.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/mygui/openmw_chargen_select_specialization.layout b/files/mygui/openmw_chargen_select_specialization.layout index 47dc32f629..9c5b54557b 100644 --- a/files/mygui/openmw_chargen_select_specialization.layout +++ b/files/mygui/openmw_chargen_select_specialization.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 3e267075ef..4c93574edd 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -9,7 +9,6 @@ - @@ -19,6 +18,7 @@ + From 3bcbd4c17090fb67d38dac4ce6ae1df20c5dcbfe Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 4 Mar 2022 21:58:39 +0100 Subject: [PATCH 2187/2859] Fix quickkeys assign UI layer --- files/mygui/openmw_quickkeys_menu_assign.layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/mygui/openmw_quickkeys_menu_assign.layout b/files/mygui/openmw_quickkeys_menu_assign.layout index 2c80fefcd8..649294e9d3 100644 --- a/files/mygui/openmw_quickkeys_menu_assign.layout +++ b/files/mygui/openmw_quickkeys_menu_assign.layout @@ -1,6 +1,6 @@ - + From d83a381f79a7c157b7ee720fb0ae55e379b532cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sun, 6 Mar 2022 15:58:16 +0200 Subject: [PATCH 2188/2859] Refactor and rename some things around Misc::Rng --- apps/openmw/mwmechanics/levelledlist.hpp | 10 +++--- apps/openmw/mwworld/containerstore.cpp | 28 ++++++++--------- apps/openmw/mwworld/containerstore.hpp | 6 ++-- components/misc/rng.cpp | 38 +++++++++------------- components/misc/rng.hpp | 40 +++++++++--------------- 5 files changed, 51 insertions(+), 71 deletions(-) diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index 25064123ce..a1eb958ed1 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -19,14 +19,14 @@ namespace MWMechanics { /// @return ID of resulting item, or empty if none - inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Seed& seed = Misc::Rng::getSeed()) + inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Generator& prng = Misc::Rng::getGenerator()) { const std::vector& items = levItem->mList; const MWWorld::Ptr& player = getPlayer(); int playerLevel = player.getClass().getCreatureStats(player).getLevel(); - if (Misc::Rng::roll0to99(seed) < levItem->mChanceNone) + if (Misc::Rng::roll0to99(prng) < levItem->mChanceNone) return std::string(); std::vector candidates; @@ -55,7 +55,7 @@ namespace MWMechanics } if (candidates.empty()) return std::string(); - std::string item = candidates[Misc::Rng::rollDice(candidates.size(), seed)]; + std::string item = candidates[Misc::Rng::rollDice(candidates.size(), prng)]; // Vanilla doesn't fail on nonexistent items in levelled lists if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) @@ -74,9 +74,9 @@ namespace MWMechanics else { if (ref.getPtr().getType() == ESM::ItemLevList::sRecordId) - return getLevelledItem(ref.getPtr().get()->mBase, false, seed); + return getLevelledItem(ref.getPtr().get()->mBase, false, prng); else - return getLevelledItem(ref.getPtr().get()->mBase, true, seed); + return getLevelledItem(ref.getPtr().get()->mBase, true, prng); } } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 1bed36bf9c..a096231f2d 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -514,12 +514,12 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor return count - toRemove; } -void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed) +void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Generator& prng) { for (const ESM::ContItem& iter : items.mList) { std::string id = Misc::StringUtils::lowerCase(iter.mItem); - addInitialItem(id, owner, iter.mCount, &seed); + addInitialItem(id, owner, iter.mCount, &prng); } flagAsModified(); @@ -540,7 +540,7 @@ void MWWorld::ContainerStore::fillNonRandom (const ESM::InventoryList& items, co } void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, - Misc::Rng::Seed* seed, bool topLevel) + Misc::Rng::Generator* prng, bool topLevel) { if (count == 0) return; //Don't restock with nothing. try @@ -548,13 +548,13 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); if (ref.getPtr().getClass().getScript(ref.getPtr()).empty()) { - addInitialItemImp(ref.getPtr(), owner, count, seed, topLevel); + addInitialItemImp(ref.getPtr(), owner, count, prng, topLevel); } else { // Adding just one item per time to make sure there isn't a stack of scripted items for (int i = 0; i < std::abs(count); i++) - addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, seed, topLevel); + addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, prng, topLevel); } } catch (const std::exception& e) @@ -564,26 +564,26 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: } void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, int count, - Misc::Rng::Seed* seed, bool topLevel) + Misc::Rng::Generator* prng, bool topLevel) { if (ptr.getType()==ESM::ItemLevList::sRecordId) { - if(!seed) + if(!prng) return; const ESM::ItemLevList* levItemList = ptr.get()->mBase; if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { for (int i=0; i 0 ? 1 : -1, seed, true); + addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, prng, true); return; } else { - std::string itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false, *seed); + std::string itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false, *prng); if (itemId.empty()) return; - addInitialItem(itemId, owner, count, seed, false); + addInitialItem(itemId, owner, count, prng, false); } } else @@ -619,8 +619,8 @@ void MWWorld::ContainerStore::resolve() { for(const auto&& ptr : *this) ptr.getRefData().setCount(0); - Misc::Rng::Seed seed{mSeed}; - fill(mPtr.get()->mBase->mInventory, "", seed); + Misc::Rng::Generator prng{mSeed}; + fill(mPtr.get()->mBase->mInventory, "", prng); addScripts(*this, mPtr.mCell); } mModified = true; @@ -640,8 +640,8 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() { for(const auto&& ptr : *this) ptr.getRefData().setCount(0); - Misc::Rng::Seed seed{mSeed}; - fill(mPtr.get()->mBase->mInventory, "", seed); + Misc::Rng::Generator prng{mSeed}; + fill(mPtr.get()->mBase->mInventory, "", prng); addScripts(*this, mPtr.mCell); } return {listener}; diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index ef0d7b5e57..1951f0708e 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -126,8 +126,8 @@ namespace MWWorld std::weak_ptr mResolutionListener; ContainerStoreIterator addImp (const Ptr& ptr, int count, bool markModified = true); - void addInitialItem (const std::string& id, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true); - void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true); + void addInitialItem (const std::string& id, const std::string& owner, int count, Misc::Rng::Generator* prng, bool topLevel=true); + void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Generator* prng, bool topLevel=true); template ContainerStoreIterator getState (CellRefList& collection, @@ -221,7 +221,7 @@ namespace MWWorld virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const; ///< @return true if the two specified objects can stack with each other - void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed = Misc::Rng::getSeed()); + void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Generator& seed = Misc::Rng::getGenerator()); ///< Insert items into *this. void fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed); diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index 113e7b1d5b..78c2a44434 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -3,50 +3,42 @@ #include #include -namespace +namespace Misc::Rng { - Misc::Rng::Seed sSeed; -} - -namespace Misc -{ - Rng::Seed::Seed(unsigned int seed) - { - mGenerator.seed(seed); - } + static Generator sGenerator; - Rng::Seed& Rng::getSeed() + Generator& getGenerator() { - return sSeed; + return sGenerator; } - void Rng::init(unsigned int seed) + void init(unsigned int seed) { - sSeed.mGenerator.seed(seed); + sGenerator.seed(seed); } - float Rng::rollProbability(Seed& seed) + float rollProbability(Generator& prng) { - return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(seed.mGenerator); + return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(prng); } - float Rng::rollClosedProbability(Seed& seed) + float rollClosedProbability(Generator& prng) { - return std::uniform_real_distribution(0, 1)(seed.mGenerator); + return std::uniform_real_distribution(0, 1)(prng); } - int Rng::rollDice(int max, Seed& seed) + int rollDice(int max, Generator& prng) { - return max > 0 ? std::uniform_int_distribution(0, max - 1)(seed.mGenerator) : 0; + return max > 0 ? std::uniform_int_distribution(0, max - 1)(prng) : 0; } - unsigned int Rng::generateDefaultSeed() + unsigned int generateDefaultSeed() { return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); } - float Rng::deviate(float mean, float deviation, Seed& seed) + float deviate(float mean, float deviation, Generator& prng) { - return std::uniform_real_distribution(mean - deviation, mean + deviation)(seed.mGenerator); + return std::uniform_real_distribution(mean - deviation, mean + deviation)(prng); } } diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 06e94897be..8d300f0913 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -4,47 +4,35 @@ #include #include -namespace Misc -{ - /* Provides central implementation of the RNG logic */ -class Rng +namespace Misc::Rng { -public: - class Seed - { - std::mt19937 mGenerator; - public: - Seed() = default; - Seed(const Seed&) = delete; - Seed(unsigned int seed); - friend class Rng; - }; - - static Seed& getSeed(); + + using Generator = std::mt19937; + + Generator& getGenerator(); + + /// returns default seed for RNG + unsigned int generateDefaultSeed(); /// seed the RNG - static void init(unsigned int seed = generateDefaultSeed()); + void init(unsigned int seed = generateDefaultSeed()); /// return value in range [0.0f, 1.0f) <- note open upper range. - static float rollProbability(Seed& seed = getSeed()); + float rollProbability(Generator& prng = getGenerator()); /// return value in range [0.0f, 1.0f] <- note closed upper range. - static float rollClosedProbability(Seed& seed = getSeed()); + float rollClosedProbability(Generator& prng = getGenerator()); /// return value in range [0, max) <- note open upper range. - static int rollDice(int max, Seed& seed = getSeed()); + int rollDice(int max, Generator& prng = getGenerator()); /// return value in range [0, 99] - static int roll0to99(Seed& seed = getSeed()) { return rollDice(100, seed); } - - /// returns default seed for RNG - static unsigned int generateDefaultSeed(); + inline int roll0to99(Generator& prng = getGenerator()) { return rollDice(100, prng); } - static float deviate(float mean, float deviation, Seed& seed = getSeed()); -}; + float deviate(float mean, float deviation, Generator& prng = getGenerator()); } From 54e114d83e10022ef961d1a9a7342e38b7c5200d Mon Sep 17 00:00:00 2001 From: Matt <3397065-ZehMatt@users.noreply.gitlab.com> Date: Sun, 6 Mar 2022 17:38:58 +0000 Subject: [PATCH 2189/2859] Avoid string copies for ESM::Variant::getString --- apps/openmw/mwgui/dialogue.cpp | 24 ++++++++++++------------ apps/openmw/mwgui/jailscreen.cpp | 2 +- components/esm3/variant.cpp | 2 +- components/esm3/variant.hpp | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 86cad0fa7a..1e740abe22 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -368,15 +368,15 @@ namespace MWGui const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - const std::string sPersuasion = gmst.find("sPersuasion")->mValue.getString(); - const std::string sCompanionShare = gmst.find("sCompanionShare")->mValue.getString(); - const std::string sBarter = gmst.find("sBarter")->mValue.getString(); - const std::string sSpells = gmst.find("sSpells")->mValue.getString(); - const std::string sTravel = gmst.find("sTravel")->mValue.getString(); - const std::string sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->mValue.getString(); - const std::string sEnchanting = gmst.find("sEnchanting")->mValue.getString(); - const std::string sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->mValue.getString(); - const std::string sRepair = gmst.find("sRepair")->mValue.getString(); + const std::string& sPersuasion = gmst.find("sPersuasion")->mValue.getString(); + const std::string& sCompanionShare = gmst.find("sCompanionShare")->mValue.getString(); + const std::string& sBarter = gmst.find("sBarter")->mValue.getString(); + const std::string& sSpells = gmst.find("sSpells")->mValue.getString(); + const std::string& sTravel = gmst.find("sTravel")->mValue.getString(); + const std::string& sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->mValue.getString(); + const std::string& sEnchanting = gmst.find("sEnchanting")->mValue.getString(); + const std::string& sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->mValue.getString(); + const std::string& sRepair = gmst.find("sRepair")->mValue.getString(); if (topic != sPersuasion && topic != sCompanionShare && topic != sBarter && topic != sSpells && topic != sTravel && topic != sSpellMakingMenuTitle @@ -605,10 +605,10 @@ namespace MWGui Goodbye* link = new Goodbye(); link->eventActivated += MyGUI::newDelegate(this, &DialogueWindow::onGoodbyeActivated); mLinks.push_back(link); - std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->mValue.getString(); + const std::string& goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->mValue.getString(); BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver, - textColours.answerPressed, - TypesetBook::InteractiveId(link)); + textColours.answerPressed, + TypesetBook::InteractiveId(link)); typesetter->lineBreak(); typesetter->write(questionStyle, to_utf8_span(goodbye.c_str())); } diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index a029fe54b6..53219a29d6 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -109,7 +109,7 @@ namespace MWGui for (const int& skill : skills) { - std::string skillName = gmst.find(ESM::Skill::sSkillNameIds[skill])->mValue.getString(); + const std::string& skillName = gmst.find(ESM::Skill::sSkillNameIds[skill])->mValue.getString(); int skillValue = player.getClass().getNpcStats(player).getSkill(skill).getBase(); std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString(); if (skill == ESM::Skill::Sneak || skill == ESM::Skill::Security) diff --git a/components/esm3/variant.cpp b/components/esm3/variant.cpp index a0f6ca7b41..732d7e7f29 100644 --- a/components/esm3/variant.cpp +++ b/components/esm3/variant.cpp @@ -48,7 +48,7 @@ namespace }; } -std::string ESM::Variant::getString() const +const std::string& ESM::Variant::getString() const { return std::get(mData); } diff --git a/components/esm3/variant.hpp b/components/esm3/variant.hpp index f0a16d4d53..3d706c163f 100644 --- a/components/esm3/variant.hpp +++ b/components/esm3/variant.hpp @@ -49,7 +49,7 @@ namespace ESM VarType getType() const { return mType; } - std::string getString() const; + const std::string& getString() const; ///< Will throw an exception, if value can not be represented as a string. int getInteger() const; From 3a117cac2247c41a28b537a2e4cc494f2159dc78 Mon Sep 17 00:00:00 2001 From: duncanspumpkin Date: Mon, 7 Mar 2022 16:05:24 +0000 Subject: [PATCH 2190/2859] Switch to a constexpr for FourCC constant Add static asssert on wrong size --- apps/essimporter/importer.cpp | 16 ++-- components/esm/defs.hpp | 160 +++++++++++++++---------------- components/esm3/aisequence.hpp | 14 +-- components/esm3/cellref.cpp | 34 +++---- components/esm3/debugprofile.cpp | 6 +- components/esm3/filter.cpp | 4 +- components/esm3/loadacti.cpp | 6 +- components/esm3/loadalch.cpp | 12 +-- components/esm3/loadappa.cpp | 10 +- components/esm3/loadarmo.cpp | 14 +-- components/esm3/loadbody.cpp | 6 +- components/esm3/loadbook.cpp | 14 +-- components/esm3/loadbsgn.cpp | 8 +- components/esm3/loadcell.cpp | 14 +-- components/esm3/loadclas.cpp | 6 +- components/esm3/loadclot.cpp | 14 +-- components/esm3/loadcont.cpp | 12 +-- components/esm3/loadcrea.cpp | 26 ++--- components/esm3/loaddial.cpp | 2 +- components/esm3/loaddoor.cpp | 10 +- components/esm3/loadench.cpp | 4 +- components/esm3/loadfact.cpp | 8 +- components/esm3/loadinfo.cpp | 26 ++--- components/esm3/loadingr.cpp | 10 +- components/esm3/loadland.cpp | 14 +-- components/esm3/loadlevlist.cpp | 6 +- components/esm3/loadligh.cpp | 12 +-- components/esm3/loadlock.cpp | 10 +- components/esm3/loadltex.cpp | 4 +- components/esm3/loadmgef.cpp | 22 ++--- components/esm3/loadmisc.cpp | 10 +- components/esm3/loadnpc.cpp | 30 +++--- components/esm3/loadpgrd.cpp | 6 +- components/esm3/loadprob.cpp | 10 +- components/esm3/loadrace.cpp | 8 +- components/esm3/loadregn.cpp | 10 +- components/esm3/loadrepa.cpp | 10 +- components/esm3/loadscpt.cpp | 8 +- components/esm3/loadskil.cpp | 6 +- components/esm3/loadsndg.cpp | 6 +- components/esm3/loadsoun.cpp | 4 +- components/esm3/loadspel.cpp | 6 +- components/esm3/loadsscr.cpp | 2 +- components/esm3/loadstat.cpp | 2 +- components/esm3/loadweap.cpp | 12 +-- components/esm3/transport.cpp | 4 +- components/esm3/variant.cpp | 8 +- 47 files changed, 323 insertions(+), 323 deletions(-) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 710c2e9ab2..9931385f8c 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -263,14 +263,14 @@ namespace ESSImport const ESM::Header& header = esm.getHeader(); context.mPlayerCellName = header.mGameData.mCurrentCell.toString(); - const unsigned int recREFR = ESM::FourCC<'R','E','F','R'>::value; - const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value; - const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value; - const unsigned int recKLST = ESM::FourCC<'K','L','S','T'>::value; - const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value; - const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value; - const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value; - const unsigned int recSPLM = ESM::FourCC<'S','P','L','M'>::value; + const unsigned int recREFR = ESM::fourCC("REFR"); + const unsigned int recPCDT = ESM::fourCC("PCDT"); + const unsigned int recFMAP = ESM::fourCC("FMAP"); + const unsigned int recKLST = ESM::fourCC("KLST"); + const unsigned int recSTLN = ESM::fourCC("STLN"); + const unsigned int recGAME = ESM::fourCC("GAME"); + const unsigned int recJOUR = ESM::fourCC("JOUR"); + const unsigned int recSPLM = ESM::fourCC("SPLM"); std::map > converters; converters[ESM::REC_GLOB] = std::shared_ptr(new ConvertGlobal()); diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index a7d2f44dd7..f587fc6690 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -61,7 +61,7 @@ struct Position friend inline bool operator<(const Position& l, const Position& r) { - const auto tuple = [] (const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; + const auto tuple = [](const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; return tuple(l) < tuple(r); } }; @@ -86,100 +86,100 @@ bool inline operator!= (const Position& left, const Position& right) noexcept left.rot[2] != right.rot[2]; } -template -struct FourCC -{ - static constexpr unsigned int value = (((((d << 8) | c) << 8) | b) << 8) | a; -}; +template +constexpr unsigned int fourCC(const char(&name)[len]) { + static_assert(len == 5, "Constant must be 4 characters long. (Plus null terminator)"); + return static_cast(name[0]) | (static_cast(name[1]) << 8) | (static_cast(name[2]) << 16) | (static_cast(name[3]) << 24); +} enum RecNameInts : unsigned int { // format 0 / legacy - REC_ACTI = FourCC<'A','C','T','I'>::value, - REC_ALCH = FourCC<'A','L','C','H'>::value, - REC_APPA = FourCC<'A','P','P','A'>::value, - REC_ARMO = FourCC<'A','R','M','O'>::value, - REC_BODY = FourCC<'B','O','D','Y'>::value, - REC_BOOK = FourCC<'B','O','O','K'>::value, - REC_BSGN = FourCC<'B','S','G','N'>::value, - REC_CELL = FourCC<'C','E','L','L'>::value, - REC_CLAS = FourCC<'C','L','A','S'>::value, - REC_CLOT = FourCC<'C','L','O','T'>::value, - REC_CNTC = FourCC<'C','N','T','C'>::value, - REC_CONT = FourCC<'C','O','N','T'>::value, - REC_CREA = FourCC<'C','R','E','A'>::value, - REC_CREC = FourCC<'C','R','E','C'>::value, - REC_DIAL = FourCC<'D','I','A','L'>::value, - REC_DOOR = FourCC<'D','O','O','R'>::value, - REC_ENCH = FourCC<'E','N','C','H'>::value, - REC_FACT = FourCC<'F','A','C','T'>::value, - REC_GLOB = FourCC<'G','L','O','B'>::value, - REC_GMST = FourCC<'G','M','S','T'>::value, - REC_INFO = FourCC<'I','N','F','O'>::value, - REC_INGR = FourCC<'I','N','G','R'>::value, - REC_LAND = FourCC<'L','A','N','D'>::value, - REC_LEVC = FourCC<'L','E','V','C'>::value, - REC_LEVI = FourCC<'L','E','V','I'>::value, - REC_LIGH = FourCC<'L','I','G','H'>::value, - REC_LOCK = FourCC<'L','O','C','K'>::value, - REC_LTEX = FourCC<'L','T','E','X'>::value, - REC_MGEF = FourCC<'M','G','E','F'>::value, - REC_MISC = FourCC<'M','I','S','C'>::value, - REC_NPC_ = FourCC<'N','P','C','_'>::value, - REC_NPCC = FourCC<'N','P','C','C'>::value, - REC_PGRD = FourCC<'P','G','R','D'>::value, - REC_PROB = FourCC<'P','R','O','B'>::value, - REC_RACE = FourCC<'R','A','C','E'>::value, - REC_REGN = FourCC<'R','E','G','N'>::value, - REC_REPA = FourCC<'R','E','P','A'>::value, - REC_SCPT = FourCC<'S','C','P','T'>::value, - REC_SKIL = FourCC<'S','K','I','L'>::value, - REC_SNDG = FourCC<'S','N','D','G'>::value, - REC_SOUN = FourCC<'S','O','U','N'>::value, - REC_SPEL = FourCC<'S','P','E','L'>::value, - REC_SSCR = FourCC<'S','S','C','R'>::value, - REC_STAT = FourCC<'S','T','A','T'>::value, - REC_WEAP = FourCC<'W','E','A','P'>::value, + REC_ACTI = fourCC("ACTI"), + REC_ALCH = fourCC("ALCH"), + REC_APPA = fourCC("APPA"), + REC_ARMO = fourCC("ARMO"), + REC_BODY = fourCC("BODY"), + REC_BOOK = fourCC("BOOK"), + REC_BSGN = fourCC("BSGN"), + REC_CELL = fourCC("CELL"), + REC_CLAS = fourCC("CLAS"), + REC_CLOT = fourCC("CLOT"), + REC_CNTC = fourCC("CNTC"), + REC_CONT = fourCC("CONT"), + REC_CREA = fourCC("CREA"), + REC_CREC = fourCC("CREC"), + REC_DIAL = fourCC("DIAL"), + REC_DOOR = fourCC("DOOR"), + REC_ENCH = fourCC("ENCH"), + REC_FACT = fourCC("FACT"), + REC_GLOB = fourCC("GLOB"), + REC_GMST = fourCC("GMST"), + REC_INFO = fourCC("INFO"), + REC_INGR = fourCC("INGR"), + REC_LAND = fourCC("LAND"), + REC_LEVC = fourCC("LEVC"), + REC_LEVI = fourCC("LEVI"), + REC_LIGH = fourCC("LIGH"), + REC_LOCK = fourCC("LOCK"), + REC_LTEX = fourCC("LTEX"), + REC_MGEF = fourCC("MGEF"), + REC_MISC = fourCC("MISC"), + REC_NPC_ = fourCC("NPC_"), + REC_NPCC = fourCC("NPCC"), + REC_PGRD = fourCC("PGRD"), + REC_PROB = fourCC("PROB"), + REC_RACE = fourCC("RACE"), + REC_REGN = fourCC("REGN"), + REC_REPA = fourCC("REPA"), + REC_SCPT = fourCC("SCPT"), + REC_SKIL = fourCC("SKIL"), + REC_SNDG = fourCC("SNDG"), + REC_SOUN = fourCC("SOUN"), + REC_SPEL = fourCC("SPEL"), + REC_SSCR = fourCC("SSCR"), + REC_STAT = fourCC("STAT"), + REC_WEAP = fourCC("WEAP"), // format 0 - saved games - REC_SAVE = FourCC<'S','A','V','E'>::value, - REC_JOUR_LEGACY = FourCC<0xa4,'U','O','R'>::value, // "\xa4UOR", rather than "JOUR", little oversight when magic numbers were - // calculated by hand, needs to be supported for older files now - REC_JOUR = FourCC<'J','O','U','R'>::value, - REC_QUES = FourCC<'Q','U','E','S'>::value, - REC_GSCR = FourCC<'G','S','C','R'>::value, - REC_PLAY = FourCC<'P','L','A','Y'>::value, - REC_CSTA = FourCC<'C','S','T','A'>::value, - REC_GMAP = FourCC<'G','M','A','P'>::value, - REC_DIAS = FourCC<'D','I','A','S'>::value, - REC_WTHR = FourCC<'W','T','H','R'>::value, - REC_KEYS = FourCC<'K','E','Y','S'>::value, - REC_DYNA = FourCC<'D','Y','N','A'>::value, - REC_ASPL = FourCC<'A','S','P','L'>::value, - REC_ACTC = FourCC<'A','C','T','C'>::value, - REC_MPRJ = FourCC<'M','P','R','J'>::value, - REC_PROJ = FourCC<'P','R','O','J'>::value, - REC_DCOU = FourCC<'D','C','O','U'>::value, - REC_MARK = FourCC<'M','A','R','K'>::value, - REC_ENAB = FourCC<'E','N','A','B'>::value, - REC_CAM_ = FourCC<'C','A','M','_'>::value, - REC_STLN = FourCC<'S','T','L','N'>::value, - REC_INPU = FourCC<'I','N','P','U'>::value, + REC_SAVE = fourCC("SAVE"), + REC_JOUR_LEGACY = fourCC("\xa4UOR"), // "\xa4UOR", rather than "JOUR", little oversight when magic numbers were + // calculated by hand, needs to be supported for older files now + REC_JOUR = fourCC("JOUR"), + REC_QUES = fourCC("QUES"), + REC_GSCR = fourCC("GSCR"), + REC_PLAY = fourCC("PLAY"), + REC_CSTA = fourCC("CSTA"), + REC_GMAP = fourCC("GMAP"), + REC_DIAS = fourCC("DIAS"), + REC_WTHR = fourCC("WTHR"), + REC_KEYS = fourCC("KEYS"), + REC_DYNA = fourCC("DYNA"), + REC_ASPL = fourCC("ASPL"), + REC_ACTC = fourCC("ACTC"), + REC_MPRJ = fourCC("MPRJ"), + REC_PROJ = fourCC("PROJ"), + REC_DCOU = fourCC("DCOU"), + REC_MARK = fourCC("MARK"), + REC_ENAB = fourCC("ENAB"), + REC_CAM_ = fourCC("CAM_"), + REC_STLN = fourCC("STLN"), + REC_INPU = fourCC("INPU"), // format 1 - REC_FILT = FourCC<'F','I','L','T'>::value, - REC_DBGP = FourCC<'D','B','G','P'>::value, ///< only used in project files - REC_LUAL = FourCC<'L','U','A','L'>::value, // LuaScriptsCfg + REC_FILT = fourCC("FILT"), + REC_DBGP = fourCC("DBGP"), ///< only used in project files + REC_LUAL = fourCC("LUAL"), // LuaScriptsCfg // format 16 - Lua scripts in saved games - REC_LUAM = FourCC<'L','U','A','M'>::value, // LuaManager data + REC_LUAM = fourCC("LUAM"), // LuaManager data }; /// Common subrecords enum SubRecNameInts { - SREC_DELE = ESM::FourCC<'D','E','L','E'>::value, - SREC_NAME = ESM::FourCC<'N','A','M','E'>::value + SREC_DELE = ESM::fourCC("DELE"), + SREC_NAME = ESM::fourCC("NAME") }; } diff --git a/components/esm3/aisequence.hpp b/components/esm3/aisequence.hpp index 3dcecc1cb0..412c7401bf 100644 --- a/components/esm3/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -22,13 +22,13 @@ namespace ESM enum AiPackages { - Ai_Wander = ESM::FourCC<'W','A','N','D'>::value, - Ai_Travel = ESM::FourCC<'T','R','A','V'>::value, - Ai_Escort = ESM::FourCC<'E','S','C','O'>::value, - Ai_Follow = ESM::FourCC<'F','O','L','L'>::value, - Ai_Activate = ESM::FourCC<'A','C','T','I'>::value, - Ai_Combat = ESM::FourCC<'C','O','M','B'>::value, - Ai_Pursue = ESM::FourCC<'P','U','R','S'>::value + Ai_Wander = ESM::fourCC("WAND"), + Ai_Travel = ESM::fourCC("TRAV"), + Ai_Escort = ESM::fourCC("ESCO"), + Ai_Follow = ESM::fourCC("FOLL"), + Ai_Activate = ESM::fourCC("ACTI"), + Ai_Combat = ESM::fourCC("COMB"), + Ai_Pursue = ESM::fourCC("PURS") }; diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 8487b3c0f0..1a0320d823 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -63,57 +63,57 @@ void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'U','N','A','M'>::value: + case ESM::fourCC("UNAM"): esm.getHT(mReferenceBlocked); break; - case ESM::FourCC<'X','S','C','L'>::value: + case ESM::fourCC("XSCL"): esm.getHT(mScale); mScale = std::clamp(mScale, 0.5f, 2.0f); break; - case ESM::FourCC<'A','N','A','M'>::value: + case ESM::fourCC("ANAM"): mOwner = esm.getHString(); break; - case ESM::FourCC<'B','N','A','M'>::value: + case ESM::fourCC("BNAM"): mGlobalVariable = esm.getHString(); break; - case ESM::FourCC<'X','S','O','L'>::value: + case ESM::fourCC("XSOL"): mSoul = esm.getHString(); break; - case ESM::FourCC<'C','N','A','M'>::value: + case ESM::fourCC("CNAM"): mFaction = esm.getHString(); break; - case ESM::FourCC<'I','N','D','X'>::value: + case ESM::fourCC("INDX"): esm.getHT(mFactionRank); break; - case ESM::FourCC<'X','C','H','G'>::value: + case ESM::fourCC("XCHG"): esm.getHT(mEnchantmentCharge); break; - case ESM::FourCC<'I','N','T','V'>::value: + case ESM::fourCC("INTV"): esm.getHT(mChargeInt); break; - case ESM::FourCC<'N','A','M','9'>::value: + case ESM::fourCC("NAM9"): esm.getHT(mGoldValue); break; - case ESM::FourCC<'D','O','D','T'>::value: + case ESM::fourCC("DODT"): esm.getHT(mDoorDest); mTeleport = true; break; - case ESM::FourCC<'D','N','A','M'>::value: + case ESM::fourCC("DNAM"): mDestCell = esm.getHString(); break; - case ESM::FourCC<'F','L','T','V'>::value: + case ESM::fourCC("FLTV"): esm.getHT(mLockLevel); break; - case ESM::FourCC<'K','N','A','M'>::value: + case ESM::fourCC("KNAM"): mKey = esm.getHString(); break; - case ESM::FourCC<'T','N','A','M'>::value: + case ESM::fourCC("TNAM"): mTrap = esm.getHString(); break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): esm.getHT(mPos, 24); break; - case ESM::FourCC<'N','A','M','0'>::value: + case ESM::fourCC("NAM0"): { esm.skipHSub(); break; diff --git a/components/esm3/debugprofile.cpp b/components/esm3/debugprofile.cpp index f862d4a289..87c3bd13ba 100644 --- a/components/esm3/debugprofile.cpp +++ b/components/esm3/debugprofile.cpp @@ -19,13 +19,13 @@ void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) case ESM::SREC_NAME: mId = esm.getHString(); break; - case ESM::FourCC<'D','E','S','C'>::value: + case ESM::fourCC("DESC"): mDescription = esm.getHString(); break; - case ESM::FourCC<'S','C','R','P'>::value: + case ESM::fourCC("SCRP"): mScriptText = esm.getHString(); break; - case ESM::FourCC<'F','L','A','G'>::value: + case ESM::fourCC("FLAG"): esm.getHT(mFlags); break; case ESM::SREC_DELE: diff --git a/components/esm3/filter.cpp b/components/esm3/filter.cpp index 03fa4ba278..a031e0f608 100644 --- a/components/esm3/filter.cpp +++ b/components/esm3/filter.cpp @@ -20,10 +20,10 @@ void ESM::Filter::load (ESMReader& esm, bool &isDeleted) case ESM::SREC_NAME: mId = esm.getHString(); break; - case ESM::FourCC<'F','I','L','T'>::value: + case ESM::fourCC("FILT"): mFilter = esm.getHString(); break; - case ESM::FourCC<'D','E','S','C'>::value: + case ESM::fourCC("DESC"): mDescription = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadacti.cpp b/components/esm3/loadacti.cpp index c4aa9cb6b8..d57ef44be2 100644 --- a/components/esm3/loadacti.cpp +++ b/components/esm3/loadacti.cpp @@ -23,13 +23,13 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadalch.cpp b/components/esm3/loadalch.cpp index 618e116cce..1aacfc8976 100644 --- a/components/esm3/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -26,23 +26,23 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'T','E','X','T'>::value: // not ITEX here for some reason + case ESM::fourCC("TEXT"): // not ITEX here for some reason mIcon = esm.getHString(); break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'A','L','D','T'>::value: + case ESM::fourCC("ALDT"): esm.getHT(mData, 12); hasData = true; break; - case ESM::FourCC<'E','N','A','M'>::value: + case ESM::fourCC("ENAM"): mEffects.add(esm); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadappa.cpp b/components/esm3/loadappa.cpp index 13ba0fbb42..f29e5bf4f9 100644 --- a/components/esm3/loadappa.cpp +++ b/components/esm3/loadappa.cpp @@ -24,20 +24,20 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'A','A','D','T'>::value: + case ESM::fourCC("AADT"): esm.getHT(mData); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadarmo.cpp b/components/esm3/loadarmo.cpp index 9c5164f8a6..b476e930a8 100644 --- a/components/esm3/loadarmo.cpp +++ b/components/esm3/loadarmo.cpp @@ -56,26 +56,26 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'A','O','D','T'>::value: + case ESM::fourCC("AODT"): esm.getHT(mData, 24); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::FourCC<'E','N','A','M'>::value: + case ESM::fourCC("ENAM"): mEnchant = esm.getHString(); break; - case ESM::FourCC<'I','N','D','X'>::value: + case ESM::fourCC("INDX"): mParts.add(esm); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadbody.cpp b/components/esm3/loadbody.cpp index 1098941c25..148e80d94c 100644 --- a/components/esm3/loadbody.cpp +++ b/components/esm3/loadbody.cpp @@ -24,13 +24,13 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mRace = esm.getHString(); break; - case ESM::FourCC<'B','Y','D','T'>::value: + case ESM::fourCC("BYDT"): esm.getHT(mData, 4); hasData = true; break; diff --git a/components/esm3/loadbook.cpp b/components/esm3/loadbook.cpp index c03046ed9f..72618f3ce8 100644 --- a/components/esm3/loadbook.cpp +++ b/components/esm3/loadbook.cpp @@ -24,26 +24,26 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'B','K','D','T'>::value: + case ESM::fourCC("BKDT"): esm.getHT(mData, 20); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::FourCC<'E','N','A','M'>::value: + case ESM::fourCC("ENAM"): mEnchant = esm.getHString(); break; - case ESM::FourCC<'T','E','X','T'>::value: + case ESM::fourCC("TEXT"): mText = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadbsgn.cpp b/components/esm3/loadbsgn.cpp index d02fc93914..c894918bb2 100644 --- a/components/esm3/loadbsgn.cpp +++ b/components/esm3/loadbsgn.cpp @@ -25,16 +25,16 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'T','N','A','M'>::value: + case ESM::fourCC("TNAM"): mTexture = esm.getHString(); break; - case ESM::FourCC<'D','E','S','C'>::value: + case ESM::fourCC("DESC"): mDescription = esm.getHString(); break; - case ESM::FourCC<'N','P','C','S'>::value: + case ESM::fourCC("NPCS"): mPowers.add(esm); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 256be62898..6a4cd6ebf9 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -75,7 +75,7 @@ namespace ESM case ESM::SREC_NAME: mName = esm.getHString(); break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): esm.getHT(mData, 12); hasData = true; break; @@ -119,13 +119,13 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'I','N','T','V'>::value: + case ESM::fourCC("INTV"): int waterl; esm.getHT(waterl); mWater = static_cast(waterl); mWaterInt = true; break; - case ESM::FourCC<'W','H','G','T'>::value: + case ESM::fourCC("WHGT"): float waterLevel; esm.getHT(waterLevel); mWaterInt = false; @@ -138,17 +138,17 @@ namespace ESM else mWater = waterLevel; break; - case ESM::FourCC<'A','M','B','I'>::value: + case ESM::fourCC("AMBI"): esm.getHT(mAmbi); mHasAmbi = true; break; - case ESM::FourCC<'R','G','N','N'>::value: + case ESM::fourCC("RGNN"): mRegion = esm.getHString(); break; - case ESM::FourCC<'N','A','M','5'>::value: + case ESM::fourCC("NAM5"): esm.getHT(mMapColor); break; - case ESM::FourCC<'N','A','M','0'>::value: + case ESM::fourCC("NAM0"): esm.getHT(mRefNumCounter); break; default: diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index 0bd966e752..ecd43796de 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -54,16 +54,16 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'C','L','D','T'>::value: + case ESM::fourCC("CLDT"): esm.getHT(mData, 60); if (mData.mIsPlayable > 1) esm.fail("Unknown bool value"); hasData = true; break; - case ESM::FourCC<'D','E','S','C'>::value: + case ESM::fourCC("DESC"): mDescription = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadclot.cpp b/components/esm3/loadclot.cpp index 05a895b0f4..1bd5db9655 100644 --- a/components/esm3/loadclot.cpp +++ b/components/esm3/loadclot.cpp @@ -26,26 +26,26 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'C','T','D','T'>::value: + case ESM::fourCC("CTDT"): esm.getHT(mData, 12); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::FourCC<'E','N','A','M'>::value: + case ESM::fourCC("ENAM"): mEnchant = esm.getHString(); break; - case ESM::FourCC<'I','N','D','X'>::value: + case ESM::fourCC("INDX"): mParts.add(esm); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadcont.cpp b/components/esm3/loadcont.cpp index 19bb649125..5cbdd80428 100644 --- a/components/esm3/loadcont.cpp +++ b/components/esm3/loadcont.cpp @@ -48,17 +48,17 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'C','N','D','T'>::value: + case ESM::fourCC("CNDT"): esm.getHT(mWeight, 4); hasWeight = true; break; - case ESM::FourCC<'F','L','A','G'>::value: + case ESM::fourCC("FLAG"): esm.getHT(mFlags, 4); if (mFlags & 0xf4) esm.fail("Unknown flags"); @@ -66,10 +66,10 @@ namespace ESM esm.fail("Flag 8 not set"); hasFlags = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'N','P','C','O'>::value: + case ESM::fourCC("NPCO"): mInventory.add(esm); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 1cec61b3ab..f1203ed39e 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -37,43 +37,43 @@ namespace ESM { mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'C','N','A','M'>::value: + case ESM::fourCC("CNAM"): mOriginal = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'N','P','D','T'>::value: + case ESM::fourCC("NPDT"): esm.getHT(mData, 96); hasNpdt = true; break; - case ESM::FourCC<'F','L','A','G'>::value: + case ESM::fourCC("FLAG"): int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; hasFlags = true; break; - case ESM::FourCC<'X','S','C','L'>::value: + case ESM::fourCC("XSCL"): esm.getHT(mScale); break; - case ESM::FourCC<'N','P','C','O'>::value: + case ESM::fourCC("NPCO"): mInventory.add(esm); break; - case ESM::FourCC<'N','P','C','S'>::value: + case ESM::fourCC("NPCS"): mSpells.add(esm); break; - case ESM::FourCC<'A','I','D','T'>::value: + case ESM::fourCC("AIDT"): esm.getHExact(&mAiData, sizeof(mAiData)); break; - case ESM::FourCC<'D','O','D','T'>::value: - case ESM::FourCC<'D','N','A','M'>::value: + case ESM::fourCC("DODT"): + case ESM::fourCC("DNAM"): mTransport.add(esm); break; case AI_Wander: @@ -88,7 +88,7 @@ namespace ESM { esm.skipHSub(); isDeleted = true; break; - case ESM::FourCC<'I','N','D','X'>::value: + case ESM::fourCC("INDX"): // seems to occur only in .ESS files, unsure of purpose int index; esm.getHT(index); diff --git a/components/esm3/loaddial.cpp b/components/esm3/loaddial.cpp index 67696be821..0c985def5b 100644 --- a/components/esm3/loaddial.cpp +++ b/components/esm3/loaddial.cpp @@ -30,7 +30,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): { esm.getSubHeader(); int size = esm.getSubSize(); diff --git a/components/esm3/loaddoor.cpp b/components/esm3/loaddoor.cpp index 48fa0faaf3..e059a1dc05 100644 --- a/components/esm3/loaddoor.cpp +++ b/components/esm3/loaddoor.cpp @@ -23,19 +23,19 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'S','N','A','M'>::value: + case ESM::fourCC("SNAM"): mOpenSound = esm.getHString(); break; - case ESM::FourCC<'A','N','A','M'>::value: + case ESM::fourCC("ANAM"): mCloseSound = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadench.cpp b/components/esm3/loadench.cpp index fae0aa211b..2e138444f3 100644 --- a/components/esm3/loadench.cpp +++ b/components/esm3/loadench.cpp @@ -25,11 +25,11 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'E','N','D','T'>::value: + case ESM::fourCC("ENDT"): esm.getHT(mData, 16); hasData = true; break; - case ESM::FourCC<'E','N','A','M'>::value: + case ESM::fourCC("ENAM"): mEffects.add(esm); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadfact.cpp b/components/esm3/loadfact.cpp index 554c30e40e..4d4697d93b 100644 --- a/components/esm3/loadfact.cpp +++ b/components/esm3/loadfact.cpp @@ -47,21 +47,21 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'R','N','A','M'>::value: + case ESM::fourCC("RNAM"): if (rankCounter >= 10) esm.fail("Rank out of range"); mRanks[rankCounter++] = esm.getHString(); break; - case ESM::FourCC<'F','A','D','T'>::value: + case ESM::fourCC("FADT"): esm.getHT(mData, 240); if (mData.mIsHidden > 1) esm.fail("Unknown flag!"); hasData = true; break; - case ESM::FourCC<'A','N','A','M'>::value: + case ESM::fourCC("ANAM"): { std::string faction = esm.getHString(); int reaction; diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 5cc1f9a090..0b5030bb82 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -25,19 +25,19 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): esm.getHT(mData, 12); break; - case ESM::FourCC<'O','N','A','M'>::value: + case ESM::fourCC("ONAM"): mActor = esm.getHString(); break; - case ESM::FourCC<'R','N','A','M'>::value: + case ESM::fourCC("RNAM"): mRace = esm.getHString(); break; - case ESM::FourCC<'C','N','A','M'>::value: + case ESM::fourCC("CNAM"): mClass = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): { mFaction = esm.getHString(); if (mFaction == "FFFF") @@ -46,19 +46,19 @@ namespace ESM } break; } - case ESM::FourCC<'A','N','A','M'>::value: + case ESM::fourCC("ANAM"): mCell = esm.getHString(); break; - case ESM::FourCC<'D','N','A','M'>::value: + case ESM::fourCC("DNAM"): mPcFaction = esm.getHString(); break; - case ESM::FourCC<'S','N','A','M'>::value: + case ESM::fourCC("SNAM"): mSound = esm.getHString(); break; case ESM::SREC_NAME: mResponse = esm.getHString(); break; - case ESM::FourCC<'S','C','V','R'>::value: + case ESM::fourCC("SCVR"): { SelectStruct ss; ss.mSelectRule = esm.getHString(); @@ -66,18 +66,18 @@ namespace ESM mSelects.push_back(ss); break; } - case ESM::FourCC<'B','N','A','M'>::value: + case ESM::fourCC("BNAM"): mResultScript = esm.getHString(); break; - case ESM::FourCC<'Q','S','T','N'>::value: + case ESM::fourCC("QSTN"): mQuestStatus = QS_Name; esm.skipRecord(); break; - case ESM::FourCC<'Q','S','T','F'>::value: + case ESM::fourCC("QSTF"): mQuestStatus = QS_Finished; esm.skipRecord(); break; - case ESM::FourCC<'Q','S','T','R'>::value: + case ESM::fourCC("QSTR"): mQuestStatus = QS_Restart; esm.skipRecord(); break; diff --git a/components/esm3/loadingr.cpp b/components/esm3/loadingr.cpp index 652e381881..173e2c5636 100644 --- a/components/esm3/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -24,20 +24,20 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'I','R','D','T'>::value: + case ESM::fourCC("IRDT"): esm.getHT(mData, 56); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index 24cac7f015..f9563411a3 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -46,7 +46,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'I','N','T','V'>::value: + case ESM::fourCC("INTV"): esm.getSubHeader(); if (esm.getSubSize() != 8) esm.fail("Subrecord size is not equal to 8"); @@ -54,7 +54,7 @@ namespace ESM esm.getT(mY); hasLocation = true; break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): esm.getHT(mFlags); break; case ESM::SREC_DELE: @@ -82,23 +82,23 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'V','N','M','L'>::value: + case ESM::fourCC("VNML"): esm.skipHSub(); mDataTypes |= DATA_VNML; break; - case ESM::FourCC<'V','H','G','T'>::value: + case ESM::fourCC("VHGT"): esm.skipHSub(); mDataTypes |= DATA_VHGT; break; - case ESM::FourCC<'W','N','A','M'>::value: + case ESM::fourCC("WNAM"): esm.getHExact(mWnam, sizeof(mWnam)); mDataTypes |= DATA_WNAM; break; - case ESM::FourCC<'V','C','L','R'>::value: + case ESM::fourCC("VCLR"): esm.skipHSub(); mDataTypes |= DATA_VCLR; break; - case ESM::FourCC<'V','T','E','X'>::value: + case ESM::fourCC("VTEX"): esm.skipHSub(); mDataTypes |= DATA_VTEX; break; diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 2aa209fadb..ace5304152 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -22,13 +22,13 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): esm.getHT(mFlags); break; - case ESM::FourCC<'N','N','A','M'>::value: + case ESM::fourCC("NNAM"): esm.getHT(mChanceNone); break; - case ESM::FourCC<'I','N','D','X'>::value: + case ESM::fourCC("INDX"): { int length = 0; esm.getHT(length); diff --git a/components/esm3/loadligh.cpp b/components/esm3/loadligh.cpp index 4e8c41fbb9..94c109e677 100644 --- a/components/esm3/loadligh.cpp +++ b/components/esm3/loadligh.cpp @@ -24,23 +24,23 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::FourCC<'L','H','D','T'>::value: + case ESM::fourCC("LHDT"): esm.getHT(mData, 24); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'S','N','A','M'>::value: + case ESM::fourCC("SNAM"): mSound = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadlock.cpp b/components/esm3/loadlock.cpp index 10ac680ab5..50d27d4e1a 100644 --- a/components/esm3/loadlock.cpp +++ b/components/esm3/loadlock.cpp @@ -24,20 +24,20 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'L','K','D','T'>::value: + case ESM::fourCC("LKDT"): esm.getHT(mData, 16); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadltex.cpp b/components/esm3/loadltex.cpp index 3d01c4fe17..de5eb53e56 100644 --- a/components/esm3/loadltex.cpp +++ b/components/esm3/loadltex.cpp @@ -23,11 +23,11 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'I','N','T','V'>::value: + case ESM::fourCC("INTV"): esm.getHT(mIndex); hasIndex = true; break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): mTexture = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index e86907564c..f15d51def6 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -211,37 +211,37 @@ void MagicEffect::load(ESMReader &esm, bool &isDeleted) esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::FourCC<'P','T','E','X'>::value: + case ESM::fourCC("PTEX"): mParticle = esm.getHString(); break; - case ESM::FourCC<'B','S','N','D'>::value: + case ESM::fourCC("BSND"): mBoltSound = esm.getHString(); break; - case ESM::FourCC<'C','S','N','D'>::value: + case ESM::fourCC("CSND"): mCastSound = esm.getHString(); break; - case ESM::FourCC<'H','S','N','D'>::value: + case ESM::fourCC("HSND"): mHitSound = esm.getHString(); break; - case ESM::FourCC<'A','S','N','D'>::value: + case ESM::fourCC("ASND"): mAreaSound = esm.getHString(); break; - case ESM::FourCC<'C','V','F','X'>::value: + case ESM::fourCC("CVFX"): mCasting = esm.getHString(); break; - case ESM::FourCC<'B','V','F','X'>::value: + case ESM::fourCC("BVFX"): mBolt = esm.getHString(); break; - case ESM::FourCC<'H','V','F','X'>::value: + case ESM::fourCC("HVFX"): mHit = esm.getHString(); break; - case ESM::FourCC<'A','V','F','X'>::value: + case ESM::fourCC("AVFX"): mArea = esm.getHString(); break; - case ESM::FourCC<'D','E','S','C'>::value: + case ESM::fourCC("DESC"): mDescription = esm.getHString(); break; default: diff --git a/components/esm3/loadmisc.cpp b/components/esm3/loadmisc.cpp index 0405382cfd..eee46ac8b0 100644 --- a/components/esm3/loadmisc.cpp +++ b/components/esm3/loadmisc.cpp @@ -24,20 +24,20 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'M','C','D','T'>::value: + case ESM::fourCC("MCDT"): esm.getHT(mData, 12); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 07ee560b54..338b453a5a 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -32,31 +32,31 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'R','N','A','M'>::value: + case ESM::fourCC("RNAM"): mRace = esm.getHString(); break; - case ESM::FourCC<'C','N','A','M'>::value: + case ESM::fourCC("CNAM"): mClass = esm.getHString(); break; - case ESM::FourCC<'A','N','A','M'>::value: + case ESM::fourCC("ANAM"): mFaction = esm.getHString(); break; - case ESM::FourCC<'B','N','A','M'>::value: + case ESM::fourCC("BNAM"): mHead = esm.getHString(); break; - case ESM::FourCC<'K','N','A','M'>::value: + case ESM::fourCC("KNAM"): mHair = esm.getHString(); break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'N','P','D','T'>::value: + case ESM::fourCC("NPDT"): hasNpdt = true; esm.getSubHeader(); if (esm.getSubSize() == 52) @@ -83,24 +83,24 @@ namespace ESM else esm.fail("NPC_NPDT must be 12 or 52 bytes long"); break; - case ESM::FourCC<'F','L','A','G'>::value: + case ESM::fourCC("FLAG"): hasFlags = true; int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; break; - case ESM::FourCC<'N','P','C','S'>::value: + case ESM::fourCC("NPCS"): mSpells.add(esm); break; - case ESM::FourCC<'N','P','C','O'>::value: + case ESM::fourCC("NPCO"): mInventory.add(esm); break; - case ESM::FourCC<'A','I','D','T'>::value: + case ESM::fourCC("AIDT"): esm.getHExact(&mAiData, sizeof(mAiData)); break; - case ESM::FourCC<'D','O','D','T'>::value: - case ESM::FourCC<'D','N','A','M'>::value: + case ESM::fourCC("DODT"): + case ESM::fourCC("DNAM"): mTransport.add(esm); break; case AI_Wander: diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index da90018619..9d0f3cf66c 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -51,11 +51,11 @@ namespace ESM case ESM::SREC_NAME: mCell = esm.getHString(); break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): esm.getHT(mData, 12); hasData = true; break; - case ESM::FourCC<'P','G','R','P'>::value: + case ESM::fourCC("PGRP"): { esm.getSubHeader(); int size = esm.getSubSize(); @@ -76,7 +76,7 @@ namespace ESM } break; } - case ESM::FourCC<'P','G','R','C'>::value: + case ESM::fourCC("PGRC"): { esm.getSubHeader(); int size = esm.getSubSize(); diff --git a/components/esm3/loadprob.cpp b/components/esm3/loadprob.cpp index 644abb6a39..10738c0863 100644 --- a/components/esm3/loadprob.cpp +++ b/components/esm3/loadprob.cpp @@ -24,20 +24,20 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'P','B','D','T'>::value: + case ESM::fourCC("PBDT"): esm.getHT(mData, 16); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadrace.cpp b/components/esm3/loadrace.cpp index 092d064cd8..ba0046bc9b 100644 --- a/components/esm3/loadrace.cpp +++ b/components/esm3/loadrace.cpp @@ -36,17 +36,17 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'R','A','D','T'>::value: + case ESM::fourCC("RADT"): esm.getHT(mData, 140); hasData = true; break; - case ESM::FourCC<'D','E','S','C'>::value: + case ESM::fourCC("DESC"): mDescription = esm.getHString(); break; - case ESM::FourCC<'N','P','C','S'>::value: + case ESM::fourCC("NPCS"): mPowers.add(esm); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadregn.cpp b/components/esm3/loadregn.cpp index 9e8921e939..e2682e644e 100644 --- a/components/esm3/loadregn.cpp +++ b/components/esm3/loadregn.cpp @@ -23,10 +23,10 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'W','E','A','T'>::value: + case ESM::fourCC("WEAT"): { esm.getSubHeader(); if (esm.getVer() == VER_12) @@ -55,13 +55,13 @@ namespace ESM } break; } - case ESM::FourCC<'B','N','A','M'>::value: + case ESM::fourCC("BNAM"): mSleepList = esm.getHString(); break; - case ESM::FourCC<'C','N','A','M'>::value: + case ESM::fourCC("CNAM"): esm.getHT(mMapColor); break; - case ESM::FourCC<'S','N','A','M'>::value: + case ESM::fourCC("SNAM"): { esm.getSubHeader(); SoundRef sr; diff --git a/components/esm3/loadrepa.cpp b/components/esm3/loadrepa.cpp index f7349256f0..aaf518a1cd 100644 --- a/components/esm3/loadrepa.cpp +++ b/components/esm3/loadrepa.cpp @@ -24,20 +24,20 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'R','I','D','T'>::value: + case ESM::fourCC("RIDT"): esm.getHT(mData, 16); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index a7e59f837e..f226d5f3be 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -93,7 +93,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'S','C','H','D'>::value: + case ESM::fourCC("SCHD"): { esm.getSubHeader(); mId = esm.getString(32); @@ -102,11 +102,11 @@ namespace ESM hasHeader = true; break; } - case ESM::FourCC<'S','C','V','R'>::value: + case ESM::fourCC("SCVR"): // list of local variables loadSCVR(esm); break; - case ESM::FourCC<'S','C','D','T'>::value: + case ESM::fourCC("SCDT"): { // compiled script esm.getSubHeader(); @@ -125,7 +125,7 @@ namespace ESM esm.getExact(mScriptData.data(), mScriptData.size()); break; } - case ESM::FourCC<'S','C','T','X'>::value: + case ESM::fourCC("SCTX"): mScriptText = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadskil.cpp b/components/esm3/loadskil.cpp index 5843b3ddb5..902410fe5a 100644 --- a/components/esm3/loadskil.cpp +++ b/components/esm3/loadskil.cpp @@ -139,15 +139,15 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::FourCC<'I','N','D','X'>::value: + case ESM::fourCC("INDX"): esm.getHT(mIndex); hasIndex = true; break; - case ESM::FourCC<'S','K','D','T'>::value: + case ESM::fourCC("SKDT"): esm.getHT(mData, 24); hasData = true; break; - case ESM::FourCC<'D','E','S','C'>::value: + case ESM::fourCC("DESC"): mDescription = esm.getHString(); break; default: diff --git a/components/esm3/loadsndg.cpp b/components/esm3/loadsndg.cpp index 17423a061b..2f5dea1eb4 100644 --- a/components/esm3/loadsndg.cpp +++ b/components/esm3/loadsndg.cpp @@ -24,14 +24,14 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): esm.getHT(mType, 4); hasData = true; break; - case ESM::FourCC<'C','N','A','M'>::value: + case ESM::fourCC("CNAM"): mCreature = esm.getHString(); break; - case ESM::FourCC<'S','N','A','M'>::value: + case ESM::fourCC("SNAM"): mSound = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadsoun.cpp b/components/esm3/loadsoun.cpp index 109d732da5..453d4eebfc 100644 --- a/components/esm3/loadsoun.cpp +++ b/components/esm3/loadsoun.cpp @@ -24,10 +24,10 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mSound = esm.getHString(); break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): esm.getHT(mData, 3); hasData = true; break; diff --git a/components/esm3/loadspel.cpp b/components/esm3/loadspel.cpp index d113cdd194..aeed60a1cb 100644 --- a/components/esm3/loadspel.cpp +++ b/components/esm3/loadspel.cpp @@ -26,14 +26,14 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'S','P','D','T'>::value: + case ESM::fourCC("SPDT"): esm.getHT(mData, 12); hasData = true; break; - case ESM::FourCC<'E','N','A','M'>::value: + case ESM::fourCC("ENAM"): ENAMstruct s; esm.getHT(s, 24); mEffects.mList.push_back(s); diff --git a/components/esm3/loadsscr.cpp b/components/esm3/loadsscr.cpp index 59d6eab58e..03c5b49380 100644 --- a/components/esm3/loadsscr.cpp +++ b/components/esm3/loadsscr.cpp @@ -24,7 +24,7 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'D','A','T','A'>::value: + case ESM::fourCC("DATA"): mData = esm.getHString(); hasData = true; break; diff --git a/components/esm3/loadstat.cpp b/components/esm3/loadstat.cpp index a4941f0895..fd557d37d5 100644 --- a/components/esm3/loadstat.cpp +++ b/components/esm3/loadstat.cpp @@ -25,7 +25,7 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadweap.cpp b/components/esm3/loadweap.cpp index e109096e09..e75ecf7516 100644 --- a/components/esm3/loadweap.cpp +++ b/components/esm3/loadweap.cpp @@ -24,23 +24,23 @@ namespace ESM mId = esm.getHString(); hasName = true; break; - case ESM::FourCC<'M','O','D','L'>::value: + case ESM::fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::FourCC<'F','N','A','M'>::value: + case ESM::fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::FourCC<'W','P','D','T'>::value: + case ESM::fourCC("WPDT"): esm.getHT(mData, 32); hasData = true; break; - case ESM::FourCC<'S','C','R','I'>::value: + case ESM::fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::FourCC<'I','T','E','X'>::value: + case ESM::fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::FourCC<'E','N','A','M'>::value: + case ESM::fourCC("ENAM"): mEnchant = esm.getHString(); break; case ESM::SREC_DELE: diff --git a/components/esm3/transport.cpp b/components/esm3/transport.cpp index 958ef6949a..40f831e748 100644 --- a/components/esm3/transport.cpp +++ b/components/esm3/transport.cpp @@ -10,13 +10,13 @@ namespace ESM void Transport::add(ESMReader &esm) { - if (esm.retSubName().toInt() == ESM::FourCC<'D','O','D','T'>::value) + if (esm.retSubName().toInt() == ESM::fourCC("DODT")) { Dest dodt; esm.getHExact(&dodt.mPos, 24); mList.push_back(dodt); } - else if (esm.retSubName().toInt() == ESM::FourCC<'D','N','A','M'>::value) + else if (esm.retSubName().toInt() == ESM::fourCC("DNAM")) { const std::string name = esm.getHString(); if (mList.empty()) diff --git a/components/esm3/variant.cpp b/components/esm3/variant.cpp index 732d7e7f29..c50581327d 100644 --- a/components/esm3/variant.cpp +++ b/components/esm3/variant.cpp @@ -10,10 +10,10 @@ namespace { - constexpr uint32_t STRV = ESM::FourCC<'S','T','R','V'>::value; - constexpr uint32_t INTV = ESM::FourCC<'I','N','T','V'>::value; - constexpr uint32_t FLTV = ESM::FourCC<'F','L','T','V'>::value; - constexpr uint32_t STTV = ESM::FourCC<'S','T','T','V'>::value; + constexpr uint32_t STRV = ESM::fourCC("STRV"); + constexpr uint32_t INTV = ESM::fourCC("INTV"); + constexpr uint32_t FLTV = ESM::fourCC("FLTV"); + constexpr uint32_t STTV = ESM::fourCC("STTV"); template struct GetValue From 688ca8b7fc57f4a7039c4c07bd8a9f44f502f224 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 8 Mar 2022 21:12:08 +0100 Subject: [PATCH 2191/2859] Revert "Rename Popup layer to Tooltip" This reverts commit 543ee33f7eb4bec43761f784cfa19d3b13f5b3fd. --- files/mygui/openmw_layers.xml | 2 +- files/mygui/openmw_tooltips.layout | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 4c93574edd..4b170bf06e 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -18,7 +18,7 @@ - + diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index fd87b5c0e0..ce927038c2 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -1,7 +1,7 @@ - + From deefdd5620df7c1a2fe0f29029fedf2123448c67 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 8 Mar 2022 21:16:28 +0100 Subject: [PATCH 2192/2859] Layer Notifications over Popup --- files/mygui/openmw_layers.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 4b170bf06e..0b70dbb000 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -9,7 +9,6 @@ - @@ -19,6 +18,7 @@ + From e2555c1bb71470cba65abd38cac420fde092ebbb Mon Sep 17 00:00:00 2001 From: duncanspumpkin Date: Tue, 8 Mar 2022 21:03:17 +0000 Subject: [PATCH 2193/2859] Add self to authors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 3edba5f25c..84f465414e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -71,6 +71,7 @@ Programmers David Teviotdale (dteviot) Diggory Hardy Dmitry Marakasov (AMDmi3) + Duncan Frost (duncans_pumpkin) Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 From 36c46ada6f89500ccd597daa3a828edafb6aafe9 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 14 Mar 2022 19:07:23 +0000 Subject: [PATCH 2194/2859] Pass unhandled Lua UI events to the parent --- components/lua/scriptscontainer.hpp | 5 ++- components/lua_ui/widget.cpp | 45 +++++++++++-------- components/lua_ui/widget.hpp | 27 ++++++++++- .../lua-scripting/widgets/widget.rst | 7 +++ 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index fcbd2ba0b7..d968c13a45 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -250,13 +250,14 @@ namespace LuaUtil sol::table mHiddenData; // same object as Script::mHiddenData in ScriptsContainer template - void operator()(Args&&... args) const + sol::object operator()(Args&&... args) const { if (mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil) - LuaUtil::call(mFunc, std::forward(args)...); + return LuaUtil::call(mFunc, std::forward(args)...); else Log(Debug::Debug) << "Ignored callback to the removed script " << mHiddenData.get(ScriptsContainer::sScriptDebugNameKey); + return sol::nil; } }; diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 96a6d096bd..a11d3db852 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -10,7 +10,8 @@ namespace LuaUi { WidgetExtension::WidgetExtension() - : mLua(nullptr) + : mPropagateEvents(true) + , mLua(nullptr) , mWidget(nullptr) , mSlot(this) , mLayout(sol::nil) @@ -18,6 +19,7 @@ namespace LuaUi , mTemplateProperties(sol::nil) , mExternal(sol::nil) , mParent(nullptr) + , mTemplateChild(false) {} void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) @@ -81,12 +83,15 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { ext->mParent = this; + ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); ext->updateCoord(); } void WidgetExtension::attachTemplate(WidgetExtension* ext) { + ext->mParent = this; + ext->mTemplateChild = true; ext->widget()->attachToWidget(widget()); ext->updateCoord(); } @@ -133,7 +138,7 @@ namespace LuaUi sol::table WidgetExtension::makeTable() const { - return sol::table(mLua, sol::create); + return sol::table(lua(), sol::create); } sol::object WidgetExtension::keyEvent(MyGUI::KeyCode code) const @@ -142,7 +147,7 @@ namespace LuaUi keySym.sym = SDLUtil::myGuiKeyToSdl(code); keySym.scancode = SDL_GetScancodeFromKey(keySym.sym); keySym.mod = SDL_GetModState(); - return sol::make_object(mLua, keySym); + return sol::make_object(lua(), keySym); } sol::object WidgetExtension::mouseEvent(int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const @@ -246,6 +251,7 @@ namespace LuaUi void WidgetExtension::updateProperties() { + mPropagateEvents = propertyValue("propagateEvents", true); mAbsoluteCoord = propertyValue("position", MyGUI::IntPoint()); mAbsoluteCoord = propertyValue("size", MyGUI::IntSize()); mRelativeCoord = propertyValue("relativePosition", MyGUI::FloatPoint()); @@ -265,7 +271,7 @@ namespace LuaUi MyGUI::IntSize WidgetExtension::parentSize() { - if (mParent) + if (mParent && !mTemplateChild) return mParent->childScalingSize(); else return widget()->getParentSize(); @@ -304,7 +310,7 @@ namespace LuaUi return mSlot->widget()->getSize(); } - void WidgetExtension::triggerEvent(std::string_view name, const sol::object& argument = sol::nil) const + void WidgetExtension::triggerEvent(std::string_view name, sol::object argument) const { auto it = mCallbacks.find(name); if (it != mCallbacks.end()) @@ -315,57 +321,58 @@ namespace LuaUi { if (code == MyGUI::KeyCode::None) { - // \todo decide how to handle unicode strings in Lua - MyGUI::UString uString; - uString.push_back(static_cast(ch)); - triggerEvent("textInput", sol::make_object(mLua, uString.asUTF8())); + propagateEvent("textInput", [ch](auto w) { + MyGUI::UString uString; + uString.push_back(static_cast(ch)); + return sol::make_object(w->lua(), uString.asUTF8()); + }); } else - triggerEvent("keyPress", keyEvent(code)); + propagateEvent("keyPress", [code](auto w){ return w->keyEvent(code); }); } void WidgetExtension::keyRelease(MyGUI::Widget*, MyGUI::KeyCode code) { - triggerEvent("keyRelease", keyEvent(code)); + propagateEvent("keyRelease", [code](auto w) { return w->keyEvent(code); }); } void WidgetExtension::mouseMove(MyGUI::Widget*, int left, int top) { - triggerEvent("mouseMove", mouseEvent(left, top)); + propagateEvent("mouseMove", [left, top](auto w) { return w->mouseEvent(left, top); }); } void WidgetExtension::mouseDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { - triggerEvent("mouseMove", mouseEvent(left, top, button)); + propagateEvent("mouseMove", [left, top, button](auto w) { return w->mouseEvent(left, top, button); }); } void WidgetExtension::mouseClick(MyGUI::Widget* _widget) { - triggerEvent("mouseClick"); + propagateEvent("mouseClick", [](auto){ return sol::nil; }); } void WidgetExtension::mouseDoubleClick(MyGUI::Widget* _widget) { - triggerEvent("mouseDoubleClick"); + propagateEvent("mouseDoubleClick", [](auto){ return sol::nil; }); } void WidgetExtension::mousePress(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { - triggerEvent("mousePress", mouseEvent(left, top, button)); + propagateEvent("mousePress", [left, top, button](auto w) { return w->mouseEvent(left, top, button); }); } void WidgetExtension::mouseRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button) { - triggerEvent("mouseRelease", mouseEvent(left, top, button)); + propagateEvent("mouseRelease", [left, top, button](auto w) { return w->mouseEvent(left, top, button); }); } void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*) { - triggerEvent("focusGain"); + propagateEvent("focusGain", [](auto){ return sol::nil; }); } void WidgetExtension::focusLoss(MyGUI::Widget*, MyGUI::Widget*) { - triggerEvent("focusLoss"); + propagateEvent("focusLoss", [](auto){ return sol::nil; }); } } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 827993d47d..c712d18ccd 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -90,8 +90,28 @@ namespace LuaUi virtual void updateProperties(); virtual void updateChildren() {}; - lua_State* lua() { return mLua; } - void triggerEvent(std::string_view name, const sol::object& argument) const; + lua_State* lua() const { return mLua; } + + void triggerEvent(std::string_view name, sol::object argument) const; + template + void propagateEvent(std::string_view name, const ArgFactory& argumentFactory) const + { + const WidgetExtension* w = this; + while (w) + { + bool shouldPropagate = true; + auto it = w->mCallbacks.find(name); + if (it != w->mCallbacks.end()) + { + sol::object res = it->second(argumentFactory(w), w->mLayout); + shouldPropagate = res.is() && res.as(); + } + if (w->mParent && w->mPropagateEvents && shouldPropagate) + w = w->mParent; + else + w = nullptr; + } + } // offsets the position and size, used only in C++ widget code MyGUI::IntCoord mForcedCoord; @@ -103,6 +123,8 @@ namespace LuaUi // used in combination with relative coord to align the widget, e. g. center it MyGUI::FloatSize mAnchor; + bool mPropagateEvents; + private: // use lua_State* instead of sol::state_view because MyGUI requires a default constructor lua_State* mLua; @@ -116,6 +138,7 @@ namespace LuaUi sol::object mTemplateProperties; sol::object mExternal; WidgetExtension* mParent; + bool mTemplateChild; void attach(WidgetExtension* ext); void attachTemplate(WidgetExtension* ext); diff --git a/docs/source/reference/lua-scripting/widgets/widget.rst b/docs/source/reference/lua-scripting/widgets/widget.rst index 36cc0917d9..d114d0957b 100644 --- a/docs/source/reference/lua-scripting/widgets/widget.rst +++ b/docs/source/reference/lua-scripting/widgets/widget.rst @@ -32,12 +32,19 @@ Properties * - visible - boolean (true) - Defines if the widget is visible + * - propagateEvents + - boolean (true) + - Allows base widget events to propagate to the widget's parent. .. TODO: document the mouse pointer property, when API for reading / adding pointer types is available Events ------ +Base widget events are special, they can propagate up to the parent widget. +This can be prevented by changing the `propagateEvents` property, or by assigning an event handler. +The event is still allowed to propagate if the event handler returns `true`. + .. list-table:: :header-rows: 1 :widths: 20 20 60 From af93ebf4334d01a72d49e68f78773597f8712de3 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 15 Feb 2022 19:38:47 +0100 Subject: [PATCH 2195/2859] [Lua] Move class-specific functions to `openmw.types` --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwlua/localscripts.cpp | 21 +-- apps/openmw/mwlua/luabindings.cpp | 74 ++++++--- apps/openmw/mwlua/luabindings.hpp | 9 +- apps/openmw/mwlua/luamanagerimp.cpp | 1 + apps/openmw/mwlua/object.cpp | 15 +- apps/openmw/mwlua/object.hpp | 18 +++ apps/openmw/mwlua/objectbindings.cpp | 142 +----------------- apps/openmw/mwlua/types/actor.cpp | 137 +++++++++++++++++ apps/openmw/mwlua/types/door.cpp | 34 +++++ apps/openmw/mwlua/types/types.hpp | 14 ++ files/builtin_scripts/openmw_aux/calendar.lua | 2 +- files/builtin_scripts/scripts/omw/camera.lua | 6 +- .../scripts/omw/head_bobbing.lua | 6 +- .../scripts/omw/third_person.lua | 6 +- 15 files changed, 286 insertions(+), 200 deletions(-) create mode 100644 apps/openmw/mwlua/types/actor.cpp create mode 100644 apps/openmw/mwlua/types/door.cpp create mode 100644 apps/openmw/mwlua/types/types.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 71c138eee9..6b439bde80 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -60,6 +60,7 @@ add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings + types/types types/door types/actor ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index c95eae43a7..7cdb31f518 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -43,31 +43,12 @@ namespace MWLua #undef CONTROL sol::usertype selfAPI = - context.mLua->sol().new_usertype("SelfObject", sol::base_classes, sol::bases()); + context.mLua->sol().new_usertype("SelfObject", sol::base_classes, sol::bases()); selfAPI[sol::meta_function::to_string] = [](SelfObject& self) { return "openmw.self[" + self.toString() + "]"; }; selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; - selfAPI["setEquipment"] = [context](const SelfObject& obj, sol::table equipment) - { - if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) - { - if (!equipment.empty()) - throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); - return; - } - SetEquipmentAction::Equipment eqp; - for (auto& [key, value] : equipment) - { - int slot = key.as(); - if (value.is()) - eqp[slot] = value.as().id(); - else - eqp[slot] = value.as(); - } - context.mLuaManager->addAction(std::make_unique(context.mLua, obj.id(), std::move(eqp))); - }; using AiPackage = MWMechanics::AiPackage; sol::usertype aiPackage = context.mLua->sol().new_usertype("AiPackage"); diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 06ba6d5c5a..2319872974 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -12,6 +12,7 @@ #include "eventqueue.hpp" #include "worldview.hpp" +#include "types/types.hpp" namespace MWLua { @@ -45,11 +46,57 @@ namespace MWLua // api["resume"] = []() {}; } + sol::table initTypesPackage(const Context& context) + { + auto* lua = context.mLua; + sol::table types(lua->sol(), sol::create); + auto addType = [&](std::string_view name, std::optional base = std::nullopt) -> sol::table + { + sol::table t(lua->sol(), sol::create); + sol::table ro = LuaUtil::makeReadOnly(t); + sol::table meta = ro[sol::metatable_key]; + meta[sol::meta_function::to_string] = [name]() { return name; }; + if (base) + { + t[sol::metatable_key] = LuaUtil::getMutableFromReadOnly(types[*base]); + t["baseType"] = types[*base]; + } + types[name] = ro; + return t; + }; + + addActorBindings(addType("Actor"), context); + addType("Item"); + + addType(ObjectTypeName::Creature, "Actor"); + addType(ObjectTypeName::NPC, "Actor"); + addType(ObjectTypeName::Player, ObjectTypeName::NPC); + + addType(ObjectTypeName::Armor, "Item"); + addType(ObjectTypeName::Book, "Item"); + addType(ObjectTypeName::Clothing, "Item"); + addType(ObjectTypeName::Ingredient, "Item"); + addType(ObjectTypeName::Light, "Item"); + addType(ObjectTypeName::MiscItem, "Item"); + addType(ObjectTypeName::Potion, "Item"); + addType(ObjectTypeName::Weapon, "Item"); + addType(ObjectTypeName::Apparatus, "Item"); + addType(ObjectTypeName::Lockpick, "Item"); + addType(ObjectTypeName::Probe, "Item"); + addType(ObjectTypeName::Repair, "Item"); + + addType(ObjectTypeName::Activator); + addDoorBindings(addType(ObjectTypeName::Door), context); + addType(ObjectTypeName::Static); + + return LuaUtil::makeReadOnly(types); + } + sol::table initCorePackage(const Context& context) { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 17; + api["API_REVISION"] = 18; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); @@ -60,35 +107,14 @@ namespace MWLua context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; addTimeBindings(api, context, false); - api["OBJECT_TYPE"] = definitionList(*lua, + api["OBJECT_TYPE"] = definitionList(*lua, // TODO: remove, use require('openmw.types') instead { ObjectTypeName::Activator, ObjectTypeName::Armor, ObjectTypeName::Book, ObjectTypeName::Clothing, ObjectTypeName::Creature, ObjectTypeName::Door, ObjectTypeName::Ingredient, ObjectTypeName::Light, ObjectTypeName::MiscItem, ObjectTypeName::NPC, ObjectTypeName::Player, ObjectTypeName::Potion, - ObjectTypeName::Static, ObjectTypeName::Weapon, ObjectTypeName::Activator, ObjectTypeName::Lockpick, + ObjectTypeName::Static, ObjectTypeName::Weapon, ObjectTypeName::Apparatus, ObjectTypeName::Lockpick, ObjectTypeName::Probe, ObjectTypeName::Repair }); - api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ - {"Helmet", MWWorld::InventoryStore::Slot_Helmet}, - {"Cuirass", MWWorld::InventoryStore::Slot_Cuirass}, - {"Greaves", MWWorld::InventoryStore::Slot_Greaves}, - {"LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron}, - {"RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron}, - {"LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet}, - {"RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet}, - {"Boots", MWWorld::InventoryStore::Slot_Boots}, - {"Shirt", MWWorld::InventoryStore::Slot_Shirt}, - {"Pants", MWWorld::InventoryStore::Slot_Pants}, - {"Skirt", MWWorld::InventoryStore::Slot_Skirt}, - {"Robe", MWWorld::InventoryStore::Slot_Robe}, - {"LeftRing", MWWorld::InventoryStore::Slot_LeftRing}, - {"RightRing", MWWorld::InventoryStore::Slot_RightRing}, - {"Amulet", MWWorld::InventoryStore::Slot_Amulet}, - {"Belt", MWWorld::InventoryStore::Slot_Belt}, - {"CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight}, - {"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft}, - {"Ammunition", MWWorld::InventoryStore::Slot_Ammunition} - })); api["i18n"] = [i18n=context.mI18n](const std::string& context) { return i18n->getContext(context); }; const MWWorld::Store* gmst = &MWBase::Environment::get().getWorld()->getStore().get(); api["getGMST"] = [lua=context.mLua, gmst](const std::string& setting) -> sol::object diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 377d6d1e4f..d80c649f4c 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -23,6 +23,7 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); sol::table initQueryPackage(const Context&); + sol::table initTypesPackage(const Context&); sol::table initFieldGroup(const Context&, const QueryFieldGroup&); @@ -38,14 +39,6 @@ namespace MWLua void initObjectBindingsForGlobalScripts(const Context&); // Implemented in cellbindings.cpp - struct LCell // for local scripts - { - MWWorld::CellStore* mStore; - }; - struct GCell // for global scripts - { - MWWorld::CellStore* mStore; - }; void initCellBindingsForLocalScripts(const Context&); void initCellBindingsForGlobalScripts(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 716612f745..cf1c69aa11 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -82,6 +82,7 @@ namespace MWLua mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); mLua.addCommonPackage("openmw.core", initCorePackage(context)); mLua.addCommonPackage("openmw.query", initQueryPackage(context)); + mLua.addCommonPackage("openmw.types", initTypesPackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); mGlobalScripts.addPackage("openmw.settings", initGlobalSettingsPackage(context)); mGlobalScripts.addPackage("openmw.storage", initGlobalStoragePackage(context, &mGlobalStorage)); diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index 816ec6712b..dc903845f4 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -60,7 +60,7 @@ namespace MWLua // for types that are not present in `luaObjectTypeInfo` (for such types result stability // is not necessary because they are not listed in OpenMW Lua documentation). if (ptr.getCellRef().getRefId() == "player") - return "Player"; + return ObjectTypeName::Player; if (isMarker(ptr)) return "Marker"; return getLuaObjectTypeName(static_cast(ptr.getType()), /*fallback=*/ptr.getTypeDescription()); @@ -79,6 +79,19 @@ namespace MWLua return 0; } + const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr) + { + if (ptr.getType() != recordType) + { + std::string msg = "Requires type '"; + msg.append(getLuaObjectTypeName(recordType)); + msg.append("', but applied to "); + msg.append(ptrToString(ptr)); + throw std::runtime_error(msg); + } + return ptr; + } + std::string ptrToString(const MWWorld::Ptr& ptr) { std::string res = "object"; diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 0b51da17c3..36744b8f93 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -3,6 +3,8 @@ #include +#include + #include #include #include @@ -48,6 +50,7 @@ namespace MWLua bool isMarker(const MWWorld::Ptr& ptr); std::string_view getLuaObjectTypeName(ESM::RecNameInts recordType, std::string_view fallback = "Unknown"); std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr); + const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr); // Each script has a set of flags that controls to which objects the script should be // automatically attached. This function maps each object types to one of the flags. @@ -103,6 +106,9 @@ namespace MWLua // Returns `true` if calling `ptr()` is safe. bool isValid() const; + virtual sol::object getObject(lua_State* lua, ObjectId id) const = 0; // returns LObject or GOBject + virtual sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const = 0; // returns LCell or GCell + protected: virtual void updatePtr() const = 0; @@ -114,17 +120,29 @@ namespace MWLua }; // Used only in local scripts + struct LCell + { + MWWorld::CellStore* mStore; + }; class LObject : public Object { using Object::Object; void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, true); } + sol::object getObject(lua_State* lua, ObjectId id) const final { return sol::make_object(lua, id, mObjectRegistry); } + sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const final { return sol::make_object(lua, LCell{store}); } }; // Used only in global scripts + struct GCell + { + MWWorld::CellStore* mStore; + }; class GObject : public Object { using Object::Object; void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, false); } + sol::object getObject(lua_State* lua, ObjectId id) const final { return sol::make_object(lua, id, mObjectRegistry); } + sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const final { return sol::make_object(lua, GCell{store}); } }; using ObjectIdList = std::shared_ptr>; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 3ae9035271..0310541eee 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -42,19 +42,6 @@ namespace MWLua template using Cell = std::conditional_t, LCell, GCell>; - static const MWWorld::Ptr& requireRecord(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr) - { - if (ptr.getType() != recordType) - { - std::string msg = "Requires type '"; - msg.append(getLuaObjectTypeName(recordType)); - msg.append("', but applied to "); - msg.append(ptrToString(ptr)); - throw std::runtime_error(msg); - } - return ptr; - } - template static void registerObjectList(const std::string& prefix, const Context& context) { @@ -122,21 +109,6 @@ namespace MWLua context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; - objectT["canMove"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.getMaxSpeed(o.ptr()) > 0; - }; - objectT["getRunSpeed"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.getRunSpeed(o.ptr()); - }; - objectT["getWalkSpeed"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.getWalkSpeed(o.ptr()); - }; objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor) { uint32_t esmRecordType = actor.ptr().getType(); @@ -199,112 +171,14 @@ namespace MWLua context.mLuaManager->addAction(std::move(action)); }; } - else - { // Only for local scripts - objectT["isOnGround"] = [](const ObjectT& o) - { - return MWBase::Environment::get().getWorld()->isOnGround(o.ptr()); - }; - objectT["isSwimming"] = [](const ObjectT& o) - { - return MWBase::Environment::get().getWorld()->isSwimming(o.ptr()); - }; - objectT["isInWeaponStance"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.isActor() && cls.getCreatureStats(o.ptr()).getDrawState() == MWMechanics::DrawState_Weapon; - }; - objectT["isInMagicStance"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.isActor() && cls.getCreatureStats(o.ptr()).getDrawState() == MWMechanics::DrawState_Spell; - }; - objectT["getCurrentSpeed"] = [](const ObjectT& o) - { - const MWWorld::Class& cls = o.ptr().getClass(); - return cls.getCurrentSpeed(o.ptr()); - }; - } } - template - static void addDoorBindings(sol::usertype& objectT, const Context& context) - { - auto ptr = [](const ObjectT& o) -> const MWWorld::Ptr& { return requireRecord(ESM::REC_DOOR, o.ptr()); }; - - objectT["isTeleport"] = sol::readonly_property([ptr](const ObjectT& o) - { - return ptr(o).getCellRef().getTeleport(); - }); - objectT["destPosition"] = sol::readonly_property([ptr](const ObjectT& o) -> osg::Vec3f - { - return ptr(o).getCellRef().getDoorDest().asVec3(); - }); - objectT["destRotation"] = sol::readonly_property([ptr](const ObjectT& o) -> osg::Vec3f - { - return ptr(o).getCellRef().getDoorDest().asRotationVec3(); - }); - objectT["destCell"] = sol::readonly_property( - [ptr, worldView=context.mWorldView](const ObjectT& o) -> sol::optional> - { - const MWWorld::CellRef& cellRef = ptr(o).getCellRef(); - if (!cellRef.getTeleport()) - return sol::nullopt; - MWWorld::CellStore* cell = worldView->findCell(cellRef.getDestCell(), cellRef.getDoorDest().asVec3()); - if (cell) - return Cell{cell}; - else - return sol::nullopt; - }); - } - - static SetEquipmentAction::Equipment parseEquipmentTable(sol::table equipment) - { - SetEquipmentAction::Equipment eqp; - for (auto& [key, value] : equipment) - { - int slot = key.as(); - if (value.is()) - eqp[slot] = value.as().id(); - else - eqp[slot] = value.as(); - } - return eqp; - } - template static void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) { using InventoryT = Inventory; sol::usertype inventoryT = context.mLua->sol().new_usertype(prefix + "Inventory"); - objectT["getEquipment"] = [context](const ObjectT& o) - { - const MWWorld::Ptr& ptr = o.ptr(); - sol::table equipment(context.mLua->sol(), sol::create); - if (!ptr.getClass().hasInventoryStore(ptr)) - return equipment; - - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - auto it = store.getSlot(slot); - if (it == store.end()) - continue; - context.mWorldView->getObjectRegistry()->registerPtr(*it); - equipment[slot] = ObjectT(getId(*it), context.mWorldView->getObjectRegistry()); - } - return equipment; - }; - objectT["isEquipped"] = [](const ObjectT& actor, const ObjectT& item) - { - const MWWorld::Ptr& ptr = actor.ptr(); - if (!ptr.getClass().hasInventoryStore(ptr)) - return false; - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - return store.isEquipped(item.ptr()); - }; - objectT["inventory"] = sol::readonly_property([](const ObjectT& o) { return InventoryT{o}; }); inventoryT[sol::meta_function::to_string] = [](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; }; @@ -363,18 +237,6 @@ namespace MWLua if constexpr (std::is_same_v) { // Only for global scripts - objectT["setEquipment"] = [context](const GObject& obj, sol::table equipment) - { - if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) - { - if (!equipment.empty()) - throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); - return; - } - context.mLuaManager->addAction(std::make_unique( - context.mLua, obj.id(), parseEquipmentTable(equipment))); - }; - // TODO // obj.inventory:drop(obj2, [count]) // obj.inventory:drop(recordId, [count]) @@ -390,9 +252,9 @@ namespace MWLua template static void initObjectBindings(const std::string& prefix, const Context& context) { - sol::usertype objectT = context.mLua->sol().new_usertype(prefix + "Object"); + sol::usertype objectT = context.mLua->sol().new_usertype( + prefix + "Object", sol::base_classes, sol::bases()); addBasicBindings(objectT, context); - addDoorBindings(objectT, context); addInventoryBindings(objectT, prefix, context); registerObjectList(prefix, context); diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp new file mode 100644 index 0000000000..ba2c05a52d --- /dev/null +++ b/apps/openmw/mwlua/types/actor.cpp @@ -0,0 +1,137 @@ +#include "types.hpp" + +#include + +#include +#include +#include + +#include "../actions.hpp" +#include "../luabindings.hpp" +#include "../localscripts.hpp" +#include "../luamanagerimp.hpp" + +namespace MWLua +{ + using SelfObject = LocalScripts::SelfObject; + + void addActorBindings(sol::table actor, const Context& context) + { + actor["STANCE"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"Nothing", MWMechanics::DrawState_Nothing}, + {"Weapon", MWMechanics::DrawState_Weapon}, + {"Spell", MWMechanics::DrawState_Spell}, + })); + actor["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"Helmet", MWWorld::InventoryStore::Slot_Helmet}, + {"Cuirass", MWWorld::InventoryStore::Slot_Cuirass}, + {"Greaves", MWWorld::InventoryStore::Slot_Greaves}, + {"LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron}, + {"RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron}, + {"LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet}, + {"RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet}, + {"Boots", MWWorld::InventoryStore::Slot_Boots}, + {"Shirt", MWWorld::InventoryStore::Slot_Shirt}, + {"Pants", MWWorld::InventoryStore::Slot_Pants}, + {"Skirt", MWWorld::InventoryStore::Slot_Skirt}, + {"Robe", MWWorld::InventoryStore::Slot_Robe}, + {"LeftRing", MWWorld::InventoryStore::Slot_LeftRing}, + {"RightRing", MWWorld::InventoryStore::Slot_RightRing}, + {"Amulet", MWWorld::InventoryStore::Slot_Amulet}, + {"Belt", MWWorld::InventoryStore::Slot_Belt}, + {"CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight}, + {"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft}, + {"Ammunition", MWWorld::InventoryStore::Slot_Ammunition} + })); + + actor["stance"] = [](const Object& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + if (cls.isActor()) + return cls.getCreatureStats(o.ptr()).getDrawState(); + else + throw std::runtime_error("Actor expected"); + }; + + actor["canMove"] = [](const Object& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getMaxSpeed(o.ptr()) > 0; + }; + actor["runSpeed"] = [](const Object& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getRunSpeed(o.ptr()); + }; + actor["walkSpeed"] = [](const Object& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getWalkSpeed(o.ptr()); + }; + actor["currentSpeed"] = [](const Object& o) + { + const MWWorld::Class& cls = o.ptr().getClass(); + return cls.getCurrentSpeed(o.ptr()); + }; + + actor["isOnGround"] = [](const LObject& o) + { + return MWBase::Environment::get().getWorld()->isOnGround(o.ptr()); + }; + actor["isSwimming"] = [](const LObject& o) + { + return MWBase::Environment::get().getWorld()->isSwimming(o.ptr()); + }; + + actor["getEquipment"] = [context](const Object& o) + { + const MWWorld::Ptr& ptr = o.ptr(); + sol::table equipment(context.mLua->sol(), sol::create); + if (!ptr.getClass().hasInventoryStore(ptr)) + return equipment; + + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + auto it = store.getSlot(slot); + if (it == store.end()) + continue; + context.mWorldView->getObjectRegistry()->registerPtr(*it); + equipment[slot] = o.getObject(context.mLua->sol(), getId(*it)); + } + return equipment; + }; + actor["hasEquipped"] = [](const Object& o, const Object& item) + { + const MWWorld::Ptr& ptr = o.ptr(); + if (!ptr.getClass().hasInventoryStore(ptr)) + return false; + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + return store.isEquipped(item.ptr()); + }; + actor["setEquipment"] = [context](const sol::object& luaObj, const sol::table& equipment) + { + if (!luaObj.is() && !luaObj.is()) + throw std::runtime_error("Incorrect type of the first argument. " + "Can be either self (in local scripts) or game object (in global scripts)"); + const Object& obj = luaObj.as(); + if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) + { + if (!equipment.empty()) + throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); + return; + } + SetEquipmentAction::Equipment eqp; + for (auto& [key, value] : equipment) + { + int slot = key.as(); + if (value.is()) + eqp[slot] = value.as().id(); + else + eqp[slot] = value.as(); + } + context.mLuaManager->addAction(std::make_unique(context.mLua, obj.id(), std::move(eqp))); + }; + } + +} diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp new file mode 100644 index 0000000000..efcf638360 --- /dev/null +++ b/apps/openmw/mwlua/types/door.cpp @@ -0,0 +1,34 @@ +#include "types.hpp" + +#include "../luabindings.hpp" + +namespace MWLua +{ + + static const MWWorld::Ptr& doorPtr(const Object& o) { return verifyType(ESM::REC_DOOR, o.ptr()); } + + void addDoorBindings(sol::table door, const Context& context) + { + door["isTeleport"] = [](const Object& o) { return doorPtr(o).getCellRef().getTeleport(); }; + door["destPosition"] = [](const Object& o) -> osg::Vec3f + { + return doorPtr(o).getCellRef().getDoorDest().asVec3(); + }; + door["destRotation"] = [](const Object& o) -> osg::Vec3f + { + return doorPtr(o).getCellRef().getDoorDest().asRotationVec3(); + }; + door["destCell"] = [worldView=context.mWorldView](sol::this_state lua, const Object& o) -> sol::object + { + const MWWorld::CellRef& cellRef = doorPtr(o).getCellRef(); + if (!cellRef.getTeleport()) + return sol::nil; + MWWorld::CellStore* cell = worldView->findCell(cellRef.getDestCell(), cellRef.getDoorDest().asVec3()); + if (cell) + return o.getCell(lua, cell); + else + return sol::nil; + }; + } + +} diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp new file mode 100644 index 0000000000..20fd208462 --- /dev/null +++ b/apps/openmw/mwlua/types/types.hpp @@ -0,0 +1,14 @@ +#ifndef MWLUA_TYPES_H +#define MWLUA_TYPES_H + +#include + +#include "../context.hpp" + +namespace MWLua +{ + void addDoorBindings(sol::table door, const Context& context); + void addActorBindings(sol::table actor, const Context& context); +} + +#endif // MWLUA_TYPES_H diff --git a/files/builtin_scripts/openmw_aux/calendar.lua b/files/builtin_scripts/openmw_aux/calendar.lua index 58e7298f1d..181b133b83 100644 --- a/files/builtin_scripts/openmw_aux/calendar.lua +++ b/files/builtin_scripts/openmw_aux/calendar.lua @@ -91,7 +91,7 @@ local function formatGameTime(formatStr, timestamp) error('Unknown tag "'..tag..'"') end - res, _ = string.gsub(formatStr or '%c', '%%.', replFn) + local res, _ = string.gsub(formatStr or '%c', '%%.', replFn) return res end diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 4b5911930e..40b25c6d13 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -5,6 +5,8 @@ local settings = require('openmw.settings') local util = require('openmw.util') local self = require('openmw.self') +local Actor = require('openmw.types').Actor + local head_bobbing = require('scripts.omw.head_bobbing') local third_person = require('scripts.omw.third_person') @@ -77,7 +79,7 @@ local function updateVanity(dt) end local function updateSmoothedSpeed(dt) - local speed = self:getCurrentSpeed() + local speed = Actor.currentSpeed(self) speed = speed / (1 + speed / 500) local maxDelta = 300 * dt smoothedSpeed = smoothedSpeed + util.clamp(speed - smoothedSpeed, -maxDelta, maxDelta) @@ -126,7 +128,7 @@ local function updateStandingPreview() third_person.standingPreview = false return end - local standingStill = self:getCurrentSpeed() == 0 and not self:isInWeaponStance() and not self:isInMagicStance() + local standingStill = Actor.currentSpeed(self) == 0 and Actor.stance(self) == Actor.STANCE.Nothing if standingStill and mode == MODE.ThirdPerson then third_person.standingPreview = true camera.setMode(MODE.Preview) diff --git a/files/builtin_scripts/scripts/omw/head_bobbing.lua b/files/builtin_scripts/scripts/omw/head_bobbing.lua index fe809fca8a..8e6ea0660f 100644 --- a/files/builtin_scripts/scripts/omw/head_bobbing.lua +++ b/files/builtin_scripts/scripts/omw/head_bobbing.lua @@ -3,6 +3,8 @@ local self = require('openmw.self') local settings = require('openmw.settings') local util = require('openmw.util') +local Actor = require('openmw.types').Actor + local doubleStepLength = settings._getFloatFromSettingsCfg('Camera', 'head bobbing step') * 2 local stepHeight = settings._getFloatFromSettingsCfg('Camera', 'head bobbing height') local maxRoll = math.rad(settings._getFloatFromSettingsCfg('Camera', 'head bobbing roll')) @@ -20,14 +22,14 @@ local sampleArc = function(x) return 1 - math.cos(x * halfArc) end local arcHeight = sampleArc(1) function M.update(dt, smoothedSpeed) - local speed = self:getCurrentSpeed() + local speed = Actor.currentSpeed(self) speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high totalMovement = totalMovement + speed * dt if not M.enabled or camera.getMode() ~= camera.MODE.FirstPerson then effectWeight = 0 return end - if self:isOnGround() then + if Actor.isOnGround(self) then effectWeight = math.min(1, effectWeight + dt * 5) else effectWeight = math.max(0, effectWeight - dt * 5) diff --git a/files/builtin_scripts/scripts/omw/third_person.lua b/files/builtin_scripts/scripts/omw/third_person.lua index ca00ef5ee9..95f872b15f 100644 --- a/files/builtin_scripts/scripts/omw/third_person.lua +++ b/files/builtin_scripts/scripts/omw/third_person.lua @@ -4,6 +4,8 @@ local util = require('openmw.util') local self = require('openmw.self') local nearby = require('openmw.nearby') +local Actor = require('openmw.types').Actor + local MODE = camera.MODE local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 } @@ -70,9 +72,9 @@ local noThirdPersonLastFrame = true local function updateState() local mode = camera.getMode() local oldState = state - if (self:isInWeaponStance() or self:isInMagicStance()) and mode == MODE.ThirdPerson then + if Actor.stance(self) ~= Actor.STANCE.Nothing and mode == MODE.ThirdPerson then state = STATE.Combat - elseif self:isSwimming() then + elseif Actor.isSwimming(self) then state = STATE.Swimming elseif oldState == STATE.Combat or oldState == STATE.Swimming then state = defaultShoulder From d251c4e2a1c10e55a85d02b08736256305b2b542 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 21 Feb 2022 00:03:40 +0100 Subject: [PATCH 2196/2859] [Lua] Change behavior of `obj.type` --- apps/openmw/mwlua/luabindings.cpp | 62 ---- apps/openmw/mwlua/luabindings.hpp | 1 - apps/openmw/mwlua/luamanagerimp.cpp | 2 + apps/openmw/mwlua/object.cpp | 78 +---- apps/openmw/mwlua/object.hpp | 33 --- apps/openmw/mwlua/objectbindings.cpp | 409 ++++++++++++++------------- apps/openmw/mwlua/types/types.cpp | 193 +++++++++++++ apps/openmw/mwlua/types/types.hpp | 17 ++ apps/openmw/mwworld/ptr.hpp | 17 ++ components/esm/defs.hpp | 5 + 10 files changed, 452 insertions(+), 365 deletions(-) create mode 100644 apps/openmw/mwlua/types/types.cpp diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 2319872974..655c7f6989 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -17,14 +17,6 @@ namespace MWLua { - static sol::table definitionList(LuaUtil::LuaState& lua, std::initializer_list values) - { - sol::table res(lua.sol(), sol::create); - for (const std::string_view& v : values) - res[v] = v; - return LuaUtil::makeReadOnly(res); - } - static void addTimeBindings(sol::table& api, const Context& context, bool global) { api["getSimulationTime"] = [world=context.mWorldView]() { return world->getSimulationTime(); }; @@ -46,52 +38,6 @@ namespace MWLua // api["resume"] = []() {}; } - sol::table initTypesPackage(const Context& context) - { - auto* lua = context.mLua; - sol::table types(lua->sol(), sol::create); - auto addType = [&](std::string_view name, std::optional base = std::nullopt) -> sol::table - { - sol::table t(lua->sol(), sol::create); - sol::table ro = LuaUtil::makeReadOnly(t); - sol::table meta = ro[sol::metatable_key]; - meta[sol::meta_function::to_string] = [name]() { return name; }; - if (base) - { - t[sol::metatable_key] = LuaUtil::getMutableFromReadOnly(types[*base]); - t["baseType"] = types[*base]; - } - types[name] = ro; - return t; - }; - - addActorBindings(addType("Actor"), context); - addType("Item"); - - addType(ObjectTypeName::Creature, "Actor"); - addType(ObjectTypeName::NPC, "Actor"); - addType(ObjectTypeName::Player, ObjectTypeName::NPC); - - addType(ObjectTypeName::Armor, "Item"); - addType(ObjectTypeName::Book, "Item"); - addType(ObjectTypeName::Clothing, "Item"); - addType(ObjectTypeName::Ingredient, "Item"); - addType(ObjectTypeName::Light, "Item"); - addType(ObjectTypeName::MiscItem, "Item"); - addType(ObjectTypeName::Potion, "Item"); - addType(ObjectTypeName::Weapon, "Item"); - addType(ObjectTypeName::Apparatus, "Item"); - addType(ObjectTypeName::Lockpick, "Item"); - addType(ObjectTypeName::Probe, "Item"); - addType(ObjectTypeName::Repair, "Item"); - - addType(ObjectTypeName::Activator); - addDoorBindings(addType(ObjectTypeName::Door), context); - addType(ObjectTypeName::Static); - - return LuaUtil::makeReadOnly(types); - } - sol::table initCorePackage(const Context& context) { auto* lua = context.mLua; @@ -107,14 +53,6 @@ namespace MWLua context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; addTimeBindings(api, context, false); - api["OBJECT_TYPE"] = definitionList(*lua, // TODO: remove, use require('openmw.types') instead - { - ObjectTypeName::Activator, ObjectTypeName::Armor, ObjectTypeName::Book, ObjectTypeName::Clothing, - ObjectTypeName::Creature, ObjectTypeName::Door, ObjectTypeName::Ingredient, ObjectTypeName::Light, - ObjectTypeName::MiscItem, ObjectTypeName::NPC, ObjectTypeName::Player, ObjectTypeName::Potion, - ObjectTypeName::Static, ObjectTypeName::Weapon, ObjectTypeName::Apparatus, ObjectTypeName::Lockpick, - ObjectTypeName::Probe, ObjectTypeName::Repair - }); api["i18n"] = [i18n=context.mI18n](const std::string& context) { return i18n->getContext(context); }; const MWWorld::Store* gmst = &MWBase::Environment::get().getWorld()->getStore().get(); api["getGMST"] = [lua=context.mLua, gmst](const std::string& setting) -> sol::object diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index d80c649f4c..f7a405b437 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -23,7 +23,6 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); sol::table initQueryPackage(const Context&); - sol::table initTypesPackage(const Context&); sol::table initFieldGroup(const Context&, const QueryFieldGroup&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index cf1c69aa11..5b21d46427 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -22,6 +22,7 @@ #include "luabindings.hpp" #include "userdataserializer.hpp" +#include "types/types.hpp" namespace MWLua { @@ -86,6 +87,7 @@ namespace MWLua mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); mGlobalScripts.addPackage("openmw.settings", initGlobalSettingsPackage(context)); mGlobalScripts.addPackage("openmw.storage", initGlobalStoragePackage(context, &mGlobalStorage)); + mCameraPackage = initCameraPackage(localContext); mUserInterfacePackage = initUserInterfacePackage(localContext); mInputPackage = initInputPackage(localContext); diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp index dc903845f4..e81a6fbcc6 100644 --- a/apps/openmw/mwlua/object.cpp +++ b/apps/openmw/mwlua/object.cpp @@ -2,6 +2,8 @@ #include +#include "types/types.hpp" + namespace MWLua { @@ -10,88 +12,12 @@ namespace MWLua return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile); } - struct LuaObjectTypeInfo - { - std::string_view mName; - ESM::LuaScriptCfg::Flags mFlag = 0; - }; - - const static std::unordered_map luaObjectTypeInfo = { - {ESM::REC_ACTI, {ObjectTypeName::Activator, ESM::LuaScriptCfg::sActivator}}, - {ESM::REC_ARMO, {ObjectTypeName::Armor, ESM::LuaScriptCfg::sArmor}}, - {ESM::REC_BOOK, {ObjectTypeName::Book, ESM::LuaScriptCfg::sBook}}, - {ESM::REC_CLOT, {ObjectTypeName::Clothing, ESM::LuaScriptCfg::sClothing}}, - {ESM::REC_CONT, {ObjectTypeName::Container, ESM::LuaScriptCfg::sContainer}}, - {ESM::REC_CREA, {ObjectTypeName::Creature, ESM::LuaScriptCfg::sCreature}}, - {ESM::REC_DOOR, {ObjectTypeName::Door, ESM::LuaScriptCfg::sDoor}}, - {ESM::REC_INGR, {ObjectTypeName::Ingredient, ESM::LuaScriptCfg::sIngredient}}, - {ESM::REC_LIGH, {ObjectTypeName::Light, ESM::LuaScriptCfg::sLight}}, - {ESM::REC_MISC, {ObjectTypeName::MiscItem, ESM::LuaScriptCfg::sMiscItem}}, - {ESM::REC_NPC_, {ObjectTypeName::NPC, ESM::LuaScriptCfg::sNPC}}, - {ESM::REC_ALCH, {ObjectTypeName::Potion, ESM::LuaScriptCfg::sPotion}}, - {ESM::REC_STAT, {ObjectTypeName::Static}}, - {ESM::REC_WEAP, {ObjectTypeName::Weapon, ESM::LuaScriptCfg::sWeapon}}, - {ESM::REC_APPA, {ObjectTypeName::Apparatus}}, - {ESM::REC_LOCK, {ObjectTypeName::Lockpick}}, - {ESM::REC_PROB, {ObjectTypeName::Probe}}, - {ESM::REC_REPA, {ObjectTypeName::Repair}}, - }; - - std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback) - { - auto it = luaObjectTypeInfo.find(type); - if (it != luaObjectTypeInfo.end()) - return it->second.mName; - else - return fallback; - } - bool isMarker(const MWWorld::Ptr& ptr) { std::string_view id = ptr.getCellRef().getRefId(); return id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker"; } - std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr) - { - // Behaviour of this function is a part of OpenMW Lua API. We can not just return - // `ptr.getTypeDescription()` because its implementation is distributed over many files - // and can be accidentally changed. We use `ptr.getTypeDescription()` only as a fallback - // for types that are not present in `luaObjectTypeInfo` (for such types result stability - // is not necessary because they are not listed in OpenMW Lua documentation). - if (ptr.getCellRef().getRefId() == "player") - return ObjectTypeName::Player; - if (isMarker(ptr)) - return "Marker"; - return getLuaObjectTypeName(static_cast(ptr.getType()), /*fallback=*/ptr.getTypeDescription()); - } - - ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr) - { - if (ptr.getCellRef().getRefId() == "player") - return ESM::LuaScriptCfg::sPlayer; - if (isMarker(ptr)) - return 0; - auto it = luaObjectTypeInfo.find(static_cast(ptr.getType())); - if (it != luaObjectTypeInfo.end()) - return it->second.mFlag; - else - return 0; - } - - const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr) - { - if (ptr.getType() != recordType) - { - std::string msg = "Requires type '"; - msg.append(getLuaObjectTypeName(recordType)); - msg.append("', but applied to "); - msg.append(ptrToString(ptr)); - throw std::runtime_error(msg); - } - return ptr; - } - std::string ptrToString(const MWWorld::Ptr& ptr) { std::string res = "object"; diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 36744b8f93..1685d7506d 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -16,31 +16,6 @@ namespace MWLua { - namespace ObjectTypeName - { - // Names of object types in Lua. - // These names are part of OpenMW Lua API. - constexpr std::string_view Activator = "Activator"; - constexpr std::string_view Armor = "Armor"; - constexpr std::string_view Book = "Book"; - constexpr std::string_view Clothing = "Clothing"; - constexpr std::string_view Container = "Container"; - constexpr std::string_view Creature = "Creature"; - constexpr std::string_view Door = "Door"; - constexpr std::string_view Ingredient = "Ingredient"; - constexpr std::string_view Light = "Light"; - constexpr std::string_view MiscItem = "Miscellaneous"; - constexpr std::string_view NPC = "NPC"; - constexpr std::string_view Player = "Player"; - constexpr std::string_view Potion = "Potion"; - constexpr std::string_view Static = "Static"; - constexpr std::string_view Weapon = "Weapon"; - constexpr std::string_view Apparatus = "Apparatus"; - constexpr std::string_view Lockpick = "Lockpick"; - constexpr std::string_view Probe = "Probe"; - constexpr std::string_view Repair = "Repair"; - } - // ObjectId is a unique identifier of a game object. // It can change only if the order of content files was change. using ObjectId = ESM::RefNum; @@ -48,13 +23,6 @@ namespace MWLua std::string idToString(const ObjectId& id); std::string ptrToString(const MWWorld::Ptr& ptr); bool isMarker(const MWWorld::Ptr& ptr); - std::string_view getLuaObjectTypeName(ESM::RecNameInts recordType, std::string_view fallback = "Unknown"); - std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr); - const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr); - - // Each script has a set of flags that controls to which objects the script should be - // automatically attached. This function maps each object types to one of the flags. - ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr); // Holds a mapping ObjectId -> MWWord::Ptr. class ObjectRegistry @@ -98,7 +66,6 @@ namespace MWLua ObjectId id() const { return mId; } std::string toString() const; - std::string_view type() const { return getLuaObjectTypeName(ptr()); } // Updates and returns the underlying Ptr. Throws an exception if object is not available. const MWWorld::Ptr& ptr() const; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 0310541eee..092a8d5151 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -10,6 +10,7 @@ #include "eventqueue.hpp" #include "luamanagerimp.hpp" +#include "types/types.hpp" namespace MWLua { @@ -39,226 +40,248 @@ namespace sol namespace MWLua { - template - using Cell = std::conditional_t, LCell, GCell>; + namespace { - template - static void registerObjectList(const std::string& prefix, const Context& context) - { - using ListT = ObjectList; - sol::state& lua = context.mLua->sol(); - ObjectRegistry* registry = context.mWorldView->getObjectRegistry(); - sol::usertype listT = lua.new_usertype(prefix + "ObjectList"); - listT[sol::meta_function::to_string] = - [](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; }; - listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); }; - listT[sol::meta_function::index] = [registry](const ListT& list, size_t index) - { - if (index > 0 && index <= list.mIds->size()) - return ObjectT((*list.mIds)[index - 1], registry); - else - throw std::runtime_error("Index out of range"); - }; - listT[sol::meta_function::ipairs] = [registry](const ListT& list) + template + using Cell = std::conditional_t, LCell, GCell>; + + template + void registerObjectList(const std::string& prefix, const Context& context) { - auto iter = [registry](const ListT& l, int64_t i) -> sol::optional> + using ListT = ObjectList; + sol::state& lua = context.mLua->sol(); + ObjectRegistry* registry = context.mWorldView->getObjectRegistry(); + sol::usertype listT = lua.new_usertype(prefix + "ObjectList"); + listT[sol::meta_function::to_string] = + [](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; }; + listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); }; + listT[sol::meta_function::index] = [registry](const ListT& list, size_t index) { - if (i >= 0 && i < static_cast(l.mIds->size())) - return std::make_tuple(i + 1, ObjectT((*l.mIds)[i], registry)); + if (index > 0 && index <= list.mIds->size()) + return ObjectT((*list.mIds)[index - 1], registry); else - return sol::nullopt; + throw std::runtime_error("Index out of range"); }; - return std::make_tuple(iter, list, 0); - }; - listT["select"] = [context](const ListT& list, const Queries::Query& query) - { - return ListT{selectObjectsFromList(query, list.mIds, context)}; - }; - } - - template - static void addBasicBindings(sol::usertype& objectT, const Context& context) - { - objectT["isValid"] = [](const ObjectT& o) { return o.isValid(); }; - objectT["recordId"] = sol::readonly_property([](const ObjectT& o) -> std::string - { - return o.ptr().getCellRef().getRefId(); - }); - objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional> - { - const MWWorld::Ptr& ptr = o.ptr(); - if (ptr.isInCell()) - return Cell{ptr.getCell()}; + if constexpr (std::is_same_v) + { + // GObject and LObject iterators are in separate branches because if they share source code + // there is a collision in sol and only one iterator can be mapped to Lua. + auto iter = sol::make_object(lua, [registry](const GObjectList& l, int64_t i) -> sol::optional> + { + if (i >= 0 && i < static_cast(l.mIds->size())) + return std::make_tuple(i + 1, GObject((*l.mIds)[i], registry)); + else + return sol::nullopt; + }); + listT[sol::meta_function::ipairs] = [iter](const GObjectList& list) { return std::make_tuple(iter, list, 0); }; + } else - return sol::nullopt; - }); - objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f - { - return o.ptr().getRefData().getPosition().asVec3(); - }); - objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f - { - return o.ptr().getRefData().getPosition().asRotationVec3(); - }); - objectT["type"] = sol::readonly_property(&ObjectT::type); - objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); - objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; - objectT[sol::meta_function::to_string] = &ObjectT::toString; - objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) - { - context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); - }; + { + auto iter = sol::make_object(lua, [registry](const LObjectList& l, int64_t i) -> sol::optional> + { + if (i >= 0 && i < static_cast(l.mIds->size())) + return std::make_tuple(i + 1, LObject((*l.mIds)[i], registry)); + else + return sol::nullopt; + }); + listT[sol::meta_function::ipairs] = [iter](const LObjectList& list) { return std::make_tuple(iter, list, 0); }; + } + listT["select"] = [context](const ListT& list, const Queries::Query& query) + { + return ListT{selectObjectsFromList(query, list.mIds, context)}; + }; + } - objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor) + template + void addBasicBindings(sol::usertype& objectT, const Context& context) { - uint32_t esmRecordType = actor.ptr().getType(); - if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_) - throw std::runtime_error("The argument of `activateBy` must be an actor who activates the object. Got: " + - ptrToString(actor.ptr())); - context.mLuaManager->addAction(std::make_unique(context.mLua, o.id(), actor.id())); - }; - - if constexpr (std::is_same_v) - { // Only for global scripts - objectT["addScript"] = [lua=context.mLua, luaManager=context.mLuaManager](const GObject& object, std::string_view path) + objectT["isValid"] = [](const ObjectT& o) { return o.isValid(); }; + objectT["recordId"] = sol::readonly_property([](const ObjectT& o) -> std::string { - const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); - std::optional scriptId = cfg.findId(path); - if (!scriptId) - throw std::runtime_error("Unknown script: " + std::string(path)); - if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom)) - throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path)); - luaManager->addCustomLocalScript(object.ptr(), *scriptId); - }; - objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path) + return o.ptr().getCellRef().getRefId(); + }); + objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional> { - const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); - std::optional scriptId = cfg.findId(path); - if (!scriptId) - return false; - MWWorld::Ptr ptr = object.ptr(); - LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); - if (localScripts) - return localScripts->hasScript(*scriptId); + const MWWorld::Ptr& ptr = o.ptr(); + if (ptr.isInCell()) + return Cell{ptr.getCell()}; else - return false; - }; - objectT["removeScript"] = [lua=context.mLua](const GObject& object, std::string_view path) + return sol::nullopt; + }); + objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f + { + return o.ptr().getRefData().getPosition().asVec3(); + }); + objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f { - const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); - std::optional scriptId = cfg.findId(path); - if (!scriptId) - throw std::runtime_error("Unknown script: " + std::string(path)); - MWWorld::Ptr ptr = object.ptr(); - LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); - if (!localScripts || !localScripts->hasScript(*scriptId)) - throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr)); - ESM::LuaScriptCfg::Flags flags = cfg[*scriptId].mFlags; - if ((flags & (localScripts->getAutoStartMode() | ESM::LuaScriptCfg::sCustom)) != ESM::LuaScriptCfg::sCustom) - throw std::runtime_error("Autostarted script can not be removed: " + std::string(path)); - localScripts->removeScript(*scriptId); + return o.ptr().getRefData().getPosition().asRotationVec3(); + }); + + objectT["type"] = sol::readonly_property([types=getTypeToPackageTable(context.mLua->sol())](const ObjectT& o) mutable + { + return types[o.ptr().getLuaType()]; + }); + + objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); + objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; + objectT[sol::meta_function::to_string] = &ObjectT::toString; + objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) + { + context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; - objectT["teleport"] = [context](const GObject& object, std::string_view cell, - const osg::Vec3f& pos, const sol::optional& optRot) + objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor) { - MWWorld::Ptr ptr = object.ptr(); - osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); - auto action = std::make_unique(context.mLua, object.id(), std::string(cell), pos, rot); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - context.mLuaManager->addTeleportPlayerAction(std::move(action)); - else - context.mLuaManager->addAction(std::move(action)); + uint32_t esmRecordType = actor.ptr().getType(); + if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_) + throw std::runtime_error("The argument of `activateBy` must be an actor who activates the object. Got: " + + ptrToString(actor.ptr())); + context.mLuaManager->addAction(std::make_unique(context.mLua, o.id(), actor.id())); }; - } - } - template - static void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) - { - using InventoryT = Inventory; - sol::usertype inventoryT = context.mLua->sol().new_usertype(prefix + "Inventory"); + if constexpr (std::is_same_v) + { // Only for global scripts + objectT["addScript"] = [lua=context.mLua, luaManager=context.mLuaManager](const GObject& object, std::string_view path) + { + const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + throw std::runtime_error("Unknown script: " + std::string(path)); + if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom)) + throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path)); + luaManager->addCustomLocalScript(object.ptr(), *scriptId); + }; + objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path) + { + const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + return false; + MWWorld::Ptr ptr = object.ptr(); + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (localScripts) + return localScripts->hasScript(*scriptId); + else + return false; + }; + objectT["removeScript"] = [lua=context.mLua](const GObject& object, std::string_view path) + { + const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration(); + std::optional scriptId = cfg.findId(path); + if (!scriptId) + throw std::runtime_error("Unknown script: " + std::string(path)); + MWWorld::Ptr ptr = object.ptr(); + LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); + if (!localScripts || !localScripts->hasScript(*scriptId)) + throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr)); + ESM::LuaScriptCfg::Flags flags = cfg[*scriptId].mFlags; + if ((flags & (localScripts->getAutoStartMode() | ESM::LuaScriptCfg::sCustom)) != ESM::LuaScriptCfg::sCustom) + throw std::runtime_error("Autostarted script can not be removed: " + std::string(path)); + localScripts->removeScript(*scriptId); + }; - objectT["inventory"] = sol::readonly_property([](const ObjectT& o) { return InventoryT{o}; }); - inventoryT[sol::meta_function::to_string] = - [](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; }; + objectT["teleport"] = [context](const GObject& object, std::string_view cell, + const osg::Vec3f& pos, const sol::optional& optRot) + { + MWWorld::Ptr ptr = object.ptr(); + osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); + auto action = std::make_unique(context.mLua, object.id(), std::string(cell), pos, rot); + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + context.mLuaManager->addTeleportPlayerAction(std::move(action)); + else + context.mLuaManager->addAction(std::move(action)); + }; + } + } - inventoryT["getAll"] = [worldView=context.mWorldView](const InventoryT& inventory, sol::optional type) + template + void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) { - int mask; - if (!type.has_value()) - mask = MWWorld::ContainerStore::Type_All; - else if (*type == ObjectTypeName::Potion) - mask = MWWorld::ContainerStore::Type_Potion; - else if (*type == ObjectTypeName::Armor) - mask = MWWorld::ContainerStore::Type_Armor; - else if (*type == ObjectTypeName::Book) - mask = MWWorld::ContainerStore::Type_Book; - else if (*type == ObjectTypeName::Clothing) - mask = MWWorld::ContainerStore::Type_Clothing; - else if (*type == ObjectTypeName::Ingredient) - mask = MWWorld::ContainerStore::Type_Ingredient; - else if (*type == ObjectTypeName::Light) - mask = MWWorld::ContainerStore::Type_Light; - else if (*type == ObjectTypeName::MiscItem) - mask = MWWorld::ContainerStore::Type_Miscellaneous; - else if (*type == ObjectTypeName::Weapon) - mask = MWWorld::ContainerStore::Type_Weapon; - else if (*type == ObjectTypeName::Apparatus) - mask = MWWorld::ContainerStore::Type_Apparatus; - else if (*type == ObjectTypeName::Lockpick) - mask = MWWorld::ContainerStore::Type_Lockpick; - else if (*type == ObjectTypeName::Probe) - mask = MWWorld::ContainerStore::Type_Probe; - else if (*type == ObjectTypeName::Repair) - mask = MWWorld::ContainerStore::Type_Repair; - else - throw std::runtime_error(std::string("inventory:getAll doesn't support type " + std::string(*type))); + using InventoryT = Inventory; + sol::usertype inventoryT = context.mLua->sol().new_usertype(prefix + "Inventory"); - const MWWorld::Ptr& ptr = inventory.mObj.ptr(); - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - ObjectIdList list = std::make_shared>(); - auto it = store.begin(mask); - while (it.getType() != -1) + objectT["inventory"] = sol::readonly_property([](const ObjectT& o) { return InventoryT{o}; }); + inventoryT[sol::meta_function::to_string] = + [](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; }; + + inventoryT["getAll"] = [worldView=context.mWorldView, ids=getPackageToTypeTable(context.mLua->sol())]( + const InventoryT& inventory, sol::optional type) { - const MWWorld::Ptr& item = *(it++); - worldView->getObjectRegistry()->registerPtr(item); - list->push_back(getId(item)); - } - return ObjectList{list}; - }; + int mask = -1; + sol::optional typeId = sol::nullopt; + if (type.has_value()) + typeId = ids[*type]; + else + mask = MWWorld::ContainerStore::Type_All; - inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId) - { - const MWWorld::Ptr& ptr = inventory.mObj.ptr(); - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - return store.count(recordId); - }; + if (typeId.has_value()) + { + switch (*typeId) + { + case ESM::REC_ALCH: mask = MWWorld::ContainerStore::Type_Potion; break; + case ESM::REC_ARMO: mask = MWWorld::ContainerStore::Type_Armor; break; + case ESM::REC_BOOK: mask = MWWorld::ContainerStore::Type_Book; break; + case ESM::REC_CLOT: mask = MWWorld::ContainerStore::Type_Clothing; break; + case ESM::REC_INGR: mask = MWWorld::ContainerStore::Type_Ingredient; break; + case ESM::REC_LIGH: mask = MWWorld::ContainerStore::Type_Light; break; + case ESM::REC_MISC: mask = MWWorld::ContainerStore::Type_Miscellaneous; break; + case ESM::REC_WEAP: mask = MWWorld::ContainerStore::Type_Weapon; break; + case ESM::REC_APPA: mask = MWWorld::ContainerStore::Type_Apparatus; break; + case ESM::REC_LOCK: mask = MWWorld::ContainerStore::Type_Lockpick; break; + case ESM::REC_PROB: mask = MWWorld::ContainerStore::Type_Probe; break; + case ESM::REC_REPA: mask = MWWorld::ContainerStore::Type_Repair; break; + default:; + } + } + + if (mask == -1) + throw std::runtime_error(std::string("Incorrect type argument in inventory:getAll: " + LuaUtil::toString(*type))); + + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + ObjectIdList list = std::make_shared>(); + auto it = store.begin(mask); + while (it.getType() != -1) + { + const MWWorld::Ptr& item = *(it++); + worldView->getObjectRegistry()->registerPtr(item); + list->push_back(getId(item)); + } + return ObjectList{list}; + }; + + inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId) + { + const MWWorld::Ptr& ptr = inventory.mObj.ptr(); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + return store.count(recordId); + }; - if constexpr (std::is_same_v) - { // Only for global scripts - // TODO - // obj.inventory:drop(obj2, [count]) - // obj.inventory:drop(recordId, [count]) - // obj.inventory:addNew(recordId, [count]) - // obj.inventory:remove(obj/recordId, [count]) - /*objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {}; - inventoryT["drop"] = [](const InventoryT& inventory) {}; - inventoryT["addNew"] = [](const InventoryT& inventory) {}; - inventoryT["remove"] = [](const InventoryT& inventory) {};*/ + if constexpr (std::is_same_v) + { // Only for global scripts + // TODO + // obj.inventory:drop(obj2, [count]) + // obj.inventory:drop(recordId, [count]) + // obj.inventory:addNew(recordId, [count]) + // obj.inventory:remove(obj/recordId, [count]) + /*objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {}; + inventoryT["drop"] = [](const InventoryT& inventory) {}; + inventoryT["addNew"] = [](const InventoryT& inventory) {}; + inventoryT["remove"] = [](const InventoryT& inventory) {};*/ + } } - } - template - static void initObjectBindings(const std::string& prefix, const Context& context) - { - sol::usertype objectT = context.mLua->sol().new_usertype( - prefix + "Object", sol::base_classes, sol::bases()); - addBasicBindings(objectT, context); - addInventoryBindings(objectT, prefix, context); + template + void initObjectBindings(const std::string& prefix, const Context& context) + { + sol::usertype objectT = context.mLua->sol().new_usertype( + prefix + "Object", sol::base_classes, sol::bases()); + addBasicBindings(objectT, context); + addInventoryBindings(objectT, prefix, context); - registerObjectList(prefix, context); - } + registerObjectList(prefix, context); + } + } // namespace void initObjectBindingsForLocalScripts(const Context& context) { diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp new file mode 100644 index 0000000000..f91400d5ea --- /dev/null +++ b/apps/openmw/mwlua/types/types.cpp @@ -0,0 +1,193 @@ +#include "types.hpp" + +#include + +namespace MWLua +{ + namespace ObjectTypeName + { + // Names of object types in Lua. + // These names are part of OpenMW Lua API. + constexpr std::string_view Actor = "Actor"; // base type for NPC, Creature, Player + constexpr std::string_view Item = "Item"; // base type for all items + + constexpr std::string_view Activator = "Activator"; + constexpr std::string_view Armor = "Armor"; + constexpr std::string_view Book = "Book"; + constexpr std::string_view Clothing = "Clothing"; + constexpr std::string_view Container = "Container"; + constexpr std::string_view Creature = "Creature"; + constexpr std::string_view Door = "Door"; + constexpr std::string_view Ingredient = "Ingredient"; + constexpr std::string_view Light = "Light"; + constexpr std::string_view MiscItem = "Miscellaneous"; + constexpr std::string_view NPC = "NPC"; + constexpr std::string_view Player = "Player"; + constexpr std::string_view Potion = "Potion"; + constexpr std::string_view Static = "Static"; + constexpr std::string_view Weapon = "Weapon"; + constexpr std::string_view Apparatus = "Apparatus"; + constexpr std::string_view Lockpick = "Lockpick"; + constexpr std::string_view Probe = "Probe"; + constexpr std::string_view Repair = "Repair"; + constexpr std::string_view Marker = "Marker"; + } + + namespace + { + struct LuaObjectTypeInfo + { + std::string_view mName; + ESM::LuaScriptCfg::Flags mFlag = 0; + }; + + const static std::unordered_map luaObjectTypeInfo = { + {ESM::REC_INTERNAL_PLAYER, {ObjectTypeName::Player, ESM::LuaScriptCfg::sPlayer}}, + {ESM::REC_INTERNAL_MARKER, {ObjectTypeName::Marker}}, + {ESM::REC_ACTI, {ObjectTypeName::Activator, ESM::LuaScriptCfg::sActivator}}, + {ESM::REC_ARMO, {ObjectTypeName::Armor, ESM::LuaScriptCfg::sArmor}}, + {ESM::REC_BOOK, {ObjectTypeName::Book, ESM::LuaScriptCfg::sBook}}, + {ESM::REC_CLOT, {ObjectTypeName::Clothing, ESM::LuaScriptCfg::sClothing}}, + {ESM::REC_CONT, {ObjectTypeName::Container, ESM::LuaScriptCfg::sContainer}}, + {ESM::REC_CREA, {ObjectTypeName::Creature, ESM::LuaScriptCfg::sCreature}}, + {ESM::REC_DOOR, {ObjectTypeName::Door, ESM::LuaScriptCfg::sDoor}}, + {ESM::REC_INGR, {ObjectTypeName::Ingredient, ESM::LuaScriptCfg::sIngredient}}, + {ESM::REC_LIGH, {ObjectTypeName::Light, ESM::LuaScriptCfg::sLight}}, + {ESM::REC_MISC, {ObjectTypeName::MiscItem, ESM::LuaScriptCfg::sMiscItem}}, + {ESM::REC_NPC_, {ObjectTypeName::NPC, ESM::LuaScriptCfg::sNPC}}, + {ESM::REC_ALCH, {ObjectTypeName::Potion, ESM::LuaScriptCfg::sPotion}}, + {ESM::REC_STAT, {ObjectTypeName::Static}}, + {ESM::REC_WEAP, {ObjectTypeName::Weapon, ESM::LuaScriptCfg::sWeapon}}, + {ESM::REC_APPA, {ObjectTypeName::Apparatus}}, + {ESM::REC_LOCK, {ObjectTypeName::Lockpick}}, + {ESM::REC_PROB, {ObjectTypeName::Probe}}, + {ESM::REC_REPA, {ObjectTypeName::Repair}}, + }; + + } + + std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback) + { + auto it = luaObjectTypeInfo.find(type); + if (it != luaObjectTypeInfo.end()) + return it->second.mName; + else + return fallback; + } + + std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr) + { + return getLuaObjectTypeName(static_cast(ptr.getLuaType()), /*fallback=*/ptr.getTypeDescription()); + } + + ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr) + { + auto it = luaObjectTypeInfo.find(static_cast(ptr.getLuaType())); + if (it != luaObjectTypeInfo.end()) + return it->second.mFlag; + else + return 0; + } + + const MWWorld::Ptr& verifyType(ESM::RecNameInts recordType, const MWWorld::Ptr& ptr) + { + if (ptr.getType() != recordType) + { + std::string msg = "Requires type '"; + msg.append(getLuaObjectTypeName(recordType)); + msg.append("', but applied to "); + msg.append(ptrToString(ptr)); + throw std::runtime_error(msg); + } + return ptr; + } + + sol::table getTypeToPackageTable(lua_State* L) + { + constexpr std::string_view key = "typeToPackage"; + sol::state_view lua(L); + if (lua[key] == sol::nil) + lua[key] = sol::table(lua, sol::create); + return lua[key]; + } + + sol::table getPackageToTypeTable(lua_State* L) + { + constexpr std::string_view key = "packageToType"; + sol::state_view lua(L); + if (lua[key] == sol::nil) + lua[key] = sol::table(lua, sol::create); + return lua[key]; + } + + sol::table initTypesPackage(const Context& context) + { + auto* lua = context.mLua; + sol::table types(lua->sol(), sol::create); + auto addType = [&](std::string_view name, std::vector recTypes, + std::optional base = std::nullopt) -> sol::table + { + sol::table t(lua->sol(), sol::create); + sol::table ro = LuaUtil::makeReadOnly(t); + sol::table meta = ro[sol::metatable_key]; + meta[sol::meta_function::to_string] = [name]() { return name; }; + if (base) + { + t["baseType"] = types[*base]; + sol::table baseMeta(lua->sol(), sol::create); + baseMeta[sol::meta_function::index] = LuaUtil::getMutableFromReadOnly(types[*base]); + t[sol::metatable_key] = baseMeta; + } + t["objectIsInstance"] = [types=recTypes](const Object& o) + { + unsigned int type = o.ptr().getLuaType(); + for (ESM::RecNameInts t : types) + if (t == type) + return true; + return false; + }; + types[name] = ro; + return t; + }; + + addActorBindings(addType(ObjectTypeName::Actor, {ESM::REC_INTERNAL_PLAYER, ESM::REC_CREA, ESM::REC_NPC_}), context); + addType(ObjectTypeName::Item, {ESM::REC_ARMO, ESM::REC_BOOK, ESM::REC_CLOT, ESM::REC_INGR, + ESM::REC_LIGH, ESM::REC_MISC, ESM::REC_ALCH, ESM::REC_WEAP, + ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA}); + + addType(ObjectTypeName::Creature, {ESM::REC_CREA}, ObjectTypeName::Actor); + addType(ObjectTypeName::NPC, {ESM::REC_INTERNAL_PLAYER, ESM::REC_NPC_}, ObjectTypeName::Actor); + addType(ObjectTypeName::Player, {ESM::REC_INTERNAL_PLAYER}, ObjectTypeName::NPC); + + addType(ObjectTypeName::Armor, {ESM::REC_ARMO}, ObjectTypeName::Item); + addType(ObjectTypeName::Book, {ESM::REC_BOOK}, ObjectTypeName::Item); + addType(ObjectTypeName::Clothing, {ESM::REC_CLOT}, ObjectTypeName::Item); + addType(ObjectTypeName::Ingredient, {ESM::REC_INGR}, ObjectTypeName::Item); + addType(ObjectTypeName::Light, {ESM::REC_LIGH}, ObjectTypeName::Item); + addType(ObjectTypeName::MiscItem, {ESM::REC_MISC}, ObjectTypeName::Item); + addType(ObjectTypeName::Potion, {ESM::REC_ALCH}, ObjectTypeName::Item); + addType(ObjectTypeName::Weapon, {ESM::REC_WEAP}, ObjectTypeName::Item); + addType(ObjectTypeName::Apparatus, {ESM::REC_APPA}, ObjectTypeName::Item); + addType(ObjectTypeName::Lockpick, {ESM::REC_LOCK}, ObjectTypeName::Item); + addType(ObjectTypeName::Probe, {ESM::REC_PROB}, ObjectTypeName::Item); + addType(ObjectTypeName::Repair, {ESM::REC_REPA}, ObjectTypeName::Item); + + addType(ObjectTypeName::Activator, {ESM::REC_ACTI}); + addType(ObjectTypeName::Container, {ESM::REC_CONT}); + addDoorBindings(addType(ObjectTypeName::Door, {ESM::REC_DOOR}), context); + addType(ObjectTypeName::Static, {ESM::REC_STAT}); + + sol::table typeToPackage = getTypeToPackageTable(context.mLua->sol()); + sol::table packageToType = getPackageToTypeTable(context.mLua->sol()); + for (const auto& [type, v] : luaObjectTypeInfo) + { + sol::object t = types[v.mName]; + if (t == sol::nil) + continue; + typeToPackage[type] = t; + packageToType[t] = type; + } + + return LuaUtil::makeReadOnly(types); + } +} diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index 20fd208462..63d3cb4db5 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -3,10 +3,27 @@ #include +#include +#include + #include "../context.hpp" namespace MWLua { + std::string_view getLuaObjectTypeName(ESM::RecNameInts type, std::string_view fallback = "Unknown"); + std::string_view getLuaObjectTypeName(const MWWorld::Ptr& ptr); + const MWWorld::Ptr& verifyType(ESM::RecNameInts type, const MWWorld::Ptr& ptr); + + sol::table getTypeToPackageTable(lua_State* L); + sol::table getPackageToTypeTable(lua_State* L); + + // Each script has a set of flags that controls to which objects the script should be + // automatically attached. This function maps each object types to one of the flags. + ESM::LuaScriptCfg::Flags getLuaScriptFlag(const MWWorld::Ptr& ptr); + + sol::table initTypesPackage(const Context& context); + + // used in initTypesPackage void addDoorBindings(sol::table door, const Context& context); void addActorBindings(sol::table actor, const Context& context); } diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 4dbdfa5545..33e062df90 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -47,6 +47,23 @@ namespace MWWorld throw std::runtime_error("Can't get type name from an empty object."); } + // `getType()` is not exactly what we usually mean by "type" because some refids have special meaning. + // This function handles these special refids (and by this adds some performance overhead). + // We use this "fixed" type in Lua because we don't want to expose the weirdness of Morrowind internals to our API. + // TODO: Implement https://gitlab.com/OpenMW/openmw/-/issues/6617 and make `getType` work the same as `getLuaType`. + unsigned int getLuaType() const + { + if(mRef == nullptr) + throw std::runtime_error("Can't get type name from an empty object."); + std::string_view id = mRef->mRef.getRefId(); + if (id == "player") + return ESM::REC_INTERNAL_PLAYER; + else if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") + return ESM::REC_INTERNAL_MARKER; + else + return mRef->getType(); + } + std::string_view getTypeDescription() const { return mRef ? mRef->getTypeDescription() : "nullptr"; diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index f587fc6690..45a8cf0f56 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -94,6 +94,11 @@ constexpr unsigned int fourCC(const char(&name)[len]) { enum RecNameInts : unsigned int { + // Special values. Can not be used in any ESM. + // Added to this enum to guarantee that the values don't collide with any records. + REC_INTERNAL_PLAYER = 0, + REC_INTERNAL_MARKER = 1, + // format 0 / legacy REC_ACTI = fourCC("ACTI"), REC_ALCH = fourCC("ALCH"), From 43bed7f0d2add7ccdd51a8cfad40b868b1dc1c08 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 10 Mar 2022 00:31:44 +0100 Subject: [PATCH 2197/2859] [Lua] Split obj.inventory into Actor.inventory(obj) and Container.content(obj) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 1 + apps/openmw/mwlua/object.hpp | 3 +++ apps/openmw/mwlua/objectbindings.cpp | 12 ++---------- apps/openmw/mwlua/types/actor.cpp | 12 ++++++------ apps/openmw/mwlua/types/container.cpp | 18 ++++++++++++++++++ apps/openmw/mwlua/types/types.cpp | 2 +- apps/openmw/mwlua/types/types.hpp | 1 + 8 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 apps/openmw/mwlua/types/container.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6b439bde80..c29b33b28c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -60,7 +60,7 @@ add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings - types/types types/door types/actor + types/types types/door types/actor types/container ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 5b21d46427..9c5c210e4d 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -391,6 +391,7 @@ namespace MWLua { assert(mInitialized); assert(flag != ESM::LuaScriptCfg::sGlobal); + assert(ptr.getType() != ESM::REC_STAT); std::shared_ptr scripts; if (flag == ESM::LuaScriptCfg::sPlayer) { diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 1685d7506d..499c21c7e7 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -118,6 +118,9 @@ namespace MWLua using GObjectList = ObjectList; using LObjectList = ObjectList; + template + struct Inventory { Obj mObj; }; + } #endif // MWLUA_OBJECT_H diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 092a8d5151..6bcf355fde 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -12,15 +12,6 @@ #include "luamanagerimp.hpp" #include "types/types.hpp" -namespace MWLua -{ - template - struct Inventory - { - ObjectT mObj; - }; -} - namespace sol { template <> @@ -149,6 +140,8 @@ namespace MWLua throw std::runtime_error("Unknown script: " + std::string(path)); if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom)) throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path)); + if (object.ptr().getType() == ESM::REC_STAT) + throw std::runtime_error("Attaching scripts to Static is not allowed: " + std::string(path)); luaManager->addCustomLocalScript(object.ptr(), *scriptId); }; objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path) @@ -200,7 +193,6 @@ namespace MWLua using InventoryT = Inventory; sol::usertype inventoryT = context.mLua->sol().new_usertype(prefix + "Inventory"); - objectT["inventory"] = sol::readonly_property([](const ObjectT& o) { return InventoryT{o}; }); inventoryT[sol::meta_function::to_string] = [](const InventoryT& inv) { return "Inventory[" + inv.mObj.toString() + "]"; }; diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index ba2c05a52d..d620472095 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -83,7 +83,11 @@ namespace MWLua return MWBase::Environment::get().getWorld()->isSwimming(o.ptr()); }; - actor["getEquipment"] = [context](const Object& o) + actor["inventory"] = sol::overload( + [](const LObject& o) { return Inventory{o}; }, + [](const GObject& o) { return Inventory{o}; } + ); + actor["equipment"] = [context](const Object& o) { const MWWorld::Ptr& ptr = o.ptr(); sol::table equipment(context.mLua->sol(), sol::create); @@ -109,12 +113,8 @@ namespace MWLua MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); return store.isEquipped(item.ptr()); }; - actor["setEquipment"] = [context](const sol::object& luaObj, const sol::table& equipment) + actor["setEquipment"] = [context](const SelfObject& obj, const sol::table& equipment) { - if (!luaObj.is() && !luaObj.is()) - throw std::runtime_error("Incorrect type of the first argument. " - "Can be either self (in local scripts) or game object (in global scripts)"); - const Object& obj = luaObj.as(); if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) { if (!equipment.empty()) diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp new file mode 100644 index 0000000000..b603b6d3f4 --- /dev/null +++ b/apps/openmw/mwlua/types/container.cpp @@ -0,0 +1,18 @@ +#include "types.hpp" + +#include "../luabindings.hpp" + +namespace MWLua +{ + + static const MWWorld::Ptr& containerPtr(const Object& o) { return verifyType(ESM::REC_CONT, o.ptr()); } + + void addContainerBindings(sol::table container, const Context& context) + { + container["content"] = sol::overload( + [](const LObject& o) { containerPtr(o); return Inventory{o}; }, + [](const GObject& o) { containerPtr(o); return Inventory{o}; } + ); + } + +} diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index f91400d5ea..556c13963f 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -173,7 +173,7 @@ namespace MWLua addType(ObjectTypeName::Repair, {ESM::REC_REPA}, ObjectTypeName::Item); addType(ObjectTypeName::Activator, {ESM::REC_ACTI}); - addType(ObjectTypeName::Container, {ESM::REC_CONT}); + addContainerBindings(addType(ObjectTypeName::Container, {ESM::REC_CONT}), context); addDoorBindings(addType(ObjectTypeName::Door, {ESM::REC_DOOR}), context); addType(ObjectTypeName::Static, {ESM::REC_STAT}); diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index 63d3cb4db5..966e32ccf2 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -24,6 +24,7 @@ namespace MWLua sol::table initTypesPackage(const Context& context); // used in initTypesPackage + void addContainerBindings(sol::table door, const Context& context); void addDoorBindings(sol::table door, const Context& context); void addActorBindings(sol::table actor, const Context& context); } From 0f84bfde51ce9cd8ec9036486c183eab732ec727 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 5 Mar 2022 19:53:39 +0100 Subject: [PATCH 2198/2859] [Lua] Replace cell.selectObjects with cell.getAll --- apps/openmw/mwlua/cellbindings.cpp | 60 +++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index dbcbbab644..3d0c4d26df 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -3,6 +3,7 @@ #include #include "../mwworld/cellstore.hpp" +#include "types/types.hpp" namespace sol { @@ -50,9 +51,64 @@ namespace MWLua if constexpr (std::is_same_v) { // only for global scripts - cellT["selectObjects"] = [context](const CellT& cell, const Queries::Query& query) + cellT["getAll"] = [worldView=context.mWorldView, ids=getPackageToTypeTable(context.mLua->sol())]( + const CellT& cell, sol::optional type) { - return GObjectList{selectObjectsFromCellStore(query, cell.mStore, context)}; + ObjectIdList res = std::make_shared>(); + auto visitor = [&](const MWWorld::Ptr& ptr) + { + worldView->getObjectRegistry()->registerPtr(ptr); + if (ptr.getLuaType() == ptr.getType()) + res->push_back(getId(ptr)); + return true; + }; + + bool ok = false; + sol::optional typeId = sol::nullopt; + if (type.has_value()) + typeId = ids[*type]; + else + { + ok = true; + cell.mStore->forEach(std::move(visitor)); + } + if (typeId.has_value()) + { + ok = true; + switch (*typeId) + { + case ESM::REC_INTERNAL_PLAYER: + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getCell() == cell.mStore) + res->push_back(getId(player)); + } + break; + + case ESM::REC_CREA: cell.mStore->template forEachType(visitor); break; + case ESM::REC_NPC_: cell.mStore->template forEachType(visitor); break; + case ESM::REC_ACTI: cell.mStore->template forEachType(visitor); break; + case ESM::REC_DOOR: cell.mStore->template forEachType(visitor); break; + case ESM::REC_CONT: cell.mStore->template forEachType(visitor); break; + + case ESM::REC_ALCH: cell.mStore->template forEachType(visitor); break; + case ESM::REC_ARMO: cell.mStore->template forEachType(visitor); break; + case ESM::REC_BOOK: cell.mStore->template forEachType(visitor); break; + case ESM::REC_CLOT: cell.mStore->template forEachType(visitor); break; + case ESM::REC_INGR: cell.mStore->template forEachType(visitor); break; + case ESM::REC_LIGH: cell.mStore->template forEachType(visitor); break; + case ESM::REC_MISC: cell.mStore->template forEachType(visitor); break; + case ESM::REC_WEAP: cell.mStore->template forEachType(visitor); break; + case ESM::REC_APPA: cell.mStore->template forEachType(visitor); break; + case ESM::REC_LOCK: cell.mStore->template forEachType(visitor); break; + case ESM::REC_PROB: cell.mStore->template forEachType(visitor); break; + case ESM::REC_REPA: cell.mStore->template forEachType(visitor); break; + default: ok = false; + } + } + if (!ok) + throw std::runtime_error(std::string("Incorrect type argument in cell:getAll: " + LuaUtil::toString(*type))); + return GObjectList{res}; }; } } From 9af49cfa687f775c561ff071d6c4be86de29c72c Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 13 Mar 2022 00:38:36 +0100 Subject: [PATCH 2199/2859] [Lua] Update documentation --- docs/source/reference/lua-scripting/api.rst | 3 + .../reference/lua-scripting/openmw_types.rst | 5 + .../reference/lua-scripting/overview.rst | 2 + files/lua_api/CMakeLists.txt | 1 + files/lua_api/openmw/core.lua | 161 +------ files/lua_api/openmw/types.lua | 456 ++++++++++++++++++ 6 files changed, 483 insertions(+), 145 deletions(-) create mode 100644 docs/source/reference/lua-scripting/openmw_types.rst create mode 100644 files/lua_api/openmw/types.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 0681c63dea..4518028073 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -12,6 +12,7 @@ Lua API reference openmw_util openmw_storage openmw_core + openmw_types openmw_async openmw_query openmw_world @@ -56,6 +57,8 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.core ` | everywhere | | Functions that are common for both global and local scripts | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.types ` | everywhere | | Functions for specific types of game objects. | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.query ` | everywhere | | Tools for constructing queries: base queries and fields. | diff --git a/docs/source/reference/lua-scripting/openmw_types.rst b/docs/source/reference/lua-scripting/openmw_types.rst new file mode 100644 index 0000000000..1819b1a6ce --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_types.rst @@ -0,0 +1,5 @@ +Package openmw.types +==================== + +.. raw:: html + :file: generated_html/openmw_types.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 8fa255bac0..a819235e2a 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -353,6 +353,8 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.core ` | everywhere | | Functions that are common for both global and local scripts | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.types ` | everywhere | | Functions for specific types of game objects. | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.query ` | everywhere | | Tools for constructing queries: base queries and fields. | diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 8c5b46e8db..2988f38552 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -13,6 +13,7 @@ set(LUA_API_FILES openmw/ui.lua openmw/util.lua openmw/world.lua + openmw/types.lua ) foreach (f ${LUA_API_FILES}) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index d57b6a405e..afb0d2f5bb 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -86,59 +86,6 @@ -- print( myMsg('Hello %{name}!', {name='World'}) ) ---- --- @type OBJECT_TYPE --- @field #string Activator "Activator" --- @field #string Armor "Armor" --- @field #string Book "Book" --- @field #string Clothing "Clothing" --- @field #string Container "Container" --- @field #string Creature "Creature" --- @field #string Door "Door" --- @field #string Ingredient "Ingredient" --- @field #string Light "Light" --- @field #string Miscellaneous "Miscellaneous" --- @field #string NPC "NPC" --- @field #string Player "Player" --- @field #string Potion "Potion" --- @field #string Static "Static" --- @field #string Apparatus "Apparatus" --- @field #string Lockpick "Lockpick" --- @field #string Probe "Probe" --- @field #string Repair "Repair" - ---- --- Possible `object.type` values. --- @field [parent=#core] #OBJECT_TYPE OBJECT_TYPE - - ---- --- @type EQUIPMENT_SLOT --- @field #number Helmet --- @field #number Cuirass --- @field #number Greaves --- @field #number LeftPauldron --- @field #number RightPauldron --- @field #number LeftGauntlet --- @field #number RightGauntlet --- @field #number Boots --- @field #number Shirt --- @field #number Pants --- @field #number Skirt --- @field #number Robe --- @field #number LeftRing --- @field #number RightRing --- @field #number Amulet --- @field #number Belt --- @field #number CarriedRight --- @field #number CarriedLeft --- @field #number Ammunition - ---- --- Available equipment slots. Used in `object:getEquipment` and `object:setEquipment`. --- @field [parent=#core] #EQUIPMENT_SLOT EQUIPMENT_SLOT - - --- -- Any object that exists in the game world and has a specific location. -- Player, actors, items, and statics are game objects. @@ -146,73 +93,18 @@ -- @field openmw.util#Vector3 position Object position. -- @field openmw.util#Vector3 rotation Object rotation (ZXY order). -- @field #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil. --- @field #string type Type of the object (see @{openmw.core#OBJECT_TYPE}). --- @field #number count Count (makes sense if holded in a container). +-- @field #table type Type of the object (one of the tables from the package @{openmw.types#types}). +-- @field #number count Count (makes sense if stored in a container). -- @field #string recordId Record ID. --- @field #Inventory inventory Inventory of an Player/NPC or content of an container. --- @field #boolean isTeleport `True` if it is a teleport door (only if `object.type` == "Door"). --- @field openmw.util#Vector3 destPosition Destination (only if a teleport door). --- @field openmw.util#Vector3 destRotation Destination rotation (only if a teleport door). --- @field #string destCell Destination cell (only if a teleport door). --- --- Is the object still exists/available. +-- Does the object still exist and is available. -- Returns true if the object exists and loaded, and false otherwise. If false, then every -- access to the object will raise an error. -- @function [parent=#GameObject] isValid -- @param self -- @return #boolean ---- --- Returns true if the object is an actor and is able to move. For dead, paralized, --- or knocked down actors in returns false. --- access to the object will raise an error. --- @function [parent=#GameObject] canMove --- @param self --- @return #boolean - ---- --- Speed of running. Returns 0 if not an actor, but for dead actors it still returns a positive value. --- @function [parent=#GameObject] getRunSpeed --- @param self --- @return #number - ---- --- Speed of walking. Returns 0 if not an actor, but for dead actors it still returns a positive value. --- @function [parent=#GameObject] getWalkSpeed --- @param self --- @return #number - ---- --- Current speed. Can be called only from a local script. --- @function [parent=#GameObject] getCurrentSpeed --- @param self --- @return #number - ---- --- Is the actor standing on ground. Can be called only from a local script. --- @function [parent=#GameObject] isOnGround --- @param self --- @return #boolean - ---- --- Is the actor in water. Can be called only from a local script. --- @function [parent=#GameObject] isSwimming --- @param self --- @return #boolean - ---- --- Is the actor in weapon stance. Can be called only from a local script. --- @function [parent=#GameObject] isInWeaponStance --- @param self --- @return #boolean - ---- --- Is the actor in magic stance. Can be called only from a local script. --- @function [parent=#GameObject] isInMagicStance --- @param self --- @return #boolean - --- -- Send local event to the object. -- @function [parent=#GameObject] sendEvent @@ -228,36 +120,10 @@ -- @usage local self = require('openmw.self') -- object:activateBy(self) ---- --- Returns `true` if the item is equipped on the object. --- @function [parent=#GameObject] isEquipped --- @param self --- @param #GameObject item --- @return #boolean - ---- --- Get equipment. --- Returns a table `slot` -> `GameObject` of currently equipped items. --- See @{openmw.core#EQUIPMENT_SLOT}. Returns empty table if the object doesn't have --- equipment slots. --- @function [parent=#GameObject] getEquipment --- @param self --- @return #map<#number,#GameObject> - ---- --- Set equipment. --- Keys in the table are equipment slots (see @{openmw.core#EQUIPMENT_SLOT}). Each --- value can be either a `GameObject` or recordId. Raises an error if --- the object doesn't have equipment slots and table is not empty. Can be --- called only on self or from a global script. --- @function [parent=#GameObject] setEquipment --- @param self --- @param equipment - --- -- Add new local script to the object. -- Can be called only from a global script. Script should be specified in a content --- file (omwgame/omwaddon/omwscripts) with a CUSTOM flag. +-- file (omwgame/omwaddon/omwscripts) with a CUSTOM flag. Scripts can not be attached to Statics. -- @function [parent=#GameObject] addScript -- @param self -- @param #string scriptPath Path to the script in OpenMW virtual filesystem. @@ -325,12 +191,15 @@ -- end --- --- Select objects from the cell with a Query (only in global scripts). --- Returns an empty list if the cell is not loaded. --- @function [parent=#Cell] selectObjects +-- Get all objects of given type from the cell. +-- @function [parent=#Cell] getAll -- @param self --- @param openmw.query#Query query +-- @param type (optional) object type (see @{openmw.types#types}) -- @return #ObjectList +-- @usage +-- local type = require('openmw.types') +-- local all = cell:getAll() +-- local weapons = cell:getAll(types.Weapon) --- @@ -348,10 +217,12 @@ -- Get all items of given type from the inventory. -- @function [parent=#Inventory] getAll -- @param self --- @param type (optional) items type (see @{openmw.core#OBJECT_TYPE}) +-- @param type (optional) items type (see @{openmw.types#types}) -- @return #ObjectList --- @usage local all = inventory:getAll() --- local weapons = inventory:getAll(core.OBJECT_TYPE.Weapon) +-- @usage +-- local type = require('openmw.types') +-- local all = inventory:getAll() +-- local weapons = inventory:getAll(types.Weapon) return nil diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua new file mode 100644 index 0000000000..82dbded2e8 --- /dev/null +++ b/files/lua_api/openmw/types.lua @@ -0,0 +1,456 @@ +--- +-- `openmw.types` defines functions for specific types of game objects. +-- @module types +-- @usage local types = require('openmw.types') + +--- Common @{#Actor} functions for Creature, NPC, and Player. +-- @field [parent=#types] #Actor Actor + +--- Common functions for Creature, NPC, and Player. +-- @type Actor + +--- +-- Whether the object is an actor. +-- @function [parent=#Actor] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- +-- Actor inventory. +-- @function [parent=#Actor] inventory +-- @param openmw.core#GameObject actor +-- @return openmw.core#Inventory + +--- +-- @type EQUIPMENT_SLOT +-- @field #number Helmet +-- @field #number Cuirass +-- @field #number Greaves +-- @field #number LeftPauldron +-- @field #number RightPauldron +-- @field #number LeftGauntlet +-- @field #number RightGauntlet +-- @field #number Boots +-- @field #number Shirt +-- @field #number Pants +-- @field #number Skirt +-- @field #number Robe +-- @field #number LeftRing +-- @field #number RightRing +-- @field #number Amulet +-- @field #number Belt +-- @field #number CarriedRight +-- @field #number CarriedLeft +-- @field #number Ammunition + +--- +-- Available @{#EQUIPMENT_SLOT} values. Used in `Actor.equipment(obj)` and `Actor.setEquipment(obj, eqp)`. +-- @field [parent=#Actor] #EQUIPMENT_SLOT EQUIPMENT_SLOT + +--- +-- @type STANCE +-- @field #number Nothing Default stance +-- @field #number Weapon Weapon stance +-- @field #number Spell Magic stance + +--- @{#STANCE} +-- @field [parent=#Actor] #STANCE STANCE + +--- +-- Returns true if the object is an actor and is able to move. For dead, paralyzed, +-- or knocked down actors it returns false. +-- @function [parent=#Actor] canMove +-- @param openmw.core#GameObject object +-- @return #boolean + +--- +-- Speed of running. For dead actors it still returns a positive value. +-- @function [parent=#Actor] runSpeed +-- @param openmw.core#GameObject actor +-- @return #number + +--- +-- Speed of walking. For dead actors it still returns a positive value. +-- @function [parent=#Actor] walkSpeed +-- @param openmw.core#GameObject actor +-- @return #number + +--- +-- Current speed. +-- @function [parent=#Actor] currentSpeed +-- @param openmw.core#GameObject actor +-- @return #number + +--- +-- Is the actor standing on ground. Can be called only from a local script. +-- @function [parent=#Actor] isOnGround +-- @param openmw.core#GameObject actor +-- @return #boolean + +--- +-- Is the actor in water. Can be called only from a local script. +-- @function [parent=#Actor] isSwimming +-- @param openmw.core#GameObject actor +-- @return #boolean + +--- +-- Returns the current stance (whether a weapon/spell is readied), see the list of @{#STANCE} values. +-- @function [parent=#Actor] stance +-- @param openmw.core#GameObject actor +-- @return #number + +--- +-- Returns `true` if the item is equipped on the actor. +-- @function [parent=#Actor] isEquipped +-- @param openmw.core#GameObject actor +-- @param openmw.core#GameObject item +-- @return #boolean + +--- +-- Get equipment. +-- Returns a table `slot` -> @{openmw.core#GameObject} of currently equipped items. +-- See @{#EQUIPMENT_SLOT}. Returns empty table if the actor doesn't have +-- equipment slots. +-- @function [parent=#Actor] equipment +-- @param openmw.core#GameObject actor +-- @return #map<#number,openmw.core#GameObject> + +--- +-- Set equipment. +-- Keys in the table are equipment slots (see @{#EQUIPMENT_SLOT}). Each +-- value can be either a `GameObject` or recordId. Raises an error if +-- the actor doesn't have equipment slots and table is not empty. Can be +-- used only in local scripts and only on self. +-- @function [parent=#Actor] setEquipment +-- @param openmw.core#GameObject actor +-- @param equipment +-- @usage local self = require('openmw.self') +-- local Actor = require('openmw.types').Actor +-- Actor.setEquipment(self, {}) -- unequip all + + + +--- @{#Item} functions (all pickable items that can be placed to an inventory or container) +-- @field [parent=#types] #Item Item + +--- Functions for pickable items that can be placed to an inventory or container +-- @type Item + +--- +-- Whether the object is an item. +-- @function [parent=#Item] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#Creature} functions +-- @field [parent=#types] #Creature Creature + +--- +-- @type Creature +-- @extends #Actor +-- @field #Actor baseType @{#Actor} + +--- +-- Whether the object is a creature. +-- @function [parent=#Creature] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#NPC} functions +-- @field [parent=#types] #NPC NPC + +--- +-- @type NPC +-- @extends #Actor +-- @field #Actor baseType @{#Actor} + +--- +-- Whether the object is an NPC or a Player. +-- @function [parent=#NPC] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#Player} functions +-- @field [parent=#types] #Player Player + +--- +-- @type Player +-- @extends #NPC +-- @field #NPC baseType @{#NPC} + +--- +-- Whether the object is a player. +-- @function [parent=#Player] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#Armor} functions +-- @field [parent=#types] #Armor Armor + +--- +-- @type Armor +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is an Armor. +-- @function [parent=#Armor] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#Book} functions +-- @field [parent=#types] #Book Book + +--- +-- @type Book +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Book. +-- @function [parent=#Book] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- @{#Clothing} functions + + +-- @field [parent=#types] #Clothing Clothing + +--- +-- @type Clothing +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Clothing. +-- @function [parent=#Clothing] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#Ingredient} functions +-- @field [parent=#types] #Ingredient Ingredient + +--- +-- @type Ingredient +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is an Ingredient. +-- @function [parent=#Ingredient] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#Light} functions +-- @field [parent=#types] #Light Light + +--- +-- @type Light +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Light. +-- @function [parent=#Light] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- Functions for @{#Miscellaneous} objects +-- @field [parent=#types] #Miscellaneous Miscellaneous + +--- +-- @type Miscellaneous +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Miscellaneous. +-- @function [parent=#Miscellaneous] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#Potion} functions +-- @field [parent=#types] #Potion Potion + +--- +-- @type Potion +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Potion. +-- @function [parent=#Potion] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + + + +--- @{#Weapon} functions +-- @field [parent=#types] #Weapon Weapon + +--- +-- @type Weapon +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Weapon. +-- @function [parent=#Weapon] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- @{#Apparatus} functions +-- @field [parent=#types] #Apparatus Apparatus + +--- +-- @type Apparatus +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is an Apparatus. +-- @function [parent=#Apparatus] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- @{#Lockpick} functions +-- @field [parent=#types] #Lockpick Lockpick + +--- +-- @type Lockpick +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Lockpick. +-- @function [parent=#Lockpick] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- @{#Probe} functions +-- @field [parent=#types] #Probe Probe + +--- +-- @type Probe +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Probe. +-- @function [parent=#Probe] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- @{#Repair} functions +-- @field [parent=#types] #Repair Repair + +--- +-- @type Repair +-- @extends #Item +-- @field #Item baseType @{#Item} + +--- +-- Whether the object is a Repair. +-- @function [parent=#Repair] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- @{#Activator} functions +-- @field [parent=#types] #Activator Activator + +--- +-- @type Activator + +--- +-- Whether the object is an Activator. +-- @function [parent=#Activator] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- @{#Container} functions +-- @field [parent=#types] #Container Container + +--- +-- @type Container + +--- +-- Container content. +-- @function [parent=#Container] content +-- @param openmw.core#GameObject object +-- @return openmw.core#Inventory + +--- +-- Whether the object is a Container. +-- @function [parent=#Container] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- @{#Door} functions +-- @field [parent=#types] #Door Door + +--- +-- @type Door + +--- +-- Whether the object is a Door. +-- @function [parent=#Door] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +--- +-- Whether the door is a teleport. +-- @function [parent=#Door] isTeleport +-- @param openmw.core#GameObject object +-- @return #boolean + +--- +-- Destination (only if a teleport door). +-- @function [parent=#Door] destPosition +-- @param openmw.core#GameObject object +-- @return openmw.util#Vector3 + +--- +-- Destination rotation (only if a teleport door). +-- @function [parent=#Door] destRotation +-- @param openmw.core#GameObject object +-- @return openmw.util#Vector3 + +--- +-- Destination cell (only if a teleport door). +-- @function [parent=#Door] destCell +-- @param openmw.core#GameObject object +-- @return openmw.core#Cell + +--- Functions for @{#Static} objects +-- @field [parent=#types] #Static Static + +--- +-- @type Static + +--- +-- Whether the object is a Static. +-- @function [parent=#Static] objectIsInstance +-- @param openmw.core#GameObject object +-- @return #boolean + +return nil + From fa115418eb97568b118d0c0851d7b4e1bbfb44ee Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 5 Mar 2022 20:11:17 +0100 Subject: [PATCH 2200/2859] [Lua] Remove queries --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 50 ----- apps/openmw/mwlua/luabindings.hpp | 4 - apps/openmw/mwlua/luamanagerimp.cpp | 1 - apps/openmw/mwlua/nearbybindings.cpp | 19 -- apps/openmw/mwlua/objectbindings.cpp | 8 +- apps/openmw/mwlua/query.cpp | 191 ------------------ apps/openmw/mwlua/query.hpp | 39 ---- apps/openmw_test_suite/CMakeLists.txt | 1 - .../lua/test_querypackage.cpp | 29 --- components/CMakeLists.txt | 4 - components/queries/luabindings.cpp | 118 ----------- components/queries/luabindings.hpp | 9 - components/queries/query.cpp | 185 ----------------- components/queries/query.hpp | 99 --------- docs/source/reference/lua-scripting/api.rst | 3 - .../reference/lua-scripting/openmw_query.rst | 5 - .../reference/lua-scripting/overview.rst | 94 --------- files/lua_api/CMakeLists.txt | 1 - files/lua_api/openmw/core.lua | 7 - files/lua_api/openmw/nearby.lua | 6 - files/lua_api/openmw/query.lua | 116 ----------- files/lua_api/openmw/world.lua | 6 - 23 files changed, 2 insertions(+), 995 deletions(-) delete mode 100644 apps/openmw/mwlua/query.cpp delete mode 100644 apps/openmw/mwlua/query.hpp delete mode 100644 apps/openmw_test_suite/lua/test_querypackage.cpp delete mode 100644 components/queries/luabindings.cpp delete mode 100644 components/queries/luabindings.hpp delete mode 100644 components/queries/query.cpp delete mode 100644 components/queries/query.hpp delete mode 100644 docs/source/reference/lua-scripting/openmw_query.rst delete mode 100644 files/lua_api/openmw/query.lua diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c29b33b28c..9a83a5cdb7 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwlua - luamanagerimp actions object worldview userdataserializer eventqueue query + luamanagerimp actions object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings types/types types/door types/actor types/container diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 655c7f6989..a46435b2ca 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -2,7 +2,6 @@ #include #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" @@ -90,59 +89,10 @@ namespace MWLua return sol::nullopt; }; api["activeActors"] = GObjectList{worldView->getActorsInScene()}; - api["selectObjects"] = [context](const Queries::Query& query) - { - ObjectIdList list; - WorldView* worldView = context.mWorldView; - if (query.mQueryType == "activators") - list = worldView->getActivatorsInScene(); - else if (query.mQueryType == "actors") - list = worldView->getActorsInScene(); - else if (query.mQueryType == "containers") - list = worldView->getContainersInScene(); - else if (query.mQueryType == "doors") - list = worldView->getDoorsInScene(); - else if (query.mQueryType == "items") - list = worldView->getItemsInScene(); - return GObjectList{selectObjectsFromList(query, list, context)}; - // TODO: Use sqlite to search objects that are not in the scene - // return GObjectList{worldView->selectObjects(query, false)}; - }; // TODO: add world.placeNewObject(recordId, cell, pos, [rot]) return LuaUtil::makeReadOnly(api); } - sol::table initQueryPackage(const Context& context) - { - Queries::registerQueryBindings(context.mLua->sol()); - sol::table query(context.mLua->sol(), sol::create); - for (std::string_view t : ObjectQueryTypes::types) - query[t] = Queries::Query(std::string(t)); - for (const QueryFieldGroup& group : getBasicQueryFieldGroups()) - query[group.mName] = initFieldGroup(context, group); - return query; // makeReadOnly is applied by LuaState::addCommonPackage - } - - sol::table initFieldGroup(const Context& context, const QueryFieldGroup& group) - { - sol::table res(context.mLua->sol(), sol::create); - for (const Queries::Field* field : group.mFields) - { - sol::table subgroup = res; - if (field->path().empty()) - throw std::logic_error("Empty path in Queries::Field"); - for (size_t i = 0; i < field->path().size() - 1; ++i) - { - const std::string& name = field->path()[i]; - if (subgroup[name] == sol::nil) - subgroup[name] = LuaUtil::makeReadOnly(context.mLua->newTable()); - subgroup = LuaUtil::getMutableFromReadOnly(subgroup[name]); - } - subgroup[field->path().back()] = field; - } - return LuaUtil::makeReadOnly(res); - } - sol::table initGlobalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage) { sol::table res(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index f7a405b437..41bc8b29ae 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -9,7 +9,6 @@ #include "context.hpp" #include "eventqueue.hpp" #include "object.hpp" -#include "query.hpp" #include "worldview.hpp" namespace MWWorld @@ -22,9 +21,6 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); - sol::table initQueryPackage(const Context&); - - sol::table initFieldGroup(const Context&, const QueryFieldGroup&); sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 9c5c210e4d..df49862df6 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -82,7 +82,6 @@ namespace MWLua mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context)); mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol())); mLua.addCommonPackage("openmw.core", initCorePackage(context)); - mLua.addCommonPackage("openmw.query", initQueryPackage(context)); mLua.addCommonPackage("openmw.types", initTypesPackage(context)); mGlobalScripts.addPackage("openmw.world", initWorldPackage(context)); mGlobalScripts.addPackage("openmw.settings", initGlobalSettingsPackage(context)); diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 624cf69da6..05bc52c5cc 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -1,7 +1,6 @@ #include "luabindings.hpp" #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -98,24 +97,6 @@ namespace MWLua api["containers"] = LObjectList{worldView->getContainersInScene()}; api["doors"] = LObjectList{worldView->getDoorsInScene()}; api["items"] = LObjectList{worldView->getItemsInScene()}; - api["selectObjects"] = [context](const Queries::Query& query) - { - ObjectIdList list; - WorldView* worldView = context.mWorldView; - if (query.mQueryType == "activators") - list = worldView->getActivatorsInScene(); - else if (query.mQueryType == "actors") - list = worldView->getActorsInScene(); - else if (query.mQueryType == "containers") - list = worldView->getContainersInScene(); - else if (query.mQueryType == "doors") - list = worldView->getDoorsInScene(); - else if (query.mQueryType == "items") - list = worldView->getItemsInScene(); - return LObjectList{selectObjectsFromList(query, list, context)}; - // TODO: Maybe use sqlite - // return LObjectList{worldView->selectObjects(query, true)}; - }; return LuaUtil::makeReadOnly(api); } } diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 6bcf355fde..08435583df 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -1,10 +1,8 @@ #include "luabindings.hpp" #include -#include - -#include "../mwclass/door.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -77,10 +75,6 @@ namespace MWLua }); listT[sol::meta_function::ipairs] = [iter](const LObjectList& list) { return std::make_tuple(iter, list, 0); }; } - listT["select"] = [context](const ListT& list, const Queries::Query& query) - { - return ListT{selectObjectsFromList(query, list.mIds, context)}; - }; } template diff --git a/apps/openmw/mwlua/query.cpp b/apps/openmw/mwlua/query.cpp deleted file mode 100644 index c357e56884..0000000000 --- a/apps/openmw/mwlua/query.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "query.hpp" - -#include - -#include - -#include "../mwclass/container.hpp" -#include "../mwworld/cellstore.hpp" - -#include "worldview.hpp" - -namespace MWLua -{ - - static std::vector initBasicFieldGroups() - { - auto createGroup = [](std::string name, const auto& arr) -> QueryFieldGroup - { - std::vector fieldPtrs; - fieldPtrs.reserve(arr.size()); - for (const Queries::Field& field : arr) - fieldPtrs.push_back(&field); - return {std::move(name), std::move(fieldPtrs)}; - }; - static std::array objectFields = { - Queries::Field({"type"}, typeid(std::string)), - Queries::Field({"recordId"}, typeid(std::string)), - Queries::Field({"cell", "name"}, typeid(std::string)), - Queries::Field({"cell", "region"}, typeid(std::string)), - Queries::Field({"cell", "isExterior"}, typeid(bool)), - Queries::Field({"count"}, typeid(int32_t)), - }; - static std::array doorFields = { - Queries::Field({"isTeleport"}, typeid(bool)), - Queries::Field({"destCell", "name"}, typeid(std::string)), - Queries::Field({"destCell", "region"}, typeid(std::string)), - Queries::Field({"destCell", "isExterior"}, typeid(bool)), - }; - return std::vector{ - createGroup("OBJECT", objectFields), - createGroup("DOOR", doorFields), - }; - } - - const std::vector& getBasicQueryFieldGroups() - { - static std::vector fieldGroups = initBasicFieldGroups(); - return fieldGroups; - } - - bool checkQueryConditions(const Queries::Query& query, const ObjectId& id, const Context& context) - { - auto compareFn = [](auto&& a, auto&& b, Queries::Condition::Type t) - { - switch (t) - { - case Queries::Condition::EQUAL: return a == b; - case Queries::Condition::NOT_EQUAL: return a != b; - case Queries::Condition::GREATER: return a > b; - case Queries::Condition::GREATER_OR_EQUAL: return a >= b; - case Queries::Condition::LESSER: return a < b; - case Queries::Condition::LESSER_OR_EQUAL: return a <= b; - default: - throw std::runtime_error("Unsupported condition type"); - } - }; - sol::object obj; - MWWorld::Ptr ptr; - if (context.mIsGlobal) - { - GObject g(id, context.mWorldView->getObjectRegistry()); - if (!g.isValid()) - return false; - ptr = g.ptr(); - obj = sol::make_object(context.mLua->sol(), g); - } - else - { - LObject l(id, context.mWorldView->getObjectRegistry()); - if (!l.isValid()) - return false; - ptr = l.ptr(); - obj = sol::make_object(context.mLua->sol(), l); - } - if (ptr.getRefData().getCount() == 0) - return false; - - // It is important to exclude all markers before checking what class it is. - // For example "prisonmarker" has class "Door" despite that it is only an invisible marker. - if (isMarker(ptr)) - return false; - - const MWWorld::Class& cls = ptr.getClass(); - if (cls.isActivator() != (query.mQueryType == ObjectQueryTypes::ACTIVATORS)) - return false; - if (cls.isActor() != (query.mQueryType == ObjectQueryTypes::ACTORS)) - return false; - if (cls.isDoor() != (query.mQueryType == ObjectQueryTypes::DOORS)) - return false; - if ((typeid(cls) == typeid(MWClass::Container)) != (query.mQueryType == ObjectQueryTypes::CONTAINERS)) - return false; - - std::vector condStack; - for (const Queries::Operation& op : query.mFilter.mOperations) - { - switch(op.mType) - { - case Queries::Operation::PUSH: - { - const Queries::Condition& cond = query.mFilter.mConditions[op.mConditionIndex]; - sol::object fieldObj = obj; - for (const std::string& field : cond.mField->path()) - fieldObj = LuaUtil::getFieldOrNil(fieldObj, field); - bool c; - if (fieldObj == sol::nil) - c = false; - else if (cond.mField->type() == typeid(std::string)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(float)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(double)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(bool)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(int32_t)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else if (cond.mField->type() == typeid(int64_t)) - c = compareFn(fieldObj.as(), std::get(cond.mValue), cond.mType); - else - throw std::runtime_error("Unknown field type"); - condStack.push_back(c); - break; - } - case Queries::Operation::NOT: - condStack.back() = !condStack.back(); - break; - case Queries::Operation::AND: - { - bool v = condStack.back(); - condStack.pop_back(); - condStack.back() = condStack.back() && v; - break; - } - case Queries::Operation::OR: - { - bool v = condStack.back(); - condStack.pop_back(); - condStack.back() = condStack.back() || v; - break; - } - } - } - return condStack.empty() || condStack.back() != 0; - } - - ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context& context) - { - if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0) - throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported"); - - ObjectIdList res = std::make_shared>(); - for (const ObjectId& id : *list) - { - if (static_cast(res->size()) == query.mLimit) - break; - if (checkQueryConditions(query, id, context)) - res->push_back(id); - } - return res; - } - - ObjectIdList selectObjectsFromCellStore(const Queries::Query& query, MWWorld::CellStore* store, const Context& context) - { - if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0) - throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported"); - - ObjectIdList res = std::make_shared>(); - auto visitor = [&](const MWWorld::Ptr& ptr) - { - if (static_cast(res->size()) == query.mLimit) - return false; - context.mWorldView->getObjectRegistry()->registerPtr(ptr); - if (checkQueryConditions(query, getId(ptr), context)) - res->push_back(getId(ptr)); - return static_cast(res->size()) != query.mLimit; - }; - store->forEach(std::move(visitor)); // TODO: maybe use store->forEachType depending on query.mType - return res; - } - -} diff --git a/apps/openmw/mwlua/query.hpp b/apps/openmw/mwlua/query.hpp deleted file mode 100644 index 65bf0c5105..0000000000 --- a/apps/openmw/mwlua/query.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef MWLUA_QUERY_H -#define MWLUA_QUERY_H - -#include - -#include - -#include "context.hpp" -#include "object.hpp" - -namespace MWLua -{ - - struct ObjectQueryTypes - { - static constexpr std::string_view ACTIVATORS = "activators"; - static constexpr std::string_view ACTORS = "actors"; - static constexpr std::string_view CONTAINERS = "containers"; - static constexpr std::string_view DOORS = "doors"; - static constexpr std::string_view ITEMS = "items"; - - static constexpr std::string_view types[] = {ACTIVATORS, ACTORS, CONTAINERS, DOORS, ITEMS}; - }; - - struct QueryFieldGroup - { - std::string mName; - std::vector mFields; - }; - const std::vector& getBasicQueryFieldGroups(); - - // TODO: Implement custom fields. QueryFieldGroup registerCustomFields(...); - - ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context&); - ObjectIdList selectObjectsFromCellStore(const Queries::Query& query, MWWorld::CellStore* store, const Context&); - -} - -#endif // MWLUA_QUERY_H diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 73e7af1d9e..767eebe6c8 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -21,7 +21,6 @@ if (GTEST_FOUND AND GMOCK_FOUND) lua/test_scriptscontainer.cpp lua/test_utilpackage.cpp lua/test_serialization.cpp - lua/test_querypackage.cpp lua/test_configuration.cpp lua/test_i18n.cpp lua/test_storage.cpp diff --git a/apps/openmw_test_suite/lua/test_querypackage.cpp b/apps/openmw_test_suite/lua/test_querypackage.cpp deleted file mode 100644 index aeaf992db0..0000000000 --- a/apps/openmw_test_suite/lua/test_querypackage.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "gmock/gmock.h" -#include - -#include - -namespace -{ - using namespace testing; - - TEST(LuaQueryPackageTest, basic) - { - sol::state lua; - lua.open_libraries(sol::lib::base, sol::lib::string); - Queries::registerQueryBindings(lua); - lua["query"] = Queries::Query("test"); - lua["fieldX"] = Queries::Field({ "x" }, typeid(std::string)); - lua["fieldY"] = Queries::Field({ "y" }, typeid(int)); - lua.safe_script("t = query:where(fieldX:eq('abc') + fieldX:like('%abcd%'))"); - lua.safe_script("t = t:where(fieldY:gt(5))"); - lua.safe_script("t = t:orderBy(fieldX)"); - lua.safe_script("t = t:orderByDesc(fieldY)"); - lua.safe_script("t = t:groupBy(fieldY)"); - lua.safe_script("t = t:limit(10):offset(5)"); - EXPECT_EQ( - lua.safe_script("return tostring(t)").get(), - "SELECT test WHERE ((x == \"abc\") OR (x LIKE \"%abcd%\")) AND (y > 5) ORDER BY x, y DESC GROUP BY y LIMIT 10 OFFSET 5"); - } -} - diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e183d54a95..efc80081e3 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -253,10 +253,6 @@ add_component_dir (fallback fallback validate ) -add_component_dir (queries - query luabindings - ) - add_component_dir (lua_ui registerscriptsettings scriptsettings properties widget element util layers content alignment resources diff --git a/components/queries/luabindings.cpp b/components/queries/luabindings.cpp deleted file mode 100644 index c830a140f7..0000000000 --- a/components/queries/luabindings.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "luabindings.hpp" - -namespace sol -{ - template <> - struct is_automagical : std::false_type {}; - - template <> - struct is_automagical : std::false_type {}; - - template <> - struct is_automagical : std::false_type {}; -} - -namespace Queries -{ - template - struct CondBuilder - { - Filter operator()(const Field& field, const sol::object& o) - { - FieldValue value; - if (field.type() == typeid(bool) && o.is()) - value = o.as(); - else if (field.type() == typeid(int32_t) && o.is()) - value = o.as(); - else if (field.type() == typeid(int64_t) && o.is()) - value = o.as(); - else if (field.type() == typeid(float) && o.is()) - value = o.as(); - else if (field.type() == typeid(double) && o.is()) - value = o.as(); - else if (field.type() == typeid(std::string) && o.is()) - value = o.as(); - else - throw std::logic_error("Invalid value for field " + field.toString()); - Filter filter; - filter.add({&field, type, value}); - return filter; - } - }; - - void registerQueryBindings(sol::state& lua) - { - sol::usertype field = lua.new_usertype("QueryField"); - sol::usertype filter = lua.new_usertype("QueryFilter"); - sol::usertype query = lua.new_usertype("Query"); - - field[sol::meta_function::to_string] = [](const Field& f) { return f.toString(); }; - field["eq"] = CondBuilder(); - field["neq"] = CondBuilder(); - field["lt"] = CondBuilder(); - field["lte"] = CondBuilder(); - field["gt"] = CondBuilder(); - field["gte"] = CondBuilder(); - field["like"] = CondBuilder(); - - filter[sol::meta_function::to_string] = [](const Filter& filter) { return filter.toString(); }; - filter[sol::meta_function::multiplication] = [](const Filter& a, const Filter& b) - { - Filter res = a; - res.add(b, Operation::AND); - return res; - }; - filter[sol::meta_function::addition] = [](const Filter& a, const Filter& b) - { - Filter res = a; - res.add(b, Operation::OR); - return res; - }; - filter[sol::meta_function::unary_minus] = [](const Filter& a) - { - Filter res = a; - if (!a.mConditions.empty()) - res.mOperations.push_back({Operation::NOT, 0}); - return res; - }; - - query[sol::meta_function::to_string] = [](const Query& q) { return q.toString(); }; - query["where"] = [](const Query& q, const Filter& filter) - { - Query res = q; - res.mFilter.add(filter, Operation::AND); - return res; - }; - query["orderBy"] = [](const Query& q, const Field& field) - { - Query res = q; - res.mOrderBy.push_back({&field, false}); - return res; - }; - query["orderByDesc"] = [](const Query& q, const Field& field) - { - Query res = q; - res.mOrderBy.push_back({&field, true}); - return res; - }; - query["groupBy"] = [](const Query& q, const Field& field) - { - Query res = q; - res.mGroupBy.push_back(&field); - return res; - }; - query["offset"] = [](const Query& q, int64_t offset) - { - Query res = q; - res.mOffset = offset; - return res; - }; - query["limit"] = [](const Query& q, int64_t limit) - { - Query res = q; - res.mLimit = limit; - return res; - }; - } -} - diff --git a/components/queries/luabindings.hpp b/components/queries/luabindings.hpp deleted file mode 100644 index 16d1a822be..0000000000 --- a/components/queries/luabindings.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#include // missing from sol/sol.hpp -#include - -#include "query.hpp" - -namespace Queries -{ - void registerQueryBindings(sol::state& lua); -} diff --git a/components/queries/query.cpp b/components/queries/query.cpp deleted file mode 100644 index 3c7f1517ee..0000000000 --- a/components/queries/query.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#include "query.hpp" - -#include -#include - -namespace Queries -{ - Field::Field(std::vector path, std::type_index type) - : mPath(std::move(path)) - , mType(type) {} - - std::string Field::toString() const - { - std::string result; - for (const std::string& segment : mPath) - { - if (!result.empty()) - result += "."; - result += segment; - } - return result; - } - - std::string toString(const FieldValue& value) - { - return std::visit([](auto&& arg) -> std::string - { - using T = std::decay_t; - if constexpr (std::is_same_v) - { - std::ostringstream oss; - oss << std::quoted(arg); - return oss.str(); - } - else if constexpr (std::is_same_v) - return arg ? "true" : "false"; - else - return std::to_string(arg); - }, value); - } - - std::string Condition::toString() const - { - std::string res; - res += mField->toString(); - switch (mType) - { - case Condition::EQUAL: res += " == "; break; - case Condition::NOT_EQUAL: res += " != "; break; - case Condition::LESSER: res += " < "; break; - case Condition::LESSER_OR_EQUAL: res += " <= "; break; - case Condition::GREATER: res += " > "; break; - case Condition::GREATER_OR_EQUAL: res += " >= "; break; - case Condition::LIKE: res += " LIKE "; break; - } - res += Queries::toString(mValue); - return res; - } - - void Filter::add(const Condition& c, Operation::Type op) - { - mOperations.push_back({Operation::PUSH, mConditions.size()}); - mConditions.push_back(c); - if (mConditions.size() > 1) - mOperations.push_back({op, 0}); - } - - void Filter::add(const Filter& f, Operation::Type op) - { - size_t conditionOffset = mConditions.size(); - size_t operationsBefore = mOperations.size(); - mConditions.insert(mConditions.end(), f.mConditions.begin(), f.mConditions.end()); - mOperations.insert(mOperations.end(), f.mOperations.begin(), f.mOperations.end()); - for (size_t i = operationsBefore; i < mOperations.size(); ++i) - mOperations[i].mConditionIndex += conditionOffset; - if (conditionOffset > 0 && !f.mConditions.empty()) - mOperations.push_back({op, 0}); - } - - std::string Filter::toString() const - { - if(mOperations.empty()) - return ""; - std::vector stack; - auto pop = [&stack](){ auto v = stack.back(); stack.pop_back(); return v; }; - auto push = [&stack](const std::string& s) { stack.push_back(s); }; - for (const Operation& op : mOperations) - { - if(op.mType == Operation::PUSH) - push(mConditions[op.mConditionIndex].toString()); - else if(op.mType == Operation::AND) - { - auto rhs = pop(); - auto lhs = pop(); - std::string res; - res += "("; - res += lhs; - res += ") AND ("; - res += rhs; - res += ")"; - push(res); - } - else if (op.mType == Operation::OR) - { - auto rhs = pop(); - auto lhs = pop(); - std::string res; - res += "("; - res += lhs; - res += ") OR ("; - res += rhs; - res += ")"; - push(res); - } - else if (op.mType == Operation::NOT) - { - std::string res; - res += "NOT ("; - res += pop(); - res += ")"; - push(res); - } - else - throw std::logic_error("Unknown operation type!"); - } - return pop(); - } - - std::string Query::toString() const - { - std::string res; - res += "SELECT "; - res += mQueryType; - - std::string filter = mFilter.toString(); - if(!filter.empty()) - { - res += " WHERE "; - res += filter; - } - - std::string order; - for(const OrderBy& ord : mOrderBy) - { - if(!order.empty()) - order += ", "; - order += ord.mField->toString(); - if(ord.mDescending) - order += " DESC"; - } - if (!order.empty()) - { - res += " ORDER BY "; - res += order; - } - - std::string group; - for (const Field* f: mGroupBy) - { - if (!group.empty()) - group += " ,"; - group += f->toString(); - } - if (!group.empty()) - { - res += " GROUP BY "; - res += group; - } - - if (mLimit != sNoLimit) - { - res += " LIMIT "; - res += std::to_string(mLimit); - } - - if (mOffset != 0) - { - res += " OFFSET "; - res += std::to_string(mOffset); - } - - return res; - } -} - diff --git a/components/queries/query.hpp b/components/queries/query.hpp deleted file mode 100644 index 45144fed62..0000000000 --- a/components/queries/query.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef COMPONENTS_QUERIES_QUERY -#define COMPONENTS_QUERIES_QUERY - -#include -#include -#include -#include -#include - -namespace Queries -{ - class Field - { - public: - Field(std::vector path, std::type_index type); - - const std::vector& path() const { return mPath; } - const std::type_index type() const { return mType; } - - std::string toString() const; - - private: - std::vector mPath; - std::type_index mType; - }; - - struct OrderBy - { - const Field* mField; - bool mDescending; - }; - - using FieldValue = std::variant; - std::string toString(const FieldValue& value); - - struct Condition - { - enum Type - { - EQUAL = 0, - NOT_EQUAL = 1, - GREATER = 2, - GREATER_OR_EQUAL = 3, - LESSER = 4, - LESSER_OR_EQUAL = 5, - LIKE = 6, - }; - - std::string toString() const; - - const Field* mField; - Type mType; - FieldValue mValue; - }; - - struct Operation - { - enum Type - { - PUSH = 0, // push condition on stack - NOT = 1, // invert top condition on stack - AND = 2, // logic AND for two top conditions - OR = 3, // logic OR for two top conditions - }; - - Type mType; - size_t mConditionIndex; // used only if mType == PUSH - }; - - struct Filter - { - std::string toString() const; - - // combines with given condition or filter using operation `AND` or `OR`. - void add(const Condition& c, Operation::Type op = Operation::AND); - void add(const Filter& f, Operation::Type op = Operation::AND); - - std::vector mConditions; - std::vector mOperations; // operations on conditions in reverse polish notation - }; - - struct Query - { - static constexpr int64_t sNoLimit = -1; - - Query(std::string type) : mQueryType(std::move(type)) {} - std::string toString() const; - - std::string mQueryType; - Filter mFilter; - std::vector mOrderBy; - std::vector mGroupBy; - int64_t mOffset = 0; - int64_t mLimit = sNoLimit; - }; -} - -#endif // !COMPONENTS_QUERIES_QUERY - diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 4518028073..cf071534d9 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -14,7 +14,6 @@ Lua API reference openmw_core openmw_types openmw_async - openmw_query openmw_world openmw_self openmw_nearby @@ -61,8 +60,6 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.query ` | everywhere | | Tools for constructing queries: base queries and fields. | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |: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/docs/source/reference/lua-scripting/openmw_query.rst b/docs/source/reference/lua-scripting/openmw_query.rst deleted file mode 100644 index ea0932e8f0..0000000000 --- a/docs/source/reference/lua-scripting/openmw_query.rst +++ /dev/null @@ -1,5 +0,0 @@ -Package openmw.query -==================== - -.. raw:: html - :file: generated_html/openmw_query.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index a819235e2a..91c0a071e0 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -357,8 +357,6 @@ Player scripts are local scripts that are attached to a player. +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers (implemented) and coroutine utils (not implemented) | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.query ` | everywhere | | Tools for constructing queries: base queries and fields. | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |: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. | @@ -632,98 +630,6 @@ Also in `openmw_aux`_ is the helper function ``runRepeatedly``, it is implemente }) -Queries -======= - -`openmw.query` contains base queries of each type (e.g. `query.doors`, `query.containers`...), which return all of the objects of given type in no particular order. You can then modify that query to filter the results, sort them, group them, etc. Queries are immutable, so any operations on them return a new copy, leaving the original unchanged. - -`openmw.world.selectObjects` and `openmw.nearby.selectObjects` both accept a query and return objects that match it. However, `nearby.selectObjects` is only available in local scripts, and returns only objects from currently active cells, while `world.selectObjects` is only available in global scripts, and returns objects regardless of them being in active cells. -**TODO:** describe how to filter out inactive objects from world queries - -An example of an object query: - -.. code-block:: Lua - - local query = require('openmw.query') - local nearby = require('openmw.nearby') - local ui = require('openmw.ui') - - local function selectDoors(namePattern) - local query = query.doors:where(query.DOOR.destCell.name:like(namePattern)) - return nearby.selectObjects(query) - end - - local function showGuildDoors() - ui.showMessage('Here are all the entrances to guilds!') - for _, door in selectDoors("%Guild%"):ipairs() do - local pos = door.position - local message = string.format("%.0f;%.0f;%.0f", pos.x, pos.y, pos.z) - ui.showMessage(message) - end - end - - return { - engineHandlers = { - onKeyPress = function(key) - if key.symbol == 'e' then - showGuildDoors() - end - end - } - } - -.. warning:: - The example above uses operation `like` that is not implemented yet. - -**TODO:** add non-object queries, explain how relations work, and define what a field is - -Queries are constructed through the following method calls: (if you've used SQL before, you will find them familiar) - -- `:where(filter)` - filters the results to match the combination of conditions passed as the argument -- `:orderBy(field)` and `:orderByDesc(field)` sort the result by the `field` argument. Sorts in descending order in case of `:orderByDesc`. Multiple calls can be chained, with the first call having priority. (i. e. if the first field is equal, objects are sorted by the second one...) **(not implemented yet)** -- `:groupBy(field)` returns only one result for each value of the `field` argument. The choice of the result is arbitrary. Useful for counting only unique objects, or checking if certain objects exist. **(not implemented yet)** -- `:limit(number)` will only return `number` of results (or fewer) -- `:offset(number)` skips the first `number` results. Particularly useful in combination with `:limit` **(not implemented yet)** - -Filters consist of conditions, which are combined with "and" (operator `*`), "or" (operator `+`), "not" (operator `-`) and braces `()`. - -To make a condition, take a field from the `openmw.query` package and call any of the following methods: - -- `:eq` equal to -- `:neq` not equal to -- `:gt` greater than -- `:gte` greater or equal to -- `:lt` less than -- `:lte` less or equal to -- `:like` matches a pattern. Only applicable to text (strings) **(not implemented yet)** - -**TODO:** describe the pattern format - -All the condition methods are type sensitive, and will throw an error if you pass a value of the wrong type into them. - -A few examples of filters: - -.. warning:: - `openmw.query.ACTOR` is not implemented yet - -.. code-block:: Lua - - local query = require('openmw.query') - local ACTOR = query.ACTOR - - local strong_guys_from_capital = (ACTOR.stats.level:gt(10) + ACTOR.stats.strength:gt(70)) - * ACTOR.cell.name:eq("Default city") - - -- could also write like this: - local strong_guys = ACTOR.stats.level:gt(10) + ACTOR.stats.strength:gt(70) - local guys_from_capital = ACTOR.cell.name:eq("Default city") - local strong_guys_from_capital_2 = strong_guys * guys_from_capital - - local DOOR = query.DOOR - - local interestingDoors = -DOOR.name:eq("") * DOOR.isTeleport:eq(true) * Door.destCell.isExterior:eq(false) - - Using IDE for Lua scripting =========================== diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 2988f38552..88efc8195c 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -8,7 +8,6 @@ set(LUA_API_FILES openmw/async.lua openmw/core.lua openmw/nearby.lua - openmw/query.lua openmw/self.lua openmw/ui.lua openmw/util.lua diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index afb0d2f5bb..43b2d29a70 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -159,13 +159,6 @@ -- @type ObjectList -- @list <#GameObject> ---- --- Filter list with a Query. --- @function [parent=#ObjectList] select --- @param self --- @param openmw.query#Query query --- @return #ObjectList - --- -- A cell of the game world. diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index c3403ded0c..3ba3d7e6fb 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -26,12 +26,6 @@ -- Everything that can be picked up in the nearby. -- @field [parent=#nearby] openmw.core#ObjectList items ---- --- Evaluates a Query. --- @function [parent=#nearby] selectObjects --- @param openmw.query#Query query --- @return openmw.core#ObjectList - --- -- @type COLLISION_TYPE -- @field [parent=#COLLISION_TYPE] #number World diff --git a/files/lua_api/openmw/query.lua b/files/lua_api/openmw/query.lua deleted file mode 100644 index b5cea061ff..0000000000 --- a/files/lua_api/openmw/query.lua +++ /dev/null @@ -1,116 +0,0 @@ ---- --- `openmw.query` constructs queries that can be used in `world.selectObjects` and `nearby.selectObjects`. --- @module query --- @usage local query = require('openmw.query') - - - ---- --- Query. A Query itself can no return objects. It only holds search conditions. --- @type Query - ---- --- Add condition. --- @function [parent=#Query] where --- @param self --- @param condition --- @return #Query - ---- --- Limit result size. --- @function [parent=#Query] limit --- @param self --- @param #number maxCount --- @return #Query - - ---- --- A field that can be used in a condition --- @type Field - ---- --- Equal --- @function [parent=#Field] eq --- @param self --- @param value - ---- --- Not equal --- @function [parent=#Field] neq --- @param self --- @param value - ---- --- Lesser --- @function [parent=#Field] lt --- @param self --- @param value - ---- --- Lesser or equal --- @function [parent=#Field] lte --- @param self --- @param value - ---- --- Greater --- @function [parent=#Field] gt --- @param self --- @param value - ---- --- Greater or equal --- @function [parent=#Field] gte --- @param self --- @param value - - ---- --- @type OBJECT --- @field [parent=#OBJECT] #Field type --- @field [parent=#OBJECT] #Field recordId --- @field [parent=#OBJECT] #Field count --- @field [parent=#OBJECT] #CellFields cell - ---- --- Fields that can be used with any object. --- @field [parent=#query] #OBJECT OBJECT - ---- --- @type DOOR --- @field [parent=#DOOR] #Field isTeleport --- @field [parent=#DOOR] #CellFields destCell - ---- --- Fields that can be used only when search for doors. --- @field [parent=#query] #DOOR DOOR - ---- --- @type CellFields --- @field [parent=#CellFields] #Field name --- @field [parent=#CellFields] #Field region --- @field [parent=#CellFields] #Field isExterior - - ---- --- Base Query to select activators. --- @field [parent=#query] #Query activators - ---- --- Base Query to select actors. --- @field [parent=#query] #Query actors - ---- --- Base Query to select containers. --- @field [parent=#query] #Query containers - ---- --- Base Query to select doors. --- @field [parent=#query] #Query doors - ---- --- Base Query to select items. --- @field [parent=#query] #Query items - -return nil - diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 127c41c064..1f5d9a539b 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -10,12 +10,6 @@ -- List of currently active actors. -- @field [parent=#world] openmw.core#ObjectList activeActors ---- --- Evaluates a Query. --- @function [parent=#world] selectObjects --- @param openmw.query#Query query --- @return openmw.core#ObjectList - --- -- Loads a named cell -- @function [parent=#world] getCellByName From 484a7c7b7ebcaefba3003a1bb50029232cd3a498 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 16 Mar 2022 18:09:21 +0100 Subject: [PATCH 2201/2859] Base dialogue order solely on mPrev --- CHANGELOG.md | 1 + components/esm3/loaddial.cpp | 43 +++++++++--------------------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 085eaedaf9..b5bd4d8923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -279,6 +279,7 @@ Bug #6294: Game crashes with empty pathgrid Bug #6606: Quests with multiple IDs cannot always be restarted Bug #6655: Constant effect absorb attribute causes the game to break + Bug #6670: Dialogue order is incorrect Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/components/esm3/loaddial.cpp b/components/esm3/loaddial.cpp index 0c985def5b..a4b21cd1b3 100644 --- a/components/esm3/loaddial.cpp +++ b/components/esm3/loaddial.cpp @@ -86,49 +86,28 @@ namespace ESM return; } - InfoContainer::iterator it = mInfo.end(); - - LookupMap::iterator lookup; - lookup = mLookup.find(info.mId); + LookupMap::iterator lookup = mLookup.find(info.mId); if (lookup != mLookup.end()) { - it = lookup->second.first; + auto it = lookup->second.first; // Since the new version of this record may have changed the next/prev linked list connection, we need to re-insert the record mInfo.erase(it); mLookup.erase(lookup); } - if (info.mNext.empty()) - { - mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); - return; - } - if (info.mPrev.empty()) - { - mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted); - return; - } - - lookup = mLookup.find(info.mPrev); - if (lookup != mLookup.end()) - { - it = lookup->second.first; - - mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted); - return; - } - - lookup = mLookup.find(info.mNext); - if (lookup != mLookup.end()) + if (!info.mPrev.empty()) { - it = lookup->second.first; + lookup = mLookup.find(info.mPrev); + if (lookup != mLookup.end()) + { + auto it = lookup->second.first; - mLookup[info.mId] = std::make_pair(mInfo.insert(it, info), isDeleted); - return; + mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted); + return; + } } - - Log(Debug::Warning) << "Warning: Failed to insert info " << info.mId; + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted); } void Dialogue::clearDeletedInfos() From 12f6530ab320d02ac3dd6a49b7d571d415f31f2e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 16 Mar 2022 19:24:36 +0100 Subject: [PATCH 2202/2859] Relocate changelog entries --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5bd4d8923..f6eb3f5738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,9 @@ Bug #6544: Far from world origin objects jitter when camera is still Bug #6559: Weapon condition inconsistency between melee and ranged critical / sneak / KO attacks Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere + Bug #6606: Quests with multiple IDs cannot always be restarted + Bug #6655: Constant effect absorb attribute causes the game to break + Bug #6670: Dialogue order is incorrect Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" @@ -277,9 +280,6 @@ Bug #6142: Groundcover plugins change cells flags Bug #6276: Deleted groundcover instances are not deleted in game Bug #6294: Game crashes with empty pathgrid - Bug #6606: Quests with multiple IDs cannot always be restarted - Bug #6655: Constant effect absorb attribute causes the game to break - Bug #6670: Dialogue order is incorrect Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu From 5b9dd10cbe2ec0c82b9e532b2056992b25fe9bba Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 10 Mar 2022 18:34:35 +0100 Subject: [PATCH 2203/2859] Limit max navmeshdb file size Use "pragma max_page_count" to define max allowed file size in combination with "pragma page_size" based on a new setting "max navmeshdb file size". * Stop navmeshtool on the first db error. * Disable writes to db in the engine on first "database or disk is full" SQLite3 error. There is no special error code for this error. * Change default "write to navmeshdb" to true. * Use time intervals for transaction duration instead of number of changes. --- apps/navmeshtool/main.cpp | 3 +- apps/navmeshtool/navmesh.cpp | 54 ++++++++----- .../detournavigator/asyncnavmeshupdater.cpp | 49 ++++++++++-- .../detournavigator/navigator.cpp | 5 +- .../detournavigator/navmeshdb.cpp | 14 +++- .../detournavigator/asyncnavmeshupdater.cpp | 80 ++++++++++++++----- .../detournavigator/asyncnavmeshupdater.hpp | 8 +- .../detournavigator/generatenavmeshtile.cpp | 1 + .../detournavigator/generatenavmeshtile.hpp | 2 + components/detournavigator/navigator.cpp | 2 +- components/detournavigator/navmeshdb.cpp | 30 ++++++- components/detournavigator/navmeshdb.hpp | 4 +- components/detournavigator/settings.cpp | 1 + components/detournavigator/settings.hpp | 1 + components/sqlite3/db.cpp | 2 +- components/sqlite3/transaction.cpp | 28 +++++-- components/sqlite3/transaction.hpp | 10 ++- .../reference/modding/settings/navigator.rst | 11 ++- files/settings-default.cfg | 5 +- 19 files changed, 242 insertions(+), 68 deletions(-) diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 894ec6b3b1..af411f03dd 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -156,8 +156,9 @@ namespace NavMeshTool settings.load(config); const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game"); + const std::uint64_t maxDbFileSize = static_cast(Settings::Manager::getInt64("max navmeshdb file size", "Navigator")); - DetourNavigator::NavMeshDb db((config.getUserDataPath() / "navmesh.db").string()); + DetourNavigator::NavMeshDb db((config.getUserDataPath() / "navmesh.db").string(), maxDbFileSize); std::vector readers(contentFiles.size()); EsmLoader::Query query; diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 8b85029949..3acddb821d 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -67,7 +67,7 @@ namespace NavMeshTool explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles) : mDb(std::move(db)) , mRemoveUnusedTiles(removeUnusedTiles) - , mTransaction(mDb.startTransaction()) + , mTransaction(mDb.startTransaction(Sqlite3::TransactionMode::Immediate)) , mNextTileId(mDb.getMaxTileId() + 1) , mNextShapeId(mDb.getMaxShapeId() + 1) {} @@ -128,14 +128,11 @@ namespace NavMeshTool void insert(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t version, const std::vector& input, PreparedNavMeshData& data) override { - if (mRemoveUnusedTiles) - { - std::lock_guard lock(mMutex); - mDeleted += static_cast(mDb.deleteTilesAt(worldspace, tilePosition)); - } - data.mUserId = static_cast(mNextTileId); { std::lock_guard lock(mMutex); + if (mRemoveUnusedTiles) + mDeleted += static_cast(mDb.deleteTilesAt(worldspace, tilePosition)); + data.mUserId = static_cast(mNextTileId); mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data)); ++mNextTileId; } @@ -157,25 +154,44 @@ namespace NavMeshTool report(); } - void wait() + void cancel() override + { + std::unique_lock lock(mMutex); + mCancelled = true; + mHasTile.notify_one(); + } + + bool wait() { - constexpr std::size_t tilesPerTransaction = 3000; + constexpr std::chrono::seconds transactionInterval(1); std::unique_lock lock(mMutex); - while (mProvided < mExpected) + auto start = std::chrono::steady_clock::now(); + while (mProvided < mExpected && !mCancelled) { mHasTile.wait(lock); - if (mProvided % tilesPerTransaction == 0) + const auto now = std::chrono::steady_clock::now(); + if (now - start > transactionInterval) { mTransaction.commit(); - mTransaction = mDb.startTransaction(); + mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate); + start = now; } } logGeneratedTiles(mProvided, mExpected); + return !mCancelled; } - void commit() { mTransaction.commit(); } + void commit() + { + const std::lock_guard lock(mMutex); + mTransaction.commit(); + } - void vacuum() { mDb.vacuum(); } + void vacuum() + { + const std::lock_guard lock(mMutex); + mDb.vacuum(); + } void removeTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range) { @@ -183,7 +199,7 @@ namespace NavMeshTool mTransaction.commit(); Log(Debug::Info) << "Removing tiles outside processed range for worldspace \"" << worldspace << "\"..."; mDeleted += static_cast(mDb.deleteTilesOutsideRange(worldspace, range)); - mTransaction = mDb.startTransaction(); + mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate); } private: @@ -191,6 +207,7 @@ namespace NavMeshTool std::atomic_size_t mInserted {0}; std::atomic_size_t mUpdated {0}; std::size_t mDeleted = 0; + bool mCancelled = false; mutable std::mutex mMutex; NavMeshDb mDb; const bool mRemoveUnusedTiles; @@ -211,7 +228,8 @@ namespace NavMeshTool } void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings, - std::size_t threadsNumber, bool removeUnusedTiles, WorldspaceData& data, NavMeshDb&& db) + std::size_t threadsNumber, bool removeUnusedTiles, WorldspaceData& data, + NavMeshDb&& db) { Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers..."; @@ -253,8 +271,8 @@ namespace NavMeshTool )); } - navMeshTileConsumer->wait(); - navMeshTileConsumer->commit(); + if (navMeshTileConsumer->wait()) + navMeshTileConsumer->commit(); const auto inserted = navMeshTileConsumer->getInserted(); const auto updated = navMeshTileConsumer->getUpdated(); diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index 71c013d9f4..1f7125d1f2 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace { @@ -19,9 +20,8 @@ namespace using namespace DetourNavigator; using namespace DetourNavigator::Tests; - void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager) + void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager, const osg::Vec2i cellPosition = osg::Vec2i(0, 0)) { - const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0}); } @@ -130,7 +130,7 @@ namespace mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); addObject(mBox, mRecastMeshManager); - auto db = std::make_unique(":memory:"); + auto db = std::make_unique(":memory:", std::numeric_limits::max()); NavMeshDb* const dbPtr = db.get(); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); @@ -138,6 +138,7 @@ namespace const std::map changedTiles {{tilePosition, ChangeType::add}}; updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); + updater.stop(); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); ASSERT_NE(recastMesh, nullptr); ShapeId nextShapeId {1}; @@ -154,7 +155,7 @@ namespace mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); addObject(mBox, mRecastMeshManager); - auto db = std::make_unique(":memory:"); + auto db = std::make_unique(":memory:", std::numeric_limits::max()); NavMeshDb* const dbPtr = db.get(); mSettings.mWriteToNavMeshDb = false; AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); @@ -163,6 +164,7 @@ namespace const std::map changedTiles {{tilePosition, ChangeType::add}}; updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); + updater.stop(); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); ASSERT_NE(recastMesh, nullptr); ShapeId nextShapeId {1}; @@ -177,7 +179,7 @@ namespace mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); addObject(mBox, mRecastMeshManager); - auto db = std::make_unique(":memory:"); + auto db = std::make_unique(":memory:", std::numeric_limits::max()); NavMeshDb* const dbPtr = db.get(); mSettings.mWriteToNavMeshDb = false; AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); @@ -186,6 +188,7 @@ namespace const std::map changedTiles {{tilePosition, ChangeType::add}}; updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); + updater.stop(); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); ASSERT_NE(recastMesh, nullptr); const auto objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), @@ -198,7 +201,8 @@ namespace mRecastMeshManager.setWorldspace(mWorldspace); addHeightFieldPlane(mRecastMeshManager); mSettings.mMaxNavMeshTilesCacheSize = 0; - AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::make_unique(":memory:")); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, + std::make_unique(":memory:", std::numeric_limits::max())); const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); @@ -239,4 +243,37 @@ namespace updater.wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0); } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_stop_writing_to_db_when_size_limit_is_reached) + { + mRecastMeshManager.setWorldspace(mWorldspace); + for (int x = -1; x <= 1; ++x) + for (int y = -1; y <= 1; ++y) + addHeightFieldPlane(mRecastMeshManager, osg::Vec2i(x, y)); + addObject(mBox, mRecastMeshManager); + auto db = std::make_unique(":memory:", 4097); + NavMeshDb* const dbPtr = db.get(); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + std::map changedTiles; + for (int x = -5; x <= 5; ++x) + for (int y = -5; y <= 5; ++y) + changedTiles.emplace(TilePosition {x, y}, ChangeType::add); + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + updater.stop(); + for (int x = -5; x <= 5; ++x) + for (int y = -5; y <= 5; ++y) + { + const TilePosition tilePosition(x, y); + const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); + ASSERT_NE(recastMesh, nullptr); + const std::optional> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v); }); + if (!objects.has_value()) + continue; + EXPECT_FALSE(dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, *objects)).has_value()) + << tilePosition.x() << " " << tilePosition.y(); + } + } } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 50bf1fe735..ec4b669692 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -23,6 +23,7 @@ #include #include #include +#include MATCHER_P3(Vec3fEq, x, y, z, "") { @@ -64,7 +65,7 @@ namespace , mOut(mPath) , mStepSize(28.333332061767578125f) { - mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:"))); + mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); } }; @@ -831,7 +832,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) { mSettings.mAsyncNavMeshUpdaterThreads = 2; - mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:"))); + mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); const std::array heightfieldData {{ 0, 0, 0, 0, 0, diff --git a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp index f7d9327e65..55d26e3b78 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp @@ -10,6 +10,7 @@ #include #include +#include namespace { @@ -27,7 +28,7 @@ namespace struct DetourNavigatorNavMeshDbTest : Test { - NavMeshDb mDb {":memory:"}; + NavMeshDb mDb {":memory:", std::numeric_limits::max()}; std::minstd_rand mRandom; std::vector generateData() @@ -166,4 +167,15 @@ namespace ASSERT_EQ(mDb.findTile(worldspace, TilePosition {x, y}, input).has_value(), -1 <= x && x <= 1 && -1 <= y && y <= 1) << "x=" << x << " y=" << y; } + + TEST_F(DetourNavigatorNavMeshDbTest, should_support_file_size_limit) + { + mDb = NavMeshDb(":memory:", 4096); + const auto f = [&] + { + for (std::int64_t i = 1; i <= 100; ++i) + insertTile(TileId {i}, TileVersion {1}); + }; + EXPECT_THROW(f(), std::runtime_error); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 4af229da8a..18bffae131 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -140,15 +140,7 @@ namespace DetourNavigator AsyncNavMeshUpdater::~AsyncNavMeshUpdater() { - mShouldStop = true; - if (mDbWorker != nullptr) - mDbWorker->stop(); - std::unique_lock lock(mMutex); - mWaiting.clear(); - mHasJob.notify_all(); - lock.unlock(); - for (auto& thread : mThreads) - thread.join(); + stop(); } void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem, @@ -235,6 +227,20 @@ namespace DetourNavigator } } + void AsyncNavMeshUpdater::stop() + { + mShouldStop = true; + if (mDbWorker != nullptr) + mDbWorker->stop(); + std::unique_lock lock(mMutex); + mWaiting.clear(); + mHasJob.notify_all(); + lock.unlock(); + for (auto& thread : mThreads) + if (thread.joinable()) + thread.join(); + } + int AsyncNavMeshUpdater::waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxProgress, Loading::Listener& listener) { std::size_t prevJobsLeft = initialJobsLeft; @@ -672,10 +678,10 @@ namespace DetourNavigator mHasJob.notify_all(); } - std::optional DbJobQueue::pop() + std::optional DbJobQueue::pop(std::chrono::steady_clock::duration timeout) { std::unique_lock lock(mMutex); - mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); }); + mHasJob.wait_for(lock, timeout, [&] { return mShouldStop || !mJobs.empty(); }); if (mJobs.empty()) return std::nullopt; const JobIt job = mJobs.front(); @@ -720,7 +726,6 @@ namespace DetourNavigator DbWorker::~DbWorker() { stop(); - mThread.join(); } void DbWorker::enqueueJob(JobIt job) @@ -741,23 +746,35 @@ namespace DetourNavigator { mShouldStop = true; mQueue.stop(); + if (mThread.joinable()) + mThread.join(); } void DbWorker::run() noexcept { - constexpr std::size_t writesPerTransaction = 100; - auto transaction = mDb->startTransaction(); + constexpr std::chrono::seconds transactionInterval(1); + auto transaction = mDb->startTransaction(Sqlite3::TransactionMode::Immediate); + auto start = std::chrono::steady_clock::now(); while (!mShouldStop) { try { - if (const auto job = mQueue.pop()) + if (const auto job = mQueue.pop(transactionInterval)) processJob(*job); - if (mWrites > writesPerTransaction) + const auto now = std::chrono::steady_clock::now(); + if (mHasChanges && now - start > transactionInterval) { - mWrites = 0; - transaction.commit(); - transaction = mDb->startTransaction(); + mHasChanges = false; + try + { + transaction.commit(); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "DbWorker exception on commit: " << e.what(); + } + transaction = mDb->startTransaction(Sqlite3::TransactionMode::Immediate); + start = now; } } catch (const std::exception& e) @@ -765,7 +782,15 @@ namespace DetourNavigator Log(Debug::Error) << "DbWorker exception: " << e.what(); } } - transaction.commit(); + if (mHasChanges) + try + { + transaction.commit(); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "DbWorker exception on final commit: " << e.what(); + } } void DbWorker::processJob(JobIt job) @@ -779,6 +804,11 @@ namespace DetourNavigator catch (const std::exception& e) { Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what(); + if (std::string_view(e.what()).find("database or disk is full") != std::string_view::npos) + { + mWriteToDb = false; + Log(Debug::Warning) << "Writes to navmeshdb are disabled because file size limit is reached or disk is full"; + } } }; @@ -807,7 +837,7 @@ namespace DetourNavigator const auto objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); if (shapeId != mNextShapeId) - ++mWrites; + mHasChanges = true; job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects); } else @@ -826,7 +856,11 @@ namespace DetourNavigator void DbWorker::processWritingJob(JobIt job) { - ++mWrites; + if (!mWriteToDb) + { + Log(Debug::Debug) << "Ignored db write job " << job->mId; + return; + } Log(Debug::Debug) << "Processing db write job " << job->mId; @@ -843,6 +877,7 @@ namespace DetourNavigator Log(Debug::Debug) << "Update db tile by job " << job->mId; job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId; mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData)); + mHasChanges = true; return; } @@ -858,5 +893,6 @@ namespace DetourNavigator mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile, mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData)); ++mNextTileId; + mHasChanges = true; } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index fa8cba03af..dc6e9b5a81 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -87,7 +87,7 @@ namespace DetourNavigator public: void push(JobIt job); - std::optional pop(); + std::optional pop(std::chrono::steady_clock::duration timeout); void update(TilePosition playerTile, int maxTiles); @@ -131,13 +131,13 @@ namespace DetourNavigator const RecastSettings& mRecastSettings; const std::unique_ptr mDb; const TileVersion mVersion; - const bool mWriteToDb; + bool mWriteToDb; TileId mNextTileId; ShapeId mNextShapeId; DbJobQueue mQueue; std::atomic_bool mShouldStop {false}; std::atomic_size_t mGetTileCount {0}; - std::size_t mWrites = 0; + bool mHasChanges = false; std::thread mThread; inline void run() noexcept; @@ -173,6 +173,8 @@ namespace DetourNavigator void wait(Loading::Listener& listener, WaitConditionType waitConditionType); + void stop(); + Stats getStats() const; void enqueueJob(JobIt job); diff --git a/components/detournavigator/generatenavmeshtile.cpp b/components/detournavigator/generatenavmeshtile.cpp index 360c05931f..a725c5bc23 100644 --- a/components/detournavigator/generatenavmeshtile.cpp +++ b/components/detournavigator/generatenavmeshtile.cpp @@ -96,6 +96,7 @@ namespace DetourNavigator { Log(Debug::Warning) << "Failed to generate navmesh for worldspace \"" << mWorldspace << "\" tile " << mTilePosition << ": " << e.what(); + consumer->cancel(); } } } diff --git a/components/detournavigator/generatenavmeshtile.hpp b/components/detournavigator/generatenavmeshtile.hpp index e6d9e26c1d..2cd669c8ec 100644 --- a/components/detournavigator/generatenavmeshtile.hpp +++ b/components/detournavigator/generatenavmeshtile.hpp @@ -49,6 +49,8 @@ namespace DetourNavigator virtual void update(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0; + + virtual void cancel() = 0; }; class GenerateNavMeshTile final : public SceneUtil::WorkItem diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 5b9341523c..d40f330771 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -16,7 +16,7 @@ namespace DetourNavigator { try { - db = std::make_unique(userDataPath + "/navmesh.db"); + db = std::make_unique(userDataPath + "/navmesh.db", settings.mMaxDbFileSize); } catch (const std::exception& e) { diff --git a/components/detournavigator/navmeshdb.cpp b/components/detournavigator/navmeshdb.cpp index 621c97f390..4d9ab3dafa 100644 --- a/components/detournavigator/navmeshdb.cpp +++ b/components/detournavigator/navmeshdb.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -130,6 +131,27 @@ namespace DetourNavigator constexpr std::string_view vacuumQuery = R"( VACUUM; )"; + + struct GetPageSize + { + static std::string_view text() noexcept { return "pragma page_size;"; } + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + std::uint64_t getPageSize(sqlite3& db) + { + Sqlite3::Statement statement(db); + std::uint64_t value = 0; + request(db, statement, &value, 1); + return value; + } + + void setMaxPageCount(sqlite3& db, std::uint64_t value) + { + const auto query = Misc::StringUtils::format("pragma max_page_count = %lu;", value); + if (const int ec = sqlite3_exec(&db, query.c_str(), nullptr, nullptr, nullptr); ec != SQLITE_OK) + throw std::runtime_error("Failed set max page count: " + std::string(sqlite3_errmsg(&db))); + } } std::ostream& operator<<(std::ostream& stream, ShapeType value) @@ -142,7 +164,7 @@ namespace DetourNavigator return stream << "unknown shape type (" << static_cast>(value) << ")"; } - NavMeshDb::NavMeshDb(std::string_view path) + NavMeshDb::NavMeshDb(std::string_view path, std::uint64_t maxFileSize) : mDb(Sqlite3::makeDb(path, schema)) , mGetMaxTileId(*mDb, DbQueries::GetMaxTileId {}) , mFindTile(*mDb, DbQueries::FindTile {}) @@ -157,11 +179,13 @@ namespace DetourNavigator , mInsertShape(*mDb, DbQueries::InsertShape {}) , mVacuum(*mDb, DbQueries::Vacuum {}) { + const auto dbPageSize = getPageSize(*mDb); + setMaxPageCount(*mDb, maxFileSize / dbPageSize + static_cast((maxFileSize % dbPageSize) != 0)); } - Sqlite3::Transaction NavMeshDb::startTransaction() + Sqlite3::Transaction NavMeshDb::startTransaction(Sqlite3::TransactionMode mode) { - return Sqlite3::Transaction(*mDb); + return Sqlite3::Transaction(*mDb, mode); } TileId NavMeshDb::getMaxTileId() diff --git a/components/detournavigator/navmeshdb.hpp b/components/detournavigator/navmeshdb.hpp index 35aaeaf677..812452206e 100644 --- a/components/detournavigator/navmeshdb.hpp +++ b/components/detournavigator/navmeshdb.hpp @@ -141,9 +141,9 @@ namespace DetourNavigator class NavMeshDb { public: - explicit NavMeshDb(std::string_view path); + explicit NavMeshDb(std::string_view path, std::uint64_t maxFileSize); - Sqlite3::Transaction startTransaction(); + Sqlite3::Transaction startTransaction(Sqlite3::TransactionMode mode = Sqlite3::TransactionMode::Default); TileId getMaxTileId(); diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index cc2c685992..040a0a7b29 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -64,6 +64,7 @@ namespace DetourNavigator result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator"); result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator"); result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator"); + result.mMaxDbFileSize = static_cast(::Settings::Manager::getInt64("max navmeshdb file size", "Navigator")); return result; } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index e6be8017d5..b107a6eb85 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -51,6 +51,7 @@ namespace DetourNavigator std::string mNavMeshPathPrefix; std::chrono::milliseconds mMinUpdateInterval; std::int64_t mNavMeshVersion = 0; + std::uint64_t mMaxDbFileSize = 0; }; RecastSettings makeRecastSettingsFromSettingsManager(); diff --git a/components/sqlite3/db.cpp b/components/sqlite3/db.cpp index b1e3afb1ae..f341fef064 100644 --- a/components/sqlite3/db.cpp +++ b/components/sqlite3/db.cpp @@ -10,7 +10,7 @@ namespace Sqlite3 { void CloseSqlite3::operator()(sqlite3* handle) const noexcept { - sqlite3_close(handle); + sqlite3_close_v2(handle); } Db makeDb(std::string_view path, const char* schema) diff --git a/components/sqlite3/transaction.cpp b/components/sqlite3/transaction.cpp index 3012538652..bafd6e8d32 100644 --- a/components/sqlite3/transaction.cpp +++ b/components/sqlite3/transaction.cpp @@ -9,25 +9,43 @@ namespace Sqlite3 { + namespace + { + const char* getBeginStatement(TransactionMode mode) + { + switch (mode) + { + case TransactionMode::Default: return "BEGIN"; + case TransactionMode::Deferred: return "BEGIN DEFERRED"; + case TransactionMode::Immediate: return "BEGIN IMMEDIATE"; + case TransactionMode::Exclusive: return "BEGIN EXCLUSIVE"; + } + throw std::logic_error("Invalid transaction mode: " + std::to_string(static_cast>(mode))); + } + } + void Rollback::operator()(sqlite3* db) const { if (db == nullptr) return; if (const int ec = sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr); ec != SQLITE_OK) - Log(Debug::Warning) << "Failed to rollback SQLite3 transaction: " << std::string(sqlite3_errmsg(db)); + Log(Debug::Debug) << "Failed to rollback SQLite3 transaction: " << sqlite3_errmsg(db) << " (" << ec << ")"; } - Transaction::Transaction(sqlite3& db) + Transaction::Transaction(sqlite3& db, TransactionMode mode) : mDb(&db) { - if (const int ec = sqlite3_exec(mDb.get(), "BEGIN", nullptr, nullptr, nullptr); ec != SQLITE_OK) - throw std::runtime_error("Failed to start transaction: " + std::string(sqlite3_errmsg(mDb.get()))); + if (const int ec = sqlite3_exec(&db, getBeginStatement(mode), nullptr, nullptr, nullptr); ec != SQLITE_OK) + { + (void) mDb.release(); + throw std::runtime_error("Failed to start transaction: " + std::string(sqlite3_errmsg(&db)) + " (" + std::to_string(ec) + ")"); + } } void Transaction::commit() { if (const int ec = sqlite3_exec(mDb.get(), "COMMIT", nullptr, nullptr, nullptr); ec != SQLITE_OK) - throw std::runtime_error("Failed to commit transaction: " + std::string(sqlite3_errmsg(mDb.get()))); + throw std::runtime_error("Failed to commit transaction: " + std::string(sqlite3_errmsg(mDb.get())) + " (" + std::to_string(ec) + ")"); (void) mDb.release(); } } diff --git a/components/sqlite3/transaction.hpp b/components/sqlite3/transaction.hpp index 88b780a0a5..ff23bc03b5 100644 --- a/components/sqlite3/transaction.hpp +++ b/components/sqlite3/transaction.hpp @@ -12,10 +12,18 @@ namespace Sqlite3 void operator()(sqlite3* handle) const; }; + enum class TransactionMode + { + Default, + Deferred, + Immediate, + Exclusive, + }; + class Transaction { public: - Transaction(sqlite3& db); + explicit Transaction(sqlite3& db, TransactionMode mode = TransactionMode::Default); void commit(); diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 6d00c770bc..85924ca8cc 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -70,10 +70,19 @@ write to navmeshdb :Type: boolean :Range: True/False -:Default: False +:Default: True If true generated navmesh tiles will be stored into disk cache while game is running. +max navmeshdb file size +----------------------- + +:Type: integer +:Range: > 0 +:Default: 2147483648 + +Approximate maximum file size of navigation mesh cache stored on disk in bytes (value > 0). + Advanced settings ***************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 3d461d2dce..f76b67a7e5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -938,7 +938,10 @@ nav mesh version = 1 enable nav mesh disk cache = true # Cache navigation mesh tiles to disk (true, false) -write to navmeshdb = false +write to navmeshdb = true + +# Approximate maximum file size of navigation mesh cache stored on disk in bytes (value > 0) +max navmeshdb file size = 2147483648 [Shadows] From d7d1a851431a688401178c728e2b27e8da12d4f6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 19 Mar 2022 12:02:26 +0100 Subject: [PATCH 2204/2859] Restore dialogue autocompletion in the console --- apps/openmw/mwworld/store.cpp | 7 +++++++ apps/openmw/mwworld/store.hpp | 2 ++ 2 files changed, 9 insertions(+) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index d35746dfff..1a6fdaf215 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1072,6 +1072,13 @@ namespace MWWorld return true; } + void Store::listIdentifier(std::vector& list) const + { + list.reserve(list.size() + getSize()); + for (const auto& dialogue : mShared) + list.push_back(dialogue->mId); + } + const MWDialogue::KeywordSearch& Store::getDialogIdKeywordSearch() const { if (mKeywordSearchModFlag) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 1ec51ad5fd..36860f291a 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -474,6 +474,8 @@ namespace MWWorld RecordId load(ESM::ESMReader &esm) override; + void listIdentifier(std::vector &list) const override; + const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() const; }; From 8e81b6c645010807b92ad5807e3a12a391488eff Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 19 Mar 2022 21:26:02 +0100 Subject: [PATCH 2205/2859] Fix updating UI image resource --- components/lua_ui/image.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 69bccac1ed..7eeca1dc0e 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -35,6 +35,7 @@ namespace LuaUi void LuaImage::updateProperties() { + deleteAllItems(); TextureResource* resource = propertyValue("resource", nullptr); MyGUI::IntCoord atlasCoord; if (resource) @@ -59,6 +60,7 @@ namespace LuaUi tileH ? textureSize.width : 0, tileV ? textureSize.height : 0 )); + setImageTile(textureSize); if (atlasCoord.width == 0) atlasCoord.width = textureSize.width; From d185cb6dce015ca3dd4645ebc9d62cf70efe0ddf Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 20 Mar 2022 00:16:41 +0100 Subject: [PATCH 2206/2859] Add Lua function `ui.screenSize()` --- apps/openmw/mwlua/uibindings.cpp | 10 ++++++++++ files/lua_api/openmw/ui.lua | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index b384994654..4c26c904f3 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include "context.hpp" #include "actions.hpp" #include "luamanagerimp.hpp" @@ -296,6 +298,14 @@ namespace MWLua return luaManager->uiResourceManager()->registerTexture(data); }; + api["screenSize"] = []() + { + return osg::Vec2f( + Settings::Manager::getInt("resolution x", "Video"), + Settings::Manager::getInt("resolution y", "Video") + ); + }; + return LuaUtil::makeReadOnly(api); } } diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 6461588a69..57be0da501 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -38,6 +38,11 @@ -- @function [parent=#ui] showMessage -- @param #string msg +--- +-- Returns the size of the OpenMW window in pixels as a 2D vector. +-- @function [parent=#ui] screenSize +-- @return openmw.util#Vector2 + --- -- Converts a given table of tables into an @{openmw.ui#Content} -- @function [parent=#ui] content From 7afa7841de72d387be90000f6707adbd54fb3a75 Mon Sep 17 00:00:00 2001 From: mriesewijk3 Date: Sun, 20 Mar 2022 10:57:18 +0100 Subject: [PATCH 2207/2859] CONTRIBUTE.md: fix typo and consistency --- CONTRIBUTING.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 264db49cc1..4cdb164a3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,9 +22,9 @@ Pull Request Guidelines To facilitate the review process, your pull request description should include the following, if applicable: * A link back to the bug report or forum discussion that prompted the change. Note: when linking bugs, use the syntax ```[Bug #xyz](https://gitlab.com/OpenMW/openmw/issues/#xyz)``` to create a clickable link. Writing only 'Bug #xyz' will unfortunately create a link to the Github pull request with that number instead. -* Summary of the changes made -* Reasoning / motivation behind the change -* What testing you have carried out to verify the change +* Summary of the changes made. +* Reasoning / motivation behind the change. +* What testing you have carried out to verify the change. Furthermore, we advise to: @@ -51,9 +51,9 @@ OpenMW, in its default configuration, is meant to be a faithful reimplementation That said, we may sometimes evaluate such issues on an individual basis. Common exceptions to the above would be: -* Issues so glaring that they would severely limit the capabilities of the engine in the future (for example, the scripting engine not being allowed to access objects in remote cells) -* Bugs where the intent is very obvious, and that have little to no balancing impact (e.g. the bug were being tired made it easier to repair items, instead of harder) -* Bugs that were fixed in an official patch for Morrowind +* Issues so glaring that they would severely limit the capabilities of the engine in the future (for example, the scripting engine not being allowed to access objects in remote cells). +* Bugs where the intent is very obvious, and that have little to no balancing impact (e.g. the bug were being tired made it easier to repair items, instead of harder). +* Bugs that were fixed in an official patch for Morrowind. Feature additions policy ===================== @@ -99,7 +99,7 @@ Code Review Merging ======= -To be able to merge PRs, commit priviledges are required. If you do not have the priviledges, just ping someone that does have them with a short comment like "Looks good to me @user". +To be able to merge PRs, commit privileges are required. If you do not have the privileges, just ping someone that does have them with a short comment like "Looks good to me @user". The person to merge the PR may either use github's Merge button or if using the command line, use the ```--no-ff``` flag (so a merge commit is created, just like with Github's merge button) and include the pull request number in the commit description. From 1b1e81b90e6191af4f047a9378b988fcfc653a76 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 20 Mar 2022 15:39:48 +0000 Subject: [PATCH 2208/2859] Lua bindings for ESM::Door and ESM::Weapon records --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/types/door.cpp | 24 ++++++++++ apps/openmw/mwlua/types/types.cpp | 2 +- apps/openmw/mwlua/types/types.hpp | 3 +- apps/openmw/mwlua/types/weapon.cpp | 68 ++++++++++++++++++++++++++++ apps/openmw/mwworld/store.hpp | 7 +++ files/lua_api/openmw/types.lua | 71 ++++++++++++++++++++++++++++++ 7 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwlua/types/weapon.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 9a83a5cdb7..b823674d31 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -60,7 +60,7 @@ add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings - types/types types/door types/actor types/container + types/types types/door types/actor types/container types/weapon ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index efcf638360..5c13e27f9e 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -1,7 +1,17 @@ #include "types.hpp" +#include + +#include + #include "../luabindings.hpp" +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + namespace MWLua { @@ -29,6 +39,20 @@ namespace MWLua else return sol::nil; }; + + const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); + door["record"] = sol::overload( + [](const Object& obj) -> const ESM::Door* { return obj.ptr().get()->mBase; }, + [store](const std::string& recordId) -> const ESM::Door* { return store->find(recordId); }); + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Door"); + record[sol::meta_function::to_string] = sol::readonly_property( + [](const ESM::Door& rec) -> std::string { return "ESM3_Door[" + rec.mId + "]"; }); + record["id"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId; }); + record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mModel; }); + record["mwscript"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript; }); + record["openSound"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mOpenSound; }); + record["closeSound"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mCloseSound; }); } } diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index 556c13963f..a175031058 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -166,7 +166,7 @@ namespace MWLua addType(ObjectTypeName::Light, {ESM::REC_LIGH}, ObjectTypeName::Item); addType(ObjectTypeName::MiscItem, {ESM::REC_MISC}, ObjectTypeName::Item); addType(ObjectTypeName::Potion, {ESM::REC_ALCH}, ObjectTypeName::Item); - addType(ObjectTypeName::Weapon, {ESM::REC_WEAP}, ObjectTypeName::Item); + addWeaponBindings(addType(ObjectTypeName::Weapon, {ESM::REC_WEAP}, ObjectTypeName::Item), context); addType(ObjectTypeName::Apparatus, {ESM::REC_APPA}, ObjectTypeName::Item); addType(ObjectTypeName::Lockpick, {ESM::REC_LOCK}, ObjectTypeName::Item); addType(ObjectTypeName::Probe, {ESM::REC_PROB}, ObjectTypeName::Item); diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index 966e32ccf2..af861c0bb4 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -24,9 +24,10 @@ namespace MWLua sol::table initTypesPackage(const Context& context); // used in initTypesPackage - void addContainerBindings(sol::table door, const Context& context); + void addContainerBindings(sol::table container, const Context& context); void addDoorBindings(sol::table door, const Context& context); void addActorBindings(sol::table actor, const Context& context); + void addWeaponBindings(sol::table weapon, const Context& context); } #endif // MWLUA_TYPES_H diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp new file mode 100644 index 0000000000..746536cc69 --- /dev/null +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -0,0 +1,68 @@ +#include "types.hpp" + +#include + +#include + +#include "../luabindings.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + void addWeaponBindings(sol::table weapon, const Context& context) + { + weapon["TYPE"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"ShortBladeOneHand", ESM::Weapon::ShortBladeOneHand}, + {"LongBladeOneHand", ESM::Weapon::LongBladeOneHand}, + {"LongBladeTwoHand", ESM::Weapon::LongBladeTwoHand}, + {"BluntOneHand", ESM::Weapon::BluntOneHand}, + {"BluntTwoClose", ESM::Weapon::BluntTwoClose}, + {"BluntTwoWide", ESM::Weapon::BluntTwoWide}, + {"SpearTwoWide", ESM::Weapon::SpearTwoWide}, + {"AxeOneHand", ESM::Weapon::AxeOneHand}, + {"AxeTwoHand", ESM::Weapon::AxeTwoHand}, + {"MarksmanBow", ESM::Weapon::MarksmanBow}, + {"MarksmanCrossbow", ESM::Weapon::MarksmanCrossbow}, + {"MarksmanThrown", ESM::Weapon::MarksmanThrown}, + {"Arrow", ESM::Weapon::Arrow}, + {"Bolt", ESM::Weapon::Bolt}, + })); + + const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); + weapon["record"] = sol::overload( + [](const Object& obj) -> const ESM::Weapon* { return obj.ptr().get()->mBase; }, + [store](const std::string& recordId) -> const ESM::Weapon* { return store->find(recordId); }); + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Weapon"); + record[sol::meta_function::to_string] = sol::readonly_property( + [](const ESM::Weapon& rec) -> std::string { return "ESM3_Weapon[" + rec.mId + "]"; }); + record["id"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mId; }); + record["name"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mModel; }); + record["icon"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mIcon; }); + record["enchant"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mEnchant; }); + record["mwscript"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mScript; }); + record["isMagical"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Magical; }); + record["isSilver"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Silver; }); + record["weight"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mWeight; }); + record["value"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mValue; }); + record["type"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mType; }); + record["health"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mHealth; }); + record["speed"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mSpeed; }); + record["reach"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mReach; }); + record["enchant"] = sol::readonly_property([](const ESM::Weapon& rec) -> float { return rec.mData.mEnchant * 0.1f; }); + record["chopMinDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mChop[0]; }); + record["chopMaxDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mChop[1]; }); + record["slashMinDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mSlash[0]; }); + record["slashMaxDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mSlash[1]; }); + record["thrustMinDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mThrust[0]; }); + record["thrustMaxDamage"] = sol::readonly_property([](const ESM::Weapon& rec) -> int { return rec.mData.mThrust[1]; }); + } + +} diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 36860f291a..b352eaa311 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -77,6 +77,8 @@ namespace MWWorld void setUp(); const T *search(int index) const; + + // calls `search` and throws an exception if not found const T *find(int index) const; }; @@ -183,6 +185,7 @@ namespace MWWorld /** Returns a random record that starts with the named ID, or nullptr if not found. */ const T *searchRandom(const std::string &id) const; + // calls `search` and throws an exception if not found const T *find(const std::string &id) const; iterator begin() const; @@ -408,6 +411,8 @@ namespace MWWorld Store(); const ESM::Attribute *search(size_t index) const; + + // calls `search` and throws an exception if not found const ESM::Attribute *find(size_t index) const; void setUp(); @@ -428,6 +433,8 @@ namespace MWWorld Store(); const ESM::WeaponType *search(const int id) const; + + // calls `search` and throws an exception if not found const ESM::WeaponType *find(const int id) const; RecordId load(ESM::ESMReader &esm) override { return RecordId({}, false); } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 82dbded2e8..917a83ee8c 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -318,6 +318,58 @@ -- @param openmw.core#GameObject object -- @return #boolean +--- Weapon.TYPE +-- @type WeaponTYPE +-- @field #number ShortBladeOneHand +-- @field #number LongBladeOneHand +-- @field #number LongBladeTwoHand +-- @field #number BluntOneHand +-- @field #number BluntTwoClose +-- @field #number BluntTwoWide +-- @field #number SpearTwoWide +-- @field #number AxeOneHand +-- @field #number AxeTwoHand +-- @field #number MarksmanBow +-- @field #number MarksmanCrossbow +-- @field #number MarksmanThrown +-- @field #number Arrow +-- @field #number Bolt + +--- @{#WeaponTYPE} +-- @field [parent=#Weapon] #WeaponTYPE TYPE + +--- +-- Returns the read-only @{#WeaponRecord} of a weapon +-- @function [parent=#Weapon] record +-- @param #any objectOrRecordId +-- @return #WeaponRecord + +--- +-- @type WeaponRecord +-- @field #string id Record id +-- @field #string name Human-readable name +-- @field #string model VFS path to the model +-- @field #string mwscript MWScript on this door (can be empty) +-- @field #string icon +-- @field #string enchant +-- @field #boolean isMagical +-- @field #boolean isSilver +-- @field #number weight +-- @field #number value +-- @field #number type See @{#Weapon.TYPE} +-- @field #number health +-- @field #number speed +-- @field #number reach +-- @field #number enchant +-- @field #number chopMinDamage +-- @field #number chopMaxDamage +-- @field #number slashMinDamage +-- @field #number slashMaxDamage +-- @field #number thrustMinDamage +-- @field #number thrustMaxDamage + + + --- @{#Apparatus} functions -- @field [parent=#types] #Apparatus Apparatus @@ -404,6 +456,8 @@ -- @param openmw.core#GameObject object -- @return #boolean + + --- @{#Door} functions -- @field [parent=#types] #Door Door @@ -440,6 +494,23 @@ -- @param openmw.core#GameObject object -- @return openmw.core#Cell +--- +-- Returns the read-only @{#DoorRecord} of a door +-- @function [parent=#Door] record +-- @param #any objectOrRecordId +-- @return #DoorRecord + +--- +-- @type DoorRecord +-- @field #string id Record id +-- @field #string name Human-readable name +-- @field #string model VFS path to the model +-- @field #string mwscript MWScript on this door (can be empty) +-- @field #string openSound VFS path to the sound of opening +-- @field #string closeSound VFS path to the sound of closing + + + --- Functions for @{#Static} objects -- @field [parent=#types] #Static Static From b502dc12f0883cf21a698bdfc0234359e1fd8ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sun, 6 Mar 2022 20:02:03 +0200 Subject: [PATCH 2209/2859] Add prng to World instance and serialize state in Save --- apps/openmw/mwbase/world.hpp | 3 +++ apps/openmw/mwworld/worldimp.cpp | 20 ++++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 5 ++++- components/esm/defs.hpp | 3 +++ components/esm3/savedgame.cpp | 2 +- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 0b55484278..23e687be5a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -9,6 +9,7 @@ #include #include +#include #include @@ -658,6 +659,8 @@ namespace MWBase virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; virtual std::vector getAll(const std::string& id) = 0; + + virtual Misc::Rng::Generator& getPrng() = 0; }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a5ab0968ee..afb248bf08 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -359,6 +359,12 @@ namespace MWWorld writer.writeHNT("LEVT", mLevitationEnabled); writer.endRecord(ESM::REC_ENAB); + std::stringstream ssPrng; + ssPrng << mPrng; + writer.startRecord(ESM::REC_RAND); + writer.writeHString(ssPrng.str()); + writer.endRecord(ESM::REC_RAND); + writer.startRecord(ESM::REC_CAM_); writer.writeHNT("FIRS", isFirstPerson()); writer.endRecord(ESM::REC_CAM_); @@ -376,6 +382,14 @@ namespace MWWorld reader.getHNT(mTeleportEnabled, "TELE"); reader.getHNT(mLevitationEnabled, "LEVT"); return; + case ESM::REC_RAND: + { + std::stringstream ssPrng; + ssPrng << reader.getHString(); + ssPrng.seekg(0); + ssPrng >> mPrng; + } + break; case ESM::REC_PLAY: mStore.checkPlayer(); mPlayer->readRecord(reader, type); @@ -3999,4 +4013,10 @@ namespace MWWorld { return mCells.getAll(id); } + + Misc::Rng::Generator& World::getPrng() + { + return mPrng; + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 61154afb59..eb089f04d7 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -4,6 +4,7 @@ #include #include +#include #include "../mwbase/world.hpp" @@ -84,7 +85,7 @@ namespace MWWorld GroundcoverStore mGroundcoverStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; - + Misc::Rng::Generator mPrng; Cells mCells; std::string mCurrentWorldSpace; @@ -736,6 +737,8 @@ namespace MWWorld void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; std::vector getAll(const std::string& id) override; + + Misc::Rng::Generator& getPrng() override; }; } diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index f587fc6690..60d34a9355 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -173,6 +173,9 @@ enum RecNameInts : unsigned int // format 16 - Lua scripts in saved games REC_LUAM = fourCC("LUAM"), // LuaManager data + + // format 21 - Random state in saved games. + REC_RAND = fourCC("RAND"), // Random state. }; /// Common subrecords diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 18498abe2c..667dbdfbce 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 20; +int ESM::SavedGame::sCurrentFormat = 21; void ESM::SavedGame::load (ESMReader &esm) { From 08fae7be6e9e09381d1b69d0c5966ac78821f892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sun, 6 Mar 2022 21:56:02 +0200 Subject: [PATCH 2210/2859] Pass the prng from world where appropriate --- apps/openmw/mwclass/activator.cpp | 10 ++-- apps/openmw/mwclass/book.cpp | 3 +- apps/openmw/mwclass/container.cpp | 6 ++- apps/openmw/mwclass/creature.cpp | 18 ++++--- apps/openmw/mwclass/creaturelevlist.cpp | 3 +- apps/openmw/mwclass/npc.cpp | 19 ++++--- apps/openmw/mwgui/jailscreen.cpp | 3 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 4 +- apps/openmw/mwgui/waitdialog.cpp | 2 +- apps/openmw/mwmechanics/actor.cpp | 5 ++ apps/openmw/mwmechanics/actor.hpp | 7 +-- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/aiavoiddoor.cpp | 3 +- apps/openmw/mwmechanics/aicombat.cpp | 51 +++++++++++++++---- apps/openmw/mwmechanics/aicombat.hpp | 24 +-------- apps/openmw/mwmechanics/aipackage.cpp | 1 + apps/openmw/mwmechanics/aitimer.hpp | 15 ++++-- apps/openmw/mwmechanics/aitravel.cpp | 3 ++ apps/openmw/mwmechanics/aiwander.cpp | 42 +++++++++++---- apps/openmw/mwmechanics/aiwander.hpp | 13 +---- apps/openmw/mwmechanics/alchemy.cpp | 7 +-- apps/openmw/mwmechanics/character.cpp | 23 ++++++--- apps/openmw/mwmechanics/combat.cpp | 13 +++-- apps/openmw/mwmechanics/disease.hpp | 3 +- apps/openmw/mwmechanics/enchanting.cpp | 3 +- apps/openmw/mwmechanics/levelledlist.hpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 7 +-- apps/openmw/mwmechanics/pickpocket.cpp | 3 +- apps/openmw/mwmechanics/recharge.cpp | 3 +- apps/openmw/mwmechanics/repair.cpp | 3 +- apps/openmw/mwmechanics/security.cpp | 6 ++- apps/openmw/mwmechanics/spellcasting.cpp | 9 ++-- apps/openmw/mwmechanics/spelleffects.cpp | 15 ++++-- apps/openmw/mwmechanics/spellresistance.cpp | 3 +- apps/openmw/mwmechanics/trading.cpp | 3 +- apps/openmw/mwscript/containerextensions.cpp | 3 +- apps/openmw/mwscript/miscextensions.cpp | 3 +- apps/openmw/mwstate/statemanagerimp.cpp | 1 + apps/openmw/mwworld/class.cpp | 3 +- apps/openmw/mwworld/containerstore.hpp | 2 +- apps/openmw/mwworld/store.cpp | 4 +- apps/openmw/mwworld/store.hpp | 3 +- components/misc/timer.hpp | 4 +- 43 files changed, 222 insertions(+), 138 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index a60c44265c..0b4edf4e1a 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -120,7 +120,8 @@ namespace MWClass if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfActivator"); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound *sound = store.get().searchRandom("WolfActivator", prng); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); @@ -156,6 +157,7 @@ namespace MWClass int type = getSndGenTypeFromName(name); std::vector fallbacksounds; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (!creatureId.empty()) { std::vector sounds; @@ -168,9 +170,9 @@ namespace MWClass } if (!sounds.empty()) - return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound; if (!fallbacksounds.empty()) - return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; } else { @@ -180,7 +182,7 @@ namespace MWClass fallbacksounds.push_back(&*sound); if (!fallbacksounds.empty()) - return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; } return std::string(); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 3fcd7368d6..f9350222f1 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -56,7 +56,8 @@ namespace MWClass if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfItem"); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound *sound = store.get().searchRandom("WolfItem", prng); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 09d33af414..e319cdf410 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -32,7 +32,8 @@ namespace MWClass { ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell) { - unsigned int seed = Misc::Rng::rollDice(std::numeric_limits::max()); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + unsigned int seed = Misc::Rng::rollDice(std::numeric_limits::max(), prng); // setting ownership not needed, since taking items from a container inherits the // container's owner automatically mStore.fillNonRandom(container.mInventory, "", seed); @@ -139,7 +140,8 @@ namespace MWClass if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfContainer"); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound *sound = store.get().searchRandom("WolfContainer", prng); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 49b3627952..6a633bc668 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -159,7 +159,8 @@ namespace MWClass resetter.mPtr = {}; - getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); if (hasInventory) getInventoryStore(ptr).autoEquip(ptr); @@ -264,8 +265,8 @@ namespace MWClass osg::Vec3f hitPosition (result.second); float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); - - if(Misc::Rng::roll0to99() >= hitchance) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if(Misc::Rng::roll0to99(prng) >= hitchance) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -392,7 +393,8 @@ namespace MWClass float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->mValue.getFloat(); float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f + getGmst().iKnockDownOddsBase->mValue.getInteger(); - if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng)) stats.setKnockedDown(true); else stats.setHitRecovery(true); // Is this supposed to always occur? @@ -429,7 +431,8 @@ namespace MWClass if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfCreature"); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound *sound = store.get().searchRandom("WolfCreature", prng); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); @@ -642,10 +645,11 @@ namespace MWClass } } + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (!sounds.empty()) - return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound; if (!fallbacksounds.empty()) - return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; return std::string(); } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 66e35e5bf8..de40030a80 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -127,7 +127,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::string id = MWMechanics::getLevelledItem(ref->mBase, true); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + std::string id = MWMechanics::getLevelledItem(ref->mBase, true, prng); if (!id.empty()) { diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index bfae090545..94027e5039 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -394,7 +394,8 @@ namespace MWClass // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items - getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); getInventoryStore(ptr).autoEquip(ptr); } @@ -584,7 +585,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill)); - if (Misc::Rng::roll0to99() >= hitchance) + if (Misc::Rng::roll0to99(world->getPrng()) >= hitchance) { othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -726,15 +727,16 @@ namespace MWClass const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const GMST& gmst = getGmst(); - int chance = store.get().find("iVoiceHitOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99() < chance) + int chance = store.get().find("iVoiceHitOdds")->mValue.getInteger(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) < chance) MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); // Check for knockdown float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->mValue.getFloat(); float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f + gmst.iKnockDownOddsBase->mValue.getInteger(); - if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng)) stats.setKnockedDown(true); else stats.setHitRecovery(true); // Is this supposed to always occur? @@ -757,7 +759,7 @@ namespace MWClass MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }; - int hitslot = hitslots[Misc::Rng::rollDice(20)]; + int hitslot = hitslots[Misc::Rng::rollDice(20, prng)]; float unmitigatedDamage = damage; float x = damage / (damage + getArmorRating(ptr)); @@ -773,7 +775,7 @@ namespace MWClass // If there's no item in the carried left slot or if it is not a shield redistribute the hit. if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft) { - if (Misc::Rng::rollDice(2) == 0) + if (Misc::Rng::rollDice(2, prng) == 0) hitslot = MWWorld::InventoryStore::Slot_Cuirass; else hitslot = MWWorld::InventoryStore::Slot_LeftPauldron; @@ -865,7 +867,8 @@ namespace MWClass if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfNPC"); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound *sound = store.get().searchRandom("WolfNPC", prng); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 53219a29d6..92c0fc9edc 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -87,7 +87,8 @@ namespace MWGui std::set skills; for (int day=0; daygetPrng(); + int skill = Misc::Rng::rollDice(ESM::Skill::Length, prng); skills.insert(skill); MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill); diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 5ba1b4aafa..2b4d474059 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -24,13 +24,13 @@ namespace MWGui float chance = player.getClass().getSkill(player, ESM::Skill::Sneak); mSourceModel->update(); - // build list of items that player is unable to find when attempts to pickpocket. if (hideItems) { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); for (size_t i = 0; igetItemCount(); ++i) { - if (Misc::Rng::roll0to99() > chance) + if (Misc::Rng::roll0to99(prng) > chance) mHiddenItems.push_back(mSourceModel->getItem(i)); } } diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 42a70dcfa7..754777c507 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -191,7 +191,7 @@ namespace MWGui if (!region->mSleepList.empty()) { // figure out if player will be woken while sleeping - int x = Misc::Rng::rollDice(hoursToWait); + int x = Misc::Rng::rollDice(hoursToWait, world->getPrng()); float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->mValue.getFloat(); if (x < fSleepRandMod * hoursToWait) { diff --git a/apps/openmw/mwmechanics/actor.cpp b/apps/openmw/mwmechanics/actor.cpp index 5801ea751d..d1d37eeb56 100644 --- a/apps/openmw/mwmechanics/actor.cpp +++ b/apps/openmw/mwmechanics/actor.cpp @@ -60,6 +60,11 @@ namespace MWMechanics mIsTurningToPlayer = turning; } + Misc::TimerStatus Actor::updateEngageCombatTimer(float duration) + { + return mEngageCombat.update(duration, MWBase::Environment::get().getWorld()->getPrng()); + } + void Actor::setPositionAdjusted(bool adjusted) { mPositionAdjusted = adjusted; diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp index c25fc1cb73..231c756810 100644 --- a/apps/openmw/mwmechanics/actor.hpp +++ b/apps/openmw/mwmechanics/actor.hpp @@ -43,10 +43,7 @@ namespace MWMechanics bool isTurningToPlayer() const; void setTurningToPlayer(bool turning); - Misc::TimerStatus updateEngageCombatTimer(float duration) - { - return mEngageCombat.update(duration); - } + Misc::TimerStatus updateEngageCombatTimer(float duration); void setPositionAdjusted(bool adjusted); bool getPositionAdjusted() const; @@ -57,7 +54,7 @@ namespace MWMechanics float mTargetAngleRadians{0.f}; GreetingState mGreetingState{Greet_None}; bool mIsTurningToPlayer{false}; - Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)}; + Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f, MWBase::Environment::get().getWorld()->getPrng())}; bool mPositionAdjusted; }; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index f13afb02d6..b7b83ed562 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -305,7 +305,7 @@ namespace MWMechanics // We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST. const float delta = MWBase::Environment::get().getFrameDuration() * 6.f; static const float fVoiceIdleOdds = world->getStore().get().find("fVoiceIdleOdds")->mValue.getFloat(); - if (Misc::Rng::rollProbability() * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor)) + if (Misc::Rng::rollProbability(world->getPrng()) * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor)) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 6a59ae2bfb..e8cb71add0 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -80,7 +80,8 @@ bool MWMechanics::AiAvoidDoor::isStuck(const osg::Vec3f& actorPos) const void MWMechanics::AiAvoidDoor::adjustDirection() { - mDirection = Misc::Rng::rollDice(MAX_DIRECTIONS); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + mDirection = Misc::Rng::rollDice(MAX_DIRECTIONS, prng); } float MWMechanics::AiAvoidDoor::getAdjustedAngle() const diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 9776bb8891..12671d50dd 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -370,7 +370,8 @@ namespace MWMechanics if (!points.empty()) { - ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size())]; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size(), prng)]; coords.toWorld(dest); state = AiCombatStorage::FleeState_RunToDestination; @@ -464,8 +465,38 @@ namespace MWMechanics sequence.mPackages.push_back(std::move(package)); } + + AiCombatStorage::AiCombatStorage() : + mAttackCooldown(0.0f), + mReaction(MWBase::Environment::get().getWorld()->getPrng()), + mTimerCombatMove(0.0f), + mReadyToAttack(false), + mAttack(false), + mAttackRange(0.0f), + mCombatMove(false), + mRotateMove(false), + mLastTargetPos(0, 0, 0), + mCell(nullptr), + mCurrentAction(), + mActionCooldown(0.0f), + mStrength(), + mForceNoShortcut(false), + mShortcutFailPos(), + mMovement(), + mFleeState(FleeState_None), + mLOS(false), + mUpdateLOSTimer(0.0f), + mFleeBlindRunTimer(0.0f), + mUseCustomDestination(false), + mCustomDestination() + { + + } + void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + // get the range of the target's weapon MWWorld::Ptr targetWeapon = MWWorld::Ptr(); const MWWorld::Class& targetClass = target.getClass(); @@ -483,7 +514,7 @@ namespace MWMechanics if (mMovement.mPosition[0] || mMovement.mPosition[1]) { - mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); + mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng); mCombatMove = true; } else if (isDistantCombat) @@ -537,11 +568,11 @@ namespace MWMechanics // if actor is within range of target's weapon. if (std::abs(angleToTarget) > osg::PI / 4) moveDuration = 0.2f; - else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) - moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); + else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability(prng) < 0.25) + moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng); if (moveDuration > 0) { - mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right + mMovement.mPosition[0] = Misc::Rng::rollProbability(prng) < 0.5 ? 1.0f : -1.0f; // to the left/right mTimerCombatMove = moveDuration; mCombatMove = true; } @@ -580,7 +611,8 @@ namespace MWMechanics if (!distantCombat) characterController.setAIAttackType(chooseBestAttack(weapon)); - mStrength = Misc::Rng::rollClosedProbability(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + mStrength = Misc::Rng::rollClosedProbability(prng); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -592,11 +624,11 @@ namespace MWMechanics // Say a provoking combat phrase const int iVoiceAttackOdds = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99() < iVoiceAttackOdds) + if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } - mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); + mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9); } else mAttackCooldown -= AI_REACTION_TIME; @@ -657,7 +689,8 @@ std::string chooseBestAttack(const ESM::Weapon* weapon) int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; - float roll = Misc::Rng::rollClosedProbability() * (slash + chop + thrust); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + float roll = Misc::Rng::rollClosedProbability(prng) * (slash + chop + thrust); if(roll <= slash) attackType = "slash"; else if(roll <= (slash + thrust)) diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index c783994b97..566ec354d6 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -59,29 +59,7 @@ namespace MWMechanics bool mUseCustomDestination; osg::Vec3f mCustomDestination; - AiCombatStorage(): - mAttackCooldown(0.0f), - mTimerCombatMove(0.0f), - mReadyToAttack(false), - mAttack(false), - mAttackRange(0.0f), - mCombatMove(false), - mRotateMove(false), - mLastTargetPos(0,0,0), - mCell(nullptr), - mCurrentAction(), - mActionCooldown(0.0f), - mStrength(), - mForceNoShortcut(false), - mShortcutFailPos(), - mMovement(), - mFleeState(FleeState_None), - mLOS(false), - mUpdateLOSTimer(0.0f), - mFleeBlindRunTimer(0.0f), - mUseCustomDestination(false), - mCustomDestination() - {} + AiCombatStorage(); void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); void updateCombatMove(float duration); diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index e8e03daad6..1a674d444a 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -43,6 +43,7 @@ namespace MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : mTypeId(typeId), + mReaction(MWBase::Environment::get().getWorld()->getPrng()), mOptions(options), mTargetActorRefId(""), mTargetActorId(-1), diff --git a/apps/openmw/mwmechanics/aitimer.hpp b/apps/openmw/mwmechanics/aitimer.hpp index 804cda1bd2..e4bc07e52d 100644 --- a/apps/openmw/mwmechanics/aitimer.hpp +++ b/apps/openmw/mwmechanics/aitimer.hpp @@ -13,13 +13,20 @@ namespace MWMechanics public: static constexpr float sDeviation = 0.1f; - Misc::TimerStatus update(float duration) { return mImpl.update(duration); } + AiReactionTimer(Misc::Rng::Generator& prng) + : mPrng{ prng } + , mImpl{ AI_REACTION_TIME, sDeviation, Misc::Rng::deviate(0, sDeviation, prng) } + { + } - void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); } + Misc::TimerStatus update(float duration) { return mImpl.update(duration, mPrng); } + + void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation, mPrng)); } private: - Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation, - Misc::Rng::deviate(0, sDeviation)}; + Misc::Rng::Generator& mPrng; + Misc::DeviatingPeriodicTimer mImpl; + }; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 4a2df475f4..919685e93d 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -36,11 +36,13 @@ namespace MWMechanics { AiTravel::AiTravel(float x, float y, float z, bool repeat, AiTravel*) : TypedAiPackage(repeat), mX(x), mY(y), mZ(z), mHidden(false) + , mDestinationCheck(MWBase::Environment::get().getWorld()->getPrng()) { } AiTravel::AiTravel(float x, float y, float z, AiInternalTravel* derived) : TypedAiPackage(derived), mX(x), mY(y), mZ(z), mHidden(true) + , mDestinationCheck(MWBase::Environment::get().getWorld()->getPrng()) { } @@ -51,6 +53,7 @@ namespace MWMechanics AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) : TypedAiPackage(travel->mRepeat), mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ), mHidden(false) + , mDestinationCheck(MWBase::Environment::get().getWorld()->getPrng()) { // Hidden ESM::AiSequence::AiTravel package should be converted into MWMechanics::AiInternalTravel type assert(!travel->mHidden); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index c63a4c0270..ebaff54f20 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -59,7 +59,8 @@ namespace MWMechanics osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance) { - const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * osg::PI; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const float randomDirection = Misc::Rng::rollClosedProbability(prng) * 2.0f * osg::PI; osg::Matrixf rotation; rotation.makeRotate(randomDirection, osg::Vec3f(0.0, 0.0, 1.0)); return position + osg::Vec3f(distance, 0.0, 0.0) * rotation; @@ -102,6 +103,22 @@ namespace MWMechanics { return std::vector(std::begin(idle), std::end(idle)); } + + } + + AiWanderStorage::AiWanderStorage() : + mState(Wander_ChooseAction), + mIsWanderingManually(false), + mCanWanderAlongPathGrid(true), + mIdleAnimation(0), + mBadIdles(), + mPopulateAvailableNodes(true), + mAllowedNodes(), + mTrimCurrentNode(false), + mCheckIdlePositionTimer(0), + mStuckCount(0), + mReaction(MWBase::Environment::get().getWorld()->getPrng()) + { } AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): @@ -250,9 +267,10 @@ namespace MWMechanics getAllowedNodes(actor, actor.getCell()->getCell(), storage); } + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (canActorMoveByZAxis(actor) && mDistance > 0) { // Typically want to idle for a short time before the next wander - if (Misc::Rng::rollDice(100) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) { + if (Misc::Rng::rollDice(100, prng) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) { wanderNearStart(actor, storage, mDistance); } @@ -262,7 +280,7 @@ namespace MWMechanics // randomly idle or wander near spawn point else if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { // Typically want to idle for a short time before the next wander - if (Misc::Rng::rollDice(100) >= 96) { + if (Misc::Rng::rollDice(100, prng) >= 96) { wanderNearStart(actor, storage, mDistance); } else { storage.setState(AiWanderStorage::Wander_IdleNow); @@ -330,10 +348,12 @@ namespace MWMechanics const auto navigator = world->getNavigator(); const auto navigatorFlags = getNavigatorFlags(actor); const auto areaCosts = getAreaCosts(actor); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); do { + // Determine a random location within radius of original position - const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance; + const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability(prng) * 0.8f) * wanderDistance; if (!isWaterCreature && !isFlyingCreature) { // findRandomPointAroundCircle uses wanderDistance as limit for random and not as exact distance @@ -544,7 +564,8 @@ namespace MWMechanics void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) { - unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng); ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); ToWorldCoordinates(dest, actor.getCell()->getCell()); @@ -650,11 +671,11 @@ namespace MWMechanics for(unsigned int counter = 0; counter < mIdle.size(); counter++) { - static float fIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore() - .get().find("fIdleChanceMultiplier")->mValue.getFloat(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + static float fIdleChanceMultiplier = world->getStore().get().find("fIdleChanceMultiplier")->mValue.getFloat(); unsigned short idleChance = static_cast(fIdleChanceMultiplier * mIdle[counter]); - unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); + unsigned short randSelect = (int)(Misc::Rng::rollProbability(world->getPrng()) * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { selectedAnimation = counter + GroupIndex_MinIdle; @@ -678,7 +699,8 @@ namespace MWMechanics if (storage.mAllowedNodes.empty()) return; - int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng); ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; ESM::Pathgrid::Point worldDest = dest; ToWorldCoordinates(worldDest, actor.getCell()->getCell()); @@ -700,7 +722,7 @@ namespace MWMechanics // AI will try to move the NPC towards every neighboring node until suitable place will be found for (int i = 0; i < initialSize; i++) { - int randomIndex = Misc::Rng::rollDice(points.size()); + int randomIndex = Misc::Rng::rollDice(points.size(), prng); ESM::Pathgrid::Point connDest = points[randomIndex]; // add an offset towards random neighboring node diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index f8506ff594..02a1054363 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -55,18 +55,7 @@ namespace MWMechanics float mCheckIdlePositionTimer; int mStuckCount; - AiWanderStorage(): - mState(Wander_ChooseAction), - mIsWanderingManually(false), - mCanWanderAlongPathGrid(true), - mIdleAnimation(0), - mBadIdles(), - mPopulateAvailableNodes(true), - mAllowedNodes(), - mTrimCurrentNode(false), - mCheckIdlePositionTimer(0), - mStuckCount(0) - {}; + AiWanderStorage(); void setState(const WanderState wanderState, const bool isManualWander = false) { diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index f513554c12..d60eac15de 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -289,7 +289,8 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) newRecord.mName = name; - int index = Misc::Rng::rollDice(6); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int index = Misc::Rng::rollDice(6, prng); assert (index>=0 && index<6); static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; @@ -527,8 +528,8 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle () removeIngredients(); return Result_RandomFailure; } - - if (getAlchemyFactor() < Misc::Rng::roll0to99()) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (getAlchemyFactor() < Misc::Rng::roll0to99(prng)) { removeIngredients(); return Result_RandomFailure; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 25a5ac6aa0..1c0a4f2c85 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -193,11 +193,13 @@ public: std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int numAnims=0; while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1))) ++numAnims; - int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] + int roll = Misc::Rng::rollDice(numAnims, prng) + 1; // [1, numAnims] if (num) *num = roll; return prefix + std::to_string(roll); @@ -209,12 +211,13 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if(mHitState == CharState_None) { if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)) { - mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds + mTimeUntilWake = Misc::Rng::rollClosedProbability(prng) * 2 + 1; // Wake up after 1 to 3 seconds if (isSwimming && mAnimation->hasAnimation("swimknockout")) { mHitState = CharState_SwimKnockOut; @@ -678,7 +681,8 @@ void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, Ch // play until the Loop Stop key 2 to 5 times, then play until the Stop key // this replicates original engine behavior for the "Idle1h" 1st-person animation - numLoops = 1 + Misc::Rng::rollDice(4); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + numLoops = 1 + Misc::Rng::rollDice(4, prng); } } @@ -1131,6 +1135,8 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const bool CharacterController::updateState(CharacterState idle) { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); int weaptype = ESM::Weapon::None; @@ -1288,7 +1294,7 @@ bool CharacterController::updateState(CharacterState idle) if(isWerewolf) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); + const ESM::Sound *sound = store.get().searchRandom("WolfEquip", prng); if(sound) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -1563,7 +1569,7 @@ bool CharacterController::updateState(CharacterState idle) mUpperBodyState = UpperCharState_StartToMinAttack; if (isRandomAttackAnimation(mCurrentWeapon)) { - mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); playSwishSound(0.0f); } } @@ -1596,7 +1602,7 @@ bool CharacterController::updateState(CharacterState idle) // most creatures don't actually have an attack wind-up animation, so use a uniform random value // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. - attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); + attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); } if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) @@ -1606,7 +1612,7 @@ bool CharacterController::updateState(CharacterState idle) if(isWerewolf) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfSwing"); + const ESM::Sound *sound = store.get().searchRandom("WolfSwing", prng); if(sound) sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); } @@ -2771,7 +2777,8 @@ void CharacterController::setAIAttackType(const std::string& attackType) void CharacterController::setAttackTypeRandomly(std::string& attackType) { - float random = Misc::Rng::rollProbability(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + float random = Misc::Rng::rollProbability(world->getPrng()); if (random >= 2/3.f) attackType = "thrust"; else if (random >= 1/3.f) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 0eccd6688c..27582840df 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -117,7 +117,8 @@ namespace MWMechanics const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); int x = std::clamp(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance); - if (Misc::Rng::roll0to99() < x) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) < x) { // Reduce shield durability by incoming damage int shieldhealth = shield->getClass().getItemHealth(*shield); @@ -212,7 +213,7 @@ namespace MWMechanics int skillValue = attacker.getClass().getSkill(attacker, weaponSkill); - if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) + if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue)) { victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker); @@ -262,7 +263,7 @@ namespace MWMechanics if (victim != getPlayer() && !appliedEnchantment) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); - if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) + if (Misc::Rng::rollProbability(world->getPrng()) < fProjectileThrownStoreChance / 100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } @@ -315,6 +316,7 @@ namespace MWMechanics bool godmode = attacker == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); if (godmode) return; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); for (int i=0; i<3; ++i) { float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude(); @@ -334,7 +336,7 @@ namespace MWMechanics saveTerm *= 1.25f * normalisedFatigue; - float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99()); + float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99(prng)); int element = ESM::MagicEffect::FireDamage; if (i == 1) @@ -444,7 +446,8 @@ namespace MWMechanics MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(isWerewolf) { - const ESM::Sound *sound = store.get().searchRandom("WolfHit"); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound *sound = store.get().searchRandom("WolfHit", prng); if(sound) sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f); } diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 426a66ecf2..82c5236ca4 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -52,7 +52,8 @@ namespace MWMechanics continue; int x = static_cast(fDiseaseXferChance * 100 * resist); - if (Misc::Rng::rollDice(10000) < x) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::rollDice(10000, prng) < x) { // Contracted disease! actor.getClass().getCreatureStats(actor).getSpells().add(spell); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index a1870cbdb0..7ed58f0d25 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -76,7 +76,8 @@ namespace MWMechanics if(mSelfEnchanting) { - if(getEnchantChance() <= (Misc::Rng::roll0to99())) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if(getEnchantChance() <= (Misc::Rng::roll0to99(prng))) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index a1eb958ed1..be5c5962bb 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -19,7 +19,7 @@ namespace MWMechanics { /// @return ID of resulting item, or empty if none - inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Generator& prng = Misc::Rng::getGenerator()) + inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Generator& prng) { const std::vector& items = levItem->mList; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 658c5c85c6..8cf6126cba 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -651,7 +651,8 @@ namespace MWMechanics float x = 0; float y = 0; - int roll = Misc::Rng::roll0to99(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); if (type == PT_Admire) { @@ -1570,8 +1571,8 @@ namespace MWMechanics } float target = x - y; - - return (Misc::Rng::roll0to99() >= target); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + return (Misc::Rng::roll0to99(prng) >= target); } void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp index 038753f7d1..1b638eadd2 100644 --- a/apps/openmw/mwmechanics/pickpocket.cpp +++ b/apps/openmw/mwmechanics/pickpocket.cpp @@ -41,7 +41,8 @@ namespace MWMechanics int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get() .find("iPickMaxChance")->mValue.getInteger(); - int roll = Misc::Rng::roll0to99(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); if (t < pcSneak / iPickMinChance) { return (roll > int(pcSneak / iPickMinChance)); diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index 51c78e1e3b..1f92466e04 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -49,7 +49,8 @@ bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem) intelligenceTerm = 1; float x = (player.getClass().getSkill(player, ESM::Skill::Enchant) + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); - int roll = Misc::Rng::roll0to99(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); if (roll < x) { std::string soul = gem.getCellRef().getSoul(); diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 81886ed9b0..ac6cc5b414 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -44,7 +44,8 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm; - int roll = Misc::Rng::roll0to99(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); if (roll <= x) { int y = static_cast(fRepairAmountMult * toolQuality * roll); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index e642a7bb4b..bfb48dd754 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -54,7 +54,8 @@ namespace MWMechanics resultMessage = "#{sLockImpossible}"; else { - if (Misc::Rng::roll0to99() <= x) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) <= x) { lock.getCellRef().unlock(); resultMessage = "#{sLockSuccess}"; @@ -98,7 +99,8 @@ namespace MWMechanics resultMessage = "#{sTrapImpossible}"; else { - if (Misc::Rng::roll0to99() <= x) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) <= x) { trap.getCellRef().setTrap(""); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index c99eee43d5..b92cbc74fb 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -87,7 +87,8 @@ namespace MWMechanics : ESM::MagicEffect::ResistBlightDisease; float x = target.getClass().getCreatureStats(target).getMagicEffects().get(requiredResistance).getMagnitude(); - if (Misc::Rng::roll0to99() <= x) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) <= x) { // Fully resisted, show message if (target == getPlayer()) @@ -339,7 +340,8 @@ namespace MWMechanics // Check success float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false); - if (Misc::Rng::roll0to99() >= successChance) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + if (Misc::Rng::roll0to99(prng) >= successChance) { if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); @@ -403,7 +405,8 @@ namespace MWMechanics + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) * creatureStats.getFatigueTerm(); - int roll = Misc::Rng::roll0to99(); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); if (roll > x) { // "X has no effect on you" diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index befe182cb5..ae18c6ae0c 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -35,7 +35,8 @@ namespace { if(effect.mMinMagnitude == effect.mMaxMagnitude) return effect.mMinMagnitude; - return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1, prng); } void modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, ESM::MagicEffect::Effects creatureEffect, MWMechanics::CreatureStats::AiSetting setting, float magnitude, bool& invalid) @@ -305,6 +306,7 @@ namespace bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f; if(canReflect || canAbsorb) { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); for(const auto& activeParam : stats.getActiveSpells()) { for(const auto& activeEffect : activeParam.getEffects()) @@ -313,14 +315,14 @@ namespace continue; if(activeEffect.mEffectId == ESM::MagicEffect::Reflect) { - if(canReflect && Misc::Rng::roll0to99() < activeEffect.mMagnitude) + if(canReflect && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) { return MWMechanics::MagicApplicationResult::REFLECTED; } } else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption) { - if(canAbsorb && Misc::Rng::roll0to99() < activeEffect.mMagnitude) + if(canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) { absorbSpell(spellParams.getId(), caster, target); return MWMechanics::MagicApplicationResult::REMOVED; @@ -405,8 +407,11 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co if(params.getType() == ESM::ActiveSpells::Type_Temporary) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(params.getId()); - if(spell && spell->mData.mType == ESM::Spell::ST_Spell) - return Misc::Rng::roll0to99() < magnitude; + if (spell && spell->mData.mType == ESM::Spell::ST_Spell) + { + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + return Misc::Rng::roll0to99(prng) < magnitude; + } } return false; }, target); diff --git a/apps/openmw/mwmechanics/spellresistance.cpp b/apps/openmw/mwmechanics/spellresistance.cpp index 1edf140915..bb39255b43 100644 --- a/apps/openmw/mwmechanics/spellresistance.cpp +++ b/apps/openmw/mwmechanics/spellresistance.cpp @@ -51,7 +51,8 @@ namespace MWMechanics if (castChance > 0) x *= 50 / castChance; - float roll = Misc::Rng::rollClosedProbability() * 100; + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + float roll = Misc::Rng::rollClosedProbability(prng) * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) roll -= resistance; diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index d9ec66bab2..a40b745d21 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -57,7 +57,8 @@ namespace MWMechanics + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); - int roll = Misc::Rng::rollDice(100) + 1; + 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) diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 01f59fd69c..40ad3b7835 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -61,7 +61,8 @@ namespace } else { - std::string itemId = MWMechanics::getLevelledItem(itemPtr.get()->mBase, false); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + std::string itemId = MWMechanics::getLevelledItem(itemPtr.get()->mBase, false, prng); if (itemId.empty()) return; MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), itemId, 1); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 0c5a47e029..38da34201b 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -111,7 +111,8 @@ namespace MWScript throw std::runtime_error ( "random: argument out of range (Don't be so negative!)"); - runtime.push (static_cast(::Misc::Rng::rollDice(limit))); // [o, limit) + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + runtime.push (static_cast(::Misc::Rng::rollDice(limit, prng))); // [o, limit) } }; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c8cd53a331..50a3a48ad8 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -466,6 +466,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str case ESM::REC_LEVI: case ESM::REC_CREA: case ESM::REC_CONT: + case ESM::REC_RAND: MWBase::Environment::get().getWorld()->readRecord(reader, n.toInt(), contentFileMap); break; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index ac78114932..34a2b49c7b 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -333,7 +333,8 @@ namespace MWWorld if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfItem"); + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + const ESM::Sound *sound = store.get().searchRandom("WolfItem", prng); std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 1951f0708e..92c28bbcb5 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -221,7 +221,7 @@ namespace MWWorld virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const; ///< @return true if the two specified objects can stack with each other - void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Generator& seed = Misc::Rng::getGenerator()); + void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Generator& seed); ///< Insert items into *this. void fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index d35746dfff..095c40697e 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -125,7 +125,7 @@ namespace MWWorld return (dit != mDynamic.end()); } template - const T *Store::searchRandom(const std::string &id) const + const T *Store::searchRandom(const std::string &id, Misc::Rng::Generator& prng) const { std::vector results; std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results), @@ -134,7 +134,7 @@ namespace MWWorld return Misc::StringUtils::ciCompareLen(id, item->mId, id.size()) == 0; }); if(!results.empty()) - return results[Misc::Rng::rollDice(results.size())]; + return results[Misc::Rng::rollDice(results.size(), prng)]; return nullptr; } template diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 1ec51ad5fd..bcd744c23c 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -10,6 +10,7 @@ #include #include +#include #include "../mwdialogue/keywordsearch.hpp" @@ -181,7 +182,7 @@ namespace MWWorld bool isDynamic(const std::string &id) const; /** Returns a random record that starts with the named ID, or nullptr if not found. */ - const T *searchRandom(const std::string &id) const; + const T *searchRandom(const std::string &id, Misc::Rng::Generator& prng) const; const T *find(const std::string &id) const; diff --git a/components/misc/timer.hpp b/components/misc/timer.hpp index 81a2ca073c..910d45bc03 100644 --- a/components/misc/timer.hpp +++ b/components/misc/timer.hpp @@ -18,7 +18,7 @@ namespace Misc : mPeriod(period), mDeviation(deviation), mTimeLeft(timeLeft) {} - TimerStatus update(float duration) + TimerStatus update(float duration, Rng::Generator& prng) { if (mTimeLeft > 0) { @@ -26,7 +26,7 @@ namespace Misc return TimerStatus::Waiting; } - mTimeLeft = Rng::deviate(mPeriod, mDeviation); + mTimeLeft = Rng::deviate(mPeriod, mDeviation, prng); return TimerStatus::Elapsed; } From 151770ccf145495de5a0b04f4e04e10eda8909c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:03:13 +0200 Subject: [PATCH 2211/2859] Separate global vs world rng functions and use custom prng --- apps/openmw/mwworld/worldimp.cpp | 22 +++++++------- components/misc/rng.cpp | 28 ++++++++++++++++-- components/misc/rng.hpp | 51 +++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index afb248bf08..16eb5f3b9b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -338,6 +338,10 @@ namespace MWWorld void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { + writer.startRecord(ESM::REC_RAND); + writer.writeHNT("RAND", mPrng.getSeed()); + writer.endRecord(ESM::REC_RAND); + // Active cells could have a dirty fog of war, sync it to the CellStore first for (CellStore* cellstore : mWorldScene->getActiveCells()) { @@ -359,12 +363,6 @@ namespace MWWorld writer.writeHNT("LEVT", mLevitationEnabled); writer.endRecord(ESM::REC_ENAB); - std::stringstream ssPrng; - ssPrng << mPrng; - writer.startRecord(ESM::REC_RAND); - writer.writeHString(ssPrng.str()); - writer.endRecord(ESM::REC_RAND); - writer.startRecord(ESM::REC_CAM_); writer.writeHNT("FIRS", isFirstPerson()); writer.endRecord(ESM::REC_CAM_); @@ -384,10 +382,11 @@ namespace MWWorld return; case ESM::REC_RAND: { - std::stringstream ssPrng; - ssPrng << reader.getHString(); - ssPrng.seekg(0); - ssPrng >> mPrng; + Misc::Rng::Generator::result_type seed{}; + reader.getHNT(seed, "RAND"); + Log(Debug::Info) << "---- World random state: " << seed << " ----"; + mPrng.seed(seed); + Misc::Rng::getGenerator().seed(seed); } break; case ESM::REC_PLAY: @@ -3723,9 +3722,10 @@ namespace MWWorld static int iNumberCreatures = mStore.get().find("iNumberCreatures")->mValue.getInteger(); int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); for (int i=0; i #include +#include + namespace Misc::Rng { static Generator sGenerator; @@ -12,29 +14,51 @@ namespace Misc::Rng return sGenerator; } + unsigned int generateDefaultSeed() + { + auto res = static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + Log(Debug::Info) << __FUNCTION__ << ": " << res; + return res; + } + void init(unsigned int seed) { sGenerator.seed(seed); } + float rollProbability() + { + return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(getGenerator()); + } + float rollProbability(Generator& prng) { return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(prng); } + float rollClosedProbability() + { + return std::uniform_real_distribution(0, 1)(getGenerator()); + } + float rollClosedProbability(Generator& prng) { return std::uniform_real_distribution(0, 1)(prng); } + int rollDice(int max) + { + return max > 0 ? std::uniform_int_distribution(0, max - 1)(getGenerator()) : 0; + } + int rollDice(int max, Generator& prng) { return max > 0 ? std::uniform_int_distribution(0, max - 1)(prng) : 0; } - unsigned int generateDefaultSeed() + float deviate(float mean, float deviation) { - return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + return std::uniform_real_distribution(mean - deviation, mean + deviation)(getGenerator()); } float deviate(float mean, float deviation, Generator& prng) diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 8d300f0913..1b7831e3c6 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -9,8 +9,41 @@ */ namespace Misc::Rng { + class Generator + { + uint32_t mState{}; - using Generator = std::mt19937; + public: + using result_type = uint32_t; + + constexpr Generator() = default; + constexpr Generator(result_type seed) : mState{ seed } {} + constexpr result_type operator()() noexcept + { + mState = (214013 * mState + 2531011); + return (mState >> 16) & max(); + } + + static constexpr result_type min() noexcept + { + return 0u; + } + + static constexpr result_type max() noexcept + { + return 0x7FFFu; + } + + void seed(result_type val) noexcept + { + mState = val; + } + + uint32_t getSeed() const noexcept + { + return mState; + } + }; Generator& getGenerator(); @@ -21,19 +54,23 @@ namespace Misc::Rng void init(unsigned int seed = generateDefaultSeed()); /// return value in range [0.0f, 1.0f) <- note open upper range. - float rollProbability(Generator& prng = getGenerator()); + float rollProbability(); + float rollProbability(Generator& prng); /// return value in range [0.0f, 1.0f] <- note closed upper range. - float rollClosedProbability(Generator& prng = getGenerator()); + float rollClosedProbability(); + float rollClosedProbability(Generator& prng); /// return value in range [0, max) <- note open upper range. - int rollDice(int max, Generator& prng = getGenerator()); + int rollDice(int max); + int rollDice(int max, Generator& prng); /// return value in range [0, 99] - inline int roll0to99(Generator& prng = getGenerator()) { return rollDice(100, prng); } - - float deviate(float mean, float deviation, Generator& prng = getGenerator()); + inline int roll0to99(Generator& prng) { return rollDice(100, prng); } + inline int roll0to99() { return rollDice(100); } + float deviate(float mean, float deviation); + float deviate(float mean, float deviation, Generator& prng); } #endif From 0611a8c3a76a4113aed9c0a6629ec75db378098c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Thu, 17 Mar 2022 14:30:00 +0200 Subject: [PATCH 2212/2859] Start new game with specified seed in options --- apps/openmw/mwbase/world.hpp | 3 +++ apps/openmw/mwworld/worldimp.cpp | 10 +++++++++- apps/openmw/mwworld/worldimp.hpp | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 23e687be5a..f86f24310a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -112,6 +112,9 @@ namespace MWBase virtual ~World() {} + virtual void setRandomSeed(uint32_t seed) = 0; + ///< \param seed The seed used when starting a new game. + virtual void startNewGame (bool bypass) = 0; ///< \param bypass Bypass regular game start. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 16eb5f3b9b..fb48707a8e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -283,6 +283,9 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->updatePlayer(); mCurrentDate->setup(mGlobalVariables); + + // Initial seed. + mPrng.seed(mRandomSeed); } void World::clear() @@ -557,7 +560,12 @@ namespace MWWorld mProjectileManager->clear(); } - const ESM::Cell *World::getExterior (const std::string& cellName) const + void World::setRandomSeed(uint32_t seed) + { + mRandomSeed = seed; + } + + const ESM::Cell* World::getExterior(const std::string& cellName) const { // first try named cells const ESM::Cell *cell = mStore.get().searchExtByName (cellName); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index eb089f04d7..6eeda94ef3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -130,6 +130,8 @@ namespace MWWorld std::map mDoorStates; ///< only holds doors that are currently moving. 1 = opening, 2 = closing + uint32_t mRandomSeed{}; + // not implemented World (const World&); World& operator= (const World&); @@ -194,6 +196,8 @@ namespace MWWorld virtual ~World(); + void setRandomSeed(uint32_t seed) override; + void startNewGame (bool bypass) override; ///< \param bypass Bypass regular game start. From f8f3bb242188fc433fba71550ce9fba5b7e18a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Thu, 17 Mar 2022 17:35:34 +0200 Subject: [PATCH 2213/2859] Use std::minstd_rand and split serialization from save/load --- apps/openmw/mwworld/worldimp.cpp | 9 +++---- components/misc/rng.cpp | 19 +++++++++++++++ components/misc/rng.hpp | 40 ++++---------------------------- 3 files changed, 27 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index fb48707a8e..6517694f15 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -342,7 +342,7 @@ namespace MWWorld void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { writer.startRecord(ESM::REC_RAND); - writer.writeHNT("RAND", mPrng.getSeed()); + writer.writeHNOString("RAND", Misc::Rng::serialize(mPrng)); writer.endRecord(ESM::REC_RAND); // Active cells could have a dirty fog of war, sync it to the CellStore first @@ -385,11 +385,8 @@ namespace MWWorld return; case ESM::REC_RAND: { - Misc::Rng::Generator::result_type seed{}; - reader.getHNT(seed, "RAND"); - Log(Debug::Info) << "---- World random state: " << seed << " ----"; - mPrng.seed(seed); - Misc::Rng::getGenerator().seed(seed); + auto data = reader.getHNOString("RAND"); + Misc::Rng::deserialize(data, mPrng); } break; case ESM::REC_PLAY: diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index b44c6c2785..01044040a0 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -14,6 +15,23 @@ namespace Misc::Rng return sGenerator; } + std::string serialize(const Generator& prng) + { + std::stringstream ss; + ss << prng; + + return ss.str(); + } + + void deserialize(std::string_view data, Generator& prng) + { + std::stringstream ss; + ss << data; + + ss.seekg(0); + ss >> prng; + } + unsigned int generateDefaultSeed() { auto res = static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); @@ -65,4 +83,5 @@ namespace Misc::Rng { return std::uniform_real_distribution(mean - deviation, mean + deviation)(prng); } + } diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 1b7831e3c6..d7ab7f3e7a 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -3,50 +3,20 @@ #include #include +#include /* Provides central implementation of the RNG logic */ namespace Misc::Rng { - class Generator - { - uint32_t mState{}; - - public: - using result_type = uint32_t; - - constexpr Generator() = default; - constexpr Generator(result_type seed) : mState{ seed } {} - constexpr result_type operator()() noexcept - { - mState = (214013 * mState + 2531011); - return (mState >> 16) & max(); - } - - static constexpr result_type min() noexcept - { - return 0u; - } - - static constexpr result_type max() noexcept - { - return 0x7FFFu; - } - - void seed(result_type val) noexcept - { - mState = val; - } - - uint32_t getSeed() const noexcept - { - return mState; - } - }; + using Generator = std::minstd_rand; Generator& getGenerator(); + std::string serialize(const Generator& prng); + void deserialize(std::string_view data, Generator& prng); + /// returns default seed for RNG unsigned int generateDefaultSeed(); From 8103ff2e6f2f8f30783db13ed095e50a8d50fdf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 21 Mar 2022 10:01:56 +0200 Subject: [PATCH 2214/2859] Make the CI happy --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 1a674d444a..e532e71478 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -43,8 +43,8 @@ namespace MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : mTypeId(typeId), - mReaction(MWBase::Environment::get().getWorld()->getPrng()), mOptions(options), + mReaction(MWBase::Environment::get().getWorld()->getPrng()), mTargetActorRefId(""), mTargetActorId(-1), mRotateOnTheRunChecks(0), diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index ebaff54f20..6cfe1e5fcd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -107,6 +107,7 @@ namespace MWMechanics } AiWanderStorage::AiWanderStorage() : + mReaction(MWBase::Environment::get().getWorld()->getPrng()), mState(Wander_ChooseAction), mIsWanderingManually(false), mCanWanderAlongPathGrid(true), @@ -116,8 +117,7 @@ namespace MWMechanics mAllowedNodes(), mTrimCurrentNode(false), mCheckIdlePositionTimer(0), - mStuckCount(0), - mReaction(MWBase::Environment::get().getWorld()->getPrng()) + mStuckCount(0) { } From 5df76f50518dfa781cd944339427fc4407631946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 21 Mar 2022 10:33:22 +0200 Subject: [PATCH 2215/2859] Remove unnecessary debug output --- components/misc/rng.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index 01044040a0..c1ed4ed95b 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -35,7 +35,6 @@ namespace Misc::Rng unsigned int generateDefaultSeed() { auto res = static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); - Log(Debug::Info) << __FUNCTION__ << ": " << res; return res; } From 769be88d439be75b6667a7612cb0428e0ac0e577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 21 Mar 2022 10:41:03 +0200 Subject: [PATCH 2216/2859] Adjust DetourNavigatorNavigatorTest to the new prng values --- apps/openmw_test_suite/detournavigator/navigator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 50bf1fe735..6cf53dd6e2 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -820,12 +820,12 @@ namespace const auto result = findRandomPointAroundCircle(*mNavigator, mAgentHalfExtents, mStart, 100.0, Flag_walk); - ASSERT_THAT(result, Optional(Vec3fEq(69.6253509521484375, 531.29852294921875, -2.6667339801788330078125))) + ASSERT_THAT(result, Optional(Vec3fEq(70.35845947265625, 335.592041015625, -2.6667339801788330078125))) << (result ? *result : osg::Vec3f()); const auto distance = (*result - mStart).length(); - EXPECT_FLOAT_EQ(distance, 73.536231994628906) << distance; + EXPECT_FLOAT_EQ(distance, 125.80865478515625) << distance; } TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) From 26c7e308edd21c97e0fda8432d66d91c35f9b1b6 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 21 Mar 2022 22:00:32 -0700 Subject: [PATCH 2217/2859] mygui bindings for color and alpha --- components/lua_ui/image.cpp | 2 ++ components/lua_ui/widget.cpp | 2 ++ docs/source/reference/lua-scripting/widgets/image.rst | 3 +++ docs/source/reference/lua-scripting/widgets/widget.rst | 8 ++++++++ 4 files changed, 15 insertions(+) diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 7eeca1dc0e..6bcc4a5846 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -68,6 +68,8 @@ namespace LuaUi atlasCoord.height = textureSize.height; setImageCoord(atlasCoord); + setColour(propertyValue("color", MyGUI::Colour(1,1,1,1))); + WidgetExtension::updateProperties(); } } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index a11d3db852..74ac8640e0 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -259,6 +259,8 @@ namespace LuaUi mAnchor = propertyValue("anchor", MyGUI::FloatSize()); mWidget->setVisible(propertyValue("visible", true)); mWidget->setPointer(propertyValue("pointer", std::string("arrow"))); + mWidget->setAlpha(propertyValue("alpha", 1.f)); + mWidget->setInheritsAlpha(propertyValue("inheritAlpha", true)); } void WidgetExtension::updateChildrenCoord() diff --git a/docs/source/reference/lua-scripting/widgets/image.rst b/docs/source/reference/lua-scripting/widgets/image.rst index bd68687cfe..c461a69a65 100644 --- a/docs/source/reference/lua-scripting/widgets/image.rst +++ b/docs/source/reference/lua-scripting/widgets/image.rst @@ -20,3 +20,6 @@ Properties * - tileV - boolean (false) - Tile the texture vertically + * - color + - util.color (1, 1, 1) + - Modulate constant color with the color of the image texture. diff --git a/docs/source/reference/lua-scripting/widgets/widget.rst b/docs/source/reference/lua-scripting/widgets/widget.rst index d114d0957b..99596a6b9b 100644 --- a/docs/source/reference/lua-scripting/widgets/widget.rst +++ b/docs/source/reference/lua-scripting/widgets/widget.rst @@ -35,6 +35,14 @@ Properties * - propagateEvents - boolean (true) - Allows base widget events to propagate to the widget's parent. + * - alpha + - number (1.0) + - | Set the opacity of the widget and its contents. + | If `inheritAlpha` is set to `true`, this becomes the maximum alpha value the widget can take. + * - inheritAlpha + - boolean (true) + - | Modulate `alpha` with parents `alpha`. + | If the parent has `inheritAlpha` set to `true`, the value after modulating is passed to the child. .. TODO: document the mouse pointer property, when API for reading / adding pointer types is available From b3e0275e89171f8d927df81ceddc1698ce626a44 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Tue, 22 Mar 2022 05:41:26 +0000 Subject: [PATCH 2218/2859] Lua UI API: Let image manager handle missing textures --- components/lua_ui/resources.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/components/lua_ui/resources.cpp b/components/lua_ui/resources.cpp index 605c9ba58b..0f9890c523 100644 --- a/components/lua_ui/resources.cpp +++ b/components/lua_ui/resources.cpp @@ -7,15 +7,9 @@ namespace LuaUi { std::shared_ptr ResourceManager::registerTexture(TextureData data) { - std::string normalizedPath = mVfs->normalizeFilename(data.mPath); - if (!mVfs->exists(normalizedPath)) - { - auto error = Misc::StringUtils::format("Texture with path \"%s\" doesn't exist", data.mPath); - throw std::logic_error(error); - } - data.mPath = normalizedPath; - - TextureResources& list = mTextures[normalizedPath]; + data.mPath = mVfs->normalizeFilename(data.mPath); + + TextureResources& list = mTextures[data.mPath]; list.push_back(std::make_shared(data)); return list.back(); } From d7de17a1ac44084ecdd9ecea526563198143c7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:09:36 +0200 Subject: [PATCH 2219/2859] Add a comment explaining the pick of the prng --- components/misc/rng.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index d7ab7f3e7a..5663b86a02 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -10,6 +10,7 @@ */ namespace Misc::Rng { + /// The use of a rather minimalistic prng is preferred to avoid saving a lot of state in the save game. using Generator = std::minstd_rand; Generator& getGenerator(); From 5d1fe6c2bc1ba2a58e270cb29bdc643660c100b0 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 22 Mar 2022 17:59:53 +0100 Subject: [PATCH 2220/2859] Add layer size, make layers API more flexible --- apps/openmw/mwlua/uibindings.cpp | 59 ++++++++++++++------- components/lua_ui/layers.cpp | 29 +++++++++++ components/lua_ui/layers.hpp | 89 ++++++++++++++++++-------------- files/lua_api/openmw/ui.lua | 22 ++++++-- 4 files changed, 136 insertions(+), 63 deletions(-) create mode 100644 components/lua_ui/layers.cpp diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 4c26c904f3..fdad57b13d 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "context.hpp" #include "actions.hpp" @@ -83,36 +84,33 @@ namespace MWLua class InsertLayerAction final : public Action { public: - InsertLayerAction(std::string_view name, std::string_view afterName, - LuaUi::Layers::Options options, LuaUtil::LuaState* state) + InsertLayerAction(std::string_view name, size_t index, + LuaUi::Layer::Options options, LuaUtil::LuaState* state) : Action(state) , mName(name) - , mAfterName(afterName) + , mIndex(index) , mOptions(options) {} void apply(WorldView&) const override { - size_t index = LuaUi::Layers::indexOf(mAfterName); - if (index == LuaUi::Layers::size()) - throw std::logic_error(std::string("Layer not found")); - LuaUi::Layers::insert(index, mName, mOptions); + LuaUi::Layer::insert(mIndex, mName, mOptions); } std::string toString() const override { std::string result("Insert UI layer \""); result += mName; - result += "\" after \""; - result += mAfterName; + result += "\" at \""; + result += mIndex; result += "\""; return result; } private: std::string mName; - std::string mAfterName; - LuaUi::Layers::Options mOptions; + size_t mIndex; + LuaUi::Layer::Options mOptions; }; // Lua arrays index from 1 @@ -227,37 +225,58 @@ namespace MWLua return element; }; + auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); + uiLayer["name"] = sol::property([](LuaUi::Layer& self) { return self.name(); }); + uiLayer["size"] = sol::property([](LuaUi::Layer& self) { return self.size(); }); + uiLayer[sol::meta_function::to_string] = [](LuaUi::Layer& self) + { + return Misc::StringUtils::format("UiLayer(%s)", self.name()); + }; + sol::table layers = context.mLua->newTable(); layers[sol::meta_function::length] = []() { - return LuaUi::Layers::size(); + return LuaUi::Layer::count(); }; layers[sol::meta_function::index] = [](size_t index) { index = fromLuaIndex(index); - return LuaUi::Layers::at(index); + return LuaUi::Layer(index); }; layers["indexOf"] = [](std::string_view name) -> sol::optional { - size_t index = LuaUi::Layers::indexOf(name); - if (index == LuaUi::Layers::size()) + size_t index = LuaUi::Layer::indexOf(name); + if (index == LuaUi::Layer::count()) return sol::nullopt; else return toLuaIndex(index); }; layers["insertAfter"] = [context](std::string_view afterName, std::string_view name, const sol::object& opt) { - LuaUi::Layers::Options options; + LuaUi::Layer::Options options; + options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); + size_t index = LuaUi::Layer::indexOf(afterName); + if (index == LuaUi::Layer::count()) + throw std::logic_error(std::string("Layer not found")); + index++; + context.mLuaManager->addAction(std::make_unique(name, index, options, context.mLua)); + }; + layers["insertBefore"] = [context](std::string_view beforename, std::string_view name, const sol::object& opt) + { + LuaUi::Layer::Options options; options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); - context.mLuaManager->addAction(std::make_unique(name, afterName, options, context.mLua)); + size_t index = LuaUi::Layer::indexOf(beforename); + if (index == LuaUi::Layer::count()) + throw std::logic_error(std::string("Layer not found")); + context.mLuaManager->addAction(std::make_unique(name, index, options, context.mLua)); }; { auto pairs = [layers](const sol::object&) { - auto next = [](const sol::table& l, size_t i) -> sol::optional> + auto next = [](const sol::table& l, size_t i) -> sol::optional> { - if (i < LuaUi::Layers::size()) - return std::make_tuple(i + 1, LuaUi::Layers::at(i)); + if (i < LuaUi::Layer::count()) + return std::make_tuple(i + 1, LuaUi::Layer(i)); else return sol::nullopt; }; diff --git a/components/lua_ui/layers.cpp b/components/lua_ui/layers.cpp new file mode 100644 index 0000000000..645c44f69f --- /dev/null +++ b/components/lua_ui/layers.cpp @@ -0,0 +1,29 @@ +#include "layers.hpp" + +#include + +namespace LuaUi +{ + size_t Layer::indexOf(std::string_view name) + { + for (size_t i = 0; i < count(); i++) + if (at(i)->getName() == name) + return i; + return count(); + } + + void Layer::insert(size_t index, std::string_view name, Options options) + { + if (index > count()) + throw std::logic_error("Invalid layer index"); + if (indexOf(name) < count()) + Log(Debug::Error) << "Layer \"" << name << "\" already exists"; + else + { + auto layer = MyGUI::LayerManager::getInstance() + .createLayerAt(std::string(name), "OverlappedLayer", index); + auto overlappedLayer = dynamic_cast(layer); + overlappedLayer->setPick(options.mInteractive); + } + } +} diff --git a/components/lua_ui/layers.hpp b/components/lua_ui/layers.hpp index 6fe7fc9c16..bb07e13c69 100644 --- a/components/lua_ui/layers.hpp +++ b/components/lua_ui/layers.hpp @@ -6,50 +6,63 @@ #include #include +#include namespace LuaUi { - namespace Layers + // this wrapper is necessary, because the MyGUI LayerManager + // stores layers in a vector and their indices could change + class Layer { - struct Options { - bool mInteractive; - }; - - size_t size() - { - return MyGUI::LayerManager::getInstance().getLayerCount(); - } - - std::string at(size_t index) - { - if (index >= size()) - throw std::logic_error("Invalid layer index"); - return MyGUI::LayerManager::getInstance().getLayer(index)->getName(); - } - - size_t indexOf(std::string_view name) - { - for (size_t i = 0; i < size(); i++) - if (at(i) == name) - return i; - return size(); - } - - void insert(size_t index, std::string_view name, Options options) - { - if (index > size()) - throw std::logic_error("Invalid layer index"); - if (indexOf(name) < size()) - Log(Debug::Error) << "Layer \"" << name << "\" already exists"; - else + public: + Layer(size_t index) + : mName(at(index)->getName()) + , mCachedIndex(index) + {} + + const std::string& name() const noexcept { return mName; }; + const osg::Vec2f size() + { + MyGUI::ILayer* p = refresh(); + MyGUI::IntSize size = p->getSize(); + return osg::Vec2f(size.width, size.height); + } + + struct Options + { + bool mInteractive; + }; + + static size_t count() + { + return MyGUI::LayerManager::getInstance().getLayerCount(); + } + + static size_t indexOf(std::string_view name); + + static void insert(size_t index, std::string_view name, Options options); + + private: + static MyGUI::ILayer* at(size_t index) + { + if (index >= count()) + throw std::logic_error("Invalid layer index"); + return MyGUI::LayerManager::getInstance().getLayer(index); + } + + MyGUI::ILayer* refresh() { - auto layer = MyGUI::LayerManager::getInstance() - .createLayerAt(std::string(name), "OverlappedLayer", index); - auto overlappedLayer = dynamic_cast(layer); - overlappedLayer->setPick(options.mInteractive); + MyGUI::ILayer* p = at(mCachedIndex); + if (p->getName() != mName) + { + mCachedIndex = indexOf(mName); + p = at(mCachedIndex); + } + return p; } - } - } + std::string mName; + size_t mCachedIndex; + }; } #endif // OPENMW_LUAUI_LAYERS diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 57be0da501..0ef35a61a5 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -77,15 +77,20 @@ -- @field #Content content Optional @{openmw.ui#Content} of children layouts --- --- Layers. Implements [iterables#List](iterables.html#List) of #string. +-- @type Layer +-- @field #string name Name of the layer +-- @field openmw.util#vector2 size Size of the layer in pixels + +--- +-- Layers. Implements [iterables#List](iterables.html#List) of #Layer. -- @type Layers --- @list <#string> +-- @list <#Layer> -- @usage -- ui.layers.insertAfter('HUD', 'NewLayer', { interactive = true }) --- local fourthLayerName = ui.layers[4] +-- local fourthLayer = ui.layers[4] -- local windowsIndex = ui.layers.indexOf('Windows') --- for i, name in ipairs(ui.layers) do --- print('layer', i, name) +-- for i, layer in ipairs(ui.layers) do +-- print('layer', i, layer.name, layer.size) -- end --- @@ -101,6 +106,13 @@ -- @param #string name Name of the new layer -- @param #table options Table with a boolean `interactive` field (default is true). Layers with interactive = false will ignore all mouse interactions. +--- +-- Creates a layer and inserts it before another layer (shifts indexes of some other layers). +-- @function [parent=#Layers] insertBefore +-- @param #string beforeName Name of the layer before which the new layer will be inserted +-- @param #string name Name of the new layer +-- @param #table options Table with a boolean `interactive` field (default is true). Layers with interactive = false will ignore all mouse interactions. + --- -- Content. An array-like container, which allows to reference elements by their name. -- Implements [iterables#List](iterables.html#List) of #Layout and [iterables#Map](iterables.html#Map) of #string to #Layout. From dcdba227f70fc6304687063cdf157ce519c116c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 25 Mar 2022 20:42:01 +0200 Subject: [PATCH 2221/2859] Use vector for mUsedPowers for deterministic order --- apps/openmw/mwmechanics/spells.cpp | 12 +++++++++--- apps/openmw/mwmechanics/spells.hpp | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 133fc5e931..09390425f6 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -185,13 +185,19 @@ namespace MWMechanics bool Spells::canUsePower(const ESM::Spell* spell) const { - const auto it = mUsedPowers.find(spell); + const auto it = std::find_if(std::begin(mUsedPowers), std::end(mUsedPowers), [&](auto& pair) { return pair.first == spell; }); return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::usePower(const ESM::Spell* spell) { - mUsedPowers[spell] = MWBase::Environment::get().getWorld()->getTimeStamp(); + // Updates or inserts a new entry with the current timestamp. + const auto it = std::find_if(std::begin(mUsedPowers), std::end(mUsedPowers), [&](auto& pair) { return pair.first == spell; }); + const auto timestamp = MWBase::Environment::get().getWorld()->getTimeStamp(); + if (it == mUsedPowers.end()) + mUsedPowers.emplace_back(spell, timestamp); + else + it->second = timestamp; } void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) @@ -223,7 +229,7 @@ namespace MWMechanics const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (!spell) continue; - mUsedPowers[spell] = MWWorld::TimeStamp(it->second); + mUsedPowers.emplace_back(spell, MWWorld::TimeStamp(it->second)); } // Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and only in old saves. Convert data to the new approach. diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 29f505d369..89e24c4eb5 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -34,7 +34,7 @@ namespace MWMechanics // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; - std::map mUsedPowers; + std::vector> mUsedPowers; bool hasDisease(const ESM::Spell::SpellType type) const; From edca5ac0b879b200f670ae94b2f56db2b46226aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 25 Mar 2022 20:47:43 +0200 Subject: [PATCH 2222/2859] Rename hasDisease to hasSpellType and refactor function --- apps/openmw/mwmechanics/spells.cpp | 18 ++++++++---------- apps/openmw/mwmechanics/spells.hpp | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 09390425f6..e3e8e849e4 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -112,25 +112,23 @@ namespace MWMechanics return mSelectedSpell; } - bool Spells::hasDisease(const ESM::Spell::SpellType type) const + bool Spells::hasSpellType(const ESM::Spell::SpellType type) const { - for (const auto spell : mSpells) - { - if (spell->mData.mType == type) - return true; - } - - return false; + auto it = std::find_if(std::begin(mSpells), std::end(mSpells), [=](const ESM::Spell* spell) + { + return spell->mData.mType == type; + }); + return it != std::end(mSpells); } bool Spells::hasCommonDisease() const { - return hasDisease(ESM::Spell::ST_Disease); + return hasSpellType(ESM::Spell::ST_Disease); } bool Spells::hasBlightDisease() const { - return hasDisease(ESM::Spell::ST_Blight); + return hasSpellType(ESM::Spell::ST_Blight); } void Spells::purge(const SpellFilter& filter) diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 89e24c4eb5..25dab0c9d4 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -36,7 +36,7 @@ namespace MWMechanics std::vector> mUsedPowers; - bool hasDisease(const ESM::Spell::SpellType type) const; + bool hasSpellType(const ESM::Spell::SpellType type) const; using SpellFilter = bool (*)(const ESM::Spell*); void purge(const SpellFilter& filter); From 93d195646ce7a9d60a319c6f70fc9fa30fa467e7 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 25 Mar 2022 20:03:13 +0000 Subject: [PATCH 2223/2859] Lua stats --- apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwlua/actions.cpp | 9 + apps/openmw/mwlua/actions.hpp | 10 + apps/openmw/mwlua/localscripts.cpp | 7 + apps/openmw/mwlua/localscripts.hpp | 24 ++ apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/stats.cpp | 369 +++++++++++++++++++++++++++++ apps/openmw/mwlua/stats.hpp | 12 + apps/openmw/mwlua/types/actor.cpp | 3 + apps/openmw/mwlua/types/npc.cpp | 11 + apps/openmw/mwlua/types/types.cpp | 2 +- apps/openmw/mwlua/types/types.hpp | 1 + files/lua_api/openmw/types.lua | 282 ++++++++++++++++++++++ 13 files changed, 732 insertions(+), 4 deletions(-) create mode 100644 apps/openmw/mwlua/stats.cpp create mode 100644 apps/openmw/mwlua/stats.hpp create mode 100644 apps/openmw/mwlua/types/npc.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b823674d31..3d6469fa33 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -59,8 +59,8 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings - camerabindings uibindings inputbindings nearbybindings - types/types types/door types/actor types/container types/weapon + camerabindings uibindings inputbindings nearbybindings stats + types/types types/door types/actor types/container types/weapon types/npc ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 8135d14299..404bae6c6d 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -1,5 +1,7 @@ #include "actions.hpp" +#include "localscripts.hpp" + #include #include @@ -168,4 +170,11 @@ namespace MWLua std::string(" actor=") + idToString(mActor); } + void StatUpdateAction::apply(WorldView& worldView) const + { + LObject obj(mId, worldView.getObjectRegistry()); + LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + if (scripts) + scripts->applyStatsCache(); + } } diff --git a/apps/openmw/mwlua/actions.hpp b/apps/openmw/mwlua/actions.hpp index 97da953437..30211b6e53 100644 --- a/apps/openmw/mwlua/actions.hpp +++ b/apps/openmw/mwlua/actions.hpp @@ -79,6 +79,16 @@ namespace MWLua ObjectId mActor; }; + class StatUpdateAction final : public Action + { + ObjectId mId; + public: + StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} + + void apply(WorldView& worldView) const override; + + std::string toString() const override { return "StatUpdateAction"; } + }; } #endif // MWLUA_ACTIONS_H diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 7cdb31f518..948bc1e869 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -182,4 +182,11 @@ namespace MWLua }, event); } + void LocalScripts::applyStatsCache() + { + const auto& ptr = mData.ptr(); + for (auto& [stat, value] : mData.mStatsCache) + stat(ptr, value); + mData.mStatsCache.clear(); + } } diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 10b9cd53af..b5bd8f95c8 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -26,8 +26,31 @@ namespace MWLua struct SelfObject : public LObject { + class CachedStat + { + public: + using Setter = void(*)(int, std::string_view, const MWWorld::Ptr&, const sol::object&); + private: + Setter mSetter; // Function that updates a stat's property + int mIndex; // Optional index to disambiguate the stat + std::string_view mProp; // Name of the stat's property + public: + CachedStat(Setter setter, int index, std::string_view prop) : mSetter(setter), mIndex(index), mProp(std::move(prop)) {} + + void operator()(const MWWorld::Ptr& ptr, const sol::object& object) const + { + mSetter(mIndex, mProp, ptr, object); + } + + bool operator<(const CachedStat& other) const + { + return std::tie(mSetter, mIndex, mProp) < std::tie(other.mSetter, other.mIndex, other.mProp); + } + }; + SelfObject(const LObject& obj) : LObject(obj), mIsActive(false) {} MWBase::LuaManager::ActorControls mControls; + std::map mStatsCache; bool mIsActive; }; @@ -45,6 +68,7 @@ namespace MWLua void receiveEngineEvent(const EngineEvent&); + void applyStatsCache(); protected: SelfObject mData; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index a46435b2ca..9f35866f89 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -41,7 +41,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 18; + api["API_REVISION"] = 19; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp new file mode 100644 index 0000000000..36d7cf6538 --- /dev/null +++ b/apps/openmw/mwlua/stats.cpp @@ -0,0 +1,369 @@ +#include "stats.hpp" + +#include +#include +#include +#include + +#include +#include + +#include "localscripts.hpp" +#include "luamanagerimp.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +namespace +{ + template + auto addIndexedAccessor(int index) + { + return sol::overload( + [index](MWLua::LocalScripts::SelfObject& o) { return T::create(&o, index); }, + [index](const MWLua::LObject& o) { return T::create(o, index); }, + [index](const MWLua::GObject& o) { return T::create(o, index); } + ); + } + + template + void addProp(const MWLua::Context& context, sol::usertype& type, std::string_view prop, G getter, bool readOnly = false) + { + if(readOnly) + type[prop] = sol::property([=](const T& stat) { return stat.get(context, prop, getter); }); + else + type[prop] = sol::property( + [=](const T& stat) { return stat.get(context, prop, getter); }, + [=](const T& stat, const sol::object& value) { stat.cache(context, prop, value); }); + } + + using SelfObject = MWLua::LocalScripts::SelfObject; + using StatObject = std::variant; + + const MWLua::Object* getObject(const StatObject& obj) + { + return std::visit([] (auto&& variant) -> const MWLua::Object* + { + using T = std::decay_t; + if constexpr(std::is_same_v) + return variant; + else if constexpr(std::is_same_v) + return &variant; + else if constexpr(std::is_same_v) + return &variant; + }, obj); + } + + template + sol::object getValue(const MWLua::Context& context, const StatObject& obj, SelfObject::CachedStat::Setter setter, int index, std::string_view prop, G getter) + { + return std::visit([&] (auto&& variant) + { + using T = std::decay_t; + if constexpr(std::is_same_v) + { + auto it = variant->mStatsCache.find({ setter, index, prop }); + if(it != variant->mStatsCache.end()) + return it->second; + return sol::make_object(context.mLua->sol(), getter(variant)); + } + else if constexpr(std::is_same_v) + return sol::make_object(context.mLua->sol(), getter(&variant)); + else if constexpr(std::is_same_v) + return sol::make_object(context.mLua->sol(), getter(&variant)); + }, obj); + } +} + +namespace MWLua +{ + class LevelStat + { + StatObject mObject; + + LevelStat(StatObject object) : mObject(std::move(object)) {} + public: + sol::object getCurrent(const Context& context) const + { + return getValue(context, mObject, &LevelStat::setValue, 0, "current", [](const MWLua::Object* obj) + { + const auto& ptr = obj->ptr(); + return ptr.getClass().getCreatureStats(ptr).getLevel(); + }); + } + + void setCurrent(const Context& context, const sol::object& value) const + { + SelfObject* obj = std::get(mObject); + if(obj->mStatsCache.empty()) + context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); + obj->mStatsCache[SelfObject::CachedStat{&LevelStat::setValue, 0, "current"}] = value; + } + + sol::object getProgress(const Context& context) const + { + const auto& ptr = getObject(mObject)->ptr(); + if(!ptr.getClass().isNpc()) + return sol::nil; + return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress()); + } + + static std::optional create(StatObject object, int index) + { + if(!getObject(object)->ptr().getClass().isActor()) + return {}; + return LevelStat{std::move(object)}; + } + + static void setValue(int, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + if(prop == "current") + stats.setLevel(value.as()); + } + }; + + class DynamicStat + { + StatObject mObject; + int mIndex; + + DynamicStat(StatObject object, int 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, &DynamicStat::setValue, mIndex, prop, [=](const MWLua::Object* obj) + { + const auto& ptr = obj->ptr(); + return (ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).*getter)(); + }); + } + + static std::optional create(StatObject object, int index) + { + if(!getObject(object)->ptr().getClass().isActor()) + return {}; + return DynamicStat{std::move(object), index}; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = std::get(mObject); + if(obj->mStatsCache.empty()) + context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); + obj->mStatsCache[SelfObject::CachedStat{&DynamicStat::setValue, mIndex, prop}] = value; + } + + static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getDynamic(index); + float floatValue = value.as(); + if(prop == "base") + stat.setBase(floatValue); + else if(prop == "current") + stat.setCurrent(floatValue, true, true); + else if(prop == "modifier") + stat.setModifier(floatValue); + stats.setDynamic(index, stat); + } + }; + + class AttributeStat + { + StatObject mObject; + int mIndex; + + AttributeStat(StatObject object, int 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, &AttributeStat::setValue, mIndex, prop, [=](const MWLua::Object* obj) + { + const auto& ptr = obj->ptr(); + return (ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).*getter)(); + }); + } + + static std::optional create(StatObject object, int index) + { + if(!getObject(object)->ptr().getClass().isActor()) + return {}; + return AttributeStat{std::move(object), index}; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = std::get(mObject); + if(obj->mStatsCache.empty()) + context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); + obj->mStatsCache[SelfObject::CachedStat{&AttributeStat::setValue, mIndex, prop}] = value; + } + + static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getAttribute(index); + float floatValue = value.as(); + if(prop == "base") + stat.setBase(floatValue); + else if(prop == "damage") + { + stat.restore(stat.getDamage()); + stat.damage(floatValue); + } + else if(prop == "modifier") + stat.setModifier(floatValue); + stats.setAttribute(index, stat); + } + }; + + class SkillStat + { + StatObject mObject; + int mIndex; + + SkillStat(StatObject object, int index) : mObject(std::move(object)), mIndex(index) {} + + static float getProgress(const MWWorld::Ptr& ptr, int index, const MWMechanics::SkillValue& stat) + { + float progress = stat.getProgress(); + if(progress != 0.f) + progress /= getMaxProgress(ptr, index, stat); + return progress; + } + + static float getMaxProgress(const MWWorld::Ptr& ptr, int index, const MWMechanics::SkillValue& stat) { + const auto& store = MWBase::Environment::get().getWorld()->getStore(); + const auto cl = store.get().find(ptr.get()->mBase->mClass); + return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(index, *cl); + } + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue(context, mObject, &SkillStat::setValue, mIndex, prop, [=](const MWLua::Object* obj) + { + const auto& ptr = obj->ptr(); + return (ptr.getClass().getNpcStats(ptr).getSkill(mIndex).*getter)(); + }); + } + + sol::object getProgress(const Context& context) const + { + return getValue(context, mObject, &SkillStat::setValue, mIndex, "progress", [=](const MWLua::Object* obj) + { + const auto& ptr = obj->ptr(); + return getProgress(ptr, mIndex, ptr.getClass().getNpcStats(ptr).getSkill(mIndex)); + }); + } + + static std::optional create(StatObject object, int index) + { + if(!getObject(object)->ptr().getClass().isNpc()) + return {}; + return SkillStat{std::move(object), index}; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = std::get(mObject); + if(obj->mStatsCache.empty()) + context.mLuaManager->addAction(std::make_unique(context.mLua, obj->id())); + obj->mStatsCache[SelfObject::CachedStat{&SkillStat::setValue, mIndex, prop}] = value; + } + + static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getNpcStats(ptr); + auto stat = stats.getSkill(index); + float floatValue = value.as(); + if(prop == "base") + stat.setBase(floatValue); + else if(prop == "damage") + { + stat.restore(stat.getDamage()); + stat.damage(floatValue); + } + else if(prop == "modifier") + stat.setModifier(floatValue); + else if(prop == "progress") + stat.setProgress(floatValue * getMaxProgress(ptr, index, stat)); + stats.setSkill(index, stat); + } + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + void addActorStatsBindings(sol::table& actor, const Context& context) + { + sol::table stats(context.mLua->sol(), sol::create); + actor["stats"] = LuaUtil::makeReadOnly(stats); + + auto levelStatT = context.mLua->sol().new_usertype("LevelStat"); + levelStatT["current"] = sol::property( + [context](const LevelStat& stat) { return stat.getCurrent(context); }, + [context](const LevelStat& stat, const sol::object& value) { stat.setCurrent(context, value); }); + levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }); + stats["level"] = addIndexedAccessor(0); + + auto dynamicStatT = context.mLua->sol().new_usertype("DynamicStat"); + addProp(context, dynamicStatT, "base", &MWMechanics::DynamicStat::getBase); + addProp(context, dynamicStatT, "current", &MWMechanics::DynamicStat::getCurrent); + addProp(context, dynamicStatT, "modifier", &MWMechanics::DynamicStat::getModifier); + sol::table dynamic(context.mLua->sol(), sol::create); + stats["dynamic"] = LuaUtil::makeReadOnly(dynamic); + dynamic["health"] = addIndexedAccessor(0); + dynamic["magicka"] = addIndexedAccessor(1); + dynamic["fatigue"] = addIndexedAccessor(2); + + auto attributeStatT = context.mLua->sol().new_usertype("AttributeStat"); + addProp(context, attributeStatT, "base", &MWMechanics::AttributeValue::getBase); + addProp(context, attributeStatT, "damage", &MWMechanics::AttributeValue::getDamage); + addProp(context, attributeStatT, "modified", &MWMechanics::AttributeValue::getModified, true); + addProp(context, attributeStatT, "modifier", &MWMechanics::AttributeValue::getModifier); + sol::table attributes(context.mLua->sol(), sol::create); + stats["attributes"] = LuaUtil::makeReadOnly(attributes); + for(int id = ESM::Attribute::Strength; id < ESM::Attribute::Length; ++id) + attributes[Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[id])] = addIndexedAccessor(id); + } + + void addNpcStatsBindings(sol::table& npc, const Context& context) + { + sol::table npcStats(context.mLua->sol(), sol::create); + sol::table baseMeta(context.mLua->sol(), sol::create); + baseMeta[sol::meta_function::index] = LuaUtil::getMutableFromReadOnly(npc["baseType"]["stats"]); + npcStats[sol::metatable_key] = baseMeta; + npc["stats"] = LuaUtil::makeReadOnly(npcStats); + + auto skillStatT = context.mLua->sol().new_usertype("SkillStat"); + addProp(context, skillStatT, "base", &MWMechanics::SkillValue::getBase); + addProp(context, skillStatT, "damage", &MWMechanics::SkillValue::getDamage); + addProp(context, skillStatT, "modified", &MWMechanics::SkillValue::getModified, true); + addProp(context, skillStatT, "modifier", &MWMechanics::SkillValue::getModifier); + skillStatT["progress"] = sol::property( + [context](const SkillStat& stat) { return stat.getProgress(context); }, + [context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); }); + sol::table skills(context.mLua->sol(), sol::create); + npcStats["skills"] = LuaUtil::makeReadOnly(skills); + for(int id = ESM::Skill::Block; id < ESM::Skill::Length; ++id) + skills[Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id])] = addIndexedAccessor(id); + } +} diff --git a/apps/openmw/mwlua/stats.hpp b/apps/openmw/mwlua/stats.hpp new file mode 100644 index 0000000000..5108b710bf --- /dev/null +++ b/apps/openmw/mwlua/stats.hpp @@ -0,0 +1,12 @@ +#ifndef MWLUA_STATS_H +#define MWLUA_STATS_H + +#include "context.hpp" + +namespace MWLua +{ + void addActorStatsBindings(sol::table& actor, const Context& context); + void addNpcStatsBindings(sol::table& npc, const Context& context); +} + +#endif diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index d620472095..321aaa20e6 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -10,6 +10,7 @@ #include "../luabindings.hpp" #include "../localscripts.hpp" #include "../luamanagerimp.hpp" +#include "../stats.hpp" namespace MWLua { @@ -132,6 +133,8 @@ namespace MWLua } context.mLuaManager->addAction(std::make_unique(context.mLua, obj.id(), std::move(eqp))); }; + + addActorStatsBindings(actor, context); } } diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp new file mode 100644 index 0000000000..8dafbe1e8a --- /dev/null +++ b/apps/openmw/mwlua/types/npc.cpp @@ -0,0 +1,11 @@ +#include "types.hpp" + +#include "../stats.hpp" + +namespace MWLua +{ + void addNpcBindings(sol::table npc, const Context& context) + { + addNpcStatsBindings(npc, context); + } +} diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index a175031058..d696fb9715 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -156,7 +156,7 @@ namespace MWLua ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA}); addType(ObjectTypeName::Creature, {ESM::REC_CREA}, ObjectTypeName::Actor); - addType(ObjectTypeName::NPC, {ESM::REC_INTERNAL_PLAYER, ESM::REC_NPC_}, ObjectTypeName::Actor); + addNpcBindings(addType(ObjectTypeName::NPC, {ESM::REC_INTERNAL_PLAYER, ESM::REC_NPC_}, ObjectTypeName::Actor), context); addType(ObjectTypeName::Player, {ESM::REC_INTERNAL_PLAYER}, ObjectTypeName::NPC); addType(ObjectTypeName::Armor, {ESM::REC_ARMO}, ObjectTypeName::Item); diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index af861c0bb4..cb345b7d01 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -28,6 +28,7 @@ namespace MWLua void addDoorBindings(sol::table door, const Context& context); void addActorBindings(sol::table actor, const Context& context); void addWeaponBindings(sol::table weapon, const Context& context); + void addNpcBindings(sol::table npc, const Context& context); } #endif // MWLUA_TYPES_H diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 917a83ee8c..b9b5df44d6 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -128,6 +128,287 @@ -- local Actor = require('openmw.types').Actor -- Actor.setEquipment(self, {}) -- unequip all +--- +-- @type LevelStat +-- @field #number current The actor's current level. +-- @field #number progress The NPC's level progress (read-only.) + +--- +-- @type DynamicStat +-- @field #number base +-- @field #number current +-- @field #number modifier + +--- +-- @type AttributeStat +-- @field #number base The actor's base attribute value. +-- @field #number damage The amount the attribute has been damaged. +-- @field #number modified The actor's current attribute value (read-only.) +-- @field #number modifier The attribute's modifier. + +--- +-- @type SkillStat +-- @field #number base The NPC's base skill value. +-- @field #number damage The amount the skill has been damaged. +-- @field #number modified The NPC's current skill value (read-only.) +-- @field #number modifier The skill's modifier. +-- @field #number progress [0-1] The NPC's skill progress. + +--- +-- @type DynamicStats + +--- +-- Health (returns @{#DynamicStat}) +-- @function [parent=#DynamicStats] health +-- @param openmw.core#GameObject actor +-- @return #DynamicStat + +--- +-- Magicka (returns @{#DynamicStat}) +-- @function [parent=#DynamicStats] magicka +-- @param openmw.core#GameObject actor +-- @return #DynamicStat + +--- +-- Fatigue (returns @{#DynamicStat}) +-- @function [parent=#DynamicStats] fatigue +-- @param openmw.core#GameObject actor +-- @return #DynamicStat + +--- +-- @type AttributeStats + +--- +-- Strength (returns @{#AttributeStat}) +-- @function [parent=#AttributeStats] strength +-- @param openmw.core#GameObject actor +-- @return #AttributeStat + +--- +-- Intelligence (returns @{#AttributeStat}) +-- @function [parent=#AttributeStats] intelligence +-- @param openmw.core#GameObject actor +-- @return #AttributeStat + +--- +-- Willpower (returns @{#AttributeStat}) +-- @function [parent=#AttributeStats] willpower +-- @param openmw.core#GameObject actor +-- @return #AttributeStat + +--- +-- Agility (returns @{#AttributeStat}) +-- @function [parent=#AttributeStats] agility +-- @param openmw.core#GameObject actor +-- @return #AttributeStat + +--- +-- Speed (returns @{#AttributeStat}) +-- @function [parent=#AttributeStats] speed +-- @param openmw.core#GameObject actor +-- @return #AttributeStat + +--- +-- Endurance (returns @{#AttributeStat}) +-- @function [parent=#AttributeStats] endurance +-- @param openmw.core#GameObject actor +-- @return #AttributeStat + +--- +-- Personality (returns @{#AttributeStat}) +-- @function [parent=#AttributeStats] personality +-- @param openmw.core#GameObject actor +-- @return #AttributeStat + +--- +-- Luck (returns @{#AttributeStat}) +-- @function [parent=#AttributeStats] luck +-- @param openmw.core#GameObject actor +-- @return #AttributeStat + +--- +-- @type SkillStats + +--- +-- Block (returns @{#SkillStat}) +-- @function [parent=#SkillStats] block +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Armorer (returns @{#SkillStat}) +-- @function [parent=#SkillStats] armorer +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Medium Armor (returns @{#SkillStat}) +-- @function [parent=#SkillStats] mediumarmor +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Heavy Armor (returns @{#SkillStat}) +-- @function [parent=#SkillStats] heavyarmor +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Blunt Weapon (returns @{#SkillStat}) +-- @function [parent=#SkillStats] bluntweapon +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Long Blade (returns @{#SkillStat}) +-- @function [parent=#SkillStats] longblade +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Axe (returns @{#SkillStat}) +-- @function [parent=#SkillStats] axe +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Spear (returns @{#SkillStat}) +-- @function [parent=#SkillStats] spear +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Athletics (returns @{#SkillStat}) +-- @function [parent=#SkillStats] athletics +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Enchant (returns @{#SkillStat}) +-- @function [parent=#SkillStats] enchant +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Destruction (returns @{#SkillStat}) +-- @function [parent=#SkillStats] destruction +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Alteration (returns @{#SkillStat}) +-- @function [parent=#SkillStats] alteration +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Illusion (returns @{#SkillStat}) +-- @function [parent=#SkillStats] illusion +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Conjuration (returns @{#SkillStat}) +-- @function [parent=#SkillStats] conjuration +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Mysticism (returns @{#SkillStat}) +-- @function [parent=#SkillStats] mysticism +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Restoration (returns @{#SkillStat}) +-- @function [parent=#SkillStats] restoration +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Alchemy (returns @{#SkillStat}) +-- @function [parent=#SkillStats] alchemy +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Unarmored (returns @{#SkillStat}) +-- @function [parent=#SkillStats] unarmored +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Security (returns @{#SkillStat}) +-- @function [parent=#SkillStats] security +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Sneak (returns @{#SkillStat}) +-- @function [parent=#SkillStats] sneak +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Acrobatics (returns @{#SkillStat}) +-- @function [parent=#SkillStats] acrobatics +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Light Armor (returns @{#SkillStat}) +-- @function [parent=#SkillStats] lightarmor +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Short Blade (returns @{#SkillStat}) +-- @function [parent=#SkillStats] shortblade +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Marksman (returns @{#SkillStat}) +-- @function [parent=#SkillStats] marksman +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Mercantile (returns @{#SkillStat}) +-- @function [parent=#SkillStats] mercantile +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Speechcraft (returns @{#SkillStat}) +-- @function [parent=#SkillStats] speechcraft +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- Hand To Hand (returns @{#SkillStat}) +-- @function [parent=#SkillStats] handtohand +-- @param openmw.core#GameObject actor +-- @return #SkillStat + +--- +-- @type ActorStats +-- @field #DynamicStats dynamic +-- @field #AttributeStats attributes + +--- +-- Level (returns @{#LevelStat}) +-- @function [parent=#ActorStats] level +-- @param openmw.core#GameObject actor +-- @return #LevelStat + +--- The actor's stats. +-- @field [parent=#Actor] #ActorStats stats + +--- +-- @type NpcStats +-- @extends ActorStats +-- @field #SkillStats skills --- @{#Item} functions (all pickable items that can be placed to an inventory or container) @@ -167,6 +448,7 @@ -- @type NPC -- @extends #Actor -- @field #Actor baseType @{#Actor} +-- @field [parent=#NPC] #NpcStats stats --- -- Whether the object is an NPC or a Player. From 6869fa18e4da0b058502900bea1e38cbc605fa3e Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 26 Mar 2022 15:16:22 +0000 Subject: [PATCH 2224/2859] Fix unsafe memory access at object.cpp --- CHANGELOG.md | 1 + apps/openmw/mwrender/objects.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5ad085e9b..67245dc1ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,6 +107,7 @@ Bug #6606: Quests with multiple IDs cannot always be restarted Bug #6655: Constant effect absorb attribute causes the game to break Bug #6670: Dialogue order is incorrect + Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index e208d7191e..7e3433bd92 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -164,7 +164,7 @@ void Objects::removeCell(const MWWorld::CellStore* store) void Objects::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) { - osg::Node* objectNode = cur.getRefData().getBaseNode(); + osg::ref_ptr objectNode = cur.getRefData().getBaseNode(); if (!objectNode) return; From 7695f03c4c825eba5ff7f96f18931601e135ff88 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 27 Mar 2022 17:20:22 +0200 Subject: [PATCH 2225/2859] Remove unused variable --- apps/openmw/mwmechanics/creaturestats.cpp | 22 ---------------------- apps/openmw/mwmechanics/creaturestats.hpp | 4 ---- 2 files changed, 26 deletions(-) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index a53caf1451..2cb66e78d5 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -597,28 +597,6 @@ namespace MWMechanics mAiSequence.readState(state.mAiSequence); mMagicEffects.readState(state.mMagicEffects); - // Rebuild the bound item cache - for(int effectId = ESM::MagicEffect::BoundDagger; effectId <= ESM::MagicEffect::BoundGloves; effectId++) - { - if(mMagicEffects.get(effectId).getMagnitude() > 0) - mBoundItems.insert(effectId); - else - { - // Check active spell effects - // We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects - auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell) - { - const auto& effects = spell.getEffects(); - return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect) - { - return effect.mEffectId == effectId; - }) != effects.end(); - }); - if(spell != mActiveSpells.end()) - mBoundItems.insert(effectId); - } - } - mSummonedCreatures = state.mSummonedCreatures; mSummonGraveyard = state.mSummonGraveyard; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 2bcb452292..46f3410912 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -261,10 +261,6 @@ namespace MWMechanics void setHitAttemptActorId(const int actorId); int getHitAttemptActorId() const; - // Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves. - // TODO: Put it somewhere else? - std::set mBoundItems; - void writeState (ESM::CreatureStats& state) const; void readState (const ESM::CreatureStats& state); From c1d700f770b5cdffebd004e25f77a02b7afdf45a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 28 Mar 2022 11:40:46 +0000 Subject: [PATCH 2226/2859] Don't reset last hit object if the ID doesn't match --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/creaturestats.cpp | 5 +++++ apps/openmw/mwmechanics/creaturestats.hpp | 1 + apps/openmw/mwscript/miscextensions.cpp | 7 ++++--- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67245dc1ff..2c665740ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ Bug #6655: Constant effect absorb attribute causes the game to break Bug #6670: Dialogue order is incorrect Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer + Bug #6682: HitOnMe doesn't fire as intended Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index a53caf1451..90817b1e2d 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -347,6 +347,11 @@ namespace MWMechanics mLastHitObject = objectid; } + void CreatureStats::clearLastHitObject() + { + mLastHitObject.clear(); + } + const std::string &CreatureStats::getLastHitObject() const { return mLastHitObject; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 2bcb452292..719b2ff65c 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -255,6 +255,7 @@ namespace MWMechanics bool getStance (Stance flag) const; void setLastHitObject(const std::string &objectid); + void clearLastHitObject(); const std::string &getLastHitObject() const; void setLastHitAttemptObject(const std::string &objectid); const std::string &getLastHitAttemptObject() const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 38da34201b..2b557fc9b6 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -977,9 +977,10 @@ namespace MWScript runtime.pop(); MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject())); - - stats.setLastHitObject(std::string()); + bool hit = ::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject()); + runtime.push(hit); + if(hit) + stats.clearLastHitObject(); } }; From f5b527e4456e45f08c3477b7615cf879cee7dab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:07:20 +0300 Subject: [PATCH 2227/2859] Restructure function isCommanded --- apps/openmw/mwmechanics/actors.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b7b83ed562..73b479dd20 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -56,14 +56,17 @@ bool isConscious(const MWWorld::Ptr& ptr) bool isCommanded(const MWWorld::Ptr& actor) { - const auto& stats = actor.getClass().getCreatureStats(actor); - for(const auto& params : stats.getActiveSpells()) + const auto& actorClass = actor.getClass(); + const auto& stats = actorClass.getCreatureStats(actor); + const bool isActorNpc = actorClass.isNpc(); + const auto level = stats.getLevel(); + for (const auto& params : stats.getActiveSpells()) { - for(const auto& effect : params.getEffects()) + for (const auto& effect : params.getEffects()) { - if(((effect.mEffectId == ESM::MagicEffect::CommandHumanoid && actor.getClass().isNpc()) - || (effect.mEffectId == ESM::MagicEffect::CommandCreature && !actor.getClass().isNpc())) - && effect.mMagnitude >= stats.getLevel()) + if (((effect.mEffectId == ESM::MagicEffect::CommandHumanoid && isActorNpc) + || (effect.mEffectId == ESM::MagicEffect::CommandCreature && !isActorNpc)) + && effect.mMagnitude >= level) return true; } } From cba51e5e1cb082ff7e6ac505524f3db1189440a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:13:40 +0300 Subject: [PATCH 2228/2859] Restructure function updateHeadTracking --- apps/openmw/mwmechanics/actors.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 73b479dd20..95a40c0554 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -249,7 +249,8 @@ namespace MWMechanics MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance, bool inCombatOrPursue) { - if (!actor.getRefData().getBaseNode()) + const auto& actorRefData = actor.getRefData(); + if (!actorRefData.getBaseNode()) return; if (targetActor.getClass().getCreatureStats(targetActor).isDead()) @@ -264,7 +265,7 @@ namespace MWMechanics if (!currentCell->isExterior() && !(currentCell->mData.mFlags & ESM::Cell::QuasiEx)) maxDistance *= fInteriorHeadTrackMult; - const osg::Vec3f actor1Pos(actor.getRefData().getPosition().asVec3()); + const osg::Vec3f actor1Pos(actorRefData.getPosition().asVec3()); const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3()); float sqrDist = (actor1Pos - actor2Pos).length2(); @@ -272,7 +273,7 @@ namespace MWMechanics return; // stop tracking when target is behind the actor - osg::Vec3f actorDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); + osg::Vec3f actorDirection = actorRefData.getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); osg::Vec3f targetDirection(actor2Pos - actor1Pos); actorDirection.z() = 0; targetDirection.z() = 0; From 23615e653a8733103930d8e512be07a163c0a9a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:17:42 +0300 Subject: [PATCH 2229/2859] Mark getActivePackage const --- apps/openmw/mwmechanics/aisequence.cpp | 2 +- apps/openmw/mwmechanics/aisequence.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index c59b3e39fe..f138d8dc97 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -430,7 +430,7 @@ bool MWMechanics::AiSequence::isEmpty() const return mPackages.empty(); } -const AiPackage& MWMechanics::AiSequence::getActivePackage() +const AiPackage& MWMechanics::AiSequence::getActivePackage() const { if(mPackages.empty()) throw std::runtime_error(std::string("No AI Package!")); diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 0d23207b63..5c6a43b9b8 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -163,7 +163,7 @@ namespace MWMechanics /// Return the current active package. /** If there is no active package, it will throw an exception **/ - const AiPackage& getActivePackage(); + const AiPackage& getActivePackage() const; /// Fills the AiSequence with packages /** Typically used for loading from the ESM From 5e44dd41ebad06e19ea45f18bae9b3d56b6054a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:20:24 +0300 Subject: [PATCH 2230/2859] Restructure function updateMovementSpeed --- apps/openmw/mwmechanics/actors.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 95a40c0554..418ed577fa 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -318,8 +318,9 @@ namespace MWMechanics if (mSmoothMovement) return; - CreatureStats &stats = actor.getClass().getCreatureStats(actor); - MWMechanics::AiSequence& seq = stats.getAiSequence(); + const auto& actorClass = actor.getClass(); + const CreatureStats &stats = actorClass.getCreatureStats(actor); + const MWMechanics::AiSequence& seq = stats.getAiSequence(); if (!seq.isEmpty() && seq.getActivePackage().useVariableSpeed()) { @@ -330,7 +331,7 @@ namespace MWMechanics if (distance < DECELERATE_DISTANCE) { float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE); - auto& movement = actor.getClass().getMovementSettings(actor); + auto& movement = actorClass.getMovementSettings(actor); movement.mPosition[0] *= speedCoef; movement.mPosition[1] *= speedCoef; } From 8631b96680adbd230c2166a005167cded8b8e65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:25:33 +0300 Subject: [PATCH 2231/2859] Restructure function updateGreetingState --- apps/openmw/mwmechanics/actors.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 418ed577fa..6ae9c9d87e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -340,11 +340,12 @@ namespace MWMechanics void Actors::updateGreetingState(const MWWorld::Ptr& actor, Actor& actorState, bool turnOnly) { - if (!actor.getClass().isActor() || actor == getPlayer()) + const auto& actorClass = actor.getClass(); + if (!actorClass.isActor() || actor == getPlayer()) return; - CreatureStats &stats = actor.getClass().getCreatureStats(actor); - const MWMechanics::AiSequence& seq = stats.getAiSequence(); + const CreatureStats& actorStats = actorClass.getCreatureStats(actor); + const MWMechanics::AiSequence& seq = actorStats.getAiSequence(); const auto packageId = seq.getTypeId(); if (seq.isInCombat() || @@ -371,7 +372,7 @@ namespace MWMechanics { actorState.setTurningToPlayer(false); // An original engine launches an endless idle2 when an actor greets player. - playAnimationGroup (actor, "idle2", 0, std::numeric_limits::max(), false); + playAnimationGroup(actor, "idle2", 0, std::numeric_limits::max(), false); } } @@ -382,14 +383,15 @@ namespace MWMechanics static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() .get().find("iGreetDistanceMultiplier")->mValue.getInteger(); - float helloDistance = static_cast(stats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier); + float helloDistance = static_cast(actorStats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier); + const auto& playerStats = player.getClass().getCreatureStats(player); int greetingTimer = actorState.getGreetingTimer(); GreetingState greetingState = actorState.getGreetingState(); if (greetingState == Greet_None) { - if ((playerPos - actorPos).length2() <= helloDistance*helloDistance && - !player.getClass().getCreatureStats(player).isDead() && !actor.getClass().getCreatureStats(actor).isParalyzed() + if ((playerPos - actorPos).length2() <= helloDistance * helloDistance && + !playerStats.isDead() && !actorStats.isParalyzed() && MWBase::Environment::get().getWorld()->getLOS(player, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) greetingTimer++; @@ -406,7 +408,7 @@ namespace MWMechanics { greetingTimer++; - if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak) + if (!actorStats.getMovementFlag(CreatureStats::Flag_ForceJump) && !actorStats.getMovementFlag(CreatureStats::Flag_ForceSneak) && (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor))) turnActorToFacePlayer(actor, actorState, dir); @@ -420,7 +422,7 @@ namespace MWMechanics if (greetingState == Greet_Done) { float resetDist = 2 * helloDistance; - if ((playerPos - actorPos).length2() >= resetDist*resetDist) + if ((playerPos - actorPos).length2() >= resetDist * resetDist) greetingState = Greet_None; } From 0b306bc1eaa75b8cef44d8314f87ce83e3b607a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:32:56 +0300 Subject: [PATCH 2232/2859] Restructure function engageCombat --- apps/openmw/mwmechanics/actors.cpp | 41 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6ae9c9d87e..10c69c947a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -432,8 +432,9 @@ namespace MWMechanics void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir) { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - actor.getClass().getMovementSettings(actor).mPosition[0] = 0; + auto& movementSettings = actor.getClass().getMovementSettings(); + movementSettings.mPosition[1] = 0; + movementSettings.mPosition[0] = 0; if (!actorState.isTurningToPlayer()) { @@ -464,7 +465,7 @@ namespace MWMechanics } } - void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer) + void Actors::engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer) { // No combat for totally static creatures if (!actor1.getClass().isMobile(actor1)) @@ -480,9 +481,9 @@ namespace MWMechanics const osg::Vec3f actor1Pos(actor1.getRefData().getPosition().asVec3()); const osg::Vec3f actor2Pos(actor2.getRefData().getPosition().asVec3()); - float sqrDist = (actor1Pos - actor2Pos).length2(); + const float sqrDist = (actor1Pos - actor2Pos).length2(); - if (sqrDist > mActorsProcessingRange*mActorsProcessingRange) + if (sqrDist > mActorsProcessingRange * mActorsProcessingRange) return; // If this is set to true, actor1 will start combat with actor2 if the awareness check at the end of the method returns true @@ -494,15 +495,16 @@ namespace MWMechanics getActorsSidingWith(actor1, allies1, cachedAllies); + auto* mechanicsManager = MWBase::Environment::get().getMechanicsManager(); // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and actor2 - for (const MWWorld::Ptr &ally : allies1) + for (const MWWorld::Ptr& ally : allies1) { if (creatureStats1.getAiSequence().isInCombat(ally)) continue; if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); @@ -524,21 +526,21 @@ namespace MWMechanics if (!aggressive && !isPlayerFollowerOrEscorter) { // Check that actor2 is in combat with actor1 - if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1)) + if (creatureStats2.getAiSequence().isInCombat(actor1)) { std::set allies2; getActorsSidingWith(actor2, allies2, cachedAllies); // Check that an ally of actor2 is also in combat with actor1 - for (const MWWorld::Ptr &ally2 : allies2) + for (const MWWorld::Ptr& ally2 : allies2) { if (ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1)) { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2); // Also have actor1's allies start combat for (const MWWorld::Ptr& ally1 : allies1) - MWBase::Environment::get().getMechanicsManager()->startCombat(ally1, actor2); + mechanicsManager->startCombat(ally1, actor2); return; } } @@ -553,13 +555,13 @@ namespace MWMechanics static const bool followersAttackOnSight = Settings::Manager::getBool("followers attack on sight", "Game"); if (!aggressive && isPlayerFollowerOrEscorter && followersAttackOnSight) { - if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1)) + if (creatureStats2.getAiSequence().isInCombat(actor1)) aggressive = true; else { - for (const MWWorld::Ptr &ally : allies1) + for (const MWWorld::Ptr& ally : allies1) { - if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(ally)) + if (creatureStats2.getAiSequence().isInCombat(ally)) { aggressive = true; break; @@ -576,15 +578,16 @@ namespace MWMechanics // Player followers and escorters with high fight should not initiate combat with the player or with // other player followers or escorters if (!isPlayerFollowerOrEscorter) - aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); + aggressive = mechanicsManager->isAggressive(actor1, actor2); } } // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter + auto* world = MWBase::Environment::get().getWorld(); if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() && creatureStats2.getAiSequence().isInCombat()) { // Check if the creature is too far - static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->mValue.getFloat(); + static const float fAlarmRadius = world->getStore().get().find("fAlarmRadius")->mValue.getFloat(); if (sqrDist > fAlarmRadius * fAlarmRadius) return; @@ -607,11 +610,11 @@ namespace MWMechanics // If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, start combat with actor2. if (aggressive) { - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); + bool LOS = world->getLOS(actor1, actor2) && + mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2); } } From 127366a948487e588d79367fb15bf0cf3e8bcbef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:38:39 +0300 Subject: [PATCH 2233/2859] Restructure function updateDrowning --- apps/openmw/mwmechanics/actors.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 10c69c947a..ee3f0fe3e3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -801,7 +801,8 @@ namespace MWMechanics void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer) { - NpcStats &stats = ptr.getClass().getNpcStats(ptr); + auto& actorClass = ptr.getClass(); + NpcStats& stats = actorClass.getNpcStats(ptr); // When npc stats are just initialized, mTimeToStartDrowning == -1 and we should get value from GMST static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->mValue.getFloat(); @@ -810,43 +811,43 @@ namespace MWMechanics if (!isPlayer && stats.getTimeToStartDrowning() < fHoldBreathTime / 2) { - AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + AiSequence& seq = actorClass.getCreatureStats(ptr).getAiSequence(); if (seq.getTypeId() != AiPackageTypeId::Breathe) //Only add it once seq.stack(AiBreathe(), ptr); } - MWBase::World *world = MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); bool knockedOutUnderwater = (isKnockedOut && world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))); - if((world->isSubmerged(ptr) || knockedOutUnderwater) - && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) + if ((world->isSubmerged(ptr) || knockedOutUnderwater) + && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) { float timeLeft = 0.0f; - if(knockedOutUnderwater) + if (knockedOutUnderwater) stats.setTimeToStartDrowning(0); else { timeLeft = stats.getTimeToStartDrowning() - duration; - if(timeLeft < 0.0f) + if (timeLeft < 0.0f) timeLeft = 0.0f; stats.setTimeToStartDrowning(timeLeft); } - bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); + bool godmode = isPlayer && world->getGodModeState(); - if(timeLeft == 0.0f && !godmode) + if (timeLeft == 0.0f && !godmode) { // If drowning, apply 3 points of damage per second static const float fSuffocationDamage = world->getStore().get().find("fSuffocationDamage")->mValue.getFloat(); DynamicStat health = stats.getHealth(); - health.setCurrent(health.getCurrent() - fSuffocationDamage*duration); + health.setCurrent(health.getCurrent() - fSuffocationDamage * duration); stats.setHealth(health); // Play a drowning sound - MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager(); - if(!sndmgr->getSoundPlaying(ptr, "drown")) + MWBase::SoundManager* sndmgr = MWBase::Environment::get().getSoundManager(); + if (!sndmgr->getSoundPlaying(ptr, "drown")) sndmgr->playSound3D(ptr, "drown", 1.0f, 1.0f); - if(isPlayer) + if (isPlayer) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); } } From 9821982944d3de8c08482e40175db46861d2e322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:48:16 +0300 Subject: [PATCH 2234/2859] Restructure function updateEquippedLight --- apps/openmw/mwmechanics/actors.cpp | 41 ++++++++++++++---------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ee3f0fe3e3..79ef8f56d4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -432,7 +432,7 @@ namespace MWMechanics void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, Actor& actorState, const osg::Vec3f& dir) { - auto& movementSettings = actor.getClass().getMovementSettings(); + auto& movementSettings = actor.getClass().getMovementSettings(actor); movementSettings.mPosition[1] = 0; movementSettings.mPosition[0] = 0; @@ -855,34 +855,30 @@ namespace MWMechanics stats.setTimeToStartDrowning(fHoldBreathTime); } - void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip) + void Actors::updateEquippedLight(const MWWorld::Ptr& ptr, float duration, bool mayEquip) { - bool isPlayer = (ptr == getPlayer()); + const bool isPlayer = (ptr == getPlayer()); + + auto& actorClass = ptr.getClass(); + auto& inventoryStore = actorClass.getInventoryStore(ptr); + + auto heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - MWWorld::InventoryStore &inventoryStore = ptr.getClass().getInventoryStore(ptr); - MWWorld::ContainerStoreIterator heldIter = - inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); /** * Automatically equip NPCs torches at night and unequip them at day */ if (!isPlayer) { - MWWorld::ContainerStoreIterator torch = inventoryStore.end(); - for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it) - { - if (it->getType() == ESM::Light::sRecordId && - it->getClass().canBeEquipped(*it, ptr).first) + auto torchIter = std::find_if(std::begin(inventoryStore), std::end(inventoryStore), [&](auto entry) { - torch = it; - break; - } - } - + return entry.getType() == ESM::Light::sRecordId && + entry.getClass().canBeEquipped(entry, ptr).first; + }); if (mayEquip) { - if (torch != inventoryStore.end()) + if (torchIter != inventoryStore.end()) { - if (!ptr.getClass().getCreatureStats (ptr).getAiSequence().isInCombat()) + if (!actorClass.getCreatureStats(ptr).getAiSequence().isInCombat()) { // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. if (heldIter != inventoryStore.end() && heldIter->getType() != ESM::Light::sRecordId) @@ -892,7 +888,7 @@ namespace MWMechanics { // For hostile NPCs, see if they have anything better to equip first auto shield = inventoryStore.getPreferredShield(ptr); - if(shield != inventoryStore.end()) + if (shield != inventoryStore.end()) inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr); } @@ -901,7 +897,7 @@ namespace MWMechanics // If we have a torch and can equip it, then equip it now. if (heldIter == inventoryStore.end()) { - inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr); + inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torchIter, ptr); } } } @@ -921,11 +917,12 @@ namespace MWMechanics //If holding a light... if(heldIter.getType() == MWWorld::ContainerStore::Type_Light) { + auto* world = MWBase::Environment::get().getWorld(); // Use time from the player's light if(isPlayer) { // But avoid using it up if the light source is hidden - MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); + MWRender::Animation *anim = world->getAnimation(ptr); if (anim && anim->getCarriedLeftShown()) { float timeRemaining = heldIter->getClass().getRemainingUsageTime(*heldIter); @@ -946,7 +943,7 @@ namespace MWMechanics } // Both NPC and player lights extinguish in water. - if(MWBase::Environment::get().getWorld()->isSwimming(ptr)) + if(world->isSwimming(ptr)) { inventoryStore.remove(*heldIter, 1, ptr); // remove it From 33706923a3f2ac6a0413750040c2ec908f120bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:25:22 +0300 Subject: [PATCH 2235/2859] Restructure function updateCrimePursuit --- apps/openmw/mwmechanics/actors.cpp | 99 ++++++++++++++++-------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 79ef8f56d4..a024507cf3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -958,59 +958,68 @@ namespace MWMechanics void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) { MWWorld::Ptr player = getPlayer(); - if (ptr != player && ptr.getClass().isNpc()) - { - // get stats of witness - CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); - NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + if (ptr == player) + return; - if (player.getClass().getNpcStats(player).isWerewolf()) - return; + auto& actorClass = ptr.getClass(); + if (!actorClass.isNpc()) + return; - if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackageTypeId::Pursue && !creatureStats.getAiSequence().isInCombat() - && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) + // get stats of witness + CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + + const auto& playerClass = player.getClass(); + const auto& playerStats = playerClass.getNpcStats(player); + if (playerStats.isWerewolf()) + return; + + auto* mechanicsManager = MWBase::Environment::get().getMechanicsManager(); + auto* world = MWBase::Environment::get().getWorld(); + + if (actorClass.isClass(ptr, "Guard") && creatureStats.getAiSequence().isInPursuit() && !creatureStats.getAiSequence().isInCombat() + && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) + { + const MWWorld::ESMStore& esmStore = world->getStore(); + static const int cutoff = esmStore.get().find("iCrimeThreshold")->mValue.getInteger(); + // Force dialogue on sight if bounty is greater than the cutoff + // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty) + if (playerStats.getBounty() >= cutoff + // TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s or so? + && world->getLOS(ptr, player) + && mechanicsManager->awarenessCheck(player, ptr)) { - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - static const int cutoff = esmStore.get().find("iCrimeThreshold")->mValue.getInteger(); - // Force dialogue on sight if bounty is greater than the cutoff - // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty) - if ( player.getClass().getNpcStats(player).getBounty() >= cutoff - // TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s or so? - && MWBase::Environment::get().getWorld()->getLOS(ptr, player) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) + static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); + if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { - static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); - if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier) - { - MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); - creatureStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId()); // Stops the guard from quitting combat if player is unreachable - } - else - creatureStats.getAiSequence().stack(AiPursue(player), ptr); - creatureStats.setAlarmed(true); - npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId()); + mechanicsManager->startCombat(ptr, player); + creatureStats.setHitAttemptActorId(playerClass.getCreatureStats(player).getActorId()); // Stops the guard from quitting combat if player is unreachable } + else + creatureStats.getAiSequence().stack(AiPursue(player), ptr); + creatureStats.setAlarmed(true); + npcStats.setCrimeId(world->getPlayer().getNewCrimeId()); } + } - // if I was a witness to a crime - if (npcStats.getCrimeId() != -1) + // if I was a witness to a crime + if (npcStats.getCrimeId() != -1) + { + // if you've paid for your crimes and I havent noticed + if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId()) { - // if you've paid for your crimes and I havent noticed - if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ) - { - // Calm witness down - if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stopPursuit(); - stopCombat(ptr); - - // Reset factors to attack - creatureStats.setAttacked(false); - creatureStats.setAlarmed(false); - creatureStats.setAiSetting(CreatureStats::AI_Fight, ptr.getClass().getBaseFightRating(ptr)); - - // Update witness crime id - npcStats.setCrimeId(-1); - } + // Calm witness down + if (ptr.getClass().isClass(ptr, "Guard")) + creatureStats.getAiSequence().stopPursuit(); + stopCombat(ptr); + + // Reset factors to attack + creatureStats.setAttacked(false); + creatureStats.setAlarmed(false); + creatureStats.setAiSetting(CreatureStats::AI_Fight, ptr.getClass().getBaseFightRating(ptr)); + + // Update witness crime id + npcStats.setCrimeId(-1); } } } From c3d02492df890bd134afbc26d1eb59c63394d02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Tue, 29 Mar 2022 19:49:02 +0300 Subject: [PATCH 2236/2859] Cleanup stopMovement --- apps/openmw/mwmechanics/aiwander.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 6cfe1e5fcd..378e5a6351 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -88,8 +88,9 @@ namespace MWMechanics void stopMovement(const MWWorld::Ptr& actor) { - actor.getClass().getMovementSettings(actor).mPosition[0] = 0; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + auto& movementSettings = actor.getClass().getMovementSettings(actor); + movementSettings.mPosition[0] = 0; + movementSettings.mPosition[1] = 0; } std::vector getInitialIdle(const std::vector& idle) From a21c17ab26ea114c365282592d14b5ffcee2b224 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Dec 2021 20:40:04 +0100 Subject: [PATCH 2237/2859] Use crash catcher in launcher --- apps/launcher/main.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 9c9acb4a17..4e054867f4 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -4,6 +4,8 @@ #include #include +#include + #ifdef MAC_OS_X_VERSION_MIN_REQUIRED #undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 @@ -12,7 +14,7 @@ #include "maindialog.hpp" -int main(int argc, char *argv[]) +int runLauncher(int argc, char *argv[]) { try { @@ -49,3 +51,8 @@ int main(int argc, char *argv[]) return 0; } } + +int main(int argc, char *argv[]) +{ + return wrapApplication(runLauncher, argc, argv, "Launcher"); +} From 2d5ccc804b72ebd86155cab94ff7ac321e66001e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 19 Dec 2021 22:49:41 +0100 Subject: [PATCH 2238/2859] Exchange binary messages between navmeshtool and launcher --- apps/launcher/datafilespage.cpp | 141 +++++++++++++--- apps/launcher/datafilespage.hpp | 13 +- apps/navmeshtool/main.cpp | 18 +- apps/navmeshtool/navmesh.cpp | 29 +++- apps/navmeshtool/navmesh.hpp | 2 +- apps/navmeshtool/worldspacedata.cpp | 23 ++- apps/navmeshtool/worldspacedata.hpp | 2 +- .../serialization/binaryreader.cpp | 23 +++ components/CMakeLists.txt | 4 + components/debug/debugging.cpp | 12 ++ components/debug/debugging.hpp | 5 + components/misc/guarded.hpp | 13 +- components/navmeshtool/protocol.cpp | 159 ++++++++++++++++++ components/navmeshtool/protocol.hpp | 78 +++++++++ components/serialization/binaryreader.hpp | 11 +- components/serialization/binarywriter.hpp | 9 +- 16 files changed, 494 insertions(+), 48 deletions(-) create mode 100644 components/navmeshtool/protocol.cpp create mode 100644 components/navmeshtool/protocol.hpp diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index c1fecf86db..e8b61cb079 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -20,11 +20,73 @@ #include #include -#include "utils/textinputdialog.hpp" +#include +#include "utils/textinputdialog.hpp" const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; +namespace Launcher +{ + namespace + { + struct HandleNavMeshToolMessage + { + int mCellsCount; + int mExpectedMaxProgress; + int mMaxProgress; + int mProgress; + + HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedCells&& message) const + { + return HandleNavMeshToolMessage { + static_cast(message.mCount), + mExpectedMaxProgress, + static_cast(message.mCount) * 100, + mProgress + }; + } + + HandleNavMeshToolMessage operator()(NavMeshTool::ProcessedCells&& message) const + { + return HandleNavMeshToolMessage { + mCellsCount, + mExpectedMaxProgress, + mMaxProgress, + std::max(mProgress, static_cast(message.mCount)) + }; + } + + HandleNavMeshToolMessage operator()(NavMeshTool::ExpectedTiles&& message) const + { + const int expectedMaxProgress = mCellsCount + static_cast(message.mCount); + return HandleNavMeshToolMessage { + mCellsCount, + expectedMaxProgress, + std::max(mMaxProgress, expectedMaxProgress), + mProgress + }; + } + + HandleNavMeshToolMessage operator()(NavMeshTool::GeneratedTiles&& message) const + { + int progress = mCellsCount + static_cast(message.mCount); + if (mExpectedMaxProgress < mMaxProgress) + progress += static_cast(std::round( + (mMaxProgress - mExpectedMaxProgress) + * (static_cast(progress) / static_cast(mExpectedMaxProgress)) + )); + return HandleNavMeshToolMessage { + mCellsCount, + mExpectedMaxProgress, + mMaxProgress, + std::max(mProgress, progress) + }; + } + }; + } +} + Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, MainDialog *parent) : QWidget(parent) @@ -95,8 +157,8 @@ void Launcher::DataFilesPage::buildView() connect(ui.updateNavMeshButton, SIGNAL(clicked()), this, SLOT(startNavMeshTool())); connect(ui.cancelNavMeshButton, SIGNAL(clicked()), this, SLOT(killNavMeshTool())); - connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(updateNavMeshProgress())); - connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardError()), this, SLOT(updateNavMeshProgress())); + connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(readNavMeshToolStdout())); + connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardError()), this, SLOT(readNavMeshToolStderr())); connect(mNavMeshToolInvoker->getProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(navMeshToolFinished(int, QProcess::ExitStatus))); } @@ -429,7 +491,9 @@ void Launcher::DataFilesPage::startNavMeshTool() ui.navMeshProgressBar->setValue(0); ui.navMeshProgressBar->setMaximum(1); - if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"))) + mNavMeshToolProgress = NavMeshToolProgress {}; + + if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"), QStringList({"--write-binary-log"}))) return; ui.cancelNavMeshButton->setEnabled(true); @@ -441,39 +505,60 @@ void Launcher::DataFilesPage::killNavMeshTool() mNavMeshToolInvoker->killProcess(); } -void Launcher::DataFilesPage::updateNavMeshProgress() +void Launcher::DataFilesPage::readNavMeshToolStderr() +{ + updateNavMeshProgress(4096); +} + +void Launcher::DataFilesPage::updateNavMeshProgress(int minDataSize) { QProcess& process = *mNavMeshToolInvoker->getProcess(); - QString text; - while (process.canReadLine()) + mNavMeshToolProgress.mMessagesData.append(process.readAllStandardError()); + if (mNavMeshToolProgress.mMessagesData.size() < minDataSize) + return; + const std::byte* const begin = reinterpret_cast(mNavMeshToolProgress.mMessagesData.constData()); + const std::byte* const end = begin + mNavMeshToolProgress.mMessagesData.size(); + const std::byte* position = begin; + HandleNavMeshToolMessage handle { + mNavMeshToolProgress.mCellsCount, + mNavMeshToolProgress.mExpectedMaxProgress, + ui.navMeshProgressBar->maximum(), + ui.navMeshProgressBar->value(), + }; + while (true) { - const QByteArray line = process.readLine(); - const auto end = std::find_if(line.rbegin(), line.rend(), [] (auto v) { return v != '\n' && v != '\r'; }); - text = QString::fromUtf8(line.mid(0, line.size() - (end - line.rbegin()))); - ui.navMeshLogPlainTextEdit->appendPlainText(text); + NavMeshTool::Message message; + const std::byte* const nextPosition = NavMeshTool::deserialize(position, end, message); + if (nextPosition == position) + break; + position = nextPosition; + handle = std::visit(handle, NavMeshTool::decode(message)); } - const QRegularExpression pattern(R"([\( ](\d+)/(\d+)[\) ])"); - QRegularExpressionMatch match = pattern.match(text); - if (!match.hasMatch()) + if (position != begin) + mNavMeshToolProgress.mMessagesData = mNavMeshToolProgress.mMessagesData.mid(position - begin); + mNavMeshToolProgress.mCellsCount = handle.mCellsCount; + mNavMeshToolProgress.mExpectedMaxProgress = handle.mExpectedMaxProgress; + ui.navMeshProgressBar->setMaximum(handle.mMaxProgress); + ui.navMeshProgressBar->setValue(handle.mProgress); +} + +void Launcher::DataFilesPage::readNavMeshToolStdout() +{ + QProcess& process = *mNavMeshToolInvoker->getProcess(); + QByteArray& logData = mNavMeshToolProgress.mLogData; + logData.append(process.readAllStandardOutput()); + const int lineEnd = logData.lastIndexOf('\n'); + if (lineEnd == -1) return; - int value = match.captured(1).toInt(); - const int maximum = match.captured(2).toInt(); - if (text.contains("cell")) - ui.navMeshProgressBar->setMaximum(maximum * 100); - else if (maximum > ui.navMeshProgressBar->maximum()) - ui.navMeshProgressBar->setMaximum(maximum); - else - value += static_cast(std::round( - (ui.navMeshProgressBar->maximum() - maximum) - * (static_cast(value) / static_cast(maximum)) - )); - ui.navMeshProgressBar->setValue(value); + const int size = logData.size() >= lineEnd && logData[lineEnd - 1] == '\r' ? lineEnd - 1 : lineEnd; + ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(logData.data(), size)); + logData = logData.mid(lineEnd + 1); } void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus) { - updateNavMeshProgress(); - ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAll())); + updateNavMeshProgress(0); + ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAllStandardOutput())); if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit) ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum()); ui.cancelNavMeshButton->setEnabled(false); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 36b69d4f8e..e004ca7542 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -73,7 +73,8 @@ namespace Launcher void startNavMeshTool(); void killNavMeshTool(); - void updateNavMeshProgress(); + void readNavMeshToolStdout(); + void readNavMeshToolStderr(); void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus); public: @@ -81,6 +82,14 @@ namespace Launcher const static char *mDefaultContentListName; private: + struct NavMeshToolProgress + { + QByteArray mLogData; + QByteArray mMessagesData; + std::map mWorldspaces; + int mCellsCount = 0; + int mExpectedMaxProgress = 0; + }; MainDialog *mMainDialog; TextInputDialog *mNewProfileDialog; @@ -96,6 +105,7 @@ namespace Launcher QString mDataLocal; Process::ProcessInvoker* mNavMeshToolInvoker; + NavMeshToolProgress mNavMeshToolProgress; void buildView(); void setProfile (int index, bool savePrevious); @@ -107,6 +117,7 @@ namespace Launcher void populateFileViews(const QString& contentModelName); void reloadCells(QStringList selectedFiles); void refreshDataFilesView (); + void updateNavMeshProgress(int minDataSize); class PathIterator { diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index af411f03dd..643c14af0a 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -31,6 +31,11 @@ #include #include +#ifdef WIN32 +#include +#include +#endif + namespace NavMeshTool { namespace @@ -86,6 +91,9 @@ namespace NavMeshTool ("remove-unused-tiles", bpo::value()->implicit_value(true) ->default_value(false), "remove tiles from cache that will not be used with current content profile") + + ("write-binary-log", bpo::value()->implicit_value(true) + ->default_value(false), "write progress in binary messages to be consumed by the launcher") ; Files::ConfigurationManager::addCommonOptions(result); @@ -145,6 +153,12 @@ namespace NavMeshTool const bool processInteriorCells = variables["process-interior-cells"].as(); const bool removeUnusedTiles = variables["remove-unused-tiles"].as(); + const bool writeBinaryLog = variables["write-binary-log"].as(); + +#ifdef WIN32 + if (writeBinaryLog) + _setmode(_fileno(stderr), _O_BINARY); +#endif Fallback::Map::init(variables["fallback"].as().mMap); @@ -180,10 +194,10 @@ namespace NavMeshTool navigatorSettings.mRecast.mSwimHeightScale = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat(); WorldspaceData cellsData = gatherWorldspaceData(navigatorSettings, readers, vfs, bulletShapeManager, - esmData, processInteriorCells); + esmData, processInteriorCells, writeBinaryLog); generateAllNavMeshTiles(agentHalfExtents, navigatorSettings, threadsNumber, removeUnusedTiles, - cellsData, std::move(db)); + writeBinaryLog, cellsData, std::move(db)); Log(Debug::Info) << "Done"; diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 3acddb821d..975f697a20 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include @@ -51,6 +53,18 @@ namespace NavMeshTool << "%) navmesh tiles are generated"; } + template + void serializeToStderr(const T& value) + { + const std::vector data = serialize(value); + getLockedRawStderr()->write(reinterpret_cast(data.data()), static_cast(data.size())); + } + + void logGeneratedTilesMessage(std::size_t number) + { + serializeToStderr(GeneratedTiles {static_cast(number)}); + } + struct LogGeneratedTiles { void operator()(std::size_t provided, std::size_t expected) const @@ -64,9 +78,10 @@ namespace NavMeshTool public: std::atomic_size_t mExpected {0}; - explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles) + explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles, bool writeBinaryLog) : mDb(std::move(db)) , mRemoveUnusedTiles(removeUnusedTiles) + , mWriteBinaryLog(writeBinaryLog) , mTransaction(mDb.startTransaction(Sqlite3::TransactionMode::Immediate)) , mNextTileId(mDb.getMaxTileId() + 1) , mNextShapeId(mDb.getMaxShapeId() + 1) @@ -178,6 +193,8 @@ namespace NavMeshTool } } logGeneratedTiles(mProvided, mExpected); + if (mWriteBinaryLog) + logGeneratedTilesMessage(mProvided); return !mCancelled; } @@ -211,6 +228,7 @@ namespace NavMeshTool mutable std::mutex mMutex; NavMeshDb mDb; const bool mRemoveUnusedTiles; + const bool mWriteBinaryLog; Transaction mTransaction; TileId mNextTileId; std::condition_variable mHasTile; @@ -223,18 +241,20 @@ namespace NavMeshTool const std::size_t provided = mProvided.fetch_add(1, std::memory_order_relaxed) + 1; mReporter(provided, mExpected); mHasTile.notify_one(); + if (mWriteBinaryLog) + logGeneratedTilesMessage(provided); } }; } void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings, - std::size_t threadsNumber, bool removeUnusedTiles, WorldspaceData& data, + std::size_t threadsNumber, bool removeUnusedTiles, bool writeBinaryLog, WorldspaceData& data, NavMeshDb&& db) { Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers..."; SceneUtil::WorkQueue workQueue(threadsNumber); - auto navMeshTileConsumer = std::make_shared(std::move(db), removeUnusedTiles); + auto navMeshTileConsumer = std::make_shared(std::move(db), removeUnusedTiles, writeBinaryLog); std::size_t tiles = 0; std::mt19937_64 random; @@ -256,6 +276,9 @@ namespace NavMeshTool tiles += worldspaceTiles.size(); + if (writeBinaryLog) + serializeToStderr(ExpectedTiles {static_cast(tiles)}); + navMeshTileConsumer->mExpected = tiles; std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random); diff --git a/apps/navmeshtool/navmesh.hpp b/apps/navmeshtool/navmesh.hpp index 3d0e9e4665..e25f1dd845 100644 --- a/apps/navmeshtool/navmesh.hpp +++ b/apps/navmeshtool/navmesh.hpp @@ -16,7 +16,7 @@ namespace NavMeshTool struct WorldspaceData; void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const DetourNavigator::Settings& settings, - std::size_t threadsNumber, bool removeUnusedTiles, WorldspaceData& cellsData, + std::size_t threadsNumber, bool removeUnusedTiles, bool writeBinaryLog, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db); } diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 50e529c6e5..fae58c363e 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include @@ -214,6 +216,13 @@ namespace NavMeshTool surface.mSize = static_cast(ESM::Land::LAND_SIZE); return {surface, landData.mMinHeight, landData.mMaxHeight}; } + + template + void serializeToStderr(const T& value) + { + const std::vector data = serialize(value); + getRawStderr().write(reinterpret_cast(data.data()), static_cast(data.size())); + } } WorldspaceNavMeshInput::WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings) @@ -226,7 +235,7 @@ namespace NavMeshTool WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector& readers, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, - bool processInteriorCells) + bool processInteriorCells, bool writeBinaryLog) { Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells..."; @@ -235,6 +244,9 @@ namespace NavMeshTool std::size_t objectsCounter = 0; + if (writeBinaryLog) + serializeToStderr(ExpectedCells {static_cast(esmData.mCells.size())}); + for (std::size_t i = 0; i < esmData.mCells.size(); ++i) { const ESM::Cell& cell = esmData.mCells[i]; @@ -242,6 +254,8 @@ namespace NavMeshTool if (!exterior && !processInteriorCells) { + if (writeBinaryLog) + serializeToStderr(ProcessedCells {static_cast(i + 1)}); Log(Debug::Info) << "Skipped interior" << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; continue; @@ -311,8 +325,13 @@ namespace NavMeshTool data.mObjects.emplace_back(std::move(object)); }); + const auto cellDescription = cell.getDescription(); + + if (writeBinaryLog) + serializeToStderr(ProcessedCells {static_cast(i + 1)}); + Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior") - << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") " << cell.getDescription() + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") " << cellDescription << " with " << (data.mObjects.size() - cellObjectsBegin) << " objects"; } diff --git a/apps/navmeshtool/worldspacedata.hpp b/apps/navmeshtool/worldspacedata.hpp index 2efb0954e0..dc2ca30c73 100644 --- a/apps/navmeshtool/worldspacedata.hpp +++ b/apps/navmeshtool/worldspacedata.hpp @@ -91,7 +91,7 @@ namespace NavMeshTool WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector& readers, const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, - bool processInteriorCells); + bool processInteriorCells, bool writeBinaryLog); } #endif diff --git a/apps/openmw_test_suite/serialization/binaryreader.cpp b/apps/openmw_test_suite/serialization/binaryreader.cpp index cb4f5c57bd..071cfabb8f 100644 --- a/apps/openmw_test_suite/serialization/binaryreader.cpp +++ b/apps/openmw_test_suite/serialization/binaryreader.cpp @@ -64,4 +64,27 @@ namespace const TestFormat format; EXPECT_THROW(binaryReader(format, values.data(), values.size()), std::runtime_error); } + + TEST(DetourNavigatorSerializationBinaryReaderTest, shouldSetPointerToCurrentBufferPosition) + { + std::vector data(8); + BinaryReader binaryReader(data.data(), data.data() + data.size()); + const std::byte* ptr = nullptr; + const TestFormat format; + binaryReader(format, ptr); + EXPECT_EQ(ptr, data.data()); + } + + TEST(DetourNavigatorSerializationBinaryReaderTest, shouldNotAdvanceAfterPointer) + { + std::vector data(8); + BinaryReader binaryReader(data.data(), data.data() + data.size()); + const std::byte* ptr1 = nullptr; + const std::byte* ptr2 = nullptr; + const TestFormat format; + binaryReader(format, ptr1); + binaryReader(format, ptr2); + EXPECT_EQ(ptr1, data.data()); + EXPECT_EQ(ptr2, data.data()); + } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index efc80081e3..d0d823a3eb 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -319,6 +319,10 @@ add_component_dir(esmloader esmdata ) +add_component_dir(navmeshtool + protocol + ) + set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index c84bb39d3d..65589bd6b4 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -138,6 +138,7 @@ namespace Debug static std::unique_ptr rawStdout = nullptr; static std::unique_ptr rawStderr = nullptr; +static std::unique_ptr rawStderrMutex = nullptr; static boost::filesystem::ofstream logfile; #if defined(_WIN32) && defined(_DEBUG) @@ -152,6 +153,16 @@ std::ostream& getRawStdout() return rawStdout ? *rawStdout : std::cout; } +std::ostream& getRawStderr() +{ + return rawStderr ? *rawStderr : std::cerr; +} + +Misc::Locked getLockedRawStderr() +{ + return Misc::Locked(*rawStderrMutex, getRawStderr()); +} + // Redirect cout and cerr to the log file void setupLogging(const std::string& logDir, const std::string& appName, std::ios_base::openmode mode) { @@ -180,6 +191,7 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c #endif rawStdout = std::make_unique(std::cout.rdbuf()); rawStderr = std::make_unique(std::cerr.rdbuf()); + rawStderrMutex = std::make_unique(); int ret = 0; try diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp index a2c5ae9e6c..d0af6d8f25 100644 --- a/components/debug/debugging.hpp +++ b/components/debug/debugging.hpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -140,6 +141,10 @@ namespace Debug // Can be used to print messages without timestamps std::ostream& getRawStdout(); +std::ostream& getRawStderr(); + +Misc::Locked getLockedRawStderr(); + void setupLogging(const std::string& logDir, const std::string& appName, std::ios_base::openmode = std::ios::out); int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 7f1005fc3a..a06789773d 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Misc { @@ -11,28 +12,28 @@ namespace Misc class Locked { public: - Locked(std::mutex& mutex, T& value) + Locked(std::mutex& mutex, std::remove_reference_t& value) : mLock(mutex), mValue(value) {} - T& get() const + std::remove_reference_t& get() const { return mValue.get(); } - T* operator ->() const + std::remove_reference_t* operator ->() const { - return std::addressof(get()); + return &get(); } - T& operator *() const + std::remove_reference_t& operator *() const { return get(); } private: std::unique_lock mLock; - std::reference_wrapper mValue; + std::reference_wrapper> mValue; }; template diff --git a/components/navmeshtool/protocol.cpp b/components/navmeshtool/protocol.cpp new file mode 100644 index 0000000000..656d5ab4d6 --- /dev/null +++ b/components/navmeshtool/protocol.cpp @@ -0,0 +1,159 @@ +#include "protocol.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace NavMeshTool +{ + namespace + { + template + struct Format : Serialization::Format> + { + using Serialization::Format>::operator(); + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, Message>> + { + if constexpr (mode == Serialization::Mode::Write) + visitor(*this, messageMagic); + else + { + static_assert(mode == Serialization::Mode::Read); + char magic[std::size(messageMagic)]; + visitor(*this, magic); + if (std::memcmp(magic, messageMagic, sizeof(magic)) != 0) + throw BadMessageMagic(); + } + visitor(*this, value.mType); + visitor(*this, value.mSize); + if constexpr (mode == Serialization::Mode::Write) + visitor(*this, value.mData, value.mSize); + else + visitor(*this, value.mData); + } + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, ExpectedCells>> + { + visitor(*this, value.mCount); + } + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, ProcessedCells>> + { + visitor(*this, value.mCount); + } + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, ExpectedTiles>> + { + visitor(*this, value.mCount); + } + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, GeneratedTiles>> + { + visitor(*this, value.mCount); + } + }; + + template + std::vector serializeToVector(const T& value) + { + constexpr Format format; + Serialization::SizeAccumulator sizeAccumulator; + format(sizeAccumulator, value); + std::vector buffer(sizeAccumulator.value()); + format(Serialization::BinaryWriter(buffer.data(), buffer.data() + buffer.size()), value); + return buffer; + } + + template + std::vector serializeImpl(const T& value) + { + const auto data = serializeToVector(value); + const Message message {static_cast(T::sMessageType), static_cast(data.size()), data.data()}; + return serializeToVector(message); + } + } + + std::vector serialize(const ExpectedCells& value) + { + return serializeImpl(value); + } + + std::vector serialize(const ProcessedCells& value) + { + return serializeImpl(value); + } + + std::vector serialize(const ExpectedTiles& value) + { + return serializeImpl(value); + } + + std::vector serialize(const GeneratedTiles& value) + { + return serializeImpl(value); + } + + const std::byte* deserialize(const std::byte* begin, const std::byte* end, Message& message) + { + try + { + constexpr Format format; + Serialization::BinaryReader reader(begin, end); + format(reader, message); + return message.mData + message.mSize; + } + catch (const Serialization::NotEnoughData&) + { + return begin; + } + } + + TypedMessage decode(const Message& message) + { + constexpr Format format; + Serialization::BinaryReader reader(message.mData, message.mData + message.mSize); + switch (static_cast(message.mType)) + { + case MessageType::ExpectedCells: + { + ExpectedCells value; + format(reader, value); + return value; + } + case MessageType::ProcessedCells: + { + ProcessedCells value; + format(reader, value); + return value; + } + case MessageType::ExpectedTiles: + { + ExpectedTiles value; + format(reader, value); + return value; + } + case MessageType::GeneratedTiles: + { + GeneratedTiles value; + format(reader, value); + return value; + } + } + throw std::logic_error("Unsupported message type: " + std::to_string(message.mType)); + } +} diff --git a/components/navmeshtool/protocol.hpp b/components/navmeshtool/protocol.hpp new file mode 100644 index 0000000000..ddb68a716c --- /dev/null +++ b/components/navmeshtool/protocol.hpp @@ -0,0 +1,78 @@ +#ifndef OPENMW_COMPONENTS_NAVMESHTOOL_PROTOCOL_H +#define OPENMW_COMPONENTS_NAVMESHTOOL_PROTOCOL_H + +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + inline constexpr char messageMagic[] = {'n', 'v', 't', 'm'}; + + struct BadMessageMagic : std::runtime_error + { + BadMessageMagic() : std::runtime_error("Bad Message magic") {} + }; + + enum class MessageType : std::uint64_t + { + ExpectedCells = 1, + ProcessedCells = 2, + ExpectedTiles = 3, + GeneratedTiles = 4, + }; + + struct Message + { + std::uint64_t mType = 0; + std::uint64_t mSize = 0; + const std::byte* mData = nullptr; + }; + + struct ExpectedCells + { + static constexpr MessageType sMessageType = MessageType::ExpectedCells; + std::uint64_t mCount = 0; + }; + + struct ProcessedCells + { + static constexpr MessageType sMessageType = MessageType::ProcessedCells; + std::uint64_t mCount = 0; + }; + + struct ExpectedTiles + { + static constexpr MessageType sMessageType = MessageType::ExpectedTiles; + std::uint64_t mCount = 0; + }; + + struct GeneratedTiles + { + static constexpr MessageType sMessageType = MessageType::GeneratedTiles; + std::uint64_t mCount = 0; + }; + + using TypedMessage = std::variant< + ExpectedCells, + ProcessedCells, + ExpectedTiles, + GeneratedTiles + >; + + std::vector serialize(const ExpectedCells& value); + + std::vector serialize(const ProcessedCells& value); + + std::vector serialize(const ExpectedTiles& value); + + std::vector serialize(const GeneratedTiles& value); + + const std::byte* deserialize(const std::byte* begin, const std::byte* end, Message& message); + + TypedMessage decode(const Message& message); +} + +#endif diff --git a/components/serialization/binaryreader.hpp b/components/serialization/binaryreader.hpp index 66e09f6ffb..79776a26a7 100644 --- a/components/serialization/binaryreader.hpp +++ b/components/serialization/binaryreader.hpp @@ -12,6 +12,11 @@ namespace Serialization { + struct NotEnoughData : std::runtime_error + { + NotEnoughData() : std::runtime_error("Not enough data") {} + }; + class BinaryReader { public: @@ -31,11 +36,13 @@ namespace Serialization else if constexpr (std::is_arithmetic_v) { if (mEnd - mPos < static_cast(sizeof(T))) - throw std::runtime_error("Not enough data"); + throw NotEnoughData(); std::memcpy(&value, mPos, sizeof(T)); mPos += sizeof(T); value = Misc::toLittleEndian(value); } + else if constexpr (std::is_pointer_v) + value = reinterpret_cast(mPos); else { format(*this, value); @@ -51,7 +58,7 @@ namespace Serialization { const std::size_t size = sizeof(T) * count; if (mEnd - mPos < static_cast(size)) - throw std::runtime_error("Not enough data"); + throw NotEnoughData(); std::memcpy(data, mPos, size); mPos += size; if constexpr (!Misc::IS_LITTLE_ENDIAN) diff --git a/components/serialization/binarywriter.hpp b/components/serialization/binarywriter.hpp index 199f208da9..64ec00d5bf 100644 --- a/components/serialization/binarywriter.hpp +++ b/components/serialization/binarywriter.hpp @@ -12,6 +12,11 @@ namespace Serialization { + struct NotEnoughSpace : std::runtime_error + { + NotEnoughSpace() : std::runtime_error("Not enough space") {} + }; + struct BinaryWriter { public: @@ -31,7 +36,7 @@ namespace Serialization else if constexpr (std::is_arithmetic_v) { if (mEnd - mDest < static_cast(sizeof(T))) - throw std::runtime_error("Not enough space"); + throw NotEnoughSpace(); writeValue(value); } else @@ -49,7 +54,7 @@ namespace Serialization { const std::size_t size = sizeof(T) * count; if (mEnd - mDest < static_cast(size)) - throw std::runtime_error("Not enough space"); + throw NotEnoughSpace(); if constexpr (Misc::IS_LITTLE_ENDIAN) { std::memcpy(mDest, data, size); From 3878a12ed31961e77ad94607c164ac08fe541043 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 6 Sep 2021 00:40:13 +0200 Subject: [PATCH 2239/2859] Delay cell grid change until scene update This allows proper profiling. Changing cell grid is not a part of physics system, it's a part of world system. --- apps/openmw/mwworld/scene.cpp | 14 +++++++++++++- apps/openmw/mwworld/scene.hpp | 13 ++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 1cb80389ce..07a613db4a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -296,6 +296,13 @@ namespace MWWorld void Scene::update (float duration, bool paused) { + if (mChangeCellGridRequest.has_value()) + { + changeCellGrid(mChangeCellGridRequest->mPosition, mChangeCellGridRequest->mCell.x(), + mChangeCellGridRequest->mCell.y(), mChangeCellGridRequest->mChangeEvent); + mChangeCellGridRequest.reset(); + } + mPreloader->updateCache(mRendering.getReferenceTime()); preloadCells(duration); @@ -515,7 +522,12 @@ namespace MWWorld osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); if (newCell != mCurrentGridCenter) - changeCellGrid(pos, newCell.x(), newCell.y()); + requestChangeCellGrid(pos, newCell); + } + + void Scene::requestChangeCellGrid(const osg::Vec3f &position, const osg::Vec2i& cell, bool changeEvent) + { + mChangeCellGridRequest = ChangeCellGridRequest {position, cell, changeEvent}; } void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index c0b6f5ae46..464088f223 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -74,6 +75,12 @@ namespace MWWorld using CellStoreCollection = std::set; private: + struct ChangeCellGridRequest + { + osg::Vec3f mPosition; + osg::Vec2i mCell; + bool mChangeEvent; + }; CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; @@ -99,12 +106,16 @@ namespace MWWorld std::vector> mWorkItems; + std::optional mChangeCellGridRequest; + void insertCell(CellStore &cell, Loading::Listener* loadingListener); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center void changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent = true); + void requestChangeCellGrid(const osg::Vec3f &position, const osg::Vec2i& cell, bool changeEvent = true); + typedef std::pair PositionCellGrid; void preloadCells(float dt); @@ -131,7 +142,7 @@ namespace MWWorld void playerMoved (const osg::Vec3f& pos); - void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); + void changePlayerCell(CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); CellStore *getCurrentCell(); From 6d7c8f25fc05eb6dd1c9f7073205f7db06d318b3 Mon Sep 17 00:00:00 2001 From: Vidi_Aquam <89811652+VidiAquam@users.noreply.github.com> Date: Sat, 2 Apr 2022 19:25:57 -0500 Subject: [PATCH 2240/2859] Implement grid snapping and angle snapping in OpenMW-CS Adds configurable snap settings for instance movement, rotation and scaling, used with the secondary edit button --- apps/opencs/model/prefs/state.cpp | 3 + apps/opencs/view/render/instancedragmodes.hpp | 5 +- apps/opencs/view/render/instancemode.cpp | 123 ++++++++++++++++-- apps/opencs/view/render/instancemode.hpp | 2 + apps/opencs/view/render/instancemovemode.cpp | 3 +- 5 files changed, 119 insertions(+), 17 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 0981ae4401..97039df2eb 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -243,6 +243,9 @@ void CSMPrefs::State::declare() secondarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert); declareCategory ("3D Scene Editing"); + declareDouble("gridsnap-movement", "Grid snap size", 16); + declareDouble("gridsnap-rotation", "Angle snap size", 15); + declareDouble("gridsnap-scale", "Scale snap size", 0.25); declareInt ("distance", "Drop Distance", 50). setTooltip ("If an instance drop can not be placed against another object at the " "insert point, it will be placed by this distance from the insert point instead"); diff --git a/apps/opencs/view/render/instancedragmodes.hpp b/apps/opencs/view/render/instancedragmodes.hpp index 01547545ae..2629a9d2f9 100644 --- a/apps/opencs/view/render/instancedragmodes.hpp +++ b/apps/opencs/view/render/instancedragmodes.hpp @@ -12,7 +12,10 @@ namespace CSVRender DragMode_Select_Only, DragMode_Select_Add, DragMode_Select_Remove, - DragMode_Select_Invert + DragMode_Select_Invert, + DragMode_Move_Snap, + DragMode_Rotate_Snap, + DragMode_Scale_Snap }; } #endif diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index ebb7f46fa5..f19d1051b1 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -64,6 +64,11 @@ osg::Quat CSVRender::InstanceMode::eulerToQuat(const osg::Vec3f& euler) const return zr * yr * xr; } +float CSVRender::InstanceMode::roundFloatToMult(const float val, const double mult) const +{ + return round(val / mult) * mult; +} + osg::Vec3f CSVRender::InstanceMode::getSelectionCenter(const std::vector >& selection) const { osg::Vec3f center = osg::Vec3f(0, 0, 0); @@ -156,15 +161,13 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) "
  • Use {scene-edit-primary} to rotate instances freely
  • " "
  • Use {scene-edit-secondary} to rotate instances within the grid
  • " "
  • The center of the view acts as the axis of rotation
  • " - "
" - "Grid rotate not implemented yet"); + ""); mSubMode->addButton (":scenetoolbar/transform-scale", "scale", "Scale selected instances" "
  • Use {scene-edit-primary} to scale instances freely
  • " "
  • Use {scene-edit-secondary} to scale instances along the grid
  • " "
  • The scaling rate is based on how close the start of a drag is to the center of the screen
  • " - "
" - "Grid scale not implemented yet"); + ""); mSubMode->setButton (mSubModeId); @@ -351,10 +354,81 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) { - if (mLocked) + if (mDragMode != DragMode_None || mLocked) return false; - return false; + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + + std::vector > selection = getWorldspaceWidget().getSelection(Mask_Reference); + if (selection.empty()) + { + // Only change selection at the start of drag if no object is already selected + if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + { + getWorldspaceWidget().clearSelection(Mask_Reference); + if (CSVRender::ObjectTag* objectTag = dynamic_cast (hit.tag.get())) + { + CSVRender::Object* object = objectTag->mObject; + object->setSelected(true); + } + } + + selection = getWorldspaceWidget().getSelection(Mask_Reference); + if (selection.empty()) + return false; + } + + mObjectsAtDragStart.clear(); + + for (std::vector >::iterator iter(selection.begin()); + iter != selection.end(); ++iter) + { + if (CSVRender::ObjectTag* objectTag = dynamic_cast (iter->get())) + { + if (mSubModeId == "move") + { + objectTag->mObject->setEdited(Object::Override_Position); + float x = objectTag->mObject->getPosition().pos[0]; + float y = objectTag->mObject->getPosition().pos[1]; + float z = objectTag->mObject->getPosition().pos[2]; + osg::Vec3f thisPoint(x, y, z); + + mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint)); + mObjectsAtDragStart.emplace_back(thisPoint); + mDragMode = DragMode_Move_Snap; + } + else if (mSubModeId == "rotate") + { + objectTag->mObject->setEdited(Object::Override_Rotation); + mDragMode = DragMode_Rotate_Snap; + } + else if (mSubModeId == "scale") + { + objectTag->mObject->setEdited(Object::Override_Scale); + mDragMode = DragMode_Scale_Snap; + + // Calculate scale factor + std::vector > editedSelection = getWorldspaceWidget().getEdited(Mask_Reference); + osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); + + int widgetHeight = getWorldspaceWidget().height(); + + float dx = pos.x() - center.x(); + float dy = (widgetHeight - pos.y()) - center.y(); + + mUnitScaleDist = std::sqrt(dx * dx + dy * dy); + } + } + } + + if (CSVRender::ObjectMarkerTag* objectTag = dynamic_cast (hit.tag.get())) + { + mDragAxis = objectTag->mAxis; + } + else + mDragAxis = -1; + + return true; } bool CSVRender::InstanceMode::primarySelectStartDrag (const QPoint& pos) @@ -400,8 +474,8 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); - if (mDragMode == DragMode_Move) {} - else if (mDragMode == DragMode_Rotate) + if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) {} + else if (mDragMode == DragMode_Rotate || mDragMode == DragMode_Rotate_Snap) { osg::Vec3f eye, centre, up; getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); @@ -465,7 +539,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou rotation = osg::Quat(angle, axis); } - else if (mDragMode == DragMode_Scale) + else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) { osg::Vec3f center = getScreenCoords(getSelectionCenter(selection)); @@ -507,7 +581,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { - if (mDragMode == DragMode_Move) + if (mDragMode == DragMode_Move || mDragMode == DragMode_Move_Snap) { ESM::Position position = objectTag->mObject->getPosition(); osg::Vec3f mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart)); @@ -518,6 +592,13 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou position.pos[1] = mObjectsAtDragStart[i].y() + addToY; position.pos[2] = mObjectsAtDragStart[i].z() + addToZ; + if (mDragMode == DragMode_Move_Snap) + { + position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble()); + position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble()); + position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble()); + } + // XYZ-locking if (mDragAxis != -1) { @@ -530,7 +611,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou objectTag->mObject->setPosition(position.pos); } - else if (mDragMode == DragMode_Rotate) + else if (mDragMode == DragMode_Rotate || mDragMode == DragMode_Rotate_Snap) { ESM::Position position = objectTag->mObject->getPosition(); @@ -546,9 +627,16 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou position.rot[2] = euler.z(); } + if (mDragMode == DragMode_Rotate_Snap) + { + position.rot[0] = CSVRender::InstanceMode::roundFloatToMult(position.rot[0], osg::DegreesToRadians(CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble())); + position.rot[1] = CSVRender::InstanceMode::roundFloatToMult(position.rot[1], osg::DegreesToRadians(CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble())); + position.rot[2] = CSVRender::InstanceMode::roundFloatToMult(position.rot[2], osg::DegreesToRadians(CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble())); + } + objectTag->mObject->setRotation(position.rot); } - else if (mDragMode == DragMode_Scale) + else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) { // Reset scale objectTag->mObject->setEdited(0); @@ -557,6 +645,11 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou float scale = objectTag->mObject->getScale(); scale *= offset.x(); + if (mDragMode == DragMode_Scale_Snap) + { + scale = CSVRender::InstanceMode::roundFloatToMult(scale, CSMPrefs::get()["3D Scene Editing"]["gridsnap-scale"].toDouble()); + } + objectTag->mObject->setScale (scale); } } @@ -593,7 +686,9 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) handleSelectDrag(pos); return; break; - + case DragMode_Move_Snap: description = "Move Instances"; break; + case DragMode_Rotate_Snap: description = "Rotate Instances"; break; + case DragMode_Scale_Snap: description = "Scale Instances"; break; case DragMode_None: break; } @@ -621,7 +716,7 @@ void CSVRender::InstanceMode::dragAborted() void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) { - if (mDragMode==DragMode_Move) + if (mDragMode==DragMode_Move || mDragMode==DragMode_Move_Snap) { osg::Vec3f eye; osg::Vec3f centre; diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 4ece934e93..feb307807a 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -53,6 +53,8 @@ namespace CSVRender osg::Vec3f quatToEuler(const osg::Quat& quat) const; osg::Quat eulerToQuat(const osg::Vec3f& euler) const; + float roundFloatToMult(const float val, const double mult) const; + osg::Vec3f getSelectionCenter(const std::vector >& selection) const; osg::Vec3f getScreenCoords(const osg::Vec3f& pos); osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp index 723af811de..5591035492 100644 --- a/apps/opencs/view/render/instancemovemode.cpp +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -6,7 +6,6 @@ CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent) "Move selected instances" "
  • Use {scene-edit-primary} to move instances around freely
  • " "
  • Use {scene-edit-secondary} to move instances around within the grid
  • " - "
" - "Grid move not implemented yet", + "", parent) {} From abf0c8048ed3b9b9655c7b872d49b28aba8bc308 Mon Sep 17 00:00:00 2001 From: Vidi_Aquam <89811652+VidiAquam@users.noreply.github.com> Date: Sun, 3 Apr 2022 09:04:12 -0500 Subject: [PATCH 2241/2859] Change reading of settings and add missing logic to mouse wheel drag --- apps/opencs/view/render/instancemode.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index f19d1051b1..e9a375f10d 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -594,9 +594,10 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou if (mDragMode == DragMode_Move_Snap) { - position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble()); - position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble()); - position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble()); + double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble(); + position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], snap); + position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], snap); + position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], snap); } // XYZ-locking @@ -629,9 +630,10 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou if (mDragMode == DragMode_Rotate_Snap) { - position.rot[0] = CSVRender::InstanceMode::roundFloatToMult(position.rot[0], osg::DegreesToRadians(CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble())); - position.rot[1] = CSVRender::InstanceMode::roundFloatToMult(position.rot[1], osg::DegreesToRadians(CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble())); - position.rot[2] = CSVRender::InstanceMode::roundFloatToMult(position.rot[2], osg::DegreesToRadians(CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble())); + double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble(); + position.rot[0] = CSVRender::InstanceMode::roundFloatToMult(position.rot[0], osg::DegreesToRadians(snap)); + position.rot[1] = CSVRender::InstanceMode::roundFloatToMult(position.rot[1], osg::DegreesToRadians(snap)); + position.rot[2] = CSVRender::InstanceMode::roundFloatToMult(position.rot[2], osg::DegreesToRadians(snap)); } objectTag->mObject->setRotation(position.rot); @@ -741,6 +743,15 @@ void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) ESM::Position position = objectTag->mObject->getPosition(); for (int i=0; i<3; ++i) position.pos[i] += offset[i]; + + if (mDragMode == DragMode_Move_Snap) + { + double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-movement"].toDouble(); + position.pos[0] = CSVRender::InstanceMode::roundFloatToMult(position.pos[0], snap); + position.pos[1] = CSVRender::InstanceMode::roundFloatToMult(position.pos[1], snap); + position.pos[2] = CSVRender::InstanceMode::roundFloatToMult(position.pos[2], snap); + } + objectTag->mObject->setPosition (position.pos); osg::Vec3f thisPoint(position.pos[0], position.pos[1], position.pos[2]); mDragStart = getMousePlaneCoords(getWorldspaceWidget().mapFromGlobal(QCursor::pos()), getProjectionSpaceCoords(thisPoint)); From a8020d8076985f6a357f2e1d842ac7ec147cb430 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sat, 2 Apr 2022 14:25:12 +0000 Subject: [PATCH 2242/2859] Make use of Gitlab's SAST https://docs.gitlab.com/ee/user/application_security/sast/ --- .gitlab-ci.yml | 6 ++++ .gitlab/sast-ruleset.toml | 71 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 .gitlab/sast-ruleset.toml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5579fcc76b..5c0acda625 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,10 @@ +include: + - template: Security/SAST.gitlab-ci.yml + # Note: We set `needs` on each job to control the job DAG. # See https://docs.gitlab.com/ee/ci/yaml/#needs stages: + - test - build # https://blog.nimbleways.com/let-s-make-faster-gitlab-ci-cd-pipelines/ @@ -10,6 +14,8 @@ variables: # These can be specified per job or per pipeline ARTIFACT_COMPRESSION_LEVEL: "fast" CACHE_COMPRESSION_LEVEL: "fast" + SAST_EXCLUDED_ANALYZERS: bandit,eslint + SAST_EXCLUDED_PATHS: spec,test,tests,tmp,extern .Ubuntu_Image: tags: diff --git a/.gitlab/sast-ruleset.toml b/.gitlab/sast-ruleset.toml new file mode 100644 index 0000000000..fb111405fa --- /dev/null +++ b/.gitlab/sast-ruleset.toml @@ -0,0 +1,71 @@ +[flawfinder] + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "readlink" # openmw isn't a privileged process + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "access" # openmw isn't a privileged process + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "random" # duh. + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "getenv" # duh. + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "open" # openmw isn't a privileged process + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "char" # too many false positives + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "read" # too many false positives + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "snprintf" # too many false positives + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "strlen" # too many false positives + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "mkstemp" # openmw doesn't run on old Unix systems + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "fopen" # openmw isn't a privileged process + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "equal" # only false positives, sigh + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "_snprintf" # only false positives, sigh + [[flawfinder.ruleset]] + disable = true + [flawfinder.ruleset.identifier] + type = "flawfinder_func_name" + value = "printf" # only false positives, sigh From 6e0d660dd51154c8d7923f11c63220cace5838a7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 3 Apr 2022 17:38:33 +0000 Subject: [PATCH 2243/2859] Check whether model is empty before trying to insert object --- apps/openmw/mwclass/activator.cpp | 3 +-- apps/openmw/mwclass/actor.cpp | 9 +++------ apps/openmw/mwclass/container.cpp | 3 +-- apps/openmw/mwclass/door.cpp | 3 +-- apps/openmw/mwclass/light.cpp | 2 +- apps/openmw/mwclass/static.cpp | 3 +-- apps/openmw/mwworld/scene.cpp | 13 +++++++++---- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 0b4edf4e1a..b1660559e6 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -45,8 +45,7 @@ namespace MWClass void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 9b5e10e1c7..8b291c5882 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -24,12 +24,9 @@ namespace MWClass void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - if (!model.empty()) - { - physics.addActor(ptr, model); - if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished()) - MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); - } + physics.addActor(ptr, model); + if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished()) + MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); } bool Actor::useAnim() const diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index e319cdf410..b98ba43df4 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,8 +111,7 @@ namespace MWClass void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 81a45f3a30..da8eb193d5 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -72,8 +72,7 @@ namespace MWClass void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); } bool Door::isDoor() const diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 26f41d30f7..070b769b83 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -50,7 +50,7 @@ namespace MWClass void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects - if (!model.empty() && (ptr.get()->mBase->mData.mFlags & ESM::Light::Carry) == 0) + if ((ptr.get()->mBase->mData.mFlags & ESM::Light::Carry) == 0) physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 0b77a5630d..1fa445e428 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -30,8 +30,7 @@ namespace MWClass void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 07a613db4a..f24316045b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -125,7 +125,8 @@ namespace // Restore effect particles MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); - ptr.getClass().insertObject (ptr, model, rotation, physics); + if (!model.empty()) + ptr.getClass().insertObject(ptr, model, rotation, physics); MWBase::Environment::get().getLuaManager()->objectAddedToScene(ptr); } @@ -584,10 +585,14 @@ namespace MWWorld if(ptr.mRef->mData.mPhysicsPostponed) { ptr.mRef->mData.mPhysicsPostponed = false; - if(ptr.mRef->mData.isEnabled() && ptr.mRef->mData.getCount() > 0) { + if (ptr.mRef->mData.isEnabled() && ptr.mRef->mData.getCount() > 0) + { std::string model = getModel(ptr, MWBase::Environment::get().getResourceSystem()->getVFS()); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - ptr.getClass().insertObjectPhysics(ptr, model, rotation, *mPhysics); + if (!model.empty()) + { + const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + ptr.getClass().insertObjectPhysics(ptr, model, rotation, *mPhysics); + } } } return true; From 14b5bef64f49925106689355b8f4046ad124d3ec Mon Sep 17 00:00:00 2001 From: Eris Caffee Date: Sun, 3 Apr 2022 22:46:39 -0500 Subject: [PATCH 2244/2859] Issue 6558 Move away from md5 in cmake Updated CMakeLists.txt files to use sha512 instead of md5 to verify downloads --- apps/openmw_test_suite/CMakeLists.txt | 2 +- extern/CMakeLists.txt | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 767eebe6c8..854526dc8c 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -93,7 +93,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) file(DOWNLOAD https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame ${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame - EXPECTED_MD5 bf3691034a38611534c74c3b89a7d2c3 + EXPECTED_HASH SHA512=6e38642bcf013c5f496a9cb0bf3ec7c9553b6e86b836e7844824c5a05f556c9391167214469b6318401684b702d7569896bf743c85aee4198612b3315ba778d6 ) target_compile_definitions(openmw_test_suite diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index b4bfe2e3bd..5f4712ea3d 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -50,7 +50,7 @@ if(NOT OPENMW_USE_SYSTEM_BULLET) include(FetchContent) FetchContent_Declare(bullet URL https://github.com/bulletphysics/bullet3/archive/refs/tags/3.17.tar.gz - URL_HASH MD5=7711bce9a49c289a08ecda34eaa0f32e + URL_HASH SHA512=a5105bf5f1dd365a64a350755c7d2c97942f74897a18dcdb3651e6732fd55cc1030a096f5808cf50575281f05e3ac09aa50a48d271a47b94cd61f5167a72b7cc SOURCE_DIR fetched/bullet ) FetchContent_MakeAvailableExcludeFromAll(bullet) @@ -85,7 +85,7 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) include(FetchContent) FetchContent_Declare(mygui URL https://github.com/MyGUI/mygui/archive/59c1388b942721887d18743ada15f1906ff11a1f.zip - URL_HASH MD5=0a64c9cccc8f96dc8c08172175e68e1c + URL_HASH SHA512=56b112b261fdc4c9b782de8ef8a9d239af3a75b8cfaa617daecc77c38ecd6521624cf5c17e6ac839b0870be5c31608d4f2a1574165d9d049f14f9e6ae03a8f98 SOURCE_DIR fetched/mygui ) FetchContent_MakeAvailableExcludeFromAll(mygui) @@ -151,7 +151,7 @@ if(NOT OPENMW_USE_SYSTEM_OSG) include(FetchContent) FetchContent_Declare(osg URL https://github.com/OpenMW/osg/archive/01cc2b585c8456a4ff843066b7e1a8715558289f.zip - URL_HASH MD5=f1496c5ce32f733581e84568cf2712af + URL_HASH SHA512=9b0a94c1c1d99c767f1857629d43c7a53bfcb74ef760993a121567831e168a1ebbfc10b0c67d7f2241c7b6c6dab2d0e6b876d9f17aca0fabe1a8c86b2279f956 SOURCE_DIR fetched/osg ) FetchContent_MakeAvailableExcludeFromAll(osg) @@ -183,7 +183,7 @@ if(NOT OPENMW_USE_SYSTEM_RECASTNAVIGATION) include(FetchContent) FetchContent_Declare(recastnavigation URL https://github.com/recastnavigation/recastnavigation/archive/e75adf86f91eb3082220085e42dda62679f9a3ea.zip - URL_HASH MD5=af905d121ef9d1cdfa979b0495cba059 + URL_HASH SHA512=93a19490cdfa55e98a6af9cc050e94af88fdb95fae2059ceeff28b62f3b48515f5fdd2c806c910550933b6861a4f6a91173ee0ed1b61c1396f7b34d4c78f0793 SOURCE_DIR fetched/recastnavigation ) FetchContent_MakeAvailableExcludeFromAll(recastnavigation) @@ -193,7 +193,7 @@ if (NOT OPENMW_USE_SYSTEM_SQLITE3) include(FetchContent) FetchContent_Declare(sqlite3 URL https://www.sqlite.org/2021/sqlite-amalgamation-3360000.zip - URL_HASH MD5=c5d360c74111bafae1b704721ff18fe6 + URL_HASH SHA512=5c18f158a599b1e91d95c91de3aa5c5de52f986845ad0cb49dfd56b650587e55e24d469571b5b864229b870d0eaf85d78893f61ef950b95389cb41692be37f58 SOURCE_DIR fetched/sqlite3 ) FetchContent_MakeAvailableExcludeFromAll(sqlite3) @@ -221,7 +221,7 @@ if (BUILD_BENCHMARKS AND NOT OPENMW_USE_SYSTEM_BENCHMARK) include(FetchContent) FetchContent_Declare(benchmark URL https://github.com/google/benchmark/archive/refs/tags/v1.5.2.zip - URL_HASH MD5=49395b757a7c4656d70f1328d93efd00 + URL_HASH SHA512=1146deca3b424703800933012ef75805d4657309d58b3484498bb51a99025bd405a69855a75af59c23fc3684bfa027224513999b8d7beaab3320c96fb6d6c540 SOURCE_DIR fetched/benchmark ) FetchContent_MakeAvailableExcludeFromAll(benchmark) From 21e4c10fa940309282681be74fa9894652b6cdd4 Mon Sep 17 00:00:00 2001 From: Matt <3397065-ZehMatt@users.noreply.gitlab.com> Date: Mon, 4 Apr 2022 13:56:19 +0000 Subject: [PATCH 2245/2859] Introduce IndexedVector --- apps/openmw/mwmechanics/actors.cpp | 10 +- apps/openmw/mwmechanics/actors.hpp | 3 +- apps/openmw/mwphysics/physicssystem.hpp | 9 +- apps/openmw/mwworld/ptr.hpp | 15 ++ apps/openmw_test_suite/CMakeLists.txt | 1 + .../misc/test_indexedvector.cpp | 88 +++++++++ components/CMakeLists.txt | 2 +- components/misc/indexedvector.hpp | 186 ++++++++++++++++++ 8 files changed, 305 insertions(+), 9 deletions(-) create mode 100644 apps/openmw_test_suite/misc/test_indexedvector.cpp create mode 100644 components/misc/indexedvector.hpp diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a024507cf3..6c2354def2 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1057,9 +1057,11 @@ namespace MWMechanics MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (!anim) return; - mActors.emplace(ptr, new Actor(ptr, anim)); - CharacterController* ctrl = mActors[ptr]->getCharacterController(); + auto* newActor = new Actor(ptr, anim); + mActors.emplace(ptr, newActor); + + CharacterController* ctrl = newActor->getCharacterController(); if (updateImmediately) ctrl->update(0); @@ -1160,7 +1162,7 @@ namespace MWMechanics mActors.erase(iter); actor->updatePtr(ptr); - mActors.insert(std::make_pair(ptr, actor)); + mActors.emplace(ptr, actor); } } @@ -1173,7 +1175,7 @@ namespace MWMechanics { removeTemporaryEffects(iter->first); delete iter->second; - mActors.erase(iter++); + iter = mActors.erase(iter); } else ++iter; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index f1985377e8..e6ec6c1021 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "../mwmechanics/actorutil.hpp" @@ -62,7 +63,7 @@ namespace MWMechanics Actors(); ~Actors(); - typedef std::map PtrActorMap; + using PtrActorMap = Misc::IndexedVector; PtrActorMap::const_iterator begin() { return mActors.begin(); } PtrActorMap::const_iterator end() { return mActors.end(); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b165f10761..30c1aa47c0 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include #include @@ -55,7 +57,7 @@ namespace MWPhysics class PhysicsTaskScheduler; class Projectile; - using ActorMap = std::unordered_map>; + using ActorMap = Misc::IndexedVector>; struct ContactPoint { @@ -299,8 +301,9 @@ namespace MWPhysics using ObjectMap = std::unordered_map>; ObjectMap mObjects; - - std::map mAnimatedObjects; // stores pointers to elements in mObjects + + using AnimatedObjectMap = Misc::IndexedVector; + AnimatedObjectMap mAnimatedObjects; // stores pointers to elements in mObjects ActorMap mActors; diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 33e062df90..911f62dac0 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "livecellref.hpp" @@ -161,6 +162,20 @@ namespace MWWorld ConstPtr(const LiveCellRefBase *liveCellRef=nullptr, const CellStoreType *cell=nullptr) : PtrBase(liveCellRef, cell, nullptr) {} }; + struct PtrHash + { + size_t operator()(const Ptr& ptr) const noexcept + { + const void* p = ptr; + return std::hash{}(p); + } + size_t operator()(const ConstPtr& ptr) const noexcept + { + const void* p = ptr; + return std::hash{}(p); + } + }; + } #endif diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 767eebe6c8..943b430e21 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -30,6 +30,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) misc/test_stringops.cpp misc/test_endianness.cpp misc/test_resourcehelpers.cpp + misc/test_indexedvector.cpp misc/progressreporter.cpp misc/compression.cpp diff --git a/apps/openmw_test_suite/misc/test_indexedvector.cpp b/apps/openmw_test_suite/misc/test_indexedvector.cpp new file mode 100644 index 0000000000..7039aed1fb --- /dev/null +++ b/apps/openmw_test_suite/misc/test_indexedvector.cpp @@ -0,0 +1,88 @@ +#include + +#include +#include + +#include +#include + +namespace +{ + TEST(IndexedVectorTests, basicInsert) + { + std::vector> vec; + Misc::IndexedVector map; + + for (int i = 0; i < 10000; i++) + { + vec.emplace_back(i, i << 13); + map.emplace(i, i << 13); + } + + ASSERT_EQ(vec.size(), map.size()); + + auto itVec = vec.begin(); + auto itMap = map.begin(); + while (itMap != map.end() && itVec != vec.end()) + { + ASSERT_EQ(itVec->first, itMap->first); + ASSERT_EQ(itVec->second, itMap->second); + itMap++; + itVec++; + } + } + + TEST(IndexedVectorTests, duplicateInsert) + { + Misc::IndexedVector map; + + auto pairVal = map.emplace(1, 5); + ASSERT_EQ(map.size(), 1); + ASSERT_EQ(pairVal.first, map.begin()); + ASSERT_EQ(pairVal.first->second, 5); + ASSERT_EQ(pairVal.second, true); + + pairVal = map.emplace(1, 10); + ASSERT_EQ(map.size(), 1); + ASSERT_EQ(pairVal.first, map.begin()); + ASSERT_EQ(pairVal.first->second, 5); + ASSERT_EQ(pairVal.second, false); + } + + TEST(IndexedVectorTests, testErase) + { + Misc::IndexedVector map; + for (int i = 0; i < 10000; i++) + { + map.emplace(i, i); + } + + auto itA = map.find(100); + ASSERT_NE(itA, map.end()); + ASSERT_EQ(itA->second, 100); + + itA = map.erase(itA); + ASSERT_EQ(map.size(), 10000 - 1); + ASSERT_NE(itA, map.end()); + ASSERT_EQ(itA->second, 101); + } + + TEST(IndexedVectorTests, testLookup) + { + Misc::IndexedVector map; + for (int i = 0; i < 10000; i++) + { + map.emplace(i, i); + } + + auto itA = map.find(100); + ASSERT_NE(itA, map.end()); + ASSERT_EQ(itA->second, 100); + + map.erase(itA); + ASSERT_EQ(map.size(), 10000 - 1); + itA = map.find(101); + ASSERT_NE(itA, map.end()); + ASSERT_EQ(itA->second, 101); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index efc80081e3..b9a5cfa2ee 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -189,7 +189,7 @@ add_component_dir (esm4 add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread - compression osguservalues errorMarker color + compression osguservalues errorMarker color indexedvector ) add_component_dir (debug diff --git a/components/misc/indexedvector.hpp b/components/misc/indexedvector.hpp new file mode 100644 index 0000000000..9ade1e1e5e --- /dev/null +++ b/components/misc/indexedvector.hpp @@ -0,0 +1,186 @@ +#ifndef OPENMW_COMPONENTS_FLATMAP_HPP +#define OPENMW_COMPONENTS_FLATMAP_HPP + +#include +#include +#include +#include +#include + +namespace Misc +{ + /// \class IndexedVector + /// Works like std::unordered_map but storage is std::vector and uses insertion order. + template > + class IndexedVector + { + using ElementType = std::pair; + + private: + using Storage = std::vector; + using LookupTable = std::unordered_map; + + public: + using iterator = typename Storage::iterator; + using const_iterator = typename Storage::const_iterator; + using value_type = typename Storage::value_type; + + void clear() + { + mData.clear(); + mLookup.clear(); + } + + size_t size() const noexcept + { + return mData.size(); + } + + /// Inserts the element at the back with move semantics. + std::pair insert(const value_type& value) + { + auto it = find(value.first); + if (it != std::end(mData)) + { + return { it, false }; + } + else + { + const auto& key = value.first; + it = mData.emplace(std::end(mData), value); + mLookup.emplace(key, std::distance(std::begin(mData), it)); + return { it, true }; + } + } + + std::pair insert(value_type&& value) + { + auto it = find(value.first); + if (it != std::end(mData)) + { + return { it, false }; + } + else + { + const auto& key = value.first; + it = mData.emplace(std::end(mData), std::move(value)); + mLookup.emplace(key, std::distance(std::begin(mData), it)); + return { it, true }; + } + } + + /// Inserts the element at the back + template + std::pair emplace(TArgs&& ...args) + { + value_type value{ std::forward(args)... }; + auto it = find(value.first); + if (it != std::end(mData)) + { + return { it, false }; + } + else + { + const auto& key = value.first; + it = mData.emplace(std::end(mData), std::move(value)); + mLookup.emplace(key, std::distance(std::begin(mData), it)); + return { it, true }; + } + } + + /// Erases a single element, iterators are invalidated after this call. + /// The returned iterator points to the value after the removed element. + iterator erase(iterator it) noexcept + { + return eraseImpl(it); + } + + iterator erase(const_iterator it) noexcept + { + return eraseImpl(it); + } + + /// Erases a single element by key, iterators are invalidated after this call. + /// The returned iterator points to the value after the removed element. + iterator erase(const Key& key) noexcept + { + auto it = find(key); + if (it == end()) + return it; + return erase(it); + } + + template + iterator find(const K& key) + { + auto it = mLookup.find(key); + if (it == std::end(mLookup)) + return end(); + return std::begin(mData) + it->second; + } + + template + const_iterator find(const K& key) const + { + auto it = mLookup.find(key); + if (it == std::end(mLookup)) + return end(); + return std::begin(mData) + it->second; + } + + iterator begin() + { + return mData.begin(); + } + + const_iterator begin() const + { + return mData.begin(); + } + + const_iterator cbegin() const + { + return mData.cbegin(); + } + + iterator end() + { + return mData.end(); + } + + const_iterator end() const + { + return mData.end(); + } + + const_iterator cend() const + { + return mData.cend(); + } + + private: + template + iterator eraseImpl(TIterator&& it) noexcept + { + const auto lookupIt = mLookup.find(it->first); + if (lookupIt == std::end(mLookup)) + return end(); + const auto index = lookupIt->second; + mLookup.erase(lookupIt); + // Adjust indices by one. + for (auto& [_, idx] : mLookup) + { + if (idx > index) + idx--; + } + return mData.erase(it); + } + + private: + Storage mData; + LookupTable mLookup; + }; + +} + +#endif From 49e21e121ab8d6200a0f0a4d3c5b6425a0485d2e Mon Sep 17 00:00:00 2001 From: Matt <3397065-ZehMatt@users.noreply.gitlab.com> Date: Mon, 4 Apr 2022 17:09:52 +0000 Subject: [PATCH 2246/2859] Revert "Merge branch 'refactor/6677-2' into 'master'" This reverts merge request !1733 --- apps/openmw/mwmechanics/actors.cpp | 10 +- apps/openmw/mwmechanics/actors.hpp | 3 +- apps/openmw/mwphysics/physicssystem.hpp | 9 +- apps/openmw/mwworld/ptr.hpp | 15 -- apps/openmw_test_suite/CMakeLists.txt | 1 - .../misc/test_indexedvector.cpp | 88 --------- components/CMakeLists.txt | 2 +- components/misc/indexedvector.hpp | 186 ------------------ 8 files changed, 9 insertions(+), 305 deletions(-) delete mode 100644 apps/openmw_test_suite/misc/test_indexedvector.cpp delete mode 100644 components/misc/indexedvector.hpp diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6c2354def2..a024507cf3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1057,11 +1057,9 @@ namespace MWMechanics MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (!anim) return; + mActors.emplace(ptr, new Actor(ptr, anim)); - auto* newActor = new Actor(ptr, anim); - mActors.emplace(ptr, newActor); - - CharacterController* ctrl = newActor->getCharacterController(); + CharacterController* ctrl = mActors[ptr]->getCharacterController(); if (updateImmediately) ctrl->update(0); @@ -1162,7 +1160,7 @@ namespace MWMechanics mActors.erase(iter); actor->updatePtr(ptr); - mActors.emplace(ptr, actor); + mActors.insert(std::make_pair(ptr, actor)); } } @@ -1175,7 +1173,7 @@ namespace MWMechanics { removeTemporaryEffects(iter->first); delete iter->second; - iter = mActors.erase(iter); + mActors.erase(iter++); } else ++iter; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index e6ec6c1021..f1985377e8 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "../mwmechanics/actorutil.hpp" @@ -63,7 +62,7 @@ namespace MWMechanics Actors(); ~Actors(); - using PtrActorMap = Misc::IndexedVector; + typedef std::map PtrActorMap; PtrActorMap::const_iterator begin() { return mActors.begin(); } PtrActorMap::const_iterator end() { return mActors.end(); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 30c1aa47c0..b165f10761 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -11,8 +11,6 @@ #include #include -#include - #include #include #include @@ -57,7 +55,7 @@ namespace MWPhysics class PhysicsTaskScheduler; class Projectile; - using ActorMap = Misc::IndexedVector>; + using ActorMap = std::unordered_map>; struct ContactPoint { @@ -301,9 +299,8 @@ namespace MWPhysics using ObjectMap = std::unordered_map>; ObjectMap mObjects; - - using AnimatedObjectMap = Misc::IndexedVector; - AnimatedObjectMap mAnimatedObjects; // stores pointers to elements in mObjects + + std::map mAnimatedObjects; // stores pointers to elements in mObjects ActorMap mActors; diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 911f62dac0..33e062df90 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "livecellref.hpp" @@ -162,20 +161,6 @@ namespace MWWorld ConstPtr(const LiveCellRefBase *liveCellRef=nullptr, const CellStoreType *cell=nullptr) : PtrBase(liveCellRef, cell, nullptr) {} }; - struct PtrHash - { - size_t operator()(const Ptr& ptr) const noexcept - { - const void* p = ptr; - return std::hash{}(p); - } - size_t operator()(const ConstPtr& ptr) const noexcept - { - const void* p = ptr; - return std::hash{}(p); - } - }; - } #endif diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 3f3a134b45..854526dc8c 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -30,7 +30,6 @@ if (GTEST_FOUND AND GMOCK_FOUND) misc/test_stringops.cpp misc/test_endianness.cpp misc/test_resourcehelpers.cpp - misc/test_indexedvector.cpp misc/progressreporter.cpp misc/compression.cpp diff --git a/apps/openmw_test_suite/misc/test_indexedvector.cpp b/apps/openmw_test_suite/misc/test_indexedvector.cpp deleted file mode 100644 index 7039aed1fb..0000000000 --- a/apps/openmw_test_suite/misc/test_indexedvector.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include - -#include -#include - -#include -#include - -namespace -{ - TEST(IndexedVectorTests, basicInsert) - { - std::vector> vec; - Misc::IndexedVector map; - - for (int i = 0; i < 10000; i++) - { - vec.emplace_back(i, i << 13); - map.emplace(i, i << 13); - } - - ASSERT_EQ(vec.size(), map.size()); - - auto itVec = vec.begin(); - auto itMap = map.begin(); - while (itMap != map.end() && itVec != vec.end()) - { - ASSERT_EQ(itVec->first, itMap->first); - ASSERT_EQ(itVec->second, itMap->second); - itMap++; - itVec++; - } - } - - TEST(IndexedVectorTests, duplicateInsert) - { - Misc::IndexedVector map; - - auto pairVal = map.emplace(1, 5); - ASSERT_EQ(map.size(), 1); - ASSERT_EQ(pairVal.first, map.begin()); - ASSERT_EQ(pairVal.first->second, 5); - ASSERT_EQ(pairVal.second, true); - - pairVal = map.emplace(1, 10); - ASSERT_EQ(map.size(), 1); - ASSERT_EQ(pairVal.first, map.begin()); - ASSERT_EQ(pairVal.first->second, 5); - ASSERT_EQ(pairVal.second, false); - } - - TEST(IndexedVectorTests, testErase) - { - Misc::IndexedVector map; - for (int i = 0; i < 10000; i++) - { - map.emplace(i, i); - } - - auto itA = map.find(100); - ASSERT_NE(itA, map.end()); - ASSERT_EQ(itA->second, 100); - - itA = map.erase(itA); - ASSERT_EQ(map.size(), 10000 - 1); - ASSERT_NE(itA, map.end()); - ASSERT_EQ(itA->second, 101); - } - - TEST(IndexedVectorTests, testLookup) - { - Misc::IndexedVector map; - for (int i = 0; i < 10000; i++) - { - map.emplace(i, i); - } - - auto itA = map.find(100); - ASSERT_NE(itA, map.end()); - ASSERT_EQ(itA->second, 100); - - map.erase(itA); - ASSERT_EQ(map.size(), 10000 - 1); - itA = map.find(101); - ASSERT_NE(itA, map.end()); - ASSERT_EQ(itA->second, 101); - } -} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b9a5cfa2ee..efc80081e3 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -189,7 +189,7 @@ add_component_dir (esm4 add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread - compression osguservalues errorMarker color indexedvector + compression osguservalues errorMarker color ) add_component_dir (debug diff --git a/components/misc/indexedvector.hpp b/components/misc/indexedvector.hpp deleted file mode 100644 index 9ade1e1e5e..0000000000 --- a/components/misc/indexedvector.hpp +++ /dev/null @@ -1,186 +0,0 @@ -#ifndef OPENMW_COMPONENTS_FLATMAP_HPP -#define OPENMW_COMPONENTS_FLATMAP_HPP - -#include -#include -#include -#include -#include - -namespace Misc -{ - /// \class IndexedVector - /// Works like std::unordered_map but storage is std::vector and uses insertion order. - template > - class IndexedVector - { - using ElementType = std::pair; - - private: - using Storage = std::vector; - using LookupTable = std::unordered_map; - - public: - using iterator = typename Storage::iterator; - using const_iterator = typename Storage::const_iterator; - using value_type = typename Storage::value_type; - - void clear() - { - mData.clear(); - mLookup.clear(); - } - - size_t size() const noexcept - { - return mData.size(); - } - - /// Inserts the element at the back with move semantics. - std::pair insert(const value_type& value) - { - auto it = find(value.first); - if (it != std::end(mData)) - { - return { it, false }; - } - else - { - const auto& key = value.first; - it = mData.emplace(std::end(mData), value); - mLookup.emplace(key, std::distance(std::begin(mData), it)); - return { it, true }; - } - } - - std::pair insert(value_type&& value) - { - auto it = find(value.first); - if (it != std::end(mData)) - { - return { it, false }; - } - else - { - const auto& key = value.first; - it = mData.emplace(std::end(mData), std::move(value)); - mLookup.emplace(key, std::distance(std::begin(mData), it)); - return { it, true }; - } - } - - /// Inserts the element at the back - template - std::pair emplace(TArgs&& ...args) - { - value_type value{ std::forward(args)... }; - auto it = find(value.first); - if (it != std::end(mData)) - { - return { it, false }; - } - else - { - const auto& key = value.first; - it = mData.emplace(std::end(mData), std::move(value)); - mLookup.emplace(key, std::distance(std::begin(mData), it)); - return { it, true }; - } - } - - /// Erases a single element, iterators are invalidated after this call. - /// The returned iterator points to the value after the removed element. - iterator erase(iterator it) noexcept - { - return eraseImpl(it); - } - - iterator erase(const_iterator it) noexcept - { - return eraseImpl(it); - } - - /// Erases a single element by key, iterators are invalidated after this call. - /// The returned iterator points to the value after the removed element. - iterator erase(const Key& key) noexcept - { - auto it = find(key); - if (it == end()) - return it; - return erase(it); - } - - template - iterator find(const K& key) - { - auto it = mLookup.find(key); - if (it == std::end(mLookup)) - return end(); - return std::begin(mData) + it->second; - } - - template - const_iterator find(const K& key) const - { - auto it = mLookup.find(key); - if (it == std::end(mLookup)) - return end(); - return std::begin(mData) + it->second; - } - - iterator begin() - { - return mData.begin(); - } - - const_iterator begin() const - { - return mData.begin(); - } - - const_iterator cbegin() const - { - return mData.cbegin(); - } - - iterator end() - { - return mData.end(); - } - - const_iterator end() const - { - return mData.end(); - } - - const_iterator cend() const - { - return mData.cend(); - } - - private: - template - iterator eraseImpl(TIterator&& it) noexcept - { - const auto lookupIt = mLookup.find(it->first); - if (lookupIt == std::end(mLookup)) - return end(); - const auto index = lookupIt->second; - mLookup.erase(lookupIt); - // Adjust indices by one. - for (auto& [_, idx] : mLookup) - { - if (idx > index) - idx--; - } - return mData.erase(it); - } - - private: - Storage mData; - LookupTable mLookup; - }; - -} - -#endif From 788745e0047aa6b50b02003866f839ea70b11e8d Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 4 Apr 2022 23:10:03 +0200 Subject: [PATCH 2247/2859] Make coordinate calculation more robust, implement Flex widget type --- components/CMakeLists.txt | 2 +- components/lua_ui/alignment.cpp | 4 +- components/lua_ui/container.cpp | 2 +- components/lua_ui/element.cpp | 2 +- components/lua_ui/flex.cpp | 106 ++++++++++++++++++++++++++++++++ components/lua_ui/flex.hpp | 27 ++++++++ components/lua_ui/util.cpp | 5 +- components/lua_ui/widget.cpp | 34 +++++++--- components/lua_ui/widget.hpp | 14 +++-- components/lua_ui/window.cpp | 10 +-- 10 files changed, 181 insertions(+), 25 deletions(-) create mode 100644 components/lua_ui/flex.cpp create mode 100644 components/lua_ui/flex.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index efc80081e3..96e64459db 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -256,7 +256,7 @@ add_component_dir (fallback add_component_dir (lua_ui registerscriptsettings scriptsettings properties widget element util layers content alignment resources - adapter text textedit window image container + adapter text textedit window image container flex ) diff --git a/components/lua_ui/alignment.cpp b/components/lua_ui/alignment.cpp index dfd09b752d..358c5ba14b 100644 --- a/components/lua_ui/alignment.cpp +++ b/components/lua_ui/alignment.cpp @@ -9,9 +9,9 @@ namespace LuaUi align |= MyGUI::Align::Left; if (horizontal == Alignment::End) align |= MyGUI::Align::Right; - if (horizontal == Alignment::Start) + if (vertical == Alignment::Start) align |= MyGUI::Align::Top; - if (horizontal == Alignment::End) + if (vertical == Alignment::End) align |= MyGUI::Align::Bottom; return align; } diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp index 867378744b..11b9c1b360 100644 --- a/components/lua_ui/container.cpp +++ b/components/lua_ui/container.cpp @@ -29,7 +29,7 @@ namespace LuaUi size.width = std::max(size.width, coord.left + coord.width); size.height = std::max(size.height, coord.top + coord.height); } - setForcedSize(size); + forceSize(size); updateCoord(); } } diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 47dd0a3d38..a939ccdc51 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -136,8 +136,8 @@ namespace LuaUi setTemplate(ext, layout.get(LayoutKeys::templateLayout)); ext->setProperties(layout.get(LayoutKeys::props)); setEventCallbacks(ext, layout.get(LayoutKeys::events)); - ext->setChildren(updateContent(ext->children(), layout.get(LayoutKeys::content))); + ext->updateCoord(); } std::string setLayer(WidgetExtension* ext, const sol::table& layout) diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp new file mode 100644 index 0000000000..bbbf70cb98 --- /dev/null +++ b/components/lua_ui/flex.cpp @@ -0,0 +1,106 @@ +#include "flex.hpp" + +namespace LuaUi +{ + void LuaFlex::updateProperties() + { + mHorizontal = propertyValue("horizontal", false); + mAutoSized = propertyValue("autoSize", true); + mAlign = propertyValue("align", Alignment::Start); + mArrange = propertyValue("arrange", Alignment::Start); + WidgetExtension::updateProperties(); + } + + namespace + { + MyGUI::IntPoint alignSize(const MyGUI::IntSize& container, const MyGUI::IntSize& content, Alignment alignment) + { + MyGUI::IntPoint alignedPosition; + { + MyGUI::IntSize alignSize = container; + switch (alignment) + { + case Alignment::Start: + alignedPosition = MyGUI::IntPoint(0, 0); + break; + case Alignment::Center: + alignSize -= content; + alignedPosition = { alignSize.width / 2, alignSize.height / 2 }; + break; + case Alignment::End: + alignSize -= content; + alignedPosition = { alignSize.width, alignSize.height }; + break; + } + } + return alignedPosition; + } + } + + void LuaFlex::updateChildren() + { + float totalGrow = 0; + MyGUI::IntSize childrenSize; + for (auto* w: children()) + { + w->clearForced(); + childrenSize += w->calculateSize(); + totalGrow += w->externalValue("grow", 0.0f); + } + mChildrenSize = childrenSize; + + MyGUI::IntSize flexSize = calculateSize(); + MyGUI::IntSize growSize; + MyGUI::FloatSize growFactor; + if (totalGrow > 0) + { + growSize = flexSize - childrenSize; + growFactor = { growSize.width / totalGrow, growSize.height / totalGrow }; + } + if (mHorizontal) + flexSize.width -= growSize.width; + else + flexSize.height-= growSize.height; + + MyGUI::IntPoint alignedPosition = alignSize(flexSize, childrenSize, mAlign); + MyGUI::IntPoint arrangedPosition = alignSize(flexSize, childrenSize, mArrange); + MyGUI::IntPoint childPosition; + if (mHorizontal) + childPosition = { alignedPosition.left, arrangedPosition.top }; + else + childPosition = { arrangedPosition.left, alignedPosition.top }; + for (auto* w : children()) + { + w->forcePosition(childPosition); + float grow = w->externalValue("grow", 0); + MyGUI::IntSize growth(growFactor.width * grow, growFactor.height * grow); + if (mHorizontal) + { + int width = w->widget()->getWidth(); + width += growth.width; + w->forceSize({width, w->widget()->getHeight()}); + childPosition.left += width; + } + else + { + int height = w->widget()->getHeight(); + height += growth.height; + w->forceSize({ w->widget()->getWidth(), height }); + childPosition.top += height; + } + } + WidgetExtension::updateProperties(); + } + + MyGUI::IntSize LuaFlex::calculateSize() + { + MyGUI::IntSize size = WidgetExtension::calculateSize(); + if (mAutoSized) { + if (mHorizontal) + size.width = mChildrenSize.width; + else + size.height = mChildrenSize.height; + } + return size; + } +} diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp new file mode 100644 index 0000000000..6cff6f0695 --- /dev/null +++ b/components/lua_ui/flex.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_LUAUI_FLEX +#define OPENMW_LUAUI_FLEX + +#include "widget.hpp" +#include "alignment.hpp" + +namespace LuaUi +{ + class LuaFlex : public MyGUI::Widget, public WidgetExtension + { + MYGUI_RTTI_DERIVED(LuaFlex) + + protected: + MyGUI::IntSize calculateSize() override; + void updateProperties() override; + void updateChildren() override; + + private: + bool mHorizontal; + bool mAutoSized; + MyGUI::IntSize mChildrenSize; + Alignment mAlign; + Alignment mArrange; + }; +} + +#endif // OPENMW_LUAUI_FLEX diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index cd2cb2c077..d7e7390647 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -9,6 +9,7 @@ #include "window.hpp" #include "image.hpp" #include "container.hpp" +#include "flex.hpp" #include "element.hpp" #include "registerscriptsettings.hpp" @@ -24,8 +25,9 @@ namespace LuaUi MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("BasisSkin"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } const std::unordered_map& widgetTypeToName() @@ -36,6 +38,7 @@ namespace LuaUi { "LuaTextEdit", "TextEdit" }, { "LuaWindow", "Window" }, { "LuaImage", "Image" }, + { "LuaFlex", "Flex" }, }; return types; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 74ac8640e0..87b1636d74 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -10,7 +10,9 @@ namespace LuaUi { WidgetExtension::WidgetExtension() - : mPropagateEvents(true) + : mForcePosition(false) + , mForceSize(false) + , mPropagateEvents(true) , mLua(nullptr) , mWidget(nullptr) , mSlot(this) @@ -85,7 +87,6 @@ namespace LuaUi ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); - ext->updateCoord(); } void WidgetExtension::attachTemplate(WidgetExtension* ext) @@ -93,7 +94,6 @@ namespace LuaUi ext->mParent = this; ext->mTemplateChild = true; ext->widget()->attachToWidget(widget()); - ext->updateCoord(); } WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) @@ -219,16 +219,30 @@ namespace LuaUi return mForcedCoord; } - void WidgetExtension::setForcedCoord(const MyGUI::IntCoord& offset) + void WidgetExtension::forceCoord(const MyGUI::IntCoord& offset) { + mForcePosition = true; + mForceSize = true; mForcedCoord = offset; } - void WidgetExtension::setForcedSize(const MyGUI::IntSize& size) + void WidgetExtension::forcePosition(const MyGUI::IntPoint& pos) { + mForcePosition = true; + mForcedCoord = pos; + } + + void WidgetExtension::forceSize(const MyGUI::IntSize& size) + { + mForceSize = true; mForcedCoord = size; } + void WidgetExtension::clearForced() { + mForcePosition = false; + mForceSize = false; + } + void WidgetExtension::updateCoord() { MyGUI::IntCoord oldCoord = mWidget->getCoord(); @@ -246,7 +260,6 @@ namespace LuaUi { mProperties = props; updateProperties(); - updateCoord(); } void WidgetExtension::updateProperties() @@ -281,9 +294,12 @@ namespace LuaUi MyGUI::IntSize WidgetExtension::calculateSize() { + if (mForceSize) + return mForcedCoord.size(); + MyGUI::IntSize pSize = parentSize(); MyGUI::IntSize newSize; - newSize = mAbsoluteCoord.size() + mForcedCoord.size(); + newSize = mAbsoluteCoord.size(); newSize.width += mRelativeCoord.width * pSize.width; newSize.height += mRelativeCoord.height * pSize.height; return newSize; @@ -291,9 +307,11 @@ namespace LuaUi MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) { + if (mForcePosition) + return mForcedCoord.point(); MyGUI::IntSize pSize = parentSize(); MyGUI::IntPoint newPosition; - newPosition = mAbsoluteCoord.point() + mForcedCoord.point(); + newPosition = mAbsoluteCoord.point(); newPosition.left += mRelativeCoord.left * pSize.width - mAnchor.width * size.width; newPosition.top += mRelativeCoord.top * pSize.height - mAnchor.height * size.height; return newPosition; diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index c712d18ccd..33bf52cd78 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -47,8 +47,11 @@ namespace LuaUi void setExternal(sol::object external) { mExternal = external; } MyGUI::IntCoord forcedCoord(); - void setForcedCoord(const MyGUI::IntCoord& offset); - void setForcedSize(const MyGUI::IntSize& size); + void forceCoord(const MyGUI::IntCoord& offset); + void forceSize(const MyGUI::IntSize& size); + void forcePosition(const MyGUI::IntPoint& pos); + void clearForced(); + void updateCoord(); const sol::table& getLayout() { return mLayout; } @@ -65,6 +68,9 @@ namespace LuaUi mOnCoordChange = callback; } + virtual MyGUI::IntSize calculateSize(); + virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); + protected: virtual void initialize(); sol::table makeTable() const; @@ -72,8 +78,6 @@ namespace LuaUi sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; MyGUI::IntSize parentSize(); - virtual MyGUI::IntSize calculateSize(); - virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); virtual MyGUI::IntSize childScalingSize(); @@ -113,6 +117,8 @@ namespace LuaUi } } + bool mForcePosition; + bool mForceSize; // offsets the position and size, used only in C++ widget code MyGUI::IntCoord mForcedCoord; // position and size in pixels diff --git a/components/lua_ui/window.cpp b/components/lua_ui/window.cpp index 41281208d4..a9fff5ff47 100644 --- a/components/lua_ui/window.cpp +++ b/components/lua_ui/window.cpp @@ -39,8 +39,7 @@ namespace LuaUi if (mCaption) mCaption->setCaption(propertyValue("caption", std::string())); mMoveResize = MyGUI::IntCoord(); - setForcedCoord(mMoveResize); - + clearForced(); WidgetExtension::updateProperties(); } @@ -70,11 +69,8 @@ namespace LuaUi change.width *= (left - mPreviousMouse.left); change.height *= (top - mPreviousMouse.top); - mMoveResize = mMoveResize + change.size(); - setForcedCoord(mMoveResize); - // position can change based on size changes - mMoveResize = mMoveResize + change.point() + getPosition() - calculateCoord().point(); - setForcedCoord(mMoveResize); + mMoveResize = mMoveResize + change; + forceCoord(mMoveResize); updateCoord(); mPreviousMouse.left = left; From 9d8f6064a171aaeb908913706db05b3d28a68b2f Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 3 Apr 2022 22:35:29 +0300 Subject: [PATCH 2248/2859] Detail the unused parts of controlled.cpp defs --- components/nif/controlled.cpp | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index c8116ce74e..73359aeb2a 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -34,11 +34,12 @@ namespace Nif mipmap = nif->getUInt(); alpha = nif->getUInt(); - nif->getChar(); // always 1 + // Renderer hints, typically of no use for us + /* bool mIsStatic = */nif->getChar(); if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,103)) - nif->getBoolean(); // Direct rendering + /* bool mDirectRendering = */nif->getBoolean(); if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,4)) - nif->getBoolean(); // NiPersistentSrcTextureRendererData is used instead of NiPixelData + /* bool mPersistRenderData = */nif->getBoolean(); } void NiSourceTexture::post(NIFFile *nif) @@ -99,22 +100,21 @@ namespace Nif NiParticleModifier::read(nif); mBounceFactor = nif->getFloat(); - if (nif->getVersion() >= NIFStream::generateVersion(4,2,2,0)) + if (nif->getVersion() >= NIFStream::generateVersion(4,2,0,2)) { // Unused in NifSkope. Need to figure out what these do. - /*bool spawnOnCollision = */nif->getBoolean(); - /*bool dieOnCollision = */nif->getBoolean(); + /*bool mSpawnOnCollision = */nif->getBoolean(); + /*bool mDieOnCollision = */nif->getBoolean(); } } void NiPlanarCollider::read(NIFStream *nif) { NiParticleCollider::read(nif); - - /*unknown*/nif->getFloat(); - - for (int i=0;i<10;++i) - /*unknown*/nif->getFloat(); + /* osg::Vec2f mExtents = */nif->getVector2(); + /* osg::Vec3f mPosition = */nif->getVector3(); + /* osg::Vec3f mXVector = */nif->getVector3(); + /* osg::Vec3f mYVector = */nif->getVector3(); mPlaneNormal = nif->getVector3(); mPlaneDistance = nif->getFloat(); @@ -124,12 +124,9 @@ namespace Nif { NiParticleModifier::read(nif); - /* - byte (0 or 1) - float (1) - float*3 - */ - nif->skip(17); + /* bool mRandomInitialAxis = */nif->getChar(); + /* osg::Vec3f mInitialAxis = */nif->getVector3(); + /* float mRotationSpeed = */nif->getFloat(); } void NiSphericalCollider::read(NIFStream* nif) From c28f997c8723cdf0ae561f205229e5b4d2fcf7fe Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Sun, 3 Apr 2022 23:05:01 +0300 Subject: [PATCH 2249/2859] Make NiPlanarCollider finite-size --- components/nif/controlled.cpp | 8 ++--- components/nif/controlled.hpp | 7 +++-- components/nifosg/particle.cpp | 57 ++++++++++++++++++++++++---------- components/nifosg/particle.hpp | 11 ++++--- 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index 73359aeb2a..0491dd7d98 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -111,11 +111,11 @@ namespace Nif void NiPlanarCollider::read(NIFStream *nif) { NiParticleCollider::read(nif); - /* osg::Vec2f mExtents = */nif->getVector2(); - /* osg::Vec3f mPosition = */nif->getVector3(); - /* osg::Vec3f mXVector = */nif->getVector3(); - /* osg::Vec3f mYVector = */nif->getVector3(); + mExtents = nif->getVector2(); + mPosition = nif->getVector3(); + mXVector = nif->getVector3(); + mYVector = nif->getVector3(); mPlaneNormal = nif->getVector3(); mPlaneDistance = nif->getFloat(); } diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 0d93381eab..5094f1fd87 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -131,10 +131,13 @@ struct NiParticleCollider : public NiParticleModifier // NiPinaColada struct NiPlanarCollider : public NiParticleCollider { - void read(NIFStream *nif) override; - + osg::Vec2f mExtents; + osg::Vec3f mPosition; + osg::Vec3f mXVector, mYVector; osg::Vec3f mPlaneNormal; float mPlaneDistance; + + void read(NIFStream *nif) override; }; struct NiSphericalCollider : public NiParticleCollider diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 493143a7a3..3b66a956e8 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -468,18 +468,24 @@ void FindGroupByRecIndex::applyNode(osg::Node &searchNode) PlanarCollider::PlanarCollider(const Nif::NiPlanarCollider *collider) : mBounceFactor(collider->mBounceFactor) + , mExtents(collider->mExtents) + , mPosition(collider->mPosition) + , mXVector(collider->mXVector) + , mYVector(collider->mYVector) , mPlane(-collider->mPlaneNormal, collider->mPlaneDistance) { } -PlanarCollider::PlanarCollider() - : mBounceFactor(0.f) -{ -} - PlanarCollider::PlanarCollider(const PlanarCollider ©, const osg::CopyOp ©op) : osgParticle::Operator(copy, copyop) , mBounceFactor(copy.mBounceFactor) + , mExtents(copy.mExtents) + , mPosition(copy.mPosition) + , mPositionInParticleSpace(copy.mPositionInParticleSpace) + , mXVector(copy.mXVector) + , mXVectorInParticleSpace(copy.mXVectorInParticleSpace) + , mYVector(copy.mYVector) + , mYVectorInParticleSpace(copy.mYVectorInParticleSpace) , mPlane(copy.mPlane) , mPlaneInParticleSpace(copy.mPlaneInParticleSpace) { @@ -487,25 +493,44 @@ PlanarCollider::PlanarCollider(const PlanarCollider ©, const osg::CopyOp &co void PlanarCollider::beginOperate(osgParticle::Program *program) { + mPositionInParticleSpace = mPosition; mPlaneInParticleSpace = mPlane; + mXVectorInParticleSpace = mXVector; + mYVectorInParticleSpace = mYVector; if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF) + { + mPositionInParticleSpace = program->transformLocalToWorld(mPosition); mPlaneInParticleSpace.transform(program->getLocalToWorldMatrix()); + mXVectorInParticleSpace = program->rotateLocalToWorld(mXVector); + mYVectorInParticleSpace = program->rotateLocalToWorld(mYVector); + } } void PlanarCollider::operate(osgParticle::Particle *particle, double dt) { - float dotproduct = particle->getVelocity() * mPlaneInParticleSpace.getNormal(); + // Does the particle in question move towards the collider? + float velDotProduct = particle->getVelocity() * mPlaneInParticleSpace.getNormal(); + if (velDotProduct <= 0) + return; - if (dotproduct > 0) - { - osg::BoundingSphere bs(particle->getPosition(), 0.f); - if (mPlaneInParticleSpace.intersect(bs) == 1) - { - osg::Vec3 reflectedVelocity = particle->getVelocity() - mPlaneInParticleSpace.getNormal() * (2 * dotproduct); - reflectedVelocity *= mBounceFactor; - particle->setVelocity(reflectedVelocity); - } - } + // Does it intersect the collider's plane? + osg::BoundingSphere bs(particle->getPosition(), 0.f); + if (mPlaneInParticleSpace.intersect(bs) != 1) + return; + + // Is it inside the collider's bounds? + osg::Vec3f relativePos = particle->getPosition() - mPositionInParticleSpace; + float xDotProduct = relativePos * mXVectorInParticleSpace; + float yDotProduct = relativePos * mYVectorInParticleSpace; + if (-mExtents.x() * 0.5f > xDotProduct || mExtents.x() * 0.5f < xDotProduct) + return; + if (-mExtents.y() * 0.5f > yDotProduct || mExtents.y() * 0.5f < yDotProduct) + return; + + // Deflect the particle + osg::Vec3 reflectedVelocity = particle->getVelocity() - mPlaneInParticleSpace.getNormal() * (2 * velDotProduct); + reflectedVelocity *= mBounceFactor; + particle->setVelocity(reflectedVelocity); } SphericalCollider::SphericalCollider(const Nif::NiSphericalCollider* collider) diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 9e68cdf347..155d35ad2c 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -104,7 +104,7 @@ namespace NifOsg { public: PlanarCollider(const Nif::NiPlanarCollider* collider); - PlanarCollider(); + PlanarCollider() = default; PlanarCollider(const PlanarCollider& copy, const osg::CopyOp& copyop); META_Object(NifOsg, PlanarCollider) @@ -113,9 +113,12 @@ namespace NifOsg void operate(osgParticle::Particle* particle, double dt) override; private: - float mBounceFactor; - osg::Plane mPlane; - osg::Plane mPlaneInParticleSpace; + float mBounceFactor{0.f}; + osg::Vec2f mExtents; + osg::Vec3f mPosition, mPositionInParticleSpace; + osg::Vec3f mXVector, mXVectorInParticleSpace; + osg::Vec3f mYVector, mYVectorInParticleSpace; + osg::Plane mPlane, mPlaneInParticleSpace; }; class SphericalCollider : public osgParticle::Operator From 3c74a20335109e16600144679327b5aa3e9db029 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 5 Apr 2022 17:26:55 +0200 Subject: [PATCH 2250/2859] Refactor horizontal/vertical cases in Flex --- components/lua_ui/flex.cpp | 67 +++++++++++++------------------------- components/lua_ui/flex.hpp | 64 ++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 44 deletions(-) diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index bbbf70cb98..eb37a05fbf 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -13,23 +13,20 @@ namespace LuaUi namespace { - MyGUI::IntPoint alignSize(const MyGUI::IntSize& container, const MyGUI::IntSize& content, Alignment alignment) + int alignSize(int container, int content, Alignment alignment) { - MyGUI::IntPoint alignedPosition; + int alignedPosition = 0; { - MyGUI::IntSize alignSize = container; switch (alignment) { case Alignment::Start: - alignedPosition = MyGUI::IntPoint(0, 0); + alignedPosition = 0; break; case Alignment::Center: - alignSize -= content; - alignedPosition = { alignSize.width / 2, alignSize.height / 2 }; + alignedPosition = (container - content) / 2; break; case Alignment::End: - alignSize -= content; - alignedPosition = { alignSize.width, alignSize.height }; + alignedPosition = container - content; break; } } @@ -44,50 +41,34 @@ namespace LuaUi for (auto* w: children()) { w->clearForced(); - childrenSize += w->calculateSize(); - totalGrow += w->externalValue("grow", 0.0f); + MyGUI::IntSize size = w->calculateSize(); + setPrimary(childrenSize, getPrimary(childrenSize) + getPrimary(size)); + setSecondary(childrenSize, std::max(getSecondary(childrenSize), getSecondary(size))); + totalGrow += std::max(0.0f, w->externalValue("grow", 0.0f)); } mChildrenSize = childrenSize; MyGUI::IntSize flexSize = calculateSize(); - MyGUI::IntSize growSize; - MyGUI::FloatSize growFactor; + int growSize = 0; + float growFactor = 0; if (totalGrow > 0) { - growSize = flexSize - childrenSize; - growFactor = { growSize.width / totalGrow, growSize.height / totalGrow }; + growSize = getPrimary(flexSize) - getPrimary(childrenSize); + growFactor = growSize / totalGrow; } - if (mHorizontal) - flexSize.width -= growSize.width; - else - flexSize.height-= growSize.height; + setPrimary(flexSize, getPrimary(flexSize) - growSize); - MyGUI::IntPoint alignedPosition = alignSize(flexSize, childrenSize, mAlign); - MyGUI::IntPoint arrangedPosition = alignSize(flexSize, childrenSize, mArrange); MyGUI::IntPoint childPosition; - if (mHorizontal) - childPosition = { alignedPosition.left, arrangedPosition.top }; - else - childPosition = { arrangedPosition.left, alignedPosition.top }; + setPrimary(childPosition, alignSize(getPrimary(flexSize), getPrimary(childrenSize), mAlign)); + setSecondary(childPosition, alignSize(getSecondary(flexSize), getSecondary(childrenSize), mArrange)); for (auto* w : children()) { w->forcePosition(childPosition); - float grow = w->externalValue("grow", 0); - MyGUI::IntSize growth(growFactor.width * grow, growFactor.height * grow); - if (mHorizontal) - { - int width = w->widget()->getWidth(); - width += growth.width; - w->forceSize({width, w->widget()->getHeight()}); - childPosition.left += width; - } - else - { - int height = w->widget()->getHeight(); - height += growth.height; - w->forceSize({ w->widget()->getWidth(), height }); - childPosition.top += height; - } + MyGUI::IntSize size = w->widget()->getSize(); + float grow = std::max(0.0f, w->externalValue("grow", 0.0f)); + setPrimary(size, getPrimary(size) + static_cast(growFactor * grow)); + w->forceSize(size); + setPrimary(childPosition, getPrimary(childPosition) + getPrimary(size)); } WidgetExtension::updateProperties(); } @@ -96,10 +77,8 @@ namespace LuaUi { MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) { - if (mHorizontal) - size.width = mChildrenSize.width; - else - size.height = mChildrenSize.height; + setPrimary(size, getPrimary(mChildrenSize)); + setSecondary(size, std::max(getSecondary(size), getSecondary(mChildrenSize))); } return size; } diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp index 6cff6f0695..0ac8963f73 100644 --- a/components/lua_ui/flex.hpp +++ b/components/lua_ui/flex.hpp @@ -14,6 +14,10 @@ namespace LuaUi MyGUI::IntSize calculateSize() override; void updateProperties() override; void updateChildren() override; + MyGUI::IntSize childScalingSize() override + { + return MyGUI::IntSize(); + } private: bool mHorizontal; @@ -21,6 +25,66 @@ namespace LuaUi MyGUI::IntSize mChildrenSize; Alignment mAlign; Alignment mArrange; + + template + inline T getPrimary(const MyGUI::types::TPoint& point) + { + return mHorizontal ? point.left : point.top; + } + + template + inline T getSecondary(const MyGUI::types::TPoint& point) + { + return mHorizontal ? point.top : point.left; + } + + template + inline void setPrimary(MyGUI::types::TPoint& point, T value) + { + if (mHorizontal) + point.left = value; + else + point.top = value; + } + + template + inline void setSecondary(MyGUI::types::TPoint& point, T value) + { + if (mHorizontal) + point.top = value; + else + point.left = value; + } + + template + inline T getPrimary(const MyGUI::types::TSize& point) + { + return mHorizontal ? point.width : point.height; + } + + template + inline T getSecondary(const MyGUI::types::TSize& point) + { + return mHorizontal ? point.height : point.width; + } + + template + inline void setPrimary(MyGUI::types::TSize& point, T value) + { + if (mHorizontal) + point.width = value; + else + point.height = value; + } + + template + inline void setSecondary(MyGUI::types::TSize& point, T value) + { + if (mHorizontal) + point.height = value; + else + point.width = value; + } }; } From 3580f8ab437a82b6afa6fb15b76d8f719547f8f2 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 5 Apr 2022 17:52:23 +0200 Subject: [PATCH 2251/2859] Add Flex widget documentation --- .../lua-scripting/user_interface.rst | 1 + .../reference/lua-scripting/widgets/flex.rst | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 docs/source/reference/lua-scripting/widgets/flex.rst diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 71ad61b410..394c53e688 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -81,6 +81,7 @@ Widget types Text: Displays text. TextEdit: Accepts text input from the user. Image: Renders a texture. + Flex: Aligns children in a column/row Example ------- diff --git a/docs/source/reference/lua-scripting/widgets/flex.rst b/docs/source/reference/lua-scripting/widgets/flex.rst new file mode 100644 index 0000000000..359d0d4394 --- /dev/null +++ b/docs/source/reference/lua-scripting/widgets/flex.rst @@ -0,0 +1,43 @@ +Flex Widget +=========== + +Aligns its children along either a column or a row, depending on the `horizontal` property. + +Properties +---------- + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - horizontal + - bool (false) + - Flex aligns its children in a row if true, otherwise in a column. + * - autoSize + - bool (true) + - | If true, Flex will automatically resize to fit its contents. + | Children can't be relatively position/sized when true. + * - align + - ui.ALIGNMENT (Start) + - Where to align the children in the main axis. + * - arrange + - ui.ALIGNMETN (Start) + - How to arrange the children in the cross axis. + +External +-------- +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default value) + - description + * - grow + - float (0) + - | Grow factor for the child. If there is unused space in the Flex, + | it will be split between widgets according to this value. + | Has no effect if `autoSize` is `true`. From 52743dadf311ad002f38bae3c707cd21c0cc408e Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 5 Apr 2022 18:35:09 +0000 Subject: [PATCH 2252/2859] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5c0acda625..5cde48b096 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: # These can be specified per job or per pipeline ARTIFACT_COMPRESSION_LEVEL: "fast" CACHE_COMPRESSION_LEVEL: "fast" - SAST_EXCLUDED_ANALYZERS: bandit,eslint + SAST_EXCLUDED_ANALYZERS: "bandit" SAST_EXCLUDED_PATHS: spec,test,tests,tmp,extern .Ubuntu_Image: From 81e9212db9369028427235f4a8c26bbb9222a503 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 6 Apr 2022 05:44:03 +0300 Subject: [PATCH 2253/2859] Slightly optimize MergeGroupsVisitor's xenophobia --- components/sceneutil/optimizer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 748ceee952..ae0985d57c 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -1935,8 +1935,8 @@ bool Optimizer::MergeGroupsVisitor::isOperationPermissible(osg::Group& node) return !node.getCullCallback() && !node.getEventCallback() && !node.getUpdateCallback() && - isOperationPermissibleForObject(&node) && - typeid(node)==typeid(osg::Group); + typeid(node)==typeid(osg::Group) && + isOperationPermissibleForObject(&node); } void Optimizer::MergeGroupsVisitor::apply(osg::LOD &lod) From eafa66ff40eabc8006ea221da64470a63ed5d047 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 6 Apr 2022 06:42:06 +0300 Subject: [PATCH 2254/2859] Don't consider NiCollisionSwitch unoptimizeable --- components/nifosg/nifloader.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3007bc6cf2..8ed17f3901 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -517,13 +517,6 @@ namespace NifOsg if (!node) node = new NifOsg::MatrixTransform(nifNode->trafo); - if (nifNode->recType == Nif::RC_NiCollisionSwitch && !(nifNode->flags & Nif::NiNode::Flag_ActiveCollision)) - { - node->setNodeMask(Loader::getIntersectionDisabledNodeMask()); - // This node must not be combined with another node. - dataVariance = osg::Object::DYNAMIC; - } - node->setDataVariance(dataVariance); return node; @@ -639,6 +632,9 @@ namespace NifOsg node->setNodeMask(Loader::getHiddenNodeMask()); } + if (nifNode->recType == Nif::RC_NiCollisionSwitch && !(nifNode->flags & Nif::NiNode::Flag_ActiveCollision)) + node->setNodeMask(Loader::getIntersectionDisabledNodeMask()); + osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); From 98b2ddfd4ca5d02b54619725b1caa955078adb2a Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Wed, 6 Apr 2022 15:27:05 +0300 Subject: [PATCH 2255/2859] Make console and maximized window defaults mimic Morrowind --- CHANGELOG.md | 1 + .../reference/modding/settings/windows.rst | 58 ++++----- files/settings-default.cfg | 114 +++++++++--------- 3 files changed, 87 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c665740ac..03777cbf87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,7 @@ Bug #6559: Weapon condition inconsistency between melee and ranged critical / sneak / KO attacks Bug #6579: OpenMW compilation error when using OSG doubles for BoundingSphere Bug #6606: Quests with multiple IDs cannot always be restarted + Bug #6653: With default settings the in-game console doesn't fit into screen Bug #6655: Constant effect absorb attribute causes the game to break Bug #6670: Dialogue order is incorrect Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer diff --git a/docs/source/reference/modding/settings/windows.rst b/docs/source/reference/modding/settings/windows.rst index 2a60745c2e..00ee78a86d 100644 --- a/docs/source/reference/modding/settings/windows.rst +++ b/docs/source/reference/modding/settings/windows.rst @@ -16,8 +16,8 @@ For example, to configure the alchemy window, the actual settings would be:: alchemy x = 0.25 alchemy y = 0.25 - alchemy h = 0.5 alchemy w = 0.5 + alchemy h = 0.5 Each window in the GUI mode remembers it's previous location when exiting the game. By far the easiest way to configure these settings is to simply move the windows around in game. @@ -49,9 +49,9 @@ stats y = 0.015 - h = 0.45 + w = 0.45 - w = 0.4275 + h = 0.4275 pin = false @@ -66,9 +66,9 @@ spells y = 0.39 - h = 0.36 + w = 0.36 - w = 0.51 + h = 0.51 pin = false @@ -83,9 +83,9 @@ map y = 0.015 - h = 0.36 + w = 0.36 - w = 0.37 + h = 0.37 pin = false @@ -100,9 +100,9 @@ inventory y = 0.54 - h = 0.45 + w = 0.45 - w = 0.38 + h = 0.38 pin = false @@ -117,9 +117,9 @@ inventory container y = 0.54 - h = 0.45 + w = 0.45 - w = 0.38 + h = 0.38 The player's inventory window while searching a container, showing the contents of the character's inventory. Activated by clicking on a container. The same window is used for searching dead bodies, and pickpocketing people. @@ -132,9 +132,9 @@ inventory barter y = 0.54 - h = 0.45 + w = 0.45 - w = 0.38 + h = 0.38 The player's inventory window while bartering. It displays goods owned by the character while bartering. Activated by clicking on the Barter choice in the dialog window for an NPC. @@ -147,9 +147,9 @@ inventory companion y = 0.54 - h = 0.45 + w = 0.45 - w = 0.38 + h = 0.38 The player's inventory window while interacting with a companion. The companion windows were added in the Tribunal expansion, but are available everywhere in the OpenMW engine. @@ -162,9 +162,9 @@ container y = 0.54 - h = 0.39 + w = 0.39 - w = 0.38 + h = 0.38 The container window, showing the contents of the container. Activated by clicking on a container. The same window is used for searching dead bodies, and pickpocketing people. @@ -177,9 +177,9 @@ barter y = 0.27 - h = 0.38 + w = 0.38 - w = 0.63 + h = 0.63 The NPC bartering window, displaying goods owned by the shopkeeper while bartering. Activated by clicking on the Barter choice in the dialog window for an NPC. @@ -192,9 +192,9 @@ companion y = 0.27 - h = 0.38 + w = 0.38 - w = 0.63 + h = 0.63 The NPC's inventory window while interacting with a companion. The companion windows were added in the Tribunal expansion, but are available everywhere in the OpenMW engine. @@ -207,9 +207,9 @@ dialogue y = 0.5 - h = 0.7 + w = 0.7 - w = 0.45 + h = 0.45 The dialogue window, for talking with NPCs. Activated by clicking on a NPC. @@ -222,10 +222,10 @@ alchemy y = 0.25 - h = 0.5 - w = 0.5 + h = 0.5 + The alchemy window, for crafting potions. Activated by dragging an alchemy tool on to the rag doll. Unlike most other windows, this window hides all other windows when opened. @@ -234,13 +234,13 @@ console ------- :Default: - x = 0.015 + x = 0.255 - y = 0.015 + y = 0.215 - h = 1.0 + w = 0.49 - w = 0.5 + h = 0.3125 The console command window. Activated by pressing the tilde (~) key. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f76b67a7e5..ecf5e1ec40 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -672,10 +672,10 @@ stats x = 0.015 stats y = 0.015 stats w = 0.4275 stats h = 0.45 -stats maximized x = 0.0 -stats maximized y = 0.0 -stats maximized w = 1.0 -stats maximized h = 1.0 +stats maximized x = 0.255 +stats maximized y = 0.275 +stats maximized w = 0.4275 +stats maximized h = 0.45 stats pin = false stats hidden = false stats maximized = false @@ -685,10 +685,10 @@ spells x = 0.63 spells y = 0.39 spells w = 0.36 spells h = 0.51 -spells maximized x = 0.0 -spells maximized y = 0.0 -spells maximized w = 1.0 -spells maximized h = 1.0 +spells maximized x = 0.32 +spells maximized y = 0.02 +spells maximized w = 0.36 +spells maximized h = 0.88 spells pin = false spells hidden = false spells maximized = false @@ -698,23 +698,23 @@ map x = 0.63 map y = 0.015 map w = 0.36 map h = 0.37 -map maximized x = 0.0 -map maximized y = 0.0 -map maximized w = 1.0 -map maximized h = 1.0 +map maximized x = 0.015 +map maximized y = 0.02 +map maximized w = 0.97 +map maximized h = 0.875 map pin = false map hidden = false map maximized = false # Player inventory window when explicitly opened. -inventory x = 0.0 +inventory x = 0.015 inventory y = 0.54 inventory w = 0.45 inventory h = 0.38 -inventory maximized x = 0.0 -inventory maximized y = 0.0 -inventory maximized w = 1.0 -inventory maximized h = 1.0 +inventory maximized x = 0.015 +inventory maximized y = 0.02 +inventory maximized w = 0.97 +inventory maximized h = 0.875 inventory pin = false inventory hidden = false inventory maximized = false @@ -724,10 +724,10 @@ inventory container x = 0.015 inventory container y = 0.54 inventory container w = 0.45 inventory container h = 0.38 -inventory container maximized x = 0.0 -inventory container maximized y = 0.5 -inventory container maximized w = 1.0 -inventory container maximized h = 0.5 +inventory container maximized x = 0.015 +inventory container maximized y = 0.02 +inventory container maximized w = 0.97 +inventory container maximized h = 0.875 inventory container maximized = false # Player inventory window when bartering with a shopkeeper. @@ -735,10 +735,10 @@ inventory barter x = 0.015 inventory barter y = 0.54 inventory barter w = 0.45 inventory barter h = 0.38 -inventory barter maximized x = 0.0 -inventory barter maximized y = 0.5 -inventory barter maximized w = 1.0 -inventory barter maximized h = 0.5 +inventory barter maximized x = 0.015 +inventory barter maximized y = 0.02 +inventory barter maximized w = 0.97 +inventory barter maximized h = 0.875 inventory barter maximized = false # Player inventory window when trading with a companion. @@ -746,10 +746,10 @@ inventory companion x = 0.015 inventory companion y = 0.54 inventory companion w = 0.45 inventory companion h = 0.38 -inventory companion maximized x = 0.0 -inventory companion maximized y = 0.5 -inventory companion maximized w = 1.0 -inventory companion maximized h = 0.5 +inventory companion maximized x = 0.015 +inventory companion maximized y = 0.02 +inventory companion maximized w = 0.97 +inventory companion maximized h = 0.875 inventory companion maximized = false # Dialog window for talking with NPCs. @@ -757,10 +757,10 @@ dialogue x = 0.15 dialogue y = 0.5 dialogue w = 0.7 dialogue h = 0.45 -dialogue maximized x = 0.0 -dialogue maximized y = 0.0 -dialogue maximized w = 1.0 -dialogue maximized h = 1.0 +dialogue maximized x = 0.015 +dialogue maximized y = 0.02 +dialogue maximized w = 0.97 +dialogue maximized h = 0.875 dialogue maximized = false # Alchemy window for crafting potions. @@ -768,21 +768,21 @@ alchemy x = 0.25 alchemy y = 0.25 alchemy w = 0.5 alchemy h = 0.5 -alchemy maximized x = 0.0 -alchemy maximized y = 0.0 -alchemy maximized w = 1.0 -alchemy maximized h = 1.0 +alchemy maximized x = 0.015 +alchemy maximized y = 0.02 +alchemy maximized w = 0.97 +alchemy maximized h = 0.875 alchemy maximized = false # Console command window for debugging commands. -console x = 0.015 -console y = 0.015 -console w = 1.0 -console h = 0.5 -console maximized x = 0.0 -console maximized y = 0.0 -console maximized w = 1.0 -console maximized h = 1.0 +console x = 0.255 +console y = 0.215 +console w = 0.49 +console h = 0.3125 +console maximized x = 0.015 +console maximized y = 0.02 +console maximized w = 0.97 +console maximized h = 0.875 console maximized = false # Container inventory when searching a container. @@ -790,10 +790,10 @@ container x = 0.49 container y = 0.54 container w = 0.39 container h = 0.38 -container maximized x = 0.0 -container maximized y = 0.0 -container maximized w = 1.0 -container maximized h = 0.5 +container maximized x = 0.015 +container maximized y = 0.02 +container maximized w = 0.97 +container maximized h = 0.875 container maximized = false # NPC inventory window when bartering with a shopkeeper. @@ -801,10 +801,10 @@ barter x = 0.6 barter y = 0.27 barter w = 0.38 barter h = 0.63 -barter maximized x = 0.0 -barter maximized y = 0.0 -barter maximized w = 1.0 -barter maximized h = 0.5 +barter maximized x = 0.015 +barter maximized y = 0.02 +barter maximized w = 0.97 +barter maximized h = 0.875 barter maximized = false # NPC inventory window when trading with a companion. @@ -812,10 +812,10 @@ companion x = 0.6 companion y = 0.27 companion w = 0.38 companion h = 0.63 -companion maximized x = 0.0 -companion maximized y = 0.0 -companion maximized w = 1.0 -companion maximized h = 0.5 +companion maximized x = 0.015 +companion maximized y = 0.02 +companion maximized w = 0.97 +companion maximized h = 0.875 companion maximized = false [Navigator] From dc20e799e174959b1692eb3c6b10f174e34d5262 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 4 Apr 2022 02:44:53 +0200 Subject: [PATCH 2256/2859] Use static object to register classes --- apps/openmw/mwclass/activator.cpp | 11 ++++------- apps/openmw/mwclass/activator.hpp | 10 +++++----- apps/openmw/mwclass/actor.hpp | 2 +- apps/openmw/mwclass/apparatus.cpp | 11 ++++------- apps/openmw/mwclass/apparatus.hpp | 9 +++++---- apps/openmw/mwclass/armor.cpp | 11 ++++------- apps/openmw/mwclass/armor.hpp | 10 ++++++---- apps/openmw/mwclass/bodypart.cpp | 11 ++++------- apps/openmw/mwclass/bodypart.hpp | 10 ++++++---- apps/openmw/mwclass/book.cpp | 11 ++++------- apps/openmw/mwclass/book.hpp | 10 ++++++---- apps/openmw/mwclass/clothing.cpp | 11 ++++------- apps/openmw/mwclass/clothing.hpp | 10 ++++++---- apps/openmw/mwclass/container.cpp | 10 ++-------- apps/openmw/mwclass/container.hpp | 14 +++++++------- apps/openmw/mwclass/creature.cpp | 12 +++++------- apps/openmw/mwclass/creature.hpp | 10 +++++++--- apps/openmw/mwclass/creaturelevlist.cpp | 12 +++++------- apps/openmw/mwclass/creaturelevlist.hpp | 12 +++++++----- apps/openmw/mwclass/door.cpp | 12 +++++------- apps/openmw/mwclass/door.hpp | 10 ++++++---- apps/openmw/mwclass/ingredient.cpp | 11 ++++------- apps/openmw/mwclass/ingredient.hpp | 10 ++++++---- apps/openmw/mwclass/itemlevlist.cpp | 11 ++++------- apps/openmw/mwclass/itemlevlist.hpp | 10 ++++++---- apps/openmw/mwclass/light.cpp | 11 ++++------- apps/openmw/mwclass/light.hpp | 10 ++++++---- apps/openmw/mwclass/lockpick.cpp | 11 ++++------- apps/openmw/mwclass/lockpick.hpp | 10 ++++++---- apps/openmw/mwclass/misc.cpp | 12 +++++------- apps/openmw/mwclass/misc.hpp | 10 ++++++---- apps/openmw/mwclass/npc.cpp | 10 ++++------ apps/openmw/mwclass/npc.hpp | 10 +++++++--- apps/openmw/mwclass/potion.cpp | 11 ++++------- apps/openmw/mwclass/potion.hpp | 10 ++++++---- apps/openmw/mwclass/probe.cpp | 11 ++++------- apps/openmw/mwclass/probe.hpp | 10 ++++++---- apps/openmw/mwclass/repair.cpp | 11 ++++------- apps/openmw/mwclass/repair.hpp | 10 ++++++---- apps/openmw/mwclass/static.cpp | 11 ++++------- apps/openmw/mwclass/static.hpp | 10 ++++++---- apps/openmw/mwclass/weapon.cpp | 11 ++++------- apps/openmw/mwclass/weapon.hpp | 9 +++++---- apps/openmw/mwworld/class.cpp | 16 ++++++++++------ apps/openmw/mwworld/class.hpp | 9 +++++---- apps/openmw/mwworld/registeredclass.hpp | 23 +++++++++++++++++++++++ 46 files changed, 252 insertions(+), 245 deletions(-) create mode 100644 apps/openmw/mwworld/registeredclass.hpp diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index b1660559e6..ad9a6c76d9 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -28,6 +28,10 @@ namespace MWClass { + Activator::Activator() + : MWWorld::RegisteredClass(ESM::Activator::sRecordId) + { + } void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -84,13 +88,6 @@ namespace MWClass return ref->mBase->mScript; } - void Activator::registerSelf() - { - std::shared_ptr instance (new Activator); - - registerClass (ESM::Activator::sRecordId, instance); - } - bool Activator::hasToolTip (const MWWorld::ConstPtr& ptr) const { return !getName(ptr).empty(); diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 48a679e0b7..d5c251d9d1 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -1,19 +1,21 @@ #ifndef GAME_MWCLASS_ACTIVATOR_H #define GAME_MWCLASS_ACTIVATOR_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Activator : public MWWorld::Class + class Activator final : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Activator(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; static int getSndGenTypeFromName(const std::string &name); public: - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering @@ -36,8 +38,6 @@ namespace MWClass std::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation - static void registerSelf(); - std::string getModel(const MWWorld::ConstPtr &ptr) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 82055e2500..f2d080a64f 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -17,7 +17,7 @@ namespace MWClass { protected: - Actor() = default; + explicit Actor(unsigned type) : Class(type) {} template float getSwimSpeedImpl(const MWWorld::Ptr& ptr, const GMST& gmst, const MWMechanics::MagicEffects& mageffects, float baseSpeed) const diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 76bee280bc..021e81346d 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -18,6 +18,10 @@ namespace MWClass { + Apparatus::Apparatus() + : MWWorld::RegisteredClass(ESM::Apparatus::sRecordId) + { + } void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -65,13 +69,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Apparatus::registerSelf() - { - std::shared_ptr instance (new Apparatus); - - registerClass (ESM::Apparatus::sRecordId, instance); - } - std::string Apparatus::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Apparatus Up"); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 828abef25e..ec84a75196 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -1,12 +1,15 @@ #ifndef GAME_MWCLASS_APPARATUS_H #define GAME_MWCLASS_APPARATUS_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Apparatus : public MWWorld::Class + class Apparatus : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Apparatus(); MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; @@ -33,8 +36,6 @@ namespace MWClass MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const override; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 5dfc5e12f1..cffc112641 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -26,6 +26,10 @@ namespace MWClass { + Armor::Armor() + : MWWorld::RegisteredClass(ESM::Armor::sRecordId) + { + } void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -159,13 +163,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Armor::registerSelf() - { - std::shared_ptr instance (new Armor); - - registerClass (ESM::Armor::sRecordId, instance); - } - std::string Armor::getUpSoundId (const MWWorld::ConstPtr& ptr) const { int es = getEquipmentSkill(ptr); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index f64f138a29..00fb23defa 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_ARMOR_H #define GAME_MWCLASS_ARMOR_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Armor : public MWWorld::Class + class Armor : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Armor(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -46,8 +50,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 06b460f75c..ecc59f5459 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -7,6 +7,10 @@ namespace MWClass { + BodyPart::BodyPart() + : MWWorld::RegisteredClass(ESM::BodyPart::sRecordId) + { + } MWWorld::Ptr BodyPart::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { @@ -32,13 +36,6 @@ namespace MWClass return false; } - void BodyPart::registerSelf() - { - std::shared_ptr instance (new BodyPart); - - registerClass (ESM::BodyPart::sRecordId, instance); - } - std::string BodyPart::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 0e372b884a..fb6c813f53 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -1,13 +1,17 @@ #ifndef GAME_MWCLASS_BODYPART_H #define GAME_MWCLASS_BODYPART_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class BodyPart : public MWWorld::Class + class BodyPart : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + BodyPart(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -21,8 +25,6 @@ namespace MWClass bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - static void registerSelf(); - std::string getModel(const MWWorld::ConstPtr &ptr) const override; }; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index f9350222f1..1c826a049d 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -23,6 +23,10 @@ namespace MWClass { + Book::Book() + : MWWorld::RegisteredClass(ESM::Book::sRecordId) + { + } void Book::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -82,13 +86,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Book::registerSelf() - { - std::shared_ptr instance (new Book); - - registerClass (ESM::Book::sRecordId, instance); - } - std::string Book::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Book Up"); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index f3d34c5168..d78d9c7500 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_BOOK_H #define GAME_MWCLASS_BOOK_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Book : public MWWorld::Class + class Book : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Book(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -30,8 +34,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index a5f5d828c9..02ab337a2b 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -21,6 +21,10 @@ namespace MWClass { + Clothing::Clothing() + : MWWorld::RegisteredClass(ESM::Clothing::sRecordId) + { + } void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -117,13 +121,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Clothing::registerSelf() - { - std::shared_ptr instance (new Clothing); - - registerClass (ESM::Clothing::sRecordId, instance); - } - std::string Clothing::getUpSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 3d5c162aa4..a044b13804 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_CLOTHING_H #define GAME_MWCLASS_CLOTHING_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Clothing : public MWWorld::Class + class Clothing : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Clothing(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -38,8 +42,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index b98ba43df4..296813f155 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -54,8 +54,9 @@ namespace MWClass } Container::Container() + : MWWorld::RegisteredClass(ESM::Container::sRecordId) + , mHarvestEnabled(Settings::Manager::getBool("graphic herbalism", "Game")) { - mHarvestEnabled = Settings::Manager::getBool("graphic herbalism", "Game"); } void Container::ensureCustomData (const MWWorld::Ptr& ptr) const @@ -236,13 +237,6 @@ namespace MWClass return ref->mBase->mScript; } - void Container::registerSelf() - { - std::shared_ptr instance (new Container); - - registerClass (ESM::Container::sRecordId, instance); - } - bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const { if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 0b290a73e1..0da53508d2 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWCLASS_CONTAINER_H #define GAME_MWCLASS_CONTAINER_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" @@ -26,9 +26,13 @@ namespace MWClass friend class Container; }; - class Container : public MWWorld::Class + class Container : public MWWorld::RegisteredClass { - bool mHarvestEnabled; + friend MWWorld::RegisteredClass; + + const bool mHarvestEnabled; + + Container(); void ensureCustomData (const MWWorld::Ptr& ptr) const; @@ -37,8 +41,6 @@ namespace MWClass bool canBeHarvested(const MWWorld::ConstPtr& ptr) const; public: - Container(); - void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering @@ -81,8 +83,6 @@ namespace MWClass void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const override; ///< Write additional state from \a ptr into \a state. - static void registerSelf(); - void respawn (const MWWorld::Ptr& ptr) const override; std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 6a633bc668..68aea5730f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -78,6 +78,11 @@ namespace MWClass { } + Creature::Creature() + : MWWorld::RegisteredClass(ESM::Creature::sRecordId) + { + } + const Creature::GMST& Creature::getGmst() { static const GMST staticGmst = [] @@ -496,13 +501,6 @@ namespace MWClass return isFlagBitSet(ptr, ESM::Creature::Essential); } - void Creature::registerSelf() - { - std::shared_ptr instance (new Creature); - - registerClass (ESM::Creature::sRecordId, instance); - } - float Creature::getMaxSpeed(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 5bb5030348..3b1c070ce7 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWCLASS_CREATURE_H #define GAME_MWCLASS_CREATURE_H +#include "../mwworld/registeredclass.hpp" + #include "actor.hpp" namespace ESM @@ -10,8 +12,12 @@ namespace ESM namespace MWClass { - class Creature : public Actor + class Creature : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Creature(); + void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; @@ -96,8 +102,6 @@ namespace MWClass float getMaxSpeed (const MWWorld::Ptr& ptr) const override; - static void registerSelf(); - std::string getModel(const MWWorld::ConstPtr &ptr) const override; void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index de40030a80..f22fbaebaa 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -28,6 +28,11 @@ namespace MWClass } }; + CreatureLevList::CreatureLevList() + : MWWorld::RegisteredClass(ESM::CreatureLevList::sRecordId) + { + } + MWWorld::Ptr CreatureLevList::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); @@ -91,13 +96,6 @@ namespace MWClass customData.mSpawn = true; } - void CreatureLevList::registerSelf() - { - std::shared_ptr instance (new CreatureLevList); - - registerClass (ESM::CreatureLevList::sRecordId, instance); - } - void CreatureLevList::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const { // disable for now, too many false positives diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index b3a940682c..81c2f80070 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -1,13 +1,17 @@ #ifndef GAME_MWCLASS_CREATURELEVLIST_H #define GAME_MWCLASS_CREATURELEVLIST_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class CreatureLevList : public MWWorld::Class + class CreatureLevList : public MWWorld::RegisteredClass { - void ensureCustomData (const MWWorld::Ptr& ptr) const; + friend MWWorld::RegisteredClass; + + CreatureLevList(); + + void ensureCustomData (const MWWorld::Ptr& ptr) const; public: @@ -17,8 +21,6 @@ namespace MWClass bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - static void registerSelf(); - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index da8eb193d5..ff4efbc1ba 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -46,6 +46,11 @@ namespace MWClass } }; + Door::Door() + : MWWorld::RegisteredClass(ESM::Door::sRecordId) + { + } + void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) @@ -259,13 +264,6 @@ namespace MWClass return ref->mBase->mScript; } - void Door::registerSelf() - { - std::shared_ptr instance (new Door); - - registerClass (ESM::Door::sRecordId, instance); - } - MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index e09d0de6c0..fd824c4732 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -3,12 +3,16 @@ #include -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Door : public MWWorld::Class + class Door : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Door(); + void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; @@ -46,8 +50,6 @@ namespace MWClass std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr - static void registerSelf(); - std::string getModel(const MWWorld::ConstPtr &ptr) const override; MWWorld::DoorState getDoorState (const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 424996e417..e0e71902c7 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -20,6 +20,10 @@ namespace MWClass { + Ingredient::Ingredient() + : MWWorld::RegisteredClass(ESM::Ingredient::sRecordId) + { + } void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -77,13 +81,6 @@ namespace MWClass return action; } - void Ingredient::registerSelf() - { - std::shared_ptr instance (new Ingredient); - - registerClass (ESM::Ingredient::sRecordId, instance); - } - std::string Ingredient::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Ingredient Up"); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 2aa831f868..2abb2f343c 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_INGREDIENT_H #define GAME_MWCLASS_INGREDIENT_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Ingredient : public MWWorld::Class + class Ingredient : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Ingredient(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -32,8 +36,6 @@ namespace MWClass std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu - - static void registerSelf(); std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index 920c6a5d22..2624be4aa8 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -4,6 +4,10 @@ namespace MWClass { + ItemLevList::ItemLevList() + : MWWorld::RegisteredClass(ESM::ItemLevList::sRecordId) + { + } std::string ItemLevList::getName (const MWWorld::ConstPtr& ptr) const { @@ -14,11 +18,4 @@ namespace MWClass { return false; } - - void ItemLevList::registerSelf() - { - std::shared_ptr instance (new ItemLevList); - - registerClass (ESM::ItemLevList::sRecordId, instance); - } } diff --git a/apps/openmw/mwclass/itemlevlist.hpp b/apps/openmw/mwclass/itemlevlist.hpp index 771f8b7a76..d7377bbcb4 100644 --- a/apps/openmw/mwclass/itemlevlist.hpp +++ b/apps/openmw/mwclass/itemlevlist.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_ITEMLEVLIST_H #define GAME_MWCLASS_ITEMLEVLIST_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class ItemLevList : public MWWorld::Class + class ItemLevList : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + ItemLevList(); + public: std::string getName (const MWWorld::ConstPtr& ptr) const override; @@ -14,8 +18,6 @@ namespace MWClass bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - - static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 070b769b83..90080784a9 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -23,6 +23,10 @@ namespace MWClass { + Light::Light() + : MWWorld::RegisteredClass(ESM::Light::sRecordId) + { + } void Light::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -120,13 +124,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Light::registerSelf() - { - std::shared_ptr instance (new Light); - - registerClass (ESM::Light::sRecordId, instance); - } - std::string Light::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Misc Up"); diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index e8aa4e5878..7a7f670a2a 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_LIGHT_H #define GAME_MWCLASS_LIGHT_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Light : public MWWorld::Class + class Light : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Light(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -44,8 +48,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index b97093ae2c..1e451ca7fe 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -20,6 +20,10 @@ namespace MWClass { + Lockpick::Lockpick() + : MWWorld::RegisteredClass(ESM::Lockpick::sRecordId) + { + } void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -76,13 +80,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Lockpick::registerSelf() - { - std::shared_ptr instance (new Lockpick); - - registerClass (ESM::Lockpick::sRecordId, instance); - } - std::string Lockpick::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Lockpick Up"); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index d4b265e397..b51aaa05a6 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_LOCKPICK_H #define GAME_MWCLASS_LOCKPICK_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Lockpick : public MWWorld::Class + class Lockpick : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Lockpick(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -34,8 +38,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 4fbe8ea04e..759eea5ccc 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -21,6 +21,11 @@ namespace MWClass { + Miscellaneous::Miscellaneous() + : MWWorld::RegisteredClass(ESM::Miscellaneous::sRecordId) + { + } + bool Miscellaneous::isGold (const MWWorld::ConstPtr& ptr) const { return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") @@ -102,13 +107,6 @@ namespace MWClass return value; } - void Miscellaneous::registerSelf() - { - std::shared_ptr instance (new Miscellaneous); - - registerClass (ESM::Miscellaneous::sRecordId, instance); - } - std::string Miscellaneous::getUpSoundId (const MWWorld::ConstPtr& ptr) const { if (isGold(ptr)) diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 18788c7ed8..59c31a219c 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_MISC_H #define GAME_MWCLASS_MISC_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Miscellaneous : public MWWorld::Class + class Miscellaneous : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Miscellaneous(); + public: MWWorld::Ptr copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const override; @@ -30,8 +34,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 94027e5039..cc4b3f7528 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -245,6 +245,10 @@ namespace namespace MWClass { + Npc::Npc() + : MWWorld::RegisteredClass(ESM::NPC::sRecordId) + { + } class NpcCustomData : public MWWorld::TypedCustomData { @@ -1039,12 +1043,6 @@ namespace MWClass return (ref->mBase->mFlags & ESM::NPC::Essential) != 0; } - void Npc::registerSelf() - { - std::shared_ptr instance (new Npc); - registerClass (ESM::NPC::sRecordId, instance); - } - bool Npc::hasToolTip(const MWWorld::ConstPtr& ptr) const { if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode()) diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 612763d12e..574d133934 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWCLASS_NPC_H #define GAME_MWCLASS_NPC_H +#include "../mwworld/registeredclass.hpp" + #include "actor.hpp" namespace ESM @@ -10,8 +12,12 @@ namespace ESM namespace MWClass { - class Npc : public Actor + class Npc : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Npc(); + void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; @@ -125,8 +131,6 @@ namespace MWClass std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const override; - static void registerSelf(); - std::string getModel(const MWWorld::ConstPtr &ptr) const override; float getSkill(const MWWorld::Ptr& ptr, int skill) const override; diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 9e7aeffbe5..6357d8938a 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -22,6 +22,10 @@ namespace MWClass { + Potion::Potion() + : MWWorld::RegisteredClass(ESM::Potion::sRecordId) + { + } void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -70,13 +74,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Potion::registerSelf() - { - std::shared_ptr instance (new Potion); - - registerClass (ESM::Potion::sRecordId, instance); - } - std::string Potion::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Potion Up"); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75b962164b..2b979e2f37 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_POTION_H #define GAME_MWCLASS_POTION_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Potion : public MWWorld::Class + class Potion : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Potion(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -33,8 +37,6 @@ namespace MWClass std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index f705e51435..7db6a3e955 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -20,6 +20,10 @@ namespace MWClass { + Probe::Probe() + : MWWorld::RegisteredClass(ESM::Probe::sRecordId) + { + } void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -76,13 +80,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Probe::registerSelf() - { - std::shared_ptr instance (new Probe); - - registerClass (ESM::Probe::sRecordId, instance); - } - std::string Probe::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Probe Up"); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index ef9273a379..06bd2ababb 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_PROBE_H #define GAME_MWCLASS_PROBE_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Probe : public MWWorld::Class + class Probe : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Probe(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -34,8 +38,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 64fcd08d78..463257696c 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -17,6 +17,10 @@ namespace MWClass { + Repair::Repair() + : MWWorld::RegisteredClass(ESM::Repair::sRecordId) + { + } void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -65,13 +69,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Repair::registerSelf() - { - std::shared_ptr instance (new Repair); - - registerClass (ESM::Repair::sRecordId, instance); - } - std::string Repair::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Repair Up"); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index c403449e18..b79ad2b124 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_REPAIR_H #define GAME_MWCLASS_REPAIR_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Repair : public MWWorld::Class + class Repair : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Repair(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -30,8 +34,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 1fa445e428..8b793dbc67 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -13,6 +13,10 @@ namespace MWClass { + Static::Static() + : MWWorld::RegisteredClass(ESM::Static::sRecordId) + { + } void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -54,13 +58,6 @@ namespace MWClass return false; } - void Static::registerSelf() - { - std::shared_ptr instance (new Static); - - registerClass (ESM::Static::sRecordId, instance); - } - MWWorld::Ptr Static::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index c747eebf2f..6dbeb46662 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -1,12 +1,16 @@ #ifndef GAME_MWCLASS_STATIC_H #define GAME_MWCLASS_STATIC_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Static : public MWWorld::Class + class Static : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + + Static(); + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: @@ -23,8 +27,6 @@ namespace MWClass bool hasToolTip (const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - static void registerSelf(); - std::string getModel(const MWWorld::ConstPtr &ptr) const override; }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index a0ec4bd0e5..261ada1e75 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -26,6 +26,10 @@ namespace MWClass { + Weapon::Weapon() + : MWWorld::RegisteredClass(ESM::Weapon::sRecordId) + { + } void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -121,13 +125,6 @@ namespace MWClass return ref->mBase->mData.mValue; } - void Weapon::registerSelf() - { - std::shared_ptr instance (new Weapon); - - registerClass (ESM::Weapon::sRecordId, instance); - } - std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index db17e6b70f..61b2a70c26 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -1,16 +1,19 @@ #ifndef GAME_MWCLASS_WEAPON_H #define GAME_MWCLASS_WEAPON_H -#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" namespace MWClass { - class Weapon : public MWWorld::Class + class Weapon : public MWWorld::RegisteredClass { + friend MWWorld::RegisteredClass; + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; public: + Weapon(); void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering @@ -45,8 +48,6 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - static void registerSelf(); - std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; ///< Return the pick up sound Id diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 34a2b49c7b..b67f173ee5 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -21,7 +21,11 @@ namespace MWWorld { - std::map > Class::sClasses; + std::map& Class::getClasses() + { + static std::map values; + return values; + } void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { @@ -228,9 +232,10 @@ namespace MWWorld const Class& Class::get (unsigned int key) { - auto iter = sClasses.find (key); + const auto& classes = getClasses(); + auto iter = classes.find(key); - if (iter==sClasses.end()) + if (iter == classes.end()) throw std::logic_error ("Class::get(): unknown class key: " + std::to_string(key)); return *iter->second; @@ -241,10 +246,9 @@ namespace MWWorld throw std::runtime_error ("class does not support persistence"); } - void Class::registerClass(unsigned int key, std::shared_ptr instance) + void Class::registerClass(Class& instance) { - instance->mType = key; - sClasses.insert(std::make_pair(key, instance)); + getClasses().emplace(instance.getType(), &instance); } std::string Class::getUpSoundId (const ConstPtr& ptr) const diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 64ac873960..f12b7ba192 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -54,12 +54,13 @@ namespace MWWorld /// \brief Base class for referenceable esm records class Class { - static std::map > sClasses; - unsigned int mType; + const unsigned mType; + + static std::map& getClasses(); protected: - Class() = default; + explicit Class(unsigned type) : mType(type) {} std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items @@ -340,7 +341,7 @@ namespace MWWorld static const Class& get (unsigned int key); ///< If there is no class for this \a key, an exception is thrown. - static void registerClass (unsigned int key, std::shared_ptr instance); + static void registerClass(Class& instance); virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/registeredclass.hpp b/apps/openmw/mwworld/registeredclass.hpp new file mode 100644 index 0000000000..7ed9632129 --- /dev/null +++ b/apps/openmw/mwworld/registeredclass.hpp @@ -0,0 +1,23 @@ +#ifndef GAME_MWWORLD_REGISTEREDCLASS_H +#define GAME_MWWORLD_REGISTEREDCLASS_H + +#include "class.hpp" + +namespace MWWorld +{ + template + class RegisteredClass : public Base + { + public: + static void registerSelf() + { + static Derived instance; + Base::registerClass(instance); + } + + protected: + explicit RegisteredClass(unsigned type) : Base(type) {} + }; +} + +#endif From a0590d91ceeff9b5c45bbed73b8ffe60cb58508b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 3 Apr 2022 20:06:16 +0200 Subject: [PATCH 2257/2859] Refactor files/builtin_scripts/CMakeLists.txt --- files/builtin_scripts/CMakeLists.txt | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 1f140a4af1..4e1c08ee8d 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -2,29 +2,22 @@ if (NOT DEFINED OPENMW_RESOURCES_ROOT) return() endif() -# Copy resource files into the build directory -set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) -set(DDIRRELATIVE resources/vfs) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "builtin.omwscripts") +set(LUA_BUILTIN_FILES + builtin.omwscripts -set(LUA_AUX_FILES openmw_aux/util.lua openmw_aux/time.lua openmw_aux/calendar.lua -) - -set(DDIRRELATIVE resources/vfs/openmw_aux) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${LUA_AUX_FILES}") -set(LUA_SCRIPTS_FILES scripts/omw/ai.lua scripts/omw/camera.lua scripts/omw/head_bobbing.lua scripts/omw/third_person.lua + + i18n/Calendar/en.lua ) -set(DDIRRELATIVE resources/vfs/scripts/omw) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${LUA_SCRIPTS_FILES}") +foreach (f ${LUA_BUILTIN_FILES}) + copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OPENMW_RESOURCES_ROOT}" "resources/vfs/${f}") +endforeach (f) -set(DDIRRELATIVE resources/vfs/i18n/Calendar) -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "i18n/Calendar/en.lua") From eca64b48e8081763272c3158497b5e73c570b8f5 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 3 Apr 2022 20:07:09 +0200 Subject: [PATCH 2258/2859] Bitwise operations in Lua --- components/lua/luastate.cpp | 2 +- components/lua/utilpackage.cpp | 31 +++++++++++++++++++++++++++++++ files/lua_api/openmw/util.lua | 27 +++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index dbd792224f..4ede81cc02 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -51,7 +51,7 @@ namespace LuaUtil LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf) : mConf(conf), mVFS(vfs) { - mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, + mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string, sol::lib::table, sol::lib::os, sol::lib::debug); mLua["math"]["randomseed"](static_cast(std::time(nullptr))); diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index d31a8259be..2eda03dc52 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -216,6 +216,37 @@ namespace LuaUtil util["normalizeAngle"] = &Misc::normalizeAngle; util["makeReadOnly"] = &makeReadOnly; + if (lua["bit32"] != sol::nil) + { + sol::table bit = lua["bit32"]; + util["bitOr"] = bit["bor"]; + util["bitAnd"] = bit["band"]; + util["bitXor"] = bit["bxor"]; + util["bitNot"] = bit["bnot"]; + } + else + { + util["bitOr"] = [](unsigned a, sol::variadic_args va) + { + for (auto v : va) + a |= v.as(); + return a; + }; + util["bitAnd"] = [](unsigned a, sol::variadic_args va) + { + for (auto v : va) + a &= v.as(); + return a; + }; + util["bitXor"] = [](unsigned a, sol::variadic_args va) + { + for (auto v : va) + a ^= v.as(); + return a; + }; + util["bitNot"] = [](unsigned a) { return ~a; }; + } + return util; } diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index 4ea0ee4b0d..dbb1cbd8ae 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -25,6 +25,33 @@ -- @param #table table Any table. -- @return #table The same table wrapped with read only userdata. +--- +-- Bitwise And (supports any number of arguments). +-- @function [parent=#util] bitAnd +-- @param #number A First argument (integer). +-- @param #number B Second argument (integer). +-- @return #number Bitwise And of A and B. + +--- +-- Bitwise Or (supports any number of arguments). +-- @function [parent=#util] bitOr +-- @param #number A First argument (integer). +-- @param #number B Second argument (integer). +-- @return #number Bitwise Or of A and B. + +--- +-- Bitwise Xor (supports any number of arguments). +-- @function [parent=#util] bitXor +-- @param #number A First argument (integer). +-- @param #number B Second argument (integer). +-- @return #number Bitwise Xor of A and B. + +--- +-- Bitwise inversion. +-- @function [parent=#util] bitNot +-- @param #number A Argument (integer). +-- @return #number Bitwise Not of A. + --- -- Immutable 2D vector From 5ed22c4c7a015f854b667f2f83c1080b1bc07a3e Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 3 Apr 2022 20:07:47 +0200 Subject: [PATCH 2259/2859] Minor fix in actors.cpp --- apps/openmw/mwmechanics/actors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a024507cf3..963fd7b806 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1556,7 +1556,7 @@ namespace MWMechanics mov.mRotation[2] = luaControls->mYawChange; mov.mSpeedFactor = osg::Vec2(luaControls->mMovement, luaControls->mSideMovement).length(); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, luaControls->mRun); - stats.setAttackingOrSpell(luaControls->mUse == 1); + stats.setAttackingOrSpell((luaControls->mUse & 1) == 1); luaControls->mChanged = false; } luaControls->mSideMovement = movement.x(); From 7186ea8ab4f36743fdc9e004cab38988748875eb Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 4 Apr 2022 22:08:44 +0200 Subject: [PATCH 2260/2859] [Lua] New overload `Actor.equipment(actor, slot)`. --- apps/openmw/mwlua/types/actor.cpp | 16 +++++++++++++++- files/lua_api/openmw/types.lua | 8 +++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 321aaa20e6..335ba517d5 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -88,7 +88,7 @@ namespace MWLua [](const LObject& o) { return Inventory{o}; }, [](const GObject& o) { return Inventory{o}; } ); - actor["equipment"] = [context](const Object& o) + auto getAllEquipment = [context](const Object& o) { const MWWorld::Ptr& ptr = o.ptr(); sol::table equipment(context.mLua->sol(), sol::create); @@ -106,6 +106,20 @@ namespace MWLua } return equipment; }; + auto getEquipmentFromSlot = [context](const Object& o, int slot) -> sol::object + { + const MWWorld::Ptr& ptr = o.ptr(); + sol::table equipment(context.mLua->sol(), sol::create); + if (!ptr.getClass().hasInventoryStore(ptr)) + return sol::nil; + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + auto it = store.getSlot(slot); + if (it == store.end()) + return sol::nil; + context.mWorldView->getObjectRegistry()->registerPtr(*it); + return o.getObject(context.mLua->sol(), getId(*it)); + }; + actor["equipment"] = sol::overload(getAllEquipment, getEquipmentFromSlot); actor["hasEquipped"] = [](const Object& o, const Object& item) { const MWWorld::Ptr& ptr = o.ptr(); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index b9b5df44d6..d62d92169d 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -108,12 +108,14 @@ --- -- Get equipment. --- Returns a table `slot` -> @{openmw.core#GameObject} of currently equipped items. +-- Has two overloads: +-- 1) With single argument: returns a table `slot` -> @{openmw.core#GameObject} of currently equipped items. -- See @{#EQUIPMENT_SLOT}. Returns empty table if the actor doesn't have --- equipment slots. +-- equipment slots. +-- 2) With two arguments: returns an item equipped to the given slot. -- @function [parent=#Actor] equipment -- @param openmw.core#GameObject actor --- @return #map<#number,openmw.core#GameObject> +-- @param #number slot (optional argument) --- -- Set equipment. From 3af8ea5dfc4ecf8397947a4f186bfb12bb52cf1a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 3 Apr 2022 20:42:19 +0200 Subject: [PATCH 2261/2859] Update Lua bindings for the camera --- apps/openmw/mwbase/world.hpp | 3 ++ apps/openmw/mwlua/camerabindings.cpp | 30 +++++++++++++++++++ apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwrender/camera.cpp | 9 +++--- apps/openmw/mwrender/camera.hpp | 8 +++++ apps/openmw/mwrender/renderingmanager.cpp | 19 ++++++++++-- apps/openmw/mwrender/renderingmanager.hpp | 3 ++ apps/openmw/mwworld/worldimp.hpp | 2 ++ files/builtin_scripts/scripts/omw/camera.lua | 3 ++ .../scripts/omw/third_person.lua | 2 ++ files/lua_api/openmw/camera.lua | 30 +++++++++++++++++++ 11 files changed, 104 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f86f24310a..625758d640 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -64,6 +64,7 @@ namespace MWRender { class Animation; class Camera; + class RenderingManager; } namespace MWMechanics @@ -664,6 +665,8 @@ namespace MWBase virtual std::vector getAll(const std::string& id) = 0; virtual Misc::Rng::Generator& getPrng() = 0; + + virtual MWRender::RenderingManager* getRenderingManager() = 0; }; } diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index abbd806cef..4e865374cf 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -1,6 +1,10 @@ #include "luabindings.hpp" +#include +#include + #include "../mwrender/camera.hpp" +#include "../mwrender/renderingmanager.hpp" namespace MWLua { @@ -10,6 +14,7 @@ namespace MWLua sol::table initCameraPackage(const Context& context) { MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera(); + MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager(); sol::table api(context.mLua->sol(), sol::create); api["MODE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( @@ -77,6 +82,31 @@ namespace MWLua api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); }; api["instantTransition"] = [camera]() { camera->instantTransition(); }; + api["getCollisionType"] = [camera]() { return camera->getCollisionType(); }; + api["setCollisionType"] = [camera](int collisionType) { camera->setCollisionType(collisionType); }; + + api["getBaseFieldOfView"] = []() + { + return osg::DegreesToRadians(std::clamp(Settings::Manager::getFloat("field of view", "Camera"), 1.f, 179.f)); + }; + api["getFieldOfView"] = [renderingManager]() { return osg::DegreesToRadians(renderingManager->getFieldOfView()); }; + api["setFieldOfView"] = [renderingManager](float v) { renderingManager->setFieldOfView(osg::RadiansToDegrees(v)); }; + + api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{camera->getViewMatrix()}; }; + + api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f + { + double width = Settings::Manager::getInt("resolution x", "Video"); + double height = Settings::Manager::getInt("resolution y", "Video"); + double aspect = (height == 0.0) ? 1.0 : width / height; + double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2); + osg::Matrixf invertedViewMatrix; + invertedViewMatrix.invert(camera->getViewMatrix()); + float x = (pos.x() * 2 - 1) * aspect * fovTan; + float y = (1 - pos.y() * 2) * fovTan; + return invertedViewMatrix.preMult(osg::Vec3f(x, y, -1)) - camera->getPosition(); + }; + return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 9f35866f89..b47e1f2dec 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -41,7 +41,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 19; + api["API_REVISION"] = 20; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 5ca102bc39..7e83a08493 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -53,6 +53,7 @@ namespace MWRender Camera::Camera (osg::Camera* camera) : mHeightScale(1.f), + mCollisionType(MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor), mCamera(camera), mAnimation(nullptr), mFirstPersonView(true), @@ -127,6 +128,7 @@ namespace MWRender pos = calculateFirstPersonPosition(recalculatedTrackedPosition); } cam->setViewMatrixAsLookAt(pos, pos + forward, up); + mViewMatrix = cam->getViewMatrix(); } void Camera::update(float duration, bool paused) @@ -174,7 +176,6 @@ namespace MWRender constexpr float focalObstacleLimit = 10.f; const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); - constexpr int collisionType = (MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor); // Adjust focal point to prevent clipping. osg::Vec3d focalOffset = getFocalPointOffset(); @@ -184,7 +185,7 @@ namespace MWRender float offsetLen = focalOffset.length(); if (offsetLen > 0) { - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, collisionType); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, mCollisionType); if (result.mHit) { double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; @@ -196,7 +197,7 @@ namespace MWRender mCameraDistance = mPreferredCameraDistance; osg::Quat orient = osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1,0,0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0,0,1)); osg::Vec3d offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, collisionType); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, mCollisionType); if (result.mHit) { mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); @@ -211,7 +212,7 @@ namespace MWRender if (mMode == newMode) return; Mode oldMode = mMode; - if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && !mAnimation->upperBodyReady()) + if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && mAnimation && !mAnimation->upperBodyReady()) { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 280d309256..ba815a7e60 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -95,11 +96,17 @@ namespace MWRender void setFirstPersonOffset(const osg::Vec3f& v) { mFirstPersonOffset = v; } osg::Vec3f getFirstPersonOffset() const { return mFirstPersonOffset; } + int getCollisionType() const { return mCollisionType; } + void setCollisionType(int collisionType) { mCollisionType = collisionType; } + + const osg::Matrixf& getViewMatrix() const { return mViewMatrix; } + private: MWWorld::Ptr mTrackingPtr; osg::ref_ptr mTrackingNode; osg::Vec3d mTrackedPosition; float mHeightScale; + int mCollisionType; osg::ref_ptr mCamera; @@ -121,6 +128,7 @@ namespace MWRender float mExtraPitch = 0, mExtraYaw = 0; bool mLockPitch = false, mLockYaw = false; osg::Vec3d mPosition; + osg::Matrixf mViewMatrix; float mCameraDistance, mPreferredCameraDistance; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4ec44f0141..a504a42faf 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -817,6 +817,11 @@ namespace MWRender updateNavMesh(); updateRecastMesh(); + if (mUpdateProjectionMatrix) + { + mUpdateProjectionMatrix = false; + updateProjectionMatrix(); + } mCamera->update(dt, paused); bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); @@ -1161,8 +1166,7 @@ namespace MWRender // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. - fov = std::min(mFieldOfView, 140.f); - float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); + float distanceMult = std::cos(osg::DegreesToRadians(std::min(fov, 140.f))/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } @@ -1300,6 +1304,17 @@ namespace MWRender } } + void RenderingManager::setFieldOfView(float val) + { + mFieldOfView = val; + mUpdateProjectionMatrix = true; + } + + float RenderingManager::getFieldOfView() const + { + return mFieldOfViewOverridden ? mFieldOfViewOverridden : mFieldOfView; + } + osg::Vec3f RenderingManager::getHalfExtents(const MWWorld::ConstPtr& object) const { osg::Vec3f halfExtents(0, 0, 0); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 119c85f82f..b25e675748 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -214,6 +214,8 @@ namespace MWRender /// temporarily override the field of view with given value. void overrideFieldOfView(float val); + void setFieldOfView(float val); + float getFieldOfView() const; /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file. void resetFieldOfView(); @@ -301,6 +303,7 @@ namespace MWRender float mFieldOfViewOverride; float mFieldOfView; float mFirstPersonFieldOfView; + bool mUpdateProjectionMatrix = false; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6eeda94ef3..dc07a9117b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -743,6 +743,8 @@ namespace MWWorld std::vector getAll(const std::string& id) override; Misc::Rng::Generator& getPrng() override; + + MWRender::RenderingManager* getRenderingManager() override { return mRendering.get(); } }; } diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 40b25c6d13..04252e1b47 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -4,6 +4,7 @@ local input = require('openmw.input') local settings = require('openmw.settings') local util = require('openmw.util') local self = require('openmw.self') +local nearby = require('openmw.nearby') local Actor = require('openmw.types').Actor @@ -23,6 +24,8 @@ local noHeadBobbing = 0 local noZoom = 0 local function init() + camera.setCollisionType(util.bitAnd(nearby.COLLISION_TYPE.Default, util.bitNot(nearby.COLLISION_TYPE.Actor))) + camera.setFieldOfView(camera.getBaseFieldOfView()) camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation')) if camera.getMode() == MODE.FirstPerson then primaryMode = MODE.FirstPerson diff --git a/files/builtin_scripts/scripts/omw/third_person.lua b/files/builtin_scripts/scripts/omw/third_person.lua index 95f872b15f..1b833d2322 100644 --- a/files/builtin_scripts/scripts/omw/third_person.lua +++ b/files/builtin_scripts/scripts/omw/third_person.lua @@ -78,6 +78,8 @@ local function updateState() state = STATE.Swimming elseif oldState == STATE.Combat or oldState == STATE.Swimming then state = defaultShoulder + elseif not state then + state = defaultShoulder end if autoSwitchShoulder and (mode == MODE.ThirdPerson or state ~= oldState or noThirdPersonLastFrame) and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then diff --git a/files/lua_api/openmw/camera.lua b/files/lua_api/openmw/camera.lua index 1d09bdf7ad..f744dcbc5c 100644 --- a/files/lua_api/openmw/camera.lua +++ b/files/lua_api/openmw/camera.lua @@ -166,6 +166,36 @@ -- Make instant the current transition of camera focal point and the current deferred rotation (see `allowCharacterDeferredRotation`). -- @function [parent=#camera] instantTransition +--- Get current camera collision type (see @{openmw.nearby#COLLISION_TYPE}). +-- @function [parent=#camera] getCollisionType +-- @return #number + +--- Set camera collision type (see @{openmw.nearby#COLLISION_TYPE}). +-- @function [parent=#camera] setCollisionType +-- @param #number collisionType + +--- Return base field of view vertical angle in radians +-- @function [parent=#camera] getBaseFieldOfView +-- @return #number + +--- Return current field of view vertical angle in radians +-- @function [parent=#camera] getFieldOfView +-- @return #number + +--- Set field of view +-- @function [parent=#camera] setFieldOfView +-- @param #number fov Field of view vertical angle in radians + +--- Get world to local transform for the camera. +-- @function [parent=#camera] getViewTransform +-- @return openmw.util#Transform + +--- Get vector from the camera to the world for the given point in viewport. +-- (0, 0) is the top left corner of the screen. +-- @function [parent=#camera] viewportToWorldVector +-- @param openmw.util#Vector2 normalizedScreenPos +-- @return openmw.util#Vector3 + return nil From a46714df673fc0e1777ba0d9fbe394682a62cf69 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 6 Apr 2022 23:00:13 +0200 Subject: [PATCH 2262/2859] Replace boost::variant by std::variant --- apps/openmw/mwscript/globalscripts.cpp | 20 ++++++++++---------- apps/openmw/mwscript/globalscripts.hpp | 5 ++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 680fd145a0..4a38d09bd0 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -16,7 +16,7 @@ namespace { - struct ScriptCreatingVisitor : public boost::static_visitor + struct ScriptCreatingVisitor { ESM::GlobalScript operator()(const MWWorld::Ptr &ptr) const { @@ -46,7 +46,7 @@ namespace } }; - struct PtrGettingVisitor : public boost::static_visitor + struct PtrGettingVisitor { const MWWorld::Ptr* operator()(const MWWorld::Ptr &ptr) const { @@ -59,7 +59,7 @@ namespace } }; - struct PtrResolvingVisitor : public boost::static_visitor + struct PtrResolvingVisitor { MWWorld::Ptr operator()(const MWWorld::Ptr &ptr) const { @@ -76,7 +76,7 @@ namespace } }; - class MatchPtrVisitor : public boost::static_visitor + class MatchPtrVisitor { const MWWorld::Ptr& mPtr; public: @@ -93,7 +93,7 @@ namespace } }; - struct IdGettingVisitor : public boost::static_visitor + struct IdGettingVisitor { std::string operator()(const MWWorld::Ptr& ptr) const { @@ -115,19 +115,19 @@ namespace MWScript const MWWorld::Ptr* GlobalScriptDesc::getPtrIfPresent() const { - return boost::apply_visitor(PtrGettingVisitor(), mTarget); + return std::visit(PtrGettingVisitor(), mTarget); } MWWorld::Ptr GlobalScriptDesc::getPtr() { - MWWorld::Ptr ptr = boost::apply_visitor(PtrResolvingVisitor(), mTarget); + MWWorld::Ptr ptr = std::visit(PtrResolvingVisitor {}, mTarget); mTarget = ptr; return ptr; } std::string GlobalScriptDesc::getId() const { - return boost::apply_visitor(IdGettingVisitor(), mTarget); + return std::visit(IdGettingVisitor {}, mTarget); } @@ -239,7 +239,7 @@ namespace MWScript { for (const auto& iter : mScripts) { - ESM::GlobalScript script = boost::apply_visitor (ScriptCreatingVisitor(), iter.second->mTarget); + ESM::GlobalScript script = std::visit (ScriptCreatingVisitor {}, iter.second->mTarget); script.mId = iter.first; @@ -338,7 +338,7 @@ namespace MWScript MatchPtrVisitor visitor(base); for (const auto& script : mScripts) { - if (boost::apply_visitor (visitor, script.second->mTarget)) + if (std::visit (visitor, script.second->mTarget)) script.second->mTarget = updated; } } diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index f0eaf88739..8725959e66 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -1,12 +1,11 @@ #ifndef GAME_SCRIPT_GLOBALSCRIPTS_H #define GAME_SCRIPT_GLOBALSCRIPTS_H -#include - #include #include #include #include +#include #include @@ -37,7 +36,7 @@ namespace MWScript { bool mRunning; Locals mLocals; - boost::variant > mTarget; // Used to start targeted script + std::variant> mTarget; // Used to start targeted script GlobalScriptDesc(); From 02bbd226b8ec3af52e300247394c86423d2eb25e Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 6 Apr 2022 23:36:44 +0200 Subject: [PATCH 2263/2859] Less primary/secondary coordinate boilerplate in Flex --- components/lua_ui/flex.cpp | 29 ++++++++++++----------- components/lua_ui/flex.hpp | 48 +++++--------------------------------- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index eb37a05fbf..13acfb3990 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -32,6 +32,11 @@ namespace LuaUi } return alignedPosition; } + + float getGrow(WidgetExtension* w) + { + return std::max(0.0f, w->externalValue("grow", 0.0f)); + } } void LuaFlex::updateChildren() @@ -42,9 +47,9 @@ namespace LuaUi { w->clearForced(); MyGUI::IntSize size = w->calculateSize(); - setPrimary(childrenSize, getPrimary(childrenSize) + getPrimary(size)); - setSecondary(childrenSize, std::max(getSecondary(childrenSize), getSecondary(size))); - totalGrow += std::max(0.0f, w->externalValue("grow", 0.0f)); + primary(childrenSize) += primary(size); + secondary(childrenSize) = std::max(secondary(childrenSize), secondary(size)); + totalGrow += getGrow(w); } mChildrenSize = childrenSize; @@ -53,22 +58,20 @@ namespace LuaUi float growFactor = 0; if (totalGrow > 0) { - growSize = getPrimary(flexSize) - getPrimary(childrenSize); + growSize = primary(flexSize) - primary(childrenSize); growFactor = growSize / totalGrow; - } - setPrimary(flexSize, getPrimary(flexSize) - growSize); + } MyGUI::IntPoint childPosition; - setPrimary(childPosition, alignSize(getPrimary(flexSize), getPrimary(childrenSize), mAlign)); - setSecondary(childPosition, alignSize(getSecondary(flexSize), getSecondary(childrenSize), mArrange)); + primary(childPosition) = alignSize(primary(flexSize) - growSize, primary(childrenSize), mAlign); + secondary(childPosition) = alignSize(secondary(flexSize), secondary(childrenSize), mArrange); for (auto* w : children()) { w->forcePosition(childPosition); MyGUI::IntSize size = w->widget()->getSize(); - float grow = std::max(0.0f, w->externalValue("grow", 0.0f)); - setPrimary(size, getPrimary(size) + static_cast(growFactor * grow)); + primary(size) += static_cast(growFactor * getGrow(w)); w->forceSize(size); - setPrimary(childPosition, getPrimary(childPosition) + getPrimary(size)); + primary(childPosition) += primary(size); } WidgetExtension::updateProperties(); } @@ -77,8 +80,8 @@ namespace LuaUi { MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) { - setPrimary(size, getPrimary(mChildrenSize)); - setSecondary(size, std::max(getSecondary(size), getSecondary(mChildrenSize))); + primary(size) = primary(mChildrenSize); + secondary(size) = std::max(secondary(size), secondary(mChildrenSize)); } return size; } diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp index 0ac8963f73..9ac85213f6 100644 --- a/components/lua_ui/flex.hpp +++ b/components/lua_ui/flex.hpp @@ -27,63 +27,27 @@ namespace LuaUi Alignment mArrange; template - inline T getPrimary(const MyGUI::types::TPoint& point) + T& primary(MyGUI::types::TPoint& point) { return mHorizontal ? point.left : point.top; } template - inline T getSecondary(const MyGUI::types::TPoint& point) + T& secondary(MyGUI::types::TPoint& point) { return mHorizontal ? point.top : point.left; } template - inline void setPrimary(MyGUI::types::TPoint& point, T value) + T& primary(MyGUI::types::TSize& size) { - if (mHorizontal) - point.left = value; - else - point.top = value; + return mHorizontal ? size.width : size.height; } template - inline void setSecondary(MyGUI::types::TPoint& point, T value) + T& secondary(MyGUI::types::TSize& size) { - if (mHorizontal) - point.top = value; - else - point.left = value; - } - - template - inline T getPrimary(const MyGUI::types::TSize& point) - { - return mHorizontal ? point.width : point.height; - } - - template - inline T getSecondary(const MyGUI::types::TSize& point) - { - return mHorizontal ? point.height : point.width; - } - - template - inline void setPrimary(MyGUI::types::TSize& point, T value) - { - if (mHorizontal) - point.width = value; - else - point.height = value; - } - - template - inline void setSecondary(MyGUI::types::TSize& point, T value) - { - if (mHorizontal) - point.height = value; - else - point.width = value; + return mHorizontal ? size.height : size.width; } }; } From ead73fce312a215374fecde6f401761b87f7e793 Mon Sep 17 00:00:00 2001 From: Wolfgang Lieff Date: Tue, 29 Mar 2022 21:47:37 +0200 Subject: [PATCH 2264/2859] initial NiFltAnimationNode support --- apps/openmw/mwrender/objectpaging.cpp | 13 +++++++++++++ components/nif/niffile.cpp | 1 + components/nif/node.hpp | 11 +++++++++++ components/nif/record.hpp | 1 + components/nifosg/nifloader.cpp | 19 +++++++++++++++++++ components/sceneutil/optimizer.cpp | 16 +++++++++++++++- components/sceneutil/optimizer.hpp | 2 ++ 7 files changed, 62 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 4748491dd4..4865f1088e 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -152,6 +153,13 @@ namespace MWRender n->setDataVariance(osg::Object::STATIC); return n; } + if (const osg::Sequence* sq = dynamic_cast(node)) + { + osg::Group* n = new osg::Group; + n->addChild(operator()(sq->getChild(sq->getValue()))); + n->setDataVariance(osg::Object::STATIC); + return n; + } mNodePath.push_back(node); @@ -301,6 +309,11 @@ namespace MWRender traverse(*lod->getChild(i)); return; } + if (osg::Sequence* sq = dynamic_cast(&node)) + { + traverse(*sq->getChild(sq->getValue())); + return; + } traverse(node); } diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 0a1acde5b5..57d1ce6457 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -36,6 +36,7 @@ static std::map makeFactory() {"NiNode" , &construct }, {"NiSwitchNode" , &construct }, {"NiLODNode" , &construct }, + {"NiFltAnimationNode" , &construct }, {"AvoidNode" , &construct }, {"NiCollisionSwitch" , &construct }, {"NiBSParticleNode" , &construct }, diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 34ac12e490..6452e2b8af 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -431,6 +431,17 @@ struct NiLODNode : public NiSwitchNode } }; +struct NiFltAnimationNode : public NiSwitchNode +{ + float Interval; + + void read(NIFStream *nif) override + { + NiSwitchNode::read(nif); + Interval = nif->getFloat(); + } +}; + // Abstract struct NiAccumulator : Record { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 133c393ab5..37084af44e 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -38,6 +38,7 @@ enum RecordType RC_NiNode, RC_NiSwitchNode, RC_NiLODNode, + RC_NiFltAnimationNode, RC_NiBillboardNode, RC_AvoidNode, RC_NiCollisionSwitch, diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3007bc6cf2..9d66d3f4b6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -416,6 +417,17 @@ namespace NifOsg return switchNode; } + static osg::ref_ptr handleSequenceNode(const Nif::NiFltAnimationNode* niFltAnimationNode) + { + osg::ref_ptr sequenceNode (new osg::Sequence); + sequenceNode->setName(niFltAnimationNode->name); + sequenceNode->setDefaultTime(niFltAnimationNode->Interval); + sequenceNode->setInterval(osg::Sequence::LOOP, 0,-1); + sequenceNode->setDuration( -1.0f, -1); + sequenceNode->setMode(osg::Sequence::START); + return sequenceNode; + } + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) { if (!st) @@ -711,6 +723,13 @@ namespace NifOsg node->addChild(lodNode); currentNode = lodNode; } + else if (nifNode->recType == Nif::RC_NiFltAnimationNode) + { + const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); + osg::ref_ptr sequenceNode = handleSequenceNode(niFltAnimationNode); + node->addChild(sequenceNode); + currentNode = sequenceNode; + } const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 748ceee952..00e113afb9 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -845,7 +846,7 @@ void Optimizer::RemoveEmptyNodesVisitor::removeEmptyNodes() ++pitr) { osg::Group* parent = *pitr; - if (!parent->asSwitch() && !dynamic_cast(parent)) + if (!parent->asSwitch() && !dynamic_cast(parent) && !dynamic_cast(parent)) { parent->removeChild(nodeToRemove.get()); if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent); @@ -887,6 +888,13 @@ void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Switch& switchNode) traverse(*switchNode.getChild(i)); } +void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Sequence& sequenceNode) +{ + // We should keep all sequence child nodes since they reflect different sequence states. + for (unsigned int i=0; i Date: Wed, 30 Mar 2022 09:27:00 +0200 Subject: [PATCH 2265/2859] refactor to use mInternal, support Flag_Reverse and updated changelog/authors.md --- AUTHORS.md | 5 +++-- CHANGELOG.md | 3 ++- apps/openmw/mwrender/objectpaging.cpp | 3 +-- components/nif/node.hpp | 9 +++++++-- components/nifosg/nifloader.cpp | 8 +++++--- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 84f465414e..264ffe2668 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -164,6 +164,7 @@ Programmers Nialsy Nick Crawford (nighthawk469) Nikolay Kasyanov (corristo) + Noah Gooder nobrakal Nolan Poe (nopoe) Nurivan Gomez (Nuri-G) @@ -228,14 +229,14 @@ Programmers viadanna Vincent Heuken Vladimir Panteleev (CyberShadow) + vocollapse Wang Ryu (bzzt) Will Herrmann (Thunderforge) - vocollapse + Wolfgang Lieff xyzz Yohaulticetl Yuri Krupenin zelurker - Noah Gooder Documentation ------------- diff --git a/CHANGELOG.md b/CHANGELOG.md index 03777cbf87..952dce3495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,11 +138,12 @@ Feature #6288: Preserve the "blocked" record flag for referenceable objects. Feature #6380: Commas are treated as whitespace in vanilla Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference - Feature #6443: NiStencilProperty is not fully supported + Feature #6443: Support NiStencilProperty Feature #6534: Shader-based object texture blending Feature #6541: Gloss-mapping Feature #6592: Missing support for NiTriShape particle emitters Feature #6600: Support NiSortAdjustNode + Feature #6684: Support NiFltAnimationNode Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp Task #6553: Simplify interpreter instruction registration diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 4865f1088e..57040eca98 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -2,7 +2,6 @@ #include -#include #include #include #include @@ -66,7 +65,7 @@ namespace MWRender case ESM::REC_CONT: return store.get().searchStatic(id)->mModel; default: - return std::string(); + return {}; } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 6452e2b8af..2f64e39a75 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -433,12 +433,17 @@ struct NiLODNode : public NiSwitchNode struct NiFltAnimationNode : public NiSwitchNode { - float Interval; + float mInterval; + enum Flags + { + Flag_Reverse = 0x40 + }; + void read(NIFStream *nif) override { NiSwitchNode::read(nif); - Interval = nif->getFloat(); + mInterval = nif->getFloat(); } }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9d66d3f4b6..8406db3839 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -421,10 +421,12 @@ namespace NifOsg { osg::ref_ptr sequenceNode (new osg::Sequence); sequenceNode->setName(niFltAnimationNode->name); - sequenceNode->setDefaultTime(niFltAnimationNode->Interval); - sequenceNode->setInterval(osg::Sequence::LOOP, 0,-1); - sequenceNode->setDuration( -1.0f, -1); + sequenceNode->setDefaultTime(niFltAnimationNode->mInterval); sequenceNode->setMode(osg::Sequence::START); + if (!niFltAnimationNode->flags & Nif::NiFltAnimationNode::Flag_Reverse) + sequenceNode->setDuration(-1.0f, -1); + else + sequenceNode->setDuration(1.0f, -1); return sequenceNode; } From af41560c3fc67d6113a4ab58d66a994a02dbfe2b Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 30 Mar 2022 15:51:21 +0200 Subject: [PATCH 2266/2859] reverse the reverse flag check; set duration to 0.2f which matches openmw to morrowind speed which is measured in 1/5 seconds --- components/nifosg/nifloader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8406db3839..9cd2685747 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -423,10 +423,10 @@ namespace NifOsg sequenceNode->setName(niFltAnimationNode->name); sequenceNode->setDefaultTime(niFltAnimationNode->mInterval); sequenceNode->setMode(osg::Sequence::START); - if (!niFltAnimationNode->flags & Nif::NiFltAnimationNode::Flag_Reverse) - sequenceNode->setDuration(-1.0f, -1); + if (niFltAnimationNode->flags & Nif::NiFltAnimationNode::Flag_Reverse) + sequenceNode->setDuration(0.2f, -1); else - sequenceNode->setDuration(1.0f, -1); + sequenceNode->setDuration(-0.2f, -1); return sequenceNode; } From c376f3793e1bdc52600efa68e2924c0d3910625e Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 31 Mar 2022 21:55:14 +0200 Subject: [PATCH 2267/2859] check that the index is now -1; otherwise bad things happen --- apps/openmw/mwrender/objectpaging.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 57040eca98..d2b66b78d9 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -155,7 +155,7 @@ namespace MWRender if (const osg::Sequence* sq = dynamic_cast(node)) { osg::Group* n = new osg::Group; - n->addChild(operator()(sq->getChild(sq->getValue()))); + n->addChild(operator()(sq->getChild(sq->getValue() != -1 ? sq->getValue() : 0))); n->setDataVariance(osg::Object::STATIC); return n; } @@ -310,7 +310,7 @@ namespace MWRender } if (osg::Sequence* sq = dynamic_cast(&node)) { - traverse(*sq->getChild(sq->getValue())); + traverse(*sq->getChild(sq->getValue() != -1 ? sq->getValue() : 0)); return; } From 373776170dc749cce60c21918138eeceecd87f70 Mon Sep 17 00:00:00 2001 From: Wolfgang Lieff Date: Thu, 7 Apr 2022 00:49:08 +0200 Subject: [PATCH 2268/2859] refactor for NiFltAnimationNode --- components/nif/node.hpp | 6 +++--- components/nifosg/nifloader.cpp | 34 ++++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 2f64e39a75..b63fc03804 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -433,17 +433,17 @@ struct NiLODNode : public NiSwitchNode struct NiFltAnimationNode : public NiSwitchNode { - float mInterval; + float mDuration; enum Flags { - Flag_Reverse = 0x40 + Flag_Swing = 0x40 }; void read(NIFStream *nif) override { NiSwitchNode::read(nif); - mInterval = nif->getFloat(); + mDuration = nif->getFloat(); } }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9cd2685747..403fc8fef8 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -417,19 +417,33 @@ namespace NifOsg return switchNode; } - static osg::ref_ptr handleSequenceNode(const Nif::NiFltAnimationNode* niFltAnimationNode) + static osg::ref_ptr prepareSequenceNode(const Nif::Node* nifNode) { + const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); osg::ref_ptr sequenceNode (new osg::Sequence); sequenceNode->setName(niFltAnimationNode->name); - sequenceNode->setDefaultTime(niFltAnimationNode->mInterval); - sequenceNode->setMode(osg::Sequence::START); - if (niFltAnimationNode->flags & Nif::NiFltAnimationNode::Flag_Reverse) - sequenceNode->setDuration(0.2f, -1); - else - sequenceNode->setDuration(-0.2f, -1); + if (niFltAnimationNode->children.length()!=0) + { + if (niFltAnimationNode->flags & Nif::NiFltAnimationNode::Flag_Swing) + sequenceNode->setDefaultTime(niFltAnimationNode->mDuration/(niFltAnimationNode->children.length()*2)); + else + sequenceNode->setDefaultTime(niFltAnimationNode->mDuration/niFltAnimationNode->children.length()); + } return sequenceNode; } + static void activateSequenceNode(osg::Group* osgNode, const Nif::Node* nifNode) + { + const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); + osg::Sequence* sequenceNode = static_cast(osgNode); + if (niFltAnimationNode->flags & Nif::NiFltAnimationNode::Flag_Swing) + sequenceNode->setInterval(osg::Sequence::SWING, 0,-1); + else + sequenceNode->setInterval(osg::Sequence::LOOP, 0,-1); + sequenceNode->setDuration(1.0f, -1); + sequenceNode->setMode(osg::Sequence::START); + } + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) { if (!st) @@ -727,8 +741,7 @@ namespace NifOsg } else if (nifNode->recType == Nif::RC_NiFltAnimationNode) { - const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); - osg::ref_ptr sequenceNode = handleSequenceNode(niFltAnimationNode); + osg::ref_ptr sequenceNode = prepareSequenceNode(nifNode); node->addChild(sequenceNode); currentNode = sequenceNode; } @@ -752,6 +765,9 @@ namespace NifOsg } } + if (nifNode->recType == Nif::RC_NiFltAnimationNode) + activateSequenceNode(currentNode,nifNode); + return node; } From 1baee5ddba4ba99f403f815ad586ccd7c00bd23e Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 7 Apr 2022 15:54:39 +0200 Subject: [PATCH 2269/2859] Add check box to remove unused tiles --- apps/launcher/datafilespage.cpp | 6 +++++- files/ui/datafilespage.ui | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index e8b61cb079..d86c9b15b6 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -493,7 +493,11 @@ void Launcher::DataFilesPage::startNavMeshTool() mNavMeshToolProgress = NavMeshToolProgress {}; - if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"), QStringList({"--write-binary-log"}))) + QStringList arguments({"--write-binary-log"}); + if (ui.navMeshRemoveUnusedTilesCheckBox->checkState() == Qt::Checked) + arguments.append("--remove-unused-tiles"); + + if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"), arguments)) return; ui.cancelNavMeshButton->setEnabled(true); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index ff330391d2..5d04ab1ed8 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -74,6 +74,16 @@ + + + + Remove unused tiles + + + true + + + From 7038c826908f33d34b637ca673362a58e652b4ff Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 7 Apr 2022 16:35:18 +0200 Subject: [PATCH 2270/2859] Configure mav navmeshdb file size from the launcher --- apps/launcher/datafilespage.cpp | 20 +++++++++++++++----- components/settings/settings.cpp | 9 ++++++++- components/settings/settings.hpp | 1 + files/ui/datafilespage.ui | 24 ++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index d86c9b15b6..58a20c7853 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -84,6 +84,11 @@ namespace Launcher }; } }; + + int getMaxNavMeshDbFileSizeMiB() + { + return static_cast(Settings::Manager::getInt64("max navmeshdb file size", "Navigator") / (1024 * 1024)); + } } } @@ -164,6 +169,8 @@ void Launcher::DataFilesPage::buildView() bool Launcher::DataFilesPage::loadSettings() { + ui.navMeshMaxSizeSpinBox->setValue(getMaxNavMeshDbFileSizeMiB()); + QStringList profiles = mLauncherSettings.getContentLists(); QString currentProfile = mLauncherSettings.getCurrentContentListName(); @@ -217,13 +224,16 @@ QStringList Launcher::DataFilesPage::filesInProfile(const QString& profileName, void Launcher::DataFilesPage::saveSettings(const QString &profile) { - QString profileName = profile; + if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB()) + Settings::Manager::setInt64("max navmeshdb file size", "Navigator", static_cast(value) * 1024 * 1024); - if (profileName.isEmpty()) - profileName = ui.profilesComboBox->currentText(); + QString profileName = profile; - //retrieve the files selected for the profile - ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); + if (profileName.isEmpty()) + profileName = ui.profilesComboBox->currentText(); + + //retrieve the files selected for the profile + ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); //set the value of the current profile (not necessarily the profile being saved!) mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText()); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 4c17394836..ecd25dbd4e 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -116,7 +116,7 @@ std::int64_t Manager::getInt64 (const std::string& setting, const std::string& c { const std::string& value = getString(setting, category); std::stringstream stream(value); - std::size_t number = 0; + std::int64_t number = 0; stream >> number; return number; } @@ -172,6 +172,13 @@ void Manager::setInt (const std::string& setting, const std::string& category, c setString(setting, category, stream.str()); } +void Manager::setInt64 (const std::string& setting, const std::string& category, const std::int64_t value) +{ + std::ostringstream stream; + stream << value; + setString(setting, category, stream.str()); +} + void Manager::setFloat (const std::string &setting, const std::string &category, const float value) { std::ostringstream stream; diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index c23d8d878e..d5d9c2d9f6 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -62,6 +62,7 @@ namespace Settings static osg::Vec3f getVector3 (const std::string& setting, const std::string& category); static void setInt (const std::string& setting, const std::string& category, int value); + static void setInt64 (const std::string& setting, const std::string& category, std::int64_t value); static void setFloat (const std::string& setting, const std::string& category, float value); static void setDouble (const std::string& setting, const std::string& category, double value); static void setString (const std::string& setting, const std::string& category, const std::string& value); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 5d04ab1ed8..e942cd652b 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -84,6 +84,30 @@ + + + + + + Max size + + + + + + + MiB + + + 2147483647 + + + 2048 + + + + + From 5156ee94be4ac937653718091498b3d81b85b64b Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Apr 2022 00:54:41 +0200 Subject: [PATCH 2271/2859] Store static id key in lower case --- apps/openmw/mwrender/objectpaging.cpp | 12 ++++++++++-- apps/openmw/mwworld/esmstore.cpp | 3 ++- apps/openmw/mwworld/esmstore.hpp | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index d2b66b78d9..cf1d689911 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -440,7 +440,10 @@ namespace MWRender if (moved) continue; - if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; + if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) + continue; + + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } @@ -454,7 +457,12 @@ namespace MWRender } for (auto [ref, deleted] : cell->mLeasedRefs) { - if (deleted) { refs.erase(ref.mRefNum); continue; } + if (deleted) + { + refs.erase(ref.mRefNum); + continue; + } + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; refs[ref.mRefNum] = std::move(ref); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 6308c9a547..c670bead9c 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -257,7 +257,8 @@ void ESMStore::setUp(bool validateRecords) } if (mStaticIds.empty()) - mStaticIds = mIds; + for (const auto& [k, v] : mIds) + mStaticIds.emplace(Misc::StringUtils::lowerCase(k), v); mSkills.setUp(); mMagicEffects.setUp(); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 8582a1daca..07f860e6de 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -77,7 +77,7 @@ namespace MWWorld // maps the id name to the record type. using IDMap = std::unordered_map; IDMap mIds; - IDMap mStaticIds; + std::unordered_map mStaticIds; std::unordered_map mRefCount; From ec3674b40a2272b74abfc6b89f463ab703914690 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 6 Apr 2022 17:06:55 +0200 Subject: [PATCH 2272/2859] Use unique_ptr instead of shared_ptr for MWWorld::Action, ProjectileManager and ESSImport::Converter. shared_ptr has additional cost of reference counter and requires additional allocation when constructed as shared_ptr(new T). --- apps/essimporter/importer.cpp | 66 ++++++++++---------- apps/openmw/mwclass/activator.cpp | 6 +- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/apparatus.cpp | 6 +- apps/openmw/mwclass/apparatus.hpp | 4 +- apps/openmw/mwclass/armor.cpp | 6 +- apps/openmw/mwclass/armor.hpp | 4 +- apps/openmw/mwclass/book.cpp | 10 +-- apps/openmw/mwclass/book.hpp | 4 +- apps/openmw/mwclass/clothing.cpp | 6 +- apps/openmw/mwclass/clothing.hpp | 4 +- apps/openmw/mwclass/container.cpp | 14 ++--- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/creature.cpp | 14 ++--- apps/openmw/mwclass/creature.hpp | 2 +- apps/openmw/mwclass/door.cpp | 14 ++--- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 6 +- apps/openmw/mwclass/ingredient.hpp | 4 +- apps/openmw/mwclass/light.cpp | 10 +-- apps/openmw/mwclass/light.hpp | 4 +- apps/openmw/mwclass/lockpick.cpp | 6 +- apps/openmw/mwclass/lockpick.hpp | 4 +- apps/openmw/mwclass/misc.cpp | 8 +-- apps/openmw/mwclass/misc.hpp | 4 +- apps/openmw/mwclass/npc.cpp | 20 +++--- apps/openmw/mwclass/npc.hpp | 2 +- apps/openmw/mwclass/potion.cpp | 6 +- apps/openmw/mwclass/potion.hpp | 4 +- apps/openmw/mwclass/probe.cpp | 6 +- apps/openmw/mwclass/probe.hpp | 4 +- apps/openmw/mwclass/repair.cpp | 6 +- apps/openmw/mwclass/repair.hpp | 4 +- apps/openmw/mwclass/weapon.cpp | 6 +- apps/openmw/mwclass/weapon.hpp | 4 +- apps/openmw/mwgui/inventorywindow.cpp | 2 +- apps/openmw/mwgui/sortfilteritemmodel.cpp | 2 +- apps/openmw/mwlua/actions.cpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aicombat.hpp | 2 +- apps/openmw/mwmechanics/aicombataction.cpp | 4 +- apps/openmw/mwmechanics/aicombataction.hpp | 2 +- apps/openmw/mwscript/containerextensions.cpp | 2 +- apps/openmw/mwscript/interpretercontext.cpp | 2 +- apps/openmw/mwworld/class.cpp | 16 ++--- apps/openmw/mwworld/class.hpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 48 files changed, 160 insertions(+), 160 deletions(-) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 9931385f8c..a683bf7047 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -272,39 +272,39 @@ namespace ESSImport const unsigned int recJOUR = ESM::fourCC("JOUR"); const unsigned int recSPLM = ESM::fourCC("SPLM"); - std::map > converters; - converters[ESM::REC_GLOB] = std::shared_ptr(new ConvertGlobal()); - converters[ESM::REC_BOOK] = std::shared_ptr(new ConvertBook()); - converters[ESM::REC_NPC_] = std::shared_ptr(new ConvertNPC()); - converters[ESM::REC_CREA] = std::shared_ptr(new ConvertCREA()); - converters[ESM::REC_NPCC] = std::shared_ptr(new ConvertNPCC()); - converters[ESM::REC_CREC] = std::shared_ptr(new ConvertCREC()); - converters[recREFR ] = std::shared_ptr(new ConvertREFR()); - converters[recPCDT ] = std::shared_ptr(new ConvertPCDT()); - converters[recFMAP ] = std::shared_ptr(new ConvertFMAP()); - converters[recKLST ] = std::shared_ptr(new ConvertKLST()); - converters[recSTLN ] = std::shared_ptr(new ConvertSTLN()); - converters[recGAME ] = std::shared_ptr(new ConvertGAME()); - converters[ESM::REC_CELL] = std::shared_ptr(new ConvertCell()); - converters[ESM::REC_ALCH] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_CLAS] = std::shared_ptr(new ConvertClass()); - converters[ESM::REC_SPEL] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_ARMO] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_WEAP] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_CLOT] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_ENCH] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_WEAP] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_LEVC] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_LEVI] = std::shared_ptr(new DefaultConverter()); - converters[ESM::REC_CNTC] = std::shared_ptr(new ConvertCNTC()); - converters[ESM::REC_FACT] = std::shared_ptr(new ConvertFACT()); - converters[ESM::REC_INFO] = std::shared_ptr(new ConvertINFO()); - converters[ESM::REC_DIAL] = std::shared_ptr(new ConvertDIAL()); - converters[ESM::REC_QUES] = std::shared_ptr(new ConvertQUES()); - converters[recJOUR ] = std::shared_ptr(new ConvertJOUR()); - converters[ESM::REC_SCPT] = std::shared_ptr(new ConvertSCPT()); - converters[ESM::REC_PROJ] = std::shared_ptr(new ConvertPROJ()); - converters[recSPLM] = std::shared_ptr(new ConvertSPLM()); + std::map> converters; + converters[ESM::REC_GLOB] = std::unique_ptr(new ConvertGlobal()); + converters[ESM::REC_BOOK] = std::unique_ptr(new ConvertBook()); + converters[ESM::REC_NPC_] = std::unique_ptr(new ConvertNPC()); + converters[ESM::REC_CREA] = std::unique_ptr(new ConvertCREA()); + converters[ESM::REC_NPCC] = std::unique_ptr(new ConvertNPCC()); + converters[ESM::REC_CREC] = std::unique_ptr(new ConvertCREC()); + converters[recREFR ] = std::unique_ptr(new ConvertREFR()); + converters[recPCDT ] = std::unique_ptr(new ConvertPCDT()); + converters[recFMAP ] = std::unique_ptr(new ConvertFMAP()); + converters[recKLST ] = std::unique_ptr(new ConvertKLST()); + converters[recSTLN ] = std::unique_ptr(new ConvertSTLN()); + converters[recGAME ] = std::unique_ptr(new ConvertGAME()); + converters[ESM::REC_CELL] = std::unique_ptr(new ConvertCell()); + converters[ESM::REC_ALCH] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_CLAS] = std::unique_ptr(new ConvertClass()); + converters[ESM::REC_SPEL] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_ARMO] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_WEAP] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_CLOT] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_ENCH] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_WEAP] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_LEVC] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_LEVI] = std::unique_ptr(new DefaultConverter()); + converters[ESM::REC_CNTC] = std::unique_ptr(new ConvertCNTC()); + converters[ESM::REC_FACT] = std::unique_ptr(new ConvertFACT()); + converters[ESM::REC_INFO] = std::unique_ptr(new ConvertINFO()); + converters[ESM::REC_DIAL] = std::unique_ptr(new ConvertDIAL()); + converters[ESM::REC_QUES] = std::unique_ptr(new ConvertQUES()); + converters[recJOUR ] = std::unique_ptr(new ConvertJOUR()); + converters[ESM::REC_SCPT] = std::unique_ptr(new ConvertSCPT()); + converters[ESM::REC_PROJ] = std::unique_ptr(new ConvertPROJ()); + converters[recSPLM] = std::unique_ptr(new ConvertSPLM()); // TODO: // - REGN (weather in certain regions?) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index ad9a6c76d9..2d386addb8 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -111,7 +111,7 @@ namespace MWClass return info; } - std::shared_ptr Activator::activate(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const + std::unique_ptr Activator::activate(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const { if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { @@ -119,12 +119,12 @@ namespace MWClass auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfActivator", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); + std::unique_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; } - return std::shared_ptr(new MWWorld::NullAction); + return std::unique_ptr(new MWWorld::NullAction); } diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index d5c251d9d1..e34786a1f0 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -35,7 +35,7 @@ namespace MWClass std::string getScript (const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr - std::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 021e81346d..99a2d7887b 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -49,7 +49,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Apparatus::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Apparatus::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -108,9 +108,9 @@ namespace MWClass return info; } - std::shared_ptr Apparatus::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Apparatus::use (const MWWorld::Ptr& ptr, bool force) const { - return std::shared_ptr(new MWWorld::ActionAlchemy(force)); + return std::unique_ptr(new MWWorld::ActionAlchemy(force)); } MWWorld::Ptr Apparatus::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index ec84a75196..ef21928075 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -23,7 +23,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -45,7 +45,7 @@ namespace MWClass std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index cffc112641..22c46bf579 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -57,7 +57,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Armor::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Armor::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -332,9 +332,9 @@ namespace MWClass return std::make_pair(1,""); } - std::shared_ptr Armor::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Armor::use (const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action(new MWWorld::ActionEquip(ptr, force)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 00fb23defa..b185596c08 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -23,7 +23,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -69,7 +69,7 @@ namespace MWClass ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. \n /// Second item in the pair specifies the error message - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 1c826a049d..c8a05a132e 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -54,7 +54,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Book::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Book::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) @@ -63,13 +63,13 @@ namespace MWClass auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfItem", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); + std::unique_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; } - return std::shared_ptr(new MWWorld::ActionRead(ptr)); + return std::unique_ptr(new MWWorld::ActionRead(ptr)); } std::string Book::getScript (const MWWorld::ConstPtr& ptr) const @@ -149,9 +149,9 @@ namespace MWClass return record->mId; } - std::shared_ptr Book::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Book::use (const MWWorld::Ptr& ptr, bool force) const { - return std::shared_ptr(new MWWorld::ActionRead(ptr)); + return std::unique_ptr(new MWWorld::ActionRead(ptr)); } MWWorld::Ptr Book::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index d78d9c7500..a5152fa812 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -49,7 +49,7 @@ namespace MWClass std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const override; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 02ab337a2b..32396cae35 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -52,7 +52,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Clothing::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Clothing::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -228,9 +228,9 @@ namespace MWClass return std::make_pair (1, ""); } - std::shared_ptr Clothing::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Clothing::use (const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action(new MWWorld::ActionEquip(ptr, force)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index a044b13804..8e0a0c90bd 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -61,7 +61,7 @@ namespace MWClass ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 296813f155..ad0b6169a9 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -131,11 +131,11 @@ namespace MWClass return true; } - std::shared_ptr Container::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Container::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) - return std::shared_ptr (new MWWorld::NullAction ()); + return std::unique_ptr (new MWWorld::NullAction ()); if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { @@ -143,7 +143,7 @@ namespace MWClass auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfContainer", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); + std::unique_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; @@ -191,24 +191,24 @@ namespace MWClass { if (canBeHarvested(ptr)) { - std::shared_ptr action (new MWWorld::ActionHarvest(ptr)); + std::unique_ptr action (new MWWorld::ActionHarvest(ptr)); return action; } - std::shared_ptr action (new MWWorld::ActionOpen(ptr)); + std::unique_ptr action (new MWWorld::ActionOpen(ptr)); return action; } else { // Activate trap - std::shared_ptr action(new MWWorld::ActionTrap(ptr.getCellRef().getTrap(), ptr)); + std::unique_ptr action(new MWWorld::ActionTrap(ptr.getCellRef().getTrap(), ptr)); action->setSound(trapActivationSound); return action; } } else { - std::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); + std::unique_ptr action(new MWWorld::FailedAction(std::string(), ptr)); action->setSound(lockedSound); return action; } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 0da53508d2..9ecf323b71 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -50,7 +50,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 68aea5730f..fc3504b947 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -430,7 +430,7 @@ namespace MWClass } } - std::shared_ptr Creature::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Creature::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) @@ -439,7 +439,7 @@ namespace MWClass auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfCreature", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); + std::unique_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; @@ -453,20 +453,20 @@ namespace MWClass // by default user can loot friendly actors during death animation if (canLoot && !stats.getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + return std::unique_ptr(new MWWorld::ActionOpen(ptr)); // otherwise wait until death animation if(stats.isDeathAnimationFinished()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + return std::unique_ptr(new MWWorld::ActionOpen(ptr)); } else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) - return std::shared_ptr(new MWWorld::ActionTalk(ptr)); + return std::unique_ptr(new MWWorld::ActionTalk(ptr)); // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + return std::unique_ptr(new MWWorld::ActionOpen(ptr)); - return std::shared_ptr(new MWWorld::FailedAction("")); + return std::unique_ptr(new MWWorld::FailedAction("")); } MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 3b1c070ce7..558ea00643 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -65,7 +65,7 @@ namespace MWClass void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const override; - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ff4efbc1ba..17f0cadbb6 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -109,7 +109,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Door::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Door::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -130,7 +130,7 @@ namespace MWClass // Make such activation a no-op for now, like how it is in the vanilla game. if (actor != MWMechanics::getPlayer() && ptr.getCellRef().getTeleport()) { - std::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); + std::unique_ptr action(new MWWorld::FailedAction(std::string(), ptr)); action->setSound(lockedSound); return action; } @@ -181,7 +181,7 @@ namespace MWClass if(isTrapped) { // Trap activation - std::shared_ptr action(new MWWorld::ActionTrap(ptr.getCellRef().getTrap(), ptr)); + std::unique_ptr action(new MWWorld::ActionTrap(ptr.getCellRef().getTrap(), ptr)); action->setSound(trapActivationSound); return action; } @@ -191,12 +191,12 @@ namespace MWClass if (actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { // player activated teleport door with telekinesis - std::shared_ptr action(new MWWorld::FailedAction); + std::unique_ptr action(new MWWorld::FailedAction); return action; } else { - std::shared_ptr action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true)); + std::unique_ptr action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true)); action->setSound(openSound); return action; } @@ -204,7 +204,7 @@ namespace MWClass else { // animated door - std::shared_ptr action(new MWWorld::ActionDoor(ptr)); + std::unique_ptr action(new MWWorld::ActionDoor(ptr)); const auto doorState = getDoorState(ptr); bool opening = true; float doorRot = ptr.getRefData().getPosition().rot[2] - ptr.getCellRef().getPosition().rot[2]; @@ -238,7 +238,7 @@ namespace MWClass else { // locked, and we can't open. - std::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); + std::unique_ptr action(new MWWorld::FailedAction(std::string(), ptr)); action->setSound(lockedSound); return action; } diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index fd824c4732..d3a369d89e 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -32,7 +32,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index e0e71902c7..2331361477 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -51,7 +51,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Ingredient::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Ingredient::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -72,9 +72,9 @@ namespace MWClass } - std::shared_ptr Ingredient::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Ingredient::use (const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action (new MWWorld::ActionEat (ptr)); + std::unique_ptr action (new MWWorld::ActionEat (ptr)); action->setSound ("Swallow"); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 2abb2f343c..95b3d94e5e 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -34,7 +34,7 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 90080784a9..a7d49da54a 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -85,15 +85,15 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Light::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Light::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) - return std::shared_ptr(new MWWorld::NullAction()); + return std::unique_ptr(new MWWorld::NullAction()); MWWorld::LiveCellRef *ref = ptr.get(); if(!(ref->mBase->mData.mFlags&ESM::Light::Carry)) - return std::shared_ptr(new MWWorld::FailedAction()); + return std::unique_ptr(new MWWorld::FailedAction()); return defaultItemActivate(ptr, actor); } @@ -184,9 +184,9 @@ namespace MWClass return Class::showsInInventory(ptr); } - std::shared_ptr Light::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Light::use (const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action(new MWWorld::ActionEquip(ptr, force)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 7a7f670a2a..0104e88b6b 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -34,7 +34,7 @@ namespace MWClass bool showsInInventory (const MWWorld::ConstPtr& ptr) const override; - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -57,7 +57,7 @@ namespace MWClass std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu void setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 1e451ca7fe..c8860df4ce 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -51,7 +51,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Lockpick::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Lockpick::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -124,9 +124,9 @@ namespace MWClass return info; } - std::shared_ptr Lockpick::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Lockpick::use (const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action(new MWWorld::ActionEquip(ptr, force)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index b51aaa05a6..3aac0c70b7 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -49,7 +49,7 @@ namespace MWClass std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 759eea5ccc..51e75650fb 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -61,7 +61,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -203,12 +203,12 @@ namespace MWClass return newPtr; } - std::shared_ptr Miscellaneous::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Miscellaneous::use (const MWWorld::Ptr& ptr, bool force) const { if (ptr.getCellRef().getSoul().empty() || !MWBase::Environment::get().getWorld()->getStore().get().search(ptr.getCellRef().getSoul())) - return std::shared_ptr(new MWWorld::NullAction()); + return std::unique_ptr(new MWWorld::NullAction()); else - return std::shared_ptr(new MWWorld::ActionSoulgem(ptr)); + return std::unique_ptr(new MWWorld::ActionSoulgem(ptr)); } bool Miscellaneous::canSell (const MWWorld::ConstPtr& item, int npcServices) const diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 59c31a219c..ebf38b10be 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -45,7 +45,7 @@ namespace MWClass std::string getModel(const MWWorld::ConstPtr &ptr) const override; - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu float getWeight (const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index cc4b3f7528..f3fcb848fd 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -860,12 +860,12 @@ namespace MWClass } } - std::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { // player got activated by another NPC if(ptr == MWMechanics::getPlayer()) - return std::shared_ptr(new MWWorld::ActionTalk(actor)); + return std::unique_ptr(new MWWorld::ActionTalk(actor)); // Werewolfs can't activate NPCs if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) @@ -874,7 +874,7 @@ namespace MWClass auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfNPC", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); + std::unique_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; @@ -888,33 +888,33 @@ namespace MWClass // by default user can loot friendly actors during death animation if (canLoot && !stats.getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + return std::unique_ptr(new MWWorld::ActionOpen(ptr)); // otherwise wait until death animation if(stats.isDeathAnimationFinished()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + return std::unique_ptr(new MWWorld::ActionOpen(ptr)); } else if (!stats.getAiSequence().isInCombat()) { if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + return std::unique_ptr(new MWWorld::ActionOpen(ptr)); // stealing // Can't talk to werewolves if (!getNpcStats(ptr).isWerewolf()) - return std::shared_ptr(new MWWorld::ActionTalk(ptr)); + return std::unique_ptr(new MWWorld::ActionTalk(ptr)); } else // In combat { const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game"); if (stealingInCombat && stats.getKnockedDown()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + return std::unique_ptr(new MWWorld::ActionOpen(ptr)); // stealing } // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); + return std::unique_ptr(new MWWorld::ActionOpen(ptr)); - return std::shared_ptr (new MWWorld::FailedAction("")); + return std::unique_ptr (new MWWorld::FailedAction("")); } MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 574d133934..ba937a4cfa 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -83,7 +83,7 @@ namespace MWClass void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 6357d8938a..2267f24b37 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -53,7 +53,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Potion::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Potion::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -123,12 +123,12 @@ namespace MWClass return info; } - std::shared_ptr Potion::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Potion::use (const MWWorld::Ptr& ptr, bool force) const { MWWorld::LiveCellRef *ref = ptr.get(); - std::shared_ptr action ( + std::unique_ptr action ( new MWWorld::ActionApply (ptr, ref->mBase->mId)); action->setSound ("Drink"); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 2b979e2f37..d5964cff85 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -34,7 +34,7 @@ namespace MWClass int getValue (const MWWorld::ConstPtr& ptr) const override; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 7db6a3e955..369f5efd6d 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -50,7 +50,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Probe::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Probe::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -124,9 +124,9 @@ namespace MWClass return info; } - std::shared_ptr Probe::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Probe::use (const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action(new MWWorld::ActionEquip(ptr, force)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 06bd2ababb..b048b37f92 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -49,7 +49,7 @@ namespace MWClass std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const override; - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 463257696c..c3a67a6086 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -48,7 +48,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Repair::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Repair::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -132,9 +132,9 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } - std::shared_ptr Repair::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Repair::use (const MWWorld::Ptr& ptr, bool force) const { - return std::shared_ptr(new MWWorld::ActionRepair(ptr, force)); + return std::unique_ptr(new MWWorld::ActionRepair(ptr, force)); } bool Repair::canSell (const MWWorld::ConstPtr& item, int npcServices) const diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index b79ad2b124..4ca0dd2511 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -45,7 +45,7 @@ namespace MWClass std::string getModel(const MWWorld::ConstPtr &ptr) const override; - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu (default implementation: return a /// null action). diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 261ada1e75..9290c19d80 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -57,7 +57,7 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - std::shared_ptr Weapon::activate (const MWWorld::Ptr& ptr, + std::unique_ptr Weapon::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { return defaultItemActivate(ptr, actor); @@ -299,9 +299,9 @@ namespace MWClass return std::make_pair(1, ""); } - std::shared_ptr Weapon::use (const MWWorld::Ptr& ptr, bool force) const + std::unique_ptr Weapon::use (const MWWorld::Ptr& ptr, bool force) const { - std::shared_ptr action(new MWWorld::ActionEquip(ptr, force)); + std::unique_ptr action(new MWWorld::ActionEquip(ptr, force)); action->setSound(getUpSoundId(ptr)); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 61b2a70c26..f09c590183 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -21,7 +21,7 @@ namespace MWClass std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. - std::shared_ptr activate (const MWWorld::Ptr& ptr, + std::unique_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation @@ -67,7 +67,7 @@ namespace MWClass ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message - std::shared_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; + std::unique_ptr use (const MWWorld::Ptr& ptr, bool force=false) const override; ///< Generate action for using via inventory menu std::string getModel(const MWWorld::ConstPtr &ptr) const override; diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 80e922fb66..680356b353 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -561,7 +561,7 @@ namespace MWGui ptr.getRefData().getLocals().setVarByInt(script, "pcskipequip", 1); } - std::shared_ptr action = ptr.getClass().use(ptr, force); + std::unique_ptr action = ptr.getClass().use(ptr, force); action->execute(player); if (isVisible()) diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index c804eafb72..acb324baca 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -256,7 +256,7 @@ namespace MWGui if ((mFilter & Filter_OnlyUsableItems) && base.getClass().getScript(base).empty()) { - std::shared_ptr actionOnUse = base.getClass().use(base); + std::unique_ptr actionOnUse = base.getClass().use(base); if (!actionOnUse || actionOnUse->isNullAction()) return false; } diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 404bae6c6d..0934b92226 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -160,7 +160,7 @@ namespace MWLua throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); - std::shared_ptr action = object.getClass().activate(object, actor); + std::unique_ptr action = object.getClass().activate(object, actor); action->execute(actor); } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 12671d50dd..cbd62d37ff 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -185,7 +185,7 @@ namespace MWMechanics actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); float& actionCooldown = storage.mActionCooldown; - std::shared_ptr& currentAction = storage.mCurrentAction; + std::unique_ptr& currentAction = storage.mCurrentAction; if (!forceFlee) { diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 566ec354d6..a51393647c 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -36,7 +36,7 @@ namespace MWMechanics bool mRotateMove; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; - std::shared_ptr mCurrentAction; + std::unique_ptr mCurrentAction; float mActionCooldown; float mStrength; bool mForceNoShortcut; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 43b43eb9f9..30573c131e 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -141,14 +141,14 @@ namespace MWMechanics return mWeapon.get()->mBase; } - std::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) + std::unique_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) { Spells& spells = actor.getClass().getCreatureStats(actor).getSpells(); float bestActionRating = 0.f; float antiFleeRating = 0.f; // Default to hand-to-hand combat - std::shared_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); + std::unique_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { bestAction->prepare(actor); diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index d17d5a3137..56d2247e99 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -85,7 +85,7 @@ namespace MWMechanics const ESM::Weapon* getWeapon() const override; }; - std::shared_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); + std::unique_ptr prepareNextAction (const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); float getBestActionRating(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy); float getDistanceMinusHalfExtents(const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool minusZDist=false); diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 40ad3b7835..8db1c04551 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -320,7 +320,7 @@ namespace MWScript MWBase::Environment::get().getWindowManager()->useItem(*it, true); else { - std::shared_ptr action = it->getClass().use(*it, true); + std::unique_ptr action = it->getClass().use(*it, true); action->execute(ptr, true); } } diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 0b9833e097..72922aa5f3 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -419,7 +419,7 @@ namespace MWScript void InterpreterContext::executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) { MWBase::Environment::get().getLuaManager()->objectActivated(ptr, actor); - std::shared_ptr action = (ptr.getClass().activate(ptr, actor)); + std::unique_ptr action = (ptr.getClass().activate(ptr, actor)); action->execute (actor); if (action->getTarget() != MWWorld::Ptr() && action->getTarget() != ptr) { diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index b67f173ee5..9dc8ba5631 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -115,14 +115,14 @@ namespace MWWorld throw std::runtime_error("class cannot be hit"); } - std::shared_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const + std::unique_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const { - return std::shared_ptr (new NullAction); + return std::unique_ptr (new NullAction); } - std::shared_ptr Class::use (const Ptr& ptr, bool force) const + std::unique_ptr Class::use (const Ptr& ptr, bool force) const { - return std::shared_ptr (new NullAction); + return std::unique_ptr (new NullAction); } ContainerStore& Class::getContainerStore (const Ptr& ptr) const @@ -329,10 +329,10 @@ namespace MWWorld { } - std::shared_ptr Class::defaultItemActivate(const Ptr &ptr, const Ptr &actor) const + std::unique_ptr Class::defaultItemActivate(const Ptr &ptr, const Ptr &actor) const { if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) - return std::shared_ptr(new NullAction()); + return std::unique_ptr(new NullAction()); if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { @@ -340,13 +340,13 @@ namespace MWWorld auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound *sound = store.get().searchRandom("WolfItem", prng); - std::shared_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); + std::unique_ptr action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); if(sound) action->setSound(sound->mId); return action; } - std::shared_ptr action(new ActionTake(ptr)); + std::unique_ptr action(new ActionTake(ptr)); action->setSound(getUpSoundId(ptr)); return action; diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index f12b7ba192..8c52021256 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -62,7 +62,7 @@ namespace MWWorld explicit Class(unsigned type) : mType(type) {} - std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; + std::unique_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items virtual Ptr copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const; @@ -139,10 +139,10 @@ namespace MWWorld ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield /// (default implementation: throw an exception) - virtual std::shared_ptr activate (const Ptr& ptr, const Ptr& actor) const; + virtual std::unique_ptr activate (const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action). - virtual std::shared_ptr use (const Ptr& ptr, bool force=false) + virtual std::unique_ptr use (const Ptr& ptr, bool force=false) const; ///< Generate action for using via inventory menu (default implementation: return a /// null action). diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6517694f15..7f652d9520 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3852,7 +3852,7 @@ namespace MWWorld if (object.getRefData().activate()) { MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); - std::shared_ptr action = object.getClass().activate(object, actor); + std::unique_ptr action = object.getClass().activate(object, actor); action->execute (actor); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index dc07a9117b..399b7232de 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -97,7 +97,7 @@ namespace MWWorld std::unique_ptr mWorldScene; std::unique_ptr mWeatherManager; std::unique_ptr mCurrentDate; - std::shared_ptr mProjectileManager; + std::unique_ptr mProjectileManager; bool mSky; bool mGodMode; From bbfdb347bdebf379a64feff2636b0bde62befd9d Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Apr 2022 17:08:18 +0200 Subject: [PATCH 2273/2859] Skip load cell ref when there is no need Primarily to avoid temporary allocations by ESMReader::getHString. --- apps/openmw/mwrender/objectpaging.cpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 4 +- apps/openmw/mwworld/store.cpp | 2 +- components/esm3/cellref.cpp | 248 ++++++++++++++++---------- components/esm3/cellref.hpp | 2 + components/esm3/esmreader.cpp | 26 +++ components/esm3/esmreader.hpp | 16 ++ components/esm3/loadcell.cpp | 12 +- components/esm3/loadcell.hpp | 10 +- 9 files changed, 219 insertions(+), 103 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index cf1d689911..7afb1bd4ba 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -435,7 +435,7 @@ namespace MWRender cMRef.mRefNum.mIndex = 0; bool deleted = false; bool moved = false; - while(cell->getNextRef(esm[index], ref, deleted, cMRef, moved)) + while (ESM::Cell::getNextRef(esm[index], ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (moved) continue; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 75136eb915..594bdcf6f2 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -568,7 +568,7 @@ namespace MWWorld cMRef.mRefNum.mIndex = 0; bool deleted = false; bool moved = false; - while(mCell->getNextRef(esm[index], ref, deleted, cMRef, moved)) + while (ESM::Cell::getNextRef(esm[index], ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (deleted || moved) continue; @@ -628,7 +628,7 @@ namespace MWWorld cMRef.mRefNum.mIndex = 0; bool deleted = false; bool moved = false; - while(mCell->getNextRef(esm[index], ref, deleted, cMRef, moved)) + while (ESM::Cell::getNextRef(esm[index], ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (moved) continue; diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 3139d05a51..80cf3b87a1 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -448,7 +448,7 @@ namespace MWWorld // // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following // implementation when the oher implementation works as well. - while (cell->getNextRef(esm, ref, deleted, cMRef, moved)) + while (ESM::Cell::getNextRef(esm, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyMoved)) { if (!moved) continue; diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 1a0320d823..2efa3b845d 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -5,6 +5,148 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +namespace ESM +{ + namespace + { + template + void loadIdImpl(ESMReader& esm, bool wideRefNum, CellRef& cellRef) + { + // According to Hrnchamd, this does not belong to the actual ref. Instead, it is a marker indicating that + // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. + // Its only purpose is a performance optimization for "immovable" things. We don't need this, and it's problematic anyway, + // because any item can theoretically be moved by a script. + if (esm.isNextSub("NAM0")) + esm.skipHSub(); + + if constexpr (load) + { + cellRef.blank(); + cellRef.mRefNum.load (esm, wideRefNum); + cellRef.mRefID = esm.getHNOString("NAME"); + + if (cellRef.mRefID.empty()) + Log(Debug::Warning) << "Warning: got CellRef with empty RefId in " << esm.getName() << " 0x" << std::hex << esm.getFileOffset(); + } + else + { + RefNum {}.load(esm, wideRefNum); + esm.skipHNOString("NAME"); + } + } + + template + void loadDataImpl(ESMReader &esm, bool &isDeleted, CellRef& cellRef) + { + const auto getHStringOrSkip = [&] (std::string& value) + { + if constexpr (load) + value = esm.getHString(); + else + esm.skipHString(); + }; + + const auto getHTOrSkip = [&] (auto& value) + { + if constexpr (load) + esm.getHT(value); + else + esm.skipHT>(); + }; + + if constexpr (load) + isDeleted = false; + + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().toInt()) + { + case ESM::fourCC("UNAM"): + getHTOrSkip(cellRef.mReferenceBlocked); + break; + case ESM::fourCC("XSCL"): + getHTOrSkip(cellRef.mScale); + if constexpr (load) + cellRef.mScale = std::clamp(cellRef.mScale, 0.5f, 2.0f); + break; + case ESM::fourCC("ANAM"): + getHStringOrSkip(cellRef.mOwner); + break; + case ESM::fourCC("BNAM"): + getHStringOrSkip(cellRef.mGlobalVariable); + break; + case ESM::fourCC("XSOL"): + getHStringOrSkip(cellRef.mSoul); + break; + case ESM::fourCC("CNAM"): + getHStringOrSkip(cellRef.mFaction); + break; + case ESM::fourCC("INDX"): + getHTOrSkip(cellRef.mFactionRank); + break; + case ESM::fourCC("XCHG"): + getHTOrSkip(cellRef.mEnchantmentCharge); + break; + case ESM::fourCC("INTV"): + getHTOrSkip(cellRef.mChargeInt); + break; + case ESM::fourCC("NAM9"): + getHTOrSkip(cellRef.mGoldValue); + break; + case ESM::fourCC("DODT"): + getHTOrSkip(cellRef.mDoorDest); + if constexpr (load) + cellRef.mTeleport = true; + break; + case ESM::fourCC("DNAM"): + getHStringOrSkip(cellRef.mDestCell); + break; + case ESM::fourCC("FLTV"): + getHTOrSkip(cellRef.mLockLevel); + break; + case ESM::fourCC("KNAM"): + getHStringOrSkip(cellRef.mKey); + break; + case ESM::fourCC("TNAM"): + getHStringOrSkip(cellRef.mTrap); + break; + case ESM::fourCC("DATA"): + if constexpr (load) + esm.getHT(cellRef.mPos, 24); + else + esm.skip(24); + break; + case ESM::fourCC("NAM0"): + { + esm.skipHSub(); + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + if constexpr (load) + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } + } + + if constexpr (load) + { + if (cellRef.mLockLevel == 0 && !cellRef.mKey.empty()) + { + cellRef.mLockLevel = UnbreakableLock; + cellRef.mTrap.clear(); + } + } + } + } +} + void ESM::RefNum::load(ESMReader& esm, bool wide, ESM::NAME tag) { if (wide) @@ -26,7 +168,6 @@ void ESM::RefNum::save(ESMWriter &esm, bool wide, ESM::NAME tag) const } } - void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); @@ -35,105 +176,12 @@ void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) { - // According to Hrnchamd, this does not belong to the actual ref. Instead, it is a marker indicating that - // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. - // Its only purpose is a performance optimization for "immovable" things. We don't need this, and it's problematic anyway, - // because any item can theoretically be moved by a script. - if (esm.isNextSub ("NAM0")) - esm.skipHSub(); - - blank(); - - mRefNum.load (esm, wideRefNum); - - mRefID = esm.getHNOString ("NAME"); - if (mRefID.empty()) - { - Log(Debug::Warning) << "Warning: got CellRef with empty RefId in " << esm.getName() << " 0x" << std::hex << esm.getFileOffset(); - } + loadIdImpl(esm, wideRefNum, *this); } void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) { - isDeleted = false; - - bool isLoaded = false; - while (!isLoaded && esm.hasMoreSubs()) - { - esm.getSubName(); - switch (esm.retSubName().toInt()) - { - case ESM::fourCC("UNAM"): - esm.getHT(mReferenceBlocked); - break; - case ESM::fourCC("XSCL"): - esm.getHT(mScale); - mScale = std::clamp(mScale, 0.5f, 2.0f); - break; - case ESM::fourCC("ANAM"): - mOwner = esm.getHString(); - break; - case ESM::fourCC("BNAM"): - mGlobalVariable = esm.getHString(); - break; - case ESM::fourCC("XSOL"): - mSoul = esm.getHString(); - break; - case ESM::fourCC("CNAM"): - mFaction = esm.getHString(); - break; - case ESM::fourCC("INDX"): - esm.getHT(mFactionRank); - break; - case ESM::fourCC("XCHG"): - esm.getHT(mEnchantmentCharge); - break; - case ESM::fourCC("INTV"): - esm.getHT(mChargeInt); - break; - case ESM::fourCC("NAM9"): - esm.getHT(mGoldValue); - break; - case ESM::fourCC("DODT"): - esm.getHT(mDoorDest); - mTeleport = true; - break; - case ESM::fourCC("DNAM"): - mDestCell = esm.getHString(); - break; - case ESM::fourCC("FLTV"): - esm.getHT(mLockLevel); - break; - case ESM::fourCC("KNAM"): - mKey = esm.getHString(); - break; - case ESM::fourCC("TNAM"): - mTrap = esm.getHString(); - break; - case ESM::fourCC("DATA"): - esm.getHT(mPos, 24); - break; - case ESM::fourCC("NAM0"): - { - esm.skipHSub(); - break; - } - case ESM::SREC_DELE: - esm.skipHSub(); - isDeleted = true; - break; - default: - esm.cacheSubName(); - isLoaded = true; - break; - } - } - - if (mLockLevel == 0 && !mKey.empty()) - { - mLockLevel = UnbreakableLock; - mTrap.clear(); - } + loadDataImpl(esm, isDeleted, *this); } void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const @@ -227,3 +275,11 @@ void ESM::CellRef::blank() mPos.rot[i] = 0; } } + +void ESM::skipLoadCellRef(ESMReader& esm, bool wideRefNum) +{ + CellRef cellRef; + loadIdImpl(esm, wideRefNum, cellRef); + bool isDeleted; + loadDataImpl(esm, isDeleted, cellRef); +} diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index cff635f455..07d4e0c80a 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -116,6 +116,8 @@ namespace ESM void blank(); }; + void skipLoadCellRef(ESMReader& esm, bool wideRefNum = false); + inline bool operator== (const RefNum& left, const RefNum& right) { return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile; diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 47974e45a8..2cf0cd29ce 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -120,6 +120,12 @@ std::string ESMReader::getHNOString(NAME name) return ""; } +void ESMReader::skipHNOString(NAME name) +{ + if (isNextSub(name)) + skipHString(); +} + std::string ESMReader::getHNString(NAME name) { getSubNameIs(name); @@ -147,6 +153,26 @@ std::string ESMReader::getHString() return getString(mCtx.leftSub); } +void ESMReader::skipHString() +{ + getSubHeader(); + + // Hack to make MultiMark.esp load. Zero-length strings do not + // occur in any of the official mods, but MultiMark makes use of + // them. For some reason, they break the rules, and contain a byte + // (value 0) even if the header says there is no data. If + // Morrowind accepts it, so should we. + if (mCtx.leftSub == 0 && hasMoreSubs() && !mEsm->peek()) + { + // Skip the following zero byte + mCtx.leftRec--; + skipT(); + return; + } + + skip(mCtx.leftSub); +} + void ESMReader::getHExact(void*p, int size) { getSubHeader(); diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index e370946ee9..464925d936 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -140,6 +140,15 @@ public: getT(x); } + template + void skipHT() + { + getSubHeader(); + if (mCtx.leftSub != sizeof(T)) + reportSubSizeMismatch(sizeof(T), mCtx.leftSub); + skipT(); + } + // Version with extra size checking, to make sure the compiler // doesn't mess up our struct padding. template @@ -152,12 +161,16 @@ public: // Read a string by the given name if it is the next record. std::string getHNOString(NAME name); + void skipHNOString(NAME name); + // Read a string with the given sub-record name std::string getHNString(NAME name); // Read a string, including the sub-record header (but not the name) std::string getHString(); + void skipHString(); + // Read the given number of bytes from a subrecord void getHExact(void*p, int size); @@ -237,6 +250,9 @@ public: template void getT(X &x) { getExact(&x, sizeof(X)); } + template + void skipT() { skip(sizeof(T)); } + void getExact(void* x, int size) { mEsm->read((char*)x, size); } void getName(NAME &name) { getT(name); } void getUint(uint32_t &u) { getT(u); } diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 6a4cd6ebf9..1a4fe1db8f 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -17,7 +17,7 @@ namespace { ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum - void adjustRefNum (ESM::RefNum& refNum, ESM::ESMReader& reader) + void adjustRefNum (ESM::RefNum& refNum, const ESM::ESMReader& reader) { unsigned int local = (refNum.mIndex & 0xff000000) >> 24; @@ -271,7 +271,8 @@ namespace ESM return false; } - bool Cell::getNextRef(ESMReader& esm, CellRef& cellRef, bool& deleted, MovedCellRef& movedCellRef, bool& moved) + bool Cell::getNextRef(ESMReader& esm, CellRef& cellRef, bool& deleted, MovedCellRef& movedCellRef, bool& moved, + GetNextRefMode mode) { deleted = false; moved = false; @@ -288,6 +289,13 @@ namespace ESM if (!esm.peekNextSub("FRMR")) return false; + if ((!moved && mode == GetNextRefMode::LoadOnlyMoved) + || (moved && mode == GetNextRefMode::LoadOnlyNotMoved)) + { + skipLoadCellRef(esm); + return true; + } + cellRef.load(esm, deleted); adjustRefNum(cellRef.mRefNum, esm); diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index 61f9fb54a3..13c14b30e9 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -67,6 +67,13 @@ struct Cell /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Cell"; } + enum class GetNextRefMode + { + LoadAll, + LoadOnlyMoved, + LoadOnlyNotMoved, + }; + enum Flags { Interior = 0x01, // Interior cell @@ -183,7 +190,8 @@ struct Cell */ static bool getNextRef(ESMReader& esm, CellRef& ref, bool& deleted); - static bool getNextRef(ESMReader& esm, CellRef& cellRef, bool& deleted, MovedCellRef& movedCellRef, bool& moved); + static bool getNextRef(ESMReader& esm, CellRef& cellRef, bool& deleted, MovedCellRef& movedCellRef, bool& moved, + GetNextRefMode mode = GetNextRefMode::LoadAll); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. From df092b558b326cbcb43c343242e09b9b1d7f6efd Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Apr 2022 22:04:32 +0200 Subject: [PATCH 2274/2859] Replace shared_ptr by unique_ptr --- apps/openmw/mwgui/mapwindow.hpp | 6 ++++-- apps/openmw/mwrender/animation.hpp | 4 ++-- apps/openmw/mwrender/localmap.cpp | 4 ++-- apps/openmw/mwrender/npcanimation.cpp | 12 ++++++------ apps/openmw/mwsound/movieaudiofactory.cpp | 4 ++-- apps/openmw/mwsound/movieaudiofactory.hpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 4 ++-- apps/openmw/mwworld/cellstore.hpp | 6 +++--- extern/osg-ffmpeg-videoplayer/audiofactory.hpp | 2 +- extern/osg-ffmpeg-videoplayer/videostate.hpp | 2 +- 10 files changed, 24 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index c50a92dac3..37c971cd19 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -6,6 +6,8 @@ #include +#include + #include "windowpinnablebase.hpp" #include @@ -142,8 +144,8 @@ namespace MWGui MyGUI::ImageBox* mMapWidget; MyGUI::ImageBox* mFogWidget; - std::shared_ptr mMapTexture; - std::shared_ptr mFogTexture; + std::unique_ptr mMapTexture; + std::unique_ptr mFogTexture; int mCellX; int mCellY; }; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index d37d548dd3..1bad0f8d95 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -61,7 +61,7 @@ public: ~PartHolder(); - osg::ref_ptr getNode() + const osg::ref_ptr& getNode() const { return mNode; } @@ -72,7 +72,7 @@ private: void operator= (const PartHolder&); PartHolder(const PartHolder&); }; -typedef std::shared_ptr PartHolderPtr; +using PartHolderPtr = std::unique_ptr; struct EffectParams { diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index a3c8d6b2ce..98eebf0b75 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -135,7 +135,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) segment.saveFogOfWar(fog->mFogTextures.back()); - cell->setFog(fog.release()); + cell->setFog(std::move(fog)); } } else @@ -169,7 +169,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) } } - cell->setFog(fog.release()); + cell->setFog(std::move(fog)); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 3fdfaed72d..a1a0dafb02 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -425,8 +425,8 @@ int NpcAnimation::getSlot(const osg::NodePath &path) const { for (int i=0; igetNode().get()) != path.end()) { @@ -1023,8 +1023,8 @@ void NpcAnimation::releaseArrow(float attackStrength) osg::Group* NpcAnimation::getArrowBone() { - PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; - if (!part) + const PartHolder* const part = mObjectParts[ESM::PRT_Weapon].get(); + if (part == nullptr) return nullptr; const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); @@ -1048,8 +1048,8 @@ osg::Group* NpcAnimation::getArrowBone() osg::Node* NpcAnimation::getWeaponNode() { - PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; - if (!part) + const PartHolder* const part = mObjectParts[ESM::PRT_Weapon].get(); + if (part == nullptr) return nullptr; return part->getNode(); } diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index d8c1c928ee..83f8398e8b 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -153,9 +153,9 @@ namespace MWSound - std::shared_ptr MovieAudioFactory::createDecoder(Video::VideoState* videoState) + std::unique_ptr MovieAudioFactory::createDecoder(Video::VideoState* videoState) { - std::shared_ptr decoder(new MWSound::MovieAudioDecoder(videoState)); + std::unique_ptr decoder(new MWSound::MovieAudioDecoder(videoState)); decoder->setupFormat(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); diff --git a/apps/openmw/mwsound/movieaudiofactory.hpp b/apps/openmw/mwsound/movieaudiofactory.hpp index 63b8fd7e90..0af1066af5 100644 --- a/apps/openmw/mwsound/movieaudiofactory.hpp +++ b/apps/openmw/mwsound/movieaudiofactory.hpp @@ -8,7 +8,7 @@ namespace MWSound class MovieAudioFactory : public Video::MovieAudioFactory { - std::shared_ptr createDecoder(Video::VideoState* videoState) override; + std::unique_ptr createDecoder(Video::VideoState* videoState) override; }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 594bdcf6f2..0c7cc631b7 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1029,9 +1029,9 @@ namespace MWWorld return !(left==right); } - void CellStore::setFog(ESM::FogState *fog) + void CellStore::setFog(std::unique_ptr&& fog) { - mFogState.reset(fog); + mFogState = std::move(fog); } ESM::FogState* CellStore::getFog() const diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 551924857b..7eea398f6e 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "timestamp.hpp" #include "ptr.hpp" @@ -39,7 +40,6 @@ namespace ESM { struct Cell; struct CellState; - struct FogState; struct CellId; struct RefNum; } @@ -66,7 +66,7 @@ namespace MWWorld // Even though fog actually belongs to the player and not cells, // it makes sense to store it here since we need it once for each cell. // Note this is nullptr until the cell is explored to save some memory - std::shared_ptr mFogState; + std::unique_ptr mFogState; const ESM::Cell *mCell; State mState; @@ -254,7 +254,7 @@ namespace MWWorld void setWaterLevel (float level); - void setFog (ESM::FogState* fog); + void setFog(std::unique_ptr&& fog); ///< \note Takes ownership of the pointer ESM::FogState* getFog () const; diff --git a/extern/osg-ffmpeg-videoplayer/audiofactory.hpp b/extern/osg-ffmpeg-videoplayer/audiofactory.hpp index 023d536e2d..803c7e9cfb 100644 --- a/extern/osg-ffmpeg-videoplayer/audiofactory.hpp +++ b/extern/osg-ffmpeg-videoplayer/audiofactory.hpp @@ -10,7 +10,7 @@ namespace Video class MovieAudioFactory { public: - virtual std::shared_ptr createDecoder(VideoState* videoState) = 0; + virtual std::unique_ptr createDecoder(VideoState* videoState) = 0; virtual ~MovieAudioFactory() {} }; diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index d1592bd910..8fdbbe64bf 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -161,7 +161,7 @@ struct VideoState { osg::ref_ptr mTexture; MovieAudioFactory* mAudioFactory; - std::shared_ptr mAudioDecoder; + std::unique_ptr mAudioDecoder; ExternalClock mExternalClock; From 4509b05bc8bf3dfc2f1f8de3f00ebb7a3657040e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 3 Apr 2022 23:23:25 +0200 Subject: [PATCH 2275/2859] Use std::make_shared instead of new make_shared allocates single storage for ref counter and the object. std::shared_ptr(new T) allocates 2 storages. --- apps/opencs/model/filter/parser.cpp | 14 +++++++------- apps/openmw/mwrender/actoranimation.cpp | 4 +--- apps/openmw/mwrender/animation.cpp | 9 ++++----- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/effectmanager.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 6 +++--- apps/openmw/mwrender/ripplesimulation.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 2 +- apps/openmw/mwrender/water.cpp | 2 +- apps/openmw/mwsound/movieaudiofactory.cpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- components/nifosg/nifloader.cpp | 4 ++-- 12 files changed, 24 insertions(+), 27 deletions(-) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 6f185d60fa..c69a54726d 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -260,11 +260,11 @@ std::shared_ptr CSMFilter::Parser::parseImp (bool allowEmpty, b { case Token::Type_Keyword_True: - return std::shared_ptr (new BooleanNode (true)); + return std::make_shared(true); case Token::Type_Keyword_False: - return std::shared_ptr (new BooleanNode (false)); + return std::make_shared(false); case Token::Type_Keyword_And: case Token::Type_Keyword_Or: @@ -278,7 +278,7 @@ std::shared_ptr CSMFilter::Parser::parseImp (bool allowEmpty, b if (mError) return std::shared_ptr(); - return std::shared_ptr (new NotNode (node)); + return std::make_shared(node); } case Token::Type_Keyword_Text: @@ -340,8 +340,8 @@ std::shared_ptr CSMFilter::Parser::parseNAry (const Token& keyw switch (keyword.mType) { - case Token::Type_Keyword_And: return std::shared_ptr (new AndNode (nodes)); - case Token::Type_Keyword_Or: return std::shared_ptr (new OrNode (nodes)); + case Token::Type_Keyword_And: return std::make_shared(nodes); + case Token::Type_Keyword_Or: return std::make_shared(nodes); default: error(); return std::shared_ptr(); } } @@ -407,7 +407,7 @@ std::shared_ptr CSMFilter::Parser::parseText() return std::shared_ptr(); } - return std::shared_ptr (new TextNode (columnId, text)); + return std::make_shared(columnId, text); } std::shared_ptr CSMFilter::Parser::parseValue() @@ -532,7 +532,7 @@ std::shared_ptr CSMFilter::Parser::parseValue() return std::shared_ptr(); } - return std::shared_ptr (new ValueNode (columnId, lowerType, upperType, lower, upper)); + return std::make_shared(columnId, lowerType, upperType, lower, upper); } void CSMFilter::Parser::error() diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 88e1d10d81..b3d76d423c 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -330,9 +330,7 @@ void ActorAnimation::resetControllers(osg::Node* node) if (node == nullptr) return; - std::shared_ptr src; - src.reset(new NullAnimationTime); - SceneUtil::ForceControllerSourcesVisitor removeVisitor(src); + SceneUtil::ForceControllerSourcesVisitor removeVisitor(std::make_shared()); node->accept(removeVisitor); } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b1c7bc89c4..cb4c074586 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -521,7 +521,7 @@ namespace MWRender , mAlpha(1.f) { for(size_t i = 0;i < sNumBlendMasks;i++) - mAnimationTimePtr[i].reset(new AnimationTime); + mAnimationTimePtr[i] = std::make_shared(); mLightListCallback = new SceneUtil::LightListCallback; } @@ -630,8 +630,7 @@ namespace MWRender if(!mResourceSystem->getVFS()->exists(kfname)) return; - std::shared_ptr animsrc; - animsrc.reset(new AnimSource); + auto animsrc = std::make_shared(); animsrc->mKeyframes = mResourceSystem->getKeyframeManager()->get(kfname); if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) @@ -660,7 +659,7 @@ namespace MWRender animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned)); } - mAnimSources.push_back(animsrc); + mAnimSources.push_back(std::move(animsrc)); SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]); mObjectRoot->accept(assignVisitor); @@ -1574,7 +1573,7 @@ namespace MWRender params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; - params.mAnimTime = std::shared_ptr(new EffectAnimationTime); + params.mAnimTime = std::make_shared(); trans->addUpdateCallback(new UpdateVfxCallback(params)); SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(params.mAnimTime)); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 627b200b71..78a524b74f 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -63,7 +63,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const updateParts(); } - mWeaponAnimationTime = std::shared_ptr(new WeaponAnimationTime(this)); + mWeaponAnimationTime = std::make_shared(this); } void CreatureWeaponAnimation::showWeapons(bool showWeapon) diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 3e785a769e..d5d01a5e41 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -32,7 +32,7 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu node->setNodeMask(Mask_Effect); Effect effect; - effect.mAnimTime.reset(new EffectAnimationTime); + effect.mAnimTime = std::make_shared(); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 3fdfaed72d..1ae792f60c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -275,8 +275,8 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr par { mNpc = mPtr.get()->mBase; - mHeadAnimationTime = std::shared_ptr(new HeadAnimationTime(mPtr)); - mWeaponAnimationTime = std::shared_ptr(new WeaponAnimationTime(this)); + mHeadAnimationTime = std::make_shared(mPtr); + mWeaponAnimationTime = std::make_shared(this); for(size_t i = 0;i < ESM::PRT_Count;i++) { @@ -846,7 +846,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g if (type == ESM::PRT_Weapon) src = mWeaponAnimationTime; else - src.reset(new NullAnimationTime); + src = std::make_shared(); SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); node->accept(assignVisitor); } diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 1045594c20..d1a8ea1ecd 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -48,7 +48,7 @@ namespace } osg::ref_ptr controller (new NifOsg::FlipController(0, 0.3f/rippleFrameCount, textures)); - controller->setSource(std::shared_ptr(new SceneUtil::FrameTimeSource)); + controller->setSource(std::make_shared()); node->addUpdateCallback(controller); osg::ref_ptr stateset (new osg::StateSet); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 8e4353f2ef..a9c56bdf19 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -642,7 +642,7 @@ namespace MWRender mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); - SceneUtil::AssignControllerSourcesVisitor assignVisitor = std::shared_ptr(new SceneUtil::FrameTimeSource); + SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::make_shared()); mParticleEffect->accept(assignVisitor); SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 47e60245ba..7d9aca9b76 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -599,7 +599,7 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) float fps = Fallback::Map::getFloat("Water_SurfaceFPS"); osg::ref_ptr controller (new NifOsg::FlipController(0, 1.f/fps, textures)); - controller->setSource(std::shared_ptr(new SceneUtil::FrameTimeSource)); + controller->setSource(std::make_shared()); node->setUpdateCallback(controller); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index d8c1c928ee..233b3c79ea 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -38,8 +38,8 @@ namespace MWSound public: MovieAudioDecoder(Video::VideoState *videoState) : Video::MovieAudioDecoder(videoState), mAudioTrack(nullptr) + , mDecoderBridge(std::make_shared(this)) { - mDecoderBridge.reset(new MWSoundDecoderBridge(this)); } size_t getSampleOffset() diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 5e60154a89..e931afa07b 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -244,7 +244,7 @@ namespace MWWorld mParent->addChild(state.mNode); - state.mEffectAnimationTime.reset(new MWRender::EffectAnimationTime); + state.mEffectAnimationTime = std::make_shared(); SceneUtil::AssignControllerSourcesVisitor assignVisitor (state.mEffectAnimationTime); state.mNode->accept(assignVisitor); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 67e8782296..17bd381a3d 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -388,9 +388,9 @@ namespace NifOsg { bool autoPlay = animflags & Nif::NiNode::AnimFlag_AutoPlay; if (autoPlay) - toSetup->setSource(std::shared_ptr(new SceneUtil::FrameTimeSource)); + toSetup->setSource(std::make_shared()); - toSetup->setFunction(std::shared_ptr(new ControllerFunction(ctrl))); + toSetup->setFunction(std::make_shared(ctrl)); } static osg::ref_ptr handleLodNode(const Nif::NiLODNode* niLodNode) From 25b26f6fa7305ff99a8de4487b15565ded8750ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 9 Apr 2022 01:06:15 +0300 Subject: [PATCH 2276/2859] Reduce calls in CharacterController::refreshHitRecoilAnims --- apps/openmw/mwmechanics/character.cpp | 29 ++++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1c0a4f2c85..affd25f60e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -207,15 +207,17 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { - bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); - bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); - bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); - bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + auto* world = MWBase::Environment::get().getWorld(); + auto& charClass = mPtr.getClass(); + auto& stats = charClass.getCreatureStats(mPtr); + bool recovery = stats.getHitRecovery(); + bool knockdown = stats.getKnockedDown(); + bool block = stats.getBlock(); + bool isSwimming = world->isSwimming(mPtr); + auto& prng = world->getPrng(); if(mHitState == CharState_None) { - if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 - || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)) + if (stats.getFatigue().getCurrent() < 0 || stats.getFatigue().getBase() == 0) { mTimeUntilWake = Misc::Rng::rollClosedProbability(prng) * 2 + 1; // Wake up after 1 to 3 seconds if (isSwimming && mAnimation->hasAnimation("swimknockout")) @@ -236,7 +238,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) mCurrentHit.erase(); } - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); + stats.setKnockedDown(true); } else if (knockdown) { @@ -255,7 +257,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) else { // Knockdown animation is missing. Cancel knockdown state. - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); + stats.setKnockedDown(false); } } else if (recovery) @@ -311,15 +313,14 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { mCurrentHit.erase(); if (knockdown) - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); + stats.setKnockedDown(false); if (recovery) - mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); + stats.setHitRecovery(false); if (block) - mPtr.getClass().getCreatureStats(mPtr).setBlock(false); + stats.setBlock(false); mHitState = CharState_None; } - else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0 - && mTimeUntilWake <= 0) + else if (isKnockedOut() && stats.getFatigue().getCurrent() > 0 && mTimeUntilWake <= 0) { mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; mAnimation->disable(mCurrentHit); From e85a979f10b5f348a4da4703c373f5ad694e8ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 9 Apr 2022 01:20:53 +0300 Subject: [PATCH 2277/2859] Reduce calls in CharacterController::handleTextKey --- apps/openmw/mwmechanics/character.cpp | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index affd25f60e..1d2ce8d44b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -959,6 +959,8 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil: sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); return; } + + auto& charClass = mPtr.getClass(); if(evt.compare(0, 10, "soundgen: ") == 0) { std::string soundgen = evt.substr(10); @@ -984,7 +986,7 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil: } } - std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); + std::string sound = charClass.getSoundIdFromSndGen(mPtr, soundgen); if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -1007,8 +1009,8 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil: // Not ours, skip it return; } - size_t off = groupname.size()+2; - size_t len = evt.size() - off; + const size_t off = groupname.size()+2; + const size_t len = evt.size() - off; if(groupname == "shield" && evt.compare(off, len, "equip attach") == 0) mAnimation->showCarriedLeft(true); @@ -1019,21 +1021,21 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil: else if(evt.compare(off, len, "unequip detach") == 0) mAnimation->showWeapons(false); else if(evt.compare(off, len, "chop hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if(evt.compare(off, len, "slash hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if(evt.compare(off, len, "thrust hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); else if(evt.compare(off, len, "hit") == 0) { if (groupname == "attack1" || groupname == "swimattack1") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if (groupname == "attack2" || groupname == "swimattack2") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if (groupname == "attack3" || groupname == "swimattack3") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); else - mPtr.getClass().hit(mPtr, mAttackStrength); + charClass.hit(mPtr, mAttackStrength); } else if (!groupname.empty() && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0) @@ -1057,11 +1059,11 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil: if (!hasHitKey) { if (groupname == "attack1" || groupname == "swimattack1") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); else if (groupname == "attack2" || groupname == "swimattack2") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); else if (groupname == "attack3" || groupname == "swimattack3") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); } } else if (evt.compare(off, len, "shoot attach") == 0) @@ -1081,7 +1083,7 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil: } else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) - mPtr.getClass().block(mPtr); + charClass.block(mPtr); else if (groupname == "containeropen" && evt.compare(off, len, "loot") == 0) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, mPtr); } From dd222b9ef16785277ede71040442be63879f5b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 9 Apr 2022 01:21:34 +0300 Subject: [PATCH 2278/2859] Reduce calls in CharacterController::updateIdleStormState --- apps/openmw/mwmechanics/character.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1d2ce8d44b..8e29f2a5b1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1101,9 +1101,10 @@ void CharacterController::updateIdleStormState(bool inwater) return; } - if (MWBase::Environment::get().getWorld()->isInStorm()) + auto* world = MWBase::Environment::get().getWorld(); + if (world->isInStorm()) { - osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + osg::Vec3f stormDirection = world->getStormDirection(); osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); stormDirection.normalize(); characterDirection.normalize(); From 058da82823eca29de6af2c27603a087206d34a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 9 Apr 2022 01:52:52 +0300 Subject: [PATCH 2279/2859] Reduce calls in CharacterController::updateState --- apps/openmw/mwmechanics/character.cpp | 59 ++++++++++++--------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 8e29f2a5b1..b2eb316e24 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1139,7 +1139,9 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const bool CharacterController::updateState(CharacterState idle) { - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + auto* world = MWBase::Environment::get().getWorld(); + auto& prng = world->getPrng(); + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); @@ -1154,7 +1156,7 @@ bool CharacterController::updateState(CharacterState idle) std::string upSoundId; std::string downSoundId; bool weaponChanged = false; - if (mPtr.getClass().hasInventoryStore(mPtr)) + if (cls.hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); @@ -1182,13 +1184,13 @@ bool CharacterController::updateState(CharacterState idle) // For biped actors, blend weapon animations with lower body animations with higher priority MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); - if (mPtr.getClass().isBipedal(mPtr)) + if (cls.isBipedal(mPtr)) priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; // We should not play equipping animation and sound during weapon->weapon transition - bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None && + const bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires), @@ -1241,7 +1243,6 @@ bool CharacterController::updateState(CharacterState idle) if(!downSoundId.empty()) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, downSoundId, 1.0f, 1.0f); } } @@ -1297,11 +1298,10 @@ bool CharacterController::updateState(CharacterState idle) if(isWerewolf) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore &store = world->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfEquip", prng); if(sound) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); } } @@ -1311,7 +1311,6 @@ bool CharacterController::updateState(CharacterState idle) if(!upSoundId.empty() && !isStillWeapon) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); } } @@ -1329,10 +1328,9 @@ bool CharacterController::updateState(CharacterState idle) if(isWerewolf) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) + if(stats.getStance(MWMechanics::CreatureStats::Stance_Run) && mHasMovedInXY - && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) + && !world->isSwimming(mPtr) && mWeaponType == ESM::Weapon::None) { if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) @@ -1347,7 +1345,7 @@ bool CharacterController::updateState(CharacterState idle) bool ammunition = true; bool isWeapon = false; float weapSpeed = 1.f; - if (mPtr.getClass().hasInventoryStore(mPtr)) + if (cls.hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); @@ -1380,12 +1378,12 @@ bool CharacterController::updateState(CharacterState idle) bool resetIdle = ammunition; if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); + world->breakInvisibility(mPtr); mAttackStrength = 0; // Randomize attacks for non-bipedal creatures - if (mPtr.getClass().getType() == ESM::Creature::sRecordId && - !mPtr.getClass().isBipedal(mPtr) && + if (cls.getType() == ESM::Creature::sRecordId && + !cls.isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { mCurrentWeapon = chooseRandomAttackAnimation(); @@ -1406,13 +1404,13 @@ bool CharacterController::updateState(CharacterState idle) } std::string spellid = stats.getSpells().getSelectedSpell(); bool isMagicItem = false; - bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); + bool canCast = mCastingManualSpell || world->startSpellCast(mPtr); if (spellid.empty()) { - if (mPtr.getClass().hasInventoryStore(mPtr)) + if (cls.hasInventoryStore(mPtr)) { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); if (inv.getSelectedEnchantItem() != inv.end()) { const MWWorld::Ptr& enchantItem = *inv.getSelectedEnchantItem(); @@ -1426,7 +1424,7 @@ bool CharacterController::updateState(CharacterState idle) if (isMagicItem && !useCastingAnimations) { // Enchanted items by default do not use casting animations - MWBase::Environment::get().getWorld()->castSpell(mPtr); + world->castSpell(mPtr); resetIdle = false; } else if(!spellid.empty() && canCast) @@ -1435,7 +1433,7 @@ bool CharacterController::updateState(CharacterState idle) cast.playSpellCastingEffects(spellid, isMagicItem); std::vector effects; - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore &store = world->getStore(); if (isMagicItem) { const ESM::Enchantment *enchantment = store.get().find(spellid); @@ -1449,7 +1447,7 @@ bool CharacterController::updateState(CharacterState idle) const ESM::MagicEffect *effect = store.get().find(effects.back().mEffectID); // use last effect of list for color of VFX_Hands - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); + const ESM::Static* castStatic = world->getStore().get().find ("VFX_Hands"); for (size_t iter = 0; iter < effects.size(); ++iter) // play hands vfx for each effect { @@ -1468,7 +1466,7 @@ bool CharacterController::updateState(CharacterState idle) { startKey = "start"; stopKey = "stop"; - MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately + world->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately mCastingManualSpell = false; } else @@ -1497,10 +1495,10 @@ bool CharacterController::updateState(CharacterState idle) } else if(mWeaponType == ESM::Weapon::PickProbe) { - MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + MWWorld::ContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr item = *weapon; // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); + MWWorld::Ptr target = world->getFacedObject(); std::string resultMessage, resultSound; if(!target.isEmpty()) @@ -1518,8 +1516,7 @@ bool CharacterController::updateState(CharacterState idle) if(!resultMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); if(!resultSound.empty()) - MWBase::Environment::get().getSoundManager()->playSound3D(target, resultSound, - 1.0f, 1.0f); + sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f); } else if (ammunition) { @@ -1545,7 +1542,7 @@ bool CharacterController::updateState(CharacterState idle) { if (isWeapon) { - MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + MWWorld::ConstContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); mAttackType = getBestAttack(weapon->get()->mBase); } else @@ -1611,11 +1608,9 @@ bool CharacterController::updateState(CharacterState idle) if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(isWerewolf) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore &store = world->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfSwing", prng); if(sound) sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); @@ -1785,9 +1780,9 @@ bool CharacterController::updateState(CharacterState idle) mUpperBodyState = UpperCharState_WeapEquiped; } - if (mPtr.getClass().hasInventoryStore(mPtr)) + if (cls.hasInventoryStore(mPtr)) { - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId && updateCarriedLeftVisible(mWeaponType)) From 21ffbcc4b466963b677b8b51d11d342f4c9e82db Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Sun, 10 Apr 2022 07:57:02 +0000 Subject: [PATCH 2280/2859] Lua i18n updates --- CI/before_install.osx.sh | 2 + CI/before_script.android.sh | 17 ++ CI/before_script.msvc.sh | 26 ++ CI/install_debian_deps.sh | 2 +- CMakeLists.txt | 18 ++ apps/openmw/mwlua/context.hpp | 4 +- apps/openmw/mwlua/luabindings.cpp | 9 +- apps/openmw/mwlua/luamanagerimp.cpp | 12 +- apps/openmw/mwlua/luamanagerimp.hpp | 4 +- apps/openmw_test_suite/CMakeLists.txt | 2 +- apps/openmw_test_suite/lua/test_i18n.cpp | 110 ------- apps/openmw_test_suite/lua/test_l10n.cpp | 157 ++++++++++ components/CMakeLists.txt | 8 +- components/l10n/messagebundles.cpp | 167 +++++++++++ components/l10n/messagebundles.hpp | 55 ++++ components/lua/i18n.cpp | 111 ------- components/lua/i18n.hpp | 41 --- components/lua/l10n.cpp | 136 +++++++++ components/lua/l10n.hpp | 42 +++ .../reference/modding/settings/general.rst | 18 ++ .../source/reference/modding/settings/lua.rst | 12 - extern/CMakeLists.txt | 63 ++++ extern/i18n.lua/CMakeLists.txt | 17 -- extern/i18n.lua/LICENSE | 22 -- extern/i18n.lua/README.md | 164 ---------- extern/i18n.lua/i18n/init.lua | 188 ------------ extern/i18n.lua/i18n/interpolate.lua | 60 ---- extern/i18n.lua/i18n/plural.lua | 280 ------------------ extern/i18n.lua/i18n/variants.lua | 49 --- extern/i18n.lua/i18n/version.lua | 1 - files/CMakeLists.txt | 1 - files/builtin_scripts/CMakeLists.txt | 2 +- files/builtin_scripts/i18n/Calendar/en.lua | 42 --- files/builtin_scripts/l10n/Calendar/en.yaml | 39 +++ files/builtin_scripts/openmw_aux/calendar.lua | 18 +- files/lua_api/openmw/core.lua | 84 ++++-- files/settings-default.cfg | 9 +- 37 files changed, 839 insertions(+), 1153 deletions(-) delete mode 100644 apps/openmw_test_suite/lua/test_i18n.cpp create mode 100644 apps/openmw_test_suite/lua/test_l10n.cpp create mode 100644 components/l10n/messagebundles.cpp create mode 100644 components/l10n/messagebundles.hpp delete mode 100644 components/lua/i18n.cpp delete mode 100644 components/lua/i18n.hpp create mode 100644 components/lua/l10n.cpp create mode 100644 components/lua/l10n.hpp delete mode 100644 extern/i18n.lua/CMakeLists.txt delete mode 100644 extern/i18n.lua/LICENSE delete mode 100644 extern/i18n.lua/README.md delete mode 100644 extern/i18n.lua/i18n/init.lua delete mode 100644 extern/i18n.lua/i18n/interpolate.lua delete mode 100644 extern/i18n.lua/i18n/plural.lua delete mode 100644 extern/i18n.lua/i18n/variants.lua delete mode 100644 extern/i18n.lua/i18n/version.lua delete mode 100644 files/builtin_scripts/i18n/Calendar/en.lua create mode 100644 files/builtin_scripts/l10n/Calendar/en.yaml diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index b3463aa9b8..b5bb35957f 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -13,6 +13,8 @@ brew update --quiet command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake command -v qmake >/dev/null 2>&1 || brew install qt@5 +command -v pkgdata >/dev/null 2>&1 || brew install icu4c +brew install yaml-cpp export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6 ccache --version diff --git a/CI/before_script.android.sh b/CI/before_script.android.sh index 80a3f11e57..47655f8ace 100755 --- a/CI/before_script.android.sh +++ b/CI/before_script.android.sh @@ -6,6 +6,20 @@ sed -i s/"NOT FFVER_OK"/"FALSE"/ CMakeLists.txt mkdir -p build cd build +# Build a version of ICU for the host so that it can use the tools during the cross-compilation +mkdir -p icu-host-build +cd icu-host-build +if [ -r ../extern/fetched/icu/icu4c/source/configure ]; then + ICU_SOURCE_DIR=../extern/fetched/icu/icu4c/source +else + wget https://github.com/unicode-org/icu/archive/refs/tags/release-70-1.zip + unzip release-70-1.zip + ICU_SOURCE_DIR=./icu-release-70-1/icu4c/source +fi +${ICU_SOURCE_DIR}/configure --disable-tests --disable-samples --disable-icuio --disable-extras CC="ccache gcc" CXX="ccache g++" +make -j $(nproc) +cd .. + cmake \ -DCMAKE_TOOLCHAIN_FILE=/android-ndk-r22/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ @@ -26,4 +40,7 @@ cmake \ -DBUILD_BULLETOBJECTTOOL=OFF \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_SQLITE3=OFF \ +-DOPENMW_USE_SYSTEM_YAML_CPP=OFF \ +-DOPENMW_USE_SYSTEM_ICU=OFF \ +-DOPENMW_ICU_HOST_BUILD_DIR="$(pwd)/icu-host-build" \ .. diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 563a65595b..5cfff39b3d 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -512,6 +512,8 @@ if ! [ -z $USE_CCACHE ]; then add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" fi +ICU_VER="70_1" + echo echo "===================================" echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}" @@ -598,6 +600,11 @@ if [ -z $SKIP_DOWNLOAD ]; then git clone -b release-1.10.0 https://github.com/google/googletest.git fi fi + + # ICU + download "ICU ${ICU_VER/_/.}"\ + "https://github.com/unicode-org/icu/releases/download/release-${ICU_VER/_/-}/icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" \ + "icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" fi cd .. #/.. @@ -1027,6 +1034,24 @@ if [ ! -z $TEST_FRAMEWORK ]; then fi +cd $DEPS +echo +# ICU +printf "ICU ${ICU_VER/_/.}... " +{ + if [ -d ICU ]; then + printf "Exists. " + elif [ -z $SKIP_EXTRACT ]; then + rm -rf ICU + eval 7z x -y icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip -o$(real_pwd)/ICU $STRIP + fi + export ICU_ROOT="$(real_pwd)/ICU" + add_cmake_opts -DICU_INCLUDE_DIR="${ICU_ROOT}/include" \ + -DICU_LIBRARY="${ICU_ROOT}/lib${BITS}/icuuc.lib " \ + -DICU_DEBUG=ON + echo Done. +} + echo cd $DEPS_INSTALL/.. echo @@ -1034,6 +1059,7 @@ echo "Setting up OpenMW build..." add_cmake_opts -DOPENMW_MP_BUILD=on add_cmake_opts -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" add_cmake_opts -DOPENMW_USE_SYSTEM_SQLITE3=OFF +add_cmake_opts -DOPENMW_USE_SYSTEM_YAML_CPP=OFF if [ ! -z $CI ]; then case $STEP in components ) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 93f701d011..c089558208 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -20,7 +20,7 @@ declare -rA GROUPED_DEPS=( libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev - librecast-dev libsqlite3-dev ca-certificates + librecast-dev libsqlite3-dev ca-certificates libicu-dev libyaml-cpp-dev " # These dependencies can alternatively be built and linked statically. diff --git a/CMakeLists.txt b/CMakeLists.txt index bc4fa56e25..dbb88e14f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -248,6 +248,23 @@ set(USED_OSG_PLUGINS osgdb_serializers_osg osgdb_tga) + +option(OPENMW_USE_SYSTEM_ICU "Use system ICU library instead of internal. If disabled, requires autotools" ON) +if(OPENMW_USE_SYSTEM_ICU) + find_package(ICU COMPONENTS uc i18n) +endif() + +option(OPENMW_USE_SYSTEM_YAML_CPP "Use system yaml-cpp library instead of internal." ON) +if(OPENMW_USE_SYSTEM_YAML_CPP) + set(_yaml_cpp_static_default OFF) +else() + set(_yaml_cpp_static_default ON) +endif() +option(YAML_CPP_STATIC "Link static build of yaml-cpp into the binaries" ${_yaml_cpp_static_default}) +if (OPENMW_USE_SYSTEM_YAML_CPP) + find_package(yaml-cpp) +endif() + add_subdirectory(extern) # Sound setup @@ -442,6 +459,7 @@ include_directories( ${LUA_INCLUDE_DIR} ${SOL_INCLUDE_DIR} ${SOL_CONFIG_DIR} + ${ICU_INCLUDE_DIRS} ) link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) diff --git a/apps/openmw/mwlua/context.hpp b/apps/openmw/mwlua/context.hpp index 7ff584d8cc..124e7f06be 100644 --- a/apps/openmw/mwlua/context.hpp +++ b/apps/openmw/mwlua/context.hpp @@ -7,7 +7,7 @@ namespace LuaUtil { class LuaState; class UserdataSerializer; - class I18nManager; + class L10nManager; } namespace MWLua @@ -21,7 +21,7 @@ namespace MWLua LuaManager* mLuaManager; LuaUtil::LuaState* mLua; LuaUtil::UserdataSerializer* mSerializer; - LuaUtil::I18nManager* mI18n; + LuaUtil::L10nManager* mL10n; WorldView* mWorldView; LocalEventQueue* mLocalEventQueue; GlobalEventQueue* mGlobalEventQueue; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index b47e1f2dec..c88045ae84 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -1,7 +1,7 @@ #include "luabindings.hpp" #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" @@ -52,7 +52,12 @@ namespace MWLua context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)}); }; addTimeBindings(api, context, false); - api["i18n"] = [i18n=context.mI18n](const std::string& context) { return i18n->getContext(context); }; + api["l10n"] = [l10n=context.mL10n](const std::string& context, const sol::object &fallbackLocale) { + if (fallbackLocale == sol::nil) + return l10n->getContext(context); + else + return l10n->getContext(context, fallbackLocale.as()); + }; const MWWorld::Store* gmst = &MWBase::Environment::get().getWorld()->getStore().get(); api["getGMST"] = [lua=context.mLua, gmst](const std::string& setting) -> sol::object { diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index df49862df6..e2037049d8 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -30,7 +30,7 @@ namespace MWLua LuaManager::LuaManager(const VFS::Manager* vfs, const std::string& libsDir) : mLua(vfs, &mConfiguration) , mUiResourceManager(vfs) - , mI18n(vfs, &mLua) + , mL10n(vfs, &mLua) { Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion(); mLua.addInternalLibSearchPath(libsDir); @@ -57,7 +57,7 @@ namespace MWLua context.mIsGlobal = true; context.mLuaManager = this; context.mLua = &mLua; - context.mI18n = &mI18n; + context.mL10n = &mL10n; context.mWorldView = &mWorldView; context.mLocalEventQueue = &mLocalEvents; context.mGlobalEventQueue = &mGlobalEvents; @@ -67,10 +67,10 @@ namespace MWLua localContext.mIsGlobal = false; localContext.mSerializer = mLocalSerializer.get(); - mI18n.init(); - std::vector preferredLanguages; - Misc::StringUtils::split(Settings::Manager::getString("i18n preferred languages", "Lua"), preferredLanguages, ", "); - mI18n.setPreferredLanguages(preferredLanguages); + mL10n.init(); + std::vector preferredLocales; + Misc::StringUtils::split(Settings::Manager::getString("preferred locales", "General"), preferredLocales, ", "); + mL10n.setPreferredLocales(preferredLocales); initObjectBindingsForGlobalScripts(context); initCellBindingsForGlobalScripts(context); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index fed6dc9dc7..e6f5af78be 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -102,7 +102,7 @@ namespace MWLua LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; LuaUi::ResourceManager mUiResourceManager; - LuaUtil::I18nManager mI18n; + LuaUtil::L10nManager mL10n; sol::table mNearbyPackage; sol::table mUserInterfacePackage; sol::table mCameraPackage; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 854526dc8c..3b12e7c227 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -22,7 +22,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) lua/test_utilpackage.cpp lua/test_serialization.cpp lua/test_configuration.cpp - lua/test_i18n.cpp + lua/test_l10n.cpp lua/test_storage.cpp lua/test_ui_content.cpp diff --git a/apps/openmw_test_suite/lua/test_i18n.cpp b/apps/openmw_test_suite/lua/test_i18n.cpp deleted file mode 100644 index 427482be64..0000000000 --- a/apps/openmw_test_suite/lua/test_i18n.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "gmock/gmock.h" -#include - -#include - -#include -#include - -#include "testing_util.hpp" - -namespace -{ - using namespace testing; - - TestFile invalidScript("not a script"); - TestFile incorrectScript("return { incorrectSection = {}, engineHandlers = { incorrectHandler = function() end } }"); - TestFile emptyScript(""); - - TestFile test1En(R"X( -return { - good_morning = "Good morning.", - you_have_arrows = { - one = "You have one arrow.", - other = "You have %{count} arrows.", - }, -} -)X"); - - TestFile test1De(R"X( -return { - good_morning = "Guten Morgen.", - you_have_arrows = { - one = "Du hast ein Pfeil.", - other = "Du hast %{count} Pfeile.", - }, - ["Hello %{name}!"] = "Hallo %{name}!", -} -)X"); - -TestFile test2En(R"X( -return { - good_morning = "Morning!", - you_have_arrows = "Arrows count: %{count}", -} -)X"); - - TestFile invalidTest2De(R"X( -require('math') -return {} -)X"); - - struct LuaI18nTest : Test - { - std::unique_ptr mVFS = createTestVFS({ - {"i18n/Test1/en.lua", &test1En}, - {"i18n/Test1/de.lua", &test1De}, - {"i18n/Test2/en.lua", &test2En}, - {"i18n/Test2/de.lua", &invalidTest2De}, - }); - - LuaUtil::ScriptsConfiguration mCfg; - std::string mLibsPath = (Files::TargetPathType("openmw_test_suite").getLocalPath() / "resources" / "lua_libs").string(); - }; - - TEST_F(LuaI18nTest, I18n) - { - internal::CaptureStdout(); - LuaUtil::LuaState lua{mVFS.get(), &mCfg}; - sol::state& l = lua.sol(); - LuaUtil::I18nManager i18n(mVFS.get(), &lua); - lua.addInternalLibSearchPath(mLibsPath); - i18n.init(); - i18n.setPreferredLanguages({"de", "en"}); - EXPECT_THAT(internal::GetCapturedStdout(), "I18n preferred languages: de en\n"); - - internal::CaptureStdout(); - l["t1"] = i18n.getContext("Test1"); - EXPECT_THAT(internal::GetCapturedStdout(), "Language file \"i18n/Test1/de.lua\" is enabled\n"); - - internal::CaptureStdout(); - l["t2"] = i18n.getContext("Test2"); - { - std::string output = internal::GetCapturedStdout(); - EXPECT_THAT(output, HasSubstr("Can not load i18n/Test2/de.lua")); - EXPECT_THAT(output, HasSubstr("Language file \"i18n/Test2/en.lua\" is enabled")); - } - - EXPECT_EQ(get(l, "t1('good_morning')"), "Guten Morgen."); - EXPECT_EQ(get(l, "t1('you_have_arrows', {count=1})"), "Du hast ein Pfeil."); - EXPECT_EQ(get(l, "t1('you_have_arrows', {count=5})"), "Du hast 5 Pfeile."); - EXPECT_EQ(get(l, "t1('Hello %{name}!', {name='World'})"), "Hallo World!"); - EXPECT_EQ(get(l, "t2('good_morning')"), "Morning!"); - EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); - - internal::CaptureStdout(); - i18n.setPreferredLanguages({"en", "de"}); - EXPECT_THAT(internal::GetCapturedStdout(), - "I18n preferred languages: en de\n" - "Language file \"i18n/Test1/en.lua\" is enabled\n" - "Language file \"i18n/Test2/en.lua\" is enabled\n"); - - EXPECT_EQ(get(l, "t1('good_morning')"), "Good morning."); - EXPECT_EQ(get(l, "t1('you_have_arrows', {count=1})"), "You have one arrow."); - EXPECT_EQ(get(l, "t1('you_have_arrows', {count=5})"), "You have 5 arrows."); - EXPECT_EQ(get(l, "t1('Hello %{name}!', {name='World'})"), "Hello World!"); - EXPECT_EQ(get(l, "t2('good_morning')"), "Morning!"); - EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); - } - -} diff --git a/apps/openmw_test_suite/lua/test_l10n.cpp b/apps/openmw_test_suite/lua/test_l10n.cpp new file mode 100644 index 0000000000..c989d56e72 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_l10n.cpp @@ -0,0 +1,157 @@ +#include "gmock/gmock.h" +#include + +#include + +#include +#include + +#include "testing_util.hpp" + +namespace +{ + using namespace testing; + + TestFile invalidScript("not a script"); + TestFile incorrectScript("return { incorrectSection = {}, engineHandlers = { incorrectHandler = function() end } }"); + TestFile emptyScript(""); + + TestFile test1En(R"X( +good_morning: "Good morning." +you_have_arrows: |- + {count, plural, + =0{You have no arrows.} + one{You have one arrow.} + other{You have {count} arrows.} + } +pc_must_come: |- + {PCGender, select, + male {He is} + female {She is} + other {They are} + } coming with us. +quest_completion: "The quest is {done, number, percent} complete." +ordinal: "You came in {num, ordinal} place." +spellout: "There {num, plural, one{is {num, spellout} thing} other{are {num, spellout} things}}." +duration: "It took {num, duration}" +numbers: "{int} and {double, number, integer} are integers, but {double} is a double" +rounding: "{value, number, :: .00}" +)X"); + + TestFile test1De(R"X( +good_morning: "Guten Morgen." +you_have_arrows: |- + {count, plural, + one{Du hast ein Pfeil.} + other{Du hast {count} Pfeile.} + } +"Hello {name}!": "Hallo {name}!" +)X"); + + TestFile test1EnUS(R"X( +currency: "You have {money, number, currency}" +)X"); + +TestFile test2En(R"X( +good_morning: "Morning!" +you_have_arrows: "Arrows count: {count}" +)X"); + + struct LuaL10nTest : Test + { + std::unique_ptr mVFS = createTestVFS({ + {"l10n/Test1/en.yaml", &test1En}, + {"l10n/Test1/en_US.yaml", &test1EnUS}, + {"l10n/Test1/de.yaml", &test1De}, + {"l10n/Test2/en.yaml", &test2En}, + {"l10n/Test3/en.yaml", &test1En}, + {"l10n/Test3/de.yaml", &test1De}, + }); + + LuaUtil::ScriptsConfiguration mCfg; + }; + + TEST_F(LuaL10nTest, L10n) + { + internal::CaptureStdout(); + LuaUtil::LuaState lua{mVFS.get(), &mCfg}; + sol::state& l = lua.sol(); + LuaUtil::L10nManager l10n(mVFS.get(), &lua); + l10n.init(); + l10n.setPreferredLocales({"de", "en"}); + EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: de en\n"); + + internal::CaptureStdout(); + l["t1"] = l10n.getContext("Test1"); + EXPECT_THAT(internal::GetCapturedStdout(), + "Fallback locale: en\n" + "Language file \"l10n/Test1/de.yaml\" is enabled\n" + "Language file \"l10n/Test1/en.yaml\" is enabled\n"); + + internal::CaptureStdout(); + l["t2"] = l10n.getContext("Test2"); + { + std::string output = internal::GetCapturedStdout(); + EXPECT_THAT(output, HasSubstr("Language file \"l10n/Test2/en.yaml\" is enabled")); + } + + EXPECT_EQ(get(l, "t1('good_morning')"), "Guten Morgen."); + EXPECT_EQ(get(l, "t1('you_have_arrows', {count=1})"), "Du hast ein Pfeil."); + EXPECT_EQ(get(l, "t1('you_have_arrows', {count=5})"), "Du hast 5 Pfeile."); + EXPECT_EQ(get(l, "t1('Hello {name}!', {name='World'})"), "Hallo World!"); + EXPECT_EQ(get(l, "t2('good_morning')"), "Morning!"); + EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); + + internal::CaptureStdout(); + l10n.setPreferredLocales({"en", "de"}); + EXPECT_THAT(internal::GetCapturedStdout(), + "Preferred locales: en de\n" + "Language file \"l10n/Test1/en.yaml\" is enabled\n" + "Language file \"l10n/Test1/de.yaml\" is enabled\n" + "Language file \"l10n/Test2/en.yaml\" is enabled\n"); + + EXPECT_EQ(get(l, "t1('good_morning')"), "Good morning."); + EXPECT_EQ(get(l, "t1('you_have_arrows', {count=1})"), "You have one arrow."); + EXPECT_EQ(get(l, "t1('you_have_arrows', {count=5})"), "You have 5 arrows."); + EXPECT_EQ(get(l, "t1('pc_must_come', {PCGender=\"male\"})"), "He is coming with us."); + EXPECT_EQ(get(l, "t1('pc_must_come', {PCGender=\"female\"})"), "She is coming with us."); + EXPECT_EQ(get(l, "t1('pc_must_come', {PCGender=\"blah\"})"), "They are coming with us."); + EXPECT_EQ(get(l, "t1('pc_must_come', {PCGender=\"other\"})"), "They are coming with us."); + EXPECT_EQ(get(l, "t1('quest_completion', {done=0.1})"), "The quest is 10% complete."); + EXPECT_EQ(get(l, "t1('quest_completion', {done=1})"), "The quest is 100% complete."); + EXPECT_EQ(get(l, "t1('ordinal', {num=1})"), "You came in 1st place."); + EXPECT_EQ(get(l, "t1('ordinal', {num=100})"), "You came in 100th place."); + EXPECT_EQ(get(l, "t1('spellout', {num=1})"), "There is one thing."); + EXPECT_EQ(get(l, "t1('spellout', {num=100})"), "There are one hundred things."); + EXPECT_EQ(get(l, "t1('duration', {num=100})"), "It took 1:40"); + EXPECT_EQ(get(l, "t1('numbers', {int=123, double=123.456})"), "123 and 123 are integers, but 123.456 is a double"); + EXPECT_EQ(get(l, "t1('rounding', {value=123.456789})"), "123.46"); + // Check that failed messages display the key instead of an empty string + EXPECT_EQ(get(l, "t1('{mismatched_braces')"), "{mismatched_braces"); + EXPECT_EQ(get(l, "t1('{unknown_arg}')"), "{unknown_arg}"); + EXPECT_EQ(get(l, "t1('{num, integer}', {num=1})"), "{num, integer}"); + // Doesn't give a valid currency symbol with `en`. Not that openmw is designed for real world currency. + l10n.setPreferredLocales({"en-US", "de"}); + EXPECT_EQ(get(l, "t1('currency', {money=10000.10})"), "You have $10,000.10"); + // Note: Not defined in English localisation file, so we fall back to the German before falling back to the key + EXPECT_EQ(get(l, "t1('Hello {name}!', {name='World'})"), "Hallo World!"); + EXPECT_EQ(get(l, "t2('good_morning')"), "Morning!"); + EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); + + // Test that locales with variants and country codes fall back to more generic locales + internal::CaptureStdout(); + l10n.setPreferredLocales({"en-GB-oed", "de"}); + EXPECT_THAT(internal::GetCapturedStdout(), + "Preferred locales: en_GB_OED de\n" + "Language file \"l10n/Test1/en.yaml\" is enabled\n" + "Language file \"l10n/Test1/de.yaml\" is enabled\n" + "Language file \"l10n/Test2/en.yaml\" is enabled\n"); + EXPECT_EQ(get(l, "t2('you_have_arrows', {count=3})"), "Arrows count: 3"); + + // Test setting fallback language + l["t3"] = l10n.getContext("Test3", "de"); + l10n.setPreferredLocales({"en"}); + EXPECT_EQ(get(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!"); + } + +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index af56b37dea..5a3292228e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -29,7 +29,11 @@ endif (GIT_CHECKOUT) # source files add_component_dir (lua - luastate scriptscontainer utilpackage serialization configuration i18n storage + luastate scriptscontainer utilpackage serialization configuration l10n storage + ) + +add_component_dir (l10n + messagebundles ) add_component_dir (settings @@ -395,6 +399,8 @@ target_link_libraries(components Base64 SQLite::SQLite3 smhasher + ${ICU_LIBRARIES} + yaml-cpp ) target_link_libraries(components ${BULLET_LIBRARIES}) diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp new file mode 100644 index 0000000000..9560e60d3b --- /dev/null +++ b/components/l10n/messagebundles.cpp @@ -0,0 +1,167 @@ +#include "messagebundles.hpp" + +#include +#include +#include +#include + +#include + +namespace l10n +{ + MessageBundles::MessageBundles(const std::vector &preferredLocales, icu::Locale &fallbackLocale) : + mFallbackLocale(fallbackLocale) + { + setPreferredLocales(preferredLocales); + } + + void MessageBundles::setPreferredLocales(const std::vector &preferredLocales) + { + mPreferredLocales.clear(); + mPreferredLocaleStrings.clear(); + for (const icu::Locale &loc: preferredLocales) + { + mPreferredLocales.push_back(loc); + mPreferredLocaleStrings.push_back(loc.getName()); + // Try without variant or country if they are specified, starting with the most specific + if (strcmp(loc.getVariant(), "") != 0) + { + icu::Locale withoutVariant(loc.getLanguage(), loc.getCountry()); + mPreferredLocales.push_back(withoutVariant); + mPreferredLocaleStrings.push_back(withoutVariant.getName()); + } + if (strcmp(loc.getCountry(), "") != 0) + { + icu::Locale withoutCountry(loc.getLanguage()); + mPreferredLocales.push_back(withoutCountry); + mPreferredLocaleStrings.push_back(withoutCountry.getName()); + } + } + } + + std::string getErrorText(const UParseError &parseError) + { + icu::UnicodeString preContext(parseError.preContext), postContext(parseError.postContext); + std::string parseErrorString; + preContext.toUTF8String(parseErrorString); + postContext.toUTF8String(parseErrorString); + return parseErrorString; + } + + static bool checkSuccess(const icu::ErrorCode &status, const std::string &message, const UParseError parseError = UParseError()) + { + if (status.isFailure()) + { + std::string errorText = getErrorText(parseError); + if (errorText.size()) + { + Log(Debug::Error) << message << ": " << status.errorName() << " in \"" << errorText << "\""; + } + else + { + Log(Debug::Error) << message << ": " << status.errorName(); + } + } + return status.isSuccess(); + } + + void MessageBundles::load(std::istream &input, const icu::Locale& lang, const std::string &path) + { + try + { + YAML::Node data = YAML::Load(input); + std::string localeName = lang.getName(); + for (const auto& it: data) + { + std::string key = it.first.as(); + std::string value = it.second.as(); + icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(value); + icu::ErrorCode status; + UParseError parseError; + icu::MessageFormat message(pattern, lang, parseError, status); + if (checkSuccess(status, std::string("Failed to create message ") + + key + " for locale " + lang.getName(), parseError)) + { + mBundles[localeName].insert(std::make_pair(key, message)); + } + } + } + catch (std::exception& e) + { + Log(Debug::Error) << "Can not load " << path << ": " << e.what(); + } + } + + const icu::MessageFormat * MessageBundles::findMessage(std::string_view key, const std::string &localeName) const + { + auto iter = mBundles.find(localeName); + if (iter != mBundles.end()) + { + auto message = iter->second.find(key.data()); + if (message != iter->second.end()) + { + return &(message->second); + } + } + return nullptr; + } + + std::string MessageBundles::formatMessage(std::string_view key, const std::map &args) const + { + std::vector argNames; + std::vector argValues; + for (auto& [key, value] : args) + { + argNames.push_back(icu::UnicodeString::fromUTF8(key)); + argValues.push_back(value); + } + return formatMessage(key, argNames, argValues); + } + + std::string MessageBundles::formatMessage(std::string_view key, const std::vector &argNames, const std::vector &args) const + { + icu::UnicodeString result; + std::string resultString; + icu::ErrorCode success; + + const icu::MessageFormat *message = nullptr; + for (auto &loc: mPreferredLocaleStrings) + { + message = findMessage(key, loc); + if (message) + break; + } + // If no requested locales included the message, try the fallback locale + if (!message) + message = findMessage(key, mFallbackLocale.getName()); + + if (message) + { + if (args.size() > 0 && argNames.size() > 0) + message->format(&argNames[0], &args[0], args.size(), result, success); + else + message->format(nullptr, nullptr, args.size(), result, success); + checkSuccess(success, std::string("Failed to format message ") + key.data()); + result.toUTF8String(resultString); + return resultString; + } + icu::Locale defaultLocale(NULL); + if (mPreferredLocales.size() > 0) + { + defaultLocale = mPreferredLocales[0]; + } + UParseError parseError; + icu::MessageFormat defaultMessage(icu::UnicodeString::fromUTF8(key), 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.size() > 0 && argNames.size() > 0) + defaultMessage.format(&argNames[0], &args[0], args.size(), result, success); + else + defaultMessage.format(nullptr, nullptr, args.size(), result, success); + checkSuccess(success, std::string("Failed to format message ") + key.data()); + result.toUTF8String(resultString); + return resultString; + } +} diff --git a/components/l10n/messagebundles.hpp b/components/l10n/messagebundles.hpp new file mode 100644 index 0000000000..02333fad2c --- /dev/null +++ b/components/l10n/messagebundles.hpp @@ -0,0 +1,55 @@ +#ifndef COMPONENTS_L10N_MESSAGEBUNDLES_H +#define COMPONENTS_L10N_MESSAGEBUNDLES_H + +#include +#include +#include +#include + +#include +#include + +namespace l10n +{ + /** + * @brief A collection of Message Bundles + * + * Class handling localised message storage and lookup, including fallback locales when messages are missing. + * + * If no fallback locale is provided (or a message fails to be found), the key will be formatted instead, + * or returned verbatim if formatting fails. + * + */ + class MessageBundles + { + public: + /* @brief Constructs an empty MessageBundles + * + * @param preferredLocales user-requested locales, in order of priority + * Each locale will be checked when looking up messages, in case some resource files are incomplete. + * For each locale which contains a country code or a variant, the locales obtained by removing first + * the variant, then the country code, will also be checked before moving on to the next locale in the list. + * @param fallbackLocale the fallback locale which should be used if messages cannot be found for the user + * preferred locales + */ + MessageBundles(const std::vector &preferredLocales, icu::Locale &fallbackLocale); + std::string formatMessage(std::string_view key, const std::map &args) const; + std::string formatMessage(std::string_view key, const std::vector &argNames, const std::vector &args) const; + void setPreferredLocales(const std::vector &preferredLocales); + const std::vector & getPreferredLocales() const { return mPreferredLocales; } + void load(std::istream &input, const icu::Locale &lang, const std::string &path); + bool isLoaded(icu::Locale loc) const { return mBundles.find(loc.getName()) != mBundles.end(); } + const icu::Locale & getFallbackLocale() const { return mFallbackLocale; } + + private: + // icu::Locale isn't hashable (or comparable), so we use the string form instead, which is canonicalized + std::unordered_map> mBundles; + const icu::Locale mFallbackLocale; + std::vector mPreferredLocaleStrings; + std::vector mPreferredLocales; + const icu::MessageFormat * findMessage(std::string_view key, const std::string &localeName) const; + }; + +} + +#endif // COMPONENTS_L10N_MESSAGEBUNDLES_H diff --git a/components/lua/i18n.cpp b/components/lua/i18n.cpp deleted file mode 100644 index e4d8e3ba78..0000000000 --- a/components/lua/i18n.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "i18n.hpp" - -#include - -namespace sol -{ - template <> - struct is_automagical : std::false_type {}; -} - -namespace LuaUtil -{ - - void I18nManager::init() - { - mPreferredLanguages.push_back("en"); - sol::usertype ctx = mLua->sol().new_usertype("I18nContext"); - ctx[sol::meta_function::call] = &Context::translate; - try - { - mI18nLoader = mLua->loadInternalLib("i18n"); - sol::set_environment(mLua->newInternalLibEnvironment(), mI18nLoader); - } - catch (std::exception& e) - { - Log(Debug::Error) << "LuaUtil::I18nManager initialization failed: " << e.what(); - } - } - - void I18nManager::setPreferredLanguages(const std::vector& langs) - { - { - Log msg(Debug::Info); - msg << "I18n preferred languages:"; - for (const std::string& l : langs) - msg << " " << l; - } - mPreferredLanguages = langs; - for (auto& [_, context] : mContexts) - context.updateLang(this); - } - - void I18nManager::Context::readLangData(I18nManager* manager, const std::string& lang) - { - std::string path = "i18n/"; - path.append(mName); - path.append("/"); - path.append(lang); - path.append(".lua"); - if (!manager->mVFS->exists(path)) - return; - try - { - sol::protected_function dataFn = manager->mLua->loadFromVFS(path); - sol::environment emptyEnv(manager->mLua->sol(), sol::create); - sol::set_environment(emptyEnv, dataFn); - sol::table data = manager->mLua->newTable(); - data[lang] = call(dataFn); - call(mI18n["load"], data); - mLoadedLangs[lang] = true; - } - catch (std::exception& e) - { - Log(Debug::Error) << "Can not load " << path << ": " << e.what(); - } - } - - sol::object I18nManager::Context::translate(std::string_view key, const sol::object& data) - { - sol::object res = call(mI18n["translate"], key, data); - if (res != sol::nil) - return res; - - // If not found in a language file - register the key itself as a message. - std::string composedKey = call(mI18n["getLocale"]).get(); - composedKey.push_back('.'); - composedKey.append(key); - call(mI18n["set"], composedKey, key); - return call(mI18n["translate"], key, data); - } - - void I18nManager::Context::updateLang(I18nManager* manager) - { - for (const std::string& lang : manager->mPreferredLanguages) - { - if (mLoadedLangs[lang] == sol::nil) - readLangData(manager, lang); - if (mLoadedLangs[lang] != sol::nil) - { - Log(Debug::Verbose) << "Language file \"i18n/" << mName << "/" << lang << ".lua\" is enabled"; - call(mI18n["setLocale"], lang); - return; - } - } - Log(Debug::Warning) << "No language files for the preferred languages found in \"i18n/" << mName << "\""; - } - - sol::object I18nManager::getContext(const std::string& contextName) - { - if (mI18nLoader == sol::nil) - throw std::runtime_error("LuaUtil::I18nManager is not initialized"); - auto it = mContexts.find(contextName); - if (it != mContexts.end()) - return sol::make_object(mLua->sol(), it->second); - Context ctx{contextName, mLua->newTable(), call(mI18nLoader, "i18n.init")}; - ctx.updateLang(this); - mContexts.emplace(contextName, ctx); - return sol::make_object(mLua->sol(), ctx); - } - -} diff --git a/components/lua/i18n.hpp b/components/lua/i18n.hpp deleted file mode 100644 index 4bc7c624f1..0000000000 --- a/components/lua/i18n.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef COMPONENTS_LUA_I18N_H -#define COMPONENTS_LUA_I18N_H - -#include "luastate.hpp" - -namespace LuaUtil -{ - - class I18nManager - { - public: - I18nManager(const VFS::Manager* vfs, LuaState* lua) : mVFS(vfs), mLua(lua) {} - void init(); - - void setPreferredLanguages(const std::vector& langs); - const std::vector& getPreferredLanguages() const { return mPreferredLanguages; } - - sol::object getContext(const std::string& contextName); - - private: - struct Context - { - std::string mName; - sol::table mLoadedLangs; - sol::table mI18n; - - void updateLang(I18nManager* manager); - void readLangData(I18nManager* manager, const std::string& lang); - sol::object translate(std::string_view key, const sol::object& data); - }; - - const VFS::Manager* mVFS; - LuaState* mLua; - sol::object mI18nLoader = sol::nil; - std::vector mPreferredLanguages; - std::map mContexts; - }; - -} - -#endif // COMPONENTS_LUA_I18N_H \ No newline at end of file diff --git a/components/lua/l10n.cpp b/components/lua/l10n.cpp new file mode 100644 index 0000000000..94d46f2522 --- /dev/null +++ b/components/lua/l10n.cpp @@ -0,0 +1,136 @@ +#include "l10n.hpp" + +#include + +#include + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace LuaUtil +{ + void L10nManager::init() + { + sol::usertype ctx = mLua->sol().new_usertype("L10nContext"); + ctx[sol::meta_function::call] = &Context::translate; + } + + void L10nManager::setPreferredLocales(const std::vector& langs) + { + mPreferredLocales.clear(); + for (const auto &lang : langs) + mPreferredLocales.push_back(icu::Locale(lang.c_str())); + { + Log msg(Debug::Info); + msg << "Preferred locales:"; + for (const icu::Locale& l : mPreferredLocales) + msg << " " << l.getName(); + } + for (auto& [_, context] : mContexts) + context.updateLang(this); + } + + void L10nManager::Context::readLangData(L10nManager* manager, const icu::Locale& lang) + { + std::string path = "l10n/"; + path.append(mName); + path.append("/"); + path.append(lang.getName()); + path.append(".yaml"); + if (!manager->mVFS->exists(path)) + return; + + mMessageBundles->load(*manager->mVFS->get(path), lang, path); + } + + std::pair, std::vector> getICUArgs(std::string_view messageId, const sol::table &table) + { + std::vector args; + std::vector argNames; + for (auto elem : table) + for (auto& [key, value] : table) + { + // Argument values + if (value.is()) + args.push_back(icu::Formattable(value.as().c_str())); + // Note: While we pass all numbers as doubles, they still seem to be handled appropriately. + // Numbers can be forced to be integers using the argType number and argStyle integer + // E.g. {var, number, integer} + else if (value.is()) + args.push_back(icu::Formattable(value.as())); + else + { + Log(Debug::Error) << "Unrecognized argument type for key \"" << key.as() + << "\" when formatting message \"" << messageId << "\""; + } + + // Argument names + argNames.push_back(icu::UnicodeString::fromUTF8(key.as())); + } + return std::make_pair(args, argNames); + } + + std::string L10nManager::Context::translate(std::string_view key, const sol::object& data) + { + std::vector args; + std::vector argNames; + + if (data.is()) { + sol::table dataTable = data.as(); + auto argData = getICUArgs(key, dataTable); + args = argData.first; + argNames = argData.second; + } + + return mMessageBundles->formatMessage(key, argNames, args); + } + + void L10nManager::Context::updateLang(L10nManager* manager) + { + icu::Locale fallbackLocale = mMessageBundles->getFallbackLocale(); + mMessageBundles->setPreferredLocales(manager->mPreferredLocales); + int localeCount = 0; + bool fallbackLocaleInPreferred = false; + for (const icu::Locale& loc: mMessageBundles->getPreferredLocales()) + { + if (!mMessageBundles->isLoaded(loc)) + readLangData(manager, loc); + if (mMessageBundles->isLoaded(loc)) + { + localeCount++; + Log(Debug::Verbose) << "Language file \"l10n/" << mName << "/" << loc.getName() << ".yaml\" is enabled"; + if (loc == fallbackLocale) + fallbackLocaleInPreferred = true; + } + } + if (!mMessageBundles->isLoaded(fallbackLocale)) + readLangData(manager, fallbackLocale); + if (mMessageBundles->isLoaded(fallbackLocale) && !fallbackLocaleInPreferred) + Log(Debug::Verbose) << "Fallback language file \"l10n/" << mName << "/" << fallbackLocale.getName() << ".yaml\" is enabled"; + + if (localeCount == 0) + { + Log(Debug::Warning) << "No language files for the preferred languages found in \"l10n/" << mName << "\""; + } + } + + sol::object L10nManager::getContext(const std::string& contextName, const std::string& fallbackLocaleName) + { + auto it = mContexts.find(contextName); + if (it != mContexts.end()) + return sol::make_object(mLua->sol(), it->second); + icu::Locale fallbackLocale(fallbackLocaleName.c_str()); + Context ctx{contextName, std::make_shared(mPreferredLocales, fallbackLocale)}; + { + Log msg(Debug::Verbose); + msg << "Fallback locale: " << fallbackLocale.getName(); + } + ctx.updateLang(this); + mContexts.emplace(contextName, ctx); + return sol::make_object(mLua->sol(), ctx); + } + +} diff --git a/components/lua/l10n.hpp b/components/lua/l10n.hpp new file mode 100644 index 0000000000..04a3ad9c2c --- /dev/null +++ b/components/lua/l10n.hpp @@ -0,0 +1,42 @@ +#ifndef COMPONENTS_LUA_I18N_H +#define COMPONENTS_LUA_I18N_H + +#include "luastate.hpp" + +#include + +namespace LuaUtil +{ + + class L10nManager + { + public: + L10nManager(const VFS::Manager* vfs, LuaState* lua) : mVFS(vfs), mLua(lua) {} + void init(); + + void setPreferredLocales(const std::vector& locales); + const std::vector& getPreferredLocales() const { return mPreferredLocales; } + + sol::object getContext(const std::string& contextName, const std::string& fallbackLocale = "en"); + + private: + struct Context + { + const std::string mName; + // Must be a shared pointer so that sol::make_object copies the pointer, not the data structure. + std::shared_ptr mMessageBundles; + + void updateLang(L10nManager* manager); + void readLangData(L10nManager* manager, const icu::Locale& lang); + std::string translate(std::string_view key, const sol::object& data); + }; + + const VFS::Manager* mVFS; + LuaState* mLua; + std::vector mPreferredLocales; + std::map mContexts; + }; + +} + +#endif // COMPONENTS_LUA_I18N_H diff --git a/docs/source/reference/modding/settings/general.rst b/docs/source/reference/modding/settings/general.rst index ee5b908b4a..ae2448c38b 100644 --- a/docs/source/reference/modding/settings/general.rst +++ b/docs/source/reference/modding/settings/general.rst @@ -68,3 +68,21 @@ notify on saved screenshot :Default: False Show message box when screenshot is saved to a file. + +preferred locales +----------------- + +:Type: string +:Default: en + +List of the preferred locales separated by comma. +For example "de,en" means German as the first prority and English as a fallback. + +Each locale must consist of a two-letter language code (e.g. "de" or "en") and +can also optionally include a two-letter country code (e.g. "en_US", "fr_CA"). +Locales with country codes can match locales without one (e.g. specifying "en_US" +will match "en"), so is recommended that you include the country codes where possible, +since if the country code isn't specified the generic language-code only locale might +refer to any of the country-specific variants. + +This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/lua.rst b/docs/source/reference/modding/settings/lua.rst index 919d530d18..4433067952 100644 --- a/docs/source/reference/modding/settings/lua.rst +++ b/docs/source/reference/modding/settings/lua.rst @@ -26,15 +26,3 @@ If one, a separate thread is used. Values >1 are not yet supported. This setting can only be configured by editing the settings configuration file. - -i18n preferred languages ------------------------- - -:Type: string -:Default: en - -List of the preferred languages separated by comma. -For example "de,en" means German as the first prority and English as a fallback. - -This setting can only be configured by editing the settings configuration file. - diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 5f4712ea3d..1c051bff61 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -226,3 +226,66 @@ if (BUILD_BENCHMARKS AND NOT OPENMW_USE_SYSTEM_BENCHMARK) ) FetchContent_MakeAvailableExcludeFromAll(benchmark) endif() + +if (NOT OPENMW_USE_SYSTEM_YAML_CPP) + if(YAML_CPP_STATIC) + set(YAML_BUILD_SHARED_LIBS OFF) + else() + set(YAML_BUILD_SHARED_LIBS ON) + endif() + + include(FetchContent) + FetchContent_Declare(yaml-cpp + URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip + URL_HASH MD5=1e8ca0d6ccf99f3ed9506c1f6937d0ec + SOURCE_DIR fetched/yaml-cpp + ) + FetchContent_MakeAvailableExcludeFromAll(yaml-cpp) +endif() + +if (NOT OPENMW_USE_SYSTEM_ICU) + if (ANDROID) + # Note: Must be a build directory, not an install root, since the configure script + # looks for a configuration file which does not get installed. + set(OPENMW_ICU_HOST_BUILD_DIR "" CACHE STRING "A pre-built ICU build directory for the host system if cross-compiling") + # We need a host version of ICU so that the tools can be run when building the data library. + set(NDK_STANDARD_ROOT ${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64) + string(REPLACE "android-" "" ANDROIDVER ${ANDROID_PLATFORM}) + set(ICU_ENV + "CC=ccache ${NDK_STANDARD_ROOT}/bin/aarch64-linux-android${ANDROIDVER}-clang" + "CXX=ccache ${NDK_STANDARD_ROOT}/bin/aarch64-linux-android${ANDROIDVER}-clang" + "RANLIB=${NDK_STANDARD_ROOT}/bin/aarch64-linux-android-ranlib" + "AR=${NDK_STANDARD_ROOT}/bin/aarch64-linux-android-ar" + "CPPFLAGS=${ANDROID_COMPILER_FLAGS}" + "LDFLAGS=${ANDROID_LINKER_FLAGS} -lc -lstdc++" + ) + # Wants a triple such as aarch64-linux-android, excluding a trailing + # -clang etc. + string(REGEX MATCH "^[^-]\+-[^-]+-[^-]+" ICU_TOOLCHAIN_NAME ${ANDROID_TOOLCHAIN_NAME}) + set(ICU_ADDITIONAL_OPTS --host=${ICU_TOOLCHAIN_NAME}${ANDROIDVER} --with-cross-build=${OPENMW_ICU_HOST_BUILD_DIR}) + endif() + include(ExternalProject) + ExternalProject_Add(icu + URL https://github.com/unicode-org/icu/archive/refs/tags/release-70-1.zip + URL_HASH MD5=49d5e2e5bab93ae1a4b56e916150544c + SOURCE_DIR fetched/icu + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${ICU_ENV} + /icu4c/source/configure --enable-static --disable-shared + --disable-tests --disable-samples --disable-icuio --disable-extras ${ICU_ADDITIONAL_OPTS} + BUILD_COMMAND make + INSTALL_COMMAND "" + ) + ExternalProject_Get_Property(icu SOURCE_DIR BINARY_DIR) + set(ICU_INCLUDE_DIRS + ${SOURCE_DIR}/icu4c/source/common + ${SOURCE_DIR}/icu4c/source/i18n + PARENT_SCOPE + ) + foreach(ICULIB data uc i18n) + add_library(ICU::${ICULIB} STATIC IMPORTED GLOBAL) + set_target_properties(ICU::${ICULIB} PROPERTIES IMPORTED_LOCATION + ${BINARY_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}icu${ICULIB}${CMAKE_STATIC_LIBRARY_SUFFIX}) + add_dependencies(ICU::${ICULIB} icu) + endforeach() + set(ICU_LIBRARIES ICU::i18n ICU::uc ICU::data PARENT_SCOPE) +endif() diff --git a/extern/i18n.lua/CMakeLists.txt b/extern/i18n.lua/CMakeLists.txt deleted file mode 100644 index 1f7a71a2c2..0000000000 --- a/extern/i18n.lua/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -if (NOT DEFINED OPENMW_RESOURCES_ROOT) - return() -endif() - -# Copy resource files into the build directory -set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) -set(DDIRRELATIVE resources/lua_libs/i18n) - -set(I18N_LUA_FILES - i18n/init.lua - i18n/interpolate.lua - i18n/plural.lua - i18n/variants.lua - i18n/version.lua -) - -copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${I18N_LUA_FILES}") diff --git a/extern/i18n.lua/LICENSE b/extern/i18n.lua/LICENSE deleted file mode 100644 index ddf484685b..0000000000 --- a/extern/i18n.lua/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License Terms -================= - -Copyright (c) 2012 Enrique García Cota. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/extern/i18n.lua/README.md b/extern/i18n.lua/README.md deleted file mode 100644 index 8b3271c321..0000000000 --- a/extern/i18n.lua/README.md +++ /dev/null @@ -1,164 +0,0 @@ -i18n.lua -======== - -[![Build Status](https://travis-ci.org/kikito/i18n.lua.png?branch=master)](https://travis-ci.org/kikito/i18n.lua) - -A very complete i18n lib for Lua - -Description -=========== - -``` lua -i18n = require 'i18n' - --- loading stuff -i18n.set('en.welcome', 'welcome to this program') -i18n.load({ - en = { - good_bye = "good-bye!", - age_msg = "your age is %{age}.", - phone_msg = { - one = "you have one new message.", - other = "you have %{count} new messages." - } - } -}) -i18n.loadFile('path/to/your/project/i18n/de.lua') -- load German language file -i18n.loadFile('path/to/your/project/i18n/fr.lua') -- load French language file -… -- section 'using language files' below describes structure of files - --- setting the translation context -i18n.setLocale('en') -- English is the default locale anyway - --- getting translations -i18n.translate('welcome') -- Welcome to this program -i18n('welcome') -- Welcome to this program -i18n('age_msg', {age = 18}) -- Your age is 18. -i18n('phone_msg', {count = 1}) -- You have one new message. -i18n('phone_msg', {count = 2}) -- You have 2 new messages. -i18n('good_bye') -- Good-bye! - -``` - -Interpolation -============= - -You can interpolate variables in 3 different ways: - -``` lua --- the most usual one -i18n.set('variables', 'Interpolating variables: %{name} %{age}') -i18n('variables', {name='john', 'age'=10}) -- Interpolating variables: john 10 - -i18n.set('lua', 'Traditional Lua way: %d %s') -i18n('lua', {1, 'message'}) -- Traditional Lua way: 1 message - -i18n.set('combined', 'Combined: %.q %.d %.o') -i18n('combined', {name='john', 'age'=10}) -- Combined: john 10 12k -``` - - - -Pluralization -============= - -This lib implements the [unicode.org plural rules](http://cldr.unicode.org/index/cldr-spec/plural-rules). Just set the locale you want to use and it will deduce the appropiate pluralization rules: - -``` lua -i18n = require 'i18n' - -i18n.load({ - en = { - msg = { - one = "one message", - other = "%{count} messages" - } - }, - ru = { - msg = { - one = "1 сообщение", - few = "%{count} сообщения", - many = "%{count} сообщений", - other = "%{count} сообщения" - } - } -}) - -i18n('msg', {count = 1}) -- one message -i18n.setLocale('ru') -i18n('msg', {count = 5}) -- 5 сообщений -``` - -The appropiate rule is chosen by finding the 'root' of the locale used: for example if the current locale is 'fr-CA', the 'fr' rules will be applied. - -If the provided functions are not enough (i.e. invented languages) it's possible to specify a custom pluralization function in the second parameter of setLocale. This function must return 'one', 'few', 'other', etc given a number. - -Fallbacks -========= - -When a value is not found, the lib has several fallback mechanisms: - -* First, it will look in the current locale's parents. For example, if the locale was set to 'en-US' and the key 'msg' was not found there, it will be looked over in 'en'. -* Second, if the value is not found in the locale ancestry, a 'fallback locale' (by default: 'en') can be used. If the fallback locale has any parents, they will be looked over too. -* Third, if all the locales have failed, but there is a param called 'default' on the provided data, it will be used. -* Otherwise the translation will return nil. - -The parents of a locale are found by splitting the locale by its hyphens. Other separation characters (spaces, underscores, etc) are not supported. - -Using language files -==================== - -It might be a good idea to store each translation in a different file. This is supported via the 'i18n.loadFile' directive: - -``` lua -… -i18n.loadFile('path/to/your/project/i18n/de.lua') -- German translation -i18n.loadFile('path/to/your/project/i18n/en.lua') -- English translation -i18n.loadFile('path/to/your/project/i18n/fr.lua') -- French translation -… -``` - -The German language file 'de.lua' should read: - -``` lua -return { - de = { - good_bye = "Auf Wiedersehen!", - age_msg = "Ihr Alter beträgt %{age}.", - phone_msg = { - one = "Sie haben eine neue Nachricht.", - other = "Sie haben %{count} neue Nachrichten." - } - } -} -``` - -If desired, you can also store all translations in one single file (eg. 'translations.lua'): - -``` lua -return { - de = { - good_bye = "Auf Wiedersehen!", - age_msg = "Ihr Alter beträgt %{age}.", - phone_msg = { - one = "Sie haben eine neue Nachricht.", - other = "Sie haben %{count} neue Nachrichten." - } - }, - fr = { - good_bye = "Au revoir !", - age_msg = "Vous avez %{age} ans.", - phone_msg = { - one = "Vous avez une noveau message.", - other = "Vous avez %{count} noveaux messages." - } - }, - … -} -``` - -Specs -===== -This project uses [busted](https://github.com/Olivine-Labs/busted) for its specs. If you want to run the specs, you will have to install it first. Then just execute the following from the root inspect folder: - - busted diff --git a/extern/i18n.lua/i18n/init.lua b/extern/i18n.lua/i18n/init.lua deleted file mode 100644 index 6bcccd0572..0000000000 --- a/extern/i18n.lua/i18n/init.lua +++ /dev/null @@ -1,188 +0,0 @@ -local i18n = {} - -local store -local locale -local pluralizeFunction -local defaultLocale = 'en' -local fallbackLocale = defaultLocale - -local currentFilePath = (...):gsub("%.init$","") - -local plural = require(currentFilePath .. '.plural') -local interpolate = require(currentFilePath .. '.interpolate') -local variants = require(currentFilePath .. '.variants') -local version = require(currentFilePath .. '.version') - -i18n.plural, i18n.interpolate, i18n.variants, i18n.version, i18n._VERSION = - plural, interpolate, variants, version, version - --- private stuff - -local function dotSplit(str) - local fields, length = {},0 - str:gsub("[^%.]+", function(c) - length = length + 1 - fields[length] = c - end) - return fields, length -end - -local function isPluralTable(t) - return type(t) == 'table' and type(t.other) == 'string' -end - -local function isPresent(str) - return type(str) == 'string' and #str > 0 -end - -local function assertPresent(functionName, paramName, value) - if isPresent(value) then return end - - local msg = "i18n.%s requires a non-empty string on its %s. Got %s (a %s value)." - error(msg:format(functionName, paramName, tostring(value), type(value))) -end - -local function assertPresentOrPlural(functionName, paramName, value) - if isPresent(value) or isPluralTable(value) then return end - - local msg = "i18n.%s requires a non-empty string or plural-form table on its %s. Got %s (a %s value)." - error(msg:format(functionName, paramName, tostring(value), type(value))) -end - -local function assertPresentOrTable(functionName, paramName, value) - if isPresent(value) or type(value) == 'table' then return end - - local msg = "i18n.%s requires a non-empty string or table on its %s. Got %s (a %s value)." - error(msg:format(functionName, paramName, tostring(value), type(value))) -end - -local function assertFunctionOrNil(functionName, paramName, value) - if value == nil or type(value) == 'function' then return end - - local msg = "i18n.%s requires a function (or nil) on param %s. Got %s (a %s value)." - error(msg:format(functionName, paramName, tostring(value), type(value))) -end - -local function defaultPluralizeFunction(count) - return plural.get(variants.root(i18n.getLocale()), count) -end - -local function pluralize(t, data) - assertPresentOrPlural('interpolatePluralTable', 't', t) - data = data or {} - local count = data.count or 1 - local plural_form = pluralizeFunction(count) - return t[plural_form] -end - -local function treatNode(node, data) - if type(node) == 'string' then - return interpolate(node, data) - elseif isPluralTable(node) then - return interpolate(pluralize(node, data), data) - end - return node -end - -local function recursiveLoad(currentContext, data) - local composedKey - for k,v in pairs(data) do - composedKey = (currentContext and (currentContext .. '.') or "") .. tostring(k) - assertPresent('load', composedKey, k) - assertPresentOrTable('load', composedKey, v) - if type(v) == 'string' then - i18n.set(composedKey, v) - else - recursiveLoad(composedKey, v) - end - end -end - -local function localizedTranslate(key, loc, data) - local path, length = dotSplit(loc .. "." .. key) - local node = store - - for i=1, length do - node = node[path[i]] - if not node then return nil end - end - - return treatNode(node, data) -end - --- public interface - -function i18n.set(key, value) - assertPresent('set', 'key', key) - assertPresentOrPlural('set', 'value', value) - - local path, length = dotSplit(key) - local node = store - - for i=1, length-1 do - key = path[i] - node[key] = node[key] or {} - node = node[key] - end - - local lastKey = path[length] - node[lastKey] = value -end - -function i18n.translate(key, data) - assertPresent('translate', 'key', key) - - data = data or {} - local usedLocale = data.locale or locale - - local fallbacks = variants.fallbacks(usedLocale, fallbackLocale) - for i=1, #fallbacks do - local value = localizedTranslate(key, fallbacks[i], data) - if value then return value end - end - - return data.default -end - -function i18n.setLocale(newLocale, newPluralizeFunction) - assertPresent('setLocale', 'newLocale', newLocale) - assertFunctionOrNil('setLocale', 'newPluralizeFunction', newPluralizeFunction) - locale = newLocale - pluralizeFunction = newPluralizeFunction or defaultPluralizeFunction -end - -function i18n.setFallbackLocale(newFallbackLocale) - assertPresent('setFallbackLocale', 'newFallbackLocale', newFallbackLocale) - fallbackLocale = newFallbackLocale -end - -function i18n.getFallbackLocale() - return fallbackLocale -end - -function i18n.getLocale() - return locale -end - -function i18n.reset() - store = {} - plural.reset() - i18n.setLocale(defaultLocale) - i18n.setFallbackLocale(defaultLocale) -end - -function i18n.load(data) - recursiveLoad(nil, data) -end - -function i18n.loadFile(path) - local chunk = assert(loadfile(path)) - local data = chunk() - i18n.load(data) -end - -setmetatable(i18n, {__call = function(_, ...) return i18n.translate(...) end}) - -i18n.reset() - -return i18n diff --git a/extern/i18n.lua/i18n/interpolate.lua b/extern/i18n.lua/i18n/interpolate.lua deleted file mode 100644 index c6bb242f05..0000000000 --- a/extern/i18n.lua/i18n/interpolate.lua +++ /dev/null @@ -1,60 +0,0 @@ -local unpack = unpack or table.unpack -- lua 5.2 compat - -local FORMAT_CHARS = { c=1, d=1, E=1, e=1, f=1, g=1, G=1, i=1, o=1, u=1, X=1, x=1, s=1, q=1, ['%']=1 } - --- matches a string of type %{age} -local function interpolateValue(string, variables) - return string:gsub("(.?)%%{%s*(.-)%s*}", - function (previous, key) - if previous == "%" then - return - else - return previous .. tostring(variables[key]) - end - end) -end - --- matches a string of type %.d -local function interpolateField(string, variables) - return string:gsub("(.?)%%<%s*(.-)%s*>%.([cdEefgGiouXxsq])", - function (previous, key, format) - if previous == "%" then - return - else - return previous .. string.format("%" .. format, variables[key] or "nil") - end - end) -end - -local function escapePercentages(string) - return string:gsub("(%%)(.?)", function(_, char) - if FORMAT_CHARS[char] then - return "%" .. char - else - return "%%" .. char - end - end) -end - -local function unescapePercentages(string) - return string:gsub("(%%%%)(.?)", function(_, char) - if FORMAT_CHARS[char] then - return "%" .. char - else - return "%%" .. char - end - end) -end - -local function interpolate(pattern, variables) - variables = variables or {} - local result = pattern - result = interpolateValue(result, variables) - result = interpolateField(result, variables) - result = escapePercentages(result) - result = string.format(result, unpack(variables)) - result = unescapePercentages(result) - return result -end - -return interpolate diff --git a/extern/i18n.lua/i18n/plural.lua b/extern/i18n.lua/i18n/plural.lua deleted file mode 100644 index bb99804ee8..0000000000 --- a/extern/i18n.lua/i18n/plural.lua +++ /dev/null @@ -1,280 +0,0 @@ -local plural = {} -local defaultFunction = nil --- helper functions - -local function assertPresentString(functionName, paramName, value) - if type(value) ~= 'string' or #value == 0 then - local msg = "Expected param %s of function %s to be a string, but got %s (a value of type %s) instead" - error(msg:format(paramName, functionName, tostring(value), type(value))) - end -end - -local function assertNumber(functionName, paramName, value) - if type(value) ~= 'number' then - local msg = "Expected param %s of function %s to be a number, but got %s (a value of type %s) instead" - error(msg:format(paramName, functionName, tostring(value), type(value))) - end -end - --- transforms "foo bar baz" into {'foo','bar','baz'} -local function words(str) - local result, length = {}, 0 - str:gsub("%S+", function(word) - length = length + 1 - result[length] = word - end) - return result -end - -local function isInteger(n) - return n == math.floor(n) -end - -local function between(value, min, max) - return value >= min and value <= max -end - -local function inside(v, list) - for i=1, #list do - if v == list[i] then return true end - end - return false -end - - --- pluralization functions - -local pluralization = {} - -local f1 = function(n) - return n == 1 and "one" or "other" -end -pluralization[f1] = words([[ - af asa bem bez bg bn brx ca cgg chr da de dv ee el - en eo es et eu fi fo fur fy gl gsw gu ha haw he is - it jmc kaj kcg kk kl ksb ku lb lg mas ml mn mr nah - nb nd ne nl nn no nr ny nyn om or pa pap ps pt rm - rof rwk saq seh sn so sq ss ssy st sv sw syr ta te - teo tig tk tn ts ur ve vun wae xh xog zu -]]) - -local f2 = function(n) - return (n == 0 or n == 1) and "one" or "other" -end -pluralization[f2] = words("ak am bh fil guw hi ln mg nso ti tl wa") - -local f3 = function(n) - if not isInteger(n) then return 'other' end - return (n == 0 and "zero") or - (n == 1 and "one") or - (n == 2 and "two") or - (between(n % 100, 3, 10) and "few") or - (between(n % 100, 11, 99) and "many") or - "other" -end -pluralization[f3] = {'ar'} - -local f4 = function() - return "other" -end -pluralization[f4] = words([[ - az bm bo dz fa hu id ig ii ja jv ka kde kea km kn - ko lo ms my root sah ses sg th to tr vi wo yo zh -]]) - -local f5 = function(n) - if not isInteger(n) then return 'other' end - local n_10, n_100 = n % 10, n % 100 - return (n_10 == 1 and n_100 ~= 11 and 'one') or - (between(n_10, 2, 4) and not between(n_100, 12, 14) and 'few') or - ((n_10 == 0 or between(n_10, 5, 9) or between(n_100, 11, 14)) and 'many') or - 'other' -end -pluralization[f5] = words('be bs hr ru sh sr uk') - -local f6 = function(n) - if not isInteger(n) then return 'other' end - local n_10, n_100 = n % 10, n % 100 - return (n_10 == 1 and not inside(n_100, {11,71,91}) and 'one') or - (n_10 == 2 and not inside(n_100, {12,72,92}) and 'two') or - (inside(n_10, {3,4,9}) and - not between(n_100, 10, 19) and - not between(n_100, 70, 79) and - not between(n_100, 90, 99) - and 'few') or - (n ~= 0 and n % 1000000 == 0 and 'many') or - 'other' -end -pluralization[f6] = {'br'} - -local f7 = function(n) - return (n == 1 and 'one') or - ((n == 2 or n == 3 or n == 4) and 'few') or - 'other' -end -pluralization[f7] = {'cz', 'sk'} - -local f8 = function(n) - return (n == 0 and 'zero') or - (n == 1 and 'one') or - (n == 2 and 'two') or - (n == 3 and 'few') or - (n == 6 and 'many') or - 'other' -end -pluralization[f8] = {'cy'} - -local f9 = function(n) - return (n >= 0 and n < 2 and 'one') or - 'other' -end -pluralization[f9] = {'ff', 'fr', 'kab'} - -local f10 = function(n) - return (n == 1 and 'one') or - (n == 2 and 'two') or - ((n == 3 or n == 4 or n == 5 or n == 6) and 'few') or - ((n == 7 or n == 8 or n == 9 or n == 10) and 'many') or - 'other' -end -pluralization[f10] = {'ga'} - -local f11 = function(n) - return ((n == 1 or n == 11) and 'one') or - ((n == 2 or n == 12) and 'two') or - (isInteger(n) and (between(n, 3, 10) or between(n, 13, 19)) and 'few') or - 'other' -end -pluralization[f11] = {'gd'} - -local f12 = function(n) - local n_10 = n % 10 - return ((n_10 == 1 or n_10 == 2 or n % 20 == 0) and 'one') or - 'other' -end -pluralization[f12] = {'gv'} - -local f13 = function(n) - return (n == 1 and 'one') or - (n == 2 and 'two') or - 'other' -end -pluralization[f13] = words('iu kw naq se sma smi smj smn sms') - -local f14 = function(n) - return (n == 0 and 'zero') or - (n == 1 and 'one') or - 'other' -end -pluralization[f14] = {'ksh'} - -local f15 = function(n) - return (n == 0 and 'zero') or - (n > 0 and n < 2 and 'one') or - 'other' -end -pluralization[f15] = {'lag'} - -local f16 = function(n) - if not isInteger(n) then return 'other' end - if between(n % 100, 11, 19) then return 'other' end - local n_10 = n % 10 - return (n_10 == 1 and 'one') or - (between(n_10, 2, 9) and 'few') or - 'other' -end -pluralization[f16] = {'lt'} - -local f17 = function(n) - return (n == 0 and 'zero') or - ((n % 10 == 1 and n % 100 ~= 11) and 'one') or - 'other' -end -pluralization[f17] = {'lv'} - -local f18 = function(n) - return((n % 10 == 1 and n ~= 11) and 'one') or - 'other' -end -pluralization[f18] = {'mk'} - -local f19 = function(n) - return (n == 1 and 'one') or - ((n == 0 or - (n ~= 1 and isInteger(n) and between(n % 100, 1, 19))) - and 'few') or - 'other' -end -pluralization[f19] = {'mo', 'ro'} - -local f20 = function(n) - if n == 1 then return 'one' end - if not isInteger(n) then return 'other' end - local n_100 = n % 100 - return ((n == 0 or between(n_100, 2, 10)) and 'few') or - (between(n_100, 11, 19) and 'many') or - 'other' -end -pluralization[f20] = {'mt'} - -local f21 = function(n) - if n == 1 then return 'one' end - if not isInteger(n) then return 'other' end - local n_10, n_100 = n % 10, n % 100 - - return ((between(n_10, 2, 4) and not between(n_100, 12, 14)) and 'few') or - ((n_10 == 0 or n_10 == 1 or between(n_10, 5, 9) or between(n_100, 12, 14)) and 'many') or - 'other' -end -pluralization[f21] = {'pl'} - -local f22 = function(n) - return (n == 0 or n == 1) and 'one' or - 'other' -end -pluralization[f22] = {'shi'} - -local f23 = function(n) - local n_100 = n % 100 - return (n_100 == 1 and 'one') or - (n_100 == 2 and 'two') or - ((n_100 == 3 or n_100 == 4) and 'few') or - 'other' -end -pluralization[f23] = {'sl'} - -local f24 = function(n) - return (isInteger(n) and (n == 0 or n == 1 or between(n, 11, 99)) and 'one') - or 'other' -end -pluralization[f24] = {'tzm'} - -local pluralizationFunctions = {} -for f,locales in pairs(pluralization) do - for _,locale in ipairs(locales) do - pluralizationFunctions[locale] = f - end -end - --- public interface - -function plural.get(locale, n) - assertPresentString('i18n.plural.get', 'locale', locale) - assertNumber('i18n.plural.get', 'n', n) - - local f = pluralizationFunctions[locale] or defaultFunction - - return f(math.abs(n)) -end - -function plural.setDefaultFunction(f) - defaultFunction = f -end - -function plural.reset() - defaultFunction = pluralizationFunctions['en'] -end - -plural.reset() - -return plural diff --git a/extern/i18n.lua/i18n/variants.lua b/extern/i18n.lua/i18n/variants.lua deleted file mode 100644 index 0cfad42f6c..0000000000 --- a/extern/i18n.lua/i18n/variants.lua +++ /dev/null @@ -1,49 +0,0 @@ -local variants = {} - -local function reverse(arr, length) - local result = {} - for i=1, length do result[i] = arr[length-i+1] end - return result, length -end - -local function concat(arr1, len1, arr2, len2) - for i = 1, len2 do - arr1[len1 + i] = arr2[i] - end - return arr1, len1 + len2 -end - -function variants.ancestry(locale) - local result, length, accum = {},0,nil - locale:gsub("[^%-]+", function(c) - length = length + 1 - accum = accum and (accum .. '-' .. c) or c - result[length] = accum - end) - return reverse(result, length) -end - -function variants.isParent(parent, child) - return not not child:match("^".. parent .. "%-") -end - -function variants.root(locale) - return locale:match("[^%-]+") -end - -function variants.fallbacks(locale, fallbackLocale) - if locale == fallbackLocale or - variants.isParent(fallbackLocale, locale) then - return variants.ancestry(locale) - end - if variants.isParent(locale, fallbackLocale) then - return variants.ancestry(fallbackLocale) - end - - local ancestry1, length1 = variants.ancestry(locale) - local ancestry2, length2 = variants.ancestry(fallbackLocale) - - return concat(ancestry1, length1, ancestry2, length2) -end - -return variants diff --git a/extern/i18n.lua/i18n/version.lua b/extern/i18n.lua/i18n/version.lua deleted file mode 100644 index eb788884ac..0000000000 --- a/extern/i18n.lua/i18n/version.lua +++ /dev/null @@ -1 +0,0 @@ -return '0.9.2' diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index 607ddeca49..cea33f0f40 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -3,4 +3,3 @@ add_subdirectory(shaders) add_subdirectory(vfs) add_subdirectory(builtin_scripts) add_subdirectory(lua_api) -add_subdirectory(../extern/i18n.lua ${CMAKE_CURRENT_BINARY_DIR}/files) diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 4e1c08ee8d..7afcd7f529 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -14,7 +14,7 @@ set(LUA_BUILTIN_FILES scripts/omw/head_bobbing.lua scripts/omw/third_person.lua - i18n/Calendar/en.lua + l10n/Calendar/en.yaml ) foreach (f ${LUA_BUILTIN_FILES}) diff --git a/files/builtin_scripts/i18n/Calendar/en.lua b/files/builtin_scripts/i18n/Calendar/en.lua deleted file mode 100644 index a4e8183a92..0000000000 --- a/files/builtin_scripts/i18n/Calendar/en.lua +++ /dev/null @@ -1,42 +0,0 @@ --- source: https://en.uesp.net/wiki/Lore:Calendar - -return { - month1 = "Morning Star", - month2 = "Sun's Dawn", - month3 = "First Seed", - month4 = "Rain's Hand", - month5 = "Second Seed", - month6 = "Midyear", - month7 = "Sun's Height", - month8 = "Last Seed", - month9 = "Hearthfire", - month10 = "Frostfall", - month11 = "Sun's Dusk", - month12 = "Evening Star", - - -- The variant of month names in the context "day X of month Y". - -- In English it is the same, but some languages require a different form. - monthInGenitive1 = "Morning Star", - monthInGenitive2 = "Sun's Dawn", - monthInGenitive3 = "First Seed", - monthInGenitive4 = "Rain's Hand", - monthInGenitive5 = "Second Seed", - monthInGenitive6 = "Midyear", - monthInGenitive7 = "Sun's Height", - monthInGenitive8 = "Last Seed", - monthInGenitive9 = "Hearthfire", - monthInGenitive10 = "Frostfall", - monthInGenitive11 = "Sun's Dusk", - monthInGenitive12 = "Evening Star", - - dateFormat = "day %{day} of %{monthInGenitive} %{year}", - - weekday1 = "Sundas", - weekday2 = "Morndas", - weekday3 = "Tirdas", - weekday4 = "Middas", - weekday5 = "Turdas", - weekday6 = "Fredas", - weekday7 = "Loredas", -} - diff --git a/files/builtin_scripts/l10n/Calendar/en.yaml b/files/builtin_scripts/l10n/Calendar/en.yaml new file mode 100644 index 0000000000..0b009f4c57 --- /dev/null +++ b/files/builtin_scripts/l10n/Calendar/en.yaml @@ -0,0 +1,39 @@ +# source: https://en.uesp.net/wiki/Lore:Calendar + +month1: "Morning Star" +month2: "Sun's Dawn" +month3: "First Seed" +month4: "Rain's Hand" +month5: "Second Seed" +month6: "Midyear" +month7: "Sun's Height" +month8: "Last Seed" +month9: "Hearthfire" +month10: "Frostfall" +month11: "Sun's Dusk" +month12: "Evening Star" + +# The variant of month names in the context "day X of month Y". +# In English it is the same, but some languages require a different form. +monthInGenitive1: "Morning Star" +monthInGenitive2: "Sun's Dawn" +monthInGenitive3: "First Seed" +monthInGenitive4: "Rain's Hand" +monthInGenitive5: "Second Seed" +monthInGenitive6: "Midyear" +monthInGenitive7: "Sun's Height" +monthInGenitive8: "Last Seed" +monthInGenitive9: "Hearthfire" +monthInGenitive10: "Frostfall" +monthInGenitive11: "Sun's Dusk" +monthInGenitive12: "Evening Star" + +dateFormat: "day {day} of {monthInGenitive} {year}" + +weekday1: "Sundas" +weekday2: "Morndas" +weekday3: "Tirdas" +weekday4: "Middas" +weekday5: "Turdas" +weekday6: "Fredas" +weekday7: "Loredas" diff --git a/files/builtin_scripts/openmw_aux/calendar.lua b/files/builtin_scripts/openmw_aux/calendar.lua index 181b133b83..85b9db8e49 100644 --- a/files/builtin_scripts/openmw_aux/calendar.lua +++ b/files/builtin_scripts/openmw_aux/calendar.lua @@ -6,7 +6,7 @@ local core = require('openmw.core') local time = require('openmw_aux.time') -local i18n = core.i18n('Calendar') +local l10n = core.l10n('Calendar') local monthsDuration = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} local daysInWeek = 7 @@ -31,10 +31,10 @@ local function gameTime(t) end local function defaultDateFormat(t) - return i18n('dateFormat', { + return l10n('dateFormat', { day = t.day, - month = i18n('month' .. t.month), - monthInGenitive = i18n('monthInGenitive' .. t.month), + month = l10n('month' .. t.month), + monthInGenitive = l10n('monthInGenitive' .. t.month), year = t.year, }) end @@ -63,8 +63,8 @@ local function formatGameTime(formatStr, timestamp) if formatStr == '*t' then return t end local replFn = function(tag) - if tag == '%a' or tag == '%A' then return i18n('weekday' .. t.wday) end - if tag == '%b' or tag == '%B' then return i18n('monthInGenitive' .. t.month) end + if tag == '%a' or tag == '%A' then return l10n('weekday' .. t.wday) end + if tag == '%b' or tag == '%B' then return l10n('monthInGenitive' .. t.month) end if tag == '%c' then return string.format('%02d:%02d %s', t.hour, t.min, defaultDateFormat(t)) end @@ -137,7 +137,7 @@ return { -- @param monthIndex -- @return #string monthName = function(m) - return i18n('month' .. ((m-1) % #monthsDuration + 1)) + return l10n('month' .. ((m-1) % #monthsDuration + 1)) end, --- The name of a month in genitive (for English is the same as `monthName`, but in some languages the form can differ). @@ -145,7 +145,7 @@ return { -- @param monthIndex -- @return #string monthNameInGenitive = function(m) - return i18n('monthInGenitive' .. ((m-1) % #monthsDuration + 1)) + return l10n('monthInGenitive' .. ((m-1) % #monthsDuration + 1)) end, --- The name of a weekday @@ -153,7 +153,7 @@ return { -- @param dayIndex -- @return #string weekdayName = function(d) - return i18n('weekday' .. ((d-1) % daysInWeek + 1)) + return l10n('weekday' .. ((d-1) % daysInWeek + 1)) end, } diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 43b2d29a70..b1a10b9cfb 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -53,37 +53,71 @@ -- @return #any --- --- Return i18n formatting function for the given context. --- It is based on `i18n.lua` library. --- Language files should be stored in VFS as `i18n//.lua`. --- See https://github.com/kikito/i18n.lua for format details. --- @function [parent=#core] i18n --- @param #string context I18n context; recommended to use the name of the mod. +-- Return l10n formatting function for the given context. +-- Language files should be stored in VFS as `l10n//.yaml`. +-- +-- Locales usually have the form {lang}_{COUNTRY}, +-- where {lang} is a lowercase two-letter language code and {COUNTRY} is an uppercase +-- two-letter country code. Capitalization and the separator must have exactly +-- this format for language files to be recognized, but when users request a +-- locale they do not need to match capitalization and can use hyphens instead of +-- underscores. +-- +-- Locales may also contain variants and keywords. See https://unicode-org.github.io/icu/userguide/locale/#language-code +-- for full details. +-- +-- Messages have the form of ICU MessageFormat strings. +-- See https://unicode-org.github.io/icu/userguide/format_parse/messages/ +-- for a guide to MessageFormat, and see +-- https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1MessageFormat.html +-- for full details of the MessageFormat syntax. +-- @function [parent=#core] l10n +-- @param #string context l10n context; recommended to use the name of the mod. +-- @param #string fallbackLocale The source locale containing the default messages +-- If omitted defaults to "en" -- @return #function -- @usage --- -- DataFiles/i18n/MyMod/en.lua --- return { --- good_morning = 'Good morning.', --- you_have_arrows = { --- one = 'You have one arrow.', --- other = 'You have %{count} arrows.', --- }, --- } +-- # DataFiles/l10n/MyMod/en.yaml +-- good_morning: 'Good morning.' +-- +-- you_have_arrows: |- {count, plural, +-- one {You have one arrow.} +-- other {You have {count} arrows.} +-- } -- @usage --- -- DataFiles/i18n/MyMod/de.lua --- return { --- good_morning = "Guten Morgen.", --- you_have_arrows = { --- one = "Du hast ein Pfeil.", --- other = "Du hast %{count} Pfeile.", --- }, --- ["Hello %{name}!"] = "Hallo %{name}!", --- } +-- # DataFiles/l10n/MyMod/de.yaml +-- good_morning: "Guten Morgen." +-- you_have_arrows: |- {count, plural, +-- one {Du hast ein Pfeil.} +-- other {Du hast {count} Pfeile.} +-- } +-- "Hello {name}!": "Hallo {name}!" -- @usage --- local myMsg = core.i18n('MyMod') +-- # DataFiles/l10n/AdvancedExample/en.yaml +-- # More complicated patterns +-- # select rules can be used to match arbitrary string arguments +-- # The default keyword other must always be provided +-- pc_must_come: {PCGender, select, +-- male {He is} +-- female {She is} +-- other {They are} +-- } coming with us. +-- # Numbers have various formatting options +-- quest_completion: "The quest is {done, number, percent} complete.", +-- # E.g. "You came in 4th place" +-- ordinal: "You came in {num, ordinal} place." +-- # E.g. "There is one thing", "There are one hundred things" +-- spellout: "There {num, plural, one{is {num, spellout} thing} other{are {num, spellout} things}}." +-- numbers: "{int} and {double, number, integer} are integers, but {double} is a double" +-- # Numbers can be formatted with custom patterns +-- # See https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html#syntax +-- rounding: "{value, number, :: .00}" +-- @usage +-- -- Usage in Lua +-- local myMsg = core.l10n('MyMod', 'en') -- print( myMsg('good_morning') ) -- print( myMsg('you_have_arrows', {count=5}) ) --- print( myMsg('Hello %{name}!', {name='World'}) ) +-- print( myMsg('Hello {name}!', {name='World'}) ) --- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ecf5e1ec40..50214668d1 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -396,6 +396,10 @@ texture mipmap = nearest # Show message box when screenshot is saved to a file. notify on saved screenshot = false +# List of the preferred languages separated by comma. +# For example "de,en" means German as the first prority and English as a fallback. +preferred locales = en + [Shaders] # Force rendering with shaders. By default, only bump-mapped objects will use shaders. @@ -1125,8 +1129,3 @@ lua debug = false # Set the maximum number of threads used for Lua scripts. # If zero, Lua scripts are processed in the main thread. lua num threads = 1 - -# List of the preferred languages separated by comma. -# For example "de,en" means German as the first prority and English as a fallback. -i18n preferred languages = en - From 8bd16179a6709d3b770c8673d090e346266094d6 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Sun, 10 Apr 2022 12:39:03 -0400 Subject: [PATCH 2281/2859] Allow CMake to find the ICU version installed through homebrew --- CI/before_install.osx.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index b5bb35957f..6871787e22 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -13,9 +13,10 @@ brew update --quiet command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake command -v qmake >/dev/null 2>&1 || brew install qt@5 -command -v pkgdata >/dev/null 2>&1 || brew install icu4c +brew install icu4c brew install yaml-cpp export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6 +export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig" ccache --version cmake --version From 6b464a9330270d8df86d35271c11e8aba7abdc6f Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 10 Apr 2022 13:35:00 +0200 Subject: [PATCH 2282/2859] Check ESMReader value size in compile time --- apps/essimporter/converter.cpp | 2 +- apps/essimporter/importcellref.cpp | 4 ++-- apps/essimporter/importplayer.cpp | 2 +- components/esm3/cellid.cpp | 2 +- components/esm3/cellref.cpp | 4 ++-- components/esm3/effectlist.cpp | 2 +- components/esm3/esmreader.hpp | 25 +++++++++++-------------- components/esm3/loadalch.cpp | 2 +- components/esm3/loadarmo.cpp | 2 +- components/esm3/loadbody.cpp | 2 +- components/esm3/loadbook.cpp | 2 +- components/esm3/loadcell.cpp | 2 +- components/esm3/loadclas.cpp | 2 +- components/esm3/loadclot.cpp | 2 +- components/esm3/loadcont.cpp | 4 ++-- components/esm3/loadcrea.cpp | 2 +- components/esm3/loadench.cpp | 2 +- components/esm3/loadfact.cpp | 2 +- components/esm3/loadinfo.cpp | 2 +- components/esm3/loadingr.cpp | 2 +- components/esm3/loadligh.cpp | 2 +- components/esm3/loadlock.cpp | 2 +- components/esm3/loadmgef.cpp | 2 +- components/esm3/loadmisc.cpp | 2 +- components/esm3/loadpgrd.cpp | 2 +- components/esm3/loadprob.cpp | 2 +- components/esm3/loadrace.cpp | 2 +- components/esm3/loadrepa.cpp | 2 +- components/esm3/loadskil.cpp | 2 +- components/esm3/loadsndg.cpp | 2 +- components/esm3/loadsoun.cpp | 2 +- components/esm3/loadspel.cpp | 4 ++-- components/esm3/loadweap.cpp | 2 +- components/esm3/player.cpp | 4 ++-- components/esm3/savedgame.cpp | 2 +- 35 files changed, 50 insertions(+), 53 deletions(-) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index ad28a9295f..0d5b391112 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -278,7 +278,7 @@ namespace ESSImport while (esm.isNextSub("MPCD")) { float notepos[3]; - esm.getHT(notepos, 3*sizeof(float)); + esm.getHTSized<3 * sizeof(float)>(notepos); // Markers seem to be arranged in a 32*32 grid, notepos has grid-indices. // This seems to be the reason markers can't be placed everywhere in interior cells, diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index e6c62cc812..22af1af5fa 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -35,8 +35,8 @@ namespace ESSImport // DATA should occur for all references, except levelled creature spawners // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess - esm.getHNOT(mPos, "DATA", 24); - esm.getHNOT(mPos, "DATA", 24); + esm.getHNOTSized<24>(mPos, "DATA"); + esm.getHNOTSized<24>(mPos, "DATA"); mDeleted = 0; if (esm.isNextSub("DELE")) diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index 52e4a9b7d0..f2ec57ab3f 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -13,7 +13,7 @@ namespace ESSImport mActorData.load(esm); - esm.getHNOT(mPos, "DATA", 24); + esm.getHNOTSized<24>(mPos, "DATA"); } void PCDT::load(ESM::ESMReader &esm) diff --git a/components/esm3/cellid.cpp b/components/esm3/cellid.cpp index ad91d30e04..87ad0cc21c 100644 --- a/components/esm3/cellid.cpp +++ b/components/esm3/cellid.cpp @@ -11,7 +11,7 @@ void ESM::CellId::load (ESMReader &esm) if (esm.isNextSub ("CIDX")) { - esm.getHT (mIndex, 8); + esm.getHTSized<8>(mIndex); mPaged = true; } else diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 2efa3b845d..9b46bf182a 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -114,7 +114,7 @@ namespace ESM break; case ESM::fourCC("DATA"): if constexpr (load) - esm.getHT(cellRef.mPos, 24); + esm.getHTSized<24>(cellRef.mPos); else esm.skip(24); break; @@ -150,7 +150,7 @@ namespace ESM void ESM::RefNum::load(ESMReader& esm, bool wide, ESM::NAME tag) { if (wide) - esm.getHNT(*this, tag, 8); + esm.getHNTSized<8>(*this, tag); else esm.getHNT(mIndex, tag); } diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index f6d5a6e071..1cfb25618d 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -16,7 +16,7 @@ void EffectList::load(ESMReader &esm) void EffectList::add(ESMReader &esm) { ENAMstruct s; - esm.getHT(s, 24); + esm.getHTSized<24>(s); mList.push_back(s); } diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 464925d936..e931cde42a 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -2,7 +2,6 @@ #define OPENMW_ESM_READER_H #include -#include #include #include @@ -114,20 +113,18 @@ public: // Version with extra size checking, to make sure the compiler // doesn't mess up our struct padding. - template - void getHNT(X &x, NAME name, int size) + template + void getHNTSized(X &x, NAME name) { - assert(sizeof(X) == size); - getSubNameIs(name); - getHT(x); + static_assert(sizeof(X) == size); + getHNT(x, name); } - template - void getHNOT(X &x, NAME name, int size) + template + void getHNOTSized(X &x, NAME name) { - assert(sizeof(X) == size); - if(isNextSub(name)) - getHT(x); + static_assert(sizeof(X) == size); + getHNOT(x, name); } // Get data of a given type/size, including subrecord header @@ -151,10 +148,10 @@ public: // Version with extra size checking, to make sure the compiler // doesn't mess up our struct padding. - template - void getHT(X &x, int size) + template + void getHTSized(X &x) { - assert(sizeof(X) == size); + static_assert(sizeof(X) == size); getHT(x); } diff --git a/components/esm3/loadalch.cpp b/components/esm3/loadalch.cpp index 1aacfc8976..7173090e4b 100644 --- a/components/esm3/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -39,7 +39,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("ALDT"): - esm.getHT(mData, 12); + esm.getHTSized<12>(mData); hasData = true; break; case ESM::fourCC("ENAM"): diff --git a/components/esm3/loadarmo.cpp b/components/esm3/loadarmo.cpp index b476e930a8..7176f82f1e 100644 --- a/components/esm3/loadarmo.cpp +++ b/components/esm3/loadarmo.cpp @@ -63,7 +63,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("AODT"): - esm.getHT(mData, 24); + esm.getHTSized<24>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadbody.cpp b/components/esm3/loadbody.cpp index 148e80d94c..55aa5b7bf5 100644 --- a/components/esm3/loadbody.cpp +++ b/components/esm3/loadbody.cpp @@ -31,7 +31,7 @@ namespace ESM mRace = esm.getHString(); break; case ESM::fourCC("BYDT"): - esm.getHT(mData, 4); + esm.getHTSized<4>(mData); hasData = true; break; case ESM::SREC_DELE: diff --git a/components/esm3/loadbook.cpp b/components/esm3/loadbook.cpp index 72618f3ce8..7e5f75e055 100644 --- a/components/esm3/loadbook.cpp +++ b/components/esm3/loadbook.cpp @@ -31,7 +31,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("BKDT"): - esm.getHT(mData, 20); + esm.getHTSized<20>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 1a4fe1db8f..b36f066501 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -76,7 +76,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("DATA"): - esm.getHT(mData, 12); + esm.getHTSized<12>(mData); hasData = true; break; case ESM::SREC_DELE: diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index ecd43796de..718ba6c3ac 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -58,7 +58,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("CLDT"): - esm.getHT(mData, 60); + esm.getHTSized<60>(mData); if (mData.mIsPlayable > 1) esm.fail("Unknown bool value"); hasData = true; diff --git a/components/esm3/loadclot.cpp b/components/esm3/loadclot.cpp index 1bd5db9655..5b6fa4210b 100644 --- a/components/esm3/loadclot.cpp +++ b/components/esm3/loadclot.cpp @@ -33,7 +33,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("CTDT"): - esm.getHT(mData, 12); + esm.getHTSized<12>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadcont.cpp b/components/esm3/loadcont.cpp index 5cbdd80428..6477bb9301 100644 --- a/components/esm3/loadcont.cpp +++ b/components/esm3/loadcont.cpp @@ -55,11 +55,11 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("CNDT"): - esm.getHT(mWeight, 4); + esm.getHTSized<4>(mWeight); hasWeight = true; break; case ESM::fourCC("FLAG"): - esm.getHT(mFlags, 4); + esm.getHTSized<4>(mFlags); if (mFlags & 0xf4) esm.fail("Unknown flags"); if (!(mFlags & 0x8)) diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index f1203ed39e..f82c488c63 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -50,7 +50,7 @@ namespace ESM { mScript = esm.getHString(); break; case ESM::fourCC("NPDT"): - esm.getHT(mData, 96); + esm.getHTSized<96>(mData); hasNpdt = true; break; case ESM::fourCC("FLAG"): diff --git a/components/esm3/loadench.cpp b/components/esm3/loadench.cpp index 2e138444f3..4280b3a8af 100644 --- a/components/esm3/loadench.cpp +++ b/components/esm3/loadench.cpp @@ -26,7 +26,7 @@ namespace ESM hasName = true; break; case ESM::fourCC("ENDT"): - esm.getHT(mData, 16); + esm.getHTSized<16>(mData); hasData = true; break; case ESM::fourCC("ENAM"): diff --git a/components/esm3/loadfact.cpp b/components/esm3/loadfact.cpp index 4d4697d93b..9fc5410164 100644 --- a/components/esm3/loadfact.cpp +++ b/components/esm3/loadfact.cpp @@ -56,7 +56,7 @@ namespace ESM mRanks[rankCounter++] = esm.getHString(); break; case ESM::fourCC("FADT"): - esm.getHT(mData, 240); + esm.getHTSized<240>(mData); if (mData.mIsHidden > 1) esm.fail("Unknown flag!"); hasData = true; diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 0b5030bb82..fb32f80188 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -26,7 +26,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case ESM::fourCC("DATA"): - esm.getHT(mData, 12); + esm.getHTSized<12>(mData); break; case ESM::fourCC("ONAM"): mActor = esm.getHString(); diff --git a/components/esm3/loadingr.cpp b/components/esm3/loadingr.cpp index 173e2c5636..d7317a8dc4 100644 --- a/components/esm3/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -31,7 +31,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("IRDT"): - esm.getHT(mData, 56); + esm.getHTSized<56>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadligh.cpp b/components/esm3/loadligh.cpp index 94c109e677..7b3c6e8787 100644 --- a/components/esm3/loadligh.cpp +++ b/components/esm3/loadligh.cpp @@ -34,7 +34,7 @@ namespace ESM mIcon = esm.getHString(); break; case ESM::fourCC("LHDT"): - esm.getHT(mData, 24); + esm.getHTSized<24>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadlock.cpp b/components/esm3/loadlock.cpp index 50d27d4e1a..0bdcbba635 100644 --- a/components/esm3/loadlock.cpp +++ b/components/esm3/loadlock.cpp @@ -31,7 +31,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("LKDT"): - esm.getHT(mData, 16); + esm.getHTSized<16>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index f15d51def6..b528e8bf9a 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -195,7 +195,7 @@ void MagicEffect::load(ESMReader &esm, bool &isDeleted) mId = indexToId (mIndex); - esm.getHNT(mData, "MEDT", 36); + esm.getHNTSized<36>(mData, "MEDT"); if (esm.getFormat() == 0) { // don't allow mods to change fixed flags in the legacy format diff --git a/components/esm3/loadmisc.cpp b/components/esm3/loadmisc.cpp index eee46ac8b0..bbf1ac0635 100644 --- a/components/esm3/loadmisc.cpp +++ b/components/esm3/loadmisc.cpp @@ -31,7 +31,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("MCDT"): - esm.getHT(mData, 12); + esm.getHTSized<12>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index 9d0f3cf66c..ed2ab2c5d3 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -52,7 +52,7 @@ namespace ESM mCell = esm.getHString(); break; case ESM::fourCC("DATA"): - esm.getHT(mData, 12); + esm.getHTSized<12>(mData); hasData = true; break; case ESM::fourCC("PGRP"): diff --git a/components/esm3/loadprob.cpp b/components/esm3/loadprob.cpp index 10738c0863..f084732775 100644 --- a/components/esm3/loadprob.cpp +++ b/components/esm3/loadprob.cpp @@ -31,7 +31,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("PBDT"): - esm.getHT(mData, 16); + esm.getHTSized<16>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadrace.cpp b/components/esm3/loadrace.cpp index ba0046bc9b..9520b792ed 100644 --- a/components/esm3/loadrace.cpp +++ b/components/esm3/loadrace.cpp @@ -40,7 +40,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("RADT"): - esm.getHT(mData, 140); + esm.getHTSized<140>(mData); hasData = true; break; case ESM::fourCC("DESC"): diff --git a/components/esm3/loadrepa.cpp b/components/esm3/loadrepa.cpp index aaf518a1cd..16dd296a25 100644 --- a/components/esm3/loadrepa.cpp +++ b/components/esm3/loadrepa.cpp @@ -31,7 +31,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("RIDT"): - esm.getHT(mData, 16); + esm.getHTSized<16>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/loadskil.cpp b/components/esm3/loadskil.cpp index 902410fe5a..f7bb0c49d6 100644 --- a/components/esm3/loadskil.cpp +++ b/components/esm3/loadskil.cpp @@ -144,7 +144,7 @@ namespace ESM hasIndex = true; break; case ESM::fourCC("SKDT"): - esm.getHT(mData, 24); + esm.getHTSized<24>(mData); hasData = true; break; case ESM::fourCC("DESC"): diff --git a/components/esm3/loadsndg.cpp b/components/esm3/loadsndg.cpp index 2f5dea1eb4..5519d835b4 100644 --- a/components/esm3/loadsndg.cpp +++ b/components/esm3/loadsndg.cpp @@ -25,7 +25,7 @@ namespace ESM hasName = true; break; case ESM::fourCC("DATA"): - esm.getHT(mType, 4); + esm.getHTSized<4>(mType); hasData = true; break; case ESM::fourCC("CNAM"): diff --git a/components/esm3/loadsoun.cpp b/components/esm3/loadsoun.cpp index 453d4eebfc..0ddda2d6b2 100644 --- a/components/esm3/loadsoun.cpp +++ b/components/esm3/loadsoun.cpp @@ -28,7 +28,7 @@ namespace ESM mSound = esm.getHString(); break; case ESM::fourCC("DATA"): - esm.getHT(mData, 3); + esm.getHTSized<3>(mData); hasData = true; break; case ESM::SREC_DELE: diff --git a/components/esm3/loadspel.cpp b/components/esm3/loadspel.cpp index aeed60a1cb..f2891c2b6c 100644 --- a/components/esm3/loadspel.cpp +++ b/components/esm3/loadspel.cpp @@ -30,12 +30,12 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("SPDT"): - esm.getHT(mData, 12); + esm.getHTSized<12>(mData); hasData = true; break; case ESM::fourCC("ENAM"): ENAMstruct s; - esm.getHT(s, 24); + esm.getHTSized<24>(s); mEffects.mList.push_back(s); break; case ESM::SREC_DELE: diff --git a/components/esm3/loadweap.cpp b/components/esm3/loadweap.cpp index e75ecf7516..452036cb95 100644 --- a/components/esm3/loadweap.cpp +++ b/components/esm3/loadweap.cpp @@ -31,7 +31,7 @@ namespace ESM mName = esm.getHString(); break; case ESM::fourCC("WPDT"): - esm.getHT(mData, 32); + esm.getHTSized<32>(mData); hasData = true; break; case ESM::fourCC("SCRI"): diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index 028a042809..1839a45749 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -10,12 +10,12 @@ void ESM::Player::load (ESMReader &esm) mCellId.load (esm); - esm.getHNT (mLastKnownExteriorPosition, "LKEP", 12); + esm.getHNTSized<12>(mLastKnownExteriorPosition, "LKEP"); if (esm.isNextSub ("MARK")) { mHasMark = true; - esm.getHT (mMarkedPosition, 24); + esm.getHTSized<24>(mMarkedPosition); mMarkedCell.load (esm); } else diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 667dbdfbce..94ea075759 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -15,7 +15,7 @@ void ESM::SavedGame::load (ESMReader &esm) mPlayerClassName = esm.getHNOString("PLCN"); mPlayerCell = esm.getHNString("PLCE"); - esm.getHNT (mInGameTime, "TSTM", 16); + esm.getHNTSized<16>(mInGameTime, "TSTM"); esm.getHNT (mTimePlayed, "TIME"); mDescription = esm.getHNString ("DESC"); From 0790af962eacc39e5c119a5b9a90f8d276b2776b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 9 Apr 2022 03:16:27 +0300 Subject: [PATCH 2283/2859] Cleanup code in mwshadowtechnique.cpp --- components/sceneutil/mwshadowtechnique.cpp | 225 +++++++++------------ 1 file changed, 98 insertions(+), 127 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 1bdc0e5693..f4c5bc1041 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #include "shadowsbin.hpp" @@ -1945,8 +1947,9 @@ bool MWShadowTechnique::computeShadowCameraSettings(Frustum& frustum, LightData& struct ConvexHull { typedef std::vector Vertices; - typedef std::pair< osg::Vec3d, osg::Vec3d > Edge; - typedef std::list< Edge > Edges; + typedef std::pair Edge; + typedef std::vector Edges; + typedef std::vector VertexSet; Edges _edges; @@ -1954,20 +1957,20 @@ struct ConvexHull void setToFrustum(MWShadowTechnique::Frustum& frustum) { - _edges.push_back( Edge(frustum.corners[0],frustum.corners[1]) ); - _edges.push_back( Edge(frustum.corners[1],frustum.corners[2]) ); - _edges.push_back( Edge(frustum.corners[2],frustum.corners[3]) ); - _edges.push_back( Edge(frustum.corners[3],frustum.corners[0]) ); + _edges.emplace_back(frustum.corners[0], frustum.corners[1]); + _edges.emplace_back(frustum.corners[1], frustum.corners[2]); + _edges.emplace_back(frustum.corners[2], frustum.corners[3]); + _edges.emplace_back(frustum.corners[3], frustum.corners[0]); - _edges.push_back( Edge(frustum.corners[4],frustum.corners[5]) ); - _edges.push_back( Edge(frustum.corners[5],frustum.corners[6]) ); - _edges.push_back( Edge(frustum.corners[6],frustum.corners[7]) ); - _edges.push_back( Edge(frustum.corners[7],frustum.corners[4]) ); + _edges.emplace_back(frustum.corners[4], frustum.corners[5]); + _edges.emplace_back(frustum.corners[5], frustum.corners[6]); + _edges.emplace_back(frustum.corners[6], frustum.corners[7]); + _edges.emplace_back(frustum.corners[7], frustum.corners[4]); - _edges.push_back( Edge(frustum.corners[0],frustum.corners[4]) ); - _edges.push_back( Edge(frustum.corners[1],frustum.corners[5]) ); - _edges.push_back( Edge(frustum.corners[2],frustum.corners[6]) ); - _edges.push_back( Edge(frustum.corners[3],frustum.corners[7]) ); + _edges.emplace_back(frustum.corners[0], frustum.corners[4]); + _edges.emplace_back(frustum.corners[1], frustum.corners[5]); + _edges.emplace_back(frustum.corners[2], frustum.corners[6]); + _edges.emplace_back(frustum.corners[3], frustum.corners[7]); } struct ConvexHull2D @@ -1981,22 +1984,22 @@ struct ConvexHull } // Calculates the 2D convex hull and returns it as a vector containing the points in CCW order with the first and last point being the same. - static std::vector convexHull(std::set &P) + static Vertices convexHull(const VertexSet &P) { size_t n = P.size(), k = 0; if (n <= 3) - return std::vector(P.cbegin(), P.cend()); + return Vertices(P.cbegin(), P.cend()); - std::vector H(2 * n); + Vertices H(2 * n); // Points are already sorted in a std::set // Build lower hull - for (auto pItr = P.cbegin(); pItr != P.cend(); ++pItr) + for(const auto& vert : P) { - while (k >= 2 && cross(H[k - 2], H[k - 1], *pItr) <= 0) + while (k >= 2 && cross(H[k - 2], H[k - 1], vert) <= 0) k--; - H[k++] = *pItr; + H[k++] = vert; } // Build upper hull @@ -2053,38 +2056,39 @@ struct ConvexHull void extendTowardsNegativeZ() { - typedef std::set VertexSet; - // Collect the set of vertices VertexSet vertices; for (const Edge& edge : _edges) { - vertices.insert(edge.first); - vertices.insert(edge.second); + vertices.emplace_back(edge.first); + vertices.emplace_back(edge.second); } + // Sort and make unique. + std::sort(vertices.begin(), vertices.end()); + vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end()); + if (vertices.size() == 0) return; // Get the vertices contributing to the 2D convex hull Vertices extremeVertices = ConvexHull2D::convexHull(vertices); - VertexSet extremeVerticesSet(extremeVertices.cbegin(), extremeVertices.cend()); // Add their extrusions to the final edge collection // We extrude as far as -1.5 as the coordinate space shouldn't ever put any shadow casters further than -1.0 Edges finalEdges; // Add edges towards -Z - for (auto vertex : extremeVertices) - finalEdges.push_back(Edge(vertex, osg::Vec3d(vertex.x(), vertex.y(), -1.5))); + for (const auto& vertex : extremeVertices) + finalEdges.emplace_back(vertex, osg::Vec3d(vertex.x(), vertex.y(), -1.5)); // Add edge loop to 'seal' the hull for (auto itr = extremeVertices.cbegin(); itr != extremeVertices.cend() - 1; ++itr) - finalEdges.push_back(Edge(osg::Vec3d(itr->x(), itr->y(), -1.5), osg::Vec3d((itr + 1)->x(), (itr + 1)->y(), -1.5))); + finalEdges.emplace_back(osg::Vec3d(itr->x(), itr->y(), -1.5), osg::Vec3d((itr + 1)->x(), (itr + 1)->y(), -1.5)); // The convex hull algorithm we are using sometimes places a point at both ends of the vector, so we don't always need to add the last edge separately. if (extremeVertices.front() != extremeVertices.back()) - finalEdges.push_back(Edge(osg::Vec3d(extremeVertices.front().x(), extremeVertices.front().y(), -1.5), osg::Vec3d(extremeVertices.back().x(), extremeVertices.back().y(), -1.5))); + finalEdges.emplace_back(osg::Vec3d(extremeVertices.front().x(), extremeVertices.front().y(), -1.5), osg::Vec3d(extremeVertices.back().x(), extremeVertices.back().y(), -1.5)); // Remove internal edges connected to extreme vertices - for (auto vertex : extremeVertices) + for (const auto& vertex : extremeVertices) { Vertices connectedVertices; for (const Edge& edge : _edges) @@ -2094,37 +2098,35 @@ struct ConvexHull else if (edge.second == vertex) connectedVertices.push_back(edge.first); } - connectedVertices.push_back(osg::Vec3d(vertex.x(), vertex.y(), -1.5)); + connectedVertices.emplace_back(vertex.x(), vertex.y(), -1.5); Vertices unwantedEdgeEnds = findInternalEdges(vertex, connectedVertices); - for (auto edgeEnd : unwantedEdgeEnds) + for (const auto& edgeEnd : unwantedEdgeEnds) { - for (auto itr = _edges.begin(); itr != _edges.end();) - { - if (*itr == Edge(vertex, edgeEnd)) + const auto edgeA = Edge(vertex, edgeEnd); + const auto edgeB = Edge(edgeEnd, vertex); + _edges.erase(std::remove_if(_edges.begin(), _edges.end(), [&](const auto& elem) { - itr = _edges.erase(itr); - break; - } - else if (*itr == Edge(edgeEnd, vertex)) - { - itr = _edges.erase(itr); - break; - } - else - ++itr; - } + return elem == edgeA || elem == edgeB; + }), _edges.end()); } } // Gather connected vertices - VertexSet unprocessedConnectedVertices(extremeVertices.begin(), extremeVertices.end()); + std::deque unprocessedConnectedVertices(extremeVertices.begin(), extremeVertices.end()); + VertexSet connectedVertices; + const auto containsVertex = [&](const auto& vert) + { + return std::find(connectedVertices.begin(), connectedVertices.end(), vert) != connectedVertices.end(); + }; + while (unprocessedConnectedVertices.size() > 0) { - osg::Vec3d vertex = *unprocessedConnectedVertices.begin(); - unprocessedConnectedVertices.erase(unprocessedConnectedVertices.begin()); - connectedVertices.insert(vertex); + osg::Vec3d vertex = unprocessedConnectedVertices.front(); + unprocessedConnectedVertices.pop_front(); + + connectedVertices.emplace_back(vertex); for (const Edge& edge : _edges) { osg::Vec3d otherEnd; @@ -2135,16 +2137,16 @@ struct ConvexHull else continue; - if (connectedVertices.count(otherEnd)) + if (containsVertex(otherEnd)) continue; - unprocessedConnectedVertices.insert(otherEnd); + unprocessedConnectedVertices.emplace_back(otherEnd); } } for (const Edge& edge : _edges) { - if (connectedVertices.count(edge.first) || connectedVertices.count(edge.second)) + if (containsVertex(edge.first) || containsVertex(edge.second)) finalEdges.push_back(edge); } @@ -2153,12 +2155,10 @@ struct ConvexHull void transform(const osg::Matrixd& m) { - for(Edges::iterator itr = _edges.begin(); - itr != _edges.end(); - ++itr) + for (auto& edge : _edges) { - itr->first = itr->first * m; - itr->second = itr->second * m; + edge.first = edge.first * m; + edge.second = edge.second * m; } } @@ -2167,18 +2167,14 @@ struct ConvexHull Vertices intersections; // OSG_NOTICE<<"clip("<=0.0 && d1>=0.0) { @@ -2215,15 +2211,15 @@ struct ConvexHull if (intersections.size() == 2) { - _edges.push_back( Edge(intersections[0], intersections[1]) ); + _edges.emplace_back(intersections[0], intersections[1]); return; } if (intersections.size() == 3) { - _edges.push_back( Edge(intersections[0], intersections[1]) ); - _edges.push_back( Edge(intersections[1], intersections[2]) ); - _edges.push_back( Edge(intersections[2], intersections[0]) ); + _edges.emplace_back(intersections[0], intersections[1]); + _edges.emplace_back(intersections[1], intersections[2]); + _edges.emplace_back(intersections[2], intersections[0]); return; } @@ -2241,11 +2237,9 @@ struct ConvexHull up.normalize(); osg::Vec3d center; - for(Vertices::iterator itr = intersections.begin(); - itr != intersections.end(); - ++itr) + for(auto& vertex : intersections) { - center += *itr; + center += vertex; center.x() = osg::maximum(center.x(), -dbl_max); center.y() = osg::maximum(center.y(), -dbl_max); @@ -2260,11 +2254,9 @@ struct ConvexHull typedef std::map>> VertexMap; VertexMap vertexMap; - for(Vertices::iterator itr = intersections.begin(); - itr != intersections.end(); - ++itr) + for (const auto& vertex : intersections) { - osg::Vec3d dv = (*itr-center); + osg::Vec3d dv = vertex - center; double h = dv * side; double v = dv * up; double angle = atan2(h,v); @@ -2285,20 +2277,18 @@ struct ConvexHull auto listItr = vertexMap[angle].begin(); while (listItr != vertexMap[angle].end() && listItr->second < sortValue) ++listItr; - vertexMap[angle].insert(listItr, std::make_pair(*itr, sortValue)); + vertexMap[angle].emplace(listItr, std::make_pair(vertex, sortValue)); } else - vertexMap[angle].push_back(std::make_pair(*itr, sortValue)); + vertexMap[angle].emplace_back(vertex, sortValue); } osg::Vec3d previous_v = vertexMap.rbegin()->second.back().first; - for(VertexMap::iterator itr = vertexMap.begin(); - itr != vertexMap.end(); - ++itr) + for (auto itr = vertexMap.begin(); itr != vertexMap.end(); ++itr) { - for (auto vertex : itr->second) + for (const auto& vertex : itr->second) { - _edges.push_back(Edge(previous_v, vertex.first)); + _edges.emplace_back(previous_v, vertex.first); previous_v = vertex.first; } } @@ -2309,24 +2299,19 @@ struct ConvexHull void clip(const osg::Polytope& polytope) { const osg::Polytope::PlaneList& planes = polytope.getPlaneList(); - for(osg::Polytope::PlaneList::const_iterator itr = planes.begin(); - itr != planes.end(); - ++itr) + for(const auto& plane : planes) { - clip(*itr); + clip(plane); } } double min(unsigned int index) const { double m = dbl_max; - for(Edges::const_iterator itr = _edges.begin(); - itr != _edges.end(); - ++itr) + for(const auto& edge : _edges) { - const Edge& edge = *itr; - if (edge.first[index]m) m = edge.first[index]; - if (edge.second[index]>m) m = edge.second[index]; + if (edge.first[index] > m) m = edge.first[index]; + if (edge.second[index] > m) m = edge.second[index]; } return m; } @@ -2350,19 +2332,15 @@ struct ConvexHull double m = dbl_max; osg::Vec3d delta; double ratio; - for(Edges::const_iterator itr = _edges.begin(); - itr != _edges.end(); - ++itr) + for (const auto& edge : _edges) { - const Edge& edge = *itr; - - delta = edge.first-eye; - ratio = delta[index]/delta[1]; - if (ratiom) m = ratio; + delta = edge.first - eye; + ratio = delta[index] / delta[1]; + if (ratio > m) m = ratio; - delta = edge.second-eye; - ratio = delta[index]/delta[1]; - if (ratio>m) m = ratio; + delta = edge.second - eye; + ratio = delta[index] / delta[1]; + if (ratio > m) m = ratio; } return m; } void output(std::ostream& out) { - out<<"ConvexHull"< Date: Sun, 10 Apr 2022 13:35:54 +0200 Subject: [PATCH 2284/2859] Fix skip DATA in cell ref loading --- components/esm3/cellref.cpp | 2 +- components/esm3/esmreader.hpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 9b46bf182a..d5ed0b4003 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -116,7 +116,7 @@ namespace ESM if constexpr (load) esm.getHTSized<24>(cellRef.mPos); else - esm.skip(24); + esm.skipHTSized<24, decltype(cellRef.mPos)>(); break; case ESM::fourCC("NAM0"): { diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index e931cde42a..ffa7a94d7b 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -155,6 +155,13 @@ public: getHT(x); } + template + void skipHTSized() + { + static_assert(sizeof(T) == size); + skipHT(); + } + // Read a string by the given name if it is the next record. std::string getHNOString(NAME name); From 19df9c3d171822ba417980165dabe11bad3bef49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sun, 10 Apr 2022 20:38:59 +0300 Subject: [PATCH 2285/2859] Use vector for edge queue --- components/sceneutil/mwshadowtechnique.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index f4c5bc1041..63010f6469 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -2113,7 +2113,7 @@ struct ConvexHull } // Gather connected vertices - std::deque unprocessedConnectedVertices(extremeVertices.begin(), extremeVertices.end()); + VertexSet unprocessedConnectedVertices = extremeVertices; VertexSet connectedVertices; const auto containsVertex = [&](const auto& vert) @@ -2121,10 +2121,10 @@ struct ConvexHull return std::find(connectedVertices.begin(), connectedVertices.end(), vert) != connectedVertices.end(); }; - while (unprocessedConnectedVertices.size() > 0) + while (!unprocessedConnectedVertices.empty()) { - osg::Vec3d vertex = unprocessedConnectedVertices.front(); - unprocessedConnectedVertices.pop_front(); + osg::Vec3d vertex = unprocessedConnectedVertices.back(); + unprocessedConnectedVertices.pop_back(); connectedVertices.emplace_back(vertex); for (const Edge& edge : _edges) From b39aea434661f50ed7b0988944c31e46fb512c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sun, 10 Apr 2022 20:56:52 +0300 Subject: [PATCH 2286/2859] Move extremeEdges into queue instead of copying --- components/sceneutil/mwshadowtechnique.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 63010f6469..931b316347 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -2113,7 +2113,7 @@ struct ConvexHull } // Gather connected vertices - VertexSet unprocessedConnectedVertices = extremeVertices; + VertexSet unprocessedConnectedVertices = std::move(extremeVertices); VertexSet connectedVertices; const auto containsVertex = [&](const auto& vert) From 2c5a4e64160111936b634e07cfa76f279cbd8c41 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Sun, 10 Apr 2022 15:26:25 -0400 Subject: [PATCH 2287/2859] Set ICU_ROOT instead of PKG_CONFIG_PATH to find ICU on macos --- CI/before_install.osx.sh | 1 - CI/before_script.osx.sh | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 6871787e22..ba269ae0e0 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -16,7 +16,6 @@ command -v qmake >/dev/null 2>&1 || brew install qt@5 brew install icu4c brew install yaml-cpp export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6 -export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig" ccache --version cmake --version diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 8b2c9c6f01..c4bb1d1a64 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -26,5 +26,6 @@ cmake \ -D BUILD_BSATOOL=TRUE \ -D BUILD_ESSIMPORTER=TRUE \ -D BUILD_NIFTEST=TRUE \ +-D ICU_ROOT="/usr/local/opt/icu4c" -G"Unix Makefiles" \ .. From e3cedb5bfcf0dd86ad9c76ff45994e9199cbd407 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Sun, 10 Apr 2022 15:26:41 -0400 Subject: [PATCH 2288/2859] Set yaml-cpp and ICU as REQUIRED --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dbb88e14f8..0cbd7a1a99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -251,7 +251,7 @@ set(USED_OSG_PLUGINS option(OPENMW_USE_SYSTEM_ICU "Use system ICU library instead of internal. If disabled, requires autotools" ON) if(OPENMW_USE_SYSTEM_ICU) - find_package(ICU COMPONENTS uc i18n) + find_package(ICU REQUIRED COMPONENTS uc i18n) endif() option(OPENMW_USE_SYSTEM_YAML_CPP "Use system yaml-cpp library instead of internal." ON) @@ -262,7 +262,7 @@ else() endif() option(YAML_CPP_STATIC "Link static build of yaml-cpp into the binaries" ${_yaml_cpp_static_default}) if (OPENMW_USE_SYSTEM_YAML_CPP) - find_package(yaml-cpp) + find_package(yaml-cpp REQUIRED) endif() add_subdirectory(extern) From db44f91fd56e06810c62768f7da2ed63e9d15814 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 10 Apr 2022 18:33:42 +0200 Subject: [PATCH 2289/2859] Remove redundant include from esm3/esmreader.hpp --- apps/essimporter/converter.hpp | 1 + apps/opencs/model/doc/savingstages.cpp | 2 ++ apps/opencs/model/world/regionmap.cpp | 1 + apps/opencs/view/filter/editwidget.cpp | 2 ++ apps/openmw/mwworld/store.cpp | 1 + components/esm3/esmreader.cpp | 1 + components/esm3/esmreader.hpp | 1 - components/esm3/loadscpt.cpp | 2 ++ components/esm3/variantimp.cpp | 1 + components/esmloader/load.cpp | 1 + 10 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 8b71775759..ac891e78df 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESSIMPORT_CONVERTER_H #include +#include #include #include diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 08bd6c81e8..df3d0cb980 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -1,5 +1,7 @@ #include "savingstages.hpp" +#include + #include #include diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 6dbbac97fb..557a8303b5 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -2,6 +2,7 @@ #include #include +#include #include diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 2c2f4443df..78dfef704b 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -1,5 +1,7 @@ #include "editwidget.hpp" +#include + #include #include #include diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 80cf3b87a1..ba33703668 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -10,6 +10,7 @@ #include #include +#include namespace MWWorld { diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 2cf0cd29ce..187b2d5bae 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace ESM { diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index ffa7a94d7b..7b75458d25 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -3,7 +3,6 @@ #include #include -#include #include diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index f226d5f3be..b97a148963 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -1,5 +1,7 @@ #include "loadscpt.hpp" +#include + #include #include "esmreader.hpp" diff --git a/components/esm3/variantimp.cpp b/components/esm3/variantimp.cpp index b9cd9a8536..df491e7cec 100644 --- a/components/esm3/variantimp.cpp +++ b/components/esm3/variantimp.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "esmreader.hpp" #include "esmwriter.hpp" diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp index 14478a6c85..95011e221c 100644 --- a/components/esmloader/load.cpp +++ b/components/esmloader/load.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace EsmLoader { From fa29b9d6f75e4a3337d65bcc29018b76f13beec0 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 10 Apr 2022 22:31:09 +0200 Subject: [PATCH 2290/2859] Fix #6627 --- apps/wizard/mainwizard.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 7987d9b08d..8c61cdd202 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -55,6 +55,9 @@ Wizard::MainWizard::MainWizard(QWidget *parent) :

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

"); + boost::filesystem::create_directories(mCfgMgr.getUserConfigPath()); + boost::filesystem::create_directories(mCfgMgr.getUserDataPath()); + setupLog(); setupGameSettings(); setupLauncherSettings(); From 5eb8c4aebec9ceba26f1a5f22890610fab6150e5 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 10 Apr 2022 17:25:26 +0200 Subject: [PATCH 2291/2859] Avoid redundant conversion to const char* and use make_shared --- apps/niftest/niftest.cpp | 2 +- apps/openmw_test_suite/files/hash.cpp | 2 +- components/bsa/bsa_file.hpp | 2 +- components/bsa/compressedbsafile.cpp | 4 ++-- components/esm/reader.cpp | 2 +- components/esm3/esmreader.cpp | 4 ++-- components/esm4/reader.cpp | 2 +- components/esm4/reader.hpp | 10 ++++++---- components/files/constrainedfilestream.cpp | 8 +++----- components/files/constrainedfilestream.hpp | 5 ++++- components/vfs/filesystemarchive.cpp | 2 +- 11 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index f857dbde37..8f8f9c979e 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -131,7 +131,7 @@ int main(int argc, char **argv) if(isNIF(name)) { //std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name.c_str()),name); + Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name), name); } else if(isBSA(name)) { diff --git a/apps/openmw_test_suite/files/hash.cpp b/apps/openmw_test_suite/files/hash.cpp index e6dbc8f6cc..f87edd4e6e 100644 --- a/apps/openmw_test_suite/files/hash.cpp +++ b/apps/openmw_test_suite/files/hash.cpp @@ -39,7 +39,7 @@ namespace std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); std::fstream(fileName, std::ios_base::out | std::ios_base::binary) .write(content.data(), static_cast(content.size())); - const auto stream = Files::openConstrainedFileStream(fileName.data(), 0, content.size()); + const auto stream = Files::openConstrainedFileStream(fileName, 0, content.size()); EXPECT_EQ(getHash(fileName, *stream), GetParam().mHash); } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 83e9ae954c..66c8234bbd 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -125,7 +125,7 @@ public: */ Files::IStreamPtr getFile(const FileStruct *file) { - return Files::openConstrainedFileStream (mFilename.c_str (), file->offset, file->fileSize); + return Files::openConstrainedFileStream(mFilename, file->offset, file->fileSize); } void addFile(const std::string& filename, std::istream& file); diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index ae8209a39a..a3f3c08864 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -355,7 +355,7 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) size_t size = fileRecord.getSizeWithoutCompressionFlag(); size_t uncompressedSize = size; bool compressed = fileRecord.isCompressed(mCompressedByDefault); - Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, size); + Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilename, fileRecord.offset, size); std::istream* fileStream = streamPtr.get(); if (mEmbeddedFileNames) { @@ -458,7 +458,7 @@ void CompressedBSAFile::convertCompressedSizesToUncompressed() continue; } - Files::IStreamPtr dataBegin = Files::openConstrainedFileStream(mFilename.c_str(), fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); + Files::IStreamPtr dataBegin = Files::openConstrainedFileStream(mFilename, fileRecord.offset, fileRecord.getSizeWithoutCompressionFlag()); if (mEmbeddedFileNames) { diff --git a/components/esm/reader.cpp b/components/esm/reader.cpp index 7fcc2cce3a..d3732abb1e 100644 --- a/components/esm/reader.cpp +++ b/components/esm/reader.cpp @@ -16,7 +16,7 @@ namespace ESM { Reader* Reader::getReader(const std::string &filename) { - Files::IStreamPtr esmStream(Files::openConstrainedFileStream (filename.c_str ())); + Files::IStreamPtr esmStream(Files::openConstrainedFileStream(filename)); std::uint32_t modVer = 0; // get the first 4 bytes of the record header only esmStream->read((char*)&modVer, sizeof(modVer)); diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 2cf0cd29ce..abe6cf9d71 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -93,7 +93,7 @@ void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) void ESMReader::openRaw(const std::string& filename) { - openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); + openRaw(Files::openConstrainedFileStream(filename), filename); } void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) @@ -110,7 +110,7 @@ void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) void ESMReader::open(const std::string &file) { - open (Files::openConstrainedFileStream (file.c_str ()), file); + open (Files::openConstrainedFileStream(file), file); } std::string ESMReader::getHNOString(NAME name) diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index f4fe8def68..1a8fff789a 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -210,7 +210,7 @@ void Reader::buildLStringIndex(const std::string& stringFile, LocalizedStringTyp sp.type = stringType; // TODO: possibly check if the resource exists? - Files::IStreamPtr filestream = Files::IStreamPtr(Files::openConstrainedFileStream(stringFile.c_str())); + Files::IStreamPtr filestream = Files::IStreamPtr(Files::openConstrainedFileStream(stringFile)); filestream->seekg(0, std::ios::end); std::size_t fileSize = filestream->tellg(); diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index c1bf808dd5..60676b116b 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -128,12 +128,14 @@ namespace ESM4 { ~Reader(); // FIXME: should be private but ESMTool uses it - void openRaw(const std::string& filename) { - openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); + void openRaw(const std::string& filename) + { + openRaw(Files::openConstrainedFileStream(filename), filename); } - void open(const std::string& filename) { - open(Files::openConstrainedFileStream (filename.c_str ()), filename); + void open(const std::string& filename) + { + open(Files::openConstrainedFileStream(filename), filename); } void close() final; diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index b9968038d1..0beaf5facc 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -27,7 +27,7 @@ namespace Files ConstrainedFileStreamBuf(const std::string &fname, size_t start, size_t length) { mFile.open (fname.c_str ()); - mSize = length != 0xFFFFFFFF ? length : mFile.size () - start; + mSize = length != std::numeric_limits::max() ? length : mFile.size () - start; if (start != 0) mFile.seek(start); @@ -109,10 +109,8 @@ namespace Files { } - IStreamPtr openConstrainedFileStream(const char *filename, - size_t start, size_t length) + IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start, std::size_t length) { - auto buf = std::unique_ptr(new ConstrainedFileStreamBuf(filename, start, length)); - return IStreamPtr(new ConstrainedFileStream(std::move(buf))); + return std::make_shared(std::make_unique(filename, start, length)); } } diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp index bf67c7b973..b828f0f6f1 100644 --- a/components/files/constrainedfilestream.hpp +++ b/components/files/constrainedfilestream.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include namespace Files { @@ -20,7 +22,8 @@ private: typedef std::shared_ptr IStreamPtr; -IStreamPtr openConstrainedFileStream(const char *filename, size_t start=0, size_t length=0xFFFFFFFF); +IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start = 0, + std::size_t length = std::numeric_limits::max()); } diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 7766a74f49..839f84de57 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -78,7 +78,7 @@ namespace VFS Files::IStreamPtr FileSystemArchiveFile::open() { - return Files::openConstrainedFileStream(mPath.c_str()); + return Files::openConstrainedFileStream(mPath); } } From 6481324eb17facebce712bd799db44840700c18c Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 11 Apr 2022 01:44:07 +0200 Subject: [PATCH 2292/2859] Read when need to skip few bytes --- components/esm3/esmreader.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index ffa7a94d7b..85e3f8a59a 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -265,7 +265,14 @@ public: // them from native encoding to UTF8 in the process. std::string getString(int size); - void skip(int bytes) { mEsm->seekg(getFileOffset()+bytes); }; + void skip(std::size_t bytes) + { + char buffer[4096]; + if (bytes > std::size(buffer)) + mEsm->seekg(getFileOffset() + bytes); + else + mEsm->read(buffer, bytes); + } /// Used for error handling [[noreturn]] void fail(const std::string &msg); From db3f9da08a5b2bc0d9aff0e10809d135261b8238 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 11 Apr 2022 14:37:22 +0200 Subject: [PATCH 2293/2859] coverity fixes and other bits --- components/l10n/messagebundles.cpp | 26 +++++++++++++------------- components/l10n/messagebundles.hpp | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index 9560e60d3b..18036a6136 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -22,19 +22,19 @@ namespace l10n for (const icu::Locale &loc: preferredLocales) { mPreferredLocales.push_back(loc); - mPreferredLocaleStrings.push_back(loc.getName()); + mPreferredLocaleStrings.emplace_back(loc.getName()); // Try without variant or country if they are specified, starting with the most specific if (strcmp(loc.getVariant(), "") != 0) { icu::Locale withoutVariant(loc.getLanguage(), loc.getCountry()); mPreferredLocales.push_back(withoutVariant); - mPreferredLocaleStrings.push_back(withoutVariant.getName()); + mPreferredLocaleStrings.emplace_back(withoutVariant.getName()); } if (strcmp(loc.getCountry(), "") != 0) { icu::Locale withoutCountry(loc.getLanguage()); mPreferredLocales.push_back(withoutCountry); - mPreferredLocaleStrings.push_back(withoutCountry.getName()); + mPreferredLocaleStrings.emplace_back(withoutCountry.getName()); } } } @@ -53,7 +53,7 @@ namespace l10n if (status.isFailure()) { std::string errorText = getErrorText(parseError); - if (errorText.size()) + if (!errorText.empty()) { Log(Debug::Error) << message << ": " << status.errorName() << " in \"" << errorText << "\""; } @@ -73,8 +73,8 @@ namespace l10n std::string localeName = lang.getName(); for (const auto& it: data) { - std::string key = it.first.as(); - std::string value = it.second.as(); + auto key = it.first.as(); + auto value = it.second.as(); icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(value); icu::ErrorCode status; UParseError parseError; @@ -110,10 +110,10 @@ namespace l10n { std::vector argNames; std::vector argValues; - for (auto& [key, value] : args) + for (auto& [k, v] : args) { - argNames.push_back(icu::UnicodeString::fromUTF8(key)); - argValues.push_back(value); + argNames.push_back(icu::UnicodeString::fromUTF8(k)); + argValues.push_back(v); } return formatMessage(key, argNames, argValues); } @@ -137,7 +137,7 @@ namespace l10n if (message) { - if (args.size() > 0 && argNames.size() > 0) + if (!args.empty() && !argNames.empty()) message->format(&argNames[0], &args[0], args.size(), result, success); else message->format(nullptr, nullptr, args.size(), result, success); @@ -145,8 +145,8 @@ namespace l10n result.toUTF8String(resultString); return resultString; } - icu::Locale defaultLocale(NULL); - if (mPreferredLocales.size() > 0) + icu::Locale defaultLocale(nullptr); + if (!mPreferredLocales.empty()) { defaultLocale = mPreferredLocales[0]; } @@ -156,7 +156,7 @@ namespace l10n // If we can't parse the key as a pattern, just return the key return std::string(key); - if (args.size() > 0 && argNames.size() > 0) + if (!args.empty() && !argNames.empty()) defaultMessage.format(&argNames[0], &args[0], args.size(), result, success); else defaultMessage.format(nullptr, nullptr, args.size(), result, success); diff --git a/components/l10n/messagebundles.hpp b/components/l10n/messagebundles.hpp index 02333fad2c..1a4636c95a 100644 --- a/components/l10n/messagebundles.hpp +++ b/components/l10n/messagebundles.hpp @@ -38,7 +38,7 @@ namespace l10n void setPreferredLocales(const std::vector &preferredLocales); const std::vector & getPreferredLocales() const { return mPreferredLocales; } void load(std::istream &input, const icu::Locale &lang, const std::string &path); - bool isLoaded(icu::Locale loc) const { return mBundles.find(loc.getName()) != mBundles.end(); } + bool isLoaded(const icu::Locale& loc) const { return mBundles.find(loc.getName()) != mBundles.end(); } const icu::Locale & getFallbackLocale() const { return mFallbackLocale; } private: From 035fe778b2974987ca342ed1b4ccccf173d08cff Mon Sep 17 00:00:00 2001 From: Vidi_Aquam <89811652+VidiAquam@users.noreply.github.com> Date: Mon, 11 Apr 2022 08:12:38 -0500 Subject: [PATCH 2294/2859] Temporary workaround for angle snapping Made the angle snap only apply to an object when the drag is finished, which is much more usable until the rotation system can be fixed completely --- apps/opencs/view/render/instancemode.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index e9a375f10d..a84b26ca51 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -628,14 +628,6 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou position.rot[2] = euler.z(); } - if (mDragMode == DragMode_Rotate_Snap) - { - double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble(); - position.rot[0] = CSVRender::InstanceMode::roundFloatToMult(position.rot[0], osg::DegreesToRadians(snap)); - position.rot[1] = CSVRender::InstanceMode::roundFloatToMult(position.rot[1], osg::DegreesToRadians(snap)); - position.rot[2] = CSVRender::InstanceMode::roundFloatToMult(position.rot[2], osg::DegreesToRadians(snap)); - } - objectTag->mObject->setRotation(position.rot); } else if (mDragMode == DragMode_Scale || mDragMode == DragMode_Scale_Snap) @@ -702,6 +694,17 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) { if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) { + if (mDragMode == DragMode_Rotate_Snap) + { + ESM::Position position = objectTag->mObject->getPosition(); + double snap = CSMPrefs::get()["3D Scene Editing"]["gridsnap-rotation"].toDouble(); + position.rot[0] = CSVRender::InstanceMode::roundFloatToMult(position.rot[0], osg::DegreesToRadians(snap)); + position.rot[1] = CSVRender::InstanceMode::roundFloatToMult(position.rot[1], osg::DegreesToRadians(snap)); + position.rot[2] = CSVRender::InstanceMode::roundFloatToMult(position.rot[2], osg::DegreesToRadians(snap)); + + objectTag->mObject->setRotation(position.rot); + } + objectTag->mObject->apply (macro); } } From 7fe6c39aa21b5fb24295261d511cae3b7aa6d843 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 11 Apr 2022 16:59:46 +0200 Subject: [PATCH 2295/2859] const the key/value --- components/l10n/messagebundles.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index 18036a6136..d02735313e 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -73,8 +73,8 @@ namespace l10n std::string localeName = lang.getName(); for (const auto& it: data) { - auto key = it.first.as(); - auto value = it.second.as(); + const auto key = it.first.as(); + const auto value = it.second.as(); icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(value); icu::ErrorCode status; UParseError parseError; From e2f393cbdb02287f00bc663b30e1b3c732697882 Mon Sep 17 00:00:00 2001 From: VidiAquam <11267882-VidiAquam@users.noreply.gitlab.com> Date: Mon, 11 Apr 2022 15:36:05 +0000 Subject: [PATCH 2296/2859] Update CHANGELOG.md, AUTHORS.md --- AUTHORS.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 84f465414e..b2050ef0bb 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -226,6 +226,7 @@ Programmers unelsson uramer viadanna + Vidi_Aquam Vincent Heuken Vladimir Panteleev (CyberShadow) Wang Ryu (bzzt) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c665740ac..7401894d25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,7 @@ Feature #2491: Ability to make OpenMW "portable" Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console + Feature #3245: Grid and angle snapping for the OpenMW-CS Feature #3616: Allow Zoom levels on the World Map Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4414: Handle duration of EXTRA SPELL magic effect From 39da3bfef8c7d26087787b9c6d14caa222d656b9 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 11 Apr 2022 19:30:54 +0200 Subject: [PATCH 2297/2859] Ignore player when checking whether AiTravel destination is occupied by other actor --- apps/openmw/mwbase/world.hpp | 3 +- apps/openmw/mwmechanics/aitravel.cpp | 2 +- apps/openmw/mwmechanics/obstacle.cpp | 12 +++++-- apps/openmw/mwmechanics/obstacle.hpp | 2 +- .../mwphysics/hasspherecollisioncallback.hpp | 12 +++---- apps/openmw/mwphysics/physicssystem.cpp | 27 +++++++++----- apps/openmw/mwphysics/physicssystem.hpp | 4 ++- apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 2 +- components/misc/span.hpp | 36 +++++++++++++++++++ 10 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 components/misc/span.hpp diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 625758d640..6479a1a404 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -10,6 +10,7 @@ #include #include +#include #include @@ -658,7 +659,7 @@ namespace MWBase virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - const MWWorld::ConstPtr& ignore, std::vector* occupyingActors = nullptr) const = 0; + const Misc::Span& ignore, std::vector* occupyingActors = nullptr) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 919685e93d..770d2f7ba6 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -83,7 +83,7 @@ namespace MWMechanics if (mDestinationCheck.update(duration) == Misc::TimerStatus::Elapsed) { std::vector occupyingActors; - if (isAreaOccupiedByOtherActor(actor, targetPos, &occupyingActors)) + if (isAreaOccupiedByOtherActor(actor, targetPos, true, &occupyingActors)) { const float actorRadius = getActorRadius(actor); const float distanceToTarget = distance(actorPos, targetPos); diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 344cc82058..3d64eae862 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -1,5 +1,7 @@ #include "obstacle.hpp" +#include + #include #include "../mwworld/class.hpp" @@ -74,13 +76,19 @@ namespace MWMechanics return MWWorld::Ptr(); // none found } - bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, bool ignorePlayer, std::vector* occupyingActors) { const auto world = MWBase::Environment::get().getWorld(); const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); - return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor, occupyingActors); + if (ignorePlayer) + { + const std::array ignore {actor, world->getPlayerConstPtr()}; + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors); + } + const std::array ignore {actor}; + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors); } ObstacleCheck::ObstacleCheck() diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 24bd5ed1c1..2026f22129 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -24,7 +24,7 @@ namespace MWMechanics /** \return Pointer to the door, or empty pointer if none exists **/ const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); - bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, bool ignorePlayer = false, std::vector* occupyingActors = nullptr); class ObstacleCheck diff --git a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp index a01ab96301..2d271a8fba 100644 --- a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp +++ b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp @@ -22,15 +22,15 @@ namespace MWPhysics return nearest.distance(position) < radius; } - template + template class HasSphereCollisionCallback final : public btBroadphaseAabbCallback { public: - HasSphereCollisionCallback(const btVector3& position, const btScalar radius, btCollisionObject* object, - const int mask, const int group, OnCollision* onCollision) + HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask, const int group, + const Ignore& ignore, OnCollision* onCollision) : mPosition(position), mRadius(radius), - mCollisionObject(object), + mIgnore(ignore), mCollisionFilterMask(mask), mCollisionFilterGroup(group), mOnCollision(onCollision) @@ -42,7 +42,7 @@ namespace MWPhysics if (mResult && mOnCollision == nullptr) return false; const auto collisionObject = static_cast(proxy->m_clientObject); - if (collisionObject == mCollisionObject + if (mIgnore(collisionObject) || !needsCollision(*proxy) || !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius)) return true; @@ -63,7 +63,7 @@ namespace MWPhysics private: btVector3 mPosition; btScalar mRadius; - btCollisionObject* mCollisionObject; + Ignore mIgnore; int mCollisionFilterMask; int mCollisionFilterGroup; OnCollision* mOnCollision; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index bbd3447b6a..06c2420df1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -2,7 +2,11 @@ #include #include + #include +#include +#include + #include #include #include @@ -884,12 +888,19 @@ namespace MWPhysics } bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const - { - btCollisionObject* object = nullptr; - const auto it = mActors.find(ignore.mRef); - if (it != mActors.end()) - object = it->second->getCollisionObject(); + const Misc::Span& ignore, std::vector* occupyingActors) const + { + std::vector ignoredObjects; + ignoredObjects.reserve(ignore.size()); + for (const auto& v : ignore) + if (const auto it = mActors.find(v.mRef); it != mActors.end()) + ignoredObjects.push_back(it->second->getCollisionObject()); + std::sort(ignoredObjects.begin(), ignoredObjects.end()); + ignoredObjects.erase(std::unique(ignoredObjects.begin(), ignoredObjects.end()), ignoredObjects.end()); + const auto ignoreFilter = [&] (const btCollisionObject* v) + { + return std::binary_search(ignoredObjects.begin(), ignoredObjects.end(), v); + }; const auto bulletPosition = Misc::Convert::toBullet(position); const auto aabbMin = bulletPosition - btVector3(radius, radius, radius); const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); @@ -897,7 +908,7 @@ namespace MWPhysics const int group = 0xff; if (occupyingActors == nullptr) { - HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group, + HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, static_cast(nullptr)); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); @@ -907,7 +918,7 @@ namespace MWPhysics if (PtrHolder* holder = static_cast(object->getUserPointer())) occupyingActors->push_back(holder->getPtr()); }; - HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group, &onCollision); + HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, &onCollision); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b165f10761..4afafede4f 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -16,6 +16,8 @@ #include #include +#include + #include "../mwworld/ptr.hpp" #include "collisiontype.hpp" @@ -277,7 +279,7 @@ namespace MWPhysics } bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const; + const Misc::Span& ignore, std::vector* occupyingActors) const; void reportStats(unsigned int frameNumber, osg::Stats& stats) const; void reportCollision(const btVector3& position, const btVector3& normal); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7f652d9520..ea2441847f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3997,7 +3997,7 @@ namespace MWWorld } bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const + const Misc::Span& ignore, std::vector* occupyingActors) const { return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore, occupyingActors); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 399b7232de..0dd79fb778 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -736,7 +736,7 @@ namespace MWWorld bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override; bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const override; + const Misc::Span& ignore, std::vector* occupyingActors) const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; diff --git a/components/misc/span.hpp b/components/misc/span.hpp new file mode 100644 index 0000000000..83a424a674 --- /dev/null +++ b/components/misc/span.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_COMPONENTS_MISC_SPAN_H +#define OPENMW_COMPONENTS_MISC_SPAN_H + +#include + +namespace Misc +{ + template + class Span + { + public: + constexpr Span() = default; + + constexpr Span(T* pointer, std::size_t size) + : mPointer(pointer) + , mSize(size) + {} + + template + constexpr Span(Range& range) + : Span(range.data(), range.size()) + {} + + constexpr T* begin() const { return mPointer; } + + constexpr T* end() const { return mPointer + mSize; } + + constexpr std::size_t size() const { return mSize; } + + private: + T* mPointer = nullptr; + std::size_t mSize = 0; + }; +} + +#endif From a65f8ebbc611667e71b10d10d1fcafc9d6151599 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 11 Apr 2022 01:04:55 +0200 Subject: [PATCH 2298/2859] Reorganize delayed Lua actions --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/actions.cpp | 180 --------------------------- apps/openmw/mwlua/actions.hpp | 94 -------------- apps/openmw/mwlua/luamanagerimp.cpp | 46 +++++++ apps/openmw/mwlua/luamanagerimp.hpp | 26 +++- apps/openmw/mwlua/objectbindings.cpp | 80 ++++++++++++ apps/openmw/mwlua/stats.cpp | 20 +++ apps/openmw/mwlua/types/actor.cpp | 97 ++++++++++++++- apps/openmw/mwlua/uibindings.cpp | 5 +- 9 files changed, 267 insertions(+), 283 deletions(-) delete mode 100644 apps/openmw/mwlua/actions.cpp delete mode 100644 apps/openmw/mwlua/actions.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3d6469fa33..f67c991958 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwlua - luamanagerimp actions object worldview userdataserializer eventqueue + luamanagerimp object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings stats types/types types/door types/actor types/container types/weapon types/npc diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp deleted file mode 100644 index 0934b92226..0000000000 --- a/apps/openmw/mwlua/actions.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "actions.hpp" - -#include "localscripts.hpp" - -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace MWLua -{ - Action::Action(LuaUtil::LuaState* state) - { - static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); - if (luaDebug) - mCallerTraceback = state->debugTraceback(); - } - - void Action::safeApply(WorldView& w) const - { - try - { - apply(w); - } - catch (const std::exception& e) - { - Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); - - if (mCallerTraceback.empty()) - Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; - else - Log(Debug::Error) << "Caller " << mCallerTraceback; - } - } - - void TeleportAction::apply(WorldView& worldView) const - { - MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); - if (!cell) - throw std::runtime_error(std::string("cell not found: '") + mCell + "'"); - - MWBase::World* world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); - const MWWorld::Class& cls = obj.getClass(); - bool isPlayer = obj == world->getPlayerPtr(); - if (cls.isActor()) - cls.getCreatureStats(obj).land(isPlayer); - if (isPlayer) - { - ESM::Position esmPos; - static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); - std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); - std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); - world->getPlayer().setTeleported(true); - if (cell->isExterior()) - world->changeToExteriorCell(esmPos, true); - else - world->changeToInteriorCell(mCell, esmPos, true); - } - else - { - MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); - world->rotateObject(newObj, mRot); - } - } - - void SetEquipmentAction::apply(WorldView& worldView) const - { - MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false); - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - std::array usedSlots; - std::fill(usedSlots.begin(), usedSlots.end(), false); - - static constexpr int anySlot = -1; - auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool - { - auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); - MWWorld::Ptr itemPtr; - if (std::holds_alternative(item)) - { - itemPtr = worldView.getObjectRegistry()->getPtr(std::get(item), false); - if (old_it != store.end() && *old_it == itemPtr) - return true; // already equipped - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 || - itemPtr.getContainerStore() != static_cast(&store)) - { - Log(Debug::Warning) << "Object" << idToString(std::get(item)) << " is not in inventory"; - return false; - } - } - else - { - const std::string& recordId = std::get(item); - if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) - return true; // already equipped - itemPtr = store.search(recordId); - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) - { - Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; - return false; - } - } - - auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); - bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end(); - if (!requestedSlotIsAllowed) - { - auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); - if (firstAllowed == allowedSlots.end()) - { - Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); - return false; - } - slot = *firstAllowed; - } - - // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. - MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); - if (it == store.end()) // should never happen - throw std::logic_error("Item not found in container"); - - store.equip(slot, it, actor); - return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed - }; - - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - auto old_it = store.getSlot(slot); - auto new_it = mEquipment.find(slot); - if (new_it == mEquipment.end()) - { - if (old_it != store.end()) - store.unequipSlot(slot, actor); - continue; - } - if (tryEquipToSlot(slot, new_it->second)) - usedSlots[slot] = true; - } - for (const auto& [slot, item] : mEquipment) - if (slot >= MWWorld::InventoryStore::Slots) - tryEquipToSlot(anySlot, item); - } - - void ActivateAction::apply(WorldView& worldView) const - { - MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true); - if (object.isEmpty()) - throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); - MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true); - if (actor.isEmpty()) - throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); - - MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); - std::unique_ptr action = object.getClass().activate(object, actor); - action->execute(actor); - } - - std::string ActivateAction::toString() const - { - return std::string("ActivateAction object=") + idToString(mObject) + - std::string(" actor=") + idToString(mActor); - } - - void StatUpdateAction::apply(WorldView& worldView) const - { - LObject obj(mId, worldView.getObjectRegistry()); - LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); - if (scripts) - scripts->applyStatsCache(); - } -} diff --git a/apps/openmw/mwlua/actions.hpp b/apps/openmw/mwlua/actions.hpp deleted file mode 100644 index 30211b6e53..0000000000 --- a/apps/openmw/mwlua/actions.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef MWLUA_ACTIONS_H -#define MWLUA_ACTIONS_H - -#include - -#include "object.hpp" -#include "worldview.hpp" - -namespace LuaUtil -{ - class LuaState; -} - -namespace MWLua -{ - - // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), - // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited - // from MWLua::Action. - - class Action - { - public: - Action(LuaUtil::LuaState* state); - virtual ~Action() {} - - void safeApply(WorldView&) const; - virtual void apply(WorldView&) const = 0; - virtual std::string toString() const = 0; - - private: - std::string mCallerTraceback; - }; - - class TeleportAction final : public Action - { - public: - TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) - : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} - - void apply(WorldView&) const override; - std::string toString() const override { return "TeleportAction"; } - - private: - ObjectId mObject; - std::string mCell; - osg::Vec3f mPos; - osg::Vec3f mRot; - }; - - class SetEquipmentAction final : public Action - { - public: - using Item = std::variant; // recordId or ObjectId - using Equipment = std::map; // slot to item - - SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment) - : Action(state), mActor(actor), mEquipment(std::move(equipment)) {} - - void apply(WorldView&) const override; - std::string toString() const override { return "SetEquipmentAction"; } - - private: - ObjectId mActor; - Equipment mEquipment; - }; - - class ActivateAction final : public Action - { - public: - ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) - : Action(state), mObject(object), mActor(actor) {} - - void apply(WorldView&) const override; - std::string toString() const override; - - private: - ObjectId mObject; - ObjectId mActor; - }; - - class StatUpdateAction final : public Action - { - ObjectId mId; - public: - StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} - - void apply(WorldView& worldView) const override; - - std::string toString() const override { return "StatUpdateAction"; } - }; -} - -#endif // MWLUA_ACTIONS_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index e2037049d8..692b089bdb 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -501,4 +501,50 @@ namespace MWLua scripts->receiveEngineEvent(LocalScripts::OnActive()); } + LuaManager::Action::Action(LuaUtil::LuaState* state) + { + static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); + if (luaDebug) + mCallerTraceback = state->debugTraceback(); + } + + void LuaManager::Action::safeApply(WorldView& w) const + { + try + { + apply(w); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); + + if (mCallerTraceback.empty()) + Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; + else + Log(Debug::Error) << "Caller " << mCallerTraceback; + } + } + + namespace + { + class FunctionAction final : public LuaManager::Action + { + public: + FunctionAction(LuaUtil::LuaState* state, std::function fn, std::string_view name) + : Action(state), mFn(std::move(fn)), mName(name) {} + + void apply(WorldView&) const override { mFn(); } + std::string toString() const override { return "FunctionAction " + mName; } + + private: + std::function mFn; + std::string mName; + }; + } + + void LuaManager::addAction(std::function action, std::string_view name) + { + mActionQueue.push_back(std::make_unique(&mLua, std::move(action), name)); + } + } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index e6f5af78be..43f5d9c3b6 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -12,7 +12,6 @@ #include "../mwbase/luamanager.hpp" -#include "actions.hpp" #include "object.hpp" #include "eventqueue.hpp" #include "globalscripts.hpp" @@ -60,9 +59,28 @@ namespace MWLua // Used only in Lua bindings void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); - void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } - void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } + + // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), + // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited + // from MWLua::Action. + class Action + { + public: + Action(LuaUtil::LuaState* state); + virtual ~Action() {} + + void safeApply(WorldView&) const; + virtual void apply(WorldView&) const = 0; + virtual std::string toString() const = 0; + + private: + std::string mCallerTraceback; + }; + + void addAction(std::function action, std::string_view name = ""); + void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } + void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } // Saving void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; @@ -149,7 +167,7 @@ namespace MWLua // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). std::vector> mActionQueue; - std::unique_ptr mTeleportPlayerAction; + std::unique_ptr mTeleportPlayerAction; std::vector mUIMessages; LuaUtil::LuaStorage mGlobalStorage{mLua.sol()}; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 08435583df..0bf3fc99c5 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -2,9 +2,12 @@ #include +#include "../mwworld/action.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" #include "eventqueue.hpp" #include "luamanagerimp.hpp" @@ -31,6 +34,83 @@ namespace MWLua namespace { + class TeleportAction final : public LuaManager::Action + { + public: + TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) + : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} + + void apply(WorldView& worldView) const override + { + MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); + if (!cell) + throw std::runtime_error(std::string("cell not found: '") + mCell + "'"); + + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); + const MWWorld::Class& cls = obj.getClass(); + bool isPlayer = obj == world->getPlayerPtr(); + if (cls.isActor()) + cls.getCreatureStats(obj).land(isPlayer); + if (isPlayer) + { + ESM::Position esmPos; + static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); + std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); + std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); + world->getPlayer().setTeleported(true); + if (cell->isExterior()) + world->changeToExteriorCell(esmPos, true); + else + world->changeToInteriorCell(mCell, esmPos, true); + } + else + { + MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); + world->rotateObject(newObj, mRot); + } + } + + std::string toString() const override { return "TeleportAction"; } + + private: + ObjectId mObject; + std::string mCell; + osg::Vec3f mPos; + osg::Vec3f mRot; + }; + + class ActivateAction final : public LuaManager::Action + { + public: + ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) + : Action(state), mObject(object), mActor(actor) {} + + void apply(WorldView& worldView) const override + { + MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true); + if (object.isEmpty()) + throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); + MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true); + if (actor.isEmpty()) + throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); + + MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); + std::unique_ptr action = object.getClass().activate(object, actor); + action->execute(actor); + } + + std::string toString() const override + { + return std::string("ActivateAction object=") + idToString(mObject) + + std::string(" actor=") + idToString(mActor); + } + + private: + ObjectId mObject; + ObjectId mActor; + }; + template using Cell = std::conditional_t, LCell, GCell>; diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 36d7cf6538..f81293513d 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -79,6 +79,26 @@ namespace namespace MWLua { + namespace + { + class StatUpdateAction final : public LuaManager::Action + { + ObjectId mId; + public: + StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} + + void apply(WorldView& worldView) const override + { + LObject obj(mId, worldView.getObjectRegistry()); + LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + if (scripts) + scripts->applyStatsCache(); + } + + std::string toString() const override { return "StatUpdateAction"; } + }; + } + class LevelStat { StatObject mObject; diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 335ba517d5..98ff5148d5 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -6,7 +6,6 @@ #include #include -#include "../actions.hpp" #include "../luabindings.hpp" #include "../localscripts.hpp" #include "../luamanagerimp.hpp" @@ -14,6 +13,102 @@ namespace MWLua { + namespace + { + class SetEquipmentAction final : public LuaManager::Action + { + public: + using Item = std::variant; // recordId or ObjectId + using Equipment = std::map; // slot to item + + SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment) + : Action(state), mActor(actor), mEquipment(std::move(equipment)) {} + + void apply(WorldView& worldView) const override + { + MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false); + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + std::array usedSlots; + std::fill(usedSlots.begin(), usedSlots.end(), false); + + static constexpr int anySlot = -1; + auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool + { + auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); + MWWorld::Ptr itemPtr; + if (std::holds_alternative(item)) + { + itemPtr = worldView.getObjectRegistry()->getPtr(std::get(item), false); + if (old_it != store.end() && *old_it == itemPtr) + return true; // already equipped + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 || + itemPtr.getContainerStore() != static_cast(&store)) + { + Log(Debug::Warning) << "Object" << idToString(std::get(item)) << " is not in inventory"; + return false; + } + } + else + { + const std::string& recordId = std::get(item); + if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) + return true; // already equipped + itemPtr = store.search(recordId); + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) + { + Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; + return false; + } + } + + auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); + bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end(); + if (!requestedSlotIsAllowed) + { + auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); + if (firstAllowed == allowedSlots.end()) + { + Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); + return false; + } + slot = *firstAllowed; + } + + // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. + MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); + if (it == store.end()) // should never happen + throw std::logic_error("Item not found in container"); + + store.equip(slot, it, actor); + return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed + }; + + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + auto old_it = store.getSlot(slot); + auto new_it = mEquipment.find(slot); + if (new_it == mEquipment.end()) + { + if (old_it != store.end()) + store.unequipSlot(slot, actor); + continue; + } + if (tryEquipToSlot(slot, new_it->second)) + usedSlots[slot] = true; + } + for (const auto& [slot, item] : mEquipment) + if (slot >= MWWorld::InventoryStore::Slots) + tryEquipToSlot(anySlot, item); + } + + std::string toString() const override { return "SetEquipmentAction"; } + + private: + ObjectId mActor; + Equipment mEquipment; + }; + } + using SelfObject = LocalScripts::SelfObject; void addActorBindings(sol::table actor, const Context& context) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index fdad57b13d..f82dd3db00 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -10,14 +10,13 @@ #include #include "context.hpp" -#include "actions.hpp" #include "luamanagerimp.hpp" namespace MWLua { namespace { - class UiAction final : public Action + class UiAction final : public LuaManager::Action { public: enum Type @@ -81,7 +80,7 @@ namespace MWLua std::shared_ptr mElement; }; - class InsertLayerAction final : public Action + class InsertLayerAction final : public LuaManager::Action { public: InsertLayerAction(std::string_view name, size_t index, From 51845e95536bdb29c86569c3c6481c06446215d4 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 18 Mar 2022 00:27:16 +0100 Subject: [PATCH 2299/2859] Rendering raycasts in Lua --- apps/openmw/mwbase/world.hpp | 4 ++++ apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 2 ++ apps/openmw/mwlua/luamanagerimp.hpp | 3 +++ apps/openmw/mwlua/nearbybindings.cpp | 22 ++++++++++++++++++++++ apps/openmw/mwphysics/raycasting.hpp | 11 ++++++----- apps/openmw/mwworld/worldimp.cpp | 19 +++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 3 +++ files/lua_api/openmw/nearby.lua | 17 +++++++++++++++++ 9 files changed, 77 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 625758d640..f90794fc52 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -57,6 +57,7 @@ namespace ESM namespace MWPhysics { + class RayCastingResult; class RayCastingInterface; } @@ -331,6 +332,9 @@ namespace MWBase virtual bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) = 0; + virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) = 0; + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index c88045ae84..3c2023ff5f 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -41,7 +41,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 20; + api["API_REVISION"] = 21; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 692b089bdb..aa2d5baa6f 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -226,6 +226,7 @@ namespace MWLua return; // The game is not started yet. // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. + mProcessingInputEvents = true; PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) { @@ -235,6 +236,7 @@ namespace MWLua mInputEvents.clear(); if (playerScripts && !mWorldView.isPaused()) playerScripts->inputUpdate(MWBase::Environment::get().getFrameDuration()); + mProcessingInputEvents = false; MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); for (const std::string& message : mUIMessages) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 43f5d9c3b6..fd9ec8b172 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -111,12 +111,15 @@ namespace MWLua LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } + bool isProcessingInputEvents() const { return mProcessingInputEvents; } + private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); bool mInitialized = false; bool mGlobalScriptsStarted = false; + bool mProcessingInputEvents = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; LuaUi::ResourceManager mUiResourceManager; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 05bc52c5cc..6ce78e569f 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -6,6 +6,7 @@ #include "../mwbase/world.hpp" #include "../mwphysics/raycasting.hpp" +#include "luamanagerimp.hpp" #include "worldview.hpp" namespace sol @@ -91,6 +92,27 @@ namespace MWLua // and use this callback from the main thread at the beginning of the next frame processing. rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); };*/ + api["castRenderingRay"] = [manager=context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to) + { + if (!manager->isProcessingInputEvents()) + { + throw std::logic_error("castRenderingRay can be used only in player scripts during processing of input events; " + "use asyncCastRenderingRay instead."); + } + MWPhysics::RayCastingResult res; + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + return res; + }; + api["asyncCastRenderingRay"] = + [manager=context.mLuaManager](const LuaUtil::Callback& callback, const osg::Vec3f& from, const osg::Vec3f& to) + { + manager->addAction([manager, callback, from, to] + { + MWPhysics::RayCastingResult res; + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + manager->queueCallback(callback, sol::make_object(callback.mFunc.lua_state(), res)); + }); + }; api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index d00f23e2c4..848f17a01a 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -9,12 +9,13 @@ namespace MWPhysics { - struct RayCastingResult + class RayCastingResult { - bool mHit; - osg::Vec3f mHitPos; - osg::Vec3f mHitNormal; - MWWorld::Ptr mHitObject; + public: + bool mHit; + osg::Vec3f mHitPos; + osg::Vec3f mHitNormal; + MWWorld::Ptr mHitObject; }; class RayCastingInterface diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7f652d9520..4d3f4aa7ec 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2042,6 +2042,25 @@ namespace MWWorld return facedObject; } + bool World::castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) + { + MWRender::RenderingManager::RayResult rayRes = mRendering->castRay(from, to, ignorePlayer, ignoreActors); + res.mHit = rayRes.mHit; + res.mHitPos = rayRes.mHitPointWorld; + res.mHitNormal = rayRes.mHitNormalWorld; + res.mHitObject = rayRes.mHitObject; + if (res.mHitObject.isEmpty() && rayRes.mHitRefnum.isSet()) + { + for (CellStore* cellstore : mWorldScene->getActiveCells()) + { + res.mHitObject = cellstore->searchViaRefNum(rayRes.mHitRefnum); + if (!res.mHitObject.isEmpty()) break; + } + } + return res.mHit; + } + bool World::isCellExterior() const { const CellStore *currentCell = mWorldScene->getCurrentCell(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 399b7232de..8cf83eb3aa 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -421,6 +421,9 @@ namespace MWWorld bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) override; + bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) override; + void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 3ba3d7e6fb..ba27269700 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -68,5 +68,22 @@ -- radius = 10, -- }) +--- +-- Cast ray from one point to another and find the first visual intersection with anything in the scene. +-- As opposite to `castRay` can find an intersection with an object without collisions. +-- In order to avoid threading issues can be used only in player scripts only in `onInputUpdate` or +-- in engine handlers for user input. In other cases use `asyncCastRenderingRay` instead. +-- @function [parent=#nearby] castRenderingRay +-- @param openmw.util#Vector3 from Start point of the ray. +-- @param openmw.util#Vector3 to End point of the ray. +-- @return #RayCastingResult + +--- +-- Asynchronously cast ray from one point to another and find the first visual intersection with anything in the scene. +-- @function [parent=#nearby] asyncCastRenderingRay +-- @param openmw.async#Callback callback The callback to pass the result to (should accept a single argument @{openmw.nearby#RayCastingResult}). +-- @param openmw.util#Vector3 from Start point of the ray. +-- @param openmw.util#Vector3 to End point of the ray. + return nil From 4447ab0ed75b6d0ee39d3769c68baf52213d1d4e Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 12 Apr 2022 00:18:39 +0200 Subject: [PATCH 2300/2859] Remove ESM:: namespace qualifier in components/esm3/ and tests --- apps/openmw_test_suite/esm/variant.cpp | 6 +- components/esm3/activespells.cpp | 24 ++-- components/esm3/aipackage.cpp | 2 +- components/esm3/aisequence.hpp | 18 +-- components/esm3/cellid.cpp | 17 ++- components/esm3/cellref.cpp | 55 ++++----- components/esm3/cellref.hpp | 4 +- components/esm3/cellstate.cpp | 9 +- components/esm3/cellstate.hpp | 2 +- components/esm3/containerstate.cpp | 9 +- components/esm3/controlsstate.cpp | 11 +- components/esm3/creaturestate.cpp | 11 +- components/esm3/creaturestats.cpp | 11 +- components/esm3/creaturestats.hpp | 4 +- components/esm3/custommarkerstate.cpp | 4 +- components/esm3/custommarkerstate.hpp | 6 +- components/esm3/debugprofile.cpp | 23 ++-- components/esm3/dialoguestate.cpp | 9 +- components/esm3/effectlist.cpp | 3 +- components/esm3/esmreader.hpp | 3 +- components/esm3/esmwriter.cpp | 18 +-- components/esm3/esmwriter.hpp | 37 +++--- components/esm3/filter.cpp | 21 ++-- components/esm3/fogstate.cpp | 12 +- components/esm3/globalmap.cpp | 11 +- components/esm3/globalscript.cpp | 9 +- components/esm3/inventorystate.cpp | 9 +- components/esm3/journalentry.cpp | 9 +- components/esm3/loadacti.cpp | 10 +- components/esm3/loadalch.cpp | 16 +-- components/esm3/loadappa.cpp | 14 +-- components/esm3/loadarmo.cpp | 18 +-- components/esm3/loadbody.cpp | 10 +- components/esm3/loadbook.cpp | 18 +-- components/esm3/loadbsgn.cpp | 12 +- components/esm3/loadcell.cpp | 25 +++-- components/esm3/loadclas.cpp | 10 +- components/esm3/loadclot.cpp | 18 +-- components/esm3/loadcont.cpp | 16 +-- components/esm3/loadcrea.cpp | 33 +++--- components/esm3/loaddial.cpp | 6 +- components/esm3/loaddial.hpp | 2 +- components/esm3/loaddoor.cpp | 14 +-- components/esm3/loadench.cpp | 8 +- components/esm3/loadfact.cpp | 12 +- components/esm3/loadfact.hpp | 2 +- components/esm3/loadglob.cpp | 6 +- components/esm3/loadgmst.cpp | 6 +- components/esm3/loadinfo.cpp | 30 ++--- components/esm3/loadingr.cpp | 14 +-- components/esm3/loadland.cpp | 24 ++-- components/esm3/loadland.hpp | 4 +- components/esm3/loadlevlist.cpp | 14 +-- components/esm3/loadlevlist.hpp | 8 +- components/esm3/loadligh.cpp | 16 +-- components/esm3/loadlock.cpp | 14 +-- components/esm3/loadltex.cpp | 8 +- components/esm3/loadmgef.cpp | 27 +++-- components/esm3/loadmgef.hpp | 4 +- components/esm3/loadmisc.cpp | 14 +-- components/esm3/loadnpc.cpp | 34 +++--- components/esm3/loadnpc.hpp | 3 +- components/esm3/loadpgrd.cpp | 10 +- components/esm3/loadprob.cpp | 14 +-- components/esm3/loadrace.cpp | 12 +- components/esm3/loadregn.cpp | 14 +-- components/esm3/loadrepa.cpp | 14 +-- components/esm3/loadscpt.cpp | 10 +- components/esm3/loadskil.cpp | 6 +- components/esm3/loadskil.hpp | 3 +- components/esm3/loadsndg.cpp | 10 +- components/esm3/loadsoun.cpp | 8 +- components/esm3/loadspel.cpp | 10 +- components/esm3/loadsscr.cpp | 6 +- components/esm3/loadstat.cpp | 10 +- components/esm3/loadstat.hpp | 3 +- components/esm3/loadtes3.cpp | 13 ++- components/esm3/loadweap.cpp | 16 +-- components/esm3/loadweap.hpp | 2 +- components/esm3/locals.cpp | 9 +- components/esm3/mappings.cpp | 150 ++++++++++++------------- components/esm3/mappings.hpp | 6 +- components/esm3/npcstate.cpp | 11 +- components/esm3/npcstats.cpp | 21 ++-- components/esm3/objectstate.cpp | 33 +++--- components/esm3/objectstate.hpp | 4 +- components/esm3/player.cpp | 15 ++- components/esm3/player.hpp | 6 +- components/esm3/projectilestate.cpp | 2 +- components/esm3/queststate.cpp | 9 +- components/esm3/savedgame.cpp | 13 ++- components/esm3/spelllist.cpp | 3 +- components/esm3/statstate.cpp | 6 +- components/esm3/stolenitems.hpp | 4 +- components/esm3/transport.cpp | 4 +- components/esm3/variant.cpp | 36 +++--- components/esm3/variantimp.cpp | 17 ++- components/esm3/weatherstate.cpp | 25 +++-- 98 files changed, 752 insertions(+), 610 deletions(-) diff --git a/apps/openmw_test_suite/esm/variant.cpp b/apps/openmw_test_suite/esm/variant.cpp index 6991a8b4a8..287540c1e0 100644 --- a/apps/openmw_test_suite/esm/variant.cpp +++ b/apps/openmw_test_suite/esm/variant.cpp @@ -425,7 +425,7 @@ namespace std::string write(const Variant& variant, const Variant::Format format) { std::ostringstream out; - ESM::ESMWriter writer; + ESMWriter writer; writer.save(out); variant.write(writer, format); writer.close(); @@ -435,7 +435,7 @@ namespace Variant read(const Variant::Format format, const std::string& data) { Variant result; - ESM::ESMReader reader; + ESMReader reader; reader.open(std::make_shared(data), ""); result.read(reader, format); return result; @@ -490,7 +490,7 @@ namespace { const auto param = GetParam(); std::ostringstream out; - ESM::ESMWriter writer; + ESMWriter writer; writer.save(out); ASSERT_THROW(param.mVariant.write(writer, param.mFormat), std::runtime_error); } diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 61be6a7838..014c5c2e77 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -3,9 +3,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +namespace ESM +{ namespace { - void save(ESM::ESMWriter& esm, const std::vector& spells, ESM::NAME tag) + void saveImpl(ESMWriter& esm, const std::vector& spells, NAME tag) { for (const auto& params : spells) { @@ -38,19 +40,19 @@ namespace } } - void load(ESM::ESMReader& esm, std::vector& spells, ESM::NAME tag) + void loadImpl(ESMReader& esm, std::vector& spells, NAME tag) { int format = esm.getFormat(); while (esm.isNextSub(tag)) { - ESM::ActiveSpells::ActiveSpellParams params; + ActiveSpells::ActiveSpellParams params; params.mId = esm.getHString(); esm.getHNT (params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString ("DISP"); params.mItem.unset(); if (format < 17) - params.mType = ESM::ActiveSpells::Type_Temporary; + params.mType = ActiveSpells::Type_Temporary; else { esm.getHNT (params.mType, "TYPE"); @@ -71,7 +73,7 @@ namespace while (esm.isNextSub("MGEF")) { - ESM::ActiveEffect effect; + ActiveEffect effect; esm.getHT(effect.mEffectId); effect.mArg = -1; esm.getHNOT(effect.mArg, "ARG_"); @@ -94,7 +96,7 @@ namespace else esm.getHNT (effect.mTimeLeft, "LEFT"); if (format < 17) - effect.mFlags = ESM::ActiveEffect::Flag_None; + effect.mFlags = ActiveEffect::Flag_None; else esm.getHNT (effect.mFlags, "FLAG"); @@ -104,19 +106,19 @@ namespace } } } +} namespace ESM { - void ActiveSpells::save(ESMWriter &esm) const { - ::save(esm, mSpells, "ID__"); - ::save(esm, mQueue, "QID_"); + saveImpl(esm, mSpells, "ID__"); + saveImpl(esm, mQueue, "QID_"); } void ActiveSpells::load(ESMReader &esm) { - ::load(esm, mSpells, "ID__"); - ::load(esm, mQueue, "QID_"); + loadImpl(esm, mSpells, "ID__"); + loadImpl(esm, mQueue, "QID_"); } } diff --git a/components/esm3/aipackage.cpp b/components/esm3/aipackage.cpp index 5a95e58ca8..55ee64db9c 100644 --- a/components/esm3/aipackage.cpp +++ b/components/esm3/aipackage.cpp @@ -65,7 +65,7 @@ namespace ESM case AI_Escort: case AI_Follow: { - const ESM::NAME name = (it->mType == AI_Escort) ? ESM::NAME("AI_E") : ESM::NAME("AI_F"); + const NAME name = (it->mType == AI_Escort) ? NAME("AI_E") : NAME("AI_F"); esm.writeHNT(name, it->mTarget, sizeof(it->mTarget)); esm.writeHNOCString("CNDT", it->mCellName); break; diff --git a/components/esm3/aisequence.hpp b/components/esm3/aisequence.hpp index 412c7401bf..f71771a4d5 100644 --- a/components/esm3/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -22,13 +22,13 @@ namespace ESM enum AiPackages { - Ai_Wander = ESM::fourCC("WAND"), - Ai_Travel = ESM::fourCC("TRAV"), - Ai_Escort = ESM::fourCC("ESCO"), - Ai_Follow = ESM::fourCC("FOLL"), - Ai_Activate = ESM::fourCC("ACTI"), - Ai_Combat = ESM::fourCC("COMB"), - Ai_Pursue = ESM::fourCC("PURS") + Ai_Wander = fourCC("WAND"), + Ai_Travel = fourCC("TRAV"), + Ai_Escort = fourCC("ESCO"), + Ai_Follow = fourCC("FOLL"), + Ai_Activate = fourCC("ACTI"), + Ai_Combat = fourCC("COMB"), + Ai_Pursue = fourCC("PURS") }; @@ -67,10 +67,10 @@ namespace ESM struct AiWander : AiPackage { AiWanderData mData; - AiWanderDuration mDurationData; // was ESM::TimeStamp mStartTime + AiWanderDuration mDurationData; // was TimeStamp mStartTime bool mStoredInitialActorPosition; - ESM::Vector3 mInitialActorPosition; + Vector3 mInitialActorPosition; /// \todo add more AiWander state diff --git a/components/esm3/cellid.cpp b/components/esm3/cellid.cpp index 87ad0cc21c..154ef53056 100644 --- a/components/esm3/cellid.cpp +++ b/components/esm3/cellid.cpp @@ -3,9 +3,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -const std::string ESM::CellId::sDefaultWorldspace = "sys::default"; +namespace ESM +{ + +const std::string CellId::sDefaultWorldspace = "sys::default"; -void ESM::CellId::load (ESMReader &esm) +void CellId::load (ESMReader &esm) { mWorldspace = esm.getHNString ("SPAC"); @@ -18,7 +21,7 @@ void ESM::CellId::load (ESMReader &esm) mPaged = false; } -void ESM::CellId::save (ESMWriter &esm) const +void CellId::save (ESMWriter &esm) const { esm.writeHNString ("SPAC", mWorldspace); @@ -26,18 +29,18 @@ void ESM::CellId::save (ESMWriter &esm) const esm.writeHNT ("CIDX", mIndex, 8); } -bool ESM::operator== (const CellId& left, const CellId& right) +bool operator== (const CellId& left, const CellId& right) { return left.mWorldspace==right.mWorldspace && left.mPaged==right.mPaged && (!left.mPaged || (left.mIndex.mX==right.mIndex.mX && left.mIndex.mY==right.mIndex.mY)); } -bool ESM::operator!= (const CellId& left, const CellId& right) +bool operator!= (const CellId& left, const CellId& right) { return !(left==right); } -bool ESM::operator < (const CellId& left, const CellId& right) +bool operator < (const CellId& left, const CellId& right) { if (left.mPaged < right.mPaged) return true; @@ -59,3 +62,5 @@ bool ESM::operator < (const CellId& left, const CellId& right) return left.mWorldspace < right.mWorldspace; } + +} diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index d5ed0b4003..770680b8e2 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -63,67 +63,67 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("UNAM"): + case fourCC("UNAM"): getHTOrSkip(cellRef.mReferenceBlocked); break; - case ESM::fourCC("XSCL"): + case fourCC("XSCL"): getHTOrSkip(cellRef.mScale); if constexpr (load) cellRef.mScale = std::clamp(cellRef.mScale, 0.5f, 2.0f); break; - case ESM::fourCC("ANAM"): + case fourCC("ANAM"): getHStringOrSkip(cellRef.mOwner); break; - case ESM::fourCC("BNAM"): + case fourCC("BNAM"): getHStringOrSkip(cellRef.mGlobalVariable); break; - case ESM::fourCC("XSOL"): + case fourCC("XSOL"): getHStringOrSkip(cellRef.mSoul); break; - case ESM::fourCC("CNAM"): + case fourCC("CNAM"): getHStringOrSkip(cellRef.mFaction); break; - case ESM::fourCC("INDX"): + case fourCC("INDX"): getHTOrSkip(cellRef.mFactionRank); break; - case ESM::fourCC("XCHG"): + case fourCC("XCHG"): getHTOrSkip(cellRef.mEnchantmentCharge); break; - case ESM::fourCC("INTV"): + case fourCC("INTV"): getHTOrSkip(cellRef.mChargeInt); break; - case ESM::fourCC("NAM9"): + case fourCC("NAM9"): getHTOrSkip(cellRef.mGoldValue); break; - case ESM::fourCC("DODT"): + case fourCC("DODT"): getHTOrSkip(cellRef.mDoorDest); if constexpr (load) cellRef.mTeleport = true; break; - case ESM::fourCC("DNAM"): + case fourCC("DNAM"): getHStringOrSkip(cellRef.mDestCell); break; - case ESM::fourCC("FLTV"): + case fourCC("FLTV"): getHTOrSkip(cellRef.mLockLevel); break; - case ESM::fourCC("KNAM"): + case fourCC("KNAM"): getHStringOrSkip(cellRef.mKey); break; - case ESM::fourCC("TNAM"): + case fourCC("TNAM"): getHStringOrSkip(cellRef.mTrap); break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): if constexpr (load) esm.getHTSized<24>(cellRef.mPos); else esm.skipHTSized<24, decltype(cellRef.mPos)>(); break; - case ESM::fourCC("NAM0"): + case fourCC("NAM0"): { esm.skipHSub(); break; } - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); if constexpr (load) isDeleted = true; @@ -145,9 +145,8 @@ namespace ESM } } } -} -void ESM::RefNum::load(ESMReader& esm, bool wide, ESM::NAME tag) +void RefNum::load(ESMReader& esm, bool wide, NAME tag) { if (wide) esm.getHNTSized<8>(*this, tag); @@ -155,7 +154,7 @@ void ESM::RefNum::load(ESMReader& esm, bool wide, ESM::NAME tag) esm.getHNT(mIndex, tag); } -void ESM::RefNum::save(ESMWriter &esm, bool wide, ESM::NAME tag) const +void RefNum::save(ESMWriter &esm, bool wide, NAME tag) const { if (wide) esm.writeHNT (tag, *this, 8); @@ -168,23 +167,23 @@ void ESM::RefNum::save(ESMWriter &esm, bool wide, ESM::NAME tag) const } } -void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) +void CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); loadData(esm, isDeleted); } -void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) +void CellRef::loadId (ESMReader& esm, bool wideRefNum) { loadIdImpl(esm, wideRefNum, *this); } -void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) +void CellRef::loadData(ESMReader &esm, bool &isDeleted) { loadDataImpl(esm, isDeleted, *this); } -void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const +void CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const { mRefNum.save (esm, wideRefNum); @@ -246,7 +245,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool esm.writeHNT("DATA", mPos, 24); } -void ESM::CellRef::blank() +void CellRef::blank() { mRefNum.unset(); mRefID.clear(); @@ -276,10 +275,12 @@ void ESM::CellRef::blank() } } -void ESM::skipLoadCellRef(ESMReader& esm, bool wideRefNum) +void skipLoadCellRef(ESMReader& esm, bool wideRefNum) { CellRef cellRef; loadIdImpl(esm, wideRefNum, cellRef); bool isDeleted; loadDataImpl(esm, isDeleted, cellRef); } + +} diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 07d4e0c80a..f710167afc 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -19,9 +19,9 @@ namespace ESM unsigned int mIndex; int mContentFile; - void load(ESMReader& esm, bool wide = false, ESM::NAME tag = "FRMR"); + void load(ESMReader& esm, bool wide = false, NAME tag = "FRMR"); - void save(ESMWriter &esm, bool wide = false, ESM::NAME tag = "FRMR") const; + void save(ESMWriter &esm, bool wide = false, NAME tag = "FRMR") const; inline bool hasContentFile() const { return mContentFile >= 0; } diff --git a/components/esm3/cellstate.cpp b/components/esm3/cellstate.cpp index 83b130dcd9..d5773d5f26 100644 --- a/components/esm3/cellstate.cpp +++ b/components/esm3/cellstate.cpp @@ -3,7 +3,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::CellState::load (ESMReader &esm) +namespace ESM +{ + +void CellState::load (ESMReader &esm) { mWaterLevel = 0; esm.getHNOT (mWaterLevel, "WLVL"); @@ -16,7 +19,7 @@ void ESM::CellState::load (ESMReader &esm) esm.getHNOT (mLastRespawn, "RESP"); } -void ESM::CellState::save (ESMWriter &esm) const +void CellState::save (ESMWriter &esm) const { if (!mId.mPaged) esm.writeHNT ("WLVL", mWaterLevel); @@ -25,3 +28,5 @@ void ESM::CellState::save (ESMWriter &esm) const esm.writeHNT ("RESP", mLastRespawn); } + +} diff --git a/components/esm3/cellstate.hpp b/components/esm3/cellstate.hpp index 9c0427f76b..bf332ae5e8 100644 --- a/components/esm3/cellstate.hpp +++ b/components/esm3/cellstate.hpp @@ -21,7 +21,7 @@ namespace ESM int mHasFogOfWar; // Do we have fog of war state (0 or 1)? (see fogstate.hpp) - ESM::TimeStamp mLastRespawn; + TimeStamp mLastRespawn; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm3/containerstate.cpp b/components/esm3/containerstate.cpp index 301549d597..b54736cb8b 100644 --- a/components/esm3/containerstate.cpp +++ b/components/esm3/containerstate.cpp @@ -1,15 +1,20 @@ #include "containerstate.hpp" -void ESM::ContainerState::load (ESMReader &esm) +namespace ESM +{ + +void ContainerState::load (ESMReader &esm) { ObjectState::load (esm); mInventory.load (esm); } -void ESM::ContainerState::save (ESMWriter &esm, bool inInventory) const +void ContainerState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); mInventory.save (esm); } + +} diff --git a/components/esm3/controlsstate.cpp b/components/esm3/controlsstate.cpp index ae4e1dff16..c985c23f15 100644 --- a/components/esm3/controlsstate.cpp +++ b/components/esm3/controlsstate.cpp @@ -3,7 +3,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -ESM::ControlsState::ControlsState() +namespace ESM +{ + +ControlsState::ControlsState() : mViewSwitchDisabled(false), mControlsDisabled(false), mJumpingDisabled(false), @@ -14,7 +17,7 @@ ESM::ControlsState::ControlsState() { } -void ESM::ControlsState::load(ESM::ESMReader& esm) +void ControlsState::load(ESMReader& esm) { int flags; esm.getHNT(flags, "CFLG"); @@ -28,7 +31,7 @@ void ESM::ControlsState::load(ESM::ESMReader& esm) mSpellDrawingDisabled = flags & SpellDrawingDisabled; } -void ESM::ControlsState::save(ESM::ESMWriter& esm) const +void ControlsState::save(ESMWriter& esm) const { int flags = 0; if (mViewSwitchDisabled) flags |= ViewSwitchDisabled; @@ -41,3 +44,5 @@ void ESM::ControlsState::save(ESM::ESMWriter& esm) const esm.writeHNT("CFLG", flags); } + +} diff --git a/components/esm3/creaturestate.cpp b/components/esm3/creaturestate.cpp index bffa4e5e45..e9a9f52cf1 100644 --- a/components/esm3/creaturestate.cpp +++ b/components/esm3/creaturestate.cpp @@ -1,6 +1,9 @@ #include "creaturestate.hpp" -void ESM::CreatureState::load (ESMReader &esm) +namespace ESM +{ + +void CreatureState::load (ESMReader &esm) { ObjectState::load (esm); @@ -12,7 +15,7 @@ void ESM::CreatureState::load (ESMReader &esm) } } -void ESM::CreatureState::save (ESMWriter &esm, bool inInventory) const +void CreatureState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); @@ -24,8 +27,10 @@ void ESM::CreatureState::save (ESMWriter &esm, bool inInventory) const } } -void ESM::CreatureState::blank() +void CreatureState::blank() { ObjectState::blank(); mCreatureStats.blank(); } + +} diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index 0f404ba58b..1a1eabd2ab 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -4,7 +4,10 @@ #include -void ESM::CreatureStats::load (ESMReader &esm) +namespace ESM +{ + +void CreatureStats::load (ESMReader &esm) { bool intFallback = esm.getFormat() < 11; for (int i=0; i<8; ++i) @@ -174,7 +177,7 @@ void ESM::CreatureStats::load (ESMReader &esm) } } -void ESM::CreatureStats::save (ESMWriter &esm) const +void CreatureStats::save (ESMWriter &esm) const { for (int i=0; i<8; ++i) mAttributes[i].save (esm); @@ -259,7 +262,7 @@ void ESM::CreatureStats::save (ESMWriter &esm) const esm.writeHNT("NOAC", mMissingACDT); } -void ESM::CreatureStats::blank() +void CreatureStats::blank() { mTradeTime.mHour = 0; mTradeTime.mDay = 0; @@ -287,3 +290,5 @@ void ESM::CreatureStats::blank() mCorprusSpells.clear(); mMissingACDT = false; } + +} diff --git a/components/esm3/creaturestats.hpp b/components/esm3/creaturestats.hpp index d47a283630..7ad168b12f 100644 --- a/components/esm3/creaturestats.hpp +++ b/components/esm3/creaturestats.hpp @@ -43,7 +43,7 @@ namespace ESM std::multimap mSummonedCreatures; std::vector mSummonGraveyard; - ESM::TimeStamp mTradeTime; + TimeStamp mTradeTime; int mGoldPool; int mActorId; //int mHitAttemptActorId; @@ -83,7 +83,7 @@ namespace ESM bool mRecalcDynamicStats; int mDrawState; signed char mDeathAnimation; - ESM::TimeStamp mTimeOfDeath; + TimeStamp mTimeOfDeath; int mLevel; bool mMissingACDT; diff --git a/components/esm3/custommarkerstate.cpp b/components/esm3/custommarkerstate.cpp index dc81c123d4..7752cc146f 100644 --- a/components/esm3/custommarkerstate.cpp +++ b/components/esm3/custommarkerstate.cpp @@ -6,7 +6,7 @@ namespace ESM { -void CustomMarker::save(ESM::ESMWriter &esm) const +void CustomMarker::save(ESMWriter &esm) const { esm.writeHNT("POSX", mWorldX); esm.writeHNT("POSY", mWorldY); @@ -15,7 +15,7 @@ void CustomMarker::save(ESM::ESMWriter &esm) const esm.writeHNString("NOTE", mNote); } -void CustomMarker::load(ESM::ESMReader &esm) +void CustomMarker::load(ESMReader &esm) { esm.getHNT(mWorldX, "POSX"); esm.getHNT(mWorldY, "POSY"); diff --git a/components/esm3/custommarkerstate.hpp b/components/esm3/custommarkerstate.hpp index 2be43c53bf..0b527c0a92 100644 --- a/components/esm3/custommarkerstate.hpp +++ b/components/esm3/custommarkerstate.hpp @@ -12,7 +12,7 @@ struct CustomMarker float mWorldX; float mWorldY; - ESM::CellId mCell; + CellId mCell; std::string mNote; @@ -21,8 +21,8 @@ struct CustomMarker return mNote == other.mNote && mCell == other.mCell && mWorldX == other.mWorldX && mWorldY == other.mWorldY; } - void load (ESM::ESMReader& reader); - void save (ESM::ESMWriter& writer) const; + void load (ESMReader& reader); + void save (ESMWriter& writer) const; }; } diff --git a/components/esm3/debugprofile.cpp b/components/esm3/debugprofile.cpp index 87c3bd13ba..cdbd47bce4 100644 --- a/components/esm3/debugprofile.cpp +++ b/components/esm3/debugprofile.cpp @@ -4,9 +4,12 @@ #include "esmwriter.hpp" #include "components/esm/defs.hpp" -unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; +namespace ESM +{ + +unsigned int DebugProfile::sRecordId = REC_DBGP; -void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) +void DebugProfile::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); @@ -16,19 +19,19 @@ void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); break; - case ESM::fourCC("DESC"): + case fourCC("DESC"): mDescription = esm.getHString(); break; - case ESM::fourCC("SCRP"): + case fourCC("SCRP"): mScriptText = esm.getHString(); break; - case ESM::fourCC("FLAG"): + case fourCC("FLAG"): esm.getHT(mFlags); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; @@ -39,7 +42,7 @@ void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) } } -void ESM::DebugProfile::save (ESMWriter& esm, bool isDeleted) const +void DebugProfile::save (ESMWriter& esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); @@ -54,9 +57,11 @@ void ESM::DebugProfile::save (ESMWriter& esm, bool isDeleted) const esm.writeHNT ("FLAG", mFlags); } -void ESM::DebugProfile::blank() +void DebugProfile::blank() { mDescription.clear(); mScriptText.clear(); mFlags = 0; } + +} diff --git a/components/esm3/dialoguestate.cpp b/components/esm3/dialoguestate.cpp index 2b1887e4eb..bdae1dbf4c 100644 --- a/components/esm3/dialoguestate.cpp +++ b/components/esm3/dialoguestate.cpp @@ -3,7 +3,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::DialogueState::load (ESMReader &esm) +namespace ESM +{ + +void DialogueState::load (ESMReader &esm) { while (esm.isNextSub ("TOPI")) mKnownTopics.push_back (esm.getHString()); @@ -30,7 +33,7 @@ void ESM::DialogueState::load (ESMReader &esm) } } -void ESM::DialogueState::save (ESMWriter &esm) const +void DialogueState::save (ESMWriter &esm) const { for (std::vector::const_iterator iter (mKnownTopics.begin()); iter!=mKnownTopics.end(); ++iter) @@ -51,3 +54,5 @@ void ESM::DialogueState::save (ESMWriter &esm) const } } } + +} diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 1cfb25618d..c578e4095a 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -3,7 +3,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -namespace ESM { +namespace ESM +{ void EffectList::load(ESMReader &esm) { diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index a91a1f1978..7994899db9 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -13,7 +13,8 @@ #include "components/esm/esmcommon.hpp" #include "loadtes3.hpp" -namespace ESM { +namespace ESM +{ class ESMReader { diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index 77ba5fc6aa..351de8612a 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -86,7 +86,7 @@ namespace ESM throw std::runtime_error ("Unclosed record remaining"); } - void ESMWriter::startRecord(ESM::NAME name, uint32_t flags) + void ESMWriter::startRecord(NAME name, uint32_t flags) { mRecordCount++; @@ -105,10 +105,10 @@ namespace ESM void ESMWriter::startRecord (uint32_t name, uint32_t flags) { - startRecord(ESM::NAME(name), flags); + startRecord(NAME(name), flags); } - void ESMWriter::startSubRecord(ESM::NAME name) + void ESMWriter::startSubRecord(NAME name) { // Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. assert (mRecords.size() <= 1); @@ -124,7 +124,7 @@ namespace ESM assert(mRecords.back().size == 0); } - void ESMWriter::endRecord(ESM::NAME name) + void ESMWriter::endRecord(NAME name) { RecordData rec = mRecords.back(); assert(rec.name == name); @@ -142,17 +142,17 @@ namespace ESM void ESMWriter::endRecord (uint32_t name) { - endRecord(ESM::NAME(name)); + endRecord(NAME(name)); } - void ESMWriter::writeHNString(ESM::NAME name, const std::string& data) + void ESMWriter::writeHNString(NAME name, const std::string& data) { startSubRecord(name); writeHString(data); endRecord(name); } - void ESMWriter::writeHNString(ESM::NAME name, const std::string& data, size_t size) + void ESMWriter::writeHNString(NAME name, const std::string& data, size_t size) { assert(data.size() <= size); startSubRecord(name); @@ -196,9 +196,9 @@ namespace ESM write("\0", 1); } - void ESMWriter::writeName(ESM::NAME name) + void ESMWriter::writeName(NAME name) { - write(name.mData, ESM::NAME::sCapacity); + write(name.mData, NAME::sCapacity); } void ESMWriter::write(const char* data, size_t size) diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 8ee507f39d..264dc1c877 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -13,13 +13,14 @@ namespace ToUTF8 class Utf8Encoder; } -namespace ESM { +namespace ESM +{ class ESMWriter { struct RecordData { - ESM::NAME name; + NAME name; std::streampos position; uint32_t size; }; @@ -30,7 +31,7 @@ class ESMWriter unsigned int getVersion() const; - // Set various header data (ESM::Header::Data). All of the below functions must be called before writing, + // Set various header data (Header::Data). All of the below functions must be called before writing, // otherwise this data will be left uninitialized. void setVersion(unsigned int ver = 0x3fa66666); void setType(int type); @@ -56,27 +57,27 @@ class ESMWriter void close(); ///< \note Does not close the stream. - void writeHNString(ESM::NAME name, const std::string& data); - void writeHNString(ESM::NAME name, const std::string& data, size_t size); - void writeHNCString(ESM::NAME name, const std::string& data) + 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) { startSubRecord(name); writeHCString(data); endRecord(name); } - void writeHNOString(ESM::NAME name, const std::string& data) + void writeHNOString(NAME name, const std::string& data) { if (!data.empty()) writeHNString(name, data); } - void writeHNOCString(ESM::NAME name, const std::string& data) + void writeHNOCString(NAME name, const std::string& data) { if (!data.empty()) writeHNCString(name, data); } template - void writeHNT(ESM::NAME name, const T& data) + void writeHNT(NAME name, const T& data) { startSubRecord(name); writeT(data); @@ -84,7 +85,7 @@ class ESMWriter } template - void writeHNT(ESM::NAME name, const T (&data)[size]) + void writeHNT(NAME name, const T (&data)[size]) { startSubRecord(name); writeT(data); @@ -94,15 +95,15 @@ class ESMWriter // Prevent using writeHNT with strings. This already happened by accident and results in // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. - void writeHNT(ESM::NAME name, const std::string& data) = delete; + void writeHNT(NAME name, const std::string& data) = delete; - void writeT(ESM::NAME data) = delete; + void writeT(NAME data) = delete; template - void writeHNT(ESM::NAME name, const T (&data)[size], int) = delete; + void writeHNT(NAME name, const T (&data)[size], int) = delete; template - void writeHNT(ESM::NAME name, const T& data, int size) + void writeHNT(NAME name, const T& data, int size) { startSubRecord(name); writeT(data, size); @@ -129,16 +130,16 @@ class ESMWriter write((char*)&data, size); } - void startRecord(ESM::NAME name, uint32_t flags = 0); + void startRecord(NAME name, uint32_t flags = 0); void startRecord(uint32_t name, uint32_t flags = 0); /// @note Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. - void startSubRecord(ESM::NAME name); - void endRecord(ESM::NAME name); + void startSubRecord(NAME name); + void endRecord(NAME name); void endRecord(uint32_t name); void writeFixedSizeString(const std::string& data, int size); void writeHString(const std::string& data); void writeHCString(const std::string& data); - void writeName(ESM::NAME data); + void writeName(NAME data); void write(const char* data, size_t size); private: diff --git a/components/esm3/filter.cpp b/components/esm3/filter.cpp index a031e0f608..d432f789d3 100644 --- a/components/esm3/filter.cpp +++ b/components/esm3/filter.cpp @@ -4,9 +4,12 @@ #include "esmwriter.hpp" #include "components/esm/defs.hpp" -unsigned int ESM::Filter::sRecordId = REC_FILT; +namespace ESM +{ + +unsigned int Filter::sRecordId = REC_FILT; -void ESM::Filter::load (ESMReader& esm, bool &isDeleted) +void Filter::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); @@ -17,16 +20,16 @@ void ESM::Filter::load (ESMReader& esm, bool &isDeleted) uint32_t name = esm.retSubName().toInt(); switch (name) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); break; - case ESM::fourCC("FILT"): + case fourCC("FILT"): mFilter = esm.getHString(); break; - case ESM::fourCC("DESC"): + case fourCC("DESC"): mDescription = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; @@ -37,7 +40,7 @@ void ESM::Filter::load (ESMReader& esm, bool &isDeleted) } } -void ESM::Filter::save (ESMWriter& esm, bool isDeleted) const +void Filter::save (ESMWriter& esm, bool isDeleted) const { esm.writeHNCString ("NAME", mId); @@ -51,8 +54,10 @@ void ESM::Filter::save (ESMWriter& esm, bool isDeleted) const esm.writeHNCString ("DESC", mDescription); } -void ESM::Filter::blank() +void Filter::blank() { mFilter.clear(); mDescription.clear(); } + +} diff --git a/components/esm3/fogstate.cpp b/components/esm3/fogstate.cpp index ff20f339f9..8b072d1ce9 100644 --- a/components/esm3/fogstate.cpp +++ b/components/esm3/fogstate.cpp @@ -10,6 +10,10 @@ #include "savedgame.hpp" +namespace ESM +{ +namespace +{ void convertFogOfWar(std::vector& imageData) { if (imageData.empty()) @@ -52,7 +56,9 @@ void convertFogOfWar(std::vector& imageData) imageData = std::vector(str.begin(), str.end()); } -void ESM::FogState::load (ESMReader &esm) +} + +void FogState::load (ESMReader &esm) { esm.getHNOT(mBounds, "BOUN"); esm.getHNOT(mNorthMarkerAngle, "ANGL"); @@ -76,7 +82,7 @@ void ESM::FogState::load (ESMReader &esm) } } -void ESM::FogState::save (ESMWriter &esm, bool interiorCell) const +void FogState::save (ESMWriter &esm, bool interiorCell) const { if (interiorCell) { @@ -92,3 +98,5 @@ void ESM::FogState::save (ESMWriter &esm, bool interiorCell) const esm.endRecord("FTEX"); } } + +} diff --git a/components/esm3/globalmap.cpp b/components/esm3/globalmap.cpp index 3005349d26..8eb4aebd8b 100644 --- a/components/esm3/globalmap.cpp +++ b/components/esm3/globalmap.cpp @@ -4,9 +4,12 @@ #include "esmwriter.hpp" #include "components/esm/defs.hpp" -unsigned int ESM::GlobalMap::sRecordId = ESM::REC_GMAP; +namespace ESM +{ + +unsigned int GlobalMap::sRecordId = REC_GMAP; -void ESM::GlobalMap::load (ESMReader &esm) +void GlobalMap::load (ESMReader &esm) { esm.getHNT(mBounds, "BNDS"); @@ -25,7 +28,7 @@ void ESM::GlobalMap::load (ESMReader &esm) } } -void ESM::GlobalMap::save (ESMWriter &esm) const +void GlobalMap::save (ESMWriter &esm) const { esm.writeHNT("BNDS", mBounds); @@ -41,3 +44,5 @@ void ESM::GlobalMap::save (ESMWriter &esm) const esm.endRecord("MRK_"); } } + +} diff --git a/components/esm3/globalscript.cpp b/components/esm3/globalscript.cpp index a8a8e79cf5..0eb1e31e9c 100644 --- a/components/esm3/globalscript.cpp +++ b/components/esm3/globalscript.cpp @@ -3,7 +3,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::GlobalScript::load (ESMReader &esm) +namespace ESM +{ + +void GlobalScript::load (ESMReader &esm) { mId = esm.getHNString ("NAME"); @@ -18,7 +21,7 @@ void ESM::GlobalScript::load (ESMReader &esm) mTargetRef.load(esm, true, "FRMR"); } -void ESM::GlobalScript::save (ESMWriter &esm) const +void GlobalScript::save (ESMWriter &esm) const { esm.writeHNString ("NAME", mId); @@ -34,3 +37,5 @@ void ESM::GlobalScript::save (ESMWriter &esm) const mTargetRef.save (esm, true, "FRMR"); } } + +} diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index 980d67f7ef..b43a7b7233 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -5,7 +5,10 @@ #include -void ESM::InventoryState::load (ESMReader &esm) +namespace ESM +{ + +void InventoryState::load (ESMReader &esm) { // obsolete int index = 0; @@ -123,7 +126,7 @@ void ESM::InventoryState::load (ESMReader &esm) } } -void ESM::InventoryState::save (ESMWriter &esm) const +void InventoryState::save (ESMWriter &esm) const { int itemsCount = static_cast(mItems.size()); if (itemsCount > 0) @@ -170,3 +173,5 @@ void ESM::InventoryState::save (ESMWriter &esm) const if (mSelectedEnchantItem != -1) esm.writeHNT ("SELE", mSelectedEnchantItem); } + +} diff --git a/components/esm3/journalentry.cpp b/components/esm3/journalentry.cpp index 93011e581b..869a5df29e 100644 --- a/components/esm3/journalentry.cpp +++ b/components/esm3/journalentry.cpp @@ -3,7 +3,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::JournalEntry::load (ESMReader &esm) +namespace ESM +{ + +void JournalEntry::load (ESMReader &esm) { esm.getHNOT (mType, "JETY"); mTopic = esm.getHNString ("YETO"); @@ -20,7 +23,7 @@ void ESM::JournalEntry::load (ESMReader &esm) mActorName = esm.getHNOString("ACT_"); } -void ESM::JournalEntry::save (ESMWriter &esm) const +void JournalEntry::save (ESMWriter &esm) const { esm.writeHNT ("JETY", mType); esm.writeHNString ("YETO", mTopic); @@ -36,3 +39,5 @@ void ESM::JournalEntry::save (ESMWriter &esm) const else if (mType==Type_Topic) esm.writeHNString ("ACT_", mActorName); } + +} diff --git a/components/esm3/loadacti.cpp b/components/esm3/loadacti.cpp index d57ef44be2..a8689bf78c 100644 --- a/components/esm3/loadacti.cpp +++ b/components/esm3/loadacti.cpp @@ -19,20 +19,20 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadalch.cpp b/components/esm3/loadalch.cpp index 7173090e4b..888e049079 100644 --- a/components/esm3/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -22,30 +22,30 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("TEXT"): // not ITEX here for some reason + case fourCC("TEXT"): // not ITEX here for some reason mIcon = esm.getHString(); break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("ALDT"): + case fourCC("ALDT"): esm.getHTSized<12>(mData); hasData = true; break; - case ESM::fourCC("ENAM"): + case fourCC("ENAM"): mEffects.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadappa.cpp b/components/esm3/loadappa.cpp index f29e5bf4f9..8bf87f1e69 100644 --- a/components/esm3/loadappa.cpp +++ b/components/esm3/loadappa.cpp @@ -20,27 +20,27 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("AADT"): + case fourCC("AADT"): esm.getHT(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadarmo.cpp b/components/esm3/loadarmo.cpp index 7176f82f1e..77cfd2ff39 100644 --- a/components/esm3/loadarmo.cpp +++ b/components/esm3/loadarmo.cpp @@ -52,33 +52,33 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("AODT"): + case fourCC("AODT"): esm.getHTSized<24>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::fourCC("ENAM"): + case fourCC("ENAM"): mEnchant = esm.getHString(); break; - case ESM::fourCC("INDX"): + case fourCC("INDX"): mParts.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadbody.cpp b/components/esm3/loadbody.cpp index 55aa5b7bf5..0275f3a63e 100644 --- a/components/esm3/loadbody.cpp +++ b/components/esm3/loadbody.cpp @@ -20,21 +20,21 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mRace = esm.getHString(); break; - case ESM::fourCC("BYDT"): + case fourCC("BYDT"): esm.getHTSized<4>(mData); hasData = true; break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadbook.cpp b/components/esm3/loadbook.cpp index 7e5f75e055..2845e67622 100644 --- a/components/esm3/loadbook.cpp +++ b/components/esm3/loadbook.cpp @@ -20,33 +20,33 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("BKDT"): + case fourCC("BKDT"): esm.getHTSized<20>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::fourCC("ENAM"): + case fourCC("ENAM"): mEnchant = esm.getHString(); break; - case ESM::fourCC("TEXT"): + case fourCC("TEXT"): mText = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadbsgn.cpp b/components/esm3/loadbsgn.cpp index c894918bb2..5be175e72e 100644 --- a/components/esm3/loadbsgn.cpp +++ b/components/esm3/loadbsgn.cpp @@ -21,23 +21,23 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("TNAM"): + case fourCC("TNAM"): mTexture = esm.getHString(); break; - case ESM::fourCC("DESC"): + case fourCC("DESC"): mDescription = esm.getHString(); break; - case ESM::fourCC("NPCS"): + case fourCC("NPCS"): mPowers.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index b36f066501..9ab9eacba1 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -14,10 +14,12 @@ #include "components/esm/defs.hpp" #include "cellid.hpp" +namespace ESM +{ namespace { ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum - void adjustRefNum (ESM::RefNum& refNum, const ESM::ESMReader& reader) + void adjustRefNum (RefNum& refNum, const ESMReader& reader) { unsigned int local = (refNum.mIndex & 0xff000000) >> 24; @@ -37,6 +39,7 @@ namespace } } } +} namespace ESM { @@ -72,14 +75,14 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mName = esm.getHString(); break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): esm.getHTSized<12>(mData); hasData = true; break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; @@ -97,7 +100,7 @@ namespace ESM if (mCellId.mPaged) { - mCellId.mWorldspace = ESM::CellId::sDefaultWorldspace; + mCellId.mWorldspace = CellId::sDefaultWorldspace; mCellId.mIndex.mX = mData.mX; mCellId.mIndex.mY = mData.mY; } @@ -119,13 +122,13 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("INTV"): + case fourCC("INTV"): int waterl; esm.getHT(waterl); mWater = static_cast(waterl); mWaterInt = true; break; - case ESM::fourCC("WHGT"): + case fourCC("WHGT"): float waterLevel; esm.getHT(waterLevel); mWaterInt = false; @@ -138,17 +141,17 @@ namespace ESM else mWater = waterLevel; break; - case ESM::fourCC("AMBI"): + case fourCC("AMBI"): esm.getHT(mAmbi); mHasAmbi = true; break; - case ESM::fourCC("RGNN"): + case fourCC("RGNN"): mRegion = esm.getHString(); break; - case ESM::fourCC("NAM5"): + case fourCC("NAM5"): esm.getHT(mMapColor); break; - case ESM::fourCC("NAM0"): + case fourCC("NAM0"): esm.getHT(mRefNumCounter); break; default: diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index 718ba6c3ac..9b34fbd541 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -50,23 +50,23 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("CLDT"): + case fourCC("CLDT"): esm.getHTSized<60>(mData); if (mData.mIsPlayable > 1) esm.fail("Unknown bool value"); hasData = true; break; - case ESM::fourCC("DESC"): + case fourCC("DESC"): mDescription = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadclot.cpp b/components/esm3/loadclot.cpp index 5b6fa4210b..ea87d1566b 100644 --- a/components/esm3/loadclot.cpp +++ b/components/esm3/loadclot.cpp @@ -22,33 +22,33 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("CTDT"): + case fourCC("CTDT"): esm.getHTSized<12>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::fourCC("ENAM"): + case fourCC("ENAM"): mEnchant = esm.getHString(); break; - case ESM::fourCC("INDX"): + case fourCC("INDX"): mParts.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadcont.cpp b/components/esm3/loadcont.cpp index 6477bb9301..b74f9f2ba2 100644 --- a/components/esm3/loadcont.cpp +++ b/components/esm3/loadcont.cpp @@ -44,21 +44,21 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("CNDT"): + case fourCC("CNDT"): esm.getHTSized<4>(mWeight); hasWeight = true; break; - case ESM::fourCC("FLAG"): + case fourCC("FLAG"): esm.getHTSized<4>(mFlags); if (mFlags & 0xf4) esm.fail("Unknown flags"); @@ -66,13 +66,13 @@ namespace ESM esm.fail("Flag 8 not set"); hasFlags = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("NPCO"): + case fourCC("NPCO"): mInventory.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index f82c488c63..c9d4e6ad14 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -6,7 +6,8 @@ #include "esmwriter.hpp" #include "components/esm/defs.hpp" -namespace ESM { +namespace ESM +{ unsigned int Creature::sRecordId = REC_CREA; @@ -33,47 +34,47 @@ namespace ESM { esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("CNAM"): + case fourCC("CNAM"): mOriginal = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("NPDT"): + case fourCC("NPDT"): esm.getHTSized<96>(mData); hasNpdt = true; break; - case ESM::fourCC("FLAG"): + case fourCC("FLAG"): int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; hasFlags = true; break; - case ESM::fourCC("XSCL"): + case fourCC("XSCL"): esm.getHT(mScale); break; - case ESM::fourCC("NPCO"): + case fourCC("NPCO"): mInventory.add(esm); break; - case ESM::fourCC("NPCS"): + case fourCC("NPCS"): mSpells.add(esm); break; - case ESM::fourCC("AIDT"): + case fourCC("AIDT"): esm.getHExact(&mAiData, sizeof(mAiData)); break; - case ESM::fourCC("DODT"): - case ESM::fourCC("DNAM"): + case fourCC("DODT"): + case fourCC("DNAM"): mTransport.add(esm); break; case AI_Wander: @@ -84,11 +85,11 @@ namespace ESM { case AI_CNDT: mAiPackage.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; - case ESM::fourCC("INDX"): + case fourCC("INDX"): // seems to occur only in .ESS files, unsure of purpose int index; esm.getHT(index); diff --git a/components/esm3/loaddial.cpp b/components/esm3/loaddial.cpp index a4b21cd1b3..b3b42b9989 100644 --- a/components/esm3/loaddial.cpp +++ b/components/esm3/loaddial.cpp @@ -30,7 +30,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("DATA"): + case fourCC("DATA"): { esm.getSubHeader(); int size = esm.getSubSize(); @@ -44,7 +44,7 @@ namespace ESM } break; } - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); mType = Unknown; isDeleted = true; @@ -76,7 +76,7 @@ namespace ESM void Dialogue::readInfo(ESMReader &esm, bool merge) { - ESM::DialInfo info; + DialInfo info; bool isDeleted = false; info.load(esm, isDeleted); diff --git a/components/esm3/loaddial.hpp b/components/esm3/loaddial.hpp index 7adc8b1cf8..7998623998 100644 --- a/components/esm3/loaddial.hpp +++ b/components/esm3/loaddial.hpp @@ -61,7 +61,7 @@ struct Dialogue /// Read the next info record /// @param merge Merge with existing list, or just push each record to the end of the list? - void readInfo (ESM::ESMReader& esm, bool merge); + void readInfo (ESMReader& esm, bool merge); void blank(); ///< Set record to default state (does not touch the ID and does not change the type). diff --git a/components/esm3/loaddoor.cpp b/components/esm3/loaddoor.cpp index e059a1dc05..c866fc96c0 100644 --- a/components/esm3/loaddoor.cpp +++ b/components/esm3/loaddoor.cpp @@ -19,26 +19,26 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("SNAM"): + case fourCC("SNAM"): mOpenSound = esm.getHString(); break; - case ESM::fourCC("ANAM"): + case fourCC("ANAM"): mCloseSound = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadench.cpp b/components/esm3/loadench.cpp index 4280b3a8af..533ba154b7 100644 --- a/components/esm3/loadench.cpp +++ b/components/esm3/loadench.cpp @@ -21,18 +21,18 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("ENDT"): + case fourCC("ENDT"): esm.getHTSized<16>(mData); hasData = true; break; - case ESM::fourCC("ENAM"): + case fourCC("ENAM"): mEffects.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadfact.cpp b/components/esm3/loadfact.cpp index 9fc5410164..513f8c3199 100644 --- a/components/esm3/loadfact.cpp +++ b/components/esm3/loadfact.cpp @@ -43,25 +43,25 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("RNAM"): + case fourCC("RNAM"): if (rankCounter >= 10) esm.fail("Rank out of range"); mRanks[rankCounter++] = esm.getHString(); break; - case ESM::fourCC("FADT"): + case fourCC("FADT"): esm.getHTSized<240>(mData); if (mData.mIsHidden > 1) esm.fail("Unknown flag!"); hasData = true; break; - case ESM::fourCC("ANAM"): + case fourCC("ANAM"): { std::string faction = esm.getHString(); int reaction; @@ -69,7 +69,7 @@ namespace ESM mReactions[faction] = reaction; break; } - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadfact.hpp b/components/esm3/loadfact.hpp index da01c004e9..d47ff5a43e 100644 --- a/components/esm3/loadfact.hpp +++ b/components/esm3/loadfact.hpp @@ -45,7 +45,7 @@ struct Faction RankData mRankData[10]; int mSkills[7]; // IDs of skills this faction require - // Each element will either contain an ESM::Skill index, or -1. + // Each element will either contain an Skill index, or -1. int mIsHidden; // 1 - hidden from player diff --git a/components/esm3/loadglob.cpp b/components/esm3/loadglob.cpp index fd4ee05665..cc2188a5b5 100644 --- a/components/esm3/loadglob.cpp +++ b/components/esm3/loadglob.cpp @@ -22,7 +22,7 @@ namespace ESM } else { - mValue.read (esm, ESM::Variant::Format_Global); + mValue.read (esm, Variant::Format_Global); } } @@ -36,13 +36,13 @@ namespace ESM } else { - mValue.write (esm, ESM::Variant::Format_Global); + mValue.write (esm, Variant::Format_Global); } } void Global::blank() { - mValue.setType (ESM::VT_None); + mValue.setType (VT_None); } bool operator== (const Global& left, const Global& right) diff --git a/components/esm3/loadgmst.cpp b/components/esm3/loadgmst.cpp index 1bcf6a1fa4..31439dc268 100644 --- a/components/esm3/loadgmst.cpp +++ b/components/esm3/loadgmst.cpp @@ -14,18 +14,18 @@ namespace ESM mRecordFlags = esm.getRecordFlags(); mId = esm.getHNString("NAME"); - mValue.read (esm, ESM::Variant::Format_Gmst); + mValue.read (esm, Variant::Format_Gmst); } void GameSetting::save (ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNCString("NAME", mId); - mValue.write (esm, ESM::Variant::Format_Gmst); + mValue.write (esm, Variant::Format_Gmst); } void GameSetting::blank() { - mValue.setType (ESM::VT_None); + mValue.setType (VT_None); } bool operator== (const GameSetting& left, const GameSetting& right) diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index fb32f80188..0c7c4db61a 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -25,19 +25,19 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("DATA"): + case fourCC("DATA"): esm.getHTSized<12>(mData); break; - case ESM::fourCC("ONAM"): + case fourCC("ONAM"): mActor = esm.getHString(); break; - case ESM::fourCC("RNAM"): + case fourCC("RNAM"): mRace = esm.getHString(); break; - case ESM::fourCC("CNAM"): + case fourCC("CNAM"): mClass = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): { mFaction = esm.getHString(); if (mFaction == "FFFF") @@ -46,19 +46,19 @@ namespace ESM } break; } - case ESM::fourCC("ANAM"): + case fourCC("ANAM"): mCell = esm.getHString(); break; - case ESM::fourCC("DNAM"): + case fourCC("DNAM"): mPcFaction = esm.getHString(); break; - case ESM::fourCC("SNAM"): + case fourCC("SNAM"): mSound = esm.getHString(); break; - case ESM::SREC_NAME: + case SREC_NAME: mResponse = esm.getHString(); break; - case ESM::fourCC("SCVR"): + case fourCC("SCVR"): { SelectStruct ss; ss.mSelectRule = esm.getHString(); @@ -66,22 +66,22 @@ namespace ESM mSelects.push_back(ss); break; } - case ESM::fourCC("BNAM"): + case fourCC("BNAM"): mResultScript = esm.getHString(); break; - case ESM::fourCC("QSTN"): + case fourCC("QSTN"): mQuestStatus = QS_Name; esm.skipRecord(); break; - case ESM::fourCC("QSTF"): + case fourCC("QSTF"): mQuestStatus = QS_Finished; esm.skipRecord(); break; - case ESM::fourCC("QSTR"): + case fourCC("QSTR"): mQuestStatus = QS_Restart; esm.skipRecord(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadingr.cpp b/components/esm3/loadingr.cpp index d7317a8dc4..ea10da42b9 100644 --- a/components/esm3/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -20,27 +20,27 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("IRDT"): + case fourCC("IRDT"): esm.getHTSized<56>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index f9563411a3..71b020f767 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -46,7 +46,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("INTV"): + case fourCC("INTV"): esm.getSubHeader(); if (esm.getSubSize() != 8) esm.fail("Subrecord size is not equal to 8"); @@ -54,10 +54,10 @@ namespace ESM esm.getT(mY); hasLocation = true; break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): esm.getHT(mFlags); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; @@ -82,23 +82,23 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("VNML"): + case fourCC("VNML"): esm.skipHSub(); mDataTypes |= DATA_VNML; break; - case ESM::fourCC("VHGT"): + case fourCC("VHGT"): esm.skipHSub(); mDataTypes |= DATA_VHGT; break; - case ESM::fourCC("WNAM"): + case fourCC("WNAM"): esm.getHExact(mWnam, sizeof(mWnam)); mDataTypes |= DATA_WNAM; break; - case ESM::fourCC("VCLR"): + case fourCC("VCLR"): esm.skipHSub(); mDataTypes |= DATA_VCLR; break; - case ESM::fourCC("VTEX"): + case fourCC("VTEX"): esm.skipHSub(); mDataTypes |= DATA_VTEX; break; @@ -162,12 +162,12 @@ namespace ESM signed char 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(ESM::Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; + 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) { for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) { - float height = mLandData->mHeights[int(row * vertMult) * ESM::Land::LAND_SIZE + int(col * vertMult)]; + float height = mLandData->mHeights[int(row * vertMult) * Land::LAND_SIZE + int(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); @@ -246,7 +246,7 @@ namespace ESM return; } - ESM::ESMReader reader; + ESMReader reader; reader.restoreContext(mContext); if (reader.isNextSub("VNML")) { @@ -306,7 +306,7 @@ namespace ESM } } - bool Land::condLoad(ESM::ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const + bool Land::condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const { if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) { reader.getHExact(ptr, size); diff --git a/components/esm3/loadland.hpp b/components/esm3/loadland.hpp index 1244a955cf..28bd36b8c7 100644 --- a/components/esm3/loadland.hpp +++ b/components/esm3/loadland.hpp @@ -109,7 +109,7 @@ struct Land // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. VNML mNormals[LAND_NUM_VERTS * 3]; - // 2D array of texture indices. An index can be used to look up an ESM::LandTexture, + // 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. uint16_t mTextures[LAND_NUM_TEXTURES]; @@ -179,7 +179,7 @@ struct Land /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded - bool condLoad(ESM::ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const; + bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const; mutable LandData *mLandData; }; diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index ace5304152..51055103bf 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -6,7 +6,7 @@ namespace ESM { - void LevelledListBase::load(ESMReader& esm, ESM::NAME recName, bool& isDeleted) + void LevelledListBase::load(ESMReader& esm, NAME recName, bool& isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); @@ -18,17 +18,17 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): esm.getHT(mFlags); break; - case ESM::fourCC("NNAM"): + case fourCC("NNAM"): esm.getHT(mChanceNone); break; - case ESM::fourCC("INDX"): + case fourCC("INDX"): { int length = 0; esm.getHT(length); @@ -50,7 +50,7 @@ namespace ESM hasList = true; break; } - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; @@ -75,7 +75,7 @@ namespace ESM esm.fail("Missing NAME subrecord"); } - void LevelledListBase::save(ESMWriter& esm, ESM::NAME recName, bool isDeleted) const + void LevelledListBase::save(ESMWriter& esm, NAME recName, bool isDeleted) const { esm.writeHNCString("NAME", mId); diff --git a/components/esm3/loadlevlist.hpp b/components/esm3/loadlevlist.hpp index 3e75ad7c70..d73003fdda 100644 --- a/components/esm3/loadlevlist.hpp +++ b/components/esm3/loadlevlist.hpp @@ -36,8 +36,8 @@ struct LevelledListBase std::vector mList; - void load(ESMReader& esm, ESM::NAME recName, bool& isDeleted); - void save(ESMWriter& esm, ESM::NAME recName, bool isDeleted) const; + void load(ESMReader& esm, NAME recName, bool& isDeleted); + void save(ESMWriter& esm, NAME recName, bool isDeleted) const; void blank(); ///< Set record to default state (does not touch the ID). @@ -53,7 +53,7 @@ struct CustomLevelledListBase : LevelledListBase struct CreatureLevList : CustomLevelledListBase { /// Record name used to read references. - static constexpr ESM::NAME sRecName {"CNAM"}; + static constexpr NAME sRecName {"CNAM"}; static constexpr RecNameInts sRecordId = RecNameInts::REC_LEVC; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "CreatureLevList"; } @@ -70,7 +70,7 @@ struct CreatureLevList : CustomLevelledListBase struct ItemLevList : CustomLevelledListBase { /// Record name used to read references. - static constexpr ESM::NAME sRecName {"INAM"}; + static constexpr NAME sRecName {"INAM"}; static constexpr RecNameInts sRecordId = RecNameInts::REC_LEVI; /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "ItemLevList"; } diff --git a/components/esm3/loadligh.cpp b/components/esm3/loadligh.cpp index 7b3c6e8787..da45674e85 100644 --- a/components/esm3/loadligh.cpp +++ b/components/esm3/loadligh.cpp @@ -20,30 +20,30 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::fourCC("LHDT"): + case fourCC("LHDT"): esm.getHTSized<24>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("SNAM"): + case fourCC("SNAM"): mSound = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadlock.cpp b/components/esm3/loadlock.cpp index 0bdcbba635..e298001cb6 100644 --- a/components/esm3/loadlock.cpp +++ b/components/esm3/loadlock.cpp @@ -20,27 +20,27 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("LKDT"): + case fourCC("LKDT"): esm.getHTSized<16>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadltex.cpp b/components/esm3/loadltex.cpp index de5eb53e56..26ce41c5b3 100644 --- a/components/esm3/loadltex.cpp +++ b/components/esm3/loadltex.cpp @@ -19,18 +19,18 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("INTV"): + case fourCC("INTV"): esm.getHT(mIndex); hasIndex = true; break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): mTexture = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index b528e8bf9a..9707f2f255 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -6,9 +6,11 @@ #include "esmwriter.hpp" #include "components/esm/defs.hpp" +namespace ESM +{ namespace { - static const char *sIds[ESM::MagicEffect::Length] = + static const char *sIds[MagicEffect::Length] = { "WaterBreathing", "SwiftSwim", @@ -181,6 +183,7 @@ namespace 0x11c8, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048 }; } +} namespace ESM { @@ -211,37 +214,37 @@ void MagicEffect::load(ESMReader &esm, bool &isDeleted) esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::fourCC("PTEX"): + case fourCC("PTEX"): mParticle = esm.getHString(); break; - case ESM::fourCC("BSND"): + case fourCC("BSND"): mBoltSound = esm.getHString(); break; - case ESM::fourCC("CSND"): + case fourCC("CSND"): mCastSound = esm.getHString(); break; - case ESM::fourCC("HSND"): + case fourCC("HSND"): mHitSound = esm.getHString(); break; - case ESM::fourCC("ASND"): + case fourCC("ASND"): mAreaSound = esm.getHString(); break; - case ESM::fourCC("CVFX"): + case fourCC("CVFX"): mCasting = esm.getHString(); break; - case ESM::fourCC("BVFX"): + case fourCC("BVFX"): mBolt = esm.getHString(); break; - case ESM::fourCC("HVFX"): + case fourCC("HVFX"): mHit = esm.getHString(); break; - case ESM::fourCC("AVFX"): + case fourCC("AVFX"): mArea = esm.getHString(); break; - case ESM::fourCC("DESC"): + case fourCC("DESC"): mDescription = esm.getHString(); break; default: diff --git a/components/esm3/loadmgef.hpp b/components/esm3/loadmgef.hpp index 00cadf99c0..64d89dab39 100644 --- a/components/esm3/loadmgef.hpp +++ b/components/esm3/loadmgef.hpp @@ -83,8 +83,8 @@ struct MagicEffect MEDTstruct mData; std::string mIcon, mParticle; // Textures - std::string mCasting, mHit, mArea; // ESM::Static - std::string mBolt; // ESM::Weapon + std::string mCasting, mHit, mArea; // Static + std::string mBolt; // Weapon std::string mCastSound, mBoltSound, mHitSound, mAreaSound; // Sounds std::string mDescription; diff --git a/components/esm3/loadmisc.cpp b/components/esm3/loadmisc.cpp index bbf1ac0635..db6a050a24 100644 --- a/components/esm3/loadmisc.cpp +++ b/components/esm3/loadmisc.cpp @@ -20,27 +20,27 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("MCDT"): + case fourCC("MCDT"): esm.getHTSized<12>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 338b453a5a..6c0b8d0a68 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -28,35 +28,35 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("RNAM"): + case fourCC("RNAM"): mRace = esm.getHString(); break; - case ESM::fourCC("CNAM"): + case fourCC("CNAM"): mClass = esm.getHString(); break; - case ESM::fourCC("ANAM"): + case fourCC("ANAM"): mFaction = esm.getHString(); break; - case ESM::fourCC("BNAM"): + case fourCC("BNAM"): mHead = esm.getHString(); break; - case ESM::fourCC("KNAM"): + case fourCC("KNAM"): mHair = esm.getHString(); break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("NPDT"): + case fourCC("NPDT"): hasNpdt = true; esm.getSubHeader(); if (esm.getSubSize() == 52) @@ -83,24 +83,24 @@ namespace ESM else esm.fail("NPC_NPDT must be 12 or 52 bytes long"); break; - case ESM::fourCC("FLAG"): + case fourCC("FLAG"): hasFlags = true; int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; break; - case ESM::fourCC("NPCS"): + case fourCC("NPCS"): mSpells.add(esm); break; - case ESM::fourCC("NPCO"): + case fourCC("NPCO"): mInventory.add(esm); break; - case ESM::fourCC("AIDT"): + case fourCC("AIDT"): esm.getHExact(&mAiData, sizeof(mAiData)); break; - case ESM::fourCC("DODT"): - case ESM::fourCC("DNAM"): + case fourCC("DODT"): + case fourCC("DNAM"): mTransport.add(esm); break; case AI_Wander: @@ -111,7 +111,7 @@ namespace ESM case AI_CNDT: mAiPackage.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index 24752beb11..b215a346bc 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -11,7 +11,8 @@ #include "loadskil.hpp" #include "transport.hpp" -namespace ESM { +namespace ESM +{ class ESMReader; class ESMWriter; diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index ed2ab2c5d3..f9204175fb 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -48,14 +48,14 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mCell = esm.getHString(); break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): esm.getHTSized<12>(mData); hasData = true; break; - case ESM::fourCC("PGRP"): + case fourCC("PGRP"): { esm.getSubHeader(); int size = esm.getSubSize(); @@ -76,7 +76,7 @@ namespace ESM } break; } - case ESM::fourCC("PGRC"): + case fourCC("PGRC"): { esm.getSubHeader(); int size = esm.getSubSize(); @@ -113,7 +113,7 @@ namespace ESM } break; } - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadprob.cpp b/components/esm3/loadprob.cpp index f084732775..6299286ea8 100644 --- a/components/esm3/loadprob.cpp +++ b/components/esm3/loadprob.cpp @@ -20,27 +20,27 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("PBDT"): + case fourCC("PBDT"): esm.getHTSized<16>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadrace.cpp b/components/esm3/loadrace.cpp index 9520b792ed..e2b5a1fcf8 100644 --- a/components/esm3/loadrace.cpp +++ b/components/esm3/loadrace.cpp @@ -32,24 +32,24 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("RADT"): + case fourCC("RADT"): esm.getHTSized<140>(mData); hasData = true; break; - case ESM::fourCC("DESC"): + case fourCC("DESC"): mDescription = esm.getHString(); break; - case ESM::fourCC("NPCS"): + case fourCC("NPCS"): mPowers.add(esm); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadregn.cpp b/components/esm3/loadregn.cpp index e2682e644e..99f09b7fad 100644 --- a/components/esm3/loadregn.cpp +++ b/components/esm3/loadregn.cpp @@ -19,14 +19,14 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("WEAT"): + case fourCC("WEAT"): { esm.getSubHeader(); if (esm.getVer() == VER_12) @@ -55,13 +55,13 @@ namespace ESM } break; } - case ESM::fourCC("BNAM"): + case fourCC("BNAM"): mSleepList = esm.getHString(); break; - case ESM::fourCC("CNAM"): + case fourCC("CNAM"): esm.getHT(mMapColor); break; - case ESM::fourCC("SNAM"): + case fourCC("SNAM"): { esm.getSubHeader(); SoundRef sr; @@ -70,7 +70,7 @@ namespace ESM mSoundList.push_back(sr); break; } - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadrepa.cpp b/components/esm3/loadrepa.cpp index 16dd296a25..8d09083cad 100644 --- a/components/esm3/loadrepa.cpp +++ b/components/esm3/loadrepa.cpp @@ -20,27 +20,27 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("RIDT"): + case fourCC("RIDT"): esm.getHTSized<16>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index b97a148963..1e5d342fa3 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -95,7 +95,7 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("SCHD"): + case fourCC("SCHD"): { esm.getSubHeader(); mId = esm.getString(32); @@ -104,11 +104,11 @@ namespace ESM hasHeader = true; break; } - case ESM::fourCC("SCVR"): + case fourCC("SCVR"): // list of local variables loadSCVR(esm); break; - case ESM::fourCC("SCDT"): + case fourCC("SCDT"): { // compiled script esm.getSubHeader(); @@ -127,10 +127,10 @@ namespace ESM esm.getExact(mScriptData.data(), mScriptData.size()); break; } - case ESM::fourCC("SCTX"): + case fourCC("SCTX"): mScriptText = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadskil.cpp b/components/esm3/loadskil.cpp index f7bb0c49d6..86ffda4fd9 100644 --- a/components/esm3/loadskil.cpp +++ b/components/esm3/loadskil.cpp @@ -139,15 +139,15 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::fourCC("INDX"): + case fourCC("INDX"): esm.getHT(mIndex); hasIndex = true; break; - case ESM::fourCC("SKDT"): + case fourCC("SKDT"): esm.getHTSized<24>(mData); hasData = true; break; - case ESM::fourCC("DESC"): + case fourCC("DESC"): mDescription = esm.getHString(); break; default: diff --git a/components/esm3/loadskil.hpp b/components/esm3/loadskil.hpp index cd4cad6a71..58e1fa6deb 100644 --- a/components/esm3/loadskil.hpp +++ b/components/esm3/loadskil.hpp @@ -6,7 +6,8 @@ #include "components/esm/defs.hpp" -namespace ESM { +namespace ESM +{ class ESMReader; class ESMWriter; diff --git a/components/esm3/loadsndg.cpp b/components/esm3/loadsndg.cpp index 5519d835b4..9113d1ac3d 100644 --- a/components/esm3/loadsndg.cpp +++ b/components/esm3/loadsndg.cpp @@ -20,21 +20,21 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): esm.getHTSized<4>(mType); hasData = true; break; - case ESM::fourCC("CNAM"): + case fourCC("CNAM"): mCreature = esm.getHString(); break; - case ESM::fourCC("SNAM"): + case fourCC("SNAM"): mSound = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadsoun.cpp b/components/esm3/loadsoun.cpp index 0ddda2d6b2..9904d5d0fb 100644 --- a/components/esm3/loadsoun.cpp +++ b/components/esm3/loadsoun.cpp @@ -20,18 +20,18 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mSound = esm.getHString(); break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): esm.getHTSized<3>(mData); hasData = true; break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadspel.cpp b/components/esm3/loadspel.cpp index f2891c2b6c..bb0c866c5e 100644 --- a/components/esm3/loadspel.cpp +++ b/components/esm3/loadspel.cpp @@ -22,23 +22,23 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("SPDT"): + case fourCC("SPDT"): esm.getHTSized<12>(mData); hasData = true; break; - case ESM::fourCC("ENAM"): + case fourCC("ENAM"): ENAMstruct s; esm.getHTSized<24>(s); mEffects.mList.push_back(s); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadsscr.cpp b/components/esm3/loadsscr.cpp index 03c5b49380..3f7a4fe19d 100644 --- a/components/esm3/loadsscr.cpp +++ b/components/esm3/loadsscr.cpp @@ -20,15 +20,15 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("DATA"): + case fourCC("DATA"): mData = esm.getHString(); hasData = true; break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadstat.cpp b/components/esm3/loadstat.cpp index fd557d37d5..cb7c9fa2df 100644 --- a/components/esm3/loadstat.cpp +++ b/components/esm3/loadstat.cpp @@ -12,8 +12,8 @@ namespace ESM { isDeleted = false; mRecordFlags = esm.getRecordFlags(); - //bool isBlocked = (mRecordFlags & ESM::FLAG_Blocked) != 0; - //bool isPersistent = (mRecordFlags & ESM::FLAG_Persistent) != 0; + //bool isBlocked = (mRecordFlags & FLAG_Blocked) != 0; + //bool isPersistent = (mRecordFlags & FLAG_Persistent) != 0; bool hasName = false; while (esm.hasMoreSubs()) @@ -21,14 +21,14 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadstat.hpp b/components/esm3/loadstat.hpp index 26d8fda3a8..71f927d21c 100644 --- a/components/esm3/loadstat.hpp +++ b/components/esm3/loadstat.hpp @@ -3,7 +3,8 @@ #include -namespace ESM { +namespace ESM +{ class ESMReader; class ESMWriter; diff --git a/components/esm3/loadtes3.cpp b/components/esm3/loadtes3.cpp index e39d65298c..0c3ccf232e 100644 --- a/components/esm3/loadtes3.cpp +++ b/components/esm3/loadtes3.cpp @@ -5,9 +5,12 @@ #include "esmwriter.hpp" #include "components/esm/defs.hpp" -void ESM::Header::blank() +namespace ESM { - mData.version = ESM::VER_13; + +void Header::blank() +{ + mData.version = VER_13; mData.type = 0; mData.author.clear(); mData.desc.clear(); @@ -16,7 +19,7 @@ void ESM::Header::blank() mMaster.clear(); } -void ESM::Header::load (ESMReader &esm) +void Header::load (ESMReader &esm) { if (esm.isNextSub ("FORM")) { @@ -65,7 +68,7 @@ void ESM::Header::load (ESMReader &esm) } } -void ESM::Header::save (ESMWriter &esm) +void Header::save (ESMWriter &esm) { if (mFormat>0) esm.writeHNT ("FORM", mFormat); @@ -84,3 +87,5 @@ void ESM::Header::save (ESMWriter &esm) esm.writeHNT ("DATA", data.size); } } + +} diff --git a/components/esm3/loadweap.cpp b/components/esm3/loadweap.cpp index 452036cb95..696194f969 100644 --- a/components/esm3/loadweap.cpp +++ b/components/esm3/loadweap.cpp @@ -20,30 +20,30 @@ namespace ESM esm.getSubName(); switch (esm.retSubName().toInt()) { - case ESM::SREC_NAME: + case SREC_NAME: mId = esm.getHString(); hasName = true; break; - case ESM::fourCC("MODL"): + case fourCC("MODL"): mModel = esm.getHString(); break; - case ESM::fourCC("FNAM"): + case fourCC("FNAM"): mName = esm.getHString(); break; - case ESM::fourCC("WPDT"): + case fourCC("WPDT"): esm.getHTSized<32>(mData); hasData = true; break; - case ESM::fourCC("SCRI"): + case fourCC("SCRI"): mScript = esm.getHString(); break; - case ESM::fourCC("ITEX"): + case fourCC("ITEX"): mIcon = esm.getHString(); break; - case ESM::fourCC("ENAM"): + case fourCC("ENAM"): mEnchant = esm.getHString(); break; - case ESM::SREC_DELE: + case SREC_DELE: esm.skipHSub(); isDeleted = true; break; diff --git a/components/esm3/loadweap.hpp b/components/esm3/loadweap.hpp index 98ba57b8b8..e75e12d685 100644 --- a/components/esm3/loadweap.hpp +++ b/components/esm3/loadweap.hpp @@ -105,7 +105,7 @@ struct WeaponType std::string mSoundId; std::string mAttachBone; std::string mSheathingBone; - ESM::Skill::SkillEnum mSkill; + Skill::SkillEnum mSkill; Class mWeaponClass; int mAmmoType; int mFlags; diff --git a/components/esm3/locals.cpp b/components/esm3/locals.cpp index 4149695fe9..7db0e544ec 100644 --- a/components/esm3/locals.cpp +++ b/components/esm3/locals.cpp @@ -3,7 +3,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::Locals::load (ESMReader &esm) +namespace ESM +{ + +void Locals::load (ESMReader &esm) { while (esm.isNextSub ("LOCA")) { @@ -16,7 +19,7 @@ void ESM::Locals::load (ESMReader &esm) } } -void ESM::Locals::save (ESMWriter &esm) const +void Locals::save (ESMWriter &esm) const { for (std::vector >::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) @@ -25,3 +28,5 @@ void ESM::Locals::save (ESMWriter &esm) const iter->second.write (esm, Variant::Format_Local); } } + +} diff --git a/components/esm3/mappings.cpp b/components/esm3/mappings.cpp index 440e735739..4553c1396f 100644 --- a/components/esm3/mappings.cpp +++ b/components/esm3/mappings.cpp @@ -4,128 +4,128 @@ namespace ESM { - ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type) + BodyPart::MeshPart getMeshPart(PartReferenceType type) { switch(type) { - case ESM::PRT_Head: - return ESM::BodyPart::MP_Head; - case ESM::PRT_Hair: - return ESM::BodyPart::MP_Hair; - case ESM::PRT_Neck: - return ESM::BodyPart::MP_Neck; - case ESM::PRT_Cuirass: - return ESM::BodyPart::MP_Chest; - case ESM::PRT_Groin: - return ESM::BodyPart::MP_Groin; - case ESM::PRT_RHand: - return ESM::BodyPart::MP_Hand; - case ESM::PRT_LHand: - return ESM::BodyPart::MP_Hand; - case ESM::PRT_RWrist: - return ESM::BodyPart::MP_Wrist; - case ESM::PRT_LWrist: - return ESM::BodyPart::MP_Wrist; - case ESM::PRT_RForearm: - return ESM::BodyPart::MP_Forearm; - case ESM::PRT_LForearm: - return ESM::BodyPart::MP_Forearm; - case ESM::PRT_RUpperarm: - return ESM::BodyPart::MP_Upperarm; - case ESM::PRT_LUpperarm: - return ESM::BodyPart::MP_Upperarm; - case ESM::PRT_RFoot: - return ESM::BodyPart::MP_Foot; - case ESM::PRT_LFoot: - return ESM::BodyPart::MP_Foot; - case ESM::PRT_RAnkle: - return ESM::BodyPart::MP_Ankle; - case ESM::PRT_LAnkle: - return ESM::BodyPart::MP_Ankle; - case ESM::PRT_RKnee: - return ESM::BodyPart::MP_Knee; - case ESM::PRT_LKnee: - return ESM::BodyPart::MP_Knee; - case ESM::PRT_RLeg: - return ESM::BodyPart::MP_Upperleg; - case ESM::PRT_LLeg: - return ESM::BodyPart::MP_Upperleg; - case ESM::PRT_Tail: - return ESM::BodyPart::MP_Tail; + case PRT_Head: + return BodyPart::MP_Head; + case PRT_Hair: + return BodyPart::MP_Hair; + case PRT_Neck: + return BodyPart::MP_Neck; + case PRT_Cuirass: + return BodyPart::MP_Chest; + case PRT_Groin: + return BodyPart::MP_Groin; + case PRT_RHand: + return BodyPart::MP_Hand; + case PRT_LHand: + return BodyPart::MP_Hand; + case PRT_RWrist: + return BodyPart::MP_Wrist; + case PRT_LWrist: + return BodyPart::MP_Wrist; + case PRT_RForearm: + return BodyPart::MP_Forearm; + case PRT_LForearm: + return BodyPart::MP_Forearm; + case PRT_RUpperarm: + return BodyPart::MP_Upperarm; + case PRT_LUpperarm: + return BodyPart::MP_Upperarm; + case PRT_RFoot: + return BodyPart::MP_Foot; + case PRT_LFoot: + return BodyPart::MP_Foot; + case PRT_RAnkle: + return BodyPart::MP_Ankle; + case PRT_LAnkle: + return BodyPart::MP_Ankle; + case PRT_RKnee: + return BodyPart::MP_Knee; + case PRT_LKnee: + return BodyPart::MP_Knee; + case PRT_RLeg: + return BodyPart::MP_Upperleg; + case PRT_LLeg: + return BodyPart::MP_Upperleg; + case PRT_Tail: + return BodyPart::MP_Tail; default: throw std::runtime_error("PartReferenceType " + std::to_string(type) + " not associated with a mesh part"); } } - std::string getBoneName(ESM::PartReferenceType type) + std::string getBoneName(PartReferenceType type) { switch(type) { - case ESM::PRT_Head: + case PRT_Head: return "head"; - case ESM::PRT_Hair: + case PRT_Hair: return "head"; // This is purposeful. - case ESM::PRT_Neck: + case PRT_Neck: return "neck"; - case ESM::PRT_Cuirass: + case PRT_Cuirass: return "chest"; - case ESM::PRT_Groin: + case PRT_Groin: return "groin"; - case ESM::PRT_Skirt: + case PRT_Skirt: return "groin"; - case ESM::PRT_RHand: + case PRT_RHand: return "right hand"; - case ESM::PRT_LHand: + case PRT_LHand: return "left hand"; - case ESM::PRT_RWrist: + case PRT_RWrist: return "right wrist"; - case ESM::PRT_LWrist: + case PRT_LWrist: return "left wrist"; - case ESM::PRT_Shield: + case PRT_Shield: return "shield bone"; - case ESM::PRT_RForearm: + case PRT_RForearm: return "right forearm"; - case ESM::PRT_LForearm: + case PRT_LForearm: return "left forearm"; - case ESM::PRT_RUpperarm: + case PRT_RUpperarm: return "right upper arm"; - case ESM::PRT_LUpperarm: + case PRT_LUpperarm: return "left upper arm"; - case ESM::PRT_RFoot: + case PRT_RFoot: return "right foot"; - case ESM::PRT_LFoot: + case PRT_LFoot: return "left foot"; - case ESM::PRT_RAnkle: + case PRT_RAnkle: return "right ankle"; - case ESM::PRT_LAnkle: + case PRT_LAnkle: return "left ankle"; - case ESM::PRT_RKnee: + case PRT_RKnee: return "right knee"; - case ESM::PRT_LKnee: + case PRT_LKnee: return "left knee"; - case ESM::PRT_RLeg: + case PRT_RLeg: return "right upper leg"; - case ESM::PRT_LLeg: + case PRT_LLeg: return "left upper leg"; - case ESM::PRT_RPauldron: + case PRT_RPauldron: return "right clavicle"; - case ESM::PRT_LPauldron: + case PRT_LPauldron: return "left clavicle"; - case ESM::PRT_Weapon: + case PRT_Weapon: return "weapon bone"; - case ESM::PRT_Tail: + case PRT_Tail: return "tail"; default: throw std::runtime_error("unknown PartReferenceType"); } } - std::string getMeshFilter(ESM::PartReferenceType type) + std::string getMeshFilter(PartReferenceType type) { switch(type) { - case ESM::PRT_Hair: + case PRT_Hair: return "hair"; default: return getBoneName(type); diff --git a/components/esm3/mappings.hpp b/components/esm3/mappings.hpp index e2fa1b62c3..a6f0ec3048 100644 --- a/components/esm3/mappings.hpp +++ b/components/esm3/mappings.hpp @@ -8,9 +8,9 @@ namespace ESM { - ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type); - std::string getBoneName(ESM::PartReferenceType type); - std::string getMeshFilter(ESM::PartReferenceType type); + BodyPart::MeshPart getMeshPart(PartReferenceType type); + std::string getBoneName(PartReferenceType type); + std::string getMeshFilter(PartReferenceType type); } #endif diff --git a/components/esm3/npcstate.cpp b/components/esm3/npcstate.cpp index 6c9988d50d..91c6ebbf09 100644 --- a/components/esm3/npcstate.cpp +++ b/components/esm3/npcstate.cpp @@ -1,6 +1,9 @@ #include "npcstate.hpp" -void ESM::NpcState::load (ESMReader &esm) +namespace ESM +{ + +void NpcState::load (ESMReader &esm) { ObjectState::load (esm); @@ -14,7 +17,7 @@ void ESM::NpcState::load (ESMReader &esm) } } -void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const +void NpcState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); @@ -28,10 +31,12 @@ void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const } } -void ESM::NpcState::blank() +void NpcState::blank() { ObjectState::blank(); mNpcStats.blank(); mCreatureStats.blank(); mHasCustomState = true; } + +} diff --git a/components/esm3/npcstats.cpp b/components/esm3/npcstats.cpp index 277335e8ca..2896f6a922 100644 --- a/components/esm3/npcstats.cpp +++ b/components/esm3/npcstats.cpp @@ -5,9 +5,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (-1), mReputation (0) {} +namespace ESM +{ + +NpcStats::Faction::Faction() : mExpelled (false), mRank (-1), mReputation (0) {} -void ESM::NpcStats::load (ESMReader &esm) +void NpcStats::load (ESMReader &esm) { while (esm.isNextSub ("FACT")) { @@ -41,17 +44,17 @@ void ESM::NpcStats::load (ESMReader &esm) // we have deprecated werewolf skills, stored interleaved // Load into one big vector, then remove every 2nd value mWerewolfDeprecatedData = true; - std::vector > skills(mSkills, mSkills + sizeof(mSkills)/sizeof(mSkills[0])); + std::vector > skills(mSkills, mSkills + sizeof(mSkills)/sizeof(mSkills[0])); for (int i=0; i<27; ++i) { - ESM::StatState skill; + StatState skill; skill.load(esm, intFallback); skills.push_back(skill); } int i=0; - for (std::vector >::iterator it = skills.begin(); it != skills.end(); ++i) + for (std::vector >::iterator it = skills.begin(); it != skills.end(); ++i) { if (i%2 == 1) it = skills.erase(it); @@ -67,7 +70,7 @@ void ESM::NpcStats::load (ESMReader &esm) esm.getHNOT (hasWerewolfAttributes, "HWAT"); if (hasWerewolfAttributes) { - ESM::StatState dummy; + StatState dummy; for (int i=0; i<8; ++i) dummy.load(esm, intFallback); mWerewolfDeprecatedData = true; @@ -122,7 +125,7 @@ void ESM::NpcStats::load (ESMReader &esm) esm.getHNOT (mCrimeId, "CRID"); } -void ESM::NpcStats::save (ESMWriter &esm) const +void NpcStats::save (ESMWriter &esm) const { for (std::map::const_iterator iter (mFactions.begin()); iter!=mFactions.end(); ++iter) @@ -191,7 +194,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const esm.writeHNT ("CRID", mCrimeId); } -void ESM::NpcStats::blank() +void NpcStats::blank() { mWerewolfDeprecatedData = false; mIsWerewolf = false; @@ -207,3 +210,5 @@ void ESM::NpcStats::blank() mTimeToStartDrowning = 20; mCrimeId = -1; } + +} diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 5204af7daf..30f6c98f38 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -8,7 +8,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::ObjectState::load (ESMReader &esm) +namespace ESM +{ + +void ObjectState::load (ESMReader &esm) { mVersion = esm.getFormat(); @@ -56,7 +59,7 @@ void ESM::ObjectState::load (ESMReader &esm) esm.getHNOT (mHasCustomState, "HCUS"); } -void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const +void ObjectState::save (ESMWriter &esm, bool inInventory) const { mRef.save (esm, true, inInventory); @@ -91,7 +94,7 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const esm.writeHNT ("HCUS", false); } -void ESM::ObjectState::blank() +void ObjectState::blank() { mRef.blank(); mHasLocals = 0; @@ -106,74 +109,76 @@ void ESM::ObjectState::blank() mHasCustomState = true; } -const ESM::NpcState& ESM::ObjectState::asNpcState() const +const NpcState& ObjectState::asNpcState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to NpcState"; throw std::logic_error(error.str()); } -ESM::NpcState& ESM::ObjectState::asNpcState() +NpcState& ObjectState::asNpcState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to NpcState"; throw std::logic_error(error.str()); } -const ESM::CreatureState& ESM::ObjectState::asCreatureState() const +const CreatureState& ObjectState::asCreatureState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureState"; throw std::logic_error(error.str()); } -ESM::CreatureState& ESM::ObjectState::asCreatureState() +CreatureState& ObjectState::asCreatureState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureState"; throw std::logic_error(error.str()); } -const ESM::ContainerState& ESM::ObjectState::asContainerState() const +const ContainerState& ObjectState::asContainerState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to ContainerState"; throw std::logic_error(error.str()); } -ESM::ContainerState& ESM::ObjectState::asContainerState() +ContainerState& ObjectState::asContainerState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to ContainerState"; throw std::logic_error(error.str()); } -const ESM::DoorState& ESM::ObjectState::asDoorState() const +const DoorState& ObjectState::asDoorState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to DoorState"; throw std::logic_error(error.str()); } -ESM::DoorState& ESM::ObjectState::asDoorState() +DoorState& ObjectState::asDoorState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to DoorState"; throw std::logic_error(error.str()); } -const ESM::CreatureLevListState& ESM::ObjectState::asCreatureLevListState() const +const CreatureLevListState& ObjectState::asCreatureLevListState() const { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureLevListState"; throw std::logic_error(error.str()); } -ESM::CreatureLevListState& ESM::ObjectState::asCreatureLevListState() +CreatureLevListState& ObjectState::asCreatureLevListState() { std::stringstream error; error << "bad cast " << typeid(this).name() << " to CreatureLevListState"; throw std::logic_error(error.str()); } -ESM::ObjectState::~ObjectState() {} +ObjectState::~ObjectState() {} + +} diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index eb09e2e854..5a9e85b71d 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -31,7 +31,7 @@ namespace ESM LuaScripts mLuaScripts; unsigned char mEnabled; int mCount; - ESM::Position mPosition; + Position mPosition; unsigned int mFlags; // Is there any class-specific state following the ObjectState @@ -39,7 +39,7 @@ namespace ESM unsigned int mVersion; - ESM::AnimationState mAnimationState; + AnimationState mAnimationState; ObjectState() : mHasLocals(0), mEnabled(0), mCount(0) diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index 1839a45749..fbf6afec93 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -3,7 +3,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::Player::load (ESMReader &esm) +namespace ESM +{ + +void Player::load (ESMReader &esm) { mObject.mRef.loadId(esm, true); mObject.load (esm); @@ -50,7 +53,7 @@ void ESM::Player::load (ESMReader &esm) bool clearModified = esm.getFormat() < 17 && !mObject.mNpcStats.mIsWerewolf; if (esm.hasMoreSubs()) { - for (int i=0; i attribute; attribute.load(esm, intFallback); @@ -60,7 +63,7 @@ void ESM::Player::load (ESMReader &esm) if (mObject.mNpcStats.mIsWerewolf) mObject.mCreatureStats.mAttributes[i] = attribute; } - for (int i=0; i skill; skill.load(esm, intFallback); @@ -69,7 +72,7 @@ void ESM::Player::load (ESMReader &esm) mSaveSkills[i] = skill.mBase + skill.mMod - skill.mDamage; if (mObject.mNpcStats.mIsWerewolf) { - if(i == ESM::Skill::Acrobatics) + if(i == Skill::Acrobatics) mSetWerewolfAcrobatics = mObject.mNpcStats.mSkills[i].mBase != skill.mBase; mObject.mNpcStats.mSkills[i] = skill; } @@ -84,7 +87,7 @@ void ESM::Player::load (ESMReader &esm) } } -void ESM::Player::save (ESMWriter &esm) const +void Player::save (ESMWriter &esm) const { mObject.save (esm); @@ -112,3 +115,5 @@ void ESM::Player::save (ESMWriter &esm) const esm.writeHNT("WWAT", mSaveAttributes); esm.writeHNT("WWSK", mSaveSkills); } + +} diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index d69571705d..6e914b0e9a 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -24,15 +24,15 @@ namespace ESM float mLastKnownExteriorPosition[3]; unsigned char mHasMark; bool mSetWerewolfAcrobatics; - ESM::Position mMarkedPosition; + Position mMarkedPosition; CellId mMarkedCell; std::string mBirthsign; int mCurrentCrimeId; int mPaidCrimeId; - float mSaveAttributes[ESM::Attribute::Length]; - float mSaveSkills[ESM::Skill::Length]; + float mSaveAttributes[Attribute::Length]; + float mSaveSkills[Skill::Length]; typedef std::map PreviousItems; // previous equipped items, needed for bound spells PreviousItems mPreviousItems; diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index 3421c19526..6a2fcc6675 100644 --- a/components/esm3/projectilestate.cpp +++ b/components/esm3/projectilestate.cpp @@ -38,7 +38,7 @@ namespace ESM mSpellId = esm.getHNString("SPEL"); if (esm.isNextSub("SRCN")) // for backwards compatibility esm.skipHSub(); - ESM::EffectList().load(esm); // for backwards compatibility + EffectList().load(esm); // for backwards compatibility esm.getHNT (mSpeed, "SPED"); if(esm.getFormat() < 17) mSlot = 0; diff --git a/components/esm3/queststate.cpp b/components/esm3/queststate.cpp index 5408cd2ffd..af7ed81f9e 100644 --- a/components/esm3/queststate.cpp +++ b/components/esm3/queststate.cpp @@ -3,16 +3,21 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::QuestState::load (ESMReader &esm) +namespace ESM +{ + +void QuestState::load (ESMReader &esm) { mTopic = esm.getHNString ("YETO"); esm.getHNOT (mState, "QSTA"); esm.getHNOT (mFinished, "QFIN"); } -void ESM::QuestState::save (ESMWriter &esm) const +void QuestState::save (ESMWriter &esm) const { esm.writeHNString ("YETO", mTopic); esm.writeHNT ("QSTA", mState); esm.writeHNT ("QFIN", mFinished); } + +} diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 94ea075759..9a4997842c 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -3,10 +3,13 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 21; +namespace ESM +{ + +unsigned int SavedGame::sRecordId = REC_SAVE; +int SavedGame::sCurrentFormat = 21; -void ESM::SavedGame::load (ESMReader &esm) +void SavedGame::load (ESMReader &esm) { mPlayerName = esm.getHNString("PLNA"); esm.getHNOT (mPlayerLevel, "PLLE"); @@ -28,7 +31,7 @@ void ESM::SavedGame::load (ESMReader &esm) esm.getExact(mScreenshot.data(), mScreenshot.size()); } -void ESM::SavedGame::save (ESMWriter &esm) const +void SavedGame::save (ESMWriter &esm) const { esm.writeHNString ("PLNA", mPlayerName); esm.writeHNT ("PLLE", mPlayerLevel); @@ -51,3 +54,5 @@ void ESM::SavedGame::save (ESMWriter &esm) const esm.write(&mScreenshot[0], mScreenshot.size()); esm.endRecord("SCRN"); } + +} diff --git a/components/esm3/spelllist.cpp b/components/esm3/spelllist.cpp index 71c7b340d2..168a8c7448 100644 --- a/components/esm3/spelllist.cpp +++ b/components/esm3/spelllist.cpp @@ -3,7 +3,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -namespace ESM { +namespace ESM +{ void SpellList::add(ESMReader &esm) { diff --git a/components/esm3/statstate.cpp b/components/esm3/statstate.cpp index 30d39e3c67..4e37df7d79 100644 --- a/components/esm3/statstate.cpp +++ b/components/esm3/statstate.cpp @@ -70,7 +70,7 @@ namespace ESM if (mProgress) esm.writeHNT("STPR", mProgress); } -} -template struct ESM::StatState; -template struct ESM::StatState; + template struct StatState; + template struct StatState; +} diff --git a/components/esm3/stolenitems.hpp b/components/esm3/stolenitems.hpp index 928fbbf757..cd7fe5a6c8 100644 --- a/components/esm3/stolenitems.hpp +++ b/components/esm3/stolenitems.hpp @@ -15,8 +15,8 @@ namespace ESM typedef std::map, int> > StolenItemsMap; StolenItemsMap mStolenItems; - void load(ESM::ESMReader& esm); - void write(ESM::ESMWriter& esm) const; + void load(ESMReader& esm); + void write(ESMWriter& esm) const; }; } diff --git a/components/esm3/transport.cpp b/components/esm3/transport.cpp index 40f831e748..b79818a228 100644 --- a/components/esm3/transport.cpp +++ b/components/esm3/transport.cpp @@ -10,13 +10,13 @@ namespace ESM void Transport::add(ESMReader &esm) { - if (esm.retSubName().toInt() == ESM::fourCC("DODT")) + if (esm.retSubName().toInt() == fourCC("DODT")) { Dest dodt; esm.getHExact(&dodt.mPos, 24); mList.push_back(dodt); } - else if (esm.retSubName().toInt() == ESM::fourCC("DNAM")) + else if (esm.retSubName().toInt() == fourCC("DNAM")) { const std::string name = esm.getHString(); if (mList.empty()) diff --git a/components/esm3/variant.cpp b/components/esm3/variant.cpp index c50581327d..b7dc293f53 100644 --- a/components/esm3/variant.cpp +++ b/components/esm3/variant.cpp @@ -8,12 +8,14 @@ #include "components/esm/defs.hpp" +namespace ESM +{ namespace { - constexpr uint32_t STRV = ESM::fourCC("STRV"); - constexpr uint32_t INTV = ESM::fourCC("INTV"); - constexpr uint32_t FLTV = ESM::fourCC("FLTV"); - constexpr uint32_t STTV = ESM::fourCC("STTV"); + constexpr uint32_t STRV = fourCC("STRV"); + constexpr uint32_t INTV = fourCC("INTV"); + constexpr uint32_t FLTV = fourCC("FLTV"); + constexpr uint32_t STTV = fourCC("STTV"); template struct GetValue @@ -48,22 +50,22 @@ namespace }; } -const std::string& ESM::Variant::getString() const +const std::string& Variant::getString() const { return std::get(mData); } -int ESM::Variant::getInteger() const +int Variant::getInteger() const { return std::visit(GetValue{}, mData); } -float ESM::Variant::getFloat() const +float Variant::getFloat() const { return std::visit(GetValue{}, mData); } -void ESM::Variant::read (ESMReader& esm, Format format) +void Variant::read (ESMReader& esm, Format format) { // type VarType type = VT_Unknown; @@ -152,7 +154,7 @@ void ESM::Variant::read (ESMReader& esm, Format format) std::visit(ReadESMVariantValue {esm, format, mType}, mData); } -void ESM::Variant::write (ESMWriter& esm, Format format) const +void Variant::write (ESMWriter& esm, Format format) const { if (mType==VT_Unknown) { @@ -175,7 +177,7 @@ void ESM::Variant::write (ESMWriter& esm, Format format) const std::visit(WriteESMVariantValue {esm, format, mType}, mData); } -void ESM::Variant::write (std::ostream& stream) const +void Variant::write (std::ostream& stream) const { switch (mType) { @@ -216,7 +218,7 @@ void ESM::Variant::write (std::ostream& stream) const } } -void ESM::Variant::setType (VarType type) +void Variant::setType (VarType type) { if (type!=mType) { @@ -246,28 +248,30 @@ void ESM::Variant::setType (VarType type) } } -void ESM::Variant::setString (const std::string& value) +void Variant::setString (const std::string& value) { std::get(mData) = value; } -void ESM::Variant::setString (std::string&& value) +void Variant::setString (std::string&& value) { std::get(mData) = std::move(value); } -void ESM::Variant::setInteger (int value) +void Variant::setInteger (int value) { std::visit(SetValue(value), mData); } -void ESM::Variant::setFloat (float value) +void Variant::setFloat (float value) { std::visit(SetValue(value), mData); } -std::ostream& ESM::operator<< (std::ostream& stream, const Variant& value) +std::ostream& operator<< (std::ostream& stream, const Variant& value) { value.write (stream); return stream; } + +} diff --git a/components/esm3/variantimp.cpp b/components/esm3/variantimp.cpp index df491e7cec..7116ce7a74 100644 --- a/components/esm3/variantimp.cpp +++ b/components/esm3/variantimp.cpp @@ -7,7 +7,10 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, std::string& out) +namespace ESM +{ + +void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, std::string& out) { if (type!=VT_String) throw std::logic_error ("not a string type"); @@ -25,7 +28,7 @@ void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType ty out = esm.getHString(); } -void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, const std::string& in) +void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, const std::string& in) { if (type!=VT_String) throw std::logic_error ("not a string type"); @@ -43,7 +46,7 @@ void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType t esm.writeHNString("STRV", in); } -void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out) +void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out) { if (type!=VT_Short && type!=VT_Long && type!=VT_Int) throw std::logic_error ("not an integer type"); @@ -93,7 +96,7 @@ void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType ty } } -void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in) +void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in) { if (type!=VT_Short && type!=VT_Long && type!=VT_Int) throw std::logic_error ("not an integer type"); @@ -133,7 +136,7 @@ void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType t } } -void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, float& out) +void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, float& out) { if (type!=VT_Float) throw std::logic_error ("not a float type"); @@ -148,7 +151,7 @@ void ESM::readESMVariantValue(ESMReader& esm, Variant::Format format, VarType ty } } -void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, float in) +void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, float in) { if (type!=VT_Float) throw std::logic_error ("not a float type"); @@ -163,3 +166,5 @@ void ESM::writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType t esm.writeHNT("FLTV", in); } } + +} diff --git a/components/esm3/weatherstate.cpp b/components/esm3/weatherstate.cpp index c791aac145..b149c2bfe0 100644 --- a/components/esm3/weatherstate.cpp +++ b/components/esm3/weatherstate.cpp @@ -3,19 +3,22 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +namespace ESM +{ namespace { - constexpr ESM::NAME currentRegionRecord = "CREG"; - constexpr ESM::NAME timePassedRecord = "TMPS"; - constexpr ESM::NAME fastForwardRecord = "FAST"; - constexpr ESM::NAME weatherUpdateTimeRecord = "WUPD"; - constexpr ESM::NAME transitionFactorRecord = "TRFC"; - constexpr ESM::NAME currentWeatherRecord = "CWTH"; - constexpr ESM::NAME nextWeatherRecord = "NWTH"; - constexpr ESM::NAME queuedWeatherRecord = "QWTH"; - constexpr ESM::NAME regionNameRecord = "RGNN"; - constexpr ESM::NAME regionWeatherRecord = "RGNW"; - constexpr ESM::NAME regionChanceRecord = "RGNC"; + constexpr NAME currentRegionRecord = "CREG"; + constexpr NAME timePassedRecord = "TMPS"; + constexpr NAME fastForwardRecord = "FAST"; + constexpr NAME weatherUpdateTimeRecord = "WUPD"; + constexpr NAME transitionFactorRecord = "TRFC"; + constexpr NAME currentWeatherRecord = "CWTH"; + constexpr NAME nextWeatherRecord = "NWTH"; + constexpr NAME queuedWeatherRecord = "QWTH"; + constexpr NAME regionNameRecord = "RGNN"; + constexpr NAME regionWeatherRecord = "RGNW"; + constexpr NAME regionChanceRecord = "RGNC"; +} } namespace ESM From 1a41cefab343bb0341d29e20e8d13aa558327eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Tue, 12 Apr 2022 18:25:03 +0300 Subject: [PATCH 2301/2859] Use active package in predictAndAvoidCollisions --- apps/openmw/mwmechanics/actors.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 963fd7b806..01c404c563 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1234,8 +1234,8 @@ namespace MWMechanics const float maxTimeToCheck = 2.0f; static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game"); - MWWorld::Ptr player = getPlayer(); - MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Ptr player = getPlayer(); + const MWBase::World* world = MWBase::Environment::get().getWorld(); for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Ptr& ptr = iter->first; @@ -1260,23 +1260,26 @@ namespace MWMechanics bool shouldTurnToApproachingActor = !isMoving; MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets). const auto& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - for (const auto& package : aiSequence) + if (!aiSequence.isEmpty()) { - if (package->getTypeId() == AiPackageTypeId::Follow) + const auto& package = aiSequence.getActivePackage(); + if (package.getTypeId() == AiPackageTypeId::Follow) + { shouldAvoidCollision = true; - else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) + } + else if (package.getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) { - if (!static_cast(package.get())->isStationary()) + if (!static_cast(package).isStationary()) shouldGiveWay = true; } - else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue) + else if (package.getTypeId() == AiPackageTypeId::Combat || package.getTypeId() == AiPackageTypeId::Pursue) { - currentTarget = package->getTarget(); + currentTarget = package.getTarget(); shouldAvoidCollision = isMoving; shouldTurnToApproachingActor = false; - break; } } + if (!shouldAvoidCollision && !shouldGiveWay) continue; From b2739dc84a8b3e38eaf6cd2c26a6036687072401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Tue, 12 Apr 2022 18:48:27 +0300 Subject: [PATCH 2302/2859] Avoid looking up settings from gmst each frame --- apps/openmw/mwmechanics/combat.cpp | 53 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 27582840df..e18fb892dc 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -1,3 +1,4 @@ + #include "combat.hpp" #include @@ -87,9 +88,11 @@ namespace MWMechanics osg::Vec3f(0,0,1))); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - if (angleDegrees < gmst.find("fCombatBlockLeftAngle")->mValue.getFloat()) + static const float fCombatBlockLeftAngle = gmst.find("fCombatBlockLeftAngle")->mValue.getFloat(); + if (angleDegrees < fCombatBlockLeftAngle) return false; - if (angleDegrees > gmst.find("fCombatBlockRightAngle")->mValue.getFloat()) + static const float fCombatBlockRightAngle = gmst.find("fCombatBlockRightAngle")->mValue.getFloat(); + if (angleDegrees > fCombatBlockRightAngle) return false; MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); @@ -97,11 +100,16 @@ namespace MWMechanics float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2f * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() + 0.1f * blockerStats.getAttribute(ESM::Attribute::Luck).getModified(); float enemySwing = attackStrength; - float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->mValue.getFloat() + gmst.find("fSwingBlockBase")->mValue.getFloat(); + static const float fSwingBlockMult = gmst.find("fSwingBlockMult")->mValue.getFloat(); + static const float fSwingBlockBase = gmst.find("fSwingBlockBase")->mValue.getFloat(); + float swingTerm = enemySwing * fSwingBlockMult + fSwingBlockBase; float blockerTerm = blockTerm * swingTerm; if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0) - blockerTerm *= gmst.find("fBlockStillBonus")->mValue.getFloat(); + { + static const float fBlockStillBonus = gmst.find("fBlockStillBonus")->mValue.getFloat(); + blockerTerm *= fBlockStillBonus; + } blockerTerm *= blockerStats.getFatigueTerm(); float attackerSkill = 0; @@ -113,8 +121,8 @@ namespace MWMechanics + 0.1f * attackerStats.getAttribute(ESM::Attribute::Luck).getModified(); attackerTerm *= attackerStats.getFatigueTerm(); - const int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); - const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); + static const int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); + static const int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); int x = std::clamp(blockerTerm - attackerTerm, iBlockMinChance, iBlockMaxChance); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); @@ -128,9 +136,9 @@ namespace MWMechanics if (shieldhealth == 0) inv.unequipItem(*shield, blocker); // Reduce blocker fatigue - const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat(); - const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat(); - const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->mValue.getFloat(); + static const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat(); + static const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat(); + static const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->mValue.getFloat(); MWMechanics::DynamicStat fatigue = blockerStats.getFatigue(); float normalizedEncumbrance = blocker.getClass().getNormalizedEncumbrance(blocker); normalizedEncumbrance = std::min(1.f, normalizedEncumbrance); @@ -248,7 +256,8 @@ namespace MWMechanics bool knockedDown = victim.getClass().getCreatureStats(victim).getKnockedDown(); if (knockedDown || unaware) { - damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat(); + static const float fCombatKODamageMult = gmst.find("fCombatKODamageMult")->mValue.getFloat(); + damage *= fCombatKODamageMult; if (!knockedDown) MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } @@ -262,7 +271,7 @@ namespace MWMechanics // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory if (victim != getPlayer() && !appliedEnchantment) { - float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); + static const float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); if (Misc::Rng::rollProbability(world->getPrng()) < fProjectileThrownStoreChance / 100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } @@ -293,11 +302,12 @@ namespace MWMechanics { defenseTerm = victimStats.getEvasion(); } + static const float fCombatInvisoMult = gmst.find("fCombatInvisoMult")->mValue.getFloat(); defenseTerm += std::min(100.f, - gmst.find("fCombatInvisoMult")->mValue.getFloat() * + fCombatInvisoMult * victimStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude()); defenseTerm += std::min(100.f, - gmst.find("fCombatInvisoMult")->mValue.getFloat() * + fCombatInvisoMult * victimStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude()); } float attackTerm = skillValue + @@ -415,8 +425,8 @@ namespace MWMechanics void getHandToHandDamage(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, float &damage, bool &healthdmg, float attackStrength) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - float minstrike = store.get().find("fMinHandToHandMult")->mValue.getFloat(); - float maxstrike = store.get().find("fMaxHandToHandMult")->mValue.getFloat(); + static const float minstrike = store.get().find("fMinHandToHandMult")->mValue.getFloat(); + static const float maxstrike = store.get().find("fMaxHandToHandMult")->mValue.getFloat(); damage = static_cast(attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand)); damage *= minstrike + ((maxstrike-minstrike)*attackStrength); @@ -440,8 +450,11 @@ namespace MWMechanics // GLOB instead of GMST because it gets updated during a quest damage *= MWBase::Environment::get().getWorld()->getGlobalFloat("werewolfclawmult"); } - if(healthdmg) - damage *= store.get().find("fHandtoHandHealthPer")->mValue.getFloat(); + if (healthdmg) + { + static const float fHandtoHandHealthPer = store.get().find("fHandtoHandHealthPer")->mValue.getFloat(); + damage *= fHandtoHandHealthPer; + } MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(isWerewolf) @@ -459,9 +472,9 @@ namespace MWMechanics { // somewhat of a guess, but using the weapon weight makes sense const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); - const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat(); - const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat(); - const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->mValue.getFloat(); + static const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat(); + static const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat(); + static const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->mValue.getFloat(); CreatureStats& stats = attacker.getClass().getCreatureStats(attacker); MWMechanics::DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = attacker.getClass().getNormalizedEncumbrance(attacker); From 165f146e694815d494e64c6059d4b4557c902e11 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Tue, 12 Apr 2022 13:07:15 -0400 Subject: [PATCH 2303/2859] Added ICU runtime DLLs to Windows builds --- CI/before_script.msvc.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 5cfff39b3d..7c9ea20bd1 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1039,16 +1039,22 @@ echo # ICU printf "ICU ${ICU_VER/_/.}... " { - if [ -d ICU ]; then + if [ -d ICU-${ICU_VER} ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf ICU - eval 7z x -y icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip -o$(real_pwd)/ICU $STRIP + rm -rf ICU-${ICU_VER} + eval 7z x -y icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip -o$(real_pwd)/ICU-${ICU_VER} $STRIP fi - export ICU_ROOT="$(real_pwd)/ICU" + export ICU_ROOT="$(real_pwd)/ICU-${ICU_VER}" add_cmake_opts -DICU_INCLUDE_DIR="${ICU_ROOT}/include" \ -DICU_LIBRARY="${ICU_ROOT}/lib${BITS}/icuuc.lib " \ -DICU_DEBUG=ON + + for config in ${CONFIGURATIONS[@]}; do + add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icudt${ICU_VER/_*/}.dll" + add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icuin${ICU_VER/_*/}.dll" + add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icuuc${ICU_VER/_*/}.dll" + done echo Done. } From 194c11f214dad1147fb993cf0a0f89abf309ff7c Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 12 Apr 2022 17:27:59 +0200 Subject: [PATCH 2304/2859] Fix loading order in EsmLoader Need to load the last present record from a sequence of loaded records. That means reverse should be called before unique or unique should be applied for a reversed range. Since unique keeps only the first element from a sub sequence of equal elements. Use forEachUnique with reversed range to avoid redundant container modifications. --- apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/esmloader/record.cpp | 105 ++++++++++++++++++++ components/esmloader/record.hpp | 7 +- 3 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 apps/openmw_test_suite/esmloader/record.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 3b12e7c227..74418a004b 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -67,6 +67,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) esmloader/load.cpp esmloader/esmdata.cpp + esmloader/record.cpp files/hash.cpp diff --git a/apps/openmw_test_suite/esmloader/record.cpp b/apps/openmw_test_suite/esmloader/record.cpp new file mode 100644 index 0000000000..fbbbdf18d8 --- /dev/null +++ b/apps/openmw_test_suite/esmloader/record.cpp @@ -0,0 +1,105 @@ +#include + +#include +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace EsmLoader; + + struct Value + { + int mKey; + int mValue; + }; + + auto tie(const Value& v) + { + return std::tie(v.mKey, v.mValue); + } + + bool operator==(const Value& l, const Value& r) + { + return tie(l) == tie(r); + } + + std::ostream& operator<<(std::ostream& s, const Value& v) + { + return s << "Value {" << v.mKey << ", " << v.mValue << "}"; + } + + Record present(const Value& v) + { + return Record(false, v); + } + + Record deleted(const Value& v) + { + return Record(true, v); + } + + struct Params + { + Records mRecords; + std::vector mResult; + }; + + struct EsmLoaderPrepareRecordTest : TestWithParam {}; + + TEST_P(EsmLoaderPrepareRecordTest, prepareRecords) + { + auto records = GetParam().mRecords; + const auto getKey = [&] (const Record& v) { return v.mValue.mKey; }; + EXPECT_THAT(prepareRecords(records, getKey), ElementsAreArray(GetParam().mResult)); + } + + const std::array params = { + Params {{}, {}}, + Params { + {present(Value {1, 1})}, + {Value {1, 1}} + }, + Params { + {deleted(Value {1, 1})}, + {} + }, + Params { + {present(Value {1, 1}), present(Value {2, 2})}, + {Value {1, 1}, Value {2, 2}} + }, + Params { + {present(Value {2, 2}), present(Value {1, 1})}, + {Value {1, 1}, Value {2, 2}} + }, + Params { + {present(Value {1, 1}), present(Value {1, 2})}, + {Value {1, 2}} + }, + Params { + {present(Value {1, 2}), present(Value {1, 1})}, + {Value {1, 1}} + }, + Params { + {present(Value {1, 1}), deleted(Value {1, 2})}, + {} + }, + Params { + {deleted(Value {1, 1}), present(Value {1, 2})}, + {Value {1, 2}} + }, + Params { + {present(Value {1, 2}), deleted(Value {1, 1})}, + {} + }, + Params { + {deleted(Value {1, 2}), present(Value {1, 1})}, + {Value {1, 1}} + }, + }; + + INSTANTIATE_TEST_SUITE_P(Params, EsmLoaderPrepareRecordTest, ValuesIn(params)); +} diff --git a/components/esmloader/record.hpp b/components/esmloader/record.hpp index b88991cca1..ec9705e823 100644 --- a/components/esmloader/record.hpp +++ b/components/esmloader/record.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_ESMLOADER_RECORD_H #include +#include #include #include @@ -31,12 +32,12 @@ namespace EsmLoader const auto greaterByKey = [&] (const auto& l, const auto& r) { return getKey(r) < getKey(l); }; const auto equalByKey = [&] (const auto& l, const auto& r) { return getKey(l) == getKey(r); }; std::stable_sort(records.begin(), records.end(), greaterByKey); - records.erase(std::unique(records.begin(), records.end(), equalByKey), records.end()); - std::reverse(records.begin(), records.end()); std::vector result; - for (Record& v : records) + Misc::forEachUnique(records.rbegin(), records.rend(), equalByKey, [&] (const auto& v) + { if (!v.mDeleted) result.emplace_back(std::move(v.mValue)); + }); return result; } } From 45c9635e94dcd20c5924060b3c43c5536060fac7 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 9 Apr 2022 20:52:05 +0200 Subject: [PATCH 2305/2859] Fix getmetatable, fix pairsForReadonly, add util.loadCode, _G --- components/lua/luastate.cpp | 42 ++++++++++++++++++++-------------- components/lua/utilpackage.cpp | 13 +++++++++++ files/lua_api/openmw/util.lua | 7 ++++++ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 4ede81cc02..38f3ae889e 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -46,7 +46,7 @@ namespace LuaUtil static const std::string safeFunctions[] = { "assert", "error", "ipairs", "next", "pairs", "pcall", "select", "tonumber", "tostring", - "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "getmetatable", "setmetatable"}; + "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable"}; static const std::string safePackages[] = {"coroutine", "math", "string", "table"}; LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf) : mConf(conf), mVFS(vfs) @@ -58,7 +58,6 @@ namespace LuaUtil mLua["math"]["randomseed"] = []{}; mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; - mLua["cmetatable"] = [](const sol::table& v) -> sol::object { return v[sol::metatable_key]; }; // Some fixes for compatibility between different Lua versions if (mLua["unpack"] == sol::nil) @@ -70,29 +69,35 @@ namespace LuaUtil mLua.script(R"( local _pairs = pairs local _ipairs = ipairs - local _cmeta = cmetatable - pairs = function(v) return ((_cmeta(v) or v).__pairs or _pairs)(v) end - ipairs = function(v) return ((_cmeta(v) or v).__ipairs or _ipairs)(v) end + pairs = function(v) return (rawget(getmetatable(v) or {}, '__pairs') or _pairs)(v) end + ipairs = function(v) return (rawget(getmetatable(v) or {}, '__ipairs') or _ipairs)(v) end )"); } mLua.script(R"( - local _pairs = pairs - local _ipairs = ipairs - local _tostring = tostring - local _write = writeToLog - local printToLog = function(name, ...) - local msg = name - for _, v in _ipairs({...}) do - msg = msg .. '\t' .. _tostring(v) + local printToLog = function(...) + local strs = {} + for i = 1, select('#', ...) do + strs[i] = tostring(select(i, ...)) end - return _write(msg) + return writeToLog(table.concat(strs, '\t')) end printGen = function(name) return function(...) return printToLog(name, ...) end end - local _cmeta = cmetatable - function pairsForReadOnly(v) return _pairs(_cmeta(v).__index) end - function ipairsForReadOnly(v) return _ipairs(_cmeta(v).__index) end + function pairsForReadOnly(v) + local nextFn, t, firstKey = pairs(getmetatable(v).__index) + return function(_, k) return nextFn(t, k) end, v, firstKey + end + function ipairsForReadOnly(v) + local nextFn, t, firstKey = ipairs(getmetatable(v).__index) + return function(_, k) return nextFn(t, k) end, v, firstKey + end + + getmetatable('').__metatable = false + getSafeMetatable = function(v) + if type(v) ~= 'table' then error('getmetatable is allowed only for tables', 2) end + return getmetatable(v) + end )"); mSandboxEnv = sol::table(mLua, sol::create); @@ -107,6 +112,7 @@ namespace LuaUtil if (mLua[s] == sol::nil) throw std::logic_error("Lua package not found: " + s); mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]); } + mSandboxEnv["getmetatable"] = mLua["getSafeMetatable"]; mCommonPackages["os"] = mSandboxEnv["os"] = makeReadOnly(tableFromPairs({ {"date", mLua["os"]["date"]}, {"difftime", mLua["os"]["difftime"]}, @@ -162,6 +168,8 @@ namespace LuaUtil sol::environment env(mLua, sol::create, mSandboxEnv); std::string envName = namePrefix + "[" + path + "]:"; env["print"] = mLua["printGen"](envName); + env["_G"] = env; + env[sol::metatable_key]["__metatable"] = false; auto maybeRunLoader = [&hiddenData](const sol::object& package) -> sol::object { diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index 2eda03dc52..7f52a770a1 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -247,6 +247,19 @@ namespace LuaUtil util["bitNot"] = [](unsigned a) { return ~a; }; } + util["loadCode"] = [](const std::string& code, const sol::table& env, sol::this_state s) + { + sol::state_view lua(s); + sol::load_result res = lua.load(code, "", sol::load_mode::text); + if (!res.valid()) + throw std::runtime_error("Lua error: " + res.get()); + sol::function fn = res; + sol::environment newEnv(lua, sol::create, env); + newEnv[sol::metatable_key][sol::meta_function::new_index] = env; + sol::set_environment(newEnv, fn); + return fn; + }; + return util; } diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index dbb1cbd8ae..3dd959b4a5 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -25,6 +25,13 @@ -- @param #table table Any table. -- @return #table The same table wrapped with read only userdata. +--- +-- Parses Lua code from string and returns as a function. +-- @function [parent=#util] loadCode +-- @param #string code Lua code. +-- @param #table table Environment to run the code in. +-- @return #function The loaded code. + --- -- Bitwise And (supports any number of arguments). -- @function [parent=#util] bitAnd From b09570692e351ec39251d8861218dd624db2e462 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 14 Apr 2022 22:12:59 +0200 Subject: [PATCH 2306/2859] Use ifstream for ESMReader ESMReader reads the whole file, there is no need in the ConstrainedFileStream. --- apps/openmw/mwworld/esmloader.cpp | 2 +- apps/openmw_test_suite/esm/variant.cpp | 2 +- apps/openmw_test_suite/mwworld/test_store.cpp | 21 +++++++------------ components/CMakeLists.txt | 2 +- components/esm3/esmreader.cpp | 14 +++++++------ components/esm3/esmreader.hpp | 10 ++++----- components/esmloader/load.cpp | 4 +--- components/files/openfile.cpp | 16 ++++++++++++++ components/files/openfile.hpp | 13 ++++++++++++ 9 files changed, 54 insertions(+), 30 deletions(-) create mode 100644 components/files/openfile.cpp create mode 100644 components/files/openfile.hpp diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 8369523dca..41a8dc478c 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -21,7 +21,7 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index, Loadin lEsm.setIndex(index); lEsm.open(filepath.string()); lEsm.resolveParentFileIndices(mEsm); - mEsm[index] = lEsm; + mEsm[index] = std::move(lEsm); mStore.load(mEsm[index], listener); } diff --git a/apps/openmw_test_suite/esm/variant.cpp b/apps/openmw_test_suite/esm/variant.cpp index 287540c1e0..53f31cc1c9 100644 --- a/apps/openmw_test_suite/esm/variant.cpp +++ b/apps/openmw_test_suite/esm/variant.cpp @@ -436,7 +436,7 @@ namespace { Variant result; ESMReader reader; - reader.open(std::make_shared(data), ""); + reader.open(std::make_unique(data), ""); result.read(reader, format); return result; } diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index bf1a40f7d9..a789e05a5d 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -224,17 +224,17 @@ protected: /// Create an ESM file in-memory containing the specified record. /// @param deleted Write record with deleted flag? template -Files::IStreamPtr getEsmFile(T record, bool deleted) +std::unique_ptr getEsmFile(T record, bool deleted) { ESM::ESMWriter writer; - auto* stream = new std::stringstream; + auto stream = std::make_unique(); writer.setFormat(0); writer.save(*stream); writer.startRecord(T::sRecordId); record.save(writer, deleted); writer.endRecord(T::sRecordId); - return Files::IStreamPtr(stream); + return stream; } /// Tests deletion of records. @@ -251,16 +251,14 @@ TEST_F(StoreTest, delete_test) ESM::ESMReader reader; // master file inserts a record - Files::IStreamPtr file = getEsmFile(record, false); - reader.open(file, "filename"); + reader.open(getEsmFile(record, false), "filename"); mEsmStore.load(reader, &dummyListener); mEsmStore.setUp(); ASSERT_TRUE (mEsmStore.get().getSize() == 1); // now a plugin deletes it - file = getEsmFile(record, true); - reader.open(file, "filename"); + reader.open(getEsmFile(record, true), "filename"); mEsmStore.load(reader, &dummyListener); mEsmStore.setUp(); @@ -268,8 +266,7 @@ TEST_F(StoreTest, delete_test) // now another plugin inserts it again // expected behaviour is the record to reappear rather than staying deleted - file = getEsmFile(record, false); - reader.open(file, "filename"); + reader.open(getEsmFile(record, false), "filename"); mEsmStore.load(reader, &dummyListener); mEsmStore.setUp(); @@ -291,16 +288,14 @@ TEST_F(StoreTest, overwrite_test) ESM::ESMReader reader; // master file inserts a record - Files::IStreamPtr file = getEsmFile(record, false); - reader.open(file, "filename"); + reader.open(getEsmFile(record, false), "filename"); mEsmStore.load(reader, &dummyListener); mEsmStore.setUp(); // now a plugin overwrites it with changed data record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it record.mModel = "the_new_model"; - file = getEsmFile(record, false); - reader.open(file, "filename"); + reader.open(getEsmFile(record, false), "filename"); mEsmStore.load(reader, &dummyListener); mEsmStore.setUp(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5a3292228e..57a60322d5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -206,7 +206,7 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF() add_component_dir (files linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager - lowlevelfile constrainedfilestream memorystream hash configfileparser + lowlevelfile constrainedfilestream memorystream hash configfileparser openfile ) add_component_dir (compiler diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 2a3ee69464..08cd100ab1 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -2,9 +2,11 @@ #include #include +#include #include #include +#include namespace ESM { @@ -82,10 +84,10 @@ void ESMReader::resolveParentFileIndices(const std::vector& allPlugin } } -void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) +void ESMReader::openRaw(std::unique_ptr&& stream, const std::string& name) { close(); - mEsm = _esm; + mEsm = std::move(stream); mCtx.filename = name; mEsm->seekg(0, mEsm->end); mCtx.leftFile = mFileSize = mEsm->tellg(); @@ -94,12 +96,12 @@ void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) void ESMReader::openRaw(const std::string& filename) { - openRaw(Files::openConstrainedFileStream(filename), filename); + openRaw(Files::openBinaryInputFileStream(filename), filename); } -void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) +void ESMReader::open(std::unique_ptr&& stream, const std::string &name) { - openRaw(_esm, name); + openRaw(std::move(stream), name); if (getRecName() != "TES3") fail("Not a valid Morrowind file"); @@ -111,7 +113,7 @@ void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) void ESMReader::open(const std::string &file) { - open (Files::openConstrainedFileStream(file), file); + open(Files::openBinaryInputFileStream(file), file); } std::string ESMReader::getHNOString(NAME name) diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 7994899db9..2c987ef025 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -3,8 +3,8 @@ #include #include - -#include +#include +#include #include @@ -61,11 +61,11 @@ public: /// Raw opening. Opens the file and sets everything up but doesn't /// parse the header. - void openRaw(Files::IStreamPtr _esm, const std::string &name); + void openRaw(std::unique_ptr&& stream, const std::string &name); /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. - void open(Files::IStreamPtr _esm, const std::string &name); + void open(std::unique_ptr&& stream, const std::string &name); void open(const std::string &file); @@ -295,7 +295,7 @@ private: void clearCtx(); - Files::IStreamPtr mEsm; + std::unique_ptr mEsm; ESM_Context mCtx; diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp index 95011e221c..2e54449a2c 100644 --- a/components/esmloader/load.cpp +++ b/components/esmloader/load.cpp @@ -188,7 +188,7 @@ namespace EsmLoader reader.skipRecord(); } - ESM::ESMReader loadEsm(const Query& query, ESM::ESMReader& reader, ShallowContent& content) + void loadEsm(const Query& query, ESM::ESMReader& reader, ShallowContent& content) { Log(Debug::Info) << "Loading ESM file " << reader.getName(); @@ -198,8 +198,6 @@ namespace EsmLoader reader.getRecHeader(); loadRecord(query, recName, reader, content); } - - return reader; } ShallowContent shallowLoad(const Query& query, const std::vector& contentFiles, diff --git a/components/files/openfile.cpp b/components/files/openfile.cpp new file mode 100644 index 0000000000..61a177b038 --- /dev/null +++ b/components/files/openfile.cpp @@ -0,0 +1,16 @@ +#include "openfile.hpp" + +#include +#include + +namespace Files +{ + std::unique_ptr openBinaryInputFileStream(const std::string& path) + { + auto stream = std::make_unique(path, std::ios::binary); + if (!stream->is_open()) + throw std::runtime_error("Failed to open '" + path + "' for reading: " + std::strerror(errno)); + stream->exceptions(std::ios::badbit); + return stream; + } +} diff --git a/components/files/openfile.hpp b/components/files/openfile.hpp new file mode 100644 index 0000000000..3cecf7bac1 --- /dev/null +++ b/components/files/openfile.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_COMPONENTS_FILES_OPENFILE_H +#define OPENMW_COMPONENTS_FILES_OPENFILE_H + +#include +#include +#include + +namespace Files +{ + std::unique_ptr openBinaryInputFileStream(const std::string& path); +} + +#endif From 3e4c683ba98d961aaa92a574fe9097388eb04a70 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 15 Apr 2022 19:14:18 +0200 Subject: [PATCH 2307/2859] Update CMakeLists.txt after a file was removed. --- apps/openmw/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index f67c991958..6f35e53fd1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager - bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation + bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor ) From fbc853804d0d28cb9b0f4306e29058e76fd93a6b Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Apr 2022 03:06:53 +0200 Subject: [PATCH 2308/2859] Fix recover from errors in Files::getHash --- apps/openmw_test_suite/files/hash.cpp | 10 ++++++++++ components/files/hash.cpp | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/files/hash.cpp b/apps/openmw_test_suite/files/hash.cpp index f87edd4e6e..645258f60e 100644 --- a/apps/openmw_test_suite/files/hash.cpp +++ b/apps/openmw_test_suite/files/hash.cpp @@ -22,6 +22,16 @@ namespace struct FilesGetHash : TestWithParam {}; + TEST(FilesGetHash, shouldClearErrors) + { + const std::string fileName = "fileName"; + std::string content; + 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)); + } + TEST_P(FilesGetHash, shouldReturnHashForStringStream) { const std::string fileName = "fileName"; diff --git a/components/files/hash.cpp b/components/files/hash.cpp index 079a169ae5..b11ecbf838 100644 --- a/components/files/hash.cpp +++ b/components/files/hash.cpp @@ -28,8 +28,8 @@ namespace Files MurmurHash3_x64_128(value.data(), static_cast(read), hash.data(), blockHash.data()); hash = blockHash; } - stream.exceptions(exceptions); stream.clear(); + stream.exceptions(exceptions); stream.seekg(start); } catch (const std::exception& e) From 61ea678a96ebd376c358bb0fc349c08f5260abb0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 16 Apr 2022 16:28:39 +0200 Subject: [PATCH 2309/2859] Implement ignored records --- CHANGELOG.md | 1 + apps/openmw/mwworld/esmloader.cpp | 3 ++- apps/openmw/mwworld/esmloader.hpp | 2 ++ apps/openmw/mwworld/esmstore.cpp | 9 ++++++--- apps/openmw/mwworld/esmstore.hpp | 2 +- components/esm/esmcommon.hpp | 3 +++ 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 525cd5807b..30f02bed33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,6 +145,7 @@ Feature #6592: Missing support for NiTriShape particle emitters Feature #6600: Support NiSortAdjustNode Feature #6684: Support NiFltAnimationNode + Feature #6699: Ignored flag Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp Task #6553: Simplify interpreter instruction registration diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 41a8dc478c..d9c7d1389f 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -11,6 +11,7 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read : mEsm(readers) , mStore(store) , mEncoder(encoder) + , mDialogue(nullptr) // A content file containing INFO records without a DIAL record appends them to the previous file's dialogue { } @@ -22,7 +23,7 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index, Loadin lEsm.open(filepath.string()); lEsm.resolveParentFileIndices(mEsm); mEsm[index] = std::move(lEsm); - mStore.load(mEsm[index], listener); + mStore.load(mEsm[index], listener, mDialogue); } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index db50d44146..4dfb48c2d1 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -13,6 +13,7 @@ namespace ToUTF8 namespace ESM { class ESMReader; + struct Dialogue; } namespace MWWorld @@ -31,6 +32,7 @@ struct EsmLoader : public ContentLoader std::vector& mEsm; MWWorld::ESMStore& mStore; ToUTF8::Utf8Encoder* mEncoder; + ESM::Dialogue* mDialogue; }; } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index c670bead9c..bad5685a7f 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -143,12 +143,10 @@ static bool isCacheableRecord(int id) return false; } -void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) +void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener, ESM::Dialogue*& dialogue) { listener->setProgressRange(1000); - ESM::Dialogue *dialogue = nullptr; - // Land texture loading needs to use a separate internal store for each plugin. // We set the number of plugins here so we can properly verify if valid plugin // indices are being passed to the LandTexture Store retrieval methods. @@ -159,6 +157,11 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) { ESM::NAME n = esm.getRecName(); esm.getRecHeader(); + if (esm.getRecordFlags() & ESM::FLAG_Ignored) + { + esm.skipRecord(); + continue; + } // Look up the record type. std::map::iterator it = mStores.find(n.toInt()); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 07f860e6de..8ad374350e 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -196,7 +196,7 @@ namespace MWWorld /// Validate entries in store after loading a save void validateDynamic(); - void load(ESM::ESMReader &esm, Loading::Listener* listener); + void load(ESM::ESMReader &esm, Loading::Listener* listener, ESM::Dialogue*& dialogue); template const Store &get() const { diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 7ca059020f..2685371ec0 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -19,7 +19,10 @@ enum Version enum RecordFlag { + // This flag exists, but is not used to determine if a record has been deleted while loading + FLAG_Deleted = 0x00000020, FLAG_Persistent = 0x00000400, + FLAG_Ignored = 0x00001000, FLAG_Blocked = 0x00002000 }; From 86d7f5a9885732e019939c351a46107a953aa650 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 16 Apr 2022 22:58:08 +0200 Subject: [PATCH 2310/2859] Fix tests --- apps/openmw_test_suite/mwworld/test_store.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index a789e05a5d..c15e238c9b 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -29,13 +29,14 @@ struct ContentFileTest : public ::testing::Test // load the content files int index=0; + ESM::Dialogue* dialogue = nullptr; for (const auto & mContentFile : mContentFiles) { ESM::ESMReader lEsm; lEsm.setEncoder(nullptr); lEsm.setIndex(index); lEsm.open(mContentFile.string()); - mEsmStore.load(lEsm, &dummyListener); + mEsmStore.load(lEsm, &dummyListener, dialogue); ++index; } @@ -249,17 +250,18 @@ TEST_F(StoreTest, delete_test) record.mId = recordId; ESM::ESMReader reader; + ESM::Dialogue* dialogue = nullptr; // master file inserts a record reader.open(getEsmFile(record, false), "filename"); - mEsmStore.load(reader, &dummyListener); + mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); ASSERT_TRUE (mEsmStore.get().getSize() == 1); // now a plugin deletes it reader.open(getEsmFile(record, true), "filename"); - mEsmStore.load(reader, &dummyListener); + mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); ASSERT_TRUE (mEsmStore.get().getSize() == 0); @@ -267,7 +269,7 @@ TEST_F(StoreTest, delete_test) // now another plugin inserts it again // expected behaviour is the record to reappear rather than staying deleted reader.open(getEsmFile(record, false), "filename"); - mEsmStore.load(reader, &dummyListener); + mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); ASSERT_TRUE (mEsmStore.get().getSize() == 1); @@ -286,17 +288,18 @@ TEST_F(StoreTest, overwrite_test) record.mId = recordId; ESM::ESMReader reader; + ESM::Dialogue* dialogue = nullptr; // master file inserts a record reader.open(getEsmFile(record, false), "filename"); - mEsmStore.load(reader, &dummyListener); + mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); // now a plugin overwrites it with changed data record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it record.mModel = "the_new_model"; reader.open(getEsmFile(record, false), "filename"); - mEsmStore.load(reader, &dummyListener); + mEsmStore.load(reader, &dummyListener, dialogue); mEsmStore.setUp(); // verify that changes were actually applied From 6c87219ba35b8f5c25f21c5ad3f84dc28183e258 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Apr 2022 09:00:58 +0200 Subject: [PATCH 2311/2859] Print record flags in esmtool --- apps/esmtool/esmtool.cpp | 4 +++- apps/esmtool/labels.cpp | 14 ++++++++++++++ apps/esmtool/labels.hpp | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a968dcb464..a6178d692d 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -15,6 +15,7 @@ #include #include "record.hpp" +#include "labels.hpp" #define ESMTOOL_VERSION 1.2 @@ -398,7 +399,8 @@ int load(Arguments& info) if(!quiet && interested) { - std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n"; + std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n" + << "Record flags: " << recordFlags(record->getFlags()) << '\n'; record->print(); } diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index d6cae207c9..405aeb9454 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -902,3 +902,17 @@ std::string weaponFlags(int flags) properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } + +std::string recordFlags(uint32_t flags) +{ + std::string properties; + if (flags == 0) properties += "[None] "; + if (flags & ESM::FLAG_Deleted) properties += "Deleted "; + if (flags & ESM::FLAG_Persistent) properties += "Persistent "; + if (flags & ESM::FLAG_Ignored) properties += "Ignored "; + if (flags & ESM::FLAG_Blocked) properties += "Blocked "; + int unused = ~(ESM::FLAG_Deleted | ESM::FLAG_Persistent | ESM::FLAG_Ignored | ESM::FLAG_Blocked); + if (flags & unused) properties += "Invalid "; + properties += Misc::StringUtils::format("(0x%08X)", flags); + return properties; +} \ No newline at end of file diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp index b06480a97b..8450ccfa03 100644 --- a/apps/esmtool/labels.hpp +++ b/apps/esmtool/labels.hpp @@ -60,6 +60,8 @@ std::string raceFlags(int flags); std::string spellFlags(int flags); std::string weaponFlags(int flags); +std::string recordFlags(uint32_t flags); + // Missing flags functions: // aiServicesFlags, possibly more From 22d02e86b09d7764666287e3bec1c894da4bc564 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Apr 2022 09:23:09 +0200 Subject: [PATCH 2312/2859] Respect ignored flag in navmeshtool and groundcover --- components/esmloader/load.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/esmloader/load.cpp b/components/esmloader/load.cpp index 2e54449a2c..2705c43c49 100644 --- a/components/esmloader/load.cpp +++ b/components/esmloader/load.cpp @@ -196,6 +196,11 @@ namespace EsmLoader { const ESM::NAME recName = reader.getRecName(); reader.getRecHeader(); + if (reader.getRecordFlags() & ESM::FLAG_Ignored) + { + reader.skipRecord(); + continue; + } loadRecord(query, recName, reader, content); } } From db84d9e6490ccf01d6a32106255c91611490397f Mon Sep 17 00:00:00 2001 From: Eris Caffee Date: Sun, 17 Apr 2022 16:28:14 +0000 Subject: [PATCH 2313/2859] Issue 2766 Warn user of old MW version detected --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/launcher/maindialog.cpp | 2 +- apps/wizard/existinginstallationpage.cpp | 38 ++++++++++++++ apps/wizard/existinginstallationpage.hpp | 5 +- apps/wizard/installationpage.cpp | 39 +++++++++++++-- apps/wizard/installationpage.hpp | 6 ++- apps/wizard/mainwizard.cpp | 2 +- apps/wizard/unshield/unshieldworker.cpp | 64 +++++++++++++++++++++++- apps/wizard/unshield/unshieldworker.hpp | 11 +++- 10 files changed, 159 insertions(+), 10 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 44c8fbc2e8..0939409926 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -76,6 +76,7 @@ Programmers Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) + Eris Caffee (eris) eroen escondida Evgeniy Mineev (sandstranger) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f02bed33..2141d20007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,7 @@ Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record + Feature #2766: Warn user if their version of Morrowind is not the latest. Feature #2780: A way to see current OpenMW version in the console Feature #3245: Grid and angle snapping for the OpenMW-CS Feature #3616: Allow Zoom levels on the World Map diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 167f6b9c26..accb27a48b 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -570,7 +570,7 @@ void Launcher::MainDialog::play() msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("
You do not have a game file selected.

\ OpenMW will not start without a game file selected.
")); - msgBox.exec(); + msgBox.exec(); return; } diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 886fcd95db..42b7766693 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -123,6 +123,11 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() return; } + if (!versionIsOK(info.absolutePath())) + { + return; + } + QString path(QDir::toNativeSeparators(info.absolutePath())); QList items = installationsList->findItems(path, Qt::MatchExactly); @@ -165,3 +170,36 @@ int Wizard::ExistingInstallationPage::nextId() const { return MainWizard::Page_LanguageSelection; } + +bool Wizard::ExistingInstallationPage::versionIsOK(QString directory_name) +{ + QDir directory = QDir(directory_name); + QFileInfoList infoList = directory.entryInfoList(QStringList(QString("Morrowind.bsa"))); + if (infoList.size() == 1) + { + qint64 actualFileSize = infoList.at(0).size(); + const qint64 expectedFileSize = 310459500; // Size of Morrowind.bsa in Steam and GOG editions. + + if (actualFileSize == expectedFileSize) + { + return true; + } + + QMessageBox msgBox; + msgBox.setWindowTitle(QObject::tr("Most recent Morrowind not detected")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + msgBox.setText(QObject::tr("
There may be a more recent version of Morrowind available.

\ + Do you wish to continue anyway?
")); + int ret = msgBox.exec(); + if (ret == QMessageBox::Yes) + { + return true; + } + + return false; + } + + return false; +} diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp index fbbbe432ee..6bdc5df134 100644 --- a/apps/wizard/existinginstallationpage.hpp +++ b/apps/wizard/existinginstallationpage.hpp @@ -3,6 +3,8 @@ #include "ui_existinginstallationpage.h" +#include + namespace Wizard { class MainWizard; @@ -25,9 +27,10 @@ namespace Wizard private: MainWizard *mWizard; + bool versionIsOK(QString directory_name); + protected: void initializePage() override; - }; } diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 9c90b0bbf7..8aad714be3 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -8,8 +8,9 @@ #include "mainwizard.hpp" -Wizard::InstallationPage::InstallationPage(QWidget *parent) : - QWizardPage(parent) +Wizard::InstallationPage::InstallationPage(QWidget *parent, Config::GameSettings &gameSettings) : + QWizardPage(parent), + mGameSettings(gameSettings) { mWizard = qobject_cast(parent); @@ -18,7 +19,7 @@ Wizard::InstallationPage::InstallationPage(QWidget *parent) : mFinished = false; mThread = new QThread(); - mUnshield = new UnshieldWorker(); + mUnshield = new UnshieldWorker(mGameSettings.value("morrowind-bsa-filesize").toLongLong()); mUnshield->moveToThread(mThread); connect(mThread, SIGNAL(started()), @@ -47,6 +48,10 @@ Wizard::InstallationPage::InstallationPage(QWidget *parent) : connect(mUnshield, SIGNAL(requestFileDialog(Wizard::Component)), this, SLOT(showFileDialog(Wizard::Component)), Qt::QueuedConnection); + + connect(mUnshield, SIGNAL(requestOldVersionDialog()), + this, SLOT(showOldVersionDialog()) + , Qt::QueuedConnection); } Wizard::InstallationPage::~InstallationPage() @@ -181,6 +186,34 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) mUnshield->setDiskPath(path); } +void Wizard::InstallationPage::showOldVersionDialog() +{ + logTextEdit->appendHtml(tr("

Detected old version of component Morrowind.

")); + mWizard->addLogText(tr("Detected old version of component Morrowind.")); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Morrowind Installation")); + msgBox.setIcon(QMessageBox::Information); + msgBox.setText(QObject::tr("There may be a more recent version of Morrowind available.

Do you wish to continue anyway?")); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + + int ret = msgBox.exec(); + if (ret == QMessageBox::No) + { + logTextEdit->appendHtml(tr("


\ + Error: The installation was aborted by the user

")); + + mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); + mWizard->mError = true; + + emit completeChanged(); + return; + } + + mUnshield->wakeAll(); +} + void Wizard::InstallationPage::installationFinished() { QMessageBox msgBox; diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index 97b1fa8bf7..49c92a5f31 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -6,6 +6,7 @@ #include "unshield/unshieldworker.hpp" #include "ui_installationpage.h" #include "inisettings.hpp" +#include class QThread; @@ -19,7 +20,7 @@ namespace Wizard { Q_OBJECT public: - InstallationPage(QWidget *parent); + InstallationPage(QWidget *parent, Config::GameSettings &gameSettings); ~InstallationPage() override; int nextId() const override; @@ -34,8 +35,11 @@ namespace Wizard void startInstallation(); + Config::GameSettings &mGameSettings; + private slots: void showFileDialog(Wizard::Component component); + void showOldVersionDialog(); void installationFinished(); void installationError(const QString &text, const QString &details); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 7987d9b08d..268aaf15af 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -325,7 +325,7 @@ void Wizard::MainWizard::setupPages() setPage(Page_InstallationTarget, new InstallationTargetPage(this, mCfgMgr)); setPage(Page_ComponentSelection, new ComponentSelectionPage(this)); #ifdef OPENMW_USE_UNSHIELD - setPage(Page_Installation, new InstallationPage(this)); + setPage(Page_Installation, new InstallationPage(this, mGameSettings)); #endif setPage(Page_Import, new ImportPage(this)); setPage(Page_Conclusion, new ConclusionPage(this)); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 14a34e8779..3b68f87cc1 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -12,8 +12,9 @@ #include #include -Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : +Wizard::UnshieldWorker::UnshieldWorker(qint64 expectedMorrowindBsaSize, QObject *parent) : QObject(parent), + mExpectedMorrowindBsaSize(expectedMorrowindBsaSize), mIniSettings() { unshield_set_log_level(0); @@ -159,6 +160,11 @@ void Wizard::UnshieldWorker::setIniCodec(QTextCodec *codec) mIniCodec = codec; } +void Wizard::UnshieldWorker::wakeAll() +{ + mWait.wakeAll(); +} + bool Wizard::UnshieldWorker::setupSettings() { // Create Morrowind.ini settings map @@ -478,6 +484,18 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) // Check if we have correct archive, other archives have Morrowind.bsa too if (tribunalFound == bloodmoonFound) { + qint64 actualFileSize = getMorrowindBsaFileSize(file); + if (actualFileSize != mExpectedMorrowindBsaSize) + { + QReadLocker readLock(&mLock); + emit requestOldVersionDialog(); + mWait.wait(&mLock); + if (mStopped) + { + qDebug() << "We are asked to stop !!"; + break; + } + } cabFile = file; found = true; // We have a GoTY disk or a Morrowind-only disk } @@ -492,6 +510,11 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) } + if (cabFile.isEmpty()) + { + break; + } + if (!found) { emit textChanged(tr("Failed to find a valid archive containing %1.bsa! Retrying.").arg(name)); @@ -939,3 +962,42 @@ bool Wizard::UnshieldWorker::findInCab(const QString &fileName, const QString &c unshield_close(unshield); return false; } + +size_t Wizard::UnshieldWorker::getMorrowindBsaFileSize(const QString &cabFile) +{ + QString fileName = QString("Morrowind.bsa"); + QByteArray array(cabFile.toUtf8()); + + Unshield *unshield; + unshield = unshield_open(array.constData()); + + if (!unshield) + { + emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); + unshield_close(unshield); + return false; + } + + for (int i = 0; i < unshield_file_group_count(unshield); ++i) + { + UnshieldFileGroup *group = unshield_file_group_get(unshield, i); + + for (size_t j = group->first_file; j <= group->last_file; ++j) + { + + if (unshield_file_is_valid(unshield, j)) + { + QString current(QString::fromUtf8(unshield_file_name(unshield, j))); + if (current.toLower() == fileName.toLower()) + { + size_t fileSize = unshield_file_size(unshield, j); + unshield_close(unshield); + return fileSize; // File is found! + } + } + } + } + + unshield_close(unshield); + return 0; +} diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 13f4a1168c..483264e990 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -12,6 +12,7 @@ #include "../inisettings.hpp" +#include namespace Wizard { @@ -26,7 +27,7 @@ namespace Wizard Q_OBJECT public: - UnshieldWorker(QObject *parent = nullptr); + UnshieldWorker(qint64 expectedMorrowindBsaSize, QObject *parent = nullptr); ~UnshieldWorker() override; void stopWorker(); @@ -38,6 +39,8 @@ namespace Wizard void setPath(const QString &path); void setIniPath(const QString &path); + void wakeAll(); + QString getPath(); QString getIniPath(); @@ -45,8 +48,9 @@ namespace Wizard bool setupSettings(); - private: + size_t getMorrowindBsaFileSize(const QString &cabFile); + private: bool writeSettings(); bool getInstallComponent(Component component); @@ -95,6 +99,8 @@ namespace Wizard bool mStopped; + qint64 mExpectedMorrowindBsaSize; + QString mPath; QString mIniPath; QString mDiskPath; @@ -113,6 +119,7 @@ namespace Wizard signals: void finished(); void requestFileDialog(Wizard::Component component); + void requestOldVersionDialog(); void textChanged(const QString &text); From ba57d0651fa3641f353b96eabd0599a779cbbf15 Mon Sep 17 00:00:00 2001 From: Eris Caffee Date: Sun, 17 Apr 2022 17:13:31 +0000 Subject: [PATCH 2314/2859] issue 6685 - wizard failure if config dir does not exist --- apps/launcher/maindialog.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 167f6b9c26..87d5a5f3d8 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -153,6 +154,20 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() if (!setupLauncherSettings()) return FirstRunDialogResultFailure; + // Dialog wizard and setup will fail if the config directory does not already exist + QDir userConfigDir = QDir(QString::fromStdString(mCfgMgr.getUserConfigPath().string())); + if ( ! userConfigDir.exists() ) { + if ( ! userConfigDir.mkpath(".") ) + { + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not create directory %0

\ + Please make sure you have the right permissions \ + and try again.
").arg(userConfigDir.canonicalPath()) + ); + return FirstRunDialogResultFailure; + } + } + if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) { QMessageBox msgBox; From 56a1505885c09e1a7088c4c243797f84025babbd Mon Sep 17 00:00:00 2001 From: Matt <3397065-ZehMatt@users.noreply.gitlab.com> Date: Sun, 17 Apr 2022 17:15:00 +0000 Subject: [PATCH 2315/2859] Cache the target for ai packages instead of looking for it every frame --- apps/openmw/mwmechanics/aicombat.cpp | 6 +++++- apps/openmw/mwmechanics/aipackage.cpp | 22 +++++++++++++++++----- apps/openmw/mwmechanics/aipackage.hpp | 9 +++------ apps/openmw/mwmechanics/aipursue.cpp | 10 +++++++++- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index cbd62d37ff..bd6bd385e8 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -451,7 +451,11 @@ namespace MWMechanics MWWorld::Ptr AiCombat::getTarget() const { - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + if (mCachedTarget.isEmpty() || mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + { + mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + } + return mCachedTarget; } void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index e532e71478..7057a99393 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -47,6 +47,7 @@ MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options mReaction(MWBase::Environment::get().getWorld()->getPrng()), mTargetActorRefId(""), mTargetActorId(-1), + mCachedTarget(), mRotateOnTheRunChecks(0), mIsShortcutting(false), mShortcutProhibited(false), @@ -56,6 +57,14 @@ MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { + if (!mCachedTarget.isEmpty()) + { + if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + mCachedTarget = MWWorld::Ptr(); + else + return mCachedTarget; + } + if (mTargetActorId == -2) return MWWorld::Ptr(); @@ -66,20 +75,22 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const mTargetActorId = -2; return MWWorld::Ptr(); } - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); - if (target.isEmpty()) + mCachedTarget = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); + if (mCachedTarget.isEmpty()) { mTargetActorId = -2; - return target; + return mCachedTarget; } else - mTargetActorId = target.getClass().getCreatureStats(target).getActorId(); + mTargetActorId = mCachedTarget.getClass().getCreatureStats(mCachedTarget).getActorId(); } if (mTargetActorId != -1) - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); else return MWWorld::Ptr(); + + return mCachedTarget; } void MWMechanics::AiPackage::reset() @@ -89,6 +100,7 @@ void MWMechanics::AiPackage::reset() mIsShortcutting = false; mShortcutProhibited = false; mShortcutFailPos = osg::Vec3f(); + mCachedTarget = MWWorld::Ptr(); mPathFinder.clearPath(); mObstacleCheck.clear(); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 498c6e3050..4e5dfcab6b 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -4,17 +4,13 @@ #include #include - + #include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.hpp" #include "aipackagetypeid.hpp" #include "aitimer.hpp" - -namespace MWWorld -{ - class Ptr; -} +#include "../mwworld/ptr.hpp" namespace ESM { @@ -165,6 +161,7 @@ namespace MWMechanics std::string mTargetActorRefId; mutable int mTargetActorId; + mutable MWWorld::Ptr mCachedTarget; short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 252bff58f8..25dd08436b 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -71,7 +71,15 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte MWWorld::Ptr AiPursue::getTarget() const { - return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + if (!mCachedTarget.isEmpty()) + { + if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + mCachedTarget = MWWorld::Ptr(); + else + return mCachedTarget; + } + mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + return mCachedTarget; } void AiPursue::writeState(ESM::AiSequence::AiSequence &sequence) const From d05a2facf3c32ef896b88095d191162dc36c49fa Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 11 Apr 2021 10:28:56 +0200 Subject: [PATCH 2316/2859] Handle NCC flag in Nif files. Objects with this flag will collide only with camera. Expose objects with NC flag to be used by Lua mods. --- CHANGELOG.md | 1 + apps/openmw/mwlua/nearbybindings.cpp | 5 +- apps/openmw/mwphysics/collisiontype.hpp | 5 +- apps/openmw/mwphysics/movementsolver.cpp | 4 +- apps/openmw/mwphysics/mtphysics.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 13 ++++- apps/openmw/mwrender/camera.cpp | 2 +- .../nifloader/testbulletnifloader.cpp | 58 ++++++++++++++++++- components/nif/extra.hpp | 5 +- components/nifbullet/bulletnifloader.cpp | 30 +++++----- components/nifbullet/bulletnifloader.hpp | 7 +-- components/resource/bulletshape.cpp | 1 + components/resource/bulletshape.hpp | 7 +++ files/builtin_scripts/scripts/omw/camera.lua | 2 +- .../scripts/omw/third_person.lua | 3 +- files/lua_api/openmw/nearby.lua | 5 +- 16 files changed, 113 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 525cd5807b..846aaea290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,6 +140,7 @@ Feature #6380: Commas are treated as whitespace in vanilla Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference Feature #6443: Support NiStencilProperty + Feature #6496: NCC flag isn't handled properly Feature #6534: Shader-based object texture blending Feature #6541: Gloss-mapping Feature #6592: Missing support for NiTriShape particle emitters diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 6ce78e569f..cf1458c883 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -54,7 +54,10 @@ namespace MWLua {"HeightMap", MWPhysics::CollisionType_HeightMap}, {"Projectile", MWPhysics::CollisionType_Projectile}, {"Water", MWPhysics::CollisionType_Water}, - {"Default", MWPhysics::CollisionType_Default} + {"Default", MWPhysics::CollisionType_Default}, + {"AnyPhysical", MWPhysics::CollisionType_AnyPhysical}, + {"Camera", MWPhysics::CollisionType_CameraOnly}, + {"VisualOnly", MWPhysics::CollisionType_VisualOnly}, })); api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) diff --git a/apps/openmw/mwphysics/collisiontype.hpp b/apps/openmw/mwphysics/collisiontype.hpp index e69534cf68..b51a22a2f5 100644 --- a/apps/openmw/mwphysics/collisiontype.hpp +++ b/apps/openmw/mwphysics/collisiontype.hpp @@ -11,7 +11,10 @@ enum CollisionType { CollisionType_HeightMap = 1<<3, CollisionType_Projectile = 1<<4, CollisionType_Water = 1<<5, - CollisionType_Default = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door + CollisionType_Default = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, + CollisionType_AnyPhysical = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door|CollisionType_Projectile|CollisionType_Water, + CollisionType_CameraOnly = 1<<6, + CollisionType_VisualOnly = 1<<7 }; } diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fd8a7f2b05..da07d5975b 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -101,7 +101,7 @@ namespace MWPhysics btVector3 to = from - btVector3(0,0,maxHeight); btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); - resultCallback1.m_collisionFilterGroup = 0xff; + resultCallback1.m_collisionFilterGroup = CollisionType_AnyPhysical; resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap; collisionWorld->rayTest(from, to, resultCallback1); @@ -426,7 +426,7 @@ namespace MWPhysics return; ProjectileConvexCallback resultCallback(projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile); - resultCallback.m_collisionFilterMask = 0xff; + resultCallback.m_collisionFilterMask = CollisionType_AnyPhysical; resultCallback.m_collisionFilterGroup = CollisionType_Projectile; const btQuaternion btrot = btQuaternion::getIdentity(); diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 0f05ccdbb1..cb8e520384 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -661,7 +661,7 @@ namespace MWPhysics btVector3 pos2 = Misc::Convert::toBullet(actor2->getCollisionObjectPosition() + osg::Vec3f(0,0,actor2->getHalfExtents().z() * 0.9)); btCollisionWorld::ClosestRayResultCallback resultCallback(pos1, pos2); - resultCallback.m_collisionFilterGroup = 0xFF; + resultCallback.m_collisionFilterGroup = CollisionType_AnyPhysical; resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 06c2420df1..0dd177e4b8 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -490,6 +490,17 @@ namespace MWPhysics assert(!getObject(ptr)); + // Override collision type based on shape content. + switch (shapeInstance->mCollisionType) + { + case Resource::BulletShape::CollisionType::Camera: + collisionType = CollisionType_CameraOnly; + break; + case Resource::BulletShape::CollisionType::None: + collisionType = CollisionType_VisualOnly; + break; + } + auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr.mRef, obj); @@ -905,7 +916,7 @@ namespace MWPhysics const auto aabbMin = bulletPosition - btVector3(radius, radius, radius); const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); const int mask = MWPhysics::CollisionType_Actor; - const int group = 0xff; + const int group = MWPhysics::CollisionType_AnyPhysical; if (occupyingActors == nullptr) { HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 7e83a08493..ea499a64bc 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -53,7 +53,7 @@ namespace MWRender Camera::Camera (osg::Camera* camera) : mHeightScale(1.f), - mCollisionType(MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor), + mCollisionType((MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor) | MWPhysics::CollisionType_CameraOnly), mCamera(camera), mAnimation(nullptr), mFirstPersonView(true), diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index f84fbe2508..f2f6cb3985 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -1009,7 +1009,53 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) + { + mNiStringExtraData.string = "NCC__"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parents.push_back(&mNiNode); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); + expected.mCollisionType = Resource::BulletShape::CollisionType::Camera; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) + { + mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.string = "NC___"; + mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parents.push_back(&mNiNode); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); + expected.mCollisionType = Resource::BulletShape::CollisionType::Camera; + + EXPECT_EQ(*result, expected); + } + + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { mNiStringExtraData.string = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; @@ -1022,12 +1068,16 @@ namespace EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); + expected.mCollisionType = Resource::BulletShape::CollisionType::None; EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.string = "NC___"; @@ -1041,7 +1091,11 @@ namespace EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); + expected.mCollisionType = Resource::BulletShape::CollisionType::None; EXPECT_EQ(*result, expected); } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 8eb14f9b01..cbcb3cabe6 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -55,9 +55,10 @@ struct NiTextKeyExtraData : public Extra struct NiStringExtraData : public Extra { - /* Two known meanings: + /* Known meanings: "MRK" - marker, only visible in the editor, not rendered in-game - "NCO" - no collision + "NCC" - no collision except with the camera + Anything else starting with "NC" - no collision */ std::string string; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 3b0eacfbd7..52be8d4a07 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -17,6 +17,8 @@ #include #include +#include + namespace { @@ -215,7 +217,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) for (const Nif::Node* node : roots) { bool autogenerated = hasAutoGeneratedCollision(*node); - handleNode(filename, *node, nullptr, 0, autogenerated, isAnimated, autogenerated); + handleNode(filename, *node, nullptr, 0, autogenerated, isAnimated, autogenerated, false, mShape->mCollisionType); } if (mCompoundShape) @@ -307,7 +309,7 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node& rootNode) } void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, - int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) + int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, unsigned int& collisionType) { // TODO: allow on-the fly collision switching via toggling this flag if (node.recType == Nif::RC_NiCollisionSwitch && !(node.flags & Nif::NiNode::Flag_ActiveCollision)) @@ -341,8 +343,13 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n if (Misc::StringUtils::ciCompareLen(sd->string, "NC", 2) == 0) { - // No collision. Use an internal flag setting to mark this. - flags |= 0x800; + // NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be uppercase + if (sd->string.length() > 2 && sd->string[2] == 'C') + // Collide only with camera. + collisionType = Resource::BulletShape::CollisionType::Camera; + else + // No collision. + collisionType = Resource::BulletShape::CollisionType::None; } else if (sd->string == "MRK" && autogenerated) { @@ -362,7 +369,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n || node.recType == Nif::RC_NiTriStrips || node.recType == Nif::RC_BSLODTriShape)) { - handleNiTriShape(node, parent, flags, getWorldTransform(node, parent), isAnimated, avoid); + handleNiTriShape(static_cast(node), parent, getWorldTransform(node, parent), isAnimated, avoid); } } @@ -377,22 +384,11 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n continue; assert(std::find(list[i]->parents.begin(), list[i]->parents.end(), ninode) != list[i]->parents.end()); - handleNode(fileName, list[i].get(), ¤tParent, flags, isCollisionNode, isAnimated, autogenerated, avoid); + handleNode(fileName, list[i].get(), ¤tParent, flags, isCollisionNode, isAnimated, autogenerated, avoid, collisionType); } } } -void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, const Nif::Parent* parent, int flags, - const osg::Matrixf &transform, bool isAnimated, bool avoid) -{ - // If the object was marked "NCO" earlier, it shouldn't collide with - // anything. So don't do anything. - if ((flags & 0x800)) - return; - - handleNiTriShape(static_cast(nifNode), parent, transform, isAnimated, avoid); -} - void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, const osg::Matrixf &transform, bool isAnimated, bool avoid) { diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 01a17a5aa1..e90c882bc3 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -56,14 +56,11 @@ public: private: bool findBoundingBox(const Nif::Node& node, const std::string& filename); - void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, int flags, - bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false); + void handleNode(const std::string& fileName, const Nif::Node& node,const Nif::Parent* parent, int flags, + bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, unsigned int& cameraOnlyCollision); bool hasAutoGeneratedCollision(const Nif::Node& rootNode); - void handleNiTriShape(const Nif::Node& nifNode, const Nif::Parent* parent, int flags, const osg::Matrixf& transform, - bool isAnimated, bool avoid); - void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, const osg::Matrixf& transform, bool isAnimated, bool avoid); diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 74515691c6..2d7fd87aed 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -96,6 +96,7 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) { mCollisionBox = mSource->mCollisionBox; mAnimatedShapes = mSource->mAnimatedShapes; + mCollisionType = mSource->mCollisionType; mCollisionShape = duplicateCollisionShape(mSource->mCollisionShape.get()); mAvoidCollisionShape = duplicateCollisionShape(mSource->mAvoidCollisionShape.get()); } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index cd8922ec8e..63db8ec482 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -59,6 +59,13 @@ namespace Resource void setLocalScaling(const btVector3& scale); bool isAnimated() const { return !mAnimatedShapes.empty(); } + + unsigned int mCollisionType = 0; + enum CollisionType + { + None = 0x1, + Camera = 0x2 + }; }; diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 04252e1b47..56e7f52487 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -24,7 +24,7 @@ local noHeadBobbing = 0 local noZoom = 0 local function init() - camera.setCollisionType(util.bitAnd(nearby.COLLISION_TYPE.Default, util.bitNot(nearby.COLLISION_TYPE.Actor))) + camera.setCollisionType(util.bitOr(util.bitAnd(nearby.COLLISION_TYPE.Default, util.bitNot(nearby.COLLISION_TYPE.Actor)), nearby.COLLISION_TYPE.Camera)) camera.setFieldOfView(camera.getBaseFieldOfView()) camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation')) if camera.getMode() == MODE.FirstPerson then diff --git a/files/builtin_scripts/scripts/omw/third_person.lua b/files/builtin_scripts/scripts/omw/third_person.lua index 1b833d2322..311dbde48b 100644 --- a/files/builtin_scripts/scripts/omw/third_person.lua +++ b/files/builtin_scripts/scripts/omw/third_person.lua @@ -28,10 +28,9 @@ local combatOffset = util.vector2(0, 15) local state = defaultShoulder -local rayOptions = {collisionType = nearby.COLLISION_TYPE.Default - nearby.COLLISION_TYPE.Actor} local function ray(from, angle, limit) local to = from + util.transform.rotateZ(angle) * util.vector3(0, limit, 0) - local res = nearby.castRay(from, to, rayOptions) + local res = nearby.castRay(from, to, {collisionType = camera.getCollisionType()}) if res.hit then return (res.hitPos - from):length() else diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index ba27269700..f290992f33 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -34,7 +34,10 @@ -- @field [parent=#COLLISION_TYPE] #number HeightMap -- @field [parent=#COLLISION_TYPE] #number Projectile -- @field [parent=#COLLISION_TYPE] #number Water --- @field [parent=#COLLISION_TYPE] #number Default Used by deafult: World+Door+Actor+HeightMap +-- @field [parent=#COLLISION_TYPE] #number Default Used by default: World+Door+Actor+HeightMap +-- @field [parent=#COLLISION_TYPE] #number AnyPhysical : World+Door+Actor+HeightMap+Projectile+Water +-- @field [parent=#COLLISION_TYPE] #number Camera Objects that should collide only with camera +-- @field [parent=#COLLISION_TYPE] #number VisualOnly Objects that were not intended to be part of the physics world --- -- Collision types that are used in `castRay`. From b2c6c499998a722e2060c4f0aa6b80d168a9a6c3 Mon Sep 17 00:00:00 2001 From: Eris Caffee Date: Sun, 17 Apr 2022 20:25:09 +0000 Subject: [PATCH 2317/2859] issue-5279 console stops scrolling properly after selection --- CHANGELOG.md | 1 + apps/openmw/mwgui/console.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 525cd5807b..1a70c819d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Bug #5120: Scripted object spawning updates physics system Bug #5192: Actor turn rate is too slow Bug #5207: Loose summons can be present in scene + Bug #5279: Ingame console stops auto-scrolling after clicking output Bug #5377: console does not appear after using menutest in inventory Bug #5379: Wandering NPCs falling through cantons Bug #5394: Windows snapping no longer works diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 3bc2607045..a3663b6b9f 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -336,6 +336,7 @@ namespace MWGui mCommandHistory.push_back(cm); mCurrent = mCommandHistory.end(); mEditString.clear(); + mHistory->setTextCursor(mHistory->getTextLength()); // Reset the command line before the command execution. // It prevents the re-triggering of the acceptCommand() event for the same command From 627656ace0da7dd53b6edb088efca55fce030e18 Mon Sep 17 00:00:00 2001 From: Eris Caffee Date: Sun, 17 Apr 2022 20:26:23 +0000 Subject: [PATCH 2318/2859] issue-6667 pressing escape during wait causes black screen --- CHANGELOG.md | 1 + apps/openmw/mwgui/waitdialog.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f02bed33..9aff710043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,6 +107,7 @@ Bug #6606: Quests with multiple IDs cannot always be restarted Bug #6653: With default settings the in-game console doesn't fit into screen Bug #6655: Constant effect absorb attribute causes the game to break + Bug #6667: Pressing the Esc key while resting or waiting causes black screen. Bug #6670: Dialogue order is incorrect Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer Bug #6682: HitOnMe doesn't fire as intended diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 754777c507..9cda792a86 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -98,12 +98,22 @@ namespace MWGui bool WaitDialog::exit() { - return (!mTimeAdvancer.isRunning()); //Only exit if not currently waiting + bool canExit = !mTimeAdvancer.isRunning(); // Only exit if not currently waiting + if (canExit) + { + clear(); + stopWaiting(); + } + return canExit; } void WaitDialog::clear() { mSleeping = false; + mHours = 1; + mManualHours = 1; + mFadeTimeRemaining = 0; + mInterruptAt = -1; mTimeAdvancer.stop(); } From 4907bcaf4163078ef59e7f70b049d7502d500787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Mon, 18 Apr 2022 18:42:47 +0000 Subject: [PATCH 2319/2859] Document OpenMW-CS tables. --- docs/source/manuals/openmw-cs/index.rst | 5 + .../source/manuals/openmw-cs/record-types.rst | 101 +++++- .../manuals/openmw-cs/tables-assets.rst | 108 +++++++ .../manuals/openmw-cs/tables-characters.rst | 300 ++++++++++++++++++ docs/source/manuals/openmw-cs/tables-file.rst | 37 +++ .../manuals/openmw-cs/tables-mechanics.rst | 176 ++++++++++ .../source/manuals/openmw-cs/tables-world.rst | 275 ++++++++++++++++ docs/source/manuals/openmw-cs/tables.rst | 99 ------ 8 files changed, 989 insertions(+), 112 deletions(-) create mode 100644 docs/source/manuals/openmw-cs/tables-assets.rst create mode 100644 docs/source/manuals/openmw-cs/tables-characters.rst create mode 100644 docs/source/manuals/openmw-cs/tables-file.rst create mode 100644 docs/source/manuals/openmw-cs/tables-mechanics.rst create mode 100644 docs/source/manuals/openmw-cs/tables-world.rst diff --git a/docs/source/manuals/openmw-cs/index.rst b/docs/source/manuals/openmw-cs/index.rst index ee82907119..5a0b9c99e0 100644 --- a/docs/source/manuals/openmw-cs/index.rst +++ b/docs/source/manuals/openmw-cs/index.rst @@ -22,5 +22,10 @@ few chapters to familiarise yourself with the new interface. files-and-directories starting-dialog tables + tables-file + tables-world + tables-mechanics + tables-characters + tables-assets record-types record-filters diff --git a/docs/source/manuals/openmw-cs/record-types.rst b/docs/source/manuals/openmw-cs/record-types.rst index 3742cc9e86..cd83555a4c 100644 --- a/docs/source/manuals/openmw-cs/record-types.rst +++ b/docs/source/manuals/openmw-cs/record-types.rst @@ -1,25 +1,20 @@ -Record Types -############ +################### +Object Record Types +################### -A game world contains many items, such as chests, weapons and monsters. All -these items are merely instances of templates we call *Objects*. The OpenMW CS -*Objects* table contains information about each of these template objects, such +A game world contains many items, such as chests, weapons and monsters. All of +these items are merely instances of templates we call Objects. The OpenMW-CS +Objects table contains information about each of these template objects, such as its value and weight in the case of items, or an aggression level in the case of NPCs. -The following is a list of all Record Types and what you can tell OpenMW CS +The following is a list of all Record Types and what you can tell OpenMW-CS about each of them. Activator Activators can have a script attached to them. As long as the cell this object is in is active the script will be run once per frame. -Potion - This is a potion which is not self-made. It has an Icon for your inventory, - weight, coin value, and an attribute called *Auto Calc* set to ``False``. - This means that the effects of this potion are pre-configured. This does not - happen when the player makes their own potion. - Apparatus This is a tool to make potions. Again there’s an icon for your inventory as well as a weight and a coin value. It also has a *Quality* value attached to @@ -54,9 +49,89 @@ Container container, however, will just refuse to take the item in question when it gets "over-encumbered". Organic Containers are containers such as plants. Containers that respawn are not safe to store stuff in. After a certain - amount of time they will reset to their default contents, meaning that + amount of time, they will reset to their default contents, meaning that everything in them is gone forever. Creature These can be monsters, animals and the like. +Creature Levelled List + A list of creatures that can periodically spawn at a particular location. + The strength, type, and number of creatures depends on the player's level. + The list accepts entries of individual Creatures or also of other Creature + Levelled Lists. Each entry has a corresponding level value that determines + the lowest player level the creature can be spawned at. + + When ``Calculate all levels <= player`` is enabled, all the creatures with + a level value lower or equal to the player can be spawned. Otherwise, + the creature levelled list will only resolve a creature that matches + the player's level + + ``Chance None`` is the percentage of possibility that no creatures will spawn. + +Door + Objects in the environment that can be opened or closed when activated. Can + also be locked and trapped. + +Ingredient + Objects required to create potions in an apparatus. Each ingredient can hold + up to four alchemical effects. + +Item Levelled List + A list of items that can spawn in a container when the player opens it. + The quality, type, and number of spawned items depends on the player's level. + The list accepts entries of individual items or also of other Item + Levelled Lists. Each entry has a corresponding level value that determines + the lowest player level the item can be spawned at. + + When ``Calculate all levels <= player`` is enabled, all the items with + a level value lower or equal to the player can be spawned. Otherwise, + the item levelled list will only resolve an item that matches the + player's level. + + ``Chance None`` is the percentage of possibility that no creatures will spawn. + +Light + An object that illuminates the environment around itself, depending on the + light's strength, range, and colour. Can be a static object in the environment, + or when configured, can be picked up and carried by NPCs. + +Lockpick + Tool required to open locks without having the proper key or using a spell. + Locks are found on various in-game objects, usually doors, cabinets, drawers, + chests, and so on. + +Miscellaneous + This is a category of objects with various in-game functions. + + * Soul Gems, used to hold trapped souls and enchant items. + * Gold piles that add their value directly to the player's gold amount when picked up. + * Keys to open locked doors. + * Certain quest items. + * Environment props such as plates, bowls, pots, tankards, and so on. Unlike environment objects of the Static type, these can be picked up. + +NPC + Player character and non-player characters. Their attributes and skills + follow the game's character system, and they are made from multiple body parts. + +Potion + This is a potion which is not self-made. It has an Icon for your inventory, + weight, coin value, and an attribute called *Auto Calc* set to ``False``. + This means that the effects of this potion are pre-configured. This does not + happen when the player makes their own potion. + +Probe + Tool required to disarm trapped doors, chests, or any other object that has + a trap assigned. + +Repair + Tool required by the player to repair damaged objects of Armor and Weapon types. + +Static + Objects from which the world is made. Walls, furniture, foliage, statues, + signs, rocks and rock formations, etc. + +Weapon + An object which the player or NPCs can carry and use it to deal damage + to their opponents in combat. Swords, spears, axes, maces, staves, bows, + crossbows, ammunition, and so on. diff --git a/docs/source/manuals/openmw-cs/tables-assets.rst b/docs/source/manuals/openmw-cs/tables-assets.rst new file mode 100644 index 0000000000..e06213ea60 --- /dev/null +++ b/docs/source/manuals/openmw-cs/tables-assets.rst @@ -0,0 +1,108 @@ +############# +Assets Tables +############# + + +Sounds +****** + +Sounds are Sound Files wrapped by OpenMW, allowing you to adjust how they behave +once they are played in-game. They are used for many audio events including +spells, NPCs commenting, environment ambients, doors, UI events, when moving items +in the inventory, and so on. + +Volume + More is louder, less is quieter. Maximum is 255. +Min Range + When the player is within Min Range distance from the Sound, the Volume will + always be at its maximum defined value. +Max Range + When the player is beyond Max Range distance from the Sound, the volume will + fade to 0. +Sound File + Source sound file on the hard-drive that holds all the actual audio information. + All available records are visible in the Sound Files table. + + +Sound Generators +**************** + +Sound generators are a way to play Sounds driven by animation events. Animated +creatures or NPCs always require to be paired with a corresponding textkeys file. +This textkeys file defines the extents of each animation, and relevant in this +case, events and triggers occuring at particular animation frames. + +For example, a typical textkey entry intended for a Sound Generator would be +named `SoundGen: Left` and be hand placed by an animator whenever the left leg +of the creature touches the ground. In OpenMW-CS, the appropriate Sound +Generator of an appropriate type would then be connected to the animated creature. +In this case the type would be `Left Foot`. Once in-game, OpenMW will play the +sound whenever its textkeys occurs in the currently playing animation. + +Creature + Which creature uses this effect. +Sound + Which record of the Sound type is used as the audio source +Sound Generator Type + Type of the sound generator that is matched to corresponding textkeys. + + * Land + * Left Foot + * Moan + * Right Foot + * Roar + * Scream + * Swim Left + * Swim Right + + +Meshes +****** + +Meshes are 3D assets that need to be assigned to objects to render them in the +game world. Entries in this table are based on contents of ``data/meshes`` +folder and cannot be edited from OpenMW-CS. + + +Icons +***** + +Icons are images used in the user interface to represent inventory items, +spells, and attributes. They can be assigned to relevant records through other +dialogues in the editor. Entries in this table are based on contents of +``data/icons`` folder and cannot be edited from OpenMW-CS. + + +Music Files +*********** + +Music is played in the background during the game and at special events such as +intro, death, or level up. Entries in this table are based on contents of +``data/music`` folder and cannot be edited from OpenMW-CS. + + +Sound Files +*********** + +Sound files are the source audio files on the hard-drive and are used by other +records related to sound. Entries in this table are based on contents of +``data/sounds`` folder and cannot be edited from OpenMW-CS. + + +Textures +******** + +Textures are images used by 3D objects, particle effects, weather system, user +interface and more. Definitions which mesh uses which texture are included in +the mesh files and cannot be assigned through the editor. To use a texture to +paint the terrain, a separate entry is needed in the Land Textures table. +Entries in this table are based on contents of ``data/textures`` folder and +cannot be edited from OpenMW-CS. + + +Videos +****** + +Videos can be shown at various points in the game, depending on where they are +called. Entries in this table are based on contents of ``data/videos`` folder +and cannot be edited from OpenMW-CS. diff --git a/docs/source/manuals/openmw-cs/tables-characters.rst b/docs/source/manuals/openmw-cs/tables-characters.rst new file mode 100644 index 0000000000..9d6f290937 --- /dev/null +++ b/docs/source/manuals/openmw-cs/tables-characters.rst @@ -0,0 +1,300 @@ +################# +Characters Tables +################# + +These tables deal with records that make up the player and NPCs. Together we'll +refer to them as characters. + + +Skills +****** + +Characters in OpenMW can perform various actions and skills define how successful +a character is in performing these actions. The skills themselves are hardcoded +and cannot be added through the editor but can have some of their parameters +adjusted. + +Attribute + Each skill is governed by an attribute. The value of the attribute will + contribute or hinder your success in using a skill. + +Specialization + A skill can belong to combat, magic, or stealth category. If the player + specializes in the same category, they get a +5 starting bonus to the skill. + +Use Value 1, 2, 3, 4 + Use Values are the amount of experience points the player will be awarded + for successful use of this skill. Skills can have one or multiple in-game + actions associated with them and each action is tied to one of the Use Values. + For example, Block skill has Use Value 1 = 2,5. This means that on each + successful blocked attack, the player's block skill will increase by 2,5 + experience points. Athletics have Use Value 1 = 0,02, and Use Value 2 = 0,03. + For each second of running the player's athletics skill will be raised by 0,02 + experience points, while for each second of swimming by 0,03 points. Note that not + all of the skills use all of the Use Values and OpenMW-CS currently doesn't tell + what action a skill's Use Value is associated with. + +Description + Flavour text that appears in the character information window. + + +Classes +******* + +All characters in OpenMW need to be of a certain class. This table list existing +classes and allows you to create, delete or modify them. + +Name + How this class is called. + +Attribute 1 and 2 + Characters of this class receive a +10 starting bonus on the two chosen + atributes. + +Specialization + Characters can specialize in combat, magic, or stealth. Each skill that + belongs to the chosen specialization receives a +5 starting bonus and is + easier to train. + +Major Skill 1 to 5 + Training these skills contributes to the player leveling up. Each of these + skills receives a +25 starting bonus and is easier to train. + +Minor Skill 1 to 5 + Training these skills contributes to the player leveling up. Each of these + skills receives a +10 starting bonus and is easier to train. + +Playable + When enabled, the player can choose to play as this class at character creation. + If disabled, only NPCs can be of this class. + +Description + Flavour text that appears in the character information window. + + +Faction +******* + +Characters in OpenMW can belong to different factions that represent interest +groups within the game's society. The most obvious example of factions are +guilds which the player can join, perform quests for and rise through its ranks. +Membership in factions can be a good source of interesting situations and quests. +If nothing else, membership affects NPC disposition towards the player. + +Name + How this faction is called. + +Attribute 1 and 2 + Two attributes valued by the faction and required to rise through its ranks. + +Hidden + When enabled, the player's membership in this faction will not be displayed + in the character window. + +Skill 1 to 7 + Skills valued by the faction and required to rise through its ranks. Not all + of the skill slots need to be filled. + +Reactions + Change of NPC disposition towards the player when joining this faction. + Disposition change depends on which factions the NPCs belong to. + +Ranks + Positions in the hierarchy of this faction. Every rank has a requirement in + attributes and skills before the player is promoted. Every rank gives the + player an NPC disposition bonus within the faction. + + +Races +***** + +All characters in OpenMW need to be of a certain race. This table list existing +races and allows you to create, delete or modify them. After a race is created, +it can be assigned to an NPC in the objects table. Race affects character's +starting attributes and skill bonuses, appearance, and voice lines. In addition, +a race assigned to the player can affect NPC disposition, quests, dialogues and +other things. + +Name + How this race is called. + +Description + Flavour text that appears in the character information window. + +Playable + When enabled, the player can choose to play as this race. If disabled, only + the NPCs can belong to it. + +Beast Race + Use models and animations for humanoids of different proportions and + movement styles. In addition, beast races can't wear closed helmets + or boots. + +Male Weight + Scaling factor of the default body type. Values above 1.0 will make members + of this race wider. Values below 1.0 will make them thinner. Applies to males. + +Male Height + Scaling factor of the default body type. Values above 1.0 will make members + of this race taller. Values below 1.0 will make them shorter. Applies to males. + +Female Weight + Scaling factor of the default body type. Values above 1.0 will make members + of this race wider. Values below 1.0 will make them thinner. Applies to females. + +Female Height + Scaling factor of the default body type. Values above 1.0 will make members + of this race taller. Values below 1.0 will make them shorter. Applies to females. + + +Birthsigns +********** + +Birthsigns permanently modify player's attributes, skills, or other abilities. +The player can have a single birthsign which is usually picked during character creation. +Modifications to the player are done through one or more spells added +to a birthsign. Spells with constant effects modify skills and attributes. +Spells that are cast are given to the player in the form of powers. + +Name + Name of the birthsign that will be displayed in the interface. + +Texture + An image that will be displayed in the birthsigns selection window. + +Description + Flavour text about the birthsign. + +Powers + A list of spells that are given to the player. When spells are added by a + birthsign, they cannot be removed in-game by the player. + + +Body Parts +********** + +Characters are made from separate parts. Together they form the whole body. +Allows customization of the outer look. Includes heads, arms, legs, torso, hand, +armor, pauldrons, chestpiece, helmets, wearables, etc. + + +Topics +****** + +Topics are, in a broader meaning, dialogue triggers. They can take the form of +clickable keywords in the dialogue, combat events, persuasion events, and other. +What response they produce and under what conditions is then defined by topic infos. +A single topic can be used by unlimited number of topic infos. There are four +different types of topics. + +Greeting 0 to 9 + Initial text that appears in the dialogue window when talking to an NPC. Hardcoded. + +Persuasion + Persuasion entries produce a dialogue response when using persuasion actions on NPCs. Hardcoded. + + * Admire, Bribe, Intimidate Taunt Fail - Conversation text that appears when the player fails a persuasion action. + * Admire, Bribe, Intimidate Taunt Succeed - Conversation text that appears when the player succeeds a persuasion action. + * Info Refusal - Conversation text that appears when the player wishes to talk about a certain topic and the conditions are not met. For example, NPC disposition is too low, the player is not a faction member, etc. + * Service Refusal - Conversation text that appears when the player wishes a service from the NPC but the conditions are not met. + +Topic + A keyword in the dialogue window that leads to further dialogue text, + similar to wiki links. These are the foundation to create dialogues from. + Entires can be freely added, edited, or removed. + +Voice + Voice entries are specific in-game events used to play a sound. Hardcoded. + + * Alarm - NPC enters combat state + * Attack - NPC performs an attack + * Flee - NPC loses their motivation to fight + * Hello - NPC addressing the player when near enough + * Hit - When NPCs are hit and injured. + * Idle - Random things NPCs say. + * Intruder + * Thief - When an NPC detects the player steal something. + + +Topic Infos +*********** + +Topic infos take topics as their triggers and define responses. Through their +many parameters they can be assigned to one or more NPCs. Important to note is +that each topic info can take a combination of parameters to accurately define +which NPCs will produce a particular response. These parameters are as follow. + +Actor + A specific NPC. + +Race + All members of a race. + +Class + NPCs of a chosen class. + +Faction + NPCs belonging to a faction. + +Cell + NPCs located in a particular cell. + +Disposition + NPC disposition towards the player. This is the 0-100 bar visible in the + conversation window and tells how much an NPC likes the player. + +Rank + NPC rank within a faction. + +Gender + NPC gender. + +PC Faction + Player's faction. + +PC Rank + Player's rank within a faction. + +Topic infos when triggered provide a response when the correct conditions are met. + +Sound File + Sound file to play when the topic info is triggered + +Response + Dialogue text that appears in a dialogue window when clicking on a keyword. + Dialogue text that appears near the bottom of the screen when a voice topic + is triggered. + +Script + Script to define further effects or branching dialogue choices when this + topic info is triggered. + +Info Conditions. + Conditions required for this topic info to be active. + + +Journals +******** + +Journals are records that define questlines. Entries can be added or removed. +When adding a new entry, you give it a unique ID which cannot be edited afterwards. +Also to remember is that journal IDs are not the actual keywords appearing in +the in-game journal. + + +Journal Infos +************* + +Journal infos are stages of a particular quest. Entries appear in the player's +journal once they are called by a script. The script can be a standalone record +or a part of a topic info. The current command is ``Journal, "Journal ID", "Quest Index"`` + +Quest Status + Finished, Name, None, Restart. No need to use them. + +Quest Index + A quest can write multiple entries into the player's journal and each of + these entries is identified by its index. + +Quest Description + Text that appears in the journal for this particular stage of the quest. diff --git a/docs/source/manuals/openmw-cs/tables-file.rst b/docs/source/manuals/openmw-cs/tables-file.rst new file mode 100644 index 0000000000..f6b5be0bfd --- /dev/null +++ b/docs/source/manuals/openmw-cs/tables-file.rst @@ -0,0 +1,37 @@ +########### +File Tables +########### + +Tables found in the File menu. + + +Verification Results +******************** + +This table shows reports created by the verify command found in the file menu. +Verify will go over the whole project and output errors / warnings when records +don't conform to the requirements of the engine. The offending records can be +accessed directly from the verification results table. The table will not update +on its own once an error / warning is fixed. Instead, use the *Refresh* option +found in the right click menu in this table. + +Type + The type of record that is causing the error / warning. + +ID + ID value of the offending record. + +Severity + Whether the entry is an error or merely a warning. + The game can still run even if not all errors are fixed. + +Description + Information on what exactly is causing the error / warning. + + +Error Log +********* + +The Error Log table shows any errors that occured while loading the game files +into OpenMW-CS. These are the files that come in ``.omwgame``, ``.omwaddon``, +``.esm``, and ``.esp`` formats. diff --git a/docs/source/manuals/openmw-cs/tables-mechanics.rst b/docs/source/manuals/openmw-cs/tables-mechanics.rst new file mode 100644 index 0000000000..8438be69b6 --- /dev/null +++ b/docs/source/manuals/openmw-cs/tables-mechanics.rst @@ -0,0 +1,176 @@ +################ +Mechanics Tables +################ + +Tables that belong into the mechanics category. + + +Scripts +******* + +Scripts are useful to expand the base functionality offered by the engine +or to create complex quest lines. Entries can be freely added or removed +through OpenMW-CS. When creating a new script, it can be defined as local +or global. + + +Start Scripts +************* + +A list of scripts that are automatically started as global scripts on game startup. +The script ``main`` is an exception, because it will be automatically started +even without being in the start script list. + + +Global Variables +**************** + +Global variables are needed to keep track of the the game's state. They can be +accessed from anywhere in the game (in contrast to local variables) and can be +altered at runtime (in contrast to GMST records). Some example of global +variables are current day, month and year, rats killed, player's crime penalty, +is player a werewolf, is player a vampire, and so on. + + +Game Settings (GMST) +******************** + +GMST are variables needed throughout the game. They can be either a float, +integer, boolean, or string. Float and integer variables affect all sorts of +behaviours of the game. String entries are the text that appears in the +user interface, dialogues, tooltips, buttons and so on. GMST records cannot +be altered at runtime. + + +Spells +****** + +Spells are combinations of magic effects with additional properties and are used +in different ways depending on their type. Some spells are the usual abracadabra +that characters cast. Other spells define immunities, diseases, modify +attributes, or give special abilities. Entries in this table can be freely +added, edited, or removed through the editor. + +Name + Name of the spell that will appear in the user interface. + +Spell Type + * Ability - Constant effect which does not need to be cast. Commonly used for racial or birthsign bonuses to attributes and skills. + * Blight - Can be contracted in-game and treated with blight disease cures (common disease cures will not work). Applies a constant effect to the recepient. + * Curse + * Disease - Can be contracted in-game and treated with common disease cures. Applies a constant effect to the recepient. + * Power - May be cast once per day at no magicka cost. Usually a racial or birthsign bonus. + * Spell - Can be cast and costs magicka. The chance to successfully cast a spell depends on the caster's skill. + +Cost + The amount of magicka spent when casting this spell. + +Auto Calc + Automatically calculate the spell's magicka cost. + +Starter Spell + Starting spells are added to the player on character creation when certain + criteria are fulfilled. The player must be able to cast spells, there is a + certain chance for that spell to be added, and there is a maximum number + of starter spells the player can have. + + +Always Succeeds + When enabled, it will ensure this spell will always be cast regardless of + the caster's skill. + +Effects + A table containing magic effects of this spell and their properties. + New entries can be added and removed through the right click menu. + + +Enchantments +************ + +Enchantments are a way for magic effects to be assigned to in-game items. +Each enchantment can hold multiple magic effects along with other properties. +Entries can be freely added or removed through the editor. + +Enchantment Type + The way to use this enchantment. + * Cast once - the enchantment is cast like a regular spell and afterwards the item is gone. Used to make scrolls. + * Constant effect - the effects of the enchantment will always apply as long as the enchanted item is equiped. + * When Strikes - the effect will apply to whatever is hit by the weapon with the enchantment. + * When Used - the enchantment is cast like a regular spell. Instead of spending character's magicka, it uses the item's available charges. + +Cost + How many points from the available charges are spent each time the + enchantment is used. In-game the cost will also depend on character's + enchanting skill. + +Charges + Total supply of points needed to use the enchantment. When there are + less charges than the cost, the enchantment cannot be used and + the item needs to be refilled. + +Auto Calc + Automatically calculate the enchantment's cost to cast. + +Effects + A table containing magic effects of this enchantment and their properties. + New entries can be added and removed through the right click menu. + + +Magic Efects +************ + +Magic effects define how in-game entities are affected when using magic. +They are required in spells, enchantments, and potions. The core gameplay +functionality of these effects is hardcoded and it's not possible to add +new entries through the editor. The existing entries can nonetheless have +their various parameters adjusted. + +School + Category this magic effect belongs to. + +Base Cost + Used when automatically calculating the spell's cost with "Auto Calc" feature. + +Icon + Which icon will be displayed in the user interface for this effect. Can only + insert records available from the icons table. + +Particle + Texture used by the particle system of this magic effect. + +Casting Object + Which object is displayed when this magic effect is cast. + +Hit Object + Which object is displayed when this magic effect hits a target. + +Area Object + Which object is displayed when this magic effect affects an area. + +Bolt Object + Which object is displayed as the projectile for this magic effect. + +Casting Sound + Sound played when this magic effect is cast. + +Hit Sound + Sound played when this magic effect hits a target. + +Area Sound + Sound played when this magic effect affects an area. + +Bolt Sound + Sound played by this magic effect's projectile. + +Allow Spellmaking + When enabled, this magic effect can be used to create spells. + +Allow Enchanting + When enabled, this magic effect can be used to create enchantments. + +Negative Light + This is a flag present in Morrowind, but is not actually used. + It doesn’t do anything in OpenMW either. + +Description + Flavour text that appears in the user interface. diff --git a/docs/source/manuals/openmw-cs/tables-world.rst b/docs/source/manuals/openmw-cs/tables-world.rst new file mode 100644 index 0000000000..91f1b744a5 --- /dev/null +++ b/docs/source/manuals/openmw-cs/tables-world.rst @@ -0,0 +1,275 @@ +############ +World Tables +############ + +These are the tables in the World menu category. The contents of the game world +can be changed by choosing one of the options in the appropriate menu at the top +of the screen. + + +Objects +******* + +This is a library of all the items, triggers, containers, NPCs, etc. in the game. +There are several kinds of Record Types. Depending on which type a record +is, it will need specific information to function. For example, an NPC needs a +value attached to its aggression level. A chest, of course, does not. All Record +Types contain at least a 3D model or else the player would not see them. Usually +they also have a *Name*, which is what the players sees when they hover their +crosshair over the object during the game. + +Please refer to the :doc:`record-types` chapter for an overview of what each +object type represents in the game's world. + + +Instances +********* + +An instance is created every time an object is placed into a cell. While the +object defines its own fundamental properties, an instance defines how and where +this object appears in the world. When the object is modified, all of its +instances will be modified as well. + +Cell + Which cell contains this instance. Is assigned automatically based on the + edit you make in the 3D view. + +Original Cell + If an object has been moved in-game this field keeps a track of the original + cell as defined through the editor. Is assigned automatically based on the edit + you make in the 3D view. + +Object ID + ID of the object from which this instance is created. + +Pos X, Y, Z + Position coordinates in 3D space relative to the parent cell. + +Rot X, Y, Z + Rotation in 3D space. + +Scale + Size factor applied to this instance. It scales the instance uniformly on + all axes. + +Owner + NPC the instance belongs to. Picking up the instance by the player is + regarded as stealing. + +Soul + This field takes the object of a *Creature* type. Option applies only to + soul gems which will contain the creature's soul and allow enchanting. + +Faction + Faction the instance belongs to. Picking up the instance without joining + this faction is regarded as stealing. + +Faction Index + The player's required rank in a faction to pick up this instance without it + seen as stealing. It allows a reward mechanic where the higher the player + is in a faction, the more of its items and resources are freely + available for use. + +Charges + How many times can this item be used. Applies to lockpicks, probes, and + repair items. Typically used to add a "used" version of the object to the + in-game world. + +Enchantment + Doesn't appear to do anything for instances. An identical field for Objects + takes an ID of an enchantment. + +Coin Value + This works only for instances created from objects with IDs ``gold_001``, + ``gold_005``, ``gold_010``, ``gold_025``, and ``gold_100``. Coin Value tells how + much gold is added to player's inventory when this instance is picked up. The + names and corresponding functionality are hardcoded into the engine. + + For all other instances this value does nothing and their price when buying + or selling is determined by the Coin Value of their Object. + +Teleport + When enabled, this instance acts as a teleport to other locations in the world. + Teleportation occurs when the player activates the instance. + +Teleport Cell + Destination cell where the player will appear. + +Teleport Pos X, Y, Z + Location coordinates where the player will appear relative to the + destination cell. + +Teleport Rot X, Y, Z + Initial orientation of the player after being teleported. + +Lock Level + Is there a lock on this instance and how difficult it is to pick. + +Key + Which key is needed to unlock the lock on this instance. + +Trap + What spell will be cast on the player if the trap is triggered. The spell + has an on touch magic effect. + +Owner Global + A global variable that lets you override ownership. This is used in original + Morrowind to make beds rentable. + + +Cells +***** + +Cells are the basic world-building units that together make up the game's world. +Each of these basic building blocks is a container for other objects to exist in. +Dividing an expansive world into smaller units is neccessary to be able to +efficiently render and process it. Cells can be one of two types: + +Exterior cells + These represent the outside world. They all fit on a grid where cells have + unique coordinates and border one another. Each exterior cell contains a part of + the terrain and together they form a seamless, continuous landmass. Entering and + leaving these cells is as simple as walking beyond their boundary after which we + enter its neighbouring cell. It is also possible to move into another interior + or exterior cell through door objects. + +Interior cells + These represent enclosed spaces such as houses, dungeons, mines, etc. They + don't have a terrain, instead their whole environment is made from objects. + Interior cells only load when the player is in them. Entering and leaving these + cells is possible through door objects or teleportation abilities. + +The Cells table provides you with a list of cells in the game and exposes +their various parameters to edit. + +Sleep Forbidden + In most cities it is forbidden to sleep outside. Sleeping in the wilderness + carries its own risks of attack, though. This entry lets you decide if a + player should be allowed to sleep on the floor in this cell or not. + +Interior Water + Setting the cell’s Interior Water to ``true`` tells the game that there needs + to be water at height 0 in this cell. This is useful for dungeons or mines + that have water in them. + + Setting the cell’s Interior Water to ``false`` tells the game that the water + at height 0 should not be used. This flag is useless for outside cells. + +Interior Sky + Should this interior cell have a sky? This is a rather unique case. The + Tribunal expansion took place in a city on the mainland. Normally this would + require the city to be composed of exterior cells so it has a sky, weather + and the like. But if the player is in an exterior cell and were to look at + their in-game map, they would see Vvardenfell with an overview of all + exterior cells. The player would have to see the city’s very own map, as if + they were walking around in an interior cell. + + So the developers decided to create a workaround and take a bit of both: The + whole city would technically work exactly like an interior cell, but it + would need a sky as if it was an exterior cell. That is what this is. This + is why the vast majority of the cells you will find in this screen will have + this option set to false: It is only meant for these "fake exteriors". + +Region + To which Region does this cell belong? This has an impact on the way the + game handles weather and encounters in this area. It is also possible for a + cell not to belong to any region. + +Interior + When enabled, it allows to manually set *Ambient*, *Sunlight*, *Fog*, + and *Fog Density* values regardless of the main sky system. + +Ambient + Colour of the secondary light, that contributes to an overall shading of the + scene. + +Sunlight + Colour of the primary light that lights the scene. + +Fog + Colour of the distant fog effect. + +Fog Density + How quickly do objects start fading into the fog. + +Water Level + Height of the water plane. Only applies to interior cells + when *Interior Water* is enabled. + +Map Color + This is a property present in Morrowind, but is not actually used. + It doesn’t do anything in OpenMW either. + + +Lands +***** + +Lands are records needed by exterior cells to show the terrain. Each exterior +cell needs its own land record and they are paired by matching IDs. Land records +can be created manually in this table, but a better approach is to simply shape +the terrain in the 3D view and the land record of affected cells will be +created automatically. + + +Land Textures +************* + +This is a list of textures that are specifically used to paint the terrain of +exterior cells. By default, the terrain shows the ``_land_default.dds`` texture +found in ``data/textures`` folder. Land texture entries can be added, edited or +removed. + +Texture Nickname + Name of this land texture. + +Texture Index + Assigned automatically and cannot be edited. + +Texture + Texture image file that is used for this land texture. + + +Pathgrids +********* + +Pathgrids allow NPCs to navigate and move along complicated paths in their surroundings. +A pathgrid contains a list of *points* connected by *edges*. NPCs will +find their way from one point to another as long as there is a path of +connecting edges between them. One pathgrid is used per cell. When recast +navigation is enabled, the pathgrids are not used. + + +Regions +******* + +Regions describe general areas of the exterior game world and define rules for +random enemy encounters, ambient sounds, and weather. Regions can be assigned +one per cell and the cells will inherit their rules. + +Name + This is how the game will show the player's location in-game. + +MapColour + This is a colour used to identify the region when viewed in *World* → *Region Map*. + +Sleep Encounter + This field takes an object of the *Creature Levelled List* type. This object + defines what kinds of enemies the player might encounter when sleeping outside + in the wilderness. + +Weather + A table listing all available weather types and their chance to occur while + the player is in this region. Entries cannot be added or removed. + +Sounds + A table listing ambient sounds that will randomly play while the player is + in this region. Entries can be freely added or removed. + + +Region Map +********** + +The region map shows a grid of exterior cells, their relative positions to one +another, and regions they belong to. In summary, it shows the world map. +Compared to the cells table which is a list, this view helps vizualize the world. +Region map does not show interior cells. diff --git a/docs/source/manuals/openmw-cs/tables.rst b/docs/source/manuals/openmw-cs/tables.rst index 43da03f079..2d63439683 100644 --- a/docs/source/manuals/openmw-cs/tables.rst +++ b/docs/source/manuals/openmw-cs/tables.rst @@ -67,102 +67,3 @@ Modified delete that instance yourself or make sure that that object is replaced by something that still exists otherwise the player will get crashes in the worst case scenario. - - - -World Screens -************* - -The contents of the game world can be changed by choosing one of the options in -the appropriate menu at the top of the screen. - - -Regions -======= - -This describes the general areas of Vvardenfell. Each of these areas has -different rules about things such as encounters and weather. - -Name - This is how the game will show the player's location in-game. - -MapColour - This is a six-digit hexadecimal representation of the colour used to - identify the region on the map available in *World* → *Region Map*. - -Sleep Encounter - These are the rules for what kinds of enemies the player might encounter - when sleeping outside in the wilderness. - - -Cells -===== - -Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot -going on simultaneously. But if the player is in Balmora, why would the -computer need to keep track the exact locations of NPCs walking through the -corridors in a Vivec canton? All that work would be quite useless and bring -the player's system down to its knees! So the world has been divided up into -squares we call *cells*. Once your character enters a cell, the game will load -everything that is going on in that cell so the player can interact with it. - -In the original Morrowind this could be seen when a small loading bar would -appear near the bottom of the screen while travelling; the player had just -entered a new cell and the game had to load all the items and NPCs. The *Cells* -screen in OpenMW CS provides you with a list of cells in the game, both the -interior cells (houses, dungeons, mines, etc.) and the exterior cells (the -outside world). - -Sleep Forbidden - Can the player sleep on the floor? In most cities it is forbidden to sleep - outside. Sleeping in the wilderness carries its own risks of attack, though, - and this entry lets you decide if a player should be allowed to sleep on the - floor in this cell or not. - -Interior Water - Should water be rendered in this interior cell? The game world consists of - an endless ocean at height 0, then the landscape is added. If part of the - landscape goes below height 0, the player will see water. - - Setting the cell’s Interior Water to true tells the game that this cell that - there needs to be water at height 0. This is useful for dungeons or mines - that have water in them. - - Setting the cell’s Interior Water to ``false`` tells the game that the water - at height 0 should not be used. This flag is useless for outside cells. - -Interior Sky - Should this interior cell have a sky? This is a rather unique case. The - Tribunal expansion took place in a city on the mainland. Normally this would - require the city to be composed of exterior cells so it has a sky, weather - and the like. But if the player is in an exterior cell and were to look at - their in-game map, they would see Vvardenfell with an overview of all - exterior cells. The player would have to see the city’s very own map, as if - they were walking around in an interior cell. - - So the developers decided to create a workaround and take a bit of both: The - whole city would technically work exactly like an interior cell, but it - would need a sky as if it was an exterior cell. That is what this is. This - is why the vast majority of the cells you will find in this screen will have - this option set to false: It is only meant for these "fake exteriors". - -Region - To which Region does this cell belong? This has an impact on the way the - game handles weather and encounters in this area. It is also possible for a - cell not to belong to any region. - - -Objects -======= - -This is a library of all the items, triggers, containers, NPCs, etc. in the -game. There are several kinds of Record Types. Depending on which type a record -is, it will need specific information to function. For example, an NPC needs a -value attached to its aggression level. A chest, of course, does not. All -Record Types contain at least a 3D model or else the player would not see them. -Usually they also have a *Name*, which is what the players sees when they hover -their reticle over the object during the game. - -Please refer to the Record Types chapter for an overview of what each type of -object does and what you can tell OpenMW CS about these objects. - From 78210290560ca8f97d365c0a2fd98fdb956d6e12 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Tue, 12 Apr 2022 13:08:58 -0400 Subject: [PATCH 2320/2859] Added missing line continuation to ICU_ROOT setting on macos CI --- CI/before_script.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index c4bb1d1a64..c51eeb61d0 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -26,6 +26,6 @@ cmake \ -D BUILD_BSATOOL=TRUE \ -D BUILD_ESSIMPORTER=TRUE \ -D BUILD_NIFTEST=TRUE \ --D ICU_ROOT="/usr/local/opt/icu4c" +-D ICU_ROOT="/usr/local/opt/icu4c" \ -G"Unix Makefiles" \ .. From 6cb36464c3c37341f8211d191680f0c4c9f01d82 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Wed, 20 Apr 2022 08:14:02 +0000 Subject: [PATCH 2321/2859] disable ICU tools for android --- extern/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 1c051bff61..f7bf9c4556 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -271,7 +271,7 @@ if (NOT OPENMW_USE_SYSTEM_ICU) SOURCE_DIR fetched/icu CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${ICU_ENV} /icu4c/source/configure --enable-static --disable-shared - --disable-tests --disable-samples --disable-icuio --disable-extras ${ICU_ADDITIONAL_OPTS} + --disable-tests --disable-samples --disable-icuio --disable-extras --disable-tools ${ICU_ADDITIONAL_OPTS} BUILD_COMMAND make INSTALL_COMMAND "" ) From 21d46dcf78f14fcdac7c292449eb46929812984d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Wed, 20 Apr 2022 12:02:22 +0000 Subject: [PATCH 2322/2859] Add fallbacks for the moons. --- files/openmw.cfg | 25 +++++++++++++++++++++++++ files/openmw.cfg.local | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/files/openmw.cfg b/files/openmw.cfg index d1ecd6f8a3..b56f2bbca1 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -500,3 +500,28 @@ fallback=Weather_Blizzard_Cloud_Speed,7.5 fallback=Weather_Blizzard_Glare_View,0 fallback=Weather_Blizzard_Ambient_Loop_Sound_ID, Blizzard fallback=Weather_Blizzard_Storm_Threshold,.50 + +# moons +fallback=Moons_Secunda_Size,20 +fallback=Moons_Secunda_Axis_Offset,50 +fallback=Moons_Secunda_Speed,.6 +fallback=Moons_Secunda_Daily_Increment,1.2 +fallback=Moons_Secunda_Moon_Shadow_Early_Fade_Angle,0.5 +fallback=Moons_Secunda_Fade_Start_Angle,50 +fallback=Moons_Secunda_Fade_End_Angle,30 +fallback=Moons_Secunda_Fade_In_Start,14 +fallback=Moons_Secunda_Fade_In_Finish,15 +fallback=Moons_Secunda_Fade_Out_Start,7 +fallback=Moons_Secunda_Fade_Out_Finish,10 +fallback=Moons_Masser_Size,55 +fallback=Moons_Masser_Axis_Offset,35 +fallback=Moons_Masser_Speed,.5 +fallback=Moons_Masser_Daily_Increment,1 +fallback=Moons_Masser_Moon_Shadow_Early_Fade_Angle,0.5 +fallback=Moons_Masser_Fade_Start_Angle,50 +fallback=Moons_Masser_Fade_End_Angle,40 +fallback=Moons_Masser_Fade_In_Start,14 +fallback=Moons_Masser_Fade_In_Finish,15 +fallback=Moons_Masser_Fade_Out_Start,7 +fallback=Moons_Masser_Fade_Out_Finish,10 +fallback=Moons_Script_Color,255,20,20 diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index f928113002..94f17d8aec 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -500,3 +500,28 @@ fallback=Weather_Blizzard_Cloud_Speed,7.5 fallback=Weather_Blizzard_Glare_View,0 fallback=Weather_Blizzard_Ambient_Loop_Sound_ID, Blizzard fallback=Weather_Blizzard_Storm_Threshold,.50 + +# moons +fallback=Moons_Secunda_Size,20 +fallback=Moons_Secunda_Axis_Offset,50 +fallback=Moons_Secunda_Speed,.6 +fallback=Moons_Secunda_Daily_Increment,1.2 +fallback=Moons_Secunda_Moon_Shadow_Early_Fade_Angle,0.5 +fallback=Moons_Secunda_Fade_Start_Angle,50 +fallback=Moons_Secunda_Fade_End_Angle,30 +fallback=Moons_Secunda_Fade_In_Start,14 +fallback=Moons_Secunda_Fade_In_Finish,15 +fallback=Moons_Secunda_Fade_Out_Start,7 +fallback=Moons_Secunda_Fade_Out_Finish,10 +fallback=Moons_Masser_Size,55 +fallback=Moons_Masser_Axis_Offset,35 +fallback=Moons_Masser_Speed,.5 +fallback=Moons_Masser_Daily_Increment,1 +fallback=Moons_Masser_Moon_Shadow_Early_Fade_Angle,0.5 +fallback=Moons_Masser_Fade_Start_Angle,50 +fallback=Moons_Masser_Fade_End_Angle,40 +fallback=Moons_Masser_Fade_In_Start,14 +fallback=Moons_Masser_Fade_In_Finish,15 +fallback=Moons_Masser_Fade_Out_Start,7 +fallback=Moons_Masser_Fade_Out_Finish,10 +fallback=Moons_Script_Color,255,20,20 From a2d596dbc70728b7df171ae173dba8a722dd565b Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 20 Feb 2022 19:34:04 +0100 Subject: [PATCH 2323/2859] Prepare navmesh scene asynchronously It is expensive operation to generate new osg::Group for updated navmesh tile which noticeably slows down main thread primarily because of SceneManager::recreateShaders call. Move it to the preload work queue that is used by RenderingManager. Leave to main thread only manipulations on the root node. Also move deallocation of no more needed data to the work queue. It's also quite expensive operation because SceneManager::recreateShaders allocates a new state set for each osg::Geometry. Deallocating them takes time. Avoid creating another work item if there is existing one that is not started yet. Make sure results are accepted in the proper serialized order by selecting completed work item with maximum {id, version}. --- apps/openmw/mwrender/navmesh.cpp | 249 +++++++++++++++++++--- apps/openmw/mwrender/navmesh.hpp | 21 +- apps/openmw/mwrender/renderingmanager.cpp | 4 +- 3 files changed, 235 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index a3c26aeb59..8345df9265 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include @@ -17,8 +19,137 @@ namespace MWRender { - NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) + struct NavMesh::LessByTilePosition + { + bool operator()(const DetourNavigator::TilePosition& lhs, + const std::pair& rhs) const + { + return lhs < rhs.first; + } + + bool operator()(const std::pair& lhs, + const DetourNavigator::TilePosition& rhs) const + { + return lhs.first < rhs; + } + }; + + struct NavMesh::CreateNavMeshTileGroups final : SceneUtil::WorkItem + { + std::size_t mId; + DetourNavigator::Version mVersion; + const std::weak_ptr mNavMesh; + const osg::ref_ptr mGroupStateSet; + const osg::ref_ptr mDebugDrawStateSet; + const DetourNavigator::Settings mSettings; + std::map mTiles; + std::atomic_bool mAborted {false}; + std::mutex mMutex; + bool mStarted = false; + std::vector> mUpdatedTiles; + std::vector mRemovedTiles; + + explicit CreateNavMeshTileGroups(std::size_t id, DetourNavigator::Version version, + std::weak_ptr navMesh, + const osg::ref_ptr& groupStateSet, const osg::ref_ptr& debugDrawStateSet, + const DetourNavigator::Settings& settings, const std::map& tiles) + : mId(id) + , mVersion(version) + , mNavMesh(navMesh) + , mGroupStateSet(groupStateSet) + , mDebugDrawStateSet(debugDrawStateSet) + , mSettings(settings) + , mTiles(tiles) + { + } + + void doWork() final + { + using DetourNavigator::TilePosition; + using DetourNavigator::Version; + + const std::lock_guard lock(mMutex); + mStarted = true; + + if (mAborted.load(std::memory_order_acquire)) + return; + + const auto navMeshPtr = mNavMesh.lock(); + if (navMeshPtr == nullptr) + return; + + std::vector> existingTiles; + + navMeshPtr->lockConst()->forEachUsedTile([&] (const TilePosition& position, const Version& version, const dtMeshTile& /*meshTile*/) + { + existingTiles.emplace_back(position, version); + }); + + if (mAborted.load(std::memory_order_acquire)) + return; + + std::sort(existingTiles.begin(), existingTiles.end()); + + std::vector removedTiles; + + for (const auto& [position, tile] : mTiles) + if (!std::binary_search(existingTiles.begin(), existingTiles.end(), position, LessByTilePosition {})) + removedTiles.push_back(position); + + std::vector> updatedTiles; + + for (const auto& [position, version] : existingTiles) + { + const auto it = mTiles.find(position); + if (it != mTiles.end() && it->second.mGroup != nullptr && it->second.mVersion == version) + continue; + + osg::ref_ptr group; + { + const auto navMesh = navMeshPtr->lockConst(); + const dtMeshTile* meshTile = DetourNavigator::getTile(navMesh->getImpl(), position); + if (meshTile == nullptr) + continue; + + if (mAborted.load(std::memory_order_acquire)) + return; + + group = SceneUtil::createNavMeshTileGroup(navMesh->getImpl(), *meshTile, mSettings, mGroupStateSet, mDebugDrawStateSet); + } + if (group == nullptr) + { + removedTiles.push_back(position); + continue; + } + MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); + group->setNodeMask(Mask_Debug); + updatedTiles.emplace_back(position, Tile {version, std::move(group)}); + } + + if (mAborted.load(std::memory_order_acquire)) + return; + + mUpdatedTiles = std::move(updatedTiles); + mRemovedTiles = std::move(removedTiles); + } + + void abort() final + { + mAborted.store(true, std::memory_order_release); + } + }; + + struct NavMesh::DeallocateCreateNavMeshTileGroups final : SceneUtil::WorkItem + { + osg::ref_ptr mWorkItem; + + explicit DeallocateCreateNavMeshTileGroups(osg::ref_ptr&& workItem) + : mWorkItem(std::move(workItem)) {} + }; + + NavMesh::NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, bool enabled) : mRootNode(root) + , mWorkQueue(workQueue) , mGroupStateSet(SceneUtil::makeNavMeshTileStateSet()) , mDebugDrawStateSet(SceneUtil::DebugDraw::makeStateSet()) , mEnabled(enabled) @@ -30,6 +161,8 @@ namespace MWRender { if (mEnabled) disable(); + for (const auto& workItem : mWorkItems) + workItem->abort(); } bool NavMesh::toggle() @@ -42,13 +175,69 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const DetourNavigator::NavMeshCacheItem& navMesh, std::size_t id, + void NavMesh::update(const std::shared_ptr& navMesh, std::size_t id, const DetourNavigator::Settings& settings) { using DetourNavigator::TilePosition; using DetourNavigator::Version; - if (!mEnabled || (!mTiles.empty() && mId == id && mVersion == navMesh.getVersion())) + if (!mEnabled) + return; + + { + std::pair lastest {0, Version {}}; + osg::ref_ptr latestCandidate; + for (auto it = mWorkItems.begin(); it != mWorkItems.end();) + { + if (!(*it)->isDone()) + { + ++it; + continue; + } + const std::pair order {(*it)->mId, (*it)->mVersion}; + if (lastest < order) + { + lastest = order; + std::swap(latestCandidate, *it); + } + if (*it != nullptr) + mWorkQueue->addWorkItem(new DeallocateCreateNavMeshTileGroups(std::move(*it))); + it = mWorkItems.erase(it); + } + + if (latestCandidate != nullptr) + { + for (const TilePosition& position : latestCandidate->mRemovedTiles) + { + const auto it = mTiles.find(position); + if (it == mTiles.end()) + continue; + mRootNode->removeChild(it->second.mGroup); + mTiles.erase(it); + } + + for (auto& [position, tile] : latestCandidate->mUpdatedTiles) + { + const auto it = mTiles.find(position); + if (it == mTiles.end()) + { + mRootNode->addChild(tile.mGroup); + mTiles.emplace_hint(it, position, std::move(tile)); + } + else + { + mRootNode->replaceChild(it->second.mGroup, tile.mGroup); + std::swap(it->second, tile); + } + } + + mWorkQueue->addWorkItem(new DeallocateCreateNavMeshTileGroups(std::move(latestCandidate))); + } + } + + const auto version = navMesh->lock()->getVersion(); + + if (!mTiles.empty() && mId == id && mVersion == version) return; if (mId != id) @@ -57,40 +246,35 @@ namespace MWRender mId = id; } - mVersion = navMesh.getVersion(); + mVersion = version; - std::vector updated; - navMesh.forEachUsedTile([&] (const TilePosition& position, const Version& version, const dtMeshTile& meshTile) + for (auto& workItem : mWorkItems) { - updated.push_back(position); - Tile& tile = mTiles[position]; - if (tile.mGroup != nullptr && tile.mVersion == version) - return; - if (tile.mGroup != nullptr) - mRootNode->removeChild(tile.mGroup); - tile.mGroup = SceneUtil::createNavMeshTileGroup(navMesh.getImpl(), meshTile, settings, - mGroupStateSet, mDebugDrawStateSet); - if (tile.mGroup == nullptr) - return; - MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(tile.mGroup, "debug"); - tile.mGroup->setNodeMask(Mask_Debug); - mRootNode->addChild(tile.mGroup); - }); - std::sort(updated.begin(), updated.end()); - for (auto it = mTiles.begin(); it != mTiles.end();) - { - if (!std::binary_search(updated.begin(), updated.end(), it->first)) - { - mRootNode->removeChild(it->second.mGroup); - it = mTiles.erase(it); - } - else - ++it; + const std::unique_lock lock(workItem->mMutex, std::try_to_lock); + + if (!lock.owns_lock()) + continue; + + if (workItem->mStarted) + continue; + + workItem->mId = id; + workItem->mVersion = version; + workItem->mTiles = mTiles; + + return; } + + osg::ref_ptr workItem = new CreateNavMeshTileGroups(id, version, navMesh, mGroupStateSet, mDebugDrawStateSet, settings, mTiles); + mWorkQueue->addWorkItem(workItem); + mWorkItems.push_back(std::move(workItem)); } void NavMesh::reset() { + for (auto& workItem : mWorkItems) + workItem->abort(); + mWorkItems.clear(); for (auto& [position, tile] : mTiles) mRootNode->removeChild(tile.mGroup); mTiles.clear(); @@ -98,15 +282,12 @@ namespace MWRender void NavMesh::enable() { - for (const auto& [position, tile] : mTiles) - mRootNode->addChild(tile.mGroup); mEnabled = true; } void NavMesh::disable() { - for (const auto& [position, tile] : mTiles) - mRootNode->removeChild(tile.mGroup); + reset(); mEnabled = false; } } diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index fd69a3e487..1c2cfbd323 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -3,11 +3,14 @@ #include #include +#include #include #include #include +#include +#include class dtNavMesh; @@ -24,18 +27,24 @@ namespace DetourNavigator struct Settings; } +namespace SceneUtil +{ + class WorkQueue; +} + namespace MWRender { class NavMesh { public: - NavMesh(const osg::ref_ptr& root, bool enabled); + explicit NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, + bool enabled); ~NavMesh(); bool toggle(); - void update(const DetourNavigator::NavMeshCacheItem& navMesh, std::size_t id, - const DetourNavigator::Settings& settings); + void update(const std::shared_ptr>& navMesh, + std::size_t id, const DetourNavigator::Settings& settings); void reset(); @@ -55,13 +64,19 @@ namespace MWRender osg::ref_ptr mGroup; }; + struct LessByTilePosition; + struct CreateNavMeshTileGroups; + struct DeallocateCreateNavMeshTileGroups; + osg::ref_ptr mRootNode; + osg::ref_ptr mWorkQueue; osg::ref_ptr mGroupStateSet; osg::ref_ptr mDebugDrawStateSet; bool mEnabled; std::size_t mId; DetourNavigator::Version mVersion; std::map mTiles; + std::vector> mWorkItems; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a504a42faf..384580adb8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -384,7 +384,7 @@ namespace MWRender // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); - mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); + mNavMesh.reset(new NavMesh(mRootNode, mWorkQueue, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); mRecastMesh.reset(new RecastMesh(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator"))); mPathgrid.reset(new Pathgrid(mRootNode)); @@ -1395,7 +1395,7 @@ namespace MWRender { try { - mNavMesh->update(*it->second->lockConst(), mNavMeshNumber, mNavigator.getSettings()); + mNavMesh->update(it->second, mNavMeshNumber, mNavigator.getSettings()); } catch (const std::exception& e) { From dac4415699464264c43ac7e46ed23e45f73bf21e Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Tue, 12 Apr 2022 13:09:53 -0400 Subject: [PATCH 2324/2859] Moved localisation docs to dedicated page --- docs/source/reference/modding/index.rst | 1 + .../source/reference/modding/localisation.rst | 83 +++++++++++++++++++ files/lua_api/openmw/core.lua | 45 ++-------- 3 files changed, 92 insertions(+), 37 deletions(-) create mode 100644 docs/source/reference/modding/localisation.rst diff --git a/docs/source/reference/modding/index.rst b/docs/source/reference/modding/index.rst index 440db96d8c..1e8560930a 100644 --- a/docs/source/reference/modding/index.rst +++ b/docs/source/reference/modding/index.rst @@ -28,3 +28,4 @@ about creating new content for OpenMW, please refer to sky-system extended paths + localisation diff --git a/docs/source/reference/modding/localisation.rst b/docs/source/reference/modding/localisation.rst new file mode 100644 index 0000000000..c0bf766d66 --- /dev/null +++ b/docs/source/reference/modding/localisation.rst @@ -0,0 +1,83 @@ +Localisation +============ + +OpenMW supports localisation of mods using ICU MessageFormat wrapped in YAML. +Currently this is only possible using the +`openmw.core.l10n <../lua-scripting/openmw_core.html##(core).l10n>`_ lua function. + +Locales +------- + +Locales usually have the form ``{lang}_{COUNTRY}``, +where ``{lang}`` is a lowercase two-letter language code and ``{COUNTRY}`` is an uppercase +two-letter country code. Localisation files *must* have this exact capitalisation and separator +to be recognized. + +When users request a locale using the :ref:`preferred locales` setting they do not need to match capitalisation +and can also use hyphens instead of underscores. The locale will be normalised to the above format. + +Locales may also contain variants and keywords, though these usually will not be necessary. +See `The Locale chapter of the ICU Guide `_ for full details. + +Localisation Files +-------------------------- + +Localisation files (containing the message names and translations) should be stored in the +VFS as files of the form ``l10n//.yaml``. + +Messages contents have the form of ICU MessageFormat strings. +See `the Formatting Messages chapter of the ICU Guide `_ +for a guide to MessageFormat, and see +`The ICU APIdoc `_ +for full details of the MessageFormat syntax. + +Examples +~~~~~~~~ + +.. code-block:: yaml + :caption: DataFiles/l10n/MyMod/en.yaml + + good_morning: 'Good morning.' + + you_have_arrows: |- + {count, plural, + one {You have one arrow.} + other {You have {count} arrows.} + } + +.. code-block:: yaml + :caption: DataFiles/l10n/MyMod/de.yaml + + good_morning: "Guten Morgen." + you_have_arrows: |- + {count, plural, + one {Du hast ein Pfeil.} + other {Du hast {count} Pfeile.} + } + "Hello {name}!": "Hallo {name}!" + +Select rules can be used to match arbitrary string arguments. +The default keyword ``other`` must always be provided. + +.. code-block:: yaml + :caption: DataFiles/l10n/AdvancedExample/en.yaml + + pc_must_come: {PCGender, select, + male {He is} + female {She is} + other {They are} + } coming with us. + +Numbers have various formatting options and can also be formatted with custom patterns. +See `The ICU Guide `_ + +.. code-block:: yaml + :caption: DataFiles/l10n/AdvancedExample2/en.yaml + + quest_completion: "The quest is {done, number, percent} complete." + # E.g. "You came in 4th place" + ordinal: "You came in {num, ordinal} place." + # E.g. "There is one thing", "There are one hundred things" + spellout: "There {num, plural, one{is {num, spellout} thing} other{are {num, spellout} things}}." + numbers: "{int} and {double, number, integer} are integers, but {double} is a double" + rounding: "{value, number, :: .00}" diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index b1a10b9cfb..dd4ccac831 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -54,25 +54,14 @@ --- -- Return l10n formatting function for the given context. --- Language files should be stored in VFS as `l10n//.yaml`. +-- Localisation files (containing the message names and translations) should be stored in +-- VFS as files of the form `l10n//.yaml`. -- --- Locales usually have the form {lang}_{COUNTRY}, --- where {lang} is a lowercase two-letter language code and {COUNTRY} is an uppercase --- two-letter country code. Capitalization and the separator must have exactly --- this format for language files to be recognized, but when users request a --- locale they do not need to match capitalization and can use hyphens instead of --- underscores. +-- See [Localisation](../modding/localisation.html) for details of the localisation file structure. -- --- Locales may also contain variants and keywords. See https://unicode-org.github.io/icu/userguide/locale/#language-code --- for full details. --- --- Messages have the form of ICU MessageFormat strings. --- See https://unicode-org.github.io/icu/userguide/format_parse/messages/ --- for a guide to MessageFormat, and see --- https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1MessageFormat.html --- for full details of the MessageFormat syntax. -- @function [parent=#core] l10n -- @param #string context l10n context; recommended to use the name of the mod. +-- This must match the directory in the VFS which stores the localisation files. -- @param #string fallbackLocale The source locale containing the default messages -- If omitted defaults to "en" -- @return #function @@ -80,39 +69,21 @@ -- # DataFiles/l10n/MyMod/en.yaml -- good_morning: 'Good morning.' -- --- you_have_arrows: |- {count, plural, +-- you_have_arrows: |- +-- {count, plural, -- one {You have one arrow.} -- other {You have {count} arrows.} -- } -- @usage -- # DataFiles/l10n/MyMod/de.yaml -- good_morning: "Guten Morgen." --- you_have_arrows: |- {count, plural, +-- you_have_arrows: |- +-- {count, plural, -- one {Du hast ein Pfeil.} -- other {Du hast {count} Pfeile.} -- } -- "Hello {name}!": "Hallo {name}!" -- @usage --- # DataFiles/l10n/AdvancedExample/en.yaml --- # More complicated patterns --- # select rules can be used to match arbitrary string arguments --- # The default keyword other must always be provided --- pc_must_come: {PCGender, select, --- male {He is} --- female {She is} --- other {They are} --- } coming with us. --- # Numbers have various formatting options --- quest_completion: "The quest is {done, number, percent} complete.", --- # E.g. "You came in 4th place" --- ordinal: "You came in {num, ordinal} place." --- # E.g. "There is one thing", "There are one hundred things" --- spellout: "There {num, plural, one{is {num, spellout} thing} other{are {num, spellout} things}}." --- numbers: "{int} and {double, number, integer} are integers, but {double} is a double" --- # Numbers can be formatted with custom patterns --- # See https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html#syntax --- rounding: "{value, number, :: .00}" --- @usage -- -- Usage in Lua -- local myMsg = core.l10n('MyMod', 'en') -- print( myMsg('good_morning') ) From 2f455aa87bd2f0ebca375dc799bb9bb455759a4d Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Tue, 12 Apr 2022 14:11:55 -0400 Subject: [PATCH 2325/2859] Added fallback details to l10n docs --- docs/source/reference/modding/localisation.rst | 11 +++++++++++ files/lua_api/openmw/core.lua | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/source/reference/modding/localisation.rst b/docs/source/reference/modding/localisation.rst index c0bf766d66..bc66053d58 100644 --- a/docs/source/reference/modding/localisation.rst +++ b/docs/source/reference/modding/localisation.rst @@ -19,6 +19,17 @@ and can also use hyphens instead of underscores. The locale will be normalised t Locales may also contain variants and keywords, though these usually will not be necessary. See `The Locale chapter of the ICU Guide `_ for full details. +Fallbacks +--------- + +When OpenMW looks up messages at runtime, it starts with the first requested locale, and then looks at that locale's more generic ancestors before looking at the next requested locale. E.g. ``en_GB_OED`` will fall back to ``en_GB``, which will fall back to ``en``. + +When including localisations with specific country variants (or more specific variants/keywords), you should always include the more generic version as well. + +E.g. if you include ``en_US.yaml`` and ``en_GB.yaml`` localisation files, you should also include ``en.yaml``, since other English locales will fall back to that (e.g. ``en_CA``, ``en_AU``, ``en_NZ``). You can put an arbitrary ``en`` locale of your choice in ``en.yaml``, and then leave the file for that variant empty (since all lookups for the variant will fall back to ``en`` anyway). + +Note that because of the fallbacks only messages which differ between variants need to be included in the country-specific localisation files. + Localisation Files -------------------------- diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index dd4ccac831..d719853da0 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -59,11 +59,18 @@ -- -- See [Localisation](../modding/localisation.html) for details of the localisation file structure. -- +-- When calling the l10n formatting function, if no localisation can be found for any of the requested locales then +-- the message key will be returned instead (and formatted, if possible). +-- This makes it possible to use the source strings as message identifiers. +-- +-- If you do not use the source string as a message identifier you should instead make certain to include +-- a fallback locale with a complete set of messages. +-- -- @function [parent=#core] l10n -- @param #string context l10n context; recommended to use the name of the mod. -- This must match the directory in the VFS which stores the localisation files. -- @param #string fallbackLocale The source locale containing the default messages --- If omitted defaults to "en" +-- If omitted defaults to "en". -- @return #function -- @usage -- # DataFiles/l10n/MyMod/en.yaml From 755c161ab75ccc299651fc46cc311755942b9aec Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Wed, 20 Apr 2022 18:57:05 -0400 Subject: [PATCH 2326/2859] Display fatal error if OPENMW_ICU_HOST_BUILD_DIR is not set on Android --- extern/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index f7bf9c4556..7d58ceb45e 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -248,6 +248,12 @@ if (NOT OPENMW_USE_SYSTEM_ICU) # Note: Must be a build directory, not an install root, since the configure script # looks for a configuration file which does not get installed. set(OPENMW_ICU_HOST_BUILD_DIR "" CACHE STRING "A pre-built ICU build directory for the host system if cross-compiling") + if (OPENMW_ICU_HOST_BUILD_DIR STREQUAL "") + message(FATAL_ERROR "If cross-compiling on android you must set the \ + OPENMW_ICU_HOST_BUILD_DIR to the path of a pre-compiled build of \ + ICU 70.1 for the system doing the build, as ICU needs to be able \ + to run its own executables as part of the build process.") + endif() # We need a host version of ICU so that the tools can be run when building the data library. set(NDK_STANDARD_ROOT ${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64) string(REPLACE "android-" "" ANDROIDVER ${ANDROID_PLATFORM}) From 206f0d4b1d6dc7765743cea76dbc6440f237b055 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Wed, 20 Apr 2022 18:58:45 -0400 Subject: [PATCH 2327/2859] Remove some hardcoded values in when building ICU on android --- extern/CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 7d58ceb45e..0c464381e3 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -257,17 +257,17 @@ if (NOT OPENMW_USE_SYSTEM_ICU) # We need a host version of ICU so that the tools can be run when building the data library. set(NDK_STANDARD_ROOT ${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64) string(REPLACE "android-" "" ANDROIDVER ${ANDROID_PLATFORM}) + # Wants a triple such as aarch64-linux-android, excluding a trailing + # -clang etc. + string(REGEX MATCH "^[^-]\+-[^-]+-[^-]+" ICU_TOOLCHAIN_NAME ${ANDROID_TOOLCHAIN_NAME}) set(ICU_ENV - "CC=ccache ${NDK_STANDARD_ROOT}/bin/aarch64-linux-android${ANDROIDVER}-clang" - "CXX=ccache ${NDK_STANDARD_ROOT}/bin/aarch64-linux-android${ANDROIDVER}-clang" - "RANLIB=${NDK_STANDARD_ROOT}/bin/aarch64-linux-android-ranlib" - "AR=${NDK_STANDARD_ROOT}/bin/aarch64-linux-android-ar" + "CC=${CMAKE_C_COMPILER_LAUNCHER} ${NDK_STANDARD_ROOT}/bin/${ICU_TOOLCHAIN_NAME}${ANDROIDVER}-clang" + "CXX=${CMAKE_CXX_COMPILER_LAUNCHER} ${NDK_STANDARD_ROOT}/bin/${ICU_TOOLCHAIN_NAME}${ANDROIDVER}-clang" + "RANLIB=${NDK_STANDARD_ROOT}/bin/${ICU_TOOLCHAIN_NAME}-ranlib" + "AR=${NDK_STANDARD_ROOT}/bin/${ICU_TOOLCHAIN_NAME}-ar" "CPPFLAGS=${ANDROID_COMPILER_FLAGS}" "LDFLAGS=${ANDROID_LINKER_FLAGS} -lc -lstdc++" ) - # Wants a triple such as aarch64-linux-android, excluding a trailing - # -clang etc. - string(REGEX MATCH "^[^-]\+-[^-]+-[^-]+" ICU_TOOLCHAIN_NAME ${ANDROID_TOOLCHAIN_NAME}) set(ICU_ADDITIONAL_OPTS --host=${ICU_TOOLCHAIN_NAME}${ANDROIDVER} --with-cross-build=${OPENMW_ICU_HOST_BUILD_DIR}) endif() include(ExternalProject) From 8a09cec0ab64e2b8e2c9d43148ba095800952790 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Wed, 20 Apr 2022 18:59:31 -0400 Subject: [PATCH 2328/2859] Only disable building ICU tools when doing an android cross-compile Otherwise the icudata library won't be built --- extern/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 0c464381e3..c95a9273af 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -268,7 +268,7 @@ if (NOT OPENMW_USE_SYSTEM_ICU) "CPPFLAGS=${ANDROID_COMPILER_FLAGS}" "LDFLAGS=${ANDROID_LINKER_FLAGS} -lc -lstdc++" ) - set(ICU_ADDITIONAL_OPTS --host=${ICU_TOOLCHAIN_NAME}${ANDROIDVER} --with-cross-build=${OPENMW_ICU_HOST_BUILD_DIR}) + set(ICU_ADDITIONAL_OPTS --disable-tools --host=${ICU_TOOLCHAIN_NAME}${ANDROIDVER} --with-cross-build=${OPENMW_ICU_HOST_BUILD_DIR}) endif() include(ExternalProject) ExternalProject_Add(icu @@ -277,7 +277,7 @@ if (NOT OPENMW_USE_SYSTEM_ICU) SOURCE_DIR fetched/icu CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${ICU_ENV} /icu4c/source/configure --enable-static --disable-shared - --disable-tests --disable-samples --disable-icuio --disable-extras --disable-tools ${ICU_ADDITIONAL_OPTS} + --disable-tests --disable-samples --disable-icuio --disable-extras ${ICU_ADDITIONAL_OPTS} BUILD_COMMAND make INSTALL_COMMAND "" ) From 8179a097f837a9593d738a8567d95f9f40aaa1b3 Mon Sep 17 00:00:00 2001 From: Benjamin Winger Date: Wed, 20 Apr 2022 19:00:23 -0400 Subject: [PATCH 2329/2859] Added ICU feature filters to limit the size of the data library when building ICU ourselves --- extern/CMakeLists.txt | 2 ++ extern/icufilters.json | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 extern/icufilters.json diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index c95a9273af..898dfeffad 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -244,6 +244,7 @@ if (NOT OPENMW_USE_SYSTEM_YAML_CPP) endif() if (NOT OPENMW_USE_SYSTEM_ICU) + set(ICU_ENV "ICU_DATA_FILTER_FILE=${CMAKE_CURRENT_SOURCE_DIR}/icufilters.json") if (ANDROID) # Note: Must be a build directory, not an install root, since the configure script # looks for a configuration file which does not get installed. @@ -261,6 +262,7 @@ if (NOT OPENMW_USE_SYSTEM_ICU) # -clang etc. string(REGEX MATCH "^[^-]\+-[^-]+-[^-]+" ICU_TOOLCHAIN_NAME ${ANDROID_TOOLCHAIN_NAME}) set(ICU_ENV + ${ICU_ENV} "CC=${CMAKE_C_COMPILER_LAUNCHER} ${NDK_STANDARD_ROOT}/bin/${ICU_TOOLCHAIN_NAME}${ANDROIDVER}-clang" "CXX=${CMAKE_CXX_COMPILER_LAUNCHER} ${NDK_STANDARD_ROOT}/bin/${ICU_TOOLCHAIN_NAME}${ANDROIDVER}-clang" "RANLIB=${NDK_STANDARD_ROOT}/bin/${ICU_TOOLCHAIN_NAME}-ranlib" diff --git a/extern/icufilters.json b/extern/icufilters.json new file mode 100644 index 0000000000..837658e5c7 --- /dev/null +++ b/extern/icufilters.json @@ -0,0 +1,19 @@ +{ + "featureFilters": { + "brkitr_rules": "exclude", + "brkitr_dictionaries": "exclude", + "brkitr_tree": "exclude", + "coll_ucadata": "exclude", + "coll_tree": "exclude", + "confusables": "exclude", + "conversion_mappings": "exclude", + "zone_tree": "exclude", + "zone_supplemental": "exclude", + "translit": "exclude", + "cnvalias": "exclude", + "lang_tree": "exclude", + "normalization": "exclude", + "region_tree": "exclude", + "stringprep": "exclude" + } +} From 51a84aaef8390dce12e4fba1e5cf0305792b6a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Thu, 21 Apr 2022 11:51:10 +0300 Subject: [PATCH 2330/2859] Include random state record in count of saved records --- apps/openmw/mwworld/worldimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c40d3b6d5b..f925ebec82 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -331,7 +331,8 @@ namespace MWWorld +1 // weather record +1 // actorId counter +1 // levitation/teleport enabled state - +1; // camera + +1 // camera + +1; // random state. } int World::countSavedGameCells() const From 88d09c336c6506712705657a39838d1a4f4a9ebe Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 9 Apr 2022 23:07:57 +0200 Subject: [PATCH 2331/2859] Lua console --- apps/openmw/mwbase/luamanager.hpp | 2 + apps/openmw/mwbase/windowmanager.hpp | 7 + apps/openmw/mwgui/console.cpp | 51 ++++-- apps/openmw/mwgui/console.hpp | 11 +- apps/openmw/mwgui/windowmanagerimp.cpp | 10 ++ apps/openmw/mwgui/windowmanagerimp.hpp | 2 + apps/openmw/mwlua/luamanagerimp.cpp | 24 +++ apps/openmw/mwlua/luamanagerimp.hpp | 9 + apps/openmw/mwlua/playerscripts.hpp | 9 +- apps/openmw/mwlua/uibindings.cpp | 29 ++++ .../lua-scripting/engine_handlers.rst | 26 ++- files/builtin_scripts/CMakeLists.txt | 3 + files/builtin_scripts/builtin.omwscripts | 3 + files/builtin_scripts/openmw_aux/util.lua | 23 ++- .../scripts/omw/console/global.lua | 81 +++++++++ .../scripts/omw/console/local.lua | 71 ++++++++ .../scripts/omw/console/player.lua | 158 ++++++++++++++++++ files/lua_api/openmw/ui.lua | 31 ++++ 18 files changed, 522 insertions(+), 28 deletions(-) create mode 100644 files/builtin_scripts/scripts/omw/console/global.lua create mode 100644 files/builtin_scripts/scripts/omw/console/local.lua create mode 100644 files/builtin_scripts/scripts/omw/console/player.lua diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 5ed751ffed..6c1ce6a27b 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -92,6 +92,8 @@ namespace MWBase // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. virtual void reloadAllScripts() = 0; + + virtual void handleConsoleCommand(const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) = 0; }; } diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index eeb2d6a56b..04f21906a0 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -154,6 +154,13 @@ namespace MWBase virtual void updateSpellWindow() = 0; virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; + virtual void setConsoleMode(const std::string& mode) = 0; + + static constexpr std::string_view sConsoleColor_Default = "#FFFFFF"; + static constexpr std::string_view sConsoleColor_Error = "#FF2222"; + static constexpr std::string_view sConsoleColor_Success = "#FF00FF"; + static constexpr std::string_view sConsoleColor_Info = "#AAAAAA"; + virtual void printToConsole(const std::string& msg, std::string_view color) = 0; /// Set time left for the player to start drowning (update the drowning bar) /// @param time time left to start drowning diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index a3663b6b9f..aaf36aab59 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -162,25 +162,34 @@ namespace MWGui MyGUI::LayerManager::getInstance().upLayerItem(mMainWidget); } - void Console::print(const std::string &msg, const std::string& color) + void Console::print(const std::string &msg, std::string_view color) { - mHistory->addText(color + MyGUI::TextIterator::toTagsString(msg)); + mHistory->addText(std::string(color) + MyGUI::TextIterator::toTagsString(msg)); } void Console::printOK(const std::string &msg) { - print(msg + "\n", "#FF00FF"); + print(msg + "\n", MWBase::WindowManager::sConsoleColor_Success); } void Console::printError(const std::string &msg) { - print(msg + "\n", "#FF2222"); + print(msg + "\n", MWBase::WindowManager::sConsoleColor_Error); } void Console::execute (const std::string& command) { // Log the command - print("> " + command + "\n"); + if (mConsoleMode.empty()) + print("> " + command + "\n"); + else + print(mConsoleMode + " " + command + "\n"); + + if (!mConsoleMode.empty() || (command.size() >= 3 && std::string_view(command).substr(0, 3) == "lua")) + { + MWBase::Environment::get().getLuaManager()->handleConsoleCommand(mConsoleMode, command, mPtr); + return; + } Compiler::Locals locals; if (!mPtr.isEmpty()) @@ -271,7 +280,7 @@ namespace MWGui } } } - else if(key == MyGUI::KeyCode::Tab) + else if(key == MyGUI::KeyCode::Tab && mConsoleMode.empty()) { std::vector matches; listNames(); @@ -475,7 +484,7 @@ namespace MWGui void Console::onResChange(int width, int height) { - setCoord(10,10, width-10, height/2); + setCoord(10, 10, width-10, height/2); } void Console::updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) @@ -489,23 +498,31 @@ namespace MWGui if (!object.isEmpty()) { if (object == mPtr) - { - setTitle("#{sConsoleTitle}"); - mPtr=MWWorld::Ptr(); - } + mPtr = MWWorld::Ptr(); else - { - setTitle("#{sConsoleTitle} (" + object.getCellRef().getRefId() + ")"); mPtr = object; - } // User clicked on an object. Restore focus to the console command line. MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } else - { - setTitle("#{sConsoleTitle}"); mPtr = MWWorld::Ptr(); - } + updateConsoleTitle(); + } + + void Console::updateConsoleTitle() + { + std::string title = "#{sConsoleTitle}"; + if (!mConsoleMode.empty()) + title = mConsoleMode + " " + title; + if (!mPtr.isEmpty()) + title.append(" (" + mPtr.getCellRef().getRefId() + ")"); + setTitle(title); + } + + void Console::setConsoleMode(std::string_view mode) + { + mConsoleMode = std::string(mode); + updateConsoleTitle(); } void Console::onReferenceUnavailable() diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 2b8cecbc01..891c4e0ac7 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -9,6 +9,8 @@ #include #include +#include "../mwbase/windowmanager.hpp" + #include "../mwscript/compilercontext.hpp" #include "../mwscript/interpretercontext.hpp" @@ -40,7 +42,7 @@ namespace MWGui void onResChange(int width, int height) override; // Print a message to the console, in specified color. - void print(const std::string &msg, const std::string& color = "#FFFFFF"); + void print(const std::string &msg, std::string_view color = MWBase::WindowManager::sConsoleColor_Default); // These are pre-colored versions that you should use. @@ -60,12 +62,19 @@ namespace MWGui void resetReference () override; + const std::string& getConsoleMode() const { return mConsoleMode; } + void setConsoleMode(std::string_view mode); + protected: void onReferenceUnavailable() override; private: + std::string mConsoleMode; + + void updateConsoleTitle(); + void keyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 0be8f2c751..23c16082b4 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2069,6 +2069,16 @@ namespace MWGui mConsole->setSelectedObject(object); } + void WindowManager::printToConsole(const std::string& msg, std::string_view color) + { + mConsole->print(msg, color); + } + + void WindowManager::setConsoleMode(const std::string& mode) + { + mConsole->setConsoleMode(mode); + } + std::string WindowManager::correctIconPath(const std::string& path) { return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS()); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 11d10ab45e..90e8d1b0d7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -195,6 +195,8 @@ namespace MWGui void updateSpellWindow() override; void setConsoleSelectedObject(const MWWorld::Ptr& object) override; + void printToConsole(const std::string& msg, std::string_view color) override; + void setConsoleMode(const std::string& mode) override; /// Set time left for the player to start drowning (update the drowning bar) /// @param time time left to start drowning diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index aa2d5baa6f..80dfef16cd 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -242,6 +242,9 @@ namespace MWLua for (const std::string& message : mUIMessages) windowManager->messageBox(message); mUIMessages.clear(); + for (auto& [msg, color] : mInGameConsoleMessages) + windowManager->printToConsole(msg, "#" + color.toHex()); + mInGameConsoleMessages.clear(); for (std::unique_ptr& action : mActionQueue) action->safeApply(mWorldView); @@ -256,6 +259,7 @@ namespace MWLua { LuaUi::clearUserInterface(); mUiResourceManager.clear(); + MWBase::Environment::get().getWindowManager()->setConsoleMode(""); mActiveLocalScripts.clear(); mLocalEvents.clear(); mGlobalEvents.clear(); @@ -480,6 +484,7 @@ namespace MWLua Log(Debug::Info) << "Reload Lua"; LuaUi::clearUserInterface(); + MWBase::Environment::get().getWindowManager()->setConsoleMode(""); mUiResourceManager.clear(); mLua.dropScriptCache(); initConfiguration(); @@ -503,6 +508,25 @@ namespace MWLua scripts->receiveEngineEvent(LocalScripts::OnActive()); } + void LuaManager::handleConsoleCommand(const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) + { + PlayerScripts* playerScripts = nullptr; + if (!mPlayer.isEmpty()) + playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); + if (!playerScripts) + { + MWBase::Environment::get().getWindowManager()->printToConsole("You must enter a game session to run Lua commands\n", + MWBase::WindowManager::sConsoleColor_Error); + return; + } + sol::object selected = sol::nil; + if (!selectedPtr.isEmpty()) + selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr), mWorldView.getObjectRegistry())); + if (!playerScripts->consoleCommand(consoleMode, command, selected)) + MWBase::Environment::get().getWindowManager()->printToConsole("No Lua handlers for console\n", + MWBase::WindowManager::sConsoleColor_Error); + } + LuaManager::Action::Action(LuaUtil::LuaState* state) { static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index fd9ec8b172..a75cac2da9 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -10,6 +10,8 @@ #include +#include + #include "../mwbase/luamanager.hpp" #include "object.hpp" @@ -60,6 +62,10 @@ namespace MWLua // Used only in Lua bindings void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } + void addInGameConsoleMessage(const std::string& msg, const Misc::Color& color) + { + mInGameConsoleMessages.push_back({msg, color}); + } // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited @@ -94,6 +100,8 @@ namespace MWLua // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. void reloadAllScripts() override; + void handleConsoleCommand(const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) override; + // Used to call Lua callbacks from C++ void queueCallback(LuaUtil::Callback callback, sol::object arg) { @@ -172,6 +180,7 @@ namespace MWLua std::vector> mActionQueue; std::unique_ptr mTeleportPlayerAction; std::vector mUIMessages; + std::vector> mInGameConsoleMessages; LuaUtil::LuaStorage mGlobalStorage{mLua.sol()}; LuaUtil::LuaStorage mPlayerStorage{mLua.sol()}; diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index 15ab451792..1e909dc72c 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -18,7 +18,7 @@ namespace MWLua PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer) { registerEngineHandlers({ - &mKeyPressHandlers, &mKeyReleaseHandlers, + &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mInputUpdateHandlers, &mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved @@ -59,7 +59,14 @@ namespace MWLua void inputUpdate(float dt) { callEngineHandlers(mInputUpdateHandlers, dt); } + bool consoleCommand(const std::string& consoleMode, const std::string& command, const sol::object& selectedObject) + { + callEngineHandlers(mConsoleCommandHandlers, consoleMode, command, selectedObject); + return !mConsoleCommandHandlers.mList.empty(); + } + private: + EngineHandlerList mConsoleCommandHandlers{"onConsoleCommand"}; EngineHandlerList mKeyPressHandlers{"onKeyPress"}; EngineHandlerList mKeyReleaseHandlers{"onKeyRelease"}; EngineHandlerList mControllerButtonPressHandlers{"onControllerButtonPress"}; diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index f82dd3db00..84ec98adfb 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -12,6 +12,8 @@ #include "context.hpp" #include "luamanagerimp.hpp" +#include "../mwbase/windowmanager.hpp" + namespace MWLua { namespace @@ -213,6 +215,33 @@ namespace MWLua { luaManager->addUIMessage(message); }; + api["CONSOLE_COLOR"] = LuaUtil::makeReadOnly(context.mLua->tableFromPairs({ + {"Default", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Default.substr(1))}, + {"Error", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Error.substr(1))}, + {"Success", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Success.substr(1))}, + {"Info", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Info.substr(1))}, + })); + api["printToConsole"] = [luaManager=context.mLuaManager](const std::string& message, const Misc::Color& color) + { + luaManager->addInGameConsoleMessage(message + "\n", color); + }; + api["setConsoleMode"] = [luaManager=context.mLuaManager](std::string_view mode) + { + luaManager->addAction( + [mode = std::string(mode)]{ MWBase::Environment::get().getWindowManager()->setConsoleMode(mode); }); + }; + api["setConsoleSelectedObject"] = [luaManager=context.mLuaManager](const sol::object& obj) + { + auto* wm = MWBase::Environment::get().getWindowManager(); + if (obj == sol::nil) + luaManager->addAction([wm]{ wm->setConsoleSelectedObject(MWWorld::Ptr()); }); + else + { + if (!obj.is()) + throw std::runtime_error("Game object expected"); + luaManager->addAction([wm, obj=obj.as()]{ wm->setConsoleSelectedObject(obj.ptr()); }); + } + }; api["content"] = [](const sol::table& table) { return LuaUi::Content(table); diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index adc07d25fe..6f24750736 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -70,23 +70,28 @@ Engine handler is a function defined by a script, that can be called by the engi :widths: 20 80 * - onInputUpdate(dt) - - | Called every frame (if the game is not paused) right after processing - | user input. Use it only for latency-critical stuff. + - | Called every frame (if the game is not paused) right after + | processing user input. Use it only for latency-critical stuff. * - onKeyPress(key) - | `Key `_ is pressed. - | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` + | Usage example: + | ``if key.symbol == 'z' and key.withShift then ...`` * - onKeyRelease(key) - | `Key `_ is released. - | Usage example: ``if key.symbol == 'z' and key.withShift then ...`` + | Usage example: + | ``if key.symbol == 'z' and key.withShift then ...`` * - onControllerButtonPress(id) - | A `button `_ on a game controller is pressed. - | Usage example: ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` + | Usage example: + | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` * - onControllerButtonRelease(id) - | A `button `_ on a game controller is released. - | Usage example: ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` + | Usage example: + | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` * - onInputAction(id) - | `Game control `_ is pressed. - | Usage example: ``if id == input.ACTION.ToggleWeapon then ...`` + | Usage example: + | ``if id == input.ACTION.ToggleWeapon then ...`` * - onTouchPress(touchEvent) - | A finger pressed on a touch device. | `Touch event `_. @@ -95,4 +100,9 @@ Engine handler is a function defined by a script, that can be called by the engi | `Touch event `_. * - onTouchMove(touchEvent) - | A finger moved on a touch device. - | `Touch event `_. \ No newline at end of file + | `Touch event `_. + * - | onConsoleCommand( + | mode, command, selectedObject) + - | User entered `command` in in-game console. Called if either + | `mode` is not default or `command` starts with prefix `lua`. + diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 7afcd7f529..1732270527 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -13,6 +13,9 @@ set(LUA_BUILTIN_FILES scripts/omw/camera.lua scripts/omw/head_bobbing.lua scripts/omw/third_person.lua + scripts/omw/console/player.lua + scripts/omw/console/global.lua + scripts/omw/console/local.lua l10n/Calendar/en.yaml ) diff --git a/files/builtin_scripts/builtin.omwscripts b/files/builtin_scripts/builtin.omwscripts index 2ffe7007f2..e1fbbcdb7c 100644 --- a/files/builtin_scripts/builtin.omwscripts +++ b/files/builtin_scripts/builtin.omwscripts @@ -1,2 +1,5 @@ PLAYER: scripts/omw/camera.lua NPC,CREATURE: scripts/omw/ai.lua +PLAYER: scripts/omw/console/player.lua +GLOBAL: scripts/omw/console/global.lua +CUSTOM: scripts/omw/console/local.lua diff --git a/files/builtin_scripts/openmw_aux/util.lua b/files/builtin_scripts/openmw_aux/util.lua index c442178b5e..4832517418 100644 --- a/files/builtin_scripts/openmw_aux/util.lua +++ b/files/builtin_scripts/openmw_aux/util.lua @@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +--- -- `openmw_aux.util` defines utility functions that are implemented in Lua rather than in C++. -- Implementation can be found in `resources/vfs/openmw_aux/util.lua`. -- @module util @@ -6,6 +6,27 @@ local aux_util = {} +--- +-- Works like `tostring` but shows also content of tables. +-- @function [parent=#util] deepToString +-- @param #any value The value to conver to string +-- @param #number maxDepth Max depth of tables unpacking (optional, 2 by default) +function aux_util.deepToString(val, level, prefix) + level = (level or 1) - 1 + local ok, iter, t = pcall(function() return pairs(val) end) + if level < 0 or not ok then + return tostring(val) + end + local prefix = prefix or '' + local newPrefix = prefix .. ' ' + local strs = {tostring(val) .. ' {\n'} + for k, v in iter, t do + strs[#strs + 1] = newPrefix .. tostring(k) .. ' = ' .. aux_util.deepToString(v, level, newPrefix) .. ',\n' + end + strs[#strs + 1] = prefix .. '}' + return table.concat(strs) +end + ------------------------------------------------------------------------------- -- Finds the nearest object to the given point in the given list. -- Ignores cells, uses only coordinates. Returns the nearest object, diff --git a/files/builtin_scripts/scripts/omw/console/global.lua b/files/builtin_scripts/scripts/omw/console/global.lua new file mode 100644 index 0000000000..e3b4375b1e --- /dev/null +++ b/files/builtin_scripts/scripts/omw/console/global.lua @@ -0,0 +1,81 @@ +local util = require('openmw.util') + +local player = nil + +local function printToConsole(...) + local strs = {} + for i = 1, select('#', ...) do + strs[i] = tostring(select(i, ...)) + end + player:sendEvent('OMWConsolePrint', table.concat(strs, '\t')) +end + +local function printRes(...) + if select('#', ...) >= 0 then + printToConsole(...) + end +end + +local env = { + I = require('openmw.interfaces'), + util = require('openmw.util'), + storage = require('openmw.storage'), + core = require('openmw.core'), + types = require('openmw.types'), + async = require('openmw.async'), + world = require('openmw.world'), + aux_util = require('openmw_aux.util'), + view = require('openmw_aux.util').deepToString, + print = printToConsole, + exit = function() player:sendEvent('OMWConsoleExit') end, + help = function() player:sendEvent('OMWConsoleHelp') end, +} +env._G = env +setmetatable(env, {__index = _G, __metatable = false}) +_G = nil + +local function executeLuaCode(code) + local fn + local ok, err = pcall(function() fn = util.loadCode('return ' .. code, env) end) + if ok then + ok, err = pcall(function() printRes(fn()) end) + else + ok, err = pcall(function() util.loadCode(code, env)() end) + end + if not ok then + player:sendEvent('OMWConsoleError', err) + end +end + +return { + eventHandlers = { + OMWConsoleEval = function(data) + player = data.player + env.selected = data.selected + executeLuaCode(data.code) + if env.selected ~= data.selected then + local ok, err = pcall(function() player:sendEvent('OMWConsoleSetSelected', env.selected) end) + if not ok then player:sendEvent('OMWConsoleError', err) end + end + end, + OMWConsoleStartLocal = function(data) + player = data.player + ok, err = pcall(function() + if not data.selected:hasScript('scripts/omw/console/local.lua') then + data.selected:addScript('scripts/omw/console/local.lua') + end + end) + if ok then + player:sendEvent('OMWConsoleSetContext', data.selected) + else + player:sendEvent('OMWConsoleError', err) + end + end, + OMWConsoleStopLocal = function(obj) + if obj:hasScript('scripts/omw/console/local.lua') then + obj:removeScript('scripts/omw/console/local.lua') + end + end, + }, +} + diff --git a/files/builtin_scripts/scripts/omw/console/local.lua b/files/builtin_scripts/scripts/omw/console/local.lua new file mode 100644 index 0000000000..adcad3d6cb --- /dev/null +++ b/files/builtin_scripts/scripts/omw/console/local.lua @@ -0,0 +1,71 @@ +local util = require('openmw.util') +local core = require('openmw.core') +local self = require('openmw.self') + +local player = nil + +local function printToConsole(...) + local strs = {} + for i = 1, select('#', ...) do + strs[i] = tostring(select(i, ...)) + end + player:sendEvent('OMWConsolePrint', table.concat(strs, '\t')) +end + +local function printRes(...) + if select('#', ...) >= 0 then + printToConsole(...) + end +end + +local env = { + I = require('openmw.interfaces'), + util = require('openmw.util'), + storage = require('openmw.storage'), + core = require('openmw.core'), + types = require('openmw.types'), + async = require('openmw.async'), + nearby = require('openmw.nearby'), + self = require('openmw.self'), + aux_util = require('openmw_aux.util'), + view = require('openmw_aux.util').deepToString, + print = printToConsole, + exit = function() player:sendEvent('OMWConsoleExit') end, + help = function() player:sendEvent('OMWConsoleHelp') end, +} +env._G = env +setmetatable(env, {__index = _G, __metatable = false}) +_G = nil + +local function executeLuaCode(code) + local fn + local ok, err = pcall(function() fn = util.loadCode('return ' .. code, env) end) + if ok then + ok, err = pcall(function() printRes(fn()) end) + else + ok, err = pcall(function() util.loadCode(code, env)() end) + end + if not ok then + player:sendEvent('OMWConsoleError', err) + end +end + +return { + eventHandlers = { + OMWConsoleEval = function(data) + player = data.player + env.selected = data.selected + executeLuaCode(data.code) + if env.selected ~= data.selected then + local ok, err = pcall(function() player:sendEvent('OMWConsoleSetSelected', env.selected) end) + if not ok then player:sendEvent('OMWConsoleError', err) end + end + end, + }, + engineHandlers = { + onLoad = function() + core.sendGlobalEvent('OMWConsoleStopLocal', self.object) + end, + } +} + diff --git a/files/builtin_scripts/scripts/omw/console/player.lua b/files/builtin_scripts/scripts/omw/console/player.lua new file mode 100644 index 0000000000..fd4309b610 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/console/player.lua @@ -0,0 +1,158 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') +local self = require('openmw.self') +local core = require('openmw.core') + +local function printHelp() + local msg = [[ +This is the built-in Lua interpreter. +help() - print this message +exit() - exit Lua mode +selected - currently selected object (click on any object to change) +view(_G) - print content of the table `_G` (current environment) + standard libraries (math, string, etc.) are loaded by default but not visible in `_G` +view(types, 2) - print table `types` (i.e. `openmw.types`) and its subtables (2 - traversal depth)]] + ui.printToConsole(msg, ui.CONSOLE_COLOR.Info) +end + +local function printToConsole(...) + local strs = {} + for i = 1, select('#', ...) do + strs[i] = tostring(select(i, ...)) + end + return ui.printToConsole(table.concat(strs, '\t'), ui.CONSOLE_COLOR.Info) +end + +local function printRes(...) + if select('#', ...) >= 0 then + printToConsole(...) + end +end + +local currentSelf = nil +local currentMode = '' + +local function exitLuaMode() + currentSelf = nil + currentMode = '' + ui.setConsoleMode('') + ui.printToConsole('Lua mode OFF', ui.CONSOLE_COLOR.Success) +end + +local function setContext(obj) + ui.printToConsole('Lua mode ON, use exit() to return, help() for more info', ui.CONSOLE_COLOR.Success) + if obj == self then + currentMode = 'Lua[Player]' + ui.printToConsole('Context: Player', ui.CONSOLE_COLOR.Success) + elseif obj then + if not obj:isValid() then error('Object not available') end + currentMode = 'Lua['..obj.recordId..']' + ui.printToConsole('Context: Local['..tostring(obj)..']', ui.CONSOLE_COLOR.Success) + else + currentMode = 'Lua[Global]' + ui.printToConsole('Context: Global', ui.CONSOLE_COLOR.Success) + end + currentSelf = obj + ui.setConsoleMode(currentMode) +end + +local function setSelected(obj) + local ok, err = pcall(function() ui.setConsoleSelectedObject(obj) end) + if ok then + ui.printToConsole('Selected object changed', ui.CONSOLE_COLOR.Success) + else + ui.printToConsole(err, ui.CONSOLE_COLOR.Error) + end +end + +local env = { + I = require('openmw.interfaces'), + util = require('openmw.util'), + storage = require('openmw.storage'), + core = require('openmw.core'), + types = require('openmw.types'), + async = require('openmw.async'), + nearby = require('openmw.nearby'), + self = require('openmw.self'), + input = require('openmw.input'), + ui = require('openmw.ui'), + camera = require('openmw.camera'), + aux_util = require('openmw_aux.util'), + view = require('openmw_aux.util').deepToString, + print = printToConsole, + exit = exitLuaMode, + help = printHelp, +} +env._G = env +setmetatable(env, {__index = _G, __metatable = false}) +_G = nil + +local function executeLuaCode(code) + local fn + local ok, err = pcall(function() fn = util.loadCode('return ' .. code, env) end) + if ok then + ok, err = pcall(function() printRes(fn()) end) + else + ok, err = pcall(function() util.loadCode(code, env)() end) + end + if not ok then + ui.printToConsole(err, ui.CONSOLE_COLOR.Error) + end +end + +local function onConsoleCommand(mode, cmd, selectedObject) + env.selected = selectedObject + if mode == '' then + cmd, arg = cmd:lower():match('(%w+) *(%w*)') + if cmd == 'lua' then + if arg == 'player' then + cmd = 'luap' + elseif arg == 'global' then + cmd = 'luag' + elseif arg == 'selected' then + cmd = 'luas' + else + local msg = [[ +Usage: 'lua player' or 'luap' - enter player context + 'lua global' or 'luag' - enter global context + 'lua selected' or 'luas' - enter local context on the selected object]] + ui.printToConsole(msg, ui.CONSOLE_COLOR.Info) + end + end + if cmd == 'luap' or (cmd == 'luas' and selectedObject == self.object) then + setContext(self) + elseif cmd == 'luag' then + setContext() + elseif cmd == 'luas' then + if selectedObject then + core.sendGlobalEvent('OMWConsoleStartLocal', {player=self.object, selected=selectedObject}) + else + ui.printToConsole('No selected object', ui.CONSOLE_COLOR.Error) + end + end + elseif mode == currentMode then + if cmd == 'exit()' then + exitLuaMode() + elseif currentSelf == self then + executeLuaCode(cmd) + if env.selected ~= selectedObject then setSelected(env.selected) end + elseif currentSelf then + currentSelf:sendEvent('OMWConsoleEval', {player=self.object, code=cmd, selected=selectedObject}) + else + core.sendGlobalEvent('OMWConsoleEval', {player=self.object, code=cmd, selected=selectedObject}) + end + end +end + +return { + engineHandlers = {onConsoleCommand = onConsoleCommand}, + eventHandlers = { + OMWConsolePrint = function(msg) ui.printToConsole(tostring(msg), ui.CONSOLE_COLOR.Info) end, + OMWConsoleError = function(msg) ui.printToConsole(tostring(msg), ui.CONSOLE_COLOR.Error) end, + OMWConsoleSetContext = setContext, + OMWConsoleSetSelected = setSelected, + OMWConsoleExit = exitLuaMode, + OMWConsoleHelp = printHelp, + } +} + diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 0ef35a61a5..45adbd8508 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -38,6 +38,37 @@ -- @function [parent=#ui] showMessage -- @param #string msg +--- +-- Predefined colors for console output +-- @field [parent=#ui] #CONSOLE_COLOR CONSOLE_COLOR + +--- +-- Predefined colors for console output +-- @type CONSOLE_COLOR +-- @field openmw.util#Color Default +-- @field openmw.util#Color Error +-- @field openmw.util#Color Success +-- @field openmw.util#Color Info + +--- +-- Print to the in-game console. +-- @function [parent=#ui] printToConsole +-- @param #string msg +-- @param openmw.util#Color color + +--- +-- Set mode of the in-game console. +-- The mode can be any string, by default is empty. +-- If not empty, then the console doesn't handle mwscript commands and +-- instead passes user input to Lua scripts via `onConsoleCommand` engine handler. +-- @function [parent=#ui] setConsoleMode +-- @param #string mode + +--- +-- Set selected object for console. +-- @function [parent=#ui] setConsoleSelectedObject +-- @param openmw.core#GameObject obj + --- -- Returns the size of the OpenMW window in pixels as a 2D vector. -- @function [parent=#ui] screenSize From e777e354146b42e3791172a281d442d4754d2079 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Apr 2022 22:45:26 +0200 Subject: [PATCH 2332/2859] Use system_clock for time in logs high_resolution_clock may not give real time. MSVC implements it as steady_clock that is basically CPU time which is usually desynchronized with real time. --- CHANGELOG.md | 1 + components/debug/debugging.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfcce6018d..bcbd47beba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ Bug #6670: Dialogue order is incorrect Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer Bug #6682: HitOnMe doesn't fire as intended + Bug #6711: Log time differs from real time Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 65589bd6b4..7d94459136 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -78,11 +78,11 @@ namespace Debug int prefixSize; { prefix[0] = '['; - uint64_t ms = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count(); - std::time_t t = ms / 1000; - prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", std::localtime(&t)) + 1; + const auto now = std::chrono::system_clock::now(); + const auto time = std::chrono::system_clock::to_time_t(now); + prefixSize = std::strftime(prefix + 1, sizeof(prefix) - 1, "%T", std::localtime(&time)) + 1; char levelLetter = " EWIVD*"[int(level)]; + const auto ms = std::chrono::duration_cast(now.time_since_epoch()).count(); prefixSize += snprintf(prefix + prefixSize, sizeof(prefix) - prefixSize, ".%03u %c] ", static_cast(ms % 1000), levelLetter); } From 9d6d0c6ffb1e98467cea07c48789b35227c8d086 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 14 Apr 2022 17:01:36 +0200 Subject: [PATCH 2333/2859] Move ConstrainedStreamBuf into separate file --- components/CMakeLists.txt | 2 +- components/files/constrainedfilestream.cpp | 103 +----------------- components/files/constrainedfilestream.hpp | 9 +- components/files/constrainedfilestreambuf.cpp | 83 ++++++++++++++ components/files/constrainedfilestreambuf.hpp | 30 +++++ 5 files changed, 120 insertions(+), 107 deletions(-) create mode 100644 components/files/constrainedfilestreambuf.cpp create mode 100644 components/files/constrainedfilestreambuf.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 57a60322d5..7121bde103 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -206,7 +206,7 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF() add_component_dir (files linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager - lowlevelfile constrainedfilestream memorystream hash configfileparser openfile + lowlevelfile constrainedfilestream memorystream hash configfileparser openfile constrainedfilestreambuf ) add_component_dir (compiler diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index 0beaf5facc..aafcacba74 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -1,109 +1,8 @@ #include "constrainedfilestream.hpp" -#include -#include - -#include "lowlevelfile.hpp" - -namespace -{ -// somewhat arbitrary though 64KB buffers didn't seem to improve performance any -const size_t sBufferSize = 8192; -} - namespace Files { - class ConstrainedFileStreamBuf : public std::streambuf - { - - size_t mOrigin; - size_t mSize; - - LowLevelFile mFile; - - char mBuffer[sBufferSize]{0}; - - public: - ConstrainedFileStreamBuf(const std::string &fname, size_t start, size_t length) - { - mFile.open (fname.c_str ()); - mSize = length != std::numeric_limits::max() ? length : mFile.size () - start; - - if (start != 0) - mFile.seek(start); - - setg(nullptr,nullptr,nullptr); - - mOrigin = start; - } - - int_type underflow() override - { - if(gptr() == egptr()) - { - size_t toRead = std::min((mOrigin+mSize)-(mFile.tell()), sBufferSize); - // Read in the next chunk of data, and set the read pointers on success - // Failure will throw exception in LowLevelFile - size_t got = mFile.read(mBuffer, toRead); - setg(&mBuffer[0], &mBuffer[0], &mBuffer[0]+got); - } - if(gptr() == egptr()) - return traits_type::eof(); - - return traits_type::to_int_type(*gptr()); - } - - pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override - { - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - // new file position, relative to mOrigin - size_t newPos; - switch (whence) - { - case std::ios_base::beg: - newPos = offset; - break; - case std::ios_base::cur: - newPos = (mFile.tell() - mOrigin - (egptr() - gptr())) + offset; - break; - case std::ios_base::end: - newPos = mSize + offset; - break; - default: - return traits_type::eof(); - } - - if (newPos > mSize) - return traits_type::eof(); - - mFile.seek(mOrigin+newPos); - - // Clear read pointers so underflow() gets called on the next read attempt. - setg(nullptr, nullptr, nullptr); - - return newPos; - } - - pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override - { - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - if ((size_t)pos > mSize) - return traits_type::eof(); - - mFile.seek(mOrigin + pos); - - // Clear read pointers so underflow() gets called on the next read attempt. - setg(nullptr, nullptr, nullptr); - return pos; - } - - }; - - ConstrainedFileStream::ConstrainedFileStream(std::unique_ptr buf) + ConstrainedFileStream::ConstrainedFileStream(std::unique_ptr buf) : std::istream(buf.get()) , mBuf(std::move(buf)) { diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp index b828f0f6f1..4284705d17 100644 --- a/components/files/constrainedfilestream.hpp +++ b/components/files/constrainedfilestream.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_CONSTRAINEDFILESTREAM_H #define OPENMW_CONSTRAINEDFILESTREAM_H +#include "constrainedfilestreambuf.hpp" + #include #include #include @@ -10,14 +12,13 @@ namespace Files { /// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters. -class ConstrainedFileStream : public std::istream +class ConstrainedFileStream final : public std::istream { public: - ConstrainedFileStream(std::unique_ptr buf); - virtual ~ConstrainedFileStream() {}; + explicit ConstrainedFileStream(std::unique_ptr buf); private: - std::unique_ptr mBuf; + std::unique_ptr mBuf; }; typedef std::shared_ptr IStreamPtr; diff --git a/components/files/constrainedfilestreambuf.cpp b/components/files/constrainedfilestreambuf.cpp new file mode 100644 index 0000000000..1263523ebe --- /dev/null +++ b/components/files/constrainedfilestreambuf.cpp @@ -0,0 +1,83 @@ +#include "constrainedfilestreambuf.hpp" + +#include +#include + +namespace Files +{ + ConstrainedFileStreamBuf::ConstrainedFileStreamBuf(const std::string& fname, std::size_t start, std::size_t length) + : mOrigin(start) + { + mFile.open(fname.c_str()); + mSize = length != std::numeric_limits::max() ? length : mFile.size () - start; + + if (start != 0) + mFile.seek(start); + + setg(nullptr, nullptr, nullptr); + } + + std::streambuf::int_type ConstrainedFileStreamBuf::underflow() + { + if (gptr() == egptr()) + { + const std::size_t toRead = std::min((mOrigin + mSize) - (mFile.tell()), sizeof(mBuffer)); + // Read in the next chunk of data, and set the read pointers on success + // Failure will throw exception in LowLevelFile + const std::size_t got = mFile.read(mBuffer, toRead); + setg(&mBuffer[0], &mBuffer[0], &mBuffer[0] + got); + } + if (gptr() == egptr()) + return traits_type::eof(); + + return traits_type::to_int_type(*gptr()); + } + + std::streambuf::pos_type ConstrainedFileStreamBuf::seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) + { + if ((mode & std::ios_base::out) || !(mode & std::ios_base::in)) + return traits_type::eof(); + + // new file position, relative to mOrigin + size_t newPos; + switch (whence) + { + case std::ios_base::beg: + newPos = offset; + break; + case std::ios_base::cur: + newPos = (mFile.tell() - mOrigin - (egptr() - gptr())) + offset; + break; + case std::ios_base::end: + newPos = mSize + offset; + break; + default: + return traits_type::eof(); + } + + if (newPos > mSize) + return traits_type::eof(); + + mFile.seek(mOrigin + newPos); + + // Clear read pointers so underflow() gets called on the next read attempt. + setg(nullptr, nullptr, nullptr); + + return newPos; + } + + std::streambuf::pos_type ConstrainedFileStreamBuf::seekpos(pos_type pos, std::ios_base::openmode mode) + { + if ((mode & std::ios_base::out) || !(mode & std::ios_base::in)) + return traits_type::eof(); + + if (static_cast(pos) > mSize) + return traits_type::eof(); + + mFile.seek(mOrigin + pos); + + // Clear read pointers so underflow() gets called on the next read attempt. + setg(nullptr, nullptr, nullptr); + return pos; + } +} diff --git a/components/files/constrainedfilestreambuf.hpp b/components/files/constrainedfilestreambuf.hpp new file mode 100644 index 0000000000..46be98c905 --- /dev/null +++ b/components/files/constrainedfilestreambuf.hpp @@ -0,0 +1,30 @@ +#ifndef OPENMW_CONSTRAINEDFILESTREAMBUF_H +#define OPENMW_CONSTRAINEDFILESTREAMBUF_H + +#include "lowlevelfile.hpp" + +#include + +namespace Files +{ + /// A file streambuf constrained to a specific region in the file, specified by the 'start' and 'length' parameters. + class ConstrainedFileStreamBuf final : public std::streambuf + { + public: + ConstrainedFileStreamBuf(const std::string& fname, std::size_t start, std::size_t length); + + int_type underflow() final; + + pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) final; + + pos_type seekpos(pos_type pos, std::ios_base::openmode mode) final; + + private: + std::size_t mOrigin; + std::size_t mSize; + LowLevelFile mFile; + char mBuffer[8192]{0}; + }; +} + +#endif From c94d8be7bfc0e71f8ee81f8bd4cbf78e00e1bd56 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Apr 2022 01:55:14 +0200 Subject: [PATCH 2334/2859] Add generic StreamWithBuffer owning the underlying buffer --- components/files/constrainedfilestream.cpp | 6 ------ components/files/constrainedfilestream.hpp | 10 ++-------- components/files/streamwithbuffer.hpp | 23 ++++++++++++++++++++++ 3 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 components/files/streamwithbuffer.hpp diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index aafcacba74..22a85ce808 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -2,12 +2,6 @@ namespace Files { - ConstrainedFileStream::ConstrainedFileStream(std::unique_ptr buf) - : std::istream(buf.get()) - , mBuf(std::move(buf)) - { - } - IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start, std::size_t length) { return std::make_shared(std::make_unique(filename, start, length)); diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp index 4284705d17..3acb04d896 100644 --- a/components/files/constrainedfilestream.hpp +++ b/components/files/constrainedfilestream.hpp @@ -2,6 +2,7 @@ #define OPENMW_CONSTRAINEDFILESTREAM_H #include "constrainedfilestreambuf.hpp" +#include "streamwithbuffer.hpp" #include #include @@ -12,14 +13,7 @@ namespace Files { /// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters. -class ConstrainedFileStream final : public std::istream -{ -public: - explicit ConstrainedFileStream(std::unique_ptr buf); - -private: - std::unique_ptr mBuf; -}; +using ConstrainedFileStream = StreamWithBuffer; typedef std::shared_ptr IStreamPtr; diff --git a/components/files/streamwithbuffer.hpp b/components/files/streamwithbuffer.hpp new file mode 100644 index 0000000000..dfd1a04376 --- /dev/null +++ b/components/files/streamwithbuffer.hpp @@ -0,0 +1,23 @@ +#ifndef OPENMW_COMPONENTS_FILES_STREAMWITHBUFFER_H +#define OPENMW_COMPONENTS_FILES_STREAMWITHBUFFER_H + +#include +#include + +namespace Files +{ + template + class StreamWithBuffer final : public std::istream + { + public: + explicit StreamWithBuffer(std::unique_ptr&& buffer) + : std::istream(buffer.get()) + , mBuffer(std::move(buffer)) + {} + + private: + std::unique_ptr mBuffer; + }; +} + +#endif From 94c1d0cced8fc3a0ca3dd280fa78870fec474e8a Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Apr 2022 02:15:39 +0200 Subject: [PATCH 2335/2859] Use unique_ptr to store istream --- apps/openmw/mwgui/videowidget.cpp | 2 +- apps/openmw_test_suite/lua/testing_util.hpp | 2 +- components/bsa/compressedbsafile.cpp | 4 +- components/esm/reader.cpp | 14 ++--- components/esm/reader.hpp | 2 +- components/esm4/reader.cpp | 54 +++++++++---------- components/esm4/reader.hpp | 10 ++-- components/files/constrainedfilestream.cpp | 2 +- components/files/constrainedfilestream.hpp | 2 +- components/fontloader/fontloader.cpp | 24 ++++----- components/nif/niffile.cpp | 8 +-- components/nif/niffile.hpp | 4 +- components/nif/nifstream.hpp | 2 +- extern/osg-ffmpeg-videoplayer/videoplayer.cpp | 4 +- extern/osg-ffmpeg-videoplayer/videoplayer.hpp | 2 +- extern/osg-ffmpeg-videoplayer/videostate.cpp | 2 +- extern/osg-ffmpeg-videoplayer/videostate.hpp | 4 +- 17 files changed, 70 insertions(+), 72 deletions(-) diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index 2aea0018d5..36dd7fd3e2 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -44,7 +44,7 @@ void VideoWidget::playVideo(const std::string &video) return; } - mPlayer->playVideo(videoStream, video); + mPlayer->playVideo(std::move(videoStream), video); osg::ref_ptr texture = mPlayer->getVideoTexture(); if (!texture) diff --git a/apps/openmw_test_suite/lua/testing_util.hpp b/apps/openmw_test_suite/lua/testing_util.hpp index ba4a418bfb..a40314bd0d 100644 --- a/apps/openmw_test_suite/lua/testing_util.hpp +++ b/apps/openmw_test_suite/lua/testing_util.hpp @@ -23,7 +23,7 @@ namespace Files::IStreamPtr open() override { - return std::make_shared(mContent, std::ios_base::in); + return std::make_unique(mContent, std::ios_base::in); } private: diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index a3f3c08864..e4117a0b89 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -370,7 +370,7 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) fileStream->read(reinterpret_cast(&uncompressedSize), sizeof(uint32_t)); size -= sizeof(uint32_t); } - std::shared_ptr memoryStreamPtr = std::make_shared(uncompressedSize); + auto memoryStreamPtr = std::make_unique(uncompressedSize); if (compressed) { @@ -403,7 +403,7 @@ Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) fileStream->read(memoryStreamPtr->getRawData(), size); } - return std::shared_ptr(memoryStreamPtr, (std::istream*)memoryStreamPtr.get()); + return std::make_unique>(std::move(memoryStreamPtr)); } BsaVersion CompressedBSAFile::detectVersion(const std::string& filePath) diff --git a/components/esm/reader.cpp b/components/esm/reader.cpp index d3732abb1e..519c547bb8 100644 --- a/components/esm/reader.cpp +++ b/components/esm/reader.cpp @@ -26,7 +26,7 @@ namespace ESM if (modVer == ESM4::REC_TES4) { - return new ESM4::Reader(esmStream, filename); + return new ESM4::Reader(std::move(esmStream), filename); } else { @@ -38,15 +38,15 @@ namespace ESM } bool Reader::getStringImpl(std::string& str, std::size_t size, - Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull) + std::istream& stream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull) { std::size_t newSize = size; if (encoder) { std::string input(size, '\0'); - filestream->read(input.data(), size); - if (filestream->gcount() == static_cast(size)) + stream.read(input.data(), size); + if (stream.gcount() == static_cast(size)) { encoder->getUtf8(input, ToUTF8::BufferAllocationPolicy::FitToRequiredSize, str); return true; @@ -58,13 +58,13 @@ namespace ESM newSize -= 1; // don't read the null terminator yet str.resize(newSize); // assumed C++11 - filestream->read(&str[0], newSize); - if ((std::size_t)filestream->gcount() == newSize) + stream.read(&str[0], newSize); + if (static_cast(stream.gcount()) == newSize) { if (hasNull) { char ch; - filestream->read(&ch, 1); // read the null terminator + stream.read(&ch, 1); // read the null terminator assert (ch == '\0' && "ESM4::Reader::getString string is not terminated with a null"); } diff --git a/components/esm/reader.hpp b/components/esm/reader.hpp index 5356d9bd01..ffc416dc18 100644 --- a/components/esm/reader.hpp +++ b/components/esm/reader.hpp @@ -53,7 +53,7 @@ namespace ESM protected: bool getStringImpl(std::string& str, std::size_t size, - Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false); + std::istream& stream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false); }; } diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index 1a8fff789a..ce7ede2bf4 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -64,8 +64,8 @@ ReaderContext::ReaderContext() : modIndex(0), recHeaderSize(sizeof(RecordHeader) currCellGrid.grid.y = 0; } -Reader::Reader(Files::IStreamPtr esmStream, const std::string& filename) - : mEncoder(nullptr), mFileSize(0), mStream(esmStream) +Reader::Reader(Files::IStreamPtr&& esmStream, const std::string& filename) + : mEncoder(nullptr), mFileSize(0), mStream(std::move(esmStream)) { // used by ESMReader only? mCtx.filename = filename; @@ -130,8 +130,7 @@ bool Reader::restoreContext(const ReaderContext& ctx) { if (mSavedStream) // TODO: doesn't seem to ever happen { - mStream = mSavedStream; - mSavedStream.reset(); + mStream = std::move(mSavedStream); } mCtx.groupStack.clear(); // probably not necessary since it will be overwritten @@ -148,11 +147,11 @@ void Reader::close() //mHeader.blank(); } -void Reader::openRaw(Files::IStreamPtr esmStream, const std::string& filename) +void Reader::openRaw(Files::IStreamPtr&& stream, const std::string& filename) { close(); - mStream = esmStream; + mStream = std::move(stream); mCtx.filename = filename; mCtx.fileRead = 0; mStream->seekg(0, mStream->end); @@ -161,9 +160,9 @@ void Reader::openRaw(Files::IStreamPtr esmStream, const std::string& filename) } -void Reader::open(Files::IStreamPtr esmStream, const std::string &filename) +void Reader::open(Files::IStreamPtr&& stream, const std::string &filename) { - openRaw(esmStream, filename); + openRaw(std::move(stream), filename); // should at least have the size of ESM3 record header (20 or 24 bytes for ESM4) assert (mFileSize >= 16); @@ -210,32 +209,33 @@ void Reader::buildLStringIndex(const std::string& stringFile, LocalizedStringTyp sp.type = stringType; // TODO: possibly check if the resource exists? - Files::IStreamPtr filestream = Files::IStreamPtr(Files::openConstrainedFileStream(stringFile)); + Files::IStreamPtr filestream = Files::openConstrainedFileStream(stringFile); filestream->seekg(0, std::ios::end); std::size_t fileSize = filestream->tellg(); filestream->seekg(0, std::ios::beg); + std::istream* stream = filestream.get(); switch (stringType) { - case Type_Strings: mStrings = filestream; break; - case Type_ILStrings: mILStrings = filestream; break; - case Type_DLStrings: mDLStrings = filestream; break; + case Type_Strings: mStrings = std::move(filestream); break; + case Type_ILStrings: mILStrings = std::move(filestream); break; + case Type_DLStrings: mDLStrings = std::move(filestream); break; default: throw std::runtime_error("ESM4::Reader::unknown localised string type"); } - filestream->read((char*)&numEntries, sizeof(numEntries)); - filestream->read((char*)&dataSize, sizeof(dataSize)); + stream->read((char*)&numEntries, sizeof(numEntries)); + stream->read((char*)&dataSize, sizeof(dataSize)); std::size_t dataStart = fileSize - dataSize; for (unsigned int i = 0; i < numEntries; ++i) { - filestream->read((char*)&stringId, sizeof(stringId)); - filestream->read((char*)&sp.offset, sizeof(sp.offset)); + stream->read((char*)&stringId, sizeof(stringId)); + stream->read((char*)&sp.offset, sizeof(sp.offset)); sp.offset += (std::uint32_t)dataStart; mLStringIndex[stringId] = sp; } - //assert (dataStart - filestream->tell() == 0 && "String file start of data section mismatch"); + //assert (dataStart - stream->tell() == 0 && "String file start of data section mismatch"); } void Reader::getLocalizedString(std::string& str) @@ -256,13 +256,13 @@ void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str) if (it != mLStringIndex.end()) { - Files::IStreamPtr filestream; + std::istream* filestream = nullptr; switch (it->second.type) { case Type_Strings: // no string size provided { - filestream = mStrings; + filestream = mStrings.get(); filestream->seekg(it->second.offset); char ch; @@ -275,8 +275,8 @@ void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str) str = std::string(data.data()); return; } - case Type_ILStrings: filestream = mILStrings; break; - case Type_DLStrings: filestream = mDLStrings; break; + case Type_ILStrings: filestream = mILStrings.get(); break; + case Type_DLStrings: filestream = mDLStrings.get(); break; default: throw std::runtime_error("ESM4::Reader::getLocalizedString unknown string type"); } @@ -285,7 +285,7 @@ void Reader::getLocalizedStringImpl(const FormId stringId, std::string& str) filestream->seekg(it->second.offset); std::uint32_t size = 0; filestream->read((char*)&size, sizeof(size)); - getStringImpl(str, size, filestream, mEncoder, true); // expect null terminated string + getStringImpl(str, size, *filestream, mEncoder, true); // expect null terminated string } else throw std::runtime_error("ESM4::Reader::getLocalizedString localized string not found"); @@ -296,8 +296,7 @@ bool Reader::getRecordHeader() // FIXME: this seems very hacky but we may have skipped subrecords from within an inflated data block if (/*mStream->eof() && */mSavedStream) { - mStream = mSavedStream; - mSavedStream.reset(); + mStream = std::move(mSavedStream); } mStream->read((char*)&mCtx.recordHeader, mCtx.recHeaderSize); @@ -336,12 +335,11 @@ void Reader::getRecordData(bool dump) Bsa::MemoryInputStream compressedRecord(recordSize); mStream->read(compressedRecord.getRawData(), recordSize); std::istream *fileStream = (std::istream*)&compressedRecord; - mSavedStream = mStream; + mSavedStream = std::move(mStream); mCtx.recordHeader.record.dataSize = uncompressedSize - sizeof(uncompressedSize); - std::shared_ptr memoryStreamPtr - = std::make_shared(uncompressedSize); + auto memoryStreamPtr = std::make_unique(uncompressedSize); boost::iostreams::filtering_streambuf inputStreamBuf; inputStreamBuf.push(boost::iostreams::zlib_decompressor()); @@ -370,7 +368,7 @@ if (dump) std::cout << ss.str() << std::endl; } //#endif - mStream = std::shared_ptr(memoryStreamPtr, (std::istream*)memoryStreamPtr.get()); + mStream = std::make_unique>(std::move(memoryStreamPtr)); } } diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index 60676b116b..68d5b9eb04 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -114,17 +114,17 @@ namespace ESM4 { //void close(); // Raw opening. Opens the file and sets everything up but doesn't parse the header. - void openRaw(Files::IStreamPtr esmStream, const std::string& filename); + void openRaw(Files::IStreamPtr&& stream, const std::string& filename); // Load ES file from a new stream, parses the header. // Closes the currently open file first, if any. - void open(Files::IStreamPtr esmStream, const std::string& filename); + void open(Files::IStreamPtr&& stream, const std::string& filename); Reader() = default; public: - Reader(Files::IStreamPtr esmStream, const std::string& filename); + Reader(Files::IStreamPtr&& esmStream, const std::string& filename); ~Reader(); // FIXME: should be private but ESMTool uses it @@ -280,10 +280,10 @@ namespace ESM4 { // Note: uses the string size from the subrecord header rather than checking null termination bool getZString(std::string& str) { - return getStringImpl(str, mCtx.subRecordHeader.dataSize, mStream, mEncoder, true); + return getStringImpl(str, mCtx.subRecordHeader.dataSize, *mStream, mEncoder, true); } bool getString(std::string& str) { - return getStringImpl(str, mCtx.subRecordHeader.dataSize, mStream, mEncoder); + return getStringImpl(str, mCtx.subRecordHeader.dataSize, *mStream, mEncoder); } void enterGroup(); diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index 22a85ce808..a56ae01aa0 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -4,6 +4,6 @@ namespace Files { IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start, std::size_t length) { - return std::make_shared(std::make_unique(filename, start, length)); + return std::make_unique(std::make_unique(filename, start, length)); } } diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp index 3acb04d896..bed4242ec4 100644 --- a/components/files/constrainedfilestream.hpp +++ b/components/files/constrainedfilestream.hpp @@ -15,7 +15,7 @@ namespace Files /// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters. using ConstrainedFileStream = StreamWithBuffer; -typedef std::shared_ptr IStreamPtr; +typedef std::unique_ptr IStreamPtr; IStreamPtr openConstrainedFileStream(const std::string& filename, std::size_t start = 0, std::size_t length = std::numeric_limits::max()); diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index d0125398f1..b67283d01f 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -137,12 +137,12 @@ namespace } } - [[noreturn]] void fail (Files::IStreamPtr file, const std::string& fileName, const std::string& message) + [[noreturn]] void fail(std::istream& stream, const std::string& fileName, const std::string& message) { std::stringstream error; error << "Font loading error: " << message; error << "\n File: " << fileName; - error << "\n Offset: 0x" << std::hex << file->tellg(); + error << "\n Offset: 0x" << std::hex << stream.tellg(); throw std::runtime_error(error.str()); } @@ -252,33 +252,33 @@ namespace Gui float fontSize; file->read((char*)&fontSize, sizeof(fontSize)); if (!file->good()) - fail(file, fileName, "File too small to be a valid font"); + fail(*file, fileName, "File too small to be a valid font"); int one; file->read((char*)&one, sizeof(one)); if (!file->good()) - fail(file, fileName, "File too small to be a valid font"); + fail(*file, fileName, "File too small to be a valid font"); if (one != 1) - fail(file, fileName, "Unexpected value"); + fail(*file, fileName, "Unexpected value"); file->read((char*)&one, sizeof(one)); if (!file->good()) - fail(file, fileName, "File too small to be a valid font"); + fail(*file, fileName, "File too small to be a valid font"); if (one != 1) - fail(file, fileName, "Unexpected value"); + fail(*file, fileName, "Unexpected value"); char name_[284]; file->read(name_, sizeof(name_)); if (!file->good()) - fail(file, fileName, "File too small to be a valid font"); + fail(*file, fileName, "File too small to be a valid font"); std::string name(name_); GlyphInfo data[256]; file->read((char*)data, sizeof(data)); if (!file->good()) - fail(file, fileName, "File too small to be a valid font"); + fail(*file, fileName, "File too small to be a valid font"); file.reset(); @@ -292,16 +292,16 @@ namespace Gui bitmapFile->read((char*)&height, sizeof(int)); if (!bitmapFile->good()) - fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); + fail(*bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); if (width <= 0 || height <= 0) - fail(bitmapFile, bitmapFilename, "Width and height must be positive"); + fail(*bitmapFile, bitmapFilename, "Width and height must be positive"); std::vector textureData; textureData.resize(width*height*4); bitmapFile->read(&textureData[0], width*height*4); if (!bitmapFile->good()) - fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); + fail(*bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); bitmapFile.reset(); std::string resourceName; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 57d1ce6457..4e0fd5a614 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -12,10 +12,10 @@ namespace Nif { /// Open a NIF stream. The name is used for error messages. -NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name) +NIFFile::NIFFile(Files::IStreamPtr&& stream, const std::string &name) : filename(name) { - parse(stream); + parse(std::move(stream)); } template @@ -170,12 +170,12 @@ std::string NIFFile::printVersion(unsigned int version) return stream.str(); } -void NIFFile::parse(Files::IStreamPtr stream) +void NIFFile::parse(Files::IStreamPtr&& stream) { const std::array fileHash = Files::getHash(filename, *stream); hash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); - NIFStream nif (this, stream); + NIFStream nif (this, std::move(stream)); // Check the header string std::string head = nif.getVersionString(); diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index def2b8870d..aa53f516fd 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -69,7 +69,7 @@ class NIFFile final : public File static std::atomic_bool sLoadUnsupportedFiles; /// Parse the file - void parse(Files::IStreamPtr stream); + void parse(Files::IStreamPtr&& stream); /// Get the file's version in a human readable form ///\returns A string containing a human readable NIF version number @@ -107,7 +107,7 @@ public: } /// Open a NIF stream. The name is used for error messages. - NIFFile(Files::IStreamPtr stream, const std::string &name); + NIFFile(Files::IStreamPtr&& stream, const std::string &name); /// Get a given record Record *getRecord(size_t index) const override diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 04243c2506..04583131b9 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -62,7 +62,7 @@ public: NIFFile * const file; - NIFStream (NIFFile * file, Files::IStreamPtr inp): inp (inp), file (file) {} + NIFStream (NIFFile * file, Files::IStreamPtr&& inp): inp (std::move(inp)), file (file) {} void skip(size_t size) { inp->ignore(size); } diff --git a/extern/osg-ffmpeg-videoplayer/videoplayer.cpp b/extern/osg-ffmpeg-videoplayer/videoplayer.cpp index 28ac3b36c2..e101bd4870 100644 --- a/extern/osg-ffmpeg-videoplayer/videoplayer.cpp +++ b/extern/osg-ffmpeg-videoplayer/videoplayer.cpp @@ -27,7 +27,7 @@ void VideoPlayer::setAudioFactory(MovieAudioFactory *factory) mAudioFactory.reset(factory); } -void VideoPlayer::playVideo(std::shared_ptr inputstream, const std::string& name) +void VideoPlayer::playVideo(std::unique_ptr&& inputstream, const std::string& name) { if(mState) close(); @@ -35,7 +35,7 @@ void VideoPlayer::playVideo(std::shared_ptr inputstream, const std try { mState = new VideoState; mState->setAudioFactory(mAudioFactory.get()); - mState->init(inputstream, name); + mState->init(std::move(inputstream), name); // wait until we have the first picture while (mState->video_st && !mState->mTexture.get()) diff --git a/extern/osg-ffmpeg-videoplayer/videoplayer.hpp b/extern/osg-ffmpeg-videoplayer/videoplayer.hpp index 97ef13032d..801f9ab560 100644 --- a/extern/osg-ffmpeg-videoplayer/videoplayer.hpp +++ b/extern/osg-ffmpeg-videoplayer/videoplayer.hpp @@ -42,7 +42,7 @@ namespace Video /// Play the given video. If a video is already playing, the old video is closed first. /// @note The video will be unpaused by default. Use the pause() and play() methods to control pausing. /// @param name A name for the video stream - only used for logging purposes. - void playVideo (std::shared_ptr inputstream, const std::string& name); + void playVideo(std::unique_ptr&& inputstream, const std::string& name); /// Get the current playback time position in the video, in seconds double getCurrentTime(); diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 9b07ece14d..096651dfd8 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -714,7 +714,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) return 0; } -void VideoState::init(std::shared_ptr inputstream, const std::string &name) +void VideoState::init(std::unique_ptr&& inputstream, const std::string &name) { int video_index = -1; int audio_index = -1; diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index 8fdbbe64bf..3681a64976 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -128,7 +128,7 @@ struct VideoState { void setAudioFactory(MovieAudioFactory* factory); - void init(std::shared_ptr inputstream, const std::string& name); + void init(std::unique_ptr&& inputstream, const std::string& name); void deinit(); void setPaused(bool isPaused); @@ -165,7 +165,7 @@ struct VideoState { ExternalClock mExternalClock; - std::shared_ptr stream; + std::unique_ptr stream; AVFormatContext* format_ctx; AVCodecContext* video_ctx; AVCodecContext* audio_ctx; From 3abcf3dd0d22c47ae9fa31eb4077b134754c65df Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 12 Apr 2022 23:25:41 +0200 Subject: [PATCH 2336/2859] Fix style guide --- components/esm4/reader.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index 60676b116b..75e002bbea 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -38,7 +38,8 @@ namespace ESM4 { // v typedef std::vector > GroupStack; - struct ReaderContext { + struct ReaderContext + { std::string filename; // in case we need to reopen to restore the context std::uint32_t modIndex; // the sequential position of this file in the load order: // 0x00 reserved, 0xFF in-game (see notes below) From 761a04ce001ac60f9808d7be7d3485069651c276 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 13 Apr 2022 18:37:22 +0200 Subject: [PATCH 2337/2859] Remove duplicated enum definition --- components/esm/common.hpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/components/esm/common.hpp b/components/esm/common.hpp index 3a8894e321..af19572c03 100644 --- a/components/esm/common.hpp +++ b/components/esm/common.hpp @@ -40,17 +40,6 @@ namespace ESM std::uint64_t size; }; - enum VarType - { - VT_Unknown = 0, - VT_None, - VT_Short, // stored as a float, kinda - VT_Int, - VT_Long, // stored as a float - VT_Float, - VT_String - }; - std::string printName(const std::uint32_t typeId); } From d5fb2f8091e955bacb2c9ba43510a24d40481d70 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 13 Apr 2022 18:41:15 +0200 Subject: [PATCH 2338/2859] Use ESM::fourCC to define ESM4 record types --- components/esm4/common.hpp | 1490 ++++++++++++++++++------------------ 1 file changed, 743 insertions(+), 747 deletions(-) diff --git a/components/esm4/common.hpp b/components/esm4/common.hpp index 2f769c28c9..b92a48e070 100644 --- a/components/esm4/common.hpp +++ b/components/esm4/common.hpp @@ -23,8 +23,6 @@ from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by trial & error. See http://en.uesp.net/wiki for details. - MKTAG macro was adapated from ScummVM. - */ #ifndef ESM4_COMMON_H #define ESM4_COMMON_H @@ -32,780 +30,778 @@ #include #include -#include "formid.hpp" +#include -// From ScummVM's endianness.h but for little endian -#ifndef MKTAG -#define MKTAG(a0,a1,a2,a3) ((std::uint32_t)((a0) | ((a1) << 8) | ((a2) << 16) | ((a3) << 24))) -#endif +#include "formid.hpp" namespace ESM4 { + using ESM::fourCC; // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format enum RecordTypes { - REC_AACT = MKTAG('A','A','C','T'), // Action - REC_ACHR = MKTAG('A','C','H','R'), // Actor Reference - REC_ACTI = MKTAG('A','C','T','I'), // Activator - REC_ADDN = MKTAG('A','D','D','N'), // Addon Node - REC_ALCH = MKTAG('A','L','C','H'), // Potion - REC_AMMO = MKTAG('A','M','M','O'), // Ammo - REC_ANIO = MKTAG('A','N','I','O'), // Animated Object - REC_APPA = MKTAG('A','P','P','A'), // Apparatus (probably unused) - REC_ARMA = MKTAG('A','R','M','A'), // Armature (Model) - REC_ARMO = MKTAG('A','R','M','O'), // Armor - REC_ARTO = MKTAG('A','R','T','O'), // Art Object - REC_ASPC = MKTAG('A','S','P','C'), // Acoustic Space - REC_ASTP = MKTAG('A','S','T','P'), // Association Type - REC_AVIF = MKTAG('A','V','I','F'), // Actor Values/Perk Tree Graphics - REC_BOOK = MKTAG('B','O','O','K'), // Book - REC_BPTD = MKTAG('B','P','T','D'), // Body Part Data - REC_CAMS = MKTAG('C','A','M','S'), // Camera Shot - REC_CELL = MKTAG('C','E','L','L'), // Cell - REC_CLAS = MKTAG('C','L','A','S'), // Class - REC_CLFM = MKTAG('C','L','F','M'), // Color - REC_CLMT = MKTAG('C','L','M','T'), // Climate - REC_CLOT = MKTAG('C','L','O','T'), // Clothing - REC_COBJ = MKTAG('C','O','B','J'), // Constructible Object (recipes) - REC_COLL = MKTAG('C','O','L','L'), // Collision Layer - REC_CONT = MKTAG('C','O','N','T'), // Container - REC_CPTH = MKTAG('C','P','T','H'), // Camera Path - REC_CREA = MKTAG('C','R','E','A'), // Creature - REC_CSTY = MKTAG('C','S','T','Y'), // Combat Style - REC_DEBR = MKTAG('D','E','B','R'), // Debris - REC_DIAL = MKTAG('D','I','A','L'), // Dialog Topic - REC_DLBR = MKTAG('D','L','B','R'), // Dialog Branch - REC_DLVW = MKTAG('D','L','V','W'), // Dialog View - REC_DOBJ = MKTAG('D','O','B','J'), // Default Object Manager - REC_DOOR = MKTAG('D','O','O','R'), // Door - REC_DUAL = MKTAG('D','U','A','L'), // Dual Cast Data (possibly unused) - //REC_ECZN = MKTAG('E','C','Z','N'), // Encounter Zone - REC_EFSH = MKTAG('E','F','S','H'), // Effect Shader - REC_ENCH = MKTAG('E','N','C','H'), // Enchantment - REC_EQUP = MKTAG('E','Q','U','P'), // Equip Slot (flag-type values) - REC_EXPL = MKTAG('E','X','P','L'), // Explosion - REC_EYES = MKTAG('E','Y','E','S'), // Eyes - REC_FACT = MKTAG('F','A','C','T'), // Faction - REC_FLOR = MKTAG('F','L','O','R'), // Flora - REC_FLST = MKTAG('F','L','S','T'), // Form List (non-levelled list) - REC_FSTP = MKTAG('F','S','T','P'), // Footstep - REC_FSTS = MKTAG('F','S','T','S'), // Footstep Set - REC_FURN = MKTAG('F','U','R','N'), // Furniture - REC_GLOB = MKTAG('G','L','O','B'), // Global Variable - REC_GMST = MKTAG('G','M','S','T'), // Game Setting - REC_GRAS = MKTAG('G','R','A','S'), // Grass - REC_GRUP = MKTAG('G','R','U','P'), // Form Group - REC_HAIR = MKTAG('H','A','I','R'), // Hair - //REC_HAZD = MKTAG('H','A','Z','D'), // Hazard - REC_HDPT = MKTAG('H','D','P','T'), // Head Part - REC_IDLE = MKTAG('I','D','L','E'), // Idle Animation - REC_IDLM = MKTAG('I','D','L','M'), // Idle Marker - REC_IMAD = MKTAG('I','M','A','D'), // Image Space Modifier - REC_IMGS = MKTAG('I','M','G','S'), // Image Space - REC_INFO = MKTAG('I','N','F','O'), // Dialog Topic Info - REC_INGR = MKTAG('I','N','G','R'), // Ingredient - REC_IPCT = MKTAG('I','P','C','T'), // Impact Data - REC_IPDS = MKTAG('I','P','D','S'), // Impact Data Set - REC_KEYM = MKTAG('K','E','Y','M'), // Key - REC_KYWD = MKTAG('K','Y','W','D'), // Keyword - REC_LAND = MKTAG('L','A','N','D'), // Land - REC_LCRT = MKTAG('L','C','R','T'), // Location Reference Type - REC_LCTN = MKTAG('L','C','T','N'), // Location - REC_LGTM = MKTAG('L','G','T','M'), // Lighting Template - REC_LIGH = MKTAG('L','I','G','H'), // Light - REC_LSCR = MKTAG('L','S','C','R'), // Load Screen - REC_LTEX = MKTAG('L','T','E','X'), // Land Texture - REC_LVLC = MKTAG('L','V','L','C'), // Leveled Creature - REC_LVLI = MKTAG('L','V','L','I'), // Leveled Item - REC_LVLN = MKTAG('L','V','L','N'), // Leveled Actor - REC_LVSP = MKTAG('L','V','S','P'), // Leveled Spell - REC_MATO = MKTAG('M','A','T','O'), // Material Object - REC_MATT = MKTAG('M','A','T','T'), // Material Type - REC_MESG = MKTAG('M','E','S','G'), // Message - REC_MGEF = MKTAG('M','G','E','F'), // Magic Effect - REC_MISC = MKTAG('M','I','S','C'), // Misc. Object - REC_MOVT = MKTAG('M','O','V','T'), // Movement Type - REC_MSTT = MKTAG('M','S','T','T'), // Movable Static - REC_MUSC = MKTAG('M','U','S','C'), // Music Type - REC_MUST = MKTAG('M','U','S','T'), // Music Track - REC_NAVI = MKTAG('N','A','V','I'), // Navigation (master data) - REC_NAVM = MKTAG('N','A','V','M'), // Nav Mesh - REC_NOTE = MKTAG('N','O','T','E'), // Note - REC_NPC_ = MKTAG('N','P','C','_'), // Actor (NPC, Creature) - REC_OTFT = MKTAG('O','T','F','T'), // Outfit - REC_PACK = MKTAG('P','A','C','K'), // AI Package - REC_PERK = MKTAG('P','E','R','K'), // Perk - REC_PGRE = MKTAG('P','G','R','E'), // Placed grenade - REC_PHZD = MKTAG('P','H','Z','D'), // Placed hazard - REC_PROJ = MKTAG('P','R','O','J'), // Projectile - REC_QUST = MKTAG('Q','U','S','T'), // Quest - REC_RACE = MKTAG('R','A','C','E'), // Race / Creature type - REC_REFR = MKTAG('R','E','F','R'), // Object Reference - REC_REGN = MKTAG('R','E','G','N'), // Region (Audio/Weather) - REC_RELA = MKTAG('R','E','L','A'), // Relationship - REC_REVB = MKTAG('R','E','V','B'), // Reverb Parameters - REC_RFCT = MKTAG('R','F','C','T'), // Visual Effect - REC_SBSP = MKTAG('S','B','S','P'), // Subspace (TES4 only?) - REC_SCEN = MKTAG('S','C','E','N'), // Scene - REC_SCPT = MKTAG('S','C','P','T'), // Script - REC_SCRL = MKTAG('S','C','R','L'), // Scroll - REC_SGST = MKTAG('S','G','S','T'), // Sigil Stone - REC_SHOU = MKTAG('S','H','O','U'), // Shout - REC_SLGM = MKTAG('S','L','G','M'), // Soul Gem - REC_SMBN = MKTAG('S','M','B','N'), // Story Manager Branch Node - REC_SMEN = MKTAG('S','M','E','N'), // Story Manager Event Node - REC_SMQN = MKTAG('S','M','Q','N'), // Story Manager Quest Node - REC_SNCT = MKTAG('S','N','C','T'), // Sound Category - REC_SNDR = MKTAG('S','N','D','R'), // Sound Reference - REC_SOPM = MKTAG('S','O','P','M'), // Sound Output Model - REC_SOUN = MKTAG('S','O','U','N'), // Sound - REC_SPEL = MKTAG('S','P','E','L'), // Spell - REC_SPGD = MKTAG('S','P','G','D'), // Shader Particle Geometry - REC_STAT = MKTAG('S','T','A','T'), // Static - REC_TACT = MKTAG('T','A','C','T'), // Talking Activator - REC_TERM = MKTAG('T','E','R','M'), // Terminal - REC_TES4 = MKTAG('T','E','S','4'), // Plugin info - REC_TREE = MKTAG('T','R','E','E'), // Tree - REC_TXST = MKTAG('T','X','S','T'), // Texture Set - REC_VTYP = MKTAG('V','T','Y','P'), // Voice Type - REC_WATR = MKTAG('W','A','T','R'), // Water Type - REC_WEAP = MKTAG('W','E','A','P'), // Weapon - REC_WOOP = MKTAG('W','O','O','P'), // Word Of Power - REC_WRLD = MKTAG('W','R','L','D'), // World Space - REC_WTHR = MKTAG('W','T','H','R'), // Weather - REC_ACRE = MKTAG('A','C','R','E'), // Placed Creature (TES4 only?) - REC_PGRD = MKTAG('P','G','R','D'), // Pathgrid (TES4 only?) - REC_ROAD = MKTAG('R','O','A','D'), // Road (TES4 only?) - REC_IMOD = MKTAG('I','M','O','D'), // Item Mod - REC_PWAT = MKTAG('P','W','A','T'), // Placeable Water - REC_SCOL = MKTAG('S','C','O','L'), // Static Collection - REC_CCRD = MKTAG('C','C','R','D'), // Caravan Card - REC_CMNY = MKTAG('C','M','N','Y'), // Caravan Money - REC_ALOC = MKTAG('A','L','O','C'), // Audio Location Controller - REC_MSET = MKTAG('M','S','E','T') // Media Set + REC_AACT = fourCC("AACT"), // Action + REC_ACHR = fourCC("ACHR"), // Actor Reference + REC_ACTI = fourCC("ACTI"), // Activator + REC_ADDN = fourCC("ADDN"), // Addon Node + REC_ALCH = fourCC("ALCH"), // Potion + REC_AMMO = fourCC("AMMO"), // Ammo + REC_ANIO = fourCC("ANIO"), // Animated Object + REC_APPA = fourCC("APPA"), // Apparatus (probably unused) + REC_ARMA = fourCC("ARMA"), // Armature (Model) + REC_ARMO = fourCC("ARMO"), // Armor + REC_ARTO = fourCC("ARTO"), // Art Object + REC_ASPC = fourCC("ASPC"), // Acoustic Space + REC_ASTP = fourCC("ASTP"), // Association Type + REC_AVIF = fourCC("AVIF"), // Actor Values/Perk Tree Graphics + REC_BOOK = fourCC("BOOK"), // Book + REC_BPTD = fourCC("BPTD"), // Body Part Data + REC_CAMS = fourCC("CAMS"), // Camera Shot + REC_CELL = fourCC("CELL"), // Cell + REC_CLAS = fourCC("CLAS"), // Class + REC_CLFM = fourCC("CLFM"), // Color + REC_CLMT = fourCC("CLMT"), // Climate + REC_CLOT = fourCC("CLOT"), // Clothing + REC_COBJ = fourCC("COBJ"), // Constructible Object (recipes) + REC_COLL = fourCC("COLL"), // Collision Layer + REC_CONT = fourCC("CONT"), // Container + REC_CPTH = fourCC("CPTH"), // Camera Path + REC_CREA = fourCC("CREA"), // Creature + REC_CSTY = fourCC("CSTY"), // Combat Style + REC_DEBR = fourCC("DEBR"), // Debris + REC_DIAL = fourCC("DIAL"), // Dialog Topic + REC_DLBR = fourCC("DLBR"), // Dialog Branch + REC_DLVW = fourCC("DLVW"), // Dialog View + REC_DOBJ = fourCC("DOBJ"), // Default Object Manager + REC_DOOR = fourCC("DOOR"), // Door + REC_DUAL = fourCC("DUAL"), // Dual Cast Data (possibly unused) + //REC_ECZN = fourCC("ECZN"), // Encounter Zone + REC_EFSH = fourCC("EFSH"), // Effect Shader + REC_ENCH = fourCC("ENCH"), // Enchantment + REC_EQUP = fourCC("EQUP"), // Equip Slot (flag-type values) + REC_EXPL = fourCC("EXPL"), // Explosion + REC_EYES = fourCC("EYES"), // Eyes + REC_FACT = fourCC("FACT"), // Faction + REC_FLOR = fourCC("FLOR"), // Flora + REC_FLST = fourCC("FLST"), // Form List (non-levelled list) + REC_FSTP = fourCC("FSTP"), // Footstep + REC_FSTS = fourCC("FSTS"), // Footstep Set + REC_FURN = fourCC("FURN"), // Furniture + REC_GLOB = fourCC("GLOB"), // Global Variable + REC_GMST = fourCC("GMST"), // Game Setting + REC_GRAS = fourCC("GRAS"), // Grass + REC_GRUP = fourCC("GRUP"), // Form Group + REC_HAIR = fourCC("HAIR"), // Hair + //REC_HAZD = fourCC("HAZD"), // Hazard + REC_HDPT = fourCC("HDPT"), // Head Part + REC_IDLE = fourCC("IDLE"), // Idle Animation + REC_IDLM = fourCC("IDLM"), // Idle Marker + REC_IMAD = fourCC("IMAD"), // Image Space Modifier + REC_IMGS = fourCC("IMGS"), // Image Space + REC_INFO = fourCC("INFO"), // Dialog Topic Info + REC_INGR = fourCC("INGR"), // Ingredient + REC_IPCT = fourCC("IPCT"), // Impact Data + REC_IPDS = fourCC("IPDS"), // Impact Data Set + REC_KEYM = fourCC("KEYM"), // Key + REC_KYWD = fourCC("KYWD"), // Keyword + REC_LAND = fourCC("LAND"), // Land + REC_LCRT = fourCC("LCRT"), // Location Reference Type + REC_LCTN = fourCC("LCTN"), // Location + REC_LGTM = fourCC("LGTM"), // Lighting Template + REC_LIGH = fourCC("LIGH"), // Light + REC_LSCR = fourCC("LSCR"), // Load Screen + REC_LTEX = fourCC("LTEX"), // Land Texture + REC_LVLC = fourCC("LVLC"), // Leveled Creature + REC_LVLI = fourCC("LVLI"), // Leveled Item + REC_LVLN = fourCC("LVLN"), // Leveled Actor + REC_LVSP = fourCC("LVSP"), // Leveled Spell + REC_MATO = fourCC("MATO"), // Material Object + REC_MATT = fourCC("MATT"), // Material Type + REC_MESG = fourCC("MESG"), // Message + REC_MGEF = fourCC("MGEF"), // Magic Effect + REC_MISC = fourCC("MISC"), // Misc. Object + REC_MOVT = fourCC("MOVT"), // Movement Type + REC_MSTT = fourCC("MSTT"), // Movable Static + REC_MUSC = fourCC("MUSC"), // Music Type + REC_MUST = fourCC("MUST"), // Music Track + REC_NAVI = fourCC("NAVI"), // Navigation (master data) + REC_NAVM = fourCC("NAVM"), // Nav Mesh + REC_NOTE = fourCC("NOTE"), // Note + REC_NPC_ = fourCC("NPC_"), // Actor (NPC, Creature) + REC_OTFT = fourCC("OTFT"), // Outfit + REC_PACK = fourCC("PACK"), // AI Package + REC_PERK = fourCC("PERK"), // Perk + REC_PGRE = fourCC("PGRE"), // Placed grenade + REC_PHZD = fourCC("PHZD"), // Placed hazard + REC_PROJ = fourCC("PROJ"), // Projectile + REC_QUST = fourCC("QUST"), // Quest + REC_RACE = fourCC("RACE"), // Race / Creature type + REC_REFR = fourCC("REFR"), // Object Reference + REC_REGN = fourCC("REGN"), // Region (Audio/Weather) + REC_RELA = fourCC("RELA"), // Relationship + REC_REVB = fourCC("REVB"), // Reverb Parameters + REC_RFCT = fourCC("RFCT"), // Visual Effect + REC_SBSP = fourCC("SBSP"), // Subspace (TES4 only?) + REC_SCEN = fourCC("SCEN"), // Scene + REC_SCPT = fourCC("SCPT"), // Script + REC_SCRL = fourCC("SCRL"), // Scroll + REC_SGST = fourCC("SGST"), // Sigil Stone + REC_SHOU = fourCC("SHOU"), // Shout + REC_SLGM = fourCC("SLGM"), // Soul Gem + REC_SMBN = fourCC("SMBN"), // Story Manager Branch Node + REC_SMEN = fourCC("SMEN"), // Story Manager Event Node + REC_SMQN = fourCC("SMQN"), // Story Manager Quest Node + REC_SNCT = fourCC("SNCT"), // Sound Category + REC_SNDR = fourCC("SNDR"), // Sound Reference + REC_SOPM = fourCC("SOPM"), // Sound Output Model + REC_SOUN = fourCC("SOUN"), // Sound + REC_SPEL = fourCC("SPEL"), // Spell + REC_SPGD = fourCC("SPGD"), // Shader Particle Geometry + REC_STAT = fourCC("STAT"), // Static + REC_TACT = fourCC("TACT"), // Talking Activator + REC_TERM = fourCC("TERM"), // Terminal + REC_TES4 = fourCC("TES4"), // Plugin info + REC_TREE = fourCC("TREE"), // Tree + REC_TXST = fourCC("TXST"), // Texture Set + REC_VTYP = fourCC("VTYP"), // Voice Type + REC_WATR = fourCC("WATR"), // Water Type + REC_WEAP = fourCC("WEAP"), // Weapon + REC_WOOP = fourCC("WOOP"), // Word Of Power + REC_WRLD = fourCC("WRLD"), // World Space + REC_WTHR = fourCC("WTHR"), // Weather + REC_ACRE = fourCC("ACRE"), // Placed Creature (TES4 only?) + REC_PGRD = fourCC("PGRD"), // Pathgrid (TES4 only?) + REC_ROAD = fourCC("ROAD"), // Road (TES4 only?) + REC_IMOD = fourCC("IMOD"), // Item Mod + REC_PWAT = fourCC("PWAT"), // Placeable Water + REC_SCOL = fourCC("SCOL"), // Static Collection + REC_CCRD = fourCC("CCRD"), // Caravan Card + REC_CMNY = fourCC("CMNY"), // Caravan Money + REC_ALOC = fourCC("ALOC"), // Audio Location Controller + REC_MSET = fourCC("MSET") // Media Set }; enum SubRecordTypes { - SUB_HEDR = MKTAG('H','E','D','R'), - SUB_CNAM = MKTAG('C','N','A','M'), - SUB_SNAM = MKTAG('S','N','A','M'), // TES4 only? - SUB_MAST = MKTAG('M','A','S','T'), - SUB_DATA = MKTAG('D','A','T','A'), - SUB_ONAM = MKTAG('O','N','A','M'), - SUB_INTV = MKTAG('I','N','T','V'), - SUB_INCC = MKTAG('I','N','C','C'), - SUB_OFST = MKTAG('O','F','S','T'), // TES4 only? - SUB_DELE = MKTAG('D','E','L','E'), // TES4 only? - - SUB_DNAM = MKTAG('D','N','A','M'), - SUB_EDID = MKTAG('E','D','I','D'), - SUB_FULL = MKTAG('F','U','L','L'), - SUB_LTMP = MKTAG('L','T','M','P'), - SUB_MHDT = MKTAG('M','H','D','T'), - SUB_MNAM = MKTAG('M','N','A','M'), - SUB_MODL = MKTAG('M','O','D','L'), - SUB_NAM0 = MKTAG('N','A','M','0'), - SUB_NAM2 = MKTAG('N','A','M','2'), - SUB_NAM3 = MKTAG('N','A','M','3'), - SUB_NAM4 = MKTAG('N','A','M','4'), - SUB_NAM9 = MKTAG('N','A','M','9'), - SUB_NAMA = MKTAG('N','A','M','A'), - SUB_PNAM = MKTAG('P','N','A','M'), - SUB_RNAM = MKTAG('R','N','A','M'), - SUB_TNAM = MKTAG('T','N','A','M'), - SUB_UNAM = MKTAG('U','N','A','M'), - SUB_WCTR = MKTAG('W','C','T','R'), - SUB_WNAM = MKTAG('W','N','A','M'), - SUB_XEZN = MKTAG('X','E','Z','N'), - SUB_XLCN = MKTAG('X','L','C','N'), - SUB_XXXX = MKTAG('X','X','X','X'), - SUB_ZNAM = MKTAG('Z','N','A','M'), - SUB_MODT = MKTAG('M','O','D','T'), - SUB_ICON = MKTAG('I','C','O','N'), // TES4 only? - - SUB_NVER = MKTAG('N','V','E','R'), - SUB_NVMI = MKTAG('N','V','M','I'), - SUB_NVPP = MKTAG('N','V','P','P'), - SUB_NVSI = MKTAG('N','V','S','I'), - - SUB_NVNM = MKTAG('N','V','N','M'), - SUB_NNAM = MKTAG('N','N','A','M'), - - SUB_XCLC = MKTAG('X','C','L','C'), - SUB_XCLL = MKTAG('X','C','L','L'), - SUB_TVDT = MKTAG('T','V','D','T'), - SUB_XCGD = MKTAG('X','C','G','D'), - SUB_LNAM = MKTAG('L','N','A','M'), - SUB_XCLW = MKTAG('X','C','L','W'), - SUB_XNAM = MKTAG('X','N','A','M'), - SUB_XCLR = MKTAG('X','C','L','R'), - SUB_XWCS = MKTAG('X','W','C','S'), - SUB_XWCN = MKTAG('X','W','C','N'), - SUB_XWCU = MKTAG('X','W','C','U'), - SUB_XCWT = MKTAG('X','C','W','T'), - SUB_XOWN = MKTAG('X','O','W','N'), - SUB_XILL = MKTAG('X','I','L','L'), - SUB_XWEM = MKTAG('X','W','E','M'), - SUB_XCCM = MKTAG('X','C','C','M'), - SUB_XCAS = MKTAG('X','C','A','S'), - SUB_XCMO = MKTAG('X','C','M','O'), - SUB_XCIM = MKTAG('X','C','I','M'), - SUB_XCMT = MKTAG('X','C','M','T'), // TES4 only? - SUB_XRNK = MKTAG('X','R','N','K'), // TES4 only? - SUB_XGLB = MKTAG('X','G','L','B'), // TES4 only? - - SUB_VNML = MKTAG('V','N','M','L'), - SUB_VHGT = MKTAG('V','H','G','T'), - SUB_VCLR = MKTAG('V','C','L','R'), - SUA_BTXT = MKTAG('B','T','X','T'), - SUB_ATXT = MKTAG('A','T','X','T'), - SUB_VTXT = MKTAG('V','T','X','T'), - SUB_VTEX = MKTAG('V','T','E','X'), - - SUB_HNAM = MKTAG('H','N','A','M'), - SUB_GNAM = MKTAG('G','N','A','M'), - - SUB_RCLR = MKTAG('R','C','L','R'), - SUB_RPLI = MKTAG('R','P','L','I'), - SUB_RPLD = MKTAG('R','P','L','D'), - SUB_RDAT = MKTAG('R','D','A','T'), - SUB_RDMD = MKTAG('R','D','M','D'), // TES4 only? - SUB_RDSD = MKTAG('R','D','S','D'), // TES4 only? - SUB_RDGS = MKTAG('R','D','G','S'), // TES4 only? - SUB_RDMO = MKTAG('R','D','M','O'), - SUB_RDSA = MKTAG('R','D','S','A'), - SUB_RDWT = MKTAG('R','D','W','T'), - SUB_RDOT = MKTAG('R','D','O','T'), - SUB_RDMP = MKTAG('R','D','M','P'), - - SUB_MODB = MKTAG('M','O','D','B'), - SUB_OBND = MKTAG('O','B','N','D'), - SUB_MODS = MKTAG('M','O','D','S'), - - SUB_NAME = MKTAG('N','A','M','E'), - SUB_XMRK = MKTAG('X','M','R','K'), - SUB_FNAM = MKTAG('F','N','A','M'), - SUB_XSCL = MKTAG('X','S','C','L'), - SUB_XTEL = MKTAG('X','T','E','L'), - SUB_XTRG = MKTAG('X','T','R','G'), - SUB_XSED = MKTAG('X','S','E','D'), - SUB_XLOD = MKTAG('X','L','O','D'), - SUB_XPCI = MKTAG('X','P','C','I'), - SUB_XLOC = MKTAG('X','L','O','C'), - SUB_XESP = MKTAG('X','E','S','P'), - SUB_XLCM = MKTAG('X','L','C','M'), - SUB_XRTM = MKTAG('X','R','T','M'), - SUB_XACT = MKTAG('X','A','C','T'), - SUB_XCNT = MKTAG('X','C','N','T'), - SUB_VMAD = MKTAG('V','M','A','D'), - SUB_XPRM = MKTAG('X','P','R','M'), - SUB_XMBO = MKTAG('X','M','B','O'), - SUB_XPOD = MKTAG('X','P','O','D'), - SUB_XRMR = MKTAG('X','R','M','R'), - SUB_INAM = MKTAG('I','N','A','M'), - SUB_SCHR = MKTAG('S','C','H','R'), - SUB_XLRM = MKTAG('X','L','R','M'), - SUB_XRGD = MKTAG('X','R','G','D'), - SUB_XRDS = MKTAG('X','R','D','S'), - SUB_XEMI = MKTAG('X','E','M','I'), - SUB_XLIG = MKTAG('X','L','I','G'), - SUB_XALP = MKTAG('X','A','L','P'), - SUB_XNDP = MKTAG('X','N','D','P'), - SUB_XAPD = MKTAG('X','A','P','D'), - SUB_XAPR = MKTAG('X','A','P','R'), - SUB_XLIB = MKTAG('X','L','I','B'), - SUB_XLKR = MKTAG('X','L','K','R'), - SUB_XLRT = MKTAG('X','L','R','T'), - SUB_XCVL = MKTAG('X','C','V','L'), - SUB_XCVR = MKTAG('X','C','V','R'), - SUB_XCZA = MKTAG('X','C','Z','A'), - SUB_XCZC = MKTAG('X','C','Z','C'), - SUB_XFVC = MKTAG('X','F','V','C'), - SUB_XHTW = MKTAG('X','H','T','W'), - SUB_XIS2 = MKTAG('X','I','S','2'), - SUB_XMBR = MKTAG('X','M','B','R'), - SUB_XCCP = MKTAG('X','C','C','P'), - SUB_XPWR = MKTAG('X','P','W','R'), - SUB_XTRI = MKTAG('X','T','R','I'), - SUB_XATR = MKTAG('X','A','T','R'), - SUB_XPRD = MKTAG('X','P','R','D'), - SUB_XPPA = MKTAG('X','P','P','A'), - SUB_PDTO = MKTAG('P','D','T','O'), - SUB_XLRL = MKTAG('X','L','R','L'), - - SUB_QNAM = MKTAG('Q','N','A','M'), - SUB_COCT = MKTAG('C','O','C','T'), - SUB_COED = MKTAG('C','O','E','D'), - SUB_CNTO = MKTAG('C','N','T','O'), - SUB_SCRI = MKTAG('S','C','R','I'), - - SUB_BNAM = MKTAG('B','N','A','M'), - - SUB_BMDT = MKTAG('B','M','D','T'), - SUB_MOD2 = MKTAG('M','O','D','2'), - SUB_MOD3 = MKTAG('M','O','D','3'), - SUB_MOD4 = MKTAG('M','O','D','4'), - SUB_MO2B = MKTAG('M','O','2','B'), - SUB_MO3B = MKTAG('M','O','3','B'), - SUB_MO4B = MKTAG('M','O','4','B'), - SUB_MO2T = MKTAG('M','O','2','T'), - SUB_MO3T = MKTAG('M','O','3','T'), - SUB_MO4T = MKTAG('M','O','4','T'), - SUB_ANAM = MKTAG('A','N','A','M'), - SUB_ENAM = MKTAG('E','N','A','M'), - SUB_ICO2 = MKTAG('I','C','O','2'), - - SUB_ACBS = MKTAG('A','C','B','S'), - SUB_SPLO = MKTAG('S','P','L','O'), - SUB_AIDT = MKTAG('A','I','D','T'), - SUB_PKID = MKTAG('P','K','I','D'), - SUB_HCLR = MKTAG('H','C','L','R'), - SUB_FGGS = MKTAG('F','G','G','S'), - SUB_FGGA = MKTAG('F','G','G','A'), - SUB_FGTS = MKTAG('F','G','T','S'), - SUB_KFFZ = MKTAG('K','F','F','Z'), - - SUB_PFIG = MKTAG('P','F','I','G'), - SUB_PFPC = MKTAG('P','F','P','C'), - - SUB_XHRS = MKTAG('X','H','R','S'), - SUB_XMRC = MKTAG('X','M','R','C'), - - SUB_SNDD = MKTAG('S','N','D','D'), - SUB_SNDX = MKTAG('S','N','D','X'), - - SUB_DESC = MKTAG('D','E','S','C'), - - SUB_ENIT = MKTAG('E','N','I','T'), - SUB_EFID = MKTAG('E','F','I','D'), - SUB_EFIT = MKTAG('E','F','I','T'), - SUB_SCIT = MKTAG('S','C','I','T'), - - SUB_SOUL = MKTAG('S','O','U','L'), - SUB_SLCP = MKTAG('S','L','C','P'), - - SUB_CSCR = MKTAG('C','S','C','R'), - SUB_CSDI = MKTAG('C','S','D','I'), - SUB_CSDC = MKTAG('C','S','D','C'), - SUB_NIFZ = MKTAG('N','I','F','Z'), - SUB_CSDT = MKTAG('C','S','D','T'), - SUB_NAM1 = MKTAG('N','A','M','1'), - SUB_NIFT = MKTAG('N','I','F','T'), - - SUB_LVLD = MKTAG('L','V','L','D'), - SUB_LVLF = MKTAG('L','V','L','F'), - SUB_LVLO = MKTAG('L','V','L','O'), - - SUB_BODT = MKTAG('B','O','D','T'), - SUB_YNAM = MKTAG('Y','N','A','M'), - SUB_DEST = MKTAG('D','E','S','T'), - SUB_DMDL = MKTAG('D','M','D','L'), - SUB_DMDS = MKTAG('D','M','D','S'), - SUB_DMDT = MKTAG('D','M','D','T'), - SUB_DSTD = MKTAG('D','S','T','D'), - SUB_DSTF = MKTAG('D','S','T','F'), - SUB_KNAM = MKTAG('K','N','A','M'), - SUB_KSIZ = MKTAG('K','S','I','Z'), - SUB_KWDA = MKTAG('K','W','D','A'), - SUB_VNAM = MKTAG('V','N','A','M'), - SUB_SDSC = MKTAG('S','D','S','C'), - SUB_MO2S = MKTAG('M','O','2','S'), - SUB_MO4S = MKTAG('M','O','4','S'), - SUB_BOD2 = MKTAG('B','O','D','2'), - SUB_BAMT = MKTAG('B','A','M','T'), - SUB_BIDS = MKTAG('B','I','D','S'), - SUB_ETYP = MKTAG('E','T','Y','P'), - SUB_BMCT = MKTAG('B','M','C','T'), - SUB_MICO = MKTAG('M','I','C','O'), - SUB_MIC2 = MKTAG('M','I','C','2'), - SUB_EAMT = MKTAG('E','A','M','T'), - SUB_EITM = MKTAG('E','I','T','M'), - - SUB_SCTX = MKTAG('S','C','T','X'), - SUB_XLTW = MKTAG('X','L','T','W'), - SUB_XMBP = MKTAG('X','M','B','P'), - SUB_XOCP = MKTAG('X','O','C','P'), - SUB_XRGB = MKTAG('X','R','G','B'), - SUB_XSPC = MKTAG('X','S','P','C'), - SUB_XTNM = MKTAG('X','T','N','M'), - SUB_ATKR = MKTAG('A','T','K','R'), - SUB_CRIF = MKTAG('C','R','I','F'), - SUB_DOFT = MKTAG('D','O','F','T'), - SUB_DPLT = MKTAG('D','P','L','T'), - SUB_ECOR = MKTAG('E','C','O','R'), - SUB_ATKD = MKTAG('A','T','K','D'), - SUB_ATKE = MKTAG('A','T','K','E'), - SUB_FTST = MKTAG('F','T','S','T'), - SUB_HCLF = MKTAG('H','C','L','F'), - SUB_NAM5 = MKTAG('N','A','M','5'), - SUB_NAM6 = MKTAG('N','A','M','6'), - SUB_NAM7 = MKTAG('N','A','M','7'), - SUB_NAM8 = MKTAG('N','A','M','8'), - SUB_PRKR = MKTAG('P','R','K','R'), - SUB_PRKZ = MKTAG('P','R','K','Z'), - SUB_SOFT = MKTAG('S','O','F','T'), - SUB_SPCT = MKTAG('S','P','C','T'), - SUB_TINC = MKTAG('T','I','N','C'), - SUB_TIAS = MKTAG('T','I','A','S'), - SUB_TINI = MKTAG('T','I','N','I'), - SUB_TINV = MKTAG('T','I','N','V'), - SUB_TPLT = MKTAG('T','P','L','T'), - SUB_VTCK = MKTAG('V','T','C','K'), - SUB_SHRT = MKTAG('S','H','R','T'), - SUB_SPOR = MKTAG('S','P','O','R'), - SUB_XHOR = MKTAG('X','H','O','R'), - SUB_CTDA = MKTAG('C','T','D','A'), - SUB_CRDT = MKTAG('C','R','D','T'), - SUB_FNMK = MKTAG('F','N','M','K'), - SUB_FNPR = MKTAG('F','N','P','R'), - SUB_WBDT = MKTAG('W','B','D','T'), - SUB_QUAL = MKTAG('Q','U','A','L'), - SUB_INDX = MKTAG('I','N','D','X'), - SUB_ATTR = MKTAG('A','T','T','R'), - SUB_MTNM = MKTAG('M','T','N','M'), - SUB_UNES = MKTAG('U','N','E','S'), - SUB_TIND = MKTAG('T','I','N','D'), - SUB_TINL = MKTAG('T','I','N','L'), - SUB_TINP = MKTAG('T','I','N','P'), - SUB_TINT = MKTAG('T','I','N','T'), - SUB_TIRS = MKTAG('T','I','R','S'), - SUB_PHWT = MKTAG('P','H','W','T'), - SUB_AHCF = MKTAG('A','H','C','F'), - SUB_AHCM = MKTAG('A','H','C','M'), - SUB_HEAD = MKTAG('H','E','A','D'), - SUB_MPAI = MKTAG('M','P','A','I'), - SUB_MPAV = MKTAG('M','P','A','V'), - SUB_DFTF = MKTAG('D','F','T','F'), - SUB_DFTM = MKTAG('D','F','T','M'), - SUB_FLMV = MKTAG('F','L','M','V'), - SUB_FTSF = MKTAG('F','T','S','F'), - SUB_FTSM = MKTAG('F','T','S','M'), - SUB_MTYP = MKTAG('M','T','Y','P'), - SUB_PHTN = MKTAG('P','H','T','N'), - SUB_RNMV = MKTAG('R','N','M','V'), - SUB_RPRF = MKTAG('R','P','R','F'), - SUB_RPRM = MKTAG('R','P','R','M'), - SUB_SNMV = MKTAG('S','N','M','V'), - SUB_SPED = MKTAG('S','P','E','D'), - SUB_SWMV = MKTAG('S','W','M','V'), - SUB_WKMV = MKTAG('W','K','M','V'), - SUB_LLCT = MKTAG('L','L','C','T'), - SUB_IDLF = MKTAG('I','D','L','F'), - SUB_IDLA = MKTAG('I','D','L','A'), - SUB_IDLC = MKTAG('I','D','L','C'), - SUB_IDLT = MKTAG('I','D','L','T'), - SUB_DODT = MKTAG('D','O','D','T'), - SUB_TX00 = MKTAG('T','X','0','0'), - SUB_TX01 = MKTAG('T','X','0','1'), - SUB_TX02 = MKTAG('T','X','0','2'), - SUB_TX03 = MKTAG('T','X','0','3'), - SUB_TX04 = MKTAG('T','X','0','4'), - SUB_TX05 = MKTAG('T','X','0','5'), - SUB_TX06 = MKTAG('T','X','0','6'), - SUB_TX07 = MKTAG('T','X','0','7'), - SUB_BPND = MKTAG('B','P','N','D'), - SUB_BPTN = MKTAG('B','P','T','N'), - SUB_BPNN = MKTAG('B','P','N','N'), - SUB_BPNT = MKTAG('B','P','N','T'), - SUB_BPNI = MKTAG('B','P','N','I'), - SUB_RAGA = MKTAG('R','A','G','A'), - - SUB_QSTI = MKTAG('Q','S','T','I'), - SUB_QSTR = MKTAG('Q','S','T','R'), - SUB_QSDT = MKTAG('Q','S','D','T'), - SUB_SCDA = MKTAG('S','C','D','A'), - SUB_SCRO = MKTAG('S','C','R','O'), - SUB_QSTA = MKTAG('Q','S','T','A'), - SUB_CTDT = MKTAG('C','T','D','T'), - SUB_SCHD = MKTAG('S','C','H','D'), - SUB_TCLF = MKTAG('T','C','L','F'), - SUB_TCLT = MKTAG('T','C','L','T'), - SUB_TRDT = MKTAG('T','R','D','T'), - SUB_TPIC = MKTAG('T','P','I','C'), - - SUB_PKDT = MKTAG('P','K','D','T'), - SUB_PSDT = MKTAG('P','S','D','T'), - SUB_PLDT = MKTAG('P','L','D','T'), - SUB_PTDT = MKTAG('P','T','D','T'), - SUB_PGRP = MKTAG('P','G','R','P'), - SUB_PGRR = MKTAG('P','G','R','R'), - SUB_PGRI = MKTAG('P','G','R','I'), - SUB_PGRL = MKTAG('P','G','R','L'), - SUB_PGAG = MKTAG('P','G','A','G'), - SUB_FLTV = MKTAG('F','L','T','V'), - - SUB_XHLT = MKTAG('X','H','L','T'), // Unofficial Oblivion Patch - SUB_XCHG = MKTAG('X','C','H','G'), // thievery.exp - - SUB_ITXT = MKTAG('I','T','X','T'), - SUB_MO5T = MKTAG('M','O','5','T'), - SUB_MOD5 = MKTAG('M','O','D','5'), - SUB_MDOB = MKTAG('M','D','O','B'), - SUB_SPIT = MKTAG('S','P','I','T'), - SUB_PTDA = MKTAG('P','T','D','A'), // TES5 - SUB_PFOR = MKTAG('P','F','O','R'), // TES5 - SUB_PFO2 = MKTAG('P','F','O','2'), // TES5 - SUB_PRCB = MKTAG('P','R','C','B'), // TES5 - SUB_PKCU = MKTAG('P','K','C','U'), // TES5 - SUB_PKC2 = MKTAG('P','K','C','2'), // TES5 - SUB_CITC = MKTAG('C','I','T','C'), // TES5 - SUB_CIS1 = MKTAG('C','I','S','1'), // TES5 - SUB_CIS2 = MKTAG('C','I','S','2'), // TES5 - SUB_TIFC = MKTAG('T','I','F','C'), // TES5 - SUB_ALCA = MKTAG('A','L','C','A'), // TES5 - SUB_ALCL = MKTAG('A','L','C','L'), // TES5 - SUB_ALCO = MKTAG('A','L','C','O'), // TES5 - SUB_ALDN = MKTAG('A','L','D','N'), // TES5 - SUB_ALEA = MKTAG('A','L','E','A'), // TES5 - SUB_ALED = MKTAG('A','L','E','D'), // TES5 - SUB_ALEQ = MKTAG('A','L','E','Q'), // TES5 - SUB_ALFA = MKTAG('A','L','F','A'), // TES5 - SUB_ALFC = MKTAG('A','L','F','C'), // TES5 - SUB_ALFD = MKTAG('A','L','F','D'), // TES5 - SUB_ALFE = MKTAG('A','L','F','E'), // TES5 - SUB_ALFI = MKTAG('A','L','F','I'), // TES5 - SUB_ALFL = MKTAG('A','L','F','L'), // TES5 - SUB_ALFR = MKTAG('A','L','F','R'), // TES5 - SUB_ALID = MKTAG('A','L','I','D'), // TES5 - SUB_ALLS = MKTAG('A','L','L','S'), // TES5 - SUB_ALNA = MKTAG('A','L','N','A'), // TES5 - SUB_ALNT = MKTAG('A','L','N','T'), // TES5 - SUB_ALPC = MKTAG('A','L','P','C'), // TES5 - SUB_ALRT = MKTAG('A','L','R','T'), // TES5 - SUB_ALSP = MKTAG('A','L','S','P'), // TES5 - SUB_ALST = MKTAG('A','L','S','T'), // TES5 - SUB_ALUA = MKTAG('A','L','U','A'), // TES5 - SUB_FLTR = MKTAG('F','L','T','R'), // TES5 - SUB_QTGL = MKTAG('Q','T','G','L'), // TES5 - SUB_TWAT = MKTAG('T','W','A','T'), // TES5 - SUB_XIBS = MKTAG('X','I','B','S'), // FO3 - SUB_REPL = MKTAG('R','E','P','L'), // FO3 - SUB_BIPL = MKTAG('B','I','P','L'), // FO3 - SUB_MODD = MKTAG('M','O','D','D'), // FO3 - SUB_MOSD = MKTAG('M','O','S','D'), // FO3 - SUB_MO3S = MKTAG('M','O','3','S'), // FO3 - SUB_XCET = MKTAG('X','C','E','T'), // FO3 - SUB_LVLG = MKTAG('L','V','L','G'), // FO3 - SUB_NVCI = MKTAG('N','V','C','I'), // FO3 - SUB_NVVX = MKTAG('N','V','V','X'), // FO3 - SUB_NVTR = MKTAG('N','V','T','R'), // FO3 - SUB_NVCA = MKTAG('N','V','C','A'), // FO3 - SUB_NVDP = MKTAG('N','V','D','P'), // FO3 - SUB_NVGD = MKTAG('N','V','G','D'), // FO3 - SUB_NVEX = MKTAG('N','V','E','X'), // FO3 - SUB_XHLP = MKTAG('X','H','L','P'), // FO3 - SUB_XRDO = MKTAG('X','R','D','O'), // FO3 - SUB_XAMT = MKTAG('X','A','M','T'), // FO3 - SUB_XAMC = MKTAG('X','A','M','C'), // FO3 - SUB_XRAD = MKTAG('X','R','A','D'), // FO3 - SUB_XORD = MKTAG('X','O','R','D'), // FO3 - SUB_XCLP = MKTAG('X','C','L','P'), // FO3 - SUB_NEXT = MKTAG('N','E','X','T'), // FO3 - SUB_QOBJ = MKTAG('Q','O','B','J'), // FO3 - SUB_POBA = MKTAG('P','O','B','A'), // FO3 - SUB_POCA = MKTAG('P','O','C','A'), // FO3 - SUB_POEA = MKTAG('P','O','E','A'), // FO3 - SUB_PKDD = MKTAG('P','K','D','D'), // FO3 - SUB_PKD2 = MKTAG('P','K','D','2'), // FO3 - SUB_PKPT = MKTAG('P','K','P','T'), // FO3 - SUB_PKED = MKTAG('P','K','E','D'), // FO3 - SUB_PKE2 = MKTAG('P','K','E','2'), // FO3 - SUB_PKAM = MKTAG('P','K','A','M'), // FO3 - SUB_PUID = MKTAG('P','U','I','D'), // FO3 - SUB_PKW3 = MKTAG('P','K','W','3'), // FO3 - SUB_PTD2 = MKTAG('P','T','D','2'), // FO3 - SUB_PLD2 = MKTAG('P','L','D','2'), // FO3 - SUB_PKFD = MKTAG('P','K','F','D'), // FO3 - SUB_IDLB = MKTAG('I','D','L','B'), // FO3 - SUB_XDCR = MKTAG('X','D','C','R'), // FO3 - SUB_DALC = MKTAG('D','A','L','C'), // FO3 - SUB_IMPS = MKTAG('I','M','P','S'), // FO3 Anchorage - SUB_IMPF = MKTAG('I','M','P','F'), // FO3 Anchorage - - SUB_XATO = MKTAG('X','A','T','O'), // FONV - SUB_INFC = MKTAG('I','N','F','C'), // FONV - SUB_INFX = MKTAG('I','N','F','X'), // FONV - SUB_TDUM = MKTAG('T','D','U','M'), // FONV - SUB_TCFU = MKTAG('T','C','F','U'), // FONV - SUB_DAT2 = MKTAG('D','A','T','2'), // FONV - SUB_RCIL = MKTAG('R','C','I','L'), // FONV - SUB_MMRK = MKTAG('M','M','R','K'), // FONV - SUB_SCRV = MKTAG('S','C','R','V'), // FONV - SUB_SCVR = MKTAG('S','C','V','R'), // FONV - SUB_SLSD = MKTAG('S','L','S','D'), // FONV - SUB_XSRF = MKTAG('X','S','R','F'), // FONV - SUB_XSRD = MKTAG('X','S','R','D'), // FONV - SUB_WMI1 = MKTAG('W','M','I','1'), // FONV - SUB_RDID = MKTAG('R','D','I','D'), // FONV - SUB_RDSB = MKTAG('R','D','S','B'), // FONV - SUB_RDSI = MKTAG('R','D','S','I'), // FONV - SUB_BRUS = MKTAG('B','R','U','S'), // FONV - SUB_VATS = MKTAG('V','A','T','S'), // FONV - SUB_VANM = MKTAG('V','A','N','M'), // FONV - SUB_MWD1 = MKTAG('M','W','D','1'), // FONV - SUB_MWD2 = MKTAG('M','W','D','2'), // FONV - SUB_MWD3 = MKTAG('M','W','D','3'), // FONV - SUB_MWD4 = MKTAG('M','W','D','4'), // FONV - SUB_MWD5 = MKTAG('M','W','D','5'), // FONV - SUB_MWD6 = MKTAG('M','W','D','6'), // FONV - SUB_MWD7 = MKTAG('M','W','D','7'), // FONV - SUB_WMI2 = MKTAG('W','M','I','2'), // FONV - SUB_WMI3 = MKTAG('W','M','I','3'), // FONV - SUB_WMS1 = MKTAG('W','M','S','1'), // FONV - SUB_WMS2 = MKTAG('W','M','S','2'), // FONV - SUB_WNM1 = MKTAG('W','N','M','1'), // FONV - SUB_WNM2 = MKTAG('W','N','M','2'), // FONV - SUB_WNM3 = MKTAG('W','N','M','3'), // FONV - SUB_WNM4 = MKTAG('W','N','M','4'), // FONV - SUB_WNM5 = MKTAG('W','N','M','5'), // FONV - SUB_WNM6 = MKTAG('W','N','M','6'), // FONV - SUB_WNM7 = MKTAG('W','N','M','7'), // FONV - SUB_JNAM = MKTAG('J','N','A','M'), // FONV - SUB_EFSD = MKTAG('E','F','S','D'), // FONV DeadMoney + SUB_HEDR = fourCC("HEDR"), + SUB_CNAM = fourCC("CNAM"), + SUB_SNAM = fourCC("SNAM"), // TES4 only? + SUB_MAST = fourCC("MAST"), + SUB_DATA = fourCC("DATA"), + SUB_ONAM = fourCC("ONAM"), + SUB_INTV = fourCC("INTV"), + SUB_INCC = fourCC("INCC"), + SUB_OFST = fourCC("OFST"), // TES4 only? + SUB_DELE = fourCC("DELE"), // TES4 only? + + SUB_DNAM = fourCC("DNAM"), + SUB_EDID = fourCC("EDID"), + SUB_FULL = fourCC("FULL"), + SUB_LTMP = fourCC("LTMP"), + SUB_MHDT = fourCC("MHDT"), + SUB_MNAM = fourCC("MNAM"), + SUB_MODL = fourCC("MODL"), + SUB_NAM0 = fourCC("NAM0"), + SUB_NAM2 = fourCC("NAM2"), + SUB_NAM3 = fourCC("NAM3"), + SUB_NAM4 = fourCC("NAM4"), + SUB_NAM9 = fourCC("NAM9"), + SUB_NAMA = fourCC("NAMA"), + SUB_PNAM = fourCC("PNAM"), + SUB_RNAM = fourCC("RNAM"), + SUB_TNAM = fourCC("TNAM"), + SUB_UNAM = fourCC("UNAM"), + SUB_WCTR = fourCC("WCTR"), + SUB_WNAM = fourCC("WNAM"), + SUB_XEZN = fourCC("XEZN"), + SUB_XLCN = fourCC("XLCN"), + SUB_XXXX = fourCC("XXXX"), + SUB_ZNAM = fourCC("ZNAM"), + SUB_MODT = fourCC("MODT"), + SUB_ICON = fourCC("ICON"), // TES4 only? + + SUB_NVER = fourCC("NVER"), + SUB_NVMI = fourCC("NVMI"), + SUB_NVPP = fourCC("NVPP"), + SUB_NVSI = fourCC("NVSI"), + + SUB_NVNM = fourCC("NVNM"), + SUB_NNAM = fourCC("NNAM"), + + SUB_XCLC = fourCC("XCLC"), + SUB_XCLL = fourCC("XCLL"), + SUB_TVDT = fourCC("TVDT"), + SUB_XCGD = fourCC("XCGD"), + SUB_LNAM = fourCC("LNAM"), + SUB_XCLW = fourCC("XCLW"), + SUB_XNAM = fourCC("XNAM"), + SUB_XCLR = fourCC("XCLR"), + SUB_XWCS = fourCC("XWCS"), + SUB_XWCN = fourCC("XWCN"), + SUB_XWCU = fourCC("XWCU"), + SUB_XCWT = fourCC("XCWT"), + SUB_XOWN = fourCC("XOWN"), + SUB_XILL = fourCC("XILL"), + SUB_XWEM = fourCC("XWEM"), + SUB_XCCM = fourCC("XCCM"), + SUB_XCAS = fourCC("XCAS"), + SUB_XCMO = fourCC("XCMO"), + SUB_XCIM = fourCC("XCIM"), + SUB_XCMT = fourCC("XCMT"), // TES4 only? + SUB_XRNK = fourCC("XRNK"), // TES4 only? + SUB_XGLB = fourCC("XGLB"), // TES4 only? + + SUB_VNML = fourCC("VNML"), + SUB_VHGT = fourCC("VHGT"), + SUB_VCLR = fourCC("VCLR"), + SUA_BTXT = fourCC("BTXT"), + SUB_ATXT = fourCC("ATXT"), + SUB_VTXT = fourCC("VTXT"), + SUB_VTEX = fourCC("VTEX"), + + SUB_HNAM = fourCC("HNAM"), + SUB_GNAM = fourCC("GNAM"), + + SUB_RCLR = fourCC("RCLR"), + SUB_RPLI = fourCC("RPLI"), + SUB_RPLD = fourCC("RPLD"), + SUB_RDAT = fourCC("RDAT"), + SUB_RDMD = fourCC("RDMD"), // TES4 only? + SUB_RDSD = fourCC("RDSD"), // TES4 only? + SUB_RDGS = fourCC("RDGS"), // TES4 only? + SUB_RDMO = fourCC("RDMO"), + SUB_RDSA = fourCC("RDSA"), + SUB_RDWT = fourCC("RDWT"), + SUB_RDOT = fourCC("RDOT"), + SUB_RDMP = fourCC("RDMP"), + + SUB_MODB = fourCC("MODB"), + SUB_OBND = fourCC("OBND"), + SUB_MODS = fourCC("MODS"), + + SUB_NAME = fourCC("NAME"), + SUB_XMRK = fourCC("XMRK"), + SUB_FNAM = fourCC("FNAM"), + SUB_XSCL = fourCC("XSCL"), + SUB_XTEL = fourCC("XTEL"), + SUB_XTRG = fourCC("XTRG"), + SUB_XSED = fourCC("XSED"), + SUB_XLOD = fourCC("XLOD"), + SUB_XPCI = fourCC("XPCI"), + SUB_XLOC = fourCC("XLOC"), + SUB_XESP = fourCC("XESP"), + SUB_XLCM = fourCC("XLCM"), + SUB_XRTM = fourCC("XRTM"), + SUB_XACT = fourCC("XACT"), + SUB_XCNT = fourCC("XCNT"), + SUB_VMAD = fourCC("VMAD"), + SUB_XPRM = fourCC("XPRM"), + SUB_XMBO = fourCC("XMBO"), + SUB_XPOD = fourCC("XPOD"), + SUB_XRMR = fourCC("XRMR"), + SUB_INAM = fourCC("INAM"), + SUB_SCHR = fourCC("SCHR"), + SUB_XLRM = fourCC("XLRM"), + SUB_XRGD = fourCC("XRGD"), + SUB_XRDS = fourCC("XRDS"), + SUB_XEMI = fourCC("XEMI"), + SUB_XLIG = fourCC("XLIG"), + SUB_XALP = fourCC("XALP"), + SUB_XNDP = fourCC("XNDP"), + SUB_XAPD = fourCC("XAPD"), + SUB_XAPR = fourCC("XAPR"), + SUB_XLIB = fourCC("XLIB"), + SUB_XLKR = fourCC("XLKR"), + SUB_XLRT = fourCC("XLRT"), + SUB_XCVL = fourCC("XCVL"), + SUB_XCVR = fourCC("XCVR"), + SUB_XCZA = fourCC("XCZA"), + SUB_XCZC = fourCC("XCZC"), + SUB_XFVC = fourCC("XFVC"), + SUB_XHTW = fourCC("XHTW"), + SUB_XIS2 = fourCC("XIS2"), + SUB_XMBR = fourCC("XMBR"), + SUB_XCCP = fourCC("XCCP"), + SUB_XPWR = fourCC("XPWR"), + SUB_XTRI = fourCC("XTRI"), + SUB_XATR = fourCC("XATR"), + SUB_XPRD = fourCC("XPRD"), + SUB_XPPA = fourCC("XPPA"), + SUB_PDTO = fourCC("PDTO"), + SUB_XLRL = fourCC("XLRL"), + + SUB_QNAM = fourCC("QNAM"), + SUB_COCT = fourCC("COCT"), + SUB_COED = fourCC("COED"), + SUB_CNTO = fourCC("CNTO"), + SUB_SCRI = fourCC("SCRI"), + + SUB_BNAM = fourCC("BNAM"), + + SUB_BMDT = fourCC("BMDT"), + SUB_MOD2 = fourCC("MOD2"), + SUB_MOD3 = fourCC("MOD3"), + SUB_MOD4 = fourCC("MOD4"), + SUB_MO2B = fourCC("MO2B"), + SUB_MO3B = fourCC("MO3B"), + SUB_MO4B = fourCC("MO4B"), + SUB_MO2T = fourCC("MO2T"), + SUB_MO3T = fourCC("MO3T"), + SUB_MO4T = fourCC("MO4T"), + SUB_ANAM = fourCC("ANAM"), + SUB_ENAM = fourCC("ENAM"), + SUB_ICO2 = fourCC("ICO2"), + + SUB_ACBS = fourCC("ACBS"), + SUB_SPLO = fourCC("SPLO"), + SUB_AIDT = fourCC("AIDT"), + SUB_PKID = fourCC("PKID"), + SUB_HCLR = fourCC("HCLR"), + SUB_FGGS = fourCC("FGGS"), + SUB_FGGA = fourCC("FGGA"), + SUB_FGTS = fourCC("FGTS"), + SUB_KFFZ = fourCC("KFFZ"), + + SUB_PFIG = fourCC("PFIG"), + SUB_PFPC = fourCC("PFPC"), + + SUB_XHRS = fourCC("XHRS"), + SUB_XMRC = fourCC("XMRC"), + + SUB_SNDD = fourCC("SNDD"), + SUB_SNDX = fourCC("SNDX"), + + SUB_DESC = fourCC("DESC"), + + SUB_ENIT = fourCC("ENIT"), + SUB_EFID = fourCC("EFID"), + SUB_EFIT = fourCC("EFIT"), + SUB_SCIT = fourCC("SCIT"), + + SUB_SOUL = fourCC("SOUL"), + SUB_SLCP = fourCC("SLCP"), + + SUB_CSCR = fourCC("CSCR"), + SUB_CSDI = fourCC("CSDI"), + SUB_CSDC = fourCC("CSDC"), + SUB_NIFZ = fourCC("NIFZ"), + SUB_CSDT = fourCC("CSDT"), + SUB_NAM1 = fourCC("NAM1"), + SUB_NIFT = fourCC("NIFT"), + + SUB_LVLD = fourCC("LVLD"), + SUB_LVLF = fourCC("LVLF"), + SUB_LVLO = fourCC("LVLO"), + + SUB_BODT = fourCC("BODT"), + SUB_YNAM = fourCC("YNAM"), + SUB_DEST = fourCC("DEST"), + SUB_DMDL = fourCC("DMDL"), + SUB_DMDS = fourCC("DMDS"), + SUB_DMDT = fourCC("DMDT"), + SUB_DSTD = fourCC("DSTD"), + SUB_DSTF = fourCC("DSTF"), + SUB_KNAM = fourCC("KNAM"), + SUB_KSIZ = fourCC("KSIZ"), + SUB_KWDA = fourCC("KWDA"), + SUB_VNAM = fourCC("VNAM"), + SUB_SDSC = fourCC("SDSC"), + SUB_MO2S = fourCC("MO2S"), + SUB_MO4S = fourCC("MO4S"), + SUB_BOD2 = fourCC("BOD2"), + SUB_BAMT = fourCC("BAMT"), + SUB_BIDS = fourCC("BIDS"), + SUB_ETYP = fourCC("ETYP"), + SUB_BMCT = fourCC("BMCT"), + SUB_MICO = fourCC("MICO"), + SUB_MIC2 = fourCC("MIC2"), + SUB_EAMT = fourCC("EAMT"), + SUB_EITM = fourCC("EITM"), + + SUB_SCTX = fourCC("SCTX"), + SUB_XLTW = fourCC("XLTW"), + SUB_XMBP = fourCC("XMBP"), + SUB_XOCP = fourCC("XOCP"), + SUB_XRGB = fourCC("XRGB"), + SUB_XSPC = fourCC("XSPC"), + SUB_XTNM = fourCC("XTNM"), + SUB_ATKR = fourCC("ATKR"), + SUB_CRIF = fourCC("CRIF"), + SUB_DOFT = fourCC("DOFT"), + SUB_DPLT = fourCC("DPLT"), + SUB_ECOR = fourCC("ECOR"), + SUB_ATKD = fourCC("ATKD"), + SUB_ATKE = fourCC("ATKE"), + SUB_FTST = fourCC("FTST"), + SUB_HCLF = fourCC("HCLF"), + SUB_NAM5 = fourCC("NAM5"), + SUB_NAM6 = fourCC("NAM6"), + SUB_NAM7 = fourCC("NAM7"), + SUB_NAM8 = fourCC("NAM8"), + SUB_PRKR = fourCC("PRKR"), + SUB_PRKZ = fourCC("PRKZ"), + SUB_SOFT = fourCC("SOFT"), + SUB_SPCT = fourCC("SPCT"), + SUB_TINC = fourCC("TINC"), + SUB_TIAS = fourCC("TIAS"), + SUB_TINI = fourCC("TINI"), + SUB_TINV = fourCC("TINV"), + SUB_TPLT = fourCC("TPLT"), + SUB_VTCK = fourCC("VTCK"), + SUB_SHRT = fourCC("SHRT"), + SUB_SPOR = fourCC("SPOR"), + SUB_XHOR = fourCC("XHOR"), + SUB_CTDA = fourCC("CTDA"), + SUB_CRDT = fourCC("CRDT"), + SUB_FNMK = fourCC("FNMK"), + SUB_FNPR = fourCC("FNPR"), + SUB_WBDT = fourCC("WBDT"), + SUB_QUAL = fourCC("QUAL"), + SUB_INDX = fourCC("INDX"), + SUB_ATTR = fourCC("ATTR"), + SUB_MTNM = fourCC("MTNM"), + SUB_UNES = fourCC("UNES"), + SUB_TIND = fourCC("TIND"), + SUB_TINL = fourCC("TINL"), + SUB_TINP = fourCC("TINP"), + SUB_TINT = fourCC("TINT"), + SUB_TIRS = fourCC("TIRS"), + SUB_PHWT = fourCC("PHWT"), + SUB_AHCF = fourCC("AHCF"), + SUB_AHCM = fourCC("AHCM"), + SUB_HEAD = fourCC("HEAD"), + SUB_MPAI = fourCC("MPAI"), + SUB_MPAV = fourCC("MPAV"), + SUB_DFTF = fourCC("DFTF"), + SUB_DFTM = fourCC("DFTM"), + SUB_FLMV = fourCC("FLMV"), + SUB_FTSF = fourCC("FTSF"), + SUB_FTSM = fourCC("FTSM"), + SUB_MTYP = fourCC("MTYP"), + SUB_PHTN = fourCC("PHTN"), + SUB_RNMV = fourCC("RNMV"), + SUB_RPRF = fourCC("RPRF"), + SUB_RPRM = fourCC("RPRM"), + SUB_SNMV = fourCC("SNMV"), + SUB_SPED = fourCC("SPED"), + SUB_SWMV = fourCC("SWMV"), + SUB_WKMV = fourCC("WKMV"), + SUB_LLCT = fourCC("LLCT"), + SUB_IDLF = fourCC("IDLF"), + SUB_IDLA = fourCC("IDLA"), + SUB_IDLC = fourCC("IDLC"), + SUB_IDLT = fourCC("IDLT"), + SUB_DODT = fourCC("DODT"), + 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_BPND = fourCC("BPND"), + SUB_BPTN = fourCC("BPTN"), + SUB_BPNN = fourCC("BPNN"), + SUB_BPNT = fourCC("BPNT"), + SUB_BPNI = fourCC("BPNI"), + SUB_RAGA = fourCC("RAGA"), + + SUB_QSTI = fourCC("QSTI"), + SUB_QSTR = fourCC("QSTR"), + SUB_QSDT = fourCC("QSDT"), + SUB_SCDA = fourCC("SCDA"), + SUB_SCRO = fourCC("SCRO"), + SUB_QSTA = fourCC("QSTA"), + SUB_CTDT = fourCC("CTDT"), + SUB_SCHD = fourCC("SCHD"), + SUB_TCLF = fourCC("TCLF"), + SUB_TCLT = fourCC("TCLT"), + SUB_TRDT = fourCC("TRDT"), + SUB_TPIC = fourCC("TPIC"), + + SUB_PKDT = fourCC("PKDT"), + SUB_PSDT = fourCC("PSDT"), + SUB_PLDT = fourCC("PLDT"), + SUB_PTDT = fourCC("PTDT"), + SUB_PGRP = fourCC("PGRP"), + SUB_PGRR = fourCC("PGRR"), + SUB_PGRI = fourCC("PGRI"), + SUB_PGRL = fourCC("PGRL"), + SUB_PGAG = fourCC("PGAG"), + SUB_FLTV = fourCC("FLTV"), + + SUB_XHLT = fourCC("XHLT"), // Unofficial Oblivion Patch + SUB_XCHG = fourCC("XCHG"), // thievery.exp + + SUB_ITXT = fourCC("ITXT"), + SUB_MO5T = fourCC("MO5T"), + SUB_MOD5 = fourCC("MOD5"), + SUB_MDOB = fourCC("MDOB"), + SUB_SPIT = fourCC("SPIT"), + SUB_PTDA = fourCC("PTDA"), // TES5 + SUB_PFOR = fourCC("PFOR"), // TES5 + SUB_PFO2 = fourCC("PFO2"), // TES5 + SUB_PRCB = fourCC("PRCB"), // TES5 + SUB_PKCU = fourCC("PKCU"), // TES5 + SUB_PKC2 = fourCC("PKC2"), // TES5 + SUB_CITC = fourCC("CITC"), // TES5 + SUB_CIS1 = fourCC("CIS1"), // TES5 + SUB_CIS2 = fourCC("CIS2"), // TES5 + SUB_TIFC = fourCC("TIFC"), // TES5 + SUB_ALCA = fourCC("ALCA"), // TES5 + SUB_ALCL = fourCC("ALCL"), // TES5 + SUB_ALCO = fourCC("ALCO"), // TES5 + 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_ALID = fourCC("ALID"), // TES5 + SUB_ALLS = fourCC("ALLS"), // TES5 + 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_FLTR = fourCC("FLTR"), // TES5 + SUB_QTGL = fourCC("QTGL"), // TES5 + SUB_TWAT = fourCC("TWAT"), // TES5 + SUB_XIBS = fourCC("XIBS"), // FO3 + SUB_REPL = fourCC("REPL"), // FO3 + SUB_BIPL = fourCC("BIPL"), // FO3 + SUB_MODD = fourCC("MODD"), // FO3 + SUB_MOSD = fourCC("MOSD"), // FO3 + SUB_MO3S = fourCC("MO3S"), // FO3 + SUB_XCET = fourCC("XCET"), // FO3 + SUB_LVLG = fourCC("LVLG"), // FO3 + SUB_NVCI = fourCC("NVCI"), // FO3 + SUB_NVVX = fourCC("NVVX"), // FO3 + SUB_NVTR = fourCC("NVTR"), // FO3 + SUB_NVCA = fourCC("NVCA"), // FO3 + SUB_NVDP = fourCC("NVDP"), // FO3 + SUB_NVGD = fourCC("NVGD"), // FO3 + SUB_NVEX = fourCC("NVEX"), // FO3 + SUB_XHLP = fourCC("XHLP"), // FO3 + SUB_XRDO = fourCC("XRDO"), // FO3 + SUB_XAMT = fourCC("XAMT"), // FO3 + SUB_XAMC = fourCC("XAMC"), // FO3 + SUB_XRAD = fourCC("XRAD"), // FO3 + SUB_XORD = fourCC("XORD"), // FO3 + SUB_XCLP = fourCC("XCLP"), // FO3 + SUB_NEXT = fourCC("NEXT"), // FO3 + SUB_QOBJ = fourCC("QOBJ"), // FO3 + SUB_POBA = fourCC("POBA"), // FO3 + SUB_POCA = fourCC("POCA"), // FO3 + SUB_POEA = fourCC("POEA"), // FO3 + SUB_PKDD = fourCC("PKDD"), // FO3 + SUB_PKD2 = fourCC("PKD2"), // FO3 + SUB_PKPT = fourCC("PKPT"), // FO3 + SUB_PKED = fourCC("PKED"), // FO3 + SUB_PKE2 = fourCC("PKE2"), // FO3 + SUB_PKAM = fourCC("PKAM"), // FO3 + SUB_PUID = fourCC("PUID"), // FO3 + SUB_PKW3 = fourCC("PKW3"), // FO3 + SUB_PTD2 = fourCC("PTD2"), // FO3 + SUB_PLD2 = fourCC("PLD2"), // FO3 + SUB_PKFD = fourCC("PKFD"), // FO3 + SUB_IDLB = fourCC("IDLB"), // FO3 + SUB_XDCR = fourCC("XDCR"), // FO3 + SUB_DALC = fourCC("DALC"), // FO3 + SUB_IMPS = fourCC("IMPS"), // FO3 Anchorage + SUB_IMPF = fourCC("IMPF"), // FO3 Anchorage + + SUB_XATO = fourCC("XATO"), // FONV + SUB_INFC = fourCC("INFC"), // FONV + SUB_INFX = fourCC("INFX"), // FONV + SUB_TDUM = fourCC("TDUM"), // FONV + SUB_TCFU = fourCC("TCFU"), // FONV + SUB_DAT2 = fourCC("DAT2"), // FONV + SUB_RCIL = fourCC("RCIL"), // FONV + SUB_MMRK = fourCC("MMRK"), // FONV + SUB_SCRV = fourCC("SCRV"), // FONV + SUB_SCVR = fourCC("SCVR"), // FONV + SUB_SLSD = fourCC("SLSD"), // FONV + SUB_XSRF = fourCC("XSRF"), // FONV + SUB_XSRD = fourCC("XSRD"), // FONV + SUB_WMI1 = fourCC("WMI1"), // FONV + SUB_RDID = fourCC("RDID"), // FONV + SUB_RDSB = fourCC("RDSB"), // FONV + SUB_RDSI = fourCC("RDSI"), // FONV + SUB_BRUS = fourCC("BRUS"), // FONV + SUB_VATS = fourCC("VATS"), // FONV + SUB_VANM = fourCC("VANM"), // FONV + 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_WMI2 = fourCC("WMI2"), // FONV + SUB_WMI3 = fourCC("WMI3"), // FONV + SUB_WMS1 = fourCC("WMS1"), // FONV + SUB_WMS2 = fourCC("WMS2"), // FONV + 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_JNAM = fourCC("JNAM"), // FONV + SUB_EFSD = fourCC("EFSD"), // FONV DeadMoney }; enum MagicEffectID { // Alteration - EFI_BRDN = MKTAG('B','R','D','N'), - EFI_FTHR = MKTAG('F','T','H','R'), - EFI_FISH = MKTAG('F','I','S','H'), - EFI_FRSH = MKTAG('F','R','S','H'), - EFI_OPEN = MKTAG('O','P','N','N'), - EFI_SHLD = MKTAG('S','H','L','D'), - EFI_LISH = MKTAG('L','I','S','H'), - EFI_WABR = MKTAG('W','A','B','R'), - EFI_WAWA = MKTAG('W','A','W','A'), + EFI_BRDN = fourCC("BRDN"), + EFI_FTHR = fourCC("FTHR"), + EFI_FISH = fourCC("FISH"), + EFI_FRSH = fourCC("FRSH"), + EFI_OPEN = fourCC("OPNN"), + EFI_SHLD = fourCC("SHLD"), + EFI_LISH = fourCC("LISH"), + EFI_WABR = fourCC("WABR"), + EFI_WAWA = fourCC("WAWA"), // Conjuration - EFI_BABO = MKTAG('B','A','B','O'), // Bound Boots - EFI_BACU = MKTAG('B','A','C','U'), // Bound Cuirass - EFI_BAGA = MKTAG('B','A','G','A'), // Bound Gauntlets - EFI_BAGR = MKTAG('B','A','G','R'), // Bound Greaves - EFI_BAHE = MKTAG('B','A','H','E'), // Bound Helmet - EFI_BASH = MKTAG('B','A','S','H'), // Bound Shield - EFI_BWAX = MKTAG('B','W','A','X'), // Bound Axe - EFI_BWBO = MKTAG('B','W','B','O'), // Bound Bow - EFI_BWDA = MKTAG('B','W','D','A'), // Bound Dagger - EFI_BWMA = MKTAG('B','W','M','A'), // Bound Mace - EFI_BWSW = MKTAG('B','W','S','W'), // Bound Sword - EFI_Z001 = MKTAG('Z','0','0','1'), // Summon Rufio's Ghost - EFI_Z002 = MKTAG('Z','0','0','2'), // Summon Ancestor Guardian - EFI_Z003 = MKTAG('Z','0','0','3'), // Summon Spiderling - EFI_Z005 = MKTAG('Z','0','0','5'), // Summon Bear - EFI_ZCLA = MKTAG('Z','C','L','A'), // Summon Clannfear - EFI_ZDAE = MKTAG('Z','D','A','E'), // Summon Daedroth - EFI_ZDRE = MKTAG('Z','D','R','E'), // Summon Dremora - EFI_ZDRL = MKTAG('Z','D','R','L'), // Summon Dremora Lord - EFI_ZFIA = MKTAG('Z','F','I','A'), // Summon Flame Atronach - EFI_ZFRA = MKTAG('Z','F','R','A'), // Summon Frost Atronach - EFI_ZGHO = MKTAG('Z','G','H','O'), // Summon Ghost - EFI_ZHDZ = MKTAG('Z','H','D','Z'), // Summon Headless Zombie - EFI_ZLIC = MKTAG('Z','L','I','C'), // Summon Lich - EFI_ZSCA = MKTAG('Z','S','C','A'), // Summon Scamp - EFI_ZSKE = MKTAG('Z','S','K','E'), // Summon Skeleton - EFI_ZSKA = MKTAG('Z','S','K','A'), // Summon Skeleton Guardian - EFI_ZSKH = MKTAG('Z','S','K','H'), // Summon Skeleton Hero - EFI_ZSKC = MKTAG('Z','S','K','C'), // Summon Skeleton Champion - EFI_ZSPD = MKTAG('Z','S','P','D'), // Summon Spider Daedra - EFI_ZSTA = MKTAG('Z','S','T','A'), // Summon Storm Atronach - EFI_ZWRA = MKTAG('Z','W','R','A'), // Summon Faded Wraith - EFI_ZWRL = MKTAG('Z','W','R','L'), // Summon Gloom Wraith - EFI_ZXIV = MKTAG('Z','X','I','V'), // Summon Xivilai - EFI_ZZOM = MKTAG('Z','Z','O','M'), // Summon Zombie - EFI_TURN = MKTAG('T','U','R','N'), // Turn Undead + EFI_BABO = fourCC("BABO"), // Bound Boots + EFI_BACU = fourCC("BACU"), // Bound Cuirass + EFI_BAGA = fourCC("BAGA"), // Bound Gauntlets + EFI_BAGR = fourCC("BAGR"), // Bound Greaves + EFI_BAHE = fourCC("BAHE"), // Bound Helmet + EFI_BASH = fourCC("BASH"), // Bound Shield + EFI_BWAX = fourCC("BWAX"), // Bound Axe + EFI_BWBO = fourCC("BWBO"), // Bound Bow + EFI_BWDA = fourCC("BWDA"), // Bound Dagger + EFI_BWMA = fourCC("BWMA"), // Bound Mace + EFI_BWSW = fourCC("BWSW"), // Bound Sword + EFI_Z001 = fourCC("Z001"), // Summon Rufio's Ghost + EFI_Z002 = fourCC("Z002"), // Summon Ancestor Guardian + EFI_Z003 = fourCC("Z003"), // Summon Spiderling + EFI_Z005 = fourCC("Z005"), // Summon Bear + EFI_ZCLA = fourCC("ZCLA"), // Summon Clannfear + EFI_ZDAE = fourCC("ZDAE"), // Summon Daedroth + EFI_ZDRE = fourCC("ZDRE"), // Summon Dremora + EFI_ZDRL = fourCC("ZDRL"), // Summon Dremora Lord + EFI_ZFIA = fourCC("ZFIA"), // Summon Flame Atronach + EFI_ZFRA = fourCC("ZFRA"), // Summon Frost Atronach + EFI_ZGHO = fourCC("ZGHO"), // Summon Ghost + EFI_ZHDZ = fourCC("ZHDZ"), // Summon Headless Zombie + EFI_ZLIC = fourCC("ZLIC"), // Summon Lich + EFI_ZSCA = fourCC("ZSCA"), // Summon Scamp + EFI_ZSKE = fourCC("ZSKE"), // Summon Skeleton + EFI_ZSKA = fourCC("ZSKA"), // Summon Skeleton Guardian + EFI_ZSKH = fourCC("ZSKH"), // Summon Skeleton Hero + EFI_ZSKC = fourCC("ZSKC"), // Summon Skeleton Champion + EFI_ZSPD = fourCC("ZSPD"), // Summon Spider Daedra + EFI_ZSTA = fourCC("ZSTA"), // Summon Storm Atronach + EFI_ZWRA = fourCC("ZWRA"), // Summon Faded Wraith + EFI_ZWRL = fourCC("ZWRL"), // Summon Gloom Wraith + EFI_ZXIV = fourCC("ZXIV"), // Summon Xivilai + EFI_ZZOM = fourCC("ZZOM"), // Summon Zombie + EFI_TURN = fourCC("TURN"), // Turn Undead // Destruction - EFI_DGAT = MKTAG('D','G','A','T'), // Damage Attribute - EFI_DGFA = MKTAG('D','G','F','A'), // Damage Fatigue - EFI_DGHE = MKTAG('D','G','H','E'), // Damage Health - EFI_DGSP = MKTAG('D','G','S','P'), // Damage Magicka - EFI_DIAR = MKTAG('D','I','A','R'), // Disintegrate Armor - EFI_DIWE = MKTAG('D','I','W','E'), // Disintegrate Weapon - EFI_DRAT = MKTAG('D','R','A','T'), // Drain Attribute - EFI_DRFA = MKTAG('D','R','F','A'), // Drain Fatigue - EFI_DRHE = MKTAG('D','R','H','E'), // Drain Health - EFI_DRSP = MKTAG('D','R','S','P'), // Drain Magicka - EFI_DRSK = MKTAG('D','R','S','K'), // Drain Skill - EFI_FIDG = MKTAG('F','I','D','G'), // Fire Damage - EFI_FRDG = MKTAG('F','R','D','G'), // Frost Damage - EFI_SHDG = MKTAG('S','H','D','G'), // Shock Damage - EFI_WKDI = MKTAG('W','K','D','I'), // Weakness to Disease - EFI_WKFI = MKTAG('W','K','F','I'), // Weakness to Fire - EFI_WKFR = MKTAG('W','K','F','R'), // Weakness to Frost - EFI_WKMA = MKTAG('W','K','M','A'), // Weakness to Magic - EFI_WKNW = MKTAG('W','K','N','W'), // Weakness to Normal Weapons - EFI_WKPO = MKTAG('W','K','P','O'), // Weakness to Poison - EFI_WKSH = MKTAG('W','K','S','H'), // Weakness to Shock + EFI_DGAT = fourCC("DGAT"), // Damage Attribute + EFI_DGFA = fourCC("DGFA"), // Damage Fatigue + EFI_DGHE = fourCC("DGHE"), // Damage Health + EFI_DGSP = fourCC("DGSP"), // Damage Magicka + EFI_DIAR = fourCC("DIAR"), // Disintegrate Armor + EFI_DIWE = fourCC("DIWE"), // Disintegrate Weapon + EFI_DRAT = fourCC("DRAT"), // Drain Attribute + EFI_DRFA = fourCC("DRFA"), // Drain Fatigue + EFI_DRHE = fourCC("DRHE"), // Drain Health + EFI_DRSP = fourCC("DRSP"), // Drain Magicka + EFI_DRSK = fourCC("DRSK"), // Drain Skill + EFI_FIDG = fourCC("FIDG"), // Fire Damage + EFI_FRDG = fourCC("FRDG"), // Frost Damage + EFI_SHDG = fourCC("SHDG"), // Shock Damage + EFI_WKDI = fourCC("WKDI"), // Weakness to Disease + EFI_WKFI = fourCC("WKFI"), // Weakness to Fire + EFI_WKFR = fourCC("WKFR"), // Weakness to Frost + EFI_WKMA = fourCC("WKMA"), // Weakness to Magic + EFI_WKNW = fourCC("WKNW"), // Weakness to Normal Weapons + EFI_WKPO = fourCC("WKPO"), // Weakness to Poison + EFI_WKSH = fourCC("WKSH"), // Weakness to Shock // Illusion - EFI_CALM = MKTAG('C','A','L','M'), // Calm - EFI_CHML = MKTAG('C','H','M','L'), // Chameleon - EFI_CHRM = MKTAG('C','H','R','M'), // Charm - EFI_COCR = MKTAG('C','O','C','R'), // Command Creature - EFI_COHU = MKTAG('C','O','H','U'), // Command Humanoid - EFI_DEMO = MKTAG('D','E','M','O'), // Demoralize - EFI_FRNZ = MKTAG('F','R','N','Z'), // Frenzy - EFI_INVI = MKTAG('I','N','V','I'), // Invisibility - EFI_LGHT = MKTAG('L','G','H','T'), // Light - EFI_NEYE = MKTAG('N','E','Y','E'), // Night-Eye - EFI_PARA = MKTAG('P','A','R','A'), // Paralyze - EFI_RALY = MKTAG('R','A','L','Y'), // Rally - EFI_SLNC = MKTAG('S','L','N','C'), // Silence + EFI_CALM = fourCC("CALM"), // Calm + EFI_CHML = fourCC("CHML"), // Chameleon + EFI_CHRM = fourCC("CHRM"), // Charm + EFI_COCR = fourCC("COCR"), // Command Creature + EFI_COHU = fourCC("COHU"), // Command Humanoid + EFI_DEMO = fourCC("DEMO"), // Demoralize + EFI_FRNZ = fourCC("FRNZ"), // Frenzy + EFI_INVI = fourCC("INVI"), // Invisibility + EFI_LGHT = fourCC("LGHT"), // Light + EFI_NEYE = fourCC("NEYE"), // Night-Eye + EFI_PARA = fourCC("PARA"), // Paralyze + EFI_RALY = fourCC("RALY"), // Rally + EFI_SLNC = fourCC("SLNC"), // Silence // Mysticism - EFI_DTCT = MKTAG('D','T','C','T'), // Detect Life - EFI_DSPL = MKTAG('D','S','P','L'), // Dispel - EFI_REDG = MKTAG('R','E','D','G'), // Reflect Damage - EFI_RFLC = MKTAG('R','F','L','C'), // Reflect Spell - EFI_STRP = MKTAG('S','T','R','P'), // Soul Trap - EFI_SABS = MKTAG('S','A','B','S'), // Spell Absorption - EFI_TELE = MKTAG('T','E','L','E'), // Telekinesis + EFI_DTCT = fourCC("DTCT"), // Detect Life + EFI_DSPL = fourCC("DSPL"), // Dispel + EFI_REDG = fourCC("REDG"), // Reflect Damage + EFI_RFLC = fourCC("RFLC"), // Reflect Spell + EFI_STRP = fourCC("STRP"), // Soul Trap + EFI_SABS = fourCC("SABS"), // Spell Absorption + EFI_TELE = fourCC("TELE"), // Telekinesis // Restoration - EFI_ABAT = MKTAG('A','B','A','T'), // Absorb Attribute - EFI_ABFA = MKTAG('A','B','F','A'), // Absorb Fatigue - EFI_ABHe = MKTAG('A','B','H','e'), // Absorb Health - EFI_ABSP = MKTAG('A','B','S','P'), // Absorb Magicka - EFI_ABSK = MKTAG('A','B','S','K'), // Absorb Skill - EFI_1400 = MKTAG('1','4','0','0'), // Cure Disease - EFI_CUPA = MKTAG('C','U','P','A'), // Cure Paralysis - EFI_CUPO = MKTAG('C','U','P','O'), // Cure Poison - EFI_FOAT = MKTAG('F','O','A','T'), // Fortify Attribute - EFI_FOFA = MKTAG('F','O','F','A'), // Fortify Fatigue - EFI_FOHE = MKTAG('F','O','H','E'), // Fortify Health - EFI_FOSP = MKTAG('F','O','S','P'), // Fortify Magicka - EFI_FOSK = MKTAG('F','O','S','K'), // Fortify Skill - EFI_RSDI = MKTAG('R','S','D','I'), // Resist Disease - EFI_RSFI = MKTAG('R','S','F','I'), // Resist Fire - EFI_RSFR = MKTAG('R','S','F','R'), // Resist Frost - EFI_RSMA = MKTAG('R','S','M','A'), // Resist Magic - EFI_RSNW = MKTAG('R','S','N','W'), // Resist Normal Weapons - EFI_RSPA = MKTAG('R','S','P','A'), // Resist Paralysis - EFI_RSPO = MKTAG('R','S','P','O'), // Resist Poison - EFI_RSSH = MKTAG('R','S','S','H'), // Resist Shock - EFI_REAT = MKTAG('R','E','A','T'), // Restore Attribute - EFI_REFA = MKTAG('R','E','F','A'), // Restore Fatigue - EFI_REHE = MKTAG('R','E','H','E'), // Restore Health - EFI_RESP = MKTAG('R','E','S','P'), // Restore Magicka + EFI_ABAT = fourCC("ABAT"), // Absorb Attribute + EFI_ABFA = fourCC("ABFA"), // Absorb Fatigue + EFI_ABHe = fourCC("ABHe"), // Absorb Health + EFI_ABSP = fourCC("ABSP"), // Absorb Magicka + EFI_ABSK = fourCC("ABSK"), // Absorb Skill + EFI_1400 = fourCC("1400"), // Cure Disease + EFI_CUPA = fourCC("CUPA"), // Cure Paralysis + EFI_CUPO = fourCC("CUPO"), // Cure Poison + EFI_FOAT = fourCC("FOAT"), // Fortify Attribute + EFI_FOFA = fourCC("FOFA"), // Fortify Fatigue + EFI_FOHE = fourCC("FOHE"), // Fortify Health + EFI_FOSP = fourCC("FOSP"), // Fortify Magicka + EFI_FOSK = fourCC("FOSK"), // Fortify Skill + EFI_RSDI = fourCC("RSDI"), // Resist Disease + EFI_RSFI = fourCC("RSFI"), // Resist Fire + EFI_RSFR = fourCC("RSFR"), // Resist Frost + EFI_RSMA = fourCC("RSMA"), // Resist Magic + EFI_RSNW = fourCC("RSNW"), // Resist Normal Weapons + EFI_RSPA = fourCC("RSPA"), // Resist Paralysis + EFI_RSPO = fourCC("RSPO"), // Resist Poison + EFI_RSSH = fourCC("RSSH"), // Resist Shock + EFI_REAT = fourCC("REAT"), // Restore Attribute + EFI_REFA = fourCC("REFA"), // Restore Fatigue + EFI_REHE = fourCC("REHE"), // Restore Health + EFI_RESP = fourCC("RESP"), // Restore Magicka // Effects - EFI_LOCK = MKTAG('L','O','C','K'), // Lock Lock - EFI_SEFF = MKTAG('S','E','F','F'), // Script Effect - EFI_Z020 = MKTAG('Z','0','2','0'), // Summon 20 Extra - EFI_MYHL = MKTAG('M','Y','H','L'), // Summon Mythic Dawn Helmet - EFI_MYTH = MKTAG('M','Y','T','H'), // Summon Mythic Dawn Armor - EFI_REAN = MKTAG('R','E','A','N'), // Reanimate - EFI_DISE = MKTAG('D','I','S','E'), // Disease Info - EFI_POSN = MKTAG('P','O','S','N'), // Poison Info - EFI_DUMY = MKTAG('D','U','M','Y'), // Mehrunes Dagon Custom Effect - EFI_STMA = MKTAG('S','T','M','A'), // Stunted Magicka - EFI_SUDG = MKTAG('S','U','D','G'), // Sun Damage - EFI_VAMP = MKTAG('V','A','M','P'), // Vampirism - EFI_DARK = MKTAG('D','A','R','K'), // Darkness - EFI_RSWD = MKTAG('R','S','W','D') // Resist Water Damage + EFI_LOCK = fourCC("LOCK"), // Lock Lock + EFI_SEFF = fourCC("SEFF"), // Script Effect + EFI_Z020 = fourCC("Z020"), // Summon 20 Extra + EFI_MYHL = fourCC("MYHL"), // Summon Mythic Dawn Helmet + EFI_MYTH = fourCC("MYTH"), // Summon Mythic Dawn Armor + EFI_REAN = fourCC("REAN"), // Reanimate + EFI_DISE = fourCC("DISE"), // Disease Info + EFI_POSN = fourCC("POSN"), // Poison Info + EFI_DUMY = fourCC("DUMY"), // Mehrunes Dagon Custom Effect + EFI_STMA = fourCC("STMA"), // Stunted Magicka + EFI_SUDG = fourCC("SUDG"), // Sun Damage + EFI_VAMP = fourCC("VAMP"), // Vampirism + EFI_DARK = fourCC("DARK"), // Darkness + EFI_RSWD = fourCC("RSWD") // Resist Water Damage }; // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Groups From d71a1efa9205a8a944b56c69227fda88855e5789 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Apr 2022 00:33:53 +0200 Subject: [PATCH 2339/2859] Rename components/esm4/acti.hpp -> components/esm4/loadacti.hpp --- apps/openmw_test_suite/esm4/includes.cpp | 2 +- components/esm4/loadacti.cpp | 2 +- components/esm4/{acti.hpp => loadacti.hpp} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename components/esm4/{acti.hpp => loadacti.hpp} (100%) diff --git a/apps/openmw_test_suite/esm4/includes.cpp b/apps/openmw_test_suite/esm4/includes.cpp index 8272571099..3c6be55230 100644 --- a/apps/openmw_test_suite/esm4/includes.cpp +++ b/apps/openmw_test_suite/esm4/includes.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -8,6 +7,7 @@ #include #include #include +#include #include #include #include diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp index cdfd6972fc..801cb9cdd8 100644 --- a/components/esm4/loadacti.cpp +++ b/components/esm4/loadacti.cpp @@ -24,7 +24,7 @@ trial & error. See http://en.uesp.net/wiki for details. */ -#include "acti.hpp" +#include "loadacti.hpp" #include #include // FIXME diff --git a/components/esm4/acti.hpp b/components/esm4/loadacti.hpp similarity index 100% rename from components/esm4/acti.hpp rename to components/esm4/loadacti.hpp From 4a49bc494167b12972f0fe398a412860822fdb57 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Apr 2022 00:32:08 +0200 Subject: [PATCH 2340/2859] Remove undefined constructor declaration --- components/esm4/loadalch.hpp | 1 - components/esm4/loadpack.hpp | 1 - 2 files changed, 2 deletions(-) diff --git a/components/esm4/loadalch.hpp b/components/esm4/loadalch.hpp index e6680dc93b..a3ff9ffe34 100644 --- a/components/esm4/loadalch.hpp +++ b/components/esm4/loadalch.hpp @@ -75,7 +75,6 @@ namespace ESM4 Data mData; EnchantedItem mItem; - Potion(); virtual ~Potion(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadpack.hpp b/components/esm4/loadpack.hpp index b7fe895941..849a51c017 100644 --- a/components/esm4/loadpack.hpp +++ b/components/esm4/loadpack.hpp @@ -97,7 +97,6 @@ namespace ESM4 PTDT mTarget; std::vector mConditions; - AIPackage(); virtual ~AIPackage(); virtual void load(ESM4::Reader& reader); From 5a1a987f6c99fe2aad73dc8e670c5ef7465d4d2d Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 23 Apr 2022 09:13:22 +0200 Subject: [PATCH 2341/2859] Remove write-only variable. --- apps/openmw/mwgui/mapwindow.cpp | 2 -- apps/openmw/mwgui/tooltips.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index b5f281d333..428b925920 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -530,12 +530,10 @@ namespace MWGui markerTexture = "textures\\detect_enchantment_icon.dds"; } - int counter = 0; for (const MWWorld::Ptr& ptr : markers) { const ESM::Position& worldPos = ptr.getRefData().getPosition(); MarkerUserData markerPos (mLocalMapRender); - ++counter; MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", getMarkerCoordinates(worldPos.pos[0], worldPos.pos[1], markerPos, 8), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 3a2bd65361..f806d3829a 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -160,13 +160,11 @@ namespace MWGui // try to go 1 level up until there is a widget that has tooltip // this is necessary because some skin elements are actually separate widgets - int i=0; while (!focus->isUserString("ToolTipType")) { focus = focus->getParent(); if (!focus) return; - ++i; } std::string type = focus->getUserString("ToolTipType"); From b88d32ff5b6d0da496d4b5a6df4d8ed4f49701a7 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 26 Apr 2020 15:31:39 +0200 Subject: [PATCH 2342/2859] Add 3 tabs in the "Data Files" page 1 with the data directories 2 with the BSA archives 3 with the content selector When user select a directory to be added, first we walk the directory hierarchy to make a list of all potential data= entries. If we find none, the selected directory is added. If more than one data directory is found, user is presented with a directory list to check which one(s) are to be added. Directories containing one or more content file are marked with an icon. data= and fallback-archive= lines are handled like content= lines: - they are part of the profile in launcher.cfg, prefixed by the profile name - they are updated in openmw.cfg when profile is selected / created Directories can be moved in the list by drag and drop or by buttons. Insertion is possible anywhere in the list. Global data path and data local are shown but are greyed out, as they are always included. No attempt is made to ensure that the user choice are valid (dependencies, overwrite of content). After a profile is loaded, any added content is highlighted in green. --- CHANGELOG.md | 1 + apps/launcher/CMakeLists.txt | 1 + apps/launcher/datafilespage.cpp | 326 ++++++++++++++++-- apps/launcher/datafilespage.hpp | 24 +- components/config/gamesettings.cpp | 36 +- components/config/gamesettings.hpp | 8 +- components/config/launchersettings.cpp | 61 +++- components/config/launchersettings.hpp | 14 +- .../contentselector/model/contentmodel.cpp | 44 ++- .../contentselector/model/contentmodel.hpp | 7 +- .../contentselector/view/contentselector.cpp | 9 +- .../contentselector/view/contentselector.hpp | 3 +- files/ui/datafilespage.ui | 135 +++++++- files/ui/directorypicker.ui | 47 +++ 14 files changed, 662 insertions(+), 54 deletions(-) create mode 100644 files/ui/directorypicker.ui diff --git a/CHANGELOG.md b/CHANGELOG.md index bfcce6018d..6f71991ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2766: Warn user if their version of Morrowind is not the latest. Feature #2780: A way to see current OpenMW version in the console + Feature #2858: Add a tab to the launcher for handling datafolders Feature #3245: Grid and angle snapping for the OpenMW-CS Feature #3616: Allow Zoom levels on the World Map Feature #4297: Implement APPLIED_ONCE flag for magic effects diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 7e8bd67e94..e3256519c3 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -44,6 +44,7 @@ set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui ${CMAKE_SOURCE_DIR}/files/ui/advancedpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/directorypicker.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 58a20c7853..9268c8e142 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -20,12 +23,34 @@ #include #include +#include #include +#include #include "utils/textinputdialog.hpp" +#include "utils/profilescombobox.hpp" + +#include "ui_directorypicker.h" const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; +namespace +{ + void contentSubdirs(const QString& path, QStringList& dirs) + { + QStringList fileFilter {"*.esm", "*.esp", "*.omwaddon", "*.bsa"}; + QStringList dirFilter {"bookart", "icons", "meshes", "music", "sound", "textures"}; + + QDir currentDir(path); + if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty() + || !currentDir.entryInfoList(dirFilter, QDir::Dirs | QDir::NoDotAndDotDot).empty()) + dirs.push_back(currentDir.absolutePath()); + + for (const auto& subdir : currentDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + contentSubdirs(subdir.absoluteFilePath(), dirs); + } +} + namespace Launcher { namespace @@ -114,6 +139,14 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: this, SLOT(updateNewProfileOkButton(QString))); connect(mCloneProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateCloneProfileOkButton(QString))); + connect(ui.directoryAddSubdirsButton, &QPushButton::released, this, [=]() { this->addSubdirectories(true); }); + connect(ui.directoryInsertButton, &QPushButton::released, this, [=]() { this->addSubdirectories(false); }); + connect(ui.directoryUpButton, &QPushButton::released, this, [=]() { this->moveDirectory(-1); }); + connect(ui.directoryDownButton, &QPushButton::released, this, [=]() { this->moveDirectory(1); }); + connect(ui.directoryRemoveButton, &QPushButton::released, this, [=]() { this->removeDirectory(); }); + connect(ui.archiveUpButton, &QPushButton::released, this, [=]() { this->moveArchive(-1); }); + connect(ui.archiveDownButton, &QPushButton::released, this, [=]() { this->moveArchive(1); }); + connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [=]() { this->sortDirectories(); }); buildView(); loadSettings(); @@ -128,13 +161,12 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: void Launcher::DataFilesPage::buildView() { - QToolButton * refreshButton = mSelector->refreshButton(); + QToolButton * refreshButton = mSelector->refreshButton(); //tool buttons ui.newProfileButton->setToolTip ("Create a new Content List"); ui.cloneProfileButton->setToolTip ("Clone the current Content List"); ui.deleteProfileButton->setToolTip ("Delete an existing Content List"); - refreshButton->setToolTip("Refresh Data Files"); //combo box ui.profilesComboBox->addItem(mDefaultContentListName); @@ -188,20 +220,94 @@ bool Launcher::DataFilesPage::loadSettings() void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) { - QStringList paths = mGameSettings.getDataDirs(); + mSelector->clearFiles(); + ui.archiveListWidget->clear(); + ui.directoryListWidget->clear(); - mDataLocal = mGameSettings.getDataLocal(); + QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName); + if (directories.isEmpty()) + directories = mGameSettings.getDataDirs(); + mDataLocal = mGameSettings.getDataLocal(); if (!mDataLocal.isEmpty()) - paths.insert(0, mDataLocal); + directories.insert(0, mDataLocal); - mSelector->clearFiles(); + const auto globalDataDir = QString(mGameSettings.getGlobalDataDir().c_str()); + if (!globalDataDir.isEmpty()) + directories.insert(0, globalDataDir); + + // add directories, archives and content files + directories.removeDuplicates(); + for (const auto& currentDir : directories) + { + // add new achives files presents in current directory + addArchivesFromDir(currentDir); + + // Display new content with green background + QColor background; + QString tooltip; + if (mNewDataDirs.contains(currentDir)) + { + tooltip += "Will be added to the current profile\n"; + background = Qt::green; + } + else + background = Qt::white; + + // add content files presents in current directory + mSelector->addFiles(currentDir, mNewDataDirs.contains(currentDir)); + + // add current directory to list + ui.directoryListWidget->addItem(currentDir); + auto row = ui.directoryListWidget->count() - 1; + auto* item = ui.directoryListWidget->item(row); + item->setBackground(background); + + // deactivate data-local and global data directory: they are always included + if (currentDir == mDataLocal || currentDir == globalDataDir) + { + auto flags = item->flags(); + item->setFlags(flags & ~(Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled|Qt::ItemIsEnabled)); + } - for (const QString &path : paths) - mSelector->addFiles(path); + // Add a "data file" icon if the directory contains a content file + if (mSelector->containsDataFiles(currentDir)) + { + item->setIcon(QIcon(":/images/openmw-plugin.png")); + tooltip += "Contains content file(s)"; + } + else + { + // Pad to correct vertical alignment + QPixmap pixmap(QSize(200, 200)); // Arbitrary big number, will be scaled down to widget size + pixmap.fill(background); + auto emptyIcon = QIcon(pixmap); + item->setIcon(emptyIcon); + } + item->setToolTip(tooltip); + } mSelector->sortFiles(); - PathIterator pathIterator(paths); + QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName); + if (selectedArchives.isEmpty()) + selectedArchives = mGameSettings.getArchiveList(); + + // sort and tick BSA according to profile + int row = 0; + for (const auto& archive : selectedArchives) + { + const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly); + if (match.isEmpty()) + continue; + const auto name = match[0]->text(); + const auto oldrow = ui.archiveListWidget->row(match[0]); + ui.archiveListWidget->takeItem(oldrow); + ui.archiveListWidget->insertItem(row, name); + ui.archiveListWidget->item(row)->setCheckState(Qt::Checked); + row++; + } + + PathIterator pathIterator(directories); mSelector->setProfileContent(filesInProfile(contentModelName, pathIterator)); } @@ -232,6 +338,9 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) if (profileName.isEmpty()) profileName = ui.profilesComboBox->currentText(); + //retrieve the data paths + auto dirList = selectedDirectoriesPaths(); + //retrieve the files selected for the profile ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); @@ -243,11 +352,36 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) { fileNames.append(item->fileName()); } - mLauncherSettings.setContentList(profileName, fileNames); - mGameSettings.setContentList(fileNames); + mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames); + mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames); } -QStringList Launcher::DataFilesPage::selectedFilePaths() +QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const +{ + QStringList dirList; + for (int i = 0; i < ui.directoryListWidget->count(); ++i) + { + if (ui.directoryListWidget->item(i)->background() != Qt::gray) + dirList.append(ui.directoryListWidget->item(i)->text()); + } + return dirList; +} + +QStringList Launcher::DataFilesPage::selectedArchivePaths(bool all) const +{ + QStringList archiveList; + for (int i = 0; i < ui.archiveListWidget->count(); ++i) + { + const auto* item = ui.archiveListWidget->item(i); + const auto archive = ui.archiveListWidget->item(i)->text(); + + if (all ||item->checkState() == Qt::Checked) + archiveList.append(item->text()); + } + return archiveList; +} + +QStringList Launcher::DataFilesPage::selectedFilePaths() const { //retrieve the files selected for the profile ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); @@ -255,15 +389,8 @@ QStringList Launcher::DataFilesPage::selectedFilePaths() for (const ContentSelectorModel::EsmFile *item : items) { QFile file(item->filePath()); - if(file.exists()) - { filePaths.append(item->filePath()); - } - else - { - slotRefreshButtonClicked(); - } } return filePaths; } @@ -307,8 +434,18 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current)); + mNewDataDirs.clear(); + mKnownArchives.clear(); populateFileViews(current); + // save list of "old" bsa to be able to display "new" bsa in a different colour + for (int i = 0; i < ui.archiveListWidget->count(); ++i) + { + auto* item = ui.archiveListWidget->item(i); + mKnownArchives.push_back(item->text()); + item->setBackground(Qt::white); + } + checkForDefaultProfile(); } @@ -397,7 +534,7 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered() if (profile.isEmpty()) return; - mLauncherSettings.setContentList(profile, selectedFilePaths()); + mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths()); addProfile(profile, true); } @@ -435,6 +572,155 @@ void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString &text) mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } +QString Launcher::DataFilesPage::selectDirectory() +{ + QFileDialog fileDialog(this); + fileDialog.setFileMode(QFileDialog::Directory); + fileDialog.setOptions(QFileDialog::Option::ShowDirsOnly | QFileDialog::Option::ReadOnly); + + if (fileDialog.exec() == QDialog::Rejected) + return {}; + + return fileDialog.selectedFiles()[0]; + +} + +void Launcher::DataFilesPage::addSubdirectories(bool append) +{ + int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow(); + + if (selectedRow == -1) + return; + + const auto rootDir = selectDirectory(); + if (rootDir.isEmpty()) + return; + + QStringList subdirs; + contentSubdirs(rootDir, subdirs); + + if (subdirs.empty()) + { + // we didn't find anything that looks like a content directory, add directory selected by user + if (ui.directoryListWidget->findItems(rootDir, Qt::MatchFixedString).isEmpty()) + { + ui.directoryListWidget->addItem(rootDir); + mNewDataDirs.push_back(rootDir); + refreshDataFilesView(); + } + return; + } + + QDialog dialog; + Ui::SelectSubdirs select; + + select.setupUi(&dialog); + + for (const auto& dir : subdirs) + { + if (!ui.directoryListWidget->findItems(dir, Qt::MatchFixedString).isEmpty()) + continue; + const auto lastRow = select.dirListWidget->count(); + select.dirListWidget->addItem(dir); + select.dirListWidget->item(lastRow)->setCheckState(Qt::Unchecked); + } + + dialog.show(); + + if (dialog.exec() == QDialog::Rejected) + return; + + for (int i = 0; i < select.dirListWidget->count(); ++i) + { + const auto* dir = select.dirListWidget->item(i); + if (dir->checkState() == Qt::Checked) + { + ui.directoryListWidget->insertItem(selectedRow++, dir->text()); + mNewDataDirs.push_back(dir->text()); + } + } + + refreshDataFilesView(); +} + +void Launcher::DataFilesPage::sortDirectories() +{ + // Ensure disabled entries (aka default directories) are always at the top. + for (auto i = 1; i < ui.directoryListWidget->count(); ++i) + { + if (!(ui.directoryListWidget->item(i)->flags() & Qt::ItemIsEnabled) && + (ui.directoryListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled)) + { + const auto item = ui.directoryListWidget->takeItem(i); + ui.directoryListWidget->insertItem(i - 1, item); + ui.directoryListWidget->setCurrentRow(i); + } + } +} + +void Launcher::DataFilesPage::moveDirectory(int step) +{ + int selectedRow = ui.directoryListWidget->currentRow(); + int newRow = selectedRow + step; + if (selectedRow == -1 || newRow < 0 || newRow > ui.directoryListWidget->count() - 1) + return; + + if (!(ui.directoryListWidget->item(newRow)->flags() & Qt::ItemIsEnabled)) + return; + + const auto item = ui.directoryListWidget->takeItem(selectedRow); + ui.directoryListWidget->insertItem(newRow, item); + ui.directoryListWidget->setCurrentRow(newRow); +} + +void Launcher::DataFilesPage::removeDirectory() +{ + for (const auto& path : ui.directoryListWidget->selectedItems()) + ui.directoryListWidget->takeItem(ui.directoryListWidget->row(path)); + refreshDataFilesView(); +} + +void Launcher::DataFilesPage::moveArchive(int step) +{ + int selectedRow = ui.archiveListWidget->currentRow(); + int newRow = selectedRow + step; + if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) + return; + + const auto* item = ui.archiveListWidget->takeItem(selectedRow); + + addArchive(item->text(), item->checkState(), newRow); + ui.archiveListWidget->setCurrentRow(newRow); +} + +void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row) +{ + if (row == -1) + row = ui.archiveListWidget->count(); + ui.archiveListWidget->insertItem(row, name); + ui.archiveListWidget->item(row)->setCheckState(selected); + if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? + ui.archiveListWidget->item(row)->setBackground(Qt::green); +} + +void Launcher::DataFilesPage::addArchivesFromDir(const QString& path) +{ + QDir dir(path, "*.bsa"); + + for (const auto& fileinfo : dir.entryInfoList()) + { + const auto absPath = fileinfo.absoluteFilePath(); + if (Bsa::CompressedBSAFile::detectVersion(absPath.toStdString()) == Bsa::BSAVER_UNKNOWN) + continue; + + const auto fileName = fileinfo.fileName(); + const auto currentList = selectedArchivePaths(true); + + if (!currentList.contains(fileName, Qt::CaseInsensitive)) + addArchive(fileName, Qt::Unchecked); + } +} + void Launcher::DataFilesPage::checkForDefaultProfile() { //don't allow deleting "Default" profile diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index e004ca7542..0a235209f3 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -43,12 +43,6 @@ namespace Launcher void saveSettings(const QString &profile = ""); bool loadSettings(); - /** - * Returns the file paths of all selected content files - * @return the file paths of all selected content files - */ - QStringList selectedFilePaths(); - signals: void signalProfileChanged (int index); void signalLoadedCellsChanged(QStringList selectedFiles); @@ -66,6 +60,11 @@ namespace Launcher void updateNewProfileOkButton(const QString &text); void updateCloneProfileOkButton(const QString &text); + void addSubdirectories(bool append); + void sortDirectories(); + void removeDirectory(); + void moveArchive(int step); + void moveDirectory(int step); void on_newProfileAction_triggered(); void on_cloneProfileAction_triggered(); @@ -103,10 +102,14 @@ namespace Launcher QString mPreviousProfile; QStringList previousSelectedFiles; QString mDataLocal; + QStringList mKnownArchives; + QStringList mNewDataDirs; Process::ProcessInvoker* mNavMeshToolInvoker; NavMeshToolProgress mNavMeshToolProgress; + void addArchive(const QString& name, Qt::CheckState selected, int row = -1); + void addArchivesFromDir(const QString& dir); void buildView(); void setProfile (int index, bool savePrevious); void setProfile (const QString &previous, const QString ¤t, bool savePrevious); @@ -118,6 +121,15 @@ namespace Launcher void reloadCells(QStringList selectedFiles); void refreshDataFilesView (); void updateNavMeshProgress(int minDataSize); + QString selectDirectory(); + + /** + * Returns the file paths of all selected content files + * @return the file paths of all selected content files + */ + QStringList selectedFilePaths() const; + QStringList selectedArchivePaths(bool all=false) const; + QStringList selectedDirectoriesPaths() const; class PathIterator { diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 2b4bce5faf..6253d53f45 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -7,7 +7,9 @@ #include +const char Config::GameSettings::sArchiveKey[] = "fallback-archive"; const char Config::GameSettings::sContentKey[] = "content"; +const char Config::GameSettings::sDirectoryKey[] = "data"; Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg) : mCfgMgr(cfg) @@ -63,6 +65,14 @@ void Config::GameSettings::validatePaths() } } +std::string Config::GameSettings::getGlobalDataDir() const +{ + // global data dir may not exists if OpenMW is not installed (ie if run from build directory) + if (boost::filesystem::exists(mCfgMgr.getGlobalDataPath())) + return boost::filesystem::canonical(mCfgMgr.getGlobalDataPath()).string(); + return {}; +} + QStringList Config::GameSettings::values(const QString &key, const QStringList &defaultValues) const { if (!mSettings.values(key).isEmpty()) @@ -475,13 +485,29 @@ bool Config::GameSettings::hasMaster() return result; } -void Config::GameSettings::setContentList(const QStringList& fileNames) +void Config::GameSettings::setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames) { - remove(sContentKey); - for (const QString& fileName : fileNames) + auto const reset = [this](const char* key, const QStringList& list) { - setMultiValue(sContentKey, fileName); - } + remove(key); + for (auto const& item : list) + setMultiValue(key, item); + }; + + reset(sDirectoryKey, dirNames); + reset(sArchiveKey, archiveNames); + reset(sContentKey, fileNames); +} + +QStringList Config::GameSettings::getDataDirs() const +{ + return Config::LauncherSettings::reverse(mDataDirs); +} + +QStringList Config::GameSettings::getArchiveList() const +{ + // QMap returns multiple rows in LIFO order, so need to reverse + return Config::LauncherSettings::reverse(values(sArchiveKey)); } QStringList Config::GameSettings::getContentList() const diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 263a151c93..d4191523ad 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -53,7 +53,8 @@ namespace Config mUserSettings.remove(key); } - inline QStringList getDataDirs() const { return mDataDirs; } + QStringList getDataDirs() const; + std::string getGlobalDataDir() const; inline void removeDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.removeAll(dir); } inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } @@ -70,7 +71,8 @@ namespace Config bool writeFile(QTextStream &stream); bool writeFileWithComments(QFile &file); - void setContentList(const QStringList& fileNames); + QStringList getArchiveList() const; + void setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames); QStringList getContentList() const; void clear(); @@ -85,7 +87,9 @@ namespace Config QStringList mDataDirs; QString mDataLocal; + static const char sArchiveKey[]; static const char sContentKey[]; + static const char sDirectoryKey[]; static bool isOrderedLine(const QString& line) ; }; diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 025bc43827..f0d9093993 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -7,9 +7,15 @@ #include +#include + +#include + const char Config::LauncherSettings::sCurrentContentListKey[] = "Profiles/currentprofile"; const char Config::LauncherSettings::sLauncherConfigFileName[] = "launcher.cfg"; const char Config::LauncherSettings::sContentListsSectionPrefix[] = "Profiles/"; +const char Config::LauncherSettings::sDirectoryListSuffix[] = "/data"; +const char Config::LauncherSettings::sArchiveListSuffix[] = "/fallback-archive"; const char Config::LauncherSettings::sContentListSuffix[] = "/content"; QStringList Config::LauncherSettings::subKeys(const QString &key) @@ -86,6 +92,16 @@ QStringList Config::LauncherSettings::getContentLists() return subKeys(QString(sContentListsSectionPrefix)); } +QString Config::LauncherSettings::makeDirectoryListKey(const QString& contentListName) +{ + return QString(sContentListsSectionPrefix) + contentListName + QString(sDirectoryListSuffix); +} + +QString Config::LauncherSettings::makeArchiveListKey(const QString& contentListName) +{ + return QString(sContentListsSectionPrefix) + contentListName + QString(sArchiveListSuffix); +} + QString Config::LauncherSettings::makeContentListKey(const QString& contentListName) { return QString(sContentListsSectionPrefix) + contentListName + QString(sContentListSuffix); @@ -94,18 +110,28 @@ QString Config::LauncherSettings::makeContentListKey(const QString& contentListN void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) { // obtain content list from game settings (if present) + QStringList dirs(gameSettings.getDataDirs()); + const QStringList archives(gameSettings.getArchiveList()); const QStringList files(gameSettings.getContentList()); // if openmw.cfg has no content, exit so we don't create an empty content list. - if (files.isEmpty()) + if (dirs.isEmpty() || files.isEmpty()) { return; } + // global and local data directories are not part of any profile + const auto globalDataDir = QString(gameSettings.getGlobalDataDir().c_str()); + const auto dataLocal = gameSettings.getDataLocal(); + dirs.removeAll(globalDataDir); + dirs.removeAll(dataLocal); + // if any existing profile in launcher matches the content list, make that profile the default for (const QString &listName : getContentLists()) { - if (isEqual(files, getContentListFiles(listName))) + if (isEqual(files, getContentListFiles(listName)) && + isEqual(archives, getArchiveList(listName)) && + isEqual(dirs, getDataDirectoryList(listName))) { setCurrentContentListName(listName); return; @@ -115,11 +141,13 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) // otherwise, add content list QString newContentListName(makeNewContentListName()); setCurrentContentListName(newContentListName); - setContentList(newContentListName, files); + setContentList(newContentListName, dirs, archives, files); } void Config::LauncherSettings::removeContentList(const QString &contentListName) { + remove(makeDirectoryListKey(contentListName)); + remove(makeArchiveListKey(contentListName)); remove(makeContentListKey(contentListName)); } @@ -129,14 +157,18 @@ void Config::LauncherSettings::setCurrentContentListName(const QString &contentL setValue(QString(sCurrentContentListKey), contentListName); } -void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& fileNames) +void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames) { - removeContentList(contentListName); - QString key = makeContentListKey(contentListName); - for (const QString& fileName : fileNames) + auto const assign = [this](const QString key, const QStringList& list) { - setMultiValue(key, fileName); - } + for (auto const& item : list) + setMultiValue(key, item); + }; + + removeContentList(contentListName); + assign(makeDirectoryListKey(contentListName), dirNames); + assign(makeArchiveListKey(contentListName), archiveNames); + assign(makeContentListKey(contentListName), fileNames); } QString Config::LauncherSettings::getCurrentContentListName() const @@ -144,6 +176,17 @@ QString Config::LauncherSettings::getCurrentContentListName() const return value(QString(sCurrentContentListKey)); } +QStringList Config::LauncherSettings::getDataDirectoryList(const QString& contentListName) const +{ + // QMap returns multiple rows in LIFO order, so need to reverse + return reverse(getSettings().values(makeDirectoryListKey(contentListName))); +} + +QStringList Config::LauncherSettings::getArchiveList(const QString& contentListName) const +{ + // QMap returns multiple rows in LIFO order, so need to reverse + return reverse(getSettings().values(makeArchiveListKey(contentListName))); +} QStringList Config::LauncherSettings::getContentListFiles(const QString& contentListName) const { // QMap returns multiple rows in LIFO order, so need to reverse diff --git a/components/config/launchersettings.hpp b/components/config/launchersettings.hpp index da492c85ce..06632423a8 100644 --- a/components/config/launchersettings.hpp +++ b/components/config/launchersettings.hpp @@ -18,7 +18,7 @@ namespace Config void setContentList(const GameSettings& gameSettings); /// Create a Content List (or replace if it already exists) - void setContentList(const QString& contentListName, const QStringList& fileNames); + void setContentList(const QString& contentListName, const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames); void removeContentList(const QString &contentListName); @@ -26,15 +26,23 @@ namespace Config QString getCurrentContentListName() const; + QStringList getDataDirectoryList(const QString& contentListName) const; + QStringList getArchiveList(const QString& contentListName) const; QStringList getContentListFiles(const QString& contentListName) const; /// \return new list that is reversed order of input static QStringList reverse(const QStringList& toReverse); static const char sLauncherConfigFileName[]; - + private: + /// \return key to use to get/set the files in the specified data Directory List + static QString makeDirectoryListKey(const QString& contentListName); + + /// \return key to use to get/set the files in the specified Archive List + static QString makeArchiveListKey(const QString& contentListName); + /// \return key to use to get/set the files in the specified Content List static QString makeContentListKey(const QString& contentListName); @@ -51,6 +59,8 @@ namespace Config /// section of launcher.cfg holding the Content Lists static const char sContentListsSectionPrefix[]; + static const char sDirectoryListSuffix[]; + static const char sArchiveListSuffix[]; static const char sContentListSuffix[]; }; } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index f7cedc83a4..f4760aaea6 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -160,6 +160,15 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int return isLoadOrderError(file) ? mWarningIcon : QVariant(); } + case Qt::BackgroundRole: + { + if (isNew(file->fileName())) + { + return QVariant(QColor(Qt::green)); + } + return QVariant(); + } + case Qt::EditRole: case Qt::DisplayRole: { @@ -413,7 +422,7 @@ void ContentSelectorModel::ContentModel::addFile(EsmFile *file) emit dataChanged (idx, idx); } -void ContentSelectorModel::ContentModel::addFiles(const QString &path) +void ContentSelectorModel::ContentModel::addFiles(const QString &path, bool newfiles) { QDir dir(path); QStringList filters; @@ -471,6 +480,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) // Put the file in the table addFile(file); + setNew(file->fileName(), newfiles); } catch(std::runtime_error &e) { // An error occurred while reading the .esp @@ -481,6 +491,16 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) } } +bool ContentSelectorModel::ContentModel::containsDataFiles(const QString &path) +{ + QDir dir(path); + QStringList filters; + filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; + dir.setNameFilters(filters); + + return dir.entryList().count() != 0; +} + void ContentSelectorModel::ContentModel::clearFiles() { const int filesCount = mFiles.count(); @@ -553,6 +573,28 @@ bool ContentSelectorModel::ContentModel::isEnabled (const QModelIndex& index) co return (flags(index) & Qt::ItemIsEnabled); } +bool ContentSelectorModel::ContentModel::isNew(const QString& filepath) const +{ + if (mNewFiles.contains(filepath)) + return mNewFiles[filepath]; + + return false; +} + +void ContentSelectorModel::ContentModel::setNew(const QString &filepath, bool isNew) +{ + if (filepath.isEmpty()) + return; + + const EsmFile *file = item(filepath); + + if (!file) + return; + + mNewFiles[filepath] = isNew; +} + + bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile *file) const { return mPluginsWithLoadOrderError.contains(file->filePath()); diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 4bbe73b427..9a3dddb1c7 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -43,8 +43,9 @@ namespace ContentSelectorModel QMimeData *mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - void addFiles(const QString &path); + void addFiles(const QString &path, bool newfiles); void sortFiles(); + bool containsDataFiles(const QString &path); void clearFiles(); QModelIndex indexFromItem(const EsmFile *item) const; @@ -56,6 +57,8 @@ namespace ContentSelectorModel bool isEnabled (const QModelIndex& index) const; bool isChecked(const QString &filepath) const; bool setCheckState(const QString &filepath, bool isChecked); + bool isNew(const QString &filepath) const; + void setNew(const QString &filepath, bool isChecked); void setContentList(const QStringList &fileList); ContentFileList checkedItems() const; void uncheckAll(); @@ -79,7 +82,9 @@ namespace ContentSelectorModel QString toolTip(const EsmFile *file) const; ContentFileList mFiles; + QStringList mArchives; QHash mCheckStates; + QHash mNewFiles; QSet mPluginsWithLoadOrderError; QString mEncoding; QIcon mWarningIcon; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index ef925148ab..441caa3b23 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -153,9 +153,9 @@ ContentSelectorModel::ContentFileList return mContentModel->checkedItems(); } -void ContentSelectorView::ContentSelector::addFiles(const QString &path) +void ContentSelectorView::ContentSelector::addFiles(const QString &path, bool newfiles) { - mContentModel->addFiles(path); + mContentModel->addFiles(path, newfiles); // add any game files to the combo box for (const QString& gameFileName : mContentModel->gameFiles()) @@ -178,6 +178,11 @@ void ContentSelectorView::ContentSelector::sortFiles() mContentModel->sortFiles(); } +bool ContentSelectorView::ContentSelector::containsDataFiles(const QString &path) +{ + return mContentModel->containsDataFiles(path); +} + void ContentSelectorView::ContentSelector::clearFiles() { mContentModel->clearFiles(); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index b40675bedc..75ca3e17b4 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -27,8 +27,9 @@ namespace ContentSelectorView QString currentFile() const; - void addFiles(const QString &path); + void addFiles(const QString &path, bool newfiles = false); void sortFiles(); + bool containsDataFiles(const QString &path); void clearFiles(); void setProfileContent (const QStringList &fileList); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index e942cd652b..dfcf02fced 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -17,16 +17,141 @@ - 0 + 2 - + - Data Files + Data Directories - - + + + + + QAbstractItemView::InternalMove + + + + + + + Scan directories for likely data directories and append them at the end of the list. + + + Append + + + + + + + Scan directories for likely data directories and insert them above the selected position + + + Insert Above + + + + + + + Move selected directory one position up + + + Move Up + + + + + + + Move selected directory one position down + + + Move Down + + + + + + + Remove selected directory + + + Remove + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-style:italic;">note: directories that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + + + + + + + + Archive Files + + + + + + QAbstractItemView::InternalMove + + + + + + + Move selected archive one position up + + + Move Up + + + + + + + Move selected archive one position down + + + Move Down + + + + + + + <html><head/><body><p><span style=" font-style:italic;">note: archives that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + + + + + + + + Content Files + + + + + + + <html><head/><body><p><span style=" font-style:italic;">note: content files that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + + + diff --git a/files/ui/directorypicker.ui b/files/ui/directorypicker.ui new file mode 100644 index 0000000000..6350bcd2d3 --- /dev/null +++ b/files/ui/directorypicker.ui @@ -0,0 +1,47 @@ + + + SelectSubdirs + + + + 0 + 0 + 800 + 500 + + + + Select directories you wish to add + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + confirmButton + accepted() + SelectSubdirs + accept() + + + confirmButton + rejected() + SelectSubdirs + reject() + + + From eae1e8708160cd26f279934162ef3214e21c6b34 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 23 Apr 2022 15:37:08 +0200 Subject: [PATCH 2343/2859] [Lua] Update openmw.storage --- apps/openmw/mwlua/luamanagerimp.cpp | 4 +- apps/openmw_test_suite/lua/test_storage.cpp | 26 ++-- components/lua/scriptscontainer.hpp | 14 ++- components/lua/storage.cpp | 125 +++++++++++--------- components/lua/storage.hpp | 44 ++++--- files/lua_api/openmw/storage.lua | 26 ++-- 6 files changed, 143 insertions(+), 96 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 80dfef16cd..602f32710f 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -277,8 +277,8 @@ namespace MWLua mPlayer.getRefData().setLuaScripts(nullptr); mPlayer = MWWorld::Ptr(); } - mGlobalStorage.clearTemporary(); - mPlayerStorage.clearTemporary(); + mGlobalStorage.clearTemporaryAndRemoveCallbacks(); + mPlayerStorage.clearTemporaryAndRemoveCallbacks(); } void LuaManager::setupPlayer(const MWWorld::Ptr& ptr) diff --git a/apps/openmw_test_suite/lua/test_storage.cpp b/apps/openmw_test_suite/lua/test_storage.cpp index a1c6af6e0a..c1d8cc4c4f 100644 --- a/apps/openmw_test_suite/lua/test_storage.cpp +++ b/apps/openmw_test_suite/lua/test_storage.cpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace @@ -19,15 +20,27 @@ namespace sol::state mLua; LuaUtil::LuaStorage::initLuaBindings(mLua); LuaUtil::LuaStorage storage(mLua); + + std::vector callbackCalls; + LuaUtil::Callback callback{ + sol::make_object(mLua, [&](const std::string& section, const sol::optional& key) + { + if (key) + callbackCalls.push_back(section + "_" + *key); + else + callbackCalls.push_back(section + "_*"); + }), + sol::table(mLua, sol::create) + }; + callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = "fakeId"; + mLua["mutable"] = storage.getMutableSection("test"); mLua["ro"] = storage.getReadOnlySection("test"); + mLua["ro"]["subscribe"](mLua["ro"], callback); mLua.safe_script("mutable:set('x', 5)"); EXPECT_EQ(get(mLua, "mutable:get('x')"), 5); EXPECT_EQ(get(mLua, "ro:get('x')"), 5); - EXPECT_FALSE(get(mLua, "mutable:wasChanged()")); - EXPECT_TRUE(get(mLua, "ro:wasChanged()")); - EXPECT_FALSE(get(mLua, "ro:wasChanged()")); EXPECT_THROW(mLua.safe_script("ro:set('y', 3)"), std::exception); @@ -42,9 +55,8 @@ namespace mLua.safe_script("mutable:reset({x=4, y=7})"); EXPECT_EQ(get(mLua, "ro:get('x')"), 4); EXPECT_EQ(get(mLua, "ro:get('y')"), 7); - EXPECT_FALSE(get(mLua, "mutable:wasChanged()")); - EXPECT_TRUE(get(mLua, "ro:wasChanged()")); - EXPECT_FALSE(get(mLua, "ro:wasChanged()")); + + EXPECT_THAT(callbackCalls, ::testing::ElementsAre("test_x", "test_*", "test_*")); } TEST(LuaUtilStorageTest, Table) @@ -81,7 +93,7 @@ namespace EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); EXPECT_EQ(get(mLua, "temporary:get('y')"), 2); - storage.clearTemporary(); + storage.clearTemporaryAndRemoveCallbacks(); mLua["permanent"] = storage.getMutableSection("permanent"); mLua["temporary"] = storage.getMutableSection("temporary"); EXPECT_EQ(get(mLua, "permanent:get('x')"), 1); diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index d968c13a45..e065c487c4 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -249,16 +249,28 @@ namespace LuaUtil sol::function mFunc; sol::table mHiddenData; // same object as Script::mHiddenData in ScriptsContainer + bool isValid() const { return mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil; } + template sol::object operator()(Args&&... args) const { - if (mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil) + if (isValid()) return LuaUtil::call(mFunc, std::forward(args)...); else Log(Debug::Debug) << "Ignored callback to the removed script " << mHiddenData.get(ScriptsContainer::sScriptDebugNameKey); return sol::nil; } + + template + void tryCall(Args&&... args) const + { + try { (*this)(std::forward(args)...); } + catch (std::exception& e) + { + Log(Debug::Error) << "Error in callback: " << e.what(); + } + } }; } diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index 91a339cb03..eff4578d00 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -8,9 +8,7 @@ namespace sol { template <> - struct is_automagical : std::false_type {}; - template <> - struct is_automagical : std::false_type {}; + struct is_automagical : std::false_type {}; } namespace LuaUtil @@ -38,19 +36,49 @@ namespace LuaUtil return sEmpty; } + void LuaStorage::Section::runCallbacks(sol::optional changedKey) + { + mStorage->mRunningCallbacks = true; + mCallbacks.erase(std::remove_if(mCallbacks.begin(), mCallbacks.end(), [&](const Callback& callback) + { + bool valid = callback.isValid(); + if (valid) + callback.tryCall(mSectionName, changedKey); + return !valid; + }), mCallbacks.end()); + mStorage->mRunningCallbacks = false; + } + void LuaStorage::Section::set(std::string_view key, const sol::object& value) { - mValues[std::string(key)] = Value(value); - mChangeCounter++; + if (mStorage->mRunningCallbacks) + throw std::runtime_error("Not allowed to change storage in storage handlers because it can lead to an infinite recursion"); + if (value != sol::nil) + mValues[std::string(key)] = Value(value); + else + { + auto it = mValues.find(key); + if (it != mValues.end()) + mValues.erase(it); + } if (mStorage->mListener) - (*mStorage->mListener)(mSectionName, key, value); + mStorage->mListener->valueChanged(mSectionName, key, value); + runCallbacks(key); } - bool LuaStorage::Section::wasChanged(int64_t& lastCheck) + void LuaStorage::Section::setAll(const sol::optional& values) { - bool res = lastCheck < mChangeCounter; - lastCheck = mChangeCounter; - return res; + if (mStorage->mRunningCallbacks) + throw std::runtime_error("Not allowed to change storage in storage handlers because it can lead to an infinite recursion"); + mValues.clear(); + if (values) + { + for (const auto& [k, v] : *values) + mValues[k.as()] = Value(v); + } + if (mStorage->mListener) + mStorage->mListener->sectionReplaced(mSectionName, values); + runCallbacks(sol::nullopt); } sol::table LuaStorage::Section::asTable() @@ -64,62 +92,53 @@ namespace LuaUtil void LuaStorage::initLuaBindings(lua_State* L) { sol::state_view lua(L); - sol::usertype roView = lua.new_usertype("ReadOnlySection"); - sol::usertype mutableView = lua.new_usertype("MutableSection"); - roView["get"] = [](sol::this_state s, SectionReadOnlyView& section, std::string_view key) + sol::usertype sview = lua.new_usertype("Section"); + sview["get"] = [](sol::this_state s, const SectionView& section, std::string_view key) { return section.mSection->get(key).getReadOnly(s); }; - roView["getCopy"] = [](sol::this_state s, SectionReadOnlyView& section, std::string_view key) + sview["getCopy"] = [](sol::this_state s, const SectionView& section, std::string_view key) { return section.mSection->get(key).getCopy(s); }; - roView["wasChanged"] = [](SectionReadOnlyView& section) { return section.mSection->wasChanged(section.mLastCheck); }; - roView["asTable"] = [](SectionReadOnlyView& section) { return section.mSection->asTable(); }; - mutableView["get"] = [](sol::this_state s, SectionMutableView& section, std::string_view key) + sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); }; + sview["subscribe"] = [](const SectionView& section, const Callback& callback) { - return section.mSection->get(key).getReadOnly(s); + std::vector& callbacks = section.mSection->mCallbacks; + if (!callbacks.empty() && callbacks.size() == callbacks.capacity()) + { + callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(), + [&](const Callback& c) { return !c.isValid(); }), + callbacks.end()); + } + callbacks.push_back(callback); }; - mutableView["getCopy"] = [](sol::this_state s, SectionMutableView& section, std::string_view key) + sview["reset"] = [](const SectionView& section, const sol::optional& newValues) { - return section.mSection->get(key).getCopy(s); + if (section.mReadOnly) + throw std::runtime_error("Access to storage is read only"); + section.mSection->setAll(newValues); }; - mutableView["wasChanged"] = [](SectionMutableView& section) { return section.mSection->wasChanged(section.mLastCheck); }; - mutableView["asTable"] = [](SectionMutableView& section) { return section.mSection->asTable(); }; - mutableView["reset"] = [](SectionMutableView& section, sol::optional newValues) + sview["removeOnExit"] = [](const SectionView& section) { - section.mSection->mValues.clear(); - if (newValues) - { - for (const auto& [k, v] : *newValues) - { - try - { - section.mSection->set(k.as(), v); - } - catch (std::exception& e) - { - Log(Debug::Error) << "LuaUtil::LuaStorage::Section::reset(table): " << e.what(); - } - } - } - section.mSection->mChangeCounter++; - section.mLastCheck = section.mSection->mChangeCounter; + if (section.mReadOnly) + throw std::runtime_error("Access to storage is read only"); + section.mSection->mPermanent = false; }; - mutableView["removeOnExit"] = [](SectionMutableView& section) { section.mSection->mPermanent = false; }; - mutableView["set"] = [](SectionMutableView& section, std::string_view key, const sol::object& value) + sview["set"] = [](const SectionView& section, std::string_view key, const sol::object& value) { - if (section.mLastCheck == section.mSection->mChangeCounter) - section.mLastCheck++; + if (section.mReadOnly) + throw std::runtime_error("Access to storage is read only"); section.mSection->set(key, value); }; } - void LuaStorage::clearTemporary() + void LuaStorage::clearTemporaryAndRemoveCallbacks() { auto it = mData.begin(); while (it != mData.end()) { + it->second->mCallbacks.clear(); if (!it->second->mPermanent) { it->second->mValues.clear(); @@ -157,7 +176,7 @@ namespace LuaUtil sol::table data(mLua, sol::create); for (const auto& [sectionName, section] : mData) { - if (section->mPermanent) + if (section->mPermanent && !section->mValues.empty()) data[sectionName] = section->asTable(); } std::string serializedData = serialize(data); @@ -178,23 +197,17 @@ namespace LuaUtil return newIt->second; } - sol::object LuaStorage::getReadOnlySection(std::string_view sectionName) - { - const std::shared_ptr
& section = getSection(sectionName); - return sol::make_object(mLua, SectionReadOnlyView{section, section->mChangeCounter}); - } - - sol::object LuaStorage::getMutableSection(std::string_view sectionName) + sol::object LuaStorage::getSection(std::string_view sectionName, bool readOnly) { const std::shared_ptr
& section = getSection(sectionName); - return sol::make_object(mLua, SectionMutableView{section, section->mChangeCounter}); + return sol::make_object(mLua, SectionView{section, readOnly}); } - sol::table LuaStorage::getAllSections() + sol::table LuaStorage::getAllSections(bool readOnly) { sol::table res(mLua, sol::create); for (const auto& [sectionName, _] : mData) - res[sectionName] = getMutableSection(sectionName); + res[sectionName] = getSection(sectionName, readOnly); return res; } diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index c23e417f0f..11ea91f039 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -4,6 +4,7 @@ #include #include +#include "scriptscontainer.hpp" #include "serialization.hpp" namespace LuaUtil @@ -16,18 +17,28 @@ namespace LuaUtil explicit LuaStorage(lua_State* lua) : mLua(lua) {} - void clearTemporary(); + void clearTemporaryAndRemoveCallbacks(); void load(const std::string& path); void save(const std::string& path) const; - sol::object getReadOnlySection(std::string_view sectionName); - sol::object getMutableSection(std::string_view sectionName); - sol::table getAllSections(); + sol::object getSection(std::string_view sectionName, bool readOnly); + sol::object getMutableSection(std::string_view sectionName) { return getSection(sectionName, false); } + sol::object getReadOnlySection(std::string_view sectionName) { return getSection(sectionName, true); } + sol::table getAllSections(bool readOnly = false); - void set(std::string_view section, std::string_view key, const sol::object& value) { getSection(section)->set(key, value); } + void setSingleValue(std::string_view section, std::string_view key, const sol::object& value) + { getSection(section)->set(key, value); } - using ListenerFn = std::function; - void setListener(ListenerFn fn) { mListener = std::move(fn); } + void setSectionValues(std::string_view section, const sol::optional& values) + { getSection(section)->setAll(values); } + + class Listener + { + public: + virtual void valueChanged(std::string_view section, std::string_view key, const sol::object& value) const = 0; + virtual void sectionReplaced(std::string_view section, const sol::optional& values) const = 0; + }; + void setListener(const Listener* listener) { mListener = listener; } private: class Value @@ -48,32 +59,29 @@ namespace LuaUtil explicit Section(LuaStorage* storage, std::string name) : mStorage(storage), mSectionName(std::move(name)) {} const Value& get(std::string_view key) const; void set(std::string_view key, const sol::object& value); - bool wasChanged(int64_t& lastCheck); + void setAll(const sol::optional& values); sol::table asTable(); + void runCallbacks(sol::optional changedKey); LuaStorage* mStorage; std::string mSectionName; std::map> mValues; + std::vector mCallbacks; bool mPermanent = true; - int64_t mChangeCounter = 0; static Value sEmpty; }; - struct SectionMutableView - { - std::shared_ptr
mSection = nullptr; - int64_t mLastCheck = 0; - }; - struct SectionReadOnlyView + struct SectionView { - std::shared_ptr
mSection = nullptr; - int64_t mLastCheck = 0; + std::shared_ptr
mSection; + bool mReadOnly; }; const std::shared_ptr
& getSection(std::string_view sectionName); lua_State* mLua; std::map> mData; - std::optional mListener; + const Listener* mListener = nullptr; + bool mRunningCallbacks = false; }; } diff --git a/files/lua_api/openmw/storage.lua b/files/lua_api/openmw/storage.lua index 5499eefb9c..353c1ca49c 100644 --- a/files/lua_api/openmw/storage.lua +++ b/files/lua_api/openmw/storage.lua @@ -6,14 +6,14 @@ -- local myModData = storage.globalSection('MyModExample') -- myModData:set("someVariable", 1.0) -- myModData:set("anotherVariable", { exampleStr='abc', exampleBool=true }) --- local function update() --- if myModCfg:checkChanged() then --- print('Data was changes by another script:') --- print('MyModExample.someVariable =', myModData:get('someVariable')) --- print('MyModExample.anotherVariable.exampleStr =', --- myModData:get('anotherVariable').exampleStr) +-- local async = require('openmw.async') +-- myModData:subscribe(async:callback(function(section, key) +-- if key then +-- print('Value is changed:', key, '=', myModData:get(key)) +-- else +-- print('All values are changed') -- end --- end +-- end)) --- -- Get a section of the global storage; can be used by any script, but only global scripts can change values. @@ -58,10 +58,12 @@ -- @param #string key --- --- Return `True` if any value in this section was changed by another script since the last `wasChanged`. --- @function [parent=#StorageSection] wasChanged +-- Subscribe to changes in this section. +-- First argument of the callback is the name of the section (so one callback can be used for different sections). +-- The second argument is the changed key (or `nil` if `reset` was used and all values were changed at the same time) +-- @function [parent=#StorageSection] subscribe -- @param self --- @return #boolean +-- @param openmw.async#Callback callback --- -- Copy all values and return them as a table. @@ -71,14 +73,14 @@ --- -- Remove all existing values and assign values from given (the arg is optional) table. --- Note: `section:reset()` removes all values, but not the section itself. Use `section:removeOnExit()` to remove the section completely. +-- This function can not be used for a global storage section from a local script. +-- Note: `section:reset()` removes the section. -- @function [parent=#StorageSection] reset -- @param self -- @param #table values (optional) New values --- -- Make the whole section temporary: will be removed on exit or when load a save. --- No section can be removed immediately because other scripts may use it at the moment. -- 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. From 0dcb1f5aac9677399f82aebc413ead001f6b35ac Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Apr 2022 19:00:47 +0200 Subject: [PATCH 2344/2859] Fix build on Windows Use wrapper header over Windows.h to undefine far and near in a single place. --- apps/opencs/editor.cpp | 2 +- apps/openmw/main.cpp | 5 +---- components/crashcatcher/windows_crashcatcher.hpp | 5 +---- components/crashcatcher/windows_crashmonitor.cpp | 4 +--- components/crashcatcher/windows_crashmonitor.hpp | 4 +--- components/crashcatcher/windows_crashshm.hpp | 4 +--- components/debug/debugging.cpp | 6 ++---- components/debug/debugging.hpp | 4 +--- components/files/lowlevelfile.hpp | 2 +- components/misc/thread.cpp | 5 +---- components/windows.hpp | 9 +++++++++ 11 files changed, 20 insertions(+), 30 deletions(-) create mode 100644 components/windows.hpp diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 07154f6d55..40defda4e2 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -15,7 +15,7 @@ #include "model/world/data.hpp" #ifdef _WIN32 -#include +#include #endif using namespace Fallback; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 456fe23b60..1dfa2a6494 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -9,10 +9,7 @@ #include "options.hpp" #if defined(_WIN32) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include +#include // makes __argc and __argv available on windows #include #endif diff --git a/components/crashcatcher/windows_crashcatcher.hpp b/components/crashcatcher/windows_crashcatcher.hpp index e1857271e9..1133afe69c 100644 --- a/components/crashcatcher/windows_crashcatcher.hpp +++ b/components/crashcatcher/windows_crashcatcher.hpp @@ -3,10 +3,7 @@ #include -#undef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#include - +#include #include namespace Crash diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index 50d3fc08a1..5688d0eeb8 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -1,8 +1,6 @@ #include "windows_crashmonitor.hpp" -#undef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#include +#include #include #include diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index eaf908bf66..d6a8dd7ac5 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -1,9 +1,7 @@ #ifndef WINDOWS_CRASHMONITOR_HPP #define WINDOWS_CRASHMONITOR_HPP -#undef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#include +#include #include #include diff --git a/components/crashcatcher/windows_crashshm.hpp b/components/crashcatcher/windows_crashshm.hpp index a474600f94..eba478e744 100644 --- a/components/crashcatcher/windows_crashshm.hpp +++ b/components/crashcatcher/windows_crashshm.hpp @@ -1,9 +1,7 @@ #ifndef WINDOWS_CRASHSHM_HPP #define WINDOWS_CRASHSHM_HPP -#undef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#include +#include namespace Crash { diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 7d94459136..e1bea70954 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -7,10 +7,8 @@ #include #ifdef _WIN32 -# include -# undef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# include +#include +#include #endif namespace Debug diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp index d0af6d8f25..3e6739c1e1 100644 --- a/components/debug/debugging.hpp +++ b/components/debug/debugging.hpp @@ -12,9 +12,7 @@ #include "debuglog.hpp" #if defined _WIN32 && defined _DEBUG -# undef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# include +#include #endif namespace Debug diff --git a/components/files/lowlevelfile.hpp b/components/files/lowlevelfile.hpp index b2634d8c76..47c2e44f15 100644 --- a/components/files/lowlevelfile.hpp +++ b/components/files/lowlevelfile.hpp @@ -19,7 +19,7 @@ #include #elif FILE_API == FILE_API_POSIX #elif FILE_API == FILE_API_WIN32 -#include +#include #else #error Unsupported File API #endif diff --git a/components/misc/thread.cpp b/components/misc/thread.cpp index c78bf17e5a..ca811fdd2f 100644 --- a/components/misc/thread.cpp +++ b/components/misc/thread.cpp @@ -25,10 +25,7 @@ namespace Misc #elif defined(WIN32) -#undef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN - -#include +#include namespace Misc { diff --git a/components/windows.hpp b/components/windows.hpp new file mode 100644 index 0000000000..003c7bcaae --- /dev/null +++ b/components/windows.hpp @@ -0,0 +1,9 @@ +#ifndef OPENMW_COMPONENTS_WINDOWS_H +#define OPENMW_COMPONENTS_WINDOWS_H + +#include + +#undef far +#undef near + +#endif From 0c2c47810cd23dafdb77e8ab956b80fc871c7c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Mon, 25 Apr 2022 12:35:00 +0300 Subject: [PATCH 2345/2859] Increase CI timeout for windows builds to 2h --- .gitlab-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5579fcc76b..1e81ffb481 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -414,7 +414,9 @@ variables: &tests-targets - MSVC2019_64_Ninja/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*/*/*.log - MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log - + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + timeout: 2h + Windows_Ninja_Engine_Release: extends: - .Windows_Ninja_Base @@ -539,6 +541,8 @@ Windows_Ninja_Tests_RelWithDebInfo: - MSVC2019_64/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*.log - MSVC2019_64/*/*/*/*/*/*/*/*.log + # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. + timeout: 2h Windows_MSBuild_Engine_Release: extends: From 43b2892cc3dda2b3bebfbbdc26c78349116b672d Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 14 Apr 2022 15:48:32 +0200 Subject: [PATCH 2346/2859] Move ESMData, reader and writer out of esmtool Arguments --- apps/esmtool/esmtool.cpp | 95 ++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a6178d692d..d6708fbfb4 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -51,10 +51,6 @@ struct Arguments std::vector types; std::string name; - - ESMData data; - ESM::ESMReader reader; - ESM::ESMWriter writer; }; bool parseOptions (int argc, char** argv, Arguments &info) @@ -186,11 +182,11 @@ bool parseOptions (int argc, char** argv, Arguments &info) } void printRaw(ESM::ESMReader &esm); -void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info); +void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data); -int load(Arguments& info); -int clone(Arguments& info); -int comp(Arguments& info); +int load(const Arguments& info, ESMData* data); +int clone(const Arguments& info); +int comp(const Arguments& info); int main(int argc, char**argv) { @@ -201,7 +197,7 @@ int main(int argc, char**argv) return 1; if (info.mode == "dump") - return load(info); + return load(info, nullptr); else if (info.mode == "clone") return clone(info); else if (info.mode == "comp") @@ -221,7 +217,7 @@ int main(int argc, char**argv) return 0; } -void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) +void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data) { bool quiet = (info.quiet_given || info.mode == "clone"); bool save = (info.mode == "clone"); @@ -241,9 +237,8 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) bool moved = false; while(cell.getNextRef(esm, ref, deleted, movedCellRef, moved)) { - if (save) { - info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); - } + if (data != nullptr && save) + data->mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); if(quiet) continue; @@ -310,9 +305,9 @@ void printRaw(ESM::ESMReader &esm) } } -int load(Arguments& info) +int load(const Arguments& info, ESMData* data) { - ESM::ESMReader& esm = info.reader; + ESM::ESMReader esm; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); @@ -340,9 +335,12 @@ int load(Arguments& info) esm.open(filename); - info.data.author = esm.getAuthor(); - info.data.description = esm.getDesc(); - info.data.masters = esm.getGameFiles(); + if (data != nullptr) + { + data->author = esm.getAuthor(); + data->description = esm.getDesc(); + data->masters = esm.getGameFiles(); + } if (!quiet) { @@ -387,12 +385,8 @@ int load(Arguments& info) // Is the user interested in this record type? bool interested = true; - if (!info.types.empty()) - { - std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), n.toStringView()); - if (match == info.types.end()) interested = false; - } + if (!info.types.empty() && std::find(info.types.begin(), info.types.end(), n.toStringView()) == info.types.end()) + interested = false; if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) interested = false; @@ -406,20 +400,22 @@ int load(Arguments& info) if (record->getType().toInt() == ESM::REC_CELL && loadCells && interested) { - loadCell(record->cast()->get(), esm, info); + loadCell(info, record->cast()->get(), esm, data); } - if (save) + if (data != nullptr) { - info.data.mRecords.push_back(std::move(record)); + if (save) + data->mRecords.push_back(std::move(record)); + ++data->mRecordStats[n.toInt()]; } - ++info.data.mRecordStats[n.toInt()]; } - - } catch(std::exception &e) { + } + catch (const std::exception &e) + { std::cout << "\nERROR:\n\n " << e.what() << std::endl; - - info.data.mRecords.clear(); + if (data != nullptr) + data->mRecords.clear(); return 1; } @@ -428,7 +424,7 @@ int load(Arguments& info) #include -int clone(Arguments& info) +int clone(const Arguments& info) { if (info.outname.empty()) { @@ -436,13 +432,14 @@ int clone(Arguments& info) return 1; } - if (load(info) != 0) + ESMData data; + if (load(info, &data) != 0) { std::cout << "Failed to load, aborting." << std::endl; return 1; } - size_t recordCount = info.data.mRecords.size(); + size_t recordCount = data.mRecords.size(); int digitCount = 1; // For a nicer output if (recordCount > 0) @@ -451,7 +448,7 @@ int clone(Arguments& info) std::cout << "Loaded " << recordCount << " records:\n\n"; int i = 0; - for (std::pair stat : info.data.mRecordStats) + for (std::pair stat : data.mRecordStats) { ESM::NAME name; name = stat.first; @@ -466,22 +463,22 @@ int clone(Arguments& info) std::cout << "\nSaving records to: " << info.outname << "...\n"; - ESM::ESMWriter& esm = info.writer; + ESM::ESMWriter esm; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); - esm.setAuthor(info.data.author); - esm.setDescription(info.data.description); - esm.setVersion(info.data.version); + esm.setAuthor(data.author); + esm.setDescription(data.description); + esm.setVersion(data.version); esm.setRecordCount (recordCount); - for (const ESM::Header::MasterData &master : info.data.masters) + for (const ESM::Header::MasterData &master : data.masters) esm.addMaster(master.name, master.size); std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary); esm.save(save); int saved = 0; - for (auto& record : info.data.mRecords) + for (auto& record : data.mRecords) { if (i <= 0) break; @@ -493,9 +490,9 @@ int clone(Arguments& info) record->save(esm); if (typeName.toInt() == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); - if (!info.data.mCellRefs[ptr].empty()) + if (!data.mCellRefs[ptr].empty()) { - for (std::pair &ref : info.data.mCellRefs[ptr]) + for (std::pair &ref : data.mCellRefs[ptr]) ref.first.save(esm, ref.second); } } @@ -518,7 +515,7 @@ int clone(Arguments& info) return 0; } -int comp(Arguments& info) +int comp(const Arguments& info) { if (info.filename.empty() || info.outname.empty()) { @@ -541,19 +538,21 @@ int comp(Arguments& info) fileOne.filename = info.filename; fileTwo.filename = info.outname; - if (load(fileOne) != 0) + ESMData dataOne; + if (load(fileOne, &dataOne) != 0) { std::cout << "Failed to load " << info.filename << ", aborting comparison." << std::endl; return 1; } - if (load(fileTwo) != 0) + ESMData dataTwo; + if (load(fileTwo, &dataTwo) != 0) { std::cout << "Failed to load " << info.outname << ", aborting comparison." << std::endl; return 1; } - if (fileOne.data.mRecords.size() != fileTwo.data.mRecords.size()) + if (dataOne.mRecords.size() != dataTwo.mRecords.size()) { std::cout << "Not equal, different amount of records." << std::endl; return 1; From c7ba4100e1278e7a5f77a3910d7b48f236783a75 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Apr 2022 18:50:37 +0200 Subject: [PATCH 2347/2859] Remove undefined and redundant ESM4 record type constructors --- components/esm4/loadaloc.hpp | 1 - components/esm4/loadgrup.hpp | 4 ---- components/esm4/loadhdpt.hpp | 1 - components/esm4/loadmstt.hpp | 1 - components/esm4/loadpgrd.hpp | 1 - components/esm4/loadpgre.hpp | 1 - components/esm4/loadpwat.hpp | 1 - 7 files changed, 10 deletions(-) diff --git a/components/esm4/loadaloc.hpp b/components/esm4/loadaloc.hpp index 1c2d17d11e..a58efd289e 100644 --- a/components/esm4/loadaloc.hpp +++ b/components/esm4/loadaloc.hpp @@ -75,7 +75,6 @@ namespace ESM4 std::uint32_t mDayStart; std::uint32_t mNightStart; - MediaLocationController(); virtual ~MediaLocationController(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadgrup.hpp b/components/esm4/loadgrup.hpp index 8eb53a0e58..4b9aa9547b 100644 --- a/components/esm4/loadgrup.hpp +++ b/components/esm4/loadgrup.hpp @@ -86,8 +86,6 @@ namespace ESM4 FormId mRoad; std::vector mCells; // FIXME should this be CellGroup* instead? - - WorldGroup() : mWorld(0), mRoad(0) {} }; // http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format/CELL @@ -150,8 +148,6 @@ namespace ESM4 // need to keep modindex and context for lazy loading (of all the files that contribute // to this group) - - CellGroup() : mCell(0), mLand(0), mPgrd(0) {} }; } diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp index 9b67b0fce8..7ec46a32bc 100644 --- a/components/esm4/loadhdpt.hpp +++ b/components/esm4/loadhdpt.hpp @@ -53,7 +53,6 @@ namespace ESM4 std::array mTriFile; FormId mBaseTexture; - HeadPart(); virtual ~HeadPart(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadmstt.hpp b/components/esm4/loadmstt.hpp index 77dc453d35..0b9873a91a 100644 --- a/components/esm4/loadmstt.hpp +++ b/components/esm4/loadmstt.hpp @@ -47,7 +47,6 @@ namespace ESM4 std::int8_t mData; FormId mLoopingSound; - MovableStatic(); virtual ~MovableStatic(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadpgrd.hpp b/components/esm4/loadpgrd.hpp index 9f522af5ca..74672a1f92 100644 --- a/components/esm4/loadpgrd.hpp +++ b/components/esm4/loadpgrd.hpp @@ -83,7 +83,6 @@ namespace ESM4 std::vector mForeign; std::vector mObjects; - Pathgrid(); virtual ~Pathgrid(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadpgre.hpp b/components/esm4/loadpgre.hpp index bb36acfc37..1929cd4c52 100644 --- a/components/esm4/loadpgre.hpp +++ b/components/esm4/loadpgre.hpp @@ -46,7 +46,6 @@ namespace ESM4 std::string mEditorId; - PlacedGrenade(); virtual ~PlacedGrenade(); virtual void load(ESM4::Reader& reader); diff --git a/components/esm4/loadpwat.hpp b/components/esm4/loadpwat.hpp index e1de91ab96..b2dc2c4e00 100644 --- a/components/esm4/loadpwat.hpp +++ b/components/esm4/loadpwat.hpp @@ -46,7 +46,6 @@ namespace ESM4 std::string mEditorId; - PlaceableWater(); virtual ~PlaceableWater(); virtual void load(ESM4::Reader& reader); From 13c970b37a2cd640ccc1ca380155e1c63568a494 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Apr 2022 18:51:30 +0200 Subject: [PATCH 2348/2859] Add const modifier to encoder type --- components/esm/reader.cpp | 2 +- components/esm/reader.hpp | 4 ++-- components/esm4/reader.hpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/esm/reader.cpp b/components/esm/reader.cpp index 519c547bb8..0e2e9a9b8a 100644 --- a/components/esm/reader.cpp +++ b/components/esm/reader.cpp @@ -38,7 +38,7 @@ namespace ESM } bool Reader::getStringImpl(std::string& str, std::size_t size, - std::istream& stream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull) + std::istream& stream, const ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull) { std::size_t newSize = size; diff --git a/components/esm/reader.hpp b/components/esm/reader.hpp index ffc416dc18..fc3845d87e 100644 --- a/components/esm/reader.hpp +++ b/components/esm/reader.hpp @@ -31,7 +31,7 @@ namespace ESM virtual inline bool hasMoreRecs() const = 0; - virtual inline void setEncoder(ToUTF8::StatelessUtf8Encoder* encoder) = 0; + virtual inline void setEncoder(const ToUTF8::StatelessUtf8Encoder* encoder) = 0; // used to check for dependencies e.g. CS::Editor::run() virtual inline const std::vector& getGameFiles() const = 0; @@ -53,7 +53,7 @@ namespace ESM protected: bool getStringImpl(std::string& str, std::size_t size, - std::istream& stream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false); + std::istream& stream, const ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false); }; } diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index 863121cf38..aaebeb335a 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -78,7 +78,7 @@ namespace ESM4 { ReaderContext mCtx; - ToUTF8::StatelessUtf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; std::size_t mFileSize; @@ -143,7 +143,7 @@ namespace ESM4 { inline bool isEsm4() const final { return true; } - inline void setEncoder(ToUTF8::StatelessUtf8Encoder* encoder) final { mEncoder = encoder; }; + inline void setEncoder(const ToUTF8::StatelessUtf8Encoder* encoder) final { mEncoder = encoder; }; const std::vector& getGameFiles() const final { return mHeader.mMaster; } From 2c9b6fffe51791ccece206f81c3a44973afab17c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Apr 2022 19:00:35 +0200 Subject: [PATCH 2349/2859] Remove preloading logic from ESM4::Cell This logic does not belong here. If client of ESM4::Cell needs to cache load results it can be done separately. --- components/esm4/loadcell.cpp | 32 ++++++++------------------------ components/esm4/loadcell.hpp | 5 ----- 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index 0b90a7cdbf..97b3603204 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -43,7 +43,14 @@ ESM4::Cell::~Cell() { } -void ESM4::Cell::init(ESM4::Reader& reader) +// TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) +// +// But, for external cells we may be scanning the whole record since we don't know if there is +// going to be an EDID subrecord. And the vast majority of cells are these kinds. +// +// So perhaps some testing needs to be done to see if scanning and skipping takes +// longer/shorter/same as loading the subrecords. +void ESM4::Cell::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); @@ -64,33 +71,10 @@ void ESM4::Cell::init(ESM4::Reader& reader) currCellGrid.grid.y = 0; reader.setCurrCellGrid(currCellGrid); // side effect: sets mCellGridValid true } -} - -// TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) -// -// But, for external cells we may be scanning the whole record since we don't know if there is -// going to be an EDID subrecord. And the vast majority of cells are these kinds. -// -// So perhaps some testing needs to be done to see if scanning and skipping takes -// longer/shorter/same as loading the subrecords. -bool ESM4::Cell::preload(ESM4::Reader& reader) -{ - if (!mPreloaded) - load(reader); - - mPreloaded = true; - return true; -} - -void ESM4::Cell::load(ESM4::Reader& reader) -{ - if (mPreloaded) - return; // WARN: we need to call setCurrCell (and maybe setCurrCellGrid?) again before loading // cell child groups if we are loading them after restoring the context // (may be easier to update the context before saving?) - init(reader); reader.setCurrCell(mFormId); // save for LAND (and other children) to access later std::uint32_t esmVer = reader.esmVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; diff --git a/components/esm4/loadcell.hpp b/components/esm4/loadcell.hpp index c2274fcbe9..3dff4fdf0a 100644 --- a/components/esm4/loadcell.hpp +++ b/components/esm4/loadcell.hpp @@ -93,11 +93,6 @@ namespace ESM4 virtual ~Cell(); - void init(ESM4::Reader& reader); // common setup for both preload() and load() - - bool mPreloaded; - bool preload(ESM4::Reader& reader); - virtual void load(ESM4::Reader& reader); //virtual void save(ESM4::Writer& writer) const; From 4b28d51d5eca8e75bf907b6fbcd1e510f9525c3e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Apr 2022 19:20:17 +0200 Subject: [PATCH 2350/2859] Remove virtual modifiers from ESM4 record functions There is no need to have virtual functions. --- components/esm4/loadachr.cpp | 4 ---- components/esm4/loadachr.hpp | 6 ++---- components/esm4/loadacre.cpp | 4 ---- components/esm4/loadacre.hpp | 6 ++---- components/esm4/loadacti.cpp | 4 ---- components/esm4/loadacti.hpp | 6 ++---- components/esm4/loadalch.cpp | 4 ---- components/esm4/loadalch.hpp | 6 ++---- components/esm4/loadaloc.cpp | 4 ---- components/esm4/loadaloc.hpp | 6 ++---- components/esm4/loadammo.cpp | 4 ---- components/esm4/loadammo.hpp | 6 ++---- components/esm4/loadanio.cpp | 4 ---- components/esm4/loadanio.hpp | 6 ++---- components/esm4/loadappa.cpp | 4 ---- components/esm4/loadappa.hpp | 6 ++---- components/esm4/loadarma.cpp | 4 ---- components/esm4/loadarma.hpp | 6 ++---- components/esm4/loadarmo.cpp | 4 ---- components/esm4/loadarmo.hpp | 6 ++---- components/esm4/loadaspc.cpp | 4 ---- components/esm4/loadaspc.hpp | 6 ++---- components/esm4/loadbook.cpp | 4 ---- components/esm4/loadbook.hpp | 6 ++---- components/esm4/loadbptd.cpp | 4 ---- components/esm4/loadbptd.hpp | 6 ++---- components/esm4/loadcell.cpp | 4 ---- components/esm4/loadcell.hpp | 6 ++---- components/esm4/loadclas.cpp | 4 ---- components/esm4/loadclas.hpp | 2 -- components/esm4/loadclfm.cpp | 4 ---- components/esm4/loadclfm.hpp | 6 ++---- components/esm4/loadclot.cpp | 4 ---- components/esm4/loadclot.hpp | 6 ++---- components/esm4/loadcont.cpp | 4 ---- components/esm4/loadcont.hpp | 6 ++---- components/esm4/loadcrea.cpp | 4 ---- components/esm4/loadcrea.hpp | 6 ++---- components/esm4/loaddial.cpp | 4 ---- components/esm4/loaddial.hpp | 6 ++---- components/esm4/loaddobj.cpp | 4 ---- components/esm4/loaddobj.hpp | 6 ++---- components/esm4/loaddoor.cpp | 4 ---- components/esm4/loaddoor.hpp | 6 ++---- components/esm4/loadeyes.cpp | 4 ---- components/esm4/loadeyes.hpp | 6 ++---- components/esm4/loadflor.cpp | 4 ---- components/esm4/loadflor.hpp | 6 ++---- components/esm4/loadflst.cpp | 4 ---- components/esm4/loadflst.hpp | 6 ++---- components/esm4/loadfurn.cpp | 4 ---- components/esm4/loadfurn.hpp | 6 ++---- components/esm4/loadglob.cpp | 4 ---- components/esm4/loadglob.hpp | 6 ++---- components/esm4/loadgras.cpp | 4 ---- components/esm4/loadgras.hpp | 6 ++---- components/esm4/loadhair.cpp | 4 ---- components/esm4/loadhair.hpp | 6 ++---- components/esm4/loadhdpt.cpp | 4 ---- components/esm4/loadhdpt.hpp | 6 ++---- components/esm4/loadidle.cpp | 4 ---- components/esm4/loadidle.hpp | 6 ++---- components/esm4/loadidlm.cpp | 4 ---- components/esm4/loadidlm.hpp | 6 ++---- components/esm4/loadimod.cpp | 4 ---- components/esm4/loadimod.hpp | 6 ++---- components/esm4/loadinfo.cpp | 4 ---- components/esm4/loadinfo.hpp | 6 ++---- components/esm4/loadingr.cpp | 4 ---- components/esm4/loadingr.hpp | 6 ++---- components/esm4/loadkeym.cpp | 4 ---- components/esm4/loadkeym.hpp | 6 ++---- components/esm4/loadland.cpp | 4 ---- components/esm4/loadland.hpp | 2 -- components/esm4/loadlgtm.cpp | 4 ---- components/esm4/loadlgtm.hpp | 6 ++---- components/esm4/loadligh.cpp | 4 ---- components/esm4/loadligh.hpp | 6 ++---- components/esm4/loadltex.cpp | 4 ---- components/esm4/loadltex.hpp | 6 ++---- components/esm4/loadlvlc.cpp | 4 ---- components/esm4/loadlvlc.hpp | 6 ++---- components/esm4/loadlvli.cpp | 4 ---- components/esm4/loadlvli.hpp | 6 ++---- components/esm4/loadlvln.cpp | 4 ---- components/esm4/loadlvln.hpp | 6 ++---- components/esm4/loadmato.cpp | 4 ---- components/esm4/loadmato.hpp | 6 ++---- components/esm4/loadmisc.cpp | 4 ---- components/esm4/loadmisc.hpp | 6 ++---- components/esm4/loadmset.cpp | 4 ---- components/esm4/loadmset.hpp | 6 ++---- components/esm4/loadmstt.cpp | 4 ---- components/esm4/loadmstt.hpp | 6 ++---- components/esm4/loadmusc.cpp | 4 ---- components/esm4/loadmusc.hpp | 6 ++---- components/esm4/loadnavi.cpp | 4 ---- components/esm4/loadnavi.hpp | 6 ++---- components/esm4/loadnavm.cpp | 4 ---- components/esm4/loadnavm.hpp | 6 ++---- components/esm4/loadnote.cpp | 4 ---- components/esm4/loadnote.hpp | 6 ++---- components/esm4/loadnpc.cpp | 4 ---- components/esm4/loadnpc.hpp | 6 ++---- components/esm4/loadotft.cpp | 4 ---- components/esm4/loadotft.hpp | 6 ++---- components/esm4/loadpack.cpp | 4 ---- components/esm4/loadpack.hpp | 6 ++---- components/esm4/loadpgrd.cpp | 4 ---- components/esm4/loadpgrd.hpp | 6 ++---- components/esm4/loadpgre.cpp | 4 ---- components/esm4/loadpgre.hpp | 6 ++---- components/esm4/loadpwat.cpp | 4 ---- components/esm4/loadpwat.hpp | 6 ++---- components/esm4/loadqust.cpp | 4 ---- components/esm4/loadqust.hpp | 6 ++---- components/esm4/loadrace.cpp | 4 ---- components/esm4/loadrace.hpp | 6 ++---- components/esm4/loadrefr.cpp | 4 ---- components/esm4/loadrefr.hpp | 6 ++---- components/esm4/loadregn.cpp | 4 ---- components/esm4/loadregn.hpp | 6 ++---- components/esm4/loadroad.cpp | 4 ---- components/esm4/loadroad.hpp | 6 ++---- components/esm4/loadsbsp.cpp | 4 ---- components/esm4/loadsbsp.hpp | 2 -- components/esm4/loadscol.cpp | 4 ---- components/esm4/loadscol.hpp | 6 ++---- components/esm4/loadscpt.cpp | 4 ---- components/esm4/loadscpt.hpp | 6 ++---- components/esm4/loadscrl.cpp | 4 ---- components/esm4/loadscrl.hpp | 6 ++---- components/esm4/loadsgst.cpp | 4 ---- components/esm4/loadsgst.hpp | 6 ++---- components/esm4/loadslgm.cpp | 4 ---- components/esm4/loadslgm.hpp | 6 ++---- components/esm4/loadsndr.cpp | 4 ---- components/esm4/loadsndr.hpp | 6 ++---- components/esm4/loadsoun.cpp | 4 ---- components/esm4/loadsoun.hpp | 6 ++---- components/esm4/loadstat.cpp | 4 ---- components/esm4/loadstat.hpp | 6 ++---- components/esm4/loadtact.cpp | 4 ---- components/esm4/loadtact.hpp | 6 ++---- components/esm4/loadterm.cpp | 4 ---- components/esm4/loadterm.hpp | 6 ++---- components/esm4/loadtree.cpp | 4 ---- components/esm4/loadtree.hpp | 6 ++---- components/esm4/loadtxst.cpp | 4 ---- components/esm4/loadtxst.hpp | 6 ++---- components/esm4/loadweap.cpp | 4 ---- components/esm4/loadweap.hpp | 6 ++---- components/esm4/loadwrld.cpp | 4 ---- components/esm4/loadwrld.hpp | 6 ++---- 154 files changed, 148 insertions(+), 610 deletions(-) diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp index e006588475..7a62b5cdbd 100644 --- a/components/esm4/loadachr.cpp +++ b/components/esm4/loadachr.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::ActorCharacter::~ActorCharacter() -{ -} - void ESM4::ActorCharacter::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp index 7f78aa32d1..89b1ad7c86 100644 --- a/components/esm4/loadachr.hpp +++ b/components/esm4/loadachr.hpp @@ -57,10 +57,8 @@ namespace ESM4 EnableParent mEsp; - virtual ~ActorCharacter(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadacre.cpp b/components/esm4/loadacre.cpp index 9eb0cc7e5f..33d402eb81 100644 --- a/components/esm4/loadacre.cpp +++ b/components/esm4/loadacre.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::ActorCreature::~ActorCreature() -{ -} - void ESM4::ActorCreature::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadacre.hpp b/components/esm4/loadacre.hpp index 0b436d762b..4be76a5a8a 100644 --- a/components/esm4/loadacre.hpp +++ b/components/esm4/loadacre.hpp @@ -54,10 +54,8 @@ namespace ESM4 EnableParent mEsp; - virtual ~ActorCreature(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp index 801cb9cdd8..8c64e0477c 100644 --- a/components/esm4/loadacti.cpp +++ b/components/esm4/loadacti.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Activator::~Activator() -{ -} - void ESM4::Activator::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadacti.hpp b/components/esm4/loadacti.hpp index f86a4aa370..7d5798c033 100644 --- a/components/esm4/loadacti.hpp +++ b/components/esm4/loadacti.hpp @@ -57,10 +57,8 @@ namespace ESM4 std::string mActivationPrompt; - virtual ~Activator(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadalch.cpp b/components/esm4/loadalch.cpp index 2c346d71b9..579bf309f0 100644 --- a/components/esm4/loadalch.cpp +++ b/components/esm4/loadalch.cpp @@ -33,10 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Potion::~Potion() -{ -} - void ESM4::Potion::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadalch.hpp b/components/esm4/loadalch.hpp index a3ff9ffe34..dbc8b80f82 100644 --- a/components/esm4/loadalch.hpp +++ b/components/esm4/loadalch.hpp @@ -75,10 +75,8 @@ namespace ESM4 Data mData; EnchantedItem mItem; - virtual ~Potion(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadaloc.cpp b/components/esm4/loadaloc.cpp index 9907e5908b..b14341f682 100644 --- a/components/esm4/loadaloc.cpp +++ b/components/esm4/loadaloc.cpp @@ -38,10 +38,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::MediaLocationController::~MediaLocationController() -{ -} - void ESM4::MediaLocationController::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadaloc.hpp b/components/esm4/loadaloc.hpp index a58efd289e..3c5ae39f55 100644 --- a/components/esm4/loadaloc.hpp +++ b/components/esm4/loadaloc.hpp @@ -75,10 +75,8 @@ namespace ESM4 std::uint32_t mDayStart; std::uint32_t mNightStart; - virtual ~MediaLocationController(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadammo.cpp b/components/esm4/loadammo.cpp index 3c30a7a73b..16cd7cab1c 100644 --- a/components/esm4/loadammo.cpp +++ b/components/esm4/loadammo.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Ammunition::~Ammunition() -{ -} - void ESM4::Ammunition::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadammo.hpp b/components/esm4/loadammo.hpp index c31d38c10c..b399245a1b 100644 --- a/components/esm4/loadammo.hpp +++ b/components/esm4/loadammo.hpp @@ -71,10 +71,8 @@ namespace ESM4 Data mData; - virtual ~Ammunition(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadanio.cpp b/components/esm4/loadanio.cpp index 15847fbbda..86441e36ee 100644 --- a/components/esm4/loadanio.cpp +++ b/components/esm4/loadanio.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::AnimObject::~AnimObject() -{ -} - void ESM4::AnimObject::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadanio.hpp b/components/esm4/loadanio.hpp index f7e85f613b..5e40b5f9d8 100644 --- a/components/esm4/loadanio.hpp +++ b/components/esm4/loadanio.hpp @@ -50,10 +50,8 @@ namespace ESM4 FormId mIdleAnim; // only in TES4 std::string mUnloadEvent; // only in TES5 - virtual ~AnimObject(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadappa.cpp b/components/esm4/loadappa.cpp index c0561322b0..a7814d99c4 100644 --- a/components/esm4/loadappa.cpp +++ b/components/esm4/loadappa.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Apparatus::~Apparatus() -{ -} - void ESM4::Apparatus::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadappa.hpp b/components/esm4/loadappa.hpp index f3b2966518..7e4e48ce5a 100644 --- a/components/esm4/loadappa.hpp +++ b/components/esm4/loadappa.hpp @@ -62,10 +62,8 @@ namespace ESM4 Data mData; - virtual ~Apparatus(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp index 6306845689..389a5b122c 100644 --- a/components/esm4/loadarma.cpp +++ b/components/esm4/loadarma.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::ArmorAddon::~ArmorAddon() -{ -} - void ESM4::ArmorAddon::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadarma.hpp b/components/esm4/loadarma.hpp index f7abb404a1..8bee9d0396 100644 --- a/components/esm4/loadarma.hpp +++ b/components/esm4/loadarma.hpp @@ -57,10 +57,8 @@ namespace ESM4 BodyTemplate mBodyTemplate; // TES5 - virtual ~ArmorAddon(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadarmo.cpp b/components/esm4/loadarmo.cpp index c6237f326d..b2b43e1111 100644 --- a/components/esm4/loadarmo.cpp +++ b/components/esm4/loadarmo.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Armor::~Armor() -{ -} - void ESM4::Armor::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadarmo.hpp b/components/esm4/loadarmo.hpp index b44ba1a973..e7f6fa5a7c 100644 --- a/components/esm4/loadarmo.hpp +++ b/components/esm4/loadarmo.hpp @@ -183,10 +183,8 @@ namespace ESM4 std::vector mAddOns; // TES5 ARMA Data mData; - virtual ~Armor(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadaspc.cpp b/components/esm4/loadaspc.cpp index 8fb9d70c71..b1e856056a 100644 --- a/components/esm4/loadaspc.cpp +++ b/components/esm4/loadaspc.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::AcousticSpace::~AcousticSpace() -{ -} - void ESM4::AcousticSpace::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadaspc.hpp b/components/esm4/loadaspc.hpp index 179297a604..9fd17ea27c 100644 --- a/components/esm4/loadaspc.hpp +++ b/components/esm4/loadaspc.hpp @@ -53,10 +53,8 @@ namespace ESM4 std::uint32_t mIsInterior; // if true only use mAmbientLoopSounds[0] - virtual ~AcousticSpace(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadbook.cpp b/components/esm4/loadbook.cpp index 939888fc43..c17ef102be 100644 --- a/components/esm4/loadbook.cpp +++ b/components/esm4/loadbook.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Book::~Book() -{ -} - void ESM4::Book::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadbook.hpp b/components/esm4/loadbook.hpp index 9cb5358bdb..a1176a3320 100644 --- a/components/esm4/loadbook.hpp +++ b/components/esm4/loadbook.hpp @@ -101,10 +101,8 @@ namespace ESM4 FormId mPickUpSound; FormId mDropSound; - virtual ~Book(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadbptd.cpp b/components/esm4/loadbptd.cpp index a65d2dd482..33229edd07 100644 --- a/components/esm4/loadbptd.cpp +++ b/components/esm4/loadbptd.cpp @@ -43,10 +43,6 @@ void ESM4::BodyPartData::BodyPart::clear() mGoreEffectsTarget.clear(); } -ESM4::BodyPartData::~BodyPartData() -{ -} - void ESM4::BodyPartData::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadbptd.hpp b/components/esm4/loadbptd.hpp index 0b971ca083..f831a53653 100644 --- a/components/esm4/loadbptd.hpp +++ b/components/esm4/loadbptd.hpp @@ -115,10 +115,8 @@ namespace ESM4 std::vector mBodyParts; - virtual ~BodyPartData(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index 97b3603204..1bd7cb783a 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -39,10 +39,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Cell::~Cell() -{ -} - // TODO: Try loading only EDID and XCLC (along with mFormId, mFlags and mParent) // // But, for external cells we may be scanning the whole record since we don't know if there is diff --git a/components/esm4/loadcell.hpp b/components/esm4/loadcell.hpp index 3dff4fdf0a..ff51c64f00 100644 --- a/components/esm4/loadcell.hpp +++ b/components/esm4/loadcell.hpp @@ -91,10 +91,8 @@ namespace ESM4 CellGroup *mCellGroup; - virtual ~Cell(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; void blank(); }; diff --git a/components/esm4/loadclas.cpp b/components/esm4/loadclas.cpp index 80c9fc2b0f..eca978276b 100644 --- a/components/esm4/loadclas.cpp +++ b/components/esm4/loadclas.cpp @@ -36,10 +36,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Class::~Class() -{ -} - void ESM4::Class::load(ESM4::Reader& reader) { //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? diff --git a/components/esm4/loadclas.hpp b/components/esm4/loadclas.hpp index c68a1c1ae5..bf091fa7f4 100644 --- a/components/esm4/loadclas.hpp +++ b/components/esm4/loadclas.hpp @@ -53,8 +53,6 @@ namespace ESM4 std::string mIcon; Data mData; - ~Class(); - void load(ESM4::Reader& reader); //void save(ESM4::Writer& reader) const; diff --git a/components/esm4/loadclfm.cpp b/components/esm4/loadclfm.cpp index 7ad9f56615..efaf1461e4 100644 --- a/components/esm4/loadclfm.cpp +++ b/components/esm4/loadclfm.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Colour::~Colour() -{ -} - void ESM4::Colour::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadclfm.hpp b/components/esm4/loadclfm.hpp index 35d4a28686..4fb81c6e00 100644 --- a/components/esm4/loadclfm.hpp +++ b/components/esm4/loadclfm.hpp @@ -57,10 +57,8 @@ namespace ESM4 ColourRGB mColour; std::uint32_t mPlayable; - virtual ~Colour(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadclot.cpp b/components/esm4/loadclot.cpp index 8f43cf2ac6..cdcc692caa 100644 --- a/components/esm4/loadclot.cpp +++ b/components/esm4/loadclot.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Clothing::~Clothing() -{ -} - void ESM4::Clothing::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadclot.hpp b/components/esm4/loadclot.hpp index 8870c58a45..16c8a145a0 100644 --- a/components/esm4/loadclot.hpp +++ b/components/esm4/loadclot.hpp @@ -70,10 +70,8 @@ namespace ESM4 Data mData; - virtual ~Clothing(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp index 6ddb6c17f9..f0c7309967 100644 --- a/components/esm4/loadcont.cpp +++ b/components/esm4/loadcont.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Container::~Container() -{ -} - void ESM4::Container::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadcont.hpp b/components/esm4/loadcont.hpp index 7218da9729..e7b47e8944 100644 --- a/components/esm4/loadcont.hpp +++ b/components/esm4/loadcont.hpp @@ -58,10 +58,8 @@ namespace ESM4 std::vector mInventory; - virtual ~Container(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp index a78aadd82d..3da8a0c114 100644 --- a/components/esm4/loadcrea.cpp +++ b/components/esm4/loadcrea.cpp @@ -40,10 +40,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Creature::~Creature() -{ -} - void ESM4::Creature::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadcrea.hpp b/components/esm4/loadcrea.hpp index 8062a670d0..05e6840312 100644 --- a/components/esm4/loadcrea.hpp +++ b/components/esm4/loadcrea.hpp @@ -138,10 +138,8 @@ namespace ESM4 FormId mBaseTemplate; // FO3/FONV std::vector mBodyParts; // FO3/FONV - virtual ~Creature(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loaddial.cpp b/components/esm4/loaddial.cpp index 28176e7c52..342828a9d6 100644 --- a/components/esm4/loaddial.cpp +++ b/components/esm4/loaddial.cpp @@ -33,10 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Dialogue::~Dialogue() -{ -} - void ESM4::Dialogue::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loaddial.hpp b/components/esm4/loaddial.hpp index 6463da4f19..8097c94baf 100644 --- a/components/esm4/loaddial.hpp +++ b/components/esm4/loaddial.hpp @@ -57,10 +57,8 @@ namespace ESM4 float mPriority; - virtual ~Dialogue(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loaddobj.cpp b/components/esm4/loaddobj.cpp index b81e29c9fc..6d791ff044 100644 --- a/components/esm4/loaddobj.cpp +++ b/components/esm4/loaddobj.cpp @@ -37,10 +37,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::DefaultObj::~DefaultObj() -{ -} - void ESM4::DefaultObj::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loaddobj.hpp b/components/esm4/loaddobj.hpp index b9557bbb05..c959a62e50 100644 --- a/components/esm4/loaddobj.hpp +++ b/components/esm4/loaddobj.hpp @@ -87,10 +87,8 @@ namespace ESM4 Defaults mData; - virtual ~DefaultObj(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loaddoor.cpp b/components/esm4/loaddoor.cpp index 7af92fa870..555c7a7d3f 100644 --- a/components/esm4/loaddoor.cpp +++ b/components/esm4/loaddoor.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Door::~Door() -{ -} - void ESM4::Door::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loaddoor.hpp b/components/esm4/loaddoor.hpp index 71e7ae70ac..9810a45989 100644 --- a/components/esm4/loaddoor.hpp +++ b/components/esm4/loaddoor.hpp @@ -63,10 +63,8 @@ namespace ESM4 FormId mLoopSound; FormId mRandomTeleport; - virtual ~Door(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadeyes.cpp b/components/esm4/loadeyes.cpp index d4f4fc4bbb..f91de07926 100644 --- a/components/esm4/loadeyes.cpp +++ b/components/esm4/loadeyes.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Eyes::~Eyes() -{ -} - void ESM4::Eyes::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadeyes.hpp b/components/esm4/loadeyes.hpp index dfc5d5e983..5f0bb0f31b 100644 --- a/components/esm4/loadeyes.hpp +++ b/components/esm4/loadeyes.hpp @@ -55,10 +55,8 @@ namespace ESM4 Data mData; - virtual ~Eyes(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadflor.cpp b/components/esm4/loadflor.cpp index fcc7fa05c1..ae9770ab74 100644 --- a/components/esm4/loadflor.cpp +++ b/components/esm4/loadflor.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Flora::~Flora() -{ -} - void ESM4::Flora::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadflor.hpp b/components/esm4/loadflor.hpp index f4961adf9c..d454108233 100644 --- a/components/esm4/loadflor.hpp +++ b/components/esm4/loadflor.hpp @@ -65,10 +65,8 @@ namespace ESM4 FormId mSound; Production mPercentHarvest; - virtual ~Flora(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadflst.cpp b/components/esm4/loadflst.cpp index f73f883148..32de2fe813 100644 --- a/components/esm4/loadflst.cpp +++ b/components/esm4/loadflst.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::FormIdList::~FormIdList() -{ -} - void ESM4::FormIdList::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadflst.hpp b/components/esm4/loadflst.hpp index c7e098b9ba..5a3d048fd4 100644 --- a/components/esm4/loadflst.hpp +++ b/components/esm4/loadflst.hpp @@ -47,10 +47,8 @@ namespace ESM4 std::vector mObjects; - virtual ~FormIdList(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp index 046f105451..0b27f50194 100644 --- a/components/esm4/loadfurn.cpp +++ b/components/esm4/loadfurn.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Furniture::~Furniture() -{ -} - void ESM4::Furniture::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadfurn.hpp b/components/esm4/loadfurn.hpp index c04f78f462..051d2b03d6 100644 --- a/components/esm4/loadfurn.hpp +++ b/components/esm4/loadfurn.hpp @@ -51,10 +51,8 @@ namespace ESM4 FormId mScriptId; std::uint32_t mActiveMarkerFlags; - virtual ~Furniture(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp index 3435726d76..739aab5b82 100644 --- a/components/esm4/loadglob.cpp +++ b/components/esm4/loadglob.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::GlobalVariable::~GlobalVariable() -{ -} - void ESM4::GlobalVariable::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadglob.hpp b/components/esm4/loadglob.hpp index 59e2c1fbd0..abe3a8ac93 100644 --- a/components/esm4/loadglob.hpp +++ b/components/esm4/loadglob.hpp @@ -47,10 +47,8 @@ namespace ESM4 std::uint8_t mType; float mValue; - virtual ~GlobalVariable(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadgras.cpp b/components/esm4/loadgras.cpp index 81be002bc9..8c4699b25b 100644 --- a/components/esm4/loadgras.cpp +++ b/components/esm4/loadgras.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Grass::~Grass() -{ -} - void ESM4::Grass::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadgras.hpp b/components/esm4/loadgras.hpp index f142779637..e5b9cb18ff 100644 --- a/components/esm4/loadgras.hpp +++ b/components/esm4/loadgras.hpp @@ -85,10 +85,8 @@ namespace ESM4 Data mData; - virtual ~Grass(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadhair.cpp b/components/esm4/loadhair.cpp index f558a46521..64d0b2192e 100644 --- a/components/esm4/loadhair.cpp +++ b/components/esm4/loadhair.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Hair::~Hair() -{ -} - void ESM4::Hair::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadhair.hpp b/components/esm4/loadhair.hpp index 22ae6ef605..b0b07f9281 100644 --- a/components/esm4/loadhair.hpp +++ b/components/esm4/loadhair.hpp @@ -58,10 +58,8 @@ namespace ESM4 Data mData; - virtual ~Hair(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index ce579f5371..9e5d4c9ffd 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::HeadPart::~HeadPart() -{ -} - void ESM4::HeadPart::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp index 7ec46a32bc..89a486eba8 100644 --- a/components/esm4/loadhdpt.hpp +++ b/components/esm4/loadhdpt.hpp @@ -53,10 +53,8 @@ namespace ESM4 std::array mTriFile; FormId mBaseTexture; - virtual ~HeadPart(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadidle.cpp b/components/esm4/loadidle.cpp index 16bcfcf9dc..df84e3df4c 100644 --- a/components/esm4/loadidle.cpp +++ b/components/esm4/loadidle.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::IdleAnimation::~IdleAnimation() -{ -} - void ESM4::IdleAnimation::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadidle.hpp b/components/esm4/loadidle.hpp index d41d715906..ad4e796359 100644 --- a/components/esm4/loadidle.hpp +++ b/components/esm4/loadidle.hpp @@ -49,10 +49,8 @@ namespace ESM4 FormId mParent; // IDLE or AACT FormId mPrevious; - virtual ~IdleAnimation(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadidlm.cpp b/components/esm4/loadidlm.cpp index 0aada6a3a3..c94cd37310 100644 --- a/components/esm4/loadidlm.cpp +++ b/components/esm4/loadidlm.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::IdleMarker::~IdleMarker() -{ -} - void ESM4::IdleMarker::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadidlm.hpp b/components/esm4/loadidlm.hpp index 717aade9c3..2874426217 100644 --- a/components/esm4/loadidlm.hpp +++ b/components/esm4/loadidlm.hpp @@ -50,10 +50,8 @@ namespace ESM4 float mIdleTimer; std::vector mIdleAnim; - virtual ~IdleMarker(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp index 7be99fbae0..4ec6518a01 100644 --- a/components/esm4/loadimod.cpp +++ b/components/esm4/loadimod.cpp @@ -34,10 +34,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::ItemMod::~ItemMod() -{ -} - void ESM4::ItemMod::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadimod.hpp b/components/esm4/loadimod.hpp index 2fee4f3156..5e6078384e 100644 --- a/components/esm4/loadimod.hpp +++ b/components/esm4/loadimod.hpp @@ -46,10 +46,8 @@ namespace ESM4 std::string mEditorId; - virtual ~ItemMod(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index d72051e8a7..a0a99b13dc 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -33,10 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::DialogInfo::~DialogInfo() -{ -} - void ESM4::DialogInfo::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadinfo.hpp b/components/esm4/loadinfo.hpp index be7e77efc4..2decff05e0 100644 --- a/components/esm4/loadinfo.hpp +++ b/components/esm4/loadinfo.hpp @@ -77,10 +77,8 @@ namespace ESM4 ScriptDefinition mScript; // FIXME: ignoring the second one after the NEXT sub-record - virtual ~DialogInfo(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadingr.cpp b/components/esm4/loadingr.cpp index 38f38463d0..9001329662 100644 --- a/components/esm4/loadingr.cpp +++ b/components/esm4/loadingr.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Ingredient::~Ingredient() -{ -} - void ESM4::Ingredient::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadingr.hpp b/components/esm4/loadingr.hpp index c011ff54cf..08b3e73efc 100644 --- a/components/esm4/loadingr.hpp +++ b/components/esm4/loadingr.hpp @@ -70,10 +70,8 @@ namespace ESM4 Data mData; - virtual ~Ingredient(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadkeym.cpp b/components/esm4/loadkeym.cpp index 85d348465f..f0c5dbedb7 100644 --- a/components/esm4/loadkeym.cpp +++ b/components/esm4/loadkeym.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Key::~Key() -{ -} - void ESM4::Key::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadkeym.hpp b/components/esm4/loadkeym.hpp index 6a31395618..68835eeeb6 100644 --- a/components/esm4/loadkeym.hpp +++ b/components/esm4/loadkeym.hpp @@ -64,10 +64,8 @@ namespace ESM4 Data mData; - virtual ~Key(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index aadc2f3aaf..651a285a72 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -38,10 +38,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Land::~Land() -{ -} - // overlap north // // 32 diff --git a/components/esm4/loadland.hpp b/components/esm4/loadland.hpp index 80a2490855..305b48c157 100644 --- a/components/esm4/loadland.hpp +++ b/components/esm4/loadland.hpp @@ -123,8 +123,6 @@ namespace ESM4 Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right std::vector mIds; // land texture (LTEX) formids - virtual ~Land(); - virtual void load(Reader& reader); //virtual void save(Writer& writer) const; diff --git a/components/esm4/loadlgtm.cpp b/components/esm4/loadlgtm.cpp index 74486c3615..d0aa3a5b49 100644 --- a/components/esm4/loadlgtm.cpp +++ b/components/esm4/loadlgtm.cpp @@ -35,10 +35,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LightingTemplate::~LightingTemplate() -{ -} - void ESM4::LightingTemplate::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadlgtm.hpp b/components/esm4/loadlgtm.hpp index 9e54bf53d9..1b625e458f 100644 --- a/components/esm4/loadlgtm.hpp +++ b/components/esm4/loadlgtm.hpp @@ -50,10 +50,8 @@ namespace ESM4 Lighting mLighting; - virtual ~LightingTemplate(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadligh.cpp b/components/esm4/loadligh.cpp index c4f0a94d51..394d10114d 100644 --- a/components/esm4/loadligh.cpp +++ b/components/esm4/loadligh.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Light::~Light() -{ -} - void ESM4::Light::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadligh.hpp b/components/esm4/loadligh.hpp index b1c642eb27..974385dbf8 100644 --- a/components/esm4/loadligh.hpp +++ b/components/esm4/loadligh.hpp @@ -84,10 +84,8 @@ namespace ESM4 Data mData; - virtual ~Light(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp index 7b442ef8df..6edcd4ae05 100644 --- a/components/esm4/loadltex.cpp +++ b/components/esm4/loadltex.cpp @@ -37,10 +37,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LandTexture::~LandTexture() -{ -} - void ESM4::LandTexture::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadltex.hpp b/components/esm4/loadltex.hpp index 6347df2827..e445f54525 100644 --- a/components/esm4/loadltex.hpp +++ b/components/esm4/loadltex.hpp @@ -62,10 +62,8 @@ namespace ESM4 // ---------------------- - virtual ~LandTexture(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp index cef4c0aff9..a3067a0561 100644 --- a/components/esm4/loadlvlc.cpp +++ b/components/esm4/loadlvlc.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LevelledCreature::~LevelledCreature() -{ -} - void ESM4::LevelledCreature::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadlvlc.hpp b/components/esm4/loadlvlc.hpp index 61f2d90e24..44aadbe36e 100644 --- a/components/esm4/loadlvlc.hpp +++ b/components/esm4/loadlvlc.hpp @@ -58,10 +58,8 @@ namespace ESM4 bool calcEachItemInCount() const; std::int8_t chanceNone() const; - virtual ~LevelledCreature(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp index 44395e92ea..68e458e73c 100644 --- a/components/esm4/loadlvli.cpp +++ b/components/esm4/loadlvli.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LevelledItem::~LevelledItem() -{ -} - void ESM4::LevelledItem::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadlvli.hpp b/components/esm4/loadlvli.hpp index 479d556d5c..290658e86d 100644 --- a/components/esm4/loadlvli.hpp +++ b/components/esm4/loadlvli.hpp @@ -54,15 +54,13 @@ namespace ESM4 std::vector mLvlObject; - virtual ~LevelledItem(); - bool calcAllLvlLessThanPlayer() const; bool calcEachItemInCount() const; bool useAll() const; std::int8_t chanceNone() const; - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp index f4782a9c40..bd1423d0ac 100644 --- a/components/esm4/loadlvln.cpp +++ b/components/esm4/loadlvln.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::LevelledNpc::~LevelledNpc() -{ -} - void ESM4::LevelledNpc::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadlvln.hpp b/components/esm4/loadlvln.hpp index 7c82a44ded..49a7bb943b 100644 --- a/components/esm4/loadlvln.hpp +++ b/components/esm4/loadlvln.hpp @@ -52,14 +52,12 @@ namespace ESM4 std::uint8_t mListCount; std::vector mLvlObject; - virtual ~LevelledNpc(); - inline bool calcAllLvlLessThanPlayer() const { return (mLvlActorFlags & 0x01) != 0; } inline bool calcEachItemInCount() const { return (mLvlActorFlags & 0x02) != 0; } inline std::int8_t chanceNone() const { return mChanceNone; } - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadmato.cpp b/components/esm4/loadmato.cpp index 839a6211df..7e3ab43026 100644 --- a/components/esm4/loadmato.cpp +++ b/components/esm4/loadmato.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Material::~Material() -{ -} - void ESM4::Material::load(ESM4::Reader& reader) { //mFormId = reader.adjustFormId(reader.hdr().record.id); // FIXME: use master adjusted? diff --git a/components/esm4/loadmato.hpp b/components/esm4/loadmato.hpp index 8f60824bd8..041b158c29 100644 --- a/components/esm4/loadmato.hpp +++ b/components/esm4/loadmato.hpp @@ -45,10 +45,8 @@ namespace ESM4 std::string mEditorId; std::string mModel; - virtual ~Material(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadmisc.cpp b/components/esm4/loadmisc.cpp index 8231df28f5..966910f775 100644 --- a/components/esm4/loadmisc.cpp +++ b/components/esm4/loadmisc.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::MiscItem::~MiscItem() -{ -} - void ESM4::MiscItem::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadmisc.hpp b/components/esm4/loadmisc.hpp index 308a6ec326..49297330ea 100644 --- a/components/esm4/loadmisc.hpp +++ b/components/esm4/loadmisc.hpp @@ -64,10 +64,8 @@ namespace ESM4 Data mData; - virtual ~MiscItem(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadmset.cpp b/components/esm4/loadmset.cpp index 67ee5666d1..55a2619239 100644 --- a/components/esm4/loadmset.cpp +++ b/components/esm4/loadmset.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::MediaSet::~MediaSet() -{ -} - void ESM4::MediaSet::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadmset.hpp b/components/esm4/loadmset.hpp index c66273bc6c..7fa9450236 100644 --- a/components/esm4/loadmset.hpp +++ b/components/esm4/loadmset.hpp @@ -86,10 +86,8 @@ namespace ESM4 FormId mSoundIntro; // HNAM FormId mSoundOutro; // INAM - virtual ~MediaSet(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadmstt.cpp b/components/esm4/loadmstt.cpp index 35c69c8a31..1deb5603f8 100644 --- a/components/esm4/loadmstt.cpp +++ b/components/esm4/loadmstt.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::MovableStatic::~MovableStatic() -{ -} - void ESM4::MovableStatic::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadmstt.hpp b/components/esm4/loadmstt.hpp index 0b9873a91a..eceabda161 100644 --- a/components/esm4/loadmstt.hpp +++ b/components/esm4/loadmstt.hpp @@ -47,10 +47,8 @@ namespace ESM4 std::int8_t mData; FormId mLoopingSound; - virtual ~MovableStatic(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadmusc.cpp b/components/esm4/loadmusc.cpp index a328fbce1e..566fcb2401 100644 --- a/components/esm4/loadmusc.cpp +++ b/components/esm4/loadmusc.cpp @@ -36,10 +36,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Music::~Music() -{ -} - void ESM4::Music::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadmusc.hpp b/components/esm4/loadmusc.hpp index 2ec10abc8c..c54c9fa67c 100644 --- a/components/esm4/loadmusc.hpp +++ b/components/esm4/loadmusc.hpp @@ -47,10 +47,8 @@ namespace ESM4 std::string mEditorId; std::string mMusicFile; - virtual ~Music(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp index 818a8e98aa..4ed72c20f1 100644 --- a/components/esm4/loadnavi.cpp +++ b/components/esm4/loadnavi.cpp @@ -38,10 +38,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Navigation::~Navigation() -{ -} - void ESM4::Navigation::IslandInfo::load(ESM4::Reader& reader) { reader.get(minX); diff --git a/components/esm4/loadnavi.hpp b/components/esm4/loadnavi.hpp index ff0d033f3b..117aa102d0 100644 --- a/components/esm4/loadnavi.hpp +++ b/components/esm4/loadnavi.hpp @@ -104,10 +104,8 @@ namespace ESM4 std::map mPathIndexMap; - virtual ~Navigation(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp index 1e84a9ffee..5fce00893c 100644 --- a/components/esm4/loadnavm.cpp +++ b/components/esm4/loadnavm.cpp @@ -35,10 +35,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::NavMesh::~NavMesh() -{ -} - void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader) { //std::cout << "start: divisor " << std::dec << divisor << ", segments " << triSegments.size() << //std::endl; diff --git a/components/esm4/loadnavm.hpp b/components/esm4/loadnavm.hpp index 7910b1373d..a7a86cf71d 100644 --- a/components/esm4/loadnavm.hpp +++ b/components/esm4/loadnavm.hpp @@ -98,10 +98,8 @@ namespace ESM4 FormId mFormId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details - virtual ~NavMesh(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; void blank(); }; diff --git a/components/esm4/loadnote.cpp b/components/esm4/loadnote.cpp index 90e39811af..5f4b836fcf 100644 --- a/components/esm4/loadnote.cpp +++ b/components/esm4/loadnote.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Note::~Note() -{ -} - void ESM4::Note::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadnote.hpp b/components/esm4/loadnote.hpp index 506674081f..33f08257e9 100644 --- a/components/esm4/loadnote.hpp +++ b/components/esm4/loadnote.hpp @@ -47,10 +47,8 @@ namespace ESM4 std::string mModel; std::string mIcon; - virtual ~Note(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 51d1d0b181..a05bedd90e 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -41,10 +41,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Npc::~Npc() -{ -} - void ESM4::Npc::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadnpc.hpp b/components/esm4/loadnpc.hpp index 1bd38e189c..316caeecc2 100644 --- a/components/esm4/loadnpc.hpp +++ b/components/esm4/loadnpc.hpp @@ -217,10 +217,8 @@ namespace ESM4 std::vector mSymTextureModeCoefficients; // size 0 or 50 std::int16_t mFgRace; - virtual ~Npc(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadotft.cpp b/components/esm4/loadotft.cpp index d1b1bb23a7..e24e3fbc9c 100644 --- a/components/esm4/loadotft.cpp +++ b/components/esm4/loadotft.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Outfit::~Outfit() -{ -} - void ESM4::Outfit::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadotft.hpp b/components/esm4/loadotft.hpp index b982a3c468..cf15775a91 100644 --- a/components/esm4/loadotft.hpp +++ b/components/esm4/loadotft.hpp @@ -47,10 +47,8 @@ namespace ESM4 std::vector mInventory; - virtual ~Outfit(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp index 5ab9555cd6..7d0e1027c7 100644 --- a/components/esm4/loadpack.cpp +++ b/components/esm4/loadpack.cpp @@ -33,10 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::AIPackage::~AIPackage() -{ -} - void ESM4::AIPackage::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadpack.hpp b/components/esm4/loadpack.hpp index 849a51c017..e04c67a7d8 100644 --- a/components/esm4/loadpack.hpp +++ b/components/esm4/loadpack.hpp @@ -97,10 +97,8 @@ namespace ESM4 PTDT mTarget; std::vector mConditions; - virtual ~AIPackage(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index 4a95c8af0a..d1b9f6d57f 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -36,10 +36,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Pathgrid::~Pathgrid() -{ -} - void ESM4::Pathgrid::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadpgrd.hpp b/components/esm4/loadpgrd.hpp index 74672a1f92..5ee864b93b 100644 --- a/components/esm4/loadpgrd.hpp +++ b/components/esm4/loadpgrd.hpp @@ -83,10 +83,8 @@ namespace ESM4 std::vector mForeign; std::vector mObjects; - virtual ~Pathgrid(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadpgre.cpp b/components/esm4/loadpgre.cpp index 665b07e523..b38305b3b5 100644 --- a/components/esm4/loadpgre.cpp +++ b/components/esm4/loadpgre.cpp @@ -34,10 +34,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::PlacedGrenade::~PlacedGrenade() -{ -} - void ESM4::PlacedGrenade::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadpgre.hpp b/components/esm4/loadpgre.hpp index 1929cd4c52..90e3749508 100644 --- a/components/esm4/loadpgre.hpp +++ b/components/esm4/loadpgre.hpp @@ -46,10 +46,8 @@ namespace ESM4 std::string mEditorId; - virtual ~PlacedGrenade(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadpwat.cpp b/components/esm4/loadpwat.cpp index 52852b0e8a..8f403470a2 100644 --- a/components/esm4/loadpwat.cpp +++ b/components/esm4/loadpwat.cpp @@ -34,10 +34,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::PlaceableWater::~PlaceableWater() -{ -} - void ESM4::PlaceableWater::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadpwat.hpp b/components/esm4/loadpwat.hpp index b2dc2c4e00..4a74faeb02 100644 --- a/components/esm4/loadpwat.hpp +++ b/components/esm4/loadpwat.hpp @@ -46,10 +46,8 @@ namespace ESM4 std::string mEditorId; - virtual ~PlaceableWater(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp index 5588f90580..39eb592514 100644 --- a/components/esm4/loadqust.cpp +++ b/components/esm4/loadqust.cpp @@ -33,10 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Quest::~Quest() -{ -} - void ESM4::Quest::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadqust.hpp b/components/esm4/loadqust.hpp index 853805900c..2fa9ff7097 100644 --- a/components/esm4/loadqust.hpp +++ b/components/esm4/loadqust.hpp @@ -71,10 +71,8 @@ namespace ESM4 ScriptDefinition mScript; - virtual ~Quest(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp index 3fa9661122..142cfd2adf 100644 --- a/components/esm4/loadrace.cpp +++ b/components/esm4/loadrace.cpp @@ -35,10 +35,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Race::~Race() -{ -} - void ESM4::Race::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadrace.hpp b/components/esm4/loadrace.hpp index c5912b719b..9166282e17 100644 --- a/components/esm4/loadrace.hpp +++ b/components/esm4/loadrace.hpp @@ -162,10 +162,8 @@ namespace ESM4 std::vector mHeadPartIdsMale; // TES5 std::vector mHeadPartIdsFemale; // TES5 - virtual ~Race(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index 24cd3d5cde..91c10c564e 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Reference::~Reference() -{ -} - void ESM4::Reference::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index b631b334ee..0494dcb074 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -106,10 +106,8 @@ namespace ESM4 FormId mTargetRef; - virtual ~Reference(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; void blank(); }; diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp index 7acda43350..2455966036 100644 --- a/components/esm4/loadregn.cpp +++ b/components/esm4/loadregn.cpp @@ -39,10 +39,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Region::~Region() -{ -} - void ESM4::Region::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadregn.hpp b/components/esm4/loadregn.hpp index 86b5ca5799..9b7962ba3c 100644 --- a/components/esm4/loadregn.hpp +++ b/components/esm4/loadregn.hpp @@ -85,10 +85,8 @@ namespace ESM4 RegionData mData; std::vector mSounds; - virtual ~Region(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; void blank(); }; diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp index fca11db779..b131811c64 100644 --- a/components/esm4/loadroad.cpp +++ b/components/esm4/loadroad.cpp @@ -33,10 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Road::~Road() -{ -} - void ESM4::Road::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadroad.hpp b/components/esm4/loadroad.hpp index 85745d000a..33fc332643 100644 --- a/components/esm4/loadroad.hpp +++ b/components/esm4/loadroad.hpp @@ -76,10 +76,8 @@ namespace ESM4 std::vector mNodes; std::vector mLinks; - virtual ~Road(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadsbsp.cpp b/components/esm4/loadsbsp.cpp index 9daef0f89c..79ad846414 100644 --- a/components/esm4/loadsbsp.cpp +++ b/components/esm4/loadsbsp.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::SubSpace::~SubSpace() -{ -} - void ESM4::SubSpace::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadsbsp.hpp b/components/esm4/loadsbsp.hpp index 601b2b0cb7..8e949cc6ad 100644 --- a/components/esm4/loadsbsp.hpp +++ b/components/esm4/loadsbsp.hpp @@ -51,8 +51,6 @@ namespace ESM4 std::string mEditorId; Dimension mDimension; - virtual ~SubSpace(); - virtual void load(Reader& reader); //virtual void save(Writer& writer) const; diff --git a/components/esm4/loadscol.cpp b/components/esm4/loadscol.cpp index 23a3cad1a3..0dd206a08f 100644 --- a/components/esm4/loadscol.cpp +++ b/components/esm4/loadscol.cpp @@ -34,10 +34,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::StaticCollection::~StaticCollection() -{ -} - void ESM4::StaticCollection::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadscol.hpp b/components/esm4/loadscol.hpp index dc1bf296e1..6a0005699d 100644 --- a/components/esm4/loadscol.hpp +++ b/components/esm4/loadscol.hpp @@ -46,10 +46,8 @@ namespace ESM4 std::string mEditorId; - virtual ~StaticCollection(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index c95a212e75..4c606e3351 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -33,10 +33,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Script::~Script() -{ -} - void ESM4::Script::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadscpt.hpp b/components/esm4/loadscpt.hpp index 459c552876..dbf36aa367 100644 --- a/components/esm4/loadscpt.hpp +++ b/components/esm4/loadscpt.hpp @@ -46,10 +46,8 @@ namespace ESM4 ScriptDefinition mScript; - virtual ~Script(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadscrl.cpp b/components/esm4/loadscrl.cpp index c09c38ae90..ae084afae3 100644 --- a/components/esm4/loadscrl.cpp +++ b/components/esm4/loadscrl.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Scroll::~Scroll() -{ -} - void ESM4::Scroll::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadscrl.hpp b/components/esm4/loadscrl.hpp index d4feded5d8..d7872f551e 100644 --- a/components/esm4/loadscrl.hpp +++ b/components/esm4/loadscrl.hpp @@ -55,10 +55,8 @@ namespace ESM4 Data mData; - virtual ~Scroll(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadsgst.cpp b/components/esm4/loadsgst.cpp index 9453cdc27d..843644fe8b 100644 --- a/components/esm4/loadsgst.cpp +++ b/components/esm4/loadsgst.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::SigilStone::~SigilStone() -{ -} - void ESM4::SigilStone::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadsgst.hpp b/components/esm4/loadsgst.hpp index 3a2d32850d..aa9b95166b 100644 --- a/components/esm4/loadsgst.hpp +++ b/components/esm4/loadsgst.hpp @@ -62,10 +62,8 @@ namespace ESM4 Data mData; - virtual ~SigilStone(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadslgm.cpp b/components/esm4/loadslgm.cpp index 97bbab9569..53e30fd1da 100644 --- a/components/esm4/loadslgm.cpp +++ b/components/esm4/loadslgm.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::SoulGem::~SoulGem() -{ -} - void ESM4::SoulGem::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadslgm.hpp b/components/esm4/loadslgm.hpp index e101fbf8a5..893603a1fe 100644 --- a/components/esm4/loadslgm.hpp +++ b/components/esm4/loadslgm.hpp @@ -63,10 +63,8 @@ namespace ESM4 Data mData; - virtual ~SoulGem(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadsndr.cpp b/components/esm4/loadsndr.cpp index 4c0bf30b24..d01fb0ed74 100644 --- a/components/esm4/loadsndr.cpp +++ b/components/esm4/loadsndr.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::SoundReference::~SoundReference() -{ -} - void ESM4::SoundReference::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadsndr.hpp b/components/esm4/loadsndr.hpp index fba1912264..de0ca3eb1a 100644 --- a/components/esm4/loadsndr.hpp +++ b/components/esm4/loadsndr.hpp @@ -73,10 +73,8 @@ namespace ESM4 TargetCondition mTargetCondition; - virtual ~SoundReference(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadsoun.cpp b/components/esm4/loadsoun.cpp index bcb776e100..a3358236b0 100644 --- a/components/esm4/loadsoun.cpp +++ b/components/esm4/loadsoun.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Sound::~Sound() -{ -} - void ESM4::Sound::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadsoun.hpp b/components/esm4/loadsoun.hpp index b766aa5534..da1d7fbd71 100644 --- a/components/esm4/loadsoun.hpp +++ b/components/esm4/loadsoun.hpp @@ -88,10 +88,8 @@ namespace ESM4 SNDX mData; SoundData mExtra; - virtual ~Sound(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp index d08812b4ee..10ce91980c 100644 --- a/components/esm4/loadstat.cpp +++ b/components/esm4/loadstat.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Static::~Static() -{ -} - void ESM4::Static::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadstat.hpp b/components/esm4/loadstat.hpp index 9c689462ef..07369b3357 100644 --- a/components/esm4/loadstat.hpp +++ b/components/esm4/loadstat.hpp @@ -49,10 +49,8 @@ namespace ESM4 float mBoundRadius; std::vector mMODT; // FIXME texture hash - virtual ~Static(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadtact.cpp b/components/esm4/loadtact.cpp index f82e70c483..0d684719be 100644 --- a/components/esm4/loadtact.cpp +++ b/components/esm4/loadtact.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::TalkingActivator::~TalkingActivator() -{ -} - void ESM4::TalkingActivator::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadtact.hpp b/components/esm4/loadtact.hpp index f3fd91a433..3a79006d78 100644 --- a/components/esm4/loadtact.hpp +++ b/components/esm4/loadtact.hpp @@ -63,10 +63,8 @@ namespace ESM4 FormId mLoopSound; // SOUN FormId mRadioTemplate; // SOUN - virtual ~TalkingActivator(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadterm.cpp b/components/esm4/loadterm.cpp index e9eedc3651..fc1d10cf70 100644 --- a/components/esm4/loadterm.cpp +++ b/components/esm4/loadterm.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Terminal::~Terminal() -{ -} - void ESM4::Terminal::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadterm.hpp b/components/esm4/loadterm.hpp index 1b1486d7a2..32ec613ff5 100644 --- a/components/esm4/loadterm.hpp +++ b/components/esm4/loadterm.hpp @@ -53,10 +53,8 @@ namespace ESM4 FormId mPasswordNote; FormId mSound; - virtual ~Terminal(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp index c70863a51b..1ac3301c3f 100644 --- a/components/esm4/loadtree.cpp +++ b/components/esm4/loadtree.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Tree::~Tree() -{ -} - void ESM4::Tree::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadtree.hpp b/components/esm4/loadtree.hpp index 2c88812b46..48c59468b2 100644 --- a/components/esm4/loadtree.hpp +++ b/components/esm4/loadtree.hpp @@ -49,10 +49,8 @@ namespace ESM4 std::string mLeafTexture; - virtual ~Tree(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp index 51f58c9816..b0f8c1113c 100644 --- a/components/esm4/loadtxst.cpp +++ b/components/esm4/loadtxst.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::TextureSet::~TextureSet() -{ -} - void ESM4::TextureSet::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadtxst.hpp b/components/esm4/loadtxst.hpp index 71251c2dcb..be968f2e6e 100644 --- a/components/esm4/loadtxst.hpp +++ b/components/esm4/loadtxst.hpp @@ -53,10 +53,8 @@ namespace ESM4 std::string mUnknown; std::string mSpecular; - virtual ~TextureSet(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadweap.cpp b/components/esm4/loadweap.cpp index 463496187e..56ae2174d2 100644 --- a/components/esm4/loadweap.cpp +++ b/components/esm4/loadweap.cpp @@ -31,10 +31,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::Weapon::~Weapon() -{ -} - void ESM4::Weapon::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadweap.hpp b/components/esm4/loadweap.hpp index 7128e26ec9..1abfc5b577 100644 --- a/components/esm4/loadweap.hpp +++ b/components/esm4/loadweap.hpp @@ -83,10 +83,8 @@ namespace ESM4 Data mData; - virtual ~Weapon(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; //void blank(); }; diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp index ca8e5fd2c5..d8ec79b625 100644 --- a/components/esm4/loadwrld.cpp +++ b/components/esm4/loadwrld.cpp @@ -32,10 +32,6 @@ #include "reader.hpp" //#include "writer.hpp" -ESM4::World::~World() -{ -} - void ESM4::World::load(ESM4::Reader& reader) { mFormId = reader.hdr().record.id; diff --git a/components/esm4/loadwrld.hpp b/components/esm4/loadwrld.hpp index 5937670daf..b6b643446c 100644 --- a/components/esm4/loadwrld.hpp +++ b/components/esm4/loadwrld.hpp @@ -126,10 +126,8 @@ namespace ESM4 std::vector mCells; std::vector mRoads; - virtual ~World(); - - virtual void load(ESM4::Reader& reader); - //virtual void save(ESM4::Writer& writer) const; + void load(ESM4::Reader& reader); + //void save(ESM4::Writer& writer) const; }; } From d2510284ec25349dbf934d7bf92d2be36ffa7148 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 14 Apr 2022 16:46:57 +0200 Subject: [PATCH 2351/2859] Support TES4 in esmtool dump --- apps/esmtool/CMakeLists.txt | 3 + apps/esmtool/arguments.hpp | 28 +++ apps/esmtool/esmtool.cpp | 126 +++++++++----- apps/esmtool/tes4.cpp | 329 ++++++++++++++++++++++++++++++++++++ apps/esmtool/tes4.hpp | 15 ++ components/CMakeLists.txt | 2 +- components/esm/format.cpp | 40 +++++ components/esm/format.hpp | 23 +++ components/esm4/common.hpp | 4 +- components/esm4/records.hpp | 84 +++++++++ 10 files changed, 606 insertions(+), 48 deletions(-) create mode 100644 apps/esmtool/arguments.hpp create mode 100644 apps/esmtool/tes4.cpp create mode 100644 apps/esmtool/tes4.hpp create mode 100644 components/esm/format.cpp create mode 100644 components/esm/format.hpp create mode 100644 components/esm4/records.hpp diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 122ca2f3af..914a40699e 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -4,6 +4,9 @@ set(ESMTOOL labels.cpp record.hpp record.cpp + arguments.hpp + tes4.hpp + tes4.cpp ) source_group(apps\\esmtool FILES ${ESMTOOL}) diff --git a/apps/esmtool/arguments.hpp b/apps/esmtool/arguments.hpp new file mode 100644 index 0000000000..96b4bb8f04 --- /dev/null +++ b/apps/esmtool/arguments.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_ESMTOOL_ARGUMENTS_H +#define OPENMW_ESMTOOL_ARGUMENTS_H + +#include +#include + +#include + +namespace EsmTool +{ + struct Arguments + { + std::optional mRawFormat; + bool quiet_given; + bool loadcells_given; + bool plain_given; + + std::string mode; + std::string encoding; + std::string filename; + std::string outname; + + std::vector types; + std::string name; + }; +} + +#endif diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index d6708fbfb4..3eb78f3e41 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -7,17 +7,29 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include #include "record.hpp" #include "labels.hpp" +#include "arguments.hpp" +#include "tes4.hpp" + +namespace +{ -#define ESMTOOL_VERSION 1.2 +using namespace EsmTool; + +constexpr unsigned majorVersion = 1; +constexpr unsigned minorVersion = 3; // Create a local alias for brevity namespace bpo = boost::program_options; @@ -36,23 +48,6 @@ struct ESMData }; -// Based on the legacy struct -struct Arguments -{ - bool raw_given; - bool quiet_given; - bool loadcells_given; - bool plain_given; - - std::string mode; - std::string encoding; - std::string filename; - std::string outname; - - std::vector types; - std::string name; -}; - bool parseOptions (int argc, char** argv, Arguments &info) { bpo::options_description desc("Inspect and extract from Morrowind ES files (ESM, ESP, ESS)\nSyntax: esmtool [options] mode infile [outfile]\nAllowed modes:\n dump\t Dumps all readable data from the input file.\n clone\t Clones the input file to the output file.\n comp\t Compares the given files.\n\nAllowed options"); @@ -60,7 +55,10 @@ bool parseOptions (int argc, char** argv, Arguments &info) desc.add_options() ("help,h", "print help message.") ("version,v", "print version information and quit.") - ("raw,r", "Show an unformatted list of all records and subrecords.") + ("raw,r", bpo::value(), + "Show an unformatted list of all records and subrecords of given format:\n" + "\n\tTES3" + "\n\tTES4") // The intention is that this option would interact better // with other modes including clone, dump, and raw. ("type,t", bpo::value< std::vector >(), @@ -122,7 +120,7 @@ bool parseOptions (int argc, char** argv, Arguments &info) } if (variables.count ("version")) { - std::cout << "ESMTool version " << ESMTOOL_VERSION << std::endl; + std::cout << "ESMTool version " << majorVersion << '.' << minorVersion << std::endl; return false; } if (!variables.count("mode")) @@ -164,7 +162,9 @@ bool parseOptions (int argc, char** argv, Arguments &info) if (variables["input-file"].as< std::vector >().size() > 1) info.outname = variables["input-file"].as< std::vector >()[1]; - info.raw_given = variables.count ("raw") != 0; + if (const auto it = variables.find("raw"); it != variables.end()) + info.mRawFormat = ESM::parseFormat(it->second.as()); + info.quiet_given = variables.count ("quiet") != 0; info.loadcells_given = variables.count ("loadcells") != 0; info.plain_given = variables.count("plain") != 0; @@ -181,13 +181,14 @@ bool parseOptions (int argc, char** argv, Arguments &info) return true; } -void printRaw(ESM::ESMReader &esm); void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data); int load(const Arguments& info, ESMData* data); int clone(const Arguments& info); int comp(const Arguments& info); +} + int main(int argc, char**argv) { try @@ -217,6 +218,9 @@ int main(int argc, char**argv) return 0; } +namespace +{ + void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMData* data) { bool quiet = (info.quiet_given || info.mode == "clone"); @@ -284,8 +288,11 @@ void loadCell(const Arguments& info, ESM::Cell &cell, ESM::ESMReader &esm, ESMDa } } -void printRaw(ESM::ESMReader &esm) +void printRawTes3(const std::string& path) { + std::cout << "TES3 RAW file listing: " << path << '\n'; + ESM::ESMReader esm; + esm.openRaw(path); while(esm.hasMoreRecs()) { ESM::NAME n = esm.getRecName(); @@ -305,35 +312,23 @@ void printRaw(ESM::ESMReader &esm) } } -int load(const Arguments& info, ESMData* data) +int loadTes3(const Arguments& info, std::unique_ptr&& stream, ESMData* data) { + std::cout << "Loading TES3 file: " << info.filename << '\n'; + ESM::ESMReader esm; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); - std::string filename = info.filename; - std::cout << "Loading file: " << filename << '\n'; - std::unordered_set skipped; - try { - - if(info.raw_given && info.mode == "dump") - { - std::cout << "RAW file listing:\n"; - - esm.openRaw(filename); - - printRaw(esm); - - return 0; - } - + try + { bool quiet = (info.quiet_given || info.mode == "clone"); bool loadCells = (info.loadcells_given || info.mode == "clone"); bool save = (info.mode == "clone"); - esm.open(filename); + esm.open(std::move(stream), info.filename); if (data != nullptr) { @@ -422,7 +417,49 @@ int load(const Arguments& info, ESMData* data) return 0; } -#include +int load(const Arguments& info, ESMData* data) +{ + if (info.mRawFormat.has_value() && info.mode == "dump") + { + switch (*info.mRawFormat) + { + case ESM::Format::Tes3: + printRawTes3(info.filename); + break; + case ESM::Format::Tes4: + std::cout << "Printing raw TES4 file is not supported: " << info.filename << "\n"; + break; + } + return 0; + } + + auto stream = Files::openBinaryInputFileStream(info.filename); + if (!stream->is_open()) + { + std::cout << "Failed to open file: " << std::strerror(errno) << '\n'; + return -1; + } + + const ESM::Format format = ESM::readFormat(*stream); + stream->seekg(0); + + switch (format) + { + case ESM::Format::Tes3: + return loadTes3(info, std::move(stream), data); + case ESM::Format::Tes4: + if (data != nullptr) + { + std::cout << "Collecting data from esm file is not supported for TES4\n"; + return -1; + } + return loadTes4(info, std::move(stream)); + } + + std::cout << "Unsupported ESM format: " << ESM::NAME(format).toStringView() << '\n'; + + return -1; +} int clone(const Arguments& info) { @@ -526,9 +563,6 @@ int comp(const Arguments& info) Arguments fileOne; Arguments fileTwo; - fileOne.raw_given = false; - fileTwo.raw_given = false; - fileOne.mode = "clone"; fileTwo.mode = "clone"; @@ -560,3 +594,5 @@ int comp(const Arguments& info) return 0; } + +} diff --git a/apps/esmtool/tes4.cpp b/apps/esmtool/tes4.cpp new file mode 100644 index 0000000000..3f213ff0b7 --- /dev/null +++ b/apps/esmtool/tes4.cpp @@ -0,0 +1,329 @@ +#include "tes4.hpp" +#include "arguments.hpp" +#include "labels.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace EsmTool +{ + namespace + { + struct Params + { + const bool mQuite; + + explicit Params(const Arguments& info) + : mQuite(info.quiet_given || info.mode == "clone") + {} + }; + + std::string toString(ESM4::GroupType type) + { + switch (type) + { + case ESM4::Grp_RecordType: return "RecordType"; + case ESM4::Grp_WorldChild: return "WorldChild"; + case ESM4::Grp_InteriorCell: return "InteriorCell"; + case ESM4::Grp_InteriorSubCell: return "InteriorSubCell"; + case ESM4::Grp_ExteriorCell: return "ExteriorCell"; + case ESM4::Grp_ExteriorSubCell: return "ExteriorSubCell"; + case ESM4::Grp_CellChild: return "CellChild"; + case ESM4::Grp_TopicChild: return "TopicChild"; + case ESM4::Grp_CellPersistentChild: return "CellPersistentChild"; + case ESM4::Grp_CellTemporaryChild: return "CellTemporaryChild"; + case ESM4::Grp_CellVisibleDistChild: return "CellVisibleDistChild"; + } + + return "Unknown (" + std::to_string(type) + ")"; + } + + template > + struct HasFormId : std::false_type {}; + + template + struct HasFormId> : std::true_type {}; + + template + constexpr bool hasFormId = HasFormId::value; + + template > + struct HasFlags : std::false_type {}; + + template + struct HasFlags> : std::true_type {}; + + template + constexpr bool hasFlags = HasFlags::value; + + template + void readTypedRecord(const Params& params, ESM4::Reader& reader) + { + reader.getRecordData(); + + T value; + value.load(reader); + + if (params.mQuite) + return; + + std::cout << "\n Record: " << ESM::NAME(reader.hdr().record.typeId).toStringView(); + if constexpr (hasFormId) + std::cout << ' ' << value.mFormId; + if constexpr (hasFlags) + std::cout << "\n Record flags: " << recordFlags(value.mFlags); + std::cout << '\n'; + } + + void readRecord(const Params& params, ESM4::Reader& reader) + { + switch (static_cast(reader.hdr().record.typeId)) + { + case ESM4::REC_AACT: break; + case ESM4::REC_ACHR: return readTypedRecord(params, reader); + case ESM4::REC_ACRE: return readTypedRecord(params, reader); + case ESM4::REC_ACTI: return readTypedRecord(params, reader); + case ESM4::REC_ADDN: break; + case ESM4::REC_ALCH: return readTypedRecord(params, reader); + case ESM4::REC_ALOC: return readTypedRecord(params, reader); + case ESM4::REC_AMMO: return readTypedRecord(params, reader); + case ESM4::REC_ANIO: return readTypedRecord(params, reader); + case ESM4::REC_APPA: return readTypedRecord(params, reader); + case ESM4::REC_ARMA: return readTypedRecord(params, reader); + case ESM4::REC_ARMO: return readTypedRecord(params, reader); + case ESM4::REC_ARTO: break; + case ESM4::REC_ASPC: return readTypedRecord(params, reader); + case ESM4::REC_ASTP: break; + case ESM4::REC_AVIF: break; + case ESM4::REC_BOOK: return readTypedRecord(params, reader); + case ESM4::REC_BPTD: return readTypedRecord(params, reader); + case ESM4::REC_CAMS: break; + case ESM4::REC_CCRD: break; + case ESM4::REC_CELL: return readTypedRecord(params, reader); + case ESM4::REC_CLAS: return readTypedRecord(params, reader); + case ESM4::REC_CLFM: return readTypedRecord(params, reader); + case ESM4::REC_CLMT: break; + case ESM4::REC_CLOT: return readTypedRecord(params, reader); + case ESM4::REC_CMNY: break; + case ESM4::REC_COBJ: break; + case ESM4::REC_COLL: break; + case ESM4::REC_CONT: return readTypedRecord(params, reader); + case ESM4::REC_CPTH: break; + case ESM4::REC_CREA: return readTypedRecord(params, reader); + case ESM4::REC_CSTY: break; + case ESM4::REC_DEBR: break; + case ESM4::REC_DIAL: return readTypedRecord(params, reader); + case ESM4::REC_DLBR: break; + case ESM4::REC_DLVW: break; + case ESM4::REC_DOBJ: return readTypedRecord(params, reader); + case ESM4::REC_DOOR: return readTypedRecord(params, reader); + case ESM4::REC_DUAL: break; + case ESM4::REC_ECZN: break; + case ESM4::REC_EFSH: break; + case ESM4::REC_ENCH: break; + case ESM4::REC_EQUP: break; + case ESM4::REC_EXPL: break; + case ESM4::REC_EYES: return readTypedRecord(params, reader); + case ESM4::REC_FACT: break; + case ESM4::REC_FLOR: return readTypedRecord(params, reader); + case ESM4::REC_FLST: return readTypedRecord(params, reader); + case ESM4::REC_FSTP: break; + case ESM4::REC_FSTS: break; + case ESM4::REC_FURN: return readTypedRecord(params, reader); + case ESM4::REC_GLOB: return readTypedRecord(params, reader); + case ESM4::REC_GMST: break; + case ESM4::REC_GRAS: return readTypedRecord(params, reader); + case ESM4::REC_GRUP: break; + case ESM4::REC_HAIR: return readTypedRecord(params, reader); + case ESM4::REC_HAZD: break; + case ESM4::REC_HDPT: return readTypedRecord(params, reader); + case ESM4::REC_IDLE: + // FIXME: ESM4::IdleAnimation::load does not work with Oblivion.esm + // return readTypedRecord(params, reader); + break; + case ESM4::REC_IDLM: return readTypedRecord(params, reader); + case ESM4::REC_IMAD: break; + case ESM4::REC_IMGS: break; + case ESM4::REC_IMOD: return readTypedRecord(params, reader); + case ESM4::REC_INFO: return readTypedRecord(params, reader); + case ESM4::REC_INGR: return readTypedRecord(params, reader); + case ESM4::REC_IPCT: break; + case ESM4::REC_IPDS: break; + case ESM4::REC_KEYM: return readTypedRecord(params, reader); + case ESM4::REC_KYWD: break; + case ESM4::REC_LAND: return readTypedRecord(params, reader); + case ESM4::REC_LCRT: break; + case ESM4::REC_LCTN: break; + case ESM4::REC_LGTM: return readTypedRecord(params, reader); + case ESM4::REC_LIGH: return readTypedRecord(params, reader); + case ESM4::REC_LSCR: break; + case ESM4::REC_LTEX: return readTypedRecord(params, reader); + case ESM4::REC_LVLC: return readTypedRecord(params, reader); + case ESM4::REC_LVLI: return readTypedRecord(params, reader); + case ESM4::REC_LVLN: return readTypedRecord(params, reader); + case ESM4::REC_LVSP: break; + case ESM4::REC_MATO: return readTypedRecord(params, reader); + case ESM4::REC_MATT: break; + case ESM4::REC_MESG: break; + case ESM4::REC_MGEF: break; + case ESM4::REC_MISC: return readTypedRecord(params, reader); + case ESM4::REC_MOVT: break; + case ESM4::REC_MSET: return readTypedRecord(params, reader); + case ESM4::REC_MSTT: return readTypedRecord(params, reader); + case ESM4::REC_MUSC: return readTypedRecord(params, reader); + case ESM4::REC_MUST: break; + case ESM4::REC_NAVI: return readTypedRecord(params, reader); + case ESM4::REC_NAVM: return readTypedRecord(params, reader); + case ESM4::REC_NOTE: return readTypedRecord(params, reader); + case ESM4::REC_NPC_: return readTypedRecord(params, reader); + case ESM4::REC_OTFT: return readTypedRecord(params, reader); + case ESM4::REC_PACK: return readTypedRecord(params, reader); + case ESM4::REC_PERK: break; + case ESM4::REC_PGRD: return readTypedRecord(params, reader); + case ESM4::REC_PGRE: return readTypedRecord(params, reader); + case ESM4::REC_PHZD: break; + case ESM4::REC_PROJ: break; + case ESM4::REC_PWAT: return readTypedRecord(params, reader); + case ESM4::REC_QUST: return readTypedRecord(params, reader); + case ESM4::REC_RACE: return readTypedRecord(params, reader); + case ESM4::REC_REFR: return readTypedRecord(params, reader); + case ESM4::REC_REGN: return readTypedRecord(params, reader); + case ESM4::REC_RELA: break; + case ESM4::REC_REVB: break; + case ESM4::REC_RFCT: break; + case ESM4::REC_ROAD: return readTypedRecord(params, reader); + case ESM4::REC_SBSP: return readTypedRecord(params, reader); + case ESM4::REC_SCEN: break; + case ESM4::REC_SCOL: return readTypedRecord(params, reader); + case ESM4::REC_SCPT: return readTypedRecord(params, reader); + case ESM4::REC_SCRL: return readTypedRecord(params, reader); + case ESM4::REC_SGST: return readTypedRecord(params, reader); + case ESM4::REC_SHOU: break; + case ESM4::REC_SLGM: return readTypedRecord(params, reader); + case ESM4::REC_SMBN: break; + case ESM4::REC_SMEN: break; + case ESM4::REC_SMQN: break; + case ESM4::REC_SNCT: break; + case ESM4::REC_SNDR: return readTypedRecord(params, reader); + case ESM4::REC_SOPM: break; + case ESM4::REC_SOUN: return readTypedRecord(params, reader); + case ESM4::REC_SPEL: break; + case ESM4::REC_SPGD: break; + case ESM4::REC_STAT: return readTypedRecord(params, reader); + case ESM4::REC_TACT: return readTypedRecord(params, reader); + case ESM4::REC_TERM: return readTypedRecord(params, reader); + case ESM4::REC_TES4: return readTypedRecord(params, reader); + case ESM4::REC_TREE: return readTypedRecord(params, reader); + case ESM4::REC_TXST: return readTypedRecord(params, reader); + case ESM4::REC_VTYP: break; + case ESM4::REC_WATR: break; + case ESM4::REC_WEAP: return readTypedRecord(params, reader); + case ESM4::REC_WOOP: break; + case ESM4::REC_WRLD: return readTypedRecord(params, reader); + case ESM4::REC_WTHR: break; + } + + if (!params.mQuite) + std::cout << "\n Unsupported record: " << ESM::NAME(reader.hdr().record.typeId).toStringView() << '\n'; + + reader.skipRecordData(); + } + + bool readItem(const Params& params, ESM4::Reader& reader); + + bool readGroup(const Params& params, ESM4::Reader& reader) + { + const ESM4::RecordHeader& header = reader.hdr(); + + if (!params.mQuite) + std::cout << "\nGroup: " << toString(static_cast(header.group.type)) + << " " << ESM::NAME(header.group.typeId).toStringView() << '\n'; + + switch (static_cast(header.group.type)) + { + case ESM4::Grp_RecordType: + case ESM4::Grp_InteriorCell: + case ESM4::Grp_InteriorSubCell: + case ESM4::Grp_ExteriorCell: + case ESM4::Grp_ExteriorSubCell: + reader.enterGroup(); + return readItem(params, reader); + case ESM4::Grp_WorldChild: + case ESM4::Grp_CellChild: + case ESM4::Grp_TopicChild: + case ESM4::Grp_CellPersistentChild: + case ESM4::Grp_CellTemporaryChild: + case ESM4::Grp_CellVisibleDistChild: + reader.adjustGRUPFormId(); + reader.enterGroup(); + if (!reader.hasMoreRecs()) + return false; + return readItem(params, reader); + } + + reader.skipGroup(); + + return true; + } + + bool readItem(const Params& params, ESM4::Reader& reader) + { + if (!reader.getRecordHeader() || !reader.hasMoreRecs()) + return false; + + const ESM4::RecordHeader& header = reader.hdr(); + + if (header.record.typeId == ESM4::REC_GRUP) + return readGroup(params, reader); + + readRecord(params, reader); + return true; + } + } + + int loadTes4(const Arguments& info, std::unique_ptr&& stream) + { + std::cout << "Loading TES4 file: " << info.filename << '\n'; + + try + { + const ToUTF8::StatelessUtf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding)); + ESM4::Reader reader(std::move(stream), info.filename); + reader.setEncoder(&encoder); + const Params params(info); + + if (!params.mQuite) + { + std::cout << "Author: " << reader.getAuthor() << '\n' + << "Description: " << reader.getDesc() << '\n' + << "File format version: " << reader.esmVersion() << '\n'; + + if (const std::vector& masterData = reader.getGameFiles(); !masterData.empty()) + { + std::cout << "Masters:" << '\n'; + for (const auto& master : masterData) + std::cout << " " << master.name << ", " << master.size << " bytes\n"; + } + } + + while (reader.hasMoreRecs()) + { + reader.exitGroupCheck(); + if (!readItem(params, reader)) + break; + } + } + catch (const std::exception& e) + { + std::cout << "\nERROR:\n\n " << e.what() << std::endl; + return -1; + } + + return 0; + } +} diff --git a/apps/esmtool/tes4.hpp b/apps/esmtool/tes4.hpp new file mode 100644 index 0000000000..8149b26049 --- /dev/null +++ b/apps/esmtool/tes4.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESMTOOL_TES4_H +#define OPENMW_ESMTOOL_TES4_H + +#include +#include +#include + +namespace EsmTool +{ + struct Arguments; + + int loadTes4(const Arguments& info, std::unique_ptr&& stream); +} + +#endif diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7121bde103..32c7c9535f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -80,7 +80,7 @@ add_component_dir (to_utf8 to_utf8 ) -add_component_dir(esm attr common defs esmcommon reader records util luascripts) +add_component_dir(esm attr common defs esmcommon reader records util luascripts format) add_component_dir (esm3 esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell diff --git a/components/esm/format.cpp b/components/esm/format.cpp new file mode 100644 index 0000000000..b41d81d57d --- /dev/null +++ b/components/esm/format.cpp @@ -0,0 +1,40 @@ +#include "format.hpp" + +#include + +namespace ESM +{ + namespace + { + bool isValidFormat(std::uint32_t value) + { + return value == static_cast(Format::Tes3) + || value == static_cast(Format::Tes4); + } + + Format toFormat(std::uint32_t value) + { + if (!isValidFormat(value)) + throw std::runtime_error("Invalid format: " + std::to_string(value)); + return static_cast(value); + } + } + + Format readFormat(std::istream& stream) + { + std::uint32_t format = 0; + stream.read(reinterpret_cast(&format), sizeof(format)); + if (stream.gcount() != sizeof(format)) + throw std::runtime_error("Not enough bytes to read file header"); + return toFormat(format); + } + + Format parseFormat(std::string_view value) + { + if (value.size() != sizeof(std::uint32_t)) + throw std::logic_error("Invalid format value: " + std::string(value)); + std::uint32_t format; + std::memcpy(&format, value.data(), sizeof(std::uint32_t)); + return toFormat(format); + } +} diff --git a/components/esm/format.hpp b/components/esm/format.hpp new file mode 100644 index 0000000000..acd93b4075 --- /dev/null +++ b/components/esm/format.hpp @@ -0,0 +1,23 @@ +#ifndef COMPONENT_ESM_FORMAT_H +#define COMPONENT_ESM_FORMAT_H + +#include "defs.hpp" + +#include +#include +#include + +namespace ESM +{ + enum class Format : std::uint32_t + { + Tes3 = fourCC("TES3"), + Tes4 = fourCC("TES4"), + }; + + Format readFormat(std::istream& stream); + + Format parseFormat(std::string_view value); +} + +#endif diff --git a/components/esm4/common.hpp b/components/esm4/common.hpp index b92a48e070..11286e4b03 100644 --- a/components/esm4/common.hpp +++ b/components/esm4/common.hpp @@ -76,7 +76,7 @@ namespace ESM4 REC_DOBJ = fourCC("DOBJ"), // Default Object Manager REC_DOOR = fourCC("DOOR"), // Door REC_DUAL = fourCC("DUAL"), // Dual Cast Data (possibly unused) - //REC_ECZN = fourCC("ECZN"), // Encounter Zone + REC_ECZN = fourCC("ECZN"), // Encounter Zone REC_EFSH = fourCC("EFSH"), // Effect Shader REC_ENCH = fourCC("ENCH"), // Enchantment REC_EQUP = fourCC("EQUP"), // Equip Slot (flag-type values) @@ -93,7 +93,7 @@ namespace ESM4 REC_GRAS = fourCC("GRAS"), // Grass REC_GRUP = fourCC("GRUP"), // Form Group REC_HAIR = fourCC("HAIR"), // Hair - //REC_HAZD = fourCC("HAZD"), // Hazard + REC_HAZD = fourCC("HAZD"), // Hazard REC_HDPT = fourCC("HDPT"), // Head Part REC_IDLE = fourCC("IDLE"), // Idle Animation REC_IDLM = fourCC("IDLM"), // Idle Marker diff --git a/components/esm4/records.hpp b/components/esm4/records.hpp new file mode 100644 index 0000000000..ae2a7a78f7 --- /dev/null +++ b/components/esm4/records.hpp @@ -0,0 +1,84 @@ +#ifndef COMPONENTS_ESM4_RECORDS_H +#define COMPONENTS_ESM4_RECORDS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif From dc3045c97014d6413813663191b927e3826caaea Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Tue, 26 Apr 2022 19:54:24 +0200 Subject: [PATCH 2352/2859] mono-only version of the shader linking system introduced in the stereo MR --- apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/shader/parsefors.cpp | 7 + apps/openmw_test_suite/shader/parselinks.cpp | 100 ++++++++ components/shader/shadermanager.cpp | 240 ++++++++++++++----- components/shader/shadermanager.hpp | 16 +- files/shaders/CMakeLists.txt | 4 + files/shaders/debug_vertex.glsl | 4 +- files/shaders/groundcover_vertex.glsl | 5 +- files/shaders/nv_default_vertex.glsl | 8 +- files/shaders/nv_nolighting_vertex.glsl | 6 +- files/shaders/objects_vertex.glsl | 8 +- files/shaders/openmw_fragment.glsl | 26 ++ files/shaders/openmw_fragment.h.glsl | 8 + files/shaders/openmw_vertex.glsl | 35 +++ files/shaders/openmw_vertex.h.glsl | 8 + files/shaders/sky_vertex.glsl | 5 +- files/shaders/terrain_vertex.glsl | 8 +- files/shaders/water_fragment.glsl | 16 +- files/shaders/water_vertex.glsl | 6 +- 19 files changed, 412 insertions(+), 99 deletions(-) create mode 100644 apps/openmw_test_suite/shader/parselinks.cpp create mode 100644 files/shaders/openmw_fragment.glsl create mode 100644 files/shaders/openmw_fragment.h.glsl create mode 100644 files/shaders/openmw_vertex.glsl create mode 100644 files/shaders/openmw_vertex.h.glsl diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 74418a004b..7e42a49f66 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -55,6 +55,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) shader/parsedefines.cpp shader/parsefors.cpp + shader/parselinks.cpp shader/shadermanager.cpp ../openmw/options.cpp diff --git a/apps/openmw_test_suite/shader/parsefors.cpp b/apps/openmw_test_suite/shader/parsefors.cpp index 330feb172d..4e64042365 100644 --- a/apps/openmw_test_suite/shader/parsefors.cpp +++ b/apps/openmw_test_suite/shader/parsefors.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace { @@ -16,6 +17,12 @@ namespace const std::string mName = "shader"; }; + static bool parseFors(std::string& source, const std::string& templateName) + { + std::vector dummy; + return parseDirectives(source, dummy, {}, {}, templateName); + } + TEST_F(ShaderParseForsTest, empty_should_succeed) { ASSERT_TRUE(parseFors(mSource, mName)); diff --git a/apps/openmw_test_suite/shader/parselinks.cpp b/apps/openmw_test_suite/shader/parselinks.cpp new file mode 100644 index 0000000000..2e3697ba50 --- /dev/null +++ b/apps/openmw_test_suite/shader/parselinks.cpp @@ -0,0 +1,100 @@ +#include + +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace Shader; + + using DefineMap = ShaderManager::DefineMap; + + struct ShaderParseLinksTest : Test + { + std::string mSource; + std::vector mLinkTargets; + ShaderManager::DefineMap mDefines; + const std::string mName = "my_shader.glsl"; + + bool parseLinks() + { + return parseDirectives(mSource, mLinkTargets, mDefines, {}, mName); + } + }; + + TEST_F(ShaderParseLinksTest, empty_should_succeed) + { + ASSERT_TRUE(parseLinks()); + EXPECT_EQ(mSource, ""); + EXPECT_TRUE(mLinkTargets.empty()); + } + + TEST_F(ShaderParseLinksTest, should_fail_for_single_escape_symbol) + { + mSource = "$"; + ASSERT_FALSE(parseLinks()); + EXPECT_EQ(mSource, "$"); + EXPECT_TRUE(mLinkTargets.empty()); + } + + TEST_F(ShaderParseLinksTest, should_fail_on_first_found_escaped_not_valid_directive) + { + mSource = "$foo "; + ASSERT_FALSE(parseLinks()); + EXPECT_EQ(mSource, "$foo "); + EXPECT_TRUE(mLinkTargets.empty()); + } + + TEST_F(ShaderParseLinksTest, should_fail_on_absent_link_target) + { + mSource = "$link "; + ASSERT_FALSE(parseLinks()); + EXPECT_EQ(mSource, "$link "); + EXPECT_TRUE(mLinkTargets.empty()); + } + + TEST_F(ShaderParseLinksTest, should_not_require_newline) + { + mSource = "$link \"foo.glsl\""; + ASSERT_TRUE(parseLinks()); + EXPECT_EQ(mSource, ""); + ASSERT_EQ(mLinkTargets.size(), 1); + EXPECT_EQ(mLinkTargets[0], "foo.glsl"); + } + + TEST_F(ShaderParseLinksTest, should_require_quotes) + { + mSource = "$link foo.glsl"; + ASSERT_FALSE(parseLinks()); + EXPECT_EQ(mSource, "$link foo.glsl"); + EXPECT_EQ(mLinkTargets.size(), 0); + } + + TEST_F(ShaderParseLinksTest, should_be_replaced_with_empty_line) + { + mSource = "$link \"foo.glsl\"\nbar"; + ASSERT_TRUE(parseLinks()); + EXPECT_EQ(mSource, "\nbar"); + ASSERT_EQ(mLinkTargets.size(), 1); + EXPECT_EQ(mLinkTargets[0], "foo.glsl"); + } + + TEST_F(ShaderParseLinksTest, should_only_accept_on_true_condition) + { + mSource = +R"glsl( +$link "foo.glsl" if 1 +$link "bar.glsl" if 0 +)glsl"; + ASSERT_TRUE(parseLinks()); + EXPECT_EQ(mSource, +R"glsl( + + +)glsl"); + ASSERT_EQ(mLinkTargets.size(), 1); + EXPECT_EQ(mLinkTargets[0], "foo.glsl"); + } +} diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 30c7ed1b1d..1341baad60 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -142,11 +143,123 @@ namespace Shader return true; } - bool parseFors(std::string& source, const std::string& templateName) + bool parseForeachDirective(std::string& source, const std::string& templateName, size_t foundPos) + { + size_t iterNameStart = foundPos + strlen("$foreach") + 1; + size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); + if (iterNameEnd == std::string::npos) + { + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; + return false; + } + std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart); + + size_t listStart = iterNameEnd + 1; + size_t listEnd = source.find_first_of("\n\r", listStart); + if (listEnd == std::string::npos) + { + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; + return false; + } + std::string list = source.substr(listStart, listEnd - listStart); + std::vector listElements; + if (list != "") + Misc::StringUtils::split(list, listElements, ","); + + size_t contentStart = source.find_first_not_of("\n\r", listEnd); + size_t contentEnd = source.find("$endforeach", contentStart); + if (contentEnd == std::string::npos) + { + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; + return false; + } + std::string content = source.substr(contentStart, contentEnd - contentStart); + + size_t overallEnd = contentEnd + std::string("$endforeach").length(); + + size_t lineDirectivePosition = source.rfind("#line", overallEnd); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString); + } + else + { + lineDirectivePosition = 0; + lineNumber = 2; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + overallEnd, '\n'); + + std::string replacement = ""; + for (std::vector::const_iterator element = listElements.cbegin(); element != listElements.cend(); element++) + { + std::string contentInstance = content; + size_t foundIterator; + while ((foundIterator = contentInstance.find(iteratorName)) != std::string::npos) + contentInstance.replace(foundIterator, iteratorName.length(), *element); + replacement += contentInstance; + } + replacement += "\n#line " + std::to_string(lineNumber); + source.replace(foundPos, overallEnd - foundPos, replacement); + return true; + } + + bool parseLinkDirective(std::string& source, std::string& linkTarget, const std::string& templateName, size_t foundPos) + { + size_t endPos = foundPos + 5; + size_t lineEnd = source.find_first_of('\n', endPos); + // If lineEnd = npos, this is the last line, so no need to check + std::string linkStatement = source.substr(endPos, lineEnd - endPos); + std::regex linkRegex( + R"r(\s*"([^"]+)"\s*)r" // Find any quoted string as the link name -> match[1] + R"r((if\s+)r" // Begin optional condition -> match[2] + R"r((!)?\s*)r" // Optional ! -> match[3] + R"r(([_a-zA-Z0-9]+)?)r" // The condition -> match[4] + R"r()?\s*)r" // End optional condition -> match[2] + ); + std::smatch linkMatch; + bool hasCondition = false; + std::string linkConditionExpression; + if (std::regex_match(linkStatement, linkMatch, linkRegex)) + { + linkTarget = linkMatch[1].str(); + hasCondition = !linkMatch[2].str().empty(); + linkConditionExpression = linkMatch[4].str(); + } + else + { + Log(Debug::Error) << "Shader " << templateName << " error: Expected a shader filename to link"; + return false; + } + if (linkTarget.empty()) + { + Log(Debug::Error) << "Shader " << templateName << " error: Empty link name"; + return false; + } + + if (hasCondition) + { + bool condition = !(linkConditionExpression.empty() || linkConditionExpression == "0"); + if (linkMatch[3].str() == "!") + condition = !condition; + + if (!condition) + linkTarget = ""; + } + + source.replace(foundPos, lineEnd - foundPos, ""); + return true; + } + + bool parseDirectives(std::string& source, std::vector& linkedShaderTemplateNames, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines, const std::string& templateName) { const char escapeCharacter = '$'; size_t foundPos = 0; - while ((foundPos = source.find(escapeCharacter)) != std::string::npos) + + while ((foundPos = source.find(escapeCharacter, foundPos)) != std::string::npos) { size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); if (endPos == std::string::npos) @@ -154,72 +267,25 @@ namespace Shader Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } - std::string command = source.substr(foundPos + 1, endPos - (foundPos + 1)); - if (command != "foreach") - { - Log(Debug::Error) << "Shader " << templateName << " error: Unknown shader directive: $" << command; - return false; - } - - size_t iterNameStart = endPos + 1; - size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); - if (iterNameEnd == std::string::npos) - { - Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; - return false; - } - std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart); - - size_t listStart = iterNameEnd + 1; - size_t listEnd = source.find_first_of("\n\r", listStart); - if (listEnd == std::string::npos) + std::string directive = source.substr(foundPos + 1, endPos - (foundPos + 1)); + if (directive == "foreach") { - Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; - return false; + if (!parseForeachDirective(source, templateName, foundPos)) + return false; } - std::string list = source.substr(listStart, listEnd - listStart); - std::vector listElements; - if (list != "") - Misc::StringUtils::split (list, listElements, ","); - - size_t contentStart = source.find_first_not_of("\n\r", listEnd); - size_t contentEnd = source.find("$endforeach", contentStart); - if (contentEnd == std::string::npos) + else if (directive == "link") { - Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; - return false; - } - std::string content = source.substr(contentStart, contentEnd - contentStart); - - size_t overallEnd = contentEnd + std::string("$endforeach").length(); - - size_t lineDirectivePosition = source.rfind("#line", overallEnd); - int lineNumber; - if (lineDirectivePosition != std::string::npos) - { - size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); - size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); - std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); - lineNumber = std::stoi(lineNumberString); + std::string linkTarget; + if (!parseLinkDirective(source, linkTarget, templateName, foundPos)) + return false; + if (!linkTarget.empty() && linkTarget != templateName) + linkedShaderTemplateNames.push_back(linkTarget); } else { - lineDirectivePosition = 0; - lineNumber = 2; - } - lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + overallEnd, '\n'); - - std::string replacement = ""; - for (std::vector::const_iterator element = listElements.cbegin(); element != listElements.cend(); element++) - { - std::string contentInstance = content; - size_t foundIterator; - while ((foundIterator = contentInstance.find(iteratorName)) != std::string::npos) - contentInstance.replace(foundIterator, iteratorName.length(), *element); - replacement += contentInstance; + Log(Debug::Error) << "Shader " << templateName << " error: Unknown shader directive: $" << directive; + return false; } - replacement += "\n#line " + std::to_string(lineNumber); - source.replace(foundPos, overallEnd - foundPos, replacement); } return true; @@ -265,6 +331,10 @@ namespace Shader else forIterators.pop_back(); } + else if (define == "link") + { + source.replace(foundPos, 1, "$"); + } else if (std::find(forIterators.begin(), forIterators.end(), define) != forIterators.end()) { source.replace(foundPos, 1, "$"); @@ -288,7 +358,7 @@ namespace Shader osg::ref_ptr ShaderManager::getShader(const std::string &templateName, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) { - std::lock_guard lock(mMutex); + std::unique_lock lock(mMutex); // read the template if we haven't already TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); @@ -319,7 +389,8 @@ namespace Shader if (shaderIt == mShaders.end()) { std::string shaderSource = templateIt->second; - if (!parseDefines(shaderSource, defines, mGlobalDefines, templateName) || !parseFors(shaderSource, templateName)) + std::vector linkedShaderNames; + if (!createSourceFromTemplate(shaderSource, linkedShaderNames, templateName, defines)) { // Add to the cache anyway to avoid logging the same error over and over. mShaders.insert(std::make_pair(std::make_pair(templateName, defines), nullptr)); @@ -333,6 +404,10 @@ namespace Shader static unsigned int counter = 0; shader->setName(Misc::StringUtils::format("%u %s", counter++, templateName)); + lock.unlock(); + getLinkedShaders(shader, linkedShaderNames, defines); + lock.lock(); + shaderIt = mShaders.insert(std::make_pair(std::make_pair(templateName, defines), shader)).first; } return shaderIt->second; @@ -348,6 +423,9 @@ namespace Shader osg::ref_ptr program = programTemplate ? cloneProgram(programTemplate) : osg::ref_ptr(new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); + addLinkedShaders(vertexShader, program); + addLinkedShaders(fragmentShader, program); + found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; @@ -377,11 +455,14 @@ namespace Shader // I'm not sure how to handle a shader that was already broken as there's no way to get a potential replacement to the nodes that need it. continue; std::string shaderSource = mShaderTemplates[templateId]; - if (!parseDefines(shaderSource, defines, mGlobalDefines, templateId) || !parseFors(shaderSource, templateId)) + std::vector linkedShaderNames; + if (!createSourceFromTemplate(shaderSource, linkedShaderNames, templateId, defines)) // We just broke the shader and there's no way to force existing objects back to fixed-function mode as we would when creating the shader. // If we put a nullptr in the shader map, we just lose the ability to put a working one in later. continue; shader->setShaderSource(shaderSource); + + getLinkedShaders(shader, linkedShaderNames, defines); } } @@ -397,4 +478,35 @@ namespace Shader program->releaseGLObjects(state); } + bool ShaderManager::createSourceFromTemplate(std::string& source, std::vector& linkedShaderTemplateNames, const std::string& templateName, const ShaderManager::DefineMap& defines) + { + if (!parseDefines(source, defines, mGlobalDefines, templateName)) + return false; + if (!parseDirectives(source, linkedShaderTemplateNames, defines, mGlobalDefines, templateName)) + return false; + return true; + } + + void ShaderManager::getLinkedShaders(osg::ref_ptr shader, const std::vector& linkedShaderNames, const DefineMap& defines) + { + mLinkedShaders.erase(shader); + if (linkedShaderNames.empty()) + return; + + for (auto& linkedShaderName : linkedShaderNames) + { + auto linkedShader = getShader(linkedShaderName, defines, shader->getType()); + if (linkedShader) + mLinkedShaders[shader].emplace_back(linkedShader); + } + } + + void ShaderManager::addLinkedShaders(osg::ref_ptr shader, osg::ref_ptr program) + { + auto linkedIt = mLinkedShaders.find(shader); + if (linkedIt != mLinkedShaders.end()) + for (const auto& linkedShader : linkedIt->second) + program->addShader(linkedShader); + } + } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 613f33b168..598dde85bb 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -51,7 +52,12 @@ namespace Shader void releaseGLObjects(osg::State* state); + bool createSourceFromTemplate(std::string& source, std::vector& linkedShaderTemplateNames, const std::string& templateName, const ShaderManager::DefineMap& defines); + private: + void getLinkedShaders(osg::ref_ptr shader, const std::vector& linkedShaderNames, const DefineMap& defines); + void addLinkedShaders(osg::ref_ptr shader, osg::ref_ptr program); + std::string mPath; DefineMap mGlobalDefines; @@ -67,15 +73,23 @@ namespace Shader typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; + typedef std::vector > ShaderList; + typedef std::map, ShaderList> LinkedShadersMap; + LinkedShadersMap mLinkedShaders; + std::mutex mMutex; osg::ref_ptr mProgramTemplate; }; - bool parseFors(std::string& source, const std::string& templateName); + bool parseForeachDirective(std::string& source, const std::string& templateName, size_t foundPos); + bool parseLinkDirective(std::string& source, std::string& linkTarget, const std::string& templateName, size_t foundPos); bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines, const std::string& templateName); + + bool parseDirectives(std::string& source, std::vector& linkedShaderTemplateNames, const ShaderManager::DefineMap& defines, + const ShaderManager::DefineMap& globalDefines, const std::string& templateName); } #endif diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 73929486cd..c873ada12f 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -16,6 +16,10 @@ set(SHADER_FILES depth.glsl objects_vertex.glsl objects_fragment.glsl + openmw_fragment.glsl + openmw_fragment.h.glsl + openmw_vertex.glsl + openmw_vertex.h.glsl terrain_vertex.glsl terrain_fragment.glsl lighting.glsl diff --git a/files/shaders/debug_vertex.glsl b/files/shaders/debug_vertex.glsl index a26d2573cf..094e866f9e 100644 --- a/files/shaders/debug_vertex.glsl +++ b/files/shaders/debug_vertex.glsl @@ -1,12 +1,12 @@ #version 120 -uniform mat4 projectionMatrix; +#include "openmw_vertex.glsl" centroid varying vec4 passColor; void main() { - gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + gl_Position = mw_modelToClip(gl_Vertex); passColor = gl_Color; } diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index c8db4be000..2914552139 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -1,5 +1,7 @@ #version 120 +#include "openmw_vertex.h.glsl" + #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -46,7 +48,6 @@ uniform mat4 osg_ViewMatrixInverse; uniform mat4 osg_ViewMatrix; uniform float windSpeed; uniform vec3 playerPos; -uniform mat4 projectionMatrix; #if @groundcoverStompMode == 0 #else @@ -143,7 +144,7 @@ void main(void) if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd) gl_Position = vec4(0.0, 0.0, 0.0, 1.0); else - gl_Position = projectionMatrix * viewPos; + gl_Position = mw_viewToClip(mw_viewStereoAdjust(viewPos)); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index 34d5415250..d27b9b2cbd 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -1,5 +1,7 @@ #version 120 +#include "openmw_vertex.h.glsl" + #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -8,8 +10,6 @@ #extension GL_EXT_gpu_shader4: require #endif -uniform mat4 projectionMatrix; - #if @diffuseMap varying vec2 diffuseMapUV; #endif @@ -38,9 +38,9 @@ varying vec3 passNormal; void main(void) { - gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + gl_Position = mw_modelToClip(gl_Vertex); - vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + vec4 viewPos = mw_modelToView(gl_Vertex); gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); diff --git a/files/shaders/nv_nolighting_vertex.glsl b/files/shaders/nv_nolighting_vertex.glsl index 617f0489a4..7b1f6961b4 100644 --- a/files/shaders/nv_nolighting_vertex.glsl +++ b/files/shaders/nv_nolighting_vertex.glsl @@ -1,6 +1,6 @@ #version 120 -uniform mat4 projectionMatrix; +#include "openmw_vertex.h.glsl" #if @diffuseMap varying vec2 diffuseMapUV; @@ -23,9 +23,9 @@ varying float passFalloff; void main(void) { - gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + gl_Position = mw_modelToClip(gl_Vertex); - vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + vec4 viewPos = mw_modelToView(gl_Vertex); gl_ClipVertex = viewPos; #if @radialFog euclideanDepth = length(viewPos.xyz); diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 77c7fef391..df60d8ea49 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -1,5 +1,7 @@ #version 120 +#include "openmw_vertex.h.glsl" + #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -8,8 +10,6 @@ #extension GL_EXT_gpu_shader4: require #endif -uniform mat4 projectionMatrix; - #if @diffuseMap varying vec2 diffuseMapUV; #endif @@ -72,9 +72,9 @@ varying vec3 passNormal; void main(void) { - gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + gl_Position = mw_modelToClip(gl_Vertex); - vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + vec4 viewPos = mw_modelToView(gl_Vertex); gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); diff --git a/files/shaders/openmw_fragment.glsl b/files/shaders/openmw_fragment.glsl new file mode 100644 index 0000000000..9de9e7afff --- /dev/null +++ b/files/shaders/openmw_fragment.glsl @@ -0,0 +1,26 @@ +#version 120 + +#include "openmw_fragment.h.glsl" + +uniform sampler2D reflectionMap; + +vec4 mw_sampleReflectionMap(vec2 uv) +{ + return texture2D(reflectionMap, uv); +} + +#if @refraction_enabled +uniform sampler2D refractionMap; +uniform sampler2D refractionDepthMap; + +vec4 mw_sampleRefractionMap(vec2 uv) +{ + return texture2D(refractionMap, uv); +} + +float mw_sampleRefractionDepthMap(vec2 uv) +{ + return texture2D(refractionDepthMap, uv).x; +} + +#endif \ No newline at end of file diff --git a/files/shaders/openmw_fragment.h.glsl b/files/shaders/openmw_fragment.h.glsl new file mode 100644 index 0000000000..c5bcf21c9d --- /dev/null +++ b/files/shaders/openmw_fragment.h.glsl @@ -0,0 +1,8 @@ +@link "openmw_fragment.glsl" + +vec4 mw_sampleReflectionMap(vec2 uv); + +#if @refraction_enabled +vec4 mw_sampleRefractionMap(vec2 uv); +float mw_sampleRefractionDepthMap(vec2 uv); +#endif \ No newline at end of file diff --git a/files/shaders/openmw_vertex.glsl b/files/shaders/openmw_vertex.glsl new file mode 100644 index 0000000000..cc14f3a0e3 --- /dev/null +++ b/files/shaders/openmw_vertex.glsl @@ -0,0 +1,35 @@ +#version 120 + +#include "openmw_vertex.h.glsl" + +uniform mat4 projectionMatrix; + +vec4 mw_modelToClip(vec4 pos) +{ + return projectionMatrix * mw_modelToView(pos); +} + +vec4 mw_modelToView(vec4 pos) +{ + return gl_ModelViewMatrix * pos; +} + +vec4 mw_viewToClip(vec4 pos) +{ + return projectionMatrix * pos; +} + +vec4 mw_viewStereoAdjust(vec4 pos) +{ + return pos; +} + +mat4 mw_viewMatrix() +{ + return gl_ModelViewMatrix; +} + +mat4 mw_projectionMatrix() +{ + return projectionMatrix; +} diff --git a/files/shaders/openmw_vertex.h.glsl b/files/shaders/openmw_vertex.h.glsl new file mode 100644 index 0000000000..00feb9310e --- /dev/null +++ b/files/shaders/openmw_vertex.h.glsl @@ -0,0 +1,8 @@ +@link "openmw_vertex.glsl" + +vec4 mw_modelToClip(vec4 pos); +vec4 mw_modelToView(vec4 pos); +vec4 mw_viewToClip(vec4 pos); +vec4 mw_viewStereoAdjust(vec4 pos); +mat4 mw_viewMatrix(); +mat4 mw_projectionMatrix(); \ No newline at end of file diff --git a/files/shaders/sky_vertex.glsl b/files/shaders/sky_vertex.glsl index 9c676140ac..8ff9c0f156 100644 --- a/files/shaders/sky_vertex.glsl +++ b/files/shaders/sky_vertex.glsl @@ -1,8 +1,9 @@ #version 120 +#include "openmw_vertex.h.glsl" + #include "skypasses.glsl" -uniform mat4 projectionMatrix; uniform int pass; varying vec4 passColor; @@ -10,7 +11,7 @@ varying vec2 diffuseMapUV; void main() { - gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + gl_Position = mw_modelToClip(gl_Vertex); passColor = gl_Color; if (pass == PASS_CLOUDS) diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index b46b299e2b..7f248d5a6b 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,5 +1,7 @@ #version 120 +#include "openmw_vertex.h.glsl" + #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -8,8 +10,6 @@ #extension GL_EXT_gpu_shader4: require #endif -uniform mat4 projectionMatrix; - varying vec2 uv; varying float euclideanDepth; varying float linearDepth; @@ -31,9 +31,9 @@ varying vec3 passNormal; void main(void) { - gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + gl_Position = mw_modelToClip(gl_Vertex); - vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + vec4 viewPos = mw_modelToView(gl_Vertex); gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index eaf52c1189..1090531f23 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -1,5 +1,7 @@ #version 120 +#include "openmw_fragment.h.glsl" + #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -207,12 +209,6 @@ varying float linearDepth; uniform sampler2D normalMap; -uniform sampler2D reflectionMap; -#if REFRACTION -uniform sampler2D refractionMap; -uniform sampler2D refractionDepthMap; -#endif - uniform float osg_SimulationTime; uniform float near; @@ -299,14 +295,14 @@ void main(void) vec2 screenCoordsOffset = normal.xy * REFL_BUMP; #if REFRACTION - float depthSample = linearizeDepth(texture2D(refractionDepthMap,screenCoords).x) * radialise; - float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-screenCoordsOffset).x) * radialise; + float depthSample = linearizeDepth(mw_sampleRefractionDepthMap(screenCoords)) * radialise; + float depthSampleDistorted = linearizeDepth(mw_sampleRefractionDepthMap(screenCoords-screenCoordsOffset)) * radialise; float surfaceDepth = linearizeDepth(gl_FragCoord.z) * radialise; float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #endif // reflection - vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; + vec3 reflection = mw_sampleReflectionMap(screenCoords + screenCoordsOffset).rgb; // specular float specular = pow(max(dot(reflect(vVec, normal), lVec), 0.0),SPEC_HARDNESS) * shadow; @@ -324,7 +320,7 @@ void main(void) rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); // refraction - vec3 refraction = texture2D(refractionMap, screenCoords - screenCoordsOffset).rgb; + vec3 refraction = mw_sampleRefractionMap(screenCoords - screenCoordsOffset).rgb; vec3 rawRefraction = refraction; // brighten up the refraction underwater diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 3a6c352ac8..b09d3b54ae 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -1,6 +1,6 @@ #version 120 -uniform mat4 projectionMatrix; +#include "openmw_vertex.h.glsl" varying vec4 position; varying float linearDepth; @@ -10,11 +10,11 @@ varying float linearDepth; void main(void) { - gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + gl_Position = mw_modelToClip(gl_Vertex); position = gl_Vertex; - vec4 viewPos = gl_ModelViewMatrix * gl_Vertex; + vec4 viewPos = mw_modelToView(gl_Vertex); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); setupShadowCoords(viewPos, normalize((gl_NormalMatrix * gl_Normal).xyz)); From 1fd4ac91689b8a480b582d57e806ce546993652c Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 23 Apr 2022 23:14:02 +0200 Subject: [PATCH 2353/2859] [Lua] Update openmw_aux.util --- files/builtin_scripts/openmw_aux/util.lua | 117 ++++++++++++++++------ 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/files/builtin_scripts/openmw_aux/util.lua b/files/builtin_scripts/openmw_aux/util.lua index 4832517418..b360383066 100644 --- a/files/builtin_scripts/openmw_aux/util.lua +++ b/files/builtin_scripts/openmw_aux/util.lua @@ -6,52 +6,107 @@ local aux_util = {} ---- --- Works like `tostring` but shows also content of tables. --- @function [parent=#util] deepToString --- @param #any value The value to conver to string --- @param #number maxDepth Max depth of tables unpacking (optional, 2 by default) -function aux_util.deepToString(val, level, prefix) - level = (level or 1) - 1 +local function deepToString(val, level, prefix) + local level = (level or 1) - 1 local ok, iter, t = pcall(function() return pairs(val) end) if level < 0 or not ok then return tostring(val) end - local prefix = prefix or '' local newPrefix = prefix .. ' ' local strs = {tostring(val) .. ' {\n'} for k, v in iter, t do - strs[#strs + 1] = newPrefix .. tostring(k) .. ' = ' .. aux_util.deepToString(v, level, newPrefix) .. ',\n' + strs[#strs + 1] = newPrefix .. tostring(k) .. ' = ' .. deepToString(v, level, newPrefix) .. ',\n' end strs[#strs + 1] = prefix .. '}' return table.concat(strs) end -------------------------------------------------------------------------------- --- Finds the nearest object to the given point in the given list. --- Ignores cells, uses only coordinates. Returns the nearest object, --- and the distance to it. If objectList is empty, returns nil. --- @function [parent=#util] findNearestTo --- @param openmw.util#Vector3 point --- @param openmw.core#ObjectList objectList --- @param #number minDist (optional) ignore objects that are closer than minDist --- @param #number maxDist (optional) ignore objects that are farther than maxDist --- @return openmw.core#GameObject, #number the nearest object and the distance -function aux_util.findNearestTo(point, objectList, minDist, maxDist) - local res = nil - local resDist = nil - local minDist = minDist or 0 - for i = 1, #objectList do - local obj = objectList[i] - local dist = (obj.position - point):length() - if dist >= minDist and (not res or dist < resDist) then - res = obj - resDist = dist +--- +-- Works like `tostring` but shows also content of tables. +-- @function [parent=#util] deepToString +-- @param #any value The value to convert to string +-- @param #number maxDepth Max depth of tables unpacking (optional, 1 by default) +function aux_util.deepToString(value, maxDepth) + return deepToString(value, maxDepth, '') +end + +--- +-- Finds the element the minimizes `scoreFn`. +-- @function [parent=#util] findMinScore +-- @param #table array Any array +-- @param #function scoreFn Function that returns either nil/false or a number for each element of the array +-- @return element The element the minimizes `scoreFn` +-- @return #number score The output of `scoreFn(element)` +-- @return #number index The index of the chosen element in the array +-- @usage -- Find the nearest NPC +-- local nearestNPC, distToNPC = aux_util.findMinScore( +-- nearby.actors, +-- function(actor) +-- return actor.type == types.NPC and (self.position - actor.position):length() +-- end) +function aux_util.findMinScore(array, scoreFn) + local bestValue, bestScore, bestIndex + for i = 1, #array do + local v = array[i] + local score = scoreFn(v) + if score and (not bestScore or bestScore > score) then + bestValue, bestScore, bestIndex = v, score, i end end - if res and (not maxDist or resDist <= maxDist) then - return res, resDist + return bestValue, bestScore, bestIndex +end + +--- +-- Computes `scoreFn` for each element of `array` and filters out elements with false and nil results. +-- @function [parent=#util] mapFilter +-- @param #table array Any array +-- @param #function scoreFn Filter function +-- @return #table Output array +-- @return #table Array of the same size with corresponding scores +-- @usage -- Find all NPCs in `nearby.actors` +-- local NPCs = aux_util.mapFilter( +-- nearby.actors, +-- function(actor) return actor.type == types.NPC end) +function aux_util.mapFilter(array, scoreFn) + local res = {} + local scores = {} + for i = 1, #array do + local v = array[i] + local f = scoreFn(v) + if f then + scores[#res + 1] = f + res[#res + 1] = v + end + end + return res, scores +end + +--- +-- Filters and sorts `array` by the scores calculated by `scoreFn`. The same as `aux_util.mapFilter`, but the result is sorted. +-- @function [parent=#util] mapFilterSort +-- @param #table array Any array +-- @param #function scoreFn Filter function +-- @return #table Output array +-- @return #table Array of the same size with corresponding scores +-- @usage -- Find all NPCs in `nearby.actors` and sort them by distances +-- local NPCs, distances = aux_util.mapFilterSort( +-- nearby.actors, +-- function(actor) +-- return actor.type == types.NPC and (self.position - actor.position):length() +-- end) +function aux_util.mapFilterSort(array, scoreFn) + local values, scores = aux_util.mapFilter(array, scoreFn) + local size = #values + local ids = {} + for i = 1, size do ids[i] = i end + table.sort(ids, function(i, j) return scores[i] < scores[j] end) + local sortedValues = {} + local sortedScores = {} + for i = 1, size do + sortedValues[i] = values[ids[i]] + sortedScores[i] = scores[ids[i]] end + return sortedValues, sortedScores end return aux_util From 164458dc568c1bedab0dea8b4039ad7b3bc1970e Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Wed, 27 Apr 2022 17:37:07 +0200 Subject: [PATCH 2354/2859] shader fixes --- files/shaders/debug_vertex.glsl | 2 +- files/shaders/groundcover_vertex.glsl | 4 ++-- files/shaders/nv_default_vertex.glsl | 4 ++-- files/shaders/objects_vertex.glsl | 4 ++-- files/shaders/terrain_vertex.glsl | 4 ++-- files/shaders/water_fragment.glsl | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/files/shaders/debug_vertex.glsl b/files/shaders/debug_vertex.glsl index 094e866f9e..fd41a6ff48 100644 --- a/files/shaders/debug_vertex.glsl +++ b/files/shaders/debug_vertex.glsl @@ -1,6 +1,6 @@ #version 120 -#include "openmw_vertex.glsl" +#include "openmw_vertex.h.glsl" centroid varying vec4 passColor; diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 2914552139..e707954e81 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_vertex.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -10,6 +8,8 @@ #extension GL_EXT_gpu_shader4: require #endif +#include "openmw_vertex.h.glsl" + #define GROUNDCOVER attribute vec4 aOffset; diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index d27b9b2cbd..c0d28f3e34 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_vertex.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -10,6 +8,8 @@ #extension GL_EXT_gpu_shader4: require #endif +#include "openmw_vertex.h.glsl" + #if @diffuseMap varying vec2 diffuseMapUV; #endif diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index df60d8ea49..e8976692a2 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_vertex.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -10,6 +8,8 @@ #extension GL_EXT_gpu_shader4: require #endif +#include "openmw_vertex.h.glsl" + #if @diffuseMap varying vec2 diffuseMapUV; #endif diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 7f248d5a6b..5519001219 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_vertex.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -10,6 +8,8 @@ #extension GL_EXT_gpu_shader4: require #endif +#include "openmw_vertex.h.glsl" + varying vec2 uv; varying float euclideanDepth; varying float linearDepth; diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 1090531f23..bf35ca78ca 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_fragment.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -10,6 +8,8 @@ #extension GL_EXT_gpu_shader4: require #endif +#include "openmw_fragment.h.glsl" + #define REFRACTION @refraction_enabled #define RAIN_RIPPLE_DETAIL @rain_ripple_detail From ad1ab1c8803f64834b9e9521d624f65358b7f8ba Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Wed, 27 Apr 2022 19:51:54 +0000 Subject: [PATCH 2355/2859] Follow up to !192 --- apps/launcher/datafilespage.cpp | 12 ++++++---- files/ui/datafilespage.ui | 40 ++++++++++++++++----------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 9268c8e142..fe6d96b51a 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -44,10 +44,10 @@ namespace QDir currentDir(path); if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty() || !currentDir.entryInfoList(dirFilter, QDir::Dirs | QDir::NoDotAndDotDot).empty()) - dirs.push_back(currentDir.absolutePath()); + dirs.push_back(currentDir.canonicalPath()); for (const auto& subdir : currentDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) - contentSubdirs(subdir.absoluteFilePath(), dirs); + contentSubdirs(subdir.canonicalFilePath(), dirs); } } @@ -236,6 +236,10 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) if (!globalDataDir.isEmpty()) directories.insert(0, globalDataDir); + // normalize user supplied directories: resolve symlink, convert to native separator, make absolute + for (auto& currentDir : directories) + currentDir = QDir(QDir::cleanPath(currentDir)).canonicalPath(); + // add directories, archives and content files directories.removeDuplicates(); for (const auto& currentDir : directories) @@ -361,7 +365,7 @@ QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const QStringList dirList; for (int i = 0; i < ui.directoryListWidget->count(); ++i) { - if (ui.directoryListWidget->item(i)->background() != Qt::gray) + if (ui.directoryListWidget->item(i)->flags() & Qt::ItemIsEnabled) dirList.append(ui.directoryListWidget->item(i)->text()); } return dirList; @@ -581,7 +585,7 @@ QString Launcher::DataFilesPage::selectDirectory() if (fileDialog.exec() == QDialog::Rejected) return {}; - return fileDialog.selectedFiles()[0]; + return QDir(fileDialog.selectedFiles()[0]).canonicalPath(); } diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index dfcf02fced..813d0109ee 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -17,8 +17,25 @@ - 2 + 0 + + + Content Files + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">note: content files that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + + + + + Data Directories @@ -137,23 +154,6 @@ - - - Content Files - - - - - - - - - <html><head/><body><p><span style=" font-style:italic;">note: content files that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> - - - - - Navigation mesh cache @@ -212,7 +212,7 @@ - + Max size @@ -261,7 +261,7 @@ false - + 6 From c7ab67c2c1ff9193b72e3694e81fdb7b827a9462 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 26 Apr 2022 22:36:03 +0200 Subject: [PATCH 2356/2859] Allow relative paths in openmw.cfg; support --replace=config. --- apps/bulletobjecttool/main.cpp | 2 +- apps/navmeshtool/main.cpp | 2 +- apps/opencs/editor.cpp | 8 +- apps/openmw/main.cpp | 4 +- apps/openmw/options.cpp | 4 +- apps/openmw_test_suite/mwworld/test_store.cpp | 8 +- components/config/gamesettings.cpp | 4 +- components/files/configurationmanager.cpp | 185 ++++++++++-------- components/files/configurationmanager.hpp | 30 +-- 9 files changed, 130 insertions(+), 117 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index c80883fe78..36ec8df9ed 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -141,7 +141,7 @@ namespace if (!local.empty()) dataDirs.push_back(std::move(local)); - config.processPaths(dataDirs); + config.filterOutNonExistingPaths(dataDirs); const auto fsStrict = variables["fs-strict"].as(); const auto resDir = variables["resources"].as(); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 643c14af0a..e024a35530 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -133,7 +133,7 @@ namespace NavMeshTool if (!local.empty()) dataDirs.push_back(std::move(local)); - config.processPaths(dataDirs); + config.filterOutNonExistingPaths(dataDirs); const auto fsStrict = variables["fs-strict"].as(); const auto resDir = variables["resources"].as(); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 40defda4e2..d8672cd84b 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -137,10 +137,12 @@ std::pair > CS::Editor::readConfi Files::PathContainer::value_type local(variables["data-local"].as()); if (!local.empty()) + { + boost::filesystem::create_directories(local); dataLocal.push_back(local); - - mCfgMgr.processPaths (dataDirs); - mCfgMgr.processPaths (dataLocal, true); + } + mCfgMgr.filterOutNonExistingPaths(dataDirs); + mCfgMgr.filterOutNonExistingPaths(dataLocal); if (!dataLocal.empty()) mLocal = dataLocal[0]; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 1dfa2a6494..a0967ec14b 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -37,8 +37,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat typedef std::vector StringsVector; bpo::options_description desc = OpenMW::makeOptionsDescription(); - Files::ConfigurationManager::addCommonOptions(desc); - bpo::variables_map variables; Files::parseArgs(argc, argv, variables, desc); @@ -82,7 +80,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (!local.empty()) dataDirs.push_back(local); - cfgMgr.processPaths(dataDirs); + cfgMgr.filterOutNonExistingPaths(dataDirs); engine.setResourceDir(variables["resources"].as()); engine.setDataDirs(dataDirs); diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp index dbf910b70f..f557e42282 100644 --- a/apps/openmw/options.cpp +++ b/apps/openmw/options.cpp @@ -16,14 +16,12 @@ namespace OpenMW bpo::options_description makeOptionsDescription() { bpo::options_description desc("Syntax: openmw \nAllowed options"); + Files::ConfigurationManager::addCommonOptions(desc); desc.add_options() ("help", "print help message") ("version", "print version information and quit") - ("replace", bpo::value()->default_value(StringsVector(), "") - ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") - ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index c15e238c9b..ab1fdd4995 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -71,12 +71,14 @@ struct ContentFileTest : public ::testing::Test } Files::PathContainer::value_type local(variables["data-local"].as()); - if (!local.empty()) { + if (!local.empty()) + { + boost::filesystem::create_directories(local); dataLocal.push_back(local); } - mConfigurationManager.processPaths (dataDirs); - mConfigurationManager.processPaths (dataLocal, true); + mConfigurationManager.filterOutNonExistingPaths(dataDirs); + mConfigurationManager.filterOutNonExistingPaths(dataLocal); if (!dataLocal.empty()) dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 6253d53f45..0ec13abe36 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -28,7 +28,7 @@ void Config::GameSettings::validatePaths() } // Parse the data dirs to convert the tokenized paths - mCfgMgr.processPaths(dataDirs); + mCfgMgr.processPaths(dataDirs, /*basePath=*/""); mDataDirs.clear(); for (auto & dataDir : dataDirs) { @@ -54,7 +54,7 @@ void Config::GameSettings::validatePaths() QByteArray bytes = local.toUtf8(); dataDirs.push_back(Files::PathContainer::value_type(std::string(bytes.constData(), bytes.length()))); - mCfgMgr.processPaths(dataDirs); + mCfgMgr.processPaths(dataDirs, /*basePath=*/""); if (!dataDirs.empty()) { QString path = QString::fromUtf8(dataDirs.front().string().c_str()); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 80426586fe..74fe03b3e2 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -11,6 +11,8 @@ namespace Files { +namespace bpo = boost::program_options; + static const char* const openmwCfgFile = "openmw.cfg"; #if defined(_WIN32) || defined(__WINDOWS__) @@ -31,7 +33,6 @@ ConfigurationManager::ConfigurationManager(bool silent) setupTokensMapping(); // Initialize with fixed paths, will be overridden in `readConfiguration`. - mLogPath = mFixedPath.getUserConfigPath(); mUserDataPath = mFixedPath.getUserDataPath(); mScreenshotPath = mFixedPath.getUserDataPath() / "screenshots"; } @@ -48,14 +49,25 @@ void ConfigurationManager::setupTokensMapping() mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } -void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, - boost::program_options::options_description& description, bool quiet) +static bool hasReplaceConfig(const bpo::variables_map& variables) +{ + if (variables["replace"].empty()) + return false; + for (const std::string& var : variables["replace"].as>()) + { + if (var == "config") + return true; + } + return false; +} + +void ConfigurationManager::readConfiguration(bpo::variables_map& variables, + const bpo::options_description& description, bool quiet) { - using ParsedConfigFile = bpo::basic_parsed_options; bool silent = mSilent; mSilent = quiet; - std::optional config = loadConfig(mFixedPath.getLocalPath(), description); + std::optional config = loadConfig(mFixedPath.getLocalPath(), description); if (config) mActiveConfigPaths.push_back(mFixedPath.getLocalPath()); else @@ -73,9 +85,10 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m std::stack extraConfigDirs; addExtraConfigDirs(extraConfigDirs, variables); - addExtraConfigDirs(extraConfigDirs, *config); + if (!hasReplaceConfig(variables)) + addExtraConfigDirs(extraConfigDirs, *config); - std::vector parsedOptions{*std::move(config)}; + std::vector parsedConfigs{*std::move(config)}; std::set alreadyParsedPaths; // needed to prevent infinite loop in case of a circular link alreadyParsedPaths.insert(boost::filesystem::path(mActiveConfigPaths.front())); @@ -92,20 +105,35 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m alreadyParsedPaths.insert(path); mActiveConfigPaths.push_back(path); config = loadConfig(path, description); - if (!config) - continue; - addExtraConfigDirs(extraConfigDirs, *config); - parsedOptions.push_back(*std::move(config)); + if (config && hasReplaceConfig(*config) && parsedConfigs.size() > 1) + { + mActiveConfigPaths.resize(1); + parsedConfigs.resize(1); + Log(Debug::Info) << "Skipping previous configs except " << (mActiveConfigPaths.front() / "openmw.cfg") << + " due to replace=config in " << (path / "openmw.cfg"); + } + mActiveConfigPaths.push_back(path); + if (config) + { + addExtraConfigDirs(extraConfigDirs, *config); + parsedConfigs.push_back(*std::move(config)); + } } - for (auto it = parsedOptions.rbegin(); it != parsedOptions.rend(); ++it) + for (auto it = parsedConfigs.rbegin(); it != parsedConfigs.rend(); ++it) { auto composingVariables = separateComposingVariables(variables, description); - boost::program_options::store(std::move(*it), variables); + for (auto& [k, v] : *it) + { + auto it = variables.find(k); + if (it == variables.end()) + variables.insert({k, v}); + else if (it->second.defaulted()) + it->second = v; + } mergeComposingVariables(variables, composingVariables, description); } - mLogPath = mActiveConfigPaths.back(); mUserDataPath = variables["user-data"].as(); if (mUserDataPath.empty()) { @@ -113,7 +141,6 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m Log(Debug::Warning) << "Error: `user-data` is not specified"; mUserDataPath = mFixedPath.getUserDataPath(); } - processPath(mUserDataPath, true); mScreenshotPath = mUserDataPath / "screenshots"; boost::filesystem::create_directories(getUserConfigPath()); @@ -123,18 +150,6 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m if (!boost::filesystem::is_directory(mScreenshotPath)) mScreenshotPath = mUserDataPath; - if (!quiet && !variables["replace"].empty()) - { - for (const std::string& var : variables["replace"].as>()) - { - if (var == "config") - { - Log(Debug::Warning) << "replace=config is not allowed and was ignored"; - break; - } - } - } - if (!quiet) { Log(Debug::Info) << "Logs dir: " << getUserConfigPath().string(); @@ -146,42 +161,30 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m } void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, - const bpo::basic_parsed_options& options) const -{ - boost::program_options::variables_map variables; - boost::program_options::store(options, variables); - boost::program_options::notify(variables); - addExtraConfigDirs(dirs, variables); -} - -void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, - const boost::program_options::variables_map& variables) const + const bpo::variables_map& variables) const { auto configIt = variables.find("config"); if (configIt == variables.end()) return; Files::PathContainer newDirs = asPathContainer(configIt->second.as()); - processPaths(newDirs); for (auto it = newDirs.rbegin(); it != newDirs.rend(); ++it) dirs.push(*it); } -void ConfigurationManager::addCommonOptions(boost::program_options::options_description& description) +void ConfigurationManager::addCommonOptions(bpo::options_description& description) { - Files::MaybeQuotedPath defaultUserData; - static_cast(defaultUserData) = boost::filesystem::path("?userdata?"); - description.add_options() ("config", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "") ->multitoken()->composing(), "additional config directories") - ("user-data", bpo::value()->default_value(defaultUserData, ""), + ("replace", bpo::value>()->default_value(std::vector(), "")->multitoken()->composing(), + "settings where the values from the current source should replace those from lower-priority sources instead of being appended") + ("user-data", bpo::value()->default_value(Files::MaybeQuotedPath(), ""), "set user data directory (used for saves, screenshots, etc)"); } -boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, - boost::program_options::options_description& description) +bpo::variables_map separateComposingVariables(bpo::variables_map & variables, const bpo::options_description& description) { - boost::program_options::variables_map composingVariables; + bpo::variables_map composingVariables; for (auto itr = variables.begin(); itr != variables.end();) { if (description.find(itr->first, false).semantic()->is_composing()) @@ -195,8 +198,8 @@ boost::program_options::variables_map separateComposingVariables(boost::program_ return composingVariables; } -void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, - boost::program_options::options_description& description) +void mergeComposingVariables(bpo::variables_map& first, bpo::variables_map& second, + const bpo::options_description& description) { // There are a few places this assumes all variables are present in second, but it's never crashed in the wild, so it looks like that's guaranteed. std::set replacedVariables; @@ -260,16 +263,18 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost Log(Debug::Error) << "Unexpected composing variable type. Curse boost and their blasted arcane templates."; } } - boost::program_options::notify(first); } -void ConfigurationManager::processPath(boost::filesystem::path& path, bool create) const +void ConfigurationManager::processPath(boost::filesystem::path& path, const boost::filesystem::path& basePath) const { std::string str = path.string(); - // Do nothing if the path doesn't start with a token if (str.empty() || str[0] != '?') + { + if (!path.is_absolute()) + path = basePath / path; return; + } std::string::size_type pos = str.find('?', 1); if (pos != std::string::npos && pos != 0) @@ -294,37 +299,49 @@ void ConfigurationManager::processPath(boost::filesystem::path& path, bool creat path.clear(); } } +} - if (!boost::filesystem::is_directory(path) && create) - { - try - { - boost::filesystem::create_directories(path); - } - catch (...) {} - } +void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, const boost::filesystem::path& basePath) const +{ + for (auto& path : dataDirs) + processPath(path, basePath); } -void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) const +void ConfigurationManager::processPaths(boost::program_options::variables_map& variables, const boost::filesystem::path& basePath) const { - for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) + for (auto& [name, var] : variables) { - processPath(*it, create); - if (!boost::filesystem::is_directory(*it)) + if (var.defaulted()) + continue; + if (var.value().type() == typeid(MaybeQuotedPathContainer)) { - if (!mSilent) - Log(Debug::Warning) << "No such dir: " << *it; - (*it).clear(); + auto& pathContainer = boost::any_cast(var.value()); + for (MaybeQuotedPath& path : pathContainer) + processPath(path, basePath); + } + else if (var.value().type() == typeid(MaybeQuotedPath)) + { + boost::filesystem::path& path = boost::any_cast(var.value()); + processPath(path, basePath); } } +} +void ConfigurationManager::filterOutNonExistingPaths(Files::PathContainer& dataDirs) const +{ dataDirs.erase(std::remove_if(dataDirs.begin(), dataDirs.end(), - std::bind(&boost::filesystem::path::empty, std::placeholders::_1)), dataDirs.end()); + [this](const boost::filesystem::path& p) + { + bool exists = boost::filesystem::is_directory(p); + if (!exists && !mSilent) + Log(Debug::Warning) << "No such dir: " << p; + return !exists; + }), + dataDirs.end()); } -std::optional> ConfigurationManager::loadConfig( - const boost::filesystem::path& path, - boost::program_options::options_description& description) +std::optional ConfigurationManager::loadConfig( + const boost::filesystem::path& path, const bpo::options_description& description) const { boost::filesystem::path cfgFile(path); cfgFile /= std::string(openmwCfgFile); @@ -336,7 +353,12 @@ std::optional> ConfigurationManager::loadConfig( boost::filesystem::ifstream configFileStream(cfgFile); if (configFileStream.is_open()) - return Files::parse_config_file(configFileStream, description, true); + { + bpo::variables_map variables; + bpo::store(Files::parse_config_file(configFileStream, description, true), variables); + processPaths(variables, path); + return variables; + } else if (!mSilent) Log(Debug::Error) << "Loading failed."; } @@ -381,32 +403,23 @@ const boost::filesystem::path& ConfigurationManager::getInstallPath() const return mFixedPath.getInstallPath(); } -const boost::filesystem::path& ConfigurationManager::getLogPath() const -{ - return mLogPath; -} - const boost::filesystem::path& ConfigurationManager::getScreenshotPath() const { return mScreenshotPath; } -void parseArgs(int argc, const char* const argv[], boost::program_options::variables_map& variables, - boost::program_options::options_description& description) +void parseArgs(int argc, const char* const argv[], bpo::variables_map& variables, + const bpo::options_description& description) { - boost::program_options::store( - boost::program_options::command_line_parser(argc, argv).options(description).allow_unregistered().run(), + bpo::store( + bpo::command_line_parser(argc, argv).options(description).allow_unregistered().run(), variables ); } -void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, - boost::program_options::options_description& description) +void parseConfig(std::istream& stream, bpo::variables_map& variables, const bpo::options_description& description) { - boost::program_options::store( - Files::parse_config_file(stream, description, true), - variables - ); + bpo::store(Files::parse_config_file(stream, description, true), variables); } std::istream& operator>> (std::istream& istream, MaybeQuotedPath& MaybeQuotedPath) diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 49844cca41..f45df612ef 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -25,11 +25,14 @@ struct ConfigurationManager virtual ~ConfigurationManager(); void readConfiguration(boost::program_options::variables_map& variables, - boost::program_options::options_description& description, bool quiet=false); + const boost::program_options::options_description& description, bool quiet=false); - void processPath(boost::filesystem::path& path, bool create = false) const; - void processPaths(Files::PathContainer& dataDirs, bool create = false) const; - ///< \param create Try creating the directory, if it does not exist. + void filterOutNonExistingPaths(Files::PathContainer& dataDirs) const; + + // Replaces tokens (`?local?`, `?global?`, etc.) in paths. Adds `basePath` prefix for relative paths. + void processPath(boost::filesystem::path& path, const boost::filesystem::path& basePath) const; + void processPaths(Files::PathContainer& dataDirs, const boost::filesystem::path& basePath) const; + void processPaths(boost::program_options::variables_map& variables, const boost::filesystem::path& basePath) const; /**< Fixed paths */ const boost::filesystem::path& getGlobalPath() const; @@ -44,7 +47,7 @@ struct ConfigurationManager const boost::filesystem::path& getCachePath() const; - const boost::filesystem::path& getLogPath() const; + const boost::filesystem::path& getLogPath() const { return getUserConfigPath(); } const boost::filesystem::path& getScreenshotPath() const; static void addCommonOptions(boost::program_options::options_description& description); @@ -55,14 +58,12 @@ struct ConfigurationManager typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; typedef std::map TokensMappingContainer; - std::optional> loadConfig( + std::optional loadConfig( const boost::filesystem::path& path, - boost::program_options::options_description& description); + const boost::program_options::options_description& description) const; void addExtraConfigDirs(std::stack& dirs, const boost::program_options::variables_map& variables) const; - void addExtraConfigDirs(std::stack& dirs, - const boost::program_options::basic_parsed_options& options) const; void setupTokensMapping(); @@ -70,7 +71,6 @@ struct ConfigurationManager FixedPathType mFixedPath; - boost::filesystem::path mLogPath; boost::filesystem::path mUserDataPath; boost::filesystem::path mScreenshotPath; @@ -80,16 +80,16 @@ struct ConfigurationManager }; boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map& variables, - boost::program_options::options_description& description); + const boost::program_options::options_description& description); void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, - boost::program_options::options_description& description); + const boost::program_options::options_description& description); void parseArgs(int argc, const char* const argv[], boost::program_options::variables_map& variables, - boost::program_options::options_description& description); + const boost::program_options::options_description& description); void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, - boost::program_options::options_description& description); + const boost::program_options::options_description& description); class MaybeQuotedPath : public boost::filesystem::path { @@ -101,6 +101,6 @@ typedef std::vector MaybeQuotedPathContainer; PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathContainer); -} /* namespace Cfg */ +} /* namespace Files */ #endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */ From bab5e56768a7ae47c0557f62f8dc4dd5a107013e Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 28 Apr 2022 15:19:54 +0200 Subject: [PATCH 2357/2859] Make the launcher dara directory and bsa list play nicer with dark themes. Known issue: the padding icon for disabled data directories is of wrong color. --- apps/launcher/datafilespage.cpp | 24 +++++++++---------- .../contentselector/model/contentmodel.cpp | 9 +++++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index fe6d96b51a..29042a72a6 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -247,16 +247,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) // add new achives files presents in current directory addArchivesFromDir(currentDir); - // Display new content with green background - QColor background; QString tooltip; - if (mNewDataDirs.contains(currentDir)) - { - tooltip += "Will be added to the current profile\n"; - background = Qt::green; - } - else - background = Qt::white; // add content files presents in current directory mSelector->addFiles(currentDir, mNewDataDirs.contains(currentDir)); @@ -265,7 +256,14 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) ui.directoryListWidget->addItem(currentDir); auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); - item->setBackground(background); + + // Display new content with green background + if (mNewDataDirs.contains(currentDir)) + { + tooltip += "Will be added to the current profile\n"; + item->setBackground(Qt::green); + item->setForeground(Qt::black); + } // deactivate data-local and global data directory: they are always included if (currentDir == mDataLocal || currentDir == globalDataDir) @@ -284,7 +282,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) { // Pad to correct vertical alignment QPixmap pixmap(QSize(200, 200)); // Arbitrary big number, will be scaled down to widget size - pixmap.fill(background); + pixmap.fill(ui.directoryListWidget->palette().base().color()); auto emptyIcon = QIcon(pixmap); item->setIcon(emptyIcon); } @@ -447,7 +445,6 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString { auto* item = ui.archiveListWidget->item(i); mKnownArchives.push_back(item->text()); - item->setBackground(Qt::white); } checkForDefaultProfile(); @@ -704,7 +701,10 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(selected); if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? + { ui.archiveListWidget->item(row)->setBackground(Qt::green); + ui.archiveListWidget->item(row)->setForeground(Qt::black); + } } void Launcher::DataFilesPage::addArchivesFromDir(const QString& path) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index f4760aaea6..c1d35ad1e2 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -169,6 +169,15 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int return QVariant(); } + case Qt::ForegroundRole: + { + if (isNew(file->fileName())) + { + return QVariant(QColor(Qt::black)); + } + return QVariant(); + } + case Qt::EditRole: case Qt::DisplayRole: { From dd5901d3517cc519819a2c45b37b1fd93e571ce8 Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Mon, 4 Apr 2022 22:51:23 +0200 Subject: [PATCH 2358/2859] Initial commit Multiview shaders. Refactor Frustum management Rewrite shared shadow map cull mask should respect stereo Stereo savegame screencap LocalMap refactoring use the vertex buffer hint instead of the display list patch to enable/disable display lists Character preview fixes --- .gitlab-ci.yml | 10 +- CI/before_script.msvc.sh | 49 ++- CMakeLists.txt | 5 + apps/openmw/engine.cpp | 55 ++- apps/openmw/engine.hpp | 20 + apps/openmw/mwbase/windowmanager.hpp | 6 + apps/openmw/mwgui/mapwindow.cpp | 4 + apps/openmw/mwgui/windowmanagerimp.cpp | 25 +- apps/openmw/mwgui/windowmanagerimp.hpp | 3 + apps/openmw/mwrender/characterpreview.cpp | 172 +++++--- apps/openmw/mwrender/characterpreview.hpp | 4 +- apps/openmw/mwrender/localmap.cpp | 302 +++++++------ apps/openmw/mwrender/localmap.hpp | 19 +- apps/openmw/mwrender/postprocessor.cpp | 191 +++++--- apps/openmw/mwrender/postprocessor.hpp | 14 +- apps/openmw/mwrender/renderingmanager.cpp | 108 ++++- apps/openmw/mwrender/renderingmanager.hpp | 5 +- apps/openmw/mwrender/screenshotmanager.cpp | 24 +- apps/openmw/mwrender/sky.cpp | 7 +- apps/openmw/mwrender/water.cpp | 14 +- cmake/CheckOsgMultiview.cmake | 26 ++ components/CMakeLists.txt | 6 +- components/sceneutil/color.cpp | 157 +++++++ components/sceneutil/color.hpp | 204 +++++++++ components/sceneutil/depth.cpp | 145 +++++- components/sceneutil/depth.hpp | 69 +++ components/sceneutil/mwshadowtechnique.cpp | 152 ++++++- components/sceneutil/mwshadowtechnique.hpp | 30 ++ components/sceneutil/rtt.cpp | 212 +++++++-- components/sceneutil/rtt.hpp | 51 ++- components/sceneutil/shadow.cpp | 3 + components/sceneutil/statesetupdater.cpp | 8 + components/sceneutil/statesetupdater.hpp | 5 + components/shader/shadervisitor.cpp | 3 + components/stereo/frustum.cpp | 162 +++++++ components/stereo/frustum.hpp | 76 ++++ components/stereo/multiview.cpp | 484 +++++++++++++++++++++ components/stereo/multiview.hpp | 85 ++++ components/stereo/stereomanager.cpp | 459 +++++++++++++++++++ components/stereo/stereomanager.hpp | 134 ++++++ components/stereo/types.cpp | 168 +++++++ components/stereo/types.hpp | 61 +++ components/terrain/material.cpp | 3 + files/settings-default.cfg | 57 +++ files/shaders/CMakeLists.txt | 2 + files/shaders/debug_fragment.glsl | 2 +- files/shaders/debug_vertex.glsl | 2 +- files/shaders/groundcover_fragment.glsl | 2 +- files/shaders/groundcover_vertex.glsl | 4 +- files/shaders/gui_fragment.glsl | 2 +- files/shaders/gui_vertex.glsl | 2 +- files/shaders/multiview_fragment.glsl | 48 ++ files/shaders/multiview_vertex.glsl | 80 ++++ files/shaders/nv_default_fragment.glsl | 2 +- files/shaders/nv_default_vertex.glsl | 5 +- files/shaders/nv_nolighting_fragment.glsl | 2 +- files/shaders/nv_nolighting_vertex.glsl | 2 +- files/shaders/objects_fragment.glsl | 2 +- files/shaders/objects_vertex.glsl | 5 +- files/shaders/s360_fragment.glsl | 2 +- files/shaders/s360_vertex.glsl | 2 +- files/shaders/shadowcasting_fragment.glsl | 2 +- files/shaders/shadowcasting_vertex.glsl | 2 +- files/shaders/sky_fragment.glsl | 2 +- files/shaders/sky_vertex.glsl | 18 +- files/shaders/terrain_fragment.glsl | 2 +- files/shaders/terrain_vertex.glsl | 5 +- files/shaders/water_fragment.glsl | 4 +- files/shaders/water_vertex.glsl | 2 +- 69 files changed, 3566 insertions(+), 434 deletions(-) create mode 100644 cmake/CheckOsgMultiview.cmake create mode 100644 components/sceneutil/color.cpp create mode 100644 components/sceneutil/color.hpp create mode 100644 components/stereo/frustum.cpp create mode 100644 components/stereo/frustum.hpp create mode 100644 components/stereo/multiview.cpp create mode 100644 components/stereo/multiview.hpp create mode 100644 components/stereo/stereomanager.cpp create mode 100644 components/stereo/stereomanager.hpp create mode 100644 components/stereo/types.cpp create mode 100644 components/stereo/types.hpp create mode 100644 files/shaders/multiview_fragment.glsl create mode 100644 files/shaders/multiview_vertex.glsl diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e81ffb481..fdfbdac13b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -424,6 +424,14 @@ Windows_Ninja_Engine_Release: <<: *engine-targets config: "Release" +Windows_Ninja_Engine_Release_MultiView: + extends: + - .Windows_Ninja_Base + variables: + <<: *engine-targets + multiview: "-M" + config: "Release" + Windows_Ninja_Engine_Debug: extends: - .Windows_Ninja_Base @@ -506,7 +514,7 @@ Windows_Ninja_Tests_RelWithDebInfo: - $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_DIR = "$(Get-Location)\ccache" - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview - cd MSVC2019_64 - cmake --build . --config $config --target ($targets.Split(',')) - ccache --show-stats diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 7c9ea20bd1..bdf7d24eb8 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -75,6 +75,7 @@ TEST_FRAMEWORK="" GOOGLE_INSTALL_ROOT="" INSTALL_PREFIX="." BUILD_BENCHMARKS="" +OSG_MULTIVIEW_BUILD="" ACTIVATE_MSVC="" SINGLE_CONFIG="" @@ -139,7 +140,10 @@ while [ $# -gt 0 ]; do b ) BUILD_BENCHMARKS=true ;; - + + M ) + OSG_MULTIVIEW_BUILD=true ;; + h ) cat < +#include +#include + #include #include @@ -40,6 +43,7 @@ #include #include +#include #include #include "mwinput/inputmanagerimp.hpp" @@ -251,6 +255,18 @@ namespace Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION); } }; + + class InitializeStereoOperation final : public osg::GraphicsOperation + { + public: + InitializeStereoOperation() : GraphicsOperation("InitializeStereoOperation", false) + {} + + void operator()(osg::GraphicsContext* graphicsContext) override + { + Stereo::Manager::instance().initializeStereo(graphicsContext); + } + }; } void OMW::Engine::executeLocalScripts() @@ -413,6 +429,9 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(nullptr) , mScreenCaptureOperation(nullptr) + , mSelectDepthFormatOperation(new SceneUtil::SelectDepthFormatOperation()) + , mSelectColorFormatOperation(new SceneUtil::Color::SelectColorFormatOperation()) + , mStereoManager(nullptr) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) @@ -448,6 +467,8 @@ OMW::Engine::~Engine() if (mScreenCaptureOperation != nullptr) mScreenCaptureOperation->stop(); + mStereoManager = nullptr; + mEnvironment.cleanup(); delete mScriptContext; @@ -640,6 +661,15 @@ void OMW::Engine::createWindow(Settings::Manager& settings) if (Debug::shouldDebugOpenGL()) realizeOperations->add(new Debug::EnableGLDebugOperation()); + realizeOperations->add(mSelectDepthFormatOperation); + realizeOperations->add(mSelectColorFormatOperation); + + if (Stereo::getStereo()) + { + realizeOperations->add(new InitializeStereoOperation()); + Stereo::setVertexBufferHint(); + } + mViewer->realize(); mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); @@ -674,11 +704,13 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setStateManager ( std::make_unique (mCfgMgr.getUserDataPath() / "saves", mContentFiles)); - createWindow(settings); + mStereoManager = std::make_unique(mViewer); - osg::ref_ptr rootNode (new osg::Group); + osg::ref_ptr rootNode(new osg::Group); mViewer->setSceneData(rootNode); + createWindow(settings); + mVFS = std::make_unique(mFSStrict); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); @@ -757,22 +789,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) osg::ref_ptr exts = osg::GLExtensions::Get(0, false); bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); - bool enableReverseZ = false; - - if (Settings::Manager::getBool("reverse z", "Camera")) - { - if (exts && exts->isClipControlSupported) - { - enableReverseZ = true; - Log(Debug::Info) << "Using reverse-z depth buffer"; - } - else - Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer"; - } - else - Log(Debug::Info) << "Using standard depth buffer"; - - SceneUtil::AutoDepth::setReversed(enableReverseZ); #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 @@ -784,6 +800,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); guiRoot->setNodeMask(MWRender::Mask_GUI); + mStereoManager->disableStereoForNode(guiRoot); rootNode->addChild(guiRoot); auto windowMgr = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), @@ -794,7 +811,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setWindowManager (std::move(windowMgr)); auto inputMgr = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); - mEnvironment.setInputManager (std::move(inputMgr)); + mEnvironment.setInputManager(std::move(inputMgr)); // Create sound system mEnvironment.setSoundManager (std::make_unique(mVFS.get(), mUseSound)); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 5fe032d27e..d30bd33e80 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -39,6 +39,11 @@ namespace MWLua class LuaManager; } +namespace Stereo +{ + class Manager; +} + namespace Files { struct ConfigurationManager; @@ -49,6 +54,16 @@ namespace osgViewer class ScreenCaptureHandler; } +namespace SceneUtil +{ + class SelectDepthFormatOperation; + + namespace Color + { + class SelectColorFormatOperation; + } +} + struct SDL_Window; namespace OMW @@ -69,9 +84,14 @@ namespace OMW osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; osg::ref_ptr mScreenCaptureOperation; + osg::ref_ptr mSelectDepthFormatOperation; + osg::ref_ptr mSelectColorFormatOperation; std::string mCellName; std::vector mContentFiles; std::vector mGroundcoverFiles; + + std::unique_ptr mStereoManager; + bool mSkipMenu; bool mUseSound; bool mCompileAll; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 04f21906a0..f47ae24e87 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -367,6 +367,12 @@ namespace MWBase virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0; virtual void asyncPrepareSaveMap() = 0; + + /// Sets the cull masks for all applicable views + virtual void setCullMask(uint32_t mask) = 0; + + /// Same as viewer->getCamera()->getCullMask(), provided for consistency. + virtual uint32_t getCullMask() = 0; }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 428b925920..b15ff1d178 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -748,7 +748,11 @@ namespace MWGui // ------------------------------------------------------------------------------------------ MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) +#ifdef USE_OPENXR + : WindowPinnableBase("openmw_map_window_vr.layout") +#else : WindowPinnableBase("openmw_map_window.layout") +#endif , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) , mGlobalMap(nullptr) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 23c16082b4..94b80d3faf 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -572,17 +572,17 @@ namespace MWGui void WindowManager::enableScene(bool enable) { unsigned int disablemask = MWRender::Mask_GUI|MWRender::Mask_PreCompile; - if (!enable && mViewer->getCamera()->getCullMask() != disablemask) + if (!enable && getCullMask() != disablemask) { mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); - mOldCullMask = mViewer->getCamera()->getCullMask(); + mOldCullMask = getCullMask(); mViewer->getUpdateVisitor()->setTraversalMask(disablemask); - mViewer->getCamera()->setCullMask(disablemask); + setCullMask(disablemask); } - else if (enable && mViewer->getCamera()->getCullMask() == disablemask) + else if (enable && getCullMask() == disablemask) { mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask); - mViewer->getCamera()->setCullMask(mOldCullMask); + setCullMask(mOldCullMask); } } @@ -1226,6 +1226,21 @@ namespace MWGui updateVisible(); } + void WindowManager::setCullMask(uint32_t mask) + { + mViewer->getCamera()->setCullMask(mask); + + // We could check whether stereo is enabled here, but these methods are + // trivial and have no effect in mono or multiview so just call them regardless. + mViewer->getCamera()->setCullMaskLeft(mask); + mViewer->getCamera()->setCullMaskRight(mask); + } + + uint32_t WindowManager::getCullMask() + { + return mViewer->getCamera()->getCullMask(); + } + void WindowManager::popGuiMode(bool noSound) { if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 90e8d1b0d7..8ab50e32ed 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -582,6 +582,9 @@ namespace MWGui void handleScheduledMessageBoxes(); void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force); + + void setCullMask(uint32_t mask) override; + uint32_t getCullMask() override; }; } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 7cca787580..f2bbe10460 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -41,9 +42,10 @@ namespace MWRender class DrawOnceCallback : public SceneUtil::NodeCallback { public: - DrawOnceCallback () + DrawOnceCallback(osg::Node* subgraph) : mRendered(false) , mLastRenderedFrame(0) + , mSubgraph(subgraph) { } @@ -61,6 +63,9 @@ namespace MWRender nv->setFrameStamp(fs); + // Update keyframe controllers in the scene graph first... + // RTTNode does not continue update traversal, so manually continue the update traversal since we need it. + mSubgraph->accept(*nv); traverse(node, nv); nv->setFrameStamp(previousFramestamp); @@ -84,6 +89,7 @@ namespace MWRender private: bool mRendered; unsigned int mLastRenderedFrame; + osg::ref_ptr mSubgraph; }; @@ -138,6 +144,96 @@ namespace MWRender } }; + class CharacterPreviewRTTNode : public SceneUtil::RTTNode + { + static constexpr float fovYDegrees = 12.3f; + static constexpr float znear = 0.1f; + static constexpr float zfar = 10000.f; + + public: + CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) + : RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, StereoAwareness::Unaware_MultiViewShaders) + , mAspectRatio(static_cast(sizeX) / static_cast(sizeY)) + { + if (SceneUtil::AutoDepth::isReversed()) + mPerspectiveMatrix = static_cast(SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar)); + else + mPerspectiveMatrix = osg::Matrixf::perspective(fovYDegrees, mAspectRatio, znear, zfar); + mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix)); + mViewMatrix = osg::Matrixf::identity(); + setColorBufferInternalFormat(GL_RGBA); + } + + void setDefaults(osg::Camera* camera) override + { + + // hints that the camera is not relative to the master camera + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setViewport(0, 0, width(), height()); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setName("CharacterPreview"); + camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + camera->setCullMask(~(Mask_UpdateVisitor)); + SceneUtil::setCameraClearDepth(camera); + + // hints that the camera is not relative to the master camera + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar); + camera->setViewport(0, 0, width(), height()); + camera->setRenderOrder(osg::Camera::PRE_RENDER); +#ifdef OSG_HAS_MULTIVIEW + if (shouldDoTextureArray()) + { + auto* viewUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2); + auto* projUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2); + viewUniform->setElement(0, osg::Matrix::identity()); + viewUniform->setElement(1, osg::Matrix::identity()); + projUniform->setElement(0, mPerspectiveMatrix); + projUniform->setElement(1, mPerspectiveMatrix); + mGroup->getOrCreateStateSet()->addUniform(viewUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + mGroup->getOrCreateStateSet()->addUniform(projUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } +#endif + + camera->setNodeMask(Mask_RenderToTexture); + camera->addChild(mGroup); + }; + + void apply(osg::Camera* camera) override + { + if(mCameraStateset) + camera->setStateSet(mCameraStateset); + camera->setViewMatrix(mViewMatrix); + }; + + void addChild(osg::Node* node) + { + mGroup->addChild(node); + } + + void setCameraStateset(osg::StateSet* stateset) + { + mCameraStateset = stateset; + } + + void setViewMatrix(const osg::Matrixf& viewMatrix) + { + mViewMatrix = viewMatrix; + } + + osg::ref_ptr mGroup = new osg::Group; + osg::Matrixf mPerspectiveMatrix; + osg::Matrixf mViewMatrix; + osg::ref_ptr mCameraStateset; + float mAspectRatio; + }; + CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt) : mParent(parent) @@ -149,31 +245,11 @@ namespace MWRender , mSizeX(sizeX) , mSizeY(sizeY) { - mTexture = new osg::Texture2D; - mTexture->setTextureSize(sizeX, sizeY); - mTexture->setInternalFormat(GL_RGBA); - mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mTextureStateSet = new osg::StateSet; mTextureStateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA)); - mCamera = new osg::Camera; - // hints that the camera is not relative to the master camera - mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - mCamera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); - mCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - mCamera->setViewport(0, 0, sizeX, sizeY); - mCamera->setRenderOrder(osg::Camera::PRE_RENDER); - mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); - mCamera->setName("CharacterPreview"); - mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); - mCamera->setCullMask(~(Mask_UpdateVisitor)); - - mCamera->setNodeMask(Mask_RenderToTexture); - - SceneUtil::setCameraClearDepth(mCamera); + mRTTNode = new CharacterPreviewRTTNode(sizeX, sizeY); + mRTTNode->setNodeMask(Mask_RenderToTexture); bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP; @@ -191,14 +267,6 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - const float fovYDegrees = 12.3f; - const float aspectRatio = static_cast(sizeX) / static_cast(sizeY); - const float znear = 0.1f; - const float zfar = 10000.f; - mCamera->setProjectionMatrixAsPerspective(fovYDegrees, aspectRatio, znear, zfar); - osg::Matrixf projectionMatrix = SceneUtil::AutoDepth::isReversed() ? static_cast(SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, aspectRatio, znear, zfar)) : static_cast(mCamera->getProjectionMatrix()); - stateset->addUniform(new osg::Uniform("projectionMatrix", projectionMatrix)); - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); // assign large value to effectively turn off fog @@ -266,23 +334,22 @@ namespace MWRender lightManager->addChild(lightSource); - mCamera->addChild(lightManager); + mRTTNode->addChild(lightManager); mNode = new osg::PositionAttitudeTransform; lightManager->addChild(mNode); - mDrawOnceCallback = new DrawOnceCallback; - mCamera->addUpdateCallback(mDrawOnceCallback); + mDrawOnceCallback = new DrawOnceCallback(mRTTNode->mGroup); + mRTTNode->addUpdateCallback(mDrawOnceCallback); - mParent->addChild(mCamera); + mParent->addChild(mRTTNode); mCharacter.mCell = nullptr; } CharacterPreview::~CharacterPreview () { - mCamera->removeChildren(0, mCamera->getNumChildren()); - mParent->removeChild(mCamera); + mParent->removeChild(mRTTNode); } int CharacterPreview::getTextureWidth() const @@ -308,7 +375,7 @@ namespace MWRender osg::ref_ptr CharacterPreview::getTexture() { - return mTexture; + return static_cast(mRTTNode->getColorTexture(nullptr)); } void CharacterPreview::rebuild() @@ -325,7 +392,7 @@ namespace MWRender void CharacterPreview::redraw() { - mCamera->setNodeMask(Mask_RenderToTexture); + mRTTNode->setNodeMask(Mask_RenderToTexture); mDrawOnceCallback->redrawNextFrame(); } @@ -346,7 +413,7 @@ namespace MWRender osg::ref_ptr stateset = new osg::StateSet; mViewport = new osg::Viewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); stateset->setAttributeAndModes(mViewport); - mCamera->setStateSet(stateset); + mRTTNode->setCameraStateset(stateset); redraw(); } @@ -433,10 +500,11 @@ namespace MWRender // Set the traversal number from the last draw, so that the frame switch used for RigGeometry double buffering works correctly visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame()); - osg::Node::NodeMask nodeMask = mCamera->getNodeMask(); - mCamera->setNodeMask(~0u); - mCamera->accept(visitor); - mCamera->setNodeMask(nodeMask); + auto* camera = mRTTNode->getCamera(nullptr); + osg::Node::NodeMask nodeMask = camera->getNodeMask(); + camera->setNodeMask(~0u); + camera->accept(visitor); + camera->setNodeMask(nodeMask); if (intersector->containsIntersections()) { @@ -459,7 +527,8 @@ namespace MWRender mNode->setScale(scale); - mCamera->setViewMatrixAsLookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0,0,1)); + auto viewMatrix = osg::Matrixf::lookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0, 0, 1)); + mRTTNode->setViewMatrix(viewMatrix); } // -------------------------------------------------------------------------------------------------- @@ -492,7 +561,7 @@ namespace MWRender rebuild(); } - class UpdateCameraCallback : public SceneUtil::NodeCallback + class UpdateCameraCallback : public SceneUtil::NodeCallback { public: UpdateCameraCallback(osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) @@ -502,10 +571,10 @@ namespace MWRender { } - void operator()(osg::Camera* cam, osg::NodeVisitor* nv) + void operator()(CharacterPreviewRTTNode* node, osg::NodeVisitor* nv) { // Update keyframe controllers in the scene graph first... - traverse(cam, nv); + traverse(node, nv); // Now update camera utilizing the updated head position osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); @@ -514,7 +583,8 @@ namespace MWRender osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3 headOffset = worldMat.getTrans(); - cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1)); + auto viewMatrix = osg::Matrixf::lookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0, 0, 1)); + node->setViewMatrix(viewMatrix); } private: @@ -531,13 +601,13 @@ namespace MWRender // attach camera to follow the head node if (mUpdateCameraCallback) - mCamera->removeUpdateCallback(mUpdateCameraCallback); + mRTTNode->removeUpdateCallback(mUpdateCameraCallback); const osg::Node* head = mAnimation->getNode("Bip01 Head"); if (head) { mUpdateCameraCallback = new UpdateCameraCallback(head, mPosition, mLookAt); - mCamera->addUpdateCallback(mUpdateCameraCallback); + mRTTNode->addUpdateCallback(mUpdateCameraCallback); } else Log(Debug::Error) << "Error: Bip01 Head node not found"; diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 0d7c1959c3..a8777d8548 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -26,6 +26,7 @@ namespace MWRender class NpcAnimation; class DrawOnceCallback; + class CharacterPreviewRTTNode; class CharacterPreview { @@ -56,10 +57,9 @@ namespace MWRender osg::ref_ptr mParent; Resource::ResourceSystem* mResourceSystem; - osg::ref_ptr mTexture; osg::ref_ptr mTextureStateSet; - osg::ref_ptr mCamera; osg::ref_ptr mDrawOnceCallback; + osg::ref_ptr mRTTNode; osg::Vec3f mPosition; osg::Vec3f mLookAt; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 98eebf0b75..2fd3c8b7f7 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -15,12 +15,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -34,36 +36,6 @@ namespace { - - class CameraLocalUpdateCallback : public SceneUtil::NodeCallback - { - public: - CameraLocalUpdateCallback(MWRender::LocalMap* parent) - : mRendered(false) - , mParent(parent) - { - } - - void operator()(osg::Camera* node, osg::NodeVisitor*) - { - if (mRendered) - node->setNodeMask(0); - - if (!mRendered) - { - mRendered = true; - mParent->markForRemoval(node); - } - - // Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's, - // so it has been updated already. - } - - private: - bool mRendered; - MWRender::LocalMap* mParent; - }; - float square(float val) { return val*val; @@ -82,6 +54,28 @@ namespace namespace MWRender { + class LocalMapRenderToTexture: public SceneUtil::RTTNode + { + public: + LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, + float x, float y, const osg::Vec3d& upVector, float zmin, float zmax); + + void setDefaults(osg::Camera* camera) override; + + bool isActive() { return mActive; } + void setIsActive(bool active) { mActive = active; } + + osg::Node* mSceneRoot; + osg::Matrix mProjectionMatrix; + osg::Matrix mViewMatrix; + bool mActive; + }; + + class CameraLocalUpdateCallback : public SceneUtil::NodeCallback + { + public: + void operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv); + }; LocalMap::LocalMap(osg::Group* root) : mRoot(root) @@ -104,10 +98,8 @@ LocalMap::LocalMap(osg::Group* root) LocalMap::~LocalMap() { - for (auto& camera : mActiveCameras) - removeCamera(camera); - for (auto& camera : mCamerasPendingRemoval) - removeCamera(camera); + for (auto& rtt : mLocalMapRTTs) + mRoot->removeChild(rtt); } const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) @@ -173,93 +165,14 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) } } -osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) -{ - osg::ref_ptr camera (new osg::Camera); - - if (SceneUtil::AutoDepth::isReversed()) - camera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10)); - else - camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); - - camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); - camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - camera->setRenderOrder(osg::Camera::PRE_RENDER); - - camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); - camera->setNodeMask(Mask_RenderToTexture); - - // Disable small feature culling, it's not going to be reliable for this camera - osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING); - camera->setCullingMode(cullingMode); - - SceneUtil::setCameraClearDepth(camera); - - osg::ref_ptr stateset = new osg::StateSet; - stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(camera->getProjectionMatrix())), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - // assign large value to effectively turn off fog - // shaders don't respect glDisable(GL_FOG) - osg::ref_ptr fog (new osg::Fog); - fog->setStart(10000000); - fog->setEnd(10000000); - stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); - - osg::ref_ptr lightmodel = new osg::LightModel; - lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); - stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::ref_ptr light = new osg::Light; - light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f)); - light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f)); - light->setAmbient(osg::Vec4(0,0,0,1)); - light->setSpecular(osg::Vec4(0,0,0,0)); - light->setLightNum(0); - light->setConstantAttenuation(1.f); - light->setLinearAttenuation(0.f); - light->setQuadraticAttenuation(0.f); - - osg::ref_ptr lightSource = new osg::LightSource; - lightSource->setLight(light); - - lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); - - // override sun for local map - SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot.get()), light, stateset); - - camera->addChild(lightSource); - camera->setStateSet(stateset); - camera->setViewport(0, 0, mMapResolution, mMapResolution); - camera->setUpdateCallback(new CameraLocalUpdateCallback(this)); - - return camera; -} - -void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int y) +void LocalMap::setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax) { - osg::ref_ptr texture (new osg::Texture2D); - texture->setTextureSize(mMapResolution, mMapResolution); - texture->setInternalFormat(GL_RGB); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mLocalMapRTTs.emplace_back(new LocalMapRenderToTexture(mSceneRoot, mMapResolution, mMapWorldSize, left, top, upVector, zmin, zmax)); - SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, texture); + mRoot->addChild(mLocalMapRTTs.back()); - camera->addChild(mSceneRoot); - mRoot->addChild(camera); - mActiveCameras.push_back(camera); - - MapSegment& segment = mInterior? mInteriorSegments[std::make_pair(x, y)] : mExteriorSegments[std::make_pair(x, y)]; - segment.mMapTexture = texture; + MapSegment& segment = mInterior? mInteriorSegments[std::make_pair(segment_x, segment_y)] : mExteriorSegments[std::make_pair(segment_x, segment_y)]; + segment.mMapTexture = static_cast(mLocalMapRTTs.back()->getColorTexture(nullptr)); } void LocalMap::requestMap(const MWWorld::CellStore* cell) @@ -321,33 +234,19 @@ osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) return found->second.mFogOfWarTexture; } -void LocalMap::removeCamera(osg::Camera *cam) -{ - cam->removeChildren(0, cam->getNumChildren()); - mRoot->removeChild(cam); -} - -void LocalMap::markForRemoval(osg::Camera *cam) +void LocalMap::cleanupCameras() { - CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), cam); - if (found == mActiveCameras.end()) + auto it = mLocalMapRTTs.begin(); + while (it != mLocalMapRTTs.end()) { - Log(Debug::Error) << "Error: trying to remove an inactive camera"; - return; + if (!(*it)->isActive()) + { + mRoot->removeChild(*it); + it = mLocalMapRTTs.erase(it); + } + else + it++; } - mActiveCameras.erase(found); - mCamerasPendingRemoval.push_back(cam); -} - -void LocalMap::cleanupCameras() -{ - if (mCamerasPendingRemoval.empty()) - return; - - for (auto& camera : mCamerasPendingRemoval) - removeCamera(camera); - - mCamerasPendingRemoval.clear(); } void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) @@ -361,9 +260,9 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) float zmin = bound.center().z() - bound.radius(); float zmax = bound.center().z() + bound.radius(); - osg::ref_ptr camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, - osg::Vec3d(0,1,0), zmin, zmax); - setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); + setupRenderToTexture(cell->getCell()->getGridX(), cell->getCell()->getGridY(), + x * mMapWorldSize + mMapWorldSize / 2.f, y * mMapWorldSize + mMapWorldSize / 2.f, + osg::Vec3d(0, 1, 0), zmin, zmax); MapSegment& segment = mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; if (!segment.mFogOfWarImage) @@ -501,11 +400,8 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) osg::Vec2f pos = osg::Vec2f(rotatedCenter.x(), rotatedCenter.y()) + center; - osg::ref_ptr camera = createOrthographicCamera(pos.x(), pos.y(), - mMapWorldSize, mMapWorldSize, - osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); - - setupRenderToTexture(camera, x, y); + setupRenderToTexture(x, y, pos.x(), pos.y(), + osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); auto coords = std::make_pair(x,y); MapSegment& segment = mInteriorSegments[coords]; @@ -682,6 +578,7 @@ void LocalMap::MapSegment::createFogOfWarTexture() mFogOfWarTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mFogOfWarTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mFogOfWarTexture->setUnRefImageDataAfterApply(false); + mFogOfWarTexture->setImage(mFogOfWarImage); } void LocalMap::MapSegment::initFogOfWar() @@ -697,7 +594,6 @@ void LocalMap::MapSegment::initFogOfWar() memcpy(mFogOfWarImage->data(), &data[0], data.size()*4); createFogOfWarTexture(); - mFogOfWarTexture->setImage(mFogOfWarImage); } void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) @@ -730,7 +626,6 @@ void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) mFogOfWarImage->dirty(); createFogOfWarTexture(); - mFogOfWarTexture->setImage(mFogOfWarImage); mHasFogState = true; } @@ -762,4 +657,107 @@ void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const fog.mImageData = std::vector(data.begin(), data.end()); } +LocalMapRenderToTexture::LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, float x, float y, const osg::Vec3d& upVector, float zmin, float zmax) + : RTTNode(res, res, 0, false, 0, StereoAwareness::Unaware_MultiViewShaders) + , mSceneRoot(sceneRoot) + , mActive(true) +{ + if (SceneUtil::AutoDepth::isReversed()) + mProjectionMatrix = SceneUtil::getReversedZProjectionMatrixAsOrtho(-mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10); + else + mProjectionMatrix.makeOrtho(-mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10); + + mViewMatrix.makeLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); + + setUpdateCallback(new CameraLocalUpdateCallback); +} + +void LocalMapRenderToTexture::setDefaults(osg::Camera* camera) +{ + // Disable small feature culling, it's not going to be reliable for this camera + osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING | osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING); + camera->setCullingMode(cullingMode); + + SceneUtil::setCameraClearDepth(camera); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + + camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setCullMaskLeft(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setCullMaskRight(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setNodeMask(Mask_RenderToTexture); + camera->setProjectionMatrix(mProjectionMatrix); + camera->setViewMatrix(mViewMatrix); + + auto* stateset = camera->getOrCreateStateSet(); + + stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(mProjectionMatrix)), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + if (Stereo::getMultiview()) + { + auto* viewUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2); + auto* projUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2); + viewUniform->setElement(0, osg::Matrix::identity()); + viewUniform->setElement(1, osg::Matrix::identity()); + projUniform->setElement(0, mProjectionMatrix); + projUniform->setElement(1, mProjectionMatrix); + stateset->addUniform(viewUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->addUniform(projUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog(new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + + osg::ref_ptr lightmodel = new osg::LightModel; + lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); + stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + osg::ref_ptr light = new osg::Light; + light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f)); + light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f)); + light->setAmbient(osg::Vec4(0, 0, 0, 1)); + light->setSpecular(osg::Vec4(0, 0, 0, 0)); + light->setLightNum(0); + light->setConstantAttenuation(1.f); + light->setLinearAttenuation(0.f); + light->setQuadraticAttenuation(0.f); + + osg::ref_ptr lightSource = new osg::LightSource; + lightSource->setLight(light); + + lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + + // override sun for local map + SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); + + camera->addChild(lightSource); + camera->addChild(mSceneRoot); +} + +void CameraLocalUpdateCallback::operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv) +{ + if (!node->isActive()) + node->setNodeMask(0); + + if (node->isActive()) + { + node->setIsActive(false); + } + + // Rtt-nodes do not forward update traversal to their cameras so we can traverse safely. + // Traverse in case there are nested callbacks. + traverse(node, nv); +} + } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index f9ccd5a011..911671aee2 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -30,6 +30,8 @@ namespace osg namespace MWRender { + class LocalMapRenderToTexture; + /// /// \brief Local map rendering /// @@ -58,13 +60,6 @@ namespace MWRender osg::ref_ptr getFogOfWarTexture (int x, int y); - void removeCamera(osg::Camera* cam); - - /** - * Indicates a camera has been queued for rendering and can be cleaned up in the next frame. For internal use only. - */ - void markForRemoval(osg::Camera* cam); - /** * Removes cameras that have already been rendered. Should be called every frame to ensure that * we do not render the same map more than once. Note, this cleanup is difficult to implement in an @@ -104,11 +99,8 @@ namespace MWRender osg::ref_ptr mRoot; osg::ref_ptr mSceneRoot; - typedef std::vector< osg::ref_ptr > CameraVector; - - CameraVector mActiveCameras; - - CameraVector mCamerasPendingRemoval; + typedef std::vector< osg::ref_ptr > RTTVector; + RTTVector mLocalMapRTTs; typedef std::set > Grid; Grid mCurrentGrid; @@ -152,8 +144,7 @@ namespace MWRender void requestExteriorMap(const MWWorld::CellStore* cell); void requestInteriorMap(const MWWorld::CellStore* cell); - osg::ref_ptr createOrthographicCamera(float left, float top, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax); - void setupRenderToTexture(osg::ref_ptr camera, int x, int y); + void setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax); bool mInterior; osg::BoundingBox mBounds; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index afdec4f1b5..9fbc5de528 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -6,15 +6,20 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include +#include + #include "vismask.hpp" namespace @@ -38,8 +43,8 @@ namespace class CullCallback : public SceneUtil::NodeCallback { public: - CullCallback() - : mLastFrameNumber(0) + CullCallback(MWRender::PostProcessor* pp) + : mPostProcessor(pp) { } @@ -47,36 +52,21 @@ namespace { osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); - unsigned int frame = cv->getTraversalNumber(); - if (frame != mLastFrameNumber) + if (!mPostProcessor->getMsaaFbo()) { - mLastFrameNumber = frame; - - MWRender::PostProcessor* postProcessor = dynamic_cast(cv->getCurrentCamera()->getUserData()); - - if (!postProcessor) - { - Log(Debug::Error) << "Failed retrieving user data for master camera: FBO setup failed"; - traverse(node, cv); - return; - } - - if (!postProcessor->getMsaaFbo()) - { - renderStage->setFrameBufferObject(postProcessor->getFbo()); - } - else - { - renderStage->setMultisampleResolveFramebufferObject(postProcessor->getFbo()); - renderStage->setFrameBufferObject(postProcessor->getMsaaFbo()); - } + renderStage->setFrameBufferObject(mPostProcessor->getFbo()); + } + else + { + renderStage->setMultisampleResolveFramebufferObject(mPostProcessor->getFbo()); + renderStage->setFrameBufferObject(mPostProcessor->getMsaaFbo()); } traverse(node, cv); } private: - unsigned int mLastFrameNumber; + MWRender::PostProcessor* mPostProcessor; }; struct ResizedCallback : osg::GraphicsContext::ResizedCallback @@ -157,15 +147,13 @@ namespace MWRender PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode) : mViewer(viewer) , mRootNode(new osg::Group) - , mDepthFormat(GL_DEPTH24_STENCIL8_EXT) { bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); - if (!SceneUtil::AutoDepth::isReversed() && !softParticles) + if (!SceneUtil::AutoDepth::isReversed() && !softParticles && !Stereo::getStereo()) return; osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); - unsigned int contextID = gc->getState()->getContextID(); osg::GLExtensions* ext = gc->getState()->get(); constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: "; @@ -184,24 +172,19 @@ namespace MWRender if (SceneUtil::AutoDepth::isReversed()) { - if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) - mDepthFormat = GL_DEPTH32F_STENCIL8; - else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) - mDepthFormat = GL_DEPTH32F_STENCIL8_NV; - else + if(SceneUtil::AutoDepth::depthSourceType() != GL_FLOAT_32_UNSIGNED_INT_24_8_REV) { // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no // benefits if no floating point depth formats are supported. - Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; - - if (!softParticles) + if (!softParticles && !Stereo::getStereo()) return; } } - int width = viewer->getCamera()->getViewport()->width(); - int height = viewer->getCamera()->getViewport()->height(); + auto* traits = gc->getTraits(); + int width = traits->width; + int height = traits->height; createTexturesAndCamera(width, height); resize(width, height); @@ -210,15 +193,17 @@ namespace MWRender mRootNode->addChild(rootNode); mViewer->setSceneData(mRootNode); - // We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate - // RTT camera this would not be needed. - mViewer->getCamera()->addCullCallback(new CullCallback); - mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); - mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); + if (!Stereo::getStereo()) + { + // We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate + // RTT camera this would not be needed. + mViewer->getCamera()->addCullCallback(new CullCallback(this)); + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); mViewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mDepthTex); + } mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); - mViewer->getCamera()->setUserData(this); } void PostProcessor::resize(int width, int height) @@ -260,15 +245,89 @@ namespace MWRender mViewer->getCamera()->resize(width, height); mHUDCamera->resize(width, height); + + if (Stereo::getStereo()) + Stereo::Manager::instance().screenResolutionChanged(); } + class HUDCameraStatesetUpdater final : public SceneUtil::StateSetUpdater + { + public: + public: + HUDCameraStatesetUpdater(osg::ref_ptr HUDCamera, osg::ref_ptr program, osg::ref_ptr sceneTex) + : mHUDCamera(HUDCamera) + , mProgram(program) + , mSceneTex(sceneTex) + { + } + + void setDefaults(osg::StateSet* stateset) override + { + stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON); + stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("sceneTex", 0)); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + if (osg::DisplaySettings::instance()->getStereo()) + { + stateset->setAttribute(new osg::Viewport); + stateset->addUniform(new osg::Uniform("viewportIndex", 0)); + } + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + if (Stereo::getMultiview()) + { + auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); + stateset->setTextureAttributeAndModes(0, multiviewFbo->multiviewColorBuffer(), osg::StateAttribute::ON); + } + } + + void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override + { + auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); + stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(0), osg::StateAttribute::ON); + + auto viewport = static_cast(stateset->getAttribute(osg::StateAttribute::VIEWPORT)); + auto fullViewport = mHUDCamera->getViewport(); + viewport->setViewport( + 0, + 0, + fullViewport->width() / 2, + fullViewport->height() + ); + } + + void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override + { + auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); + stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(1), osg::StateAttribute::ON); + + auto viewport = static_cast(stateset->getAttribute(osg::StateAttribute::VIEWPORT)); + auto fullViewport = mHUDCamera->getViewport(); + viewport->setViewport( + fullViewport->width() / 2, + 0, + fullViewport->width() / 2, + fullViewport->height() + ); + } + + private: + osg::ref_ptr mHUDCamera; + osg::ref_ptr mProgram; + osg::ref_ptr mSceneTex; + }; + void PostProcessor::createTexturesAndCamera(int width, int height) { mDepthTex = new osg::Texture2D; mDepthTex->setTextureSize(width, height); - mDepthTex->setSourceFormat(GL_DEPTH_STENCIL_EXT); - mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_UNSIGNED_INT_24_8_EXT); - mDepthTex->setInternalFormat(mDepthFormat); + mDepthTex->setSourceFormat(SceneUtil::AutoDepth::depthSourceFormat()); + mDepthTex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); + mDepthTex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); mDepthTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); @@ -283,9 +342,9 @@ namespace MWRender mSceneTex = new osg::Texture2D; mSceneTex->setTextureSize(width, height); - mSceneTex->setSourceFormat(GL_RGB); - mSceneTex->setSourceType(GL_UNSIGNED_BYTE); - mSceneTex->setInternalFormat(GL_RGB); + mSceneTex->setSourceFormat(SceneUtil::Color::colorSourceFormat()); + mSceneTex->setSourceType(SceneUtil::Color::colorSourceType()); + mSceneTex->setInternalFormat(SceneUtil::Color::colorInternalFormat()); mSceneTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); mSceneTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); mSceneTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); @@ -296,6 +355,7 @@ namespace MWRender mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); + mHUDCamera->setClearMask(0); mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); mHUDCamera->setAllowEventFocus(false); mHUDCamera->setViewport(0, 0, width, height); @@ -325,8 +385,28 @@ namespace MWRender } )GLSL"; + constexpr char fragSrcMultiview[] = R"GLSL( + #version 330 compatibility + + #extension GL_EXT_texture_array : require + + varying vec2 uv; + uniform sampler2DArray sceneTex; + + void main() + { + vec3 array_uv = vec3(uv.x * 2, uv.y, 0); + if(array_uv.x >= 1.0) + { + array_uv.x -= 1.0; + array_uv.z = 1; + } + gl_FragData[0] = texture2DArray(sceneTex, array_uv); + } + )GLSL"; + osg::ref_ptr vertShader = new osg::Shader(osg::Shader::VERTEX, vertSrc); - osg::ref_ptr fragShader = new osg::Shader(osg::Shader::FRAGMENT, fragSrc); + osg::ref_ptr fragShader = new osg::Shader(osg::Shader::FRAGMENT, Stereo::getMultiview() ? fragSrcMultiview : fragSrc); osg::ref_ptr program = new osg::Program; program->addShader(vertShader); @@ -334,13 +414,8 @@ namespace MWRender mHUDCamera->addChild(createFullScreenTri()); mHUDCamera->setNodeMask(Mask_RenderToTexture); - - auto* stateset = mHUDCamera->getOrCreateStateSet(); - stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(program, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("sceneTex", 0)); - stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mHUDCamera->setCullCallback(new HUDCameraStatesetUpdater(mHUDCamera, program, mSceneTex)); } } + diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index f2ef238737..b1217b011b 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -7,11 +7,18 @@ #include #include +#include + namespace osgViewer { class Viewer; } +namespace Stereo +{ + class MultiviewFramebuffer; +} + namespace MWRender { class PostProcessor : public osg::Referenced @@ -23,7 +30,6 @@ namespace MWRender auto getFbo() { return mFbo; } auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; } - int getDepthFormat() { return mDepthFormat; } osg::ref_ptr getOpaqueDepthTex() { return mOpaqueDepthTex; } void resize(int width, int height); @@ -35,6 +41,7 @@ namespace MWRender osg::ref_ptr mRootNode; osg::ref_ptr mHUDCamera; + std::shared_ptr mMultiviewFbo; osg::ref_ptr mMsaaFbo; osg::ref_ptr mFbo; osg::ref_ptr mFirstPersonDepthRBProxy; @@ -42,9 +49,8 @@ namespace MWRender osg::ref_ptr mSceneTex; osg::ref_ptr mDepthTex; osg::ref_ptr mOpaqueDepthTex; - - int mDepthFormat; }; } -#endif \ No newline at end of file +#endif + diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 384580adb8..15d690534a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -21,6 +21,9 @@ #include +#include +#include + #include #include #include @@ -31,6 +34,7 @@ #include #include +#include #include #include #include @@ -45,6 +49,7 @@ #include +#include "../mwbase/windowmanager.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" @@ -70,6 +75,54 @@ namespace MWRender { + class PerViewUniformStateUpdater final : public SceneUtil::StateSetUpdater + { + public: + public: + PerViewUniformStateUpdater() + { + } + + void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(mProjectionMatrix); + } + + void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(Stereo::Manager::instance().computeEyeProjection(0, SceneUtil::AutoDepth::isReversed())); + } + + void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(Stereo::Manager::instance().computeEyeProjection(1, SceneUtil::AutoDepth::isReversed())); + } + + void setProjectionMatrix(const osg::Matrixf& projectionMatrix) + { + mProjectionMatrix = projectionMatrix; + } + + const osg::Matrixf& projectionMatrix() const + { + return mProjectionMatrix; + } + + private: + osg::Matrixf mProjectionMatrix; + }; + class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater { public: @@ -82,9 +135,8 @@ namespace MWRender { } - void setDefaults(osg::StateSet *stateset) override + void setDefaults(osg::StateSet* stateset) override { - stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); stateset->addUniform(new osg::Uniform("linearFac", 0.f)); stateset->addUniform(new osg::Uniform("near", 0.f)); stateset->addUniform(new osg::Uniform("far", 0.f)); @@ -98,10 +150,6 @@ namespace MWRender void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { - auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); - if (uProjectionMatrix) - uProjectionMatrix->set(mProjectionMatrix); - auto* uLinearFac = stateset->getUniform("linearFac"); if (uLinearFac) uLinearFac->set(mLinearFac); @@ -130,11 +178,6 @@ namespace MWRender } } - void setProjectionMatrix(const osg::Matrixf& projectionMatrix) - { - mProjectionMatrix = projectionMatrix; - } - void setLinearFac(float linearFac) { mLinearFac = linearFac; @@ -167,7 +210,6 @@ namespace MWRender private: - osg::Matrixf mProjectionMatrix; float mLinearFac; float mNear; float mFar; @@ -312,14 +354,16 @@ namespace MWRender auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - // Shadows and radial fog have problems with fixed-function mode + // Shadows and radial fog have problems with fixed-function mode. bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("soft particles", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP - || reverseZ; + || reverseZ + || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); + // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); @@ -368,6 +412,9 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; globalDefines["useGPUShader4"] = "0"; + globalDefines["GLSLVersion"] = "120"; + globalDefines["useOVR_multiview"] = "0"; + globalDefines["numViews"] = "1"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; @@ -453,16 +500,19 @@ namespace MWRender mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover); rootNode->addUpdateCallback(mSharedUniformStateUpdater); + mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(); + rootNode->addCullCallback(mPerViewUniformStateUpdater); + mPostProcessor = new PostProcessor(viewer, mRootNode); - resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat()); + resourceSystem->getSceneManager()->setDepthFormat(SceneUtil::AutoDepth::depthInternalFormat()); resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex()); - if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(mPostProcessor->getDepthFormat())) + if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(SceneUtil::AutoDepth::depthInternalFormat())) Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it."; // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); - + mCamera.reset(new Camera(mViewer->getCamera())); mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); @@ -514,7 +564,8 @@ namespace MWRender mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mViewer->getCamera()->setCullingMode(cullingMode); - mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); + auto mask = ~(Mask_UpdateVisitor | Mask_SimpleWater); + MWBase::Environment::get().getWindowManager()->setCullMask(mask); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); @@ -537,6 +588,7 @@ namespace MWRender SceneUtil::setCameraClearDepth(mViewer->getCamera()); + updateProjectionMatrix(); mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); @@ -753,14 +805,15 @@ namespace MWRender } else if (mode == Render_Scene) { - unsigned int mask = mViewer->getCamera()->getCullMask(); + auto* wm = MWBase::Environment::get().getWindowManager(); + unsigned int mask = wm->getCullMask(); bool enabled = !(mask&sToggleWorldMask); if (enabled) mask |= sToggleWorldMask; else mask &= ~sToggleWorldMask; mWater->showWorld(enabled); - mViewer->getCamera()->setCullMask(mask); + wm->setCullMask(mask); return enabled; } else if (mode == Render_NavMesh) @@ -1155,14 +1208,23 @@ namespace MWRender if (SceneUtil::AutoDepth::isReversed()) { mSharedUniformStateUpdater->setLinearFac(-mNearClip / (mViewDistance - mNearClip) - 1.f); - mSharedUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); + mPerViewUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); } else - mSharedUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix()); + mPerViewUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix()); mSharedUniformStateUpdater->setNear(mNearClip); mSharedUniformStateUpdater->setFar(mViewDistance); - mSharedUniformStateUpdater->setScreenRes(width, height); + if (Stereo::getStereo()) + { + auto res = Stereo::Manager::instance().eyeResolution(); + mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); + Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->projectionMatrix()); + } + else + { + mSharedUniformStateUpdater->setScreenRes(width, height); + } // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b25e675748..fe3b33b20e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -76,6 +76,7 @@ namespace MWRender { class StateUpdater; class SharedUniformStateUpdater; + class PerViewUniformStateUpdater; class EffectManager; class ScreenshotManager; @@ -97,7 +98,7 @@ namespace MWRender class RenderingManager : public MWRender::RenderingInterface { public: - RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, + RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore); ~RenderingManager(); @@ -255,6 +256,7 @@ namespace MWRender void updateRecastMesh(); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; @@ -292,6 +294,7 @@ namespace MWRender osg::ref_ptr mStateUpdater; osg::ref_ptr mSharedUniformStateUpdater; + osg::ref_ptr mPerViewUniformStateUpdater; osg::Vec4f mAmbientColor; float mMinimumAmbientLuminance; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 90e6eec124..65b6a1bdbc 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include @@ -85,6 +87,12 @@ namespace MWRender { int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); + if (Stereo::getStereo()) + { + auto eyeRes = Stereo::Manager::instance().eyeResolution(); + screenW = eyeRes.x(); + screenH = eyeRes.y(); + } double imageaspect = (double)mWidth/(double)mHeight; int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); @@ -94,13 +102,19 @@ namespace MWRender // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer. Also ensure that the readbuffer is set correctly with rendeirng to FBO. // glReadPixel() cannot read from multisampled targets PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); + osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); - if (postProcessor && postProcessor->getFbo()) + if (ext) { - osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); - if (ext) + osg::FrameBufferObject* fbo = nullptr; + if (Stereo::getStereo()) + fbo = Stereo::Manager::instance().multiviewFramebuffer()->layerFbo(0); + else if (postProcessor) + fbo = postProcessor->getFbo(); + + if (fbo) { - ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, postProcessor->getFbo()->getHandle(renderInfo.getContextID())); + ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo->getHandle(renderInfo.getContextID())); renderInfo.getState()->glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); } } @@ -346,7 +360,7 @@ namespace MWRender rttCamera->addChild(mWater->getReflectionNode()); rttCamera->addChild(mWater->getRefractionNode()); - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson)); + rttCamera->setCullMask(MWBase::Environment::get().getWindowManager()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson)); rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index a9c56bdf19..f0a591477a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -23,6 +23,7 @@ #include #include +#include #include @@ -332,8 +333,10 @@ namespace MWRender if (mSceneManager->getForceShaders()) { - auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", {}, osg::Shader::VERTEX); - auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", {}, osg::Shader::FRAGMENT); + Shader::ShaderManager::DefineMap defines = {}; + Stereo::Manager::instance().shaderStereoDefines(defines); + auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", defines, osg::Shader::VERTEX); + auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", defines, osg::Shader::FRAGMENT); auto program = mSceneManager->getShaderManager().getProgram(vertex, fragment); mEarlyRenderBinRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", -1)); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(program, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 7d9aca9b76..87f5eada20 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -33,6 +34,7 @@ #include #include +#include #include @@ -44,6 +46,8 @@ #include "../mwworld/cellstore.hpp" +#include "../mwbase/environment.hpp" + #include "vismask.hpp" #include "ripplesimulation.hpp" #include "renderbin.hpp" @@ -166,7 +170,7 @@ private: class InheritViewPointCallback : public SceneUtil::NodeCallback { public: - InheritViewPointCallback() {} + InheritViewPointCallback() {} void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { @@ -260,7 +264,7 @@ class Refraction : public SceneUtil::RTTNode { public: Refraction(uint32_t rttSize) - : RTTNode(rttSize, rttSize, 1, false) + : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) , mNodeMask(Refraction::sDefaultCullMask) { mClipCullNode = new ClipCullNode; @@ -335,7 +339,7 @@ class Reflection : public SceneUtil::RTTNode { public: Reflection(uint32_t rttSize, bool isInterior) - : RTTNode(rttSize, rttSize, 0, false) + : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) { setInterior(isInterior); mClipCullNode = new ClipCullNode; @@ -458,6 +462,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setNodeMask(Mask_Water); mWaterGeom->setDataVariance(osg::Object::STATIC); + mWaterGeom->setName("Water Geometry"); mWaterNode = new osg::PositionAttitudeTransform; mWaterNode->setName("Water Root"); @@ -468,6 +473,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem osg::ref_ptr geom2 (osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES)); createSimpleWaterStateSet(geom2, Fallback::Map::getFloat("Water_Map_Alpha")); geom2->setNodeMask(Mask_SimpleWater); + geom2->setName("Simple Water Geometry"); mWaterNode->addChild(geom2); mSceneRoot->addChild(mWaterNode); @@ -679,6 +685,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); + Stereo::Manager::instance().shaderStereoDefines(defineMap); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr vertexShader(shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); @@ -694,6 +701,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R normalMap->setMaxAnisotropy(16); normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mRainIntensityUpdater = new RainIntensityUpdater(); node->setUpdateCallback(mRainIntensityUpdater); diff --git a/cmake/CheckOsgMultiview.cmake b/cmake/CheckOsgMultiview.cmake new file mode 100644 index 0000000000..1dcea31a32 --- /dev/null +++ b/cmake/CheckOsgMultiview.cmake @@ -0,0 +1,26 @@ +set(TMP_ROOT ${CMAKE_BINARY_DIR}/try-compile) +file(MAKE_DIRECTORY ${TMP_ROOT}) + +file(WRITE ${TMP_ROOT}/checkmultiview.cpp +" +#include +int main(void) +{ + (void)osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER; + return 0; +} +") + +message(STATUS "Checking if OSG supports multiview") + +try_compile(RESULT_VAR + ${TMP_ROOT}/temp + ${TMP_ROOT}/checkmultiview.cpp + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${OPENSCENEGRAPH_INCLUDE_DIRS}" + ) +set(HAVE_MULTIVIEW ${RESULT_VAR}) +if(HAVE_MULTIVIEW) + message(STATUS "Osg supports multiview") +else(HAVE_MULTIVIEW) + message(NOTICE "Osg does not support multiview, disabling use of GL_OVR_multiview") +endif(HAVE_MULTIVIEW) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 32c7c9535f..5ad88523b8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -61,7 +61,7 @@ add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt - screencapture depth + screencapture depth color ) add_component_dir (nif @@ -196,6 +196,10 @@ add_component_dir (misc compression osguservalues errorMarker color ) +add_component_dir (stereo + frustum multiview stereomanager types + ) + add_component_dir (debug debugging debuglog gldebug ) diff --git a/components/sceneutil/color.cpp b/components/sceneutil/color.cpp new file mode 100644 index 0000000000..825d7f65fd --- /dev/null +++ b/components/sceneutil/color.cpp @@ -0,0 +1,157 @@ +#include "color.hpp" + +#include +#include + +#include + +#include +#include + +namespace SceneUtil +{ + + bool isColorFormat(GLenum format) + { + static constexpr std::array formats = { + GL_RGB, + GL_RGB4, + GL_RGB5, + GL_RGB8, + GL_RGB8_SNORM, + GL_RGB10, + GL_RGB12, + GL_RGB16, + GL_RGB16_SNORM, + GL_SRGB, + GL_SRGB8, + GL_RGB16F, + GL_RGB32F, + GL_R11F_G11F_B10F, + GL_RGB9_E5, + GL_RGB8I, + GL_RGB8UI, + GL_RGB16I, + GL_RGB16UI, + GL_RGB32I, + GL_RGB32UI, + GL_RGBA, + GL_RGBA2, + GL_RGBA4, + GL_RGB5_A1, + GL_RGBA8, + GL_RGBA8_SNORM, + GL_RGB10_A2, + GL_RGB10_A2UI, + GL_RGBA12, + GL_RGBA16, + GL_RGBA16_SNORM, + GL_SRGB_ALPHA8, + GL_SRGB8_ALPHA8, + GL_RGBA16F, + GL_RGBA32F, + GL_RGBA8I, + GL_RGBA8UI, + GL_RGBA16I, + GL_RGBA16UI, + GL_RGBA32I, + GL_RGBA32UI, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } + + bool isFloatingPointColorFormat(GLenum format) + { + static constexpr std::array formats = { + GL_RGB16F, + GL_RGB32F, + GL_R11F_G11F_B10F, + GL_RGBA16F, + GL_RGBA32F, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } + + int getColorFormatChannelCount(GLenum format) + { + static constexpr std::array formats = { + GL_RGBA, + GL_RGBA2, + GL_RGBA4, + GL_RGB5_A1, + GL_RGBA8, + GL_RGBA8_SNORM, + GL_RGB10_A2, + GL_RGB10_A2UI, + GL_RGBA12, + GL_RGBA16, + GL_RGBA16_SNORM, + GL_SRGB_ALPHA8, + GL_SRGB8_ALPHA8, + GL_RGBA16F, + GL_RGBA32F, + GL_RGBA8I, + GL_RGBA8UI, + GL_RGBA16I, + GL_RGBA16UI, + GL_RGBA32I, + GL_RGBA32UI, + }; + if (std::find(formats.cbegin(), formats.cend(), format) != formats.cend()) + return 4; + return 3; + } + + void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType) + { + if (getColorFormatChannelCount(internalFormat == 4)) + sourceFormat = GL_RGBA; + else + sourceFormat = GL_RGB; + + if (isFloatingPointColorFormat(internalFormat)) + sourceType = GL_FLOAT; + else + sourceType = GL_UNSIGNED_BYTE; + } + + namespace Color + { + GLenum sColorInternalFormat; + GLenum sColorSourceFormat; + GLenum sColorSourceType; + + GLenum colorInternalFormat() + { + return sColorInternalFormat; + } + + GLenum colorSourceFormat() + { + return sColorSourceFormat; + } + + GLenum colorSourceType() + { + return sColorSourceType; + } + + void SelectColorFormatOperation::operator()([[maybe_unused]] osg::GraphicsContext* graphicsContext) + { + sColorInternalFormat = GL_RGB; + + for (auto supportedFormat : mSupportedFormats) + { + if (isColorFormat(supportedFormat)) + { + sColorInternalFormat = supportedFormat; + break; + } + } + + getColorFormatSourceFormatAndType(sColorInternalFormat, sColorSourceFormat, sColorSourceType); + } + } +} diff --git a/components/sceneutil/color.hpp b/components/sceneutil/color.hpp new file mode 100644 index 0000000000..cd950c2749 --- /dev/null +++ b/components/sceneutil/color.hpp @@ -0,0 +1,204 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_COLOR_H +#define OPENMW_COMPONENTS_SCENEUTIL_COLOR_H + +#include + +namespace SceneUtil +{ + bool isColorFormat(GLenum format); + bool isFloatingPointColorFormat(GLenum format); + int getColorFormatChannelCount(GLenum format); + void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType); + + namespace Color + { + GLenum colorSourceFormat(); + GLenum colorSourceType(); + GLenum colorInternalFormat(); + + class SelectColorFormatOperation final : public osg::GraphicsOperation + { + public: + SelectColorFormatOperation() : GraphicsOperation("SelectColorFormatOperation", false) + {} + + void operator()(osg::GraphicsContext* graphicsContext) override; + + void setSupportedFormats(const std::vector& supportedFormats) + { + mSupportedFormats = supportedFormats; + } + + private: + std::vector mSupportedFormats; + }; + } +} + +#ifndef GL_RGB +#define GL_RGB 0x1907 +#endif + +#ifndef GL_RGBA +#define GL_RGBA 0x1908 +#endif + +#ifndef GL_RGB4 +#define GL_RGB4 0x804F +#endif + +#ifndef GL_RGB5 +#define GL_RGB5 0x8050 +#endif + +#ifndef GL_RGB8 +#define GL_RGB8 0x8051 +#endif + +#ifndef GL_RGB8_SNORM +#define GL_RGB8_SNORM 0x8F96 +#endif + +#ifndef GL_RGB10 +#define GL_RGB10 0x8052 +#endif + +#ifndef GL_RGB12 +#define GL_RGB12 0x8053 +#endif + +#ifndef GL_RGB16 +#define GL_RGB16 0x8054 +#endif + +#ifndef GL_RGB16_SNORM +#define GL_RGB16_SNORM 0x8F9A +#endif + +#ifndef GL_RGBA2 +#define GL_RGBA2 0x8055 +#endif + +#ifndef GL_RGBA4 +#define GL_RGBA4 0x8056 +#endif + +#ifndef GL_RGB5_A1 +#define GL_RGB5_A1 0x8057 +#endif + +#ifndef GL_RGBA8 +#define GL_RGBA8 0x8058 +#endif + +#ifndef GL_RGBA8_SNORM +#define GL_RGBA8_SNORM 0x8F97 +#endif + +#ifndef GL_RGB10_A2 +#define GL_RGB10_A2 0x906F +#endif + +#ifndef GL_RGB10_A2UI +#define GL_RGB10_A2UI 0x906F +#endif + +#ifndef GL_RGBA12 +#define GL_RGBA12 0x805A +#endif + +#ifndef GL_RGBA16 +#define GL_RGBA16 0x805B +#endif + +#ifndef GL_RGBA16_SNORM +#define GL_RGBA16_SNORM 0x8F9B +#endif + +#ifndef GL_SRGB +#define GL_SRGB 0x8C40 +#endif + +#ifndef GL_SRGB8 +#define GL_SRGB8 0x8C41 +#endif + +#ifndef GL_SRGB_ALPHA8 +#define GL_SRGB_ALPHA8 0x8C42 +#endif + +#ifndef GL_SRGB8_ALPHA8 +#define GL_SRGB8_ALPHA8 0x8C43 +#endif + +#ifndef GL_RGB16F +#define GL_RGB16F 0x881B +#endif + +#ifndef GL_RGBA16F +#define GL_RGBA16F 0x881A +#endif + +#ifndef GL_RGB32F +#define GL_RGB32F 0x8815 +#endif + +#ifndef GL_RGBA32F +#define GL_RGBA32F 0x8814 +#endif + +#ifndef GL_R11F_G11F_B10F +#define GL_R11F_G11F_B10F 0x8C3A +#endif + + +#ifndef GL_RGB8I +#define GL_RGB8I 0x8D8F +#endif + +#ifndef GL_RGB8UI +#define GL_RGB8UI 0x8D7D +#endif + +#ifndef GL_RGB16I +#define GL_RGB16I 0x8D89 +#endif + +#ifndef GL_RGB16UI +#define GL_RGB16UI 0x8D77 +#endif + +#ifndef GL_RGB32I +#define GL_RGB32I 0x8D83 +#endif + +#ifndef GL_RGB32UI +#define GL_RGB32UI 0x8D71 +#endif + +#ifndef GL_RGBA8I +#define GL_RGBA8I 0x8D8E +#endif + +#ifndef GL_RGBA8UI +#define GL_RGBA8UI 0x8D7C +#endif + +#ifndef GL_RGBA16I +#define GL_RGBA16I 0x8D88 +#endif + +#ifndef GL_RGBA16UI +#define GL_RGBA16UI 0x8D76 +#endif + +#ifndef GL_RGBA32I +#define GL_RGBA32I 0x8D82 +#endif + +#ifndef GL_RGBA32UI +#define GL_RGBA32UI 0x8D70 +#endif + + +#endif diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp index ee95f8c7ec..c52bdf2ed2 100644 --- a/components/sceneutil/depth.cpp +++ b/components/sceneutil/depth.cpp @@ -2,8 +2,7 @@ #include -#include - +#include #include namespace SceneUtil @@ -56,4 +55,144 @@ namespace SceneUtil return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); } -} \ No newline at end of file + + bool isDepthFormat(GLenum format) + { + constexpr std::array formats = { + GL_DEPTH_COMPONENT32F, + GL_DEPTH_COMPONENT32F_NV, + GL_DEPTH_COMPONENT16, + GL_DEPTH_COMPONENT24, + GL_DEPTH_COMPONENT32, + GL_DEPTH32F_STENCIL8, + GL_DEPTH32F_STENCIL8_NV, + GL_DEPTH24_STENCIL8, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } + + bool isDepthStencilFormat(GLenum format) + { + constexpr std::array formats = { + GL_DEPTH32F_STENCIL8, + GL_DEPTH32F_STENCIL8_NV, + GL_DEPTH24_STENCIL8, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } + + void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType) + { + switch (internalFormat) + { + case GL_DEPTH_COMPONENT16: + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32: + sourceType = GL_UNSIGNED_INT; + sourceFormat = GL_DEPTH_COMPONENT; + break; + case GL_DEPTH_COMPONENT32F: + case GL_DEPTH_COMPONENT32F_NV: + sourceType = GL_FLOAT; + sourceFormat = GL_DEPTH_COMPONENT; + break; + case GL_DEPTH24_STENCIL8: + sourceType = GL_UNSIGNED_INT_24_8_EXT; + sourceFormat = GL_DEPTH_STENCIL_EXT; + break; + case GL_DEPTH32F_STENCIL8: + case GL_DEPTH32F_STENCIL8_NV: + sourceType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + sourceFormat = GL_DEPTH_STENCIL_EXT; + break; + default: + sourceType = GL_UNSIGNED_INT; + sourceFormat = GL_DEPTH_COMPONENT; + break; + } + } + + GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat) + { + switch (internalFormat) + { + case GL_DEPTH24_STENCIL8: + return GL_DEPTH_COMPONENT24; + break; + case GL_DEPTH32F_STENCIL8: + return GL_DEPTH_COMPONENT32F; + break; + case GL_DEPTH32F_STENCIL8_NV: + return GL_DEPTH_COMPONENT32F_NV; + break; + default: + return internalFormat; + break; + } + } + + void SelectDepthFormatOperation::operator()(osg::GraphicsContext* graphicsContext) + { + bool enableReverseZ = false; + + if (Settings::Manager::getBool("reverse z", "Camera")) + { + osg::ref_ptr exts = osg::GLExtensions::Get(0, false); + if (exts && exts->isClipControlSupported) + { + enableReverseZ = true; + Log(Debug::Info) << "Using reverse-z depth buffer"; + } + else + Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer"; + } + else + Log(Debug::Info) << "Using standard depth buffer"; + + SceneUtil::AutoDepth::setReversed(enableReverseZ); + + constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: "; + std::vector requestedFormats; + unsigned int contextID = graphicsContext->getState()->getContextID(); + if (SceneUtil::AutoDepth::isReversed()) + { + if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) + { + requestedFormats.push_back(GL_DEPTH32F_STENCIL8); + } + else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) + { + requestedFormats.push_back(GL_DEPTH32F_STENCIL8_NV); + } + else + { + Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; + } + } + + requestedFormats.push_back(GL_DEPTH24_STENCIL8); + if (mSupportedFormats.empty()) + { + SceneUtil::AutoDepth::setDepthFormat(requestedFormats.front()); + } + else + { + for (auto requestedFormat : requestedFormats) + { + if (std::find(mSupportedFormats.cbegin(), mSupportedFormats.cend(), requestedFormat) != mSupportedFormats.cend()) + { + SceneUtil::AutoDepth::setDepthFormat(requestedFormat); + break; + } + } + } + } + + void AutoDepth::setDepthFormat(GLenum format) + { + sDepthInternalFormat = format; + getDepthFormatSourceFormatAndType(sDepthInternalFormat, sDepthSourceFormat, sDepthSourceType); + } +} diff --git a/components/sceneutil/depth.hpp b/components/sceneutil/depth.hpp index 1f4ba55297..67cf5bdb45 100644 --- a/components/sceneutil/depth.hpp +++ b/components/sceneutil/depth.hpp @@ -9,6 +9,26 @@ #define GL_DEPTH32F_STENCIL8_NV 0x8DAC #endif +#ifndef GL_DEPTH32F_STENCIL8 +#define GL_DEPTH32F_STENCIL8 0x8CAD +#endif + +#ifndef GL_FLOAT_32_UNSIGNED_INT_24_8_REV +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#endif + +#ifndef GL_DEPTH24_STENCIL8 +#define GL_DEPTH24_STENCIL8 0x88F0 +#endif + +#ifndef GL_DEPTH_STENCIL_EXT +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#endif + +#ifndef GL_UNSIGNED_INT_24_8_EXT +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#endif + namespace SceneUtil { // Sets camera clear depth to 0 if reversed depth buffer is in use, 1 otherwise. @@ -28,6 +48,18 @@ namespace SceneUtil // Returns true if the GL format is a floating point depth format. bool isFloatingPointDepthFormat(GLenum format); + // Returns true if the GL format is a depth format + bool isDepthFormat(GLenum format); + + // Returns true if the GL format is a depth+stencil format + bool isDepthStencilFormat(GLenum format); + + // Returns the corresponding source format and type for the given internal format + void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType); + + // Converts depth-stencil formats to their corresponding depth formats. + GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat); + // Brief wrapper around an osg::Depth that applies the reversed depth function when a reversed depth buffer is in use class AutoDepth : public osg::Depth { @@ -72,9 +104,29 @@ namespace SceneUtil return AutoDepth::sReversed; } + static void setDepthFormat(GLenum format); + + static GLenum depthInternalFormat() + { + return AutoDepth::sDepthInternalFormat; + } + + static GLenum depthSourceFormat() + { + return AutoDepth::sDepthSourceFormat; + } + + static GLenum depthSourceType() + { + return AutoDepth::sDepthSourceType; + } + private: static inline bool sReversed = false; + static inline GLenum sDepthSourceFormat = GL_DEPTH_COMPONENT; + static inline GLenum sDepthInternalFormat = GL_DEPTH_COMPONENT24; + static inline GLenum sDepthSourceType = GL_UNSIGNED_INT; osg::Depth::Function getReversedDepthFunction() const { @@ -116,6 +168,23 @@ namespace SceneUtil traverse(node); } }; + + class SelectDepthFormatOperation : public osg::GraphicsOperation + { + public: + SelectDepthFormatOperation() : GraphicsOperation("SelectDepthFormatOperation", false) + {} + + void operator()(osg::GraphicsContext* graphicsContext) override; + + void setSupportedFormats(const std::vector& supportedFormats) + { + mSupportedFormats = supportedFormats; + } + + private: + std::vector mSupportedFormats; + }; } #endif diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 931b316347..60dd613df5 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -633,6 +633,7 @@ void MWShadowTechnique::ShadowData::releaseGLObjects(osg::State* state) const // Frustum // MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar): + useCustomClipSpace(false), corners(8), faces(6), edges(12) @@ -652,19 +653,40 @@ MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, d OSG_INFO<<"zNear = "< castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", { {"GLSLVersion", "120"} }, osg::Shader::VERTEX); osg::ref_ptr exts = osg::GLExtensions::Get(0, false); std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) @@ -910,7 +932,8 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"}, {"adjustCoverage", "1"}, - {"useGPUShader4", useGPUShader4} + {"useGPUShader4", useGPUShader4}, + {"GLSLVersion", "120"} }, osg::Shader::FRAGMENT)); } } @@ -931,6 +954,67 @@ MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(os return vdd.release(); } +void SceneUtil::MWShadowTechnique::copyShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs) +{ + // Prepare for rendering shadows using the shadow map owned by rhs. + + // To achieve this i first copy all data that is not specific to this cv's camera and thus read-only, + // trusting openmw and osg won't overwrite that data before this frame is done rendering. + // This works due to the double buffering of CullVisitors by osg, but also requires that cull passes are serialized (relative to one another). + // Then initialize new copies of the data that will be written with view-specific data + // (the stateset and the texgens). + + lhs->_viewDependentShadowMap = rhs->_viewDependentShadowMap; + auto* stateset = lhs->getStateSet(cv.getTraversalNumber()); + stateset->clear(); + lhs->_lightDataList = rhs->_lightDataList; + lhs->_numValidShadows = rhs->_numValidShadows; + + ShadowDataList& sdl = lhs->getShadowDataList(); + ShadowDataList previous_sdl; + previous_sdl.swap(sdl); + for (const auto& rhs_sd : rhs->getShadowDataList()) + { + osg::ref_ptr lhs_sd; + + if (previous_sdl.empty()) + { + OSG_INFO << "Create new ShadowData" << std::endl; + lhs_sd = new ShadowData(lhs); + } + else + { + OSG_INFO << "Taking ShadowData from from of previous_sdl" << std::endl; + lhs_sd = previous_sdl.front(); + previous_sdl.erase(previous_sdl.begin()); + } + lhs_sd->_camera = rhs_sd->_camera; + lhs_sd->_textureUnit = rhs_sd->_textureUnit; + lhs_sd->_texture = rhs_sd->_texture; + sdl.push_back(lhs_sd); + } + + assignTexGenSettings(cv, lhs); + + if (lhs->_numValidShadows > 0) + { + prepareStateSetForRenderingShadow(*lhs, cv.getTraversalNumber()); + } +} + +void SceneUtil::MWShadowTechnique::setCustomFrustumCallback(CustomFrustumCallback* cfc) +{ + _customFrustumCallback = cfc; +} + +void SceneUtil::MWShadowTechnique::assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd) +{ + for (const auto& sd : vdd->getShadowDataList()) + { + assignTexGenSettings(&cv, sd->_camera, sd->_textureUnit, sd->_texgen); + } +} + void MWShadowTechnique::update(osg::NodeVisitor& nv) { OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<getMaximumShadowMapDistance(),maxZFar); if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio(); @@ -1047,6 +1132,36 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) cv.setNearFarRatio(minZNear / maxZFar); Frustum frustum(&cv, minZNear, maxZFar); + if (_customFrustumCallback) + { + OSG_INFO << "Calling custom frustum callback" << std::endl; + osgUtil::CullVisitor* sharedFrustumHint = nullptr; + _customClipSpace.init(); + _customFrustumCallback->operator()(cv, _customClipSpace, sharedFrustumHint); + frustum.setCustomClipSpace(_customClipSpace); + if (sharedFrustumHint) + { + // user hinted another view shares its frustum + std::lock_guard lock(_viewDependentDataMapMutex); + auto itr = _viewDependentDataMap.find(sharedFrustumHint); + if (itr != _viewDependentDataMap.end()) + { + OSG_INFO << "User provided a valid shared frustum hint, re-using previously generated shadow map" << std::endl; + + copyShadowMap(cv, vdd, itr->second); + + // return compute near far mode back to it's original settings + cv.setComputeNearFarMode(cachedNearFarMode); + return; + } + else + { + OSG_INFO << "User provided a shared frustum hint, but it was not valid." << std::endl; + } + } + } + + frustum.init(); if (_debugHud) { osg::ref_ptr vertexArray = new osg::Vec3Array(); @@ -1066,7 +1181,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) reducedNear = minZNear; reducedFar = maxZFar; } - + // return compute near far mode back to it's original settings cv.setComputeNearFarMode(cachedNearFarMode); @@ -1430,7 +1545,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) vdsmCallback->getProjectionMatrix()->set(camera->getProjectionMatrix()); } } - + // 4.4 compute main scene graph TexGen + uniform settings + setup state // assignTexGenSettings(&cv, camera.get(), textureUnit, sd->_texgen.get()); @@ -1459,6 +1574,8 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) } } + vdd->setNumValidShadows(numValidShadows); + if (numValidShadows>0) { prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); @@ -1665,7 +1782,14 @@ osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustu OSG_INFO<<"computeLightViewFrustumPolytope()"< Vertices; Vertices corners; @@ -140,6 +145,18 @@ namespace SceneUtil { osg::Vec3d frustumCenterLine; }; + /** Custom frustum callback allowing the application to request shadow maps covering a + * different furstum than the camera normally would cover, by customizing the corners of the clip space. */ + struct CustomFrustumCallback : osg::Referenced + { + /** The callback operator. + * Output the custum frustum to the boundingBox variable. + * If sharedFrustumHint is set to a valid cull visitor, the shadow maps of that cull visitor will be re-used instead of recomputing new shadow maps + * Note that the customClipSpace bounding box will be uninitialized when this operator is called. If it is not initalized, or a valid shared frustum hint set, + * the resulting shadow map may be invalid. */ + virtual void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) = 0; + }; + // forward declare class ViewDependentData; @@ -197,7 +214,12 @@ namespace SceneUtil { virtual void releaseGLObjects(osg::State* = 0) const; + unsigned int numValidShadows(void) const { return _numValidShadows; } + + void setNumValidShadows(unsigned int numValidShadows) { _numValidShadows = numValidShadows; } + protected: + friend class MWShadowTechnique; virtual ~ViewDependentData() {} MWShadowTechnique* _viewDependentShadowMap; @@ -206,13 +228,19 @@ namespace SceneUtil { LightDataList _lightDataList; ShadowDataList _shadowDataList; + + unsigned int _numValidShadows; }; virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); + void copyShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs); + + void setCustomFrustumCallback(CustomFrustumCallback* cfc); + void assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd); virtual void createShaders(); @@ -246,6 +274,8 @@ namespace SceneUtil { typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr > ViewDependentDataMap; mutable std::mutex _viewDependentDataMapMutex; ViewDependentDataMap _viewDependentDataMap; + osg::ref_ptr _customFrustumCallback; + osg::BoundingBoxd _customClipSpace; osg::ref_ptr _shadowRecievingPlaceholderStateSet; diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index 9fe9a44516..099425330a 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -4,11 +4,16 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include +#include namespace SceneUtil { @@ -22,11 +27,15 @@ namespace SceneUtil } }; - RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, int renderOrderNum, bool doPerViewMapping) + RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, int renderOrderNum, StereoAwareness stereoAwareness) : mTextureWidth(textureWidth) , mTextureHeight(textureHeight) + , mSamples(samples) + , mGenerateMipmaps(generateMipmaps) + , mColorBufferInternalFormat(Color::colorInternalFormat()) + , mDepthBufferInternalFormat(AutoDepth::depthInternalFormat()) , mRenderOrderNum(renderOrderNum) - , mDoPerViewMapping(doPerViewMapping) + , mStereoAwareness(stereoAwareness) { addCullCallback(new CullCallback); setCullingActive(false); @@ -34,28 +43,139 @@ namespace SceneUtil RTTNode::~RTTNode() { + for (auto& vdd : mViewDependentDataMap) + { + auto* camera = vdd.second->mCamera.get(); + if (camera) + { + camera->removeChildren(0, camera->getNumChildren()); + } + } + mViewDependentDataMap.clear(); } void RTTNode::cull(osgUtil::CullVisitor* cv) { + auto frameNumber = cv->getFrameStamp()->getFrameNumber(); auto* vdd = getViewDependentData(cv); - apply(vdd->mCamera); - vdd->mCamera->accept(*cv); + if (frameNumber > vdd->mFrameNumber) + { + apply(vdd->mCamera); + auto& sm = Stereo::Manager::instance(); + if (sm.getEye(cv) == Stereo::Eye::Left) + applyLeft(vdd->mCamera); + if (sm.getEye(cv) == Stereo::Eye::Right) + applyRight(vdd->mCamera); + vdd->mCamera->accept(*cv); + } + vdd->mFrameNumber = frameNumber; + } + + void RTTNode::setColorBufferInternalFormat(GLint internalFormat) + { + mColorBufferInternalFormat = internalFormat; + } + + void RTTNode::setDepthBufferInternalFormat(GLint internalFormat) + { + mDepthBufferInternalFormat = internalFormat; + } + + bool RTTNode::shouldDoPerViewMapping() + { + if(mStereoAwareness != StereoAwareness::Aware) + return false; + if (!Stereo::getMultiview()) + return true; + return false; + } + + bool RTTNode::shouldDoTextureArray() + { + if (mStereoAwareness == StereoAwareness::Unaware) + return false; + if (Stereo::getMultiview()) + return true; + return false; + } + + bool RTTNode::shouldDoTextureView() + { + if (mStereoAwareness != StereoAwareness::Unaware_MultiViewShaders) + return false; + if (Stereo::getMultiview()) + return true; + return false; + } + + osg::Texture2DArray* RTTNode::createTextureArray(GLint internalFormat) + { + osg::Texture2DArray* textureArray = new osg::Texture2DArray; + textureArray->setTextureSize(mTextureWidth, mTextureHeight, 2); + textureArray->setInternalFormat(internalFormat); + GLenum sourceFormat = 0; + GLenum sourceType = 0; + if (SceneUtil::isDepthFormat(internalFormat)) + { + SceneUtil::getDepthFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); + } + else + { + SceneUtil::getColorFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); + } + textureArray->setSourceFormat(sourceFormat); + textureArray->setSourceType(sourceType); + textureArray->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + textureArray->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + textureArray->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + textureArray->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + textureArray->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return textureArray; + } + + osg::Texture2D* RTTNode::createTexture(GLint internalFormat) + { + osg::Texture2D* texture = new osg::Texture2D; + texture->setTextureSize(mTextureWidth, mTextureHeight); + texture->setInternalFormat(internalFormat); + GLenum sourceFormat = 0; + GLenum sourceType = 0; + if (SceneUtil::isDepthFormat(internalFormat)) + { + SceneUtil::getDepthFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); + } + else + { + SceneUtil::getColorFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); + } + texture->setSourceFormat(sourceFormat); + texture->setSourceType(sourceType); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return texture; } osg::Texture* RTTNode::getColorTexture(osgUtil::CullVisitor* cv) { - return getViewDependentData(cv)->mCamera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._texture; + return getViewDependentData(cv)->mColorTexture; } osg::Texture* RTTNode::getDepthTexture(osgUtil::CullVisitor* cv) { - return getViewDependentData(cv)->mCamera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._texture; + return getViewDependentData(cv)->mDepthTexture; + } + + osg::Camera* RTTNode::getCamera(osgUtil::CullVisitor* cv) + { + return getViewDependentData(cv)->mCamera; } RTTNode::ViewDependentData* RTTNode::getViewDependentData(osgUtil::CullVisitor* cv) { - if (!mDoPerViewMapping) + if (!shouldDoPerViewMapping()) // Always setting it to null is an easy way to disable per-view mapping when mDoPerViewMapping is false. // This is safe since the visitor is never dereferenced. cv = nullptr; @@ -63,7 +183,8 @@ namespace SceneUtil if (mViewDependentDataMap.count(cv) == 0) { auto camera = new osg::Camera(); - mViewDependentDataMap[cv].reset(new ViewDependentData); + auto vdd = std::make_shared(); + mViewDependentDataMap[cv] = vdd; mViewDependentDataMap[cv]->mCamera = camera; camera->setRenderOrder(osg::Camera::PRE_RENDER, mRenderOrderNum); @@ -72,34 +193,63 @@ namespace SceneUtil camera->setViewport(0, 0, mTextureWidth, mTextureHeight); SceneUtil::setCameraClearDepth(camera); - setDefaults(mViewDependentDataMap[cv]->mCamera.get()); + setDefaults(camera); + + if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER)) + vdd->mColorTexture = camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._texture; + if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER)) + vdd->mDepthTexture = camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._texture; - // Create any buffer attachments not added in setDefaults - if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0) +#ifdef OSG_HAS_MULTIVIEW + if (shouldDoTextureArray()) { - auto colorBuffer = new osg::Texture2D; - colorBuffer->setTextureSize(mTextureWidth, mTextureHeight); - colorBuffer->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - colorBuffer->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - colorBuffer->setInternalFormat(GL_RGB); - colorBuffer->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - colorBuffer->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::COLOR_BUFFER, colorBuffer); - SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, colorBuffer); + // Create any buffer attachments not added in setDefaults + if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0) + { + vdd->mColorTexture = createTextureArray(mColorBufferInternalFormat); + camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, mGenerateMipmaps, mSamples); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, mGenerateMipmaps); + } + + if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) + { + vdd->mDepthTexture = createTextureArray(mDepthBufferInternalFormat); + camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples); + } + + if (shouldDoTextureView()) + { + // In this case, shaders being set to multiview forces us to render to a multiview framebuffer even though we don't need that. + // This forces us to make Texture2DArray. To make this possible to sample as a Texture2D, make a Texture2D view into the texture array. + vdd->mColorTexture = Stereo::createTextureView_Texture2DFromTexture2DArray(static_cast(vdd->mColorTexture.get()), 0); + vdd->mDepthTexture = Stereo::createTextureView_Texture2DFromTexture2DArray(static_cast(vdd->mDepthTexture.get()), 0); + } + } + else +#endif + { + // Create any buffer attachments not added in setDefaults + if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0) + { + vdd->mColorTexture = createTexture(mColorBufferInternalFormat); + camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps, mSamples); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps); + } + + if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) + { + vdd->mDepthTexture = createTexture(mDepthBufferInternalFormat); + camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, 0, false, mSamples); + } } - if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) + // OSG appears not to properly initialize this metadata. So when multisampling is enabled, OSG will use incorrect formats for the resolve buffers. + if (mSamples > 1) { - auto depthBuffer = new osg::Texture2D; - depthBuffer->setTextureSize(mTextureWidth, mTextureHeight); - depthBuffer->setSourceFormat(GL_DEPTH_STENCIL_EXT); - depthBuffer->setInternalFormat(GL_DEPTH24_STENCIL8_EXT); - depthBuffer->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - depthBuffer->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - depthBuffer->setSourceType(GL_UNSIGNED_INT_24_8_EXT); - depthBuffer->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - depthBuffer->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, depthBuffer); + camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mColorBufferInternalFormat; + camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = mGenerateMipmaps; + camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mDepthBufferInternalFormat; + camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = mGenerateMipmaps; } } diff --git a/components/sceneutil/rtt.hpp b/components/sceneutil/rtt.hpp index c587006c96..ec8243792f 100644 --- a/components/sceneutil/rtt.hpp +++ b/components/sceneutil/rtt.hpp @@ -9,6 +9,7 @@ namespace osg { class Texture2D; + class Texture2DArray; class Camera; } @@ -19,6 +20,8 @@ namespace osgUtil namespace SceneUtil { + class CreateTextureViewsCallback; + /// @brief Implements per-view RTT operations. /// @par With a naive RTT implementation, subsequent views of multiple views will overwrite the results of the previous views, leading to /// the results of the last view being broadcast to all views. An error in all cases where the RTT result depends on the view. @@ -32,37 +35,73 @@ namespace SceneUtil class RTTNode : public osg::Node { public: - RTTNode(uint32_t textureWidth, uint32_t textureHeight, int renderOrderNum, bool doPerViewMapping); + enum class StereoAwareness + { + Unaware, //! RTT does not vary by view. A single RTT context is created + Aware, //! RTT varies by view. One RTT context per view is created. Textures are automatically created as arrays if multiview is enabled. + Unaware_MultiViewShaders, //! RTT does not vary by view, but renders with multiview shaders and needs to create texture arrays if multiview is enabled. + }; + + RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, int renderOrderNum, StereoAwareness stereoAwareness); ~RTTNode(); osg::Texture* getColorTexture(osgUtil::CullVisitor* cv); osg::Texture* getDepthTexture(osgUtil::CullVisitor* cv); + osg::Camera* getCamera(osgUtil::CullVisitor* cv); - /// Apply state - to override in derived classes - /// @note Due to the view mapping approach you *have* to apply all camera settings, even if they have not changed since the last frame. + /// Set default settings - optionally override in derived classes virtual void setDefaults(osg::Camera* camera) {}; - /// Set default settings - optionally override in derived classes + /// Apply state - to override in derived classes + /// @note Due to the view mapping approach you *have* to apply all camera settings, even if they have not changed since the last frame. virtual void apply(osg::Camera* camera) {}; + /// Apply any state specific to the Left view. Default implementation does nothing. Called after apply() + virtual void applyLeft(osg::Camera* camera) {} + /// Apply any state specific to the Right view. Default implementation does nothing. Called after apply() + virtual void applyRight(osg::Camera* camera) {} + void cull(osgUtil::CullVisitor* cv); + uint32_t width() const { return mTextureWidth; } + uint32_t height() const { return mTextureHeight; } + uint32_t samples() const { return mSamples; } + bool generatesMipmaps() const { return mGenerateMipmaps; } + + void setColorBufferInternalFormat(GLint internalFormat); + void setDepthBufferInternalFormat(GLint internalFormat); + + protected: + bool shouldDoPerViewMapping(); + bool shouldDoTextureArray(); + bool shouldDoTextureView(); + osg::Texture2DArray* createTextureArray(GLint internalFormat); + osg::Texture2D* createTexture(GLint internalFormat); + private: + friend class CreateTextureViewsCallback; struct ViewDependentData { osg::ref_ptr mCamera; + osg::ref_ptr mColorTexture; + osg::ref_ptr mDepthTexture; + unsigned int mFrameNumber = 0; }; ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); - typedef std::map< osgUtil::CullVisitor*, std::unique_ptr > ViewDependentDataMap; + typedef std::map< osgUtil::CullVisitor*, std::shared_ptr > ViewDependentDataMap; ViewDependentDataMap mViewDependentDataMap; uint32_t mTextureWidth; uint32_t mTextureHeight; + uint32_t mSamples; + bool mGenerateMipmaps; + GLint mColorBufferInternalFormat; + GLint mDepthBufferInternalFormat; int mRenderOrderNum; - bool mDoPerViewMapping; + StereoAwareness mStereoAwareness; }; } #endif diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index dfe4cf1507..5220fce78a 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "mwshadowtechnique.hpp" @@ -101,6 +102,7 @@ namespace SceneUtil mIndoorShadowCastingMask(indoorShadowCastingMask) { mShadowedScene->setShadowTechnique(mShadowTechnique); + Stereo::Manager::instance().setShadowTechnique(mShadowTechnique); mShadowedScene->addChild(sceneRoot); rootNode->addChild(mShadowedScene); @@ -117,6 +119,7 @@ namespace SceneUtil ShadowManager::~ShadowManager() { + Stereo::Manager::instance().setShadowTechnique(nullptr); } Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index 9ef82b9271..9778cf4852 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -1,5 +1,7 @@ #include "statesetupdater.hpp" +#include + #include #include #include @@ -38,6 +40,12 @@ namespace SceneUtil { auto stateset = getCvDependentStateset(cv); apply(stateset, cv); + auto& sm = Stereo::Manager::instance(); + if (sm.getEye(cv) == Stereo::Eye::Left) + applyLeft(stateset, cv); + if (sm.getEye(cv) == Stereo::Eye::Right) + applyRight(stateset, cv); + cv->pushStateSet(stateset); traverse(node, cv); cv->popStateSet(); diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index cc2e248457..fb8a9c2fc9 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -41,6 +41,11 @@ namespace SceneUtil /// even if it has not changed since the last frame. virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) {} + /// Apply any state specific to the Left view. Default implementation does nothing. Called after apply() \note requires the updater be a cull callback + virtual void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) {} + /// Apply any state specific to the Right view. Default implementation does nothing. Called after apply() \note requires the updater be a cull callback + virtual void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) {} + /// Set default state - optionally override in derived classes /// @par May be used e.g. to allocate StateAttributes. virtual void setDefaults(osg::StateSet* stateset) {} diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index d8bbeeadc2..8923fec666 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -647,6 +648,8 @@ namespace Shader defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; + Stereo::Manager::instance().shaderStereoDefines(defineMap); + std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; diff --git a/components/stereo/frustum.cpp b/components/stereo/frustum.cpp new file mode 100644 index 0000000000..81fd022f58 --- /dev/null +++ b/components/stereo/frustum.cpp @@ -0,0 +1,162 @@ +#include "stereomanager.hpp" +#include "multiview.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "frustum.hpp" + +namespace Stereo +{ +#ifdef OSG_HAS_MULTIVIEW + struct MultiviewFrustumCallback final : public osg::CullSettings::InitialFrustumCallback + { + MultiviewFrustumCallback(StereoFrustumManager* sfm) + : mSfm(sfm) + { + + } + + void setInitialFrustum(osg::CullStack& cullStack, osg::Polytope& frustum) const override + { + auto cm = cullStack.getCullingMode(); + bool nearCulling = !!(cm & osg::CullSettings::NEAR_PLANE_CULLING); + bool farCulling = !!(cm & osg::CullSettings::FAR_PLANE_CULLING); + frustum.setToBoundingBox(mSfm->boundingBox(), nearCulling, farCulling); + } + + StereoFrustumManager* mSfm; + }; +#endif + + struct ShadowFrustumCallback final : public SceneUtil::MWShadowTechnique::CustomFrustumCallback + { + ShadowFrustumCallback(StereoFrustumManager* parent) : mParent(parent) + { + } + + void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) override + { + mParent->customFrustumCallback(cv, customClipSpace, sharedFrustumHint); + } + + StereoFrustumManager* mParent; + }; + + void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb) + { + static const std::array clipCorners = {{ + {-1.0, -1.0, -1.0}, + { 1.0, -1.0, -1.0}, + { 1.0, -1.0, 1.0}, + {-1.0, -1.0, 1.0}, + {-1.0, 1.0, -1.0}, + { 1.0, 1.0, -1.0}, + { 1.0, 1.0, 1.0}, + {-1.0, 1.0, 1.0} + }}; + + osg::Matrix slaveClipToView; + slaveClipToView.invert(slaveProjection); + + for (const auto& clipCorner : clipCorners) + { + auto masterViewVertice = clipCorner * slaveClipToView; + auto masterClipVertice = masterViewVertice * masterProjection; + bb.expandBy(masterClipVertice); + } + } + + StereoFrustumManager::StereoFrustumManager(osg::Camera* camera) + : mCamera(camera) + , mShadowTechnique(nullptr) + , mShadowFrustumCallback(nullptr) + { + if (Stereo::getMultiview()) + { +#ifdef OSG_HAS_MULTIVIEW + mMultiviewFrustumCallback = new MultiviewFrustumCallback(this); + mCamera->setInitialFrustumCallback(mMultiviewFrustumCallback); +#endif + } + + if (Settings::Manager::getBool("shared shadow maps", "Stereo")) + { + mShadowFrustumCallback = new ShadowFrustumCallback(this); + auto* renderer = static_cast(mCamera->getRenderer()); + for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) }) + { + mSharedFrustums[sceneView->getCullVisitorRight()] = sceneView->getCullVisitorLeft(); + } + } + } + + StereoFrustumManager::~StereoFrustumManager() + { + if (Stereo::getMultiview()) + { +#ifdef OSG_HAS_MULTIVIEW + mCamera->setInitialFrustumCallback(nullptr); +#endif + } + + if (mShadowTechnique) + mShadowTechnique->setCustomFrustumCallback(nullptr); + } + + void StereoFrustumManager::setShadowTechnique( + SceneUtil::MWShadowTechnique* shadowTechnique) + { + if (mShadowTechnique) + mShadowTechnique->setCustomFrustumCallback(nullptr); + mShadowTechnique = shadowTechnique; + if (mShadowTechnique) + mShadowTechnique->setCustomFrustumCallback(mShadowFrustumCallback); + } + + void StereoFrustumManager::customFrustumCallback( + osgUtil::CullVisitor& cv, + osg::BoundingBoxd& customClipSpace, + osgUtil::CullVisitor*& sharedFrustumHint) + { + auto it = mSharedFrustums.find(&cv); + if (it != mSharedFrustums.end()) + { + sharedFrustumHint = it->second; + } + + customClipSpace = mBoundingBox; + } + + void StereoFrustumManager::update(std::array projections) + { + mBoundingBox.init(); + for (auto& projection : projections) + joinBoundingBoxes(mCamera->getProjectionMatrix(), projection, mBoundingBox); + } +} diff --git a/components/stereo/frustum.hpp b/components/stereo/frustum.hpp new file mode 100644 index 0000000000..579d3b6876 --- /dev/null +++ b/components/stereo/frustum.hpp @@ -0,0 +1,76 @@ +#ifndef STEREO_FRUSTUM_H +#define STEREO_FRUSTUM_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace osg +{ + class FrameBufferObject; + class Texture2D; + class Texture2DMultisample; + class Texture2DArray; +} + +namespace osgViewer +{ + class Viewer; +} + +namespace usgUtil +{ + class CullVisitor; +} + +namespace SceneUtil +{ + class MWShadowTechnique; +} + +namespace Stereo +{ +#ifdef OSG_HAS_MULTIVIEW + struct MultiviewFrustumCallback; +#endif + + struct ShadowFrustumCallback; + + void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb); + + class StereoFrustumManager + { + public: + StereoFrustumManager(osg::Camera* camera); + ~StereoFrustumManager(); + + void update(std::array projections); + + const osg::BoundingBoxd& boundingBox() const { return mBoundingBox; } + + void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique); + + void customFrustumCallback(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint); + + private: + osg::ref_ptr mCamera; + osg::ref_ptr mShadowTechnique; + osg::ref_ptr mShadowFrustumCallback; + std::map< osgUtil::CullVisitor*, osgUtil::CullVisitor*> mSharedFrustums; + osg::BoundingBoxd mBoundingBox; + +#ifdef OSG_HAS_MULTIVIEW + osg::ref_ptr mMultiviewFrustumCallback; +#endif + }; +} + +#endif diff --git a/components/stereo/multiview.cpp b/components/stereo/multiview.cpp new file mode 100644 index 0000000000..56c1ada349 --- /dev/null +++ b/components/stereo/multiview.cpp @@ -0,0 +1,484 @@ +#include "multiview.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace Stereo +{ + namespace + { + bool getMultiviewSupportedImpl(unsigned int contextID) + { +#ifdef OSG_HAS_MULTIVIEW + if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview")) + { + Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview\" not supported)"; + return false; + } + + if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview2")) + { + Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview2\" not supported)"; + return false; + } + return true; +#else + Log(Debug::Verbose) << "Disabling Multiview (OSG does not support multiview)"; + return false; +#endif + } + + bool getMultiviewSupported(unsigned int contextID) + { + static bool supported = getMultiviewSupportedImpl(contextID); + return supported; + } + + bool getTextureViewSupportedImpl(unsigned int contextID) + { + if (!osg::isGLExtensionOrVersionSupported(contextID, "ARB_texture_view", 4.3)) + { + Log(Debug::Verbose) << "Disabling texture views (opengl extension \"ARB_texture_view\" not supported)"; + return false; + } + return true; + } + + bool getTextureViewSupported(unsigned int contextID) + { + static bool supported = getTextureViewSupportedImpl(contextID); + return supported; + } + + bool getMultiviewImpl(unsigned int contextID) + { + if (!Stereo::getStereo()) + { + Log(Debug::Verbose) << "Disabling Multiview (disabled by config)"; + return false; + } + + if (!Settings::Manager::getBool("multiview", "Stereo")) + { + Log(Debug::Verbose) << "Disabling Multiview (disabled by config)"; + return false; + } + + if (!getMultiviewSupported(contextID)) + { + return false; + } + + if (!getTextureViewSupported(contextID)) + { + Log(Debug::Verbose) << "Disabling Multiview (texture views not supported)"; + return false; + } + + Log(Debug::Verbose) << "Enabling Multiview"; + return true; + } + + bool getMultiview(unsigned int contextID) + { + static bool multiView = getMultiviewImpl(contextID); + return multiView; + } + } + + bool getTextureViewSupported() + { + return getTextureViewSupported(0); + } + + bool getMultiview() + { + return getMultiview(0); + } + + void configureExtensions(unsigned int contextID) + { + getTextureViewSupported(contextID); + getMultiviewSupported(contextID); + getMultiview(contextID); + } + + void setVertexBufferHint() + { + if (getStereo() && Settings::Manager::getBool("multiview", "Stereo")) + { + auto* ds = osg::DisplaySettings::instance().get(); + if (!Settings::Manager::getBool("allow display lists for multiview", "Stereo") + && ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE) + { + // Note that this only works if this code is executed before realize() is called on the viewer. + // The hint is read by the state object only once, before the user realize operations are run. + // Therefore we have to set this hint without access to a graphics context to let us determine + // if multiview will actually be supported or not. So if the user has requested multiview, we + // will just have to set it regardless. + ds->setVertexBufferHint(osg::DisplaySettings::VertexBufferHint::VERTEX_BUFFER_OBJECT); + Log(Debug::Verbose) << "Disabling display lists"; + } + } + } + + class Texture2DViewSubloadCallback : public osg::Texture2D::SubloadCallback + { + public: + Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer); + + void load(const osg::Texture2D& texture, osg::State& state) const override; + void subload(const osg::Texture2D& texture, osg::State& state) const override; + + private: + osg::ref_ptr mTextureArray; + int mLayer; + }; + + Texture2DViewSubloadCallback::Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer) + : mTextureArray(textureArray) + , mLayer(layer) + { + } + + void Texture2DViewSubloadCallback::load(const osg::Texture2D& texture, osg::State& state) const + { + state.checkGLErrors("before Texture2DViewSubloadCallback::load()"); + + auto contextId = state.getContextID(); + auto* gl = osg::GLExtensions::Get(contextId, false); + mTextureArray->apply(state); + + auto sourceTextureObject = mTextureArray->getTextureObject(contextId); + if (!sourceTextureObject) + { + Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2DArray did not have a texture object"; + return; + } + + auto targetTextureObject = texture.getTextureObject(contextId); + if (!sourceTextureObject) + { + Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2D did not have a texture object"; + return; + } + + + // OSG already bound this texture ID, giving it a target. + // Delete it and make a new texture ID. + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &targetTextureObject->_id); + glGenTextures(1, &targetTextureObject->_id); + + auto sourceId = sourceTextureObject->_id; + auto targetId = targetTextureObject->_id; + auto internalFormat = sourceTextureObject->_profile._internalFormat; + auto levels = std::max(1, sourceTextureObject->_profile._numMipmapLevels); + + { + ////// OSG BUG + // Texture views require immutable storage. + // OSG should always give immutable storage to sized internal formats, but does not do so for depth formats. + // Fortunately, we can just call glTexStorage3D here to make it immutable. This probably discards depth info for that frame, but whatever. +#ifndef GL_TEXTURE_IMMUTABLE_FORMAT +#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F +#endif + // Store any current binding and re-apply it after so i don't mess with state. + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &oldBinding); + + // Bind the source texture and check if it's immutable. + glBindTexture(GL_TEXTURE_2D_ARRAY, sourceId); + GLint immutable = 0; + glGetTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable); + if(!immutable) + { + // It wasn't immutable, so make it immutable. + gl->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, internalFormat, sourceTextureObject->_profile._width, sourceTextureObject->_profile._height, 2); + state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTexStorage3D"); + } + glBindTexture(GL_TEXTURE_2D_ARRAY, oldBinding); + } + + gl->glTextureView(targetId, GL_TEXTURE_2D, sourceId, internalFormat, 0, levels, mLayer, 1); + state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTextureView"); + glBindTexture(GL_TEXTURE_2D, targetId); + } + + void Texture2DViewSubloadCallback::subload(const osg::Texture2D& texture, osg::State& state) const + { + // Nothing to do + } + + osg::ref_ptr createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer) + { + if (!getTextureViewSupported()) + { + Log(Debug::Error) << "createTextureView_Texture2DFromTexture2DArray: Tried to use a texture view but glTextureView is not supported"; + return nullptr; + } + + osg::ref_ptr texture2d = new osg::Texture2D; + texture2d->setSubloadCallback(new Texture2DViewSubloadCallback(textureArray, layer)); + texture2d->setTextureSize(textureArray->getTextureWidth(), textureArray->getTextureHeight()); + texture2d->setBorderColor(textureArray->getBorderColor()); + texture2d->setBorderWidth(textureArray->getBorderWidth()); + texture2d->setLODBias(textureArray->getLODBias()); + texture2d->setFilter(osg::Texture::FilterParameter::MAG_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MAG_FILTER)); + texture2d->setFilter(osg::Texture::FilterParameter::MIN_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MIN_FILTER)); + texture2d->setInternalFormat(textureArray->getInternalFormat()); + texture2d->setNumMipmapLevels(textureArray->getNumMipmapLevels()); + return texture2d; + } + + class UpdateRenderStagesCallback : public SceneUtil::NodeCallback + { + public: + UpdateRenderStagesCallback(Stereo::MultiviewFramebuffer* multiviewFramebuffer) + : mMultiviewFramebuffer(multiviewFramebuffer) + { + mViewport = new osg::Viewport(0, 0, multiviewFramebuffer->width(), multiviewFramebuffer->height()); + mViewportStateset = new osg::StateSet(); + mViewportStateset->setAttribute(mViewport.get()); + } + + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); + + bool msaa = mMultiviewFramebuffer->samples() > 1; + + if (!Stereo::getMultiview()) + { + auto eye = static_cast(Stereo::Manager::instance().getEye(cv)); + + if (msaa) + { + renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerMsaaFbo(eye)); + renderStage->setMultisampleResolveFramebufferObject(mMultiviewFramebuffer->layerFbo(eye)); + } + else + { + renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerFbo(eye)); + } + } + + // OSG tries to do a horizontal split, but we want to render to separate framebuffers instead. + renderStage->setViewport(mViewport); + cv->pushStateSet(mViewportStateset.get()); + traverse(node, cv); + cv->popStateSet(); + } + + private: + Stereo::MultiviewFramebuffer* mMultiviewFramebuffer; + osg::ref_ptr mViewport; + osg::ref_ptr mViewportStateset; + }; + + MultiviewFramebuffer::MultiviewFramebuffer(int width, int height, int samples) + : mWidth(width) + , mHeight(height) + , mSamples(samples) + , mMultiview(getMultiview()) + , mMultiviewFbo{ new osg::FrameBufferObject } + , mLayerFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject } + , mLayerMsaaFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject } + { + } + + MultiviewFramebuffer::~MultiviewFramebuffer() + { + } + + void MultiviewFramebuffer::attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + if (mMultiview) + { +#ifdef OSG_HAS_MULTIVIEW + mMultiviewColorTexture = createTextureArray(sourceFormat, sourceType, internalFormat); + mMultiviewFbo->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMultiviewColorTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0)); + for (unsigned i = 0; i < 2; i++) + { + mColorTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewColorTexture.get(), i); + mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); + } +#endif + } + else + { + for (unsigned i = 0; i < 2; i++) + { + if (mSamples > 1) + { + mMsaaColorTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat); + mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMsaaColorTexture[i])); + } + mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); + mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); + } + } + } + + void MultiviewFramebuffer::attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + if (mMultiview) + { +#ifdef OSG_HAS_MULTIVIEW + mMultiviewDepthTexture = createTextureArray(sourceFormat, sourceType, internalFormat); + mMultiviewFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMultiviewDepthTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0)); + for (unsigned i = 0; i < 2; i++) + { + mDepthTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewDepthTexture.get(), i); + mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); + } +#endif + } + else + { + for (unsigned i = 0; i < 2; i++) + { + if (mSamples > 1) + { + mMsaaDepthTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat); + mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMsaaDepthTexture[i])); + } + mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); + mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); + } + } + } + + osg::FrameBufferObject* MultiviewFramebuffer::multiviewFbo() + { + return mMultiviewFbo; + } + + osg::FrameBufferObject* MultiviewFramebuffer::layerFbo(int i) + { + return mLayerFbo[i]; + } + + osg::FrameBufferObject* MultiviewFramebuffer::layerMsaaFbo(int i) + { + return mLayerMsaaFbo[i]; + } + + osg::Texture2DArray* MultiviewFramebuffer::multiviewColorBuffer() + { + return mMultiviewColorTexture; + } + + osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i) + { + return mColorTexture[i]; + } + + osg::Texture2D* MultiviewFramebuffer::layerDepthBuffer(int i) + { + return mDepthTexture[i]; + } + void MultiviewFramebuffer::attachTo(osg::Camera* camera) + { +#ifdef OSG_HAS_MULTIVIEW + if (mMultiview) + { + if (mMultiviewColorTexture) + { + camera->attach(osg::Camera::COLOR_BUFFER, mMultiviewColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples); + camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mMultiviewColorTexture->getInternalFormat(); + camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = false; + } + if (mMultiviewDepthTexture) + { + camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mMultiviewDepthTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples); + camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mMultiviewDepthTexture->getInternalFormat(); + camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = false; + } + } +#endif + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + + if (!mCullCallback) + mCullCallback = new UpdateRenderStagesCallback(this); + camera->addCullCallback(mCullCallback); + } + + void MultiviewFramebuffer::detachFrom(osg::Camera* camera) + { +#ifdef OSG_HAS_MULTIVIEW + if (mMultiview) + { + if (mMultiviewColorTexture) + { + camera->detach(osg::Camera::COLOR_BUFFER); + } + if (mMultiviewDepthTexture) + { + camera->detach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER); + } + } +#endif + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); + if (mCullCallback) + camera->removeCullCallback(mCullCallback); + } + + osg::Texture2D* MultiviewFramebuffer::createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + osg::Texture2D* texture = new osg::Texture2D; + texture->setTextureSize(mWidth, mHeight); + texture->setSourceFormat(sourceFormat); + texture->setSourceType(sourceType); + texture->setInternalFormat(internalFormat); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return texture; + } + + osg::Texture2DMultisample* MultiviewFramebuffer::createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + osg::Texture2DMultisample* texture = new osg::Texture2DMultisample; + texture->setTextureSize(mWidth, mHeight); + texture->setNumSamples(mSamples); + texture->setSourceFormat(sourceFormat); + texture->setSourceType(sourceType); + texture->setInternalFormat(internalFormat); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return texture; + } + + osg::Texture2DArray* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + osg::Texture2DArray* textureArray = new osg::Texture2DArray; + textureArray->setTextureSize(mWidth, mHeight, 2); + textureArray->setSourceFormat(sourceFormat); + textureArray->setSourceType(sourceType); + textureArray->setInternalFormat(internalFormat); + textureArray->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + textureArray->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + textureArray->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + textureArray->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + textureArray->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return textureArray; + } +} diff --git a/components/stereo/multiview.hpp b/components/stereo/multiview.hpp new file mode 100644 index 0000000000..298d042ffc --- /dev/null +++ b/components/stereo/multiview.hpp @@ -0,0 +1,85 @@ +#ifndef STEREO_MULTIVIEW_H +#define STEREO_MULTIVIEW_H + +#include +#include +#include + +#include +#include + +namespace osg +{ + class FrameBufferObject; + class Texture; + class Texture2D; + class Texture2DMultisample; + class Texture2DArray; +} + +namespace Stereo +{ + class UpdateRenderStagesCallback; + + //! Check if TextureView is supported. Results are undefined if called before configureExtensions(). + bool getTextureViewSupported(); + + //! Check if Multiview should be used. Results are undefined if called before configureExtensions(). + bool getMultiview(); + + //! Use the provided context to check what extensions are supported and configure use of multiview based on extensions and settings. + void configureExtensions(unsigned int contextID); + + //! Sets the appropriate vertex buffer hint on OSG's display settings if needed + void setVertexBufferHint(); + + //! Creates a Texture2D as a texture view into a Texture2DArray + osg::ref_ptr createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer); + + //! Class that manages the specifics of GL_OVR_Multiview aware framebuffers, separating the layers into separate framebuffers, and disabling + class MultiviewFramebuffer + { + public: + MultiviewFramebuffer(int width, int height, int samples); + ~MultiviewFramebuffer(); + + void attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat); + void attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat); + + osg::FrameBufferObject* multiviewFbo(); + osg::FrameBufferObject* layerFbo(int i); + osg::FrameBufferObject* layerMsaaFbo(int i); + osg::Texture2DArray* multiviewColorBuffer(); + osg::Texture2D* layerColorBuffer(int i); + osg::Texture2D* layerDepthBuffer(int i); + + void attachTo(osg::Camera* camera); + void detachFrom(osg::Camera* camera); + + int width() const { return mWidth; } + int height() const { return mHeight; } + int samples() const { return mSamples; }; + + private: + osg::Texture2D* createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat); + osg::Texture2DMultisample* createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat); + osg::Texture2DArray* createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat); + + int mWidth; + int mHeight; + int mSamples; + bool mMultiview; + osg::ref_ptr mCullCallback; + osg::ref_ptr mMultiviewFbo; + std::array, 2> mLayerFbo; + std::array, 2> mLayerMsaaFbo; + osg::ref_ptr mMultiviewColorTexture; + osg::ref_ptr mMultiviewDepthTexture; + std::array, 2> mColorTexture; + std::array, 2> mMsaaColorTexture; + std::array, 2> mDepthTexture; + std::array, 2> mMsaaDepthTexture; + }; +} + +#endif diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp new file mode 100644 index 0000000000..7214fe0031 --- /dev/null +++ b/components/stereo/stereomanager.cpp @@ -0,0 +1,459 @@ +#include "stereomanager.hpp" +#include "multiview.hpp" +#include "frustum.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +namespace Stereo +{ + // Update stereo view/projection during update + class StereoUpdateCallback final : public osg::Callback + { + public: + StereoUpdateCallback(Manager* stereoView) : stereoView(stereoView) {} + + bool run(osg::Object* object, osg::Object* data) override + { + auto b = traverse(object, data); + stereoView->update(); + return b; + } + + Manager* stereoView; + }; + + // Update states during cull + class BruteForceStereoStatesetUpdateCallback final : public SceneUtil::StateSetUpdater + { + public: + BruteForceStereoStatesetUpdateCallback(Manager* manager) + : mManager(manager) + { + } + + protected: + virtual void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override + { + } + + void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + osg::Matrix dummy; + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(mManager->computeEyeProjection(0, SceneUtil::AutoDepth::isReversed())); + } + + void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + osg::Matrix dummy; + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(mManager->computeEyeProjection(1, SceneUtil::AutoDepth::isReversed())); + } + + private: + Manager* mManager; + }; + + // Update states during cull + class MultiviewStereoStatesetUpdateCallback : public SceneUtil::StateSetUpdater + { + public: + MultiviewStereoStatesetUpdateCallback(Manager* manager) + : mManager(manager) + { + } + + protected: + virtual void setDefaults(osg::StateSet* stateset) + { + stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2)); + stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2)); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + mManager->updateMultiviewStateset(stateset); + } + + private: + Manager* mManager; + }; + + static Manager* sInstance = nullptr; + + Manager& Manager::instance() + { + return *sInstance; + } + + struct CustomViewCallback : public Manager::UpdateViewCallback + { + public: + CustomViewCallback(); + + void updateView(View& left, View& right) override; + + private: + View mLeft; + View mRight; + }; + + Manager::Manager(osgViewer::Viewer* viewer) + : mViewer(viewer) + , mMainCamera(mViewer->getCamera()) + , mUpdateCallback(new StereoUpdateCallback(this)) + , mMasterProjectionMatrix(osg::Matrix::identity()) + , mEyeResolutionOverriden(false) + , mEyeResolutionOverride(0,0) + , mFrustumManager(nullptr) + , mUpdateViewCallback(nullptr) + { + if (sInstance) + throw std::logic_error("Double instance of Stereo::Manager"); + sInstance = this; + + if (Settings::Manager::getBool("use custom view", "Stereo")) + mUpdateViewCallback = std::make_shared(); + + if (Settings::Manager::getBool("use custom eye resolution", "Stereo")) + { + osg::Vec2i eyeResolution = osg::Vec2i(); + eyeResolution.x() = Settings::Manager::getInt("eye resolution x", "Stereo View"); + eyeResolution.y() = Settings::Manager::getInt("eye resolution y", "Stereo View"); + overrideEyeResolution(eyeResolution); + } + } + + Manager::~Manager() + { + } + + void Manager::initializeStereo(osg::GraphicsContext* gc) + { + mMainCamera->addUpdateCallback(mUpdateCallback); + mFrustumManager = std::make_unique(mViewer->getCamera()); + + auto ci = gc->getState()->getContextID(); + configureExtensions(ci); + + if(getMultiview()) + setupOVRMultiView2Technique(); + else + setupBruteForceTechnique(); + + updateStereoFramebuffer(); + + } + + void Manager::shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const + { + if (getMultiview()) + { + defines["GLSLVersion"] = "330 compatibility"; + defines["useOVR_multiview"] = "1"; + defines["numViews"] = "2"; + } + else + { + defines["useOVR_multiview"] = "0"; + defines["numViews"] = "1"; + } + } + + void Manager::overrideEyeResolution(const osg::Vec2i& eyeResolution) + { + mEyeResolutionOverride = eyeResolution; + mEyeResolutionOverriden = true; + + if (mMultiviewFramebuffer) + updateStereoFramebuffer(); + } + + void Manager::screenResolutionChanged() + { + updateStereoFramebuffer(); + } + + osg::Vec2i Manager::eyeResolution() + { + if (mEyeResolutionOverriden) + return mEyeResolutionOverride; + auto width = mMainCamera->getViewport()->width() / 2; + auto height = mMainCamera->getViewport()->height(); + + return osg::Vec2i(width, height); + } + + void Manager::disableStereoForNode(osg::Node* node) + { + // Re-apply the main camera's full viewport to return to full screen rendering. + node->getOrCreateStateSet()->setAttribute(mMainCamera->getViewport()); + } + + void Manager::setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique) + { + if (mFrustumManager) + mFrustumManager->setShadowTechnique(shadowTechnique); + } + + void Manager::setupBruteForceTechnique() + { + auto* ds = osg::DisplaySettings::instance().get(); + ds->setStereo(true); + ds->setStereoMode(osg::DisplaySettings::StereoMode::HORIZONTAL_SPLIT); + ds->setUseSceneViewForStereoHint(true); + + mMainCamera->addCullCallback(new BruteForceStereoStatesetUpdateCallback(this)); + + struct ComputeStereoMatricesCallback : public osgUtil::SceneView::ComputeStereoMatricesCallback + { + ComputeStereoMatricesCallback(Manager* sv) + : mManager(sv) + { + + } + + osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection) const override + { + (void)projection; + return mManager->computeEyeProjection(0, false); + } + + osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const override + { + (void)view; + return mManager->computeEyeView(0); + } + + osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection) const override + { + (void)projection; + return mManager->computeEyeProjection(1, false); + } + + osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const override + { + (void)view; + return mManager->computeEyeView(1); + } + + Manager* mManager; + }; + + auto* renderer = static_cast(mMainCamera->getRenderer()); + for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) }) + { + sceneView->setComputeStereoMatricesCallback(new ComputeStereoMatricesCallback(this)); + + auto* cvMain = sceneView->getCullVisitor(); + auto* cvLeft = sceneView->getCullVisitorLeft(); + auto* cvRight = sceneView->getCullVisitorRight(); + if (!cvMain) + sceneView->setCullVisitor(cvMain = new osgUtil::CullVisitor()); + if (!cvLeft) + sceneView->setCullVisitor(cvLeft = cvMain->clone()); + if (!cvRight) + sceneView->setCullVisitor(cvRight = cvMain->clone()); + + // Osg by default gives cullVisitorLeft and cullVisitor the same identifier. + // So we make our own to avoid confusion + cvMain->setIdentifier(mIdentifierMain); + cvLeft->setIdentifier(mIdentifierLeft); + cvRight->setIdentifier(mIdentifierRight); + } + } + + void Manager::setupOVRMultiView2Technique() + { + auto* ds = osg::DisplaySettings::instance().get(); + ds->setStereo(false); + + mMainCamera->addCullCallback(new MultiviewStereoStatesetUpdateCallback(this)); + } + + void Manager::updateStereoFramebuffer() + { + auto samples = Settings::Manager::getInt("antialiasing", "Video"); + auto eyeRes = eyeResolution(); + + if (mMultiviewFramebuffer) + mMultiviewFramebuffer->detachFrom(mMainCamera); + mMultiviewFramebuffer = std::make_shared(static_cast(eyeRes.x()), static_cast(eyeRes.y()), samples); + mMultiviewFramebuffer->attachColorComponent(SceneUtil::Color::colorSourceFormat(), SceneUtil::Color::colorSourceType(), SceneUtil::Color::colorInternalFormat()); + mMultiviewFramebuffer->attachDepthComponent(SceneUtil::AutoDepth::depthSourceFormat(), SceneUtil::AutoDepth::depthSourceType(), SceneUtil::AutoDepth::depthInternalFormat()); + mMultiviewFramebuffer->attachTo(mMainCamera); + } + + void Manager::update() + { + double near_ = 1.f; + double far_ = 10000.f; + + near_ = Settings::Manager::getFloat("near clip", "Camera"); + far_ = Settings::Manager::getFloat("viewing distance", "Camera"); + auto projectionMatrix = mMainCamera->getProjectionMatrix(); + + if (mUpdateViewCallback) + { + mUpdateViewCallback->updateView(mView[0], mView[1]); + auto viewMatrix = mMainCamera->getViewMatrix(); + mViewOffsetMatrix[0] = mView[0].viewMatrix(true); + mViewOffsetMatrix[1] = mView[1].viewMatrix(true); + mViewMatrix[0] = viewMatrix * mViewOffsetMatrix[0]; + mViewMatrix[1] = viewMatrix * mViewOffsetMatrix[1]; + mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false); + mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false); + if (SceneUtil::AutoDepth::isReversed()) + { + mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true); + mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, true); + } + + View masterView; + masterView.fov.angleDown = std::min(mView[0].fov.angleDown, mView[1].fov.angleDown); + masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp); + masterView.fov.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft); + masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight); + projectionMatrix = masterView.perspectiveMatrix(near_, far_, false); + mMainCamera->setProjectionMatrix(projectionMatrix); + } + else + { + auto* ds = osg::DisplaySettings::instance().get(); + auto viewMatrix = mMainCamera->getViewMatrix(); + mViewMatrix[0] = ds->computeLeftEyeViewImplementation(viewMatrix); + mViewMatrix[1] = ds->computeRightEyeViewImplementation(viewMatrix); + mViewOffsetMatrix[0] = osg::Matrix::inverse(viewMatrix) * mViewMatrix[0]; + mViewOffsetMatrix[1] = osg::Matrix::inverse(viewMatrix) * mViewMatrix[1]; + mProjectionMatrix[0] = ds->computeLeftEyeProjectionImplementation(projectionMatrix); + mProjectionMatrix[1] = ds->computeRightEyeProjectionImplementation(projectionMatrix); + if (SceneUtil::AutoDepth::isReversed()) + { + mProjectionMatrixReverseZ[0] = ds->computeLeftEyeProjectionImplementation(mMasterProjectionMatrix); + mProjectionMatrixReverseZ[1] = ds->computeRightEyeProjectionImplementation(mMasterProjectionMatrix); + } + } + + mFrustumManager->update( + { + mViewOffsetMatrix[0] * mProjectionMatrix[0], + mViewOffsetMatrix[1] * mProjectionMatrix[1] + }); + } + + void Manager::updateMultiviewStateset(osg::StateSet* stateset) + { + // Update stereo uniforms + auto * viewMatrixMultiViewUniform = stateset->getUniform("viewMatrixMultiView"); + auto * projectionMatrixMultiViewUniform = stateset->getUniform("projectionMatrixMultiView"); + + for (int view : {0, 1}) + { + viewMatrixMultiViewUniform->setElement(view, mViewOffsetMatrix[view]); + projectionMatrixMultiViewUniform->setElement(view, computeEyeProjection(view, SceneUtil::AutoDepth::isReversed())); + } + } + + void Manager::setUpdateViewCallback(std::shared_ptr cb) + { + mUpdateViewCallback = cb; + } + + void Manager::setCullCallback(osg::ref_ptr cb) + { + mMainCamera->setCullCallback(cb); + } + + osg::Matrixd Manager::computeEyeProjection(int view, bool reverseZ) const + { + return reverseZ ? mProjectionMatrixReverseZ[view] : mProjectionMatrix[view]; + } + + osg::Matrixd Manager::computeEyeView(int view) const + { + return mViewMatrix[view]; + } + + Eye Manager::getEye(const osgUtil::CullVisitor* cv) const + { + if (cv->getIdentifier() == mIdentifierMain) + return Eye::Center; + if (cv->getIdentifier() == mIdentifierLeft) + return Eye::Left; + if (cv->getIdentifier() == mIdentifierRight) + return Eye::Right; + return Eye::Center; + } + + bool getStereo() + { + static bool stereo = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); + return stereo; + } + + CustomViewCallback::CustomViewCallback() + { + mLeft.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View"); + mLeft.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View"); + mLeft.pose.position.z() = Settings::Manager::getDouble("left eye offset z", "Stereo View"); + mLeft.pose.orientation.x() = Settings::Manager::getDouble("left eye orientation x", "Stereo View"); + mLeft.pose.orientation.y() = Settings::Manager::getDouble("left eye orientation y", "Stereo View"); + mLeft.pose.orientation.z() = Settings::Manager::getDouble("left eye orientation z", "Stereo View"); + mLeft.pose.orientation.w() = Settings::Manager::getDouble("left eye orientation w", "Stereo View"); + mLeft.fov.angleLeft = Settings::Manager::getDouble("left eye fov left", "Stereo View"); + mLeft.fov.angleRight = Settings::Manager::getDouble("left eye fov right", "Stereo View"); + mLeft.fov.angleUp = Settings::Manager::getDouble("left eye fov up", "Stereo View"); + mLeft.fov.angleDown = Settings::Manager::getDouble("left eye fov down", "Stereo View"); + + mRight.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View"); + mRight.pose.position.y() = Settings::Manager::getDouble("right eye offset y", "Stereo View"); + mRight.pose.position.z() = Settings::Manager::getDouble("right eye offset z", "Stereo View"); + mRight.pose.orientation.x() = Settings::Manager::getDouble("right eye orientation x", "Stereo View"); + mRight.pose.orientation.y() = Settings::Manager::getDouble("right eye orientation y", "Stereo View"); + mRight.pose.orientation.z() = Settings::Manager::getDouble("right eye orientation z", "Stereo View"); + mRight.pose.orientation.w() = Settings::Manager::getDouble("right eye orientation w", "Stereo View"); + mRight.fov.angleLeft = Settings::Manager::getDouble("right eye fov left", "Stereo View"); + mRight.fov.angleRight = Settings::Manager::getDouble("right eye fov right", "Stereo View"); + mRight.fov.angleUp = Settings::Manager::getDouble("right eye fov up", "Stereo View"); + mRight.fov.angleDown = Settings::Manager::getDouble("right eye fov down", "Stereo View"); + } + + void CustomViewCallback::updateView(View& left, View& right) + { + left = mLeft; + right = mRight; + } +} diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp new file mode 100644 index 0000000000..fea5a8543f --- /dev/null +++ b/components/stereo/stereomanager.hpp @@ -0,0 +1,134 @@ +#ifndef STEREO_MANAGER_H +#define STEREO_MANAGER_H + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace osg +{ + class FrameBufferObject; + class Texture2D; + class Texture2DMultisample; + class Texture2DArray; +} + +namespace osgViewer +{ + class Viewer; +} + +namespace SceneUtil +{ + class MWShadowTechnique; +} + +namespace Stereo +{ + class MultiviewFramebuffer; + class StereoFrustumManager; + class MultiviewStereoStatesetUpdateCallback; + + bool getStereo(); + + //! Class that provides tools for managing stereo mode + class Manager + { + public: + struct UpdateViewCallback + { + virtual ~UpdateViewCallback() = default; + + //! Called during the update traversal of every frame to update stereo views. + virtual void updateView(View& left, View& right) = 0; + }; + + //! Gets the singleton instance + static Manager& instance(); + + Manager(osgViewer::Viewer* viewer); + ~Manager(); + + //! Called during update traversal + void update(); + + void initializeStereo(osg::GraphicsContext* gc); + + //! Callback that updates stereo configuration during the update pass + void setUpdateViewCallback(std::shared_ptr cb); + + //! Set the cull callback on the appropriate camera object + void setCullCallback(osg::ref_ptr cb); + + osg::Matrixd computeEyeProjection(int view, bool reverseZ) const; + osg::Matrixd computeEyeView(int view) const; + + //! Sets up any definitions necessary for stereo rendering + void shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const; + + const std::shared_ptr& multiviewFramebuffer() { return mMultiviewFramebuffer; }; + + //! Sets rendering resolution of each eye to eyeResolution. + //! Once set, there will no longer be any connection between rendering resolution and screen/window resolution. + void overrideEyeResolution(const osg::Vec2i& eyeResolution); + + //! Notify stereo manager that the screen/window resolution has changed. + void screenResolutionChanged(); + + //! Get current eye resolution + osg::Vec2i eyeResolution(); + + //! The projection intended for rendering. When reverse Z is enabled, this is not the same as the camera's projection matrix, + //! and therefore must be provided to the manager explicitly. + void setMasterProjectionMatrix(const osg::Matrix& projectionMatrix) { mMasterProjectionMatrix = projectionMatrix; } + + //! Causes the subgraph represented by the node to draw to the full viewport. + //! This has no effect if stereo is not enabled + void disableStereoForNode(osg::Node* node); + + void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique); + + /// Determine which view the cull visitor belongs to + Eye getEye(const osgUtil::CullVisitor* cv) const; + + private: + friend class MultiviewStereoStatesetUpdateCallback; + void updateMultiviewStateset(osg::StateSet* stateset); + void updateStereoFramebuffer(); + void setupBruteForceTechnique(); + void setupOVRMultiView2Technique(); + + osg::ref_ptr mViewer; + osg::ref_ptr mMainCamera; + osg::ref_ptr mUpdateCallback; + std::string mError; + osg::Matrix mMasterProjectionMatrix; + std::shared_ptr mMultiviewFramebuffer; + bool mEyeResolutionOverriden; + osg::Vec2i mEyeResolutionOverride; + + std::array mView; + std::array mViewMatrix; + std::array mViewOffsetMatrix; + std::array mProjectionMatrix; + std::array mProjectionMatrixReverseZ; + + std::unique_ptr mFrustumManager; + std::shared_ptr mUpdateViewCallback; + + using Identifier = osgUtil::CullVisitor::Identifier; + osg::ref_ptr mIdentifierMain = new Identifier(); + osg::ref_ptr mIdentifierLeft = new Identifier(); + osg::ref_ptr mIdentifierRight = new Identifier(); + }; +} + +#endif diff --git a/components/stereo/types.cpp b/components/stereo/types.cpp new file mode 100644 index 0000000000..4829e32b55 --- /dev/null +++ b/components/stereo/types.cpp @@ -0,0 +1,168 @@ +#include "types.hpp" + +#include + +namespace Stereo +{ + + Pose Pose::operator+(const Pose& rhs) + { + Pose pose = *this; + pose.position += this->orientation * rhs.position; + pose.orientation = rhs.orientation * this->orientation; + return pose; + } + + const Pose& Pose::operator+=(const Pose& rhs) + { + *this = *this + rhs; + return *this; + } + + Pose Pose::operator*(float scalar) + { + Pose pose = *this; + pose.position *= scalar; + return pose; + } + + const Pose& Pose::operator*=(float scalar) + { + *this = *this * scalar; + return *this; + } + + Pose Pose::operator/(float scalar) + { + Pose pose = *this; + pose.position /= scalar; + return pose; + } + const Pose& Pose::operator/=(float scalar) + { + *this = *this / scalar; + return *this; + } + + bool Pose::operator==(const Pose& rhs) const + { + return position == rhs.position && orientation == rhs.orientation; + } + + osg::Matrix View::viewMatrix(bool useGLConventions) + { + auto position = pose.position; + auto orientation = pose.orientation; + + if (useGLConventions) + { + // When applied as an offset to an existing view matrix, + // that view matrix will already convert points to a camera space + // with opengl conventions. So we need to convert offsets to opengl + // conventions. + float y = position.y(); + float z = position.z(); + position.y() = z; + position.z() = -y; + + y = orientation.y(); + z = orientation.z(); + orientation.y() = z; + orientation.z() = -y; + + osg::Matrix viewMatrix; + viewMatrix.setTrans(-position); + viewMatrix.postMultRotate(orientation.conj()); + return viewMatrix; + } + else + { + osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0); + osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1); + osg::Matrix viewMatrix; + viewMatrix.makeLookAt(position, position + forward, up); + + return viewMatrix; + } + } + + osg::Matrix View::perspectiveMatrix(float near, float far, bool reverseZ) + { + const float tanLeft = tanf(fov.angleLeft); + const float tanRight = tanf(fov.angleRight); + const float tanDown = tanf(fov.angleDown); + const float tanUp = tanf(fov.angleUp); + + const float tanWidth = tanRight - tanLeft; + const float tanHeight = tanUp - tanDown; + + float matrix[16] = {}; + + matrix[0] = 2 / tanWidth; + matrix[4] = 0; + matrix[8] = (tanRight + tanLeft) / tanWidth; + matrix[12] = 0; + + matrix[1] = 0; + matrix[5] = 2 / tanHeight; + matrix[9] = (tanUp + tanDown) / tanHeight; + matrix[13] = 0; + + if (reverseZ) { + matrix[2] = 0; + matrix[6] = 0; + matrix[10] = (2.f * near) / (far - near); + matrix[14] = ((2.f * near) * far) / (far - near); + } + else { + matrix[2] = 0; + matrix[6] = 0; + matrix[10] = -(far + near) / (far - near); + matrix[14] = -(far * (2.f * near)) / (far - near); + } + + matrix[3] = 0; + matrix[7] = 0; + matrix[11] = -1; + matrix[15] = 0; + + return osg::Matrix(matrix); + } + + bool FieldOfView::operator==(const FieldOfView& rhs) const + { + return angleDown == rhs.angleDown + && angleUp == rhs.angleUp + && angleLeft == rhs.angleLeft + && angleRight == rhs.angleRight; + } + + bool View::operator==(const View& rhs) const + { + return pose == rhs.pose && fov == rhs.fov; + } + + std::ostream& operator <<( + std::ostream& os, + const Pose& pose) + { + os << "position=" << pose.position << ", orientation=" << pose.orientation; + return os; + } + + std::ostream& operator <<( + std::ostream& os, + const FieldOfView& fov) + { + os << "left=" << fov.angleLeft << ", right=" << fov.angleRight << ", down=" << fov.angleDown << ", up=" << fov.angleUp; + return os; + } + + std::ostream& operator <<( + std::ostream& os, + const View& view) + { + os << "pose=< " << view.pose << " >, fov=< " << view.fov << " >"; + return os; + } +} diff --git a/components/stereo/types.hpp b/components/stereo/types.hpp new file mode 100644 index 0000000000..9f2aa074d5 --- /dev/null +++ b/components/stereo/types.hpp @@ -0,0 +1,61 @@ +#ifndef STEREO_TYPES_H +#define STEREO_TYPES_H + +#include +#include + + +namespace Stereo +{ + enum class Eye + { + Left = 0, + Right = 1, + Center = 2 + }; + + struct Pose + { + //! Position in space + osg::Vec3 position{ 0,0,0 }; + //! Orientation in space. + osg::Quat orientation{ 0,0,0,1 }; + + //! Add one pose to another + Pose operator+(const Pose& rhs); + const Pose& operator+=(const Pose& rhs); + + //! Scale a pose (does not affect orientation) + Pose operator*(float scalar); + const Pose& operator*=(float scalar); + Pose operator/(float scalar); + const Pose& operator/=(float scalar); + + bool operator==(const Pose& rhs) const; + }; + + struct FieldOfView { + float angleLeft{ 0.f }; + float angleRight{ 0.f }; + float angleUp{ 0.f }; + float angleDown{ 0.f }; + + bool operator==(const FieldOfView& rhs) const; + }; + + struct View + { + Pose pose; + FieldOfView fov; + bool operator==(const View& rhs) const; + + osg::Matrix viewMatrix(bool useGLConventions); + osg::Matrix perspectiveMatrix(float near, float far, bool reverseZ); + }; + + std::ostream& operator <<(std::ostream& os, const Pose& pose); + std::ostream& operator <<(std::ostream& os, const FieldOfView& fov); + std::ostream& operator <<(std::ostream& os, const View& view); +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 6ae1a970d3..8e892a08c8 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -251,6 +252,8 @@ namespace Terrain defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; + Stereo::Manager::instance().shaderStereoDefines(defineMap); + osg::ref_ptr vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); osg::ref_ptr fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); if (!vertexShader || !fragmentShader) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 50214668d1..ec03720dfe 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1129,3 +1129,60 @@ lua debug = false # Set the maximum number of threads used for Lua scripts. # If zero, Lua scripts are processed in the main thread. lua num threads = 1 + +[Stereo] +# Enable/disable stereo view. This setting is ignored in VR. +stereo enabled = false + +# If enabled, OpenMW will use the GL_OVR_MultiView and GL_OVR_MultiView2 extensions where possible. +multiview = false + +# May accelerate the BruteForce method when shadows are enabled +shared shadow maps = true + +# If false, OpenMW-VR will disable display lists when using multiview. Necessary on some buggy drivers, but may incur a slight performance penalty. +allow display lists for multiview = false + +# If false, the default OSG horizontal split will be used for stereo +# If true, the config defined in the [Stereo View] settings category will be used +# Note: This option is ignored in VR, and exists primarily for debugging purposes +use custom view = false + +# If true, overrides rendering resolution for each eye. +# Note: This option is ignored in VR, and exists primarily for debugging purposes +use custom eye resolution = false + +[Stereo View] +# The default values are based on an HP Reverb G2 HMD +eye resolution x = 3128 +eye resolution y = 3060 + +# Left eye offset from center, expressed in MW units (1 meter = ~70) +left eye offset x = -2.35 +left eye offset y = 0.0 +left eye offset z = 0.0 +# Left eye orientation, expressed as a quaternion +left eye orientation x = 0.0 +left eye orientation y = 0.0 +left eye orientation z = 0.0 +left eye orientation w = 1.0 +# Left eye field of view, expressed in radians +left eye fov left = -0.86 +left eye fov right = 0.78 +left eye fov up = 0.8 +left eye fov down = -0.8 + +# Left eye offset from center, expressed in MW units (1 meter = ~70) +right eye offset x = 2.35 +right eye offset y = 0.0 +right eye offset z = 0.0 +# Left eye orientation, expressed as a quaternion +right eye orientation x = 0.0 +right eye orientation y = 0.0 +right eye orientation z = 0.0 +right eye orientation w = 1.0 +# Left eye field of view +right eye fov left = -0.78 +right eye fov right = 0.86 +right eye fov up = 0.8 +right eye fov down = -0.8 \ No newline at end of file diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index c873ada12f..30909d4311 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -40,6 +40,8 @@ set(SHADER_FILES gui_fragment.glsl debug_vertex.glsl debug_fragment.glsl + multiview_fragment.glsl + multiview_vertex.glsl sky_vertex.glsl sky_fragment.glsl skypasses.glsl diff --git a/files/shaders/debug_fragment.glsl b/files/shaders/debug_fragment.glsl index 1b25510d66..ab6ad813ee 100644 --- a/files/shaders/debug_fragment.glsl +++ b/files/shaders/debug_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "vertexcolors.glsl" diff --git a/files/shaders/debug_vertex.glsl b/files/shaders/debug_vertex.glsl index fd41a6ff48..5331859122 100644 --- a/files/shaders/debug_vertex.glsl +++ b/files/shaders/debug_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "openmw_vertex.h.glsl" diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index d669634190..c7e6ec36aa 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index e707954e81..660a4624f0 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/gui_fragment.glsl b/files/shaders/gui_fragment.glsl index a8c9434711..7c0a3b4811 100644 --- a/files/shaders/gui_fragment.glsl +++ b/files/shaders/gui_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion uniform sampler2D diffuseMap; diff --git a/files/shaders/gui_vertex.glsl b/files/shaders/gui_vertex.glsl index b378b097bd..670de5b6e3 100644 --- a/files/shaders/gui_vertex.glsl +++ b/files/shaders/gui_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion varying vec2 diffuseMapUV; varying vec4 passColor; diff --git a/files/shaders/multiview_fragment.glsl b/files/shaders/multiview_fragment.glsl new file mode 100644 index 0000000000..662dbe5f55 --- /dev/null +++ b/files/shaders/multiview_fragment.glsl @@ -0,0 +1,48 @@ +#ifndef MULTIVIEW_FRAGMENT +#define MULTIVIEW_FRAGMENT + +// This file either enables or disables GL_OVR_multiview2 related code. +// For use in fragment shaders + +// REQUIREMENT: +// GLSL version: 330 or greater +// GLSL profile: compatibility +// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set. +// +// This file provides symbols for sampling stereo-aware textures. Without multiview, these texture uniforms are sampler2D, +// while in stereo the same uniforms are sampler2DArray instead. The symbols defined in this file mask this difference, allowing +// the same code to work in both cases. Use mw_stereoAwareSampler2D and mw_stereoAwareTexture2D, where you otherwise would use +// sampler2D and texture2D() +// +// USAGE: +// For stereo-aware textures, such as reflections, use the mw_stereoAwareSampler2D sampler and mw_stereoAwareTexture2D method +// instead of the usual sampler2D and texture2D. +// +// Using water reflection as an example, the old code for these textures changes from +// uniform sampler2D reflectionMap; +// ... +// vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; +// +// to +// uniform mw_stereoAwareSampler2D reflectionMap; +// ... +// vec3 reflection = mw_stereoAwareTexture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; +// + +#if @useOVR_multiview + +#extension GL_OVR_multiview : require +#extension GL_OVR_multiview2 : require +#extension GL_EXT_texture_array : require + +#define mw_stereoAwareSampler2D sampler2DArray +#define mw_stereoAwareTexture2D(texture, uv) texture2DArray(texture, vec3((uv), gl_ViewID_OVR)) + +#else // useOVR_multiview + +#define mw_stereoAwareSampler2D sampler2D +#define mw_stereoAwareTexture2D(texture, uv) texture2D(texture, uv) + +#endif // useOVR_multiview + +#endif // MULTIVIEW_FRAGMENT \ No newline at end of file diff --git a/files/shaders/multiview_vertex.glsl b/files/shaders/multiview_vertex.glsl new file mode 100644 index 0000000000..0cabeb84a2 --- /dev/null +++ b/files/shaders/multiview_vertex.glsl @@ -0,0 +1,80 @@ +#ifndef MULTIVIEW_VERTEX +#define MULTIVIEW_VERTEX + +// This file either enables or disables GL_OVR_multiview related code. +// For use in vertex shaders + +// REQUIREMENT: +// GLSL version: 330 or greater +// GLSL profile: compatibility +// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set. + +// USAGE: +// To create a stereo-aware vertex shader, use the matrix accessor functions defined in this .glsl file to compute gl_Position. +// For the vertex stage, usually only gl_Position needs to be computed with stereo awareness, while other variables such as viewPos +// should be computed in the center camera's view space and take no stereo awareness. +// +// A typical gl_Position line will look like the following: +// gl_Position = mw_stereoAwareProjectionMatrix() * (mw_stereoAwareModelViewMatrix() * gl_Vertex); +// +// If you need to perform intermediate computations before determining the final values of gl_Position and viewPos, +// your code might look more like the following: +// vec4 intermediateViewPos = gl_ModelViewMatrix * gl_Vertex; +// vec4 viewPos = myWhateverCode(intermediateViewPos); +// gl_Position = mw_stereoAwareProjectionMatrix() * mw_stereoAwareViewPosition(viewPos); +// + +#if @useOVR_multiview + +#extension GL_OVR_multiview : require + +#ifndef MULTIVIEW_FRAGMENT +// Layout cannot be used in the fragment shader +layout(num_views = @numViews) in; +#endif + +uniform mat4 projectionMatrixMultiView[@numViews]; +uniform mat4 viewMatrixMultiView[@numViews]; + +// NOTE: +// stereo-aware inverse view matrices and normal matrices have not been implemented. +// Some effects like specular highlights would need stereo aware normal matrices to be 100% correct. +// But the difference is not likely to be noticeable unless you're actively looking for it. + +mat4 mw_stereoAwareProjectionMatrix() +{ + return projectionMatrixMultiView[gl_ViewID_OVR]; +} + +mat4 mw_stereoAwareModelViewMatrix() +{ + return viewMatrixMultiView[gl_ViewID_OVR] * gl_ModelViewMatrix; +} + +vec4 mw_stereoAwareViewPosition(vec4 viewPos) +{ + return viewMatrixMultiView[gl_ViewID_OVR] * viewPos; +} + +#else // useOVR_multiview + +uniform mat4 projectionMatrix; + +mat4 mw_stereoAwareProjectionMatrix() +{ + return projectionMatrix; +} + +mat4 mw_stereoAwareModelViewMatrix() +{ + return gl_ModelViewMatrix; +} + +vec4 mw_stereoAwareViewPosition(vec4 viewPos) +{ + return viewPos; +} + +#endif // useOVR_multiview + +#endif // MULTIVIEW_VERTEX \ No newline at end of file diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index ff81a2b94d..e4d8046662 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #pragma import_defines(FORCE_OPAQUE) #if @useUBO diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index c0d28f3e34..2554677018 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -9,7 +11,6 @@ #endif #include "openmw_vertex.h.glsl" - #if @diffuseMap varying vec2 diffuseMapUV; #endif diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl index 7c4f4737e0..116677318a 100644 --- a/files/shaders/nv_nolighting_fragment.glsl +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #pragma import_defines(FORCE_OPAQUE) #if @useGPUShader4 diff --git a/files/shaders/nv_nolighting_vertex.glsl b/files/shaders/nv_nolighting_vertex.glsl index 7b1f6961b4..a3d884d026 100644 --- a/files/shaders/nv_nolighting_vertex.glsl +++ b/files/shaders/nv_nolighting_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "openmw_vertex.h.glsl" diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 85950c7468..7e7ef647cc 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #pragma import_defines(FORCE_OPAQUE) #if @useUBO diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index e8976692a2..4764e63ce4 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -9,7 +11,6 @@ #endif #include "openmw_vertex.h.glsl" - #if @diffuseMap varying vec2 diffuseMapUV; #endif diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index f52e1478ee..f5f623c828 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion varying vec2 uv; uniform samplerCube cubeMap; diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl index ad96620c3f..375dcda0ba 100644 --- a/files/shaders/s360_vertex.glsl +++ b/files/shaders/s360_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion varying vec2 uv; diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 07fad047e1..186934fec2 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #if @useGPUShader4 #extension GL_EXT_gpu_shader4: require diff --git a/files/shaders/shadowcasting_vertex.glsl b/files/shaders/shadowcasting_vertex.glsl index e36f21a4de..25d5c402d7 100644 --- a/files/shaders/shadowcasting_vertex.glsl +++ b/files/shaders/shadowcasting_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion varying vec2 diffuseMapUV; diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl index cfa3650c02..0f2ff938c2 100644 --- a/files/shaders/sky_fragment.glsl +++ b/files/shaders/sky_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "skypasses.glsl" diff --git a/files/shaders/sky_vertex.glsl b/files/shaders/sky_vertex.glsl index 8ff9c0f156..5b6a99be01 100644 --- a/files/shaders/sky_vertex.glsl +++ b/files/shaders/sky_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #include "openmw_vertex.h.glsl" @@ -9,6 +11,20 @@ uniform int pass; varying vec4 passColor; varying vec2 diffuseMapUV; +mat4 selectModelViewMatrix() +{ +#if @useOVR_multiview + mat4 viewOffsetMatrix = viewMatrixMultiView[gl_ViewID_OVR]; + // Sky geometries aren't actually all that distant. So delete view translation to keep them looking distant. + viewOffsetMatrix[3][0] = 0; + viewOffsetMatrix[3][1] = 0; + viewOffsetMatrix[3][2] = 0; + return viewOffsetMatrix * gl_ModelViewMatrix; +#else + return gl_ModelViewMatrix; +#endif +} + void main() { gl_Position = mw_modelToClip(gl_Vertex); diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index d9d4a6dc30..e5532f9bce 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 5519001219..abceb019b7 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -9,7 +11,6 @@ #endif #include "openmw_vertex.h.glsl" - varying vec2 uv; varying float euclideanDepth; varying float linearDepth; diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index bf35ca78ca..87645f2135 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_fragment.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index b09d3b54ae..e3dd29d2a0 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "openmw_vertex.h.glsl" From 606a795a54303712f46c41df3b5799f548b794f2 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 24 Apr 2022 15:29:16 +0000 Subject: [PATCH 2359/2859] multiview linker-method --- .gitlab-ci.yml | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 1 - components/sceneutil/mwshadowtechnique.cpp | 5 +- components/stereo/stereomanager.cpp | 1 - files/shaders/CMakeLists.txt | 4 +- files/shaders/debug_fragment.glsl | 2 +- files/shaders/debug_vertex.glsl | 2 +- files/shaders/groundcover_fragment.glsl | 2 +- files/shaders/groundcover_vertex.glsl | 4 +- files/shaders/gui_fragment.glsl | 2 +- files/shaders/gui_vertex.glsl | 2 +- files/shaders/multiview_fragment.glsl | 48 ------------ files/shaders/multiview_vertex.glsl | 80 -------------------- files/shaders/nv_default_fragment.glsl | 2 +- files/shaders/nv_default_vertex.glsl | 4 +- files/shaders/nv_nolighting_fragment.glsl | 2 +- files/shaders/nv_nolighting_vertex.glsl | 2 +- files/shaders/objects_fragment.glsl | 2 +- files/shaders/objects_vertex.glsl | 4 +- files/shaders/openmw_fragment.h.glsl | 3 +- files/shaders/openmw_fragment_multiview.glsl | 31 ++++++++ files/shaders/openmw_vertex.h.glsl | 3 +- files/shaders/openmw_vertex_multiview.glsl | 41 ++++++++++ files/shaders/s360_fragment.glsl | 2 +- files/shaders/s360_vertex.glsl | 2 +- files/shaders/shadowcasting_fragment.glsl | 2 +- files/shaders/shadowcasting_vertex.glsl | 2 +- files/shaders/sky_fragment.glsl | 2 +- files/shaders/sky_vertex.glsl | 10 +-- files/shaders/terrain_fragment.glsl | 2 +- files/shaders/terrain_vertex.glsl | 4 +- files/shaders/water_fragment.glsl | 4 +- files/shaders/water_vertex.glsl | 2 +- 33 files changed, 112 insertions(+), 169 deletions(-) delete mode 100644 files/shaders/multiview_fragment.glsl delete mode 100644 files/shaders/multiview_vertex.glsl create mode 100644 files/shaders/openmw_fragment_multiview.glsl create mode 100644 files/shaders/openmw_vertex_multiview.glsl diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fdfbdac13b..696e8113c5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -378,7 +378,7 @@ variables: &tests-targets - $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_DIR = "$(Get-Location)\ccache" - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview - cd MSVC2019_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config --target ($targets.Split(',')) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15d690534a..20e69450d1 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -412,7 +412,6 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; globalDefines["useGPUShader4"] = "0"; - globalDefines["GLSLVersion"] = "120"; globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 60dd613df5..3210bf4a4e 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -921,7 +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_vertex.glsl", { {"GLSLVersion", "120"} }, osg::Shader::VERTEX); + osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", { }, osg::Shader::VERTEX); osg::ref_ptr exts = osg::GLExtensions::Get(0, false); std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) @@ -932,8 +932,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"}, {"adjustCoverage", "1"}, - {"useGPUShader4", useGPUShader4}, - {"GLSLVersion", "120"} + {"useGPUShader4", useGPUShader4} }, osg::Shader::FRAGMENT)); } } diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index 7214fe0031..bbbdc6e934 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -182,7 +182,6 @@ namespace Stereo { if (getMultiview()) { - defines["GLSLVersion"] = "330 compatibility"; defines["useOVR_multiview"] = "1"; defines["numViews"] = "2"; } diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 30909d4311..97f32ad979 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -18,8 +18,10 @@ set(SHADER_FILES objects_fragment.glsl openmw_fragment.glsl openmw_fragment.h.glsl + openmw_fragment_multiview.glsl openmw_vertex.glsl openmw_vertex.h.glsl + openmw_vertex_multiview.glsl terrain_vertex.glsl terrain_fragment.glsl lighting.glsl @@ -40,8 +42,6 @@ set(SHADER_FILES gui_fragment.glsl debug_vertex.glsl debug_fragment.glsl - multiview_fragment.glsl - multiview_vertex.glsl sky_vertex.glsl sky_fragment.glsl skypasses.glsl diff --git a/files/shaders/debug_fragment.glsl b/files/shaders/debug_fragment.glsl index ab6ad813ee..1b25510d66 100644 --- a/files/shaders/debug_fragment.glsl +++ b/files/shaders/debug_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #include "vertexcolors.glsl" diff --git a/files/shaders/debug_vertex.glsl b/files/shaders/debug_vertex.glsl index 5331859122..fd41a6ff48 100644 --- a/files/shaders/debug_vertex.glsl +++ b/files/shaders/debug_vertex.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #include "openmw_vertex.h.glsl" diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index c7e6ec36aa..d669634190 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 660a4624f0..ff3dacdd99 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -1,6 +1,6 @@ -#version @GLSLVersion +#version 120 -#include "multiview_vertex.glsl" +#include "openmw_vertex.h.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/gui_fragment.glsl b/files/shaders/gui_fragment.glsl index 7c0a3b4811..a8c9434711 100644 --- a/files/shaders/gui_fragment.glsl +++ b/files/shaders/gui_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 uniform sampler2D diffuseMap; diff --git a/files/shaders/gui_vertex.glsl b/files/shaders/gui_vertex.glsl index 670de5b6e3..b378b097bd 100644 --- a/files/shaders/gui_vertex.glsl +++ b/files/shaders/gui_vertex.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 varying vec2 diffuseMapUV; varying vec4 passColor; diff --git a/files/shaders/multiview_fragment.glsl b/files/shaders/multiview_fragment.glsl deleted file mode 100644 index 662dbe5f55..0000000000 --- a/files/shaders/multiview_fragment.glsl +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef MULTIVIEW_FRAGMENT -#define MULTIVIEW_FRAGMENT - -// This file either enables or disables GL_OVR_multiview2 related code. -// For use in fragment shaders - -// REQUIREMENT: -// GLSL version: 330 or greater -// GLSL profile: compatibility -// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set. -// -// This file provides symbols for sampling stereo-aware textures. Without multiview, these texture uniforms are sampler2D, -// while in stereo the same uniforms are sampler2DArray instead. The symbols defined in this file mask this difference, allowing -// the same code to work in both cases. Use mw_stereoAwareSampler2D and mw_stereoAwareTexture2D, where you otherwise would use -// sampler2D and texture2D() -// -// USAGE: -// For stereo-aware textures, such as reflections, use the mw_stereoAwareSampler2D sampler and mw_stereoAwareTexture2D method -// instead of the usual sampler2D and texture2D. -// -// Using water reflection as an example, the old code for these textures changes from -// uniform sampler2D reflectionMap; -// ... -// vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; -// -// to -// uniform mw_stereoAwareSampler2D reflectionMap; -// ... -// vec3 reflection = mw_stereoAwareTexture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; -// - -#if @useOVR_multiview - -#extension GL_OVR_multiview : require -#extension GL_OVR_multiview2 : require -#extension GL_EXT_texture_array : require - -#define mw_stereoAwareSampler2D sampler2DArray -#define mw_stereoAwareTexture2D(texture, uv) texture2DArray(texture, vec3((uv), gl_ViewID_OVR)) - -#else // useOVR_multiview - -#define mw_stereoAwareSampler2D sampler2D -#define mw_stereoAwareTexture2D(texture, uv) texture2D(texture, uv) - -#endif // useOVR_multiview - -#endif // MULTIVIEW_FRAGMENT \ No newline at end of file diff --git a/files/shaders/multiview_vertex.glsl b/files/shaders/multiview_vertex.glsl deleted file mode 100644 index 0cabeb84a2..0000000000 --- a/files/shaders/multiview_vertex.glsl +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef MULTIVIEW_VERTEX -#define MULTIVIEW_VERTEX - -// This file either enables or disables GL_OVR_multiview related code. -// For use in vertex shaders - -// REQUIREMENT: -// GLSL version: 330 or greater -// GLSL profile: compatibility -// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set. - -// USAGE: -// To create a stereo-aware vertex shader, use the matrix accessor functions defined in this .glsl file to compute gl_Position. -// For the vertex stage, usually only gl_Position needs to be computed with stereo awareness, while other variables such as viewPos -// should be computed in the center camera's view space and take no stereo awareness. -// -// A typical gl_Position line will look like the following: -// gl_Position = mw_stereoAwareProjectionMatrix() * (mw_stereoAwareModelViewMatrix() * gl_Vertex); -// -// If you need to perform intermediate computations before determining the final values of gl_Position and viewPos, -// your code might look more like the following: -// vec4 intermediateViewPos = gl_ModelViewMatrix * gl_Vertex; -// vec4 viewPos = myWhateverCode(intermediateViewPos); -// gl_Position = mw_stereoAwareProjectionMatrix() * mw_stereoAwareViewPosition(viewPos); -// - -#if @useOVR_multiview - -#extension GL_OVR_multiview : require - -#ifndef MULTIVIEW_FRAGMENT -// Layout cannot be used in the fragment shader -layout(num_views = @numViews) in; -#endif - -uniform mat4 projectionMatrixMultiView[@numViews]; -uniform mat4 viewMatrixMultiView[@numViews]; - -// NOTE: -// stereo-aware inverse view matrices and normal matrices have not been implemented. -// Some effects like specular highlights would need stereo aware normal matrices to be 100% correct. -// But the difference is not likely to be noticeable unless you're actively looking for it. - -mat4 mw_stereoAwareProjectionMatrix() -{ - return projectionMatrixMultiView[gl_ViewID_OVR]; -} - -mat4 mw_stereoAwareModelViewMatrix() -{ - return viewMatrixMultiView[gl_ViewID_OVR] * gl_ModelViewMatrix; -} - -vec4 mw_stereoAwareViewPosition(vec4 viewPos) -{ - return viewMatrixMultiView[gl_ViewID_OVR] * viewPos; -} - -#else // useOVR_multiview - -uniform mat4 projectionMatrix; - -mat4 mw_stereoAwareProjectionMatrix() -{ - return projectionMatrix; -} - -mat4 mw_stereoAwareModelViewMatrix() -{ - return gl_ModelViewMatrix; -} - -vec4 mw_stereoAwareViewPosition(vec4 viewPos) -{ - return viewPos; -} - -#endif // useOVR_multiview - -#endif // MULTIVIEW_VERTEX \ No newline at end of file diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index e4d8046662..ff81a2b94d 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #pragma import_defines(FORCE_OPAQUE) #if @useUBO diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index 2554677018..f182bbe8be 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -1,6 +1,6 @@ -#version @GLSLVersion +#version 120 -#include "multiview_vertex.glsl" +#include "openmw_vertex.h.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl index 116677318a..7c4f4737e0 100644 --- a/files/shaders/nv_nolighting_fragment.glsl +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #pragma import_defines(FORCE_OPAQUE) #if @useGPUShader4 diff --git a/files/shaders/nv_nolighting_vertex.glsl b/files/shaders/nv_nolighting_vertex.glsl index a3d884d026..7b1f6961b4 100644 --- a/files/shaders/nv_nolighting_vertex.glsl +++ b/files/shaders/nv_nolighting_vertex.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #include "openmw_vertex.h.glsl" diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 7e7ef647cc..85950c7468 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #pragma import_defines(FORCE_OPAQUE) #if @useUBO diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 4764e63ce4..10543f767a 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -1,6 +1,6 @@ -#version @GLSLVersion +#version 120 -#include "multiview_vertex.glsl" +#include "openmw_vertex.h.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/openmw_fragment.h.glsl b/files/shaders/openmw_fragment.h.glsl index c5bcf21c9d..ae1a131b42 100644 --- a/files/shaders/openmw_fragment.h.glsl +++ b/files/shaders/openmw_fragment.h.glsl @@ -1,4 +1,5 @@ -@link "openmw_fragment.glsl" +@link "openmw_fragment.glsl" if !@useOVR_multiview +@link "openmw_fragment_multiview.glsl" if @useOVR_multiview vec4 mw_sampleReflectionMap(vec2 uv); diff --git a/files/shaders/openmw_fragment_multiview.glsl b/files/shaders/openmw_fragment_multiview.glsl new file mode 100644 index 0000000000..1a2c0d991b --- /dev/null +++ b/files/shaders/openmw_fragment_multiview.glsl @@ -0,0 +1,31 @@ +#version 330 compatibility + +#extension GL_OVR_multiview : require +#extension GL_OVR_multiview2 : require +#extension GL_EXT_texture_array : require + +#include "openmw_fragment.h.glsl" + +uniform sampler2DArray reflectionMap; + +vec4 mw_sampleReflectionMap(vec2 uv) +{ + return texture2DArray(reflectionMap, vec3((uv), gl_ViewID_OVR)); +} + +#if @refraction_enabled + +uniform sampler2DArray refractionMap; +uniform sampler2DArray refractionDepthMap; + +vec4 mw_sampleRefractionMap(vec2 uv) +{ + return texture2DArray(refractionMap, vec3((uv), gl_ViewID_OVR)); +} + +float mw_sampleRefractionDepthMap(vec2 uv) +{ + return texture2DArray(refractionDepthMap, vec3((uv), gl_ViewID_OVR)).x; +} + +#endif \ No newline at end of file diff --git a/files/shaders/openmw_vertex.h.glsl b/files/shaders/openmw_vertex.h.glsl index 00feb9310e..5f591e4311 100644 --- a/files/shaders/openmw_vertex.h.glsl +++ b/files/shaders/openmw_vertex.h.glsl @@ -1,4 +1,5 @@ -@link "openmw_vertex.glsl" +@link "openmw_vertex.glsl" if !@useOVR_multiview +@link "openmw_vertex_multiview.glsl" if @useOVR_multiview vec4 mw_modelToClip(vec4 pos); vec4 mw_modelToView(vec4 pos); diff --git a/files/shaders/openmw_vertex_multiview.glsl b/files/shaders/openmw_vertex_multiview.glsl new file mode 100644 index 0000000000..f65d9f0da0 --- /dev/null +++ b/files/shaders/openmw_vertex_multiview.glsl @@ -0,0 +1,41 @@ +#version 330 compatibility + +#extension GL_OVR_multiview : require +#extension GL_OVR_multiview2 : require + +layout(num_views = @numViews) in; + +#include "openmw_vertex.h.glsl" + +uniform mat4 projectionMatrixMultiView[@numViews]; +uniform mat4 viewMatrixMultiView[@numViews]; + +vec4 mw_modelToClip(vec4 pos) +{ + return projectionMatrixMultiView[gl_ViewID_OVR] * mw_modelToView(pos); +} + +vec4 mw_modelToView(vec4 pos) +{ + return viewMatrixMultiView[gl_ViewID_OVR] * gl_ModelViewMatrix * pos; +} + +vec4 mw_viewToClip(vec4 pos) +{ + return projectionMatrixMultiView[gl_ViewID_OVR] * pos; +} + +vec4 mw_viewStereoAdjust(vec4 pos) +{ + return viewMatrixMultiView[gl_ViewID_OVR] * pos; +} + +mat4 mw_viewMatrix() +{ + return viewMatrixMultiView[gl_ViewID_OVR] * gl_ModelViewMatrix; +} + +mat4 mw_projectionMatrix() +{ + return projectionMatrixMultiView[gl_ViewID_OVR]; +} diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index f5f623c828..f52e1478ee 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 varying vec2 uv; uniform samplerCube cubeMap; diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl index 375dcda0ba..ad96620c3f 100644 --- a/files/shaders/s360_vertex.glsl +++ b/files/shaders/s360_vertex.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 varying vec2 uv; diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 186934fec2..07fad047e1 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #if @useGPUShader4 #extension GL_EXT_gpu_shader4: require diff --git a/files/shaders/shadowcasting_vertex.glsl b/files/shaders/shadowcasting_vertex.glsl index 25d5c402d7..e36f21a4de 100644 --- a/files/shaders/shadowcasting_vertex.glsl +++ b/files/shaders/shadowcasting_vertex.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 varying vec2 diffuseMapUV; diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl index 0f2ff938c2..cfa3650c02 100644 --- a/files/shaders/sky_fragment.glsl +++ b/files/shaders/sky_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #include "skypasses.glsl" diff --git a/files/shaders/sky_vertex.glsl b/files/shaders/sky_vertex.glsl index 5b6a99be01..6d9ef7a073 100644 --- a/files/shaders/sky_vertex.glsl +++ b/files/shaders/sky_vertex.glsl @@ -1,6 +1,6 @@ -#version @GLSLVersion +#version 120 -#include "multiview_vertex.glsl" +#include "openmw_vertex.h.glsl" #include "openmw_vertex.h.glsl" @@ -14,12 +14,12 @@ varying vec2 diffuseMapUV; mat4 selectModelViewMatrix() { #if @useOVR_multiview - mat4 viewOffsetMatrix = viewMatrixMultiView[gl_ViewID_OVR]; + mat4 viewOffsetMatrix = mw_viewMatrix(); // Sky geometries aren't actually all that distant. So delete view translation to keep them looking distant. viewOffsetMatrix[3][0] = 0; viewOffsetMatrix[3][1] = 0; viewOffsetMatrix[3][2] = 0; - return viewOffsetMatrix * gl_ModelViewMatrix; + return viewOffsetMatrix; #else return gl_ModelViewMatrix; #endif @@ -27,7 +27,7 @@ mat4 selectModelViewMatrix() void main() { - gl_Position = mw_modelToClip(gl_Vertex); + gl_Position = mw_viewToClip(selectModelViewMatrix() * gl_Vertex); passColor = gl_Color; if (pass == PASS_CLOUDS) diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index e5532f9bce..d9d4a6dc30 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index abceb019b7..07ffeee202 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,6 +1,6 @@ -#version @GLSLVersion +#version 120 -#include "multiview_vertex.glsl" +#include "openmw_vertex.h.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 87645f2135..8db92a2984 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -1,6 +1,6 @@ -#version @GLSLVersion +#version 120 -#include "multiview_fragment.glsl" +#include "openmw_fragment.h.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index e3dd29d2a0..b09d3b54ae 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -1,4 +1,4 @@ -#version @GLSLVersion +#version 120 #include "openmw_vertex.h.glsl" From 276cb6f1704c049b067b67077594a5bd47aa4612 Mon Sep 17 00:00:00 2001 From: cody glassman Date: Thu, 28 Apr 2022 20:02:13 -0700 Subject: [PATCH 2360/2859] clamp vertex lighting in fragment shader properly --- CHANGELOG.md | 1 + files/shaders/groundcover_fragment.glsl | 3 ++- files/shaders/objects_fragment.glsl | 3 ++- files/shaders/terrain_fragment.glsl | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4561574175..d819dbe303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ Bug #6670: Dialogue order is incorrect Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer Bug #6682: HitOnMe doesn't fire as intended + Bug #6697: Shaders vertex lighting incorrectly clamped Bug #6711: Log time differs from real time Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index d669634190..cb53e7a63e 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -73,9 +73,10 @@ void main() vec3 diffuseLight, ambientLight; doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); lighting = diffuseLight + ambientLight; - clampLightingResult(lighting); #endif + clampLightingResult(lighting); + gl_FragData[0].xyz *= lighting; #if @radialFog diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 85950c7468..bf9f207297 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -194,9 +194,10 @@ void main() doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); vec3 emission = getEmissionColor().xyz * emissiveMult; lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; - clampLightingResult(lighting); #endif + clampLightingResult(lighting); + gl_FragData[0].xyz *= lighting; #if @envMap && !@preLightEnv diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index d9d4a6dc30..a8106f4ad5 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -87,9 +87,10 @@ void main() vec3 diffuseLight, ambientLight; doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; - clampLightingResult(lighting); #endif + clampLightingResult(lighting); + gl_FragData[0].xyz *= lighting; #if @specularMap From de291b0ec4cfa01266ee9bd2b964324f05eb54a3 Mon Sep 17 00:00:00 2001 From: Eris Caffee Date: Fri, 29 Apr 2022 09:56:08 +0000 Subject: [PATCH 2361/2859] Issue-6706 Save the size of the Options window --- CHANGELOG.md | 1 + apps/openmw/mwgui/windowmanagerimp.cpp | 1 + .../reference/modding/settings/windows.rst | 16 ++++++++++++++++ files/settings-default.cfg | 11 +++++++++++ 4 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4561574175..adfd4e6991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -152,6 +152,7 @@ Feature #6600: Support NiSortAdjustNode Feature #6684: Support NiFltAnimationNode Feature #6699: Ignored flag + Feature #6706: Save the size of the Options window Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp Task #6553: Simplify interpreter instruction registration diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 23c16082b4..707029972f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -388,6 +388,7 @@ namespace MWGui mSettingsWindow = new SettingsWindow(); mWindows.push_back(mSettingsWindow); + trackWindow(mSettingsWindow, "settings"); mGuiModeStates[GM_Settings] = GuiModeState(mSettingsWindow); mConfirmationDialog = new ConfirmationDialog(); diff --git a/docs/source/reference/modding/settings/windows.rst b/docs/source/reference/modding/settings/windows.rst index 00ee78a86d..d745c765d9 100644 --- a/docs/source/reference/modding/settings/windows.rst +++ b/docs/source/reference/modding/settings/windows.rst @@ -244,3 +244,19 @@ console The console command window. Activated by pressing the tilde (~) key. + +settings +-------- + +:Default: + x = 0.33 + + y = 0.18 + + w = 0.33 + + h = 0.66 +The settings window. +Activated by clicking Options in the main menu. + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 50214668d1..49fbbd6516 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -822,6 +822,17 @@ companion maximized w = 0.97 companion maximized h = 0.875 companion maximized = false +# Settings menu +settings x = 0.33 +settings y = 0.18 +settings w = 0.33 +settings h = 0.67 +settings maximized x = 0.015 +settings maximized y = 0.02 +settings maximized w = 0.97 +settings maximized h = 0.875 +settings maximized = false + [Navigator] # Enable navigator (true, false). When enabled background threads are started to build navmesh for world geometry. From 79577f37de99f70ca229ec6f981da02003ad985c Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Fri, 29 Apr 2022 17:35:24 +0200 Subject: [PATCH 2362/2859] Rebase artifacts + cleaned up the remaining unnecessary exposures of stereo awareness. --- apps/openmw/mwrender/skyutil.cpp | 34 ++++++++++++++++++++++ components/stereo/stereomanager.cpp | 5 ++++ components/stereo/stereomanager.hpp | 1 + files/shaders/groundcover_vertex.glsl | 4 +-- files/shaders/nv_default_vertex.glsl | 2 -- files/shaders/objects_vertex.glsl | 2 -- files/shaders/openmw_vertex.glsl | 10 ------- files/shaders/openmw_vertex.h.glsl | 4 +-- files/shaders/openmw_vertex_multiview.glsl | 12 +------- files/shaders/sky_vertex.glsl | 18 +----------- files/shaders/terrain_vertex.glsl | 2 -- files/shaders/water_fragment.glsl | 2 -- 12 files changed, 44 insertions(+), 52 deletions(-) diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index ead9d9af70..ec9e6e7635 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -602,6 +603,38 @@ namespace MWRender } } + + class SkyMultiviewStatesetUpdater: public SceneUtil::StateSetUpdater + { + public: + SkyMultiviewStatesetUpdater() + { + } + + protected: + virtual void setDefaults(osg::StateSet* stateset) + { + stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2), osg::StateAttribute::OVERRIDE); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + auto* viewMatrixMultiViewUniform = stateset->getUniform("viewMatrixMultiView"); + auto& sm = Stereo::Manager::instance(); + + for (int view : {0, 1}) + { + auto viewOffsetMatrix = sm.computeEyeViewOffset(view); + for (int col : {0, 1, 2}) + viewOffsetMatrix(3, col) = 0; + + viewMatrixMultiViewUniform->setElement(view, viewOffsetMatrix); + } + } + + private: + }; + CameraRelativeTransform::CameraRelativeTransform() { // Culling works in node-local space, not in camera space, so we can't cull this node correctly @@ -610,6 +643,7 @@ namespace MWRender setCullingActive(false); addCullCallback(new CameraRelativeTransformCullCallback); + addCullCallback(new SkyMultiviewStatesetUpdater); } CameraRelativeTransform::CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index bbbdc6e934..3cca855e7e 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -406,6 +406,11 @@ namespace Stereo return mViewMatrix[view]; } + osg::Matrixd Manager::computeEyeViewOffset(int view) const + { + return mViewOffsetMatrix[view]; + } + Eye Manager::getEye(const osgUtil::CullVisitor* cv) const { if (cv->getIdentifier() == mIdentifierMain) diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index fea5a8543f..49ae685e6b 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -70,6 +70,7 @@ namespace Stereo osg::Matrixd computeEyeProjection(int view, bool reverseZ) const; osg::Matrixd computeEyeView(int view) const; + osg::Matrixd computeEyeViewOffset(int view) const; //! Sets up any definitions necessary for stereo rendering void shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const; diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index ff3dacdd99..fff8293b61 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_vertex.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif @@ -146,7 +144,7 @@ void main(void) if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd) gl_Position = vec4(0.0, 0.0, 0.0, 1.0); else - gl_Position = mw_viewToClip(mw_viewStereoAdjust(viewPos)); + gl_Position = mw_viewToClip(viewPos); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index f182bbe8be..0d014b02fa 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_vertex.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 10543f767a..5c94417491 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_vertex.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif diff --git a/files/shaders/openmw_vertex.glsl b/files/shaders/openmw_vertex.glsl index cc14f3a0e3..68a98c7880 100644 --- a/files/shaders/openmw_vertex.glsl +++ b/files/shaders/openmw_vertex.glsl @@ -23,13 +23,3 @@ vec4 mw_viewStereoAdjust(vec4 pos) { return pos; } - -mat4 mw_viewMatrix() -{ - return gl_ModelViewMatrix; -} - -mat4 mw_projectionMatrix() -{ - return projectionMatrix; -} diff --git a/files/shaders/openmw_vertex.h.glsl b/files/shaders/openmw_vertex.h.glsl index 5f591e4311..3794121656 100644 --- a/files/shaders/openmw_vertex.h.glsl +++ b/files/shaders/openmw_vertex.h.glsl @@ -4,6 +4,4 @@ vec4 mw_modelToClip(vec4 pos); vec4 mw_modelToView(vec4 pos); vec4 mw_viewToClip(vec4 pos); -vec4 mw_viewStereoAdjust(vec4 pos); -mat4 mw_viewMatrix(); -mat4 mw_projectionMatrix(); \ No newline at end of file +vec4 mw_viewStereoAdjust(vec4 pos); \ No newline at end of file diff --git a/files/shaders/openmw_vertex_multiview.glsl b/files/shaders/openmw_vertex_multiview.glsl index f65d9f0da0..c860112d5c 100644 --- a/files/shaders/openmw_vertex_multiview.glsl +++ b/files/shaders/openmw_vertex_multiview.glsl @@ -22,20 +22,10 @@ vec4 mw_modelToView(vec4 pos) vec4 mw_viewToClip(vec4 pos) { - return projectionMatrixMultiView[gl_ViewID_OVR] * pos; + return projectionMatrixMultiView[gl_ViewID_OVR] * viewMatrixMultiView[gl_ViewID_OVR] * pos; } vec4 mw_viewStereoAdjust(vec4 pos) { return viewMatrixMultiView[gl_ViewID_OVR] * pos; } - -mat4 mw_viewMatrix() -{ - return viewMatrixMultiView[gl_ViewID_OVR] * gl_ModelViewMatrix; -} - -mat4 mw_projectionMatrix() -{ - return projectionMatrixMultiView[gl_ViewID_OVR]; -} diff --git a/files/shaders/sky_vertex.glsl b/files/shaders/sky_vertex.glsl index 6d9ef7a073..8ff9c0f156 100644 --- a/files/shaders/sky_vertex.glsl +++ b/files/shaders/sky_vertex.glsl @@ -2,8 +2,6 @@ #include "openmw_vertex.h.glsl" -#include "openmw_vertex.h.glsl" - #include "skypasses.glsl" uniform int pass; @@ -11,23 +9,9 @@ uniform int pass; varying vec4 passColor; varying vec2 diffuseMapUV; -mat4 selectModelViewMatrix() -{ -#if @useOVR_multiview - mat4 viewOffsetMatrix = mw_viewMatrix(); - // Sky geometries aren't actually all that distant. So delete view translation to keep them looking distant. - viewOffsetMatrix[3][0] = 0; - viewOffsetMatrix[3][1] = 0; - viewOffsetMatrix[3][2] = 0; - return viewOffsetMatrix; -#else - return gl_ModelViewMatrix; -#endif -} - void main() { - gl_Position = mw_viewToClip(selectModelViewMatrix() * gl_Vertex); + gl_Position = mw_modelToClip(gl_Vertex); passColor = gl_Color; if (pass == PASS_CLOUDS) diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 07ffeee202..a426061941 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_vertex.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 8db92a2984..bf35ca78ca 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -1,7 +1,5 @@ #version 120 -#include "openmw_fragment.h.glsl" - #if @useUBO #extension GL_ARB_uniform_buffer_object : require #endif From 31a97141b7f827859eba3e14fb665de726157c73 Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Fri, 29 Apr 2022 21:59:26 +0200 Subject: [PATCH 2363/2859] Mac --- components/sceneutil/color.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/sceneutil/color.hpp b/components/sceneutil/color.hpp index cd950c2749..e4812d1149 100644 --- a/components/sceneutil/color.hpp +++ b/components/sceneutil/color.hpp @@ -200,5 +200,8 @@ namespace SceneUtil #define GL_RGBA32UI 0x8D70 #endif +#ifndef GL_RGB9_E5 +#define GL_RGB9_E5 0x8C3D +#endif #endif From 13a05dbd633a4ae31e722e9a92cc3bdcabd3320f Mon Sep 17 00:00:00 2001 From: cody glassman Date: Fri, 29 Apr 2022 17:01:28 -0700 Subject: [PATCH 2364/2859] simple water should respect filter settings --- CHANGELOG.md | 1 + apps/openmw/mwrender/water.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e52a1ea0..d0591139eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Bug #5913: Failed assertion during Ritual of Trees quest Bug #5928: Glow in the Dahrk functionality used without mod installed Bug #5937: Lights always need to be rotated by 90 degrees + Bug #5989: Simple water isn't affected by texture filter settings Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6051: NaN water height in ESM file is not handled gracefully Bug #6066: addtopic "return" does not work from within script. No errors thrown diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 7d9aca9b76..20cd789d7f 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -590,6 +590,7 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) osg::ref_ptr tex (new osg::Texture2D(mResourceSystem->getImageManager()->getImage(texname.str()))); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(tex); textures.push_back(tex); } From ed6cd487ee1773846943bbd0a76ffeb5f2471efd Mon Sep 17 00:00:00 2001 From: cody glassman Date: Fri, 29 Apr 2022 17:26:09 -0700 Subject: [PATCH 2365/2859] allow updating filtering at runtime --- apps/openmw/mwrender/renderingmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 384580adb8..b5ad5dac7b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1182,6 +1182,7 @@ namespace MWRender ); mTerrain->updateTextureFiltering(); + mWater->processChangedSettings({}); mViewer->startThreading(); } From 9a96d64611bc5b60542977010dd372c0ef6bdf6b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 30 Apr 2022 10:11:49 +0200 Subject: [PATCH 2366/2859] Use subrecord size instead of version to load WEAT --- components/esm3/loadregn.cpp | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/components/esm3/loadregn.cpp b/components/esm3/loadregn.cpp index 99f09b7fad..98dd585e33 100644 --- a/components/esm3/loadregn.cpp +++ b/components/esm3/loadregn.cpp @@ -29,26 +29,17 @@ namespace ESM case fourCC("WEAT"): { esm.getSubHeader(); - if (esm.getVer() == VER_12) + // May include the additional two bytes (but not necessarily) + if (esm.getSubSize() == sizeof(mData)) + { + esm.getT(mData); + } + else if (esm.getSubSize() == sizeof(mData) - 2) { mData.mA = 0; mData.mB = 0; esm.getExact(&mData, sizeof(mData) - 2); } - else if (esm.getVer() == VER_13) - { - // May include the additional two bytes (but not necessarily) - if (esm.getSubSize() == sizeof(mData)) - { - esm.getT(mData); - } - else - { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData)-2); - } - } else { esm.fail("Don't know what to do in this version"); From db1970059948afe958689f496325d9228c7d8139 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 30 Apr 2022 16:45:45 +0200 Subject: [PATCH 2367/2859] Make stack manipulation unconditional --- CHANGELOG.md | 1 + apps/openmw/mwscript/cellextensions.cpp | 2 + apps/openmw/mwscript/statsextensions.cpp | 1 + .../mwscript/transformationextensions.cpp | 140 ++++++++++-------- 4 files changed, 84 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e52a1ea0..120df16b2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,7 @@ Bug #6682: HitOnMe doesn't fire as intended Bug #6697: Shaders vertex lighting incorrectly clamped Bug #6711: Log time differs from real time + Bug #6717: Broken script causes interpreter stack corruption Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index d5ac222244..f00fcfd447 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -208,6 +208,7 @@ namespace MWScript void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Float level = runtime[0].mFloat; + runtime.pop(); if (!MWMechanics::getPlayer().isInCell()) { @@ -231,6 +232,7 @@ namespace MWScript void execute (Interpreter::Runtime& runtime) override { Interpreter::Type_Float level = runtime[0].mFloat; + runtime.pop(); if (!MWMechanics::getPlayer().isInCell()) { diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index a62d40065d..08f4cd6f27 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1208,6 +1208,7 @@ namespace MWScript void execute (Interpreter::Runtime& runtime) override { // dummy + runtime.pop(); runtime.push(0); } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 9f2ecf9e34..c4e78dbc96 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -193,18 +193,23 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - if (axis == "x") - { - runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[0])); - } - else if (axis == "y") - { - runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[1])); - } - else if (axis == "z") + float ret = 0.f; + if (!axis.empty()) { - runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[2])); + if (axis[0] == 'x') + { + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[0]); + } + else if (axis[0] == 'y') + { + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[1]); + } + else if (axis[0] == 'z') + { + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[2]); + } } + runtime.push(ret); } }; @@ -220,18 +225,23 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - if (axis=="x") - { - runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0])); - } - else if (axis=="y") + float ret = 0.f; + if (!axis.empty()) { - runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1])); - } - else if (axis=="z") - { - runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2])); + if (axis[0] == 'x') + { + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); + } + else if (axis[0] == 'y') + { + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); + } + else if (axis[0] == 'z') + { + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2]); + } } + runtime.push(ret); } }; @@ -247,18 +257,23 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - if(axis == "x") - { - runtime.push(ptr.getRefData().getPosition().pos[0]); - } - else if(axis == "y") + float ret = 0.f; + if (!axis.empty()) { - runtime.push(ptr.getRefData().getPosition().pos[1]); - } - else if(axis == "z") - { - runtime.push(ptr.getRefData().getPosition().pos[2]); + if (axis[0] == 'x') + { + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().pos[0]); + } + else if (axis[0] == 'y') + { + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().pos[1]); + } + else if (axis[0] == 'z') + { + ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().pos[2]); + } } + runtime.push(ret); } }; @@ -271,14 +286,14 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (!ptr.isInCell()) - return; - std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float pos = runtime[0].mFloat; runtime.pop(); + if (!ptr.isInCell()) + return; + // Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here. const auto curPos = ptr.getRefData().getPosition().asVec3(); @@ -328,18 +343,23 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - if(axis == "x") + float ret = 0.f; + if (!axis.empty()) { - runtime.push(ptr.getCellRef().getPosition().pos[0]); - } - else if(axis == "y") - { - runtime.push(ptr.getCellRef().getPosition().pos[1]); - } - else if(axis == "z") - { - runtime.push(ptr.getCellRef().getPosition().pos[2]); + if (axis[0] == 'x') + { + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().pos[0]); + } + else if (axis[0] == 'y') + { + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().pos[1]); + } + else if (axis[0] == 'z') + { + ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().pos[2]); + } } + runtime.push(ret); } }; @@ -352,15 +372,6 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (ptr.getContainerStore()) - return; - - bool isPlayer = ptr == MWMechanics::getPlayer(); - if (isPlayer) - { - MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); - } - Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; @@ -372,6 +383,15 @@ namespace MWScript std::string cellID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); + if (ptr.getContainerStore()) + return; + + bool isPlayer = ptr == MWMechanics::getPlayer(); + if (isPlayer) + { + MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); + } + MWWorld::CellStore* store = nullptr; try { @@ -424,14 +444,6 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (!ptr.isInCell()) - return; - - if (ptr == MWMechanics::getPlayer()) - { - MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); - } - Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; @@ -440,6 +452,14 @@ namespace MWScript runtime.pop(); Interpreter::Type_Float zRot = runtime[0].mFloat; runtime.pop(); + + if (!ptr.isInCell()) + return; + + if (ptr == MWMechanics::getPlayer()) + { + MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); + } int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); From 1bc24d51203bfc11145354c1cf4ec1e2a0a59d26 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 30 Apr 2022 18:32:10 +0200 Subject: [PATCH 2368/2859] Bring HitAttemptOnMe in line with HitOnMe --- apps/openmw/mwmechanics/creaturestats.cpp | 5 +++++ apps/openmw/mwmechanics/creaturestats.hpp | 1 + apps/openmw/mwscript/miscextensions.cpp | 7 ++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index b30d052d64..2faa6332ec 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -362,6 +362,11 @@ namespace MWMechanics mLastHitAttemptObject = objectid; } + void CreatureStats::clearLastHitAttemptObject() + { + mLastHitAttemptObject.clear(); + } + const std::string &CreatureStats::getLastHitAttemptObject() const { return mLastHitAttemptObject; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index d233d87cd0..8908ed838c 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -258,6 +258,7 @@ namespace MWMechanics void clearLastHitObject(); const std::string &getLastHitObject() const; void setLastHitAttemptObject(const std::string &objectid); + void clearLastHitAttemptObject(); const std::string &getLastHitAttemptObject() const; void setHitAttemptActorId(const int actorId); int getHitAttemptActorId() const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 2b557fc9b6..678e195808 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -997,9 +997,10 @@ namespace MWScript runtime.pop(); MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitAttemptObject())); - - stats.setLastHitAttemptObject(std::string()); + bool hit = ::Misc::StringUtils::ciEqual(objectID, stats.getLastHitAttemptObject()); + runtime.push(hit); + if(hit) + stats.clearLastHitAttemptObject(); } }; From 92538dde8941801ebe6034d3759443db954bd8b3 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 May 2022 15:45:22 +0200 Subject: [PATCH 2369/2859] End SNAM record as SNAM record --- components/esm3/loadregn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm3/loadregn.cpp b/components/esm3/loadregn.cpp index 98dd585e33..4f097a5e2b 100644 --- a/components/esm3/loadregn.cpp +++ b/components/esm3/loadregn.cpp @@ -100,7 +100,7 @@ namespace ESM esm.startSubRecord("SNAM"); esm.writeFixedSizeString(it->mSound, 32); esm.writeT(it->mChance); - esm.endRecord("NPCO"); + esm.endRecord("SNAM"); } } From b67a0a8f2bbef990794c941a82eab9014424a5a5 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 May 2022 14:46:47 +0200 Subject: [PATCH 2370/2859] Fix uninitialized coverity warnings --- apps/esmtool/arguments.hpp | 6 +++--- apps/esmtool/esmtool.cpp | 20 ++++---------------- components/esm3/esmwriter.hpp | 1 + components/esm3/loadnpc.cpp | 3 +++ components/esm4/loadhdpt.cpp | 16 ++++++++++++---- components/esm4/loadland.cpp | 1 + components/esm4/loadqust.cpp | 8 ++++---- components/esm4/loadtes4.cpp | 2 +- 8 files changed, 29 insertions(+), 28 deletions(-) diff --git a/apps/esmtool/arguments.hpp b/apps/esmtool/arguments.hpp index 96b4bb8f04..edd8c9b06c 100644 --- a/apps/esmtool/arguments.hpp +++ b/apps/esmtool/arguments.hpp @@ -11,9 +11,9 @@ namespace EsmTool struct Arguments { std::optional mRawFormat; - bool quiet_given; - bool loadcells_given; - bool plain_given; + bool quiet_given = false; + bool loadcells_given = false; + bool plain_given = false; std::string mode; std::string encoding; diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 3eb78f3e41..2e64168046 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -36,11 +36,7 @@ namespace bpo = boost::program_options; struct ESMData { - std::string author; - std::string description; - unsigned int version; - std::vector masters; - + ESM::Header mHeader; std::deque> mRecords; // Value: (Reference, Deleted flag) std::map > > mCellRefs; @@ -331,11 +327,7 @@ int loadTes3(const Arguments& info, std::unique_ptr&& stream, ESM esm.open(std::move(stream), info.filename); if (data != nullptr) - { - data->author = esm.getAuthor(); - data->description = esm.getDesc(); - data->masters = esm.getGameFiles(); - } + data->mHeader = esm.getHeader(); if (!quiet) { @@ -503,14 +495,10 @@ int clone(const Arguments& info) ESM::ESMWriter esm; ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); - esm.setAuthor(data.author); - esm.setDescription(data.description); - esm.setVersion(data.version); + esm.setHeader(data.mHeader); + esm.setVersion(ESM::VER_13); esm.setRecordCount (recordCount); - for (const ESM::Header::MasterData &master : data.masters) - esm.addMaster(master.name, master.size); - std::fstream save(info.outname.c_str(), std::fstream::out | std::fstream::binary); esm.save(save); diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 264dc1c877..473e948f98 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -38,6 +38,7 @@ class ESMWriter void setEncoder(ToUTF8::Utf8Encoder *encoding); void setAuthor(const std::string& author); void setDescription(const std::string& desc); + void setHeader(const Header& value) { mHeader = value; } // Set the record count for writing it in the file header void setRecordCount (int count); diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 6c0b8d0a68..dc91c5ea0c 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -158,6 +158,9 @@ namespace ESM npdt12.mDisposition = mNpdt.mDisposition; npdt12.mReputation = mNpdt.mReputation; npdt12.mRank = mNpdt.mRank; + npdt12.mUnknown1 = 0; + npdt12.mUnknown2 = 0; + npdt12.mUnknown3 = 0; npdt12.mGold = mNpdt.mGold; esm.writeHNT("NPDT", npdt12, 12); } diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index 9e5d4c9ffd..ee1d3dd526 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -27,6 +27,7 @@ #include "loadhdpt.hpp" #include +#include //#include // FIXME: testing only #include "reader.hpp" @@ -38,7 +39,7 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; - std::uint32_t type; + std::optional type; while (reader.getSubRecordHeader()) { @@ -52,7 +53,9 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) case ESM4::SUB_HNAM: reader.getFormId(mAdditionalPart); break; case ESM4::SUB_NAM0: // TES5 { - reader.get(type); + std::uint32_t value; + reader.get(value); + type = value; break; } @@ -61,8 +64,13 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) std::string file; reader.getZString(file); - // FIXME: check type >= 0 && type < 3 - mTriFile[type] = std::move(file); + if (!type.has_value()) + throw std::runtime_error("Failed to read ESM4 HDPT record: subrecord NAM0 does not precede subrecord NAM1: file type is unknown"); + + if (*type >= mTriFile.size()) + throw std::runtime_error("Failed to read ESM4 HDPT record: invalid file type: " + std::to_string(*type)); + + mTriFile[*type] = std::move(file); break; } diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 651a285a72..4cbbfd69eb 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -58,6 +58,7 @@ void ESM4::Land::load(ESM4::Reader& reader) mFormId = reader.hdr().record.id; reader.adjustFormId(mFormId); mFlags = reader.hdr().record.flags; + mDataTypes = 0; TxtLayer layer; std::int8_t currentAddQuad = -1; // for VTXT following ATXT diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp index 39eb592514..14aa51e17a 100644 --- a/components/esm4/loadqust.cpp +++ b/components/esm4/loadqust.cpp @@ -65,18 +65,20 @@ void ESM4::Quest::load(ESM4::Reader& reader) case ESM4::SUB_SCRI: reader.get(mQuestScript); break; case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? { - TargetCondition cond; - if (subHdr.dataSize == 24) // TES4 { + TargetCondition cond; reader.get(&cond, 24); cond.reference = 0; // unused in TES4 but keep it clean + mTargetConditions.push_back(cond); } else if (subHdr.dataSize == 28) { + TargetCondition cond; reader.get(cond); // FO3/FONV if (cond.reference) reader.adjustFormId(cond.reference); + mTargetConditions.push_back(cond); } else { @@ -85,8 +87,6 @@ void ESM4::Quest::load(ESM4::Reader& reader) } // FIXME: support TES5 - mTargetConditions.push_back(cond); - break; } case ESM4::SUB_SCHR: reader.get(mScript.scriptHeader); break; diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp index 42c4acde4e..3e81492825 100644 --- a/components/esm4/loadtes4.cpp +++ b/components/esm4/loadtes4.cpp @@ -67,7 +67,7 @@ void ESM4::Header::load(ESM4::Reader& reader) throw std::runtime_error("TES4 MAST data read error"); // NOTE: some mods do not have DATA following MAST so can't read DATA here - + m.size = 0; mMaster.push_back (m); break; } From af5b1b30838ea4a931ff9b515a3a34c4d8065617 Mon Sep 17 00:00:00 2001 From: Max Henzerling Date: Tue, 26 Apr 2022 18:31:27 -0700 Subject: [PATCH 2371/2859] Add option to open record editting subviews in new windows instead of exclusive docking. --- apps/opencs/model/prefs/state.cpp | 3 +++ apps/opencs/view/doc/view.cpp | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 97039df2eb..0acd8680c8 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -93,6 +93,9 @@ void CSMPrefs::State::declare() "If this option is enabled, types of affected records are selected " "manually before a command execution.\nOtherwise, all associated " "records are deleted/reverted immediately."); + declareBool ("subview-new-window", "Open Record in new window", false) + .setTooltip("When editing a record, open the view in a new window," + " rather than docked in the main view."); declareCategory ("ID Dialogues"); declareBool ("toolbar", "Show toolbar", true); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index c89437d70d..20e71d057d 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -25,6 +25,8 @@ #include "../world/subviews.hpp" #include "../world/scenesubview.hpp" #include "../world/tablesubview.hpp" +#include "../world/dialoguesubview.hpp" +#include "../world/scriptsubview.hpp" #include "../tools/subviews.hpp" @@ -609,7 +611,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin view = mSubViewFactory.makeSubView (id, *mDocument); } assert(view); - view->setParent(this); + view->setParent(this); // unfloats view view->setEditLock (mDocument->getState() & CSMDoc::State_Locked); mSubViews.append(view); // only after assert @@ -661,6 +663,21 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin this, SLOT(onRequestFocus(const std::string&))); } + if (CSMPrefs::State::get()["ID Tables"]["subview-new-window"].isTrue()) + { + CSVWorld::DialogueSubView* dialogueView = dynamic_cast(view); + if (dialogueView) + { + dialogueView->setFloating(true); + } + + CSVWorld::ScriptSubView* scriptView = dynamic_cast(view); + if (scriptView) + { + scriptView->setFloating(true); + } + } + view->show(); if (!hint.empty()) From 41be5a17f4d262f5e626a65238e0595e2abcefef Mon Sep 17 00:00:00 2001 From: Max Henzerling Date: Thu, 28 Apr 2022 10:52:02 -0700 Subject: [PATCH 2372/2859] comments from PR --- apps/opencs/view/doc/view.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 20e71d057d..1fea81ce97 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -611,7 +611,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin view = mSubViewFactory.makeSubView (id, *mDocument); } assert(view); - view->setParent(this); // unfloats view + view->setParent(this); view->setEditLock (mDocument->getState() & CSMDoc::State_Locked); mSubViews.append(view); // only after assert @@ -667,15 +667,11 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin { CSVWorld::DialogueSubView* dialogueView = dynamic_cast(view); if (dialogueView) - { dialogueView->setFloating(true); - } CSVWorld::ScriptSubView* scriptView = dynamic_cast(view); if (scriptView) - { scriptView->setFloating(true); - } } view->show(); From 2771fa1bb49fb7749a0f8594a40e87025c11d851 Mon Sep 17 00:00:00 2001 From: Max Henzerling Date: Thu, 28 Apr 2022 13:11:35 -0700 Subject: [PATCH 2373/2859] create and add issue to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b388f0c18c..29a199808d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,6 +156,7 @@ Feature #6684: Support NiFltAnimationNode Feature #6699: Ignored flag Feature #6706: Save the size of the Options window + Feature #6721: [OpenMW-CS] Add option to open records in new window Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp Task #6553: Simplify interpreter instruction registration From eedae407ab6cb031c8cdb761a994c0522962e541 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 2 May 2022 19:49:30 +0100 Subject: [PATCH 2374/2859] Tell CMake where to find ICU properly I had a problem where CMake picked up half of ICU from its C API, which is included with the Windows SDK, and half from the C++ API we provide. This should prevent that. * ICU_ROOT takes precedence as a CMake variable so do that instead of as an environment variable. * ICU_LIBRARY is an output of FindICU.cmake, not an input, so don't set it. * FindICU.cmake needs telling about components via their own variables. --- 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 bdf7d24eb8..17ff27f9f1 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1066,10 +1066,12 @@ printf "ICU ${ICU_VER/_/.}... " rm -rf ICU-${ICU_VER} eval 7z x -y icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip -o$(real_pwd)/ICU-${ICU_VER} $STRIP fi - export ICU_ROOT="$(real_pwd)/ICU-${ICU_VER}" - add_cmake_opts -DICU_INCLUDE_DIR="${ICU_ROOT}/include" \ - -DICU_LIBRARY="${ICU_ROOT}/lib${BITS}/icuuc.lib " \ - -DICU_DEBUG=ON + ICU_ROOT="$(real_pwd)/ICU-${ICU_VER}" + add_cmake_opts -DICU_ROOT="${ICU_ROOT}" \ + -DICU_INCLUDE_DIR="${ICU_ROOT}/include" \ + -DICU_I18N_LIBRARY="${ICU_ROOT}/lib${BITS}/icuin.lib " \ + -DICU_UC_LIBRARY="${ICU_ROOT}/lib${BITS}/icuuc.lib " \ + -DICU_DEBUG=ON for config in ${CONFIGURATIONS[@]}; do add_runtime_dlls $config "$(pwd)/ICU-${ICU_VER}/bin${BITS}/icudt${ICU_VER/_*/}.dll" From a6c9c9d1f8b6b664229f4d68f72c2d2134a93b02 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 2 May 2022 20:11:01 +0100 Subject: [PATCH 2375/2859] Don't return a random anonymous node from getArrowBone when the current weapon doesn't fire arrows. --- apps/openmw/mwrender/creatureanimation.cpp | 2 ++ apps/openmw/mwrender/npcanimation.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 78a524b74f..f3a92bff46 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -232,6 +232,8 @@ osg::Group *CreatureWeaponAnimation::getArrowBone() int type = weapon->get()->mBase->mData.mType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; + if (ammoType == ESM::Weapon::None) + return nullptr; // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 5b96fdb6ba..0d74b8d302 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1034,6 +1034,8 @@ osg::Group* NpcAnimation::getArrowBone() int type = weapon->get()->mBase->mData.mType; int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; + if (ammoType == ESM::Weapon::None) + return nullptr; // Try to find and attachment bone in actor's skeleton, otherwise fall back to the ArrowBone in weapon's mesh osg::Group* bone = getBoneByName(MWMechanics::getWeaponType(ammoType)->mAttachBone); From 143dcad0e8de610a7d503010da86993f923cf33c Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 2 May 2022 21:17:24 +0000 Subject: [PATCH 2376/2859] Use an `if` instead of an assert --- components/esm4/loadtes4.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp index 42c4acde4e..7123f60e38 100644 --- a/components/esm4/loadtes4.cpp +++ b/components/esm4/loadtes4.cpp @@ -53,9 +53,8 @@ void ESM4::Header::load(ESM4::Reader& reader) { if (!reader.getExact(mData.version) || !reader.getExact(mData.records) || !reader.getExact(mData.nextObjectId)) throw std::runtime_error("TES4 HEDR data read error"); - - assert((size_t)subHdr.dataSize == sizeof(mData.version)+sizeof(mData.records)+sizeof(mData.nextObjectId) - && "TES4 HEDR data size mismatch"); + if ((size_t)subHdr.dataSize != sizeof(mData.version)+sizeof(mData.records)+sizeof(mData.nextObjectId)) + throw std::runtime_error("TES4 HEDR data size mismatch"); break; } case ESM4::SUB_CNAM: reader.getZString(mAuthor); break; From 03659bef86ba60a1a30aebac9790742072875010 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 3 May 2022 17:36:49 +0000 Subject: [PATCH 2377/2859] MWUI interface (resolve https://gitlab.com/OpenMW/openmw/-/issues/6594) --- apps/openmw/mwlua/uibindings.cpp | 48 ++----- components/lua_ui/element.cpp | 24 ++-- components/lua_ui/element.hpp | 7 + components/lua_ui/flex.cpp | 17 ++- components/lua_ui/flex.hpp | 1 + components/lua_ui/widget.cpp | 14 +- components/lua_ui/widget.hpp | 2 +- docs/source/generate_luadoc.sh | 1 + docs/source/reference/lua-scripting/api.rst | 4 + .../lua-scripting/interface_mwui.rst | 6 + .../reference/lua-scripting/openmw_aux_ui.rst | 5 + files/builtin_scripts/CMakeLists.txt | 9 +- files/builtin_scripts/builtin.omwscripts | 1 + files/builtin_scripts/openmw_aux/ui.lua | 36 ++++++ .../scripts/omw/mwui/borders.lua | 120 ++++++++++++++++++ .../builtin_scripts/scripts/omw/mwui/box.lua | 45 +++++++ .../scripts/omw/mwui/constants.lua | 8 ++ .../builtin_scripts/scripts/omw/mwui/init.lua | 101 +++++++++++++++ .../builtin_scripts/scripts/omw/mwui/text.lua | 15 +++ .../scripts/omw/mwui/textEdit.lua | 47 +++++++ files/lua_api/openmw/ui.lua | 18 +-- 21 files changed, 465 insertions(+), 64 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_mwui.rst create mode 100644 docs/source/reference/lua-scripting/openmw_aux_ui.rst create mode 100644 files/builtin_scripts/openmw_aux/ui.lua create mode 100644 files/builtin_scripts/scripts/omw/mwui/borders.lua create mode 100644 files/builtin_scripts/scripts/omw/mwui/box.lua create mode 100644 files/builtin_scripts/scripts/omw/mwui/constants.lua create mode 100644 files/builtin_scripts/scripts/omw/mwui/init.lua create mode 100644 files/builtin_scripts/scripts/omw/mwui/text.lua create mode 100644 files/builtin_scripts/scripts/omw/mwui/textEdit.lua diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 84ec98adfb..5b66c8b945 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -82,38 +82,6 @@ namespace MWLua std::shared_ptr mElement; }; - class InsertLayerAction final : public LuaManager::Action - { - public: - InsertLayerAction(std::string_view name, size_t index, - LuaUi::Layer::Options options, LuaUtil::LuaState* state) - : Action(state) - , mName(name) - , mIndex(index) - , mOptions(options) - {} - - void apply(WorldView&) const override - { - LuaUi::Layer::insert(mIndex, mName, mOptions); - } - - std::string toString() const override - { - std::string result("Insert UI layer \""); - result += mName; - result += "\" at \""; - result += mIndex; - result += "\""; - return result; - } - - private: - std::string mName; - size_t mIndex; - LuaUi::Layer::Options mOptions; - }; - // Lua arrays index from 1 inline size_t fromLuaIndex(size_t i) { return i - 1; } inline size_t toLuaIndex(size_t i) { return i + 1; } @@ -252,6 +220,18 @@ namespace MWLua context.mLuaManager->addAction(std::make_unique(UiAction::CREATE, element, context.mLua)); return element; }; + api["updateAll"] = [context]() + { + LuaUi::Element::forEach([](LuaUi::Element* e) { e->mUpdate = true; }); + context.mLuaManager->addAction([]() + { + LuaUi::Element::forEach([](LuaUi::Element* e) { e->update(); }); + }, "Update all UI elements"); + }; + api["_getMenuTransparency"] = []() + { + return Settings::Manager::getFloat("menu transparency", "GUI"); + }; auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); uiLayer["name"] = sol::property([](LuaUi::Layer& self) { return self.name(); }); @@ -287,7 +267,7 @@ namespace MWLua if (index == LuaUi::Layer::count()) throw std::logic_error(std::string("Layer not found")); index++; - context.mLuaManager->addAction(std::make_unique(name, index, options, context.mLua)); + context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); }; layers["insertBefore"] = [context](std::string_view beforename, std::string_view name, const sol::object& opt) { @@ -296,7 +276,7 @@ namespace MWLua size_t index = LuaUi::Layer::indexOf(beforename); if (index == LuaUi::Layer::count()) throw std::logic_error(std::string("Layer not found")); - context.mLuaManager->addAction(std::make_unique(name, index, options, context.mLua)); + context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); }; { auto pairs = [layers](const sol::object&) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index a939ccdc51..46eb543cca 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -20,9 +20,11 @@ namespace LuaUi constexpr std::string_view external = "external"; } + const std::string defaultWidgetType = "LuaWidget"; + std::string widgetType(const sol::table& layout) { - return layout.get_or(LayoutKeys::type, std::string("LuaWidget")); + return layout.get_or(LayoutKeys::type, defaultWidgetType); } void destroyWidget(LuaUi::WidgetExtension* ext) @@ -74,11 +76,6 @@ namespace LuaUi void setTemplate(WidgetExtension* ext, const sol::object& templateLayout) { - // \todo remove when none of the widgets require this workaround - sol::object skin = LuaUtil::getFieldOrNil(templateLayout, "skin"); - if (skin.is()) - ext->widget()->changeWidgetSkin(skin.as()); - sol::object props = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::props); ext->setTemplateProperties(props); sol::object content = LuaUtil::getFieldOrNil(templateLayout, LayoutKeys::content); @@ -107,13 +104,22 @@ namespace LuaUi WidgetExtension* createWidget(const sol::table& layout) { - std::string type = widgetType(layout); - std::string name = layout.get_or(LayoutKeys::name, std::string()); - + sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type); + std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType); + sol::object templateTypeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type); + if (templateTypeField != sol::nil) + { + std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType); + if (typeField != sol::nil && templateType != type) + throw std::logic_error(std::string("Template layout type ") + type + + std::string(" doesn't match template type ") + templateType); + type = templateType; + } static auto widgetTypeMap = widgetTypeToName(); if (widgetTypeMap.find(type) == widgetTypeMap.end()) 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, diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index 13f4ea052d..1387a1b51e 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -9,6 +9,13 @@ namespace LuaUi { static std::shared_ptr make(sol::table layout); + template + static void forEach(Callback callback) + { + for(auto& [e, _] : sAllElements) + callback(e); + } + WidgetExtension* mRoot; WidgetExtension* mAttachedTo; sol::table mLayout; diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index 13acfb3990..e90fba2a1a 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -56,24 +56,25 @@ namespace LuaUi MyGUI::IntSize flexSize = calculateSize(); int growSize = 0; float growFactor = 0; - if (totalGrow > 0) + if (totalGrow > 0 && !mAutoSized) { growSize = primary(flexSize) - primary(childrenSize); growFactor = growSize / totalGrow; - } + } MyGUI::IntPoint childPosition; primary(childPosition) = alignSize(primary(flexSize) - growSize, primary(childrenSize), mAlign); - secondary(childPosition) = alignSize(secondary(flexSize), secondary(childrenSize), mArrange); for (auto* w : children()) { + MyGUI::IntSize size = w->calculateSize(); + secondary(childPosition) = alignSize(secondary(flexSize), secondary(size), mArrange); w->forcePosition(childPosition); - MyGUI::IntSize size = w->widget()->getSize(); primary(size) += static_cast(growFactor * getGrow(w)); w->forceSize(size); primary(childPosition) += primary(size); + w->updateCoord(); } - WidgetExtension::updateProperties(); + WidgetExtension::updateChildren(); } MyGUI::IntSize LuaFlex::calculateSize() @@ -85,4 +86,10 @@ namespace LuaUi } return size; } + + void LuaFlex::updateCoord() + { + updateChildren(); + WidgetExtension::updateCoord(); + } } diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp index 9ac85213f6..7c583dc3c3 100644 --- a/components/lua_ui/flex.hpp +++ b/components/lua_ui/flex.hpp @@ -18,6 +18,7 @@ namespace LuaUi { return MyGUI::IntSize(); } + void updateCoord() override; private: bool mHorizontal; diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 87b1636d74..4c1bb9752f 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -197,10 +197,13 @@ namespace LuaUi MyGUI::IntSize slotSize = mSlot->widget()->getSize(); MyGUI::IntPoint slotPosition = mSlot->widget()->getAbsolutePosition() - widget()->getAbsolutePosition(); MyGUI::IntCoord slotCoord(slotPosition, slotSize); - if (mWidget->getSubWidgetMain()) - mWidget->getSubWidgetMain()->setCoord(slotCoord); - if (mWidget->getSubWidgetText()) - mWidget->getSubWidgetText()->setCoord(slotCoord); + MyGUI::Widget* clientWidget = mWidget->getClientWidget(); + if (!clientWidget) + clientWidget = mWidget; + if (clientWidget->getSubWidgetMain()) + clientWidget->getSubWidgetMain()->setCoord(slotCoord); + if (clientWidget->getSubWidgetText()) + clientWidget->getSubWidgetText()->setCoord(slotCoord); } } @@ -250,8 +253,7 @@ namespace LuaUi if (oldCoord != newCoord) mWidget->setCoord(newCoord); - if (oldCoord.size() != newCoord.size()) - updateChildrenCoord(); + updateChildrenCoord(); if (oldCoord != newCoord && mOnCoordChange.has_value()) mOnCoordChange.value()(this, newCoord); } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 33bf52cd78..239d2e6bb3 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -52,7 +52,7 @@ namespace LuaUi void forcePosition(const MyGUI::IntPoint& pos); void clearForced(); - void updateCoord(); + virtual void updateCoord(); const sol::table& getLayout() { return mLayout; } void setLayout(const sol::table& layout) { mLayout = layout; } diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index b3ec10ba97..5376aa0ab9 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -67,3 +67,4 @@ cd $FILES_DIR/builtin_scripts $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/ai.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera.lua +$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/mwui/init.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index cf071534d9..110771fbcf 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -23,8 +23,10 @@ Lua API reference openmw_aux_calendar openmw_aux_util openmw_aux_time + openmw_aux_ui interface_ai interface_camera + interface_mwui iterables @@ -87,6 +89,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw_aux.time ` | everywhere | | Timers and game time utils | +---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw_aux.ui ` | by player scripts | | User interface utils | ++---------------------------------------------------------+--------------------+---------------------------------------------------------------+ **Interfaces of built-in scripts** diff --git a/docs/source/reference/lua-scripting/interface_mwui.rst b/docs/source/reference/lua-scripting/interface_mwui.rst new file mode 100644 index 0000000000..cd9b00cb87 --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_mwui.rst @@ -0,0 +1,6 @@ +Interface MWUI +============== + +.. raw:: html + :file: generated_html/scripts_omw_mwui_init.html + diff --git a/docs/source/reference/lua-scripting/openmw_aux_ui.rst b/docs/source/reference/lua-scripting/openmw_aux_ui.rst new file mode 100644 index 0000000000..18c0926c03 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_aux_ui.rst @@ -0,0 +1,5 @@ +Package openmw_aux.ui +======================= + +.. raw:: html + :file: generated_html/openmw_aux_ui.html diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 1732270527..a3ba1f5149 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -8,6 +8,7 @@ set(LUA_BUILTIN_FILES openmw_aux/util.lua openmw_aux/time.lua openmw_aux/calendar.lua + openmw_aux/ui.lua scripts/omw/ai.lua scripts/omw/camera.lua @@ -18,9 +19,15 @@ set(LUA_BUILTIN_FILES scripts/omw/console/local.lua l10n/Calendar/en.yaml + + scripts/omw/mwui/constants.lua + scripts/omw/mwui/borders.lua + scripts/omw/mwui/box.lua + scripts/omw/mwui/text.lua + scripts/omw/mwui/textEdit.lua + scripts/omw/mwui/init.lua ) foreach (f ${LUA_BUILTIN_FILES}) copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OPENMW_RESOURCES_ROOT}" "resources/vfs/${f}") endforeach (f) - diff --git a/files/builtin_scripts/builtin.omwscripts b/files/builtin_scripts/builtin.omwscripts index e1fbbcdb7c..af6320e0b2 100644 --- a/files/builtin_scripts/builtin.omwscripts +++ b/files/builtin_scripts/builtin.omwscripts @@ -3,3 +3,4 @@ NPC,CREATURE: scripts/omw/ai.lua PLAYER: scripts/omw/console/player.lua GLOBAL: scripts/omw/console/global.lua CUSTOM: scripts/omw/console/local.lua +PLAYER: scripts/omw/mwui/init.lua diff --git a/files/builtin_scripts/openmw_aux/ui.lua b/files/builtin_scripts/openmw_aux/ui.lua new file mode 100644 index 0000000000..ec9b81fdb1 --- /dev/null +++ b/files/builtin_scripts/openmw_aux/ui.lua @@ -0,0 +1,36 @@ +local ui = require('openmw.ui') + +--- +-- `openmw_aux.ui` defines utility functions for UI. +-- Implementation can be found in `resources/vfs/openmw_aux/ui.lua`. +-- @module ui +-- @usage local auxUi = require('openmw_aux.ui') +local aux_ui = {} + +local function deepContentCopy(content) + local result = ui.content{} + for _, v in ipairs(content) do + result:add(aux_ui.deepLayoutCopy(v)) + end + return result +end + +--- +-- @function [parent=#ui] templates +-- @param #table layout +-- @return #table copied layout +function aux_ui.deepLayoutCopy(layout) + local result = {} + for k, v in pairs(layout) do + if k == 'content' then + result[k] = deepContentCopy(v) + elseif type(v) == 'table' then + result[k] = aux_ui.deepLayoutCopy(v) + else + result[k] = v + end + end + return result +end + +return aux_ui \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/borders.lua b/files/builtin_scripts/scripts/omw/mwui/borders.lua new file mode 100644 index 0000000000..d0d3e50cdf --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/borders.lua @@ -0,0 +1,120 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') + +local constants = require('scripts.omw.mwui.constants') + +local v2 = util.vector2 + +local sideParts = { + left = util.vector2(0, 0.5), + right = util.vector2(1, 0.5), + top = util.vector2(0.5, 0), + bottom = util.vector2(0.5, 1), +} +local cornerParts = { + top_left_corner = util.vector2(0, 0), + top_right_corner = util.vector2(1, 0), + bottom_left_corner = util.vector2(0, 1), + bottom_right_corner = util.vector2(1, 1), +} + +local resources = {} +do + local boxBorderPattern = 'textures/menu_thin_border_%s.dds' + for k, _ in pairs(sideParts) do + resources[k] = ui.texture{ path = boxBorderPattern:format(k) } + end + for k, _ in pairs(cornerParts) do + resources[k] = ui.texture{ path = boxBorderPattern:format(k) } + end +end + +local borderPieces = {} +for k, align in pairs(sideParts) do + local resource = resources[k] + local horizontal = align.x ~= 0.5 + borderPieces[#borderPieces + 1] = { + type = ui.TYPE.Image, + props = { + resource = resource, + relativePosition = align, + anchor = align, + relativeSize = horizontal and v2(0, 1) or v2(1, 0), + size = (horizontal and v2(1, -2) or v2(-2, 1)) * constants.borderSize, + tileH = not horizontal, + tileV = horizontal, + }, + } +end + +for k, align in pairs(cornerParts) do + local resource = resources[k] + borderPieces[#borderPieces + 1] = { + type = ui.TYPE.Image, + props = { + resource = resource, + relativePosition = align, + anchor = align, + size = v2(1, 1) * constants.borderSize, + }, + } +end + +borderPieces[#borderPieces + 1] = { + external = { + slot = true, + }, + props = { + position = v2(1, 1) * (constants.borderSize + constants.padding), + size = v2(-2, -2) * (constants.borderSize + constants.padding), + relativeSize = v2(1, 1), + }, +} + +local borders = { + content = ui.content(borderPieces) +} +borders.content:add({ + external = { + slot = true, + }, + props = { + size = v2(-2, -2) * constants.borderSize, + }, +}) + +local horizontalLine = { + content = ui.content { + { + type = ui.TYPE.Image, + props = { + resource = resources.top, + tileH = true, + tileV = false, + size = v2(0, constants.borderSize), + relativeSize = v2(1, 0), + }, + }, + }, +} + +local verticalLine = { + content = ui.content { + { + type = ui.TYPE.Image, + props = { + resource = resources.left, + tileH = false, + tileV = true, + size = v2(constants.borderSize, 0), + relativeSize = v2(0, 1), + }, + }, + }, +} + +return function(templates) + templates.borders = borders + templates.horizontalLine = horizontalLine + templates.verticalLine = verticalLine +end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/box.lua b/files/builtin_scripts/scripts/omw/mwui/box.lua new file mode 100644 index 0000000000..e7737165ae --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/box.lua @@ -0,0 +1,45 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') + +local whiteTexture = ui.texture{ path = 'white' } + +local menuTransparency = ui._getMenuTransparency() + +return function(templates) + templates.backgroundTransparent = { + props = { + resource = whiteTexture, + color = util.color.rgb(0, 0, 0), + alpha = menuTransparency, + }, + } + templates.backgroundSolid = { + props = { + resource = whiteTexture, + color = util.color.rgb(0, 0, 0), + }, + } + templates.box = { + props = { + inheritAlpha = false, + }, + content = ui.content { + { + type = ui.TYPE.Image, + template = templates.backgroundTransparent, + props = { + relativeSize = util.vector2(1, 1), + }, + }, + { + template = templates.borders, + props = { + relativeSize = util.vector2(1, 1), + }, + external = { + slot = true, + }, + }, + }, + } +end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/constants.lua b/files/builtin_scripts/scripts/omw/mwui/constants.lua new file mode 100644 index 0000000000..ddfe5c9214 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/constants.lua @@ -0,0 +1,8 @@ +local util = require('openmw.util') + +return { + textNormalSize = 16, + sandColor = util.color.rgb(202 / 255, 165 / 255, 96 / 255), + borderSize = 4, + padding = 2, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/init.lua b/files/builtin_scripts/scripts/omw/mwui/init.lua new file mode 100644 index 0000000000..af41bece98 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/init.lua @@ -0,0 +1,101 @@ +local util = require('openmw.util') + +local function shallowLayoutCopy(source, target) + for k in pairs(target) do + target[k] = nil + end + for k, v in pairs(source) do + target[k] = v + end + return target +end + +--- +-- @type Templates +-- @usage +-- local I = require('openmw.interfaces') +-- local ui = require('openmw.ui') +-- local auxUi = require('openmw_aux.ui') +-- ui.create { +-- template = I.MWUI.templates.textNormal, +-- layer = 'Windows', +-- type = ui.TYPE.Text, +-- props = { +-- text = 'Hello, world!', +-- }, +-- } +-- -- important to copy here +-- local myText = auxUi.deepLayoutCopy(I.MWUI.templates.textNormal) +-- myText.props.textSize = 20 +-- I.MWUI.templates.textNormal = myText +-- ui.updateAll() + +local templatesMeta = { + __index = function(self, key) + return self.__templates[key] + end, + __newindex = function(self, key, template) + local target = self.__templates[key] + if target == template then + error("Overriding a template with itself") + else + shallowLayoutCopy(template, target) + end + end, +} + +--- +-- @module MWUI +-- @usage require('openmw.interfaces').MWUI +local function TemplateOverrides(templates) + return setmetatable({ + __templates = util.makeReadOnly(templates), + }, templatesMeta) +end + +--- +-- @field [parent=#MWUI] #Templates templates +local templates = {} + +--- +-- Standard rectangular border +-- @field [parent=#Templates] openmw.ui#Layout border +require('scripts.omw.mwui.borders')(templates) + +--- +-- Border combined with a transparent background +-- @field [parent=#Templates] openmw.ui#Layout box +--- +-- A transparent background +-- @field [parent=#Templates] openmw.ui#Layout backgroundTransparent +--- +-- A solid, non-transparent background +-- @field [parent=#Templates] openmw.ui#Layout backgroundSolid +require('scripts.omw.mwui.box')(templates) + +--- +-- Standard "sand" colored text +-- @field [parent=#Templates] openmw.ui#Layout textNormal +require('scripts.omw.mwui.text')(templates) + +--- +-- Single line text input +-- @field [parent=#Templates] openmw.ui#Layout textEditLine + +--- +-- Multiline text input +-- @field [parent=#Templates] openmw.ui#Layout textEditBox +require('scripts.omw.mwui.textEdit')(templates) + +--- +-- Interface version +-- @field [parent=#MWUI] #number version +local interface = { + version = 0, + templates = TemplateOverrides(templates), +} + +return { + interfaceName = "MWUI", + interface = interface, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/text.lua b/files/builtin_scripts/scripts/omw/mwui/text.lua new file mode 100644 index 0000000000..f62e4384c1 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/text.lua @@ -0,0 +1,15 @@ +local ui = require('openmw.ui') + +local constants = require('scripts.omw.mwui.constants') + +local textNormal = { + type = ui.TYPE.Text, + props = { + textSize = constants.textNormalSize, + textColor = constants.sandColor, + }, +} + +return function(templates) + templates.textNormal = textNormal +end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/textEdit.lua b/files/builtin_scripts/scripts/omw/mwui/textEdit.lua new file mode 100644 index 0000000000..1fbc619611 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/textEdit.lua @@ -0,0 +1,47 @@ +local util = require('openmw.util') +local ui = require('openmw.ui') + +local constants = require('scripts.omw.mwui.constants') + +return function(templates) + local borderContent = ui.content { + { + template = templates.borders, + props = { + relativeSize = util.vector2(1, 1), + }, + content = ui.content { + { + external = { + slot = true, + }, + }, + } + }, + } + + templates.textEditLine = { + type = ui.TYPE.TextEdit, + props = { + textSize = constants.textNormalSize, + textColor = constants.sandColor, + textAlignH = ui.ALIGNMENT.Start, + textAlignV = ui.ALIGNMENT.Center, + multiline = false, + }, + content = borderContent, + } + + templates.textEditBox = { + type = ui.TYPE.TextEdit, + props = { + textSize = constants.textNormalSize, + textColor = constants.sandColor, + textAlignH = ui.ALIGNMENT.Start, + textAlignV = ui.ALIGNMENT.Start, + multiline = true, + wordWrap = true, + }, + content = borderContent, + } +end \ No newline at end of file diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 45adbd8508..b52d9c81e4 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -9,6 +9,13 @@ -- Widget types -- @field [parent=#ui] #TYPE TYPE +--- +-- Alignment values (details depend on the specific property). For horizontal alignment the order is left to right, for vertical alignment the order is top to bottom. +-- @type ALIGNMENT +-- @field Start +-- @field Center +-- @field End + --- -- Alignment values (left to right, top to bottom) -- @field [parent=#ui] #ALIGNMENT ALIGNMENT @@ -25,14 +32,6 @@ -- @field TextEdit Accepts user text input -- @field Window Can be moved and resized by the user ---- --- Alignment values (details depend on the specific property). --- For horizontal alignment the order is left to right, for vertical alignment the order is top to bottom. --- @type ALIGNMENT --- @field Start --- @field Center --- @field End - --- -- Shows given message at the bottom of the screen. -- @function [parent=#ui] showMessage @@ -98,6 +97,9 @@ -- @field #string searchHints Additional keywords used in search, not displayed anywhere -- @field #Element element The page's UI, which will be attached to the settings tab. The root widget has to have a fixed size. Set the `size` field in `props`, `relativeSize` is ignored. +--- +-- Update all existing UI elements. Potentially extremely slow, so only call this when necessary, e. g. after overriding a template. +-- @function [parent=#ui] updateAll --- -- Layout From 2b9c7e77bda70515a4b63516a64d0c1dfeb8aa24 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 3 May 2022 21:30:21 +0100 Subject: [PATCH 2378/2859] Appease the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a199808d..8caa2fe0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,7 @@ Bug #6697: Shaders vertex lighting incorrectly clamped Bug #6711: Log time differs from real time Bug #6717: Broken script causes interpreter stack corruption + Bug #6718: Throwable weapons cause arrow enchantment effect to be applied to the whole body Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" From 2f10ccd18f2907d8164f1c3f4b0b9890e06cbf1e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 3 May 2022 22:51:34 +0000 Subject: [PATCH 2379/2859] Disable Chocolatey community repo This should work around https://github.com/chocolatey/choco/issues/1541, which causes priority to be ignored and our proxy cache to be skipped --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 696e8113c5..89b6b81179 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -350,6 +350,7 @@ variables: &tests-targets before_script: - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 + - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - choco install ccache -y @@ -487,6 +488,7 @@ Windows_Ninja_Tests_RelWithDebInfo: before_script: - Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" - choco source add -n=openmw-proxy -s="https://repo.openmw.org/repository/Chocolatey/" --priority=1 + - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - choco install ccache -y From 55d32432b935b3410bed782f70c96cd65ffb9405 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 4 May 2022 21:15:08 +0200 Subject: [PATCH 2380/2859] Don't mark idle animations as bad when blocking them --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8caa2fe0c4..fd762a82ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ Bug #6711: Log time differs from real time Bug #6717: Broken script causes interpreter stack corruption Bug #6718: Throwable weapons cause arrow enchantment effect to be applied to the whole body + Bug #6730: LoopGroup stalls animation after playing :Stop frame until another animation is played Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b2eb316e24..48c7e5125f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2457,7 +2457,7 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int // We should not interrupt persistent animations by non-persistent ones if (isPersistentAnimPlaying() && !persist) - return false; + return true; // If this animation is a looped animation (has a "loop start" key) that is already playing // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count From a64979e25d494332523133a33b42b89917419c5b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 4 May 2022 22:33:39 +0200 Subject: [PATCH 2381/2859] Replace empty std::string assignments --- apps/opencs/model/doc/savingstages.cpp | 4 ++-- apps/opencs/model/world/nestedcoladapterimp.hpp | 2 +- apps/opencs/model/world/refidadapterimp.hpp | 10 +++++----- apps/opencs/view/render/scenewidget.cpp | 2 +- apps/opencs/view/widget/scenetooltexturebrush.cpp | 2 +- apps/openmw/engine.cpp | 8 ++------ apps/openmw/mwclass/armor.cpp | 6 ++++-- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 2 +- apps/openmw/mwgui/charactercreation.cpp | 2 +- apps/openmw/mwgui/itemwidget.cpp | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 4 ++-- apps/openmw/mwgui/tooltips.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.cpp | 4 ++-- apps/openmw/mwmechanics/character.cpp | 10 +++++----- apps/openmw/mwscript/statsextensions.cpp | 14 +++++++------- apps/openmw/mwworld/weather.cpp | 2 +- components/esm3/inventorystate.cpp | 2 +- components/esm4/loadcell.cpp | 8 ++++---- components/esm4/loadnavi.cpp | 6 +++--- components/esm4/loadnavm.cpp | 6 +++--- components/esm4/loadregn.cpp | 2 +- components/esm4/loadstat.cpp | 2 +- components/esm4/loadtes4.cpp | 2 +- components/esm4/reader.cpp | 6 +++--- components/resource/stats.cpp | 2 +- components/settings/settings.cpp | 4 ++-- components/shader/shadermanager.cpp | 4 ++-- 30 files changed, 63 insertions(+), 65 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index df3d0cb980..bba5aea92f 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -145,7 +145,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message ESM::DialInfo info = (*iter)->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); - info.mPrev = ""; + info.mPrev.clear(); if (iter!=range.first) { CSMWorld::InfoCollection::RecordConstIterator prev = iter; @@ -157,7 +157,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message CSMWorld::InfoCollection::RecordConstIterator next = iter; ++next; - info.mNext = ""; + info.mNext.clear(); if (next!=range.second) { info.mNext = (*next)->get().mId.substr ((*next)->get().mId.find_last_of ('#')+1); diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index a5daefc3cd..b1785cc919 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -163,7 +163,7 @@ namespace CSMWorld std::vector& spells = raceOrBthSgn.mPowers.mList; // blank row - std::string spell = ""; + std::string spell; spells.insert(spells.begin()+position, spell); diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 41de82a0ef..0cfde25569 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1527,7 +1527,7 @@ namespace CSMWorld ESM::Transport::Dest newRow; newRow.mPos = newPos; - newRow.mCellName = ""; + newRow.mCellName.clear(); if (position >= (int)list.size()) list.push_back(newRow); @@ -1679,7 +1679,7 @@ namespace CSMWorld for (int i = 0; i < 8; ++i) newRow.mWander.mIdle[i] = 0; newRow.mWander.mShouldRepeat = 1; - newRow.mCellName = ""; + newRow.mCellName.clear(); if (position >= (int)list.size()) list.push_back(newRow); @@ -2013,8 +2013,8 @@ namespace CSMWorld ESM::PartReference newPart; newPart.mPart = 0; // 0 == head - newPart.mMale = ""; - newPart.mFemale = ""; + newPart.mMale.clear(); + newPart.mFemale.clear(); if (position >= (int)list.size()) list.push_back(newPart); @@ -2362,7 +2362,7 @@ namespace CSMWorld std::vector& list = leveled.mList; ESM::LevelledListBase::LevelItem newItem; - newItem.mId = ""; + newItem.mId.clear(); newItem.mLevel = 0; if (position >= (int)list.size()) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index b0c2140da9..2cc3b3a582 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -44,7 +44,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) //ds->setNumMultiSamples(8); osg::ref_ptr traits = new osg::GraphicsContext::Traits; - traits->windowName = ""; + traits->windowName.clear(); traits->windowDecoration = true; traits->x = 0; traits->y = 0; diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index 80eca21785..f55bda0464 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -185,7 +185,7 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture) mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(rowInNew, landTextureFilename).value()); } else { - newBrushTextureId = ""; + newBrushTextureId.clear(); mBrushTextureLabel = "No selected texture or invalid texture"; mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel)); } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 91250ca0fb..650f6511f9 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -770,19 +770,15 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt"; std::string userGameControllerdb; - if (boost::filesystem::exists(userdefault)){ + if (boost::filesystem::exists(userdefault)) userGameControllerdb = userdefault; - } - else - userGameControllerdb = ""; std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; else if (boost::filesystem::exists(globaldefault)) gameControllerdb = globaldefault; - else - gameControllerdb = ""; //if it doesn't exist, pass in an empty string + //else if it doesn't exist, pass in an empty string // gui needs our shaders path before everything else mResourceSystem->getSceneManager()->setShaderPath((mResDir / "shaders").string()); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 22c46bf579..da2420d94a 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -205,7 +205,9 @@ namespace MWClass // get armor type string (light/medium/heavy) std::string typeText; if (ref->mBase->mData.mWeight == 0) - typeText = ""; + { + // no type + } else { int armorType = getEquipmentSkill(ptr); @@ -255,7 +257,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Armor newItem = *ref->mBase; - newItem.mId=""; + newItem.mId.clear(); newItem.mName=newName; newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index c8a05a132e..86a0c56b9b 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -140,7 +140,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Book newItem = *ref->mBase; - newItem.mId=""; + newItem.mId.clear(); newItem.mName=newName; newItem.mData.mIsScroll = 1; newItem.mData.mEnchant=enchCharge; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 32396cae35..79c502d673 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -189,7 +189,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Clothing newItem = *ref->mBase; - newItem.mId=""; + newItem.mId.clear(); newItem.mName=newName; newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 9290c19d80..9fbc7a8da9 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -266,7 +266,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Weapon newItem = *ref->mBase; - newItem.mId=""; + newItem.mId.clear(); newItem.mName=newName; newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index feb3c22035..9d8516ae79 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -135,7 +135,7 @@ namespace MWDialogue if (actor.getClass().getCreatureStats(actor).isDead()) return false; - mLastTopic = ""; + mLastTopic.clear(); // Note that we intentionally don't reset mPermanentDispositionChange mChoice = -1; diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 43da1ef83f..19ba432d58 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -249,7 +249,7 @@ namespace MWGui break; case GM_ClassGenerate: mGenerateClassStep = 0; - mGenerateClass = ""; + mGenerateClass.clear(); mGenerateClassSpecializations[0] = 0; mGenerateClassSpecializations[1] = 0; mGenerateClassSpecializations[2] = 0; diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index d2dfa827b4..f914ee8556 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -146,7 +146,7 @@ namespace MWGui if (state == None) { if (!isMagic) - backgroundTex = ""; + backgroundTex.clear(); } else if (state == Equip) { diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 99876e0c85..7f53916d65 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -142,8 +142,8 @@ namespace MWGui else { key->type = Type_Unassigned; - key->id = ""; - key->name = ""; + key->id.clear(); + key->name.clear(); MyGUI::TextBox* textBox = key->button->createWidgetReal("SandText", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index f806d3829a..10ed206e3f 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -122,7 +122,7 @@ namespace MWGui info.caption = mFocusObject.getClass().getName(mFocusObject); if (info.caption.empty()) info.caption=mFocusObject.getCellRef().getRefId(); - info.icon=""; + info.icon.clear(); tooltipSize = createToolTip(info, checkOwned()); } else @@ -371,7 +371,7 @@ namespace MWGui ToolTipInfo info = object.getToolTipInfo(mFocusObject, count); if (!image) - info.icon = ""; + info.icon.clear(); tooltipSize = createToolTip(info, isOwned); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 6f053643d6..e3d61e7c08 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1313,7 +1313,7 @@ namespace MWGui void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item) { mSelectedEnchantItem = item; - mSelectedSpell = ""; + mSelectedSpell.clear(); const ESM::Enchantment* ench = mStore->get() .find(item.getClass().getEnchantment(item)); @@ -1346,7 +1346,7 @@ namespace MWGui void WindowManager::unsetSelectedSpell() { - mSelectedSpell = ""; + mSelectedSpell.clear(); mSelectedEnchantItem = MWWorld::Ptr(); mHud->unsetSelectedSpell(); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b2eb316e24..f626419707 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -759,19 +759,19 @@ void CharacterController::playDeath(float startpoint, CharacterState death) // However, they could still trigger text keys, such as Hit events, or sounds. mMovementState = CharState_None; mAnimation->disable(mCurrentMovement); - mCurrentMovement = ""; + mCurrentMovement.clear(); mUpperBodyState = UpperCharState_Nothing; mAnimation->disable(mCurrentWeapon); - mCurrentWeapon = ""; + mCurrentWeapon.clear(); mHitState = CharState_None; mAnimation->disable(mCurrentHit); - mCurrentHit = ""; + mCurrentHit.clear(); mIdleState = CharState_None; mAnimation->disable(mCurrentIdle); - mCurrentIdle = ""; + mCurrentIdle.clear(); mJumpState = JumpState_None; mAnimation->disable(mCurrentJump); - mCurrentJump = ""; + mCurrentJump.clear(); mMovementAnimationControlled = true; mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 08f4cd6f27..9a9a315bdb 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -553,7 +553,7 @@ namespace MWScript { MWWorld::ConstPtr actor = R()(runtime, false); - std::string factionID = ""; + std::string factionID; if(arg0==0) { @@ -585,7 +585,7 @@ namespace MWScript { MWWorld::ConstPtr actor = R()(runtime, false); - std::string factionID = ""; + std::string factionID; if(arg0==0) { @@ -624,7 +624,7 @@ namespace MWScript { MWWorld::ConstPtr actor = R()(runtime, false); - std::string factionID = ""; + std::string factionID; if(arg0==0) { @@ -656,7 +656,7 @@ namespace MWScript { MWWorld::ConstPtr ptr = R()(runtime, false); - std::string factionID = ""; + std::string factionID; if(arg0 >0) { factionID = runtime.getStringLiteral (runtime[0].mInteger); @@ -922,7 +922,7 @@ namespace MWScript { MWWorld::ConstPtr ptr = R()(runtime, false); - std::string factionID = ""; + std::string factionID; if(arg0 >0 ) { factionID = runtime.getStringLiteral (runtime[0].mInteger); @@ -954,7 +954,7 @@ namespace MWScript { MWWorld::ConstPtr ptr = R()(runtime, false); - std::string factionID = ""; + std::string factionID; if(arg0 >0 ) { factionID = runtime.getStringLiteral (runtime[0].mInteger); @@ -981,7 +981,7 @@ namespace MWScript { MWWorld::ConstPtr ptr = R()(runtime, false); - std::string factionID = ""; + std::string factionID; if(arg0 >0 ) { factionID = runtime.getStringLiteral (runtime[0].mInteger); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index e2b75edc40..16a0bef131 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -945,7 +945,7 @@ namespace MWWorld { stopSounds(); - mCurrentRegion = ""; + mCurrentRegion.clear(); mTimePassed = 0.0f; mWeatherUpdateTime = 0.0f; forceWeather(0); diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index b43a7b7233..7dc971d205 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -59,7 +59,7 @@ void InventoryState::load (ESMReader &esm) //Get its name std::string id = esm.getHString(); int count; - std::string parentGroup = ""; + std::string parentGroup; //Then get its count esm.getHNT (count, "COUN"); //Old save formats don't have information about parent group; check for that diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index 1bd7cb783a..6da827499d 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -85,7 +85,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) if (!reader.getZString(mEditorId)) throw std::runtime_error ("CELL EDID data read error"); #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "CELL Editor ID: " << mEditorId << std::endl; #endif @@ -108,7 +108,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) reader.get(mX); reader.get(mY); #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "CELL group " << ESM4::printLabel(reader.grp().label, reader.grp().type) << std::endl; std::cout << padding << "CELL formId " << std::hex << reader.hdr().record.id << std::endl; @@ -143,7 +143,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) reader.get((std::uint8_t&)mCellFlags); // 8 bits in Obvlivion } #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "flags: " << std::hex << mCellFlags << std::endl; #endif @@ -156,7 +156,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) { reader.getFormId(*it); #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "region: " << std::hex << *it << std::endl; #endif diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp index 4ed72c20f1..ae8d433f28 100644 --- a/components/esm4/loadnavi.cpp +++ b/components/esm4/loadnavi.cpp @@ -68,7 +68,7 @@ void ESM4::Navigation::IslandInfo::load(ESM4::Reader& reader) reader.get(*it); // FIXME: debugging only #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "NVMI vert " << std::dec << (*it).x << ", " << (*it).y << ", " << (*it).z << std::endl; #endif @@ -88,7 +88,7 @@ void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader) // FIXME: for debugging only #if 0 - std::string padding = ""; + std::string padding; if (flags == ESM4::FLG_Modified) padding.insert(0, 2, '-'); else if (flags == ESM4::FLG_Unmodified) @@ -157,7 +157,7 @@ void ESM4::Navigation::NavMeshInfo::load(ESM4::Reader& reader) reader.get(cellGrid.grid.x); // FIXME: debugging only #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); if (worldSpaceId == ESM4::FLG_Morrowind) std::cout << padding << "NVMI MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp index 5fce00893c..5a90f67b01 100644 --- a/components/esm4/loadnavm.cpp +++ b/components/esm4/loadnavm.cpp @@ -69,7 +69,7 @@ void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader) reader.get(cellGrid.grid.x); // FIXME: debugging only #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); if (worldSpaceId == ESM4::FLG_Morrowind) std::cout << padding << "NVNM MW: X " << std::dec << cellGrid.grid.x << ", Y " << cellGrid.grid.y << std::endl; @@ -82,7 +82,7 @@ void ESM4::NavMesh::NVNMstruct::load(ESM4::Reader& reader) reader.get(cellGrid.cellId); #if 0 - std::string padding = ""; // FIXME + std::string padding; // FIXME padding.insert(0, reader.stackSize()*2, ' '); if (worldSpaceId == 0) // interior std::cout << padding << "NVNM Interior: cellId " << std::hex << cellGrid.cellId << std::endl; @@ -194,7 +194,7 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) // FIXME: debugging only #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "NAVM flags 0x" << std::hex << reader.hdr().record.flags << std::endl; std::cout << padding << "NAVM id 0x" << std::hex << reader.hdr().record.id << std::endl; diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp index 2455966036..b57ce81aa0 100644 --- a/components/esm4/loadregn.cpp +++ b/components/esm4/loadregn.cpp @@ -62,7 +62,7 @@ void ESM4::Region::load(ESM4::Reader& reader) { reader.get(*it); #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "RPLD: 0x" << std::hex << *it << std::endl; #endif diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp index 10ce91980c..d57294cc40 100644 --- a/components/esm4/loadstat.cpp +++ b/components/esm4/loadstat.cpp @@ -58,7 +58,7 @@ void ESM4::Static::load(ESM4::Reader& reader) { reader.get(*it); #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "MODT: " << std::hex << *it << std::endl; #endif diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp index 7123f60e38..7814b0f4ad 100644 --- a/components/esm4/loadtes4.cpp +++ b/components/esm4/loadtes4.cpp @@ -85,7 +85,7 @@ void ESM4::Header::load(ESM4::Reader& reader) if (!reader.getExact(mOverride)) throw std::runtime_error("TES4 ONAM data read error"); #if 0 - std::string padding = ""; + std::string padding; padding.insert(0, reader.stackSize()*2, ' '); std::cout << padding << "ESM4::Header::ONAM overrides: " << formIdToString(mOverride) << std::endl; #endif diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index ce7ede2bf4..9ac8da8802 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -423,7 +423,7 @@ void Reader::skipSubRecordData(std::uint32_t size) void Reader::enterGroup() { #ifdef DEBUG_GROUPSTACK - std::string padding = ""; // FIXME: debugging only + std::string padding; // FIXME: debugging only padding.insert(0, mCtx.groupStack.size()*2, ' '); std::cout << padding << "Starting record group " << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << std::endl; @@ -473,7 +473,7 @@ void Reader::exitGroupCheck() mCtx.groupStack.pop_back(); #ifdef DEBUG_GROUPSTACK - std::string padding = ""; // FIXME: debugging only + std::string padding; // FIXME: debugging only padding.insert(0, mCtx.groupStack.size()*2, ' '); std::cout << padding << "Finished record group " << printLabel(grp.label, grp.type) << std::endl; #endif @@ -521,7 +521,7 @@ void Reader::skipGroupData() void Reader::skipGroup() { #ifdef DEBUG_GROUPSTACK - std::string padding = ""; // FIXME: debugging only + std::string padding; // FIXME: debugging only padding.insert(0, mCtx.groupStack.size()*2, ' '); std::cout << padding << "Skipping record group " << printLabel(mCtx.recordHeader.group.label, mCtx.recordHeader.group.type) << std::endl; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index d97ddd1d6f..d8578a67cc 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -106,7 +106,7 @@ Profiler::Profiler(bool offlineCollect): if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); else - _font = ""; + _font.clear(); _characterSize = 18; diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index ecd25dbd4e..e9ad6a1bf5 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -28,8 +28,8 @@ std::string Manager::load(const Files::ConfigurationManager& cfgMgr, bool loadEd throw std::runtime_error("No config dirs! ConfigurationManager::readConfiguration must be called first."); // Create file name strings for either the engine or the editor. - std::string defaultSettingsFile = ""; - std::string userSettingsFile = ""; + std::string defaultSettingsFile; + std::string userSettingsFile; if (!loadEditorSettings) { diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 1341baad60..b15825126a 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -193,7 +193,7 @@ namespace Shader } lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + overallEnd, '\n'); - std::string replacement = ""; + std::string replacement; for (std::vector::const_iterator element = listElements.cbegin(); element != listElements.cend(); element++) { std::string contentInstance = content; @@ -247,7 +247,7 @@ namespace Shader condition = !condition; if (!condition) - linkTarget = ""; + linkTarget.clear(); } source.replace(foundPos, lineEnd - foundPos, ""); From 2bbd0ba97602be1421265a392a51f3c226e47e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Thu, 5 May 2022 00:57:15 +0300 Subject: [PATCH 2382/2859] #5336: Refactor World::updatePlayer in to Player::update --- apps/openmw/mwworld/player.cpp | 56 ++++++++++++++++++++++++++++++++ apps/openmw/mwworld/player.hpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 50 +--------------------------- apps/openmw/mwworld/worldimp.hpp | 1 - 4 files changed, 59 insertions(+), 50 deletions(-) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 45e87a7942..64acf1c10f 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -23,6 +24,9 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellutil.hpp" +#include "../mwrender/renderingmanager.hpp" +#include "../mwrender/camera.hpp" + #include "cellstore.hpp" #include "class.hpp" #include "ptr.hpp" @@ -514,4 +518,56 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, castChance); MWBase::Environment::get().getWindowManager()->updateSpellWindow(); } + + void Player::update() + { + auto player = getPlayer(); + auto* world = MWBase::Environment::get().getWorld(); + auto* rendering = world->getRenderingManager(); + auto& store = world->getStore(); + auto& playerClass = player.getClass(); + auto* windowMgr = MWBase::Environment::get().getWindowManager(); + + if (player.getCell()->isExterior()) + { + ESM::Position pos = player.getRefData().getPosition(); + setLastKnownExteriorPosition(pos.asVec3()); + } + + bool isWerewolf = playerClass.getNpcStats(player).isWerewolf(); + bool isFirstPerson = world->isFirstPerson(); + if (isWerewolf && isFirstPerson) + { + float werewolfFov = Fallback::Map::getFloat("General_Werewolf_FOV"); + if (werewolfFov != 0) + rendering->overrideFieldOfView(werewolfFov); + windowMgr->setWerewolfOverlay(true); + } + else + { + rendering->resetFieldOfView(); + windowMgr->setWerewolfOverlay(false); + } + + // Sink the camera while sneaking + bool sneaking = playerClass.getCreatureStats(player).getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool swimming = world->isSwimming(player); + bool flying = world->isFlying(player); + + static const float i1stPersonSneakDelta = store.get().find("i1stPersonSneakDelta")->mValue.getFloat(); + if (sneaking && !swimming && !flying) + rendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); + else + rendering->getCamera()->setSneakOffset(0.f); + + int blind = 0; + const auto& magicEffects = playerClass.getCreatureStats(player).getMagicEffects(); + if (!world->getGodModeState()) + blind = static_cast(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude()); + windowMgr->setBlindness(std::clamp(blind, 0, 100)); + + int nightEye = static_cast(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude()); + rendering->setNightEyeFactor(std::min(1.f, (nightEye / 100.f))); + } + } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 4add58541e..3a3c58cb5d 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -135,6 +135,8 @@ namespace MWWorld void erasePreviousItem(const std::string& boundItemId); void setSelectedSpell(const std::string& spellId); + + void update(); }; } #endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f925ebec82..ff343e0828 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1845,7 +1845,7 @@ namespace MWWorld updateNavigator(); } - updatePlayer(); + mPlayer->update(); mPhysics->debugDraw(); @@ -1876,54 +1876,6 @@ namespace MWWorld } } - void World::updatePlayer() - { - MWWorld::Ptr player = getPlayerPtr(); - - // TODO: move to MWWorld::Player - - if (player.getCell()->isExterior()) - { - ESM::Position pos = player.getRefData().getPosition(); - mPlayer->setLastKnownExteriorPosition(pos.asVec3()); - } - - bool isWerewolf = player.getClass().getNpcStats(player).isWerewolf(); - bool isFirstPerson = this->isFirstPerson(); - if (isWerewolf && isFirstPerson) - { - float werewolfFov = Fallback::Map::getFloat("General_Werewolf_FOV"); - if (werewolfFov != 0) - mRendering->overrideFieldOfView(werewolfFov); - MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(true); - } - else - { - mRendering->resetFieldOfView(); - MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(false); - } - - // Sink the camera while sneaking - bool sneaking = player.getClass().getCreatureStats(getPlayerPtr()).getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool swimming = isSwimming(player); - bool flying = isFlying(player); - - static const float i1stPersonSneakDelta = mStore.get().find("i1stPersonSneakDelta")->mValue.getFloat(); - if (sneaking && !swimming && !flying) - mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); - else - mRendering->getCamera()->setSneakOffset(0.f); - - int blind = 0; - const auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects(); - if (!mGodMode) - blind = static_cast(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude()); - MWBase::Environment::get().getWindowManager()->setBlindness(std::clamp(blind, 0, 100)); - - int nightEye = static_cast(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude()); - mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f))); - } - void World::preloadSpells() { std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 2dc1f3c81a..fb1b80daf3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -141,7 +141,6 @@ namespace MWWorld Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); - void updatePlayer(); void preloadSpells(); From bc5e43ab600036afe820f9fad30acd5807120f89 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 5 May 2022 21:53:20 +0200 Subject: [PATCH 2383/2859] Fix copy paste error --- apps/openmw/mwscript/transformationextensions.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index c4e78dbc96..d87888c282 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -262,15 +262,15 @@ namespace MWScript { if (axis[0] == 'x') { - ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().pos[0]); + ret = ptr.getRefData().getPosition().pos[0]; } else if (axis[0] == 'y') { - ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().pos[1]); + ret = ptr.getRefData().getPosition().pos[1]; } else if (axis[0] == 'z') { - ret = osg::RadiansToDegrees(ptr.getRefData().getPosition().pos[2]); + ret = ptr.getRefData().getPosition().pos[2]; } } runtime.push(ret); @@ -348,15 +348,15 @@ namespace MWScript { if (axis[0] == 'x') { - ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().pos[0]); + ret = ptr.getCellRef().getPosition().pos[0]; } else if (axis[0] == 'y') { - ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().pos[1]); + ret = ptr.getCellRef().getPosition().pos[1]; } else if (axis[0] == 'z') { - ret = osg::RadiansToDegrees(ptr.getCellRef().getPosition().pos[2]); + ret = ptr.getCellRef().getPosition().pos[2]; } } runtime.push(ret); From 06225c69684446d20b3f42603517f9fee6cd3f0b Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 19:41:29 +0000 Subject: [PATCH 2384/2859] Fix collecting cobertura reports --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 89b6b81179..df4a9cc719 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -201,7 +201,9 @@ Ubuntu_GCC_tests_coverage: name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} when: always reports: - cobertura: coverage.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml junit: build/tests.xml Ubuntu_Static_Deps: From 86d6ab593e2f2f85802cd5e08d5f07189672abb6 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 6 May 2022 19:57:41 +0000 Subject: [PATCH 2385/2859] Fix #6731 --- components/l10n/messagebundles.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index d02735313e..fab59a7cb8 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -151,7 +151,8 @@ namespace l10n defaultLocale = mPreferredLocales[0]; } UParseError parseError; - icu::MessageFormat defaultMessage(icu::UnicodeString::fromUTF8(key), defaultLocale, parseError, success); + icu::MessageFormat defaultMessage(icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), 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); From 70c7f1880d3dd59223aaf6daa41ee00b067eb5b5 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 6 May 2022 23:11:47 +0200 Subject: [PATCH 2386/2859] [Lua] pairs and ipairs for ObjectList (resolves #6732) --- apps/openmw/mwlua/objectbindings.cpp | 26 ++------------------------ components/lua/luastate.cpp | 9 +++++++++ 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 0bf3fc99c5..3cab81c38d 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -131,30 +131,8 @@ namespace MWLua else throw std::runtime_error("Index out of range"); }; - if constexpr (std::is_same_v) - { - // GObject and LObject iterators are in separate branches because if they share source code - // there is a collision in sol and only one iterator can be mapped to Lua. - auto iter = sol::make_object(lua, [registry](const GObjectList& l, int64_t i) -> sol::optional> - { - if (i >= 0 && i < static_cast(l.mIds->size())) - return std::make_tuple(i + 1, GObject((*l.mIds)[i], registry)); - else - return sol::nullopt; - }); - listT[sol::meta_function::ipairs] = [iter](const GObjectList& list) { return std::make_tuple(iter, list, 0); }; - } - else - { - auto iter = sol::make_object(lua, [registry](const LObjectList& l, int64_t i) -> sol::optional> - { - if (i >= 0 && i < static_cast(l.mIds->size())) - return std::make_tuple(i + 1, LObject((*l.mIds)[i], registry)); - else - return sol::nullopt; - }); - listT[sol::meta_function::ipairs] = [iter](const LObjectList& list) { return std::make_tuple(iter, list, 0); }; - } + listT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); } template diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 38f3ae889e..ac32f12992 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -92,6 +92,15 @@ namespace LuaUtil local nextFn, t, firstKey = ipairs(getmetatable(v).__index) return function(_, k) return nextFn(t, k) end, v, firstKey end + local function nextForArray(array, index) + index = (index or 0) + 1 + if index < #array then + return index, array[index] + end + end + function ipairsForArray(array) + return nextForArray, array, 0 + end getmetatable('').__metatable = false getSafeMetatable = function(v) From 79676aee15bdab383d42ae8d86598312c398bb44 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 17:46:50 +0200 Subject: [PATCH 2387/2859] Make Environment a storage of referencing pointers instead of owned Engine controls lifetime of managers therefore it should own them. Environment is only access provider. This allows to avoid redundant virtual calls and also some functions from managers base classes can be removed if they are used only by Engine. --- apps/openmw/engine.cpp | 140 ++++++++++-------- apps/openmw/engine.hpp | 57 +++++++- apps/openmw/mwbase/environment.cpp | 168 ++-------------------- apps/openmw/mwbase/environment.hpp | 101 ++++++------- apps/openmw/mwinput/actionmanager.cpp | 4 +- apps/openmw/mwlua/uibindings.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 10 +- apps/openmw/mwmechanics/character.cpp | 6 +- apps/openmw/mwphysics/physicssystem.cpp | 2 +- apps/openmw/mwrender/landmanager.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwworld/player.cpp | 6 +- components/misc/notnullptr.hpp | 33 +++++ 13 files changed, 246 insertions(+), 287 deletions(-) create mode 100644 components/misc/notnullptr.hpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 650f6511f9..a99bd72831 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -271,7 +271,7 @@ namespace void OMW::Engine::executeLocalScripts() { - MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts(); + MWWorld::LocalScripts& localScripts = mWorld->getLocalScripts(); localScripts.startIteration(); std::pair script; @@ -279,7 +279,7 @@ void OMW::Engine::executeLocalScripts() { MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); - mEnvironment.getScriptManager()->run (script.first, interpreterContext); + mScriptManager->run (script.first, interpreterContext); } } @@ -297,7 +297,7 @@ bool OMW::Engine::frame(float frametime) // update input { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mEnvironment.getInputManager()->update(frametime, false); + mInputManager->update(frametime, false); } // When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug. @@ -306,21 +306,21 @@ bool OMW::Engine::frame(float frametime) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (!mEnvironment.getWindowManager()->isWindowVisible()) + if (!mWindowManager->isWindowVisible()) { - mEnvironment.getSoundManager()->pausePlayback(); + mSoundManager->pausePlayback(); return false; } else - mEnvironment.getSoundManager()->resumePlayback(); + mSoundManager->resumePlayback(); // sound if (mUseSound) - mEnvironment.getSoundManager()->update(frametime); + mSoundManager->update(frametime); } // Main menu opened? Then scripts are also paused. - bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu); + bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu); // Should be called after input manager update and before any change to the game world. // It applies to the game world queued changes from the previous frame. @@ -329,35 +329,35 @@ bool OMW::Engine::frame(float frametime) // update game state { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mEnvironment.getStateManager()->update (frametime); + mStateManager->update (frametime); } - bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); + bool guiActive = mWindowManager->isGuiMode(); { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { if (!paused) { - if (mEnvironment.getWorld()->getScriptsEnabled()) + if (mWorld->getScriptsEnabled()) { // local scripts executeLocalScripts(); // global scripts - mEnvironment.getScriptManager()->getGlobalScripts().run(); + mScriptManager->getGlobalScripts().run(); } - mEnvironment.getWorld()->markCellAsUnchanged(); + mWorld->markCellAsUnchanged(); } if (!guiActive) { - double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0; - mEnvironment.getWorld()->advanceTime(hours, true); - mEnvironment.getWorld()->rechargeItems(frametime, true); + double hours = (frametime * mWorld->getTimeScaleFactor()) / 3600.0; + mWorld->advanceTime(hours, true); + mWorld->rechargeItems(frametime, true); } } } @@ -366,16 +366,16 @@ bool OMW::Engine::frame(float frametime) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mEnvironment.getMechanicsManager()->update(frametime, guiActive); + mMechanicsManager->update(frametime, guiActive); } - if (mEnvironment.getStateManager()->getState() == MWBase::StateManager::State_Running) + if (mStateManager->getState() == MWBase::StateManager::State_Running) { - MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); + MWWorld::Ptr player = mWorld->getPlayerPtr(); if(!guiActive && player.getClass().getCreatureStats(player).isDead()) - mEnvironment.getStateManager()->endGame(); + mStateManager->endGame(); } } @@ -383,9 +383,9 @@ bool OMW::Engine::frame(float frametime) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mEnvironment.getWorld()->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); + mWorld->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); } } @@ -393,16 +393,16 @@ bool OMW::Engine::frame(float frametime) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mEnvironment.getWorld()->update(frametime, guiActive); + mWorld->update(frametime, guiActive); } } // update GUI { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mEnvironment.getWindowManager()->update(frametime); + mWindowManager->update(frametime); } if (stats->collectStats("resource")) @@ -443,7 +443,6 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mExportFonts(false) , mRandomSeed(0) , mScriptContext (nullptr) - , mLuaManager (nullptr) , mFSStrict (false) , mScriptBlacklistUse (true) , mNewGame (false) @@ -471,6 +470,17 @@ OMW::Engine::~Engine() mEnvironment.cleanup(); + mMechanicsManager = nullptr; + mDialogueManager = nullptr; + mJournal = nullptr; + mScriptManager = nullptr; + mWindowManager = nullptr; + mWorld = nullptr; + mSoundManager = nullptr; + mInputManager = nullptr; + mStateManager = nullptr; + mLuaManager = nullptr; + delete mScriptContext; mScriptContext = nullptr; @@ -701,8 +711,8 @@ void OMW::Engine::setWindowIcon() void OMW::Engine::prepareEngine (Settings::Manager & settings) { - mEnvironment.setStateManager ( - std::make_unique (mCfgMgr.getUserDataPath() / "saves", mContentFiles)); + mStateManager = std::make_unique(mCfgMgr.getUserDataPath() / "saves", mContentFiles); + mEnvironment.setStateManager(*mStateManager); mStereoManager = std::make_unique(mViewer); @@ -723,6 +733,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) Settings::Manager::getString("texture mipmap", "General"), Settings::Manager::getInt("anisotropy", "General") ); + mEnvironment.setResourceSystem(*mResourceSystem); int numThreads = Settings::Manager::getInt("preload num threads", "Cells"); if (numThreads <= 0) @@ -744,9 +755,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mViewer->addEventHandler(mScreenCaptureHandler); - auto luaMgr = std::make_unique(mVFS.get(), (mResDir / "lua_libs").string()); - mLuaManager = luaMgr.get(); - mEnvironment.setLuaManager(std::move(luaMgr)); + mLuaManager = std::make_unique(mVFS.get(), (mResDir / "lua_libs").string()); + mEnvironment.setLuaManager(*mLuaManager); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so @@ -799,34 +809,36 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mStereoManager->disableStereoForNode(guiRoot); rootNode->addChild(guiRoot); - auto windowMgr = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), + mWindowManager = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string(), shadersSupported); - auto* windowMgrInternal = windowMgr.get(); - mEnvironment.setWindowManager (std::move(windowMgr)); + mEnvironment.setWindowManager(*mWindowManager); - auto inputMgr = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); - mEnvironment.setInputManager(std::move(inputMgr)); + mInputManager = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, + mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); + mEnvironment.setInputManager(*mInputManager); // Create sound system - mEnvironment.setSoundManager (std::make_unique(mVFS.get(), mUseSound)); + mSoundManager = std::make_unique(mVFS.get(), mUseSound); + mEnvironment.setSoundManager(*mSoundManager); if (!mSkipMenu) { const std::string& logo = Fallback::Map::getString("Movies_Company_Logo"); if (!logo.empty()) - mEnvironment.getWindowManager()->playVideo(logo, true); + mWindowManager->playVideo(logo, true); } // Create the world - mEnvironment.setWorld(std::make_unique(mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mWorld = std::make_unique(mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, - mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); - mEnvironment.getWorld()->setupPlayer(); + mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()); + mWorld->setupPlayer(); + mEnvironment.setWorld(*mWorld); - windowMgrInternal->setStore(mEnvironment.getWorld()->getStore()); - windowMgrInternal->initUI(); + mWindowManager->setStore(mWorld->getStore()); + mWindowManager->initUI(); //Load translation data mTranslationDataStorage.setEncoder(mEncoder); @@ -839,21 +851,25 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions (&mExtensions); - mEnvironment.setScriptManager (std::make_unique(mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode, - mScriptBlacklistUse ? mScriptBlacklist : std::vector())); + mScriptManager = std::make_unique(mWorld->getStore(), *mScriptContext, mWarningsMode, + mScriptBlacklistUse ? mScriptBlacklist : std::vector()); + mEnvironment.setScriptManager(*mScriptManager); // Create game mechanics system - mEnvironment.setMechanicsManager (std::make_unique()); + mMechanicsManager = std::make_unique(); + mEnvironment.setMechanicsManager(*mMechanicsManager); // Create dialog system - mEnvironment.setJournal (std::make_unique()); - mEnvironment.setDialogueManager (std::make_unique(mExtensions, mTranslationDataStorage)); - mEnvironment.setResourceSystem(mResourceSystem.get()); + mJournal = std::make_unique(); + mEnvironment.setJournal(*mJournal); + + mDialogueManager = std::make_unique(mExtensions, mTranslationDataStorage); + mEnvironment.setDialogueManager(*mDialogueManager); // scripts if (mCompileAll) { - std::pair result = mEnvironment.getScriptManager()->compileAll(); + std::pair result = mScriptManager->compileAll(); if (result.first) Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " scripts (" @@ -1014,25 +1030,25 @@ void OMW::Engine::go() // Start the game if (!mSaveGameFile.empty()) { - mEnvironment.getStateManager()->loadGame(mSaveGameFile); + mStateManager->loadGame(mSaveGameFile); } else if (!mSkipMenu) { // start in main menu - mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - mEnvironment.getSoundManager()->playTitleMusic(); + mWindowManager->pushGuiMode (MWGui::GM_MainMenu); + mSoundManager->playTitleMusic(); const std::string& logo = Fallback::Map::getString("Movies_Morrowind_Logo"); if (!logo.empty()) - mEnvironment.getWindowManager()->playVideo(logo, true); + mWindowManager->playVideo(logo, true); } else { - mEnvironment.getStateManager()->newGame (!mNewGame); + mStateManager->newGame (!mNewGame); } - if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running) + if (!mStartupScript.empty() && mStateManager->getState() == MWState::StateManager::State_Running) { - mEnvironment.getWindowManager()->executeInConsole(mStartupScript); + mWindowManager->executeInConsole(mStartupScript); } LuaWorker luaWorker(this); // starts a separate lua thread if "lua num threads" > 0 @@ -1041,7 +1057,7 @@ void OMW::Engine::go() double simulationTime = 0.0; Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); - while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) + while (!mViewer->done() && !mStateManager->hasQuitRequest()) { const double dt = std::chrono::duration_cast>(std::min( frameRateLimiter.getLastFrameDuration(), @@ -1060,7 +1076,7 @@ void OMW::Engine::go() mViewer->eventTraversal(); mViewer->updateTraversal(); - mEnvironment.getWorld()->updateWindowManager(); + mWorld->updateWindowManager(); luaWorker.allowUpdate(); // if there is a separate Lua thread, it starts the update now @@ -1068,7 +1084,7 @@ void OMW::Engine::go() luaWorker.finishUpdate(); - bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); + bool guiActive = mWindowManager->isGuiMode(); if (!guiActive) simulationTime += dt; } diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index d30bd33e80..c281f032ea 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -64,6 +64,51 @@ namespace SceneUtil } } +namespace MWState +{ + class StateManager; +} + +namespace MWGui +{ + class WindowManager; +} + +namespace MWInput +{ + class InputManager; +} + +namespace MWSound +{ + class SoundManager; +} + +namespace MWWorld +{ + class World; +} + +namespace MWScript +{ + class ScriptManager; +} + +namespace MWMechanics +{ + class MechanicsManager; +} + +namespace MWDialogue +{ + class DialogueManager; +} + +namespace MWDialogue +{ + class Journal; +} + struct SDL_Window; namespace OMW @@ -75,6 +120,16 @@ namespace OMW std::unique_ptr mVFS; std::unique_ptr mResourceSystem; osg::ref_ptr mWorkQueue; + std::unique_ptr mWorld; + std::unique_ptr mSoundManager; + std::unique_ptr mScriptManager; + std::unique_ptr mWindowManager; + std::unique_ptr mMechanicsManager; + std::unique_ptr mDialogueManager; + std::unique_ptr mJournal; + std::unique_ptr mInputManager; + std::unique_ptr mStateManager; + std::unique_ptr mLuaManager; MWBase::Environment mEnvironment; ToUTF8::FromType mEncoding; ToUTF8::Utf8Encoder* mEncoder; @@ -111,8 +166,6 @@ namespace OMW Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; - MWLua::LuaManager* mLuaManager; - Files::Collections mFileCollections; bool mFSStrict; Translation::Storage mTranslationDataStorage; diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 1b212c73ab..70027989a6 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -19,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr; MWBase::Environment::Environment() { - assert(!sThis); + assert(sThis == nullptr); sThis = this; } @@ -28,167 +28,21 @@ MWBase::Environment::~Environment() sThis = nullptr; } -void MWBase::Environment::setWorld (std::unique_ptr&& world) -{ - mWorld = std::move(world); -} - -void MWBase::Environment::setSoundManager (std::unique_ptr&& soundManager) -{ - mSoundManager = std::move(soundManager); -} - -void MWBase::Environment::setScriptManager (std::unique_ptr&& scriptManager) -{ - mScriptManager = std::move(scriptManager); -} - -void MWBase::Environment::setWindowManager (std::unique_ptr&& windowManager) -{ - mWindowManager = std::move(windowManager); -} - -void MWBase::Environment::setMechanicsManager (std::unique_ptr&& mechanicsManager) -{ - mMechanicsManager = std::move(mechanicsManager); -} - -void MWBase::Environment::setDialogueManager (std::unique_ptr&& dialogueManager) -{ - mDialogueManager = std::move(dialogueManager); -} - -void MWBase::Environment::setJournal (std::unique_ptr&& journal) -{ - mJournal = std::move(journal); -} - -void MWBase::Environment::setInputManager (std::unique_ptr&& inputManager) -{ - mInputManager = std::move(inputManager); -} - -void MWBase::Environment::setStateManager (std::unique_ptr&& stateManager) -{ - mStateManager = std::move(stateManager); -} - -void MWBase::Environment::setLuaManager (std::unique_ptr&& luaManager) -{ - mLuaManager = std::move(luaManager); -} - -void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem) -{ - mResourceSystem = resourceSystem; -} - -void MWBase::Environment::setFrameDuration (float duration) -{ - mFrameDuration = duration; -} - -void MWBase::Environment::setFrameRateLimit(float limit) -{ - mFrameRateLimit = limit; -} - -float MWBase::Environment::getFrameRateLimit() const -{ - return mFrameRateLimit; -} - -MWBase::World *MWBase::Environment::getWorld() const -{ - assert (mWorld); - return mWorld.get(); -} - -MWBase::SoundManager *MWBase::Environment::getSoundManager() const -{ - assert (mSoundManager); - return mSoundManager.get(); -} - -MWBase::ScriptManager *MWBase::Environment::getScriptManager() const -{ - assert (mScriptManager); - return mScriptManager.get(); -} - -MWBase::WindowManager *MWBase::Environment::getWindowManager() const -{ - assert (mWindowManager); - return mWindowManager.get(); -} - -MWBase::MechanicsManager *MWBase::Environment::getMechanicsManager() const -{ - assert (mMechanicsManager); - return mMechanicsManager.get(); -} - -MWBase::DialogueManager *MWBase::Environment::getDialogueManager() const -{ - assert (mDialogueManager); - return mDialogueManager.get(); -} - -MWBase::Journal *MWBase::Environment::getJournal() const -{ - assert (mJournal); - return mJournal.get(); -} - -MWBase::InputManager *MWBase::Environment::getInputManager() const -{ - assert (mInputManager); - return mInputManager.get(); -} - -MWBase::StateManager *MWBase::Environment::getStateManager() const -{ - assert (mStateManager); - return mStateManager.get(); -} - -MWBase::LuaManager *MWBase::Environment::getLuaManager() const -{ - assert (mLuaManager); - return mLuaManager.get(); -} - -Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const -{ - return mResourceSystem; -} - -float MWBase::Environment::getFrameDuration() const -{ - return mFrameDuration; -} - void MWBase::Environment::cleanup() { - mMechanicsManager.reset(); - mDialogueManager.reset(); - mJournal.reset(); - mScriptManager.reset(); - mWindowManager.reset(); - mWorld.reset(); - mSoundManager.reset(); - mInputManager.reset(); - mStateManager.reset(); - mLuaManager.reset(); + mMechanicsManager = nullptr; + mDialogueManager = nullptr; + mJournal = nullptr; + mScriptManager = nullptr; + mWindowManager = nullptr; + mWorld = nullptr; + mSoundManager = nullptr; + mInputManager = nullptr; + mStateManager = nullptr; + mLuaManager = nullptr; mResourceSystem = nullptr; } -const MWBase::Environment& MWBase::Environment::get() -{ - assert (sThis); - return *sThis; -} - void MWBase::Environment::reportStats(unsigned int frameNumber, osg::Stats& stats) const { mMechanicsManager->reportStats(frameNumber, stats); diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 00af5d2869..e8ea605a9a 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -1,6 +1,8 @@ #ifndef GAME_BASE_ENVIRONMENT_H #define GAME_BASE_ENVIRONMENT_H +#include + #include namespace osg @@ -34,25 +36,19 @@ namespace MWBase { static Environment *sThis; - std::unique_ptr mWorld; - std::unique_ptr mSoundManager; - std::unique_ptr mScriptManager; - std::unique_ptr mWindowManager; - std::unique_ptr mMechanicsManager; - std::unique_ptr mDialogueManager; - std::unique_ptr mJournal; - std::unique_ptr mInputManager; - std::unique_ptr mStateManager; - std::unique_ptr mLuaManager; - Resource::ResourceSystem* mResourceSystem{}; - float mFrameDuration{}; - float mFrameRateLimit{}; - - Environment (const Environment&); - ///< not implemented - - Environment& operator= (const Environment&); - ///< not implemented + World* mWorld = nullptr; + SoundManager* mSoundManager = nullptr; + ScriptManager* mScriptManager = nullptr; + WindowManager* mWindowManager = nullptr; + MechanicsManager* mMechanicsManager = nullptr; + DialogueManager* mDialogueManager = nullptr; + Journal* mJournal = nullptr; + InputManager* mInputManager = nullptr; + StateManager* mStateManager = nullptr; + LuaManager* mLuaManager = nullptr; + Resource::ResourceSystem* mResourceSystem = nullptr; + float mFrameRateLimit = 0; + float mFrameDuration = 0; public: @@ -60,63 +56,70 @@ namespace MWBase ~Environment(); - void setWorld (std::unique_ptr&& world); + Environment(const Environment&) = delete; + + Environment& operator=(const Environment&) = delete; + + void setWorld(World& value) { mWorld = &value; } + + void setSoundManager(SoundManager& value) { mSoundManager = &value; } - void setSoundManager (std::unique_ptr&& soundManager); + void setScriptManager(ScriptManager& value) { mScriptManager = &value; } - void setScriptManager (std::unique_ptr&& scriptManager); + void setWindowManager(WindowManager& value) { mWindowManager = &value; } - void setWindowManager (std::unique_ptr&& windowManager); + void setMechanicsManager(MechanicsManager& value) { mMechanicsManager = &value; } - void setMechanicsManager (std::unique_ptr&& mechanicsManager); + void setDialogueManager(DialogueManager& value) { mDialogueManager = &value; } - void setDialogueManager (std::unique_ptr&& dialogueManager); + void setJournal(Journal& value) { mJournal = &value; } - void setJournal (std::unique_ptr&& journal); + void setInputManager(InputManager& value) { mInputManager = &value; } - void setInputManager (std::unique_ptr&& inputManager); + void setStateManager(StateManager& value) { mStateManager = &value; } - void setStateManager (std::unique_ptr&& stateManager); + void setLuaManager(LuaManager& value) { mLuaManager = &value; } - void setLuaManager (std::unique_ptr&& luaManager); + void setResourceSystem(Resource::ResourceSystem& value) { mResourceSystem = &value; } - void setResourceSystem (Resource::ResourceSystem *resourceSystem); + Misc::NotNullPtr getWorld() const { return mWorld; } - void setFrameDuration (float duration); - ///< Set length of current frame in seconds. + Misc::NotNullPtr getSoundManager() const { return mSoundManager; } - void setFrameRateLimit(float frameRateLimit); - float getFrameRateLimit() const; + Misc::NotNullPtr getScriptManager() const { return mScriptManager; } - World *getWorld() const; + Misc::NotNullPtr getWindowManager() const { return mWindowManager; } - SoundManager *getSoundManager() const; + Misc::NotNullPtr getMechanicsManager() const { return mMechanicsManager; } - ScriptManager *getScriptManager() const; + Misc::NotNullPtr getDialogueManager() const { return mDialogueManager; } - WindowManager *getWindowManager() const; + Misc::NotNullPtr getJournal() const { return mJournal; } - MechanicsManager *getMechanicsManager() const; + Misc::NotNullPtr getInputManager() const { return mInputManager; } - DialogueManager *getDialogueManager() const; + Misc::NotNullPtr getStateManager() const { return mStateManager; } - Journal *getJournal() const; + Misc::NotNullPtr getLuaManager() const { return mLuaManager; } - InputManager *getInputManager() const; + Misc::NotNullPtr getResourceSystem() const { return mResourceSystem; } - StateManager *getStateManager() const; + float getFrameRateLimit() const { return mFrameRateLimit; } - LuaManager *getLuaManager() const; + void setFrameRateLimit(float value) { mFrameRateLimit = value; } - Resource::ResourceSystem *getResourceSystem() const; + float getFrameDuration() const { return mFrameDuration; } - float getFrameDuration() const; + void setFrameDuration(float value) { mFrameDuration = value; } void cleanup(); - ///< Delete all mw*-subsystems. - static const Environment& get(); - ///< Return instance of this class. + /// Return instance of this class. + static const Environment& get() + { + assert(sThis != nullptr); + return *sThis; + } void reportStats(unsigned int frameNumber, osg::Stats& stats) const; }; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index 63b0a197a7..eae6996acd 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -157,8 +157,8 @@ namespace MWInput void ActionManager::executeAction(int action) { MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action}); - auto* inputManager = MWBase::Environment::get().getInputManager(); - auto* windowManager = MWBase::Environment::get().getWindowManager(); + const auto inputManager = MWBase::Environment::get().getInputManager(); + const auto windowManager = MWBase::Environment::get().getWindowManager(); // trigger action activated switch (action) { diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 5b66c8b945..b66aa35368 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -200,7 +200,7 @@ namespace MWLua }; api["setConsoleSelectedObject"] = [luaManager=context.mLuaManager](const sol::object& obj) { - auto* wm = MWBase::Environment::get().getWindowManager(); + const auto wm = MWBase::Environment::get().getWindowManager(); if (obj == sol::nil) luaManager->addAction([wm]{ wm->setConsoleSelectedObject(MWWorld::Ptr()); }); else diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 01c404c563..a659c30341 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -495,7 +495,7 @@ namespace MWMechanics getActorsSidingWith(actor1, allies1, cachedAllies); - auto* mechanicsManager = MWBase::Environment::get().getMechanicsManager(); + const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and actor2 for (const MWWorld::Ptr& ally : allies1) { @@ -583,7 +583,7 @@ namespace MWMechanics } // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter - auto* world = MWBase::Environment::get().getWorld(); + const auto world = MWBase::Environment::get().getWorld(); if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() && creatureStats2.getAiSequence().isInCombat()) { // Check if the creature is too far @@ -917,7 +917,7 @@ namespace MWMechanics //If holding a light... if(heldIter.getType() == MWWorld::ContainerStore::Type_Light) { - auto* world = MWBase::Environment::get().getWorld(); + const auto world = MWBase::Environment::get().getWorld(); // Use time from the player's light if(isPlayer) { @@ -974,8 +974,8 @@ namespace MWMechanics if (playerStats.isWerewolf()) return; - auto* mechanicsManager = MWBase::Environment::get().getMechanicsManager(); - auto* world = MWBase::Environment::get().getWorld(); + const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); + const auto world = MWBase::Environment::get().getWorld(); if (actorClass.isClass(ptr, "Guard") && creatureStats.getAiSequence().isInPursuit() && !creatureStats.getAiSequence().isInCombat() && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 26baba30d4..209229efc5 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -207,7 +207,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { - auto* world = MWBase::Environment::get().getWorld(); + const auto world = MWBase::Environment::get().getWorld(); auto& charClass = mPtr.getClass(); auto& stats = charClass.getCreatureStats(mPtr); bool recovery = stats.getHitRecovery(); @@ -1101,7 +1101,7 @@ void CharacterController::updateIdleStormState(bool inwater) return; } - auto* world = MWBase::Environment::get().getWorld(); + const auto world = MWBase::Environment::get().getWorld(); if (world->isInStorm()) { osg::Vec3f stormDirection = world->getStormDirection(); @@ -1139,7 +1139,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const bool CharacterController::updateState(CharacterState idle) { - auto* world = MWBase::Environment::get().getWorld(); + const auto world = MWBase::Environment::get().getWorld(); auto& prng = world->getPrng(); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 0dd177e4b8..1c1a2d6fbd 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -789,7 +789,7 @@ namespace MWPhysics void PhysicsSystem::moveActors() { auto* player = getActor(MWMechanics::getPlayer()); - auto* world = MWBase::Environment::get().getWorld(); + const auto world = MWBase::Environment::get().getWorld(); // copy new ptr position in temporary vector. player is handled separately as its movement might change active cell. std::vector> newPositions; diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index 6af1d9782c..2395eeab69 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -25,7 +25,7 @@ osg::ref_ptr LandManager::getLand(int x, int y) return static_cast(obj.get()); else { - const auto* world = MWBase::Environment::get().getWorld(); + const auto world = MWBase::Environment::get().getWorld(); if (!world) return nullptr; const ESM::Land* land = world->getStore().get().search(x,y); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 944e947cd1..52b647add3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -804,7 +804,7 @@ namespace MWRender } else if (mode == Render_Scene) { - auto* wm = MWBase::Environment::get().getWindowManager(); + const auto wm = MWBase::Environment::get().getWindowManager(); unsigned int mask = wm->getCullMask(); bool enabled = !(mask&sToggleWorldMask); if (enabled) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 64acf1c10f..dd5ad0c0f9 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -522,11 +522,11 @@ namespace MWWorld void Player::update() { auto player = getPlayer(); - auto* world = MWBase::Environment::get().getWorld(); - auto* rendering = world->getRenderingManager(); + const auto world = MWBase::Environment::get().getWorld(); + const auto rendering = world->getRenderingManager(); auto& store = world->getStore(); auto& playerClass = player.getClass(); - auto* windowMgr = MWBase::Environment::get().getWindowManager(); + const auto windowMgr = MWBase::Environment::get().getWindowManager(); if (player.getCell()->isExterior()) { diff --git a/components/misc/notnullptr.hpp b/components/misc/notnullptr.hpp new file mode 100644 index 0000000000..a7e02c3614 --- /dev/null +++ b/components/misc/notnullptr.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_MISC_NOTNULLPTR_H +#define OPENMW_COMPONENTS_MISC_NOTNULLPTR_H + +#include +#include +#include + +namespace Misc +{ + template + class NotNullPtr + { + public: + NotNullPtr(T* value) + : mValue(value) + { + assert(mValue != nullptr); + } + + NotNullPtr(std::nullptr_t) = delete; + + operator T*() const { return mValue; } + + T* operator->() const { return mValue; } + + T& operator*() const { return *mValue; } + + private: + T* mValue; + }; +} + +#endif From 2dc6e755b2d304b26d6c3a2a40ffe0c016f4b9d3 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 18:23:57 +0200 Subject: [PATCH 2388/2859] Remove redundant update virtual functions --- apps/openmw/mwbase/mechanicsmanager.hpp | 6 ------ apps/openmw/mwbase/soundmanager.hpp | 2 -- apps/openmw/mwbase/statemanager.hpp | 2 -- apps/openmw/mwbase/windowmanager.hpp | 2 -- apps/openmw/mwbase/world.hpp | 5 ----- apps/openmw/mwgui/windowmanagerimp.hpp | 2 +- apps/openmw/mwinput/inputmanagerimp.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwsound/soundmanagerimp.hpp | 2 +- apps/openmw/mwstate/statemanagerimp.hpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 6 +++--- 11 files changed, 8 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 5b41ff7fda..b6e167ab9e 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -66,12 +66,6 @@ namespace MWBase virtual void drop (const MWWorld::CellStore *cellStore) = 0; ///< Deregister all objects in the given cell. - virtual void update (float duration, bool paused) = 0; - ///< Update objects - /// - /// \param paused In game type does not currently advance (this usually means some GUI - /// component is up). - virtual void setPlayerName (const std::string& name) = 0; ///< Set player name. diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 2bac561fdd..9d3e99d06b 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -176,8 +176,6 @@ namespace MWBase virtual void pausePlayback() = 0; virtual void resumePlayback() = 0; - virtual void update(float duration) = 0; - virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) = 0; virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0; diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index 157833a0ee..0537ff02d2 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -88,8 +88,6 @@ namespace MWBase /// iterator. virtual CharacterIterator characterEnd() = 0; - - virtual void update (float duration) = 0; }; } diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index f47ae24e87..45ea43daa8 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -247,8 +247,6 @@ namespace MWBase /// returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual int readPressedButton() = 0; - virtual void update (float duration) = 0; - virtual void updateConsoleObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) = 0; /** diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 98783f3c62..899be7dfb9 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -408,11 +408,6 @@ namespace MWBase ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record - virtual void update (float duration, bool paused) = 0; - virtual void updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) = 0; - - virtual void updateWindowManager () = 0; - virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) = 0; ///< copy and place an object into the gameworld at the specified cursor position /// @param object diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 8ab50e32ed..9a7ea2f1a7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -277,7 +277,7 @@ namespace MWGui int readPressedButton () override; ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) - void update (float duration) override; + void update (float duration); /** * Fetches a GMST string from the store, if there is no setting with the given diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 3f9f1b3be1..cf222d3e46 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -61,7 +61,7 @@ namespace MWInput /// Clear all savegame-specific data void clear() override; - void update(float dt, bool disableControls=false, bool disableEvents=false) override; + void update(float dt, bool disableControls, bool disableEvents=false) override; void changeInputMode(bool guiMode) override; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 6128b8bf52..4dd8790d2a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -54,7 +54,7 @@ namespace MWMechanics void drop(const MWWorld::CellStore *cellStore) override; ///< Deregister all objects in the given cell. - void update (float duration, bool paused) override; + void update(float duration, bool paused); ///< Update objects /// /// \param paused In game type does not currently advance (this usually means some GUI diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index e659e7a12a..d03059ba3e 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -244,7 +244,7 @@ namespace MWSound void pausePlayback() override; void resumePlayback() override; - void update(float duration) override; + void update(float duration); void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) override; diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index a29e72b3ad..273a3eb3bc 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -84,7 +84,7 @@ namespace MWState CharacterIterator characterEnd() override; - void update (float duration) override; + void update(float duration); }; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index fb1b80daf3..09bae6653d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -495,10 +495,10 @@ namespace MWWorld ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record - void update (float duration, bool paused) override; - void updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) override; + void update(float duration, bool paused); + void updatePhysics(float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - void updateWindowManager () override; + void updateWindowManager(); MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) override; ///< copy and place an object into the gameworld at the specified cursor position From 2d1d7e644c7750f5d705e2bb833b8441a14d1b69 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 6 May 2022 23:46:36 +0200 Subject: [PATCH 2389/2859] [Lua] Fix the bug the object:activate() doesn't trigger mwscripts --- apps/openmw/mwlua/objectbindings.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 0bf3fc99c5..8487856d81 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -95,9 +95,12 @@ namespace MWLua if (actor.isEmpty()) throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); - MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); - std::unique_ptr action = object.getClass().activate(object, actor); - action->execute(actor); + if (object.getRefData().activate()) + { + MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); + std::unique_ptr action = object.getClass().activate(object, actor); + action->execute(actor); + } } std::string toString() const override From 76160076db9a5a6cc17212e88df2721c69b89794 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 10 Jul 2021 13:43:53 +0200 Subject: [PATCH 2390/2859] Introduce OpenMW integration tests --- .../integration_tests/test_lua_api/player.lua | 63 +++++++++++++ .../integration_tests/test_lua_api/test.lua | 67 +++++++++++++ .../test_lua_api/test.omwscripts | 3 + .../test_lua_api/testing_util.lua | 93 +++++++++++++++++++ scripts/integration_tests.py | 87 +++++++++++++++++ 5 files changed, 313 insertions(+) create mode 100644 scripts/data/integration_tests/test_lua_api/player.lua create mode 100644 scripts/data/integration_tests/test_lua_api/test.lua create mode 100644 scripts/data/integration_tests/test_lua_api/test.omwscripts create mode 100644 scripts/data/integration_tests/test_lua_api/testing_util.lua create mode 100755 scripts/integration_tests.py diff --git a/scripts/data/integration_tests/test_lua_api/player.lua b/scripts/data/integration_tests/test_lua_api/player.lua new file mode 100644 index 0000000000..c46ab5c46e --- /dev/null +++ b/scripts/data/integration_tests/test_lua_api/player.lua @@ -0,0 +1,63 @@ +local testing = require('testing_util') +local self = require('openmw.self') +local util = require('openmw.util') +local core = require('openmw.core') +local input = require('openmw.input') +local types = require('openmw.types') + +input.setControlSwitch(input.CONTROL_SWITCH.Fighting, false) +input.setControlSwitch(input.CONTROL_SWITCH.Jumping, false) +input.setControlSwitch(input.CONTROL_SWITCH.Looking, false) +input.setControlSwitch(input.CONTROL_SWITCH.Magic, false) +input.setControlSwitch(input.CONTROL_SWITCH.VanityMode, false) +input.setControlSwitch(input.CONTROL_SWITCH.ViewMode, false) + +testing.registerLocalTest('playerMovement', + function() + local startTime = core.getSimulationTime() + local pos = self.position + + while core.getSimulationTime() < startTime + 0.5 do + self.controls.jump = false + self.controls.run = true + self.controls.movement = 0 + self.controls.sideMovement = 0 + local progress = (core.getSimulationTime() - startTime) / 0.5 + self.controls.yawChange = util.normalizeAngle(math.rad(90) * progress - self.rotation.z) + coroutine.yield() + end + testing.expectEqualWithDelta(self.rotation.z, math.rad(90), 0.05, 'Incorrect rotation') + + while core.getSimulationTime() < startTime + 1.5 do + self.controls.jump = false + self.controls.run = true + self.controls.movement = 1 + self.controls.sideMovement = 0 + self.controls.yawChange = 0 + coroutine.yield() + end + direction = (self.position - pos) / types.Actor.runSpeed(self) + testing.expectEqualWithDelta(direction.x, 1, 0.1, 'Run forward, X coord') + testing.expectEqualWithDelta(direction.y, 0, 0.1, 'Run forward, Y coord') + + pos = self.position + while core.getSimulationTime() < startTime + 2.5 do + self.controls.jump = false + self.controls.run = false + self.controls.movement = -1 + self.controls.sideMovement = -1 + self.controls.yawChange = 0 + coroutine.yield() + end + direction = (self.position - pos) / types.Actor.walkSpeed(self) + testing.expectEqualWithDelta(direction.x, -0.707, 0.1, 'Walk diagonally, X coord') + testing.expectEqualWithDelta(direction.y, 0.707, 0.1, 'Walk diagonally, Y coord') + end) + +return { + engineHandlers = { + onUpdate = testing.updateLocal, + }, + eventHandlers = testing.eventHandlers +} + diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua new file mode 100644 index 0000000000..573844199b --- /dev/null +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -0,0 +1,67 @@ +local testing = require('testing_util') +local core = require('openmw.core') +local async = require('openmw.async') +local util = require('openmw.util') + +local function testTimers() + testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') + testing.expectAlmostEqual(core.getSimulationTimeScale(), 1, 'incorrect getSimulationTimeScale result') + + local startGameTime = core.getGameTime() + local startSimulationTime = core.getSimulationTime() + + local ts1, ts2, th1, th2 + local cb = async:registerTimerCallback("tfunc", function(arg) + if arg == 'g' then + th1 = core.getGameTime() - startGameTime + else + ts1 = core.getSimulationTime() - startSimulationTime + end + end) + async:newGameTimer(36, cb, 'g') + async:newSimulationTimer(0.5, cb, 's') + async:newUnsavableGameTimer(72, function() + th2 = core.getGameTime() - startGameTime + end) + async:newUnsavableSimulationTimer(1, function() + ts2 = core.getSimulationTime() - startSimulationTime + end) + + while not (ts1 and ts2 and th1 and th2) do coroutine.yield() end + + testing.expectAlmostEqual(th1, 36, 'async:newGameTimer failed') + testing.expectAlmostEqual(ts1, 0.5, 'async:newSimulationTimer failed') + testing.expectAlmostEqual(th2, 72, 'async:newUnsavableGameTimer failed') + testing.expectAlmostEqual(ts2, 1, 'async:newUnsavableSimulationTimer failed') +end + +local function testTeleport() + player:teleport('', util.vector3(100, 50, 0), util.vector3(0, 0, math.rad(-90))) + coroutine.yield() + testing.expect(player.cell.isExterior, 'teleport to exterior failed') + testing.expectEqualWithDelta(player.position.x, 100, 1, 'incorrect position after teleporting') + testing.expectEqualWithDelta(player.position.y, 50, 1, 'incorrect position after teleporting') + testing.expectEqualWithDelta(player.rotation.z, math.rad(-90), 0.05, 'incorrect rotation after teleporting') + + player:teleport('', util.vector3(50, -100, 0)) + coroutine.yield() + testing.expect(player.cell.isExterior, 'teleport to exterior failed') + testing.expectEqualWithDelta(player.position.x, 50, 1, 'incorrect position after teleporting') + testing.expectEqualWithDelta(player.position.y, -100, 1, 'incorrect position after teleporting') + testing.expectEqualWithDelta(player.rotation.z, math.rad(-90), 0.05, 'teleporting changes rotation') +end + +tests = { + {'timers', testTimers}, + {'playerMovement', function() testing.runLocalTest(player, 'playerMovement') end}, + {'teleport', testTeleport}, +} + +return { + engineHandlers = { + onUpdate = testing.testRunner(tests), + onPlayerAdded = function(p) player = p end, + }, + eventHandlers = testing.eventHandlers, +} + diff --git a/scripts/data/integration_tests/test_lua_api/test.omwscripts b/scripts/data/integration_tests/test_lua_api/test.omwscripts new file mode 100644 index 0000000000..97f523afbd --- /dev/null +++ b/scripts/data/integration_tests/test_lua_api/test.omwscripts @@ -0,0 +1,3 @@ +GLOBAL: test.lua +PLAYER: player.lua + diff --git a/scripts/data/integration_tests/test_lua_api/testing_util.lua b/scripts/data/integration_tests/test_lua_api/testing_util.lua new file mode 100644 index 0000000000..719502e741 --- /dev/null +++ b/scripts/data/integration_tests/test_lua_api/testing_util.lua @@ -0,0 +1,93 @@ +local core = require('openmw.core') + +local M = {} +local currentLocalTest = nil +local currentLocalTestError = nil + +function M.testRunner(tests) + local fn = function() + for i, test in ipairs(tests) do + local name, fn = unpack(test) + print('TEST_START', i, name) + local status, err = pcall(fn) + if status then + print('TEST_OK', i, name) + else + print('TEST_FAILED', i, name, err) + end + end + core.quit() + end + local co = coroutine.create(fn) + return function() + if coroutine.status(co) ~= 'dead' then + coroutine.resume(co) + end + end +end + +function M.runLocalTest(obj, name) + currentLocalTest = name + currentLocalTestError = nil + obj:sendEvent('runLocalTest', name) + while currentLocalTest do coroutine.yield() end + if currentLocalTestError then error(currentLocalTestError, 2) end +end + +function M.expect(cond, delta, msg) + if not cond then + error(msg or '"true" expected', 2) + end +end + +function M.expectEqualWithDelta(v1, v2, delta, msg) + if math.abs(v1 - v2) > delta then + error(string.format('%s: %f ~= %f', msg or '', v1, v2), 2) + end +end + +function M.expectAlmostEqual(v1, v2, msg) + if math.abs(v1 - v2) / (math.abs(v1) + math.abs(v2)) > 0.05 then + error(string.format('%s: %f ~= %f', msg or '', v1, v2), 2) + end +end + +local localTests = {} +local localTestRunner = nil + +function M.registerLocalTest(name, fn) + localTests[name] = fn +end + +function M.updateLocal() + if localTestRunner and coroutine.status(localTestRunner) ~= 'dead' then + coroutine.resume(localTestRunner) + else + localTestRunner = nil + end +end + +M.eventHandlers = { + runLocalTest = function(name) -- used only in local scripts + fn = localTests[name] + if not fn then + core.sendGlobalEvent('localTestFinished', {name=name, errMsg='Test not found'}) + return + end + localTestRunner = coroutine.create(function() + local status, err = pcall(fn) + if status then err = nil end + core.sendGlobalEvent('localTestFinished', {name=name, errMsg=err}) + end) + end, + localTestFinished = function(data) -- used only in global scripts + if data.name ~= currentLocalTest then + error(string.format('localTestFinished with incorrect name %s, expected %s', data.name, currentLocalTest)) + end + currentLocalTest = nil + currentLocalTestError = data.errMsg + end, +} + +return M + diff --git a/scripts/integration_tests.py b/scripts/integration_tests.py new file mode 100755 index 0000000000..b125e688fb --- /dev/null +++ b/scripts/integration_tests.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +import argparse, datetime, os, subprocess, sys, shutil +from pathlib import Path + +parser = argparse.ArgumentParser(description="OpenMW integration tests.") +parser.add_argument( + "example_suite", + type=str, + help="path to openmw example suite (use 'git clone https://gitlab.com/OpenMW/example-suite/' to get it)", +) +parser.add_argument("--omw", type=str, default="openmw", help="path to openmw binary") +parser.add_argument( + "--workdir", type=str, default="integration_tests_output", help="directory for temporary files and logs" +) +args = parser.parse_args() + +example_suite_dir = Path(args.example_suite).resolve() +example_suite_content = example_suite_dir / "game_template" / "data" / "template.omwgame" +if not example_suite_content.is_file(): + sys.exit( + f"{example_suite_content} not found, use 'git clone https://gitlab.com/OpenMW/example-suite/' to get it" + ) + +openmw_binary = Path(args.omw).resolve() +if not openmw_binary.is_file(): + sys.exit(f"{openmw_binary} not found") + +work_dir = Path(args.workdir).resolve() +work_dir.mkdir(parents=True, exist_ok=True) +config_dir = work_dir / "config" +userdata_dir = work_dir / "userdata" +tests_dir = Path(__file__).resolve().parent / "data" / "integration_tests" +time_str = datetime.datetime.now().strftime("%Y-%m-%d-%H.%M.%S") + + +def runTest(name): + print(f"Start {name}") + shutil.rmtree(config_dir, ignore_errors=True) + config_dir.mkdir() + shutil.copyfile(example_suite_dir / "settings.cfg", config_dir / "settings.cfg") + test_dir = tests_dir / name + with open(config_dir / "openmw.cfg", "w", encoding="utf-8") as omw_cfg: + omw_cfg.writelines( + ( + f'data="{example_suite_dir}{os.sep}game_template{os.sep}data"\n', + f'data-local="{test_dir}"\n', + f'user-data="{userdata_dir}"\n', + "content=template.omwgame\n", + ) + ) + if (test_dir / "test.omwscripts").exists(): + omw_cfg.write("content=test.omwscripts\n") + with subprocess.Popen( + [f"{openmw_binary}", "--replace=config", f"--config={config_dir}", "--skip-menu", "--no-grab"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", + ) as process: + quit_requested = False + for line in process.stdout: + words = line.split(" ") + if len(words) > 1 and words[1] == "E]": + print(line, end="") + elif "Quit requested by a Lua script" in line: + quit_requested = True + elif "TEST_START" in line: + w = line.split("TEST_START")[1].split("\t") + print(f"TEST {w[2].strip()}\t\t", end="") + elif "TEST_OK" in line: + print(f"OK") + elif "TEST_FAILED" in line: + w = line.split("TEST_FAILED")[1].split("\t") + print(f"FAILED {w[3]}\t\t") + process.wait(5) + if not quit_requested: + print("ERROR: Unexpected termination") + shutil.copyfile(config_dir / "openmw.log", work_dir / f"{name}.{time_str}.log") + print(f"{name} finished") + + +for entry in tests_dir.glob("test_*"): + if entry.is_dir(): + runTest(entry.name) +shutil.rmtree(config_dir, ignore_errors=True) +shutil.rmtree(userdata_dir, ignore_errors=True) + From 096255534a00cf93c50eda583dea9207d6655268 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 7 Mar 2022 21:28:05 +0100 Subject: [PATCH 2391/2859] Initial Lua Settings API prototype --- files/builtin_scripts/builtin.omwscripts | 2 + .../scripts/omw/settings/common.lua | 150 ++++++++++++++++++ .../scripts/omw/settings/global.lua | 30 ++++ .../scripts/omw/settings/interface.lua | 51 ++++++ .../scripts/omw/settings/player.lua | 26 +++ .../scripts/omw/settings/register.lua | 76 +++++++++ .../scripts/omw/settings/render.lua | 61 +++++++ 7 files changed, 396 insertions(+) create mode 100644 files/builtin_scripts/scripts/omw/settings/common.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/global.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/interface.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/player.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/register.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/render.lua diff --git a/files/builtin_scripts/builtin.omwscripts b/files/builtin_scripts/builtin.omwscripts index af6320e0b2..989575ee1a 100644 --- a/files/builtin_scripts/builtin.omwscripts +++ b/files/builtin_scripts/builtin.omwscripts @@ -4,3 +4,5 @@ PLAYER: scripts/omw/console/player.lua GLOBAL: scripts/omw/console/global.lua CUSTOM: scripts/omw/console/local.lua PLAYER: scripts/omw/mwui/init.lua +GLOBAL: scripts/omw/settings/global.lua +PLAYER: scripts/omw/settings/player.lua diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua new file mode 100644 index 0000000000..f6645b3a11 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -0,0 +1,150 @@ +local prequire = function(path) + local status, result = pcall(function() + return require(path) + end) + return status and result or nil +end + +local core = require('openmw.core') +local types = require('openmw.types') +local storage = require('openmw.storage') +local self = prequire('openmw.self') +local world = prequire('openmw.world') + +local isPlayerScript = self and true or false +local isGlobalScript = world and true or false + +local EVENTS = { + SettingChanged = 'omwSettingsChanged', + SettingSet = 'omwSettingsGlobalSet', + GroupRegistered = 'omwSettingsGroupRegistered', +} + +local SCOPE = { + Global = 'Global', + Player = 'Player', + SaveGlobal = 'SaveGlobal', + SavePlayer = 'SavePlayer', +} + +local groups = storage.globalSection('OMW_Settings_Groups') +local saveGlobalSection = storage.globalSection('OMW_Settings_SaveGlobal') + +if isGlobalScript then + groups:removeOnExit() + saveGlobalSection:removeOnExit() +end + +local savePlayerSection = nil +if isPlayerScript then + savePlayerSection = storage.playerSection('OMW_Setting_SavePlayer') + savePlayerSection:removeOnExit() +end + +local scopes = { + [SCOPE.Global] = storage.globalSection('OMW_Setting_Global'), + [SCOPE.Player] = isPlayerScript and storage.playerSection('OMW_Setting_Player'), + [SCOPE.SaveGlobal] = saveGlobalSection, + [SCOPE.SavePlayer] = savePlayerSection, +} + +local function isGlobalScope(scope) + return scope == SCOPE.Global or scope == SCOPE.SaveGlobal +end + +local function getSetting(groupName, settingName) + local group = groups:get(groupName) + if not group then + error('Unknown group') + end + local setting = group[settingName] + if not setting then + error('Unknown setting') + end + return setting +end + +local function getSettingValue(groupName, settingName) + local setting = getSetting(groupName, settingName) + local scopeSection = scopes[setting.scope] + if not scopeSection then + error(('Setting %s is not available in this context'):format(setting.name)) + end + if not scopeSection:get(groupName) then + scopeSection:set(groupName, {}) + end + return scopeSection:get(groupName)[setting.name] or setting.default +end + +local function notifySettingChange(scope, event) + if isGlobalScope(scope) then + core.sendGlobalEvent(EVENTS.SettingChanged, event) + for _, a in ipairs(world.activeActors) do + if a.type == types.Player then + a:sendEvent(EVENTS.SettingChanged, event) + end + end + else + self:sendEvent(EVENTS.SettingChanged, event) + end +end + +local function setSettingValue(groupName, settingName, value) + local setting = getSetting(groupName, settingName) + local event = { + groupName = groupName, + settingName = setting.name, + value = value, + } + if isPlayerScript and isGlobalScope(setting.scope) then + core.sendGlobalEvent(EVENTS.SettingSet, event) + return + end + + local scopeSection = scopes[setting.scope] + if not scopeSection:get(groupName) then + scopeSection:set(groupName, {}) + end + local copy = scopeSection:getCopy(groupName) + copy[setting.name] = value + scopeSection:set(groupName, copy) + + notifySettingChange(setting.scope, event) +end + +local groupMeta = { + __index = { + get = function(self, settingName) + return getSettingValue(self.name, settingName) + end, + set = function(self, settingName, value) + setSettingValue(self.name, settingName, value) + end, + onChange = function(self, callback) + table.insert(self.__callbacks, callback) + end, + __changed = function(self, settingName, value) + for _, callback in ipairs(self.__callbacks) do + callback(settingName, value) + end + end, + }, +} +local cachedGroups = {} +local function getGroup(groupName) + if not cachedGroups[groupName] then + cachedGroups[groupName] = setmetatable({ + name = groupName, + __callbacks = {}, + }, groupMeta) + end + return cachedGroups[groupName] +end + +return { + EVENTS = EVENTS, + SCOPE = SCOPE, + scopes = scopes, + groups = groups, + getGroup = getGroup, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/global.lua b/files/builtin_scripts/scripts/omw/settings/global.lua new file mode 100644 index 0000000000..148d21847a --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/global.lua @@ -0,0 +1,30 @@ +local common = require('scripts.omw.settings.common') +local register = require('scripts.omw.settings.register') + +local saveScope = common.scopes[common.SCOPE.SaveGlobal] +return { + interfaceName = 'Settings', + interface = { + SCOPE = common.SCOPE, + getGroup = common.getGroup, + registerGroup = register.registerGroup, + }, + engineHandlers = { + onLoad = function(saved) + common.groups:reset() + saveScope:reset(saved) + end, + onSave = function() + return saveScope:asTable() + end, + onPlayerAdded = register.onPlayerAdded, + }, + eventHandlers = { + [common.EVENTS.SettingChanged] = function(e) + common.getGroup(e.groupName):__changed(e.settingName, e.value) + end, + [common.EVENTS.SettingSet] = function(e) + common.getGroup(e.groupName):set(e.settingName, e.value) + end, + } +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/interface.lua b/files/builtin_scripts/scripts/omw/settings/interface.lua new file mode 100644 index 0000000000..4fb60d2c4d --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/interface.lua @@ -0,0 +1,51 @@ +return function(player) + local core = require('openmw.core') + local types = require('openmw.types') + local world = not player and require('openmw.world') + + + local sections = require('scripts.omw.settings.sections') + local render = player and require('scripts.omw.settings.render') or nil + + local settingChangeEvent = 'omwSettingChanged' + local globalSetEvent = 'omwSettingGlobalSet' + local registerEvent = 'omwSettingGroupRegistered' + + local groups, scopes, SCOPE, isGlobal = sections.groups, sections.scopes, sections.SCOPE, sections.isGlobal + + + + local saveScope = scopes[player and SCOPE.SavePlayer or SCOPE.SaveGlobal] + return { + interfaceName = 'Settings', + interface = { + getGroup = getGroup, + SCOPE = SCOPE, + registerGroup = not player and require('scripts.omw.settings.register') or nil, + registerType = player and render.registerType or nil, + }, + engineHandlers = { + onLoad = function(saved) + if not player then groups:reset() end + saveScope:reset(saved) + end, + onSave = function() + return saveScope:asTable() + end, + }, + eventHandlers = { + [settingChangeEvent] = function(e) + getGroup(e.groupName):__changed(e.settingName, e.value) + end, + [globalSetEvent] = not player and function(e) + local setting = getSetting(e.groupName, e.settingName) + if isGlobal(setting.scope) then + setSettingValue(e.groupName, e.settingName, e.value) + else + error(('Unexpected Setting event for a non-global setting %s'):format(e.settingName)) + end + end or nil, + [registerEvent] = player and render.onGroupRegistered or nil, + } + } +end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua new file mode 100644 index 0000000000..42e333a4be --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -0,0 +1,26 @@ +local common = require('scripts.omw.settings.common') +local render = require('scripts.omw.settings.render') + +local saveScope = common.scopes[common.SCOPE.SavePlayer] +return { + interfaceName = 'Settings', + interface = { + SCOPE = common.SCOPE, + getGroup = common.getGroup, + registerRenderer = render.registerRenderer, + }, + engineHandlers = { + onLoad = function(saved) + saveScope:reset(saved) + end, + onSave = function() + return saveScope:asTable() + end, + }, + eventHandlers = { + [common.EVENTS.SettingChanged] = function(e) + common.getGroup(e.groupName):__changed(e.settingName, e.value) + end, + [common.EVENTS.GroupRegistered] = render.onGroupRegistered, + } +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua new file mode 100644 index 0000000000..93dcfc18bb --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -0,0 +1,76 @@ +local world = require('openmw.world') +local types = require('openmw.types') + +local common = require('scripts.omw.settings.common') + +local groups, SCOPE = common.groups, common.SCOPE + +local function validScope(scope) + local valid = false + for _, v in pairs(SCOPE) do + if v == scope then + valid = true + break + end + end + return valid +end + +local function validateSettingOptions(options) + if type(options.name) ~= 'string' then + error('Setting must have a name') + end + if options.default == nil then + error('Setting must have a default value') + end + if type(options.description) ~= 'string' then + error('Setting must have a description') + end + if not validScope(options.scope) then + error(('Invalid setting scope %s'):format(options.scope)) + end + if type(options.renderer) ~= 'string' then + error('Setting must have a renderer') + end +end + +local function addSetting(group, options) + validateSettingOptions(options) + if group[options.name] then + error(('Duplicate setting name %s'):format(options.name)) + end + group[options.name] = { + name = options.name, + scope = options.scope or SCOPE.Global, + default = options.default, + description = options.description, + renderer = options.renderer, + } +end + +local function registerGroup(groupName, list) + if groups:get(groupName) then + print(('Overwriting group %s'):format(groupName)) + end + local settings = {} + for _, opt in ipairs(list) do + addSetting(settings, opt) + end + groups:set(groupName, settings) + for _, a in ipairs(world.activeActors) do + if a.type == types.Player and a:isValid() then + a:sendEvent(common.EVENTS.GroupRegistered, groupName) + end + end +end + +local function onPlayerAdded(player) + for groupName in pairs(groups:asTable()) do + player:sendEvent(common.EVENTS.GroupRegistered, groupName) + end +end + +return { + registerGroup = registerGroup, + onPlayerAdded = onPlayerAdded, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua new file mode 100644 index 0000000000..e77badcff7 --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -0,0 +1,61 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') + +local common = require('scripts.omw.settings.common') + +local renderers = {} +local function registerRenderer(name, renderFunction) + renderers[name] = renderFunction +end + +local groupOptions = {} + +local function renderSetting(groupName, setting, value, index) + local renderFunction = renderers[setting.renderer] + if not renderFunction then + error(('Setting %s of %s has unknown renderer %s'):format(setting.name, groupName, setting.renderer)) + end + local layout = renderFunction(setting, value or setting.default, function(value) + local group = common.getGroup(groupName) + group:set(setting.name, value) + local element = groupOptions[groupName].element + element.layout.content[setting.name] = renderSetting(groupName, setting, value, index) + element:update() + end) + layout.name = setting.name + -- temporary hacky position and size + layout.props = layout.props or {} + layout.props.position = util.vector2(0, 100 * (index - 1)) + layout.props.size = util.vector2(400, 100) + return layout +end + +local function onGroupRegistered(groupName) + local group = common.groups:get(groupName) + local layout = { + content = ui.content{}, + } + local searchHints = { groupName } + local count = 0 + for _, setting in pairs(group) do + count = count + 1 + layout.content:add(renderSetting(groupName, setting, setting.default, count)) + table.insert(searchHints, setting.name) + end + layout.props = { + size = util.vector2(400, 100 * count) + } + local options = { + name = groupName, + element = ui.create(layout), + searchHints = table.concat(searchHints, ' '), + } + groupOptions[groupName] = options + print(('registering group %s'):format(groupName)) + ui.registerSettingsPage(options) +end + +return { + onGroupRegistered = onGroupRegistered, + registerRenderer = registerRenderer, +} \ No newline at end of file From aea2c019de175c1ff64e5799d85c4faec3dc5af7 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 29 Mar 2022 20:17:48 +0200 Subject: [PATCH 2392/2859] Fix content names for layouts inserted with :add() --- components/lua_ui/content.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/lua_ui/content.cpp b/components/lua_ui/content.cpp index 6f9cf61f2f..e7cf474bc9 100644 --- a/components/lua_ui/content.cpp +++ b/components/lua_ui/content.cpp @@ -44,11 +44,10 @@ namespace LuaUi void Content::insert(size_t index, const sol::table& table) { - size_t size = mOrdered.size(); - if (size < index) + if (mOrdered.size() < index) throw std::logic_error("Can't have gaps in UI Content."); mOrdered.insert(mOrdered.begin() + index, table); - for (size_t i = index; i < size; ++i) + for (size_t i = index; i < mOrdered.size(); ++i) { sol::optional name = mOrdered[i]["name"]; if (name.has_value()) From 1f5e3b78d4c0dcbeb4a9a6e0c5c8f36629120ba6 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 7 Apr 2022 17:43:03 +0200 Subject: [PATCH 2393/2859] Use Flex, don't force re-renders on layout table changes --- components/lua_ui/element.cpp | 3 +-- components/lua_ui/flex.cpp | 1 + .../scripts/omw/settings/render.lua | 16 +++++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 46eb543cca..9f70cfc1da 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -55,8 +55,7 @@ namespace LuaUi { WidgetExtension* ext = children[i]; sol::table newLayout = content.at(i); - if (ext->widget()->getTypeName() == widgetType(newLayout) - && ext->getLayout() == newLayout) + if (ext->widget()->getTypeName() == widgetType(newLayout)) { updateWidget(ext, newLayout); } diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index e90fba2a1a..dfe943a02f 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -71,6 +71,7 @@ namespace LuaUi w->forcePosition(childPosition); primary(size) += static_cast(growFactor * getGrow(w)); w->forceSize(size); + w->updateCoord(); primary(childPosition) += primary(size); w->updateCoord(); } diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index e77badcff7..0f9249a390 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -19,32 +19,30 @@ local function renderSetting(groupName, setting, value, index) local group = common.getGroup(groupName) group:set(setting.name, value) local element = groupOptions[groupName].element - element.layout.content[setting.name] = renderSetting(groupName, setting, value, index) + local settingLayout = renderSetting(groupName, setting, value, index) + settingLayout.name = setting.name + element.layout.content[setting.name] = settingLayout element:update() end) layout.name = setting.name - -- temporary hacky position and size - layout.props = layout.props or {} - layout.props.position = util.vector2(0, 100 * (index - 1)) - layout.props.size = util.vector2(400, 100) return layout end local function onGroupRegistered(groupName) local group = common.groups:get(groupName) local layout = { + type = ui.TYPE.Flex, content = ui.content{}, } local searchHints = { groupName } local count = 0 for _, setting in pairs(group) do count = count + 1 - layout.content:add(renderSetting(groupName, setting, setting.default, count)) + local settingLayout = renderSetting(groupName, setting, setting.default, count) + settingLayout.name = setting.name + layout.content:add(settingLayout) table.insert(searchHints, setting.name) end - layout.props = { - size = util.vector2(400, 100 * count) - } local options = { name = groupName, element = ui.create(layout), From a0c0c39a8cde66a402726f35129eedb79976de50 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 9 Apr 2022 19:58:32 +0200 Subject: [PATCH 2394/2859] Separate setting keys and names, support localization --- .../scripts/omw/settings/common.lua | 58 ++++++------- .../scripts/omw/settings/player.lua | 1 + .../scripts/omw/settings/register.lua | 19 ++--- .../scripts/omw/settings/render.lua | 81 ++++++++++++++----- 4 files changed, 95 insertions(+), 64 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index f6645b3a11..ec7afcae22 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -52,28 +52,28 @@ local function isGlobalScope(scope) return scope == SCOPE.Global or scope == SCOPE.SaveGlobal end -local function getSetting(groupName, settingName) - local group = groups:get(groupName) +local function getSetting(groupKey, settingKey) + local group = groups:get(groupKey) if not group then error('Unknown group') end - local setting = group[settingName] + local setting = group[settingKey] if not setting then error('Unknown setting') end return setting end -local function getSettingValue(groupName, settingName) - local setting = getSetting(groupName, settingName) +local function getSettingValue(groupKey, settingKey) + local setting = getSetting(groupKey, settingKey) local scopeSection = scopes[setting.scope] if not scopeSection then - error(('Setting %s is not available in this context'):format(setting.name)) + error(('Setting %s is not available in this context'):format(setting.key)) end - if not scopeSection:get(groupName) then - scopeSection:set(groupName, {}) + if not scopeSection:get(groupKey) then + scopeSection:set(groupKey, {}) end - return scopeSection:get(groupName)[setting.name] or setting.default + return scopeSection:get(groupKey)[setting.key] or setting.default end local function notifySettingChange(scope, event) @@ -89,11 +89,11 @@ local function notifySettingChange(scope, event) end end -local function setSettingValue(groupName, settingName, value) - local setting = getSetting(groupName, settingName) +local function setSettingValue(groupKey, settingKey, value) + local setting = getSetting(groupKey, settingKey) local event = { - groupName = groupName, - settingName = setting.name, + groupName = groupKey, + settingName = setting.key, value = value, } if isPlayerScript and isGlobalScope(setting.scope) then @@ -102,43 +102,43 @@ local function setSettingValue(groupName, settingName, value) end local scopeSection = scopes[setting.scope] - if not scopeSection:get(groupName) then - scopeSection:set(groupName, {}) + if not scopeSection:get(groupKey) then + scopeSection:set(groupKey, {}) end - local copy = scopeSection:getCopy(groupName) - copy[setting.name] = value - scopeSection:set(groupName, copy) + local copy = scopeSection:getCopy(groupKey) + copy[setting.key] = value + scopeSection:set(groupKey, copy) notifySettingChange(setting.scope, event) end local groupMeta = { __index = { - get = function(self, settingName) - return getSettingValue(self.name, settingName) + get = function(self, settingKey) + return getSettingValue(self.key, settingKey) end, - set = function(self, settingName, value) - setSettingValue(self.name, settingName, value) + set = function(self, settingKey, value) + setSettingValue(self.key, settingKey, value) end, onChange = function(self, callback) table.insert(self.__callbacks, callback) end, - __changed = function(self, settingName, value) + __changed = function(self, settingKey, value) for _, callback in ipairs(self.__callbacks) do - callback(settingName, value) + callback(settingKey, value) end end, }, } local cachedGroups = {} -local function getGroup(groupName) - if not cachedGroups[groupName] then - cachedGroups[groupName] = setmetatable({ - name = groupName, +local function getGroup(groupKey) + if not cachedGroups[groupKey] then + cachedGroups[groupKey] = setmetatable({ + key = groupKey, __callbacks = {}, }, groupMeta) end - return cachedGroups[groupName] + return cachedGroups[groupKey] end return { diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 42e333a4be..fb9a434e05 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -8,6 +8,7 @@ return { SCOPE = common.SCOPE, getGroup = common.getGroup, registerRenderer = render.registerRenderer, + localizeGroup = render.localizeGroup, }, engineHandlers = { onLoad = function(saved) diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index 93dcfc18bb..bc8812acf6 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -17,14 +17,8 @@ local function validScope(scope) end local function validateSettingOptions(options) - if type(options.name) ~= 'string' then - error('Setting must have a name') - end - if options.default == nil then - error('Setting must have a default value') - end - if type(options.description) ~= 'string' then - error('Setting must have a description') + if type(options.key) ~= 'string' then + error('Setting must have a key') end if not validScope(options.scope) then error(('Invalid setting scope %s'):format(options.scope)) @@ -36,14 +30,13 @@ end local function addSetting(group, options) validateSettingOptions(options) - if group[options.name] then - error(('Duplicate setting name %s'):format(options.name)) + if group[options.key] then + error(('Duplicate setting key %s'):format(options.key)) end - group[options.name] = { - name = options.name, + group[options.key] = { + key = options.key, scope = options.scope or SCOPE.Global, default = options.default, - description = options.description, renderer = options.renderer, } end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 0f9249a390..17217aab6f 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -9,51 +9,88 @@ local function registerRenderer(name, renderFunction) end local groupOptions = {} +local localization = {} -local function renderSetting(groupName, setting, value, index) +local function renderSetting(groupKey, setting, value, index) local renderFunction = renderers[setting.renderer] if not renderFunction then - error(('Setting %s of %s has unknown renderer %s'):format(setting.name, groupName, setting.renderer)) + error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local layout = renderFunction(setting, value or setting.default, function(value) - local group = common.getGroup(groupName) - group:set(setting.name, value) - local element = groupOptions[groupName].element - local settingLayout = renderSetting(groupName, setting, value, index) - settingLayout.name = setting.name - element.layout.content[setting.name] = settingLayout + local loc = localization[groupKey] and localization[groupKey].settings[setting.key] or { + name = setting.key, + description = '', + } + local layout = renderFunction(loc, value or setting.default, function(value) + local group = common.getGroup(groupKey) + group:set(setting.key, value) + local element = groupOptions[groupKey].element + local settingLayout = renderSetting(groupKey, setting, value, index) + settingLayout.name = setting.key + element.layout.content.settings.content[setting.key] = settingLayout element:update() end) - layout.name = setting.name + layout.name = setting.key return layout end -local function onGroupRegistered(groupName) - local group = common.groups:get(groupName) - local layout = { +local function updateLocalization(groupKey) + local loc = localization[groupKey] + local options = groupOptions[groupKey] + if not options or not loc then return end + local searchHints = { loc.name, loc.description } + options.name = loc.name + options.searchHints = table.concat(searchHints, ' ') + local layout = options.element.layout + layout.content.header.props.text = loc.description +end + +local function onGroupRegistered(groupKey) + local group = common.groups:get(groupKey) + local settingsLayout = { + name = 'settings', type = ui.TYPE.Flex, content = ui.content{}, } - local searchHints = { groupName } local count = 0 for _, setting in pairs(group) do count = count + 1 - local settingLayout = renderSetting(groupName, setting, setting.default, count) - settingLayout.name = setting.name - layout.content:add(settingLayout) - table.insert(searchHints, setting.name) + local settingLayout = renderSetting(groupKey, setting, setting.default, count) + settingLayout.key = setting.key + settingsLayout.content:add(settingLayout) end + local layout = { + type = ui.TYPE.Flex, + content = ui.content { + { + name = 'header', + type = ui.TYPE.Text, + props = { + text = '', + textSize = 30, + textColor = util.color.rgb(1, 1, 1), + }, + }, + settingsLayout, + }, + } local options = { - name = groupName, + name = groupKey, element = ui.create(layout), - searchHints = table.concat(searchHints, ' '), + searchHints = '', } - groupOptions[groupName] = options - print(('registering group %s'):format(groupName)) + groupOptions[groupKey] = options + updateLocalization(groupKey) + print(('registering group %s'):format(groupKey)) ui.registerSettingsPage(options) end +local function localizeGroup(groupKey, loc) + localization[groupKey] = loc + updateLocalization(groupKey) +end + return { onGroupRegistered = onGroupRegistered, registerRenderer = registerRenderer, + localizeGroup = localizeGroup, } \ No newline at end of file From 711f982e1941915f1b99454a03b32f088670f486 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 13 Apr 2022 20:08:33 +0200 Subject: [PATCH 2395/2859] Simplify renderers, standard setting reset --- .../scripts/omw/settings/player.lua | 18 +++++ .../scripts/omw/settings/register.lua | 1 + .../scripts/omw/settings/render.lua | 80 ++++++++++++------- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index fb9a434e05..1f55380ccc 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,5 +1,23 @@ local common = require('scripts.omw.settings.common') local render = require('scripts.omw.settings.render') +local ui = require('openmw.ui') +local async = require('openmw.async') +local util = require('openmw.util') + +render.registerRenderer('text', function(value, set, arg) + return { + type = ui.TYPE.TextEdit, + props = { + size = util.vector2(arg and arg.size or 300, 100), + text = value, + textColor = util.color.rgb(1, 1, 1), + textSize = 30, + }, + events = { + textChanged = async:callback(function(s) set(s) end), + }, + } +end) local saveScope = common.scopes[common.SCOPE.SavePlayer] return { diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index bc8812acf6..29f4263fa8 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -38,6 +38,7 @@ local function addSetting(group, options) scope = options.scope or SCOPE.Global, default = options.default, renderer = options.renderer, + argument = options.argument, } end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 17217aab6f..9fc42bb72e 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -1,5 +1,6 @@ local ui = require('openmw.ui') local util = require('openmw.util') +local async = require('openmw.async') local common = require('scripts.omw.settings.common') @@ -11,26 +12,55 @@ end local groupOptions = {} local localization = {} -local function renderSetting(groupKey, setting, value, index) +local function renderSetting(groupKey, setting, value) local renderFunction = renderers[setting.renderer] if not renderFunction then error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local loc = localization[groupKey] and localization[groupKey].settings[setting.key] or { - name = setting.key, - description = '', - } - local layout = renderFunction(loc, value or setting.default, function(value) + local settingName = localization[groupKey] + and localization[groupKey].settings[setting.key].name + or setting.key + local set = function(value) local group = common.getGroup(groupKey) group:set(setting.key, value) - local element = groupOptions[groupKey].element - local settingLayout = renderSetting(groupKey, setting, value, index) - settingLayout.name = setting.key - element.layout.content.settings.content[setting.key] = settingLayout - element:update() - end) - layout.name = setting.key - return layout + renderSetting(groupKey, setting, value) + end + local element = groupOptions[groupKey].element + local settingsLayout = element.layout.content.settings + settingsLayout.content[setting.key] = { + name = setting.key, + type = ui.TYPE.Flex, + props = { + horizontal = true, + align = ui.ALIGNMENT.Start, + arrange = ui.ALIGNMENT.Center, + }, + content = ui.content { + { + type = ui.TYPE.Text, + props = { + text = settingName .. ':', + textColor = util.color.rgb(1, 1, 1), + textSize = 30, + }, + }, + renderFunction(value or setting.default, set, setting.argument), + { + type = ui.TYPE.Text, + props = { + text = 'Reset', + textColor = util.color.rgb(1, 1, 1), + textSize = 30, + }, + events = { + mouseClick = async:callback(function() + set(setting.default) + end), + }, + }, + }, + } + element:update() end local function updateLocalization(groupKey) @@ -46,18 +76,6 @@ end local function onGroupRegistered(groupKey) local group = common.groups:get(groupKey) - local settingsLayout = { - name = 'settings', - type = ui.TYPE.Flex, - content = ui.content{}, - } - local count = 0 - for _, setting in pairs(group) do - count = count + 1 - local settingLayout = renderSetting(groupKey, setting, setting.default, count) - settingLayout.key = setting.key - settingsLayout.content:add(settingLayout) - end local layout = { type = ui.TYPE.Flex, content = ui.content { @@ -70,7 +88,11 @@ local function onGroupRegistered(groupKey) textColor = util.color.rgb(1, 1, 1), }, }, - settingsLayout, + { + name = 'settings', + type = ui.TYPE.Flex, + content = ui.content{}, + }, }, } local options = { @@ -79,6 +101,10 @@ local function onGroupRegistered(groupKey) searchHints = '', } groupOptions[groupKey] = options + for _, setting in pairs(group) do + layout.content.settings.content:add({ name = setting.key }) + renderSetting(groupKey, setting, setting.default) + end updateLocalization(groupKey) print(('registering group %s'):format(groupKey)) ui.registerSettingsPage(options) From 76b16f57dab8d8a0a8331c9dc667c7e9f26e47fc Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 16 Apr 2022 10:41:20 +0200 Subject: [PATCH 2396/2859] Change settings localization, add group name and description --- .../scripts/omw/settings/common.lua | 2 +- .../scripts/omw/settings/player.lua | 6 +- .../scripts/omw/settings/register.lua | 58 +++++-- .../scripts/omw/settings/render.lua | 155 +++++++++++------- 4 files changed, 148 insertions(+), 73 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index ec7afcae22..6f6cebb2cf 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -57,7 +57,7 @@ local function getSetting(groupKey, settingKey) if not group then error('Unknown group') end - local setting = group[settingKey] + local setting = group.settings[settingKey] if not setting then error('Unknown setting') end diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 1f55380ccc..b4cf75b127 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -8,10 +8,11 @@ render.registerRenderer('text', function(value, set, arg) return { type = ui.TYPE.TextEdit, props = { - size = util.vector2(arg and arg.size or 300, 100), + size = util.vector2(arg and arg.size or 300, 30), text = value, textColor = util.color.rgb(1, 1, 1), - textSize = 30, + textSize = 15, + textAlignV = ui.ALIGNMENT.Center, }, events = { textChanged = async:callback(function(s) set(s) end), @@ -26,7 +27,6 @@ return { SCOPE = common.SCOPE, getGroup = common.getGroup, registerRenderer = render.registerRenderer, - localizeGroup = render.localizeGroup, }, engineHandlers = { onLoad = function(saved) diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index 29f4263fa8..ca41843e00 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -26,34 +26,70 @@ local function validateSettingOptions(options) if type(options.renderer) ~= 'string' then error('Setting must have a renderer') end + if type(options.name) ~= 'string' then + error('Setting must have a name localization key') + end + if type(options.description) ~= 'string' then + error('Setting must have a descripiton localization key') + end end -local function addSetting(group, options) +local function addSetting(settings, options) validateSettingOptions(options) - if group[options.key] then + if settings[options.key] then error(('Duplicate setting key %s'):format(options.key)) end - group[options.key] = { + settings[options.key] = { key = options.key, scope = options.scope or SCOPE.Global, default = options.default, renderer = options.renderer, argument = options.argument, + + name = options.name, + description = options.description, } end -local function registerGroup(groupName, list) - if groups:get(groupName) then - print(('Overwriting group %s'):format(groupName)) +local function validateGroupOptions(options) + if type(options.key) ~= 'string' then + error('Group must have a key') + end + if type(options.localization) ~= 'string' then + error('Group must have a localization context') + end + if type(options.name) ~= 'string' then + error('Group must have a name localization key') + end + if type(options.description) ~= 'string' then + error('Group must have a description localization key') + end + if type(options.settings) ~= 'table' then + error('Group must have a table of settings') end - local settings = {} - for _, opt in ipairs(list) do - addSetting(settings, opt) +end + +local function registerGroup(options) + validateGroupOptions(options) + if groups:get(options.key) then + print(('Overwriting group %s'):format(options.key)) + end + local group = { + key = options.key, + localization = options.localization, + + name = options.name, + description = options.description, + + settings = {}, + } + for _, opt in ipairs(options.settings) do + addSetting(group.settings, opt) end - groups:set(groupName, settings) + groups:set(options.key, group) for _, a in ipairs(world.activeActors) do if a.type == types.Player and a:isValid() then - a:sendEvent(common.EVENTS.GroupRegistered, groupName) + a:sendEvent(common.EVENTS.GroupRegistered, options.key) end end end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 9fc42bb72e..57368a6a15 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -1,6 +1,7 @@ local ui = require('openmw.ui') local util = require('openmw.util') local async = require('openmw.async') +local core = require('openmw.core') local common = require('scripts.omw.settings.common') @@ -10,84 +11,122 @@ local function registerRenderer(name, renderFunction) end local groupOptions = {} -local localization = {} + + +local padding = function(size) + return { + props = { + size = util.vector2(size, size), + } + } +end + +local header = { + props = { + textColor = util.color.rgb(1, 1, 1), + textSize = 30, + }, +} + +local normal = { + props = { + textColor = util.color.rgb(1, 1, 1), + textSize = 25, + }, +} local function renderSetting(groupKey, setting, value) local renderFunction = renderers[setting.renderer] if not renderFunction then error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local settingName = localization[groupKey] - and localization[groupKey].settings[setting.key].name - or setting.key + local group = common.getGroup(groupKey) + value = value or group:get(setting.key) local set = function(value) - local group = common.getGroup(groupKey) group:set(setting.key, value) renderSetting(groupKey, setting, value) end local element = groupOptions[groupKey].element + local localization = groupOptions[groupKey].localization local settingsLayout = element.layout.content.settings settingsLayout.content[setting.key] = { name = setting.key, type = ui.TYPE.Flex, - props = { - horizontal = true, - align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.Center, - }, content = ui.content { { - type = ui.TYPE.Text, - props = { - text = settingName .. ':', - textColor = util.color.rgb(1, 1, 1), - textSize = 30, - }, - }, - renderFunction(value or setting.default, set, setting.argument), - { - type = ui.TYPE.Text, + type = ui.TYPE.Flex, props = { - text = 'Reset', - textColor = util.color.rgb(1, 1, 1), - textSize = 30, + horizontal = true, + align = ui.ALIGNMENT.Start, + arrange = ui.ALIGNMENT.End, }, - events = { - mouseClick = async:callback(function() - set(setting.default) - end), + content = ui.content { + { + type = ui.TYPE.Text, + template = normal, + props = { + text = localization(setting.name), + }, + }, + padding(10), + renderFunction(value, set, setting.argument), + padding(10), + { + type = ui.TYPE.Text, + template = normal, + props = { + text = 'Reset', + }, + events = { + mouseClick = async:callback(function() + set(setting.default) + end), + }, + }, }, }, + padding(20), }, } element:update() end -local function updateLocalization(groupKey) - local loc = localization[groupKey] - local options = groupOptions[groupKey] - if not options or not loc then return end - local searchHints = { loc.name, loc.description } - options.name = loc.name - options.searchHints = table.concat(searchHints, ' ') - local layout = options.element.layout - layout.content.header.props.text = loc.description -end - -local function onGroupRegistered(groupKey) +local function renderGroup(groupKey) local group = common.groups:get(groupKey) - local layout = { + local element = groupOptions[groupKey].element + local localization = groupOptions[groupKey].localization + element.layout = { type = ui.TYPE.Flex, content = ui.content { + padding(10), { - name = 'header', - type = ui.TYPE.Text, + type = ui.TYPE.Flex, props = { - text = '', - textSize = 30, - textColor = util.color.rgb(1, 1, 1), + horizontal = true, + align = ui.ALIGNMENT.Start, + arrange = ui.ALIGNMENT.Center, + }, + content = ui.content { + { + name = 'name', + type = ui.TYPE.Text, + template = header, + props = { + text = localization(group.name), + }, + }, + padding(10), + { + name = 'description', + type = ui.TYPE.Text, + template = normal, + props = { + text = localization(group.description), + }, + }, }, }, + padding(50), { name = 'settings', type = ui.TYPE.Flex, @@ -95,28 +134,28 @@ local function onGroupRegistered(groupKey) }, }, } + local settingsContent = element.layout.content.settings.content + for _, setting in pairs(group.settings) do + settingsContent:add({ name = setting.key }) + renderSetting(groupKey, setting) + end + element:update() +end + +local function onGroupRegistered(groupKey) + local group = common.groups:get(groupKey) local options = { name = groupKey, - element = ui.create(layout), + element = ui.create{}, searchHints = '', + localization = core.l10n(group.localization), } groupOptions[groupKey] = options - for _, setting in pairs(group) do - layout.content.settings.content:add({ name = setting.key }) - renderSetting(groupKey, setting, setting.default) - end - updateLocalization(groupKey) - print(('registering group %s'):format(groupKey)) + renderGroup(groupKey) ui.registerSettingsPage(options) end -local function localizeGroup(groupKey, loc) - localization[groupKey] = loc - updateLocalization(groupKey) -end - return { onGroupRegistered = onGroupRegistered, registerRenderer = registerRenderer, - localizeGroup = localizeGroup, } \ No newline at end of file From 38e0f5c0af4587faa918a19a85d8a60cba235366 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 16 Apr 2022 11:29:37 +0200 Subject: [PATCH 2397/2859] Fix a docs typo --- docs/source/reference/lua-scripting/widgets/flex.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/lua-scripting/widgets/flex.rst b/docs/source/reference/lua-scripting/widgets/flex.rst index 359d0d4394..2648e88996 100644 --- a/docs/source/reference/lua-scripting/widgets/flex.rst +++ b/docs/source/reference/lua-scripting/widgets/flex.rst @@ -24,7 +24,7 @@ Properties - ui.ALIGNMENT (Start) - Where to align the children in the main axis. * - arrange - - ui.ALIGNMETN (Start) + - ui.ALIGNMENT (Start) - How to arrange the children in the cross axis. External From a35bc1dee0663a05ab5763beaa16ae282f59f0bc Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 18 Apr 2022 08:42:02 +0200 Subject: [PATCH 2398/2859] openmw_aux.settings, rework to support local scripts --- files/builtin_scripts/openmw_aux/settings.lua | 4 + .../scripts/omw/settings/common.lua | 243 ++++++++++-------- .../scripts/omw/settings/global.lua | 34 ++- .../scripts/omw/settings/interface.lua | 51 ---- .../scripts/omw/settings/player.lua | 17 +- .../scripts/omw/settings/register.lua | 14 +- .../scripts/omw/settings/render.lua | 22 +- 7 files changed, 197 insertions(+), 188 deletions(-) create mode 100644 files/builtin_scripts/openmw_aux/settings.lua delete mode 100644 files/builtin_scripts/scripts/omw/settings/interface.lua diff --git a/files/builtin_scripts/openmw_aux/settings.lua b/files/builtin_scripts/openmw_aux/settings.lua new file mode 100644 index 0000000000..8ce9929eaa --- /dev/null +++ b/files/builtin_scripts/openmw_aux/settings.lua @@ -0,0 +1,4 @@ +local common = require('scripts.omw.settings.common') +return { + group = common.group, +} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 6f6cebb2cf..bc01bc20b5 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -1,23 +1,20 @@ -local prequire = function(path) - local status, result = pcall(function() - return require(path) - end) - return status and result or nil -end - +local storage = require('openmw.storage') local core = require('openmw.core') local types = require('openmw.types') -local storage = require('openmw.storage') -local self = prequire('openmw.self') -local world = prequire('openmw.world') - -local isPlayerScript = self and true or false -local isGlobalScript = world and true or false +local selfObject +do + local success, result = pcall(function() return require('openmw.self') end) + selfObject = success and result or nil +end +local playerObject = selfObject and selfObject.type == types.Player and selfObject or nil +local eventPrefix = 'omwSettings' local EVENTS = { - SettingChanged = 'omwSettingsChanged', - SettingSet = 'omwSettingsGlobalSet', - GroupRegistered = 'omwSettingsGroupRegistered', + SettingChanged = eventPrefix .. 'Changed', + SetValue = eventPrefix .. 'GlobalSetValue', + GroupRegistered = eventPrefix .. 'GroupRegistered', + RegisterGroup = eventPrefix .. 'RegisterGroup', + Subscribe = eventPrefix .. 'Subscribe', } local SCOPE = { @@ -27,124 +24,160 @@ local SCOPE = { SavePlayer = 'SavePlayer', } -local groups = storage.globalSection('OMW_Settings_Groups') -local saveGlobalSection = storage.globalSection('OMW_Settings_SaveGlobal') - -if isGlobalScript then - groups:removeOnExit() - saveGlobalSection:removeOnExit() +local function isPlayerScope(scope) + return scope == SCOPE.Player or scope == SCOPE.SavePlayer end -local savePlayerSection = nil -if isPlayerScript then - savePlayerSection = storage.playerSection('OMW_Setting_SavePlayer') - savePlayerSection:removeOnExit() +local function isSaveScope(scope) + return scope == SCOPE.SaveGlobal or scope == SCOPE.SavePlayer end -local scopes = { - [SCOPE.Global] = storage.globalSection('OMW_Setting_Global'), - [SCOPE.Player] = isPlayerScript and storage.playerSection('OMW_Setting_Player'), - [SCOPE.SaveGlobal] = saveGlobalSection, - [SCOPE.SavePlayer] = savePlayerSection, -} +local prefix = 'omw_settings_' +local settingsPattern = prefix .. 'settings_%s%s' -local function isGlobalScope(scope) - return scope == SCOPE.Global or scope == SCOPE.SaveGlobal +local groupsSection = storage.globalSection(prefix .. 'groups') +if groupsSection.removeOnExit then + groupsSection:removeOnExit() end -local function getSetting(groupKey, settingKey) - local group = groups:get(groupKey) - if not group then - error('Unknown group') +local function values(groupKey, scope) + local player = isPlayerScope(scope) + local save = isSaveScope(scope) + local sectionKey = settingsPattern:format(groupKey, save and '_save' or '') + local section + if player then + section = storage.playerSection and storage.playerSection(sectionKey) or nil + else + section = storage.globalSection(sectionKey) end - local setting = group.settings[settingKey] - if not setting then - error('Unknown setting') + if save and section and section.removeOnExit then + section:removeOnExit() end - return setting + return section end -local function getSettingValue(groupKey, settingKey) - local setting = getSetting(groupKey, settingKey) - local scopeSection = scopes[setting.scope] - if not scopeSection then - error(('Setting %s is not available in this context'):format(setting.key)) +local function saveScope(scope) + local saved = {} + for _, group in pairs(groupsSection:asTable()) do + saved[group.key] = values(group.key, scope):asTable() end - if not scopeSection:get(groupKey) then - scopeSection:set(groupKey, {}) + return saved +end + +local function loadScope(scope, saved) + if not saved then return end + for _, group in pairs(saved) do + values(group.key, scope):reset(saved[group.key]) end - return scopeSection:get(groupKey)[setting.key] or setting.default end -local function notifySettingChange(scope, event) - if isGlobalScope(scope) then - core.sendGlobalEvent(EVENTS.SettingChanged, event) - for _, a in ipairs(world.activeActors) do - if a.type == types.Player then - a:sendEvent(EVENTS.SettingChanged, event) - end - end - else - self:sendEvent(EVENTS.SettingChanged, event) +local function groupSubscribeEvent(groupKey) + return ('%sSubscribe%s'):format(eventPrefix, groupKey) +end + +local subscriptions = {} +local function handleSubscription(event) + if not subscriptions[event.groupKey] then + subscriptions[event.groupKey] = {} end + table.insert(subscriptions[event.groupKey], event.object or false) end -local function setSettingValue(groupKey, settingKey, value) - local setting = getSetting(groupKey, settingKey) +local function subscribe(self) + local groupKey = rawget(self, 'groupKey') local event = { - groupName = groupKey, - settingName = setting.key, - value = value, + groupKey = groupKey, + object = selfObject, } - if isPlayerScript and isGlobalScope(setting.scope) then - core.sendGlobalEvent(EVENTS.SettingSet, event) - return - end - - local scopeSection = scopes[setting.scope] - if not scopeSection:get(groupKey) then - scopeSection:set(groupKey, {}) + core.sendGlobalEvent(EVENTS.Subscribe, event) + if playerObject then + playerObject:sendEvent(EVENTS.Subscribe, event) end - local copy = scopeSection:getCopy(groupKey) - copy[setting.key] = value - scopeSection:set(groupKey, copy) - - notifySettingChange(setting.scope, event) + return groupSubscribeEvent(groupKey) end local groupMeta = { - __index = { - get = function(self, settingKey) - return getSettingValue(self.key, settingKey) - end, - set = function(self, settingKey, value) - setSettingValue(self.key, settingKey, value) - end, - onChange = function(self, callback) - table.insert(self.__callbacks, callback) - end, - __changed = function(self, settingKey, value) - for _, callback in ipairs(self.__callbacks) do - callback(settingKey, value) + __newindex = function(self, settingKey, value) + local group = groupsSection:get(rawget(self, 'groupKey')) + local setting = group.settings[settingKey] + if not setting then + error(('Setting %s does not exist'):format(settingKey)) + end + local section = values(group.key, setting.scope) + local event = { + groupKey = group.key, + settingKey = settingKey, + value = value, + } + if section.set then + section:set(settingKey, value) + if playerObject then + playerObject:sendEvent(EVENTS.SettingChanged, event) + else + core.sendGlobalEvent(EVENTS.SettingChanged, event) + end + if subscriptions[group.key] then + local eventKey = groupSubscribeEvent(group.key) + for _, object in ipairs(subscriptions[group.key]) do + if object then + object:sendEvent(eventKey, event) + else + core.sendGlobalEvent(eventKey, event) + end + end end - end, - }, + else + if isPlayerScope(setting.scope) then + error(("Can't change player scope setting %s from global scope"):format(settingKey)) + else + core.sendGlobalEvent(EVENTS.SetValue, event) + end + end + end, + __index = function(self, key) + if key == "subscribe" then return subscribe end + local settingKey = key + local group = groupsSection:get(rawget(self, 'groupKey')) + local setting = group.settings[settingKey] + if not setting then + error(('Unknown setting %s'):format(settingKey)) + end + local section = rawget(self, 'sections')[setting.scope] + if not section then + error(("Can't access setting %s from scope %s"):format(settingKey, setting.scope)) + end + return section:get(setting.key) or setting.default + end, } -local cachedGroups = {} -local function getGroup(groupKey) - if not cachedGroups[groupKey] then - cachedGroups[groupKey] = setmetatable({ - key = groupKey, - __callbacks = {}, - }, groupMeta) + +local function group(groupKey) + if not groupsSection:get(groupKey) then + print(("Settings group %s wasn't registered yet"):format(groupKey)) end - return cachedGroups[groupKey] + local s = {} + for _, scope in pairs(SCOPE) do + local section = values(groupKey, scope) + if section then + s[scope] = section + end + end + return setmetatable({ + groupKey = groupKey, + sections = s, + }, groupMeta) end return { - EVENTS = EVENTS, SCOPE = SCOPE, - scopes = scopes, - groups = groups, - getGroup = getGroup, + EVENTS = EVENTS, + isPlayerScope = isPlayerScope, + isSaveScope = isSaveScope, + values = values, + groups = function() + return groupsSection + end, + saveScope = saveScope, + loadScope = loadScope, + group = group, + handleSubscription = handleSubscription, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/global.lua b/files/builtin_scripts/scripts/omw/settings/global.lua index 148d21847a..ec14a07f73 100644 --- a/files/builtin_scripts/scripts/omw/settings/global.lua +++ b/files/builtin_scripts/scripts/omw/settings/global.lua @@ -1,30 +1,44 @@ local common = require('scripts.omw.settings.common') local register = require('scripts.omw.settings.register') +local world = require('openmw.world') +local types = require('openmw.types') -local saveScope = common.scopes[common.SCOPE.SaveGlobal] return { interfaceName = 'Settings', interface = { SCOPE = common.SCOPE, - getGroup = common.getGroup, + group = common.group, registerGroup = register.registerGroup, }, engineHandlers = { onLoad = function(saved) - common.groups:reset() - saveScope:reset(saved) + common.groups():reset() + common.loadScope(common.SCOPE.SaveGlobal, saved) end, onSave = function() - return saveScope:asTable() + common.saveScope(common.SCOPE.SaveGlobal) end, onPlayerAdded = register.onPlayerAdded, }, eventHandlers = { - [common.EVENTS.SettingChanged] = function(e) - common.getGroup(e.groupName):__changed(e.settingName, e.value) + [common.EVENTS.SetValue] = function(event) + local group = common.group(event.groupKey) + group[event.settingKey] = event.value end, - [common.EVENTS.SettingSet] = function(e) - common.getGroup(e.groupName):set(e.settingName, e.value) + [common.EVENTS.RegisterGroup] = function(options) + if common.groups():get(options.key) then return end + register.registerGroup(options) end, - } + [common.EVENTS.SettingChanged] = function(event) + local setting = common.groups():get(event.groupKey).settings[event.settingKey] + if common.isPlayerScope(setting.scope) then + for _, a in ipairs(world.activeActors) do + if a.type == types.Player and a:isValid() then + a:sendEvent(common.EVENTS.SettingChanged, event) + end + end + end + end, + [common.EVENTS.Subscribe] = common.handleSubscription, + }, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/interface.lua b/files/builtin_scripts/scripts/omw/settings/interface.lua deleted file mode 100644 index 4fb60d2c4d..0000000000 --- a/files/builtin_scripts/scripts/omw/settings/interface.lua +++ /dev/null @@ -1,51 +0,0 @@ -return function(player) - local core = require('openmw.core') - local types = require('openmw.types') - local world = not player and require('openmw.world') - - - local sections = require('scripts.omw.settings.sections') - local render = player and require('scripts.omw.settings.render') or nil - - local settingChangeEvent = 'omwSettingChanged' - local globalSetEvent = 'omwSettingGlobalSet' - local registerEvent = 'omwSettingGroupRegistered' - - local groups, scopes, SCOPE, isGlobal = sections.groups, sections.scopes, sections.SCOPE, sections.isGlobal - - - - local saveScope = scopes[player and SCOPE.SavePlayer or SCOPE.SaveGlobal] - return { - interfaceName = 'Settings', - interface = { - getGroup = getGroup, - SCOPE = SCOPE, - registerGroup = not player and require('scripts.omw.settings.register') or nil, - registerType = player and render.registerType or nil, - }, - engineHandlers = { - onLoad = function(saved) - if not player then groups:reset() end - saveScope:reset(saved) - end, - onSave = function() - return saveScope:asTable() - end, - }, - eventHandlers = { - [settingChangeEvent] = function(e) - getGroup(e.groupName):__changed(e.settingName, e.value) - end, - [globalSetEvent] = not player and function(e) - local setting = getSetting(e.groupName, e.settingName) - if isGlobal(setting.scope) then - setSettingValue(e.groupName, e.settingName, e.value) - else - error(('Unexpected Setting event for a non-global setting %s'):format(e.settingName)) - end - end or nil, - [registerEvent] = player and render.onGroupRegistered or nil, - } - } -end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index b4cf75b127..39d989d5cc 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,8 +1,10 @@ local common = require('scripts.omw.settings.common') local render = require('scripts.omw.settings.render') + local ui = require('openmw.ui') local async = require('openmw.async') local util = require('openmw.util') +local core = require('openmw.core') render.registerRenderer('text', function(value, set, arg) return { @@ -20,26 +22,27 @@ render.registerRenderer('text', function(value, set, arg) } end) -local saveScope = common.scopes[common.SCOPE.SavePlayer] return { interfaceName = 'Settings', interface = { SCOPE = common.SCOPE, - getGroup = common.getGroup, + group = common.group, registerRenderer = render.registerRenderer, + registerGroup = function(options) + core.sendGlobalEvent(common.EVENTS.RegisterGroup, options) + end, }, engineHandlers = { onLoad = function(saved) - saveScope:reset(saved) + common.loadScope(common.SCOPE.SavePlayer, saved) end, onSave = function() - return saveScope:asTable() + common.saveScope(common.SCOPE.SavePlayer) end, }, eventHandlers = { - [common.EVENTS.SettingChanged] = function(e) - common.getGroup(e.groupName):__changed(e.settingName, e.value) - end, [common.EVENTS.GroupRegistered] = render.onGroupRegistered, + [common.EVENTS.SettingChanged] = render.onSettingChanged, + [common.EVENTS.Subscribe] = common.handleSubscription, } } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua index ca41843e00..02a85cdeca 100644 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ b/files/builtin_scripts/scripts/omw/settings/register.lua @@ -3,11 +3,9 @@ local types = require('openmw.types') local common = require('scripts.omw.settings.common') -local groups, SCOPE = common.groups, common.SCOPE - local function validScope(scope) local valid = false - for _, v in pairs(SCOPE) do + for _, v in pairs(common.SCOPE) do if v == scope then valid = true break @@ -41,7 +39,7 @@ local function addSetting(settings, options) end settings[options.key] = { key = options.key, - scope = options.scope or SCOPE.Global, + scope = options.scope, default = options.default, renderer = options.renderer, argument = options.argument, @@ -70,14 +68,14 @@ local function validateGroupOptions(options) end local function registerGroup(options) + local groups = common.groups() validateGroupOptions(options) if groups:get(options.key) then - print(('Overwriting group %s'):format(options.key)) - end + error(('Duplicate group %s'):format(options.key)) + end local group = { key = options.key, localization = options.localization, - name = options.name, description = options.description, @@ -95,7 +93,7 @@ local function registerGroup(options) end local function onPlayerAdded(player) - for groupName in pairs(groups:asTable()) do + for groupName in pairs(common.groups():asTable()) do player:sendEvent(common.EVENTS.GroupRegistered, groupName) end end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 57368a6a15..bbbb8bfc6d 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -40,10 +40,10 @@ local function renderSetting(groupKey, setting, value) if not renderFunction then error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) end - local group = common.getGroup(groupKey) - value = value or group:get(setting.key) + local group = common.group(groupKey) + value = value or group[setting.key] local set = function(value) - group:set(setting.key, value) + group[setting.key] = value renderSetting(groupKey, setting, value) end local element = groupOptions[groupKey].element @@ -92,7 +92,7 @@ local function renderSetting(groupKey, setting, value) end local function renderGroup(groupKey) - local group = common.groups:get(groupKey) + local group = common.groups():get(groupKey) local element = groupOptions[groupKey].element local localization = groupOptions[groupKey].localization element.layout = { @@ -143,19 +143,27 @@ local function renderGroup(groupKey) end local function onGroupRegistered(groupKey) - local group = common.groups:get(groupKey) + local group = common.groups():get(groupKey) + local loc = core.l10n(group.localization) local options = { - name = groupKey, + name = loc(group.name), element = ui.create{}, searchHints = '', - localization = core.l10n(group.localization), + localization = loc, } groupOptions[groupKey] = options renderGroup(groupKey) ui.registerSettingsPage(options) end +local function onSettingChanged(event) + local group = common.groups():get(event.groupKey) + local setting = group.settings[event.settingKey] + renderSetting(event.groupKey, setting, event.value) +end + return { onGroupRegistered = onGroupRegistered, + onSettingChanged = onSettingChanged, registerRenderer = registerRenderer, } \ No newline at end of file From b899320e9fa9b330877ac6b865a497ca8f75df2d Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 27 Apr 2022 21:28:16 +0200 Subject: [PATCH 2399/2859] Use storage subscribe, unify groups into pages --- .../scripts/omw/settings/common.lua | 259 +++++++----------- .../scripts/omw/settings/global.lua | 39 +-- .../scripts/omw/settings/player.lua | 29 +- .../scripts/omw/settings/register.lua | 104 ------- .../scripts/omw/settings/render.lua | 211 ++++++++++---- 5 files changed, 276 insertions(+), 366 deletions(-) delete mode 100644 files/builtin_scripts/scripts/omw/settings/register.lua diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index bc01bc20b5..3ea462daf9 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -1,183 +1,126 @@ local storage = require('openmw.storage') -local core = require('openmw.core') -local types = require('openmw.types') -local selfObject -do - local success, result = pcall(function() return require('openmw.self') end) - selfObject = success and result or nil -end -local playerObject = selfObject and selfObject.type == types.Player and selfObject or nil - -local eventPrefix = 'omwSettings' -local EVENTS = { - SettingChanged = eventPrefix .. 'Changed', - SetValue = eventPrefix .. 'GlobalSetValue', - GroupRegistered = eventPrefix .. 'GroupRegistered', - RegisterGroup = eventPrefix .. 'RegisterGroup', - Subscribe = eventPrefix .. 'Subscribe', -} - -local SCOPE = { - Global = 'Global', - Player = 'Player', - SaveGlobal = 'SaveGlobal', - SavePlayer = 'SavePlayer', -} -local function isPlayerScope(scope) - return scope == SCOPE.Player or scope == SCOPE.SavePlayer -end - -local function isSaveScope(scope) - return scope == SCOPE.SaveGlobal or scope == SCOPE.SavePlayer -end +local contextSection = storage.playerSection or storage.globalSection +local groupSectionKey = 'OmwSettingGroups' +local groupSection = contextSection(groupSectionKey) +groupSection:removeOnExit() -local prefix = 'omw_settings_' -local settingsPattern = prefix .. 'settings_%s%s' - -local groupsSection = storage.globalSection(prefix .. 'groups') -if groupsSection.removeOnExit then - groupsSection:removeOnExit() -end - -local function values(groupKey, scope) - local player = isPlayerScope(scope) - local save = isSaveScope(scope) - local sectionKey = settingsPattern:format(groupKey, save and '_save' or '') - local section - if player then - section = storage.playerSection and storage.playerSection(sectionKey) or nil - else - section = storage.globalSection(sectionKey) +local function validateSettingOptions(options) + if type(options.key) ~= 'string' then + error('Setting must have a key') end - if save and section and section.removeOnExit then - section:removeOnExit() + if type(options.saveOnly) ~= 'boolean' then + error('Setting must be save only or not') end - return section -end - -local function saveScope(scope) - local saved = {} - for _, group in pairs(groupsSection:asTable()) do - saved[group.key] = values(group.key, scope):asTable() + if type(options.renderer) ~= 'string' then + error('Setting must have a renderer') + end + if type(options.name) ~= 'string' then + error('Setting must have a name localization key') + end + if type(options.description) ~= 'string' then + error('Setting must have a descripiton localization key') end - return saved end -local function loadScope(scope, saved) - if not saved then return end - for _, group in pairs(saved) do - values(group.key, scope):reset(saved[group.key]) +local function validateGroupOptions(options) + if type(options.key) ~= 'string' then + error('Group must have a key') + end + local conventionPrefix = "Settings" + if options.key:sub(1, conventionPrefix:len()) ~= conventionPrefix then + print(("Group key %s doesn't start with %s"):format(options.key, conventionPrefix)) + end + if type(options.page) ~= 'string' then + error('Group must belong to a page') + end + if type(options.l10n) ~= 'string' then + error('Group must have a localization context') + end + if type(options.name) ~= 'string' then + error('Group must have a name localization key') + end + if type(options.description) ~= 'string' then + error('Group must have a description localization key') + end + if type(options.settings) ~= 'table' then + error('Group must have a table of settings') + end + for _, opt in ipairs(options.settings) do + validateSettingOptions(opt) end end -local function groupSubscribeEvent(groupKey) - return ('%sSubscribe%s'):format(eventPrefix, groupKey) +local function registerSetting(options) + return { + key = options.key, + saveOnly = options.saveOnly, + default = options.default, + renderer = options.renderer, + argument = options.argument, + + name = options.name, + description = options.description, + } end -local subscriptions = {} -local function handleSubscription(event) - if not subscriptions[event.groupKey] then - subscriptions[event.groupKey] = {} +local function registerGroup(options) + validateGroupOptions(options) + if groupSection:get(options.key) then + error(('Group with key %s was already registered'):format(options.key)) end - table.insert(subscriptions[event.groupKey], event.object or false) -end + local group = { + key = options.key, + page = options.page, + l10n = options.l10n, + name = options.name, + description = options.description, -local function subscribe(self) - local groupKey = rawget(self, 'groupKey') - local event = { - groupKey = groupKey, - object = selfObject, + settings = {}, } - core.sendGlobalEvent(EVENTS.Subscribe, event) - if playerObject then - playerObject:sendEvent(EVENTS.Subscribe, event) + local valueSection = contextSection(options.key) + for _, opt in ipairs(options.settings) do + local setting = registerSetting(opt) + if group.settings[setting.key] then + error(('Duplicate setting key %s'):format(options.key)) + end + group.settings[setting.key] = setting + if not valueSection:get(setting.key) then + valueSection:set(setting.key, setting.default) + end end - return groupSubscribeEvent(groupKey) + groupSection:set(group.key, group) end -local groupMeta = { - __newindex = function(self, settingKey, value) - local group = groupsSection:get(rawget(self, 'groupKey')) - local setting = group.settings[settingKey] - if not setting then - error(('Setting %s does not exist'):format(settingKey)) - end - local section = values(group.key, setting.scope) - local event = { - groupKey = group.key, - settingKey = settingKey, - value = value, - } - if section.set then - section:set(settingKey, value) - if playerObject then - playerObject:sendEvent(EVENTS.SettingChanged, event) - else - core.sendGlobalEvent(EVENTS.SettingChanged, event) - end - if subscriptions[group.key] then - local eventKey = groupSubscribeEvent(group.key) - for _, object in ipairs(subscriptions[group.key]) do - if object then - object:sendEvent(eventKey, event) - else - core.sendGlobalEvent(eventKey, event) - end - end - end - else - if isPlayerScope(setting.scope) then - error(("Can't change player scope setting %s from global scope"):format(settingKey)) - else - core.sendGlobalEvent(EVENTS.SetValue, event) - end - end +return { + getSection = function(global, key) + return (global and storage.globalSection or storage.playerSection)(key) end, - __index = function(self, key) - if key == "subscribe" then return subscribe end - local settingKey = key - local group = groupsSection:get(rawget(self, 'groupKey')) - local setting = group.settings[settingKey] - if not setting then - error(('Unknown setting %s'):format(settingKey)) - end - local section = rawget(self, 'sections')[setting.scope] - if not section then - error(("Can't access setting %s from scope %s"):format(settingKey, setting.scope)) + setGlobalEvent = 'OMWSettingsGlobalSet', + groupSectionKey = groupSectionKey, + onLoad = function(saved) + if not saved then return end + for groupKey, settings in pairs(saved) do + local section = contextSection(groupKey) + for key, value in pairs(settings) do + section:set(key, value) + end end - return section:get(setting.key) or setting.default end, -} - -local function group(groupKey) - if not groupsSection:get(groupKey) then - print(("Settings group %s wasn't registered yet"):format(groupKey)) - end - local s = {} - for _, scope in pairs(SCOPE) do - local section = values(groupKey, scope) - if section then - s[scope] = section + onSave = function() + local saved = {} + for groupKey, group in pairs(groupSection:asTable()) do + local section = contextSection(groupKey) + saved[groupKey] = {} + for key, value in pairs(section:asTable()) do + if group.settings[key].saveOnly then + saved[groupKey][key] = value + end + end end - end - return setmetatable({ - groupKey = groupKey, - sections = s, - }, groupMeta) -end - -return { - SCOPE = SCOPE, - EVENTS = EVENTS, - isPlayerScope = isPlayerScope, - isSaveScope = isSaveScope, - values = values, - groups = function() - return groupsSection + groupSection:reset() + return saved end, - saveScope = saveScope, - loadScope = loadScope, - group = group, - handleSubscription = handleSubscription, + registerGroup = registerGroup, + validateGroupOptions = validateGroupOptions, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/global.lua b/files/builtin_scripts/scripts/omw/settings/global.lua index ec14a07f73..5818668b0e 100644 --- a/files/builtin_scripts/scripts/omw/settings/global.lua +++ b/files/builtin_scripts/scripts/omw/settings/global.lua @@ -1,44 +1,19 @@ +local storage = require('openmw.storage') + local common = require('scripts.omw.settings.common') -local register = require('scripts.omw.settings.register') -local world = require('openmw.world') -local types = require('openmw.types') return { interfaceName = 'Settings', interface = { - SCOPE = common.SCOPE, - group = common.group, - registerGroup = register.registerGroup, + registerGroup = common.registerGroup, }, engineHandlers = { - onLoad = function(saved) - common.groups():reset() - common.loadScope(common.SCOPE.SaveGlobal, saved) - end, - onSave = function() - common.saveScope(common.SCOPE.SaveGlobal) - end, - onPlayerAdded = register.onPlayerAdded, + onLoad = common.onLoad, + onSave = common.onSave, }, eventHandlers = { - [common.EVENTS.SetValue] = function(event) - local group = common.group(event.groupKey) - group[event.settingKey] = event.value - end, - [common.EVENTS.RegisterGroup] = function(options) - if common.groups():get(options.key) then return end - register.registerGroup(options) - end, - [common.EVENTS.SettingChanged] = function(event) - local setting = common.groups():get(event.groupKey).settings[event.settingKey] - if common.isPlayerScope(setting.scope) then - for _, a in ipairs(world.activeActors) do - if a.type == types.Player and a:isValid() then - a:sendEvent(common.EVENTS.SettingChanged, event) - end - end - end + [common.setGlobalEvent] = function(e) + storage.globalSection(e.groupKey):set(e.settingKey, e.value) end, - [common.EVENTS.Subscribe] = common.handleSubscription, }, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 39d989d5cc..14d5b12033 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,16 +1,15 @@ -local common = require('scripts.omw.settings.common') -local render = require('scripts.omw.settings.render') - local ui = require('openmw.ui') local async = require('openmw.async') local util = require('openmw.util') -local core = require('openmw.core') + +local common = require('scripts.omw.settings.common') +local render = require('scripts.omw.settings.render') render.registerRenderer('text', function(value, set, arg) return { type = ui.TYPE.TextEdit, props = { - size = util.vector2(arg and arg.size or 300, 30), + size = util.vector2(arg and arg.size or 150, 30), text = value, textColor = util.color.rgb(1, 1, 1), textSize = 15, @@ -25,24 +24,12 @@ end) return { interfaceName = 'Settings', interface = { - SCOPE = common.SCOPE, - group = common.group, + registerPage = render.registerPage, registerRenderer = render.registerRenderer, - registerGroup = function(options) - core.sendGlobalEvent(common.EVENTS.RegisterGroup, options) - end, + registerGroup = common.registerGroup, }, engineHandlers = { - onLoad = function(saved) - common.loadScope(common.SCOPE.SavePlayer, saved) - end, - onSave = function() - common.saveScope(common.SCOPE.SavePlayer) - end, + onLoad = common.onLoad, + onSave = common.onSave, }, - eventHandlers = { - [common.EVENTS.GroupRegistered] = render.onGroupRegistered, - [common.EVENTS.SettingChanged] = render.onSettingChanged, - [common.EVENTS.Subscribe] = common.handleSubscription, - } } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/register.lua b/files/builtin_scripts/scripts/omw/settings/register.lua deleted file mode 100644 index 02a85cdeca..0000000000 --- a/files/builtin_scripts/scripts/omw/settings/register.lua +++ /dev/null @@ -1,104 +0,0 @@ -local world = require('openmw.world') -local types = require('openmw.types') - -local common = require('scripts.omw.settings.common') - -local function validScope(scope) - local valid = false - for _, v in pairs(common.SCOPE) do - if v == scope then - valid = true - break - end - end - return valid -end - -local function validateSettingOptions(options) - if type(options.key) ~= 'string' then - error('Setting must have a key') - end - if not validScope(options.scope) then - error(('Invalid setting scope %s'):format(options.scope)) - end - if type(options.renderer) ~= 'string' then - error('Setting must have a renderer') - end - if type(options.name) ~= 'string' then - error('Setting must have a name localization key') - end - if type(options.description) ~= 'string' then - error('Setting must have a descripiton localization key') - end -end - -local function addSetting(settings, options) - validateSettingOptions(options) - if settings[options.key] then - error(('Duplicate setting key %s'):format(options.key)) - end - settings[options.key] = { - key = options.key, - scope = options.scope, - default = options.default, - renderer = options.renderer, - argument = options.argument, - - name = options.name, - description = options.description, - } -end - -local function validateGroupOptions(options) - if type(options.key) ~= 'string' then - error('Group must have a key') - end - if type(options.localization) ~= 'string' then - error('Group must have a localization context') - end - if type(options.name) ~= 'string' then - error('Group must have a name localization key') - end - if type(options.description) ~= 'string' then - error('Group must have a description localization key') - end - if type(options.settings) ~= 'table' then - error('Group must have a table of settings') - end -end - -local function registerGroup(options) - local groups = common.groups() - validateGroupOptions(options) - if groups:get(options.key) then - error(('Duplicate group %s'):format(options.key)) - end - local group = { - key = options.key, - localization = options.localization, - name = options.name, - description = options.description, - - settings = {}, - } - for _, opt in ipairs(options.settings) do - addSetting(group.settings, opt) - end - groups:set(options.key, group) - for _, a in ipairs(world.activeActors) do - if a.type == types.Player and a:isValid() then - a:sendEvent(common.EVENTS.GroupRegistered, options.key) - end - end -end - -local function onPlayerAdded(player) - for groupName in pairs(common.groups():asTable()) do - player:sendEvent(common.EVENTS.GroupRegistered, groupName) - end -end - -return { - registerGroup = registerGroup, - onPlayerAdded = onPlayerAdded, -} \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index bbbb8bfc6d..0d06214600 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -2,6 +2,7 @@ local ui = require('openmw.ui') local util = require('openmw.util') local async = require('openmw.async') local core = require('openmw.core') +local storage = require('openmw.storage') local common = require('scripts.omw.settings.common') @@ -10,8 +11,8 @@ local function registerRenderer(name, renderFunction) renderers[name] = renderFunction end -local groupOptions = {} - +local groups = {} +local pageOptions = {} local padding = function(size) return { @@ -20,36 +21,46 @@ local padding = function(size) } } end +local smallPadding = padding(10) +local bigPadding = padding(25) -local header = { +local pageHeader = { props = { textColor = util.color.rgb(1, 1, 1), textSize = 30, }, } - -local normal = { +local groupHeader = { props = { textColor = util.color.rgb(1, 1, 1), textSize = 25, }, } +local normal = { + props = { + textColor = util.color.rgb(1, 1, 1), + textSize = 20, + }, +} -local function renderSetting(groupKey, setting, value) +local function renderSetting(group, setting, value, global) local renderFunction = renderers[setting.renderer] if not renderFunction then - error(('Setting %s of %s has unknown renderer %s'):format(setting.key, groupKey, setting.renderer)) + error(('Setting %s of %s has unknown renderer %s'):format(setting.key, group.key, setting.renderer)) end - local group = common.group(groupKey) - value = value or group[setting.key] local set = function(value) - group[setting.key] = value - renderSetting(groupKey, setting, value) + if global then + core.sendGlobalEvent(common.setGlobalEvent, { + groupKey = group.key, + settingKey = setting.key, + value = value, + }) + else + storage.playerSection(group.key):set(setting.key, value) + end end - local element = groupOptions[groupKey].element - local localization = groupOptions[groupKey].localization - local settingsLayout = element.layout.content.settings - settingsLayout.content[setting.key] = { + local l10n = core.l10n(group.l10n) + return { name = setting.key, type = ui.TYPE.Flex, content = ui.content { @@ -65,12 +76,12 @@ local function renderSetting(groupKey, setting, value) type = ui.TYPE.Text, template = normal, props = { - text = localization(setting.name), + text = l10n(setting.name), }, }, - padding(10), + smallPadding, renderFunction(value, set, setting.argument), - padding(10), + smallPadding, { type = ui.TYPE.Text, template = normal, @@ -85,20 +96,20 @@ local function renderSetting(groupKey, setting, value) }, }, }, - padding(20), }, } - element:update() end -local function renderGroup(groupKey) - local group = common.groups():get(groupKey) - local element = groupOptions[groupKey].element - local localization = groupOptions[groupKey].localization - element.layout = { +local groupLayoutName = function(key, global) + return ('%s%s'):format(global and 'global_' or 'player_', key) +end + +local function renderGroup(group, global) + local l10n = core.l10n(group.l10n) + local layout = { + name = groupLayoutName(group.key, global), type = ui.TYPE.Flex, content = ui.content { - padding(10), { type = ui.TYPE.Flex, props = { @@ -110,60 +121,158 @@ local function renderGroup(groupKey) { name = 'name', type = ui.TYPE.Text, - template = header, + template = groupHeader, props = { - text = localization(group.name), + text = l10n(group.name), }, }, - padding(10), + smallPadding, { name = 'description', type = ui.TYPE.Text, template = normal, props = { - text = localization(group.description), + text = l10n(group.description), }, }, }, }, - padding(50), + smallPadding, { name = 'settings', type = ui.TYPE.Flex, content = ui.content{}, }, + bigPadding, }, } - local settingsContent = element.layout.content.settings.content + local settingsContent = layout.content.settings.content + local valueSection = common.getSection(global, group.key) for _, setting in pairs(group.settings) do - settingsContent:add({ name = setting.key }) - renderSetting(groupKey, setting) + settingsContent:add(renderSetting(group, setting, valueSection:get(setting.key), global)) end - element:update() + return layout end -local function onGroupRegistered(groupKey) - local group = common.groups():get(groupKey) - local loc = core.l10n(group.localization) - local options = { - name = loc(group.name), - element = ui.create{}, +local function renderPage(page) + local l10n = core.l10n(page.l10n) + local layout = { + name = page.key, + type = ui.TYPE.Flex, + content = ui.content { + smallPadding, + { + type = ui.TYPE.Flex, + props = { + horizontal = true, + align = ui.ALIGNMENT.Start, + arrange = ui.ALIGNMENT.Center, + }, + content = ui.content { + { + name = 'name', + type = ui.TYPE.Text, + template = pageHeader, + props = { + text = l10n(page.name), + }, + }, + smallPadding, + { + name = 'description', + type = ui.TYPE.Text, + template = normal, + props = { + text = l10n(page.description), + }, + }, + }, + }, + bigPadding, + { + name = 'groups', + type = ui.TYPE.Flex, + content = ui.content {}, + }, + }, + } + local groupsContent = layout.content.groups.content + for _, pageGroup in ipairs(groups[page.key]) do + local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) + groupsContent:add(renderGroup(group, pageGroup.global)) + end + return { + name = l10n(page.name), + element = ui.create(layout), searchHints = '', - localization = loc, } - groupOptions[groupKey] = options - renderGroup(groupKey) - ui.registerSettingsPage(options) end -local function onSettingChanged(event) - local group = common.groups():get(event.groupKey) - local setting = group.settings[event.settingKey] - renderSetting(event.groupKey, setting, event.value) +local function onSettingChanged(global) + return async:callback(function(groupKey, settingKey) + local group = common.getSection(global, common.groupSectionKey):get(groupKey) + if not pageOptions[group.page] then return end + + local element = pageOptions[group.page].element + local groupLayout = element.layout.content.groups.content[groupLayoutName(group.key, global)] + local settingsLayout = groupLayout.content.settings + local value = common.getSection(global, group.key):get(settingKey) + settingsLayout.content[settingKey] = renderSetting(group, group.settings[settingKey], value, global) + element:update() + end) +end +local function onGroupRegistered(global, key) + local group = common.getSection(global, common.groupSectionKey):get(key) + groups[group.page] = groups[group.page] or {} + table.insert(groups[group.page], { + key = group.key, + global = global, + }) + common.getSection(global, group.key):subscribe(onSettingChanged(global)) + + if not pageOptions[group.page] then return end + local element = pageOptions[group.page].element + local groupsLayout = element.layout.content.groups + -- TODO: make group order deterministic + groupsLayout.content:add(renderGroup(group, global)) + element:update() +end +local globalGroups = storage.globalSection(common.groupSectionKey) +for groupKey in pairs(globalGroups:asTable()) do + onGroupRegistered(true, groupKey) +end +globalGroups:subscribe(async:callback(function(_, key) + if key then onGroupRegistered(true, key) end +end)) +storage.playerSection(common.groupSectionKey):subscribe(async:callback(function(_, key) + if key then onGroupRegistered(false, key) end +end)) + +local function registerPage(options) + if type(options.key) ~= 'string' then + error('Page must have a key') + end + if type(options.l10n) ~= 'string' then + error('Page must have a localization context') + end + if type(options.name) ~= 'string' then + error('Page must have a name') + end + if type(options.description) ~= 'string' then + error('Page must have a description') + end + local page = { + key = options.key, + l10n = options.l10n, + name = options.name, + description = options.description, + } + groups[page.key] = groups[page.key] or {} + pageOptions[page.key] = renderPage(page) + ui.registerSettingsPage(pageOptions[page.key]) end return { - onGroupRegistered = onGroupRegistered, - onSettingChanged = onSettingChanged, + registerPage = registerPage, registerRenderer = registerRenderer, } \ No newline at end of file From d9b4871f0c7425026a0826ac7cde645ca8148056 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 27 Apr 2022 22:05:39 +0200 Subject: [PATCH 2400/2859] Apply Flex arrange to each child separately --- files/builtin_scripts/scripts/omw/settings/player.lua | 2 +- files/builtin_scripts/scripts/omw/settings/render.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 14d5b12033..434f7b69c5 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -13,7 +13,7 @@ render.registerRenderer('text', function(value, set, arg) text = value, textColor = util.color.rgb(1, 1, 1), textSize = 15, - textAlignV = ui.ALIGNMENT.Center, + textAlignV = ui.ALIGNMENT.End, }, events = { textChanged = async:callback(function(s) set(s) end), diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 0d06214600..403eae44a6 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -115,7 +115,7 @@ local function renderGroup(group, global) props = { horizontal = true, align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.Center, + arrange = ui.ALIGNMENT.End, }, content = ui.content { { @@ -166,7 +166,7 @@ local function renderPage(page) props = { horizontal = true, align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.Center, + arrange = ui.ALIGNMENT.End, }, content = ui.content { { From 9b27973479ce9914c2cf54412fd591e0d5f00495 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 28 Apr 2022 08:08:42 +0000 Subject: [PATCH 2401/2859] Deleted files/builtin_scripts/openmw_aux/settings.lua --- files/builtin_scripts/openmw_aux/settings.lua | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 files/builtin_scripts/openmw_aux/settings.lua diff --git a/files/builtin_scripts/openmw_aux/settings.lua b/files/builtin_scripts/openmw_aux/settings.lua deleted file mode 100644 index 8ce9929eaa..0000000000 --- a/files/builtin_scripts/openmw_aux/settings.lua +++ /dev/null @@ -1,4 +0,0 @@ -local common = require('scripts.omw.settings.common') -return { - group = common.group, -} \ No newline at end of file From 26154c85a163db5cf76057f4ae6517c5071913c8 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 29 Apr 2022 18:56:37 +0200 Subject: [PATCH 2402/2859] Check if options arguments are a table --- files/builtin_scripts/scripts/omw/settings/common.lua | 6 ++++++ files/builtin_scripts/scripts/omw/settings/render.lua | 3 +++ 2 files changed, 9 insertions(+) diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 3ea462daf9..96bfd747a2 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -6,6 +6,9 @@ local groupSection = contextSection(groupSectionKey) groupSection:removeOnExit() local function validateSettingOptions(options) + if type(options) ~= 'table' then + error('Setting options must be a table') + end if type(options.key) ~= 'string' then error('Setting must have a key') end @@ -24,6 +27,9 @@ local function validateSettingOptions(options) end local function validateGroupOptions(options) + if type(options) ~= 'table' then + error('Group options must be a table') + end if type(options.key) ~= 'string' then error('Group must have a key') end diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 403eae44a6..ba537b2099 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -249,6 +249,9 @@ storage.playerSection(common.groupSectionKey):subscribe(async:callback(function( end)) local function registerPage(options) + if type(options) ~= 'table' then + error('Page options must be a table') + end if type(options.key) ~= 'string' then error('Page must have a key') end From 5e90b1db0d4131ee6b52eb99c6a92d4267f1e525 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 29 Apr 2022 19:23:20 +0200 Subject: [PATCH 2403/2859] Define order of groups in a page --- .../scripts/omw/settings/common.lua | 4 +++ .../scripts/omw/settings/render.lua | 28 +++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 96bfd747a2..51e22b4465 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -40,6 +40,9 @@ local function validateGroupOptions(options) if type(options.page) ~= 'string' then error('Group must belong to a page') end + if type(options.order) ~= 'number' and type(options.order) ~= 'nil' then + error('Group order must be a number') + end if type(options.l10n) ~= 'string' then error('Group must have a localization context') end @@ -78,6 +81,7 @@ local function registerGroup(options) local group = { key = options.key, page = options.page, + order = options.order or 0, l10n = options.l10n, name = options.name, description = options.description, diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index ba537b2099..95b4bf2ff1 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -154,6 +154,12 @@ local function renderGroup(group, global) return layout end +local function pageGroupComparator(a, b) + return a.order < b.order or ( + a.order == b.order and a.key < b.key + ) +end + local function renderPage(page) local l10n = core.l10n(page.l10n) local layout = { @@ -197,7 +203,11 @@ local function renderPage(page) }, } local groupsContent = layout.content.groups.content - for _, pageGroup in ipairs(groups[page.key]) do + local pageGroups = groups[page.key] + local sortedGroups = {} + for i, v in ipairs(pageGroups) do sortedGroups[i] = v end + table.sort(sortedGroups, pageGroupComparator) + for _, pageGroup in ipairs(sortedGroups) do local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) groupsContent:add(renderGroup(group, pageGroup.global)) end @@ -224,17 +234,25 @@ end local function onGroupRegistered(global, key) local group = common.getSection(global, common.groupSectionKey):get(key) groups[group.page] = groups[group.page] or {} - table.insert(groups[group.page], { + local pageGroup = { key = group.key, global = global, - }) + order = group.order, + } + table.insert(groups[group.page], pageGroup) common.getSection(global, group.key):subscribe(onSettingChanged(global)) + local index = 1 + for _, g in ipairs(groups[group.page]) do + if pageGroupComparator(pageGroup, g) then + index = index + 1 + end + end + if not pageOptions[group.page] then return end local element = pageOptions[group.page].element local groupsLayout = element.layout.content.groups - -- TODO: make group order deterministic - groupsLayout.content:add(renderGroup(group, global)) + groupsLayout.content:insert(index, renderGroup(group, global)) element:update() end local globalGroups = storage.globalSection(common.groupSectionKey) From 1b62dda9f6648c15a166b50d17c74f01c5abc73c Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 29 Apr 2022 19:35:01 +0200 Subject: [PATCH 2404/2859] Generate search hints --- .../scripts/omw/settings/render.lua | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index 95b4bf2ff1..c072d20e94 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -11,6 +11,7 @@ local function registerRenderer(name, renderFunction) renderers[name] = renderFunction end +local pages = {} local groups = {} local pageOptions = {} @@ -160,6 +161,25 @@ local function pageGroupComparator(a, b) ) end +local function generateSearchHints(page) + local hints = {} + local l10n = core.l10n(page.l10n) + table.insert(hints, l10n(page.name)) + table.insert(hints, l10n(page.description)) + local pageGroups = groups[page.key] + for _, pageGroup in pairs(pageGroups) do + local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) + local l10n = core.l10n(group.l10n) + table.insert(hints, l10n(group.name)) + table.insert(hints, l10n(group.description)) + for _, setting in pairs(group.settings) do + table.insert(hints, l10n(setting.name)) + table.insert(hints, l10n(setting.description)) + end + end + return table.concat(hints, ' ') +end + local function renderPage(page) local l10n = core.l10n(page.l10n) local layout = { @@ -214,7 +234,7 @@ local function renderPage(page) return { name = l10n(page.name), element = ui.create(layout), - searchHints = '', + searchHints = generateSearchHints(page), } end @@ -242,18 +262,12 @@ local function onGroupRegistered(global, key) table.insert(groups[group.page], pageGroup) common.getSection(global, group.key):subscribe(onSettingChanged(global)) - local index = 1 - for _, g in ipairs(groups[group.page]) do - if pageGroupComparator(pageGroup, g) then - index = index + 1 - end + if not pages[group.page] then return end + local options = renderPage(pages[group.page]) + pageOptions[group.page].element:destroy() + for k, v in pairs(options) do + pageOptions[group.page][k] = v end - - if not pageOptions[group.page] then return end - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - groupsLayout.content:insert(index, renderGroup(group, global)) - element:update() end local globalGroups = storage.globalSection(common.groupSectionKey) for groupKey in pairs(globalGroups:asTable()) do @@ -288,6 +302,7 @@ local function registerPage(options) name = options.name, description = options.description, } + pages[page.key] = page groups[page.key] = groups[page.key] or {} pageOptions[page.key] = renderPage(page) ui.registerSettingsPage(pageOptions[page.key]) From cd3535cd63f074941e4c1772eb531f8bc3f1b99b Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 1 May 2022 16:57:33 +0200 Subject: [PATCH 2405/2859] Document Settings interface, add scripts to CMakeLists --- docs/source/generate_luadoc.sh | 1 + docs/source/reference/lua-scripting/api.rst | 24 ++-- .../lua-scripting/interface_settings.rst | 6 + .../reference/lua-scripting/overview.rst | 25 ++-- files/builtin_scripts/CMakeLists.txt | 4 + .../scripts/omw/settings/common.lua | 9 +- .../scripts/omw/settings/player.lua | 125 ++++++++++++++++++ 7 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_settings.rst diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index 5376aa0ab9..d6bbbe3634 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -68,3 +68,4 @@ $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/ai.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/mwui/init.lua +$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/settings/player.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 110771fbcf..a6009edf1a 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -27,6 +27,7 @@ Lua API reference interface_ai interface_camera interface_mwui + interface_settings iterables @@ -94,12 +95,19 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid **Interfaces of built-in scripts** -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -| Interface | Can be used | Description | -+=========================================================+====================+===============================================================+ -|:ref:`AI ` | by local scripts | | Control basic AI of NPCs and creatures. | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`Camera ` | by player scripts | | Allows to alter behavior of the built-in camera script | -| | | | without overriding the script completely. | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ +.. list-table:: + :widths: 20 20 60 + * - Interface + - Can be used + - Description + * - :ref:`AI ` + - by local scripts + - Control basic AI of NPCs and creatures. + * - :ref:`Camera ` + - by player scripts + - | Allows to alter behavior of the built-in camera script + | without overriding the script completely. + * - :ref:`Settings ` + - by player and global scripts + - Save, display and track changes of setting values. diff --git a/docs/source/reference/lua-scripting/interface_settings.rst b/docs/source/reference/lua-scripting/interface_settings.rst new file mode 100644 index 0000000000..cd1994ccfa --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_settings.rst @@ -0,0 +1,6 @@ +Interface Settings +================== + +.. raw:: html + :file: generated_html/scripts_omw_settings_player.html + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 91c0a071e0..e21469a53f 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -460,15 +460,22 @@ The order in which the scripts are started is important. So if one mod should ov **Interfaces of built-in scripts** -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -| Interface | Can be used | Description | -+=========================================================+====================+===============================================================+ -|:ref:`AI ` | by local scripts | | Control basic AI of NPCs and creatures. | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`Camera ` | by player scripts | | Allows to alter behavior of the built-in camera script | -| | | | without overriding the script completely. | -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ - +.. list-table:: + :widths: 20 20 60 + + * - Interface + - Can be used + - Description + * - :ref:`AI ` + - by local scripts + - Control basic AI of NPCs and creatures. + * - :ref:`Camera ` + - by player scripts + - | Allows to alter behavior of the built-in camera script + | without overriding the script completely. + * - :ref:`Settings ` + - by player and global scripts + - Save, display and track changes of setting values. Event system ============ diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index a3ba1f5149..318e389585 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -17,6 +17,10 @@ set(LUA_BUILTIN_FILES scripts/omw/console/player.lua scripts/omw/console/global.lua scripts/omw/console/local.lua + scripts/omw/settings/player.lua + scripts/omw/settings/global.lua + scripts/omw/settings/common.lua + scripts/omw/settings/render.lua l10n/Calendar/en.yaml diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 51e22b4465..1e87dfd62b 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -12,8 +12,8 @@ local function validateSettingOptions(options) if type(options.key) ~= 'string' then error('Setting must have a key') end - if type(options.saveOnly) ~= 'boolean' then - error('Setting must be save only or not') + if type(options.permanentStorage) ~= 'boolean' then + error('Setting must have a permanentStorage flag') end if type(options.renderer) ~= 'string' then error('Setting must have a renderer') @@ -63,7 +63,7 @@ end local function registerSetting(options) return { key = options.key, - saveOnly = options.saveOnly, + permanentStorage = options.permanentStorage, default = options.default, renderer = options.renderer, argument = options.argument, @@ -123,7 +123,7 @@ return { local section = contextSection(groupKey) saved[groupKey] = {} for key, value in pairs(section:asTable()) do - if group.settings[key].saveOnly then + if not group.settings[key].permanentStorage then saved[groupKey][key] = value end end @@ -132,5 +132,4 @@ return { return saved end, registerGroup = registerGroup, - validateGroupOptions = validateGroupOptions, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 434f7b69c5..612ba3ec7b 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -21,11 +21,136 @@ render.registerRenderer('text', function(value, set, arg) } end) +--- +-- @type PageOptions +-- @field #string key A unique key +-- @field #string l10n A localization context (an argument of core.l10n) +-- @field #string name A key from the localization context +-- @field #string description A key from the localization context + +--- +-- @type GroupOptions +-- @field #string key A unique key, starts with "Settings" by convention +-- @field #string l10n A localization context (an argument of core.l10n) +-- @field #string name A key from the localization context +-- @field #string description A key from the localization context +-- @field #string page Key of a page which will contain this group +-- @field #number order Groups within the same page are sorted by this number, or their key for equal values. +-- Defaults to 0. +-- @field #list<#SettingOptions> settings A [iterables#List](iterables.html#List) of #SettingOptions + +--- +-- @type SettingOptions +-- @field #string key A unique key +-- @field #string name A key from the localization context +-- @field #string description A key from the localization context +-- @field default A default value +-- @field #string renderer A renderer key +-- @field argument An argument for the renderer +-- @field #boolean permanentStorage Whether the setting should is stored in permanent storage, or in the save file + return { interfaceName = 'Settings', + --- + -- @module Settings + -- @usage + -- -- In a player script + -- local storage = require('openmw.storage') + -- local I = require('openmw.interfaces') + -- I.Settings.registerGroup({ + -- key = 'SettingsPlayerMyMod', + -- page = 'MyPage', + -- l10n = 'mymod', + -- name = 'modName', + -- description = 'modDescription', + -- settings = { + -- { + -- key = 'Greeting', + -- renderer = 'text', + -- name = 'greetingName', + -- description = 'greetingDescription', + -- default = 'Hello, world!', + -- argument = { + -- size = 200, + -- }, + -- }, + -- }, + -- }) + -- local playerSettings = storage.playerSection('SettingsPlayerMyMod') + -- -- access a setting page registered by a global script + -- local globalSettings = storage.globalSection('SettingsGlobalMyMod') interface = { + --- + -- @field [parent=#Settings] #string version + version = 0, + --- + -- @function [parent=#Settings] registerPage Register a page to be displayed in the settings menu, + -- only available in player scripts + -- @param #PageOptions options + -- @usage + -- I.Settings.registerPage({ + -- key = 'MyModName', + -- l10n = 'MyModName', + -- name = 'MyModName', + -- description = 'MyModDescription', + -- })--- registerPage = render.registerPage, + --- + -- @function [parent=#Settings] registerRenderer Register a renderer, + -- only avaialable in player scripts + -- @param #string key + -- @param #function renderer A renderer function, receives setting's value, + -- a function to change it and an argument from the setting options + -- @usage + -- I.Settings.registerRenderer('text', function(value, set, arg) + -- return { + -- type = ui.TYPE.TextEdit, + -- props = { + -- size = util.vector2(arg and arg.size or 150, 30), + -- text = value, + -- textColor = util.color.rgb(1, 1, 1), + -- textSize = 15, + -- textAlignV = ui.ALIGNMENT.End, + -- }, + -- events = { + -- textChanged = async:callback(function(s) set(s) end), + -- }, + -- } + -- end) registerRenderer = render.registerRenderer, + --- + -- @function [parent=#Settings] registerGroup Register a group to be attached to a page, + -- available both in player and global scripts + -- @param #GroupOptions options + -- @usage + -- I.Settings.registerGroup { + -- key = 'SettingsTest', + -- page = 'test', + -- l10n = 'test', + -- name = 'Player', + -- description = 'Player settings group', + -- settings = { + -- { + -- key = 'Greeting', + -- saveOnly = true, + -- default = 'Hi', + -- renderer = 'text', + -- argument = { + -- size = 200, + -- }, + -- name = 'Text Input', + -- description = 'Short text input', + -- }, + -- { + -- key = 'Key', + -- saveOnly = false, + -- default = input.KEY.LeftAlt, + -- renderer = 'keybind', + -- name = 'Key', + -- description = 'Bind Key', + -- }, + -- } + -- } registerGroup = common.registerGroup, }, engineHandlers = { From 6a97a21fbd020a02bff074abc4ca1bcc26c06b58 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 8 May 2022 13:09:02 +0200 Subject: [PATCH 2406/2859] [Lua] Fix stupid bug in tostring for ESM::Weapon and ESM::Door --- apps/openmw/mwlua/types/door.cpp | 3 +-- apps/openmw/mwlua/types/weapon.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index 5c13e27f9e..3b5e9a29cc 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -45,8 +45,7 @@ namespace MWLua [](const Object& obj) -> const ESM::Door* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Door* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Door"); - record[sol::meta_function::to_string] = sol::readonly_property( - [](const ESM::Door& rec) -> std::string { return "ESM3_Door[" + rec.mId + "]"; }); + record[sol::meta_function::to_string] = [](const ESM::Door& rec) -> std::string { return "ESM3_Door[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mModel; }); diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index 746536cc69..3b6265697e 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -38,8 +38,7 @@ namespace MWLua [](const Object& obj) -> const ESM::Weapon* { return obj.ptr().get()->mBase; }, [store](const std::string& recordId) -> const ESM::Weapon* { return store->find(recordId); }); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Weapon"); - record[sol::meta_function::to_string] = sol::readonly_property( - [](const ESM::Weapon& rec) -> std::string { return "ESM3_Weapon[" + rec.mId + "]"; }); + record[sol::meta_function::to_string] = [](const ESM::Weapon& rec) -> std::string { return "ESM3_Weapon[" + rec.mId + "]"; }; record["id"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mId; }); record["name"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mModel; }); From 8473336b06e9128db266c30c26999e2d97fe1d16 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 19:56:10 +0200 Subject: [PATCH 2407/2859] Remove redundant virtual functions --- apps/openmw/mwbase/statemanager.hpp | 2 -- apps/openmw/mwbase/windowmanager.hpp | 2 -- apps/openmw/mwbase/world.hpp | 1 - apps/openmw/mwgui/windowmanagerimp.hpp | 2 +- apps/openmw/mwstate/statemanagerimp.hpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 6 files changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index 0537ff02d2..c18db4190d 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -53,8 +53,6 @@ namespace MWBase /// /// \param bypass Skip new game mechanics. - virtual void endGame() = 0; - virtual void resumeGame() = 0; virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 45ea43daa8..b0076b02f2 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -282,8 +282,6 @@ namespace MWBase /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; - virtual void loadUserFonts() = 0; - virtual Loading::Listener* getLoadingScreen() = 0; /// Should the cursor be visible? diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 899be7dfb9..4aaf84c126 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -623,7 +623,6 @@ namespace MWBase virtual bool isPlayerInJail() const = 0; virtual void rest(double hours) = 0; - virtual void rechargeItems(double duration, bool activeOnly) = 0; virtual void setPlayerTraveling(bool traveling) = 0; virtual bool isPlayerTraveling() const = 0; diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 9a7ea2f1a7..9a4de2b33f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -142,7 +142,7 @@ namespace MWGui void setStore (const MWWorld::ESMStore& store); void initUI(); - void loadUserFonts() override; + void loadUserFonts(); Loading::Listener* getLoadingScreen() override; diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 273a3eb3bc..3da71e8401 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -46,7 +46,7 @@ namespace MWState /// /// \param bypass Skip new game mechanics. - void endGame() override; + void endGame(); void resumeGame() override; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 09bae6653d..d5ff82f52d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -587,7 +587,7 @@ namespace MWWorld ///< check if the player is allowed to rest void rest(double hours) override; - void rechargeItems(double duration, bool activeOnly) override; + void rechargeItems(double duration, bool activeOnly); /// \todo Probably shouldn't be here MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override; From f03360b66641cbb572b72641b5d57c108b902717 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 20:12:17 +0200 Subject: [PATCH 2408/2859] Move RenderingManager::update call to World::update There is no need to do that in Scene::update and pass paused argument there. --- apps/openmw/mwworld/scene.cpp | 4 +--- apps/openmw/mwworld/scene.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 +++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f24316045b..98bfc514ed 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -295,7 +295,7 @@ namespace MWWorld mPhysics->updateScale(ptr); } - void Scene::update (float duration, bool paused) + void Scene::update(float duration) { if (mChangeCellGridRequest.has_value()) { @@ -306,8 +306,6 @@ namespace MWWorld mPreloader->updateCache(mRendering.getReferenceTime()); preloadCells(duration); - - mRendering.update (duration, paused); } void Scene::unloadCell(CellStore* cell) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 464088f223..371c3cbbc5 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -164,7 +164,7 @@ namespace MWWorld void markCellAsUnchanged(); - void update (float duration, bool paused); + void update(float duration); void addObjectToScene (const Ptr& ptr); ///< Add an object that already exists in the world model to the scene. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ff343e0828..1ba27ac8b6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1849,7 +1849,9 @@ namespace MWWorld mPhysics->debugDraw(); - mWorldScene->update (duration, paused); + mWorldScene->update(duration); + + mRendering->update(duration, paused); updateSoundListener(); From 9320fb50abede4e09ca4ed4f77c35cb4f74693b8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 20:36:25 +0200 Subject: [PATCH 2409/2859] Remove redundant MWBase::Environment::get().getWorld() calls --- apps/openmw/mwworld/cellutils.hpp | 20 +++++++++++ apps/openmw/mwworld/worldimp.cpp | 60 +++++++++++++------------------ 2 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 apps/openmw/mwworld/cellutils.hpp diff --git a/apps/openmw/mwworld/cellutils.hpp b/apps/openmw/mwworld/cellutils.hpp new file mode 100644 index 0000000000..c699982af5 --- /dev/null +++ b/apps/openmw/mwworld/cellutils.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_MWWORLD_CELLUTILS_H +#define OPENMW_MWWORLD_CELLUTILS_H + +#include +#include + +#include + +namespace MWWorld +{ + inline ESM::CellId::CellIndex positionToIndex(float x, float y) + { + return { + static_cast(std::floor(x / Constants::CellSizeInUnits)), + static_cast(std::floor(y / Constants::CellSizeInUnits)) + }; + } +} + +#endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1ba27ac8b6..c7d253b4c5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -78,6 +78,7 @@ #include "contentloader.hpp" #include "esmloader.hpp" +#include "cellutils.hpp" namespace MWWorld { @@ -1276,11 +1277,10 @@ namespace MWWorld MWWorld::Ptr World::moveObject(const Ptr& ptr, const osg::Vec3f& position, bool movePhysics, bool moveToActive) { - int cellX, cellY; - positionToIndex(position.x(), position.y(), cellX, cellY); + const auto index = MWWorld::positionToIndex(position.x(), position.y()); CellStore* cell = ptr.getCell(); - CellStore* newCell = getExterior(cellX, cellY); + CellStore* newCell = getExterior(index.mX, index.mY); bool isCellActive = getPlayerPtr().isInCell() && getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell); if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor())) @@ -1518,8 +1518,9 @@ namespace MWWorld void World::positionToIndex (float x, float y, int &cellX, int &cellY) const { - cellX = static_cast(std::floor(x / Constants::CellSizeInUnits)); - cellY = static_cast(std::floor(y / Constants::CellSizeInUnits)); + const auto index = MWWorld::positionToIndex(x, y); + cellX = index.mX; + cellY = index.mY; } void World::queueMovement(const Ptr &ptr, const osg::Vec3f &velocity) @@ -2074,11 +2075,6 @@ namespace MWWorld struct GetDoorMarkerVisitor { - GetDoorMarkerVisitor(std::vector& out) - : mOut(out) - { - } - std::vector& mOut; bool operator()(const MWWorld::Ptr& ptr) @@ -2104,11 +2100,7 @@ namespace MWWorld else { cellid.mPaged = true; - MWBase::Environment::get().getWorld()->positionToIndex( - ref.mRef.getDoorDest().pos[0], - ref.mRef.getDoorDest().pos[1], - cellid.mIndex.mX, - cellid.mIndex.mY); + cellid.mIndex = MWWorld::positionToIndex(ref.mRef.getDoorDest().pos[0], ref.mRef.getDoorDest().pos[1]); } newMarker.dest = cellid; @@ -2124,7 +2116,7 @@ namespace MWWorld void World::getDoorMarkers (CellStore* cell, std::vector& out) { - GetDoorMarkerVisitor visitor(out); + GetDoorMarkerVisitor visitor {out}; cell->forEachType(visitor); } @@ -2210,9 +2202,8 @@ namespace MWWorld throw std::runtime_error("copyObjectToCell(): cannot copy object to null cell"); if (cell->isExterior()) { - int cellX, cellY; - positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY); - cell = mCells.getExterior(cellX, cellY); + const auto index = MWWorld::positionToIndex(pos.pos[0], pos.pos[1]); + cell = mCells.getExterior(index.mX, index.mY); } MWWorld::Ptr dropped = @@ -2819,10 +2810,9 @@ namespace MWWorld // door to exterior if (door->getDestCell().empty()) { - int x, y; ESM::Position doorDest = door->getDoorDest(); - positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y); - source = getExterior(x, y); + const auto index = MWWorld::positionToIndex(doorDest.pos[0], doorDest.pos[1]); + source = getExterior(index.mX, index.mY); } // door to interior else @@ -3154,7 +3144,7 @@ namespace MWWorld const osg::Vec3f sourcePos = worldPos + orient * osg::Vec3f(0,-1,0) * 64.f; // Early out if the launch position is underwater - bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); + bool underwater = isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); if (underwater) { MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); @@ -3415,15 +3405,12 @@ namespace MWWorld struct AddDetectedReferenceVisitor { - AddDetectedReferenceVisitor(std::vector& out, const Ptr& detector, World::DetectionType type, float squaredDist) - : mOut(out), mDetector(detector), mSquaredDist(squaredDist), mType(type) - { - } - std::vector& mOut; Ptr mDetector; float mSquaredDist; World::DetectionType mType; + const MWWorld::ESMStore& mStore; + bool operator() (const MWWorld::Ptr& ptr) { if ((ptr.getRefData().getPosition().asVec3() - mDetector.getRefData().getPosition().asVec3()).length2() >= mSquaredDist) @@ -3439,14 +3426,13 @@ namespace MWWorld // but ignore containers without resolved content if (isContainer && ptr.getRefData().getCustomData() == nullptr) { - const auto& store = MWBase::Environment::get().getWorld()->getStore(); for(const auto& containerItem : ptr.get()->mBase->mInventory.mList) { if(containerItem.mCount) { try { - ManualRef ref(store, containerItem.mItem, containerItem.mCount); + ManualRef ref(mStore, containerItem.mItem, containerItem.mCount); if(needToAdd(ref.getPtr(), mDetector)) { mOut.push_back(ptr); @@ -3521,7 +3507,7 @@ namespace MWWorld dist = feetToGameUnits(dist); - AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist); + AddDetectedReferenceVisitor visitor {out, ptr, dist * dist, type, mStore}; for (CellStore* cellStore : mWorldScene->getActiveCells()) { @@ -3701,10 +3687,9 @@ namespace MWWorld static int iNumberCreatures = mStore.get().find("iNumberCreatures")->mValue.getInteger(); int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); for (int i=0; imoveObject(ptr, origPos.asVec3()); - MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.asRotationVec3()); + mWorld.moveObject(ptr, origPos.asVec3()); + mWorld.rotateObject(ptr, origPos.asRotationVec3()); ptr.getClass().adjustPosition(ptr, true); } return true; } }; + void World::resetActors() { for (CellStore* cellstore : mWorldScene->getActiveCells()) { - ResetActorsVisitor visitor; + ResetActorsVisitor visitor {*this}; cellstore->forEach(visitor); } } From b32a787cd86f054ba5a601797af51c786d9c7975 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 21:03:02 +0200 Subject: [PATCH 2410/2859] Add explicit dependency to World from Scene To avoid redundant MWBase::Environment::get().getWorld() calls and virtual calls. --- apps/openmw/mwbase/world.hpp | 4 +- apps/openmw/mwworld/scene.cpp | 129 +++++++++++++++---------------- apps/openmw/mwworld/scene.hpp | 4 +- apps/openmw/mwworld/worldimp.cpp | 6 +- apps/openmw/mwworld/worldimp.hpp | 4 +- 5 files changed, 74 insertions(+), 73 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4aaf84c126..f78befe470 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -273,7 +273,7 @@ namespace MWBase virtual float getDistanceToFacedObject() = 0; - virtual float getMaxActivationDistance() = 0; + virtual float getMaxActivationDistance() const = 0; /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to @@ -548,7 +548,7 @@ namespace MWBase const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; virtual void updateProjectilesCasters() = 0; - virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0; + virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) const = 0; virtual const std::vector& getContentFiles() const = 0; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 98bfc514ed..0da47a9a74 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -40,6 +40,7 @@ #include "cellvisitors.hpp" #include "cellstore.hpp" #include "cellpreloader.hpp" +#include "worldimp.hpp" namespace { @@ -97,7 +98,7 @@ namespace return model; } - void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, + void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) @@ -123,7 +124,7 @@ namespace rendering.addWaterRippleEmitter(ptr); // Restore effect particles - MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + world.applyLoopingParticles(ptr); if (!model.empty()) ptr.getClass().insertObject(ptr, model, rotation, physics); @@ -131,7 +132,8 @@ namespace MWBase::Environment::get().getLuaManager()->objectAddedToScene(ptr); } - void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) + void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const MWPhysics::PhysicsSystem& physics, + DetourNavigator::Navigator& navigator) { if (const auto object = physics.getObject(ptr)) { @@ -145,7 +147,7 @@ namespace const auto center = (aabbMax + aabbMin) * 0.5f; - const auto distanceFromDoor = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 0.5f; + const auto distanceFromDoor = world.getMaxActivationDistance() * 0.5f; const auto toPoint = aabbMax.x() - aabbMin.x() < aabbMax.y() - aabbMin.y() ? btVector3(distanceFromDoor, 0, 0) : btVector3(0, distanceFromDoor, 0); @@ -183,7 +185,7 @@ namespace } else if (physics.getActor(ptr)) { - navigator.addAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr)); + navigator.addAgent(world.getPathfindingHalfExtents(ptr)); } } @@ -318,7 +320,6 @@ namespace MWWorld ListAndResetObjectsVisitor visitor; cell->forEach(visitor); - const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { if (const auto object = mPhysics->getObject(ptr)) @@ -329,7 +330,7 @@ namespace MWWorld } else if (mPhysics->getActor(ptr)) { - mNavigator.removeAgent(world->getPathfindingHalfExtents(ptr)); + mNavigator.removeAgent(mWorld.getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); mPhysics->remove(ptr); } @@ -350,17 +351,17 @@ namespace MWWorld if (cell->getCell()->hasWater()) mNavigator.removeWater(osg::Vec2i(cellX, cellY)); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + if (const auto pathgrid = mWorld.getStore().get().search(*cell->getCell())) mNavigator.removePathgrid(*pathgrid); - mNavigator.update(world->getPlayerPtr().getRefData().getPosition().asVec3()); + mNavigator.update(mWorld.getPlayerPtr().getRefData().getPosition().asVec3()); MWBase::Environment::get().getMechanicsManager()->drop (cell); mRendering.removeCell(cell); MWBase::Environment::get().getWindowManager()->removeCell(cell); - MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell); + mWorld.getLocalScripts().clearCell (cell); MWBase::Environment::get().getSoundManager()->stopSound (cell); mActiveCells.erase(cell); @@ -375,8 +376,6 @@ namespace MWWorld Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - const auto world = MWBase::Environment::get().getWorld(); - const int cellX = cell->getCell()->getGridX(); const int cellY = cell->getCell()->getGridY(); @@ -423,12 +422,12 @@ namespace MWWorld } } - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + if (const auto pathgrid = mWorld.getStore().get().search(*cell->getCell())) mNavigator.addPathgrid(*cell->getCell(), *pathgrid); // register local scripts // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + mWorld.getLocalScripts().addCell (cell); if (respawn) cell->respawn(); @@ -459,7 +458,7 @@ namespace MWWorld else mPhysics->disableWater(); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const auto player = mWorld.getPlayerPtr(); // The player is loaded before the scene and by default it is grounded, with the scene fully loaded, we validate and correct this. if (player.mCell == cell) // Only run once, during initial cell load. @@ -498,14 +497,14 @@ namespace MWWorld if (currentGridCenter) { float centerX, centerY; - MWBase::Environment::get().getWorld()->indexToPosition(currentGridCenter->x(), currentGridCenter->y(), centerX, centerY, true); + mWorld.indexToPosition(currentGridCenter->x(), currentGridCenter->y(), centerX, centerY, true); float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold if (distance <= maxDistance) return *currentGridCenter; } osg::Vec2i newCenter; - MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newCenter.x(), newCenter.y()); + mWorld.positionToIndex(pos.x(), pos.y(), newCenter.x(), newCenter.y()); return newCenter; } @@ -559,7 +558,7 @@ namespace MWWorld mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; - const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector> + const auto cellsToLoad = [&] (CellStoreCollection& collection, int range) -> std::vector> { std::vector> cellsPositionsToLoad; for (int x = playerCellX - range; x <= playerCellX + range; ++x) @@ -568,7 +567,7 @@ namespace MWWorld { if (!isCellInCollection(x, y, collection)) { - refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + refsToLoad += mWorld.getExterior(x, y)->count(); cellsPositionsToLoad.emplace_back(x, y); } } @@ -624,12 +623,12 @@ namespace MWWorld { if (!isCellInCollection(x, y, mActiveCells)) { - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); + CellStore *cell = mWorld.getExterior(x, y); loadCell(cell, loadingListener, changeEvent, pos); } } - CellStore* current = MWBase::Environment::get().getWorld()->getExterior(playerCellX, playerCellY); + CellStore* current = mWorld.getExterior(playerCellX, playerCellY); MWBase::Environment::get().getWindowManager()->changeCell(current); if (changeEvent) @@ -645,7 +644,7 @@ namespace MWWorld mRendering.getResourceSystem()->setExpiryDelay(1.f); - const MWWorld::Store &cells = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store &cells = mWorld.getStore().get(); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -657,7 +656,7 @@ namespace MWWorld { loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); + CellStore *cell = mWorld.getExterior(it->mData.mX, it->mData.mY); loadCell(cell, nullptr, false, osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits); auto iter = mActiveCells.begin(); @@ -690,7 +689,7 @@ namespace MWWorld mRendering.getResourceSystem()->setExpiryDelay(1.f); - const MWWorld::Store &cells = MWBase::Environment::get().getWorld()->getStore().get(); + const MWWorld::Store &cells = mWorld.getStore().get(); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -702,9 +701,9 @@ namespace MWWorld { loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); - CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); + CellStore *cell = mWorld.getInterior(it->mName); ESM::Position position; - MWBase::Environment::get().getWorld()->findInteriorPosition(it->mName, position); + mWorld.findInteriorPosition(it->mName, position); loadCell(cell, nullptr, false, position.asVec3()); auto iter = mActiveCells.begin(); @@ -737,16 +736,16 @@ namespace MWWorld mRendering.enableTerrain(cell->isExterior()); - MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr old = world->getPlayerPtr(); - world->getPlayer().setCell(cell); + MWWorld::Ptr old = mWorld.getPlayerPtr(); + mWorld.getPlayer().setCell(cell); - MWWorld::Ptr player = world->getPlayerPtr(); + MWWorld::Ptr player = mWorld.getPlayerPtr(); mRendering.updatePlayerPtr(player); - if (adjustPlayerPos) { - world->moveObject(player, pos.asVec3()); - world->rotateObject(player, pos.asRotationVec3()); + if (adjustPlayerPos) + { + mWorld.moveObject(player, pos.asVec3()); + mWorld.rotateObject(player, pos.asRotationVec3()); player.getClass().adjustPosition(player, true); } @@ -756,14 +755,15 @@ namespace MWWorld mPhysics->updatePtr(old, player); - world->adjustSky(); + mWorld.adjustSky(); mLastPlayerPos = player.getRefData().getPosition().asVec3(); } - Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, + Scene::Scene(MWWorld::World& world, MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, DetourNavigator::Navigator& navigator) - : mCurrentCell (nullptr), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator) + : mCurrentCell (nullptr), mCellChanged (false) + , mWorld(world), mPhysics(physics), mRendering(rendering), mNavigator(navigator) , mCellLoadingThreshold(1024.f) , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) , mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells")) @@ -804,7 +804,7 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { - CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); + CellStore *cell = mWorld.getInterior(cellName); bool useFading = (mCurrentCell != nullptr); if (useFading) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); @@ -816,12 +816,11 @@ namespace MWWorld if(mCurrentCell != nullptr && *mCurrentCell == *cell) { - MWBase::World *world = MWBase::Environment::get().getWorld(); - world->moveObject(world->getPlayerPtr(), position.asVec3()); - world->rotateObject(world->getPlayerPtr(), position.asRotationVec3()); + mWorld.moveObject(mWorld.getPlayerPtr(), position.asVec3()); + mWorld.rotateObject(mWorld.getPlayerPtr(), position.asRotationVec3()); if (adjustPlayerPos) - world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); + mWorld.getPlayerPtr().getClass().adjustPosition(mWorld.getPlayerPtr(), true); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); return; } @@ -850,7 +849,7 @@ namespace MWWorld mRendering.configureFog(mCurrentCell->getCell()); // Sky system - MWBase::Environment::get().getWorld()->adjustSky(); + mWorld.adjustSky(); if (changeEvent) mCellChanged = true; @@ -868,14 +867,14 @@ namespace MWWorld int x = 0; int y = 0; - MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); + mWorld.positionToIndex (position.pos[0], position.pos[1], x, y); if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); changeCellGrid(position.asVec3(), x, y, changeEvent); - CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); + CellStore* current = mWorld.getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); if (changeEvent) @@ -896,20 +895,20 @@ namespace MWWorld { InsertVisitor insertVisitor(cell, loadingListener); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mRendering, mPagedRefs); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mNavigator); }); } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs); - addObject(ptr, *mPhysics, mNavigator); - MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); + addObject(ptr, mWorld, *mPhysics, mRendering, mPagedRefs); + addObject(ptr, mWorld, *mPhysics, mNavigator); + mWorld.scaleObject(ptr, ptr.getCellRef().getScale()); if (mCurrentCell != nullptr) { - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const auto player = mWorld.getPlayerPtr(); mNavigator.update(player.getRefData().getPosition().asVec3()); } } @@ -929,13 +928,13 @@ namespace MWWorld mNavigator.removeObject(DetourNavigator::ObjectId(object)); if (mCurrentCell != nullptr) { - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const auto player = mWorld.getPlayerPtr(); mNavigator.update(player.getRefData().getPosition().asVec3()); } } else if (mPhysics->getActor(ptr)) { - mNavigator.removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr)); + mNavigator.removeAgent(mWorld.getPathfindingHalfExtents(ptr)); } mPhysics->remove(ptr); mRendering.removeObject (ptr); @@ -1020,7 +1019,7 @@ namespace MWWorld if (dt<=1e-06) return; std::vector exteriorPositions; - const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::ConstPtr player = mWorld.getPlayerPtr(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); osg::Vec3f moved = playerPos - mLastPlayerPos; osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime; @@ -1070,13 +1069,13 @@ namespace MWWorld try { if (!door.getCellRef().getDestCell().empty()) - preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell())); + preloadCell(mWorld.getInterior(door.getCellRef().getDestCell())); else { osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3(); int x,y; - MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y); - preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); + mWorld.positionToIndex (pos.x(), pos.y(), x, y); + preloadCell(mWorld.getExterior(x,y), true); exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } @@ -1090,7 +1089,7 @@ namespace MWWorld void Scene::preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos) { - if (!MWBase::Environment::get().getWorld()->isCellExterior()) + if (!mWorld.isCellExterior()) return; int halfGridSizePlusOne = mHalfGridSize + 1; @@ -1100,7 +1099,7 @@ namespace MWWorld cellX = mCurrentGridCenter.x(); cellY = mCurrentGridCenter.y(); float centerX, centerY; - MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); + mWorld.indexToPosition(cellX, cellY, centerX, centerY, true); for (int dx = -halfGridSizePlusOne; dx <= halfGridSizePlusOne; ++dx) { @@ -1110,14 +1109,14 @@ namespace MWWorld continue; // only care about the outer (not yet loaded) part of the grid float thisCellCenterX, thisCellCenterY; - MWBase::Environment::get().getWorld()->indexToPosition(cellX+dx, cellY+dy, thisCellCenterX, thisCellCenterY, true); + mWorld.indexToPosition(cellX+dx, cellY+dy, thisCellCenterX, thisCellCenterY, true); float dist = std::max(std::abs(thisCellCenterX - playerPos.x()), std::abs(thisCellCenterY - playerPos.y())); dist = std::min(dist,std::max(std::abs(thisCellCenterX - predictedPos.x()), std::abs(thisCellCenterY - predictedPos.y()))); float loadDist = Constants::CellSizeInUnits / 2 + Constants::CellSizeInUnits - mCellLoadingThreshold + mPreloadDistance; if (dist < loadDist) - preloadCell(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy)); + preloadCell(mWorld.getExterior(cellX+dx, cellY+dy)); } } } @@ -1133,7 +1132,7 @@ namespace MWWorld { for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy) { - mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(x+dx, y+dy), mRendering.getReferenceTime()); + mPreloader->preload(mWorld.getExterior(x+dx, y+dy), mRendering.getReferenceTime()); if (++numpreloaded >= mPreloader->getMaxCacheSize()) break; } @@ -1196,7 +1195,7 @@ namespace MWWorld void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time { - const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::ConstPtr player = mWorld.getPlayerPtr(); ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); for (MWWorld::CellStore* cellStore : mActiveCells) @@ -1208,13 +1207,13 @@ namespace MWWorld for (ESM::Transport::Dest& dest : listVisitor.mList) { if (!dest.mCellName.empty()) - preloadCell(MWBase::Environment::get().getWorld()->getInterior(dest.mCellName)); + preloadCell(mWorld.getInterior(dest.mCellName)); else { osg::Vec3f pos = dest.mPos.asVec3(); int x,y; - MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y); - preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); + mWorld.positionToIndex( pos.x(), pos.y(), x, y); + preloadCell(mWorld.getExterior(x,y), true); exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 371c3cbbc5..fd66d71cf2 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -62,6 +62,7 @@ namespace MWWorld class Player; class CellStore; class CellPreloader; + class World; enum class RotationOrder { @@ -85,6 +86,7 @@ namespace MWWorld CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; bool mCellChanged; + MWWorld::World& mWorld; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; DetourNavigator::Navigator& mNavigator; @@ -131,7 +133,7 @@ namespace MWWorld public: - Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, + Scene(MWWorld::World& world, MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, DetourNavigator::Navigator& navigator); ~Scene(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c7d253b4c5..851ba2daf4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -201,7 +201,7 @@ namespace MWWorld mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mStore)); - mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get(), *mNavigator)); + mWorldScene.reset(new Scene(*this, *mRendering.get(), mPhysics.get(), *mNavigator)); } void World::fillGlobalVariables() @@ -1035,7 +1035,7 @@ namespace MWWorld return mWorldScene->markCellAsUnchanged(); } - float World::getMaxActivationDistance () + float World::getMaxActivationDistance() const { if (mActivationDistanceOverride >= 0) return static_cast(mActivationDistanceOverride); @@ -3175,7 +3175,7 @@ namespace MWWorld mProjectileManager->updateCasters(); } - void World::applyLoopingParticles(const MWWorld::Ptr& ptr) + void World::applyLoopingParticles(const MWWorld::Ptr& ptr) const { const MWWorld::Class &cls = ptr.getClass(); if (cls.isActor()) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index d5ff82f52d..02d6d4afb7 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -396,7 +396,7 @@ namespace MWWorld ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). - float getMaxActivationDistance() override; + float getMaxActivationDistance() const override; void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const override; @@ -643,7 +643,7 @@ namespace MWWorld const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; void updateProjectilesCasters() override; - void applyLoopingParticles(const MWWorld::Ptr& ptr) override; + void applyLoopingParticles(const MWWorld::Ptr& ptr) const override; const std::vector& getContentFiles() const override; void breakInvisibility (const MWWorld::Ptr& actor) override; From 31bd87936fc9d417a98622ac639e3c286d55e691 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 21:06:14 +0200 Subject: [PATCH 2411/2859] Remove redundant virtual World::adjustSky function --- apps/openmw/mwbase/world.hpp | 2 -- apps/openmw/mwworld/worldimp.hpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f78befe470..336af37a89 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -150,8 +150,6 @@ namespace MWBase virtual bool toggleWorld() = 0; virtual bool toggleBorders() = 0; - virtual void adjustSky() = 0; - virtual MWWorld::Player& getPlayer() = 0; virtual MWWorld::Ptr getPlayerPtr() = 0; virtual MWWorld::ConstPtr getPlayerConstPtr() const = 0; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 02d6d4afb7..8098c6ec20 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -232,7 +232,7 @@ namespace MWWorld bool toggleWorld() override; bool toggleBorders() override; - void adjustSky() override; + void adjustSky(); Player& getPlayer() override; MWWorld::Ptr getPlayerPtr() override; From 9c5887aab6b5b4a04c9832a2511e842a9d60de55 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 2 May 2022 22:04:19 +0200 Subject: [PATCH 2412/2859] Add NPC and Creature record bindings --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/types/creature.cpp | 31 ++++++++++++++++++++++++++++ apps/openmw/mwlua/types/npc.cpp | 24 +++++++++++++++++++++ apps/openmw/mwlua/types/types.cpp | 2 +- apps/openmw/mwlua/types/types.hpp | 1 + files/lua_api/openmw/types.lua | 26 +++++++++++++++++++++++ 6 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 apps/openmw/mwlua/types/creature.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6f35e53fd1..7bbf179d95 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -60,7 +60,7 @@ add_openmw_dir (mwlua luamanagerimp object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings stats - types/types types/door types/actor types/container types/weapon types/npc + types/types types/door types/actor types/container types/weapon types/npc types/creature ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp new file mode 100644 index 0000000000..87be291493 --- /dev/null +++ b/apps/openmw/mwlua/types/creature.cpp @@ -0,0 +1,31 @@ +#include "types.hpp" + +#include + +#include + +#include "../stats.hpp" +#include "../luabindings.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + void addCreatureBindings(sol::table creature, const Context& context) + { + const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); + creature["record"] = sol::overload( + [](const Object& obj) -> const ESM::Creature* { return obj.ptr().get()->mBase; }, + [store](const std::string& recordId) -> const ESM::Creature* { return store->find(recordId); }); + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Creature"); + record[sol::meta_function::to_string] = [](const ESM::Creature& rec) { return "ESM3_Creature[" + rec.mId + "]"; }; + record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; }); + record["model"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mModel; }); + record["mwscript"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mScript; }); + record["baseCreature"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mOriginal; }); + } +} diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 8dafbe1e8a..0c9dd3fb9c 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -1,11 +1,35 @@ #include "types.hpp" +#include + +#include + #include "../stats.hpp" +#include "../luabindings.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} namespace MWLua { void addNpcBindings(sol::table npc, const Context& context) { addNpcStatsBindings(npc, context); + + const MWWorld::Store* store = &MWBase::Environment::get().getWorld()->getStore().get(); + npc["record"] = sol::overload( + [](const Object& obj) -> const ESM::NPC* { return obj.ptr().get()->mBase; }, + [store](const std::string& recordId) -> const ESM::NPC* { return store->find(recordId); }); + sol::usertype record = context.mLua->sol().new_usertype("ESM3_NPC"); + record[sol::meta_function::to_string] = [](const ESM::NPC& rec) { return "ESM3_NPC[" + rec.mId + "]"; }; + record["name"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mName; }); + record["race"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace; }); + record["class"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass; }); + record["mwscript"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mScript; }); + record["hair"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair; }); + record["head"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead; }); } } diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index d696fb9715..f8b20ea59c 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -155,7 +155,7 @@ namespace MWLua ESM::REC_LIGH, ESM::REC_MISC, ESM::REC_ALCH, ESM::REC_WEAP, ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA}); - addType(ObjectTypeName::Creature, {ESM::REC_CREA}, ObjectTypeName::Actor); + addCreatureBindings(addType(ObjectTypeName::Creature, {ESM::REC_CREA}, ObjectTypeName::Actor), context); addNpcBindings(addType(ObjectTypeName::NPC, {ESM::REC_INTERNAL_PLAYER, ESM::REC_NPC_}, ObjectTypeName::Actor), context); addType(ObjectTypeName::Player, {ESM::REC_INTERNAL_PLAYER}, ObjectTypeName::NPC); diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index cb345b7d01..bd4cb5e384 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -29,6 +29,7 @@ namespace MWLua void addActorBindings(sol::table actor, const Context& context); void addWeaponBindings(sol::table weapon, const Context& context); void addNpcBindings(sol::table npc, const Context& context); + void addCreatureBindings(sol::table creature, const Context& context); } #endif // MWLUA_TYPES_H diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d62d92169d..ae40cee595 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -441,6 +441,19 @@ -- @param openmw.core#GameObject object -- @return #boolean +--- +-- Returns the read-only @{#CreatureRecord} of a creature +-- @function [parent=#Creature] record +-- @param #any objectOrRecordId +-- @return #CreatureRecord + +--- +-- @type CreatureRecord +-- @field #string name +-- @field #string baseCreature Record id of a base creature, which was modified to create this one +-- @field #string model VFS path to the creature's model +-- @field #string mwscript + --- @{#NPC} functions @@ -458,7 +471,20 @@ -- @param openmw.core#GameObject object -- @return #boolean +--- +-- Returns the read-only @{#NpcRecord} of an NPC +-- @function [parent=#NPC] record +-- @param #any objectOrRecordId +-- @return #NpcRecord +--- +-- @type NpcRecord +-- @field #string name +-- @field #string race +-- @field #string class Name of the NPC's class (e. g. Acrobat) +-- @field #string mwscript MWScript that is attached to this NPC +-- @field #string hair Path to the hair body part model +-- @field #string head Path to the head body part model --- @{#Player} functions -- @field [parent=#types] #Player Player From 87ca575d60e40e57bc341043094179308e0d5ae9 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 6 May 2022 23:59:27 +0200 Subject: [PATCH 2413/2859] List private members of MWMechanics::Actors in a single place --- apps/openmw/mwmechanics/actors.hpp | 46 ++++++++++++++---------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index f1985377e8..ff992399b0 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -39,24 +39,6 @@ namespace MWMechanics class Actors { - std::map mDeathCount; - - void adjustMagicEffects (const MWWorld::Ptr& creature, float duration); - - void calculateRestoration (const MWWorld::Ptr& ptr, float duration); - - void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer); - - void updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip); - - void updateCrimePursuit (const MWWorld::Ptr& ptr, float duration); - - void killDeadActors (); - - void purgeSpellEffects (int casterActorId); - - void predictAndAvoidCollisions(float duration); - public: Actors(); @@ -200,14 +182,30 @@ namespace MWMechanics GreetingState getGreetingState(const MWWorld::Ptr& ptr) const; bool isTurningToPlayer(const MWWorld::Ptr& ptr) const; - private: - void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); + private: + std::map mDeathCount; + PtrActorMap mActors; + float mTimerDisposeSummonsCorpses; + float mActorsProcessingRange; + bool mSmoothMovement; - PtrActorMap mActors; - float mTimerDisposeSummonsCorpses; - float mActorsProcessingRange; + void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); - bool mSmoothMovement; + void adjustMagicEffects (const MWWorld::Ptr& creature, float duration); + + void calculateRestoration (const MWWorld::Ptr& ptr, float duration); + + void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer); + + void updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip); + + void updateCrimePursuit (const MWWorld::Ptr& ptr, float duration); + + void killDeadActors (); + + void purgeSpellEffects (int casterActorId); + + void predictAndAvoidCollisions(float duration); }; } From 66c9b6c19984a993e700ed22110a6aa021d52253 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 May 2022 00:11:12 +0200 Subject: [PATCH 2414/2859] Make MWMechanics::Actors local static variables to be members --- apps/openmw/mwmechanics/actors.cpp | 63 +++++++++++++++--------------- apps/openmw/mwmechanics/actors.hpp | 12 ++++++ 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a659c30341..a209535cd2 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1207,18 +1207,17 @@ namespace MWMechanics } // check if we still have any player enemies to switch music - static int currentMusic = 0; - - if (currentMusic != 1 && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() && - MWBase::Environment::get().getSoundManager()->isMusicPlaying())) + if (mCurrentMusic != MusicType::Explore && !hasHostiles + && !(player.getClass().getCreatureStats(player).isDead() + && MWBase::Environment::get().getSoundManager()->isMusicPlaying())) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - currentMusic = 1; + mCurrentMusic = MusicType::Explore; } - else if (currentMusic != 2 && hasHostiles) + else if (mCurrentMusic != MusicType::Battle && hasHostiles) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); - currentMusic = 2; + mCurrentMusic = MusicType::Battle; } } @@ -1374,15 +1373,19 @@ namespace MWMechanics { if(!paused) { - static float timerUpdateHeadTrack = 0; - static float timerUpdateEquippedLight = 0; - static float timerUpdateHello = 0; const float updateEquippedLightInterval = 1.0f; - if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; - if (timerUpdateHello >= 0.25f) timerUpdateHello = 0; - if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; - if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0; + if (mTimerUpdateHeadTrack >= 0.3f) + mTimerUpdateHeadTrack = 0; + + if (mTimerUpdateHello >= 0.25f) + mTimerUpdateHello = 0; + + if (mTimerDisposeSummonsCorpses >= 0.2f) + mTimerDisposeSummonsCorpses = 0; + + if (mTimerUpdateEquippedLight >= updateEquippedLightInterval) + mTimerUpdateEquippedLight = 0; // show torches only when there are darkness and no precipitations MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -1470,7 +1473,7 @@ namespace MWMechanics engageCombat(iter->first, it->first, cachedAllies, it->first == player); } } - if (timerUpdateHeadTrack == 0) + if (mTimerUpdateHeadTrack == 0) { float sqrHeadTrackDistance = std::numeric_limits::max(); MWWorld::Ptr headTrackTarget; @@ -1518,7 +1521,7 @@ namespace MWMechanics if (isConscious(iter->first) && !(luaControls && luaControls->mDisableAI)) { stats.getAiSequence().execute(iter->first, *ctrl, duration); - updateGreetingState(iter->first, *iter->second, timerUpdateHello > 0); + updateGreetingState(iter->first, *iter->second, mTimerUpdateHello > 0); playIdleDialogue(iter->first); updateMovementSpeed(iter->first); } @@ -1535,7 +1538,7 @@ namespace MWMechanics // We can not update drowning state for actors outside of AI distance - they can not resurface to breathe updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); } - if(timerUpdateEquippedLight == 0 && iter->first.getClass().hasInventoryStore(iter->first)) + if(mTimerUpdateEquippedLight == 0 && iter->first.getClass().hasInventoryStore(iter->first)) updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); if (luaControls && isConscious(iter->first)) @@ -1577,9 +1580,9 @@ namespace MWMechanics if (avoidCollisions) predictAndAvoidCollisions(duration); - timerUpdateHeadTrack += duration; - timerUpdateEquippedLight += duration; - timerUpdateHello += duration; + mTimerUpdateHeadTrack += duration; + mTimerUpdateEquippedLight += duration; + mTimerUpdateHello += duration; mTimerDisposeSummonsCorpses += duration; // Animation/movement update @@ -1829,8 +1832,6 @@ namespace MWMechanics void Actors::updateSneaking(CharacterController* ctrl, float duration) { - static float sneakTimer = 0.f; // Times update of sneak icon - if (!ctrl) { MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); @@ -1845,17 +1846,15 @@ namespace MWMechanics return; } - static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice" - MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& gmst = world->getStore().get(); static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat(); static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat(); - if (sneakTimer >= fSneakUseDelay) - sneakTimer = 0.f; + if (mSneakTimer >= fSneakUseDelay) + mSneakTimer = 0.f; - if (sneakTimer == 0.f) + if (mSneakTimer == 0.f) { // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. bool avoidedNotice = false; @@ -1893,18 +1892,18 @@ namespace MWMechanics } } - if (sneakSkillTimer >= fSneakUseDelay) - sneakSkillTimer = 0.f; + if (mSneakSkillTimer >= fSneakUseDelay) + mSneakSkillTimer = 0.f; - if (avoidedNotice && sneakSkillTimer == 0.f) + if (avoidedNotice && mSneakSkillTimer == 0.f) player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); if (!detected) MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); } - sneakTimer += duration; - sneakSkillTimer += duration; + mSneakTimer += duration; + mSneakSkillTimer += duration; } int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index ff992399b0..1bc45774d0 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -183,11 +183,23 @@ namespace MWMechanics bool isTurningToPlayer(const MWWorld::Ptr& ptr) const; private: + enum class MusicType + { + Explore, + Battle + }; + std::map mDeathCount; PtrActorMap mActors; float mTimerDisposeSummonsCorpses; + float mTimerUpdateHeadTrack = 0; + float mTimerUpdateEquippedLight = 0; + float mTimerUpdateHello = 0; + float mSneakTimer = 0; // Times update of sneak icon + float mSneakSkillTimer = 0; // Times sneak skill progress from "avoid notice" float mActorsProcessingRange; bool mSmoothMovement; + MusicType mCurrentMusic = MusicType::Explore; void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); From ef64587cbfff83de5d1bf4f3b296f23081cc3940 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 May 2022 00:12:00 +0200 Subject: [PATCH 2415/2859] Mark unchanging static as const --- apps/openmw/mwmechanics/actors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a209535cd2..5e2e083648 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -380,7 +380,7 @@ namespace MWMechanics return; // Play a random voice greeting if the player gets too close - static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() + static const int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() .get().find("iGreetDistanceMultiplier")->mValue.getInteger(); float helloDistance = static_cast(actorStats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier); From 05901a2480c89e1aa3710fa469a8d6b193415a62 Mon Sep 17 00:00:00 2001 From: cody glassman Date: Tue, 3 May 2022 22:50:31 -0700 Subject: [PATCH 2416/2859] add borderless windows, deprecate fullscreen mode --- CHANGELOG.md | 1 + apps/launcher/graphicspage.cpp | 18 +-- apps/openmw/engine.cpp | 8 +- apps/openmw/mwgui/settingswindow.cpp | 113 +++++++++++------- apps/openmw/mwgui/settingswindow.hpp | 8 +- apps/openmw/mwgui/windowmanagerimp.cpp | 4 +- components/sdlutil/sdlvideowrapper.cpp | 7 +- components/sdlutil/sdlvideowrapper.hpp | 7 +- components/settings/settings.hpp | 7 ++ .../reference/modding/settings/video.rst | 22 ++-- files/mygui/openmw_settings_window.layout | 22 ++-- files/settings-default.cfg | 5 +- files/ui/graphicspage.ui | 30 ++++- 13 files changed, 163 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd762a82ac..c7396ba328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,6 +157,7 @@ Feature #6600: Support NiSortAdjustNode Feature #6684: Support NiFltAnimationNode Feature #6699: Ignored flag + Feature #6700: Support borderless fullscreen Feature #6706: Save the size of the Options window Feature #6721: [OpenMW-CS] Add option to open records in new window Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index e6d857a943..d3e5d1c86d 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -42,7 +42,7 @@ Launcher::GraphicsPage::GraphicsPage(QWidget *parent) customWidthSpinBox->setMaximum(res.width()); customHeightSpinBox->setMaximum(res.height()); - connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int))); + connect(windowModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFullScreenChanged(int))); connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool))); connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int))); connect(framerateLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotFramerateLimitToggled(bool))); @@ -94,8 +94,10 @@ bool Launcher::GraphicsPage::loadSettings() if (Settings::Manager::getBool("vsync", "Video")) vSyncCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("fullscreen", "Video")) - fullScreenCheckBox->setCheckState(Qt::Checked); + size_t windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); + if (windowMode > static_cast(Settings::WindowMode::Windowed)) + windowMode = 0; + windowModeComboBox->setCurrentIndex(windowMode); if (Settings::Manager::getBool("window border", "Video")) windowBorderCheckBox->setCheckState(Qt::Checked); @@ -183,9 +185,9 @@ void Launcher::GraphicsPage::saveSettings() if (cVSync != Settings::Manager::getBool("vsync", "Video")) Settings::Manager::setBool("vsync", "Video", cVSync); - bool cFullScreen = fullScreenCheckBox->checkState(); - if (cFullScreen != Settings::Manager::getBool("fullscreen", "Video")) - Settings::Manager::setBool("fullscreen", "Video", cFullScreen); + int cWindowMode = windowModeComboBox->currentIndex(); + if (cWindowMode != Settings::Manager::getInt("window mode", "Video")) + Settings::Manager::setInt("window mode", "Video", cWindowMode); bool cWindowBorder = windowBorderCheckBox->checkState(); if (cWindowBorder != Settings::Manager::getBool("window border", "Video")) @@ -355,9 +357,9 @@ void Launcher::GraphicsPage::screenChanged(int screen) } } -void Launcher::GraphicsPage::slotFullScreenChanged(int state) +void Launcher::GraphicsPage::slotFullScreenChanged(int mode) { - if (state == Qt::Checked) { + if (mode == static_cast(Settings::WindowMode::Fullscreen) || mode == static_cast(Settings::WindowMode::BorderlessFullscreen)) { standardRadioButton->toggle(); customRadioButton->setEnabled(false); customWidthSpinBox->setEnabled(false); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a99bd72831..2e12fecd39 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -554,7 +554,7 @@ void OMW::Engine::createWindow(Settings::Manager& settings) int screen = settings.getInt("screen", "Video"); int width = settings.getInt("resolution x", "Video"); int height = settings.getInt("resolution y", "Video"); - bool fullscreen = settings.getBool("fullscreen", "Video"); + Settings::WindowMode windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); bool windowBorder = settings.getBool("window border", "Video"); bool vsync = settings.getBool("vsync", "Video"); unsigned int antialiasing = std::max(0, settings.getInt("antialiasing", "Video")); @@ -562,15 +562,17 @@ void OMW::Engine::createWindow(Settings::Manager& settings) int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); - if(fullscreen) + if(windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::BorderlessFullscreen) { pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); } Uint32 flags = SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE; - if(fullscreen) + if(windowMode == Settings::WindowMode::Fullscreen) flags |= SDL_WINDOW_FULLSCREEN; + else if (windowMode == Settings::WindowMode::BorderlessFullscreen) + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; // Allows for Windows snapping features to properly work in borderless window SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "1"); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index e79132f0ab..95a555da99 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -224,7 +224,7 @@ namespace MWGui getWidget(mSettingsTab, "SettingsTab"); getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); - getWidget(mFullscreenButton, "FullscreenButton"); + getWidget(mWindowModeList, "WindowModeList"); getWidget(mWindowBorderButton, "WindowBorderButton"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); getWidget(mAnisotropyBox, "AnisotropyBox"); @@ -274,6 +274,8 @@ namespace MWGui mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); mMaxLights->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onMaxLightsChanged); + mWindowModeList->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWindowModeChanged); + mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); @@ -325,7 +327,8 @@ namespace MWGui updateMaxLightsComboBox(mMaxLights); - mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); + Settings::WindowMode windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); + mWindowBorderButton->setEnabled(windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::BorderlessFullscreen); mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); @@ -433,6 +436,15 @@ namespace MWGui apply(); } + void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos) + { + if (pos == MyGUI::ITEM_NONE) + return; + + Settings::Manager::setInt("window mode", "Video", static_cast(_sender->getIndexSelected())); + apply(); + } + void SettingsWindow::onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos) { int count = 8 * (pos + 1); @@ -485,49 +497,6 @@ namespace MWGui newState = true; } - if (_sender == mFullscreenButton) - { - // check if this resolution is supported in fullscreen - if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) - { - std::string resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); - int resX, resY; - parseResolution (resX, resY, resStr); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); - } - - bool supported = false; - int fallbackX = 0, fallbackY = 0; - for (unsigned int i=0; igetItemCount(); ++i) - { - std::string resStr = mResolutionList->getItemNameAt(i); - int resX, resY; - parseResolution (resX, resY, resStr); - - if (i == 0) - { - fallbackX = resX; - fallbackY = resY; - } - - if (resX == Settings::Manager::getInt("resolution x", "Video") - && resY == Settings::Manager::getInt("resolution y", "Video")) - supported = true; - } - - if (!supported && mResolutionList->getItemCount()) - { - if (fallbackX != 0 && fallbackY != 0) - { - Settings::Manager::setInt("resolution x", "Video", fallbackX); - Settings::Manager::setInt("resolution y", "Video", fallbackY); - } - } - - mWindowBorderButton->setEnabled(!newState); - } - if (getSettingType(_sender) == checkButtonType) { Settings::Manager::setBool(getSettingName(_sender), getSettingCategory(_sender), newState); @@ -691,6 +660,59 @@ namespace MWGui mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(lightingMethodStr)); } + void SettingsWindow::updateWindowModeSettings() + { + size_t index = static_cast(Settings::Manager::getInt("window mode", "Video")); + + if (index > static_cast(Settings::WindowMode::Windowed)) + index = MyGUI::ITEM_NONE; + + mWindowModeList->setIndexSelected(index); + + if (index != static_cast(Settings::WindowMode::Windowed) && index != MyGUI::ITEM_NONE) + { + // check if this resolution is supported in fullscreen + if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) + { + std::string resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); + int resX, resY; + parseResolution (resX, resY, resStr); + Settings::Manager::setInt("resolution x", "Video", resX); + Settings::Manager::setInt("resolution y", "Video", resY); + } + + bool supported = false; + int fallbackX = 0, fallbackY = 0; + for (unsigned int i=0; igetItemCount(); ++i) + { + std::string resStr = mResolutionList->getItemNameAt(i); + int resX, resY; + parseResolution (resX, resY, resStr); + + if (i == 0) + { + fallbackX = resX; + fallbackY = resY; + } + + if (resX == Settings::Manager::getInt("resolution x", "Video") + && resY == Settings::Manager::getInt("resolution y", "Video")) + supported = true; + } + + if (!supported && mResolutionList->getItemCount()) + { + if (fallbackX != 0 && fallbackY != 0) + { + Settings::Manager::setInt("resolution x", "Video", fallbackX); + Settings::Manager::setInt("resolution y", "Video", fallbackY); + } + } + + mWindowBorderButton->setEnabled(false); + } + } + void SettingsWindow::layoutControlsBox() { const int h = 18; @@ -866,6 +888,7 @@ namespace MWGui highlightCurrentResolution(); updateControlsBox(); updateLightSettings(); + updateWindowModeSettings(); resetScrollbars(); renderScriptSettings(); resizeScriptSettings(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 1a888257a8..bfce6c30dd 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -18,6 +18,8 @@ namespace MWGui void updateLightSettings(); + void updateWindowModeSettings(); + void onResChange(int, int) override { center(); } protected: @@ -26,7 +28,7 @@ namespace MWGui // graphics MyGUI::ListBox* mResolutionList; - MyGUI::Button* mFullscreenButton; + MyGUI::ComboBox* mWindowModeList; MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::Widget* mAnisotropyBox; @@ -72,6 +74,8 @@ namespace MWGui void onLightsResetButtonClicked(MyGUI::Widget* _sender); void onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos); + void onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos); + void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); void onResetDefaultBindings(MyGUI::Widget* _sender); @@ -94,7 +98,7 @@ namespace MWGui void renderScriptSettings(); void computeMinimumWindowSize(); - + private: void resetScrollbars(); }; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e3d61e7c08..80fb1e546b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1086,7 +1086,7 @@ namespace MWGui else if (setting.first == "Video" && ( setting.second == "resolution x" || setting.second == "resolution y" - || setting.second == "fullscreen" + || setting.second == "window mode" || setting.second == "window border")) changeRes = true; @@ -1101,7 +1101,7 @@ namespace MWGui { mVideoWrapper->setVideoMode(Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video"), - Settings::Manager::getBool("fullscreen", "Video"), + static_cast(Settings::Manager::getInt("window mode", "Video")), Settings::Manager::getBool("window border", "Video")); } } diff --git a/components/sdlutil/sdlvideowrapper.cpp b/components/sdlutil/sdlvideowrapper.cpp index b3ba98ee3c..dec0942259 100644 --- a/components/sdlutil/sdlvideowrapper.cpp +++ b/components/sdlutil/sdlvideowrapper.cpp @@ -1,6 +1,7 @@ #include "sdlvideowrapper.hpp" #include +#include #include @@ -68,21 +69,21 @@ namespace SDLUtil Log(Debug::Warning) << "Couldn't set gamma: " << SDL_GetError(); } - void VideoWrapper::setVideoMode(int width, int height, bool fullscreen, bool windowBorder) + void VideoWrapper::setVideoMode(int width, int height, Settings::WindowMode windowMode, bool windowBorder) { SDL_SetWindowFullscreen(mWindow, 0); if (SDL_GetWindowFlags(mWindow) & SDL_WINDOW_MAXIMIZED) SDL_RestoreWindow(mWindow); - if (fullscreen) + if (windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::BorderlessFullscreen) { SDL_DisplayMode mode; SDL_GetWindowDisplayMode(mWindow, &mode); mode.w = width; mode.h = height; SDL_SetWindowDisplayMode(mWindow, &mode); - SDL_SetWindowFullscreen(mWindow, fullscreen); + SDL_SetWindowFullscreen(mWindow, windowMode == Settings::WindowMode::Fullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP); } else { diff --git a/components/sdlutil/sdlvideowrapper.hpp b/components/sdlutil/sdlvideowrapper.hpp index 3866c3ec31..b9bcd66b5c 100644 --- a/components/sdlutil/sdlvideowrapper.hpp +++ b/components/sdlutil/sdlvideowrapper.hpp @@ -12,6 +12,11 @@ namespace osgViewer class Viewer; } +namespace Settings +{ + enum class WindowMode; +} + namespace SDLUtil { @@ -25,7 +30,7 @@ namespace SDLUtil void setGammaContrast(float gamma, float contrast); - void setVideoMode(int width, int height, bool fullscreen, bool windowBorder); + void setVideoMode(int width, int height, Settings::WindowMode windowMode, bool windowBorder); void centerWindow(); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index d5d9c2d9f6..89adfea7e0 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -16,6 +16,13 @@ namespace Files namespace Settings { + enum class WindowMode + { + Fullscreen = 0, + BorderlessFullscreen, + Windowed + }; + /// /// \brief Settings management (can change during runtime) /// diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index 040b9948f7..f57b4e92d5 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -31,17 +31,23 @@ The window resolution can be selected from a menu of common screen sizes in the Video tab of the Video Panel of the Options menu, or in the Graphics tab of the OpenMW Launcher. The vertical resolution can also be set to a custom value in the Graphics tab of the OpenMW Launcher. -fullscreen ----------- +window mode +----------- -:Type: boolean -:Range: True/False -:Default: False +:Type: integer +:Range: 0, 1, 2 +:Default: 2 (Windowed) + +This setting determines the window mode. + +0: Fullscreen + +1: Borderless Fullscreen -This setting determines whether the entire screen is used for the specified resolution. +2: Windowed -This setting can be toggled in game using the Fullscreen button in the Video tab of the Video panel in the Options menu. -It can also be toggled with the Full Screen check box in the Graphics tab of the OpenMW Launcher. +This setting can be toggled in game using the dropdown list in the Video tab of the Video panel in the Options menu. +It can also be toggled with the window mode dropdown in the Graphics tab of the OpenMW Launcher. screen ------ diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index d0e2edfbf6..b5c3185693 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -270,17 +270,17 @@ - - - - - - - - + + + + + + + + - + @@ -290,7 +290,7 @@ - + @@ -301,7 +301,7 @@ - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index e320652f36..bc3c104765 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -611,8 +611,9 @@ hrtf = resolution x = 800 resolution y = 600 -# OpenMW takes complete control of the screen. -fullscreen = false +# Specify the window mode. +# 0 = Fullscreen, 1 = Borderless Fullscreen, 2 = Windowed +window mode = 0 # Determines which screen OpenMW is on. (>=0). screen = 0 diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui index 508a955d7b..6a186906a5 100644 --- a/files/ui/graphicspage.ui +++ b/files/ui/graphicspage.ui @@ -112,17 +112,39 @@ - + Window Border - - + + + + 0 + + + + Fullscreen + + + + + Borderless Fullscreen + + + + + Windowed + + + + + + - Full Screen + Window Mode: From ad139f2f9ad94c967eb3cc2db443e12ce88ab620 Mon Sep 17 00:00:00 2001 From: cody glassman Date: Sun, 8 May 2022 22:56:35 -0700 Subject: [PATCH 2417/2859] rename to windowed fullscreen --- CHANGELOG.md | 2 +- apps/launcher/graphicspage.cpp | 2 +- apps/openmw/engine.cpp | 4 ++-- apps/openmw/mwgui/settingswindow.cpp | 2 +- components/sdlutil/sdlvideowrapper.cpp | 2 +- components/settings/settings.hpp | 2 +- docs/source/reference/modding/settings/video.rst | 5 +++-- files/mygui/openmw_settings_window.layout | 2 +- files/settings-default.cfg | 2 +- files/ui/graphicspage.ui | 2 +- 10 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7396ba328..66f5868589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,7 +157,7 @@ Feature #6600: Support NiSortAdjustNode Feature #6684: Support NiFltAnimationNode Feature #6699: Ignored flag - Feature #6700: Support borderless fullscreen + Feature #6700: Support windowed fullscreen Feature #6706: Save the size of the Options window Feature #6721: [OpenMW-CS] Add option to open records in new window Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index d3e5d1c86d..5a57be0e53 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -359,7 +359,7 @@ void Launcher::GraphicsPage::screenChanged(int screen) void Launcher::GraphicsPage::slotFullScreenChanged(int mode) { - if (mode == static_cast(Settings::WindowMode::Fullscreen) || mode == static_cast(Settings::WindowMode::BorderlessFullscreen)) { + if (mode == static_cast(Settings::WindowMode::Fullscreen) || mode == static_cast(Settings::WindowMode::WindowedFullscreen)) { standardRadioButton->toggle(); customRadioButton->setEnabled(false); customWidthSpinBox->setEnabled(false); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2e12fecd39..fa84bcf14c 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -562,7 +562,7 @@ void OMW::Engine::createWindow(Settings::Manager& settings) int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); - if(windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::BorderlessFullscreen) + if(windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::WindowedFullscreen) { pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); @@ -571,7 +571,7 @@ void OMW::Engine::createWindow(Settings::Manager& settings) Uint32 flags = SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE; if(windowMode == Settings::WindowMode::Fullscreen) flags |= SDL_WINDOW_FULLSCREEN; - else if (windowMode == Settings::WindowMode::BorderlessFullscreen) + else if (windowMode == Settings::WindowMode::WindowedFullscreen) flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; // Allows for Windows snapping features to properly work in borderless window diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 95a555da99..785cec9ef6 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -328,7 +328,7 @@ namespace MWGui updateMaxLightsComboBox(mMaxLights); Settings::WindowMode windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); - mWindowBorderButton->setEnabled(windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::BorderlessFullscreen); + mWindowBorderButton->setEnabled(windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::WindowedFullscreen); mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); diff --git a/components/sdlutil/sdlvideowrapper.cpp b/components/sdlutil/sdlvideowrapper.cpp index dec0942259..7d62979e7f 100644 --- a/components/sdlutil/sdlvideowrapper.cpp +++ b/components/sdlutil/sdlvideowrapper.cpp @@ -76,7 +76,7 @@ namespace SDLUtil if (SDL_GetWindowFlags(mWindow) & SDL_WINDOW_MAXIMIZED) SDL_RestoreWindow(mWindow); - if (windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::BorderlessFullscreen) + if (windowMode == Settings::WindowMode::Fullscreen || windowMode == Settings::WindowMode::WindowedFullscreen) { SDL_DisplayMode mode; SDL_GetWindowDisplayMode(mWindow, &mode); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 89adfea7e0..693941df10 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -19,7 +19,7 @@ namespace Settings enum class WindowMode { Fullscreen = 0, - BorderlessFullscreen, + WindowedFullscreen, Windowed }; diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index f57b4e92d5..b05fbf0380 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -40,12 +40,13 @@ window mode This setting determines the window mode. -0: Fullscreen +0: Exclusive fullscreen -1: Borderless Fullscreen +1: Windowed fullscreen, borderless window that matches screen resolution 2: Windowed + This setting can be toggled in game using the dropdown list in the Video tab of the Video panel in the Options menu. It can also be toggled with the window mode dropdown in the Graphics tab of the OpenMW Launcher. diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index b5c3185693..a134d8ecb8 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -276,7 +276,7 @@ - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index bc3c104765..3a41f72d9b 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -612,7 +612,7 @@ resolution x = 800 resolution y = 600 # Specify the window mode. -# 0 = Fullscreen, 1 = Borderless Fullscreen, 2 = Windowed +# 0 = Fullscreen, 1 = Windowed Fullscreen, 2 = Windowed window mode = 0 # Determines which screen OpenMW is on. (>=0). diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui index 6a186906a5..8cf26aa1e2 100644 --- a/files/ui/graphicspage.ui +++ b/files/ui/graphicspage.ui @@ -131,7 +131,7 @@ - Borderless Fullscreen + Windowed Fullscreen From 882245b9354189df0005837330f3e78427831468 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Mon, 9 May 2022 19:40:48 +0000 Subject: [PATCH 2418/2859] Lua Bindings: Add view distance bindings to camera --- apps/openmw/mwlua/camerabindings.cpp | 7 +++++++ apps/openmw/mwrender/renderingmanager.cpp | 21 +++++++++++++-------- apps/openmw/mwrender/renderingmanager.hpp | 5 ++++- files/lua_api/openmw/camera.lua | 13 +++++++++++++ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index 4e865374cf..345564ef0c 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -92,6 +92,13 @@ namespace MWLua api["getFieldOfView"] = [renderingManager]() { return osg::DegreesToRadians(renderingManager->getFieldOfView()); }; api["setFieldOfView"] = [renderingManager](float v) { renderingManager->setFieldOfView(osg::RadiansToDegrees(v)); }; + api["getBaseViewDistance"] = []() + { + return std::max(0.f, Settings::Manager::getFloat("viewing distance", "Camera")); + }; + api["getViewDistance"] = [renderingManager]() { return renderingManager->getViewDistance(); }; + api["setViewDistance"] = [renderingManager](float d) { renderingManager->setViewDistance(d, true); }; + api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{camera->getViewMatrix()}; }; api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 52b647add3..24e37a93ea 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -363,7 +363,7 @@ namespace MWRender || reverseZ || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); - + // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); @@ -511,7 +511,7 @@ namespace MWRender // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); - + mCamera.reset(new Camera(mViewer->getCamera())); mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); @@ -1293,10 +1293,7 @@ namespace MWRender } else if (it->first == "Camera" && it->second == "viewing distance") { - mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - if(!Settings::Manager::getBool("use distant fog", "Fog")) - mStateUpdater->setFogEnd(mViewDistance); - updateProjection = true; + setViewDistance(Settings::Manager::getFloat("viewing distance", "Camera")); } else if (it->first == "General" && (it->second == "texture filter" || it->second == "texture mipmap" || @@ -1346,9 +1343,17 @@ namespace MWRender } } - float RenderingManager::getNearClipDistance() const + void RenderingManager::setViewDistance(float distance, bool delay) { - return mNearClip; + mViewDistance = distance; + + if (delay) + { + mUpdateProjectionMatrix = true; + return; + } + + updateProjectionMatrix(); } float RenderingManager::getTerrainHeightAt(const osg::Vec3f &pos) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index fe3b33b20e..5b5c28b198 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -206,7 +206,10 @@ namespace MWRender void processChangedSettings(const Settings::CategorySettingVector& settings); - float getNearClipDistance() const; + float getNearClipDistance() const { return mNearClip; } + float getViewDistance() const { return mViewDistance; } + + void setViewDistance(float distance, bool delay = false); float getTerrainHeightAt(const osg::Vec3f& pos); diff --git a/files/lua_api/openmw/camera.lua b/files/lua_api/openmw/camera.lua index f744dcbc5c..f699663c49 100644 --- a/files/lua_api/openmw/camera.lua +++ b/files/lua_api/openmw/camera.lua @@ -186,6 +186,19 @@ -- @function [parent=#camera] setFieldOfView -- @param #number fov Field of view vertical angle in radians +--- Return base view distance. +-- @function [parent=#camera] getBaseViewDistance +-- @return #number + +--- Return current view distance. +-- @function [parent=#camera] getViewDistance +-- @return #number + +--- Set view distance. +--- Takes effect on the next frame. +-- @function [parent=#camera] setViewDistance +-- @param #number distance View distance in game units + --- Get world to local transform for the camera. -- @function [parent=#camera] getViewTransform -- @return openmw.util#Transform From a75c7c49f011982aec86243f83a6d4066532391a Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 8 May 2022 21:28:43 +0200 Subject: [PATCH 2419/2859] Disable writes to navmeshdb on database is locked error Simultaneously writing to sqlite3 database is not possible. Process exclusively locks the database for this. Another process will fail to perform any request when database is locked. Alternatively it can wait. Handling this situation properly requires complexity that is not really needed. Users are not expected to run multiple openmw processes simultaneously using the same navmeshdb. Before this change running multiple openmw processes using the same navmeshdb can lead to a crash when first transaction fails to start because there is exception thrown and not catched. Remove use of explicit transactions from DbWorker. Handling all possible transaction states due to different errors brings unnecessary complexity. Initially they were introduced to increase time between flushes to disk. This makes sense for navmeshtool because of massive number of writes but for the engine this is not an issue. --- .../detournavigator/asyncnavmeshupdater.cpp | 19 ++++++- .../detournavigator/asyncnavmeshupdater.cpp | 53 ++++++------------- .../detournavigator/asyncnavmeshupdater.hpp | 3 +- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index 1f7125d1f2..06de335d50 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -262,6 +262,20 @@ namespace updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.wait(mListener, WaitConditionType::allJobsDone); updater.stop(); + const std::set present { + TilePosition(-2, 0), + TilePosition(-1, -1), + TilePosition(-1, 0), + TilePosition(-1, 1), + TilePosition(0, -2), + TilePosition(0, -1), + TilePosition(0, 0), + TilePosition(0, 1), + TilePosition(0, 2), + TilePosition(1, -1), + TilePosition(1, 0), + TilePosition(1, 1), + }; for (int x = -5; x <= 5; ++x) for (int y = -5; y <= 5; ++y) { @@ -272,8 +286,9 @@ namespace [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v); }); if (!objects.has_value()) continue; - EXPECT_FALSE(dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, *objects)).has_value()) - << tilePosition.x() << " " << tilePosition.y(); + EXPECT_EQ(dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, *objects)).has_value(), + present.find(tilePosition) != present.end()) + << tilePosition.x() << " " << tilePosition.y() << " present=" << (present.find(tilePosition) != present.end()); } } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 18bffae131..36c54a99b0 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -678,10 +678,10 @@ namespace DetourNavigator mHasJob.notify_all(); } - std::optional DbJobQueue::pop(std::chrono::steady_clock::duration timeout) + std::optional DbJobQueue::pop() { std::unique_lock lock(mMutex); - mHasJob.wait_for(lock, timeout, [&] { return mShouldStop || !mJobs.empty(); }); + mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); }); if (mJobs.empty()) return std::nullopt; const JobIt job = mJobs.front(); @@ -752,45 +752,18 @@ namespace DetourNavigator void DbWorker::run() noexcept { - constexpr std::chrono::seconds transactionInterval(1); - auto transaction = mDb->startTransaction(Sqlite3::TransactionMode::Immediate); - auto start = std::chrono::steady_clock::now(); while (!mShouldStop) { try { - if (const auto job = mQueue.pop(transactionInterval)) + if (const auto job = mQueue.pop()) processJob(*job); - const auto now = std::chrono::steady_clock::now(); - if (mHasChanges && now - start > transactionInterval) - { - mHasChanges = false; - try - { - transaction.commit(); - } - catch (const std::exception& e) - { - Log(Debug::Error) << "DbWorker exception on commit: " << e.what(); - } - transaction = mDb->startTransaction(Sqlite3::TransactionMode::Immediate); - start = now; - } } catch (const std::exception& e) { Log(Debug::Error) << "DbWorker exception: " << e.what(); } } - if (mHasChanges) - try - { - transaction.commit(); - } - catch (const std::exception& e) - { - Log(Debug::Error) << "DbWorker exception on final commit: " << e.what(); - } } void DbWorker::processJob(JobIt job) @@ -804,10 +777,19 @@ namespace DetourNavigator catch (const std::exception& e) { Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what(); - if (std::string_view(e.what()).find("database or disk is full") != std::string_view::npos) + if (mWriteToDb) { - mWriteToDb = false; - Log(Debug::Warning) << "Writes to navmeshdb are disabled because file size limit is reached or disk is full"; + const std::string_view message(e.what()); + if (message.find("database or disk is full") != std::string_view::npos) + { + mWriteToDb = false; + Log(Debug::Warning) << "Writes to navmeshdb are disabled because file size limit is reached or disk is full"; + } + else if (message.find("database is locked") != std::string_view::npos) + { + mWriteToDb = false; + Log(Debug::Warning) << "Writes to navmeshdb are disabled to avoid concurrent writes from multiple processes"; + } } } }; @@ -833,11 +815,8 @@ namespace DetourNavigator Log(Debug::Debug) << "Serializing input for job " << job->mId; if (mWriteToDb) { - const ShapeId shapeId = mNextShapeId; const auto objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); - if (shapeId != mNextShapeId) - mHasChanges = true; job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects); } else @@ -877,7 +856,6 @@ namespace DetourNavigator Log(Debug::Debug) << "Update db tile by job " << job->mId; job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId; mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData)); - mHasChanges = true; return; } @@ -893,6 +871,5 @@ namespace DetourNavigator mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile, mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData)); ++mNextTileId; - mHasChanges = true; } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index dc6e9b5a81..ec8c849ca8 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -87,7 +87,7 @@ namespace DetourNavigator public: void push(JobIt job); - std::optional pop(std::chrono::steady_clock::duration timeout); + std::optional pop(); void update(TilePosition playerTile, int maxTiles); @@ -137,7 +137,6 @@ namespace DetourNavigator DbJobQueue mQueue; std::atomic_bool mShouldStop {false}; std::atomic_size_t mGetTileCount {0}; - bool mHasChanges = false; std::thread mThread; inline void run() noexcept; From e92c88a133286eb3b23d172aae07c1145c299b6f Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 10 May 2022 20:00:42 +0200 Subject: [PATCH 2420/2859] Make the documentation clearer, match the documentation when no buttons are pressed for an event --- components/lua_ui/widget.cpp | 4 +++- files/lua_api/openmw/ui.lua | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 4c1bb9752f..641f34c9ca 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -156,9 +156,11 @@ namespace LuaUi MyGUI::IntPoint absolutePosition = mWidget->getAbsolutePosition(); osg::Vec2f offset = position - osg::Vec2f(absolutePosition.left, absolutePosition.top); sol::table table = makeTable(); + int sdlButton = SDLUtil::myGuiMouseButtonToSdl(button); table["position"] = position; table["offset"] = offset; - table["button"] = SDLUtil::myGuiMouseButtonToSdl(button); + if (sdlButton == 0) // nil if no button was pressed + table["button"] = sdlButton; return table; } diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index b52d9c81e4..52660a5c3e 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -217,7 +217,8 @@ -- @type MouseEvent -- @field openmw.util#Vector2 position Absolute position of the mouse cursor -- @field openmw.util#Vector2 offset Position of the mouse cursor relative to the widget --- @field #number button Mouse button which triggered the event (could be nil) +-- @field #number button Mouse button which triggered the event. +-- Matches the arguments of @{openmw_input#input.isMouseButtonPressed} (`nil` for none, 1 for left, 3 for right). --- -- Register a new texture resource. Can be used to manually atlas UI textures. From 926cdfbe19b0dc0ebb3faf7181e0539b03690c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Wed, 11 May 2022 16:37:16 +0300 Subject: [PATCH 2421/2859] Use random seed specified by settings for new games --- apps/openmw/engine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index fa84bcf14c..f39daa44d8 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -837,6 +837,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()); mWorld->setupPlayer(); + mWorld->setRandomSeed(mRandomSeed); mEnvironment.setWorld(*mWorld); mWindowManager->setStore(mWorld->getStore()); From a710cf6d10af2782455fb1f50a6b8988d6c4d5f9 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 11 May 2022 23:12:16 +0200 Subject: [PATCH 2422/2859] Remove Environment cleanup Some managers may use the environment in the destructors. Setting them to nullptr may lead to nullptr dereference when the object is still alive and can be accessible. But after object is destructed it's UB anyway to dereference nullptr or a dangling pointer. --- apps/openmw/engine.cpp | 2 -- apps/openmw/mwbase/environment.cpp | 15 --------------- apps/openmw/mwbase/environment.hpp | 2 -- 3 files changed, 19 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f39daa44d8..982ae92f11 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -468,8 +468,6 @@ OMW::Engine::~Engine() mStereoManager = nullptr; - mEnvironment.cleanup(); - mMechanicsManager = nullptr; mDialogueManager = nullptr; mJournal = nullptr; diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 70027989a6..927db90288 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -28,21 +28,6 @@ MWBase::Environment::~Environment() sThis = nullptr; } -void MWBase::Environment::cleanup() -{ - mMechanicsManager = nullptr; - mDialogueManager = nullptr; - mJournal = nullptr; - mScriptManager = nullptr; - mWindowManager = nullptr; - mWorld = nullptr; - mSoundManager = nullptr; - mInputManager = nullptr; - mStateManager = nullptr; - mLuaManager = nullptr; - mResourceSystem = nullptr; -} - void MWBase::Environment::reportStats(unsigned int frameNumber, osg::Stats& stats) const { mMechanicsManager->reportStats(frameNumber, stats); diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index e8ea605a9a..0c5d39425b 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -112,8 +112,6 @@ namespace MWBase void setFrameDuration(float value) { mFrameDuration = value; } - void cleanup(); - /// Return instance of this class. static const Environment& get() { From de4a75821ea2fa418cb9eb52fb93a44f9c22a986 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 11 May 2022 23:43:35 +0000 Subject: [PATCH 2423/2859] Correct default window mode --- files/settings-default.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 3a41f72d9b..d97f529db2 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -613,7 +613,7 @@ resolution y = 600 # Specify the window mode. # 0 = Fullscreen, 1 = Windowed Fullscreen, 2 = Windowed -window mode = 0 +window mode = 2 # Determines which screen OpenMW is on. (>=0). screen = 0 From 95ad67eb8bed39e2beda04c82806a8fdec477342 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 12 May 2022 22:57:00 +0200 Subject: [PATCH 2424/2859] Add extraRoll to the Lua camera package --- apps/openmw/mwlua/camerabindings.cpp | 2 ++ apps/openmw/mwrender/camera.cpp | 2 +- apps/openmw/mwrender/camera.hpp | 4 +++- files/builtin_scripts/scripts/omw/camera.lua | 2 +- files/builtin_scripts/scripts/omw/head_bobbing.lua | 2 +- files/lua_api/openmw/camera.lua | 12 ++++++++++++ 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index 345564ef0c..32abb8babe 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -67,8 +67,10 @@ namespace MWLua api["setRoll"] = [camera](float v) { camera->setRoll(-v); }; api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); }; api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); }; + api["setExtraRoll"] = [camera](float v) { camera->setExtraRoll(-v); }; api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); }; api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); }; + api["getExtraRoll"] = [camera]() { return -camera->getExtraRoll(); }; api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); }; api["setPreferredThirdPersonDistance"] = [camera](float v) { camera->setPreferredCameraDistance(v); }; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index ea499a64bc..625b6c3e97 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -110,7 +110,7 @@ namespace MWRender void Camera::updateCamera(osg::Camera *cam) { - osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * + osg::Quat orient = osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); osg::Vec3d forward = orient * osg::Vec3d(0,1,0); diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index ba815a7e60..e17e63ddaf 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -60,8 +60,10 @@ namespace MWRender float getExtraPitch() const { return mExtraPitch; } float getExtraYaw() const { return mExtraYaw; } + float getExtraRoll() const { return mExtraRoll; } void setExtraPitch(float angle) { mExtraPitch = angle; } void setExtraYaw(float angle) { mExtraYaw = angle; } + void setExtraRoll(float angle) { mExtraRoll = angle; } /// @param Force view mode switch, even if currently not allowed by the animation. void toggleViewMode(bool force=false); @@ -125,7 +127,7 @@ namespace MWRender float mHeight; float mPitch, mYaw, mRoll; - float mExtraPitch = 0, mExtraYaw = 0; + float mExtraPitch = 0, mExtraYaw = 0, mExtraRoll = 0; bool mLockPitch = false, mLockYaw = false; osg::Vec3d mPosition; osg::Matrixf mViewMatrix; diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 56e7f52487..9845135f50 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -150,7 +150,7 @@ end local function onUpdate(dt) camera.setExtraPitch(0) camera.setExtraYaw(0) - camera.setRoll(0) + camera.setExtraRoll(0) camera.setFirstPersonOffset(util.vector3(0, 0, 0)) updateSmoothedSpeed(dt) end diff --git a/files/builtin_scripts/scripts/omw/head_bobbing.lua b/files/builtin_scripts/scripts/omw/head_bobbing.lua index 8e6ea0660f..b3d96a7eba 100644 --- a/files/builtin_scripts/scripts/omw/head_bobbing.lua +++ b/files/builtin_scripts/scripts/omw/head_bobbing.lua @@ -46,7 +46,7 @@ function M.update(dt, smoothedSpeed) local zOffset = (0.5 - effect) * coef * stepHeight -- range from -stepHeight/2 to stepHeight/2 local roll = ((stepState > 0 and 1) or -1) * effect * coef * maxRoll -- range from -maxRoll to maxRoll camera.setFirstPersonOffset(camera.getFirstPersonOffset() + util.vector3(0, 0, zOffset)) - camera.setRoll(camera.getRoll() + roll) + camera.setExtraRoll(camera.getExtraRoll() + roll) end return M diff --git a/files/lua_api/openmw/camera.lua b/files/lua_api/openmw/camera.lua index f699663c49..84858cadd4 100644 --- a/files/lua_api/openmw/camera.lua +++ b/files/lua_api/openmw/camera.lua @@ -111,6 +111,18 @@ -- @function [parent=#camera] setExtraYaw -- @param #number value +--- +-- Additional summand for the roll angle that is not affected by player input. +-- Full yaw is `getRoll()+getExtraRoll()`. +-- @function [parent=#camera] getExtraRoll +-- @return #number + +--- +-- Additional summand for the roll angle; useful for camera shaking effects. +-- Full yaw is `getRoll()+getExtraRoll()`. +-- @function [parent=#camera] setExtraRoll +-- @param #number value + --- -- Set camera position; can be used only if camera is in Static mode. -- @function [parent=#camera] setStaticPosition From 0643685ea547f3bddf84455655adb97feadfde32 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 13 May 2022 18:22:59 +0200 Subject: [PATCH 2425/2859] [Lua] Rename onInputUpdate -> onFrame and call it even when the game is on pause (#6745) --- apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 4 ++-- apps/openmw/mwlua/playerscripts.hpp | 6 +++--- .../reference/lua-scripting/engine_handlers.rst | 12 +++++++----- files/builtin_scripts/scripts/omw/camera.lua | 5 +++-- files/lua_api/openmw/nearby.lua | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 3c2023ff5f..dc50d093ca 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -41,7 +41,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 21; + api["API_REVISION"] = 22; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 602f32710f..3c458bd7b8 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -234,8 +234,8 @@ namespace MWLua playerScripts->processInputEvent(event); } mInputEvents.clear(); - if (playerScripts && !mWorldView.isPaused()) - playerScripts->inputUpdate(MWBase::Environment::get().getFrameDuration()); + if (playerScripts) + playerScripts->onFrame(mWorldView.isPaused() ? 0.0 : MWBase::Environment::get().getFrameDuration()); mProcessingInputEvents = false; MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index 1e909dc72c..eb248ccc40 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -20,7 +20,7 @@ namespace MWLua registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, - &mActionHandlers, &mInputUpdateHandlers, + &mActionHandlers, &mOnFrameHandlers, &mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved }); } @@ -57,7 +57,7 @@ namespace MWLua } } - void inputUpdate(float dt) { callEngineHandlers(mInputUpdateHandlers, dt); } + void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); } bool consoleCommand(const std::string& consoleMode, const std::string& command, const sol::object& selectedObject) { @@ -72,7 +72,7 @@ namespace MWLua EngineHandlerList mControllerButtonPressHandlers{"onControllerButtonPress"}; EngineHandlerList mControllerButtonReleaseHandlers{"onControllerButtonRelease"}; EngineHandlerList mActionHandlers{"onInputAction"}; - EngineHandlerList mInputUpdateHandlers{"onInputUpdate"}; + EngineHandlerList mOnFrameHandlers{"onFrame"}; EngineHandlerList mTouchpadPressed{ "onTouchPress" }; EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; EngineHandlerList mTouchpadMoved{ "onTouchMove" }; diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 6f24750736..524314341c 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -15,8 +15,8 @@ Engine handler is a function defined by a script, that can be called by the engi | `assigned to a script in openmw-cs (not yet implemented).` | ``onInterfaceOverride`` can be called before ``onInit``. * - onUpdate(dt) - - | Called every frame if the game is not paused. `dt` is the time - | from the last update in seconds. + - | Called every frame if the game is not paused. `dt` is + | the simulation time from the last update in seconds. * - onSave() -> savedData - | Called when the game is saving. May be called in inactive state, | so it shouldn't use `openmw.nearby`. @@ -69,9 +69,11 @@ Engine handler is a function defined by a script, that can be called by the engi .. list-table:: :widths: 20 80 - * - onInputUpdate(dt) - - | Called every frame (if the game is not paused) right after - | processing user input. Use it only for latency-critical stuff. + * - onFrame(dt) + - | Called every frame (even if the game is paused) right after + | processing user input. Use it only for latency-critical stuff + | and for UI that should work on pause. + | `dt` is simulation time delta (0 when on pause). * - onKeyPress(key) - | `Key `_ is pressed. | Usage example: diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 9845135f50..8a614c9ac3 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -155,7 +155,8 @@ local function onUpdate(dt) updateSmoothedSpeed(dt) end -local function onInputUpdate(dt) +local function onFrame(dt) + if core.isWorldPaused() then return end local mode = camera.getMode() if mode == MODE.FirstPerson or mode == MODE.ThirdPerson then primaryMode = mode @@ -232,7 +233,7 @@ return { }, engineHandlers = { onUpdate = onUpdate, - onInputUpdate = onInputUpdate, + onFrame = onFrame, onInputAction = function(action) if core.isWorldPaused() then return end if action == input.ACTION.ZoomIn then diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index f290992f33..076d5cebbd 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -74,7 +74,7 @@ --- -- Cast ray from one point to another and find the first visual intersection with anything in the scene. -- As opposite to `castRay` can find an intersection with an object without collisions. --- In order to avoid threading issues can be used only in player scripts only in `onInputUpdate` or +-- In order to avoid threading issues can be used only in player scripts only in `onFrame` or -- in engine handlers for user input. In other cases use `asyncCastRenderingRay` instead. -- @function [parent=#nearby] castRenderingRay -- @param openmw.util#Vector3 from Start point of the ray. From 3e343a53d0a213fec47f4aa994f9f3ca970c7428 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 13 May 2022 17:47:11 +0000 Subject: [PATCH 2426/2859] Fix wrong function name in Lua docs --- files/builtin_scripts/openmw_aux/ui.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/builtin_scripts/openmw_aux/ui.lua b/files/builtin_scripts/openmw_aux/ui.lua index ec9b81fdb1..44e1029b4b 100644 --- a/files/builtin_scripts/openmw_aux/ui.lua +++ b/files/builtin_scripts/openmw_aux/ui.lua @@ -16,7 +16,7 @@ local function deepContentCopy(content) end --- --- @function [parent=#ui] templates +-- @function [parent=#ui] deepLayoutCopy -- @param #table layout -- @return #table copied layout function aux_ui.deepLayoutCopy(layout) @@ -33,4 +33,4 @@ function aux_ui.deepLayoutCopy(layout) return result end -return aux_ui \ No newline at end of file +return aux_ui From 9649bfc4cb55abc36159275dc4e8afab4523b7a4 Mon Sep 17 00:00:00 2001 From: ShadIK02 Date: Fri, 13 May 2022 21:59:47 +0000 Subject: [PATCH 2427/2859] When building Win32 (VS 2019), there are problems in line 45. This change solves the problem. Special thanks to AnyOldName3 for the hint :) --- components/debug/gldebug.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/debug/gldebug.cpp b/components/debug/gldebug.cpp index ccf49edd40..a0c7aa6206 100644 --- a/components/debug/gldebug.cpp +++ b/components/debug/gldebug.cpp @@ -42,7 +42,7 @@ either expressed or implied, of the FreeBSD Project. namespace Debug { - void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) + void GL_APIENTRY debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { #ifdef GL_DEBUG_OUTPUT std::string srcStr; From 52d05be04ba193ba32385efd51f64972088449a4 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 14 May 2022 10:27:30 +0000 Subject: [PATCH 2428/2859] Styling for Settings --- components/lua_ui/container.cpp | 40 +- components/lua_ui/container.hpp | 6 + components/lua_ui/element.cpp | 27 +- components/lua_ui/flex.cpp | 18 +- components/lua_ui/flex.hpp | 6 +- components/lua_ui/image.cpp | 1 + components/lua_ui/textedit.cpp | 45 ++- components/lua_ui/textedit.hpp | 6 +- components/lua_ui/util.cpp | 1 + components/lua_ui/widget.cpp | 88 ++--- components/lua_ui/widget.hpp | 6 +- docs/source/reference/lua-scripting/api.rst | 3 + .../reference/lua-scripting/overview.rst | 3 + .../lua-scripting/user_interface.rst | 1 + .../lua-scripting/widgets/container.rst | 8 + .../reference/lua-scripting/widgets/flex.rst | 6 +- files/builtin_scripts/CMakeLists.txt | 2 + .../scripts/omw/mwui/borders.lua | 245 ++++++++---- .../scripts/omw/mwui/constants.lua | 7 +- .../builtin_scripts/scripts/omw/mwui/init.lua | 22 +- .../scripts/omw/mwui/space.lua | 39 ++ .../builtin_scripts/scripts/omw/mwui/text.lua | 11 +- .../scripts/omw/mwui/textEdit.lua | 18 +- .../scripts/omw/settings/common.lua | 13 +- .../scripts/omw/settings/player.lua | 76 ++-- .../scripts/omw/settings/render.lua | 355 +++++++++++------- .../scripts/omw/settings/renderers.lua | 39 ++ files/mygui/openmw_lua.xml | 2 +- 28 files changed, 719 insertions(+), 375 deletions(-) create mode 100644 docs/source/reference/lua-scripting/widgets/container.rst create mode 100644 files/builtin_scripts/scripts/omw/mwui/space.lua create mode 100644 files/builtin_scripts/scripts/omw/settings/renderers.lua diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp index 11b9c1b360..52fea684d7 100644 --- a/components/lua_ui/container.cpp +++ b/components/lua_ui/container.cpp @@ -7,11 +7,6 @@ namespace LuaUi void LuaContainer::updateChildren() { WidgetExtension::updateChildren(); - for (auto w : children()) - { - w->onCoordChange([this](WidgetExtension* child, MyGUI::IntCoord coord) - { updateSizeToFit(); }); - } updateSizeToFit(); } @@ -20,16 +15,39 @@ namespace LuaUi return MyGUI::IntSize(); } + MyGUI::IntSize LuaContainer::templateScalingSize() + { + return mInnerSize; + } + void LuaContainer::updateSizeToFit() { - MyGUI::IntSize size; + MyGUI::IntSize innerSize = MyGUI::IntSize(); for (auto w : children()) { - MyGUI::IntCoord coord = w->widget()->getCoord(); - size.width = std::max(size.width, coord.left + coord.width); - size.height = std::max(size.height, coord.top + coord.height); + 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()) + { + MyGUI::IntCoord coord = w->calculateCoord(); + outerSize.width = std::max(outerSize.width, coord.left + coord.width); + outerSize.height = std::max(outerSize.height, coord.top + coord.height); } - forceSize(size); - updateCoord(); + mInnerSize = innerSize; + mOuterSize = outerSize; + } + + MyGUI::IntSize LuaContainer::calculateSize() + { + return mOuterSize; + } + + void LuaContainer::updateCoord() + { + updateSizeToFit(); + WidgetExtension::updateCoord(); } } diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp index 664fb08ea2..1a8adee89f 100644 --- a/components/lua_ui/container.hpp +++ b/components/lua_ui/container.hpp @@ -9,12 +9,18 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaContainer) + MyGUI::IntSize calculateSize() override; + void updateCoord() override; + protected: void updateChildren() override; MyGUI::IntSize childScalingSize() override; + MyGUI::IntSize templateScalingSize() override; private: void updateSizeToFit(); + MyGUI::IntSize mInnerSize; + MyGUI::IntSize mOuterSize; }; } diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 9f70cfc1da..b55d89e613 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -24,7 +24,18 @@ namespace LuaUi std::string widgetType(const sol::table& layout) { - return layout.get_or(LayoutKeys::type, defaultWidgetType); + sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type); + std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType); + sol::object templateTypeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type); + if (templateTypeField != sol::nil) + { + std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType); + if (typeField != sol::nil && templateType != type) + throw std::logic_error(std::string("Template layout type ") + type + + std::string(" doesn't match template type ") + templateType); + type = templateType; + } + return type; } void destroyWidget(LuaUi::WidgetExtension* ext) @@ -103,18 +114,8 @@ namespace LuaUi WidgetExtension* createWidget(const sol::table& layout) { - sol::object typeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::type); - std::string type = LuaUtil::getValueOrDefault(typeField, defaultWidgetType); - sol::object templateTypeField = LuaUtil::getFieldOrNil(layout, LayoutKeys::templateLayout, LayoutKeys::type); - if (templateTypeField != sol::nil) - { - std::string templateType = LuaUtil::getValueOrDefault(templateTypeField, defaultWidgetType); - if (typeField != sol::nil && templateType != type) - throw std::logic_error(std::string("Template layout type ") + type - + std::string(" doesn't match template type ") + templateType); - type = templateType; - } static auto widgetTypeMap = widgetTypeToName(); + std::string type = widgetType(layout); if (widgetTypeMap.find(type) == widgetTypeMap.end()) throw std::logic_error(std::string("Invalid widget type ") += type); @@ -242,7 +243,7 @@ namespace LuaUi if (!mLayer.empty()) Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; mAttachedTo->setChildren({ mRoot }); - mRoot->updateCoord(); + mAttachedTo->updateCoord(); } } } diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index dfe943a02f..b54120e78b 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -56,7 +56,7 @@ namespace LuaUi MyGUI::IntSize flexSize = calculateSize(); int growSize = 0; float growFactor = 0; - if (totalGrow > 0 && !mAutoSized) + if (totalGrow > 0) { growSize = primary(flexSize) - primary(childrenSize); growFactor = growSize / totalGrow; @@ -67,22 +67,32 @@ namespace LuaUi for (auto* w : children()) { MyGUI::IntSize size = w->calculateSize(); + primary(size) += static_cast(growFactor * getGrow(w)); + float stretch = std::clamp(w->externalValue("stretch", 0.0f), 0.0f, 1.0f); + secondary(size) = std::max(secondary(size), static_cast(stretch * secondary(flexSize))); secondary(childPosition) = alignSize(secondary(flexSize), secondary(size), mArrange); w->forcePosition(childPosition); - primary(size) += static_cast(growFactor * getGrow(w)); w->forceSize(size); w->updateCoord(); primary(childPosition) += primary(size); - w->updateCoord(); } WidgetExtension::updateChildren(); } + MyGUI::IntSize LuaFlex::childScalingSize() + { + // Call the base method to prevent relativeSize feedback loop + MyGUI::IntSize size = WidgetExtension::calculateSize(); + if (mAutoSized) + primary(size) = 0; + return size; + } + MyGUI::IntSize LuaFlex::calculateSize() { MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) { - primary(size) = primary(mChildrenSize); + primary(size) = std::max(primary(size), primary(mChildrenSize)); secondary(size) = std::max(secondary(size), secondary(mChildrenSize)); } return size; diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp index 7c583dc3c3..50a3404425 100644 --- a/components/lua_ui/flex.hpp +++ b/components/lua_ui/flex.hpp @@ -14,10 +14,8 @@ namespace LuaUi MyGUI::IntSize calculateSize() override; void updateProperties() override; void updateChildren() override; - MyGUI::IntSize childScalingSize() override - { - return MyGUI::IntSize(); - } + MyGUI::IntSize childScalingSize() override; + void updateCoord() override; private: diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 6bcc4a5846..8319780b23 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -31,6 +31,7 @@ namespace LuaUi { changeWidgetSkin("LuaImage"); mTileRect = dynamic_cast(getSubWidgetMain()); + WidgetExtension::initialize(); } void LuaImage::updateProperties() diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index ac44872f21..0441eb4e5d 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -6,38 +6,57 @@ namespace LuaUi { void LuaTextEdit::initialize() { - changeWidgetSkin("LuaTextEdit"); - - eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); - + mEditBox = createWidget("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); + mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); + registerEvents(mEditBox); WidgetExtension::initialize(); } void LuaTextEdit::deinitialize() { - eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); + mEditBox->eventEditTextChange -= MyGUI::newDelegate(this, &LuaTextEdit::textChange); + clearEvents(mEditBox); WidgetExtension::deinitialize(); } void LuaTextEdit::updateProperties() { - setCaption(propertyValue("text", std::string())); - setFontHeight(propertyValue("textSize", 10)); - setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); - setEditMultiLine(propertyValue("multiline", false)); - setEditWordWrap(propertyValue("wordWrap", false)); + mEditBox->setCaption(propertyValue("text", std::string())); + mEditBox->setFontHeight(propertyValue("textSize", 10)); + mEditBox->setTextColour(propertyValue("textColor", MyGUI::Colour(0, 0, 0, 1))); + mEditBox->setEditMultiLine(propertyValue("multiline", false)); + mEditBox->setEditWordWrap(propertyValue("wordWrap", false)); Alignment horizontal(propertyValue("textAlignH", Alignment::Start)); Alignment vertical(propertyValue("textAlignV", Alignment::Start)); - setTextAlign(alignmentToMyGui(horizontal, vertical)); + mEditBox->setTextAlign(alignmentToMyGui(horizontal, vertical)); - setEditStatic(propertyValue("readOnly", false)); + mEditBox->setEditStatic(propertyValue("readOnly", false)); WidgetExtension::updateProperties(); } void LuaTextEdit::textChange(MyGUI::EditBox*) { - triggerEvent("textChanged", sol::make_object(lua(), getCaption().asUTF8())); + triggerEvent("textChanged", sol::make_object(lua(), mEditBox->getCaption().asUTF8())); + } + + void LuaTextEdit::updateCoord() + { + WidgetExtension::updateCoord(); + { + MyGUI::IntSize slotSize = slot()->calculateSize(); + MyGUI::IntPoint slotPosition = slot()->widget()->getAbsolutePosition() - widget()->getAbsolutePosition(); + MyGUI::IntCoord slotCoord(slotPosition, slotSize); + mEditBox->setCoord(slotCoord); + } + } + + void LuaTextEdit::updateChildren() + { + WidgetExtension::updateChildren(); + // otherwise it won't be focusable + mEditBox->detachFromWidget(); + mEditBox->attachToWidget(this); } } diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index 208240283c..1158e91e2c 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -7,7 +7,7 @@ namespace LuaUi { - class LuaTextEdit : public MyGUI::EditBox, public WidgetExtension + class LuaTextEdit : public MyGUI::Widget, public WidgetExtension { MYGUI_RTTI_DERIVED(LuaTextEdit) @@ -15,9 +15,13 @@ namespace LuaUi void initialize() override; void deinitialize() override; void updateProperties() override; + void updateCoord() override; + void updateChildren() override; private: void textChange(MyGUI::EditBox*); + + MyGUI::EditBox* mEditBox; }; } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index d7e7390647..f3cb0d288c 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -39,6 +39,7 @@ namespace LuaUi { "LuaWindow", "Window" }, { "LuaImage", "Image" }, { "LuaFlex", "Flex" }, + { "LuaContainer", "Container" }, }; return types; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 641f34c9ca..bd9e47ab34 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -35,37 +35,13 @@ namespace LuaUi void WidgetExtension::initialize() { // \todo might be more efficient to only register these if there are Lua callbacks - mWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::keyPress); - mWidget->eventKeyButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::keyRelease); - mWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &WidgetExtension::mouseClick); - mWidget->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WidgetExtension::mouseDoubleClick); - mWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::mousePress); - mWidget->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease); - mWidget->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove); - mWidget->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag); - - mWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); - mWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); - mWidget->eventKeySetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); - mWidget->eventKeyLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); + registerEvents(mWidget); } void WidgetExtension::deinitialize() { clearCallbacks(); - mWidget->eventKeyButtonPressed.clear(); - mWidget->eventKeyButtonReleased.clear(); - mWidget->eventMouseButtonClick.clear(); - mWidget->eventMouseButtonDoubleClick.clear(); - mWidget->eventMouseButtonPressed.clear(); - mWidget->eventMouseButtonReleased.clear(); - mWidget->eventMouseMove.clear(); - mWidget->eventMouseDrag.m_event.clear(); - - mWidget->eventMouseSetFocus.clear(); - mWidget->eventMouseLostFocus.clear(); - mWidget->eventKeySetFocus.clear(); - mWidget->eventKeyLostFocus.clear(); + clearEvents(mWidget); mOnCoordChange.reset(); @@ -75,6 +51,39 @@ namespace LuaUi w->deinitialize(); } + void WidgetExtension::registerEvents(MyGUI::Widget* w) + { + w->eventKeyButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::keyPress); + w->eventKeyButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::keyRelease); + w->eventMouseButtonClick += MyGUI::newDelegate(this, &WidgetExtension::mouseClick); + w->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WidgetExtension::mouseDoubleClick); + w->eventMouseButtonPressed += MyGUI::newDelegate(this, &WidgetExtension::mousePress); + w->eventMouseButtonReleased += MyGUI::newDelegate(this, &WidgetExtension::mouseRelease); + w->eventMouseMove += MyGUI::newDelegate(this, &WidgetExtension::mouseMove); + w->eventMouseDrag += MyGUI::newDelegate(this, &WidgetExtension::mouseDrag); + + w->eventMouseSetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); + w->eventMouseLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); + w->eventKeySetFocus += MyGUI::newDelegate(this, &WidgetExtension::focusGain); + w->eventKeyLostFocus += MyGUI::newDelegate(this, &WidgetExtension::focusLoss); + } + void WidgetExtension::clearEvents(MyGUI::Widget* w) + { + w->eventKeyButtonPressed.clear(); + w->eventKeyButtonReleased.clear(); + w->eventMouseButtonClick.clear(); + w->eventMouseButtonDoubleClick.clear(); + w->eventMouseButtonPressed.clear(); + w->eventMouseButtonReleased.clear(); + w->eventMouseMove.clear(); + w->eventMouseDrag.m_event.clear(); + + w->eventMouseSetFocus.clear(); + w->eventMouseLostFocus.clear(); + w->eventKeySetFocus.clear(); + w->eventKeyLostFocus.clear(); + } + void WidgetExtension::reset() { // detach all children from the slot widget, in case it gets destroyed @@ -188,25 +197,11 @@ namespace LuaUi void WidgetExtension::updateTemplate() { - WidgetExtension* oldSlot = mSlot; WidgetExtension* slot = findDeepInTemplates("slot"); if (slot == nullptr) mSlot = this; else mSlot = slot->mSlot; - if (mSlot != oldSlot) - { - MyGUI::IntSize slotSize = mSlot->widget()->getSize(); - MyGUI::IntPoint slotPosition = mSlot->widget()->getAbsolutePosition() - widget()->getAbsolutePosition(); - MyGUI::IntCoord slotCoord(slotPosition, slotSize); - MyGUI::Widget* clientWidget = mWidget->getClientWidget(); - if (!clientWidget) - clientWidget = mWidget; - if (clientWidget->getSubWidgetMain()) - clientWidget->getSubWidgetMain()->setCoord(slotCoord); - if (clientWidget->getSubWidgetText()) - clientWidget->getSubWidgetText()->setCoord(slotCoord); - } } void WidgetExtension::setCallback(const std::string& name, const LuaUtil::Callback& callback) @@ -290,10 +285,12 @@ namespace LuaUi MyGUI::IntSize WidgetExtension::parentSize() { - if (mParent && !mTemplateChild) - return mParent->childScalingSize(); + if (!mParent) + return widget()->getParentSize(); // size of the layer + if (mTemplateChild) + return mParent->templateScalingSize(); else - return widget()->getParentSize(); + return mParent->childScalingSize(); } MyGUI::IntSize WidgetExtension::calculateSize() @@ -334,6 +331,11 @@ namespace LuaUi return mSlot->widget()->getSize(); } + MyGUI::IntSize WidgetExtension::templateScalingSize() + { + return widget()->getSize(); + } + void WidgetExtension::triggerEvent(std::string_view name, sol::object argument) const { auto it = mCallbacks.find(name); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 239d2e6bb3..b6bef55235 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -70,16 +70,20 @@ namespace LuaUi virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); + MyGUI::IntCoord calculateCoord(); protected: virtual void initialize(); + void registerEvents(MyGUI::Widget* w); + void clearEvents(MyGUI::Widget* w); + sol::table makeTable() const; sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; MyGUI::IntSize parentSize(); - MyGUI::IntCoord calculateCoord(); virtual MyGUI::IntSize childScalingSize(); + virtual MyGUI::IntSize templateScalingSize(); template T propertyValue(std::string_view name, const T& defaultValue) diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index a6009edf1a..0c850d3676 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -111,3 +111,6 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. + * - :ref:`MWUI ` + - by player scripts + - Morrowind-style UI templates. diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index e21469a53f..a075d9228e 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -476,6 +476,9 @@ The order in which the scripts are started is important. So if one mod should ov * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. + * - :ref:`MWUI ` + - by player scripts + - Morrowind-style UI templates. Event system ============ diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index 394c53e688..d1d3162a4b 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -82,6 +82,7 @@ Widget types TextEdit: Accepts text input from the user. Image: Renders a texture. Flex: Aligns children in a column/row + Container: Wraps around its children Example ------- diff --git a/docs/source/reference/lua-scripting/widgets/container.rst b/docs/source/reference/lua-scripting/widgets/container.rst new file mode 100644 index 0000000000..6b15f25110 --- /dev/null +++ b/docs/source/reference/lua-scripting/widgets/container.rst @@ -0,0 +1,8 @@ +Container Widget +================ + +Wraps around its children. Convenient for creating border-type templates. + +Relative size and position don't work for children. + +For template children, relative size and position depend on the children's combined size. \ No newline at end of file diff --git a/docs/source/reference/lua-scripting/widgets/flex.rst b/docs/source/reference/lua-scripting/widgets/flex.rst index 2648e88996..468cb93958 100644 --- a/docs/source/reference/lua-scripting/widgets/flex.rst +++ b/docs/source/reference/lua-scripting/widgets/flex.rst @@ -15,7 +15,8 @@ Properties - description * - horizontal - bool (false) - - Flex aligns its children in a row if true, otherwise in a column. + - | Flex aligns its children in a row (main axis is horizontal) if true, + | otherwise in a column (main axis is vertical). * - autoSize - bool (true) - | If true, Flex will automatically resize to fit its contents. @@ -41,3 +42,6 @@ External - | Grow factor for the child. If there is unused space in the Flex, | it will be split between widgets according to this value. | Has no effect if `autoSize` is `true`. + * - stretch + - float (0) + - | Stretches the child to a percentage of the Flex's cross axis size. diff --git a/files/builtin_scripts/CMakeLists.txt b/files/builtin_scripts/CMakeLists.txt index 318e389585..103351d044 100644 --- a/files/builtin_scripts/CMakeLists.txt +++ b/files/builtin_scripts/CMakeLists.txt @@ -21,6 +21,7 @@ set(LUA_BUILTIN_FILES scripts/omw/settings/global.lua scripts/omw/settings/common.lua scripts/omw/settings/render.lua + scripts/omw/settings/renderers.lua l10n/Calendar/en.yaml @@ -29,6 +30,7 @@ set(LUA_BUILTIN_FILES scripts/omw/mwui/box.lua scripts/omw/mwui/text.lua scripts/omw/mwui/textEdit.lua + scripts/omw/mwui/space.lua scripts/omw/mwui/init.lua ) diff --git a/files/builtin_scripts/scripts/omw/mwui/borders.lua b/files/builtin_scripts/scripts/omw/mwui/borders.lua index d0d3e50cdf..bd8d99a7b7 100644 --- a/files/builtin_scripts/scripts/omw/mwui/borders.lua +++ b/files/builtin_scripts/scripts/omw/mwui/borders.lua @@ -1,120 +1,203 @@ local ui = require('openmw.ui') local util = require('openmw.util') +local auxUi = require('openmw_aux.ui') + local constants = require('scripts.omw.mwui.constants') local v2 = util.vector2 +local whiteTexture = ui.texture{ path = 'white' } +local menuTransparency = ui._getMenuTransparency() local sideParts = { - left = util.vector2(0, 0.5), - right = util.vector2(1, 0.5), - top = util.vector2(0.5, 0), - bottom = util.vector2(0.5, 1), + left = v2(0, 0), + right = v2(1, 0), + top = v2(0, 0), + bottom = v2(0, 1), } local cornerParts = { - top_left_corner = util.vector2(0, 0), - top_right_corner = util.vector2(1, 0), - bottom_left_corner = util.vector2(0, 1), - bottom_right_corner = util.vector2(1, 1), + top_left = v2(0, 0), + top_right = v2(1, 0), + bottom_left = v2(0, 1), + bottom_right = v2(1, 1), } -local resources = {} +local borderSidePattern = 'textures/menu_thin_border_%s.dds' +local borderCornerPattern = 'textures/menu_thin_border_%s_corner.dds' + +local borderResources = {} do - local boxBorderPattern = 'textures/menu_thin_border_%s.dds' - for k, _ in pairs(sideParts) do - resources[k] = ui.texture{ path = boxBorderPattern:format(k) } + for k in pairs(sideParts) do + borderResources[k] = ui.texture{ path = borderSidePattern:format(k) } end - for k, _ in pairs(cornerParts) do - resources[k] = ui.texture{ path = boxBorderPattern:format(k) } + for k in pairs(cornerParts) do + borderResources[k] = ui.texture{ path = borderCornerPattern:format(k) } end end local borderPieces = {} -for k, align in pairs(sideParts) do - local resource = resources[k] - local horizontal = align.x ~= 0.5 - borderPieces[#borderPieces + 1] = { +for k in pairs(sideParts) do + local horizontal = k == 'top' or k == 'bottom' + borderPieces[k] = { type = ui.TYPE.Image, props = { - resource = resource, - relativePosition = align, - anchor = align, - relativeSize = horizontal and v2(0, 1) or v2(1, 0), - size = (horizontal and v2(1, -2) or v2(-2, 1)) * constants.borderSize, - tileH = not horizontal, - tileV = horizontal, + resource = borderResources[k], + tileH = horizontal, + tileV = not horizontal, }, } end - -for k, align in pairs(cornerParts) do - local resource = resources[k] - borderPieces[#borderPieces + 1] = { +for k in pairs(cornerParts) do + borderPieces[k] = { type = ui.TYPE.Image, props = { - resource = resource, - relativePosition = align, - anchor = align, - size = v2(1, 1) * constants.borderSize, + resource = borderResources[k], }, } end -borderPieces[#borderPieces + 1] = { - external = { - slot = true, - }, - props = { - position = v2(1, 1) * (constants.borderSize + constants.padding), - size = v2(-2, -2) * (constants.borderSize + constants.padding), - relativeSize = v2(1, 1), - }, -} -local borders = { - content = ui.content(borderPieces) -} -borders.content:add({ - external = { - slot = true, - }, - props = { - size = v2(-2, -2) * constants.borderSize, - }, -}) - -local horizontalLine = { - content = ui.content { - { - type = ui.TYPE.Image, + +local function borderTemplates(borderSize) + local borderV = v2(1, 1) * borderSize + local result = {} + result.horizontalLine = { + type = ui.TYPE.Image, + props = { + resource = borderResources.top, + tileH = true, + tileV = false, + size = v2(0, borderSize), + relativeSize = v2(1, 0), + }, + } + + result.verticalLine = { + type = ui.TYPE.Image, + props = { + resource = borderResources.left, + tileH = false, + tileV = true, + size = v2(borderSize, 0), + relativeSize = v2(0, 1), + }, + } + + result.borders = { + content = ui.content {}, + } + for k, v in pairs(sideParts) do + local horizontal = k == 'top' or k == 'bottom' + local direction = horizontal and v2(1, 0) or v2(0, 1) + result.borders.content:add { + template = borderPieces[k], + props = { + position = (direction - v) * borderSize, + relativePosition = v, + size = (v2(1, 1) - direction * 3) * borderSize, + relativeSize = direction, + } + } + end + for k, v in pairs(cornerParts) do + result.borders.content:add { + template = borderPieces[k], props = { - resource = resources.top, - tileH = true, - tileV = false, - size = v2(0, constants.borderSize), - relativeSize = v2(1, 0), + position = -v * borderSize, + relativePosition = v, + size = borderV, }, - }, - }, -} + } + end + result.borders.content:add { + external = { slot = true }, + props = { + position = borderV, + size = borderV * -2, + relativeSize = v2(1, 1), + } + } -local verticalLine = { - content = ui.content { - { - type = ui.TYPE.Image, + result.box = { + type = ui.TYPE.Container, + content = ui.content{}, + } + for k, v in pairs(sideParts) do + local horizontal = k == 'top' or k == 'bottom' + local direction = horizontal and v2(1, 0) or v2(0, 1) + result.box.content:add { + template = borderPieces[k], props = { - resource = resources.left, - tileH = false, - tileV = true, - size = v2(constants.borderSize, 0), - relativeSize = v2(0, 1), + position = (direction + v) * borderSize, + relativePosition = v, + size = (v2(1, 1) - direction) * borderSize, + relativeSize = direction, + } + } + end + for k, v in pairs(cornerParts) do + result.box.content:add { + template = borderPieces[k], + props = { + position = v * borderSize, + relativePosition = v, + size = borderV, }, + } + end + result.box.content:add { + external = { slot = true }, + props = { + position = borderV, + relativeSize = v2(1, 1), + } + } + + local backgroundTransparent = { + type = ui.TYPE.Image, + props = { + resource = whiteTexture, + color = util.color.rgb(0, 0, 0), + alpha = menuTransparency, }, - }, -} + } + local backgroundSolid = { + type = ui.TYPE.Image, + props = { + resource = whiteTexture, + color = util.color.rgb(0, 0, 0), + }, + } + + result.boxTransparent = auxUi.deepLayoutCopy(result.box) + result.boxTransparent.content:insert(1, { + template = backgroundTransparent, + props = { + relativeSize = v2(1, 1), + size = borderV * 2, + }, + }) + + result.boxSolid = auxUi.deepLayoutCopy(result.box) + result.boxSolid.content:insert(1, { + template = backgroundSolid, + props = { + relativeSize = v2(1, 1), + size = borderV * 2, + }, + }) + + return result +end + +local thinBorders = borderTemplates(constants.border) +local thickBorders = borderTemplates(constants.thickBorder) return function(templates) - templates.borders = borders - templates.horizontalLine = horizontalLine - templates.verticalLine = verticalLine + for k, t in pairs(thinBorders) do + templates[k] = t + end + for k, t in pairs(thickBorders) do + templates[k .. 'Thick'] = t + end end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/constants.lua b/files/builtin_scripts/scripts/omw/mwui/constants.lua index ddfe5c9214..3791025717 100644 --- a/files/builtin_scripts/scripts/omw/mwui/constants.lua +++ b/files/builtin_scripts/scripts/omw/mwui/constants.lua @@ -2,7 +2,10 @@ local util = require('openmw.util') return { textNormalSize = 16, - sandColor = util.color.rgb(202 / 255, 165 / 255, 96 / 255), - borderSize = 4, + textHeaderSize = 16, + headerColor = util.color.rgb(223 / 255, 201 / 255, 159 / 255), + normalColor = util.color.rgb(202 / 255, 165 / 255, 96 / 255), + border = 2, + thickBorder = 4, padding = 2, } \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/init.lua b/files/builtin_scripts/scripts/omw/mwui/init.lua index af41bece98..d95a07040b 100644 --- a/files/builtin_scripts/scripts/omw/mwui/init.lua +++ b/files/builtin_scripts/scripts/omw/mwui/init.lua @@ -57,21 +57,27 @@ end -- @field [parent=#MWUI] #Templates templates local templates = {} +--- +-- Container that adds padding around its content. +-- @field [parent=#MWUI] #table padding +--- +-- Standard spacing interval +-- @field [parent=#MWUI] #number interval +require('scripts.omw.mwui.space')(templates) + --- -- Standard rectangular border -- @field [parent=#Templates] openmw.ui#Layout border -require('scripts.omw.mwui.borders')(templates) - --- --- Border combined with a transparent background +-- Container wrapping the content with borders -- @field [parent=#Templates] openmw.ui#Layout box --- --- A transparent background --- @field [parent=#Templates] openmw.ui#Layout backgroundTransparent +-- Same as box, but with a semi-transparent background +-- @field [parent=#Templates] openmw.ui#Layout boxTransparent --- --- A solid, non-transparent background --- @field [parent=#Templates] openmw.ui#Layout backgroundSolid -require('scripts.omw.mwui.box')(templates) +-- Same as box, but with a solid background +-- @field [parent=#Templates] openmw.ui#Layout boxSolid +require('scripts.omw.mwui.borders')(templates) --- -- Standard "sand" colored text diff --git a/files/builtin_scripts/scripts/omw/mwui/space.lua b/files/builtin_scripts/scripts/omw/mwui/space.lua new file mode 100644 index 0000000000..1f65a98e7f --- /dev/null +++ b/files/builtin_scripts/scripts/omw/mwui/space.lua @@ -0,0 +1,39 @@ +local ui = require('openmw.ui') +local util = require('openmw.util') + +local constants = require('scripts.omw.mwui.constants') + +local borderV = util.vector2(1, 1) * constants.border + +return function(templates) + templates.padding = { + type = ui.TYPE.Container, + content = ui.content { + { + props = { + size = borderV, + }, + }, + { + external = { slot = true }, + props = { + position = borderV, + relativeSize = util.vector2(1, 1), + }, + }, + { + props = { + position = borderV, + relativePosition = util.vector2(1, 1), + size = borderV, + }, + }, + } + } + templates.interval = { + type = ui.TYPE.Widget, + props = { + size = borderV, + }, + } +end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/text.lua b/files/builtin_scripts/scripts/omw/mwui/text.lua index f62e4384c1..8fc1133000 100644 --- a/files/builtin_scripts/scripts/omw/mwui/text.lua +++ b/files/builtin_scripts/scripts/omw/mwui/text.lua @@ -6,10 +6,19 @@ local textNormal = { type = ui.TYPE.Text, props = { textSize = constants.textNormalSize, - textColor = constants.sandColor, + textColor = constants.normalColor, + }, +} + +local textHeader = { + type = ui.TYPE.Text, + props = { + textSize = constants.textHeaderSize, + textColor = constants.headerColor, }, } return function(templates) templates.textNormal = textNormal + templates.textHeader = textHeader end \ No newline at end of file diff --git a/files/builtin_scripts/scripts/omw/mwui/textEdit.lua b/files/builtin_scripts/scripts/omw/mwui/textEdit.lua index 1fbc619611..4f86aa356f 100644 --- a/files/builtin_scripts/scripts/omw/mwui/textEdit.lua +++ b/files/builtin_scripts/scripts/omw/mwui/textEdit.lua @@ -3,6 +3,8 @@ local ui = require('openmw.ui') local constants = require('scripts.omw.mwui.constants') +local borderOffset = util.vector2(1, 1) * constants.border + return function(templates) local borderContent = ui.content { { @@ -12,10 +14,14 @@ return function(templates) }, content = ui.content { { + props = { + position = borderOffset, + relativeSize = util.vector2(1, 1), + }, external = { slot = true, }, - }, + } } }, } @@ -23,10 +29,9 @@ return function(templates) templates.textEditLine = { type = ui.TYPE.TextEdit, props = { + size = util.vector2(150, constants.textNormalSize) + borderOffset * 4, textSize = constants.textNormalSize, - textColor = constants.sandColor, - textAlignH = ui.ALIGNMENT.Start, - textAlignV = ui.ALIGNMENT.Center, + textColor = constants.normalColor, multiline = false, }, content = borderContent, @@ -35,10 +40,9 @@ return function(templates) templates.textEditBox = { type = ui.TYPE.TextEdit, props = { + size = util.vector2(150, 5 * constants.textNormalSize) + borderOffset * 4, textSize = constants.textNormalSize, - textColor = constants.sandColor, - textAlignH = ui.ALIGNMENT.Start, - textAlignV = ui.ALIGNMENT.Start, + textColor = constants.normalColor, multiline = true, wordWrap = true, }, diff --git a/files/builtin_scripts/scripts/omw/settings/common.lua b/files/builtin_scripts/scripts/omw/settings/common.lua index 1e87dfd62b..8150f2cc61 100644 --- a/files/builtin_scripts/scripts/omw/settings/common.lua +++ b/files/builtin_scripts/scripts/omw/settings/common.lua @@ -21,8 +21,8 @@ local function validateSettingOptions(options) if type(options.name) ~= 'string' then error('Setting must have a name localization key') end - if type(options.description) ~= 'string' then - error('Setting must have a descripiton localization key') + if options.description ~= nil and type(options.description) ~= 'string' then + error('Setting description key must be a string') end end @@ -49,8 +49,8 @@ local function validateGroupOptions(options) if type(options.name) ~= 'string' then error('Group must have a name localization key') end - if type(options.description) ~= 'string' then - error('Group must have a description localization key') + if options.description ~= nil and type(options.description) ~= 'string' then + error('Group description key must be a string') end if type(options.settings) ~= 'table' then error('Group must have a table of settings') @@ -89,8 +89,9 @@ local function registerGroup(options) settings = {}, } local valueSection = contextSection(options.key) - for _, opt in ipairs(options.settings) do + for i, opt in ipairs(options.settings) do local setting = registerSetting(opt) + setting.order = i if group.settings[setting.key] then error(('Duplicate setting key %s'):format(options.key)) end @@ -123,7 +124,7 @@ return { local section = contextSection(groupKey) saved[groupKey] = {} for key, value in pairs(section:asTable()) do - if not group.settings[key].permanentStorage then + if group.settings[key] and not group.settings[key].permanentStorage then saved[groupKey][key] = value end end diff --git a/files/builtin_scripts/scripts/omw/settings/player.lua b/files/builtin_scripts/scripts/omw/settings/player.lua index 612ba3ec7b..a4201bc8a8 100644 --- a/files/builtin_scripts/scripts/omw/settings/player.lua +++ b/files/builtin_scripts/scripts/omw/settings/player.lua @@ -1,39 +1,21 @@ -local ui = require('openmw.ui') -local async = require('openmw.async') -local util = require('openmw.util') - local common = require('scripts.omw.settings.common') local render = require('scripts.omw.settings.render') -render.registerRenderer('text', function(value, set, arg) - return { - type = ui.TYPE.TextEdit, - props = { - size = util.vector2(arg and arg.size or 150, 30), - text = value, - textColor = util.color.rgb(1, 1, 1), - textSize = 15, - textAlignV = ui.ALIGNMENT.End, - }, - events = { - textChanged = async:callback(function(s) set(s) end), - }, - } -end) +require('scripts.omw.settings.renderers')(render.registerRenderer) --- -- @type PageOptions -- @field #string key A unique key -- @field #string l10n A localization context (an argument of core.l10n) -- @field #string name A key from the localization context --- @field #string description A key from the localization context +-- @field #string description A key from the localization context (optional, can be `nil`) --- -- @type GroupOptions -- @field #string key A unique key, starts with "Settings" by convention -- @field #string l10n A localization context (an argument of core.l10n) -- @field #string name A key from the localization context --- @field #string description A key from the localization context +-- @field #string description A key from the localization context (optional, can be `nil`) -- @field #string page Key of a page which will contain this group -- @field #number order Groups within the same page are sorted by this number, or their key for equal values. -- Defaults to 0. @@ -43,7 +25,7 @@ end) -- @type SettingOptions -- @field #string key A unique key -- @field #string name A key from the localization context --- @field #string description A key from the localization context +-- @field #string description A key from the localization context (optional, can be `nil`) -- @field default A default value -- @field #string renderer A renderer key -- @field argument An argument for the renderer @@ -57,26 +39,33 @@ return { -- -- In a player script -- local storage = require('openmw.storage') -- local I = require('openmw.interfaces') - -- I.Settings.registerGroup({ + -- I.Settings.registerPage { + -- key = 'MyModPage', + -- l10n = 'MyMod', + -- name = 'My Mod Name', + -- description = 'My Mod Description', + -- } + -- I.Settings.registerGroup { -- key = 'SettingsPlayerMyMod', - -- page = 'MyPage', - -- l10n = 'mymod', - -- name = 'modName', - -- description = 'modDescription', + -- page = 'MyModPage', + -- l10n = 'MyMod', + -- name = 'My Group Name', + -- description = 'My Group Description', -- settings = { -- { -- key = 'Greeting', - -- renderer = 'text', - -- name = 'greetingName', - -- description = 'greetingDescription', + -- renderer = 'textLine', + -- name = 'Greeting', + -- description = 'Text to display when the game starts', -- default = 'Hello, world!', - -- argument = { - -- size = 200, - -- }, + -- permanentStorage = false, -- }, -- }, - -- }) + -- } -- local playerSettings = storage.playerSection('SettingsPlayerMyMod') + -- ... + -- ui.showMessage(playerSettings:get('Greeting')) + -- -- ... -- -- access a setting page registered by a global script -- local globalSettings = storage.globalSection('SettingsGlobalMyMod') interface = { @@ -132,22 +121,19 @@ return { -- settings = { -- { -- key = 'Greeting', - -- saveOnly = true, + -- permanentStorage = true, -- default = 'Hi', - -- renderer = 'text', - -- argument = { - -- size = 200, - -- }, + -- renderer = 'textLine', -- name = 'Text Input', -- description = 'Short text input', -- }, -- { - -- key = 'Key', - -- saveOnly = false, - -- default = input.KEY.LeftAlt, - -- renderer = 'keybind', - -- name = 'Key', - -- description = 'Bind Key', + -- key = 'Flag', + -- permanentStorage = false, + -- default = false, + -- renderer = 'yeNo', + -- name = 'Flag', + -- description = 'Flag toggle', -- }, -- } -- } diff --git a/files/builtin_scripts/scripts/omw/settings/render.lua b/files/builtin_scripts/scripts/omw/settings/render.lua index c072d20e94..8274f04670 100644 --- a/files/builtin_scripts/scripts/omw/settings/render.lua +++ b/files/builtin_scripts/scripts/omw/settings/render.lua @@ -3,6 +3,7 @@ local util = require('openmw.util') local async = require('openmw.async') local core = require('openmw.core') local storage = require('openmw.storage') +local I = require('openmw.interfaces') local common = require('scripts.omw.settings.common') @@ -15,34 +16,68 @@ local pages = {} local groups = {} local pageOptions = {} -local padding = function(size) - return { - props = { - size = util.vector2(size, size), - } - } -end -local smallPadding = padding(10) -local bigPadding = padding(25) - -local pageHeader = { - props = { - textColor = util.color.rgb(1, 1, 1), - textSize = 30, +local interval = { template = I.MWUI.templates.interval } +local growingIntreval = { + template = I.MWUI.templates.interval, + external = { + grow = 1, }, } -local groupHeader = { +local spacer = { props = { - textColor = util.color.rgb(1, 1, 1), - textSize = 25, + size = util.vector2(0, 10), }, } -local normal = { +local bigSpacer = { props = { - textColor = util.color.rgb(1, 1, 1), - textSize = 20, + size = util.vector2(0, 50), + }, +} +local stretchingLine = { + template = I.MWUI.templates.horizontalLine, + external = { + stretch = 1, }, } +local spacedLines = function(count) + local content = {} + table.insert(content, spacer) + table.insert(content, stretchingLine) + for i = 2, count do + table.insert(content, interval) + table.insert(content, stretchingLine) + end + table.insert(content, spacer) + return { + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content(content), + } +end + +local function interlaceSeparator(layouts, separator) + local result = {} + result[1] = layouts[1] + for i = 2, #layouts do + table.insert(result, separator) + table.insert(result, layouts[i]) + end + return result +end + +local function setSettingValue(global, groupKey, settingKey, value) + if global then + core.sendGlobalEvent(common.setGlobalEvent, { + groupKey = groupKey, + settingKey = settingKey, + value = value, + }) + else + storage.playerSection(groupKey):set(settingKey, value) + end +end local function renderSetting(group, setting, value, global) local renderFunction = renderers[setting.renderer] @@ -50,48 +85,83 @@ local function renderSetting(group, setting, value, global) error(('Setting %s of %s has unknown renderer %s'):format(setting.key, group.key, setting.renderer)) end local set = function(value) - if global then - core.sendGlobalEvent(common.setGlobalEvent, { - groupKey = group.key, - settingKey = setting.key, - value = value, - }) - else - storage.playerSection(group.key):set(setting.key, value) - end + setSettingValue(global, group.key, setting.key, value) end local l10n = core.l10n(group.l10n) - return { - name = setting.key, + local titleLayout = { type = ui.TYPE.Flex, content = ui.content { { - type = ui.TYPE.Flex, + template = I.MWUI.templates.textNormal, props = { - horizontal = true, - align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.End, + text = l10n(setting.name), + textSize = 18, }, + }, + }, + } + if setting.description then + titleLayout.content:add(interval) + titleLayout.content:add { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(setting.description), + textSize = 16, + }, + } + end + return { + name = setting.key, + type = ui.TYPE.Flex, + props = { + horizontal = true, + arrange = ui.ALIGNMENT.Center, + }, + external = { + stretch = 1, + }, + content = ui.content { + titleLayout, + growingIntreval, + renderFunction(value, set, setting.argument), + }, + } +end + +local groupLayoutName = function(key, global) + return ('%s%s'):format(global and 'global_' or 'player_', key) +end + +local function renderGroup(group, global) + local l10n = core.l10n(group.l10n) + + local valueSection = common.getSection(global, group.key) + local settingLayouts = {} + local sortedSettings = {} + for _, setting in pairs(group.settings) do + sortedSettings[setting.order] = setting + end + for _, setting in ipairs(sortedSettings) do + table.insert(settingLayouts, renderSetting(group, setting, valueSection:get(setting.key), global)) + end + local settingsContent = ui.content(interlaceSeparator(settingLayouts, spacedLines(1))) + + local resetButtonLayout = { + template = I.MWUI.templates.box, + content = ui.content { + { + template = I.MWUI.templates.padding, content = ui.content { { - type = ui.TYPE.Text, - template = normal, - props = { - text = l10n(setting.name), - }, - }, - smallPadding, - renderFunction(value, set, setting.argument), - smallPadding, - { - type = ui.TYPE.Text, - template = normal, + template = I.MWUI.templates.textNormal, props = { text = 'Reset', }, events = { mouseClick = async:callback(function() - set(setting.default) + for _, setting in pairs(group.settings) do + setSettingValue(global, group.key, setting.key, setting.default) + end end), }, }, @@ -99,60 +169,66 @@ local function renderSetting(group, setting, value, global) }, }, } -end -local groupLayoutName = function(key, global) - return ('%s%s'):format(global and 'global_' or 'player_', key) -end + local titleLayout = { + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content { + { + template = I.MWUI.templates.textHeader, + props = { + text = l10n(group.name), + textSize = 20, + }, + } + }, + } + if group.description then + titleLayout.content:add(interval) + titleLayout.content:add { + template = I.MWUI.templates.textHeader, + props = { + text = l10n(group.description), + textSize = 18, + }, + } + end -local function renderGroup(group, global) - local l10n = core.l10n(group.l10n) - local layout = { + return { name = groupLayoutName(group.key, global), type = ui.TYPE.Flex, + external = { + stretch = 1, + }, content = ui.content { { type = ui.TYPE.Flex, props = { horizontal = true, - align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.End, + arrange = ui.ALIGNMENT.Center, + }, + external = { + stretch = 1, }, content = ui.content { - { - name = 'name', - type = ui.TYPE.Text, - template = groupHeader, - props = { - text = l10n(group.name), - }, - }, - smallPadding, - { - name = 'description', - type = ui.TYPE.Text, - template = normal, - props = { - text = l10n(group.description), - }, - }, + titleLayout, + growingIntreval, + resetButtonLayout, }, }, - smallPadding, + spacedLines(2), { name = 'settings', type = ui.TYPE.Flex, - content = ui.content{}, + content = settingsContent, + external = { + stretch = 1, + }, }, - bigPadding, }, } - local settingsContent = layout.content.settings.content - local valueSection = common.getSection(global, group.key) - for _, setting in pairs(group.settings) do - settingsContent:add(renderSetting(group, setting, valueSection:get(setting.key), global)) - end - return layout end local function pageGroupComparator(a, b) @@ -165,16 +241,22 @@ local function generateSearchHints(page) local hints = {} local l10n = core.l10n(page.l10n) table.insert(hints, l10n(page.name)) - table.insert(hints, l10n(page.description)) + if page.description then + table.insert(hints, l10n(page.description)) + end local pageGroups = groups[page.key] for _, pageGroup in pairs(pageGroups) do local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) local l10n = core.l10n(group.l10n) table.insert(hints, l10n(group.name)) - table.insert(hints, l10n(group.description)) + if group.description then + table.insert(hints, l10n(group.description)) + end for _, setting in pairs(group.settings) do table.insert(hints, l10n(setting.name)) - table.insert(hints, l10n(setting.description)) + if setting.description then + table.insert(hints, l10n(setting.description)) + end end end return table.concat(hints, ' ') @@ -182,55 +264,60 @@ end local function renderPage(page) local l10n = core.l10n(page.l10n) - local layout = { - name = page.key, + local sortedGroups = {} + for i, v in ipairs(groups[page.key]) do sortedGroups[i] = v end + table.sort(sortedGroups, pageGroupComparator) + local groupLayouts = {} + for _, pageGroup in ipairs(sortedGroups) do + local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) + table.insert(groupLayouts, renderGroup(group, pageGroup.global)) + end + local groupsLayout = { + name = 'groups', + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content(interlaceSeparator(groupLayouts, bigSpacer)), + } + local titleLayout = { type = ui.TYPE.Flex, + external = { + stretch = 1, + }, content = ui.content { - smallPadding, { - type = ui.TYPE.Flex, + template = I.MWUI.templates.textHeader, props = { - horizontal = true, - align = ui.ALIGNMENT.Start, - arrange = ui.ALIGNMENT.End, - }, - content = ui.content { - { - name = 'name', - type = ui.TYPE.Text, - template = pageHeader, - props = { - text = l10n(page.name), - }, - }, - smallPadding, - { - name = 'description', - type = ui.TYPE.Text, - template = normal, - props = { - text = l10n(page.description), - }, - }, + text = l10n(page.name), + textSize = 22, }, }, - bigPadding, - { - name = 'groups', - type = ui.TYPE.Flex, - content = ui.content {}, - }, + spacedLines(3), }, } - local groupsContent = layout.content.groups.content - local pageGroups = groups[page.key] - local sortedGroups = {} - for i, v in ipairs(pageGroups) do sortedGroups[i] = v end - table.sort(sortedGroups, pageGroupComparator) - for _, pageGroup in ipairs(sortedGroups) do - local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) - groupsContent:add(renderGroup(group, pageGroup.global)) + if page.description then + titleLayout.content:add { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(page.description), + textSize = 20, + }, + } end + local layout = { + name = page.key, + type = ui.TYPE.Flex, + props = { + position = util.vector2(10, 10), + }, + content = ui.content { + titleLayout, + bigSpacer, + groupsLayout, + bigSpacer, + }, + } return { name = l10n(page.name), element = ui.create(layout), @@ -241,13 +328,15 @@ end local function onSettingChanged(global) return async:callback(function(groupKey, settingKey) local group = common.getSection(global, common.groupSectionKey):get(groupKey) - if not pageOptions[group.page] then return end + if not group or not pageOptions[group.page] then return end - local element = pageOptions[group.page].element - local groupLayout = element.layout.content.groups.content[groupLayoutName(group.key, global)] - local settingsLayout = groupLayout.content.settings local value = common.getSection(global, group.key):get(settingKey) - settingsLayout.content[settingKey] = renderSetting(group, group.settings[settingKey], value, global) + + 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() end) end @@ -293,8 +382,8 @@ local function registerPage(options) if type(options.name) ~= 'string' then error('Page must have a name') end - if type(options.description) ~= 'string' then - error('Page must have a description') + if options.description ~= nil and type(options.description) ~= 'string' then + error('Page description key must be a string') end local page = { key = options.key, diff --git a/files/builtin_scripts/scripts/omw/settings/renderers.lua b/files/builtin_scripts/scripts/omw/settings/renderers.lua new file mode 100644 index 0000000000..9b862c3b4b --- /dev/null +++ b/files/builtin_scripts/scripts/omw/settings/renderers.lua @@ -0,0 +1,39 @@ +local ui = require('openmw.ui') +local async = require('openmw.async') +local I = require('openmw.interfaces') + +return function(registerRenderer) + registerRenderer('textLine', function(value, set) + return { + template = I.MWUI.templates.textEditLine, + props = { + text = tostring(value), + }, + events = { + textChanged = async:callback(function(s) set(s) end), + }, + } + end) + + registerRenderer('yesNo', function(value, set) + return { + template = I.MWUI.templates.box, + content = ui.content { + { + template = I.MWUI.templates.padding, + content = ui.content { + { + template = I.MWUI.templates.textNormal, + props = { + text = value and 'Yes' or 'No', + }, + events = { + mouseClick = async:callback(function() set(not value) end), + }, + }, + }, + }, + }, + } + end) +end \ No newline at end of file diff --git a/files/mygui/openmw_lua.xml b/files/mygui/openmw_lua.xml index 6e5e232a2a..b792b1c21f 100644 --- a/files/mygui/openmw_lua.xml +++ b/files/mygui/openmw_lua.xml @@ -12,7 +12,7 @@ - + From 1766f89c4d45423fe13f2a8e8411b518b2910e4f Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 14 May 2022 16:55:11 +0200 Subject: [PATCH 2429/2859] Fix a typo --- components/lua_ui/widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index bd9e47ab34..49d69f8fd1 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -168,7 +168,7 @@ namespace LuaUi int sdlButton = SDLUtil::myGuiMouseButtonToSdl(button); table["position"] = position; table["offset"] = offset; - if (sdlButton == 0) // nil if no button was pressed + if (sdlButton != 0) // nil if no button was pressed table["button"] = sdlButton; return table; } From c0cc5feecd70f6f4f8f07735c8011e52cc111f14 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 15 May 2022 14:52:44 +0200 Subject: [PATCH 2430/2859] Initialize DialInfo::DATAstruct with the proper blank values --- CHANGELOG.md | 1 + components/esm3/loadinfo.cpp | 7 +------ components/esm3/loadinfo.hpp | 2 ++ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f5868589..2d264b5f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ Bug #6717: Broken script causes interpreter stack corruption Bug #6718: Throwable weapons cause arrow enchantment effect to be applied to the whole body Bug #6730: LoopGroup stalls animation after playing :Stop frame until another animation is played + Bug #6753: Info records without a DATA subrecords are loaded incorrectly Feature #890: OpenMW-CS: Column filtering Feature #1465: "Reset" argument for AI functions Feature #2491: Ability to make OpenMW "portable" diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 0c7c4db61a..20604c8be4 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -133,12 +133,7 @@ namespace ESM void DialInfo::blank() { - mData.mUnknown1 = 0; - mData.mDisposition = 0; - mData.mRank = 0; - mData.mGender = 0; - mData.mPCrank = 0; - mData.mUnknown2 = 0; + mData = {}; mSelects.clear(); mPrev.clear(); diff --git a/components/esm3/loadinfo.hpp b/components/esm3/loadinfo.hpp index 7f135087ee..067dc62471 100644 --- a/components/esm3/loadinfo.hpp +++ b/components/esm3/loadinfo.hpp @@ -43,6 +43,8 @@ struct DialInfo signed char mGender; // See Gender enum signed char mPCrank; // Player rank signed char mUnknown2; + + DATAstruct() : mDisposition(0), mRank(-1), mGender(Gender::NA), mPCrank(-1) {} }; // 12 bytes DATAstruct mData; From 04843fed6d5fd7f784912480536b44f6d3c40cec Mon Sep 17 00:00:00 2001 From: cody glassman Date: Fri, 13 May 2022 18:58:00 -0700 Subject: [PATCH 2431/2859] moddable post-processing pipeline --- CHANGELOG.md | 1 + apps/launcher/advancedpage.cpp | 19 + apps/launcher/advancedpage.hpp | 1 + apps/opencs/model/world/data.cpp | 1 + apps/openmw/CMakeLists.txt | 6 +- apps/openmw/engine.cpp | 5 + apps/openmw/mwbase/windowmanager.hpp | 3 + apps/openmw/mwbase/world.hpp | 7 + apps/openmw/mwgui/postprocessorhud.cpp | 454 +++++++ apps/openmw/mwgui/postprocessorhud.hpp | 107 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 32 +- apps/openmw/mwgui/windowmanagerimp.hpp | 4 + apps/openmw/mwinput/actionmanager.cpp | 3 + apps/openmw/mwinput/actions.hpp | 2 + apps/openmw/mwinput/bindingsmanager.cpp | 6 +- apps/openmw/mwlua/inputbindings.cpp | 1 + apps/openmw/mwlua/luabindings.hpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 2 + apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/postprocessingbindings.cpp | 140 +++ apps/openmw/mwrender/animation.cpp | 4 +- apps/openmw/mwrender/characterpreview.cpp | 20 +- apps/openmw/mwrender/hdr.cpp | 123 ++ apps/openmw/mwrender/hdr.hpp | 71 ++ apps/openmw/mwrender/localmap.cpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 25 +- apps/openmw/mwrender/pingpongcanvas.cpp | 260 ++++ apps/openmw/mwrender/pingpongcanvas.hpp | 88 ++ apps/openmw/mwrender/pingpongcull.cpp | 47 + apps/openmw/mwrender/pingpongcull.hpp | 22 + apps/openmw/mwrender/postprocessor.cpp | 936 ++++++++++----- apps/openmw/mwrender/postprocessor.hpp | 210 +++- apps/openmw/mwrender/renderingmanager.cpp | 85 +- apps/openmw/mwrender/renderingmanager.hpp | 8 +- apps/openmw/mwrender/screenshotmanager.cpp | 11 +- apps/openmw/mwrender/sky.cpp | 10 + apps/openmw/mwrender/sky.hpp | 3 + apps/openmw/mwrender/skyutil.cpp | 6 + apps/openmw/mwrender/skyutil.hpp | 1 + apps/openmw/mwrender/transparentpass.cpp | 89 ++ apps/openmw/mwrender/transparentpass.hpp | 38 + apps/openmw/mwrender/water.cpp | 2 + apps/openmw/mwworld/scene.cpp | 5 + apps/openmw/mwworld/weather.cpp | 8 +- apps/openmw/mwworld/weather.hpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 15 + apps/openmw/mwworld/worldimp.hpp | 7 + apps/openmw_test_suite/CMakeLists.txt | 4 + apps/openmw_test_suite/fx/lexer.cpp | 216 ++++ apps/openmw_test_suite/fx/technique.cpp | 204 ++++ apps/openmw_test_suite/lua/testing_util.hpp | 5 + .../settings/shadermanager.cpp | 66 ++ components/CMakeLists.txt | 4 + components/fx/lexer.cpp | 301 +++++ components/fx/lexer.hpp | 75 ++ components/fx/lexer_types.hpp | 56 + components/fx/parse_constants.hpp | 133 +++ components/fx/pass.cpp | 253 ++++ components/fx/pass.hpp | 79 ++ components/fx/stateupdater.cpp | 60 + components/fx/stateupdater.hpp | 192 +++ components/fx/technique.cpp | 1049 +++++++++++++++++ components/fx/technique.hpp | 300 +++++ components/fx/types.hpp | 259 ++++ components/fx/widgets.cpp | 164 +++ components/fx/widgets.hpp | 266 +++++ components/resource/scenemanager.cpp | 21 +- components/resource/scenemanager.hpp | 15 +- components/sceneutil/clearcolor.hpp | 42 + components/sceneutil/depth.cpp | 12 - components/sceneutil/depth.hpp | 3 - components/sceneutil/rtt.cpp | 2 +- components/sceneutil/util.cpp | 36 - components/serialization/osgyaml.hpp | 64 + components/settings/shadermanager.hpp | 174 +++ components/shader/shadervisitor.cpp | 74 +- components/shader/shadervisitor.hpp | 12 +- components/std140/ubo.hpp | 162 +++ components/stereo/multiview.cpp | 32 +- components/stereo/multiview.hpp | 5 +- components/stereo/stereomanager.hpp | 1 - components/terrain/chunkmanager.cpp | 4 +- components/terrain/material.cpp | 17 +- components/terrain/material.hpp | 6 +- components/vfs/archive.hpp | 2 + components/vfs/bsaarchive.hpp | 4 + components/vfs/filesystemarchive.hpp | 2 + components/vfs/manager.cpp | 11 + components/vfs/manager.hpp | 5 + docs/source/reference/index.rst | 1 + docs/source/reference/lua-scripting/api.rst | 61 +- .../lua-scripting/openmw_postprocessing.rst | 5 + .../reference/lua-scripting/overview.rst | 60 +- .../reference/modding/settings/index.rst | 1 + .../modding/settings/postprocessing.rst | 65 + .../reference/modding/settings/windows.rst | 14 + .../source/reference/postprocessing/index.rst | 11 + .../source/reference/postprocessing/omwfx.rst | 862 ++++++++++++++ .../reference/postprocessing/overview.rst | 45 + files/lua_api/CMakeLists.txt | 1 + files/lua_api/openmw/input.lua | 1 + files/lua_api/openmw/postprocessing.lua | 92 ++ files/mygui/CMakeLists.txt | 2 + files/mygui/openmw_list.skin.xml | 8 + files/mygui/openmw_postprocessor_hud.layout | 138 +++ files/mygui/openmw_postprocessor_hud.skin.xml | 79 ++ files/mygui/openmw_resources.xml | 11 + files/mygui/openmw_settings_window.layout | 33 +- files/mygui/skins.xml | 1 + files/settings-default.cfg | 30 +- files/shaders/CMakeLists.txt | 6 + .../blended_depth_postpass_fragment.glsl | 19 + .../blended_depth_postpass_vertex.glsl | 20 + files/shaders/fullscreen_tri_fragment.glsl | 10 + files/shaders/fullscreen_tri_vertex.glsl | 9 + files/shaders/groundcover_fragment.glsl | 14 +- files/shaders/groundcover_vertex.glsl | 5 +- files/shaders/hdr_fragment.glsl | 14 + files/shaders/hdr_luminance_fragment.glsl | 17 + files/shaders/nv_default_fragment.glsl | 13 +- files/shaders/objects_fragment.glsl | 19 +- files/shaders/openmw_fragment.glsl | 9 +- files/shaders/openmw_fragment.h.glsl | 4 +- files/shaders/openmw_fragment_multiview.glsl | 9 +- files/shaders/terrain_fragment.glsl | 21 +- files/shaders/water_fragment.glsl | 4 + files/ui/advancedpage.ui | 88 ++ files/vfs/CMakeLists.txt | 7 + files/vfs/shaders/displaydepth.omwfx | 26 + files/vfs/shaders/main.omwfx | 45 + 130 files changed, 8603 insertions(+), 566 deletions(-) create mode 100644 apps/openmw/mwgui/postprocessorhud.cpp create mode 100644 apps/openmw/mwgui/postprocessorhud.hpp create mode 100644 apps/openmw/mwlua/postprocessingbindings.cpp create mode 100644 apps/openmw/mwrender/hdr.cpp create mode 100644 apps/openmw/mwrender/hdr.hpp create mode 100644 apps/openmw/mwrender/pingpongcanvas.cpp create mode 100644 apps/openmw/mwrender/pingpongcanvas.hpp create mode 100644 apps/openmw/mwrender/pingpongcull.cpp create mode 100644 apps/openmw/mwrender/pingpongcull.hpp create mode 100644 apps/openmw/mwrender/transparentpass.cpp create mode 100644 apps/openmw/mwrender/transparentpass.hpp create mode 100644 apps/openmw_test_suite/fx/lexer.cpp create mode 100644 apps/openmw_test_suite/fx/technique.cpp create mode 100644 apps/openmw_test_suite/settings/shadermanager.cpp create mode 100644 components/fx/lexer.cpp create mode 100644 components/fx/lexer.hpp create mode 100644 components/fx/lexer_types.hpp create mode 100644 components/fx/parse_constants.hpp create mode 100644 components/fx/pass.cpp create mode 100644 components/fx/pass.hpp create mode 100644 components/fx/stateupdater.cpp create mode 100644 components/fx/stateupdater.hpp create mode 100644 components/fx/technique.cpp create mode 100644 components/fx/technique.hpp create mode 100644 components/fx/types.hpp create mode 100644 components/fx/widgets.cpp create mode 100644 components/fx/widgets.hpp create mode 100755 components/sceneutil/clearcolor.hpp create mode 100644 components/serialization/osgyaml.hpp create mode 100644 components/settings/shadermanager.hpp create mode 100644 components/std140/ubo.hpp create mode 100644 docs/source/reference/lua-scripting/openmw_postprocessing.rst create mode 100644 docs/source/reference/modding/settings/postprocessing.rst create mode 100644 docs/source/reference/postprocessing/index.rst create mode 100644 docs/source/reference/postprocessing/omwfx.rst create mode 100644 docs/source/reference/postprocessing/overview.rst create mode 100644 files/lua_api/openmw/postprocessing.lua create mode 100644 files/mygui/openmw_postprocessor_hud.layout create mode 100644 files/mygui/openmw_postprocessor_hud.skin.xml create mode 100644 files/shaders/blended_depth_postpass_fragment.glsl create mode 100644 files/shaders/blended_depth_postpass_vertex.glsl create mode 100644 files/shaders/fullscreen_tri_fragment.glsl create mode 100644 files/shaders/fullscreen_tri_vertex.glsl create mode 100644 files/shaders/hdr_fragment.glsl create mode 100644 files/shaders/hdr_luminance_fragment.glsl create mode 100644 files/vfs/shaders/displaydepth.omwfx create mode 100644 files/vfs/shaders/main.omwfx diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d264b5f02..5c64b831bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,7 @@ Feature #2858: Add a tab to the launcher for handling datafolders Feature #3245: Grid and angle snapping for the OpenMW-CS Feature #3616: Allow Zoom levels on the World Map + Feature #4067: Post Processing Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4414: Handle duration of EXTRA SPELL magic effect Feature #4595: Unique object identifier diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index fc1d84a61f..6f4ca60edf 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -145,6 +145,12 @@ bool Launcher::AdvancedPage::loadSettings() objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain")); loadSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); + + connect(postprocessEnabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotPostProcessToggled(bool))); + loadSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing"); + loadSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing"); + loadSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing"); + postprocessHDRTimeComboBox->setValue(Settings::Manager::getDouble("hdr exposure time", "Post Processing")); } // Audio @@ -302,6 +308,11 @@ void Launcher::AdvancedPage::saveSettings() Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); saveSettingBool(nightDaySwitchesCheckBox, "day night switches", "Game"); + + saveSettingBool(postprocessEnabledCheckBox, "enabled", "Post Processing"); + saveSettingBool(postprocessLiveReloadCheckBox, "live reload", "Post Processing"); + saveSettingBool(postprocessTransparentPostpassCheckBox, "transparent postpass", "Post Processing"); + Settings::Manager::setDouble("hdr exposure time", "Post Processing", postprocessHDRTimeComboBox->value()); } // Audio @@ -464,3 +475,11 @@ void Launcher::AdvancedPage::slotViewOverShoulderToggled(bool checked) { viewOverShoulderVerticalLayout->setEnabled(viewOverShoulderCheckBox->checkState()); } + +void Launcher::AdvancedPage::slotPostProcessToggled(bool checked) +{ + postprocessLiveReloadCheckBox->setEnabled(checked); + postprocessTransparentPostpassCheckBox->setEnabled(checked); + postprocessHDRTimeComboBox->setEnabled(checked); + postprocessHDRTimeLabel->setEnabled(checked); +} diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index 1d16fae706..6a3f8319d2 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -30,6 +30,7 @@ namespace Launcher void on_runScriptAfterStartupBrowseButton_clicked(); void slotAnimSourcesToggled(bool checked); void slotViewOverShoulderToggled(bool checked); + void slotPostProcessToggled(bool checked); private: Config::GameSettings &mGameSettings; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 12274b912f..b10efaa3a4 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -84,6 +84,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat defines["radialFog"] = "0"; defines["lightingModel"] = "0"; defines["reverseZ"] = "0"; + defines["refraction_enabled"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7bbf179d95..1756824164 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -22,7 +22,8 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover + postprocessor pingpongcull hdr pingpongcanvas transparentpass ) add_openmw_dir (mwinput @@ -43,6 +44,7 @@ add_openmw_dir (mwgui tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher + postprocessorhud ) add_openmw_dir (mwdialogue @@ -59,7 +61,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings - camerabindings uibindings inputbindings nearbybindings stats + camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats types/types types/door types/actor types/container types/weapon types/npc types/creature ) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 982ae92f11..af3db12b5f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -46,6 +46,8 @@ #include #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -987,6 +989,8 @@ void OMW::Engine::go() Settings::Manager settings; std::string settingspath = settings.load(mCfgMgr); + Settings::ShaderManager::get().load((mCfgMgr.getUserConfigPath() / "shaders.yaml").string()); + MWClass::registerClasses(); // Create encoder @@ -1110,6 +1114,7 @@ void OMW::Engine::go() // Save user settings settings.saveUser(settingspath); + Settings::ShaderManager::get().save(); mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string()); Log(Debug::Info) << "Quitting peacefully."; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index b0076b02f2..c1c99436cb 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -70,6 +70,7 @@ namespace MWGui class WindowModal; class JailScreen; class MessageBox; + class PostProcessorHud; enum ShowInDialogueMode { ShowInDialogueMode_IfPossible, @@ -147,6 +148,7 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual const std::vector getActiveMessageBoxes() = 0; + virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force=false) = 0; @@ -326,6 +328,7 @@ namespace MWBase virtual void toggleConsole() = 0; virtual void toggleDebugWindow() = 0; + virtual void togglePostProcessorHud() = 0; /// Cycle to next or previous spell virtual void cycleSpell(bool next) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 336af37a89..15177b4301 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -67,6 +67,7 @@ namespace MWRender class Animation; class Camera; class RenderingManager; + class PostProcessor; } namespace MWMechanics @@ -238,6 +239,10 @@ namespace MWBase virtual int getCurrentWeather() const = 0; + virtual int getNextWeather() const = 0; + + virtual float getWeatherTransition() const = 0; + virtual unsigned int getNightDayMode() const = 0; virtual int getMasserPhase() const = 0; @@ -664,6 +669,8 @@ namespace MWBase virtual Misc::Rng::Generator& getPrng() = 0; virtual MWRender::RenderingManager* getRenderingManager() = 0; + + virtual MWRender::PostProcessor* getPostProcessor() = 0; }; } diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp new file mode 100644 index 0000000000..cfdbfda7e8 --- /dev/null +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -0,0 +1,454 @@ +#include "postprocessorhud.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "../mwrender/postprocessor.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +namespace +{ + void saveChain() + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + std::ostringstream chain; + + for (size_t i = 1; i < processor->getTechniques().size(); ++i) + { + auto technique = processor->getTechniques()[i]; + + if (!technique) + continue; + + chain << technique->getName(); + + if (i < processor-> getTechniques().size() - 1) + chain << ","; + } + + Settings::Manager::setString("chain", "Post Processing", chain.str()); + } +} + +namespace MWGui +{ + void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) + { + if (MyGUI::InputManager::getInstance().isShiftPressed() && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) + return; + + MyGUI::ListBox::onKeyButtonPressed(key, ch); + } + + PostProcessorHud::PostProcessorHud() + : WindowBase("openmw_postprocessor_hud.layout") + { + getWidget(mTabConfiguration, "TabConfiguration"); + getWidget(mActiveList, "ActiveList"); + getWidget(mInactiveList, "InactiveList"); + getWidget(mModeToggle, "ModeToggle"); + getWidget(mConfigLayout, "ConfigLayout"); + getWidget(mFilter, "Filter"); + getWidget(mButtonActivate, "ButtonActivate"); + getWidget(mButtonDeactivate, "ButtonDeactivate"); + getWidget(mButtonUp, "ButtonUp"); + getWidget(mButtonDown, "ButtonDown"); + + mButtonActivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyActivatePressed); + mButtonDeactivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyDeactivatePressed); + mButtonUp->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderUpPressed); + mButtonDown->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderDownPressed); + + mActiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); + mInactiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); + + mActiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); + mInactiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); + + mModeToggle->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyModeToggle); + + mFilter->eventEditTextChange += MyGUI::newDelegate(this, &PostProcessorHud::notifyFilterChanged); + + mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &PostProcessorHud::notifyWindowResize); + + mShaderInfo = mConfigLayout->createWidget("HeaderText", {}, MyGUI::Align::Default); + mShaderInfo->setUserString("VStretch", "true"); + mShaderInfo->setUserString("HStretch", "true"); + mShaderInfo->setTextAlign(MyGUI::Align::Left | MyGUI::Align::Top); + mShaderInfo->setEditReadOnly(true); + mShaderInfo->setEditWordWrap(true); + mShaderInfo->setEditMultiLine(true); + + mConfigLayout->setVisibleVScroll(true); + + mConfigArea = mConfigLayout->createWidget("", {}, MyGUI::Align::Default); + } + + void PostProcessorHud::notifyFilterChanged(MyGUI::EditBox* sender) + { + updateTechniques(); + } + + void PostProcessorHud::notifyWindowResize(MyGUI::Window* sender) + { + layout(); + } + + void PostProcessorHud::notifyResetButtonClicked(MyGUI::Widget* sender) + { + for (size_t i = 1; i < mConfigArea->getChildCount(); ++i) + { + if (auto* child = dynamic_cast(mConfigArea->getChildAt(i))) + child->toDefault(); + } + } + + void PostProcessorHud::notifyListChangePosition(MyGUI::ListBox* sender, size_t index) + { + if (sender == mActiveList) + mInactiveList->clearIndexSelected(); + else if (sender == mInactiveList) + mActiveList->clearIndexSelected(); + + if (index >= sender->getItemCount()) + return; + + updateConfigView(sender->getItemNameAt(index)); + } + + void PostProcessorHud::toggleTechnique(bool enabled) + { + auto* list = enabled ? mInactiveList : mActiveList; + + size_t selected = list->getIndexSelected(); + + if (selected != MyGUI::ITEM_NONE) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + mOverrideHint = list->getItemNameAt(selected); + if (enabled) + processor->enableTechnique(*list->getItemDataAt>(selected)); + else + processor->disableTechnique(*list->getItemDataAt>(selected)); + saveChain(); + } + } + + void PostProcessorHud::notifyActivatePressed(MyGUI::Widget* sender) + { + toggleTechnique(true); + } + + void PostProcessorHud::notifyDeactivatePressed(MyGUI::Widget* sender) + { + toggleTechnique(false); + } + + void PostProcessorHud::moveShader(Direction direction) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + size_t selected = mActiveList->getIndexSelected(); + + if (selected == MyGUI::ITEM_NONE) + return; + + int index = direction == Direction::Up ? static_cast(selected) - 1 : selected + 1; + index = std::clamp(index, 0, mActiveList->getItemCount() - 1); + + if (static_cast(index) != selected) + { + if (processor->enableTechnique(*mActiveList->getItemDataAt>(selected), index)) + saveChain(); + } + } + + void PostProcessorHud::notifyShaderUpPressed(MyGUI::Widget* sender) + { + moveShader(Direction::Up); + } + + void PostProcessorHud::notifyShaderDownPressed(MyGUI::Widget* sender) + { + moveShader(Direction::Down); + } + + void PostProcessorHud::notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch) + { + MyGUI::ListBox* list = static_cast(sender); + + if (list->getIndexSelected() == MyGUI::ITEM_NONE) + return; + + if (key == MyGUI::KeyCode::ArrowLeft && list == mActiveList) + { + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + toggleTechnique(false); + } + else + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mInactiveList); + mActiveList->clearIndexSelected(); + select(mInactiveList, 0); + } + } + else if (key == MyGUI::KeyCode::ArrowRight && list == mInactiveList) + { + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + toggleTechnique(true); + } + else + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mActiveList); + mInactiveList->clearIndexSelected(); + select(mActiveList, 0); + } + } + else if (list == mActiveList && MyGUI::InputManager::getInstance().isShiftPressed() && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) + { + moveShader(key == MyGUI::KeyCode::ArrowUp ? Direction::Up : Direction::Down); + } + } + + void PostProcessorHud::notifyModeToggle(MyGUI::Widget* sender) + { + Settings::ShaderManager::Mode prev = Settings::ShaderManager::get().getMode(); + toggleMode(prev == Settings::ShaderManager::Mode::Debug ? Settings::ShaderManager::Mode::Normal : Settings::ShaderManager::Mode::Debug); + } + + void PostProcessorHud::onOpen() + { + toggleMode(Settings::ShaderManager::Mode::Debug); + updateTechniques(); + } + + void PostProcessorHud::onClose() + { + toggleMode(Settings::ShaderManager::Mode::Normal); + } + + void PostProcessorHud::layout() + { + constexpr int padding = 12; + constexpr int padding2 = padding * 2; + mShaderInfo->setCoord(padding, padding, mConfigLayout->getSize().width - padding2 - padding, mShaderInfo->getTextSize().height); + + int totalHeight = mShaderInfo->getTop() + mShaderInfo->getTextSize().height + padding; + + mConfigArea->setCoord({padding, totalHeight, mShaderInfo->getSize().width, mConfigLayout->getHeight()}); + + int childHeights = 0; + MyGUI::EnumeratorWidgetPtr enumerator = mConfigArea->getEnumerator(); + while (enumerator.next()) + { + enumerator.current()->setCoord(padding, childHeights + padding, mShaderInfo->getSize().width - padding2, enumerator.current()->getHeight()); + childHeights += enumerator.current()->getHeight() + padding; + } + totalHeight += childHeights; + + mConfigArea->setSize(mConfigArea->getWidth(), childHeights); + + mConfigLayout->setCanvasSize(mConfigLayout->getWidth() - padding2, totalHeight); + mConfigLayout->setSize(mConfigLayout->getWidth(), mConfigLayout->getParentSize().height - padding2); + } + + void PostProcessorHud::select(ListWrapper* list, size_t index) + { + list->setIndexSelected(index); + notifyListChangePosition(list, index); + } + + void PostProcessorHud::toggleMode(Settings::ShaderManager::Mode mode) + { + Settings::ShaderManager::get().setMode(mode); + + mModeToggle->setCaptionWithReplacing(mode == Settings::ShaderManager::Mode::Debug ? "#{sOn}" :"#{sOff}"); + + MWBase::Environment::get().getWorld()->getPostProcessor()->toggleMode(); + + if (!isVisible()) + return; + + if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) + updateConfigView(mInactiveList->getItemNameAt(mInactiveList->getIndexSelected())); + else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) + updateConfigView(mActiveList->getItemNameAt(mActiveList->getIndexSelected())); + } + + void PostProcessorHud::updateConfigView(const std::string& name) + { + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + auto technique = processor->loadTechnique(name); + + if (!technique) + return; + + while (mConfigArea->getChildCount() > 0) + MyGUI::Gui::getInstance().destroyWidget(mConfigArea->getChildAt(0)); + + mShaderInfo->setCaption(""); + + if (!technique) + return; + + std::ostringstream ss; + + const std::string NA = "NA"; + const std::string endl = "\n"; + + std::string author = technique->getAuthor().empty() ? NA : std::string(technique->getAuthor()); + std::string version = technique->getVersion().empty() ? NA : std::string(technique->getVersion()); + std::string description = technique->getDescription().empty() ? NA : std::string(technique->getDescription()); + + auto serializeBool = [](bool value) { + return value ? "#{sYes}" : "#{sNo}"; + }; + + const auto flags = technique->getFlags(); + + const auto flag_interior = serializeBool (!(flags & fx::Technique::Flag_Disable_Interiors)); + const auto flag_exterior = serializeBool (!(flags & fx::Technique::Flag_Disable_Exteriors)); + const auto flag_underwater = serializeBool(!(flags & fx::Technique::Flag_Disable_Underwater)); + const auto flag_abovewater = serializeBool(!(flags & fx::Technique::Flag_Disable_Abovewater)); + + switch (technique->getStatus()) + { + case fx::Technique::Status::Success: + case fx::Technique::Status::Uncompiled: + ss << "#{fontcolourhtml=header}Author: #{fontcolourhtml=normal} " << author << endl << endl + << "#{fontcolourhtml=header}Version: #{fontcolourhtml=normal} " << version << endl << endl + << "#{fontcolourhtml=header}Description: #{fontcolourhtml=normal} " << description << endl << endl + << "#{fontcolourhtml=header}Interiors: #{fontcolourhtml=normal} " << flag_interior + << "#{fontcolourhtml=header} Exteriors: #{fontcolourhtml=normal} " << flag_exterior + << "#{fontcolourhtml=header} Underwater: #{fontcolourhtml=normal} " << flag_underwater + << "#{fontcolourhtml=header} Abovewater: #{fontcolourhtml=normal} " << flag_abovewater; + break; + case fx::Technique::Status::File_Not_exists: + ss << "#{fontcolourhtml=negative}Shader Error: #{fontcolourhtml=header} <" << std::string(technique->getFileName()) << ">#{fontcolourhtml=normal} not found." << endl << endl + << "Ensure the shader file is in a 'Shaders/' sub directory in a data files directory"; + break; + case fx::Technique::Status::Parse_Error: + ss << "#{fontcolourhtml=negative}Shader Compile Error: #{fontcolourhtml=normal} <" << std::string(technique->getName()) << "> failed to compile." << endl << endl + << technique->getLastError(); + break; + } + + mShaderInfo->setCaptionWithReplacing(ss.str()); + + if (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug) + { + if (technique->getUniformMap().size() > 0) + { + MyGUI::Button* resetButton = mConfigArea->createWidget("MW_Button", {0,0,0,24}, MyGUI::Align::Default); + resetButton->setCaption("Reset all to default"); + resetButton->setTextAlign(MyGUI::Align::Center); + resetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyResetButtonClicked); + } + + for (const auto& uniform : technique->getUniformMap()) + { + if (!uniform->mStatic || uniform->mSamplerType) + continue; + + if (!uniform->mHeader.empty()) + mConfigArea->createWidget("MW_UniformGroup", {0,0,0,34}, MyGUI::Align::Default)->setCaption(uniform->mHeader); + + fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget("MW_UniformEdit", {0,0,0,22}, MyGUI::Align::Default); + uwidget->init(uniform); + } + } + + layout(); + } + + void PostProcessorHud::updateTechniques() + { + if (!isVisible()) + return; + + std::string hint; + ListWrapper* hintWidget = nullptr; + if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) + { + hint = mInactiveList->getItemNameAt(mInactiveList->getIndexSelected()); + hintWidget = mInactiveList; + } + else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) + { + hint = mActiveList->getItemNameAt(mActiveList->getIndexSelected()); + hintWidget = mActiveList; + } + + mInactiveList->removeAllItems(); + mActiveList->removeAllItems(); + + auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + + for (const auto& [name, _] : processor->getTechniqueMap()) + { + auto technique = processor->loadTechnique(name); + + if (!technique) + continue; + + if (!technique->getHidden() && !processor->isTechniqueEnabled(technique) && name.find(mFilter->getCaption()) != std::string::npos) + mInactiveList->addItem(name, technique); + } + + for (auto technique : processor->getTechniques()) + { + if (!technique->getHidden()) + mActiveList->addItem(technique->getName(), technique); + } + + auto tryFocus = [this](ListWrapper* widget, const std::string& hint) + { + size_t index = widget->findItemIndexWith(hint); + + if (index != MyGUI::ITEM_NONE) + { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(widget); + select(widget, index); + } + }; + + if (!mOverrideHint.empty()) + { + tryFocus(mActiveList, mOverrideHint); + tryFocus(mInactiveList, mOverrideHint); + + mOverrideHint.clear(); + } + else if (hintWidget && !hint.empty()) + tryFocus(hintWidget, hint); + } + + void PostProcessorHud::registerMyGUIComponents() + { + MyGUI::FactoryManager& factory = MyGUI::FactoryManager::getInstance(); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + factory.registerFactory("Widget"); + } +} diff --git a/apps/openmw/mwgui/postprocessorhud.hpp b/apps/openmw/mwgui/postprocessorhud.hpp new file mode 100644 index 0000000000..44baf79b63 --- /dev/null +++ b/apps/openmw/mwgui/postprocessorhud.hpp @@ -0,0 +1,107 @@ +#ifndef MYGUI_POSTPROCESSOR_HUD_H +#define MYGUI_POSTPROCESSOR_HUD_H + +#include "windowbase.hpp" + +#include +#include + +#include + +namespace MyGUI +{ + class ScrollView; + class EditBox; + class TabItem; +} +namespace Gui +{ + class AutoSizedButton; + class AutoSizedEditBox; +} + +namespace MWGui +{ + class PostProcessorHud : public WindowBase + { + class ListWrapper final : public MyGUI::ListBox + { + MYGUI_RTTI_DERIVED(ListWrapper) + protected: + void onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) override; + }; + + public: + PostProcessorHud(); + + void onOpen() override; + + void onClose() override; + + void updateTechniques(); + + void toggleMode(Settings::ShaderManager::Mode mode); + + static void registerMyGUIComponents(); + + private: + + void notifyWindowResize(MyGUI::Window* sender); + + void notifyFilterChanged(MyGUI::EditBox* sender); + + void updateConfigView(const std::string& name); + + void notifyModeToggle(MyGUI::Widget* sender); + + void notifyResetButtonClicked(MyGUI::Widget* sender); + + void notifyListChangePosition(MyGUI::ListBox* sender, size_t index); + + void notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch); + + void notifyActivatePressed(MyGUI::Widget* sender); + + void notifyDeactivatePressed(MyGUI::Widget* sender); + + void notifyShaderUpPressed(MyGUI::Widget* sender); + + void notifyShaderDownPressed(MyGUI::Widget* sender); + + enum class Direction + { + Up, + Down + }; + + void moveShader(Direction direction); + + void toggleTechnique(bool enabled); + + void select(ListWrapper* list, size_t index); + + void layout(); + + MyGUI::TabItem* mTabConfiguration; + + ListWrapper* mActiveList; + ListWrapper* mInactiveList; + + Gui::AutoSizedButton* mButtonActivate; + Gui::AutoSizedButton* mButtonDeactivate; + Gui::AutoSizedButton* mButtonDown; + Gui::AutoSizedButton* mButtonUp; + + MyGUI::ScrollView* mConfigLayout; + + MyGUI::Widget* mConfigArea; + + MyGUI::EditBox* mFilter; + Gui::AutoSizedButton* mModeToggle; + Gui::AutoSizedEditBox* mShaderInfo; + + std::string mOverrideHint; + }; +} + +#endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 80fb1e546b..3b79b21a7c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -71,6 +71,7 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwrender/localmap.hpp" +#include "../mwrender/postprocessor.hpp" #include "console.hpp" #include "journalwindow.hpp" @@ -113,6 +114,7 @@ #include "itemwidget.hpp" #include "screenfader.hpp" #include "debugwindow.hpp" +#include "postprocessorhud.hpp" #include "spellview.hpp" #include "draganddrop.hpp" #include "container.hpp" @@ -164,6 +166,7 @@ namespace MWGui , mHitFader(nullptr) , mScreenFader(nullptr) , mDebugWindow(nullptr) + , mPostProcessorHud(nullptr) , mJailScreen(nullptr) , mContainerWindow(nullptr) , mTranslationDataStorage (translationDataStorage) @@ -217,7 +220,8 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Layer"); MyGUI::FactoryManager::getInstance().registerFactory("Layer"); - BookPage::registerMyGUIComponents (); + BookPage::registerMyGUIComponents(); + PostProcessorHud::registerMyGUIComponents(); ItemView::registerComponents(); ItemChargeView::registerComponents(); ItemWidget::registerComponents(); @@ -469,6 +473,10 @@ namespace MWGui mDebugWindow = new DebugWindow(); mWindows.push_back(mDebugWindow); + mPostProcessorHud = new PostProcessorHud(); + mWindows.push_back(mPostProcessorHud); + trackWindow(mPostProcessorHud, "postprocessor"); + mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker"); mHud->setVisible(true); @@ -897,6 +905,8 @@ namespace MWGui mDebugWindow->onFrame(frameDuration); + mPostProcessorHud->onFrame(frameDuration); + if (mCharGen) mCharGen->onFrame(frameDuration); @@ -1400,6 +1410,7 @@ namespace MWGui MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; } MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; } MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; } + MWGui::PostProcessorHud* WindowManager::getPostProcessorHud() { return mPostProcessorHud; } void WindowManager::useItem(const MWWorld::Ptr &item, bool bypassBeastRestrictions) { @@ -1488,6 +1499,7 @@ namespace MWGui return !mGuiModes.empty() || isConsoleMode() || + (mPostProcessorHud && mPostProcessorHud->isVisible()) || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); } @@ -2054,6 +2066,24 @@ namespace MWGui #endif } + void WindowManager::togglePostProcessorHud() + { + if (!MWBase::Environment::get().getWorld()->getPostProcessor()->isEnabled()) + return; + + bool visible = mPostProcessorHud->isVisible(); + + if (!visible && !mGuiModes.empty()) + mKeyboardNavigation->saveFocus(mGuiModes.back()); + + mPostProcessorHud->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 9a4de2b33f..58400ec76b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -123,6 +123,7 @@ namespace MWGui class WindowModal; class ScreenFader; class DebugWindow; + class PostProcessorHud; class JailScreen; class KeyboardNavigation; @@ -188,6 +189,7 @@ namespace MWGui MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; const std::vector getActiveMessageBoxes() override; + MWGui::PostProcessorHud* getPostProcessorHud() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions=false) override; @@ -366,6 +368,7 @@ namespace MWGui void toggleConsole() override; void toggleDebugWindow() override; + void togglePostProcessorHud() override; /// Cycle to next or previous spell void cycleSpell(bool next) override; @@ -452,6 +455,7 @@ namespace MWGui ScreenFader* mHitFader; ScreenFader* mScreenFader; DebugWindow* mDebugWindow; + PostProcessorHud* mPostProcessorHud; JailScreen* mJailScreen; ContainerWindow* mContainerWindow; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index eae6996acd..6b5a100722 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -241,6 +241,9 @@ namespace MWInput case A_ToggleDebug: windowManager->toggleDebugWindow(); break; + case A_TogglePostProcessorHUD: + windowManager->togglePostProcessorHud(); + break; case A_QuickSave: quickSave(); break; diff --git a/apps/openmw/mwinput/actions.hpp b/apps/openmw/mwinput/actions.hpp index a1c1607126..c7bdbf28d3 100644 --- a/apps/openmw/mwinput/actions.hpp +++ b/apps/openmw/mwinput/actions.hpp @@ -73,6 +73,8 @@ namespace MWInput A_ZoomIn, A_ZoomOut, + A_TogglePostProcessorHUD, + A_Last // Marker for the last item }; } diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 68be849dbd..82a469890c 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -286,6 +286,7 @@ namespace MWInput defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; defaultKeyBindings[A_QuickSave] = SDL_SCANCODE_F5; defaultKeyBindings[A_QuickLoad] = SDL_SCANCODE_F9; + defaultKeyBindings[A_TogglePostProcessorHUD] = SDL_SCANCODE_F2; std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; @@ -502,6 +503,8 @@ namespace MWInput return "#{sQuickSaveCmd}"; case A_QuickLoad: return "#{sQuickLoadCmd}"; + case A_TogglePostProcessorHUD: + return "Toggle Post Processor HUD"; default: return std::string(); // not configurable } @@ -563,7 +566,8 @@ namespace MWInput A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, A_CycleWeaponRight, A_AutoMove, A_Jump, A_Inventory, A_Journal, A_Rest, A_Console, A_QuickSave, A_QuickLoad, A_ToggleHUD, A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, - A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10 + A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, + A_TogglePostProcessorHUD }; return actions; diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 1be6e086fa..fc16f514fd 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -136,6 +136,7 @@ namespace MWLua {"ToggleHUD", MWInput::A_ToggleHUD}, {"ToggleDebug", MWInput::A_ToggleDebug}, + {"TogglePostProcessorHUD", MWInput::A_TogglePostProcessorHUD}, {"ZoomIn", MWInput::A_ZoomIn}, {"ZoomOut", MWInput::A_ZoomOut} diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index 41bc8b29ae..af41199ad9 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -21,6 +21,7 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); + sol::table initPostprocessingPackage(const Context&); sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 602f32710f..b0e22db110 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -95,6 +95,7 @@ namespace MWLua mPlayerSettingsPackage = initPlayerSettingsPackage(localContext); mLocalStoragePackage = initLocalStoragePackage(localContext, &mGlobalStorage); mPlayerStoragePackage = initPlayerStoragePackage(localContext, &mGlobalStorage, &mPlayerStorage); + mPostprocessingPackage = initPostprocessingPackage(localContext); initConfiguration(); mInitialized = true; @@ -407,6 +408,7 @@ namespace MWLua scripts->addPackage("openmw.input", mInputPackage); scripts->addPackage("openmw.settings", mPlayerSettingsPackage); scripts->addPackage("openmw.storage", mPlayerStoragePackage); + scripts->addPackage("openmw.postprocessing", mPostprocessingPackage); } else { diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a75cac2da9..0700895497 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -140,6 +140,7 @@ namespace MWLua sol::table mPlayerSettingsPackage; sol::table mLocalStoragePackage; sol::table mPlayerStoragePackage; + sol::table mPostprocessingPackage; GlobalScripts mGlobalScripts{&mLua}; std::set mActiveLocalScripts; diff --git a/apps/openmw/mwlua/postprocessingbindings.cpp b/apps/openmw/mwlua/postprocessingbindings.cpp new file mode 100644 index 0000000000..b016c7716a --- /dev/null +++ b/apps/openmw/mwlua/postprocessingbindings.cpp @@ -0,0 +1,140 @@ +#include "luabindings.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwrender/postprocessor.hpp" + +#include "luamanagerimp.hpp" + +namespace +{ + template + class SetUniformShaderAction final : public MWLua::LuaManager::Action + { + public: + SetUniformShaderAction(LuaUtil::LuaState* state, std::shared_ptr shader, const std::string& name, const T& value) + : MWLua::LuaManager::Action(state), mShader(std::move(shader)), mName(name), mValue(value) {} + + void apply(MWLua::WorldView&) const override + { + MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(mShader, mName, mValue); + } + + std::string toString() const override + { + return std::string("SetUniformShaderAction shader=") + (mShader ? mShader->getName() : "nil") + + std::string("uniform=") + (mShader ? mName : "nil"); + } + + private: + std::shared_ptr mShader; + std::string mName; + T mValue; + }; +} + +namespace MWLua +{ + struct Shader; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + struct Shader + { + std::shared_ptr mShader; + + Shader(std::shared_ptr shader) : mShader(std::move(shader)) {} + + std::string toString() const + { + if (!mShader) + return "Shader(nil)"; + + return Misc::StringUtils::format("Shader(%s, %s)", mShader->getName(), mShader->getFileName()); + } + + bool mQueuedAction = false; + }; + + sol::table initPostprocessingPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + sol::usertype shader = context.mLua->sol().new_usertype("Shader"); + shader[sol::meta_function::to_string] = [](const Shader& shader) { return shader.toString(); }; + + shader["enable"] = [context](Shader& shader, sol::optional optPos) + { + std::optional pos = std::nullopt; + if (optPos) + pos = optPos.value(); + + if (shader.mShader && shader.mShader->isValid()) + shader.mQueuedAction = true; + + context.mLuaManager->addAction( + [&] { MWBase::Environment::get().getWorld()->getPostProcessor()->enableTechnique(shader.mShader, pos); }, + "Enable shader " + (shader.mShader ? shader.mShader->getName() : "nil") + ); + }; + + shader["disable"] = [context](Shader& shader) + { + shader.mQueuedAction = false; + + context.mLuaManager->addAction( + [&] { MWBase::Environment::get().getWorld()->getPostProcessor()->disableTechnique(shader.mShader); }, + "Disable shader " + (shader.mShader ? shader.mShader->getName() : "nil") + ); + }; + + shader["isEnabled"] = [](const Shader& shader) + { + return shader.mQueuedAction; + }; + + shader["setBool"] = [context](const Shader& shader, const std::string& name, bool value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setFloat"] = [context](const Shader& shader, const std::string& name, float value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setInt"] = [context](const Shader& shader, const std::string& name, int value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setVector2"] = [context](const Shader& shader, const std::string& name, const osg::Vec2f& value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setVector3"] = [context](const Shader& shader, const std::string& name, const osg::Vec3f& value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + shader["setVector4"] = [context](const Shader& shader, const std::string& name, const osg::Vec4f& value) + { + context.mLuaManager->addAction(std::make_unique>(context.mLua, shader.mShader, name, value)); + }; + + api["load"] = [](const std::string& name) + { + return Shader(MWBase::Environment::get().getWorld()->getPostProcessor()->loadTechnique(name, false)); + }; + + return LuaUtil::makeReadOnly(api); + } + +} diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cb4c074586..0ec8d8885d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -1563,7 +1564,8 @@ 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)); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index f2bbe10460..3a6d2df7ff 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -147,7 +147,7 @@ namespace MWRender class CharacterPreviewRTTNode : public SceneUtil::RTTNode { static constexpr float fovYDegrees = 12.3f; - static constexpr float znear = 0.1f; + static constexpr float znear = 4.0f; static constexpr float zfar = 10000.f; public: @@ -162,31 +162,23 @@ namespace MWRender mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix)); mViewMatrix = osg::Matrixf::identity(); setColorBufferInternalFormat(GL_RGBA); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); } void setDefaults(osg::Camera* camera) override { - - // hints that the camera is not relative to the master camera + camera->setName("CharacterPreview"); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + camera->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar); camera->setViewport(0, 0, width(), height()); camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setName("CharacterPreview"); - camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); camera->setCullMask(~(Mask_UpdateVisitor)); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); SceneUtil::setCameraClearDepth(camera); - // hints that the camera is not relative to the master camera - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - camera->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar); - camera->setViewport(0, 0, width(), height()); - camera->setRenderOrder(osg::Camera::PRE_RENDER); #ifdef OSG_HAS_MULTIVIEW if (shouldDoTextureArray()) { diff --git a/apps/openmw/mwrender/hdr.cpp b/apps/openmw/mwrender/hdr.cpp new file mode 100644 index 0000000000..95015da83a --- /dev/null +++ b/apps/openmw/mwrender/hdr.cpp @@ -0,0 +1,123 @@ +#include "hdr.hpp" + +#include +#include + +#include "pingpongcanvas.hpp" + +namespace MWRender +{ + HDRDriver::HDRDriver(Shader::ShaderManager& shaderManager) + : mCompiled(false) + , mEnabled(false) + , mWidth(1) + , mHeight(1) + { + const float hdrExposureTime = std::clamp(Settings::Manager::getFloat("hdr exposure time", "Post Processing"), 0.f, 1.f); + + constexpr float minLog = -9.0; + constexpr float maxLog = 4.0; + constexpr float logLumRange = (maxLog - minLog); + constexpr float invLogLumRange = 1.0 / logLumRange; + constexpr float epsilon = 0.004; + + Shader::ShaderManager::DefineMap defines = { + {"minLog", std::to_string(minLog)}, + {"maxLog", std::to_string(maxLog)}, + {"logLumRange", std::to_string(logLumRange)}, + {"invLogLumRange", std::to_string(invLogLumRange)}, + {"hdrExposureTime", std::to_string(hdrExposureTime)}, + {"epsilon", std::to_string(epsilon)}, + }; + + auto vertex = shaderManager.getShader("fullscreen_tri_vertex.glsl", {}, osg::Shader::VERTEX); + auto hdrLuminance = shaderManager.getShader("hdr_luminance_fragment.glsl", defines, osg::Shader::FRAGMENT); + auto hdr = shaderManager.getShader("hdr_fragment.glsl", defines, osg::Shader::FRAGMENT); + + mProgram = shaderManager.getProgram(vertex, hdr); + mLuminanceProgram = shaderManager.getProgram(vertex, hdrLuminance); + } + + void HDRDriver::compile() + { + int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); + + for (auto& buffer : mBuffers) + { + buffer.texture = new osg::Texture2D; + buffer.texture->setInternalFormat(GL_R16F); + buffer.texture->setSourceFormat(GL_RED); + buffer.texture->setSourceType(GL_FLOAT); + buffer.texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST); + buffer.texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR); + buffer.texture->setTextureSize(mWidth, mHeight); + buffer.texture->setNumMipmapLevels(mipmapLevels); + + buffer.finalTexture = new osg::Texture2D; + buffer.finalTexture->setInternalFormat(GL_R16F); + buffer.finalTexture->setSourceFormat(GL_RED); + buffer.finalTexture->setSourceType(GL_FLOAT); + buffer.finalTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); + buffer.finalTexture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); + buffer.finalTexture->setTextureSize(1, 1); + + buffer.finalFbo = new osg::FrameBufferObject; + buffer.finalFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.finalTexture)); + + buffer.fullscreenFbo = new osg::FrameBufferObject; + buffer.fullscreenFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.texture)); + + buffer.mipmapFbo = new osg::FrameBufferObject; + buffer.mipmapFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.texture, mipmapLevels - 1)); + + buffer.fullscreenStateset = new osg::StateSet; + buffer.fullscreenStateset->setAttributeAndModes(mLuminanceProgram); + buffer.fullscreenStateset->addUniform(new osg::Uniform("sceneTex", 0)); + + buffer.mipmapStateset = new osg::StateSet; + buffer.mipmapStateset->setAttributeAndModes(mProgram); + buffer.mipmapStateset->setTextureAttributeAndModes(0, buffer.texture); + buffer.mipmapStateset->addUniform(new osg::Uniform("luminanceSceneTex", 0)); + buffer.mipmapStateset->addUniform(new osg::Uniform("prevLuminanceSceneTex", 1)); + } + + mBuffers[0].mipmapStateset->setTextureAttributeAndModes(1, mBuffers[1].finalTexture); + mBuffers[1].mipmapStateset->setTextureAttributeAndModes(1, mBuffers[0].finalTexture); + + mCompiled = true; + } + + void HDRDriver::draw(const PingPongCanvas& canvas, osg::RenderInfo& renderInfo, osg::State& state, osg::GLExtensions* ext, size_t frameId) + { + if (!mEnabled) + return; + + if (!mCompiled) + compile(); + + auto& hdrBuffer = mBuffers[frameId]; + hdrBuffer.fullscreenFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + hdrBuffer.fullscreenStateset->setTextureAttributeAndModes(0, canvas.getSceneTexture(frameId)); + + state.apply(hdrBuffer.fullscreenStateset); + canvas.drawGeometry(renderInfo); + + state.applyTextureAttribute(0, hdrBuffer.texture); + ext->glGenerateMipmap(GL_TEXTURE_2D); + + hdrBuffer.mipmapFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); + hdrBuffer.finalFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_LINEAR); + + state.apply(hdrBuffer.mipmapStateset); + canvas.drawGeometry(renderInfo); + + ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0); + } + + osg::ref_ptr HDRDriver::getLuminanceTexture(size_t frameId) const + { + return mBuffers[frameId].finalTexture; + } +} \ No newline at end of file diff --git a/apps/openmw/mwrender/hdr.hpp b/apps/openmw/mwrender/hdr.hpp new file mode 100644 index 0000000000..95bdc6aa0a --- /dev/null +++ b/apps/openmw/mwrender/hdr.hpp @@ -0,0 +1,71 @@ +#ifndef OPENMW_MWRENDER_HDR_H +#define OPENMW_MWRENDER_HDR_H + +#include + +#include +#include +#include + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class PingPongCanvas; + + class HDRDriver + { + + public: + + HDRDriver() = default; + + HDRDriver(Shader::ShaderManager& shaderManager); + + void draw(const PingPongCanvas& canvas, osg::RenderInfo& renderInfo, osg::State& state, osg::GLExtensions* ext, size_t frameId); + + bool isEnabled() const { return mEnabled; } + + void enable() { mEnabled = true; } + void disable() { mEnabled = false; } + + void dirty(int w, int h) + { + mWidth = w; + mHeight = h; + mCompiled = false; + } + + osg::ref_ptr getLuminanceTexture(size_t frameId) const; + + private: + + void compile(); + + struct HDRContainer + { + osg::ref_ptr fullscreenFbo; + osg::ref_ptr mipmapFbo; + osg::ref_ptr finalFbo; + osg::ref_ptr texture; + osg::ref_ptr finalTexture; + osg::ref_ptr fullscreenStateset; + osg::ref_ptr mipmapStateset; + }; + + std::array mBuffers; + osg::ref_ptr mLuminanceProgram; + osg::ref_ptr mProgram; + + bool mCompiled; + bool mEnabled; + + int mWidth; + int mHeight; + }; +} + +#endif diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 2fd3c8b7f7..fda98a1589 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -670,6 +670,7 @@ LocalMapRenderToTexture::LocalMapRenderToTexture(osg::Node* sceneRoot, int res, mViewMatrix.makeLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); setUpdateCallback(new CameraLocalUpdateCallback); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); } void LocalMapRenderToTexture::setDefaults(osg::Camera* camera) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 0d74b8d302..7ce5594f96 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -327,24 +327,31 @@ public: state->applyAttribute(mDepth); - if (postProcessor && postProcessor->getFirstPersonRBProxy()) - { - osg::GLExtensions* ext = state->get(); + unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2; - osg::FrameBufferAttachment(postProcessor->getFirstPersonRBProxy()).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); + if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)) + { + postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // color accumulation pass bin->drawImplementation(renderInfo, previous); - auto primaryFBO = postProcessor->getMsaaFbo() ? postProcessor->getMsaaFbo() : postProcessor->getFbo(); - primaryFBO->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER).attach(*state, GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, ext); + auto primaryFBO = postProcessor->getPrimaryFbo(frameId); + + if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) + postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); + else + primaryFBO->apply(*state); - state->pushStateSet(mStateSet); - state->apply(); // depth accumulation pass + osg::ref_ptr restore = bin->getStateSet(); + bin->setStateSet(mStateSet); bin->drawImplementation(renderInfo, previous); - state->popStateSet(); + bin->setStateSet(restore); + + if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) + primaryFBO->apply(*state); } else { diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp new file mode 100644 index 0000000000..9592a8bbba --- /dev/null +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -0,0 +1,260 @@ +#include "pingpongcanvas.hpp" + +#include +#include + +#include "postprocessor.hpp" + +namespace MWRender +{ + PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager) + : mFallbackStateSet(new osg::StateSet) + , mQueuedDispatchArray(std::nullopt) + , mQueuedDispatchFrameId(0) + { + setUseDisplayList(false); + setUseVertexBufferObjects(true); + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-1, -1, 0)); + verts->push_back(osg::Vec3f(-1, 3, 0)); + verts->push_back(osg::Vec3f(3, -1, 0)); + + setVertexArray(verts); + + addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); + + mHDRDriver = HDRDriver(shaderManager); + mHDRDriver.disable(); + + auto fallbackVertex = shaderManager.getShader("fullscreen_tri_vertex.glsl", {}, osg::Shader::VERTEX); + auto fallbackFragment = shaderManager.getShader("fullscreen_tri_fragment.glsl", {}, osg::Shader::FRAGMENT); + mFallbackProgram = shaderManager.getProgram(fallbackVertex, fallbackFragment); + + mFallbackStateSet->setAttributeAndModes(mFallbackProgram); + mFallbackStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", 0)); + } + + void PingPongCanvas::setCurrentFrameData(size_t frameId, fx::DispatchArray&& data) + { + mQueuedDispatchArray = fx::DispatchArray(data); + mQueuedDispatchFrameId = !frameId; + + mBufferData[frameId].data = std::move(data); + } + + void PingPongCanvas::setMask(size_t frameId, bool underwater, bool exterior) + { + mBufferData[frameId].mask = 0; + + mBufferData[frameId].mask |= underwater ? fx::Technique::Flag_Disable_Underwater : fx::Technique::Flag_Disable_Abovewater; + mBufferData[frameId].mask |= exterior ? fx::Technique::Flag_Disable_Exteriors : fx::Technique::Flag_Disable_Interiors; + } + + void PingPongCanvas::drawGeometry(osg::RenderInfo& renderInfo) const + { + osg::Geometry::drawImplementation(renderInfo); + } + + void PingPongCanvas::drawImplementation(osg::RenderInfo& renderInfo) const + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions* ext = state.get(); + + size_t frameId = state.getFrameStamp()->getFrameNumber() % 2; + + auto& bufferData = mBufferData[frameId]; + + if (mQueuedDispatchArray && mQueuedDispatchFrameId == frameId) + { + mBufferData[frameId].data = std::move(mQueuedDispatchArray.value()); + mQueuedDispatchArray = std::nullopt; + } + + const auto& data = bufferData.data; + + std::vector filtered; + + filtered.reserve(data.size()); + + const fx::DispatchNode::SubPass* resolvePass = nullptr; + + for (size_t i = 0; i < data.size(); ++i) + { + const auto& node = data[i]; + + if (bufferData.mask & node.mFlags) + continue; + + for (auto it = node.mPasses.crbegin(); it != node.mPasses.crend(); ++it) + { + if (!(*it).mRenderTarget) + { + resolvePass = &(*it); + break; + } + } + + filtered.push_back(i); + } + + auto* viewport = state.getCurrentViewport(); + + if (filtered.empty() || !bufferData.postprocessing) + { + if (bufferData.postprocessing) + Log(Debug::Error) << "Critical error, postprocess shaders failed to compile. Using default shader."; + + mFallbackStateSet->setTextureAttributeAndModes(0, bufferData.sceneTex); + + state.pushStateSet(mFallbackStateSet); + state.apply(); + viewport->apply(state); + + drawGeometry(renderInfo); + state.popStateSet(); + return; + } + + const unsigned int handle = mFbos[0] ? mFbos[0]->getHandle(state.getContextID()) : 0; + + if (handle == 0 || bufferData.dirty) + { + for (auto& fbo : mFbos) + { + fbo = new osg::FrameBufferObject; + fbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(new osg::Texture2D(*bufferData.sceneTexLDR))); + fbo->apply(state); + glClearColor(0.5, 0.5, 0.5, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + + mHDRDriver.dirty(bufferData.sceneTex->getTextureWidth(), bufferData.sceneTex->getTextureHeight()); + + bufferData.dirty = false; + } + + constexpr std::array, 3> buffers = {{ + {GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT2_EXT}, + {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT}, + {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT} + }}; + + (bufferData.hdr) ? mHDRDriver.enable() : mHDRDriver.disable(); + + // A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly supported, so that's what we use for now. + mHDRDriver.draw(*this, renderInfo, state, ext, frameId); + + auto buffer = buffers[0]; + + int lastDraw = 0; + int lastShader = 0; + + unsigned int lastApplied = handle; + + const unsigned int cid = state.getContextID(); + + const osg::ref_ptr& destinationFbo = bufferData.destination ? bufferData.destination : nullptr; + unsigned int destinationHandle = destinationFbo ? destinationFbo->getHandle(cid) : 0; + + auto bindDestinationFbo = [&]() { + if (destinationFbo) + { + destinationFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + lastApplied = destinationHandle; + } + else + { + ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0); + + lastApplied = 0; + } + }; + + for (const size_t& index : filtered) + { + const auto& node = data[index]; + + node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, bufferData.depthTex); + + if (bufferData.hdr) + node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_EyeAdaptation, mHDRDriver.getLuminanceTexture(frameId)); + + if (bufferData.normalsTex) + node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, bufferData.normalsTex); + + state.pushStateSet(node.mRootStateSet); + state.apply(); + + for (size_t passIndex = 0; passIndex < node.mPasses.size(); ++passIndex) + { + const auto& pass = node.mPasses[passIndex]; + + bool lastPass = passIndex == node.mPasses.size() - 1; + + if (lastShader == 0) + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, bufferData.sceneTex); + else + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, (osg::Texture2D*)mFbos[lastShader - GL_COLOR_ATTACHMENT0_EXT]->getAttachment(osg::Camera::COLOR_BUFFER0).getTexture()); + + if (lastDraw == 0) + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, bufferData.sceneTex); + else + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, (osg::Texture2D*)mFbos[lastDraw - GL_COLOR_ATTACHMENT0_EXT]->getAttachment(osg::Camera::COLOR_BUFFER0).getTexture()); + + if (pass.mRenderTarget) + { + pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + if (pass.mRenderTexture->getNumMipmapLevels() > 0) + { + state.setActiveTextureUnit(0); + state.applyTextureAttribute(0, pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture()); + ext->glGenerateMipmap(GL_TEXTURE_2D); + } + + lastApplied = pass.mRenderTarget->getHandle(state.getContextID());; + } + else if (&pass == resolvePass) + { + bindDestinationFbo(); + } + else if (lastPass) + { + lastDraw = buffer[0]; + lastShader = buffer[0]; + mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + buffer = buffers[lastShader - GL_COLOR_ATTACHMENT0_EXT]; + + lastApplied = mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->getHandle(cid); + } + else + { + mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + lastDraw = buffer[0]; + std::swap(buffer[0], buffer[1]); + + lastApplied = mFbos[buffer[0] - GL_COLOR_ATTACHMENT0_EXT]->getHandle(cid); + } + + state.pushStateSet(pass.mStateSet); + state.apply(); + + if (!state.getLastAppliedProgramObject()) + mFallbackProgram->apply(state); + + drawGeometry(renderInfo); + + state.popStateSet(); + state.apply(); + } + + state.popStateSet(); + } + + if (lastApplied != destinationHandle) + { + bindDestinationFbo(); + } + } +} diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp new file mode 100644 index 0000000000..bb39f9bcf3 --- /dev/null +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -0,0 +1,88 @@ +#ifndef OPENMW_MWRENDER_PINGPONGCANVAS_H +#define OPENMW_MWRENDER_PINGPONGCANVAS_H + +#include +#include + +#include +#include +#include + +#include + +#include "postprocessor.hpp" +#include "hdr.hpp" + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class PingPongCanvas : public osg::Geometry + { + public: + PingPongCanvas(Shader::ShaderManager& shaderManager); + + void drawImplementation(osg::RenderInfo& renderInfo) const override; + + void dirty(size_t frameId) { mBufferData[frameId].dirty = true; } + + const fx::DispatchArray& getCurrentFrameData(size_t frame) { return mBufferData[frame % 2].data; } + + // Sets current frame pass data and stores copy of dispatch array to apply to next frame data + void setCurrentFrameData(size_t frameId, fx::DispatchArray&& data); + + void setMask(size_t frameId, bool underwater, bool exterior); + + void setSceneTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].sceneTex = tex; } + + void setLDRSceneTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].sceneTexLDR = tex; } + + void setDepthTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].depthTex = tex; } + + void setNormalsTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].normalsTex = tex; } + + void setHDR(size_t frameId, bool hdr) { mBufferData[frameId].hdr = hdr; } + + void setPostProcessing(size_t frameId, bool postprocessing) { mBufferData[frameId].postprocessing = postprocessing; } + + const osg::ref_ptr& getSceneTexture(size_t frameId) const { return mBufferData[frameId].sceneTex; } + + void drawGeometry(osg::RenderInfo& renderInfo) const; + + private: + void copyNewFrameData(size_t frameId) const; + + mutable HDRDriver mHDRDriver; + + osg::ref_ptr mFallbackProgram; + osg::ref_ptr mFallbackStateSet; + + struct BufferData + { + bool dirty = false; + bool hdr = false; + bool postprocessing = true; + + fx::DispatchArray data; + fx::FlagsType mask; + + osg::ref_ptr destination; + + osg::ref_ptr sceneTex; + osg::ref_ptr depthTex; + osg::ref_ptr sceneTexLDR; + osg::ref_ptr normalsTex; + }; + + mutable std::array mBufferData; + mutable std::array, 3> mFbos; + + mutable std::optional mQueuedDispatchArray; + mutable size_t mQueuedDispatchFrameId; + }; +} + +#endif diff --git a/apps/openmw/mwrender/pingpongcull.cpp b/apps/openmw/mwrender/pingpongcull.cpp new file mode 100644 index 0000000000..2bec155bbb --- /dev/null +++ b/apps/openmw/mwrender/pingpongcull.cpp @@ -0,0 +1,47 @@ +#include "pingpongcull.hpp" + +#include +#include +#include + +#include "postprocessor.hpp" +#include "pingpongcanvas.hpp" + +namespace MWRender +{ + void PingPongCull::operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); + size_t frame = cv->getTraversalNumber(); + size_t frameId = frame % 2; + + MWRender::PostProcessor* postProcessor = dynamic_cast(cv->getCurrentCamera()->getUserData()); + + postProcessor->getStateUpdater()->setViewMatrix(cv->getCurrentCamera()->getViewMatrix()); + postProcessor->getStateUpdater()->setInvViewMatrix(cv->getCurrentCamera()->getInverseViewMatrix()); + postProcessor->getStateUpdater()->setPrevViewMatrix(mLastViewMatrix); + mLastViewMatrix = cv->getCurrentCamera()->getViewMatrix(); + postProcessor->getStateUpdater()->setEyePos(cv->getEyePoint()); + postProcessor->getStateUpdater()->setEyeVec(cv->getLookVectorLocal()); + + if (!postProcessor || !postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) + { + renderStage->setMultisampleResolveFramebufferObject(nullptr); + renderStage->setFrameBufferObject(nullptr); + traverse(node, cv); + return; + } + + if (!postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)) + { + renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); + } + else + { + renderStage->setMultisampleResolveFramebufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); + renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)); + } + + traverse(node, cv); + } +} diff --git a/apps/openmw/mwrender/pingpongcull.hpp b/apps/openmw/mwrender/pingpongcull.hpp new file mode 100644 index 0000000000..d4514e20d3 --- /dev/null +++ b/apps/openmw/mwrender/pingpongcull.hpp @@ -0,0 +1,22 @@ +#ifndef OPENMW_MWRENDER_PINGPONGCULL_H +#define OPENMW_MWRENDER_PINGPONGCULL_H + +#include + +#include + +#include "postprocessor.hpp" + +namespace MWRender +{ + class PostProcessor; + class PingPongCull : public SceneUtil::NodeCallback + { + public: + void operator()(osg::Node* node, osgUtil::CullVisitor* nv); + private: + osg::Matrixf mLastViewMatrix; + }; +} + +#endif diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 9fbc5de528..6fcbb4d51f 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -2,354 +2,696 @@ #include -#include -#include -#include +#include #include +#include #include -#include - -#include #include #include #include #include -#include - +#include +#include +#include +#include +#include #include #include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwgui/postprocessorhud.hpp" + +#include "transparentpass.hpp" +#include "pingpongcull.hpp" +#include "renderingmanager.hpp" #include "vismask.hpp" +#include "sky.hpp" namespace { - osg::ref_ptr createFullScreenTri() + struct ResizedCallback : osg::GraphicsContext::ResizedCallback { - osg::ref_ptr geom = new osg::Geometry; - - osg::ref_ptr verts = new osg::Vec3Array; - verts->push_back(osg::Vec3f(-1, -1, 0)); - verts->push_back(osg::Vec3f(-1, 3, 0)); - verts->push_back(osg::Vec3f(3, -1, 0)); + ResizedCallback(MWRender::PostProcessor* postProcessor) + : mPostProcessor(postProcessor) + { } - geom->setVertexArray(verts); + void resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height) override + { + gc->resizedImplementation(x, y, width, height); - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); + mPostProcessor->setRenderTargetSize(width, height); + mPostProcessor->resize(); + } - return geom; - } + MWRender::PostProcessor* mPostProcessor; + }; - class CullCallback : public SceneUtil::NodeCallback + class HUDCullCallback : public SceneUtil::NodeCallback { public: - CullCallback(MWRender::PostProcessor* pp) - : mPostProcessor(pp) + void operator()(osg::Camera* camera, osgUtil::CullVisitor* cv) { + osg::ref_ptr stateset = new osg::StateSet; + auto& sm = Stereo::Manager::instance(); + auto* fullViewport = camera->getViewport(); + if (sm.getEye(cv) == Stereo::Eye::Left) + stateset->setAttributeAndModes(new osg::Viewport(0, 0, fullViewport->width() / 2, fullViewport->height())); + if (sm.getEye(cv) == Stereo::Eye::Right) + stateset->setAttributeAndModes(new osg::Viewport(fullViewport->width() / 2, 0, fullViewport->width() / 2, fullViewport->height())); + + cv->pushStateSet(stateset); + traverse(camera, cv); + cv->popViewport(); } + }; +} - void operator()(osg::Node* node, osgUtil::CullVisitor* cv) - { - osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); +namespace MWRender +{ + PostProcessor::PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs) + : osg::Group() + , mRootNode(rootNode) + , mSamples(Settings::Manager::getInt("antialiasing", "Video")) + , mDirty(false) + , mDirtyFrameId(0) + , mRendering(rendering) + , mViewer(viewer) + , mVFS(vfs) + , mReload(false) + , mEnabled(false) + , mUsePostProcessing(false) + , mSoftParticles(false) + , mDisableDepthPasses(false) + , mLastFrameNumber(0) + , mLastSimulationTime(0.f) + , mExteriorFlag(false) + , mUnderwater(false) + , mHDR(false) + , mNormals(false) + , mPrevNormals(false) + , mNormalsSupported(false) + , mMainTemplate(new osg::Texture2D) + { + mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders") && !Stereo::getStereo() && !Stereo::getMultiview(); + mUsePostProcessing = Settings::Manager::getBool("enabled", "Post Processing"); - if (!mPostProcessor->getMsaaFbo()) - { - renderStage->setFrameBufferObject(mPostProcessor->getFbo()); - } - else + osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); + osg::GLExtensions* ext = gc->getState()->get(); + + mWidth = gc->getTraits()->width; + mHeight = gc->getTraits()->height; + + if (!ext->glDisablei && ext->glDisableIndexedEXT) + ext->glDisablei = ext->glDisableIndexedEXT; + + if (ext->glDisablei) + mNormalsSupported = true; + else + Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders."; + + if (mSoftParticles) + for (int i = 0; i < 2; ++i) + mTextures[i][Tex_OpaqueDepth] = new osg::Texture2D; + + mGLSLVersion = ext->glslLanguageVersion * 100; + mUBO = ext && ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; + mStateUpdater = new fx::StateUpdater(mUBO); + + if (!SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !mUsePostProcessing && !Stereo::getStereo() && !Stereo::getMultiview()) + return; + + enable(mUsePostProcessing); + } + + PostProcessor::~PostProcessor() + { + if (auto* bin = osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")) + bin->setDrawCallback(nullptr); + } + + void PostProcessor::resize() + { + for (auto& technique : mTechniques) + { + for (auto& [name, rt] : technique->getRenderTargetsMap()) { - renderStage->setMultisampleResolveFramebufferObject(mPostProcessor->getFbo()); - renderStage->setFrameBufferObject(mPostProcessor->getMsaaFbo()); + const auto [w, h] = rt.mSize.get(mWidth, mHeight); + rt.mTarget->setTextureSize(w, h); + rt.mTarget->dirtyTextureObject(); } - - traverse(node, cv); } - private: - MWRender::PostProcessor* mPostProcessor; - }; + size_t frameId = frame() % 2; - struct ResizedCallback : osg::GraphicsContext::ResizedCallback + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); + + mHUDCamera->resize(mWidth, mHeight); + mViewer->getCamera()->resize(mWidth, mHeight); + mRendering.updateProjectionMatrix(); + mRendering.setScreenRes(mWidth, mHeight); + + dirtyTechniques(); + + mPingPongCanvas->dirty(frameId); + + mDirty = true; + mDirtyFrameId = !frameId; + + if (Stereo::getStereo()) + Stereo::Manager::instance().screenResolutionChanged(); + } + + void PostProcessor::enable(bool usePostProcessing) { - ResizedCallback(MWRender::PostProcessor* postProcessor) - : mPostProcessor(postProcessor) + mReload = true; + mEnabled = true; + mUsePostProcessing = usePostProcessing && !Stereo::getStereo() && !Stereo::getMultiview(); + + if (!mDisableDepthPasses && !Stereo::getStereo() && !Stereo::getMultiview()) { + mTransparentDepthPostPass = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), Settings::Manager::getBool("transparent postpass", "Post Processing")); + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); } - void resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height) override + if (mUsePostProcessing && mTechniqueFileMap.empty()) { - gc->resizedImplementation(x, y, width, height); - mPostProcessor->resize(width, height); + for (const auto& name : mVFS->getRecursiveDirectoryIterator(fx::Technique::sSubdir)) + { + std::filesystem::path path = name; + std::string fileExt = Misc::StringUtils::lowerCase(path.extension().string()); + if (!path.parent_path().has_parent_path() && fileExt == fx::Technique::sExt) + { + auto absolutePath = std::filesystem::path(mVFS->getAbsoluteFileName(name)); + + mTechniqueFileMap[absolutePath.stem().string()] = absolutePath; + } + } } - MWRender::PostProcessor* mPostProcessor; - }; + mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mMainTemplate->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mMainTemplate->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mMainTemplate->setInternalFormat(GL_RGBA); + mMainTemplate->setSourceType(GL_UNSIGNED_BYTE); + mMainTemplate->setSourceFormat(GL_RGBA); - // Copies the currently bound depth attachment to a new texture so drawables in transparent renderbin can safely sample from depth. - class OpaqueDepthCopyCallback : public osgUtil::RenderBin::DrawCallback - { - public: - OpaqueDepthCopyCallback(osg::ref_ptr opaqueDepthTex, osg::ref_ptr sourceFbo) - : mOpaqueDepthFbo(new osg::FrameBufferObject) - , mSourceFbo(sourceFbo) - , mOpaqueDepthTex(opaqueDepthTex) - , mColorAttached(false) - { - mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER, osg::FrameBufferAttachment(opaqueDepthTex)); + createTexturesAndCamera(frame() % 2); -#ifdef __APPLE__ - // Mac OS drivers complain that a FBO is incomplete if it has no color attachment - mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_RGB))); - mColorAttached = true; -#endif - } + removeChild(mHUDCamera); + removeChild(mRootNode); + + addChild(mHUDCamera); + addChild(mRootNode); + + mViewer->setSceneData(this); + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mViewer->getCamera()->setImplicitBufferAttachmentMask(0, 0); + mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); + mViewer->getCamera()->setUserData(this); + + setCullCallback(mStateUpdater); + mHUDCamera->setCullCallback(new HUDCullCallback); - void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override + static bool init = false; + + if (init) { - if (bin->getStage()->getFrameBufferObject() == mSourceFbo) - { - osg::State& state = *renderInfo.getState(); - osg::GLExtensions* ext = state.get(); + resize(); + init = true; + } - mSourceFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); - postBindOperation(state); + init = true; + } - mOpaqueDepthFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - postBindOperation(state); + void PostProcessor::disable() + { + if (!mSoftParticles) + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(nullptr); - ext->glBlitFramebuffer(0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), 0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); + if (!SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !Stereo::getStereo() && !Stereo::getMultiview()) + { + removeChild(mHUDCamera); + setCullCallback(nullptr); - mSourceFbo->apply(state); - } + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); + mViewer->getCamera()->getGraphicsContext()->setResizedCallback(nullptr); + mViewer->getCamera()->setUserData(nullptr); - bin->drawImplementation(renderInfo, previous); + mEnabled = false; } - private: - void postBindOperation(osg::State& state) + + mUsePostProcessing = false; + mRendering.getSkyManager()->setSunglare(true); + } + + void PostProcessor::traverse(osg::NodeVisitor& nv) + { + if (!mEnabled) { - if (mColorAttached) - return; - #if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE) - state.glDrawBuffer(GL_NONE); - state.glReadBuffer(GL_NONE); - #endif + osg::Group::traverse(nv); + return; } - osg::ref_ptr mOpaqueDepthFbo; - osg::ref_ptr mSourceFbo; - osg::ref_ptr mOpaqueDepthTex; - bool mColorAttached; - }; -} + size_t frameId = nv.getTraversalNumber() % 2; -namespace MWRender -{ - PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode) - : mViewer(viewer) - , mRootNode(new osg::Group) + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + cull(frameId, static_cast(&nv)); + else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + update(frameId); + + osg::Group::traverse(nv); + } + + void PostProcessor::cull(size_t frameId, osgUtil::CullVisitor* cv) { - bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); + const auto& fbo = getFbo(FBO_Intercept, frameId); + if (fbo) + { + osgUtil::RenderStage* rs = cv->getRenderStage(); + if (rs && rs->getMultisampleResolveFramebufferObject()) + rs->setMultisampleResolveFramebufferObject(fbo); + } - if (!SceneUtil::AutoDepth::isReversed() && !softParticles && !Stereo::getStereo()) - return; + mPingPongCanvas->setPostProcessing(frameId, mUsePostProcessing); + mPingPongCanvas->setNormalsTexture(frameId, mNormals ? getTexture(Tex_Normal, frameId) : nullptr); + mPingPongCanvas->setMask(frameId, mUnderwater, mExteriorFlag); + mPingPongCanvas->setHDR(frameId, getHDR()); - osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); - osg::GLExtensions* ext = gc->getState()->get(); + if (Stereo::getStereo()) + { + auto& sm = Stereo::Manager::instance(); - constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: "; + int index = sm.getEye(cv) == Stereo::Eye::Left ? 0 : 1; - if (!ext->isFrameBufferObjectSupported) + mPingPongCanvas->setSceneTexture(frameId, sm.multiviewFramebuffer()->layerColorBuffer(index)); + mPingPongCanvas->setDepthTexture(frameId, sm.multiviewFramebuffer()->layerDepthBuffer(index)); + } + else if (Stereo::getMultiview()) { - Log(Debug::Warning) << errPreamble << "FrameBufferObject unsupported."; - return; + auto& sm = Stereo::Manager::instance(); + + mPingPongCanvas->setSceneTexture(frameId, sm.multiviewFramebuffer()->multiviewColorBuffer()); + mPingPongCanvas->setDepthTexture(frameId, sm.multiviewFramebuffer()->multiviewDepthBuffer()); } + else + { + mPingPongCanvas->setSceneTexture(frameId, getTexture(Tex_Scene, frameId)); + if (mDisableDepthPasses) + mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_Depth, frameId)); + else + mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_OpaqueDepth, frameId)); + + mPingPongCanvas->setLDRSceneTexture(frameId, getTexture(Tex_Scene_LDR, frameId)); - if (Settings::Manager::getInt("antialiasing", "Video") > 1 && !ext->isRenderbufferMultisampleSupported()) + if (mTransparentDepthPostPass) + { + mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; + mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; + mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; + } + } + + size_t frame = cv->getTraversalNumber(); + + mStateUpdater->setResolution(osg::Vec2f(cv->getViewport()->width(), cv->getViewport()->height())); + + // per-frame data + if (frame != mLastFrameNumber) { - Log(Debug::Warning) << errPreamble << "RenderBufferMultiSample unsupported. Disabling antialiasing will resolve this issue."; - return; + mLastFrameNumber = frame; + + auto stamp = cv->getFrameStamp(); + + mStateUpdater->setSimulationTime(static_cast(stamp->getSimulationTime())); + mStateUpdater->setDeltaSimulationTime(static_cast(stamp->getSimulationTime() - mLastSimulationTime)); + mLastSimulationTime = stamp->getSimulationTime(); + + for (const auto& dispatchNode : mPingPongCanvas->getCurrentFrameData(frame)) + { + for (auto& uniform : dispatchNode.mHandle->getUniformMap()) + { + if (uniform->getType().has_value() && !uniform->mSamplerType) + if (auto* u = dispatchNode.mRootStateSet->getUniform(uniform->mName)) + uniform->setUniform(u); + } + } } + } + + void PostProcessor::update(size_t frameId) + { + static const bool liveReload = Settings::Manager::getBool("live reload", "Post Processing"); - if (SceneUtil::AutoDepth::isReversed()) + if (liveReload) { - if(SceneUtil::AutoDepth::depthSourceType() != GL_FLOAT_32_UNSIGNED_INT_24_8_REV) + for (auto& technique : mTechniques) { - // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. - // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no - // benefits if no floating point depth formats are supported. - if (!softParticles && !Stereo::getStereo()) - return; + if (technique->getStatus() == fx::Technique::Status::File_Not_exists) + continue; + + technique->setLastModificationTime(std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()])); + + if(technique->isValid() && !technique->isDirty()) + continue; + + if (technique->isDirty()) + { + technique->compile(); + + if (technique->isValid()) + Log(Debug::Info) << "Reloaded technique : " << mTechniqueFileMap[technique->getName()].string(); + + if (!mReload) + mReload = technique->isValid(); + } } } - auto* traits = gc->getTraits(); - int width = traits->width; - int height = traits->height; + if (mReload) + { + mReload = false; + + if (!mTechniques.empty()) + reloadMainPass(*mTechniques[0]); - createTexturesAndCamera(width, height); - resize(width, height); + reloadTechniques(); - mRootNode->addChild(mHUDCamera); - mRootNode->addChild(rootNode); - mViewer->setSceneData(mRootNode); + if (!mUsePostProcessing) + resize(); + } - if (!Stereo::getStereo()) + if (mDirty && mDirtyFrameId == frameId) { - // We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate - // RTT camera this would not be needed. - mViewer->getCamera()->addCullCallback(new CullCallback(this)); - mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); - mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); - mViewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mDepthTex); + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); + mDirty = false; } - mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); + if (mNormalsSupported && mNormals != mPrevNormals) + { + mPrevNormals = mNormals; + + mViewer->stopThreading(); + + auto& shaderManager = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager(); + auto defines = shaderManager.getGlobalDefines(); + defines["disableNormals"] = mNormals ? "0" : "1"; + shaderManager.setGlobalDefines(defines); + + mViewer->startThreading(); + + createTexturesAndCamera(frameId); + createObjectsForFrame(frameId); + mDirty = true; + mDirtyFrameId = !frameId; + } } - void PostProcessor::resize(int width, int height) + void PostProcessor::createObjectsForFrame(size_t frameId) { - mDepthTex->setTextureSize(width, height); - mSceneTex->setTextureSize(width, height); - mDepthTex->dirtyTextureObject(); - mSceneTex->dirtyTextureObject(); + if (Stereo::getStereo() || Stereo::getMultiview()) + return; - if (mOpaqueDepthTex) + auto& fbos = mFbos[frameId]; + auto& textures = mTextures[frameId]; + + for (auto& tex : textures) { - mOpaqueDepthTex->setTextureSize(width, height); - mOpaqueDepthTex->dirtyTextureObject(); + if (!tex) + continue; + + tex->setTextureSize(mWidth, mHeight); + tex->dirtyTextureObject(); } - int samples = Settings::Manager::getInt("antialiasing", "Video"); + fbos[FBO_Primary] = new osg::FrameBufferObject; + fbos[FBO_Primary]->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(textures[Tex_Scene])); + if (mNormals && mNormalsSupported) + fbos[FBO_Primary]->setAttachment(osg::Camera::COLOR_BUFFER1, osg::FrameBufferAttachment(textures[Tex_Normal])); + fbos[FBO_Primary]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(textures[Tex_Depth])); - mFbo = new osg::FrameBufferObject; - mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex)); - mFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTex)); + fbos[FBO_FirstPerson] = new osg::FrameBufferObject; + osg::ref_ptr fpDepthRb = new osg::RenderBuffer(mWidth, mHeight, textures[Tex_Depth]->getInternalFormat(), mSamples > 1 ? mSamples : 0); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(fpDepthRb)); // When MSAA is enabled we must first render to a render buffer, then // blit the result to the FBO which is either passed to the main frame // buffer for display or used as the entry point for a post process chain. - if (samples > 1) + if (mSamples > 1) { - mMsaaFbo = new osg::FrameBufferObject; - osg::ref_ptr colorRB = new osg::RenderBuffer(width, height, mSceneTex->getInternalFormat(), samples); - osg::ref_ptr depthRB = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); - mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); - mMsaaFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(depthRB)); + fbos[FBO_Multisample] = new osg::FrameBufferObject; + osg::ref_ptr colorRB = new osg::RenderBuffer(mWidth, mHeight, textures[Tex_Scene]->getInternalFormat(), mSamples); + if (mNormals && mNormalsSupported) + { + osg::ref_ptr normalRB = new osg::RenderBuffer(mWidth, mHeight, textures[Tex_Normal]->getInternalFormat(), mSamples); + fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, osg::FrameBufferAttachment(normalRB)); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, osg::FrameBufferAttachment(normalRB)); + } + osg::ref_ptr depthRB = new osg::RenderBuffer(mWidth, mHeight, textures[Tex_Depth]->getInternalFormat(), mSamples); + fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); + fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(depthRB)); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); + + fbos[FBO_Intercept] = new osg::FrameBufferObject; + fbos[FBO_Intercept]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(textures[Tex_Scene])); + fbos[FBO_Intercept]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, osg::FrameBufferAttachment(textures[Tex_Normal])); + } + else + { + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(textures[Tex_Scene])); + if (mNormals && mNormalsSupported) + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, osg::FrameBufferAttachment(textures[Tex_Normal])); } - if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY")) - mFirstPersonDepthRBProxy = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); - - if (Settings::Manager::getBool("soft particles", "Shaders")) - osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(new OpaqueDepthCopyCallback(mOpaqueDepthTex, mMsaaFbo ? mMsaaFbo : mFbo)); - - mViewer->getCamera()->resize(width, height); - mHUDCamera->resize(width, height); + if (textures[Tex_OpaqueDepth]) + { + fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; + fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(textures[Tex_OpaqueDepth])); + } - if (Stereo::getStereo()) - Stereo::Manager::instance().screenResolutionChanged(); +#ifdef __APPLE__ + if (textures[Tex_OpaqueDepth]) + fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(textures[Tex_OpaqueDepth]->getTextureWidth(), textures[Tex_OpaqueDepth]->getTextureHeight(), textures[Tex_Scene]->getInternalFormat()))); +#endif } - class HUDCameraStatesetUpdater final : public SceneUtil::StateSetUpdater + void PostProcessor::dirtyTechniques() { - public: - public: - HUDCameraStatesetUpdater(osg::ref_ptr HUDCamera, osg::ref_ptr program, osg::ref_ptr sceneTex) - : mHUDCamera(HUDCamera) - , mProgram(program) - , mSceneTex(sceneTex) - { - } + if (!isEnabled()) + return; - void setDefaults(osg::StateSet* stateset) override + fx::DispatchArray data; + + bool sunglare = true; + mHDR = false; + mNormals = false; + + for (const auto& technique : mTechniques) { - stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("sceneTex", 0)); - stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + if (!technique->isValid()) + continue; - if (osg::DisplaySettings::instance()->getStereo()) + if (technique->getGLSLVersion() > mGLSLVersion) { - stateset->setAttribute(new osg::Viewport); - stateset->addUniform(new osg::Uniform("viewportIndex", 0)); + Log(Debug::Warning) << "Technique " << technique->getName() << " requires GLSL version " << technique->getGLSLVersion() << " which is unsupported by your hardware."; + continue; } + + fx::DispatchNode node; + + node.mFlags = technique->getFlags(); + + if (technique->getHDR()) + mHDR = true; + + if (technique->getNormals()) + mNormals = true; + + if (node.mFlags & fx::Technique::Flag_Disable_SunGlare) + sunglare = false; + + // required default samplers available to every shader pass + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", Unit_LastShader)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastPass", Unit_LastPass)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth)); + + if (mNormals) + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals)); + + if (technique->getHDR()) + node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation)); + + int texUnit = Unit_NextFree; + + // user-defined samplers + for (const osg::Texture* texture : technique->getTextures()) + { + if (const auto* tex1D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture1D(*tex1D)); + else if (const auto* tex2D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture2D(*tex2D)); + else if (const auto* tex3D = dynamic_cast(texture)) + node.mRootStateSet->setTextureAttribute(texUnit, new osg::Texture3D(*tex3D)); + + node.mRootStateSet->addUniform(new osg::Uniform(texture->getName().c_str(), texUnit++)); + } + + // user-defined uniforms + for (auto& uniform : technique->getUniformMap()) + { + if (uniform->mSamplerType) continue; + + if (auto type = uniform->getType()) + uniform->setUniform(node.mRootStateSet->getOrCreateUniform(uniform->mName, type.value())); + } + + int subTexUnit = texUnit; + + for (const auto& pass : technique->getPasses()) + { + fx::DispatchNode::SubPass subPass; + + pass->prepareStateSet(subPass.mStateSet, technique->getName()); + + node.mHandle = technique; + + if (!pass->getTarget().empty()) + { + const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()]; + + const auto [w, h] = rt.mSize.get(mWidth, mHeight); + + subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget); + subPass.mRenderTexture->setTextureSize(w, h); + subPass.mRenderTexture->setName(std::string(pass->getTarget())); + + if (rt.mMipMap) + subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + + subPass.mRenderTarget = new osg::FrameBufferObject; + subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(subPass.mRenderTexture)); + subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); + + node.mRootStateSet->setTextureAttributeAndModes(subTexUnit, subPass.mRenderTexture); + node.mRootStateSet->addUniform(new osg::Uniform(subPass.mRenderTexture->getName().c_str(), subTexUnit++)); + } + node.mPasses.emplace_back(std::move(subPass)); + } + + data.emplace_back(std::move(node)); } - void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + size_t frameId = frame() % 2; + + mPingPongCanvas->setCurrentFrameData(frameId, std::move(data)); + + if (auto hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) + hud->updateTechniques(); + + mRendering.getSkyManager()->setSunglare(sunglare); + } + + bool PostProcessor::enableTechnique(std::shared_ptr technique, std::optional location) + { + if (!technique || technique->getName() == "main" || (location.has_value() && location.value() <= 0)) + return false; + + disableTechnique(technique, false); + + int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); + + mTechniques.insert(mTechniques.begin() + pos, technique); + dirtyTechniques(); + + return true; + } + + bool PostProcessor::disableTechnique(std::shared_ptr technique, bool dirty) + { + for (size_t i = 1; i < mTechniques.size(); ++i) { - if (Stereo::getMultiview()) + if (technique.get() == mTechniques[i].get()) { - auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); - stateset->setTextureAttributeAndModes(0, multiviewFbo->multiviewColorBuffer(), osg::StateAttribute::ON); + mTechniques.erase(mTechniques.begin() + i); + if (dirty) + dirtyTechniques(); + return true; } } - void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override + return false; + } + + bool PostProcessor::isTechniqueEnabled(const std::shared_ptr& technique) const + { + for (const auto& t : mTechniques) { - auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); - stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(0), osg::StateAttribute::ON); - - auto viewport = static_cast(stateset->getAttribute(osg::StateAttribute::VIEWPORT)); - auto fullViewport = mHUDCamera->getViewport(); - viewport->setViewport( - 0, - 0, - fullViewport->width() / 2, - fullViewport->height() - ); + if (technique.get() == t.get()) + return technique->isValid(); } - void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override + return false; + } + + void PostProcessor::createTexturesAndCamera(size_t frameId) + { + auto& textures = mTextures[frameId]; + + for (auto& texture : textures) { - auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); - stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(1), osg::StateAttribute::ON); - - auto viewport = static_cast(stateset->getAttribute(osg::StateAttribute::VIEWPORT)); - auto fullViewport = mHUDCamera->getViewport(); - viewport->setViewport( - fullViewport->width() / 2, - 0, - fullViewport->width() / 2, - fullViewport->height() - ); + if (!texture) + texture = new osg::Texture2D; + texture->setTextureSize(mWidth, mHeight); + texture->setSourceFormat(GL_RGBA); + texture->setSourceType(GL_UNSIGNED_BYTE); + texture->setInternalFormat(GL_RGBA); + texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setResizeNonPowerOfTwoHint(false); } - private: - osg::ref_ptr mHUDCamera; - osg::ref_ptr mProgram; - osg::ref_ptr mSceneTex; - }; + textures[Tex_Normal]->setSourceFormat(GL_RGB); + textures[Tex_Normal]->setInternalFormat(GL_RGB); - void PostProcessor::createTexturesAndCamera(int width, int height) - { - mDepthTex = new osg::Texture2D; - mDepthTex->setTextureSize(width, height); - mDepthTex->setSourceFormat(SceneUtil::AutoDepth::depthSourceFormat()); - mDepthTex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); - mDepthTex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); - mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); - mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); - mDepthTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mDepthTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mDepthTex->setResizeNonPowerOfTwoHint(false); - - if (Settings::Manager::getBool("soft particles", "Shaders")) + if (mMainTemplate) + { + textures[Tex_Scene]->setSourceFormat(mMainTemplate->getSourceFormat()); + textures[Tex_Scene]->setSourceType(mMainTemplate->getSourceType()); + textures[Tex_Scene]->setInternalFormat(mMainTemplate->getInternalFormat()); + textures[Tex_Scene]->setFilter(osg::Texture2D::MIN_FILTER, mMainTemplate->getFilter(osg::Texture2D::MIN_FILTER)); + textures[Tex_Scene]->setFilter(osg::Texture2D::MAG_FILTER, mMainTemplate->getFilter(osg::Texture2D::MAG_FILTER)); + textures[Tex_Scene]->setWrap(osg::Texture::WRAP_S, mMainTemplate->getWrap(osg::Texture2D::WRAP_S)); + textures[Tex_Scene]->setWrap(osg::Texture::WRAP_T, mMainTemplate->getWrap(osg::Texture2D::WRAP_T)); + } + + auto setupDepth = [] (osg::Texture2D* tex) { + tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); + tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); + tex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); + }; + + setupDepth(textures[Tex_Depth]); + + if (mDisableDepthPasses) + { + textures[Tex_OpaqueDepth] = nullptr; + } + else { - mOpaqueDepthTex = new osg::Texture2D(*mDepthTex); - mOpaqueDepthTex->setName("opaqueTexMap"); + setupDepth(textures[Tex_OpaqueDepth]); + textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); } - mSceneTex = new osg::Texture2D; - mSceneTex->setTextureSize(width, height); - mSceneTex->setSourceFormat(SceneUtil::Color::colorSourceFormat()); - mSceneTex->setSourceType(SceneUtil::Color::colorSourceType()); - mSceneTex->setInternalFormat(SceneUtil::Color::colorInternalFormat()); - mSceneTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); - mSceneTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); - mSceneTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mSceneTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mSceneTex->setResizeNonPowerOfTwoHint(false); + if (mHUDCamera) + return; mHUDCamera = new osg::Camera; mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); @@ -358,64 +700,106 @@ namespace MWRender mHUDCamera->setClearMask(0); mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); mHUDCamera->setAllowEventFocus(false); - mHUDCamera->setViewport(0, 0, width, height); + mHUDCamera->setViewport(0, 0, mWidth, mHeight); - // Shaders calculate correct UV coordinates for our fullscreen triangle - constexpr char vertSrc[] = R"GLSL( - #version 120 + mViewer->getCamera()->removeCullCallback(mPingPongCull); + mPingPongCull = new PingPongCull; + mViewer->getCamera()->addCullCallback(mPingPongCull); - varying vec2 uv; + mPingPongCanvas = new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()); - void main() - { - gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0); - uv = gl_Position.xy * 0.5 + 0.5; - } - )GLSL"; + mHUDCamera->addChild(mPingPongCanvas); + mHUDCamera->setNodeMask(Mask_RenderToTexture); - constexpr char fragSrc[] = R"GLSL( - #version 120 + mHUDCamera->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + mHUDCamera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } - varying vec2 uv; - uniform sampler2D sceneTex; + std::shared_ptr PostProcessor::loadTechnique(const std::string& name, bool insert) + { + if (!isEnabled()) + return nullptr; - void main() - { - gl_FragData[0] = texture2D(sceneTex, uv); - } - )GLSL"; + for (size_t i = 0; i < mTemplates.size(); ++i) + if (name == mTemplates[i]->getName()) + return mTemplates[i]; - constexpr char fragSrcMultiview[] = R"GLSL( - #version 330 compatibility + auto technique = std::make_shared(*mVFS, *mRendering.getResourceSystem()->getImageManager(), name, mWidth, mHeight, mUBO, mNormalsSupported); - #extension GL_EXT_texture_array : require + technique->compile(); - varying vec2 uv; - uniform sampler2DArray sceneTex; + if (technique->getStatus() != fx::Technique::Status::File_Not_exists) + technique->setLastModificationTime(std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]), false); - void main() + if (!insert) + return technique; + + reloadMainPass(*technique); + + mTemplates.push_back(std::move(technique)); + + return mTemplates.back(); + } + + void PostProcessor::addTemplate(std::shared_ptr technique) + { + if (!isEnabled()) + return; + + for (size_t i = 0; i < mTemplates.size(); ++i) + if (technique.get() == mTemplates[i].get()) + return; + + mTemplates.push_back(technique); + } + + void PostProcessor::reloadTechniques() + { + if (!isEnabled()) + return; + + mTechniques.clear(); + + std::vector techniqueStrings; + Misc::StringUtils::split(Settings::Manager::getString("chain", "Post Processing"), techniqueStrings, ","); + + techniqueStrings.insert(techniqueStrings.begin(), "main"); + + for (auto& techniqueName : techniqueStrings) + { + Misc::StringUtils::trim(techniqueName); + + if (techniqueName.empty()) + continue; + + if ((&techniqueName != &techniqueStrings.front()) && Misc::StringUtils::ciEqual(techniqueName, "main")) { - vec3 array_uv = vec3(uv.x * 2, uv.y, 0); - if(array_uv.x >= 1.0) - { - array_uv.x -= 1.0; - array_uv.z = 1; - } - gl_FragData[0] = texture2DArray(sceneTex, array_uv); + Log(Debug::Warning) << "main.omwfx techniqued specified in chain, this is not allowed. technique file will be ignored if it exists."; + continue; } - )GLSL"; - osg::ref_ptr vertShader = new osg::Shader(osg::Shader::VERTEX, vertSrc); - osg::ref_ptr fragShader = new osg::Shader(osg::Shader::FRAGMENT, Stereo::getMultiview() ? fragSrcMultiview : fragSrc); + mTechniques.push_back(loadTechnique(techniqueName)); + } + + dirtyTechniques(); + } - osg::ref_ptr program = new osg::Program; - program->addShader(vertShader); - program->addShader(fragShader); + void PostProcessor::reloadMainPass(fx::Technique& technique) + { + if (!technique.getMainTemplate()) + return; - mHUDCamera->addChild(createFullScreenTri()); - mHUDCamera->setNodeMask(Mask_RenderToTexture); - mHUDCamera->setCullCallback(new HUDCameraStatesetUpdater(mHUDCamera, program, mSceneTex)); + mMainTemplate = technique.getMainTemplate(); + + resize(); } + void PostProcessor::toggleMode() + { + for (auto& technique : mTemplates) + technique->compile(); + + dirtyTechniques(); + } } diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index b1217b011b..8560b76eaa 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -1,11 +1,26 @@ #ifndef OPENMW_MWRENDER_POSTPROCESSOR_H #define OPENMW_MWRENDER_POSTPROCESSOR_H +#include +#include +#include +#include + +#include + #include #include #include #include -#include + +#include + +#include +#include +#include + +#include "pingpongcanvas.hpp" +#include "transparentpass.hpp" #include @@ -19,38 +34,197 @@ namespace Stereo class MultiviewFramebuffer; } +namespace VFS +{ + class Manager; +} + +namespace Shader +{ + class ShaderManager; +} + namespace MWRender { - class PostProcessor : public osg::Referenced + class RenderingManager; + class PingPongCull; + class PingPongCanvas; + class TransparentDepthBinCallback; + + class PostProcessor : public osg::Group { public: - PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode); + using FBOArray = std::array, 5>; + using TextureArray = std::array, 5>; + using TechniqueList = std::vector>; + + enum TextureIndex + { + Tex_Scene, + Tex_Scene_LDR, + Tex_Depth, + Tex_OpaqueDepth, + Tex_Normal + }; + + enum FBOIndex + { + FBO_Primary, + FBO_Multisample, + FBO_FirstPerson, + FBO_OpaqueDepth, + FBO_Intercept + }; + + enum TextureUnits + { + Unit_LastShader = 0, + Unit_LastPass, + Unit_Depth, + Unit_EyeAdaptation, + Unit_Normals, + Unit_NextFree + }; + + PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs); + + ~PostProcessor(); + + void traverse(osg::NodeVisitor& nv) override; + + osg::ref_ptr getFbo(FBOIndex index, unsigned int frameId) { return mFbos[frameId][index]; } + + osg::ref_ptr getTexture(TextureIndex index, unsigned int frameId) { return mTextures[frameId][index]; } + + osg::ref_ptr getPrimaryFbo(unsigned int frameId) { return mFbos[frameId][FBO_Multisample] ? mFbos[frameId][FBO_Multisample] : mFbos[frameId][FBO_Primary]; } + + osg::ref_ptr getStateUpdater() { return mStateUpdater; } + + const TechniqueList& getTechniques() { return mTechniques; } + + const TechniqueList& getTemplates() const { return mTemplates; } + + osg::ref_ptr getCanvas() { return mPingPongCanvas; } + + const auto& getTechniqueMap() const { return mTechniqueFileMap; } + + void resize(); + + bool enableTechnique(std::shared_ptr technique, std::optional location = std::nullopt); + + bool disableTechnique(std::shared_ptr technique, bool dirty = true); + + bool getSupportsNormalsRT() const { return mNormalsSupported; } + + template + void setUniform(std::shared_ptr technique, const std::string& name, const T& value) + { + if (!isEnabled()) + return; + + auto it = technique->findUniform(name); - auto getMsaaFbo() { return mMsaaFbo; } - auto getFbo() { return mFbo; } - auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; } + if (it == technique->getUniformMap().end()) + return; - osg::ref_ptr getOpaqueDepthTex() { return mOpaqueDepthTex; } + if ((*it)->mStatic) + { + Log(Debug::Warning) << "Attempting to set a configration variable [" << name << "] as a uniform"; + return; + } - void resize(int width, int height); + (*it)->setValue(value); + } + + bool isTechniqueEnabled(const std::shared_ptr& technique) const; + + void setExteriorFlag(bool exterior) { mExteriorFlag = exterior; } + + void setUnderwaterFlag(bool underwater) { mUnderwater = underwater; } + + void toggleMode(); + + std::shared_ptr loadTechnique(const std::string& name, bool insert=true); + + void addTemplate(std::shared_ptr technique); + + bool isEnabled() const { return mUsePostProcessing && mEnabled; } + + bool softParticlesEnabled() const {return mSoftParticles; } + + bool getHDR() const { return mHDR; } + + void disable(); + + void enable(bool usePostProcessing = true); + + void setRenderTargetSize(int width, int height) { mWidth = width; mHeight = height; } private: - void createTexturesAndCamera(int width, int height); + size_t frame() const { return mViewer->getFrameStamp()->getFrameNumber(); } + + void createObjectsForFrame(size_t frameId); + + void createTexturesAndCamera(size_t frameId); + + void reloadTechniques(); + + void reloadMainPass(fx::Technique& technique); + + void dirtyTechniques(); + + void update(size_t frameId); + + void cull(size_t frameId, osgUtil::CullVisitor* cv); - osgViewer::Viewer* mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mHUDCamera; - std::shared_ptr mMultiviewFbo; - osg::ref_ptr mMsaaFbo; - osg::ref_ptr mFbo; - osg::ref_ptr mFirstPersonDepthRBProxy; + std::array mTextures; + std::array mFbos; + + TechniqueList mTechniques; + TechniqueList mTemplates; + + std::unordered_map mTechniqueFileMap; + + int mSamples; + + bool mDirty; + size_t mDirtyFrameId; - osg::ref_ptr mSceneTex; - osg::ref_ptr mDepthTex; - osg::ref_ptr mOpaqueDepthTex; + RenderingManager& mRendering; + osgViewer::Viewer* mViewer; + const VFS::Manager* mVFS; + + bool mReload; + bool mEnabled; + bool mUsePostProcessing; + bool mSoftParticles; + bool mDisableDepthPasses; + + size_t mLastFrameNumber; + float mLastSimulationTime; + + bool mExteriorFlag; + bool mUnderwater; + bool mHDR; + bool mNormals; + bool mPrevNormals; + bool mNormalsSupported; + bool mUBO; + int mGLSLVersion; + + osg::ref_ptr mMainTemplate; + + osg::ref_ptr mStateUpdater; + osg::ref_ptr mPingPongCull; + osg::ref_ptr mPingPongCanvas; + osg::ref_ptr mTransparentDepthPostPass; + + int mWidth; + int mHeight; }; } #endif - diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 24e37a93ea..359b2e0f9d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -54,7 +54,9 @@ #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" #include "../mwgui/loadingscreen.hpp" +#include "../mwgui/postprocessorhud.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwbase/windowmanager.hpp" #include "sky.hpp" #include "effectmanager.hpp" @@ -114,7 +116,7 @@ namespace MWRender mProjectionMatrix = projectionMatrix; } - const osg::Matrixf& projectionMatrix() const + const osg::Matrixf& getProjectionMatrix() const { return mProjectionMatrix; } @@ -208,7 +210,6 @@ namespace MWRender mPlayerPos = playerPos; } - private: float mLinearFac; float mNear; @@ -411,9 +412,11 @@ namespace MWRender globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + globalDefines["refraction_enabled"] = "0"; globalDefines["useGPUShader4"] = "0"; globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; + globalDefines["disableNormals"] = "1"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; @@ -502,12 +505,9 @@ namespace MWRender mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(); rootNode->addCullCallback(mPerViewUniformStateUpdater); - mPostProcessor = new PostProcessor(viewer, mRootNode); - resourceSystem->getSceneManager()->setDepthFormat(SceneUtil::AutoDepth::depthInternalFormat()); - resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex()); - - if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(SceneUtil::AutoDepth::depthInternalFormat())) - Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it."; + mPostProcessor = new PostProcessor(*this, viewer, mRootNode, resourceSystem->getVFS()); + resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 0), mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 1)); + resourceSystem->getSceneManager()->setSupportsNormalsRT(mPostProcessor->getSupportsNormalsRT()); // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -692,9 +692,10 @@ namespace MWRender void RenderingManager::configureAmbient(const ESM::Cell *cell) { + bool isInterior = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); bool needsAdjusting = false; if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) - needsAdjusting = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); + needsAdjusting = isInterior; auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient); @@ -724,11 +725,14 @@ namespace MWRender mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f)); } - void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular) + void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis) { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(specular); + + mPostProcessor->getStateUpdater()->setSunColor(diffuse); + mPostProcessor->getStateUpdater()->setSunVis(sunVis); } void RenderingManager::setSunDirection(const osg::Vec3f &direction) @@ -738,6 +742,8 @@ namespace MWRender mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); mSky->setSunDirection(position); + + mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); } void RenderingManager::addCell(const MWWorld::CellStore *store) @@ -779,6 +785,7 @@ namespace MWRender mShadowManager->enableOutdoorMode(); else mShadowManager->enableIndoorMode(); + mPostProcessor->getStateUpdater()->setIsInterior(!enabled); } bool RenderingManager::toggleBorders() @@ -877,9 +884,28 @@ namespace MWRender mCamera->update(dt, paused); bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); - mStateUpdater->setFogStart(mFog->getFogStart(isUnderwater)); - mStateUpdater->setFogEnd(mFog->getFogEnd(isUnderwater)); - setFogColor(mFog->getFogColor(isUnderwater)); + + float fogStart = mFog->getFogStart(isUnderwater); + float fogEnd = mFog->getFogEnd(isUnderwater); + osg::Vec4f fogColor = mFog->getFogColor(isUnderwater); + + mStateUpdater->setFogStart(fogStart); + mStateUpdater->setFogEnd(fogEnd); + setFogColor(fogColor); + + auto world = MWBase::Environment::get().getWorld(); + const auto& stateUpdater = mPostProcessor->getStateUpdater(); + + stateUpdater->setFogRange(fogStart, fogEnd); + stateUpdater->setNearFar(mNearClip, mViewDistance); + stateUpdater->setIsUnderwater(isUnderwater); + stateUpdater->setFogColor(fogColor); + stateUpdater->setGameHour(world->getTimeStamp().getHour()); + stateUpdater->setWeatherId(world->getCurrentWeather()); + stateUpdater->setNextWeatherId(world->getNextWeather()); + stateUpdater->setWeatherTransition(world->getWeatherTransition()); + stateUpdater->setWindSpeed(world->getWindSpeed()); + mPostProcessor->setUnderwaterFlag(isUnderwater); } void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr) @@ -939,6 +965,8 @@ namespace MWRender mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water)); mWater->setHeight(height); mSky->setWaterHeight(height); + + mPostProcessor->getStateUpdater()->setWaterHeight(height); } void RenderingManager::screenshot(osg::Image* image, int w, int h) @@ -1131,6 +1159,11 @@ namespace MWRender return mObjects->getAnimation(ptr); } + PostProcessor* RenderingManager::getPostProcessor() + { + return mPostProcessor; + } + void RenderingManager::setupPlayer(const MWWorld::Ptr &player) { if (!mPlayerNode) @@ -1218,9 +1251,9 @@ namespace MWRender { auto res = Stereo::Manager::instance().eyeResolution(); mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); - Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->projectionMatrix()); + Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); } - else + else if (!mPostProcessor->isEnabled()) { mSharedUniformStateUpdater->setScreenRes(width, height); } @@ -1229,6 +1262,17 @@ namespace MWRender // Limit FOV here just for sure, otherwise viewing distance can be too high. float distanceMult = std::cos(osg::DegreesToRadians(std::min(fov, 140.f))/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + + if (mPostProcessor) + { + mPostProcessor->getStateUpdater()->setProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); + mPostProcessor->getStateUpdater()->setFov(fov); + } + } + + void RenderingManager::setScreenRes(int width, int height) + { + mSharedUniformStateUpdater->setScreenRes(width, height); } void RenderingManager::updateTextureFiltering() @@ -1335,6 +1379,17 @@ namespace MWRender mViewer->startThreading(); } } + else if (it->first == "Post Processing" && it->second == "enabled") + { + if (Settings::Manager::getBool("enabled", "Post Processing")) + mPostProcessor->enable(); + else + { + mPostProcessor->disable(); + if (auto* hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) + hud->setVisible(false); + } + } } if (updateProjection) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 5b5c28b198..850b1c5a39 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -128,7 +128,8 @@ namespace MWRender void skySetMoonColour(bool red); void setSunDirection(const osg::Vec3f& direction); - void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular); + void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis); + void setNight(bool isNight) { mNight = isNight; } void configureAmbient(const ESM::Cell* cell); void configureFog(const ESM::Cell* cell); @@ -192,6 +193,8 @@ namespace MWRender Animation* getAnimation(const MWWorld::Ptr& ptr); const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const; + PostProcessor* getPostProcessor(); + void addWaterRippleEmitter(const MWWorld::Ptr& ptr); void removeWaterRippleEmitter(const MWWorld::Ptr& ptr); void emitWaterRipple(const osg::Vec3f& pos); @@ -247,6 +250,8 @@ namespace MWRender void updateProjectionMatrix(); + void setScreenRes(int width, int height); + private: void updateTextureFiltering(); void updateAmbient(); @@ -310,6 +315,7 @@ namespace MWRender float mFieldOfView; float mFirstPersonFieldOfView; bool mUpdateProjectionMatrix = false; + bool mNight = false; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 65b6a1bdbc..64961bf037 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -106,17 +106,16 @@ namespace MWRender if (ext) { + size_t frameId = renderInfo.getState()->getFrameStamp()->getFrameNumber() % 2; osg::FrameBufferObject* fbo = nullptr; + if (Stereo::getStereo()) fbo = Stereo::Manager::instance().multiviewFramebuffer()->layerFbo(0); - else if (postProcessor) - fbo = postProcessor->getFbo(); + else if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) + fbo = postProcessor->getFbo(PostProcessor::FBO_Primary, frameId); if (fbo) - { - ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo->getHandle(renderInfo.getContextID())); - renderInfo.getState()->glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); - } + fbo->apply(*renderInfo.getState(), osg::FrameBufferObject::READ_FRAMEBUFFER); } mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f0a591477a..d8add9a685 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -248,6 +248,7 @@ namespace MWRender , mBaseWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) + , mSunglareEnabled(true) , mPrecipitationAlpha(0.f) , mDirtyParticlesEffect(false) { @@ -303,6 +304,7 @@ namespace MWRender atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager)); + mSun->setSunglare(mSunglareEnabled); mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); @@ -776,6 +778,14 @@ namespace MWRender return mBaseWindSpeed; } + void SkyManager::setSunglare(bool enabled) + { + mSunglareEnabled = enabled; + + if (mSun) + mSun->setSunglare(mSunglareEnabled); + } + void SkyManager::sunEnable() { if (!mCreated) return; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index e2ceae45f4..1fdf476bd5 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -96,6 +96,8 @@ namespace MWRender float getBaseWindSpeed() const; + void setSunglare(bool enabled); + private: void create(); ///< no need to call this, automatically done on first enable() @@ -184,6 +186,7 @@ namespace MWRender bool mEnabled; bool mSunEnabled; + bool mSunglareEnabled; float mPrecipitationAlpha; bool mDirtyParticlesEffect; diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index ec9e6e7635..843582064b 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -812,6 +812,12 @@ namespace MWRender mSunGlareCallback->setTimeOfDayFade(val); } + void Sun::setSunglare(bool enabled) + { + mSunGlareNode->setNodeMask(enabled ? ~0u : 0); + mSunFlashNode->setNodeMask(enabled ? ~0u : 0); + } + osg::ref_ptr Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible) { osg::ref_ptr oqn = new osg::OcclusionQueryNode; diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index a0d9ed72b4..604e5909e8 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -248,6 +248,7 @@ namespace MWRender void setDirection(const osg::Vec3f& direction); void setGlareTimeOfDayFade(float val); + void setSunglare(bool enabled); private: /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. diff --git a/apps/openmw/mwrender/transparentpass.cpp b/apps/openmw/mwrender/transparentpass.cpp new file mode 100644 index 0000000000..8bb5713230 --- /dev/null +++ b/apps/openmw/mwrender/transparentpass.cpp @@ -0,0 +1,89 @@ +#include "transparentpass.hpp" + +#include +#include + +#include + +#include + +namespace MWRender +{ + TransparentDepthBinCallback::TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass) + : mStateSet(new osg::StateSet) + , mPostPass(postPass) + { + osg::ref_ptr image = new osg::Image; + image->allocateImage(1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE); + image->setColor(osg::Vec4(1,1,1,1), 0, 0); + + osg::ref_ptr dummyTexture = new osg::Texture2D(image); + + constexpr osg::StateAttribute::OverrideValue modeOff = osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE; + constexpr osg::StateAttribute::OverrideValue modeOn = osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE; + + mStateSet->setTextureAttributeAndModes(0, dummyTexture); + + osg::ref_ptr vertex = shaderManager.getShader("blended_depth_postpass_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr fragment = shaderManager.getShader("blended_depth_postpass_fragment.glsl", {}, osg::Shader::FRAGMENT); + + mStateSet->setAttributeAndModes(new osg::BlendFunc, modeOff); + mStateSet->setAttributeAndModes(shaderManager.getProgram(vertex, fragment), modeOn); + + for (unsigned int unit = 1; unit < 8; ++unit) + mStateSet->setTextureMode(unit, GL_TEXTURE_2D, modeOff); + } + + void TransparentDepthBinCallback::drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions* ext = state.get(); + + bool validFbo = false; + unsigned int frameId = state.getFrameStamp()->getFrameNumber() % 2; + + const auto& fbo = mFbo[frameId]; + const auto& msaaFbo = mMsaaFbo[frameId]; + const auto& opaqueFbo = mOpaqueFbo[frameId]; + + if (bin->getStage()->getMultisampleResolveFramebufferObject() && bin->getStage()->getMultisampleResolveFramebufferObject() == fbo) + validFbo = true; + else if (bin->getStage()->getFrameBufferObject() && (bin->getStage()->getFrameBufferObject() == fbo || bin->getStage()->getFrameBufferObject() == msaaFbo)) + validFbo = true; + + if (!validFbo) + { + bin->drawImplementation(renderInfo, previous); + return; + } + + const osg::Texture* tex = opaqueFbo->getAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER).getTexture(); + + opaqueFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + ext->glBlitFramebuffer(0, 0, tex->getTextureWidth(), tex->getTextureHeight(), 0, 0, tex->getTextureWidth(), tex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); + + if (msaaFbo) + msaaFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + else + fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + // draws scene into primary attachments + bin->drawImplementation(renderInfo, previous); + + if (!mPostPass) + return; + + opaqueFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + osg::ref_ptr restore = bin->getStateSet(); + bin->setStateSet(mStateSet); + // draws transparent post-pass to populate a postprocess friendly depth texture with alpha-clipped geometry + bin->drawImplementation(renderInfo, previous); + bin->setStateSet(restore); + + if (!msaaFbo) + fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + } + +} \ No newline at end of file diff --git a/apps/openmw/mwrender/transparentpass.hpp b/apps/openmw/mwrender/transparentpass.hpp new file mode 100644 index 0000000000..a933dde989 --- /dev/null +++ b/apps/openmw/mwrender/transparentpass.hpp @@ -0,0 +1,38 @@ +#ifndef OPENMW_MWRENDER_TRANSPARENTPASS_H +#define OPENMW_MWRENDER_TRANSPARENTPASS_H + +#include + +#include +#include + +#include + +#include "postprocessor.hpp" + +namespace Shader +{ + class ShaderManager; +} + +namespace MWRender +{ + class TransparentDepthBinCallback : public osgUtil::RenderBin::DrawCallback + { + public: + TransparentDepthBinCallback(Shader::ShaderManager& shaderManager, bool postPass); + + void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override; + + std::array, 2> mFbo; + std::array, 2> mMsaaFbo; + std::array, 2> mOpaqueFbo; + + private: + osg::ref_ptr mStateSet; + bool mPostPass; + }; + +} + +#endif \ No newline at end of file diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c67a206416..9896893da2 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -267,6 +267,7 @@ public: : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) , mNodeMask(Refraction::sDefaultCullMask) { + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); mClipCullNode = new ClipCullNode; } @@ -342,6 +343,7 @@ public: : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) { setInterior(isInterior); + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); mClipCullNode = new ClipCullNode; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0da47a9a74..db1b9e592f 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -27,6 +27,7 @@ #include "../mwrender/renderingmanager.hpp" #include "../mwrender/landmanager.hpp" +#include "../mwrender/postprocessor.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/actor.hpp" @@ -860,6 +861,8 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent); + + MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx); } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) @@ -879,6 +882,8 @@ namespace MWWorld if (changeEvent) MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); + + MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(true); } CellStore* Scene::getCurrentCell () diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 16a0bef131..eb0226d506 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -786,6 +786,7 @@ namespace MWWorld -0.268f, // approx tan( -15 degrees ) static_cast(sin(theta))); mRendering.setSunDirection( final * -1 ); + mRendering.setNight(is_night); } float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); @@ -807,7 +808,7 @@ namespace MWWorld mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mResult.mDLFogOffset/100.0f, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade, mResult.mGlareView * glareFade); mRendering.getSkyManager()->setWeather(mResult); @@ -857,11 +858,6 @@ namespace MWWorld mFastForward = !incremental ? true : mFastForward; } - unsigned int WeatherManager::getWeatherID() const - { - return mCurrentWeather; - } - NightDayMode WeatherManager::getNightDayMode() const { return mNightDayMode; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index b29ad9e994..21b690f7b6 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -307,7 +307,11 @@ namespace MWWorld void advanceTime(double hours, bool incremental); - unsigned int getWeatherID() const; + int getWeatherID() const { return mCurrentWeather; } + + int getNextWeatherID() const { return mNextWeather; } + + float getTransitionFactor() const { return mTransitionFactor; } bool useTorches(float hour) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 851ba2daf4..78eeab79ff 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -55,6 +55,7 @@ #include "../mwrender/renderingmanager.hpp" #include "../mwrender/camera.hpp" #include "../mwrender/vismask.hpp" +#include "../mwrender/postprocessor.hpp" #include "../mwscript/globalscripts.hpp" @@ -2045,6 +2046,16 @@ namespace MWWorld return mWeatherManager->getWeatherID(); } + int World::getNextWeather() const + { + return mWeatherManager->getNextWeatherID(); + } + + float World::getWeatherTransition() const + { + return mWeatherManager->getTransitionFactor(); + } + unsigned int World::getNightDayMode() const { return mWeatherManager->getNightDayMode(); @@ -3986,4 +3997,8 @@ namespace MWWorld return mPrng; } + MWRender::PostProcessor* World::getPostProcessor() + { + return mRendering->getPostProcessor(); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8098c6ec20..088c4097aa 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -54,6 +54,7 @@ namespace MWRender class SkyManager; class Animation; class Camera; + class PostProcessor; } namespace ToUTF8 @@ -329,6 +330,10 @@ namespace MWWorld int getCurrentWeather() const override; + int getNextWeather() const override; + + float getWeatherTransition() const override; + unsigned int getNightDayMode() const override; int getMasserPhase() const override; @@ -747,6 +752,8 @@ namespace MWWorld Misc::Rng::Generator& getPrng() override; MWRender::RenderingManager* getRenderingManager() override { return mRendering.get(); } + + MWRender::PostProcessor* getPostProcessor() override; }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 7e42a49f66..b17e49207d 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -52,6 +52,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) serialization/integration.cpp settings/parser.cpp + settings/shadermanager.cpp shader/parsedefines.cpp shader/parsefors.cpp @@ -75,6 +76,9 @@ if (GTEST_FOUND AND GMOCK_FOUND) toutf8/toutf8.cpp esm4/includes.cpp + + fx/lexer.cpp + fx/technique.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/fx/lexer.cpp b/apps/openmw_test_suite/fx/lexer.cpp new file mode 100644 index 0000000000..5024622a71 --- /dev/null +++ b/apps/openmw_test_suite/fx/lexer.cpp @@ -0,0 +1,216 @@ +#include + +#include + +namespace +{ + using namespace testing; + using namespace fx::Lexer; + + struct LexerTest : Test {}; + + struct LexerSingleTokenTest : Test + { + template + void test() + { + const std::string content = std::string(Token::repr); + Lexer lexer(content); + + EXPECT_TRUE(std::holds_alternative(lexer.next())); + } + }; + + TEST_F(LexerSingleTokenTest, single_token_shared) { test(); } + TEST_F(LexerSingleTokenTest, single_token_technique) { test(); } + TEST_F(LexerSingleTokenTest, single_token_main_pass) { test(); } + TEST_F(LexerSingleTokenTest, single_token_render_target) { test(); } + TEST_F(LexerSingleTokenTest, single_token_vertex) { test(); } + TEST_F(LexerSingleTokenTest, single_token_fragment) { test(); } + TEST_F(LexerSingleTokenTest, single_token_compute) { test(); } + TEST_F(LexerSingleTokenTest, single_token_sampler_1d) { test(); } + TEST_F(LexerSingleTokenTest, single_token_sampler_2d) { test(); } + TEST_F(LexerSingleTokenTest, single_token_sampler_3d) { test(); } + TEST_F(LexerSingleTokenTest, single_token_true) { test(); } + TEST_F(LexerSingleTokenTest, single_token_false) { test(); } + TEST_F(LexerSingleTokenTest, single_token_vec2) { test(); } + TEST_F(LexerSingleTokenTest, single_token_vec3) { test(); } + TEST_F(LexerSingleTokenTest, single_token_vec4) { test(); } + + TEST(LexerTest, peek_whitespace_only_content_should_be_eof) + { + Lexer lexer(R"( + + )"); + + EXPECT_TRUE(std::holds_alternative(lexer.peek())); + } + + TEST(LexerTest, float_with_no_prefixed_digits) + { + Lexer lexer(R"( + 0.123; + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_FLOAT_EQ(std::get(token).value, 0.123f); + } + + TEST(LexerTest, float_with_alpha_prefix) + { + Lexer lexer(R"( + abc.123; + )"); + + EXPECT_TRUE(std::holds_alternative(lexer.next())); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_FLOAT_EQ(std::get(token).value, 0.123f); + } + + TEST(LexerTest, float_with_numeric_prefix) + { + Lexer lexer(R"( + 123.123; + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_FLOAT_EQ(std::get(token).value, 123.123f); + } + + TEST(LexerTest, int_should_not_be_float) + { + Lexer lexer(R"( + 123 + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + EXPECT_EQ(std::get(token).value, 123); + } + + TEST(LexerTest, simple_string) + { + Lexer lexer(R"( + "test string" + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + + std::string parsed = std::string(std::get(token).value); + EXPECT_EQ("test string", parsed); + } + + TEST(LexerTest, fail_on_unterminated_double_quotes) + { + Lexer lexer(R"( + "unterminated string' + )"); + + EXPECT_THROW(lexer.next(), LexerException); + } + + TEST(LexerTest, multiline_strings_with_single_quotes) + { + Lexer lexer(R"( + "string that is + on multiple with 'single quotes' + and correctly terminated!" + )"); + + auto token = lexer.next(); + EXPECT_TRUE(std::holds_alternative(token)); + } + + TEST(LexerTest, fail_on_unterminated_double_quotes_with_multiline_strings) + { + Lexer lexer(R"( + "string that is + on multiple with 'single quotes' + and but is unterminated :( + )"); + + EXPECT_THROW(lexer.next(), LexerException); + } + + TEST(LexerTest, jump_with_single_nested_bracket) + { + const std::string content = R"( + #version 120 + + void main() + { + return 0; + }})"; + + const std::string expected = content.substr(0, content.size() - 1); + + Lexer lexer(content); + + auto block = lexer.jump(); + + EXPECT_NE(block, std::nullopt); + EXPECT_EQ(expected, std::string(block.value())); + } + + TEST(LexerTest, jump_with_single_line_comments_and_mismatching_brackets) + { + const std::string content = R"( + #version 120 + + void main() + { + // } + return 0; + }})"; + + const std::string expected = content.substr(0, content.size() - 1); + + Lexer lexer(content); + + auto block = lexer.jump(); + + EXPECT_NE(block, std::nullopt); + EXPECT_EQ(expected, std::string(block.value())); + } + + TEST(LexerTest, jump_with_multi_line_comments_and_mismatching_brackets) + { + const std::string content = R"( + #version 120 + + void main() + { + /* + } + */ + return 0; + }})"; + + const std::string expected = content.substr(0, content.size() - 1); + + Lexer lexer(content); + + auto block = lexer.jump(); + + EXPECT_NE(block, std::nullopt); + EXPECT_EQ(expected, std::string(block.value())); + } + + TEST(LexerTest, immediate_closed_blocks) + { + Lexer lexer(R"(block{})"); + + EXPECT_TRUE(std::holds_alternative(lexer.next())); + EXPECT_TRUE(std::holds_alternative(lexer.next())); + auto block = lexer.jump(); + EXPECT_TRUE(block.has_value()); + EXPECT_TRUE(block.value().empty()); + EXPECT_TRUE(std::holds_alternative(lexer.next())); + } + +} diff --git a/apps/openmw_test_suite/fx/technique.cpp b/apps/openmw_test_suite/fx/technique.cpp new file mode 100644 index 0000000000..d10e1f1b37 --- /dev/null +++ b/apps/openmw_test_suite/fx/technique.cpp @@ -0,0 +1,204 @@ +#include "gmock/gmock.h" +#include + +#include +#include +#include +#include + +#include "../lua/testing_util.hpp" + +namespace +{ + +TestFile technique_properties(R"( + fragment main {} + vertex main {} + technique { + passes = main; + version = "0.1a"; + description = "description"; + author = "author"; + glsl_version = 330; + glsl_profile = "compatability"; + glsl_extensions = GL_EXT_gpu_shader4, GL_ARB_uniform_buffer_object; + flags = disable_sunglare; + hdr = true; + } +)"); + +TestFile rendertarget_properties{R"( + render_target rendertarget { + width_ratio = 0.5; + height_ratio = 0.5; + internal_format = r16f; + source_type = float; + source_format = red; + mipmaps = true; + wrap_s = clamp_to_edge; + wrap_t = repeat; + min_filter = linear; + mag_filter = nearest; + } + fragment downsample2x(target=rendertarget) { + + omw_In vec2 omw_TexCoord; + + void main() + { + omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r; + } + } + fragment main { } + technique { passes = downsample2x, main; } +)"}; + + +TestFile uniform_properties{R"( + uniform_vec4 uVec4 { + default = vec4(0,0,0,0); + min = vec4(0,1,0,0); + max = vec4(0,0,1,0); + step = 0.5; + header = "header"; + static = true; + description = "description"; + } + fragment main { } + technique { passes = main; } +)"}; + + +TestFile missing_sampler_source{R"( + sampler_1d mysampler1d { } + fragment main { } + technique { passes = main; } +)"}; + +TestFile repeated_shared_block{R"( + shared { + float myfloat = 1.0; + } + shared {} + fragment main { } + technique { passes = main; } +)"}; + + + using namespace testing; + using namespace fx; + + struct TechniqueTest : Test + { + std::unique_ptr mVFS; + Resource::ImageManager mImageManager; + std::unique_ptr mTechnique; + + TechniqueTest() + : mVFS(createTestVFS({ + {"shaders/technique_properties.omwfx", &technique_properties}, + {"shaders/rendertarget_properties.omwfx", &rendertarget_properties}, + {"shaders/uniform_properties.omwfx", &uniform_properties}, + {"shaders/missing_sampler_source.omwfx", &missing_sampler_source}, + {"shaders/repeated_shared_block.omwfx", &repeated_shared_block}, + })) + , mImageManager(mVFS.get()) + { + Settings::Manager::setBool("radial fog", "Shaders", true); + Settings::Manager::setBool("stereo enabled", "Stereo", false); + } + + void compile(const std::string& name) + { + mTechnique = std::make_unique(*mVFS.get(), mImageManager, name, 1, 1, true, true); + mTechnique->compile(); + } + }; + + TEST_F(TechniqueTest, technique_properties) + { + std::unordered_set targetExtensions = { + "GL_EXT_gpu_shader4", + "GL_ARB_uniform_buffer_object" + }; + + compile("technique_properties"); + + EXPECT_EQ(mTechnique->getVersion(), "0.1a"); + EXPECT_EQ(mTechnique->getDescription(), "description"); + EXPECT_EQ(mTechnique->getAuthor(), "author"); + EXPECT_EQ(mTechnique->getGLSLVersion(), 330); + EXPECT_EQ(mTechnique->getGLSLProfile(), "compatability"); + EXPECT_EQ(mTechnique->getGLSLExtensions(), targetExtensions); + EXPECT_EQ(mTechnique->getFlags(), Technique::Flag_Disable_SunGlare); + EXPECT_EQ(mTechnique->getHDR(), true); + EXPECT_EQ(mTechnique->getPasses().size(), 1); + EXPECT_EQ(mTechnique->getPasses().front()->getName(), "main"); + } + + TEST_F(TechniqueTest, rendertarget_properties) + { + compile("rendertarget_properties"); + + EXPECT_EQ(mTechnique->getRenderTargetsMap().size(), 1); + + const std::string_view name = mTechnique->getRenderTargetsMap().begin()->first; + auto& rt = mTechnique->getRenderTargetsMap().begin()->second; + auto& texture = rt.mTarget; + + EXPECT_EQ(name, "rendertarget"); + EXPECT_EQ(rt.mMipMap, true); + EXPECT_EQ(rt.mSize.mWidthRatio, 0.5f); + EXPECT_EQ(rt.mSize.mHeightRatio, 0.5f); + EXPECT_EQ(texture->getWrap(osg::Texture::WRAP_S), osg::Texture::CLAMP_TO_EDGE); + EXPECT_EQ(texture->getWrap(osg::Texture::WRAP_T), osg::Texture::REPEAT); + EXPECT_EQ(texture->getFilter(osg::Texture::MIN_FILTER), osg::Texture::LINEAR); + EXPECT_EQ(texture->getFilter(osg::Texture::MAG_FILTER), osg::Texture::NEAREST); + EXPECT_EQ(texture->getSourceType(), static_cast(GL_FLOAT)); + EXPECT_EQ(texture->getSourceFormat(), static_cast(GL_RED)); + EXPECT_EQ(texture->getInternalFormat(), static_cast(GL_R16F)); + + EXPECT_EQ(mTechnique->getPasses().size(), 2); + EXPECT_EQ(mTechnique->getPasses()[0]->getTarget(), "rendertarget"); + } + + TEST_F(TechniqueTest, uniform_properties) + { + compile("uniform_properties"); + + EXPECT_EQ(mTechnique->getUniformMap().size(), 1); + + const auto& uniform = mTechnique->getUniformMap().front(); + + EXPECT_TRUE(uniform->mStatic); + EXPECT_FLOAT_EQ(uniform->mStep, 0.5f); + EXPECT_EQ(uniform->getDefault(), osg::Vec4f(0,0,0,0)); + EXPECT_EQ(uniform->getMin(), osg::Vec4f(0,1,0,0)); + EXPECT_EQ(uniform->getMax(), osg::Vec4f(0,0,1,0)); + EXPECT_EQ(uniform->mHeader, "header"); + EXPECT_EQ(uniform->mDescription, "description"); + EXPECT_EQ(uniform->mName, "uVec4"); + } + + TEST_F(TechniqueTest, fail_with_missing_source_for_sampler) + { + internal::CaptureStdout(); + + compile("missing_sampler_source"); + + std::string output = internal::GetCapturedStdout(); + Log(Debug::Error) << output; + EXPECT_THAT(output, HasSubstr("sampler_1d 'mysampler1d' requires a filename")); + } + + TEST_F(TechniqueTest, fail_with_repeated_shared_block) + { + internal::CaptureStdout(); + + compile("repeated_shared_block"); + + std::string output = internal::GetCapturedStdout(); + Log(Debug::Error) << output; + EXPECT_THAT(output, HasSubstr("repeated 'shared' block")); + } +} \ No newline at end of file diff --git a/apps/openmw_test_suite/lua/testing_util.hpp b/apps/openmw_test_suite/lua/testing_util.hpp index a40314bd0d..217c8ae5d5 100644 --- a/apps/openmw_test_suite/lua/testing_util.hpp +++ b/apps/openmw_test_suite/lua/testing_util.hpp @@ -26,6 +26,11 @@ namespace return std::make_unique(mContent, std::ios_base::in); } + std::string getPath() override + { + return "TestFile"; + } + private: const std::string mContent; }; diff --git a/apps/openmw_test_suite/settings/shadermanager.cpp b/apps/openmw_test_suite/settings/shadermanager.cpp new file mode 100644 index 0000000000..8f8e09e134 --- /dev/null +++ b/apps/openmw_test_suite/settings/shadermanager.cpp @@ -0,0 +1,66 @@ +#include + +#include + +#include + +namespace +{ + using namespace testing; + using namespace Settings; + + struct ShaderSettingsTest : Test + { + template + void withSettingsFile( const std::string& content, F&& f) + { + const auto path = std::string(UnitTest::GetInstance()->current_test_info()->name()) + ".yaml"; + + { + std::ofstream stream; + stream.open(path); + stream << content; + stream.close(); + } + + f(path); + } + }; + + TEST_F(ShaderSettingsTest, fail_to_fetch_then_set_and_succeed) + { + const std::string content = +R"YAML( +config: + shader: + vec3_uniform: [1.0, 2.0] +)YAML"; + + withSettingsFile(content, [this] (const auto& path) { + EXPECT_TRUE(ShaderManager::get().load(path)); + EXPECT_FALSE(ShaderManager::get().getValue("shader", "vec3_uniform").has_value()); + EXPECT_TRUE(ShaderManager::get().setValue("shader", "vec3_uniform", osg::Vec3f(1, 2, 3))); + EXPECT_TRUE(ShaderManager::get().getValue("shader", "vec3_uniform").has_value()); + EXPECT_EQ(ShaderManager::get().getValue("shader", "vec3_uniform").value(), osg::Vec3f(1, 2, 3)); + EXPECT_TRUE(ShaderManager::get().save()); + }); + } + + TEST_F(ShaderSettingsTest, fail_to_load_file_then_fail_to_set_and_get) + { + const std::string content = +R"YAML( +config: + shader: + uniform: 12.0 + >Defeated by a sideways carrot +)YAML"; + + withSettingsFile(content, [this] (const auto& path) { + EXPECT_FALSE(ShaderManager::get().load(path)); + EXPECT_FALSE(ShaderManager::get().setValue("shader", "uniform", 12.0)); + EXPECT_FALSE(ShaderManager::get().getValue("shader", "uniform").has_value()); + EXPECT_FALSE(ShaderManager::get().save()); + }); + } +} \ No newline at end of file diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5ad88523b8..8e7f1bbfa5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -82,6 +82,10 @@ add_component_dir (to_utf8 add_component_dir(esm attr common defs esmcommon reader records util luascripts format) +add_component_dir(fx pass technique lexer widgets stateupdater) + +add_component_dir(std140 ubo) + add_component_dir (esm3 esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst diff --git a/components/fx/lexer.cpp b/components/fx/lexer.cpp new file mode 100644 index 0000000000..d416dd692d --- /dev/null +++ b/components/fx/lexer.cpp @@ -0,0 +1,301 @@ +#include "lexer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "types.hpp" + +namespace fx +{ + namespace Lexer + { + Lexer::Lexer(std::string_view buffer) + : mHead(buffer.data()) + , mTail(mHead + buffer.length()) + , mAbsolutePos(0) + , mColumn(0) + , mLine(0) + , mBuffer(buffer) + , mLastToken(Eof{}) + { } + + Token Lexer::next() + { + if (mLookahead) + { + auto token = *mLookahead; + drop(); + return token; + } + + mLastToken = scanToken(); + + return mLastToken; + } + + Token Lexer::peek() + { + if (!mLookahead) + mLookahead = scanToken(); + + return *mLookahead; + } + + void Lexer::drop() + { + mLookahead = std::nullopt; + } + + std::optional Lexer::jump() + { + bool multi = false; + bool single = false; + auto start = mHead; + std::size_t level = 1; + + mLastJumpBlock.line = mLine; + + if (head() == '}') + { + mLastJumpBlock.content = {}; + return mLastJumpBlock.content; + } + + for (; mHead != mTail; advance()) + { + if (head() == '\n') + { + mLine++; + mColumn = 0; + if (single) + { + single = false; + continue; + } + } + else if (multi && head() == '*' && peekChar('/')) + { + multi = false; + advance(); + continue; + } + else if (multi || single) + { + continue; + } + else if (head() == '/' && peekChar('/')) + { + single = true; + advance(); + continue; + } + else if (head() == '/' && peekChar('*')) + { + multi = true; + advance(); + continue; + } + + if (head() == '{') + level++; + else if (head() == '}') + level--; + + if (level == 0) + { + mHead--; + auto sv = std::string_view{start, static_cast(mHead + 1 - start)}; + mLastJumpBlock.content = sv; + return sv; + } + } + + mLastJumpBlock = {}; + return std::nullopt; + } + + Lexer::Block Lexer::getLastJumpBlock() const + { + return mLastJumpBlock; + } + + [[noreturn]] void Lexer::error(const std::string& msg) + { + throw LexerException(Misc::StringUtils::format("Line %zu Col %zu. %s", mLine + 1, mColumn, msg)); + } + + void Lexer::advance() + { + mAbsolutePos++; + mHead++; + mColumn++; + } + + char Lexer::head() + { + return *mHead; + } + + bool Lexer::peekChar(char c) + { + if (mHead == mTail) + return false; + return *(mHead + 1) == c; + } + + Token Lexer::scanToken() + { + while (true) + { + if (mHead == mTail) + return {Eof{}}; + + if (head() == '\n') + { + mLine++; + mColumn = 0; + } + + if (!std::isspace(head())) + break; + + advance(); + } + + if (head() == '\"') + return scanStringLiteral(); + + if (std::isalpha(head())) + return scanLiteral(); + + if (std::isdigit(head()) || head() == '.' || head() == '-') + return scanNumber(); + + switch(head()) + { + case '=': + advance(); + return {Equal{}}; + case '{': + advance(); + return {Open_bracket{}}; + case '}': + advance(); + return {Close_bracket{}}; + case '(': + advance(); + return {Open_Parenthesis{}}; + case ')': + advance(); + return {Close_Parenthesis{}}; + case '\"': + advance(); + return {Quote{}}; + case ':': + advance(); + return {Colon{}}; + case ';': + advance(); + return {SemiColon{}}; + case '|': + advance(); + return {VBar{}}; + case ',': + advance(); + return {Comma{}}; + default: + error(Misc::StringUtils::format("unexpected token <%c>", head())); + } + } + + Token Lexer::scanLiteral() + { + auto start = mHead; + advance(); + + while (mHead != mTail && (std::isalnum(head()) || head() == '_')) + advance(); + + std::string_view value{start, static_cast(mHead - start)}; + + if (value == "shared") return Shared{}; + if (value == "technique") return Technique{}; + if (value == "main_pass") return Main_Pass{}; + if (value == "render_target") return Render_Target{}; + if (value == "vertex") return Vertex{}; + if (value == "fragment") return Fragment{}; + if (value == "compute") return Compute{}; + if (value == "sampler_1d") return Sampler_1D{}; + if (value == "sampler_2d") return Sampler_2D{}; + if (value == "sampler_3d") return Sampler_3D{}; + if (value == "uniform_bool") return Uniform_Bool{}; + if (value == "uniform_float") return Uniform_Float{}; + if (value == "uniform_int") return Uniform_Int{}; + if (value == "uniform_vec2") return Uniform_Vec2{}; + if (value == "uniform_vec3") return Uniform_Vec3{}; + if (value == "uniform_vec4") return Uniform_Vec4{}; + if (value == "true") return True{}; + if (value == "false") return False{}; + if (value == "vec2") return Vec2{}; + if (value == "vec3") return Vec3{}; + if (value == "vec4") return Vec4{}; + + return Literal{value}; + } + + Token Lexer::scanStringLiteral() + { + advance(); // consume quote + auto start = mHead; + + bool terminated = false; + + for (; mHead != mTail; advance()) + { + if (head() == '\"') + { + terminated = true; + advance(); + break; + } + } + + if (!terminated) + error("unterminated string"); + + return String{{start, static_cast(mHead - start - 1)}}; + } + + Token Lexer::scanNumber() + { + double buffer; + + char* endPtr; + buffer = std::strtod(mHead, &endPtr); + + if (endPtr == nullptr) + error("critical error while parsing number"); + + const char* tmp = mHead; + mHead = endPtr; + + for (; tmp != endPtr; ++tmp) + { + if ((*tmp == '.')) + return Float{static_cast(buffer)}; + } + + return Integer{static_cast(buffer)}; + } + } +} diff --git a/components/fx/lexer.hpp b/components/fx/lexer.hpp new file mode 100644 index 0000000000..e24239399c --- /dev/null +++ b/components/fx/lexer.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_COMPONENTS_FX_LEXER_H +#define OPENMW_COMPONENTS_FX_LEXER_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "lexer_types.hpp" + +namespace fx +{ + namespace Lexer + { + struct LexerException : std::runtime_error + { + LexerException(const std::string& message) : std::runtime_error(message) {} + LexerException(const char* message) : std::runtime_error(message) {} + }; + + class Lexer + { + public: + struct Block + { + int line; + std::string_view content; + }; + + Lexer(std::string_view buffer); + Lexer() = delete; + + Token next(); + Token peek(); + + // Jump ahead to next uncommented closing bracket at level zero. Assumes the head is at an opening bracket. + // Returns the contents of the block excluding the brackets and places cursor at closing bracket. + std::optional jump(); + + Block getLastJumpBlock() const; + + [[noreturn]] void error(const std::string& msg); + + private: + void drop(); + void advance(); + char head(); + bool peekChar(char c); + + Token scanToken(); + Token scanLiteral(); + Token scanStringLiteral(); + Token scanNumber(); + + const char* mHead; + const char* mTail; + std::size_t mAbsolutePos; + std::size_t mColumn; + std::size_t mLine; + std::string_view mBuffer; + Token mLastToken; + std::optional mLookahead; + + Block mLastJumpBlock; + }; + } +} + +#endif diff --git a/components/fx/lexer_types.hpp b/components/fx/lexer_types.hpp new file mode 100644 index 0000000000..0d81c483b7 --- /dev/null +++ b/components/fx/lexer_types.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_COMPONENTS_FX_LEXER_TYPES_H +#define OPENMW_COMPONENTS_FX_LEXER_TYPES_H + +#include +#include + +namespace fx +{ + namespace Lexer + { + struct Float { inline static constexpr std::string_view repr = "float"; float value = 0.0;}; + struct Integer { inline static constexpr std::string_view repr = "integer"; int value = 0;}; + struct Boolean { inline static constexpr std::string_view repr = "boolean"; bool value = false;}; + struct Literal { inline static constexpr std::string_view repr = "literal"; std::string_view value;}; + struct String { inline static constexpr std::string_view repr = "string"; std::string_view value;}; + struct Shared { inline static constexpr std::string_view repr = "shared"; }; + struct Vertex { inline static constexpr std::string_view repr = "vertex"; }; + struct Fragment { inline static constexpr std::string_view repr = "fragment"; }; + struct Compute { inline static constexpr std::string_view repr = "compute"; }; + struct Technique { inline static constexpr std::string_view repr = "technique"; }; + struct Main_Pass { inline static constexpr std::string_view repr = "main_pass"; }; + struct Render_Target { inline static constexpr std::string_view repr = "render_target"; }; + struct Sampler_1D { inline static constexpr std::string_view repr = "sampler_1d"; }; + struct Sampler_2D { inline static constexpr std::string_view repr = "sampler_2d"; }; + struct Sampler_3D { inline static constexpr std::string_view repr = "sampler_3d"; }; + struct Uniform_Bool { inline static constexpr std::string_view repr = "uniform_bool"; }; + struct Uniform_Float { inline static constexpr std::string_view repr = "uniform_float"; }; + struct Uniform_Int { inline static constexpr std::string_view repr = "uniform_int"; }; + struct Uniform_Vec2 { inline static constexpr std::string_view repr = "uniform_vec2"; }; + struct Uniform_Vec3 { inline static constexpr std::string_view repr = "uniform_vec3"; }; + struct Uniform_Vec4 { inline static constexpr std::string_view repr = "uniform_vec4"; }; + struct Eof { inline static constexpr std::string_view repr = "eof"; }; + struct Equal { inline static constexpr std::string_view repr = "equal"; }; + struct Open_bracket { inline static constexpr std::string_view repr = "open_bracket"; }; + struct Close_bracket { inline static constexpr std::string_view repr = "close_bracket"; }; + struct Open_Parenthesis { inline static constexpr std::string_view repr = "open_parenthesis"; }; + struct Close_Parenthesis{ inline static constexpr std::string_view repr = "close_parenthesis"; }; + struct Quote { inline static constexpr std::string_view repr = "quote"; }; + struct SemiColon { inline static constexpr std::string_view repr = "semicolon"; }; + struct Comma { inline static constexpr std::string_view repr = "comma"; }; + struct VBar { inline static constexpr std::string_view repr = "vbar"; }; + struct Colon { inline static constexpr std::string_view repr = "colon"; }; + struct True { inline static constexpr std::string_view repr = "true"; }; + struct False { inline static constexpr std::string_view repr = "false"; }; + struct Vec2 { inline static constexpr std::string_view repr = "vec2"; }; + struct Vec3 { inline static constexpr std::string_view repr = "vec3"; }; + struct Vec4 { inline static constexpr std::string_view repr = "vec4"; }; + + using Token = std::variant; + } +} + +#endif \ No newline at end of file diff --git a/components/fx/parse_constants.hpp b/components/fx/parse_constants.hpp new file mode 100644 index 0000000000..18d32ee53a --- /dev/null +++ b/components/fx/parse_constants.hpp @@ -0,0 +1,133 @@ +#ifndef OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H +#define OPENMW_COMPONENTS_FX_PARSE_CONSTANTS_H + +#include +#include + +#include +#include +#include +#include + +#include + +#include "technique.hpp" + +namespace fx +{ + namespace constants + { + constexpr std::array, 6> TechniqueFlag = {{ + {"disable_interiors" , Technique::Flag_Disable_Interiors}, + {"disable_exteriors" , Technique::Flag_Disable_Exteriors}, + {"disable_underwater" , Technique::Flag_Disable_Underwater}, + {"disable_abovewater" , Technique::Flag_Disable_Abovewater}, + {"disable_sunglare" , Technique::Flag_Disable_SunGlare}, + {"hidden" , Technique::Flag_Hidden} + }}; + + constexpr std::array, 6> SourceFormat = {{ + {"red" , GL_RED}, + {"rg" , GL_RG}, + {"rgb" , GL_RGB}, + {"bgr" , GL_BGR}, + {"rgba", GL_RGBA}, + {"bgra", GL_BGRA}, + }}; + + constexpr std::array, 9> SourceType = {{ + {"byte" , GL_BYTE}, + {"unsigned_byte" , GL_UNSIGNED_BYTE}, + {"short" , GL_SHORT}, + {"unsigned_short" , GL_UNSIGNED_SHORT}, + {"int" , GL_INT}, + {"unsigned_int" , GL_UNSIGNED_INT}, + {"unsigned_int_24_8", GL_UNSIGNED_INT_24_8}, + {"float" , GL_FLOAT}, + {"double" , GL_DOUBLE}, + }}; + + constexpr std::array, 16> InternalFormat = {{ + {"red" , GL_RED}, + {"r16f" , GL_R16F}, + {"r32f" , GL_R32F}, + {"rg" , GL_RG}, + {"rg16f" , GL_RG16F}, + {"rg32f" , GL_RG32F}, + {"rgb" , GL_RGB}, + {"rgb16f" , GL_RGB16F}, + {"rgb32f" , GL_RGB32F}, + {"rgba" , GL_RGBA}, + {"rgba16f" , GL_RGBA16F}, + {"rgba32f" , GL_RGBA32F}, + {"depth_component16" , GL_DEPTH_COMPONENT16}, + {"depth_component24" , GL_DEPTH_COMPONENT24}, + {"depth_component32" , GL_DEPTH_COMPONENT32}, + {"depth_component32f", GL_DEPTH_COMPONENT32F} + }}; + + constexpr std::array, 13> Compression = {{ + {"auto" , osg::Texture::USE_USER_DEFINED_FORMAT}, + {"arb" , osg::Texture::USE_ARB_COMPRESSION}, + {"s3tc_dxt1" , osg::Texture::USE_S3TC_DXT1_COMPRESSION}, + {"s3tc_dxt3" , osg::Texture::USE_S3TC_DXT3_COMPRESSION}, + {"s3tc_dxt5" , osg::Texture::USE_S3TC_DXT5_COMPRESSION}, + {"pvrtc_2bpp" , osg::Texture::USE_PVRTC_2BPP_COMPRESSION}, + {"pvrtc_4bpp" , osg::Texture::USE_PVRTC_4BPP_COMPRESSION}, + {"etc" , osg::Texture::USE_ETC_COMPRESSION}, + {"etc2" , osg::Texture::USE_ETC2_COMPRESSION}, + {"rgtc1" , osg::Texture::USE_RGTC1_COMPRESSION}, + {"rgtc2" , osg::Texture::USE_RGTC2_COMPRESSION}, + {"s3tc_dxt1c" , osg::Texture::USE_S3TC_DXT1c_COMPRESSION}, + {"s3tc_dxt1a" , osg::Texture::USE_S3TC_DXT1a_COMPRESSION} + }}; + + constexpr std::array, 6> WrapMode = {{ + {"clamp" , osg::Texture::CLAMP}, + {"clamp_to_edge" , osg::Texture::CLAMP_TO_EDGE}, + {"clamp_to_border", osg::Texture::CLAMP_TO_BORDER}, + {"repeat" , osg::Texture::REPEAT}, + {"mirror" , osg::Texture::MIRROR} + }}; + + constexpr std::array, 6> FilterMode = {{ + {"linear" , osg::Texture::LINEAR}, + {"linear_mipmap_linear" , osg::Texture::LINEAR_MIPMAP_LINEAR}, + {"linear_mipmap_nearest" , osg::Texture::LINEAR_MIPMAP_NEAREST}, + {"nearest" , osg::Texture::NEAREST}, + {"nearest_mipmap_linear" , osg::Texture::NEAREST_MIPMAP_LINEAR}, + {"nearest_mipmap_nearest", osg::Texture::NEAREST_MIPMAP_NEAREST} + }}; + + constexpr std::array, 15> BlendFunc = {{ + {"dst_alpha" , osg::BlendFunc::DST_ALPHA}, + {"dst_color" , osg::BlendFunc::DST_COLOR}, + {"one" , osg::BlendFunc::ONE}, + {"one_minus_dst_alpha" , osg::BlendFunc::ONE_MINUS_DST_ALPHA}, + {"one_minus_dst_color" , osg::BlendFunc::ONE_MINUS_DST_COLOR}, + {"one_minus_src_alpha" , osg::BlendFunc::ONE_MINUS_SRC_ALPHA}, + {"one_minus_src_color" , osg::BlendFunc::ONE_MINUS_SRC_COLOR}, + {"src_alpha" , osg::BlendFunc::SRC_ALPHA}, + {"src_alpha_saturate" , osg::BlendFunc::SRC_ALPHA_SATURATE}, + {"src_color" , osg::BlendFunc::SRC_COLOR}, + {"constant_color" , osg::BlendFunc::CONSTANT_COLOR}, + {"one_minus_constant_color" , osg::BlendFunc::ONE_MINUS_CONSTANT_COLOR}, + {"constant_alpha" , osg::BlendFunc::CONSTANT_ALPHA}, + {"one_minus_constant_alpha" , osg::BlendFunc::ONE_MINUS_CONSTANT_ALPHA}, + {"zero" , osg::BlendFunc::ZERO} + }}; + + constexpr std::array, 8> BlendEquation = {{ + {"rgba_min" , osg::BlendEquation::RGBA_MIN}, + {"rgba_max" , osg::BlendEquation::RGBA_MAX}, + {"alpha_min" , osg::BlendEquation::ALPHA_MIN}, + {"alpha_max" , osg::BlendEquation::ALPHA_MAX}, + {"logic_op" , osg::BlendEquation::LOGIC_OP}, + {"add" , osg::BlendEquation::FUNC_ADD}, + {"subtract" , osg::BlendEquation::FUNC_SUBTRACT}, + {"reverse_subtract" , osg::BlendEquation::FUNC_REVERSE_SUBTRACT} + }}; + } +} + +#endif \ No newline at end of file diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp new file mode 100644 index 0000000000..0d86bdcf50 --- /dev/null +++ b/components/fx/pass.cpp @@ -0,0 +1,253 @@ +#include "pass.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "technique.hpp" +#include "stateupdater.hpp" + +namespace +{ + constexpr char s_DefaultVertex[] = R"GLSL( +#if OMW_USE_BINDINGS + omw_In vec2 omw_Vertex; +#endif +omw_Out vec2 omw_TexCoord; + +void main() +{ + omw_Position = vec4(omw_Vertex.xy, 0.0, 1.0); + omw_TexCoord = omw_Position.xy * 0.5 + 0.5; +})GLSL"; + +} + +namespace fx +{ + Pass::Pass(Pass::Type type, Pass::Order order, bool ubo) + : mCompiled(false) + , mType(type) + , mOrder(order) + , mLegacyGLSL(true) + , mUBO(ubo) + { + } + + std::string Pass::getPassHeader(Technique& technique, std::string_view preamble, bool fragOut) + { + std::string header = R"GLSL( +#version @version @profile +@extensions + +@uboStruct + +#define OMW_REVERSE_Z @reverseZ +#define OMW_RADIAL_FOG @radialFog +#define OMW_HDR @hdr +#define OMW_NORMALS @normals +#define OMW_USE_BINDINGS @useBindings +#define OMW_MULTIVIEW @multiview +#define omw_In @in +#define omw_Out @out +#define omw_Position @position +#define omw_Texture1D @texture1D +#define omw_Texture2D @texture2D +#define omw_Texture3D @texture3D +#define omw_Vertex @vertex +#define omw_FragColor @fragColor + +@fragBinding + +uniform @builtinSampler omw_SamplerLastShader; +uniform @builtinSampler omw_SamplerLastPass; +uniform @builtinSampler omw_SamplerDepth; +uniform @builtinSampler omw_SamplerNormals; + +#if @ubo + layout(std140) uniform _data { _omw_data omw; }; +#else + uniform _omw_data omw; +#endif + + float omw_GetDepth(vec2 uv) + { +#if OMW_MULTIVIEW + float depth = omw_Texture2D(omw_SamplerDepth, vec3(uv, gl_ViewID_OVR)).r; +#else + float depth = omw_Texture2D(omw_SamplerDepth, uv).r; +#endif +#if OMW_REVERSE_Z + return 1.0 - depth; +#else + return depth; +#endif + } + + vec4 omw_GetLastShader(vec2 uv) + { +#if OMW_MULTIVIEW + return omw_Texture2D(omw_SamplerLastShader, vec3(uv, gl_ViewID_OVR)); +#else + return omw_Texture2D(omw_SamplerLastShader, uv); +#endif + } + + vec4 omw_GetLastPass(vec2 uv) + { +#if OMW_MULTIVIEW + return omw_Texture2D(omw_SamplerLastPass, vec3(uv, gl_ViewID_OVR)); +#else + return omw_Texture2D(omw_SamplerLastPass, uv); +#endif + } + + vec3 omw_GetNormals(vec2 uv) + { +#if OMW_MULTIVIEW + return omw_Texture2D(omw_SamplerNormals, vec3(uv, gl_ViewID_OVR)).rgb * 2.0 - 1.0; +#else + return omw_Texture2D(omw_SamplerNormals, uv).rgb * 2.0 - 1.0; +#endif + } + +#if OMW_HDR + uniform sampler2D omw_EyeAdaptation; +#endif + + float omw_GetEyeAdaptation() + { +#if OMW_HDR + return omw_Texture2D(omw_EyeAdaptation, vec2(0.5, 0.5)).r; +#else + return 1.0; +#endif + } +)GLSL"; + + std::stringstream extBlock; + for (const auto& extension : technique.getGLSLExtensions()) + extBlock << "#ifdef " << extension << '\n' << "\t#extension " << extension << ": enable" << '\n' << "#endif" << '\n'; + + const std::vector> defines = { + {"@version", std::to_string(technique.getGLSLVersion())}, + {"@multiview", Stereo::getMultiview() ? "1" : "0"}, + {"@builtinSampler", Stereo::getMultiview() ? "sampler2DArray" : "sampler2D"}, + {"@profile", technique.getGLSLProfile()}, + {"@extensions", extBlock.str()}, + {"@uboStruct", StateUpdater::getStructDefinition()}, + {"@ubo", mUBO ? "1" : "0"}, + {"@normals", technique.getNormals() ? "1" : "0"}, + {"@reverseZ", SceneUtil::AutoDepth::isReversed() ? "1" : "0"}, + {"@radialFog", Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"}, + {"@hdr", technique.getHDR() ? "1" : "0"}, + {"@in", mLegacyGLSL ? "varying" : "in"}, + {"@out", mLegacyGLSL ? "varying" : "out"}, + {"@position", "gl_Position"}, + {"@texture1D", mLegacyGLSL ? "texture1D" : "texture"}, + {"@texture2D", mLegacyGLSL ? "texture2D" : "texture"}, + {"@texture3D", mLegacyGLSL ? "texture3D" : "texture"}, + {"@vertex", mLegacyGLSL ? "gl_Vertex" : "_omw_Vertex"}, + {"@fragColor", mLegacyGLSL ? "gl_FragColor" : "_omw_FragColor"}, + {"@useBindings", mLegacyGLSL ? "0" : "1"}, + {"@fragBinding", mLegacyGLSL ? "" : "out vec4 omw_FragColor;"} + }; + + for (const auto& [define, value]: defines) + for (size_t pos = header.find(define); pos != std::string::npos; pos = header.find(define)) + header.replace(pos, define.size(), value); + + for (auto& uniform : technique.getUniformMap()) + if (auto glsl = uniform->getGLSL()) + header.append(glsl.value()); + + header.append(preamble); + + return header; + } + + void Pass::prepareStateSet(osg::StateSet* stateSet, const std::string& name) const + { + osg::ref_ptr program = new osg::Program; + if (mType == Type::Pixel) + { + program->addShader(new osg::Shader(*mVertex)); + program->addShader(new osg::Shader(*mFragment)); + } + else if (mType == Type::Compute) + { + program->addShader(new osg::Shader(*mCompute)); + } + + if (mUBO) + program->addBindUniformBlock("_data", static_cast(Resource::SceneManager::UBOBinding::PostProcessor)); + + program->setName(name); + + if (!mLegacyGLSL) + { + program->addBindFragDataLocation("_omw_FragColor", 0); + program->addBindAttribLocation("_omw_Vertex", 0); + } + + stateSet->setAttribute(program); + + if (mBlendSource && mBlendDest) + stateSet->setAttribute(new osg::BlendFunc(mBlendSource.value(), mBlendDest.value())); + + if (mBlendEq) + stateSet->setAttribute(new osg::BlendEquation(mBlendEq.value())); + + if (mClearColor) + stateSet->setAttribute(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT)); + } + + void Pass::dirty() + { + mVertex = nullptr; + mFragment = nullptr; + mCompute = nullptr; + mCompiled = false; + } + + void Pass::compile(Technique& technique, std::string_view preamble) + { + if (mCompiled) + return; + + mLegacyGLSL = technique.getGLSLVersion() != 330; + + if (mType == Type::Pixel) + { + if (!mVertex) + mVertex = new osg::Shader(osg::Shader::VERTEX, s_DefaultVertex); + + mVertex->setShaderSource(getPassHeader(technique, preamble).append(mVertex->getShaderSource())); + mFragment->setShaderSource(getPassHeader(technique, preamble, true).append(mFragment->getShaderSource())); + + mVertex->setName(mName); + mFragment->setName(mName); + } + else if (mType == Type::Compute) + { + mCompute->setShaderSource(getPassHeader(technique, preamble).append(mCompute->getShaderSource())); + mCompute->setName(mName); + } + + mCompiled = true; + } + +} diff --git a/components/fx/pass.hpp b/components/fx/pass.hpp new file mode 100644 index 0000000000..bd13c3f99b --- /dev/null +++ b/components/fx/pass.hpp @@ -0,0 +1,79 @@ +#ifndef OPENMW_COMPONENTS_FX_PASS_H +#define OPENMW_COMPONENTS_FX_PASS_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace fx +{ + class Technique; + + class Pass + { + public: + + enum class Order + { + Forward, + Post + }; + + enum class Type + { + None, + Pixel, + Compute + }; + + friend class Technique; + + Pass(Type type=Type::Pixel, Order order=Order::Post, bool ubo = false); + + void compile(Technique& technique, std::string_view preamble); + + std::string_view getTarget() const { return mTarget; } + + void prepareStateSet(osg::StateSet* stateSet, const std::string& name) const; + + std::string getName() const { return mName; } + + void dirty(); + + private: + std::string getPassHeader(Technique& technique, std::string_view preamble, bool fragOut = false); + + bool mCompiled; + + osg::ref_ptr mVertex; + osg::ref_ptr mFragment; + osg::ref_ptr mCompute; + + Type mType; + Order mOrder; + std::string mName; + bool mLegacyGLSL; + bool mUBO; + bool mSupportsNormals; + + std::string_view mTarget; + std::optional mClearColor; + + std::optional mBlendSource; + std::optional mBlendDest; + std::optional mBlendEq; + }; +} + +#endif diff --git a/components/fx/stateupdater.cpp b/components/fx/stateupdater.cpp new file mode 100644 index 0000000000..c362c16f54 --- /dev/null +++ b/components/fx/stateupdater.cpp @@ -0,0 +1,60 @@ +#include "stateupdater.hpp" + +#include +#include + +#include +#include + +namespace fx +{ + StateUpdater::StateUpdater(bool useUBO) : mUseUBO(useUBO) {} + + void StateUpdater::setDefaults(osg::StateSet* stateset) + { + if (mUseUBO) + { + osg::ref_ptr ubo = new osg::UniformBufferObject; + + osg::ref_ptr> data = new osg::BufferTemplate(); + data->setBufferObject(ubo); + + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::PostProcessor), data, 0, mData.getGPUSize()); + + stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); + } + else + { + const auto createUniform = [&] (const auto& v) { + using T = std::decay_t; + std::string name = "omw." + std::string(T::sName); + stateset->addUniform(new osg::Uniform(name.c_str(), mData.get())); + }; + + std::apply([&] (const auto& ... v) { (createUniform(v) , ...); }, mData.getData()); + } + } + + void StateUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) + { + if (mUseUBO) + { + osg::UniformBufferBinding* ubb = dynamic_cast(stateset->getAttribute(osg::StateAttribute::UNIFORMBUFFERBINDING, static_cast(Resource::SceneManager::UBOBinding::PostProcessor))); + + auto& dest = static_cast*>(ubb->getBufferData())->getData(); + mData.copyTo(dest); + + ubb->getBufferData()->dirty(); + } + else + { + const auto setUniform = [&] (const auto& v) { + using T = std::decay_t; + std::string name = "omw." + std::string(T::sName); + stateset->getUniform(name)->set(mData.get()); + }; + + std::apply([&] (const auto& ... v) { (setUniform(v) , ...); }, mData.getData()); + } + } +} \ No newline at end of file diff --git a/components/fx/stateupdater.hpp b/components/fx/stateupdater.hpp new file mode 100644 index 0000000000..25bb17fffa --- /dev/null +++ b/components/fx/stateupdater.hpp @@ -0,0 +1,192 @@ +#ifndef OPENMW_COMPONENTS_FX_STATEUPDATER_H +#define OPENMW_COMPONENTS_FX_STATEUPDATER_H + +#include + +#include +#include + +namespace fx +{ + class StateUpdater : public SceneUtil::StateSetUpdater + { + public: + StateUpdater(bool useUBO); + + void setProjectionMatrix(const osg::Matrixf& matrix) + { + mData.get() = matrix; + mData.get() = osg::Matrixf::inverse(matrix); + } + + void setViewMatrix(const osg::Matrixf& matrix) { mData.get() = matrix; } + + void setInvViewMatrix(const osg::Matrixf& matrix) { mData.get() = matrix; } + + void setPrevViewMatrix(const osg::Matrixf& matrix) { mData.get() = matrix;} + + void setEyePos(const osg::Vec3f& pos) { mData.get() = osg::Vec4f(pos, 0.f); } + + void setEyeVec(const osg::Vec3f& vec) { mData.get() = osg::Vec4f(vec, 0.f); } + + void setFogColor(const osg::Vec4f& color) { mData.get() = color; } + + void setSunColor(const osg::Vec4f& color) { mData.get() = color; } + + void setSunPos(const osg::Vec4f& pos, bool night) + { + mData.get() = pos; + + if (night) + mData.get().z() *= -1.f; + } + + void setResolution(const osg::Vec2f& size) + { + mData.get() = size; + mData.get() = {1.f / size.x(), 1.f / size.y()}; + } + + void setSunVis(float vis) + { + mData.get() = vis; + } + + void setFogRange(float near, float far) + { + mData.get() = near; + mData.get() = far; + } + + void setNearFar(float near, float far) + { + mData.get() = near; + mData.get() = far; + } + + void setIsUnderwater(bool underwater) { mData.get() = underwater; } + + void setIsInterior(bool interior) { mData.get() = interior; } + + void setFov(float fov) { mData.get() = fov; } + + void setGameHour(float hour) { mData.get() = hour; } + + void setWeatherId(int id) { mData.get() = id; } + + void setNextWeatherId(int id) { mData.get() = id; } + + void setWaterHeight(float height) { mData.get() = height; } + + void setSimulationTime(float time) { mData.get() = time; } + + void setDeltaSimulationTime(float time) { mData.get() = time; } + + void setWindSpeed(float speed) { mData.get() = speed; } + + void setWeatherTransition(float transition) { mData.get() = transition; } + + static std::string getStructDefinition() + { + static std::string definition = UniformData::getDefinition("_omw_data"); + return definition; + } + + private: + struct ProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "projectionMatrix"; }; + + struct InvProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "invProjectionMatrix"; }; + + struct ViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "viewMatrix"; }; + + struct PrevViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "prevViewMatrix"; }; + + struct InvViewMatrix : std140::Mat4 { static constexpr std::string_view sName = "invViewMatrix"; }; + + struct EyePos : std140::Vec4 { static constexpr std::string_view sName = "eyePos"; }; + + struct EyeVec : std140::Vec4 { static constexpr std::string_view sName = "eyeVec"; }; + + struct FogColor : std140::Vec4 { static constexpr std::string_view sName = "fogColor"; }; + + struct SunColor : std140::Vec4 { static constexpr std::string_view sName = "sunColor"; }; + + struct SunPos : std140::Vec4 { static constexpr std::string_view sName = "sunPos"; }; + + struct Resolution : std140::Vec2 { static constexpr std::string_view sName = "resolution"; }; + + struct RcpResolution : std140::Vec2 { static constexpr std::string_view sName = "rcpResolution"; }; + + struct FogNear : std140::Float { static constexpr std::string_view sName = "fogNear"; }; + + struct FogFar : std140::Float { static constexpr std::string_view sName = "fogFar"; }; + + struct Near : std140::Float { static constexpr std::string_view sName = "near"; }; + + struct Far : std140::Float { static constexpr std::string_view sName = "far"; }; + + struct Fov : std140::Float { static constexpr std::string_view sName = "fov"; }; + + struct GameHour : std140::Float { static constexpr std::string_view sName = "gameHour"; }; + + struct SunVis : std140::Float { static constexpr std::string_view sName = "sunVis"; }; + + struct WaterHeight : std140::Float { static constexpr std::string_view sName = "waterHeight"; }; + + struct SimulationTime : std140::Float { static constexpr std::string_view sName = "simulationTime"; }; + + struct DeltaSimulationTime : std140::Float { static constexpr std::string_view sName = "deltaSimulationTime"; }; + + struct WindSpeed : std140::Float { static constexpr std::string_view sName = "windSpeed"; }; + + struct WeatherTransition : std140::Float { static constexpr std::string_view sName = "weatherTransition"; }; + + struct WeatherID : std140::Int { static constexpr std::string_view sName = "weatherID"; }; + + struct NextWeatherID : std140::Int { static constexpr std::string_view sName = "nextWeatherID"; }; + + struct IsUnderwater : std140::Bool { static constexpr std::string_view sName = "isUnderwater"; }; + + struct IsInterior : std140::Bool { static constexpr std::string_view sName = "isInterior"; }; + + using UniformData = std140::UBO< + ProjectionMatrix, + InvProjectionMatrix, + ViewMatrix, + PrevViewMatrix, + InvViewMatrix, + EyePos, + EyeVec, + FogColor, + SunColor, + SunPos, + Resolution, + RcpResolution, + FogNear, + FogFar, + Near, + Far, + Fov, + GameHour, + SunVis, + WaterHeight, + SimulationTime, + DeltaSimulationTime, + WindSpeed, + WeatherTransition, + WeatherID, + NextWeatherID, + IsUnderwater, + IsInterior + >; + + private: + void setDefaults(osg::StateSet* stateset) override; + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; + + UniformData mData; + bool mUseUBO; + }; +} + +#endif \ No newline at end of file diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp new file mode 100644 index 0000000000..86a6189fd7 --- /dev/null +++ b/components/fx/technique.cpp @@ -0,0 +1,1049 @@ +#include "technique.hpp" + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "parse_constants.hpp" + +namespace +{ + struct ProxyTextureData + { + osg::Texture::WrapMode wrap_s = osg::Texture::CLAMP_TO_EDGE; + osg::Texture::WrapMode wrap_t = osg::Texture::CLAMP_TO_EDGE; + osg::Texture::WrapMode wrap_r = osg::Texture::CLAMP_TO_EDGE; + osg::Texture::FilterMode min_filter = osg::Texture::LINEAR_MIPMAP_LINEAR; + osg::Texture::FilterMode mag_filter =osg::Texture::LINEAR; + osg::Texture::InternalFormatMode compression = osg::Texture::USE_IMAGE_DATA_FORMAT; + std::optional source_format; + std::optional source_type; + std::optional internal_format; + }; +} + +namespace fx +{ + Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, const std::string& name, int width, int height, bool ubo, bool supportsNormals) + : mName(name) + , mFileName((std::filesystem::path(Technique::sSubdir) / (mName + Technique::sExt)).string()) + , mLastModificationTime(std::filesystem::file_time_type()) + , mWidth(width) + , mHeight(height) + , mVFS(vfs) + , mImageManager(imageManager) + , mUBO(ubo) + , mSupportsNormals(supportsNormals) + { + clear(); + } + + void Technique::clear() + { + mTextures.clear(); + mStatus = Status::Uncompiled; + mDirty = false; + mValid = false; + mHDR = false; + mNormals = false; + mEnabled = true; + mPassMap.clear(); + mPasses.clear(); + mPassKeys.clear(); + mDefinedUniforms.clear(); + mRenderTargets.clear(); + mMainTemplate = nullptr; + mLastAppliedType = Pass::Type::None; + mFlags = 0; + mShared.clear(); + mAuthor = {}; + mDescription = {}; + mVersion = {}; + mGLSLExtensions.clear(); + mGLSLVersion = mUBO ? 330 : 120; + mGLSLProfile.clear(); + } + + std::string Technique::getBlockWithLineDirective() + { + auto block = mLexer->getLastJumpBlock(); + std::string content = std::string(block.content); + + content = "\n#line " + std::to_string(block.line + 1) + "\n" + std::string(block.content) + "\n"; + return content; + } + + Technique::UniformMap::iterator Technique::findUniform(const std::string& name) + { + return std::find_if(mDefinedUniforms.begin(), mDefinedUniforms.end(), [&name](const auto& uniform) + { + return uniform->mName == name; + }); + } + + bool Technique::compile() + { + clear(); + + if (!mVFS.exists(mFileName)) + { + Log(Debug::Error) << "Could not load technique, file does not exist '" << mFileName << "'"; + + mStatus = Status::File_Not_exists; + return false; + } + + try + { + std::string source(std::istreambuf_iterator(*mVFS.get(getFileName())), {}); + + parse(std::move(source)); + + if (mPassKeys.empty()) + error("no pass list found, ensure you define one in a 'technique' block"); + + int swaps = 0; + + for (auto name : mPassKeys) + { + auto it = mPassMap.find(name); + + if (it == mPassMap.end()) + error(Misc::StringUtils::format("pass '%s' was found in the pass list, but there was no matching 'fragment', 'vertex' or 'compute' block", std::string(name))); + + if (mLastAppliedType != Pass::Type::None && mLastAppliedType != it->second->mType) + { + swaps++; + if (swaps == 2) + Log(Debug::Warning) << "compute and pixel shaders are being swapped multiple times in shader chain, this can lead to serious performance drain."; + } + else + mLastAppliedType = it->second->mType; + + if (Stereo::getMultiview()) + { + mGLSLExtensions.insert("GL_OVR_multiview"); + mGLSLExtensions.insert("GL_OVR_multiview2"); + mGLSLExtensions.insert("GL_EXT_texture_array"); + } + + it->second->compile(*this, mShared); + + if (!it->second->mTarget.empty()) + { + auto rtIt = mRenderTargets.find(it->second->mTarget); + if (rtIt == mRenderTargets.end()) + error(Misc::StringUtils::format("target '%s' not defined", std::string(it->second->mTarget))); + } + + mPasses.emplace_back(it->second); + } + + if (mPasses.empty()) + error("invalid pass list, no passes defined for technique"); + + mValid = true; + } + catch(const std::runtime_error& e) + { + clear(); + mStatus = Status::Parse_Error; + + mLastError = "Failed parsing technique '" + getName() + "' " + e.what();; + Log(Debug::Error) << mLastError; + } + + return mValid; + } + + std::string Technique::getName() const + { + return mName; + } + + std::string Technique::getFileName() const + { + return mFileName; + } + + void Technique::setLastModificationTime(std::filesystem::file_time_type timeStamp, bool dirty) + { + if (dirty && mLastModificationTime != timeStamp) + mDirty = true; + + mLastModificationTime = timeStamp; + } + + [[noreturn]] void Technique::error(const std::string& msg) + { + mLexer->error(msg); + } + + template<> + void Technique::parseBlockImp() + { + if (!mLexer->jump()) + error(Misc::StringUtils::format("unterminated 'shared' block, expected closing brackets")); + + if (!mShared.empty()) + error("repeated 'shared' block, only one allowed per technique file"); + + mShared = getBlockWithLineDirective(); + } + + template<> + void Technique::parseBlockImp() + { + if (!mPassKeys.empty()) + error("exactly one 'technique' block can appear per file"); + + while (!isNext() && !isNext()) + { + expect(); + + auto key = std::get(mToken).value; + + expect(); + + if (key == "passes") + mPassKeys = parseLiteralList(); + else if (key == "version") + mVersion = parseString(); + else if (key == "description") + mDescription = parseString(); + else if (key == "author") + mAuthor = parseString(); + else if (key == "glsl_version") + { + int version = parseInteger(); + if (mUBO && version > 330) + mGLSLVersion = version; + } + else if (key == "flags") + mFlags = parseFlags(); + else if (key == "hdr") + mHDR = parseBool(); + else if (key == "pass_normals") + mNormals = parseBool() && mSupportsNormals; + else if (key == "glsl_profile") + { + expect(); + mGLSLProfile = std::string(std::get(mToken).value); + } + else if (key == "glsl_extensions") + { + for (const auto& ext : parseLiteralList()) + mGLSLExtensions.emplace(ext); + } + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); + + expect(); + } + + if (mPassKeys.empty()) + error("pass list in 'technique' block cannot be empty."); + } + + template<> + void Technique::parseBlockImp() + { + if (mMainTemplate) + error("duplicate 'main_pass' block"); + + if (mName != "main") + error("'main_pass' block can only be defined in the 'main.omwfx' technique file"); + + mMainTemplate = new osg::Texture2D; + + mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + while (!isNext() && !isNext()) + { + expect(); + + auto key = std::get(mToken).value; + + expect(); + + if (key == "wrap_s") + mMainTemplate->setWrap(osg::Texture::WRAP_S, parseWrapMode()); + else if (key == "wrap_t") + mMainTemplate->setWrap(osg::Texture::WRAP_T, parseWrapMode()); + // Skip depth attachments for main scene, as some engine settings rely on specific depth formats. + // Allowing this to be overriden will cause confusion. + else if (key == "internal_format") + mMainTemplate->setInternalFormat(parseInternalFormat()); + else if (key == "source_type") + mMainTemplate->setSourceType(parseSourceType()); + else if (key == "source_format") + mMainTemplate->setSourceFormat(parseSourceFormat()); + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); + + expect(); + } + } + + template<> + void Technique::parseBlockImp() + { + if (mRenderTargets.count(mBlockName)) + error(Misc::StringUtils::format("redeclaration of render target '%s'", std::string(mBlockName))); + + fx::Types::RenderTarget rt; + rt.mTarget->setTextureSize(mWidth, mHeight); + rt.mTarget->setSourceFormat(GL_RGB); + rt.mTarget->setInternalFormat(GL_RGB); + rt.mTarget->setSourceType(GL_UNSIGNED_BYTE); + + while (!isNext() && !isNext()) + { + expect(); + + auto key = std::get(mToken).value; + + expect(); + + if (key == "min_filter") + rt.mTarget->setFilter(osg::Texture2D::MIN_FILTER, parseFilterMode()); + else if (key == "mag_filter") + rt.mTarget->setFilter(osg::Texture2D::MAG_FILTER, parseFilterMode()); + else if (key == "wrap_s") + rt.mTarget->setWrap(osg::Texture2D::WRAP_S, parseWrapMode()); + else if (key == "wrap_t") + rt.mTarget->setWrap(osg::Texture2D::WRAP_T, parseWrapMode()); + else if (key == "width_ratio") + rt.mSize.mWidthRatio = parseFloat(); + else if (key == "height_ratio") + rt.mSize.mHeightRatio = parseFloat(); + else if (key == "width") + rt.mSize.mWidth = parseInteger(); + else if (key == "height") + rt.mSize.mHeight = parseInteger(); + else if (key == "internal_format") + rt.mTarget->setInternalFormat(parseInternalFormat()); + else if (key == "source_type") + rt.mTarget->setSourceType(parseSourceType()); + else if (key == "source_format") + rt.mTarget->setSourceFormat(parseSourceFormat()); + else if (key == "mipmaps") + rt.mMipMap = parseBool(); + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); + + expect(); + } + + mRenderTargets.emplace(mBlockName, std::move(rt)); + } + + template<> + void Technique::parseBlockImp() + { + if (!mLexer->jump()) + error(Misc::StringUtils::format("unterminated 'vertex' block, expected closing brackets")); + + auto& pass = mPassMap[mBlockName]; + + if (!pass) + pass = std::make_shared(); + + pass->mName = mBlockName; + + if (pass->mCompute) + error(Misc::StringUtils::format("'compute' block already defined. Usage is ambiguous.")); + else if (!pass->mVertex) + pass->mVertex = new osg::Shader(osg::Shader::VERTEX, getBlockWithLineDirective()); + else + error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); + + pass->mType = Pass::Type::Pixel; + } + + template<> + void Technique::parseBlockImp() + { + if (!mLexer->jump()) + error(Misc::StringUtils::format("unterminated 'fragment' block, expected closing brackets")); + + auto& pass = mPassMap[mBlockName]; + + if (!pass) + pass = std::make_shared(); + + pass->mUBO = mUBO; + pass->mName = mBlockName; + + if (pass->mCompute) + error(Misc::StringUtils::format("'compute' block already defined. Usage is ambiguous.")); + else if (!pass->mFragment) + pass->mFragment = new osg::Shader(osg::Shader::FRAGMENT, getBlockWithLineDirective()); + else + error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); + + pass->mType = Pass::Type::Pixel; + } + + template<> + void Technique::parseBlockImp() + { + if (!mLexer->jump()) + error(Misc::StringUtils::format("unterminated 'compute' block, expected closing brackets")); + + auto& pass = mPassMap[mBlockName]; + + if (!pass) + pass = std::make_shared(); + + pass->mName = mBlockName; + + if (pass->mFragment) + error(Misc::StringUtils::format("'fragment' block already defined. Usage is ambiguous.")); + else if (pass->mVertex) + error(Misc::StringUtils::format("'vertex' block already defined. Usage is ambiguous.")); + else if (!pass->mFragment) + pass->mCompute = new osg::Shader(osg::Shader::COMPUTE, getBlockWithLineDirective()); + else + error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); + + pass->mType = Pass::Type::Compute; + } + + template + void Technique::parseSampler() + { + if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end()) + error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName))); + + ProxyTextureData proxy; + osg::ref_ptr sampler; + + constexpr bool is1D = std::is_same_v; + constexpr bool is3D = std::is_same_v; + + Types::SamplerType type; + + while (!isNext() && !isNext()) + { + expect(); + + auto key = asLiteral(); + + expect(); + + if (!is1D && key == "min_filter") + proxy.min_filter = parseFilterMode(); + else if (!is1D && key == "mag_filter") + proxy.mag_filter = parseFilterMode(); + else if (key == "wrap_s") + proxy.wrap_s = parseWrapMode(); + else if (key == "wrap_t") + proxy.wrap_t = parseWrapMode(); + else if (is3D && key == "wrap_r") + proxy.wrap_r = parseWrapMode(); + else if (key == "compression") + proxy.compression = parseCompression(); + else if (key == "source_type") + proxy.source_type = parseSourceType(); + else if (key == "source_format") + proxy.source_format = parseSourceFormat(); + else if (key == "internal_format") + proxy.internal_format = parseInternalFormat(); + else if (key == "source") + { + expect(); + auto image = mImageManager.getImage(std::string{std::get(mToken).value}); + if constexpr (is1D) + { + type = Types::SamplerType::Texture_1D; + sampler = new osg::Texture1D(image); + } + else if constexpr (is3D) + { + type = Types::SamplerType::Texture_3D; + sampler = new osg::Texture3D(image); + } + else + { + type = Types::SamplerType::Texture_2D; + sampler = new osg::Texture2D(image); + } + } + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); + + expect(); + } + if (!sampler) + error(Misc::StringUtils::format("%s '%s' requires a filename", std::string(T::repr), std::string{mBlockName})); + + if (!is1D) + { + sampler->setFilter(osg::Texture::MIN_FILTER, proxy.min_filter); + sampler->setFilter(osg::Texture::MAG_FILTER, proxy.mag_filter); + } + if (is3D) + sampler->setWrap(osg::Texture::WRAP_R, proxy.wrap_r); + sampler->setWrap(osg::Texture::WRAP_S, proxy.wrap_s); + sampler->setWrap(osg::Texture::WRAP_T, proxy.wrap_t); + sampler->setInternalFormatMode(proxy.compression); + if (proxy.internal_format.has_value()) + sampler->setInternalFormat(proxy.internal_format.value()); + if (proxy.source_type.has_value()) + sampler->setSourceType(proxy.source_type.value()); + if (proxy.internal_format.has_value()) + sampler->setSourceFormat(proxy.internal_format.value()); + sampler->setName(std::string{mBlockName}); + + mTextures.emplace_back(sampler); + + std::shared_ptr uniform = std::make_shared(); + uniform->mSamplerType = type; + uniform->mName = std::string(mBlockName); + mDefinedUniforms.emplace_back(std::move(uniform)); + } + + template + void Technique::parseUniform() + { + if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end()) + error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName))); + + std::shared_ptr uniform = std::make_shared(); + Types::Uniform data; + + while (!isNext() && !isNext()) + { + expect(); + + auto key = asLiteral(); + + expect("error parsing config for uniform block"); + + constexpr bool isVec = std::is_same_v || std::is_same_v || std::is_same_v; + constexpr bool isFloat = std::is_same_v; + constexpr bool isInt = std::is_same_v; + + std::optional step; + + if constexpr (isInt) + step = 1.0; + + if (key == "default") + { + if constexpr (isVec) + data.mDefault = parseVec(); + else if constexpr (isFloat) + data.mDefault = parseFloat(); + else if constexpr (isInt) + data.mDefault = parseInteger(); + else + data.mDefault = parseBool(); + } + else if (key == "min") + { + if constexpr (isVec) + data.mMin = parseVec(); + else if constexpr (isFloat) + data.mMin = parseFloat(); + else if constexpr (isInt) + data.mMin = parseInteger(); + else + data.mMin = parseBool(); + } + else if (key == "max") + { + if constexpr (isVec) + data.mMax = parseVec(); + else if constexpr (isFloat) + data.mMax = parseFloat(); + else if constexpr (isInt) + data.mMax = parseInteger(); + else + data.mMax = parseBool(); + } + else if (key == "step") + step = parseFloat(); + else if (key == "static") + uniform->mStatic = parseBool(); + else if (key == "description") + { + expect(); + uniform->mDescription = std::get(mToken).value; + } + else if (key == "header") + { + expect(); + uniform->mHeader = std::get(mToken).value; + } + else + error(Misc::StringUtils::format("unexpected key '%s'", std::string{key})); + + if (step) + uniform->mStep = step.value(); + + expect(); + } + + uniform->mName = std::string(mBlockName); + uniform->mData = data; + uniform->mTechniqueName = mName; + + if (auto cached = Settings::ShaderManager::get().getValue(mName, uniform->mName)) + uniform->setValue(cached.value()); + + mDefinedUniforms.emplace_back(std::move(uniform)); + } + + template<> + void Technique::parseBlockImp() + { + parseSampler(); + } + + template<> + void Technique::parseBlockImp() + { + parseSampler(); + } + + template<> + void Technique::parseBlockImp() + { + parseSampler(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template<> + void Technique::parseBlockImp() + { + parseUniform(); + } + + template + void Technique::expect(const std::string& err) + { + mToken = mLexer->next(); + if (!std::holds_alternative(mToken)) + { + if (err.empty()) + error(Misc::StringUtils::format("Expected %s", std::string(T::repr))); + else + error(Misc::StringUtils::format("%s. Expected %s", err, std::string(T::repr))); + } + } + + template + void Technique::expect(const std::string& err) + { + mToken = mLexer->next(); + if (!std::holds_alternative(mToken) && !std::holds_alternative(mToken)) + { + if (err.empty()) + error(Misc::StringUtils::format("%s. Expected %s or %s", err, std::string(T::repr), std::string(T2::repr))); + else + error(Misc::StringUtils::format("Expected %s or %s", std::string(T::repr), std::string(T2::repr))); + } + } + + template + bool Technique::isNext() + { + return std::holds_alternative(mLexer->peek()); + } + + void Technique::parse(std::string&& buffer) + { + mBuffer = std::move(buffer); + Misc::StringUtils::replaceAll(mBuffer, "\r\n", "\n"); + mLexer = std::make_unique(mBuffer); + + for (auto t = mLexer->next(); !std::holds_alternative(t); t = mLexer->next()) + { + std::visit([=](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) + parseBlock(false); + else if constexpr (std::is_same_v) + parseBlock(false); + else if constexpr (std::is_same_v) + parseBlock(false); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else if constexpr (std::is_same_v) + parseBlock(); + else + error("invalid top level block"); + } + , t); + } + } + + template + void Technique::parseBlock(bool named) + { + mBlockName = T::repr; + + if (named) + { + expect("name is required for preceeding block decleration"); + + mBlockName = std::get(mToken).value; + + if (isNext()) + parseBlockHeader(); + } + + expect(); + + parseBlockImp(); + + expect(); + } + + template + std::vector Technique::parseLiteralList() + { + std::vector data; + + while (!isNext()) + { + expect(); + + data.emplace_back(std::get(mToken).value); + + if (!isNext()) + break; + + mLexer->next(); + } + + return data; + } + + void Technique::parseBlockHeader() + { + expect(); + + if (isNext()) + { + mLexer->next(); + return; + } + + auto& pass = mPassMap[mBlockName]; + + if (!pass) + pass = std::make_shared(); + + bool clear = true; + osg::Vec4f clearColor = {1,1,1,1}; + + while (!isNext()) + { + expect("invalid key in block header"); + + std::string_view key = std::get(mToken).value; + + expect(); + + if (key == "target") + { + expect(); + pass->mTarget = std::get(mToken).value; + } + else if (key == "blend") + { + expect(); + osg::BlendEquation::Equation blendEq = parseBlendEquation(); + expect(); + osg::BlendFunc::BlendFuncMode blendSrc = parseBlendFuncMode(); + expect(); + osg::BlendFunc::BlendFuncMode blendDest = parseBlendFuncMode(); + expect(); + + pass->mBlendSource = blendSrc; + pass->mBlendDest = blendDest; + if (blendEq != osg::BlendEquation::FUNC_ADD) + pass->mBlendEq = blendEq; + } + else if (key == "clear") + clear = parseBool(); + else if (key == "clear_color") + clearColor = parseVec(); + else + error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key))); + + mToken = mLexer->next(); + + if (std::holds_alternative(mToken)) + { + if (std::holds_alternative(mLexer->peek())) + error(Misc::StringUtils::format("leading comma in '%s' is not allowed", std::string(mBlockName))); + else + continue; + } + + if (std::holds_alternative(mToken)) + return; + } + + if (clear) + pass->mClearColor = clearColor; + + error("malformed block header"); + } + + std::string_view Technique::asLiteral() const + { + return std::get(mToken).value; + } + + FlagsType Technique::parseFlags() + { + auto parseBit = [this] (std::string_view term) { + for (const auto& [identifer, bit]: constants::TechniqueFlag) + { + if (Misc::StringUtils::ciEqual(term, identifer)) + return bit; + } + error(Misc::StringUtils::format("unrecognized flag '%s'", std::string(term))); + }; + + FlagsType flag = 0; + for (const auto& bit : parseLiteralList()) + flag |= parseBit(bit); + + return flag; + } + + osg::Texture::FilterMode Technique::parseFilterMode() + { + expect(); + + for (const auto& [identifer, mode]: constants::FilterMode) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized filter mode '%s'", std::string{asLiteral()})); + } + + osg::Texture::WrapMode Technique::parseWrapMode() + { + expect(); + + for (const auto& [identifer, mode]: constants::WrapMode) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized wrap mode '%s'", std::string{asLiteral()})); + } + + osg::Texture::InternalFormatMode Technique::parseCompression() + { + expect(); + + for (const auto& [identifer, mode]: constants::Compression) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized compression '%s'", std::string{asLiteral()})); + } + + int Technique::parseInternalFormat() + { + expect(); + + for (const auto& [identifer, mode]: constants::InternalFormat) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized internal format '%s'", std::string{asLiteral()})); + } + + int Technique::parseSourceType() + { + expect(); + + for (const auto& [identifer, mode]: constants::SourceType) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized source type '%s'", std::string{asLiteral()})); + } + + int Technique::parseSourceFormat() + { + expect(); + + for (const auto& [identifer, mode]: constants::SourceFormat) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized source format '%s'", std::string{asLiteral()})); + } + + osg::BlendEquation::Equation Technique::parseBlendEquation() + { + expect(); + + for (const auto& [identifer, mode]: constants::BlendEquation) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized blend equation '%s'", std::string{asLiteral()})); + } + + osg::BlendFunc::BlendFuncMode Technique::parseBlendFuncMode() + { + expect(); + + for (const auto& [identifer, mode]: constants::BlendFunc) + { + if (asLiteral() == identifer) + return mode; + } + + error(Misc::StringUtils::format("unrecognized blend function '%s'", std::string{asLiteral()})); + } + + bool Technique::parseBool() + { + mToken = mLexer->next(); + + if (std::holds_alternative(mToken)) + return true; + if (std::holds_alternative(mToken)) + return false; + + error("expected 'true' or 'false' as boolean value"); + } + + std::string_view Technique::parseString() + { + expect(); + + return std::get(mToken).value; + } + + float Technique::parseFloat() + { + mToken = mLexer->next(); + + if (std::holds_alternative(mToken)) + return std::get(mToken).value; + if (std::holds_alternative(mToken)) + return static_cast(std::get(mToken).value); + + error("expected float value"); + } + + int Technique::parseInteger() + { + expect(); + + return std::get(mToken).value; + } + + template + OSGVec Technique::parseVec() + { + expect(); + expect(); + + OSGVec value; + + for (int i = 0; i < OSGVec::num_components; ++i) + { + value[i] = parseFloat(); + + if (i < OSGVec::num_components - 1) + expect(); + } + + expect("check definition of the vector"); + + return value; + } +} diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp new file mode 100644 index 0000000000..ca8ef96a52 --- /dev/null +++ b/components/fx/technique.hpp @@ -0,0 +1,300 @@ +#ifndef OPENMW_COMPONENTS_FX_TECHNIQUE_H +#define OPENMW_COMPONENTS_FX_TECHNIQUE_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pass.hpp" +#include "lexer.hpp" +#include "types.hpp" + +namespace Resource +{ + class ImageManager; +} + +namespace VFS +{ + class Manager; +} + +namespace fx +{ + using FlagsType = size_t; + + struct DispatchNode + { + DispatchNode() = default; + + DispatchNode(const DispatchNode& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) + : mHandle(other.mHandle) + , mFlags(other.mFlags) + , mRootStateSet(other.mRootStateSet) + { + mPasses.reserve(other.mPasses.size()); + + for (const auto& subpass : other.mPasses) + mPasses.emplace_back(subpass, copyOp); + } + + struct SubPass { + SubPass() = default; + + osg::ref_ptr mStateSet = new osg::StateSet; + osg::ref_ptr mRenderTarget; + osg::ref_ptr mRenderTexture; + + SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) + : mStateSet(new osg::StateSet(*other.mStateSet, copyOp)) + { + if (other.mRenderTarget) + mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp); + if (other.mRenderTexture) + mRenderTexture = new osg::Texture2D(*other.mRenderTexture, copyOp); + } + }; + + // not safe to read/write in draw thread + std::shared_ptr mHandle = nullptr; + + FlagsType mFlags = 0; + + std::vector mPasses; + + osg::ref_ptr mRootStateSet = new osg::StateSet; + }; + + using DispatchArray = std::vector; + + class Technique + { + public: + using PassList = std::vector>; + using TexList = std::vector>; + + using UniformMap = std::vector>; + using RenderTargetMap = std::unordered_map; + + inline static std::string sExt = ".omwfx"; + inline static std::string sSubdir = "shaders"; + + enum class Status + { + Success, + Uncompiled, + File_Not_exists, + Parse_Error + }; + + static constexpr FlagsType Flag_Disable_Interiors = (1 << 0); + static constexpr FlagsType Flag_Disable_Exteriors = (1 << 1); + static constexpr FlagsType Flag_Disable_Underwater = (1 << 2); + static constexpr FlagsType Flag_Disable_Abovewater = (1 << 3); + static constexpr FlagsType Flag_Disable_SunGlare = (1 << 4); + static constexpr FlagsType Flag_Hidden = (1 << 5); + + Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, const std::string& name, int width, int height, bool ubo, bool supportsNormals); + + bool compile(); + + std::string getName() const; + + std::string getFileName() const; + + void setLastModificationTime(std::filesystem::file_time_type timeStamp, bool dirty = true); + + bool isDirty() const { return mDirty; } + + void setDirty(bool dirty) { mDirty = dirty; } + + bool isValid() const { return mValid; } + + bool getHDR() const { return mHDR; } + + bool getNormals() const { return mNormals && mSupportsNormals; } + + const PassList& getPasses() { return mPasses; } + + const TexList& getTextures() const { return mTextures; } + + Status getStatus() const { return mStatus; } + + std::string_view getAuthor() const { return mAuthor; } + + std::string_view getDescription() const { return mDescription; } + + std::string_view getVersion() const { return mVersion; } + + int getGLSLVersion() const { return mGLSLVersion; } + + std::string getGLSLProfile() const { return mGLSLProfile; } + + const std::unordered_set& getGLSLExtensions() const { return mGLSLExtensions; } + + osg::ref_ptr getMainTemplate() const { return mMainTemplate; } + + FlagsType getFlags() const { return mFlags; } + + bool getHidden() const { return mFlags & Flag_Hidden; } + + UniformMap& getUniformMap() { return mDefinedUniforms; } + + RenderTargetMap& getRenderTargetsMap() { return mRenderTargets; } + + std::string getLastError() const { return mLastError; } + + UniformMap::iterator findUniform(const std::string& name); + + private: + [[noreturn]] void error(const std::string& msg); + + void clear(); + + std::string_view asLiteral() const; + + template + void expect(const std::string& err=""); + + template + void expect(const std::string& err=""); + + template + bool isNext(); + + void parse(std::string&& buffer); + + template + void parseUniform(); + + template + void parseSampler(); + + template + void parseBlock(bool named=true); + + template + void parseBlockImp() {} + + void parseBlockHeader(); + + bool parseBool(); + + std::string_view parseString(); + + float parseFloat(); + + int parseInteger(); + + int parseInternalFormat(); + + int parseSourceType(); + + int parseSourceFormat(); + + osg::BlendEquation::Equation parseBlendEquation(); + + osg::BlendFunc::BlendFuncMode parseBlendFuncMode(); + + osg::Texture::WrapMode parseWrapMode(); + + osg::Texture::InternalFormatMode parseCompression(); + + FlagsType parseFlags(); + + osg::Texture::FilterMode parseFilterMode(); + + template + std::vector parseLiteralList(); + + template + OSGVec parseVec(); + + std::string getBlockWithLineDirective(); + + std::unique_ptr mLexer; + Lexer::Token mToken; + + std::string mShared; + std::string mName; + std::string mFileName; + std::string_view mBlockName; + std::string_view mAuthor; + std::string_view mDescription; + std::string_view mVersion; + + std::unordered_set mGLSLExtensions; + int mGLSLVersion; + std::string mGLSLProfile; + + FlagsType mFlags; + + Status mStatus; + + bool mEnabled; + + std::filesystem::file_time_type mLastModificationTime; + bool mDirty; + bool mValid; + bool mHDR; + bool mNormals; + int mWidth; + int mHeight; + + osg::ref_ptr mMainTemplate; + RenderTargetMap mRenderTargets; + + TexList mTextures; + PassList mPasses; + + std::unordered_map> mPassMap; + std::vector mPassKeys; + + Pass::Type mLastAppliedType; + + UniformMap mDefinedUniforms; + + const VFS::Manager& mVFS; + Resource::ImageManager& mImageManager; + bool mUBO; + bool mSupportsNormals; + + std::string mBuffer; + + std::string mLastError; + }; + + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); + template<> void Technique::parseBlockImp(); +} + +#endif diff --git a/components/fx/types.hpp b/components/fx/types.hpp new file mode 100644 index 0000000000..f12810197f --- /dev/null +++ b/components/fx/types.hpp @@ -0,0 +1,259 @@ +#ifndef OPENMW_COMPONENTS_FX_TYPES_H +#define OPENMW_COMPONENTS_FX_TYPES_H + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "pass.hpp" + +namespace fx +{ + namespace Types + { + struct SizeProxy + { + std::optional mWidthRatio; + std::optional mHeightRatio; + std::optional mWidth; + std::optional mHeight; + + std::tuple get(int width, int height) const + { + int scaledWidth = width; + int scaledHeight = height; + + if (mWidthRatio) + scaledWidth = width * mWidthRatio.value(); + else if (mWidth) + scaledWidth = mWidth.value(); + + if (mHeightRatio > 0.f) + scaledHeight = height * mHeightRatio.value(); + else if (mHeight) + scaledHeight = mHeight.value(); + + return std::make_tuple(scaledWidth, scaledHeight); + } + }; + + struct RenderTarget + { + osg::ref_ptr mTarget = new osg::Texture2D; + SizeProxy mSize; + bool mMipMap = false; + }; + + template + struct Uniform + { + std::optional mValue; + T mDefault; + T mMin = std::numeric_limits::lowest(); + T mMax = std::numeric_limits::max(); + + using value_type = T; + + T getValue() const + { + return mValue.value_or(mDefault); + } + }; + + using Uniform_t = std::variant< + Uniform, + Uniform, + Uniform, + Uniform, + Uniform, + Uniform + >; + + enum SamplerType + { + Texture_1D, + Texture_2D, + Texture_3D + }; + + struct UniformBase + { + std::string mName; + std::string mHeader; + std::string mTechniqueName; + std::string mDescription; + + bool mStatic = true; + std::optional mSamplerType = std::nullopt; + double mStep; + + Uniform_t mData; + + template + T getValue() const + { + auto value = Settings::ShaderManager::get().getValue(mTechniqueName, mName); + + return value.value_or(std::get>(mData).getValue()); + } + + template + T getMin() const + { + return std::get>(mData).mMin; + } + + template + T getMax() const + { + return std::get>(mData).mMax; + } + + template + T getDefault() const + { + return std::get>(mData).mDefault; + } + + template + void setValue(const T& value) + { + std::visit([&, value](auto&& arg){ + using U = typename std::decay_t::value_type; + + if constexpr (std::is_same_v) + { + arg.mValue = value; + + if (mStatic) + Settings::ShaderManager::get().setValue(mTechniqueName, mName, value); + } + else + { + Log(Debug::Warning) << "Attempting to set uniform '" << mName << "' with wrong type"; + } + }, mData); + } + + void setUniform(osg::Uniform* uniform) + { + auto type = getType(); + if (!type || type.value() != uniform->getType()) + return; + + std::visit([&](auto&& arg) + { + const auto value = arg.getValue(); + uniform->set(value); + }, mData); + } + + std::optional getType() const + { + return std::visit([](auto&& arg) -> std::optional { + using T = typename std::decay_t::value_type; + + if constexpr (std::is_same_v) + return osg::Uniform::FLOAT_VEC2; + else if constexpr (std::is_same_v) + return osg::Uniform::FLOAT_VEC3; + else if constexpr (std::is_same_v) + return osg::Uniform::FLOAT_VEC4; + else if constexpr (std::is_same_v) + return osg::Uniform::FLOAT; + else if constexpr (std::is_same_v) + return osg::Uniform::INT; + else if constexpr (std::is_same_v) + return osg::Uniform::BOOL; + + return std::nullopt; + }, mData); + } + + std::optional getGLSL() + { + if (mSamplerType) + { + switch (mSamplerType.value()) + { + case Texture_1D: + return Misc::StringUtils::format("uniform sampler1D %s;", mName); + case Texture_2D: + return Misc::StringUtils::format("uniform sampler2D %s;", mName); + case Texture_3D: + return Misc::StringUtils::format("uniform sampler3D %s;", mName); + } + } + + bool useUniform = (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug || mStatic == false); + + return std::visit([&](auto&& arg) -> std::optional { + using T = typename std::decay_t::value_type; + + auto value = arg.getValue(); + + if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform vec2 %s;", mName); + + return Misc::StringUtils::format("const vec2 %s=vec2(%f,%f);", mName, value[0], value[1]); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform vec3 %s;", mName); + + return Misc::StringUtils::format("const vec3 %s=vec3(%f,%f,%f);", mName, value[0], value[1], value[2]); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform vec4 %s;", mName); + + return Misc::StringUtils::format("const vec4 %s=vec4(%f,%f,%f,%f);", mName, value[0], value[1], value[2], value[3]); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform float %s;", mName); + + return Misc::StringUtils::format("const float %s=%f;", mName, value); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform int %s;", mName); + + return Misc::StringUtils::format("const int %s=%i;", mName, value); + } + else if constexpr (std::is_same_v) + { + if (useUniform) + return Misc::StringUtils::format("uniform bool %s;", mName); + + return Misc::StringUtils::format("const bool %s=%s;", mName, value ? "true" : "false"); + } + + return std::nullopt; + + }, mData); + } + + }; + } +} + +#endif diff --git a/components/fx/widgets.cpp b/components/fx/widgets.cpp new file mode 100644 index 0000000000..2206fd8c7f --- /dev/null +++ b/components/fx/widgets.cpp @@ -0,0 +1,164 @@ +#include "widgets.hpp" + +#include + +namespace +{ + template + void createVectorWidget(const std::shared_ptr& uniform, MyGUI::Widget* client, fx::Widgets::UniformBase* base) + { + int height = client->getHeight(); + base->setSize(base->getSize().width, (base->getSize().height - height) + (height * T::num_components)); + client->setSize(client->getSize().width, height * T::num_components); + + for (int i = 0; i < T::num_components; ++i) + { + auto* widget = client->createWidget("MW_ValueEditNumber", {0, height * i, client->getWidth(), height}, MyGUI::Align::Default); + widget->setData(uniform, static_cast(i)); + base->addItem(widget); + } + } +} + +namespace fx +{ + namespace Widgets + { + void EditBool::setValue(bool value) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + mCheckbutton->setCaptionWithReplacing(value ? "#{sOn}" : "#{sOff}"); + + uniform->setValue(value); + } + + void EditBool::setValueFromUniform() + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + setValue(uniform->template getValue()); + } + + void EditBool::toDefault() + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + setValue(uniform->getDefault()); + } + + void EditBool::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mCheckbutton, "Checkbutton"); + + mCheckbutton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditBool::notifyMouseButtonClick); + } + + + void EditBool::notifyMouseButtonClick(MyGUI::Widget* sender) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + setValue(!uniform->getValue()); + } + + void UniformBase::init(const std::shared_ptr& uniform) + { + mLabel->setCaption(uniform->mName); + + if (uniform->mDescription.empty()) + { + mLabel->setUserString("ToolTipType", ""); + } + else + { + mLabel->setUserString("ToolTipType", "Layout"); + mLabel->setUserString("ToolTipLayout", "TextToolTip"); + mLabel->setUserString("Caption_Text", uniform->mDescription); + } + + std::visit([this, &uniform](auto&& arg) { + using T = typename std::decay_t::value_type; + + if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditNumber", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditNumber", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditBool", {0, 0, mClient->getWidth(), mClient->getHeight()}, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } + + mReset->eventMouseButtonClick += MyGUI::newDelegate(this, &UniformBase::notifyResetClicked); + + for (EditBase* base : mBases) + base->setValueFromUniform(); + + }, uniform->mData); + } + + void UniformBase::addItem(EditBase* item) + { + mBases.emplace_back(item); + } + + void UniformBase::toDefault() + { + for (EditBase* base : mBases) + { + if (base) + base->toDefault(); + } + } + + void UniformBase::notifyResetClicked(MyGUI::Widget* sender) + { + toDefault(); + } + + void UniformBase::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mReset, "Reset"); + assignWidget(mLabel, "Label"); + assignWidget(mClient, "Client"); + } + } +} \ No newline at end of file diff --git a/components/fx/widgets.hpp b/components/fx/widgets.hpp new file mode 100644 index 0000000000..f15e676fb6 --- /dev/null +++ b/components/fx/widgets.hpp @@ -0,0 +1,266 @@ +#ifndef OPENMW_COMPONENTS_FX_WIDGETS_H +#define OPENMW_COMPONENTS_FX_WIDGETS_H + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "technique.hpp" +#include "types.hpp" + +namespace Gui +{ + class AutoSizedTextBox; + class AutoSizedButton; +} + +namespace fx +{ + namespace Widgets + { + enum Index + { + None = -1, + Zero = 0, + One = 1, + Two = 2, + Three = 3 + }; + + class EditBase + { + public: + virtual ~EditBase() = default; + + void setData(const std::shared_ptr& uniform, Index index = None) + { + mUniform = uniform; + mIndex = index; + } + + virtual void setValueFromUniform() = 0; + + virtual void toDefault() = 0; + + protected: + std::weak_ptr mUniform; + Index mIndex; + }; + + class EditBool : public EditBase, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(EditBool) + + public: + void setValue(bool value); + void setValueFromUniform() override; + void toDefault() override; + + private: + void initialiseOverride() override; + void notifyMouseButtonClick(MyGUI::Widget* sender); + + MyGUI::Button* mCheckbutton; + }; + + template + class EditNumber : public EditBase, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(EditNumber) + + public: + EditNumber() : mLastPointerX(0) {} + + void setValue(T value) + { + mValue = value; + if constexpr (std::is_floating_point_v) + mValueLabel->setCaption(Misc::StringUtils::format("%.3f", mValue)); + else + mValueLabel->setCaption(std::to_string(mValue)); + + if (auto uniform = mUniform.lock()) + { + if constexpr (std::is_fundamental_v) + uniform->template setValue(mValue); + else + { + UType uvalue = uniform->template getValue(); + uvalue[mIndex] = mValue; + uniform->template setValue(uvalue); + } + } + } + + void setValueFromUniform() override + { + if (auto uniform = mUniform.lock()) + { + T value; + + if constexpr (std::is_fundamental_v) + value = uniform->template getValue(); + else + value = uniform->template getValue()[mIndex]; + + setValue(value); + } + } + + void toDefault() override + { + if (auto uniform = mUniform.lock()) + { + if constexpr (std::is_fundamental_v) + setValue(uniform->template getDefault()); + else + setValue(uniform->template getDefault()[mIndex]); + } + } + + private: + + void initialiseOverride() override + { + Base::initialiseOverride(); + + assignWidget(mDragger, "Dragger"); + assignWidget(mValueLabel, "Value"); + assignWidget(mButtonIncrease, "ButtonIncrease"); + assignWidget(mButtonDecrease, "ButtonDecrease"); + + mButtonIncrease->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNumber::notifyButtonClicked); + mButtonDecrease->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNumber::notifyButtonClicked); + + mDragger->eventMouseButtonPressed += MyGUI::newDelegate(this, &EditNumber::notifyMouseButtonPressed); + mDragger->eventMouseDrag += MyGUI::newDelegate(this, &EditNumber::notifyMouseButtonDragged); + mDragger->eventMouseWheel += MyGUI::newDelegate(this, &EditNumber::notifyMouseWheel); + } + + void notifyMouseWheel(MyGUI::Widget* sender, int rel) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + if (rel > 0) + increment(uniform->mStep); + else + increment(-uniform->mStep); + } + + void notifyMouseButtonDragged(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) + { + if (id != MyGUI::MouseButton::Left) + return; + + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + int delta = left - mLastPointerX; + + // allow finer tuning when shift is pressed + constexpr double scaling = 20.0; + T step = MyGUI::InputManager::getInstance().isShiftPressed() ? uniform->mStep / scaling : uniform->mStep; + + if (step == 0) + { + if constexpr (std::is_integral_v) + step = 1; + else + step = uniform->mStep; + } + + if (delta > 0) + increment(step); + else if (delta < 0) + increment(-step); + + mLastPointerX = left; + } + + void notifyMouseButtonPressed(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) + { + if (id != MyGUI::MouseButton::Left) + return; + + mLastPointerX = left; + } + + void increment(T step) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + if constexpr (std::is_fundamental_v) + setValue(std::clamp(uniform->template getValue() + step, uniform->template getMin(), uniform->template getMax())); + else + setValue(std::clamp(uniform->template getValue()[mIndex] + step, uniform->template getMin()[mIndex], uniform->template getMax()[mIndex])); + } + + void notifyButtonClicked(MyGUI::Widget* sender) + { + auto uniform = mUniform.lock(); + + if (!uniform) + return; + + if (sender == mButtonDecrease) + increment(-uniform->mStep); + else if (sender == mButtonIncrease) + increment(uniform->mStep); + } + + MyGUI::Button* mButtonDecrease; + MyGUI::Button* mButtonIncrease; + MyGUI::Widget* mDragger; + MyGUI::TextBox* mValueLabel; + T mValue; + + int mLastPointerX; + }; + + class EditNumberFloat4 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat4) }; + class EditNumberFloat3 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat3) }; + class EditNumberFloat2 : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat2) }; + class EditNumberFloat : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberFloat) }; + class EditNumberInt : public EditNumber { MYGUI_RTTI_DERIVED(EditNumberInt) }; + + class UniformBase final : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(UniformBase) + + public: + void init(const std::shared_ptr& uniform); + + void toDefault(); + + void addItem(EditBase* item); + + private: + + void notifyResetClicked(MyGUI::Widget* sender); + + void initialiseOverride() override; + + Gui::AutoSizedButton* mReset; + Gui::AutoSizedTextBox* mLabel; + MyGUI::Widget* mClient; + std::vector mBases; + }; + } +} + +#endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 8ddc3fd102..a2779109ac 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -318,7 +318,7 @@ namespace Resource , mApplyLightingToEnvMaps(false) , mLightingMethod(SceneUtil::LightingMethod::FFP) , mConvertAlphaTestToAlphaToCoverage(false) - , mDepthFormat(0) + , mSupportsNormalsRT(false) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -348,7 +348,7 @@ namespace Resource if (forceShadersForNode) shaderVisitor->setForceShaders(true); if (disableSoftParticles) - shaderVisitor->setOpaqueDepthTex(nullptr); + shaderVisitor->setOpaqueDepthTex(nullptr, nullptr); node->accept(*shaderVisitor); } @@ -368,16 +368,6 @@ namespace Resource return mClampLighting; } - void SceneManager::setDepthFormat(GLenum format) - { - mDepthFormat = format; - } - - GLenum SceneManager::getDepthFormat() const - { - return mDepthFormat; - } - void SceneManager::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; @@ -440,9 +430,9 @@ namespace Resource mConvertAlphaTestToAlphaToCoverage = convert; } - void SceneManager::setOpaqueDepthTex(osg::ref_ptr texture) + void SceneManager::setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong) { - mOpaqueDepthTex = texture; + mOpaqueDepthTex = { texturePing, texturePong }; } SceneManager::~SceneManager() @@ -930,7 +920,8 @@ namespace Resource shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); - shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex); + shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex[0], mOpaqueDepthTex[1]); + shaderVisitor->setSupportsNormalsRT(mSupportsNormalsRT); return shaderVisitor; } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index b3a42bac45..ba5c75f7ea 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -91,9 +91,6 @@ namespace Resource void setClampLighting(bool clamp); bool getClampLighting() const; - void setDepthFormat(GLenum format); - GLenum getDepthFormat() const; - /// @see ShaderVisitor::setAutoUseNormalMaps void setAutoUseNormalMaps(bool use); @@ -112,12 +109,13 @@ namespace Resource void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; - void setOpaqueDepthTex(osg::ref_ptr texture); + void setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong); enum class UBOBinding { // If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate - LightBuffer + LightBuffer, + PostProcessor }; void setLightingMethod(SceneUtil::LightingMethod method); SceneUtil::LightingMethod getLightingMethod() const; @@ -195,6 +193,9 @@ namespace Resource void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } + bool getSupportsNormalsRT() const { return mSupportsNormalsRT; } + private: Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); @@ -211,8 +212,8 @@ namespace Resource SceneUtil::LightingMethod mLightingMethod; SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; - GLenum mDepthFormat; - osg::ref_ptr mOpaqueDepthTex; + bool mSupportsNormalsRT; + std::array, 2> mOpaqueDepthTex; osg::ref_ptr mSharedStateManager; mutable std::mutex mSharedStateMutex; diff --git a/components/sceneutil/clearcolor.hpp b/components/sceneutil/clearcolor.hpp new file mode 100755 index 0000000000..e6e6468ecc --- /dev/null +++ b/components/sceneutil/clearcolor.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_CLEARCOLOR_H +#define OPENMW_COMPONENTS_SCENEUTIL_CLEARCOLOR_H + +#include +#include + +namespace SceneUtil +{ + class ClearColor : public osg::StateAttribute + { + public: + ClearColor() : mMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) {} + ClearColor(const osg::Vec4f& color, GLbitfield mask) : mColor(color), mMask(mask) {} + + ClearColor(const ClearColor& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mColor(copy.mColor), mMask(copy.mMask) {} + + META_StateAttribute(fx, ClearColor, static_cast(100)) + + int compare(const StateAttribute& sa) const override + { + COMPARE_StateAttribute_Types(ClearColor, sa); + + COMPARE_StateAttribute_Parameter(mColor); + COMPARE_StateAttribute_Parameter(mMask); + + return 0; + } + + void apply(osg::State& state) const override + { + glClearColor(mColor[0], mColor[1], mColor[2], mColor[3]); + glClear(mMask); + } + + private: + osg::Vec4f mColor; + GLbitfield mMask; + }; +} + +#endif diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp index c52bdf2ed2..f51c973389 100644 --- a/components/sceneutil/depth.cpp +++ b/components/sceneutil/depth.cpp @@ -44,18 +44,6 @@ namespace SceneUtil ); } - bool isFloatingPointDepthFormat(GLenum format) - { - constexpr std::array formats = { - GL_DEPTH_COMPONENT32F, - GL_DEPTH_COMPONENT32F_NV, - GL_DEPTH32F_STENCIL8, - GL_DEPTH32F_STENCIL8_NV, - }; - - return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); - } - bool isDepthFormat(GLenum format) { constexpr std::array formats = { diff --git a/components/sceneutil/depth.hpp b/components/sceneutil/depth.hpp index 67cf5bdb45..f7b1875206 100644 --- a/components/sceneutil/depth.hpp +++ b/components/sceneutil/depth.hpp @@ -45,9 +45,6 @@ namespace SceneUtil // Returns an orthographic projection matrix for use with a reversed z-buffer. osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far); - // Returns true if the GL format is a floating point depth format. - bool isFloatingPointDepthFormat(GLenum format); - // Returns true if the GL format is a depth format bool isDepthFormat(GLenum format); diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index 099425330a..4ab1b4c92a 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -33,7 +33,7 @@ namespace SceneUtil , mSamples(samples) , mGenerateMipmaps(generateMipmaps) , mColorBufferInternalFormat(Color::colorInternalFormat()) - , mDepthBufferInternalFormat(AutoDepth::depthInternalFormat()) + , mDepthBufferInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()) , mRenderOrderNum(renderOrderNum) , mStereoAwareness(stereoAwareness) { diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index b70a0e2481..52b7be1798 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -152,42 +152,6 @@ void GlowUpdater::setDuration(float duration) mDuration = duration; } -// Allows camera to render to a color and floating point depth texture with a multisampled framebuffer. -class AttachMultisampledDepthColorCallback : public SceneUtil::NodeCallback -{ -public: - AttachMultisampledDepthColorCallback(osg::Texture2D* colorTex, osg::Texture2D* depthTex, int samples, int colorSamples) - { - int width = colorTex->getTextureWidth(); - int height = colorTex->getTextureHeight(); - - osg::ref_ptr rbColor = new osg::RenderBuffer(width, height, colorTex->getInternalFormat(), samples, colorSamples); - osg::ref_ptr rbDepth = new osg::RenderBuffer(width, height, depthTex->getInternalFormat(), samples, colorSamples); - - mMsaaFbo = new osg::FrameBufferObject; - mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(rbColor)); - mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(rbDepth)); - - mFbo = new osg::FrameBufferObject; - mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorTex)); - mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthTex)); - } - - void operator()(osg::Node* node, osgUtil::CullVisitor* cv) - { - osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); - - renderStage->setMultisampleResolveFramebufferObject(mFbo); - renderStage->setFrameBufferObject(mMsaaFbo); - - traverse(node, cv); - } - -private: - osg::ref_ptr mFbo; - osg::ref_ptr mMsaaFbo; -}; - osg::Vec4f colourFromRGB(unsigned int clr) { osg::Vec4f colour(((clr >> 0) & 0xFF) / 255.0f, diff --git a/components/serialization/osgyaml.hpp b/components/serialization/osgyaml.hpp new file mode 100644 index 0000000000..866d1db2fe --- /dev/null +++ b/components/serialization/osgyaml.hpp @@ -0,0 +1,64 @@ +#ifndef OPENMW_COMPONENTS_SERIALIZATION_OSGYAML_H +#define OPENMW_COMPONENTS_SERIALIZATION_OSGYAML_H + +#include + +#include +#include +#include + +namespace Serialization +{ + template + YAML::Node encodeOSGVec(const OSGVec& rhs) + { + YAML::Node node; + for (int i = 0; i < OSGVec::num_components; ++i) + node.push_back(rhs[i]); + + return node; + } + + template + bool decodeOSGVec(const YAML::Node& node, OSGVec& rhs) + { + if (!node.IsSequence() || node.size() != OSGVec::num_components) + return false; + + for (int i = 0; i < OSGVec::num_components; ++i) + rhs[i] = node[i].as(); + + return true; + } +} + +namespace YAML +{ + + template<> + struct convert + { + static Node encode(const osg::Vec2f& rhs) { return Serialization::encodeOSGVec(rhs); } + + static bool decode(const Node& node, osg::Vec2f& rhs) { return Serialization::decodeOSGVec(node, rhs); } + }; + + template<> + struct convert + { + static Node encode(const osg::Vec3f& rhs) { return Serialization::encodeOSGVec(rhs); } + + static bool decode(const Node& node, osg::Vec3f& rhs) { return Serialization::decodeOSGVec(node, rhs); } + }; + + template<> + struct convert + { + static Node encode(const osg::Vec4f& rhs) { return Serialization::encodeOSGVec(rhs); } + + static bool decode(const Node& node, osg::Vec4f& rhs) { return Serialization::decodeOSGVec(node, rhs); } + }; + +} + +#endif \ No newline at end of file diff --git a/components/settings/shadermanager.hpp b/components/settings/shadermanager.hpp new file mode 100644 index 0000000000..322d6169b3 --- /dev/null +++ b/components/settings/shadermanager.hpp @@ -0,0 +1,174 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H +#define OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace Settings +{ + /* + * Manages the shader.yaml file which is auto-generated and lives next to settings.cfg. + * This YAML file is simply a mapping of technique name to a list of uniforms and their values. + * Currently only vec2f, vec3f, vec4f, int, and float uniforms are supported. + * + * config: + * TECHNIQUE: + * MY_FLOAT: 10.34 + * MY_VEC2: [0.23, 0.34] + * TECHNIQUE2: + * MY_VEC3: [0.22, 0.33, 0.20] + */ + class ShaderManager + { + public: + + enum class Mode + { + Normal, + Debug + }; + + ShaderManager() = default; + ShaderManager(ShaderManager const&) = delete; + void operator=(ShaderManager const&) = delete; + + static ShaderManager& get() + { + static ShaderManager instance; + return instance; + } + + Mode getMode() + { + return mMode; + } + + void setMode(Mode mode) + { + mMode = mode; + } + + const YAML::Node& getRoot() + { + return mData; + } + + template + bool setValue(const std::string& tname, const std::string& uname, const T& value) + { + if (mData.IsNull()) + { + Log(Debug::Warning) << "Failed setting " << tname << ", " << uname << " : shader settings failed to load"; + return false; + } + + mData["config"][tname][uname] = value; + return true; + } + + template + std::optional getValue(const std::string& tname, const std::string& uname) + { + if (mData.IsNull()) + { + Log(Debug::Warning) << "Failed getting " << tname << ", " << uname << " : shader settings failed to load"; + return std::nullopt; + } + + try + { + auto value = mData["config"][tname][uname]; + + if (!value) + return std::nullopt; + + return value.as(); + } + catch(const YAML::BadConversion& e) + { + Log(Debug::Warning) << "Failed retrieving " << tname << ", " << uname << " : mismatched types in config file."; + } + + return std::nullopt; + } + + bool load(const std::string& path) + { + mData = YAML::Null; + mPath = std::filesystem::path(path); + + Log(Debug::Info) << "Loading shader settings file: " << mPath; + + if (!std::filesystem::exists(mPath)) + { + std::ofstream fout(mPath); + if (!fout) + { + Log(Debug::Error) << "Failed creating shader settings file: " << mPath; + return false; + } + } + + try + { + mData = YAML::LoadFile(mPath.string()); + mData.SetStyle(YAML::EmitterStyle::Block); + + if (!mData["config"]) + mData["config"] = YAML::Node(); + + return true; + } + catch(const YAML::Exception& e) + { + Log(Debug::Error) << "Shader settings failed to load, " << e.msg; + } + + return false; + } + + bool save() + { + if (mData.IsNull()) + { + Log(Debug::Error) << "Shader settings failed to load, settings will not be saved: " << mPath; + return false; + } + + Log(Debug::Info) << "Saving shader settings file: " << mPath; + + YAML::Emitter out; + out.SetMapFormat(YAML::Block); + out << mData; + + std::ofstream fout(mPath.string()); + fout << out.c_str(); + + if (!fout) + { + Log(Debug::Error) << "Failed saving shader settings file: " << mPath; + return false; + } + + return true; + } + + private: + std::filesystem::path mPath; + YAML::Node mData; + Mode mMode = Mode::Normal; + }; +} + +#endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 8923fec666..4ef0d7a0ea 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -28,6 +29,50 @@ #include "removedalphafunc.hpp" #include "shadermanager.hpp" +namespace +{ + class OpaqueDepthAttribute : public osg::StateAttribute + { + public: + OpaqueDepthAttribute() = default; + + OpaqueDepthAttribute(const OpaqueDepthAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy, copyop), mTextures(copy.mTextures), mUnit(copy.mUnit) {} + + void setTexturesAndUnit(const std::array, 2>& textures, int unit) + { + mTextures = textures; + mUnit = unit; + } + + META_StateAttribute(Shader, OpaqueDepthAttribute, osg::StateAttribute::TEXTURE) + + int compare(const StateAttribute& sa) const override + { + COMPARE_StateAttribute_Types(OpaqueDepthAttribute, sa); + + COMPARE_StateAttribute_Parameter(mTextures); + + return 0; + } + + void apply(osg::State& state) const override + { + auto index = state.getFrameStamp()->getFrameNumber() % 2; + + if (!mTextures[index]) + return; + + state.setActiveTextureUnit(mUnit); + state.applyTextureAttribute(mUnit, mTextures[index]); + } + + private: + mutable std::array, 2> mTextures; + int mUnit; + }; +} + namespace Shader { /** @@ -165,6 +210,7 @@ namespace Shader , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) , mConvertAlphaTestToAlphaToCoverage(false) + , mSupportsNormalsRT(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) @@ -611,6 +657,14 @@ namespace Shader defineMap["endLight"] = "0"; } + if (reqs.mAlphaBlend && mSupportsNormalsRT) + { + if (reqs.mSoftParticles) + defineMap["disableNormals"] = "1"; + else + writableStateSet->setAttribute(new osg::Disablei(GL_BLEND, 1)); + } + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); // This disables the deprecated fixed-function alpha test @@ -629,7 +683,7 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } - if (reqs.mSoftParticles) + if (reqs.mSoftParticles && mOpaqueDepthTex.front()) { osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); @@ -639,14 +693,18 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); addedState->addUniform("particleSize"); - writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); + constexpr int unit = 2; + + writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unit)); addedState->addUniform("opaqueDepthTex"); - writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); - addedState->setTextureAttributeAndModes(2, mOpaqueDepthTex); + osg::ref_ptr opaqueDepthAttr = new OpaqueDepthAttribute; + opaqueDepthAttr->setTexturesAndUnit(mOpaqueDepthTex, unit); + writableStateSet->setAttributeAndModes(opaqueDepthAttr, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + addedState->setAttributeAndModes(opaqueDepthAttr); } - defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; + defineMap["softParticles"] = reqs.mSoftParticles && mOpaqueDepthTex.front() ? "1" : "0"; Stereo::Manager::instance().shaderStereoDefines(defineMap); @@ -840,7 +898,7 @@ namespace Shader { pushRequirements(drawable); - if (partsys && mOpaqueDepthTex) + if (partsys) { mRequirements.back().mSoftParticles = true; mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum; @@ -915,9 +973,9 @@ namespace Shader mConvertAlphaTestToAlphaToCoverage = convert; } - void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr texture) + void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong) { - mOpaqueDepthTex = texture; + mOpaqueDepthTex = { texturePing, texturePong }; } ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 72dec05b5e..cd1ca421d9 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_SHADERVISITOR_H #define OPENMW_COMPONENTS_SHADERVISITOR_H +#include + #include #include #include @@ -46,7 +48,9 @@ namespace Shader void setConvertAlphaTestToAlphaToCoverage(bool convert); - void setOpaqueDepthTex(osg::ref_ptr texture); + void setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong); + + void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } void apply(osg::Node& node) override; @@ -73,6 +77,8 @@ namespace Shader bool mConvertAlphaTestToAlphaToCoverage; + bool mSupportsNormalsRT; + ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; @@ -87,7 +93,7 @@ namespace Shader bool mShaderRequired; int mColorMode; - + bool mMaterialOverridden; bool mAlphaTestOverridden; bool mAlphaBlendOverridden; @@ -116,7 +122,7 @@ namespace Shader bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); osg::ref_ptr mProgramTemplate; - osg::ref_ptr mOpaqueDepthTex; + std::array, 2> mOpaqueDepthTex; }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor diff --git a/components/std140/ubo.hpp b/components/std140/ubo.hpp new file mode 100644 index 0000000000..6154cd32ac --- /dev/null +++ b/components/std140/ubo.hpp @@ -0,0 +1,162 @@ +#ifndef COMPONENTS_STD140_UBO_H +#define COMPONENTS_STD140_UBO_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace std140 +{ + struct Mat4 + { + using Value = osg::Matrixf; + Value mValue; + static constexpr size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "mat4"; + }; + + struct Vec4 + { + using Value = osg::Vec4f; + Value mValue; + static constexpr size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "vec4"; + }; + + struct Vec3 + { + using Value = osg::Vec3f; + Value mValue; + static constexpr std::size_t sAlign = 4 * sizeof(osg::Vec3f::value_type); + static constexpr std::string_view sTypeName = "vec3"; + }; + + struct Vec2 + { + using Value = osg::Vec2f; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "vec2"; + }; + + struct Float + { + using Value = float; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "float"; + }; + + struct Int + { + using Value = std::int32_t; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "int"; + }; + + struct UInt + { + using Value = std::uint32_t; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "uint"; + }; + + struct Bool + { + using Value = std::int32_t; + Value mValue; + static constexpr std::size_t sAlign = sizeof(Value); + static constexpr std::string_view sTypeName = "bool"; + }; + + template + class UBO + { + private: + + template + struct contains : std::bool_constant<(std::is_base_of_v || ...)> { }; + + static_assert((contains() && ...)); + + static constexpr size_t roundUpRemainder(size_t x, size_t multiple) + { + size_t remainder = x % multiple; + if (remainder == 0) + return 0; + return multiple - remainder; + } + + template + static constexpr std::size_t getOffset() + { + bool found = false; + std::size_t size = 0; + (( + found = found || std::is_same_v, + size += (found ? 0 : sizeof(typename CArgs::Value) + roundUpRemainder(size, CArgs::sAlign)) + ) , ...); + return size + roundUpRemainder(size, T::sAlign); + } + + public: + + static constexpr size_t getGPUSize() + { + std::size_t size = 0; + ((size += (sizeof(typename CArgs::Value) + roundUpRemainder(size, CArgs::sAlign))), ...); + return size; + } + + static std::string getDefinition(const std::string& name) + { + std::string structDefinition = "struct " + name + " {\n"; + ((structDefinition += (" " + std::string(CArgs::sTypeName) + " " + std::string(CArgs::sName) + ";\n")), ...); + return structDefinition + "};"; + } + + using BufferType = std::array; + using TupleType = std::tuple; + + template + typename T::Value& get() + { + return std::get(mData).mValue; + } + + template + const typename T::Value& get() const + { + return std::get(mData).mValue; + } + + void copyTo(BufferType& buffer) const + { + const auto copy = [&] (const auto& v) { + static_assert(std::is_standard_layout_v>); + constexpr std::size_t offset = getOffset>(); + std::memcpy(buffer.data() + offset, &v.mValue, sizeof(v.mValue)); + }; + + std::apply([&] (const auto& ... v) { (copy(v) , ...); }, mData); + } + + const auto& getData() const + { + return mData; + } + + private: + std::tuple mData; + }; +} + +#endif diff --git a/components/stereo/multiview.cpp b/components/stereo/multiview.cpp index 56c1ada349..de48bf6d17 100644 --- a/components/stereo/multiview.cpp +++ b/components/stereo/multiview.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -322,10 +321,7 @@ namespace Stereo for (unsigned i = 0; i < 2; i++) { if (mSamples > 1) - { - mMsaaColorTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat); - mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMsaaColorTexture[i])); - } + mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples))); mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); } @@ -351,10 +347,7 @@ namespace Stereo for (unsigned i = 0; i < 2; i++) { if (mSamples > 1) - { - mMsaaDepthTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat); - mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMsaaDepthTexture[i])); - } + mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples))); mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); } @@ -381,6 +374,11 @@ namespace Stereo return mMultiviewColorTexture; } + osg::Texture2DArray* MultiviewFramebuffer::multiviewDepthBuffer() + { + return mMultiviewDepthTexture; + } + osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i) { return mColorTexture[i]; @@ -451,22 +449,6 @@ namespace Stereo return texture; } - osg::Texture2DMultisample* MultiviewFramebuffer::createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat) - { - osg::Texture2DMultisample* texture = new osg::Texture2DMultisample; - texture->setTextureSize(mWidth, mHeight); - texture->setNumSamples(mSamples); - texture->setSourceFormat(sourceFormat); - texture->setSourceType(sourceType); - texture->setInternalFormat(internalFormat); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); - return texture; - } - osg::Texture2DArray* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat) { osg::Texture2DArray* textureArray = new osg::Texture2DArray; diff --git a/components/stereo/multiview.hpp b/components/stereo/multiview.hpp index 298d042ffc..cfdc721d1a 100644 --- a/components/stereo/multiview.hpp +++ b/components/stereo/multiview.hpp @@ -13,7 +13,6 @@ namespace osg class FrameBufferObject; class Texture; class Texture2D; - class Texture2DMultisample; class Texture2DArray; } @@ -50,6 +49,7 @@ namespace Stereo osg::FrameBufferObject* layerFbo(int i); osg::FrameBufferObject* layerMsaaFbo(int i); osg::Texture2DArray* multiviewColorBuffer(); + osg::Texture2DArray* multiviewDepthBuffer(); osg::Texture2D* layerColorBuffer(int i); osg::Texture2D* layerDepthBuffer(int i); @@ -62,7 +62,6 @@ namespace Stereo private: osg::Texture2D* createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat); - osg::Texture2DMultisample* createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat); osg::Texture2DArray* createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat); int mWidth; @@ -76,9 +75,7 @@ namespace Stereo osg::ref_ptr mMultiviewColorTexture; osg::ref_ptr mMultiviewDepthTexture; std::array, 2> mColorTexture; - std::array, 2> mMsaaColorTexture; std::array, 2> mDepthTexture; - std::array, 2> mMsaaDepthTexture; }; } diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index 49ae685e6b..41c05b8785 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -17,7 +17,6 @@ namespace osg { class FrameBufferObject; class Texture2D; - class Texture2DMultisample; class Texture2DArray; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index d57b73768f..badca42977 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -180,7 +180,7 @@ std::vector > ChunkManager::createPasses(float chunk float blendmapScale = mStorage->getBlendmapScale(chunkSize); - return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); + return ::Terrain::createPasses(useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry) @@ -268,7 +268,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve layer.mDiffuseMap = compositeMap->mTexture; layer.mParallax = false; layer.mSpecular = false; - geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); + geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager, std::vector(1, layer), std::vector >(), 1.f, 1.f)); } else { diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 8e892a08c8..1c6770e6bc 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include +#include #include #include @@ -194,9 +196,10 @@ namespace namespace Terrain { - std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector &layers, + std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { + auto& shaderManager = sceneManager->getShaderManager(); std::vector > passes; unsigned int blendmapIndex = 0; @@ -209,6 +212,8 @@ namespace Terrain if (!blendmaps.empty()) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + if (sceneManager->getSupportsNormalsRT()) + stateset->setAttribute(new osg::Disablei(GL_BLEND, 1)); stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin"); if (!firstLayer) { @@ -251,18 +256,18 @@ namespace Terrain defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; - + defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0"; Stereo::Manager::instance().shaderStereoDefines(defineMap); - osg::ref_ptr vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); - osg::ref_ptr fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); + osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); + osg::ref_ptr fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); if (!vertexShader || !fragmentShader) { // Try again without shader. Error already logged by above - return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); + return createPasses(false, sceneManager, layers, blendmaps, blendmapScale, layerTileSize); } - stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); + stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader)); stateset->addUniform(UniformCollection::value().mColorMode); } else diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 5f78af6a06..d5ef40a29e 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -10,9 +10,9 @@ namespace osg class Texture2D; } -namespace Shader +namespace Resource { - class ShaderManager; + class SceneManager; } namespace Terrain @@ -26,7 +26,7 @@ namespace Terrain bool mSpecular; }; - std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, + std::vector > createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index 971ac15b39..faefe30dfe 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -14,6 +14,8 @@ namespace VFS virtual ~File() {} virtual Files::IStreamPtr open() = 0; + + virtual std::string getPath() = 0; }; class Archive diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 1bf9cf4381..a52104efd7 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -15,6 +15,8 @@ namespace VFS Files::IStreamPtr open() override; + std::string getPath() override { return mInfo->name(); } + const Bsa::BSAFile::FileStruct* mInfo; Bsa::BSAFile* mFile; }; @@ -26,6 +28,8 @@ namespace VFS Files::IStreamPtr open() override; + std::string getPath() override { return mInfo->name(); } + const Bsa::BSAFile::FileStruct* mInfo; Bsa::CompressedBSAFile* mCompressedFile; }; diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index 70463d32f2..fa9b50edc5 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -13,6 +13,8 @@ namespace VFS Files::IStreamPtr open() override; + std::string getPath() override { return mPath; } + private: std::string mPath; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index faebc782aa..8a43eda5ee 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -105,6 +105,17 @@ namespace VFS return {}; } + std::string Manager::getAbsoluteFileName(const std::string& name) const + { + std::string normalized = name; + normalize_path(normalized, mStrict); + + std::map::const_iterator found = mIndex.find(normalized); + if (found == mIndex.end()) + throw std::runtime_error("Resource '" + normalized + "' not found"); + return found->second->getPath(); + } + namespace { bool startsWith(std::string_view text, std::string_view start) diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 8568e8e784..98acfb7955 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -90,6 +90,11 @@ namespace VFS /// @note May be called from any thread once the index has been built. RecursiveDirectoryRange getRecursiveDirectoryIterator(const std::string& path) const; + /// Retrieve the absolute path to the file + /// @note Throws an exception if the file can not be found. + /// @note May be called from any thread once the index has been built. + std::string getAbsoluteFileName(const std::string& name) const; + private: bool mStrict; diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index aa6ff1d96f..9aa409f784 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -7,4 +7,5 @@ Reference Material modding/index lua-scripting/index + postprocessing/index documentationHowTo diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 0c850d3676..c3b77a15ee 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -20,6 +20,7 @@ Lua API reference openmw_input openmw_ui openmw_camera + openmw_postprocessing openmw_aux_calendar openmw_aux_util openmw_aux_time @@ -46,35 +47,37 @@ It can not be overloaded even if there is a lua file with the same name. The list of available packages is different for global and for local scripts. Player scripts are local scripts that are attached to a player. -+---------------------------------------------------------+--------------------+---------------------------------------------------------------+ -| Package | Can be used | Description | -+=========================================================+====================+===============================================================+ -|:ref:`openmw.interfaces